@tanstack/ai 0.0.3 → 0.2.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 (146) hide show
  1. package/README.md +26 -0
  2. package/dist/esm/activities/chat/adapter.d.ts +100 -0
  3. package/dist/esm/activities/chat/adapter.js +14 -0
  4. package/dist/esm/activities/chat/adapter.js.map +1 -0
  5. package/dist/esm/{utilities → activities/chat}/agent-loop-strategies.d.ts +4 -4
  6. package/dist/esm/activities/chat/agent-loop-strategies.js.map +1 -0
  7. package/dist/esm/activities/chat/index.d.ts +166 -0
  8. package/dist/esm/{core/chat.js → activities/chat/index.js} +131 -33
  9. package/dist/esm/activities/chat/index.js.map +1 -0
  10. package/dist/esm/{message-converters.d.ts → activities/chat/messages.d.ts} +1 -1
  11. package/dist/esm/{message-converters.js → activities/chat/messages.js} +7 -7
  12. package/dist/esm/activities/chat/messages.js.map +1 -0
  13. package/dist/esm/activities/chat/stream/json-parser.js.map +1 -0
  14. package/dist/esm/{stream → activities/chat/stream}/message-updaters.d.ts +1 -1
  15. package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -0
  16. package/dist/esm/{stream → activities/chat/stream}/processor.d.ts +1 -1
  17. package/dist/esm/{stream → activities/chat/stream}/processor.js +1 -1
  18. package/dist/esm/activities/chat/stream/processor.js.map +1 -0
  19. package/dist/esm/activities/chat/stream/strategies.js.map +1 -0
  20. package/dist/esm/{stream → activities/chat/stream}/types.d.ts +2 -9
  21. package/dist/esm/activities/chat/tools/schema-converter.d.ts +116 -0
  22. package/dist/esm/activities/chat/tools/schema-converter.js +115 -0
  23. package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -0
  24. package/dist/esm/{tools → activities/chat/tools}/tool-calls.d.ts +1 -1
  25. package/dist/esm/{tools → activities/chat/tools}/tool-calls.js +23 -30
  26. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -0
  27. package/dist/esm/{tools → activities/chat/tools}/tool-definition.d.ts +22 -18
  28. package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -0
  29. package/dist/esm/activities/generateImage/adapter.d.ts +68 -0
  30. package/dist/esm/activities/generateImage/adapter.js +14 -0
  31. package/dist/esm/activities/generateImage/adapter.js.map +1 -0
  32. package/dist/esm/activities/generateImage/index.d.ts +89 -0
  33. package/dist/esm/activities/generateImage/index.js +15 -0
  34. package/dist/esm/activities/generateImage/index.js.map +1 -0
  35. package/dist/esm/activities/generateSpeech/adapter.d.ts +62 -0
  36. package/dist/esm/activities/generateSpeech/adapter.js +14 -0
  37. package/dist/esm/activities/generateSpeech/adapter.js.map +1 -0
  38. package/dist/esm/activities/generateSpeech/index.d.ts +69 -0
  39. package/dist/esm/activities/generateSpeech/index.js +15 -0
  40. package/dist/esm/activities/generateSpeech/index.js.map +1 -0
  41. package/dist/esm/activities/generateTranscription/adapter.d.ts +62 -0
  42. package/dist/esm/activities/generateTranscription/adapter.js +14 -0
  43. package/dist/esm/activities/generateTranscription/adapter.js.map +1 -0
  44. package/dist/esm/activities/generateTranscription/index.d.ts +71 -0
  45. package/dist/esm/activities/generateTranscription/index.js +15 -0
  46. package/dist/esm/activities/generateTranscription/index.js.map +1 -0
  47. package/dist/esm/activities/generateVideo/adapter.d.ts +80 -0
  48. package/dist/esm/activities/generateVideo/adapter.js +14 -0
  49. package/dist/esm/activities/generateVideo/adapter.js.map +1 -0
  50. package/dist/esm/activities/generateVideo/index.d.ts +136 -0
  51. package/dist/esm/activities/generateVideo/index.js +47 -0
  52. package/dist/esm/activities/generateVideo/index.js.map +1 -0
  53. package/dist/esm/activities/index.d.ts +22 -0
  54. package/dist/esm/activities/index.js +34 -0
  55. package/dist/esm/activities/index.js.map +1 -0
  56. package/dist/esm/activities/summarize/adapter.d.ts +74 -0
  57. package/dist/esm/activities/summarize/adapter.js +14 -0
  58. package/dist/esm/activities/summarize/adapter.js.map +1 -0
  59. package/dist/esm/activities/summarize/index.d.ts +100 -0
  60. package/dist/esm/activities/summarize/index.js +90 -0
  61. package/dist/esm/activities/summarize/index.js.map +1 -0
  62. package/dist/esm/event-client.d.ts +4 -18
  63. package/dist/esm/event-client.js.map +1 -1
  64. package/dist/esm/index.d.ts +16 -14
  65. package/dist/esm/index.js +29 -20
  66. package/dist/esm/stream-to-response.d.ts +95 -0
  67. package/dist/esm/stream-to-response.js +118 -0
  68. package/dist/esm/stream-to-response.js.map +1 -0
  69. package/dist/esm/types.d.ts +370 -133
  70. package/package.json +7 -6
  71. package/src/activities/chat/adapter.ts +150 -0
  72. package/src/{utilities → activities/chat}/agent-loop-strategies.ts +4 -4
  73. package/src/{core/chat.ts → activities/chat/index.ts} +435 -79
  74. package/src/{message-converters.ts → activities/chat/messages.ts} +10 -13
  75. package/src/{stream → activities/chat/stream}/message-updaters.ts +1 -1
  76. package/src/{stream → activities/chat/stream}/processor.ts +2 -5
  77. package/src/{stream → activities/chat/stream}/types.ts +8 -18
  78. package/src/activities/chat/tools/schema-converter.ts +332 -0
  79. package/src/{tools → activities/chat/tools}/tool-calls.ts +63 -44
  80. package/src/{tools → activities/chat/tools}/tool-definition.ts +51 -38
  81. package/src/activities/generateImage/adapter.ts +104 -0
  82. package/src/activities/generateImage/index.ts +162 -0
  83. package/src/activities/generateSpeech/adapter.ts +87 -0
  84. package/src/activities/generateSpeech/index.ts +122 -0
  85. package/src/activities/generateTranscription/adapter.ts +89 -0
  86. package/src/activities/generateTranscription/index.ts +132 -0
  87. package/src/activities/generateVideo/adapter.ts +116 -0
  88. package/src/activities/generateVideo/index.ts +261 -0
  89. package/src/activities/index.ts +164 -0
  90. package/src/activities/summarize/adapter.ts +107 -0
  91. package/src/activities/summarize/index.ts +287 -0
  92. package/src/event-client.ts +5 -21
  93. package/src/index.ts +60 -15
  94. package/src/stream-to-response.ts +237 -0
  95. package/src/types.ts +429 -284
  96. package/dist/esm/base-adapter.d.ts +0 -36
  97. package/dist/esm/base-adapter.js +0 -12
  98. package/dist/esm/base-adapter.js.map +0 -1
  99. package/dist/esm/core/chat-common-options.d.ts +0 -52
  100. package/dist/esm/core/chat.d.ts +0 -30
  101. package/dist/esm/core/chat.js.map +0 -1
  102. package/dist/esm/core/embedding.d.ts +0 -8
  103. package/dist/esm/core/embedding.js +0 -33
  104. package/dist/esm/core/embedding.js.map +0 -1
  105. package/dist/esm/core/summarize.d.ts +0 -9
  106. package/dist/esm/core/summarize.js +0 -36
  107. package/dist/esm/core/summarize.js.map +0 -1
  108. package/dist/esm/message-converters.js.map +0 -1
  109. package/dist/esm/stream/json-parser.js.map +0 -1
  110. package/dist/esm/stream/message-updaters.js.map +0 -1
  111. package/dist/esm/stream/processor.js.map +0 -1
  112. package/dist/esm/stream/strategies.js.map +0 -1
  113. package/dist/esm/tools/tool-calls.js.map +0 -1
  114. package/dist/esm/tools/tool-definition.js.map +0 -1
  115. package/dist/esm/tools/zod-converter.d.ts +0 -30
  116. package/dist/esm/tools/zod-converter.js +0 -36
  117. package/dist/esm/tools/zod-converter.js.map +0 -1
  118. package/dist/esm/utilities/agent-loop-strategies.js.map +0 -1
  119. package/dist/esm/utilities/chat-options.d.ts +0 -6
  120. package/dist/esm/utilities/chat-options.js +0 -7
  121. package/dist/esm/utilities/chat-options.js.map +0 -1
  122. package/dist/esm/utilities/messages.d.ts +0 -30
  123. package/dist/esm/utilities/messages.js +0 -7
  124. package/dist/esm/utilities/messages.js.map +0 -1
  125. package/dist/esm/utilities/stream-to-response.d.ts +0 -48
  126. package/dist/esm/utilities/stream-to-response.js +0 -62
  127. package/dist/esm/utilities/stream-to-response.js.map +0 -1
  128. package/src/base-adapter.ts +0 -86
  129. package/src/core/chat-common-options.ts +0 -55
  130. package/src/core/embedding.ts +0 -54
  131. package/src/core/summarize.ts +0 -56
  132. package/src/tools/zod-converter.ts +0 -85
  133. package/src/utilities/chat-options.ts +0 -35
  134. package/src/utilities/messages.ts +0 -63
  135. package/src/utilities/stream-to-response.ts +0 -116
  136. /package/dist/esm/{utilities → activities/chat}/agent-loop-strategies.js +0 -0
  137. /package/dist/esm/{stream → activities/chat/stream}/index.d.ts +0 -0
  138. /package/dist/esm/{stream → activities/chat/stream}/json-parser.d.ts +0 -0
  139. /package/dist/esm/{stream → activities/chat/stream}/json-parser.js +0 -0
  140. /package/dist/esm/{stream → activities/chat/stream}/message-updaters.js +0 -0
  141. /package/dist/esm/{stream → activities/chat/stream}/strategies.d.ts +0 -0
  142. /package/dist/esm/{stream → activities/chat/stream}/strategies.js +0 -0
  143. /package/dist/esm/{tools → activities/chat/tools}/tool-definition.js +0 -0
  144. /package/src/{stream → activities/chat/stream}/index.ts +0 -0
  145. /package/src/{stream → activities/chat/stream}/json-parser.ts +0 -0
  146. /package/src/{stream → activities/chat/stream}/strategies.ts +0 -0
@@ -1,26 +1,186 @@
1
- import { aiEventClient } from '../event-client.js'
2
- import { ToolCallManager, executeToolCalls } from '../tools/tool-calls'
3
- import { maxIterations as maxIterationsStrategy } from '../utilities/agent-loop-strategies'
1
+ /**
2
+ * Text Activity
3
+ *
4
+ * Handles agentic text generation, one-shot text generation, and agentic structured output.
5
+ * This is a self-contained module with implementation, types, and JSDoc.
6
+ */
7
+
8
+ import { aiEventClient } from '../../event-client.js'
9
+ import { streamToText } from '../../stream-to-response.js'
10
+ import { ToolCallManager, executeToolCalls } from './tools/tool-calls'
11
+ import {
12
+ convertSchemaToJsonSchema,
13
+ isStandardSchema,
14
+ parseWithStandardSchema,
15
+ } from './tools/schema-converter'
16
+ import { maxIterations as maxIterationsStrategy } from './agent-loop-strategies'
4
17
  import type {
5
18
  ApprovalRequest,
6
19
  ClientToolRequest,
7
20
  ToolResult,
8
- } from '../tools/tool-calls'
21
+ } from './tools/tool-calls'
22
+ import type { AnyTextAdapter } from './adapter'
9
23
  import type {
10
- AIAdapter,
11
24
  AgentLoopStrategy,
12
- ChatOptions,
13
- ChatStreamOptionsForModel,
25
+ ConstrainedModelMessage,
14
26
  DoneStreamChunk,
27
+ InferSchemaType,
15
28
  ModelMessage,
29
+ SchemaInput,
16
30
  StreamChunk,
31
+ TextOptions,
17
32
  Tool,
18
33
  ToolCall,
19
- } from '../types'
34
+ } from '../../types'
35
+
36
+ // ===========================
37
+ // Activity Kind
38
+ // ===========================
39
+
40
+ /** The adapter kind this activity handles */
41
+ export const kind = 'text' as const
42
+
43
+ // ===========================
44
+ // Activity Options Type
45
+ // ===========================
46
+
47
+ /**
48
+ * Options for the text activity.
49
+ * Types are extracted directly from the adapter (which has pre-resolved generics).
50
+ *
51
+ * @template TAdapter - The text adapter type (created by a provider function)
52
+ * @template TSchema - Optional Standard Schema for structured output
53
+ * @template TStream - Whether to stream the output (default: true)
54
+ */
55
+ export interface TextActivityOptions<
56
+ TAdapter extends AnyTextAdapter,
57
+ TSchema extends SchemaInput | undefined,
58
+ TStream extends boolean,
59
+ > {
60
+ /** The text adapter to use (created by a provider function like openaiText('gpt-4o')) */
61
+ adapter: TAdapter
62
+ /** Conversation messages - content types are constrained by the adapter's input modalities and metadata */
63
+ messages?: Array<
64
+ ConstrainedModelMessage<{
65
+ inputModalities: TAdapter['~types']['inputModalities']
66
+ messageMetadataByModality: TAdapter['~types']['messageMetadataByModality']
67
+ }>
68
+ >
69
+ /** System prompts to prepend to the conversation */
70
+ systemPrompts?: TextOptions['systemPrompts']
71
+ /** Tools for function calling (auto-executed when called) */
72
+ tools?: TextOptions['tools']
73
+ /** Controls the randomness of the output. Higher values make output more random. Range: [0.0, 2.0] */
74
+ temperature?: TextOptions['temperature']
75
+ /** Nucleus sampling parameter. The model considers tokens with topP probability mass. */
76
+ topP?: TextOptions['topP']
77
+ /** The maximum number of tokens to generate in the response. */
78
+ maxTokens?: TextOptions['maxTokens']
79
+ /** Additional metadata to attach to the request. */
80
+ metadata?: TextOptions['metadata']
81
+ /** Model-specific provider options (type comes from adapter) */
82
+ modelOptions?: TAdapter['~types']['providerOptions']
83
+ /** AbortController for cancellation */
84
+ abortController?: TextOptions['abortController']
85
+ /** Strategy for controlling the agent loop */
86
+ agentLoopStrategy?: TextOptions['agentLoopStrategy']
87
+ /** Unique conversation identifier for tracking */
88
+ conversationId?: TextOptions['conversationId']
89
+ /**
90
+ * Optional Standard Schema for structured output.
91
+ * When provided, the activity will:
92
+ * 1. Run the full agentic loop (executing tools as needed)
93
+ * 2. Once complete, return a Promise with the parsed output matching the schema
94
+ *
95
+ * Supports any Standard Schema compliant library (Zod v4+, ArkType, Valibot, etc.)
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * const result = await chat({
100
+ * adapter: openaiText('gpt-4o'),
101
+ * messages: [{ role: 'user', content: 'Generate a person' }],
102
+ * outputSchema: z.object({ name: z.string(), age: z.number() })
103
+ * })
104
+ * // result is { name: string, age: number }
105
+ * ```
106
+ */
107
+ outputSchema?: TSchema
108
+ /**
109
+ * Whether to stream the text result.
110
+ * When true (default), returns an AsyncIterable<StreamChunk> for streaming output.
111
+ * When false, returns a Promise<string> with the collected text content.
112
+ *
113
+ * Note: If outputSchema is provided, this option is ignored and the result
114
+ * is always a Promise<InferSchemaType<TSchema>>.
115
+ *
116
+ * @default true
117
+ *
118
+ * @example Non-streaming text
119
+ * ```ts
120
+ * const text = await chat({
121
+ * adapter: openaiText('gpt-4o'),
122
+ * messages: [{ role: 'user', content: 'Hello!' }],
123
+ * stream: false
124
+ * })
125
+ * // text is a string with the full response
126
+ * ```
127
+ */
128
+ stream?: TStream
129
+ }
130
+
131
+ // ===========================
132
+ // Chat Options Helper
133
+ // ===========================
134
+
135
+ /**
136
+ * Create typed options for the chat() function without executing.
137
+ * This is useful for pre-defining configurations with full type inference.
138
+ *
139
+ * @example
140
+ * ```ts
141
+ * const chatOptions = createChatOptions({
142
+ * adapter: anthropicText('claude-sonnet-4-5'),
143
+ * })
144
+ *
145
+ * const stream = chat({ ...chatOptions, messages })
146
+ * ```
147
+ */
148
+ export function createChatOptions<
149
+ TAdapter extends AnyTextAdapter,
150
+ TSchema extends SchemaInput | undefined = undefined,
151
+ TStream extends boolean = true,
152
+ >(
153
+ options: TextActivityOptions<TAdapter, TSchema, TStream>,
154
+ ): TextActivityOptions<TAdapter, TSchema, TStream> {
155
+ return options
156
+ }
157
+
158
+ // ===========================
159
+ // Activity Result Type
160
+ // ===========================
20
161
 
21
- interface ChatEngineConfig<
22
- TAdapter extends AIAdapter<any, any, any, any>,
23
- TParams extends ChatOptions<any, any> = ChatOptions<any>,
162
+ /**
163
+ * Result type for the text activity.
164
+ * - If outputSchema is provided: Promise<InferSchemaType<TSchema>>
165
+ * - If stream is false: Promise<string>
166
+ * - Otherwise (stream is true, default): AsyncIterable<StreamChunk>
167
+ */
168
+ export type TextActivityResult<
169
+ TSchema extends SchemaInput | undefined,
170
+ TStream extends boolean = true,
171
+ > = TSchema extends SchemaInput
172
+ ? Promise<InferSchemaType<TSchema>>
173
+ : TStream extends false
174
+ ? Promise<string>
175
+ : AsyncIterable<StreamChunk>
176
+
177
+ // ===========================
178
+ // ChatEngine Implementation
179
+ // ===========================
180
+
181
+ interface TextEngineConfig<
182
+ TAdapter extends AnyTextAdapter,
183
+ TParams extends TextOptions<any, any> = TextOptions<any>,
24
184
  > {
25
185
  adapter: TAdapter
26
186
  systemPrompts?: Array<string>
@@ -28,11 +188,11 @@ interface ChatEngineConfig<
28
188
  }
29
189
 
30
190
  type ToolPhaseResult = 'continue' | 'stop' | 'wait'
31
- type CyclePhase = 'processChat' | 'executeToolCalls'
191
+ type CyclePhase = 'processText' | 'executeToolCalls'
32
192
 
33
- class ChatEngine<
34
- TAdapter extends AIAdapter<any, any, any, any>,
35
- TParams extends ChatOptions<any, any> = ChatOptions<any>,
193
+ class TextEngine<
194
+ TAdapter extends AnyTextAdapter,
195
+ TParams extends TextOptions<any, any> = TextOptions<any>,
36
196
  > {
37
197
  private readonly adapter: TAdapter
38
198
  private readonly params: TParams
@@ -57,9 +217,9 @@ class ChatEngine<
57
217
  private shouldEmitStreamEnd = true
58
218
  private earlyTermination = false
59
219
  private toolPhase: ToolPhaseResult = 'continue'
60
- private cyclePhase: CyclePhase = 'processChat'
220
+ private cyclePhase: CyclePhase = 'processText'
61
221
 
62
- constructor(config: ChatEngineConfig<TAdapter, TParams>) {
222
+ constructor(config: TextEngineConfig<TAdapter, TParams>) {
63
223
  this.adapter = config.adapter
64
224
  this.params = config.params
65
225
  this.systemPrompts = config.params.systemPrompts || []
@@ -77,8 +237,18 @@ class ChatEngine<
77
237
  this.effectiveSignal = config.params.abortController?.signal
78
238
  }
79
239
 
80
- async *chat(): AsyncGenerator<StreamChunk> {
81
- this.beforeChat()
240
+ /** Get the accumulated content after the chat loop completes */
241
+ getAccumulatedContent(): string {
242
+ return this.accumulatedContent
243
+ }
244
+
245
+ /** Get the final messages array after the chat loop completes */
246
+ getMessages(): Array<ModelMessage> {
247
+ return this.messages
248
+ }
249
+
250
+ async *run(): AsyncGenerator<StreamChunk> {
251
+ this.beforeRun()
82
252
 
83
253
  try {
84
254
  const pendingPhase = yield* this.checkForPendingToolCalls()
@@ -93,7 +263,7 @@ class ChatEngine<
93
263
 
94
264
  this.beginCycle()
95
265
 
96
- if (this.cyclePhase === 'processChat') {
266
+ if (this.cyclePhase === 'processText') {
97
267
  yield* this.streamModelResponse()
98
268
  } else {
99
269
  yield* this.processToolCalls()
@@ -102,16 +272,31 @@ class ChatEngine<
102
272
  this.endCycle()
103
273
  } while (this.shouldContinue())
104
274
  } finally {
105
- this.afterChat()
275
+ this.afterRun()
106
276
  }
107
277
  }
108
278
 
109
- private beforeChat(): void {
279
+ private beforeRun(): void {
110
280
  this.streamStartTime = Date.now()
111
- const { model, tools, options, providerOptions, conversationId } =
112
- this.params
113
-
114
- aiEventClient.emit('chat:started', {
281
+ const {
282
+ model,
283
+ tools,
284
+ temperature,
285
+ topP,
286
+ maxTokens,
287
+ metadata,
288
+ modelOptions,
289
+ conversationId,
290
+ } = this.params
291
+
292
+ // Gather flattened options into an object for event emission
293
+ const options: Record<string, unknown> = {}
294
+ if (temperature !== undefined) options.temperature = temperature
295
+ if (topP !== undefined) options.topP = topP
296
+ if (maxTokens !== undefined) options.maxTokens = maxTokens
297
+ if (metadata !== undefined) options.metadata = metadata
298
+
299
+ aiEventClient.emit('text:started', {
115
300
  requestId: this.requestId,
116
301
  streamId: this.streamId,
117
302
  model: model,
@@ -122,8 +307,8 @@ class ChatEngine<
122
307
  timestamp: Date.now(),
123
308
  clientId: conversationId,
124
309
  toolNames: tools?.map((t) => t.name),
125
- options: options as Record<string, unknown> | undefined,
126
- providerOptions: providerOptions as Record<string, unknown> | undefined,
310
+ options: Object.keys(options).length > 0 ? options : undefined,
311
+ modelOptions: modelOptions as Record<string, unknown> | undefined,
127
312
  })
128
313
 
129
314
  aiEventClient.emit('stream:started', {
@@ -134,15 +319,15 @@ class ChatEngine<
134
319
  })
135
320
  }
136
321
 
137
- private afterChat(): void {
322
+ private afterRun(): void {
138
323
  if (!this.shouldEmitStreamEnd) {
139
324
  return
140
325
  }
141
326
 
142
327
  const now = Date.now()
143
328
 
144
- // Emit chat:completed with final state
145
- aiEventClient.emit('chat:completed', {
329
+ // Emit text:completed with final state
330
+ aiEventClient.emit('text:completed', {
146
331
  requestId: this.requestId,
147
332
  streamId: this.streamId,
148
333
  model: this.params.model,
@@ -163,18 +348,18 @@ class ChatEngine<
163
348
  }
164
349
 
165
350
  private beginCycle(): void {
166
- if (this.cyclePhase === 'processChat') {
351
+ if (this.cyclePhase === 'processText') {
167
352
  this.beginIteration()
168
353
  }
169
354
  }
170
355
 
171
356
  private endCycle(): void {
172
- if (this.cyclePhase === 'processChat') {
357
+ if (this.cyclePhase === 'processText') {
173
358
  this.cyclePhase = 'executeToolCalls'
174
359
  return
175
360
  }
176
361
 
177
- this.cyclePhase = 'processChat'
362
+ this.cyclePhase = 'processText'
178
363
  this.iterationCount++
179
364
  }
180
365
 
@@ -185,17 +370,30 @@ class ChatEngine<
185
370
  }
186
371
 
187
372
  private async *streamModelResponse(): AsyncGenerator<StreamChunk> {
188
- const adapterOptions = this.params.options || {}
189
- const providerOptions = this.params.providerOptions
373
+ const { temperature, topP, maxTokens, metadata, modelOptions } = this.params
190
374
  const tools = this.params.tools
191
375
 
376
+ // Convert tool schemas to JSON Schema before passing to adapter
377
+ const toolsWithJsonSchemas = tools?.map((tool) => ({
378
+ ...tool,
379
+ inputSchema: tool.inputSchema
380
+ ? convertSchemaToJsonSchema(tool.inputSchema)
381
+ : undefined,
382
+ outputSchema: tool.outputSchema
383
+ ? convertSchemaToJsonSchema(tool.outputSchema)
384
+ : undefined,
385
+ }))
386
+
192
387
  for await (const chunk of this.adapter.chatStream({
193
388
  model: this.params.model,
194
389
  messages: this.messages,
195
- tools,
196
- options: adapterOptions,
390
+ tools: toolsWithJsonSchemas,
391
+ temperature,
392
+ topP,
393
+ maxTokens,
394
+ metadata,
197
395
  request: this.effectiveRequest,
198
- providerOptions,
396
+ modelOptions,
199
397
  systemPrompts: this.systemPrompts,
200
398
  })) {
201
399
  if (this.isAborted()) {
@@ -365,7 +563,7 @@ class ChatEngine<
365
563
 
366
564
  const doneChunk = this.createSyntheticDoneChunk()
367
565
 
368
- aiEventClient.emit('chat:iteration', {
566
+ aiEventClient.emit('text:iteration', {
369
567
  requestId: this.requestId,
370
568
  streamId: this.streamId,
371
569
  iterationNumber: this.iterationCount + 1,
@@ -431,7 +629,7 @@ class ChatEngine<
431
629
  return
432
630
  }
433
631
 
434
- aiEventClient.emit('chat:iteration', {
632
+ aiEventClient.emit('text:iteration', {
435
633
  requestId: this.requestId,
436
634
  streamId: this.streamId,
437
635
  iterationNumber: this.iterationCount + 1,
@@ -710,62 +908,220 @@ class ChatEngine<
710
908
  }
711
909
  }
712
910
 
911
+ // ===========================
912
+ // Activity Implementation
913
+ // ===========================
914
+
713
915
  /**
714
- * Standalone chat streaming function with type inference from adapter
715
- * Returns an async iterable of StreamChunks for streaming responses
716
- * Includes automatic tool execution loop
916
+ * Text activity - handles agentic text generation, one-shot text generation, and agentic structured output.
717
917
  *
718
- * @param options Chat options
719
- * @param options.adapter - AI adapter instance to use
720
- * @param options.model - Model name (autocompletes based on adapter)
721
- * @param options.messages - Conversation messages
722
- * @param options.tools - Optional tools for function calling (auto-executed)
723
- * @param options.agentLoopStrategy - Optional strategy for controlling tool execution loop
918
+ * This activity supports four modes:
919
+ * 1. **Streaming agentic text**: Stream responses with automatic tool execution
920
+ * 2. **Streaming one-shot text**: Simple streaming request/response without tools
921
+ * 3. **Non-streaming text**: Returns collected text as a string (stream: false)
922
+ * 4. **Agentic structured output**: Run tools, then return structured data
724
923
  *
725
- * @example
726
- * ```typescript
727
- * const stream = chat({
728
- * adapter: openai(),
729
- * model: 'gpt-4o',
730
- * messages: [{ role: 'user', content: 'Hello!' }],
731
- * tools: [weatherTool], // Optional: auto-executed when called
732
- * });
924
+ * @example Full agentic text (streaming with tools)
925
+ * ```ts
926
+ * import { chat } from '@tanstack/ai'
927
+ * import { openaiText } from '@tanstack/ai-openai'
733
928
  *
734
- * for await (const chunk of stream) {
929
+ * for await (const chunk of chat({
930
+ * adapter: openaiText('gpt-4o'),
931
+ * messages: [{ role: 'user', content: 'What is the weather?' }],
932
+ * tools: [weatherTool]
933
+ * })) {
735
934
  * if (chunk.type === 'content') {
736
- * console.log(chunk.delta);
935
+ * console.log(chunk.delta)
737
936
  * }
738
937
  * }
739
938
  * ```
939
+ *
940
+ * @example One-shot text (streaming without tools)
941
+ * ```ts
942
+ * for await (const chunk of chat({
943
+ * adapter: openaiText('gpt-4o'),
944
+ * messages: [{ role: 'user', content: 'Hello!' }]
945
+ * })) {
946
+ * console.log(chunk)
947
+ * }
948
+ * ```
949
+ *
950
+ * @example Non-streaming text (stream: false)
951
+ * ```ts
952
+ * const text = await chat({
953
+ * adapter: openaiText('gpt-4o'),
954
+ * messages: [{ role: 'user', content: 'Hello!' }],
955
+ * stream: false
956
+ * })
957
+ * // text is a string with the full response
958
+ * ```
959
+ *
960
+ * @example Agentic structured output (tools + structured response)
961
+ * ```ts
962
+ * import { z } from 'zod'
963
+ *
964
+ * const result = await chat({
965
+ * adapter: openaiText('gpt-4o'),
966
+ * messages: [{ role: 'user', content: 'Research and summarize the topic' }],
967
+ * tools: [researchTool, analyzeTool],
968
+ * outputSchema: z.object({
969
+ * summary: z.string(),
970
+ * keyPoints: z.array(z.string())
971
+ * })
972
+ * })
973
+ * // result is { summary: string, keyPoints: string[] }
974
+ * ```
740
975
  */
741
- export async function* chat<
742
- TAdapter extends AIAdapter<any, any, any, any, any, any>,
743
- const TModel extends TAdapter extends AIAdapter<
744
- infer Models,
745
- any,
746
- any,
747
- any,
748
- any,
749
- any
750
- >
751
- ? Models[number]
752
- : string,
976
+ export function chat<
977
+ TAdapter extends AnyTextAdapter,
978
+ TSchema extends SchemaInput | undefined = undefined,
979
+ TStream extends boolean = true,
753
980
  >(
754
- options: ChatStreamOptionsForModel<TAdapter, TModel>,
981
+ options: TextActivityOptions<TAdapter, TSchema, TStream>,
982
+ ): TextActivityResult<TSchema, TStream> {
983
+ const { outputSchema, stream } = options
984
+
985
+ // If outputSchema is provided, run agentic structured output
986
+ if (outputSchema) {
987
+ return runAgenticStructuredOutput(
988
+ options as unknown as TextActivityOptions<
989
+ AnyTextAdapter,
990
+ SchemaInput,
991
+ boolean
992
+ >,
993
+ ) as TextActivityResult<TSchema, TStream>
994
+ }
995
+
996
+ // If stream is explicitly false, run non-streaming text
997
+ if (stream === false) {
998
+ return runNonStreamingText(
999
+ options as unknown as TextActivityOptions<
1000
+ AnyTextAdapter,
1001
+ undefined,
1002
+ false
1003
+ >,
1004
+ ) as TextActivityResult<TSchema, TStream>
1005
+ }
1006
+
1007
+ // Otherwise, run streaming text (default)
1008
+ return runStreamingText(
1009
+ options as unknown as TextActivityOptions<AnyTextAdapter, undefined, true>,
1010
+ ) as TextActivityResult<TSchema, TStream>
1011
+ }
1012
+
1013
+ /**
1014
+ * Run streaming text (agentic or one-shot depending on tools)
1015
+ */
1016
+ async function* runStreamingText(
1017
+ options: TextActivityOptions<AnyTextAdapter, undefined, true>,
755
1018
  ): AsyncIterable<StreamChunk> {
756
- const { adapter, ...chatOptions } = options
1019
+ const { adapter, ...textOptions } = options
1020
+ const model = adapter.model
757
1021
 
758
- const engine = new ChatEngine({
1022
+ const engine = new TextEngine({
759
1023
  adapter,
760
- params: chatOptions as ChatOptions<
761
- string,
1024
+ params: { ...textOptions, model } as TextOptions<
762
1025
  Record<string, any>,
763
- undefined,
764
1026
  Record<string, any>
765
1027
  >,
766
1028
  })
767
1029
 
768
- for await (const chunk of engine.chat()) {
1030
+ for await (const chunk of engine.run()) {
769
1031
  yield chunk
770
1032
  }
771
1033
  }
1034
+
1035
+ /**
1036
+ * Run non-streaming text - collects all content and returns as a string.
1037
+ * Runs the full agentic loop (if tools are provided) but returns collected text.
1038
+ */
1039
+ function runNonStreamingText(
1040
+ options: TextActivityOptions<AnyTextAdapter, undefined, false>,
1041
+ ): Promise<string> {
1042
+ // Run the streaming text and collect all text using streamToText
1043
+ const stream = runStreamingText(
1044
+ options as unknown as TextActivityOptions<AnyTextAdapter, undefined, true>,
1045
+ )
1046
+
1047
+ return streamToText(stream)
1048
+ }
1049
+
1050
+ /**
1051
+ * Run agentic structured output:
1052
+ * 1. Execute the full agentic loop (with tools)
1053
+ * 2. Once complete, call adapter.structuredOutput with the conversation context
1054
+ * 3. Validate and return the structured result
1055
+ */
1056
+ async function runAgenticStructuredOutput<TSchema extends SchemaInput>(
1057
+ options: TextActivityOptions<AnyTextAdapter, TSchema, boolean>,
1058
+ ): Promise<InferSchemaType<TSchema>> {
1059
+ const { adapter, outputSchema, ...textOptions } = options
1060
+ const model = adapter.model
1061
+
1062
+ if (!outputSchema) {
1063
+ throw new Error('outputSchema is required for structured output')
1064
+ }
1065
+
1066
+ // Create the engine and run the agentic loop
1067
+ const engine = new TextEngine({
1068
+ adapter,
1069
+ params: { ...textOptions, model } as TextOptions<
1070
+ Record<string, unknown>,
1071
+ Record<string, unknown>
1072
+ >,
1073
+ })
1074
+
1075
+ // Consume the stream to run the agentic loop
1076
+ for await (const _chunk of engine.run()) {
1077
+ // Just consume the stream to execute the agentic loop
1078
+ }
1079
+
1080
+ // Get the final messages from the engine (includes tool results)
1081
+ const finalMessages = engine.getMessages()
1082
+
1083
+ // Build text options for structured output, excluding tools since
1084
+ // the agentic loop is complete and we only need the final response
1085
+ const {
1086
+ tools: _tools,
1087
+ agentLoopStrategy: _als,
1088
+ ...structuredTextOptions
1089
+ } = textOptions
1090
+
1091
+ // Convert the schema to JSON Schema before passing to the adapter
1092
+ const jsonSchema = convertSchemaToJsonSchema(outputSchema)
1093
+ if (!jsonSchema) {
1094
+ throw new Error('Failed to convert output schema to JSON Schema')
1095
+ }
1096
+
1097
+ // Call the adapter's structured output method with the conversation context
1098
+ // The adapter receives JSON Schema and can apply vendor-specific patches
1099
+ const result = await adapter.structuredOutput({
1100
+ chatOptions: {
1101
+ ...structuredTextOptions,
1102
+ model,
1103
+ messages: finalMessages,
1104
+ },
1105
+ outputSchema: jsonSchema,
1106
+ })
1107
+
1108
+ // Validate the result against the schema if it's a Standard Schema
1109
+ if (isStandardSchema(outputSchema)) {
1110
+ return parseWithStandardSchema<InferSchemaType<TSchema>>(
1111
+ outputSchema,
1112
+ result.data,
1113
+ )
1114
+ }
1115
+
1116
+ // For plain JSON Schema, return the data as-is
1117
+ return result.data as InferSchemaType<TSchema>
1118
+ }
1119
+
1120
+ // Re-export adapter types
1121
+ export type {
1122
+ TextAdapter,
1123
+ TextAdapterConfig,
1124
+ StructuredOutputOptions,
1125
+ StructuredOutputResult,
1126
+ } from './adapter'
1127
+ export { BaseTextAdapter } from './adapter'