@tanstack/ai 0.0.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/LICENSE +21 -0
- package/README.md +131 -0
- package/dist/esm/base-adapter.d.ts +35 -0
- package/dist/esm/base-adapter.js +12 -0
- package/dist/esm/base-adapter.js.map +1 -0
- package/dist/esm/core/chat-common-options.d.ts +52 -0
- package/dist/esm/core/chat.d.ts +30 -0
- package/dist/esm/core/chat.js +533 -0
- package/dist/esm/core/chat.js.map +1 -0
- package/dist/esm/core/embedding.d.ts +8 -0
- package/dist/esm/core/embedding.js +33 -0
- package/dist/esm/core/embedding.js.map +1 -0
- package/dist/esm/core/summarize.d.ts +9 -0
- package/dist/esm/core/summarize.js +36 -0
- package/dist/esm/core/summarize.js.map +1 -0
- package/dist/esm/event-client.d.ts +311 -0
- package/dist/esm/event-client.js +62 -0
- package/dist/esm/event-client.js.map +1 -0
- package/dist/esm/index.d.ts +16 -0
- package/dist/esm/index.js +50 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/message-converters.d.ts +52 -0
- package/dist/esm/message-converters.js +162 -0
- package/dist/esm/message-converters.js.map +1 -0
- package/dist/esm/stream/index.d.ts +11 -0
- package/dist/esm/stream/json-parser.d.ts +38 -0
- package/dist/esm/stream/json-parser.js +28 -0
- package/dist/esm/stream/json-parser.js.map +1 -0
- package/dist/esm/stream/message-updaters.d.ts +44 -0
- package/dist/esm/stream/message-updaters.js +141 -0
- package/dist/esm/stream/message-updaters.js.map +1 -0
- package/dist/esm/stream/processor.d.ts +242 -0
- package/dist/esm/stream/processor.js +693 -0
- package/dist/esm/stream/processor.js.map +1 -0
- package/dist/esm/stream/strategies.d.ts +43 -0
- package/dist/esm/stream/strategies.js +54 -0
- package/dist/esm/stream/strategies.js.map +1 -0
- package/dist/esm/stream/types.d.ts +71 -0
- package/dist/esm/tools/tool-calls.d.ts +112 -0
- package/dist/esm/tools/tool-calls.js +302 -0
- package/dist/esm/tools/tool-calls.js.map +1 -0
- package/dist/esm/tools/tool-definition.d.ts +125 -0
- package/dist/esm/tools/tool-definition.js +25 -0
- package/dist/esm/tools/tool-definition.js.map +1 -0
- package/dist/esm/tools/zod-converter.d.ts +30 -0
- package/dist/esm/tools/zod-converter.js +36 -0
- package/dist/esm/tools/zod-converter.js.map +1 -0
- package/dist/esm/types.d.ts +619 -0
- package/dist/esm/utilities/agent-loop-strategies.d.ts +59 -0
- package/dist/esm/utilities/agent-loop-strategies.js +23 -0
- package/dist/esm/utilities/agent-loop-strategies.js.map +1 -0
- package/dist/esm/utilities/chat-options.d.ts +6 -0
- package/dist/esm/utilities/chat-options.js +7 -0
- package/dist/esm/utilities/chat-options.js.map +1 -0
- package/dist/esm/utilities/messages.d.ts +30 -0
- package/dist/esm/utilities/messages.js +7 -0
- package/dist/esm/utilities/messages.js.map +1 -0
- package/dist/esm/utilities/stream-to-response.d.ts +48 -0
- package/dist/esm/utilities/stream-to-response.js +62 -0
- package/dist/esm/utilities/stream-to-response.js.map +1 -0
- package/package.json +65 -0
- package/src/base-adapter.ts +85 -0
- package/src/core/chat-common-options.ts +55 -0
- package/src/core/chat.ts +771 -0
- package/src/core/embedding.ts +54 -0
- package/src/core/summarize.ts +56 -0
- package/src/event-client.ts +389 -0
- package/src/index.ts +68 -0
- package/src/message-converters.ts +285 -0
- package/src/stream/index.ts +41 -0
- package/src/stream/json-parser.ts +58 -0
- package/src/stream/message-updaters.ts +275 -0
- package/src/stream/processor.ts +1092 -0
- package/src/stream/strategies.ts +78 -0
- package/src/stream/types.ts +94 -0
- package/src/tools/tool-calls.ts +471 -0
- package/src/tools/tool-definition.ts +206 -0
- package/src/tools/zod-converter.ts +85 -0
- package/src/types.ts +872 -0
- package/src/utilities/agent-loop-strategies.ts +85 -0
- package/src/utilities/chat-options.ts +35 -0
- package/src/utilities/messages.ts +63 -0
- package/src/utilities/stream-to-response.ts +116 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
class ToolCallManager {
|
|
2
|
+
constructor(tools) {
|
|
3
|
+
this.toolCallsMap = /* @__PURE__ */ new Map();
|
|
4
|
+
this.tools = tools;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Add a tool call chunk to the accumulator
|
|
8
|
+
* Handles streaming tool calls by accumulating arguments
|
|
9
|
+
*/
|
|
10
|
+
addToolCallChunk(chunk) {
|
|
11
|
+
const index = chunk.index;
|
|
12
|
+
const existing = this.toolCallsMap.get(index);
|
|
13
|
+
if (!existing) {
|
|
14
|
+
if (chunk.toolCall.id && chunk.toolCall.function.name) {
|
|
15
|
+
this.toolCallsMap.set(index, {
|
|
16
|
+
id: chunk.toolCall.id,
|
|
17
|
+
type: "function",
|
|
18
|
+
function: {
|
|
19
|
+
name: chunk.toolCall.function.name,
|
|
20
|
+
arguments: chunk.toolCall.function.arguments || ""
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
if (chunk.toolCall.function.name && !existing.function.name) {
|
|
26
|
+
existing.function.name = chunk.toolCall.function.name;
|
|
27
|
+
}
|
|
28
|
+
if (chunk.toolCall.function.arguments) {
|
|
29
|
+
existing.function.arguments += chunk.toolCall.function.arguments;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check if there are any complete tool calls to execute
|
|
35
|
+
*/
|
|
36
|
+
hasToolCalls() {
|
|
37
|
+
return this.getToolCalls().length > 0;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get all complete tool calls (filtered for valid ID and name)
|
|
41
|
+
*/
|
|
42
|
+
getToolCalls() {
|
|
43
|
+
return Array.from(this.toolCallsMap.values()).filter(
|
|
44
|
+
(tc) => tc.id && tc.function.name && tc.function.name.trim().length > 0
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Execute all tool calls and return tool result messages
|
|
49
|
+
* Also yields tool_result chunks for streaming
|
|
50
|
+
*/
|
|
51
|
+
async *executeTools(doneChunk) {
|
|
52
|
+
const toolCallsArray = this.getToolCalls();
|
|
53
|
+
const toolResults = [];
|
|
54
|
+
for (const toolCall of toolCallsArray) {
|
|
55
|
+
const tool = this.tools.find((t) => t.name === toolCall.function.name);
|
|
56
|
+
let toolResultContent;
|
|
57
|
+
if (tool?.execute) {
|
|
58
|
+
try {
|
|
59
|
+
let args;
|
|
60
|
+
try {
|
|
61
|
+
args = JSON.parse(toolCall.function.arguments);
|
|
62
|
+
} catch (parseError) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Failed to parse tool arguments as JSON: ${toolCall.function.arguments}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (tool.inputSchema) {
|
|
68
|
+
try {
|
|
69
|
+
args = tool.inputSchema.parse(args);
|
|
70
|
+
} catch (validationError) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Input validation failed for tool ${tool.name}: ${validationError.message}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
let result = await tool.execute(args);
|
|
77
|
+
if (tool.outputSchema && result !== void 0 && result !== null) {
|
|
78
|
+
try {
|
|
79
|
+
result = tool.outputSchema.parse(result);
|
|
80
|
+
} catch (validationError) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Output validation failed for tool ${tool.name}: ${validationError.message}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
toolResultContent = typeof result === "string" ? result : JSON.stringify(result);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
toolResultContent = `Error executing tool: ${error.message}`;
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
toolResultContent = `Tool ${toolCall.function.name} does not have an execute function`;
|
|
92
|
+
}
|
|
93
|
+
yield {
|
|
94
|
+
type: "tool_result",
|
|
95
|
+
id: doneChunk.id,
|
|
96
|
+
model: doneChunk.model,
|
|
97
|
+
timestamp: Date.now(),
|
|
98
|
+
toolCallId: toolCall.id,
|
|
99
|
+
content: toolResultContent
|
|
100
|
+
};
|
|
101
|
+
toolResults.push({
|
|
102
|
+
role: "tool",
|
|
103
|
+
content: toolResultContent,
|
|
104
|
+
toolCallId: toolCall.id
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return toolResults;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Clear the tool calls map for the next iteration
|
|
111
|
+
*/
|
|
112
|
+
clear() {
|
|
113
|
+
this.toolCallsMap.clear();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function executeToolCalls(toolCalls, tools, approvals = /* @__PURE__ */ new Map(), clientResults = /* @__PURE__ */ new Map()) {
|
|
117
|
+
const results = [];
|
|
118
|
+
const needsApproval = [];
|
|
119
|
+
const needsClientExecution = [];
|
|
120
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
121
|
+
for (const tool of tools) {
|
|
122
|
+
toolMap.set(tool.name, tool);
|
|
123
|
+
}
|
|
124
|
+
for (const toolCall of toolCalls) {
|
|
125
|
+
const tool = toolMap.get(toolCall.function.name);
|
|
126
|
+
const toolName = toolCall.function.name;
|
|
127
|
+
if (!tool) {
|
|
128
|
+
results.push({
|
|
129
|
+
toolCallId: toolCall.id,
|
|
130
|
+
toolName,
|
|
131
|
+
result: { error: `Unknown tool: ${toolName}` },
|
|
132
|
+
state: "output-error"
|
|
133
|
+
});
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
let input = {};
|
|
137
|
+
const argsStr = toolCall.function.arguments.trim() || "{}";
|
|
138
|
+
{
|
|
139
|
+
try {
|
|
140
|
+
input = JSON.parse(argsStr);
|
|
141
|
+
} catch (parseError) {
|
|
142
|
+
throw new Error(`Failed to parse tool arguments as JSON: ${argsStr}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (tool.inputSchema) {
|
|
146
|
+
try {
|
|
147
|
+
input = tool.inputSchema.parse(input);
|
|
148
|
+
} catch (validationError) {
|
|
149
|
+
results.push({
|
|
150
|
+
toolCallId: toolCall.id,
|
|
151
|
+
toolName,
|
|
152
|
+
result: {
|
|
153
|
+
error: `Input validation failed for tool ${tool.name}: ${validationError.message}`
|
|
154
|
+
},
|
|
155
|
+
state: "output-error"
|
|
156
|
+
});
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (!tool.execute) {
|
|
161
|
+
if (tool.needsApproval) {
|
|
162
|
+
const approvalId = `approval_${toolCall.id}`;
|
|
163
|
+
if (approvals.has(approvalId)) {
|
|
164
|
+
const approved = approvals.get(approvalId);
|
|
165
|
+
if (approved) {
|
|
166
|
+
if (clientResults.has(toolCall.id)) {
|
|
167
|
+
results.push({
|
|
168
|
+
toolCallId: toolCall.id,
|
|
169
|
+
toolName,
|
|
170
|
+
result: clientResults.get(toolCall.id)
|
|
171
|
+
});
|
|
172
|
+
} else {
|
|
173
|
+
needsClientExecution.push({
|
|
174
|
+
toolCallId: toolCall.id,
|
|
175
|
+
toolName,
|
|
176
|
+
input
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
results.push({
|
|
181
|
+
toolCallId: toolCall.id,
|
|
182
|
+
toolName,
|
|
183
|
+
result: { error: "User declined tool execution" },
|
|
184
|
+
state: "output-error"
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
needsApproval.push({
|
|
189
|
+
toolCallId: toolCall.id,
|
|
190
|
+
toolName: toolCall.function.name,
|
|
191
|
+
input,
|
|
192
|
+
approvalId
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
if (clientResults.has(toolCall.id)) {
|
|
197
|
+
results.push({
|
|
198
|
+
toolCallId: toolCall.id,
|
|
199
|
+
toolName,
|
|
200
|
+
result: clientResults.get(toolCall.id)
|
|
201
|
+
});
|
|
202
|
+
} else {
|
|
203
|
+
needsClientExecution.push({
|
|
204
|
+
toolCallId: toolCall.id,
|
|
205
|
+
toolName,
|
|
206
|
+
input
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (tool.needsApproval) {
|
|
213
|
+
const approvalId = `approval_${toolCall.id}`;
|
|
214
|
+
if (approvals.has(approvalId)) {
|
|
215
|
+
const approved = approvals.get(approvalId);
|
|
216
|
+
if (approved) {
|
|
217
|
+
const startTime2 = Date.now();
|
|
218
|
+
try {
|
|
219
|
+
let result = await tool.execute(input);
|
|
220
|
+
const duration = Date.now() - startTime2;
|
|
221
|
+
if (tool.outputSchema && result !== void 0 && result !== null) {
|
|
222
|
+
const parsed = tool.outputSchema.safeParse(result);
|
|
223
|
+
if (parsed.success) {
|
|
224
|
+
result = parsed.data;
|
|
225
|
+
} else {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`Output validation failed for tool ${tool.name}: ${parsed.error.message}`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
results.push({
|
|
232
|
+
toolCallId: toolCall.id,
|
|
233
|
+
toolName,
|
|
234
|
+
result: typeof result === "string" ? JSON.parse(result) : result || null,
|
|
235
|
+
duration
|
|
236
|
+
});
|
|
237
|
+
} catch (error) {
|
|
238
|
+
const duration = Date.now() - startTime2;
|
|
239
|
+
results.push({
|
|
240
|
+
toolCallId: toolCall.id,
|
|
241
|
+
toolName,
|
|
242
|
+
result: { error: error.message },
|
|
243
|
+
state: "output-error",
|
|
244
|
+
duration
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
results.push({
|
|
249
|
+
toolCallId: toolCall.id,
|
|
250
|
+
toolName,
|
|
251
|
+
result: { error: "User declined tool execution" },
|
|
252
|
+
state: "output-error"
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
needsApproval.push({
|
|
257
|
+
toolCallId: toolCall.id,
|
|
258
|
+
toolName,
|
|
259
|
+
input,
|
|
260
|
+
approvalId
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const startTime = Date.now();
|
|
266
|
+
try {
|
|
267
|
+
let result = await tool.execute(input);
|
|
268
|
+
const duration = Date.now() - startTime;
|
|
269
|
+
if (tool.outputSchema && result !== void 0 && result !== null) {
|
|
270
|
+
const parsed = tool.outputSchema.safeParse(result);
|
|
271
|
+
if (parsed.success) {
|
|
272
|
+
result = parsed.data;
|
|
273
|
+
} else {
|
|
274
|
+
throw new Error(
|
|
275
|
+
`Output validation failed for tool ${tool.name}: ${parsed.error.message}`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
results.push({
|
|
280
|
+
toolCallId: toolCall.id,
|
|
281
|
+
toolName,
|
|
282
|
+
result: typeof result === "string" ? JSON.parse(result) : result || null,
|
|
283
|
+
duration
|
|
284
|
+
});
|
|
285
|
+
} catch (error) {
|
|
286
|
+
const duration = Date.now() - startTime;
|
|
287
|
+
results.push({
|
|
288
|
+
toolCallId: toolCall.id,
|
|
289
|
+
toolName,
|
|
290
|
+
result: { error: error.message },
|
|
291
|
+
state: "output-error",
|
|
292
|
+
duration
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return { results, needsApproval, needsClientExecution };
|
|
297
|
+
}
|
|
298
|
+
export {
|
|
299
|
+
ToolCallManager,
|
|
300
|
+
executeToolCalls
|
|
301
|
+
};
|
|
302
|
+
//# sourceMappingURL=tool-calls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-calls.js","sources":["../../../src/tools/tool-calls.ts"],"sourcesContent":["import type {\n DoneStreamChunk,\n ModelMessage,\n Tool,\n ToolCall,\n ToolResultStreamChunk,\n} from '../types'\n\n/**\n * Manages tool call accumulation and execution for the chat() method's automatic tool execution loop.\n *\n * Responsibilities:\n * - Accumulates streaming tool call chunks (ID, name, arguments)\n * - Validates tool calls (filters out incomplete ones)\n * - Executes tool `execute` functions with parsed arguments\n * - Emits `tool_result` chunks for client visibility\n * - Returns tool result messages for conversation history\n *\n * This class is used internally by the AI.chat() method to handle the automatic\n * tool execution loop. It can also be used independently for custom tool execution logic.\n *\n * @example\n * ```typescript\n * const manager = new ToolCallManager(tools);\n *\n * // During streaming, accumulate tool calls\n * for await (const chunk of stream) {\n * if (chunk.type === \"tool_call\") {\n * manager.addToolCallChunk(chunk);\n * }\n * }\n *\n * // After stream completes, execute tools\n * if (manager.hasToolCalls()) {\n * const toolResults = yield* manager.executeTools(doneChunk);\n * messages = [...messages, ...toolResults];\n * manager.clear();\n * }\n * ```\n */\nexport class ToolCallManager {\n private toolCallsMap = new Map<number, ToolCall>()\n private tools: ReadonlyArray<Tool>\n\n constructor(tools: ReadonlyArray<Tool>) {\n this.tools = tools\n }\n\n /**\n * Add a tool call chunk to the accumulator\n * Handles streaming tool calls by accumulating arguments\n */\n addToolCallChunk(chunk: {\n toolCall: {\n id: string\n type: 'function'\n function: {\n name: string\n arguments: string\n }\n }\n index: number\n }): void {\n const index = chunk.index\n const existing = this.toolCallsMap.get(index)\n\n if (!existing) {\n // Only create entry if we have a tool call ID and name\n if (chunk.toolCall.id && chunk.toolCall.function.name) {\n this.toolCallsMap.set(index, {\n id: chunk.toolCall.id,\n type: 'function',\n function: {\n name: chunk.toolCall.function.name,\n arguments: chunk.toolCall.function.arguments || '',\n },\n })\n }\n } else {\n // Update name if it wasn't set before\n if (chunk.toolCall.function.name && !existing.function.name) {\n existing.function.name = chunk.toolCall.function.name\n }\n // Accumulate arguments for streaming tool calls\n if (chunk.toolCall.function.arguments) {\n existing.function.arguments += chunk.toolCall.function.arguments\n }\n }\n }\n\n /**\n * Check if there are any complete tool calls to execute\n */\n hasToolCalls(): boolean {\n return this.getToolCalls().length > 0\n }\n\n /**\n * Get all complete tool calls (filtered for valid ID and name)\n */\n getToolCalls(): Array<ToolCall> {\n return Array.from(this.toolCallsMap.values()).filter(\n (tc) => tc.id && tc.function.name && tc.function.name.trim().length > 0,\n )\n }\n\n /**\n * Execute all tool calls and return tool result messages\n * Also yields tool_result chunks for streaming\n */\n async *executeTools(\n doneChunk: DoneStreamChunk,\n ): AsyncGenerator<ToolResultStreamChunk, Array<ModelMessage>, void> {\n const toolCallsArray = this.getToolCalls()\n const toolResults: Array<ModelMessage> = []\n\n for (const toolCall of toolCallsArray) {\n const tool = this.tools.find((t) => t.name === toolCall.function.name)\n\n let toolResultContent: string\n if (tool?.execute) {\n try {\n // Parse arguments\n let args: any\n try {\n args = JSON.parse(toolCall.function.arguments)\n } catch (parseError) {\n throw new Error(\n `Failed to parse tool arguments as JSON: ${toolCall.function.arguments}`,\n )\n }\n\n // Validate input against inputSchema\n if (tool.inputSchema) {\n try {\n args = tool.inputSchema.parse(args)\n } catch (validationError: any) {\n throw new Error(\n `Input validation failed for tool ${tool.name}: ${validationError.message}`,\n )\n }\n }\n\n // Execute the tool\n let result = await tool.execute(args)\n\n // Validate output against outputSchema if provided\n if (tool.outputSchema && result !== undefined && result !== null) {\n try {\n result = tool.outputSchema.parse(result)\n } catch (validationError: any) {\n throw new Error(\n `Output validation failed for tool ${tool.name}: ${validationError.message}`,\n )\n }\n }\n\n toolResultContent =\n typeof result === 'string' ? result : JSON.stringify(result)\n } catch (error: any) {\n // If tool execution fails, add error message\n toolResultContent = `Error executing tool: ${error.message}`\n }\n } else {\n // Tool doesn't have execute function, add placeholder\n toolResultContent = `Tool ${toolCall.function.name} does not have an execute function`\n }\n\n // Emit tool_result chunk so callers can track tool execution\n yield {\n type: 'tool_result',\n id: doneChunk.id,\n model: doneChunk.model,\n timestamp: Date.now(),\n toolCallId: toolCall.id,\n content: toolResultContent,\n }\n\n // Add tool result message\n toolResults.push({\n role: 'tool',\n content: toolResultContent,\n toolCallId: toolCall.id,\n })\n }\n\n return toolResults\n }\n\n /**\n * Clear the tool calls map for the next iteration\n */\n clear(): void {\n this.toolCallsMap.clear()\n }\n}\n\nexport interface ToolResult {\n toolCallId: string\n toolName: string\n result: any\n state?: 'output-available' | 'output-error'\n /** Duration of tool execution in milliseconds (only for server-executed tools) */\n duration?: number\n}\n\nexport interface ApprovalRequest {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n}\n\nexport interface ClientToolRequest {\n toolCallId: string\n toolName: string\n input: any\n}\n\ninterface ExecuteToolCallsResult {\n /** Tool results ready to send to LLM */\n results: Array<ToolResult>\n /** Tools that need user approval before execution */\n needsApproval: Array<ApprovalRequest>\n /** Tools that need client-side execution */\n needsClientExecution: Array<ClientToolRequest>\n}\n\n/**\n * Execute tool calls based on their configuration\n *\n * Handles three cases:\n * 1. Client tools (no execute) - request client to execute\n * 2. Server tools with approval - check approval before executing\n * 3. Normal server tools - execute immediately\n *\n * @param toolCalls - Tool calls from the LLM\n * @param tools - Available tools with their configurations\n * @param approvals - Map of approval decisions (approval.id -> approved boolean)\n * @param clientResults - Map of client-side execution results (toolCallId -> result)\n */\nexport async function executeToolCalls(\n toolCalls: Array<ToolCall>,\n tools: ReadonlyArray<Tool>,\n approvals: Map<string, boolean> = new Map(),\n clientResults: Map<string, any> = new Map(),\n): Promise<ExecuteToolCallsResult> {\n const results: Array<ToolResult> = []\n const needsApproval: Array<ApprovalRequest> = []\n const needsClientExecution: Array<ClientToolRequest> = []\n\n // Create tool lookup map\n const toolMap = new Map<string, Tool>()\n for (const tool of tools) {\n toolMap.set(tool.name, tool)\n }\n\n for (const toolCall of toolCalls) {\n const tool = toolMap.get(toolCall.function.name)\n const toolName = toolCall.function.name\n\n if (!tool) {\n // Unknown tool - return error\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: `Unknown tool: ${toolName}` },\n state: 'output-error',\n })\n continue\n }\n\n // Parse arguments, throwing error if invalid JSON\n let input: any = {}\n const argsStr = toolCall.function.arguments.trim() || '{}'\n if (argsStr) {\n try {\n input = JSON.parse(argsStr)\n } catch (parseError) {\n // If parsing fails, throw error to fail fast\n throw new Error(`Failed to parse tool arguments as JSON: ${argsStr}`)\n }\n }\n\n // Validate input against inputSchema\n if (tool.inputSchema) {\n try {\n input = tool.inputSchema.parse(input)\n } catch (validationError: any) {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: {\n error: `Input validation failed for tool ${tool.name}: ${validationError.message}`,\n },\n state: 'output-error',\n })\n continue\n }\n }\n\n // CASE 1: Client-side tool (no execute function)\n if (!tool.execute) {\n // Check if tool needs approval\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n\n // Check if approval decision exists\n if (approvals.has(approvalId)) {\n const approved = approvals.get(approvalId)\n\n if (approved) {\n // Approved - check if client has executed\n if (clientResults.has(toolCall.id)) {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: clientResults.get(toolCall.id),\n })\n } else {\n // Approved but not executed yet - request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: 'User declined tool execution' },\n state: 'output-error',\n })\n }\n } else {\n // Need approval first\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName: toolCall.function.name,\n input,\n approvalId,\n })\n }\n } else {\n // No approval needed - check if client has executed\n if (clientResults.has(toolCall.id)) {\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: clientResults.get(toolCall.id),\n })\n } else {\n // Request client execution\n needsClientExecution.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n })\n }\n }\n continue\n }\n\n // CASE 2: Server tool with approval required\n if (tool.needsApproval) {\n const approvalId = `approval_${toolCall.id}`\n\n // Check if approval decision exists\n if (approvals.has(approvalId)) {\n const approved = approvals.get(approvalId)\n\n if (approved) {\n // Execute after approval\n const startTime = Date.now()\n try {\n let result = await tool.execute(input)\n const duration = Date.now() - startTime\n\n // Validate output against outputSchema if provided\n if (tool.outputSchema && result !== undefined && result !== null) {\n const parsed = tool.outputSchema.safeParse(result)\n if (parsed.success) {\n result = parsed.data\n } else {\n throw new Error(\n `Output validation failed for tool ${tool.name}: ${parsed.error.message}`,\n )\n }\n }\n\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n typeof result === 'string'\n ? JSON.parse(result)\n : result || null,\n duration,\n })\n } catch (error: any) {\n const duration = Date.now() - startTime\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: error.message },\n state: 'output-error',\n duration,\n })\n }\n } else {\n // User declined\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: 'User declined tool execution' },\n state: 'output-error',\n })\n }\n } else {\n // Need approval\n needsApproval.push({\n toolCallId: toolCall.id,\n toolName,\n input,\n approvalId,\n })\n }\n continue\n }\n\n // CASE 3: Normal server tool - execute immediately\n const startTime = Date.now()\n try {\n let result = await tool.execute(input)\n const duration = Date.now() - startTime\n\n // Validate output against outputSchema if provided\n if (tool.outputSchema && result !== undefined && result !== null) {\n const parsed = tool.outputSchema.safeParse(result)\n if (parsed.success) {\n result = parsed.data\n } else {\n throw new Error(\n `Output validation failed for tool ${tool.name}: ${parsed.error.message}`,\n )\n }\n }\n\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result:\n typeof result === 'string' ? JSON.parse(result) : result || null,\n duration,\n })\n } catch (error: any) {\n const duration = Date.now() - startTime\n results.push({\n toolCallId: toolCall.id,\n toolName,\n result: { error: error.message },\n state: 'output-error',\n duration,\n })\n }\n }\n\n return { results, needsApproval, needsClientExecution }\n}\n"],"names":["startTime"],"mappings":"AAwCO,MAAM,gBAAgB;AAAA,EAI3B,YAAY,OAA4B;AAHxC,SAAQ,mCAAmB,IAAA;AAIzB,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAiB,OAUR;AACP,UAAM,QAAQ,MAAM;AACpB,UAAM,WAAW,KAAK,aAAa,IAAI,KAAK;AAE5C,QAAI,CAAC,UAAU;AAEb,UAAI,MAAM,SAAS,MAAM,MAAM,SAAS,SAAS,MAAM;AACrD,aAAK,aAAa,IAAI,OAAO;AAAA,UAC3B,IAAI,MAAM,SAAS;AAAA,UACnB,MAAM;AAAA,UACN,UAAU;AAAA,YACR,MAAM,MAAM,SAAS,SAAS;AAAA,YAC9B,WAAW,MAAM,SAAS,SAAS,aAAa;AAAA,UAAA;AAAA,QAClD,CACD;AAAA,MACH;AAAA,IACF,OAAO;AAEL,UAAI,MAAM,SAAS,SAAS,QAAQ,CAAC,SAAS,SAAS,MAAM;AAC3D,iBAAS,SAAS,OAAO,MAAM,SAAS,SAAS;AAAA,MACnD;AAEA,UAAI,MAAM,SAAS,SAAS,WAAW;AACrC,iBAAS,SAAS,aAAa,MAAM,SAAS,SAAS;AAAA,MACzD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,eAAwB;AACtB,WAAO,KAAK,eAAe,SAAS;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,eAAgC;AAC9B,WAAO,MAAM,KAAK,KAAK,aAAa,OAAA,CAAQ,EAAE;AAAA,MAC5C,CAAC,OAAO,GAAG,MAAM,GAAG,SAAS,QAAQ,GAAG,SAAS,KAAK,KAAA,EAAO,SAAS;AAAA,IAAA;AAAA,EAE1E;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,aACL,WACkE;AAClE,UAAM,iBAAiB,KAAK,aAAA;AAC5B,UAAM,cAAmC,CAAA;AAEzC,eAAW,YAAY,gBAAgB;AACrC,YAAM,OAAO,KAAK,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,SAAS,SAAS,IAAI;AAErE,UAAI;AACJ,UAAI,MAAM,SAAS;AACjB,YAAI;AAEF,cAAI;AACJ,cAAI;AACF,mBAAO,KAAK,MAAM,SAAS,SAAS,SAAS;AAAA,UAC/C,SAAS,YAAY;AACnB,kBAAM,IAAI;AAAA,cACR,2CAA2C,SAAS,SAAS,SAAS;AAAA,YAAA;AAAA,UAE1E;AAGA,cAAI,KAAK,aAAa;AACpB,gBAAI;AACF,qBAAO,KAAK,YAAY,MAAM,IAAI;AAAA,YACpC,SAAS,iBAAsB;AAC7B,oBAAM,IAAI;AAAA,gBACR,oCAAoC,KAAK,IAAI,KAAK,gBAAgB,OAAO;AAAA,cAAA;AAAA,YAE7E;AAAA,UACF;AAGA,cAAI,SAAS,MAAM,KAAK,QAAQ,IAAI;AAGpC,cAAI,KAAK,gBAAgB,WAAW,UAAa,WAAW,MAAM;AAChE,gBAAI;AACF,uBAAS,KAAK,aAAa,MAAM,MAAM;AAAA,YACzC,SAAS,iBAAsB;AAC7B,oBAAM,IAAI;AAAA,gBACR,qCAAqC,KAAK,IAAI,KAAK,gBAAgB,OAAO;AAAA,cAAA;AAAA,YAE9E;AAAA,UACF;AAEA,8BACE,OAAO,WAAW,WAAW,SAAS,KAAK,UAAU,MAAM;AAAA,QAC/D,SAAS,OAAY;AAEnB,8BAAoB,yBAAyB,MAAM,OAAO;AAAA,QAC5D;AAAA,MACF,OAAO;AAEL,4BAAoB,QAAQ,SAAS,SAAS,IAAI;AAAA,MACpD;AAGA,YAAM;AAAA,QACJ,MAAM;AAAA,QACN,IAAI,UAAU;AAAA,QACd,OAAO,UAAU;AAAA,QACjB,WAAW,KAAK,IAAA;AAAA,QAChB,YAAY,SAAS;AAAA,QACrB,SAAS;AAAA,MAAA;AAIX,kBAAY,KAAK;AAAA,QACf,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,SAAS;AAAA,MAAA,CACtB;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,aAAa,MAAA;AAAA,EACpB;AACF;AA8CA,eAAsB,iBACpB,WACA,OACA,YAAkC,oBAAI,OACtC,gBAAkC,oBAAI,OACL;AACjC,QAAM,UAA6B,CAAA;AACnC,QAAM,gBAAwC,CAAA;AAC9C,QAAM,uBAAiD,CAAA;AAGvD,QAAM,8BAAc,IAAA;AACpB,aAAW,QAAQ,OAAO;AACxB,YAAQ,IAAI,KAAK,MAAM,IAAI;AAAA,EAC7B;AAEA,aAAW,YAAY,WAAW;AAChC,UAAM,OAAO,QAAQ,IAAI,SAAS,SAAS,IAAI;AAC/C,UAAM,WAAW,SAAS,SAAS;AAEnC,QAAI,CAAC,MAAM;AAET,cAAQ,KAAK;AAAA,QACX,YAAY,SAAS;AAAA,QACrB;AAAA,QACA,QAAQ,EAAE,OAAO,iBAAiB,QAAQ,GAAA;AAAA,QAC1C,OAAO;AAAA,MAAA,CACR;AACD;AAAA,IACF;AAGA,QAAI,QAAa,CAAA;AACjB,UAAM,UAAU,SAAS,SAAS,UAAU,UAAU;AACzC;AACX,UAAI;AACF,gBAAQ,KAAK,MAAM,OAAO;AAAA,MAC5B,SAAS,YAAY;AAEnB,cAAM,IAAI,MAAM,2CAA2C,OAAO,EAAE;AAAA,MACtE;AAAA,IACF;AAGA,QAAI,KAAK,aAAa;AACpB,UAAI;AACF,gBAAQ,KAAK,YAAY,MAAM,KAAK;AAAA,MACtC,SAAS,iBAAsB;AAC7B,gBAAQ,KAAK;AAAA,UACX,YAAY,SAAS;AAAA,UACrB;AAAA,UACA,QAAQ;AAAA,YACN,OAAO,oCAAoC,KAAK,IAAI,KAAK,gBAAgB,OAAO;AAAA,UAAA;AAAA,UAElF,OAAO;AAAA,QAAA,CACR;AACD;AAAA,MACF;AAAA,IACF;AAGA,QAAI,CAAC,KAAK,SAAS;AAEjB,UAAI,KAAK,eAAe;AACtB,cAAM,aAAa,YAAY,SAAS,EAAE;AAG1C,YAAI,UAAU,IAAI,UAAU,GAAG;AAC7B,gBAAM,WAAW,UAAU,IAAI,UAAU;AAEzC,cAAI,UAAU;AAEZ,gBAAI,cAAc,IAAI,SAAS,EAAE,GAAG;AAClC,sBAAQ,KAAK;AAAA,gBACX,YAAY,SAAS;AAAA,gBACrB;AAAA,gBACA,QAAQ,cAAc,IAAI,SAAS,EAAE;AAAA,cAAA,CACtC;AAAA,YACH,OAAO;AAEL,mCAAqB,KAAK;AAAA,gBACxB,YAAY,SAAS;AAAA,gBACrB;AAAA,gBACA;AAAA,cAAA,CACD;AAAA,YACH;AAAA,UACF,OAAO;AAEL,oBAAQ,KAAK;AAAA,cACX,YAAY,SAAS;AAAA,cACrB;AAAA,cACA,QAAQ,EAAE,OAAO,+BAAA;AAAA,cACjB,OAAO;AAAA,YAAA,CACR;AAAA,UACH;AAAA,QACF,OAAO;AAEL,wBAAc,KAAK;AAAA,YACjB,YAAY,SAAS;AAAA,YACrB,UAAU,SAAS,SAAS;AAAA,YAC5B;AAAA,YACA;AAAA,UAAA,CACD;AAAA,QACH;AAAA,MACF,OAAO;AAEL,YAAI,cAAc,IAAI,SAAS,EAAE,GAAG;AAClC,kBAAQ,KAAK;AAAA,YACX,YAAY,SAAS;AAAA,YACrB;AAAA,YACA,QAAQ,cAAc,IAAI,SAAS,EAAE;AAAA,UAAA,CACtC;AAAA,QACH,OAAO;AAEL,+BAAqB,KAAK;AAAA,YACxB,YAAY,SAAS;AAAA,YACrB;AAAA,YACA;AAAA,UAAA,CACD;AAAA,QACH;AAAA,MACF;AACA;AAAA,IACF;AAGA,QAAI,KAAK,eAAe;AACtB,YAAM,aAAa,YAAY,SAAS,EAAE;AAG1C,UAAI,UAAU,IAAI,UAAU,GAAG;AAC7B,cAAM,WAAW,UAAU,IAAI,UAAU;AAEzC,YAAI,UAAU;AAEZ,gBAAMA,aAAY,KAAK,IAAA;AACvB,cAAI;AACF,gBAAI,SAAS,MAAM,KAAK,QAAQ,KAAK;AACrC,kBAAM,WAAW,KAAK,IAAA,IAAQA;AAG9B,gBAAI,KAAK,gBAAgB,WAAW,UAAa,WAAW,MAAM;AAChE,oBAAM,SAAS,KAAK,aAAa,UAAU,MAAM;AACjD,kBAAI,OAAO,SAAS;AAClB,yBAAS,OAAO;AAAA,cAClB,OAAO;AACL,sBAAM,IAAI;AAAA,kBACR,qCAAqC,KAAK,IAAI,KAAK,OAAO,MAAM,OAAO;AAAA,gBAAA;AAAA,cAE3E;AAAA,YACF;AAEA,oBAAQ,KAAK;AAAA,cACX,YAAY,SAAS;AAAA,cACrB;AAAA,cACA,QACE,OAAO,WAAW,WACd,KAAK,MAAM,MAAM,IACjB,UAAU;AAAA,cAChB;AAAA,YAAA,CACD;AAAA,UACH,SAAS,OAAY;AACnB,kBAAM,WAAW,KAAK,IAAA,IAAQA;AAC9B,oBAAQ,KAAK;AAAA,cACX,YAAY,SAAS;AAAA,cACrB;AAAA,cACA,QAAQ,EAAE,OAAO,MAAM,QAAA;AAAA,cACvB,OAAO;AAAA,cACP;AAAA,YAAA,CACD;AAAA,UACH;AAAA,QACF,OAAO;AAEL,kBAAQ,KAAK;AAAA,YACX,YAAY,SAAS;AAAA,YACrB;AAAA,YACA,QAAQ,EAAE,OAAO,+BAAA;AAAA,YACjB,OAAO;AAAA,UAAA,CACR;AAAA,QACH;AAAA,MACF,OAAO;AAEL,sBAAc,KAAK;AAAA,UACjB,YAAY,SAAS;AAAA,UACrB;AAAA,UACA;AAAA,UACA;AAAA,QAAA,CACD;AAAA,MACH;AACA;AAAA,IACF;AAGA,UAAM,YAAY,KAAK,IAAA;AACvB,QAAI;AACF,UAAI,SAAS,MAAM,KAAK,QAAQ,KAAK;AACrC,YAAM,WAAW,KAAK,IAAA,IAAQ;AAG9B,UAAI,KAAK,gBAAgB,WAAW,UAAa,WAAW,MAAM;AAChE,cAAM,SAAS,KAAK,aAAa,UAAU,MAAM;AACjD,YAAI,OAAO,SAAS;AAClB,mBAAS,OAAO;AAAA,QAClB,OAAO;AACL,gBAAM,IAAI;AAAA,YACR,qCAAqC,KAAK,IAAI,KAAK,OAAO,MAAM,OAAO;AAAA,UAAA;AAAA,QAE3E;AAAA,MACF;AAEA,cAAQ,KAAK;AAAA,QACX,YAAY,SAAS;AAAA,QACrB;AAAA,QACA,QACE,OAAO,WAAW,WAAW,KAAK,MAAM,MAAM,IAAI,UAAU;AAAA,QAC9D;AAAA,MAAA,CACD;AAAA,IACH,SAAS,OAAY;AACnB,YAAM,WAAW,KAAK,IAAA,IAAQ;AAC9B,cAAQ,KAAK;AAAA,QACX,YAAY,SAAS;AAAA,QACrB;AAAA,QACA,QAAQ,EAAE,OAAO,MAAM,QAAA;AAAA,QACvB,OAAO;AAAA,QACP;AAAA,MAAA,CACD;AAAA,IACH;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,eAAe,qBAAA;AACnC;"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Tool } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Marker type for server-side tools
|
|
5
|
+
*/
|
|
6
|
+
export interface ServerTool<TInput extends z.ZodType = z.ZodType, TOutput extends z.ZodType = z.ZodType, TName extends string = string> extends Tool<TInput, TOutput, TName> {
|
|
7
|
+
__toolSide: 'server';
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Marker type for client-side tools
|
|
11
|
+
*/
|
|
12
|
+
export interface ClientTool<TInput extends z.ZodType = z.ZodType, TOutput extends z.ZodType = z.ZodType, TName extends string = string> {
|
|
13
|
+
__toolSide: 'client';
|
|
14
|
+
name: TName;
|
|
15
|
+
description: string;
|
|
16
|
+
inputSchema?: TInput;
|
|
17
|
+
outputSchema?: TOutput;
|
|
18
|
+
needsApproval?: boolean;
|
|
19
|
+
metadata?: Record<string, any>;
|
|
20
|
+
execute?: (args: z.infer<TInput>) => Promise<z.infer<TOutput>> | z.infer<TOutput>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Tool definition that can be used directly or instantiated for server/client
|
|
24
|
+
*/
|
|
25
|
+
export interface ToolDefinitionInstance<TInput extends z.ZodType = z.ZodType, TOutput extends z.ZodType = z.ZodType, TName extends string = string> extends Tool<TInput, TOutput, TName> {
|
|
26
|
+
__toolSide: 'definition';
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Union type for any kind of client-side tool (client tool or definition)
|
|
30
|
+
*/
|
|
31
|
+
export type AnyClientTool = ClientTool<any, any> | ToolDefinitionInstance<any, any>;
|
|
32
|
+
/**
|
|
33
|
+
* Extract the tool name as a literal type
|
|
34
|
+
*/
|
|
35
|
+
export type InferToolName<T> = T extends {
|
|
36
|
+
name: infer N;
|
|
37
|
+
} ? N : never;
|
|
38
|
+
/**
|
|
39
|
+
* Extract the input type from a tool (inferred from Zod schema)
|
|
40
|
+
*/
|
|
41
|
+
export type InferToolInput<T> = T extends {
|
|
42
|
+
inputSchema?: infer TInput;
|
|
43
|
+
} ? TInput extends z.ZodType ? z.infer<TInput> : any : any;
|
|
44
|
+
/**
|
|
45
|
+
* Extract the output type from a tool (inferred from Zod schema)
|
|
46
|
+
*/
|
|
47
|
+
export type InferToolOutput<T> = T extends {
|
|
48
|
+
outputSchema?: infer TOutput;
|
|
49
|
+
} ? TOutput extends z.ZodType ? z.infer<TOutput> : any : any;
|
|
50
|
+
/**
|
|
51
|
+
* Tool definition configuration
|
|
52
|
+
*/
|
|
53
|
+
export interface ToolDefinitionConfig<TInput extends z.ZodType = z.ZodType, TOutput extends z.ZodType = z.ZodType, TName extends string = string> {
|
|
54
|
+
name: TName;
|
|
55
|
+
description: string;
|
|
56
|
+
inputSchema?: TInput;
|
|
57
|
+
outputSchema?: TOutput;
|
|
58
|
+
needsApproval?: boolean;
|
|
59
|
+
metadata?: Record<string, any>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Tool definition builder that allows creating server or client tools from a shared definition
|
|
63
|
+
*/
|
|
64
|
+
export interface ToolDefinition<TInput extends z.ZodType = z.ZodType, TOutput extends z.ZodType = z.ZodType, TName extends string = string> extends ToolDefinitionInstance<TInput, TOutput, TName> {
|
|
65
|
+
/**
|
|
66
|
+
* Create a server-side tool with execute function
|
|
67
|
+
*/
|
|
68
|
+
server: (execute: (args: z.infer<TInput>) => Promise<z.infer<TOutput>> | z.infer<TOutput>) => ServerTool<TInput, TOutput, TName>;
|
|
69
|
+
/**
|
|
70
|
+
* Create a client-side tool with optional execute function
|
|
71
|
+
*/
|
|
72
|
+
client: (execute?: (args: z.infer<TInput>) => Promise<z.infer<TOutput>> | z.infer<TOutput>) => ClientTool<TInput, TOutput, TName>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create an isomorphic tool definition that can be used directly or instantiated for server/client
|
|
76
|
+
*
|
|
77
|
+
* The definition contains all tool metadata (name, description, schemas) and can be:
|
|
78
|
+
* 1. Used directly in chat() on the server (as a tool definition without execute)
|
|
79
|
+
* 2. Instantiated as a server tool with .server()
|
|
80
|
+
* 3. Instantiated as a client tool with .client()
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* import { toolDefinition } from '@tanstack/ai';
|
|
85
|
+
* import { z } from 'zod';
|
|
86
|
+
*
|
|
87
|
+
* const addToCartTool = toolDefinition({
|
|
88
|
+
* name: 'addToCart',
|
|
89
|
+
* description: 'Add a guitar to the shopping cart (requires approval)',
|
|
90
|
+
* needsApproval: true,
|
|
91
|
+
* inputSchema: z.object({
|
|
92
|
+
* guitarId: z.string(),
|
|
93
|
+
* quantity: z.number(),
|
|
94
|
+
* }),
|
|
95
|
+
* outputSchema: z.object({
|
|
96
|
+
* success: z.boolean(),
|
|
97
|
+
* cartId: z.string(),
|
|
98
|
+
* totalItems: z.number(),
|
|
99
|
+
* }),
|
|
100
|
+
* });
|
|
101
|
+
*
|
|
102
|
+
* // Use directly in chat (server-side, no execute function)
|
|
103
|
+
* chat({
|
|
104
|
+
* tools: [addToCartTool],
|
|
105
|
+
* // ...
|
|
106
|
+
* });
|
|
107
|
+
*
|
|
108
|
+
* // Or create server-side implementation
|
|
109
|
+
* const addToCartServer = addToCartTool.server(async (args) => {
|
|
110
|
+
* // args is typed as { guitarId: string; quantity: number }
|
|
111
|
+
* return {
|
|
112
|
+
* success: true,
|
|
113
|
+
* cartId: 'CART_' + Date.now(),
|
|
114
|
+
* totalItems: args.quantity,
|
|
115
|
+
* };
|
|
116
|
+
* });
|
|
117
|
+
*
|
|
118
|
+
* // Or create client-side implementation
|
|
119
|
+
* const addToCartClient = addToCartTool.client(async (args) => {
|
|
120
|
+
* // Client-specific logic (e.g., localStorage)
|
|
121
|
+
* return { success: true, cartId: 'local', totalItems: 1 };
|
|
122
|
+
* });
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export declare function toolDefinition<TInput extends z.ZodType = z.ZodAny, TOutput extends z.ZodType = z.ZodAny, TName extends string = string>(config: ToolDefinitionConfig<TInput, TOutput, TName>): ToolDefinition<TInput, TOutput, TName>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
function toolDefinition(config) {
|
|
2
|
+
const definition = {
|
|
3
|
+
__toolSide: "definition",
|
|
4
|
+
...config,
|
|
5
|
+
server(execute) {
|
|
6
|
+
return {
|
|
7
|
+
__toolSide: "server",
|
|
8
|
+
...config,
|
|
9
|
+
execute
|
|
10
|
+
};
|
|
11
|
+
},
|
|
12
|
+
client(execute) {
|
|
13
|
+
return {
|
|
14
|
+
__toolSide: "client",
|
|
15
|
+
...config,
|
|
16
|
+
execute
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
return definition;
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
toolDefinition
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=tool-definition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-definition.js","sources":["../../../src/tools/tool-definition.ts"],"sourcesContent":["import type { z } from 'zod'\nimport type { Tool } from '../types'\n\n/**\n * Marker type for server-side tools\n */\nexport interface ServerTool<\n TInput extends z.ZodType = z.ZodType,\n TOutput extends z.ZodType = z.ZodType,\n TName extends string = string,\n> extends Tool<TInput, TOutput, TName> {\n __toolSide: 'server'\n}\n\n/**\n * Marker type for client-side tools\n */\nexport interface ClientTool<\n TInput extends z.ZodType = z.ZodType,\n TOutput extends z.ZodType = z.ZodType,\n TName extends string = string,\n> {\n __toolSide: 'client'\n name: TName\n description: string\n inputSchema?: TInput\n outputSchema?: TOutput\n needsApproval?: boolean\n metadata?: Record<string, any>\n execute?: (\n args: z.infer<TInput>,\n ) => Promise<z.infer<TOutput>> | z.infer<TOutput>\n}\n\n/**\n * Tool definition that can be used directly or instantiated for server/client\n */\nexport interface ToolDefinitionInstance<\n TInput extends z.ZodType = z.ZodType,\n TOutput extends z.ZodType = z.ZodType,\n TName extends string = string,\n> extends Tool<TInput, TOutput, TName> {\n __toolSide: 'definition'\n}\n\n/**\n * Union type for any kind of client-side tool (client tool or definition)\n */\nexport type AnyClientTool =\n | ClientTool<any, any>\n | ToolDefinitionInstance<any, any>\n\n/**\n * Extract the tool name as a literal type\n */\nexport type InferToolName<T> = T extends { name: infer N } ? N : never\n\n/**\n * Extract the input type from a tool (inferred from Zod schema)\n */\nexport type InferToolInput<T> = T extends { inputSchema?: infer TInput }\n ? TInput extends z.ZodType\n ? z.infer<TInput>\n : any\n : any\n\n/**\n * Extract the output type from a tool (inferred from Zod schema)\n */\nexport type InferToolOutput<T> = T extends { outputSchema?: infer TOutput }\n ? TOutput extends z.ZodType\n ? z.infer<TOutput>\n : any\n : any\n\n/**\n * Tool definition configuration\n */\nexport interface ToolDefinitionConfig<\n TInput extends z.ZodType = z.ZodType,\n TOutput extends z.ZodType = z.ZodType,\n TName extends string = string,\n> {\n name: TName\n description: string\n inputSchema?: TInput\n outputSchema?: TOutput\n needsApproval?: boolean\n metadata?: Record<string, any>\n}\n\n/**\n * Tool definition builder that allows creating server or client tools from a shared definition\n */\nexport interface ToolDefinition<\n TInput extends z.ZodType = z.ZodType,\n TOutput extends z.ZodType = z.ZodType,\n TName extends string = string,\n> extends ToolDefinitionInstance<TInput, TOutput, TName> {\n /**\n * Create a server-side tool with execute function\n */\n server: (\n execute: (\n args: z.infer<TInput>,\n ) => Promise<z.infer<TOutput>> | z.infer<TOutput>,\n ) => ServerTool<TInput, TOutput, TName>\n\n /**\n * Create a client-side tool with optional execute function\n */\n client: (\n execute?: (\n args: z.infer<TInput>,\n ) => Promise<z.infer<TOutput>> | z.infer<TOutput>,\n ) => ClientTool<TInput, TOutput, TName>\n}\n\n/**\n * Create an isomorphic tool definition that can be used directly or instantiated for server/client\n *\n * The definition contains all tool metadata (name, description, schemas) and can be:\n * 1. Used directly in chat() on the server (as a tool definition without execute)\n * 2. Instantiated as a server tool with .server()\n * 3. Instantiated as a client tool with .client()\n *\n * @example\n * ```typescript\n * import { toolDefinition } from '@tanstack/ai';\n * import { z } from 'zod';\n *\n * const addToCartTool = toolDefinition({\n * name: 'addToCart',\n * description: 'Add a guitar to the shopping cart (requires approval)',\n * needsApproval: true,\n * inputSchema: z.object({\n * guitarId: z.string(),\n * quantity: z.number(),\n * }),\n * outputSchema: z.object({\n * success: z.boolean(),\n * cartId: z.string(),\n * totalItems: z.number(),\n * }),\n * });\n *\n * // Use directly in chat (server-side, no execute function)\n * chat({\n * tools: [addToCartTool],\n * // ...\n * });\n *\n * // Or create server-side implementation\n * const addToCartServer = addToCartTool.server(async (args) => {\n * // args is typed as { guitarId: string; quantity: number }\n * return {\n * success: true,\n * cartId: 'CART_' + Date.now(),\n * totalItems: args.quantity,\n * };\n * });\n *\n * // Or create client-side implementation\n * const addToCartClient = addToCartTool.client(async (args) => {\n * // Client-specific logic (e.g., localStorage)\n * return { success: true, cartId: 'local', totalItems: 1 };\n * });\n * ```\n */\nexport function toolDefinition<\n TInput extends z.ZodType = z.ZodAny,\n TOutput extends z.ZodType = z.ZodAny,\n TName extends string = string,\n>(\n config: ToolDefinitionConfig<TInput, TOutput, TName>,\n): ToolDefinition<TInput, TOutput, TName> {\n const definition: ToolDefinition<TInput, TOutput, TName> = {\n __toolSide: 'definition',\n ...config,\n server(\n execute: (\n args: z.infer<TInput>,\n ) => Promise<z.infer<TOutput>> | z.infer<TOutput>,\n ): ServerTool<TInput, TOutput, TName> {\n return {\n __toolSide: 'server',\n ...config,\n execute,\n }\n },\n\n client(\n execute?: (\n args: z.infer<TInput>,\n ) => Promise<z.infer<TOutput>> | z.infer<TOutput>,\n ): ClientTool<TInput, TOutput, TName> {\n return {\n __toolSide: 'client',\n ...config,\n execute,\n }\n },\n }\n\n return definition\n}\n"],"names":[],"mappings":"AAyKO,SAAS,eAKd,QACwC;AACxC,QAAM,aAAqD;AAAA,IACzD,YAAY;AAAA,IACZ,GAAG;AAAA,IACH,OACE,SAGoC;AACpC,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,GAAG;AAAA,QACH;AAAA,MAAA;AAAA,IAEJ;AAAA,IAEA,OACE,SAGoC;AACpC,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,GAAG;AAAA,QACH;AAAA,MAAA;AAAA,IAEJ;AAAA,EAAA;AAGF,SAAO;AACT;"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Converts a Zod schema to JSON Schema format compatible with LLM providers.
|
|
4
|
+
*
|
|
5
|
+
*
|
|
6
|
+
* @param schema - Zod schema to convert
|
|
7
|
+
* @returns JSON Schema object that can be sent to LLM providers
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { z } from 'zod';
|
|
12
|
+
*
|
|
13
|
+
* const schema = z.object({
|
|
14
|
+
* location: z.string().describe('City name'),
|
|
15
|
+
* unit: z.enum(['celsius', 'fahrenheit']).optional()
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* const jsonSchema = convertZodToJsonSchema(schema);
|
|
19
|
+
* // Returns:
|
|
20
|
+
* // {
|
|
21
|
+
* // type: 'object',
|
|
22
|
+
* // properties: {
|
|
23
|
+
* // location: { type: 'string', description: 'City name' },
|
|
24
|
+
* // unit: { type: 'string', enum: ['celsius', 'fahrenheit'] }
|
|
25
|
+
* // },
|
|
26
|
+
* // required: ['location']
|
|
27
|
+
* // }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function convertZodToJsonSchema(schema: z.ZodType | undefined): Record<string, any> | undefined;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { toJSONSchema } from "zod";
|
|
2
|
+
function convertZodToJsonSchema(schema) {
|
|
3
|
+
if (!schema) return void 0;
|
|
4
|
+
const jsonSchema = toJSONSchema(schema, {
|
|
5
|
+
target: "openapi-3.0",
|
|
6
|
+
reused: "ref"
|
|
7
|
+
});
|
|
8
|
+
let result = jsonSchema;
|
|
9
|
+
if (typeof result === "object" && "$schema" in result) {
|
|
10
|
+
const { $schema, ...rest } = result;
|
|
11
|
+
result = rest;
|
|
12
|
+
}
|
|
13
|
+
if (typeof result === "object") {
|
|
14
|
+
const isZodObject = typeof schema === "object" && "def" in schema && schema.def.type === "object";
|
|
15
|
+
if (isZodObject && !result.type) {
|
|
16
|
+
result.type = "object";
|
|
17
|
+
}
|
|
18
|
+
if (Object.keys(result).length === 0) {
|
|
19
|
+
result.type = "object";
|
|
20
|
+
}
|
|
21
|
+
if ("properties" in result && !result.type) {
|
|
22
|
+
result.type = "object";
|
|
23
|
+
}
|
|
24
|
+
if (result.type === "object" && !("properties" in result)) {
|
|
25
|
+
result.properties = {};
|
|
26
|
+
}
|
|
27
|
+
if (result.type === "object" && !("required" in result)) {
|
|
28
|
+
result.required = [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
convertZodToJsonSchema
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=zod-converter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zod-converter.js","sources":["../../../src/tools/zod-converter.ts"],"sourcesContent":["import { toJSONSchema } from 'zod'\nimport type { z } from 'zod'\n/**\n * Converts a Zod schema to JSON Schema format compatible with LLM providers.\n *\n *\n * @param schema - Zod schema to convert\n * @returns JSON Schema object that can be sent to LLM providers\n *\n * @example\n * ```typescript\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * location: z.string().describe('City name'),\n * unit: z.enum(['celsius', 'fahrenheit']).optional()\n * });\n *\n * const jsonSchema = convertZodToJsonSchema(schema);\n * // Returns:\n * // {\n * // type: 'object',\n * // properties: {\n * // location: { type: 'string', description: 'City name' },\n * // unit: { type: 'string', enum: ['celsius', 'fahrenheit'] }\n * // },\n * // required: ['location']\n * // }\n * ```\n */\nexport function convertZodToJsonSchema(\n schema: z.ZodType | undefined,\n): Record<string, any> | undefined {\n if (!schema) return undefined\n\n // Use Alcyone Labs fork which is compatible with Zod v4\n const jsonSchema = toJSONSchema(schema, {\n target: 'openapi-3.0',\n reused: 'ref',\n })\n\n // Remove $schema property as it's not needed for LLM providers\n let result = jsonSchema\n if (typeof result === 'object' && '$schema' in result) {\n const { $schema, ...rest } = result\n result = rest\n }\n\n // Ensure object schemas always have type: \"object\"\n // This fixes cases where zod-to-json-schema doesn't set type for empty objects\n if (typeof result === 'object') {\n // Check if the input schema is a ZodObject by inspecting its internal structure\n const isZodObject =\n typeof schema === 'object' &&\n 'def' in schema &&\n schema.def.type === 'object'\n\n // If we know it's a ZodObject but result doesn't have type, set it\n if (isZodObject && !result.type) {\n result.type = 'object'\n }\n\n // If result is completely empty (no keys), it's likely an empty object schema\n if (Object.keys(result).length === 0) {\n result.type = 'object'\n }\n\n // If it has properties (even empty), it should be an object type\n if ('properties' in result && !result.type) {\n result.type = 'object'\n }\n\n // Ensure properties exists for object types (even if empty)\n if (result.type === 'object' && !('properties' in result)) {\n result.properties = {}\n }\n\n // Ensure required exists for object types (even if empty array)\n if (result.type === 'object' && !('required' in result)) {\n result.required = []\n }\n }\n\n return result\n}\n"],"names":[],"mappings":";AA8BO,SAAS,uBACd,QACiC;AACjC,MAAI,CAAC,OAAQ,QAAO;AAGpB,QAAM,aAAa,aAAa,QAAQ;AAAA,IACtC,QAAQ;AAAA,IACR,QAAQ;AAAA,EAAA,CACT;AAGD,MAAI,SAAS;AACb,MAAI,OAAO,WAAW,YAAY,aAAa,QAAQ;AACrD,UAAM,EAAE,SAAS,GAAG,KAAA,IAAS;AAC7B,aAAS;AAAA,EACX;AAIA,MAAI,OAAO,WAAW,UAAU;AAE9B,UAAM,cACJ,OAAO,WAAW,YAClB,SAAS,UACT,OAAO,IAAI,SAAS;AAGtB,QAAI,eAAe,CAAC,OAAO,MAAM;AAC/B,aAAO,OAAO;AAAA,IAChB;AAGA,QAAI,OAAO,KAAK,MAAM,EAAE,WAAW,GAAG;AACpC,aAAO,OAAO;AAAA,IAChB;AAGA,QAAI,gBAAgB,UAAU,CAAC,OAAO,MAAM;AAC1C,aAAO,OAAO;AAAA,IAChB;AAGA,QAAI,OAAO,SAAS,YAAY,EAAE,gBAAgB,SAAS;AACzD,aAAO,aAAa,CAAA;AAAA,IACtB;AAGA,QAAI,OAAO,SAAS,YAAY,EAAE,cAAc,SAAS;AACvD,aAAO,WAAW,CAAA;AAAA,IACpB;AAAA,EACF;AAEA,SAAO;AACT;"}
|