@tanstack/ai 0.11.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/esm/activities/chat/adapter.d.ts +12 -4
  2. package/dist/esm/activities/chat/adapter.js.map +1 -1
  3. package/dist/esm/activities/chat/index.d.ts +23 -3
  4. package/dist/esm/activities/chat/index.js +78 -17
  5. package/dist/esm/activities/chat/index.js.map +1 -1
  6. package/dist/esm/activities/chat/middleware/compose.d.ts +3 -1
  7. package/dist/esm/activities/chat/middleware/compose.js +79 -1
  8. package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
  9. package/dist/esm/activities/generateImage/index.d.ts +7 -0
  10. package/dist/esm/activities/generateImage/index.js +19 -3
  11. package/dist/esm/activities/generateImage/index.js.map +1 -1
  12. package/dist/esm/activities/generateSpeech/index.d.ts +7 -0
  13. package/dist/esm/activities/generateSpeech/index.js +21 -3
  14. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  15. package/dist/esm/activities/generateTranscription/index.d.ts +7 -0
  16. package/dist/esm/activities/generateTranscription/index.js +32 -13
  17. package/dist/esm/activities/generateTranscription/index.js.map +1 -1
  18. package/dist/esm/activities/generateVideo/index.d.ts +7 -0
  19. package/dist/esm/activities/generateVideo/index.js +49 -7
  20. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  21. package/dist/esm/activities/summarize/index.d.ts +7 -0
  22. package/dist/esm/activities/summarize/index.js +54 -19
  23. package/dist/esm/activities/summarize/index.js.map +1 -1
  24. package/dist/esm/adapter-internals.d.ts +3 -0
  25. package/dist/esm/adapter-internals.js +7 -0
  26. package/dist/esm/adapter-internals.js.map +1 -0
  27. package/dist/esm/index.d.ts +3 -0
  28. package/dist/esm/index.js +2 -0
  29. package/dist/esm/index.js.map +1 -1
  30. package/dist/esm/logger/console-logger.d.ts +11 -0
  31. package/dist/esm/logger/console-logger.js +27 -0
  32. package/dist/esm/logger/console-logger.js.map +1 -0
  33. package/dist/esm/logger/internal-logger.d.ts +33 -0
  34. package/dist/esm/logger/internal-logger.js +69 -0
  35. package/dist/esm/logger/internal-logger.js.map +1 -0
  36. package/dist/esm/logger/resolve.d.ts +14 -0
  37. package/dist/esm/logger/resolve.js +54 -0
  38. package/dist/esm/logger/resolve.js.map +1 -0
  39. package/dist/esm/logger/types.d.ts +75 -0
  40. package/dist/esm/tools/provider-tool.d.ts +21 -0
  41. package/dist/esm/types.d.ts +34 -0
  42. package/package.json +6 -2
  43. package/skills/ai-core/SKILL.md +5 -3
  44. package/skills/ai-core/debug-logging/SKILL.md +263 -0
  45. package/src/activities/chat/adapter.ts +14 -3
  46. package/src/activities/chat/index.ts +119 -24
  47. package/src/activities/chat/middleware/compose.ts +84 -1
  48. package/src/activities/generateImage/index.ts +29 -3
  49. package/src/activities/generateSpeech/index.ts +35 -3
  50. package/src/activities/generateTranscription/index.ts +45 -13
  51. package/src/activities/generateVideo/index.ts +65 -6
  52. package/src/activities/summarize/index.ts +66 -20
  53. package/src/adapter-internals.ts +7 -0
  54. package/src/index.ts +12 -0
  55. package/src/logger/console-logger.ts +49 -0
  56. package/src/logger/internal-logger.ts +107 -0
  57. package/src/logger/resolve.ts +72 -0
  58. package/src/logger/types.ts +78 -0
  59. package/src/tools/provider-tool.ts +25 -0
  60. package/src/types.ts +36 -0
@@ -10,7 +10,12 @@ export interface TextAdapterConfig {
10
10
  headers?: Record<string, string>;
11
11
  }
12
12
  /**
13
- * Options for structured output generation
13
+ * Options for structured output generation.
14
+ *
15
+ * The internal logger is threaded through `chatOptions.logger` (inherited from
16
+ * `TextOptions`). Adapter implementations must call `logger.request()` before
17
+ * SDK calls, `logger.provider()` for each chunk received, and `logger.errors()`
18
+ * in catch blocks.
14
19
  */
15
20
  export interface StructuredOutputOptions<TProviderOptions extends object> {
16
21
  /** Text options for the request */
@@ -38,8 +43,9 @@ export interface StructuredOutputResult<T = unknown> {
38
43
  * - TProviderOptions: Provider-specific options for this model (already resolved)
39
44
  * - TInputModalities: Supported input modalities for this model (already resolved)
40
45
  * - TMessageMetadata: Metadata types for content parts (already resolved)
46
+ * - TToolCapabilities: Tuple of tool-kind strings supported by this model, resolved from `supports.tools`
41
47
  */
42
- export interface TextAdapter<TModel extends string, TProviderOptions extends Record<string, any>, TInputModalities extends ReadonlyArray<Modality>, TMessageMetadataByModality extends DefaultMessageMetadataByModality> {
48
+ export interface TextAdapter<TModel extends string, TProviderOptions extends Record<string, any>, TInputModalities extends ReadonlyArray<Modality>, TMessageMetadataByModality extends DefaultMessageMetadataByModality, TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>> {
43
49
  /** Discriminator for adapter kind */
44
50
  readonly kind: 'text';
45
51
  /** Provider name identifier (e.g., 'openai', 'anthropic') */
@@ -53,6 +59,7 @@ export interface TextAdapter<TModel extends string, TProviderOptions extends Rec
53
59
  providerOptions: TProviderOptions;
54
60
  inputModalities: TInputModalities;
55
61
  messageMetadataByModality: TMessageMetadataByModality;
62
+ toolCapabilities: TToolCapabilities;
56
63
  };
57
64
  /**
58
65
  * Stream text completions from the model
@@ -72,14 +79,14 @@ export interface TextAdapter<TModel extends string, TProviderOptions extends Rec
72
79
  * A TextAdapter with any/unknown type parameters.
73
80
  * Useful as a constraint in generic functions and interfaces.
74
81
  */
75
- export type AnyTextAdapter = TextAdapter<any, any, any, any>;
82
+ export type AnyTextAdapter = TextAdapter<any, any, any, any, any>;
76
83
  /**
77
84
  * Abstract base class for text adapters.
78
85
  * Extend this class to implement a text adapter for a specific provider.
79
86
  *
80
87
  * Generic parameters match TextAdapter - all pre-resolved by the provider function.
81
88
  */
82
- export declare abstract class BaseTextAdapter<TModel extends string, TProviderOptions extends Record<string, any>, TInputModalities extends ReadonlyArray<Modality>, TMessageMetadataByModality extends DefaultMessageMetadataByModality> implements TextAdapter<TModel, TProviderOptions, TInputModalities, TMessageMetadataByModality> {
89
+ export declare abstract class BaseTextAdapter<TModel extends string, TProviderOptions extends Record<string, any>, TInputModalities extends ReadonlyArray<Modality>, TMessageMetadataByModality extends DefaultMessageMetadataByModality, TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>> implements TextAdapter<TModel, TProviderOptions, TInputModalities, TMessageMetadataByModality, TToolCapabilities> {
83
90
  readonly kind: "text";
84
91
  abstract readonly name: string;
85
92
  readonly model: TModel;
@@ -87,6 +94,7 @@ export declare abstract class BaseTextAdapter<TModel extends string, TProviderOp
87
94
  providerOptions: TProviderOptions;
88
95
  inputModalities: TInputModalities;
89
96
  messageMetadataByModality: TMessageMetadataByModality;
97
+ toolCapabilities: TToolCapabilities;
90
98
  };
91
99
  protected config: TextAdapterConfig;
92
100
  constructor(config: TextAdapterConfig | undefined, model: TModel);
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.js","sources":["../../../../src/activities/chat/adapter.ts"],"sourcesContent":["import type {\n DefaultMessageMetadataByModality,\n JSONSchema,\n Modality,\n StreamChunk,\n TextOptions,\n} from '../../types'\n\n/**\n * Configuration for adapter instances\n */\nexport interface TextAdapterConfig {\n apiKey?: string\n baseUrl?: string\n timeout?: number\n maxRetries?: number\n headers?: Record<string, string>\n}\n\n/**\n * Options for structured output generation\n */\nexport interface StructuredOutputOptions<TProviderOptions extends object> {\n /** Text options for the request */\n chatOptions: TextOptions<TProviderOptions>\n /** JSON Schema for structured output - already converted from Zod in the ai layer */\n outputSchema: JSONSchema\n}\n\n/**\n * Result from structured output generation\n */\nexport interface StructuredOutputResult<T = unknown> {\n /** The parsed data conforming to the schema */\n data: T\n /** The raw text response from the model before parsing */\n rawText: string\n}\n\n/**\n * Text adapter interface with pre-resolved generics.\n *\n * An adapter is created by a provider function: `provider('model')` → `adapter`\n * All type resolution happens at the provider call site, not in this interface.\n *\n * Generic parameters:\n * - TModel: The specific model name (e.g., 'gpt-4o')\n * - TProviderOptions: Provider-specific options for this model (already resolved)\n * - TInputModalities: Supported input modalities for this model (already resolved)\n * - TMessageMetadata: Metadata types for content parts (already resolved)\n */\nexport interface TextAdapter<\n TModel extends string,\n TProviderOptions extends Record<string, any>,\n TInputModalities extends ReadonlyArray<Modality>,\n TMessageMetadataByModality extends DefaultMessageMetadataByModality,\n> {\n /** Discriminator for adapter kind */\n readonly kind: 'text'\n /** Provider name identifier (e.g., 'openai', 'anthropic') */\n readonly name: string\n /** The model this adapter is configured for */\n readonly model: TModel\n\n /**\n * @internal Type-only properties for inference. Not assigned at runtime.\n */\n '~types': {\n providerOptions: TProviderOptions\n inputModalities: TInputModalities\n messageMetadataByModality: TMessageMetadataByModality\n }\n\n /**\n * Stream text completions from the model\n */\n chatStream: (\n options: TextOptions<TProviderOptions>,\n ) => AsyncIterable<StreamChunk>\n\n /**\n * Generate structured output using the provider's native structured output API.\n * This method uses stream: false and sends the JSON schema to the provider\n * to ensure the response conforms to the expected structure.\n *\n * @param options - Structured output options containing chat options and JSON schema\n * @returns Promise with the raw data (validation is done in the chat function)\n */\n structuredOutput: (\n options: StructuredOutputOptions<TProviderOptions>,\n ) => Promise<StructuredOutputResult<unknown>>\n}\n\n/**\n * A TextAdapter with any/unknown type parameters.\n * Useful as a constraint in generic functions and interfaces.\n */\nexport type AnyTextAdapter = TextAdapter<any, any, any, any>\n\n/**\n * Abstract base class for text adapters.\n * Extend this class to implement a text adapter for a specific provider.\n *\n * Generic parameters match TextAdapter - all pre-resolved by the provider function.\n */\nexport abstract class BaseTextAdapter<\n TModel extends string,\n TProviderOptions extends Record<string, any>,\n TInputModalities extends ReadonlyArray<Modality>,\n TMessageMetadataByModality extends DefaultMessageMetadataByModality,\n> implements TextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n TMessageMetadataByModality\n> {\n readonly kind = 'text' as const\n abstract readonly name: string\n readonly model: TModel\n\n // Type-only property - never assigned at runtime\n declare '~types': {\n providerOptions: TProviderOptions\n inputModalities: TInputModalities\n messageMetadataByModality: TMessageMetadataByModality\n }\n\n protected config: TextAdapterConfig\n\n constructor(config: TextAdapterConfig = {}, model: TModel) {\n this.config = config\n this.model = model\n }\n\n abstract chatStream(\n options: TextOptions<TProviderOptions>,\n ): AsyncIterable<StreamChunk>\n\n /**\n * Generate structured output using the provider's native structured output API.\n * Concrete implementations should override this to use provider-specific structured output.\n */\n abstract structuredOutput(\n options: StructuredOutputOptions<TProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>>\n\n protected generateId(): string {\n return `${this.name}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n }\n}\n"],"names":[],"mappings":"AAyGO,MAAe,gBAUpB;AAAA,EAcA,YAAY,SAA4B,CAAA,GAAI,OAAe;AAb3D,SAAS,OAAO;AAcd,SAAK,SAAS;AACd,SAAK,QAAQ;AAAA,EACf;AAAA,EAcU,aAAqB;AAC7B,WAAO,GAAG,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,UAAU,CAAC,CAAC;AAAA,EAC9E;AACF;"}
1
+ {"version":3,"file":"adapter.js","sources":["../../../../src/activities/chat/adapter.ts"],"sourcesContent":["import type {\n DefaultMessageMetadataByModality,\n JSONSchema,\n Modality,\n StreamChunk,\n TextOptions,\n} from '../../types'\n\n/**\n * Configuration for adapter instances\n */\nexport interface TextAdapterConfig {\n apiKey?: string\n baseUrl?: string\n timeout?: number\n maxRetries?: number\n headers?: Record<string, string>\n}\n\n/**\n * Options for structured output generation.\n *\n * The internal logger is threaded through `chatOptions.logger` (inherited from\n * `TextOptions`). Adapter implementations must call `logger.request()` before\n * SDK calls, `logger.provider()` for each chunk received, and `logger.errors()`\n * in catch blocks.\n */\nexport interface StructuredOutputOptions<TProviderOptions extends object> {\n /** Text options for the request */\n chatOptions: TextOptions<TProviderOptions>\n /** JSON Schema for structured output - already converted from Zod in the ai layer */\n outputSchema: JSONSchema\n}\n\n/**\n * Result from structured output generation\n */\nexport interface StructuredOutputResult<T = unknown> {\n /** The parsed data conforming to the schema */\n data: T\n /** The raw text response from the model before parsing */\n rawText: string\n}\n\n/**\n * Text adapter interface with pre-resolved generics.\n *\n * An adapter is created by a provider function: `provider('model')` → `adapter`\n * All type resolution happens at the provider call site, not in this interface.\n *\n * Generic parameters:\n * - TModel: The specific model name (e.g., 'gpt-4o')\n * - TProviderOptions: Provider-specific options for this model (already resolved)\n * - TInputModalities: Supported input modalities for this model (already resolved)\n * - TMessageMetadata: Metadata types for content parts (already resolved)\n * - TToolCapabilities: Tuple of tool-kind strings supported by this model, resolved from `supports.tools`\n */\nexport interface TextAdapter<\n TModel extends string,\n TProviderOptions extends Record<string, any>,\n TInputModalities extends ReadonlyArray<Modality>,\n TMessageMetadataByModality extends DefaultMessageMetadataByModality,\n TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>,\n> {\n /** Discriminator for adapter kind */\n readonly kind: 'text'\n /** Provider name identifier (e.g., 'openai', 'anthropic') */\n readonly name: string\n /** The model this adapter is configured for */\n readonly model: TModel\n\n /**\n * @internal Type-only properties for inference. Not assigned at runtime.\n */\n '~types': {\n providerOptions: TProviderOptions\n inputModalities: TInputModalities\n messageMetadataByModality: TMessageMetadataByModality\n toolCapabilities: TToolCapabilities\n }\n\n /**\n * Stream text completions from the model\n */\n chatStream: (\n options: TextOptions<TProviderOptions>,\n ) => AsyncIterable<StreamChunk>\n\n /**\n * Generate structured output using the provider's native structured output API.\n * This method uses stream: false and sends the JSON schema to the provider\n * to ensure the response conforms to the expected structure.\n *\n * @param options - Structured output options containing chat options and JSON schema\n * @returns Promise with the raw data (validation is done in the chat function)\n */\n structuredOutput: (\n options: StructuredOutputOptions<TProviderOptions>,\n ) => Promise<StructuredOutputResult<unknown>>\n}\n\n/**\n * A TextAdapter with any/unknown type parameters.\n * Useful as a constraint in generic functions and interfaces.\n */\nexport type AnyTextAdapter = TextAdapter<any, any, any, any, any>\n\n/**\n * Abstract base class for text adapters.\n * Extend this class to implement a text adapter for a specific provider.\n *\n * Generic parameters match TextAdapter - all pre-resolved by the provider function.\n */\nexport abstract class BaseTextAdapter<\n TModel extends string,\n TProviderOptions extends Record<string, any>,\n TInputModalities extends ReadonlyArray<Modality>,\n TMessageMetadataByModality extends DefaultMessageMetadataByModality,\n TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>,\n> implements TextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n TMessageMetadataByModality,\n TToolCapabilities\n> {\n readonly kind = 'text' as const\n abstract readonly name: string\n readonly model: TModel\n\n // Type-only property - never assigned at runtime\n declare '~types': {\n providerOptions: TProviderOptions\n inputModalities: TInputModalities\n messageMetadataByModality: TMessageMetadataByModality\n toolCapabilities: TToolCapabilities\n }\n\n protected config: TextAdapterConfig\n\n constructor(config: TextAdapterConfig = {}, model: TModel) {\n this.config = config\n this.model = model\n }\n\n abstract chatStream(\n options: TextOptions<TProviderOptions>,\n ): AsyncIterable<StreamChunk>\n\n /**\n * Generate structured output using the provider's native structured output API.\n * Concrete implementations should override this to use provider-specific structured output.\n */\n abstract structuredOutput(\n options: StructuredOutputOptions<TProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>>\n\n protected generateId(): string {\n return `${this.name}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n }\n}\n"],"names":[],"mappings":"AAiHO,MAAe,gBAYpB;AAAA,EAeA,YAAY,SAA4B,CAAA,GAAI,OAAe;AAd3D,SAAS,OAAO;AAed,SAAK,SAAS;AACd,SAAK,QAAQ;AAAA,EACf;AAAA,EAcU,aAAqB;AAC7B,WAAO,GAAG,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,UAAU,CAAC,CAAC;AAAA,EAC9E;AACF;"}
@@ -1,6 +1,8 @@
1
1
  import { AnyTextAdapter } from './adapter.js';
2
- import { ConstrainedModelMessage, InferSchemaType, SchemaInput, StreamChunk, TextOptions } from '../../types.js';
2
+ import { ConstrainedModelMessage, InferSchemaType, SchemaInput, StreamChunk, TextOptions, Tool } from '../../types.js';
3
3
  import { ChatMiddleware } from './middleware/types.js';
4
+ import { DebugOption } from '../../logger/types.js';
5
+ import { ProviderTool } from '../../tools/provider-tool.js';
4
6
  /** The adapter kind this activity handles */
5
7
  export declare const kind: "text";
6
8
  /**
@@ -21,8 +23,19 @@ export interface TextActivityOptions<TAdapter extends AnyTextAdapter, TSchema ex
21
23
  }>>;
22
24
  /** System prompts to prepend to the conversation */
23
25
  systemPrompts?: TextOptions['systemPrompts'];
24
- /** Tools for function calling (auto-executed when called) */
25
- tools?: TextOptions['tools'];
26
+ /**
27
+ * Tools for function calling (auto-executed when called).
28
+ *
29
+ * Accepts two shapes:
30
+ * - User-defined tools via `toolDefinition()` — plain `Tool`, always assignable.
31
+ * - Provider tools from `@tanstack/ai-<provider>/tools` (e.g. `webSearchTool`)
32
+ * — branded and type-checked against the selected model's
33
+ * `supports.tools` list. Passing an unsupported tool produces a
34
+ * compile-time error on the array element.
35
+ */
36
+ tools?: Array<(Tool & {
37
+ readonly '~toolKind'?: never;
38
+ }) | ProviderTool<string, TAdapter['~types']['toolCapabilities'][number]>>;
26
39
  /** Controls the randomness of the output. Higher values make output more random. Range: [0.0, 2.0] */
27
40
  temperature?: TextOptions['temperature'];
28
41
  /** Nucleus sampling parameter. The model considers tokens with topP probability mass. */
@@ -102,6 +115,13 @@ export interface TextActivityOptions<TAdapter extends AnyTextAdapter, TSchema ex
102
115
  * Can be used to pass request-scoped data (e.g., user ID, request context).
103
116
  */
104
117
  context?: unknown;
118
+ /**
119
+ * Enable debug logging. Pass `true` to enable all categories with the default
120
+ * console logger, `false` to silence everything, or a `DebugConfig` object for
121
+ * granular control and/or a custom `Logger`. Defaults to `undefined`, which
122
+ * means only the `errors` category is active.
123
+ */
124
+ debug?: DebugOption;
105
125
  }
106
126
  /**
107
127
  * Create typed options for the chat() function without executing.
@@ -1,6 +1,7 @@
1
1
  import { devtoolsMiddleware } from "@tanstack/ai-event-client";
2
2
  import { stripToSpecMiddleware } from "../../strip-to-spec-middleware.js";
3
3
  import { streamToText } from "../../stream-to-response.js";
4
+ import { resolveDebugOption } from "../../logger/resolve.js";
4
5
  import { LazyToolManager } from "./tools/lazy-tool-manager.js";
5
6
  import { ToolCallManager, MiddlewareAbortError, executeToolCalls } from "./tools/tool-calls.js";
6
7
  import { convertSchemaToJsonSchema, isStandardSchema, parseWithStandardSchema } from "./tools/schema-converter.js";
@@ -12,7 +13,7 @@ function createChatOptions(options) {
12
13
  return options;
13
14
  }
14
15
  class TextEngine {
15
- constructor(config) {
16
+ constructor(config, logger) {
16
17
  this.iterationCount = 0;
17
18
  this.lastFinishReason = null;
18
19
  this.streamStartTime = 0;
@@ -25,6 +26,7 @@ class TextEngine {
25
26
  this.cyclePhase = "processText";
26
27
  this.deferredPromises = [];
27
28
  this.terminalHookCalled = false;
29
+ this.logger = logger;
28
30
  this.adapter = config.adapter;
29
31
  this.params = config.params;
30
32
  this.systemPrompts = config.params.systemPrompts || [];
@@ -55,7 +57,7 @@ class TextEngine {
55
57
  ...config.middleware || [],
56
58
  stripToSpecMiddleware()
57
59
  ];
58
- this.middlewareRunner = new MiddlewareRunner(allMiddleware);
60
+ this.middlewareRunner = new MiddlewareRunner(allMiddleware, logger);
59
61
  this.middlewareAbortController = new AbortController();
60
62
  this.middlewareCtx = {
61
63
  requestId: this.requestId,
@@ -104,6 +106,9 @@ class TextEngine {
104
106
  }
105
107
  async *run() {
106
108
  this.beforeRun();
109
+ this.logger.agentLoop("run started", {
110
+ conversationId: this.middlewareCtx.conversationId
111
+ });
107
112
  try {
108
113
  this.middlewareCtx.phase = "init";
109
114
  const initialConfig = this.buildMiddlewareConfig();
@@ -121,6 +126,9 @@ class TextEngine {
121
126
  if (this.earlyTermination || this.isCancelled()) {
122
127
  return;
123
128
  }
129
+ this.logger.agentLoop(`iteration=${this.middlewareCtx.iteration}`, {
130
+ iteration: this.middlewareCtx.iteration
131
+ });
124
132
  await this.beginCycle();
125
133
  if (this.cyclePhase === "processText") {
126
134
  this.middlewareCtx.phase = "beforeModel";
@@ -137,6 +145,9 @@ class TextEngine {
137
145
  }
138
146
  this.endCycle();
139
147
  } while (this.shouldContinue());
148
+ this.logger.agentLoop("run finished", {
149
+ finishReason: this.lastFinishReason
150
+ });
140
151
  if (!this.terminalHookCalled && this.toolPhase !== "wait") {
141
152
  this.terminalHookCalled = true;
142
153
  await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
@@ -156,6 +167,10 @@ class TextEngine {
156
167
  duration: Date.now() - this.streamStartTime
157
168
  });
158
169
  } else {
170
+ this.logger.errors("chat run failed", {
171
+ error,
172
+ conversationId: this.middlewareCtx.conversationId
173
+ });
159
174
  await this.middlewareRunner.runOnError(this.middlewareCtx, {
160
175
  error,
161
176
  duration: Date.now() - this.streamStartTime
@@ -224,6 +239,16 @@ class TextEngine {
224
239
  outputSchema: tool.outputSchema ? convertSchemaToJsonSchema(tool.outputSchema) : void 0
225
240
  }));
226
241
  this.middlewareCtx.phase = "modelStream";
242
+ const providerName = this.adapter.provider ?? this.adapter.name;
243
+ this.logger.request(
244
+ `activity=chat provider=${providerName} model=${this.params.model} messages=${this.messages.length} tools=${this.tools.length} stream=true`,
245
+ {
246
+ provider: providerName,
247
+ model: this.params.model,
248
+ messageCount: this.messages.length,
249
+ toolCount: this.tools.length
250
+ }
251
+ );
227
252
  for await (const chunk of this.adapter.chatStream({
228
253
  model: this.params.model,
229
254
  messages: this.messages,
@@ -235,6 +260,7 @@ class TextEngine {
235
260
  request: this.effectiveRequest,
236
261
  modelOptions,
237
262
  systemPrompts: this.systemPrompts,
263
+ logger: this.logger,
238
264
  threadId: this.threadId,
239
265
  runId: this.runIdOverride
240
266
  })) {
@@ -248,6 +274,7 @@ class TextEngine {
248
274
  chunk
249
275
  );
250
276
  for (const outputChunk of outputChunks) {
277
+ this.logger.output(`type=${outputChunk.type}`, { chunk: outputChunk });
251
278
  yield outputChunk;
252
279
  this.middlewareCtx.chunkIndex++;
253
280
  }
@@ -357,6 +384,10 @@ class TextEngine {
357
384
  (eventName, data) => this.createCustomEventChunk(eventName, data),
358
385
  {
359
386
  onBeforeToolCall: async (toolCall, tool, args) => {
387
+ this.logger.tools(`phase=before name=${toolCall.function.name}`, {
388
+ name: toolCall.function.name,
389
+ args
390
+ });
360
391
  const hookCtx = {
361
392
  toolCall,
362
393
  tool,
@@ -370,6 +401,10 @@ class TextEngine {
370
401
  );
371
402
  },
372
403
  onAfterToolCall: async (info) => {
404
+ this.logger.tools(`phase=after name=${info.toolName}`, {
405
+ name: info.toolName,
406
+ result: info.result
407
+ });
373
408
  await this.middlewareRunner.runOnAfterToolCall(
374
409
  this.middlewareCtx,
375
410
  info
@@ -480,6 +515,10 @@ class TextEngine {
480
515
  (eventName, data) => this.createCustomEventChunk(eventName, data),
481
516
  {
482
517
  onBeforeToolCall: async (toolCall, tool, args) => {
518
+ this.logger.tools(`phase=before name=${toolCall.function.name}`, {
519
+ name: toolCall.function.name,
520
+ args
521
+ });
483
522
  const hookCtx = {
484
523
  toolCall,
485
524
  tool,
@@ -493,6 +532,10 @@ class TextEngine {
493
532
  );
494
533
  },
495
534
  onAfterToolCall: async (info) => {
535
+ this.logger.tools(`phase=after name=${info.toolName}`, {
536
+ name: info.toolName,
537
+ result: info.result
538
+ });
496
539
  await this.middlewareRunner.runOnAfterToolCall(
497
540
  this.middlewareCtx,
498
541
  info
@@ -846,14 +889,18 @@ function chat(options) {
846
889
  );
847
890
  }
848
891
  async function* runStreamingText(options) {
849
- const { adapter, middleware, context, ...textOptions } = options;
892
+ const { adapter, middleware, context, debug, ...textOptions } = options;
850
893
  const model = adapter.model;
851
- const engine = new TextEngine({
852
- adapter,
853
- params: { ...textOptions, model },
854
- middleware,
855
- context
856
- });
894
+ const logger = resolveDebugOption(debug);
895
+ const engine = new TextEngine(
896
+ {
897
+ adapter,
898
+ params: { ...textOptions, model, logger },
899
+ middleware,
900
+ context
901
+ },
902
+ logger
903
+ );
857
904
  for await (const chunk of engine.run()) {
858
905
  yield chunk;
859
906
  }
@@ -865,17 +912,21 @@ function runNonStreamingText(options) {
865
912
  return streamToText(stream);
866
913
  }
867
914
  async function runAgenticStructuredOutput(options) {
868
- const { adapter, outputSchema, middleware, context, ...textOptions } = options;
915
+ const { adapter, outputSchema, middleware, context, debug, ...textOptions } = options;
869
916
  const model = adapter.model;
917
+ const logger = resolveDebugOption(debug);
870
918
  if (!outputSchema) {
871
919
  throw new Error("outputSchema is required for structured output");
872
920
  }
873
- const engine = new TextEngine({
874
- adapter,
875
- params: { ...textOptions, model },
876
- middleware,
877
- context
878
- });
921
+ const engine = new TextEngine(
922
+ {
923
+ adapter,
924
+ params: { ...textOptions, model, logger },
925
+ middleware,
926
+ context
927
+ },
928
+ logger
929
+ );
879
930
  for await (const _chunk of engine.run()) {
880
931
  }
881
932
  const finalMessages = engine.getMessages();
@@ -888,11 +939,21 @@ async function runAgenticStructuredOutput(options) {
888
939
  if (!jsonSchema) {
889
940
  throw new Error("Failed to convert output schema to JSON Schema");
890
941
  }
942
+ const providerName = adapter.provider ?? adapter.name;
943
+ logger.request(
944
+ `activity=chat-structured provider=${providerName} model=${model} messages=${finalMessages.length}`,
945
+ {
946
+ provider: providerName,
947
+ model,
948
+ messageCount: finalMessages.length
949
+ }
950
+ );
891
951
  const result = await adapter.structuredOutput({
892
952
  chatOptions: {
893
953
  ...structuredTextOptions,
894
954
  model,
895
- messages: finalMessages
955
+ messages: finalMessages,
956
+ logger
896
957
  },
897
958
  outputSchema: jsonSchema
898
959
  });