@providerkit/core 0.21.1 → 0.23.0

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.
Files changed (137) hide show
  1. package/README.md +7 -7
  2. package/dist/attribution.d.ts +2 -2
  3. package/dist/auth/flows.d.ts.map +1 -1
  4. package/dist/auth/flows.js +5 -5
  5. package/dist/auth/flows.js.map +1 -1
  6. package/dist/auth/index.d.ts +1 -0
  7. package/dist/auth/index.d.ts.map +1 -1
  8. package/dist/auth/index.js +1 -0
  9. package/dist/auth/index.js.map +1 -1
  10. package/dist/auth/oauth.d.ts +12 -0
  11. package/dist/auth/oauth.d.ts.map +1 -1
  12. package/dist/auth/oauth.js +19 -1
  13. package/dist/auth/oauth.js.map +1 -1
  14. package/dist/auth/token-source.d.ts +9 -3
  15. package/dist/auth/token-source.d.ts.map +1 -1
  16. package/dist/auth/token-source.js +24 -9
  17. package/dist/auth/token-source.js.map +1 -1
  18. package/dist/auth/with-token.d.ts +29 -0
  19. package/dist/auth/with-token.d.ts.map +1 -0
  20. package/dist/auth/with-token.js +85 -0
  21. package/dist/auth/with-token.js.map +1 -0
  22. package/dist/capability.d.ts +9 -9
  23. package/dist/capability.js +9 -9
  24. package/dist/capability.js.map +1 -1
  25. package/dist/context.d.ts +43 -5
  26. package/dist/context.d.ts.map +1 -1
  27. package/dist/context.js +123 -4
  28. package/dist/context.js.map +1 -1
  29. package/dist/errors.d.ts +26 -17
  30. package/dist/errors.d.ts.map +1 -1
  31. package/dist/errors.js +57 -18
  32. package/dist/errors.js.map +1 -1
  33. package/dist/fallback.d.ts +45 -1
  34. package/dist/fallback.d.ts.map +1 -1
  35. package/dist/fallback.js +138 -75
  36. package/dist/fallback.js.map +1 -1
  37. package/dist/image.d.ts +1 -1
  38. package/dist/image.d.ts.map +1 -1
  39. package/dist/jev.d.ts +9 -9
  40. package/dist/jev.d.ts.map +1 -1
  41. package/dist/jev.js +1 -1
  42. package/dist/key-pool.d.ts +14 -8
  43. package/dist/key-pool.d.ts.map +1 -1
  44. package/dist/key-pool.js +35 -18
  45. package/dist/key-pool.js.map +1 -1
  46. package/dist/presets.d.ts +33 -24
  47. package/dist/presets.d.ts.map +1 -1
  48. package/dist/presets.js +25 -14
  49. package/dist/presets.js.map +1 -1
  50. package/dist/providers/anthropic.d.ts +5 -5
  51. package/dist/providers/anthropic.d.ts.map +1 -1
  52. package/dist/providers/anthropic.js +89 -34
  53. package/dist/providers/anthropic.js.map +1 -1
  54. package/dist/providers/factory.d.ts +2 -2
  55. package/dist/providers/factory.d.ts.map +1 -1
  56. package/dist/providers/factory.js +2 -1
  57. package/dist/providers/factory.js.map +1 -1
  58. package/dist/providers/gemini.d.ts +8 -2
  59. package/dist/providers/gemini.d.ts.map +1 -1
  60. package/dist/providers/gemini.js +117 -41
  61. package/dist/providers/gemini.js.map +1 -1
  62. package/dist/providers/openai.d.ts +12 -9
  63. package/dist/providers/openai.d.ts.map +1 -1
  64. package/dist/providers/openai.js +72 -21
  65. package/dist/providers/openai.js.map +1 -1
  66. package/dist/providers/responses.d.ts +3 -3
  67. package/dist/providers/responses.d.ts.map +1 -1
  68. package/dist/providers/responses.js +103 -57
  69. package/dist/providers/responses.js.map +1 -1
  70. package/dist/providers/zai.d.ts +2 -2
  71. package/dist/providers/zai.js +2 -2
  72. package/dist/rate-limit.d.ts +13 -3
  73. package/dist/rate-limit.d.ts.map +1 -1
  74. package/dist/rate-limit.js +10 -4
  75. package/dist/rate-limit.js.map +1 -1
  76. package/dist/retry.d.ts +16 -5
  77. package/dist/retry.d.ts.map +1 -1
  78. package/dist/retry.js +29 -16
  79. package/dist/retry.js.map +1 -1
  80. package/dist/schema.d.ts +6 -6
  81. package/dist/schema.js +8 -8
  82. package/dist/tool-args.d.ts +23 -1
  83. package/dist/tool-args.d.ts.map +1 -1
  84. package/dist/tool-args.js +30 -2
  85. package/dist/tool-args.js.map +1 -1
  86. package/dist/tools.d.ts +3 -3
  87. package/dist/tools.js +2 -2
  88. package/dist/tools.js.map +1 -1
  89. package/dist/transport.d.ts +40 -10
  90. package/dist/transport.d.ts.map +1 -1
  91. package/dist/transport.js +82 -21
  92. package/dist/transport.js.map +1 -1
  93. package/dist/types.d.ts +37 -23
  94. package/dist/types.d.ts.map +1 -1
  95. package/dist/types.js +3 -11
  96. package/dist/types.js.map +1 -1
  97. package/dist/usage.d.ts +4 -4
  98. package/dist/usage.d.ts.map +1 -1
  99. package/dist/usage.js +3 -3
  100. package/dist/usage.js.map +1 -1
  101. package/dist/watchdog.d.ts +14 -12
  102. package/dist/watchdog.d.ts.map +1 -1
  103. package/dist/watchdog.js +35 -16
  104. package/dist/watchdog.js.map +1 -1
  105. package/dist/zod.d.ts +4 -4
  106. package/dist/zod.js +3 -3
  107. package/package.json +1 -1
  108. package/src/attribution.ts +2 -2
  109. package/src/auth/flows.ts +5 -4
  110. package/src/auth/index.ts +1 -0
  111. package/src/auth/oauth.ts +23 -1
  112. package/src/auth/token-source.ts +30 -11
  113. package/src/auth/with-token.ts +97 -0
  114. package/src/capability.ts +12 -12
  115. package/src/context.ts +141 -6
  116. package/src/errors.ts +63 -23
  117. package/src/fallback.ts +173 -75
  118. package/src/image.ts +1 -1
  119. package/src/jev.ts +11 -11
  120. package/src/key-pool.ts +37 -20
  121. package/src/presets.ts +38 -24
  122. package/src/providers/anthropic.ts +97 -38
  123. package/src/providers/factory.ts +4 -3
  124. package/src/providers/gemini.ts +121 -44
  125. package/src/providers/openai.ts +78 -25
  126. package/src/providers/responses.ts +113 -61
  127. package/src/providers/zai.ts +2 -2
  128. package/src/rate-limit.ts +35 -7
  129. package/src/retry.ts +46 -15
  130. package/src/schema.ts +8 -8
  131. package/src/tool-args.ts +46 -2
  132. package/src/tools.ts +4 -4
  133. package/src/transport.ts +103 -23
  134. package/src/types.ts +37 -23
  135. package/src/usage.ts +5 -5
  136. package/src/watchdog.ts +38 -19
  137. package/src/zod.ts +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"watchdog.js","sourceRoot":"","sources":["../src/watchdog.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"watchdog.js","sourceRoot":"","sources":["../src/watchdog.ts"],"names":[],"mappings":"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"}
package/dist/zod.d.ts CHANGED
@@ -5,8 +5,8 @@ import type { JsonObjectSchema } from "./types.ts";
5
5
  * A zod schema as the JSON Schema every provider's tool contract wants.
6
6
  *
7
7
  * `$schema` is dropped. zod emits the dialect URI at the root, and a provider
8
- * validating a tool's `parameters` against its own supported subset — OpenAI
9
- * under `strict: true`, Gemini's `parametersJsonSchema` — rejects the whole
8
+ * validating a tool's `parameters` against its own supported subset (OpenAI
9
+ * under `strict: true`, Gemini's `parametersJsonSchema`) rejects the whole
10
10
  * tool over that one key, with a message that names neither zod nor the field.
11
11
  */
12
12
  export declare function toJsonObjectSchema(schema: z.ZodType, label?: string): JsonObjectSchema;
@@ -26,13 +26,13 @@ export interface ZodToolSpec<I, O> {
26
26
  * rejecting the call. Worth it for a TERMINAL tool, which gets no second
27
27
  * chance: a forced-submit salvage turn runs exactly once, and discarding an
28
28
  * otherwise-valid answer over a few extra characters loses the whole run.
29
- * Off by default — an ordinary tool can simply be called again.
29
+ * Off by default: an ordinary tool can simply be called again.
30
30
  */
31
31
  clampOverflow?: boolean;
32
32
  }
33
33
  /**
34
34
  * A tool whose arguments are validated by zod, with the failure reported to
35
- * the MODEL in words it can act on — `topic: expected string, received number`
35
+ * the model in words it can act on. `topic: expected string, received number`
36
36
  * beats a stack trace it cannot read.
37
37
  */
38
38
  export declare function zodTool<I, O>(spec: ZodToolSpec<I, O>): Tool<I, O>;
package/dist/zod.js CHANGED
@@ -10,8 +10,8 @@ import { clampToSchema } from "./schema.js";
10
10
  * A zod schema as the JSON Schema every provider's tool contract wants.
11
11
  *
12
12
  * `$schema` is dropped. zod emits the dialect URI at the root, and a provider
13
- * validating a tool's `parameters` against its own supported subset — OpenAI
14
- * under `strict: true`, Gemini's `parametersJsonSchema` — rejects the whole
13
+ * validating a tool's `parameters` against its own supported subset (OpenAI
14
+ * under `strict: true`, Gemini's `parametersJsonSchema`) rejects the whole
15
15
  * tool over that one key, with a message that names neither zod nor the field.
16
16
  */
17
17
  export function toJsonObjectSchema(schema, label = "schema") {
@@ -26,7 +26,7 @@ export function toJsonObjectSchema(schema, label = "schema") {
26
26
  }
27
27
  /**
28
28
  * A tool whose arguments are validated by zod, with the failure reported to
29
- * the MODEL in words it can act on — `topic: expected string, received number`
29
+ * the model in words it can act on. `topic: expected string, received number`
30
30
  * beats a stack trace it cannot read.
31
31
  */
32
32
  export function zodTool(spec) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@providerkit/core",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "description": "The layer under your agent loop: one seam for every LLM provider, plus the failure handling you only learn in production.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -9,9 +9,9 @@
9
9
  // HTTP header names are case-insensitive but this seam carries them as a
10
10
  // plain record, so `http-referer` and `HTTP-Referer` must mean the same opt-out.
11
11
  export interface Attribution {
12
- /** Site URL, sent as `HTTP-Referer` — e.g. "https://your.app". */
12
+ /** Site URL, sent as `HTTP-Referer` (e.g. "https://your.app"). */
13
13
  siteUrl?: string;
14
- /** App name, sent as `X-Title` — e.g. "Your App". */
14
+ /** App name, sent as `X-Title` (e.g. "Your App"). */
15
15
  siteName?: string;
16
16
  }
17
17
 
package/src/auth/flows.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  accountFromToken,
9
9
  asRecord,
10
10
  authError,
11
+ authFetch,
11
12
  generatePKCE,
12
13
  jwtClaims,
13
14
  num,
@@ -308,7 +309,7 @@ export function copilotHeaders(appName: string, turn: "user" | "agent"): Record<
308
309
  }
309
310
 
310
311
  async function mintCopilotToken(host: AuthHost, githubToken: string): Promise<Credential> {
311
- const res = await (host.fetchImpl ?? fetch)(COPILOT_TOKEN_URL, {
312
+ const res = await authFetch(host, COPILOT_TOKEN_URL, {
312
313
  headers: {
313
314
  Accept: "application/json",
314
315
  Authorization: `Bearer ${githubToken}`,
@@ -361,7 +362,7 @@ async function refreshCopilot(host: AuthHost, credential: Credential): Promise<C
361
362
  * lookup did, so any failure means no name. */
362
363
  async function githubLogin(host: AuthHost, githubToken: string): Promise<string | undefined> {
363
364
  try {
364
- const res = await (host.fetchImpl ?? fetch)(GITHUB_USER_URL, {
365
+ const res = await authFetch(host, GITHUB_USER_URL, {
365
366
  headers: { Accept: "application/json", Authorization: `Bearer ${githubToken}` },
366
367
  });
367
368
  return res.ok ? str(asRecord(await res.json()).login) : undefined;
@@ -405,7 +406,7 @@ const META_KEY_URL = "https://api.meta.ai/muse-code/key";
405
406
  const META_KEY_LIFETIME_MS = 24 * 60 * 60 * 1000;
406
407
 
407
408
  async function mintMetaKey(host: AuthHost, identityToken: string): Promise<Credential> {
408
- const res = await (host.fetchImpl ?? fetch)(META_KEY_URL, {
409
+ const res = await authFetch(host, META_KEY_URL, {
409
410
  method: "POST",
410
411
  headers: {
411
412
  Accept: "application/json",
@@ -494,7 +495,7 @@ function openrouterFlow(host: AuthHost): AuthFlow {
494
495
  redirectUri: OPENROUTER.redirectUri,
495
496
  signal,
496
497
  });
497
- const res = await (host.fetchImpl ?? fetch)(OPENROUTER.keysUrl, {
498
+ const res = await authFetch(host, OPENROUTER.keysUrl, {
498
499
  method: "POST",
499
500
  headers: { "Content-Type": "application/json", Accept: "application/json" },
500
501
  body: JSON.stringify({ code, code_verifier: verifier, code_challenge_method: "S256" }),
package/src/auth/index.ts CHANGED
@@ -5,3 +5,4 @@ export * from "./oauth.ts";
5
5
  export * from "./device.ts";
6
6
  export * from "./flows.ts";
7
7
  export * from "./token-source.ts";
8
+ export * from "./with-token.ts";
package/src/auth/oauth.ts CHANGED
@@ -25,6 +25,8 @@ export interface AuthHost {
25
25
  }): Promise<string>;
26
26
  /** Replaces `fetch`, for tests. */
27
27
  fetchImpl?: typeof fetch;
28
+ /** How long one request to a sign-in server may take, in ms. Default 30 s. */
29
+ timeoutMs?: number;
28
30
  }
29
31
 
30
32
  /** The stored result of a sign-in. */
@@ -76,6 +78,26 @@ export function authError(
76
78
  /** Refresh this long before the server's own expiry. */
77
79
  export const REFRESH_SKEW_MS = 5 * 60 * 1000;
78
80
 
81
+ /** The deadline cc-proxy puts on its own token requests. */
82
+ const AUTH_TIMEOUT_MS = 30_000;
83
+
84
+ /**
85
+ * `fetch` with a deadline, for every request in `auth/`.
86
+ *
87
+ * A sign-in server that accepts the connection and never answers would hold
88
+ * the one shared refresh forever, and every call waiting on it with it. No
89
+ * clock above this one cuts it: the watchdog only starts once the model request
90
+ * is sent. The timeout rejects with a `TimeoutError`, which is not a refusal,
91
+ * so `tokenSource` keeps the stored credential.
92
+ */
93
+ export function authFetch(host: AuthHost, url: string, init: RequestInit = {}): Promise<Response> {
94
+ const deadline = AbortSignal.timeout(host.timeoutMs ?? AUTH_TIMEOUT_MS);
95
+ return (host.fetchImpl ?? fetch)(url, {
96
+ ...init,
97
+ signal: init.signal ? AbortSignal.any([init.signal, deadline]) : deadline,
98
+ });
99
+ }
100
+
79
101
  export const str = (value: unknown): string | undefined =>
80
102
  typeof value === "string" && value.length > 0 ? value : undefined;
81
103
 
@@ -119,7 +141,7 @@ export async function postToken(
119
141
  opts: { encode: "json" | "form"; allowErrorBody?: boolean },
120
142
  ): Promise<Record<string, unknown>> {
121
143
  const json = opts.encode === "json";
122
- const res = await (host.fetchImpl ?? fetch)(url, {
144
+ const res = await authFetch(host, url, {
123
145
  method: "POST",
124
146
  headers: {
125
147
  "Content-Type": json ? "application/json" : "application/x-www-form-urlencoded",
@@ -16,12 +16,16 @@ export interface TokenSourceOptions<C extends Credential = Credential> {
16
16
 
17
17
  export interface TokenSource<C extends Credential = Credential> {
18
18
  /** A credential whose `accessToken` is good now. Throws a `ProviderError`
19
- * coded `refresh_dead` when the user must sign in again. */
20
- getToken(): Promise<C>;
19
+ * coded `refresh_dead` when the user must sign in again.
20
+ *
21
+ * Pass `rejected`, the access token a server just answered 401 to, to renew
22
+ * it even though it has not expired. A server can refuse a token early: it
23
+ * was revoked, or another client rotated it. */
24
+ getToken(opts?: { rejected?: string }): Promise<C>;
21
25
  }
22
26
 
23
27
  /**
24
- * Refresh when the stored credential has expired, and only then.
28
+ * Refresh when the stored credential has expired or was rejected, and only then.
25
29
  *
26
30
  * - Concurrent calls share one refresh and the result is saved before use.
27
31
  * - When the server rejects the refresh, the stored credential is reloaded. If
@@ -36,7 +40,20 @@ export interface TokenSource<C extends Credential = Credential> {
36
40
  export function tokenSource<C extends Credential>(options: TokenSourceOptions<C>): TokenSource<C> {
37
41
  let inFlight: Promise<C> | undefined;
38
42
 
39
- async function refreshStored(stale: C): Promise<C> {
43
+ async function loadSignedIn(): Promise<C> {
44
+ const credential = await options.load();
45
+ if (!credential) throw authError("refresh_dead", "Not signed in. Sign in first.");
46
+ return credential;
47
+ }
48
+
49
+ async function refreshStored(rejected: string | undefined): Promise<C> {
50
+ // Read again inside the single flight. A caller whose read raced a refresh
51
+ // that had just saved would otherwise start a second one with the refresh
52
+ // token the first already spent. Five calls refused on one token is the
53
+ // normal case for that race, and a server that rotates refresh tokens may
54
+ // take a reused one as stolen and end the whole sign-in.
55
+ const stale = await loadSignedIn();
56
+ if (isUsable(stale, rejected)) return stale;
40
57
  try {
41
58
  const fresh = await options.refresh(stale);
42
59
  await options.save(fresh);
@@ -50,13 +67,10 @@ export function tokenSource<C extends Credential>(options: TokenSourceOptions<C>
50
67
  }
51
68
 
52
69
  return {
53
- async getToken() {
54
- const credential = await options.load();
55
- if (!credential) {
56
- throw authError("refresh_dead", "Not signed in. Sign in first.");
57
- }
58
- if (Date.now() < credential.expiresAt) return credential;
59
- inFlight ??= refreshStored(credential).finally(() => {
70
+ async getToken({ rejected } = {}) {
71
+ const credential = await loadSignedIn();
72
+ if (isUsable(credential, rejected)) return credential;
73
+ inFlight ??= refreshStored(rejected).finally(() => {
60
74
  inFlight = undefined;
61
75
  });
62
76
  return inFlight;
@@ -64,6 +78,11 @@ export function tokenSource<C extends Credential>(options: TokenSourceOptions<C>
64
78
  };
65
79
  }
66
80
 
81
+ /** Unexpired, and not the token a server just refused. */
82
+ function isUsable(credential: Credential, rejected: string | undefined): boolean {
83
+ return Date.now() < credential.expiresAt && credential.accessToken !== rejected;
84
+ }
85
+
67
86
  /** A refresh the server turned down for good, as opposed to one that never
68
87
  * reached it or came back unreadable. */
69
88
  function isRejected(error: unknown): error is ProviderError {
@@ -0,0 +1,97 @@
1
+ // A provider that signs every call with the stored sign-in, and renews the token
2
+ // once when a server refuses it before it expired. The sibling of
3
+ // `withKeyPool`: that one rotates between keys, this one renews the one token.
4
+ //
5
+ // Type-only imports from the sign-in side, so core's root can export this
6
+ // without pulling the flows in.
7
+ import { ProviderError } from "../errors.ts";
8
+ import type { PresetProviderConfig } from "../providers/factory.ts";
9
+ import type { Provider } from "../types.ts";
10
+ import type { Credential } from "./oauth.ts";
11
+ import type { TokenSource } from "./token-source.ts";
12
+
13
+ /** The credential half of `PresetProviderConfig`, so `build` can spread it in. */
14
+ export type TokenAuth = Pick<PresetProviderConfig, "apiKey" | "baseUrl" | "headers">;
15
+
16
+ /**
17
+ * Give any provider the signed-in user's token, read fresh on every call.
18
+ *
19
+ * ```ts
20
+ * const provider = withToken(token, (auth) => createPresetProvider("github-copilot", auth));
21
+ * ```
22
+ *
23
+ * `build` runs once per attempt and must do no I/O, like `withKeyPool`'s
24
+ * factory. It gets the access token as `apiKey`, the credential's own host as
25
+ * `baseUrl` (Copilot gives each plan its own) and its `ChatGPT-Account-Id`
26
+ * header, which the Codex backend refuses the request without. To add headers
27
+ * of your own, merge them with `auth.headers` rather than replacing them.
28
+ *
29
+ * A 401 before any output renews the token and replays the call once: a token
30
+ * can be refused before its expiry, and without this the caller sees an auth
31
+ * failure that a fallback pool benches for an hour. A second 401 is a real one
32
+ * and surfaces. A 401 after output never replays (invariant 1).
33
+ *
34
+ * Only 401 counts. A 400 or 403 on a model call is a bad request or a plan
35
+ * that lacks the model, and a new token fixes neither.
36
+ */
37
+ export function withToken<C extends Credential>(
38
+ source: TokenSource<C>,
39
+ build: (auth: TokenAuth) => Provider,
40
+ ): Provider {
41
+ // Building does no I/O, so a throwaway instance reads `id` and `model`
42
+ // without a token.
43
+ const identity = build({ apiKey: "" });
44
+
45
+ return {
46
+ id: identity.id,
47
+ model: identity.model,
48
+
49
+ async *createStream(messages, tools, opts = {}) {
50
+ const credential = await unlessAborted(source.getToken(), opts.signal);
51
+ let emitted = false;
52
+ try {
53
+ for await (const chunk of build(authOf(credential)).createStream(messages, tools, opts)) {
54
+ emitted = true;
55
+ yield chunk;
56
+ }
57
+ return;
58
+ } catch (error) {
59
+ if (emitted || !(error instanceof ProviderError) || error.status !== 401) throw error;
60
+ }
61
+ const renewed = await unlessAborted(
62
+ source.getToken({ rejected: credential.accessToken }),
63
+ opts.signal,
64
+ );
65
+ yield* build(authOf(renewed)).createStream(messages, tools, opts);
66
+ },
67
+ };
68
+ }
69
+
70
+ function authOf(credential: Credential): TokenAuth {
71
+ return {
72
+ apiKey: credential.accessToken,
73
+ ...(credential.baseUrl ? { baseUrl: credential.baseUrl } : {}),
74
+ ...(credential.chatgptAccountId
75
+ ? { headers: { "ChatGPT-Account-Id": credential.chatgptAccountId } }
76
+ : {}),
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Wait for the token, or reject with the caller's abort, whichever is first.
82
+ *
83
+ * The refresh itself is never aborted. It is shared by every caller, and once
84
+ * its POST is out the server may already have rotated the refresh token, so
85
+ * abandoning it before the save loses the only copy of the new one. And
86
+ * without the race an unanswered refresh would outlive the watchdog, which
87
+ * aborts the signal but never races a read.
88
+ */
89
+ function unlessAborted<T>(pending: Promise<T>, signal: AbortSignal | undefined): Promise<T> {
90
+ if (!signal) return pending;
91
+ return new Promise<T>((resolve, reject) => {
92
+ const onAbort = () => reject(signal.reason);
93
+ if (signal.aborted) onAbort();
94
+ else signal.addEventListener("abort", onAbort, { once: true });
95
+ pending.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
96
+ });
97
+ }
package/src/capability.ts CHANGED
@@ -18,12 +18,12 @@
18
18
 
19
19
  import type { JsonWithTools, Provider } from "./types.ts";
20
20
 
21
- /** Nothing a model can answer from memory — so a tool call is the only correct
22
- * turn, and NOT making one is a real signal rather than a judgement call. */
21
+ /** A question the model cannot answer from memory, so a tool call is the only
22
+ * correct turn. Not making one is a real signal rather than a judgement call. */
23
23
  const PROBE_TOOL = {
24
24
  name: "get_current_time",
25
25
  description:
26
- "The only source of the current time. You cannot know the time without calling this — never answer a time question from memory or guess.",
26
+ "The only source of the current time. You cannot know the time without calling this. Never answer a time question from memory or guess.",
27
27
  inputSchema: {
28
28
  type: "object" as const,
29
29
  properties: { timezone: { type: "string", description: "IANA timezone, e.g. Asia/Tokyo" } },
@@ -46,7 +46,7 @@ const SHAPES: JsonWithTools[] = ["response_format", "prompt"];
46
46
  export interface JsonWithToolsProbe {
47
47
  /**
48
48
  * The shape to configure, or `null` when neither called the tool on every
49
- * sample — that model cannot be trusted with tools and a schema together,
49
+ * sample. That model cannot be trusted with tools and a schema together,
50
50
  * whichever way the schema rides, and the honest fix is a different model.
51
51
  */
52
52
  use: JsonWithTools | null;
@@ -58,8 +58,8 @@ export interface JsonWithToolsProbe {
58
58
 
59
59
  export interface ProbeOptions {
60
60
  /**
61
- * Samples per shape. Above one because the interesting failures are partial —
62
- * `gemini-3.8-flash` called its tool 3/10 — and a single sample reports a
61
+ * Samples per shape. Above one because the interesting failures are partial:
62
+ * `gemini-3.8-flash` called its tool 3/10. A single sample reports a
63
63
  * coin flip as a capability. A shape passes only when EVERY sample calls.
64
64
  */
65
65
  samples?: number;
@@ -95,12 +95,12 @@ async function callsTool(
95
95
 
96
96
  /**
97
97
  * Ask a model, on the wire, which {@link JsonWithTools} shape it actually
98
- * serves — then configure the provider with `probe.use`.
98
+ * serves. Then configure the provider with `probe.use`.
99
99
  *
100
100
  * Both shapes are always tried, because a result you can read beats a result
101
101
  * you have to trust: `calls` is the table to log, and "0/3 and 3/3" is what
102
102
  * makes a later regression obvious. It costs `samples × 2` short calls, made
103
- * one after another, once, at whatever moment the app decides to ask — boot is
103
+ * one after another, once, at whatever moment the app decides to ask. Boot is
104
104
  * the usual one.
105
105
  *
106
106
  * Probe each provider on its own, never a fallback chain: the shape is a fact
@@ -117,8 +117,8 @@ async function callsTool(
117
117
  * report "this model cannot call tools", which is a much worse thing to
118
118
  * believe than "the call failed".
119
119
  *
120
- * What it catches is the structural failure — the model that CANNOT emit the
121
- * call, every time, on the easiest question there is. A model that is merely
120
+ * It catches a structural failure: a model that cannot emit the call, every
121
+ * time, on the easiest question there is. A model that is merely
122
122
  * unreliable will pass: `gemini-3.8-flash` answers this probe 3/3 and a real
123
123
  * sales turn 3/10. That is the right line to draw. A harder probe would start
124
124
  * failing good models for being terse, and "sometimes forgets its tools" is a
@@ -226,8 +226,8 @@ function normalizeId(id: string): string {
226
226
  * - `deepseek/deepseek-v4.1-flash` matches `deepseek-v4.1-flash`
227
227
  * - `accounts/fireworks/models/glm-5p3-flash` matches `glm-5.3-flash`
228
228
  *
229
- * Never throws — if the catalog cannot be retrieved or the model is unknown,
230
- * returns `undefined` so capability lookups never break caller execution.
229
+ * Never throws. If the catalog cannot be retrieved or the model is unknown,
230
+ * it returns `undefined` so capability lookups never break caller execution.
231
231
  */
232
232
  export async function resolveModelCapabilities(
233
233
  modelId: string,
package/src/context.ts CHANGED
@@ -1,21 +1,22 @@
1
- // Deciding when a conversation no longer fits, and where to cut it.
1
+ // Deciding when a conversation no longer fits, where to cut it, and how to
2
+ // make a saved one valid to send again.
2
3
  //
3
4
  // Everything here is pure and testable. The model call that writes the summary
4
5
  // and the row that stores it belong to the caller — what is decidable without
5
6
  // I/O is decided here.
6
- import type { ChatMessage } from "./types.ts";
7
+ import type { ChatMessage, ToolCall } from "./types.ts";
7
8
 
8
9
  /**
9
10
  * What the answer needs after the prompt: an output budget, plus the tools'
10
11
  * own schemas, plus the slack no provider documents.
11
12
  *
12
- * Absolute rather than a percentage on purpose — a 1M window does not need a
13
+ * Absolute rather than a percentage on purpose: a 1M window does not need a
13
14
  * 100k cushion, and a 128k window needs more than 12k.
14
15
  */
15
16
  export const CONTEXT_RESERVE_TOKENS = 32_000;
16
17
 
17
18
  /**
18
- * Four characters per token — the rule of thumb every provider's own
19
+ * Four characters per token is the rule of thumb every provider's own
19
20
  * calculator agrees with to within a fifth, which is all the precision this
20
21
  * needs. It decides WHEN to fold: folding one turn early costs a cheap model
21
22
  * call, folding one turn late costs the whole turn.
@@ -64,7 +65,7 @@ export function conversationTokens(messages: readonly ChatMessage[]): number {
64
65
  }
65
66
 
66
67
  /**
67
- * The provider's own input count says the wall is close — fold before the next
68
+ * The provider's own input count says the limit is close. Fold before the next
68
69
  * step rather than after the 400.
69
70
  *
70
71
  * Takes the REPORTED count, not an estimate, because it is the only figure
@@ -99,7 +100,7 @@ export function historyBudgetTokens(contextWindow: number): number {
99
100
  *
100
101
  * 1. Never split a tool call from its result. Every provider rejects a tool
101
102
  * result whose call is missing, so a cut landing between them produces a
102
- * 400 on the very next turn — the failure compaction was called to avoid.
103
+ * 400 on the very next turn, the failure compaction was called to avoid.
103
104
  * 2. Never cut into the system prompt. It is not history.
104
105
  *
105
106
  * Returns the index the kept tail starts at, or 0 when everything already fits.
@@ -153,6 +154,140 @@ export function applyCompaction(
153
154
  ];
154
155
  }
155
156
 
157
+ type ToolResult = Extract<ChatMessage, { role: "tool" }>;
158
+
159
+ /** What a call with no result answers, by whether running it again is safe.
160
+ * The model reads this and decides whether to call again, so the two must
161
+ * differ on exactly that. */
162
+ const INTERRUPTED_READ_ONLY =
163
+ "This call was interrupted before it returned. It changed nothing, so it is safe to call again.";
164
+ const INTERRUPTED =
165
+ "This call was interrupted before it returned. It may have partly run, so check its effect before calling it again.";
166
+
167
+ /** `id`, or `id_2`, `id_3`… whichever is free first. Only ids already handed
168
+ * out are consulted, so an id never depends on what comes after it. */
169
+ function freshId(id: string, used: Set<string>): string {
170
+ let fresh = id;
171
+ for (let k = 2; used.has(fresh); k++) fresh = `${id}_${k}`;
172
+ used.add(fresh);
173
+ return fresh;
174
+ }
175
+
176
+ interface Span {
177
+ calls: ToolCall[];
178
+ vendorIds: string[];
179
+ results: (ToolResult | undefined)[];
180
+ rest: ChatMessage[];
181
+ }
182
+
183
+ /** The first call in the span asked under `id` that has no result yet, or -1.
184
+ * First unanswered rather than first: two calls in one reply can share an id,
185
+ * and their results then pair in order. */
186
+ function openSlot(span: Span, id: string): number {
187
+ return span.vendorIds.findIndex((asked, i) => asked === id && !span.results[i]);
188
+ }
189
+
190
+ /** A result with no call to answer, kept as words the model can still read
191
+ * instead of a `tool` message every provider answers with a 400. */
192
+ function orphanNote(result: ToolResult): ChatMessage {
193
+ const text = `[Tool result for ${result.toolCallId}; the tool call is missing or already has a result]\n${result.content}`;
194
+ return {
195
+ role: "user",
196
+ content: result.images?.length ? [{ type: "text", text }, ...result.images] : text,
197
+ };
198
+ }
199
+
200
+ /**
201
+ * Make a saved, resumed or interrupted history valid to send: every call has
202
+ * exactly one result, right after it, under an id no other call uses.
203
+ *
204
+ * A span runs from an assistant message with tool calls to the next assistant
205
+ * message. Its results move to just after the calls, in call order, and the
206
+ * rest of the span (a steer the user typed while the tools ran) keeps its
207
+ * order after them. Then:
208
+ *
209
+ * - A call with no result in its span gets one under the same id, saying it was
210
+ * interrupted. `isReadOnly` picks the wording; without it every tool may
211
+ * have partly run. These calls come back as `interrupted`, under the ids
212
+ * the request carries.
213
+ * - A result whose call is not in its span becomes a user note, images kept.
214
+ * Paired against the span only, because chat and Messages both require the
215
+ * result in the turn right after its call: a looser pairing still 400s.
216
+ * - A repeated id becomes `{id}_{k}`, the smallest free k, call and result
217
+ * together. Kimi and Gemini restart their ids every reply
218
+ * (`functions.search:0`, `call_0`), and Anthropic and Responses reject the
219
+ * second use.
220
+ * - An empty id becomes `call_{message}_{call}`, never `normalizeToolId`'s
221
+ * random fallback.
222
+ * - An assistant message with no calls and only whitespace is dropped, as if
223
+ * it were never there, so a placeholder saved before the tools ran orphans
224
+ * nothing.
225
+ *
226
+ * No side effects, and the same input always gives the same bytes: a span's
227
+ * output and every id depend only on what came before, so repairing a longer
228
+ * history keeps the shorter one's bytes and the prompt cache keeps hitting.
229
+ * Repairing a repaired history changes nothing. Every adapter here runs this
230
+ * on what it sends; call it yourself to know what `interrupted` holds.
231
+ */
232
+ export function repairHistory(
233
+ messages: readonly ChatMessage[],
234
+ opts: { isReadOnly?: (toolName: string) => boolean } = {},
235
+ ): { messages: ChatMessage[]; interrupted: ToolCall[] } {
236
+ const out: ChatMessage[] = [];
237
+ const interrupted: ToolCall[] = [];
238
+ const used = new Set<string>();
239
+ // The open span: its calls as sent, the id the vendor gave each, the result
240
+ // each has found so far, and everything else in arrival order.
241
+ let span: Span | undefined;
242
+
243
+ const close = () => {
244
+ if (!span) return;
245
+ for (const [i, call] of span.calls.entries()) {
246
+ const result = span.results[i];
247
+ if (result) {
248
+ out.push(result);
249
+ continue;
250
+ }
251
+ interrupted.push(call);
252
+ const content = opts.isReadOnly?.(call.name) ? INTERRUPTED_READ_ONLY : INTERRUPTED;
253
+ out.push({ role: "tool", toolCallId: call.id, name: call.name, content });
254
+ }
255
+ out.push(...span.rest);
256
+ span = undefined;
257
+ };
258
+
259
+ for (const [index, message] of messages.entries()) {
260
+ if (message.role === "assistant") {
261
+ const calls = message.toolCalls ?? [];
262
+ if (calls.length === 0 && !message.content.trim()) continue;
263
+ close();
264
+ if (calls.length === 0) {
265
+ out.push(message);
266
+ continue;
267
+ }
268
+ const sent = calls.map((call, c) => {
269
+ const id = freshId(call.id.trim() ? call.id : `call_${index}_${c}`, used);
270
+ return id === call.id ? call : { ...call, id };
271
+ });
272
+ const renamed = sent.some((call, c) => call !== calls[c]);
273
+ out.push(renamed ? { ...message, toolCalls: sent } : message);
274
+ span = { calls: sent, vendorIds: calls.map((call) => call.id), results: [], rest: [] };
275
+ } else if (message.role === "tool") {
276
+ const slot = span ? openSlot(span, message.toolCallId) : -1;
277
+ if (span && slot !== -1) {
278
+ const id = span.calls[slot]!.id;
279
+ span.results[slot] = id === message.toolCallId ? message : { ...message, toolCallId: id };
280
+ } else {
281
+ (span?.rest ?? out).push(orphanNote(message));
282
+ }
283
+ } else {
284
+ (span?.rest ?? out).push(message);
285
+ }
286
+ }
287
+ close();
288
+ return { messages: out, interrupted };
289
+ }
290
+
156
291
  /**
157
292
  * The context window for a model, when nothing volunteered one.
158
293
  *