@renseiai/agentfactory-server 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +71 -0
- package/dist/src/a2a-server.d.ts +88 -0
- package/dist/src/a2a-server.d.ts.map +1 -0
- package/dist/src/a2a-server.integration.test.d.ts +9 -0
- package/dist/src/a2a-server.integration.test.d.ts.map +1 -0
- package/dist/src/a2a-server.integration.test.js +397 -0
- package/dist/src/a2a-server.js +235 -0
- package/dist/src/a2a-server.test.d.ts +2 -0
- package/dist/src/a2a-server.test.d.ts.map +1 -0
- package/dist/src/a2a-server.test.js +311 -0
- package/dist/src/a2a-types.d.ts +125 -0
- package/dist/src/a2a-types.d.ts.map +1 -0
- package/dist/src/a2a-types.js +8 -0
- package/dist/src/agent-tracking.d.ts +201 -0
- package/dist/src/agent-tracking.d.ts.map +1 -0
- package/dist/src/agent-tracking.js +349 -0
- package/dist/src/env-validation.d.ts +65 -0
- package/dist/src/env-validation.d.ts.map +1 -0
- package/dist/src/env-validation.js +134 -0
- package/dist/src/governor-dedup.d.ts +15 -0
- package/dist/src/governor-dedup.d.ts.map +1 -0
- package/dist/src/governor-dedup.js +31 -0
- package/dist/src/governor-event-bus.d.ts +54 -0
- package/dist/src/governor-event-bus.d.ts.map +1 -0
- package/dist/src/governor-event-bus.js +152 -0
- package/dist/src/governor-storage.d.ts +28 -0
- package/dist/src/governor-storage.d.ts.map +1 -0
- package/dist/src/governor-storage.js +52 -0
- package/dist/src/index.d.ts +26 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +50 -0
- package/dist/src/issue-lock.d.ts +129 -0
- package/dist/src/issue-lock.d.ts.map +1 -0
- package/dist/src/issue-lock.js +508 -0
- package/dist/src/logger.d.ts +76 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +218 -0
- package/dist/src/orphan-cleanup.d.ts +64 -0
- package/dist/src/orphan-cleanup.d.ts.map +1 -0
- package/dist/src/orphan-cleanup.js +369 -0
- package/dist/src/pending-prompts.d.ts +67 -0
- package/dist/src/pending-prompts.d.ts.map +1 -0
- package/dist/src/pending-prompts.js +176 -0
- package/dist/src/processing-state-storage.d.ts +38 -0
- package/dist/src/processing-state-storage.d.ts.map +1 -0
- package/dist/src/processing-state-storage.js +61 -0
- package/dist/src/quota-tracker.d.ts +62 -0
- package/dist/src/quota-tracker.d.ts.map +1 -0
- package/dist/src/quota-tracker.js +155 -0
- package/dist/src/rate-limit.d.ts +111 -0
- package/dist/src/rate-limit.d.ts.map +1 -0
- package/dist/src/rate-limit.js +171 -0
- package/dist/src/redis-circuit-breaker.d.ts +67 -0
- package/dist/src/redis-circuit-breaker.d.ts.map +1 -0
- package/dist/src/redis-circuit-breaker.js +290 -0
- package/dist/src/redis-rate-limiter.d.ts +51 -0
- package/dist/src/redis-rate-limiter.d.ts.map +1 -0
- package/dist/src/redis-rate-limiter.js +168 -0
- package/dist/src/redis.d.ts +146 -0
- package/dist/src/redis.d.ts.map +1 -0
- package/dist/src/redis.js +343 -0
- package/dist/src/session-hash.d.ts +48 -0
- package/dist/src/session-hash.d.ts.map +1 -0
- package/dist/src/session-hash.js +80 -0
- package/dist/src/session-storage.d.ts +166 -0
- package/dist/src/session-storage.d.ts.map +1 -0
- package/dist/src/session-storage.js +397 -0
- package/dist/src/token-storage.d.ts +118 -0
- package/dist/src/token-storage.d.ts.map +1 -0
- package/dist/src/token-storage.js +263 -0
- package/dist/src/types.d.ts +11 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +7 -0
- package/dist/src/webhook-idempotency.d.ts +44 -0
- package/dist/src/webhook-idempotency.d.ts.map +1 -0
- package/dist/src/webhook-idempotency.js +148 -0
- package/dist/src/work-queue.d.ts +120 -0
- package/dist/src/work-queue.d.ts.map +1 -0
- package/dist/src/work-queue.js +384 -0
- package/dist/src/worker-auth.d.ts +29 -0
- package/dist/src/worker-auth.d.ts.map +1 -0
- package/dist/src/worker-auth.js +49 -0
- package/dist/src/worker-storage.d.ts +108 -0
- package/dist/src/worker-storage.d.ts.map +1 -0
- package/dist/src/worker-storage.js +295 -0
- package/dist/src/workflow-state-integration.test.d.ts +2 -0
- package/dist/src/workflow-state-integration.test.d.ts.map +1 -0
- package/dist/src/workflow-state-integration.test.js +342 -0
- package/dist/src/workflow-state.test.d.ts +2 -0
- package/dist/src/workflow-state.test.d.ts.map +1 -0
- package/dist/src/workflow-state.test.js +113 -0
- package/package.json +72 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A Server Handlers
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic utilities for exposing AgentFactory fleet capabilities
|
|
5
|
+
* via the Agent-to-Agent (A2A) protocol. Provides AgentCard generation,
|
|
6
|
+
* JSON-RPC request routing, and SSE event formatting.
|
|
7
|
+
*
|
|
8
|
+
* Consuming applications wire these handlers into their own HTTP server
|
|
9
|
+
* (Express, Hono, Next.js, etc.) — this module has no framework dependency.
|
|
10
|
+
*/
|
|
11
|
+
import { extractBearerToken, verifyApiKey } from './worker-auth.js';
|
|
12
|
+
/**
|
|
13
|
+
* Mapping from AgentWorkType values to auto-generated skill descriptors.
|
|
14
|
+
* Used when the caller does not supply an explicit skill list.
|
|
15
|
+
*/
|
|
16
|
+
const WORK_TYPE_SKILLS = {
|
|
17
|
+
development: {
|
|
18
|
+
id: 'code-development',
|
|
19
|
+
name: 'Code Development',
|
|
20
|
+
description: 'Implement features, fix bugs, and write code changes',
|
|
21
|
+
tags: ['coding', 'development'],
|
|
22
|
+
},
|
|
23
|
+
qa: {
|
|
24
|
+
id: 'quality-assurance',
|
|
25
|
+
name: 'Quality Assurance',
|
|
26
|
+
description: 'Run tests, review code quality, and verify changes',
|
|
27
|
+
tags: ['testing', 'qa'],
|
|
28
|
+
},
|
|
29
|
+
research: {
|
|
30
|
+
id: 'research-analysis',
|
|
31
|
+
name: 'Research & Analysis',
|
|
32
|
+
description: 'Investigate topics, gather information, and produce research reports',
|
|
33
|
+
tags: ['research', 'analysis'],
|
|
34
|
+
},
|
|
35
|
+
'backlog-creation': {
|
|
36
|
+
id: 'backlog-creation',
|
|
37
|
+
name: 'Backlog Creation',
|
|
38
|
+
description: 'Create and prioritize backlog items from requirements',
|
|
39
|
+
tags: ['planning', 'backlog'],
|
|
40
|
+
},
|
|
41
|
+
inflight: {
|
|
42
|
+
id: 'inflight-work',
|
|
43
|
+
name: 'In-Flight Work',
|
|
44
|
+
description: 'Continue work on in-progress tasks',
|
|
45
|
+
tags: ['workflow'],
|
|
46
|
+
},
|
|
47
|
+
acceptance: {
|
|
48
|
+
id: 'acceptance-review',
|
|
49
|
+
name: 'Acceptance Review',
|
|
50
|
+
description: 'Review completed work against acceptance criteria',
|
|
51
|
+
tags: ['review', 'acceptance'],
|
|
52
|
+
},
|
|
53
|
+
refinement: {
|
|
54
|
+
id: 'refinement',
|
|
55
|
+
name: 'Refinement',
|
|
56
|
+
description: 'Refine and improve existing work based on feedback',
|
|
57
|
+
tags: ['refinement', 'iteration'],
|
|
58
|
+
},
|
|
59
|
+
coordination: {
|
|
60
|
+
id: 'coordination',
|
|
61
|
+
name: 'Coordination',
|
|
62
|
+
description: 'Coordinate work across multiple agents and tasks',
|
|
63
|
+
tags: ['coordination', 'orchestration'],
|
|
64
|
+
},
|
|
65
|
+
'qa-coordination': {
|
|
66
|
+
id: 'qa-coordination',
|
|
67
|
+
name: 'QA Coordination',
|
|
68
|
+
description: 'Coordinate quality assurance across multiple agents',
|
|
69
|
+
tags: ['qa', 'coordination'],
|
|
70
|
+
},
|
|
71
|
+
'acceptance-coordination': {
|
|
72
|
+
id: 'acceptance-coordination',
|
|
73
|
+
name: 'Acceptance Coordination',
|
|
74
|
+
description: 'Coordinate acceptance reviews across multiple agents',
|
|
75
|
+
tags: ['acceptance', 'coordination'],
|
|
76
|
+
},
|
|
77
|
+
'refinement-coordination': {
|
|
78
|
+
id: 'refinement-coordination',
|
|
79
|
+
name: 'Refinement Coordination',
|
|
80
|
+
description: 'Coordinate refinement across sub-issues after QA/acceptance failure',
|
|
81
|
+
tags: ['refinement', 'coordination'],
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Build an A2A AgentCard from the supplied configuration.
|
|
86
|
+
*
|
|
87
|
+
* If no explicit skills are provided the card is populated with skills
|
|
88
|
+
* derived from every known {@link AgentWorkType}.
|
|
89
|
+
*
|
|
90
|
+
* @param config - Server configuration
|
|
91
|
+
* @returns A fully-formed AgentCard
|
|
92
|
+
*/
|
|
93
|
+
export function buildAgentCard(config) {
|
|
94
|
+
const skills = config.skills ?? Object.values(WORK_TYPE_SKILLS);
|
|
95
|
+
return {
|
|
96
|
+
name: config.name,
|
|
97
|
+
description: config.description,
|
|
98
|
+
url: config.url,
|
|
99
|
+
version: config.version ?? '1.0.0',
|
|
100
|
+
capabilities: {
|
|
101
|
+
streaming: config.streaming ?? false,
|
|
102
|
+
pushNotifications: false,
|
|
103
|
+
stateTransitionHistory: false,
|
|
104
|
+
},
|
|
105
|
+
skills,
|
|
106
|
+
...(config.authSchemes && { authentication: config.authSchemes }),
|
|
107
|
+
defaultInputContentTypes: ['text/plain'],
|
|
108
|
+
defaultOutputContentTypes: ['text/plain'],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// ---- JSON-RPC error helpers ------------------------------------------------
|
|
112
|
+
const JSON_RPC_VERSION = '2.0';
|
|
113
|
+
function rpcError(id, code, message, data) {
|
|
114
|
+
return {
|
|
115
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
116
|
+
id,
|
|
117
|
+
error: { code, message, ...(data !== undefined && { data }) },
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function rpcResult(id, result) {
|
|
121
|
+
return {
|
|
122
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
123
|
+
id,
|
|
124
|
+
result,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// ---- Default auth verifier --------------------------------------------------
|
|
128
|
+
function defaultVerifyAuth(authHeader) {
|
|
129
|
+
const token = extractBearerToken(authHeader ?? null);
|
|
130
|
+
if (!token)
|
|
131
|
+
return false;
|
|
132
|
+
return verifyApiKey(token);
|
|
133
|
+
}
|
|
134
|
+
// ---- Factory ----------------------------------------------------------------
|
|
135
|
+
/**
|
|
136
|
+
* Create a framework-agnostic A2A request handler.
|
|
137
|
+
*
|
|
138
|
+
* The returned function processes a single JSON-RPC request and returns
|
|
139
|
+
* the corresponding response. The consuming application is responsible
|
|
140
|
+
* for HTTP parsing, serialisation, and transport.
|
|
141
|
+
*
|
|
142
|
+
* Supported methods:
|
|
143
|
+
* - `message/send` — send a message (optionally to an existing task)
|
|
144
|
+
* - `tasks/get` — retrieve a task by ID
|
|
145
|
+
* - `tasks/cancel` — cancel a task by ID
|
|
146
|
+
*
|
|
147
|
+
* @param options - Callbacks for task lifecycle and (optional) auth
|
|
148
|
+
* @returns An async handler function
|
|
149
|
+
*/
|
|
150
|
+
export function createA2aRequestHandler(options) {
|
|
151
|
+
const { onSendMessage, onGetTask, onCancelTask } = options;
|
|
152
|
+
const verifyAuth = options.verifyAuth ?? defaultVerifyAuth;
|
|
153
|
+
return async (request, authHeader) => {
|
|
154
|
+
const id = request.id ?? null;
|
|
155
|
+
// --- Auth check ---
|
|
156
|
+
if (!verifyAuth(authHeader)) {
|
|
157
|
+
return rpcError(id, -32000, 'Unauthorized');
|
|
158
|
+
}
|
|
159
|
+
// --- Method routing ---
|
|
160
|
+
switch (request.method) {
|
|
161
|
+
case 'message/send': {
|
|
162
|
+
const params = request.params;
|
|
163
|
+
if (!params || !params.message) {
|
|
164
|
+
return rpcError(id, -32602, 'Invalid params: message is required');
|
|
165
|
+
}
|
|
166
|
+
const message = params.message;
|
|
167
|
+
const taskId = params.taskId;
|
|
168
|
+
try {
|
|
169
|
+
const task = await onSendMessage(message, taskId);
|
|
170
|
+
return rpcResult(id, task);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
const msg = err instanceof Error ? err.message : 'Internal error';
|
|
174
|
+
return rpcError(id, -32603, msg);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
case 'tasks/get': {
|
|
178
|
+
const params = request.params;
|
|
179
|
+
if (!params || typeof params.taskId !== 'string') {
|
|
180
|
+
return rpcError(id, -32602, 'Invalid params: taskId is required');
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const task = await onGetTask(params.taskId);
|
|
184
|
+
if (!task) {
|
|
185
|
+
return rpcError(id, -32001, 'Task not found');
|
|
186
|
+
}
|
|
187
|
+
return rpcResult(id, task);
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
const msg = err instanceof Error ? err.message : 'Internal error';
|
|
191
|
+
return rpcError(id, -32603, msg);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
case 'tasks/cancel': {
|
|
195
|
+
const params = request.params;
|
|
196
|
+
if (!params || typeof params.taskId !== 'string') {
|
|
197
|
+
return rpcError(id, -32602, 'Invalid params: taskId is required');
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const task = await onCancelTask(params.taskId);
|
|
201
|
+
if (!task) {
|
|
202
|
+
return rpcError(id, -32001, 'Task not found');
|
|
203
|
+
}
|
|
204
|
+
return rpcResult(id, task);
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
const msg = err instanceof Error ? err.message : 'Internal error';
|
|
208
|
+
return rpcError(id, -32603, msg);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
default:
|
|
212
|
+
return rpcError(id, -32601, `Method not found: ${request.method}`);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// SSE event formatter
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
/**
|
|
220
|
+
* Format an A2A task event as a Server-Sent Events (SSE) message.
|
|
221
|
+
*
|
|
222
|
+
* The output follows the SSE text/event-stream format:
|
|
223
|
+
* ```
|
|
224
|
+
* event: <type>
|
|
225
|
+
* data: <JSON payload>
|
|
226
|
+
*
|
|
227
|
+
* ```
|
|
228
|
+
*
|
|
229
|
+
* @param event - The task event to format
|
|
230
|
+
* @returns A string ready to write to an SSE response stream
|
|
231
|
+
*/
|
|
232
|
+
export function formatSseEvent(event) {
|
|
233
|
+
const data = JSON.stringify(event);
|
|
234
|
+
return `event: ${event.type}\ndata: ${data}\n\n`;
|
|
235
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a2a-server.test.d.ts","sourceRoot":"","sources":["../../src/a2a-server.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { buildAgentCard, createA2aRequestHandler, formatSseEvent } from './a2a-server.js';
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Helpers
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
function makeRequest(method, params, id = 1) {
|
|
7
|
+
return { jsonrpc: '2.0', id, method, params };
|
|
8
|
+
}
|
|
9
|
+
function makeMessage(text, role = 'user') {
|
|
10
|
+
return { role, parts: [{ type: 'text', text }] };
|
|
11
|
+
}
|
|
12
|
+
function makeTask(overrides = {}) {
|
|
13
|
+
return {
|
|
14
|
+
id: 'task-1',
|
|
15
|
+
status: 'submitted',
|
|
16
|
+
messages: [],
|
|
17
|
+
artifacts: [],
|
|
18
|
+
...overrides,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Build a handler with permissive auth and controllable callbacks */
|
|
22
|
+
function createTestHandler(opts = {}) {
|
|
23
|
+
const onSendMessage = opts.onSendMessage ?? vi.fn(async () => makeTask());
|
|
24
|
+
const onGetTask = opts.onGetTask ?? vi.fn(async () => makeTask());
|
|
25
|
+
const onCancelTask = opts.onCancelTask ?? vi.fn(async () => makeTask());
|
|
26
|
+
const verifyAuth = opts.verifyAuth ?? (() => true);
|
|
27
|
+
const handler = createA2aRequestHandler({
|
|
28
|
+
onSendMessage,
|
|
29
|
+
onGetTask,
|
|
30
|
+
onCancelTask,
|
|
31
|
+
verifyAuth,
|
|
32
|
+
});
|
|
33
|
+
return { handler, onSendMessage, onGetTask, onCancelTask };
|
|
34
|
+
}
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// buildAgentCard
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
describe('buildAgentCard', () => {
|
|
39
|
+
const baseConfig = {
|
|
40
|
+
name: 'TestAgent',
|
|
41
|
+
description: 'A test agent',
|
|
42
|
+
url: 'https://example.com/a2a',
|
|
43
|
+
};
|
|
44
|
+
it('generates a valid card from minimal config', () => {
|
|
45
|
+
const card = buildAgentCard(baseConfig);
|
|
46
|
+
expect(card.name).toBe('TestAgent');
|
|
47
|
+
expect(card.description).toBe('A test agent');
|
|
48
|
+
expect(card.url).toBe('https://example.com/a2a');
|
|
49
|
+
expect(card.version).toBe('1.0.0');
|
|
50
|
+
expect(card.capabilities.streaming).toBe(false);
|
|
51
|
+
expect(card.capabilities.pushNotifications).toBe(false);
|
|
52
|
+
expect(card.capabilities.stateTransitionHistory).toBe(false);
|
|
53
|
+
expect(card.defaultInputContentTypes).toEqual(['text/plain']);
|
|
54
|
+
expect(card.defaultOutputContentTypes).toEqual(['text/plain']);
|
|
55
|
+
});
|
|
56
|
+
it('uses the provided version', () => {
|
|
57
|
+
const card = buildAgentCard({ ...baseConfig, version: '2.3.1' });
|
|
58
|
+
expect(card.version).toBe('2.3.1');
|
|
59
|
+
});
|
|
60
|
+
it('sets streaming capability when configured', () => {
|
|
61
|
+
const card = buildAgentCard({ ...baseConfig, streaming: true });
|
|
62
|
+
expect(card.capabilities.streaming).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
it('includes authentication schemes when provided', () => {
|
|
65
|
+
const card = buildAgentCard({
|
|
66
|
+
...baseConfig,
|
|
67
|
+
authSchemes: [{ type: 'http', scheme: 'bearer' }],
|
|
68
|
+
});
|
|
69
|
+
expect(card.authentication).toEqual([{ type: 'http', scheme: 'bearer' }]);
|
|
70
|
+
});
|
|
71
|
+
it('does not include authentication when not provided', () => {
|
|
72
|
+
const card = buildAgentCard(baseConfig);
|
|
73
|
+
expect(card.authentication).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
it('auto-generates skills from AgentWorkType values when none provided', () => {
|
|
76
|
+
const card = buildAgentCard(baseConfig);
|
|
77
|
+
expect(card.skills.length).toBeGreaterThan(0);
|
|
78
|
+
const ids = card.skills.map((s) => s.id);
|
|
79
|
+
expect(ids).toContain('code-development');
|
|
80
|
+
expect(ids).toContain('quality-assurance');
|
|
81
|
+
expect(ids).toContain('research-analysis');
|
|
82
|
+
expect(ids).toContain('backlog-creation');
|
|
83
|
+
expect(ids).toContain('coordination');
|
|
84
|
+
});
|
|
85
|
+
it('uses explicit skills when provided, ignoring auto-generation', () => {
|
|
86
|
+
const customSkills = [
|
|
87
|
+
{ id: 'custom-skill', name: 'Custom', description: 'A custom skill' },
|
|
88
|
+
];
|
|
89
|
+
const card = buildAgentCard({ ...baseConfig, skills: customSkills });
|
|
90
|
+
expect(card.skills).toEqual(customSkills);
|
|
91
|
+
expect(card.skills).toHaveLength(1);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// createA2aRequestHandler — method routing
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
describe('createA2aRequestHandler', () => {
|
|
98
|
+
describe('message/send', () => {
|
|
99
|
+
it('routes message/send and returns the task', async () => {
|
|
100
|
+
const task = makeTask({ id: 'task-42', status: 'working' });
|
|
101
|
+
const onSendMessage = vi.fn(async () => task);
|
|
102
|
+
const { handler } = createTestHandler({ onSendMessage });
|
|
103
|
+
const message = makeMessage('Hello agent');
|
|
104
|
+
const res = await handler(makeRequest('message/send', { message }));
|
|
105
|
+
expect(res.error).toBeUndefined();
|
|
106
|
+
expect(res.result).toEqual(task);
|
|
107
|
+
expect(onSendMessage).toHaveBeenCalledWith(message, undefined);
|
|
108
|
+
});
|
|
109
|
+
it('passes taskId to onSendMessage when provided', async () => {
|
|
110
|
+
const onSendMessage = vi.fn(async () => makeTask());
|
|
111
|
+
const { handler } = createTestHandler({ onSendMessage });
|
|
112
|
+
const message = makeMessage('Continue working');
|
|
113
|
+
await handler(makeRequest('message/send', { message, taskId: 'existing-task' }));
|
|
114
|
+
expect(onSendMessage).toHaveBeenCalledWith(message, 'existing-task');
|
|
115
|
+
});
|
|
116
|
+
it('returns -32602 when message param is missing', async () => {
|
|
117
|
+
const { handler } = createTestHandler();
|
|
118
|
+
const res = await handler(makeRequest('message/send', {}));
|
|
119
|
+
expect(res.error?.code).toBe(-32602);
|
|
120
|
+
expect(res.error?.message).toContain('message is required');
|
|
121
|
+
});
|
|
122
|
+
it('returns -32602 when params are missing entirely', async () => {
|
|
123
|
+
const { handler } = createTestHandler();
|
|
124
|
+
const res = await handler(makeRequest('message/send'));
|
|
125
|
+
expect(res.error?.code).toBe(-32602);
|
|
126
|
+
});
|
|
127
|
+
it('returns -32603 when onSendMessage throws', async () => {
|
|
128
|
+
const onSendMessage = vi.fn(async () => {
|
|
129
|
+
throw new Error('Queue full');
|
|
130
|
+
});
|
|
131
|
+
const { handler } = createTestHandler({ onSendMessage });
|
|
132
|
+
const res = await handler(makeRequest('message/send', { message: makeMessage('hi') }));
|
|
133
|
+
expect(res.error?.code).toBe(-32603);
|
|
134
|
+
expect(res.error?.message).toBe('Queue full');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
describe('tasks/get', () => {
|
|
138
|
+
it('returns the task when found', async () => {
|
|
139
|
+
const task = makeTask({ id: 'task-99', status: 'completed' });
|
|
140
|
+
const onGetTask = vi.fn(async () => task);
|
|
141
|
+
const { handler } = createTestHandler({ onGetTask });
|
|
142
|
+
const res = await handler(makeRequest('tasks/get', { taskId: 'task-99' }));
|
|
143
|
+
expect(res.error).toBeUndefined();
|
|
144
|
+
expect(res.result).toEqual(task);
|
|
145
|
+
expect(onGetTask).toHaveBeenCalledWith('task-99');
|
|
146
|
+
});
|
|
147
|
+
it('returns -32001 when task is not found', async () => {
|
|
148
|
+
const onGetTask = vi.fn(async () => null);
|
|
149
|
+
const { handler } = createTestHandler({ onGetTask });
|
|
150
|
+
const res = await handler(makeRequest('tasks/get', { taskId: 'missing' }));
|
|
151
|
+
expect(res.error?.code).toBe(-32001);
|
|
152
|
+
expect(res.error?.message).toBe('Task not found');
|
|
153
|
+
});
|
|
154
|
+
it('returns -32602 when taskId is missing', async () => {
|
|
155
|
+
const { handler } = createTestHandler();
|
|
156
|
+
const res = await handler(makeRequest('tasks/get', {}));
|
|
157
|
+
expect(res.error?.code).toBe(-32602);
|
|
158
|
+
expect(res.error?.message).toContain('taskId is required');
|
|
159
|
+
});
|
|
160
|
+
it('returns -32602 when taskId is not a string', async () => {
|
|
161
|
+
const { handler } = createTestHandler();
|
|
162
|
+
const res = await handler(makeRequest('tasks/get', { taskId: 123 }));
|
|
163
|
+
expect(res.error?.code).toBe(-32602);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
describe('tasks/cancel', () => {
|
|
167
|
+
it('returns the canceled task', async () => {
|
|
168
|
+
const task = makeTask({ id: 'task-5', status: 'canceled' });
|
|
169
|
+
const onCancelTask = vi.fn(async () => task);
|
|
170
|
+
const { handler } = createTestHandler({ onCancelTask });
|
|
171
|
+
const res = await handler(makeRequest('tasks/cancel', { taskId: 'task-5' }));
|
|
172
|
+
expect(res.error).toBeUndefined();
|
|
173
|
+
expect(res.result).toEqual(task);
|
|
174
|
+
expect(onCancelTask).toHaveBeenCalledWith('task-5');
|
|
175
|
+
});
|
|
176
|
+
it('returns -32001 when task is not found', async () => {
|
|
177
|
+
const onCancelTask = vi.fn(async () => null);
|
|
178
|
+
const { handler } = createTestHandler({ onCancelTask });
|
|
179
|
+
const res = await handler(makeRequest('tasks/cancel', { taskId: 'no-such' }));
|
|
180
|
+
expect(res.error?.code).toBe(-32001);
|
|
181
|
+
expect(res.error?.message).toBe('Task not found');
|
|
182
|
+
});
|
|
183
|
+
it('returns -32602 when taskId is missing', async () => {
|
|
184
|
+
const { handler } = createTestHandler();
|
|
185
|
+
const res = await handler(makeRequest('tasks/cancel'));
|
|
186
|
+
expect(res.error?.code).toBe(-32602);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
describe('unknown method', () => {
|
|
190
|
+
it('returns -32601 for an unrecognised method', async () => {
|
|
191
|
+
const { handler } = createTestHandler();
|
|
192
|
+
const res = await handler(makeRequest('foo/bar'));
|
|
193
|
+
expect(res.error?.code).toBe(-32601);
|
|
194
|
+
expect(res.error?.message).toContain('Method not found');
|
|
195
|
+
expect(res.error?.message).toContain('foo/bar');
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
describe('authentication', () => {
|
|
199
|
+
it('returns -32000 when verifyAuth rejects', async () => {
|
|
200
|
+
const verifyAuth = () => false;
|
|
201
|
+
const { handler } = createTestHandler({ verifyAuth });
|
|
202
|
+
const res = await handler(makeRequest('tasks/get', { taskId: 'x' }));
|
|
203
|
+
expect(res.error?.code).toBe(-32000);
|
|
204
|
+
expect(res.error?.message).toBe('Unauthorized');
|
|
205
|
+
});
|
|
206
|
+
it('passes the auth header to verifyAuth', async () => {
|
|
207
|
+
const verifyAuth = vi.fn(() => true);
|
|
208
|
+
const { handler } = createTestHandler({ verifyAuth });
|
|
209
|
+
await handler(makeRequest('tasks/get', { taskId: 'x' }), 'Bearer tok-123');
|
|
210
|
+
expect(verifyAuth).toHaveBeenCalledWith('Bearer tok-123');
|
|
211
|
+
});
|
|
212
|
+
it('passes undefined when no auth header is provided', async () => {
|
|
213
|
+
const verifyAuth = vi.fn(() => true);
|
|
214
|
+
const { handler } = createTestHandler({ verifyAuth });
|
|
215
|
+
await handler(makeRequest('tasks/get', { taskId: 'x' }));
|
|
216
|
+
expect(verifyAuth).toHaveBeenCalledWith(undefined);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
describe('response format', () => {
|
|
220
|
+
it('always includes jsonrpc 2.0 version', async () => {
|
|
221
|
+
const { handler } = createTestHandler();
|
|
222
|
+
const res = await handler(makeRequest('tasks/get', { taskId: 'x' }));
|
|
223
|
+
expect(res.jsonrpc).toBe('2.0');
|
|
224
|
+
});
|
|
225
|
+
it('echoes back the request id', async () => {
|
|
226
|
+
const { handler } = createTestHandler();
|
|
227
|
+
const res = await handler(makeRequest('tasks/get', { taskId: 'x' }, 42));
|
|
228
|
+
expect(res.id).toBe(42);
|
|
229
|
+
});
|
|
230
|
+
it('echoes string ids correctly', async () => {
|
|
231
|
+
const { handler } = createTestHandler();
|
|
232
|
+
const res = await handler(makeRequest('tasks/get', { taskId: 'x' }, 'req-abc'));
|
|
233
|
+
expect(res.id).toBe('req-abc');
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// formatSseEvent
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
describe('formatSseEvent', () => {
|
|
241
|
+
it('formats a TaskStatusUpdate event', () => {
|
|
242
|
+
const event = {
|
|
243
|
+
type: 'TaskStatusUpdate',
|
|
244
|
+
taskId: 'task-1',
|
|
245
|
+
status: 'working',
|
|
246
|
+
final: false,
|
|
247
|
+
};
|
|
248
|
+
const result = formatSseEvent(event);
|
|
249
|
+
expect(result).toBe('event: TaskStatusUpdate\n' +
|
|
250
|
+
`data: ${JSON.stringify(event)}\n\n`);
|
|
251
|
+
});
|
|
252
|
+
it('formats a TaskStatusUpdate with message', () => {
|
|
253
|
+
const event = {
|
|
254
|
+
type: 'TaskStatusUpdate',
|
|
255
|
+
taskId: 'task-2',
|
|
256
|
+
status: 'completed',
|
|
257
|
+
message: makeMessage('Done!', 'agent'),
|
|
258
|
+
final: true,
|
|
259
|
+
};
|
|
260
|
+
const result = formatSseEvent(event);
|
|
261
|
+
expect(result).toContain('event: TaskStatusUpdate\n');
|
|
262
|
+
expect(result).toContain('"final":true');
|
|
263
|
+
expect(result).toContain('"text":"Done!"');
|
|
264
|
+
expect(result.endsWith('\n\n')).toBe(true);
|
|
265
|
+
});
|
|
266
|
+
it('formats a TaskArtifactUpdate event', () => {
|
|
267
|
+
const event = {
|
|
268
|
+
type: 'TaskArtifactUpdate',
|
|
269
|
+
taskId: 'task-3',
|
|
270
|
+
artifact: {
|
|
271
|
+
name: 'report.md',
|
|
272
|
+
parts: [{ type: 'text', text: '# Report' }],
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
const result = formatSseEvent(event);
|
|
276
|
+
expect(result).toBe('event: TaskArtifactUpdate\n' +
|
|
277
|
+
`data: ${JSON.stringify(event)}\n\n`);
|
|
278
|
+
});
|
|
279
|
+
it('uses the event type as the SSE event name', () => {
|
|
280
|
+
const event = {
|
|
281
|
+
type: 'TaskStatusUpdate',
|
|
282
|
+
taskId: 'task-1',
|
|
283
|
+
status: 'failed',
|
|
284
|
+
final: true,
|
|
285
|
+
};
|
|
286
|
+
const lines = formatSseEvent(event).split('\n');
|
|
287
|
+
expect(lines[0]).toBe('event: TaskStatusUpdate');
|
|
288
|
+
});
|
|
289
|
+
it('puts the full JSON payload on the data line', () => {
|
|
290
|
+
const event = {
|
|
291
|
+
type: 'TaskStatusUpdate',
|
|
292
|
+
taskId: 'task-1',
|
|
293
|
+
status: 'submitted',
|
|
294
|
+
final: false,
|
|
295
|
+
};
|
|
296
|
+
const lines = formatSseEvent(event).split('\n');
|
|
297
|
+
const dataLine = lines[1];
|
|
298
|
+
const parsed = JSON.parse(dataLine.replace('data: ', ''));
|
|
299
|
+
expect(parsed.taskId).toBe('task-1');
|
|
300
|
+
expect(parsed.status).toBe('submitted');
|
|
301
|
+
});
|
|
302
|
+
it('ends with double newline', () => {
|
|
303
|
+
const event = {
|
|
304
|
+
type: 'TaskStatusUpdate',
|
|
305
|
+
taskId: 't',
|
|
306
|
+
status: 'working',
|
|
307
|
+
final: false,
|
|
308
|
+
};
|
|
309
|
+
expect(formatSseEvent(event)).toMatch(/\n\n$/);
|
|
310
|
+
});
|
|
311
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A Protocol Types
|
|
3
|
+
*
|
|
4
|
+
* TypeScript type definitions for the Agent-to-Agent (A2A) protocol.
|
|
5
|
+
* Covers AgentCard discovery, JSON-RPC messaging, task lifecycle,
|
|
6
|
+
* and Server-Sent Events (SSE) streaming.
|
|
7
|
+
*/
|
|
8
|
+
/** Describes an agent's identity, capabilities, and discoverable skills */
|
|
9
|
+
export interface A2aAgentCard {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
url: string;
|
|
13
|
+
version: string;
|
|
14
|
+
capabilities: A2aCapabilities;
|
|
15
|
+
skills: A2aSkill[];
|
|
16
|
+
authentication?: A2aAuthScheme[];
|
|
17
|
+
defaultInputContentTypes?: string[];
|
|
18
|
+
defaultOutputContentTypes?: string[];
|
|
19
|
+
}
|
|
20
|
+
/** Feature flags advertised by the agent */
|
|
21
|
+
export interface A2aCapabilities {
|
|
22
|
+
streaming: boolean;
|
|
23
|
+
pushNotifications: boolean;
|
|
24
|
+
stateTransitionHistory: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** A discrete capability the agent can perform */
|
|
27
|
+
export interface A2aSkill {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
tags?: string[];
|
|
32
|
+
examples?: string[];
|
|
33
|
+
}
|
|
34
|
+
/** Authentication scheme supported by the agent endpoint */
|
|
35
|
+
export interface A2aAuthScheme {
|
|
36
|
+
type: 'apiKey' | 'http' | 'oauth2';
|
|
37
|
+
/** For http type, e.g. 'bearer' */
|
|
38
|
+
scheme?: string;
|
|
39
|
+
/** For apiKey type, e.g. 'header' */
|
|
40
|
+
in?: string;
|
|
41
|
+
/** For apiKey type, the header name */
|
|
42
|
+
name?: string;
|
|
43
|
+
}
|
|
44
|
+
/** A JSON-RPC 2.0 request object */
|
|
45
|
+
export interface JsonRpcRequest {
|
|
46
|
+
jsonrpc: '2.0';
|
|
47
|
+
id: string | number;
|
|
48
|
+
method: string;
|
|
49
|
+
params?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
/** A JSON-RPC 2.0 response object */
|
|
52
|
+
export interface JsonRpcResponse {
|
|
53
|
+
jsonrpc: '2.0';
|
|
54
|
+
id: string | number | null;
|
|
55
|
+
result?: unknown;
|
|
56
|
+
error?: JsonRpcError;
|
|
57
|
+
}
|
|
58
|
+
/** A JSON-RPC 2.0 error object */
|
|
59
|
+
export interface JsonRpcError {
|
|
60
|
+
code: number;
|
|
61
|
+
message: string;
|
|
62
|
+
data?: unknown;
|
|
63
|
+
}
|
|
64
|
+
/** Represents a unit of work managed by the agent */
|
|
65
|
+
export interface A2aTask {
|
|
66
|
+
id: string;
|
|
67
|
+
status: A2aTaskStatus;
|
|
68
|
+
messages: A2aMessage[];
|
|
69
|
+
artifacts: A2aArtifact[];
|
|
70
|
+
metadata?: Record<string, unknown>;
|
|
71
|
+
}
|
|
72
|
+
/** Possible states of an A2A task */
|
|
73
|
+
export type A2aTaskStatus = 'submitted' | 'working' | 'input-required' | 'completed' | 'failed' | 'canceled';
|
|
74
|
+
/** A message exchanged between user and agent */
|
|
75
|
+
export interface A2aMessage {
|
|
76
|
+
role: 'user' | 'agent';
|
|
77
|
+
parts: A2aPart[];
|
|
78
|
+
metadata?: Record<string, unknown>;
|
|
79
|
+
}
|
|
80
|
+
/** A content part within a message or artifact */
|
|
81
|
+
export type A2aPart = A2aTextPart | A2aFilePart | A2aDataPart;
|
|
82
|
+
/** Plain text content part */
|
|
83
|
+
export interface A2aTextPart {
|
|
84
|
+
type: 'text';
|
|
85
|
+
text: string;
|
|
86
|
+
}
|
|
87
|
+
/** File content part (URI or inline bytes) */
|
|
88
|
+
export interface A2aFilePart {
|
|
89
|
+
type: 'file';
|
|
90
|
+
file: {
|
|
91
|
+
name: string;
|
|
92
|
+
mimeType: string;
|
|
93
|
+
uri?: string;
|
|
94
|
+
bytes?: string;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** Structured data content part */
|
|
98
|
+
export interface A2aDataPart {
|
|
99
|
+
type: 'data';
|
|
100
|
+
data: Record<string, unknown>;
|
|
101
|
+
}
|
|
102
|
+
/** An output artifact produced by the agent */
|
|
103
|
+
export interface A2aArtifact {
|
|
104
|
+
name: string;
|
|
105
|
+
description?: string;
|
|
106
|
+
parts: A2aPart[];
|
|
107
|
+
metadata?: Record<string, unknown>;
|
|
108
|
+
}
|
|
109
|
+
/** Notifies the client of a task status change */
|
|
110
|
+
export interface A2aTaskStatusUpdateEvent {
|
|
111
|
+
type: 'TaskStatusUpdate';
|
|
112
|
+
taskId: string;
|
|
113
|
+
status: A2aTaskStatus;
|
|
114
|
+
message?: A2aMessage;
|
|
115
|
+
final: boolean;
|
|
116
|
+
}
|
|
117
|
+
/** Notifies the client of a new or updated artifact */
|
|
118
|
+
export interface A2aTaskArtifactUpdateEvent {
|
|
119
|
+
type: 'TaskArtifactUpdate';
|
|
120
|
+
taskId: string;
|
|
121
|
+
artifact: A2aArtifact;
|
|
122
|
+
}
|
|
123
|
+
/** Union of all SSE event types emitted during task streaming */
|
|
124
|
+
export type A2aTaskEvent = A2aTaskStatusUpdateEvent | A2aTaskArtifactUpdateEvent;
|
|
125
|
+
//# sourceMappingURL=a2a-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a2a-types.d.ts","sourceRoot":"","sources":["../../src/a2a-types.ts"],"names":[],"mappings":"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"}
|