a2acalling 0.1.3 → 0.1.4

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 +88 -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.4",
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,74 @@ 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
+ // Write prompt to temp file to avoid shell escaping issues
117
+ const tmpFile = `/tmp/a2a-${Date.now()}.txt`;
118
+ fs.writeFileSync(tmpFile, prompt);
119
+
120
+ // Call openclaw agent to spawn a sub-agent
121
+ const result = execSync(
122
+ `cat "${tmpFile}" | openclaw agent --session-id "${sessionId}" --timeout 55 2>/dev/null`,
123
+ {
124
+ encoding: 'utf8',
125
+ timeout: 60000,
126
+ maxBuffer: 1024 * 1024,
127
+ cwd: process.env.OPENCLAW_WORKSPACE || '/root/clawd',
128
+ env: { ...process.env, FORCE_COLOR: '0' }
129
+ }
130
+ );
131
+
132
+ // Clean up temp file
133
+ try { fs.unlinkSync(tmpFile); } catch (e) {}
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
+ return lines.join('\n').trim() || '[No response]';
143
+
144
+ } catch (err) {
145
+ console.error('[a2a] Sub-agent spawn failed:', err.message);
146
+
147
+ // Fallback to direct API call
148
+ return await callAgentDirect(message, federationContext);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Fallback: Call LLM directly via OpenRouter
154
+ */
155
+ async function callAgentDirect(message, federationContext) {
91
156
  if (!apiConfig) {
92
157
  return '[Agent configuration error: No API key available]';
93
158
  }
@@ -109,47 +174,26 @@ Disclosure level: ${federationContext.disclosure || 'minimal'}
109
174
 
110
175
  Respond naturally as yourself. Be collaborative but protect your owner's private information based on the disclosure level. Keep responses concise.`;
111
176
 
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
- });
177
+ const body = JSON.stringify({
178
+ model: 'anthropic/claude-sonnet-4',
179
+ max_tokens: 1024,
180
+ messages: [
181
+ { role: 'system', content: systemPrompt },
182
+ { role: 'user', content: message }
183
+ ]
184
+ });
133
185
 
134
- const headers = isOpenRouter
135
- ? {
186
+ return new Promise((resolve) => {
187
+ const req = https.request({
188
+ hostname: 'openrouter.ai',
189
+ path: '/api/v1/chat/completions',
190
+ method: 'POST',
191
+ headers: {
136
192
  'Content-Type': 'application/json',
137
193
  'Authorization': `Bearer ${apiConfig.key}`,
138
194
  'HTTP-Referer': 'https://openclaw.ai',
139
195
  '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,
196
+ },
153
197
  timeout: 55000
154
198
  }, (res) => {
155
199
  let data = '';
@@ -157,44 +201,21 @@ Respond naturally as yourself. Be collaborative but protect your owner's private
157
201
  res.on('end', () => {
158
202
  try {
159
203
  const json = JSON.parse(data);
160
-
161
- // OpenRouter format
162
204
  if (json.choices && json.choices[0]?.message?.content) {
163
205
  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;
206
+ } else if (json.error) {
207
+ resolve(`[Error: ${json.error.message || 'Unknown'}]`);
208
+ } else {
209
+ resolve('[No response]');
171
210
  }
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
211
  } catch (e) {
182
- console.error('[a2a] Parse error:', e.message, data.slice(0, 200));
183
- resolve('[Agent response parsing error]');
212
+ resolve('[Parse error]');
184
213
  }
185
214
  });
186
215
  });
187
216
 
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
-
217
+ req.on('error', () => resolve('[Agent unavailable]'));
218
+ req.on('timeout', () => { req.destroy(); resolve('[Timeout]'); });
198
219
  req.write(body);
199
220
  req.end();
200
221
  });