call-ai 0.8.4 → 0.8.5-dev-preview

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/api.js CHANGED
@@ -1,7 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getMeta = void 0;
3
4
  exports.callAI = callAI;
4
5
  const strategies_1 = require("./strategies");
6
+ const response_metadata_1 = require("./response-metadata");
7
+ Object.defineProperty(exports, "getMeta", { enumerable: true, get: function () { return response_metadata_1.getMeta; } });
8
+ const key_management_1 = require("./key-management");
9
+ const error_handling_1 = require("./error-handling");
10
+ const api_core_1 = require("./api-core");
11
+ const non_streaming_1 = require("./non-streaming");
12
+ const streaming_1 = require("./streaming");
5
13
  // Import package version for debugging
6
14
  // eslint-disable-next-line @typescript-eslint/no-var-requires
7
15
  const PACKAGE_VERSION = require("../package.json").version;
@@ -18,6 +26,9 @@ const FALLBACK_MODEL = "openrouter/auto";
18
26
  function callAI(prompt, options = {}) {
19
27
  // Check if we need to force streaming based on model strategy
20
28
  const schemaStrategy = (0, strategies_1.chooseSchemaStrategy)(options.model, options.schema || null);
29
+ if (!options.max_tokens) {
30
+ options.max_tokens = 100000;
31
+ }
21
32
  // Handle special case: Claude with tools requires streaming
22
33
  if (!options.stream && schemaStrategy.shouldForceStream) {
23
34
  // Buffer streaming results into a single response
@@ -32,7 +43,9 @@ function callAI(prompt, options = {}) {
32
43
  const streamPromise = (async () => {
33
44
  // Do setup and validation before returning the generator
34
45
  const { endpoint, requestOptions, model, schemaStrategy } = prepareRequestParams(prompt, { ...options, stream: true });
35
- if (options.debug) {
46
+ // Use either explicit debug option or global debug flag
47
+ const debug = options.debug || key_management_1.globalDebug;
48
+ if (debug) {
36
49
  console.log(`[callAI:${PACKAGE_VERSION}] Making fetch request to: ${endpoint}`);
37
50
  console.log(`[callAI:${PACKAGE_VERSION}] With model: ${model}`);
38
51
  console.log(`[callAI:${PACKAGE_VERSION}] Request headers:`, JSON.stringify(requestOptions.headers));
@@ -90,7 +103,7 @@ function callAI(prompt, options = {}) {
90
103
  let isInvalidModel = false;
91
104
  try {
92
105
  // Check if this is an invalid model error
93
- const modelCheckResult = await checkForInvalidModelError(clonedResponse, model, false, options.skipRetry, options.debug);
106
+ const modelCheckResult = await (0, error_handling_1.checkForInvalidModelError)(clonedResponse, model, options.debug);
94
107
  isInvalidModel = modelCheckResult.isInvalidModel;
95
108
  if (isInvalidModel) {
96
109
  if (options.debug) {
@@ -208,7 +221,7 @@ function callAI(prompt, options = {}) {
208
221
  if (options.debug) {
209
222
  console.log(`[callAI:${PACKAGE_VERSION}] Response OK, creating streaming generator`);
210
223
  }
211
- return createStreamingGenerator(response, options, schemaStrategy, model);
224
+ return (0, streaming_1.createStreamingGenerator)(response, options, schemaStrategy, model);
212
225
  })();
213
226
  // For backward compatibility with v0.6.x where users didn't await the result
214
227
  if (process.env.NODE_ENV !== "production") {
@@ -218,7 +231,7 @@ function callAI(prompt, options = {}) {
218
231
  }
219
232
  // Create a proxy object that acts both as a Promise and an AsyncGenerator for backward compatibility
220
233
  // @ts-ignore - We're deliberately implementing a proxy with dual behavior
221
- return createBackwardCompatStreamingProxy(streamPromise);
234
+ return (0, api_core_1.createBackwardCompatStreamingProxy)(streamPromise);
222
235
  }
223
236
  /**
224
237
  * Buffer streaming results into a single response for cases where
@@ -233,157 +246,54 @@ async function bufferStreamingResults(prompt, options) {
233
246
  try {
234
247
  // Get streaming generator
235
248
  const generator = (await callAI(prompt, streamingOptions));
236
- // Buffer all chunks
237
- let finalResult = "";
238
- let chunkCount = 0;
239
- for await (const chunk of generator) {
240
- finalResult = chunk; // Each chunk contains the full accumulated text
241
- chunkCount++;
249
+ // For Claude JSON responses, take only the last chunk (the final processed result)
250
+ // For all other cases, concatenate chunks as before
251
+ const isClaudeJson = /claude/.test(options.model || "") && options.schema;
252
+ if (isClaudeJson) {
253
+ // For Claude with JSON schema, we only want the last yielded value
254
+ // which will be the complete, properly processed JSON
255
+ let lastChunk = "";
256
+ for await (const chunk of generator) {
257
+ // Replace the last chunk entirely instead of concatenating
258
+ lastChunk = chunk;
259
+ }
260
+ return lastChunk;
261
+ }
262
+ else {
263
+ // For all other cases, concatenate chunks
264
+ let result = "";
265
+ for await (const chunk of generator) {
266
+ result += chunk;
267
+ }
268
+ return result;
242
269
  }
243
- return finalResult;
244
270
  }
245
271
  catch (error) {
246
- handleApiError(error, "Streaming buffer error", options.debug);
272
+ // Handle errors with standard API error handling
273
+ await (0, error_handling_1.handleApiError)(error, "Buffered streaming", options.debug, {
274
+ apiKey: options.apiKey,
275
+ endpoint: options.endpoint,
276
+ skipRefresh: options.skipRefresh,
277
+ refreshToken: options.refreshToken,
278
+ updateRefreshToken: options.updateRefreshToken,
279
+ });
280
+ // If we get here, key was refreshed successfully, retry the operation with the new key
281
+ // Retry with the refreshed key
282
+ return bufferStreamingResults(prompt, {
283
+ ...options,
284
+ apiKey: key_management_1.keyStore.current || undefined, // Use the refreshed key from keyStore
285
+ });
247
286
  }
287
+ // This line should never be reached, but it satisfies the linter by ensuring
288
+ // all code paths return a value
289
+ throw new Error("Unexpected code path in bufferStreamingResults");
248
290
  }
249
291
  /**
250
292
  * Standardized API error handler
251
293
  */
252
- /**
253
- * Create a proxy that acts both as a Promise and an AsyncGenerator for backward compatibility
254
- * @internal This is for internal use only, not part of public API
255
- */
256
- function createBackwardCompatStreamingProxy(promise) {
257
- // Create a proxy that forwards methods to the Promise or AsyncGenerator as appropriate
258
- return new Proxy({}, {
259
- get(target, prop) {
260
- // First check if it's an AsyncGenerator method (needed for for-await)
261
- if (prop === "next" ||
262
- prop === "throw" ||
263
- prop === "return" ||
264
- prop === Symbol.asyncIterator) {
265
- // Create wrapper functions that await the Promise first
266
- if (prop === Symbol.asyncIterator) {
267
- return function () {
268
- return {
269
- // Implement async iterator that gets the generator first
270
- async next(value) {
271
- try {
272
- const generator = await promise;
273
- return generator.next(value);
274
- }
275
- catch (error) {
276
- // Turn Promise rejection into iterator result with error thrown
277
- return Promise.reject(error);
278
- }
279
- },
280
- };
281
- };
282
- }
283
- // Methods like next, throw, return
284
- return async function (value) {
285
- const generator = await promise;
286
- return generator[prop](value);
287
- };
288
- }
289
- // Then check if it's a Promise method
290
- if (prop === "then" || prop === "catch" || prop === "finally") {
291
- return promise[prop].bind(promise);
292
- }
293
- return undefined;
294
- },
295
- });
296
- }
297
- /**
298
- * Standardized API error handler
299
- */
300
- function handleApiError(error, context, debug = false) {
301
- if (debug) {
302
- console.error(`[callAI:${context}]:`, error);
303
- }
304
- throw new Error(`${context}: ${String(error)}`);
305
- }
306
- /**
307
- * Helper to check if an error indicates invalid model and handle fallback
308
- */
309
- async function checkForInvalidModelError(response, model, isRetry, skipRetry = false, debug = false) {
310
- // Skip retry immediately if skipRetry is true or if we're already retrying
311
- if (skipRetry || isRetry) {
312
- return { isInvalidModel: false };
313
- }
314
- // We want to check all 4xx errors, not just 400
315
- if (response.status < 400 || response.status >= 500) {
316
- return { isInvalidModel: false };
317
- }
318
- // Clone the response so we can read the body
319
- const clonedResponse = response.clone();
320
- try {
321
- const errorData = await clonedResponse.json();
322
- if (debug) {
323
- console.log(`[callAI:${PACKAGE_VERSION}] Checking for invalid model error:`, {
324
- model,
325
- statusCode: response.status,
326
- errorData,
327
- });
328
- }
329
- // Common patterns for invalid model errors across different providers
330
- const invalidModelPatterns = [
331
- "not a valid model",
332
- "model .* does not exist",
333
- "invalid model",
334
- "unknown model",
335
- "no provider was found",
336
- "fake-model", // For our test case
337
- "does-not-exist", // For our test case
338
- ];
339
- // Check if error message contains any of our patterns
340
- let errorMessage = "";
341
- if (errorData.error && errorData.error.message) {
342
- errorMessage = errorData.error.message.toLowerCase();
343
- }
344
- else if (errorData.message) {
345
- errorMessage = errorData.message.toLowerCase();
346
- }
347
- else {
348
- errorMessage = JSON.stringify(errorData).toLowerCase();
349
- }
350
- // Test the error message against each pattern
351
- const isInvalidModel = invalidModelPatterns.some((pattern) => errorMessage.includes(pattern.toLowerCase()));
352
- if (isInvalidModel && debug) {
353
- console.warn(`[callAI:${PACKAGE_VERSION}] Model ${model} not valid, will retry with ${FALLBACK_MODEL}`);
354
- }
355
- return { isInvalidModel, errorData };
356
- }
357
- catch (parseError) {
358
- // If we can't parse the response as JSON, try to read it as text
359
- if (debug) {
360
- console.error("Failed to parse error response as JSON:", parseError);
361
- }
362
- try {
363
- const textResponse = await response.clone().text();
364
- if (debug) {
365
- console.log("Error response as text:", textResponse);
366
- }
367
- // Even if it's not JSON, check if it contains any of our known patterns
368
- const lowerText = textResponse.toLowerCase();
369
- const isInvalidModel = lowerText.includes("invalid model") ||
370
- lowerText.includes("not exist") ||
371
- lowerText.includes("fake-model");
372
- if (isInvalidModel) {
373
- if (debug) {
374
- console.warn(`[callAI:${PACKAGE_VERSION}] Detected invalid model in text response for ${model}`);
375
- }
376
- }
377
- return { isInvalidModel, errorData: { text: textResponse } };
378
- }
379
- catch (textError) {
380
- if (debug) {
381
- console.error("Failed to read error response as text:", textError);
382
- }
383
- return { isInvalidModel: false };
384
- }
385
- }
386
- }
294
+ // createBackwardCompatStreamingProxy is imported from api-core.ts
295
+ // handleApiError is imported from error-handling.ts
296
+ // checkForInvalidModelError is imported from error-handling.ts
387
297
  /**
388
298
  * Prepare request parameters common to both streaming and non-streaming calls
389
299
  */
@@ -400,48 +310,59 @@ function prepareRequestParams(prompt, options) {
400
310
  // Get custom chat API origin if set
401
311
  const customChatOrigin = options.chatUrl ||
402
312
  (typeof window !== "undefined" ? window.CALLAI_CHAT_URL : null) ||
403
- (typeof process !== "undefined" && process.env ? process.env.CALLAI_CHAT_URL : null);
313
+ (typeof process !== "undefined" && process.env
314
+ ? process.env.CALLAI_CHAT_URL
315
+ : null);
404
316
  // Use custom origin or default OpenRouter URL
405
317
  const endpoint = options.endpoint ||
406
- (customChatOrigin ? `${customChatOrigin}/api/v1/chat/completions` : "https://openrouter.ai/api/v1/chat/completions");
318
+ (customChatOrigin
319
+ ? `${customChatOrigin}/api/v1/chat/completions`
320
+ : "https://openrouter.ai/api/v1/chat/completions");
407
321
  // Handle both string prompts and message arrays for backward compatibility
408
322
  const messages = Array.isArray(prompt)
409
323
  ? prompt
410
324
  : [{ role: "user", content: prompt }];
411
- // Build request parameters
325
+ // Common parameters for both streaming and non-streaming
412
326
  const requestParams = {
413
- model: model,
414
- stream: options.stream === true,
415
- messages: messages,
327
+ model,
328
+ messages,
329
+ temperature: options.temperature !== undefined ? options.temperature : 0.7,
330
+ top_p: options.topP !== undefined ? options.topP : 1,
331
+ max_tokens: options.maxTokens || 2048,
332
+ stream: options.stream !== undefined ? options.stream : false,
416
333
  };
417
- // Support for multimodal content (like images)
418
- if (options.modalities && options.modalities.length > 0) {
419
- requestParams.modalities = options.modalities;
334
+ // Add optional parameters if specified
335
+ if (options.stop) {
336
+ // Handle both single string and array of stop sequences
337
+ requestParams.stop = Array.isArray(options.stop)
338
+ ? options.stop
339
+ : [options.stop];
420
340
  }
421
- // Apply the strategy's request preparation
422
- const strategyParams = schemaStrategy.prepareRequest(schema, messages);
423
- // If the strategy returns custom messages, use those instead
424
- if (strategyParams.messages) {
425
- requestParams.messages = strategyParams.messages;
341
+ // Add response_format parameter for models that support JSON output
342
+ if (options.responseFormat === "json") {
343
+ requestParams.response_format = { type: "json_object" };
426
344
  }
427
- // Add all other strategy parameters
428
- Object.entries(strategyParams).forEach(([key, value]) => {
429
- if (key !== "messages") {
430
- requestParams[key] = value;
431
- }
432
- });
433
- // Add any other options provided, but exclude internal keys
434
- Object.entries(options).forEach(([key, value]) => {
435
- if (!["apiKey", "model", "endpoint", "stream", "schema"].includes(key)) {
436
- requestParams[key] = value;
437
- }
438
- });
345
+ // Add schema structure if provided (for function calling/JSON mode)
346
+ if (schema) {
347
+ // Apply schema-specific parameters using the selected strategy
348
+ Object.assign(requestParams, schemaStrategy.prepareRequest(schema, messages));
349
+ }
350
+ // HTTP headers for the request
351
+ const headers = {
352
+ Authorization: `Bearer ${apiKey}`,
353
+ "Content-Type": "application/json",
354
+ "HTTP-Referer": options.referer || "https://vibes.diy",
355
+ "X-Title": options.title || "Vibes",
356
+ };
357
+ // Add any additional headers
358
+ if (options.headers) {
359
+ Object.assign(headers, options.headers);
360
+ }
361
+ // Build the requestOptions object for fetch
439
362
  const requestOptions = {
440
363
  method: "POST",
441
364
  headers: {
442
- Authorization: `Bearer ${apiKey}`,
443
- "HTTP-Referer": "https://vibes.diy",
444
- "X-Title": "Vibes",
365
+ ...headers,
445
366
  "Content-Type": "application/json",
446
367
  },
447
368
  body: JSON.stringify(requestParams),
@@ -459,25 +380,40 @@ function prepareRequestParams(prompt, options) {
459
380
  */
460
381
  async function callAINonStreaming(prompt, options = {}, isRetry = false) {
461
382
  try {
383
+ // Start timing for metadata
384
+ const startTime = Date.now();
385
+ // Create metadata object
386
+ const meta = {
387
+ model: options.model || "unknown",
388
+ timing: {
389
+ startTime: startTime,
390
+ },
391
+ };
462
392
  const { endpoint, requestOptions, model, schemaStrategy } = prepareRequestParams(prompt, options);
463
393
  const response = await fetch(endpoint, requestOptions);
394
+ // We don't store the raw Response object in metadata anymore
464
395
  // Handle HTTP errors, with potential fallback for invalid model
465
396
  if (!response.ok || response.status >= 400) {
466
- const { isInvalidModel } = await checkForInvalidModelError(response, model, isRetry, options.skipRetry, options.debug);
397
+ const { isInvalidModel } = await (0, error_handling_1.checkForInvalidModelError)(response, model, options.debug);
467
398
  if (isInvalidModel) {
468
399
  // Retry with fallback model
469
400
  return callAINonStreaming(prompt, { ...options, model: FALLBACK_MODEL }, true);
470
401
  }
471
- throw new Error(`HTTP error! Status: ${response.status}`);
402
+ // Create a proper error object with the status code preserved
403
+ const error = new Error(`HTTP error! Status: ${response.status}`);
404
+ // Add status code as a property of the error object
405
+ error.status = response.status;
406
+ error.statusCode = response.status; // Add statusCode for compatibility with different error patterns
407
+ throw error;
472
408
  }
473
409
  let result;
474
410
  // For Claude, use text() instead of json() to avoid potential hanging
475
411
  if (/claude/i.test(model)) {
476
412
  try {
477
- result = await extractClaudeResponse(response);
413
+ result = await (0, non_streaming_1.extractClaudeResponse)(response);
478
414
  }
479
415
  catch (error) {
480
- handleApiError(error, "Claude API response processing failed", options.debug);
416
+ (0, error_handling_1.handleApiError)(error, "Claude API response processing failed", options.debug);
481
417
  }
482
418
  }
483
419
  else {
@@ -508,309 +444,41 @@ async function callAINonStreaming(prompt, options = {}, isRetry = false) {
508
444
  });
509
445
  }
510
446
  // Extract content from the response
511
- const content = extractContent(result, schemaStrategy);
512
- // Process the content based on model type
513
- return schemaStrategy.processResponse(content);
514
- }
515
- catch (error) {
516
- handleApiError(error, "Non-streaming API call", options.debug);
517
- }
518
- }
519
- /**
520
- * Extract content from API response accounting for different formats
521
- */
522
- function extractContent(result, schemaStrategy) {
523
- // Find tool use content or normal content
524
- let content;
525
- // Extract tool use content if necessary
526
- if (schemaStrategy.strategy === "tool_mode" &&
527
- result.stop_reason === "tool_use") {
528
- // Try to find tool_use block in different response formats
529
- if (result.content && Array.isArray(result.content)) {
530
- const toolUseBlock = result.content.find((block) => block.type === "tool_use");
531
- if (toolUseBlock) {
532
- content = toolUseBlock;
533
- }
534
- }
535
- if (!content && result.choices && Array.isArray(result.choices)) {
536
- const choice = result.choices[0];
537
- if (choice.message && Array.isArray(choice.message.content)) {
538
- const toolUseBlock = choice.message.content.find((block) => block.type === "tool_use");
539
- if (toolUseBlock) {
540
- content = toolUseBlock;
541
- }
542
- }
447
+ const content = (0, non_streaming_1.extractContent)(result, schemaStrategy);
448
+ // Store the raw response data for user access
449
+ if (result) {
450
+ // Store the parsed JSON result from the API call
451
+ meta.rawResponse = result;
543
452
  }
544
- }
545
- // If no tool use content was found, use the standard message content
546
- if (!content) {
547
- if (!result.choices || !result.choices.length) {
548
- throw new Error("Invalid response format from API");
453
+ // Update model info
454
+ meta.model = model;
455
+ // Update timing info
456
+ if (meta.timing) {
457
+ meta.timing.endTime = Date.now();
458
+ meta.timing.duration = meta.timing.endTime - meta.timing.startTime;
549
459
  }
550
- content = result.choices[0]?.message?.content || "";
551
- }
552
- return content;
553
- }
554
- /**
555
- * Extract response from Claude API with timeout handling
556
- */
557
- async function extractClaudeResponse(response) {
558
- let textResponse;
559
- const textPromise = response.text();
560
- const timeoutPromise = new Promise((_resolve, reject) => {
561
- setTimeout(() => {
562
- reject(new Error("Text extraction timed out after 5 seconds"));
563
- }, 5000);
564
- });
565
- try {
566
- textResponse = (await Promise.race([
567
- textPromise,
568
- timeoutPromise,
569
- ]));
570
- }
571
- catch (textError) {
572
- // Always log timeout errors
573
- console.error(`Text extraction timed out or failed:`, textError);
574
- throw new Error("Claude response text extraction timed out. This is likely an issue with the Claude API's response format.");
575
- }
576
- try {
577
- return JSON.parse(textResponse);
578
- }
579
- catch (err) {
580
- // Always log JSON parsing errors
581
- console.error(`Failed to parse Claude response as JSON:`, err);
582
- throw new Error(`Failed to parse Claude response as JSON: ${err}`);
583
- }
584
- }
585
- /**
586
- * Generator factory function for streaming API calls
587
- * This is called after the fetch is made and response is validated
588
- *
589
- * Note: Even though we checked response.ok before creating this generator,
590
- * we need to be prepared for errors that may occur during streaming. Some APIs
591
- * return a 200 OK initially but then deliver error information in the stream.
592
- */
593
- async function* createStreamingGenerator(response, options, schemaStrategy, model) {
594
- if (options.debug) {
595
- console.log(`[callAI:${PACKAGE_VERSION}] Starting streaming generator with model: ${model}`);
596
- console.log(`[callAI:${PACKAGE_VERSION}] Response status:`, response.status);
597
- console.log(`[callAI:${PACKAGE_VERSION}] Response type:`, response.type);
598
- console.log(`[callAI:${PACKAGE_VERSION}] Response Content-Type:`, response.headers.get("content-type"));
599
- }
600
- try {
601
- // Handle streaming response
602
- if (!response.body) {
603
- throw new Error("Response body is undefined - API endpoint may not support streaming");
604
- }
605
- const reader = response.body.getReader();
606
- const decoder = new TextDecoder();
607
- let completeText = "";
608
- let chunkCount = 0;
609
- let toolCallsAssembled = "";
610
- while (true) {
611
- const { done, value } = await reader.read();
612
- if (done) {
613
- if (options.debug) {
614
- console.log(`[callAI:${PACKAGE_VERSION}] Stream done=true after ${chunkCount} chunks`);
615
- console.log(`[callAI-streaming:complete v${PACKAGE_VERSION}] Stream finished after ${chunkCount} chunks`);
616
- }
617
- break;
618
- }
619
- // Increment chunk counter before processing
620
- chunkCount++;
621
- const chunk = decoder.decode(value);
622
- if (options.debug) {
623
- console.log(`[callAI:${PACKAGE_VERSION}] Raw chunk #${chunkCount} (${chunk.length} bytes):`, chunk.length > 200 ? chunk.substring(0, 200) + "..." : chunk);
624
- }
625
- const lines = chunk.split("\n").filter((line) => line.trim() !== "");
626
- if (options.debug) {
627
- console.log(`[callAI:${PACKAGE_VERSION}] Chunk #${chunkCount} contains ${lines.length} non-empty lines`);
628
- }
629
- for (const line of lines) {
630
- if (options.debug) {
631
- console.log(`[callAI:${PACKAGE_VERSION}] Processing line:`, line.length > 100 ? line.substring(0, 100) + "..." : line);
632
- }
633
- if (line.startsWith("data: ")) {
634
- let data = line.slice(6);
635
- if (data === "[DONE]") {
636
- if (options.debug) {
637
- console.log(`[callAI:${PACKAGE_VERSION}] Received [DONE] marker`);
638
- }
639
- break;
640
- }
641
- if (options.debug) {
642
- console.log(`[callAI:raw] ${line}`);
643
- }
644
- // Skip [DONE] marker or OPENROUTER PROCESSING lines
645
- if (line.includes("[DONE]") ||
646
- line.includes("OPENROUTER PROCESSING")) {
647
- continue;
648
- }
649
- try {
650
- const jsonLine = line.replace("data: ", "");
651
- if (!jsonLine.trim()) {
652
- if (options.debug) {
653
- console.log(`[callAI:${PACKAGE_VERSION}] Empty JSON line after data: prefix`);
654
- }
655
- continue;
656
- }
657
- if (options.debug) {
658
- console.log(`[callAI:${PACKAGE_VERSION}] JSON line (first 100 chars):`, jsonLine.length > 100
659
- ? jsonLine.substring(0, 100) + "..."
660
- : jsonLine);
661
- }
662
- // Parse the JSON chunk
663
- let json;
664
- try {
665
- json = JSON.parse(jsonLine);
666
- if (options.debug) {
667
- console.log(`[callAI:${PACKAGE_VERSION}] Parsed JSON:`, JSON.stringify(json).substring(0, 1000));
668
- }
669
- }
670
- catch (parseError) {
671
- if (options.debug) {
672
- console.error(`[callAI:${PACKAGE_VERSION}] JSON parse error:`, parseError);
673
- }
674
- continue;
675
- }
676
- // Enhanced error detection - check for BOTH error and json.error
677
- // Some APIs return 200 OK but then deliver errors in the stream
678
- if (json.error || (typeof json === "object" && "error" in json)) {
679
- if (options.debug) {
680
- console.error(`[callAI:${PACKAGE_VERSION}] Detected error in streaming response:`, json);
681
- }
682
- // Create a detailed error object similar to our HTTP error handling
683
- const errorMessage = json.error?.message ||
684
- json.error?.toString() ||
685
- JSON.stringify(json.error || json);
686
- const detailedError = new Error(`API streaming error: ${errorMessage}`);
687
- // Add error metadata
688
- detailedError.status = json.error?.status || 400;
689
- detailedError.statusText =
690
- json.error?.type || "Bad Request";
691
- detailedError.details = JSON.stringify(json.error || json);
692
- console.error(`[callAI:${PACKAGE_VERSION}] Throwing stream error:`, detailedError);
693
- throw detailedError;
694
- }
695
- // Handle tool use response - Claude with schema cases
696
- const isClaudeWithSchema = /claude/i.test(model) && schemaStrategy.strategy === "tool_mode";
697
- if (isClaudeWithSchema) {
698
- // Claude streaming tool calls - need to assemble arguments
699
- if (json.choices && json.choices.length > 0) {
700
- const choice = json.choices[0];
701
- // Handle finish reason tool_calls
702
- if (choice.finish_reason === "tool_calls") {
703
- try {
704
- // Parse the assembled JSON
705
- completeText = toolCallsAssembled;
706
- yield completeText;
707
- continue;
708
- }
709
- catch (e) {
710
- console.error("[callAIStreaming] Error parsing assembled tool call:", e);
711
- }
712
- }
713
- // Assemble tool_calls arguments from delta
714
- if (choice.delta && choice.delta.tool_calls) {
715
- const toolCall = choice.delta.tool_calls[0];
716
- if (toolCall &&
717
- toolCall.function &&
718
- toolCall.function.arguments !== undefined) {
719
- toolCallsAssembled += toolCall.function.arguments;
720
- // We don't yield here to avoid partial JSON
721
- }
722
- }
723
- }
724
- }
725
- // Handle tool use response - old format
726
- if (isClaudeWithSchema &&
727
- (json.stop_reason === "tool_use" || json.type === "tool_use")) {
728
- // First try direct tool use object format
729
- if (json.type === "tool_use") {
730
- completeText = schemaStrategy.processResponse(json);
731
- yield completeText;
732
- continue;
733
- }
734
- // Extract the tool use content
735
- if (json.content && Array.isArray(json.content)) {
736
- const toolUseBlock = json.content.find((block) => block.type === "tool_use");
737
- if (toolUseBlock) {
738
- completeText = schemaStrategy.processResponse(toolUseBlock);
739
- yield completeText;
740
- continue;
741
- }
742
- }
743
- // Find tool_use in assistant's content blocks
744
- if (json.choices && Array.isArray(json.choices)) {
745
- const choice = json.choices[0];
746
- if (choice.message && Array.isArray(choice.message.content)) {
747
- const toolUseBlock = choice.message.content.find((block) => block.type === "tool_use");
748
- if (toolUseBlock) {
749
- completeText = schemaStrategy.processResponse(toolUseBlock);
750
- yield completeText;
751
- continue;
752
- }
753
- }
754
- // Handle case where the tool use is in the delta
755
- if (choice.delta && Array.isArray(choice.delta.content)) {
756
- const toolUseBlock = choice.delta.content.find((block) => block.type === "tool_use");
757
- if (toolUseBlock) {
758
- completeText = schemaStrategy.processResponse(toolUseBlock);
759
- yield completeText;
760
- continue;
761
- }
762
- }
763
- }
764
- }
765
- // Extract content from the delta
766
- if (json.choices?.[0]?.delta?.content !== undefined) {
767
- const content = json.choices[0].delta.content || "";
768
- // Treat all models the same - yield as content arrives
769
- completeText += content;
770
- yield schemaStrategy.processResponse(completeText);
771
- }
772
- // Handle message content format (non-streaming deltas)
773
- else if (json.choices?.[0]?.message?.content !== undefined) {
774
- const content = json.choices[0].message.content || "";
775
- completeText += content;
776
- yield schemaStrategy.processResponse(completeText);
777
- }
778
- // Handle content blocks for Claude/Anthropic response format
779
- else if (json.choices?.[0]?.message?.content &&
780
- Array.isArray(json.choices[0].message.content)) {
781
- const contentBlocks = json.choices[0].message.content;
782
- // Find text or tool_use blocks
783
- for (const block of contentBlocks) {
784
- if (block.type === "text") {
785
- completeText += block.text || "";
786
- }
787
- else if (isClaudeWithSchema && block.type === "tool_use") {
788
- completeText = schemaStrategy.processResponse(block);
789
- break; // We found what we need
790
- }
791
- }
792
- yield schemaStrategy.processResponse(completeText);
793
- }
794
- }
795
- catch (e) {
796
- if (options.debug) {
797
- console.error(`[callAIStreaming] Error parsing JSON chunk:`, e);
798
- }
799
- }
800
- }
801
- }
802
- }
803
- // We no longer need special error handling here as errors are thrown immediately
804
- // No extra error handling needed here - errors are thrown immediately
805
- // If we have assembled tool calls but haven't yielded them yet
806
- if (toolCallsAssembled && (!completeText || completeText.length === 0)) {
807
- return toolCallsAssembled;
808
- }
809
- // Ensure the final return has proper, processed content
810
- return schemaStrategy.processResponse(completeText);
460
+ // Process the content based on model type
461
+ const processedContent = schemaStrategy.processResponse(content);
462
+ // Box the string for WeakMap storage
463
+ const boxed = (0, response_metadata_1.boxString)(processedContent);
464
+ response_metadata_1.responseMetadata.set(boxed, meta);
465
+ return processedContent;
811
466
  }
812
467
  catch (error) {
813
- // Standardize error handling
814
- handleApiError(error, "Streaming API call", options.debug);
468
+ await (0, error_handling_1.handleApiError)(error, "Non-streaming API call", options.debug, {
469
+ apiKey: options.apiKey,
470
+ endpoint: options.endpoint,
471
+ skipRefresh: options.skipRefresh,
472
+ refreshToken: options.refreshToken,
473
+ updateRefreshToken: options.updateRefreshToken,
474
+ });
475
+ // If we get here, key was refreshed successfully, retry the operation with the new key
476
+ // Retry with the refreshed key
477
+ return callAINonStreaming(prompt, {
478
+ ...options,
479
+ apiKey: key_management_1.keyStore.current || undefined, // Use the refreshed key from keyStore
480
+ }, true); // Set isRetry to true
815
481
  }
482
+ // This line will never be reached, but it satisfies the linter
483
+ throw new Error("Unexpected code path in callAINonStreaming");
816
484
  }