@tanstack/ai 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/esm/activities/chat/adapter.js +3 -1
  2. package/dist/esm/activities/chat/adapter.js.map +1 -1
  3. package/dist/esm/activities/chat/index.d.ts +4 -4
  4. package/dist/esm/activities/chat/index.js +403 -276
  5. package/dist/esm/activities/chat/index.js.map +1 -1
  6. package/dist/esm/activities/chat/messages.js +1 -1
  7. package/dist/esm/activities/chat/messages.js.map +1 -1
  8. package/dist/esm/activities/chat/middleware/compose.d.ts +10 -1
  9. package/dist/esm/activities/chat/middleware/compose.js +57 -0
  10. package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
  11. package/dist/esm/activities/chat/middleware/index.d.ts +1 -1
  12. package/dist/esm/activities/chat/middleware/types.d.ts +40 -8
  13. package/dist/esm/activities/chat/stream/processor.d.ts +8 -8
  14. package/dist/esm/activities/chat/stream/processor.js +29 -21
  15. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  16. package/dist/esm/activities/chat/stream/strategies.d.ts +3 -3
  17. package/dist/esm/activities/chat/stream/strategies.js +4 -4
  18. package/dist/esm/activities/chat/stream/strategies.js.map +1 -1
  19. package/dist/esm/activities/chat/tools/lazy-tool-manager.js +5 -0
  20. package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
  21. package/dist/esm/activities/chat/tools/schema-converter.d.ts +13 -2
  22. package/dist/esm/activities/chat/tools/schema-converter.js +47 -37
  23. package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
  24. package/dist/esm/activities/chat/tools/tool-calls.d.ts +2 -2
  25. package/dist/esm/activities/chat/tools/tool-calls.js +17 -9
  26. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  27. package/dist/esm/activities/chat/tools/tool-definition.js +1 -1
  28. package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
  29. package/dist/esm/activities/generateAudio/adapter.js +3 -1
  30. package/dist/esm/activities/generateAudio/adapter.js.map +1 -1
  31. package/dist/esm/activities/generateAudio/index.d.ts +5 -1
  32. package/dist/esm/activities/generateAudio/index.js.map +1 -1
  33. package/dist/esm/activities/generateImage/adapter.js +3 -1
  34. package/dist/esm/activities/generateImage/adapter.js.map +1 -1
  35. package/dist/esm/activities/generateImage/index.js +5 -0
  36. package/dist/esm/activities/generateImage/index.js.map +1 -1
  37. package/dist/esm/activities/generateSpeech/adapter.js +3 -1
  38. package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
  39. package/dist/esm/activities/generateTranscription/adapter.js +3 -1
  40. package/dist/esm/activities/generateTranscription/adapter.js.map +1 -1
  41. package/dist/esm/activities/generateVideo/adapter.js +3 -1
  42. package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
  43. package/dist/esm/activities/stream-generation-result.js +6 -2
  44. package/dist/esm/activities/stream-generation-result.js.map +1 -1
  45. package/dist/esm/activities/summarize/adapter.js +3 -1
  46. package/dist/esm/activities/summarize/adapter.js.map +1 -1
  47. package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +1 -1
  48. package/dist/esm/activities/summarize/chat-stream-summarize.js +5 -0
  49. package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
  50. package/dist/esm/activities/summarize/index.js.map +1 -1
  51. package/dist/esm/extend-adapter.js.map +1 -1
  52. package/dist/esm/index.d.ts +3 -2
  53. package/dist/esm/index.js +4 -1
  54. package/dist/esm/index.js.map +1 -1
  55. package/dist/esm/logger/internal-logger.js +2 -0
  56. package/dist/esm/logger/internal-logger.js.map +1 -1
  57. package/dist/esm/middlewares/content-guard.js +5 -4
  58. package/dist/esm/middlewares/content-guard.js.map +1 -1
  59. package/dist/esm/middlewares/otel.js +25 -18
  60. package/dist/esm/middlewares/otel.js.map +1 -1
  61. package/dist/esm/realtime/index.d.ts +1 -1
  62. package/dist/esm/realtime/index.js.map +1 -1
  63. package/dist/esm/strip-to-spec-middleware.js.map +1 -1
  64. package/dist/esm/tools/provider-tool.d.ts +9 -0
  65. package/dist/esm/tools/provider-tool.js +7 -0
  66. package/dist/esm/tools/provider-tool.js.map +1 -0
  67. package/dist/esm/types.d.ts +6 -6
  68. package/dist/esm/utilities/ag-ui-wire.js +4 -1
  69. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  70. package/dist/esm/utilities/chat-params.js.map +1 -1
  71. package/package.json +2 -2
  72. package/skills/ai-core/middleware/SKILL.md +124 -18
  73. package/skills/ai-core/structured-outputs/SKILL.md +13 -0
  74. package/src/activities/chat/index.ts +685 -394
  75. package/src/activities/chat/messages.ts +3 -2
  76. package/src/activities/chat/middleware/compose.ts +65 -5
  77. package/src/activities/chat/middleware/index.ts +1 -0
  78. package/src/activities/chat/middleware/types.ts +64 -11
  79. package/src/activities/chat/stream/processor.ts +21 -21
  80. package/src/activities/chat/stream/strategies.ts +3 -3
  81. package/src/activities/chat/tools/schema-converter.ts +98 -58
  82. package/src/activities/chat/tools/tool-calls.ts +18 -11
  83. package/src/activities/chat/tools/tool-definition.ts +6 -1
  84. package/src/activities/generateAudio/index.ts +5 -4
  85. package/src/activities/generateImage/index.ts +8 -3
  86. package/src/activities/stream-generation-result.ts +11 -2
  87. package/src/activities/summarize/chat-stream-summarize.ts +5 -2
  88. package/src/activities/summarize/index.ts +2 -2
  89. package/src/extend-adapter.ts +1 -1
  90. package/src/index.ts +6 -1
  91. package/src/middlewares/content-guard.ts +12 -4
  92. package/src/middlewares/otel.ts +32 -24
  93. package/src/realtime/index.ts +1 -1
  94. package/src/strip-to-spec-middleware.ts +1 -4
  95. package/src/tools/provider-tool.ts +14 -0
  96. package/src/types.ts +12 -8
  97. package/src/utilities/ag-ui-wire.ts +4 -3
  98. package/src/utilities/chat-params.ts +2 -2
@@ -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
- while (pendingEvents.length > 0) {
324
- yield pendingEvents.shift()!
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
- while (pendingEvents.length > 0) {
330
- yield pendingEvents.shift()!
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
- const executionPromise = Promise.resolve(tool.execute!(input, context))
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
- while (pendingEvents.length > 0) {
413
- yield pendingEvents.shift()!
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
- while (pendingEvents.length > 0) {
452
- yield pendingEvents.shift()!
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
- TAdapter extends AudioAdapter<any, any>
32
- ? TAdapter['~types']['providerOptions']
33
- : object
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
@@ -210,7 +210,7 @@ async function runGenerateImage<
210
210
  prompt: rest.prompt,
211
211
  numberOfImages: rest.numberOfImages,
212
212
  size: rest.size,
213
- modelOptions: rest.modelOptions as Record<string, unknown> | undefined,
213
+ modelOptions: rest.modelOptions,
214
214
  timestamp: startTime,
215
215
  })
216
216
 
@@ -227,12 +227,17 @@ 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,
233
238
  })),
234
239
  duration,
235
- modelOptions: rest.modelOptions as Record<string, unknown> | undefined,
240
+ modelOptions: rest.modelOptions,
236
241
  timestamp: Date.now(),
237
242
  })
238
243
 
@@ -241,7 +246,7 @@ async function runGenerateImage<
241
246
  requestId,
242
247
  model,
243
248
  usage: result.usage,
244
- modelOptions: rest.modelOptions as Record<string, unknown> | undefined,
249
+ modelOptions: rest.modelOptions,
245
250
  timestamp: Date.now(),
246
251
  })
247
252
  }
@@ -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
- code: payload.code,
67
+ ...codeFields,
62
68
  // Deprecated nested form for backward compatibility
63
- error: payload,
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 unknown as SummarizeActivityOptions<
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 unknown as SummarizeActivityOptions<
170
+ options as SummarizeActivityOptions<
171
171
  SummarizeAdapter<string, object>,
172
172
  false
173
173
  >,
@@ -65,7 +65,7 @@ export function createModel<
65
65
  return {
66
66
  name,
67
67
  input,
68
- modelOptions: {} as unknown,
68
+ modelOptions: {},
69
69
  }
70
70
  }
71
71
 
package/src/index.ts CHANGED
@@ -53,7 +53,10 @@ export {
53
53
  } from './activities/chat/tools/tool-definition'
54
54
 
55
55
  // Schema conversion (Standard JSON Schema compliant)
56
- export { convertSchemaToJsonSchema } from './activities/chat/tools/schema-converter'
56
+ export {
57
+ convertSchemaToJsonSchema,
58
+ StandardSchemaValidationError,
59
+ } from './activities/chat/tools/schema-converter'
57
60
 
58
61
  // Stream utilities
59
62
  export {
@@ -69,6 +72,7 @@ export { ToolCallManager } from './activities/chat/tools/tool-calls'
69
72
 
70
73
  // Provider tool type
71
74
  export type { ProviderTool } from './tools/provider-tool'
75
+ export { brandProviderTool } from './tools/provider-tool'
72
76
 
73
77
  // Agent loop strategies
74
78
  export {
@@ -90,6 +94,7 @@ export type {
90
94
  ChatMiddlewareContext,
91
95
  ChatMiddlewarePhase,
92
96
  ChatMiddlewareConfig,
97
+ StructuredOutputMiddlewareConfig,
93
98
  ToolCallHookContext,
94
99
  BeforeToolCallDecision,
95
100
  AfterToolCallInfo,
@@ -142,11 +142,16 @@ 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
- ...chunk,
152
+ ...rest,
147
153
  delta: filtered,
148
- content: undefined,
149
- } as StreamChunk
154
+ }
150
155
  },
151
156
  }
152
157
  }
@@ -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 pending.length === 1 ? pending[0]! : pending
290
+ return wasEmpty ? emitChunk : pending
283
291
  },
284
292
  }
285
293
  }
@@ -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 ?? 'unknown'}]`)
144
+ parts.push(`[${type}]`)
142
145
  }
143
146
  }
144
147
  return parts.join(' ')
@@ -253,10 +256,7 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
253
256
  const span = state.currentIterationSpan
254
257
  const iteration = state.iterationCount - 1
255
258
  safeCall('otel.onSpanEnd', () =>
256
- onSpanEnd?.(
257
- { kind: 'iteration', ctx, iteration } as OtelSpanInfo<'iteration'>,
258
- span,
259
- ),
259
+ onSpanEnd?.({ kind: 'iteration', ctx, iteration }, span),
260
260
  )
261
261
  span.end()
262
262
  state.currentIterationSpan = null
@@ -630,9 +630,10 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
630
630
 
631
631
  if (!info.ok && info.error !== undefined) {
632
632
  toolSpan.recordException(info.error as Exception)
633
+ const msg = errorMessage(info.error)
633
634
  toolSpan.setStatus({
634
635
  code: SpanStatusCode.ERROR,
635
- message: errorMessage(info.error),
636
+ ...(msg !== undefined && { message: msg }),
636
637
  })
637
638
  }
638
639
 
@@ -672,7 +673,7 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
672
673
  toolName: info.toolName,
673
674
  toolCallId: info.toolCallId,
674
675
  iteration: state.iterationCount - 1,
675
- } as OtelSpanInfo<'tool'>,
676
+ },
676
677
  toolSpan,
677
678
  ),
678
679
  )
@@ -688,13 +689,16 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
688
689
 
689
690
  const errType = errorTypeName(info.error)
690
691
  const message = errorMessage(info.error)
692
+ const statusMessage =
693
+ message !== undefined ? { message } : ({} as const)
691
694
  const exception = info.error as Exception
692
695
 
693
- if (state.currentIterationSpan) {
694
- state.currentIterationSpan.recordException(exception)
695
- state.currentIterationSpan.setStatus({
696
+ const iterationSpan = state.currentIterationSpan
697
+ if (iterationSpan) {
698
+ iterationSpan.recordException(exception)
699
+ iterationSpan.setStatus({
696
700
  code: SpanStatusCode.ERROR,
697
- message,
701
+ ...statusMessage,
698
702
  })
699
703
  safeCall('otel.onSpanEnd', () =>
700
704
  onSpanEnd?.(
@@ -702,18 +706,18 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
702
706
  kind: 'iteration',
703
707
  ctx,
704
708
  iteration: state.iterationCount - 1,
705
- } as OtelSpanInfo<'iteration'>,
706
- state.currentIterationSpan!,
709
+ },
710
+ iterationSpan,
707
711
  ),
708
712
  )
709
- state.currentIterationSpan.end()
713
+ iterationSpan.end()
710
714
  state.currentIterationSpan = null
711
715
  }
712
716
 
713
717
  for (const [id, entry] of state.toolSpans) {
714
718
  const { span, toolName } = entry
715
719
  span.recordException(exception)
716
- span.setStatus({ code: SpanStatusCode.ERROR, message })
720
+ span.setStatus({ code: SpanStatusCode.ERROR, ...statusMessage })
717
721
  safeCall('otel.onSpanEnd', () =>
718
722
  onSpanEnd?.(
719
723
  {
@@ -722,7 +726,7 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
722
726
  toolCallId: id,
723
727
  toolName,
724
728
  iteration: state.iterationCount - 1,
725
- } as OtelSpanInfo<'tool'>,
729
+ },
726
730
  span,
727
731
  ),
728
732
  )
@@ -731,7 +735,10 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
731
735
  }
732
736
 
733
737
  state.rootSpan.recordException(exception)
734
- state.rootSpan.setStatus({ code: SpanStatusCode.ERROR, message })
738
+ state.rootSpan.setStatus({
739
+ code: SpanStatusCode.ERROR,
740
+ ...statusMessage,
741
+ })
735
742
 
736
743
  if (durationHistogram) {
737
744
  durationHistogram.record(info.duration / 1000, {
@@ -763,19 +770,20 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
763
770
  span.setStatus({ code: SpanStatusCode.ERROR, message: 'cancelled' })
764
771
  }
765
772
 
766
- if (state.currentIterationSpan) {
767
- closeCancelled(state.currentIterationSpan)
773
+ const iterationSpan = state.currentIterationSpan
774
+ if (iterationSpan) {
775
+ closeCancelled(iterationSpan)
768
776
  safeCall('otel.onSpanEnd', () =>
769
777
  onSpanEnd?.(
770
778
  {
771
779
  kind: 'iteration',
772
780
  ctx,
773
781
  iteration: state.iterationCount - 1,
774
- } as OtelSpanInfo<'iteration'>,
775
- state.currentIterationSpan!,
782
+ },
783
+ iterationSpan,
776
784
  ),
777
785
  )
778
- state.currentIterationSpan.end()
786
+ iterationSpan.end()
779
787
  state.currentIterationSpan = null
780
788
  }
781
789
  for (const [id, entry] of state.toolSpans) {
@@ -789,7 +797,7 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
789
797
  toolCallId: id,
790
798
  toolName,
791
799
  iteration: state.iterationCount - 1,
792
- } as OtelSpanInfo<'tool'>,
800
+ },
793
801
  span,
794
802
  ),
795
803
  )
@@ -834,7 +842,7 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
834
842
  toolCallId: id,
835
843
  toolName,
836
844
  iteration: state.iterationCount - 1,
837
- } as OtelSpanInfo<'tool'>,
845
+ },
838
846
  span,
839
847
  ),
840
848
  )
@@ -1,7 +1,7 @@
1
1
  import type { RealtimeToken, RealtimeTokenOptions } from './types'
2
2
 
3
3
  // Re-export all types
4
- export * from './types'
4
+ export type * from './types'
5
5
 
6
6
  /**
7
7
  * Generate a realtime token using the provided adapter.
@@ -12,10 +12,7 @@ import type { StreamChunk } from './types'
12
12
  */
13
13
  export function stripToSpec(chunk: StreamChunk): StreamChunk {
14
14
  // Only strip the deprecated nested error object from RUN_ERROR
15
- if (
16
- (chunk as StreamChunk & { type: string }).type === 'RUN_ERROR' &&
17
- 'error' in chunk
18
- ) {
15
+ if (chunk.type === 'RUN_ERROR' && 'error' in chunk) {
19
16
  const { error: _deprecated, ...rest } = chunk as Record<string, unknown>
20
17
  return rest as StreamChunk
21
18
  }
@@ -23,3 +23,17 @@ export interface ProviderTool<
23
23
  readonly '~provider': TProvider
24
24
  readonly '~toolKind': TKind
25
25
  }
26
+
27
+ /**
28
+ * Attach the `ProviderTool` phantom brand to a plain `Tool`-shaped object.
29
+ *
30
+ * The brand fields (`'~provider'`, `'~toolKind'`) exist only in the type
31
+ * system and are never assigned at runtime, so this is a single audited
32
+ * type-only assertion. Use it inside adapter `xxxTool()` factories instead
33
+ * of `as unknown as` — the cast collapses to one named site.
34
+ */
35
+ export function brandProviderTool<T extends ProviderTool<string, string>>(
36
+ tool: Omit<T, '~provider' | '~toolKind'>,
37
+ ): T {
38
+ return tool as T
39
+ }
package/src/types.ts CHANGED
@@ -586,7 +586,9 @@ export interface Tool<
586
586
  * return weather; // Can return object or string
587
587
  * }
588
588
  */
589
- execute?: (args: any, context?: ToolExecutionContext) => Promise<any> | any
589
+ execute?:
590
+ | ((args: any, context?: ToolExecutionContext) => Promise<any> | any)
591
+ | undefined
590
592
 
591
593
  /** If true, tool execution requires user approval before running. Works with both server and client tools. */
592
594
  needsApproval?: boolean
@@ -595,7 +597,7 @@ export interface Tool<
595
597
  lazy?: boolean
596
598
 
597
599
  /** Additional metadata for adapters or custom extensions */
598
- metadata?: Record<string, any>
600
+ metadata?: Record<string, any> | undefined
599
601
  }
600
602
 
601
603
  export interface ToolConfig {
@@ -729,7 +731,7 @@ export interface TextOptions<
729
731
  > {
730
732
  model: string
731
733
  messages: Array<ModelMessage>
732
- tools?: Array<Tool<any, any, any>>
734
+ tools?: Array<Tool<any, any, any>> | undefined
733
735
  /**
734
736
  * System prompts to include with the request.
735
737
  *
@@ -791,7 +793,7 @@ export interface TextOptions<
791
793
  * - Anthropic: `metadata` (Record<string, any>) - includes optional user_id (max 256 chars)
792
794
  * - Gemini: Not directly available in TextProviderOptions
793
795
  */
794
- metadata?: Record<string, any>
796
+ metadata?: Record<string, any> | undefined
795
797
  modelOptions?: TProviderOptionsForModel
796
798
  request?: Request | RequestInit
797
799
 
@@ -938,10 +940,12 @@ export interface RunErrorEvent extends AGUIRunErrorEvent {
938
940
  * @deprecated Use top-level `message` and `code` fields instead.
939
941
  * Kept for backward compatibility.
940
942
  */
941
- error?: {
942
- message: string
943
- code?: string
944
- }
943
+ error?:
944
+ | {
945
+ message: string
946
+ code?: string | undefined
947
+ }
948
+ | undefined
945
949
  }
946
950
 
947
951
  /**
@@ -53,6 +53,7 @@ export function uiMessagesToWire(
53
53
  // Defensive: if parts is missing (ModelMessage-shaped input), pass through as-is.
54
54
  // UIMessage always has parts; ModelMessage uses content directly.
55
55
  const parts: ReadonlyArray<MessagePart> =
56
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- runtime input may be ModelMessage-shaped (no `parts`); cast forces the optional-chain fallback below to remain in scope
56
57
  (msg.parts as ReadonlyArray<MessagePart> | undefined) ?? []
57
58
 
58
59
  if (msg.role === 'system') {
@@ -61,7 +62,7 @@ export function uiMessagesToWire(
61
62
  content:
62
63
  parts.length > 0
63
64
  ? collectText(parts)
64
- : ((msg as unknown as { content?: string }).content ?? ''),
65
+ : ((msg as { content?: string }).content ?? ''),
65
66
  })
66
67
  continue
67
68
  }
@@ -72,7 +73,7 @@ export function uiMessagesToWire(
72
73
  content:
73
74
  parts.length > 0
74
75
  ? collectUserContent(parts)
75
- : ((msg as unknown as { content?: string }).content ?? ''),
76
+ : ((msg as { content?: string }).content ?? ''),
76
77
  })
77
78
  continue
78
79
  }
@@ -161,7 +162,7 @@ function collectUserContent(
161
162
  p.type === 'video' ||
162
163
  p.type === 'document'
163
164
  ) {
164
- out.push(p as AGUIInputContent)
165
+ out.push(p)
165
166
  }
166
167
  }
167
168
  return out
@@ -131,7 +131,7 @@ export async function chatParamsFromRequest(
131
131
  'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',
132
132
  { status: 400 },
133
133
  )
134
- ;(res as unknown as { cause?: unknown }).cause = cause
134
+ ;(res as { cause?: unknown }).cause = cause
135
135
  throw res
136
136
  }
137
137
  try {
@@ -145,7 +145,7 @@ export async function chatParamsFromRequest(
145
145
  'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',
146
146
  { status: 400 },
147
147
  )
148
- ;(res as unknown as { cause?: unknown }).cause = cause
148
+ ;(res as { cause?: unknown }).cause = cause
149
149
  throw res
150
150
  }
151
151
  }