@tanstack/ai 0.44.1 → 0.45.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 (53) hide show
  1. package/dist/esm/activities/chat/adapter.d.ts +13 -1
  2. package/dist/esm/activities/chat/adapter.js.map +1 -1
  3. package/dist/esm/activities/chat/index.js +108 -68
  4. package/dist/esm/activities/chat/index.js.map +1 -1
  5. package/dist/esm/activities/chat/messages.js +24 -27
  6. package/dist/esm/activities/chat/messages.js.map +1 -1
  7. package/dist/esm/activities/chat/stream/processor.js +14 -13
  8. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  9. package/dist/esm/activities/chat/tools/approval-schema.js +11 -8
  10. package/dist/esm/activities/chat/tools/approval-schema.js.map +1 -1
  11. package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
  12. package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
  13. package/dist/esm/activities/chat/tools/tool-calls.js +40 -31
  14. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  15. package/dist/esm/activities/generateVideo/index.js +3 -2
  16. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  17. package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
  18. package/dist/esm/adapter-internals.d.ts +2 -0
  19. package/dist/esm/adapter-internals.js +3 -1
  20. package/dist/esm/interrupt-resume.js +24 -20
  21. package/dist/esm/interrupt-resume.js.map +1 -1
  22. package/dist/esm/interrupts.js +2 -1
  23. package/dist/esm/interrupts.js.map +1 -1
  24. package/dist/esm/logger/console-logger.js +1 -3
  25. package/dist/esm/logger/console-logger.js.map +1 -1
  26. package/dist/esm/logger/resolve.js +2 -1
  27. package/dist/esm/logger/resolve.js.map +1 -1
  28. package/dist/esm/stream-durability.js +1 -1
  29. package/dist/esm/stream-durability.js.map +1 -1
  30. package/dist/esm/types.d.ts +19 -16
  31. package/dist/esm/utilities/chat-params.js +1 -3
  32. package/dist/esm/utilities/chat-params.js.map +1 -1
  33. package/dist/esm/utilities/media-prompt.js +1 -3
  34. package/dist/esm/utilities/media-prompt.js.map +1 -1
  35. package/dist/esm/utilities/structured-output-events.d.ts +17 -0
  36. package/dist/esm/utilities/structured-output-events.js +32 -0
  37. package/dist/esm/utilities/structured-output-events.js.map +1 -0
  38. package/dist/esm/utilities/structured-output-text.d.ts +7 -0
  39. package/dist/esm/utilities/structured-output-text.js +50 -0
  40. package/dist/esm/utilities/structured-output-text.js.map +1 -0
  41. package/package.json +2 -2
  42. package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +6 -2
  43. package/skills/ai-core/media-generation/SKILL.md +33 -19
  44. package/skills/ai-core/structured-outputs/SKILL.md +73 -1
  45. package/skills/ai-core/tool-calling/SKILL.md +1 -1
  46. package/src/activities/chat/adapter.ts +16 -1
  47. package/src/activities/chat/index.ts +128 -47
  48. package/src/activities/chat/stream/processor.ts +6 -3
  49. package/src/activities/chat/tools/tool-calls.ts +12 -4
  50. package/src/adapter-internals.ts +8 -0
  51. package/src/types.ts +27 -19
  52. package/src/utilities/structured-output-events.ts +44 -0
  53. package/src/utilities/structured-output-text.ts +63 -0
@@ -1854,9 +1854,10 @@ export class StreamProcessor {
1854
1854
 
1855
1855
  if (chunk.name === 'structured-output.start' && chunk.value) {
1856
1856
  const v = chunk.value as { messageId?: string }
1857
- const targetId = v.messageId ?? messageId
1857
+ const { messageId: targetId } = this.ensureAssistantMessage(
1858
+ v.messageId ?? messageId ?? undefined,
1859
+ )
1858
1860
  if (targetId) {
1859
- this.ensureAssistantMessage(targetId)
1860
1861
  this.structuredMessageIds.add(targetId)
1861
1862
  this.structuredOutputUpdateBatches.delete(targetId)
1862
1863
  this.events.onStructuredOutputChange?.({
@@ -1876,7 +1877,9 @@ export class StreamProcessor {
1876
1877
  reasoning?: string
1877
1878
  messageId?: string
1878
1879
  }
1879
- const targetId = v.messageId ?? messageId
1880
+ const { messageId: targetId } = this.ensureAssistantMessage(
1881
+ v.messageId ?? messageId ?? undefined,
1882
+ )
1880
1883
  if (targetId) {
1881
1884
  this.flushStructuredOutputUpdate(targetId)
1882
1885
  this.messages = completeStructuredOutputPart(
@@ -813,7 +813,7 @@ export async function* executeToolCalls<TContext = unknown>(
813
813
  continue
814
814
  }
815
815
 
816
- // Parse arguments, throwing error if invalid JSON
816
+ // Parse arguments
817
817
  let input: unknown = {}
818
818
  const argsStr = toolCall.function.arguments.trim() || '{}'
819
819
  if (argsStr) {
@@ -821,9 +821,17 @@ export async function* executeToolCalls<TContext = unknown>(
821
821
  const parsed = JSON.parse(argsStr)
822
822
  // Normalize null/non-object to {} (e.g. Anthropic empty tool_use blocks)
823
823
  input = parsed && typeof parsed === 'object' ? parsed : {}
824
- } catch (parseError) {
825
- // If parsing fails, throw error to fail fast
826
- throw new Error(`Failed to parse tool arguments as JSON: ${argsStr}`)
824
+ } catch {
825
+ results.push({
826
+ toolCallId: toolCall.id,
827
+ toolName,
828
+ result: {
829
+ error: `Failed to parse tool arguments as JSON: ${argsStr}`,
830
+ },
831
+ input,
832
+ state: 'output-error',
833
+ })
834
+ continue
827
835
  }
828
836
  }
829
837
 
@@ -25,3 +25,11 @@ export {
25
25
  PendingTurnCapability,
26
26
  providePendingTurn,
27
27
  } from './activities/chat/middleware/pending-turn'
28
+ export {
29
+ appendOutputSchemaInstruction,
30
+ parseJsonFromAssistantText,
31
+ } from './utilities/structured-output-text'
32
+ export {
33
+ structuredOutputCompleteChunk,
34
+ structuredOutputStartChunk,
35
+ } from './utilities/structured-output-events'
package/src/types.ts CHANGED
@@ -958,10 +958,13 @@ export interface TextOptions<
958
958
  * `supportsCombinedToolsAndSchema(modelOptions) === true`. The adapter
959
959
  * should then wire the schema into the upstream request (e.g.
960
960
  * `response_format: { type: 'json_schema', ... }`, `text.format`,
961
- * `output_format`) alongside any `tools`. The model's natural final
962
- * turn carries the schema-constrained JSON text and the engine
963
- * harvests it from the agent loop without a separate finalization
964
- * round-trip.
961
+ * `output_format`, `--json-schema`) alongside any `tools`.
962
+ *
963
+ * How the engine then takes the object depends on
964
+ * `combinedStructuredOutputSource()`:
965
+ * - `'text'` (default): the final-turn assistant text is the JSON.
966
+ * - `'event'`: the adapter emits `structured-output.complete` during
967
+ * `chatStream`. Accumulated prose is not parsed.
965
968
  *
966
969
  * Adapters that did NOT declare the capability never see this field
967
970
  * populated — the engine instead invokes `structuredOutput` /
@@ -1555,20 +1558,25 @@ export interface CodeModeExternalErrorEvent extends CustomEvent {
1555
1558
  name: 'code_mode:external_error'
1556
1559
  value: { function: string; error: string; duration: number }
1557
1560
  }
1558
- export interface CodeModeSkillCallEvent extends CustomEvent {
1559
- name: 'code_mode:skill_call'
1560
- value: { skill: string; input: unknown; timestamp: number }
1561
+ export interface CodeModeSnippetCallEvent extends CustomEvent {
1562
+ name: 'code_mode:snippet_call'
1563
+ value: { snippet: string; input: unknown; timestamp: number }
1561
1564
  }
1562
- export interface CodeModeSkillResultEvent extends CustomEvent {
1563
- name: 'code_mode:skill_result'
1564
- value: { skill: string; result: unknown; duration: number; timestamp: number }
1565
+ export interface CodeModeSnippetResultEvent extends CustomEvent {
1566
+ name: 'code_mode:snippet_result'
1567
+ value: {
1568
+ snippet: string
1569
+ result: unknown
1570
+ duration: number
1571
+ timestamp: number
1572
+ }
1565
1573
  }
1566
- export interface CodeModeSkillErrorEvent extends CustomEvent {
1567
- name: 'code_mode:skill_error'
1568
- value: { skill: string; error: string; duration: number; timestamp: number }
1574
+ export interface CodeModeSnippetErrorEvent extends CustomEvent {
1575
+ name: 'code_mode:snippet_error'
1576
+ value: { snippet: string; error: string; duration: number; timestamp: number }
1569
1577
  }
1570
- export interface SkillRegisteredEvent extends CustomEvent {
1571
- name: 'skill:registered'
1578
+ export interface SnippetRegisteredEvent extends CustomEvent {
1579
+ name: 'snippet:registered'
1572
1580
  value: { id: string; name: string; description: string; timestamp: number }
1573
1581
  }
1574
1582
 
@@ -1587,10 +1595,10 @@ export type KnownCustomEvent =
1587
1595
  | CodeModeExternalCallEvent
1588
1596
  | CodeModeExternalResultEvent
1589
1597
  | CodeModeExternalErrorEvent
1590
- | CodeModeSkillCallEvent
1591
- | CodeModeSkillResultEvent
1592
- | CodeModeSkillErrorEvent
1593
- | SkillRegisteredEvent
1598
+ | CodeModeSnippetCallEvent
1599
+ | CodeModeSnippetResultEvent
1600
+ | CodeModeSnippetErrorEvent
1601
+ | SnippetRegisteredEvent
1594
1602
  | StructuredOutputStartEvent
1595
1603
  | StructuredOutputCompleteEvent
1596
1604
  | ApprovalRequestedEvent
@@ -0,0 +1,44 @@
1
+ import { EventType } from '../types'
2
+ import type { StreamChunk } from '../types'
3
+
4
+ export function structuredOutputStartChunk(args: {
5
+ messageId: string
6
+ model: string
7
+ threadId: string
8
+ runId: string
9
+ timestamp?: number
10
+ }): StreamChunk {
11
+ return {
12
+ type: EventType.CUSTOM,
13
+ name: 'structured-output.start',
14
+ value: { messageId: args.messageId },
15
+ model: args.model,
16
+ timestamp: args.timestamp ?? Date.now(),
17
+ threadId: args.threadId,
18
+ runId: args.runId,
19
+ }
20
+ }
21
+
22
+ export function structuredOutputCompleteChunk(args: {
23
+ messageId: string
24
+ model: string
25
+ threadId: string
26
+ runId: string
27
+ object: unknown
28
+ raw: string
29
+ timestamp?: number
30
+ }): StreamChunk {
31
+ return {
32
+ type: EventType.CUSTOM,
33
+ name: 'structured-output.complete',
34
+ value: {
35
+ object: args.object,
36
+ raw: args.raw,
37
+ messageId: args.messageId,
38
+ },
39
+ model: args.model,
40
+ timestamp: args.timestamp ?? Date.now(),
41
+ threadId: args.threadId,
42
+ runId: args.runId,
43
+ }
44
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Parse JSON from a model/harness assistant string.
3
+ * Strips a wrapping markdown fence when the whole payload is fenced.
4
+ * If the model wrote prose first, take the last JSON object or array.
5
+ */
6
+ export function parseJsonFromAssistantText(raw: string): unknown {
7
+ const trimmed = raw.trim()
8
+ if (trimmed === '') {
9
+ throw new SyntaxError('Assistant text is empty')
10
+ }
11
+
12
+ const candidates: Array<string> = []
13
+ const wholeFence = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/)
14
+ if (wholeFence?.[1]) candidates.push(wholeFence[1].trim())
15
+ candidates.push(trimmed)
16
+ const lastFence = [
17
+ ...trimmed.matchAll(/```(?:json)?\s*([\s\S]*?)\s*```/g),
18
+ ].at(-1)
19
+ if (lastFence?.[1]) candidates.push(lastFence[1].trim())
20
+ const extracted = extractLastJsonSlice(trimmed)
21
+ if (extracted !== undefined) candidates.push(extracted)
22
+
23
+ let lastError: unknown
24
+ for (const candidate of candidates) {
25
+ try {
26
+ return JSON.parse(candidate)
27
+ } catch (error) {
28
+ lastError = error
29
+ }
30
+ }
31
+ throw lastError instanceof Error
32
+ ? lastError
33
+ : new SyntaxError('No JSON object found in assistant text')
34
+ }
35
+
36
+ function extractLastJsonSlice(text: string): string | undefined {
37
+ for (let end = text.length - 1; end >= 0; end--) {
38
+ if (text[end] !== '}' && text[end] !== ']') continue
39
+ for (let start = end; start >= 0; start--) {
40
+ const opener = text[start]
41
+ if (opener !== '{' && opener !== '[') continue
42
+ const slice = text.slice(start, end + 1)
43
+ try {
44
+ JSON.parse(slice)
45
+ return slice
46
+ } catch {
47
+ // Try an earlier opener, then an earlier closer.
48
+ }
49
+ }
50
+ }
51
+ return undefined
52
+ }
53
+
54
+ export function appendOutputSchemaInstruction(
55
+ prompt: string,
56
+ schema: unknown,
57
+ ): string {
58
+ return `${prompt}
59
+
60
+ Respond with a single JSON object that matches this JSON Schema. Do not wrap the object in markdown unless you must.
61
+
62
+ ${JSON.stringify(schema)}`
63
+ }