@tanstack/ai 0.23.1 → 0.25.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/dist/esm/activities/chat/adapter.d.ts +3 -1
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/index.d.ts +33 -9
- package/dist/esm/activities/chat/index.js +19 -9
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +2 -1
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +14 -14
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +21 -21
- package/dist/esm/activities/chat/runtime-context-types.d.ts +43 -0
- package/dist/esm/activities/chat/stream/message-updaters.d.ts +2 -2
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +1 -0
- package/dist/esm/activities/chat/stream/processor.js +35 -12
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +15 -5
- package/dist/esm/activities/chat/tools/tool-calls.js +59 -19
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.d.ts +12 -8
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
- package/dist/esm/activities/error-payload.d.ts +26 -0
- package/dist/esm/activities/error-payload.js +12 -1
- package/dist/esm/activities/error-payload.js.map +1 -1
- package/dist/esm/activities/generateAudio/index.js +9 -0
- package/dist/esm/activities/generateAudio/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/index.js +9 -0
- package/dist/esm/activities/generateSpeech/index.js.map +1 -1
- package/dist/esm/adapter-internals.d.ts +1 -1
- package/dist/esm/adapter-internals.js +3 -2
- package/dist/esm/client.d.ts +1 -1
- package/dist/esm/client.js +3 -1
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +9 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/tool-registry.d.ts +7 -7
- package/dist/esm/tool-registry.js +1 -1
- package/dist/esm/tool-registry.js.map +1 -1
- package/dist/esm/types.d.ts +56 -59
- package/dist/esm/utilities/ag-ui-wire.js +1 -1
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/chat-params.d.ts +8 -3
- package/dist/esm/utilities/chat-params.js +6 -2
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/dist/esm/utilities/tool-result.d.ts +21 -0
- package/dist/esm/utilities/tool-result.js +37 -0
- package/dist/esm/utilities/tool-result.js.map +1 -0
- package/dist/esm/utilities/usage.d.ts +31 -0
- package/dist/esm/utilities/usage.js +11 -0
- package/dist/esm/utilities/usage.js.map +1 -0
- package/package.json +2 -2
- package/src/activities/chat/adapter.ts +3 -0
- package/src/activities/chat/index.ts +219 -47
- package/src/activities/chat/messages.ts +2 -1
- package/src/activities/chat/middleware/compose.ts +23 -17
- package/src/activities/chat/middleware/types.ts +21 -21
- package/src/activities/chat/runtime-context-types.ts +68 -0
- package/src/activities/chat/stream/message-updaters.ts +2 -1
- package/src/activities/chat/stream/processor.ts +48 -8
- package/src/activities/chat/tools/tool-calls.ts +138 -43
- package/src/activities/chat/tools/tool-definition.ts +25 -31
- package/src/activities/error-payload.ts +44 -0
- package/src/activities/generateAudio/index.ts +10 -0
- package/src/activities/generateSpeech/index.ts +10 -0
- package/src/adapter-internals.ts +4 -1
- package/src/client.ts +5 -1
- package/src/index.ts +10 -0
- package/src/tool-registry.ts +16 -14
- package/src/types.ts +118 -79
- package/src/utilities/ag-ui-wire.ts +4 -1
- package/src/utilities/chat-params.ts +22 -7
- package/src/utilities/tool-result.ts +60 -0
- package/src/utilities/usage.ts +41 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { normalizeToolResult } from '../../../utilities/tool-result'
|
|
1
2
|
import { isStandardSchema, parseWithStandardSchema } from './schema-converter'
|
|
2
3
|
import type {
|
|
4
|
+
AnyTool,
|
|
5
|
+
ContentPart,
|
|
3
6
|
CustomEvent,
|
|
4
7
|
ModelMessage,
|
|
5
8
|
RunFinishedEvent,
|
|
@@ -9,11 +12,18 @@ import type {
|
|
|
9
12
|
ToolCallEndEvent,
|
|
10
13
|
ToolCallStartEvent,
|
|
11
14
|
ToolExecutionContext,
|
|
15
|
+
ToolOutputState,
|
|
12
16
|
} from '../../../types'
|
|
13
17
|
import type {
|
|
14
18
|
AfterToolCallInfo,
|
|
15
19
|
BeforeToolCallDecision,
|
|
16
20
|
} from '../middleware/types'
|
|
21
|
+
import type {
|
|
22
|
+
ContextFromTool,
|
|
23
|
+
DefinedContext,
|
|
24
|
+
MergeContext,
|
|
25
|
+
UnionToIntersection,
|
|
26
|
+
} from '../runtime-context-types'
|
|
17
27
|
|
|
18
28
|
function safeJsonParse(value: string): unknown {
|
|
19
29
|
try {
|
|
@@ -46,6 +56,36 @@ export class MiddlewareAbortError extends Error {
|
|
|
46
56
|
}
|
|
47
57
|
}
|
|
48
58
|
|
|
59
|
+
// The leaf context-inference primitives (ContextFromTool, MergeContext,
|
|
60
|
+
// UnionToIntersection, DefinedContext) are shared with the chat activity
|
|
61
|
+
// options layer — see ../runtime-context-types.
|
|
62
|
+
type RequiredContextFromToolUnion<T> = T extends unknown
|
|
63
|
+
? undefined extends ContextFromTool<T>
|
|
64
|
+
? never
|
|
65
|
+
: ContextFromTool<T>
|
|
66
|
+
: never
|
|
67
|
+
|
|
68
|
+
type ContextFromToolUnion<T> = [
|
|
69
|
+
UnionToIntersection<DefinedContext<ContextFromTool<T>>>,
|
|
70
|
+
] extends [never]
|
|
71
|
+
? unknown
|
|
72
|
+
: [RequiredContextFromToolUnion<T>] extends [never]
|
|
73
|
+
? UnionToIntersection<DefinedContext<ContextFromTool<T>>> | undefined
|
|
74
|
+
: UnionToIntersection<DefinedContext<ContextFromTool<T>>>
|
|
75
|
+
|
|
76
|
+
type ContextFromTools<TTools> = TTools extends readonly [
|
|
77
|
+
infer THead,
|
|
78
|
+
...infer TTail,
|
|
79
|
+
]
|
|
80
|
+
? MergeContext<ContextFromTool<THead>, ContextFromTools<TTail>>
|
|
81
|
+
: TTools extends ReadonlyArray<infer TTool>
|
|
82
|
+
? ContextFromToolUnion<TTool>
|
|
83
|
+
: unknown
|
|
84
|
+
|
|
85
|
+
type ExecuteToolsContextArgs<TContext> = undefined extends TContext
|
|
86
|
+
? [userContext?: TContext]
|
|
87
|
+
: [userContext: TContext]
|
|
88
|
+
|
|
49
89
|
/**
|
|
50
90
|
* Manages tool call accumulation and execution for the chat() method's automatic tool execution loop.
|
|
51
91
|
*
|
|
@@ -80,11 +120,22 @@ export class MiddlewareAbortError extends Error {
|
|
|
80
120
|
* }
|
|
81
121
|
* ```
|
|
82
122
|
*/
|
|
83
|
-
export class ToolCallManager
|
|
123
|
+
export class ToolCallManager<
|
|
124
|
+
TToolsOrContext = ReadonlyArray<AnyTool>,
|
|
125
|
+
TContext = TToolsOrContext extends ReadonlyArray<AnyTool>
|
|
126
|
+
? ContextFromTools<TToolsOrContext>
|
|
127
|
+
: TToolsOrContext,
|
|
128
|
+
> {
|
|
84
129
|
private readonly toolCallsMap = new Map<number, ToolCall>()
|
|
85
|
-
private readonly tools: ReadonlyArray<
|
|
86
|
-
|
|
87
|
-
|
|
130
|
+
private readonly tools: TToolsOrContext extends ReadonlyArray<AnyTool>
|
|
131
|
+
? TToolsOrContext
|
|
132
|
+
: ReadonlyArray<AnyTool>
|
|
133
|
+
|
|
134
|
+
constructor(
|
|
135
|
+
tools: TToolsOrContext extends ReadonlyArray<AnyTool>
|
|
136
|
+
? TToolsOrContext
|
|
137
|
+
: ReadonlyArray<AnyTool>,
|
|
138
|
+
) {
|
|
88
139
|
this.tools = tools
|
|
89
140
|
}
|
|
90
141
|
|
|
@@ -161,14 +212,18 @@ export class ToolCallManager {
|
|
|
161
212
|
*/
|
|
162
213
|
async *executeTools(
|
|
163
214
|
finishEvent: RunFinishedEvent,
|
|
215
|
+
...contextArgs: ExecuteToolsContextArgs<TContext>
|
|
164
216
|
): AsyncGenerator<ToolCallEndEvent, Array<ModelMessage>, void> {
|
|
165
217
|
const toolCallsArray = this.getToolCalls()
|
|
166
218
|
const toolResults: Array<ModelMessage> = []
|
|
219
|
+
const hasRuntimeContext = contextArgs.length > 0
|
|
220
|
+
const userContext = contextArgs[0]
|
|
167
221
|
|
|
168
222
|
for (const toolCall of toolCallsArray) {
|
|
169
223
|
const tool = this.tools.find((t) => t.name === toolCall.function.name)
|
|
170
224
|
|
|
171
|
-
let toolResultContent: string
|
|
225
|
+
let toolResultContent: string | Array<ContentPart>
|
|
226
|
+
let toolResultState: ToolOutputState | undefined
|
|
172
227
|
if (tool?.execute) {
|
|
173
228
|
try {
|
|
174
229
|
// Parse arguments (normalize null/non-object to {} for empty tool_use blocks)
|
|
@@ -199,15 +254,21 @@ export class ToolCallManager {
|
|
|
199
254
|
}
|
|
200
255
|
|
|
201
256
|
// Execute the tool
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
257
|
+
const executionContext = {
|
|
258
|
+
toolCallId: toolCall.id,
|
|
259
|
+
context: userContext,
|
|
260
|
+
emitCustomEvent: () => {},
|
|
261
|
+
} as ToolExecutionContext<TContext>
|
|
262
|
+
let result = hasRuntimeContext
|
|
263
|
+
? await tool.execute(args, executionContext)
|
|
264
|
+
: await tool.execute(args)
|
|
265
|
+
|
|
266
|
+
// Validate output against outputSchema if provided (for Standard
|
|
267
|
+
// Schema compliant schemas). Unlike the previous implementation we
|
|
268
|
+
// intentionally validate `undefined`/`null` results too, so a tool
|
|
269
|
+
// whose schema forbids them surfaces a validation error instead of
|
|
270
|
+
// silently passing — the schema itself decides whether they're valid.
|
|
271
|
+
if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {
|
|
211
272
|
try {
|
|
212
273
|
result = parseWithStandardSchema(tool.outputSchema, result)
|
|
213
274
|
} catch (validationError: unknown) {
|
|
@@ -221,13 +282,13 @@ export class ToolCallManager {
|
|
|
221
282
|
}
|
|
222
283
|
}
|
|
223
284
|
|
|
224
|
-
toolResultContent =
|
|
225
|
-
typeof result === 'string' ? result : JSON.stringify(result)
|
|
285
|
+
toolResultContent = normalizeToolResult(result)
|
|
226
286
|
} catch (error: unknown) {
|
|
227
287
|
// If tool execution fails, add error message
|
|
228
288
|
const message =
|
|
229
289
|
error instanceof Error ? error.message : 'Unknown error'
|
|
230
290
|
toolResultContent = `Error executing tool: ${message}`
|
|
291
|
+
toolResultState = 'output-error'
|
|
231
292
|
}
|
|
232
293
|
} else {
|
|
233
294
|
// Tool doesn't have execute function, add placeholder
|
|
@@ -243,6 +304,7 @@ export class ToolCallManager {
|
|
|
243
304
|
model: finishEvent.model,
|
|
244
305
|
timestamp: Date.now(),
|
|
245
306
|
result: toolResultContent,
|
|
307
|
+
...(toolResultState !== undefined && { state: toolResultState }),
|
|
246
308
|
} as ToolCallEndEvent
|
|
247
309
|
|
|
248
310
|
// Add tool result message
|
|
@@ -370,7 +432,7 @@ async function applyBeforeToolCallDecision(
|
|
|
370
432
|
result:
|
|
371
433
|
typeof skipResult === 'string'
|
|
372
434
|
? safeJsonParse(skipResult)
|
|
373
|
-
: skipResult
|
|
435
|
+
: (skipResult ?? null),
|
|
374
436
|
duration: 0,
|
|
375
437
|
})
|
|
376
438
|
if (middlewareHooks.onAfterToolCall) {
|
|
@@ -394,12 +456,12 @@ async function applyBeforeToolCallDecision(
|
|
|
394
456
|
* Execute a server-side tool with event polling, output validation, and middleware hooks.
|
|
395
457
|
* Yields CustomEvent chunks during execution and pushes the result to the results array.
|
|
396
458
|
*/
|
|
397
|
-
async function* executeServerTool(
|
|
459
|
+
async function* executeServerTool<TContext = unknown>(
|
|
398
460
|
toolCall: ToolCall,
|
|
399
|
-
tool:
|
|
461
|
+
tool: AnyTool,
|
|
400
462
|
toolName: string,
|
|
401
463
|
input: unknown,
|
|
402
|
-
context: ToolExecutionContext
|
|
464
|
+
context: ToolExecutionContext<TContext>,
|
|
403
465
|
pendingEvents: Array<CustomEvent>,
|
|
404
466
|
results: Array<ToolResult>,
|
|
405
467
|
middlewareHooks?: ToolExecutionMiddlewareHooks,
|
|
@@ -419,18 +481,14 @@ async function* executeServerTool(
|
|
|
419
481
|
yield pendingEvent
|
|
420
482
|
}
|
|
421
483
|
|
|
422
|
-
// Validate output against outputSchema if provided
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
isStandardSchema(tool.outputSchema) &&
|
|
426
|
-
result !== undefined &&
|
|
427
|
-
result !== null
|
|
428
|
-
) {
|
|
484
|
+
// Validate output against outputSchema if provided. Validates
|
|
485
|
+
// `undefined`/`null` too — the schema decides whether they're valid.
|
|
486
|
+
if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {
|
|
429
487
|
result = parseWithStandardSchema(tool.outputSchema, result)
|
|
430
488
|
}
|
|
431
489
|
|
|
432
490
|
const finalResult =
|
|
433
|
-
typeof result === 'string' ? safeJsonParse(result) : result
|
|
491
|
+
typeof result === 'string' ? safeJsonParse(result) : (result ?? null)
|
|
434
492
|
|
|
435
493
|
results.push({
|
|
436
494
|
toolCallId: toolCall.id,
|
|
@@ -486,6 +544,35 @@ async function* executeServerTool(
|
|
|
486
544
|
}
|
|
487
545
|
}
|
|
488
546
|
|
|
547
|
+
function buildClientToolResult(
|
|
548
|
+
toolCallId: string,
|
|
549
|
+
toolName: string,
|
|
550
|
+
tool: AnyTool,
|
|
551
|
+
rawResult: unknown,
|
|
552
|
+
): ToolResult {
|
|
553
|
+
try {
|
|
554
|
+
let result = rawResult
|
|
555
|
+
if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {
|
|
556
|
+
result = parseWithStandardSchema(tool.outputSchema, result)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return {
|
|
560
|
+
toolCallId,
|
|
561
|
+
toolName,
|
|
562
|
+
result:
|
|
563
|
+
typeof result === 'string' ? safeJsonParse(result) : (result ?? null),
|
|
564
|
+
}
|
|
565
|
+
} catch (error: unknown) {
|
|
566
|
+
const message = error instanceof Error ? error.message : 'Validation failed'
|
|
567
|
+
return {
|
|
568
|
+
toolCallId,
|
|
569
|
+
toolName,
|
|
570
|
+
result: { error: message },
|
|
571
|
+
state: 'output-error',
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
489
576
|
/**
|
|
490
577
|
* Execute tool calls based on their configuration.
|
|
491
578
|
* Yields CustomEvent chunks during tool execution for real-time progress updates.
|
|
@@ -501,9 +588,9 @@ async function* executeServerTool(
|
|
|
501
588
|
* @param clientResults - Map of client-side execution results (toolCallId -> result)
|
|
502
589
|
* @param createCustomEventChunk - Factory to create CustomEvent chunks (optional)
|
|
503
590
|
*/
|
|
504
|
-
export async function* executeToolCalls(
|
|
591
|
+
export async function* executeToolCalls<TContext = unknown>(
|
|
505
592
|
toolCalls: Array<ToolCall>,
|
|
506
|
-
tools: ReadonlyArray<
|
|
593
|
+
tools: ReadonlyArray<AnyTool>,
|
|
507
594
|
approvals: Map<string, boolean> = new Map(),
|
|
508
595
|
clientResults: Map<string, any> = new Map(),
|
|
509
596
|
createCustomEventChunk?: (
|
|
@@ -511,13 +598,14 @@ export async function* executeToolCalls(
|
|
|
511
598
|
value: Record<string, any>,
|
|
512
599
|
) => CustomEvent,
|
|
513
600
|
middlewareHooks?: ToolExecutionMiddlewareHooks,
|
|
601
|
+
userContext?: TContext,
|
|
514
602
|
): AsyncGenerator<CustomEvent, ExecuteToolCallsResult, void> {
|
|
515
603
|
const results: Array<ToolResult> = []
|
|
516
604
|
const needsApproval: Array<ApprovalRequest> = []
|
|
517
605
|
const needsClientExecution: Array<ClientToolRequest> = []
|
|
518
606
|
|
|
519
607
|
// Create tool lookup map
|
|
520
|
-
const toolMap = new Map<string,
|
|
608
|
+
const toolMap = new Map<string, AnyTool>()
|
|
521
609
|
for (const tool of tools) {
|
|
522
610
|
toolMap.set(tool.name, tool)
|
|
523
611
|
}
|
|
@@ -588,8 +676,9 @@ export async function* executeToolCalls(
|
|
|
588
676
|
|
|
589
677
|
// Create a ToolExecutionContext for this tool call with event emission
|
|
590
678
|
const pendingEvents: Array<CustomEvent> = []
|
|
591
|
-
const context
|
|
679
|
+
const context = {
|
|
592
680
|
toolCallId: toolCall.id,
|
|
681
|
+
context: userContext,
|
|
593
682
|
emitCustomEvent: (eventName: string, value: Record<string, any>) => {
|
|
594
683
|
if (createCustomEventChunk) {
|
|
595
684
|
pendingEvents.push(
|
|
@@ -600,7 +689,7 @@ export async function* executeToolCalls(
|
|
|
600
689
|
)
|
|
601
690
|
}
|
|
602
691
|
},
|
|
603
|
-
}
|
|
692
|
+
} as ToolExecutionContext<TContext>
|
|
604
693
|
|
|
605
694
|
// CASE 1: Client-side tool (no execute function)
|
|
606
695
|
if (!tool.execute) {
|
|
@@ -615,11 +704,14 @@ export async function* executeToolCalls(
|
|
|
615
704
|
if (approved) {
|
|
616
705
|
// Approved - check if client has executed
|
|
617
706
|
if (clientResults.has(toolCall.id)) {
|
|
618
|
-
results.push(
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
707
|
+
results.push(
|
|
708
|
+
buildClientToolResult(
|
|
709
|
+
toolCall.id,
|
|
710
|
+
toolName,
|
|
711
|
+
tool,
|
|
712
|
+
clientResults.get(toolCall.id),
|
|
713
|
+
),
|
|
714
|
+
)
|
|
623
715
|
} else {
|
|
624
716
|
// Approved but not executed yet - request client execution
|
|
625
717
|
needsClientExecution.push({
|
|
@@ -649,11 +741,14 @@ export async function* executeToolCalls(
|
|
|
649
741
|
} else {
|
|
650
742
|
// No approval needed - check if client has executed
|
|
651
743
|
if (clientResults.has(toolCall.id)) {
|
|
652
|
-
results.push(
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
744
|
+
results.push(
|
|
745
|
+
buildClientToolResult(
|
|
746
|
+
toolCall.id,
|
|
747
|
+
toolName,
|
|
748
|
+
tool,
|
|
749
|
+
clientResults.get(toolCall.id),
|
|
750
|
+
),
|
|
751
|
+
)
|
|
657
752
|
} else {
|
|
658
753
|
// Request client execution
|
|
659
754
|
needsClientExecution.push({
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { StandardJSONSchemaV1 } from '@standard-schema/spec'
|
|
2
2
|
import type {
|
|
3
|
-
InferSchemaType,
|
|
4
3
|
JSONSchema,
|
|
5
4
|
SchemaInput,
|
|
6
5
|
Tool,
|
|
7
|
-
|
|
6
|
+
ToolExecuteFunction,
|
|
8
7
|
} from '../../../types'
|
|
9
8
|
|
|
10
9
|
/**
|
|
@@ -14,7 +13,8 @@ export interface ServerTool<
|
|
|
14
13
|
TInput extends SchemaInput = SchemaInput,
|
|
15
14
|
TOutput extends SchemaInput = SchemaInput,
|
|
16
15
|
TName extends string = string,
|
|
17
|
-
|
|
16
|
+
TContext = unknown,
|
|
17
|
+
> extends Tool<TInput, TOutput, TName, TContext> {
|
|
18
18
|
__toolSide: 'server'
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -25,6 +25,7 @@ export interface ClientTool<
|
|
|
25
25
|
TInput extends SchemaInput = SchemaInput,
|
|
26
26
|
TOutput extends SchemaInput = SchemaInput,
|
|
27
27
|
TName extends string = string,
|
|
28
|
+
TContext = unknown,
|
|
28
29
|
> {
|
|
29
30
|
__toolSide: 'client'
|
|
30
31
|
name: TName
|
|
@@ -39,9 +40,7 @@ export interface ClientTool<
|
|
|
39
40
|
needsApproval?: boolean
|
|
40
41
|
lazy?: boolean
|
|
41
42
|
metadata?: Record<string, unknown>
|
|
42
|
-
execute?:
|
|
43
|
-
args: InferSchemaType<TInput>,
|
|
44
|
-
) => Promise<InferSchemaType<TOutput>> | InferSchemaType<TOutput>
|
|
43
|
+
execute?: ToolExecuteFunction<TInput, TOutput, TContext>
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
/**
|
|
@@ -51,7 +50,8 @@ export interface ToolDefinitionInstance<
|
|
|
51
50
|
TInput extends SchemaInput = SchemaInput,
|
|
52
51
|
TOutput extends SchemaInput = SchemaInput,
|
|
53
52
|
TName extends string = string,
|
|
54
|
-
|
|
53
|
+
TContext = unknown,
|
|
54
|
+
> extends Tool<TInput, TOutput, TName, TContext> {
|
|
55
55
|
__toolSide: 'definition'
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -59,8 +59,12 @@ export interface ToolDefinitionInstance<
|
|
|
59
59
|
* Union type for any kind of client-side tool (client tool or definition)
|
|
60
60
|
*/
|
|
61
61
|
export type AnyClientTool =
|
|
62
|
-
| ClientTool<
|
|
63
|
-
|
|
62
|
+
| (Omit<ClientTool<any, any, string, any>, 'execute'> & {
|
|
63
|
+
execute?: ((args: any, context?: any) => any) | undefined
|
|
64
|
+
})
|
|
65
|
+
| (Omit<ToolDefinitionInstance<any, any, string, any>, 'execute'> & {
|
|
66
|
+
execute?: ((args: any, context?: any) => any) | undefined
|
|
67
|
+
})
|
|
64
68
|
|
|
65
69
|
/**
|
|
66
70
|
* Extract the tool name as a literal type
|
|
@@ -117,21 +121,16 @@ export interface ToolDefinition<
|
|
|
117
121
|
/**
|
|
118
122
|
* Create a server-side tool with execute function
|
|
119
123
|
*/
|
|
120
|
-
server: (
|
|
121
|
-
execute:
|
|
122
|
-
|
|
123
|
-
context?: ToolExecutionContext,
|
|
124
|
-
) => Promise<InferSchemaType<TOutput>> | InferSchemaType<TOutput>,
|
|
125
|
-
) => ServerTool<TInput, TOutput, TName>
|
|
124
|
+
server: <TContext = unknown>(
|
|
125
|
+
execute: ToolExecuteFunction<TInput, TOutput, TContext>,
|
|
126
|
+
) => ServerTool<TInput, TOutput, TName, TContext>
|
|
126
127
|
|
|
127
128
|
/**
|
|
128
129
|
* Create a client-side tool with optional execute function
|
|
129
130
|
*/
|
|
130
|
-
client: (
|
|
131
|
-
execute?:
|
|
132
|
-
|
|
133
|
-
) => Promise<InferSchemaType<TOutput>> | InferSchemaType<TOutput>,
|
|
134
|
-
) => ClientTool<TInput, TOutput, TName>
|
|
131
|
+
client: <TContext = unknown>(
|
|
132
|
+
execute?: ToolExecuteFunction<TInput, TOutput, TContext>,
|
|
133
|
+
) => ClientTool<TInput, TOutput, TName, TContext>
|
|
135
134
|
}
|
|
136
135
|
|
|
137
136
|
/**
|
|
@@ -199,12 +198,9 @@ export function toolDefinition<
|
|
|
199
198
|
const definition: ToolDefinition<TInput, TOutput, TName> = {
|
|
200
199
|
__toolSide: 'definition',
|
|
201
200
|
...config,
|
|
202
|
-
server(
|
|
203
|
-
execute:
|
|
204
|
-
|
|
205
|
-
context?: ToolExecutionContext,
|
|
206
|
-
) => Promise<InferSchemaType<TOutput>> | InferSchemaType<TOutput>,
|
|
207
|
-
): ServerTool<TInput, TOutput, TName> {
|
|
201
|
+
server<TContext = unknown>(
|
|
202
|
+
execute: ToolExecuteFunction<TInput, TOutput, TContext>,
|
|
203
|
+
): ServerTool<TInput, TOutput, TName, TContext> {
|
|
208
204
|
return {
|
|
209
205
|
__toolSide: 'server',
|
|
210
206
|
...config,
|
|
@@ -212,11 +208,9 @@ export function toolDefinition<
|
|
|
212
208
|
}
|
|
213
209
|
},
|
|
214
210
|
|
|
215
|
-
client(
|
|
216
|
-
execute?:
|
|
217
|
-
|
|
218
|
-
) => Promise<InferSchemaType<TOutput>> | InferSchemaType<TOutput>,
|
|
219
|
-
): ClientTool<TInput, TOutput, TName> {
|
|
211
|
+
client<TContext = unknown>(
|
|
212
|
+
execute?: ToolExecuteFunction<TInput, TOutput, TContext>,
|
|
213
|
+
): ClientTool<TInput, TOutput, TName, TContext> {
|
|
220
214
|
return {
|
|
221
215
|
__toolSide: 'client',
|
|
222
216
|
...config,
|
|
@@ -62,3 +62,47 @@ export function toRunErrorPayload(
|
|
|
62
62
|
}
|
|
63
63
|
return { message: fallbackMessage, code: undefined }
|
|
64
64
|
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Extract the provider's *structured error body* from a thrown value, to attach
|
|
68
|
+
* as the AG-UI `rawEvent` on a RUN_ERROR event. This is the recoverable upstream
|
|
69
|
+
* detail (provider name, the upstream model's error JSON, rate-limit/overload
|
|
70
|
+
* codes, etc.) that `toRunErrorPayload`'s `{ message, code }` deliberately drops.
|
|
71
|
+
*
|
|
72
|
+
* Security boundary: only known provider-response-body fields are forwarded —
|
|
73
|
+
* never the raw SDK exception object, which can carry request metadata such as
|
|
74
|
+
* auth headers or request ids. The recognized sources, in priority order:
|
|
75
|
+
*
|
|
76
|
+
* - `error.rawEvent` — a provider body an adapter attached explicitly (e.g. the
|
|
77
|
+
* OpenRouter mid-stream `chunk.error`).
|
|
78
|
+
* - `error.error` (object) — the parsed provider response body exposed by SDK
|
|
79
|
+
* `APIError` instances (OpenAI/Anthropic `{ type, message, code, param }`,
|
|
80
|
+
* OpenRouter typed errors whose `.error` carries `.metadata`). This is
|
|
81
|
+
* provider-shaped data, distinct from `.headers` / `.request_id`.
|
|
82
|
+
* - `error.metadata` — OpenRouter's `provider_name` + raw upstream body, when
|
|
83
|
+
* surfaced directly on the thrown error.
|
|
84
|
+
*
|
|
85
|
+
* Returns `undefined` when no structured provider body is present, so callers
|
|
86
|
+
* omit the field entirely rather than setting it to `null`:
|
|
87
|
+
*
|
|
88
|
+
* const rawEvent = toRunErrorRawEvent(error)
|
|
89
|
+
* yield { type: EventType.RUN_ERROR, ..., ...(rawEvent !== undefined && { rawEvent }) }
|
|
90
|
+
*/
|
|
91
|
+
export function toRunErrorRawEvent(error: unknown): unknown {
|
|
92
|
+
if (!error || typeof error !== 'object') return undefined
|
|
93
|
+
const e = error as {
|
|
94
|
+
rawEvent?: unknown
|
|
95
|
+
error?: unknown
|
|
96
|
+
metadata?: unknown
|
|
97
|
+
}
|
|
98
|
+
if (e.rawEvent !== undefined && e.rawEvent !== null) return e.rawEvent
|
|
99
|
+
if (
|
|
100
|
+
e.error !== undefined &&
|
|
101
|
+
e.error !== null &&
|
|
102
|
+
typeof e.error === 'object'
|
|
103
|
+
) {
|
|
104
|
+
return e.error
|
|
105
|
+
}
|
|
106
|
+
if (e.metadata !== undefined && e.metadata !== null) return e.metadata
|
|
107
|
+
return undefined
|
|
108
|
+
}
|
|
@@ -174,6 +174,16 @@ async function runGenerateAudio<
|
|
|
174
174
|
timestamp: Date.now(),
|
|
175
175
|
})
|
|
176
176
|
|
|
177
|
+
if (result.usage) {
|
|
178
|
+
aiEventClient.emit('audio:usage', {
|
|
179
|
+
requestId,
|
|
180
|
+
model,
|
|
181
|
+
usage: result.usage,
|
|
182
|
+
modelOptions: rest.modelOptions as Record<string, unknown> | undefined,
|
|
183
|
+
timestamp: Date.now(),
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
177
187
|
logger.output(`activity=generateAudio provider=${providerName}`, {
|
|
178
188
|
contentType: result.audio.contentType,
|
|
179
189
|
audioDuration: result.audio.duration,
|
|
@@ -187,6 +187,16 @@ async function runGenerateSpeech<
|
|
|
187
187
|
timestamp: Date.now(),
|
|
188
188
|
})
|
|
189
189
|
|
|
190
|
+
if (result.usage) {
|
|
191
|
+
aiEventClient.emit('speech:usage', {
|
|
192
|
+
requestId,
|
|
193
|
+
model,
|
|
194
|
+
usage: result.usage,
|
|
195
|
+
modelOptions: rest.modelOptions as Record<string, unknown> | undefined,
|
|
196
|
+
timestamp: Date.now(),
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
190
200
|
logger.output(`activity=generateSpeech bytes=${result.audio.length}`, {
|
|
191
201
|
bytes: result.audio.length,
|
|
192
202
|
contentType: result.contentType,
|
package/src/adapter-internals.ts
CHANGED
|
@@ -6,4 +6,7 @@ export type { ResolvedCategories } from './logger/internal-logger'
|
|
|
6
6
|
export { InternalLogger } from './logger/internal-logger'
|
|
7
7
|
export type { Logger } from './logger/types'
|
|
8
8
|
export { resolveDebugOption } from './logger/resolve'
|
|
9
|
-
export {
|
|
9
|
+
export {
|
|
10
|
+
toRunErrorPayload,
|
|
11
|
+
toRunErrorRawEvent,
|
|
12
|
+
} from './activities/error-payload'
|
package/src/client.ts
CHANGED
|
@@ -46,7 +46,11 @@ export {
|
|
|
46
46
|
type ToolDefinitionInstance,
|
|
47
47
|
} from './activities/chat/tools/tool-definition'
|
|
48
48
|
|
|
49
|
-
export {
|
|
49
|
+
export {
|
|
50
|
+
convertSchemaToJsonSchema,
|
|
51
|
+
isStandardSchema,
|
|
52
|
+
parseWithStandardSchema,
|
|
53
|
+
} from './activities/chat/tools/schema-converter'
|
|
50
54
|
|
|
51
55
|
export {
|
|
52
56
|
convertMessagesToModelMessages,
|
package/src/index.ts
CHANGED
|
@@ -55,6 +55,8 @@ export {
|
|
|
55
55
|
// Schema conversion (Standard JSON Schema compliant)
|
|
56
56
|
export {
|
|
57
57
|
convertSchemaToJsonSchema,
|
|
58
|
+
isStandardSchema,
|
|
59
|
+
parseWithStandardSchema,
|
|
58
60
|
StandardSchemaValidationError,
|
|
59
61
|
} from './activities/chat/tools/schema-converter'
|
|
60
62
|
|
|
@@ -109,6 +111,9 @@ export type {
|
|
|
109
111
|
// All types
|
|
110
112
|
export * from './types'
|
|
111
113
|
|
|
114
|
+
// Usage utilities
|
|
115
|
+
export { buildBaseUsage, type BaseUsageInput } from './utilities/usage'
|
|
116
|
+
|
|
112
117
|
// System prompts (type + normaliser used by adapters)
|
|
113
118
|
export type { SystemPrompt, NormalizedSystemPrompt } from './system-prompts'
|
|
114
119
|
export { normalizeSystemPrompts } from './system-prompts'
|
|
@@ -187,6 +192,11 @@ export {
|
|
|
187
192
|
// AG-UI wire serialization (used internally by @tanstack/ai-client)
|
|
188
193
|
export { uiMessagesToWire } from './utilities/ag-ui-wire'
|
|
189
194
|
export type { WireMessage } from './utilities/ag-ui-wire'
|
|
195
|
+
export {
|
|
196
|
+
isContentPart,
|
|
197
|
+
isContentPartArray,
|
|
198
|
+
normalizeToolResult,
|
|
199
|
+
} from './utilities/tool-result'
|
|
190
200
|
|
|
191
201
|
// Adapter extension utilities
|
|
192
202
|
export { createModel, extendAdapter } from './extend-adapter'
|
package/src/tool-registry.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AnyTool } from './types'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A registry that holds tools and allows dynamic tool management.
|
|
@@ -6,12 +6,12 @@ import type { Tool } from './types'
|
|
|
6
6
|
* The registry can be either mutable (allowing additions/removals during execution)
|
|
7
7
|
* or frozen (static tool list, for backward compatibility with tools arrays).
|
|
8
8
|
*/
|
|
9
|
-
export interface ToolRegistry {
|
|
9
|
+
export interface ToolRegistry<TTool extends AnyTool = AnyTool> {
|
|
10
10
|
/**
|
|
11
11
|
* Get all current tools in the registry.
|
|
12
12
|
* Called each agent loop iteration to get the latest tool list.
|
|
13
13
|
*/
|
|
14
|
-
getTools: () =>
|
|
14
|
+
getTools: () => Array<TTool>
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Add a tool to the registry dynamically.
|
|
@@ -19,7 +19,7 @@ export interface ToolRegistry {
|
|
|
19
19
|
*
|
|
20
20
|
* @param tool - The tool to add
|
|
21
21
|
*/
|
|
22
|
-
add: (tool:
|
|
22
|
+
add: (tool: TTool) => void
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Remove a tool from the registry by name.
|
|
@@ -43,7 +43,7 @@ export interface ToolRegistry {
|
|
|
43
43
|
* @param name - The name of the tool to get
|
|
44
44
|
* @returns The tool if found, undefined otherwise
|
|
45
45
|
*/
|
|
46
|
-
get: (name: string) =>
|
|
46
|
+
get: (name: string) => TTool | undefined
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* Whether this registry is frozen (immutable).
|
|
@@ -75,10 +75,10 @@ export interface ToolRegistry {
|
|
|
75
75
|
* registry.add(newTool) // Immediately available to LLM
|
|
76
76
|
* ```
|
|
77
77
|
*/
|
|
78
|
-
export function createToolRegistry(
|
|
79
|
-
initialTools: Array<
|
|
80
|
-
): ToolRegistry {
|
|
81
|
-
const tools = new Map<string,
|
|
78
|
+
export function createToolRegistry<TTool extends AnyTool = AnyTool>(
|
|
79
|
+
initialTools: Array<TTool> = [],
|
|
80
|
+
): ToolRegistry<TTool> {
|
|
81
|
+
const tools = new Map<string, TTool>()
|
|
82
82
|
|
|
83
83
|
for (const tool of initialTools) {
|
|
84
84
|
tools.set(tool.name, tool)
|
|
@@ -87,7 +87,7 @@ export function createToolRegistry(
|
|
|
87
87
|
return {
|
|
88
88
|
getTools: () => Array.from(tools.values()),
|
|
89
89
|
|
|
90
|
-
add: (tool:
|
|
90
|
+
add: (tool: TTool) => {
|
|
91
91
|
tools.set(tool.name, tool)
|
|
92
92
|
},
|
|
93
93
|
|
|
@@ -116,8 +116,10 @@ export function createToolRegistry(
|
|
|
116
116
|
* @param tools - The static array of tools
|
|
117
117
|
* @returns A frozen ToolRegistry
|
|
118
118
|
*/
|
|
119
|
-
export function createFrozenRegistry
|
|
120
|
-
|
|
119
|
+
export function createFrozenRegistry<TTool extends AnyTool = AnyTool>(
|
|
120
|
+
tools: Array<TTool> = [],
|
|
121
|
+
): ToolRegistry<TTool> {
|
|
122
|
+
const toolMap = new Map<string, TTool>()
|
|
121
123
|
|
|
122
124
|
for (const tool of tools) {
|
|
123
125
|
toolMap.set(tool.name, tool)
|
|
@@ -126,9 +128,9 @@ export function createFrozenRegistry(tools: Array<Tool> = []): ToolRegistry {
|
|
|
126
128
|
const frozenTools = Object.freeze([...tools])
|
|
127
129
|
|
|
128
130
|
return {
|
|
129
|
-
getTools: () => frozenTools,
|
|
131
|
+
getTools: () => [...frozenTools],
|
|
130
132
|
|
|
131
|
-
add: (_tool:
|
|
133
|
+
add: (_tool: TTool) => {
|
|
132
134
|
// No-op for frozen registry
|
|
133
135
|
},
|
|
134
136
|
|