@amalgm/agents 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PURPOSE.md +45 -0
- package/README.md +110 -0
- package/dist/agent-store.d.ts +17 -0
- package/dist/agent-store.js +111 -0
- package/dist/agents.d.ts +39 -0
- package/dist/agents.js +129 -0
- package/dist/bin/agents.d.ts +2 -0
- package/dist/bin/agents.js +5 -0
- package/dist/bin/fatal.d.ts +1 -0
- package/dist/bin/fatal.js +5 -0
- package/dist/bin/mcp.d.ts +2 -0
- package/dist/bin/mcp.js +19 -0
- package/dist/bin/rest.d.ts +2 -0
- package/dist/bin/rest.js +21 -0
- package/dist/cli/agent-commands.d.ts +3 -0
- package/dist/cli/agent-commands.js +23 -0
- package/dist/cli/args.d.ts +7 -0
- package/dist/cli/args.js +31 -0
- package/dist/cli/files.d.ts +2 -0
- package/dist/cli/files.js +21 -0
- package/dist/cli/help.d.ts +1 -0
- package/dist/cli/help.js +24 -0
- package/dist/cli/open.d.ts +3 -0
- package/dist/cli/open.js +12 -0
- package/dist/cli/run.d.ts +1 -0
- package/dist/cli/run.js +38 -0
- package/dist/cli/session-commands.d.ts +4 -0
- package/dist/cli/session-commands.js +54 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +2 -0
- package/dist/database.d.ts +9 -0
- package/dist/database.js +35 -0
- package/dist/definition.d.ts +4 -0
- package/dist/definition.js +93 -0
- package/dist/drivers.d.ts +4 -0
- package/dist/drivers.js +25 -0
- package/dist/errors.d.ts +9 -0
- package/dist/errors.js +28 -0
- package/dist/event-store.d.ts +13 -0
- package/dist/event-store.js +50 -0
- package/dist/http/agent-routes.d.ts +2 -0
- package/dist/http/agent-routes.js +40 -0
- package/dist/http/request.d.ts +5 -0
- package/dist/http/request.js +40 -0
- package/dist/http/server.d.ts +2 -0
- package/dist/http/server.js +82 -0
- package/dist/http/session-routes.d.ts +2 -0
- package/dist/http/session-routes.js +67 -0
- package/dist/http/stream.d.ts +3 -0
- package/dist/http/stream.js +30 -0
- package/dist/http-types.d.ts +23 -0
- package/dist/http-types.js +1 -0
- package/dist/http.d.ts +2 -0
- package/dist/http.js +1 -0
- package/dist/ids.d.ts +5 -0
- package/dist/ids.js +32 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/json.d.ts +7 -0
- package/dist/json.js +41 -0
- package/dist/mcp/agent-tools.d.ts +3 -0
- package/dist/mcp/agent-tools.js +51 -0
- package/dist/mcp/helpers.d.ts +5 -0
- package/dist/mcp/helpers.js +27 -0
- package/dist/mcp/server.d.ts +6 -0
- package/dist/mcp/server.js +73 -0
- package/dist/mcp/session-tools.d.ts +3 -0
- package/dist/mcp/session-tools.js +81 -0
- package/dist/mcp/tools.d.ts +3 -0
- package/dist/mcp/tools.js +5 -0
- package/dist/mcp/types.d.ts +18 -0
- package/dist/mcp/types.js +1 -0
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.js +2 -0
- package/dist/messages.d.ts +3 -0
- package/dist/messages.js +56 -0
- package/dist/rows.d.ts +7 -0
- package/dist/rows.js +65 -0
- package/dist/runtime.d.ts +29 -0
- package/dist/runtime.js +176 -0
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +127 -0
- package/dist/session-store.d.ts +15 -0
- package/dist/session-store.js +83 -0
- package/dist/turn-store.d.ts +31 -0
- package/dist/turn-store.js +164 -0
- package/dist/types.d.ts +176 -0
- package/dist/types.js +1 -0
- package/docs/ARCHITECTURE.md +73 -0
- package/docs/CLI.md +61 -0
- package/docs/DATA_MODEL.md +56 -0
- package/docs/DEFINITIONS.md +65 -0
- package/docs/DRIVERS.md +72 -0
- package/docs/ENGINE_INTEGRATION.md +74 -0
- package/docs/MCP.md +24 -0
- package/docs/REST.md +78 -0
- package/docs/SDK.md +72 -0
- package/docs/SECURITY.md +38 -0
- package/examples/basic.ts +31 -0
- package/examples/reviewer.json +25 -0
- package/package.json +64 -0
- package/skills/amalgm-agents/SKILL.md +30 -0
- package/skills/amalgm-agents/agents/openai.yaml +4 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AgentError } from '../errors.js';
|
|
2
|
+
export function streamEvents(agents, sessionId, request, response, after = 0) {
|
|
3
|
+
if (!agents.getSession(sessionId))
|
|
4
|
+
throw new AgentError('not_found', `Session not found: ${sessionId}`);
|
|
5
|
+
response.writeHead(200, {
|
|
6
|
+
'content-type': 'text/event-stream',
|
|
7
|
+
'cache-control': 'no-cache, no-transform',
|
|
8
|
+
connection: 'keep-alive',
|
|
9
|
+
});
|
|
10
|
+
response.flushHeaders();
|
|
11
|
+
let last = after;
|
|
12
|
+
const write = (event) => {
|
|
13
|
+
if (event.sequence <= last || response.destroyed)
|
|
14
|
+
return;
|
|
15
|
+
last = event.sequence;
|
|
16
|
+
response.write(`id: ${event.sequence}\nevent: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`);
|
|
17
|
+
};
|
|
18
|
+
for (const event of agents.listEvents(sessionId, after, 1000))
|
|
19
|
+
write(event);
|
|
20
|
+
const unsubscribe = agents.subscribe(sessionId, write);
|
|
21
|
+
const heartbeat = setInterval(() => {
|
|
22
|
+
if (!response.destroyed)
|
|
23
|
+
response.write(': heartbeat\n\n');
|
|
24
|
+
}, 15_000);
|
|
25
|
+
heartbeat.unref();
|
|
26
|
+
request.once('close', () => {
|
|
27
|
+
clearInterval(heartbeat);
|
|
28
|
+
unsubscribe();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Server } from 'node:http';
|
|
2
|
+
import type { AddressInfo } from 'node:net';
|
|
3
|
+
import type { Agents } from './agents.js';
|
|
4
|
+
import type { AgentsOptions } from './types.js';
|
|
5
|
+
export interface RestServerOptions extends AgentsOptions {
|
|
6
|
+
agents?: Agents;
|
|
7
|
+
token?: string;
|
|
8
|
+
bodyLimitBytes?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface RestServer {
|
|
11
|
+
agents: Agents;
|
|
12
|
+
server: Server;
|
|
13
|
+
listen(port?: number, host?: string): Promise<AddressInfo>;
|
|
14
|
+
close(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export interface RouteContext {
|
|
17
|
+
agents: Agents;
|
|
18
|
+
method: string;
|
|
19
|
+
path: string[];
|
|
20
|
+
url: URL;
|
|
21
|
+
body(): Promise<Record<string, unknown>>;
|
|
22
|
+
json(status: number, value: unknown): void;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/http.d.ts
ADDED
package/dist/http.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createRestServer } from './http/server.js';
|
package/dist/ids.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function newId(prefix: string): string;
|
|
2
|
+
export declare function requireId(value: unknown, field: string): string;
|
|
3
|
+
export declare function optionalId(value: unknown, field: string): string | undefined;
|
|
4
|
+
export declare function randomKey(prefix: string): string;
|
|
5
|
+
export declare function optionalKey(value: unknown): string | undefined;
|
package/dist/ids.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { AgentError } from './errors.js';
|
|
3
|
+
const ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
4
|
+
export function newId(prefix) {
|
|
5
|
+
return `${prefix}-${crypto.randomUUID()}`;
|
|
6
|
+
}
|
|
7
|
+
export function requireId(value, field) {
|
|
8
|
+
const id = typeof value === 'string' ? value.trim() : '';
|
|
9
|
+
if (!ID_PATTERN.test(id)) {
|
|
10
|
+
throw new AgentError('invalid_input', `${field} must be 1-128 lowercase letters, numbers, dots, underscores, or hyphens.`);
|
|
11
|
+
}
|
|
12
|
+
return id;
|
|
13
|
+
}
|
|
14
|
+
export function optionalId(value, field) {
|
|
15
|
+
if (value === undefined || value === null || value === '')
|
|
16
|
+
return undefined;
|
|
17
|
+
return requireId(value, field);
|
|
18
|
+
}
|
|
19
|
+
export function randomKey(prefix) {
|
|
20
|
+
return newId(prefix);
|
|
21
|
+
}
|
|
22
|
+
export function optionalKey(value) {
|
|
23
|
+
if (value === undefined || value === null || value === '')
|
|
24
|
+
return undefined;
|
|
25
|
+
if (typeof value !== 'string')
|
|
26
|
+
throw new AgentError('invalid_input', 'idempotency key must be a string.');
|
|
27
|
+
const key = value.trim();
|
|
28
|
+
if (!key || key.length > 256 || /[\u0000-\u001f\u007f]/.test(key)) {
|
|
29
|
+
throw new AgentError('invalid_input', 'idempotency key must be 1-256 printable characters.');
|
|
30
|
+
}
|
|
31
|
+
return key;
|
|
32
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { Agents } from './agents.js';
|
|
2
|
+
export { AgentError, asAgentError } from './errors.js';
|
|
3
|
+
export { defineDriver, driverSpecifiers, loadDriverModules } from './drivers.js';
|
|
4
|
+
export { definitionHash, normalizeDefinition, patchDefinition } from './definition.js';
|
|
5
|
+
export { messageText, normalizeMessage } from './messages.js';
|
|
6
|
+
export type * from './types.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { Agents } from './agents.js';
|
|
2
|
+
export { AgentError, asAgentError } from './errors.js';
|
|
3
|
+
export { defineDriver, driverSpecifiers, loadDriverModules } from './drivers.js';
|
|
4
|
+
export { definitionHash, normalizeDefinition, patchDefinition } from './definition.js';
|
|
5
|
+
export { messageText, normalizeMessage } from './messages.js';
|
package/dist/json.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { JsonObject, JsonValue } from './types.js';
|
|
2
|
+
export declare function isObject(value: unknown): value is Record<string, unknown>;
|
|
3
|
+
export declare function jsonClone<T extends JsonValue>(value: T): T;
|
|
4
|
+
export declare function parseObject(value: string): JsonObject;
|
|
5
|
+
export declare function canonicalJson(value: JsonValue): string;
|
|
6
|
+
export declare function assertJsonObject(value: unknown, field: string): JsonObject;
|
|
7
|
+
export declare function assertByteLimit(value: JsonValue, limit: number, label: string): void;
|
package/dist/json.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { AgentError } from './errors.js';
|
|
2
|
+
export function isObject(value) {
|
|
3
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
export function jsonClone(value) {
|
|
6
|
+
return JSON.parse(JSON.stringify(value));
|
|
7
|
+
}
|
|
8
|
+
export function parseObject(value) {
|
|
9
|
+
const parsed = JSON.parse(value);
|
|
10
|
+
if (!isObject(parsed))
|
|
11
|
+
throw new AgentError('internal', 'Stored JSON is not an object.');
|
|
12
|
+
return parsed;
|
|
13
|
+
}
|
|
14
|
+
function sortValue(value) {
|
|
15
|
+
if (Array.isArray(value))
|
|
16
|
+
return value.map(sortValue);
|
|
17
|
+
if (!isObject(value))
|
|
18
|
+
return value;
|
|
19
|
+
return Object.fromEntries(Object.entries(value)
|
|
20
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
21
|
+
.map(([key, item]) => [key, sortValue(item)]));
|
|
22
|
+
}
|
|
23
|
+
export function canonicalJson(value) {
|
|
24
|
+
return JSON.stringify(sortValue(value));
|
|
25
|
+
}
|
|
26
|
+
export function assertJsonObject(value, field) {
|
|
27
|
+
if (!isObject(value))
|
|
28
|
+
throw new AgentError('invalid_input', `${field} must be an object.`);
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(JSON.stringify(value));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw new AgentError('invalid_input', `${field} must contain JSON values only.`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function assertByteLimit(value, limit, label) {
|
|
37
|
+
const bytes = Buffer.byteLength(JSON.stringify(value));
|
|
38
|
+
if (bytes > limit) {
|
|
39
|
+
throw new AgentError('too_large', `${label} is ${bytes} bytes; maximum is ${limit}.`, { bytes, limit });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { objectArg, objectSchema, stringArg, toolResult } from './helpers.js';
|
|
2
|
+
const string = { type: 'string' };
|
|
3
|
+
export function agentTools(agents) {
|
|
4
|
+
return [
|
|
5
|
+
{
|
|
6
|
+
name: 'agents_list',
|
|
7
|
+
description: 'List persistent agent definitions. This does not list chats.',
|
|
8
|
+
inputSchema: objectSchema({ include_deleted: { type: 'boolean' } }),
|
|
9
|
+
handler(input) {
|
|
10
|
+
return toolResult({ agents: agents.listAgents(input.include_deleted === true) });
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'agents_get',
|
|
15
|
+
description: 'Read one agent and its current immutable revision.',
|
|
16
|
+
inputSchema: objectSchema({ agent_id: string, include_deleted: { type: 'boolean' } }, ['agent_id']),
|
|
17
|
+
handler(input) {
|
|
18
|
+
const id = stringArg(input, 'agent_id');
|
|
19
|
+
return toolResult({ agent: agents.getAgent(id, input.include_deleted === true) });
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'agents_create',
|
|
24
|
+
description: 'Create an agent from a complete definition.',
|
|
25
|
+
inputSchema: objectSchema({ definition: { type: 'object' } }, ['definition']),
|
|
26
|
+
handler(input) {
|
|
27
|
+
return toolResult({
|
|
28
|
+
agent: agents.createAgent(objectArg(input, 'definition')),
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'agents_update',
|
|
34
|
+
description: 'Patch an agent. Executable changes create an immutable revision.',
|
|
35
|
+
inputSchema: objectSchema({ agent_id: string, patch: { type: 'object' } }, ['agent_id', 'patch']),
|
|
36
|
+
handler(input) {
|
|
37
|
+
return toolResult({
|
|
38
|
+
agent: agents.updateAgent(stringArg(input, 'agent_id'), objectArg(input, 'patch')),
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'agents_delete',
|
|
44
|
+
description: 'Delete an agent while retaining immutable session history.',
|
|
45
|
+
inputSchema: objectSchema({ agent_id: string }, ['agent_id']),
|
|
46
|
+
handler(input) {
|
|
47
|
+
return toolResult({ agent: agents.deleteAgent(stringArg(input, 'agent_id')) });
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { JsonObject } from '../types.js';
|
|
2
|
+
export declare const objectSchema: (properties: JsonObject, required?: string[]) => JsonObject;
|
|
3
|
+
export declare function stringArg(input: Record<string, unknown>, name: string): string;
|
|
4
|
+
export declare function objectArg(input: Record<string, unknown>, name: string): Record<string, unknown>;
|
|
5
|
+
export declare function toolResult(value: unknown): JsonObject;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AgentError } from '../errors.js';
|
|
2
|
+
import { isObject } from '../json.js';
|
|
3
|
+
export const objectSchema = (properties, required = []) => ({
|
|
4
|
+
type: 'object',
|
|
5
|
+
properties,
|
|
6
|
+
additionalProperties: false,
|
|
7
|
+
...(required.length ? { required } : {}),
|
|
8
|
+
});
|
|
9
|
+
export function stringArg(input, name) {
|
|
10
|
+
const value = input[name];
|
|
11
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
12
|
+
throw new AgentError('invalid_input', `${name} is required.`);
|
|
13
|
+
}
|
|
14
|
+
return value.trim();
|
|
15
|
+
}
|
|
16
|
+
export function objectArg(input, name) {
|
|
17
|
+
const value = input[name];
|
|
18
|
+
if (!isObject(value))
|
|
19
|
+
throw new AgentError('invalid_input', `${name} must be an object.`);
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
export function toolResult(value) {
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: 'text', text: JSON.stringify(value, null, 2) }],
|
|
25
|
+
structuredContent: value,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import readline from 'node:readline';
|
|
2
|
+
import { Agents } from '../agents.js';
|
|
3
|
+
import { asAgentError } from '../errors.js';
|
|
4
|
+
import { createMcpTools } from './tools.js';
|
|
5
|
+
function descriptor(tool) {
|
|
6
|
+
const { handler, ...definition } = tool;
|
|
7
|
+
return definition;
|
|
8
|
+
}
|
|
9
|
+
export function createMcpServer(options = {}) {
|
|
10
|
+
const agents = options.agents || new Agents(options);
|
|
11
|
+
const owned = !options.agents;
|
|
12
|
+
const tools = createMcpTools(agents);
|
|
13
|
+
const byName = new Map(tools.map((tool) => [tool.name, tool]));
|
|
14
|
+
async function handle(message) {
|
|
15
|
+
if (message.method === 'initialize') {
|
|
16
|
+
return {
|
|
17
|
+
protocolVersion: message.params?.protocolVersion || '2024-11-05',
|
|
18
|
+
capabilities: { tools: { listChanged: false } },
|
|
19
|
+
serverInfo: { name: 'amalgm-agents', version: '0.1.0' },
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (message.method === 'ping')
|
|
23
|
+
return {};
|
|
24
|
+
if (message.method === 'tools/list')
|
|
25
|
+
return { tools: tools.map(descriptor) };
|
|
26
|
+
if (message.method === 'tools/call') {
|
|
27
|
+
const name = message.params?.name;
|
|
28
|
+
const tool = typeof name === 'string' ? byName.get(name) : undefined;
|
|
29
|
+
if (!tool)
|
|
30
|
+
throw Object.assign(new Error(`Unknown tool: ${String(name)}`), { rpcCode: -32602 });
|
|
31
|
+
const args = message.params?.arguments;
|
|
32
|
+
return tool.handler(args && typeof args === 'object' ? args : {});
|
|
33
|
+
}
|
|
34
|
+
if (message.method?.startsWith('notifications/'))
|
|
35
|
+
return undefined;
|
|
36
|
+
throw Object.assign(new Error(`Method not found: ${message.method}`), { rpcCode: -32601 });
|
|
37
|
+
}
|
|
38
|
+
async function start(input = process.stdin, output = process.stdout) {
|
|
39
|
+
const lines = readline.createInterface({ input, crlfDelay: Infinity });
|
|
40
|
+
lines.on('line', (line) => {
|
|
41
|
+
void respond(line, output);
|
|
42
|
+
});
|
|
43
|
+
return async () => {
|
|
44
|
+
lines.close();
|
|
45
|
+
if (owned)
|
|
46
|
+
await agents.close();
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
async function respond(line, output) {
|
|
50
|
+
let request;
|
|
51
|
+
try {
|
|
52
|
+
request = JSON.parse(line);
|
|
53
|
+
const result = await handle(request);
|
|
54
|
+
if (request.id === undefined || result === undefined)
|
|
55
|
+
return;
|
|
56
|
+
output.write(`${JSON.stringify({ jsonrpc: '2.0', id: request.id, result })}\n`);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (request?.id === undefined)
|
|
60
|
+
return;
|
|
61
|
+
const normalized = asAgentError(error);
|
|
62
|
+
const rpcCode = typeof error === 'object' && error && 'rpcCode' in error
|
|
63
|
+
? Number(error.rpcCode)
|
|
64
|
+
: -32603;
|
|
65
|
+
output.write(`${JSON.stringify({
|
|
66
|
+
jsonrpc: '2.0',
|
|
67
|
+
id: request.id,
|
|
68
|
+
error: { code: rpcCode, message: normalized.message, data: { code: normalized.code } },
|
|
69
|
+
})}\n`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { tools, handle, start };
|
|
73
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { AgentError } from '../errors.js';
|
|
2
|
+
import { objectSchema, toolResult } from './helpers.js';
|
|
3
|
+
const string = { type: 'string' };
|
|
4
|
+
function text(input, ...keys) {
|
|
5
|
+
for (const key of keys) {
|
|
6
|
+
if (typeof input[key] === 'string' && input[key])
|
|
7
|
+
return input[key];
|
|
8
|
+
}
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
function resolveAgentId(agents, requested) {
|
|
12
|
+
const normalized = requested.toLowerCase();
|
|
13
|
+
const matches = agents.listAgents().filter(({ id, definition }) => {
|
|
14
|
+
return id.toLowerCase() === normalized || definition.name.toLowerCase() === normalized;
|
|
15
|
+
});
|
|
16
|
+
if (matches.length === 1)
|
|
17
|
+
return matches[0].id;
|
|
18
|
+
if (matches.length > 1)
|
|
19
|
+
throw new AgentError('conflict', `Agent name is ambiguous: ${requested}`);
|
|
20
|
+
throw new AgentError('not_found', `Agent not found: ${requested}`);
|
|
21
|
+
}
|
|
22
|
+
export function sessionTools(agents) {
|
|
23
|
+
return [
|
|
24
|
+
{
|
|
25
|
+
name: 'agents_get_conversation',
|
|
26
|
+
description: 'Read one durable agent session, including its turns and ordered events.',
|
|
27
|
+
inputSchema: objectSchema({ conversation_id: string, session_id: string, after: { type: 'number' } }),
|
|
28
|
+
handler(input) {
|
|
29
|
+
const id = text(input, 'conversation_id', 'session_id');
|
|
30
|
+
if (!id)
|
|
31
|
+
throw new AgentError('invalid_input', 'conversation_id is required.');
|
|
32
|
+
return toolResult({
|
|
33
|
+
conversation_id: id,
|
|
34
|
+
session: agents.getSession(id),
|
|
35
|
+
turns: agents.listTurns(id),
|
|
36
|
+
events: agents.listEvents(id, Number(input.after || 0)),
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'talk_to_agent',
|
|
42
|
+
description: 'Start or continue a durable agent session. Agents sessions are not Chat conversations.',
|
|
43
|
+
inputSchema: objectSchema({
|
|
44
|
+
agent: string,
|
|
45
|
+
agent_id: string,
|
|
46
|
+
conversation_id: string,
|
|
47
|
+
session_id: string,
|
|
48
|
+
description: string,
|
|
49
|
+
prompt: string,
|
|
50
|
+
message: { type: ['string', 'object'] },
|
|
51
|
+
idempotency_key: string,
|
|
52
|
+
run_in_background: { type: 'boolean' },
|
|
53
|
+
}),
|
|
54
|
+
async handler(input) {
|
|
55
|
+
const requestedAgent = text(input, 'agent', 'agent_id');
|
|
56
|
+
if (!requestedAgent)
|
|
57
|
+
throw new AgentError('invalid_input', 'agent is required.');
|
|
58
|
+
const agentId = resolveAgentId(agents, requestedAgent);
|
|
59
|
+
const message = input.message ?? input.prompt;
|
|
60
|
+
if (message === undefined)
|
|
61
|
+
throw new AgentError('invalid_input', 'prompt or message is required.');
|
|
62
|
+
const sessionId = text(input, 'conversation_id', 'session_id');
|
|
63
|
+
const idempotencyKey = text(input, 'idempotency_key');
|
|
64
|
+
const call = agents.talk(agentId, {
|
|
65
|
+
message: message,
|
|
66
|
+
...(sessionId ? { sessionId } : {}),
|
|
67
|
+
...(idempotencyKey ? { idempotencyKey } : {}),
|
|
68
|
+
});
|
|
69
|
+
const base = {
|
|
70
|
+
conversation_id: call.session.id,
|
|
71
|
+
session: call.session,
|
|
72
|
+
turn: call.turn,
|
|
73
|
+
duplicate: call.duplicate,
|
|
74
|
+
};
|
|
75
|
+
if (input.run_in_background === true)
|
|
76
|
+
return toolResult(base);
|
|
77
|
+
return toolResult({ ...base, turn: await call.completion });
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { JsonObject } from '../types.js';
|
|
2
|
+
export interface McpTool {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: JsonObject;
|
|
6
|
+
handler(input: Record<string, unknown>): Promise<unknown> | unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface JsonRpcRequest {
|
|
9
|
+
jsonrpc?: string;
|
|
10
|
+
id?: string | number | null;
|
|
11
|
+
method?: string;
|
|
12
|
+
params?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
export interface McpServer {
|
|
15
|
+
tools: McpTool[];
|
|
16
|
+
handle(message: JsonRpcRequest): Promise<unknown>;
|
|
17
|
+
start(input?: NodeJS.ReadableStream, output?: NodeJS.WritableStream): Promise<() => Promise<void>>;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/mcp.d.ts
ADDED
package/dist/mcp.js
ADDED
package/dist/messages.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { AgentError } from './errors.js';
|
|
2
|
+
import { isObject } from './json.js';
|
|
3
|
+
function normalizePart(value) {
|
|
4
|
+
if (!isObject(value))
|
|
5
|
+
throw new AgentError('invalid_input', 'message parts must be objects.');
|
|
6
|
+
if (value.type === 'text') {
|
|
7
|
+
if (typeof value.text !== 'string' || !value.text) {
|
|
8
|
+
throw new AgentError('invalid_input', 'text parts require non-empty text.');
|
|
9
|
+
}
|
|
10
|
+
return { type: 'text', text: value.text };
|
|
11
|
+
}
|
|
12
|
+
if (value.type === 'reference') {
|
|
13
|
+
if (typeof value.uri !== 'string' || !value.uri.trim()) {
|
|
14
|
+
throw new AgentError('invalid_input', 'reference parts require uri.');
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
type: 'reference',
|
|
18
|
+
uri: value.uri.trim(),
|
|
19
|
+
name: typeof value.name === 'string' && value.name.trim() ? value.name.trim() : null,
|
|
20
|
+
mediaType: typeof value.mediaType === 'string' && value.mediaType.trim()
|
|
21
|
+
? value.mediaType.trim()
|
|
22
|
+
: null,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (value.type === 'data') {
|
|
26
|
+
if (typeof value.name !== 'string' || !value.name.trim() || value.data === undefined) {
|
|
27
|
+
throw new AgentError('invalid_input', 'data parts require name and data.');
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
type: 'data',
|
|
31
|
+
name: value.name.trim(),
|
|
32
|
+
data: JSON.parse(JSON.stringify(value.data)),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
throw new AgentError('invalid_input', `Unsupported message part: ${String(value.type)}`);
|
|
36
|
+
}
|
|
37
|
+
export function normalizeMessage(value, role = 'user') {
|
|
38
|
+
if (typeof value === 'string') {
|
|
39
|
+
if (!value)
|
|
40
|
+
throw new AgentError('invalid_input', 'message must not be empty.');
|
|
41
|
+
return { role, parts: [{ type: 'text', text: value }] };
|
|
42
|
+
}
|
|
43
|
+
if (!isObject(value) || !Array.isArray(value.parts) || value.parts.length === 0) {
|
|
44
|
+
throw new AgentError('invalid_input', 'message requires at least one part.');
|
|
45
|
+
}
|
|
46
|
+
const validRoles = new Set(['user', 'assistant', 'system']);
|
|
47
|
+
if (!validRoles.has(value.role))
|
|
48
|
+
throw new AgentError('invalid_input', 'message role is invalid.');
|
|
49
|
+
return { role: value.role, parts: value.parts.map(normalizePart) };
|
|
50
|
+
}
|
|
51
|
+
export function messageText(message) {
|
|
52
|
+
return message.parts
|
|
53
|
+
.filter((part) => part.type === 'text')
|
|
54
|
+
.map((part) => part.text)
|
|
55
|
+
.join('');
|
|
56
|
+
}
|
package/dist/rows.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AgentRecord, AgentRevision, AgentSession, AgentTurn, SessionEvent } from './types.js';
|
|
2
|
+
export type Row = Record<string, unknown>;
|
|
3
|
+
export declare function revisionFromRow(row: Row): AgentRevision;
|
|
4
|
+
export declare function agentFromRow(row: Row): AgentRecord;
|
|
5
|
+
export declare function sessionFromRow(row: Row): AgentSession;
|
|
6
|
+
export declare function turnFromRow(row: Row): AgentTurn;
|
|
7
|
+
export declare function eventFromRow(row: Row): SessionEvent;
|
package/dist/rows.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { parseObject } from './json.js';
|
|
2
|
+
function nullable(value) {
|
|
3
|
+
return typeof value === 'string' && value ? value : null;
|
|
4
|
+
}
|
|
5
|
+
export function revisionFromRow(row) {
|
|
6
|
+
return {
|
|
7
|
+
id: String(row.revision_id ?? row.id),
|
|
8
|
+
agentId: String(row.agent_id),
|
|
9
|
+
number: Number(row.revision_number),
|
|
10
|
+
hash: String(row.definition_hash),
|
|
11
|
+
definition: parseObject(String(row.definition_json)),
|
|
12
|
+
createdAt: String(row.revision_created_at ?? row.created_at),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function agentFromRow(row) {
|
|
16
|
+
const revision = revisionFromRow(row);
|
|
17
|
+
return {
|
|
18
|
+
id: String(row.id),
|
|
19
|
+
currentRevisionId: String(row.current_revision_id),
|
|
20
|
+
currentRevision: Number(row.current_revision_number),
|
|
21
|
+
definition: revision.definition,
|
|
22
|
+
createdAt: String(row.created_at),
|
|
23
|
+
updatedAt: String(row.updated_at),
|
|
24
|
+
deletedAt: nullable(row.deleted_at),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function sessionFromRow(row) {
|
|
28
|
+
return {
|
|
29
|
+
id: String(row.id),
|
|
30
|
+
agentId: String(row.agent_id),
|
|
31
|
+
agentRevisionId: String(row.agent_revision_id),
|
|
32
|
+
agentRevision: Number(row.agent_revision_number),
|
|
33
|
+
status: String(row.status),
|
|
34
|
+
driverSessionId: nullable(row.driver_session_id),
|
|
35
|
+
metadata: parseObject(String(row.metadata_json)),
|
|
36
|
+
createdAt: String(row.created_at),
|
|
37
|
+
updatedAt: String(row.updated_at),
|
|
38
|
+
archivedAt: nullable(row.archived_at),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function turnFromRow(row) {
|
|
42
|
+
return {
|
|
43
|
+
id: String(row.id),
|
|
44
|
+
sessionId: String(row.session_id),
|
|
45
|
+
idempotencyKey: String(row.idempotency_key),
|
|
46
|
+
status: String(row.status),
|
|
47
|
+
input: JSON.parse(String(row.input_json)),
|
|
48
|
+
result: row.result_json ? parseObject(String(row.result_json)) : null,
|
|
49
|
+
error: row.error_json ? parseObject(String(row.error_json)) : null,
|
|
50
|
+
createdAt: String(row.created_at),
|
|
51
|
+
startedAt: nullable(row.started_at),
|
|
52
|
+
completedAt: nullable(row.completed_at),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function eventFromRow(row) {
|
|
56
|
+
return {
|
|
57
|
+
id: String(row.id),
|
|
58
|
+
sessionId: String(row.session_id),
|
|
59
|
+
turnId: nullable(row.turn_id),
|
|
60
|
+
sequence: Number(row.sequence),
|
|
61
|
+
type: String(row.type),
|
|
62
|
+
data: parseObject(String(row.data_json)),
|
|
63
|
+
createdAt: String(row.created_at),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AgentStore } from './agent-store.js';
|
|
2
|
+
import type { EventStore } from './event-store.js';
|
|
3
|
+
import type { SessionStore } from './session-store.js';
|
|
4
|
+
import type { TurnStore } from './turn-store.js';
|
|
5
|
+
import type { AgentDriver, AgentTurn, EnqueuedTurn, SendInput } from './types.js';
|
|
6
|
+
export interface RuntimeLimits {
|
|
7
|
+
maxInputBytes: number;
|
|
8
|
+
maxEventBytes: number;
|
|
9
|
+
turnTimeoutMs: number;
|
|
10
|
+
}
|
|
11
|
+
export declare class AgentRuntime {
|
|
12
|
+
private readonly agents;
|
|
13
|
+
private readonly sessions;
|
|
14
|
+
private readonly turns;
|
|
15
|
+
private readonly events;
|
|
16
|
+
private readonly limits;
|
|
17
|
+
private readonly drivers;
|
|
18
|
+
private readonly active;
|
|
19
|
+
constructor(agents: AgentStore, sessions: SessionStore, turns: TurnStore, events: EventStore, limits: RuntimeLimits, drivers?: AgentDriver[]);
|
|
20
|
+
register(driver: AgentDriver): void;
|
|
21
|
+
driverIds(): string[];
|
|
22
|
+
enqueue(sessionId: string, input: SendInput): EnqueuedTurn;
|
|
23
|
+
send(sessionId: string, input: SendInput): Promise<AgentTurn>;
|
|
24
|
+
cancel(sessionId: string): AgentTurn;
|
|
25
|
+
shutdown(): Promise<void>;
|
|
26
|
+
private execute;
|
|
27
|
+
private emit;
|
|
28
|
+
private normalizeResult;
|
|
29
|
+
}
|