@tanstack/ai 0.19.1 → 0.20.1
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 +9 -3
- package/dist/esm/activities/chat/adapter.js +3 -1
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/index.d.ts +13 -4
- package/dist/esm/activities/chat/index.js +46 -18
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +1 -1
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.js +2 -0
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +8 -7
- package/dist/esm/activities/chat/stream/processor.d.ts +8 -8
- package/dist/esm/activities/chat/stream/processor.js +29 -21
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/stream/strategies.d.ts +3 -3
- package/dist/esm/activities/chat/stream/strategies.js +4 -4
- package/dist/esm/activities/chat/stream/strategies.js.map +1 -1
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js +5 -0
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.d.ts +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.js +36 -35
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +2 -2
- package/dist/esm/activities/chat/tools/tool-calls.js +17 -9
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.js +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
- package/dist/esm/activities/generateAudio/adapter.js +3 -1
- package/dist/esm/activities/generateAudio/adapter.js.map +1 -1
- package/dist/esm/activities/generateAudio/index.d.ts +5 -1
- package/dist/esm/activities/generateAudio/index.js.map +1 -1
- package/dist/esm/activities/generateImage/adapter.js +3 -1
- package/dist/esm/activities/generateImage/adapter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.js +5 -0
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/adapter.js +3 -1
- package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
- package/dist/esm/activities/generateTranscription/adapter.js +3 -1
- package/dist/esm/activities/generateTranscription/adapter.js.map +1 -1
- package/dist/esm/activities/generateVideo/adapter.js +3 -1
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
- package/dist/esm/activities/stream-generation-result.js +6 -2
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/activities/summarize/adapter.js +3 -1
- package/dist/esm/activities/summarize/adapter.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js +5 -0
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/logger/internal-logger.js +2 -0
- package/dist/esm/logger/internal-logger.js.map +1 -1
- package/dist/esm/middlewares/content-guard.js +5 -4
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/middlewares/otel.js +38 -16
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/realtime/index.d.ts +1 -1
- package/dist/esm/realtime/index.js.map +1 -1
- package/dist/esm/system-prompts.d.ts +66 -0
- package/dist/esm/system-prompts.js +23 -0
- package/dist/esm/system-prompts.js.map +1 -0
- package/dist/esm/tools/provider-tool.d.ts +9 -0
- package/dist/esm/tools/provider-tool.js +7 -0
- package/dist/esm/tools/provider-tool.js.map +1 -0
- package/dist/esm/types.d.ts +22 -7
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/package.json +2 -2
- package/src/activities/chat/adapter.ts +11 -2
- package/src/activities/chat/index.ts +46 -27
- package/src/activities/chat/messages.ts +2 -1
- package/src/activities/chat/middleware/types.ts +14 -11
- package/src/activities/chat/stream/processor.ts +19 -19
- package/src/activities/chat/stream/strategies.ts +3 -3
- package/src/activities/chat/tools/schema-converter.ts +77 -52
- package/src/activities/chat/tools/tool-calls.ts +18 -11
- package/src/activities/chat/tools/tool-definition.ts +6 -1
- package/src/activities/generateAudio/index.ts +5 -4
- package/src/activities/generateImage/index.ts +5 -0
- package/src/activities/stream-generation-result.ts +11 -2
- package/src/activities/summarize/chat-stream-summarize.ts +5 -2
- package/src/activities/summarize/index.ts +2 -2
- package/src/index.ts +5 -0
- package/src/middlewares/content-guard.ts +11 -3
- package/src/middlewares/otel.ts +47 -16
- package/src/realtime/index.ts +1 -1
- package/src/system-prompts.ts +98 -0
- package/src/tools/provider-tool.ts +14 -0
- package/src/types.ts +28 -9
- package/src/utilities/ag-ui-wire.ts +2 -2
- package/src/utilities/chat-params.ts +2 -2
|
@@ -1,11 +1,29 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
2
|
-
|
|
3
1
|
import type {
|
|
4
2
|
StandardJSONSchemaV1,
|
|
5
3
|
StandardSchemaV1,
|
|
6
4
|
} from '@standard-schema/spec'
|
|
7
5
|
import type { JSONSchema, SchemaInput } from '../../../types'
|
|
8
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Build a JSONSchema object from any plain key/value source. The `JSONSchema`
|
|
9
|
+
* interface's `[key: string]: any` index signature makes every property
|
|
10
|
+
* assignable through bracket access without a type cast — copying keys here
|
|
11
|
+
* lets us narrow either `Record<string, unknown>` (returned by
|
|
12
|
+
* `~standard.jsonSchema.input()`) or a `JSONSchema` (from the SchemaInput
|
|
13
|
+
* pass-through arm) into the typed view used by the rest of this module.
|
|
14
|
+
*
|
|
15
|
+
* Accepts `object` so callers don't need a cast when narrowing from union
|
|
16
|
+
* types like `SchemaInput`.
|
|
17
|
+
*/
|
|
18
|
+
function toJsonSchema(obj: object): JSONSchema {
|
|
19
|
+
const result: JSONSchema = {}
|
|
20
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
21
|
+
if (key === '$schema') continue // not needed by LLM providers
|
|
22
|
+
result[key] = value
|
|
23
|
+
}
|
|
24
|
+
return result
|
|
25
|
+
}
|
|
26
|
+
|
|
9
27
|
/**
|
|
10
28
|
* Check if a value is a Standard JSON Schema compliant schema.
|
|
11
29
|
* Standard JSON Schema compliant libraries (Zod v4+, ArkType, Valibot with toStandardJsonSchema, etc.)
|
|
@@ -19,6 +37,7 @@ export function isStandardJSONSchema(
|
|
|
19
37
|
schema !== null &&
|
|
20
38
|
'~standard' in schema &&
|
|
21
39
|
typeof (schema as StandardJSONSchemaV1)['~standard'] === 'object' &&
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for caller-provided unknown; type assertion narrows but doesn't validate the wire payload
|
|
22
41
|
(schema as StandardJSONSchemaV1)['~standard'].version === 1 &&
|
|
23
42
|
typeof (schema as StandardJSONSchemaV1)['~standard'].jsonSchema ===
|
|
24
43
|
'object' &&
|
|
@@ -37,9 +56,9 @@ export function isStandardSchema(schema: unknown): schema is StandardSchemaV1 {
|
|
|
37
56
|
schema !== null &&
|
|
38
57
|
'~standard' in schema &&
|
|
39
58
|
typeof schema['~standard'] === 'object' &&
|
|
40
|
-
schema !== null &&
|
|
41
59
|
schema['~standard'] !== null &&
|
|
42
60
|
'version' in schema['~standard'] &&
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for caller-provided unknown; in-operator narrows but doesn't validate the wire payload
|
|
43
62
|
schema['~standard'].version === 1 &&
|
|
44
63
|
'validate' in schema['~standard'] &&
|
|
45
64
|
typeof schema['~standard'].validate === 'function'
|
|
@@ -58,19 +77,20 @@ export function isStandardSchema(schema: unknown): schema is StandardSchemaV1 {
|
|
|
58
77
|
* @returns Transformed schema compatible with OpenAI structured output
|
|
59
78
|
*/
|
|
60
79
|
function makeStructuredOutputCompatible(
|
|
61
|
-
schema:
|
|
80
|
+
schema: JSONSchema,
|
|
62
81
|
originalRequired: Array<string> = [],
|
|
63
|
-
):
|
|
64
|
-
const result = { ...schema }
|
|
82
|
+
): JSONSchema {
|
|
83
|
+
const result: JSONSchema = { ...schema }
|
|
65
84
|
|
|
66
85
|
// Handle object types
|
|
67
86
|
if (result.type === 'object' && result.properties) {
|
|
68
|
-
const properties = { ...result.properties }
|
|
87
|
+
const properties: Record<string, JSONSchema> = { ...result.properties }
|
|
69
88
|
const allPropertyNames = Object.keys(properties)
|
|
70
89
|
|
|
71
90
|
// Transform each property
|
|
72
91
|
for (const propName of allPropertyNames) {
|
|
73
92
|
const prop = properties[propName]
|
|
93
|
+
if (!prop) continue
|
|
74
94
|
const wasOptional = !originalRequired.includes(propName)
|
|
75
95
|
|
|
76
96
|
// Recursively transform nested objects/arrays
|
|
@@ -83,12 +103,12 @@ function makeStructuredOutputCompatible(
|
|
|
83
103
|
? { ...transformed, type: ['object', 'null'] }
|
|
84
104
|
: transformed
|
|
85
105
|
} else if (prop.type === 'array' && prop.items) {
|
|
86
|
-
const
|
|
106
|
+
const items = Array.isArray(prop.items) ? prop.items[0] : prop.items
|
|
107
|
+
const transformed: JSONSchema = {
|
|
87
108
|
...prop,
|
|
88
|
-
items:
|
|
89
|
-
|
|
90
|
-
prop.items
|
|
91
|
-
),
|
|
109
|
+
items: items
|
|
110
|
+
? makeStructuredOutputCompatible(items, items.required || [])
|
|
111
|
+
: prop.items,
|
|
92
112
|
}
|
|
93
113
|
properties[propName] = wasOptional
|
|
94
114
|
? { ...transformed, type: ['array', 'null'] }
|
|
@@ -118,10 +138,10 @@ function makeStructuredOutputCompatible(
|
|
|
118
138
|
|
|
119
139
|
// Handle array types with object items
|
|
120
140
|
if (result.type === 'array' && result.items) {
|
|
121
|
-
result.items
|
|
122
|
-
|
|
123
|
-
result.items.required || []
|
|
124
|
-
|
|
141
|
+
const items = Array.isArray(result.items) ? result.items[0] : result.items
|
|
142
|
+
if (items) {
|
|
143
|
+
result.items = makeStructuredOutputCompatible(items, items.required || [])
|
|
144
|
+
}
|
|
125
145
|
}
|
|
126
146
|
|
|
127
147
|
return result
|
|
@@ -216,42 +236,33 @@ export function convertSchemaToJsonSchema(
|
|
|
216
236
|
target: 'draft-07',
|
|
217
237
|
})
|
|
218
238
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const { $schema, ...rest } = result
|
|
224
|
-
result = rest
|
|
225
|
-
}
|
|
239
|
+
// Rebuild structurally so the typed JSONSchema view is acquired without
|
|
240
|
+
// a `Record<string, unknown> as JSONSchema` cast; `toJsonSchema()` also
|
|
241
|
+
// drops the `$schema` key which LLM providers don't need.
|
|
242
|
+
let result: JSONSchema = toJsonSchema(jsonSchema)
|
|
226
243
|
|
|
227
244
|
// Ensure object schemas always have type: "object"
|
|
245
|
+
// If it has properties (even empty), it should be an object type
|
|
246
|
+
if ('properties' in result && !result.type) {
|
|
247
|
+
result.type = 'object'
|
|
248
|
+
}
|
|
228
249
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Ensure properties exists for object types (even if empty)
|
|
236
|
-
if (result.type === 'object' && !('properties' in result)) {
|
|
237
|
-
result.properties = {}
|
|
238
|
-
}
|
|
250
|
+
// Ensure properties exists for object types (even if empty)
|
|
251
|
+
if (result.type === 'object' && !('properties' in result)) {
|
|
252
|
+
result.properties = {}
|
|
253
|
+
}
|
|
239
254
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
255
|
+
// Ensure required exists for object types (even if empty array)
|
|
256
|
+
if (result.type === 'object' && !('required' in result)) {
|
|
257
|
+
result.required = []
|
|
258
|
+
}
|
|
244
259
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
result,
|
|
249
|
-
(result.required as Array<string>) || [],
|
|
250
|
-
)
|
|
251
|
-
}
|
|
260
|
+
// Apply structured output transformation if requested
|
|
261
|
+
if (forStructuredOutput) {
|
|
262
|
+
result = makeStructuredOutputCompatible(result, result.required || [])
|
|
252
263
|
}
|
|
253
264
|
|
|
254
|
-
return result
|
|
265
|
+
return result
|
|
255
266
|
}
|
|
256
267
|
|
|
257
268
|
// Detect Standard Schema validators (Zod, ArkType, Valibot, …) that don't
|
|
@@ -271,14 +282,25 @@ export function convertSchemaToJsonSchema(
|
|
|
271
282
|
// If it's not a Standard JSON Schema, assume it's already a JSONSchema and pass through
|
|
272
283
|
// Still apply structured output transformation if requested
|
|
273
284
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
285
|
+
// At this branch, `schema` is the plain `JSONSchema` arm of `SchemaInput`
|
|
286
|
+
// (the two `~standard` arms were handled above). When no transformation
|
|
287
|
+
// is requested we pass the schema through by reference to preserve
|
|
288
|
+
// identity for callers that compare via `===`.
|
|
289
|
+
if (typeof schema !== 'object') {
|
|
290
|
+
// The SchemaInput union is object-shaped on every arm; if we ever hit a
|
|
291
|
+
// non-object here, propagate it untouched and let the downstream
|
|
292
|
+
// provider error loudly rather than silently widen.
|
|
293
|
+
return schema
|
|
279
294
|
}
|
|
280
295
|
|
|
281
|
-
|
|
296
|
+
if (forStructuredOutput) {
|
|
297
|
+
// Build a typed view structurally so we don't need a SchemaInput→JSONSchema
|
|
298
|
+
// cast on the transformation path.
|
|
299
|
+
const typedView = toJsonSchema(schema)
|
|
300
|
+
return makeStructuredOutputCompatible(typedView, typedView.required || [])
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return schema
|
|
282
304
|
}
|
|
283
305
|
|
|
284
306
|
/**
|
|
@@ -293,7 +315,10 @@ export async function validateWithStandardSchema<T>(
|
|
|
293
315
|
data: unknown,
|
|
294
316
|
): Promise<
|
|
295
317
|
| { success: true; data: T }
|
|
296
|
-
| {
|
|
318
|
+
| {
|
|
319
|
+
success: false
|
|
320
|
+
issues: Array<{ message: string; path?: Array<string> | undefined }>
|
|
321
|
+
}
|
|
297
322
|
> {
|
|
298
323
|
if (!isStandardSchema(schema)) {
|
|
299
324
|
// If it's not a Standard Schema, just return the data as-is
|
|
@@ -81,8 +81,8 @@ export class MiddlewareAbortError extends Error {
|
|
|
81
81
|
* ```
|
|
82
82
|
*/
|
|
83
83
|
export class ToolCallManager {
|
|
84
|
-
private toolCallsMap = new Map<number, ToolCall>()
|
|
85
|
-
private tools: ReadonlyArray<Tool>
|
|
84
|
+
private readonly toolCallsMap = new Map<number, ToolCall>()
|
|
85
|
+
private readonly tools: ReadonlyArray<Tool>
|
|
86
86
|
|
|
87
87
|
constructor(tools: ReadonlyArray<Tool>) {
|
|
88
88
|
this.tools = tools
|
|
@@ -320,14 +320,16 @@ async function* executeWithEventPolling<T>(
|
|
|
320
320
|
])
|
|
321
321
|
|
|
322
322
|
// Flush any pending events
|
|
323
|
-
|
|
324
|
-
|
|
323
|
+
let event: CustomEvent | undefined
|
|
324
|
+
while ((event = pendingEvents.shift()) !== undefined) {
|
|
325
|
+
yield event
|
|
325
326
|
}
|
|
326
327
|
}
|
|
327
328
|
|
|
328
329
|
// Final flush in case events were emitted right at completion
|
|
329
|
-
|
|
330
|
-
|
|
330
|
+
let event: CustomEvent | undefined
|
|
331
|
+
while ((event = pendingEvents.shift()) !== undefined) {
|
|
332
|
+
yield event
|
|
331
333
|
}
|
|
332
334
|
|
|
333
335
|
return state.result
|
|
@@ -404,13 +406,17 @@ async function* executeServerTool(
|
|
|
404
406
|
): AsyncGenerator<CustomEvent, void, void> {
|
|
405
407
|
const startTime = Date.now()
|
|
406
408
|
try {
|
|
407
|
-
|
|
409
|
+
if (!tool.execute) {
|
|
410
|
+
throw new Error(`Tool ${toolName} has no execute() implementation`)
|
|
411
|
+
}
|
|
412
|
+
const executionPromise = Promise.resolve(tool.execute(input, context))
|
|
408
413
|
let result = yield* executeWithEventPolling(executionPromise, pendingEvents)
|
|
409
414
|
const duration = Date.now() - startTime
|
|
410
415
|
|
|
411
416
|
// Flush remaining events
|
|
412
|
-
|
|
413
|
-
|
|
417
|
+
let pendingEvent: CustomEvent | undefined
|
|
418
|
+
while ((pendingEvent = pendingEvents.shift()) !== undefined) {
|
|
419
|
+
yield pendingEvent
|
|
414
420
|
}
|
|
415
421
|
|
|
416
422
|
// Validate output against outputSchema if provided
|
|
@@ -448,8 +454,9 @@ async function* executeServerTool(
|
|
|
448
454
|
const duration = Date.now() - startTime
|
|
449
455
|
|
|
450
456
|
// Flush remaining events
|
|
451
|
-
|
|
452
|
-
|
|
457
|
+
let pendingEvent: CustomEvent | undefined
|
|
458
|
+
while ((pendingEvent = pendingEvents.shift()) !== undefined) {
|
|
459
|
+
yield pendingEvent
|
|
453
460
|
}
|
|
454
461
|
|
|
455
462
|
if (error instanceof MiddlewareAbortError) {
|
|
@@ -29,6 +29,11 @@ export interface ClientTool<
|
|
|
29
29
|
__toolSide: 'client'
|
|
30
30
|
name: TName
|
|
31
31
|
description: string
|
|
32
|
+
// Note: `inputSchema` / `outputSchema` stay as bare optionals (not
|
|
33
|
+
// widened to `| undefined`). They participate in inference via
|
|
34
|
+
// `InferToolInput` / `InferToolOutput` — widening with `| undefined`
|
|
35
|
+
// breaks the `infer TInput extends StandardJSONSchemaV1<...>` chain
|
|
36
|
+
// because `undefined` doesn't extend the schema constraint.
|
|
32
37
|
inputSchema?: TInput
|
|
33
38
|
outputSchema?: TOutput
|
|
34
39
|
needsApproval?: boolean
|
|
@@ -215,7 +220,7 @@ export function toolDefinition<
|
|
|
215
220
|
return {
|
|
216
221
|
__toolSide: 'client',
|
|
217
222
|
...config,
|
|
218
|
-
execute,
|
|
223
|
+
...(execute !== undefined && { execute }),
|
|
219
224
|
}
|
|
220
225
|
},
|
|
221
226
|
}
|
|
@@ -27,10 +27,11 @@ export const kind = 'audio' as const
|
|
|
27
27
|
/**
|
|
28
28
|
* Extract provider options from an AudioAdapter via ~types.
|
|
29
29
|
*/
|
|
30
|
-
export type AudioProviderOptions<TAdapter> =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
export type AudioProviderOptions<TAdapter> = TAdapter extends {
|
|
31
|
+
'~types': { providerOptions: infer P extends object }
|
|
32
|
+
}
|
|
33
|
+
? P
|
|
34
|
+
: object
|
|
34
35
|
|
|
35
36
|
// ===========================
|
|
36
37
|
// Activity Options Type
|
|
@@ -227,6 +227,11 @@ async function runGenerateImage<
|
|
|
227
227
|
requestId,
|
|
228
228
|
provider: adapter.name,
|
|
229
229
|
model,
|
|
230
|
+
// GeneratedImage is a discriminated `{ url } | { b64Json }` union, but the
|
|
231
|
+
// wire shape on the devtools event is a plain optional pair. Use
|
|
232
|
+
// conditional spreads so the emitted record only sets the field actually
|
|
233
|
+
// present — `exactOptionalPropertyTypes` rejects `field: undefined`
|
|
234
|
+
// against `field?: string` targets.
|
|
230
235
|
images: result.images.map((image) => ({
|
|
231
236
|
url: image.url,
|
|
232
237
|
b64Json: image.b64Json,
|
|
@@ -55,12 +55,21 @@ export async function* streamGenerationResult<TResult>(
|
|
|
55
55
|
}
|
|
56
56
|
} catch (error: unknown) {
|
|
57
57
|
const payload = toRunErrorPayload(error, 'Generation failed')
|
|
58
|
+
// `code` is omitted entirely when undefined so the event matches the
|
|
59
|
+
// AG-UI `code?: string` shape under `exactOptionalPropertyTypes`. The
|
|
60
|
+
// deprecated nested `error` form preserves the same conditional
|
|
61
|
+
// structure for backward compatibility.
|
|
62
|
+
const codeFields =
|
|
63
|
+
payload.code !== undefined ? { code: payload.code } : undefined
|
|
58
64
|
yield {
|
|
59
65
|
type: EventType.RUN_ERROR,
|
|
60
66
|
message: payload.message,
|
|
61
|
-
|
|
67
|
+
...codeFields,
|
|
62
68
|
// Deprecated nested form for backward compatibility
|
|
63
|
-
error:
|
|
69
|
+
error: {
|
|
70
|
+
message: payload.message,
|
|
71
|
+
...codeFields,
|
|
72
|
+
},
|
|
64
73
|
timestamp: Date.now(),
|
|
65
74
|
}
|
|
66
75
|
}
|
|
@@ -46,7 +46,7 @@ export class ChatStreamSummarizeAdapter<
|
|
|
46
46
|
> extends BaseSummarizeAdapter<TModel, TProviderOptions> {
|
|
47
47
|
readonly name: string
|
|
48
48
|
|
|
49
|
-
private textAdapter: ChatStreamCapable
|
|
49
|
+
private readonly textAdapter: ChatStreamCapable
|
|
50
50
|
|
|
51
51
|
constructor(
|
|
52
52
|
textAdapter: ChatStreamCapable,
|
|
@@ -124,7 +124,7 @@ export class ChatStreamSummarizeAdapter<
|
|
|
124
124
|
return { id, model, summary, usage }
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
async *summarizeStream(
|
|
127
|
+
override async *summarizeStream(
|
|
128
128
|
options: SummarizationOptions<TProviderOptions>,
|
|
129
129
|
): AsyncIterable<StreamChunk> {
|
|
130
130
|
const systemPrompt = this.buildSummarizationPrompt(options)
|
|
@@ -221,6 +221,9 @@ export class ChatStreamSummarizeAdapter<
|
|
|
221
221
|
case 'concise':
|
|
222
222
|
prompt += 'Provide a very concise summary in 1-2 sentences. '
|
|
223
223
|
break
|
|
224
|
+
case undefined:
|
|
225
|
+
prompt += 'Provide a clear and concise summary. '
|
|
226
|
+
break
|
|
224
227
|
default:
|
|
225
228
|
prompt += 'Provide a clear and concise summary. '
|
|
226
229
|
}
|
|
@@ -159,7 +159,7 @@ export function summarize<
|
|
|
159
159
|
|
|
160
160
|
if (stream) {
|
|
161
161
|
return runStreamingSummarize(
|
|
162
|
-
options as
|
|
162
|
+
options as SummarizeActivityOptions<
|
|
163
163
|
SummarizeAdapter<string, object>,
|
|
164
164
|
true
|
|
165
165
|
>,
|
|
@@ -167,7 +167,7 @@ export function summarize<
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
return runSummarize(
|
|
170
|
-
options as
|
|
170
|
+
options as SummarizeActivityOptions<
|
|
171
171
|
SummarizeAdapter<string, object>,
|
|
172
172
|
false
|
|
173
173
|
>,
|
package/src/index.ts
CHANGED
|
@@ -69,6 +69,7 @@ export { ToolCallManager } from './activities/chat/tools/tool-calls'
|
|
|
69
69
|
|
|
70
70
|
// Provider tool type
|
|
71
71
|
export type { ProviderTool } from './tools/provider-tool'
|
|
72
|
+
export { brandProviderTool } from './tools/provider-tool'
|
|
72
73
|
|
|
73
74
|
// Agent loop strategies
|
|
74
75
|
export {
|
|
@@ -104,6 +105,10 @@ export type {
|
|
|
104
105
|
// All types
|
|
105
106
|
export * from './types'
|
|
106
107
|
|
|
108
|
+
// System prompts (type + normaliser used by adapters)
|
|
109
|
+
export type { SystemPrompt, NormalizedSystemPrompt } from './system-prompts'
|
|
110
|
+
export { normalizeSystemPrompts } from './system-prompts'
|
|
111
|
+
|
|
107
112
|
// Utility functions
|
|
108
113
|
export { detectImageMimeType } from './utils'
|
|
109
114
|
|
|
@@ -142,10 +142,15 @@ function createDeltaStrategy(
|
|
|
142
142
|
|
|
143
143
|
if (blockOnMatch) return null // drop chunk
|
|
144
144
|
|
|
145
|
+
// Strip out the previous `content` field by destructuring it away — with
|
|
146
|
+
// `exactOptionalPropertyTypes` we can't assign `content: undefined`
|
|
147
|
+
// against `content?: string`. The replacement event carries only the
|
|
148
|
+
// filtered delta.
|
|
149
|
+
const { content: _strippedContent, ...rest } = chunk
|
|
150
|
+
void _strippedContent
|
|
145
151
|
return {
|
|
146
|
-
...
|
|
152
|
+
...rest,
|
|
147
153
|
delta: filtered,
|
|
148
|
-
content: undefined,
|
|
149
154
|
} as StreamChunk
|
|
150
155
|
},
|
|
151
156
|
}
|
|
@@ -278,8 +283,11 @@ function createBufferedStrategy(
|
|
|
278
283
|
content: filtered.slice(0, safeFilteredEnd),
|
|
279
284
|
} as StreamChunk
|
|
280
285
|
|
|
286
|
+
// `pending` was empty before this push iff `emitChunk` is now the only
|
|
287
|
+
// entry — return it directly without re-indexing through `pending[0]`.
|
|
288
|
+
const wasEmpty = pending.length === 0
|
|
281
289
|
pending.push(emitChunk)
|
|
282
|
-
return
|
|
290
|
+
return wasEmpty ? emitChunk : pending
|
|
283
291
|
},
|
|
284
292
|
}
|
|
285
293
|
}
|
package/src/middlewares/otel.ts
CHANGED
|
@@ -137,8 +137,11 @@ function serializeContent(content: unknown): string {
|
|
|
137
137
|
case 'document':
|
|
138
138
|
parts.push('[document]')
|
|
139
139
|
break
|
|
140
|
+
case undefined:
|
|
141
|
+
parts.push('[unknown]')
|
|
142
|
+
break
|
|
140
143
|
default:
|
|
141
|
-
parts.push(`[${type
|
|
144
|
+
parts.push(`[${type}]`)
|
|
142
145
|
}
|
|
143
146
|
}
|
|
144
147
|
return parts.join(' ')
|
|
@@ -372,9 +375,29 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
|
|
|
372
375
|
state.assistantTextBufferTruncated = false
|
|
373
376
|
|
|
374
377
|
if (captureContent) {
|
|
378
|
+
const systemPromptContents = config.systemPrompts.map((p) =>
|
|
379
|
+
typeof p === 'string' ? p : p.content,
|
|
380
|
+
)
|
|
381
|
+
// Anthropic prompt-caching users need to know which prompt carried
|
|
382
|
+
// `cache_control`: it's the one attribute that explains cache
|
|
383
|
+
// hit/miss in observability. Serialise per-prompt metadata as a
|
|
384
|
+
// single JSON span attribute so backends that don't understand
|
|
385
|
+
// GenAI events can still surface it. Kept off span events to
|
|
386
|
+
// avoid breaking the one-event-per-message GenAI semconv contract.
|
|
387
|
+
const systemPromptMetadata = config.systemPrompts.map((p) =>
|
|
388
|
+
typeof p === 'string' || p.metadata === undefined
|
|
389
|
+
? null
|
|
390
|
+
: p.metadata,
|
|
391
|
+
)
|
|
392
|
+
if (systemPromptMetadata.some((m) => m !== null)) {
|
|
393
|
+
iterSpan.setAttribute(
|
|
394
|
+
'tanstack.ai.system_prompt.metadata',
|
|
395
|
+
JSON.stringify(systemPromptMetadata),
|
|
396
|
+
)
|
|
397
|
+
}
|
|
375
398
|
// Span events follow the original GenAI semconv (one event per
|
|
376
399
|
// message). Backends that read events get content this way.
|
|
377
|
-
for (const sys of
|
|
400
|
+
for (const sys of systemPromptContents) {
|
|
378
401
|
iterSpan.addEvent('gen_ai.system.message', {
|
|
379
402
|
content: redactContent(sys),
|
|
380
403
|
})
|
|
@@ -391,7 +414,7 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
|
|
|
391
414
|
// (`gen_ai.input.messages`) — backends like PostHog read prompt
|
|
392
415
|
// content from this attribute, not from span events.
|
|
393
416
|
const inputMessages: Array<{ role: string; content: string }> = []
|
|
394
|
-
for (const sys of
|
|
417
|
+
for (const sys of systemPromptContents) {
|
|
395
418
|
inputMessages.push({
|
|
396
419
|
role: 'system',
|
|
397
420
|
content: redactContent(sys),
|
|
@@ -610,9 +633,10 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
|
|
|
610
633
|
|
|
611
634
|
if (!info.ok && info.error !== undefined) {
|
|
612
635
|
toolSpan.recordException(info.error as Exception)
|
|
636
|
+
const msg = errorMessage(info.error)
|
|
613
637
|
toolSpan.setStatus({
|
|
614
638
|
code: SpanStatusCode.ERROR,
|
|
615
|
-
message:
|
|
639
|
+
...(msg !== undefined && { message: msg }),
|
|
616
640
|
})
|
|
617
641
|
}
|
|
618
642
|
|
|
@@ -668,13 +692,16 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
|
|
|
668
692
|
|
|
669
693
|
const errType = errorTypeName(info.error)
|
|
670
694
|
const message = errorMessage(info.error)
|
|
695
|
+
const statusMessage =
|
|
696
|
+
message !== undefined ? { message } : ({} as const)
|
|
671
697
|
const exception = info.error as Exception
|
|
672
698
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
699
|
+
const iterationSpan = state.currentIterationSpan
|
|
700
|
+
if (iterationSpan) {
|
|
701
|
+
iterationSpan.recordException(exception)
|
|
702
|
+
iterationSpan.setStatus({
|
|
676
703
|
code: SpanStatusCode.ERROR,
|
|
677
|
-
|
|
704
|
+
...statusMessage,
|
|
678
705
|
})
|
|
679
706
|
safeCall('otel.onSpanEnd', () =>
|
|
680
707
|
onSpanEnd?.(
|
|
@@ -683,17 +710,17 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
|
|
|
683
710
|
ctx,
|
|
684
711
|
iteration: state.iterationCount - 1,
|
|
685
712
|
} as OtelSpanInfo<'iteration'>,
|
|
686
|
-
|
|
713
|
+
iterationSpan,
|
|
687
714
|
),
|
|
688
715
|
)
|
|
689
|
-
|
|
716
|
+
iterationSpan.end()
|
|
690
717
|
state.currentIterationSpan = null
|
|
691
718
|
}
|
|
692
719
|
|
|
693
720
|
for (const [id, entry] of state.toolSpans) {
|
|
694
721
|
const { span, toolName } = entry
|
|
695
722
|
span.recordException(exception)
|
|
696
|
-
span.setStatus({ code: SpanStatusCode.ERROR,
|
|
723
|
+
span.setStatus({ code: SpanStatusCode.ERROR, ...statusMessage })
|
|
697
724
|
safeCall('otel.onSpanEnd', () =>
|
|
698
725
|
onSpanEnd?.(
|
|
699
726
|
{
|
|
@@ -711,7 +738,10 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
|
|
|
711
738
|
}
|
|
712
739
|
|
|
713
740
|
state.rootSpan.recordException(exception)
|
|
714
|
-
state.rootSpan.setStatus({
|
|
741
|
+
state.rootSpan.setStatus({
|
|
742
|
+
code: SpanStatusCode.ERROR,
|
|
743
|
+
...statusMessage,
|
|
744
|
+
})
|
|
715
745
|
|
|
716
746
|
if (durationHistogram) {
|
|
717
747
|
durationHistogram.record(info.duration / 1000, {
|
|
@@ -743,8 +773,9 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
|
|
|
743
773
|
span.setStatus({ code: SpanStatusCode.ERROR, message: 'cancelled' })
|
|
744
774
|
}
|
|
745
775
|
|
|
746
|
-
|
|
747
|
-
|
|
776
|
+
const iterationSpan = state.currentIterationSpan
|
|
777
|
+
if (iterationSpan) {
|
|
778
|
+
closeCancelled(iterationSpan)
|
|
748
779
|
safeCall('otel.onSpanEnd', () =>
|
|
749
780
|
onSpanEnd?.(
|
|
750
781
|
{
|
|
@@ -752,10 +783,10 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
|
|
|
752
783
|
ctx,
|
|
753
784
|
iteration: state.iterationCount - 1,
|
|
754
785
|
} as OtelSpanInfo<'iteration'>,
|
|
755
|
-
|
|
786
|
+
iterationSpan,
|
|
756
787
|
),
|
|
757
788
|
)
|
|
758
|
-
|
|
789
|
+
iterationSpan.end()
|
|
759
790
|
state.currentIterationSpan = null
|
|
760
791
|
}
|
|
761
792
|
for (const [id, entry] of state.toolSpans) {
|