@yanlinglabs/winter-provider-runtime 0.0.5 → 0.0.7
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/README.md +13 -4
- package/dist/adapters/anthropic/console-broker.d.ts +104 -0
- package/dist/adapters/anthropic/console-oauth.d.ts +19 -83
- package/dist/adapters/anthropic/index.d.ts +3 -2
- package/dist/adapters/anthropic/messages.d.ts +0 -7
- package/dist/adapters/openai/pkce.d.ts +9 -0
- package/dist/credentials/types.d.ts +9 -1
- package/dist/{index-446qxzss.js → index-m3qxyre5.js} +166 -165
- package/dist/index.d.ts +3 -2
- package/dist/index.js +228 -118
- package/dist/testing.js +2 -2
- package/dist/types.d.ts +8 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -61,12 +61,21 @@ not the same as runnable on every path — these exports need the Bun runtime:
|
|
|
61
61
|
| Function | Import | Needs | Why |
|
|
62
62
|
| --- | --- | --- | --- |
|
|
63
63
|
| `startCodexLogin()` | `@yanlinglabs/winter-provider-runtime` | `Bun.serve` | The authorization-code flow receives the vendor's redirect on `127.0.0.1`, which needs a real HTTP listener. |
|
|
64
|
-
| `startAnthropicConsoleLogin()` | `@yanlinglabs/winter-provider-runtime` | `Bun.serve` | Same flow, same listener. |
|
|
65
64
|
| `startXaiOauthFake()` | `@yanlinglabs/winter-provider-runtime/testing` | `Bun.serve` | Binds a loopback server on `127.0.0.1:0` to stand in for the vendor. |
|
|
66
65
|
| `startXaiChatFake()` | `@yanlinglabs/winter-provider-runtime/testing` | `Bun.serve` | Same. |
|
|
67
|
-
|
|
68
|
-
(
|
|
69
|
-
|
|
66
|
+
| `startAnthropicConsoleBrokerLogin()` | `@yanlinglabs/winter-provider-runtime` | `Bun.spawn` | Spawns Anthropic's own `claude` binary and pipes its stdin/stdout/stderr for the host-brokered Console login (P10a-1 amendment) — never a loopback listener, since this SDK is not itself a party to the OAuth exchange. |
|
|
67
|
+
| `refreshAnthropicBearer()` | `@yanlinglabs/winter-provider-runtime` | `Bun.spawn` | Spawns Anthropic's `ant` binary to mint the native provider's bearer token from the profile the login above wrote. |
|
|
68
|
+
| `logoutAnthropicConsole()` | `@yanlinglabs/winter-provider-runtime` | `Bun.spawn` | Spawns `claude auth logout`. |
|
|
69
|
+
|
|
70
|
+
(`startCodexLogin()` and the two loopback fakes go through one `runLoginFlow`/`Bun.serve` seam, which
|
|
71
|
+
is not on either barrel and which a consumer cannot call. The Console-broker trio is a SEPARATE
|
|
72
|
+
mechanism — each of the three guards itself directly, with no shared seam, since spawning a
|
|
73
|
+
subprocess and piping its stdio is a different shape from opening a listener.)
|
|
74
|
+
|
|
75
|
+
**`startAnthropicConsoleLogin()` is RETIRED (P10a-1, 2026-09-13)**: the derived PKCE login it ran —
|
|
76
|
+
re-implementing Claude Code's private OAuth client — was deleted after the platform refused its grant
|
|
77
|
+
for every derivable request shape. Console OAuth is host-brokered now, through the three functions
|
|
78
|
+
above; this SDK must never re-implement the OAuth protocol itself again.
|
|
70
79
|
|
|
71
80
|
Each throws `BunRequiredError` (exported from both barrels) as its FIRST action — before any network
|
|
72
81
|
call, file write or credential read — naming the function, the Bun API and what to do instead. Catch
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { CredentialStore } from "../../types.js";
|
|
2
|
+
/** `ANTHROPIC_PROFILE`'s default (P10a-2) -- one profile for the login, the official leg, and every `ant` call. */
|
|
3
|
+
export declare const DEFAULT_ANTHROPIC_CONSOLE_PROFILE = "winter";
|
|
4
|
+
/** Config every door in this file shares -- one process env, one profile, one pair of binaries. */
|
|
5
|
+
export interface AnthropicConsoleBrokerOptions {
|
|
6
|
+
/** The resolved `claude` executable -- the SAME binary the official leg spawns (P9c-1). */
|
|
7
|
+
claudeExecutable: string;
|
|
8
|
+
/**
|
|
9
|
+
* The resolved `ant` executable. Absent means "not installed/resolved" -- `refreshAnthropicBearer`
|
|
10
|
+
* answers a named, typed failure rather than throwing or leaving a login half-finished.
|
|
11
|
+
*/
|
|
12
|
+
antExecutable?: string;
|
|
13
|
+
/** `<home>/runtimes/anthropic-config` (P10a-2). Created by the HOST before this file is ever called; this file only reads and writes inside it, never creates it. */
|
|
14
|
+
anthropicConfigDir: string;
|
|
15
|
+
/** P9c-1's own config dir for the official leg's OWN credentials, carried on every spawn alongside `anthropicConfigDir` because a build of `claude` may read either variable. */
|
|
16
|
+
claudeConfigDir: string;
|
|
17
|
+
/** `ANTHROPIC_PROFILE`. Defaults to `DEFAULT_ANTHROPIC_CONSOLE_PROFILE`. */
|
|
18
|
+
profile?: string;
|
|
19
|
+
/** The Keychain service the bearer material lands in -- `config.keychainService` from the host. */
|
|
20
|
+
service?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Every stdout/stderr line, in the order this file observed them, REDACTED of any URL's query
|
|
23
|
+
* string before this file ever calls it (R6-F: a progress channel, never material).
|
|
24
|
+
*/
|
|
25
|
+
onLine?: (line: string) => void;
|
|
26
|
+
/** Injectable for a fixture: a stub executable under mkdtemp, never the real `claude`/`ant`. */
|
|
27
|
+
spawn?: typeof Bun.spawn;
|
|
28
|
+
now?: () => number;
|
|
29
|
+
}
|
|
30
|
+
export interface AnthropicConsoleLoginHandle {
|
|
31
|
+
/**
|
|
32
|
+
* Writes `code + "\n"` to the child's stdin and closes it -- exactly what a human types at the
|
|
33
|
+
* `Paste code here if prompted > ` prompt. Resolves once the write completes, NOT once the login
|
|
34
|
+
* finishes; await `done` for that. Calling this before the child is ready for input, or more than
|
|
35
|
+
* once, is a caller error this file does not guard against -- the same trust boundary `openUrl`
|
|
36
|
+
* callers already hold for every other login in this package.
|
|
37
|
+
*/
|
|
38
|
+
submitCode(code: string): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Resolves on process exit. A non-zero exit is `{ ok: false, reason }`, the reason being the last
|
|
41
|
+
* non-empty stderr line this file observed (already redacted) -- never the bare exit code alone,
|
|
42
|
+
* and never a URL. A ZERO exit is only `{ ok: true, profile }` once `refreshAnthropicBearer` has
|
|
43
|
+
* ALSO succeeded: an exit-0 login that could not mint a usable bearer is not a login Winter can act
|
|
44
|
+
* on, so this file does not report success until both steps have.
|
|
45
|
+
*/
|
|
46
|
+
done: Promise<{
|
|
47
|
+
ok: true;
|
|
48
|
+
profile: string;
|
|
49
|
+
} | {
|
|
50
|
+
ok: false;
|
|
51
|
+
reason: string;
|
|
52
|
+
}>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Fix round 1, item 3 (MINOR): a TYPED refusal from `submitCode`, rather than a silent no-op or an
|
|
56
|
+
* untyped throw -- a caller that raced its own timeout against a slow paste, or that mis-drives this
|
|
57
|
+
* handle from two places, gets something it can `instanceof`-check rather than a message to parse.
|
|
58
|
+
*/
|
|
59
|
+
export type SubmitCodeRefusalReason = "already-submitted" | "not-running";
|
|
60
|
+
export declare class SubmitCodeRefused extends Error {
|
|
61
|
+
readonly name = "SubmitCodeRefused";
|
|
62
|
+
readonly reason: SubmitCodeRefusalReason;
|
|
63
|
+
constructor(reason: SubmitCodeRefusalReason, message: string);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Starts `claude auth login --console` and returns a handle a host drives interactively: it prints
|
|
67
|
+
* the authorize URL and a prompt through `onLine`, and the host calls `submitCode` once the operator
|
|
68
|
+
* has one. See the file banner for what was MEASURED about this shape rather than assumed.
|
|
69
|
+
*
|
|
70
|
+
* SYNCHRONOUS RETURN, matching `runGit`'s own precedent for a spawn that can fail before any process
|
|
71
|
+
* exists: `Bun.spawn` throws SYNCHRONOUSLY on an unresolvable executable (ENOENT), and a function that
|
|
72
|
+
* only surfaced that inside an async `done` would leave a caller unable to tell "the binary doesn't
|
|
73
|
+
* exist" from "the login is running" until the first `await`. Here it is instead reflected into an
|
|
74
|
+
* ALREADY-SETTLED handle: `done` is already resolved `{ ok: false, reason }`, and `submitCode` REFUSES
|
|
75
|
+
* (fix round 1, item 3) with `SubmitCodeRefused("not-running", …)` — there is no process to write to,
|
|
76
|
+
* which is the SAME condition a call after a real process exits refuses under.
|
|
77
|
+
*/
|
|
78
|
+
export declare function startAnthropicConsoleBrokerLogin(store: CredentialStore, options: AnthropicConsoleBrokerOptions): AnthropicConsoleLoginHandle;
|
|
79
|
+
/**
|
|
80
|
+
* Runs `ant auth print-credentials --profile <profile> --access-token` and writes the bearer material
|
|
81
|
+
* `anthropic:default` (P10a-4) -- called automatically by `startAnthropicConsoleBrokerLogin` on a
|
|
82
|
+
* successful login, and separately by a host's own refresh timer (60 s before `expiresAt`, per
|
|
83
|
+
* P10a-4) since renewal never re-runs the interactive login.
|
|
84
|
+
*
|
|
85
|
+
* A FAILURE LEAVES ANY EXISTING MATERIAL UNTOUCHED: this function never calls `store.set` on any path
|
|
86
|
+
* that did not itself produce a fresh token, so a transient failure of the broker cannot erase a
|
|
87
|
+
* still-good credential.
|
|
88
|
+
*/
|
|
89
|
+
export declare function refreshAnthropicBearer(store: CredentialStore, options: AnthropicConsoleBrokerOptions): Promise<{
|
|
90
|
+
ok: true;
|
|
91
|
+
expiresAt: number;
|
|
92
|
+
} | {
|
|
93
|
+
ok: false;
|
|
94
|
+
reason: string;
|
|
95
|
+
}>;
|
|
96
|
+
/** Does `<anthropicConfigDir>/credentials/<profile>.json` exist? A plain file-exists check -- this file never opens or parses it here. */
|
|
97
|
+
export declare function anthropicConsoleProfileExists(anthropicConfigDir: string, profile?: string): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Runs `claude auth logout` (best-effort) and then deletes the bearer material regardless of whether
|
|
100
|
+
* the binary succeeded -- a host that cannot reach the binary should still be able to forget the
|
|
101
|
+
* bearer material it already holds; leaving it in the Keychain because a subprocess failed would be
|
|
102
|
+
* the worse of the two failures.
|
|
103
|
+
*/
|
|
104
|
+
export declare function logoutAnthropicConsole(store: CredentialStore, options: AnthropicConsoleBrokerOptions): Promise<void>;
|
|
@@ -1,41 +1,4 @@
|
|
|
1
|
-
import type { CredentialRef
|
|
2
|
-
/**
|
|
3
|
-
* The Console OAuth constants, derived from `@anthropic-ai/claude-agent-sdk@0.3.250`.
|
|
4
|
-
*
|
|
5
|
-
* Pinned field-by-field against `compat/anthropic/0.3.250/derived-p6b.ts`. Note what is NOT here:
|
|
6
|
-
* the consumer authorize host, the consumer origin, the two `claude_cli`-scoped endpoints, and the
|
|
7
|
-
* vendor's own product beta. A constant that exists is a constant something can come to use, so the
|
|
8
|
-
* excluded ones live only in the capture document, as a record of the exclusion.
|
|
9
|
-
*/
|
|
10
|
-
export declare const CONSOLE_OAUTH: {
|
|
11
|
-
/** A PUBLIC PKCE client id from a public npm artifact — there is no client secret, and none is sent. */
|
|
12
|
-
readonly clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
13
|
-
readonly authorizeUrl: "https://platform.claude.com/oauth/authorize";
|
|
14
|
-
readonly tokenUrl: "https://platform.claude.com/v1/oauth/token";
|
|
15
|
-
/** The account id is NOT in the token response; it is read from here, as `account.uuid`. */
|
|
16
|
-
readonly profileUrl: "https://api.anthropic.com/api/oauth/profile";
|
|
17
|
-
readonly scope: "user:inference user:profile";
|
|
18
|
-
/**
|
|
19
|
-
* `0` — an ephemeral port, and the DERIVED value rather than a test convenience. This client's
|
|
20
|
-
* registration accepts a loopback URI on any port; codex's fixed 1455/1457 pair is the opposite
|
|
21
|
-
* case, and copying that shape here would bind a port for no reason and fight a concurrent login.
|
|
22
|
-
*/
|
|
23
|
-
readonly callbackPort: 0;
|
|
24
|
-
readonly callbackPath: "/callback";
|
|
25
|
-
/** The `anthropic-beta` value that accompanies an OAuth bearer on every request. */
|
|
26
|
-
readonly betaHeader: "oauth-2025-04-20";
|
|
27
|
-
/**
|
|
28
|
-
* The profile-response field that names the credential record, as a dotted path.
|
|
29
|
-
*
|
|
30
|
-
* Present so that EVERY field of the capture's derived table has a counterpart here and the
|
|
31
|
-
* constants test gates all nine — a derived value with no shipped twin is a value nothing stops
|
|
32
|
-
* from drifting. `fetchAccountId` reads exactly this path; it is stated rather than walked because
|
|
33
|
-
* one fixed shape does not need a path interpreter.
|
|
34
|
-
*/
|
|
35
|
-
readonly accountIdPath: "account.uuid";
|
|
36
|
-
};
|
|
37
|
-
/** How long before expiry a token is renewed rather than used. One minute of slack over a turn that may take seconds to start. */
|
|
38
|
-
export declare const OAUTH_REFRESH_WINDOW_MS = 60000;
|
|
1
|
+
import type { CredentialRef } from "../../types.js";
|
|
39
2
|
/**
|
|
40
3
|
* The ONE provider id whose `oauth` credential is an Anthropic Console one.
|
|
41
4
|
*
|
|
@@ -43,16 +6,28 @@ export declare const OAUTH_REFRESH_WINDOW_MS = 60000;
|
|
|
43
6
|
* condition: R6b-5 makes this adapter MULTI-PROVIDER — a third party that speaks the Anthropic
|
|
44
7
|
* Messages dialect ships as its own `<id>-anthropic` row on this same `adapterId`, with its own
|
|
45
8
|
* `defaultEndpoints.api`. Nothing upstream of the adapter checks that a stored credential's KIND
|
|
46
|
-
* matches its row's `authKinds`, so without this gate
|
|
47
|
-
*
|
|
48
|
-
* to Anthropic — and would stamp Anthropic's beta on that third party's request. Both are the same
|
|
49
|
-
* mistake the `bearer` arm already refuses to make, with a considerably worse failure.
|
|
9
|
+
* matches its row's `authKinds`, so without this gate a sibling row's `oauth` credential would have
|
|
10
|
+
* Anthropic's beta stamped on its request — a third party's turn wearing Anthropic's own header.
|
|
50
11
|
*/
|
|
51
12
|
export declare const ANTHROPIC_CONSOLE_PROVIDER_ID = "anthropic";
|
|
52
13
|
/**
|
|
53
|
-
*
|
|
14
|
+
* What survives of the old `CONSOLE_OAUTH` object once its login-only fields are gone: the ONE header
|
|
15
|
+
* value `messages.ts` still sends alongside an Anthropic Console credential's bearer -- an `oauth`-
|
|
16
|
+
* kind one, or (P10a, M5) a `bearer`-kind one from the host-brokered `console-broker.ts` leg, on the
|
|
17
|
+
* `anthropic` provider row only (`isConsoleProvider`).
|
|
18
|
+
*
|
|
19
|
+
* See the file banner for why this is renamed rather than trimmed in place. P10a-1/M3 measured that
|
|
20
|
+
* the header belongs on the `anthropic` row's bearer material too, not only on `oauth`; see
|
|
21
|
+
* `messages.ts`'s `buildHeaders` for the settled gate.
|
|
22
|
+
*/
|
|
23
|
+
export declare const CONSOLE_BEARER: {
|
|
24
|
+
/** The `anthropic-beta` value that accompanies an OAuth or Console bearer on every request. */
|
|
25
|
+
readonly betaHeader: "oauth-2025-04-20";
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The ONE spelling of an Anthropic OAuth/bearer record's name (R6-10).
|
|
54
29
|
*
|
|
55
|
-
* Exported and used by both
|
|
30
|
+
* Exported and used by both a host's broker and the adapter, because a host that assembles
|
|
56
31
|
* `anthropic:<id>` by hand will eventually assemble it differently from whatever reads it — a
|
|
57
32
|
* credential written to a key nothing looks up, failing as "no credential configured" with the
|
|
58
33
|
* record sitting right there.
|
|
@@ -60,42 +35,3 @@ export declare const ANTHROPIC_CONSOLE_PROVIDER_ID = "anthropic";
|
|
|
60
35
|
export declare function anthropicCredentialRef(accountId: string, service?: string): Extract<CredentialRef, {
|
|
61
36
|
kind: "keychain";
|
|
62
37
|
}>;
|
|
63
|
-
export interface AnthropicConsoleLoginOptions {
|
|
64
|
-
/** Opens the browser. HOST-supplied: the SDK never shells out to one itself. */
|
|
65
|
-
openUrl: (url: string) => Promise<void>;
|
|
66
|
-
/** Overridden by a fixture; production uses `CONSOLE_OAUTH`'s own values. */
|
|
67
|
-
authorizeUrl?: string;
|
|
68
|
-
tokenUrl?: string;
|
|
69
|
-
profileUrl?: string;
|
|
70
|
-
callbackPort?: number;
|
|
71
|
-
timeoutMs?: number;
|
|
72
|
-
/** The Keychain service the record lands in — `config.keychainService` from the host. */
|
|
73
|
-
service?: string;
|
|
74
|
-
onAuthStatus?: (status: {
|
|
75
|
-
isAuthenticating: boolean;
|
|
76
|
-
output?: string[];
|
|
77
|
-
error?: string;
|
|
78
|
-
}) => void;
|
|
79
|
-
}
|
|
80
|
-
export interface AnthropicConsoleLoginResult {
|
|
81
|
-
ref: Extract<CredentialRef, {
|
|
82
|
-
kind: "keychain";
|
|
83
|
-
}>;
|
|
84
|
-
accountId: string;
|
|
85
|
-
/** Epoch milliseconds. */
|
|
86
|
-
expiresAt: number;
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* The host-invoked Console login. Runs the PKCE loopback flow, asks who signed in, and PERSISTS the
|
|
90
|
-
* result through the credential store — returning the ref a session should then be configured with.
|
|
91
|
-
*
|
|
92
|
-
* WHY THERE IS A SECOND REQUEST. The token response carries no account of any kind (the capture's
|
|
93
|
-
* §2.3), so unlike codex — where the id token's own claim names the record — the account id has to
|
|
94
|
-
* be fetched. `GET /api/oauth/profile` under the new bearer is what the pinned artifact itself does,
|
|
95
|
-
* and `user:profile` is in the scope precisely to authorise it.
|
|
96
|
-
*
|
|
97
|
-
* A LOGIN THAT CANNOT NAME ITS RECORD IS A REFUSAL, not a fallback to some default slot: R6-10 is
|
|
98
|
-
* explicit that a credential occupies one record per provider/account, and `anthropic:undefined`
|
|
99
|
-
* would be a shared global slot wearing a per-account name.
|
|
100
|
-
*/
|
|
101
|
-
export declare function startAnthropicConsoleLogin(store: CredentialStore, options: AnthropicConsoleLoginOptions): Promise<AnthropicConsoleLoginResult>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { ANTHROPIC_ADAPTER_ID, ANTHROPIC_API_VERSION, ANTHROPIC_DEFAULT_BASE_URL, ANTHROPIC_DEFAULT_MAX_TOKENS, createAnthropicMessagesAdapter, findDescriptor, mapAnthropicEffort, toWireMessages, } from "./messages.js";
|
|
2
2
|
export type { AnthropicAdapterOptions, EffortMapping } from "./messages.js";
|
|
3
|
-
export {
|
|
4
|
-
export
|
|
3
|
+
export { CONSOLE_BEARER, anthropicCredentialRef } from "./console-oauth.js";
|
|
4
|
+
export { DEFAULT_ANTHROPIC_CONSOLE_PROFILE, anthropicConsoleProfileExists, logoutAnthropicConsole, refreshAnthropicBearer, startAnthropicConsoleBrokerLogin, } from "./console-broker.js";
|
|
5
|
+
export type { AnthropicConsoleBrokerOptions, AnthropicConsoleLoginHandle } from "./console-broker.js";
|
|
@@ -30,13 +30,6 @@ export interface AnthropicAdapterOptions {
|
|
|
30
30
|
/** `anthropic-beta` values, joined with commas. A PROTOCOL header (R6-L): every endpoint needs it to be spoken to, and it names no account. */
|
|
31
31
|
betas?: string[];
|
|
32
32
|
defaultMaxOutputTokens?: number;
|
|
33
|
-
/**
|
|
34
|
-
* The OAuth token endpoint a near-expiry `oauth` credential is renewed through (D20).
|
|
35
|
-
*
|
|
36
|
-
* Injectable for a fixture exactly as codex's is; production uses `CONSOLE_OAUTH.tokenUrl`. It has
|
|
37
|
-
* no effect on an `api-key` credential, which is every other row this adapter serves.
|
|
38
|
-
*/
|
|
39
|
-
tokenUrl?: string;
|
|
40
33
|
}
|
|
41
34
|
export declare function toWireMessages(messages: ProviderMessageLike[]): Array<{
|
|
42
35
|
role: "user" | "assistant";
|
|
@@ -65,6 +65,14 @@ export interface LoginConfig {
|
|
|
65
65
|
*/
|
|
66
66
|
includeStateInTokenRequest?: boolean;
|
|
67
67
|
scope: string;
|
|
68
|
+
/**
|
|
69
|
+
* Extra authorize-request query parameters a client's registration REQUIRES beyond the PKCE set —
|
|
70
|
+
* appended after the standard parameters. Opt-in per login so one endpoint's shape changes nothing
|
|
71
|
+
* about another's. The Anthropic Console client needs `code=true` (derived-shapes-p6b.md §2.2, the
|
|
72
|
+
* FIRST parameter of its authorize request); without it the authorize page answers
|
|
73
|
+
* "Authorization failed — Invalid request format" before any callback (measured 2026-09-13).
|
|
74
|
+
*/
|
|
75
|
+
extraAuthorizeParams?: Record<string, string>;
|
|
68
76
|
timeoutMs?: number;
|
|
69
77
|
/** Opens the browser. HOST-supplied: the SDK never shells out to a browser itself. */
|
|
70
78
|
openUrl: (url: string) => Promise<void>;
|
|
@@ -108,4 +116,5 @@ export declare function buildAuthorizeUrl(cfg: {
|
|
|
108
116
|
scope: string;
|
|
109
117
|
state: string;
|
|
110
118
|
challenge: string;
|
|
119
|
+
extraAuthorizeParams?: Record<string, string>;
|
|
111
120
|
}): string;
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { CredentialMaterial, CredentialRef, CredentialStore } from "../types.js";
|
|
2
|
-
export type CredentialResolutionCode = "unsupported" | "malformed" | "io"
|
|
2
|
+
export type CredentialResolutionCode = "unsupported" | "malformed" | "io"
|
|
3
|
+
/**
|
|
4
|
+
* P10a-1 (2026-09-13): a `startProviderLogin` refusal, not a store one — added here rather than
|
|
5
|
+
* reusing `"unsupported"` because a host needs to tell "this login is not wired" (`qoder`) apart
|
|
6
|
+
* from "this login exists, but it is not this SDK's to run" (Anthropic Console). The Console flow
|
|
7
|
+
* is host-brokered (`claude auth login --console` / `ant auth print-credentials`); this SDK must
|
|
8
|
+
* never implement it again.
|
|
9
|
+
*/
|
|
10
|
+
| "console_login_is_host_brokered";
|
|
3
11
|
/**
|
|
4
12
|
* A typed credential failure. **Its message never contains credential material** — construct it from
|
|
5
13
|
* a locator (`redactRef`) and a reason, never from a file's contents or a parsed value. The stores
|
|
@@ -40996,7 +40996,7 @@ import { CLAUDE_RESERVED_SLOT_NAMES as CLAUDE_RESERVED_SLOT_NAMES2, CURRENCY_RE
|
|
|
40996
40996
|
// package.json
|
|
40997
40997
|
var package_default = {
|
|
40998
40998
|
name: "@yanlinglabs/winter-provider-runtime",
|
|
40999
|
-
version: "0.0.
|
|
40999
|
+
version: "0.0.7",
|
|
41000
41000
|
license: "MIT",
|
|
41001
41001
|
type: "module",
|
|
41002
41002
|
engines: {
|
|
@@ -41241,169 +41241,6 @@ async function* parseSse2(body, opts) {
|
|
|
41241
41241
|
}
|
|
41242
41242
|
}
|
|
41243
41243
|
|
|
41244
|
-
// src/adapters/openai/pkce.ts
|
|
41245
|
-
var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
41246
|
-
function base64Url2(bytes) {
|
|
41247
|
-
let out = "";
|
|
41248
|
-
for (let i = 0;i < bytes.length; i += 3) {
|
|
41249
|
-
const a = bytes[i];
|
|
41250
|
-
const b = bytes[i + 1];
|
|
41251
|
-
const c = bytes[i + 2];
|
|
41252
|
-
out += BASE64URL_ALPHABET[a >> 2];
|
|
41253
|
-
out += BASE64URL_ALPHABET[(a & 3) << 4 | (b ?? 0) >> 4];
|
|
41254
|
-
if (b === undefined)
|
|
41255
|
-
break;
|
|
41256
|
-
out += BASE64URL_ALPHABET[(b & 15) << 2 | (c ?? 0) >> 6];
|
|
41257
|
-
if (c === undefined)
|
|
41258
|
-
break;
|
|
41259
|
-
out += BASE64URL_ALPHABET[c & 63];
|
|
41260
|
-
}
|
|
41261
|
-
return out;
|
|
41262
|
-
}
|
|
41263
|
-
function randomBase64Url(byteLength) {
|
|
41264
|
-
const bytes = new Uint8Array(byteLength);
|
|
41265
|
-
crypto.getRandomValues(bytes);
|
|
41266
|
-
return base64Url2(bytes);
|
|
41267
|
-
}
|
|
41268
|
-
async function generatePkce() {
|
|
41269
|
-
const verifier = randomBase64Url(48);
|
|
41270
|
-
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
41271
|
-
return { verifier, challenge: base64Url2(new Uint8Array(digest)) };
|
|
41272
|
-
}
|
|
41273
|
-
function decodeAccountId(idToken) {
|
|
41274
|
-
try {
|
|
41275
|
-
const payloadSegment = idToken.split(".")[1];
|
|
41276
|
-
if (payloadSegment === undefined)
|
|
41277
|
-
return;
|
|
41278
|
-
const normalized = payloadSegment.replace(/-/g, "+").replace(/_/g, "/");
|
|
41279
|
-
const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
|
|
41280
|
-
const payload = JSON.parse(atob(padded));
|
|
41281
|
-
const auth = payload["https://api.openai.com/auth"];
|
|
41282
|
-
if (auth === null || typeof auth !== "object")
|
|
41283
|
-
return;
|
|
41284
|
-
const accountId = auth.chatgpt_account_id;
|
|
41285
|
-
return typeof accountId === "string" && accountId.length > 0 ? accountId : undefined;
|
|
41286
|
-
} catch {
|
|
41287
|
-
return;
|
|
41288
|
-
}
|
|
41289
|
-
}
|
|
41290
|
-
async function exchange(tokenUrl, params, label = "codex", bodyEncoding = "form") {
|
|
41291
|
-
const built = createEndpointPolicy2(new URL(tokenUrl).origin, { generated: true });
|
|
41292
|
-
if (!built.ok)
|
|
41293
|
-
throw new ProviderRequestError2({ code: "capability", message: built.reason, retryable: false });
|
|
41294
|
-
const response = await boundedFetch2(tokenUrl, {
|
|
41295
|
-
method: "POST",
|
|
41296
|
-
headers: { "content-type": bodyEncoding === "json" ? "application/json" : "application/x-www-form-urlencoded", accept: "application/json", "user-agent": winterUserAgent2() },
|
|
41297
|
-
body: bodyEncoding === "json" ? JSON.stringify(params) : new URLSearchParams(params).toString(),
|
|
41298
|
-
policy: built.policy,
|
|
41299
|
-
maxBodyBytes: 512 * 1024,
|
|
41300
|
-
timeoutMs: 30000
|
|
41301
|
-
});
|
|
41302
|
-
if (!response.ok) {
|
|
41303
|
-
await response.text().catch(() => "");
|
|
41304
|
-
throw new ProviderRequestError2({ code: response.status === 400 || response.status === 401 ? "auth" : "server", message: `${label} token exchange failed: HTTP ${response.status}`, status: response.status, retryable: response.status >= 500 });
|
|
41305
|
-
}
|
|
41306
|
-
const payload = await response.json();
|
|
41307
|
-
if (typeof payload.access_token !== "string" || payload.access_token.length === 0) {
|
|
41308
|
-
throw new ProviderRequestError2({ code: "auth", message: `${label} token exchange returned no access token`, retryable: false });
|
|
41309
|
-
}
|
|
41310
|
-
const idToken = typeof payload.id_token === "string" ? payload.id_token : undefined;
|
|
41311
|
-
const accountId = idToken !== undefined ? decodeAccountId(idToken) : undefined;
|
|
41312
|
-
return {
|
|
41313
|
-
accessToken: payload.access_token,
|
|
41314
|
-
...typeof payload.refresh_token === "string" ? { refreshToken: payload.refresh_token } : {},
|
|
41315
|
-
...idToken !== undefined ? { idToken } : {},
|
|
41316
|
-
...accountId !== undefined ? { accountId } : {},
|
|
41317
|
-
expiresAt: Date.now() + (typeof payload.expires_in === "number" ? payload.expires_in : 3600) * 1000
|
|
41318
|
-
};
|
|
41319
|
-
}
|
|
41320
|
-
function refreshTokens(tokenUrl, clientId, refreshToken) {
|
|
41321
|
-
return exchange(tokenUrl, { grant_type: "refresh_token", client_id: clientId, refresh_token: refreshToken });
|
|
41322
|
-
}
|
|
41323
|
-
async function runLoginFlow(cfg) {
|
|
41324
|
-
requireBunRuntime2(cfg.functionName ?? "runLoginFlow", "Bun.serve", "The authorization-code flow has to receive the vendor's redirect on 127.0.0.1, which needs a real HTTP listener. Run the login under Bun (or complete it in a Bun process and pass the resulting credential ref to your Node session).");
|
|
41325
|
-
const { verifier, challenge } = await generatePkce();
|
|
41326
|
-
const state = randomBase64Url(16);
|
|
41327
|
-
const report = (output) => cfg.onAuthStatus?.({ isAuthenticating: true, output: [output] });
|
|
41328
|
-
let resolveCode;
|
|
41329
|
-
let rejectFlow;
|
|
41330
|
-
const codePromise = new Promise((resolve, reject) => {
|
|
41331
|
-
resolveCode = resolve;
|
|
41332
|
-
rejectFlow = reject;
|
|
41333
|
-
});
|
|
41334
|
-
codePromise.catch(() => {});
|
|
41335
|
-
const callbackPath = cfg.callbackPath ?? "/auth/callback";
|
|
41336
|
-
const label = cfg.label ?? "codex";
|
|
41337
|
-
const ports = cfg.callbackPort === 0 ? [0] : [cfg.callbackPort, ...cfg.fallbackCallbackPort !== undefined ? [cfg.fallbackCallbackPort] : []];
|
|
41338
|
-
let server;
|
|
41339
|
-
let lastError;
|
|
41340
|
-
for (const port of ports) {
|
|
41341
|
-
try {
|
|
41342
|
-
server = Bun.serve({
|
|
41343
|
-
port,
|
|
41344
|
-
hostname: "127.0.0.1",
|
|
41345
|
-
fetch(req) {
|
|
41346
|
-
const url = new URL(req.url);
|
|
41347
|
-
if (url.pathname !== callbackPath)
|
|
41348
|
-
return new Response("not found", { status: 404 });
|
|
41349
|
-
if (url.searchParams.get("state") !== state) {
|
|
41350
|
-
rejectFlow(new Error("OAuth state mismatch — refusing to complete a login this process did not start"));
|
|
41351
|
-
return new Response("state mismatch", { status: 400 });
|
|
41352
|
-
}
|
|
41353
|
-
const code = url.searchParams.get("code");
|
|
41354
|
-
if (code === null || code.length === 0) {
|
|
41355
|
-
rejectFlow(new Error("the OAuth callback carried no authorization code"));
|
|
41356
|
-
return new Response("missing code", { status: 400 });
|
|
41357
|
-
}
|
|
41358
|
-
resolveCode(code);
|
|
41359
|
-
return new Response("Signed in — you can close this tab.", { headers: { "content-type": "text/plain" } });
|
|
41360
|
-
}
|
|
41361
|
-
});
|
|
41362
|
-
break;
|
|
41363
|
-
} catch (err) {
|
|
41364
|
-
lastError = err;
|
|
41365
|
-
}
|
|
41366
|
-
}
|
|
41367
|
-
if (server === undefined) {
|
|
41368
|
-
const detail = lastError instanceof Error ? lastError.message : String(lastError);
|
|
41369
|
-
throw new ProviderRequestError2({ code: "capability", message: `could not open the ${label} login callback on port ${ports.join(" or ")} — is another login in progress? (${detail})`, retryable: false });
|
|
41370
|
-
}
|
|
41371
|
-
const redirectUri = `http://localhost:${server.port}${callbackPath}`;
|
|
41372
|
-
const authUrl = new URL(cfg.authorizeUrl);
|
|
41373
|
-
authUrl.search = new URLSearchParams({
|
|
41374
|
-
response_type: "code",
|
|
41375
|
-
client_id: cfg.clientId,
|
|
41376
|
-
redirect_uri: redirectUri,
|
|
41377
|
-
scope: cfg.scope,
|
|
41378
|
-
state,
|
|
41379
|
-
code_challenge: challenge,
|
|
41380
|
-
code_challenge_method: "S256"
|
|
41381
|
-
}).toString();
|
|
41382
|
-
const timeout = setTimeout(() => rejectFlow(new Error(`the ${label} login timed out`)), cfg.timeoutMs ?? 5 * 60000);
|
|
41383
|
-
try {
|
|
41384
|
-
report("opening the browser for sign-in");
|
|
41385
|
-
cfg.openUrl(authUrl.toString()).catch((err) => rejectFlow(new Error(`could not open the browser: ${err instanceof Error ? err.message : String(err)}`)));
|
|
41386
|
-
const code = await codePromise;
|
|
41387
|
-
report("exchanging the authorization code");
|
|
41388
|
-
const tokens = await exchange(cfg.tokenUrl, {
|
|
41389
|
-
grant_type: "authorization_code",
|
|
41390
|
-
client_id: cfg.clientId,
|
|
41391
|
-
code,
|
|
41392
|
-
redirect_uri: redirectUri,
|
|
41393
|
-
code_verifier: verifier,
|
|
41394
|
-
...cfg.includeStateInTokenRequest === true ? { state } : {}
|
|
41395
|
-
}, label, cfg.bodyEncoding ?? "form");
|
|
41396
|
-
cfg.onAuthStatus?.({ isAuthenticating: false, output: ["signed in"] });
|
|
41397
|
-
return tokens;
|
|
41398
|
-
} catch (err) {
|
|
41399
|
-
cfg.onAuthStatus?.({ isAuthenticating: false, error: err instanceof Error ? err.message : String(err) });
|
|
41400
|
-
throw err;
|
|
41401
|
-
} finally {
|
|
41402
|
-
clearTimeout(timeout);
|
|
41403
|
-
server.stop(true);
|
|
41404
|
-
}
|
|
41405
|
-
}
|
|
41406
|
-
|
|
41407
41244
|
// src/registry.ts
|
|
41408
41245
|
class WinterProviderResolutionError2 extends Error {
|
|
41409
41246
|
code;
|
|
@@ -43721,6 +43558,170 @@ function codexCredentialAccount2(accountId) {
|
|
|
43721
43558
|
return `codex-oauth:${accountId}`;
|
|
43722
43559
|
}
|
|
43723
43560
|
|
|
43561
|
+
// src/adapters/openai/pkce.ts
|
|
43562
|
+
var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
43563
|
+
function base64Url2(bytes) {
|
|
43564
|
+
let out = "";
|
|
43565
|
+
for (let i = 0;i < bytes.length; i += 3) {
|
|
43566
|
+
const a = bytes[i];
|
|
43567
|
+
const b = bytes[i + 1];
|
|
43568
|
+
const c = bytes[i + 2];
|
|
43569
|
+
out += BASE64URL_ALPHABET[a >> 2];
|
|
43570
|
+
out += BASE64URL_ALPHABET[(a & 3) << 4 | (b ?? 0) >> 4];
|
|
43571
|
+
if (b === undefined)
|
|
43572
|
+
break;
|
|
43573
|
+
out += BASE64URL_ALPHABET[(b & 15) << 2 | (c ?? 0) >> 6];
|
|
43574
|
+
if (c === undefined)
|
|
43575
|
+
break;
|
|
43576
|
+
out += BASE64URL_ALPHABET[c & 63];
|
|
43577
|
+
}
|
|
43578
|
+
return out;
|
|
43579
|
+
}
|
|
43580
|
+
function randomBase64Url(byteLength) {
|
|
43581
|
+
const bytes = new Uint8Array(byteLength);
|
|
43582
|
+
crypto.getRandomValues(bytes);
|
|
43583
|
+
return base64Url2(bytes);
|
|
43584
|
+
}
|
|
43585
|
+
async function generatePkce() {
|
|
43586
|
+
const verifier = randomBase64Url(48);
|
|
43587
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
43588
|
+
return { verifier, challenge: base64Url2(new Uint8Array(digest)) };
|
|
43589
|
+
}
|
|
43590
|
+
function decodeAccountId(idToken) {
|
|
43591
|
+
try {
|
|
43592
|
+
const payloadSegment = idToken.split(".")[1];
|
|
43593
|
+
if (payloadSegment === undefined)
|
|
43594
|
+
return;
|
|
43595
|
+
const normalized = payloadSegment.replace(/-/g, "+").replace(/_/g, "/");
|
|
43596
|
+
const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
|
|
43597
|
+
const payload = JSON.parse(atob(padded));
|
|
43598
|
+
const auth = payload["https://api.openai.com/auth"];
|
|
43599
|
+
if (auth === null || typeof auth !== "object")
|
|
43600
|
+
return;
|
|
43601
|
+
const accountId = auth.chatgpt_account_id;
|
|
43602
|
+
return typeof accountId === "string" && accountId.length > 0 ? accountId : undefined;
|
|
43603
|
+
} catch {
|
|
43604
|
+
return;
|
|
43605
|
+
}
|
|
43606
|
+
}
|
|
43607
|
+
async function exchange(tokenUrl, params, label = "codex", bodyEncoding = "form") {
|
|
43608
|
+
const built = createEndpointPolicy2(new URL(tokenUrl).origin, { generated: true });
|
|
43609
|
+
if (!built.ok)
|
|
43610
|
+
throw new ProviderRequestError2({ code: "capability", message: built.reason, retryable: false });
|
|
43611
|
+
const response = await boundedFetch2(tokenUrl, {
|
|
43612
|
+
method: "POST",
|
|
43613
|
+
headers: { "content-type": bodyEncoding === "json" ? "application/json" : "application/x-www-form-urlencoded", accept: "application/json", "user-agent": winterUserAgent2() },
|
|
43614
|
+
body: bodyEncoding === "json" ? JSON.stringify(params) : new URLSearchParams(params).toString(),
|
|
43615
|
+
policy: built.policy,
|
|
43616
|
+
maxBodyBytes: 512 * 1024,
|
|
43617
|
+
timeoutMs: 30000
|
|
43618
|
+
});
|
|
43619
|
+
if (!response.ok) {
|
|
43620
|
+
await response.text().catch(() => "");
|
|
43621
|
+
throw new ProviderRequestError2({ code: response.status === 400 || response.status === 401 ? "auth" : "server", message: `${label} token exchange failed: HTTP ${response.status}`, status: response.status, retryable: response.status >= 500 });
|
|
43622
|
+
}
|
|
43623
|
+
const payload = await response.json();
|
|
43624
|
+
if (typeof payload.access_token !== "string" || payload.access_token.length === 0) {
|
|
43625
|
+
throw new ProviderRequestError2({ code: "auth", message: `${label} token exchange returned no access token`, retryable: false });
|
|
43626
|
+
}
|
|
43627
|
+
const idToken = typeof payload.id_token === "string" ? payload.id_token : undefined;
|
|
43628
|
+
const accountId = idToken !== undefined ? decodeAccountId(idToken) : undefined;
|
|
43629
|
+
return {
|
|
43630
|
+
accessToken: payload.access_token,
|
|
43631
|
+
...typeof payload.refresh_token === "string" ? { refreshToken: payload.refresh_token } : {},
|
|
43632
|
+
...idToken !== undefined ? { idToken } : {},
|
|
43633
|
+
...accountId !== undefined ? { accountId } : {},
|
|
43634
|
+
expiresAt: Date.now() + (typeof payload.expires_in === "number" ? payload.expires_in : 3600) * 1000
|
|
43635
|
+
};
|
|
43636
|
+
}
|
|
43637
|
+
function refreshTokens(tokenUrl, clientId, refreshToken) {
|
|
43638
|
+
return exchange(tokenUrl, { grant_type: "refresh_token", client_id: clientId, refresh_token: refreshToken });
|
|
43639
|
+
}
|
|
43640
|
+
async function runLoginFlow(cfg) {
|
|
43641
|
+
requireBunRuntime2(cfg.functionName ?? "runLoginFlow", "Bun.serve", "The authorization-code flow has to receive the vendor's redirect on 127.0.0.1, which needs a real HTTP listener. Run the login under Bun (or complete it in a Bun process and pass the resulting credential ref to your Node session).");
|
|
43642
|
+
const { verifier, challenge } = await generatePkce();
|
|
43643
|
+
const state = randomBase64Url(16);
|
|
43644
|
+
const report = (output) => cfg.onAuthStatus?.({ isAuthenticating: true, output: [output] });
|
|
43645
|
+
let resolveCode;
|
|
43646
|
+
let rejectFlow;
|
|
43647
|
+
const codePromise = new Promise((resolve, reject) => {
|
|
43648
|
+
resolveCode = resolve;
|
|
43649
|
+
rejectFlow = reject;
|
|
43650
|
+
});
|
|
43651
|
+
codePromise.catch(() => {});
|
|
43652
|
+
const callbackPath = cfg.callbackPath ?? "/auth/callback";
|
|
43653
|
+
const label = cfg.label ?? "codex";
|
|
43654
|
+
const ports = cfg.callbackPort === 0 ? [0] : [cfg.callbackPort, ...cfg.fallbackCallbackPort !== undefined ? [cfg.fallbackCallbackPort] : []];
|
|
43655
|
+
let server;
|
|
43656
|
+
let lastError;
|
|
43657
|
+
for (const port of ports) {
|
|
43658
|
+
try {
|
|
43659
|
+
server = Bun.serve({
|
|
43660
|
+
port,
|
|
43661
|
+
hostname: "127.0.0.1",
|
|
43662
|
+
fetch(req) {
|
|
43663
|
+
const url = new URL(req.url);
|
|
43664
|
+
if (url.pathname !== callbackPath)
|
|
43665
|
+
return new Response("not found", { status: 404 });
|
|
43666
|
+
if (url.searchParams.get("state") !== state) {
|
|
43667
|
+
rejectFlow(new Error("OAuth state mismatch — refusing to complete a login this process did not start"));
|
|
43668
|
+
return new Response("state mismatch", { status: 400 });
|
|
43669
|
+
}
|
|
43670
|
+
const code = url.searchParams.get("code");
|
|
43671
|
+
if (code === null || code.length === 0) {
|
|
43672
|
+
rejectFlow(new Error("the OAuth callback carried no authorization code"));
|
|
43673
|
+
return new Response("missing code", { status: 400 });
|
|
43674
|
+
}
|
|
43675
|
+
resolveCode(code);
|
|
43676
|
+
return new Response("Signed in — you can close this tab.", { headers: { "content-type": "text/plain" } });
|
|
43677
|
+
}
|
|
43678
|
+
});
|
|
43679
|
+
break;
|
|
43680
|
+
} catch (err) {
|
|
43681
|
+
lastError = err;
|
|
43682
|
+
}
|
|
43683
|
+
}
|
|
43684
|
+
if (server === undefined) {
|
|
43685
|
+
const detail = lastError instanceof Error ? lastError.message : String(lastError);
|
|
43686
|
+
throw new ProviderRequestError2({ code: "capability", message: `could not open the ${label} login callback on port ${ports.join(" or ")} — is another login in progress? (${detail})`, retryable: false });
|
|
43687
|
+
}
|
|
43688
|
+
const redirectUri = `http://localhost:${server.port}${callbackPath}`;
|
|
43689
|
+
const authUrl = new URL(cfg.authorizeUrl);
|
|
43690
|
+
authUrl.search = new URLSearchParams({
|
|
43691
|
+
response_type: "code",
|
|
43692
|
+
client_id: cfg.clientId,
|
|
43693
|
+
redirect_uri: redirectUri,
|
|
43694
|
+
scope: cfg.scope,
|
|
43695
|
+
state,
|
|
43696
|
+
code_challenge: challenge,
|
|
43697
|
+
code_challenge_method: "S256",
|
|
43698
|
+
...cfg.extraAuthorizeParams ?? {}
|
|
43699
|
+
}).toString();
|
|
43700
|
+
const timeout = setTimeout(() => rejectFlow(new Error(`the ${label} login timed out`)), cfg.timeoutMs ?? 5 * 60000);
|
|
43701
|
+
try {
|
|
43702
|
+
report("opening the browser for sign-in");
|
|
43703
|
+
cfg.openUrl(authUrl.toString()).catch((err) => rejectFlow(new Error(`could not open the browser: ${err instanceof Error ? err.message : String(err)}`)));
|
|
43704
|
+
const code = await codePromise;
|
|
43705
|
+
report("exchanging the authorization code");
|
|
43706
|
+
const tokens = await exchange(cfg.tokenUrl, {
|
|
43707
|
+
grant_type: "authorization_code",
|
|
43708
|
+
client_id: cfg.clientId,
|
|
43709
|
+
code,
|
|
43710
|
+
redirect_uri: redirectUri,
|
|
43711
|
+
code_verifier: verifier,
|
|
43712
|
+
...cfg.includeStateInTokenRequest === true ? { state } : {}
|
|
43713
|
+
}, label, cfg.bodyEncoding ?? "form");
|
|
43714
|
+
cfg.onAuthStatus?.({ isAuthenticating: false, output: ["signed in"] });
|
|
43715
|
+
return tokens;
|
|
43716
|
+
} catch (err) {
|
|
43717
|
+
cfg.onAuthStatus?.({ isAuthenticating: false, error: err instanceof Error ? err.message : String(err) });
|
|
43718
|
+
throw err;
|
|
43719
|
+
} finally {
|
|
43720
|
+
clearTimeout(timeout);
|
|
43721
|
+
server.stop(true);
|
|
43722
|
+
}
|
|
43723
|
+
}
|
|
43724
|
+
|
|
43724
43725
|
// src/adapters/openai/quota.ts
|
|
43725
43726
|
class QuotaManager2 {
|
|
43726
43727
|
maxConcurrent;
|
|
@@ -43995,4 +43996,4 @@ function crc32(bytes, seed = 0) {
|
|
|
43995
43996
|
return (c ^ 4294967295) >>> 0;
|
|
43996
43997
|
}
|
|
43997
43998
|
|
|
43998
|
-
export { isLocalAddressClass2, classifyAddress2, isDisallowedAddress2, CredentialResolutionError2, redactMaterial2, redactRef2, isNoCredential, unsupported, readOnlyWriteRefusal, createCompositeCredentialStore2, createMemoryCredentialStore2, connectionEndpointOptions2, CREDENTIAL_HEADER_NAMES2, stripCredentialHeaders2, applyPrivilegedHeaders2, evaluateEndpoint2, createEndpointPolicy2, stampFamilyFields, loadCatalog, ProviderStallError2, parseProviderErrorCode2, parseRetryAfterMs2, redactCredentialMaterial2, normalizeHttpError2, isProviderError2, normalizeThrown2, toSdkAssistantMessageError2, DEFAULT_MAX_RETRIES2, RETRY_BACKOFF_BASE_MS2, RETRY_BACKOFF_CAP_MS2, RETRY_AFTER_HONOUR_CEILING_MS2, createRetryPolicy2, withRetry2, DEFAULT_MAX_REDIRECTS2, ProviderRequestError2, ProviderBodyLimitError2, boundedFetch2, WINTER_BRAND, activeWinterIdentity2, setWinterIdentity2, winterUserAgent2, renderIdentityHeaders2, identityHeaderLookup, winterIdentityHeaders, BunRequiredError2, hasBunRuntime2, requireBunRuntime2, PRIVILEGED_IDENTITY_HEADERS2, WINTER_IDENTITY_HEADERS, hostHeaders2, THINKING_ENABLED_NEEDS_BUDGET2, containsImage, parseSse2,
|
|
43999
|
+
export { isLocalAddressClass2, classifyAddress2, isDisallowedAddress2, CredentialResolutionError2, redactMaterial2, redactRef2, isNoCredential, unsupported, readOnlyWriteRefusal, createCompositeCredentialStore2, createMemoryCredentialStore2, connectionEndpointOptions2, CREDENTIAL_HEADER_NAMES2, stripCredentialHeaders2, applyPrivilegedHeaders2, evaluateEndpoint2, createEndpointPolicy2, stampFamilyFields, loadCatalog, ProviderStallError2, parseProviderErrorCode2, parseRetryAfterMs2, redactCredentialMaterial2, normalizeHttpError2, isProviderError2, normalizeThrown2, toSdkAssistantMessageError2, DEFAULT_MAX_RETRIES2, RETRY_BACKOFF_BASE_MS2, RETRY_BACKOFF_CAP_MS2, RETRY_AFTER_HONOUR_CEILING_MS2, createRetryPolicy2, withRetry2, DEFAULT_MAX_REDIRECTS2, ProviderRequestError2, ProviderBodyLimitError2, boundedFetch2, WINTER_BRAND, activeWinterIdentity2, setWinterIdentity2, winterUserAgent2, renderIdentityHeaders2, identityHeaderLookup, winterIdentityHeaders, BunRequiredError2, hasBunRuntime2, requireBunRuntime2, PRIVILEGED_IDENTITY_HEADERS2, WINTER_IDENTITY_HEADERS, hostHeaders2, THINKING_ENABLED_NEEDS_BUDGET2, containsImage, parseSse2, WinterProviderResolutionError2, estimateCostUsd2, createRegistry2, GOOGLE_ADAPTER_ID2, GOOGLE_DEFAULT_BASE_URL2, GOOGLE_API_VERSION_PATH2, googleCompletionMarker2, toContents2, mapGoogleEffort2, createGoogleFamilyAdapter2, geminiTransport2, createGoogleGenerateContentAdapter2, RS2562, base64UrlEncode2, pkcs8DerFromPem2, importRs256PrivateKey2, signRs256Jwt2, GCP_CLOUD_PLATFORM_SCOPE2, createServiceAccountTokenSource2, VERTEX_ADAPTER_ID2, VERTEX_API_VERSION_PATH2, vertexEndpointUrl2, vertexModelPath2, vertexTransport2, createVertexGeminiAdapter2, crc32, BEDROCK_SERVICE, sha256Hex, buildCanonicalRequest, buildStringToSign, computeSignature, signRequest2, parseAuthorization2, DEFAULT_HEADER_TIMEOUT_MS, capabilityRefusal, resolveEndpoint, resolveAuth, buildHeaders, identityFor, mapEffortAgainst, resolveReasoning, assertWithinLimits, capabilitiesFrom, assertRepresentableTools, EventQueue, httpErrorFrom, errorEvent, fetchOpenAiModels, validateViaModels, OPENAI_API_BASE_URL2, buildResponsesBody, streamResponsesTurn, createResponsesAdapter2, privilegedHeaders, responsesTurn, OPENAI_CHAT_BASE_URL2, DEEPSEEK_BASE_URL2, OPENROUTER_BASE_URL2, chatTurn, createChatCompletionsAdapter2, openRouterProfile2, deepSeekProfile2, CODEX2, CODEX_ORIGINATOR2, CODEX_MODELS, codexCredentialAccount2, base64Url2, refreshTokens, runLoginFlow, QuotaManager2, quotaEvent, AZURE_PREVIEW_API_VERSION2, createAzureOpenAIAdapter2, sameDomain2, sameFamily2, readableStateOf2, summaryRequestOf2, shouldRequestSummary2, createEndpointResolver2, endpointFromOrigin2 };
|
package/dist/index.d.ts
CHANGED
|
@@ -26,8 +26,9 @@ export type { KeychainRef, OauthMaterial, RefreshOauthMaterialInput } from "./ad
|
|
|
26
26
|
export { runDeviceCodeFlow } from "./adapters/oauth/device-code.js";
|
|
27
27
|
export type { DeviceCodeConfig } from "./adapters/oauth/device-code.js";
|
|
28
28
|
export { BunRequiredError, hasBunRuntime, requireBunRuntime } from "./bun-required.js";
|
|
29
|
-
export {
|
|
30
|
-
export
|
|
29
|
+
export { CONSOLE_BEARER, anthropicCredentialRef } from "./adapters/anthropic/index.js";
|
|
30
|
+
export { DEFAULT_ANTHROPIC_CONSOLE_PROFILE, anthropicConsoleProfileExists, logoutAnthropicConsole, refreshAnthropicBearer, startAnthropicConsoleBrokerLogin, } from "./adapters/anthropic/index.js";
|
|
31
|
+
export type { AnthropicConsoleBrokerOptions, AnthropicConsoleLoginHandle } from "./adapters/anthropic/index.js";
|
|
31
32
|
export { parseSse } from "./sse.js";
|
|
32
33
|
export type { SseEvent, SseOptions } from "./sse.js";
|
|
33
34
|
export { WinterProviderResolutionError, createRegistry, estimateCostUsd } from "./registry.js";
|
package/dist/index.js
CHANGED
|
@@ -51,8 +51,6 @@ import {
|
|
|
51
51
|
THINKING_ENABLED_NEEDS_BUDGET2,
|
|
52
52
|
containsImage,
|
|
53
53
|
parseSse2,
|
|
54
|
-
refreshTokens,
|
|
55
|
-
runLoginFlow,
|
|
56
54
|
WinterProviderResolutionError2,
|
|
57
55
|
estimateCostUsd2,
|
|
58
56
|
createRegistry2,
|
|
@@ -105,6 +103,8 @@ import {
|
|
|
105
103
|
CODEX_ORIGINATOR2,
|
|
106
104
|
CODEX_MODELS,
|
|
107
105
|
codexCredentialAccount2,
|
|
106
|
+
refreshTokens,
|
|
107
|
+
runLoginFlow,
|
|
108
108
|
QuotaManager2,
|
|
109
109
|
quotaEvent,
|
|
110
110
|
createAzureOpenAIAdapter2,
|
|
@@ -115,7 +115,7 @@ import {
|
|
|
115
115
|
shouldRequestSummary2,
|
|
116
116
|
createEndpointResolver2,
|
|
117
117
|
endpointFromOrigin2
|
|
118
|
-
} from "./index-
|
|
118
|
+
} from "./index-m3qxyre5.js";
|
|
119
119
|
// src/credentials/env.ts
|
|
120
120
|
var NAME = "env credential store";
|
|
121
121
|
function createEnvCredentialStore(opts) {
|
|
@@ -443,92 +443,13 @@ async function runDeviceCodeFlow(cfg) {
|
|
|
443
443
|
}
|
|
444
444
|
}
|
|
445
445
|
// src/adapters/anthropic/console-oauth.ts
|
|
446
|
-
var CONSOLE_OAUTH = {
|
|
447
|
-
clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
|
448
|
-
authorizeUrl: "https://platform.claude.com/oauth/authorize",
|
|
449
|
-
tokenUrl: "https://platform.claude.com/v1/oauth/token",
|
|
450
|
-
profileUrl: "https://api.anthropic.com/api/oauth/profile",
|
|
451
|
-
scope: "user:inference user:profile",
|
|
452
|
-
callbackPort: 0,
|
|
453
|
-
callbackPath: "/callback",
|
|
454
|
-
betaHeader: "oauth-2025-04-20",
|
|
455
|
-
accountIdPath: "account.uuid"
|
|
456
|
-
};
|
|
457
|
-
var OAUTH_REFRESH_WINDOW_MS = 60000;
|
|
458
446
|
var ANTHROPIC_CONSOLE_PROVIDER_ID = "anthropic";
|
|
447
|
+
var CONSOLE_BEARER = {
|
|
448
|
+
betaHeader: "oauth-2025-04-20"
|
|
449
|
+
};
|
|
459
450
|
function anthropicCredentialRef(accountId, service) {
|
|
460
451
|
return { kind: "keychain", account: `anthropic:${accountId}`, ...service !== undefined ? { service } : {} };
|
|
461
452
|
}
|
|
462
|
-
async function startAnthropicConsoleLogin(store, options) {
|
|
463
|
-
const tokens = await runLoginFlow({
|
|
464
|
-
clientId: CONSOLE_OAUTH.clientId,
|
|
465
|
-
authorizeUrl: options.authorizeUrl ?? CONSOLE_OAUTH.authorizeUrl,
|
|
466
|
-
tokenUrl: options.tokenUrl ?? CONSOLE_OAUTH.tokenUrl,
|
|
467
|
-
callbackPort: options.callbackPort ?? CONSOLE_OAUTH.callbackPort,
|
|
468
|
-
callbackPath: CONSOLE_OAUTH.callbackPath,
|
|
469
|
-
label: "Anthropic Console",
|
|
470
|
-
functionName: "startAnthropicConsoleLogin",
|
|
471
|
-
bodyEncoding: "json",
|
|
472
|
-
includeStateInTokenRequest: true,
|
|
473
|
-
scope: CONSOLE_OAUTH.scope,
|
|
474
|
-
...options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {},
|
|
475
|
-
openUrl: options.openUrl,
|
|
476
|
-
...options.onAuthStatus !== undefined ? { onAuthStatus: options.onAuthStatus } : {}
|
|
477
|
-
});
|
|
478
|
-
const accountId = await fetchAccountId(options.profileUrl ?? CONSOLE_OAUTH.profileUrl, tokens.accessToken);
|
|
479
|
-
if (accountId === undefined) {
|
|
480
|
-
throw new ProviderRequestError2({
|
|
481
|
-
code: "capability",
|
|
482
|
-
message: "the Anthropic Console sign-in completed but the profile lookup reported no account id, so the credential has no per-account record to occupy (R6-10)",
|
|
483
|
-
retryable: false
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
const ref = anthropicCredentialRef(accountId, options.service);
|
|
487
|
-
await store.set(ref, materialFor(tokens, accountId));
|
|
488
|
-
return { ref, accountId, expiresAt: tokens.expiresAt };
|
|
489
|
-
}
|
|
490
|
-
async function fetchAccountId(profileUrl, accessToken) {
|
|
491
|
-
const built = createEndpointPolicy2(new URL(profileUrl).origin, { generated: true });
|
|
492
|
-
if (!built.ok)
|
|
493
|
-
throw new ProviderRequestError2({ code: "capability", message: built.reason, retryable: false });
|
|
494
|
-
const response = await boundedFetch2(profileUrl, {
|
|
495
|
-
method: "GET",
|
|
496
|
-
headers: {
|
|
497
|
-
authorization: `Bearer ${accessToken}`,
|
|
498
|
-
accept: "application/json",
|
|
499
|
-
"user-agent": winterUserAgent2()
|
|
500
|
-
},
|
|
501
|
-
policy: built.policy,
|
|
502
|
-
maxBodyBytes: 512 * 1024,
|
|
503
|
-
timeoutMs: 30000
|
|
504
|
-
});
|
|
505
|
-
if (!response.ok) {
|
|
506
|
-
await response.text().catch(() => "");
|
|
507
|
-
throw new ProviderRequestError2({
|
|
508
|
-
code: response.status === 401 || response.status === 403 ? "auth" : "server",
|
|
509
|
-
message: `the Anthropic Console profile lookup failed: HTTP ${response.status}`,
|
|
510
|
-
status: response.status,
|
|
511
|
-
retryable: response.status >= 500
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
let payload;
|
|
515
|
-
try {
|
|
516
|
-
payload = await response.json();
|
|
517
|
-
} catch {
|
|
518
|
-
throw new ProviderRequestError2({ code: "server", message: "the Anthropic Console profile lookup returned a body that is not JSON", retryable: false });
|
|
519
|
-
}
|
|
520
|
-
const uuid = payload.account?.uuid;
|
|
521
|
-
return typeof uuid === "string" && uuid.length > 0 ? uuid : undefined;
|
|
522
|
-
}
|
|
523
|
-
function materialFor(tokens, accountId) {
|
|
524
|
-
return {
|
|
525
|
-
kind: "oauth",
|
|
526
|
-
accessToken: tokens.accessToken,
|
|
527
|
-
...tokens.refreshToken !== undefined ? { refreshToken: tokens.refreshToken } : {},
|
|
528
|
-
accountId,
|
|
529
|
-
expiresAt: tokens.expiresAt
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
453
|
|
|
533
454
|
// src/adapters/anthropic/messages.ts
|
|
534
455
|
var ANTHROPIC_ADAPTER_ID = "winter.anthropic-messages";
|
|
@@ -741,32 +662,12 @@ function resolveEndpoint2(ctx, defaultBaseUrl) {
|
|
|
741
662
|
function isConsoleProvider(ctx) {
|
|
742
663
|
return ctx.connection.providerId === ANTHROPIC_CONSOLE_PROVIDER_ID;
|
|
743
664
|
}
|
|
744
|
-
async function resolveFreshMaterial(ctx
|
|
745
|
-
|
|
746
|
-
if (material === null || material.kind !== "oauth")
|
|
747
|
-
return material;
|
|
748
|
-
if (!isConsoleProvider(ctx))
|
|
749
|
-
return material;
|
|
750
|
-
if (ctx.authRef.kind !== "keychain")
|
|
751
|
-
return material;
|
|
752
|
-
if (material.refreshToken === undefined || material.refreshToken.length === 0)
|
|
753
|
-
return material;
|
|
754
|
-
if (material.expiresAt === undefined)
|
|
755
|
-
return material;
|
|
756
|
-
if (material.expiresAt - Date.now() >= OAUTH_REFRESH_WINDOW_MS)
|
|
757
|
-
return material;
|
|
758
|
-
return await refreshOauthMaterial({
|
|
759
|
-
store: ctx.credentials,
|
|
760
|
-
ref: ctx.authRef,
|
|
761
|
-
tokenUrl: opts.tokenUrl ?? CONSOLE_OAUTH.tokenUrl,
|
|
762
|
-
clientId: CONSOLE_OAUTH.clientId,
|
|
763
|
-
extraFields: { scope: CONSOLE_OAUTH.scope },
|
|
764
|
-
bodyEncoding: "json"
|
|
765
|
-
});
|
|
665
|
+
async function resolveFreshMaterial(ctx) {
|
|
666
|
+
return await ctx.credentials.get(ctx.authRef);
|
|
766
667
|
}
|
|
767
668
|
async function buildHeaders2(ctx, policy, opts, json, identity = {}) {
|
|
768
|
-
const material = await resolveFreshMaterial(ctx
|
|
769
|
-
const betas = [...opts.betas ?? [], ...material?.kind === "oauth" && isConsoleProvider(ctx) ? [
|
|
669
|
+
const material = await resolveFreshMaterial(ctx);
|
|
670
|
+
const betas = [...opts.betas ?? [], ...(material?.kind === "oauth" || material?.kind === "bearer") && isConsoleProvider(ctx) ? [CONSOLE_BEARER.betaHeader] : []].filter((value, index, all) => all.indexOf(value) === index);
|
|
770
671
|
const headers = {
|
|
771
672
|
"user-agent": winterUserAgent2(),
|
|
772
673
|
...identity,
|
|
@@ -1160,6 +1061,212 @@ function createAnthropicMessagesAdapter(opts = {}) {
|
|
|
1160
1061
|
}
|
|
1161
1062
|
};
|
|
1162
1063
|
}
|
|
1064
|
+
// src/adapters/anthropic/console-broker.ts
|
|
1065
|
+
import { existsSync } from "node:fs";
|
|
1066
|
+
import { join as join2 } from "node:path";
|
|
1067
|
+
var DEFAULT_ANTHROPIC_CONSOLE_PROFILE = "winter";
|
|
1068
|
+
|
|
1069
|
+
class SubmitCodeRefused extends Error {
|
|
1070
|
+
name = "SubmitCodeRefused";
|
|
1071
|
+
reason;
|
|
1072
|
+
constructor(reason, message) {
|
|
1073
|
+
super(message);
|
|
1074
|
+
this.reason = reason;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
var INHERITED_ENV_NAMES = ["PATH", "HOME", "TMPDIR", "TERM", "SHELL", "LANG"];
|
|
1078
|
+
function brokerEnv(options, profile) {
|
|
1079
|
+
const env = {};
|
|
1080
|
+
for (const name of INHERITED_ENV_NAMES) {
|
|
1081
|
+
const value = process.env[name];
|
|
1082
|
+
if (value !== undefined)
|
|
1083
|
+
env[name] = value;
|
|
1084
|
+
}
|
|
1085
|
+
for (const [name, value] of Object.entries(process.env)) {
|
|
1086
|
+
if (name.startsWith("LC_") && value !== undefined)
|
|
1087
|
+
env[name] = value;
|
|
1088
|
+
}
|
|
1089
|
+
env["ANTHROPIC_PROFILE"] = profile;
|
|
1090
|
+
env["ANTHROPIC_CONFIG_DIR"] = options.anthropicConfigDir;
|
|
1091
|
+
env["CLAUDE_CONFIG_DIR"] = options.claudeConfigDir;
|
|
1092
|
+
return env;
|
|
1093
|
+
}
|
|
1094
|
+
function redactUrlQuery(line) {
|
|
1095
|
+
const urlRedacted = line.replace(/(https?:\/\/[^\s?]+)\?[^\s]*/g, "$1?…");
|
|
1096
|
+
return urlRedacted.replace(/code=.*/i, "code=…");
|
|
1097
|
+
}
|
|
1098
|
+
function pump(chunk, buffer, onLine, record) {
|
|
1099
|
+
let working = buffer + chunk;
|
|
1100
|
+
let newlineAt;
|
|
1101
|
+
while ((newlineAt = working.indexOf(`
|
|
1102
|
+
`)) !== -1) {
|
|
1103
|
+
const raw = working.slice(0, newlineAt);
|
|
1104
|
+
working = working.slice(newlineAt + 1);
|
|
1105
|
+
const redacted = redactUrlQuery(raw);
|
|
1106
|
+
if (record !== undefined && redacted.trim().length > 0)
|
|
1107
|
+
record.push(redacted);
|
|
1108
|
+
onLine(redacted);
|
|
1109
|
+
}
|
|
1110
|
+
return working;
|
|
1111
|
+
}
|
|
1112
|
+
async function drainStream(stream, onLine, record) {
|
|
1113
|
+
if (stream === null || stream === undefined)
|
|
1114
|
+
return;
|
|
1115
|
+
const reader = stream.getReader();
|
|
1116
|
+
const decoder = new TextDecoder;
|
|
1117
|
+
let buffer = "";
|
|
1118
|
+
for (;; ) {
|
|
1119
|
+
const { done, value } = await reader.read();
|
|
1120
|
+
if (done)
|
|
1121
|
+
break;
|
|
1122
|
+
buffer = pump(decoder.decode(value, { stream: true }), buffer, onLine, record);
|
|
1123
|
+
}
|
|
1124
|
+
if (buffer.length > 0) {
|
|
1125
|
+
const redacted = redactUrlQuery(buffer);
|
|
1126
|
+
if (record !== undefined && redacted.trim().length > 0)
|
|
1127
|
+
record.push(redacted);
|
|
1128
|
+
onLine(redacted);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
function startAnthropicConsoleBrokerLogin(store, options) {
|
|
1132
|
+
requireBunRuntime2("startAnthropicConsoleBrokerLogin", "Bun.spawn", "The Console login has to spawn the `claude` binary and pipe its stdin/stdout/stderr, which needs Bun's subprocess API. Run the login under Bun (or complete it in a Bun process and pass the resulting credential ref to your Node session).");
|
|
1133
|
+
const profile = options.profile ?? DEFAULT_ANTHROPIC_CONSOLE_PROFILE;
|
|
1134
|
+
const onLine = options.onLine ?? (() => {});
|
|
1135
|
+
const spawnFn = options.spawn ?? Bun.spawn;
|
|
1136
|
+
let child;
|
|
1137
|
+
try {
|
|
1138
|
+
child = spawnFn([options.claudeExecutable, "auth", "login", "--console"], {
|
|
1139
|
+
env: brokerEnv(options, profile),
|
|
1140
|
+
stdin: "pipe",
|
|
1141
|
+
stdout: "pipe",
|
|
1142
|
+
stderr: "pipe"
|
|
1143
|
+
});
|
|
1144
|
+
} catch (err) {
|
|
1145
|
+
const reason = `"claude auth login --console" could not be started: ${err instanceof Error ? err.message : String(err)}`;
|
|
1146
|
+
return {
|
|
1147
|
+
submitCode: async () => {
|
|
1148
|
+
throw new SubmitCodeRefused("not-running", "the console login process never started, so there is nothing to write the code to");
|
|
1149
|
+
},
|
|
1150
|
+
done: Promise.resolve({ ok: false, reason })
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
const stderrLines = [];
|
|
1154
|
+
const stdoutPump = drainStream(child.stdout, onLine);
|
|
1155
|
+
const stderrPump = drainStream(child.stderr, onLine, stderrLines);
|
|
1156
|
+
let submitted = false;
|
|
1157
|
+
let exited = false;
|
|
1158
|
+
child.exited.then(() => {
|
|
1159
|
+
exited = true;
|
|
1160
|
+
});
|
|
1161
|
+
const done = (async () => {
|
|
1162
|
+
const [exitCode] = await Promise.all([child.exited, stdoutPump, stderrPump]);
|
|
1163
|
+
if (exitCode !== 0) {
|
|
1164
|
+
const reason = stderrLines.at(-1) ?? `"claude auth login --console" exited with code ${exitCode}`;
|
|
1165
|
+
return { ok: false, reason };
|
|
1166
|
+
}
|
|
1167
|
+
const refreshed = await refreshAnthropicBearer(store, options);
|
|
1168
|
+
if (!refreshed.ok)
|
|
1169
|
+
return { ok: false, reason: refreshed.reason };
|
|
1170
|
+
return { ok: true, profile };
|
|
1171
|
+
})();
|
|
1172
|
+
return {
|
|
1173
|
+
async submitCode(code) {
|
|
1174
|
+
if (exited)
|
|
1175
|
+
throw new SubmitCodeRefused("not-running", "the console login process has already exited; there is nothing left to write the code to");
|
|
1176
|
+
if (submitted)
|
|
1177
|
+
throw new SubmitCodeRefused("already-submitted", "submitCode was already called once for this login; a second call cannot un-write what was already sent");
|
|
1178
|
+
submitted = true;
|
|
1179
|
+
const stdin = child.stdin;
|
|
1180
|
+
if (stdin === undefined || stdin === null || typeof stdin === "number")
|
|
1181
|
+
return;
|
|
1182
|
+
stdin.write(`${code}
|
|
1183
|
+
`);
|
|
1184
|
+
await stdin.end();
|
|
1185
|
+
},
|
|
1186
|
+
done
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
async function refreshAnthropicBearer(store, options) {
|
|
1190
|
+
requireBunRuntime2("refreshAnthropicBearer", "Bun.spawn", "Minting the native provider's bearer token spawns the `ant` binary, which needs Bun's subprocess API. Run it under Bun.");
|
|
1191
|
+
if (options.antExecutable === undefined) {
|
|
1192
|
+
return { ok: false, reason: 'the "ant" broker is not installed/resolved (settings.runtimes.antExecutable, or `brew install anthropics/tap/ant`) -- the console login itself succeeded, but nothing can mint the native provider\'s bearer token without it' };
|
|
1193
|
+
}
|
|
1194
|
+
const profile = options.profile ?? DEFAULT_ANTHROPIC_CONSOLE_PROFILE;
|
|
1195
|
+
const spawnFn = options.spawn ?? Bun.spawn;
|
|
1196
|
+
let child;
|
|
1197
|
+
try {
|
|
1198
|
+
child = spawnFn([options.antExecutable, "auth", "print-credentials", "--profile", profile, "--access-token"], {
|
|
1199
|
+
env: brokerEnv(options, profile),
|
|
1200
|
+
stdout: "pipe",
|
|
1201
|
+
stderr: "pipe"
|
|
1202
|
+
});
|
|
1203
|
+
} catch (err) {
|
|
1204
|
+
return { ok: false, reason: `"ant auth print-credentials" could not be started: ${err instanceof Error ? err.message : String(err)}` };
|
|
1205
|
+
}
|
|
1206
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
1207
|
+
new Response(child.stdout).text(),
|
|
1208
|
+
new Response(child.stderr).text(),
|
|
1209
|
+
child.exited
|
|
1210
|
+
]);
|
|
1211
|
+
if (exitCode !== 0) {
|
|
1212
|
+
const lastLine = stderr.trim().split(`
|
|
1213
|
+
`).map((line) => redactUrlQuery(line)).filter((line) => line.length > 0).at(-1);
|
|
1214
|
+
return { ok: false, reason: lastLine ?? `"ant auth print-credentials" exited with code ${exitCode}` };
|
|
1215
|
+
}
|
|
1216
|
+
const token = stdout.trim();
|
|
1217
|
+
if (token.length === 0)
|
|
1218
|
+
return { ok: false, reason: '"ant auth print-credentials" printed no token' };
|
|
1219
|
+
const now = options.now ?? Date.now;
|
|
1220
|
+
const nowValue = now();
|
|
1221
|
+
let expiresAt = await readProfileExpiresAt(options.anthropicConfigDir, profile) ?? nowValue + 3600000;
|
|
1222
|
+
if (expiresAt <= nowValue) {
|
|
1223
|
+
options.onLine?.("refreshAnthropicBearer: profile expiresAt was not in the future; clamped to now+60s");
|
|
1224
|
+
expiresAt = nowValue + 60000;
|
|
1225
|
+
}
|
|
1226
|
+
const material = { kind: "bearer", token, expiresAt };
|
|
1227
|
+
const ref = anthropicCredentialRef("default", options.service);
|
|
1228
|
+
try {
|
|
1229
|
+
await store.set(ref, material);
|
|
1230
|
+
} catch {
|
|
1231
|
+
return { ok: false, reason: "the bearer credential could not be written" };
|
|
1232
|
+
}
|
|
1233
|
+
return { ok: true, expiresAt };
|
|
1234
|
+
}
|
|
1235
|
+
var MAX_PROFILE_FILE_BYTES = 64 * 1024;
|
|
1236
|
+
async function readProfileExpiresAt(anthropicConfigDir, profile) {
|
|
1237
|
+
try {
|
|
1238
|
+
const raw = await Bun.file(join2(anthropicConfigDir, "credentials", `${profile}.json`)).slice(0, MAX_PROFILE_FILE_BYTES).text();
|
|
1239
|
+
const parsed = JSON.parse(raw);
|
|
1240
|
+
return normalizeExpiresAt(parsed.expires_at);
|
|
1241
|
+
} catch {
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
function normalizeExpiresAt(value) {
|
|
1246
|
+
if (typeof value === "number") {
|
|
1247
|
+
if (!Number.isFinite(value))
|
|
1248
|
+
return;
|
|
1249
|
+
return value < 1000000000000 ? value * 1000 : value;
|
|
1250
|
+
}
|
|
1251
|
+
if (typeof value === "string") {
|
|
1252
|
+
const parsed = Date.parse(value);
|
|
1253
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
1254
|
+
}
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
function anthropicConsoleProfileExists(anthropicConfigDir, profile = DEFAULT_ANTHROPIC_CONSOLE_PROFILE) {
|
|
1258
|
+
return existsSync(join2(anthropicConfigDir, "credentials", `${profile}.json`));
|
|
1259
|
+
}
|
|
1260
|
+
async function logoutAnthropicConsole(store, options) {
|
|
1261
|
+
requireBunRuntime2("logoutAnthropicConsole", "Bun.spawn", "Signing out spawns the `claude` binary, which needs Bun's subprocess API. Run it under Bun.");
|
|
1262
|
+
const profile = options.profile ?? DEFAULT_ANTHROPIC_CONSOLE_PROFILE;
|
|
1263
|
+
const spawnFn = options.spawn ?? Bun.spawn;
|
|
1264
|
+
try {
|
|
1265
|
+
const child = spawnFn([options.claudeExecutable, "auth", "logout"], { env: brokerEnv(options, profile), stdout: "pipe", stderr: "pipe" });
|
|
1266
|
+
await child.exited;
|
|
1267
|
+
} catch {}
|
|
1268
|
+
await store.delete(anthropicCredentialRef("default", options.service));
|
|
1269
|
+
}
|
|
1163
1270
|
// src/discovery.ts
|
|
1164
1271
|
var MAX_MODEL_ID_CHARS = 256;
|
|
1165
1272
|
var MAX_DISPLAY_NAME_CHARS = 512;
|
|
@@ -2237,10 +2344,10 @@ async function startCodexLogin(store, options) {
|
|
|
2237
2344
|
throw capabilityRefusal("the codex token exchange returned no ChatGPT account id, so the credential has no per-account record to occupy (R6-10)");
|
|
2238
2345
|
}
|
|
2239
2346
|
const ref = codexCredentialRef(tokens.accountId, options.service);
|
|
2240
|
-
await store.set(ref,
|
|
2347
|
+
await store.set(ref, materialFor(tokens));
|
|
2241
2348
|
return { ref, accountId: tokens.accountId, expiresAt: tokens.expiresAt };
|
|
2242
2349
|
}
|
|
2243
|
-
function
|
|
2350
|
+
function materialFor(tokens) {
|
|
2244
2351
|
return {
|
|
2245
2352
|
kind: "oauth",
|
|
2246
2353
|
accessToken: tokens.accessToken,
|
|
@@ -2436,7 +2543,7 @@ function decodeSubject(idToken) {
|
|
|
2436
2543
|
return;
|
|
2437
2544
|
}
|
|
2438
2545
|
}
|
|
2439
|
-
function
|
|
2546
|
+
function materialFor2(tokens, accountId) {
|
|
2440
2547
|
return {
|
|
2441
2548
|
kind: "oauth",
|
|
2442
2549
|
accessToken: tokens.accessToken,
|
|
@@ -2470,7 +2577,7 @@ async function startXaiLogin(store, options = {}) {
|
|
|
2470
2577
|
throw capabilityRefusal("the xAI token exchange returned no account subject, so the credential has no per-account record to occupy (R6-10)");
|
|
2471
2578
|
}
|
|
2472
2579
|
const ref = xaiCredentialRef(accountId, options.service);
|
|
2473
|
-
await store.set(ref,
|
|
2580
|
+
await store.set(ref, materialFor2(tokens, accountId));
|
|
2474
2581
|
return { ref, accountId, expiresAt: tokens.expiresAt };
|
|
2475
2582
|
}
|
|
2476
2583
|
var REFRESH_WINDOW_MS = 60000;
|
|
@@ -2749,7 +2856,7 @@ function createHistoryRenderer(registry, options = {}) {
|
|
|
2749
2856
|
report.droppedNativeState++;
|
|
2750
2857
|
report.strippedInDialectBlocks += strippedBlocks;
|
|
2751
2858
|
const link = message.uuid !== undefined ? chain.get(message.uuid) : undefined;
|
|
2752
|
-
const material =
|
|
2859
|
+
const material = materialFor3(link, source, allowExposed);
|
|
2753
2860
|
if (material === undefined) {
|
|
2754
2861
|
report.withoutMaterial++;
|
|
2755
2862
|
} else {
|
|
@@ -2800,7 +2907,7 @@ function createHistoryRenderer(registry, options = {}) {
|
|
|
2800
2907
|
renderWithReport
|
|
2801
2908
|
};
|
|
2802
2909
|
}
|
|
2803
|
-
function
|
|
2910
|
+
function materialFor3(link, source, allowExposed) {
|
|
2804
2911
|
const text = link?.summary;
|
|
2805
2912
|
if (text === undefined || text.length === 0)
|
|
2806
2913
|
return;
|
|
@@ -3068,10 +3175,11 @@ export {
|
|
|
3068
3175
|
BEDROCK_ADAPTER_VERSION,
|
|
3069
3176
|
BunRequiredError2 as BunRequiredError,
|
|
3070
3177
|
CODEX_ORIGINATOR2 as CODEX_ORIGINATOR,
|
|
3071
|
-
|
|
3178
|
+
CONSOLE_BEARER,
|
|
3072
3179
|
CREDENTIAL_HEADER_NAMES2 as CREDENTIAL_HEADER_NAMES,
|
|
3073
3180
|
CredentialResolutionError2 as CredentialResolutionError,
|
|
3074
3181
|
DEEPSEEK_BASE_URL2 as DEEPSEEK_BASE_URL,
|
|
3182
|
+
DEFAULT_ANTHROPIC_CONSOLE_PROFILE,
|
|
3075
3183
|
DEFAULT_MAX_REDIRECTS2 as DEFAULT_MAX_REDIRECTS,
|
|
3076
3184
|
DEFAULT_MAX_RETRIES2 as DEFAULT_MAX_RETRIES,
|
|
3077
3185
|
DERIVED_XAI,
|
|
@@ -3082,7 +3190,6 @@ export {
|
|
|
3082
3190
|
GOOGLE_DEFAULT_BASE_URL2 as GOOGLE_DEFAULT_BASE_URL,
|
|
3083
3191
|
INSTRUCTION_FILE_BASENAMES,
|
|
3084
3192
|
MIN_DECORATION_BODY_CHARS,
|
|
3085
|
-
OAUTH_REFRESH_WINDOW_MS,
|
|
3086
3193
|
OPENAI_API_BASE_URL2 as OPENAI_API_BASE_URL,
|
|
3087
3194
|
OPENAI_CHAT_BASE_URL2 as OPENAI_CHAT_BASE_URL,
|
|
3088
3195
|
OPENROUTER_BASE_URL2 as OPENROUTER_BASE_URL,
|
|
@@ -3103,6 +3210,7 @@ export {
|
|
|
3103
3210
|
XAI_OAUTH,
|
|
3104
3211
|
XAI_OAUTH_ADAPTER_ID,
|
|
3105
3212
|
activeWinterIdentity2 as activeWinterIdentity,
|
|
3213
|
+
anthropicConsoleProfileExists,
|
|
3106
3214
|
anthropicCredentialRef,
|
|
3107
3215
|
applyDecorationToContent,
|
|
3108
3216
|
applyPrivilegedHeaders2 as applyPrivilegedHeaders,
|
|
@@ -3152,6 +3260,7 @@ export {
|
|
|
3152
3260
|
isDisallowedAddress2 as isDisallowedAddress,
|
|
3153
3261
|
isLocalAddressClass2 as isLocalAddressClass,
|
|
3154
3262
|
isProviderError2 as isProviderError,
|
|
3263
|
+
logoutAnthropicConsole,
|
|
3155
3264
|
mapAnthropicEffort,
|
|
3156
3265
|
mapBedrockEffort,
|
|
3157
3266
|
mapGoogleEffort2 as mapGoogleEffort,
|
|
@@ -3166,6 +3275,7 @@ export {
|
|
|
3166
3275
|
redactCredentialMaterial2 as redactCredentialMaterial,
|
|
3167
3276
|
redactMaterial2 as redactMaterial,
|
|
3168
3277
|
redactRef2 as redactRef,
|
|
3278
|
+
refreshAnthropicBearer,
|
|
3169
3279
|
refreshOauthMaterial,
|
|
3170
3280
|
renderIdentityHeaders2 as renderIdentityHeaders,
|
|
3171
3281
|
requireBunRuntime2 as requireBunRuntime,
|
|
@@ -3177,7 +3287,7 @@ export {
|
|
|
3177
3287
|
setWinterIdentity2 as setWinterIdentity,
|
|
3178
3288
|
shouldRequestSummary2 as shouldRequestSummary,
|
|
3179
3289
|
signRequest2 as signRequest,
|
|
3180
|
-
|
|
3290
|
+
startAnthropicConsoleBrokerLogin,
|
|
3181
3291
|
startCodexLogin,
|
|
3182
3292
|
startXaiLogin,
|
|
3183
3293
|
stripCredentialHeaders2 as stripCredentialHeaders,
|
package/dist/testing.js
CHANGED
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
requireBunRuntime2,
|
|
6
6
|
hostHeaders2,
|
|
7
7
|
THINKING_ENABLED_NEEDS_BUDGET2,
|
|
8
|
-
base64Url2,
|
|
9
8
|
googleCompletionMarker2,
|
|
10
9
|
RS2562,
|
|
11
10
|
base64UrlEncode2,
|
|
@@ -22,10 +21,11 @@ import {
|
|
|
22
21
|
computeSignature,
|
|
23
22
|
parseAuthorization2,
|
|
24
23
|
CODEX2,
|
|
24
|
+
base64Url2,
|
|
25
25
|
QuotaManager2,
|
|
26
26
|
AZURE_PREVIEW_API_VERSION2,
|
|
27
27
|
readableStateOf2
|
|
28
|
-
} from "./index-
|
|
28
|
+
} from "./index-m3qxyre5.js";
|
|
29
29
|
|
|
30
30
|
// src/adapters/bedrock/testing.ts
|
|
31
31
|
function encodeHeader(header) {
|
package/dist/types.d.ts
CHANGED
|
@@ -45,6 +45,14 @@ export type CredentialMaterial = {
|
|
|
45
45
|
} | {
|
|
46
46
|
kind: "bearer";
|
|
47
47
|
token: string;
|
|
48
|
+
/**
|
|
49
|
+
* P10a-4 (2026-09-13): when the token is host-brokered and renewable (Anthropic Console's
|
|
50
|
+
* `ant auth print-credentials`, refreshed on a timer ahead of expiry), the host stamps this so
|
|
51
|
+
* the renewer knows when to run again. OPTIONAL: most `bearer` material (a gateway or proxy
|
|
52
|
+
* token a host pastes in by hand) has no known expiry, and absence must read as "unknown", not
|
|
53
|
+
* "already expired" -- the same reasoning `oauth`'s own `expiresAt` already follows below.
|
|
54
|
+
*/
|
|
55
|
+
expiresAt?: number;
|
|
48
56
|
} | {
|
|
49
57
|
kind: "oauth";
|
|
50
58
|
accessToken: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yanlinglabs/winter-provider-runtime",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@yanlinglabs/winter-agent-sdk": "0.0.
|
|
46
|
-
"@yanlinglabs/winter-provider-catalog": "0.0.
|
|
45
|
+
"@yanlinglabs/winter-agent-sdk": "0.0.7",
|
|
46
|
+
"@yanlinglabs/winter-provider-catalog": "0.0.7"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {}
|
|
49
49
|
}
|