@adaptic/lumic-utils 1.0.24 → 1.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/{apollo-client.client-CyxU1hTu.js → apollo-client.client-CtjgIfRT.js} +3 -3
  2. package/dist/{apollo-client.client-CyxU1hTu.js.map → apollo-client.client-CtjgIfRT.js.map} +1 -1
  3. package/dist/{apollo-client.client-9wJcufhf.js → apollo-client.client-Uv0-ZrFd.js} +4 -4
  4. package/dist/{apollo-client.client-9wJcufhf.js.map → apollo-client.client-Uv0-ZrFd.js.map} +1 -1
  5. package/dist/{apollo-client.server-CbagxkmK.js → apollo-client.server-DaXmxoBl.js} +3 -3
  6. package/dist/{apollo-client.server-CbagxkmK.js.map → apollo-client.server-DaXmxoBl.js.map} +1 -1
  7. package/dist/{apollo-client.server-DB3jLbBx.js → apollo-client.server-JYHXy9ib.js} +3 -3
  8. package/dist/{apollo-client.server-DB3jLbBx.js.map → apollo-client.server-JYHXy9ib.js.map} +1 -1
  9. package/dist/{index-B4yVKGNR.js → index-Bu-2kk3p.js} +2 -2
  10. package/dist/{index-B4yVKGNR.js.map → index-Bu-2kk3p.js.map} +1 -1
  11. package/dist/{index-KzQOh2uu.js → index-DgFDhFuO.js} +528 -453
  12. package/dist/{index-KzQOh2uu.js.map → index-DgFDhFuO.js.map} +1 -1
  13. package/dist/{index-CL79JTWc.js → index-HopiPnjc.js} +2 -2
  14. package/dist/{index-CL79JTWc.js.map → index-HopiPnjc.js.map} +1 -1
  15. package/dist/{index-EMQ1uJMh.js → index-z7Y1nTVX.js} +528 -453
  16. package/dist/{index-EMQ1uJMh.js.map → index-z7Y1nTVX.js.map} +1 -1
  17. package/dist/index.cjs +1 -1
  18. package/dist/index.mjs +1 -1
  19. package/dist/test.cjs +17 -21
  20. package/dist/test.cjs.map +1 -1
  21. package/dist/test.mjs +17 -21
  22. package/dist/test.mjs.map +1 -1
  23. package/dist/types/functions/get-weather.d.ts +1 -1
  24. package/dist/types/functions/google-sheets.d.ts +2 -2
  25. package/dist/types/functions/json-llm-tools.d.ts +1 -1
  26. package/dist/types/functions/llm-anthropic.d.ts +1 -1
  27. package/dist/types/functions/llm-deepseek.d.ts +6 -2
  28. package/dist/types/functions/llm-openai-compatible.d.ts +2 -2
  29. package/dist/types/functions/llm-openai.d.ts +8 -3
  30. package/dist/types/functions/llm-utils.d.ts +1 -1
  31. package/dist/types/test-llm-functions-archive.d.ts +1 -1
  32. package/dist/types/test.d.ts +1 -1
  33. package/dist/types/types/openai-types.d.ts +26 -10
  34. package/dist/types/utils/aws-initialise.d.ts +4 -4
  35. package/dist/types/utils/retry.d.ts +8 -0
  36. package/package.json +2 -2
@@ -571,19 +571,10 @@ 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.
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.
574
+ * Default model tiers per provider for use with LLM_MODEL_MINI/NORMAL/ADVANCED env vars
584
575
  */
585
576
  const PROVIDER_DEFAULT_MODELS = {
586
- openai: { mini: 'gpt-5.4-nano', normal: 'gpt-5.4-mini', advanced: 'gpt-5.4' },
577
+ openai: { mini: 'gpt-5.4-nano', normal: 'gpt-5.4-mini', advanced: 'gpt-5.5' },
587
578
  anthropic: { mini: 'claude-haiku-4-5', normal: 'claude-sonnet-4-6', advanced: 'claude-opus-4-7' },
588
579
  deepseek: { mini: 'deepseek-v4-flash', normal: 'deepseek-v4-flash', advanced: 'deepseek-v4-pro' },
589
580
  kimi: { mini: 'kimi-k2-0905-preview', normal: 'kimi-k2.5', advanced: 'kimi-k2.6' },
@@ -1537,13 +1528,11 @@ let openai;
1537
1528
  function initializeOpenAI(apiKey) {
1538
1529
  const key = apiKey || getSecrets().openai.apiKey;
1539
1530
  if (!key) {
1540
- throw new Error("OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set");
1531
+ throw new Error('OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set');
1541
1532
  }
1542
1533
  return new OpenAI({
1543
1534
  apiKey: key,
1544
- defaultHeaders: {
1545
- "User-Agent": "My Server-side Application (compatible; OpenAI API Client)",
1546
- },
1535
+ defaultHeaders: { 'User-Agent': 'My Server-side Application (compatible; OpenAI API Client)' },
1547
1536
  });
1548
1537
  }
1549
1538
  /**
@@ -1561,7 +1550,9 @@ function initializeOpenAI(apiKey) {
1561
1550
  */
1562
1551
  async function fixJsonWithAI(jsonStr, options) {
1563
1552
  // Backward compatibility: handle string apiKey as first positional param
1564
- const opts = typeof options === "string" ? { apiKey: options } : (options ?? {});
1553
+ const opts = typeof options === 'string'
1554
+ ? { apiKey: options }
1555
+ : (options ?? {});
1565
1556
  const apiKey = opts.apiKey;
1566
1557
  const maxDepth = opts.maxDepth ?? 2;
1567
1558
  const depth = opts._depth ?? 0;
@@ -1574,18 +1565,18 @@ async function fixJsonWithAI(jsonStr, options) {
1574
1565
  openai = initializeOpenAI(apiKey);
1575
1566
  }
1576
1567
  const completion = await openai.chat.completions.create({
1577
- model: "gpt-5-mini",
1568
+ model: 'gpt-5-mini',
1578
1569
  messages: [
1579
1570
  {
1580
- role: "system",
1581
- content: "You are a JSON fixer. Return only valid JSON without any additional text or explanation.",
1571
+ role: 'system',
1572
+ content: 'You are a JSON fixer. Return only valid JSON without any additional text or explanation.'
1582
1573
  },
1583
1574
  {
1584
- role: "user",
1585
- content: `Fix this broken JSON:\n${jsonStr}`,
1586
- },
1575
+ role: 'user',
1576
+ content: `Fix this broken JSON:\n${jsonStr}`
1577
+ }
1587
1578
  ],
1588
- response_format: { type: "json_object" },
1579
+ response_format: { type: 'json_object' }
1589
1580
  });
1590
1581
  const fixedJson = completion.choices[0]?.message?.content;
1591
1582
  if (fixedJson && isValidJson(fixedJson)) {
@@ -1599,32 +1590,23 @@ async function fixJsonWithAI(jsonStr, options) {
1599
1590
  _depth: depth + 1,
1600
1591
  });
1601
1592
  }
1602
- throw new JsonParseError("Failed to fix JSON with AI: returned invalid JSON");
1593
+ throw new JsonParseError('Failed to fix JSON with AI: returned invalid JSON');
1603
1594
  }
1604
1595
  catch (err) {
1605
1596
  // Re-throw JsonParseError as-is (e.g. max depth exceeded)
1606
1597
  if (err instanceof JsonParseError) {
1607
1598
  throw err;
1608
1599
  }
1609
- const errorMessage = err instanceof Error ? err.message : "Unknown error occurred";
1600
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
1610
1601
  throw new JsonParseError(`Error fixing JSON with AI: ${errorMessage}`);
1611
1602
  }
1612
1603
  }
1613
1604
 
1614
1605
  // llm-utils.ts
1615
1606
  function normalizeModelName(model) {
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
- ];
1607
+ return model.replace(/_/g, '-').toLowerCase();
1608
+ }
1609
+ const CODE_BLOCK_TYPES = ['javascript', 'js', 'graphql', 'json', 'typescript', 'python', 'markdown', 'yaml'];
1628
1610
  /**
1629
1611
  * Tries to parse JSON from the given content string according to the specified
1630
1612
  * response format. If the response format is 'json' or a JSON schema object, it
@@ -1648,9 +1630,7 @@ async function parseResponse(content, responseFormat, options = {}) {
1648
1630
  if (recursionDepth > maxRetries) {
1649
1631
  throw new Error(`Maximum recursion depth (${maxRetries}) exceeded while parsing JSON. AI fixing may have returned broken JSON.`);
1650
1632
  }
1651
- if (responseFormat === "json" ||
1652
- (typeof responseFormat === "object" &&
1653
- responseFormat?.type === "json_schema")) {
1633
+ if (responseFormat === 'json' || (typeof responseFormat === 'object' && responseFormat?.type === 'json_schema')) {
1654
1634
  let cleanedContent = content.trim();
1655
1635
  let detectedType = null;
1656
1636
  // Remove code block markers if present
@@ -1661,15 +1641,24 @@ async function parseResponse(content, responseFormat, options = {}) {
1661
1641
  }
1662
1642
  return false;
1663
1643
  });
1664
- if (startsWithCodeBlock && cleanedContent.endsWith("```")) {
1665
- const firstLineEndIndex = cleanedContent.indexOf("\n");
1644
+ if (startsWithCodeBlock && cleanedContent.endsWith('```')) {
1645
+ const firstLineEndIndex = cleanedContent.indexOf('\n');
1666
1646
  if (firstLineEndIndex !== -1) {
1667
1647
  cleanedContent = cleanedContent.slice(firstLineEndIndex + 1, -3).trim();
1668
1648
  getLumicLogger().info(`Code block for type ${detectedType} detected and removed. Cleaned content: ${cleanedContent}`);
1669
1649
  }
1670
1650
  }
1671
- // Multiple JSON parsing strategies
1672
- // Default pipeline: direct parse -> extract from brackets -> remove leading/trailing text -> fixBrokenJson (string manipulation) -> FAIL
1651
+ // Whether the cleaned content structurally resembles JSON. Used to gate the
1652
+ // lenient fixBrokenJson strategy, which will otherwise coerce arbitrary
1653
+ // prose into a bogus object/array.
1654
+ const inputResemblesJson = cleanedContent.startsWith('{') || cleanedContent.startsWith('[');
1655
+ // Multiple JSON parsing strategies.
1656
+ // Default pipeline: direct parse -> extract from brackets -> remove
1657
+ // leading/trailing text -> GATED fixBrokenJson -> FAIL. fixBrokenJson is
1658
+ // deliberately gated (Strategy 4): its output is accepted only when the
1659
+ // input looked like JSON AND the repaired value round-trips through
1660
+ // JSON.stringify/JSON.parse, so a "successful" repair of non-JSON text is
1661
+ // rejected instead of silently returned as garbage.
1673
1662
  const jsonParsingStrategies = [
1674
1663
  // Strategy 1: Direct parsing
1675
1664
  () => {
@@ -1695,7 +1684,7 @@ async function parseResponse(content, responseFormat, options = {}) {
1695
1684
  },
1696
1685
  // Strategy 3: Remove leading/trailing text and try parsing
1697
1686
  () => {
1698
- const jsonMatch = cleanedContent.replace(/^[^{[]*|[^}\]]*$/g, "");
1687
+ const jsonMatch = cleanedContent.replace(/^[^{[]*|[^}\]]*$/g, '');
1699
1688
  try {
1700
1689
  return JSON.parse(jsonMatch);
1701
1690
  }
@@ -1703,55 +1692,175 @@ async function parseResponse(content, responseFormat, options = {}) {
1703
1692
  return null;
1704
1693
  }
1705
1694
  },
1706
- // Strategy 4: Use fixBrokenJson (string manipulation only, no AI)
1695
+ // Strategy 4: Gated fixBrokenJson (string manipulation only, no AI)
1707
1696
  () => {
1708
- return fixBrokenJson(cleanedContent);
1697
+ if (!inputResemblesJson) {
1698
+ return null;
1699
+ }
1700
+ const fixed = fixBrokenJson(cleanedContent);
1701
+ if (fixed === null) {
1702
+ return null;
1703
+ }
1704
+ try {
1705
+ // Round-trip guard: ensures the repaired value is genuinely
1706
+ // JSON-serializable before we trust it.
1707
+ return JSON.parse(JSON.stringify(fixed));
1708
+ }
1709
+ catch {
1710
+ return null;
1711
+ }
1709
1712
  },
1710
1713
  ];
1711
1714
  // Try each parsing strategy
1712
1715
  for (const strategy of jsonParsingStrategies) {
1713
- const result = await strategy();
1716
+ const result = strategy();
1714
1717
  if (result !== null) {
1715
- return result;
1718
+ return finalizeJsonResult(result, responseFormat);
1716
1719
  }
1717
1720
  }
1718
1721
  // Strategy 5: Use AI fixing (only if explicitly enabled)
1719
1722
  if (enableAiFix) {
1720
- getLumicLogger().warn("Using AI-powered JSON fixing. This adds cost and latency. Consider improving JSON generation instead.");
1723
+ getLumicLogger().warn('Using AI-powered JSON fixing. This adds cost and latency. Consider improving JSON generation instead.');
1724
+ let aiFixedJson = null;
1721
1725
  try {
1722
- const aiFixedJson = await fixJsonWithAI(cleanedContent);
1723
- if (aiFixedJson !== null) {
1724
- // Validate the AI-fixed JSON by attempting to stringify and re-parse it
1725
- // This ensures it's actually valid JSON and not just something that passed fixJsonWithAI
1726
- try {
1727
- const validated = JSON.parse(JSON.stringify(aiFixedJson));
1728
- return validated;
1729
- }
1730
- catch {
1731
- // AI returned something that looks like JSON but isn't valid
1732
- // Increment recursion depth and try parsing the AI response
1733
- getLumicLogger().warn("AI fixing returned invalid JSON, attempting recursive parse...");
1734
- return parseResponse(JSON.stringify(aiFixedJson), responseFormat, {
1735
- ...options,
1736
- _recursionDepth: recursionDepth + 1,
1737
- });
1738
- }
1739
- }
1726
+ aiFixedJson = await fixJsonWithAI(cleanedContent);
1740
1727
  }
1741
1728
  catch (error) {
1742
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
1729
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1743
1730
  getLumicLogger().error(`AI JSON fixing failed: ${errorMessage}`);
1744
- // Continue to final error below
1731
+ // Fall through to the final error below.
1732
+ }
1733
+ if (aiFixedJson !== null) {
1734
+ let validated;
1735
+ try {
1736
+ // Validate the AI-fixed JSON by attempting to stringify and re-parse
1737
+ // it. This ensures it is actually valid JSON, not just something that
1738
+ // passed fixJsonWithAI.
1739
+ validated = JSON.parse(JSON.stringify(aiFixedJson));
1740
+ }
1741
+ catch {
1742
+ // AI returned something that looks like JSON but isn't valid.
1743
+ // Increment recursion depth and try parsing the AI response.
1744
+ getLumicLogger().warn('AI fixing returned invalid JSON, attempting recursive parse...');
1745
+ return parseResponse(JSON.stringify(aiFixedJson), responseFormat, {
1746
+ ...options,
1747
+ _recursionDepth: recursionDepth + 1,
1748
+ });
1749
+ }
1750
+ // Schema validation (json_schema format) happens here, OUTSIDE the
1751
+ // JSON.parse try/catch, so a schema mismatch propagates as a thrown
1752
+ // JsonParseError rather than being mistaken for invalid JSON.
1753
+ return finalizeJsonResult(validated, responseFormat);
1745
1754
  }
1746
1755
  }
1747
1756
  // If all strategies fail, throw an error
1748
1757
  getLumicLogger().error(`Failed to parse JSON from content: ${cleanedContent}. Original JSON: ${content}`);
1749
- throw new Error("Unable to parse JSON response");
1758
+ throw new Error('Unable to parse JSON response');
1750
1759
  }
1751
1760
  else {
1752
1761
  return content;
1753
1762
  }
1754
1763
  }
1764
+ /**
1765
+ * Reports the JSON type of a runtime value for schema-validation diagnostics.
1766
+ *
1767
+ * @param value The value whose JSON type should be described.
1768
+ * @returns One of `'null' | 'array' | 'object' | 'string' | 'number' | 'boolean' | 'undefined'`.
1769
+ */
1770
+ function jsonTypeOf(value) {
1771
+ if (value === null)
1772
+ return 'null';
1773
+ if (Array.isArray(value))
1774
+ return 'array';
1775
+ return typeof value;
1776
+ }
1777
+ /**
1778
+ * Extracts a declared primitive `type` string from a property schema, if present.
1779
+ *
1780
+ * @param propertySchema The (untyped) per-property JSON-Schema fragment.
1781
+ * @returns The declared type string, or null when none is declared.
1782
+ */
1783
+ function extractSchemaType(propertySchema) {
1784
+ if (typeof propertySchema === 'object' && propertySchema !== null && 'type' in propertySchema) {
1785
+ const declared = propertySchema.type;
1786
+ return typeof declared === 'string' ? declared : null;
1787
+ }
1788
+ return null;
1789
+ }
1790
+ /**
1791
+ * Checks whether a runtime value satisfies a declared JSON-Schema primitive
1792
+ * type. Unknown/unsupported type keywords are treated permissively (they never
1793
+ * cause a rejection), so this validator only ever rejects clear mismatches.
1794
+ *
1795
+ * @param value The runtime value to check.
1796
+ * @param expectedType The declared JSON-Schema type keyword.
1797
+ * @returns True when the value matches (or the keyword is unsupported).
1798
+ */
1799
+ function valueMatchesJsonType(value, expectedType) {
1800
+ switch (expectedType) {
1801
+ case 'string':
1802
+ return typeof value === 'string';
1803
+ case 'number':
1804
+ return typeof value === 'number';
1805
+ case 'integer':
1806
+ return typeof value === 'number' && Number.isInteger(value);
1807
+ case 'boolean':
1808
+ return typeof value === 'boolean';
1809
+ case 'object':
1810
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
1811
+ case 'array':
1812
+ return Array.isArray(value);
1813
+ case 'null':
1814
+ return value === null;
1815
+ default:
1816
+ return true;
1817
+ }
1818
+ }
1819
+ /**
1820
+ * Validates a parsed value against a `json_schema` object schema. Enforces that
1821
+ * the value is a plain object, that every `required` property is present, and
1822
+ * that present properties whose schema declares a primitive `type` match it.
1823
+ *
1824
+ * @param value The parsed value to validate.
1825
+ * @param schema The object schema supplied on the response format.
1826
+ * @throws {JsonParseError} When the value violates the schema.
1827
+ */
1828
+ function validateJsonSchema(value, schema) {
1829
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
1830
+ throw new JsonParseError(`JSON schema validation failed: expected an object but received ${jsonTypeOf(value)}.`);
1831
+ }
1832
+ const record = value;
1833
+ const missing = (schema.required ?? []).filter((key) => !(key in record));
1834
+ if (missing.length > 0) {
1835
+ throw new JsonParseError(`JSON schema validation failed: missing required properties: ${missing.join(', ')}.`);
1836
+ }
1837
+ for (const [key, propertySchema] of Object.entries(schema.properties)) {
1838
+ if (!(key in record))
1839
+ continue;
1840
+ const expectedType = extractSchemaType(propertySchema);
1841
+ if (expectedType !== null && !valueMatchesJsonType(record[key], expectedType)) {
1842
+ throw new JsonParseError(`JSON schema validation failed: property "${key}" expected type "${expectedType}" but received ${jsonTypeOf(record[key])}.`);
1843
+ }
1844
+ }
1845
+ }
1846
+ /**
1847
+ * Finalizes a successfully-parsed JSON value. When the caller supplied a
1848
+ * `json_schema` response format, the value is validated against that schema
1849
+ * before being returned so schema violations surface as a thrown
1850
+ * {@link JsonParseError} instead of flowing silently downstream. For plain
1851
+ * `json` formats the value is returned unchanged.
1852
+ *
1853
+ * @param value The parsed value produced by a parsing strategy.
1854
+ * @param responseFormat The originally requested response format.
1855
+ * @returns The value narrowed to `T`.
1856
+ * @throws {JsonParseError} When a supplied json_schema is not satisfied.
1857
+ */
1858
+ function finalizeJsonResult(value, responseFormat) {
1859
+ if (typeof responseFormat === 'object' && responseFormat?.type === 'json_schema') {
1860
+ validateJsonSchema(value, responseFormat.schema);
1861
+ }
1862
+ return value;
1863
+ }
1755
1864
 
1756
1865
  /**
1757
1866
  * Default timeout values (in milliseconds) for external service calls.
@@ -2023,11 +2132,19 @@ const DEFAULT_RETRY_CONFIG = {
2023
2132
  * @throws The last error encountered if all retry attempts fail or if a non-retryable error occurs
2024
2133
  */
2025
2134
  async function withRetry(fn, config = {}, label = 'unknown') {
2026
- const { maxRetries, baseDelayMs, maxDelayMs, retryableErrors, circuitBreaker } = {
2135
+ const { maxRetries, baseDelayMs, maxDelayMs, retryableErrors, circuitBreaker, signal } = {
2027
2136
  ...DEFAULT_RETRY_CONFIG,
2028
2137
  ...config,
2029
2138
  };
2030
2139
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
2140
+ // Short-circuit on cancellation BEFORE starting a fresh attempt so
2141
+ // the caller does not pay the cost of issuing a request whose result
2142
+ // they have already discarded.
2143
+ if (signal?.aborted) {
2144
+ throw signal.reason instanceof Error
2145
+ ? signal.reason
2146
+ : new Error('Aborted');
2147
+ }
2031
2148
  try {
2032
2149
  // If a circuit breaker is provided, execute through it
2033
2150
  const result = circuitBreaker
@@ -2040,6 +2157,13 @@ async function withRetry(fn, config = {}, label = 'unknown') {
2040
2157
  if (error instanceof CircuitBreakerOpenError) {
2041
2158
  throw error;
2042
2159
  }
2160
+ // Do not retry on cooperative cancellation — the caller has
2161
+ // explicitly asked us to stop.
2162
+ if (signal?.aborted) {
2163
+ throw signal.reason instanceof Error
2164
+ ? signal.reason
2165
+ : new Error('Aborted');
2166
+ }
2043
2167
  // Don't retry if we've exhausted all attempts
2044
2168
  if (attempt === maxRetries) {
2045
2169
  throw error;
@@ -2053,7 +2177,26 @@ async function withRetry(fn, config = {}, label = 'unknown') {
2053
2177
  const jitter = Math.random() * 1000;
2054
2178
  const delay = Math.min(exponentialDelay + jitter, maxDelayMs);
2055
2179
  getLumicLogger().warn(`[${label}] Attempt ${attempt + 1} failed, retrying in ${Math.round(delay)}ms`);
2056
- await new Promise((resolve) => setTimeout(resolve, delay));
2180
+ // Sleep but bail early if signal aborts mid-backoff.
2181
+ await new Promise((resolve, reject) => {
2182
+ const timer = setTimeout(() => {
2183
+ if (signal)
2184
+ signal.removeEventListener('abort', onAbort);
2185
+ resolve();
2186
+ }, delay);
2187
+ const onAbort = () => {
2188
+ clearTimeout(timer);
2189
+ reject(signal?.reason instanceof Error ? signal.reason : new Error('Aborted'));
2190
+ };
2191
+ if (signal) {
2192
+ if (signal.aborted) {
2193
+ clearTimeout(timer);
2194
+ reject(signal.reason instanceof Error ? signal.reason : new Error('Aborted'));
2195
+ return;
2196
+ }
2197
+ signal.addEventListener('abort', onAbort, { once: true });
2198
+ }
2199
+ });
2057
2200
  }
2058
2201
  }
2059
2202
  // This should never be reached, but TypeScript needs it for type safety
@@ -2340,15 +2483,13 @@ const isRetryableLLMError = (error) => {
2340
2483
  if (error instanceof Error) {
2341
2484
  const message = error.message;
2342
2485
  // Retry on rate limits (429)
2343
- if (message.includes("429") ||
2344
- message.includes("rate limit") ||
2345
- message.includes("Rate limit")) {
2486
+ if (message.includes('429') || message.includes('rate limit') || message.includes('Rate limit')) {
2346
2487
  return true;
2347
2488
  }
2348
2489
  // Retry on transient body-corruption 400s. Match the exact OpenAI error
2349
2490
  // string to avoid retrying genuine client-side validation 400s (which
2350
2491
  // would re-fail forever and waste retry budget).
2351
- if (message.includes("could not parse the JSON body of your request")) {
2492
+ if (message.includes('could not parse the JSON body of your request')) {
2352
2493
  return true;
2353
2494
  }
2354
2495
  }
@@ -2417,26 +2558,23 @@ function isReasoningModel(model) {
2417
2558
  * @throws Error if the response format is invalid or incompatible.
2418
2559
  */
2419
2560
  function getResponseFormatOption(responseFormat, normalizedModel) {
2420
- if (responseFormat === "text") {
2421
- return { type: "text" };
2561
+ if (responseFormat === 'text') {
2562
+ return { type: 'text' };
2422
2563
  }
2423
- if (responseFormat === "json") {
2424
- return supportsJsonMode(normalizedModel)
2425
- ? { type: "json_object" }
2426
- : { type: "text" };
2564
+ if (responseFormat === 'json') {
2565
+ return supportsJsonMode(normalizedModel) ? { type: 'json_object' } : { type: 'text' };
2427
2566
  }
2428
- if (typeof responseFormat === "object" &&
2429
- responseFormat.type === "json_schema") {
2567
+ if (typeof responseFormat === 'object' && responseFormat.type === 'json_schema') {
2430
2568
  if (!isStructuredOutputCompatibleModel(normalizedModel)) {
2431
2569
  throw new Error(`${normalizedModel} is not compatible with structured outputs / json object.`);
2432
2570
  }
2433
2571
  return {
2434
- type: "json_schema",
2572
+ type: 'json_schema',
2435
2573
  json_schema: {
2436
- type: "object",
2574
+ type: 'object',
2437
2575
  properties: responseFormat.schema.properties,
2438
2576
  required: responseFormat.schema.required || [],
2439
- name: "Response",
2577
+ name: 'Response',
2440
2578
  },
2441
2579
  };
2442
2580
  }
@@ -2495,7 +2633,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2495
2633
  const normalizedModel = normalizeModelName(options.model || DEFAULT_MODEL);
2496
2634
  const apiKey = options.apiKey || getSecrets().openai.apiKey;
2497
2635
  if (!apiKey) {
2498
- throw new Error("OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set");
2636
+ throw new Error('OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set');
2499
2637
  }
2500
2638
  const openai = new OpenAI({
2501
2639
  apiKey: apiKey,
@@ -2505,7 +2643,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2505
2643
  // Add developer prompt if present
2506
2644
  if (options.developerPrompt && supportsDeveloperPrompt(normalizedModel)) {
2507
2645
  messages.push({
2508
- role: "developer",
2646
+ role: 'developer',
2509
2647
  content: options.developerPrompt,
2510
2648
  });
2511
2649
  }
@@ -2514,15 +2652,15 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2514
2652
  messages.push(...options.context);
2515
2653
  }
2516
2654
  // Add user content
2517
- if (typeof content === "string") {
2655
+ if (typeof content === 'string') {
2518
2656
  messages.push({
2519
- role: "user",
2657
+ role: 'user',
2520
2658
  content,
2521
2659
  });
2522
2660
  }
2523
2661
  else {
2524
2662
  messages.push({
2525
- role: "user",
2663
+ role: 'user',
2526
2664
  content,
2527
2665
  });
2528
2666
  }
@@ -2536,8 +2674,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2536
2674
  queryOptions.tools = options.tools;
2537
2675
  }
2538
2676
  // Only include temperature if the model supports it
2539
- if (options.temperature !== undefined &&
2540
- supportsTemperature(normalizedModel)) {
2677
+ if (options.temperature !== undefined && supportsTemperature(normalizedModel)) {
2541
2678
  queryOptions.temperature = options.temperature;
2542
2679
  }
2543
2680
  // Only include max_completion_tokens when specified
@@ -2547,16 +2684,25 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2547
2684
  // Only set response_format when it's not the default 'text' type
2548
2685
  // (sending response_format: {type: 'text'} is redundant and may cause issues)
2549
2686
  const responseFormatOption = getResponseFormatOption(responseFormat, normalizedModel);
2550
- if (responseFormatOption.type !== "text") {
2687
+ if (responseFormatOption.type !== 'text') {
2551
2688
  queryOptions.response_format = responseFormatOption;
2552
2689
  }
2553
2690
  let completion;
2554
2691
  try {
2555
- completion = await withRetry(() => openai.chat.completions.create(queryOptions), {
2692
+ completion = await withRetry(
2693
+ // Pass the AbortSignal into the OpenAI SDK request-options object.
2694
+ // The OpenAI SDK honours `signal` to cancel the in-flight fetch
2695
+ // immediately when it fires — this is what makes caller-driven
2696
+ // cancel-and-restart (e.g. signal-monitoring's materiality
2697
+ // re-trigger path in the engine) actually release the HTTP
2698
+ // connection and response-body buffer rather than just discarding
2699
+ // the resolved value at the engine boundary.
2700
+ () => openai.chat.completions.create(queryOptions, options.signal ? { signal: options.signal } : undefined), {
2556
2701
  maxRetries: 3,
2557
2702
  baseDelayMs: 2000,
2558
2703
  maxDelayMs: 30000,
2559
2704
  retryableErrors: isRetryableLLMError,
2705
+ signal: options.signal,
2560
2706
  }, `OpenAI:${normalizedModel}`);
2561
2707
  }
2562
2708
  catch (error) {
@@ -2568,19 +2714,15 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2568
2714
  // for production prompts containing sensitive context.
2569
2715
  const errorMessage = error instanceof Error ? error.message : String(error);
2570
2716
  const totalContentChars = messages.reduce((sum, msg) => {
2571
- if (typeof msg.content === "string")
2717
+ if (typeof msg.content === 'string')
2572
2718
  return sum + msg.content.length;
2573
2719
  if (Array.isArray(msg.content)) {
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));
2720
+ return sum + msg.content.reduce((s, part) => {
2721
+ if (typeof part === 'object' && part !== null && 'text' in part && typeof part.text === 'string') {
2722
+ return s + part.text.length;
2723
+ }
2724
+ return s;
2725
+ }, 0);
2584
2726
  }
2585
2727
  return sum;
2586
2728
  }, 0);
@@ -2607,7 +2749,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2607
2749
  const cachedTokens = completion.usage?.prompt_tokens_details?.cached_tokens ?? 0;
2608
2750
  const response = {
2609
2751
  id: completion.id,
2610
- content: completion.choices[0]?.message?.content || "",
2752
+ content: completion.choices[0]?.message?.content || '',
2611
2753
  tool_calls: completion.choices[0]?.message?.tool_calls,
2612
2754
  usage: {
2613
2755
  prompt_tokens: completion.usage?.prompt_tokens ?? 0,
@@ -2617,7 +2759,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2617
2759
  },
2618
2760
  system_fingerprint: completion.system_fingerprint,
2619
2761
  service_tier: options.service_tier,
2620
- provider: "openai",
2762
+ provider: 'openai',
2621
2763
  model: normalizedModel,
2622
2764
  };
2623
2765
  return response;
@@ -2630,7 +2772,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2630
2772
  * @param options The options for the LLM call. Defaults to an empty object.
2631
2773
  * @return A promise that resolves to the response from the LLM.
2632
2774
  */
2633
- const makeOpenAIChatCompletionCall = async (content, responseFormat = "text", options = {}) => {
2775
+ const makeOpenAIChatCompletionCall = async (content, responseFormat = 'text', options = {}) => {
2634
2776
  const mergedOptions = {
2635
2777
  ...DEFAULT_OPTIONS$1,
2636
2778
  ...options,
@@ -2639,7 +2781,7 @@ const makeOpenAIChatCompletionCall = async (content, responseFormat = "text", op
2639
2781
  // Track cost in the global cost tracker. Pass cached tokens through so the
2640
2782
  // tracker applies the discounted cached-input rate (typically ~50% of the
2641
2783
  // standard input rate) instead of billing every input token at full price.
2642
- getLLMCostTracker().trackUsage("openai", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens);
2784
+ getLLMCostTracker().trackUsage('openai', completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens);
2643
2785
  // Handle tool calls differently
2644
2786
  if (completion.tool_calls && completion.tool_calls.length > 0) {
2645
2787
  const toolCallResponse = {
@@ -2655,10 +2797,10 @@ const makeOpenAIChatCompletionCall = async (content, responseFormat = "text", op
2655
2797
  prompt_tokens: completion.usage.prompt_tokens,
2656
2798
  completion_tokens: completion.usage.completion_tokens,
2657
2799
  reasoning_tokens: 0,
2658
- provider: "openai",
2800
+ provider: 'openai',
2659
2801
  model: completion.model,
2660
2802
  cached_tokens: completion.usage.cached_tokens,
2661
- cost: calculateCost("openai", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
2803
+ cost: calculateCost('openai', completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
2662
2804
  },
2663
2805
  tool_calls: completion.tool_calls,
2664
2806
  };
@@ -2666,7 +2808,7 @@ const makeOpenAIChatCompletionCall = async (content, responseFormat = "text", op
2666
2808
  // Handle regular responses
2667
2809
  const parsedResponse = await parseResponse(completion.content, responseFormat);
2668
2810
  if (parsedResponse === null) {
2669
- throw new Error("Failed to parse response");
2811
+ throw new Error('Failed to parse response');
2670
2812
  }
2671
2813
  return {
2672
2814
  response: parsedResponse,
@@ -2674,10 +2816,10 @@ const makeOpenAIChatCompletionCall = async (content, responseFormat = "text", op
2674
2816
  prompt_tokens: completion.usage.prompt_tokens,
2675
2817
  completion_tokens: completion.usage.completion_tokens,
2676
2818
  reasoning_tokens: 0,
2677
- provider: "openai",
2819
+ provider: 'openai',
2678
2820
  model: completion.model,
2679
2821
  cached_tokens: completion.usage.cached_tokens,
2680
- cost: calculateCost("openai", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
2822
+ cost: calculateCost('openai', completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
2681
2823
  },
2682
2824
  tool_calls: completion.tool_calls,
2683
2825
  };
@@ -2712,44 +2854,45 @@ const makeResponsesAPICall = async (input, options = {}) => {
2712
2854
  const normalizedModel = normalizeModelName(options.model || DEFAULT_MODEL);
2713
2855
  const apiKey = options.apiKey || getSecrets().openai.apiKey;
2714
2856
  if (!apiKey) {
2715
- throw new Error("OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set");
2857
+ throw new Error('OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set');
2716
2858
  }
2717
2859
  const openai = new OpenAI({
2718
2860
  apiKey: apiKey,
2719
2861
  timeout: options.timeout ?? LLM_TIMEOUT_MS,
2720
2862
  });
2721
- // Remove apiKey, model, and timeout from options before creating request body
2722
- const { apiKey: _, model: __, timeout: ___, ...cleanOptions } = options;
2863
+ // Remove apiKey, model, timeout, and signal from options before creating request body
2864
+ const { apiKey: _, model: __, timeout: ___, signal: ____, ...cleanOptions } = options;
2723
2865
  const requestBody = {
2724
2866
  model: normalizedModel,
2725
2867
  input,
2726
2868
  ...cleanOptions,
2727
2869
  };
2728
2870
  // Make the API call to the Responses endpoint
2729
- const response = await withRetry(() => openai.responses.create(requestBody), {
2871
+ const response = await withRetry(() => openai.responses.create(requestBody, options.signal ? { signal: options.signal } : undefined), {
2730
2872
  maxRetries: 3,
2731
2873
  baseDelayMs: 2000,
2732
2874
  maxDelayMs: 30000,
2733
2875
  retryableErrors: isRetryableLLMError,
2876
+ signal: options.signal,
2734
2877
  }, `OpenAI-Responses:${normalizedModel}`);
2735
2878
  // Responses API exposes cached input tokens under `input_tokens_details.cached_tokens`
2736
2879
  // (the equivalent of Chat Completions' `prompt_tokens_details.cached_tokens`).
2737
2880
  const responsesCachedTokens = response.usage?.input_tokens_details?.cached_tokens || 0;
2738
2881
  // Track cost in the global cost tracker
2739
- getLLMCostTracker().trackUsage("openai", normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, responsesCachedTokens);
2882
+ getLLMCostTracker().trackUsage('openai', normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, responsesCachedTokens);
2740
2883
  // Extract tool calls from the output
2741
2884
  const toolCalls = response.output
2742
- ?.filter((item) => item.type === "function_call")
2885
+ ?.filter((item) => item.type === 'function_call')
2743
2886
  .map((toolCall) => ({
2744
2887
  id: toolCall.call_id,
2745
- type: "function",
2888
+ type: 'function',
2746
2889
  function: {
2747
2890
  name: toolCall.name,
2748
2891
  arguments: toolCall.arguments,
2749
2892
  },
2750
2893
  }));
2751
2894
  // Extract code interpreter outputs if present
2752
- const codeInterpreterCalls = response.output?.filter((item) => item.type === "code_interpreter_call");
2895
+ const codeInterpreterCalls = response.output?.filter((item) => item.type === 'code_interpreter_call');
2753
2896
  let codeInterpreterOutputs = undefined;
2754
2897
  if (codeInterpreterCalls && codeInterpreterCalls.length > 0) {
2755
2898
  // Each code_interpreter_call has an 'outputs' array property
@@ -2775,34 +2918,32 @@ const makeResponsesAPICall = async (input, options = {}) => {
2775
2918
  prompt_tokens: response.usage?.input_tokens || 0,
2776
2919
  completion_tokens: response.usage?.output_tokens || 0,
2777
2920
  reasoning_tokens: response.usage?.output_tokens_details?.reasoning_tokens || 0,
2778
- provider: "openai",
2921
+ provider: 'openai',
2779
2922
  model: normalizedModel,
2780
2923
  cached_tokens: responsesCachedTokens,
2781
- cost: calculateCost("openai", normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, responsesCachedTokens),
2924
+ cost: calculateCost('openai', normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, responsesCachedTokens),
2782
2925
  },
2783
2926
  tool_calls: toolCalls,
2784
- ...(codeInterpreterOutputs
2785
- ? { code_interpreter_outputs: codeInterpreterOutputs }
2786
- : {}),
2927
+ ...(codeInterpreterOutputs ? { code_interpreter_outputs: codeInterpreterOutputs } : {}),
2787
2928
  };
2788
2929
  }
2789
2930
  // Extract text content from the response output
2790
2931
  const textContent = response.output
2791
- ?.filter((item) => item.type === "message")
2932
+ ?.filter((item) => item.type === 'message')
2792
2933
  .map((item) => item.content
2793
- .filter((content) => content.type === "output_text")
2934
+ .filter((content) => content.type === 'output_text')
2794
2935
  .map((content) => content.text)
2795
- .join(""))
2796
- .join("") || "";
2936
+ .join(''))
2937
+ .join('') || '';
2797
2938
  // Determine the format for parsing the response
2798
- let parsingFormat = "text";
2799
- if (requestBody.text?.format?.type === "json_object") {
2800
- parsingFormat = "json";
2939
+ let parsingFormat = 'text';
2940
+ if (requestBody.text?.format?.type === 'json_object') {
2941
+ parsingFormat = 'json';
2801
2942
  }
2802
2943
  // Handle regular responses
2803
2944
  const parsedResponse = await parseResponse(textContent, parsingFormat);
2804
2945
  if (parsedResponse === null) {
2805
- throw new Error("Failed to parse response from Responses API");
2946
+ throw new Error('Failed to parse response from Responses API');
2806
2947
  }
2807
2948
  return {
2808
2949
  response: parsedResponse,
@@ -2810,15 +2951,13 @@ const makeResponsesAPICall = async (input, options = {}) => {
2810
2951
  prompt_tokens: response.usage?.input_tokens || 0,
2811
2952
  completion_tokens: response.usage?.output_tokens || 0,
2812
2953
  reasoning_tokens: response.usage?.output_tokens_details?.reasoning_tokens || 0,
2813
- provider: "openai",
2954
+ provider: 'openai',
2814
2955
  model: normalizedModel,
2815
2956
  cached_tokens: responsesCachedTokens,
2816
- cost: calculateCost("openai", normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, responsesCachedTokens),
2957
+ cost: calculateCost('openai', normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, responsesCachedTokens),
2817
2958
  },
2818
2959
  tool_calls: toolCalls,
2819
- ...(codeInterpreterOutputs
2820
- ? { code_interpreter_outputs: codeInterpreterOutputs }
2821
- : {}),
2960
+ ...(codeInterpreterOutputs ? { code_interpreter_outputs: codeInterpreterOutputs } : {}),
2822
2961
  };
2823
2962
  };
2824
2963
 
@@ -8128,12 +8267,12 @@ function sanitizeObject(obj, sensitiveFields = ['apiKey', 'token', 'secret', 'pa
8128
8267
  const isRetryableAnthropicError = (error) => {
8129
8268
  if (error instanceof Error) {
8130
8269
  const message = error.message;
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")) {
8270
+ if (message.includes('429') ||
8271
+ message.includes('500') ||
8272
+ message.includes('503') ||
8273
+ message.includes('529') ||
8274
+ message.includes('rate limit') ||
8275
+ message.includes('overloaded')) {
8137
8276
  return true;
8138
8277
  }
8139
8278
  }
@@ -8148,11 +8287,8 @@ const isRetryableAnthropicError = (error) => {
8148
8287
  function translateToolsToAnthropic(tools) {
8149
8288
  return tools.map((tool) => ({
8150
8289
  name: tool.function.name,
8151
- description: tool.function.description || "",
8152
- input_schema: (tool.function.parameters || {
8153
- type: "object",
8154
- properties: {},
8155
- }),
8290
+ description: tool.function.description || '',
8291
+ input_schema: (tool.function.parameters || { type: 'object', properties: {} }),
8156
8292
  }));
8157
8293
  }
8158
8294
  /**
@@ -8165,31 +8301,27 @@ function translateContextToAnthropic(context) {
8165
8301
  const systemParts = [];
8166
8302
  const messages = [];
8167
8303
  for (const msg of context) {
8168
- if (msg.role === "system" || msg.role === "developer") {
8169
- if (typeof msg.content === "string") {
8304
+ if (msg.role === 'system' || msg.role === 'developer') {
8305
+ if (typeof msg.content === 'string') {
8170
8306
  systemParts.push(msg.content);
8171
8307
  }
8172
8308
  continue;
8173
8309
  }
8174
- if (msg.role === "user") {
8175
- const content = typeof msg.content === "string"
8310
+ if (msg.role === 'user') {
8311
+ const content = typeof msg.content === 'string'
8176
8312
  ? msg.content
8177
8313
  : JSON.stringify(msg.content);
8178
- messages.push({ role: "user", content });
8314
+ messages.push({ role: 'user', content });
8179
8315
  continue;
8180
8316
  }
8181
- if (msg.role === "assistant") {
8317
+ if (msg.role === 'assistant') {
8182
8318
  const assistantMsg = msg;
8183
8319
  // If the assistant message has tool_calls, translate to Anthropic tool_use blocks
8184
8320
  if (assistantMsg.tool_calls && assistantMsg.tool_calls.length > 0) {
8185
8321
  const contentBlocks = [];
8186
8322
  // Include text content if present
8187
- if (typeof assistantMsg.content === "string" &&
8188
- assistantMsg.content.trim()) {
8189
- contentBlocks.push({
8190
- type: "text",
8191
- text: assistantMsg.content,
8192
- });
8323
+ if (typeof assistantMsg.content === 'string' && assistantMsg.content.trim()) {
8324
+ contentBlocks.push({ type: 'text', text: assistantMsg.content });
8193
8325
  }
8194
8326
  // Translate each tool_call to a tool_use block
8195
8327
  for (const tc of assistantMsg.tool_calls) {
@@ -8201,36 +8333,32 @@ function translateContextToAnthropic(context) {
8201
8333
  input = { raw: tc.function.arguments };
8202
8334
  }
8203
8335
  contentBlocks.push({
8204
- type: "tool_use",
8336
+ type: 'tool_use',
8205
8337
  id: tc.id,
8206
8338
  name: tc.function.name,
8207
8339
  input,
8208
8340
  });
8209
8341
  }
8210
- messages.push({ role: "assistant", content: contentBlocks });
8342
+ messages.push({ role: 'assistant', content: contentBlocks });
8211
8343
  }
8212
8344
  else {
8213
- const content = typeof assistantMsg.content === "string"
8345
+ const content = typeof assistantMsg.content === 'string'
8214
8346
  ? assistantMsg.content
8215
8347
  : JSON.stringify(assistantMsg.content);
8216
- messages.push({ role: "assistant", content });
8348
+ messages.push({ role: 'assistant', content });
8217
8349
  }
8218
8350
  continue;
8219
8351
  }
8220
- if (msg.role === "tool") {
8352
+ if (msg.role === 'tool') {
8221
8353
  // Anthropic expects tool results as user messages with tool_result content blocks
8222
8354
  const toolMsg = msg;
8223
8355
  messages.push({
8224
- role: "user",
8225
- content: [
8226
- {
8227
- type: "tool_result",
8356
+ role: 'user',
8357
+ content: [{
8358
+ type: 'tool_result',
8228
8359
  tool_use_id: toolMsg.tool_call_id,
8229
- content: typeof toolMsg.content === "string"
8230
- ? toolMsg.content
8231
- : JSON.stringify(toolMsg.content),
8232
- },
8233
- ],
8360
+ content: typeof toolMsg.content === 'string' ? toolMsg.content : JSON.stringify(toolMsg.content),
8361
+ }],
8234
8362
  });
8235
8363
  continue;
8236
8364
  }
@@ -8251,13 +8379,13 @@ function translateContextToAnthropic(context) {
8251
8379
  merged.push({ role: msg.role, content: toContentBlocks(msg.content) });
8252
8380
  }
8253
8381
  }
8254
- return { messages: merged, systemText: systemParts.join("\n\n") };
8382
+ return { messages: merged, systemText: systemParts.join('\n\n') };
8255
8383
  }
8256
8384
  /** Convert string or content block array to a uniform content block array. */
8257
8385
  function toContentBlocks(content) {
8258
- if (typeof content === "string") {
8386
+ if (typeof content === 'string') {
8259
8387
  const textBlock = {
8260
- type: "text",
8388
+ type: 'text',
8261
8389
  text: content,
8262
8390
  citations: null,
8263
8391
  };
@@ -8277,11 +8405,11 @@ function toContentBlocks(content) {
8277
8405
  * @param options The options for the LLM call.
8278
8406
  * @return A promise that resolves to the response from the Anthropic API.
8279
8407
  */
8280
- async function makeAnthropicCall(content, responseFormat = "text", options = {}) {
8281
- const model = (options.model || "claude-sonnet-4-6");
8408
+ async function makeAnthropicCall(content, responseFormat = 'text', options = {}) {
8409
+ const model = (options.model || 'claude-sonnet-4-6');
8282
8410
  const apiKey = options.apiKey || getSecrets().anthropic.apiKey;
8283
8411
  if (!apiKey) {
8284
- throw new Error("Anthropic API key is not provided and ANTHROPIC_API_KEY environment variable is not set");
8412
+ throw new Error('Anthropic API key is not provided and ANTHROPIC_API_KEY environment variable is not set');
8285
8413
  }
8286
8414
  const client = new Anthropic({
8287
8415
  apiKey,
@@ -8293,10 +8421,8 @@ async function makeAnthropicCall(content, responseFormat = "text", options = {})
8293
8421
  systemParts.push(options.developerPrompt);
8294
8422
  }
8295
8423
  // If JSON response is requested, instruct the model via system prompt
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.");
8424
+ if (responseFormat === 'json' || (typeof responseFormat === 'object' && responseFormat.type === 'json_schema')) {
8425
+ systemParts.push('You MUST respond with valid JSON only. No markdown, no code blocks, no explanatory text — just the raw JSON object or array.');
8300
8426
  }
8301
8427
  // Build messages array
8302
8428
  const messages = [];
@@ -8309,7 +8435,7 @@ async function makeAnthropicCall(content, responseFormat = "text", options = {})
8309
8435
  messages.push(...translated.messages);
8310
8436
  }
8311
8437
  // Add user content
8312
- messages.push({ role: "user", content });
8438
+ messages.push({ role: 'user', content });
8313
8439
  // Build request params
8314
8440
  const requestParams = {
8315
8441
  model,
@@ -8318,37 +8444,46 @@ async function makeAnthropicCall(content, responseFormat = "text", options = {})
8318
8444
  };
8319
8445
  // Add system prompt if present
8320
8446
  if (systemParts.length > 0) {
8321
- requestParams.system = systemParts.join("\n\n");
8322
- }
8323
- // Add temperature if specified
8324
- if (options.temperature !== undefined) {
8325
- requestParams.temperature = options.temperature;
8326
- }
8327
- if (options.top_p !== undefined) {
8328
- requestParams.top_p = options.top_p;
8329
- }
8447
+ requestParams.system = systemParts.join('\n\n');
8448
+ }
8449
+ // Sampling controls are intentionally disabled for the Anthropic provider.
8450
+ //
8451
+ // The claude-*-4.x model family rejects any request that specifies BOTH
8452
+ // `temperature` and `top_p` ("`temperature` and `top_p` cannot both be
8453
+ // specified for this model. Please use only one." — HTTP 400), which aborts
8454
+ // the entire caller flow (e.g. the engine's account decision session).
8455
+ // Callers across the org pass both knobs generically, so rather than make
8456
+ // each one provider-aware we drop both here and let Anthropic use its model
8457
+ // defaults. `options.temperature` / `options.top_p` are intentionally not
8458
+ // forwarded. This is the Anthropic adapter only — the OpenAI, DeepSeek, and
8459
+ // OpenAI-compatible adapters still honor `temperature` and `top_p`.
8330
8460
  // Translate and add tools if present
8331
8461
  if (options.tools && options.tools.length > 0) {
8332
8462
  requestParams.tools = translateToolsToAnthropic(options.tools);
8333
8463
  }
8334
8464
  try {
8335
- const response = await withRetry(() => client.messages.create(requestParams), {
8465
+ const response = await withRetry(
8466
+ // Pass AbortSignal into the Anthropic SDK request-options object
8467
+ // so caller-driven cancellation interrupts the in-flight HTTP
8468
+ // request immediately. See LLMOptions.signal JSDoc for context.
8469
+ () => client.messages.create(requestParams, options.signal ? { signal: options.signal } : undefined), {
8336
8470
  maxRetries: 3,
8337
8471
  baseDelayMs: 2000,
8338
8472
  maxDelayMs: 30000,
8339
8473
  retryableErrors: isRetryableAnthropicError,
8474
+ signal: options.signal,
8340
8475
  }, `Anthropic:${model}`);
8341
8476
  const inputTokens = response.usage.input_tokens;
8342
8477
  const outputTokens = response.usage.output_tokens;
8343
8478
  // Track cost
8344
- getLLMCostTracker().trackUsage("anthropic", model, inputTokens, outputTokens);
8479
+ getLLMCostTracker().trackUsage('anthropic', model, inputTokens, outputTokens);
8345
8480
  // Extract tool use blocks
8346
- const toolUseBlocks = response.content.filter((block) => block.type === "tool_use");
8481
+ const toolUseBlocks = response.content.filter((block) => block.type === 'tool_use');
8347
8482
  if (toolUseBlocks.length > 0) {
8348
8483
  // Translate Anthropic tool_use to OpenAI tool_calls format
8349
8484
  const toolCalls = toolUseBlocks.map((block) => ({
8350
8485
  id: block.id,
8351
- type: "function",
8486
+ type: 'function',
8352
8487
  function: {
8353
8488
  name: block.name,
8354
8489
  arguments: JSON.stringify(block.input),
@@ -8367,22 +8502,22 @@ async function makeAnthropicCall(content, responseFormat = "text", options = {})
8367
8502
  prompt_tokens: inputTokens,
8368
8503
  completion_tokens: outputTokens,
8369
8504
  reasoning_tokens: 0,
8370
- provider: "anthropic",
8505
+ provider: 'anthropic',
8371
8506
  model,
8372
- cost: calculateCost("anthropic", model, inputTokens, outputTokens),
8507
+ cost: calculateCost('anthropic', model, inputTokens, outputTokens),
8373
8508
  },
8374
8509
  tool_calls: toolCalls,
8375
8510
  };
8376
8511
  }
8377
8512
  // Extract text content
8378
8513
  const textContent = response.content
8379
- .filter((block) => block.type === "text")
8514
+ .filter((block) => block.type === 'text')
8380
8515
  .map((block) => block.text)
8381
- .join("");
8516
+ .join('');
8382
8517
  // Parse response
8383
8518
  const parsedResponse = await parseResponse(textContent, responseFormat);
8384
8519
  if (parsedResponse === null) {
8385
- throw new Error("Failed to parse Anthropic response");
8520
+ throw new Error('Failed to parse Anthropic response');
8386
8521
  }
8387
8522
  return {
8388
8523
  response: parsedResponse,
@@ -8390,9 +8525,9 @@ async function makeAnthropicCall(content, responseFormat = "text", options = {})
8390
8525
  prompt_tokens: inputTokens,
8391
8526
  completion_tokens: outputTokens,
8392
8527
  reasoning_tokens: 0,
8393
- provider: "anthropic",
8528
+ provider: 'anthropic',
8394
8529
  model,
8395
- cost: calculateCost("anthropic", model, inputTokens, outputTokens),
8530
+ cost: calculateCost('anthropic', model, inputTokens, outputTokens),
8396
8531
  },
8397
8532
  };
8398
8533
  }
@@ -8414,9 +8549,7 @@ async function makeAnthropicCall(content, responseFormat = "text", options = {})
8414
8549
  const isRetryableError = (error) => {
8415
8550
  if (error instanceof Error) {
8416
8551
  const message = error.message;
8417
- if (message.includes("429") ||
8418
- message.includes("rate limit") ||
8419
- message.includes("Rate limit")) {
8552
+ if (message.includes('429') || message.includes('rate limit') || message.includes('Rate limit')) {
8420
8553
  return true;
8421
8554
  }
8422
8555
  }
@@ -8430,16 +8563,14 @@ function resolveApiKey(provider, optionsApiKey) {
8430
8563
  if (optionsApiKey)
8431
8564
  return optionsApiKey;
8432
8565
  const secrets = getSecrets();
8433
- const pathParts = provider.apiKeySecretPath.split(".");
8566
+ const pathParts = provider.apiKeySecretPath.split('.');
8434
8567
  let current = secrets;
8435
8568
  for (const part of pathParts) {
8436
- if (current === null ||
8437
- current === undefined ||
8438
- typeof current !== "object")
8439
- return "";
8569
+ if (current === null || current === undefined || typeof current !== 'object')
8570
+ return '';
8440
8571
  current = current[part];
8441
8572
  }
8442
- return (typeof current === "string" ? current : "") || "";
8573
+ return (typeof current === 'string' ? current : '') || '';
8443
8574
  }
8444
8575
  /**
8445
8576
  * Makes a call to an OpenAI-compatible provider (Kimi, Qwen, or any future provider).
@@ -8453,12 +8584,12 @@ function resolveApiKey(provider, optionsApiKey) {
8453
8584
  * @param providerName The provider key in OPENAI_COMPATIBLE_PROVIDERS.
8454
8585
  * @return A promise that resolves to the response from the provider.
8455
8586
  */
8456
- async function makeOpenAICompatibleCall(content, responseFormat = "text", options = {}, providerName) {
8587
+ async function makeOpenAICompatibleCall(content, responseFormat = 'text', options = {}, providerName) {
8457
8588
  const providerConfig = OPENAI_COMPATIBLE_PROVIDERS[providerName];
8458
8589
  if (!providerConfig) {
8459
8590
  throw new Error(`Unknown OpenAI-compatible provider: ${providerName}`);
8460
8591
  }
8461
- const model = normalizeModelName(options.model || "");
8592
+ const model = normalizeModelName(options.model || '');
8462
8593
  const apiKey = resolveApiKey(providerConfig, options.apiKey);
8463
8594
  if (!apiKey) {
8464
8595
  throw new Error(`${providerConfig.name} API key is not provided and ${providerConfig.apiKeyEnvVar} environment variable is not set`);
@@ -8471,36 +8602,31 @@ async function makeOpenAICompatibleCall(content, responseFormat = "text", option
8471
8602
  const messages = [];
8472
8603
  // Add system message with developer prompt if present
8473
8604
  if (options.developerPrompt) {
8474
- messages.push({ role: "system", content: options.developerPrompt });
8605
+ messages.push({ role: 'system', content: options.developerPrompt });
8475
8606
  }
8476
8607
  // Add context if present
8477
8608
  if (options.context) {
8478
8609
  messages.push(...options.context);
8479
8610
  }
8480
8611
  // Add user content
8481
- if (typeof content === "string") {
8482
- messages.push({ role: "user", content });
8612
+ if (typeof content === 'string') {
8613
+ messages.push({ role: 'user', content });
8483
8614
  }
8484
8615
  else {
8485
- messages.push({ role: "user", content });
8616
+ messages.push({ role: 'user', content });
8486
8617
  }
8487
8618
  // Determine if the model supports JSON mode
8488
8619
  const supportsJson = isValidModel(model) && getModelCapabilities(model).supportsJson;
8489
8620
  // If JSON response format, add hint to system prompt
8490
- if ((responseFormat === "json" ||
8491
- (typeof responseFormat === "object" &&
8492
- responseFormat.type === "json_schema")) &&
8621
+ if ((responseFormat === 'json' || (typeof responseFormat === 'object' && responseFormat.type === 'json_schema')) &&
8493
8622
  supportsJson) {
8494
- if (!messages.some((m) => m.role === "system")) {
8495
- messages.unshift({
8496
- role: "system",
8497
- content: "Please respond in valid JSON format.",
8498
- });
8623
+ if (!messages.some((m) => m.role === 'system')) {
8624
+ messages.unshift({ role: 'system', content: 'Please respond in valid JSON format.' });
8499
8625
  }
8500
8626
  else {
8501
- const systemMsgIndex = messages.findIndex((m) => m.role === "system");
8627
+ const systemMsgIndex = messages.findIndex((m) => m.role === 'system');
8502
8628
  const systemMsg = messages[systemMsgIndex];
8503
- if (typeof systemMsg.content === "string") {
8629
+ if (typeof systemMsg.content === 'string') {
8504
8630
  messages[systemMsgIndex] = {
8505
8631
  ...systemMsg,
8506
8632
  content: `${systemMsg.content} Please respond in valid JSON format.`,
@@ -8513,8 +8639,8 @@ async function makeOpenAICompatibleCall(content, responseFormat = "text", option
8513
8639
  messages,
8514
8640
  };
8515
8641
  // Add response format if JSON is supported
8516
- if (responseFormat !== "text" && supportsJson) {
8517
- queryOptions.response_format = { type: "json_object" };
8642
+ if (responseFormat !== 'text' && supportsJson) {
8643
+ queryOptions.response_format = { type: 'json_object' };
8518
8644
  }
8519
8645
  // Temperature
8520
8646
  if (options.temperature !== undefined) {
@@ -8538,11 +8664,16 @@ async function makeOpenAICompatibleCall(content, responseFormat = "text", option
8538
8664
  queryOptions.tools = options.tools;
8539
8665
  }
8540
8666
  try {
8541
- const completion = await withRetry(() => openai.chat.completions.create(queryOptions), {
8667
+ const completion = await withRetry(
8668
+ // Pass AbortSignal into the SDK request-options object so
8669
+ // caller-driven cancellation interrupts the in-flight HTTP
8670
+ // request immediately. See LLMOptions.signal JSDoc for context.
8671
+ () => openai.chat.completions.create(queryOptions, options.signal ? { signal: options.signal } : undefined), {
8542
8672
  maxRetries: 3,
8543
8673
  baseDelayMs: 2000,
8544
8674
  maxDelayMs: 30000,
8545
8675
  retryableErrors: isRetryableError,
8676
+ signal: options.signal,
8546
8677
  }, `${providerConfig.name}:${model}`);
8547
8678
  const promptTokens = completion.usage?.prompt_tokens || 0;
8548
8679
  const completionTokens = completion.usage?.completion_tokens || 0;
@@ -8572,7 +8703,7 @@ async function makeOpenAICompatibleCall(content, responseFormat = "text", option
8572
8703
  };
8573
8704
  }
8574
8705
  // Handle regular responses
8575
- const textContent = completion.choices[0]?.message?.content || "";
8706
+ const textContent = completion.choices[0]?.message?.content || '';
8576
8707
  const parsedResponse = await parseResponse(textContent, responseFormat);
8577
8708
  if (parsedResponse === null) {
8578
8709
  throw new Error(`Failed to parse ${providerConfig.name} response`);
@@ -8864,9 +8995,7 @@ const isRetryableDeepseekError = (error) => {
8864
8995
  if (error instanceof Error) {
8865
8996
  const message = error.message;
8866
8997
  // Retry only on rate limits (429)
8867
- if (message.includes("429") ||
8868
- message.includes("rate limit") ||
8869
- message.includes("Rate limit")) {
8998
+ if (message.includes('429') || message.includes('rate limit') || message.includes('Rate limit')) {
8870
8999
  return true;
8871
9000
  }
8872
9001
  }
@@ -8876,7 +9005,7 @@ const isRetryableDeepseekError = (error) => {
8876
9005
  * Default options for Deepseek API calls
8877
9006
  */
8878
9007
  const DEFAULT_DEEPSEEK_OPTIONS = {
8879
- defaultModel: "deepseek-chat",
9008
+ defaultModel: 'deepseek-chat',
8880
9009
  };
8881
9010
  /**
8882
9011
  * Checks if the given model is a supported Deepseek model
@@ -8888,7 +9017,7 @@ const isSupportedDeepseekModel = (model) => {
8888
9017
  return false;
8889
9018
  }
8890
9019
  const capabilities = getModelCapabilities(model);
8891
- return capabilities.provider === "deepseek";
9020
+ return capabilities.provider === 'deepseek';
8892
9021
  };
8893
9022
  /**
8894
9023
  * Checks if the given Deepseek model supports JSON output format
@@ -8921,19 +9050,17 @@ const supportsToolCalling = (model) => {
8921
9050
  */
8922
9051
  function getDeepseekResponseFormatOption(responseFormat, model) {
8923
9052
  // Check if the model supports JSON output
8924
- if (responseFormat !== "text" && !supportsJsonOutput(model)) {
9053
+ if (responseFormat !== 'text' && !supportsJsonOutput(model)) {
8925
9054
  getLumicLogger().warn(`Model ${model} does not support JSON output. Using text format instead.`);
8926
- return { type: "text" };
9055
+ return { type: 'text' };
8927
9056
  }
8928
- if (responseFormat === "text") {
8929
- return { type: "text" };
9057
+ if (responseFormat === 'text') {
9058
+ return { type: 'text' };
8930
9059
  }
8931
- if (responseFormat === "json" ||
8932
- (typeof responseFormat === "object" &&
8933
- responseFormat.type === "json_schema")) {
8934
- return { type: "json_object" };
9060
+ if (responseFormat === 'json' || (typeof responseFormat === 'object' && responseFormat.type === 'json_schema')) {
9061
+ return { type: 'json_object' };
8935
9062
  }
8936
- return { type: "text" };
9063
+ return { type: 'text' };
8937
9064
  }
8938
9065
  /**
8939
9066
  * Creates a completion using the Deepseek API
@@ -8952,26 +9079,24 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
8952
9079
  throw new Error(`Unsupported Deepseek model: ${normalizedModel}. Please use 'deepseek-chat' or 'deepseek-reasoner'.`);
8953
9080
  }
8954
9081
  // Check if tools are requested with a model that doesn't support them
8955
- if (options.tools &&
8956
- options.tools.length > 0 &&
8957
- !supportsToolCalling(normalizedModel)) {
9082
+ if (options.tools && options.tools.length > 0 && !supportsToolCalling(normalizedModel)) {
8958
9083
  throw new Error(`Model ${normalizedModel} does not support tool calling.`);
8959
9084
  }
8960
9085
  const apiKey = options.apiKey || getSecrets().deepseek.apiKey;
8961
9086
  if (!apiKey) {
8962
- throw new Error("Deepseek API key is not provided and DEEPSEEK_API_KEY environment variable is not set");
9087
+ throw new Error('Deepseek API key is not provided and DEEPSEEK_API_KEY environment variable is not set');
8963
9088
  }
8964
9089
  // Initialize OpenAI client with Deepseek API URL
8965
9090
  const openai = new OpenAI({
8966
9091
  apiKey,
8967
- baseURL: "https://api.deepseek.com",
9092
+ baseURL: 'https://api.deepseek.com',
8968
9093
  timeout: options.timeout ?? LLM_TIMEOUT_MS,
8969
9094
  });
8970
9095
  const messages = [];
8971
9096
  // Add system message with developer prompt if present
8972
9097
  if (options.developerPrompt) {
8973
9098
  messages.push({
8974
- role: "system",
9099
+ role: 'system',
8975
9100
  content: options.developerPrompt,
8976
9101
  });
8977
9102
  }
@@ -8980,35 +9105,33 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
8980
9105
  messages.push(...options.context);
8981
9106
  }
8982
9107
  // Add user content
8983
- if (typeof content === "string") {
9108
+ if (typeof content === 'string') {
8984
9109
  messages.push({
8985
- role: "user",
9110
+ role: 'user',
8986
9111
  content,
8987
9112
  });
8988
9113
  }
8989
9114
  else {
8990
9115
  messages.push({
8991
- role: "user",
9116
+ role: 'user',
8992
9117
  content,
8993
9118
  });
8994
9119
  }
8995
9120
  // If JSON response format, include a hint in the system prompt
8996
- if ((responseFormat === "json" ||
8997
- (typeof responseFormat === "object" &&
8998
- responseFormat.type === "json_schema")) &&
8999
- supportsJsonOutput(normalizedModel)) {
9121
+ if ((responseFormat === 'json' || (typeof responseFormat === 'object' && responseFormat.type === 'json_schema'))
9122
+ && supportsJsonOutput(normalizedModel)) {
9000
9123
  // If there's no system message yet, add one
9001
- if (!messages.some((m) => m.role === "system")) {
9124
+ if (!messages.some(m => m.role === 'system')) {
9002
9125
  messages.unshift({
9003
- role: "system",
9004
- content: "Please respond in valid JSON format.",
9126
+ role: 'system',
9127
+ content: 'Please respond in valid JSON format.',
9005
9128
  });
9006
9129
  }
9007
9130
  else {
9008
9131
  // Append to existing system message
9009
- const systemMsgIndex = messages.findIndex((m) => m.role === "system");
9132
+ const systemMsgIndex = messages.findIndex(m => m.role === 'system');
9010
9133
  const systemMsg = messages[systemMsgIndex];
9011
- if (typeof systemMsg.content === "string") {
9134
+ if (typeof systemMsg.content === 'string') {
9012
9135
  messages[systemMsgIndex] = {
9013
9136
  ...systemMsg,
9014
9137
  content: `${systemMsg.content} Please respond in valid JSON format.`,
@@ -9049,7 +9172,7 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
9049
9172
  0;
9050
9173
  return {
9051
9174
  id: completion.id,
9052
- content: completion.choices[0]?.message?.content || "",
9175
+ content: completion.choices[0]?.message?.content || '',
9053
9176
  tool_calls: completion.choices[0]?.message?.tool_calls,
9054
9177
  usage: {
9055
9178
  prompt_tokens: completion.usage?.prompt_tokens ?? 0,
@@ -9058,7 +9181,7 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
9058
9181
  cached_tokens: cachedTokens,
9059
9182
  },
9060
9183
  system_fingerprint: completion.system_fingerprint,
9061
- provider: "deepseek",
9184
+ provider: 'deepseek',
9062
9185
  model: normalizedModel,
9063
9186
  };
9064
9187
  }
@@ -9074,8 +9197,12 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
9074
9197
  * @param responseFormat The format of the response. Defaults to 'json'.
9075
9198
  * @param options Configuration options including model ('deepseek-chat' or 'deepseek-reasoner'), tools, and apiKey.
9076
9199
  * @return A promise that resolves to the response from the Deepseek API.
9200
+ * @throws {Error} If the requested capability (JSON output / tool calling) is
9201
+ * unsupported by the model, or if the underlying API call fails. This matches
9202
+ * the OpenAI and Anthropic providers, which surface failures by throwing
9203
+ * rather than returning a success-shaped response carrying an error payload.
9077
9204
  */
9078
- const makeDeepseekCall = async (content, responseFormat = "json", options = {}) => {
9205
+ const makeDeepseekCall = async (content, responseFormat = 'json', options = {}) => {
9079
9206
  // Set default model if not provided
9080
9207
  const mergedOptions = {
9081
9208
  ...options,
@@ -9084,51 +9211,21 @@ const makeDeepseekCall = async (content, responseFormat = "json", options = {})
9084
9211
  mergedOptions.model = DEFAULT_DEEPSEEK_OPTIONS.defaultModel;
9085
9212
  }
9086
9213
  const modelName = normalizeModelName(mergedOptions.model);
9087
- // Check if the requested response format is compatible with the model
9088
- if (responseFormat !== "text" && !supportsJsonOutput(modelName)) {
9089
- getLumicLogger().warn(`Model ${modelName} does not support JSON output. Will return error in the response.`);
9090
- return {
9091
- response: {
9092
- error: `Model ${modelName} does not support JSON output format.`,
9093
- },
9094
- usage: {
9095
- prompt_tokens: 0,
9096
- completion_tokens: 0,
9097
- reasoning_tokens: 0,
9098
- provider: "deepseek",
9099
- model: modelName,
9100
- cached_tokens: 0,
9101
- cost: 0,
9102
- },
9103
- tool_calls: undefined,
9104
- };
9214
+ // Check if the requested response format is compatible with the model.
9215
+ // Throw rather than returning a fake-success LLMResponse: a caller that
9216
+ // asked for JSON must not silently receive an { error } payload typed as T.
9217
+ if (responseFormat !== 'text' && !supportsJsonOutput(modelName)) {
9218
+ throw new Error(`Model ${modelName} does not support JSON output format.`);
9105
9219
  }
9106
- // Check if tools are requested with a model that doesn't support them
9107
- if (mergedOptions.tools &&
9108
- mergedOptions.tools.length > 0 &&
9109
- !supportsToolCalling(modelName)) {
9110
- getLumicLogger().warn(`Model ${modelName} does not support tool calling. Will return error in the response.`);
9111
- return {
9112
- response: {
9113
- error: `Model ${modelName} does not support tool calling.`,
9114
- },
9115
- usage: {
9116
- prompt_tokens: 0,
9117
- completion_tokens: 0,
9118
- reasoning_tokens: 0,
9119
- provider: "deepseek",
9120
- model: modelName,
9121
- cached_tokens: 0,
9122
- cost: 0,
9123
- },
9124
- tool_calls: undefined,
9125
- };
9220
+ // Check if tools are requested with a model that doesn't support them.
9221
+ if (mergedOptions.tools && mergedOptions.tools.length > 0 && !supportsToolCalling(modelName)) {
9222
+ throw new Error(`Model ${modelName} does not support tool calling.`);
9126
9223
  }
9127
9224
  try {
9128
9225
  const completion = await createDeepseekCompletion(content, responseFormat, mergedOptions);
9129
9226
  // Track cost in the global cost tracker. Pass cached tokens through so the
9130
9227
  // discounted cached-input pricing tier is applied.
9131
- getLLMCostTracker().trackUsage("deepseek", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens);
9228
+ getLLMCostTracker().trackUsage('deepseek', completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens);
9132
9229
  // Handle tool calls similarly to OpenAI
9133
9230
  if (completion.tool_calls && completion.tool_calls.length > 0) {
9134
9231
  const toolCallResponse = {
@@ -9144,10 +9241,10 @@ const makeDeepseekCall = async (content, responseFormat = "json", options = {})
9144
9241
  prompt_tokens: completion.usage.prompt_tokens,
9145
9242
  completion_tokens: completion.usage.completion_tokens,
9146
9243
  reasoning_tokens: 0, // Deepseek doesn't provide reasoning tokens separately
9147
- provider: "deepseek",
9244
+ provider: 'deepseek',
9148
9245
  model: completion.model,
9149
9246
  cached_tokens: completion.usage.cached_tokens,
9150
- cost: calculateCost("deepseek", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
9247
+ cost: calculateCost('deepseek', completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
9151
9248
  },
9152
9249
  tool_calls: completion.tool_calls,
9153
9250
  };
@@ -9155,7 +9252,7 @@ const makeDeepseekCall = async (content, responseFormat = "json", options = {})
9155
9252
  // Handle regular responses
9156
9253
  const parsedResponse = await parseResponse(completion.content, responseFormat);
9157
9254
  if (parsedResponse === null) {
9158
- throw new Error("Failed to parse Deepseek response");
9255
+ throw new Error('Failed to parse Deepseek response');
9159
9256
  }
9160
9257
  return {
9161
9258
  response: parsedResponse,
@@ -9163,32 +9260,22 @@ const makeDeepseekCall = async (content, responseFormat = "json", options = {})
9163
9260
  prompt_tokens: completion.usage.prompt_tokens,
9164
9261
  completion_tokens: completion.usage.completion_tokens,
9165
9262
  reasoning_tokens: 0, // Deepseek doesn't provide reasoning tokens separately
9166
- provider: "deepseek",
9263
+ provider: 'deepseek',
9167
9264
  model: completion.model,
9168
9265
  cached_tokens: completion.usage.cached_tokens,
9169
- cost: calculateCost("deepseek", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
9266
+ cost: calculateCost('deepseek', completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
9170
9267
  },
9171
9268
  tool_calls: completion.tool_calls,
9172
9269
  };
9173
9270
  }
9174
9271
  catch (error) {
9175
- // If there's an error due to incompatible features, return a structured error
9272
+ // Mirror the OpenAI/Anthropic providers: log with sanitized context and
9273
+ // rethrow so callers observe the failure. Returning a success-shaped
9274
+ // response with an { error } payload here previously let hard failures
9275
+ // (auth, timeout, rate-limit exhaustion, unparseable output) masquerade as
9276
+ // successful completions downstream.
9176
9277
  getLumicLogger().error(`Error in Deepseek API call: ${sanitizeError(error)}`);
9177
- return {
9178
- response: {
9179
- error: sanitizeError(error),
9180
- },
9181
- usage: {
9182
- prompt_tokens: 0,
9183
- completion_tokens: 0,
9184
- reasoning_tokens: 0,
9185
- provider: "deepseek",
9186
- model: modelName,
9187
- cached_tokens: 0,
9188
- cost: 0,
9189
- },
9190
- tool_calls: undefined,
9191
- };
9278
+ throw error;
9192
9279
  }
9193
9280
  };
9194
9281
 
@@ -9291,15 +9378,15 @@ const generateCacheKey = (auth, envVarsCheck = null) => {
9291
9378
  if (auth) {
9292
9379
  return `${auth.AWS_ACCESS_KEY_ID}:${auth.AWS_SECRET_ACCESS_KEY}:${auth.AWS_REGION}`;
9293
9380
  }
9294
- if (envVarsCheck?.source === "lambda") {
9381
+ if (envVarsCheck?.source === 'lambda') {
9295
9382
  // In Lambda, use function name as cache key since it uses IAM role
9296
9383
  const secrets = getSecrets();
9297
- return `lambda:${secrets.aws.lambdaFunctionName || "default"}`;
9384
+ return `lambda:${secrets.aws.lambdaFunctionName || 'default'}`;
9298
9385
  }
9299
9386
  if (envVarsCheck?.vars) {
9300
9387
  return `${envVarsCheck.vars.accessKeyId}:${envVarsCheck.vars.secretAccessKey}:${envVarsCheck.vars.region}`;
9301
9388
  }
9302
- return "default";
9389
+ return 'default';
9303
9390
  };
9304
9391
  const validateEnvironmentVars = () => {
9305
9392
  const secrets = getSecrets();
@@ -9312,37 +9399,37 @@ const validateEnvironmentVars = () => {
9312
9399
  // We're in a Lambda environment
9313
9400
  return {
9314
9401
  isValid: true,
9315
- source: "lambda",
9316
- vars: { accessKeyId: "", secretAccessKey: "", region: "" }, // No need for explicit credentials in Lambda
9402
+ source: 'lambda',
9403
+ vars: { accessKeyId: '', secretAccessKey: '', region: '' } // No need for explicit credentials in Lambda
9317
9404
  };
9318
9405
  }
9319
9406
  else if (areVarsComplete(secrets.aws.myAccessKeyId, secrets.aws.mySecretAccessKey, secrets.aws.myRegion)) {
9320
9407
  return {
9321
9408
  isValid: true,
9322
- source: "my_prefix",
9409
+ source: 'my_prefix',
9323
9410
  vars: {
9324
- accessKeyId: secrets.aws.myAccessKeyId ?? "",
9325
- secretAccessKey: secrets.aws.mySecretAccessKey ?? "",
9326
- region: secrets.aws.myRegion ?? "",
9327
- },
9411
+ accessKeyId: secrets.aws.myAccessKeyId,
9412
+ secretAccessKey: secrets.aws.mySecretAccessKey,
9413
+ region: secrets.aws.myRegion
9414
+ }
9328
9415
  };
9329
9416
  }
9330
9417
  else if (areVarsComplete(secrets.aws.accessKeyId, secrets.aws.secretAccessKey, secrets.aws.region)) {
9331
9418
  return {
9332
9419
  isValid: true,
9333
- source: "standard",
9420
+ source: 'standard',
9334
9421
  vars: {
9335
- accessKeyId: secrets.aws.accessKeyId ?? "",
9336
- secretAccessKey: secrets.aws.secretAccessKey ?? "",
9337
- region: secrets.aws.region ?? "",
9338
- },
9422
+ accessKeyId: secrets.aws.accessKeyId,
9423
+ secretAccessKey: secrets.aws.secretAccessKey,
9424
+ region: secrets.aws.region
9425
+ }
9339
9426
  };
9340
9427
  }
9341
9428
  // If we get here, no complete set of variables was found
9342
9429
  return {
9343
9430
  isValid: false,
9344
9431
  source: null,
9345
- missingVars: [],
9432
+ missingVars: []
9346
9433
  };
9347
9434
  };
9348
9435
  const initialiseAWSClient = (ClientClass, auth = null) => {
@@ -9351,52 +9438,48 @@ const initialiseAWSClient = (ClientClass, auth = null) => {
9351
9438
  const requestTimeout = ClientClass === clientS3.S3Client ? AWS_S3_TIMEOUT_MS : AWS_LAMBDA_TIMEOUT_MS;
9352
9439
  // Case 1: Explicit auth provided
9353
9440
  if (auth) {
9354
- if (typeof auth !== "object" || auth === null) {
9355
- throw new Error("Auth parameter must be an object");
9441
+ if (typeof auth !== 'object' || auth === null) {
9442
+ throw new Error('Auth parameter must be an object');
9356
9443
  }
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]);
9444
+ const requiredAuthFields = ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_REGION'];
9445
+ const missingFields = requiredAuthFields.filter(field => !auth[field]);
9363
9446
  if (missingFields.length > 0) {
9364
- throw new Error(`Auth object is missing required fields: ${missingFields.join(", ")}`);
9447
+ throw new Error(`Auth object is missing required fields: ${missingFields.join(', ')}`);
9365
9448
  }
9366
9449
  return new ClientClass({
9367
9450
  region: auth.AWS_REGION,
9368
9451
  credentials: {
9369
9452
  accessKeyId: auth.AWS_ACCESS_KEY_ID,
9370
- secretAccessKey: auth.AWS_SECRET_ACCESS_KEY,
9453
+ secretAccessKey: auth.AWS_SECRET_ACCESS_KEY
9371
9454
  },
9372
9455
  requestHandler: {
9373
- requestTimeout,
9374
- },
9456
+ requestTimeout
9457
+ }
9375
9458
  });
9376
9459
  }
9377
9460
  // Case 2: Check environment variables
9378
9461
  const envVarsCheck = validateEnvironmentVars();
9379
9462
  if (!envVarsCheck.isValid) {
9380
- throw new Error("No valid AWS credentials found in environment variables");
9463
+ throw new Error('No valid AWS credentials found in environment variables');
9381
9464
  }
9382
9465
  // Case 2a: Lambda execution environment
9383
- if (envVarsCheck.source === "lambda") {
9466
+ if (envVarsCheck.source === 'lambda') {
9384
9467
  return new ClientClass({
9385
9468
  requestHandler: {
9386
- requestTimeout,
9387
- },
9469
+ requestTimeout
9470
+ }
9388
9471
  }); // AWS SDK will automatically use Lambda role credentials
9389
9472
  }
9390
9473
  // Case 2b: MY_ prefixed vars or standard AWS vars
9391
9474
  return new ClientClass({
9392
- region: envVarsCheck.vars?.region ?? "",
9475
+ region: envVarsCheck.vars.region,
9393
9476
  credentials: {
9394
- accessKeyId: envVarsCheck.vars?.accessKeyId ?? "",
9395
- secretAccessKey: envVarsCheck.vars?.secretAccessKey ?? "",
9477
+ accessKeyId: envVarsCheck.vars.accessKeyId,
9478
+ secretAccessKey: envVarsCheck.vars.secretAccessKey
9396
9479
  },
9397
9480
  requestHandler: {
9398
- requestTimeout,
9399
- },
9481
+ requestTimeout
9482
+ }
9400
9483
  });
9401
9484
  }
9402
9485
  catch (error) {
@@ -9409,9 +9492,7 @@ const initialiseS3Client = (auth = null) => {
9409
9492
  const envVarsCheck = auth ? null : validateEnvironmentVars();
9410
9493
  const cacheKey = generateCacheKey(auth, envVarsCheck);
9411
9494
  if (s3ClientCache.has(cacheKey)) {
9412
- const cached = s3ClientCache.get(cacheKey);
9413
- if (cached)
9414
- return cached;
9495
+ return s3ClientCache.get(cacheKey);
9415
9496
  }
9416
9497
  // Create new client and cache it
9417
9498
  const client = initialiseAWSClient(clientS3.S3Client, auth);
@@ -9423,9 +9504,7 @@ const initialiseLambdaClient = (auth = null) => {
9423
9504
  const envVarsCheck = auth ? null : validateEnvironmentVars();
9424
9505
  const cacheKey = generateCacheKey(auth, envVarsCheck);
9425
9506
  if (lambdaClientCache.has(cacheKey)) {
9426
- const cached = lambdaClientCache.get(cacheKey);
9427
- if (cached)
9428
- return cached;
9507
+ return lambdaClientCache.get(cacheKey);
9429
9508
  }
9430
9509
  // Create new client and cache it
9431
9510
  const client = initialiseAWSClient(clientLambda.Lambda, auth);
@@ -9925,17 +10004,15 @@ const isRetryableS3Error = (error) => {
9925
10004
  if (error instanceof Error) {
9926
10005
  const message = error.message;
9927
10006
  // Retry on throttling
9928
- if (message.includes("SlowDown") || message.includes("RequestTimeout")) {
10007
+ if (message.includes('SlowDown') || message.includes('RequestTimeout')) {
9929
10008
  return true;
9930
10009
  }
9931
10010
  // Retry on 5xx server errors
9932
- if (message.includes("InternalError") ||
9933
- message.includes("ServiceUnavailable")) {
10011
+ if (message.includes('InternalError') || message.includes('ServiceUnavailable')) {
9934
10012
  return true;
9935
10013
  }
9936
10014
  // Retry on connection errors
9937
- if (message.includes("RequestTimeTooSkewed") ||
9938
- message.includes("NetworkingError")) {
10015
+ if (message.includes('RequestTimeTooSkewed') || message.includes('NetworkingError')) {
9939
10016
  return true;
9940
10017
  }
9941
10018
  }
@@ -9949,11 +10026,10 @@ const generateRandomHash = (length) => {
9949
10026
  };
9950
10027
  function generateDateTimeStamp() {
9951
10028
  const now = new Date();
9952
- return now
9953
- .toISOString()
9954
- .replace(/T/, "-")
9955
- .replace(/\..+/, "")
9956
- .replace(/:/g, "-");
10029
+ return now.toISOString()
10030
+ .replace(/T/, '-')
10031
+ .replace(/\..+/, '')
10032
+ .replace(/:/g, '-');
9957
10033
  }
9958
10034
  const isValidBucketName = (name) => {
9959
10035
  const regex = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/;
@@ -9984,7 +10060,7 @@ async function uploadFile(s3Client, bucketName, filePath, destinationPath, isPub
9984
10060
  Bucket: bucketName,
9985
10061
  Key: s3Key,
9986
10062
  Body: fileContent,
9987
- ACL: isPublic ? "public-read" : undefined,
10063
+ ACL: isPublic ? 'public-read' : undefined
9988
10064
  });
9989
10065
  await withRetry(() => s3Client.send(command), {
9990
10066
  maxRetries: 3,
@@ -10004,7 +10080,7 @@ async function uploadDirectory(s3Client, bucketName, dirPath, destinationPath, i
10004
10080
  Bucket: bucketName,
10005
10081
  Key: s3Key,
10006
10082
  Body: fileContent,
10007
- ACL: isPublic ? "public-read" : undefined,
10083
+ ACL: isPublic ? 'public-read' : undefined
10008
10084
  });
10009
10085
  await withRetry(() => s3Client.send(command), {
10010
10086
  maxRetries: 3,
@@ -10020,7 +10096,7 @@ async function zipDirectory(sourceDir, outPath) {
10020
10096
  const zip = new AdmZip();
10021
10097
  let fileCount = 0;
10022
10098
  try {
10023
- async function addFilesToZip(currentPath, relativePath = "") {
10099
+ async function addFilesToZip(currentPath, relativePath = '') {
10024
10100
  const files = await fs$1.readdir(currentPath, { withFileTypes: true });
10025
10101
  for (const file of files) {
10026
10102
  const filePath = path$1.join(currentPath, file.name);
@@ -10063,33 +10139,27 @@ async function downloadFromS3Helper(s3Client, bucketName, s3Path, localPath) {
10063
10139
  Prefix: s3Path,
10064
10140
  ContinuationToken: continuationToken,
10065
10141
  });
10066
- const listResponse = (await withRetry(() => s3Client.send(listCommand), {
10142
+ const listResponse = await withRetry(() => s3Client.send(listCommand), {
10067
10143
  maxRetries: 3,
10068
10144
  baseDelayMs: 1000,
10069
10145
  maxDelayMs: 15000,
10070
10146
  retryableErrors: isRetryableS3Error,
10071
- }, `S3:ListObjects:${bucketName}`));
10147
+ }, `S3:ListObjects:${bucketName}`);
10072
10148
  const objects = listResponse.Contents || [];
10073
10149
  if (objects.length === 0) {
10074
10150
  return { fileCount: 0, folderCount: 0, totalSize: 0, fileList: [] };
10075
10151
  }
10076
- if (objects.length === 1 && objects[0].Key?.endsWith(".zip")) {
10152
+ if (objects.length === 1 && objects[0].Key?.endsWith('.zip')) {
10077
10153
  // It's a zip file, download and extract it
10078
10154
  const zipPath = path$1.join(localPath, path$1.basename(objects[0].Key));
10079
10155
  await downloadFile(s3Client, bucketName, objects[0].Key, zipPath);
10080
- const { totalFiles, totalFolders, totalSize: extractedSize, fileList: extractedFileList, } = await unzipFile(zipPath, localPath);
10156
+ const { totalFiles, totalFolders, totalSize: extractedSize, fileList: extractedFileList } = await unzipFile(zipPath, localPath);
10081
10157
  await fs$1.unlink(zipPath);
10082
- return {
10083
- fileCount: totalFiles,
10084
- folderCount: totalFolders,
10085
- totalSize: extractedSize,
10086
- fileList: extractedFileList,
10087
- };
10158
+ return { fileCount: totalFiles, folderCount: totalFolders, totalSize: extractedSize, fileList: extractedFileList };
10088
10159
  }
10089
10160
  else {
10090
10161
  // Non-zip files. Download files in batches.
10091
- for (let i = 0; i < objects.length; i += 1000) {
10092
- // AWS allows up to 1000 per request
10162
+ for (let i = 0; i < objects.length; i += 1000) { // AWS allows up to 1000 per request
10093
10163
  const batch = objects.slice(i, i + 1000);
10094
10164
  await Promise.all(batch.map(async (obj) => {
10095
10165
  if (!obj.Key)
@@ -10105,9 +10175,7 @@ async function downloadFromS3Helper(s3Client, bucketName, s3Path, localPath) {
10105
10175
  }
10106
10176
  // Count folders
10107
10177
  const relativePath = path$1.relative(localPath, path$1.dirname(localFilePath));
10108
- if (relativePath &&
10109
- !relativePath.startsWith("..") &&
10110
- relativePath !== ".") {
10178
+ if (relativePath && !relativePath.startsWith('..') && relativePath !== '.') {
10111
10179
  folderCount++;
10112
10180
  }
10113
10181
  }));
@@ -10128,7 +10196,7 @@ async function downloadFile(s3Client, bucketName, s3Key, localFilePath) {
10128
10196
  await withRetry(async () => {
10129
10197
  const { Body } = await s3Client.send(getCommand);
10130
10198
  if (!Body)
10131
- throw new Error("No body returned from S3");
10199
+ throw new Error('No body returned from S3');
10132
10200
  const writeStream = fs.createWriteStream(localFilePath);
10133
10201
  await promises.pipeline(Body, writeStream);
10134
10202
  }, {
@@ -10141,14 +10209,27 @@ async function downloadFile(s3Client, bucketName, s3Key, localFilePath) {
10141
10209
  async function unzipFile(zipPath, destPath) {
10142
10210
  const zip = new AdmZip(zipPath);
10143
10211
  try {
10144
- zip.extractAllToAsync(destPath, true);
10145
- const stats = await getDirectoryStats(destPath);
10146
- return stats;
10212
+ // extractAllToAsync is fire-and-forget: it returns void and reports
10213
+ // failures through its callback. The previous call neither awaited it nor
10214
+ // observed the callback, so getDirectoryStats raced an in-flight (or
10215
+ // failed) extraction and any extraction error was silently dropped. Wrap
10216
+ // it in a Promise so the extraction is fully awaited and errors surface.
10217
+ await new Promise((resolve, reject) => {
10218
+ zip.extractAllToAsync(destPath, true, false, (error) => {
10219
+ if (error) {
10220
+ reject(error);
10221
+ }
10222
+ else {
10223
+ resolve();
10224
+ }
10225
+ });
10226
+ });
10227
+ return await getDirectoryStats(destPath);
10147
10228
  }
10148
10229
  catch (error) {
10149
10230
  const errorMessage = error instanceof Error ? error.message : String(error);
10150
10231
  getLumicLogger().error(`Error unzipping file: ${errorMessage}`);
10151
- throw error;
10232
+ throw new ZipError(`Failed to extract archive "${zipPath}" to "${destPath}": ${errorMessage}`, error);
10152
10233
  }
10153
10234
  }
10154
10235
  async function getDirectoryStats(dirPath) {
@@ -10184,19 +10265,17 @@ async function emptyBucket(s3Client, bucketName) {
10184
10265
  Bucket: bucketName,
10185
10266
  ContinuationToken: continuationToken,
10186
10267
  });
10187
- const response = (await withRetry(() => s3Client.send(listCommand), {
10268
+ const response = await withRetry(() => s3Client.send(listCommand), {
10188
10269
  maxRetries: 3,
10189
10270
  baseDelayMs: 1000,
10190
10271
  maxDelayMs: 15000,
10191
10272
  retryableErrors: isRetryableS3Error,
10192
- }, `S3:ListObjects:${bucketName}`));
10273
+ }, `S3:ListObjects:${bucketName}`);
10193
10274
  const { Contents, IsTruncated, NextContinuationToken } = response;
10194
10275
  if (Contents && Contents.length > 0) {
10195
10276
  const deleteCommand = new clientS3.DeleteObjectsCommand({
10196
10277
  Bucket: bucketName,
10197
- Delete: {
10198
- Objects: Contents.filter((c) => c.Key).map((c) => ({ Key: c.Key })),
10199
- },
10278
+ Delete: { Objects: Contents.map(({ Key }) => ({ Key: Key })) },
10200
10279
  });
10201
10280
  await withRetry(() => s3Client.send(deleteCommand), {
10202
10281
  maxRetries: 3,
@@ -23104,11 +23183,11 @@ let poolConfig = DEFAULT_POOL_CONFIG;
23104
23183
  async function loadApolloModules() {
23105
23184
  if (typeof window === "undefined" || process.env.AWS_EXECUTION_ENV) {
23106
23185
  // Server-side (or Lambda): load the CommonJS‑based implementation.
23107
- return (await Promise.resolve().then(function () { return require('./apollo-client.server-DB3jLbBx.js'); }));
23186
+ return (await Promise.resolve().then(function () { return require('./apollo-client.server-JYHXy9ib.js'); }));
23108
23187
  }
23109
23188
  else {
23110
23189
  // Client-side: load the ESM‑based implementation.
23111
- return (await Promise.resolve().then(function () { return require('./apollo-client.client-CyxU1hTu.js'); }));
23190
+ return (await Promise.resolve().then(function () { return require('./apollo-client.client-CtjgIfRT.js'); }));
23112
23191
  }
23113
23192
  }
23114
23193
  /**
@@ -79135,7 +79214,7 @@ const RETRY_CONFIG = {
79135
79214
  BASE_DELAY: 2000, // Increased base delay to 2 seconds
79136
79215
  MAX_DELAY: 64000,
79137
79216
  MAX_RETRIES: 5,
79138
- MAX_RANDOM_DELAY: 1000,
79217
+ MAX_RANDOM_DELAY: 1000
79139
79218
  };
79140
79219
  /**
79141
79220
  * Determines if an error is related to Google Sheets API quotas or rate limits.
@@ -79161,10 +79240,10 @@ const RETRY_CONFIG = {
79161
79240
  */
79162
79241
  function isQuotaError(error) {
79163
79242
  const message = error.message.toLowerCase();
79164
- return (message.includes("quota exceeded") ||
79165
- message.includes("rate limit") ||
79166
- message.includes("429") ||
79167
- message.includes("too many requests"));
79243
+ return message.includes('quota exceeded') ||
79244
+ message.includes('rate limit') ||
79245
+ message.includes('429') ||
79246
+ message.includes('too many requests');
79168
79247
  }
79169
79248
  /**
79170
79249
  * Implements exponential backoff retry mechanism for Google Sheets API calls.
@@ -79223,14 +79302,14 @@ async function withExponentialBackoff(operation) {
79223
79302
  const exponentialDelay = Math.min(Math.pow(2, retries) * RETRY_CONFIG.BASE_DELAY + randomDelay, RETRY_CONFIG.MAX_DELAY);
79224
79303
  logIfDebug(`Quota/Rate limit exceeded. Error: ${error.message}`);
79225
79304
  logIfDebug(`Retrying in ${exponentialDelay}ms (attempt ${retries}/${RETRY_CONFIG.MAX_RETRIES})`);
79226
- await new Promise((resolve) => setTimeout(resolve, exponentialDelay));
79305
+ await new Promise(resolve => setTimeout(resolve, exponentialDelay));
79227
79306
  }
79228
79307
  }
79229
79308
  }
79230
79309
  function checkEnvVars() {
79231
79310
  const secrets = getSecrets();
79232
79311
  if (!secrets.googleSheets.clientEmail || !secrets.googleSheets.privateKey) {
79233
- throw new Error("GOOGLE_SHEETS_CLIENT_EMAIL or GOOGLE_SHEETS_PRIVATE_KEY is not defined in environment variables.");
79312
+ throw new Error('GOOGLE_SHEETS_CLIENT_EMAIL or GOOGLE_SHEETS_PRIVATE_KEY is not defined in environment variables.');
79234
79313
  }
79235
79314
  }
79236
79315
  /**
@@ -79245,16 +79324,16 @@ async function getAuthClient() {
79245
79324
  const secrets = getSecrets();
79246
79325
  const credentials = {
79247
79326
  client_email: secrets.googleSheets.clientEmail,
79248
- private_key: secrets.googleSheets.privateKey?.replace(/\\n/g, "\n"),
79327
+ private_key: secrets.googleSheets.privateKey?.replace(/\\n/g, '\n'),
79249
79328
  };
79250
79329
  const auth = new require$$0$6.GoogleAuth({
79251
79330
  credentials,
79252
- scopes: ["https://www.googleapis.com/auth/spreadsheets"],
79331
+ scopes: ['https://www.googleapis.com/auth/spreadsheets'],
79253
79332
  });
79254
79333
  return auth.getClient();
79255
79334
  }
79256
79335
  catch (error) {
79257
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
79336
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
79258
79337
  logIfDebug(`Error initializing Google Sheets auth: ${errorMessage}`);
79259
79338
  throw new Error(`Failed to initialize Google Sheets auth: ${errorMessage}`);
79260
79339
  }
@@ -79266,7 +79345,7 @@ async function getSheetsClient() {
79266
79345
  const auth = await getAuthClient();
79267
79346
  return new buildExports.sheets_v4.Sheets({
79268
79347
  auth,
79269
- timeout: GOOGLE_SHEETS_TIMEOUT_MS,
79348
+ timeout: GOOGLE_SHEETS_TIMEOUT_MS
79270
79349
  });
79271
79350
  }
79272
79351
  /**
@@ -79299,7 +79378,7 @@ function escapeSheetName(sheetName) {
79299
79378
  * @param startColumn Optional starting column (defaults to 'A')
79300
79379
  * @returns Promise resolving to response with success status and row details
79301
79380
  */
79302
- async function addRow(config, values, startColumn = "A") {
79381
+ async function addRow(config, values, startColumn = 'A') {
79303
79382
  try {
79304
79383
  const sheets = await getSheetsClient();
79305
79384
  const escapedSheetName = escapeSheetName(config.sheetName);
@@ -79307,7 +79386,7 @@ async function addRow(config, values, startColumn = "A") {
79307
79386
  const response = await withExponentialBackoff(() => sheets.spreadsheets.values.append({
79308
79387
  spreadsheetId: config.spreadsheetId,
79309
79388
  range,
79310
- valueInputOption: "USER_ENTERED",
79389
+ valueInputOption: 'USER_ENTERED',
79311
79390
  requestBody: {
79312
79391
  values: [values],
79313
79392
  },
@@ -79315,9 +79394,9 @@ async function addRow(config, values, startColumn = "A") {
79315
79394
  //logIfDebug(`Added row to sheet ${config.sheetName}: ${values.join(', ')}`);
79316
79395
  const rowNumber = response.data.updates?.updatedRange
79317
79396
  ? parseInt(response.data.updates.updatedRange
79318
- .split("!")[1]
79319
- .split(":")[0]
79320
- .replace(/[^0-9]/g, ""))
79397
+ .split('!')[1]
79398
+ .split(':')[0]
79399
+ .replace(/[^0-9]/g, ''))
79321
79400
  : 0;
79322
79401
  return {
79323
79402
  success: true,
@@ -79328,7 +79407,7 @@ async function addRow(config, values, startColumn = "A") {
79328
79407
  };
79329
79408
  }
79330
79409
  catch (error) {
79331
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
79410
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
79332
79411
  logIfDebug(`Error adding row to sheet: ${errorMessage}`);
79333
79412
  return {
79334
79413
  success: false,
@@ -79370,7 +79449,7 @@ async function addSheet(spreadsheetId, sheetTitle) {
79370
79449
  };
79371
79450
  }
79372
79451
  catch (error) {
79373
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
79452
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
79374
79453
  logIfDebug(`Error adding sheet: ${errorMessage}`);
79375
79454
  return {
79376
79455
  success: false,
@@ -79394,7 +79473,7 @@ async function writeCell(config, cell, value) {
79394
79473
  await withExponentialBackoff(() => sheets.spreadsheets.values.update({
79395
79474
  spreadsheetId: config.spreadsheetId,
79396
79475
  range,
79397
- valueInputOption: "USER_ENTERED",
79476
+ valueInputOption: 'USER_ENTERED',
79398
79477
  requestBody: {
79399
79478
  values: [[value]],
79400
79479
  },
@@ -79408,7 +79487,7 @@ async function writeCell(config, cell, value) {
79408
79487
  };
79409
79488
  }
79410
79489
  catch (error) {
79411
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
79490
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
79412
79491
  logIfDebug(`Error writing to cell: ${errorMessage}`);
79413
79492
  return {
79414
79493
  success: false,
@@ -79432,11 +79511,8 @@ async function getCell(config, cell) {
79432
79511
  spreadsheetId: config.spreadsheetId,
79433
79512
  range,
79434
79513
  }));
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)}`);
79514
+ const value = response.data.values?.[0]?.[0];
79515
+ logIfDebug(`Read value from cell ${range}: ${value}`);
79440
79516
  return {
79441
79517
  success: true,
79442
79518
  data: {
@@ -79445,7 +79521,7 @@ async function getCell(config, cell) {
79445
79521
  };
79446
79522
  }
79447
79523
  catch (error) {
79448
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
79524
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
79449
79525
  logIfDebug(`Error reading from cell: ${errorMessage}`);
79450
79526
  return {
79451
79527
  success: false,
@@ -79478,11 +79554,10 @@ function convertA1ToRowCol(a1Notation) {
79478
79554
  * @returns Padded array of arrays
79479
79555
  */
79480
79556
  function padArrays(arrays) {
79481
- const maxLength = Math.max(...arrays.map((arr) => arr.length));
79482
- return arrays.map((arr) => {
79557
+ const maxLength = Math.max(...arrays.map(arr => arr.length));
79558
+ return arrays.map(arr => {
79483
79559
  if (arr.length < maxLength) {
79484
- const padding = Array(maxLength - arr.length).fill("");
79485
- return [...arr, ...padding];
79560
+ return [...arr, ...Array(maxLength - arr.length).fill('')];
79486
79561
  }
79487
79562
  return arr;
79488
79563
  });
@@ -79494,10 +79569,10 @@ function padArrays(arrays) {
79494
79569
  * @param startCell Optional starting cell in A1 notation (defaults to 'A1')
79495
79570
  * @returns Promise resolving to response with success status and update details
79496
79571
  */
79497
- async function writeArray(config, data, startCell = "A1") {
79572
+ async function writeArray(config, data, startCell = 'A1') {
79498
79573
  try {
79499
79574
  if (!data.length) {
79500
- throw new Error("Data array is empty");
79575
+ throw new Error('Data array is empty');
79501
79576
  }
79502
79577
  const sheets = await getSheetsClient();
79503
79578
  const { row: startRow, column: startCol } = convertA1ToRowCol(startCell);
@@ -79513,7 +79588,7 @@ async function writeArray(config, data, startCell = "A1") {
79513
79588
  const response = await withExponentialBackoff(() => sheets.spreadsheets.values.update({
79514
79589
  spreadsheetId: config.spreadsheetId,
79515
79590
  range,
79516
- valueInputOption: "USER_ENTERED",
79591
+ valueInputOption: 'USER_ENTERED',
79517
79592
  requestBody: {
79518
79593
  values: paddedData,
79519
79594
  },
@@ -79529,7 +79604,7 @@ async function writeArray(config, data, startCell = "A1") {
79529
79604
  };
79530
79605
  }
79531
79606
  catch (error) {
79532
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
79607
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
79533
79608
  logIfDebug(`Error writing array to sheet: ${errorMessage}`);
79534
79609
  return {
79535
79610
  success: false,
@@ -81823,4 +81898,4 @@ exports.withCorrelationId = withCorrelationId;
81823
81898
  exports.withMetrics = withMetrics;
81824
81899
  exports.withRateLimit = withRateLimit;
81825
81900
  exports.withRetry = withRetry;
81826
- //# sourceMappingURL=index-KzQOh2uu.js.map
81901
+ //# sourceMappingURL=index-DgFDhFuO.js.map