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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/server.js +95 -67
package/README.md CHANGED
@@ -304,4 +304,4 @@ MIT — go build something cool.
304
304
 
305
305
  ---
306
306
 
307
- *Let your people talk to my people.* 🤝
307
+ *I'll have my people call your people.* 🤝
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2acalling",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Agent-to-agent calling for OpenClaw - federated agent communication",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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 LLM via OpenRouter or Anthropic
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
- // Use OpenRouter or Anthropic based on config
113
- const isOpenRouter = apiConfig.provider === 'openrouter';
114
- const hostname = isOpenRouter ? 'openrouter.ai' : 'api.anthropic.com';
115
- const apiPath = isOpenRouter ? '/api/v1/chat/completions' : '/v1/messages';
116
- const model = isOpenRouter ? 'anthropic/claude-sonnet-4' : 'claude-sonnet-4-20250514';
117
-
118
- const body = isOpenRouter
119
- ? JSON.stringify({
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
- const headers = isOpenRouter
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
- return;
165
- }
166
-
167
- // Anthropic format
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
- console.error('[a2a] Parse error:', e.message, data.slice(0, 200));
183
- resolve('[Agent response parsing error]');
219
+ resolve('[Parse error]');
184
220
  }
185
221
  });
186
222
  });
187
223
 
188
- req.on('error', (e) => {
189
- console.error('[a2a] Request error:', e.message);
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
  });