a2acalling 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/server.js +95 -67
package/README.md
CHANGED
package/package.json
CHANGED
package/src/server.js
CHANGED
|
@@ -85,9 +85,81 @@ console.log(`[a2a] Agent: ${agentContext.name} (${agentContext.owner}'s agent)`)
|
|
|
85
85
|
console.log(`[a2a] API: ${apiConfig ? `${apiConfig.provider} ✓` : 'NOT FOUND ✗'}`);
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
* Call
|
|
88
|
+
* Call agent via OpenClaw sub-agent (full tool access)
|
|
89
89
|
*/
|
|
90
90
|
async function callAgent(message, federationContext) {
|
|
91
|
+
const { execSync } = require('child_process');
|
|
92
|
+
|
|
93
|
+
const callerName = federationContext.caller?.name || 'Unknown Agent';
|
|
94
|
+
const callerOwner = federationContext.caller?.owner || '';
|
|
95
|
+
const ownerInfo = callerOwner ? ` (${callerOwner}'s agent)` : '';
|
|
96
|
+
const tierInfo = federationContext.tier || 'public';
|
|
97
|
+
const topics = federationContext.allowed_topics?.join(', ') || 'general chat';
|
|
98
|
+
const disclosure = federationContext.disclosure || 'minimal';
|
|
99
|
+
|
|
100
|
+
// Build the federation context for the sub-agent
|
|
101
|
+
const prompt = `[A2A Federation Call]
|
|
102
|
+
From: ${callerName}${ownerInfo}
|
|
103
|
+
Access Level: ${tierInfo}
|
|
104
|
+
Topics: ${topics}
|
|
105
|
+
Disclosure: ${disclosure}
|
|
106
|
+
|
|
107
|
+
Message: ${message}
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
Respond to this federated agent call. Be yourself - collaborative but protect private info based on disclosure level. Keep response concise (under 500 chars).`;
|
|
111
|
+
|
|
112
|
+
// Use a unique session ID for this conversation
|
|
113
|
+
const sessionId = `a2a-${federationContext.conversation_id || Date.now()}`;
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
// Escape the prompt for shell (replace quotes and newlines)
|
|
117
|
+
const escapedPrompt = prompt
|
|
118
|
+
.replace(/\\/g, '\\\\')
|
|
119
|
+
.replace(/"/g, '\\"')
|
|
120
|
+
.replace(/\n/g, '\\n')
|
|
121
|
+
.replace(/\r/g, '');
|
|
122
|
+
|
|
123
|
+
// Call openclaw agent to spawn a sub-agent
|
|
124
|
+
const result = execSync(
|
|
125
|
+
`openclaw agent --session-id "${sessionId}" --message "${escapedPrompt}" --timeout 55 2>&1`,
|
|
126
|
+
{
|
|
127
|
+
encoding: 'utf8',
|
|
128
|
+
timeout: 65000,
|
|
129
|
+
maxBuffer: 1024 * 1024,
|
|
130
|
+
cwd: process.env.OPENCLAW_WORKSPACE || '/root/clawd',
|
|
131
|
+
env: { ...process.env, FORCE_COLOR: '0' }
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// Filter out plugin registration messages and return clean response
|
|
136
|
+
const lines = result.split('\n').filter(line =>
|
|
137
|
+
!line.includes('[telegram-topic-tracker]') &&
|
|
138
|
+
!line.includes('Plugin registered') &&
|
|
139
|
+
line.trim()
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const response = lines.join('\n').trim();
|
|
143
|
+
|
|
144
|
+
if (!response || response.includes('error') || response.includes('failed')) {
|
|
145
|
+
console.error('[a2a] Sub-agent returned error, using fallback');
|
|
146
|
+
return await callAgentDirect(message, federationContext);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return response;
|
|
150
|
+
|
|
151
|
+
} catch (err) {
|
|
152
|
+
console.error('[a2a] Sub-agent spawn failed:', err.message);
|
|
153
|
+
|
|
154
|
+
// Fallback to direct API call only if sub-agent completely fails
|
|
155
|
+
return await callAgentDirect(message, federationContext);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Fallback: Call LLM directly via OpenRouter
|
|
161
|
+
*/
|
|
162
|
+
async function callAgentDirect(message, federationContext) {
|
|
91
163
|
if (!apiConfig) {
|
|
92
164
|
return '[Agent configuration error: No API key available]';
|
|
93
165
|
}
|
|
@@ -109,47 +181,26 @@ Disclosure level: ${federationContext.disclosure || 'minimal'}
|
|
|
109
181
|
|
|
110
182
|
Respond naturally as yourself. Be collaborative but protect your owner's private information based on the disclosure level. Keep responses concise.`;
|
|
111
183
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
model,
|
|
121
|
-
max_tokens: 1024,
|
|
122
|
-
messages: [
|
|
123
|
-
{ role: 'system', content: systemPrompt },
|
|
124
|
-
{ role: 'user', content: message }
|
|
125
|
-
]
|
|
126
|
-
})
|
|
127
|
-
: JSON.stringify({
|
|
128
|
-
model,
|
|
129
|
-
max_tokens: 1024,
|
|
130
|
-
system: systemPrompt,
|
|
131
|
-
messages: [{ role: 'user', content: message }]
|
|
132
|
-
});
|
|
184
|
+
const body = JSON.stringify({
|
|
185
|
+
model: 'anthropic/claude-sonnet-4',
|
|
186
|
+
max_tokens: 1024,
|
|
187
|
+
messages: [
|
|
188
|
+
{ role: 'system', content: systemPrompt },
|
|
189
|
+
{ role: 'user', content: message }
|
|
190
|
+
]
|
|
191
|
+
});
|
|
133
192
|
|
|
134
|
-
|
|
135
|
-
|
|
193
|
+
return new Promise((resolve) => {
|
|
194
|
+
const req = https.request({
|
|
195
|
+
hostname: 'openrouter.ai',
|
|
196
|
+
path: '/api/v1/chat/completions',
|
|
197
|
+
method: 'POST',
|
|
198
|
+
headers: {
|
|
136
199
|
'Content-Type': 'application/json',
|
|
137
200
|
'Authorization': `Bearer ${apiConfig.key}`,
|
|
138
201
|
'HTTP-Referer': 'https://openclaw.ai',
|
|
139
202
|
'X-Title': 'A2A Federation'
|
|
140
|
-
}
|
|
141
|
-
: {
|
|
142
|
-
'Content-Type': 'application/json',
|
|
143
|
-
'x-api-key': apiConfig.key,
|
|
144
|
-
'anthropic-version': '2023-06-01'
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
return new Promise((resolve) => {
|
|
148
|
-
const req = https.request({
|
|
149
|
-
hostname,
|
|
150
|
-
path: apiPath,
|
|
151
|
-
method: 'POST',
|
|
152
|
-
headers,
|
|
203
|
+
},
|
|
153
204
|
timeout: 55000
|
|
154
205
|
}, (res) => {
|
|
155
206
|
let data = '';
|
|
@@ -157,44 +208,21 @@ Respond naturally as yourself. Be collaborative but protect your owner's private
|
|
|
157
208
|
res.on('end', () => {
|
|
158
209
|
try {
|
|
159
210
|
const json = JSON.parse(data);
|
|
160
|
-
|
|
161
|
-
// OpenRouter format
|
|
162
211
|
if (json.choices && json.choices[0]?.message?.content) {
|
|
163
212
|
resolve(json.choices[0].message.content);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (json.content && json.content[0]?.text) {
|
|
169
|
-
resolve(json.content[0].text);
|
|
170
|
-
return;
|
|
213
|
+
} else if (json.error) {
|
|
214
|
+
resolve(`[Error: ${json.error.message || 'Unknown'}]`);
|
|
215
|
+
} else {
|
|
216
|
+
resolve('[No response]');
|
|
171
217
|
}
|
|
172
|
-
|
|
173
|
-
if (json.error) {
|
|
174
|
-
console.error('[a2a] API error:', json.error);
|
|
175
|
-
resolve(`[Agent error: ${json.error.message || JSON.stringify(json.error)}]`);
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
console.error('[a2a] Unexpected response:', JSON.stringify(json).slice(0, 200));
|
|
180
|
-
resolve('[No response generated]');
|
|
181
218
|
} catch (e) {
|
|
182
|
-
|
|
183
|
-
resolve('[Agent response parsing error]');
|
|
219
|
+
resolve('[Parse error]');
|
|
184
220
|
}
|
|
185
221
|
});
|
|
186
222
|
});
|
|
187
223
|
|
|
188
|
-
req.on('error', (
|
|
189
|
-
|
|
190
|
-
resolve(`[Agent temporarily unavailable: ${e.message}]`);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
req.on('timeout', () => {
|
|
194
|
-
req.destroy();
|
|
195
|
-
resolve('[Agent response timeout]');
|
|
196
|
-
});
|
|
197
|
-
|
|
224
|
+
req.on('error', () => resolve('[Agent unavailable]'));
|
|
225
|
+
req.on('timeout', () => { req.destroy(); resolve('[Timeout]'); });
|
|
198
226
|
req.write(body);
|
|
199
227
|
req.end();
|
|
200
228
|
});
|