@adaptic/lumic-utils 1.0.22 → 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-Bu5mc2I8.js → apollo-client.client-9wJcufhf.js} +4 -4
- package/dist/{apollo-client.client-Bu5mc2I8.js.map → apollo-client.client-9wJcufhf.js.map} +1 -1
- package/dist/{apollo-client.client-YzlpL7kf.js → apollo-client.client-CyxU1hTu.js} +3 -3
- package/dist/{apollo-client.client-YzlpL7kf.js.map → apollo-client.client-CyxU1hTu.js.map} +1 -1
- package/dist/{apollo-client.server-sD1QteQO.js → apollo-client.server-CbagxkmK.js} +3 -3
- package/dist/{apollo-client.server-sD1QteQO.js.map → apollo-client.server-CbagxkmK.js.map} +1 -1
- package/dist/{apollo-client.server-BTHAQqYl.js → apollo-client.server-DB3jLbBx.js} +3 -3
- package/dist/{apollo-client.server-BTHAQqYl.js.map → apollo-client.server-DB3jLbBx.js.map} +1 -1
- package/dist/{index-CWx6sW9a.js → index-B4yVKGNR.js} +2 -2
- package/dist/{index-CWx6sW9a.js.map → index-B4yVKGNR.js.map} +1 -1
- package/dist/{index-B_4Q2noT.js → index-CL79JTWc.js} +2 -2
- package/dist/{index-B_4Q2noT.js.map → index-CL79JTWc.js.map} +1 -1
- package/dist/{index-DPaCMqua.js → index-EMQ1uJMh.js} +380 -281
- package/dist/{index-DPaCMqua.js.map → index-EMQ1uJMh.js.map} +1 -1
- package/dist/{index-DfuMX-MS.js → index-KzQOh2uu.js} +380 -281
- package/dist/{index-DfuMX-MS.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 +17 -8
- package/dist/types/utils/aws-initialise.d.ts +4 -4
- package/package.json +2 -2
|
@@ -100,13 +100,21 @@ const OPENAI_COMPATIBLE_PROVIDERS = {
|
|
|
100
100
|
*/
|
|
101
101
|
const SUPPORTED_MODELS = {
|
|
102
102
|
// ── OpenAI GPT-5.5 series (current flagship) ─────────────────────────
|
|
103
|
+
// GPT-5.x series: OpenAI's 2026 API constraint — only the default
|
|
104
|
+
// temperature value (1.0) is accepted. Passing any other value returns
|
|
105
|
+
// HTTP 400: "Unsupported value: 'temperature' does not support 0.2 with
|
|
106
|
+
// this model. Only the default (1) value is supported." Mark
|
|
107
|
+
// supportsTemperature=false so the lumic-utils gate at llm-call.ts:194
|
|
108
|
+
// OMITS the parameter entirely (OpenAI then uses its default of 1.0).
|
|
109
|
+
// Ref: community.openai.com/t/temperature-in-gpt-5-models/1337133
|
|
110
|
+
// Ref: developers.openai.com/api/docs/models/gpt-5.4-nano
|
|
103
111
|
'gpt-5.5': {
|
|
104
112
|
provider: 'openai',
|
|
105
113
|
supportsTools: true,
|
|
106
114
|
supportsJson: true,
|
|
107
115
|
supportsStructuredOutput: true,
|
|
108
116
|
supportsDeveloperPrompt: true,
|
|
109
|
-
supportsTemperature:
|
|
117
|
+
supportsTemperature: false,
|
|
110
118
|
isReasoningModel: false,
|
|
111
119
|
},
|
|
112
120
|
'gpt-5.5-pro': {
|
|
@@ -115,7 +123,7 @@ const SUPPORTED_MODELS = {
|
|
|
115
123
|
supportsJson: true,
|
|
116
124
|
supportsStructuredOutput: true,
|
|
117
125
|
supportsDeveloperPrompt: true,
|
|
118
|
-
supportsTemperature:
|
|
126
|
+
supportsTemperature: false,
|
|
119
127
|
isReasoningModel: false,
|
|
120
128
|
},
|
|
121
129
|
// ── OpenAI GPT-5.4 series ────────────────────────────────────────────
|
|
@@ -125,7 +133,7 @@ const SUPPORTED_MODELS = {
|
|
|
125
133
|
supportsJson: true,
|
|
126
134
|
supportsStructuredOutput: true,
|
|
127
135
|
supportsDeveloperPrompt: true,
|
|
128
|
-
supportsTemperature:
|
|
136
|
+
supportsTemperature: false,
|
|
129
137
|
isReasoningModel: false,
|
|
130
138
|
},
|
|
131
139
|
'gpt-5.4-mini': {
|
|
@@ -134,7 +142,7 @@ const SUPPORTED_MODELS = {
|
|
|
134
142
|
supportsJson: true,
|
|
135
143
|
supportsStructuredOutput: true,
|
|
136
144
|
supportsDeveloperPrompt: true,
|
|
137
|
-
supportsTemperature:
|
|
145
|
+
supportsTemperature: false,
|
|
138
146
|
isReasoningModel: false,
|
|
139
147
|
},
|
|
140
148
|
'gpt-5.4-nano': {
|
|
@@ -143,7 +151,7 @@ const SUPPORTED_MODELS = {
|
|
|
143
151
|
supportsJson: true,
|
|
144
152
|
supportsStructuredOutput: true,
|
|
145
153
|
supportsDeveloperPrompt: true,
|
|
146
|
-
supportsTemperature:
|
|
154
|
+
supportsTemperature: false,
|
|
147
155
|
isReasoningModel: false,
|
|
148
156
|
},
|
|
149
157
|
// ── OpenAI GPT-5 series ──────────────────────────────────────────────
|
|
@@ -153,7 +161,7 @@ const SUPPORTED_MODELS = {
|
|
|
153
161
|
supportsJson: true,
|
|
154
162
|
supportsStructuredOutput: true,
|
|
155
163
|
supportsDeveloperPrompt: true,
|
|
156
|
-
supportsTemperature:
|
|
164
|
+
supportsTemperature: false,
|
|
157
165
|
isReasoningModel: false,
|
|
158
166
|
},
|
|
159
167
|
'gpt-5-mini': {
|
|
@@ -162,7 +170,7 @@ const SUPPORTED_MODELS = {
|
|
|
162
170
|
supportsJson: true,
|
|
163
171
|
supportsStructuredOutput: true,
|
|
164
172
|
supportsDeveloperPrompt: true,
|
|
165
|
-
supportsTemperature:
|
|
173
|
+
supportsTemperature: false,
|
|
166
174
|
isReasoningModel: false,
|
|
167
175
|
},
|
|
168
176
|
// ── OpenAI GPT-4.1 series ────────────────────────────────────────────
|
|
@@ -563,10 +571,19 @@ function getModelProvider(model) {
|
|
|
563
571
|
return SUPPORTED_MODELS[model].provider;
|
|
564
572
|
}
|
|
565
573
|
/**
|
|
566
|
-
* 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.
|
|
567
584
|
*/
|
|
568
585
|
const PROVIDER_DEFAULT_MODELS = {
|
|
569
|
-
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' },
|
|
570
587
|
anthropic: { mini: 'claude-haiku-4-5', normal: 'claude-sonnet-4-6', advanced: 'claude-opus-4-7' },
|
|
571
588
|
deepseek: { mini: 'deepseek-v4-flash', normal: 'deepseek-v4-flash', advanced: 'deepseek-v4-pro' },
|
|
572
589
|
kimi: { mini: 'kimi-k2-0905-preview', normal: 'kimi-k2.5', advanced: 'kimi-k2.6' },
|
|
@@ -1520,11 +1537,13 @@ let openai;
|
|
|
1520
1537
|
function initializeOpenAI(apiKey) {
|
|
1521
1538
|
const key = apiKey || getSecrets().openai.apiKey;
|
|
1522
1539
|
if (!key) {
|
|
1523
|
-
throw new Error(
|
|
1540
|
+
throw new Error("OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set");
|
|
1524
1541
|
}
|
|
1525
1542
|
return new OpenAI({
|
|
1526
1543
|
apiKey: key,
|
|
1527
|
-
defaultHeaders: {
|
|
1544
|
+
defaultHeaders: {
|
|
1545
|
+
"User-Agent": "My Server-side Application (compatible; OpenAI API Client)",
|
|
1546
|
+
},
|
|
1528
1547
|
});
|
|
1529
1548
|
}
|
|
1530
1549
|
/**
|
|
@@ -1542,9 +1561,7 @@ function initializeOpenAI(apiKey) {
|
|
|
1542
1561
|
*/
|
|
1543
1562
|
async function fixJsonWithAI(jsonStr, options) {
|
|
1544
1563
|
// Backward compatibility: handle string apiKey as first positional param
|
|
1545
|
-
const opts = typeof options ===
|
|
1546
|
-
? { apiKey: options }
|
|
1547
|
-
: (options ?? {});
|
|
1564
|
+
const opts = typeof options === "string" ? { apiKey: options } : (options ?? {});
|
|
1548
1565
|
const apiKey = opts.apiKey;
|
|
1549
1566
|
const maxDepth = opts.maxDepth ?? 2;
|
|
1550
1567
|
const depth = opts._depth ?? 0;
|
|
@@ -1557,18 +1574,18 @@ async function fixJsonWithAI(jsonStr, options) {
|
|
|
1557
1574
|
openai = initializeOpenAI(apiKey);
|
|
1558
1575
|
}
|
|
1559
1576
|
const completion = await openai.chat.completions.create({
|
|
1560
|
-
model:
|
|
1577
|
+
model: "gpt-5-mini",
|
|
1561
1578
|
messages: [
|
|
1562
1579
|
{
|
|
1563
|
-
role:
|
|
1564
|
-
content:
|
|
1580
|
+
role: "system",
|
|
1581
|
+
content: "You are a JSON fixer. Return only valid JSON without any additional text or explanation.",
|
|
1565
1582
|
},
|
|
1566
1583
|
{
|
|
1567
|
-
role:
|
|
1568
|
-
content: `Fix this broken JSON:\n${jsonStr}
|
|
1569
|
-
}
|
|
1584
|
+
role: "user",
|
|
1585
|
+
content: `Fix this broken JSON:\n${jsonStr}`,
|
|
1586
|
+
},
|
|
1570
1587
|
],
|
|
1571
|
-
response_format: { type:
|
|
1588
|
+
response_format: { type: "json_object" },
|
|
1572
1589
|
});
|
|
1573
1590
|
const fixedJson = completion.choices[0]?.message?.content;
|
|
1574
1591
|
if (fixedJson && isValidJson(fixedJson)) {
|
|
@@ -1582,23 +1599,32 @@ async function fixJsonWithAI(jsonStr, options) {
|
|
|
1582
1599
|
_depth: depth + 1,
|
|
1583
1600
|
});
|
|
1584
1601
|
}
|
|
1585
|
-
throw new JsonParseError(
|
|
1602
|
+
throw new JsonParseError("Failed to fix JSON with AI: returned invalid JSON");
|
|
1586
1603
|
}
|
|
1587
1604
|
catch (err) {
|
|
1588
1605
|
// Re-throw JsonParseError as-is (e.g. max depth exceeded)
|
|
1589
1606
|
if (err instanceof JsonParseError) {
|
|
1590
1607
|
throw err;
|
|
1591
1608
|
}
|
|
1592
|
-
const errorMessage = err instanceof Error ? err.message :
|
|
1609
|
+
const errorMessage = err instanceof Error ? err.message : "Unknown error occurred";
|
|
1593
1610
|
throw new JsonParseError(`Error fixing JSON with AI: ${errorMessage}`);
|
|
1594
1611
|
}
|
|
1595
1612
|
}
|
|
1596
1613
|
|
|
1597
1614
|
// llm-utils.ts
|
|
1598
1615
|
function normalizeModelName(model) {
|
|
1599
|
-
return model.replace(/_/g,
|
|
1600
|
-
}
|
|
1601
|
-
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
|
+
];
|
|
1602
1628
|
/**
|
|
1603
1629
|
* Tries to parse JSON from the given content string according to the specified
|
|
1604
1630
|
* response format. If the response format is 'json' or a JSON schema object, it
|
|
@@ -1622,7 +1648,9 @@ async function parseResponse(content, responseFormat, options = {}) {
|
|
|
1622
1648
|
if (recursionDepth > maxRetries) {
|
|
1623
1649
|
throw new Error(`Maximum recursion depth (${maxRetries}) exceeded while parsing JSON. AI fixing may have returned broken JSON.`);
|
|
1624
1650
|
}
|
|
1625
|
-
if (responseFormat ===
|
|
1651
|
+
if (responseFormat === "json" ||
|
|
1652
|
+
(typeof responseFormat === "object" &&
|
|
1653
|
+
responseFormat?.type === "json_schema")) {
|
|
1626
1654
|
let cleanedContent = content.trim();
|
|
1627
1655
|
let detectedType = null;
|
|
1628
1656
|
// Remove code block markers if present
|
|
@@ -1633,8 +1661,8 @@ async function parseResponse(content, responseFormat, options = {}) {
|
|
|
1633
1661
|
}
|
|
1634
1662
|
return false;
|
|
1635
1663
|
});
|
|
1636
|
-
if (startsWithCodeBlock && cleanedContent.endsWith(
|
|
1637
|
-
const firstLineEndIndex = cleanedContent.indexOf(
|
|
1664
|
+
if (startsWithCodeBlock && cleanedContent.endsWith("```")) {
|
|
1665
|
+
const firstLineEndIndex = cleanedContent.indexOf("\n");
|
|
1638
1666
|
if (firstLineEndIndex !== -1) {
|
|
1639
1667
|
cleanedContent = cleanedContent.slice(firstLineEndIndex + 1, -3).trim();
|
|
1640
1668
|
getLumicLogger().info(`Code block for type ${detectedType} detected and removed. Cleaned content: ${cleanedContent}`);
|
|
@@ -1667,7 +1695,7 @@ async function parseResponse(content, responseFormat, options = {}) {
|
|
|
1667
1695
|
},
|
|
1668
1696
|
// Strategy 3: Remove leading/trailing text and try parsing
|
|
1669
1697
|
() => {
|
|
1670
|
-
const jsonMatch = cleanedContent.replace(/^[^{[]*|[^}\]]*$/g,
|
|
1698
|
+
const jsonMatch = cleanedContent.replace(/^[^{[]*|[^}\]]*$/g, "");
|
|
1671
1699
|
try {
|
|
1672
1700
|
return JSON.parse(jsonMatch);
|
|
1673
1701
|
}
|
|
@@ -1689,7 +1717,7 @@ async function parseResponse(content, responseFormat, options = {}) {
|
|
|
1689
1717
|
}
|
|
1690
1718
|
// Strategy 5: Use AI fixing (only if explicitly enabled)
|
|
1691
1719
|
if (enableAiFix) {
|
|
1692
|
-
getLumicLogger().warn(
|
|
1720
|
+
getLumicLogger().warn("Using AI-powered JSON fixing. This adds cost and latency. Consider improving JSON generation instead.");
|
|
1693
1721
|
try {
|
|
1694
1722
|
const aiFixedJson = await fixJsonWithAI(cleanedContent);
|
|
1695
1723
|
if (aiFixedJson !== null) {
|
|
@@ -1702,7 +1730,7 @@ async function parseResponse(content, responseFormat, options = {}) {
|
|
|
1702
1730
|
catch {
|
|
1703
1731
|
// AI returned something that looks like JSON but isn't valid
|
|
1704
1732
|
// Increment recursion depth and try parsing the AI response
|
|
1705
|
-
getLumicLogger().warn(
|
|
1733
|
+
getLumicLogger().warn("AI fixing returned invalid JSON, attempting recursive parse...");
|
|
1706
1734
|
return parseResponse(JSON.stringify(aiFixedJson), responseFormat, {
|
|
1707
1735
|
...options,
|
|
1708
1736
|
_recursionDepth: recursionDepth + 1,
|
|
@@ -1711,14 +1739,14 @@ async function parseResponse(content, responseFormat, options = {}) {
|
|
|
1711
1739
|
}
|
|
1712
1740
|
}
|
|
1713
1741
|
catch (error) {
|
|
1714
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
1742
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1715
1743
|
getLumicLogger().error(`AI JSON fixing failed: ${errorMessage}`);
|
|
1716
1744
|
// Continue to final error below
|
|
1717
1745
|
}
|
|
1718
1746
|
}
|
|
1719
1747
|
// If all strategies fail, throw an error
|
|
1720
1748
|
getLumicLogger().error(`Failed to parse JSON from content: ${cleanedContent}. Original JSON: ${content}`);
|
|
1721
|
-
throw new Error(
|
|
1749
|
+
throw new Error("Unable to parse JSON response");
|
|
1722
1750
|
}
|
|
1723
1751
|
else {
|
|
1724
1752
|
return content;
|
|
@@ -2312,13 +2340,15 @@ const isRetryableLLMError = (error) => {
|
|
|
2312
2340
|
if (error instanceof Error) {
|
|
2313
2341
|
const message = error.message;
|
|
2314
2342
|
// Retry on rate limits (429)
|
|
2315
|
-
if (message.includes(
|
|
2343
|
+
if (message.includes("429") ||
|
|
2344
|
+
message.includes("rate limit") ||
|
|
2345
|
+
message.includes("Rate limit")) {
|
|
2316
2346
|
return true;
|
|
2317
2347
|
}
|
|
2318
2348
|
// Retry on transient body-corruption 400s. Match the exact OpenAI error
|
|
2319
2349
|
// string to avoid retrying genuine client-side validation 400s (which
|
|
2320
2350
|
// would re-fail forever and waste retry budget).
|
|
2321
|
-
if (message.includes(
|
|
2351
|
+
if (message.includes("could not parse the JSON body of your request")) {
|
|
2322
2352
|
return true;
|
|
2323
2353
|
}
|
|
2324
2354
|
}
|
|
@@ -2387,23 +2417,26 @@ function isReasoningModel(model) {
|
|
|
2387
2417
|
* @throws Error if the response format is invalid or incompatible.
|
|
2388
2418
|
*/
|
|
2389
2419
|
function getResponseFormatOption(responseFormat, normalizedModel) {
|
|
2390
|
-
if (responseFormat ===
|
|
2391
|
-
return { type:
|
|
2420
|
+
if (responseFormat === "text") {
|
|
2421
|
+
return { type: "text" };
|
|
2392
2422
|
}
|
|
2393
|
-
if (responseFormat ===
|
|
2394
|
-
return supportsJsonMode(normalizedModel)
|
|
2423
|
+
if (responseFormat === "json") {
|
|
2424
|
+
return supportsJsonMode(normalizedModel)
|
|
2425
|
+
? { type: "json_object" }
|
|
2426
|
+
: { type: "text" };
|
|
2395
2427
|
}
|
|
2396
|
-
if (typeof responseFormat ===
|
|
2428
|
+
if (typeof responseFormat === "object" &&
|
|
2429
|
+
responseFormat.type === "json_schema") {
|
|
2397
2430
|
if (!isStructuredOutputCompatibleModel(normalizedModel)) {
|
|
2398
2431
|
throw new Error(`${normalizedModel} is not compatible with structured outputs / json object.`);
|
|
2399
2432
|
}
|
|
2400
2433
|
return {
|
|
2401
|
-
type:
|
|
2434
|
+
type: "json_schema",
|
|
2402
2435
|
json_schema: {
|
|
2403
|
-
type:
|
|
2436
|
+
type: "object",
|
|
2404
2437
|
properties: responseFormat.schema.properties,
|
|
2405
2438
|
required: responseFormat.schema.required || [],
|
|
2406
|
-
name:
|
|
2439
|
+
name: "Response",
|
|
2407
2440
|
},
|
|
2408
2441
|
};
|
|
2409
2442
|
}
|
|
@@ -2462,7 +2495,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2462
2495
|
const normalizedModel = normalizeModelName(options.model || DEFAULT_MODEL);
|
|
2463
2496
|
const apiKey = options.apiKey || getSecrets().openai.apiKey;
|
|
2464
2497
|
if (!apiKey) {
|
|
2465
|
-
throw new Error(
|
|
2498
|
+
throw new Error("OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set");
|
|
2466
2499
|
}
|
|
2467
2500
|
const openai = new OpenAI({
|
|
2468
2501
|
apiKey: apiKey,
|
|
@@ -2472,7 +2505,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2472
2505
|
// Add developer prompt if present
|
|
2473
2506
|
if (options.developerPrompt && supportsDeveloperPrompt(normalizedModel)) {
|
|
2474
2507
|
messages.push({
|
|
2475
|
-
role:
|
|
2508
|
+
role: "developer",
|
|
2476
2509
|
content: options.developerPrompt,
|
|
2477
2510
|
});
|
|
2478
2511
|
}
|
|
@@ -2481,15 +2514,15 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2481
2514
|
messages.push(...options.context);
|
|
2482
2515
|
}
|
|
2483
2516
|
// Add user content
|
|
2484
|
-
if (typeof content ===
|
|
2517
|
+
if (typeof content === "string") {
|
|
2485
2518
|
messages.push({
|
|
2486
|
-
role:
|
|
2519
|
+
role: "user",
|
|
2487
2520
|
content,
|
|
2488
2521
|
});
|
|
2489
2522
|
}
|
|
2490
2523
|
else {
|
|
2491
2524
|
messages.push({
|
|
2492
|
-
role:
|
|
2525
|
+
role: "user",
|
|
2493
2526
|
content,
|
|
2494
2527
|
});
|
|
2495
2528
|
}
|
|
@@ -2503,7 +2536,8 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2503
2536
|
queryOptions.tools = options.tools;
|
|
2504
2537
|
}
|
|
2505
2538
|
// Only include temperature if the model supports it
|
|
2506
|
-
if (options.temperature !== undefined &&
|
|
2539
|
+
if (options.temperature !== undefined &&
|
|
2540
|
+
supportsTemperature(normalizedModel)) {
|
|
2507
2541
|
queryOptions.temperature = options.temperature;
|
|
2508
2542
|
}
|
|
2509
2543
|
// Only include max_completion_tokens when specified
|
|
@@ -2513,7 +2547,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2513
2547
|
// Only set response_format when it's not the default 'text' type
|
|
2514
2548
|
// (sending response_format: {type: 'text'} is redundant and may cause issues)
|
|
2515
2549
|
const responseFormatOption = getResponseFormatOption(responseFormat, normalizedModel);
|
|
2516
|
-
if (responseFormatOption.type !==
|
|
2550
|
+
if (responseFormatOption.type !== "text") {
|
|
2517
2551
|
queryOptions.response_format = responseFormatOption;
|
|
2518
2552
|
}
|
|
2519
2553
|
let completion;
|
|
@@ -2534,15 +2568,19 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2534
2568
|
// for production prompts containing sensitive context.
|
|
2535
2569
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2536
2570
|
const totalContentChars = messages.reduce((sum, msg) => {
|
|
2537
|
-
if (typeof msg.content ===
|
|
2571
|
+
if (typeof msg.content === "string")
|
|
2538
2572
|
return sum + msg.content.length;
|
|
2539
2573
|
if (Array.isArray(msg.content)) {
|
|
2540
|
-
return sum +
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
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));
|
|
2546
2584
|
}
|
|
2547
2585
|
return sum;
|
|
2548
2586
|
}, 0);
|
|
@@ -2569,7 +2607,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2569
2607
|
const cachedTokens = completion.usage?.prompt_tokens_details?.cached_tokens ?? 0;
|
|
2570
2608
|
const response = {
|
|
2571
2609
|
id: completion.id,
|
|
2572
|
-
content: completion.choices[0]?.message?.content ||
|
|
2610
|
+
content: completion.choices[0]?.message?.content || "",
|
|
2573
2611
|
tool_calls: completion.choices[0]?.message?.tool_calls,
|
|
2574
2612
|
usage: {
|
|
2575
2613
|
prompt_tokens: completion.usage?.prompt_tokens ?? 0,
|
|
@@ -2579,7 +2617,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2579
2617
|
},
|
|
2580
2618
|
system_fingerprint: completion.system_fingerprint,
|
|
2581
2619
|
service_tier: options.service_tier,
|
|
2582
|
-
provider:
|
|
2620
|
+
provider: "openai",
|
|
2583
2621
|
model: normalizedModel,
|
|
2584
2622
|
};
|
|
2585
2623
|
return response;
|
|
@@ -2592,7 +2630,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
|
|
|
2592
2630
|
* @param options The options for the LLM call. Defaults to an empty object.
|
|
2593
2631
|
* @return A promise that resolves to the response from the LLM.
|
|
2594
2632
|
*/
|
|
2595
|
-
const makeOpenAIChatCompletionCall = async (content, responseFormat =
|
|
2633
|
+
const makeOpenAIChatCompletionCall = async (content, responseFormat = "text", options = {}) => {
|
|
2596
2634
|
const mergedOptions = {
|
|
2597
2635
|
...DEFAULT_OPTIONS$1,
|
|
2598
2636
|
...options,
|
|
@@ -2601,7 +2639,7 @@ const makeOpenAIChatCompletionCall = async (content, responseFormat = 'text', op
|
|
|
2601
2639
|
// Track cost in the global cost tracker. Pass cached tokens through so the
|
|
2602
2640
|
// tracker applies the discounted cached-input rate (typically ~50% of the
|
|
2603
2641
|
// standard input rate) instead of billing every input token at full price.
|
|
2604
|
-
getLLMCostTracker().trackUsage(
|
|
2642
|
+
getLLMCostTracker().trackUsage("openai", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens);
|
|
2605
2643
|
// Handle tool calls differently
|
|
2606
2644
|
if (completion.tool_calls && completion.tool_calls.length > 0) {
|
|
2607
2645
|
const toolCallResponse = {
|
|
@@ -2617,10 +2655,10 @@ const makeOpenAIChatCompletionCall = async (content, responseFormat = 'text', op
|
|
|
2617
2655
|
prompt_tokens: completion.usage.prompt_tokens,
|
|
2618
2656
|
completion_tokens: completion.usage.completion_tokens,
|
|
2619
2657
|
reasoning_tokens: 0,
|
|
2620
|
-
provider:
|
|
2658
|
+
provider: "openai",
|
|
2621
2659
|
model: completion.model,
|
|
2622
2660
|
cached_tokens: completion.usage.cached_tokens,
|
|
2623
|
-
cost: calculateCost(
|
|
2661
|
+
cost: calculateCost("openai", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
|
|
2624
2662
|
},
|
|
2625
2663
|
tool_calls: completion.tool_calls,
|
|
2626
2664
|
};
|
|
@@ -2628,7 +2666,7 @@ const makeOpenAIChatCompletionCall = async (content, responseFormat = 'text', op
|
|
|
2628
2666
|
// Handle regular responses
|
|
2629
2667
|
const parsedResponse = await parseResponse(completion.content, responseFormat);
|
|
2630
2668
|
if (parsedResponse === null) {
|
|
2631
|
-
throw new Error(
|
|
2669
|
+
throw new Error("Failed to parse response");
|
|
2632
2670
|
}
|
|
2633
2671
|
return {
|
|
2634
2672
|
response: parsedResponse,
|
|
@@ -2636,10 +2674,10 @@ const makeOpenAIChatCompletionCall = async (content, responseFormat = 'text', op
|
|
|
2636
2674
|
prompt_tokens: completion.usage.prompt_tokens,
|
|
2637
2675
|
completion_tokens: completion.usage.completion_tokens,
|
|
2638
2676
|
reasoning_tokens: 0,
|
|
2639
|
-
provider:
|
|
2677
|
+
provider: "openai",
|
|
2640
2678
|
model: completion.model,
|
|
2641
2679
|
cached_tokens: completion.usage.cached_tokens,
|
|
2642
|
-
cost: calculateCost(
|
|
2680
|
+
cost: calculateCost("openai", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
|
|
2643
2681
|
},
|
|
2644
2682
|
tool_calls: completion.tool_calls,
|
|
2645
2683
|
};
|
|
@@ -2674,7 +2712,7 @@ const makeResponsesAPICall = async (input, options = {}) => {
|
|
|
2674
2712
|
const normalizedModel = normalizeModelName(options.model || DEFAULT_MODEL);
|
|
2675
2713
|
const apiKey = options.apiKey || getSecrets().openai.apiKey;
|
|
2676
2714
|
if (!apiKey) {
|
|
2677
|
-
throw new Error(
|
|
2715
|
+
throw new Error("OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set");
|
|
2678
2716
|
}
|
|
2679
2717
|
const openai = new OpenAI({
|
|
2680
2718
|
apiKey: apiKey,
|
|
@@ -2698,20 +2736,20 @@ const makeResponsesAPICall = async (input, options = {}) => {
|
|
|
2698
2736
|
// (the equivalent of Chat Completions' `prompt_tokens_details.cached_tokens`).
|
|
2699
2737
|
const responsesCachedTokens = response.usage?.input_tokens_details?.cached_tokens || 0;
|
|
2700
2738
|
// Track cost in the global cost tracker
|
|
2701
|
-
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);
|
|
2702
2740
|
// Extract tool calls from the output
|
|
2703
2741
|
const toolCalls = response.output
|
|
2704
|
-
?.filter((item) => item.type ===
|
|
2742
|
+
?.filter((item) => item.type === "function_call")
|
|
2705
2743
|
.map((toolCall) => ({
|
|
2706
2744
|
id: toolCall.call_id,
|
|
2707
|
-
type:
|
|
2745
|
+
type: "function",
|
|
2708
2746
|
function: {
|
|
2709
2747
|
name: toolCall.name,
|
|
2710
2748
|
arguments: toolCall.arguments,
|
|
2711
2749
|
},
|
|
2712
2750
|
}));
|
|
2713
2751
|
// Extract code interpreter outputs if present
|
|
2714
|
-
const codeInterpreterCalls = response.output?.filter((item) => item.type ===
|
|
2752
|
+
const codeInterpreterCalls = response.output?.filter((item) => item.type === "code_interpreter_call");
|
|
2715
2753
|
let codeInterpreterOutputs = undefined;
|
|
2716
2754
|
if (codeInterpreterCalls && codeInterpreterCalls.length > 0) {
|
|
2717
2755
|
// Each code_interpreter_call has an 'outputs' array property
|
|
@@ -2737,32 +2775,34 @@ const makeResponsesAPICall = async (input, options = {}) => {
|
|
|
2737
2775
|
prompt_tokens: response.usage?.input_tokens || 0,
|
|
2738
2776
|
completion_tokens: response.usage?.output_tokens || 0,
|
|
2739
2777
|
reasoning_tokens: response.usage?.output_tokens_details?.reasoning_tokens || 0,
|
|
2740
|
-
provider:
|
|
2778
|
+
provider: "openai",
|
|
2741
2779
|
model: normalizedModel,
|
|
2742
2780
|
cached_tokens: responsesCachedTokens,
|
|
2743
|
-
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),
|
|
2744
2782
|
},
|
|
2745
2783
|
tool_calls: toolCalls,
|
|
2746
|
-
...(codeInterpreterOutputs
|
|
2784
|
+
...(codeInterpreterOutputs
|
|
2785
|
+
? { code_interpreter_outputs: codeInterpreterOutputs }
|
|
2786
|
+
: {}),
|
|
2747
2787
|
};
|
|
2748
2788
|
}
|
|
2749
2789
|
// Extract text content from the response output
|
|
2750
2790
|
const textContent = response.output
|
|
2751
|
-
?.filter((item) => item.type ===
|
|
2791
|
+
?.filter((item) => item.type === "message")
|
|
2752
2792
|
.map((item) => item.content
|
|
2753
|
-
.filter((content) => content.type ===
|
|
2793
|
+
.filter((content) => content.type === "output_text")
|
|
2754
2794
|
.map((content) => content.text)
|
|
2755
|
-
.join(
|
|
2756
|
-
.join(
|
|
2795
|
+
.join(""))
|
|
2796
|
+
.join("") || "";
|
|
2757
2797
|
// Determine the format for parsing the response
|
|
2758
|
-
let parsingFormat =
|
|
2759
|
-
if (requestBody.text?.format?.type ===
|
|
2760
|
-
parsingFormat =
|
|
2798
|
+
let parsingFormat = "text";
|
|
2799
|
+
if (requestBody.text?.format?.type === "json_object") {
|
|
2800
|
+
parsingFormat = "json";
|
|
2761
2801
|
}
|
|
2762
2802
|
// Handle regular responses
|
|
2763
2803
|
const parsedResponse = await parseResponse(textContent, parsingFormat);
|
|
2764
2804
|
if (parsedResponse === null) {
|
|
2765
|
-
throw new Error(
|
|
2805
|
+
throw new Error("Failed to parse response from Responses API");
|
|
2766
2806
|
}
|
|
2767
2807
|
return {
|
|
2768
2808
|
response: parsedResponse,
|
|
@@ -2770,13 +2810,15 @@ const makeResponsesAPICall = async (input, options = {}) => {
|
|
|
2770
2810
|
prompt_tokens: response.usage?.input_tokens || 0,
|
|
2771
2811
|
completion_tokens: response.usage?.output_tokens || 0,
|
|
2772
2812
|
reasoning_tokens: response.usage?.output_tokens_details?.reasoning_tokens || 0,
|
|
2773
|
-
provider:
|
|
2813
|
+
provider: "openai",
|
|
2774
2814
|
model: normalizedModel,
|
|
2775
2815
|
cached_tokens: responsesCachedTokens,
|
|
2776
|
-
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),
|
|
2777
2817
|
},
|
|
2778
2818
|
tool_calls: toolCalls,
|
|
2779
|
-
...(codeInterpreterOutputs
|
|
2819
|
+
...(codeInterpreterOutputs
|
|
2820
|
+
? { code_interpreter_outputs: codeInterpreterOutputs }
|
|
2821
|
+
: {}),
|
|
2780
2822
|
};
|
|
2781
2823
|
};
|
|
2782
2824
|
|
|
@@ -8086,12 +8128,12 @@ function sanitizeObject(obj, sensitiveFields = ['apiKey', 'token', 'secret', 'pa
|
|
|
8086
8128
|
const isRetryableAnthropicError = (error) => {
|
|
8087
8129
|
if (error instanceof Error) {
|
|
8088
8130
|
const message = error.message;
|
|
8089
|
-
if (message.includes(
|
|
8090
|
-
message.includes(
|
|
8091
|
-
message.includes(
|
|
8092
|
-
message.includes(
|
|
8093
|
-
message.includes(
|
|
8094
|
-
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")) {
|
|
8095
8137
|
return true;
|
|
8096
8138
|
}
|
|
8097
8139
|
}
|
|
@@ -8106,8 +8148,11 @@ const isRetryableAnthropicError = (error) => {
|
|
|
8106
8148
|
function translateToolsToAnthropic(tools) {
|
|
8107
8149
|
return tools.map((tool) => ({
|
|
8108
8150
|
name: tool.function.name,
|
|
8109
|
-
description: tool.function.description ||
|
|
8110
|
-
input_schema: (tool.function.parameters || {
|
|
8151
|
+
description: tool.function.description || "",
|
|
8152
|
+
input_schema: (tool.function.parameters || {
|
|
8153
|
+
type: "object",
|
|
8154
|
+
properties: {},
|
|
8155
|
+
}),
|
|
8111
8156
|
}));
|
|
8112
8157
|
}
|
|
8113
8158
|
/**
|
|
@@ -8120,27 +8165,31 @@ function translateContextToAnthropic(context) {
|
|
|
8120
8165
|
const systemParts = [];
|
|
8121
8166
|
const messages = [];
|
|
8122
8167
|
for (const msg of context) {
|
|
8123
|
-
if (msg.role ===
|
|
8124
|
-
if (typeof msg.content ===
|
|
8168
|
+
if (msg.role === "system" || msg.role === "developer") {
|
|
8169
|
+
if (typeof msg.content === "string") {
|
|
8125
8170
|
systemParts.push(msg.content);
|
|
8126
8171
|
}
|
|
8127
8172
|
continue;
|
|
8128
8173
|
}
|
|
8129
|
-
if (msg.role ===
|
|
8130
|
-
const content = typeof msg.content ===
|
|
8174
|
+
if (msg.role === "user") {
|
|
8175
|
+
const content = typeof msg.content === "string"
|
|
8131
8176
|
? msg.content
|
|
8132
8177
|
: JSON.stringify(msg.content);
|
|
8133
|
-
messages.push({ role:
|
|
8178
|
+
messages.push({ role: "user", content });
|
|
8134
8179
|
continue;
|
|
8135
8180
|
}
|
|
8136
|
-
if (msg.role ===
|
|
8181
|
+
if (msg.role === "assistant") {
|
|
8137
8182
|
const assistantMsg = msg;
|
|
8138
8183
|
// If the assistant message has tool_calls, translate to Anthropic tool_use blocks
|
|
8139
8184
|
if (assistantMsg.tool_calls && assistantMsg.tool_calls.length > 0) {
|
|
8140
8185
|
const contentBlocks = [];
|
|
8141
8186
|
// Include text content if present
|
|
8142
|
-
if (typeof assistantMsg.content ===
|
|
8143
|
-
|
|
8187
|
+
if (typeof assistantMsg.content === "string" &&
|
|
8188
|
+
assistantMsg.content.trim()) {
|
|
8189
|
+
contentBlocks.push({
|
|
8190
|
+
type: "text",
|
|
8191
|
+
text: assistantMsg.content,
|
|
8192
|
+
});
|
|
8144
8193
|
}
|
|
8145
8194
|
// Translate each tool_call to a tool_use block
|
|
8146
8195
|
for (const tc of assistantMsg.tool_calls) {
|
|
@@ -8152,32 +8201,36 @@ function translateContextToAnthropic(context) {
|
|
|
8152
8201
|
input = { raw: tc.function.arguments };
|
|
8153
8202
|
}
|
|
8154
8203
|
contentBlocks.push({
|
|
8155
|
-
type:
|
|
8204
|
+
type: "tool_use",
|
|
8156
8205
|
id: tc.id,
|
|
8157
8206
|
name: tc.function.name,
|
|
8158
8207
|
input,
|
|
8159
8208
|
});
|
|
8160
8209
|
}
|
|
8161
|
-
messages.push({ role:
|
|
8210
|
+
messages.push({ role: "assistant", content: contentBlocks });
|
|
8162
8211
|
}
|
|
8163
8212
|
else {
|
|
8164
|
-
const content = typeof assistantMsg.content ===
|
|
8213
|
+
const content = typeof assistantMsg.content === "string"
|
|
8165
8214
|
? assistantMsg.content
|
|
8166
8215
|
: JSON.stringify(assistantMsg.content);
|
|
8167
|
-
messages.push({ role:
|
|
8216
|
+
messages.push({ role: "assistant", content });
|
|
8168
8217
|
}
|
|
8169
8218
|
continue;
|
|
8170
8219
|
}
|
|
8171
|
-
if (msg.role ===
|
|
8220
|
+
if (msg.role === "tool") {
|
|
8172
8221
|
// Anthropic expects tool results as user messages with tool_result content blocks
|
|
8173
8222
|
const toolMsg = msg;
|
|
8174
8223
|
messages.push({
|
|
8175
|
-
role:
|
|
8176
|
-
content: [
|
|
8177
|
-
|
|
8224
|
+
role: "user",
|
|
8225
|
+
content: [
|
|
8226
|
+
{
|
|
8227
|
+
type: "tool_result",
|
|
8178
8228
|
tool_use_id: toolMsg.tool_call_id,
|
|
8179
|
-
content: typeof toolMsg.content ===
|
|
8180
|
-
|
|
8229
|
+
content: typeof toolMsg.content === "string"
|
|
8230
|
+
? toolMsg.content
|
|
8231
|
+
: JSON.stringify(toolMsg.content),
|
|
8232
|
+
},
|
|
8233
|
+
],
|
|
8181
8234
|
});
|
|
8182
8235
|
continue;
|
|
8183
8236
|
}
|
|
@@ -8198,13 +8251,13 @@ function translateContextToAnthropic(context) {
|
|
|
8198
8251
|
merged.push({ role: msg.role, content: toContentBlocks(msg.content) });
|
|
8199
8252
|
}
|
|
8200
8253
|
}
|
|
8201
|
-
return { messages: merged, systemText: systemParts.join(
|
|
8254
|
+
return { messages: merged, systemText: systemParts.join("\n\n") };
|
|
8202
8255
|
}
|
|
8203
8256
|
/** Convert string or content block array to a uniform content block array. */
|
|
8204
8257
|
function toContentBlocks(content) {
|
|
8205
|
-
if (typeof content ===
|
|
8258
|
+
if (typeof content === "string") {
|
|
8206
8259
|
const textBlock = {
|
|
8207
|
-
type:
|
|
8260
|
+
type: "text",
|
|
8208
8261
|
text: content,
|
|
8209
8262
|
citations: null,
|
|
8210
8263
|
};
|
|
@@ -8224,11 +8277,11 @@ function toContentBlocks(content) {
|
|
|
8224
8277
|
* @param options The options for the LLM call.
|
|
8225
8278
|
* @return A promise that resolves to the response from the Anthropic API.
|
|
8226
8279
|
*/
|
|
8227
|
-
async function makeAnthropicCall(content, responseFormat =
|
|
8228
|
-
const model = (options.model ||
|
|
8280
|
+
async function makeAnthropicCall(content, responseFormat = "text", options = {}) {
|
|
8281
|
+
const model = (options.model || "claude-sonnet-4-6");
|
|
8229
8282
|
const apiKey = options.apiKey || getSecrets().anthropic.apiKey;
|
|
8230
8283
|
if (!apiKey) {
|
|
8231
|
-
throw new Error(
|
|
8284
|
+
throw new Error("Anthropic API key is not provided and ANTHROPIC_API_KEY environment variable is not set");
|
|
8232
8285
|
}
|
|
8233
8286
|
const client = new Anthropic({
|
|
8234
8287
|
apiKey,
|
|
@@ -8240,8 +8293,10 @@ async function makeAnthropicCall(content, responseFormat = 'text', options = {})
|
|
|
8240
8293
|
systemParts.push(options.developerPrompt);
|
|
8241
8294
|
}
|
|
8242
8295
|
// If JSON response is requested, instruct the model via system prompt
|
|
8243
|
-
if (responseFormat ===
|
|
8244
|
-
|
|
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.");
|
|
8245
8300
|
}
|
|
8246
8301
|
// Build messages array
|
|
8247
8302
|
const messages = [];
|
|
@@ -8254,7 +8309,7 @@ async function makeAnthropicCall(content, responseFormat = 'text', options = {})
|
|
|
8254
8309
|
messages.push(...translated.messages);
|
|
8255
8310
|
}
|
|
8256
8311
|
// Add user content
|
|
8257
|
-
messages.push({ role:
|
|
8312
|
+
messages.push({ role: "user", content });
|
|
8258
8313
|
// Build request params
|
|
8259
8314
|
const requestParams = {
|
|
8260
8315
|
model,
|
|
@@ -8263,7 +8318,7 @@ async function makeAnthropicCall(content, responseFormat = 'text', options = {})
|
|
|
8263
8318
|
};
|
|
8264
8319
|
// Add system prompt if present
|
|
8265
8320
|
if (systemParts.length > 0) {
|
|
8266
|
-
requestParams.system = systemParts.join(
|
|
8321
|
+
requestParams.system = systemParts.join("\n\n");
|
|
8267
8322
|
}
|
|
8268
8323
|
// Add temperature if specified
|
|
8269
8324
|
if (options.temperature !== undefined) {
|
|
@@ -8286,14 +8341,14 @@ async function makeAnthropicCall(content, responseFormat = 'text', options = {})
|
|
|
8286
8341
|
const inputTokens = response.usage.input_tokens;
|
|
8287
8342
|
const outputTokens = response.usage.output_tokens;
|
|
8288
8343
|
// Track cost
|
|
8289
|
-
getLLMCostTracker().trackUsage(
|
|
8344
|
+
getLLMCostTracker().trackUsage("anthropic", model, inputTokens, outputTokens);
|
|
8290
8345
|
// Extract tool use blocks
|
|
8291
|
-
const toolUseBlocks = response.content.filter((block) => block.type ===
|
|
8346
|
+
const toolUseBlocks = response.content.filter((block) => block.type === "tool_use");
|
|
8292
8347
|
if (toolUseBlocks.length > 0) {
|
|
8293
8348
|
// Translate Anthropic tool_use to OpenAI tool_calls format
|
|
8294
8349
|
const toolCalls = toolUseBlocks.map((block) => ({
|
|
8295
8350
|
id: block.id,
|
|
8296
|
-
type:
|
|
8351
|
+
type: "function",
|
|
8297
8352
|
function: {
|
|
8298
8353
|
name: block.name,
|
|
8299
8354
|
arguments: JSON.stringify(block.input),
|
|
@@ -8312,22 +8367,22 @@ async function makeAnthropicCall(content, responseFormat = 'text', options = {})
|
|
|
8312
8367
|
prompt_tokens: inputTokens,
|
|
8313
8368
|
completion_tokens: outputTokens,
|
|
8314
8369
|
reasoning_tokens: 0,
|
|
8315
|
-
provider:
|
|
8370
|
+
provider: "anthropic",
|
|
8316
8371
|
model,
|
|
8317
|
-
cost: calculateCost(
|
|
8372
|
+
cost: calculateCost("anthropic", model, inputTokens, outputTokens),
|
|
8318
8373
|
},
|
|
8319
8374
|
tool_calls: toolCalls,
|
|
8320
8375
|
};
|
|
8321
8376
|
}
|
|
8322
8377
|
// Extract text content
|
|
8323
8378
|
const textContent = response.content
|
|
8324
|
-
.filter((block) => block.type ===
|
|
8379
|
+
.filter((block) => block.type === "text")
|
|
8325
8380
|
.map((block) => block.text)
|
|
8326
|
-
.join(
|
|
8381
|
+
.join("");
|
|
8327
8382
|
// Parse response
|
|
8328
8383
|
const parsedResponse = await parseResponse(textContent, responseFormat);
|
|
8329
8384
|
if (parsedResponse === null) {
|
|
8330
|
-
throw new Error(
|
|
8385
|
+
throw new Error("Failed to parse Anthropic response");
|
|
8331
8386
|
}
|
|
8332
8387
|
return {
|
|
8333
8388
|
response: parsedResponse,
|
|
@@ -8335,9 +8390,9 @@ async function makeAnthropicCall(content, responseFormat = 'text', options = {})
|
|
|
8335
8390
|
prompt_tokens: inputTokens,
|
|
8336
8391
|
completion_tokens: outputTokens,
|
|
8337
8392
|
reasoning_tokens: 0,
|
|
8338
|
-
provider:
|
|
8393
|
+
provider: "anthropic",
|
|
8339
8394
|
model,
|
|
8340
|
-
cost: calculateCost(
|
|
8395
|
+
cost: calculateCost("anthropic", model, inputTokens, outputTokens),
|
|
8341
8396
|
},
|
|
8342
8397
|
};
|
|
8343
8398
|
}
|
|
@@ -8359,7 +8414,9 @@ async function makeAnthropicCall(content, responseFormat = 'text', options = {})
|
|
|
8359
8414
|
const isRetryableError = (error) => {
|
|
8360
8415
|
if (error instanceof Error) {
|
|
8361
8416
|
const message = error.message;
|
|
8362
|
-
if (message.includes(
|
|
8417
|
+
if (message.includes("429") ||
|
|
8418
|
+
message.includes("rate limit") ||
|
|
8419
|
+
message.includes("Rate limit")) {
|
|
8363
8420
|
return true;
|
|
8364
8421
|
}
|
|
8365
8422
|
}
|
|
@@ -8373,14 +8430,16 @@ function resolveApiKey(provider, optionsApiKey) {
|
|
|
8373
8430
|
if (optionsApiKey)
|
|
8374
8431
|
return optionsApiKey;
|
|
8375
8432
|
const secrets = getSecrets();
|
|
8376
|
-
const pathParts = provider.apiKeySecretPath.split(
|
|
8433
|
+
const pathParts = provider.apiKeySecretPath.split(".");
|
|
8377
8434
|
let current = secrets;
|
|
8378
8435
|
for (const part of pathParts) {
|
|
8379
|
-
if (current === null ||
|
|
8380
|
-
|
|
8436
|
+
if (current === null ||
|
|
8437
|
+
current === undefined ||
|
|
8438
|
+
typeof current !== "object")
|
|
8439
|
+
return "";
|
|
8381
8440
|
current = current[part];
|
|
8382
8441
|
}
|
|
8383
|
-
return (typeof current ===
|
|
8442
|
+
return (typeof current === "string" ? current : "") || "";
|
|
8384
8443
|
}
|
|
8385
8444
|
/**
|
|
8386
8445
|
* Makes a call to an OpenAI-compatible provider (Kimi, Qwen, or any future provider).
|
|
@@ -8394,12 +8453,12 @@ function resolveApiKey(provider, optionsApiKey) {
|
|
|
8394
8453
|
* @param providerName The provider key in OPENAI_COMPATIBLE_PROVIDERS.
|
|
8395
8454
|
* @return A promise that resolves to the response from the provider.
|
|
8396
8455
|
*/
|
|
8397
|
-
async function makeOpenAICompatibleCall(content, responseFormat =
|
|
8456
|
+
async function makeOpenAICompatibleCall(content, responseFormat = "text", options = {}, providerName) {
|
|
8398
8457
|
const providerConfig = OPENAI_COMPATIBLE_PROVIDERS[providerName];
|
|
8399
8458
|
if (!providerConfig) {
|
|
8400
8459
|
throw new Error(`Unknown OpenAI-compatible provider: ${providerName}`);
|
|
8401
8460
|
}
|
|
8402
|
-
const model = normalizeModelName(options.model ||
|
|
8461
|
+
const model = normalizeModelName(options.model || "");
|
|
8403
8462
|
const apiKey = resolveApiKey(providerConfig, options.apiKey);
|
|
8404
8463
|
if (!apiKey) {
|
|
8405
8464
|
throw new Error(`${providerConfig.name} API key is not provided and ${providerConfig.apiKeyEnvVar} environment variable is not set`);
|
|
@@ -8412,31 +8471,36 @@ async function makeOpenAICompatibleCall(content, responseFormat = 'text', option
|
|
|
8412
8471
|
const messages = [];
|
|
8413
8472
|
// Add system message with developer prompt if present
|
|
8414
8473
|
if (options.developerPrompt) {
|
|
8415
|
-
messages.push({ role:
|
|
8474
|
+
messages.push({ role: "system", content: options.developerPrompt });
|
|
8416
8475
|
}
|
|
8417
8476
|
// Add context if present
|
|
8418
8477
|
if (options.context) {
|
|
8419
8478
|
messages.push(...options.context);
|
|
8420
8479
|
}
|
|
8421
8480
|
// Add user content
|
|
8422
|
-
if (typeof content ===
|
|
8423
|
-
messages.push({ role:
|
|
8481
|
+
if (typeof content === "string") {
|
|
8482
|
+
messages.push({ role: "user", content });
|
|
8424
8483
|
}
|
|
8425
8484
|
else {
|
|
8426
|
-
messages.push({ role:
|
|
8485
|
+
messages.push({ role: "user", content });
|
|
8427
8486
|
}
|
|
8428
8487
|
// Determine if the model supports JSON mode
|
|
8429
8488
|
const supportsJson = isValidModel(model) && getModelCapabilities(model).supportsJson;
|
|
8430
8489
|
// If JSON response format, add hint to system prompt
|
|
8431
|
-
if ((responseFormat ===
|
|
8490
|
+
if ((responseFormat === "json" ||
|
|
8491
|
+
(typeof responseFormat === "object" &&
|
|
8492
|
+
responseFormat.type === "json_schema")) &&
|
|
8432
8493
|
supportsJson) {
|
|
8433
|
-
if (!messages.some((m) => m.role ===
|
|
8434
|
-
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
|
+
});
|
|
8435
8499
|
}
|
|
8436
8500
|
else {
|
|
8437
|
-
const systemMsgIndex = messages.findIndex((m) => m.role ===
|
|
8501
|
+
const systemMsgIndex = messages.findIndex((m) => m.role === "system");
|
|
8438
8502
|
const systemMsg = messages[systemMsgIndex];
|
|
8439
|
-
if (typeof systemMsg.content ===
|
|
8503
|
+
if (typeof systemMsg.content === "string") {
|
|
8440
8504
|
messages[systemMsgIndex] = {
|
|
8441
8505
|
...systemMsg,
|
|
8442
8506
|
content: `${systemMsg.content} Please respond in valid JSON format.`,
|
|
@@ -8449,8 +8513,8 @@ async function makeOpenAICompatibleCall(content, responseFormat = 'text', option
|
|
|
8449
8513
|
messages,
|
|
8450
8514
|
};
|
|
8451
8515
|
// Add response format if JSON is supported
|
|
8452
|
-
if (responseFormat !==
|
|
8453
|
-
queryOptions.response_format = { type:
|
|
8516
|
+
if (responseFormat !== "text" && supportsJson) {
|
|
8517
|
+
queryOptions.response_format = { type: "json_object" };
|
|
8454
8518
|
}
|
|
8455
8519
|
// Temperature
|
|
8456
8520
|
if (options.temperature !== undefined) {
|
|
@@ -8508,7 +8572,7 @@ async function makeOpenAICompatibleCall(content, responseFormat = 'text', option
|
|
|
8508
8572
|
};
|
|
8509
8573
|
}
|
|
8510
8574
|
// Handle regular responses
|
|
8511
|
-
const textContent = completion.choices[0]?.message?.content ||
|
|
8575
|
+
const textContent = completion.choices[0]?.message?.content || "";
|
|
8512
8576
|
const parsedResponse = await parseResponse(textContent, responseFormat);
|
|
8513
8577
|
if (parsedResponse === null) {
|
|
8514
8578
|
throw new Error(`Failed to parse ${providerConfig.name} response`);
|
|
@@ -8800,7 +8864,9 @@ const isRetryableDeepseekError = (error) => {
|
|
|
8800
8864
|
if (error instanceof Error) {
|
|
8801
8865
|
const message = error.message;
|
|
8802
8866
|
// Retry only on rate limits (429)
|
|
8803
|
-
if (message.includes(
|
|
8867
|
+
if (message.includes("429") ||
|
|
8868
|
+
message.includes("rate limit") ||
|
|
8869
|
+
message.includes("Rate limit")) {
|
|
8804
8870
|
return true;
|
|
8805
8871
|
}
|
|
8806
8872
|
}
|
|
@@ -8810,7 +8876,7 @@ const isRetryableDeepseekError = (error) => {
|
|
|
8810
8876
|
* Default options for Deepseek API calls
|
|
8811
8877
|
*/
|
|
8812
8878
|
const DEFAULT_DEEPSEEK_OPTIONS = {
|
|
8813
|
-
defaultModel:
|
|
8879
|
+
defaultModel: "deepseek-chat",
|
|
8814
8880
|
};
|
|
8815
8881
|
/**
|
|
8816
8882
|
* Checks if the given model is a supported Deepseek model
|
|
@@ -8822,7 +8888,7 @@ const isSupportedDeepseekModel = (model) => {
|
|
|
8822
8888
|
return false;
|
|
8823
8889
|
}
|
|
8824
8890
|
const capabilities = getModelCapabilities(model);
|
|
8825
|
-
return capabilities.provider ===
|
|
8891
|
+
return capabilities.provider === "deepseek";
|
|
8826
8892
|
};
|
|
8827
8893
|
/**
|
|
8828
8894
|
* Checks if the given Deepseek model supports JSON output format
|
|
@@ -8855,17 +8921,19 @@ const supportsToolCalling = (model) => {
|
|
|
8855
8921
|
*/
|
|
8856
8922
|
function getDeepseekResponseFormatOption(responseFormat, model) {
|
|
8857
8923
|
// Check if the model supports JSON output
|
|
8858
|
-
if (responseFormat !==
|
|
8924
|
+
if (responseFormat !== "text" && !supportsJsonOutput(model)) {
|
|
8859
8925
|
getLumicLogger().warn(`Model ${model} does not support JSON output. Using text format instead.`);
|
|
8860
|
-
return { type:
|
|
8926
|
+
return { type: "text" };
|
|
8861
8927
|
}
|
|
8862
|
-
if (responseFormat ===
|
|
8863
|
-
return { type:
|
|
8928
|
+
if (responseFormat === "text") {
|
|
8929
|
+
return { type: "text" };
|
|
8864
8930
|
}
|
|
8865
|
-
if (responseFormat ===
|
|
8866
|
-
|
|
8931
|
+
if (responseFormat === "json" ||
|
|
8932
|
+
(typeof responseFormat === "object" &&
|
|
8933
|
+
responseFormat.type === "json_schema")) {
|
|
8934
|
+
return { type: "json_object" };
|
|
8867
8935
|
}
|
|
8868
|
-
return { type:
|
|
8936
|
+
return { type: "text" };
|
|
8869
8937
|
}
|
|
8870
8938
|
/**
|
|
8871
8939
|
* Creates a completion using the Deepseek API
|
|
@@ -8884,24 +8952,26 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
|
|
|
8884
8952
|
throw new Error(`Unsupported Deepseek model: ${normalizedModel}. Please use 'deepseek-chat' or 'deepseek-reasoner'.`);
|
|
8885
8953
|
}
|
|
8886
8954
|
// Check if tools are requested with a model that doesn't support them
|
|
8887
|
-
if (options.tools &&
|
|
8955
|
+
if (options.tools &&
|
|
8956
|
+
options.tools.length > 0 &&
|
|
8957
|
+
!supportsToolCalling(normalizedModel)) {
|
|
8888
8958
|
throw new Error(`Model ${normalizedModel} does not support tool calling.`);
|
|
8889
8959
|
}
|
|
8890
8960
|
const apiKey = options.apiKey || getSecrets().deepseek.apiKey;
|
|
8891
8961
|
if (!apiKey) {
|
|
8892
|
-
throw new Error(
|
|
8962
|
+
throw new Error("Deepseek API key is not provided and DEEPSEEK_API_KEY environment variable is not set");
|
|
8893
8963
|
}
|
|
8894
8964
|
// Initialize OpenAI client with Deepseek API URL
|
|
8895
8965
|
const openai = new OpenAI({
|
|
8896
8966
|
apiKey,
|
|
8897
|
-
baseURL:
|
|
8967
|
+
baseURL: "https://api.deepseek.com",
|
|
8898
8968
|
timeout: options.timeout ?? LLM_TIMEOUT_MS,
|
|
8899
8969
|
});
|
|
8900
8970
|
const messages = [];
|
|
8901
8971
|
// Add system message with developer prompt if present
|
|
8902
8972
|
if (options.developerPrompt) {
|
|
8903
8973
|
messages.push({
|
|
8904
|
-
role:
|
|
8974
|
+
role: "system",
|
|
8905
8975
|
content: options.developerPrompt,
|
|
8906
8976
|
});
|
|
8907
8977
|
}
|
|
@@ -8910,33 +8980,35 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
|
|
|
8910
8980
|
messages.push(...options.context);
|
|
8911
8981
|
}
|
|
8912
8982
|
// Add user content
|
|
8913
|
-
if (typeof content ===
|
|
8983
|
+
if (typeof content === "string") {
|
|
8914
8984
|
messages.push({
|
|
8915
|
-
role:
|
|
8985
|
+
role: "user",
|
|
8916
8986
|
content,
|
|
8917
8987
|
});
|
|
8918
8988
|
}
|
|
8919
8989
|
else {
|
|
8920
8990
|
messages.push({
|
|
8921
|
-
role:
|
|
8991
|
+
role: "user",
|
|
8922
8992
|
content,
|
|
8923
8993
|
});
|
|
8924
8994
|
}
|
|
8925
8995
|
// If JSON response format, include a hint in the system prompt
|
|
8926
|
-
if ((responseFormat ===
|
|
8927
|
-
|
|
8996
|
+
if ((responseFormat === "json" ||
|
|
8997
|
+
(typeof responseFormat === "object" &&
|
|
8998
|
+
responseFormat.type === "json_schema")) &&
|
|
8999
|
+
supportsJsonOutput(normalizedModel)) {
|
|
8928
9000
|
// If there's no system message yet, add one
|
|
8929
|
-
if (!messages.some(m => m.role ===
|
|
9001
|
+
if (!messages.some((m) => m.role === "system")) {
|
|
8930
9002
|
messages.unshift({
|
|
8931
|
-
role:
|
|
8932
|
-
content:
|
|
9003
|
+
role: "system",
|
|
9004
|
+
content: "Please respond in valid JSON format.",
|
|
8933
9005
|
});
|
|
8934
9006
|
}
|
|
8935
9007
|
else {
|
|
8936
9008
|
// Append to existing system message
|
|
8937
|
-
const systemMsgIndex = messages.findIndex(m => m.role ===
|
|
9009
|
+
const systemMsgIndex = messages.findIndex((m) => m.role === "system");
|
|
8938
9010
|
const systemMsg = messages[systemMsgIndex];
|
|
8939
|
-
if (typeof systemMsg.content ===
|
|
9011
|
+
if (typeof systemMsg.content === "string") {
|
|
8940
9012
|
messages[systemMsgIndex] = {
|
|
8941
9013
|
...systemMsg,
|
|
8942
9014
|
content: `${systemMsg.content} Please respond in valid JSON format.`,
|
|
@@ -8977,7 +9049,7 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
|
|
|
8977
9049
|
0;
|
|
8978
9050
|
return {
|
|
8979
9051
|
id: completion.id,
|
|
8980
|
-
content: completion.choices[0]?.message?.content ||
|
|
9052
|
+
content: completion.choices[0]?.message?.content || "",
|
|
8981
9053
|
tool_calls: completion.choices[0]?.message?.tool_calls,
|
|
8982
9054
|
usage: {
|
|
8983
9055
|
prompt_tokens: completion.usage?.prompt_tokens ?? 0,
|
|
@@ -8986,7 +9058,7 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
|
|
|
8986
9058
|
cached_tokens: cachedTokens,
|
|
8987
9059
|
},
|
|
8988
9060
|
system_fingerprint: completion.system_fingerprint,
|
|
8989
|
-
provider:
|
|
9061
|
+
provider: "deepseek",
|
|
8990
9062
|
model: normalizedModel,
|
|
8991
9063
|
};
|
|
8992
9064
|
}
|
|
@@ -9003,7 +9075,7 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
|
|
|
9003
9075
|
* @param options Configuration options including model ('deepseek-chat' or 'deepseek-reasoner'), tools, and apiKey.
|
|
9004
9076
|
* @return A promise that resolves to the response from the Deepseek API.
|
|
9005
9077
|
*/
|
|
9006
|
-
const makeDeepseekCall = async (content, responseFormat =
|
|
9078
|
+
const makeDeepseekCall = async (content, responseFormat = "json", options = {}) => {
|
|
9007
9079
|
// Set default model if not provided
|
|
9008
9080
|
const mergedOptions = {
|
|
9009
9081
|
...options,
|
|
@@ -9013,17 +9085,17 @@ const makeDeepseekCall = async (content, responseFormat = 'json', options = {})
|
|
|
9013
9085
|
}
|
|
9014
9086
|
const modelName = normalizeModelName(mergedOptions.model);
|
|
9015
9087
|
// Check if the requested response format is compatible with the model
|
|
9016
|
-
if (responseFormat !==
|
|
9088
|
+
if (responseFormat !== "text" && !supportsJsonOutput(modelName)) {
|
|
9017
9089
|
getLumicLogger().warn(`Model ${modelName} does not support JSON output. Will return error in the response.`);
|
|
9018
9090
|
return {
|
|
9019
9091
|
response: {
|
|
9020
|
-
error: `Model ${modelName} does not support JSON output format
|
|
9092
|
+
error: `Model ${modelName} does not support JSON output format.`,
|
|
9021
9093
|
},
|
|
9022
9094
|
usage: {
|
|
9023
9095
|
prompt_tokens: 0,
|
|
9024
9096
|
completion_tokens: 0,
|
|
9025
9097
|
reasoning_tokens: 0,
|
|
9026
|
-
provider:
|
|
9098
|
+
provider: "deepseek",
|
|
9027
9099
|
model: modelName,
|
|
9028
9100
|
cached_tokens: 0,
|
|
9029
9101
|
cost: 0,
|
|
@@ -9032,17 +9104,19 @@ const makeDeepseekCall = async (content, responseFormat = 'json', options = {})
|
|
|
9032
9104
|
};
|
|
9033
9105
|
}
|
|
9034
9106
|
// Check if tools are requested with a model that doesn't support them
|
|
9035
|
-
if (mergedOptions.tools &&
|
|
9107
|
+
if (mergedOptions.tools &&
|
|
9108
|
+
mergedOptions.tools.length > 0 &&
|
|
9109
|
+
!supportsToolCalling(modelName)) {
|
|
9036
9110
|
getLumicLogger().warn(`Model ${modelName} does not support tool calling. Will return error in the response.`);
|
|
9037
9111
|
return {
|
|
9038
9112
|
response: {
|
|
9039
|
-
error: `Model ${modelName} does not support tool calling
|
|
9113
|
+
error: `Model ${modelName} does not support tool calling.`,
|
|
9040
9114
|
},
|
|
9041
9115
|
usage: {
|
|
9042
9116
|
prompt_tokens: 0,
|
|
9043
9117
|
completion_tokens: 0,
|
|
9044
9118
|
reasoning_tokens: 0,
|
|
9045
|
-
provider:
|
|
9119
|
+
provider: "deepseek",
|
|
9046
9120
|
model: modelName,
|
|
9047
9121
|
cached_tokens: 0,
|
|
9048
9122
|
cost: 0,
|
|
@@ -9054,7 +9128,7 @@ const makeDeepseekCall = async (content, responseFormat = 'json', options = {})
|
|
|
9054
9128
|
const completion = await createDeepseekCompletion(content, responseFormat, mergedOptions);
|
|
9055
9129
|
// Track cost in the global cost tracker. Pass cached tokens through so the
|
|
9056
9130
|
// discounted cached-input pricing tier is applied.
|
|
9057
|
-
getLLMCostTracker().trackUsage(
|
|
9131
|
+
getLLMCostTracker().trackUsage("deepseek", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens);
|
|
9058
9132
|
// Handle tool calls similarly to OpenAI
|
|
9059
9133
|
if (completion.tool_calls && completion.tool_calls.length > 0) {
|
|
9060
9134
|
const toolCallResponse = {
|
|
@@ -9070,10 +9144,10 @@ const makeDeepseekCall = async (content, responseFormat = 'json', options = {})
|
|
|
9070
9144
|
prompt_tokens: completion.usage.prompt_tokens,
|
|
9071
9145
|
completion_tokens: completion.usage.completion_tokens,
|
|
9072
9146
|
reasoning_tokens: 0, // Deepseek doesn't provide reasoning tokens separately
|
|
9073
|
-
provider:
|
|
9147
|
+
provider: "deepseek",
|
|
9074
9148
|
model: completion.model,
|
|
9075
9149
|
cached_tokens: completion.usage.cached_tokens,
|
|
9076
|
-
cost: calculateCost(
|
|
9150
|
+
cost: calculateCost("deepseek", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
|
|
9077
9151
|
},
|
|
9078
9152
|
tool_calls: completion.tool_calls,
|
|
9079
9153
|
};
|
|
@@ -9081,7 +9155,7 @@ const makeDeepseekCall = async (content, responseFormat = 'json', options = {})
|
|
|
9081
9155
|
// Handle regular responses
|
|
9082
9156
|
const parsedResponse = await parseResponse(completion.content, responseFormat);
|
|
9083
9157
|
if (parsedResponse === null) {
|
|
9084
|
-
throw new Error(
|
|
9158
|
+
throw new Error("Failed to parse Deepseek response");
|
|
9085
9159
|
}
|
|
9086
9160
|
return {
|
|
9087
9161
|
response: parsedResponse,
|
|
@@ -9089,10 +9163,10 @@ const makeDeepseekCall = async (content, responseFormat = 'json', options = {})
|
|
|
9089
9163
|
prompt_tokens: completion.usage.prompt_tokens,
|
|
9090
9164
|
completion_tokens: completion.usage.completion_tokens,
|
|
9091
9165
|
reasoning_tokens: 0, // Deepseek doesn't provide reasoning tokens separately
|
|
9092
|
-
provider:
|
|
9166
|
+
provider: "deepseek",
|
|
9093
9167
|
model: completion.model,
|
|
9094
9168
|
cached_tokens: completion.usage.cached_tokens,
|
|
9095
|
-
cost: calculateCost(
|
|
9169
|
+
cost: calculateCost("deepseek", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
|
|
9096
9170
|
},
|
|
9097
9171
|
tool_calls: completion.tool_calls,
|
|
9098
9172
|
};
|
|
@@ -9102,13 +9176,13 @@ const makeDeepseekCall = async (content, responseFormat = 'json', options = {})
|
|
|
9102
9176
|
getLumicLogger().error(`Error in Deepseek API call: ${sanitizeError(error)}`);
|
|
9103
9177
|
return {
|
|
9104
9178
|
response: {
|
|
9105
|
-
error: sanitizeError(error)
|
|
9179
|
+
error: sanitizeError(error),
|
|
9106
9180
|
},
|
|
9107
9181
|
usage: {
|
|
9108
9182
|
prompt_tokens: 0,
|
|
9109
9183
|
completion_tokens: 0,
|
|
9110
9184
|
reasoning_tokens: 0,
|
|
9111
|
-
provider:
|
|
9185
|
+
provider: "deepseek",
|
|
9112
9186
|
model: modelName,
|
|
9113
9187
|
cached_tokens: 0,
|
|
9114
9188
|
cost: 0,
|
|
@@ -9217,15 +9291,15 @@ const generateCacheKey = (auth, envVarsCheck = null) => {
|
|
|
9217
9291
|
if (auth) {
|
|
9218
9292
|
return `${auth.AWS_ACCESS_KEY_ID}:${auth.AWS_SECRET_ACCESS_KEY}:${auth.AWS_REGION}`;
|
|
9219
9293
|
}
|
|
9220
|
-
if (envVarsCheck?.source ===
|
|
9294
|
+
if (envVarsCheck?.source === "lambda") {
|
|
9221
9295
|
// In Lambda, use function name as cache key since it uses IAM role
|
|
9222
9296
|
const secrets = getSecrets();
|
|
9223
|
-
return `lambda:${secrets.aws.lambdaFunctionName ||
|
|
9297
|
+
return `lambda:${secrets.aws.lambdaFunctionName || "default"}`;
|
|
9224
9298
|
}
|
|
9225
9299
|
if (envVarsCheck?.vars) {
|
|
9226
9300
|
return `${envVarsCheck.vars.accessKeyId}:${envVarsCheck.vars.secretAccessKey}:${envVarsCheck.vars.region}`;
|
|
9227
9301
|
}
|
|
9228
|
-
return
|
|
9302
|
+
return "default";
|
|
9229
9303
|
};
|
|
9230
9304
|
const validateEnvironmentVars = () => {
|
|
9231
9305
|
const secrets = getSecrets();
|
|
@@ -9238,37 +9312,37 @@ const validateEnvironmentVars = () => {
|
|
|
9238
9312
|
// We're in a Lambda environment
|
|
9239
9313
|
return {
|
|
9240
9314
|
isValid: true,
|
|
9241
|
-
source:
|
|
9242
|
-
vars: { accessKeyId:
|
|
9315
|
+
source: "lambda",
|
|
9316
|
+
vars: { accessKeyId: "", secretAccessKey: "", region: "" }, // No need for explicit credentials in Lambda
|
|
9243
9317
|
};
|
|
9244
9318
|
}
|
|
9245
9319
|
else if (areVarsComplete(secrets.aws.myAccessKeyId, secrets.aws.mySecretAccessKey, secrets.aws.myRegion)) {
|
|
9246
9320
|
return {
|
|
9247
9321
|
isValid: true,
|
|
9248
|
-
source:
|
|
9322
|
+
source: "my_prefix",
|
|
9249
9323
|
vars: {
|
|
9250
|
-
accessKeyId: secrets.aws.myAccessKeyId,
|
|
9251
|
-
secretAccessKey: secrets.aws.mySecretAccessKey,
|
|
9252
|
-
region: secrets.aws.myRegion
|
|
9253
|
-
}
|
|
9324
|
+
accessKeyId: secrets.aws.myAccessKeyId ?? "",
|
|
9325
|
+
secretAccessKey: secrets.aws.mySecretAccessKey ?? "",
|
|
9326
|
+
region: secrets.aws.myRegion ?? "",
|
|
9327
|
+
},
|
|
9254
9328
|
};
|
|
9255
9329
|
}
|
|
9256
9330
|
else if (areVarsComplete(secrets.aws.accessKeyId, secrets.aws.secretAccessKey, secrets.aws.region)) {
|
|
9257
9331
|
return {
|
|
9258
9332
|
isValid: true,
|
|
9259
|
-
source:
|
|
9333
|
+
source: "standard",
|
|
9260
9334
|
vars: {
|
|
9261
|
-
accessKeyId: secrets.aws.accessKeyId,
|
|
9262
|
-
secretAccessKey: secrets.aws.secretAccessKey,
|
|
9263
|
-
region: secrets.aws.region
|
|
9264
|
-
}
|
|
9335
|
+
accessKeyId: secrets.aws.accessKeyId ?? "",
|
|
9336
|
+
secretAccessKey: secrets.aws.secretAccessKey ?? "",
|
|
9337
|
+
region: secrets.aws.region ?? "",
|
|
9338
|
+
},
|
|
9265
9339
|
};
|
|
9266
9340
|
}
|
|
9267
9341
|
// If we get here, no complete set of variables was found
|
|
9268
9342
|
return {
|
|
9269
9343
|
isValid: false,
|
|
9270
9344
|
source: null,
|
|
9271
|
-
missingVars: []
|
|
9345
|
+
missingVars: [],
|
|
9272
9346
|
};
|
|
9273
9347
|
};
|
|
9274
9348
|
const initialiseAWSClient = (ClientClass, auth = null) => {
|
|
@@ -9277,48 +9351,52 @@ const initialiseAWSClient = (ClientClass, auth = null) => {
|
|
|
9277
9351
|
const requestTimeout = ClientClass === clientS3.S3Client ? AWS_S3_TIMEOUT_MS : AWS_LAMBDA_TIMEOUT_MS;
|
|
9278
9352
|
// Case 1: Explicit auth provided
|
|
9279
9353
|
if (auth) {
|
|
9280
|
-
if (typeof auth !==
|
|
9281
|
-
throw new Error(
|
|
9354
|
+
if (typeof auth !== "object" || auth === null) {
|
|
9355
|
+
throw new Error("Auth parameter must be an object");
|
|
9282
9356
|
}
|
|
9283
|
-
const requiredAuthFields = [
|
|
9284
|
-
|
|
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]);
|
|
9285
9363
|
if (missingFields.length > 0) {
|
|
9286
|
-
throw new Error(`Auth object is missing required fields: ${missingFields.join(
|
|
9364
|
+
throw new Error(`Auth object is missing required fields: ${missingFields.join(", ")}`);
|
|
9287
9365
|
}
|
|
9288
9366
|
return new ClientClass({
|
|
9289
9367
|
region: auth.AWS_REGION,
|
|
9290
9368
|
credentials: {
|
|
9291
9369
|
accessKeyId: auth.AWS_ACCESS_KEY_ID,
|
|
9292
|
-
secretAccessKey: auth.AWS_SECRET_ACCESS_KEY
|
|
9370
|
+
secretAccessKey: auth.AWS_SECRET_ACCESS_KEY,
|
|
9293
9371
|
},
|
|
9294
9372
|
requestHandler: {
|
|
9295
|
-
requestTimeout
|
|
9296
|
-
}
|
|
9373
|
+
requestTimeout,
|
|
9374
|
+
},
|
|
9297
9375
|
});
|
|
9298
9376
|
}
|
|
9299
9377
|
// Case 2: Check environment variables
|
|
9300
9378
|
const envVarsCheck = validateEnvironmentVars();
|
|
9301
9379
|
if (!envVarsCheck.isValid) {
|
|
9302
|
-
throw new Error(
|
|
9380
|
+
throw new Error("No valid AWS credentials found in environment variables");
|
|
9303
9381
|
}
|
|
9304
9382
|
// Case 2a: Lambda execution environment
|
|
9305
|
-
if (envVarsCheck.source ===
|
|
9383
|
+
if (envVarsCheck.source === "lambda") {
|
|
9306
9384
|
return new ClientClass({
|
|
9307
9385
|
requestHandler: {
|
|
9308
|
-
requestTimeout
|
|
9309
|
-
}
|
|
9386
|
+
requestTimeout,
|
|
9387
|
+
},
|
|
9310
9388
|
}); // AWS SDK will automatically use Lambda role credentials
|
|
9311
9389
|
}
|
|
9312
9390
|
// Case 2b: MY_ prefixed vars or standard AWS vars
|
|
9313
9391
|
return new ClientClass({
|
|
9314
|
-
region: envVarsCheck.vars
|
|
9392
|
+
region: envVarsCheck.vars?.region ?? "",
|
|
9315
9393
|
credentials: {
|
|
9316
|
-
accessKeyId: envVarsCheck.vars
|
|
9317
|
-
secretAccessKey: envVarsCheck.vars
|
|
9394
|
+
accessKeyId: envVarsCheck.vars?.accessKeyId ?? "",
|
|
9395
|
+
secretAccessKey: envVarsCheck.vars?.secretAccessKey ?? "",
|
|
9318
9396
|
},
|
|
9319
9397
|
requestHandler: {
|
|
9320
|
-
requestTimeout
|
|
9321
|
-
}
|
|
9398
|
+
requestTimeout,
|
|
9399
|
+
},
|
|
9322
9400
|
});
|
|
9323
9401
|
}
|
|
9324
9402
|
catch (error) {
|
|
@@ -9331,7 +9409,9 @@ const initialiseS3Client = (auth = null) => {
|
|
|
9331
9409
|
const envVarsCheck = auth ? null : validateEnvironmentVars();
|
|
9332
9410
|
const cacheKey = generateCacheKey(auth, envVarsCheck);
|
|
9333
9411
|
if (s3ClientCache.has(cacheKey)) {
|
|
9334
|
-
|
|
9412
|
+
const cached = s3ClientCache.get(cacheKey);
|
|
9413
|
+
if (cached)
|
|
9414
|
+
return cached;
|
|
9335
9415
|
}
|
|
9336
9416
|
// Create new client and cache it
|
|
9337
9417
|
const client = initialiseAWSClient(clientS3.S3Client, auth);
|
|
@@ -9343,7 +9423,9 @@ const initialiseLambdaClient = (auth = null) => {
|
|
|
9343
9423
|
const envVarsCheck = auth ? null : validateEnvironmentVars();
|
|
9344
9424
|
const cacheKey = generateCacheKey(auth, envVarsCheck);
|
|
9345
9425
|
if (lambdaClientCache.has(cacheKey)) {
|
|
9346
|
-
|
|
9426
|
+
const cached = lambdaClientCache.get(cacheKey);
|
|
9427
|
+
if (cached)
|
|
9428
|
+
return cached;
|
|
9347
9429
|
}
|
|
9348
9430
|
// Create new client and cache it
|
|
9349
9431
|
const client = initialiseAWSClient(clientLambda.Lambda, auth);
|
|
@@ -9843,15 +9925,17 @@ const isRetryableS3Error = (error) => {
|
|
|
9843
9925
|
if (error instanceof Error) {
|
|
9844
9926
|
const message = error.message;
|
|
9845
9927
|
// Retry on throttling
|
|
9846
|
-
if (message.includes(
|
|
9928
|
+
if (message.includes("SlowDown") || message.includes("RequestTimeout")) {
|
|
9847
9929
|
return true;
|
|
9848
9930
|
}
|
|
9849
9931
|
// Retry on 5xx server errors
|
|
9850
|
-
if (message.includes(
|
|
9932
|
+
if (message.includes("InternalError") ||
|
|
9933
|
+
message.includes("ServiceUnavailable")) {
|
|
9851
9934
|
return true;
|
|
9852
9935
|
}
|
|
9853
9936
|
// Retry on connection errors
|
|
9854
|
-
if (message.includes(
|
|
9937
|
+
if (message.includes("RequestTimeTooSkewed") ||
|
|
9938
|
+
message.includes("NetworkingError")) {
|
|
9855
9939
|
return true;
|
|
9856
9940
|
}
|
|
9857
9941
|
}
|
|
@@ -9865,10 +9949,11 @@ const generateRandomHash = (length) => {
|
|
|
9865
9949
|
};
|
|
9866
9950
|
function generateDateTimeStamp() {
|
|
9867
9951
|
const now = new Date();
|
|
9868
|
-
return now
|
|
9869
|
-
.
|
|
9870
|
-
.replace(
|
|
9871
|
-
.replace(
|
|
9952
|
+
return now
|
|
9953
|
+
.toISOString()
|
|
9954
|
+
.replace(/T/, "-")
|
|
9955
|
+
.replace(/\..+/, "")
|
|
9956
|
+
.replace(/:/g, "-");
|
|
9872
9957
|
}
|
|
9873
9958
|
const isValidBucketName = (name) => {
|
|
9874
9959
|
const regex = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/;
|
|
@@ -9899,7 +9984,7 @@ async function uploadFile(s3Client, bucketName, filePath, destinationPath, isPub
|
|
|
9899
9984
|
Bucket: bucketName,
|
|
9900
9985
|
Key: s3Key,
|
|
9901
9986
|
Body: fileContent,
|
|
9902
|
-
ACL: isPublic ?
|
|
9987
|
+
ACL: isPublic ? "public-read" : undefined,
|
|
9903
9988
|
});
|
|
9904
9989
|
await withRetry(() => s3Client.send(command), {
|
|
9905
9990
|
maxRetries: 3,
|
|
@@ -9919,7 +10004,7 @@ async function uploadDirectory(s3Client, bucketName, dirPath, destinationPath, i
|
|
|
9919
10004
|
Bucket: bucketName,
|
|
9920
10005
|
Key: s3Key,
|
|
9921
10006
|
Body: fileContent,
|
|
9922
|
-
ACL: isPublic ?
|
|
10007
|
+
ACL: isPublic ? "public-read" : undefined,
|
|
9923
10008
|
});
|
|
9924
10009
|
await withRetry(() => s3Client.send(command), {
|
|
9925
10010
|
maxRetries: 3,
|
|
@@ -9935,7 +10020,7 @@ async function zipDirectory(sourceDir, outPath) {
|
|
|
9935
10020
|
const zip = new AdmZip();
|
|
9936
10021
|
let fileCount = 0;
|
|
9937
10022
|
try {
|
|
9938
|
-
async function addFilesToZip(currentPath, relativePath =
|
|
10023
|
+
async function addFilesToZip(currentPath, relativePath = "") {
|
|
9939
10024
|
const files = await fs$1.readdir(currentPath, { withFileTypes: true });
|
|
9940
10025
|
for (const file of files) {
|
|
9941
10026
|
const filePath = path$1.join(currentPath, file.name);
|
|
@@ -9978,27 +10063,33 @@ async function downloadFromS3Helper(s3Client, bucketName, s3Path, localPath) {
|
|
|
9978
10063
|
Prefix: s3Path,
|
|
9979
10064
|
ContinuationToken: continuationToken,
|
|
9980
10065
|
});
|
|
9981
|
-
const listResponse = await withRetry(() => s3Client.send(listCommand), {
|
|
10066
|
+
const listResponse = (await withRetry(() => s3Client.send(listCommand), {
|
|
9982
10067
|
maxRetries: 3,
|
|
9983
10068
|
baseDelayMs: 1000,
|
|
9984
10069
|
maxDelayMs: 15000,
|
|
9985
10070
|
retryableErrors: isRetryableS3Error,
|
|
9986
|
-
}, `S3:ListObjects:${bucketName}`);
|
|
10071
|
+
}, `S3:ListObjects:${bucketName}`));
|
|
9987
10072
|
const objects = listResponse.Contents || [];
|
|
9988
10073
|
if (objects.length === 0) {
|
|
9989
10074
|
return { fileCount: 0, folderCount: 0, totalSize: 0, fileList: [] };
|
|
9990
10075
|
}
|
|
9991
|
-
if (objects.length === 1 && objects[0].Key?.endsWith(
|
|
10076
|
+
if (objects.length === 1 && objects[0].Key?.endsWith(".zip")) {
|
|
9992
10077
|
// It's a zip file, download and extract it
|
|
9993
10078
|
const zipPath = path$1.join(localPath, path$1.basename(objects[0].Key));
|
|
9994
10079
|
await downloadFile(s3Client, bucketName, objects[0].Key, zipPath);
|
|
9995
|
-
const { totalFiles, totalFolders, totalSize: extractedSize, fileList: extractedFileList } = await unzipFile(zipPath, localPath);
|
|
10080
|
+
const { totalFiles, totalFolders, totalSize: extractedSize, fileList: extractedFileList, } = await unzipFile(zipPath, localPath);
|
|
9996
10081
|
await fs$1.unlink(zipPath);
|
|
9997
|
-
return {
|
|
10082
|
+
return {
|
|
10083
|
+
fileCount: totalFiles,
|
|
10084
|
+
folderCount: totalFolders,
|
|
10085
|
+
totalSize: extractedSize,
|
|
10086
|
+
fileList: extractedFileList,
|
|
10087
|
+
};
|
|
9998
10088
|
}
|
|
9999
10089
|
else {
|
|
10000
10090
|
// Non-zip files. Download files in batches.
|
|
10001
|
-
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
|
|
10002
10093
|
const batch = objects.slice(i, i + 1000);
|
|
10003
10094
|
await Promise.all(batch.map(async (obj) => {
|
|
10004
10095
|
if (!obj.Key)
|
|
@@ -10014,7 +10105,9 @@ async function downloadFromS3Helper(s3Client, bucketName, s3Path, localPath) {
|
|
|
10014
10105
|
}
|
|
10015
10106
|
// Count folders
|
|
10016
10107
|
const relativePath = path$1.relative(localPath, path$1.dirname(localFilePath));
|
|
10017
|
-
if (relativePath &&
|
|
10108
|
+
if (relativePath &&
|
|
10109
|
+
!relativePath.startsWith("..") &&
|
|
10110
|
+
relativePath !== ".") {
|
|
10018
10111
|
folderCount++;
|
|
10019
10112
|
}
|
|
10020
10113
|
}));
|
|
@@ -10035,7 +10128,7 @@ async function downloadFile(s3Client, bucketName, s3Key, localFilePath) {
|
|
|
10035
10128
|
await withRetry(async () => {
|
|
10036
10129
|
const { Body } = await s3Client.send(getCommand);
|
|
10037
10130
|
if (!Body)
|
|
10038
|
-
throw new Error(
|
|
10131
|
+
throw new Error("No body returned from S3");
|
|
10039
10132
|
const writeStream = fs.createWriteStream(localFilePath);
|
|
10040
10133
|
await promises.pipeline(Body, writeStream);
|
|
10041
10134
|
}, {
|
|
@@ -10091,17 +10184,19 @@ async function emptyBucket(s3Client, bucketName) {
|
|
|
10091
10184
|
Bucket: bucketName,
|
|
10092
10185
|
ContinuationToken: continuationToken,
|
|
10093
10186
|
});
|
|
10094
|
-
const response = await withRetry(() => s3Client.send(listCommand), {
|
|
10187
|
+
const response = (await withRetry(() => s3Client.send(listCommand), {
|
|
10095
10188
|
maxRetries: 3,
|
|
10096
10189
|
baseDelayMs: 1000,
|
|
10097
10190
|
maxDelayMs: 15000,
|
|
10098
10191
|
retryableErrors: isRetryableS3Error,
|
|
10099
|
-
}, `S3:ListObjects:${bucketName}`);
|
|
10192
|
+
}, `S3:ListObjects:${bucketName}`));
|
|
10100
10193
|
const { Contents, IsTruncated, NextContinuationToken } = response;
|
|
10101
10194
|
if (Contents && Contents.length > 0) {
|
|
10102
10195
|
const deleteCommand = new clientS3.DeleteObjectsCommand({
|
|
10103
10196
|
Bucket: bucketName,
|
|
10104
|
-
Delete: {
|
|
10197
|
+
Delete: {
|
|
10198
|
+
Objects: Contents.filter((c) => c.Key).map((c) => ({ Key: c.Key })),
|
|
10199
|
+
},
|
|
10105
10200
|
});
|
|
10106
10201
|
await withRetry(() => s3Client.send(deleteCommand), {
|
|
10107
10202
|
maxRetries: 3,
|
|
@@ -23009,11 +23104,11 @@ let poolConfig = DEFAULT_POOL_CONFIG;
|
|
|
23009
23104
|
async function loadApolloModules() {
|
|
23010
23105
|
if (typeof window === "undefined" || process.env.AWS_EXECUTION_ENV) {
|
|
23011
23106
|
// Server-side (or Lambda): load the CommonJS‑based implementation.
|
|
23012
|
-
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'); }));
|
|
23013
23108
|
}
|
|
23014
23109
|
else {
|
|
23015
23110
|
// Client-side: load the ESM‑based implementation.
|
|
23016
|
-
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'); }));
|
|
23017
23112
|
}
|
|
23018
23113
|
}
|
|
23019
23114
|
/**
|
|
@@ -79040,7 +79135,7 @@ const RETRY_CONFIG = {
|
|
|
79040
79135
|
BASE_DELAY: 2000, // Increased base delay to 2 seconds
|
|
79041
79136
|
MAX_DELAY: 64000,
|
|
79042
79137
|
MAX_RETRIES: 5,
|
|
79043
|
-
MAX_RANDOM_DELAY: 1000
|
|
79138
|
+
MAX_RANDOM_DELAY: 1000,
|
|
79044
79139
|
};
|
|
79045
79140
|
/**
|
|
79046
79141
|
* Determines if an error is related to Google Sheets API quotas or rate limits.
|
|
@@ -79066,10 +79161,10 @@ const RETRY_CONFIG = {
|
|
|
79066
79161
|
*/
|
|
79067
79162
|
function isQuotaError(error) {
|
|
79068
79163
|
const message = error.message.toLowerCase();
|
|
79069
|
-
return message.includes(
|
|
79070
|
-
message.includes(
|
|
79071
|
-
message.includes(
|
|
79072
|
-
message.includes(
|
|
79164
|
+
return (message.includes("quota exceeded") ||
|
|
79165
|
+
message.includes("rate limit") ||
|
|
79166
|
+
message.includes("429") ||
|
|
79167
|
+
message.includes("too many requests"));
|
|
79073
79168
|
}
|
|
79074
79169
|
/**
|
|
79075
79170
|
* Implements exponential backoff retry mechanism for Google Sheets API calls.
|
|
@@ -79128,14 +79223,14 @@ async function withExponentialBackoff(operation) {
|
|
|
79128
79223
|
const exponentialDelay = Math.min(Math.pow(2, retries) * RETRY_CONFIG.BASE_DELAY + randomDelay, RETRY_CONFIG.MAX_DELAY);
|
|
79129
79224
|
logIfDebug(`Quota/Rate limit exceeded. Error: ${error.message}`);
|
|
79130
79225
|
logIfDebug(`Retrying in ${exponentialDelay}ms (attempt ${retries}/${RETRY_CONFIG.MAX_RETRIES})`);
|
|
79131
|
-
await new Promise(resolve => setTimeout(resolve, exponentialDelay));
|
|
79226
|
+
await new Promise((resolve) => setTimeout(resolve, exponentialDelay));
|
|
79132
79227
|
}
|
|
79133
79228
|
}
|
|
79134
79229
|
}
|
|
79135
79230
|
function checkEnvVars() {
|
|
79136
79231
|
const secrets = getSecrets();
|
|
79137
79232
|
if (!secrets.googleSheets.clientEmail || !secrets.googleSheets.privateKey) {
|
|
79138
|
-
throw new Error(
|
|
79233
|
+
throw new Error("GOOGLE_SHEETS_CLIENT_EMAIL or GOOGLE_SHEETS_PRIVATE_KEY is not defined in environment variables.");
|
|
79139
79234
|
}
|
|
79140
79235
|
}
|
|
79141
79236
|
/**
|
|
@@ -79150,16 +79245,16 @@ async function getAuthClient() {
|
|
|
79150
79245
|
const secrets = getSecrets();
|
|
79151
79246
|
const credentials = {
|
|
79152
79247
|
client_email: secrets.googleSheets.clientEmail,
|
|
79153
|
-
private_key: secrets.googleSheets.privateKey?.replace(/\\n/g,
|
|
79248
|
+
private_key: secrets.googleSheets.privateKey?.replace(/\\n/g, "\n"),
|
|
79154
79249
|
};
|
|
79155
79250
|
const auth = new require$$0$6.GoogleAuth({
|
|
79156
79251
|
credentials,
|
|
79157
|
-
scopes: [
|
|
79252
|
+
scopes: ["https://www.googleapis.com/auth/spreadsheets"],
|
|
79158
79253
|
});
|
|
79159
79254
|
return auth.getClient();
|
|
79160
79255
|
}
|
|
79161
79256
|
catch (error) {
|
|
79162
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
79257
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
79163
79258
|
logIfDebug(`Error initializing Google Sheets auth: ${errorMessage}`);
|
|
79164
79259
|
throw new Error(`Failed to initialize Google Sheets auth: ${errorMessage}`);
|
|
79165
79260
|
}
|
|
@@ -79171,7 +79266,7 @@ async function getSheetsClient() {
|
|
|
79171
79266
|
const auth = await getAuthClient();
|
|
79172
79267
|
return new buildExports.sheets_v4.Sheets({
|
|
79173
79268
|
auth,
|
|
79174
|
-
timeout: GOOGLE_SHEETS_TIMEOUT_MS
|
|
79269
|
+
timeout: GOOGLE_SHEETS_TIMEOUT_MS,
|
|
79175
79270
|
});
|
|
79176
79271
|
}
|
|
79177
79272
|
/**
|
|
@@ -79204,7 +79299,7 @@ function escapeSheetName(sheetName) {
|
|
|
79204
79299
|
* @param startColumn Optional starting column (defaults to 'A')
|
|
79205
79300
|
* @returns Promise resolving to response with success status and row details
|
|
79206
79301
|
*/
|
|
79207
|
-
async function addRow(config, values, startColumn =
|
|
79302
|
+
async function addRow(config, values, startColumn = "A") {
|
|
79208
79303
|
try {
|
|
79209
79304
|
const sheets = await getSheetsClient();
|
|
79210
79305
|
const escapedSheetName = escapeSheetName(config.sheetName);
|
|
@@ -79212,7 +79307,7 @@ async function addRow(config, values, startColumn = 'A') {
|
|
|
79212
79307
|
const response = await withExponentialBackoff(() => sheets.spreadsheets.values.append({
|
|
79213
79308
|
spreadsheetId: config.spreadsheetId,
|
|
79214
79309
|
range,
|
|
79215
|
-
valueInputOption:
|
|
79310
|
+
valueInputOption: "USER_ENTERED",
|
|
79216
79311
|
requestBody: {
|
|
79217
79312
|
values: [values],
|
|
79218
79313
|
},
|
|
@@ -79220,9 +79315,9 @@ async function addRow(config, values, startColumn = 'A') {
|
|
|
79220
79315
|
//logIfDebug(`Added row to sheet ${config.sheetName}: ${values.join(', ')}`);
|
|
79221
79316
|
const rowNumber = response.data.updates?.updatedRange
|
|
79222
79317
|
? parseInt(response.data.updates.updatedRange
|
|
79223
|
-
.split(
|
|
79224
|
-
.split(
|
|
79225
|
-
.replace(/[^0-9]/g,
|
|
79318
|
+
.split("!")[1]
|
|
79319
|
+
.split(":")[0]
|
|
79320
|
+
.replace(/[^0-9]/g, ""))
|
|
79226
79321
|
: 0;
|
|
79227
79322
|
return {
|
|
79228
79323
|
success: true,
|
|
@@ -79233,7 +79328,7 @@ async function addRow(config, values, startColumn = 'A') {
|
|
|
79233
79328
|
};
|
|
79234
79329
|
}
|
|
79235
79330
|
catch (error) {
|
|
79236
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
79331
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
79237
79332
|
logIfDebug(`Error adding row to sheet: ${errorMessage}`);
|
|
79238
79333
|
return {
|
|
79239
79334
|
success: false,
|
|
@@ -79275,7 +79370,7 @@ async function addSheet(spreadsheetId, sheetTitle) {
|
|
|
79275
79370
|
};
|
|
79276
79371
|
}
|
|
79277
79372
|
catch (error) {
|
|
79278
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
79373
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
79279
79374
|
logIfDebug(`Error adding sheet: ${errorMessage}`);
|
|
79280
79375
|
return {
|
|
79281
79376
|
success: false,
|
|
@@ -79299,7 +79394,7 @@ async function writeCell(config, cell, value) {
|
|
|
79299
79394
|
await withExponentialBackoff(() => sheets.spreadsheets.values.update({
|
|
79300
79395
|
spreadsheetId: config.spreadsheetId,
|
|
79301
79396
|
range,
|
|
79302
|
-
valueInputOption:
|
|
79397
|
+
valueInputOption: "USER_ENTERED",
|
|
79303
79398
|
requestBody: {
|
|
79304
79399
|
values: [[value]],
|
|
79305
79400
|
},
|
|
@@ -79313,7 +79408,7 @@ async function writeCell(config, cell, value) {
|
|
|
79313
79408
|
};
|
|
79314
79409
|
}
|
|
79315
79410
|
catch (error) {
|
|
79316
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
79411
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
79317
79412
|
logIfDebug(`Error writing to cell: ${errorMessage}`);
|
|
79318
79413
|
return {
|
|
79319
79414
|
success: false,
|
|
@@ -79337,8 +79432,11 @@ async function getCell(config, cell) {
|
|
|
79337
79432
|
spreadsheetId: config.spreadsheetId,
|
|
79338
79433
|
range,
|
|
79339
79434
|
}));
|
|
79340
|
-
const
|
|
79341
|
-
|
|
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)}`);
|
|
79342
79440
|
return {
|
|
79343
79441
|
success: true,
|
|
79344
79442
|
data: {
|
|
@@ -79347,7 +79445,7 @@ async function getCell(config, cell) {
|
|
|
79347
79445
|
};
|
|
79348
79446
|
}
|
|
79349
79447
|
catch (error) {
|
|
79350
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
79448
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
79351
79449
|
logIfDebug(`Error reading from cell: ${errorMessage}`);
|
|
79352
79450
|
return {
|
|
79353
79451
|
success: false,
|
|
@@ -79380,10 +79478,11 @@ function convertA1ToRowCol(a1Notation) {
|
|
|
79380
79478
|
* @returns Padded array of arrays
|
|
79381
79479
|
*/
|
|
79382
79480
|
function padArrays(arrays) {
|
|
79383
|
-
const maxLength = Math.max(...arrays.map(arr => arr.length));
|
|
79384
|
-
return arrays.map(arr => {
|
|
79481
|
+
const maxLength = Math.max(...arrays.map((arr) => arr.length));
|
|
79482
|
+
return arrays.map((arr) => {
|
|
79385
79483
|
if (arr.length < maxLength) {
|
|
79386
|
-
|
|
79484
|
+
const padding = Array(maxLength - arr.length).fill("");
|
|
79485
|
+
return [...arr, ...padding];
|
|
79387
79486
|
}
|
|
79388
79487
|
return arr;
|
|
79389
79488
|
});
|
|
@@ -79395,10 +79494,10 @@ function padArrays(arrays) {
|
|
|
79395
79494
|
* @param startCell Optional starting cell in A1 notation (defaults to 'A1')
|
|
79396
79495
|
* @returns Promise resolving to response with success status and update details
|
|
79397
79496
|
*/
|
|
79398
|
-
async function writeArray(config, data, startCell =
|
|
79497
|
+
async function writeArray(config, data, startCell = "A1") {
|
|
79399
79498
|
try {
|
|
79400
79499
|
if (!data.length) {
|
|
79401
|
-
throw new Error(
|
|
79500
|
+
throw new Error("Data array is empty");
|
|
79402
79501
|
}
|
|
79403
79502
|
const sheets = await getSheetsClient();
|
|
79404
79503
|
const { row: startRow, column: startCol } = convertA1ToRowCol(startCell);
|
|
@@ -79414,7 +79513,7 @@ async function writeArray(config, data, startCell = 'A1') {
|
|
|
79414
79513
|
const response = await withExponentialBackoff(() => sheets.spreadsheets.values.update({
|
|
79415
79514
|
spreadsheetId: config.spreadsheetId,
|
|
79416
79515
|
range,
|
|
79417
|
-
valueInputOption:
|
|
79516
|
+
valueInputOption: "USER_ENTERED",
|
|
79418
79517
|
requestBody: {
|
|
79419
79518
|
values: paddedData,
|
|
79420
79519
|
},
|
|
@@ -79430,7 +79529,7 @@ async function writeArray(config, data, startCell = 'A1') {
|
|
|
79430
79529
|
};
|
|
79431
79530
|
}
|
|
79432
79531
|
catch (error) {
|
|
79433
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
79532
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
79434
79533
|
logIfDebug(`Error writing array to sheet: ${errorMessage}`);
|
|
79435
79534
|
return {
|
|
79436
79535
|
success: false,
|
|
@@ -81724,4 +81823,4 @@ exports.withCorrelationId = withCorrelationId;
|
|
|
81724
81823
|
exports.withMetrics = withMetrics;
|
|
81725
81824
|
exports.withRateLimit = withRateLimit;
|
|
81726
81825
|
exports.withRetry = withRetry;
|
|
81727
|
-
//# sourceMappingURL=index-
|
|
81826
|
+
//# sourceMappingURL=index-KzQOh2uu.js.map
|