@oh-my-pi/pi-ai 17.2.0 → 17.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.2.2] - 2026-07-31
6
+
7
+ ### Added
8
+
9
+ - Added support for the `gmi-cloud` provider registry, including API-key paste login validation and integration with `@oh-my-pi/pi-catalog`.
10
+
11
+ ### Changed
12
+
13
+ - Updated `AuthStorage.redeemResetCredit` to prioritize spending the soonest-expiring available saved reset credit, and improved error handling to distinguish between transport failures (`credit_list_failed`) and a genuine lack of credits.
14
+ - Exported `SENSITIVE_TOKEN_RE` from `providers/transform-messages` to allow hosts to route credential shapes through reversible obfuscation instead of irreversible redaction.
15
+
16
+ ### Fixed
17
+
18
+ - Fixed an issue where Cursor conversation checkpoints were incorrectly recorded as billable output tokens, ensuring accurate usage totals.
19
+ - Fixed an issue in `AuthStorage.refreshStoredOAuthCredential` where expired OAuth credentials were returned without being refreshed when a credential mismatch occurred, which previously resulted in misleading "No API key found" errors.
20
+ - Fixed Cursor history replay issues by preserving structured message order for assistant tool calls/results, retaining Kimi K3 thinking blocks, and preventing unsafe mid-session switches to K3.
21
+
22
+ ## [17.2.1] - 2026-07-30
23
+
24
+ ### Added
25
+
26
+ - Added exact OAuth credential-row resolution by durable credential id. The targeted path refreshes only that row and never ranks, rotates, or falls back to sibling accounts.
27
+
28
+ ### Changed
29
+
30
+ - Anthropic OAuth requests now reproduce Cowork's current `claude-desktop` request profile, including client/runtime metadata, beta selection, system and billing attestation, the 64K output cap, and stable HTTP/1.1 header ordering.
31
+
5
32
  ## [17.2.0] - 2026-07-30
6
33
 
7
34
  ### Added
@@ -676,7 +676,9 @@ export interface ResetCreditRedeemOutcome {
676
676
  * Result code. Backend codes: `reset` (success), `already_redeemed`,
677
677
  * `no_credit`, `nothing_to_reset`. Locally-synthesized: `no_account`
678
678
  * (target not found), `account_unavailable` (token refresh failed),
679
- * `http_<status>` (unexpected HTTP).
679
+ * `credit_list_failed` (transport/auth failure while listing credits —
680
+ * retryable, unlike a genuine `no_credit`), `http_<status>` (unexpected
681
+ * HTTP).
680
682
  */
681
683
  code: CodexResetConsumeCode;
682
684
  accountId?: string;
@@ -1083,6 +1085,18 @@ export declare class AuthStorage {
1083
1085
  * overrides have replaced OAuth with an explicit API key.
1084
1086
  */
1085
1087
  getOAuthAccessAt(provider: string, position: number, options?: AuthApiKeyOptions): Promise<OAuthAccessResolution | undefined>;
1088
+ /**
1089
+ * Resolve one stored OAuth credential by its durable storage row id.
1090
+ *
1091
+ * Unlike the normal session resolver, this method never ranks, rotates, or
1092
+ * falls back to sibling credentials. A forced refresh re-mints only the
1093
+ * requested row, preserving exact-account affinity for operations whose
1094
+ * provenance and policy boundary are tied to one workspace.
1095
+ *
1096
+ * Returns `undefined` when the row does not exist for `provider` or an
1097
+ * explicit runtime/config API-key override suppresses OAuth.
1098
+ */
1099
+ getOAuthAccessByCredentialId(provider: string, credentialId: number, options?: AuthApiKeyOptions): Promise<OAuthAccessResolution | undefined>;
1086
1100
  /**
1087
1101
  * List saved rate-limit resets for every stored OAuth account of `provider`
1088
1102
  * (Codex), fetched LIVE from the dedicated `rate-limit-reset-credits` route.
@@ -10,7 +10,7 @@ export type AnthropicHeaderOptions = {
10
10
  modelHeaders?: Record<string, string>;
11
11
  isCloudflareAiGateway?: boolean;
12
12
  claudeCodeSessionId?: string;
13
- claudeCodeBetas?: readonly string[];
13
+ coworkBetas?: readonly string[];
14
14
  /** Allow explicit fingerprint headers to replace OAuth defaults on non-official endpoints. */
15
15
  allowAnthropicHeaderOverrides?: boolean;
16
16
  };
@@ -33,19 +33,17 @@ export declare function clearAnthropicFastModeFallback(providerSessionState: Map
33
33
  */
34
34
  export declare function isAnthropicFastModeFallbackDisabled(providerSessionState: Map<string, ProviderSessionState> | undefined, model: Model<Api>): boolean;
35
35
  export * from "./claude-code-fingerprint.js";
36
- export declare function mapStainlessOs(platform: string): "MacOS" | "Windows" | "Linux" | "FreeBSD" | `Other::${string}`;
37
36
  export declare function mapStainlessArch(arch: string): "x64" | "arm64" | "x86" | `other::${string}`;
38
- export declare const claudeCodeHeaders: {
39
- "X-Stainless-Retry-Count": string;
40
- "X-Stainless-Runtime-Version": string;
37
+ /** Static headers emitted by Cowork's Linux Claude runtime. */
38
+ export declare const coworkHeaders: {
39
+ "X-Stainless-Arch": "arm64" | "x64" | "x86" | `other::${string}`;
40
+ "X-Stainless-Lang": string;
41
+ "X-Stainless-OS": string;
41
42
  "X-Stainless-Package-Version": string;
43
+ "X-Stainless-Retry-Count": string;
42
44
  "X-Stainless-Runtime": string;
43
- "X-Stainless-Lang": string;
44
- "X-Stainless-Arch": "arm64" | "x64" | "x86" | `other::${string}`;
45
- "X-Stainless-OS": "FreeBSD" | "Linux" | "MacOS" | "Windows" | `Other::${string}`;
45
+ "X-Stainless-Runtime-Version": string;
46
46
  "X-Stainless-Timeout": string;
47
- "anthropic-client-platform": string;
48
- "anthropic-client-version": string;
49
47
  };
50
48
  /**
51
49
  * Wraps a fetch implementation to patch the Claude Code billing-header `cch`
@@ -1,15 +1,19 @@
1
1
  /**
2
- * Claude Code stealth-fingerprint constants, kept in a leaf module so
3
- * fingerprint consumers outside the provider (`registry/oauth/anthropic`,
4
- * `usage/claude`) don't import the heavy `providers/anthropic` module.
2
+ * Cowork inference-fingerprint constants, kept in a leaf module so consumers
3
+ * outside the provider (`registry/oauth/anthropic`, `usage/claude`) don't
4
+ * import the heavy `providers/anthropic` module.
5
+ *
5
6
  * That import edge was a live init cycle: `providers/anthropic` → `stream` →
6
7
  * `registry` → `registry/oauth/anthropic` → back into the still-initializing
7
- * provider module, which threw a TDZ ReferenceError whenever
8
- * `providers/anthropic` was the first module loaded.
8
+ * provider module.
9
9
  */
10
- export declare const claudeCodeVersion = "2.1.165";
11
- export declare const claudeAgentSdkVersion = "0.3.165";
12
- export declare const claudeClientVersion = "1.11187.4";
10
+ /** Claude runtime version bundled by the current Cowork desktop release. */
11
+ export declare const claudeCodeVersion = "2.1.220";
12
+ /** User-Agent emitted by Cowork's `claude-desktop` inference entrypoint. */
13
+ export declare const coworkUserAgent = "claude-cli/2.1.220 (external, claude-desktop)";
14
+ /** Prefix used to isolate custom Anthropic OAuth tools from built-in tools. */
13
15
  export declare const claudeToolPrefix: string;
16
+ /** Identity block prepended by Cowork's Claude runtime. */
14
17
  export declare const claudeCodeSystemInstruction = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
18
+ /** Cowork's per-request output-token ceiling. */
15
19
  export declare const CLAUDE_CODE_MAX_OUTPUT_TOKENS = 64000;
@@ -0,0 +1,3 @@
1
+ import type { FetchImpl } from "../types.js";
2
+ /** Sends Cowork-profiled HTTPS requests with stable header order, HTTP/1.1, and streaming decompression. */
3
+ export declare const coworkFetch: FetchImpl;
@@ -214,7 +214,7 @@ export declare function buildMcpToolDefinitions(tools: Tool[] | undefined): McpT
214
214
  */
215
215
  export declare function buildCursorSystemPromptJsons(systemPrompt: readonly string[] | undefined): string[];
216
216
  /** Exported for tests: decodes Cursor history blobs built from conversation messages. */
217
- export declare function buildCursorHistoryForTest(messages: Message[], activeUserMessageIndex?: number): {
217
+ export declare function buildCursorHistoryForTest(messages: Message[], activeUserMessageIndex?: number, targetModelId?: string): {
218
218
  rootPromptMessagesJson: unknown[];
219
219
  turnUserMessagesJson: JsonValue[];
220
220
  turnStepMessagesJson: JsonValue[][];
@@ -1,4 +1,22 @@
1
1
  import type { Api, AssistantMessage, Message, Model } from "../types.js";
2
+ /**
3
+ * Normalize tool call ID for cross-provider compatibility.
4
+ * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
5
+ * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).
6
+ *
7
+ * For aborted/errored turns, this function:
8
+ * - Preserves tool call structure (unlike converting to text summaries)
9
+ * - Injects synthetic "aborted" tool results
10
+ */
11
+ /**
12
+ * Credential-shaped token patterns scrubbed from outbound provider traffic when
13
+ * credential redaction is enabled. Exported so hosts can route the same shapes
14
+ * through reversible obfuscation (keyed placeholders restored before local tool
15
+ * execution) instead of the irreversible `[*_token_redacted]` rewrite below —
16
+ * an irreversible placeholder echoed back in edit-tool `old_text` can never
17
+ * match the real bytes on disk.
18
+ */
19
+ export declare const SENSITIVE_TOKEN_RE: RegExp;
2
20
  /**
3
21
  * Toggle outbound credential-pattern redaction. When disabled (the default),
4
22
  * {@link redactSensitiveCredentials} and {@link redactSensitiveInObject} are
@@ -0,0 +1,7 @@
1
+ import type { OAuthLoginCallbacks } from "./oauth/types.js";
2
+ export declare const loginGmiCloud: (options: import("./oauth/index.js").OAuthController) => Promise<string>;
3
+ export declare const gmiCloudProvider: {
4
+ readonly id: "gmi-cloud";
5
+ readonly name: "GMI Cloud";
6
+ readonly login: (cb: OAuthLoginCallbacks) => Promise<string>;
7
+ };
@@ -97,6 +97,10 @@ declare const ALL: ({
97
97
  readonly refreshToken: (credentials: import("./oauth/index.js").OAuthCredentials) => Promise<import("./oauth/index.js").OAuthCredentials>;
98
98
  readonly callbackPort: 8080;
99
99
  readonly pasteCodeFlow: true;
100
+ } | {
101
+ readonly id: "gmi-cloud";
102
+ readonly name: "GMI Cloud";
103
+ readonly login: (cb: import("./oauth/index.js").OAuthLoginCallbacks) => Promise<string>;
100
104
  } | {
101
105
  readonly id: "google";
102
106
  readonly name: "Google Gemini";
@@ -67,6 +67,15 @@ interface CodexResetAuth {
67
67
  * failure (non-2xx or thrown), letting callers treat it the same as "no data".
68
68
  */
69
69
  export declare function listCodexResetCredits(auth: CodexResetAuth): Promise<CodexResetCreditList | null>;
70
+ /**
71
+ * Pick the credit to spend: the available one that expires soonest.
72
+ *
73
+ * Credits are perishable, so spending in expiry order maximizes the bank's
74
+ * lifetime value. Available credits without a parseable `expiresAt` rank after
75
+ * dated ones; when nothing is available the first credit is returned unchanged
76
+ * (the consume then surfaces the backend's business outcome verbatim).
77
+ */
78
+ export declare function pickSoonestExpiringCredit(credits: readonly CodexResetCredit[]): CodexResetCredit | undefined;
70
79
  /**
71
80
  * Spend one saved reset. `redeemRequestId` is the idempotency key; one is
72
81
  * generated when omitted, so retrying with the SAME id is safe and won't
@@ -29,6 +29,8 @@ export interface ConnectProxiedSocketOptions {
29
29
  signal?: AbortSignal;
30
30
  /** Maximum wall-clock time to establish the final TLS tunnel. Disabled when absent or non-positive. */
31
31
  timeoutMs?: number;
32
+ /** Target TLS profile. Cursor defaults to HTTP/2 when this is absent. */
33
+ tls?: tls.ConnectionOptions;
32
34
  }
33
35
  /**
34
36
  * Tunnel a socket connection through an HTTP CONNECT proxy.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "17.2.0",
4
+ "version": "17.2.2",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -38,9 +38,9 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bufbuild/protobuf": "^2.12.1",
41
- "@oh-my-pi/pi-catalog": "17.2.0",
42
- "@oh-my-pi/pi-utils": "17.2.0",
43
- "@oh-my-pi/pi-wire": "17.2.0",
41
+ "@oh-my-pi/pi-catalog": "17.2.2",
42
+ "@oh-my-pi/pi-utils": "17.2.2",
43
+ "@oh-my-pi/pi-wire": "17.2.2",
44
44
  "arktype": "2.2.3",
45
45
  "zod": "^4"
46
46
  },
@@ -62,6 +62,7 @@ import {
62
62
  type CodexResetCredit,
63
63
  consumeCodexResetCredit,
64
64
  listCodexResetCredits,
65
+ pickSoonestExpiringCredit,
65
66
  } from "./usage/openai-codex-reset";
66
67
  import { opencodeGoUsageProvider } from "./usage/opencode-go";
67
68
  import { syntheticUsageProvider } from "./usage/synthetic";
@@ -952,7 +953,9 @@ export interface ResetCreditRedeemOutcome {
952
953
  * Result code. Backend codes: `reset` (success), `already_redeemed`,
953
954
  * `no_credit`, `nothing_to_reset`. Locally-synthesized: `no_account`
954
955
  * (target not found), `account_unavailable` (token refresh failed),
955
- * `http_<status>` (unexpected HTTP).
956
+ * `credit_list_failed` (transport/auth failure while listing credits —
957
+ * retryable, unlike a genuine `no_credit`), `http_<status>` (unexpected
958
+ * HTTP).
956
959
  */
957
960
  code: CodexResetConsumeCode;
958
961
  accountId?: string;
@@ -2327,10 +2330,19 @@ export class AuthStorage {
2327
2330
  if (!current) {
2328
2331
  return { credential: undefined, refreshed: false, removed: false };
2329
2332
  }
2330
- if (options.observedCredential && !authCredentialEquals(current, options.observedCredential)) {
2333
+ const currentIsFresh = Date.now() + refreshSkewMs < current.expires;
2334
+ // A peer rotated the credential out from under the caller's observation.
2335
+ // Adopt the stored copy only when it is still usable; a stored copy that
2336
+ // is itself expired must fall through to a refresh rather than be handed
2337
+ // back and fail the downstream `getOAuthApiKey` expiry precondition.
2338
+ if (
2339
+ options.observedCredential &&
2340
+ !authCredentialEquals(current, options.observedCredential) &&
2341
+ currentIsFresh
2342
+ ) {
2331
2343
  return { credential: current, refreshed: false, removed: false };
2332
2344
  }
2333
- if (!options.forceRefresh && Date.now() + refreshSkewMs < current.expires) {
2345
+ if (!options.forceRefresh && currentIsFresh) {
2334
2346
  return { credential: current, refreshed: false, removed: false };
2335
2347
  }
2336
2348
  if (options.canRefresh && !options.canRefresh(current)) {
@@ -2370,10 +2382,17 @@ export class AuthStorage {
2370
2382
  if (!current) {
2371
2383
  return { credential: undefined, refreshed: false, removed: false };
2372
2384
  }
2373
- if (options.observedCredential && !authCredentialEquals(current, options.observedCredential)) {
2385
+ const currentIsFresh = Date.now() + refreshSkewMs < current.expires;
2386
+ // Re-check after acquiring the lease: only adopt the stored copy on an
2387
+ // observed mismatch when it is still usable, mirroring the pre-lease guard.
2388
+ if (
2389
+ options.observedCredential &&
2390
+ !authCredentialEquals(current, options.observedCredential) &&
2391
+ currentIsFresh
2392
+ ) {
2374
2393
  return { credential: current, refreshed: false, removed: false };
2375
2394
  }
2376
- if (!options.forceRefresh && Date.now() + refreshSkewMs < current.expires) {
2395
+ if (!options.forceRefresh && currentIsFresh) {
2377
2396
  return { credential: current, refreshed: false, removed: false };
2378
2397
  }
2379
2398
  if (options.canRefresh && !options.canRefresh(current)) {
@@ -5476,6 +5495,33 @@ export class AuthStorage {
5476
5495
  return this.#resolveStoredOAuthAccess(provider, selection, providerKey, options);
5477
5496
  }
5478
5497
 
5498
+ /**
5499
+ * Resolve one stored OAuth credential by its durable storage row id.
5500
+ *
5501
+ * Unlike the normal session resolver, this method never ranks, rotates, or
5502
+ * falls back to sibling credentials. A forced refresh re-mints only the
5503
+ * requested row, preserving exact-account affinity for operations whose
5504
+ * provenance and policy boundary are tied to one workspace.
5505
+ *
5506
+ * Returns `undefined` when the row does not exist for `provider` or an
5507
+ * explicit runtime/config API-key override suppresses OAuth.
5508
+ */
5509
+ async getOAuthAccessByCredentialId(
5510
+ provider: string,
5511
+ credentialId: number,
5512
+ options?: AuthApiKeyOptions,
5513
+ ): Promise<OAuthAccessResolution | undefined> {
5514
+ if (this.#runtimeOverrides.has(provider) || this.#configOverrides.has(provider)) {
5515
+ return undefined;
5516
+ }
5517
+ const selection = this.#getStoredOAuthSelections(provider).find(
5518
+ candidate => candidate.credentialId === credentialId,
5519
+ );
5520
+ if (!selection) return undefined;
5521
+ const providerKey = this.#getProviderTypeKey(provider, "oauth");
5522
+ return this.#resolveStoredOAuthAccess(provider, selection, providerKey, options);
5523
+ }
5524
+
5479
5525
  /**
5480
5526
  * List saved rate-limit resets for every stored OAuth account of `provider`
5481
5527
  * (Codex), fetched LIVE from the dedicated `rate-limit-reset-credits` route.
@@ -5563,7 +5609,13 @@ export class AuthStorage {
5563
5609
  fetch: this.#usageFetch,
5564
5610
  signal: options.signal,
5565
5611
  });
5566
- const credit = list?.credits.find(entry => (entry.status ?? "available") === "available") ?? list?.credits[0];
5612
+ // Transport/auth failure is NOT "no credits": callers treat `no_credit`
5613
+ // as terminal for the episode, so conflating them would bury a live
5614
+ // credit behind one flaky request.
5615
+ if (!list) {
5616
+ return { ok: false, code: "credit_list_failed", accountId: match.accountId, email: match.email };
5617
+ }
5618
+ const credit = pickSoonestExpiringCredit(list.credits);
5567
5619
  if (!credit) return { ok: false, code: "no_credit", accountId: match.accountId, email: match.email };
5568
5620
  creditId = credit.id;
5569
5621
  }
@@ -87,11 +87,10 @@ import {
87
87
  } from "./anthropic-wire";
88
88
  import {
89
89
  CLAUDE_CODE_MAX_OUTPUT_TOKENS,
90
- claudeAgentSdkVersion,
91
- claudeClientVersion,
92
90
  claudeCodeSystemInstruction,
93
91
  claudeCodeVersion,
94
92
  claudeToolPrefix,
93
+ coworkUserAgent,
95
94
  } from "./claude-code-fingerprint";
96
95
  import {
97
96
  buildCopilotDynamicHeaders,
@@ -111,7 +110,7 @@ export type AnthropicHeaderOptions = {
111
110
  modelHeaders?: Record<string, string>;
112
111
  isCloudflareAiGateway?: boolean;
113
112
  claudeCodeSessionId?: string;
114
- claudeCodeBetas?: readonly string[];
113
+ coworkBetas?: readonly string[];
115
114
  /** Allow explicit fingerprint headers to replace OAuth defaults on non-official endpoints. */
116
115
  allowAnthropicHeaderOverrides?: boolean;
117
116
  };
@@ -158,52 +157,49 @@ function mergeAnthropicBetaHeader(callerHeaders: Record<string, string>, beta: s
158
157
  const midConversationSystemBeta = "mid-conversation-system-2026-04-07";
159
158
  const contextManagementBeta = "context-management-2025-06-27";
160
159
  const structuredOutputsBeta = "structured-outputs-2025-12-15";
161
- const claudeCodeUtilityBetaDefaults = [
162
- "oauth-2025-04-20",
160
+ const context1mBeta = "context-1m-2025-08-07";
161
+ const thinkingTokenCountBeta = "thinking-token-count-2026-05-13";
162
+ const fallbackCreditBeta = "fallback-credit-2026-06-01";
163
+ const coworkUtilityBetaDefaults = [
163
164
  "interleaved-thinking-2025-05-14",
165
+ thinkingTokenCountBeta,
164
166
  contextManagementBeta,
165
167
  "prompt-caching-scope-2026-01-05",
166
168
  structuredOutputsBeta,
167
169
  ] as const;
168
- const claudeCodeAgentBetaDefaults = [
170
+ const coworkAgentBetaDefaults = [
169
171
  "claude-code-20250219",
170
- "oauth-2025-04-20",
171
172
  "interleaved-thinking-2025-05-14",
173
+ thinkingTokenCountBeta,
172
174
  contextManagementBeta,
173
175
  "prompt-caching-scope-2026-01-05",
174
176
  midConversationSystemBeta,
175
177
  "advanced-tool-use-2025-11-20",
176
178
  ] as const;
177
179
  const extendedCacheTtlBeta = "extended-cache-ttl-2025-04-11";
178
- const claudeCodeAgentPostEffortBetas = [extendedCacheTtlBeta] as const;
179
180
  const fineGrainedToolStreamingBeta = "fine-grained-tool-streaming-2025-05-14";
180
181
  const interleavedThinkingBeta = "interleaved-thinking-2025-05-14";
181
- // Asks the API to redact thinking blocks from responses. Only sent when the
182
- // caller explicitly hides thinking (`thinkingDisplay: "omitted"`); sending it
183
- // by default suppresses the thinking traces callers expect to stream.
184
- const redactThinkingBeta = "redact-thinking-2026-02-12";
185
182
  const fastModeBeta = "fast-mode-2026-02-01";
186
183
  const taskBudgetBeta = "task-budgets-2026-03-13";
187
184
  const effortBeta = "effort-2025-11-24";
188
185
  const serverSideFallbackBeta = "server-side-fallback-2026-06-01";
189
186
 
190
- function buildClaudeCodeBetas(
187
+ function buildCoworkBetas(
191
188
  agentRequest: boolean,
192
189
  thinkingRequest: boolean,
193
- redactThinking: boolean,
190
+ longContext: boolean,
194
191
  disableStrictTools = false,
195
192
  ): readonly string[] {
196
- if (!agentRequest && !redactThinking && !disableStrictTools) return claudeCodeUtilityBetaDefaults;
193
+ if (!agentRequest && !disableStrictTools) return coworkUtilityBetaDefaults;
197
194
  const betas: string[] = [];
198
- for (const beta of agentRequest ? claudeCodeAgentBetaDefaults : claudeCodeUtilityBetaDefaults) {
195
+ for (const beta of agentRequest ? coworkAgentBetaDefaults : coworkUtilityBetaDefaults) {
199
196
  if (disableStrictTools && beta === structuredOutputsBeta) continue;
200
197
  betas.push(beta);
201
- // Match CC's header order: redact-thinking immediately follows interleaved-thinking.
202
- if (redactThinking && beta === interleavedThinkingBeta) betas.push(redactThinkingBeta);
198
+ if (agentRequest && longContext && beta === "claude-code-20250219") betas.push(context1mBeta);
203
199
  }
204
200
  if (!agentRequest) return betas;
205
201
  if (thinkingRequest) betas.push(effortBeta);
206
- betas.push(...claudeCodeAgentPostEffortBetas);
202
+ betas.push(fallbackCreditBeta);
207
203
  return betas;
208
204
  }
209
205
 
@@ -246,11 +242,10 @@ export function buildAnthropicHeaders(options: AnthropicHeaderOptions): Record<s
246
242
  const incomingUserAgent = getHeaderCaseInsensitive(options.modelHeaders, "User-Agent");
247
243
  const incomingAuthorization = getHeaderCaseInsensitive(options.modelHeaders, "Authorization");
248
244
  const incomingApiKey = getHeaderCaseInsensitive(options.modelHeaders, "X-Api-Key");
249
- // Claude Code betas (oauth-2025-04-20, claude-code-20250219, …) are part of
250
- // the OAuth fingerprint; API-key requests default to extras only, matching
251
- // the streaming path (buildAnthropicClientOptions passes [] for non-OAuth).
245
+ // Cowork's beta profile is part of the OAuth fingerprint; API-key requests
246
+ // default to extras only, matching the streaming path.
252
247
  const betaHeader = buildBetaHeader(
253
- options.claudeCodeBetas ?? (oauthToken ? buildClaudeCodeBetas(true, true, false) : []),
248
+ options.coworkBetas ?? (oauthToken ? buildCoworkBetas(true, true, false) : []),
254
249
  extraBetas,
255
250
  );
256
251
  const acceptHeader = oauthToken ? "application/json" : stream ? "text/event-stream" : "application/json";
@@ -308,19 +303,22 @@ export function buildAnthropicHeaders(options: AnthropicHeaderOptions): Record<s
308
303
  }
309
304
 
310
305
  if (oauthToken) {
311
- const userAgent = isClaudeCodeClientUserAgent(incomingUserAgent)
312
- ? incomingUserAgent
313
- : `claude-cli/${claudeCodeVersion} (external, local-agent, agent-sdk/${claudeAgentSdkVersion})`;
306
+ const userAgent = isClaudeCodeClientUserAgent(incomingUserAgent) ? incomingUserAgent : coworkUserAgent;
314
307
  const headers = {
315
308
  ...modelHeaders,
316
- ...claudeCodeHeaders,
317
309
  Accept: acceptHeader,
318
- Authorization: `Bearer ${options.apiKey}`,
319
- ...sharedHeaders,
320
- ...(betaHeader ? { "anthropic-beta": betaHeader } : {}),
310
+ "Content-Type": "application/json",
311
+ "User-Agent": userAgent,
321
312
  ...(options.claudeCodeSessionId ? { "X-Claude-Code-Session-Id": options.claudeCodeSessionId } : {}),
313
+ ...coworkHeaders,
314
+ ...(betaHeader ? { "anthropic-beta": betaHeader } : {}),
315
+ "anthropic-dangerous-direct-browser-access": "true",
316
+ "anthropic-version": "2023-06-01",
317
+ Authorization: `Bearer ${options.apiKey}`,
318
+ "x-app": "cli",
322
319
  "x-client-request-id": nodeCrypto.randomUUID(),
323
- "User-Agent": userAgent,
320
+ Connection: "keep-alive",
321
+ "Accept-Encoding": "gzip, deflate, br, zstd",
324
322
  ...(incomingApiKey ? { "X-Api-Key": incomingApiKey } : {}),
325
323
  };
326
324
  return allowAnthropicHeaderOverrides ? mergeHeaders(headers, anthropicHeaderOverrides) : headers;
@@ -503,26 +501,10 @@ function getCacheControl(
503
501
  };
504
502
  }
505
503
 
506
- // Stealth mode: mimic Claude Code's request fingerprint. Constants live in the
507
- // leaf module so registry/usage consumers avoid an init cycle through this file.
504
+ // Cowork mode: mimic the desktop agent's direct inference transport. Constants
505
+ // live in the leaf module so registry/usage consumers avoid an init cycle.
508
506
  export * from "./claude-code-fingerprint";
509
507
 
510
- export function mapStainlessOs(platform: string): "MacOS" | "Windows" | "Linux" | "FreeBSD" | `Other::${string}` {
511
- switch (platform.toLowerCase()) {
512
- case "darwin":
513
- return "MacOS";
514
- case "windows":
515
- case "win32":
516
- return "Windows";
517
- case "linux":
518
- return "Linux";
519
- case "freebsd":
520
- return "FreeBSD";
521
- default:
522
- return `Other::${platform.toLowerCase()}`;
523
- }
524
- }
525
-
526
508
  export function mapStainlessArch(arch: string): "x64" | "arm64" | "x86" | `other::${string}` {
527
509
  switch (arch.toLowerCase()) {
528
510
  case "amd64":
@@ -540,22 +522,21 @@ export function mapStainlessArch(arch: string): "x64" | "arm64" | "x86" | `other
540
522
  }
541
523
  }
542
524
 
543
- export const claudeCodeHeaders = {
544
- "X-Stainless-Retry-Count": "0",
545
- "X-Stainless-Runtime-Version": "v24.3.0",
525
+ /** Static headers emitted by Cowork's Linux Claude runtime. */
526
+ export const coworkHeaders = {
527
+ "X-Stainless-Arch": mapStainlessArch(process.arch),
528
+ "X-Stainless-Lang": "js",
529
+ "X-Stainless-OS": "Linux",
546
530
  "X-Stainless-Package-Version": "0.94.0",
531
+ "X-Stainless-Retry-Count": "0",
547
532
  "X-Stainless-Runtime": "node",
548
- "X-Stainless-Lang": "js",
549
- "X-Stainless-Arch": mapStainlessArch(process.arch),
550
- "X-Stainless-OS": mapStainlessOs(process.platform),
551
- "X-Stainless-Timeout": "900",
552
- "anthropic-client-platform": "desktop_app",
553
- "anthropic-client-version": claudeClientVersion,
533
+ "X-Stainless-Runtime-Version": "v26.3.0",
534
+ "X-Stainless-Timeout": "600",
554
535
  };
555
536
 
556
537
  const enforcedHeaderKeys = new Set(
557
538
  [
558
- ...Object.keys(claudeCodeHeaders),
539
+ ...Object.keys(coworkHeaders),
559
540
  "Accept",
560
541
  "Accept-Encoding",
561
542
  "Connection",
@@ -574,7 +555,7 @@ const enforcedHeaderKeys = new Set(
574
555
  );
575
556
 
576
557
  const overridableAnthropicHeaderKeys = new Set(
577
- [...Object.keys(claudeCodeHeaders), "anthropic-beta", "User-Agent", "x-app"].map(key => key.toLowerCase()),
558
+ [...Object.keys(coworkHeaders), "anthropic-beta", "User-Agent", "x-app"].map(key => key.toLowerCase()),
578
559
  );
579
560
 
580
561
  const CLAUDE_BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
@@ -591,7 +572,7 @@ function createClaudeBillingHeader(firstUserMessageText: string): string {
591
572
  .slice(0, 3);
592
573
  // cch=00000: placeholder replaced with the real attestation hash by wrapFetchForCch
593
574
  // before the request hits the wire (see below).
594
- return `${CLAUDE_BILLING_HEADER_PREFIX} cc_version=${claudeCodeVersion}.${versionSuffix}; cc_entrypoint=local-agent; ${CCH_PLACEHOLDER_STR};`;
575
+ return `${CLAUDE_BILLING_HEADER_PREFIX} cc_version=${claudeCodeVersion}.${versionSuffix}; cc_entrypoint=claude-desktop; ${CCH_PLACEHOLDER_STR};`;
595
576
  }
596
577
 
597
578
  // cch attestation: XXHash64(body_with_placeholder, seed) low-20-bits, 5 hex chars.
@@ -1184,7 +1165,7 @@ export type AnthropicClientOptionsResult = {
1184
1165
  fetchOptions?: AnthropicFetchOptions;
1185
1166
  };
1186
1167
 
1187
- const CLAUDE_CODE_TLS_CIPHERS = tls.DEFAULT_CIPHERS;
1168
+ const COWORK_TLS_CIPHERS = tls.DEFAULT_CIPHERS;
1188
1169
 
1189
1170
  type FoundryTlsOptions = {
1190
1171
  ca?: string | string[];
@@ -1347,7 +1328,7 @@ function resolveFoundryTlsOptions(model: Model<"anthropic-messages">): FoundryTl
1347
1328
  return resolved;
1348
1329
  }
1349
1330
 
1350
- function buildClaudeCodeTlsFetchOptions(
1331
+ function buildCoworkTlsFetchOptions(
1351
1332
  model: Model<"anthropic-messages">,
1352
1333
  baseUrl: string | undefined,
1353
1334
  ): AnthropicFetchOptions | undefined {
@@ -1369,7 +1350,7 @@ function buildClaudeCodeTlsFetchOptions(
1369
1350
  tls: {
1370
1351
  rejectUnauthorized: true,
1371
1352
  serverName,
1372
- ...(CLAUDE_CODE_TLS_CIPHERS ? { ciphers: CLAUDE_CODE_TLS_CIPHERS } : {}),
1353
+ ...(COWORK_TLS_CIPHERS ? { ciphers: COWORK_TLS_CIPHERS } : {}),
1373
1354
  ...(foundryTlsOptions ?? {}),
1374
1355
  },
1375
1356
  };
@@ -2865,7 +2846,6 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
2865
2846
  dynamicHeaders,
2866
2847
  hasTools = false,
2867
2848
  thinkingEnabled = false,
2868
- thinkingDisplay,
2869
2849
  isOAuth,
2870
2850
  maxRetryDelayMs,
2871
2851
  claudeCodeSessionId,
@@ -2903,7 +2883,7 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
2903
2883
  const needsFineGrainedToolStreamingBeta =
2904
2884
  hasTools && isOfficialAnthropicApiUrl(baseUrl) && !supportsEagerToolInputStreaming;
2905
2885
  const foundryCustomHeaders = resolveAnthropicCustomHeaders(model);
2906
- const tlsFetchOptions = buildClaudeCodeTlsFetchOptions(model, baseUrl);
2886
+ const tlsFetchOptions = buildCoworkTlsFetchOptions(model, baseUrl);
2907
2887
  // Disable Bun's native ~300s pre-response fetch timeout (issue #2422).
2908
2888
  // `AnthropicMessagesClient` already arms its own DEFAULT_TIMEOUT_MS timer
2909
2889
  // per request, so the native ceiling can only short-circuit slow-prefill
@@ -2969,11 +2949,11 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
2969
2949
  isCloudflareAiGateway: model.provider === "cloudflare-ai-gateway",
2970
2950
  allowAnthropicHeaderOverrides: model.compat.allowAnthropicHeaderOverrides,
2971
2951
  claudeCodeSessionId,
2972
- claudeCodeBetas: oauthToken
2973
- ? buildClaudeCodeBetas(
2952
+ coworkBetas: oauthToken
2953
+ ? buildCoworkBetas(
2974
2954
  hasTools || thinkingEnabled,
2975
2955
  thinkingEnabled,
2976
- thinkingDisplay === "omitted",
2956
+ (model.contextWindow ?? 0) >= 1_000_000,
2977
2957
  disableStrictTools,
2978
2958
  )
2979
2959
  : [],
@@ -1,19 +1,20 @@
1
1
  /**
2
- * Claude Code stealth-fingerprint constants, kept in a leaf module so
3
- * fingerprint consumers outside the provider (`registry/oauth/anthropic`,
4
- * `usage/claude`) don't import the heavy `providers/anthropic` module.
2
+ * Cowork inference-fingerprint constants, kept in a leaf module so consumers
3
+ * outside the provider (`registry/oauth/anthropic`, `usage/claude`) don't
4
+ * import the heavy `providers/anthropic` module.
5
+ *
5
6
  * That import edge was a live init cycle: `providers/anthropic` → `stream` →
6
7
  * `registry` → `registry/oauth/anthropic` → back into the still-initializing
7
- * provider module, which threw a TDZ ReferenceError whenever
8
- * `providers/anthropic` was the first module loaded.
8
+ * provider module.
9
9
  */
10
10
 
11
- export const claudeCodeVersion = "2.1.165";
12
- export const claudeAgentSdkVersion = "0.3.165";
13
- export const claudeClientVersion = "1.11187.4";
11
+ /** Claude runtime version bundled by the current Cowork desktop release. */
12
+ export const claudeCodeVersion = "2.1.220";
13
+ /** User-Agent emitted by Cowork's `claude-desktop` inference entrypoint. */
14
+ export const coworkUserAgent = `claude-cli/${claudeCodeVersion} (external, claude-desktop)`;
15
+ /** Prefix used to isolate custom Anthropic OAuth tools from built-in tools. */
14
16
  export const claudeToolPrefix: string = "_";
17
+ /** Identity block prepended by Cowork's Claude runtime. */
15
18
  export const claudeCodeSystemInstruction = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
16
- // Claude Code caps requested output at 64k tokens even when the model ceiling is
17
- // higher (e.g. Opus 4.8 supports 128k); OAuth requests clamp to match the wire
18
- // fingerprint. API-key requests keep the full model ceiling.
19
+ /** Cowork's per-request output-token ceiling. */
19
20
  export const CLAUDE_CODE_MAX_OUTPUT_TOKENS = 64000;