@meshagent/meshagent 0.41.4 → 0.41.5

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 (62) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/dist/browser/agent-client.d.ts +10 -0
  3. package/dist/browser/agent-client.js +36 -1
  4. package/dist/browser/agent.d.ts +109 -14
  5. package/dist/browser/agent.js +533 -68
  6. package/dist/browser/datasets-client.d.ts +70 -0
  7. package/dist/browser/datasets-client.js +79 -0
  8. package/dist/browser/index.d.ts +2 -0
  9. package/dist/browser/index.js +2 -0
  10. package/dist/browser/oauth-scopes.d.ts +2 -0
  11. package/dist/browser/oauth-scopes.js +21 -0
  12. package/dist/browser/participant-token.d.ts +1 -3
  13. package/dist/browser/participant-token.js +2 -3
  14. package/dist/browser/room-client.d.ts +16 -0
  15. package/dist/browser/room-client.js +69 -0
  16. package/dist/browser/storage-client.d.ts +3 -1
  17. package/dist/browser/storage-client.js +5 -2
  18. package/dist/browser/tool-content-type.d.ts +1 -1
  19. package/dist/browser/tool-content-type.js +3 -2
  20. package/dist/browser/toolkit-config.d.ts +84 -0
  21. package/dist/browser/toolkit-config.js +415 -0
  22. package/dist/esm/agent-client.d.ts +10 -0
  23. package/dist/esm/agent-client.js +36 -1
  24. package/dist/esm/agent.d.ts +109 -14
  25. package/dist/esm/agent.js +533 -68
  26. package/dist/esm/datasets-client.d.ts +70 -0
  27. package/dist/esm/datasets-client.js +79 -0
  28. package/dist/esm/index.d.ts +2 -0
  29. package/dist/esm/index.js +2 -0
  30. package/dist/esm/oauth-scopes.d.ts +2 -0
  31. package/dist/esm/oauth-scopes.js +21 -0
  32. package/dist/esm/participant-token.d.ts +1 -3
  33. package/dist/esm/participant-token.js +2 -3
  34. package/dist/esm/room-client.d.ts +16 -0
  35. package/dist/esm/room-client.js +69 -0
  36. package/dist/esm/storage-client.d.ts +3 -1
  37. package/dist/esm/storage-client.js +5 -2
  38. package/dist/esm/tool-content-type.d.ts +1 -1
  39. package/dist/esm/tool-content-type.js +3 -2
  40. package/dist/esm/toolkit-config.d.ts +84 -0
  41. package/dist/esm/toolkit-config.js +415 -0
  42. package/dist/node/agent-client.d.ts +10 -0
  43. package/dist/node/agent-client.js +36 -1
  44. package/dist/node/agent.d.ts +109 -14
  45. package/dist/node/agent.js +533 -68
  46. package/dist/node/datasets-client.d.ts +70 -0
  47. package/dist/node/datasets-client.js +79 -0
  48. package/dist/node/index.d.ts +2 -0
  49. package/dist/node/index.js +2 -0
  50. package/dist/node/oauth-scopes.d.ts +2 -0
  51. package/dist/node/oauth-scopes.js +21 -0
  52. package/dist/node/participant-token.d.ts +1 -3
  53. package/dist/node/participant-token.js +2 -3
  54. package/dist/node/room-client.d.ts +16 -0
  55. package/dist/node/room-client.js +69 -0
  56. package/dist/node/storage-client.d.ts +3 -1
  57. package/dist/node/storage-client.js +5 -2
  58. package/dist/node/tool-content-type.d.ts +1 -1
  59. package/dist/node/tool-content-type.js +3 -2
  60. package/dist/node/toolkit-config.d.ts +84 -0
  61. package/dist/node/toolkit-config.js +415 -0
  62. package/package.json +1 -1
@@ -1,67 +1,250 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RemoteTaskRunner = exports.HostedToolkit = exports.Toolkit = exports.Tool = void 0;
3
+ exports.RemoteTaskRunner = exports.HostedToolkit = exports.Toolkit = exports.Tool = exports.ContentTool = exports.FunctionTool = exports.BaseTool = exports.ToolStreamOutput = exports.ToolContentOutput = exports.ToolCallOutput = exports.ToolStreamInput = exports.ToolContentInput = exports.ToolInput = exports.RoomToolContext = exports.ToolContext = exports.InvalidToolDataException = void 0;
4
4
  exports.startHostedToolkit = startHostedToolkit;
5
5
  const response_1 = require("./response");
6
6
  const room_server_client_1 = require("./room-server-client");
7
7
  const tool_content_type_1 = require("./tool-content-type");
8
8
  const utils_1 = require("./utils");
9
9
  const room_event_1 = require("./room-event");
10
- class Tool {
11
- constructor({ name, description, title, inputSchema, inputSpec, outputSpec, outputSchema }) {
10
+ const participant_1 = require("./participant");
11
+ const stream_controller_1 = require("./stream-controller");
12
+ class InvalidToolDataException extends room_server_client_1.RoomServerException {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = "InvalidToolDataException";
16
+ }
17
+ }
18
+ exports.InvalidToolDataException = InvalidToolDataException;
19
+ class ToolContext {
20
+ constructor({ caller, onBehalfOf } = {}) {
21
+ this.caller = caller;
22
+ this.onBehalfOf = onBehalfOf;
23
+ }
24
+ }
25
+ exports.ToolContext = ToolContext;
26
+ class RoomToolContext extends ToolContext {
27
+ constructor({ room, caller, onBehalfOf }) {
28
+ super({ caller, onBehalfOf });
29
+ this.room = room;
30
+ }
31
+ }
32
+ exports.RoomToolContext = RoomToolContext;
33
+ class ToolInput {
34
+ }
35
+ exports.ToolInput = ToolInput;
36
+ class ToolContentInput extends ToolInput {
37
+ constructor(content) {
38
+ super();
39
+ this.content = content;
40
+ }
41
+ }
42
+ exports.ToolContentInput = ToolContentInput;
43
+ class ToolStreamInput extends ToolInput {
44
+ constructor(stream) {
45
+ super();
46
+ this.stream = stream;
47
+ }
48
+ }
49
+ exports.ToolStreamInput = ToolStreamInput;
50
+ class ToolCallOutput {
51
+ }
52
+ exports.ToolCallOutput = ToolCallOutput;
53
+ class ToolContentOutput extends ToolCallOutput {
54
+ constructor(content) {
55
+ super();
56
+ this.content = content;
57
+ }
58
+ }
59
+ exports.ToolContentOutput = ToolContentOutput;
60
+ class ToolStreamOutput extends ToolCallOutput {
61
+ constructor(stream, { inputClosed } = {}) {
62
+ super();
63
+ this.stream = stream;
64
+ this.inputClosed = inputClosed;
65
+ }
66
+ }
67
+ exports.ToolStreamOutput = ToolStreamOutput;
68
+ class BaseTool {
69
+ constructor({ name, description, title, inputSchema, inputSpec, outputSpec, outputSchema, defs }) {
12
70
  this.name = name;
13
71
  this.description = description;
14
72
  this.title = title;
15
- if (inputSpec !== undefined && inputSchema !== undefined) {
16
- this.inputSpec = new tool_content_type_1.ToolContentSpec({
17
- types: [...inputSpec.types],
18
- stream: inputSpec.stream,
19
- schema: inputSchema,
20
- });
73
+ this.inputSchema = inputSchema;
74
+ this.inputSpec = inputSpec;
75
+ this.outputSpec = outputSpec;
76
+ this.outputSchema = outputSchema;
77
+ this.defs = defs;
78
+ }
79
+ }
80
+ exports.BaseTool = BaseTool;
81
+ class FunctionTool extends BaseTool {
82
+ async *executeStream(context, arguments_) {
83
+ yield await this.execute(context, arguments_);
84
+ }
85
+ }
86
+ exports.FunctionTool = FunctionTool;
87
+ class ContentTool extends BaseTool {
88
+ }
89
+ exports.ContentTool = ContentTool;
90
+ class Tool extends BaseTool {
91
+ constructor(params) {
92
+ const inputSpec = params.inputSpec !== undefined && params.inputSchema !== undefined
93
+ ? new tool_content_type_1.ToolContentSpec({ types: [...params.inputSpec.types], stream: params.inputSpec.stream, schema: params.inputSchema })
94
+ : params.inputSpec;
95
+ const outputSpec = params.outputSpec !== undefined && params.outputSchema !== undefined
96
+ ? new tool_content_type_1.ToolContentSpec({ types: [...params.outputSpec.types], stream: params.outputSpec.stream, schema: params.outputSchema })
97
+ : params.outputSpec;
98
+ super({
99
+ name: params.name,
100
+ description: params.description,
101
+ title: params.title,
102
+ inputSchema: params.inputSchema,
103
+ inputSpec: inputSpec ?? (params.inputSchema !== undefined
104
+ ? new tool_content_type_1.ToolContentSpec({ types: ["json"], stream: false, schema: params.inputSchema })
105
+ : undefined),
106
+ outputSpec: outputSpec ?? (params.outputSchema !== undefined
107
+ ? new tool_content_type_1.ToolContentSpec({ types: ["json"], stream: false, schema: params.outputSchema })
108
+ : undefined),
109
+ outputSchema: params.outputSchema,
110
+ defs: params.defs,
111
+ });
112
+ }
113
+ }
114
+ exports.Tool = Tool;
115
+ function isRecord(value) {
116
+ return typeof value === "object" && value !== null && !Array.isArray(value);
117
+ }
118
+ function contentType(content) {
119
+ if (content instanceof response_1.BinaryContent)
120
+ return "binary";
121
+ if (content instanceof response_1.JsonContent)
122
+ return "json";
123
+ if (content instanceof response_1.TextContent)
124
+ return "text";
125
+ if (content instanceof response_1.FileContent)
126
+ return "file";
127
+ if (content instanceof response_1.LinkContent)
128
+ return "link";
129
+ if (content instanceof response_1.EmptyContent)
130
+ return "empty";
131
+ return undefined;
132
+ }
133
+ function schemaValue(content) {
134
+ if (content instanceof response_1.BinaryContent)
135
+ return content.headers;
136
+ if (content instanceof response_1.JsonContent)
137
+ return content.json;
138
+ if (content instanceof response_1.TextContent)
139
+ return content.text;
140
+ if (content instanceof response_1.EmptyContent)
141
+ return null;
142
+ if (content instanceof response_1.LinkContent)
143
+ return { name: content.name, url: content.url };
144
+ if (content instanceof response_1.FileContent)
145
+ return { name: content.name, mime_type: content.mimeType, size: content.data.length };
146
+ if (content instanceof response_1.ControlContent)
147
+ return { method: content.method };
148
+ if (content instanceof response_1.ErrorContent)
149
+ return content.code === undefined ? { text: content.text } : { text: content.text, code: content.code };
150
+ const [header] = (0, utils_1.unpackMessage)(content.pack());
151
+ return header;
152
+ }
153
+ function schemaWithDefs(schema, defs) {
154
+ if (schema === undefined)
155
+ return undefined;
156
+ if (defs === undefined)
157
+ return { ...schema };
158
+ const merged = { ...schema };
159
+ const existingDefs = merged["$defs"];
160
+ merged["$defs"] = isRecord(existingDefs) ? { ...defs, ...existingDefs } : { ...defs };
161
+ return merged;
162
+ }
163
+ function validateJsonSchemaValue(schema, value, root = schema) {
164
+ if (typeof schema["$ref"] === "string") {
165
+ const ref = schema["$ref"];
166
+ if (ref.startsWith("#/$defs/")) {
167
+ const name = ref.slice("#/$defs/".length);
168
+ const defs = root["$defs"];
169
+ if (isRecord(defs) && isRecord(defs[name])) {
170
+ return validateJsonSchemaValue(defs[name], value, root);
171
+ }
21
172
  }
22
- else if (inputSpec !== undefined) {
23
- this.inputSpec = inputSpec;
173
+ }
174
+ if (Array.isArray(schema.enum) && !schema.enum.some((item) => JSON.stringify(item) === JSON.stringify(value))) {
175
+ return "value is not one of the allowed enum values";
176
+ }
177
+ if ("const" in schema && JSON.stringify(schema.const) !== JSON.stringify(value)) {
178
+ return "value does not match const";
179
+ }
180
+ const rawType = schema.type;
181
+ const types = Array.isArray(rawType) ? rawType : rawType === undefined ? [] : [rawType];
182
+ const matchesType = (type) => {
183
+ switch (type) {
184
+ case "object": return isRecord(value);
185
+ case "array": return Array.isArray(value);
186
+ case "string": return typeof value === "string";
187
+ case "number": return typeof value === "number" && Number.isFinite(value);
188
+ case "integer": return typeof value === "number" && Number.isInteger(value);
189
+ case "boolean": return typeof value === "boolean";
190
+ case "null": return value === null;
191
+ default: return true;
24
192
  }
25
- else if (inputSchema !== undefined) {
26
- this.inputSpec = new tool_content_type_1.ToolContentSpec({
27
- types: ["json"],
28
- stream: false,
29
- schema: inputSchema,
30
- });
193
+ };
194
+ if (types.length > 0 && !types.some(matchesType)) {
195
+ return `expected type ${types.join(" or ")}`;
196
+ }
197
+ if (isRecord(value)) {
198
+ const required = Array.isArray(schema.required) ? schema.required : [];
199
+ for (const key of required) {
200
+ if (typeof key === "string" && !(key in value)) {
201
+ return `missing required property ${key}`;
202
+ }
31
203
  }
32
- if (outputSpec !== undefined && outputSchema !== undefined) {
33
- this.outputSpec = new tool_content_type_1.ToolContentSpec({
34
- types: [...outputSpec.types],
35
- stream: outputSpec.stream,
36
- schema: outputSchema,
37
- });
204
+ const properties = isRecord(schema.properties) ? schema.properties : {};
205
+ for (const [key, propertySchema] of Object.entries(properties)) {
206
+ if (key in value && isRecord(propertySchema)) {
207
+ const error = validateJsonSchemaValue(propertySchema, value[key], root);
208
+ if (error !== undefined)
209
+ return `${key}: ${error}`;
210
+ }
38
211
  }
39
- else if (outputSpec !== undefined) {
40
- this.outputSpec = outputSpec;
212
+ if (schema.additionalProperties === false) {
213
+ for (const key of Object.keys(value)) {
214
+ if (!(key in properties))
215
+ return `unexpected property ${key}`;
216
+ }
41
217
  }
42
- else if (outputSchema !== undefined) {
43
- this.outputSpec = new tool_content_type_1.ToolContentSpec({
44
- types: ["json"],
45
- stream: false,
46
- schema: outputSchema,
47
- });
218
+ }
219
+ if (Array.isArray(value) && isRecord(schema.items)) {
220
+ for (let index = 0; index < value.length; index += 1) {
221
+ const error = validateJsonSchemaValue(schema.items, value[index], root);
222
+ if (error !== undefined)
223
+ return `${index}: ${error}`;
48
224
  }
49
225
  }
50
- get inputSchema() {
51
- return this.inputSpec?.schema;
226
+ if (typeof value === "string") {
227
+ if (typeof schema.minLength === "number" && value.length < schema.minLength)
228
+ return `string is shorter than ${schema.minLength}`;
229
+ if (typeof schema.maxLength === "number" && value.length > schema.maxLength)
230
+ return `string is longer than ${schema.maxLength}`;
52
231
  }
53
- get outputSchema() {
54
- return this.outputSpec?.schema;
232
+ if (typeof value === "number") {
233
+ if (typeof schema.minimum === "number" && value < schema.minimum)
234
+ return `number is less than ${schema.minimum}`;
235
+ if (typeof schema.maximum === "number" && value > schema.maximum)
236
+ return `number is greater than ${schema.maximum}`;
55
237
  }
238
+ return undefined;
56
239
  }
57
- exports.Tool = Tool;
58
240
  class Toolkit {
59
- constructor({ name, title = name, description = "", tools, rules = [] }) {
241
+ constructor({ name, title = name, description = "", tools, rules = [], validationMode = "full" }) {
60
242
  this.name = name;
61
243
  this.title = title;
62
244
  this.description = description;
63
245
  this.tools = tools;
64
246
  this.rules = rules;
247
+ this.validationMode = validationMode;
65
248
  }
66
249
  getTool(name) {
67
250
  const tool = this.tools.find((t) => t.name === name);
@@ -76,14 +259,117 @@ class Toolkit {
76
259
  json[tool.name] = {
77
260
  description: tool.description,
78
261
  title: tool.title,
79
- input_spec: tool.inputSpec?.toJson(),
80
- output_spec: tool.outputSpec?.toJson(),
262
+ input_spec: this.resolveInputSpec(tool)?.toJson(),
263
+ output_spec: this.resolveOutputSpec(tool)?.toJson(),
264
+ defs: tool.defs,
81
265
  };
82
266
  }
83
267
  return json;
84
268
  }
85
- async execute(name, args) {
86
- return this.getTool(name).execute(args);
269
+ async execute(first, second, third) {
270
+ if (typeof first === "string") {
271
+ const output = await this.executeTool(new ToolContext(), first, new ToolContentInput(new response_1.JsonContent({ json: second })));
272
+ if (output instanceof ToolContentOutput) {
273
+ return output.content;
274
+ }
275
+ throw new Error(`tool ${first} returned streamed output`);
276
+ }
277
+ if (typeof second !== "string" || third === undefined) {
278
+ throw new Error("toolkit execute requires a tool name and input");
279
+ }
280
+ return await this.executeTool(first, second, third);
281
+ }
282
+ async executeTool(context, name, input) {
283
+ const tool = this.getTool(name);
284
+ if (tool instanceof ContentTool) {
285
+ return await tool.execute(context, input);
286
+ }
287
+ if (!(input instanceof ToolContentInput)) {
288
+ throw new Error(`tool ${name} does not accept streamed input`);
289
+ }
290
+ const args = this.decodeFunctionToolArguments(name, input.content);
291
+ if (tool instanceof FunctionTool) {
292
+ return new ToolContentOutput(await tool.execute(context, args));
293
+ }
294
+ if (tool instanceof Tool) {
295
+ return new ToolContentOutput(await tool.execute(args));
296
+ }
297
+ throw new Error(`tool ${name} has unsupported type`);
298
+ }
299
+ decodeFunctionToolArguments(toolName, input) {
300
+ if (input instanceof response_1.EmptyContent)
301
+ return {};
302
+ if (input instanceof response_1.JsonContent) {
303
+ if (!isRecord(input.json)) {
304
+ throw new Error(`tool ${toolName} requires JSON object input`);
305
+ }
306
+ return input.json;
307
+ }
308
+ throw new Error(`tool ${toolName} requires JSON object input`);
309
+ }
310
+ get shouldValidateContentTypes() {
311
+ return this.validationMode === "full" || this.validationMode === "contentTypes";
312
+ }
313
+ get shouldValidateSchema() {
314
+ return this.validationMode === "full";
315
+ }
316
+ resolveInputSpec(tool) {
317
+ if (tool instanceof ContentTool)
318
+ return tool.inputSpec;
319
+ if (tool.inputSpec !== undefined)
320
+ return tool.inputSpec;
321
+ return new tool_content_type_1.ToolContentSpec({ types: ["json"], stream: false, schema: tool.inputSchema ?? { type: "object", additionalProperties: true } });
322
+ }
323
+ resolveOutputSpec(tool) {
324
+ if (tool.outputSpec !== undefined) {
325
+ if (tool.outputSchema !== undefined && tool.outputSpec.schema === undefined && tool.outputSpec.types.includes("json")) {
326
+ return new tool_content_type_1.ToolContentSpec({ types: [...tool.outputSpec.types], stream: tool.outputSpec.stream, schema: tool.outputSchema });
327
+ }
328
+ return tool.outputSpec;
329
+ }
330
+ if (tool.outputSchema === undefined)
331
+ return undefined;
332
+ return new tool_content_type_1.ToolContentSpec({ types: ["json"], stream: false, schema: tool.outputSchema });
333
+ }
334
+ validateStreamMode({ tool, direction, spec, stream }) {
335
+ if (!this.shouldValidateContentTypes || spec === undefined)
336
+ return;
337
+ if (spec.stream !== stream) {
338
+ const expected = spec.stream ? "streamed" : "single-content";
339
+ const actual = stream ? "streamed" : "single-content";
340
+ throw new InvalidToolDataException(`tool ${tool.name} ${direction} is ${actual} but ${direction}_spec requires ${expected} ${direction}`);
341
+ }
342
+ }
343
+ validateContentType({ tool, direction, spec, content }) {
344
+ if (!this.shouldValidateContentTypes || spec === undefined)
345
+ return;
346
+ const type = contentType(content);
347
+ if (type === undefined || !spec.types.includes(type)) {
348
+ const allowed = spec.types.join(", ");
349
+ const actual = type ?? content.constructor.name;
350
+ throw new InvalidToolDataException(`tool ${tool.name} ${direction} content type ${actual} is not allowed by ${direction}_spec (${allowed})`);
351
+ }
352
+ }
353
+ validateSchema({ tool, direction, content, schema }) {
354
+ if (!this.shouldValidateSchema)
355
+ return;
356
+ const resolved = schemaWithDefs(schema, tool.defs);
357
+ if (resolved === undefined)
358
+ return;
359
+ const error = validateJsonSchemaValue(resolved, schemaValue(content), resolved);
360
+ if (error !== undefined) {
361
+ throw new InvalidToolDataException(`tool ${tool.name} ${direction} does not match ${direction}_schema: ${error}`);
362
+ }
363
+ }
364
+ validateInputContent(tool, content) {
365
+ const spec = this.resolveInputSpec(tool);
366
+ this.validateContentType({ tool, direction: "input", spec, content });
367
+ this.validateSchema({ tool, direction: "input", content, schema: spec?.schema });
368
+ }
369
+ validateOutputContent(tool, content) {
370
+ const spec = this.resolveOutputSpec(tool);
371
+ this.validateContentType({ tool, direction: "output", spec, content });
372
+ this.validateSchema({ tool, direction: "output", content, schema: spec?.schema });
87
373
  }
88
374
  }
89
375
  exports.Toolkit = Toolkit;
@@ -100,19 +386,24 @@ exports.HostedToolkit = HostedToolkit;
100
386
  class _RemoteToolkitWrapper {
101
387
  constructor({ toolkit, room }) {
102
388
  this._toolCallHandler = this._toolCall.bind(this);
389
+ this._toolCallRequestChunkHandler = this._toolCallRequestChunk.bind(this);
103
390
  this._roomEventHandler = this._onRoomEvent.bind(this);
104
391
  this._started = false;
105
392
  this._public = false;
106
393
  this._registerTask = null;
394
+ this._requestStreams = new Map();
395
+ this._requestStreamTools = new Map();
396
+ this._pendingRequestChunks = new Map();
107
397
  this.toolkit = toolkit;
108
398
  this.client = room;
109
399
  }
110
400
  async start({ public_: isPublic = false } = {}) {
111
401
  if (this._started) {
112
- throw new room_server_client_1.RoomServerException(`toolkit '${this.toolkit.name}' is already started`);
402
+ throw new room_server_client_1.RoomServerException(`toolkit ${this.toolkit.name} is already started`);
113
403
  }
114
404
  this._public = isPublic;
115
405
  this.client.protocol.addHandler(`room.tool_call.${this.toolkit.name}`, this._toolCallHandler);
406
+ this.client.protocol.addHandler(`room.tool_call_request_chunk.${this.toolkit.name}`, this._toolCallRequestChunkHandler);
116
407
  this.client.on("disconnected", this._roomEventHandler);
117
408
  this.client.on("reconnected", this._roomEventHandler);
118
409
  try {
@@ -123,6 +414,7 @@ class _RemoteToolkitWrapper {
123
414
  this.client.off("disconnected", this._roomEventHandler);
124
415
  this.client.off("reconnected", this._roomEventHandler);
125
416
  this.client.protocol.removeHandler(`room.tool_call.${this.toolkit.name}`, this._toolCallHandler);
417
+ this.client.protocol.removeHandler(`room.tool_call_request_chunk.${this.toolkit.name}`, this._toolCallRequestChunkHandler);
126
418
  throw error;
127
419
  }
128
420
  }
@@ -133,11 +425,13 @@ class _RemoteToolkitWrapper {
133
425
  this._started = false;
134
426
  this.client.off("disconnected", this._roomEventHandler);
135
427
  this.client.off("reconnected", this._roomEventHandler);
428
+ this._failActiveRequestStreams(new room_server_client_1.RoomServerException("hosted toolkit stopped"));
136
429
  try {
137
430
  await this._unregister();
138
431
  }
139
432
  finally {
140
433
  this.client.protocol.removeHandler(`room.tool_call.${this.toolkit.name}`, this._toolCallHandler);
434
+ this.client.protocol.removeHandler(`room.tool_call_request_chunk.${this.toolkit.name}`, this._toolCallRequestChunkHandler);
141
435
  }
142
436
  }
143
437
  async _register(public_) {
@@ -148,8 +442,7 @@ class _RemoteToolkitWrapper {
148
442
  tools: this.toolkit.getTools(),
149
443
  public: public_,
150
444
  });
151
- const json = response.json;
152
- this._registrationId = json["id"];
445
+ this._registrationId = response.json["id"];
153
446
  }
154
447
  async _unregister() {
155
448
  const registrationId = this._registrationId;
@@ -161,6 +454,23 @@ class _RemoteToolkitWrapper {
161
454
  id: registrationId,
162
455
  });
163
456
  }
457
+ _failActiveRequestStreams(error) {
458
+ const streams = [...this._requestStreams.values()];
459
+ this._requestStreams.clear();
460
+ this._requestStreamTools.clear();
461
+ this._pendingRequestChunks.clear();
462
+ for (const stream of streams) {
463
+ stream.add({ error });
464
+ stream.close();
465
+ }
466
+ }
467
+ _closeRequestStream(toolCallId) {
468
+ this._pendingRequestChunks.delete(toolCallId);
469
+ this._requestStreamTools.delete(toolCallId);
470
+ const stream = this._requestStreams.get(toolCallId);
471
+ this._requestStreams.delete(toolCallId);
472
+ stream?.close();
473
+ }
164
474
  _scheduleRegisterIfNeeded() {
165
475
  if (!this._started || this._registrationId != null || this._registerTask != null || this.client.isClosed) {
166
476
  return;
@@ -179,46 +489,201 @@ class _RemoteToolkitWrapper {
179
489
  }
180
490
  if (event.status === "disconnected") {
181
491
  this._registrationId = undefined;
492
+ const message = event.message?.trim();
493
+ this._failActiveRequestStreams(new room_server_client_1.RoomServerException(message == null || message.length === 0
494
+ ? "room connection lost before streamed tool call request completed"
495
+ : `room connection lost before streamed tool call request completed: ${message}`));
182
496
  return;
183
497
  }
184
498
  if (event.status === "reconnected") {
185
499
  this._scheduleRegisterIfNeeded();
186
500
  }
187
501
  }
188
- async _toolCall(protocol, messageId, type, data) {
502
+ _resolveParticipant(participantId) {
503
+ if (typeof participantId !== "string" || participantId.length === 0)
504
+ return undefined;
505
+ const local = this.client.localParticipant;
506
+ if (local != null && local.id === participantId)
507
+ return local;
508
+ for (const remote of this.client.messaging.remoteParticipants) {
509
+ if (remote.id === participantId)
510
+ return remote;
511
+ }
512
+ return new participant_1.RemoteParticipant(this.client, participantId, "user");
513
+ }
514
+ _contentFromToolCallArguments(rawArguments, payload) {
515
+ if (!isRecord(rawArguments)) {
516
+ throw new Error("arguments must be a content header object");
517
+ }
518
+ if (typeof rawArguments.type === "string") {
519
+ return (0, response_1.unpackContent)((0, utils_1.packMessage)(rawArguments, payload.length > 0 ? payload : undefined));
520
+ }
521
+ return new response_1.JsonContent({ json: rawArguments });
522
+ }
523
+ async _sendToolCallResponse(messageId, chunk) {
524
+ try {
525
+ await this.client.protocol.send("room.tool_call_response", chunk.pack(), { id: messageId });
526
+ return true;
527
+ }
528
+ catch (error) {
529
+ console.debug("unable to send tool call response", error);
530
+ return false;
531
+ }
532
+ }
533
+ async _sendToolCallResponseChunk(messageId, toolCallId, chunk) {
534
+ const [header, payload] = (0, utils_1.unpackMessage)(chunk.pack());
535
+ try {
536
+ await this.client.protocol.send("room.tool_call_response_chunk", (0, utils_1.packMessage)({ tool_call_id: toolCallId, chunk: header }, payload.length > 0 ? payload : undefined), { id: messageId });
537
+ return true;
538
+ }
539
+ catch (error) {
540
+ console.debug("unable to send tool call response chunk", error);
541
+ return false;
542
+ }
543
+ }
544
+ _streamFromController(controller) {
545
+ return {
546
+ async *[Symbol.asyncIterator]() {
547
+ for await (const item of controller.stream) {
548
+ if ("error" in item)
549
+ throw item.error;
550
+ yield item.content;
551
+ }
552
+ },
553
+ };
554
+ }
555
+ _enqueueRequestStreamChunk(stream, chunk, tool) {
556
+ if (chunk instanceof response_1.ControlContent) {
557
+ if (chunk.method === "open")
558
+ return;
559
+ if (chunk.method === "close") {
560
+ stream.close();
561
+ return;
562
+ }
563
+ return;
564
+ }
565
+ if (tool !== undefined) {
566
+ try {
567
+ this.toolkit.validateInputContent(tool, chunk);
568
+ }
569
+ catch (error) {
570
+ stream.add({ error });
571
+ stream.close();
572
+ return;
573
+ }
574
+ }
575
+ stream.add({ content: chunk });
576
+ }
577
+ async _toolCall(protocol, messageId, _type, data) {
189
578
  if (!this.client.isActiveProtocol(protocol)) {
190
579
  return;
191
580
  }
581
+ const toolCallIdFallback = `${messageId}`;
582
+ let toolCallId = toolCallIdFallback;
583
+ let openedResponseStream = false;
192
584
  try {
193
- const [message, _] = (0, utils_1.unpackMessage)(data);
585
+ const [message, payload] = (0, utils_1.unpackMessage)(data);
194
586
  const toolName = message["name"];
195
- const rawArguments = message["arguments"];
196
- let args;
197
- if (rawArguments &&
198
- typeof rawArguments === "object" &&
199
- !Array.isArray(rawArguments) &&
200
- "type" in rawArguments) {
201
- const content = rawArguments;
202
- const contentType = content["type"];
203
- if (contentType === "json") {
204
- args = content["json"] ?? {};
205
- }
206
- else if (contentType === "empty") {
207
- args = {};
208
- }
209
- else {
210
- throw new Error(`tool '${toolName}' requires JSON object input, received content type '${String(contentType)}'`);
587
+ if (typeof toolName !== "string" || toolName.length === 0) {
588
+ throw new Error("tool call requires a tool name");
589
+ }
590
+ toolCallId = typeof message["tool_call_id"] === "string" && message["tool_call_id"].length > 0
591
+ ? message["tool_call_id"]
592
+ : toolCallIdFallback;
593
+ const inputChunk = this._contentFromToolCallArguments(message["arguments"], payload);
594
+ const requestStream = inputChunk instanceof response_1.ControlContent && inputChunk.method === "open";
595
+ if (inputChunk instanceof response_1.ControlContent && !requestStream) {
596
+ await this._sendToolCallResponse(messageId, new response_1.ErrorContent({ text: "request stream must start with an open control chunk" }));
597
+ return;
598
+ }
599
+ const tool = this.toolkit.getTool(toolName);
600
+ this.toolkit.validateStreamMode({ tool, direction: "input", spec: this.toolkit.resolveInputSpec(tool), stream: requestStream });
601
+ let resolvedInput;
602
+ if (requestStream) {
603
+ const controller = new stream_controller_1.StreamController();
604
+ this._requestStreams.set(toolCallId, controller);
605
+ this._requestStreamTools.set(toolCallId, tool);
606
+ this._enqueueRequestStreamChunk(controller, new response_1.ControlContent({ method: "open" }), tool);
607
+ const buffered = this._pendingRequestChunks.get(toolCallId) ?? [];
608
+ this._pendingRequestChunks.delete(toolCallId);
609
+ for (const chunk of buffered) {
610
+ this._enqueueRequestStreamChunk(controller, chunk, tool);
211
611
  }
612
+ resolvedInput = new ToolStreamInput(this._streamFromController(controller));
212
613
  }
213
614
  else {
214
- args = rawArguments ?? {};
615
+ this.toolkit.validateInputContent(tool, inputChunk);
616
+ resolvedInput = new ToolContentInput(inputChunk);
617
+ }
618
+ const context = new RoomToolContext({
619
+ room: this.client,
620
+ caller: this._resolveParticipant(message["caller_id"]),
621
+ onBehalfOf: this._resolveParticipant(message["on_behalf_of_id"]),
622
+ });
623
+ const output = await this.toolkit.execute(context, toolName, resolvedInput);
624
+ if (output instanceof ToolContentOutput) {
625
+ this.toolkit.validateStreamMode({ tool, direction: "output", spec: this.toolkit.resolveOutputSpec(tool), stream: false });
626
+ this.toolkit.validateOutputContent(tool, output.content);
627
+ await this._sendToolCallResponse(messageId, output.content);
628
+ return;
629
+ }
630
+ if (output instanceof ToolStreamOutput) {
631
+ this.toolkit.validateStreamMode({ tool, direction: "output", spec: this.toolkit.resolveOutputSpec(tool), stream: true });
632
+ openedResponseStream = true;
633
+ if (!await this._sendToolCallResponse(messageId, new response_1.ControlContent({ method: "open" })))
634
+ return;
635
+ for await (const chunk of output.stream) {
636
+ this.toolkit.validateOutputContent(tool, chunk);
637
+ if (!await this._sendToolCallResponseChunk(messageId, toolCallId, chunk))
638
+ return;
639
+ }
640
+ await this._sendToolCallResponseChunk(messageId, toolCallId, new response_1.ControlContent({ method: "close" }));
641
+ return;
642
+ }
643
+ throw new Error(`tool ${toolName} returned unsupported output`);
644
+ }
645
+ catch (error) {
646
+ if (!openedResponseStream) {
647
+ await this._sendToolCallResponse(messageId, new response_1.ErrorContent({ text: String(error) }));
648
+ return;
215
649
  }
216
- const response = await this.toolkit.execute(toolName, args);
217
- await this.client.protocol.send("room.tool_call_response", response.pack(), { id: messageId });
650
+ if (!(error instanceof InvalidToolDataException)) {
651
+ await this._sendToolCallResponseChunk(messageId, toolCallId, new response_1.ErrorContent({ text: String(error) }));
652
+ }
653
+ await this._sendToolCallResponseChunk(messageId, toolCallId, new response_1.ControlContent({
654
+ method: "close",
655
+ statusCode: error instanceof InvalidToolDataException ? response_1.ControlCloseStatus.INVALID_DATA : undefined,
656
+ message: error instanceof InvalidToolDataException ? error.message : undefined,
657
+ }));
658
+ }
659
+ finally {
660
+ this._closeRequestStream(toolCallId);
218
661
  }
219
- catch (e) {
220
- const err = new response_1.ErrorContent({ text: String(e) });
221
- await this.client.protocol.send("room.tool_call_response", err.pack(), { id: messageId });
662
+ }
663
+ async _toolCallRequestChunk(protocol, _messageId, _type, data) {
664
+ if (!this.client.isActiveProtocol(protocol)) {
665
+ return;
666
+ }
667
+ try {
668
+ const [message, payload] = (0, utils_1.unpackMessage)(data);
669
+ const toolCallId = message["tool_call_id"];
670
+ if (typeof toolCallId !== "string" || toolCallId.length === 0)
671
+ return;
672
+ const chunkHeader = message["chunk"];
673
+ if (!isRecord(chunkHeader))
674
+ return;
675
+ const chunk = (0, response_1.unpackContent)((0, utils_1.packMessage)(chunkHeader, payload.length > 0 ? payload : undefined));
676
+ const stream = this._requestStreams.get(toolCallId);
677
+ if (stream === undefined) {
678
+ const buffered = this._pendingRequestChunks.get(toolCallId) ?? [];
679
+ buffered.push(chunk);
680
+ this._pendingRequestChunks.set(toolCallId, buffered);
681
+ return;
682
+ }
683
+ this._enqueueRequestStreamChunk(stream, chunk, this._requestStreamTools.get(toolCallId));
684
+ }
685
+ catch (error) {
686
+ console.warn("ignoring malformed request stream chunk", error);
222
687
  }
223
688
  }
224
689
  }