@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.
- package/CHANGELOG.md +3 -0
- package/dist/browser/agent-client.d.ts +10 -0
- package/dist/browser/agent-client.js +36 -1
- package/dist/browser/agent.d.ts +109 -14
- package/dist/browser/agent.js +533 -68
- package/dist/browser/datasets-client.d.ts +70 -0
- package/dist/browser/datasets-client.js +79 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/oauth-scopes.d.ts +2 -0
- package/dist/browser/oauth-scopes.js +21 -0
- package/dist/browser/participant-token.d.ts +1 -3
- package/dist/browser/participant-token.js +2 -3
- package/dist/browser/room-client.d.ts +16 -0
- package/dist/browser/room-client.js +69 -0
- package/dist/browser/storage-client.d.ts +3 -1
- package/dist/browser/storage-client.js +5 -2
- package/dist/browser/tool-content-type.d.ts +1 -1
- package/dist/browser/tool-content-type.js +3 -2
- package/dist/browser/toolkit-config.d.ts +84 -0
- package/dist/browser/toolkit-config.js +415 -0
- package/dist/esm/agent-client.d.ts +10 -0
- package/dist/esm/agent-client.js +36 -1
- package/dist/esm/agent.d.ts +109 -14
- package/dist/esm/agent.js +533 -68
- package/dist/esm/datasets-client.d.ts +70 -0
- package/dist/esm/datasets-client.js +79 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/oauth-scopes.d.ts +2 -0
- package/dist/esm/oauth-scopes.js +21 -0
- package/dist/esm/participant-token.d.ts +1 -3
- package/dist/esm/participant-token.js +2 -3
- package/dist/esm/room-client.d.ts +16 -0
- package/dist/esm/room-client.js +69 -0
- package/dist/esm/storage-client.d.ts +3 -1
- package/dist/esm/storage-client.js +5 -2
- package/dist/esm/tool-content-type.d.ts +1 -1
- package/dist/esm/tool-content-type.js +3 -2
- package/dist/esm/toolkit-config.d.ts +84 -0
- package/dist/esm/toolkit-config.js +415 -0
- package/dist/node/agent-client.d.ts +10 -0
- package/dist/node/agent-client.js +36 -1
- package/dist/node/agent.d.ts +109 -14
- package/dist/node/agent.js +533 -68
- package/dist/node/datasets-client.d.ts +70 -0
- package/dist/node/datasets-client.js +79 -0
- package/dist/node/index.d.ts +2 -0
- package/dist/node/index.js +2 -0
- package/dist/node/oauth-scopes.d.ts +2 -0
- package/dist/node/oauth-scopes.js +21 -0
- package/dist/node/participant-token.d.ts +1 -3
- package/dist/node/participant-token.js +2 -3
- package/dist/node/room-client.d.ts +16 -0
- package/dist/node/room-client.js +69 -0
- package/dist/node/storage-client.d.ts +3 -1
- package/dist/node/storage-client.js +5 -2
- package/dist/node/tool-content-type.d.ts +1 -1
- package/dist/node/tool-content-type.js +3 -2
- package/dist/node/toolkit-config.d.ts +84 -0
- package/dist/node/toolkit-config.js +415 -0
- package/package.json +1 -1
package/dist/browser/agent.js
CHANGED
|
@@ -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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
|
80
|
-
output_spec: tool
|
|
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(
|
|
86
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
585
|
+
const [message, payload] = (0, utils_1.unpackMessage)(data);
|
|
194
586
|
const toolName = message["name"];
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
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
|
-
|
|
217
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
}
|