@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
@@ -551,19 +551,10 @@ function getModelProvider(model) {
551
551
  return SUPPORTED_MODELS[model].provider;
552
552
  }
553
553
  /**
554
- * Default model tiers per provider for use with LLM_MODEL_MINI/NORMAL/ADVANCED env vars.
555
- *
556
- * OpenAI `advanced` was reverted from `gpt-5.5` back to `gpt-5.4` on 2026-05-30:
557
- * the heavy tool-call prompt in adaptic-engine's trading-decision path was
558
- * timing out even after the engine raised `LLM_CALL_TIMEOUT_MS` to 90s (5+
559
- * tickers per loop still exceeded the budget). `gpt-5.5`'s 1M-context window
560
- * is not required on that path — the prompt fits comfortably inside `gpt-5.4`'s
561
- * envelope and `gpt-5.4` returns inside the original 25-30s p95. `gpt-5.5`
562
- * remains registered in OPENAI_MODELS and reachable by explicit model id;
563
- * only the `advanced` tier default is rolled back.
554
+ * Default model tiers per provider for use with LLM_MODEL_MINI/NORMAL/ADVANCED env vars
564
555
  */
565
556
  const PROVIDER_DEFAULT_MODELS = {
566
- openai: { mini: 'gpt-5.4-nano', normal: 'gpt-5.4-mini', advanced: 'gpt-5.4' },
557
+ openai: { mini: 'gpt-5.4-nano', normal: 'gpt-5.4-mini', advanced: 'gpt-5.5' },
567
558
  anthropic: { mini: 'claude-haiku-4-5', normal: 'claude-sonnet-4-6', advanced: 'claude-opus-4-7' },
568
559
  deepseek: { mini: 'deepseek-v4-flash', normal: 'deepseek-v4-flash', advanced: 'deepseek-v4-pro' },
569
560
  kimi: { mini: 'kimi-k2-0905-preview', normal: 'kimi-k2.5', advanced: 'kimi-k2.6' },
@@ -1517,13 +1508,11 @@ let openai;
1517
1508
  function initializeOpenAI(apiKey) {
1518
1509
  const key = apiKey || getSecrets().openai.apiKey;
1519
1510
  if (!key) {
1520
- throw new Error("OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set");
1511
+ throw new Error('OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set');
1521
1512
  }
1522
1513
  return new OpenAI({
1523
1514
  apiKey: key,
1524
- defaultHeaders: {
1525
- "User-Agent": "My Server-side Application (compatible; OpenAI API Client)",
1526
- },
1515
+ defaultHeaders: { 'User-Agent': 'My Server-side Application (compatible; OpenAI API Client)' },
1527
1516
  });
1528
1517
  }
1529
1518
  /**
@@ -1541,7 +1530,9 @@ function initializeOpenAI(apiKey) {
1541
1530
  */
1542
1531
  async function fixJsonWithAI(jsonStr, options) {
1543
1532
  // Backward compatibility: handle string apiKey as first positional param
1544
- const opts = typeof options === "string" ? { apiKey: options } : (options ?? {});
1533
+ const opts = typeof options === 'string'
1534
+ ? { apiKey: options }
1535
+ : (options ?? {});
1545
1536
  const apiKey = opts.apiKey;
1546
1537
  const maxDepth = opts.maxDepth ?? 2;
1547
1538
  const depth = opts._depth ?? 0;
@@ -1554,18 +1545,18 @@ async function fixJsonWithAI(jsonStr, options) {
1554
1545
  openai = initializeOpenAI(apiKey);
1555
1546
  }
1556
1547
  const completion = await openai.chat.completions.create({
1557
- model: "gpt-5-mini",
1548
+ model: 'gpt-5-mini',
1558
1549
  messages: [
1559
1550
  {
1560
- role: "system",
1561
- content: "You are a JSON fixer. Return only valid JSON without any additional text or explanation.",
1551
+ role: 'system',
1552
+ content: 'You are a JSON fixer. Return only valid JSON without any additional text or explanation.'
1562
1553
  },
1563
1554
  {
1564
- role: "user",
1565
- content: `Fix this broken JSON:\n${jsonStr}`,
1566
- },
1555
+ role: 'user',
1556
+ content: `Fix this broken JSON:\n${jsonStr}`
1557
+ }
1567
1558
  ],
1568
- response_format: { type: "json_object" },
1559
+ response_format: { type: 'json_object' }
1569
1560
  });
1570
1561
  const fixedJson = completion.choices[0]?.message?.content;
1571
1562
  if (fixedJson && isValidJson(fixedJson)) {
@@ -1579,32 +1570,23 @@ async function fixJsonWithAI(jsonStr, options) {
1579
1570
  _depth: depth + 1,
1580
1571
  });
1581
1572
  }
1582
- throw new JsonParseError("Failed to fix JSON with AI: returned invalid JSON");
1573
+ throw new JsonParseError('Failed to fix JSON with AI: returned invalid JSON');
1583
1574
  }
1584
1575
  catch (err) {
1585
1576
  // Re-throw JsonParseError as-is (e.g. max depth exceeded)
1586
1577
  if (err instanceof JsonParseError) {
1587
1578
  throw err;
1588
1579
  }
1589
- const errorMessage = err instanceof Error ? err.message : "Unknown error occurred";
1580
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
1590
1581
  throw new JsonParseError(`Error fixing JSON with AI: ${errorMessage}`);
1591
1582
  }
1592
1583
  }
1593
1584
 
1594
1585
  // llm-utils.ts
1595
1586
  function normalizeModelName(model) {
1596
- return model.replace(/_/g, "-").toLowerCase();
1597
- }
1598
- const CODE_BLOCK_TYPES = [
1599
- "javascript",
1600
- "js",
1601
- "graphql",
1602
- "json",
1603
- "typescript",
1604
- "python",
1605
- "markdown",
1606
- "yaml",
1607
- ];
1587
+ return model.replace(/_/g, '-').toLowerCase();
1588
+ }
1589
+ const CODE_BLOCK_TYPES = ['javascript', 'js', 'graphql', 'json', 'typescript', 'python', 'markdown', 'yaml'];
1608
1590
  /**
1609
1591
  * Tries to parse JSON from the given content string according to the specified
1610
1592
  * response format. If the response format is 'json' or a JSON schema object, it
@@ -1628,9 +1610,7 @@ async function parseResponse(content, responseFormat, options = {}) {
1628
1610
  if (recursionDepth > maxRetries) {
1629
1611
  throw new Error(`Maximum recursion depth (${maxRetries}) exceeded while parsing JSON. AI fixing may have returned broken JSON.`);
1630
1612
  }
1631
- if (responseFormat === "json" ||
1632
- (typeof responseFormat === "object" &&
1633
- responseFormat?.type === "json_schema")) {
1613
+ if (responseFormat === 'json' || (typeof responseFormat === 'object' && responseFormat?.type === 'json_schema')) {
1634
1614
  let cleanedContent = content.trim();
1635
1615
  let detectedType = null;
1636
1616
  // Remove code block markers if present
@@ -1641,15 +1621,24 @@ async function parseResponse(content, responseFormat, options = {}) {
1641
1621
  }
1642
1622
  return false;
1643
1623
  });
1644
- if (startsWithCodeBlock && cleanedContent.endsWith("```")) {
1645
- const firstLineEndIndex = cleanedContent.indexOf("\n");
1624
+ if (startsWithCodeBlock && cleanedContent.endsWith('```')) {
1625
+ const firstLineEndIndex = cleanedContent.indexOf('\n');
1646
1626
  if (firstLineEndIndex !== -1) {
1647
1627
  cleanedContent = cleanedContent.slice(firstLineEndIndex + 1, -3).trim();
1648
1628
  getLumicLogger().info(`Code block for type ${detectedType} detected and removed. Cleaned content: ${cleanedContent}`);
1649
1629
  }
1650
1630
  }
1651
- // Multiple JSON parsing strategies
1652
- // Default pipeline: direct parse -> extract from brackets -> remove leading/trailing text -> fixBrokenJson (string manipulation) -> FAIL
1631
+ // Whether the cleaned content structurally resembles JSON. Used to gate the
1632
+ // lenient fixBrokenJson strategy, which will otherwise coerce arbitrary
1633
+ // prose into a bogus object/array.
1634
+ const inputResemblesJson = cleanedContent.startsWith('{') || cleanedContent.startsWith('[');
1635
+ // Multiple JSON parsing strategies.
1636
+ // Default pipeline: direct parse -> extract from brackets -> remove
1637
+ // leading/trailing text -> GATED fixBrokenJson -> FAIL. fixBrokenJson is
1638
+ // deliberately gated (Strategy 4): its output is accepted only when the
1639
+ // input looked like JSON AND the repaired value round-trips through
1640
+ // JSON.stringify/JSON.parse, so a "successful" repair of non-JSON text is
1641
+ // rejected instead of silently returned as garbage.
1653
1642
  const jsonParsingStrategies = [
1654
1643
  // Strategy 1: Direct parsing
1655
1644
  () => {
@@ -1675,7 +1664,7 @@ async function parseResponse(content, responseFormat, options = {}) {
1675
1664
  },
1676
1665
  // Strategy 3: Remove leading/trailing text and try parsing
1677
1666
  () => {
1678
- const jsonMatch = cleanedContent.replace(/^[^{[]*|[^}\]]*$/g, "");
1667
+ const jsonMatch = cleanedContent.replace(/^[^{[]*|[^}\]]*$/g, '');
1679
1668
  try {
1680
1669
  return JSON.parse(jsonMatch);
1681
1670
  }
@@ -1683,55 +1672,175 @@ async function parseResponse(content, responseFormat, options = {}) {
1683
1672
  return null;
1684
1673
  }
1685
1674
  },
1686
- // Strategy 4: Use fixBrokenJson (string manipulation only, no AI)
1675
+ // Strategy 4: Gated fixBrokenJson (string manipulation only, no AI)
1687
1676
  () => {
1688
- return fixBrokenJson(cleanedContent);
1677
+ if (!inputResemblesJson) {
1678
+ return null;
1679
+ }
1680
+ const fixed = fixBrokenJson(cleanedContent);
1681
+ if (fixed === null) {
1682
+ return null;
1683
+ }
1684
+ try {
1685
+ // Round-trip guard: ensures the repaired value is genuinely
1686
+ // JSON-serializable before we trust it.
1687
+ return JSON.parse(JSON.stringify(fixed));
1688
+ }
1689
+ catch {
1690
+ return null;
1691
+ }
1689
1692
  },
1690
1693
  ];
1691
1694
  // Try each parsing strategy
1692
1695
  for (const strategy of jsonParsingStrategies) {
1693
- const result = await strategy();
1696
+ const result = strategy();
1694
1697
  if (result !== null) {
1695
- return result;
1698
+ return finalizeJsonResult(result, responseFormat);
1696
1699
  }
1697
1700
  }
1698
1701
  // Strategy 5: Use AI fixing (only if explicitly enabled)
1699
1702
  if (enableAiFix) {
1700
- getLumicLogger().warn("Using AI-powered JSON fixing. This adds cost and latency. Consider improving JSON generation instead.");
1703
+ getLumicLogger().warn('Using AI-powered JSON fixing. This adds cost and latency. Consider improving JSON generation instead.');
1704
+ let aiFixedJson = null;
1701
1705
  try {
1702
- const aiFixedJson = await fixJsonWithAI(cleanedContent);
1703
- if (aiFixedJson !== null) {
1704
- // Validate the AI-fixed JSON by attempting to stringify and re-parse it
1705
- // This ensures it's actually valid JSON and not just something that passed fixJsonWithAI
1706
- try {
1707
- const validated = JSON.parse(JSON.stringify(aiFixedJson));
1708
- return validated;
1709
- }
1710
- catch {
1711
- // AI returned something that looks like JSON but isn't valid
1712
- // Increment recursion depth and try parsing the AI response
1713
- getLumicLogger().warn("AI fixing returned invalid JSON, attempting recursive parse...");
1714
- return parseResponse(JSON.stringify(aiFixedJson), responseFormat, {
1715
- ...options,
1716
- _recursionDepth: recursionDepth + 1,
1717
- });
1718
- }
1719
- }
1706
+ aiFixedJson = await fixJsonWithAI(cleanedContent);
1720
1707
  }
1721
1708
  catch (error) {
1722
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
1709
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1723
1710
  getLumicLogger().error(`AI JSON fixing failed: ${errorMessage}`);
1724
- // Continue to final error below
1711
+ // Fall through to the final error below.
1712
+ }
1713
+ if (aiFixedJson !== null) {
1714
+ let validated;
1715
+ try {
1716
+ // Validate the AI-fixed JSON by attempting to stringify and re-parse
1717
+ // it. This ensures it is actually valid JSON, not just something that
1718
+ // passed fixJsonWithAI.
1719
+ validated = JSON.parse(JSON.stringify(aiFixedJson));
1720
+ }
1721
+ catch {
1722
+ // AI returned something that looks like JSON but isn't valid.
1723
+ // Increment recursion depth and try parsing the AI response.
1724
+ getLumicLogger().warn('AI fixing returned invalid JSON, attempting recursive parse...');
1725
+ return parseResponse(JSON.stringify(aiFixedJson), responseFormat, {
1726
+ ...options,
1727
+ _recursionDepth: recursionDepth + 1,
1728
+ });
1729
+ }
1730
+ // Schema validation (json_schema format) happens here, OUTSIDE the
1731
+ // JSON.parse try/catch, so a schema mismatch propagates as a thrown
1732
+ // JsonParseError rather than being mistaken for invalid JSON.
1733
+ return finalizeJsonResult(validated, responseFormat);
1725
1734
  }
1726
1735
  }
1727
1736
  // If all strategies fail, throw an error
1728
1737
  getLumicLogger().error(`Failed to parse JSON from content: ${cleanedContent}. Original JSON: ${content}`);
1729
- throw new Error("Unable to parse JSON response");
1738
+ throw new Error('Unable to parse JSON response');
1730
1739
  }
1731
1740
  else {
1732
1741
  return content;
1733
1742
  }
1734
1743
  }
1744
+ /**
1745
+ * Reports the JSON type of a runtime value for schema-validation diagnostics.
1746
+ *
1747
+ * @param value The value whose JSON type should be described.
1748
+ * @returns One of `'null' | 'array' | 'object' | 'string' | 'number' | 'boolean' | 'undefined'`.
1749
+ */
1750
+ function jsonTypeOf(value) {
1751
+ if (value === null)
1752
+ return 'null';
1753
+ if (Array.isArray(value))
1754
+ return 'array';
1755
+ return typeof value;
1756
+ }
1757
+ /**
1758
+ * Extracts a declared primitive `type` string from a property schema, if present.
1759
+ *
1760
+ * @param propertySchema The (untyped) per-property JSON-Schema fragment.
1761
+ * @returns The declared type string, or null when none is declared.
1762
+ */
1763
+ function extractSchemaType(propertySchema) {
1764
+ if (typeof propertySchema === 'object' && propertySchema !== null && 'type' in propertySchema) {
1765
+ const declared = propertySchema.type;
1766
+ return typeof declared === 'string' ? declared : null;
1767
+ }
1768
+ return null;
1769
+ }
1770
+ /**
1771
+ * Checks whether a runtime value satisfies a declared JSON-Schema primitive
1772
+ * type. Unknown/unsupported type keywords are treated permissively (they never
1773
+ * cause a rejection), so this validator only ever rejects clear mismatches.
1774
+ *
1775
+ * @param value The runtime value to check.
1776
+ * @param expectedType The declared JSON-Schema type keyword.
1777
+ * @returns True when the value matches (or the keyword is unsupported).
1778
+ */
1779
+ function valueMatchesJsonType(value, expectedType) {
1780
+ switch (expectedType) {
1781
+ case 'string':
1782
+ return typeof value === 'string';
1783
+ case 'number':
1784
+ return typeof value === 'number';
1785
+ case 'integer':
1786
+ return typeof value === 'number' && Number.isInteger(value);
1787
+ case 'boolean':
1788
+ return typeof value === 'boolean';
1789
+ case 'object':
1790
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
1791
+ case 'array':
1792
+ return Array.isArray(value);
1793
+ case 'null':
1794
+ return value === null;
1795
+ default:
1796
+ return true;
1797
+ }
1798
+ }
1799
+ /**
1800
+ * Validates a parsed value against a `json_schema` object schema. Enforces that
1801
+ * the value is a plain object, that every `required` property is present, and
1802
+ * that present properties whose schema declares a primitive `type` match it.
1803
+ *
1804
+ * @param value The parsed value to validate.
1805
+ * @param schema The object schema supplied on the response format.
1806
+ * @throws {JsonParseError} When the value violates the schema.
1807
+ */
1808
+ function validateJsonSchema(value, schema) {
1809
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
1810
+ throw new JsonParseError(`JSON schema validation failed: expected an object but received ${jsonTypeOf(value)}.`);
1811
+ }
1812
+ const record = value;
1813
+ const missing = (schema.required ?? []).filter((key) => !(key in record));
1814
+ if (missing.length > 0) {
1815
+ throw new JsonParseError(`JSON schema validation failed: missing required properties: ${missing.join(', ')}.`);
1816
+ }
1817
+ for (const [key, propertySchema] of Object.entries(schema.properties)) {
1818
+ if (!(key in record))
1819
+ continue;
1820
+ const expectedType = extractSchemaType(propertySchema);
1821
+ if (expectedType !== null && !valueMatchesJsonType(record[key], expectedType)) {
1822
+ throw new JsonParseError(`JSON schema validation failed: property "${key}" expected type "${expectedType}" but received ${jsonTypeOf(record[key])}.`);
1823
+ }
1824
+ }
1825
+ }
1826
+ /**
1827
+ * Finalizes a successfully-parsed JSON value. When the caller supplied a
1828
+ * `json_schema` response format, the value is validated against that schema
1829
+ * before being returned so schema violations surface as a thrown
1830
+ * {@link JsonParseError} instead of flowing silently downstream. For plain
1831
+ * `json` formats the value is returned unchanged.
1832
+ *
1833
+ * @param value The parsed value produced by a parsing strategy.
1834
+ * @param responseFormat The originally requested response format.
1835
+ * @returns The value narrowed to `T`.
1836
+ * @throws {JsonParseError} When a supplied json_schema is not satisfied.
1837
+ */
1838
+ function finalizeJsonResult(value, responseFormat) {
1839
+ if (typeof responseFormat === 'object' && responseFormat?.type === 'json_schema') {
1840
+ validateJsonSchema(value, responseFormat.schema);
1841
+ }
1842
+ return value;
1843
+ }
1735
1844
 
1736
1845
  /**
1737
1846
  * Default timeout values (in milliseconds) for external service calls.
@@ -2003,11 +2112,19 @@ const DEFAULT_RETRY_CONFIG = {
2003
2112
  * @throws The last error encountered if all retry attempts fail or if a non-retryable error occurs
2004
2113
  */
2005
2114
  async function withRetry(fn, config = {}, label = 'unknown') {
2006
- const { maxRetries, baseDelayMs, maxDelayMs, retryableErrors, circuitBreaker } = {
2115
+ const { maxRetries, baseDelayMs, maxDelayMs, retryableErrors, circuitBreaker, signal } = {
2007
2116
  ...DEFAULT_RETRY_CONFIG,
2008
2117
  ...config,
2009
2118
  };
2010
2119
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
2120
+ // Short-circuit on cancellation BEFORE starting a fresh attempt so
2121
+ // the caller does not pay the cost of issuing a request whose result
2122
+ // they have already discarded.
2123
+ if (signal?.aborted) {
2124
+ throw signal.reason instanceof Error
2125
+ ? signal.reason
2126
+ : new Error('Aborted');
2127
+ }
2011
2128
  try {
2012
2129
  // If a circuit breaker is provided, execute through it
2013
2130
  const result = circuitBreaker
@@ -2020,6 +2137,13 @@ async function withRetry(fn, config = {}, label = 'unknown') {
2020
2137
  if (error instanceof CircuitBreakerOpenError) {
2021
2138
  throw error;
2022
2139
  }
2140
+ // Do not retry on cooperative cancellation — the caller has
2141
+ // explicitly asked us to stop.
2142
+ if (signal?.aborted) {
2143
+ throw signal.reason instanceof Error
2144
+ ? signal.reason
2145
+ : new Error('Aborted');
2146
+ }
2023
2147
  // Don't retry if we've exhausted all attempts
2024
2148
  if (attempt === maxRetries) {
2025
2149
  throw error;
@@ -2033,7 +2157,26 @@ async function withRetry(fn, config = {}, label = 'unknown') {
2033
2157
  const jitter = Math.random() * 1000;
2034
2158
  const delay = Math.min(exponentialDelay + jitter, maxDelayMs);
2035
2159
  getLumicLogger().warn(`[${label}] Attempt ${attempt + 1} failed, retrying in ${Math.round(delay)}ms`);
2036
- await new Promise((resolve) => setTimeout(resolve, delay));
2160
+ // Sleep but bail early if signal aborts mid-backoff.
2161
+ await new Promise((resolve, reject) => {
2162
+ const timer = setTimeout(() => {
2163
+ if (signal)
2164
+ signal.removeEventListener('abort', onAbort);
2165
+ resolve();
2166
+ }, delay);
2167
+ const onAbort = () => {
2168
+ clearTimeout(timer);
2169
+ reject(signal?.reason instanceof Error ? signal.reason : new Error('Aborted'));
2170
+ };
2171
+ if (signal) {
2172
+ if (signal.aborted) {
2173
+ clearTimeout(timer);
2174
+ reject(signal.reason instanceof Error ? signal.reason : new Error('Aborted'));
2175
+ return;
2176
+ }
2177
+ signal.addEventListener('abort', onAbort, { once: true });
2178
+ }
2179
+ });
2037
2180
  }
2038
2181
  }
2039
2182
  // This should never be reached, but TypeScript needs it for type safety
@@ -2320,15 +2463,13 @@ const isRetryableLLMError = (error) => {
2320
2463
  if (error instanceof Error) {
2321
2464
  const message = error.message;
2322
2465
  // Retry on rate limits (429)
2323
- if (message.includes("429") ||
2324
- message.includes("rate limit") ||
2325
- message.includes("Rate limit")) {
2466
+ if (message.includes('429') || message.includes('rate limit') || message.includes('Rate limit')) {
2326
2467
  return true;
2327
2468
  }
2328
2469
  // Retry on transient body-corruption 400s. Match the exact OpenAI error
2329
2470
  // string to avoid retrying genuine client-side validation 400s (which
2330
2471
  // would re-fail forever and waste retry budget).
2331
- if (message.includes("could not parse the JSON body of your request")) {
2472
+ if (message.includes('could not parse the JSON body of your request')) {
2332
2473
  return true;
2333
2474
  }
2334
2475
  }
@@ -2397,26 +2538,23 @@ function isReasoningModel(model) {
2397
2538
  * @throws Error if the response format is invalid or incompatible.
2398
2539
  */
2399
2540
  function getResponseFormatOption(responseFormat, normalizedModel) {
2400
- if (responseFormat === "text") {
2401
- return { type: "text" };
2541
+ if (responseFormat === 'text') {
2542
+ return { type: 'text' };
2402
2543
  }
2403
- if (responseFormat === "json") {
2404
- return supportsJsonMode(normalizedModel)
2405
- ? { type: "json_object" }
2406
- : { type: "text" };
2544
+ if (responseFormat === 'json') {
2545
+ return supportsJsonMode(normalizedModel) ? { type: 'json_object' } : { type: 'text' };
2407
2546
  }
2408
- if (typeof responseFormat === "object" &&
2409
- responseFormat.type === "json_schema") {
2547
+ if (typeof responseFormat === 'object' && responseFormat.type === 'json_schema') {
2410
2548
  if (!isStructuredOutputCompatibleModel(normalizedModel)) {
2411
2549
  throw new Error(`${normalizedModel} is not compatible with structured outputs / json object.`);
2412
2550
  }
2413
2551
  return {
2414
- type: "json_schema",
2552
+ type: 'json_schema',
2415
2553
  json_schema: {
2416
- type: "object",
2554
+ type: 'object',
2417
2555
  properties: responseFormat.schema.properties,
2418
2556
  required: responseFormat.schema.required || [],
2419
- name: "Response",
2557
+ name: 'Response',
2420
2558
  },
2421
2559
  };
2422
2560
  }
@@ -2475,7 +2613,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2475
2613
  const normalizedModel = normalizeModelName(options.model || DEFAULT_MODEL);
2476
2614
  const apiKey = options.apiKey || getSecrets().openai.apiKey;
2477
2615
  if (!apiKey) {
2478
- throw new Error("OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set");
2616
+ throw new Error('OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set');
2479
2617
  }
2480
2618
  const openai = new OpenAI({
2481
2619
  apiKey: apiKey,
@@ -2485,7 +2623,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2485
2623
  // Add developer prompt if present
2486
2624
  if (options.developerPrompt && supportsDeveloperPrompt(normalizedModel)) {
2487
2625
  messages.push({
2488
- role: "developer",
2626
+ role: 'developer',
2489
2627
  content: options.developerPrompt,
2490
2628
  });
2491
2629
  }
@@ -2494,15 +2632,15 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2494
2632
  messages.push(...options.context);
2495
2633
  }
2496
2634
  // Add user content
2497
- if (typeof content === "string") {
2635
+ if (typeof content === 'string') {
2498
2636
  messages.push({
2499
- role: "user",
2637
+ role: 'user',
2500
2638
  content,
2501
2639
  });
2502
2640
  }
2503
2641
  else {
2504
2642
  messages.push({
2505
- role: "user",
2643
+ role: 'user',
2506
2644
  content,
2507
2645
  });
2508
2646
  }
@@ -2516,8 +2654,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2516
2654
  queryOptions.tools = options.tools;
2517
2655
  }
2518
2656
  // Only include temperature if the model supports it
2519
- if (options.temperature !== undefined &&
2520
- supportsTemperature(normalizedModel)) {
2657
+ if (options.temperature !== undefined && supportsTemperature(normalizedModel)) {
2521
2658
  queryOptions.temperature = options.temperature;
2522
2659
  }
2523
2660
  // Only include max_completion_tokens when specified
@@ -2527,16 +2664,25 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2527
2664
  // Only set response_format when it's not the default 'text' type
2528
2665
  // (sending response_format: {type: 'text'} is redundant and may cause issues)
2529
2666
  const responseFormatOption = getResponseFormatOption(responseFormat, normalizedModel);
2530
- if (responseFormatOption.type !== "text") {
2667
+ if (responseFormatOption.type !== 'text') {
2531
2668
  queryOptions.response_format = responseFormatOption;
2532
2669
  }
2533
2670
  let completion;
2534
2671
  try {
2535
- completion = await withRetry(() => openai.chat.completions.create(queryOptions), {
2672
+ completion = await withRetry(
2673
+ // Pass the AbortSignal into the OpenAI SDK request-options object.
2674
+ // The OpenAI SDK honours `signal` to cancel the in-flight fetch
2675
+ // immediately when it fires — this is what makes caller-driven
2676
+ // cancel-and-restart (e.g. signal-monitoring's materiality
2677
+ // re-trigger path in the engine) actually release the HTTP
2678
+ // connection and response-body buffer rather than just discarding
2679
+ // the resolved value at the engine boundary.
2680
+ () => openai.chat.completions.create(queryOptions, options.signal ? { signal: options.signal } : undefined), {
2536
2681
  maxRetries: 3,
2537
2682
  baseDelayMs: 2000,
2538
2683
  maxDelayMs: 30000,
2539
2684
  retryableErrors: isRetryableLLMError,
2685
+ signal: options.signal,
2540
2686
  }, `OpenAI:${normalizedModel}`);
2541
2687
  }
2542
2688
  catch (error) {
@@ -2548,19 +2694,15 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2548
2694
  // for production prompts containing sensitive context.
2549
2695
  const errorMessage = error instanceof Error ? error.message : String(error);
2550
2696
  const totalContentChars = messages.reduce((sum, msg) => {
2551
- if (typeof msg.content === "string")
2697
+ if (typeof msg.content === 'string')
2552
2698
  return sum + msg.content.length;
2553
2699
  if (Array.isArray(msg.content)) {
2554
- return (sum +
2555
- msg.content.reduce((s, part) => {
2556
- if (typeof part === "object" &&
2557
- part !== null &&
2558
- "text" in part &&
2559
- typeof part.text === "string") {
2560
- return s + part.text.length;
2561
- }
2562
- return s;
2563
- }, 0));
2700
+ return sum + msg.content.reduce((s, part) => {
2701
+ if (typeof part === 'object' && part !== null && 'text' in part && typeof part.text === 'string') {
2702
+ return s + part.text.length;
2703
+ }
2704
+ return s;
2705
+ }, 0);
2564
2706
  }
2565
2707
  return sum;
2566
2708
  }, 0);
@@ -2587,7 +2729,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2587
2729
  const cachedTokens = completion.usage?.prompt_tokens_details?.cached_tokens ?? 0;
2588
2730
  const response = {
2589
2731
  id: completion.id,
2590
- content: completion.choices[0]?.message?.content || "",
2732
+ content: completion.choices[0]?.message?.content || '',
2591
2733
  tool_calls: completion.choices[0]?.message?.tool_calls,
2592
2734
  usage: {
2593
2735
  prompt_tokens: completion.usage?.prompt_tokens ?? 0,
@@ -2597,7 +2739,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2597
2739
  },
2598
2740
  system_fingerprint: completion.system_fingerprint,
2599
2741
  service_tier: options.service_tier,
2600
- provider: "openai",
2742
+ provider: 'openai',
2601
2743
  model: normalizedModel,
2602
2744
  };
2603
2745
  return response;
@@ -2610,7 +2752,7 @@ async function createCompletion(content, responseFormat, options = DEFAULT_OPTIO
2610
2752
  * @param options The options for the LLM call. Defaults to an empty object.
2611
2753
  * @return A promise that resolves to the response from the LLM.
2612
2754
  */
2613
- const makeOpenAIChatCompletionCall = async (content, responseFormat = "text", options = {}) => {
2755
+ const makeOpenAIChatCompletionCall = async (content, responseFormat = 'text', options = {}) => {
2614
2756
  const mergedOptions = {
2615
2757
  ...DEFAULT_OPTIONS$1,
2616
2758
  ...options,
@@ -2619,7 +2761,7 @@ const makeOpenAIChatCompletionCall = async (content, responseFormat = "text", op
2619
2761
  // Track cost in the global cost tracker. Pass cached tokens through so the
2620
2762
  // tracker applies the discounted cached-input rate (typically ~50% of the
2621
2763
  // standard input rate) instead of billing every input token at full price.
2622
- getLLMCostTracker().trackUsage("openai", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens);
2764
+ getLLMCostTracker().trackUsage('openai', completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens);
2623
2765
  // Handle tool calls differently
2624
2766
  if (completion.tool_calls && completion.tool_calls.length > 0) {
2625
2767
  const toolCallResponse = {
@@ -2635,10 +2777,10 @@ const makeOpenAIChatCompletionCall = async (content, responseFormat = "text", op
2635
2777
  prompt_tokens: completion.usage.prompt_tokens,
2636
2778
  completion_tokens: completion.usage.completion_tokens,
2637
2779
  reasoning_tokens: 0,
2638
- provider: "openai",
2780
+ provider: 'openai',
2639
2781
  model: completion.model,
2640
2782
  cached_tokens: completion.usage.cached_tokens,
2641
- cost: calculateCost("openai", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
2783
+ cost: calculateCost('openai', completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
2642
2784
  },
2643
2785
  tool_calls: completion.tool_calls,
2644
2786
  };
@@ -2646,7 +2788,7 @@ const makeOpenAIChatCompletionCall = async (content, responseFormat = "text", op
2646
2788
  // Handle regular responses
2647
2789
  const parsedResponse = await parseResponse(completion.content, responseFormat);
2648
2790
  if (parsedResponse === null) {
2649
- throw new Error("Failed to parse response");
2791
+ throw new Error('Failed to parse response');
2650
2792
  }
2651
2793
  return {
2652
2794
  response: parsedResponse,
@@ -2654,10 +2796,10 @@ const makeOpenAIChatCompletionCall = async (content, responseFormat = "text", op
2654
2796
  prompt_tokens: completion.usage.prompt_tokens,
2655
2797
  completion_tokens: completion.usage.completion_tokens,
2656
2798
  reasoning_tokens: 0,
2657
- provider: "openai",
2799
+ provider: 'openai',
2658
2800
  model: completion.model,
2659
2801
  cached_tokens: completion.usage.cached_tokens,
2660
- cost: calculateCost("openai", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
2802
+ cost: calculateCost('openai', completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
2661
2803
  },
2662
2804
  tool_calls: completion.tool_calls,
2663
2805
  };
@@ -2692,44 +2834,45 @@ const makeResponsesAPICall = async (input, options = {}) => {
2692
2834
  const normalizedModel = normalizeModelName(options.model || DEFAULT_MODEL);
2693
2835
  const apiKey = options.apiKey || getSecrets().openai.apiKey;
2694
2836
  if (!apiKey) {
2695
- throw new Error("OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set");
2837
+ throw new Error('OpenAI API key is not provided and OPENAI_API_KEY environment variable is not set');
2696
2838
  }
2697
2839
  const openai = new OpenAI({
2698
2840
  apiKey: apiKey,
2699
2841
  timeout: options.timeout ?? LLM_TIMEOUT_MS,
2700
2842
  });
2701
- // Remove apiKey, model, and timeout from options before creating request body
2702
- const { apiKey: _, model: __, timeout: ___, ...cleanOptions } = options;
2843
+ // Remove apiKey, model, timeout, and signal from options before creating request body
2844
+ const { apiKey: _, model: __, timeout: ___, signal: ____, ...cleanOptions } = options;
2703
2845
  const requestBody = {
2704
2846
  model: normalizedModel,
2705
2847
  input,
2706
2848
  ...cleanOptions,
2707
2849
  };
2708
2850
  // Make the API call to the Responses endpoint
2709
- const response = await withRetry(() => openai.responses.create(requestBody), {
2851
+ const response = await withRetry(() => openai.responses.create(requestBody, options.signal ? { signal: options.signal } : undefined), {
2710
2852
  maxRetries: 3,
2711
2853
  baseDelayMs: 2000,
2712
2854
  maxDelayMs: 30000,
2713
2855
  retryableErrors: isRetryableLLMError,
2856
+ signal: options.signal,
2714
2857
  }, `OpenAI-Responses:${normalizedModel}`);
2715
2858
  // Responses API exposes cached input tokens under `input_tokens_details.cached_tokens`
2716
2859
  // (the equivalent of Chat Completions' `prompt_tokens_details.cached_tokens`).
2717
2860
  const responsesCachedTokens = response.usage?.input_tokens_details?.cached_tokens || 0;
2718
2861
  // Track cost in the global cost tracker
2719
- getLLMCostTracker().trackUsage("openai", normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, responsesCachedTokens);
2862
+ getLLMCostTracker().trackUsage('openai', normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, responsesCachedTokens);
2720
2863
  // Extract tool calls from the output
2721
2864
  const toolCalls = response.output
2722
- ?.filter((item) => item.type === "function_call")
2865
+ ?.filter((item) => item.type === 'function_call')
2723
2866
  .map((toolCall) => ({
2724
2867
  id: toolCall.call_id,
2725
- type: "function",
2868
+ type: 'function',
2726
2869
  function: {
2727
2870
  name: toolCall.name,
2728
2871
  arguments: toolCall.arguments,
2729
2872
  },
2730
2873
  }));
2731
2874
  // Extract code interpreter outputs if present
2732
- const codeInterpreterCalls = response.output?.filter((item) => item.type === "code_interpreter_call");
2875
+ const codeInterpreterCalls = response.output?.filter((item) => item.type === 'code_interpreter_call');
2733
2876
  let codeInterpreterOutputs = undefined;
2734
2877
  if (codeInterpreterCalls && codeInterpreterCalls.length > 0) {
2735
2878
  // Each code_interpreter_call has an 'outputs' array property
@@ -2755,34 +2898,32 @@ const makeResponsesAPICall = async (input, options = {}) => {
2755
2898
  prompt_tokens: response.usage?.input_tokens || 0,
2756
2899
  completion_tokens: response.usage?.output_tokens || 0,
2757
2900
  reasoning_tokens: response.usage?.output_tokens_details?.reasoning_tokens || 0,
2758
- provider: "openai",
2901
+ provider: 'openai',
2759
2902
  model: normalizedModel,
2760
2903
  cached_tokens: responsesCachedTokens,
2761
- cost: calculateCost("openai", normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, responsesCachedTokens),
2904
+ cost: calculateCost('openai', normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, responsesCachedTokens),
2762
2905
  },
2763
2906
  tool_calls: toolCalls,
2764
- ...(codeInterpreterOutputs
2765
- ? { code_interpreter_outputs: codeInterpreterOutputs }
2766
- : {}),
2907
+ ...(codeInterpreterOutputs ? { code_interpreter_outputs: codeInterpreterOutputs } : {}),
2767
2908
  };
2768
2909
  }
2769
2910
  // Extract text content from the response output
2770
2911
  const textContent = response.output
2771
- ?.filter((item) => item.type === "message")
2912
+ ?.filter((item) => item.type === 'message')
2772
2913
  .map((item) => item.content
2773
- .filter((content) => content.type === "output_text")
2914
+ .filter((content) => content.type === 'output_text')
2774
2915
  .map((content) => content.text)
2775
- .join(""))
2776
- .join("") || "";
2916
+ .join(''))
2917
+ .join('') || '';
2777
2918
  // Determine the format for parsing the response
2778
- let parsingFormat = "text";
2779
- if (requestBody.text?.format?.type === "json_object") {
2780
- parsingFormat = "json";
2919
+ let parsingFormat = 'text';
2920
+ if (requestBody.text?.format?.type === 'json_object') {
2921
+ parsingFormat = 'json';
2781
2922
  }
2782
2923
  // Handle regular responses
2783
2924
  const parsedResponse = await parseResponse(textContent, parsingFormat);
2784
2925
  if (parsedResponse === null) {
2785
- throw new Error("Failed to parse response from Responses API");
2926
+ throw new Error('Failed to parse response from Responses API');
2786
2927
  }
2787
2928
  return {
2788
2929
  response: parsedResponse,
@@ -2790,15 +2931,13 @@ const makeResponsesAPICall = async (input, options = {}) => {
2790
2931
  prompt_tokens: response.usage?.input_tokens || 0,
2791
2932
  completion_tokens: response.usage?.output_tokens || 0,
2792
2933
  reasoning_tokens: response.usage?.output_tokens_details?.reasoning_tokens || 0,
2793
- provider: "openai",
2934
+ provider: 'openai',
2794
2935
  model: normalizedModel,
2795
2936
  cached_tokens: responsesCachedTokens,
2796
- cost: calculateCost("openai", normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, responsesCachedTokens),
2937
+ cost: calculateCost('openai', normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, responsesCachedTokens),
2797
2938
  },
2798
2939
  tool_calls: toolCalls,
2799
- ...(codeInterpreterOutputs
2800
- ? { code_interpreter_outputs: codeInterpreterOutputs }
2801
- : {}),
2940
+ ...(codeInterpreterOutputs ? { code_interpreter_outputs: codeInterpreterOutputs } : {}),
2802
2941
  };
2803
2942
  };
2804
2943
 
@@ -8108,12 +8247,12 @@ function sanitizeObject(obj, sensitiveFields = ['apiKey', 'token', 'secret', 'pa
8108
8247
  const isRetryableAnthropicError = (error) => {
8109
8248
  if (error instanceof Error) {
8110
8249
  const message = error.message;
8111
- if (message.includes("429") ||
8112
- message.includes("500") ||
8113
- message.includes("503") ||
8114
- message.includes("529") ||
8115
- message.includes("rate limit") ||
8116
- message.includes("overloaded")) {
8250
+ if (message.includes('429') ||
8251
+ message.includes('500') ||
8252
+ message.includes('503') ||
8253
+ message.includes('529') ||
8254
+ message.includes('rate limit') ||
8255
+ message.includes('overloaded')) {
8117
8256
  return true;
8118
8257
  }
8119
8258
  }
@@ -8128,11 +8267,8 @@ const isRetryableAnthropicError = (error) => {
8128
8267
  function translateToolsToAnthropic(tools) {
8129
8268
  return tools.map((tool) => ({
8130
8269
  name: tool.function.name,
8131
- description: tool.function.description || "",
8132
- input_schema: (tool.function.parameters || {
8133
- type: "object",
8134
- properties: {},
8135
- }),
8270
+ description: tool.function.description || '',
8271
+ input_schema: (tool.function.parameters || { type: 'object', properties: {} }),
8136
8272
  }));
8137
8273
  }
8138
8274
  /**
@@ -8145,31 +8281,27 @@ function translateContextToAnthropic(context) {
8145
8281
  const systemParts = [];
8146
8282
  const messages = [];
8147
8283
  for (const msg of context) {
8148
- if (msg.role === "system" || msg.role === "developer") {
8149
- if (typeof msg.content === "string") {
8284
+ if (msg.role === 'system' || msg.role === 'developer') {
8285
+ if (typeof msg.content === 'string') {
8150
8286
  systemParts.push(msg.content);
8151
8287
  }
8152
8288
  continue;
8153
8289
  }
8154
- if (msg.role === "user") {
8155
- const content = typeof msg.content === "string"
8290
+ if (msg.role === 'user') {
8291
+ const content = typeof msg.content === 'string'
8156
8292
  ? msg.content
8157
8293
  : JSON.stringify(msg.content);
8158
- messages.push({ role: "user", content });
8294
+ messages.push({ role: 'user', content });
8159
8295
  continue;
8160
8296
  }
8161
- if (msg.role === "assistant") {
8297
+ if (msg.role === 'assistant') {
8162
8298
  const assistantMsg = msg;
8163
8299
  // If the assistant message has tool_calls, translate to Anthropic tool_use blocks
8164
8300
  if (assistantMsg.tool_calls && assistantMsg.tool_calls.length > 0) {
8165
8301
  const contentBlocks = [];
8166
8302
  // Include text content if present
8167
- if (typeof assistantMsg.content === "string" &&
8168
- assistantMsg.content.trim()) {
8169
- contentBlocks.push({
8170
- type: "text",
8171
- text: assistantMsg.content,
8172
- });
8303
+ if (typeof assistantMsg.content === 'string' && assistantMsg.content.trim()) {
8304
+ contentBlocks.push({ type: 'text', text: assistantMsg.content });
8173
8305
  }
8174
8306
  // Translate each tool_call to a tool_use block
8175
8307
  for (const tc of assistantMsg.tool_calls) {
@@ -8181,36 +8313,32 @@ function translateContextToAnthropic(context) {
8181
8313
  input = { raw: tc.function.arguments };
8182
8314
  }
8183
8315
  contentBlocks.push({
8184
- type: "tool_use",
8316
+ type: 'tool_use',
8185
8317
  id: tc.id,
8186
8318
  name: tc.function.name,
8187
8319
  input,
8188
8320
  });
8189
8321
  }
8190
- messages.push({ role: "assistant", content: contentBlocks });
8322
+ messages.push({ role: 'assistant', content: contentBlocks });
8191
8323
  }
8192
8324
  else {
8193
- const content = typeof assistantMsg.content === "string"
8325
+ const content = typeof assistantMsg.content === 'string'
8194
8326
  ? assistantMsg.content
8195
8327
  : JSON.stringify(assistantMsg.content);
8196
- messages.push({ role: "assistant", content });
8328
+ messages.push({ role: 'assistant', content });
8197
8329
  }
8198
8330
  continue;
8199
8331
  }
8200
- if (msg.role === "tool") {
8332
+ if (msg.role === 'tool') {
8201
8333
  // Anthropic expects tool results as user messages with tool_result content blocks
8202
8334
  const toolMsg = msg;
8203
8335
  messages.push({
8204
- role: "user",
8205
- content: [
8206
- {
8207
- type: "tool_result",
8336
+ role: 'user',
8337
+ content: [{
8338
+ type: 'tool_result',
8208
8339
  tool_use_id: toolMsg.tool_call_id,
8209
- content: typeof toolMsg.content === "string"
8210
- ? toolMsg.content
8211
- : JSON.stringify(toolMsg.content),
8212
- },
8213
- ],
8340
+ content: typeof toolMsg.content === 'string' ? toolMsg.content : JSON.stringify(toolMsg.content),
8341
+ }],
8214
8342
  });
8215
8343
  continue;
8216
8344
  }
@@ -8231,13 +8359,13 @@ function translateContextToAnthropic(context) {
8231
8359
  merged.push({ role: msg.role, content: toContentBlocks(msg.content) });
8232
8360
  }
8233
8361
  }
8234
- return { messages: merged, systemText: systemParts.join("\n\n") };
8362
+ return { messages: merged, systemText: systemParts.join('\n\n') };
8235
8363
  }
8236
8364
  /** Convert string or content block array to a uniform content block array. */
8237
8365
  function toContentBlocks(content) {
8238
- if (typeof content === "string") {
8366
+ if (typeof content === 'string') {
8239
8367
  const textBlock = {
8240
- type: "text",
8368
+ type: 'text',
8241
8369
  text: content,
8242
8370
  citations: null,
8243
8371
  };
@@ -8257,11 +8385,11 @@ function toContentBlocks(content) {
8257
8385
  * @param options The options for the LLM call.
8258
8386
  * @return A promise that resolves to the response from the Anthropic API.
8259
8387
  */
8260
- async function makeAnthropicCall(content, responseFormat = "text", options = {}) {
8261
- const model = (options.model || "claude-sonnet-4-6");
8388
+ async function makeAnthropicCall(content, responseFormat = 'text', options = {}) {
8389
+ const model = (options.model || 'claude-sonnet-4-6');
8262
8390
  const apiKey = options.apiKey || getSecrets().anthropic.apiKey;
8263
8391
  if (!apiKey) {
8264
- throw new Error("Anthropic API key is not provided and ANTHROPIC_API_KEY environment variable is not set");
8392
+ throw new Error('Anthropic API key is not provided and ANTHROPIC_API_KEY environment variable is not set');
8265
8393
  }
8266
8394
  const client = new Anthropic({
8267
8395
  apiKey,
@@ -8273,10 +8401,8 @@ async function makeAnthropicCall(content, responseFormat = "text", options = {})
8273
8401
  systemParts.push(options.developerPrompt);
8274
8402
  }
8275
8403
  // If JSON response is requested, instruct the model via system prompt
8276
- if (responseFormat === "json" ||
8277
- (typeof responseFormat === "object" &&
8278
- responseFormat.type === "json_schema")) {
8279
- systemParts.push("You MUST respond with valid JSON only. No markdown, no code blocks, no explanatory text — just the raw JSON object or array.");
8404
+ if (responseFormat === 'json' || (typeof responseFormat === 'object' && responseFormat.type === 'json_schema')) {
8405
+ systemParts.push('You MUST respond with valid JSON only. No markdown, no code blocks, no explanatory text — just the raw JSON object or array.');
8280
8406
  }
8281
8407
  // Build messages array
8282
8408
  const messages = [];
@@ -8289,7 +8415,7 @@ async function makeAnthropicCall(content, responseFormat = "text", options = {})
8289
8415
  messages.push(...translated.messages);
8290
8416
  }
8291
8417
  // Add user content
8292
- messages.push({ role: "user", content });
8418
+ messages.push({ role: 'user', content });
8293
8419
  // Build request params
8294
8420
  const requestParams = {
8295
8421
  model,
@@ -8298,37 +8424,46 @@ async function makeAnthropicCall(content, responseFormat = "text", options = {})
8298
8424
  };
8299
8425
  // Add system prompt if present
8300
8426
  if (systemParts.length > 0) {
8301
- requestParams.system = systemParts.join("\n\n");
8302
- }
8303
- // Add temperature if specified
8304
- if (options.temperature !== undefined) {
8305
- requestParams.temperature = options.temperature;
8306
- }
8307
- if (options.top_p !== undefined) {
8308
- requestParams.top_p = options.top_p;
8309
- }
8427
+ requestParams.system = systemParts.join('\n\n');
8428
+ }
8429
+ // Sampling controls are intentionally disabled for the Anthropic provider.
8430
+ //
8431
+ // The claude-*-4.x model family rejects any request that specifies BOTH
8432
+ // `temperature` and `top_p` ("`temperature` and `top_p` cannot both be
8433
+ // specified for this model. Please use only one." — HTTP 400), which aborts
8434
+ // the entire caller flow (e.g. the engine's account decision session).
8435
+ // Callers across the org pass both knobs generically, so rather than make
8436
+ // each one provider-aware we drop both here and let Anthropic use its model
8437
+ // defaults. `options.temperature` / `options.top_p` are intentionally not
8438
+ // forwarded. This is the Anthropic adapter only — the OpenAI, DeepSeek, and
8439
+ // OpenAI-compatible adapters still honor `temperature` and `top_p`.
8310
8440
  // Translate and add tools if present
8311
8441
  if (options.tools && options.tools.length > 0) {
8312
8442
  requestParams.tools = translateToolsToAnthropic(options.tools);
8313
8443
  }
8314
8444
  try {
8315
- const response = await withRetry(() => client.messages.create(requestParams), {
8445
+ const response = await withRetry(
8446
+ // Pass AbortSignal into the Anthropic SDK request-options object
8447
+ // so caller-driven cancellation interrupts the in-flight HTTP
8448
+ // request immediately. See LLMOptions.signal JSDoc for context.
8449
+ () => client.messages.create(requestParams, options.signal ? { signal: options.signal } : undefined), {
8316
8450
  maxRetries: 3,
8317
8451
  baseDelayMs: 2000,
8318
8452
  maxDelayMs: 30000,
8319
8453
  retryableErrors: isRetryableAnthropicError,
8454
+ signal: options.signal,
8320
8455
  }, `Anthropic:${model}`);
8321
8456
  const inputTokens = response.usage.input_tokens;
8322
8457
  const outputTokens = response.usage.output_tokens;
8323
8458
  // Track cost
8324
- getLLMCostTracker().trackUsage("anthropic", model, inputTokens, outputTokens);
8459
+ getLLMCostTracker().trackUsage('anthropic', model, inputTokens, outputTokens);
8325
8460
  // Extract tool use blocks
8326
- const toolUseBlocks = response.content.filter((block) => block.type === "tool_use");
8461
+ const toolUseBlocks = response.content.filter((block) => block.type === 'tool_use');
8327
8462
  if (toolUseBlocks.length > 0) {
8328
8463
  // Translate Anthropic tool_use to OpenAI tool_calls format
8329
8464
  const toolCalls = toolUseBlocks.map((block) => ({
8330
8465
  id: block.id,
8331
- type: "function",
8466
+ type: 'function',
8332
8467
  function: {
8333
8468
  name: block.name,
8334
8469
  arguments: JSON.stringify(block.input),
@@ -8347,22 +8482,22 @@ async function makeAnthropicCall(content, responseFormat = "text", options = {})
8347
8482
  prompt_tokens: inputTokens,
8348
8483
  completion_tokens: outputTokens,
8349
8484
  reasoning_tokens: 0,
8350
- provider: "anthropic",
8485
+ provider: 'anthropic',
8351
8486
  model,
8352
- cost: calculateCost("anthropic", model, inputTokens, outputTokens),
8487
+ cost: calculateCost('anthropic', model, inputTokens, outputTokens),
8353
8488
  },
8354
8489
  tool_calls: toolCalls,
8355
8490
  };
8356
8491
  }
8357
8492
  // Extract text content
8358
8493
  const textContent = response.content
8359
- .filter((block) => block.type === "text")
8494
+ .filter((block) => block.type === 'text')
8360
8495
  .map((block) => block.text)
8361
- .join("");
8496
+ .join('');
8362
8497
  // Parse response
8363
8498
  const parsedResponse = await parseResponse(textContent, responseFormat);
8364
8499
  if (parsedResponse === null) {
8365
- throw new Error("Failed to parse Anthropic response");
8500
+ throw new Error('Failed to parse Anthropic response');
8366
8501
  }
8367
8502
  return {
8368
8503
  response: parsedResponse,
@@ -8370,9 +8505,9 @@ async function makeAnthropicCall(content, responseFormat = "text", options = {})
8370
8505
  prompt_tokens: inputTokens,
8371
8506
  completion_tokens: outputTokens,
8372
8507
  reasoning_tokens: 0,
8373
- provider: "anthropic",
8508
+ provider: 'anthropic',
8374
8509
  model,
8375
- cost: calculateCost("anthropic", model, inputTokens, outputTokens),
8510
+ cost: calculateCost('anthropic', model, inputTokens, outputTokens),
8376
8511
  },
8377
8512
  };
8378
8513
  }
@@ -8394,9 +8529,7 @@ async function makeAnthropicCall(content, responseFormat = "text", options = {})
8394
8529
  const isRetryableError = (error) => {
8395
8530
  if (error instanceof Error) {
8396
8531
  const message = error.message;
8397
- if (message.includes("429") ||
8398
- message.includes("rate limit") ||
8399
- message.includes("Rate limit")) {
8532
+ if (message.includes('429') || message.includes('rate limit') || message.includes('Rate limit')) {
8400
8533
  return true;
8401
8534
  }
8402
8535
  }
@@ -8410,16 +8543,14 @@ function resolveApiKey(provider, optionsApiKey) {
8410
8543
  if (optionsApiKey)
8411
8544
  return optionsApiKey;
8412
8545
  const secrets = getSecrets();
8413
- const pathParts = provider.apiKeySecretPath.split(".");
8546
+ const pathParts = provider.apiKeySecretPath.split('.');
8414
8547
  let current = secrets;
8415
8548
  for (const part of pathParts) {
8416
- if (current === null ||
8417
- current === undefined ||
8418
- typeof current !== "object")
8419
- return "";
8549
+ if (current === null || current === undefined || typeof current !== 'object')
8550
+ return '';
8420
8551
  current = current[part];
8421
8552
  }
8422
- return (typeof current === "string" ? current : "") || "";
8553
+ return (typeof current === 'string' ? current : '') || '';
8423
8554
  }
8424
8555
  /**
8425
8556
  * Makes a call to an OpenAI-compatible provider (Kimi, Qwen, or any future provider).
@@ -8433,12 +8564,12 @@ function resolveApiKey(provider, optionsApiKey) {
8433
8564
  * @param providerName The provider key in OPENAI_COMPATIBLE_PROVIDERS.
8434
8565
  * @return A promise that resolves to the response from the provider.
8435
8566
  */
8436
- async function makeOpenAICompatibleCall(content, responseFormat = "text", options = {}, providerName) {
8567
+ async function makeOpenAICompatibleCall(content, responseFormat = 'text', options = {}, providerName) {
8437
8568
  const providerConfig = OPENAI_COMPATIBLE_PROVIDERS[providerName];
8438
8569
  if (!providerConfig) {
8439
8570
  throw new Error(`Unknown OpenAI-compatible provider: ${providerName}`);
8440
8571
  }
8441
- const model = normalizeModelName(options.model || "");
8572
+ const model = normalizeModelName(options.model || '');
8442
8573
  const apiKey = resolveApiKey(providerConfig, options.apiKey);
8443
8574
  if (!apiKey) {
8444
8575
  throw new Error(`${providerConfig.name} API key is not provided and ${providerConfig.apiKeyEnvVar} environment variable is not set`);
@@ -8451,36 +8582,31 @@ async function makeOpenAICompatibleCall(content, responseFormat = "text", option
8451
8582
  const messages = [];
8452
8583
  // Add system message with developer prompt if present
8453
8584
  if (options.developerPrompt) {
8454
- messages.push({ role: "system", content: options.developerPrompt });
8585
+ messages.push({ role: 'system', content: options.developerPrompt });
8455
8586
  }
8456
8587
  // Add context if present
8457
8588
  if (options.context) {
8458
8589
  messages.push(...options.context);
8459
8590
  }
8460
8591
  // Add user content
8461
- if (typeof content === "string") {
8462
- messages.push({ role: "user", content });
8592
+ if (typeof content === 'string') {
8593
+ messages.push({ role: 'user', content });
8463
8594
  }
8464
8595
  else {
8465
- messages.push({ role: "user", content });
8596
+ messages.push({ role: 'user', content });
8466
8597
  }
8467
8598
  // Determine if the model supports JSON mode
8468
8599
  const supportsJson = isValidModel(model) && getModelCapabilities(model).supportsJson;
8469
8600
  // If JSON response format, add hint to system prompt
8470
- if ((responseFormat === "json" ||
8471
- (typeof responseFormat === "object" &&
8472
- responseFormat.type === "json_schema")) &&
8601
+ if ((responseFormat === 'json' || (typeof responseFormat === 'object' && responseFormat.type === 'json_schema')) &&
8473
8602
  supportsJson) {
8474
- if (!messages.some((m) => m.role === "system")) {
8475
- messages.unshift({
8476
- role: "system",
8477
- content: "Please respond in valid JSON format.",
8478
- });
8603
+ if (!messages.some((m) => m.role === 'system')) {
8604
+ messages.unshift({ role: 'system', content: 'Please respond in valid JSON format.' });
8479
8605
  }
8480
8606
  else {
8481
- const systemMsgIndex = messages.findIndex((m) => m.role === "system");
8607
+ const systemMsgIndex = messages.findIndex((m) => m.role === 'system');
8482
8608
  const systemMsg = messages[systemMsgIndex];
8483
- if (typeof systemMsg.content === "string") {
8609
+ if (typeof systemMsg.content === 'string') {
8484
8610
  messages[systemMsgIndex] = {
8485
8611
  ...systemMsg,
8486
8612
  content: `${systemMsg.content} Please respond in valid JSON format.`,
@@ -8493,8 +8619,8 @@ async function makeOpenAICompatibleCall(content, responseFormat = "text", option
8493
8619
  messages,
8494
8620
  };
8495
8621
  // Add response format if JSON is supported
8496
- if (responseFormat !== "text" && supportsJson) {
8497
- queryOptions.response_format = { type: "json_object" };
8622
+ if (responseFormat !== 'text' && supportsJson) {
8623
+ queryOptions.response_format = { type: 'json_object' };
8498
8624
  }
8499
8625
  // Temperature
8500
8626
  if (options.temperature !== undefined) {
@@ -8518,11 +8644,16 @@ async function makeOpenAICompatibleCall(content, responseFormat = "text", option
8518
8644
  queryOptions.tools = options.tools;
8519
8645
  }
8520
8646
  try {
8521
- const completion = await withRetry(() => openai.chat.completions.create(queryOptions), {
8647
+ const completion = await withRetry(
8648
+ // Pass AbortSignal into the SDK request-options object so
8649
+ // caller-driven cancellation interrupts the in-flight HTTP
8650
+ // request immediately. See LLMOptions.signal JSDoc for context.
8651
+ () => openai.chat.completions.create(queryOptions, options.signal ? { signal: options.signal } : undefined), {
8522
8652
  maxRetries: 3,
8523
8653
  baseDelayMs: 2000,
8524
8654
  maxDelayMs: 30000,
8525
8655
  retryableErrors: isRetryableError,
8656
+ signal: options.signal,
8526
8657
  }, `${providerConfig.name}:${model}`);
8527
8658
  const promptTokens = completion.usage?.prompt_tokens || 0;
8528
8659
  const completionTokens = completion.usage?.completion_tokens || 0;
@@ -8552,7 +8683,7 @@ async function makeOpenAICompatibleCall(content, responseFormat = "text", option
8552
8683
  };
8553
8684
  }
8554
8685
  // Handle regular responses
8555
- const textContent = completion.choices[0]?.message?.content || "";
8686
+ const textContent = completion.choices[0]?.message?.content || '';
8556
8687
  const parsedResponse = await parseResponse(textContent, responseFormat);
8557
8688
  if (parsedResponse === null) {
8558
8689
  throw new Error(`Failed to parse ${providerConfig.name} response`);
@@ -8844,9 +8975,7 @@ const isRetryableDeepseekError = (error) => {
8844
8975
  if (error instanceof Error) {
8845
8976
  const message = error.message;
8846
8977
  // Retry only on rate limits (429)
8847
- if (message.includes("429") ||
8848
- message.includes("rate limit") ||
8849
- message.includes("Rate limit")) {
8978
+ if (message.includes('429') || message.includes('rate limit') || message.includes('Rate limit')) {
8850
8979
  return true;
8851
8980
  }
8852
8981
  }
@@ -8856,7 +8985,7 @@ const isRetryableDeepseekError = (error) => {
8856
8985
  * Default options for Deepseek API calls
8857
8986
  */
8858
8987
  const DEFAULT_DEEPSEEK_OPTIONS = {
8859
- defaultModel: "deepseek-chat",
8988
+ defaultModel: 'deepseek-chat',
8860
8989
  };
8861
8990
  /**
8862
8991
  * Checks if the given model is a supported Deepseek model
@@ -8868,7 +8997,7 @@ const isSupportedDeepseekModel = (model) => {
8868
8997
  return false;
8869
8998
  }
8870
8999
  const capabilities = getModelCapabilities(model);
8871
- return capabilities.provider === "deepseek";
9000
+ return capabilities.provider === 'deepseek';
8872
9001
  };
8873
9002
  /**
8874
9003
  * Checks if the given Deepseek model supports JSON output format
@@ -8901,19 +9030,17 @@ const supportsToolCalling = (model) => {
8901
9030
  */
8902
9031
  function getDeepseekResponseFormatOption(responseFormat, model) {
8903
9032
  // Check if the model supports JSON output
8904
- if (responseFormat !== "text" && !supportsJsonOutput(model)) {
9033
+ if (responseFormat !== 'text' && !supportsJsonOutput(model)) {
8905
9034
  getLumicLogger().warn(`Model ${model} does not support JSON output. Using text format instead.`);
8906
- return { type: "text" };
9035
+ return { type: 'text' };
8907
9036
  }
8908
- if (responseFormat === "text") {
8909
- return { type: "text" };
9037
+ if (responseFormat === 'text') {
9038
+ return { type: 'text' };
8910
9039
  }
8911
- if (responseFormat === "json" ||
8912
- (typeof responseFormat === "object" &&
8913
- responseFormat.type === "json_schema")) {
8914
- return { type: "json_object" };
9040
+ if (responseFormat === 'json' || (typeof responseFormat === 'object' && responseFormat.type === 'json_schema')) {
9041
+ return { type: 'json_object' };
8915
9042
  }
8916
- return { type: "text" };
9043
+ return { type: 'text' };
8917
9044
  }
8918
9045
  /**
8919
9046
  * Creates a completion using the Deepseek API
@@ -8932,26 +9059,24 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
8932
9059
  throw new Error(`Unsupported Deepseek model: ${normalizedModel}. Please use 'deepseek-chat' or 'deepseek-reasoner'.`);
8933
9060
  }
8934
9061
  // Check if tools are requested with a model that doesn't support them
8935
- if (options.tools &&
8936
- options.tools.length > 0 &&
8937
- !supportsToolCalling(normalizedModel)) {
9062
+ if (options.tools && options.tools.length > 0 && !supportsToolCalling(normalizedModel)) {
8938
9063
  throw new Error(`Model ${normalizedModel} does not support tool calling.`);
8939
9064
  }
8940
9065
  const apiKey = options.apiKey || getSecrets().deepseek.apiKey;
8941
9066
  if (!apiKey) {
8942
- throw new Error("Deepseek API key is not provided and DEEPSEEK_API_KEY environment variable is not set");
9067
+ throw new Error('Deepseek API key is not provided and DEEPSEEK_API_KEY environment variable is not set');
8943
9068
  }
8944
9069
  // Initialize OpenAI client with Deepseek API URL
8945
9070
  const openai = new OpenAI({
8946
9071
  apiKey,
8947
- baseURL: "https://api.deepseek.com",
9072
+ baseURL: 'https://api.deepseek.com',
8948
9073
  timeout: options.timeout ?? LLM_TIMEOUT_MS,
8949
9074
  });
8950
9075
  const messages = [];
8951
9076
  // Add system message with developer prompt if present
8952
9077
  if (options.developerPrompt) {
8953
9078
  messages.push({
8954
- role: "system",
9079
+ role: 'system',
8955
9080
  content: options.developerPrompt,
8956
9081
  });
8957
9082
  }
@@ -8960,35 +9085,33 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
8960
9085
  messages.push(...options.context);
8961
9086
  }
8962
9087
  // Add user content
8963
- if (typeof content === "string") {
9088
+ if (typeof content === 'string') {
8964
9089
  messages.push({
8965
- role: "user",
9090
+ role: 'user',
8966
9091
  content,
8967
9092
  });
8968
9093
  }
8969
9094
  else {
8970
9095
  messages.push({
8971
- role: "user",
9096
+ role: 'user',
8972
9097
  content,
8973
9098
  });
8974
9099
  }
8975
9100
  // If JSON response format, include a hint in the system prompt
8976
- if ((responseFormat === "json" ||
8977
- (typeof responseFormat === "object" &&
8978
- responseFormat.type === "json_schema")) &&
8979
- supportsJsonOutput(normalizedModel)) {
9101
+ if ((responseFormat === 'json' || (typeof responseFormat === 'object' && responseFormat.type === 'json_schema'))
9102
+ && supportsJsonOutput(normalizedModel)) {
8980
9103
  // If there's no system message yet, add one
8981
- if (!messages.some((m) => m.role === "system")) {
9104
+ if (!messages.some(m => m.role === 'system')) {
8982
9105
  messages.unshift({
8983
- role: "system",
8984
- content: "Please respond in valid JSON format.",
9106
+ role: 'system',
9107
+ content: 'Please respond in valid JSON format.',
8985
9108
  });
8986
9109
  }
8987
9110
  else {
8988
9111
  // Append to existing system message
8989
- const systemMsgIndex = messages.findIndex((m) => m.role === "system");
9112
+ const systemMsgIndex = messages.findIndex(m => m.role === 'system');
8990
9113
  const systemMsg = messages[systemMsgIndex];
8991
- if (typeof systemMsg.content === "string") {
9114
+ if (typeof systemMsg.content === 'string') {
8992
9115
  messages[systemMsgIndex] = {
8993
9116
  ...systemMsg,
8994
9117
  content: `${systemMsg.content} Please respond in valid JSON format.`,
@@ -9029,7 +9152,7 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
9029
9152
  0;
9030
9153
  return {
9031
9154
  id: completion.id,
9032
- content: completion.choices[0]?.message?.content || "",
9155
+ content: completion.choices[0]?.message?.content || '',
9033
9156
  tool_calls: completion.choices[0]?.message?.tool_calls,
9034
9157
  usage: {
9035
9158
  prompt_tokens: completion.usage?.prompt_tokens ?? 0,
@@ -9038,7 +9161,7 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
9038
9161
  cached_tokens: cachedTokens,
9039
9162
  },
9040
9163
  system_fingerprint: completion.system_fingerprint,
9041
- provider: "deepseek",
9164
+ provider: 'deepseek',
9042
9165
  model: normalizedModel,
9043
9166
  };
9044
9167
  }
@@ -9054,8 +9177,12 @@ async function createDeepseekCompletion(content, responseFormat, options = {}) {
9054
9177
  * @param responseFormat The format of the response. Defaults to 'json'.
9055
9178
  * @param options Configuration options including model ('deepseek-chat' or 'deepseek-reasoner'), tools, and apiKey.
9056
9179
  * @return A promise that resolves to the response from the Deepseek API.
9180
+ * @throws {Error} If the requested capability (JSON output / tool calling) is
9181
+ * unsupported by the model, or if the underlying API call fails. This matches
9182
+ * the OpenAI and Anthropic providers, which surface failures by throwing
9183
+ * rather than returning a success-shaped response carrying an error payload.
9057
9184
  */
9058
- const makeDeepseekCall = async (content, responseFormat = "json", options = {}) => {
9185
+ const makeDeepseekCall = async (content, responseFormat = 'json', options = {}) => {
9059
9186
  // Set default model if not provided
9060
9187
  const mergedOptions = {
9061
9188
  ...options,
@@ -9064,51 +9191,21 @@ const makeDeepseekCall = async (content, responseFormat = "json", options = {})
9064
9191
  mergedOptions.model = DEFAULT_DEEPSEEK_OPTIONS.defaultModel;
9065
9192
  }
9066
9193
  const modelName = normalizeModelName(mergedOptions.model);
9067
- // Check if the requested response format is compatible with the model
9068
- if (responseFormat !== "text" && !supportsJsonOutput(modelName)) {
9069
- getLumicLogger().warn(`Model ${modelName} does not support JSON output. Will return error in the response.`);
9070
- return {
9071
- response: {
9072
- error: `Model ${modelName} does not support JSON output format.`,
9073
- },
9074
- usage: {
9075
- prompt_tokens: 0,
9076
- completion_tokens: 0,
9077
- reasoning_tokens: 0,
9078
- provider: "deepseek",
9079
- model: modelName,
9080
- cached_tokens: 0,
9081
- cost: 0,
9082
- },
9083
- tool_calls: undefined,
9084
- };
9194
+ // Check if the requested response format is compatible with the model.
9195
+ // Throw rather than returning a fake-success LLMResponse: a caller that
9196
+ // asked for JSON must not silently receive an { error } payload typed as T.
9197
+ if (responseFormat !== 'text' && !supportsJsonOutput(modelName)) {
9198
+ throw new Error(`Model ${modelName} does not support JSON output format.`);
9085
9199
  }
9086
- // Check if tools are requested with a model that doesn't support them
9087
- if (mergedOptions.tools &&
9088
- mergedOptions.tools.length > 0 &&
9089
- !supportsToolCalling(modelName)) {
9090
- getLumicLogger().warn(`Model ${modelName} does not support tool calling. Will return error in the response.`);
9091
- return {
9092
- response: {
9093
- error: `Model ${modelName} does not support tool calling.`,
9094
- },
9095
- usage: {
9096
- prompt_tokens: 0,
9097
- completion_tokens: 0,
9098
- reasoning_tokens: 0,
9099
- provider: "deepseek",
9100
- model: modelName,
9101
- cached_tokens: 0,
9102
- cost: 0,
9103
- },
9104
- tool_calls: undefined,
9105
- };
9200
+ // Check if tools are requested with a model that doesn't support them.
9201
+ if (mergedOptions.tools && mergedOptions.tools.length > 0 && !supportsToolCalling(modelName)) {
9202
+ throw new Error(`Model ${modelName} does not support tool calling.`);
9106
9203
  }
9107
9204
  try {
9108
9205
  const completion = await createDeepseekCompletion(content, responseFormat, mergedOptions);
9109
9206
  // Track cost in the global cost tracker. Pass cached tokens through so the
9110
9207
  // discounted cached-input pricing tier is applied.
9111
- getLLMCostTracker().trackUsage("deepseek", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens);
9208
+ getLLMCostTracker().trackUsage('deepseek', completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens);
9112
9209
  // Handle tool calls similarly to OpenAI
9113
9210
  if (completion.tool_calls && completion.tool_calls.length > 0) {
9114
9211
  const toolCallResponse = {
@@ -9124,10 +9221,10 @@ const makeDeepseekCall = async (content, responseFormat = "json", options = {})
9124
9221
  prompt_tokens: completion.usage.prompt_tokens,
9125
9222
  completion_tokens: completion.usage.completion_tokens,
9126
9223
  reasoning_tokens: 0, // Deepseek doesn't provide reasoning tokens separately
9127
- provider: "deepseek",
9224
+ provider: 'deepseek',
9128
9225
  model: completion.model,
9129
9226
  cached_tokens: completion.usage.cached_tokens,
9130
- cost: calculateCost("deepseek", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
9227
+ cost: calculateCost('deepseek', completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
9131
9228
  },
9132
9229
  tool_calls: completion.tool_calls,
9133
9230
  };
@@ -9135,7 +9232,7 @@ const makeDeepseekCall = async (content, responseFormat = "json", options = {})
9135
9232
  // Handle regular responses
9136
9233
  const parsedResponse = await parseResponse(completion.content, responseFormat);
9137
9234
  if (parsedResponse === null) {
9138
- throw new Error("Failed to parse Deepseek response");
9235
+ throw new Error('Failed to parse Deepseek response');
9139
9236
  }
9140
9237
  return {
9141
9238
  response: parsedResponse,
@@ -9143,32 +9240,22 @@ const makeDeepseekCall = async (content, responseFormat = "json", options = {})
9143
9240
  prompt_tokens: completion.usage.prompt_tokens,
9144
9241
  completion_tokens: completion.usage.completion_tokens,
9145
9242
  reasoning_tokens: 0, // Deepseek doesn't provide reasoning tokens separately
9146
- provider: "deepseek",
9243
+ provider: 'deepseek',
9147
9244
  model: completion.model,
9148
9245
  cached_tokens: completion.usage.cached_tokens,
9149
- cost: calculateCost("deepseek", completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
9246
+ cost: calculateCost('deepseek', completion.model, completion.usage.prompt_tokens, completion.usage.completion_tokens, 0, completion.usage.cached_tokens),
9150
9247
  },
9151
9248
  tool_calls: completion.tool_calls,
9152
9249
  };
9153
9250
  }
9154
9251
  catch (error) {
9155
- // If there's an error due to incompatible features, return a structured error
9252
+ // Mirror the OpenAI/Anthropic providers: log with sanitized context and
9253
+ // rethrow so callers observe the failure. Returning a success-shaped
9254
+ // response with an { error } payload here previously let hard failures
9255
+ // (auth, timeout, rate-limit exhaustion, unparseable output) masquerade as
9256
+ // successful completions downstream.
9156
9257
  getLumicLogger().error(`Error in Deepseek API call: ${sanitizeError(error)}`);
9157
- return {
9158
- response: {
9159
- error: sanitizeError(error),
9160
- },
9161
- usage: {
9162
- prompt_tokens: 0,
9163
- completion_tokens: 0,
9164
- reasoning_tokens: 0,
9165
- provider: "deepseek",
9166
- model: modelName,
9167
- cached_tokens: 0,
9168
- cost: 0,
9169
- },
9170
- tool_calls: undefined,
9171
- };
9258
+ throw error;
9172
9259
  }
9173
9260
  };
9174
9261
 
@@ -9271,15 +9358,15 @@ const generateCacheKey = (auth, envVarsCheck = null) => {
9271
9358
  if (auth) {
9272
9359
  return `${auth.AWS_ACCESS_KEY_ID}:${auth.AWS_SECRET_ACCESS_KEY}:${auth.AWS_REGION}`;
9273
9360
  }
9274
- if (envVarsCheck?.source === "lambda") {
9361
+ if (envVarsCheck?.source === 'lambda') {
9275
9362
  // In Lambda, use function name as cache key since it uses IAM role
9276
9363
  const secrets = getSecrets();
9277
- return `lambda:${secrets.aws.lambdaFunctionName || "default"}`;
9364
+ return `lambda:${secrets.aws.lambdaFunctionName || 'default'}`;
9278
9365
  }
9279
9366
  if (envVarsCheck?.vars) {
9280
9367
  return `${envVarsCheck.vars.accessKeyId}:${envVarsCheck.vars.secretAccessKey}:${envVarsCheck.vars.region}`;
9281
9368
  }
9282
- return "default";
9369
+ return 'default';
9283
9370
  };
9284
9371
  const validateEnvironmentVars = () => {
9285
9372
  const secrets = getSecrets();
@@ -9292,37 +9379,37 @@ const validateEnvironmentVars = () => {
9292
9379
  // We're in a Lambda environment
9293
9380
  return {
9294
9381
  isValid: true,
9295
- source: "lambda",
9296
- vars: { accessKeyId: "", secretAccessKey: "", region: "" }, // No need for explicit credentials in Lambda
9382
+ source: 'lambda',
9383
+ vars: { accessKeyId: '', secretAccessKey: '', region: '' } // No need for explicit credentials in Lambda
9297
9384
  };
9298
9385
  }
9299
9386
  else if (areVarsComplete(secrets.aws.myAccessKeyId, secrets.aws.mySecretAccessKey, secrets.aws.myRegion)) {
9300
9387
  return {
9301
9388
  isValid: true,
9302
- source: "my_prefix",
9389
+ source: 'my_prefix',
9303
9390
  vars: {
9304
- accessKeyId: secrets.aws.myAccessKeyId ?? "",
9305
- secretAccessKey: secrets.aws.mySecretAccessKey ?? "",
9306
- region: secrets.aws.myRegion ?? "",
9307
- },
9391
+ accessKeyId: secrets.aws.myAccessKeyId,
9392
+ secretAccessKey: secrets.aws.mySecretAccessKey,
9393
+ region: secrets.aws.myRegion
9394
+ }
9308
9395
  };
9309
9396
  }
9310
9397
  else if (areVarsComplete(secrets.aws.accessKeyId, secrets.aws.secretAccessKey, secrets.aws.region)) {
9311
9398
  return {
9312
9399
  isValid: true,
9313
- source: "standard",
9400
+ source: 'standard',
9314
9401
  vars: {
9315
- accessKeyId: secrets.aws.accessKeyId ?? "",
9316
- secretAccessKey: secrets.aws.secretAccessKey ?? "",
9317
- region: secrets.aws.region ?? "",
9318
- },
9402
+ accessKeyId: secrets.aws.accessKeyId,
9403
+ secretAccessKey: secrets.aws.secretAccessKey,
9404
+ region: secrets.aws.region
9405
+ }
9319
9406
  };
9320
9407
  }
9321
9408
  // If we get here, no complete set of variables was found
9322
9409
  return {
9323
9410
  isValid: false,
9324
9411
  source: null,
9325
- missingVars: [],
9412
+ missingVars: []
9326
9413
  };
9327
9414
  };
9328
9415
  const initialiseAWSClient = (ClientClass, auth = null) => {
@@ -9331,52 +9418,48 @@ const initialiseAWSClient = (ClientClass, auth = null) => {
9331
9418
  const requestTimeout = ClientClass === S3Client ? AWS_S3_TIMEOUT_MS : AWS_LAMBDA_TIMEOUT_MS;
9332
9419
  // Case 1: Explicit auth provided
9333
9420
  if (auth) {
9334
- if (typeof auth !== "object" || auth === null) {
9335
- throw new Error("Auth parameter must be an object");
9421
+ if (typeof auth !== 'object' || auth === null) {
9422
+ throw new Error('Auth parameter must be an object');
9336
9423
  }
9337
- const requiredAuthFields = [
9338
- "AWS_ACCESS_KEY_ID",
9339
- "AWS_SECRET_ACCESS_KEY",
9340
- "AWS_REGION",
9341
- ];
9342
- const missingFields = requiredAuthFields.filter((field) => !auth[field]);
9424
+ const requiredAuthFields = ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_REGION'];
9425
+ const missingFields = requiredAuthFields.filter(field => !auth[field]);
9343
9426
  if (missingFields.length > 0) {
9344
- throw new Error(`Auth object is missing required fields: ${missingFields.join(", ")}`);
9427
+ throw new Error(`Auth object is missing required fields: ${missingFields.join(', ')}`);
9345
9428
  }
9346
9429
  return new ClientClass({
9347
9430
  region: auth.AWS_REGION,
9348
9431
  credentials: {
9349
9432
  accessKeyId: auth.AWS_ACCESS_KEY_ID,
9350
- secretAccessKey: auth.AWS_SECRET_ACCESS_KEY,
9433
+ secretAccessKey: auth.AWS_SECRET_ACCESS_KEY
9351
9434
  },
9352
9435
  requestHandler: {
9353
- requestTimeout,
9354
- },
9436
+ requestTimeout
9437
+ }
9355
9438
  });
9356
9439
  }
9357
9440
  // Case 2: Check environment variables
9358
9441
  const envVarsCheck = validateEnvironmentVars();
9359
9442
  if (!envVarsCheck.isValid) {
9360
- throw new Error("No valid AWS credentials found in environment variables");
9443
+ throw new Error('No valid AWS credentials found in environment variables');
9361
9444
  }
9362
9445
  // Case 2a: Lambda execution environment
9363
- if (envVarsCheck.source === "lambda") {
9446
+ if (envVarsCheck.source === 'lambda') {
9364
9447
  return new ClientClass({
9365
9448
  requestHandler: {
9366
- requestTimeout,
9367
- },
9449
+ requestTimeout
9450
+ }
9368
9451
  }); // AWS SDK will automatically use Lambda role credentials
9369
9452
  }
9370
9453
  // Case 2b: MY_ prefixed vars or standard AWS vars
9371
9454
  return new ClientClass({
9372
- region: envVarsCheck.vars?.region ?? "",
9455
+ region: envVarsCheck.vars.region,
9373
9456
  credentials: {
9374
- accessKeyId: envVarsCheck.vars?.accessKeyId ?? "",
9375
- secretAccessKey: envVarsCheck.vars?.secretAccessKey ?? "",
9457
+ accessKeyId: envVarsCheck.vars.accessKeyId,
9458
+ secretAccessKey: envVarsCheck.vars.secretAccessKey
9376
9459
  },
9377
9460
  requestHandler: {
9378
- requestTimeout,
9379
- },
9461
+ requestTimeout
9462
+ }
9380
9463
  });
9381
9464
  }
9382
9465
  catch (error) {
@@ -9389,9 +9472,7 @@ const initialiseS3Client = (auth = null) => {
9389
9472
  const envVarsCheck = auth ? null : validateEnvironmentVars();
9390
9473
  const cacheKey = generateCacheKey(auth, envVarsCheck);
9391
9474
  if (s3ClientCache.has(cacheKey)) {
9392
- const cached = s3ClientCache.get(cacheKey);
9393
- if (cached)
9394
- return cached;
9475
+ return s3ClientCache.get(cacheKey);
9395
9476
  }
9396
9477
  // Create new client and cache it
9397
9478
  const client = initialiseAWSClient(S3Client, auth);
@@ -9403,9 +9484,7 @@ const initialiseLambdaClient = (auth = null) => {
9403
9484
  const envVarsCheck = auth ? null : validateEnvironmentVars();
9404
9485
  const cacheKey = generateCacheKey(auth, envVarsCheck);
9405
9486
  if (lambdaClientCache.has(cacheKey)) {
9406
- const cached = lambdaClientCache.get(cacheKey);
9407
- if (cached)
9408
- return cached;
9487
+ return lambdaClientCache.get(cacheKey);
9409
9488
  }
9410
9489
  // Create new client and cache it
9411
9490
  const client = initialiseAWSClient(Lambda, auth);
@@ -9905,17 +9984,15 @@ const isRetryableS3Error = (error) => {
9905
9984
  if (error instanceof Error) {
9906
9985
  const message = error.message;
9907
9986
  // Retry on throttling
9908
- if (message.includes("SlowDown") || message.includes("RequestTimeout")) {
9987
+ if (message.includes('SlowDown') || message.includes('RequestTimeout')) {
9909
9988
  return true;
9910
9989
  }
9911
9990
  // Retry on 5xx server errors
9912
- if (message.includes("InternalError") ||
9913
- message.includes("ServiceUnavailable")) {
9991
+ if (message.includes('InternalError') || message.includes('ServiceUnavailable')) {
9914
9992
  return true;
9915
9993
  }
9916
9994
  // Retry on connection errors
9917
- if (message.includes("RequestTimeTooSkewed") ||
9918
- message.includes("NetworkingError")) {
9995
+ if (message.includes('RequestTimeTooSkewed') || message.includes('NetworkingError')) {
9919
9996
  return true;
9920
9997
  }
9921
9998
  }
@@ -9929,11 +10006,10 @@ const generateRandomHash = (length) => {
9929
10006
  };
9930
10007
  function generateDateTimeStamp() {
9931
10008
  const now = new Date();
9932
- return now
9933
- .toISOString()
9934
- .replace(/T/, "-")
9935
- .replace(/\..+/, "")
9936
- .replace(/:/g, "-");
10009
+ return now.toISOString()
10010
+ .replace(/T/, '-')
10011
+ .replace(/\..+/, '')
10012
+ .replace(/:/g, '-');
9937
10013
  }
9938
10014
  const isValidBucketName = (name) => {
9939
10015
  const regex = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/;
@@ -9964,7 +10040,7 @@ async function uploadFile(s3Client, bucketName, filePath, destinationPath, isPub
9964
10040
  Bucket: bucketName,
9965
10041
  Key: s3Key,
9966
10042
  Body: fileContent,
9967
- ACL: isPublic ? "public-read" : undefined,
10043
+ ACL: isPublic ? 'public-read' : undefined
9968
10044
  });
9969
10045
  await withRetry(() => s3Client.send(command), {
9970
10046
  maxRetries: 3,
@@ -9984,7 +10060,7 @@ async function uploadDirectory(s3Client, bucketName, dirPath, destinationPath, i
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,
@@ -10000,7 +10076,7 @@ async function zipDirectory(sourceDir, outPath) {
10000
10076
  const zip = new AdmZip();
10001
10077
  let fileCount = 0;
10002
10078
  try {
10003
- async function addFilesToZip(currentPath, relativePath = "") {
10079
+ async function addFilesToZip(currentPath, relativePath = '') {
10004
10080
  const files = await fs$1.readdir(currentPath, { withFileTypes: true });
10005
10081
  for (const file of files) {
10006
10082
  const filePath = path__default.join(currentPath, file.name);
@@ -10043,33 +10119,27 @@ async function downloadFromS3Helper(s3Client, bucketName, s3Path, localPath) {
10043
10119
  Prefix: s3Path,
10044
10120
  ContinuationToken: continuationToken,
10045
10121
  });
10046
- const listResponse = (await withRetry(() => s3Client.send(listCommand), {
10122
+ const listResponse = await withRetry(() => s3Client.send(listCommand), {
10047
10123
  maxRetries: 3,
10048
10124
  baseDelayMs: 1000,
10049
10125
  maxDelayMs: 15000,
10050
10126
  retryableErrors: isRetryableS3Error,
10051
- }, `S3:ListObjects:${bucketName}`));
10127
+ }, `S3:ListObjects:${bucketName}`);
10052
10128
  const objects = listResponse.Contents || [];
10053
10129
  if (objects.length === 0) {
10054
10130
  return { fileCount: 0, folderCount: 0, totalSize: 0, fileList: [] };
10055
10131
  }
10056
- if (objects.length === 1 && objects[0].Key?.endsWith(".zip")) {
10132
+ if (objects.length === 1 && objects[0].Key?.endsWith('.zip')) {
10057
10133
  // It's a zip file, download and extract it
10058
10134
  const zipPath = path__default.join(localPath, path__default.basename(objects[0].Key));
10059
10135
  await downloadFile(s3Client, bucketName, objects[0].Key, zipPath);
10060
- const { totalFiles, totalFolders, totalSize: extractedSize, fileList: extractedFileList, } = await unzipFile(zipPath, localPath);
10136
+ const { totalFiles, totalFolders, totalSize: extractedSize, fileList: extractedFileList } = await unzipFile(zipPath, localPath);
10061
10137
  await fs$1.unlink(zipPath);
10062
- return {
10063
- fileCount: totalFiles,
10064
- folderCount: totalFolders,
10065
- totalSize: extractedSize,
10066
- fileList: extractedFileList,
10067
- };
10138
+ return { fileCount: totalFiles, folderCount: totalFolders, totalSize: extractedSize, fileList: extractedFileList };
10068
10139
  }
10069
10140
  else {
10070
10141
  // Non-zip files. Download files in batches.
10071
- for (let i = 0; i < objects.length; i += 1000) {
10072
- // AWS allows up to 1000 per request
10142
+ for (let i = 0; i < objects.length; i += 1000) { // AWS allows up to 1000 per request
10073
10143
  const batch = objects.slice(i, i + 1000);
10074
10144
  await Promise.all(batch.map(async (obj) => {
10075
10145
  if (!obj.Key)
@@ -10085,9 +10155,7 @@ async function downloadFromS3Helper(s3Client, bucketName, s3Path, localPath) {
10085
10155
  }
10086
10156
  // Count folders
10087
10157
  const relativePath = path__default.relative(localPath, path__default.dirname(localFilePath));
10088
- if (relativePath &&
10089
- !relativePath.startsWith("..") &&
10090
- relativePath !== ".") {
10158
+ if (relativePath && !relativePath.startsWith('..') && relativePath !== '.') {
10091
10159
  folderCount++;
10092
10160
  }
10093
10161
  }));
@@ -10108,7 +10176,7 @@ async function downloadFile(s3Client, bucketName, s3Key, localFilePath) {
10108
10176
  await withRetry(async () => {
10109
10177
  const { Body } = await s3Client.send(getCommand);
10110
10178
  if (!Body)
10111
- throw new Error("No body returned from S3");
10179
+ throw new Error('No body returned from S3');
10112
10180
  const writeStream = createWriteStream(localFilePath);
10113
10181
  await pipeline(Body, writeStream);
10114
10182
  }, {
@@ -10121,14 +10189,27 @@ async function downloadFile(s3Client, bucketName, s3Key, localFilePath) {
10121
10189
  async function unzipFile(zipPath, destPath) {
10122
10190
  const zip = new AdmZip(zipPath);
10123
10191
  try {
10124
- zip.extractAllToAsync(destPath, true);
10125
- const stats = await getDirectoryStats(destPath);
10126
- return stats;
10192
+ // extractAllToAsync is fire-and-forget: it returns void and reports
10193
+ // failures through its callback. The previous call neither awaited it nor
10194
+ // observed the callback, so getDirectoryStats raced an in-flight (or
10195
+ // failed) extraction and any extraction error was silently dropped. Wrap
10196
+ // it in a Promise so the extraction is fully awaited and errors surface.
10197
+ await new Promise((resolve, reject) => {
10198
+ zip.extractAllToAsync(destPath, true, false, (error) => {
10199
+ if (error) {
10200
+ reject(error);
10201
+ }
10202
+ else {
10203
+ resolve();
10204
+ }
10205
+ });
10206
+ });
10207
+ return await getDirectoryStats(destPath);
10127
10208
  }
10128
10209
  catch (error) {
10129
10210
  const errorMessage = error instanceof Error ? error.message : String(error);
10130
10211
  getLumicLogger().error(`Error unzipping file: ${errorMessage}`);
10131
- throw error;
10212
+ throw new ZipError(`Failed to extract archive "${zipPath}" to "${destPath}": ${errorMessage}`, error);
10132
10213
  }
10133
10214
  }
10134
10215
  async function getDirectoryStats(dirPath) {
@@ -10164,19 +10245,17 @@ async function emptyBucket(s3Client, bucketName) {
10164
10245
  Bucket: bucketName,
10165
10246
  ContinuationToken: continuationToken,
10166
10247
  });
10167
- const response = (await withRetry(() => s3Client.send(listCommand), {
10248
+ const response = await withRetry(() => s3Client.send(listCommand), {
10168
10249
  maxRetries: 3,
10169
10250
  baseDelayMs: 1000,
10170
10251
  maxDelayMs: 15000,
10171
10252
  retryableErrors: isRetryableS3Error,
10172
- }, `S3:ListObjects:${bucketName}`));
10253
+ }, `S3:ListObjects:${bucketName}`);
10173
10254
  const { Contents, IsTruncated, NextContinuationToken } = response;
10174
10255
  if (Contents && Contents.length > 0) {
10175
10256
  const deleteCommand = new DeleteObjectsCommand({
10176
10257
  Bucket: bucketName,
10177
- Delete: {
10178
- Objects: Contents.filter((c) => c.Key).map((c) => ({ Key: c.Key })),
10179
- },
10258
+ Delete: { Objects: Contents.map(({ Key }) => ({ Key: Key })) },
10180
10259
  });
10181
10260
  await withRetry(() => s3Client.send(deleteCommand), {
10182
10261
  maxRetries: 3,
@@ -23084,11 +23163,11 @@ let poolConfig = DEFAULT_POOL_CONFIG;
23084
23163
  async function loadApolloModules() {
23085
23164
  if (typeof window === "undefined" || process.env.AWS_EXECUTION_ENV) {
23086
23165
  // Server-side (or Lambda): load the CommonJS‑based implementation.
23087
- return (await import('./apollo-client.server-CbagxkmK.js'));
23166
+ return (await import('./apollo-client.server-DaXmxoBl.js'));
23088
23167
  }
23089
23168
  else {
23090
23169
  // Client-side: load the ESM‑based implementation.
23091
- return (await import('./apollo-client.client-9wJcufhf.js'));
23170
+ return (await import('./apollo-client.client-Uv0-ZrFd.js'));
23092
23171
  }
23093
23172
  }
23094
23173
  /**
@@ -79115,7 +79194,7 @@ const RETRY_CONFIG = {
79115
79194
  BASE_DELAY: 2000, // Increased base delay to 2 seconds
79116
79195
  MAX_DELAY: 64000,
79117
79196
  MAX_RETRIES: 5,
79118
- MAX_RANDOM_DELAY: 1000,
79197
+ MAX_RANDOM_DELAY: 1000
79119
79198
  };
79120
79199
  /**
79121
79200
  * Determines if an error is related to Google Sheets API quotas or rate limits.
@@ -79141,10 +79220,10 @@ const RETRY_CONFIG = {
79141
79220
  */
79142
79221
  function isQuotaError(error) {
79143
79222
  const message = error.message.toLowerCase();
79144
- return (message.includes("quota exceeded") ||
79145
- message.includes("rate limit") ||
79146
- message.includes("429") ||
79147
- message.includes("too many requests"));
79223
+ return message.includes('quota exceeded') ||
79224
+ message.includes('rate limit') ||
79225
+ message.includes('429') ||
79226
+ message.includes('too many requests');
79148
79227
  }
79149
79228
  /**
79150
79229
  * Implements exponential backoff retry mechanism for Google Sheets API calls.
@@ -79203,14 +79282,14 @@ async function withExponentialBackoff(operation) {
79203
79282
  const exponentialDelay = Math.min(Math.pow(2, retries) * RETRY_CONFIG.BASE_DELAY + randomDelay, RETRY_CONFIG.MAX_DELAY);
79204
79283
  logIfDebug(`Quota/Rate limit exceeded. Error: ${error.message}`);
79205
79284
  logIfDebug(`Retrying in ${exponentialDelay}ms (attempt ${retries}/${RETRY_CONFIG.MAX_RETRIES})`);
79206
- await new Promise((resolve) => setTimeout(resolve, exponentialDelay));
79285
+ await new Promise(resolve => setTimeout(resolve, exponentialDelay));
79207
79286
  }
79208
79287
  }
79209
79288
  }
79210
79289
  function checkEnvVars() {
79211
79290
  const secrets = getSecrets();
79212
79291
  if (!secrets.googleSheets.clientEmail || !secrets.googleSheets.privateKey) {
79213
- throw new Error("GOOGLE_SHEETS_CLIENT_EMAIL or GOOGLE_SHEETS_PRIVATE_KEY is not defined in environment variables.");
79292
+ throw new Error('GOOGLE_SHEETS_CLIENT_EMAIL or GOOGLE_SHEETS_PRIVATE_KEY is not defined in environment variables.');
79214
79293
  }
79215
79294
  }
79216
79295
  /**
@@ -79225,16 +79304,16 @@ async function getAuthClient() {
79225
79304
  const secrets = getSecrets();
79226
79305
  const credentials = {
79227
79306
  client_email: secrets.googleSheets.clientEmail,
79228
- private_key: secrets.googleSheets.privateKey?.replace(/\\n/g, "\n"),
79307
+ private_key: secrets.googleSheets.privateKey?.replace(/\\n/g, '\n'),
79229
79308
  };
79230
79309
  const auth = new GoogleAuth({
79231
79310
  credentials,
79232
- scopes: ["https://www.googleapis.com/auth/spreadsheets"],
79311
+ scopes: ['https://www.googleapis.com/auth/spreadsheets'],
79233
79312
  });
79234
79313
  return auth.getClient();
79235
79314
  }
79236
79315
  catch (error) {
79237
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
79316
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
79238
79317
  logIfDebug(`Error initializing Google Sheets auth: ${errorMessage}`);
79239
79318
  throw new Error(`Failed to initialize Google Sheets auth: ${errorMessage}`);
79240
79319
  }
@@ -79246,7 +79325,7 @@ async function getSheetsClient() {
79246
79325
  const auth = await getAuthClient();
79247
79326
  return new buildExports.sheets_v4.Sheets({
79248
79327
  auth,
79249
- timeout: GOOGLE_SHEETS_TIMEOUT_MS,
79328
+ timeout: GOOGLE_SHEETS_TIMEOUT_MS
79250
79329
  });
79251
79330
  }
79252
79331
  /**
@@ -79279,7 +79358,7 @@ function escapeSheetName(sheetName) {
79279
79358
  * @param startColumn Optional starting column (defaults to 'A')
79280
79359
  * @returns Promise resolving to response with success status and row details
79281
79360
  */
79282
- async function addRow(config, values, startColumn = "A") {
79361
+ async function addRow(config, values, startColumn = 'A') {
79283
79362
  try {
79284
79363
  const sheets = await getSheetsClient();
79285
79364
  const escapedSheetName = escapeSheetName(config.sheetName);
@@ -79287,7 +79366,7 @@ async function addRow(config, values, startColumn = "A") {
79287
79366
  const response = await withExponentialBackoff(() => sheets.spreadsheets.values.append({
79288
79367
  spreadsheetId: config.spreadsheetId,
79289
79368
  range,
79290
- valueInputOption: "USER_ENTERED",
79369
+ valueInputOption: 'USER_ENTERED',
79291
79370
  requestBody: {
79292
79371
  values: [values],
79293
79372
  },
@@ -79295,9 +79374,9 @@ async function addRow(config, values, startColumn = "A") {
79295
79374
  //logIfDebug(`Added row to sheet ${config.sheetName}: ${values.join(', ')}`);
79296
79375
  const rowNumber = response.data.updates?.updatedRange
79297
79376
  ? parseInt(response.data.updates.updatedRange
79298
- .split("!")[1]
79299
- .split(":")[0]
79300
- .replace(/[^0-9]/g, ""))
79377
+ .split('!')[1]
79378
+ .split(':')[0]
79379
+ .replace(/[^0-9]/g, ''))
79301
79380
  : 0;
79302
79381
  return {
79303
79382
  success: true,
@@ -79308,7 +79387,7 @@ async function addRow(config, values, startColumn = "A") {
79308
79387
  };
79309
79388
  }
79310
79389
  catch (error) {
79311
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
79390
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
79312
79391
  logIfDebug(`Error adding row to sheet: ${errorMessage}`);
79313
79392
  return {
79314
79393
  success: false,
@@ -79350,7 +79429,7 @@ async function addSheet(spreadsheetId, sheetTitle) {
79350
79429
  };
79351
79430
  }
79352
79431
  catch (error) {
79353
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
79432
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
79354
79433
  logIfDebug(`Error adding sheet: ${errorMessage}`);
79355
79434
  return {
79356
79435
  success: false,
@@ -79374,7 +79453,7 @@ async function writeCell(config, cell, value) {
79374
79453
  await withExponentialBackoff(() => sheets.spreadsheets.values.update({
79375
79454
  spreadsheetId: config.spreadsheetId,
79376
79455
  range,
79377
- valueInputOption: "USER_ENTERED",
79456
+ valueInputOption: 'USER_ENTERED',
79378
79457
  requestBody: {
79379
79458
  values: [[value]],
79380
79459
  },
@@ -79388,7 +79467,7 @@ async function writeCell(config, cell, value) {
79388
79467
  };
79389
79468
  }
79390
79469
  catch (error) {
79391
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
79470
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
79392
79471
  logIfDebug(`Error writing to cell: ${errorMessage}`);
79393
79472
  return {
79394
79473
  success: false,
@@ -79412,11 +79491,8 @@ async function getCell(config, cell) {
79412
79491
  spreadsheetId: config.spreadsheetId,
79413
79492
  range,
79414
79493
  }));
79415
- const rawValue = response.data.values?.[0]?.[0];
79416
- const value = rawValue === undefined || rawValue === null
79417
- ? null
79418
- : rawValue;
79419
- logIfDebug(`Read value from cell ${range}: ${String(value)}`);
79494
+ const value = response.data.values?.[0]?.[0];
79495
+ logIfDebug(`Read value from cell ${range}: ${value}`);
79420
79496
  return {
79421
79497
  success: true,
79422
79498
  data: {
@@ -79425,7 +79501,7 @@ async function getCell(config, cell) {
79425
79501
  };
79426
79502
  }
79427
79503
  catch (error) {
79428
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
79504
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
79429
79505
  logIfDebug(`Error reading from cell: ${errorMessage}`);
79430
79506
  return {
79431
79507
  success: false,
@@ -79458,11 +79534,10 @@ function convertA1ToRowCol(a1Notation) {
79458
79534
  * @returns Padded array of arrays
79459
79535
  */
79460
79536
  function padArrays(arrays) {
79461
- const maxLength = Math.max(...arrays.map((arr) => arr.length));
79462
- return arrays.map((arr) => {
79537
+ const maxLength = Math.max(...arrays.map(arr => arr.length));
79538
+ return arrays.map(arr => {
79463
79539
  if (arr.length < maxLength) {
79464
- const padding = Array(maxLength - arr.length).fill("");
79465
- return [...arr, ...padding];
79540
+ return [...arr, ...Array(maxLength - arr.length).fill('')];
79466
79541
  }
79467
79542
  return arr;
79468
79543
  });
@@ -79474,10 +79549,10 @@ function padArrays(arrays) {
79474
79549
  * @param startCell Optional starting cell in A1 notation (defaults to 'A1')
79475
79550
  * @returns Promise resolving to response with success status and update details
79476
79551
  */
79477
- async function writeArray(config, data, startCell = "A1") {
79552
+ async function writeArray(config, data, startCell = 'A1') {
79478
79553
  try {
79479
79554
  if (!data.length) {
79480
- throw new Error("Data array is empty");
79555
+ throw new Error('Data array is empty');
79481
79556
  }
79482
79557
  const sheets = await getSheetsClient();
79483
79558
  const { row: startRow, column: startCol } = convertA1ToRowCol(startCell);
@@ -79493,7 +79568,7 @@ async function writeArray(config, data, startCell = "A1") {
79493
79568
  const response = await withExponentialBackoff(() => sheets.spreadsheets.values.update({
79494
79569
  spreadsheetId: config.spreadsheetId,
79495
79570
  range,
79496
- valueInputOption: "USER_ENTERED",
79571
+ valueInputOption: 'USER_ENTERED',
79497
79572
  requestBody: {
79498
79573
  values: paddedData,
79499
79574
  },
@@ -79509,7 +79584,7 @@ async function writeArray(config, data, startCell = "A1") {
79509
79584
  };
79510
79585
  }
79511
79586
  catch (error) {
79512
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
79587
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
79513
79588
  logIfDebug(`Error writing array to sheet: ${errorMessage}`);
79514
79589
  return {
79515
79590
  success: false,
@@ -81616,4 +81691,4 @@ const lumic = {
81616
81691
  };
81617
81692
 
81618
81693
  export { GraphQLInterfaceType as $, print as A, getNamedType as B, isInputType as C, isRequiredArgument as D, isNamedType as E, GraphQLError as F, GraphQLNonNull as G, isOutputType as H, isRequiredInputField as I, isCompositeType as J, Kind as K, getNullableType as L, getEnterLeaveForKind as M, isNode as N, OperationTypeNode as O, didYouMean as P, naturalCompare as Q, suggestionList as R, specifiedScalarTypes as S, keyMap as T, isType as U, isNullableType as V, visit as W, visitInParallel as X, keyValMap as Y, assertObjectType as Z, GraphQLScalarType as _, isListType as a, validateGoogleSheetsRange as a$, GraphQLUnionType as a0, GraphQLInputObjectType as a1, assertNullableType as a2, assertInterfaceType as a3, mapValue as a4, isSpecifiedScalarType as a5, isPrintableAsBlockString as a6, printBlockString as a7, BREAK as a8, GRAPHQL_MAX_INT as a9, printSourceLocation as aA, resolveObjMapThunk as aB, resolveReadonlyArrayThunk as aC, valueFromASTUntyped as aD, version$4 as aE, versionInfo as aF, getAugmentedNamespace as aG, isDigit$1 as aH, isNameStart as aI, dedentBlockStringLines as aJ, isNameContinue as aK, setLumicLogger as aL, getLumicLogger as aM, sanitizeForLog as aN, sanitizeError as aO, sanitizeAWSAuth as aP, sanitizeObject as aQ, getSecrets as aR, resetSecrets as aS, requireSecret as aT, withRetry as aU, CircuitBreaker as aV, CircuitBreakerState as aW, CircuitBreakerOpenError as aX, DEFAULT_CIRCUIT_BREAKER_CONFIG as aY, validateSlackChannel as aZ, validateS3Key as a_, GRAPHQL_MIN_INT as aa, GraphQLFloat as ab, GraphQLInt as ac, Location as ad, Token as ae, assertAbstractType as af, assertCompositeType as ag, assertEnumType as ah, assertEnumValueName as ai, assertInputObjectType as aj, assertInputType as ak, assertLeafType as al, assertListType as am, assertNamedType as an, assertNonNullType as ao, assertOutputType as ap, assertScalarType as aq, assertType as ar, assertUnionType as as, assertWrappingType as at, formatError as au, getLocation as av, getVisitFn as aw, isWrappingType as ax, printError as ay, printLocation as az, isAbstractType as b, PDFError as b$, LLMCostTracker as b0, getLLMCostTracker as b1, setLLMCostTracker as b2, resetLLMCostTracker as b3, setMetricsCollector as b4, getMetricsCollector as b5, resetMetricsCollector as b6, withMetrics as b7, generateCorrelationId as b8, getCorrelationId as b9, openAIChatCompletionSchema as bA, openAIImageResponseSchema as bB, validateOpenAIChatCompletion as bC, safeValidateOpenAIChatCompletion as bD, perplexityResponseSchema as bE, validatePerplexityResponse as bF, safeValidatePerplexityResponse as bG, googleSheetsValueRangeSchema as bH, validateGoogleSheetsResponse as bI, safeValidateGoogleSheetsResponse as bJ, s3ListObjectsSchema as bK, s3GetObjectSchema as bL, lambdaInvokeResponseSchema as bM, validateS3ListObjects as bN, safeValidateS3ListObjects as bO, validateLambdaResponse as bP, safeValidateLambdaResponse as bQ, createValidator as bR, createSafeValidator as bS, LumicError as bT, SlackError as bU, LLMError as bV, AWSLambdaError as bW, AWSS3Error as bX, GoogleSheetsError as bY, PerplexityError as bZ, JsonParseError as b_, getCorrelationContext as ba, withCorrelationId as bb, getCorrelationHeaders as bc, TokenBucketRateLimiter as bd, RATE_LIMIT_PROFILES as be, getRateLimiter as bf, resetAllRateLimiters as bg, withRateLimit as bh, checkIntegrationHealth as bi, SUPPORTED_MODELS as bj, isValidModel as bk, getModelCapabilities as bl, getModelProvider as bm, MODEL_ALIASES as bn, OPENAI_COMPATIBLE_PROVIDERS as bo, PROVIDER_DEFAULT_MODELS as bp, LLM_DEFAULT_PROVIDER as bq, LLM_MINI_PROVIDER as br, LLM_NORMAL_PROVIDER as bs, LLM_ADVANCED_PROVIDER as bt, LLM_PROVIDER as bu, LLM_MODEL_MINI as bv, LLM_MODEL_NORMAL as bw, LLM_MODEL_ADVANCED as bx, makeAnthropicCall as by, makeOpenAICompatibleCall as bz, isInterfaceType as c, ZipError as c0, SLACK_TIMEOUT_MS as c1, PERPLEXITY_TIMEOUT_MS as c2, GOOGLE_SHEETS_TIMEOUT_MS as c3, LLM_TIMEOUT_MS as c4, AWS_LAMBDA_TIMEOUT_MS as c5, AWS_S3_TIMEOUT_MS as c6, OPENWEATHER_TIMEOUT_MS as c7, GENERIC_FETCH_TIMEOUT_MS as c8, isObjectType as d, assertName as e, devAssert as f, isObjectLike as g, defineArguments as h, isNonNullType as i, argsToArgsConfig as j, GraphQLBoolean as k, lumic as l, GraphQLString as m, instanceOf as n, inspect as o, isInputObjectType as p, isLeafType as q, isEnumType as r, GraphQLID as s, toObjMap as t, invariant as u, GraphQLObjectType as v, GraphQLEnumType as w, GraphQLList as x, isScalarType as y, isUnionType as z };
81619
- //# sourceMappingURL=index-EMQ1uJMh.js.map
81694
+ //# sourceMappingURL=index-z7Y1nTVX.js.map