@reminix/runtime 0.0.13 → 0.0.16
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/README.md +65 -166
- package/dist/agent-adapter.d.ts +3 -7
- package/dist/agent-adapter.d.ts.map +1 -1
- package/dist/agent-adapter.js +24 -11
- package/dist/agent-adapter.js.map +1 -1
- package/dist/agent.d.ts +50 -105
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +179 -328
- package/dist/agent.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +70 -23
- package/dist/server.js.map +1 -1
- package/dist/tool.d.ts +28 -22
- package/dist/tool.d.ts.map +1 -1
- package/dist/tool.js +25 -19
- package/dist/tool.js.map +1 -1
- package/dist/types.d.ts +41 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -2,17 +2,125 @@
|
|
|
2
2
|
* Agent classes for Reminix Runtime.
|
|
3
3
|
*/
|
|
4
4
|
import { VERSION } from './version.js';
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
/** Default template when none specified and no custom input/output. */
|
|
6
|
+
const DEFAULT_AGENT_TEMPLATE = 'prompt';
|
|
7
|
+
/** JSON schema for a single tool call (OpenAI-style). */
|
|
8
|
+
const TOOL_CALL_ITEM_SCHEMA = {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
id: { type: 'string', description: 'Tool call id' },
|
|
12
|
+
type: { type: 'string', enum: ['function'], description: 'Tool call type' },
|
|
13
|
+
function: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
name: { type: 'string', description: 'Function/tool name' },
|
|
17
|
+
arguments: { type: 'string', description: 'JSON string of arguments' },
|
|
18
|
+
},
|
|
19
|
+
required: ['name', 'arguments'],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
required: ['id', 'type', 'function'],
|
|
23
|
+
};
|
|
24
|
+
/** JSON schema for a message item (OpenAI-style; supports tool_calls and tool results). */
|
|
25
|
+
const CHAT_INPUT_MESSAGE_ITEMS = {
|
|
10
26
|
type: 'object',
|
|
11
27
|
properties: {
|
|
12
|
-
|
|
28
|
+
role: { type: 'string', description: 'Message role (user, assistant, system, tool)' },
|
|
29
|
+
content: { type: 'string', description: 'Message content', nullable: true },
|
|
30
|
+
tool_calls: {
|
|
31
|
+
type: 'array',
|
|
32
|
+
description: 'Tool calls requested by the model (assistant messages)',
|
|
33
|
+
items: TOOL_CALL_ITEM_SCHEMA,
|
|
34
|
+
},
|
|
35
|
+
tool_call_id: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'Id of the tool call this message is a result for (tool messages)',
|
|
38
|
+
},
|
|
39
|
+
name: { type: 'string', description: 'Tool name (tool messages)' },
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
const AGENT_TEMPLATES = {
|
|
43
|
+
prompt: {
|
|
44
|
+
input: {
|
|
45
|
+
type: 'object',
|
|
46
|
+
properties: {
|
|
47
|
+
prompt: { type: 'string', description: 'The prompt or task for the agent' },
|
|
48
|
+
},
|
|
49
|
+
required: ['prompt'],
|
|
50
|
+
},
|
|
51
|
+
output: { type: 'string' },
|
|
52
|
+
},
|
|
53
|
+
chat: {
|
|
54
|
+
input: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
messages: {
|
|
58
|
+
type: 'array',
|
|
59
|
+
description: 'Chat messages (OpenAI-style)',
|
|
60
|
+
items: CHAT_INPUT_MESSAGE_ITEMS,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
required: ['messages'],
|
|
64
|
+
},
|
|
65
|
+
output: { type: 'string' },
|
|
66
|
+
},
|
|
67
|
+
task: {
|
|
68
|
+
input: {
|
|
69
|
+
type: 'object',
|
|
70
|
+
properties: {
|
|
71
|
+
task: { type: 'string', description: 'Task name or description' },
|
|
72
|
+
},
|
|
73
|
+
required: ['task'],
|
|
74
|
+
additionalProperties: true,
|
|
75
|
+
},
|
|
76
|
+
output: {
|
|
77
|
+
description: 'Structured JSON result (object, array, string, number, boolean, or null)',
|
|
78
|
+
type: 'object',
|
|
79
|
+
additionalProperties: true,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
rag: {
|
|
83
|
+
input: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
query: { type: 'string', description: 'The question to answer from documents' },
|
|
87
|
+
messages: {
|
|
88
|
+
type: 'array',
|
|
89
|
+
description: 'Optional prior conversation (chat-style RAG)',
|
|
90
|
+
items: CHAT_INPUT_MESSAGE_ITEMS,
|
|
91
|
+
},
|
|
92
|
+
collectionIds: {
|
|
93
|
+
type: 'array',
|
|
94
|
+
items: { type: 'string' },
|
|
95
|
+
description: 'Optional knowledge collection IDs to scope the search',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
required: ['query'],
|
|
99
|
+
},
|
|
100
|
+
output: { type: 'string' },
|
|
101
|
+
},
|
|
102
|
+
thread: {
|
|
103
|
+
input: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {
|
|
106
|
+
messages: {
|
|
107
|
+
type: 'array',
|
|
108
|
+
description: 'Chat messages with tool_calls and tool results (OpenAI-style)',
|
|
109
|
+
items: CHAT_INPUT_MESSAGE_ITEMS,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
required: ['messages'],
|
|
113
|
+
},
|
|
114
|
+
output: {
|
|
115
|
+
type: 'array',
|
|
116
|
+
description: 'Updated message thread (OpenAI-style, may include assistant message and tool_calls)',
|
|
117
|
+
items: CHAT_INPUT_MESSAGE_ITEMS,
|
|
118
|
+
},
|
|
13
119
|
},
|
|
14
|
-
required: ['prompt'],
|
|
15
120
|
};
|
|
121
|
+
/** Default input/output schemas (same as prompt template). Used by AgentBase and custom agents. */
|
|
122
|
+
const DEFAULT_AGENT_INPUT = AGENT_TEMPLATES[DEFAULT_AGENT_TEMPLATE].input;
|
|
123
|
+
const DEFAULT_AGENT_OUTPUT = AGENT_TEMPLATES[DEFAULT_AGENT_TEMPLATE].output;
|
|
16
124
|
/**
|
|
17
125
|
* Abstract base class defining the agent interface.
|
|
18
126
|
*
|
|
@@ -21,29 +129,22 @@ const DEFAULT_AGENT_PARAMETERS = {
|
|
|
21
129
|
* `AgentAdapter` for framework adapters.
|
|
22
130
|
*/
|
|
23
131
|
export class AgentBase {
|
|
24
|
-
/**
|
|
25
|
-
* Whether execute supports streaming. Override to enable.
|
|
26
|
-
*/
|
|
27
|
-
get streaming() {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
132
|
/**
|
|
31
133
|
* Return agent metadata for discovery.
|
|
32
134
|
* Override this to provide custom metadata.
|
|
33
135
|
*/
|
|
34
136
|
get metadata() {
|
|
35
137
|
return {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
responseKeys: ['content'],
|
|
138
|
+
capabilities: { streaming: false },
|
|
139
|
+
input: DEFAULT_AGENT_INPUT,
|
|
140
|
+
output: DEFAULT_AGENT_OUTPUT,
|
|
40
141
|
};
|
|
41
142
|
}
|
|
42
143
|
/**
|
|
43
|
-
* Handle a streaming
|
|
144
|
+
* Handle a streaming invoke request.
|
|
44
145
|
*/
|
|
45
146
|
// eslint-disable-next-line require-yield
|
|
46
|
-
async *
|
|
147
|
+
async *invokeStream(_request) {
|
|
47
148
|
throw new Error('Streaming not implemented for this agent');
|
|
48
149
|
}
|
|
49
150
|
/**
|
|
@@ -96,7 +197,6 @@ export class AgentBase {
|
|
|
96
197
|
{
|
|
97
198
|
name: this.name,
|
|
98
199
|
...this.metadata,
|
|
99
|
-
streaming: this.streaming,
|
|
100
200
|
},
|
|
101
201
|
],
|
|
102
202
|
}, { headers: corsHeaders });
|
|
@@ -106,38 +206,29 @@ export class AgentBase {
|
|
|
106
206
|
if (method === 'POST' && invokeMatch) {
|
|
107
207
|
const agentName = invokeMatch[1];
|
|
108
208
|
if (agentName !== this.name) {
|
|
109
|
-
return Response.json({ error: `Agent '${agentName}' not found` }, { status: 404, headers: corsHeaders });
|
|
209
|
+
return Response.json({ error: { type: 'NotFoundError', message: `Agent '${agentName}' not found` } }, { status: 404, headers: corsHeaders });
|
|
110
210
|
}
|
|
111
211
|
const body = (await request.json());
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// Extract declared keys from body into input object
|
|
115
|
-
// e.g., requestKeys: ['prompt'] with body { prompt: '...' } -> input = { prompt: '...' }
|
|
116
|
-
const input = {};
|
|
117
|
-
for (const key of requestKeys) {
|
|
118
|
-
if (key in body) {
|
|
119
|
-
input[key] = body[key];
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
const executeRequest = {
|
|
123
|
-
input,
|
|
212
|
+
const invokeRequest = {
|
|
213
|
+
input: body.input ?? {},
|
|
124
214
|
stream: body.stream === true,
|
|
125
215
|
context: body.context,
|
|
126
216
|
};
|
|
127
217
|
// Handle streaming
|
|
128
|
-
if (
|
|
218
|
+
if (invokeRequest.stream) {
|
|
129
219
|
const stream = new ReadableStream({
|
|
130
220
|
start: async (controller) => {
|
|
131
221
|
const encoder = new TextEncoder();
|
|
132
222
|
try {
|
|
133
|
-
for await (const chunk of this.
|
|
134
|
-
controller.enqueue(encoder.encode(`data: ${chunk}\n\n`));
|
|
223
|
+
for await (const chunk of this.invokeStream(invokeRequest)) {
|
|
224
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ delta: chunk })}\n\n`));
|
|
135
225
|
}
|
|
136
|
-
controller.enqueue(encoder.encode(
|
|
226
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ done: true })}\n\n`));
|
|
137
227
|
}
|
|
138
228
|
catch (error) {
|
|
139
229
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
140
|
-
|
|
230
|
+
const errorType = error instanceof Error ? error.constructor.name : 'ExecutionError';
|
|
231
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ error: { type: errorType, message } })}\n\n`));
|
|
141
232
|
}
|
|
142
233
|
controller.close();
|
|
143
234
|
},
|
|
@@ -151,15 +242,16 @@ export class AgentBase {
|
|
|
151
242
|
},
|
|
152
243
|
});
|
|
153
244
|
}
|
|
154
|
-
const response = await this.
|
|
245
|
+
const response = await this.invoke(invokeRequest);
|
|
155
246
|
return Response.json(response, { headers: corsHeaders });
|
|
156
247
|
}
|
|
157
248
|
// Not found
|
|
158
|
-
return Response.json({ error: 'Not found' }, { status: 404, headers: corsHeaders });
|
|
249
|
+
return Response.json({ error: { type: 'NotFoundError', message: 'Not found' } }, { status: 404, headers: corsHeaders });
|
|
159
250
|
}
|
|
160
251
|
catch (error) {
|
|
161
252
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
162
|
-
|
|
253
|
+
const errorType = error instanceof Error ? error.constructor.name : 'ExecutionError';
|
|
254
|
+
return Response.json({ error: { type: errorType, message } }, { status: 500, headers: corsHeaders });
|
|
163
255
|
}
|
|
164
256
|
};
|
|
165
257
|
}
|
|
@@ -182,8 +274,8 @@ export class AgentBase {
|
|
|
182
274
|
export class Agent extends AgentBase {
|
|
183
275
|
_name;
|
|
184
276
|
_metadata;
|
|
185
|
-
|
|
186
|
-
|
|
277
|
+
_invokeHandler = null;
|
|
278
|
+
_invokeStreamHandler = null;
|
|
187
279
|
/**
|
|
188
280
|
* Create a new agent.
|
|
189
281
|
*
|
|
@@ -206,19 +298,15 @@ export class Agent extends AgentBase {
|
|
|
206
298
|
*/
|
|
207
299
|
get metadata() {
|
|
208
300
|
return {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
301
|
+
capabilities: {
|
|
302
|
+
streaming: this._invokeStreamHandler !== null,
|
|
303
|
+
...this._metadata.capabilities,
|
|
304
|
+
},
|
|
305
|
+
input: this._metadata.input ?? DEFAULT_AGENT_INPUT,
|
|
306
|
+
output: this._metadata.output ?? DEFAULT_AGENT_OUTPUT,
|
|
213
307
|
...this._metadata,
|
|
214
308
|
};
|
|
215
309
|
}
|
|
216
|
-
/**
|
|
217
|
-
* Whether execute supports streaming.
|
|
218
|
-
*/
|
|
219
|
-
get streaming() {
|
|
220
|
-
return this._executeStreamHandler !== null;
|
|
221
|
-
}
|
|
222
310
|
/**
|
|
223
311
|
* Register a handler.
|
|
224
312
|
*
|
|
@@ -228,7 +316,7 @@ export class Agent extends AgentBase {
|
|
|
228
316
|
* });
|
|
229
317
|
*/
|
|
230
318
|
handler(fn) {
|
|
231
|
-
this.
|
|
319
|
+
this._invokeHandler = fn;
|
|
232
320
|
return this;
|
|
233
321
|
}
|
|
234
322
|
/**
|
|
@@ -236,31 +324,31 @@ export class Agent extends AgentBase {
|
|
|
236
324
|
*
|
|
237
325
|
* @example
|
|
238
326
|
* agent.streamHandler(async function* (request) {
|
|
239
|
-
* yield '
|
|
240
|
-
* yield '
|
|
327
|
+
* yield 'Hello';
|
|
328
|
+
* yield ' world!';
|
|
241
329
|
* });
|
|
242
330
|
*/
|
|
243
331
|
streamHandler(fn) {
|
|
244
|
-
this.
|
|
332
|
+
this._invokeStreamHandler = fn;
|
|
245
333
|
return this;
|
|
246
334
|
}
|
|
247
335
|
/**
|
|
248
|
-
* Handle an
|
|
336
|
+
* Handle an invoke request.
|
|
249
337
|
*/
|
|
250
|
-
async
|
|
251
|
-
if (this.
|
|
252
|
-
throw new Error(`No
|
|
338
|
+
async invoke(request) {
|
|
339
|
+
if (this._invokeHandler === null) {
|
|
340
|
+
throw new Error(`No invoke handler registered for agent '${this._name}'`);
|
|
253
341
|
}
|
|
254
|
-
return this.
|
|
342
|
+
return this._invokeHandler(request);
|
|
255
343
|
}
|
|
256
344
|
/**
|
|
257
|
-
* Handle a streaming
|
|
345
|
+
* Handle a streaming invoke request.
|
|
258
346
|
*/
|
|
259
|
-
async *
|
|
260
|
-
if (this.
|
|
261
|
-
throw new Error(`No streaming
|
|
347
|
+
async *invokeStream(request) {
|
|
348
|
+
if (this._invokeStreamHandler === null) {
|
|
349
|
+
throw new Error(`No streaming invoke handler registered for agent '${this._name}'`);
|
|
262
350
|
}
|
|
263
|
-
yield* this.
|
|
351
|
+
yield* this._invokeStreamHandler(request);
|
|
264
352
|
}
|
|
265
353
|
}
|
|
266
354
|
/**
|
|
@@ -269,77 +357,38 @@ export class Agent extends AgentBase {
|
|
|
269
357
|
function isAsyncGeneratorFunction(fn) {
|
|
270
358
|
return fn?.constructor?.name === 'AsyncGeneratorFunction';
|
|
271
359
|
}
|
|
272
|
-
/**
|
|
273
|
-
* Wrap output schema to match the full response structure based on responseKeys.
|
|
274
|
-
*
|
|
275
|
-
* If responseKeys = ["output"], wraps the schema as { output: <schema> }
|
|
276
|
-
* If responseKeys = ["message"], wraps the schema as { message: <schema> }
|
|
277
|
-
* If responseKeys = ["message", "output"], wraps as { message: <schema>, output: <schema> }
|
|
278
|
-
*
|
|
279
|
-
* @param outputSchema - The schema for the return value (or undefined)
|
|
280
|
-
* @param responseKeys - List of top-level response keys
|
|
281
|
-
* @returns Wrapped schema describing the full response object, or undefined if outputSchema is undefined
|
|
282
|
-
*/
|
|
283
|
-
function wrapOutputSchemaForResponseKeys(outputSchema, responseKeys) {
|
|
284
|
-
if (outputSchema === undefined || responseKeys.length === 0) {
|
|
285
|
-
return undefined;
|
|
286
|
-
}
|
|
287
|
-
// If single response key, wrap the output schema
|
|
288
|
-
if (responseKeys.length === 1) {
|
|
289
|
-
return {
|
|
290
|
-
type: 'object',
|
|
291
|
-
properties: { [responseKeys[0]]: outputSchema },
|
|
292
|
-
required: responseKeys,
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
// Multiple response keys - need to split the output schema
|
|
296
|
-
// For now, assume the output schema describes the first key's value
|
|
297
|
-
// and other keys are optional/unknown
|
|
298
|
-
const properties = { [responseKeys[0]]: outputSchema };
|
|
299
|
-
const required = [responseKeys[0]];
|
|
300
|
-
// For additional keys, we don't know their schema, so mark as optional
|
|
301
|
-
// Users can override via metadata if they need full schema
|
|
302
|
-
for (const key of responseKeys.slice(1)) {
|
|
303
|
-
properties[key] = { type: 'object' }; // Placeholder - should be overridden
|
|
304
|
-
}
|
|
305
|
-
return {
|
|
306
|
-
type: 'object',
|
|
307
|
-
properties,
|
|
308
|
-
required,
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
360
|
/**
|
|
312
361
|
* Create an agent from a configuration object.
|
|
313
362
|
*
|
|
314
|
-
* By default, agents expect `{ prompt: string }` in the request body and
|
|
315
|
-
* return `{ output:
|
|
363
|
+
* By default, agents expect `{ input: { prompt: string } }` in the request body and
|
|
364
|
+
* return `{ output: string }`. You can customize by providing `input` schema.
|
|
316
365
|
*
|
|
317
366
|
* @example
|
|
318
367
|
* ```typescript
|
|
319
|
-
* // Simple agent with default
|
|
320
|
-
* // Request: { prompt: 'Hello world' }
|
|
368
|
+
* // Simple agent with default input/output
|
|
369
|
+
* // Request: { input: { prompt: 'Hello world' } }
|
|
321
370
|
* // Response: { output: 'You said: Hello world' }
|
|
322
371
|
* const echo = agent('echo', {
|
|
323
372
|
* description: 'Echo the prompt',
|
|
324
373
|
* handler: async ({ prompt }) => `You said: ${prompt}`,
|
|
325
374
|
* });
|
|
326
375
|
*
|
|
327
|
-
* // Agent with custom
|
|
328
|
-
* // Request: { a: 1, b: 2 }
|
|
376
|
+
* // Agent with custom input schema
|
|
377
|
+
* // Request: { input: { a: 1, b: 2 } }
|
|
329
378
|
* // Response: { output: 3 }
|
|
330
379
|
* const calculator = agent('calculator', {
|
|
331
380
|
* description: 'Add two numbers',
|
|
332
|
-
*
|
|
381
|
+
* input: {
|
|
333
382
|
* type: 'object',
|
|
334
383
|
* properties: { a: { type: 'number' }, b: { type: 'number' } },
|
|
335
384
|
* required: ['a', 'b'],
|
|
336
385
|
* },
|
|
386
|
+
* output: { type: 'number' },
|
|
337
387
|
* handler: async ({ a, b }) => (a as number) + (b as number),
|
|
338
388
|
* });
|
|
339
389
|
*
|
|
340
390
|
* // Streaming agent (async generator)
|
|
341
|
-
* // Request: { prompt: 'hello world' }
|
|
342
|
-
* // Response: { output: 'hello world ' } (streamed)
|
|
391
|
+
* // Request: { input: { prompt: 'hello world' }, stream: true }
|
|
343
392
|
* const streamer = agent('streamer', {
|
|
344
393
|
* description: 'Stream text word by word',
|
|
345
394
|
* handler: async function* ({ prompt }) {
|
|
@@ -351,43 +400,25 @@ function wrapOutputSchemaForResponseKeys(outputSchema, responseKeys) {
|
|
|
351
400
|
* ```
|
|
352
401
|
*/
|
|
353
402
|
export function agent(name, options) {
|
|
354
|
-
//
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
const baseMetadata = {
|
|
364
|
-
type: 'agent',
|
|
365
|
-
description: options.description,
|
|
366
|
-
parameters,
|
|
367
|
-
requestKeys,
|
|
368
|
-
responseKeys,
|
|
369
|
-
};
|
|
370
|
-
// If metadata override includes responseKeys, re-wrap output schema
|
|
371
|
-
const finalResponseKeys = options.metadata?.responseKeys ?? responseKeys;
|
|
372
|
-
const finalWrappedOutput = options.metadata?.responseKeys !== undefined
|
|
373
|
-
? wrapOutputSchemaForResponseKeys(options.output, finalResponseKeys)
|
|
374
|
-
: wrappedOutput;
|
|
375
|
-
if (finalWrappedOutput !== undefined) {
|
|
376
|
-
baseMetadata.output = finalWrappedOutput;
|
|
377
|
-
}
|
|
403
|
+
// Default template is 'prompt' when no template and no custom input/output
|
|
404
|
+
const effectiveTemplate = options.template ??
|
|
405
|
+
(options.input === undefined && options.output === undefined
|
|
406
|
+
? DEFAULT_AGENT_TEMPLATE
|
|
407
|
+
: undefined);
|
|
408
|
+
const inputSchema = options.input ??
|
|
409
|
+
(effectiveTemplate ? AGENT_TEMPLATES[effectiveTemplate].input : DEFAULT_AGENT_INPUT);
|
|
410
|
+
const outputSchema = options.output ??
|
|
411
|
+
(effectiveTemplate ? AGENT_TEMPLATES[effectiveTemplate].output : DEFAULT_AGENT_OUTPUT);
|
|
378
412
|
const agentInstance = new Agent(name, {
|
|
379
413
|
metadata: {
|
|
380
|
-
|
|
381
|
-
|
|
414
|
+
description: options.description,
|
|
415
|
+
input: inputSchema,
|
|
416
|
+
output: outputSchema,
|
|
417
|
+
...(effectiveTemplate !== undefined && { template: effectiveTemplate }),
|
|
382
418
|
},
|
|
383
419
|
});
|
|
384
420
|
// Detect if handler is an async generator function
|
|
385
421
|
const isStreaming = isAsyncGeneratorFunction(options.handler);
|
|
386
|
-
// Get the response keys from the agent's metadata (allows custom override)
|
|
387
|
-
const getResponseKeys = () => {
|
|
388
|
-
const keys = agentInstance.metadata.responseKeys;
|
|
389
|
-
return keys && keys.length > 0 ? keys : ['content'];
|
|
390
|
-
};
|
|
391
422
|
if (isStreaming) {
|
|
392
423
|
const streamFn = options.handler;
|
|
393
424
|
// Register streaming handler
|
|
@@ -400,194 +431,14 @@ export function agent(name, options) {
|
|
|
400
431
|
for await (const chunk of streamFn(request.input, request.context)) {
|
|
401
432
|
chunks.push(chunk);
|
|
402
433
|
}
|
|
403
|
-
|
|
404
|
-
// If result is dict, use as-is; otherwise wrap in first responseKey
|
|
405
|
-
if (typeof result === 'object' && result !== null && !Array.isArray(result)) {
|
|
406
|
-
return result;
|
|
407
|
-
}
|
|
408
|
-
const responseKeys = getResponseKeys();
|
|
409
|
-
return { [responseKeys[0]]: result };
|
|
434
|
+
return { output: chunks.join('') };
|
|
410
435
|
});
|
|
411
436
|
}
|
|
412
437
|
else {
|
|
413
438
|
const regularHandler = options.handler;
|
|
414
439
|
agentInstance.handler(async (request) => {
|
|
415
440
|
const result = await regularHandler(request.input, request.context);
|
|
416
|
-
|
|
417
|
-
const responseKeys = getResponseKeys();
|
|
418
|
-
if (typeof result === 'object' &&
|
|
419
|
-
result !== null &&
|
|
420
|
-
!Array.isArray(result) &&
|
|
421
|
-
responseKeys.every((key) => key in result)) {
|
|
422
|
-
return result;
|
|
423
|
-
}
|
|
424
|
-
return { [responseKeys[0]]: result };
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
return agentInstance;
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* Create a chat agent from a configuration object.
|
|
431
|
-
*
|
|
432
|
-
* This is a convenience factory that creates an agent with a standard chat
|
|
433
|
-
* interface (messages in, messages out).
|
|
434
|
-
*
|
|
435
|
-
* Request: `{ messages: [...] }`
|
|
436
|
-
* Response: `{ messages: [{ role: 'assistant', content: '...' }, ...] }`
|
|
437
|
-
*
|
|
438
|
-
* @example
|
|
439
|
-
* ```typescript
|
|
440
|
-
* // Non-streaming chat agent
|
|
441
|
-
* // Request: { messages: [{ role: 'user', content: 'hello' }] }
|
|
442
|
-
* // Response: { messages: [{ role: 'assistant', content: 'You said: hello' }] }
|
|
443
|
-
* const bot = chatAgent('bot', {
|
|
444
|
-
* description: 'A simple chatbot',
|
|
445
|
-
* handler: async (messages) => {
|
|
446
|
-
* const lastMsg = messages.at(-1)?.content ?? '';
|
|
447
|
-
* return [{ role: 'assistant', content: `You said: ${lastMsg}` }];
|
|
448
|
-
* },
|
|
449
|
-
* });
|
|
450
|
-
*
|
|
451
|
-
* // Streaming chat agent (async generator)
|
|
452
|
-
* const streamingBot = chatAgent('streaming-bot', {
|
|
453
|
-
* description: 'A streaming chatbot',
|
|
454
|
-
* handler: async function* (messages) {
|
|
455
|
-
* yield 'Hello';
|
|
456
|
-
* yield ' ';
|
|
457
|
-
* yield 'world!';
|
|
458
|
-
* },
|
|
459
|
-
* });
|
|
460
|
-
* ```
|
|
461
|
-
*/
|
|
462
|
-
export function chatAgent(name, options) {
|
|
463
|
-
// Chat agents have default request/response keys (can be overridden via metadata)
|
|
464
|
-
const requestKeys = ['messages'];
|
|
465
|
-
const responseKeys = ['messages'];
|
|
466
|
-
// Message item schema (shared between parameters and output)
|
|
467
|
-
const messageItemSchema = {
|
|
468
|
-
type: 'object',
|
|
469
|
-
properties: {
|
|
470
|
-
role: {
|
|
471
|
-
type: 'string',
|
|
472
|
-
enum: ['system', 'user', 'assistant', 'tool'],
|
|
473
|
-
},
|
|
474
|
-
content: {
|
|
475
|
-
type: ['string', 'null'],
|
|
476
|
-
},
|
|
477
|
-
name: {
|
|
478
|
-
type: 'string',
|
|
479
|
-
},
|
|
480
|
-
tool_call_id: {
|
|
481
|
-
type: 'string',
|
|
482
|
-
},
|
|
483
|
-
tool_calls: {
|
|
484
|
-
type: 'array',
|
|
485
|
-
items: {
|
|
486
|
-
type: 'object',
|
|
487
|
-
properties: {
|
|
488
|
-
id: { type: 'string' },
|
|
489
|
-
type: { type: 'string', enum: ['function'] },
|
|
490
|
-
function: {
|
|
491
|
-
type: 'object',
|
|
492
|
-
properties: {
|
|
493
|
-
name: { type: 'string' },
|
|
494
|
-
arguments: { type: 'string' },
|
|
495
|
-
},
|
|
496
|
-
required: ['name', 'arguments'],
|
|
497
|
-
},
|
|
498
|
-
},
|
|
499
|
-
required: ['id', 'type', 'function'],
|
|
500
|
-
},
|
|
501
|
-
},
|
|
502
|
-
},
|
|
503
|
-
required: ['role'],
|
|
504
|
-
};
|
|
505
|
-
// Define standard chat agent schemas
|
|
506
|
-
const parametersSchema = {
|
|
507
|
-
type: 'object',
|
|
508
|
-
properties: {
|
|
509
|
-
messages: {
|
|
510
|
-
type: 'array',
|
|
511
|
-
items: messageItemSchema,
|
|
512
|
-
},
|
|
513
|
-
},
|
|
514
|
-
required: ['messages'],
|
|
515
|
-
};
|
|
516
|
-
// Messages schema (array of messages, the value, not the full response)
|
|
517
|
-
const messagesSchema = {
|
|
518
|
-
type: 'array',
|
|
519
|
-
items: messageItemSchema,
|
|
520
|
-
};
|
|
521
|
-
// Wrap messages schema to match responseKeys structure
|
|
522
|
-
const wrappedOutput = wrapOutputSchemaForResponseKeys(messagesSchema, responseKeys);
|
|
523
|
-
// Build metadata (allow metadata override to change responseKeys)
|
|
524
|
-
const baseMetadata = {
|
|
525
|
-
type: 'chat_agent',
|
|
526
|
-
description: options.description,
|
|
527
|
-
parameters: parametersSchema,
|
|
528
|
-
requestKeys,
|
|
529
|
-
responseKeys,
|
|
530
|
-
};
|
|
531
|
-
// If metadata override includes responseKeys, re-wrap output schema
|
|
532
|
-
const finalResponseKeys = options.metadata?.responseKeys ?? responseKeys;
|
|
533
|
-
const finalWrappedOutput = options.metadata?.responseKeys !== undefined
|
|
534
|
-
? wrapOutputSchemaForResponseKeys(messagesSchema, finalResponseKeys)
|
|
535
|
-
: wrappedOutput;
|
|
536
|
-
if (finalWrappedOutput !== undefined) {
|
|
537
|
-
baseMetadata.output = finalWrappedOutput;
|
|
538
|
-
}
|
|
539
|
-
const agentInstance = new Agent(name, {
|
|
540
|
-
metadata: {
|
|
541
|
-
...baseMetadata,
|
|
542
|
-
...options.metadata,
|
|
543
|
-
},
|
|
544
|
-
});
|
|
545
|
-
// Detect if handler is an async generator function
|
|
546
|
-
const isStreaming = isAsyncGeneratorFunction(options.handler);
|
|
547
|
-
// Get the response keys from the agent's metadata (allows custom override)
|
|
548
|
-
const getResponseKeys = () => {
|
|
549
|
-
const keys = agentInstance.metadata.responseKeys;
|
|
550
|
-
return keys && keys.length > 0 ? keys : ['messages'];
|
|
551
|
-
};
|
|
552
|
-
if (isStreaming) {
|
|
553
|
-
const streamFn = options.handler;
|
|
554
|
-
// Register streaming handler
|
|
555
|
-
agentInstance.streamHandler(async function* (request) {
|
|
556
|
-
const rawMessages = (request.input.messages ?? []);
|
|
557
|
-
yield* streamFn(rawMessages, request.context);
|
|
558
|
-
});
|
|
559
|
-
// Also register non-streaming handler that collects chunks
|
|
560
|
-
agentInstance.handler(async (request) => {
|
|
561
|
-
const rawMessages = (request.input.messages ?? []);
|
|
562
|
-
const chunks = [];
|
|
563
|
-
for await (const chunk of streamFn(rawMessages, request.context)) {
|
|
564
|
-
chunks.push(chunk);
|
|
565
|
-
}
|
|
566
|
-
const result = [{ role: 'assistant', content: chunks.join('') }];
|
|
567
|
-
// For chat agents, always wrap in first responseKey (typically "messages")
|
|
568
|
-
const responseKeys = getResponseKeys();
|
|
569
|
-
return { [responseKeys[0]]: result };
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
else {
|
|
573
|
-
const regularHandler = options.handler;
|
|
574
|
-
agentInstance.handler(async (request) => {
|
|
575
|
-
const rawMessages = (request.input.messages ?? []);
|
|
576
|
-
const result = await regularHandler(rawMessages, request.context);
|
|
577
|
-
const responseKeys = getResponseKeys();
|
|
578
|
-
// Check if result is already a full response dict with all responseKeys
|
|
579
|
-
// (This handles cases where responseKeys are overridden and function returns a dict)
|
|
580
|
-
if (typeof result === 'object' &&
|
|
581
|
-
result !== null &&
|
|
582
|
-
!Array.isArray(result) &&
|
|
583
|
-
responseKeys.every((key) => key in result)) {
|
|
584
|
-
return result;
|
|
585
|
-
}
|
|
586
|
-
// Convert list of Message objects to list of dicts
|
|
587
|
-
const messagesList = Array.isArray(result)
|
|
588
|
-
? result.map((m) => ({ role: m.role, content: m.content }))
|
|
589
|
-
: [{ role: result.role, content: result.content }];
|
|
590
|
-
return { [responseKeys[0]]: messagesList };
|
|
441
|
+
return { output: result };
|
|
591
442
|
});
|
|
592
443
|
}
|
|
593
444
|
return agentInstance;
|