Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 34 additions & 17 deletions apps/server/src/provider/providerPoolSnapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,23 +99,40 @@ describe("withProviderPools", () => {
assert.deepEqual(shared.at(-1)?.continuation, { groupKey: "codex:home:shared" });
});

it("reports the healthiest member's status and auth", () => {
const published = withProviderPools({
providers: [
provider("claudeAgent", claude, {
status: "error",
auth: { status: "unauthenticated" },
checkedAt: "2026-10-19T00:00:01.000Z",
}),
provider("claudeAgent_work", claude, { checkedAt: "2026-10-19T00:00:02.000Z" }),
],
providerPools: pools({ claudeAgent: ["claudeAgent", "claudeAgent_work"] }),
});
const pool = published.at(-1);
assert.equal(pool?.status, "ready");
assert.equal(pool?.auth.status, "authenticated");
assert.equal(pool?.checkedAt, "2026-10-19T00:00:02.000Z");
});
it.each([false, true])(
"reports the healthiest member's catalog and health (unavailable: %s)",
(unavailable) => {
const published = withProviderPools({
providers: [
provider("claudeAgent", claude, {
status: "error",
auth: { status: "unauthenticated" },
checkedAt: "2026-10-19T00:00:01.000Z",
...(unavailable
? {
availability: "unavailable",
enabled: false,
installed: false,
models: [],
slashCommands: [],
}
: {}),
}),
provider("claudeAgent_work", claude, { checkedAt: "2026-10-19T00:00:02.000Z" }),
],
providerPools: pools({ claudeAgent: ["claudeAgent", "claudeAgent_work"] }),
});
const pool = published.at(-1);
assert.equal(pool?.status, "ready");
assert.equal(pool?.auth.status, "authenticated");
assert.equal(pool?.checkedAt, "2026-10-19T00:00:02.000Z");
assert.deepEqual(
pool?.models.map((entry) => entry.slug),
["opus", "sonnet"],
);
assert.deepEqual(pool?.slashCommands, [{ name: "compact" }, { name: "review" }]);
},
);

it("keeps the pool and member identities when only one account remains enabled", () => {
const providers = [personal, provider("claudeAgent_work", claude, { enabled: false })];
Expand Down
17 changes: 11 additions & 6 deletions apps/server/src/provider/providerPoolSnapshot.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
isProviderAvailable,
PROVIDER_DISPLAY_NAMES,
resolveActiveProviderPools,
type ActiveProviderPool,
Expand Down Expand Up @@ -64,12 +65,16 @@ function makePoolEntry(
members: ReadonlyArray<ServerProvider>,
): ServerProvider {
const healthiest = healthiestMember(members);
const slugs = [...new Set(members.flatMap((member) => member.models.map((model) => model.slug)))];
// Unavailable placeholders have no catalog and cannot receive failover.
const catalogMembers = members.filter(isProviderAvailable);
const slugs = [
...new Set(catalogMembers.flatMap((member) => member.models.map((model) => model.slug))),
];
// A failover re-sends the run's options to another member, so a model is
// shared only when every member accepts the same options for it.
// shared only when every available member accepts the same options for it.
const sharedSlugs = new Set(
slugs.filter((slug) => {
const signatures = members.map((member) => {
const signatures = catalogMembers.map((member) => {
const model = member.models.find((candidate) => candidate.slug === slug);
return model === undefined ? undefined : optionSignature(model);
});
Expand All @@ -78,7 +83,7 @@ function makePoolEntry(
);
}),
);
const models = members
const models = catalogMembers
.flatMap((member) => member.models)
.filter((model, index, all) => {
if (!sharedSlugs.has(model.slug)) return false;
Expand Down Expand Up @@ -114,8 +119,8 @@ function makePoolEntry(
auth: healthiest.auth,
checkedAt: healthiest.checkedAt,
models,
slashCommands: intersectByName(members.map((member) => member.slashCommands)),
skills: intersectByName(members.map((member) => member.skills)),
slashCommands: intersectByName(catalogMembers.map((member) => member.slashCommands)),
skills: intersectByName(catalogMembers.map((member) => member.skills)),
pool: {
role: "pool",
memberInstanceIds: pool.memberInstanceIds,
Expand Down
Loading