agentic-flow 1.2.7 → 1.3.1

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 (31) hide show
  1. package/README.md +53 -3
  2. package/dist/agents/claudeAgent.js +19 -1
  3. package/dist/agents/directApiAgent.js +1 -1
  4. package/dist/cli/claude-code-wrapper.js +11 -2
  5. package/dist/cli-proxy.js +111 -12
  6. package/dist/cli-standalone-proxy.js +1 -1
  7. package/dist/proxy/anthropic-to-openrouter.js +184 -94
  8. package/dist/proxy/anthropic-to-requesty.js +627 -0
  9. package/dist/proxy/tool-emulation.js +365 -0
  10. package/dist/utils/modelCapabilities.js +276 -0
  11. package/docs/plans/agent-booster/00-INDEX.md +230 -0
  12. package/docs/plans/agent-booster/00-OVERVIEW.md +454 -0
  13. package/docs/plans/agent-booster/01-ARCHITECTURE.md +699 -0
  14. package/docs/plans/agent-booster/02-INTEGRATION.md +771 -0
  15. package/docs/plans/agent-booster/03-BENCHMARKS.md +616 -0
  16. package/docs/plans/agent-booster/04-NPM-SDK.md +673 -0
  17. package/docs/plans/agent-booster/GITHUB-ISSUE.md +523 -0
  18. package/docs/plans/agent-booster/README.md +576 -0
  19. package/docs/plans/requesty/00-overview.md +176 -0
  20. package/docs/plans/requesty/01-api-research.md +573 -0
  21. package/docs/plans/requesty/02-architecture.md +1076 -0
  22. package/docs/plans/requesty/03-implementation-phases.md +1129 -0
  23. package/docs/plans/requesty/04-testing-strategy.md +905 -0
  24. package/docs/plans/requesty/05-migration-guide.md +576 -0
  25. package/docs/plans/requesty/README.md +290 -0
  26. package/package.json +1 -1
  27. package/dist/agents/sdkAgent.js +0 -151
  28. package/dist/examples/parallel-swarm-deployment.js +0 -171
  29. package/dist/utils/.claude-flow/metrics/agent-metrics.json +0 -1
  30. package/dist/utils/.claude-flow/metrics/performance.json +0 -9
  31. package/dist/utils/.claude-flow/metrics/task-metrics.json +0 -10
@@ -3,16 +3,28 @@
3
3
  import express from 'express';
4
4
  import { logger } from '../utils/logger.js';
5
5
  import { getMaxTokensForModel } from './provider-instructions.js';
6
+ import { detectModelCapabilities } from '../utils/modelCapabilities.js';
7
+ import { ToolEmulator, executeEmulation } from './tool-emulation.js';
6
8
  export class AnthropicToOpenRouterProxy {
7
9
  app;
8
10
  openrouterApiKey;
9
11
  openrouterBaseUrl;
10
12
  defaultModel;
13
+ capabilities;
11
14
  constructor(config) {
12
15
  this.app = express();
13
16
  this.openrouterApiKey = config.openrouterApiKey;
14
17
  this.openrouterBaseUrl = config.openrouterBaseUrl || 'https://openrouter.ai/api/v1';
15
18
  this.defaultModel = config.defaultModel || 'meta-llama/llama-3.1-8b-instruct';
19
+ this.capabilities = config.capabilities;
20
+ // Debug logging
21
+ if (this.capabilities) {
22
+ logger.info('Proxy initialized with capabilities', {
23
+ model: this.defaultModel,
24
+ requiresEmulation: this.capabilities.requiresEmulation,
25
+ strategy: this.capabilities.emulationStrategy
26
+ });
27
+ }
16
28
  this.setupMiddleware();
17
29
  this.setupRoutes();
18
30
  }
@@ -66,99 +78,10 @@ export class AnthropicToOpenRouterProxy {
66
78
  : JSON.stringify(firstMsg.content).substring(0, 200)
67
79
  });
68
80
  }
69
- // Convert Anthropic format to OpenAI format
70
- const openaiReq = this.convertAnthropicToOpenAI(anthropicReq);
71
- // VERBOSE LOGGING: Log converted OpenAI request
72
- logger.info('=== CONVERTED OPENAI REQUEST ===', {
73
- anthropicModel: anthropicReq.model,
74
- openaiModel: openaiReq.model,
75
- messageCount: openaiReq.messages.length,
76
- systemPrompt: openaiReq.messages[0]?.content?.substring(0, 300),
77
- toolCount: openaiReq.tools?.length || 0,
78
- toolNames: openaiReq.tools?.map(t => t.function.name) || [],
79
- maxTokens: openaiReq.max_tokens,
80
- apiKeyPresent: !!this.openrouterApiKey,
81
- apiKeyPrefix: this.openrouterApiKey?.substring(0, 10)
82
- });
83
- // Forward to OpenRouter
84
- const response = await fetch(`${this.openrouterBaseUrl}/chat/completions`, {
85
- method: 'POST',
86
- headers: {
87
- 'Authorization': `Bearer ${this.openrouterApiKey}`,
88
- 'Content-Type': 'application/json',
89
- 'HTTP-Referer': 'https://github.com/ruvnet/agentic-flow',
90
- 'X-Title': 'Agentic Flow'
91
- },
92
- body: JSON.stringify(openaiReq)
93
- });
94
- if (!response.ok) {
95
- const error = await response.text();
96
- logger.error('OpenRouter API error', { status: response.status, error });
97
- return res.status(response.status).json({
98
- error: {
99
- type: 'api_error',
100
- message: error
101
- }
102
- });
103
- }
104
- // VERBOSE LOGGING: Log OpenRouter response status
105
- logger.info('=== OPENROUTER RESPONSE RECEIVED ===', {
106
- status: response.status,
107
- statusText: response.statusText,
108
- headers: Object.fromEntries(response.headers.entries())
109
- });
110
- // Handle streaming vs non-streaming
111
- if (anthropicReq.stream) {
112
- logger.info('Handling streaming response...');
113
- // Stream response
114
- res.setHeader('Content-Type', 'text/event-stream');
115
- res.setHeader('Cache-Control', 'no-cache');
116
- res.setHeader('Connection', 'keep-alive');
117
- const reader = response.body?.getReader();
118
- if (!reader) {
119
- throw new Error('No response body');
120
- }
121
- const decoder = new TextDecoder();
122
- while (true) {
123
- const { done, value } = await reader.read();
124
- if (done)
125
- break;
126
- const chunk = decoder.decode(value);
127
- const anthropicChunk = this.convertOpenAIStreamToAnthropic(chunk);
128
- res.write(anthropicChunk);
129
- }
130
- res.end();
131
- }
132
- else {
133
- logger.info('Handling non-streaming response...');
134
- // Non-streaming response
135
- const openaiRes = await response.json();
136
- // VERBOSE LOGGING: Log raw OpenAI response
137
- logger.info('=== RAW OPENAI RESPONSE ===', {
138
- id: openaiRes.id,
139
- model: openaiRes.model,
140
- choices: openaiRes.choices?.length,
141
- finishReason: openaiRes.choices?.[0]?.finish_reason,
142
- hasToolCalls: !!(openaiRes.choices?.[0]?.message?.tool_calls),
143
- toolCallCount: openaiRes.choices?.[0]?.message?.tool_calls?.length || 0,
144
- toolCallNames: openaiRes.choices?.[0]?.message?.tool_calls?.map((tc) => tc.function.name) || [],
145
- contentPreview: openaiRes.choices?.[0]?.message?.content?.substring(0, 300),
146
- usage: openaiRes.usage
147
- });
148
- const anthropicRes = this.convertOpenAIToAnthropic(openaiRes);
149
- // VERBOSE LOGGING: Log converted Anthropic response
150
- logger.info('=== CONVERTED ANTHROPIC RESPONSE ===', {
151
- id: anthropicRes.id,
152
- model: anthropicRes.model,
153
- role: anthropicRes.role,
154
- stopReason: anthropicRes.stop_reason,
155
- contentBlocks: anthropicRes.content?.length,
156
- contentTypes: anthropicRes.content?.map((c) => c.type),
157
- toolUseCount: anthropicRes.content?.filter((c) => c.type === 'tool_use').length,
158
- textPreview: anthropicRes.content?.find((c) => c.type === 'text')?.text?.substring(0, 200),
159
- usage: anthropicRes.usage
160
- });
161
- res.json(anthropicRes);
81
+ // Route to appropriate handler based on capabilities
82
+ const result = await this.handleRequest(anthropicReq, res);
83
+ if (result) {
84
+ res.json(result);
162
85
  }
163
86
  }
164
87
  catch (error) {
@@ -182,6 +105,168 @@ export class AnthropicToOpenRouterProxy {
182
105
  });
183
106
  });
184
107
  }
108
+ async handleRequest(anthropicReq, res) {
109
+ let model = anthropicReq.model || this.defaultModel;
110
+ // If SDK is requesting a Claude model but we're using OpenRouter with a different default,
111
+ // override to use the CLI-specified model
112
+ if (model.startsWith('claude-') && this.defaultModel && !this.defaultModel.startsWith('claude-')) {
113
+ logger.info(`Overriding SDK Claude model ${model} with CLI-specified ${this.defaultModel}`);
114
+ model = this.defaultModel;
115
+ anthropicReq.model = model;
116
+ }
117
+ const capabilities = this.capabilities || detectModelCapabilities(model);
118
+ // Check if emulation is required
119
+ if (capabilities.requiresEmulation && anthropicReq.tools && anthropicReq.tools.length > 0) {
120
+ logger.info(`Using tool emulation for model: ${model}`);
121
+ return this.handleEmulatedRequest(anthropicReq, capabilities);
122
+ }
123
+ return this.handleNativeRequest(anthropicReq, res);
124
+ }
125
+ async handleNativeRequest(anthropicReq, res) {
126
+ // Convert Anthropic format to OpenAI format
127
+ const openaiReq = this.convertAnthropicToOpenAI(anthropicReq);
128
+ // VERBOSE LOGGING: Log converted OpenAI request
129
+ logger.info('=== CONVERTED OPENAI REQUEST ===', {
130
+ anthropicModel: anthropicReq.model,
131
+ openaiModel: openaiReq.model,
132
+ messageCount: openaiReq.messages.length,
133
+ systemPrompt: openaiReq.messages[0]?.content?.substring(0, 300),
134
+ toolCount: openaiReq.tools?.length || 0,
135
+ toolNames: openaiReq.tools?.map(t => t.function.name) || [],
136
+ maxTokens: openaiReq.max_tokens,
137
+ apiKeyPresent: !!this.openrouterApiKey,
138
+ apiKeyPrefix: this.openrouterApiKey?.substring(0, 10)
139
+ });
140
+ // Forward to OpenRouter
141
+ const response = await fetch(`${this.openrouterBaseUrl}/chat/completions`, {
142
+ method: 'POST',
143
+ headers: {
144
+ 'Authorization': `Bearer ${this.openrouterApiKey}`,
145
+ 'Content-Type': 'application/json',
146
+ 'HTTP-Referer': 'https://github.com/ruvnet/agentic-flow',
147
+ 'X-Title': 'Agentic Flow'
148
+ },
149
+ body: JSON.stringify(openaiReq)
150
+ });
151
+ if (!response.ok) {
152
+ const error = await response.text();
153
+ logger.error('OpenRouter API error', { status: response.status, error });
154
+ res.status(response.status).json({
155
+ error: {
156
+ type: 'api_error',
157
+ message: error
158
+ }
159
+ });
160
+ return null;
161
+ }
162
+ // VERBOSE LOGGING: Log OpenRouter response status
163
+ logger.info('=== OPENROUTER RESPONSE RECEIVED ===', {
164
+ status: response.status,
165
+ statusText: response.statusText,
166
+ headers: Object.fromEntries(response.headers.entries())
167
+ });
168
+ // Handle streaming vs non-streaming
169
+ if (anthropicReq.stream) {
170
+ logger.info('Handling streaming response...');
171
+ // Stream response
172
+ res.setHeader('Content-Type', 'text/event-stream');
173
+ res.setHeader('Cache-Control', 'no-cache');
174
+ res.setHeader('Connection', 'keep-alive');
175
+ const reader = response.body?.getReader();
176
+ if (!reader) {
177
+ throw new Error('No response body');
178
+ }
179
+ const decoder = new TextDecoder();
180
+ while (true) {
181
+ const { done, value } = await reader.read();
182
+ if (done)
183
+ break;
184
+ const chunk = decoder.decode(value);
185
+ const anthropicChunk = this.convertOpenAIStreamToAnthropic(chunk);
186
+ res.write(anthropicChunk);
187
+ }
188
+ res.end();
189
+ return null; // Already sent response
190
+ }
191
+ else {
192
+ logger.info('Handling non-streaming response...');
193
+ // Non-streaming response
194
+ const openaiRes = await response.json();
195
+ // VERBOSE LOGGING: Log raw OpenAI response
196
+ logger.info('=== RAW OPENAI RESPONSE ===', {
197
+ id: openaiRes.id,
198
+ model: openaiRes.model,
199
+ choices: openaiRes.choices?.length,
200
+ finishReason: openaiRes.choices?.[0]?.finish_reason,
201
+ hasToolCalls: !!(openaiRes.choices?.[0]?.message?.tool_calls),
202
+ toolCallCount: openaiRes.choices?.[0]?.message?.tool_calls?.length || 0,
203
+ toolCallNames: openaiRes.choices?.[0]?.message?.tool_calls?.map((tc) => tc.function.name) || [],
204
+ contentPreview: openaiRes.choices?.[0]?.message?.content?.substring(0, 300),
205
+ usage: openaiRes.usage
206
+ });
207
+ const anthropicRes = this.convertOpenAIToAnthropic(openaiRes);
208
+ // VERBOSE LOGGING: Log converted Anthropic response
209
+ logger.info('=== CONVERTED ANTHROPIC RESPONSE ===', {
210
+ id: anthropicRes.id,
211
+ model: anthropicRes.model,
212
+ role: anthropicRes.role,
213
+ stopReason: anthropicRes.stop_reason,
214
+ contentBlocks: anthropicRes.content?.length,
215
+ contentTypes: anthropicRes.content?.map((c) => c.type),
216
+ toolUseCount: anthropicRes.content?.filter((c) => c.type === 'tool_use').length,
217
+ textPreview: anthropicRes.content?.find((c) => c.type === 'text')?.text?.substring(0, 200),
218
+ usage: anthropicRes.usage
219
+ });
220
+ return anthropicRes;
221
+ }
222
+ }
223
+ async handleEmulatedRequest(anthropicReq, capabilities) {
224
+ const emulator = new ToolEmulator(anthropicReq.tools || [], capabilities.emulationStrategy);
225
+ const lastMessage = anthropicReq.messages[anthropicReq.messages.length - 1];
226
+ const userMessage = typeof lastMessage.content === 'string'
227
+ ? lastMessage.content
228
+ : (lastMessage.content.find(c => c.type === 'text')?.text || '');
229
+ const result = await executeEmulation(emulator, userMessage, async (prompt) => {
230
+ // Call model with emulation prompt
231
+ const openaiReq = {
232
+ model: anthropicReq.model || this.defaultModel,
233
+ messages: [{ role: 'user', content: prompt }],
234
+ temperature: anthropicReq.temperature,
235
+ max_tokens: anthropicReq.max_tokens
236
+ };
237
+ const response = await this.callOpenRouter(openaiReq);
238
+ return response.choices[0].message.content;
239
+ }, async (toolCall) => {
240
+ logger.warn(`Tool execution not yet implemented: ${toolCall.name}`);
241
+ return { error: 'Tool execution not implemented in Phase 2' };
242
+ }, { maxIterations: 5, verbose: process.env.VERBOSE === 'true' });
243
+ return {
244
+ id: `emulated_${Date.now()}`,
245
+ type: 'message',
246
+ role: 'assistant',
247
+ content: [{ type: 'text', text: result.finalAnswer || 'No response' }],
248
+ model: anthropicReq.model || this.defaultModel,
249
+ stop_reason: 'end_turn',
250
+ usage: { input_tokens: 0, output_tokens: 0 }
251
+ };
252
+ }
253
+ async callOpenRouter(openaiReq) {
254
+ const response = await fetch(`${this.openrouterBaseUrl}/chat/completions`, {
255
+ method: 'POST',
256
+ headers: {
257
+ 'Authorization': `Bearer ${this.openrouterApiKey}`,
258
+ 'Content-Type': 'application/json',
259
+ 'HTTP-Referer': 'https://github.com/ruvnet/agentic-flow',
260
+ 'X-Title': 'Agentic Flow'
261
+ },
262
+ body: JSON.stringify(openaiReq)
263
+ });
264
+ if (!response.ok) {
265
+ const error = await response.text();
266
+ throw new Error(`OpenRouter API error: ${error}`);
267
+ }
268
+ return response.json();
269
+ }
185
270
  convertAnthropicToOpenAI(anthropicReq) {
186
271
  logger.info('=== STARTING ANTHROPIC TO OPENAI CONVERSION ===');
187
272
  const messages = [];
@@ -507,7 +592,12 @@ export class AnthropicToOpenRouterProxy {
507
592
  });
508
593
  console.log(`\n✅ Anthropic Proxy running at http://localhost:${port}`);
509
594
  console.log(` OpenRouter Base URL: ${this.openrouterBaseUrl}`);
510
- console.log(` Default Model: ${this.defaultModel}\n`);
595
+ console.log(` Default Model: ${this.defaultModel}`);
596
+ if (this.capabilities?.requiresEmulation) {
597
+ console.log(`\n ⚙️ Tool Emulation: ${this.capabilities.emulationStrategy.toUpperCase()} pattern`);
598
+ console.log(` 📊 Expected reliability: ${this.capabilities.emulationStrategy === 'react' ? '70-85%' : '50-70%'}`);
599
+ }
600
+ console.log('');
511
601
  });
512
602
  }
513
603
  }