@tanstack/ai 0.37.0 → 0.38.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/mcp/manager.js +10 -1
- package/dist/esm/activities/chat/mcp/manager.js.map +1 -1
- package/dist/esm/activities/chat/mcp/types.d.ts +22 -0
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.js +23 -0
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +6 -1
- package/dist/esm/activities/chat/tools/tool-calls.js +36 -0
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/client.d.ts +1 -1
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/types.d.ts +34 -1
- package/package.json +1 -1
- package/skills/ai-core/chat-experience/SKILL.md +30 -2
- package/skills/ai-core/media-generation/SKILL.md +13 -0
- package/skills/ai-core/tool-calling/SKILL.md +10 -3
- package/src/activities/chat/mcp/manager.ts +31 -1
- package/src/activities/chat/mcp/types.ts +23 -0
- package/src/activities/chat/messages.ts +5 -0
- package/src/activities/chat/stream/processor.ts +42 -0
- package/src/activities/chat/tools/tool-calls.ts +96 -2
- package/src/client.ts +1 -0
- package/src/types.ts +31 -0
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
AfterToolCallInfo,
|
|
19
19
|
BeforeToolCallDecision,
|
|
20
20
|
} from '../middleware/types'
|
|
21
|
+
import type { McpResourceReadResult } from '../mcp/types'
|
|
21
22
|
import type {
|
|
22
23
|
ContextFromTool,
|
|
23
24
|
DefinedContext,
|
|
@@ -33,6 +34,92 @@ function safeJsonParse(value: string): unknown {
|
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* MCP Apps metadata attached to a server tool at discovery (see
|
|
39
|
+
* `@tanstack/ai-mcp` discovery + `MCPManager.discover()`).
|
|
40
|
+
*
|
|
41
|
+
* - `uiResourceUri` / `serverId` are stamped by ai-mcp at tool discovery.
|
|
42
|
+
* - `readResource` is bound by `MCPManager.discover()` (the one site that has
|
|
43
|
+
* both the tool and its originating source) so the resource can be eagerly
|
|
44
|
+
* read at the emit site. Under `chat()`-managed MCP lifecycle
|
|
45
|
+
* (`connection:'close'`), the MCP source is not disposed until the run
|
|
46
|
+
* drains, so `readResource` is still live at this emit point. Note: a caller
|
|
47
|
+
* who closes the MCP source early (outside `chat()`'s managed lifecycle)
|
|
48
|
+
* degrades fail-soft — `readResource` may reject, the widget is absent, but
|
|
49
|
+
* the tool result still flows to the model.
|
|
50
|
+
* `@tanstack/ai` never imports `@tanstack/ai-mcp`; this travels structurally
|
|
51
|
+
* on the tool.
|
|
52
|
+
*/
|
|
53
|
+
interface McpToolAppMeta {
|
|
54
|
+
uiResourceUri?: string
|
|
55
|
+
serverId?: string
|
|
56
|
+
/** Server-native (unprefixed) MCP tool name — used as the renderer's toolName. */
|
|
57
|
+
serverToolName?: string
|
|
58
|
+
readResource?: (uri: string) => Promise<McpResourceReadResult>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readMcpAppMeta(tool: AnyTool): McpToolAppMeta | undefined {
|
|
62
|
+
const meta = (tool.metadata as { mcp?: McpToolAppMeta } | undefined)?.mcp
|
|
63
|
+
return meta
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Eagerly read a tool's linked `ui://` resource (MCP Apps) and emit a
|
|
68
|
+
* `ui-resource` CUSTOM event so the client can render the widget. The model
|
|
69
|
+
* still receives the normal text tool-result; the widget rides alongside and
|
|
70
|
+
* never enters model input.
|
|
71
|
+
*
|
|
72
|
+
* Fail-soft: any read error logs a warning and emits nothing — it never throws,
|
|
73
|
+
* so the normal tool-result still flows and a broken widget cannot break the run.
|
|
74
|
+
*/
|
|
75
|
+
async function emitUiResourceIfLinked<TContext>(
|
|
76
|
+
tool: AnyTool,
|
|
77
|
+
context: ToolExecutionContext<TContext>,
|
|
78
|
+
): Promise<void> {
|
|
79
|
+
const mcp = readMcpAppMeta(tool)
|
|
80
|
+
const uiUri = mcp?.uiResourceUri
|
|
81
|
+
if (!uiUri || !mcp.readResource) return
|
|
82
|
+
|
|
83
|
+
// The try covers ONLY the fallible read — keep `emitCustomEvent` out of it so
|
|
84
|
+
// an exception from the emit path can't be mislabeled as a read failure.
|
|
85
|
+
let matched: McpResourceReadResult['contents'][number] | undefined
|
|
86
|
+
try {
|
|
87
|
+
const res = await mcp.readResource(uiUri)
|
|
88
|
+
// Emit ONLY the content whose uri matches the requested `uiUri`. A source
|
|
89
|
+
// can return unrelated contents; falling back to `contents[0]` would risk
|
|
90
|
+
// rendering a widget that doesn't correspond to the linked resource. This
|
|
91
|
+
// is a display widget — a mismatched resource is worse than none, so if no
|
|
92
|
+
// content matches we fail-soft (warn + return) rather than emit.
|
|
93
|
+
matched = res.contents.find((c) => c.uri === uiUri)
|
|
94
|
+
} catch (err) {
|
|
95
|
+
// fail-soft — the text tool-result already flows; a broken widget must
|
|
96
|
+
// not break the run.
|
|
97
|
+
console.warn(`[mcp-apps] failed to read ui resource ${uiUri}:`, err)
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
if (!matched) {
|
|
101
|
+
console.warn(
|
|
102
|
+
`[mcp-apps] ui resource ${uiUri} returned no content matching that uri; not emitting`,
|
|
103
|
+
)
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
// NOTE: `toolCallId` is intentionally NOT set here — it is stamped onto
|
|
107
|
+
// every emitted event by the `executeToolCalls` context wrapper, so the
|
|
108
|
+
// UIResourceEvent.value.toolCallId / UIResourcePart.toolCallId contract is
|
|
109
|
+
// still satisfied downstream.
|
|
110
|
+
context.emitCustomEvent('ui-resource', {
|
|
111
|
+
resource: {
|
|
112
|
+
uri: matched.uri,
|
|
113
|
+
mimeType: matched.mimeType ?? 'text/html',
|
|
114
|
+
text: matched.text,
|
|
115
|
+
blob: matched.blob,
|
|
116
|
+
},
|
|
117
|
+
serverId: mcp.serverId,
|
|
118
|
+
toolName: mcp.serverToolName ?? tool.name,
|
|
119
|
+
meta: undefined,
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
36
123
|
/**
|
|
37
124
|
* Optional middleware hooks for tool execution.
|
|
38
125
|
* When provided, these callbacks are invoked before/after each tool execution.
|
|
@@ -456,7 +543,7 @@ async function applyBeforeToolCallDecision(
|
|
|
456
543
|
* Execute a server-side tool with event polling, output validation, and middleware hooks.
|
|
457
544
|
* Yields CustomEvent chunks during execution and pushes the result to the results array.
|
|
458
545
|
*/
|
|
459
|
-
async function* executeServerTool<TContext = unknown>(
|
|
546
|
+
export async function* executeServerTool<TContext = unknown>(
|
|
460
547
|
toolCall: ToolCall,
|
|
461
548
|
tool: AnyTool,
|
|
462
549
|
toolName: string,
|
|
@@ -475,7 +562,14 @@ async function* executeServerTool<TContext = unknown>(
|
|
|
475
562
|
let result = yield* executeWithEventPolling(executionPromise, pendingEvents)
|
|
476
563
|
const duration = Date.now() - startTime
|
|
477
564
|
|
|
478
|
-
//
|
|
565
|
+
// MCP Apps: if this tool links a ui:// resource, eagerly read it and queue
|
|
566
|
+
// a `ui-resource` CUSTOM event. The MCP source stays live until the run
|
|
567
|
+
// drains (MCPManager's `connection:'close'` policy disposes on completion),
|
|
568
|
+
// so `readResource` is callable here. Fail-soft: a read error warns and
|
|
569
|
+
// emits nothing — the text result still flows.
|
|
570
|
+
await emitUiResourceIfLinked(tool, context)
|
|
571
|
+
|
|
572
|
+
// Flush remaining events (including any queued ui-resource event)
|
|
479
573
|
let pendingEvent: CustomEvent | undefined
|
|
480
574
|
while ((pendingEvent = pendingEvents.shift()) !== undefined) {
|
|
481
575
|
yield pendingEvent
|
package/src/client.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -416,6 +416,23 @@ export interface StructuredOutputPart<TData = unknown> {
|
|
|
416
416
|
errorMessage?: string
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
+
export interface UIResourcePart {
|
|
420
|
+
type: 'ui-resource'
|
|
421
|
+
/** The ui:// resource object in MCP-native shape — fed straight to the renderer. */
|
|
422
|
+
resource: { uri: string; mimeType: string; text?: string; blob?: string }
|
|
423
|
+
/** Pool prefix / config key — routes interactive calls to the right MCP server. */
|
|
424
|
+
serverId?: string
|
|
425
|
+
/** Links the widget to the originating tool call — correlates it with the
|
|
426
|
+
* sibling ToolCallPart/ToolResultPart in the same message. */
|
|
427
|
+
toolCallId: string
|
|
428
|
+
/** Server-native (unprefixed) MCP tool name whose UI this resource renders.
|
|
429
|
+
* Required by the renderer (`@mcp-ui/client`'s `AppRenderer` `toolName` prop). */
|
|
430
|
+
toolName: string
|
|
431
|
+
/** Reserved for future passthrough of the resource/tool `_meta.ui` (e.g. frame-size hints).
|
|
432
|
+
* Currently always `undefined` — nothing populates this field yet. */
|
|
433
|
+
meta?: Record<string, unknown>
|
|
434
|
+
}
|
|
435
|
+
|
|
419
436
|
export type MessagePart<TData = unknown> =
|
|
420
437
|
| TextPart
|
|
421
438
|
| ImagePart
|
|
@@ -426,6 +443,7 @@ export type MessagePart<TData = unknown> =
|
|
|
426
443
|
| ToolResultPart
|
|
427
444
|
| ThinkingPart
|
|
428
445
|
| StructuredOutputPart<TData>
|
|
446
|
+
| UIResourcePart
|
|
429
447
|
|
|
430
448
|
/**
|
|
431
449
|
* UIMessage - Domain-specific message format optimized for building chat UIs
|
|
@@ -1308,6 +1326,19 @@ export interface ToolInputAvailableEvent extends CustomEvent {
|
|
|
1308
1326
|
}
|
|
1309
1327
|
}
|
|
1310
1328
|
|
|
1329
|
+
/** Emitted when an MCP tool returns a ui:// resource (MCP Apps). Reconciled into
|
|
1330
|
+
* a UIResourcePart on the assistant UIMessage. Never enters model input. */
|
|
1331
|
+
export interface UIResourceEvent extends CustomEvent {
|
|
1332
|
+
name: 'ui-resource'
|
|
1333
|
+
value: {
|
|
1334
|
+
resource: UIResourcePart['resource']
|
|
1335
|
+
serverId?: string
|
|
1336
|
+
toolCallId: string
|
|
1337
|
+
toolName: string
|
|
1338
|
+
meta?: Record<string, unknown>
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1311
1342
|
/**
|
|
1312
1343
|
* Public type for streams returned by `chat({ outputSchema, stream: true })`.
|
|
1313
1344
|
*
|