@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 +27 -0
- package/dist/types/auth-storage.d.ts +15 -1
- package/dist/types/providers/anthropic.d.ts +8 -10
- package/dist/types/providers/claude-code-fingerprint.d.ts +12 -8
- package/dist/types/providers/cowork-fetch.d.ts +3 -0
- package/dist/types/providers/cursor.d.ts +1 -1
- package/dist/types/providers/transform-messages.d.ts +18 -0
- package/dist/types/registry/gmi-cloud.d.ts +7 -0
- package/dist/types/registry/registry.d.ts +4 -0
- package/dist/types/usage/openai-codex-reset.d.ts +9 -0
- package/dist/types/utils/proxy.d.ts +2 -0
- package/package.json +4 -4
- package/src/auth-storage.ts +58 -6
- package/src/providers/anthropic.ts +49 -69
- package/src/providers/claude-code-fingerprint.ts +12 -11
- package/src/providers/cowork-fetch.ts +201 -0
- package/src/providers/cursor.ts +240 -56
- package/src/providers/transform-messages.ts +9 -1
- package/src/registry/gmi-cloud.ts +22 -0
- package/src/registry/registry.ts +2 -0
- package/src/stream.ts +12 -4
- package/src/usage/openai-codex-reset.ts +27 -0
- package/src/utils/proxy.ts +6 -2
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
|
-
* `
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
"X-Stainless-
|
|
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-
|
|
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
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
|
8
|
-
* `providers/anthropic` was the first module loaded.
|
|
8
|
+
* provider module.
|
|
9
9
|
*/
|
|
10
|
-
|
|
11
|
-
export declare const
|
|
12
|
-
|
|
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;
|
|
@@ -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.
|
|
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.
|
|
42
|
-
"@oh-my-pi/pi-utils": "17.2.
|
|
43
|
-
"@oh-my-pi/pi-wire": "17.2.
|
|
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
|
},
|
package/src/auth-storage.ts
CHANGED
|
@@ -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
|
-
* `
|
|
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
|
-
|
|
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 &&
|
|
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
|
-
|
|
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 &&
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
162
|
-
|
|
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
|
|
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
|
|
187
|
+
function buildCoworkBetas(
|
|
191
188
|
agentRequest: boolean,
|
|
192
189
|
thinkingRequest: boolean,
|
|
193
|
-
|
|
190
|
+
longContext: boolean,
|
|
194
191
|
disableStrictTools = false,
|
|
195
192
|
): readonly string[] {
|
|
196
|
-
if (!agentRequest && !
|
|
193
|
+
if (!agentRequest && !disableStrictTools) return coworkUtilityBetaDefaults;
|
|
197
194
|
const betas: string[] = [];
|
|
198
|
-
for (const beta of agentRequest ?
|
|
195
|
+
for (const beta of agentRequest ? coworkAgentBetaDefaults : coworkUtilityBetaDefaults) {
|
|
199
196
|
if (disableStrictTools && beta === structuredOutputsBeta) continue;
|
|
200
197
|
betas.push(beta);
|
|
201
|
-
|
|
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(
|
|
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
|
-
//
|
|
250
|
-
//
|
|
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.
|
|
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
|
-
|
|
319
|
-
|
|
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
|
-
"
|
|
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
|
-
//
|
|
507
|
-
// leaf module so registry/usage consumers avoid an init cycle
|
|
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
|
-
|
|
544
|
-
|
|
545
|
-
"X-Stainless-
|
|
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-
|
|
549
|
-
"X-Stainless-
|
|
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(
|
|
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(
|
|
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=
|
|
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
|
|
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
|
|
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
|
-
...(
|
|
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 =
|
|
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
|
-
|
|
2973
|
-
?
|
|
2952
|
+
coworkBetas: oauthToken
|
|
2953
|
+
? buildCoworkBetas(
|
|
2974
2954
|
hasTools || thinkingEnabled,
|
|
2975
2955
|
thinkingEnabled,
|
|
2976
|
-
|
|
2956
|
+
(model.contextWindow ?? 0) >= 1_000_000,
|
|
2977
2957
|
disableStrictTools,
|
|
2978
2958
|
)
|
|
2979
2959
|
: [],
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
|
8
|
-
* `providers/anthropic` was the first module loaded.
|
|
8
|
+
* provider module.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
export const
|
|
13
|
-
|
|
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
|
-
|
|
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;
|