@tanstack/ai 0.48.0 → 0.49.1

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.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A BYOK provider declared by an adapter. `id` is the `x-byok-<id>` slug and
3
+ * is required — `{ id?: string }` is not a {@link ByokProvider}.
4
+ */
5
+ export interface ByokProvider<TId extends string = string> {
6
+ readonly id: TId;
7
+ readonly label: string;
8
+ /**
9
+ * Env var names the relay may read. Names only — never put `process.env`
10
+ * values here. This object is imported on the client.
11
+ */
12
+ readonly env?: ReadonlyArray<string>;
13
+ }
14
+ /**
15
+ * Input for {@link defineByokProvider}. `id` cannot be optional: if `TId`
16
+ * includes `undefined`, `id` becomes `never` and the object is unassignable.
17
+ */
18
+ export type ByokProviderInit<TId extends string> = {
19
+ readonly id: undefined extends TId ? never : TId;
20
+ readonly label: string;
21
+ readonly env?: string | ReadonlyArray<string>;
22
+ };
23
+ export declare function defineByokProvider<const TId extends string>(provider: ByokProviderInit<TId>): ByokProvider<TId>;
@@ -0,0 +1,19 @@
1
+ import { isProviderId } from "./providers.js";
2
+ //#region src/byok/define-provider.ts
3
+ function normalizeEnv(env) {
4
+ if (env === void 0) return void 0;
5
+ return typeof env === "string" ? [env] : env;
6
+ }
7
+ function defineByokProvider(provider) {
8
+ if (!isProviderId(provider.id)) throw new Error(`Invalid BYOK provider id: ${String(provider.id)}`);
9
+ const env = normalizeEnv(provider.env);
10
+ return {
11
+ id: provider.id,
12
+ label: provider.label,
13
+ ...env ? { env } : {}
14
+ };
15
+ }
16
+ //#endregion
17
+ export { defineByokProvider };
18
+
19
+ //# sourceMappingURL=define-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-provider.js","names":[],"sources":["../../../src/byok/define-provider.ts"],"sourcesContent":["import { isProviderId } from './providers'\n\n/**\n * A BYOK provider declared by an adapter. `id` is the `x-byok-<id>` slug and\n * is required — `{ id?: string }` is not a {@link ByokProvider}.\n */\nexport interface ByokProvider<TId extends string = string> {\n readonly id: TId\n readonly label: string\n /**\n * Env var names the relay may read. Names only — never put `process.env`\n * values here. This object is imported on the client.\n */\n readonly env?: ReadonlyArray<string>\n}\n\n/**\n * Input for {@link defineByokProvider}. `id` cannot be optional: if `TId`\n * includes `undefined`, `id` becomes `never` and the object is unassignable.\n */\nexport type ByokProviderInit<TId extends string> = {\n readonly id: undefined extends TId ? never : TId\n readonly label: string\n readonly env?: string | ReadonlyArray<string>\n}\n\nfunction normalizeEnv(\n env: string | ReadonlyArray<string> | undefined,\n): ReadonlyArray<string> | undefined {\n if (env === undefined) return undefined\n return typeof env === 'string' ? [env] : env\n}\n\nexport function defineByokProvider<const TId extends string>(\n provider: ByokProviderInit<TId>,\n): ByokProvider<TId> {\n if (!isProviderId(provider.id)) {\n throw new Error(`Invalid BYOK provider id: ${String(provider.id)}`)\n }\n const env = normalizeEnv(provider.env)\n return {\n id: provider.id,\n label: provider.label,\n ...(env ? { env } : {}),\n }\n}\n"],"mappings":";;AA0BA,SAAS,aACP,KACmC;CACnC,IAAI,QAAQ,KAAA,GAAW,OAAO,KAAA;CAC9B,OAAO,OAAO,QAAQ,WAAW,CAAC,GAAG,IAAI;AAC3C;AAEA,SAAgB,mBACd,UACmB;CACnB,IAAI,CAAC,aAAa,SAAS,EAAE,GAC3B,MAAM,IAAI,MAAM,6BAA6B,OAAO,SAAS,EAAE,GAAG;CAEpE,MAAM,MAAM,aAAa,SAAS,GAAG;CACrC,OAAO;EACL,IAAI,SAAS;EACb,OAAO,SAAS;EAChB,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC;CACvB;AACF"}
@@ -0,0 +1,13 @@
1
+ import { ProviderId } from './providers.js';
2
+ export declare class ByokMissingError extends Error {
3
+ readonly provider: ProviderId;
4
+ constructor(provider: ProviderId);
5
+ }
6
+ export declare class ByokBlockedError extends Error {
7
+ readonly provider: ProviderId;
8
+ readonly reason: 'missing' | 'locked';
9
+ constructor(provider: ProviderId, reason: 'missing' | 'locked');
10
+ }
11
+ export declare class ByokUnresolvedProviderError extends Error {
12
+ constructor();
13
+ }
@@ -0,0 +1,29 @@
1
+ //#region src/byok/errors.ts
2
+ var ByokMissingError = class extends Error {
3
+ provider;
4
+ constructor(provider) {
5
+ super(`Missing ${provider} API key`);
6
+ this.name = "ByokMissingError";
7
+ this.provider = provider;
8
+ }
9
+ };
10
+ var ByokBlockedError = class extends Error {
11
+ provider;
12
+ reason;
13
+ constructor(provider, reason) {
14
+ super(reason === "locked" ? `${provider} key is locked` : `Missing ${provider} API key`);
15
+ this.name = "ByokBlockedError";
16
+ this.provider = provider;
17
+ this.reason = reason;
18
+ }
19
+ };
20
+ var ByokUnresolvedProviderError = class extends Error {
21
+ constructor() {
22
+ super("BYOK is enabled but no provider slug was resolved. Pass byokProvider or forwardedProps.provider.");
23
+ this.name = "ByokUnresolvedProviderError";
24
+ }
25
+ };
26
+ //#endregion
27
+ export { ByokBlockedError, ByokMissingError, ByokUnresolvedProviderError };
28
+
29
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../../../src/byok/errors.ts"],"sourcesContent":["import type { ProviderId } from './providers'\n\nexport class ByokMissingError extends Error {\n readonly provider: ProviderId\n\n constructor(provider: ProviderId) {\n super(`Missing ${provider} API key`)\n this.name = 'ByokMissingError'\n this.provider = provider\n }\n}\n\nexport class ByokBlockedError extends Error {\n readonly provider: ProviderId\n readonly reason: 'missing' | 'locked'\n\n constructor(provider: ProviderId, reason: 'missing' | 'locked') {\n super(\n reason === 'locked'\n ? `${provider} key is locked`\n : `Missing ${provider} API key`,\n )\n this.name = 'ByokBlockedError'\n this.provider = provider\n this.reason = reason\n }\n}\n\nexport class ByokUnresolvedProviderError extends Error {\n constructor() {\n super(\n 'BYOK is enabled but no provider slug was resolved. Pass byokProvider or forwardedProps.provider.',\n )\n this.name = 'ByokUnresolvedProviderError'\n }\n}\n"],"mappings":";AAEA,IAAa,mBAAb,cAAsC,MAAM;CAC1C;CAEA,YAAY,UAAsB;EAChC,MAAM,WAAW,SAAS,SAAS;EACnC,KAAK,OAAO;EACZ,KAAK,WAAW;CAClB;AACF;AAEA,IAAa,mBAAb,cAAsC,MAAM;CAC1C;CACA;CAEA,YAAY,UAAsB,QAA8B;EAC9D,MACE,WAAW,WACP,GAAG,SAAS,kBACZ,WAAW,SAAS,SAC1B;EACA,KAAK,OAAO;EACZ,KAAK,WAAW;EAChB,KAAK,SAAS;CAChB;AACF;AAEA,IAAa,8BAAb,cAAiD,MAAM;CACrD,cAAc;EACZ,MACE,kGACF;EACA,KAAK,OAAO;CACd;AACF"}
@@ -0,0 +1,10 @@
1
+ import { ByokProvider } from './define-provider.js';
2
+ import { ProviderId } from './providers.js';
3
+ /**
4
+ * Read a key on the relay. Import from `@tanstack/ai/byok/server` so this
5
+ * `process.env` access is not in the client graph.
6
+ *
7
+ * The header wins. A {@link ByokProvider} then tries `provider.env` in order.
8
+ * A slug is header-only.
9
+ */
10
+ export declare function getByokKey(request: Request, provider: ProviderId | ByokProvider): string | null;
@@ -0,0 +1,26 @@
1
+ import { byokHeaderName, resolveProviderId } from "./providers.js";
2
+ //#region src/byok/get-key.ts
3
+ /**
4
+ * Read a key on the relay. Import from `@tanstack/ai/byok/server` so this
5
+ * `process.env` access is not in the client graph.
6
+ *
7
+ * The header wins. A {@link ByokProvider} then tries `provider.env` in order.
8
+ * A slug is header-only.
9
+ */
10
+ function getByokKey(request, provider) {
11
+ const value = request.headers.get(byokHeaderName(resolveProviderId(provider)));
12
+ if (typeof value === "string") {
13
+ const trimmed = value.trim();
14
+ if (trimmed.length > 0) return trimmed;
15
+ }
16
+ if (typeof provider === "string") return null;
17
+ for (const name of provider.env ?? []) {
18
+ const envValue = process.env[name];
19
+ if (typeof envValue === "string" && envValue.length > 0) return envValue;
20
+ }
21
+ return null;
22
+ }
23
+ //#endregion
24
+ export { getByokKey };
25
+
26
+ //# sourceMappingURL=get-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-key.js","names":[],"sources":["../../../src/byok/get-key.ts"],"sourcesContent":["import { byokHeaderName, resolveProviderId } from './providers'\nimport type { ByokProvider } from './define-provider'\nimport type { ProviderId } from './providers'\n\n/**\n * Read a key on the relay. Import from `@tanstack/ai/byok/server` so this\n * `process.env` access is not in the client graph.\n *\n * The header wins. A {@link ByokProvider} then tries `provider.env` in order.\n * A slug is header-only.\n */\nexport function getByokKey(\n request: Request,\n provider: ProviderId | ByokProvider,\n): string | null {\n const value = request.headers.get(byokHeaderName(resolveProviderId(provider)))\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed.length > 0) return trimmed\n }\n if (typeof provider === 'string') return null\n for (const name of provider.env ?? []) {\n const envValue = process.env[name]\n if (typeof envValue === 'string' && envValue.length > 0) return envValue\n }\n return null\n}\n"],"mappings":";;;;;;;;;AAWA,SAAgB,WACd,SACA,UACe;CACf,MAAM,QAAQ,QAAQ,QAAQ,IAAI,eAAe,kBAAkB,QAAQ,CAAC,CAAC;CAC7E,IAAI,OAAO,UAAU,UAAU;EAC7B,MAAM,UAAU,MAAM,KAAK;EAC3B,IAAI,QAAQ,SAAS,GAAG,OAAO;CACjC;CACA,IAAI,OAAO,aAAa,UAAU,OAAO;CACzC,KAAK,MAAM,QAAQ,SAAS,OAAO,CAAC,GAAG;EACrC,MAAM,WAAW,QAAQ,IAAI;EAC7B,IAAI,OAAO,aAAa,YAAY,SAAS,SAAS,GAAG,OAAO;CAClE;CACA,OAAO;AACT"}
@@ -0,0 +1,11 @@
1
+ import { ByokProvider } from './define-provider.js';
2
+ import { ProviderId } from './providers.js';
3
+ export interface ByokMissingBody {
4
+ error: {
5
+ type: 'byok_missing';
6
+ provider: ProviderId;
7
+ message: string;
8
+ };
9
+ }
10
+ export declare function isByokMissingBody(value: unknown): value is ByokMissingBody;
11
+ export declare function byokMissing(provider: ProviderId | ByokProvider): Response;
@@ -0,0 +1,30 @@
1
+ import { isProviderId, resolveProviderId } from "./providers.js";
2
+ //#region src/byok/missing.ts
3
+ function isByokMissingBody(value) {
4
+ if (typeof value !== "object" || value === null) return false;
5
+ if (!("error" in value)) return false;
6
+ const error = value.error;
7
+ if (typeof error !== "object" || error === null) return false;
8
+ if (!("type" in error) || error.type !== "byok_missing") return false;
9
+ if (!("provider" in error) || typeof error.provider !== "string") return false;
10
+ if (!isProviderId(error.provider)) return false;
11
+ if (!("message" in error) || typeof error.message !== "string") return false;
12
+ return true;
13
+ }
14
+ function byokMissing(provider) {
15
+ const id = resolveProviderId(provider);
16
+ if (!isProviderId(id)) throw new Error(`Invalid BYOK provider id: ${id}`);
17
+ const body = { error: {
18
+ type: "byok_missing",
19
+ provider: id,
20
+ message: `Missing ${id} API key`
21
+ } };
22
+ return new Response(JSON.stringify(body), {
23
+ status: 401,
24
+ headers: { "content-type": "application/json" }
25
+ });
26
+ }
27
+ //#endregion
28
+ export { byokMissing, isByokMissingBody };
29
+
30
+ //# sourceMappingURL=missing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing.js","names":[],"sources":["../../../src/byok/missing.ts"],"sourcesContent":["import { isProviderId, resolveProviderId } from './providers'\nimport type { ByokProvider } from './define-provider'\nimport type { ProviderId } from './providers'\n\nexport interface ByokMissingBody {\n error: {\n type: 'byok_missing'\n provider: ProviderId\n message: string\n }\n}\n\nexport function isByokMissingBody(value: unknown): value is ByokMissingBody {\n if (typeof value !== 'object' || value === null) return false\n if (!('error' in value)) return false\n const error = value.error\n if (typeof error !== 'object' || error === null) return false\n if (!('type' in error) || error.type !== 'byok_missing') return false\n if (!('provider' in error) || typeof error.provider !== 'string') {\n return false\n }\n if (!isProviderId(error.provider)) return false\n if (!('message' in error) || typeof error.message !== 'string') return false\n return true\n}\n\nexport function byokMissing(provider: ProviderId | ByokProvider): Response {\n const id = resolveProviderId(provider)\n if (!isProviderId(id)) {\n throw new Error(`Invalid BYOK provider id: ${id}`)\n }\n const body: ByokMissingBody = {\n error: {\n type: 'byok_missing',\n provider: id,\n message: `Missing ${id} API key`,\n },\n }\n return new Response(JSON.stringify(body), {\n status: 401,\n headers: { 'content-type': 'application/json' },\n })\n}\n"],"mappings":";;AAYA,SAAgB,kBAAkB,OAA0C;CAC1E,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CACxD,IAAI,EAAE,WAAW,QAAQ,OAAO;CAChC,MAAM,QAAQ,MAAM;CACpB,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CACxD,IAAI,EAAE,UAAU,UAAU,MAAM,SAAS,gBAAgB,OAAO;CAChE,IAAI,EAAE,cAAc,UAAU,OAAO,MAAM,aAAa,UACtD,OAAO;CAET,IAAI,CAAC,aAAa,MAAM,QAAQ,GAAG,OAAO;CAC1C,IAAI,EAAE,aAAa,UAAU,OAAO,MAAM,YAAY,UAAU,OAAO;CACvE,OAAO;AACT;AAEA,SAAgB,YAAY,UAA+C;CACzE,MAAM,KAAK,kBAAkB,QAAQ;CACrC,IAAI,CAAC,aAAa,EAAE,GAClB,MAAM,IAAI,MAAM,6BAA6B,IAAI;CAEnD,MAAM,OAAwB,EAC5B,OAAO;EACL,MAAM;EACN,UAAU;EACV,SAAS,WAAW,GAAG;CACzB,EACF;CACA,OAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;EACxC,QAAQ;EACR,SAAS,EAAE,gBAAgB,mBAAmB;CAChD,CAAC;AACH"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Provider ids are open slugs, not a closed catalog. `@tanstack/ai` does not
3
+ * list adapters. Any matching string is a valid id and becomes `x-byok-<id>`.
4
+ */
5
+ export type ProviderId = string;
6
+ /** `[a-z][a-z0-9-]{0,63}` — lowercase, no underscores, max 64 chars. */
7
+ export declare const BYOK_PROVIDER_ID_PATTERN: RegExp;
8
+ export declare const BYOK_HEADER_PREFIX = "x-byok-";
9
+ export declare function isProviderId(value: unknown): value is ProviderId;
10
+ export declare function resolveProviderId(provider: string | {
11
+ readonly id: string;
12
+ }): string;
13
+ export declare function byokHeaderName(provider: string): string;
@@ -0,0 +1,18 @@
1
+ //#region src/byok/providers.ts
2
+ /** `[a-z][a-z0-9-]{0,63}` — lowercase, no underscores, max 64 chars. */
3
+ var BYOK_PROVIDER_ID_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
4
+ var BYOK_HEADER_PREFIX = "x-byok-";
5
+ function isProviderId(value) {
6
+ return typeof value === "string" && BYOK_PROVIDER_ID_PATTERN.test(value);
7
+ }
8
+ function resolveProviderId(provider) {
9
+ return typeof provider === "string" ? provider : provider.id;
10
+ }
11
+ function byokHeaderName(provider) {
12
+ if (!isProviderId(provider)) throw new Error(`Invalid BYOK provider id: ${provider}`);
13
+ return `${BYOK_HEADER_PREFIX}${provider}`;
14
+ }
15
+ //#endregion
16
+ export { BYOK_HEADER_PREFIX, BYOK_PROVIDER_ID_PATTERN, byokHeaderName, isProviderId, resolveProviderId };
17
+
18
+ //# sourceMappingURL=providers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.js","names":[],"sources":["../../../src/byok/providers.ts"],"sourcesContent":["/**\n * Provider ids are open slugs, not a closed catalog. `@tanstack/ai` does not\n * list adapters. Any matching string is a valid id and becomes `x-byok-<id>`.\n */\nexport type ProviderId = string\n\n/** `[a-z][a-z0-9-]{0,63}` — lowercase, no underscores, max 64 chars. */\nexport const BYOK_PROVIDER_ID_PATTERN = /^[a-z][a-z0-9-]{0,63}$/\n\nexport const BYOK_HEADER_PREFIX = 'x-byok-'\n\nexport function isProviderId(value: unknown): value is ProviderId {\n return typeof value === 'string' && BYOK_PROVIDER_ID_PATTERN.test(value)\n}\n\nexport function resolveProviderId(\n provider: string | { readonly id: string },\n): string {\n return typeof provider === 'string' ? provider : provider.id\n}\n\nexport function byokHeaderName(provider: string): string {\n if (!isProviderId(provider)) {\n throw new Error(`Invalid BYOK provider id: ${provider}`)\n }\n return `${BYOK_HEADER_PREFIX}${provider}`\n}\n"],"mappings":";;AAOA,IAAa,2BAA2B;AAExC,IAAa,qBAAqB;AAElC,SAAgB,aAAa,OAAqC;CAChE,OAAO,OAAO,UAAU,YAAY,yBAAyB,KAAK,KAAK;AACzE;AAEA,SAAgB,kBACd,UACQ;CACR,OAAO,OAAO,aAAa,WAAW,WAAW,SAAS;AAC5D;AAEA,SAAgB,eAAe,UAA0B;CACvD,IAAI,CAAC,aAAa,QAAQ,GACxB,MAAM,IAAI,MAAM,6BAA6B,UAAU;CAEzD,OAAO,GAAG,qBAAqB;AACjC"}
@@ -0,0 +1,2 @@
1
+ export declare function maskKey(key: string): string;
2
+ export declare function scrubSecrets(input: string, secrets: ReadonlyArray<string>): string;
@@ -0,0 +1,17 @@
1
+ //#region src/byok/scrub.ts
2
+ function maskKey(key) {
3
+ if (key.length <= 4) return "••";
4
+ return key.slice(-4);
5
+ }
6
+ function scrubSecrets(input, secrets) {
7
+ let next = input;
8
+ for (const secret of secrets) {
9
+ if (secret.length === 0) continue;
10
+ next = next.split(secret).join("[redacted]");
11
+ }
12
+ return next;
13
+ }
14
+ //#endregion
15
+ export { maskKey, scrubSecrets };
16
+
17
+ //# sourceMappingURL=scrub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrub.js","names":[],"sources":["../../../src/byok/scrub.ts"],"sourcesContent":["export function maskKey(key: string): string {\n if (key.length <= 4) return '••'\n return key.slice(-4)\n}\n\nexport function scrubSecrets(\n input: string,\n secrets: ReadonlyArray<string>,\n): string {\n let next = input\n for (const secret of secrets) {\n if (secret.length === 0) continue\n next = next.split(secret).join('[redacted]')\n }\n return next\n}\n"],"mappings":";AAAA,SAAgB,QAAQ,KAAqB;CAC3C,IAAI,IAAI,UAAU,GAAG,OAAO;CAC5B,OAAO,IAAI,MAAM,EAAE;AACrB;AAEA,SAAgB,aACd,OACA,SACQ;CACR,IAAI,OAAO;CACX,KAAK,MAAM,UAAU,SAAS;EAC5B,IAAI,OAAO,WAAW,GAAG;EACzB,OAAO,KAAK,MAAM,MAAM,CAAC,CAAC,KAAK,YAAY;CAC7C;CACA,OAAO;AACT"}
@@ -0,0 +1,3 @@
1
+ export { getByokKey } from './get-key.js';
2
+ export { byokMissing, isByokMissingBody } from './missing.js';
3
+ export type { ByokMissingBody } from './missing.js';
@@ -0,0 +1,3 @@
1
+ import { byokMissing, isByokMissingBody } from "./missing.js";
2
+ import { getByokKey } from "./get-key.js";
3
+ export { byokMissing, getByokKey, isByokMissingBody };
@@ -0,0 +1,8 @@
1
+ export { BYOK_PROVIDER_ID_PATTERN, BYOK_HEADER_PREFIX, byokHeaderName, isProviderId, } from './byok/providers.js';
2
+ export type { ProviderId } from './byok/providers.js';
3
+ export { defineByokProvider } from './byok/define-provider.js';
4
+ export type { ByokProvider, ByokProviderInit } from './byok/define-provider.js';
5
+ export { isByokMissingBody, byokMissing } from './byok/missing.js';
6
+ export type { ByokMissingBody } from './byok/missing.js';
7
+ export { ByokMissingError, ByokBlockedError, ByokUnresolvedProviderError, } from './byok/errors.js';
8
+ export { maskKey, scrubSecrets } from './byok/scrub.js';
@@ -0,0 +1,6 @@
1
+ import { BYOK_HEADER_PREFIX, BYOK_PROVIDER_ID_PATTERN, byokHeaderName, isProviderId } from "./byok/providers.js";
2
+ import { defineByokProvider } from "./byok/define-provider.js";
3
+ import { byokMissing, isByokMissingBody } from "./byok/missing.js";
4
+ import { ByokBlockedError, ByokMissingError, ByokUnresolvedProviderError } from "./byok/errors.js";
5
+ import { maskKey, scrubSecrets } from "./byok/scrub.js";
6
+ export { BYOK_HEADER_PREFIX, BYOK_PROVIDER_ID_PATTERN, ByokBlockedError, ByokMissingError, ByokUnresolvedProviderError, byokHeaderName, byokMissing, defineByokProvider, isByokMissingBody, isProviderId, maskKey, scrubSecrets };
@@ -173,6 +173,10 @@ async function validateInterruptResumeBatch(input) {
173
173
  });
174
174
  continue;
175
175
  }
176
+ if (entry.status === "cancelled") {
177
+ if (entry.payload !== void 0) errors.push(interruptItemError(input, record.interruptId, "invalid-payload", `Cancelled interrupt ${record.interruptId} must not include a payload.`));
178
+ continue;
179
+ }
176
180
  const tool = runtimeTool(input.tools, binding.toolName);
177
181
  if (!tool) {
178
182
  errors.push(interruptItemError(input, record.interruptId, "stale", `Tool ${binding.toolName} is unavailable for interrupt ${record.interruptId}.`, { source: "server" }));
@@ -197,10 +201,6 @@ async function validateInterruptResumeBatch(input) {
197
201
  schemaDrifted = true;
198
202
  }
199
203
  }
200
- if (entry.status === "cancelled") {
201
- if (entry.payload !== void 0) errors.push(interruptItemError(input, record.interruptId, "invalid-payload", `Cancelled interrupt ${record.interruptId} must not include a payload.`));
202
- continue;
203
- }
204
204
  if (schemaDrifted) continue;
205
205
  if (binding.kind === "client-tool-execution") {
206
206
  if (responseSchema !== void 0) await pushSchemaIssues({
@@ -1 +1 @@
1
- {"version":3,"file":"interrupt-resume.js","names":[],"sources":["../../src/interrupt-resume.ts"],"sourcesContent":["import {\n INTERRUPT_BINDING_VERSION,\n canonicalizeInterruptResolutions,\n} from './interrupts'\nimport {\n canonicalInterruptJson,\n digestInterruptJson,\n} from './interrupt-serialization'\nimport {\n hashSchemaInput,\n normalizeApprovalSchema,\n} from './activities/chat/tools/approval-schema'\nimport {\n isStandardSchema,\n validateWithStandardSchema,\n} from './activities/chat/tools/schema-converter'\nimport type {\n InterruptBinding,\n InterruptSubmissionError,\n ItemInterruptErrorCode,\n ToolApprovalResolution,\n UnopenedInterruptBinding,\n} from './interrupts'\nimport type {\n ChatMiddlewareConfig,\n ChatResumeToolState,\n} from './activities/chat/middleware/types'\nimport type {\n GenericInterruptRequest,\n InterruptDefinition,\n} from './interrupt-definition'\nimport type { Interrupt, RunAgentResumeItem } from './types'\n\n/**\n * The `Interrupt.metadata` key under which this package's resume binding\n * travels.\n *\n * Exported so anything that produces an interrupt this package must later\n * resume — an application middleware raising a generic pause, a future\n * workflow-to-AG-UI projection — attaches the binding through\n * {@link withInterruptBinding} rather than copying the string. Everything\n * outside this key is the plain AG-UI envelope and is left untouched.\n */\nexport const INTERRUPT_BINDING_METADATA_KEY = 'tanstack:interruptBinding'\n\nconst interruptBindingMetadataKey = INTERRUPT_BINDING_METADATA_KEY\n\n/** The persistence-neutral shape required to validate an interrupt resume. */\nexport interface PendingInterruptResumeRecord {\n interruptId: string\n payload: unknown\n binding: InterruptBinding\n /** Present for a first-party generic interrupt. */\n genericRequest?: GenericInterruptRequest<\n InterruptDefinition<any, any, any, any>\n >\n}\n\nexport interface ValidateInterruptResumeBatchInput {\n threadId: string\n interruptedRunId: string\n generation: number\n pending: ReadonlyArray<PendingInterruptResumeRecord>\n resume?: ReadonlyArray<RunAgentResumeItem>\n tools: ChatMiddlewareConfig['tools']\n now?: number\n}\n\nexport interface ValidatedInterruptResumeBatch {\n errors: ReadonlyArray<InterruptSubmissionError>\n resolutions?: ReadonlyArray<RunAgentResumeItem>\n canonicalResolutions?: string\n fingerprint?: string\n resumeToolState?: ChatResumeToolState\n}\n\nexport class InterruptResumeValidationError extends Error {\n override readonly name = 'InterruptResumeValidationError'\n\n constructor(readonly errors: ReadonlyArray<InterruptSubmissionError>) {\n super(errors.map((error) => error.message).join(' '))\n }\n}\n\nfunction objectValue(value: unknown): Record<string, unknown> | null {\n return value && typeof value === 'object'\n ? (value as Record<string, unknown>)\n : null\n}\n\nfunction stringField(\n value: Record<string, unknown>,\n key: string,\n): string | undefined {\n return typeof value[key] === 'string' ? value[key] : undefined\n}\n\nfunction normalizeIssuePath(\n path: ReadonlyArray<unknown> | undefined,\n): ReadonlyArray<string | number> | undefined {\n if (!path) return undefined\n return path.map((segment) => {\n if (typeof segment === 'string' || typeof segment === 'number') {\n return segment\n }\n const record = objectValue(segment)\n const key = record?.key\n return typeof key === 'number' ? key : String(key ?? segment)\n })\n}\n\nexport function interruptItemError(\n input: Pick<\n ValidateInterruptResumeBatchInput,\n 'threadId' | 'interruptedRunId' | 'generation'\n >,\n interruptId: string,\n code: ItemInterruptErrorCode,\n message: string,\n options?: {\n path?: ReadonlyArray<string | number>\n source?: 'client' | 'server'\n retryable?: boolean\n },\n): InterruptSubmissionError {\n return {\n scope: 'item',\n threadId: input.threadId,\n interruptedRunId: input.interruptedRunId,\n generation: input.generation,\n interruptId,\n code,\n message,\n source: options?.source ?? 'client',\n retryable: options?.retryable ?? false,\n ...(options?.path ? { path: options.path } : {}),\n }\n}\n\nasync function validateSchemaValue(input: {\n schema: unknown\n value: unknown\n onIssue: (message: string, path?: ReadonlyArray<string | number>) => void\n}): Promise<void> {\n if (isStandardSchema(input.schema)) {\n const result = await validateWithStandardSchema<unknown>(\n input.schema,\n input.value,\n )\n if (!result.success) {\n for (const issue of result.issues) {\n input.onIssue(issue.message, normalizeIssuePath(issue.path))\n }\n }\n return\n }\n\n // A non-Standard-Schema value (a raw JSON Schema, e.g. a generic interrupt's\n // wire responseSchema) is not validated by the library. The application\n // validates the resume value itself if it needs to; otherwise it flows\n // through as-is.\n}\n\ntype RuntimeTool = ChatMiddlewareConfig['tools'][number] & {\n approvalSchema?: Parameters<typeof normalizeApprovalSchema>[0]\n}\n\nfunction runtimeTool(\n tools: ChatMiddlewareConfig['tools'],\n name: string,\n): RuntimeTool | undefined {\n return tools.find((tool) => tool.name === name) as RuntimeTool | undefined\n}\n\nasync function parseSchemaValue(\n schema: unknown,\n value: unknown,\n): Promise<{ success: true; data: unknown } | { success: false }> {\n if (!isStandardSchema(schema)) return { success: true, data: value }\n const result = await validateWithStandardSchema<unknown>(schema, value)\n return result.success\n ? { success: true, data: result.data }\n : { success: false }\n}\n\nfunction descriptorResponseSchema(\n record: PendingInterruptResumeRecord,\n): unknown {\n return objectValue(record.payload)?.responseSchema\n}\n\nfunction schemaHash(schema: unknown): string {\n return digestInterruptJson(canonicalInterruptJson(schema))\n}\n\nasync function pushSchemaIssues(input: {\n request: ValidateInterruptResumeBatchInput\n errors: Array<InterruptSubmissionError>\n interruptId: string\n schema: unknown\n value: unknown\n code: ItemInterruptErrorCode\n label: string\n}): Promise<void> {\n try {\n await validateSchemaValue({\n schema: input.schema,\n value: input.value,\n onIssue: (message, path) => {\n input.errors.push(\n interruptItemError(\n input.request,\n input.interruptId,\n input.code,\n `${input.label}: ${message}`,\n { path },\n ),\n )\n },\n })\n } catch (error) {\n input.errors.push(\n interruptItemError(\n input.request,\n input.interruptId,\n 'invalid-response-schema',\n `${input.label} could not be validated: ${error instanceof Error ? error.message : String(error)}`,\n { source: 'server' },\n ),\n )\n }\n}\n\nfunction validateDescriptorSchema(\n input: ValidateInterruptResumeBatchInput,\n record: PendingInterruptResumeRecord,\n binding: InterruptBinding,\n errors: Array<InterruptSubmissionError>,\n): unknown {\n const schema = descriptorResponseSchema(record)\n const responseSchemaHash = binding.responseSchemaHash\n if (schema === undefined && responseSchemaHash === undefined) {\n return undefined\n }\n if (\n schema === undefined ||\n responseSchemaHash === undefined ||\n schemaHash(schema) !== responseSchemaHash\n ) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-response-schema',\n `Interrupt ${record.interruptId} response schema no longer matches its binding.`,\n { source: 'server' },\n ),\n )\n }\n return schema\n}\n\n/**\n * Validate and translate a complete interrupt batch before any tool executes.\n * Used by ephemeral chat resume; a durable layer may share the same validator.\n */\nexport async function validateInterruptResumeBatch(\n input: ValidateInterruptResumeBatchInput,\n): Promise<ValidatedInterruptResumeBatch> {\n const grouped = new Map<string, Array<InterruptSubmissionError>>()\n const batchErrors: Array<InterruptSubmissionError> = []\n const group = (interruptId: string): Array<InterruptSubmissionError> => {\n const existing = grouped.get(interruptId)\n if (existing) return existing\n const created: Array<InterruptSubmissionError> = []\n grouped.set(interruptId, created)\n return created\n }\n const pendingById = new Map(\n input.pending.map((record) => [record.interruptId, record]),\n )\n const resumeById = new Map<string, RunAgentResumeItem>()\n const counts = new Map<string, number>()\n for (const entry of input.resume ?? []) {\n counts.set(entry.interruptId, (counts.get(entry.interruptId) ?? 0) + 1)\n if (!resumeById.has(entry.interruptId))\n resumeById.set(entry.interruptId, entry)\n }\n\n for (const [interruptId, count] of counts) {\n if (count > 1) {\n group(interruptId).push(\n interruptItemError(\n input,\n interruptId,\n 'conflict',\n `Interrupt ${interruptId} has duplicate resume entries.`,\n ),\n )\n }\n }\n\n const pendingGenerics = input.pending.filter(\n (record) => record.binding.kind === 'generic',\n )\n const genericBatchSatisfied =\n pendingGenerics.length > 0 &&\n pendingGenerics.every((record) => resumeById.has(record.interruptId))\n\n let incomplete = false\n for (const record of input.pending) {\n const errors = group(record.interruptId)\n const entry = resumeById.get(record.interruptId)\n const binding = record.binding\n if (!entry) {\n // Client tools that share a generic interrupt batch wait for\n // `toolResume`. `continue` re-emits them; `cancel` / `stop` skip them.\n if (genericBatchSatisfied && binding.kind === 'client-tool-execution') {\n continue\n }\n incomplete = true\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'unknown-interrupt',\n `Missing resume entry for interrupt ${record.interruptId}.`,\n ),\n )\n }\n if (\n binding.interruptedRunId !== input.interruptedRunId ||\n binding.generation !== input.generation ||\n binding.interruptId !== record.interruptId\n ) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'stale',\n `Interrupt ${record.interruptId} has stale correlation metadata.`,\n { source: 'server' },\n ),\n )\n }\n if (binding.expiresAt !== undefined) {\n const expiresAt = Date.parse(binding.expiresAt)\n if (!Number.isFinite(expiresAt)) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-payload',\n `Interrupt ${record.interruptId} has an invalid expiresAt.`,\n { source: 'server' },\n ),\n )\n } else if (expiresAt <= (input.now ?? Date.now())) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'expired',\n `Interrupt ${record.interruptId} has expired.`,\n { source: 'server' },\n ),\n )\n }\n }\n\n const responseSchema = validateDescriptorSchema(\n input,\n record,\n binding,\n errors,\n )\n if (!entry) continue\n const entryStatus: unknown = entry.status\n if (entryStatus !== 'resolved' && entryStatus !== 'cancelled') {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-payload',\n `Interrupt ${record.interruptId} has invalid status ${String(entryStatus)}.`,\n ),\n )\n continue\n }\n if (binding.kind === 'generic') {\n const genericRequest = record.genericRequest\n if (genericRequest !== undefined) {\n const batchIndex = binding.batchIndex\n if (\n binding.definitionId !== genericRequest.definition.id ||\n binding.key !== genericRequest.key ||\n binding.interruptId !== record.interruptId ||\n batchIndex === undefined ||\n !Number.isInteger(batchIndex) ||\n batchIndex < 0\n ) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'stale',\n `Generic interrupt ${record.interruptId} has stale definition metadata.`,\n { source: 'server' },\n ),\n )\n }\n }\n if (entry.status === 'cancelled') {\n if (entry.payload !== undefined) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-payload',\n `Cancelled interrupt ${record.interruptId} must not include a payload.`,\n ),\n )\n }\n } else if (genericRequest !== undefined) {\n await pushSchemaIssues({\n request: input,\n errors,\n interruptId: record.interruptId,\n schema: genericRequest.definition.responseSchema,\n value: entry.payload,\n code: 'invalid-payload',\n label: `Interrupt ${record.interruptId} payload is invalid`,\n })\n } else if (responseSchema !== undefined) {\n await pushSchemaIssues({\n request: input,\n errors,\n interruptId: record.interruptId,\n schema: responseSchema,\n value: entry.payload,\n code: 'invalid-payload',\n label: `Interrupt ${record.interruptId} payload is invalid`,\n })\n }\n continue\n }\n\n const tool = runtimeTool(input.tools, binding.toolName)\n if (!tool) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'stale',\n `Tool ${binding.toolName} is unavailable for interrupt ${record.interruptId}.`,\n { source: 'server' },\n ),\n )\n continue\n }\n\n let approval: ReturnType<typeof normalizeApprovalSchema> | undefined\n let schemaDrifted = false\n if (binding.kind === 'client-tool-execution') {\n if (hashSchemaInput(tool.outputSchema) !== binding.outputSchemaHash) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'stale',\n `Tool ${binding.toolName} output schema has changed.`,\n { source: 'server' },\n ),\n )\n schemaDrifted = true\n }\n } else {\n try {\n approval = normalizeApprovalSchema(\n tool.approvalSchema,\n tool.inputSchema,\n )\n } catch {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'stale',\n `Tool ${binding.toolName} approval schema is unavailable.`,\n { source: 'server' },\n ),\n )\n schemaDrifted = true\n }\n if (\n approval !== undefined &&\n (hashSchemaInput(tool.inputSchema) !== binding.inputSchemaHash ||\n approval.approvalSchemaHash !== binding.approvalSchemaHash ||\n approval.responseSchemaHash !== binding.responseSchemaHash)\n ) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'stale',\n `Tool ${binding.toolName} approval schema has changed.`,\n { source: 'server' },\n ),\n )\n schemaDrifted = true\n }\n }\n\n if (entry.status === 'cancelled') {\n if (entry.payload !== undefined) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-payload',\n `Cancelled interrupt ${record.interruptId} must not include a payload.`,\n ),\n )\n }\n continue\n }\n if (schemaDrifted) continue\n\n if (binding.kind === 'client-tool-execution') {\n if (responseSchema !== undefined) {\n await pushSchemaIssues({\n request: input,\n errors,\n interruptId: record.interruptId,\n schema: responseSchema,\n value: entry.payload,\n code: 'invalid-tool-output',\n label: `Tool ${binding.toolName} output is invalid`,\n })\n }\n if (tool.outputSchema !== undefined) {\n await pushSchemaIssues({\n request: input,\n errors,\n interruptId: record.interruptId,\n schema: tool.outputSchema,\n value: entry.payload,\n code: 'invalid-tool-output',\n label: `Tool ${binding.toolName} output is invalid`,\n })\n }\n continue\n }\n\n if (approval === undefined) continue\n const envelope = objectValue(entry.payload)\n const approved =\n typeof entry.payload === 'boolean'\n ? entry.payload\n : typeof envelope?.approved === 'boolean'\n ? envelope.approved\n : undefined\n if (approved === undefined) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-payload',\n `Approval ${record.interruptId} must be a boolean or decision envelope.`,\n ),\n )\n continue\n }\n if (envelope) {\n await pushSchemaIssues({\n request: input,\n errors,\n interruptId: record.interruptId,\n schema: approval.responseSchema,\n value: entry.payload,\n code: 'invalid-payload',\n label: `Approval ${record.interruptId} envelope is invalid`,\n })\n }\n if (approved && envelope?.editedArgs !== undefined) {\n if (tool.inputSchema === undefined) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-edited-args',\n `Approval ${record.interruptId} cannot edit arguments without an input schema.`,\n ),\n )\n } else {\n await pushSchemaIssues({\n request: input,\n errors,\n interruptId: record.interruptId,\n schema: tool.inputSchema,\n value: envelope.editedArgs,\n code: 'invalid-edited-args',\n label: `Approval ${record.interruptId} edited arguments are invalid`,\n })\n }\n }\n const branch = approved\n ? approval.branches.approve\n : approval.branches.reject\n if (branch) {\n if (!envelope) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-payload',\n `Approval ${record.interruptId} requires a payload for the ${approved ? 'approve' : 'reject'} decision.`,\n ),\n )\n } else {\n await pushSchemaIssues({\n request: input,\n errors,\n interruptId: record.interruptId,\n schema: branch.source,\n value: envelope.payload,\n code: 'invalid-payload',\n label: `Approval ${record.interruptId} payload is invalid`,\n })\n }\n }\n }\n\n for (const entry of input.resume ?? []) {\n if (!pendingById.has(entry.interruptId)) {\n incomplete = true\n group(entry.interruptId).push(\n interruptItemError(\n input,\n entry.interruptId,\n 'unknown-interrupt',\n `Resume entry references unknown interrupt ${entry.interruptId}.`,\n ),\n )\n }\n }\n\n if (incomplete) {\n batchErrors.push({\n scope: 'batch',\n threadId: input.threadId,\n interruptedRunId: input.interruptedRunId,\n generation: input.generation,\n code: 'incomplete-batch',\n message:\n 'Resume entries must resolve or cancel the complete interrupt batch.',\n source: 'client',\n retryable: false,\n interruptIds: input.pending.map((record) => record.interruptId),\n })\n }\n\n const itemErrors = [...grouped.entries()]\n .sort(([left], [right]) => left.localeCompare(right))\n .flatMap(([, errors]) => errors)\n if (itemErrors.length > 0) {\n batchErrors.push({\n scope: 'batch',\n threadId: input.threadId,\n interruptedRunId: input.interruptedRunId,\n generation: input.generation,\n code: 'item-validation-failed',\n message: 'One or more interrupt resolutions are invalid.',\n source: 'client',\n retryable: false,\n interruptIds: input.pending.map((record) => record.interruptId),\n })\n return { errors: [...itemErrors, ...batchErrors] }\n }\n\n const canonical = canonicalizeInterruptResolutions(input.resume ?? [])\n const approvals = new Map<string, ToolApprovalResolution>()\n const clientToolResults = new Map<string, unknown>()\n const genericInterrupts = new Map<\n string,\n | { interruptId: string; status: 'resolved'; payload: unknown }\n | { interruptId: string; status: 'cancelled' }\n >()\n const deniedToolResults = new Map<string, unknown>()\n const cancelledToolCallIds = new Set<string>()\n\n for (const record of input.pending) {\n const entry = resumeById.get(record.interruptId)\n if (!entry) continue\n const binding = record.binding\n if (binding.kind === 'generic') {\n if (entry.status !== 'resolved') {\n genericInterrupts.set(record.interruptId, {\n interruptId: record.interruptId,\n status: 'cancelled',\n })\n continue\n }\n if (record.genericRequest === undefined) {\n genericInterrupts.set(record.interruptId, {\n interruptId: record.interruptId,\n status: 'resolved',\n payload: entry.payload,\n })\n continue\n }\n const parsed = await parseSchemaValue(\n record.genericRequest.definition.responseSchema,\n entry.payload,\n )\n if (!parsed.success) {\n return {\n errors: [\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-payload',\n `Interrupt ${record.interruptId} payload is invalid.`,\n ),\n ],\n }\n }\n genericInterrupts.set(record.interruptId, {\n interruptId: record.interruptId,\n status: 'resolved',\n payload: parsed.data,\n })\n continue\n }\n if (entry.status === 'cancelled') {\n cancelledToolCallIds.add(binding.toolCallId)\n continue\n }\n if (binding.kind === 'client-tool-execution') {\n clientToolResults.set(binding.toolCallId, entry.payload)\n continue\n }\n const envelope = objectValue(entry.payload)\n const resolution: ToolApprovalResolution =\n typeof entry.payload === 'boolean'\n ? entry.payload\n : envelope?.approved === true\n ? {\n approved: true,\n ...(envelope.editedArgs !== undefined\n ? { editedArgs: envelope.editedArgs }\n : {}),\n ...(envelope.payload !== undefined\n ? { payload: envelope.payload }\n : {}),\n }\n : {\n approved: false,\n ...(envelope?.payload !== undefined\n ? { payload: envelope.payload }\n : {}),\n }\n approvals.set(binding.toolCallId, resolution)\n if (\n resolution === false ||\n (typeof resolution === 'object' && !resolution.approved)\n ) {\n deniedToolResults.set(\n binding.toolCallId,\n typeof resolution === 'object' ? resolution.payload : undefined,\n )\n }\n }\n\n return {\n errors: [],\n resolutions: canonical.resolutions,\n canonicalResolutions: canonical.canonicalResolutions,\n fingerprint: canonical.fingerprint,\n resumeToolState: {\n approvals,\n clientToolResults,\n genericInterrupts,\n deniedToolResults,\n cancelledToolCallIds,\n },\n }\n}\n\n/**\n * Is this a binding written by a version of the protocol we understand?\n *\n * A missing `v` is read as {@link INTERRUPT_BINDING_VERSION} so bindings\n * written before the field existed still resume. A `v` we don't recognise is\n * rejected outright — a newer or foreign producer's binding must not be\n * duck-typed into ours.\n */\nfunction isSupportedBindingVersion(raw: Record<string, unknown>): boolean {\n const version = raw['v']\n if (version === undefined) return true\n return version === INTERRUPT_BINDING_VERSION\n}\n\nexport function readUnopenedInterruptBinding(\n descriptor: Interrupt,\n): UnopenedInterruptBinding | undefined {\n const metadata = objectValue(descriptor.metadata)\n const raw = metadata\n ? objectValue(metadata[interruptBindingMetadataKey])\n : null\n if (!raw || stringField(raw, 'interruptId') !== descriptor.id)\n return undefined\n if (!isSupportedBindingVersion(raw)) return undefined\n const kind = stringField(raw, 'kind')\n const interruptId = stringField(raw, 'interruptId')\n const responseSchemaHash = stringField(raw, 'responseSchemaHash')\n const expiresAt = stringField(raw, 'expiresAt')\n if (!interruptId || responseSchemaHash === '') return undefined\n if (expiresAt !== undefined && !Number.isFinite(Date.parse(expiresAt))) {\n return undefined\n }\n const v = INTERRUPT_BINDING_VERSION\n if (kind === 'generic') {\n const definitionId = stringField(raw, 'definitionId')\n const key = stringField(raw, 'key')\n const batchIndex = raw['batchIndex']\n const payloadSchemaHash = stringField(raw, 'payloadSchemaHash')\n const hasFirstPartyFields =\n definitionId !== undefined ||\n key !== undefined ||\n batchIndex !== undefined ||\n payloadSchemaHash !== undefined\n if (\n hasFirstPartyFields &&\n (!definitionId ||\n !key ||\n typeof batchIndex !== 'number' ||\n !Number.isInteger(batchIndex) ||\n batchIndex < 0)\n ) {\n return undefined\n }\n return {\n v,\n kind,\n interruptId,\n ...(responseSchemaHash ? { responseSchemaHash } : {}),\n ...(expiresAt ? { expiresAt } : {}),\n ...(definitionId ? { definitionId } : {}),\n ...(key ? { key } : {}),\n ...(typeof batchIndex === 'number' ? { batchIndex } : {}),\n ...(payloadSchemaHash ? { payloadSchemaHash } : {}),\n }\n }\n if (!responseSchemaHash) return undefined\n const toolName = stringField(raw, 'toolName')\n const toolCallId = stringField(raw, 'toolCallId')\n if (!toolName || !toolCallId) return undefined\n if (kind === 'client-tool-execution') {\n const outputSchemaHash = stringField(raw, 'outputSchemaHash')\n if (!outputSchemaHash) return undefined\n return {\n v,\n kind,\n interruptId,\n toolName,\n toolCallId,\n outputSchemaHash,\n responseSchemaHash,\n ...(expiresAt ? { expiresAt } : {}),\n }\n }\n if (kind === 'tool-approval') {\n const inputSchemaHash = stringField(raw, 'inputSchemaHash')\n const approvalSchemaHash = stringField(raw, 'approvalSchemaHash')\n if (!inputSchemaHash || !approvalSchemaHash) return undefined\n return {\n v,\n kind,\n interruptId,\n toolName,\n toolCallId,\n originalArgs: raw.originalArgs,\n inputSchemaHash,\n approvalSchemaHash,\n responseSchemaHash,\n ...(expiresAt ? { expiresAt } : {}),\n }\n }\n return undefined\n}\n\n/**\n * Attach a resume binding to an interrupt descriptor, under\n * {@link INTERRUPT_BINDING_METADATA_KEY}.\n *\n * This is the supported way to make an interrupt resumable by this package.\n * The descriptor keeps its AG-UI shape; only `metadata` gains the namespaced\n * key. Pass the unopened form (no `interruptedRunId` / `generation`) when\n * emitting from inside a run — those fields are stamped as the run finishes.\n */\nexport function withInterruptBinding(\n descriptor: Interrupt,\n binding: UnopenedInterruptBinding | InterruptBinding,\n): Interrupt {\n return {\n ...descriptor,\n metadata: {\n ...descriptor.metadata,\n [interruptBindingMetadataKey]: {\n ...binding,\n v: INTERRUPT_BINDING_VERSION,\n interruptId: descriptor.id,\n },\n },\n }\n}\n\n/**\n * Read the opened resume binding off a descriptor, or `undefined` when the\n * descriptor carries no binding of a version we understand.\n *\n * `undefined` means \"this interrupt is not ours to resume\" — it is not a\n * failure to recover from by inventing a binding.\n */\nexport function readInterruptBinding(\n descriptor: Interrupt,\n): InterruptBinding | undefined {\n const unopened = readUnopenedInterruptBinding(descriptor)\n if (!unopened) return undefined\n const metadata = objectValue(descriptor.metadata)\n const raw = metadata\n ? objectValue(metadata[interruptBindingMetadataKey])\n : null\n if (!raw) return undefined\n const interruptedRunId = stringField(raw, 'interruptedRunId')\n const generation = raw['generation']\n if (\n !interruptedRunId ||\n typeof generation !== 'number' ||\n !Number.isInteger(generation) ||\n generation < 0\n ) {\n return undefined\n }\n return { ...unopened, interruptedRunId, generation }\n}\n\nexport function withoutInterruptBinding(descriptor: Interrupt): Interrupt {\n const metadata = objectValue(descriptor.metadata)\n if (!metadata || !(interruptBindingMetadataKey in metadata)) return descriptor\n const publicMetadata = { ...metadata }\n delete publicMetadata[interruptBindingMetadataKey]\n return { ...descriptor, metadata: publicMetadata }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"interrupt-resume.js","names":[],"sources":["../../src/interrupt-resume.ts"],"sourcesContent":["import {\n INTERRUPT_BINDING_VERSION,\n canonicalizeInterruptResolutions,\n} from './interrupts'\nimport {\n canonicalInterruptJson,\n digestInterruptJson,\n} from './interrupt-serialization'\nimport {\n hashSchemaInput,\n normalizeApprovalSchema,\n} from './activities/chat/tools/approval-schema'\nimport {\n isStandardSchema,\n validateWithStandardSchema,\n} from './activities/chat/tools/schema-converter'\nimport type {\n InterruptBinding,\n InterruptSubmissionError,\n ItemInterruptErrorCode,\n ToolApprovalResolution,\n UnopenedInterruptBinding,\n} from './interrupts'\nimport type {\n ChatMiddlewareConfig,\n ChatResumeToolState,\n} from './activities/chat/middleware/types'\nimport type {\n GenericInterruptRequest,\n InterruptDefinition,\n} from './interrupt-definition'\nimport type { Interrupt, RunAgentResumeItem } from './types'\n\n/**\n * The `Interrupt.metadata` key under which this package's resume binding\n * travels.\n *\n * Exported so anything that produces an interrupt this package must later\n * resume — an application middleware raising a generic pause, a future\n * workflow-to-AG-UI projection — attaches the binding through\n * {@link withInterruptBinding} rather than copying the string. Everything\n * outside this key is the plain AG-UI envelope and is left untouched.\n */\nexport const INTERRUPT_BINDING_METADATA_KEY = 'tanstack:interruptBinding'\n\nconst interruptBindingMetadataKey = INTERRUPT_BINDING_METADATA_KEY\n\n/** The persistence-neutral shape required to validate an interrupt resume. */\nexport interface PendingInterruptResumeRecord {\n interruptId: string\n payload: unknown\n binding: InterruptBinding\n /** Present for a first-party generic interrupt. */\n genericRequest?: GenericInterruptRequest<\n InterruptDefinition<any, any, any, any>\n >\n}\n\nexport interface ValidateInterruptResumeBatchInput {\n threadId: string\n interruptedRunId: string\n generation: number\n pending: ReadonlyArray<PendingInterruptResumeRecord>\n resume?: ReadonlyArray<RunAgentResumeItem>\n tools: ChatMiddlewareConfig['tools']\n now?: number\n}\n\nexport interface ValidatedInterruptResumeBatch {\n errors: ReadonlyArray<InterruptSubmissionError>\n resolutions?: ReadonlyArray<RunAgentResumeItem>\n canonicalResolutions?: string\n fingerprint?: string\n resumeToolState?: ChatResumeToolState\n}\n\nexport class InterruptResumeValidationError extends Error {\n override readonly name = 'InterruptResumeValidationError'\n\n constructor(readonly errors: ReadonlyArray<InterruptSubmissionError>) {\n super(errors.map((error) => error.message).join(' '))\n }\n}\n\nfunction objectValue(value: unknown): Record<string, unknown> | null {\n return value && typeof value === 'object'\n ? (value as Record<string, unknown>)\n : null\n}\n\nfunction stringField(\n value: Record<string, unknown>,\n key: string,\n): string | undefined {\n return typeof value[key] === 'string' ? value[key] : undefined\n}\n\nfunction normalizeIssuePath(\n path: ReadonlyArray<unknown> | undefined,\n): ReadonlyArray<string | number> | undefined {\n if (!path) return undefined\n return path.map((segment) => {\n if (typeof segment === 'string' || typeof segment === 'number') {\n return segment\n }\n const record = objectValue(segment)\n const key = record?.key\n return typeof key === 'number' ? key : String(key ?? segment)\n })\n}\n\nexport function interruptItemError(\n input: Pick<\n ValidateInterruptResumeBatchInput,\n 'threadId' | 'interruptedRunId' | 'generation'\n >,\n interruptId: string,\n code: ItemInterruptErrorCode,\n message: string,\n options?: {\n path?: ReadonlyArray<string | number>\n source?: 'client' | 'server'\n retryable?: boolean\n },\n): InterruptSubmissionError {\n return {\n scope: 'item',\n threadId: input.threadId,\n interruptedRunId: input.interruptedRunId,\n generation: input.generation,\n interruptId,\n code,\n message,\n source: options?.source ?? 'client',\n retryable: options?.retryable ?? false,\n ...(options?.path ? { path: options.path } : {}),\n }\n}\n\nasync function validateSchemaValue(input: {\n schema: unknown\n value: unknown\n onIssue: (message: string, path?: ReadonlyArray<string | number>) => void\n}): Promise<void> {\n if (isStandardSchema(input.schema)) {\n const result = await validateWithStandardSchema<unknown>(\n input.schema,\n input.value,\n )\n if (!result.success) {\n for (const issue of result.issues) {\n input.onIssue(issue.message, normalizeIssuePath(issue.path))\n }\n }\n return\n }\n\n // A non-Standard-Schema value (a raw JSON Schema, e.g. a generic interrupt's\n // wire responseSchema) is not validated by the library. The application\n // validates the resume value itself if it needs to; otherwise it flows\n // through as-is.\n}\n\ntype RuntimeTool = ChatMiddlewareConfig['tools'][number] & {\n approvalSchema?: Parameters<typeof normalizeApprovalSchema>[0]\n}\n\nfunction runtimeTool(\n tools: ChatMiddlewareConfig['tools'],\n name: string,\n): RuntimeTool | undefined {\n return tools.find((tool) => tool.name === name) as RuntimeTool | undefined\n}\n\nasync function parseSchemaValue(\n schema: unknown,\n value: unknown,\n): Promise<{ success: true; data: unknown } | { success: false }> {\n if (!isStandardSchema(schema)) return { success: true, data: value }\n const result = await validateWithStandardSchema<unknown>(schema, value)\n return result.success\n ? { success: true, data: result.data }\n : { success: false }\n}\n\nfunction descriptorResponseSchema(\n record: PendingInterruptResumeRecord,\n): unknown {\n return objectValue(record.payload)?.responseSchema\n}\n\nfunction schemaHash(schema: unknown): string {\n return digestInterruptJson(canonicalInterruptJson(schema))\n}\n\nasync function pushSchemaIssues(input: {\n request: ValidateInterruptResumeBatchInput\n errors: Array<InterruptSubmissionError>\n interruptId: string\n schema: unknown\n value: unknown\n code: ItemInterruptErrorCode\n label: string\n}): Promise<void> {\n try {\n await validateSchemaValue({\n schema: input.schema,\n value: input.value,\n onIssue: (message, path) => {\n input.errors.push(\n interruptItemError(\n input.request,\n input.interruptId,\n input.code,\n `${input.label}: ${message}`,\n { path },\n ),\n )\n },\n })\n } catch (error) {\n input.errors.push(\n interruptItemError(\n input.request,\n input.interruptId,\n 'invalid-response-schema',\n `${input.label} could not be validated: ${error instanceof Error ? error.message : String(error)}`,\n { source: 'server' },\n ),\n )\n }\n}\n\nfunction validateDescriptorSchema(\n input: ValidateInterruptResumeBatchInput,\n record: PendingInterruptResumeRecord,\n binding: InterruptBinding,\n errors: Array<InterruptSubmissionError>,\n): unknown {\n const schema = descriptorResponseSchema(record)\n const responseSchemaHash = binding.responseSchemaHash\n if (schema === undefined && responseSchemaHash === undefined) {\n return undefined\n }\n if (\n schema === undefined ||\n responseSchemaHash === undefined ||\n schemaHash(schema) !== responseSchemaHash\n ) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-response-schema',\n `Interrupt ${record.interruptId} response schema no longer matches its binding.`,\n { source: 'server' },\n ),\n )\n }\n return schema\n}\n\n/**\n * Validate and translate a complete interrupt batch before any tool executes.\n * Used by ephemeral chat resume; a durable layer may share the same validator.\n */\nexport async function validateInterruptResumeBatch(\n input: ValidateInterruptResumeBatchInput,\n): Promise<ValidatedInterruptResumeBatch> {\n const grouped = new Map<string, Array<InterruptSubmissionError>>()\n const batchErrors: Array<InterruptSubmissionError> = []\n const group = (interruptId: string): Array<InterruptSubmissionError> => {\n const existing = grouped.get(interruptId)\n if (existing) return existing\n const created: Array<InterruptSubmissionError> = []\n grouped.set(interruptId, created)\n return created\n }\n const pendingById = new Map(\n input.pending.map((record) => [record.interruptId, record]),\n )\n const resumeById = new Map<string, RunAgentResumeItem>()\n const counts = new Map<string, number>()\n for (const entry of input.resume ?? []) {\n counts.set(entry.interruptId, (counts.get(entry.interruptId) ?? 0) + 1)\n if (!resumeById.has(entry.interruptId))\n resumeById.set(entry.interruptId, entry)\n }\n\n for (const [interruptId, count] of counts) {\n if (count > 1) {\n group(interruptId).push(\n interruptItemError(\n input,\n interruptId,\n 'conflict',\n `Interrupt ${interruptId} has duplicate resume entries.`,\n ),\n )\n }\n }\n\n const pendingGenerics = input.pending.filter(\n (record) => record.binding.kind === 'generic',\n )\n const genericBatchSatisfied =\n pendingGenerics.length > 0 &&\n pendingGenerics.every((record) => resumeById.has(record.interruptId))\n\n let incomplete = false\n for (const record of input.pending) {\n const errors = group(record.interruptId)\n const entry = resumeById.get(record.interruptId)\n const binding = record.binding\n if (!entry) {\n // Client tools that share a generic interrupt batch wait for\n // `toolResume`. `continue` re-emits them; `cancel` / `stop` skip them.\n if (genericBatchSatisfied && binding.kind === 'client-tool-execution') {\n continue\n }\n incomplete = true\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'unknown-interrupt',\n `Missing resume entry for interrupt ${record.interruptId}.`,\n ),\n )\n }\n if (\n binding.interruptedRunId !== input.interruptedRunId ||\n binding.generation !== input.generation ||\n binding.interruptId !== record.interruptId\n ) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'stale',\n `Interrupt ${record.interruptId} has stale correlation metadata.`,\n { source: 'server' },\n ),\n )\n }\n if (binding.expiresAt !== undefined) {\n const expiresAt = Date.parse(binding.expiresAt)\n if (!Number.isFinite(expiresAt)) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-payload',\n `Interrupt ${record.interruptId} has an invalid expiresAt.`,\n { source: 'server' },\n ),\n )\n } else if (expiresAt <= (input.now ?? Date.now())) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'expired',\n `Interrupt ${record.interruptId} has expired.`,\n { source: 'server' },\n ),\n )\n }\n }\n\n const responseSchema = validateDescriptorSchema(\n input,\n record,\n binding,\n errors,\n )\n if (!entry) continue\n const entryStatus: unknown = entry.status\n if (entryStatus !== 'resolved' && entryStatus !== 'cancelled') {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-payload',\n `Interrupt ${record.interruptId} has invalid status ${String(entryStatus)}.`,\n ),\n )\n continue\n }\n if (binding.kind === 'generic') {\n const genericRequest = record.genericRequest\n if (genericRequest !== undefined) {\n const batchIndex = binding.batchIndex\n if (\n binding.definitionId !== genericRequest.definition.id ||\n binding.key !== genericRequest.key ||\n binding.interruptId !== record.interruptId ||\n batchIndex === undefined ||\n !Number.isInteger(batchIndex) ||\n batchIndex < 0\n ) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'stale',\n `Generic interrupt ${record.interruptId} has stale definition metadata.`,\n { source: 'server' },\n ),\n )\n }\n }\n if (entry.status === 'cancelled') {\n if (entry.payload !== undefined) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-payload',\n `Cancelled interrupt ${record.interruptId} must not include a payload.`,\n ),\n )\n }\n } else if (genericRequest !== undefined) {\n await pushSchemaIssues({\n request: input,\n errors,\n interruptId: record.interruptId,\n schema: genericRequest.definition.responseSchema,\n value: entry.payload,\n code: 'invalid-payload',\n label: `Interrupt ${record.interruptId} payload is invalid`,\n })\n } else if (responseSchema !== undefined) {\n await pushSchemaIssues({\n request: input,\n errors,\n interruptId: record.interruptId,\n schema: responseSchema,\n value: entry.payload,\n code: 'invalid-payload',\n label: `Interrupt ${record.interruptId} payload is invalid`,\n })\n }\n continue\n }\n\n if (entry.status === 'cancelled') {\n if (entry.payload !== undefined) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-payload',\n `Cancelled interrupt ${record.interruptId} must not include a payload.`,\n ),\n )\n }\n continue\n }\n\n const tool = runtimeTool(input.tools, binding.toolName)\n if (!tool) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'stale',\n `Tool ${binding.toolName} is unavailable for interrupt ${record.interruptId}.`,\n { source: 'server' },\n ),\n )\n continue\n }\n\n let approval: ReturnType<typeof normalizeApprovalSchema> | undefined\n let schemaDrifted = false\n if (binding.kind === 'client-tool-execution') {\n if (hashSchemaInput(tool.outputSchema) !== binding.outputSchemaHash) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'stale',\n `Tool ${binding.toolName} output schema has changed.`,\n { source: 'server' },\n ),\n )\n schemaDrifted = true\n }\n } else {\n try {\n approval = normalizeApprovalSchema(\n tool.approvalSchema,\n tool.inputSchema,\n )\n } catch {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'stale',\n `Tool ${binding.toolName} approval schema is unavailable.`,\n { source: 'server' },\n ),\n )\n schemaDrifted = true\n }\n if (\n approval !== undefined &&\n (hashSchemaInput(tool.inputSchema) !== binding.inputSchemaHash ||\n approval.approvalSchemaHash !== binding.approvalSchemaHash ||\n approval.responseSchemaHash !== binding.responseSchemaHash)\n ) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'stale',\n `Tool ${binding.toolName} approval schema has changed.`,\n { source: 'server' },\n ),\n )\n schemaDrifted = true\n }\n }\n\n if (schemaDrifted) continue\n\n if (binding.kind === 'client-tool-execution') {\n if (responseSchema !== undefined) {\n await pushSchemaIssues({\n request: input,\n errors,\n interruptId: record.interruptId,\n schema: responseSchema,\n value: entry.payload,\n code: 'invalid-tool-output',\n label: `Tool ${binding.toolName} output is invalid`,\n })\n }\n if (tool.outputSchema !== undefined) {\n await pushSchemaIssues({\n request: input,\n errors,\n interruptId: record.interruptId,\n schema: tool.outputSchema,\n value: entry.payload,\n code: 'invalid-tool-output',\n label: `Tool ${binding.toolName} output is invalid`,\n })\n }\n continue\n }\n\n if (approval === undefined) continue\n const envelope = objectValue(entry.payload)\n const approved =\n typeof entry.payload === 'boolean'\n ? entry.payload\n : typeof envelope?.approved === 'boolean'\n ? envelope.approved\n : undefined\n if (approved === undefined) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-payload',\n `Approval ${record.interruptId} must be a boolean or decision envelope.`,\n ),\n )\n continue\n }\n if (envelope) {\n await pushSchemaIssues({\n request: input,\n errors,\n interruptId: record.interruptId,\n schema: approval.responseSchema,\n value: entry.payload,\n code: 'invalid-payload',\n label: `Approval ${record.interruptId} envelope is invalid`,\n })\n }\n if (approved && envelope?.editedArgs !== undefined) {\n if (tool.inputSchema === undefined) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-edited-args',\n `Approval ${record.interruptId} cannot edit arguments without an input schema.`,\n ),\n )\n } else {\n await pushSchemaIssues({\n request: input,\n errors,\n interruptId: record.interruptId,\n schema: tool.inputSchema,\n value: envelope.editedArgs,\n code: 'invalid-edited-args',\n label: `Approval ${record.interruptId} edited arguments are invalid`,\n })\n }\n }\n const branch = approved\n ? approval.branches.approve\n : approval.branches.reject\n if (branch) {\n if (!envelope) {\n errors.push(\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-payload',\n `Approval ${record.interruptId} requires a payload for the ${approved ? 'approve' : 'reject'} decision.`,\n ),\n )\n } else {\n await pushSchemaIssues({\n request: input,\n errors,\n interruptId: record.interruptId,\n schema: branch.source,\n value: envelope.payload,\n code: 'invalid-payload',\n label: `Approval ${record.interruptId} payload is invalid`,\n })\n }\n }\n }\n\n for (const entry of input.resume ?? []) {\n if (!pendingById.has(entry.interruptId)) {\n incomplete = true\n group(entry.interruptId).push(\n interruptItemError(\n input,\n entry.interruptId,\n 'unknown-interrupt',\n `Resume entry references unknown interrupt ${entry.interruptId}.`,\n ),\n )\n }\n }\n\n if (incomplete) {\n batchErrors.push({\n scope: 'batch',\n threadId: input.threadId,\n interruptedRunId: input.interruptedRunId,\n generation: input.generation,\n code: 'incomplete-batch',\n message:\n 'Resume entries must resolve or cancel the complete interrupt batch.',\n source: 'client',\n retryable: false,\n interruptIds: input.pending.map((record) => record.interruptId),\n })\n }\n\n const itemErrors = [...grouped.entries()]\n .sort(([left], [right]) => left.localeCompare(right))\n .flatMap(([, errors]) => errors)\n if (itemErrors.length > 0) {\n batchErrors.push({\n scope: 'batch',\n threadId: input.threadId,\n interruptedRunId: input.interruptedRunId,\n generation: input.generation,\n code: 'item-validation-failed',\n message: 'One or more interrupt resolutions are invalid.',\n source: 'client',\n retryable: false,\n interruptIds: input.pending.map((record) => record.interruptId),\n })\n return { errors: [...itemErrors, ...batchErrors] }\n }\n\n const canonical = canonicalizeInterruptResolutions(input.resume ?? [])\n const approvals = new Map<string, ToolApprovalResolution>()\n const clientToolResults = new Map<string, unknown>()\n const genericInterrupts = new Map<\n string,\n | { interruptId: string; status: 'resolved'; payload: unknown }\n | { interruptId: string; status: 'cancelled' }\n >()\n const deniedToolResults = new Map<string, unknown>()\n const cancelledToolCallIds = new Set<string>()\n\n for (const record of input.pending) {\n const entry = resumeById.get(record.interruptId)\n if (!entry) continue\n const binding = record.binding\n if (binding.kind === 'generic') {\n if (entry.status !== 'resolved') {\n genericInterrupts.set(record.interruptId, {\n interruptId: record.interruptId,\n status: 'cancelled',\n })\n continue\n }\n if (record.genericRequest === undefined) {\n genericInterrupts.set(record.interruptId, {\n interruptId: record.interruptId,\n status: 'resolved',\n payload: entry.payload,\n })\n continue\n }\n const parsed = await parseSchemaValue(\n record.genericRequest.definition.responseSchema,\n entry.payload,\n )\n if (!parsed.success) {\n return {\n errors: [\n interruptItemError(\n input,\n record.interruptId,\n 'invalid-payload',\n `Interrupt ${record.interruptId} payload is invalid.`,\n ),\n ],\n }\n }\n genericInterrupts.set(record.interruptId, {\n interruptId: record.interruptId,\n status: 'resolved',\n payload: parsed.data,\n })\n continue\n }\n if (entry.status === 'cancelled') {\n cancelledToolCallIds.add(binding.toolCallId)\n continue\n }\n if (binding.kind === 'client-tool-execution') {\n clientToolResults.set(binding.toolCallId, entry.payload)\n continue\n }\n const envelope = objectValue(entry.payload)\n const resolution: ToolApprovalResolution =\n typeof entry.payload === 'boolean'\n ? entry.payload\n : envelope?.approved === true\n ? {\n approved: true,\n ...(envelope.editedArgs !== undefined\n ? { editedArgs: envelope.editedArgs }\n : {}),\n ...(envelope.payload !== undefined\n ? { payload: envelope.payload }\n : {}),\n }\n : {\n approved: false,\n ...(envelope?.payload !== undefined\n ? { payload: envelope.payload }\n : {}),\n }\n approvals.set(binding.toolCallId, resolution)\n if (\n resolution === false ||\n (typeof resolution === 'object' && !resolution.approved)\n ) {\n deniedToolResults.set(\n binding.toolCallId,\n typeof resolution === 'object' ? resolution.payload : undefined,\n )\n }\n }\n\n return {\n errors: [],\n resolutions: canonical.resolutions,\n canonicalResolutions: canonical.canonicalResolutions,\n fingerprint: canonical.fingerprint,\n resumeToolState: {\n approvals,\n clientToolResults,\n genericInterrupts,\n deniedToolResults,\n cancelledToolCallIds,\n },\n }\n}\n\n/**\n * Is this a binding written by a version of the protocol we understand?\n *\n * A missing `v` is read as {@link INTERRUPT_BINDING_VERSION} so bindings\n * written before the field existed still resume. A `v` we don't recognise is\n * rejected outright — a newer or foreign producer's binding must not be\n * duck-typed into ours.\n */\nfunction isSupportedBindingVersion(raw: Record<string, unknown>): boolean {\n const version = raw['v']\n if (version === undefined) return true\n return version === INTERRUPT_BINDING_VERSION\n}\n\nexport function readUnopenedInterruptBinding(\n descriptor: Interrupt,\n): UnopenedInterruptBinding | undefined {\n const metadata = objectValue(descriptor.metadata)\n const raw = metadata\n ? objectValue(metadata[interruptBindingMetadataKey])\n : null\n if (!raw || stringField(raw, 'interruptId') !== descriptor.id)\n return undefined\n if (!isSupportedBindingVersion(raw)) return undefined\n const kind = stringField(raw, 'kind')\n const interruptId = stringField(raw, 'interruptId')\n const responseSchemaHash = stringField(raw, 'responseSchemaHash')\n const expiresAt = stringField(raw, 'expiresAt')\n if (!interruptId || responseSchemaHash === '') return undefined\n if (expiresAt !== undefined && !Number.isFinite(Date.parse(expiresAt))) {\n return undefined\n }\n const v = INTERRUPT_BINDING_VERSION\n if (kind === 'generic') {\n const definitionId = stringField(raw, 'definitionId')\n const key = stringField(raw, 'key')\n const batchIndex = raw['batchIndex']\n const payloadSchemaHash = stringField(raw, 'payloadSchemaHash')\n const hasFirstPartyFields =\n definitionId !== undefined ||\n key !== undefined ||\n batchIndex !== undefined ||\n payloadSchemaHash !== undefined\n if (\n hasFirstPartyFields &&\n (!definitionId ||\n !key ||\n typeof batchIndex !== 'number' ||\n !Number.isInteger(batchIndex) ||\n batchIndex < 0)\n ) {\n return undefined\n }\n return {\n v,\n kind,\n interruptId,\n ...(responseSchemaHash ? { responseSchemaHash } : {}),\n ...(expiresAt ? { expiresAt } : {}),\n ...(definitionId ? { definitionId } : {}),\n ...(key ? { key } : {}),\n ...(typeof batchIndex === 'number' ? { batchIndex } : {}),\n ...(payloadSchemaHash ? { payloadSchemaHash } : {}),\n }\n }\n if (!responseSchemaHash) return undefined\n const toolName = stringField(raw, 'toolName')\n const toolCallId = stringField(raw, 'toolCallId')\n if (!toolName || !toolCallId) return undefined\n if (kind === 'client-tool-execution') {\n const outputSchemaHash = stringField(raw, 'outputSchemaHash')\n if (!outputSchemaHash) return undefined\n return {\n v,\n kind,\n interruptId,\n toolName,\n toolCallId,\n outputSchemaHash,\n responseSchemaHash,\n ...(expiresAt ? { expiresAt } : {}),\n }\n }\n if (kind === 'tool-approval') {\n const inputSchemaHash = stringField(raw, 'inputSchemaHash')\n const approvalSchemaHash = stringField(raw, 'approvalSchemaHash')\n if (!inputSchemaHash || !approvalSchemaHash) return undefined\n return {\n v,\n kind,\n interruptId,\n toolName,\n toolCallId,\n originalArgs: raw.originalArgs,\n inputSchemaHash,\n approvalSchemaHash,\n responseSchemaHash,\n ...(expiresAt ? { expiresAt } : {}),\n }\n }\n return undefined\n}\n\n/**\n * Attach a resume binding to an interrupt descriptor, under\n * {@link INTERRUPT_BINDING_METADATA_KEY}.\n *\n * This is the supported way to make an interrupt resumable by this package.\n * The descriptor keeps its AG-UI shape; only `metadata` gains the namespaced\n * key. Pass the unopened form (no `interruptedRunId` / `generation`) when\n * emitting from inside a run — those fields are stamped as the run finishes.\n */\nexport function withInterruptBinding(\n descriptor: Interrupt,\n binding: UnopenedInterruptBinding | InterruptBinding,\n): Interrupt {\n return {\n ...descriptor,\n metadata: {\n ...descriptor.metadata,\n [interruptBindingMetadataKey]: {\n ...binding,\n v: INTERRUPT_BINDING_VERSION,\n interruptId: descriptor.id,\n },\n },\n }\n}\n\n/**\n * Read the opened resume binding off a descriptor, or `undefined` when the\n * descriptor carries no binding of a version we understand.\n *\n * `undefined` means \"this interrupt is not ours to resume\" — it is not a\n * failure to recover from by inventing a binding.\n */\nexport function readInterruptBinding(\n descriptor: Interrupt,\n): InterruptBinding | undefined {\n const unopened = readUnopenedInterruptBinding(descriptor)\n if (!unopened) return undefined\n const metadata = objectValue(descriptor.metadata)\n const raw = metadata\n ? objectValue(metadata[interruptBindingMetadataKey])\n : null\n if (!raw) return undefined\n const interruptedRunId = stringField(raw, 'interruptedRunId')\n const generation = raw['generation']\n if (\n !interruptedRunId ||\n typeof generation !== 'number' ||\n !Number.isInteger(generation) ||\n generation < 0\n ) {\n return undefined\n }\n return { ...unopened, interruptedRunId, generation }\n}\n\nexport function withoutInterruptBinding(descriptor: Interrupt): Interrupt {\n const metadata = objectValue(descriptor.metadata)\n if (!metadata || !(interruptBindingMetadataKey in metadata)) return descriptor\n const publicMetadata = { ...metadata }\n delete publicMetadata[interruptBindingMetadataKey]\n return { ...descriptor, metadata: publicMetadata }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai",
3
- "version": "0.48.0",
3
+ "version": "0.49.1",
4
4
  "description": "Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -48,6 +48,14 @@
48
48
  "./adapter-internals": {
49
49
  "types": "./dist/esm/adapter-internals.d.ts",
50
50
  "import": "./dist/esm/adapter-internals.js"
51
+ },
52
+ "./byok": {
53
+ "types": "./dist/esm/byok.d.ts",
54
+ "import": "./dist/esm/byok.js"
55
+ },
56
+ "./byok/server": {
57
+ "types": "./dist/esm/byok/server.d.ts",
58
+ "import": "./dist/esm/byok/server.js"
51
59
  }
52
60
  },
53
61
  "sideEffects": false,
@@ -458,8 +458,8 @@ const { messages, queue, sendMessage, cancelQueued, isLoading } = useChat({
458
458
 
459
459
  The top-level `queue` option also accepts a plain `WhenBusy` string
460
460
  shorthand (e.g. `queue: 'interrupt'`) or a `QueueStrategy` function for
461
- per-send action control. Strategy form always drains FIFO; actions are
462
- `'queue' | 'drop' | 'interrupt'`.
461
+ per-send action control. Strategy form always drains FIFO. Actions use
462
+ the `WhenBusy` type.
463
463
 
464
464
  **Drain vs flush:** queued messages auto-send only after a **successful**
465
465
  settle. They are **discarded** on stream error/abort of the active
@@ -0,0 +1,46 @@
1
+ import { isProviderId } from './providers'
2
+
3
+ /**
4
+ * A BYOK provider declared by an adapter. `id` is the `x-byok-<id>` slug and
5
+ * is required — `{ id?: string }` is not a {@link ByokProvider}.
6
+ */
7
+ export interface ByokProvider<TId extends string = string> {
8
+ readonly id: TId
9
+ readonly label: string
10
+ /**
11
+ * Env var names the relay may read. Names only — never put `process.env`
12
+ * values here. This object is imported on the client.
13
+ */
14
+ readonly env?: ReadonlyArray<string>
15
+ }
16
+
17
+ /**
18
+ * Input for {@link defineByokProvider}. `id` cannot be optional: if `TId`
19
+ * includes `undefined`, `id` becomes `never` and the object is unassignable.
20
+ */
21
+ export type ByokProviderInit<TId extends string> = {
22
+ readonly id: undefined extends TId ? never : TId
23
+ readonly label: string
24
+ readonly env?: string | ReadonlyArray<string>
25
+ }
26
+
27
+ function normalizeEnv(
28
+ env: string | ReadonlyArray<string> | undefined,
29
+ ): ReadonlyArray<string> | undefined {
30
+ if (env === undefined) return undefined
31
+ return typeof env === 'string' ? [env] : env
32
+ }
33
+
34
+ export function defineByokProvider<const TId extends string>(
35
+ provider: ByokProviderInit<TId>,
36
+ ): ByokProvider<TId> {
37
+ if (!isProviderId(provider.id)) {
38
+ throw new Error(`Invalid BYOK provider id: ${String(provider.id)}`)
39
+ }
40
+ const env = normalizeEnv(provider.env)
41
+ return {
42
+ id: provider.id,
43
+ label: provider.label,
44
+ ...(env ? { env } : {}),
45
+ }
46
+ }
@@ -0,0 +1,36 @@
1
+ import type { ProviderId } from './providers'
2
+
3
+ export class ByokMissingError extends Error {
4
+ readonly provider: ProviderId
5
+
6
+ constructor(provider: ProviderId) {
7
+ super(`Missing ${provider} API key`)
8
+ this.name = 'ByokMissingError'
9
+ this.provider = provider
10
+ }
11
+ }
12
+
13
+ export class ByokBlockedError extends Error {
14
+ readonly provider: ProviderId
15
+ readonly reason: 'missing' | 'locked'
16
+
17
+ constructor(provider: ProviderId, reason: 'missing' | 'locked') {
18
+ super(
19
+ reason === 'locked'
20
+ ? `${provider} key is locked`
21
+ : `Missing ${provider} API key`,
22
+ )
23
+ this.name = 'ByokBlockedError'
24
+ this.provider = provider
25
+ this.reason = reason
26
+ }
27
+ }
28
+
29
+ export class ByokUnresolvedProviderError extends Error {
30
+ constructor() {
31
+ super(
32
+ 'BYOK is enabled but no provider slug was resolved. Pass byokProvider or forwardedProps.provider.',
33
+ )
34
+ this.name = 'ByokUnresolvedProviderError'
35
+ }
36
+ }
@@ -0,0 +1,27 @@
1
+ import { byokHeaderName, resolveProviderId } from './providers'
2
+ import type { ByokProvider } from './define-provider'
3
+ import type { ProviderId } from './providers'
4
+
5
+ /**
6
+ * Read a key on the relay. Import from `@tanstack/ai/byok/server` so this
7
+ * `process.env` access is not in the client graph.
8
+ *
9
+ * The header wins. A {@link ByokProvider} then tries `provider.env` in order.
10
+ * A slug is header-only.
11
+ */
12
+ export function getByokKey(
13
+ request: Request,
14
+ provider: ProviderId | ByokProvider,
15
+ ): string | null {
16
+ const value = request.headers.get(byokHeaderName(resolveProviderId(provider)))
17
+ if (typeof value === 'string') {
18
+ const trimmed = value.trim()
19
+ if (trimmed.length > 0) return trimmed
20
+ }
21
+ if (typeof provider === 'string') return null
22
+ for (const name of provider.env ?? []) {
23
+ const envValue = process.env[name]
24
+ if (typeof envValue === 'string' && envValue.length > 0) return envValue
25
+ }
26
+ return null
27
+ }
@@ -0,0 +1,43 @@
1
+ import { isProviderId, resolveProviderId } from './providers'
2
+ import type { ByokProvider } from './define-provider'
3
+ import type { ProviderId } from './providers'
4
+
5
+ export interface ByokMissingBody {
6
+ error: {
7
+ type: 'byok_missing'
8
+ provider: ProviderId
9
+ message: string
10
+ }
11
+ }
12
+
13
+ export function isByokMissingBody(value: unknown): value is ByokMissingBody {
14
+ if (typeof value !== 'object' || value === null) return false
15
+ if (!('error' in value)) return false
16
+ const error = value.error
17
+ if (typeof error !== 'object' || error === null) return false
18
+ if (!('type' in error) || error.type !== 'byok_missing') return false
19
+ if (!('provider' in error) || typeof error.provider !== 'string') {
20
+ return false
21
+ }
22
+ if (!isProviderId(error.provider)) return false
23
+ if (!('message' in error) || typeof error.message !== 'string') return false
24
+ return true
25
+ }
26
+
27
+ export function byokMissing(provider: ProviderId | ByokProvider): Response {
28
+ const id = resolveProviderId(provider)
29
+ if (!isProviderId(id)) {
30
+ throw new Error(`Invalid BYOK provider id: ${id}`)
31
+ }
32
+ const body: ByokMissingBody = {
33
+ error: {
34
+ type: 'byok_missing',
35
+ provider: id,
36
+ message: `Missing ${id} API key`,
37
+ },
38
+ }
39
+ return new Response(JSON.stringify(body), {
40
+ status: 401,
41
+ headers: { 'content-type': 'application/json' },
42
+ })
43
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Provider ids are open slugs, not a closed catalog. `@tanstack/ai` does not
3
+ * list adapters. Any matching string is a valid id and becomes `x-byok-<id>`.
4
+ */
5
+ export type ProviderId = string
6
+
7
+ /** `[a-z][a-z0-9-]{0,63}` — lowercase, no underscores, max 64 chars. */
8
+ export const BYOK_PROVIDER_ID_PATTERN = /^[a-z][a-z0-9-]{0,63}$/
9
+
10
+ export const BYOK_HEADER_PREFIX = 'x-byok-'
11
+
12
+ export function isProviderId(value: unknown): value is ProviderId {
13
+ return typeof value === 'string' && BYOK_PROVIDER_ID_PATTERN.test(value)
14
+ }
15
+
16
+ export function resolveProviderId(
17
+ provider: string | { readonly id: string },
18
+ ): string {
19
+ return typeof provider === 'string' ? provider : provider.id
20
+ }
21
+
22
+ export function byokHeaderName(provider: string): string {
23
+ if (!isProviderId(provider)) {
24
+ throw new Error(`Invalid BYOK provider id: ${provider}`)
25
+ }
26
+ return `${BYOK_HEADER_PREFIX}${provider}`
27
+ }
@@ -0,0 +1,16 @@
1
+ export function maskKey(key: string): string {
2
+ if (key.length <= 4) return '••'
3
+ return key.slice(-4)
4
+ }
5
+
6
+ export function scrubSecrets(
7
+ input: string,
8
+ secrets: ReadonlyArray<string>,
9
+ ): string {
10
+ let next = input
11
+ for (const secret of secrets) {
12
+ if (secret.length === 0) continue
13
+ next = next.split(secret).join('[redacted]')
14
+ }
15
+ return next
16
+ }
@@ -0,0 +1,3 @@
1
+ export { getByokKey } from './get-key'
2
+ export { byokMissing, isByokMissingBody } from './missing'
3
+ export type { ByokMissingBody } from './missing'
package/src/byok.ts ADDED
@@ -0,0 +1,17 @@
1
+ export {
2
+ BYOK_PROVIDER_ID_PATTERN,
3
+ BYOK_HEADER_PREFIX,
4
+ byokHeaderName,
5
+ isProviderId,
6
+ } from './byok/providers'
7
+ export type { ProviderId } from './byok/providers'
8
+ export { defineByokProvider } from './byok/define-provider'
9
+ export type { ByokProvider, ByokProviderInit } from './byok/define-provider'
10
+ export { isByokMissingBody, byokMissing } from './byok/missing'
11
+ export type { ByokMissingBody } from './byok/missing'
12
+ export {
13
+ ByokMissingError,
14
+ ByokBlockedError,
15
+ ByokUnresolvedProviderError,
16
+ } from './byok/errors'
17
+ export { maskKey, scrubSecrets } from './byok/scrub'
@@ -445,6 +445,20 @@ export async function validateInterruptResumeBatch(
445
445
  continue
446
446
  }
447
447
 
448
+ if (entry.status === 'cancelled') {
449
+ if (entry.payload !== undefined) {
450
+ errors.push(
451
+ interruptItemError(
452
+ input,
453
+ record.interruptId,
454
+ 'invalid-payload',
455
+ `Cancelled interrupt ${record.interruptId} must not include a payload.`,
456
+ ),
457
+ )
458
+ }
459
+ continue
460
+ }
461
+
448
462
  const tool = runtimeTool(input.tools, binding.toolName)
449
463
  if (!tool) {
450
464
  errors.push(
@@ -511,19 +525,6 @@ export async function validateInterruptResumeBatch(
511
525
  }
512
526
  }
513
527
 
514
- if (entry.status === 'cancelled') {
515
- if (entry.payload !== undefined) {
516
- errors.push(
517
- interruptItemError(
518
- input,
519
- record.interruptId,
520
- 'invalid-payload',
521
- `Cancelled interrupt ${record.interruptId} must not include a payload.`,
522
- ),
523
- )
524
- }
525
- continue
526
- }
527
528
  if (schemaDrifted) continue
528
529
 
529
530
  if (binding.kind === 'client-tool-execution') {