@yanlinglabs/winter-provider-runtime 0.0.5 → 0.0.6

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 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
- (Internally all four go through one `runLoginFlow`/`Bun.serve` seam, which is not on either barrel
69
- and which a consumer cannot call.)
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, CredentialStore } from "../../types.js";
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,25 @@ 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 an `oauth` credential stored against a sibling
47
- * row would have its REFRESH TOKEN posted to `platform.claude.com` — a third party's credential sent
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
- * The ONE spelling of an Anthropic OAuth record's name (R6-10).
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 `oauth`-kind Anthropic credential's bearer.
16
+ *
17
+ * See the file banner for why this is renamed rather than trimmed in place, and P10a-1/M3 for why the
18
+ * value itself is not yet a settled decision — the constant stays until a measurement says otherwise.
19
+ */
20
+ export declare const CONSOLE_BEARER: {
21
+ /** The `anthropic-beta` value that accompanies an OAuth bearer on every request. */
22
+ readonly betaHeader: "oauth-2025-04-20";
23
+ };
24
+ /**
25
+ * The ONE spelling of an Anthropic OAuth/bearer record's name (R6-10).
54
26
  *
55
- * Exported and used by both the login and the adapter, because a host that assembles
27
+ * Exported and used by both a host's broker and the adapter, because a host that assembles
56
28
  * `anthropic:<id>` by hand will eventually assemble it differently from whatever reads it — a
57
29
  * credential written to a key nothing looks up, failing as "no credential configured" with the
58
30
  * record sitting right there.
@@ -60,42 +32,3 @@ export declare const ANTHROPIC_CONSOLE_PROVIDER_ID = "anthropic";
60
32
  export declare function anthropicCredentialRef(accountId: string, service?: string): Extract<CredentialRef, {
61
33
  kind: "keychain";
62
34
  }>;
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 { CONSOLE_OAUTH, OAUTH_REFRESH_WINDOW_MS, anthropicCredentialRef, startAnthropicConsoleLogin } from "./console-oauth.js";
4
- export type { AnthropicConsoleLoginOptions, AnthropicConsoleLoginResult } from "./console-oauth.js";
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.5",
40999
+ version: "0.0.6",
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, base64Url2, refreshTokens, runLoginFlow, 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, QuotaManager2, quotaEvent, AZURE_PREVIEW_API_VERSION2, createAzureOpenAIAdapter2, sameDomain2, sameFamily2, readableStateOf2, summaryRequestOf2, shouldRequestSummary2, createEndpointResolver2, endpointFromOrigin2 };
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 { CONSOLE_OAUTH, OAUTH_REFRESH_WINDOW_MS, anthropicCredentialRef, startAnthropicConsoleLogin } from "./adapters/anthropic/index.js";
30
- export type { AnthropicConsoleLoginOptions, AnthropicConsoleLoginResult } from "./adapters/anthropic/index.js";
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-446qxzss.js";
118
+ } from "./index-r2frr72q.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, opts) {
745
- const material = await ctx.credentials.get(ctx.authRef);
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, opts);
769
- const betas = [...opts.betas ?? [], ...material?.kind === "oauth" && isConsoleProvider(ctx) ? [CONSOLE_OAUTH.betaHeader] : []].filter((value, index, all) => all.indexOf(value) === index);
669
+ const material = await resolveFreshMaterial(ctx);
670
+ const betas = [...opts.betas ?? [], ...material?.kind === "oauth" && 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,195 @@ 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 expiresAt = await readProfileExpiresAt(options.anthropicConfigDir, profile) ?? now() + 3600000;
1221
+ const material = { kind: "bearer", token, expiresAt };
1222
+ const ref = anthropicCredentialRef("default", options.service);
1223
+ try {
1224
+ await store.set(ref, material);
1225
+ } catch {
1226
+ return { ok: false, reason: "the bearer credential could not be written" };
1227
+ }
1228
+ return { ok: true, expiresAt };
1229
+ }
1230
+ var MAX_PROFILE_FILE_BYTES = 64 * 1024;
1231
+ async function readProfileExpiresAt(anthropicConfigDir, profile) {
1232
+ try {
1233
+ const raw = await Bun.file(join2(anthropicConfigDir, "credentials", `${profile}.json`)).slice(0, MAX_PROFILE_FILE_BYTES).text();
1234
+ const parsed = JSON.parse(raw);
1235
+ return typeof parsed.expires_at === "number" ? parsed.expires_at : undefined;
1236
+ } catch {
1237
+ return;
1238
+ }
1239
+ }
1240
+ function anthropicConsoleProfileExists(anthropicConfigDir, profile = DEFAULT_ANTHROPIC_CONSOLE_PROFILE) {
1241
+ return existsSync(join2(anthropicConfigDir, "credentials", `${profile}.json`));
1242
+ }
1243
+ async function logoutAnthropicConsole(store, options) {
1244
+ requireBunRuntime2("logoutAnthropicConsole", "Bun.spawn", "Signing out spawns the `claude` binary, which needs Bun's subprocess API. Run it under Bun.");
1245
+ const profile = options.profile ?? DEFAULT_ANTHROPIC_CONSOLE_PROFILE;
1246
+ const spawnFn = options.spawn ?? Bun.spawn;
1247
+ try {
1248
+ const child = spawnFn([options.claudeExecutable, "auth", "logout"], { env: brokerEnv(options, profile), stdout: "pipe", stderr: "pipe" });
1249
+ await child.exited;
1250
+ } catch {}
1251
+ await store.delete(anthropicCredentialRef("default", options.service));
1252
+ }
1163
1253
  // src/discovery.ts
1164
1254
  var MAX_MODEL_ID_CHARS = 256;
1165
1255
  var MAX_DISPLAY_NAME_CHARS = 512;
@@ -2237,10 +2327,10 @@ async function startCodexLogin(store, options) {
2237
2327
  throw capabilityRefusal("the codex token exchange returned no ChatGPT account id, so the credential has no per-account record to occupy (R6-10)");
2238
2328
  }
2239
2329
  const ref = codexCredentialRef(tokens.accountId, options.service);
2240
- await store.set(ref, materialFor2(tokens));
2330
+ await store.set(ref, materialFor(tokens));
2241
2331
  return { ref, accountId: tokens.accountId, expiresAt: tokens.expiresAt };
2242
2332
  }
2243
- function materialFor2(tokens) {
2333
+ function materialFor(tokens) {
2244
2334
  return {
2245
2335
  kind: "oauth",
2246
2336
  accessToken: tokens.accessToken,
@@ -2436,7 +2526,7 @@ function decodeSubject(idToken) {
2436
2526
  return;
2437
2527
  }
2438
2528
  }
2439
- function materialFor3(tokens, accountId) {
2529
+ function materialFor2(tokens, accountId) {
2440
2530
  return {
2441
2531
  kind: "oauth",
2442
2532
  accessToken: tokens.accessToken,
@@ -2470,7 +2560,7 @@ async function startXaiLogin(store, options = {}) {
2470
2560
  throw capabilityRefusal("the xAI token exchange returned no account subject, so the credential has no per-account record to occupy (R6-10)");
2471
2561
  }
2472
2562
  const ref = xaiCredentialRef(accountId, options.service);
2473
- await store.set(ref, materialFor3(tokens, accountId));
2563
+ await store.set(ref, materialFor2(tokens, accountId));
2474
2564
  return { ref, accountId, expiresAt: tokens.expiresAt };
2475
2565
  }
2476
2566
  var REFRESH_WINDOW_MS = 60000;
@@ -2749,7 +2839,7 @@ function createHistoryRenderer(registry, options = {}) {
2749
2839
  report.droppedNativeState++;
2750
2840
  report.strippedInDialectBlocks += strippedBlocks;
2751
2841
  const link = message.uuid !== undefined ? chain.get(message.uuid) : undefined;
2752
- const material = materialFor4(link, source, allowExposed);
2842
+ const material = materialFor3(link, source, allowExposed);
2753
2843
  if (material === undefined) {
2754
2844
  report.withoutMaterial++;
2755
2845
  } else {
@@ -2800,7 +2890,7 @@ function createHistoryRenderer(registry, options = {}) {
2800
2890
  renderWithReport
2801
2891
  };
2802
2892
  }
2803
- function materialFor4(link, source, allowExposed) {
2893
+ function materialFor3(link, source, allowExposed) {
2804
2894
  const text = link?.summary;
2805
2895
  if (text === undefined || text.length === 0)
2806
2896
  return;
@@ -3068,10 +3158,11 @@ export {
3068
3158
  BEDROCK_ADAPTER_VERSION,
3069
3159
  BunRequiredError2 as BunRequiredError,
3070
3160
  CODEX_ORIGINATOR2 as CODEX_ORIGINATOR,
3071
- CONSOLE_OAUTH,
3161
+ CONSOLE_BEARER,
3072
3162
  CREDENTIAL_HEADER_NAMES2 as CREDENTIAL_HEADER_NAMES,
3073
3163
  CredentialResolutionError2 as CredentialResolutionError,
3074
3164
  DEEPSEEK_BASE_URL2 as DEEPSEEK_BASE_URL,
3165
+ DEFAULT_ANTHROPIC_CONSOLE_PROFILE,
3075
3166
  DEFAULT_MAX_REDIRECTS2 as DEFAULT_MAX_REDIRECTS,
3076
3167
  DEFAULT_MAX_RETRIES2 as DEFAULT_MAX_RETRIES,
3077
3168
  DERIVED_XAI,
@@ -3082,7 +3173,6 @@ export {
3082
3173
  GOOGLE_DEFAULT_BASE_URL2 as GOOGLE_DEFAULT_BASE_URL,
3083
3174
  INSTRUCTION_FILE_BASENAMES,
3084
3175
  MIN_DECORATION_BODY_CHARS,
3085
- OAUTH_REFRESH_WINDOW_MS,
3086
3176
  OPENAI_API_BASE_URL2 as OPENAI_API_BASE_URL,
3087
3177
  OPENAI_CHAT_BASE_URL2 as OPENAI_CHAT_BASE_URL,
3088
3178
  OPENROUTER_BASE_URL2 as OPENROUTER_BASE_URL,
@@ -3103,6 +3193,7 @@ export {
3103
3193
  XAI_OAUTH,
3104
3194
  XAI_OAUTH_ADAPTER_ID,
3105
3195
  activeWinterIdentity2 as activeWinterIdentity,
3196
+ anthropicConsoleProfileExists,
3106
3197
  anthropicCredentialRef,
3107
3198
  applyDecorationToContent,
3108
3199
  applyPrivilegedHeaders2 as applyPrivilegedHeaders,
@@ -3152,6 +3243,7 @@ export {
3152
3243
  isDisallowedAddress2 as isDisallowedAddress,
3153
3244
  isLocalAddressClass2 as isLocalAddressClass,
3154
3245
  isProviderError2 as isProviderError,
3246
+ logoutAnthropicConsole,
3155
3247
  mapAnthropicEffort,
3156
3248
  mapBedrockEffort,
3157
3249
  mapGoogleEffort2 as mapGoogleEffort,
@@ -3166,6 +3258,7 @@ export {
3166
3258
  redactCredentialMaterial2 as redactCredentialMaterial,
3167
3259
  redactMaterial2 as redactMaterial,
3168
3260
  redactRef2 as redactRef,
3261
+ refreshAnthropicBearer,
3169
3262
  refreshOauthMaterial,
3170
3263
  renderIdentityHeaders2 as renderIdentityHeaders,
3171
3264
  requireBunRuntime2 as requireBunRuntime,
@@ -3177,7 +3270,7 @@ export {
3177
3270
  setWinterIdentity2 as setWinterIdentity,
3178
3271
  shouldRequestSummary2 as shouldRequestSummary,
3179
3272
  signRequest2 as signRequest,
3180
- startAnthropicConsoleLogin,
3273
+ startAnthropicConsoleBrokerLogin,
3181
3274
  startCodexLogin,
3182
3275
  startXaiLogin,
3183
3276
  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-446qxzss.js";
28
+ } from "./index-r2frr72q.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.5",
3
+ "version": "0.0.6",
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.5",
46
- "@yanlinglabs/winter-provider-catalog": "0.0.5"
45
+ "@yanlinglabs/winter-agent-sdk": "0.0.6",
46
+ "@yanlinglabs/winter-provider-catalog": "0.0.6"
47
47
  },
48
48
  "scripts": {}
49
49
  }