@adaptic/lumic-utils 1.0.23 → 1.0.24
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/{apollo-client.client-DrSy1wz-.js → apollo-client.client-9wJcufhf.js} +4 -4
- package/dist/{apollo-client.client-DrSy1wz-.js.map → apollo-client.client-9wJcufhf.js.map} +1 -1
- package/dist/{apollo-client.client-FeVPHV0i.js → apollo-client.client-CyxU1hTu.js} +3 -3
- package/dist/{apollo-client.client-FeVPHV0i.js.map → apollo-client.client-CyxU1hTu.js.map} +1 -1
- package/dist/{apollo-client.server-CJ6iRLa2.js → apollo-client.server-CbagxkmK.js} +3 -3
- package/dist/{apollo-client.server-CJ6iRLa2.js.map → apollo-client.server-CbagxkmK.js.map} +1 -1
- package/dist/{apollo-client.server-BEGHAF48.js → apollo-client.server-DB3jLbBx.js} +3 -3
- package/dist/{apollo-client.server-BEGHAF48.js.map → apollo-client.server-DB3jLbBx.js.map} +1 -1
- package/dist/{index-Dz0wKyTF.js → index-B4yVKGNR.js} +2 -2
- package/dist/{index-Dz0wKyTF.js.map → index-B4yVKGNR.js.map} +1 -1
- package/dist/{index-vRskhk1H.js → index-CL79JTWc.js} +2 -2
- package/dist/{index-vRskhk1H.js.map → index-CL79JTWc.js.map} +1 -1
- package/dist/{index-CWoI2dXN.js → index-EMQ1uJMh.js} +365 -274
- package/dist/{index-WGzi__C5.js.map → index-EMQ1uJMh.js.map} +1 -1
- package/dist/{index-WGzi__C5.js → index-KzQOh2uu.js} +365 -274
- package/dist/{index-CWoI2dXN.js.map → index-KzQOh2uu.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/test.cjs +21 -17
- package/dist/test.cjs.map +1 -1
- package/dist/test.mjs +21 -17
- package/dist/test.mjs.map +1 -1
- package/dist/types/functions/get-weather.d.ts +1 -1
- package/dist/types/functions/google-sheets.d.ts +2 -2
- package/dist/types/functions/json-llm-tools.d.ts +1 -1
- package/dist/types/functions/llm-anthropic.d.ts +1 -1
- package/dist/types/functions/llm-deepseek.d.ts +2 -2
- package/dist/types/functions/llm-openai-compatible.d.ts +2 -2
- package/dist/types/functions/llm-openai.d.ts +3 -3
- package/dist/types/functions/llm-utils.d.ts +1 -1
- package/dist/types/test-llm-functions-archive.d.ts +1 -1
- package/dist/types/test.d.ts +1 -1
- package/dist/types/types/openai-types.d.ts +10 -1
- package/dist/types/utils/aws-initialise.d.ts +4 -4
- package/package.json +2 -2
|
@@ -571,10 +571,19 @@ function getModelProvider(model) {
|
|
|
571
571
|
return SUPPORTED_MODELS[model].provider;
|
|
572
572
|
}
|
|
573
573
|
/**
|
|
574
|
-
* Default model tiers per provider for use with LLM_MODEL_MINI/NORMAL/ADVANCED env vars
|
|
574
|
+
* Default model tiers per provider for use with LLM_MODEL_MINI/NORMAL/ADVANCED env vars.
|
|
575
|
+
*
|
|
576
|
+
* OpenAI `advanced` was reverted from `gpt-5.5` back to `gpt-5.4` on 2026-05-30:
|
|
577
|
+
* the heavy tool-call prompt in adaptic-engine's trading-decision path was
|
|
578
|
+
* timing out even after the engine raised `LLM_CALL_TIMEOUT_MS` to 90s (5+
|
|
579
|
+
* tickers per loop still exceeded the budget). `gpt-5.5`'s 1M-context window
|
|
580
|
+
* is not required on that path — the prompt fits comfortably inside `gpt-5.4`'s
|
|
581
|
+
* envelope and `gpt-5.4` returns inside the original 25-30s p95. `gpt-5.5`
|
|
582
|
+
* remains registered in OPENAI_MODELS and reachable by explicit model id;
|
|
583
|
+
* only the `advanced` tier default is rolled back.
|
|
575
584
|
*/
|
|
576
585
|
const PROVIDER_DEFAULT_MODELS = {
|
|
577
|
-
openai: { mini: 'gpt-5.4-nano', normal: 'gpt-5.4-mini', advanced: 'gpt-5.
|
|
586
|
+
openai: { mini: 'gpt-5.4-nano', normal: 'gpt-5.4-mini', advanced: 'gpt-5.4' },
|
|
578
587
|
anthropic: { mini: 'claude-haiku-4-5', normal: 'claude-sonnet-4-6', advanced: 'claude-opus-4-7' },
|
|
579
588
|
deepseek: { mini: 'deepseek-v4-flash', normal: 'deepseek-v4-flash', advanced: 'deepseek-v4-pro' },
|
|
580
589
|
kimi: { mini: 'kimi-k2-0905-preview', normal: 'kimi-k2.5', advanced: 'kimi-k2.6' },
|
|
@@ -1528,11 +1537,13 @@ let openai;
|
|
|
1528
1537
|
function initializeOpenAI(apiKey) {
|
|
1529
1538
|
const key = apiKey || getSecrets().openai.apiKey;
|
|
1530
1539
|
if (!key) {
|
|
1531
|
-
throw new Error(
|
|
1540
|
+
throw new Error("OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set");
|
|
1532
1541
|
}
|
|
1533
1542
|
return new OpenAI({
|
|
1534
1543
|
apiKey: key,
|
|
1535
|
-
defaultHeaders: {
|
|
1544
|
+
defaultHeaders: {
|
|
1545
|
+
"User-Agent": "My Server-side Application (compatible; OpenAI API Client)",
|
|
1546
|
+
},
|
|
1536
1547
|
});
|
|
1537
1548
|
}
|
|
1538
1549
|
/**
|
|
@@ -1550,9 +1561,7 @@ function initializeOpenAI(apiKey) {
|
|
|
1550
1561
|
*/
|
|
1551
1562
|
async function fixJsonWithAI(jsonStr, options) {
|
|
1552
1563
|
// Backward compatibility: handle string apiKey as first positional param
|
|
1553
|
-
const opts = typeof options ===
|
|
1554
|
-
? { apiKey: options }
|
|
1555
|
-
: (options ?? {});
|
|
1564
|
+
const opts = typeof options === "string" ? { apiKey: options } : (options ?? {});
|
|
1556
1565
|
const apiKey = opts.apiKey;
|
|
1557
1566
|
const maxDepth = opts.maxDepth ?? 2;
|
|
1558
1567
|
const depth = opts._depth ?? 0;
|
|
@@ -1565,18 +1574,18 @@ async function fixJsonWithAI(jsonStr, options) {
|
|
|
1565
1574
|
openai = initializeOpenAI(apiKey);
|
|
1566
1575
|
}
|
|
1567
1576
|
const completion = await openai.chat.completions.create({
|
|
1568
|
-
model:
|
|
1577
|
+
model: "gpt-5-mini",
|
|
1569
1578
|
messages: [
|
|
1570
1579
|
{
|
|
1571
|
-
role:
|
|
1572
|
-
content:
|
|
1580
|
+
role: "system",
|
|
1581
|
+
content: "You are a JSON fixer. Return only valid JSON without any additional text or explanation.",
|
|
1573
1582
|
},
|
|
1574
1583
|
{
|
|
1575
|
-
role:
|
|
1576
|
-
content: `Fix this broken JSON:\n${jsonStr}
|
|
1577
|
-
}
|
|
1584
|
+
role: "user",
|
|
1585
|
+
content: `Fix this broken JSON:\n${jsonStr}`,
|
|
1586
|
+
},
|
|
1578
1587
|
],
|
|
1579
|
-
response_format: { type:
|
|
1588
|
+
response_format: { type: "json_object" },
|
|
1580
1589
|
});
|
|
1581
1590
|
const fixedJson = completion.choices[0]?.message?.content;
|
|
1582
1591
|
if (fixedJson && isValidJson(fixedJson)) {
|
|
@@ -1590,23 +1599,32 @@ async function fixJsonWithAI(jsonStr, options) {
|
|
|
1590
1599
|
_depth: depth + 1,
|
|
1591
1600
|
});
|
|
1592
1601
|
}
|
|
1593
|
-
throw new JsonParseError(
|
|
1602
|
+
throw new JsonParseError("Failed to fix JSON with AI: returned invalid JSON");
|
|
1594
1603
|
}
|
|
1595
1604
|
catch (err) {
|
|
1596
1605
|
// Re-throw JsonParseError as-is (e.g. max depth exceeded)
|
|
1597
1606
|
if (err instanceof JsonParseError) {
|
|
1598
1607
|
throw err;
|
|
1599
1608
|
}
|
|
1600
|
-
const errorMessage = err instanceof Error ? err.message :
|
|
1609
|
+
const errorMessage = err instanceof Error ? err.message : "Unknown error occurred";
|
|
1601
1610
|
throw new JsonParseError(`Error fixing JSON with AI: ${errorMessage}`);
|
|
1602
1611
|
}
|
|
1603
1612
|
}
|
|
1604
1613
|
|
|
1605
1614
|
// llm-utils.ts
|
|
1606
1615
|
function normalizeModelName(model) {
|
|
1607
|
-
return model.replace(/_/g,
|
|
1608
|
-
}
|
|
1609
|
-
const CODE_BLOCK_TYPES = [
|
|
1616
|
+
return model.replace(/_/g, "-").toLowerCase();
|
|
1617
|
+
}
|
|
1618
|
+
const CODE_BLOCK_TYPES = [
|
|
1619
|
+
"javascript",
|
|
1620
|
+
"js",
|
|
1621
|
+
"graphql",
|
|
1622
|
+
"json",
|
|
1623
|
+
"typescript",
|
|
1624
|
+
"python",
|
|
1625
|
+
"markdown",
|
|
1626
|
+
"yaml",
|
|
1627
|
+
];
|
|
1610
1628
|
/**
|
|
1611
1629
|
* Tries to parse JSON from the given content string according to the specified
|
|
1612
1630
|
* response format. If the response format is 'json' or a JSON schema object, it
|
|
@@ -1630,7 +1648,9 @@ async function parseResponse(content, responseFormat, options = {}) {
|
|
|
1630
1648
|
if (recursionDepth > maxRetries) {
|
|
1631
1649
|
throw new Error(`Maximum recursion depth (${maxRetries}) exceeded while parsing JSON. AI fixing may have returned broken JSON.`);
|
|
1632
1650
|
}
|
|
1633
|
-
if (responseFormat ===
|
|
1651
|
+
if (responseFormat === "json" ||
|
|
1652
|
+
(typeof responseFormat === "object" &&
|
|
1653
|
+
responseFormat?.type === "json_schema")) {
|
|
1634
1654
|
let cleanedContent = content.trim();
|
|
1635
1655
|
let detectedType = null;
|
|
1636
1656
|
// Remove code block markers if present
|
|
@@ -1641,8 +1661,8 @@ async function parseResponse(content, responseFormat, options = {}) {
|
|
|
1641
1661
|
}
|
|
1642
1662
|
return false;
|
|
1643
1663
|
});
|
|
1644
|
-
if (startsWithCodeBlock && cleanedContent.endsWith(
|
|
1645
|
-
const firstLineEndIndex = cleanedContent.indexOf(
|
|
1664
|
+
if (startsWithCodeBlock && cleanedContent.endsWith("```")) {
|
|
1665
|
+
const firstLineEndIndex = cleanedContent.indexOf("\n");
|
|
1646
1666
|
if (firstLineEndIndex !== -1) {
|
|
1647
1667
|
cleanedContent = cleanedContent.slice(firstLineEndIndex + 1, -3).trim();
|
|
1648
1668
|
getLumicLogger().info(`Code block for type ${detectedType} detected and removed. Cleaned content: ${cleanedContent}`);
|
|
@@ -1675,7 +1695,7 @@ async function parseResponse(content, responseFormat, options = {}) {
|
|
|
1675
1695
|
},
|
|
1676
1696
|
// Strategy 3: Remove leading/trailing text and try parsing
|
|
1677
1697
|
() => {
|
|
1678
|
-
const jsonMatch = cleanedContent.replace(/^[^{[]*|[^}\]]*$/g,
|
|
1698
|
+
const jsonMatch = cleanedContent.replace(/^[^{[]*|[^}\]]*$/g, "");
|
|
1679
1699
|
try {
|
|
1680
1700
|
return JSON.parse(jsonMatch);
|
|
1681
1701
|
}
|
|
@@ -1697,7 +1717,7 @@ async function parseResponse(content, responseFormat, options = {}) {
|
|
|
1697
1717
|
}
|
|
1698
1718
|
// Strategy 5: Use AI fixing (only if explicitly enabled)
|
|
1699
1719
|
if (enableAiFix) {
|
|
1700
|
-
getLumicLogger().warn(
|
|
1720
|
+
getLumicLogger().warn("Using AI-powered JSON fixing. This adds cost and latency. Consider improving JSON generation instead.");
|
|
1701
1721
|
try {
|
|
1702
1722
|
const aiFixedJson = await fixJsonWithAI(cleanedContent);
|
|
1703
1723
|
if (aiFixedJson !== null) {
|
|
@@ -1710,7 +1730,7 @@ async function parseResponse(content, responseFormat, options = {}) {
|
|
|
1710
1730
|
catch {
|
|
1711
1731
|
// AI returned something that looks like JSON but isn't valid
|
|
1712
1732
|
// Increment recursion depth and try parsing the AI response
|
|
1713
|
-
getLumicLogger().warn(
|
|
1733
|
+
getLumicLogger().warn("AI fixing returned invalid JSON, attempting recursive parse...");
|
|
1714
1734
|
return parseResponse(JSON.stringify(aiFixedJson), responseFormat, {
|
|
1715
1735
|
...options,
|
|
1716
1736
|
_recursionDepth: recursionDepth + 1,
|
|
@@ -1719,14 +1739,14 @@ async function parseResponse(content, responseFormat, options = {}) {
|
|
|
1719
1739
|
}
|
|
1720
1740
|
}
|
|
1721
1741
|
catch (error) {
|
|
1722
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
1742
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1723
1743
|
getLumicLogger().error(`AI JSON fixing failed: ${errorMessage}`);
|
|
1724
1744
|
// Continue to final error below
|
|
1725
1745
|
}
|
|
1726
1746
|
}
|
|
1727
1747
|
// If all strategies fail, throw an error
|
|
1728
1748
|
getLumicLogger().error(`Failed to parse JSON from content: ${cleanedContent}. Original JSON: ${content}`);
|
|
1729
|
-
throw new Error(
|
|
1749
|
+
throw new Error("Unable to parse JSON response");
|
|
1730
1750
|
}
|
|
1731
1751
|
else {
|
|
1732
1752
|
return content;
|
|
@@ -2320,13 +2340,15 @@ const isRetryableLLMError = (error) => {
|
|
|
2320
2340
|
if (error instanceof Error) {
|
|
2321
2341
|
const message = error.message;
|
|
2322
2342
|
// Retry on rate limits (429)
|
|
2323
|
-
if (message.includes(
|
|
2343
|
+
if (message.includes("429") ||
|
|
2344
|
+
message.includes("rate limit") ||
|
|
2345
|
+
message.includes("Rate limit")) {
|
|
2324
2346
|
return true;
|
|
2325
2347
|
}
|
|
2326
2348
|
// Retry on transient body-corruption 400s. Match the exact OpenAI error
|
|
2327
2349
|
// string to avoid retrying genuine client-side validation 400s (which
|
|
2328
2350
|
// would re-fail forever and waste retry budget).
|
|
2329
|
-
if (message.includes(
|
|
2351
|
+
if (message.includes("could not parse the JSON body of your request")) {
|
|
2330
2352
|
return true;
|
|
2331
2353
|
}
|
|
2332
2354
|
}
|
|
@@ -2395,23 +2417,26 @@ function isReasoningModel(model) {
|
|
|
2395
2417
|
* @throws Error if the response format is invalid or incompatible.
|
|
2396
2418
|
*/
|
|
2397
2419
|
function getResponseFormatOption(responseFormat, normalizedModel) {
|
|
2398
|
-
if (responseFormat ===
|
|
2399
|
-
return { type:
|
|
2420
|
+
if (responseFormat === "text") {
|
|
2421
|
+
return { type: "text" };
|
|
2400
2422
|
}
|
|
2401
|
-
if (responseFormat ===
|
|
2402
|
-
return supportsJsonMode(normalizedModel)
|
|
2423
|
+
if (responseFormat === "json") {
|
|
2424
|
+
return supportsJsonMode(normalizedModel)
|
|
2425
|
+
? { type: "json_object" }
|
|
2426
|
+
: { type: "text" };
|
|
2403
2427
|
}
|
|
2404
|
-
if (typeof responseFormat ===
|
|
2428
|
+
if (typeof responseFormat === "object" &&
|
|
2429
|
+
responseFormat.type === "json_schema") {
|
|
2405
2430
|
if (!isStructuredOutputCompatibleModel(normalizedModel)) {
|
|
2406
2431
|
throw new Error(`${normalizedModel} is not compatible with structured outputs / json object.`);
|
|
2407
2432
|
}
|
|
2408
2433
|
return {
|
|
2409
|
-
type:
|
|
2434
|
+
type: "json_schema",
|
|
2410
2435
|
json_schema: {
|
|
2411
|
-
type:
|
|
2436
|
+
type: "object",
|
|
2412
2437
|
properties: responseFormat.schema.properties,
|
|
2413
2438
|
required: responseFormat.schema.required || [],
|
|
2414
|
-
name:
|
|
2439
|
+
name: "Response",
|
|
2415
2440
|
},
|
|
2416
2441
|
};
|
|
2417
2442
|
}
|
|
@@ -2470,7 +2495,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2470
2495
|
const normalizedModel = normalizeModelName(options.model || DEFAULT_MODEL);
|
|
2471
2496
|
const apiKey = options.apiKey || getSecrets().openai.apiKey;
|
|
2472
2497
|
if (!apiKey) {
|
|
2473
|
-
throw new Error(
|
|
2498
|
+
throw new Error("OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set");
|
|
2474
2499
|
}
|
|
2475
2500
|
const openai = new OpenAI({
|
|
2476
2501
|
apiKey: apiKey,
|
|
@@ -2480,7 +2505,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2480
2505
|
// Add developer prompt if present
|
|
2481
2506
|
if (options.developerPrompt && supportsDeveloperPrompt(normalizedModel)) {
|
|
2482
2507
|
messages.push({
|
|
2483
|
-
role:
|
|
2508
|
+
role: "developer",
|
|
2484
2509
|
content: options.developerPrompt,
|
|
2485
2510
|
});
|
|
2486
2511
|
}
|
|
@@ -2489,15 +2514,15 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2489
2514
|
messages.push(...options.context);
|
|
2490
2515
|
}
|
|
2491
2516
|
// Add user content
|
|
2492
|
-
if (typeof content ===
|
|
2517
|
+
if (typeof content === "string") {
|
|
2493
2518
|
messages.push({
|
|
2494
|
-
role:
|
|
2519
|
+
role: "user",
|
|
2495
2520
|
content,
|
|
2496
2521
|
});
|
|
2497
2522
|
}
|
|
2498
2523
|
else {
|
|
2499
2524
|
messages.push({
|
|
2500
|
-
role:
|
|
2525
|
+
role: "user",
|
|
2501
2526
|
content,
|
|
2502
2527
|
});
|
|
2503
2528
|
}
|
|
@@ -2511,7 +2536,8 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2511
2536
|
queryOptions.tools = options.tools;
|
|
2512
2537
|
}
|
|
2513
2538
|
// Only include temperature if the model supports it
|
|
2514
|
-
if (options.temperature !== undefined &&
|
|
2539
|
+
if (options.temperature !== undefined &&
|
|
2540
|
+
supportsTemperature(normalizedModel)) {
|
|
2515
2541
|
queryOptions.temperature = options.temperature;
|
|
2516
2542
|
}
|
|
2517
2543
|
// Only include max_completion_tokens when specified
|
|
@@ -2521,7 +2547,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2521
2547
|
// Only set response_format when it's not the default 'text' type
|
|
2522
2548
|
// (sending response_format: {type: 'text'} is redundant and may cause issues)
|
|
2523
2549
|
const responseFormatOption = getResponseFormatOption(responseFormat, normalizedModel);
|
|
2524
|
-
if (responseFormatOption.type !==
|
|
2550
|
+
if (responseFormatOption.type !== "text") {
|
|
2525
2551
|
queryOptions.response_format = responseFormatOption;
|
|
2526
2552
|
}
|
|
2527
2553
|
let completion;
|
|
@@ -2542,15 +2568,19 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2542
2568
|
// for production prompts containing sensitive context.
|
|
2543
2569
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2544
2570
|
const totalContentChars = messages.reduce((sum, msg) => {
|
|
2545
|
-
if (typeof msg.content ===
|
|
2571
|
+
if (typeof msg.content === "string")
|
|
2546
2572
|
return sum + msg.content.length;
|
|
2547
2573
|
if (Array.isArray(msg.content)) {
|
|
2548
|
-
return sum +
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2574
|
+
return (sum +
|
|
2575
|
+
msg.content.reduce((s, part) => {
|
|
2576
|
+
if (typeof part === "object" &&
|
|
2577
|
+
part !== null &&
|
|
2578
|
+
"text" in part &&
|
|
2579
|
+
typeof part.text === "string") {
|
|
2580
|
+
return s + part.text.length;
|
|
2581
|
+
}
|
|
2582
|
+
return s;
|
|
2583
|
+
}, 0));
|
|
2554
2584
|
}
|
|
2555
2585
|
return sum;
|
|
2556
2586
|
}, 0);
|
|
@@ -2577,7 +2607,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2577
2607
|
const cachedTokens = completion.usage?.prompt_tokens_details?.cached_tokens ?? 0;
|
|
2578
2608
|
const response = {
|
|
2579
2609
|
id: completion.id,
|
|
2580
|
-
content: completion.choices[0]?.message?.content ||
|
|
2610
|
+
content: completion.choices[0]?.message?.content || "",
|
|
2581
2611
|
tool_calls: completion.choices[0]?.message?.tool_calls,
|
|
2582
2612
|
usage: {
|
|
2583
2613
|
prompt_tokens: completion.usage?.prompt_tokens ?? 0,
|
|
@@ -2587,7 +2617,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2587
2617
|
},
|
|
2588
2618
|
system_fingerprint: completion.system_fingerprint,
|
|
2589
2619
|
service_tier: options.service_tier,
|
|
2590
|
-
provider:
|
|
2620
|
+
provider: "openai",
|
|
2591
2621
|
model: normalizedModel,
|
|
2592
2622
|
};
|
|
2593
2623
|
return response;
|
|
@@ -2600,7 +2630,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2600
2630
|
* @param options The options for the LLM call. Defaults to an empty object.
|
|
2601
2631
|
* @return A promise that resolves to the response from the LLM.
|
|
2602
2632
|
*/
|
|
2603
|
-
const makeOpenAIChatCompletionCall = async (content, responseFormat =
|
|
2633
|
+
const makeOpenAIChatCompletionCall = async (content, responseFormat = "text", options = {}) => {
|
|
2604
2634
|
const mergedOptions = {
|
|
2605
2635
|
...DEFAULT_OPTIONS$1,
|
|
2606
2636
|
...options,
|
|
@@ -2609,7 +2639,7 @@ const makeOpenAIChatCompletionCall = async (content, responseFormat = 'text', op
|
|
|
2609
2639
|
// Track cost in the global cost tracker. Pass cached tokens through so the
|
|
2610
2640
|
// tracker applies the discounted cached-input rate (typically ~50% of the
|
|
2611
2641
|
// standard input rate) instead of billing every input token at full price.
|
|
2612
|
-
getLLMCostTracker().trackUsage(
|
|
2642
|
+
getLLMCostTracker().trackUsage("openai", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens);
|
|
2613
2643
|
// Handle tool calls differently
|
|
2614
2644
|
if (completion.tool_calls && completion.tool_calls.length > 0) {
|
|
2615
2645
|
const toolCallResponse = {
|
|
@@ -2625,10 +2655,10 @@ const makeOpenAIChatCompletionCall = async (content, responseFormat = 'text', op
|
|
|
2625
2655
|
prompt_tokens: completion.usage.prompt_tokens,
|
|
2626
2656
|
completion_tokens: completion.usage.completion_tokens,
|
|
2627
2657
|
reasoning_tokens: 0,
|
|
2628
|
-
provider:
|
|
2658
|
+
provider: "openai",
|
|
2629
2659
|
model: completion.model,
|
|
2630
2660
|
cached_tokens: completion.usage.cached_tokens,
|
|
2631
|
-
cost: calculateCost(
|
|
2661
|
+
cost: calculateCost("openai", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
|
|
2632
2662
|
},
|
|
2633
2663
|
tool_calls: completion.tool_calls,
|
|
2634
2664
|
};
|
|
@@ -2636,7 +2666,7 @@ const makeOpenAIChatCompletionCall = async (content, responseFormat = 'text', op
|
|
|
2636
2666
|
// Handle regular responses
|
|
2637
2667
|
const parsedResponse = await parseResponse(completion.content, responseFormat);
|
|
2638
2668
|
if (parsedResponse === null) {
|
|
2639
|
-
throw new Error(
|
|
2669
|
+
throw new Error("Failed to parse response");
|
|
2640
2670
|
}
|
|
2641
2671
|
return {
|
|
2642
2672
|
response: parsedResponse,
|
|
@@ -2644,10 +2674,10 @@ const makeOpenAIChatCompletionCall = async (content, responseFormat = 'text', op
|
|
|
2644
2674
|
prompt_tokens: completion.usage.prompt_tokens,
|
|
2645
2675
|
completion_tokens: completion.usage.completion_tokens,
|
|
2646
2676
|
reasoning_tokens: 0,
|
|
2647
|
-
provider:
|
|
2677
|
+
provider: "openai",
|
|
2648
2678
|
model: completion.model,
|
|
2649
2679
|
cached_tokens: completion.usage.cached_tokens,
|
|
2650
|
-
cost: calculateCost(
|
|
2680
|
+
cost: calculateCost("openai", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
|
|
2651
2681
|
},
|
|
2652
2682
|
tool_calls: completion.tool_calls,
|
|
2653
2683
|
};
|
|
@@ -2682,7 +2712,7 @@ const makeResponsesAPICall = async (input, options = {}) => {
|
|
|
2682
2712
|
const normalizedModel = normalizeModelName(options.model || DEFAULT_MODEL);
|
|
2683
2713
|
const apiKey = options.apiKey || getSecrets().openai.apiKey;
|
|
2684
2714
|
if (!apiKey) {
|
|
2685
|
-
throw new Error(
|
|
2715
|
+
throw new Error("OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set");
|
|
2686
2716
|
}
|
|
2687
2717
|
const openai = new OpenAI({
|
|
2688
2718
|
apiKey: apiKey,
|
|
@@ -2706,20 +2736,20 @@ const makeResponsesAPICall = async (input, options = {}) => {
|
|
|
2706
2736
|
// (the equivalent of Chat Completions' `prompt_tokens_details.cached_tokens`).
|
|
2707
2737
|
const responsesCachedTokens = response.usage?.input_tokens_details?.cached_tokens || 0;
|
|
2708
2738
|
// Track cost in the global cost tracker
|
|
2709
|
-
getLLMCostTracker().trackUsage(
|
|
2739
|
+
getLLMCostTracker().trackUsage("openai", normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, responsesCachedTokens);
|
|
2710
2740
|
// Extract tool calls from the output
|
|
2711
2741
|
const toolCalls = response.output
|
|
2712
|
-
?.filter((item) => item.type ===
|
|
2742
|
+
?.filter((item) => item.type === "function_call")
|
|
2713
2743
|
.map((toolCall) => ({
|
|
2714
2744
|
id: toolCall.call_id,
|
|
2715
|
-
type:
|
|
2745
|
+
type: "function",
|
|
2716
2746
|
function: {
|
|
2717
2747
|
name: toolCall.name,
|
|
2718
2748
|
arguments: toolCall.arguments,
|
|
2719
2749
|
},
|
|
2720
2750
|
}));
|
|
2721
2751
|
// Extract code interpreter outputs if present
|
|
2722
|
-
const codeInterpreterCalls = response.output?.filter((item) => item.type ===
|
|
2752
|
+
const codeInterpreterCalls = response.output?.filter((item) => item.type === "code_interpreter_call");
|
|
2723
2753
|
let codeInterpreterOutputs = undefined;
|
|
2724
2754
|
if (codeInterpreterCalls && codeInterpreterCalls.length > 0) {
|
|
2725
2755
|
// Each code_interpreter_call has an 'outputs' array property
|
|
@@ -2745,32 +2775,34 @@ const makeResponsesAPICall = async (input, options = {}) => {
|
|
|
2745
2775
|
prompt_tokens: response.usage?.input_tokens || 0,
|
|
2746
2776
|
completion_tokens: response.usage?.output_tokens || 0,
|
|
2747
2777
|
reasoning_tokens: response.usage?.output_tokens_details?.reasoning_tokens || 0,
|
|
2748
|
-
provider:
|
|
2778
|
+
provider: "openai",
|
|
2749
2779
|
model: normalizedModel,
|
|
2750
2780
|
cached_tokens: responsesCachedTokens,
|
|
2751
|
-
cost: calculateCost(
|
|
2781
|
+
cost: calculateCost("openai", normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, responsesCachedTokens),
|
|
2752
2782
|
},
|
|
2753
2783
|
tool_calls: toolCalls,
|
|
2754
|
-
...(codeInterpreterOutputs
|
|
2784
|
+
...(codeInterpreterOutputs
|
|
2785
|
+
? { code_interpreter_outputs: codeInterpreterOutputs }
|
|
2786
|
+
: {}),
|
|
2755
2787
|
};
|
|
2756
2788
|
}
|
|
2757
2789
|
// Extract text content from the response output
|
|
2758
2790
|
const textContent = response.output
|
|
2759
|
-
?.filter((item) => item.type ===
|
|
2791
|
+
?.filter((item) => item.type === "message")
|
|
2760
2792
|
.map((item) => item.content
|
|
2761
|
-
.filter((content) => content.type ===
|
|
2793
|
+
.filter((content) => content.type === "output_text")
|
|
2762
2794
|
.map((content) => content.text)
|
|
2763
|
-
.join(
|
|
2764
|
-
.join(
|
|
2795
|
+
.join(""))
|
|
2796
|
+
.join("") || "";
|
|
2765
2797
|
// Determine the format for parsing the response
|
|
2766
|
-
let parsingFormat =
|
|
2767
|
-
if (requestBody.text?.format?.type ===
|
|
2768
|
-
parsingFormat =
|
|
2798
|
+
let parsingFormat = "text";
|
|
2799
|
+
if (requestBody.text?.format?.type === "json_object") {
|
|
2800
|
+
parsingFormat = "json";
|
|
2769
2801
|
}
|
|
2770
2802
|
// Handle regular responses
|
|
2771
2803
|
const parsedResponse = await parseResponse(textContent, parsingFormat);
|
|
2772
2804
|
if (parsedResponse === null) {
|
|
2773
|
-
throw new Error(
|
|
2805
|
+
throw new Error("Failed to parse response from Responses API");
|
|
2774
2806
|
}
|
|
2775
2807
|
return {
|
|
2776
2808
|
response: parsedResponse,
|
|
@@ -2778,13 +2810,15 @@ const makeResponsesAPICall = async (input, options = {}) => {
|
|
|
2778
2810
|
prompt_tokens: response.usage?.input_tokens || 0,
|
|
2779
2811
|
completion_tokens: response.usage?.output_tokens || 0,
|
|
2780
2812
|
reasoning_tokens: response.usage?.output_tokens_details?.reasoning_tokens || 0,
|
|
2781
|
-
provider:
|
|
2813
|
+
provider: "openai",
|
|
2782
2814
|
model: normalizedModel,
|
|
2783
2815
|
cached_tokens: responsesCachedTokens,
|
|
2784
|
-
cost: calculateCost(
|
|
2816
|
+
cost: calculateCost("openai", normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, responsesCachedTokens),
|
|
2785
2817
|
},
|
|
2786
2818
|
tool_calls: toolCalls,
|
|
2787
|
-
...(codeInterpreterOutputs
|
|
2819
|
+
...(codeInterpreterOutputs
|
|
2820
|
+
? { code_interpreter_outputs: codeInterpreterOutputs }
|
|
2821
|
+
: {}),
|
|
2788
2822
|
};
|
|
2789
2823
|
};
|
|
2790
2824
|
|
|
@@ -8094,12 +8128,12 @@ function sanitizeObject(obj, sensitiveFields = ['apiKey', 'token', 'secret', 'pa
|
|
|
8094
8128
|
const isRetryableAnthropicError = (error) => {
|
|
8095
8129
|
if (error instanceof Error) {
|
|
8096
8130
|
const message = error.message;
|
|
8097
|
-
if (message.includes(
|
|
8098
|
-
message.includes(
|
|
8099
|
-
message.includes(
|
|
8100
|
-
message.includes(
|
|
8101
|
-
message.includes(
|
|
8102
|
-
message.includes(
|
|
8131
|
+
if (message.includes("429") ||
|
|
8132
|
+
message.includes("500") ||
|
|
8133
|
+
message.includes("503") ||
|
|
8134
|
+
message.includes("529") ||
|
|
8135
|
+
message.includes("rate limit") ||
|
|
8136
|
+
message.includes("overloaded")) {
|
|
8103
8137
|
return true;
|
|
8104
8138
|
}
|
|
8105
8139
|
}
|
|
@@ -8114,8 +8148,11 @@ const isRetryableAnthropicError = (error) => {
|
|
|
8114
8148
|
function translateToolsToAnthropic(tools) {
|
|
8115
8149
|
return tools.map((tool) => ({
|
|
8116
8150
|
name: tool.function.name,
|
|
8117
|
-
description: tool.function.description ||
|
|
8118
|
-
input_schema: (tool.function.parameters || {
|
|
8151
|
+
description: tool.function.description || "",
|
|
8152
|
+
input_schema: (tool.function.parameters || {
|
|
8153
|
+
type: "object",
|
|
8154
|
+
properties: {},
|
|
8155
|
+
}),
|
|
8119
8156
|
}));
|
|
8120
8157
|
}
|
|
8121
8158
|
/**
|
|
@@ -8128,27 +8165,31 @@ function translateContextToAnthropic(context) {
|
|
|
8128
8165
|
const systemParts = [];
|
|
8129
8166
|
const messages = [];
|
|
8130
8167
|
for (const msg of context) {
|
|
8131
|
-
if (msg.role ===
|
|
8132
|
-
if (typeof msg.content ===
|
|
8168
|
+
if (msg.role === "system" || msg.role === "developer") {
|
|
8169
|
+
if (typeof msg.content === "string") {
|
|
8133
8170
|
systemParts.push(msg.content);
|
|
8134
8171
|
}
|
|
8135
8172
|
continue;
|
|
8136
8173
|
}
|
|
8137
|
-
if (msg.role ===
|
|
8138
|
-
const content = typeof msg.content ===
|
|
8174
|
+
if (msg.role === "user") {
|
|
8175
|
+
const content = typeof msg.content === "string"
|
|
8139
8176
|
? msg.content
|
|
8140
8177
|
: JSON.stringify(msg.content);
|
|
8141
|
-
messages.push({ role:
|
|
8178
|
+
messages.push({ role: "user", content });
|
|
8142
8179
|
continue;
|
|
8143
8180
|
}
|
|
8144
|
-
if (msg.role ===
|
|
8181
|
+
if (msg.role === "assistant") {
|
|
8145
8182
|
const assistantMsg = msg;
|
|
8146
8183
|
// If the assistant message has tool_calls, translate to Anthropic tool_use blocks
|
|
8147
8184
|
if (assistantMsg.tool_calls && assistantMsg.tool_calls.length > 0) {
|
|
8148
8185
|
const contentBlocks = [];
|
|
8149
8186
|
// Include text content if present
|
|
8150
|
-
if (typeof assistantMsg.content ===
|
|
8151
|
-
|
|
8187
|
+
if (typeof assistantMsg.content === "string" &&
|
|
8188
|
+
assistantMsg.content.trim()) {
|
|
8189
|
+
contentBlocks.push({
|
|
8190
|
+
type: "text",
|
|
8191
|
+
text: assistantMsg.content,
|
|
8192
|
+
});
|
|
8152
8193
|
}
|
|
8153
8194
|
// Translate each tool_call to a tool_use block
|
|
8154
8195
|
for (const tc of assistantMsg.tool_calls) {
|
|
@@ -8160,32 +8201,36 @@ function translateContextToAnthropic(context) {
|
|
|
8160
8201
|
input = { raw: tc.function.arguments };
|
|
8161
8202
|
}
|
|
8162
8203
|
contentBlocks.push({
|
|
8163
|
-
type:
|
|
8204
|
+
type: "tool_use",
|
|
8164
8205
|
id: tc.id,
|
|
8165
8206
|
name: tc.function.name,
|
|
8166
8207
|
input,
|
|
8167
8208
|
});
|
|
8168
8209
|
}
|
|
8169
|
-
messages.push({ role:
|
|
8210
|
+
messages.push({ role: "assistant", content: contentBlocks });
|
|
8170
8211
|
}
|
|
8171
8212
|
else {
|
|
8172
|
-
const content = typeof assistantMsg.content ===
|
|
8213
|
+
const content = typeof assistantMsg.content === "string"
|
|
8173
8214
|
? assistantMsg.content
|
|
8174
8215
|
: JSON.stringify(assistantMsg.content);
|
|
8175
|
-
messages.push({ role:
|
|
8216
|
+
messages.push({ role: "assistant", content });
|
|
8176
8217
|
}
|
|
8177
8218
|
continue;
|
|
8178
8219
|
}
|
|
8179
|
-
if (msg.role ===
|
|
8220
|
+
if (msg.role === "tool") {
|
|
8180
8221
|
// Anthropic expects tool results as user messages with tool_result content blocks
|
|
8181
8222
|
const toolMsg = msg;
|
|
8182
8223
|
messages.push({
|
|
8183
|
-
role:
|
|
8184
|
-
content: [
|
|
8185
|
-
|
|
8224
|
+
role: "user",
|
|
8225
|
+
content: [
|
|
8226
|
+
{
|
|
8227
|
+
type: "tool_result",
|
|
8186
8228
|
tool_use_id: toolMsg.tool_call_id,
|
|
8187
|
-
content: typeof toolMsg.content ===
|
|
8188
|
-
|
|
8229
|
+
content: typeof toolMsg.content === "string"
|
|
8230
|
+
? toolMsg.content
|
|
8231
|
+
: JSON.stringify(toolMsg.content),
|
|
8232
|
+
},
|
|
8233
|
+
],
|
|
8189
8234
|
});
|
|
8190
8235
|
continue;
|
|
8191
8236
|
}
|
|
@@ -8206,13 +8251,13 @@ function translateContextToAnthropic(context) {
|
|
|
8206
8251
|
merged.push({ role: msg.role, content: toContentBlocks(msg.content) });
|
|
8207
8252
|
}
|
|
8208
8253
|
}
|
|
8209
|
-
return { messages: merged, systemText: systemParts.join(
|
|
8254
|
+
return { messages: merged, systemText: systemParts.join("\n\n") };
|
|
8210
8255
|
}
|
|
8211
8256
|
/** Convert string or content block array to a uniform content block array. */
|
|
8212
8257
|
function toContentBlocks(content) {
|
|
8213
|
-
if (typeof content ===
|
|
8258
|
+
if (typeof content === "string") {
|
|
8214
8259
|
const textBlock = {
|
|
8215
|
-
type:
|
|
8260
|
+
type: "text",
|
|
8216
8261
|
text: content,
|
|
8217
8262
|
citations: null,
|
|
8218
8263
|
};
|
|
@@ -8232,11 +8277,11 @@ function toContentBlocks(content) {
|
|
|
8232
8277
|
* @param options The options for the LLM call.
|
|
8233
8278
|
* @return A promise that resolves to the response from the Anthropic API.
|
|
8234
8279
|
*/
|
|
8235
|
-
async function makeAnthropicCall(content, responseFormat =
|
|
8236
|
-
const model = (options.model ||
|
|
8280
|
+
async function makeAnthropicCall(content, responseFormat = "text", options = {}) {
|
|
8281
|
+
const model = (options.model || "claude-sonnet-4-6");
|
|
8237
8282
|
const apiKey = options.apiKey || getSecrets().anthropic.apiKey;
|
|
8238
8283
|
if (!apiKey) {
|
|
8239
|
-
throw new Error(
|
|
8284
|
+
throw new Error("Anthropic API key is not provided and ANTHROPIC_API_KEY environment variable is not set");
|
|
8240
8285
|
}
|
|
8241
8286
|
const client = new Anthropic({
|
|
8242
8287
|
apiKey,
|
|
@@ -8248,8 +8293,10 @@ async function makeAnthropicCall(content, responseFormat = 'text', options = {})
|
|
|
8248
8293
|
systemParts.push(options.developerPrompt);
|
|
8249
8294
|
}
|
|
8250
8295
|
// If JSON response is requested, instruct the model via system prompt
|
|
8251
|
-
if (responseFormat ===
|
|
8252
|
-
|
|
8296
|
+
if (responseFormat === "json" ||
|
|
8297
|
+
(typeof responseFormat === "object" &&
|
|
8298
|
+
responseFormat.type === "json_schema")) {
|
|
8299
|
+
systemParts.push("You MUST respond with valid JSON only. No markdown, no code blocks, no explanatory text — just the raw JSON object or array.");
|
|
8253
8300
|
}
|
|
8254
8301
|
// Build messages array
|
|
8255
8302
|
const messages = [];
|
|
@@ -8262,7 +8309,7 @@ async function makeAnthropicCall(content, responseFormat = 'text', options = {})
|
|
|
8262
8309
|
messages.push(...translated.messages);
|
|
8263
8310
|
}
|
|
8264
8311
|
// Add user content
|
|
8265
|
-
messages.push({ role:
|
|
8312
|
+
messages.push({ role: "user", content });
|
|
8266
8313
|
// Build request params
|
|
8267
8314
|
const requestParams = {
|
|
8268
8315
|
model,
|
|
@@ -8271,7 +8318,7 @@ async function makeAnthropicCall(content, responseFormat = 'text', options = {})
|
|
|
8271
8318
|
};
|
|
8272
8319
|
// Add system prompt if present
|
|
8273
8320
|
if (systemParts.length > 0) {
|
|
8274
|
-
requestParams.system = systemParts.join(
|
|
8321
|
+
requestParams.system = systemParts.join("\n\n");
|
|
8275
8322
|
}
|
|
8276
8323
|
// Add temperature if specified
|
|
8277
8324
|
if (options.temperature !== undefined) {
|
|
@@ -8294,14 +8341,14 @@ async function makeAnthropicCall(content, responseFormat = 'text', options = {})
|
|
|
8294
8341
|
const inputTokens = response.usage.input_tokens;
|
|
8295
8342
|
const outputTokens = response.usage.output_tokens;
|
|
8296
8343
|
// Track cost
|
|
8297
|
-
getLLMCostTracker().trackUsage(
|
|
8344
|
+
getLLMCostTracker().trackUsage("anthropic", model, inputTokens, outputTokens);
|
|
8298
8345
|
// Extract tool use blocks
|
|
8299
|
-
const toolUseBlocks = response.content.filter((block) => block.type ===
|
|
8346
|
+
const toolUseBlocks = response.content.filter((block) => block.type === "tool_use");
|
|
8300
8347
|
if (toolUseBlocks.length > 0) {
|
|
8301
8348
|
// Translate Anthropic tool_use to OpenAI tool_calls format
|
|
8302
8349
|
const toolCalls = toolUseBlocks.map((block) => ({
|
|
8303
8350
|
id: block.id,
|
|
8304
|
-
type:
|
|
8351
|
+
type: "function",
|
|
8305
8352
|
function: {
|
|
8306
8353
|
name: block.name,
|
|
8307
8354
|
arguments: JSON.stringify(block.input),
|
|
@@ -8320,22 +8367,22 @@ async function makeAnthropicCall(content, responseFormat = 'text', options = {})
|
|
|
8320
8367
|
prompt_tokens: inputTokens,
|
|
8321
8368
|
completion_tokens: outputTokens,
|
|
8322
8369
|
reasoning_tokens: 0,
|
|
8323
|
-
provider:
|
|
8370
|
+
provider: "anthropic",
|
|
8324
8371
|
model,
|
|
8325
|
-
cost: calculateCost(
|
|
8372
|
+
cost: calculateCost("anthropic", model, inputTokens, outputTokens),
|
|
8326
8373
|
},
|
|
8327
8374
|
tool_calls: toolCalls,
|
|
8328
8375
|
};
|
|
8329
8376
|
}
|
|
8330
8377
|
// Extract text content
|
|
8331
8378
|
const textContent = response.content
|
|
8332
|
-
.filter((block) => block.type ===
|
|
8379
|
+
.filter((block) => block.type === "text")
|
|
8333
8380
|
.map((block) => block.text)
|
|
8334
|
-
.join(
|
|
8381
|
+
.join("");
|
|
8335
8382
|
// Parse response
|
|
8336
8383
|
const parsedResponse = await parseResponse(textContent, responseFormat);
|
|
8337
8384
|
if (parsedResponse === null) {
|
|
8338
|
-
throw new Error(
|
|
8385
|
+
throw new Error("Failed to parse Anthropic response");
|
|
8339
8386
|
}
|
|
8340
8387
|
return {
|
|
8341
8388
|
response: parsedResponse,
|
|
@@ -8343,9 +8390,9 @@ async function makeAnthropicCall(content, responseFormat = 'text', options = {})
|
|
|
8343
8390
|
prompt_tokens: inputTokens,
|
|
8344
8391
|
completion_tokens: outputTokens,
|
|
8345
8392
|
reasoning_tokens: 0,
|
|
8346
|
-
provider:
|
|
8393
|
+
provider: "anthropic",
|
|
8347
8394
|
model,
|
|
8348
|
-
cost: calculateCost(
|
|
8395
|
+
cost: calculateCost("anthropic", model, inputTokens, outputTokens),
|
|
8349
8396
|
},
|
|
8350
8397
|
};
|
|
8351
8398
|
}
|
|
@@ -8367,7 +8414,9 @@ async function makeAnthropicCall(content, responseFormat = 'text', options = {})
|
|
|
8367
8414
|
const isRetryableError = (error) => {
|
|
8368
8415
|
if (error instanceof Error) {
|
|
8369
8416
|
const message = error.message;
|
|
8370
|
-
if (message.includes(
|
|
8417
|
+
if (message.includes("429") ||
|
|
8418
|
+
message.includes("rate limit") ||
|
|
8419
|
+
message.includes("Rate limit")) {
|
|
8371
8420
|
return true;
|
|
8372
8421
|
}
|
|
8373
8422
|
}
|
|
@@ -8381,14 +8430,16 @@ function resolveApiKey(provider, optionsApiKey) {
|
|
|
8381
8430
|
if (optionsApiKey)
|
|
8382
8431
|
return optionsApiKey;
|
|
8383
8432
|
const secrets = getSecrets();
|
|
8384
|
-
const pathParts = provider.apiKeySecretPath.split(
|
|
8433
|
+
const pathParts = provider.apiKeySecretPath.split(".");
|
|
8385
8434
|
let current = secrets;
|
|
8386
8435
|
for (const part of pathParts) {
|
|
8387
|
-
if (current === null ||
|
|
8388
|
-
|
|
8436
|
+
if (current === null ||
|
|
8437
|
+
current === undefined ||
|
|
8438
|
+
typeof current !== "object")
|
|
8439
|
+
return "";
|
|
8389
8440
|
current = current[part];
|
|
8390
8441
|
}
|
|
8391
|
-
return (typeof current ===
|
|
8442
|
+
return (typeof current === "string" ? current : "") || "";
|
|
8392
8443
|
}
|
|
8393
8444
|
/**
|
|
8394
8445
|
* Makes a call to an OpenAI-compatible provider (Kimi, Qwen, or any future provider).
|
|
@@ -8402,12 +8453,12 @@ function resolveApiKey(provider, optionsApiKey) {
|
|
|
8402
8453
|
* @param providerName The provider key in OPENAI_COMPATIBLE_PROVIDERS.
|
|
8403
8454
|
* @return A promise that resolves to the response from the provider.
|
|
8404
8455
|
*/
|
|
8405
|
-
async function makeOpenAICompatibleCall(content, responseFormat =
|
|
8456
|
+
async function makeOpenAICompatibleCall(content, responseFormat = "text", options = {}, providerName) {
|
|
8406
8457
|
const providerConfig = OPENAI_COMPATIBLE_PROVIDERS[providerName];
|
|
8407
8458
|
if (!providerConfig) {
|
|
8408
8459
|
throw new Error(`Unknown OpenAI-compatible provider: ${providerName}`);
|
|
8409
8460
|
}
|
|
8410
|
-
const model = normalizeModelName(options.model ||
|
|
8461
|
+
const model = normalizeModelName(options.model || "");
|
|
8411
8462
|
const apiKey = resolveApiKey(providerConfig, options.apiKey);
|
|
8412
8463
|
if (!apiKey) {
|
|
8413
8464
|
throw new Error(`${providerConfig.name} API key is not provided and ${providerConfig.apiKeyEnvVar} environment variable is not set`);
|
|
@@ -8420,31 +8471,36 @@ async function makeOpenAICompatibleCall(content, responseFormat = 'text', option
|
|
|
8420
8471
|
const messages = [];
|
|
8421
8472
|
// Add system message with developer prompt if present
|
|
8422
8473
|
if (options.developerPrompt) {
|
|
8423
|
-
messages.push({ role:
|
|
8474
|
+
messages.push({ role: "system", content: options.developerPrompt });
|
|
8424
8475
|
}
|
|
8425
8476
|
// Add context if present
|
|
8426
8477
|
if (options.context) {
|
|
8427
8478
|
messages.push(...options.context);
|
|
8428
8479
|
}
|
|
8429
8480
|
// Add user content
|
|
8430
|
-
if (typeof content ===
|
|
8431
|
-
messages.push({ role:
|
|
8481
|
+
if (typeof content === "string") {
|
|
8482
|
+
messages.push({ role: "user", content });
|
|
8432
8483
|
}
|
|
8433
8484
|
else {
|
|
8434
|
-
messages.push({ role:
|
|
8485
|
+
messages.push({ role: "user", content });
|
|
8435
8486
|
}
|
|
8436
8487
|
// Determine if the model supports JSON mode
|
|
8437
8488
|
const supportsJson = isValidModel(model) && getModelCapabilities(model).supportsJson;
|
|
8438
8489
|
// If JSON response format, add hint to system prompt
|
|
8439
|
-
if ((responseFormat ===
|
|
8490
|
+
if ((responseFormat === "json" ||
|
|
8491
|
+
(typeof responseFormat === "object" &&
|
|
8492
|
+
responseFormat.type === "json_schema")) &&
|
|
8440
8493
|
supportsJson) {
|
|
8441
|
-
if (!messages.some((m) => m.role ===
|
|
8442
|
-
messages.unshift({
|
|
8494
|
+
if (!messages.some((m) => m.role === "system")) {
|
|
8495
|
+
messages.unshift({
|
|
8496
|
+
role: "system",
|
|
8497
|
+
content: "Please respond in valid JSON format.",
|
|
8498
|
+
});
|
|
8443
8499
|
}
|
|
8444
8500
|
else {
|
|
8445
|
-
const systemMsgIndex = messages.findIndex((m) => m.role ===
|
|
8501
|
+
const systemMsgIndex = messages.findIndex((m) => m.role === "system");
|
|
8446
8502
|
const systemMsg = messages[systemMsgIndex];
|
|
8447
|
-
if (typeof systemMsg.content ===
|
|
8503
|
+
if (typeof systemMsg.content === "string") {
|
|
8448
8504
|
messages[systemMsgIndex] = {
|
|
8449
8505
|
...systemMsg,
|
|
8450
8506
|
content: `${systemMsg.content} Please respond in valid JSON format.`,
|
|
@@ -8457,8 +8513,8 @@ async function makeOpenAICompatibleCall(content, responseFormat = 'text', option
|
|
|
8457
8513
|
messages,
|
|
8458
8514
|
};
|
|
8459
8515
|
// Add response format if JSON is supported
|
|
8460
|
-
if (responseFormat !==
|
|
8461
|
-
queryOptions.response_format = { type:
|
|
8516
|
+
if (responseFormat !== "text" && supportsJson) {
|
|
8517
|
+
queryOptions.response_format = { type: "json_object" };
|
|
8462
8518
|
}
|
|
8463
8519
|
// Temperature
|
|
8464
8520
|
if (options.temperature !== undefined) {
|
|
@@ -8516,7 +8572,7 @@ async function makeOpenAICompatibleCall(content, responseFormat = 'text', option
|
|
|
8516
8572
|
};
|
|
8517
8573
|
}
|
|
8518
8574
|
// Handle regular responses
|
|
8519
|
-
const textContent = completion.choices[0]?.message?.content ||
|
|
8575
|
+
const textContent = completion.choices[0]?.message?.content || "";
|
|
8520
8576
|
const parsedResponse = await parseResponse(textContent, responseFormat);
|
|
8521
8577
|
if (parsedResponse === null) {
|
|
8522
8578
|
throw new Error(`Failed to parse ${providerConfig.name} response`);
|
|
@@ -8808,7 +8864,9 @@ const isRetryableDeepseekError = (error) => {
|
|
|
8808
8864
|
if (error instanceof Error) {
|
|
8809
8865
|
const message = error.message;
|
|
8810
8866
|
// Retry only on rate limits (429)
|
|
8811
|
-
if (message.includes(
|
|
8867
|
+
if (message.includes("429") ||
|
|
8868
|
+
message.includes("rate limit") ||
|
|
8869
|
+
message.includes("Rate limit")) {
|
|
8812
8870
|
return true;
|
|
8813
8871
|
}
|
|
8814
8872
|
}
|
|
@@ -8818,7 +8876,7 @@ const isRetryableDeepseekError = (error) => {
|
|
|
8818
8876
|
* Default options for Deepseek API calls
|
|
8819
8877
|
*/
|
|
8820
8878
|
const DEFAULT_DEEPSEEK_OPTIONS = {
|
|
8821
|
-
defaultModel:
|
|
8879
|
+
defaultModel: "deepseek-chat",
|
|
8822
8880
|
};
|
|
8823
8881
|
/**
|
|
8824
8882
|
* Checks if the given model is a supported Deepseek model
|
|
@@ -8830,7 +8888,7 @@ const isSupportedDeepseekModel = (model) => {
|
|
|
8830
8888
|
return false;
|
|
8831
8889
|
}
|
|
8832
8890
|
const capabilities = getModelCapabilities(model);
|
|
8833
|
-
return capabilities.provider ===
|
|
8891
|
+
return capabilities.provider === "deepseek";
|
|
8834
8892
|
};
|
|
8835
8893
|
/**
|
|
8836
8894
|
* Checks if the given Deepseek model supports JSON output format
|
|
@@ -8863,17 +8921,19 @@ const supportsToolCalling = (model) => {
|
|
|
8863
8921
|
*/
|
|
8864
8922
|
function getDeepseekResponseFormatOption(responseFormat, model) {
|
|
8865
8923
|
// Check if the model supports JSON output
|
|
8866
|
-
if (responseFormat !==
|
|
8924
|
+
if (responseFormat !== "text" && !supportsJsonOutput(model)) {
|
|
8867
8925
|
getLumicLogger().warn(`Model ${model} does not support JSON output. Using text format instead.`);
|
|
8868
|
-
return { type:
|
|
8926
|
+
return { type: "text" };
|
|
8869
8927
|
}
|
|
8870
|
-
if (responseFormat ===
|
|
8871
|
-
return { type:
|
|
8928
|
+
if (responseFormat === "text") {
|
|
8929
|
+
return { type: "text" };
|
|
8872
8930
|
}
|
|
8873
|
-
if (responseFormat ===
|
|
8874
|
-
|
|
8931
|
+
if (responseFormat === "json" ||
|
|
8932
|
+
(typeof responseFormat === "object" &&
|
|
8933
|
+
responseFormat.type === "json_schema")) {
|
|
8934
|
+
return { type: "json_object" };
|
|
8875
8935
|
}
|
|
8876
|
-
return { type:
|
|
8936
|
+
return { type: "text" };
|
|
8877
8937
|
}
|
|
8878
8938
|
/**
|
|
8879
8939
|
* Creates a completion using the Deepseek API
|
|
@@ -8892,24 +8952,26 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
|
|
|
8892
8952
|
throw new Error(`Unsupported Deepseek model: ${normalizedModel}. Please use 'deepseek-chat' or 'deepseek-reasoner'.`);
|
|
8893
8953
|
}
|
|
8894
8954
|
// Check if tools are requested with a model that doesn't support them
|
|
8895
|
-
if (options.tools &&
|
|
8955
|
+
if (options.tools &&
|
|
8956
|
+
options.tools.length > 0 &&
|
|
8957
|
+
!supportsToolCalling(normalizedModel)) {
|
|
8896
8958
|
throw new Error(`Model ${normalizedModel} does not support tool calling.`);
|
|
8897
8959
|
}
|
|
8898
8960
|
const apiKey = options.apiKey || getSecrets().deepseek.apiKey;
|
|
8899
8961
|
if (!apiKey) {
|
|
8900
|
-
throw new Error(
|
|
8962
|
+
throw new Error("Deepseek API key is not provided and DEEPSEEK_API_KEY environment variable is not set");
|
|
8901
8963
|
}
|
|
8902
8964
|
// Initialize OpenAI client with Deepseek API URL
|
|
8903
8965
|
const openai = new OpenAI({
|
|
8904
8966
|
apiKey,
|
|
8905
|
-
baseURL:
|
|
8967
|
+
baseURL: "https://api.deepseek.com",
|
|
8906
8968
|
timeout: options.timeout ?? LLM_TIMEOUT_MS,
|
|
8907
8969
|
});
|
|
8908
8970
|
const messages = [];
|
|
8909
8971
|
// Add system message with developer prompt if present
|
|
8910
8972
|
if (options.developerPrompt) {
|
|
8911
8973
|
messages.push({
|
|
8912
|
-
role:
|
|
8974
|
+
role: "system",
|
|
8913
8975
|
content: options.developerPrompt,
|
|
8914
8976
|
});
|
|
8915
8977
|
}
|
|
@@ -8918,33 +8980,35 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
|
|
|
8918
8980
|
messages.push(...options.context);
|
|
8919
8981
|
}
|
|
8920
8982
|
// Add user content
|
|
8921
|
-
if (typeof content ===
|
|
8983
|
+
if (typeof content === "string") {
|
|
8922
8984
|
messages.push({
|
|
8923
|
-
role:
|
|
8985
|
+
role: "user",
|
|
8924
8986
|
content,
|
|
8925
8987
|
});
|
|
8926
8988
|
}
|
|
8927
8989
|
else {
|
|
8928
8990
|
messages.push({
|
|
8929
|
-
role:
|
|
8991
|
+
role: "user",
|
|
8930
8992
|
content,
|
|
8931
8993
|
});
|
|
8932
8994
|
}
|
|
8933
8995
|
// If JSON response format, include a hint in the system prompt
|
|
8934
|
-
if ((responseFormat ===
|
|
8935
|
-
|
|
8996
|
+
if ((responseFormat === "json" ||
|
|
8997
|
+
(typeof responseFormat === "object" &&
|
|
8998
|
+
responseFormat.type === "json_schema")) &&
|
|
8999
|
+
supportsJsonOutput(normalizedModel)) {
|
|
8936
9000
|
// If there's no system message yet, add one
|
|
8937
|
-
if (!messages.some(m => m.role ===
|
|
9001
|
+
if (!messages.some((m) => m.role === "system")) {
|
|
8938
9002
|
messages.unshift({
|
|
8939
|
-
role:
|
|
8940
|
-
content:
|
|
9003
|
+
role: "system",
|
|
9004
|
+
content: "Please respond in valid JSON format.",
|
|
8941
9005
|
});
|
|
8942
9006
|
}
|
|
8943
9007
|
else {
|
|
8944
9008
|
// Append to existing system message
|
|
8945
|
-
const systemMsgIndex = messages.findIndex(m => m.role ===
|
|
9009
|
+
const systemMsgIndex = messages.findIndex((m) => m.role === "system");
|
|
8946
9010
|
const systemMsg = messages[systemMsgIndex];
|
|
8947
|
-
if (typeof systemMsg.content ===
|
|
9011
|
+
if (typeof systemMsg.content === "string") {
|
|
8948
9012
|
messages[systemMsgIndex] = {
|
|
8949
9013
|
...systemMsg,
|
|
8950
9014
|
content: `${systemMsg.content} Please respond in valid JSON format.`,
|
|
@@ -8985,7 +9049,7 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
|
|
|
8985
9049
|
0;
|
|
8986
9050
|
return {
|
|
8987
9051
|
id: completion.id,
|
|
8988
|
-
content: completion.choices[0]?.message?.content ||
|
|
9052
|
+
content: completion.choices[0]?.message?.content || "",
|
|
8989
9053
|
tool_calls: completion.choices[0]?.message?.tool_calls,
|
|
8990
9054
|
usage: {
|
|
8991
9055
|
prompt_tokens: completion.usage?.prompt_tokens ?? 0,
|
|
@@ -8994,7 +9058,7 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
|
|
|
8994
9058
|
cached_tokens: cachedTokens,
|
|
8995
9059
|
},
|
|
8996
9060
|
system_fingerprint: completion.system_fingerprint,
|
|
8997
|
-
provider:
|
|
9061
|
+
provider: "deepseek",
|
|
8998
9062
|
model: normalizedModel,
|
|
8999
9063
|
};
|
|
9000
9064
|
}
|
|
@@ -9011,7 +9075,7 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
|
|
|
9011
9075
|
* @param options Configuration options including model ('deepseek-chat' or 'deepseek-reasoner'), tools, and apiKey.
|
|
9012
9076
|
* @return A promise that resolves to the response from the Deepseek API.
|
|
9013
9077
|
*/
|
|
9014
|
-
const makeDeepseekCall = async (content, responseFormat =
|
|
9078
|
+
const makeDeepseekCall = async (content, responseFormat = "json", options = {}) => {
|
|
9015
9079
|
// Set default model if not provided
|
|
9016
9080
|
const mergedOptions = {
|
|
9017
9081
|
...options,
|
|
@@ -9021,17 +9085,17 @@ const makeDeepseekCall = async (content, responseFormat = 'json', options = {})
|
|
|
9021
9085
|
}
|
|
9022
9086
|
const modelName = normalizeModelName(mergedOptions.model);
|
|
9023
9087
|
// Check if the requested response format is compatible with the model
|
|
9024
|
-
if (responseFormat !==
|
|
9088
|
+
if (responseFormat !== "text" && !supportsJsonOutput(modelName)) {
|
|
9025
9089
|
getLumicLogger().warn(`Model ${modelName} does not support JSON output. Will return error in the response.`);
|
|
9026
9090
|
return {
|
|
9027
9091
|
response: {
|
|
9028
|
-
error: `Model ${modelName} does not support JSON output format
|
|
9092
|
+
error: `Model ${modelName} does not support JSON output format.`,
|
|
9029
9093
|
},
|
|
9030
9094
|
usage: {
|
|
9031
9095
|
prompt_tokens: 0,
|
|
9032
9096
|
completion_tokens: 0,
|
|
9033
9097
|
reasoning_tokens: 0,
|
|
9034
|
-
provider:
|
|
9098
|
+
provider: "deepseek",
|
|
9035
9099
|
model: modelName,
|
|
9036
9100
|
cached_tokens: 0,
|
|
9037
9101
|
cost: 0,
|
|
@@ -9040,17 +9104,19 @@ const makeDeepseekCall = async (content, responseFormat = 'json', options = {})
|
|
|
9040
9104
|
};
|
|
9041
9105
|
}
|
|
9042
9106
|
// Check if tools are requested with a model that doesn't support them
|
|
9043
|
-
if (mergedOptions.tools &&
|
|
9107
|
+
if (mergedOptions.tools &&
|
|
9108
|
+
mergedOptions.tools.length > 0 &&
|
|
9109
|
+
!supportsToolCalling(modelName)) {
|
|
9044
9110
|
getLumicLogger().warn(`Model ${modelName} does not support tool calling. Will return error in the response.`);
|
|
9045
9111
|
return {
|
|
9046
9112
|
response: {
|
|
9047
|
-
error: `Model ${modelName} does not support tool calling
|
|
9113
|
+
error: `Model ${modelName} does not support tool calling.`,
|
|
9048
9114
|
},
|
|
9049
9115
|
usage: {
|
|
9050
9116
|
prompt_tokens: 0,
|
|
9051
9117
|
completion_tokens: 0,
|
|
9052
9118
|
reasoning_tokens: 0,
|
|
9053
|
-
provider:
|
|
9119
|
+
provider: "deepseek",
|
|
9054
9120
|
model: modelName,
|
|
9055
9121
|
cached_tokens: 0,
|
|
9056
9122
|
cost: 0,
|
|
@@ -9062,7 +9128,7 @@ const makeDeepseekCall = async (content, responseFormat = 'json', options = {})
|
|
|
9062
9128
|
const completion = await createDeepseekCompletion(content, responseFormat, mergedOptions);
|
|
9063
9129
|
// Track cost in the global cost tracker. Pass cached tokens through so the
|
|
9064
9130
|
// discounted cached-input pricing tier is applied.
|
|
9065
|
-
getLLMCostTracker().trackUsage(
|
|
9131
|
+
getLLMCostTracker().trackUsage("deepseek", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens);
|
|
9066
9132
|
// Handle tool calls similarly to OpenAI
|
|
9067
9133
|
if (completion.tool_calls && completion.tool_calls.length > 0) {
|
|
9068
9134
|
const toolCallResponse = {
|
|
@@ -9078,10 +9144,10 @@ const makeDeepseekCall = async (content, responseFormat = 'json', options = {})
|
|
|
9078
9144
|
prompt_tokens: completion.usage.prompt_tokens,
|
|
9079
9145
|
completion_tokens: completion.usage.completion_tokens,
|
|
9080
9146
|
reasoning_tokens: 0, // Deepseek doesn't provide reasoning tokens separately
|
|
9081
|
-
provider:
|
|
9147
|
+
provider: "deepseek",
|
|
9082
9148
|
model: completion.model,
|
|
9083
9149
|
cached_tokens: completion.usage.cached_tokens,
|
|
9084
|
-
cost: calculateCost(
|
|
9150
|
+
cost: calculateCost("deepseek", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
|
|
9085
9151
|
},
|
|
9086
9152
|
tool_calls: completion.tool_calls,
|
|
9087
9153
|
};
|
|
@@ -9089,7 +9155,7 @@ const makeDeepseekCall = async (content, responseFormat = 'json', options = {})
|
|
|
9089
9155
|
// Handle regular responses
|
|
9090
9156
|
const parsedResponse = await parseResponse(completion.content, responseFormat);
|
|
9091
9157
|
if (parsedResponse === null) {
|
|
9092
|
-
throw new Error(
|
|
9158
|
+
throw new Error("Failed to parse Deepseek response");
|
|
9093
9159
|
}
|
|
9094
9160
|
return {
|
|
9095
9161
|
response: parsedResponse,
|
|
@@ -9097,10 +9163,10 @@ const makeDeepseekCall = async (content, responseFormat = 'json', options = {})
|
|
|
9097
9163
|
prompt_tokens: completion.usage.prompt_tokens,
|
|
9098
9164
|
completion_tokens: completion.usage.completion_tokens,
|
|
9099
9165
|
reasoning_tokens: 0, // Deepseek doesn't provide reasoning tokens separately
|
|
9100
|
-
provider:
|
|
9166
|
+
provider: "deepseek",
|
|
9101
9167
|
model: completion.model,
|
|
9102
9168
|
cached_tokens: completion.usage.cached_tokens,
|
|
9103
|
-
cost: calculateCost(
|
|
9169
|
+
cost: calculateCost("deepseek", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
|
|
9104
9170
|
},
|
|
9105
9171
|
tool_calls: completion.tool_calls,
|
|
9106
9172
|
};
|
|
@@ -9110,13 +9176,13 @@ const makeDeepseekCall = async (content, responseFormat = 'json', options = {})
|
|
|
9110
9176
|
getLumicLogger().error(`Error in Deepseek API call: ${sanitizeError(error)}`);
|
|
9111
9177
|
return {
|
|
9112
9178
|
response: {
|
|
9113
|
-
error: sanitizeError(error)
|
|
9179
|
+
error: sanitizeError(error),
|
|
9114
9180
|
},
|
|
9115
9181
|
usage: {
|
|
9116
9182
|
prompt_tokens: 0,
|
|
9117
9183
|
completion_tokens: 0,
|
|
9118
9184
|
reasoning_tokens: 0,
|
|
9119
|
-
provider:
|
|
9185
|
+
provider: "deepseek",
|
|
9120
9186
|
model: modelName,
|
|
9121
9187
|
cached_tokens: 0,
|
|
9122
9188
|
cost: 0,
|
|
@@ -9225,15 +9291,15 @@ const generateCacheKey = (auth, envVarsCheck = null) => {
|
|
|
9225
9291
|
if (auth) {
|
|
9226
9292
|
return `${auth.AWS_ACCESS_KEY_ID}:${auth.AWS_SECRET_ACCESS_KEY}:${auth.AWS_REGION}`;
|
|
9227
9293
|
}
|
|
9228
|
-
if (envVarsCheck?.source ===
|
|
9294
|
+
if (envVarsCheck?.source === "lambda") {
|
|
9229
9295
|
// In Lambda, use function name as cache key since it uses IAM role
|
|
9230
9296
|
const secrets = getSecrets();
|
|
9231
|
-
return `lambda:${secrets.aws.lambdaFunctionName ||
|
|
9297
|
+
return `lambda:${secrets.aws.lambdaFunctionName || "default"}`;
|
|
9232
9298
|
}
|
|
9233
9299
|
if (envVarsCheck?.vars) {
|
|
9234
9300
|
return `${envVarsCheck.vars.accessKeyId}:${envVarsCheck.vars.secretAccessKey}:${envVarsCheck.vars.region}`;
|
|
9235
9301
|
}
|
|
9236
|
-
return
|
|
9302
|
+
return "default";
|
|
9237
9303
|
};
|
|
9238
9304
|
const validateEnvironmentVars = () => {
|
|
9239
9305
|
const secrets = getSecrets();
|
|
@@ -9246,37 +9312,37 @@ const validateEnvironmentVars = () => {
|
|
|
9246
9312
|
// We're in a Lambda environment
|
|
9247
9313
|
return {
|
|
9248
9314
|
isValid: true,
|
|
9249
|
-
source:
|
|
9250
|
-
vars: { accessKeyId:
|
|
9315
|
+
source: "lambda",
|
|
9316
|
+
vars: { accessKeyId: "", secretAccessKey: "", region: "" }, // No need for explicit credentials in Lambda
|
|
9251
9317
|
};
|
|
9252
9318
|
}
|
|
9253
9319
|
else if (areVarsComplete(secrets.aws.myAccessKeyId, secrets.aws.mySecretAccessKey, secrets.aws.myRegion)) {
|
|
9254
9320
|
return {
|
|
9255
9321
|
isValid: true,
|
|
9256
|
-
source:
|
|
9322
|
+
source: "my_prefix",
|
|
9257
9323
|
vars: {
|
|
9258
|
-
accessKeyId: secrets.aws.myAccessKeyId,
|
|
9259
|
-
secretAccessKey: secrets.aws.mySecretAccessKey,
|
|
9260
|
-
region: secrets.aws.myRegion
|
|
9261
|
-
}
|
|
9324
|
+
accessKeyId: secrets.aws.myAccessKeyId ?? "",
|
|
9325
|
+
secretAccessKey: secrets.aws.mySecretAccessKey ?? "",
|
|
9326
|
+
region: secrets.aws.myRegion ?? "",
|
|
9327
|
+
},
|
|
9262
9328
|
};
|
|
9263
9329
|
}
|
|
9264
9330
|
else if (areVarsComplete(secrets.aws.accessKeyId, secrets.aws.secretAccessKey, secrets.aws.region)) {
|
|
9265
9331
|
return {
|
|
9266
9332
|
isValid: true,
|
|
9267
|
-
source:
|
|
9333
|
+
source: "standard",
|
|
9268
9334
|
vars: {
|
|
9269
|
-
accessKeyId: secrets.aws.accessKeyId,
|
|
9270
|
-
secretAccessKey: secrets.aws.secretAccessKey,
|
|
9271
|
-
region: secrets.aws.region
|
|
9272
|
-
}
|
|
9335
|
+
accessKeyId: secrets.aws.accessKeyId ?? "",
|
|
9336
|
+
secretAccessKey: secrets.aws.secretAccessKey ?? "",
|
|
9337
|
+
region: secrets.aws.region ?? "",
|
|
9338
|
+
},
|
|
9273
9339
|
};
|
|
9274
9340
|
}
|
|
9275
9341
|
// If we get here, no complete set of variables was found
|
|
9276
9342
|
return {
|
|
9277
9343
|
isValid: false,
|
|
9278
9344
|
source: null,
|
|
9279
|
-
missingVars: []
|
|
9345
|
+
missingVars: [],
|
|
9280
9346
|
};
|
|
9281
9347
|
};
|
|
9282
9348
|
const initialiseAWSClient = (ClientClass, auth = null) => {
|
|
@@ -9285,48 +9351,52 @@ const initialiseAWSClient = (ClientClass, auth = null) => {
|
|
|
9285
9351
|
const requestTimeout = ClientClass === clientS3.S3Client ? AWS_S3_TIMEOUT_MS : AWS_LAMBDA_TIMEOUT_MS;
|
|
9286
9352
|
// Case 1: Explicit auth provided
|
|
9287
9353
|
if (auth) {
|
|
9288
|
-
if (typeof auth !==
|
|
9289
|
-
throw new Error(
|
|
9354
|
+
if (typeof auth !== "object" || auth === null) {
|
|
9355
|
+
throw new Error("Auth parameter must be an object");
|
|
9290
9356
|
}
|
|
9291
|
-
const requiredAuthFields = [
|
|
9292
|
-
|
|
9357
|
+
const requiredAuthFields = [
|
|
9358
|
+
"AWS_ACCESS_KEY_ID",
|
|
9359
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
9360
|
+
"AWS_REGION",
|
|
9361
|
+
];
|
|
9362
|
+
const missingFields = requiredAuthFields.filter((field) => !auth[field]);
|
|
9293
9363
|
if (missingFields.length > 0) {
|
|
9294
|
-
throw new Error(`Auth object is missing required fields: ${missingFields.join(
|
|
9364
|
+
throw new Error(`Auth object is missing required fields: ${missingFields.join(", ")}`);
|
|
9295
9365
|
}
|
|
9296
9366
|
return new ClientClass({
|
|
9297
9367
|
region: auth.AWS_REGION,
|
|
9298
9368
|
credentials: {
|
|
9299
9369
|
accessKeyId: auth.AWS_ACCESS_KEY_ID,
|
|
9300
|
-
secretAccessKey: auth.AWS_SECRET_ACCESS_KEY
|
|
9370
|
+
secretAccessKey: auth.AWS_SECRET_ACCESS_KEY,
|
|
9301
9371
|
},
|
|
9302
9372
|
requestHandler: {
|
|
9303
|
-
requestTimeout
|
|
9304
|
-
}
|
|
9373
|
+
requestTimeout,
|
|
9374
|
+
},
|
|
9305
9375
|
});
|
|
9306
9376
|
}
|
|
9307
9377
|
// Case 2: Check environment variables
|
|
9308
9378
|
const envVarsCheck = validateEnvironmentVars();
|
|
9309
9379
|
if (!envVarsCheck.isValid) {
|
|
9310
|
-
throw new Error(
|
|
9380
|
+
throw new Error("No valid AWS credentials found in environment variables");
|
|
9311
9381
|
}
|
|
9312
9382
|
// Case 2a: Lambda execution environment
|
|
9313
|
-
if (envVarsCheck.source ===
|
|
9383
|
+
if (envVarsCheck.source === "lambda") {
|
|
9314
9384
|
return new ClientClass({
|
|
9315
9385
|
requestHandler: {
|
|
9316
|
-
requestTimeout
|
|
9317
|
-
}
|
|
9386
|
+
requestTimeout,
|
|
9387
|
+
},
|
|
9318
9388
|
}); // AWS SDK will automatically use Lambda role credentials
|
|
9319
9389
|
}
|
|
9320
9390
|
// Case 2b: MY_ prefixed vars or standard AWS vars
|
|
9321
9391
|
return new ClientClass({
|
|
9322
|
-
region: envVarsCheck.vars
|
|
9392
|
+
region: envVarsCheck.vars?.region ?? "",
|
|
9323
9393
|
credentials: {
|
|
9324
|
-
accessKeyId: envVarsCheck.vars
|
|
9325
|
-
secretAccessKey: envVarsCheck.vars
|
|
9394
|
+
accessKeyId: envVarsCheck.vars?.accessKeyId ?? "",
|
|
9395
|
+
secretAccessKey: envVarsCheck.vars?.secretAccessKey ?? "",
|
|
9326
9396
|
},
|
|
9327
9397
|
requestHandler: {
|
|
9328
|
-
requestTimeout
|
|
9329
|
-
}
|
|
9398
|
+
requestTimeout,
|
|
9399
|
+
},
|
|
9330
9400
|
});
|
|
9331
9401
|
}
|
|
9332
9402
|
catch (error) {
|
|
@@ -9339,7 +9409,9 @@ const initialiseS3Client = (auth = null) => {
|
|
|
9339
9409
|
const envVarsCheck = auth ? null : validateEnvironmentVars();
|
|
9340
9410
|
const cacheKey = generateCacheKey(auth, envVarsCheck);
|
|
9341
9411
|
if (s3ClientCache.has(cacheKey)) {
|
|
9342
|
-
|
|
9412
|
+
const cached = s3ClientCache.get(cacheKey);
|
|
9413
|
+
if (cached)
|
|
9414
|
+
return cached;
|
|
9343
9415
|
}
|
|
9344
9416
|
// Create new client and cache it
|
|
9345
9417
|
const client = initialiseAWSClient(clientS3.S3Client, auth);
|
|
@@ -9351,7 +9423,9 @@ const initialiseLambdaClient = (auth = null) => {
|
|
|
9351
9423
|
const envVarsCheck = auth ? null : validateEnvironmentVars();
|
|
9352
9424
|
const cacheKey = generateCacheKey(auth, envVarsCheck);
|
|
9353
9425
|
if (lambdaClientCache.has(cacheKey)) {
|
|
9354
|
-
|
|
9426
|
+
const cached = lambdaClientCache.get(cacheKey);
|
|
9427
|
+
if (cached)
|
|
9428
|
+
return cached;
|
|
9355
9429
|
}
|
|
9356
9430
|
// Create new client and cache it
|
|
9357
9431
|
const client = initialiseAWSClient(clientLambda.Lambda, auth);
|
|
@@ -9851,15 +9925,17 @@ const isRetryableS3Error = (error) => {
|
|
|
9851
9925
|
if (error instanceof Error) {
|
|
9852
9926
|
const message = error.message;
|
|
9853
9927
|
// Retry on throttling
|
|
9854
|
-
if (message.includes(
|
|
9928
|
+
if (message.includes("SlowDown") || message.includes("RequestTimeout")) {
|
|
9855
9929
|
return true;
|
|
9856
9930
|
}
|
|
9857
9931
|
// Retry on 5xx server errors
|
|
9858
|
-
if (message.includes(
|
|
9932
|
+
if (message.includes("InternalError") ||
|
|
9933
|
+
message.includes("ServiceUnavailable")) {
|
|
9859
9934
|
return true;
|
|
9860
9935
|
}
|
|
9861
9936
|
// Retry on connection errors
|
|
9862
|
-
if (message.includes(
|
|
9937
|
+
if (message.includes("RequestTimeTooSkewed") ||
|
|
9938
|
+
message.includes("NetworkingError")) {
|
|
9863
9939
|
return true;
|
|
9864
9940
|
}
|
|
9865
9941
|
}
|
|
@@ -9873,10 +9949,11 @@ const generateRandomHash = (length) => {
|
|
|
9873
9949
|
};
|
|
9874
9950
|
function generateDateTimeStamp() {
|
|
9875
9951
|
const now = new Date();
|
|
9876
|
-
return now
|
|
9877
|
-
.
|
|
9878
|
-
.replace(
|
|
9879
|
-
.replace(
|
|
9952
|
+
return now
|
|
9953
|
+
.toISOString()
|
|
9954
|
+
.replace(/T/, "-")
|
|
9955
|
+
.replace(/\..+/, "")
|
|
9956
|
+
.replace(/:/g, "-");
|
|
9880
9957
|
}
|
|
9881
9958
|
const isValidBucketName = (name) => {
|
|
9882
9959
|
const regex = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/;
|
|
@@ -9907,7 +9984,7 @@ async function uploadFile(s3Client, bucketName, filePath, destinationPath, isPub
|
|
|
9907
9984
|
Bucket: bucketName,
|
|
9908
9985
|
Key: s3Key,
|
|
9909
9986
|
Body: fileContent,
|
|
9910
|
-
ACL: isPublic ?
|
|
9987
|
+
ACL: isPublic ? "public-read" : undefined,
|
|
9911
9988
|
});
|
|
9912
9989
|
await withRetry(() => s3Client.send(command), {
|
|
9913
9990
|
maxRetries: 3,
|
|
@@ -9927,7 +10004,7 @@ async function uploadDirectory(s3Client, bucketName, dirPath, destinationPath, i
|
|
|
9927
10004
|
Bucket: bucketName,
|
|
9928
10005
|
Key: s3Key,
|
|
9929
10006
|
Body: fileContent,
|
|
9930
|
-
ACL: isPublic ?
|
|
10007
|
+
ACL: isPublic ? "public-read" : undefined,
|
|
9931
10008
|
});
|
|
9932
10009
|
await withRetry(() => s3Client.send(command), {
|
|
9933
10010
|
maxRetries: 3,
|
|
@@ -9943,7 +10020,7 @@ async function zipDirectory(sourceDir, outPath) {
|
|
|
9943
10020
|
const zip = new AdmZip();
|
|
9944
10021
|
let fileCount = 0;
|
|
9945
10022
|
try {
|
|
9946
|
-
async function addFilesToZip(currentPath, relativePath =
|
|
10023
|
+
async function addFilesToZip(currentPath, relativePath = "") {
|
|
9947
10024
|
const files = await fs$1.readdir(currentPath, { withFileTypes: true });
|
|
9948
10025
|
for (const file of files) {
|
|
9949
10026
|
const filePath = path$1.join(currentPath, file.name);
|
|
@@ -9986,27 +10063,33 @@ async function downloadFromS3Helper(s3Client, bucketName, s3Path, localPath) {
|
|
|
9986
10063
|
Prefix: s3Path,
|
|
9987
10064
|
ContinuationToken: continuationToken,
|
|
9988
10065
|
});
|
|
9989
|
-
const listResponse = await withRetry(() => s3Client.send(listCommand), {
|
|
10066
|
+
const listResponse = (await withRetry(() => s3Client.send(listCommand), {
|
|
9990
10067
|
maxRetries: 3,
|
|
9991
10068
|
baseDelayMs: 1000,
|
|
9992
10069
|
maxDelayMs: 15000,
|
|
9993
10070
|
retryableErrors: isRetryableS3Error,
|
|
9994
|
-
}, `S3:ListObjects:${bucketName}`);
|
|
10071
|
+
}, `S3:ListObjects:${bucketName}`));
|
|
9995
10072
|
const objects = listResponse.Contents || [];
|
|
9996
10073
|
if (objects.length === 0) {
|
|
9997
10074
|
return { fileCount: 0, folderCount: 0, totalSize: 0, fileList: [] };
|
|
9998
10075
|
}
|
|
9999
|
-
if (objects.length === 1 && objects[0].Key?.endsWith(
|
|
10076
|
+
if (objects.length === 1 && objects[0].Key?.endsWith(".zip")) {
|
|
10000
10077
|
// It's a zip file, download and extract it
|
|
10001
10078
|
const zipPath = path$1.join(localPath, path$1.basename(objects[0].Key));
|
|
10002
10079
|
await downloadFile(s3Client, bucketName, objects[0].Key, zipPath);
|
|
10003
|
-
const { totalFiles, totalFolders, totalSize: extractedSize, fileList: extractedFileList } = await unzipFile(zipPath, localPath);
|
|
10080
|
+
const { totalFiles, totalFolders, totalSize: extractedSize, fileList: extractedFileList, } = await unzipFile(zipPath, localPath);
|
|
10004
10081
|
await fs$1.unlink(zipPath);
|
|
10005
|
-
return {
|
|
10082
|
+
return {
|
|
10083
|
+
fileCount: totalFiles,
|
|
10084
|
+
folderCount: totalFolders,
|
|
10085
|
+
totalSize: extractedSize,
|
|
10086
|
+
fileList: extractedFileList,
|
|
10087
|
+
};
|
|
10006
10088
|
}
|
|
10007
10089
|
else {
|
|
10008
10090
|
// Non-zip files. Download files in batches.
|
|
10009
|
-
for (let i = 0; i < objects.length; i += 1000) {
|
|
10091
|
+
for (let i = 0; i < objects.length; i += 1000) {
|
|
10092
|
+
// AWS allows up to 1000 per request
|
|
10010
10093
|
const batch = objects.slice(i, i + 1000);
|
|
10011
10094
|
await Promise.all(batch.map(async (obj) => {
|
|
10012
10095
|
if (!obj.Key)
|
|
@@ -10022,7 +10105,9 @@ async function downloadFromS3Helper(s3Client, bucketName, s3Path, localPath) {
|
|
|
10022
10105
|
}
|
|
10023
10106
|
// Count folders
|
|
10024
10107
|
const relativePath = path$1.relative(localPath, path$1.dirname(localFilePath));
|
|
10025
|
-
if (relativePath &&
|
|
10108
|
+
if (relativePath &&
|
|
10109
|
+
!relativePath.startsWith("..") &&
|
|
10110
|
+
relativePath !== ".") {
|
|
10026
10111
|
folderCount++;
|
|
10027
10112
|
}
|
|
10028
10113
|
}));
|
|
@@ -10043,7 +10128,7 @@ async function downloadFile(s3Client, bucketName, s3Key, localFilePath) {
|
|
|
10043
10128
|
await withRetry(async () => {
|
|
10044
10129
|
const { Body } = await s3Client.send(getCommand);
|
|
10045
10130
|
if (!Body)
|
|
10046
|
-
throw new Error(
|
|
10131
|
+
throw new Error("No body returned from S3");
|
|
10047
10132
|
const writeStream = fs.createWriteStream(localFilePath);
|
|
10048
10133
|
await promises.pipeline(Body, writeStream);
|
|
10049
10134
|
}, {
|
|
@@ -10099,17 +10184,19 @@ async function emptyBucket(s3Client, bucketName) {
|
|
|
10099
10184
|
Bucket: bucketName,
|
|
10100
10185
|
ContinuationToken: continuationToken,
|
|
10101
10186
|
});
|
|
10102
|
-
const response = await withRetry(() => s3Client.send(listCommand), {
|
|
10187
|
+
const response = (await withRetry(() => s3Client.send(listCommand), {
|
|
10103
10188
|
maxRetries: 3,
|
|
10104
10189
|
baseDelayMs: 1000,
|
|
10105
10190
|
maxDelayMs: 15000,
|
|
10106
10191
|
retryableErrors: isRetryableS3Error,
|
|
10107
|
-
}, `S3:ListObjects:${bucketName}`);
|
|
10192
|
+
}, `S3:ListObjects:${bucketName}`));
|
|
10108
10193
|
const { Contents, IsTruncated, NextContinuationToken } = response;
|
|
10109
10194
|
if (Contents && Contents.length > 0) {
|
|
10110
10195
|
const deleteCommand = new clientS3.DeleteObjectsCommand({
|
|
10111
10196
|
Bucket: bucketName,
|
|
10112
|
-
Delete: {
|
|
10197
|
+
Delete: {
|
|
10198
|
+
Objects: Contents.filter((c) => c.Key).map((c) => ({ Key: c.Key })),
|
|
10199
|
+
},
|
|
10113
10200
|
});
|
|
10114
10201
|
await withRetry(() => s3Client.send(deleteCommand), {
|
|
10115
10202
|
maxRetries: 3,
|
|
@@ -23017,11 +23104,11 @@ let poolConfig = DEFAULT_POOL_CONFIG;
|
|
|
23017
23104
|
async function loadApolloModules() {
|
|
23018
23105
|
if (typeof window === "undefined" || process.env.AWS_EXECUTION_ENV) {
|
|
23019
23106
|
// Server-side (or Lambda): load the CommonJS‑based implementation.
|
|
23020
|
-
return (await Promise.resolve().then(function () { return require('./apollo-client.server-
|
|
23107
|
+
return (await Promise.resolve().then(function () { return require('./apollo-client.server-DB3jLbBx.js'); }));
|
|
23021
23108
|
}
|
|
23022
23109
|
else {
|
|
23023
23110
|
// Client-side: load the ESM‑based implementation.
|
|
23024
|
-
return (await Promise.resolve().then(function () { return require('./apollo-client.client-
|
|
23111
|
+
return (await Promise.resolve().then(function () { return require('./apollo-client.client-CyxU1hTu.js'); }));
|
|
23025
23112
|
}
|
|
23026
23113
|
}
|
|
23027
23114
|
/**
|
|
@@ -79048,7 +79135,7 @@ const RETRY_CONFIG = {
|
|
|
79048
79135
|
BASE_DELAY: 2000, // Increased base delay to 2 seconds
|
|
79049
79136
|
MAX_DELAY: 64000,
|
|
79050
79137
|
MAX_RETRIES: 5,
|
|
79051
|
-
MAX_RANDOM_DELAY: 1000
|
|
79138
|
+
MAX_RANDOM_DELAY: 1000,
|
|
79052
79139
|
};
|
|
79053
79140
|
/**
|
|
79054
79141
|
* Determines if an error is related to Google Sheets API quotas or rate limits.
|
|
@@ -79074,10 +79161,10 @@ const RETRY_CONFIG = {
|
|
|
79074
79161
|
*/
|
|
79075
79162
|
function isQuotaError(error) {
|
|
79076
79163
|
const message = error.message.toLowerCase();
|
|
79077
|
-
return message.includes(
|
|
79078
|
-
message.includes(
|
|
79079
|
-
message.includes(
|
|
79080
|
-
message.includes(
|
|
79164
|
+
return (message.includes("quota exceeded") ||
|
|
79165
|
+
message.includes("rate limit") ||
|
|
79166
|
+
message.includes("429") ||
|
|
79167
|
+
message.includes("too many requests"));
|
|
79081
79168
|
}
|
|
79082
79169
|
/**
|
|
79083
79170
|
* Implements exponential backoff retry mechanism for Google Sheets API calls.
|
|
@@ -79136,14 +79223,14 @@ async function withExponentialBackoff(operation) {
|
|
|
79136
79223
|
const exponentialDelay = Math.min(Math.pow(2, retries) * RETRY_CONFIG.BASE_DELAY + randomDelay, RETRY_CONFIG.MAX_DELAY);
|
|
79137
79224
|
logIfDebug(`Quota/Rate limit exceeded. Error: ${error.message}`);
|
|
79138
79225
|
logIfDebug(`Retrying in ${exponentialDelay}ms (attempt ${retries}/${RETRY_CONFIG.MAX_RETRIES})`);
|
|
79139
|
-
await new Promise(resolve => setTimeout(resolve, exponentialDelay));
|
|
79226
|
+
await new Promise((resolve) => setTimeout(resolve, exponentialDelay));
|
|
79140
79227
|
}
|
|
79141
79228
|
}
|
|
79142
79229
|
}
|
|
79143
79230
|
function checkEnvVars() {
|
|
79144
79231
|
const secrets = getSecrets();
|
|
79145
79232
|
if (!secrets.googleSheets.clientEmail || !secrets.googleSheets.privateKey) {
|
|
79146
|
-
throw new Error(
|
|
79233
|
+
throw new Error("GOOGLE_SHEETS_CLIENT_EMAIL or GOOGLE_SHEETS_PRIVATE_KEY is not defined in environment variables.");
|
|
79147
79234
|
}
|
|
79148
79235
|
}
|
|
79149
79236
|
/**
|
|
@@ -79158,16 +79245,16 @@ async function getAuthClient() {
|
|
|
79158
79245
|
const secrets = getSecrets();
|
|
79159
79246
|
const credentials = {
|
|
79160
79247
|
client_email: secrets.googleSheets.clientEmail,
|
|
79161
|
-
private_key: secrets.googleSheets.privateKey?.replace(/\\n/g,
|
|
79248
|
+
private_key: secrets.googleSheets.privateKey?.replace(/\\n/g, "\n"),
|
|
79162
79249
|
};
|
|
79163
79250
|
const auth = new require$$0$6.GoogleAuth({
|
|
79164
79251
|
credentials,
|
|
79165
|
-
scopes: [
|
|
79252
|
+
scopes: ["https://www.googleapis.com/auth/spreadsheets"],
|
|
79166
79253
|
});
|
|
79167
79254
|
return auth.getClient();
|
|
79168
79255
|
}
|
|
79169
79256
|
catch (error) {
|
|
79170
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
79257
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
79171
79258
|
logIfDebug(`Error initializing Google Sheets auth: ${errorMessage}`);
|
|
79172
79259
|
throw new Error(`Failed to initialize Google Sheets auth: ${errorMessage}`);
|
|
79173
79260
|
}
|
|
@@ -79179,7 +79266,7 @@ async function getSheetsClient() {
|
|
|
79179
79266
|
const auth = await getAuthClient();
|
|
79180
79267
|
return new buildExports.sheets_v4.Sheets({
|
|
79181
79268
|
auth,
|
|
79182
|
-
timeout: GOOGLE_SHEETS_TIMEOUT_MS
|
|
79269
|
+
timeout: GOOGLE_SHEETS_TIMEOUT_MS,
|
|
79183
79270
|
});
|
|
79184
79271
|
}
|
|
79185
79272
|
/**
|
|
@@ -79212,7 +79299,7 @@ function escapeSheetName(sheetName) {
|
|
|
79212
79299
|
* @param startColumn Optional starting column (defaults to 'A')
|
|
79213
79300
|
* @returns Promise resolving to response with success status and row details
|
|
79214
79301
|
*/
|
|
79215
|
-
async function addRow(config, values, startColumn =
|
|
79302
|
+
async function addRow(config, values, startColumn = "A") {
|
|
79216
79303
|
try {
|
|
79217
79304
|
const sheets = await getSheetsClient();
|
|
79218
79305
|
const escapedSheetName = escapeSheetName(config.sheetName);
|
|
@@ -79220,7 +79307,7 @@ async function addRow(config, values, startColumn = 'A') {
|
|
|
79220
79307
|
const response = await withExponentialBackoff(() => sheets.spreadsheets.values.append({
|
|
79221
79308
|
spreadsheetId: config.spreadsheetId,
|
|
79222
79309
|
range,
|
|
79223
|
-
valueInputOption:
|
|
79310
|
+
valueInputOption: "USER_ENTERED",
|
|
79224
79311
|
requestBody: {
|
|
79225
79312
|
values: [values],
|
|
79226
79313
|
},
|
|
@@ -79228,9 +79315,9 @@ async function addRow(config, values, startColumn = 'A') {
|
|
|
79228
79315
|
//logIfDebug(`Added row to sheet ${config.sheetName}: ${values.join(', ')}`);
|
|
79229
79316
|
const rowNumber = response.data.updates?.updatedRange
|
|
79230
79317
|
? parseInt(response.data.updates.updatedRange
|
|
79231
|
-
.split(
|
|
79232
|
-
.split(
|
|
79233
|
-
.replace(/[^0-9]/g,
|
|
79318
|
+
.split("!")[1]
|
|
79319
|
+
.split(":")[0]
|
|
79320
|
+
.replace(/[^0-9]/g, ""))
|
|
79234
79321
|
: 0;
|
|
79235
79322
|
return {
|
|
79236
79323
|
success: true,
|
|
@@ -79241,7 +79328,7 @@ async function addRow(config, values, startColumn = 'A') {
|
|
|
79241
79328
|
};
|
|
79242
79329
|
}
|
|
79243
79330
|
catch (error) {
|
|
79244
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
79331
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
79245
79332
|
logIfDebug(`Error adding row to sheet: ${errorMessage}`);
|
|
79246
79333
|
return {
|
|
79247
79334
|
success: false,
|
|
@@ -79283,7 +79370,7 @@ async function addSheet(spreadsheetId, sheetTitle) {
|
|
|
79283
79370
|
};
|
|
79284
79371
|
}
|
|
79285
79372
|
catch (error) {
|
|
79286
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
79373
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
79287
79374
|
logIfDebug(`Error adding sheet: ${errorMessage}`);
|
|
79288
79375
|
return {
|
|
79289
79376
|
success: false,
|
|
@@ -79307,7 +79394,7 @@ async function writeCell(config, cell, value) {
|
|
|
79307
79394
|
await withExponentialBackoff(() => sheets.spreadsheets.values.update({
|
|
79308
79395
|
spreadsheetId: config.spreadsheetId,
|
|
79309
79396
|
range,
|
|
79310
|
-
valueInputOption:
|
|
79397
|
+
valueInputOption: "USER_ENTERED",
|
|
79311
79398
|
requestBody: {
|
|
79312
79399
|
values: [[value]],
|
|
79313
79400
|
},
|
|
@@ -79321,7 +79408,7 @@ async function writeCell(config, cell, value) {
|
|
|
79321
79408
|
};
|
|
79322
79409
|
}
|
|
79323
79410
|
catch (error) {
|
|
79324
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
79411
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
79325
79412
|
logIfDebug(`Error writing to cell: ${errorMessage}`);
|
|
79326
79413
|
return {
|
|
79327
79414
|
success: false,
|
|
@@ -79345,8 +79432,11 @@ async function getCell(config, cell) {
|
|
|
79345
79432
|
spreadsheetId: config.spreadsheetId,
|
|
79346
79433
|
range,
|
|
79347
79434
|
}));
|
|
79348
|
-
const
|
|
79349
|
-
|
|
79435
|
+
const rawValue = response.data.values?.[0]?.[0];
|
|
79436
|
+
const value = rawValue === undefined || rawValue === null
|
|
79437
|
+
? null
|
|
79438
|
+
: rawValue;
|
|
79439
|
+
logIfDebug(`Read value from cell ${range}: ${String(value)}`);
|
|
79350
79440
|
return {
|
|
79351
79441
|
success: true,
|
|
79352
79442
|
data: {
|
|
@@ -79355,7 +79445,7 @@ async function getCell(config, cell) {
|
|
|
79355
79445
|
};
|
|
79356
79446
|
}
|
|
79357
79447
|
catch (error) {
|
|
79358
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
79448
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
79359
79449
|
logIfDebug(`Error reading from cell: ${errorMessage}`);
|
|
79360
79450
|
return {
|
|
79361
79451
|
success: false,
|
|
@@ -79388,10 +79478,11 @@ function convertA1ToRowCol(a1Notation) {
|
|
|
79388
79478
|
* @returns Padded array of arrays
|
|
79389
79479
|
*/
|
|
79390
79480
|
function padArrays(arrays) {
|
|
79391
|
-
const maxLength = Math.max(...arrays.map(arr => arr.length));
|
|
79392
|
-
return arrays.map(arr => {
|
|
79481
|
+
const maxLength = Math.max(...arrays.map((arr) => arr.length));
|
|
79482
|
+
return arrays.map((arr) => {
|
|
79393
79483
|
if (arr.length < maxLength) {
|
|
79394
|
-
|
|
79484
|
+
const padding = Array(maxLength - arr.length).fill("");
|
|
79485
|
+
return [...arr, ...padding];
|
|
79395
79486
|
}
|
|
79396
79487
|
return arr;
|
|
79397
79488
|
});
|
|
@@ -79403,10 +79494,10 @@ function padArrays(arrays) {
|
|
|
79403
79494
|
* @param startCell Optional starting cell in A1 notation (defaults to 'A1')
|
|
79404
79495
|
* @returns Promise resolving to response with success status and update details
|
|
79405
79496
|
*/
|
|
79406
|
-
async function writeArray(config, data, startCell =
|
|
79497
|
+
async function writeArray(config, data, startCell = "A1") {
|
|
79407
79498
|
try {
|
|
79408
79499
|
if (!data.length) {
|
|
79409
|
-
throw new Error(
|
|
79500
|
+
throw new Error("Data array is empty");
|
|
79410
79501
|
}
|
|
79411
79502
|
const sheets = await getSheetsClient();
|
|
79412
79503
|
const { row: startRow, column: startCol } = convertA1ToRowCol(startCell);
|
|
@@ -79422,7 +79513,7 @@ async function writeArray(config, data, startCell = 'A1') {
|
|
|
79422
79513
|
const response = await withExponentialBackoff(() => sheets.spreadsheets.values.update({
|
|
79423
79514
|
spreadsheetId: config.spreadsheetId,
|
|
79424
79515
|
range,
|
|
79425
|
-
valueInputOption:
|
|
79516
|
+
valueInputOption: "USER_ENTERED",
|
|
79426
79517
|
requestBody: {
|
|
79427
79518
|
values: paddedData,
|
|
79428
79519
|
},
|
|
@@ -79438,7 +79529,7 @@ async function writeArray(config, data, startCell = 'A1') {
|
|
|
79438
79529
|
};
|
|
79439
79530
|
}
|
|
79440
79531
|
catch (error) {
|
|
79441
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
79532
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
79442
79533
|
logIfDebug(`Error writing array to sheet: ${errorMessage}`);
|
|
79443
79534
|
return {
|
|
79444
79535
|
success: false,
|
|
@@ -81732,4 +81823,4 @@ exports.withCorrelationId = withCorrelationId;
|
|
|
81732
81823
|
exports.withMetrics = withMetrics;
|
|
81733
81824
|
exports.withRateLimit = withRateLimit;
|
|
81734
81825
|
exports.withRetry = withRetry;
|
|
81735
|
-
//# sourceMappingURL=index-
|
|
81826
|
+
//# sourceMappingURL=index-KzQOh2uu.js.map
|