@vybestack/llxprt-code-core 0.1.23-nightly.250903.97906524 → 0.1.23-nightly.250905.67589d14
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/dist/src/adapters/IStreamAdapter.d.ts +3 -3
- package/dist/src/auth/types.d.ts +4 -4
- package/dist/src/config/index.d.ts +7 -0
- package/dist/src/config/index.js +8 -0
- package/dist/src/config/index.js.map +1 -0
- package/dist/src/core/client.d.ts +9 -21
- package/dist/src/core/client.js +46 -144
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/compression-config.d.ts +1 -1
- package/dist/src/core/compression-config.js +4 -5
- package/dist/src/core/compression-config.js.map +1 -1
- package/dist/src/core/coreToolScheduler.js +50 -15
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +51 -2
- package/dist/src/core/geminiChat.js +592 -93
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.js +70 -19
- package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -1
- package/dist/src/index.d.ts +1 -2
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/providers/BaseProvider.d.ts +8 -3
- package/dist/src/providers/BaseProvider.js.map +1 -1
- package/dist/src/providers/IProvider.d.ts +9 -3
- package/dist/src/providers/LoggingProviderWrapper.d.ts +10 -3
- package/dist/src/providers/LoggingProviderWrapper.js +33 -27
- package/dist/src/providers/LoggingProviderWrapper.js.map +1 -1
- package/dist/src/providers/ProviderContentGenerator.d.ts +2 -2
- package/dist/src/providers/ProviderContentGenerator.js +9 -6
- package/dist/src/providers/ProviderContentGenerator.js.map +1 -1
- package/dist/src/providers/anthropic/AnthropicProvider.d.ts +12 -17
- package/dist/src/providers/anthropic/AnthropicProvider.js +238 -447
- package/dist/src/providers/anthropic/AnthropicProvider.js.map +1 -1
- package/dist/src/providers/gemini/GeminiProvider.d.ts +12 -6
- package/dist/src/providers/gemini/GeminiProvider.js +184 -458
- package/dist/src/providers/gemini/GeminiProvider.js.map +1 -1
- package/dist/src/providers/openai/ConversationCache.d.ts +3 -3
- package/dist/src/providers/openai/IChatGenerateParams.d.ts +9 -4
- package/dist/src/providers/openai/OpenAIProvider.d.ts +14 -61
- package/dist/src/providers/openai/OpenAIProvider.js +270 -575
- package/dist/src/providers/openai/OpenAIProvider.js.map +1 -1
- package/dist/src/providers/openai/buildResponsesRequest.d.ts +3 -3
- package/dist/src/providers/openai/buildResponsesRequest.js +67 -37
- package/dist/src/providers/openai/buildResponsesRequest.js.map +1 -1
- package/dist/src/providers/openai/estimateRemoteTokens.d.ts +2 -2
- package/dist/src/providers/openai/estimateRemoteTokens.js +21 -8
- package/dist/src/providers/openai/estimateRemoteTokens.js.map +1 -1
- package/dist/src/providers/openai/parseResponsesStream.d.ts +6 -2
- package/dist/src/providers/openai/parseResponsesStream.js +99 -391
- package/dist/src/providers/openai/parseResponsesStream.js.map +1 -1
- package/dist/src/providers/openai/syntheticToolResponses.d.ts +5 -5
- package/dist/src/providers/openai/syntheticToolResponses.js +102 -91
- package/dist/src/providers/openai/syntheticToolResponses.js.map +1 -1
- package/dist/src/providers/openai-responses/OpenAIResponsesProvider.d.ts +16 -17
- package/dist/src/providers/openai-responses/OpenAIResponsesProvider.js +222 -224
- package/dist/src/providers/openai-responses/OpenAIResponsesProvider.js.map +1 -1
- package/dist/src/providers/types.d.ts +1 -1
- package/dist/src/services/history/ContentConverters.d.ts +6 -1
- package/dist/src/services/history/ContentConverters.js +155 -18
- package/dist/src/services/history/ContentConverters.js.map +1 -1
- package/dist/src/services/history/HistoryService.d.ts +52 -0
- package/dist/src/services/history/HistoryService.js +245 -93
- package/dist/src/services/history/HistoryService.js.map +1 -1
- package/dist/src/services/history/IContent.d.ts +4 -0
- package/dist/src/services/history/IContent.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +16 -4
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/tools/IToolFormatter.d.ts +2 -2
- package/dist/src/tools/ToolFormatter.d.ts +3 -3
- package/dist/src/tools/ToolFormatter.js +80 -37
- package/dist/src/tools/ToolFormatter.js.map +1 -1
- package/dist/src/tools/todo-schemas.d.ts +4 -4
- package/package.json +8 -7
- package/dist/src/core/ContentGeneratorAdapter.d.ts +0 -37
- package/dist/src/core/ContentGeneratorAdapter.js +0 -58
- package/dist/src/core/ContentGeneratorAdapter.js.map +0 -1
- package/dist/src/providers/IMessage.d.ts +0 -38
- package/dist/src/providers/IMessage.js +0 -17
- package/dist/src/providers/IMessage.js.map +0 -1
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.d.ts +0 -69
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.js +0 -577
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.js.map +0 -1
@@ -1,10 +1,8 @@
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
2
2
|
import { DebugLogger } from '../../debug/index.js';
|
3
|
-
import { retryWithBackoff } from '../../utils/retry.js';
|
4
3
|
import { ToolFormatter } from '../../tools/ToolFormatter.js';
|
5
4
|
import { BaseProvider } from '../BaseProvider.js';
|
6
5
|
import { getSettingsService } from '../../settings/settingsServiceInstance.js';
|
7
|
-
import { ContentGeneratorRole } from '../ContentGeneratorRole.js';
|
8
6
|
export class AnthropicProvider extends BaseProvider {
|
9
7
|
logger;
|
10
8
|
anthropic;
|
@@ -15,16 +13,6 @@ export class AnthropicProvider extends BaseProvider {
|
|
15
13
|
currentModel = 'claude-sonnet-4-20250514'; // Default model
|
16
14
|
modelParams;
|
17
15
|
_cachedAuthKey; // Track cached auth key for client recreation
|
18
|
-
// Model cache for latest resolution
|
19
|
-
modelCache = null;
|
20
|
-
modelCacheTTL = 5 * 60 * 1000; // 5 minutes
|
21
|
-
// Retry configuration
|
22
|
-
retryableErrorMessages = [
|
23
|
-
'overloaded',
|
24
|
-
'rate_limit',
|
25
|
-
'server_error',
|
26
|
-
'service_unavailable',
|
27
|
-
];
|
28
16
|
// Model patterns for max output tokens
|
29
17
|
modelTokenPatterns = [
|
30
18
|
{ pattern: /claude-.*opus-4/i, tokens: 32000 },
|
@@ -177,311 +165,6 @@ export class AnthropicProvider extends BaseProvider {
|
|
177
165
|
return []; // Return empty array on error
|
178
166
|
}
|
179
167
|
}
|
180
|
-
async *generateChatCompletion(messages, tools, _toolFormat) {
|
181
|
-
const authToken = await this.getAuthToken();
|
182
|
-
if (!authToken) {
|
183
|
-
throw new Error('Authentication required to generate Anthropic chat completions');
|
184
|
-
}
|
185
|
-
// Get streaming setting from ephemeral settings (default: enabled)
|
186
|
-
const streamingSetting = this._config?.getEphemeralSettings?.()?.['streaming'];
|
187
|
-
const streamingEnabled = streamingSetting !== 'disabled';
|
188
|
-
// Update Anthropic client with resolved authentication if needed
|
189
|
-
await this.updateClientWithResolvedAuth();
|
190
|
-
const apiCall = async () => {
|
191
|
-
// Resolve model if it uses -latest alias
|
192
|
-
const resolvedModel = await this.resolveLatestModel(this.currentModel);
|
193
|
-
// Always validate and fix message history to prevent tool_use/tool_result mismatches
|
194
|
-
// This is necessary for both cancelled tools and retries
|
195
|
-
const validatedMessages = this.validateAndFixMessages(messages);
|
196
|
-
// Use the resolved model for the API call
|
197
|
-
const modelForApi = resolvedModel;
|
198
|
-
// Check if we're in OAuth mode early
|
199
|
-
const authToken = await this.getAuthToken();
|
200
|
-
const isOAuth = authToken && authToken.startsWith('sk-ant-oat');
|
201
|
-
// Extract system message if present and handle tool responses
|
202
|
-
let systemMessage;
|
203
|
-
let llxprtPrompts; // Store llxprt prompts separately
|
204
|
-
const anthropicMessages = [];
|
205
|
-
for (const msg of validatedMessages) {
|
206
|
-
if (msg.role === 'system') {
|
207
|
-
if (isOAuth) {
|
208
|
-
// In OAuth mode, save system content for injection as user message
|
209
|
-
llxprtPrompts = msg.content;
|
210
|
-
}
|
211
|
-
else {
|
212
|
-
// In normal mode, use as system message
|
213
|
-
systemMessage = msg.content;
|
214
|
-
}
|
215
|
-
}
|
216
|
-
else if (msg.role === 'tool') {
|
217
|
-
// Anthropic expects tool responses as user messages with tool_result content
|
218
|
-
anthropicMessages.push({
|
219
|
-
role: 'user',
|
220
|
-
content: [
|
221
|
-
{
|
222
|
-
type: 'tool_result',
|
223
|
-
tool_use_id: msg.tool_call_id || 'unknown',
|
224
|
-
content: msg.content,
|
225
|
-
},
|
226
|
-
],
|
227
|
-
});
|
228
|
-
}
|
229
|
-
else if (msg.role === 'assistant' && msg.tool_calls) {
|
230
|
-
// Handle assistant messages with tool calls
|
231
|
-
const content = [];
|
232
|
-
if (msg.content) {
|
233
|
-
content.push({ type: 'text', text: msg.content });
|
234
|
-
}
|
235
|
-
for (const toolCall of msg.tool_calls) {
|
236
|
-
content.push({
|
237
|
-
type: 'tool_use',
|
238
|
-
id: toolCall.id,
|
239
|
-
name: toolCall.function.name,
|
240
|
-
input: toolCall.function.arguments
|
241
|
-
? JSON.parse(toolCall.function.arguments)
|
242
|
-
: {},
|
243
|
-
});
|
244
|
-
}
|
245
|
-
anthropicMessages.push({
|
246
|
-
role: 'assistant',
|
247
|
-
content,
|
248
|
-
});
|
249
|
-
}
|
250
|
-
else {
|
251
|
-
// Regular user/assistant messages
|
252
|
-
anthropicMessages.push({
|
253
|
-
role: msg.role,
|
254
|
-
content: msg.content,
|
255
|
-
});
|
256
|
-
}
|
257
|
-
}
|
258
|
-
// In OAuth mode, inject llxprt prompts as conversation content
|
259
|
-
// ONLY for the very first message in a new conversation
|
260
|
-
if (isOAuth && llxprtPrompts && anthropicMessages.length === 0) {
|
261
|
-
// This is the very first message - inject the context
|
262
|
-
const contextMessage = `Important context for using llxprt tools:
|
263
|
-
|
264
|
-
Tool Parameter Reference:
|
265
|
-
- read_file uses parameter 'absolute_path' (not 'file_path')
|
266
|
-
- write_file uses parameter 'file_path' (not 'path')
|
267
|
-
- list_directory uses parameter 'path'
|
268
|
-
- replace uses 'file_path', 'old_string', 'new_string'
|
269
|
-
- search_file_content (grep) expects regex patterns, not literal text
|
270
|
-
- todo_write requires 'todos' array with {id, content, status, priority}
|
271
|
-
- All file paths must be absolute (starting with /)
|
272
|
-
|
273
|
-
${llxprtPrompts}`;
|
274
|
-
// Inject at the beginning of the conversation
|
275
|
-
anthropicMessages.unshift({
|
276
|
-
role: 'user',
|
277
|
-
content: contextMessage,
|
278
|
-
}, {
|
279
|
-
role: 'assistant',
|
280
|
-
content: "I understand the llxprt tool parameters and context. I'll use the correct parameter names for each tool. Ready to help with your tasks.",
|
281
|
-
});
|
282
|
-
}
|
283
|
-
// For ongoing conversations, the context was already injected in the first message
|
284
|
-
// so we don't need to inject it again
|
285
|
-
// Convert ITool[] to Anthropic's tool format if tools are provided
|
286
|
-
const anthropicTools = tools
|
287
|
-
? this.toolFormatter.toProviderFormat(tools, 'anthropic')
|
288
|
-
: undefined;
|
289
|
-
// Create the request options with proper typing
|
290
|
-
const createOptions = {
|
291
|
-
model: modelForApi,
|
292
|
-
messages: anthropicMessages,
|
293
|
-
max_tokens: this.getMaxTokensForModel(resolvedModel),
|
294
|
-
...this.modelParams, // Apply model params first
|
295
|
-
stream: streamingEnabled, // Use ephemeral streaming setting
|
296
|
-
};
|
297
|
-
// Set system message based on auth mode
|
298
|
-
if (isOAuth) {
|
299
|
-
// OAuth mode: Use Claude Code system prompt (required for Max/Pro)
|
300
|
-
createOptions.system =
|
301
|
-
"You are Claude Code, Anthropic's official CLI for Claude.";
|
302
|
-
// llxprt prompts were already injected as conversation content above
|
303
|
-
}
|
304
|
-
else if (systemMessage) {
|
305
|
-
// Normal mode: Use full llxprt system prompt
|
306
|
-
createOptions.system = systemMessage;
|
307
|
-
}
|
308
|
-
if (anthropicTools) {
|
309
|
-
createOptions.tools = anthropicTools;
|
310
|
-
}
|
311
|
-
if (streamingEnabled) {
|
312
|
-
return this.anthropic.messages.create(createOptions);
|
313
|
-
}
|
314
|
-
else {
|
315
|
-
return this.anthropic.messages.create(createOptions);
|
316
|
-
}
|
317
|
-
};
|
318
|
-
try {
|
319
|
-
const response = await retryWithBackoff(apiCall, {
|
320
|
-
shouldRetry: (error) => this.isRetryableError(error),
|
321
|
-
});
|
322
|
-
if (streamingEnabled) {
|
323
|
-
// Handle streaming response
|
324
|
-
const stream = response;
|
325
|
-
let currentUsage;
|
326
|
-
// Track current tool call being streamed
|
327
|
-
let currentToolCall;
|
328
|
-
// Process the stream
|
329
|
-
for await (const chunk of stream) {
|
330
|
-
this.logger.debug(() => `Received chunk type: ${chunk.type}${chunk.type === 'message_start'
|
331
|
-
? ` - ${JSON.stringify(chunk, null, 2)}`
|
332
|
-
: ''}`);
|
333
|
-
if (chunk.type === 'message_start') {
|
334
|
-
// Initial usage info
|
335
|
-
this.logger.debug(() => `message_start chunk: ${JSON.stringify(chunk, null, 2)}`);
|
336
|
-
if (chunk.message?.usage) {
|
337
|
-
const usage = chunk.message.usage;
|
338
|
-
// Don't require both fields - Anthropic might send them separately
|
339
|
-
currentUsage = {
|
340
|
-
input_tokens: usage.input_tokens ?? 0,
|
341
|
-
output_tokens: usage.output_tokens ?? 0,
|
342
|
-
};
|
343
|
-
this.logger.debug(() => `Set currentUsage from message_start: ${JSON.stringify(currentUsage)}`);
|
344
|
-
yield {
|
345
|
-
role: 'assistant',
|
346
|
-
content: '',
|
347
|
-
usage: {
|
348
|
-
prompt_tokens: currentUsage.input_tokens,
|
349
|
-
completion_tokens: currentUsage.output_tokens,
|
350
|
-
total_tokens: currentUsage.input_tokens + currentUsage.output_tokens,
|
351
|
-
},
|
352
|
-
};
|
353
|
-
}
|
354
|
-
}
|
355
|
-
else if (chunk.type === 'content_block_start') {
|
356
|
-
// Handle tool use blocks
|
357
|
-
if (chunk.content_block.type === 'tool_use') {
|
358
|
-
currentToolCall = {
|
359
|
-
id: chunk.content_block.id,
|
360
|
-
name: chunk.content_block.name,
|
361
|
-
input: '',
|
362
|
-
};
|
363
|
-
}
|
364
|
-
}
|
365
|
-
else if (chunk.type === 'content_block_delta') {
|
366
|
-
// Yield content chunks
|
367
|
-
if (chunk.delta.type === 'text_delta') {
|
368
|
-
yield {
|
369
|
-
role: 'assistant',
|
370
|
-
content: chunk.delta.text,
|
371
|
-
};
|
372
|
-
}
|
373
|
-
else if (chunk.delta.type === 'input_json_delta' &&
|
374
|
-
currentToolCall) {
|
375
|
-
// Handle input deltas for tool calls
|
376
|
-
currentToolCall.input += chunk.delta.partial_json;
|
377
|
-
}
|
378
|
-
}
|
379
|
-
else if (chunk.type === 'content_block_stop') {
|
380
|
-
// Complete the tool call
|
381
|
-
if (currentToolCall) {
|
382
|
-
const toolCallResult = this.toolFormatter.fromProviderFormat({
|
383
|
-
id: currentToolCall.id,
|
384
|
-
type: 'tool_use',
|
385
|
-
name: currentToolCall.name,
|
386
|
-
input: currentToolCall.input
|
387
|
-
? JSON.parse(currentToolCall.input)
|
388
|
-
: undefined,
|
389
|
-
}, 'anthropic');
|
390
|
-
yield {
|
391
|
-
role: 'assistant',
|
392
|
-
content: '',
|
393
|
-
tool_calls: toolCallResult,
|
394
|
-
};
|
395
|
-
currentToolCall = undefined;
|
396
|
-
}
|
397
|
-
}
|
398
|
-
else if (chunk.type === 'message_delta') {
|
399
|
-
// Update usage if provided
|
400
|
-
if (chunk.usage) {
|
401
|
-
this.logger.debug(() => `message_delta usage: ${JSON.stringify(chunk.usage, null, 2)}`);
|
402
|
-
}
|
403
|
-
if (chunk.usage) {
|
404
|
-
// Anthropic may send partial usage data - merge with existing
|
405
|
-
currentUsage = {
|
406
|
-
input_tokens: chunk.usage.input_tokens ?? currentUsage?.input_tokens ?? 0,
|
407
|
-
output_tokens: chunk.usage.output_tokens ?? currentUsage?.output_tokens ?? 0,
|
408
|
-
};
|
409
|
-
this.logger.debug(() => `Updated currentUsage from message_delta: ${JSON.stringify(currentUsage)}`);
|
410
|
-
yield {
|
411
|
-
role: 'assistant',
|
412
|
-
content: '',
|
413
|
-
usage: {
|
414
|
-
prompt_tokens: currentUsage.input_tokens,
|
415
|
-
completion_tokens: currentUsage.output_tokens,
|
416
|
-
total_tokens: currentUsage.input_tokens + currentUsage.output_tokens,
|
417
|
-
},
|
418
|
-
};
|
419
|
-
}
|
420
|
-
}
|
421
|
-
else if (chunk.type === 'message_stop') {
|
422
|
-
// Final usage info
|
423
|
-
if (currentUsage) {
|
424
|
-
this.logger.debug(() => `Yielding final usage: ${JSON.stringify(currentUsage)}`);
|
425
|
-
yield {
|
426
|
-
role: 'assistant',
|
427
|
-
content: '',
|
428
|
-
usage: {
|
429
|
-
prompt_tokens: currentUsage.input_tokens,
|
430
|
-
completion_tokens: currentUsage.output_tokens,
|
431
|
-
total_tokens: currentUsage.input_tokens + currentUsage.output_tokens,
|
432
|
-
},
|
433
|
-
};
|
434
|
-
}
|
435
|
-
else {
|
436
|
-
this.logger.debug(() => 'No currentUsage data at message_stop');
|
437
|
-
}
|
438
|
-
}
|
439
|
-
}
|
440
|
-
}
|
441
|
-
else {
|
442
|
-
// Handle non-streaming response
|
443
|
-
const message = response;
|
444
|
-
let fullContent = '';
|
445
|
-
const toolCalls = [];
|
446
|
-
// Process content blocks
|
447
|
-
for (const content of message.content) {
|
448
|
-
if (content.type === 'text') {
|
449
|
-
fullContent += content.text;
|
450
|
-
}
|
451
|
-
else if (content.type === 'tool_use') {
|
452
|
-
toolCalls.push({
|
453
|
-
id: content.id,
|
454
|
-
type: 'function',
|
455
|
-
function: {
|
456
|
-
name: content.name,
|
457
|
-
arguments: JSON.stringify(content.input),
|
458
|
-
},
|
459
|
-
});
|
460
|
-
}
|
461
|
-
}
|
462
|
-
// Build response message
|
463
|
-
const responseMessage = {
|
464
|
-
role: ContentGeneratorRole.ASSISTANT,
|
465
|
-
content: fullContent,
|
466
|
-
};
|
467
|
-
if (toolCalls.length > 0) {
|
468
|
-
responseMessage.tool_calls = toolCalls;
|
469
|
-
}
|
470
|
-
if (message.usage) {
|
471
|
-
responseMessage.usage = {
|
472
|
-
prompt_tokens: message.usage.input_tokens,
|
473
|
-
completion_tokens: message.usage.output_tokens,
|
474
|
-
total_tokens: message.usage.input_tokens + message.usage.output_tokens,
|
475
|
-
};
|
476
|
-
}
|
477
|
-
yield responseMessage;
|
478
|
-
}
|
479
|
-
}
|
480
|
-
catch (error) {
|
481
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
482
|
-
throw new Error(`Anthropic API error: ${errorMessage}`);
|
483
|
-
}
|
484
|
-
}
|
485
168
|
setApiKey(apiKey) {
|
486
169
|
// Call base provider implementation
|
487
170
|
super.setApiKey(apiKey);
|
@@ -555,54 +238,6 @@ ${llxprtPrompts}`;
|
|
555
238
|
return 'claude-sonnet-4-latest';
|
556
239
|
}
|
557
240
|
}
|
558
|
-
/**
|
559
|
-
* Resolves a model ID that may contain "-latest" to the actual model ID.
|
560
|
-
* Caches the result to avoid frequent API calls.
|
561
|
-
*/
|
562
|
-
async resolveLatestModel(modelId) {
|
563
|
-
// If it's not a latest alias, return as-is
|
564
|
-
if (!modelId.endsWith('-latest')) {
|
565
|
-
return modelId;
|
566
|
-
}
|
567
|
-
// Check cache
|
568
|
-
const now = Date.now();
|
569
|
-
if (this.modelCache &&
|
570
|
-
now - this.modelCache.timestamp < this.modelCacheTTL) {
|
571
|
-
// Find the corresponding model from cache
|
572
|
-
const model = this.modelCache.models.find((m) => m.id === modelId);
|
573
|
-
if (model) {
|
574
|
-
// The latest aliases are synthetic, find the real model
|
575
|
-
const tier = modelId.includes('opus') ? 'opus' : 'sonnet';
|
576
|
-
const realModel = this.modelCache.models
|
577
|
-
.filter((m) => m.id.startsWith(`claude-${tier}-4-`) && !m.id.endsWith('-latest'))
|
578
|
-
.sort((a, b) => b.id.localeCompare(a.id))[0];
|
579
|
-
return realModel ? realModel.id : modelId;
|
580
|
-
}
|
581
|
-
}
|
582
|
-
try {
|
583
|
-
// Ensure client has proper auth before calling getModels
|
584
|
-
await this.updateClientWithResolvedAuth();
|
585
|
-
// Fetch fresh models
|
586
|
-
const models = await this.getModels();
|
587
|
-
this.modelCache = { models, timestamp: now };
|
588
|
-
// Find the real model for this latest alias
|
589
|
-
const tier = modelId.includes('opus') ? 'opus' : 'sonnet';
|
590
|
-
const realModel = models
|
591
|
-
.filter((m) => m.id.startsWith(`claude-${tier}-4-`) && !m.id.endsWith('-latest'))
|
592
|
-
.sort((a, b) => b.id.localeCompare(a.id))[0];
|
593
|
-
return realModel ? realModel.id : modelId;
|
594
|
-
}
|
595
|
-
catch (_error) {
|
596
|
-
// If we can't fetch models, just use simple fallback like Claude Code does
|
597
|
-
this.logger.debug(() => 'Failed to fetch models for latest resolution, using fallback');
|
598
|
-
if (modelId.includes('opus')) {
|
599
|
-
return 'opus';
|
600
|
-
}
|
601
|
-
else {
|
602
|
-
return 'sonnet'; // Default to sonnet like Claude Code
|
603
|
-
}
|
604
|
-
}
|
605
|
-
}
|
606
241
|
getMaxTokensForModel(modelId) {
|
607
242
|
// Handle latest aliases explicitly
|
608
243
|
if (modelId === 'claude-opus-4-latest' ||
|
@@ -637,88 +272,6 @@ ${llxprtPrompts}`;
|
|
637
272
|
// Default for Claude 3.x models
|
638
273
|
return 200000;
|
639
274
|
}
|
640
|
-
isRetryableError(error) {
|
641
|
-
if (!(error instanceof Error))
|
642
|
-
return false;
|
643
|
-
const errorMessage = error.message.toLowerCase();
|
644
|
-
if (error.message.includes('rate_limit_error'))
|
645
|
-
return true;
|
646
|
-
// Check for Anthropic-specific error patterns
|
647
|
-
if (error.message.includes('Anthropic API error:')) {
|
648
|
-
// Extract the actual error content
|
649
|
-
const match = error.message.match(/{"type":"error","error":({.*})}/);
|
650
|
-
if (match) {
|
651
|
-
try {
|
652
|
-
const errorData = JSON.parse(match[1]);
|
653
|
-
const errorType = errorData.type?.toLowerCase() || '';
|
654
|
-
const errorMsg = errorData.message?.toLowerCase() || '';
|
655
|
-
return this.retryableErrorMessages.some((retryable) => errorType.includes(retryable) || errorMsg.includes(retryable));
|
656
|
-
}
|
657
|
-
catch {
|
658
|
-
// If parsing fails, fall back to string matching
|
659
|
-
}
|
660
|
-
}
|
661
|
-
}
|
662
|
-
// Direct error message checking
|
663
|
-
return this.retryableErrorMessages.some((msg) => errorMessage.includes(msg));
|
664
|
-
}
|
665
|
-
/**
|
666
|
-
* Validates and potentially fixes the message history to ensure proper tool_use/tool_result pairing.
|
667
|
-
* This prevents the "tool_use ids were found without tool_result blocks" error after a failed request.
|
668
|
-
*/
|
669
|
-
validateAndFixMessages(messages) {
|
670
|
-
const fixedMessages = [];
|
671
|
-
let pendingToolCalls = [];
|
672
|
-
for (let i = 0; i < messages.length; i++) {
|
673
|
-
const msg = messages[i];
|
674
|
-
if (msg.role === 'assistant' && msg.tool_calls) {
|
675
|
-
// Track tool calls from assistant
|
676
|
-
fixedMessages.push(msg);
|
677
|
-
pendingToolCalls = msg.tool_calls.map((tc) => ({
|
678
|
-
id: tc.id,
|
679
|
-
name: tc.function.name,
|
680
|
-
}));
|
681
|
-
}
|
682
|
-
else if (msg.role === 'tool' && pendingToolCalls.length > 0) {
|
683
|
-
// Match tool results with pending tool calls
|
684
|
-
fixedMessages.push(msg);
|
685
|
-
// Remove the matched tool call
|
686
|
-
pendingToolCalls = pendingToolCalls.filter((tc) => tc.id !== msg.tool_call_id);
|
687
|
-
}
|
688
|
-
else if (msg.role === 'assistant' ||
|
689
|
-
msg.role === 'user' ||
|
690
|
-
msg.role === 'system') {
|
691
|
-
// If we have pending tool calls and encounter a non-tool message,
|
692
|
-
// we need to add dummy tool results to maintain consistency
|
693
|
-
if (pendingToolCalls.length > 0 && msg.role !== 'system') {
|
694
|
-
// Add dummy tool results for unmatched tool calls
|
695
|
-
for (const toolCall of pendingToolCalls) {
|
696
|
-
fixedMessages.push({
|
697
|
-
role: 'tool',
|
698
|
-
tool_call_id: toolCall.id,
|
699
|
-
content: 'Error: Tool execution was interrupted. Please retry.',
|
700
|
-
});
|
701
|
-
}
|
702
|
-
pendingToolCalls = [];
|
703
|
-
}
|
704
|
-
fixedMessages.push(msg);
|
705
|
-
}
|
706
|
-
else {
|
707
|
-
fixedMessages.push(msg);
|
708
|
-
}
|
709
|
-
}
|
710
|
-
// Handle any remaining pending tool calls at the end
|
711
|
-
if (pendingToolCalls.length > 0) {
|
712
|
-
for (const toolCall of pendingToolCalls) {
|
713
|
-
fixedMessages.push({
|
714
|
-
role: 'tool',
|
715
|
-
tool_call_id: toolCall.id,
|
716
|
-
content: 'Error: Tool execution was interrupted. Please retry.',
|
717
|
-
});
|
718
|
-
}
|
719
|
-
}
|
720
|
-
return fixedMessages;
|
721
|
-
}
|
722
275
|
/**
|
723
276
|
* Anthropic always requires payment (API key or OAuth)
|
724
277
|
*/
|
@@ -770,5 +323,243 @@ ${llxprtPrompts}`;
|
|
770
323
|
async isAuthenticated() {
|
771
324
|
return super.isAuthenticated();
|
772
325
|
}
|
326
|
+
/**
|
327
|
+
* Generate chat completion with IContent interface
|
328
|
+
* Convert IContent directly to Anthropic API format
|
329
|
+
*/
|
330
|
+
async *generateChatCompletion(content, tools) {
|
331
|
+
// Convert IContent directly to Anthropic API format (no IMessage!)
|
332
|
+
const anthropicMessages = [];
|
333
|
+
// Extract system message if present
|
334
|
+
let systemMessage;
|
335
|
+
for (const c of content) {
|
336
|
+
if (c.speaker === 'human') {
|
337
|
+
const textBlock = c.blocks.find((b) => b.type === 'text');
|
338
|
+
// Check for system message (this is a hack for now - should be explicit)
|
339
|
+
if (anthropicMessages.length === 0 &&
|
340
|
+
textBlock?.text?.includes('You are')) {
|
341
|
+
systemMessage = textBlock.text;
|
342
|
+
continue;
|
343
|
+
}
|
344
|
+
anthropicMessages.push({
|
345
|
+
role: 'user',
|
346
|
+
content: textBlock?.text || '',
|
347
|
+
});
|
348
|
+
}
|
349
|
+
else if (c.speaker === 'ai') {
|
350
|
+
const textBlocks = c.blocks.filter((b) => b.type === 'text');
|
351
|
+
const toolCallBlocks = c.blocks.filter((b) => b.type === 'tool_call');
|
352
|
+
if (toolCallBlocks.length > 0) {
|
353
|
+
// Build content array with text and tool_use blocks
|
354
|
+
const contentArray = [];
|
355
|
+
// Add text if present
|
356
|
+
const contentText = textBlocks.map((b) => b.text).join('');
|
357
|
+
if (contentText) {
|
358
|
+
contentArray.push({ type: 'text', text: contentText });
|
359
|
+
}
|
360
|
+
// Add tool uses
|
361
|
+
for (const tc of toolCallBlocks) {
|
362
|
+
contentArray.push({
|
363
|
+
type: 'tool_use',
|
364
|
+
id: tc.id.replace(/^hist_tool_/, 'toolu_'),
|
365
|
+
name: tc.name,
|
366
|
+
input: tc.parameters,
|
367
|
+
});
|
368
|
+
}
|
369
|
+
anthropicMessages.push({
|
370
|
+
role: 'assistant',
|
371
|
+
content: contentArray,
|
372
|
+
});
|
373
|
+
}
|
374
|
+
else {
|
375
|
+
// Text-only message
|
376
|
+
const contentText = textBlocks.map((b) => b.text).join('');
|
377
|
+
anthropicMessages.push({
|
378
|
+
role: 'assistant',
|
379
|
+
content: contentText,
|
380
|
+
});
|
381
|
+
}
|
382
|
+
}
|
383
|
+
else if (c.speaker === 'tool') {
|
384
|
+
const toolResponseBlock = c.blocks.find((b) => b.type === 'tool_response');
|
385
|
+
if (!toolResponseBlock) {
|
386
|
+
throw new Error('Tool content must have a tool_response block');
|
387
|
+
}
|
388
|
+
// Anthropic expects tool results as user messages
|
389
|
+
anthropicMessages.push({
|
390
|
+
role: 'user',
|
391
|
+
content: [
|
392
|
+
{
|
393
|
+
type: 'tool_result',
|
394
|
+
tool_use_id: toolResponseBlock.callId.replace(/^hist_tool_/, 'toolu_'),
|
395
|
+
content: JSON.stringify(toolResponseBlock.result),
|
396
|
+
},
|
397
|
+
],
|
398
|
+
});
|
399
|
+
}
|
400
|
+
else {
|
401
|
+
throw new Error(`Unknown speaker type: ${c.speaker}`);
|
402
|
+
}
|
403
|
+
}
|
404
|
+
// Convert Gemini format tools to ITool format then use toolFormatter
|
405
|
+
const anthropicTools = tools
|
406
|
+
? (() => {
|
407
|
+
// First convert to ITool format
|
408
|
+
const iTools = tools[0].functionDeclarations.map((decl) => ({
|
409
|
+
type: 'function',
|
410
|
+
function: {
|
411
|
+
name: decl.name,
|
412
|
+
description: decl.description || '',
|
413
|
+
parameters: decl.parameters || {},
|
414
|
+
},
|
415
|
+
}));
|
416
|
+
// Then use the toolFormatter to properly convert to Anthropic format
|
417
|
+
// This handles schema validation, type conversion, and filtering
|
418
|
+
const converted = this.toolFormatter.toProviderFormat(iTools, 'anthropic');
|
419
|
+
return converted;
|
420
|
+
})()
|
421
|
+
: undefined;
|
422
|
+
// Ensure authentication
|
423
|
+
await this.updateClientWithResolvedAuth();
|
424
|
+
// Check OAuth mode
|
425
|
+
const authToken = await this.getAuthToken();
|
426
|
+
const isOAuth = authToken && authToken.startsWith('sk-ant-oat');
|
427
|
+
// Get streaming setting from ephemeral settings (default: enabled)
|
428
|
+
const streamingSetting = this._config?.getEphemeralSettings?.()?.['streaming'];
|
429
|
+
const streamingEnabled = streamingSetting !== 'disabled';
|
430
|
+
// Build request with proper typing
|
431
|
+
const requestBody = {
|
432
|
+
model: this.currentModel,
|
433
|
+
messages: anthropicMessages,
|
434
|
+
max_tokens: this.getMaxTokensForModel(this.currentModel),
|
435
|
+
stream: streamingEnabled,
|
436
|
+
...(this.modelParams || {}),
|
437
|
+
...(isOAuth
|
438
|
+
? {
|
439
|
+
system: "You are Claude Code, Anthropic's official CLI for Claude.",
|
440
|
+
}
|
441
|
+
: systemMessage
|
442
|
+
? { system: systemMessage }
|
443
|
+
: {}),
|
444
|
+
...(anthropicTools && anthropicTools.length > 0
|
445
|
+
? { tools: anthropicTools }
|
446
|
+
: {}),
|
447
|
+
};
|
448
|
+
// Make the API call directly with type assertion
|
449
|
+
const response = await this.anthropic.messages.create(requestBody);
|
450
|
+
if (streamingEnabled) {
|
451
|
+
// Handle streaming response - response is already a Stream when streaming is enabled
|
452
|
+
const stream = response;
|
453
|
+
let currentToolCall;
|
454
|
+
for await (const chunk of stream) {
|
455
|
+
if (chunk.type === 'content_block_start') {
|
456
|
+
if (chunk.content_block.type === 'tool_use') {
|
457
|
+
currentToolCall = {
|
458
|
+
id: chunk.content_block.id,
|
459
|
+
name: chunk.content_block.name,
|
460
|
+
input: '',
|
461
|
+
};
|
462
|
+
}
|
463
|
+
}
|
464
|
+
else if (chunk.type === 'content_block_delta') {
|
465
|
+
if (chunk.delta.type === 'text_delta') {
|
466
|
+
// Emit text immediately as IContent
|
467
|
+
yield {
|
468
|
+
speaker: 'ai',
|
469
|
+
blocks: [{ type: 'text', text: chunk.delta.text }],
|
470
|
+
};
|
471
|
+
}
|
472
|
+
else if (chunk.delta.type === 'input_json_delta' &&
|
473
|
+
currentToolCall) {
|
474
|
+
currentToolCall.input += chunk.delta.partial_json;
|
475
|
+
}
|
476
|
+
}
|
477
|
+
else if (chunk.type === 'content_block_stop') {
|
478
|
+
if (currentToolCall) {
|
479
|
+
// Emit tool call as IContent
|
480
|
+
try {
|
481
|
+
const input = JSON.parse(currentToolCall.input);
|
482
|
+
yield {
|
483
|
+
speaker: 'ai',
|
484
|
+
blocks: [
|
485
|
+
{
|
486
|
+
type: 'tool_call',
|
487
|
+
id: currentToolCall.id.replace(/^toolu_/, 'hist_tool_'),
|
488
|
+
name: currentToolCall.name,
|
489
|
+
parameters: input,
|
490
|
+
},
|
491
|
+
],
|
492
|
+
};
|
493
|
+
}
|
494
|
+
catch (_e) {
|
495
|
+
// If parsing fails, emit with string parameters
|
496
|
+
yield {
|
497
|
+
speaker: 'ai',
|
498
|
+
blocks: [
|
499
|
+
{
|
500
|
+
type: 'tool_call',
|
501
|
+
id: currentToolCall.id.replace(/^toolu_/, 'hist_tool_'),
|
502
|
+
name: currentToolCall.name,
|
503
|
+
parameters: currentToolCall.input,
|
504
|
+
},
|
505
|
+
],
|
506
|
+
};
|
507
|
+
}
|
508
|
+
currentToolCall = undefined;
|
509
|
+
}
|
510
|
+
}
|
511
|
+
else if (chunk.type === 'message_delta' && chunk.usage) {
|
512
|
+
// Emit usage metadata
|
513
|
+
yield {
|
514
|
+
speaker: 'ai',
|
515
|
+
blocks: [],
|
516
|
+
metadata: {
|
517
|
+
usage: {
|
518
|
+
promptTokens: chunk.usage.input_tokens || 0,
|
519
|
+
completionTokens: chunk.usage.output_tokens || 0,
|
520
|
+
totalTokens: (chunk.usage.input_tokens || 0) +
|
521
|
+
(chunk.usage.output_tokens || 0),
|
522
|
+
},
|
523
|
+
},
|
524
|
+
};
|
525
|
+
}
|
526
|
+
}
|
527
|
+
}
|
528
|
+
else {
|
529
|
+
// Handle non-streaming response
|
530
|
+
const message = response;
|
531
|
+
const blocks = [];
|
532
|
+
// Process content blocks
|
533
|
+
for (const contentBlock of message.content) {
|
534
|
+
if (contentBlock.type === 'text') {
|
535
|
+
blocks.push({ type: 'text', text: contentBlock.text });
|
536
|
+
}
|
537
|
+
else if (contentBlock.type === 'tool_use') {
|
538
|
+
blocks.push({
|
539
|
+
type: 'tool_call',
|
540
|
+
id: contentBlock.id.replace(/^toolu_/, 'hist_tool_'),
|
541
|
+
name: contentBlock.name,
|
542
|
+
parameters: contentBlock.input,
|
543
|
+
});
|
544
|
+
}
|
545
|
+
}
|
546
|
+
// Build response IContent
|
547
|
+
const result = {
|
548
|
+
speaker: 'ai',
|
549
|
+
blocks,
|
550
|
+
};
|
551
|
+
// Add usage metadata if present
|
552
|
+
if (message.usage) {
|
553
|
+
result.metadata = {
|
554
|
+
usage: {
|
555
|
+
promptTokens: message.usage.input_tokens,
|
556
|
+
completionTokens: message.usage.output_tokens,
|
557
|
+
totalTokens: message.usage.input_tokens + message.usage.output_tokens,
|
558
|
+
},
|
559
|
+
};
|
560
|
+
}
|
561
|
+
yield result;
|
562
|
+
}
|
563
|
+
}
|
773
564
|
}
|
774
565
|
//# sourceMappingURL=AnthropicProvider.js.map
|