@renseiai/agentfactory 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 +125 -0
- package/dist/src/config/index.d.ts +3 -0
- package/dist/src/config/index.d.ts.map +1 -0
- package/dist/src/config/index.js +1 -0
- package/dist/src/config/repository-config.d.ts +44 -0
- package/dist/src/config/repository-config.d.ts.map +1 -0
- package/dist/src/config/repository-config.js +88 -0
- package/dist/src/config/repository-config.test.d.ts +2 -0
- package/dist/src/config/repository-config.test.d.ts.map +1 -0
- package/dist/src/config/repository-config.test.js +249 -0
- package/dist/src/deployment/deployment-checker.d.ts +110 -0
- package/dist/src/deployment/deployment-checker.d.ts.map +1 -0
- package/dist/src/deployment/deployment-checker.js +242 -0
- package/dist/src/deployment/index.d.ts +3 -0
- package/dist/src/deployment/index.d.ts.map +1 -0
- package/dist/src/deployment/index.js +2 -0
- package/dist/src/frontend/index.d.ts +2 -0
- package/dist/src/frontend/index.d.ts.map +1 -0
- package/dist/src/frontend/index.js +1 -0
- package/dist/src/frontend/types.d.ts +106 -0
- package/dist/src/frontend/types.d.ts.map +1 -0
- package/dist/src/frontend/types.js +11 -0
- package/dist/src/governor/decision-engine.d.ts +52 -0
- package/dist/src/governor/decision-engine.d.ts.map +1 -0
- package/dist/src/governor/decision-engine.js +220 -0
- package/dist/src/governor/decision-engine.test.d.ts +2 -0
- package/dist/src/governor/decision-engine.test.d.ts.map +1 -0
- package/dist/src/governor/decision-engine.test.js +629 -0
- package/dist/src/governor/event-bus.d.ts +43 -0
- package/dist/src/governor/event-bus.d.ts.map +1 -0
- package/dist/src/governor/event-bus.js +8 -0
- package/dist/src/governor/event-deduplicator.d.ts +43 -0
- package/dist/src/governor/event-deduplicator.d.ts.map +1 -0
- package/dist/src/governor/event-deduplicator.js +53 -0
- package/dist/src/governor/event-driven-governor.d.ts +131 -0
- package/dist/src/governor/event-driven-governor.d.ts.map +1 -0
- package/dist/src/governor/event-driven-governor.js +379 -0
- package/dist/src/governor/event-driven-governor.test.d.ts +2 -0
- package/dist/src/governor/event-driven-governor.test.d.ts.map +1 -0
- package/dist/src/governor/event-driven-governor.test.js +673 -0
- package/dist/src/governor/event-types.d.ts +78 -0
- package/dist/src/governor/event-types.d.ts.map +1 -0
- package/dist/src/governor/event-types.js +32 -0
- package/dist/src/governor/governor-types.d.ts +82 -0
- package/dist/src/governor/governor-types.d.ts.map +1 -0
- package/dist/src/governor/governor-types.js +21 -0
- package/dist/src/governor/governor.d.ts +100 -0
- package/dist/src/governor/governor.d.ts.map +1 -0
- package/dist/src/governor/governor.js +262 -0
- package/dist/src/governor/governor.test.d.ts +2 -0
- package/dist/src/governor/governor.test.d.ts.map +1 -0
- package/dist/src/governor/governor.test.js +514 -0
- package/dist/src/governor/human-touchpoints.d.ts +131 -0
- package/dist/src/governor/human-touchpoints.d.ts.map +1 -0
- package/dist/src/governor/human-touchpoints.js +251 -0
- package/dist/src/governor/human-touchpoints.test.d.ts +2 -0
- package/dist/src/governor/human-touchpoints.test.d.ts.map +1 -0
- package/dist/src/governor/human-touchpoints.test.js +366 -0
- package/dist/src/governor/in-memory-event-bus.d.ts +29 -0
- package/dist/src/governor/in-memory-event-bus.d.ts.map +1 -0
- package/dist/src/governor/in-memory-event-bus.js +79 -0
- package/dist/src/governor/index.d.ts +14 -0
- package/dist/src/governor/index.d.ts.map +1 -0
- package/dist/src/governor/index.js +13 -0
- package/dist/src/governor/override-parser.d.ts +60 -0
- package/dist/src/governor/override-parser.d.ts.map +1 -0
- package/dist/src/governor/override-parser.js +98 -0
- package/dist/src/governor/override-parser.test.d.ts +2 -0
- package/dist/src/governor/override-parser.test.d.ts.map +1 -0
- package/dist/src/governor/override-parser.test.js +312 -0
- package/dist/src/governor/platform-adapter.d.ts +69 -0
- package/dist/src/governor/platform-adapter.d.ts.map +1 -0
- package/dist/src/governor/platform-adapter.js +11 -0
- package/dist/src/governor/processing-state.d.ts +66 -0
- package/dist/src/governor/processing-state.d.ts.map +1 -0
- package/dist/src/governor/processing-state.js +43 -0
- package/dist/src/governor/processing-state.test.d.ts +2 -0
- package/dist/src/governor/processing-state.test.d.ts.map +1 -0
- package/dist/src/governor/processing-state.test.js +96 -0
- package/dist/src/governor/top-of-funnel.d.ts +118 -0
- package/dist/src/governor/top-of-funnel.d.ts.map +1 -0
- package/dist/src/governor/top-of-funnel.js +168 -0
- package/dist/src/governor/top-of-funnel.test.d.ts +2 -0
- package/dist/src/governor/top-of-funnel.test.d.ts.map +1 -0
- package/dist/src/governor/top-of-funnel.test.js +331 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +10 -0
- package/dist/src/linear-cli.d.ts +38 -0
- package/dist/src/linear-cli.d.ts.map +1 -0
- package/dist/src/linear-cli.js +674 -0
- package/dist/src/logger.d.ts +117 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +430 -0
- package/dist/src/manifest/generate.d.ts +20 -0
- package/dist/src/manifest/generate.d.ts.map +1 -0
- package/dist/src/manifest/generate.js +65 -0
- package/dist/src/manifest/index.d.ts +4 -0
- package/dist/src/manifest/index.d.ts.map +1 -0
- package/dist/src/manifest/index.js +2 -0
- package/dist/src/manifest/route-manifest.d.ts +34 -0
- package/dist/src/manifest/route-manifest.d.ts.map +1 -0
- package/dist/src/manifest/route-manifest.js +148 -0
- package/dist/src/orchestrator/activity-emitter.d.ts +119 -0
- package/dist/src/orchestrator/activity-emitter.d.ts.map +1 -0
- package/dist/src/orchestrator/activity-emitter.js +306 -0
- package/dist/src/orchestrator/api-activity-emitter.d.ts +167 -0
- package/dist/src/orchestrator/api-activity-emitter.d.ts.map +1 -0
- package/dist/src/orchestrator/api-activity-emitter.js +417 -0
- package/dist/src/orchestrator/heartbeat-writer.d.ts +57 -0
- package/dist/src/orchestrator/heartbeat-writer.d.ts.map +1 -0
- package/dist/src/orchestrator/heartbeat-writer.js +137 -0
- package/dist/src/orchestrator/index.d.ts +20 -0
- package/dist/src/orchestrator/index.d.ts.map +1 -0
- package/dist/src/orchestrator/index.js +22 -0
- package/dist/src/orchestrator/log-analyzer.d.ts +160 -0
- package/dist/src/orchestrator/log-analyzer.d.ts.map +1 -0
- package/dist/src/orchestrator/log-analyzer.js +572 -0
- package/dist/src/orchestrator/log-config.d.ts +39 -0
- package/dist/src/orchestrator/log-config.d.ts.map +1 -0
- package/dist/src/orchestrator/log-config.js +45 -0
- package/dist/src/orchestrator/orchestrator.d.ts +316 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -0
- package/dist/src/orchestrator/orchestrator.js +3290 -0
- package/dist/src/orchestrator/parse-work-result.d.ts +16 -0
- package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -0
- package/dist/src/orchestrator/parse-work-result.js +135 -0
- package/dist/src/orchestrator/parse-work-result.test.d.ts +2 -0
- package/dist/src/orchestrator/parse-work-result.test.d.ts.map +1 -0
- package/dist/src/orchestrator/parse-work-result.test.js +234 -0
- package/dist/src/orchestrator/progress-logger.d.ts +72 -0
- package/dist/src/orchestrator/progress-logger.d.ts.map +1 -0
- package/dist/src/orchestrator/progress-logger.js +135 -0
- package/dist/src/orchestrator/session-logger.d.ts +159 -0
- package/dist/src/orchestrator/session-logger.d.ts.map +1 -0
- package/dist/src/orchestrator/session-logger.js +275 -0
- package/dist/src/orchestrator/state-recovery.d.ts +96 -0
- package/dist/src/orchestrator/state-recovery.d.ts.map +1 -0
- package/dist/src/orchestrator/state-recovery.js +302 -0
- package/dist/src/orchestrator/state-types.d.ts +165 -0
- package/dist/src/orchestrator/state-types.d.ts.map +1 -0
- package/dist/src/orchestrator/state-types.js +7 -0
- package/dist/src/orchestrator/stream-parser.d.ts +151 -0
- package/dist/src/orchestrator/stream-parser.d.ts.map +1 -0
- package/dist/src/orchestrator/stream-parser.js +137 -0
- package/dist/src/orchestrator/types.d.ts +232 -0
- package/dist/src/orchestrator/types.d.ts.map +1 -0
- package/dist/src/orchestrator/types.js +4 -0
- package/dist/src/orchestrator/validate-git-remote.test.d.ts +2 -0
- package/dist/src/orchestrator/validate-git-remote.test.d.ts.map +1 -0
- package/dist/src/orchestrator/validate-git-remote.test.js +61 -0
- package/dist/src/providers/a2a-auth.d.ts +81 -0
- package/dist/src/providers/a2a-auth.d.ts.map +1 -0
- package/dist/src/providers/a2a-auth.js +188 -0
- package/dist/src/providers/a2a-auth.test.d.ts +2 -0
- package/dist/src/providers/a2a-auth.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-auth.test.js +232 -0
- package/dist/src/providers/a2a-provider.d.ts +254 -0
- package/dist/src/providers/a2a-provider.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.integration.test.d.ts +9 -0
- package/dist/src/providers/a2a-provider.integration.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.integration.test.js +665 -0
- package/dist/src/providers/a2a-provider.js +811 -0
- package/dist/src/providers/a2a-provider.test.d.ts +2 -0
- package/dist/src/providers/a2a-provider.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.test.js +681 -0
- package/dist/src/providers/amp-provider.d.ts +20 -0
- package/dist/src/providers/amp-provider.d.ts.map +1 -0
- package/dist/src/providers/amp-provider.js +24 -0
- package/dist/src/providers/claude-provider.d.ts +18 -0
- package/dist/src/providers/claude-provider.d.ts.map +1 -0
- package/dist/src/providers/claude-provider.js +437 -0
- package/dist/src/providers/codex-provider.d.ts +133 -0
- package/dist/src/providers/codex-provider.d.ts.map +1 -0
- package/dist/src/providers/codex-provider.js +381 -0
- package/dist/src/providers/codex-provider.test.d.ts +2 -0
- package/dist/src/providers/codex-provider.test.d.ts.map +1 -0
- package/dist/src/providers/codex-provider.test.js +387 -0
- package/dist/src/providers/index.d.ts +44 -0
- package/dist/src/providers/index.d.ts.map +1 -0
- package/dist/src/providers/index.js +85 -0
- package/dist/src/providers/spring-ai-provider.d.ts +90 -0
- package/dist/src/providers/spring-ai-provider.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.integration.test.d.ts +13 -0
- package/dist/src/providers/spring-ai-provider.integration.test.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.integration.test.js +351 -0
- package/dist/src/providers/spring-ai-provider.js +317 -0
- package/dist/src/providers/spring-ai-provider.test.d.ts +2 -0
- package/dist/src/providers/spring-ai-provider.test.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.test.js +200 -0
- package/dist/src/providers/types.d.ts +165 -0
- package/dist/src/providers/types.d.ts.map +1 -0
- package/dist/src/providers/types.js +13 -0
- package/dist/src/templates/adapters.d.ts +51 -0
- package/dist/src/templates/adapters.d.ts.map +1 -0
- package/dist/src/templates/adapters.js +104 -0
- package/dist/src/templates/adapters.test.d.ts +2 -0
- package/dist/src/templates/adapters.test.d.ts.map +1 -0
- package/dist/src/templates/adapters.test.js +165 -0
- package/dist/src/templates/agent-definition.d.ts +85 -0
- package/dist/src/templates/agent-definition.d.ts.map +1 -0
- package/dist/src/templates/agent-definition.js +97 -0
- package/dist/src/templates/agent-definition.test.d.ts +2 -0
- package/dist/src/templates/agent-definition.test.d.ts.map +1 -0
- package/dist/src/templates/agent-definition.test.js +209 -0
- package/dist/src/templates/index.d.ts +14 -0
- package/dist/src/templates/index.d.ts.map +1 -0
- package/dist/src/templates/index.js +11 -0
- package/dist/src/templates/loader.d.ts +41 -0
- package/dist/src/templates/loader.d.ts.map +1 -0
- package/dist/src/templates/loader.js +114 -0
- package/dist/src/templates/registry.d.ts +80 -0
- package/dist/src/templates/registry.d.ts.map +1 -0
- package/dist/src/templates/registry.js +177 -0
- package/dist/src/templates/registry.test.d.ts +2 -0
- package/dist/src/templates/registry.test.d.ts.map +1 -0
- package/dist/src/templates/registry.test.js +198 -0
- package/dist/src/templates/renderer.d.ts +29 -0
- package/dist/src/templates/renderer.d.ts.map +1 -0
- package/dist/src/templates/renderer.js +35 -0
- package/dist/src/templates/strategy-templates.test.d.ts +2 -0
- package/dist/src/templates/strategy-templates.test.d.ts.map +1 -0
- package/dist/src/templates/strategy-templates.test.js +619 -0
- package/dist/src/templates/types.d.ts +233 -0
- package/dist/src/templates/types.d.ts.map +1 -0
- package/dist/src/templates/types.js +127 -0
- package/dist/src/templates/types.test.d.ts +2 -0
- package/dist/src/templates/types.test.d.ts.map +1 -0
- package/dist/src/templates/types.test.js +232 -0
- package/dist/src/tools/index.d.ts +6 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +3 -0
- package/dist/src/tools/linear-runner.d.ts +34 -0
- package/dist/src/tools/linear-runner.d.ts.map +1 -0
- package/dist/src/tools/linear-runner.js +700 -0
- package/dist/src/tools/plugins/linear.d.ts +9 -0
- package/dist/src/tools/plugins/linear.d.ts.map +1 -0
- package/dist/src/tools/plugins/linear.js +138 -0
- package/dist/src/tools/registry.d.ts +9 -0
- package/dist/src/tools/registry.d.ts.map +1 -0
- package/dist/src/tools/registry.js +18 -0
- package/dist/src/tools/types.d.ts +18 -0
- package/dist/src/tools/types.d.ts.map +1 -0
- package/dist/src/tools/types.js +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,811 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A (Agent-to-Agent) Client Provider
|
|
3
|
+
*
|
|
4
|
+
* Invokes external A2A-compliant agents over HTTP using the Google A2A protocol.
|
|
5
|
+
* Uses JSON-RPC 2.0 for requests and SSE streaming for responses.
|
|
6
|
+
*
|
|
7
|
+
* A2A Protocol overview:
|
|
8
|
+
* - Agent discovery via GET /.well-known/agent-card.json
|
|
9
|
+
* - Task creation via POST /a2a with JSON-RPC method "message/send" or "message/stream"
|
|
10
|
+
* - Task lifecycle: submitted → working → completed/failed/canceled
|
|
11
|
+
* - Input-required is a paused state requesting user input
|
|
12
|
+
* - SSE events: TaskStatusUpdateEvent, TaskArtifactUpdateEvent
|
|
13
|
+
*
|
|
14
|
+
* Env vars:
|
|
15
|
+
* A2A_AGENT_URL — base URL of the A2A agent (e.g., https://agent.example.com)
|
|
16
|
+
* A2A_AGENT_URL_{WORKTYPE} — work-type-specific override (e.g., A2A_AGENT_URL_RESEARCH)
|
|
17
|
+
* A2A_API_KEY — API key for authentication (sent as x-api-key header)
|
|
18
|
+
* A2A_BEARER_TOKEN — Bearer token for authentication (sent as Authorization header)
|
|
19
|
+
*/
|
|
20
|
+
import { randomUUID } from 'crypto';
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Agent Card Discovery
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
/**
|
|
25
|
+
* Fetch and parse the A2A Agent Card from the well-known endpoint.
|
|
26
|
+
*
|
|
27
|
+
* @param baseUrl - Base URL of the A2A agent (e.g., "https://agent.example.com")
|
|
28
|
+
* @returns Parsed agent card
|
|
29
|
+
* @throws Error if the fetch fails or the response is not valid JSON
|
|
30
|
+
*/
|
|
31
|
+
export async function fetchAgentCard(baseUrl) {
|
|
32
|
+
const url = `${baseUrl.replace(/\/+$/, '')}/.well-known/agent-card.json`;
|
|
33
|
+
const response = await fetch(url, {
|
|
34
|
+
method: 'GET',
|
|
35
|
+
headers: { 'Accept': 'application/json' },
|
|
36
|
+
});
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
throw new Error(`Failed to fetch A2A agent card from ${url}: ${response.status} ${response.statusText}`);
|
|
39
|
+
}
|
|
40
|
+
const card = (await response.json());
|
|
41
|
+
if (!card.name || !card.url) {
|
|
42
|
+
throw new Error(`Invalid A2A agent card from ${url}: missing required fields "name" and/or "url"`);
|
|
43
|
+
}
|
|
44
|
+
return card;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Extract text content from an array of A2A parts.
|
|
48
|
+
* Concatenates all text parts, includes data parts as JSON, and notes file parts.
|
|
49
|
+
*/
|
|
50
|
+
function extractTextFromParts(parts) {
|
|
51
|
+
const texts = [];
|
|
52
|
+
for (const part of parts) {
|
|
53
|
+
switch (part.type) {
|
|
54
|
+
case 'text':
|
|
55
|
+
texts.push(part.text);
|
|
56
|
+
break;
|
|
57
|
+
case 'data':
|
|
58
|
+
texts.push(JSON.stringify(part.data));
|
|
59
|
+
break;
|
|
60
|
+
case 'file':
|
|
61
|
+
texts.push(`[File: ${part.file.name ?? part.file.uri ?? 'unnamed'}]`);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return texts.join('\n');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Map a single A2A task event to one or more normalized AgentEvents.
|
|
69
|
+
* Exported for unit testing — the AgentHandle uses this internally.
|
|
70
|
+
*
|
|
71
|
+
* @param event - The A2A SSE task event
|
|
72
|
+
* @param state - Mutable state accumulator for the session
|
|
73
|
+
* @returns Array of normalized AgentEvent objects
|
|
74
|
+
*/
|
|
75
|
+
export function mapA2aTaskEvent(event, state) {
|
|
76
|
+
switch (event.type) {
|
|
77
|
+
case 'TaskStatusUpdate': {
|
|
78
|
+
// Track task and session IDs
|
|
79
|
+
if (event.id && !state.taskId) {
|
|
80
|
+
state.taskId = event.id;
|
|
81
|
+
}
|
|
82
|
+
if (event.sessionId && !state.sessionId) {
|
|
83
|
+
state.sessionId = event.sessionId;
|
|
84
|
+
}
|
|
85
|
+
const status = event.status;
|
|
86
|
+
switch (status.state) {
|
|
87
|
+
case 'submitted': {
|
|
88
|
+
// Task has been created — emit init event
|
|
89
|
+
const sessionId = event.sessionId ?? event.id;
|
|
90
|
+
state.sessionId = sessionId;
|
|
91
|
+
state.taskId = event.id;
|
|
92
|
+
return [{
|
|
93
|
+
type: 'init',
|
|
94
|
+
sessionId,
|
|
95
|
+
raw: event,
|
|
96
|
+
}];
|
|
97
|
+
}
|
|
98
|
+
case 'working': {
|
|
99
|
+
state.turnCount++;
|
|
100
|
+
const events = [];
|
|
101
|
+
// If there is a message with the status, emit the text
|
|
102
|
+
if (status.message && status.message.parts.length > 0) {
|
|
103
|
+
const text = extractTextFromParts(status.message.parts);
|
|
104
|
+
if (text) {
|
|
105
|
+
events.push({
|
|
106
|
+
type: 'assistant_text',
|
|
107
|
+
text,
|
|
108
|
+
raw: event,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// If no message content, emit a system event noting work is in progress
|
|
113
|
+
if (events.length === 0) {
|
|
114
|
+
events.push({
|
|
115
|
+
type: 'system',
|
|
116
|
+
subtype: 'working',
|
|
117
|
+
message: `Task ${event.id} is working (turn ${state.turnCount})`,
|
|
118
|
+
raw: event,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return events;
|
|
122
|
+
}
|
|
123
|
+
case 'input-required': {
|
|
124
|
+
const message = status.message
|
|
125
|
+
? extractTextFromParts(status.message.parts)
|
|
126
|
+
: 'Agent requires additional input';
|
|
127
|
+
return [{
|
|
128
|
+
type: 'system',
|
|
129
|
+
subtype: 'input_required',
|
|
130
|
+
message,
|
|
131
|
+
raw: event,
|
|
132
|
+
}];
|
|
133
|
+
}
|
|
134
|
+
case 'completed': {
|
|
135
|
+
const message = status.message
|
|
136
|
+
? extractTextFromParts(status.message.parts)
|
|
137
|
+
: undefined;
|
|
138
|
+
return [{
|
|
139
|
+
type: 'result',
|
|
140
|
+
success: true,
|
|
141
|
+
message,
|
|
142
|
+
cost: {
|
|
143
|
+
inputTokens: state.totalInputTokens || undefined,
|
|
144
|
+
outputTokens: state.totalOutputTokens || undefined,
|
|
145
|
+
numTurns: state.turnCount || undefined,
|
|
146
|
+
},
|
|
147
|
+
raw: event,
|
|
148
|
+
}];
|
|
149
|
+
}
|
|
150
|
+
case 'failed': {
|
|
151
|
+
const errorMessage = status.message
|
|
152
|
+
? extractTextFromParts(status.message.parts)
|
|
153
|
+
: 'Task failed';
|
|
154
|
+
return [{
|
|
155
|
+
type: 'result',
|
|
156
|
+
success: false,
|
|
157
|
+
errors: [errorMessage],
|
|
158
|
+
errorSubtype: 'task_failed',
|
|
159
|
+
raw: event,
|
|
160
|
+
}];
|
|
161
|
+
}
|
|
162
|
+
case 'canceled': {
|
|
163
|
+
const cancelMessage = status.message
|
|
164
|
+
? extractTextFromParts(status.message.parts)
|
|
165
|
+
: 'Task canceled';
|
|
166
|
+
return [{
|
|
167
|
+
type: 'result',
|
|
168
|
+
success: false,
|
|
169
|
+
errors: [cancelMessage],
|
|
170
|
+
errorSubtype: 'canceled',
|
|
171
|
+
raw: event,
|
|
172
|
+
}];
|
|
173
|
+
}
|
|
174
|
+
default:
|
|
175
|
+
return [{
|
|
176
|
+
type: 'system',
|
|
177
|
+
subtype: 'unknown',
|
|
178
|
+
message: `Unknown A2A task status: ${status.state}`,
|
|
179
|
+
raw: event,
|
|
180
|
+
}];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
case 'TaskArtifactUpdate': {
|
|
184
|
+
// Track task and session IDs
|
|
185
|
+
if (event.id && !state.taskId) {
|
|
186
|
+
state.taskId = event.id;
|
|
187
|
+
}
|
|
188
|
+
if (event.sessionId && !state.sessionId) {
|
|
189
|
+
state.sessionId = event.sessionId;
|
|
190
|
+
}
|
|
191
|
+
const artifact = event.artifact;
|
|
192
|
+
const text = extractTextFromParts(artifact.parts);
|
|
193
|
+
return [{
|
|
194
|
+
type: 'assistant_text',
|
|
195
|
+
text: text || `[Artifact: ${artifact.name ?? 'unnamed'}]`,
|
|
196
|
+
raw: event,
|
|
197
|
+
}];
|
|
198
|
+
}
|
|
199
|
+
default:
|
|
200
|
+
return [{
|
|
201
|
+
type: 'system',
|
|
202
|
+
subtype: 'unknown',
|
|
203
|
+
message: `Unhandled A2A event type: ${event.type}`,
|
|
204
|
+
raw: event,
|
|
205
|
+
}];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// Provider
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
export class A2aProvider {
|
|
212
|
+
name = 'a2a';
|
|
213
|
+
spawn(config) {
|
|
214
|
+
return this.createHandle(config);
|
|
215
|
+
}
|
|
216
|
+
resume(sessionId, config) {
|
|
217
|
+
return this.createHandle(config, sessionId);
|
|
218
|
+
}
|
|
219
|
+
createHandle(config, resumeSessionId) {
|
|
220
|
+
const abortController = config.abortController;
|
|
221
|
+
// Resolve the A2A agent URL — check work-type-specific env vars first
|
|
222
|
+
const agentUrl = resolveAgentUrl(config.env);
|
|
223
|
+
if (!agentUrl) {
|
|
224
|
+
return new A2aAgentHandle({
|
|
225
|
+
agentUrl: '',
|
|
226
|
+
abortController,
|
|
227
|
+
initError: 'A2A_AGENT_URL environment variable is not set. ' +
|
|
228
|
+
'Set A2A_AGENT_URL or A2A_AGENT_URL_{WORKTYPE} to the base URL of the A2A agent.',
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
// Resolve authentication headers
|
|
232
|
+
const authHeaders = resolveAuthHeaders(config.env);
|
|
233
|
+
// Build the initial JSON-RPC params
|
|
234
|
+
const messageParams = {
|
|
235
|
+
message: {
|
|
236
|
+
role: 'user',
|
|
237
|
+
parts: [{ type: 'text', text: config.prompt }],
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
// If resuming, include the existing session/task ID
|
|
241
|
+
if (resumeSessionId) {
|
|
242
|
+
messageParams.sessionId = resumeSessionId;
|
|
243
|
+
}
|
|
244
|
+
return new A2aAgentHandle({
|
|
245
|
+
agentUrl,
|
|
246
|
+
abortController,
|
|
247
|
+
authHeaders,
|
|
248
|
+
initialParams: messageParams,
|
|
249
|
+
resumeSessionId: resumeSessionId ?? null,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
class A2aAgentHandle {
|
|
254
|
+
sessionId = null;
|
|
255
|
+
agentUrl;
|
|
256
|
+
abortController;
|
|
257
|
+
authHeaders;
|
|
258
|
+
initialParams;
|
|
259
|
+
initError;
|
|
260
|
+
mapperState = {
|
|
261
|
+
sessionId: null,
|
|
262
|
+
taskId: null,
|
|
263
|
+
totalInputTokens: 0,
|
|
264
|
+
totalOutputTokens: 0,
|
|
265
|
+
turnCount: 0,
|
|
266
|
+
};
|
|
267
|
+
constructor(options) {
|
|
268
|
+
this.agentUrl = options.agentUrl;
|
|
269
|
+
this.abortController = options.abortController;
|
|
270
|
+
this.authHeaders = options.authHeaders ?? {};
|
|
271
|
+
this.initialParams = options.initialParams;
|
|
272
|
+
this.initError = options.initError;
|
|
273
|
+
if (options.resumeSessionId) {
|
|
274
|
+
this.sessionId = options.resumeSessionId;
|
|
275
|
+
this.mapperState.sessionId = options.resumeSessionId;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
get stream() {
|
|
279
|
+
return this.createEventStream();
|
|
280
|
+
}
|
|
281
|
+
async injectMessage(text) {
|
|
282
|
+
const params = {
|
|
283
|
+
message: {
|
|
284
|
+
role: 'user',
|
|
285
|
+
parts: [{ type: 'text', text }],
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
// Include session/task IDs if we have them
|
|
289
|
+
if (this.mapperState.sessionId) {
|
|
290
|
+
params.sessionId = this.mapperState.sessionId;
|
|
291
|
+
}
|
|
292
|
+
if (this.mapperState.taskId) {
|
|
293
|
+
params.taskId = this.mapperState.taskId;
|
|
294
|
+
}
|
|
295
|
+
await this.sendJsonRpc('message/send', params);
|
|
296
|
+
}
|
|
297
|
+
async stop() {
|
|
298
|
+
// Send cancel request if we have a task ID
|
|
299
|
+
if (this.mapperState.taskId) {
|
|
300
|
+
try {
|
|
301
|
+
await this.sendJsonRpc('tasks/cancel', {
|
|
302
|
+
id: this.mapperState.taskId,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
// Best-effort cancellation — ignore errors
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
this.abortController.abort();
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Send a JSON-RPC 2.0 request to the A2A agent endpoint.
|
|
313
|
+
*/
|
|
314
|
+
async sendJsonRpc(method, params) {
|
|
315
|
+
const rpcUrl = `${this.agentUrl.replace(/\/+$/, '')}/a2a`;
|
|
316
|
+
const request = {
|
|
317
|
+
jsonrpc: '2.0',
|
|
318
|
+
id: randomUUID(),
|
|
319
|
+
method,
|
|
320
|
+
params,
|
|
321
|
+
};
|
|
322
|
+
const response = await fetch(rpcUrl, {
|
|
323
|
+
method: 'POST',
|
|
324
|
+
headers: {
|
|
325
|
+
'Content-Type': 'application/json',
|
|
326
|
+
'Accept': 'text/event-stream, application/json',
|
|
327
|
+
...this.authHeaders,
|
|
328
|
+
},
|
|
329
|
+
body: JSON.stringify(request),
|
|
330
|
+
signal: this.abortController.signal,
|
|
331
|
+
});
|
|
332
|
+
if (!response.ok) {
|
|
333
|
+
const body = await response.text().catch(() => '');
|
|
334
|
+
throw new Error(`A2A JSON-RPC request failed: ${response.status} ${response.statusText}` +
|
|
335
|
+
(body ? ` — ${body}` : ''));
|
|
336
|
+
}
|
|
337
|
+
return response;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Create the async event stream by sending the initial request and parsing
|
|
341
|
+
* either SSE (for message/stream) or JSON (for message/send) response.
|
|
342
|
+
*/
|
|
343
|
+
async *createEventStream() {
|
|
344
|
+
// Handle init-time errors (e.g., missing agent URL)
|
|
345
|
+
if (this.initError) {
|
|
346
|
+
yield {
|
|
347
|
+
type: 'error',
|
|
348
|
+
message: this.initError,
|
|
349
|
+
raw: null,
|
|
350
|
+
};
|
|
351
|
+
yield {
|
|
352
|
+
type: 'result',
|
|
353
|
+
success: false,
|
|
354
|
+
errors: [this.initError],
|
|
355
|
+
errorSubtype: 'configuration_error',
|
|
356
|
+
raw: null,
|
|
357
|
+
};
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (!this.initialParams) {
|
|
361
|
+
yield {
|
|
362
|
+
type: 'error',
|
|
363
|
+
message: 'No initial message params configured',
|
|
364
|
+
raw: null,
|
|
365
|
+
};
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
let response;
|
|
369
|
+
let useStreaming = true;
|
|
370
|
+
try {
|
|
371
|
+
// Try streaming first (message/stream)
|
|
372
|
+
response = await this.sendJsonRpc('message/stream', this.initialParams);
|
|
373
|
+
}
|
|
374
|
+
catch (err) {
|
|
375
|
+
if (this.abortController.signal.aborted) {
|
|
376
|
+
yield {
|
|
377
|
+
type: 'result',
|
|
378
|
+
success: false,
|
|
379
|
+
errors: ['Request aborted'],
|
|
380
|
+
errorSubtype: 'aborted',
|
|
381
|
+
raw: null,
|
|
382
|
+
};
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
// Fall back to non-streaming (message/send)
|
|
386
|
+
try {
|
|
387
|
+
response = await this.sendJsonRpc('message/send', this.initialParams);
|
|
388
|
+
useStreaming = false;
|
|
389
|
+
}
|
|
390
|
+
catch (sendErr) {
|
|
391
|
+
if (this.abortController.signal.aborted) {
|
|
392
|
+
yield {
|
|
393
|
+
type: 'result',
|
|
394
|
+
success: false,
|
|
395
|
+
errors: ['Request aborted'],
|
|
396
|
+
errorSubtype: 'aborted',
|
|
397
|
+
raw: null,
|
|
398
|
+
};
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const errorMessage = sendErr instanceof Error
|
|
402
|
+
? sendErr.message
|
|
403
|
+
: 'Failed to connect to A2A agent';
|
|
404
|
+
yield {
|
|
405
|
+
type: 'error',
|
|
406
|
+
message: errorMessage,
|
|
407
|
+
raw: sendErr,
|
|
408
|
+
};
|
|
409
|
+
yield {
|
|
410
|
+
type: 'result',
|
|
411
|
+
success: false,
|
|
412
|
+
errors: [errorMessage],
|
|
413
|
+
errorSubtype: 'connection_error',
|
|
414
|
+
raw: null,
|
|
415
|
+
};
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
420
|
+
if (useStreaming && contentType.includes('text/event-stream')) {
|
|
421
|
+
// Parse SSE stream
|
|
422
|
+
yield* this.parseSSEStream(response);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
// Parse single JSON response
|
|
426
|
+
yield* this.parseSingleResponse(response);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Parse an SSE event stream from the response body.
|
|
431
|
+
* Each SSE event has an optional `event:` field and a `data:` field with JSON.
|
|
432
|
+
*/
|
|
433
|
+
async *parseSSEStream(response) {
|
|
434
|
+
const body = response.body;
|
|
435
|
+
if (!body) {
|
|
436
|
+
yield {
|
|
437
|
+
type: 'error',
|
|
438
|
+
message: 'A2A response has no body',
|
|
439
|
+
raw: null,
|
|
440
|
+
};
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const reader = body.getReader();
|
|
444
|
+
const decoder = new TextDecoder();
|
|
445
|
+
let buffer = '';
|
|
446
|
+
let currentEventType = '';
|
|
447
|
+
let currentData = '';
|
|
448
|
+
let hasResult = false;
|
|
449
|
+
try {
|
|
450
|
+
while (true) {
|
|
451
|
+
const { done, value } = await reader.read();
|
|
452
|
+
if (done)
|
|
453
|
+
break;
|
|
454
|
+
buffer += decoder.decode(value, { stream: true });
|
|
455
|
+
// Process complete lines
|
|
456
|
+
const lines = buffer.split('\n');
|
|
457
|
+
// Keep the last potentially incomplete line in the buffer
|
|
458
|
+
buffer = lines.pop() ?? '';
|
|
459
|
+
for (const line of lines) {
|
|
460
|
+
if (line.startsWith('event:')) {
|
|
461
|
+
currentEventType = line.slice(6).trim();
|
|
462
|
+
}
|
|
463
|
+
else if (line.startsWith('data:')) {
|
|
464
|
+
currentData = line.slice(5).trim();
|
|
465
|
+
}
|
|
466
|
+
else if (line === '' && currentData) {
|
|
467
|
+
// Empty line signals end of SSE event
|
|
468
|
+
let taskEvent;
|
|
469
|
+
try {
|
|
470
|
+
const parsed = JSON.parse(currentData);
|
|
471
|
+
// Use the event type from the SSE `event:` field if present,
|
|
472
|
+
// otherwise use the `type` field from the JSON data
|
|
473
|
+
if (currentEventType) {
|
|
474
|
+
parsed.type = currentEventType;
|
|
475
|
+
}
|
|
476
|
+
taskEvent = parsed;
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
// Non-JSON SSE data — emit as system event
|
|
480
|
+
yield {
|
|
481
|
+
type: 'system',
|
|
482
|
+
subtype: 'raw_output',
|
|
483
|
+
message: currentData,
|
|
484
|
+
raw: currentData,
|
|
485
|
+
};
|
|
486
|
+
currentEventType = '';
|
|
487
|
+
currentData = '';
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
const mapped = mapA2aTaskEvent(taskEvent, this.mapperState);
|
|
491
|
+
for (const agentEvent of mapped) {
|
|
492
|
+
if (agentEvent.type === 'init') {
|
|
493
|
+
this.sessionId = this.mapperState.sessionId;
|
|
494
|
+
}
|
|
495
|
+
if (agentEvent.type === 'result') {
|
|
496
|
+
hasResult = true;
|
|
497
|
+
}
|
|
498
|
+
yield agentEvent;
|
|
499
|
+
}
|
|
500
|
+
currentEventType = '';
|
|
501
|
+
currentData = '';
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
// Process any remaining data in the buffer
|
|
506
|
+
if (currentData) {
|
|
507
|
+
try {
|
|
508
|
+
const parsed = JSON.parse(currentData);
|
|
509
|
+
if (currentEventType) {
|
|
510
|
+
parsed.type = currentEventType;
|
|
511
|
+
}
|
|
512
|
+
const taskEvent = parsed;
|
|
513
|
+
const mapped = mapA2aTaskEvent(taskEvent, this.mapperState);
|
|
514
|
+
for (const agentEvent of mapped) {
|
|
515
|
+
if (agentEvent.type === 'init') {
|
|
516
|
+
this.sessionId = this.mapperState.sessionId;
|
|
517
|
+
}
|
|
518
|
+
if (agentEvent.type === 'result') {
|
|
519
|
+
hasResult = true;
|
|
520
|
+
}
|
|
521
|
+
yield agentEvent;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
catch {
|
|
525
|
+
// Ignore trailing non-JSON data
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
catch (err) {
|
|
530
|
+
if (this.abortController.signal.aborted) {
|
|
531
|
+
if (!hasResult) {
|
|
532
|
+
yield {
|
|
533
|
+
type: 'result',
|
|
534
|
+
success: false,
|
|
535
|
+
errors: ['Stream aborted'],
|
|
536
|
+
errorSubtype: 'aborted',
|
|
537
|
+
raw: null,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const errorMessage = err instanceof Error ? err.message : 'SSE stream error';
|
|
543
|
+
yield {
|
|
544
|
+
type: 'error',
|
|
545
|
+
message: errorMessage,
|
|
546
|
+
raw: err,
|
|
547
|
+
};
|
|
548
|
+
if (!hasResult) {
|
|
549
|
+
yield {
|
|
550
|
+
type: 'result',
|
|
551
|
+
success: false,
|
|
552
|
+
errors: [errorMessage],
|
|
553
|
+
errorSubtype: 'stream_error',
|
|
554
|
+
raw: null,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
finally {
|
|
559
|
+
reader.releaseLock();
|
|
560
|
+
}
|
|
561
|
+
// If we never got a result event, synthesize a success (stream ended cleanly)
|
|
562
|
+
if (!hasResult) {
|
|
563
|
+
yield {
|
|
564
|
+
type: 'result',
|
|
565
|
+
success: true,
|
|
566
|
+
cost: {
|
|
567
|
+
inputTokens: this.mapperState.totalInputTokens || undefined,
|
|
568
|
+
outputTokens: this.mapperState.totalOutputTokens || undefined,
|
|
569
|
+
numTurns: this.mapperState.turnCount || undefined,
|
|
570
|
+
},
|
|
571
|
+
raw: { streamEnded: true },
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Parse a single JSON response (non-streaming message/send).
|
|
577
|
+
* The response contains a complete A2A Task object.
|
|
578
|
+
*/
|
|
579
|
+
async *parseSingleResponse(response) {
|
|
580
|
+
let body;
|
|
581
|
+
try {
|
|
582
|
+
body = await response.text();
|
|
583
|
+
}
|
|
584
|
+
catch (err) {
|
|
585
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to read response body';
|
|
586
|
+
yield {
|
|
587
|
+
type: 'error',
|
|
588
|
+
message: errorMessage,
|
|
589
|
+
raw: err,
|
|
590
|
+
};
|
|
591
|
+
yield {
|
|
592
|
+
type: 'result',
|
|
593
|
+
success: false,
|
|
594
|
+
errors: [errorMessage],
|
|
595
|
+
errorSubtype: 'response_error',
|
|
596
|
+
raw: null,
|
|
597
|
+
};
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
let rpcResponse;
|
|
601
|
+
try {
|
|
602
|
+
rpcResponse = JSON.parse(body);
|
|
603
|
+
}
|
|
604
|
+
catch {
|
|
605
|
+
yield {
|
|
606
|
+
type: 'error',
|
|
607
|
+
message: `Invalid JSON response from A2A agent: ${body.slice(0, 200)}`,
|
|
608
|
+
raw: body,
|
|
609
|
+
};
|
|
610
|
+
yield {
|
|
611
|
+
type: 'result',
|
|
612
|
+
success: false,
|
|
613
|
+
errors: ['Invalid JSON response from A2A agent'],
|
|
614
|
+
errorSubtype: 'parse_error',
|
|
615
|
+
raw: null,
|
|
616
|
+
};
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
// Check for JSON-RPC error
|
|
620
|
+
if (rpcResponse.error) {
|
|
621
|
+
yield {
|
|
622
|
+
type: 'error',
|
|
623
|
+
message: rpcResponse.error.message,
|
|
624
|
+
code: String(rpcResponse.error.code),
|
|
625
|
+
raw: rpcResponse.error,
|
|
626
|
+
};
|
|
627
|
+
yield {
|
|
628
|
+
type: 'result',
|
|
629
|
+
success: false,
|
|
630
|
+
errors: [rpcResponse.error.message],
|
|
631
|
+
errorSubtype: 'jsonrpc_error',
|
|
632
|
+
raw: rpcResponse,
|
|
633
|
+
};
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
// Parse the result as an A2A Task
|
|
637
|
+
const task = rpcResponse.result;
|
|
638
|
+
if (!task) {
|
|
639
|
+
yield {
|
|
640
|
+
type: 'error',
|
|
641
|
+
message: 'A2A response has no result',
|
|
642
|
+
raw: rpcResponse,
|
|
643
|
+
};
|
|
644
|
+
yield {
|
|
645
|
+
type: 'result',
|
|
646
|
+
success: false,
|
|
647
|
+
errors: ['A2A response has no result'],
|
|
648
|
+
errorSubtype: 'empty_result',
|
|
649
|
+
raw: rpcResponse,
|
|
650
|
+
};
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
// Set session/task IDs
|
|
654
|
+
this.mapperState.taskId = task.id;
|
|
655
|
+
this.mapperState.sessionId = task.sessionId ?? task.id;
|
|
656
|
+
this.sessionId = this.mapperState.sessionId;
|
|
657
|
+
// Emit init event
|
|
658
|
+
yield {
|
|
659
|
+
type: 'init',
|
|
660
|
+
sessionId: this.mapperState.sessionId,
|
|
661
|
+
raw: task,
|
|
662
|
+
};
|
|
663
|
+
// Emit artifact text if present
|
|
664
|
+
if (task.artifacts) {
|
|
665
|
+
for (const artifact of task.artifacts) {
|
|
666
|
+
const text = extractTextFromParts(artifact.parts);
|
|
667
|
+
if (text) {
|
|
668
|
+
yield {
|
|
669
|
+
type: 'assistant_text',
|
|
670
|
+
text,
|
|
671
|
+
raw: artifact,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
// Emit status message text if present
|
|
677
|
+
if (task.status.message && task.status.message.parts.length > 0) {
|
|
678
|
+
const statusText = extractTextFromParts(task.status.message.parts);
|
|
679
|
+
if (statusText) {
|
|
680
|
+
yield {
|
|
681
|
+
type: 'assistant_text',
|
|
682
|
+
text: statusText,
|
|
683
|
+
raw: task.status,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
// Emit final result based on task status
|
|
688
|
+
switch (task.status.state) {
|
|
689
|
+
case 'completed':
|
|
690
|
+
yield {
|
|
691
|
+
type: 'result',
|
|
692
|
+
success: true,
|
|
693
|
+
message: task.artifacts
|
|
694
|
+
? extractTextFromParts(task.artifacts[task.artifacts.length - 1].parts)
|
|
695
|
+
: undefined,
|
|
696
|
+
cost: {
|
|
697
|
+
inputTokens: this.mapperState.totalInputTokens || undefined,
|
|
698
|
+
outputTokens: this.mapperState.totalOutputTokens || undefined,
|
|
699
|
+
numTurns: this.mapperState.turnCount || undefined,
|
|
700
|
+
},
|
|
701
|
+
raw: task,
|
|
702
|
+
};
|
|
703
|
+
break;
|
|
704
|
+
case 'failed':
|
|
705
|
+
yield {
|
|
706
|
+
type: 'result',
|
|
707
|
+
success: false,
|
|
708
|
+
errors: [task.status.message
|
|
709
|
+
? extractTextFromParts(task.status.message.parts)
|
|
710
|
+
: 'Task failed'],
|
|
711
|
+
errorSubtype: 'task_failed',
|
|
712
|
+
raw: task,
|
|
713
|
+
};
|
|
714
|
+
break;
|
|
715
|
+
case 'canceled':
|
|
716
|
+
yield {
|
|
717
|
+
type: 'result',
|
|
718
|
+
success: false,
|
|
719
|
+
errors: [task.status.message
|
|
720
|
+
? extractTextFromParts(task.status.message.parts)
|
|
721
|
+
: 'Task canceled'],
|
|
722
|
+
errorSubtype: 'canceled',
|
|
723
|
+
raw: task,
|
|
724
|
+
};
|
|
725
|
+
break;
|
|
726
|
+
case 'input-required':
|
|
727
|
+
yield {
|
|
728
|
+
type: 'system',
|
|
729
|
+
subtype: 'input_required',
|
|
730
|
+
message: task.status.message
|
|
731
|
+
? extractTextFromParts(task.status.message.parts)
|
|
732
|
+
: 'Agent requires additional input',
|
|
733
|
+
raw: task,
|
|
734
|
+
};
|
|
735
|
+
break;
|
|
736
|
+
default:
|
|
737
|
+
// For submitted/working states, emit as system event
|
|
738
|
+
yield {
|
|
739
|
+
type: 'result',
|
|
740
|
+
success: true,
|
|
741
|
+
cost: {
|
|
742
|
+
inputTokens: this.mapperState.totalInputTokens || undefined,
|
|
743
|
+
outputTokens: this.mapperState.totalOutputTokens || undefined,
|
|
744
|
+
numTurns: this.mapperState.turnCount || undefined,
|
|
745
|
+
},
|
|
746
|
+
raw: task,
|
|
747
|
+
};
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
// ---------------------------------------------------------------------------
|
|
753
|
+
// Helpers
|
|
754
|
+
// ---------------------------------------------------------------------------
|
|
755
|
+
/**
|
|
756
|
+
* Resolve the A2A agent URL from environment variables.
|
|
757
|
+
* Checks work-type-specific overrides first, then falls back to the base URL.
|
|
758
|
+
*
|
|
759
|
+
* Resolution order:
|
|
760
|
+
* 1. A2A_AGENT_URL_{WORKTYPE} from config.env
|
|
761
|
+
* 2. A2A_AGENT_URL from config.env
|
|
762
|
+
* 3. A2A_AGENT_URL_{WORKTYPE} from process.env
|
|
763
|
+
* 4. A2A_AGENT_URL from process.env
|
|
764
|
+
*/
|
|
765
|
+
function resolveAgentUrl(env) {
|
|
766
|
+
// Check for work-type-specific URL in config env
|
|
767
|
+
for (const [key, value] of Object.entries(env)) {
|
|
768
|
+
if (key.startsWith('A2A_AGENT_URL_') && value) {
|
|
769
|
+
return value;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
// Check base URL in config env
|
|
773
|
+
if (env.A2A_AGENT_URL) {
|
|
774
|
+
return env.A2A_AGENT_URL;
|
|
775
|
+
}
|
|
776
|
+
// Check process.env for work-type-specific
|
|
777
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
778
|
+
if (key.startsWith('A2A_AGENT_URL_') && value) {
|
|
779
|
+
return value;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
// Check process.env base URL
|
|
783
|
+
return process.env.A2A_AGENT_URL;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Resolve authentication headers from environment variables.
|
|
787
|
+
* Supports API key and bearer token authentication.
|
|
788
|
+
*/
|
|
789
|
+
function resolveAuthHeaders(env) {
|
|
790
|
+
const headers = {};
|
|
791
|
+
// API key auth
|
|
792
|
+
const apiKey = env.A2A_API_KEY || process.env.A2A_API_KEY;
|
|
793
|
+
if (apiKey) {
|
|
794
|
+
headers['x-api-key'] = apiKey;
|
|
795
|
+
}
|
|
796
|
+
// Bearer token auth
|
|
797
|
+
const bearerToken = env.A2A_BEARER_TOKEN || process.env.A2A_BEARER_TOKEN;
|
|
798
|
+
if (bearerToken) {
|
|
799
|
+
headers['Authorization'] = `Bearer ${bearerToken}`;
|
|
800
|
+
}
|
|
801
|
+
return headers;
|
|
802
|
+
}
|
|
803
|
+
// ---------------------------------------------------------------------------
|
|
804
|
+
// Factory
|
|
805
|
+
// ---------------------------------------------------------------------------
|
|
806
|
+
/**
|
|
807
|
+
* Create a new A2A provider instance.
|
|
808
|
+
*/
|
|
809
|
+
export function createA2aProvider() {
|
|
810
|
+
return new A2aProvider();
|
|
811
|
+
}
|