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