@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.
- package/README.md +26 -0
- package/dist/esm/activities/chat/adapter.d.ts +100 -0
- package/dist/esm/activities/chat/adapter.js +14 -0
- package/dist/esm/activities/chat/adapter.js.map +1 -0
- package/dist/esm/{utilities → activities/chat}/agent-loop-strategies.d.ts +4 -4
- package/dist/esm/activities/chat/agent-loop-strategies.js.map +1 -0
- package/dist/esm/activities/chat/index.d.ts +166 -0
- package/dist/esm/{core/chat.js → activities/chat/index.js} +131 -33
- package/dist/esm/activities/chat/index.js.map +1 -0
- package/dist/esm/{message-converters.d.ts → activities/chat/messages.d.ts} +1 -1
- package/dist/esm/{message-converters.js → activities/chat/messages.js} +7 -7
- package/dist/esm/activities/chat/messages.js.map +1 -0
- package/dist/esm/activities/chat/stream/json-parser.js.map +1 -0
- package/dist/esm/{stream → activities/chat/stream}/message-updaters.d.ts +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -0
- package/dist/esm/{stream → activities/chat/stream}/processor.d.ts +1 -1
- package/dist/esm/{stream → activities/chat/stream}/processor.js +1 -1
- package/dist/esm/activities/chat/stream/processor.js.map +1 -0
- package/dist/esm/activities/chat/stream/strategies.js.map +1 -0
- package/dist/esm/{stream → activities/chat/stream}/types.d.ts +2 -9
- package/dist/esm/activities/chat/tools/schema-converter.d.ts +116 -0
- package/dist/esm/activities/chat/tools/schema-converter.js +115 -0
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -0
- package/dist/esm/{tools → activities/chat/tools}/tool-calls.d.ts +1 -1
- package/dist/esm/{tools → activities/chat/tools}/tool-calls.js +23 -30
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -0
- package/dist/esm/{tools → activities/chat/tools}/tool-definition.d.ts +22 -18
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -0
- package/dist/esm/activities/generateImage/adapter.d.ts +68 -0
- package/dist/esm/activities/generateImage/adapter.js +14 -0
- package/dist/esm/activities/generateImage/adapter.js.map +1 -0
- package/dist/esm/activities/generateImage/index.d.ts +89 -0
- package/dist/esm/activities/generateImage/index.js +15 -0
- package/dist/esm/activities/generateImage/index.js.map +1 -0
- package/dist/esm/activities/generateSpeech/adapter.d.ts +62 -0
- package/dist/esm/activities/generateSpeech/adapter.js +14 -0
- package/dist/esm/activities/generateSpeech/adapter.js.map +1 -0
- package/dist/esm/activities/generateSpeech/index.d.ts +69 -0
- package/dist/esm/activities/generateSpeech/index.js +15 -0
- package/dist/esm/activities/generateSpeech/index.js.map +1 -0
- package/dist/esm/activities/generateTranscription/adapter.d.ts +62 -0
- package/dist/esm/activities/generateTranscription/adapter.js +14 -0
- package/dist/esm/activities/generateTranscription/adapter.js.map +1 -0
- package/dist/esm/activities/generateTranscription/index.d.ts +71 -0
- package/dist/esm/activities/generateTranscription/index.js +15 -0
- package/dist/esm/activities/generateTranscription/index.js.map +1 -0
- package/dist/esm/activities/generateVideo/adapter.d.ts +80 -0
- package/dist/esm/activities/generateVideo/adapter.js +14 -0
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -0
- package/dist/esm/activities/generateVideo/index.d.ts +136 -0
- package/dist/esm/activities/generateVideo/index.js +47 -0
- package/dist/esm/activities/generateVideo/index.js.map +1 -0
- package/dist/esm/activities/index.d.ts +22 -0
- package/dist/esm/activities/index.js +34 -0
- package/dist/esm/activities/index.js.map +1 -0
- package/dist/esm/activities/summarize/adapter.d.ts +74 -0
- package/dist/esm/activities/summarize/adapter.js +14 -0
- package/dist/esm/activities/summarize/adapter.js.map +1 -0
- package/dist/esm/activities/summarize/index.d.ts +100 -0
- package/dist/esm/activities/summarize/index.js +90 -0
- package/dist/esm/activities/summarize/index.js.map +1 -0
- package/dist/esm/event-client.d.ts +4 -18
- package/dist/esm/event-client.js.map +1 -1
- package/dist/esm/index.d.ts +16 -14
- package/dist/esm/index.js +29 -20
- package/dist/esm/stream-to-response.d.ts +95 -0
- package/dist/esm/stream-to-response.js +118 -0
- package/dist/esm/stream-to-response.js.map +1 -0
- package/dist/esm/types.d.ts +370 -133
- package/package.json +7 -6
- package/src/activities/chat/adapter.ts +150 -0
- package/src/{utilities → activities/chat}/agent-loop-strategies.ts +4 -4
- package/src/{core/chat.ts → activities/chat/index.ts} +435 -79
- package/src/{message-converters.ts → activities/chat/messages.ts} +10 -13
- package/src/{stream → activities/chat/stream}/message-updaters.ts +1 -1
- package/src/{stream → activities/chat/stream}/processor.ts +2 -5
- package/src/{stream → activities/chat/stream}/types.ts +8 -18
- package/src/activities/chat/tools/schema-converter.ts +332 -0
- package/src/{tools → activities/chat/tools}/tool-calls.ts +63 -44
- package/src/{tools → activities/chat/tools}/tool-definition.ts +51 -38
- package/src/activities/generateImage/adapter.ts +104 -0
- package/src/activities/generateImage/index.ts +162 -0
- package/src/activities/generateSpeech/adapter.ts +87 -0
- package/src/activities/generateSpeech/index.ts +122 -0
- package/src/activities/generateTranscription/adapter.ts +89 -0
- package/src/activities/generateTranscription/index.ts +132 -0
- package/src/activities/generateVideo/adapter.ts +116 -0
- package/src/activities/generateVideo/index.ts +261 -0
- package/src/activities/index.ts +164 -0
- package/src/activities/summarize/adapter.ts +107 -0
- package/src/activities/summarize/index.ts +287 -0
- package/src/event-client.ts +5 -21
- package/src/index.ts +60 -15
- package/src/stream-to-response.ts +237 -0
- package/src/types.ts +429 -284
- package/dist/esm/base-adapter.d.ts +0 -36
- package/dist/esm/base-adapter.js +0 -12
- package/dist/esm/base-adapter.js.map +0 -1
- package/dist/esm/core/chat-common-options.d.ts +0 -52
- package/dist/esm/core/chat.d.ts +0 -30
- package/dist/esm/core/chat.js.map +0 -1
- package/dist/esm/core/embedding.d.ts +0 -8
- package/dist/esm/core/embedding.js +0 -33
- package/dist/esm/core/embedding.js.map +0 -1
- package/dist/esm/core/summarize.d.ts +0 -9
- package/dist/esm/core/summarize.js +0 -36
- package/dist/esm/core/summarize.js.map +0 -1
- package/dist/esm/message-converters.js.map +0 -1
- package/dist/esm/stream/json-parser.js.map +0 -1
- package/dist/esm/stream/message-updaters.js.map +0 -1
- package/dist/esm/stream/processor.js.map +0 -1
- package/dist/esm/stream/strategies.js.map +0 -1
- package/dist/esm/tools/tool-calls.js.map +0 -1
- package/dist/esm/tools/tool-definition.js.map +0 -1
- package/dist/esm/tools/zod-converter.d.ts +0 -30
- package/dist/esm/tools/zod-converter.js +0 -36
- package/dist/esm/tools/zod-converter.js.map +0 -1
- package/dist/esm/utilities/agent-loop-strategies.js.map +0 -1
- package/dist/esm/utilities/chat-options.d.ts +0 -6
- package/dist/esm/utilities/chat-options.js +0 -7
- package/dist/esm/utilities/chat-options.js.map +0 -1
- package/dist/esm/utilities/messages.d.ts +0 -30
- package/dist/esm/utilities/messages.js +0 -7
- package/dist/esm/utilities/messages.js.map +0 -1
- package/dist/esm/utilities/stream-to-response.d.ts +0 -48
- package/dist/esm/utilities/stream-to-response.js +0 -62
- package/dist/esm/utilities/stream-to-response.js.map +0 -1
- package/src/base-adapter.ts +0 -86
- package/src/core/chat-common-options.ts +0 -55
- package/src/core/embedding.ts +0 -54
- package/src/core/summarize.ts +0 -56
- package/src/tools/zod-converter.ts +0 -85
- package/src/utilities/chat-options.ts +0 -35
- package/src/utilities/messages.ts +0 -63
- package/src/utilities/stream-to-response.ts +0 -116
- /package/dist/esm/{utilities → activities/chat}/agent-loop-strategies.js +0 -0
- /package/dist/esm/{stream → activities/chat/stream}/index.d.ts +0 -0
- /package/dist/esm/{stream → activities/chat/stream}/json-parser.d.ts +0 -0
- /package/dist/esm/{stream → activities/chat/stream}/json-parser.js +0 -0
- /package/dist/esm/{stream → activities/chat/stream}/message-updaters.js +0 -0
- /package/dist/esm/{stream → activities/chat/stream}/strategies.d.ts +0 -0
- /package/dist/esm/{stream → activities/chat/stream}/strategies.js +0 -0
- /package/dist/esm/{tools → activities/chat/tools}/tool-definition.js +0 -0
- /package/src/{stream → activities/chat/stream}/index.ts +0 -0
- /package/src/{stream → activities/chat/stream}/json-parser.ts +0 -0
- /package/src/{stream → activities/chat/stream}/strategies.ts +0 -0
|
@@ -1,26 +1,186 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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 '
|
|
21
|
+
} from './tools/tool-calls'
|
|
22
|
+
import type { AnyTextAdapter } from './adapter'
|
|
9
23
|
import type {
|
|
10
|
-
AIAdapter,
|
|
11
24
|
AgentLoopStrategy,
|
|
12
|
-
|
|
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 '
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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 = '
|
|
191
|
+
type CyclePhase = 'processText' | 'executeToolCalls'
|
|
32
192
|
|
|
33
|
-
class
|
|
34
|
-
TAdapter extends
|
|
35
|
-
TParams extends
|
|
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 = '
|
|
220
|
+
private cyclePhase: CyclePhase = 'processText'
|
|
61
221
|
|
|
62
|
-
constructor(config:
|
|
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
|
-
|
|
81
|
-
|
|
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 === '
|
|
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.
|
|
275
|
+
this.afterRun()
|
|
106
276
|
}
|
|
107
277
|
}
|
|
108
278
|
|
|
109
|
-
private
|
|
279
|
+
private beforeRun(): void {
|
|
110
280
|
this.streamStartTime = Date.now()
|
|
111
|
-
const {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
|
126
|
-
|
|
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
|
|
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
|
|
145
|
-
aiEventClient.emit('
|
|
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 === '
|
|
351
|
+
if (this.cyclePhase === 'processText') {
|
|
167
352
|
this.beginIteration()
|
|
168
353
|
}
|
|
169
354
|
}
|
|
170
355
|
|
|
171
356
|
private endCycle(): void {
|
|
172
|
-
if (this.cyclePhase === '
|
|
357
|
+
if (this.cyclePhase === 'processText') {
|
|
173
358
|
this.cyclePhase = 'executeToolCalls'
|
|
174
359
|
return
|
|
175
360
|
}
|
|
176
361
|
|
|
177
|
-
this.cyclePhase = '
|
|
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
|
|
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
|
-
|
|
390
|
+
tools: toolsWithJsonSchemas,
|
|
391
|
+
temperature,
|
|
392
|
+
topP,
|
|
393
|
+
maxTokens,
|
|
394
|
+
metadata,
|
|
197
395
|
request: this.effectiveRequest,
|
|
198
|
-
|
|
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('
|
|
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('
|
|
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
|
-
*
|
|
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
|
-
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
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
|
-
* ```
|
|
727
|
-
*
|
|
728
|
-
*
|
|
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
|
|
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
|
|
742
|
-
TAdapter extends
|
|
743
|
-
|
|
744
|
-
|
|
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:
|
|
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, ...
|
|
1019
|
+
const { adapter, ...textOptions } = options
|
|
1020
|
+
const model = adapter.model
|
|
757
1021
|
|
|
758
|
-
const engine = new
|
|
1022
|
+
const engine = new TextEngine({
|
|
759
1023
|
adapter,
|
|
760
|
-
params:
|
|
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.
|
|
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'
|