@shardflux/sdk 0.6.2 → 0.8.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/CHANGELOG.md +109 -1
- package/README.md +202 -9
- package/dist/capture-adapters.d.ts +178 -0
- package/dist/capture-adapters.js +432 -0
- package/dist/capture-serialize.d.ts +97 -0
- package/dist/capture-serialize.js +481 -0
- package/dist/capture-text.d.ts +14 -0
- package/dist/capture-text.js +13 -0
- package/dist/capture.d.ts +288 -0
- package/dist/capture.js +1326 -0
- package/dist/cell.d.ts +7 -0
- package/dist/cell.js +13 -0
- package/dist/client.d.ts +16 -0
- package/dist/client.js +21 -4
- package/dist/egress.d.ts +18 -0
- package/dist/errors.d.ts +8 -1
- package/dist/generated/app-api.d.ts +215 -0
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.js +4 -1
- package/dist/lifecycle.d.ts +3 -1
- package/dist/lifecycle.js +5 -1
- package/dist/progress.d.ts +3 -1
- package/dist/tar.d.ts +40 -0
- package/dist/tar.js +150 -0
- package/dist/template-file.d.ts +92 -0
- package/dist/template-file.js +326 -0
- package/dist/templates.d.ts +318 -9
- package/dist/templates.js +432 -13
- package/dist/tools.d.ts +49 -33
- package/dist/tools.js +25 -4
- package/dist/workspace.d.ts +25 -2
- package/dist/workspace.js +49 -2
- package/package.json +25 -2
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { errorInfo, parseArguments } from "./capture-serialize.js";
|
|
2
|
+
const str = (v) => (typeof v === 'string' && v.length > 0 ? v : null);
|
|
3
|
+
const obj = (v) => (typeof v === 'object' && v !== null ? v : null);
|
|
4
|
+
/** A copy of `target` with its prototype and own properties, and `key` replaced by `value` (the original is untouched). */
|
|
5
|
+
export function copyWith(target, key, value) {
|
|
6
|
+
const descriptors = Object.getOwnPropertyDescriptors(target);
|
|
7
|
+
const own = descriptors[key];
|
|
8
|
+
descriptors[key] = { value, writable: true, configurable: true, enumerable: own ? own.enumerable === true : false };
|
|
9
|
+
return Object.create(Object.getPrototypeOf(target), descriptors);
|
|
10
|
+
}
|
|
11
|
+
function wrapMethod(target, key, spec, core) {
|
|
12
|
+
const original = target[key];
|
|
13
|
+
const wrapped = function (...args) {
|
|
14
|
+
let s;
|
|
15
|
+
try {
|
|
16
|
+
s = spec(args);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return original.apply(this, args);
|
|
20
|
+
}
|
|
21
|
+
return core.observe(s, original, this, args);
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(wrapped, 'name', { value: original.name, configurable: true });
|
|
24
|
+
Object.defineProperty(wrapped, 'length', { value: original.length, configurable: true });
|
|
25
|
+
return copyWith(target, key, wrapped);
|
|
26
|
+
}
|
|
27
|
+
// ---- shape detection --------------------------------------------------------------------------------------------
|
|
28
|
+
const toolName = (t, key) => key ?? str(t.name) ?? str(t.id) ?? 'tool';
|
|
29
|
+
const isOpenAIFunctionTool = (t) => t.type === 'function' && typeof t.invoke === 'function' && 'parameters' in t;
|
|
30
|
+
const isAnthropicRunnable = (t) => typeof t.run === 'function' && ('input_schema' in t || typeof t.parse === 'function');
|
|
31
|
+
const isMastraTool = (t) => typeof t.execute === 'function' && Object.prototype.hasOwnProperty.call(t, 'mastra') && 'id' in t;
|
|
32
|
+
const isWorkspaceTool = (t) => typeof t.execute === 'function' && 'permission' in t && 'parameters' in t;
|
|
33
|
+
/** A tool definition without an executor (AI SDK provider or client-side tool, Anthropic server tool): left as it is. */
|
|
34
|
+
const isDefinitionOnly = (t) => typeof t.execute !== 'function' && typeof t.run !== 'function' && typeof t.invoke !== 'function' && ('inputSchema' in t || 'input_schema' in t || 'parameters' in t || t.type === 'provider' || t.type === 'provider-defined');
|
|
35
|
+
/** A Mastra ValidationError is returned, not thrown: recorded with status error. */
|
|
36
|
+
function mastraPost(v) {
|
|
37
|
+
const o = obj(v);
|
|
38
|
+
if (o && o.error === true && 'validationErrors' in o)
|
|
39
|
+
return { output: v, status: 'error', error: { type: 'ValidationError', message: typeof o.message === 'string' ? o.message : 'validation failed' } };
|
|
40
|
+
return { output: v };
|
|
41
|
+
}
|
|
42
|
+
/** A LangChain ToolMessage: recorded as its content (plus artifact), status error when the message says so. */
|
|
43
|
+
export function isToolMessage(v) {
|
|
44
|
+
const o = obj(v);
|
|
45
|
+
if (!o || typeof o.tool_call_id !== 'string' || !('content' in o))
|
|
46
|
+
return false;
|
|
47
|
+
try {
|
|
48
|
+
const getType = o._getType;
|
|
49
|
+
return o.type === 'tool' || (typeof getType === 'function' && getType.call(o) === 'tool') || o.constructor?.name === 'ToolMessage';
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function toolMessagePost(v) {
|
|
56
|
+
if (!isToolMessage(v))
|
|
57
|
+
return { output: v };
|
|
58
|
+
const output = v.artifact !== undefined ? { content: v.content, artifact: v.artifact } : v.content;
|
|
59
|
+
if (v.status === 'error')
|
|
60
|
+
return { output, status: 'error', error: { type: 'ToolError', message: typeof v.content === 'string' ? v.content : JSON.stringify(v.content) } };
|
|
61
|
+
return { output };
|
|
62
|
+
}
|
|
63
|
+
/** An MCP CallToolResult with isError: status error, message from its first text block. */
|
|
64
|
+
function mcpPost(v) {
|
|
65
|
+
const o = obj(v);
|
|
66
|
+
if (!o || (o.isError !== true && o.is_error !== true))
|
|
67
|
+
return { output: v };
|
|
68
|
+
const first = Array.isArray(o.content) ? o.content.map(obj).find((b) => b?.type === 'text' && typeof b.text === 'string') : undefined;
|
|
69
|
+
return { output: v, status: 'error', error: { type: 'McpToolError', message: first?.text ?? 'the tool reported an error' } };
|
|
70
|
+
}
|
|
71
|
+
function executeSpec(name, source, final, post) {
|
|
72
|
+
return (args) => {
|
|
73
|
+
const opts = obj(args[1]);
|
|
74
|
+
const callId = str(opts?.toolCallId) ?? str(obj(opts?.agent)?.toolCallId) ?? null;
|
|
75
|
+
return { tool: name, source, input: args[0], callId, final, ...(post ? { post } : {}) };
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function wrapOne(core, t, key, preferred) {
|
|
79
|
+
if (typeof t === 'function') {
|
|
80
|
+
if (!key)
|
|
81
|
+
throw new TypeError('capture.tools(): a bare function needs a name; pass an object { name: fn }');
|
|
82
|
+
return wrapFunction(core, key, t);
|
|
83
|
+
}
|
|
84
|
+
const o = obj(t);
|
|
85
|
+
if (!o)
|
|
86
|
+
throw new TypeError(`capture.tools(): ${key ? `"${key}"` : 'an item'} is not a tool (got ${t === null ? 'null' : typeof t})`);
|
|
87
|
+
const name = toolName(o, key);
|
|
88
|
+
if (isOpenAIFunctionTool(o))
|
|
89
|
+
return wrapMethod(o, 'invoke', openAIInvokeSpec(name), core);
|
|
90
|
+
if (isAnthropicRunnable(o))
|
|
91
|
+
return wrapMethod(o, 'run', anthropicRunSpec(name), core);
|
|
92
|
+
if (typeof o.execute === 'function') {
|
|
93
|
+
if (isWorkspaceTool(o))
|
|
94
|
+
return wrapMethod(o, 'execute', executeSpec(name, 'workspace', false), core);
|
|
95
|
+
if (isMastraTool(o) || preferred === 'mastra')
|
|
96
|
+
return wrapMethod(o, 'execute', executeSpec(name, 'mastra', true, mastraPost), core);
|
|
97
|
+
if ('inputSchema' in o || preferred === 'ai-sdk')
|
|
98
|
+
return wrapMethod(o, 'execute', executeSpec(name, 'ai-sdk', true), core);
|
|
99
|
+
return wrapMethod(o, 'execute', executeSpec(name, preferred ?? 'wrap', false), core);
|
|
100
|
+
}
|
|
101
|
+
if (typeof o.invoke === 'function')
|
|
102
|
+
return wrapMethod(o, 'invoke', langchainInvokeSpec(name), core);
|
|
103
|
+
if (typeof o.run === 'function')
|
|
104
|
+
return wrapMethod(o, 'run', anthropicRunSpec(name), core);
|
|
105
|
+
if (isDefinitionOnly(o))
|
|
106
|
+
return o;
|
|
107
|
+
throw new TypeError(`capture.tools(): ${key ? `"${key}"` : `"${name}"`} has no execute, run or invoke method`);
|
|
108
|
+
}
|
|
109
|
+
function wrapFunction(core, name, fn) {
|
|
110
|
+
const wrapped = function (...args) {
|
|
111
|
+
return core.observe({ tool: name, source: 'wrap', input: args.length === 1 ? args[0] : args, callId: null }, fn, this, args);
|
|
112
|
+
};
|
|
113
|
+
Object.defineProperty(wrapped, 'name', { value: fn.name, configurable: true });
|
|
114
|
+
Object.defineProperty(wrapped, 'length', { value: fn.length, configurable: true });
|
|
115
|
+
return wrapped;
|
|
116
|
+
}
|
|
117
|
+
const TOOL_METADATA = ['name', 'description', 'parameters', 'inputSchema', 'input_schema', 'schema', 'id'];
|
|
118
|
+
/**
|
|
119
|
+
* One tool object, not a collection: an executor (`execute` / `run` / `invoke`) plus tool metadata (name, description,
|
|
120
|
+
* parameters or a schema), and not a record whose values are all functions: `{ execute: sqlFn, fetch_url: fetchFn }`
|
|
121
|
+
* is two plain-function tools, one of them named `execute`.
|
|
122
|
+
*/
|
|
123
|
+
function looksLikeOneTool(o) {
|
|
124
|
+
if (typeof o.execute !== 'function' && typeof o.run !== 'function' && typeof o.invoke !== 'function')
|
|
125
|
+
return false;
|
|
126
|
+
const values = Object.values(o);
|
|
127
|
+
if (values.length > 0 && values.every((v) => typeof v === 'function'))
|
|
128
|
+
return false;
|
|
129
|
+
return TOOL_METADATA.some((k) => k in o && typeof o[k] !== 'function' && o[k] !== undefined);
|
|
130
|
+
}
|
|
131
|
+
function wrapCollection(core, tools, preferred) {
|
|
132
|
+
if (Array.isArray(tools))
|
|
133
|
+
return tools.map((t) => wrapOne(core, t, undefined, preferred));
|
|
134
|
+
const o = obj(tools);
|
|
135
|
+
if (!o)
|
|
136
|
+
throw new TypeError(`capture.tools() takes an array or an object of tools (got ${tools === null ? 'null' : typeof tools})`);
|
|
137
|
+
if (looksLikeOneTool(o))
|
|
138
|
+
return wrapOne(core, o, undefined, preferred);
|
|
139
|
+
const out = Object.create(Object.getPrototypeOf(o));
|
|
140
|
+
for (const [k, v] of Object.entries(o))
|
|
141
|
+
out[k] = wrapOne(core, v, k, preferred);
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
// ---- per framework ----------------------------------------------------------------------------------------------
|
|
145
|
+
function anthropicRunSpec(name) {
|
|
146
|
+
return (args) => {
|
|
147
|
+
const ctx = obj(args[1]);
|
|
148
|
+
const use = obj(ctx?.toolUse) ?? obj(ctx?.toolUseBlock);
|
|
149
|
+
return { tool: name, source: 'anthropic', input: args[0], callId: str(use?.id) };
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function openAIInvokeSpec(name) {
|
|
153
|
+
return (args) => {
|
|
154
|
+
const details = obj(args[2]);
|
|
155
|
+
const call = obj(details?.toolCall);
|
|
156
|
+
return { tool: name, source: 'openai-agents', input: parseArguments(args[1]), callId: str(call?.callId) ?? str(call?.id) };
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function langchainInvokeSpec(name) {
|
|
160
|
+
return (args) => {
|
|
161
|
+
const input = obj(args[0]);
|
|
162
|
+
const isCall = input !== null && input.type === 'tool_call' && 'args' in input;
|
|
163
|
+
const config = obj(args[1]);
|
|
164
|
+
const callId = (isCall ? str(input.id) : null) ?? str(obj(config?.toolCall)?.id);
|
|
165
|
+
return { tool: name, source: 'langchain', input: isCall ? input.args : args[0], callId, post: toolMessagePost };
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const LANGCHAIN_PENDING = 10_000;
|
|
169
|
+
const SHARDFLUX_MCP = '^mcp__shardflux__';
|
|
170
|
+
export function createAdapters(core) {
|
|
171
|
+
const aiSdkEnd = (event) => {
|
|
172
|
+
try {
|
|
173
|
+
const call = event.toolCall ?? {};
|
|
174
|
+
const out = event.toolOutput;
|
|
175
|
+
let output;
|
|
176
|
+
let error;
|
|
177
|
+
if (out) {
|
|
178
|
+
if (out.type === 'tool-error')
|
|
179
|
+
error = out.error ?? new Error('tool error');
|
|
180
|
+
else
|
|
181
|
+
output = out.output;
|
|
182
|
+
}
|
|
183
|
+
else if (typeof event.success === 'boolean') {
|
|
184
|
+
if (event.success)
|
|
185
|
+
output = event.output;
|
|
186
|
+
else
|
|
187
|
+
error = event.error ?? new Error('tool error');
|
|
188
|
+
}
|
|
189
|
+
const durationMs = event.toolExecutionMs ?? event.durationMs;
|
|
190
|
+
core.hook({
|
|
191
|
+
tool: call.toolName ?? 'tool',
|
|
192
|
+
input: call.input,
|
|
193
|
+
output,
|
|
194
|
+
error,
|
|
195
|
+
callId: call.toolCallId ?? null,
|
|
196
|
+
...(typeof durationMs === 'number' ? { durationMs, startedAt: Date.now() - durationMs } : {}),
|
|
197
|
+
source: 'ai-sdk',
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// never into the harness
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const aiSdk = {
|
|
205
|
+
tools: (tools) => wrapCollection(core, tools, 'ai-sdk'),
|
|
206
|
+
callbacks: ((opts = {}) => (opts.legacy ? { experimental_onToolCallFinish: aiSdkEnd } : { onToolExecutionEnd: aiSdkEnd })),
|
|
207
|
+
};
|
|
208
|
+
const mastra = {
|
|
209
|
+
hooks: (existing) => {
|
|
210
|
+
const theirs = existing?.afterToolCall;
|
|
211
|
+
return {
|
|
212
|
+
...(existing ?? {}),
|
|
213
|
+
afterToolCall: async (ctx) => {
|
|
214
|
+
try {
|
|
215
|
+
const context = obj(ctx.context);
|
|
216
|
+
const post = mastraPost(ctx.output);
|
|
217
|
+
const agentId = str(obj(ctx.metadata)?.agentId);
|
|
218
|
+
core.hook({
|
|
219
|
+
tool: str(ctx.toolName) ?? 'tool',
|
|
220
|
+
input: ctx.input,
|
|
221
|
+
output: post.output,
|
|
222
|
+
error: ctx.error ?? post.error,
|
|
223
|
+
...(post.status && ctx.error === undefined ? { status: post.status } : {}),
|
|
224
|
+
callId: str(context?.toolCallId) ?? str(obj(context?.agent)?.toolCallId),
|
|
225
|
+
...(agentId ? { meta: { agent_id: agentId } } : {}),
|
|
226
|
+
source: 'mastra',
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// never into the harness
|
|
231
|
+
}
|
|
232
|
+
if (theirs)
|
|
233
|
+
await theirs(ctx);
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
tools: (tools) => wrapCollection(core, tools, 'mastra'),
|
|
238
|
+
};
|
|
239
|
+
const anthropic = {
|
|
240
|
+
tools: (tools) => {
|
|
241
|
+
if (!Array.isArray(tools))
|
|
242
|
+
return wrapCollection(core, tools);
|
|
243
|
+
return tools.map((t) => {
|
|
244
|
+
const o = obj(t);
|
|
245
|
+
if (o && typeof o.run === 'function')
|
|
246
|
+
return wrapMethod(o, 'run', anthropicRunSpec(toolName(o)), core);
|
|
247
|
+
return t; // server tools and plain definitions pass through
|
|
248
|
+
});
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
const openaiAgents = {
|
|
252
|
+
tools: (tools) => {
|
|
253
|
+
const one = (t) => {
|
|
254
|
+
const o = obj(t);
|
|
255
|
+
return o && typeof o.invoke === 'function' ? wrapMethod(o, 'invoke', openAIInvokeSpec(toolName(o)), core) : t;
|
|
256
|
+
};
|
|
257
|
+
return (Array.isArray(tools) ? tools.map(one) : one(tools));
|
|
258
|
+
},
|
|
259
|
+
execute: (name, fn) => {
|
|
260
|
+
const wrapped = function (...args) {
|
|
261
|
+
const call = obj(obj(args[2])?.toolCall);
|
|
262
|
+
return core.observe({ tool: name, source: 'openai-agents', input: args[0], callId: str(call?.callId) ?? str(call?.id) }, fn, this, args);
|
|
263
|
+
};
|
|
264
|
+
Object.defineProperty(wrapped, 'name', { value: fn.name, configurable: true });
|
|
265
|
+
Object.defineProperty(wrapped, 'length', { value: fn.length, configurable: true });
|
|
266
|
+
return wrapped;
|
|
267
|
+
},
|
|
268
|
+
attach: (target) => {
|
|
269
|
+
// Runner: (context, agent, tool, result, details); Agent: (context, tool, result, details). Read from the end.
|
|
270
|
+
const listener = (...args) => {
|
|
271
|
+
try {
|
|
272
|
+
const details = obj(args[args.length - 1]);
|
|
273
|
+
const result = args[args.length - 2];
|
|
274
|
+
const tool = obj(args[args.length - 3]);
|
|
275
|
+
const call = obj(details?.toolCall);
|
|
276
|
+
core.hook({
|
|
277
|
+
tool: str(tool?.name) ?? str(call?.name) ?? 'tool',
|
|
278
|
+
input: parseArguments(call?.arguments),
|
|
279
|
+
output: result,
|
|
280
|
+
callId: str(call?.callId) ?? str(call?.id),
|
|
281
|
+
source: 'openai-agents',
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
// never into the harness
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
target.on('agent_tool_end', listener);
|
|
289
|
+
return () => {
|
|
290
|
+
target.off('agent_tool_end', listener);
|
|
291
|
+
};
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
const claude = {
|
|
295
|
+
hooks: (existing) => {
|
|
296
|
+
const record = (input, toolUseID, failed) => {
|
|
297
|
+
try {
|
|
298
|
+
const i = obj(input) ?? {};
|
|
299
|
+
const meta = {};
|
|
300
|
+
if (str(i.agent_id))
|
|
301
|
+
meta.agent_id = i.agent_id;
|
|
302
|
+
if (str(i.agent_type))
|
|
303
|
+
meta.agent_type = i.agent_type;
|
|
304
|
+
if (str(obj(i.mcp_server)?.name))
|
|
305
|
+
meta.server = obj(i.mcp_server).name;
|
|
306
|
+
const durationMs = typeof i.duration_ms === 'number' ? i.duration_ms : undefined;
|
|
307
|
+
core.hook({
|
|
308
|
+
tool: str(i.tool_name) ?? 'tool',
|
|
309
|
+
input: i.tool_input,
|
|
310
|
+
...(failed
|
|
311
|
+
? { error: { type: i.is_interrupt === true ? 'Interrupted' : 'ToolError', message: typeof i.error === 'string' ? i.error : errorInfo(i.error).message }, status: i.is_interrupt === true ? 'cancelled' : 'error' }
|
|
312
|
+
: { output: i.tool_response }),
|
|
313
|
+
callId: str(i.tool_use_id) ?? str(toolUseID),
|
|
314
|
+
...(durationMs !== undefined ? { durationMs, startedAt: Date.now() - durationMs } : {}),
|
|
315
|
+
...(Object.keys(meta).length > 0 ? { meta } : {}),
|
|
316
|
+
source: 'claude-agent-sdk',
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
// never into the harness
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
const base = { ...(existing ?? {}) };
|
|
324
|
+
const add = (event, m) => [...(base[event] ?? []), m];
|
|
325
|
+
return {
|
|
326
|
+
...base,
|
|
327
|
+
PreToolUse: add('PreToolUse', {
|
|
328
|
+
matcher: SHARDFLUX_MCP,
|
|
329
|
+
hooks: [
|
|
330
|
+
async () => {
|
|
331
|
+
await core.settle();
|
|
332
|
+
return {};
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
}),
|
|
336
|
+
PostToolUse: add('PostToolUse', {
|
|
337
|
+
hooks: [
|
|
338
|
+
(input, toolUseID) => {
|
|
339
|
+
record(input, toolUseID, false);
|
|
340
|
+
return Promise.resolve({});
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
}),
|
|
344
|
+
PostToolUseFailure: add('PostToolUseFailure', {
|
|
345
|
+
hooks: [
|
|
346
|
+
(input, toolUseID) => {
|
|
347
|
+
record(input, toolUseID, true);
|
|
348
|
+
return Promise.resolve({});
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
}),
|
|
352
|
+
};
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
const langchain = {
|
|
356
|
+
handler: () => {
|
|
357
|
+
const pending = new Map();
|
|
358
|
+
const take = (runId) => {
|
|
359
|
+
const p = pending.get(runId);
|
|
360
|
+
pending.delete(runId);
|
|
361
|
+
return p;
|
|
362
|
+
};
|
|
363
|
+
return {
|
|
364
|
+
handleToolStart(tool, input, runId, _parentRunId, _tags, _metadata, runName, toolCallId) {
|
|
365
|
+
try {
|
|
366
|
+
const t = obj(tool);
|
|
367
|
+
const ids = Array.isArray(t?.id) ? t.id : [];
|
|
368
|
+
const name = str(runName) ?? str(t?.name) ?? str(ids[ids.length - 1]) ?? 'tool';
|
|
369
|
+
pending.set(runId, { tool: name, input: parseArguments(input), callId: str(toolCallId), startedAt: Date.now() });
|
|
370
|
+
if (pending.size > LANGCHAIN_PENDING)
|
|
371
|
+
pending.delete(pending.keys().next().value);
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
// never into the harness
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
handleToolEnd(output, runId) {
|
|
378
|
+
try {
|
|
379
|
+
const p = take(runId);
|
|
380
|
+
const post = toolMessagePost(output);
|
|
381
|
+
const msg = isToolMessage(output) ? output : null;
|
|
382
|
+
core.hook({
|
|
383
|
+
tool: p?.tool ?? str(msg?.name) ?? 'tool',
|
|
384
|
+
input: p?.input,
|
|
385
|
+
output: post.output,
|
|
386
|
+
...(post.error !== undefined ? { error: post.error } : {}),
|
|
387
|
+
...(post.status ? { status: post.status } : {}),
|
|
388
|
+
callId: p?.callId ?? str(msg?.tool_call_id),
|
|
389
|
+
...(p ? { startedAt: p.startedAt, durationMs: Date.now() - p.startedAt } : {}),
|
|
390
|
+
source: 'langchain',
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
// never into the harness
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
handleToolError(err, runId) {
|
|
398
|
+
try {
|
|
399
|
+
const p = take(runId);
|
|
400
|
+
core.hook({ tool: p?.tool ?? 'tool', input: p?.input, error: err ?? new Error('tool error'), callId: p?.callId ?? null, ...(p ? { startedAt: p.startedAt, durationMs: Date.now() - p.startedAt } : {}), source: 'langchain' });
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
// never into the harness
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
},
|
|
408
|
+
};
|
|
409
|
+
const mcp = {
|
|
410
|
+
instrument: (client, opts = {}) => {
|
|
411
|
+
const target = client;
|
|
412
|
+
const hadOwn = Object.prototype.hasOwnProperty.call(target, 'callTool');
|
|
413
|
+
const ownBefore = target.callTool;
|
|
414
|
+
const original = client.callTool;
|
|
415
|
+
const meta = opts.server ? { server: opts.server } : undefined;
|
|
416
|
+
const patched = function (...args) {
|
|
417
|
+
const params = obj(args[0]);
|
|
418
|
+
return core.observe({ tool: str(params?.name) ?? 'tool', source: 'mcp', input: params?.arguments ?? {}, callId: null, hookLevel: true, post: mcpPost, ...(meta ? { meta } : {}) }, original, this, args);
|
|
419
|
+
};
|
|
420
|
+
target.callTool = patched;
|
|
421
|
+
return () => {
|
|
422
|
+
if (target.callTool !== patched)
|
|
423
|
+
return; // patched again by someone else: leave theirs
|
|
424
|
+
if (hadOwn)
|
|
425
|
+
target.callTool = ownBefore;
|
|
426
|
+
else
|
|
427
|
+
delete target.callTool;
|
|
428
|
+
};
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
return { wrapAny: (tools) => wrapCollection(core, tools), aiSdk, mastra, anthropic, openaiAgents, claude, langchain, mcp };
|
|
432
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/** One file to write, `path` relative to the run directory. */
|
|
2
|
+
export interface StoredFile {
|
|
3
|
+
path: string;
|
|
4
|
+
data: Uint8Array;
|
|
5
|
+
contentType: string;
|
|
6
|
+
sha256: string;
|
|
7
|
+
}
|
|
8
|
+
/** An entry of the index line's `parts` (multi-part outputs). */
|
|
9
|
+
export interface CapturePart {
|
|
10
|
+
path: string;
|
|
11
|
+
content_type: string;
|
|
12
|
+
bytes: number;
|
|
13
|
+
sha256: string;
|
|
14
|
+
}
|
|
15
|
+
export type DropReason = 'queue_full' | 'too_large' | 'write_failed' | 'serialize_failed';
|
|
16
|
+
/** What an output becomes: the files to store (in write order) and the index line's output fields. */
|
|
17
|
+
export interface OutputPlan {
|
|
18
|
+
files: StoredFile[];
|
|
19
|
+
outputPath: string | null;
|
|
20
|
+
contentType: string | null;
|
|
21
|
+
/** Bytes of every stored file. */
|
|
22
|
+
bytes: number;
|
|
23
|
+
sha256: string | null;
|
|
24
|
+
truncated: boolean;
|
|
25
|
+
parts?: CapturePart[];
|
|
26
|
+
dropped?: DropReason;
|
|
27
|
+
/** `stream_not_captured` for a Response, ReadableStream, Node stream or Blob (never read). */
|
|
28
|
+
note?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare const DEFAULT_MAX_OUTPUT_BYTES: number;
|
|
31
|
+
export declare const sha256Hex: (data: Uint8Array) => string;
|
|
32
|
+
/**
|
|
33
|
+
* The tool name as used in file names: every Unicode code point outside `[A-Za-z0-9._-]` becomes `_`, cut to 64
|
|
34
|
+
* characters; an empty result becomes `tool`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function sanitizeToolName(name: string): string;
|
|
37
|
+
/** Sequence numbers in file names: zero-padded to 6 digits (more digits past 999999). */
|
|
38
|
+
export declare const padSeq: (seq: number) => string;
|
|
39
|
+
/** Raw bytes as the capture stores them: Uint8Array (and Buffer), ArrayBuffer, DataView. Other typed arrays are JSON. */
|
|
40
|
+
export declare function asBytes(value: unknown): Uint8Array | null;
|
|
41
|
+
/** Extension and content type from magic bytes; `.bin` `application/octet-stream` when none matches. */
|
|
42
|
+
export declare function sniff(bytes: Uint8Array): {
|
|
43
|
+
ext: string;
|
|
44
|
+
contentType: string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Values capture never reads (reading would consume them): Response, ReadableStream, Node streams, Blob, generator
|
|
48
|
+
* objects and other async iterables (a wrapped tool's async generator is teed instead; see capture.ts).
|
|
49
|
+
*/
|
|
50
|
+
export declare function isUnreadable(value: unknown): boolean;
|
|
51
|
+
/** Parses a JSON arguments string (OpenAI); anything else is kept as it is. An empty string is `{}`. */
|
|
52
|
+
export declare function parseArguments(v: unknown): unknown;
|
|
53
|
+
/** An error as the index line and outputs carry it: `{type, message}`. */
|
|
54
|
+
export declare function errorInfo(err: unknown): {
|
|
55
|
+
type: string;
|
|
56
|
+
message: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Converts any value to plain JSON data (objects, arrays, strings, finite numbers, booleans, null): `toJSON`, Date →
|
|
60
|
+
* ISO string, Map → object, Set → array, BigInt → string, NaN/Infinity → null, errors → `{type, message}`, nested bytes
|
|
61
|
+
* → `{"$base64"}`, cycles → `"[Circular]"`, unconvertible → `{"$type","$repr"}`. Functions and symbols inside objects
|
|
62
|
+
* are left out and inside arrays become null, as in JSON.stringify. Never throws. `undefined` at the top stays
|
|
63
|
+
* undefined.
|
|
64
|
+
*/
|
|
65
|
+
export declare function toJsonSafe(value: unknown): unknown;
|
|
66
|
+
/** Compact JSON of any value (via toJsonSafe); never throws. Undefined becomes `null`. */
|
|
67
|
+
export declare function safeStringify(value: unknown): string;
|
|
68
|
+
/** A JSON object or array in strict JSON (no NaN/Infinity), surrounded only by JSON whitespace. */
|
|
69
|
+
export declare function isJsonContainerText(s: string): boolean;
|
|
70
|
+
/** The longest prefix of at most `max` bytes that ends on a UTF-8 character boundary. */
|
|
71
|
+
export declare function cutUtf8(bytes: Uint8Array, max: number): Uint8Array;
|
|
72
|
+
export declare function dropped(reason: DropReason, note?: string): OutputPlan;
|
|
73
|
+
type Json = null | boolean | number | string | Json[] | {
|
|
74
|
+
[k: string]: Json;
|
|
75
|
+
};
|
|
76
|
+
/** An MCP-like result: `content` a non-empty array of objects with a string `type`, other keys from the MCP set. */
|
|
77
|
+
export declare function isMcpLike(v: unknown): v is {
|
|
78
|
+
content: Array<Record<string, Json>>;
|
|
79
|
+
} & Record<string, Json>;
|
|
80
|
+
/** A content-block array: non-empty, every item an object whose `type` is a known block type. */
|
|
81
|
+
export declare function isBlockArray(v: unknown): v is Array<Record<string, Json>>;
|
|
82
|
+
/**
|
|
83
|
+
* Plans how `value` is stored. `base` is `<seq>-<tool>` (zero-padded seq, sanitized tool name). Never throws.
|
|
84
|
+
*/
|
|
85
|
+
export declare function planOutput(value: unknown, base: string, maxOutputBytes?: number): OutputPlan;
|
|
86
|
+
/** Items of an async iterable as `.jsonl` (rule 7): one compact JSON line per item, cut at `max` bytes. */
|
|
87
|
+
export declare class JsonlCollector {
|
|
88
|
+
#private;
|
|
89
|
+
truncated: boolean;
|
|
90
|
+
count: number;
|
|
91
|
+
constructor(max: number);
|
|
92
|
+
push(item: unknown): void;
|
|
93
|
+
/** The items collected so far, parsed back (a cut last line is left out). */
|
|
94
|
+
values(): unknown[];
|
|
95
|
+
plan(base: string): OutputPlan;
|
|
96
|
+
}
|
|
97
|
+
export {};
|