Skip to content
Closed
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
4 changes: 3 additions & 1 deletion src/server/request-log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1717,8 +1717,10 @@ export function noteProviderAttemptSend(
if (attempt && attempt.sendCount > 0 && attemptAccountChanged(previous, next, attempt.provider, logCtx.provider)) {
// An input estimate is not evidence that a failed send used that many tokens.
delete attempt.inputTokenEstimate;
// An account-scoped recovery kind names the status the sealed account answered.
finishRequestAttempt(attempt, attempt.status >= 100 ? attempt.status
: recovery === "key-401" ? 401 : recovery?.includes("429") ? 429 : 502,
: recovery === "key-401" || recovery === "oauth-401" ? 401 : recovery === "oauth-account-403" ? 403
: recovery === "oauth-account-402" ? 402 : recovery?.includes("429") ? 429 : 502,
Date.now() - (logCtx.activeAttemptStartedAt ?? Date.now()), attempt.usage);
// This attempt is being sealed because a NAMED recovery rejected it, so the recovery kind
// is direct evidence here rather than an inference from history. Without this the sealed
Expand Down
11 changes: 11 additions & 0 deletions tests/usage/key-attribution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,17 @@ describe("key attempt accounting", () => {
expect(active).toMatchObject({ ordinal: 2, provider: secondProvider,
sendCount: 1, usage: { inputTokens: 50, outputTokens: 5 } });
});
test.each([["oauth-account-403", 403], ["oauth-401", 401], ["oauth-account-402", 402]] as const)(
"an account rotation after %s seals the first row with the status it answered", (recovery, status) => {
const oauth = { adapter: "anthropic-messages", authMode: "oauth" as const, baseUrl: "https://example.test" };
const active = beginRequestAttempt(1, "anthropic", "model", "anthropic-messages");
const ctx: RequestLogContext = { provider: formatAnthropicProviderForLog("anthropic", "account-a"),
model: "model", activeAttempt: active, activeAttemptStartedAt: Date.now(), attempts: [active] };
noteProviderAttemptSend(ctx, "anthropic", oauth, undefined);
ctx.provider = formatAnthropicProviderForLog("anthropic", "account-b");
noteProviderAttemptSend(ctx, "anthropic", oauth, undefined, recovery);
expect(ctx.attempts?.[0]).toMatchObject({ ordinal: 1, status });
});
test("wire snapshots replace the current send against a pre-send baseline", () => {
const active = beginRequestAttempt(1, "test", "model", "openai-chat");
const ctx: RequestLogContext = { provider: "test", model: "model", activeAttempt: active, attempts: [active] };
Expand Down
Loading