@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.
Files changed (82) hide show
  1. package/dist/src/adapters/IStreamAdapter.d.ts +3 -3
  2. package/dist/src/auth/types.d.ts +4 -4
  3. package/dist/src/config/index.d.ts +7 -0
  4. package/dist/src/config/index.js +8 -0
  5. package/dist/src/config/index.js.map +1 -0
  6. package/dist/src/core/client.d.ts +9 -21
  7. package/dist/src/core/client.js +46 -144
  8. package/dist/src/core/client.js.map +1 -1
  9. package/dist/src/core/compression-config.d.ts +1 -1
  10. package/dist/src/core/compression-config.js +4 -5
  11. package/dist/src/core/compression-config.js.map +1 -1
  12. package/dist/src/core/coreToolScheduler.js +50 -15
  13. package/dist/src/core/coreToolScheduler.js.map +1 -1
  14. package/dist/src/core/geminiChat.d.ts +51 -2
  15. package/dist/src/core/geminiChat.js +592 -93
  16. package/dist/src/core/geminiChat.js.map +1 -1
  17. package/dist/src/core/nonInteractiveToolExecutor.js +70 -19
  18. package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -1
  19. package/dist/src/index.d.ts +1 -2
  20. package/dist/src/index.js +2 -2
  21. package/dist/src/index.js.map +1 -1
  22. package/dist/src/providers/BaseProvider.d.ts +8 -3
  23. package/dist/src/providers/BaseProvider.js.map +1 -1
  24. package/dist/src/providers/IProvider.d.ts +9 -3
  25. package/dist/src/providers/LoggingProviderWrapper.d.ts +10 -3
  26. package/dist/src/providers/LoggingProviderWrapper.js +33 -27
  27. package/dist/src/providers/LoggingProviderWrapper.js.map +1 -1
  28. package/dist/src/providers/ProviderContentGenerator.d.ts +2 -2
  29. package/dist/src/providers/ProviderContentGenerator.js +9 -6
  30. package/dist/src/providers/ProviderContentGenerator.js.map +1 -1
  31. package/dist/src/providers/anthropic/AnthropicProvider.d.ts +12 -17
  32. package/dist/src/providers/anthropic/AnthropicProvider.js +238 -447
  33. package/dist/src/providers/anthropic/AnthropicProvider.js.map +1 -1
  34. package/dist/src/providers/gemini/GeminiProvider.d.ts +12 -6
  35. package/dist/src/providers/gemini/GeminiProvider.js +184 -458
  36. package/dist/src/providers/gemini/GeminiProvider.js.map +1 -1
  37. package/dist/src/providers/openai/ConversationCache.d.ts +3 -3
  38. package/dist/src/providers/openai/IChatGenerateParams.d.ts +9 -4
  39. package/dist/src/providers/openai/OpenAIProvider.d.ts +14 -61
  40. package/dist/src/providers/openai/OpenAIProvider.js +270 -575
  41. package/dist/src/providers/openai/OpenAIProvider.js.map +1 -1
  42. package/dist/src/providers/openai/buildResponsesRequest.d.ts +3 -3
  43. package/dist/src/providers/openai/buildResponsesRequest.js +67 -37
  44. package/dist/src/providers/openai/buildResponsesRequest.js.map +1 -1
  45. package/dist/src/providers/openai/estimateRemoteTokens.d.ts +2 -2
  46. package/dist/src/providers/openai/estimateRemoteTokens.js +21 -8
  47. package/dist/src/providers/openai/estimateRemoteTokens.js.map +1 -1
  48. package/dist/src/providers/openai/parseResponsesStream.d.ts +6 -2
  49. package/dist/src/providers/openai/parseResponsesStream.js +99 -391
  50. package/dist/src/providers/openai/parseResponsesStream.js.map +1 -1
  51. package/dist/src/providers/openai/syntheticToolResponses.d.ts +5 -5
  52. package/dist/src/providers/openai/syntheticToolResponses.js +102 -91
  53. package/dist/src/providers/openai/syntheticToolResponses.js.map +1 -1
  54. package/dist/src/providers/openai-responses/OpenAIResponsesProvider.d.ts +16 -17
  55. package/dist/src/providers/openai-responses/OpenAIResponsesProvider.js +222 -224
  56. package/dist/src/providers/openai-responses/OpenAIResponsesProvider.js.map +1 -1
  57. package/dist/src/providers/types.d.ts +1 -1
  58. package/dist/src/services/history/ContentConverters.d.ts +6 -1
  59. package/dist/src/services/history/ContentConverters.js +155 -18
  60. package/dist/src/services/history/ContentConverters.js.map +1 -1
  61. package/dist/src/services/history/HistoryService.d.ts +52 -0
  62. package/dist/src/services/history/HistoryService.js +245 -93
  63. package/dist/src/services/history/HistoryService.js.map +1 -1
  64. package/dist/src/services/history/IContent.d.ts +4 -0
  65. package/dist/src/services/history/IContent.js.map +1 -1
  66. package/dist/src/telemetry/types.d.ts +16 -4
  67. package/dist/src/telemetry/types.js.map +1 -1
  68. package/dist/src/tools/IToolFormatter.d.ts +2 -2
  69. package/dist/src/tools/ToolFormatter.d.ts +3 -3
  70. package/dist/src/tools/ToolFormatter.js +80 -37
  71. package/dist/src/tools/ToolFormatter.js.map +1 -1
  72. package/dist/src/tools/todo-schemas.d.ts +4 -4
  73. package/package.json +8 -7
  74. package/dist/src/core/ContentGeneratorAdapter.d.ts +0 -37
  75. package/dist/src/core/ContentGeneratorAdapter.js +0 -58
  76. package/dist/src/core/ContentGeneratorAdapter.js.map +0 -1
  77. package/dist/src/providers/IMessage.d.ts +0 -38
  78. package/dist/src/providers/IMessage.js +0 -17
  79. package/dist/src/providers/IMessage.js.map +0 -1
  80. package/dist/src/providers/adapters/GeminiCompatibleWrapper.d.ts +0 -69
  81. package/dist/src/providers/adapters/GeminiCompatibleWrapper.js +0 -577
  82. 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