@tanstack/ai 0.38.0 → 0.39.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.
Files changed (46) hide show
  1. package/dist/esm/activities/chat/index.js +32 -0
  2. package/dist/esm/activities/chat/index.js.map +1 -1
  3. package/dist/esm/activities/chat/middleware/compose.d.ts +7 -1
  4. package/dist/esm/activities/chat/middleware/compose.js +27 -0
  5. package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
  6. package/dist/esm/activities/chat/middleware/index.d.ts +1 -1
  7. package/dist/esm/activities/chat/middleware/sandbox-runtime.d.ts +8 -0
  8. package/dist/esm/activities/chat/middleware/sandbox-runtime.js +9 -0
  9. package/dist/esm/activities/chat/middleware/sandbox-runtime.js.map +1 -0
  10. package/dist/esm/activities/chat/middleware/types.d.ts +22 -0
  11. package/dist/esm/activities/chat/stream/processor.d.ts +18 -0
  12. package/dist/esm/activities/chat/stream/processor.js +86 -4
  13. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  14. package/dist/esm/adapter-internals.d.ts +2 -0
  15. package/dist/esm/adapter-internals.js +4 -0
  16. package/dist/esm/adapter-internals.js.map +1 -1
  17. package/dist/esm/index.d.ts +3 -2
  18. package/dist/esm/index.js +3 -0
  19. package/dist/esm/index.js.map +1 -1
  20. package/dist/esm/logger/internal-logger.d.ts +2 -0
  21. package/dist/esm/logger/internal-logger.js +6 -1
  22. package/dist/esm/logger/internal-logger.js.map +1 -1
  23. package/dist/esm/logger/resolve.js +6 -3
  24. package/dist/esm/logger/resolve.js.map +1 -1
  25. package/dist/esm/logger/types.d.ts +5 -0
  26. package/dist/esm/types.d.ts +40 -1
  27. package/dist/esm/utilities/provider-executed.d.ts +18 -0
  28. package/dist/esm/utilities/provider-executed.js +15 -0
  29. package/dist/esm/utilities/provider-executed.js.map +1 -0
  30. package/package.json +1 -1
  31. package/skills/ai-core/adapter-configuration/SKILL.md +25 -12
  32. package/skills/ai-core/adapter-configuration/references/anthropic-adapter.md +43 -12
  33. package/skills/ai-core/media-generation/SKILL.md +1 -1
  34. package/src/activities/chat/index.ts +47 -0
  35. package/src/activities/chat/middleware/compose.ts +34 -0
  36. package/src/activities/chat/middleware/index.ts +2 -0
  37. package/src/activities/chat/middleware/sandbox-runtime.ts +21 -0
  38. package/src/activities/chat/middleware/types.ts +37 -0
  39. package/src/activities/chat/stream/processor.ts +141 -3
  40. package/src/adapter-internals.ts +6 -0
  41. package/src/index.ts +9 -0
  42. package/src/logger/internal-logger.ts +6 -0
  43. package/src/logger/resolve.ts +3 -0
  44. package/src/logger/types.ts +5 -0
  45. package/src/types.ts +43 -1
  46. package/src/utilities/provider-executed.ts +32 -0
@@ -1 +1 @@
1
- {"version":3,"file":"internal-logger.js","sources":["../../../src/logger/internal-logger.ts"],"sourcesContent":["import type { DebugCategories, Logger } from './types'\n\n/**\n * Fully-resolved categories map. Every flag is a definite boolean (never\n * undefined), produced by `resolveDebugOption` from a `DebugOption`.\n */\nexport type ResolvedCategories = Required<DebugCategories>\n\n/**\n * Package-internal logger wrapper used by every activity and adapter in\n * `@tanstack/ai`. Wraps a user-supplied (or default `ConsoleLogger`) `Logger`\n * plus a fully-resolved per-category map. Each category has a dedicated\n * method that no-ops when its flag is `false`, or prepends a\n * `[tanstack-ai:<category>] ` prefix and calls the underlying logger's\n * `error` (for the `errors` category) or `debug` (for everything else).\n *\n * Not exported from the package root. Adapter packages consume it via the\n * `@tanstack/ai/adapter-internals` subpath export.\n */\n/**\n * Emoji marker per category — bracketing the `[tanstack-ai:<cat>]` tag on\n * both sides makes it trivial to visually pick out a category when scanning\n * dense streaming logs.\n */\nconst CATEGORY_EMOJI: Record<keyof ResolvedCategories, string> = {\n request: '📤',\n provider: '📥',\n output: '📨',\n middleware: '🧩',\n tools: '🔧',\n agentLoop: '🔁',\n config: '⚙️',\n errors: '❌',\n}\n\nexport class InternalLogger {\n constructor(\n private readonly logger: Logger,\n private readonly categories: ResolvedCategories,\n ) {}\n\n /** Whether a category is enabled. Cheap, safe to call on hot paths. */\n isEnabled(category: keyof ResolvedCategories): boolean {\n return this.categories[category]\n }\n\n private emit(\n level: 'debug' | 'error',\n category: keyof ResolvedCategories,\n message: string,\n meta?: Record<string, unknown>,\n ): void {\n if (!this.categories[category]) return\n const emoji = CATEGORY_EMOJI[category]\n const prefixed = `${emoji} [tanstack-ai:${category}] ${emoji} ${message}`\n try {\n if (level === 'error') this.logger.error(prefixed, meta)\n else this.logger.debug(prefixed, meta)\n } catch {\n // User-supplied logger threw; swallow so we never mask the original\n // error that triggered this log call.\n }\n }\n\n /** Log a raw chunk/frame received from a provider SDK. */\n provider(message: string, meta?: Record<string, unknown>): void {\n this.emit('debug', 'provider', message, meta)\n }\n\n /** Log a chunk/result yielded to the consumer after middleware. */\n output(message: string, meta?: Record<string, unknown>): void {\n this.emit('debug', 'output', message, meta)\n }\n\n /** Log inputs/outputs around a middleware hook invocation. Chat-only. */\n middleware(message: string, meta?: Record<string, unknown>): void {\n this.emit('debug', 'middleware', message, meta)\n }\n\n /** Log before/after a tool-call execution. Chat-only. */\n tools(message: string, meta?: Record<string, unknown>): void {\n this.emit('debug', 'tools', message, meta)\n }\n\n /** Log an agent-loop iteration marker or phase transition. Chat-only. */\n agentLoop(message: string, meta?: Record<string, unknown>): void {\n this.emit('debug', 'agentLoop', message, meta)\n }\n\n /** Log a config transform returned by a middleware `onConfig` hook. Chat-only. */\n config(message: string, meta?: Record<string, unknown>): void {\n this.emit('debug', 'config', message, meta)\n }\n\n /**\n * Log a caught error. Defaults to on even when `debug` is unspecified.\n * Uses the underlying logger's `error` level.\n */\n errors(message: string, meta?: Record<string, unknown>): void {\n this.emit('error', 'errors', message, meta)\n }\n\n /** Log outgoing request metadata before an adapter SDK call. */\n request(message: string, meta?: Record<string, unknown>): void {\n this.emit('debug', 'request', message, meta)\n }\n\n /**\n * Log a non-fatal misconfiguration or recoverable anomaly. Gated by the\n * `errors` category — on by default (and when `debug` is unspecified), so\n * silent-drop conditions surface, but still silenced by `debug: false`,\n * which honors the \"disable everything including errors\" contract. Routes to\n * the underlying logger's `warn` level.\n */\n warn(message: string, meta?: Record<string, unknown>): void {\n if (!this.categories.errors) return\n const prefixed = `⚠️ [tanstack-ai:warn] ⚠️ ${message}`\n try {\n this.logger.warn(prefixed, meta)\n } catch {\n // User-supplied logger threw; swallow so a broken logger never masks the\n // condition we were trying to surface.\n }\n }\n}\n"],"names":[],"mappings":"AAwBA,MAAM,iBAA2D;AAAA,EAC/D,SAAS;AAAA,EACT,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AACV;AAEO,MAAM,eAAe;AAAA,EAC1B,YACmB,QACA,YACjB;AAFiB,SAAA,SAAA;AACA,SAAA,aAAA;AAAA,EAChB;AAAA,EAFgB;AAAA,EACA;AAAA;AAAA,EAInB,UAAU,UAA6C;AACrD,WAAO,KAAK,WAAW,QAAQ;AAAA,EACjC;AAAA,EAEQ,KACN,OACA,UACA,SACA,MACM;AACN,QAAI,CAAC,KAAK,WAAW,QAAQ,EAAG;AAChC,UAAM,QAAQ,eAAe,QAAQ;AACrC,UAAM,WAAW,GAAG,KAAK,iBAAiB,QAAQ,KAAK,KAAK,IAAI,OAAO;AACvE,QAAI;AACF,UAAI,UAAU,QAAS,MAAK,OAAO,MAAM,UAAU,IAAI;AAAA,UAClD,MAAK,OAAO,MAAM,UAAU,IAAI;AAAA,IACvC,QAAQ;AAAA,IAGR;AAAA,EACF;AAAA;AAAA,EAGA,SAAS,SAAiB,MAAsC;AAC9D,SAAK,KAAK,SAAS,YAAY,SAAS,IAAI;AAAA,EAC9C;AAAA;AAAA,EAGA,OAAO,SAAiB,MAAsC;AAC5D,SAAK,KAAK,SAAS,UAAU,SAAS,IAAI;AAAA,EAC5C;AAAA;AAAA,EAGA,WAAW,SAAiB,MAAsC;AAChE,SAAK,KAAK,SAAS,cAAc,SAAS,IAAI;AAAA,EAChD;AAAA;AAAA,EAGA,MAAM,SAAiB,MAAsC;AAC3D,SAAK,KAAK,SAAS,SAAS,SAAS,IAAI;AAAA,EAC3C;AAAA;AAAA,EAGA,UAAU,SAAiB,MAAsC;AAC/D,SAAK,KAAK,SAAS,aAAa,SAAS,IAAI;AAAA,EAC/C;AAAA;AAAA,EAGA,OAAO,SAAiB,MAAsC;AAC5D,SAAK,KAAK,SAAS,UAAU,SAAS,IAAI;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,SAAiB,MAAsC;AAC5D,SAAK,KAAK,SAAS,UAAU,SAAS,IAAI;AAAA,EAC5C;AAAA;AAAA,EAGA,QAAQ,SAAiB,MAAsC;AAC7D,SAAK,KAAK,SAAS,WAAW,SAAS,IAAI;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,KAAK,SAAiB,MAAsC;AAC1D,QAAI,CAAC,KAAK,WAAW,OAAQ;AAC7B,UAAM,WAAW,4BAA4B,OAAO;AACpD,QAAI;AACF,WAAK,OAAO,KAAK,UAAU,IAAI;AAAA,IACjC,QAAQ;AAAA,IAGR;AAAA,EACF;AACF;"}
1
+ {"version":3,"file":"internal-logger.js","sources":["../../../src/logger/internal-logger.ts"],"sourcesContent":["import type { DebugCategories, Logger } from './types'\n\n/**\n * Fully-resolved categories map. Every flag is a definite boolean (never\n * undefined), produced by `resolveDebugOption` from a `DebugOption`.\n */\nexport type ResolvedCategories = Required<DebugCategories>\n\n/**\n * Package-internal logger wrapper used by every activity and adapter in\n * `@tanstack/ai`. Wraps a user-supplied (or default `ConsoleLogger`) `Logger`\n * plus a fully-resolved per-category map. Each category has a dedicated\n * method that no-ops when its flag is `false`, or prepends a\n * `[tanstack-ai:<category>] ` prefix and calls the underlying logger's\n * `error` (for the `errors` category) or `debug` (for everything else).\n *\n * Not exported from the package root. Adapter packages consume it via the\n * `@tanstack/ai/adapter-internals` subpath export.\n */\n/**\n * Emoji marker per category — bracketing the `[tanstack-ai:<cat>]` tag on\n * both sides makes it trivial to visually pick out a category when scanning\n * dense streaming logs.\n */\nconst CATEGORY_EMOJI: Record<keyof ResolvedCategories, string> = {\n request: '📤',\n provider: '📥',\n output: '📨',\n middleware: '🧩',\n tools: '🔧',\n agentLoop: '🔁',\n config: '⚙️',\n errors: '❌',\n sandbox: '📦',\n}\n\nexport class InternalLogger {\n constructor(\n private readonly logger: Logger,\n private readonly categories: ResolvedCategories,\n ) {}\n\n /** Whether a category is enabled. Cheap, safe to call on hot paths. */\n isEnabled(category: keyof ResolvedCategories): boolean {\n return this.categories[category]\n }\n\n private emit(\n level: 'debug' | 'error',\n category: keyof ResolvedCategories,\n message: string,\n meta?: Record<string, unknown>,\n ): void {\n if (!this.categories[category]) return\n const emoji = CATEGORY_EMOJI[category]\n const prefixed = `${emoji} [tanstack-ai:${category}] ${emoji} ${message}`\n try {\n if (level === 'error') this.logger.error(prefixed, meta)\n else this.logger.debug(prefixed, meta)\n } catch {\n // User-supplied logger threw; swallow so we never mask the original\n // error that triggered this log call.\n }\n }\n\n /** Log a raw chunk/frame received from a provider SDK. */\n provider(message: string, meta?: Record<string, unknown>): void {\n this.emit('debug', 'provider', message, meta)\n }\n\n /** Log a chunk/result yielded to the consumer after middleware. */\n output(message: string, meta?: Record<string, unknown>): void {\n this.emit('debug', 'output', message, meta)\n }\n\n /** Log inputs/outputs around a middleware hook invocation. Chat-only. */\n middleware(message: string, meta?: Record<string, unknown>): void {\n this.emit('debug', 'middleware', message, meta)\n }\n\n /** Log before/after a tool-call execution. Chat-only. */\n tools(message: string, meta?: Record<string, unknown>): void {\n this.emit('debug', 'tools', message, meta)\n }\n\n /** Log sandbox internals (watcher, file events, hook dispatch). Chat-only. */\n sandbox(message: string, meta?: Record<string, unknown>): void {\n this.emit('debug', 'sandbox', message, meta)\n }\n\n /** Log an agent-loop iteration marker or phase transition. Chat-only. */\n agentLoop(message: string, meta?: Record<string, unknown>): void {\n this.emit('debug', 'agentLoop', message, meta)\n }\n\n /** Log a config transform returned by a middleware `onConfig` hook. Chat-only. */\n config(message: string, meta?: Record<string, unknown>): void {\n this.emit('debug', 'config', message, meta)\n }\n\n /**\n * Log a caught error. Defaults to on even when `debug` is unspecified.\n * Uses the underlying logger's `error` level.\n */\n errors(message: string, meta?: Record<string, unknown>): void {\n this.emit('error', 'errors', message, meta)\n }\n\n /** Log outgoing request metadata before an adapter SDK call. */\n request(message: string, meta?: Record<string, unknown>): void {\n this.emit('debug', 'request', message, meta)\n }\n\n /**\n * Log a non-fatal misconfiguration or recoverable anomaly. Gated by the\n * `errors` category — on by default (and when `debug` is unspecified), so\n * silent-drop conditions surface, but still silenced by `debug: false`,\n * which honors the \"disable everything including errors\" contract. Routes to\n * the underlying logger's `warn` level.\n */\n warn(message: string, meta?: Record<string, unknown>): void {\n if (!this.categories.errors) return\n const prefixed = `⚠️ [tanstack-ai:warn] ⚠️ ${message}`\n try {\n this.logger.warn(prefixed, meta)\n } catch {\n // User-supplied logger threw; swallow so a broken logger never masks the\n // condition we were trying to surface.\n }\n }\n}\n"],"names":[],"mappings":"AAwBA,MAAM,iBAA2D;AAAA,EAC/D,SAAS;AAAA,EACT,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AACX;AAEO,MAAM,eAAe;AAAA,EAC1B,YACmB,QACA,YACjB;AAFiB,SAAA,SAAA;AACA,SAAA,aAAA;AAAA,EAChB;AAAA,EAFgB;AAAA,EACA;AAAA;AAAA,EAInB,UAAU,UAA6C;AACrD,WAAO,KAAK,WAAW,QAAQ;AAAA,EACjC;AAAA,EAEQ,KACN,OACA,UACA,SACA,MACM;AACN,QAAI,CAAC,KAAK,WAAW,QAAQ,EAAG;AAChC,UAAM,QAAQ,eAAe,QAAQ;AACrC,UAAM,WAAW,GAAG,KAAK,iBAAiB,QAAQ,KAAK,KAAK,IAAI,OAAO;AACvE,QAAI;AACF,UAAI,UAAU,QAAS,MAAK,OAAO,MAAM,UAAU,IAAI;AAAA,UAClD,MAAK,OAAO,MAAM,UAAU,IAAI;AAAA,IACvC,QAAQ;AAAA,IAGR;AAAA,EACF;AAAA;AAAA,EAGA,SAAS,SAAiB,MAAsC;AAC9D,SAAK,KAAK,SAAS,YAAY,SAAS,IAAI;AAAA,EAC9C;AAAA;AAAA,EAGA,OAAO,SAAiB,MAAsC;AAC5D,SAAK,KAAK,SAAS,UAAU,SAAS,IAAI;AAAA,EAC5C;AAAA;AAAA,EAGA,WAAW,SAAiB,MAAsC;AAChE,SAAK,KAAK,SAAS,cAAc,SAAS,IAAI;AAAA,EAChD;AAAA;AAAA,EAGA,MAAM,SAAiB,MAAsC;AAC3D,SAAK,KAAK,SAAS,SAAS,SAAS,IAAI;AAAA,EAC3C;AAAA;AAAA,EAGA,QAAQ,SAAiB,MAAsC;AAC7D,SAAK,KAAK,SAAS,WAAW,SAAS,IAAI;AAAA,EAC7C;AAAA;AAAA,EAGA,UAAU,SAAiB,MAAsC;AAC/D,SAAK,KAAK,SAAS,aAAa,SAAS,IAAI;AAAA,EAC/C;AAAA;AAAA,EAGA,OAAO,SAAiB,MAAsC;AAC5D,SAAK,KAAK,SAAS,UAAU,SAAS,IAAI;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,SAAiB,MAAsC;AAC5D,SAAK,KAAK,SAAS,UAAU,SAAS,IAAI;AAAA,EAC5C;AAAA;AAAA,EAGA,QAAQ,SAAiB,MAAsC;AAC7D,SAAK,KAAK,SAAS,WAAW,SAAS,IAAI;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,KAAK,SAAiB,MAAsC;AAC1D,QAAI,CAAC,KAAK,WAAW,OAAQ;AAC7B,UAAM,WAAW,4BAA4B,OAAO;AACpD,QAAI;AACF,WAAK,OAAO,KAAK,UAAU,IAAI;AAAA,IACjC,QAAQ;AAAA,IAGR;AAAA,EACF;AACF;"}
@@ -8,7 +8,8 @@ const ALL_OFF = {
8
8
  agentLoop: false,
9
9
  config: false,
10
10
  errors: false,
11
- request: false
11
+ request: false,
12
+ sandbox: false
12
13
  };
13
14
  const ALL_ON = {
14
15
  provider: true,
@@ -18,7 +19,8 @@ const ALL_ON = {
18
19
  agentLoop: true,
19
20
  config: true,
20
21
  errors: true,
21
- request: true
22
+ request: true,
23
+ sandbox: true
22
24
  };
23
25
  const errorsOnlyCategories = () => ({
24
26
  ...ALL_OFF,
@@ -32,7 +34,8 @@ const resolveCategoriesFromPartial = (partial) => ({
32
34
  agentLoop: partial.agentLoop ?? true,
33
35
  config: partial.config ?? true,
34
36
  errors: partial.errors ?? true,
35
- request: partial.request ?? true
37
+ request: partial.request ?? true,
38
+ sandbox: partial.sandbox ?? true
36
39
  });
37
40
  function resolveDebugOption(debug) {
38
41
  if (debug === void 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.js","sources":["../../../src/logger/resolve.ts"],"sourcesContent":["import { ConsoleLogger } from './console-logger'\nimport { InternalLogger } from './internal-logger'\nimport type { ResolvedCategories } from './internal-logger'\nimport type { DebugCategories, DebugConfig, DebugOption, Logger } from './types'\n\nconst ALL_OFF: ResolvedCategories = {\n provider: false,\n output: false,\n middleware: false,\n tools: false,\n agentLoop: false,\n config: false,\n errors: false,\n request: false,\n}\n\nconst ALL_ON: ResolvedCategories = {\n provider: true,\n output: true,\n middleware: true,\n tools: true,\n agentLoop: true,\n config: true,\n errors: true,\n request: true,\n}\n\nconst errorsOnlyCategories = (): ResolvedCategories => ({\n ...ALL_OFF,\n errors: true,\n})\n\nconst resolveCategoriesFromPartial = (\n partial: DebugCategories,\n): ResolvedCategories => ({\n provider: partial.provider ?? true,\n output: partial.output ?? true,\n middleware: partial.middleware ?? true,\n tools: partial.tools ?? true,\n agentLoop: partial.agentLoop ?? true,\n config: partial.config ?? true,\n errors: partial.errors ?? true,\n request: partial.request ?? true,\n})\n\n/**\n * Normalize a `DebugOption` into an `InternalLogger` ready to be threaded\n * through the library's activities and adapters. See the `DebugOption`\n * resolution table in the spec for the complete rules.\n *\n * - `undefined`: only the `errors` category is enabled; default `ConsoleLogger`.\n * - `true`: all categories enabled; default `ConsoleLogger`.\n * - `false`: all categories disabled (including `errors`); default `ConsoleLogger`.\n * - `DebugConfig`: each unspecified category defaults to `true`; an optional\n * `logger` replaces the default `ConsoleLogger`.\n */\nexport function resolveDebugOption(\n debug: DebugOption | undefined,\n): InternalLogger {\n if (debug === undefined) {\n return new InternalLogger(new ConsoleLogger(), errorsOnlyCategories())\n }\n if (debug === true) {\n return new InternalLogger(new ConsoleLogger(), ALL_ON)\n }\n if (debug === false) {\n return new InternalLogger(new ConsoleLogger(), ALL_OFF)\n }\n const { logger, ...cats }: DebugConfig = debug\n const userLogger: Logger = logger ?? new ConsoleLogger()\n return new InternalLogger(userLogger, resolveCategoriesFromPartial(cats))\n}\n"],"names":[],"mappings":";;AAKA,MAAM,UAA8B;AAAA,EAClC,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AACX;AAEA,MAAM,SAA6B;AAAA,EACjC,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AACX;AAEA,MAAM,uBAAuB,OAA2B;AAAA,EACtD,GAAG;AAAA,EACH,QAAQ;AACV;AAEA,MAAM,+BAA+B,CACnC,aACwB;AAAA,EACxB,UAAU,QAAQ,YAAY;AAAA,EAC9B,QAAQ,QAAQ,UAAU;AAAA,EAC1B,YAAY,QAAQ,cAAc;AAAA,EAClC,OAAO,QAAQ,SAAS;AAAA,EACxB,WAAW,QAAQ,aAAa;AAAA,EAChC,QAAQ,QAAQ,UAAU;AAAA,EAC1B,QAAQ,QAAQ,UAAU;AAAA,EAC1B,SAAS,QAAQ,WAAW;AAC9B;AAaO,SAAS,mBACd,OACgB;AAChB,MAAI,UAAU,QAAW;AACvB,WAAO,IAAI,eAAe,IAAI,cAAA,GAAiB,sBAAsB;AAAA,EACvE;AACA,MAAI,UAAU,MAAM;AAClB,WAAO,IAAI,eAAe,IAAI,cAAA,GAAiB,MAAM;AAAA,EACvD;AACA,MAAI,UAAU,OAAO;AACnB,WAAO,IAAI,eAAe,IAAI,cAAA,GAAiB,OAAO;AAAA,EACxD;AACA,QAAM,EAAE,QAAQ,GAAG,KAAA,IAAsB;AACzC,QAAM,aAAqB,UAAU,IAAI,cAAA;AACzC,SAAO,IAAI,eAAe,YAAY,6BAA6B,IAAI,CAAC;AAC1E;"}
1
+ {"version":3,"file":"resolve.js","sources":["../../../src/logger/resolve.ts"],"sourcesContent":["import { ConsoleLogger } from './console-logger'\nimport { InternalLogger } from './internal-logger'\nimport type { ResolvedCategories } from './internal-logger'\nimport type { DebugCategories, DebugConfig, DebugOption, Logger } from './types'\n\nconst ALL_OFF: ResolvedCategories = {\n provider: false,\n output: false,\n middleware: false,\n tools: false,\n agentLoop: false,\n config: false,\n errors: false,\n request: false,\n sandbox: false,\n}\n\nconst ALL_ON: ResolvedCategories = {\n provider: true,\n output: true,\n middleware: true,\n tools: true,\n agentLoop: true,\n config: true,\n errors: true,\n request: true,\n sandbox: true,\n}\n\nconst errorsOnlyCategories = (): ResolvedCategories => ({\n ...ALL_OFF,\n errors: true,\n})\n\nconst resolveCategoriesFromPartial = (\n partial: DebugCategories,\n): ResolvedCategories => ({\n provider: partial.provider ?? true,\n output: partial.output ?? true,\n middleware: partial.middleware ?? true,\n tools: partial.tools ?? true,\n agentLoop: partial.agentLoop ?? true,\n config: partial.config ?? true,\n errors: partial.errors ?? true,\n request: partial.request ?? true,\n sandbox: partial.sandbox ?? true,\n})\n\n/**\n * Normalize a `DebugOption` into an `InternalLogger` ready to be threaded\n * through the library's activities and adapters. See the `DebugOption`\n * resolution table in the spec for the complete rules.\n *\n * - `undefined`: only the `errors` category is enabled; default `ConsoleLogger`.\n * - `true`: all categories enabled; default `ConsoleLogger`.\n * - `false`: all categories disabled (including `errors`); default `ConsoleLogger`.\n * - `DebugConfig`: each unspecified category defaults to `true`; an optional\n * `logger` replaces the default `ConsoleLogger`.\n */\nexport function resolveDebugOption(\n debug: DebugOption | undefined,\n): InternalLogger {\n if (debug === undefined) {\n return new InternalLogger(new ConsoleLogger(), errorsOnlyCategories())\n }\n if (debug === true) {\n return new InternalLogger(new ConsoleLogger(), ALL_ON)\n }\n if (debug === false) {\n return new InternalLogger(new ConsoleLogger(), ALL_OFF)\n }\n const { logger, ...cats }: DebugConfig = debug\n const userLogger: Logger = logger ?? new ConsoleLogger()\n return new InternalLogger(userLogger, resolveCategoriesFromPartial(cats))\n}\n"],"names":[],"mappings":";;AAKA,MAAM,UAA8B;AAAA,EAClC,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AACX;AAEA,MAAM,SAA6B;AAAA,EACjC,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AACX;AAEA,MAAM,uBAAuB,OAA2B;AAAA,EACtD,GAAG;AAAA,EACH,QAAQ;AACV;AAEA,MAAM,+BAA+B,CACnC,aACwB;AAAA,EACxB,UAAU,QAAQ,YAAY;AAAA,EAC9B,QAAQ,QAAQ,UAAU;AAAA,EAC1B,YAAY,QAAQ,cAAc;AAAA,EAClC,OAAO,QAAQ,SAAS;AAAA,EACxB,WAAW,QAAQ,aAAa;AAAA,EAChC,QAAQ,QAAQ,UAAU;AAAA,EAC1B,QAAQ,QAAQ,UAAU;AAAA,EAC1B,SAAS,QAAQ,WAAW;AAAA,EAC5B,SAAS,QAAQ,WAAW;AAC9B;AAaO,SAAS,mBACd,OACgB;AAChB,MAAI,UAAU,QAAW;AACvB,WAAO,IAAI,eAAe,IAAI,cAAA,GAAiB,sBAAsB;AAAA,EACvE;AACA,MAAI,UAAU,MAAM;AAClB,WAAO,IAAI,eAAe,IAAI,cAAA,GAAiB,MAAM;AAAA,EACvD;AACA,MAAI,UAAU,OAAO;AACnB,WAAO,IAAI,eAAe,IAAI,cAAA,GAAiB,OAAO;AAAA,EACxD;AACA,QAAM,EAAE,QAAQ,GAAG,KAAA,IAAsB;AACzC,QAAM,aAAqB,UAAU,IAAI,cAAA;AACzC,SAAO,IAAI,eAAe,YAAY,6BAA6B,IAAI,CAAC;AAC1E;"}
@@ -59,6 +59,11 @@ export interface DebugCategories {
59
59
  * Outgoing call metadata (provider, model, message/tool counts) emitted before each adapter SDK call.
60
60
  */
61
61
  request?: boolean;
62
+ /**
63
+ * Sandbox internals: watcher start/stop + mechanism, file events, sandbox
64
+ * hook dispatch, ensure/bootstrap and lifecycle transitions. Chat-only.
65
+ */
66
+ sandbox?: boolean;
62
67
  }
63
68
  /**
64
69
  * Granular debug configuration combining per-category toggles with an optional custom logger. Any unspecified category flag defaults to `true`.
@@ -1,6 +1,7 @@
1
1
  import { StandardJSONSchemaV1, StandardSchemaV1 } from '@standard-schema/spec';
2
2
  import { InternalLogger } from './logger/internal-logger.js';
3
3
  import { SystemPrompt } from './system-prompts.js';
4
+ import { CapabilityContext } from './activities/chat/middleware/capabilities.js';
4
5
  import { CompletionTokensDetails, PromptTokensDetails, ProviderUsageDetails, TokenUsage, UsageCostBreakdown } from '@tanstack/ai-event-client';
5
6
  import { BaseEvent as AGUIBaseEvent, CustomEvent as AGUICustomEvent, MessagesSnapshotEvent as AGUIMessagesSnapshotEvent, ReasoningEncryptedValueEvent as AGUIReasoningEncryptedValueEvent, ReasoningEndEvent as AGUIReasoningEndEvent, ReasoningMessageContentEvent as AGUIReasoningMessageContentEvent, ReasoningMessageEndEvent as AGUIReasoningMessageEndEvent, ReasoningMessageStartEvent as AGUIReasoningMessageStartEvent, ReasoningStartEvent as AGUIReasoningStartEvent, RunErrorEvent as AGUIRunErrorEvent, RunFinishedEvent as AGUIRunFinishedEvent, RunStartedEvent as AGUIRunStartedEvent, StateDeltaEvent as AGUIStateDeltaEvent, StateSnapshotEvent as AGUIStateSnapshotEvent, StepFinishedEvent as AGUIStepFinishedEvent, StepStartedEvent as AGUIStepStartedEvent, TextMessageContentEvent as AGUITextMessageContentEvent, TextMessageEndEvent as AGUITextMessageEndEvent, TextMessageStartEvent as AGUITextMessageStartEvent, ToolCallArgsEvent as AGUIToolCallArgsEvent, ToolCallEndEvent as AGUIToolCallEndEvent, ToolCallResultEvent as AGUIToolCallResultEvent, ToolCallStartEvent as AGUIToolCallStartEvent, EventType } from '@ag-ui/core';
6
7
  /**
@@ -95,6 +96,25 @@ export interface ToolCall<TMetadata = unknown> {
95
96
  * `@tanstack/ai-gemini` sets this to `{ thoughtSignature?: string }`. */
96
97
  metadata?: TMetadata;
97
98
  }
99
+ /**
100
+ * Convention for tool-call `metadata` that marks a call as **provider-executed**
101
+ * — run by the provider's own infrastructure (e.g. Anthropic `web_search` /
102
+ * `web_fetch` server tools) rather than by the agent loop. Adapters set
103
+ * `providerExecuted: true` so that:
104
+ *
105
+ * 1. The agent loop never tries to execute the call client-side (see
106
+ * {@link isProviderExecutedToolCall} usage in the chat engine), and
107
+ * 2. The adapter can stash the raw provider result alongside it so the call —
108
+ * and its evidence — round-trips into the next turn's request.
109
+ *
110
+ * Provider-specific payloads live under a namespaced key (e.g. `anthropic`),
111
+ * keeping this convention opaque to the framework core. The index signature
112
+ * preserves those per-adapter fields.
113
+ */
114
+ export interface ProviderExecutedToolMetadata {
115
+ providerExecuted?: boolean;
116
+ [key: string]: unknown;
117
+ }
98
118
  /**
99
119
  * Supported input modality types for multimodal content.
100
120
  * - 'text': Plain text content
@@ -251,7 +271,9 @@ export interface ToolCallPart<TMetadata = unknown> {
251
271
  /** Tool execution output (for client tools or after approval) */
252
272
  output?: any;
253
273
  /** Provider-specific metadata that round-trips with the tool call.
254
- * Typed per-adapter via `TToolCallMetadata`. */
274
+ * Typed per-adapter via `TToolCallMetadata`. May follow the
275
+ * {@link ProviderExecutedToolMetadata} convention to mark provider-executed
276
+ * server tools (e.g. Anthropic `web_search`). */
255
277
  metadata?: TMetadata;
256
278
  }
257
279
  export interface ToolResultPart {
@@ -761,6 +783,23 @@ export interface TextOptions<TProviderOptionsSuperset extends Record<string, any
761
783
  * Surfaced for observability/middleware; not consumed by the LLM call.
762
784
  */
763
785
  parentRunId?: string;
786
+ /**
787
+ * Middleware capability context for this run. The engine populates it with
788
+ * the live middleware context so harness adapters that declare
789
+ * `requires: [SomeCapability]` can read provided capabilities from inside
790
+ * `chatStream` — e.g. `getSandbox(options.capabilities)`. Capabilities are
791
+ * provisioned by middleware `setup` before the adapter runs. Undefined for
792
+ * direct adapter usage outside the chat engine.
793
+ */
794
+ capabilities?: CapabilityContext;
795
+ /**
796
+ * Client approval decisions for this run, keyed by approval id. The engine
797
+ * populates this from approvals carried on the incoming messages. Harness
798
+ * adapters consult it to resolve `ask`-policy permission requests (the agent
799
+ * pauses on a risky action; the client re-runs with a decision recorded
800
+ * here). Undefined for direct adapter usage outside the chat engine.
801
+ */
802
+ approvals?: ReadonlyMap<string, boolean>;
764
803
  }
765
804
  /**
766
805
  * Re-export EventType enum from @ag-ui/core for use in event creation.
@@ -0,0 +1,18 @@
1
+ import { ProviderExecutedToolMetadata } from '../types.js';
2
+ /**
3
+ * Narrow a tool call's opaque `metadata` to the provider-executed convention.
4
+ * Returns the typed metadata when the call is provider-executed, else `null`.
5
+ *
6
+ * @see ProviderExecutedToolMetadata
7
+ */
8
+ export declare function getProviderExecutedMetadata(toolCall: {
9
+ metadata?: unknown;
10
+ } | null | undefined): ProviderExecutedToolMetadata | null;
11
+ /**
12
+ * True when a tool call was executed by the provider (e.g. Anthropic
13
+ * `web_search` / `web_fetch` server tools) rather than the agent loop. Such
14
+ * calls must not be routed to client-side execution and are already "complete".
15
+ */
16
+ export declare function isProviderExecutedToolCall(toolCall: {
17
+ metadata?: unknown;
18
+ } | null | undefined): boolean;
@@ -0,0 +1,15 @@
1
+ function getProviderExecutedMetadata(toolCall) {
2
+ const metadata = toolCall?.metadata;
3
+ if (typeof metadata === "object" && metadata !== null && metadata.providerExecuted === true) {
4
+ return metadata;
5
+ }
6
+ return null;
7
+ }
8
+ function isProviderExecutedToolCall(toolCall) {
9
+ return getProviderExecutedMetadata(toolCall) !== null;
10
+ }
11
+ export {
12
+ getProviderExecutedMetadata,
13
+ isProviderExecutedToolCall
14
+ };
15
+ //# sourceMappingURL=provider-executed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-executed.js","sources":["../../../src/utilities/provider-executed.ts"],"sourcesContent":["import type { ProviderExecutedToolMetadata } from '../types'\n\n/**\n * Narrow a tool call's opaque `metadata` to the provider-executed convention.\n * Returns the typed metadata when the call is provider-executed, else `null`.\n *\n * @see ProviderExecutedToolMetadata\n */\nexport function getProviderExecutedMetadata(\n toolCall: { metadata?: unknown } | null | undefined,\n): ProviderExecutedToolMetadata | null {\n const metadata = toolCall?.metadata\n if (\n typeof metadata === 'object' &&\n metadata !== null &&\n (metadata as ProviderExecutedToolMetadata).providerExecuted === true\n ) {\n return metadata as ProviderExecutedToolMetadata\n }\n return null\n}\n\n/**\n * True when a tool call was executed by the provider (e.g. Anthropic\n * `web_search` / `web_fetch` server tools) rather than the agent loop. Such\n * calls must not be routed to client-side execution and are already \"complete\".\n */\nexport function isProviderExecutedToolCall(\n toolCall: { metadata?: unknown } | null | undefined,\n): boolean {\n return getProviderExecutedMetadata(toolCall) !== null\n}\n"],"names":[],"mappings":"AAQO,SAAS,4BACd,UACqC;AACrC,QAAM,WAAW,UAAU;AAC3B,MACE,OAAO,aAAa,YACpB,aAAa,QACZ,SAA0C,qBAAqB,MAChE;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,2BACd,UACS;AACT,SAAO,4BAA4B,QAAQ,MAAM;AACnD;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai",
3
- "version": "0.38.0",
3
+ "version": "0.39.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",
@@ -2,13 +2,14 @@
2
2
  name: ai-core/adapter-configuration
3
3
  description: >
4
4
  Provider adapter selection and configuration: openaiText, anthropicText,
5
- geminiText, ollamaText, grokText, groqText, openRouterText, openaiCompatible. Per-model
6
- type safety with modelOptions, reasoning/thinking configuration,
5
+ geminiText, ollamaText, grokText, groqText, openRouterText, bedrockText,
6
+ openaiCompatible. Per-model type safety with modelOptions, reasoning/thinking configuration,
7
7
  runtime adapter switching, extendAdapter() for custom models, createModel().
8
8
  Generic OpenAI-compatible providers (DeepSeek, Together, Fireworks, etc.) via
9
9
  openaiCompatible({ baseURL, apiKey, models }) from @tanstack/ai-openai/compatible.
10
10
  API key env vars: OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY/GEMINI_API_KEY,
11
- XAI_API_KEY, GROQ_API_KEY, OPENROUTER_API_KEY, OLLAMA_HOST.
11
+ XAI_API_KEY, GROQ_API_KEY, OPENROUTER_API_KEY, OLLAMA_HOST,
12
+ BEDROCK_API_KEY (or AWS_BEARER_TOKEN_BEDROCK).
12
13
  type: sub-skill
13
14
  library: tanstack-ai
14
15
  library_version: '0.10.0'
@@ -78,6 +79,7 @@ The text adapter is the primary one for chat/completions:
78
79
  | Groq | `@tanstack/ai-groq` | `groqText` | `GROQ_API_KEY` |
79
80
  | OpenRouter | `@tanstack/ai-openrouter` | `openRouterText` | `OPENROUTER_API_KEY` |
80
81
  | Ollama | `@tanstack/ai-ollama` | `ollamaText` | `OLLAMA_HOST` (default: `http://localhost:11434`) |
82
+ | Bedrock | `@tanstack/ai-bedrock` | `bedrockText` | `BEDROCK_API_KEY` or `AWS_BEARER_TOKEN_BEDROCK` |
81
83
  | OpenAI-compatible | `@tanstack/ai-openai/compatible` | `openaiCompatible` / `openaiCompatibleText` | provider-specific (passed via `apiKey`) |
82
84
 
83
85
  ```typescript
@@ -89,6 +91,7 @@ import { grokText } from '@tanstack/ai-grok'
89
91
  import { groqText } from '@tanstack/ai-groq'
90
92
  import { openRouterText } from '@tanstack/ai-openrouter'
91
93
  import { ollamaText } from '@tanstack/ai-ollama'
94
+ import { bedrockText } from '@tanstack/ai-bedrock'
92
95
 
93
96
  // Model string is passed to the factory, NOT to chat()
94
97
  const adapter = openaiText('gpt-5.2')
@@ -98,6 +101,7 @@ const adapter4 = grokText('grok-4')
98
101
  const adapter5 = groqText('llama-3.3-70b-versatile')
99
102
  const adapter6 = openRouterText('anthropic/claude-sonnet-4')
100
103
  const adapter7 = ollamaText('llama3.3')
104
+ const adapter8 = bedrockText('us.anthropic.claude-3-7-sonnet-20250219-v1:0')
101
105
 
102
106
  // Optional: pass explicit API key
103
107
  const adapterWithKey = openaiText('gpt-5.2', {
@@ -105,6 +109,14 @@ const adapterWithKey = openaiText('gpt-5.2', {
105
109
  })
106
110
  ```
107
111
 
112
+ `@tanstack/ai-bedrock` (Amazon Bedrock) branches on `config.api`:
113
+
114
+ - `bedrockText(model)` or `bedrockText(model, { api: 'converse' })` (the default) — Bedrock's native Converse API via `@aws-sdk/client-bedrock-runtime` (adapter name `bedrock-converse`). Reaches the broad catalog: Claude, Nova, Llama, Mistral, DeepSeek, and more.
115
+ - `bedrockText(model, { api: 'chat' })` — OpenAI-compatible Chat Completions endpoint (adapter name `bedrock`). Open-weight models only (gpt-oss, DeepSeek V3.x, Gemma, Qwen, etc.). Does NOT reach Claude, Nova, or Llama.
116
+ - `bedrockText(model, { api: 'responses' })` — OpenAI-compatible Responses API, mantle-only (adapter name `bedrock-responses`). Currently gpt-oss family.
117
+
118
+ Use `createBedrockText(model, apiKey, config?)` to pass the key explicitly. Auth resolves from `BEDROCK_API_KEY` / `AWS_BEARER_TOKEN_BEDROCK`, or SigV4 via the standard AWS credential chain (no extra packages needed — handled by `@aws-sdk/client-bedrock-runtime`).
119
+
108
120
  ### 2. Runtime Adapter Switching
109
121
 
110
122
  Use an adapter factory map to switch providers dynamically based on user
@@ -397,15 +409,16 @@ Source: docs/migration/migration.md
397
409
  Each provider uses a specific env var name. Using the wrong one causes a
398
410
  runtime error:
399
411
 
400
- | Provider | Correct Env Var | Common Mistake |
401
- | ---------- | ------------------------------------ | ------------------------------------------------------------------------ |
402
- | OpenAI | `OPENAI_API_KEY` | |
403
- | Anthropic | `ANTHROPIC_API_KEY` | |
404
- | Gemini | `GOOGLE_API_KEY` or `GEMINI_API_KEY` | `GOOGLE_GENAI_API_KEY` (does not work) |
405
- | Grok (xAI) | `XAI_API_KEY` | `GROK_API_KEY` (does not work) |
406
- | Groq | `GROQ_API_KEY` | |
407
- | OpenRouter | `OPENROUTER_API_KEY` | |
408
- | Ollama | `OLLAMA_HOST` | No API key needed, just the host URL (default: `http://localhost:11434`) |
412
+ | Provider | Correct Env Var | Common Mistake |
413
+ | ---------- | ---------------------------------------------- | ------------------------------------------------------------------------ |
414
+ | OpenAI | `OPENAI_API_KEY` | |
415
+ | Anthropic | `ANTHROPIC_API_KEY` | |
416
+ | Gemini | `GOOGLE_API_KEY` or `GEMINI_API_KEY` | `GOOGLE_GENAI_API_KEY` (does not work) |
417
+ | Grok (xAI) | `XAI_API_KEY` | `GROK_API_KEY` (does not work) |
418
+ | Groq | `GROQ_API_KEY` | |
419
+ | OpenRouter | `OPENROUTER_API_KEY` | |
420
+ | Ollama | `OLLAMA_HOST` | No API key needed, just the host URL (default: `http://localhost:11434`) |
421
+ | Bedrock | `BEDROCK_API_KEY` / `AWS_BEARER_TOKEN_BEDROCK` | Falls back to SigV4 credentials when no API key is set |
409
422
 
410
423
  Source: adapter source code (`utils/client.ts` in each adapter package).
411
424
 
@@ -21,17 +21,23 @@ import { anthropicText } from '@tanstack/ai-anthropic'
21
21
 
22
22
  ## Key Chat Models
23
23
 
24
- | Model | Context Window | Max Output | Notes |
25
- | ------------------- | -------------- | ---------- | ------------------------------- |
26
- | `claude-opus-4-6` | 200K | 128K | Most capable, adaptive thinking |
27
- | `claude-sonnet-4-6` | 1M | 64K | Best balance, adaptive thinking |
28
- | `claude-sonnet-4-5` | 200K | 64K | Previous gen balanced |
29
- | `claude-opus-4-5` | 200K | 32K | Previous gen most capable |
30
- | `claude-haiku-4-5` | 200K | 64K | Fast and affordable |
31
- | `claude-sonnet-4` | 200K | 64K | Older balanced model |
32
- | `claude-opus-4` | 200K | 32K | Older most capable |
33
-
34
- Note: Model IDs use the format `claude-opus-4-6`, `claude-sonnet-4-6`, etc.
24
+ | Model | Context Window | Max Output | Notes |
25
+ | ------------------- | -------------- | ---------- | ------------------------------------------- |
26
+ | `claude-fable-5` | 1M | 128K | Most capable; thinking always on (adaptive) |
27
+ | `claude-sonnet-5` | 1M | 128K | Best balance; adaptive thinking by default |
28
+ | `claude-opus-4-8` | 1M | 128K | Opus tier; adaptive thinking, no sampling |
29
+ | `claude-opus-4-7` | 1M | 128K | Older Opus; adaptive thinking, no sampling |
30
+ | `claude-opus-4-6` | 200K | 128K | Older Opus, adaptive + budget thinking |
31
+ | `claude-sonnet-4-6` | 1M | 64K | Previous gen balanced, adaptive + budget |
32
+ | `claude-sonnet-4-5` | 200K | 64K | Previous gen balanced |
33
+ | `claude-opus-4-5` | 200K | 32K | Previous gen most capable |
34
+ | `claude-opus-4-1` | 200K | 64K | Deprecated (retires 2026-08-05) |
35
+ | `claude-haiku-4-5` | 200K | 64K | Fast and affordable |
36
+
37
+ Note: Model IDs use the format `claude-sonnet-5`, `claude-opus-4-8`, etc.
38
+ Retired models (Claude 3.x, Sonnet 3.7, Opus 4 / Sonnet 4) and the `-fast`
39
+ variant ids were removed — every registered id resolves against the
40
+ first-party Anthropic API.
35
41
 
36
42
  ## Provider-Specific modelOptions
37
43
 
@@ -90,11 +96,36 @@ chat({
90
96
  ANTHROPIC_API_KEY
91
97
  ```
92
98
 
99
+ ## Adaptive-era modelOptions (Sonnet 5, Fable 5, Opus 4.7/4.8)
100
+
101
+ The per-model types restrict `modelOptions` on the newest models:
102
+
103
+ ```typescript
104
+ chat({
105
+ adapter: anthropicText('claude-sonnet-5'), // or 'claude-fable-5', 'claude-opus-4-8'
106
+ messages,
107
+ modelOptions: {
108
+ // Adaptive thinking only — budget_tokens is rejected (400).
109
+ // On claude-fable-5, { type: 'disabled' } is also rejected;
110
+ // elsewhere it opts out of thinking.
111
+ thinking: { type: 'adaptive', display: 'summarized' },
112
+ // Effort lives under output_config; 'xhigh' is available on
113
+ // Opus 4.7+, Sonnet 5, and Fable 5.
114
+ output_config: { effort: 'xhigh' },
115
+ max_tokens: 64_000,
116
+ // NO temperature / top_p / top_k — the API rejects them on these models
117
+ },
118
+ })
119
+ ```
120
+
93
121
  ## Gotchas
94
122
 
95
123
  - `thinking.budget_tokens` must be >= 1024 AND less than `modelOptions.max_tokens`.
96
124
  Failing either check throws a validation error.
97
125
  - Cannot set both `top_p` and `temperature` at the same time (throws error).
98
- - `claude-3-5-haiku` and `claude-3-haiku` do NOT support extended thinking.
126
+ - `claude-sonnet-5`, `claude-fable-5`, `claude-opus-4-8`, and
127
+ `claude-opus-4-7` do NOT accept `temperature`, `top_p`, `top_k`, or
128
+ `thinking: { type: 'enabled', budget_tokens }` — adaptive thinking +
129
+ `output_config.effort` replace them (typed per model).
99
130
  - System prompts support prompt caching via `cache_control` on `TextBlockParam[]`.
100
131
  - All Claude models accept `text`, `image`, and `document` (PDF) input.
@@ -151,7 +151,7 @@ function ImageGenerator() {
151
151
 
152
152
  Supported adapters: `openaiImage` (dall-e-2, dall-e-3, gpt-image-1,
153
153
  gpt-image-1-mini, gpt-image-2) and `geminiImage` (gemini-3.1-flash-image-preview,
154
- imagen-4.0-generate-001, etc.).
154
+ gemini-3.1-flash-lite-image, imagen-4.0-generate-001, etc.).
155
155
 
156
156
  ```typescript
157
157
  import { generateImage } from '@tanstack/ai'
@@ -12,6 +12,7 @@ import { streamToText } from '../../stream-to-response.js'
12
12
  import { resolveDebugOption } from '../../logger/resolve'
13
13
  import { EventType } from '../../types'
14
14
  import { normalizeToolResult } from '../../utilities/tool-result'
15
+ import { isProviderExecutedToolCall } from '../../utilities/provider-executed'
15
16
  import { LazyToolManager } from './tools/lazy-tool-manager'
16
17
  import {
17
18
  MiddlewareAbortError,
@@ -27,6 +28,7 @@ import {
27
28
  import { maxIterations as maxIterationsStrategy } from './agent-loop-strategies'
28
29
  import { convertMessagesToModelMessages, generateMessageId } from './messages'
29
30
  import { MiddlewareRunner } from './middleware/compose'
31
+ import { provideSandboxRuntime } from './middleware/sandbox-runtime'
30
32
  import { CapabilityRegistry } from './middleware/capabilities'
31
33
  import { validateCapabilities } from './middleware/validate'
32
34
  import { MCPManager } from './mcp/manager'
@@ -67,6 +69,7 @@ import type {
67
69
  ChatMiddleware,
68
70
  ChatMiddlewareConfig,
69
71
  ChatMiddlewareContext,
72
+ SandboxFileEvent,
70
73
  StructuredOutputMiddlewareConfig,
71
74
  } from './middleware/types'
72
75
  import type { CheckCoverage } from './middleware/builder'
@@ -542,6 +545,7 @@ class TextEngine<
542
545
  // Middleware support
543
546
  private readonly middlewareRunner: MiddlewareRunner<TContext>
544
547
  private readonly middlewareCtx: ChatMiddlewareContext<TContext>
548
+ private readonly sandboxFileQueue: Array<StreamChunk> = []
545
549
  private readonly deferredPromises: Array<Promise<unknown>> = []
546
550
  private abortReason?: string
547
551
  private readonly middlewareAbortController?: AbortController
@@ -701,6 +705,21 @@ class TextEngine<
701
705
  capability[0](this.middlewareCtx, { optional: true }),
702
706
  provide: (capability, value) => capability[1](this.middlewareCtx, value),
703
707
  }
708
+
709
+ // Provide the internal SandboxRuntime capability so harness adapters and
710
+ // sandbox middleware can emit file events. The sink logs, fans the event
711
+ // out through the middleware `onFile*` hooks (fire-and-forget), and queues
712
+ // a `sandbox.file` custom chunk to be drained into the public stream.
713
+ provideSandboxRuntime(this.middlewareCtx, {
714
+ logger: this.logger,
715
+ emit: (event: SandboxFileEvent) => {
716
+ this.logger.sandbox(`file ${event.type} ${event.path}`, { event })
717
+ void this.middlewareRunner.runSandboxFile(this.middlewareCtx, event)
718
+ this.sandboxFileQueue.push(
719
+ this.createCustomEventChunk('sandbox.file', { ...event }),
720
+ )
721
+ },
722
+ })
704
723
  }
705
724
 
706
725
  /** Get the accumulated content after the chat loop completes */
@@ -1021,6 +1040,10 @@ class TextEngine<
1021
1040
  threadId: this.threadId,
1022
1041
  runId: this.runIdOverride,
1023
1042
  parentRunId: this.parentRunIdOverride,
1043
+ // Expose provided capabilities (e.g. sandbox) to harness adapters.
1044
+ capabilities: this.middlewareCtx,
1045
+ // Client approval decisions, for harness interactive-approval resolution.
1046
+ approvals: this.initialApprovals,
1024
1047
  ...(combinedSchema ? { outputSchema: combinedSchema } : {}),
1025
1048
  })) {
1026
1049
  if (this.isCancelled()) {
@@ -1105,10 +1128,16 @@ class TextEngine<
1105
1128
  await this.middlewareRunner.runOnUsage(this.middlewareCtx, chunk.usage)
1106
1129
  }
1107
1130
 
1131
+ // Drain any sandbox.file events emitted while processing this chunk.
1132
+ yield* this.drainSandboxFileQueue()
1133
+
1108
1134
  if (this.earlyTermination) {
1109
1135
  break
1110
1136
  }
1111
1137
  }
1138
+
1139
+ // Drain any remaining sandbox.file events emitted after the stream ended.
1140
+ yield* this.drainSandboxFileQueue()
1112
1141
  }
1113
1142
 
1114
1143
  private handleStreamChunk(chunk: StreamChunk): void {
@@ -1856,6 +1885,13 @@ class TextEngine<
1856
1885
  for (const message of this.messages) {
1857
1886
  if (message.role === 'assistant' && message.toolCalls) {
1858
1887
  for (const toolCall of message.toolCalls) {
1888
+ // Provider-executed tool calls (e.g. Anthropic `web_search`) were
1889
+ // already run by the provider; they carry no client result, so they
1890
+ // would otherwise look "pending" forever and the loop would try (and
1891
+ // fail) to execute them client-side. Skip them.
1892
+ if (isProviderExecutedToolCall(toolCall)) {
1893
+ continue
1894
+ }
1859
1895
  if (!completedToolIds.has(toolCall.id)) {
1860
1896
  pending.push(toolCall)
1861
1897
  }
@@ -2502,6 +2538,17 @@ class TextEngine<
2502
2538
  }
2503
2539
  }
2504
2540
 
2541
+ /**
2542
+ * Drain queued `sandbox.file` chunks (emitted via the SandboxRuntime sink)
2543
+ * through the middleware pipeline and into the public stream.
2544
+ */
2545
+ private async *drainSandboxFileQueue(): AsyncGenerator<StreamChunk> {
2546
+ while (this.sandboxFileQueue.length > 0) {
2547
+ const chunk = this.sandboxFileQueue.shift()
2548
+ if (chunk) yield* this.pipeThroughMiddleware(chunk)
2549
+ }
2550
+ }
2551
+
2505
2552
  /**
2506
2553
  * Drain an executeToolCalls async generator, yielding any CustomEvent chunks
2507
2554
  * through the middleware pipeline and returning the final ExecuteToolCallsResult.
@@ -11,6 +11,7 @@ import type {
11
11
  ErrorInfo,
12
12
  FinishInfo,
13
13
  IterationInfo,
14
+ SandboxFileEvent,
14
15
  StructuredOutputMiddlewareConfig,
15
16
  ToolCallHookContext,
16
17
  ToolPhaseCompleteInfo,
@@ -344,6 +345,39 @@ export class MiddlewareRunner<TContext = unknown> {
344
345
  return chunks
345
346
  }
346
347
 
348
+ /**
349
+ * Dispatch a sandbox file event to every middleware's `sandbox` hooks, in
350
+ * array order: the catch-all `onFile` then the type-specific hook. Errors are
351
+ * logged and swallowed so one bad hook can't break the run.
352
+ */
353
+ async runSandboxFile(
354
+ ctx: ChatMiddlewareContext<TContext>,
355
+ event: SandboxFileEvent,
356
+ ): Promise<void> {
357
+ const typed = (
358
+ {
359
+ create: 'onFileCreate',
360
+ change: 'onFileChange',
361
+ delete: 'onFileDelete',
362
+ } as const
363
+ )[event.type]
364
+ for (const mw of this.middlewares) {
365
+ const hooks = mw.sandbox
366
+ if (!hooks) continue
367
+ for (const fn of [hooks.onFile, hooks[typed]]) {
368
+ if (!fn) continue
369
+ try {
370
+ await fn(ctx, event)
371
+ } catch (error) {
372
+ this.logger.sandbox(
373
+ `hook=${typed} middleware=${mw.name ?? 'unnamed'} threw`,
374
+ { middleware: mw.name ?? 'unnamed', error },
375
+ )
376
+ }
377
+ }
378
+ }
379
+ }
380
+
347
381
  /**
348
382
  * Run onBeforeToolCall through middleware in order.
349
383
  * Returns the first non-void decision, or undefined to continue normally.
@@ -13,6 +13,8 @@ export type {
13
13
  FinishInfo,
14
14
  AbortInfo,
15
15
  ErrorInfo,
16
+ SandboxFileEvent,
17
+ ChatSandboxHooks,
16
18
  } from './types'
17
19
 
18
20
  export { MiddlewareRunner } from './compose'
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Internal runtime seam the chat engine PROVIDES so the sandbox middleware can
3
+ * surface file events without a public ctx method. `emit` runs every
4
+ * middleware's `sandbox` hooks AND emits a CUSTOM `sandbox.file` chunk into the
5
+ * stream; `logger` lets the sandbox layer log under the `sandbox` debug
6
+ * category. Consumed (optionally) by `withSandbox` in `@tanstack/ai-sandbox`.
7
+ */
8
+ import { createCapability } from './capabilities'
9
+ import type { InternalLogger } from '../../../logger/internal-logger'
10
+ import type { SandboxFileEvent } from './types'
11
+
12
+ export interface SandboxRuntime {
13
+ emit: (event: SandboxFileEvent) => void
14
+ logger: InternalLogger
15
+ }
16
+
17
+ export const SandboxRuntimeCapability =
18
+ createCapability<SandboxRuntime>()('sandbox-runtime')
19
+
20
+ export const [getSandboxRuntime, provideSandboxRuntime] =
21
+ SandboxRuntimeCapability
@@ -13,6 +13,37 @@ import type {
13
13
  CapabilityRegistry,
14
14
  } from './capabilities'
15
15
 
16
+ /** A file change observed inside a sandbox during a chat run. */
17
+ export interface SandboxFileEvent {
18
+ type: 'create' | 'change' | 'delete'
19
+ /** Absolute path inside the sandbox (under the workspace root). */
20
+ path: string
21
+ timestamp: number
22
+ }
23
+
24
+ /**
25
+ * Sandbox file-event hooks a chat middleware can declare. Fire server-side for
26
+ * every file create/change/delete observed in the sandbox during the run.
27
+ */
28
+ export interface ChatSandboxHooks<TContext = unknown> {
29
+ onFile?: (
30
+ ctx: ChatMiddlewareContext<TContext>,
31
+ e: SandboxFileEvent,
32
+ ) => void | Promise<void>
33
+ onFileCreate?: (
34
+ ctx: ChatMiddlewareContext<TContext>,
35
+ e: SandboxFileEvent,
36
+ ) => void | Promise<void>
37
+ onFileChange?: (
38
+ ctx: ChatMiddlewareContext<TContext>,
39
+ e: SandboxFileEvent,
40
+ ) => void | Promise<void>
41
+ onFileDelete?: (
42
+ ctx: ChatMiddlewareContext<TContext>,
43
+ e: SandboxFileEvent,
44
+ ) => void | Promise<void>
45
+ }
46
+
16
47
  // ===========================
17
48
  // Middleware Context
18
49
  // ===========================
@@ -539,6 +570,12 @@ export interface ChatMiddleware<TContext = unknown> {
539
570
  ctx: ChatMiddlewareContext<TContext>,
540
571
  info: ErrorInfo,
541
572
  ) => void | Promise<void>
573
+
574
+ /**
575
+ * Sandbox file-event hooks. Fire when a sandbox provided by `withSandbox` is
576
+ * active during the run and a file is created/changed/deleted. Server-side.
577
+ */
578
+ sandbox?: ChatSandboxHooks<TContext>
542
579
  }
543
580
 
544
581
  /** A `ChatMiddleware` with a permissive context — for use as a constraint. */