Skip to content
Open
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
116 changes: 59 additions & 57 deletions packages/provider-core/src/server/managedProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -712,62 +712,64 @@ describe("makeManagedServerProvider", () => {
).pipe(Effect.provide(layerAlwaysRunTest)),
);

it.effect("keeps sparse usage unavailable after probe failure until a full probe recovers", () =>
Effect.scoped(
Effect.gen(function* () {
const failedLimits = {
checkedAt: "2026-04-10T00:00:01.000Z",
windows: [],
unavailable: { reason: "probeFailed", message: "Usage probe timed out." },
} as const;
const probeSnapshot = yield* Ref.make<ServerProvider>({
...refreshedSnapshot,
usageLimits: failedLimits,
});
const provider = yield* makeManagedServerProvider<TestSettings>({
resolveMaintenance: () => Effect.succeed(maintenanceCapabilities),
getSettings: Effect.succeed({ enabled: true }),
streamSettings: Stream.empty,
haveSettingsChanged: (previous, next) => previous.enabled !== next.enabled,
initialSnapshot: () => Effect.succeed(initialSnapshot),
checkProvider: Ref.get(probeSnapshot),
refreshOnInterval: false,
});
yield* Stream.take(provider.streamChanges, 1).pipe(Stream.runDrain);

const weekly = {
id: "seven_day",
kind: "weekly",
label: "Weekly",
usedPercent: 20,
} as const;
yield* provider.applyUsageLimits({
checkedAt: "2026-04-10T00:00:02.000Z",
windows: [weekly],
});
const partial = yield* provider.getSnapshot;
assert.deepStrictEqual(partial.usageLimits, {
checkedAt: "2026-04-10T00:00:02.000Z",
windows: [weekly],
unavailable: failedLimits.unavailable,
});
yield* provider.applyUsageLimits({
checkedAt: "2026-04-10T00:00:03.000Z",
windows: [{ ...weekly }],
});
assert.strictEqual(yield* provider.getSnapshot, partial);
assert.deepStrictEqual((yield* provider.refresh).usageLimits, failedLimits);

const completeLimits = {
checkedAt: "2026-04-10T00:00:04.000Z",
windows: [
{ id: "five_hour", kind: "session", label: "Session", usedPercent: 10 },
{ ...weekly, usedPercent: 25 },
],
} as const;
yield* Ref.set(probeSnapshot, { ...refreshedSnapshot, usageLimits: completeLimits });
assert.deepStrictEqual((yield* provider.refresh).usageLimits, completeLimits);
}),
).pipe(Effect.provide(layerAlwaysRunTest)),
it.effect(
"keeps sparse usage marked unavailable through probe failures until a full probe recovers",
() =>
Effect.scoped(
Effect.gen(function* () {
const failedLimits = {
checkedAt: "2026-04-10T00:00:01.000Z",
windows: [],
unavailable: { reason: "probeFailed", message: "Usage probe timed out." },
} as const;
const probeSnapshot = yield* Ref.make<ServerProvider>({
...refreshedSnapshot,
usageLimits: failedLimits,
});
const provider = yield* makeManagedServerProvider<TestSettings>({
resolveMaintenance: () => Effect.succeed(maintenanceCapabilities),
getSettings: Effect.succeed({ enabled: true }),
streamSettings: Stream.empty,
haveSettingsChanged: (previous, next) => previous.enabled !== next.enabled,
initialSnapshot: () => Effect.succeed(initialSnapshot),
checkProvider: Ref.get(probeSnapshot),
refreshOnInterval: false,
});
yield* Stream.take(provider.streamChanges, 1).pipe(Stream.runDrain);

const weekly = {
id: "seven_day",
kind: "weekly",
label: "Weekly",
usedPercent: 20,
} as const;
yield* provider.applyUsageLimits({
checkedAt: "2026-04-10T00:00:02.000Z",
windows: [weekly],
});
const partial = yield* provider.getSnapshot;
assert.deepStrictEqual(partial.usageLimits, {
checkedAt: "2026-04-10T00:00:02.000Z",
windows: [weekly],
unavailable: failedLimits.unavailable,
});
yield* provider.applyUsageLimits({
checkedAt: "2026-04-10T00:00:03.000Z",
windows: [{ ...weekly }],
});
assert.strictEqual(yield* provider.getSnapshot, partial);
assert.deepStrictEqual((yield* provider.refresh).usageLimits, partial.usageLimits);

const completeLimits = {
checkedAt: "2026-04-10T00:00:04.000Z",
windows: [
{ id: "five_hour", kind: "session", label: "Session", usedPercent: 10 },
{ ...weekly, usedPercent: 25 },
],
} as const;
yield* Ref.set(probeSnapshot, { ...refreshedSnapshot, usageLimits: completeLimits });
assert.deepStrictEqual((yield* provider.refresh).usageLimits, completeLimits);
}),
).pipe(Effect.provide(layerAlwaysRunTest)),
);
});
9 changes: 9 additions & 0 deletions packages/provider-core/src/server/usageLimits.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,5 +118,14 @@ describe("resolveUsageLimitsAfterProbe", () => {
expect(resolveUsageLimitsAfterProbe({ published, probed: failed })).toBe(published);
expect(resolveUsageLimitsAfterProbe({ published, probed: unsupported })).toBe(unsupported);
expect(resolveUsageLimitsAfterProbe({ published: undefined, probed: failed })).toBe(failed);
const streamed = {
checkedAt,
windows: [weekly],
unavailable: { reason: "probeFailed" as const },
};
expect(resolveUsageLimitsAfterProbe({ published: streamed, probed: failed })).toBe(streamed);
expect(
resolveUsageLimitsAfterProbe({ published: { ...streamed, windows: [] }, probed: failed }),
).toBe(failed);
});
});
7 changes: 6 additions & 1 deletion packages/provider-core/src/server/usageLimits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,12 @@ export function resolveUsageLimitsAfterProbe(input: {
readonly probed: ServerProviderUsageLimits | undefined;
}): ServerProviderUsageLimits | undefined {
const { published, probed } = input;
if (probed?.unavailable?.reason === "probeFailed" && published && !published.unavailable) {
// Probes can fail permanently (org-disabled nonessential traffic); keep streamed windows.
if (
probed?.unavailable?.reason === "probeFailed" &&
published &&
(!published.unavailable || published.windows.length > 0)
) {
return published;
}
if (
Expand Down
87 changes: 54 additions & 33 deletions packages/shared/src/usageLimits.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
paceRemainingPercent,
usesChatGptSharing,
resolveNewThreadModelSelection,
limitsNotice,
} from "./usageLimits.ts";

const now = Date.parse("2026-09-03T12:00:00.000Z");
Expand Down Expand Up @@ -188,41 +189,38 @@ describe("pools", () => {
auth: { status: "authenticated", email: "Same@example.com" },
usageLimits: { checkedAt, windows: [{ ...window, usedPercent: 40 }] },
});
const input = new Map([
[EnvironmentId.make("env-a"), { ...laptop, serverConfig: { providers: [native] } }],
[
EnvironmentId.make("env-b"),
{
entry: { target: { label: "Desktop" } },
serverConfig: {
providers: [
{
...native,
usageLimits: {
checkedAt: "2026-09-03T11:30:00.000Z",
windows: [{ ...window, usedPercent: 55 }],
const newer: NonNullable<ServerProvider["usageLimits"]> = {
checkedAt: "2026-09-03T11:30:00.000Z",
windows: [{ ...window, usedPercent: 55 }],
};
const inputWith = (newerLimits: NonNullable<ServerProvider["usageLimits"]>) =>
new Map([
[EnvironmentId.make("env-a"), { ...laptop, serverConfig: { providers: [native] } }],
[
EnvironmentId.make("env-b"),
{
entry: { target: { label: "Desktop" } },
serverConfig: {
providers: [{ ...native, usageLimits: newerLimits }],
usageLimitSources: [
{
...source,
accounts: [
{
id: "claude-same@example.com.json",
driver: claude,
email: "same@example.com",
plan: "Claude Subscription",
usageLimits: { checkedAt, windows: [{ ...window, usedPercent: 10 }] },
},
],
},
},
],
usageLimitSources: [
{
...source,
accounts: [
{
id: "claude-same@example.com.json",
driver: claude,
email: "same@example.com",
plan: "Claude Subscription",
usageLimits: { checkedAt, windows: [{ ...window, usedPercent: 10 }] },
},
],
},
],
],
},
},
},
],
]);
const accounts = collectLimitAccounts(input);
],
]);
const accounts = collectLimitAccounts(inputWith(newer));
expect(accounts).toHaveLength(1);
expect(accounts[0]).toMatchObject({
key: "env-a:claude",
Expand All @@ -234,6 +232,11 @@ describe("pools", () => {
],
});
expect(accounts[0]?.limits.windows[0]?.usedPercent).toBe(55);
const [clean] = collectLimitAccounts(
inputWith({ ...newer, unavailable: { reason: "probeFailed" } }),
);
expect(clean?.limits).toMatchObject({ windows: [{ usedPercent: 40 }] });
expect(clean?.limits.unavailable).toBeUndefined();
});

it("uses the freshest reported plan even when the newest quota has no plan", () => {
Expand Down Expand Up @@ -1346,6 +1349,24 @@ describe("/usage-limits", () => {
),
).toEqual(["hub:duplicate", "hub:oss"]);
const failed = provider({ usageLimits: { ...limits, unavailable: { reason: "probeFailed" } } });
expect(limitsNotice(failed.usageLimits!)).toBeNull();
expect(limitsNotice({ ...failed.usageLimits!, windows: [] })).toBe("Could not read limits.");
const newerEmpty = provider({
usageLimits: {
checkedAt: "2026-09-03T11:59:00.000Z",
windows: [],
unavailable: { reason: "probeFailed" },
},
});
for (const duplicates of [
[failed, newerEmpty],
[newerEmpty, failed],
]) {
expect(
collectProviderUsageLimits(selected.instanceId, duplicates, [], now)?.accounts[0]?.limits
.windows,
).toEqual(limits.windows);
}
expect(
collectProviderUsageLimits(selected.instanceId, [failed], sources, now)?.accounts.map(
(account) => account.id,
Expand Down
17 changes: 11 additions & 6 deletions packages/shared/src/usageLimits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,8 @@ export function collectLimitAccounts(presentations: LimitPresentations): readonl
!previous.environments.some((seen) => seen.environmentId === candidate.environmentId),
),
];
const winner = fresher ? next : previous;
const byRank = readRank(next.limits) - readRank(previous.limits);
const winner = byRank > 0 || (byRank === 0 && fresher) ? next : previous;
const creditSource = creditSources.get(key);
// A hub e-mail names the account better than the driver's default instance name.
const hubEmail = [previous, next].some(
Expand Down Expand Up @@ -618,12 +619,17 @@ export function limitsNotice(limits: ServerProviderUsageLimits): string | null {
if (limits.unavailable?.reason === "unsupported") {
return limits.unavailable.message ?? "This account has no subscription limits.";
}
if (limits.unavailable?.reason === "probeFailed") {
if (limits.unavailable?.reason === "probeFailed" && limits.windows.length === 0) {
return limits.unavailable.message ?? "Could not read limits.";
}
return limits.windows.length === 0 ? "No limits reported." : null;
}

function readRank(limits: ServerProviderUsageLimits): number {
if (limitsNotice(limits) !== null) return 0;
return limits.unavailable ? 1 : 2;
}

export function remainingPercent(window: ServerProviderUsageWindow): number {
return Math.round(100 - Math.max(0, Math.min(100, window.usedPercent)));
}
Expand Down Expand Up @@ -772,12 +778,11 @@ export function collectProviderUsageLimits(
const snapshot = { provider, limits: provider.usageLimits };
const key = resolveKey(nativeReport(provider)) ?? `instance:${provider.instanceId}`;
const previous = nativeByAccount.get(key);
const previousUsable = previous ? limitsNotice(previous.usage.limits) === null : false;
const usable = limitsNotice(snapshot.limits) === null;
const rank = readRank(snapshot.limits);
const providerWins =
!previous ||
(usable && !previousUsable) ||
(usable === previousUsable &&
rank > readRank(previous.usage.limits) ||
(rank === readRank(previous.usage.limits) &&
Date.parse(snapshot.limits.checkedAt) > Date.parse(previous.usage.limits.checkedAt));
const previousCredit = previous?.credits;
const credits =
Expand Down
Loading