@jecruz/merc-ai 0.1.4 → 0.2.0

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 (111) hide show
  1. package/README.md +34 -7
  2. package/dist/env-api-keys.d.ts.map +1 -1
  3. package/dist/env-api-keys.js +1 -1
  4. package/dist/env-api-keys.js.map +1 -1
  5. package/dist/image-models.generated.d.ts +345 -0
  6. package/dist/image-models.generated.d.ts.map +1 -1
  7. package/dist/image-models.generated.js +348 -3
  8. package/dist/image-models.generated.js.map +1 -1
  9. package/dist/index.d.ts +3 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +3 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/models.d.ts.map +1 -1
  14. package/dist/models.generated.d.ts +15282 -3882
  15. package/dist/models.generated.d.ts.map +1 -1
  16. package/dist/models.generated.js +18368 -10964
  17. package/dist/models.generated.js.map +1 -1
  18. package/dist/models.js +4 -1
  19. package/dist/models.js.map +1 -1
  20. package/dist/providers/amazon-bedrock.d.ts.map +1 -1
  21. package/dist/providers/amazon-bedrock.js +188 -19
  22. package/dist/providers/amazon-bedrock.js.map +1 -1
  23. package/dist/providers/anthropic.d.ts.map +1 -1
  24. package/dist/providers/anthropic.js +101 -34
  25. package/dist/providers/anthropic.js.map +1 -1
  26. package/dist/providers/azure-openai-responses.d.ts +2 -0
  27. package/dist/providers/azure-openai-responses.d.ts.map +1 -1
  28. package/dist/providers/azure-openai-responses.js +8 -3
  29. package/dist/providers/azure-openai-responses.js.map +1 -1
  30. package/dist/providers/google-shared.d.ts +15 -4
  31. package/dist/providers/google-shared.d.ts.map +1 -1
  32. package/dist/providers/google-shared.js +80 -15
  33. package/dist/providers/google-shared.js.map +1 -1
  34. package/dist/providers/google-vertex.d.ts.map +1 -1
  35. package/dist/providers/google-vertex.js +23 -60
  36. package/dist/providers/google-vertex.js.map +1 -1
  37. package/dist/providers/google.d.ts.map +1 -1
  38. package/dist/providers/google.js +19 -68
  39. package/dist/providers/google.js.map +1 -1
  40. package/dist/providers/mistral.d.ts +1 -1
  41. package/dist/providers/mistral.d.ts.map +1 -1
  42. package/dist/providers/mistral.js +44 -60
  43. package/dist/providers/mistral.js.map +1 -1
  44. package/dist/providers/openai-codex-responses.d.ts.map +1 -1
  45. package/dist/providers/openai-codex-responses.js +250 -124
  46. package/dist/providers/openai-codex-responses.js.map +1 -1
  47. package/dist/providers/openai-completions.d.ts.map +1 -1
  48. package/dist/providers/openai-completions.js +94 -46
  49. package/dist/providers/openai-completions.js.map +1 -1
  50. package/dist/providers/openai-prompt-cache.d.ts +3 -0
  51. package/dist/providers/openai-prompt-cache.d.ts.map +1 -0
  52. package/dist/providers/openai-prompt-cache.js +10 -0
  53. package/dist/providers/openai-prompt-cache.js.map +1 -0
  54. package/dist/providers/openai-responses-shared.d.ts.map +1 -1
  55. package/dist/providers/openai-responses-shared.js +154 -211
  56. package/dist/providers/openai-responses-shared.js.map +1 -1
  57. package/dist/providers/openai-responses.d.ts.map +1 -1
  58. package/dist/providers/openai-responses.js +15 -9
  59. package/dist/providers/openai-responses.js.map +1 -1
  60. package/dist/providers/openrouter-images.d.ts.map +1 -1
  61. package/dist/providers/openrouter-images.js +5 -2
  62. package/dist/providers/openrouter-images.js.map +1 -1
  63. package/dist/providers/simple-options.d.ts +15 -3
  64. package/dist/providers/simple-options.d.ts.map +1 -1
  65. package/dist/providers/simple-options.js +36 -4
  66. package/dist/providers/simple-options.js.map +1 -1
  67. package/dist/types.d.ts +35 -2
  68. package/dist/types.d.ts.map +1 -1
  69. package/dist/types.js.map +1 -1
  70. package/dist/utils/error-body.d.ts +5 -0
  71. package/dist/utils/error-body.d.ts.map +1 -1
  72. package/dist/utils/error-body.js +53 -5
  73. package/dist/utils/error-body.js.map +1 -1
  74. package/dist/utils/estimate.d.ts +22 -0
  75. package/dist/utils/estimate.d.ts.map +1 -0
  76. package/dist/utils/estimate.js +104 -0
  77. package/dist/utils/estimate.js.map +1 -0
  78. package/dist/utils/event-stream.d.ts.map +1 -1
  79. package/dist/utils/event-stream.js +25 -7
  80. package/dist/utils/event-stream.js.map +1 -1
  81. package/dist/utils/json-parse.d.ts +3 -0
  82. package/dist/utils/json-parse.d.ts.map +1 -1
  83. package/dist/utils/json-parse.js +15 -0
  84. package/dist/utils/json-parse.js.map +1 -1
  85. package/dist/utils/oauth/anthropic.d.ts.map +1 -1
  86. package/dist/utils/oauth/anthropic.js +16 -10
  87. package/dist/utils/oauth/anthropic.js.map +1 -1
  88. package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  89. package/dist/utils/oauth/github-copilot.js +57 -21
  90. package/dist/utils/oauth/github-copilot.js.map +1 -1
  91. package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
  92. package/dist/utils/oauth/openai-codex.js +73 -16
  93. package/dist/utils/oauth/openai-codex.js.map +1 -1
  94. package/dist/utils/oauth/types.d.ts +2 -0
  95. package/dist/utils/oauth/types.d.ts.map +1 -1
  96. package/dist/utils/oauth/types.js.map +1 -1
  97. package/dist/utils/overflow.d.ts +10 -1
  98. package/dist/utils/overflow.d.ts.map +1 -1
  99. package/dist/utils/overflow.js +47 -7
  100. package/dist/utils/overflow.js.map +1 -1
  101. package/dist/utils/provider-env.d.ts.map +1 -1
  102. package/dist/utils/provider-env.js +45 -10
  103. package/dist/utils/provider-env.js.map +1 -1
  104. package/dist/utils/tool-call-id.d.ts +11 -0
  105. package/dist/utils/tool-call-id.d.ts.map +1 -0
  106. package/dist/utils/tool-call-id.js +96 -0
  107. package/dist/utils/tool-call-id.js.map +1 -0
  108. package/dist/utils/validation.d.ts.map +1 -1
  109. package/dist/utils/validation.js +9 -0
  110. package/dist/utils/validation.js.map +1 -1
  111. package/package.json +6 -3
@@ -1 +1 @@
1
- {"version":3,"file":"overflow.d.ts","sourceRoot":"","sources":["../../src/utils/overflow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAyEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CA6B5F;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C","sourcesContent":["import type { AssistantMessage } from \"../types.js\";\n\n/**\n * Regex patterns to detect context overflow errors from different providers.\n *\n * These patterns match error messages returned when the input exceeds\n * the model's context window.\n *\n * Provider-specific patterns (with example error messages):\n *\n * - Anthropic: \"prompt is too long: 213462 tokens > 200000 maximum\"\n * - Anthropic: \"413 {\\\"error\\\":{\\\"type\\\":\\\"request_too_large\\\",\\\"message\\\":\\\"Request exceeds the maximum size\\\"}}\"\n * - OpenAI: \"Your input exceeds the context window of this model\"\n * - OpenAI/LiteLLM: \"Requested token count exceeds the model's maximum context length of 131072 tokens\"\n * - Google: \"The input token count (1196265) exceeds the maximum number of tokens allowed (1048575)\"\n * - xAI: \"This model's maximum prompt length is 131072 but the request contains 537812 tokens\"\n * - Groq: \"Please reduce the length of the messages or completion\"\n * - OpenRouter: \"This endpoint's maximum context length is X tokens. However, you requested about Y tokens\"\n * - llama.cpp: \"the request exceeds the available context size, try increasing it\"\n * - LM Studio: \"tokens to keep from the initial prompt is greater than the context length\"\n * - GitHub Copilot: \"prompt token count of X exceeds the limit of Y\"\n * - MiniMax: \"invalid params, context window exceeds limit\"\n * - Kimi For Coding: \"Your request exceeded model token limit: X (requested: Y)\"\n * - DS4: \"Prompt has X tokens, but the configured context size is Y tokens\"\n * - Cerebras: \"400/413 status code (no body)\"\n * - Mistral: \"Prompt contains X tokens ... too large for model with Y maximum context length\"\n * - z.ai: Does NOT error, accepts overflow silently - handled via usage.input > contextWindow\n * - Xiaomi MiMo: Truncates input to fill contextWindow exactly, then returns finish_reason \"length\"\n * with output=0 (no room left to generate). Detected via stopReason \"length\" + zero output +\n * input filling the context window.\n * - Ollama: Some deployments truncate silently, others return errors like \"prompt too long; exceeded max context length by X tokens\"\n */\nconst OVERFLOW_PATTERNS = [\n\t/prompt is too long/i, // Anthropic token overflow\n\t/request_too_large/i, // Anthropic request byte-size overflow (HTTP 413)\n\t/input is too long for requested model/i, // Amazon Bedrock\n\t/exceeds the context window/i, // OpenAI (Completions & Responses API)\n\t/exceeds (?:the )?(?:model'?s )?maximum context length(?: of [\\d,]+ tokens?|\\s*\\([\\d,]+\\))/i, // OpenAI-compatible proxies (LiteLLM)\n\t/input token count.*exceeds the maximum/i, // Google (Gemini)\n\t/maximum prompt length is \\d+/i, // xAI (Grok)\n\t/reduce the length of the messages/i, // Groq\n\t/maximum context length is \\d+ tokens/i, // OpenRouter (all backends)\n\t/exceeds (?:the )?maximum allowed input length of [\\d,]+ tokens?/i, // OpenRouter/Poolside\n\t/exceeds the limit of \\d+/i, // GitHub Copilot\n\t/exceeds the available context size/i, // llama.cpp server\n\t/greater than the context length/i, // LM Studio\n\t/context window exceeds limit/i, // MiniMax\n\t/exceeded model token limit/i, // Kimi For Coding\n\t/too large for model with \\d+ maximum context length/i, // Mistral\n\t/prompt has [\\d,]+ tokens?, but the configured context size is [\\d,]+ tokens?/i, // DS4 server\n\t/model_context_window_exceeded/i, // z.ai non-standard finish_reason surfaced as error text\n\t/prompt too long; exceeded (?:max )?context length/i, // Ollama explicit overflow error\n\t/context[_ ]length[_ ]exceeded/i, // Generic fallback\n\t/too many tokens/i, // Generic fallback\n\t/token limit exceeded/i, // Generic fallback\n\t/^4(?:00|13)\\s*(?:status code)?\\s*\\(no body\\)/i, // Cerebras: 400/413 with no body\n];\n\n/**\n * Patterns that indicate non-overflow errors (e.g. rate limiting, server errors).\n * Error messages matching any of these are excluded from overflow detection\n * even if they also match an OVERFLOW_PATTERN.\n *\n * Example: Bedrock formats throttling errors as \"ThrottlingException: Too many tokens,\n * please wait before trying again.\" which would match the /too many tokens/i overflow\n * pattern without this exclusion.\n */\nconst NON_OVERFLOW_PATTERNS = [\n\t/^(Throttling error|Service unavailable):/i, // AWS Bedrock non-overflow errors (human-readable prefixes from formatBedrockError)\n\t/rate limit/i, // Generic rate limiting\n\t/too many requests/i, // Generic HTTP 429 style\n];\n\n/**\n * Check if an assistant message represents a context overflow error.\n *\n * This handles two cases:\n * 1. Error-based overflow: Most providers return stopReason \"error\" with a\n * specific error message pattern.\n * 2. Silent overflow: Some providers accept overflow requests and return\n * successfully. For these, we check if usage.input exceeds the context window.\n *\n * ## Reliability by Provider\n *\n * **Reliable detection (returns error with detectable message):**\n * - Anthropic: \"prompt is too long: X tokens > Y maximum\" or \"request_too_large\"\n * - OpenAI (Completions & Responses): \"exceeds the context window\" or \"exceeds the model's maximum context length of X tokens\"\n * - Google Gemini: \"input token count exceeds the maximum\"\n * - xAI (Grok): \"maximum prompt length is X but request contains Y\"\n * - Groq: \"reduce the length of the messages\"\n * - Cerebras: 400/413 status code (no body)\n * - Mistral: \"Prompt contains X tokens ... too large for model with Y maximum context length\"\n * - OpenRouter (all backends): \"maximum context length is X tokens\"\n * - llama.cpp: \"exceeds the available context size\"\n * - LM Studio: \"greater than the context length\"\n * - Kimi For Coding: \"exceeded model token limit: X (requested: Y)\"\n * - DS4: \"Prompt has X tokens, but the configured context size is Y tokens\"\n *\n * **Unreliable detection:**\n * - z.ai: Sometimes accepts overflow silently (detectable via usage.input > contextWindow),\n * sometimes returns rate limit errors. Pass contextWindow param to detect silent overflow.\n * - Xiaomi MiMo: Truncates input to fit contextWindow then returns stopReason \"length\" with\n * output=0. Pass contextWindow param to detect via the \"filled context + zero output\" signal.\n * - Ollama: May truncate input silently for some setups, but may also return explicit\n * overflow errors that match the patterns above. Silent truncation still cannot be\n * detected here because we do not know the expected token count.\n *\n * ## Custom Providers\n *\n * If you've added custom models via settings.json, this function may not detect\n * overflow errors from those providers. To add support:\n *\n * 1. Send a request that exceeds the model's context window\n * 2. Check the errorMessage in the response\n * 3. Create a regex pattern that matches the error\n * 4. The pattern should be added to OVERFLOW_PATTERNS in this file, or\n * check the errorMessage yourself before calling this function\n *\n * @param message - The assistant message to check\n * @param contextWindow - Optional context window size for detecting silent overflow (z.ai)\n * @returns true if the message indicates a context overflow\n */\nexport function isContextOverflow(message: AssistantMessage, contextWindow?: number): boolean {\n\t// Case 1: Check error message patterns\n\tif (message.stopReason === \"error\" && message.errorMessage) {\n\t\t// Skip messages matching known non-overflow patterns (e.g. throttling / rate-limit)\n\t\tconst isNonOverflow = NON_OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!));\n\t\tif (!isNonOverflow && OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!))) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Case 2: Silent overflow (z.ai style) - successful but usage exceeds context\n\tif (contextWindow && message.stopReason === \"stop\") {\n\t\tconst inputTokens = message.usage.input + message.usage.cacheRead;\n\t\tif (inputTokens > contextWindow) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Case 3: Length-stop overflow (Xiaomi MiMo style) - server truncates oversized input\n\t// to fit the context window, leaving no room for output. Returns stopReason \"length\"\n\t// with output=0 and input+cacheRead filling the context window.\n\tif (contextWindow && message.stopReason === \"length\" && message.usage.output === 0) {\n\t\tconst inputTokens = message.usage.input + message.usage.cacheRead;\n\t\tif (inputTokens >= contextWindow * 0.99) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Get the overflow patterns for testing purposes.\n */\nexport function getOverflowPatterns(): RegExp[] {\n\treturn [...OVERFLOW_PATTERNS];\n}\n"]}
1
+ {"version":3,"file":"overflow.d.ts","sourceRoot":"","sources":["../../src/utils/overflow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAgGpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAqC5F;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAEhG;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C","sourcesContent":["import type { AssistantMessage } from \"../types.js\";\n\n/**\n * Regex patterns to detect context overflow errors from different providers.\n *\n * These patterns match error messages returned when the input exceeds\n * the model's context window or the provider's request byte-size limit.\n *\n * Provider-specific patterns (with example error messages):\n *\n * - Anthropic: \"prompt is too long: 213462 tokens > 200000 maximum\"\n * - Anthropic: \"413 {\\\"error\\\":{\\\"type\\\":\\\"request_too_large\\\",\\\"message\\\":\\\"Request exceeds the maximum size\\\"}}\"\n * - OpenAI: \"Your input exceeds the context window of this model\"\n * - OpenAI/LiteLLM: \"Requested token count exceeds the model's maximum context length of 131072 tokens\"\n * - OpenAI-compatible: \"Input length (265330) exceeds model's maximum context length (262144).\"\n * - Google: \"The input token count (1196265) exceeds the maximum number of tokens allowed (1048575)\"\n * - xAI: \"This model's maximum prompt length is 131072 but the request contains 537812 tokens\"\n * - Groq: \"Please reduce the length of the messages or completion\"\n * - OpenRouter: \"This endpoint's maximum context length is X tokens. However, you requested about Y tokens\"\n * - llama.cpp: \"the request exceeds the available context size, try increasing it\"\n * - LM Studio: \"tokens to keep from the initial prompt is greater than the context length\"\n * - GitHub Copilot: \"prompt token count of X exceeds the limit of Y\"\n * - GitHub Copilot: 'github-copilot API error (413): {\"message\":\"failed to parse request\",\"code\":\"\"}'\n * - MiniMax: \"invalid params, context window exceeds limit\"\n * - Kimi For Coding: \"Your request exceeded model token limit: X (requested: Y)\"\n * - DS4: \"Prompt has X tokens, but the configured context size is Y tokens\"\n * - Cerebras: \"400/413 status code (no body)\"\n * - Mistral: \"Prompt contains X tokens ... too large for model with Y maximum context length\"\n * - z.ai: `{\"code\":\"1261\",\"message\":\"Prompt too long\"}`, `{\"code\":\"1261\",\"message\":\"Prompt exceeds max length\"}` (CN endpoint), or silent overflow via usage.input > contextWindow\n * - Xiaomi MiMo: Truncates input to fill contextWindow exactly, then returns finish_reason \"length\"\n * with output=0 (no room left to generate). Detected via stopReason \"length\" + zero output +\n * input filling the context window.\n * - Ollama: Some deployments truncate silently, others return errors like \"prompt too long; exceeded max context length by X tokens\"\n */\nconst OVERFLOW_PATTERNS = [\n\t/prompt (?:is )?too long/i, // Anthropic and z.ai token overflow\n\t/prompt exceeds max length/i, // z.ai CN endpoint token overflow\n\t/request_too_large/i, // Anthropic request byte-size overflow (HTTP 413)\n\t/input is too long for requested model/i, // Amazon Bedrock\n\t/exceeds the context window/i, // OpenAI (Completions & Responses API)\n\t/exceeds (?:the )?(?:model'?s )?maximum context length(?: of [\\d,]+ tokens?|\\s*\\([\\d,]+\\))/i, // OpenAI-compatible proxies (LiteLLM)\n\t/input token count.*exceeds the maximum/i, // Google (Gemini)\n\t/maximum prompt length is \\d+/i, // xAI (Grok)\n\t/reduce the length of the messages/i, // Groq\n\t/maximum context length is \\d+ tokens/i, // OpenRouter (all backends)\n\t/exceeds (?:the )?maximum allowed input length of [\\d,]+ tokens?/i, // OpenRouter/Poolside\n\t/exceeds the limit of \\d+/i, // GitHub Copilot\n\t/exceeds the available context size/i, // llama.cpp server\n\t/greater than the context length/i, // LM Studio\n\t/context window exceeds limit/i, // MiniMax\n\t/exceeded model token limit/i, // Kimi For Coding\n\t/too large for model with \\d+ maximum context length/i, // Mistral\n\t/prompt has [\\d,]+ tokens?, but the configured context size is [\\d,]+ tokens?/i, // DS4 server\n\t/model_context_window_exceeded/i, // z.ai non-standard finish_reason surfaced as error text\n\t/prompt too long; exceeded (?:max )?context length/i, // Ollama explicit overflow error\n\t/context[_ ]length[_ ]exceeded/i, // Generic fallback\n\t/too many tokens/i, // Generic fallback\n\t/token limit exceeded/i, // Generic fallback\n\t// HTTP 413 means request-size overflow regardless of provider or body text.\n\t// Bare SDK status; formatted provider headers are parsed separately below.\n\t/^413(?:\\s|:|$)/i,\n];\n\n/** Match the first single-line API-error header with linear work and unrestricted provider names. */\nfunction hasRequestSizeOverflowHeader(message: string): boolean {\n\tconst header = \" api error (\";\n\tfor (let index = 0; index < message.length; index++) {\n\t\tconst code = message.charCodeAt(index);\n\t\tif (code === 10 || code === 13) return false;\n\t\tif (code !== 32 || index === 0) continue;\n\t\t// The fixed-size comparison cannot backtrack over the provider prefix.\n\t\tif (message.slice(index, index + header.length).toLowerCase() === header) {\n\t\t\t// Stop at the first header so a different status's body cannot supply a 413 header.\n\t\t\treturn message.startsWith(\"413):\", index + header.length);\n\t\t}\n\t}\n\treturn false;\n}\n\nconst CEREBRAS_BODYLESS_OVERFLOW_PATTERN = /^4(?:00|13)\\s*(?:status code)?\\s*\\(no body\\)/i;\n\n/**\n * Patterns that indicate non-overflow errors (e.g. rate limiting, server errors).\n * Error messages matching any of these are excluded from overflow detection\n * even if they also match an OVERFLOW_PATTERN.\n *\n * Example: Bedrock formats throttling errors as \"ThrottlingException: Too many tokens,\n * please wait before trying again.\" which would match the /too many tokens/i overflow\n * pattern without this exclusion.\n */\nconst NON_OVERFLOW_PATTERNS = [\n\t/^(Throttling error|Service unavailable):/i, // AWS Bedrock non-overflow errors (human-readable prefixes from formatBedrockError)\n\t/rate limit/i, // Generic rate limiting\n\t/too many requests/i, // Generic HTTP 429 style\n];\n\n/**\n * Check if an assistant message represents a context overflow error.\n *\n * This handles two cases:\n * 1. Error-based overflow: Most providers return stopReason \"error\" with a\n * specific error message pattern.\n * 2. Silent overflow: Some providers accept overflow requests and return\n * successfully. For these, we check if usage.input exceeds the context window.\n *\n * ## Reliability by Provider\n *\n * **Reliable detection (returns error with detectable message):**\n * - Anthropic: \"prompt is too long: X tokens > Y maximum\" or \"request_too_large\"\n * - OpenAI (Completions & Responses): \"exceeds the context window\", \"exceeds the model's maximum context length of X tokens\", or \"exceeds model's maximum context length (X)\"\n * - Google Gemini: \"input token count exceeds the maximum\"\n * - xAI (Grok): \"maximum prompt length is X but request contains Y\"\n * - Groq: \"reduce the length of the messages\"\n * - GitHub Copilot: HTTP 413 \"failed to parse request\" (request byte-size overflow)\n * - Cerebras: 400/413 status code (no body)\n * - Mistral: \"Prompt contains X tokens ... too large for model with Y maximum context length\"\n * - OpenRouter (all backends): \"maximum context length is X tokens\"\n * - llama.cpp: \"exceeds the available context size\"\n * - LM Studio: \"greater than the context length\"\n * - Kimi For Coding: \"exceeded model token limit: X (requested: Y)\"\n * - DS4: \"Prompt has X tokens, but the configured context size is Y tokens\"\n * - z.ai: \"Prompt too long\"\n *\n * **Unreliable detection:**\n * - z.ai: Sometimes accepts overflow silently (detectable via usage.input > contextWindow),\n * sometimes returns rate limit errors. Pass contextWindow param to detect silent overflow.\n * - Xiaomi MiMo: Truncates input to fit contextWindow then returns stopReason \"length\" with\n * output=0. Pass contextWindow param to detect via the \"filled context + zero output\" signal.\n * - Ollama: May truncate input silently for some setups, but may also return explicit\n * overflow errors that match the patterns above. Silent truncation still cannot be\n * detected here because we do not know the expected token count.\n *\n * ## Custom Providers\n *\n * If you've added custom models via settings.json, this function may not detect\n * overflow errors from those providers. To add support:\n *\n * 1. Send a request that exceeds the model's context window\n * 2. Check the errorMessage in the response\n * 3. Create a regex pattern that matches the error\n * 4. The pattern should be added to OVERFLOW_PATTERNS in this file, or\n * check the errorMessage yourself before calling this function\n *\n * @param message - The assistant message to check\n * @param contextWindow - Optional context window size for detecting silent overflow (z.ai)\n * @returns true if the message indicates a context overflow\n */\nexport function isContextOverflow(message: AssistantMessage, contextWindow?: number): boolean {\n\t// Case 1: Check error message patterns\n\tif (message.stopReason === \"error\" && message.errorMessage) {\n\t\t// Skip messages matching known non-overflow patterns (e.g. throttling / rate-limit)\n\t\tconst isNonOverflow = NON_OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!));\n\t\tif (!isNonOverflow) {\n\t\t\tif (\n\t\t\t\thasRequestSizeOverflowHeader(message.errorMessage) ||\n\t\t\t\tOVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!))\n\t\t\t) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (message.provider === \"cerebras\" && CEREBRAS_BODYLESS_OVERFLOW_PATTERN.test(message.errorMessage)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Case 2: Silent overflow (z.ai style) - successful but usage exceeds context\n\tif (contextWindow && message.stopReason === \"stop\") {\n\t\tconst inputTokens = message.usage.input + message.usage.cacheRead;\n\t\tif (inputTokens > contextWindow) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Case 3: Length-stop overflow (Xiaomi MiMo style) - server truncates oversized input\n\t// to fit the context window, leaving no room for output. Returns stopReason \"length\"\n\t// with output=0 and input+cacheRead filling the context window.\n\tif (contextWindow && message.stopReason === \"length\" && message.usage.output === 0) {\n\t\tconst inputTokens = message.usage.input + message.usage.cacheRead;\n\t\tif (inputTokens >= contextWindow * 0.99) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Check whether a length stop ended below the caller or model's intended output limit.\n * Such responses may be caused by context pressure or provider-side truncation, so callers\n * can make one bounded compact-and-retry attempt. `desiredMaxOutput` must be the original\n * limit before any context-based clamping.\n */\nexport function isRecoverableLength(message: AssistantMessage, desiredMaxOutput: number): boolean {\n\treturn message.stopReason === \"length\" && desiredMaxOutput > 0 && message.usage.output < desiredMaxOutput;\n}\n\n/**\n * Get the overflow patterns for testing purposes.\n */\nexport function getOverflowPatterns(): RegExp[] {\n\treturn [...OVERFLOW_PATTERNS];\n}\n"]}
@@ -2,7 +2,7 @@
2
2
  * Regex patterns to detect context overflow errors from different providers.
3
3
  *
4
4
  * These patterns match error messages returned when the input exceeds
5
- * the model's context window.
5
+ * the model's context window or the provider's request byte-size limit.
6
6
  *
7
7
  * Provider-specific patterns (with example error messages):
8
8
  *
@@ -10,6 +10,7 @@
10
10
  * - Anthropic: "413 {\"error\":{\"type\":\"request_too_large\",\"message\":\"Request exceeds the maximum size\"}}"
11
11
  * - OpenAI: "Your input exceeds the context window of this model"
12
12
  * - OpenAI/LiteLLM: "Requested token count exceeds the model's maximum context length of 131072 tokens"
13
+ * - OpenAI-compatible: "Input length (265330) exceeds model's maximum context length (262144)."
13
14
  * - Google: "The input token count (1196265) exceeds the maximum number of tokens allowed (1048575)"
14
15
  * - xAI: "This model's maximum prompt length is 131072 but the request contains 537812 tokens"
15
16
  * - Groq: "Please reduce the length of the messages or completion"
@@ -17,19 +18,21 @@
17
18
  * - llama.cpp: "the request exceeds the available context size, try increasing it"
18
19
  * - LM Studio: "tokens to keep from the initial prompt is greater than the context length"
19
20
  * - GitHub Copilot: "prompt token count of X exceeds the limit of Y"
21
+ * - GitHub Copilot: 'github-copilot API error (413): {"message":"failed to parse request","code":""}'
20
22
  * - MiniMax: "invalid params, context window exceeds limit"
21
23
  * - Kimi For Coding: "Your request exceeded model token limit: X (requested: Y)"
22
24
  * - DS4: "Prompt has X tokens, but the configured context size is Y tokens"
23
25
  * - Cerebras: "400/413 status code (no body)"
24
26
  * - Mistral: "Prompt contains X tokens ... too large for model with Y maximum context length"
25
- * - z.ai: Does NOT error, accepts overflow silently - handled via usage.input > contextWindow
27
+ * - z.ai: `{"code":"1261","message":"Prompt too long"}`, `{"code":"1261","message":"Prompt exceeds max length"}` (CN endpoint), or silent overflow via usage.input > contextWindow
26
28
  * - Xiaomi MiMo: Truncates input to fill contextWindow exactly, then returns finish_reason "length"
27
29
  * with output=0 (no room left to generate). Detected via stopReason "length" + zero output +
28
30
  * input filling the context window.
29
31
  * - Ollama: Some deployments truncate silently, others return errors like "prompt too long; exceeded max context length by X tokens"
30
32
  */
31
33
  const OVERFLOW_PATTERNS = [
32
- /prompt is too long/i, // Anthropic token overflow
34
+ /prompt (?:is )?too long/i, // Anthropic and z.ai token overflow
35
+ /prompt exceeds max length/i, // z.ai CN endpoint token overflow
33
36
  /request_too_large/i, // Anthropic request byte-size overflow (HTTP 413)
34
37
  /input is too long for requested model/i, // Amazon Bedrock
35
38
  /exceeds the context window/i, // OpenAI (Completions & Responses API)
@@ -51,8 +54,28 @@ const OVERFLOW_PATTERNS = [
51
54
  /context[_ ]length[_ ]exceeded/i, // Generic fallback
52
55
  /too many tokens/i, // Generic fallback
53
56
  /token limit exceeded/i, // Generic fallback
54
- /^4(?:00|13)\s*(?:status code)?\s*\(no body\)/i, // Cerebras: 400/413 with no body
57
+ // HTTP 413 means request-size overflow regardless of provider or body text.
58
+ // Bare SDK status; formatted provider headers are parsed separately below.
59
+ /^413(?:\s|:|$)/i,
55
60
  ];
61
+ /** Match the first single-line API-error header with linear work and unrestricted provider names. */
62
+ function hasRequestSizeOverflowHeader(message) {
63
+ const header = " api error (";
64
+ for (let index = 0; index < message.length; index++) {
65
+ const code = message.charCodeAt(index);
66
+ if (code === 10 || code === 13)
67
+ return false;
68
+ if (code !== 32 || index === 0)
69
+ continue;
70
+ // The fixed-size comparison cannot backtrack over the provider prefix.
71
+ if (message.slice(index, index + header.length).toLowerCase() === header) {
72
+ // Stop at the first header so a different status's body cannot supply a 413 header.
73
+ return message.startsWith("413):", index + header.length);
74
+ }
75
+ }
76
+ return false;
77
+ }
78
+ const CEREBRAS_BODYLESS_OVERFLOW_PATTERN = /^4(?:00|13)\s*(?:status code)?\s*\(no body\)/i;
56
79
  /**
57
80
  * Patterns that indicate non-overflow errors (e.g. rate limiting, server errors).
58
81
  * Error messages matching any of these are excluded from overflow detection
@@ -80,10 +103,11 @@ const NON_OVERFLOW_PATTERNS = [
80
103
  *
81
104
  * **Reliable detection (returns error with detectable message):**
82
105
  * - Anthropic: "prompt is too long: X tokens > Y maximum" or "request_too_large"
83
- * - OpenAI (Completions & Responses): "exceeds the context window" or "exceeds the model's maximum context length of X tokens"
106
+ * - OpenAI (Completions & Responses): "exceeds the context window", "exceeds the model's maximum context length of X tokens", or "exceeds model's maximum context length (X)"
84
107
  * - Google Gemini: "input token count exceeds the maximum"
85
108
  * - xAI (Grok): "maximum prompt length is X but request contains Y"
86
109
  * - Groq: "reduce the length of the messages"
110
+ * - GitHub Copilot: HTTP 413 "failed to parse request" (request byte-size overflow)
87
111
  * - Cerebras: 400/413 status code (no body)
88
112
  * - Mistral: "Prompt contains X tokens ... too large for model with Y maximum context length"
89
113
  * - OpenRouter (all backends): "maximum context length is X tokens"
@@ -91,6 +115,7 @@ const NON_OVERFLOW_PATTERNS = [
91
115
  * - LM Studio: "greater than the context length"
92
116
  * - Kimi For Coding: "exceeded model token limit: X (requested: Y)"
93
117
  * - DS4: "Prompt has X tokens, but the configured context size is Y tokens"
118
+ * - z.ai: "Prompt too long"
94
119
  *
95
120
  * **Unreliable detection:**
96
121
  * - z.ai: Sometimes accepts overflow silently (detectable via usage.input > contextWindow),
@@ -121,8 +146,14 @@ export function isContextOverflow(message, contextWindow) {
121
146
  if (message.stopReason === "error" && message.errorMessage) {
122
147
  // Skip messages matching known non-overflow patterns (e.g. throttling / rate-limit)
123
148
  const isNonOverflow = NON_OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage));
124
- if (!isNonOverflow && OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage))) {
125
- return true;
149
+ if (!isNonOverflow) {
150
+ if (hasRequestSizeOverflowHeader(message.errorMessage) ||
151
+ OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage))) {
152
+ return true;
153
+ }
154
+ if (message.provider === "cerebras" && CEREBRAS_BODYLESS_OVERFLOW_PATTERN.test(message.errorMessage)) {
155
+ return true;
156
+ }
126
157
  }
127
158
  }
128
159
  // Case 2: Silent overflow (z.ai style) - successful but usage exceeds context
@@ -143,6 +174,15 @@ export function isContextOverflow(message, contextWindow) {
143
174
  }
144
175
  return false;
145
176
  }
177
+ /**
178
+ * Check whether a length stop ended below the caller or model's intended output limit.
179
+ * Such responses may be caused by context pressure or provider-side truncation, so callers
180
+ * can make one bounded compact-and-retry attempt. `desiredMaxOutput` must be the original
181
+ * limit before any context-based clamping.
182
+ */
183
+ export function isRecoverableLength(message, desiredMaxOutput) {
184
+ return message.stopReason === "length" && desiredMaxOutput > 0 && message.usage.output < desiredMaxOutput;
185
+ }
146
186
  /**
147
187
  * Get the overflow patterns for testing purposes.
148
188
  */
@@ -1 +1 @@
1
- {"version":3,"file":"overflow.js","sourceRoot":"","sources":["../../src/utils/overflow.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,iBAAiB,GAAG;IACzB,qBAAqB,EAAE,2BAA2B;IAClD,oBAAoB,EAAE,kDAAkD;IACxE,wCAAwC,EAAE,iBAAiB;IAC3D,6BAA6B,EAAE,uCAAuC;IACtE,4FAA4F,EAAE,sCAAsC;IACpI,yCAAyC,EAAE,kBAAkB;IAC7D,+BAA+B,EAAE,aAAa;IAC9C,oCAAoC,EAAE,OAAO;IAC7C,uCAAuC,EAAE,4BAA4B;IACrE,kEAAkE,EAAE,sBAAsB;IAC1F,2BAA2B,EAAE,iBAAiB;IAC9C,qCAAqC,EAAE,mBAAmB;IAC1D,kCAAkC,EAAE,YAAY;IAChD,+BAA+B,EAAE,UAAU;IAC3C,6BAA6B,EAAE,kBAAkB;IACjD,sDAAsD,EAAE,UAAU;IAClE,+EAA+E,EAAE,aAAa;IAC9F,gCAAgC,EAAE,yDAAyD;IAC3F,oDAAoD,EAAE,iCAAiC;IACvF,gCAAgC,EAAE,mBAAmB;IACrD,kBAAkB,EAAE,mBAAmB;IACvC,uBAAuB,EAAE,mBAAmB;IAC5C,+CAA+C,EAAE,iCAAiC;CAClF,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,qBAAqB,GAAG;IAC7B,2CAA2C,EAAE,oFAAoF;IACjI,aAAa,EAAE,wBAAwB;IACvC,oBAAoB,EAAE,yBAAyB;CAC/C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAyB,EAAE,aAAsB,EAAW;IAC7F,uCAAuC;IACvC,IAAI,OAAO,CAAC,UAAU,KAAK,OAAO,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;QAC5D,oFAAoF;QACpF,MAAM,aAAa,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,YAAa,CAAC,CAAC,CAAC;QACvF,IAAI,CAAC,aAAa,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,YAAa,CAAC,CAAC,EAAE,CAAC;YACpF,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IAED,8EAA8E;IAC9E,IAAI,aAAa,IAAI,OAAO,CAAC,UAAU,KAAK,MAAM,EAAE,CAAC;QACpD,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC;QAClE,IAAI,WAAW,GAAG,aAAa,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IAED,sFAAsF;IACtF,qFAAqF;IACrF,gEAAgE;IAChE,IAAI,aAAa,IAAI,OAAO,CAAC,UAAU,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpF,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC;QAClE,IAAI,WAAW,IAAI,aAAa,GAAG,IAAI,EAAE,CAAC;YACzC,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IAED,OAAO,KAAK,CAAC;AAAA,CACb;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,GAAa;IAC/C,OAAO,CAAC,GAAG,iBAAiB,CAAC,CAAC;AAAA,CAC9B","sourcesContent":["import type { AssistantMessage } from \"../types.js\";\n\n/**\n * Regex patterns to detect context overflow errors from different providers.\n *\n * These patterns match error messages returned when the input exceeds\n * the model's context window.\n *\n * Provider-specific patterns (with example error messages):\n *\n * - Anthropic: \"prompt is too long: 213462 tokens > 200000 maximum\"\n * - Anthropic: \"413 {\\\"error\\\":{\\\"type\\\":\\\"request_too_large\\\",\\\"message\\\":\\\"Request exceeds the maximum size\\\"}}\"\n * - OpenAI: \"Your input exceeds the context window of this model\"\n * - OpenAI/LiteLLM: \"Requested token count exceeds the model's maximum context length of 131072 tokens\"\n * - Google: \"The input token count (1196265) exceeds the maximum number of tokens allowed (1048575)\"\n * - xAI: \"This model's maximum prompt length is 131072 but the request contains 537812 tokens\"\n * - Groq: \"Please reduce the length of the messages or completion\"\n * - OpenRouter: \"This endpoint's maximum context length is X tokens. However, you requested about Y tokens\"\n * - llama.cpp: \"the request exceeds the available context size, try increasing it\"\n * - LM Studio: \"tokens to keep from the initial prompt is greater than the context length\"\n * - GitHub Copilot: \"prompt token count of X exceeds the limit of Y\"\n * - MiniMax: \"invalid params, context window exceeds limit\"\n * - Kimi For Coding: \"Your request exceeded model token limit: X (requested: Y)\"\n * - DS4: \"Prompt has X tokens, but the configured context size is Y tokens\"\n * - Cerebras: \"400/413 status code (no body)\"\n * - Mistral: \"Prompt contains X tokens ... too large for model with Y maximum context length\"\n * - z.ai: Does NOT error, accepts overflow silently - handled via usage.input > contextWindow\n * - Xiaomi MiMo: Truncates input to fill contextWindow exactly, then returns finish_reason \"length\"\n * with output=0 (no room left to generate). Detected via stopReason \"length\" + zero output +\n * input filling the context window.\n * - Ollama: Some deployments truncate silently, others return errors like \"prompt too long; exceeded max context length by X tokens\"\n */\nconst OVERFLOW_PATTERNS = [\n\t/prompt is too long/i, // Anthropic token overflow\n\t/request_too_large/i, // Anthropic request byte-size overflow (HTTP 413)\n\t/input is too long for requested model/i, // Amazon Bedrock\n\t/exceeds the context window/i, // OpenAI (Completions & Responses API)\n\t/exceeds (?:the )?(?:model'?s )?maximum context length(?: of [\\d,]+ tokens?|\\s*\\([\\d,]+\\))/i, // OpenAI-compatible proxies (LiteLLM)\n\t/input token count.*exceeds the maximum/i, // Google (Gemini)\n\t/maximum prompt length is \\d+/i, // xAI (Grok)\n\t/reduce the length of the messages/i, // Groq\n\t/maximum context length is \\d+ tokens/i, // OpenRouter (all backends)\n\t/exceeds (?:the )?maximum allowed input length of [\\d,]+ tokens?/i, // OpenRouter/Poolside\n\t/exceeds the limit of \\d+/i, // GitHub Copilot\n\t/exceeds the available context size/i, // llama.cpp server\n\t/greater than the context length/i, // LM Studio\n\t/context window exceeds limit/i, // MiniMax\n\t/exceeded model token limit/i, // Kimi For Coding\n\t/too large for model with \\d+ maximum context length/i, // Mistral\n\t/prompt has [\\d,]+ tokens?, but the configured context size is [\\d,]+ tokens?/i, // DS4 server\n\t/model_context_window_exceeded/i, // z.ai non-standard finish_reason surfaced as error text\n\t/prompt too long; exceeded (?:max )?context length/i, // Ollama explicit overflow error\n\t/context[_ ]length[_ ]exceeded/i, // Generic fallback\n\t/too many tokens/i, // Generic fallback\n\t/token limit exceeded/i, // Generic fallback\n\t/^4(?:00|13)\\s*(?:status code)?\\s*\\(no body\\)/i, // Cerebras: 400/413 with no body\n];\n\n/**\n * Patterns that indicate non-overflow errors (e.g. rate limiting, server errors).\n * Error messages matching any of these are excluded from overflow detection\n * even if they also match an OVERFLOW_PATTERN.\n *\n * Example: Bedrock formats throttling errors as \"ThrottlingException: Too many tokens,\n * please wait before trying again.\" which would match the /too many tokens/i overflow\n * pattern without this exclusion.\n */\nconst NON_OVERFLOW_PATTERNS = [\n\t/^(Throttling error|Service unavailable):/i, // AWS Bedrock non-overflow errors (human-readable prefixes from formatBedrockError)\n\t/rate limit/i, // Generic rate limiting\n\t/too many requests/i, // Generic HTTP 429 style\n];\n\n/**\n * Check if an assistant message represents a context overflow error.\n *\n * This handles two cases:\n * 1. Error-based overflow: Most providers return stopReason \"error\" with a\n * specific error message pattern.\n * 2. Silent overflow: Some providers accept overflow requests and return\n * successfully. For these, we check if usage.input exceeds the context window.\n *\n * ## Reliability by Provider\n *\n * **Reliable detection (returns error with detectable message):**\n * - Anthropic: \"prompt is too long: X tokens > Y maximum\" or \"request_too_large\"\n * - OpenAI (Completions & Responses): \"exceeds the context window\" or \"exceeds the model's maximum context length of X tokens\"\n * - Google Gemini: \"input token count exceeds the maximum\"\n * - xAI (Grok): \"maximum prompt length is X but request contains Y\"\n * - Groq: \"reduce the length of the messages\"\n * - Cerebras: 400/413 status code (no body)\n * - Mistral: \"Prompt contains X tokens ... too large for model with Y maximum context length\"\n * - OpenRouter (all backends): \"maximum context length is X tokens\"\n * - llama.cpp: \"exceeds the available context size\"\n * - LM Studio: \"greater than the context length\"\n * - Kimi For Coding: \"exceeded model token limit: X (requested: Y)\"\n * - DS4: \"Prompt has X tokens, but the configured context size is Y tokens\"\n *\n * **Unreliable detection:**\n * - z.ai: Sometimes accepts overflow silently (detectable via usage.input > contextWindow),\n * sometimes returns rate limit errors. Pass contextWindow param to detect silent overflow.\n * - Xiaomi MiMo: Truncates input to fit contextWindow then returns stopReason \"length\" with\n * output=0. Pass contextWindow param to detect via the \"filled context + zero output\" signal.\n * - Ollama: May truncate input silently for some setups, but may also return explicit\n * overflow errors that match the patterns above. Silent truncation still cannot be\n * detected here because we do not know the expected token count.\n *\n * ## Custom Providers\n *\n * If you've added custom models via settings.json, this function may not detect\n * overflow errors from those providers. To add support:\n *\n * 1. Send a request that exceeds the model's context window\n * 2. Check the errorMessage in the response\n * 3. Create a regex pattern that matches the error\n * 4. The pattern should be added to OVERFLOW_PATTERNS in this file, or\n * check the errorMessage yourself before calling this function\n *\n * @param message - The assistant message to check\n * @param contextWindow - Optional context window size for detecting silent overflow (z.ai)\n * @returns true if the message indicates a context overflow\n */\nexport function isContextOverflow(message: AssistantMessage, contextWindow?: number): boolean {\n\t// Case 1: Check error message patterns\n\tif (message.stopReason === \"error\" && message.errorMessage) {\n\t\t// Skip messages matching known non-overflow patterns (e.g. throttling / rate-limit)\n\t\tconst isNonOverflow = NON_OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!));\n\t\tif (!isNonOverflow && OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!))) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Case 2: Silent overflow (z.ai style) - successful but usage exceeds context\n\tif (contextWindow && message.stopReason === \"stop\") {\n\t\tconst inputTokens = message.usage.input + message.usage.cacheRead;\n\t\tif (inputTokens > contextWindow) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Case 3: Length-stop overflow (Xiaomi MiMo style) - server truncates oversized input\n\t// to fit the context window, leaving no room for output. Returns stopReason \"length\"\n\t// with output=0 and input+cacheRead filling the context window.\n\tif (contextWindow && message.stopReason === \"length\" && message.usage.output === 0) {\n\t\tconst inputTokens = message.usage.input + message.usage.cacheRead;\n\t\tif (inputTokens >= contextWindow * 0.99) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Get the overflow patterns for testing purposes.\n */\nexport function getOverflowPatterns(): RegExp[] {\n\treturn [...OVERFLOW_PATTERNS];\n}\n"]}
1
+ {"version":3,"file":"overflow.js","sourceRoot":"","sources":["../../src/utils/overflow.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"../types.js\";\n\n/**\n * Regex patterns to detect context overflow errors from different providers.\n *\n * These patterns match error messages returned when the input exceeds\n * the model's context window or the provider's request byte-size limit.\n *\n * Provider-specific patterns (with example error messages):\n *\n * - Anthropic: \"prompt is too long: 213462 tokens > 200000 maximum\"\n * - Anthropic: \"413 {\\\"error\\\":{\\\"type\\\":\\\"request_too_large\\\",\\\"message\\\":\\\"Request exceeds the maximum size\\\"}}\"\n * - OpenAI: \"Your input exceeds the context window of this model\"\n * - OpenAI/LiteLLM: \"Requested token count exceeds the model's maximum context length of 131072 tokens\"\n * - OpenAI-compatible: \"Input length (265330) exceeds model's maximum context length (262144).\"\n * - Google: \"The input token count (1196265) exceeds the maximum number of tokens allowed (1048575)\"\n * - xAI: \"This model's maximum prompt length is 131072 but the request contains 537812 tokens\"\n * - Groq: \"Please reduce the length of the messages or completion\"\n * - OpenRouter: \"This endpoint's maximum context length is X tokens. However, you requested about Y tokens\"\n * - llama.cpp: \"the request exceeds the available context size, try increasing it\"\n * - LM Studio: \"tokens to keep from the initial prompt is greater than the context length\"\n * - GitHub Copilot: \"prompt token count of X exceeds the limit of Y\"\n * - GitHub Copilot: 'github-copilot API error (413): {\"message\":\"failed to parse request\",\"code\":\"\"}'\n * - MiniMax: \"invalid params, context window exceeds limit\"\n * - Kimi For Coding: \"Your request exceeded model token limit: X (requested: Y)\"\n * - DS4: \"Prompt has X tokens, but the configured context size is Y tokens\"\n * - Cerebras: \"400/413 status code (no body)\"\n * - Mistral: \"Prompt contains X tokens ... too large for model with Y maximum context length\"\n * - z.ai: `{\"code\":\"1261\",\"message\":\"Prompt too long\"}`, `{\"code\":\"1261\",\"message\":\"Prompt exceeds max length\"}` (CN endpoint), or silent overflow via usage.input > contextWindow\n * - Xiaomi MiMo: Truncates input to fill contextWindow exactly, then returns finish_reason \"length\"\n * with output=0 (no room left to generate). Detected via stopReason \"length\" + zero output +\n * input filling the context window.\n * - Ollama: Some deployments truncate silently, others return errors like \"prompt too long; exceeded max context length by X tokens\"\n */\nconst OVERFLOW_PATTERNS = [\n\t/prompt (?:is )?too long/i, // Anthropic and z.ai token overflow\n\t/prompt exceeds max length/i, // z.ai CN endpoint token overflow\n\t/request_too_large/i, // Anthropic request byte-size overflow (HTTP 413)\n\t/input is too long for requested model/i, // Amazon Bedrock\n\t/exceeds the context window/i, // OpenAI (Completions & Responses API)\n\t/exceeds (?:the )?(?:model'?s )?maximum context length(?: of [\\d,]+ tokens?|\\s*\\([\\d,]+\\))/i, // OpenAI-compatible proxies (LiteLLM)\n\t/input token count.*exceeds the maximum/i, // Google (Gemini)\n\t/maximum prompt length is \\d+/i, // xAI (Grok)\n\t/reduce the length of the messages/i, // Groq\n\t/maximum context length is \\d+ tokens/i, // OpenRouter (all backends)\n\t/exceeds (?:the )?maximum allowed input length of [\\d,]+ tokens?/i, // OpenRouter/Poolside\n\t/exceeds the limit of \\d+/i, // GitHub Copilot\n\t/exceeds the available context size/i, // llama.cpp server\n\t/greater than the context length/i, // LM Studio\n\t/context window exceeds limit/i, // MiniMax\n\t/exceeded model token limit/i, // Kimi For Coding\n\t/too large for model with \\d+ maximum context length/i, // Mistral\n\t/prompt has [\\d,]+ tokens?, but the configured context size is [\\d,]+ tokens?/i, // DS4 server\n\t/model_context_window_exceeded/i, // z.ai non-standard finish_reason surfaced as error text\n\t/prompt too long; exceeded (?:max )?context length/i, // Ollama explicit overflow error\n\t/context[_ ]length[_ ]exceeded/i, // Generic fallback\n\t/too many tokens/i, // Generic fallback\n\t/token limit exceeded/i, // Generic fallback\n\t// HTTP 413 means request-size overflow regardless of provider or body text.\n\t// Bare SDK status; formatted provider headers are parsed separately below.\n\t/^413(?:\\s|:|$)/i,\n];\n\n/** Match the first single-line API-error header with linear work and unrestricted provider names. */\nfunction hasRequestSizeOverflowHeader(message: string): boolean {\n\tconst header = \" api error (\";\n\tfor (let index = 0; index < message.length; index++) {\n\t\tconst code = message.charCodeAt(index);\n\t\tif (code === 10 || code === 13) return false;\n\t\tif (code !== 32 || index === 0) continue;\n\t\t// The fixed-size comparison cannot backtrack over the provider prefix.\n\t\tif (message.slice(index, index + header.length).toLowerCase() === header) {\n\t\t\t// Stop at the first header so a different status's body cannot supply a 413 header.\n\t\t\treturn message.startsWith(\"413):\", index + header.length);\n\t\t}\n\t}\n\treturn false;\n}\n\nconst CEREBRAS_BODYLESS_OVERFLOW_PATTERN = /^4(?:00|13)\\s*(?:status code)?\\s*\\(no body\\)/i;\n\n/**\n * Patterns that indicate non-overflow errors (e.g. rate limiting, server errors).\n * Error messages matching any of these are excluded from overflow detection\n * even if they also match an OVERFLOW_PATTERN.\n *\n * Example: Bedrock formats throttling errors as \"ThrottlingException: Too many tokens,\n * please wait before trying again.\" which would match the /too many tokens/i overflow\n * pattern without this exclusion.\n */\nconst NON_OVERFLOW_PATTERNS = [\n\t/^(Throttling error|Service unavailable):/i, // AWS Bedrock non-overflow errors (human-readable prefixes from formatBedrockError)\n\t/rate limit/i, // Generic rate limiting\n\t/too many requests/i, // Generic HTTP 429 style\n];\n\n/**\n * Check if an assistant message represents a context overflow error.\n *\n * This handles two cases:\n * 1. Error-based overflow: Most providers return stopReason \"error\" with a\n * specific error message pattern.\n * 2. Silent overflow: Some providers accept overflow requests and return\n * successfully. For these, we check if usage.input exceeds the context window.\n *\n * ## Reliability by Provider\n *\n * **Reliable detection (returns error with detectable message):**\n * - Anthropic: \"prompt is too long: X tokens > Y maximum\" or \"request_too_large\"\n * - OpenAI (Completions & Responses): \"exceeds the context window\", \"exceeds the model's maximum context length of X tokens\", or \"exceeds model's maximum context length (X)\"\n * - Google Gemini: \"input token count exceeds the maximum\"\n * - xAI (Grok): \"maximum prompt length is X but request contains Y\"\n * - Groq: \"reduce the length of the messages\"\n * - GitHub Copilot: HTTP 413 \"failed to parse request\" (request byte-size overflow)\n * - Cerebras: 400/413 status code (no body)\n * - Mistral: \"Prompt contains X tokens ... too large for model with Y maximum context length\"\n * - OpenRouter (all backends): \"maximum context length is X tokens\"\n * - llama.cpp: \"exceeds the available context size\"\n * - LM Studio: \"greater than the context length\"\n * - Kimi For Coding: \"exceeded model token limit: X (requested: Y)\"\n * - DS4: \"Prompt has X tokens, but the configured context size is Y tokens\"\n * - z.ai: \"Prompt too long\"\n *\n * **Unreliable detection:**\n * - z.ai: Sometimes accepts overflow silently (detectable via usage.input > contextWindow),\n * sometimes returns rate limit errors. Pass contextWindow param to detect silent overflow.\n * - Xiaomi MiMo: Truncates input to fit contextWindow then returns stopReason \"length\" with\n * output=0. Pass contextWindow param to detect via the \"filled context + zero output\" signal.\n * - Ollama: May truncate input silently for some setups, but may also return explicit\n * overflow errors that match the patterns above. Silent truncation still cannot be\n * detected here because we do not know the expected token count.\n *\n * ## Custom Providers\n *\n * If you've added custom models via settings.json, this function may not detect\n * overflow errors from those providers. To add support:\n *\n * 1. Send a request that exceeds the model's context window\n * 2. Check the errorMessage in the response\n * 3. Create a regex pattern that matches the error\n * 4. The pattern should be added to OVERFLOW_PATTERNS in this file, or\n * check the errorMessage yourself before calling this function\n *\n * @param message - The assistant message to check\n * @param contextWindow - Optional context window size for detecting silent overflow (z.ai)\n * @returns true if the message indicates a context overflow\n */\nexport function isContextOverflow(message: AssistantMessage, contextWindow?: number): boolean {\n\t// Case 1: Check error message patterns\n\tif (message.stopReason === \"error\" && message.errorMessage) {\n\t\t// Skip messages matching known non-overflow patterns (e.g. throttling / rate-limit)\n\t\tconst isNonOverflow = NON_OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!));\n\t\tif (!isNonOverflow) {\n\t\t\tif (\n\t\t\t\thasRequestSizeOverflowHeader(message.errorMessage) ||\n\t\t\t\tOVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!))\n\t\t\t) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (message.provider === \"cerebras\" && CEREBRAS_BODYLESS_OVERFLOW_PATTERN.test(message.errorMessage)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Case 2: Silent overflow (z.ai style) - successful but usage exceeds context\n\tif (contextWindow && message.stopReason === \"stop\") {\n\t\tconst inputTokens = message.usage.input + message.usage.cacheRead;\n\t\tif (inputTokens > contextWindow) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Case 3: Length-stop overflow (Xiaomi MiMo style) - server truncates oversized input\n\t// to fit the context window, leaving no room for output. Returns stopReason \"length\"\n\t// with output=0 and input+cacheRead filling the context window.\n\tif (contextWindow && message.stopReason === \"length\" && message.usage.output === 0) {\n\t\tconst inputTokens = message.usage.input + message.usage.cacheRead;\n\t\tif (inputTokens >= contextWindow * 0.99) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Check whether a length stop ended below the caller or model's intended output limit.\n * Such responses may be caused by context pressure or provider-side truncation, so callers\n * can make one bounded compact-and-retry attempt. `desiredMaxOutput` must be the original\n * limit before any context-based clamping.\n */\nexport function isRecoverableLength(message: AssistantMessage, desiredMaxOutput: number): boolean {\n\treturn message.stopReason === \"length\" && desiredMaxOutput > 0 && message.usage.output < desiredMaxOutput;\n}\n\n/**\n * Get the overflow patterns for testing purposes.\n */\nexport function getOverflowPatterns(): RegExp[] {\n\treturn [...OVERFLOW_PATTERNS];\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"provider-env.d.ts","sourceRoot":"","sources":["../../src/utils/provider-env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAIvF;AAED,8EAA8E;AAC9E,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,MAAM,CAanF","sourcesContent":["import type { ProviderEnv } from \"../types.js\";\n\nlet procEnvCache: Map<string, string> | null = null;\n\n/**\n * Resolve a provider environment value from a scoped override, process.env,\n * or the Bun sandbox fallback used by direct AI package consumers.\n */\nexport function getProviderEnvValue(name: string, env?: ProviderEnv): string | undefined {\n\tif (env && Object.hasOwn(env, name)) return env[name];\n\tif (typeof process !== \"undefined\" && process.env[name] !== undefined) return process.env[name];\n\treturn getBunSandboxEnvValue(name);\n}\n\n/** Resolve a WebSocket/HTTP proxy using the supplied provider environment. */\nexport function resolveProviderProxyUrl(targetUrl: string, env: ProviderEnv): string {\n\tconst target = new URL(targetUrl);\n\tconst protocol = target.protocol.replace(\":\", \"\");\n\tconst port = Number(target.port) || (protocol === \"wss\" || protocol === \"https\" ? 443 : 80);\n\tconst noProxy = getScopedAlias(env, [\"NO_PROXY\", \"no_proxy\"]);\n\tif (noProxy && matchesNoProxy(target.hostname, port, noProxy)) return \"\";\n\n\tconst proxy =\n\t\tprotocol === \"wss\" || protocol === \"https\"\n\t\t\t? getScopedAlias(env, [\"HTTPS_PROXY\", \"https_proxy\", \"HTTP_PROXY\", \"http_proxy\"])\n\t\t\t: getScopedAlias(env, [\"HTTP_PROXY\", \"http_proxy\"]);\n\tif (!proxy) return \"\";\n\treturn proxy.includes(\"://\") ? proxy : `${protocol === \"wss\" ? \"https\" : \"http\"}://${proxy}`;\n}\n\nfunction getBunSandboxEnvValue(name: string): string | undefined {\n\tif (typeof process === \"undefined\" || !process.versions?.bun || Object.keys(process.env).length > 0) {\n\t\treturn undefined;\n\t}\n\n\tif (procEnvCache === null) {\n\t\tprocEnvCache = new Map();\n\t\ttry {\n\t\t\tconst { readFileSync } = require(\"node:fs\") as {\n\t\t\t\treadFileSync(path: string, encoding: BufferEncoding): string;\n\t\t\t};\n\t\t\tfor (const entry of readFileSync(\"/proc/self/environ\", \"utf-8\").split(\"\\0\")) {\n\t\t\t\tconst index = entry.indexOf(\"=\");\n\t\t\t\tif (index > 0) procEnvCache.set(entry.slice(0, index), entry.slice(index + 1));\n\t\t\t}\n\t\t} catch {\n\t\t\t// /proc/self/environ is optional in restricted runtimes.\n\t\t}\n\t}\n\n\treturn procEnvCache.get(name);\n}\n\nfunction getScopedAlias(env: ProviderEnv, names: string[]): string | undefined {\n\tconst hasScopedAlias = names.some((name) => Object.hasOwn(env, name));\n\tif (hasScopedAlias) {\n\t\tfor (const name of names) {\n\t\t\tif (Object.hasOwn(env, name)) return env[name];\n\t\t}\n\t\treturn undefined;\n\t}\n\tfor (const name of names) {\n\t\tconst value = getProviderEnvValue(name);\n\t\tif (value !== undefined) return value;\n\t}\n\treturn undefined;\n}\n\nfunction matchesNoProxy(hostname: string, port: number, value: string): boolean {\n\tif (value.trim() === \"*\") return true;\n\treturn value.split(/[\\s,]+/).some((token) => {\n\t\tif (!token) return false;\n\t\tconst match = token.match(/^(.+):(\\d+)$/);\n\t\tconst host = (match?.[1] ?? token).toLowerCase();\n\t\tif (match && Number(match[2]) !== port) return false;\n\t\tconst normalizedHostname = hostname.toLowerCase();\n\t\treturn host.startsWith(\"*\")\n\t\t\t? normalizedHostname.endsWith(host.slice(1))\n\t\t\t: host.startsWith(\".\")\n\t\t\t\t? normalizedHostname.endsWith(host)\n\t\t\t\t: normalizedHostname === host;\n\t});\n}\n"]}
1
+ {"version":3,"file":"provider-env.d.ts","sourceRoot":"","sources":["../../src/utils/provider-env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAIvF;AAED,8EAA8E;AAC9E,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,MAAM,CAanF","sourcesContent":["import type { ProviderEnv } from \"../types.js\";\n\nlet procEnvCache: Map<string, string> | null = null;\n\n/**\n * Resolve a provider environment value from a scoped override, process.env,\n * or the Bun sandbox fallback used by direct AI package consumers.\n */\nexport function getProviderEnvValue(name: string, env?: ProviderEnv): string | undefined {\n\tif (env && Object.hasOwn(env, name)) return env[name];\n\tif (typeof process !== \"undefined\" && process.env[name] !== undefined) return process.env[name];\n\treturn getBunSandboxEnvValue(name);\n}\n\n/** Resolve a WebSocket/HTTP proxy using the supplied provider environment. */\nexport function resolveProviderProxyUrl(targetUrl: string, env: ProviderEnv): string {\n\tconst target = new URL(targetUrl);\n\tconst protocol = target.protocol.replace(\":\", \"\");\n\tconst port = Number(target.port) || (protocol === \"wss\" || protocol === \"https\" ? 443 : 80);\n\tconst noProxy = getScopedAlias(env, [\"NO_PROXY\", \"no_proxy\"]);\n\tif (noProxy && matchesNoProxy(target.hostname, port, noProxy)) return \"\";\n\n\tconst proxy =\n\t\tprotocol === \"wss\" || protocol === \"https\"\n\t\t\t? getScopedAlias(env, [\"HTTPS_PROXY\", \"https_proxy\", \"HTTP_PROXY\", \"http_proxy\"])\n\t\t\t: getScopedAlias(env, [\"HTTP_PROXY\", \"http_proxy\"]);\n\tif (!proxy) return \"\";\n\treturn proxy.includes(\"://\") ? proxy : `${protocol === \"wss\" ? \"https\" : \"http\"}://${proxy}`;\n}\n\nfunction getBunSandboxEnvValue(name: string): string | undefined {\n\tif (typeof process === \"undefined\" || !process.versions?.bun || Object.keys(process.env).length > 0) {\n\t\treturn undefined;\n\t}\n\n\tif (procEnvCache === null) {\n\t\tprocEnvCache = new Map();\n\t\ttry {\n\t\t\tconst { readFileSync } = require(\"node:fs\") as {\n\t\t\t\treadFileSync(path: string, encoding: BufferEncoding): string;\n\t\t\t};\n\t\t\tfor (const entry of readFileSync(\"/proc/self/environ\", \"utf-8\").split(\"\\0\")) {\n\t\t\t\tconst index = entry.indexOf(\"=\");\n\t\t\t\tif (index > 0) procEnvCache.set(entry.slice(0, index), entry.slice(index + 1));\n\t\t\t}\n\t\t} catch {\n\t\t\t// /proc/self/environ is optional in restricted runtimes.\n\t\t}\n\t}\n\n\treturn procEnvCache.get(name);\n}\n\nfunction getScopedAlias(env: ProviderEnv, names: string[]): string | undefined {\n\tconst hasScopedAlias = names.some((name) => Object.hasOwn(env, name));\n\tif (hasScopedAlias) {\n\t\tfor (const name of names) {\n\t\t\tif (Object.hasOwn(env, name)) return env[name];\n\t\t}\n\t\treturn undefined;\n\t}\n\tfor (const name of names) {\n\t\tconst value = getProviderEnvValue(name);\n\t\tif (value !== undefined) return value;\n\t}\n\treturn undefined;\n}\n\nfunction stripBrackets(host: string): string {\n\treturn host.startsWith(\"[\") && host.endsWith(\"]\") ? host.slice(1, -1) : host;\n}\n\nfunction parseNoProxyEntry(entry: string): { host: string; port: number } | undefined {\n\tconst trimmed = entry.trim().toLowerCase();\n\tif (!trimmed) return undefined;\n\n\tif (trimmed.startsWith(\"[\")) {\n\t\tconst closingBracket = trimmed.indexOf(\"]\");\n\t\tif (closingBracket !== -1) {\n\t\t\tconst host = trimmed.slice(1, closingBracket);\n\t\t\tconst rest = trimmed.slice(closingBracket + 1);\n\t\t\tif (rest.startsWith(\":\")) {\n\t\t\t\tconst port = Number.parseInt(rest.slice(1), 10);\n\t\t\t\treturn { host, port: Number.isNaN(port) ? 0 : port };\n\t\t\t}\n\t\t\treturn { host, port: 0 };\n\t\t}\n\t}\n\n\t// A bare IPv6 literal has several colons and no port.\n\tif (trimmed.split(\":\").length > 2) return { host: trimmed, port: 0 };\n\n\tconst colonIndex = trimmed.lastIndexOf(\":\");\n\tif (colonIndex !== -1) {\n\t\tconst port = Number.parseInt(trimmed.slice(colonIndex + 1), 10);\n\t\tif (!Number.isNaN(port)) return { host: trimmed.slice(0, colonIndex), port };\n\t}\n\n\treturn { host: trimmed, port: 0 };\n}\n\nfunction matchesNoProxy(hostname: string, port: number, value: string): boolean {\n\tif (value.trim() === \"*\") return true;\n\tconst targetHost = stripBrackets(hostname.toLowerCase());\n\treturn value.split(/[\\s,]+/).some((token) => {\n\t\tconst parsed = parseNoProxyEntry(token);\n\t\tif (!parsed) return false;\n\t\tif (parsed.port && parsed.port !== port) return false;\n\n\t\tlet domain = stripBrackets(parsed.host);\n\t\tif (domain.startsWith(\"*.\")) {\n\t\t\tdomain = domain.slice(2);\n\t\t} else if (domain.startsWith(\".\") || domain.startsWith(\"*\")) {\n\t\t\tdomain = domain.slice(1);\n\t\t}\n\t\tif (!domain) return false;\n\n\t\t// A root-domain entry covers the domain itself and all of its subdomains.\n\t\treturn targetHost === domain || targetHost.endsWith(`.${domain}`);\n\t});\n}\n"]}
@@ -61,22 +61,57 @@ function getScopedAlias(env, names) {
61
61
  }
62
62
  return undefined;
63
63
  }
64
+ function stripBrackets(host) {
65
+ return host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
66
+ }
67
+ function parseNoProxyEntry(entry) {
68
+ const trimmed = entry.trim().toLowerCase();
69
+ if (!trimmed)
70
+ return undefined;
71
+ if (trimmed.startsWith("[")) {
72
+ const closingBracket = trimmed.indexOf("]");
73
+ if (closingBracket !== -1) {
74
+ const host = trimmed.slice(1, closingBracket);
75
+ const rest = trimmed.slice(closingBracket + 1);
76
+ if (rest.startsWith(":")) {
77
+ const port = Number.parseInt(rest.slice(1), 10);
78
+ return { host, port: Number.isNaN(port) ? 0 : port };
79
+ }
80
+ return { host, port: 0 };
81
+ }
82
+ }
83
+ // A bare IPv6 literal has several colons and no port.
84
+ if (trimmed.split(":").length > 2)
85
+ return { host: trimmed, port: 0 };
86
+ const colonIndex = trimmed.lastIndexOf(":");
87
+ if (colonIndex !== -1) {
88
+ const port = Number.parseInt(trimmed.slice(colonIndex + 1), 10);
89
+ if (!Number.isNaN(port))
90
+ return { host: trimmed.slice(0, colonIndex), port };
91
+ }
92
+ return { host: trimmed, port: 0 };
93
+ }
64
94
  function matchesNoProxy(hostname, port, value) {
65
95
  if (value.trim() === "*")
66
96
  return true;
97
+ const targetHost = stripBrackets(hostname.toLowerCase());
67
98
  return value.split(/[\s,]+/).some((token) => {
68
- if (!token)
99
+ const parsed = parseNoProxyEntry(token);
100
+ if (!parsed)
101
+ return false;
102
+ if (parsed.port && parsed.port !== port)
69
103
  return false;
70
- const match = token.match(/^(.+):(\d+)$/);
71
- const host = (match?.[1] ?? token).toLowerCase();
72
- if (match && Number(match[2]) !== port)
104
+ let domain = stripBrackets(parsed.host);
105
+ if (domain.startsWith("*.")) {
106
+ domain = domain.slice(2);
107
+ }
108
+ else if (domain.startsWith(".") || domain.startsWith("*")) {
109
+ domain = domain.slice(1);
110
+ }
111
+ if (!domain)
73
112
  return false;
74
- const normalizedHostname = hostname.toLowerCase();
75
- return host.startsWith("*")
76
- ? normalizedHostname.endsWith(host.slice(1))
77
- : host.startsWith(".")
78
- ? normalizedHostname.endsWith(host)
79
- : normalizedHostname === host;
113
+ // A root-domain entry covers the domain itself and all of its subdomains.
114
+ return targetHost === domain || targetHost.endsWith(`.${domain}`);
80
115
  });
81
116
  }
82
117
  //# sourceMappingURL=provider-env.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"provider-env.js","sourceRoot":"","sources":["../../src/utils/provider-env.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ProviderEnv } from \"../types.js\";\n\nlet procEnvCache: Map<string, string> | null = null;\n\n/**\n * Resolve a provider environment value from a scoped override, process.env,\n * or the Bun sandbox fallback used by direct AI package consumers.\n */\nexport function getProviderEnvValue(name: string, env?: ProviderEnv): string | undefined {\n\tif (env && Object.hasOwn(env, name)) return env[name];\n\tif (typeof process !== \"undefined\" && process.env[name] !== undefined) return process.env[name];\n\treturn getBunSandboxEnvValue(name);\n}\n\n/** Resolve a WebSocket/HTTP proxy using the supplied provider environment. */\nexport function resolveProviderProxyUrl(targetUrl: string, env: ProviderEnv): string {\n\tconst target = new URL(targetUrl);\n\tconst protocol = target.protocol.replace(\":\", \"\");\n\tconst port = Number(target.port) || (protocol === \"wss\" || protocol === \"https\" ? 443 : 80);\n\tconst noProxy = getScopedAlias(env, [\"NO_PROXY\", \"no_proxy\"]);\n\tif (noProxy && matchesNoProxy(target.hostname, port, noProxy)) return \"\";\n\n\tconst proxy =\n\t\tprotocol === \"wss\" || protocol === \"https\"\n\t\t\t? getScopedAlias(env, [\"HTTPS_PROXY\", \"https_proxy\", \"HTTP_PROXY\", \"http_proxy\"])\n\t\t\t: getScopedAlias(env, [\"HTTP_PROXY\", \"http_proxy\"]);\n\tif (!proxy) return \"\";\n\treturn proxy.includes(\"://\") ? proxy : `${protocol === \"wss\" ? \"https\" : \"http\"}://${proxy}`;\n}\n\nfunction getBunSandboxEnvValue(name: string): string | undefined {\n\tif (typeof process === \"undefined\" || !process.versions?.bun || Object.keys(process.env).length > 0) {\n\t\treturn undefined;\n\t}\n\n\tif (procEnvCache === null) {\n\t\tprocEnvCache = new Map();\n\t\ttry {\n\t\t\tconst { readFileSync } = require(\"node:fs\") as {\n\t\t\t\treadFileSync(path: string, encoding: BufferEncoding): string;\n\t\t\t};\n\t\t\tfor (const entry of readFileSync(\"/proc/self/environ\", \"utf-8\").split(\"\\0\")) {\n\t\t\t\tconst index = entry.indexOf(\"=\");\n\t\t\t\tif (index > 0) procEnvCache.set(entry.slice(0, index), entry.slice(index + 1));\n\t\t\t}\n\t\t} catch {\n\t\t\t// /proc/self/environ is optional in restricted runtimes.\n\t\t}\n\t}\n\n\treturn procEnvCache.get(name);\n}\n\nfunction getScopedAlias(env: ProviderEnv, names: string[]): string | undefined {\n\tconst hasScopedAlias = names.some((name) => Object.hasOwn(env, name));\n\tif (hasScopedAlias) {\n\t\tfor (const name of names) {\n\t\t\tif (Object.hasOwn(env, name)) return env[name];\n\t\t}\n\t\treturn undefined;\n\t}\n\tfor (const name of names) {\n\t\tconst value = getProviderEnvValue(name);\n\t\tif (value !== undefined) return value;\n\t}\n\treturn undefined;\n}\n\nfunction matchesNoProxy(hostname: string, port: number, value: string): boolean {\n\tif (value.trim() === \"*\") return true;\n\treturn value.split(/[\\s,]+/).some((token) => {\n\t\tif (!token) return false;\n\t\tconst match = token.match(/^(.+):(\\d+)$/);\n\t\tconst host = (match?.[1] ?? token).toLowerCase();\n\t\tif (match && Number(match[2]) !== port) return false;\n\t\tconst normalizedHostname = hostname.toLowerCase();\n\t\treturn host.startsWith(\"*\")\n\t\t\t? normalizedHostname.endsWith(host.slice(1))\n\t\t\t: host.startsWith(\".\")\n\t\t\t\t? normalizedHostname.endsWith(host)\n\t\t\t\t: normalizedHostname === host;\n\t});\n}\n"]}
1
+ {"version":3,"file":"provider-env.js","sourceRoot":"","sources":["../../src/utils/provider-env.ts"],"names":[],"mappings":"AAEA,IAAI,YAAY,GAA+B,IAAI,CAAC;AAEpD;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,GAAiB,EAAsB;IACxF,IAAI,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;IACtD,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChG,OAAO,qBAAqB,CAAC,IAAI,CAAC,CAAC;AAAA,CACnC;AAED,8EAA8E;AAC9E,MAAM,UAAU,uBAAuB,CAAC,SAAiB,EAAE,GAAgB,EAAU;IACpF,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,KAAK,KAAK,IAAI,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC5F,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC;IAC9D,IAAI,OAAO,IAAI,cAAc,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC;IAEzE,MAAM,KAAK,GACV,QAAQ,KAAK,KAAK,IAAI,QAAQ,KAAK,OAAO;QACzC,CAAC,CAAC,cAAc,CAAC,GAAG,EAAE,CAAC,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC;QACjF,CAAC,CAAC,cAAc,CAAC,GAAG,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC;IACtD,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,MAAM,KAAK,EAAE,CAAC;AAAA,CAC7F;AAED,SAAS,qBAAqB,CAAC,IAAY,EAAsB;IAChE,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrG,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;QAC3B,YAAY,GAAG,IAAI,GAAG,EAAE,CAAC;QACzB,IAAI,CAAC;YACJ,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,SAAS,CAEzC,CAAC;YACF,KAAK,MAAM,KAAK,IAAI,YAAY,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7E,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBACjC,IAAI,KAAK,GAAG,CAAC;oBAAE,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;YAChF,CAAC;QACF,CAAC;QAAC,MAAM,CAAC;YACR,yDAAyD;QAC1D,CAAC;IACF,CAAC;IAED,OAAO,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAAA,CAC9B;AAED,SAAS,cAAc,CAAC,GAAgB,EAAE,KAAe,EAAsB;IAC9E,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IACtE,IAAI,cAAc,EAAE,CAAC;QACpB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAC1B,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC;gBAAE,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;QAChD,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;IACvC,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB;AAED,SAAS,aAAa,CAAC,IAAY,EAAU;IAC5C,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAAA,CAC7E;AAED,SAAS,iBAAiB,CAAC,KAAa,EAA8C;IACrF,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAE/B,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7B,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5C,IAAI,cAAc,KAAK,CAAC,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;YAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC;YAC/C,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC1B,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAChD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACtD,CAAC;YACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAC1B,CAAC;IACF,CAAC;IAED,sDAAsD;IACtD,IAAI,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAErE,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,UAAU,KAAK,CAAC,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAChE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,IAAI,EAAE,CAAC;IAC9E,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAAA,CAClC;AAED,SAAS,cAAc,CAAC,QAAgB,EAAE,IAAY,EAAE,KAAa,EAAW;IAC/E,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,UAAU,GAAG,aAAa,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;IACzD,OAAO,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC1B,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QAEtD,IAAI,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC1B,CAAC;aAAM,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7D,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC1B,CAAC;QACD,IAAI,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAE1B,0EAA0E;QAC1E,OAAO,UAAU,KAAK,MAAM,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC;IAAA,CAClE,CAAC,CAAC;AAAA,CACH","sourcesContent":["import type { ProviderEnv } from \"../types.js\";\n\nlet procEnvCache: Map<string, string> | null = null;\n\n/**\n * Resolve a provider environment value from a scoped override, process.env,\n * or the Bun sandbox fallback used by direct AI package consumers.\n */\nexport function getProviderEnvValue(name: string, env?: ProviderEnv): string | undefined {\n\tif (env && Object.hasOwn(env, name)) return env[name];\n\tif (typeof process !== \"undefined\" && process.env[name] !== undefined) return process.env[name];\n\treturn getBunSandboxEnvValue(name);\n}\n\n/** Resolve a WebSocket/HTTP proxy using the supplied provider environment. */\nexport function resolveProviderProxyUrl(targetUrl: string, env: ProviderEnv): string {\n\tconst target = new URL(targetUrl);\n\tconst protocol = target.protocol.replace(\":\", \"\");\n\tconst port = Number(target.port) || (protocol === \"wss\" || protocol === \"https\" ? 443 : 80);\n\tconst noProxy = getScopedAlias(env, [\"NO_PROXY\", \"no_proxy\"]);\n\tif (noProxy && matchesNoProxy(target.hostname, port, noProxy)) return \"\";\n\n\tconst proxy =\n\t\tprotocol === \"wss\" || protocol === \"https\"\n\t\t\t? getScopedAlias(env, [\"HTTPS_PROXY\", \"https_proxy\", \"HTTP_PROXY\", \"http_proxy\"])\n\t\t\t: getScopedAlias(env, [\"HTTP_PROXY\", \"http_proxy\"]);\n\tif (!proxy) return \"\";\n\treturn proxy.includes(\"://\") ? proxy : `${protocol === \"wss\" ? \"https\" : \"http\"}://${proxy}`;\n}\n\nfunction getBunSandboxEnvValue(name: string): string | undefined {\n\tif (typeof process === \"undefined\" || !process.versions?.bun || Object.keys(process.env).length > 0) {\n\t\treturn undefined;\n\t}\n\n\tif (procEnvCache === null) {\n\t\tprocEnvCache = new Map();\n\t\ttry {\n\t\t\tconst { readFileSync } = require(\"node:fs\") as {\n\t\t\t\treadFileSync(path: string, encoding: BufferEncoding): string;\n\t\t\t};\n\t\t\tfor (const entry of readFileSync(\"/proc/self/environ\", \"utf-8\").split(\"\\0\")) {\n\t\t\t\tconst index = entry.indexOf(\"=\");\n\t\t\t\tif (index > 0) procEnvCache.set(entry.slice(0, index), entry.slice(index + 1));\n\t\t\t}\n\t\t} catch {\n\t\t\t// /proc/self/environ is optional in restricted runtimes.\n\t\t}\n\t}\n\n\treturn procEnvCache.get(name);\n}\n\nfunction getScopedAlias(env: ProviderEnv, names: string[]): string | undefined {\n\tconst hasScopedAlias = names.some((name) => Object.hasOwn(env, name));\n\tif (hasScopedAlias) {\n\t\tfor (const name of names) {\n\t\t\tif (Object.hasOwn(env, name)) return env[name];\n\t\t}\n\t\treturn undefined;\n\t}\n\tfor (const name of names) {\n\t\tconst value = getProviderEnvValue(name);\n\t\tif (value !== undefined) return value;\n\t}\n\treturn undefined;\n}\n\nfunction stripBrackets(host: string): string {\n\treturn host.startsWith(\"[\") && host.endsWith(\"]\") ? host.slice(1, -1) : host;\n}\n\nfunction parseNoProxyEntry(entry: string): { host: string; port: number } | undefined {\n\tconst trimmed = entry.trim().toLowerCase();\n\tif (!trimmed) return undefined;\n\n\tif (trimmed.startsWith(\"[\")) {\n\t\tconst closingBracket = trimmed.indexOf(\"]\");\n\t\tif (closingBracket !== -1) {\n\t\t\tconst host = trimmed.slice(1, closingBracket);\n\t\t\tconst rest = trimmed.slice(closingBracket + 1);\n\t\t\tif (rest.startsWith(\":\")) {\n\t\t\t\tconst port = Number.parseInt(rest.slice(1), 10);\n\t\t\t\treturn { host, port: Number.isNaN(port) ? 0 : port };\n\t\t\t}\n\t\t\treturn { host, port: 0 };\n\t\t}\n\t}\n\n\t// A bare IPv6 literal has several colons and no port.\n\tif (trimmed.split(\":\").length > 2) return { host: trimmed, port: 0 };\n\n\tconst colonIndex = trimmed.lastIndexOf(\":\");\n\tif (colonIndex !== -1) {\n\t\tconst port = Number.parseInt(trimmed.slice(colonIndex + 1), 10);\n\t\tif (!Number.isNaN(port)) return { host: trimmed.slice(0, colonIndex), port };\n\t}\n\n\treturn { host: trimmed, port: 0 };\n}\n\nfunction matchesNoProxy(hostname: string, port: number, value: string): boolean {\n\tif (value.trim() === \"*\") return true;\n\tconst targetHost = stripBrackets(hostname.toLowerCase());\n\treturn value.split(/[\\s,]+/).some((token) => {\n\t\tconst parsed = parseNoProxyEntry(token);\n\t\tif (!parsed) return false;\n\t\tif (parsed.port && parsed.port !== port) return false;\n\n\t\tlet domain = stripBrackets(parsed.host);\n\t\tif (domain.startsWith(\"*.\")) {\n\t\t\tdomain = domain.slice(2);\n\t\t} else if (domain.startsWith(\".\") || domain.startsWith(\"*\")) {\n\t\t\tdomain = domain.slice(1);\n\t\t}\n\t\tif (!domain) return false;\n\n\t\t// A root-domain entry covers the domain itself and all of its subdomains.\n\t\treturn targetHost === domain || targetHost.endsWith(`.${domain}`);\n\t});\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import type { Api, AssistantMessage, Model } from "../types.js";
2
+ type TargetModel = Pick<Model<Api>, "provider" | "api" | "id">;
3
+ type SourceModel = Pick<AssistantMessage, "provider" | "api">;
4
+ /** Normalize Responses correlation and item IDs identically for execution and replay. */
5
+ export declare function normalizeResponsesToolCallId(id: string, source: SourceModel, target: SourceModel): string;
6
+ /** Identify Google API model families that carry explicit function-call IDs. */
7
+ export declare function requiresToolCallId(modelId: string): boolean;
8
+ /** Create the provider's replay ID transformation; reuse one instance per request. */
9
+ export declare function createProviderToolCallIdNormalizer(model: TargetModel, allowedResponsesProviders?: ReadonlySet<string>): (id: string, source?: SourceModel) => string;
10
+ export {};
11
+ //# sourceMappingURL=tool-call-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-call-id.d.ts","sourceRoot":"","sources":["../../src/utils/tool-call-id.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAGhE,KAAK,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,UAAU,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC;AAC/D,KAAK,WAAW,GAAG,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,KAAK,CAAC,CAAC;AAS9D,yFAAyF;AACzF,wBAAgB,4BAA4B,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,MAAM,CASzG;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAO3D;AAED,sFAAsF;AACtF,wBAAgB,kCAAkC,CACjD,KAAK,EAAE,WAAW,EAClB,yBAAyB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAC7C,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,MAAM,CA2B9C","sourcesContent":["import type { Api, AssistantMessage, Model } from \"../types.js\";\nimport { shortHash } from \"./hash.js\";\n\ntype TargetModel = Pick<Model<Api>, \"provider\" | \"api\" | \"id\">;\ntype SourceModel = Pick<AssistantMessage, \"provider\" | \"api\">;\n\nfunction normalizeResponsesPart(part: string): string {\n\treturn part\n\t\t.replace(/[^a-zA-Z0-9_-]/g, \"_\")\n\t\t.slice(0, 64)\n\t\t.replace(/_+$/, \"\");\n}\n\n/** Normalize Responses correlation and item IDs identically for execution and replay. */\nexport function normalizeResponsesToolCallId(id: string, source: SourceModel, target: SourceModel): string {\n\tconst [callId, itemId] = id.split(\"|\");\n\tconst call = normalizeResponsesPart(callId) || `call_${shortHash(callId)}`;\n\tif (itemId === undefined) return call;\n\tconst foreign = source.provider !== target.provider || source.api !== target.api;\n\tlet item = foreign ? `fc_${shortHash(itemId)}` : normalizeResponsesPart(itemId);\n\tif (!item.startsWith(\"fc_\")) item = normalizeResponsesPart(`fc_${item}`);\n\tif (!item.startsWith(\"fc_\") || item.length === 3) item = `fc_${shortHash(itemId)}`;\n\treturn `${call}|${item}`;\n}\n\n/** Identify Google API model families that carry explicit function-call IDs. */\nexport function requiresToolCallId(modelId: string): boolean {\n\tconst match = modelId.toLowerCase().match(/^gemini(?:-live)?-(\\d+)/);\n\treturn (\n\t\tmodelId.startsWith(\"claude-\") ||\n\t\tmodelId.startsWith(\"gpt-oss-\") ||\n\t\t(match !== null && Number.parseInt(match[1], 10) >= 3)\n\t);\n}\n\n/** Create the provider's replay ID transformation; reuse one instance per request. */\nexport function createProviderToolCallIdNormalizer(\n\tmodel: TargetModel,\n\tallowedResponsesProviders?: ReadonlySet<string>,\n): (id: string, source?: SourceModel) => string {\n\tif (model.api === \"mistral-conversations\") return createMistralNormalizer();\n\treturn (id: string, source: SourceModel = model): string => {\n\t\tswitch (model.api) {\n\t\t\tcase \"openai-responses\":\n\t\t\tcase \"openai-codex-responses\":\n\t\t\tcase \"azure-openai-responses\": {\n\t\t\t\tconst composite =\n\t\t\t\t\tallowedResponsesProviders?.has(model.provider) ??\n\t\t\t\t\t([\"openai\", \"openai-codex\", \"opencode\"].includes(model.provider) ||\n\t\t\t\t\t\t(model.api === \"azure-openai-responses\" && model.provider === \"azure-openai-responses\"));\n\t\t\t\treturn composite\n\t\t\t\t\t? normalizeResponsesToolCallId(id, source, model)\n\t\t\t\t\t: normalizeResponsesPart(id) || `call_${shortHash(id)}`;\n\t\t\t}\n\t\t\tcase \"openai-completions\":\n\t\t\t\treturn normalizeChatId(id, model.provider);\n\t\t\tcase \"anthropic-messages\":\n\t\t\tcase \"bedrock-converse-stream\":\n\t\t\t\treturn id.replace(/[^a-zA-Z0-9_-]/g, \"_\").slice(0, 64);\n\t\t\tcase \"google-generative-ai\":\n\t\t\tcase \"google-vertex\":\n\t\t\t\treturn requiresToolCallId(model.id) ? id.replace(/[^a-zA-Z0-9_-]/g, \"_\").slice(0, 64) : id;\n\t\t\tdefault:\n\t\t\t\treturn id;\n\t\t}\n\t};\n}\n\nfunction normalizeChatId(id: string, provider: string): string {\n\tif (!id.includes(\"|\")) return provider === \"openai\" ? id.slice(0, 40) : id;\n\tconst separatorIndex = id.indexOf(\"|\");\n\tconst callId = id.slice(0, separatorIndex).replace(/[^a-zA-Z0-9_-]/g, \"_\");\n\tconst itemId = id.slice(separatorIndex + 1).replace(/[^a-zA-Z0-9_-]/g, \"_\");\n\tconst combined = itemId.length > 0 ? `${callId}_${itemId}` : callId;\n\tif (combined.length <= 40) return combined;\n\tconst hash = shortHash(id).slice(0, 8);\n\treturn `${callId.slice(0, Math.max(1, 40 - hash.length - 1))}_${hash}`;\n}\n\nfunction createMistralNormalizer(): (id: string) => string {\n\tconst ids = new Map<string, string>();\n\tconst owners = new Map<string, string>();\n\treturn (id) => {\n\t\tconst existing = ids.get(id);\n\t\tif (existing) return existing;\n\t\tconst normalized = id.replace(/[^a-zA-Z0-9]/g, \"\");\n\t\tconst seed = normalized || id;\n\t\tlet attempt = 0;\n\t\twhile (true) {\n\t\t\tconst candidate =\n\t\t\t\tattempt === 0 && normalized.length === 9\n\t\t\t\t\t? normalized\n\t\t\t\t\t: shortHash(attempt === 0 ? seed : `${seed}:${attempt}`)\n\t\t\t\t\t\t\t.replace(/[^a-zA-Z0-9]/g, \"\")\n\t\t\t\t\t\t\t.slice(0, 9);\n\t\t\tconst owner = owners.get(candidate);\n\t\t\tif (!owner || owner === id) {\n\t\t\t\tids.set(id, candidate);\n\t\t\t\towners.set(candidate, id);\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t\tattempt++;\n\t\t}\n\t};\n}\n"]}
@@ -0,0 +1,96 @@
1
+ import { shortHash } from "./hash.js";
2
+ function normalizeResponsesPart(part) {
3
+ return part
4
+ .replace(/[^a-zA-Z0-9_-]/g, "_")
5
+ .slice(0, 64)
6
+ .replace(/_+$/, "");
7
+ }
8
+ /** Normalize Responses correlation and item IDs identically for execution and replay. */
9
+ export function normalizeResponsesToolCallId(id, source, target) {
10
+ const [callId, itemId] = id.split("|");
11
+ const call = normalizeResponsesPart(callId) || `call_${shortHash(callId)}`;
12
+ if (itemId === undefined)
13
+ return call;
14
+ const foreign = source.provider !== target.provider || source.api !== target.api;
15
+ let item = foreign ? `fc_${shortHash(itemId)}` : normalizeResponsesPart(itemId);
16
+ if (!item.startsWith("fc_"))
17
+ item = normalizeResponsesPart(`fc_${item}`);
18
+ if (!item.startsWith("fc_") || item.length === 3)
19
+ item = `fc_${shortHash(itemId)}`;
20
+ return `${call}|${item}`;
21
+ }
22
+ /** Identify Google API model families that carry explicit function-call IDs. */
23
+ export function requiresToolCallId(modelId) {
24
+ const match = modelId.toLowerCase().match(/^gemini(?:-live)?-(\d+)/);
25
+ return (modelId.startsWith("claude-") ||
26
+ modelId.startsWith("gpt-oss-") ||
27
+ (match !== null && Number.parseInt(match[1], 10) >= 3));
28
+ }
29
+ /** Create the provider's replay ID transformation; reuse one instance per request. */
30
+ export function createProviderToolCallIdNormalizer(model, allowedResponsesProviders) {
31
+ if (model.api === "mistral-conversations")
32
+ return createMistralNormalizer();
33
+ return (id, source = model) => {
34
+ switch (model.api) {
35
+ case "openai-responses":
36
+ case "openai-codex-responses":
37
+ case "azure-openai-responses": {
38
+ const composite = allowedResponsesProviders?.has(model.provider) ??
39
+ (["openai", "openai-codex", "opencode"].includes(model.provider) ||
40
+ (model.api === "azure-openai-responses" && model.provider === "azure-openai-responses"));
41
+ return composite
42
+ ? normalizeResponsesToolCallId(id, source, model)
43
+ : normalizeResponsesPart(id) || `call_${shortHash(id)}`;
44
+ }
45
+ case "openai-completions":
46
+ return normalizeChatId(id, model.provider);
47
+ case "anthropic-messages":
48
+ case "bedrock-converse-stream":
49
+ return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
50
+ case "google-generative-ai":
51
+ case "google-vertex":
52
+ return requiresToolCallId(model.id) ? id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64) : id;
53
+ default:
54
+ return id;
55
+ }
56
+ };
57
+ }
58
+ function normalizeChatId(id, provider) {
59
+ if (!id.includes("|"))
60
+ return provider === "openai" ? id.slice(0, 40) : id;
61
+ const separatorIndex = id.indexOf("|");
62
+ const callId = id.slice(0, separatorIndex).replace(/[^a-zA-Z0-9_-]/g, "_");
63
+ const itemId = id.slice(separatorIndex + 1).replace(/[^a-zA-Z0-9_-]/g, "_");
64
+ const combined = itemId.length > 0 ? `${callId}_${itemId}` : callId;
65
+ if (combined.length <= 40)
66
+ return combined;
67
+ const hash = shortHash(id).slice(0, 8);
68
+ return `${callId.slice(0, Math.max(1, 40 - hash.length - 1))}_${hash}`;
69
+ }
70
+ function createMistralNormalizer() {
71
+ const ids = new Map();
72
+ const owners = new Map();
73
+ return (id) => {
74
+ const existing = ids.get(id);
75
+ if (existing)
76
+ return existing;
77
+ const normalized = id.replace(/[^a-zA-Z0-9]/g, "");
78
+ const seed = normalized || id;
79
+ let attempt = 0;
80
+ while (true) {
81
+ const candidate = attempt === 0 && normalized.length === 9
82
+ ? normalized
83
+ : shortHash(attempt === 0 ? seed : `${seed}:${attempt}`)
84
+ .replace(/[^a-zA-Z0-9]/g, "")
85
+ .slice(0, 9);
86
+ const owner = owners.get(candidate);
87
+ if (!owner || owner === id) {
88
+ ids.set(id, candidate);
89
+ owners.set(candidate, id);
90
+ return candidate;
91
+ }
92
+ attempt++;
93
+ }
94
+ };
95
+ }
96
+ //# sourceMappingURL=tool-call-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-call-id.js","sourceRoot":"","sources":["../../src/utils/tool-call-id.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Api, AssistantMessage, Model } from \"../types.js\";\nimport { shortHash } from \"./hash.js\";\n\ntype TargetModel = Pick<Model<Api>, \"provider\" | \"api\" | \"id\">;\ntype SourceModel = Pick<AssistantMessage, \"provider\" | \"api\">;\n\nfunction normalizeResponsesPart(part: string): string {\n\treturn part\n\t\t.replace(/[^a-zA-Z0-9_-]/g, \"_\")\n\t\t.slice(0, 64)\n\t\t.replace(/_+$/, \"\");\n}\n\n/** Normalize Responses correlation and item IDs identically for execution and replay. */\nexport function normalizeResponsesToolCallId(id: string, source: SourceModel, target: SourceModel): string {\n\tconst [callId, itemId] = id.split(\"|\");\n\tconst call = normalizeResponsesPart(callId) || `call_${shortHash(callId)}`;\n\tif (itemId === undefined) return call;\n\tconst foreign = source.provider !== target.provider || source.api !== target.api;\n\tlet item = foreign ? `fc_${shortHash(itemId)}` : normalizeResponsesPart(itemId);\n\tif (!item.startsWith(\"fc_\")) item = normalizeResponsesPart(`fc_${item}`);\n\tif (!item.startsWith(\"fc_\") || item.length === 3) item = `fc_${shortHash(itemId)}`;\n\treturn `${call}|${item}`;\n}\n\n/** Identify Google API model families that carry explicit function-call IDs. */\nexport function requiresToolCallId(modelId: string): boolean {\n\tconst match = modelId.toLowerCase().match(/^gemini(?:-live)?-(\\d+)/);\n\treturn (\n\t\tmodelId.startsWith(\"claude-\") ||\n\t\tmodelId.startsWith(\"gpt-oss-\") ||\n\t\t(match !== null && Number.parseInt(match[1], 10) >= 3)\n\t);\n}\n\n/** Create the provider's replay ID transformation; reuse one instance per request. */\nexport function createProviderToolCallIdNormalizer(\n\tmodel: TargetModel,\n\tallowedResponsesProviders?: ReadonlySet<string>,\n): (id: string, source?: SourceModel) => string {\n\tif (model.api === \"mistral-conversations\") return createMistralNormalizer();\n\treturn (id: string, source: SourceModel = model): string => {\n\t\tswitch (model.api) {\n\t\t\tcase \"openai-responses\":\n\t\t\tcase \"openai-codex-responses\":\n\t\t\tcase \"azure-openai-responses\": {\n\t\t\t\tconst composite =\n\t\t\t\t\tallowedResponsesProviders?.has(model.provider) ??\n\t\t\t\t\t([\"openai\", \"openai-codex\", \"opencode\"].includes(model.provider) ||\n\t\t\t\t\t\t(model.api === \"azure-openai-responses\" && model.provider === \"azure-openai-responses\"));\n\t\t\t\treturn composite\n\t\t\t\t\t? normalizeResponsesToolCallId(id, source, model)\n\t\t\t\t\t: normalizeResponsesPart(id) || `call_${shortHash(id)}`;\n\t\t\t}\n\t\t\tcase \"openai-completions\":\n\t\t\t\treturn normalizeChatId(id, model.provider);\n\t\t\tcase \"anthropic-messages\":\n\t\t\tcase \"bedrock-converse-stream\":\n\t\t\t\treturn id.replace(/[^a-zA-Z0-9_-]/g, \"_\").slice(0, 64);\n\t\t\tcase \"google-generative-ai\":\n\t\t\tcase \"google-vertex\":\n\t\t\t\treturn requiresToolCallId(model.id) ? id.replace(/[^a-zA-Z0-9_-]/g, \"_\").slice(0, 64) : id;\n\t\t\tdefault:\n\t\t\t\treturn id;\n\t\t}\n\t};\n}\n\nfunction normalizeChatId(id: string, provider: string): string {\n\tif (!id.includes(\"|\")) return provider === \"openai\" ? id.slice(0, 40) : id;\n\tconst separatorIndex = id.indexOf(\"|\");\n\tconst callId = id.slice(0, separatorIndex).replace(/[^a-zA-Z0-9_-]/g, \"_\");\n\tconst itemId = id.slice(separatorIndex + 1).replace(/[^a-zA-Z0-9_-]/g, \"_\");\n\tconst combined = itemId.length > 0 ? `${callId}_${itemId}` : callId;\n\tif (combined.length <= 40) return combined;\n\tconst hash = shortHash(id).slice(0, 8);\n\treturn `${callId.slice(0, Math.max(1, 40 - hash.length - 1))}_${hash}`;\n}\n\nfunction createMistralNormalizer(): (id: string) => string {\n\tconst ids = new Map<string, string>();\n\tconst owners = new Map<string, string>();\n\treturn (id) => {\n\t\tconst existing = ids.get(id);\n\t\tif (existing) return existing;\n\t\tconst normalized = id.replace(/[^a-zA-Z0-9]/g, \"\");\n\t\tconst seed = normalized || id;\n\t\tlet attempt = 0;\n\t\twhile (true) {\n\t\t\tconst candidate =\n\t\t\t\tattempt === 0 && normalized.length === 9\n\t\t\t\t\t? normalized\n\t\t\t\t\t: shortHash(attempt === 0 ? seed : `${seed}:${attempt}`)\n\t\t\t\t\t\t\t.replace(/[^a-zA-Z0-9]/g, \"\")\n\t\t\t\t\t\t\t.slice(0, 9);\n\t\t\tconst owner = owners.get(candidate);\n\t\t\tif (!owner || owner === id) {\n\t\t\t\tids.set(id, candidate);\n\t\t\t\towners.set(candidate, id);\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t\tattempt++;\n\t\t}\n\t};\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AA0QlD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,QAAQ,GAAG,GAAG,CAMvE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,GAAG,GAAG,CAgCzE","sourcesContent":["import { Compile } from \"typebox/compile\";\nimport type { TLocalizedValidationError } from \"typebox/error\";\nimport { Value } from \"typebox/value\";\nimport type { Tool, ToolCall } from \"../types.js\";\n\nconst validatorCache = new WeakMap<object, ReturnType<typeof Compile>>();\nconst TYPEBOX_KIND = Symbol.for(\"TypeBox.Kind\");\n\ninterface JsonSchemaObject {\n\ttype?: string | string[];\n\tproperties?: Record<string, JsonSchemaObject>;\n\titems?: JsonSchemaObject | JsonSchemaObject[];\n\tadditionalProperties?: boolean | JsonSchemaObject;\n\tallOf?: JsonSchemaObject[];\n\tanyOf?: JsonSchemaObject[];\n\toneOf?: JsonSchemaObject[];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction isJsonSchemaObject(value: unknown): value is JsonSchemaObject {\n\treturn isRecord(value);\n}\n\nfunction hasTypeBoxMetadata(schema: unknown): boolean {\n\treturn isRecord(schema) && Object.getOwnPropertySymbols(schema).includes(TYPEBOX_KIND);\n}\n\nfunction getSchemaTypes(schema: JsonSchemaObject): string[] {\n\tif (typeof schema.type === \"string\") {\n\t\treturn [schema.type];\n\t}\n\tif (Array.isArray(schema.type)) {\n\t\treturn schema.type.filter((type): type is string => typeof type === \"string\");\n\t}\n\treturn [];\n}\n\nfunction matchesJsonType(value: unknown, type: string): boolean {\n\tswitch (type) {\n\t\tcase \"number\":\n\t\t\treturn typeof value === \"number\";\n\t\tcase \"integer\":\n\t\t\treturn typeof value === \"number\" && Number.isInteger(value);\n\t\tcase \"boolean\":\n\t\t\treturn typeof value === \"boolean\";\n\t\tcase \"string\":\n\t\t\treturn typeof value === \"string\";\n\t\tcase \"null\":\n\t\t\treturn value === null;\n\t\tcase \"array\":\n\t\t\treturn Array.isArray(value);\n\t\tcase \"object\":\n\t\t\treturn isRecord(value) && !Array.isArray(value);\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nfunction isValidatorSchema(value: unknown): value is Tool[\"parameters\"] {\n\treturn isRecord(value);\n}\n\nfunction getSubSchemaValidator(schema: JsonSchemaObject): ReturnType<typeof Compile> | undefined {\n\tif (!isValidatorSchema(schema)) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\treturn getValidator(schema);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction coercePrimitiveByType(value: unknown, type: string): unknown {\n\tswitch (type) {\n\t\tcase \"number\": {\n\t\t\tif (value === null) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (typeof value === \"string\" && value.trim() !== \"\") {\n\t\t\t\tconst parsed = Number(value);\n\t\t\t\tif (Number.isFinite(parsed)) {\n\t\t\t\t\treturn parsed;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (typeof value === \"boolean\") {\n\t\t\t\treturn value ? 1 : 0;\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t\tcase \"integer\": {\n\t\t\tif (value === null) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (typeof value === \"string\" && value.trim() !== \"\") {\n\t\t\t\tconst parsed = Number(value);\n\t\t\t\tif (Number.isInteger(parsed)) {\n\t\t\t\t\treturn parsed;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (typeof value === \"boolean\") {\n\t\t\t\treturn value ? 1 : 0;\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t\tcase \"boolean\": {\n\t\t\tif (value === null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (typeof value === \"string\") {\n\t\t\t\tif (value === \"true\") {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tif (value === \"false\") {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (typeof value === \"number\") {\n\t\t\t\tif (value === 1) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tif (value === 0) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t\tcase \"string\": {\n\t\t\tif (value === null) {\n\t\t\t\treturn \"\";\n\t\t\t}\n\t\t\tif (typeof value === \"number\" || typeof value === \"boolean\") {\n\t\t\t\treturn String(value);\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t\tcase \"null\": {\n\t\t\tif (value === \"\" || value === 0 || value === false) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t\tdefault:\n\t\t\treturn value;\n\t}\n}\n\nfunction applySchemaObjectCoercion(value: Record<string, unknown>, schema: JsonSchemaObject): void {\n\tconst properties = schema.properties;\n\tconst definedKeys = new Set<string>(properties ? Object.keys(properties) : []);\n\n\tif (properties) {\n\t\tfor (const [key, propertySchema] of Object.entries(properties)) {\n\t\t\tif (!(key in value)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvalue[key] = coerceWithJsonSchema(value[key], propertySchema);\n\t\t}\n\t}\n\n\tif (schema.additionalProperties && isJsonSchemaObject(schema.additionalProperties)) {\n\t\tfor (const [key, propertyValue] of Object.entries(value)) {\n\t\t\tif (definedKeys.has(key)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvalue[key] = coerceWithJsonSchema(propertyValue, schema.additionalProperties);\n\t\t}\n\t}\n}\n\nfunction applySchemaArrayCoercion(value: unknown[], schema: JsonSchemaObject): void {\n\tif (Array.isArray(schema.items)) {\n\t\tfor (let index = 0; index < value.length; index++) {\n\t\t\tconst itemSchema = schema.items[index];\n\t\t\tif (!itemSchema) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvalue[index] = coerceWithJsonSchema(value[index], itemSchema);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (isJsonSchemaObject(schema.items)) {\n\t\tfor (let index = 0; index < value.length; index++) {\n\t\t\tvalue[index] = coerceWithJsonSchema(value[index], schema.items);\n\t\t}\n\t}\n}\n\nfunction coerceWithUnionSchema(value: unknown, schemas: JsonSchemaObject[]): unknown {\n\tfor (const schema of schemas) {\n\t\tconst candidate = structuredClone(value);\n\t\tconst coerced = coerceWithJsonSchema(candidate, schema);\n\t\tconst validator = getSubSchemaValidator(schema);\n\t\tif (validator?.Check(coerced)) {\n\t\t\treturn coerced;\n\t\t}\n\t}\n\treturn value;\n}\n\nfunction coerceWithJsonSchema(value: unknown, schema: JsonSchemaObject): unknown {\n\tlet nextValue = value;\n\n\tif (Array.isArray(schema.allOf)) {\n\t\tfor (const nested of schema.allOf) {\n\t\t\tnextValue = coerceWithJsonSchema(nextValue, nested);\n\t\t}\n\t}\n\n\tif (Array.isArray(schema.anyOf)) {\n\t\tnextValue = coerceWithUnionSchema(nextValue, schema.anyOf);\n\t}\n\n\tif (Array.isArray(schema.oneOf)) {\n\t\tnextValue = coerceWithUnionSchema(nextValue, schema.oneOf);\n\t}\n\n\tconst schemaTypes = getSchemaTypes(schema);\n\tconst matchesUnionMember =\n\t\tschemaTypes.length > 1 && schemaTypes.some((schemaType) => matchesJsonType(nextValue, schemaType));\n\tif (schemaTypes.length > 0 && !matchesUnionMember) {\n\t\tfor (const schemaType of schemaTypes) {\n\t\t\tconst candidate = coercePrimitiveByType(nextValue, schemaType);\n\t\t\tif (candidate !== nextValue) {\n\t\t\t\tnextValue = candidate;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (schemaTypes.includes(\"object\") && isRecord(nextValue) && !Array.isArray(nextValue)) {\n\t\tapplySchemaObjectCoercion(nextValue, schema);\n\t}\n\n\tif (schemaTypes.includes(\"array\") && Array.isArray(nextValue)) {\n\t\tapplySchemaArrayCoercion(nextValue, schema);\n\t}\n\n\treturn nextValue;\n}\n\nfunction getValidator(schema: Tool[\"parameters\"]): ReturnType<typeof Compile> {\n\tconst key = schema as object;\n\tconst cached = validatorCache.get(key);\n\tif (cached) {\n\t\treturn cached;\n\t}\n\tconst validator = Compile(schema);\n\tvalidatorCache.set(key, validator);\n\treturn validator;\n}\n\nfunction formatValidationPath(error: TLocalizedValidationError): string {\n\tif (error.keyword === \"required\") {\n\t\tconst requiredProperties = (error.params as { requiredProperties?: string[] }).requiredProperties;\n\t\tconst requiredProperty = requiredProperties?.[0];\n\t\tif (requiredProperty) {\n\t\t\tconst basePath = error.instancePath.replace(/^\\//, \"\").replace(/\\//g, \".\");\n\t\t\treturn basePath ? `${basePath}.${requiredProperty}` : requiredProperty;\n\t\t}\n\t}\n\tconst path = error.instancePath.replace(/^\\//, \"\").replace(/\\//g, \".\");\n\treturn path || \"root\";\n}\n\n/**\n * Finds a tool by name and validates the tool call arguments against its TypeBox schema\n * @param tools Array of tool definitions\n * @param toolCall The tool call from the LLM\n * @returns The validated arguments\n * @throws Error if tool is not found or validation fails\n */\nexport function validateToolCall(tools: Tool[], toolCall: ToolCall): any {\n\tconst tool = tools.find((t) => t.name === toolCall.name);\n\tif (!tool) {\n\t\tthrow new Error(`Tool \"${toolCall.name}\" not found`);\n\t}\n\treturn validateToolArguments(tool, toolCall);\n}\n\n/**\n * Validates tool call arguments against the tool's TypeBox schema\n * @param tool The tool definition with TypeBox schema\n * @param toolCall The tool call from the LLM\n * @returns The validated (and potentially coerced) arguments\n * @throws Error with formatted message if validation fails\n */\nexport function validateToolArguments(tool: Tool, toolCall: ToolCall): any {\n\tconst args = structuredClone(toolCall.arguments);\n\tValue.Convert(tool.parameters, args);\n\n\tconst validator = getValidator(tool.parameters);\n\tif (!hasTypeBoxMetadata(tool.parameters) && isJsonSchemaObject(tool.parameters)) {\n\t\tconst coerced = coerceWithJsonSchema(args, tool.parameters);\n\t\tif (coerced !== args) {\n\t\t\tif (isRecord(args) && isRecord(coerced)) {\n\t\t\t\tfor (const key of Object.keys(args)) {\n\t\t\t\t\tdelete args[key];\n\t\t\t\t}\n\t\t\t\tObject.assign(args, coerced);\n\t\t\t} else {\n\t\t\t\treturn validator.Check(coerced) ? coerced : args;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (validator.Check(args)) {\n\t\treturn args;\n\t}\n\n\tconst errors =\n\t\tvalidator\n\t\t\t.Errors(args)\n\t\t\t.map((error) => ` - ${formatValidationPath(error)}: ${error.message}`)\n\t\t\t.join(\"\\n\") || \"Unknown validation error\";\n\n\tconst errorMessage = `Validation failed for tool \"${toolCall.name}\":\\n${errors}\\n\\nReceived arguments:\\n${JSON.stringify(toolCall.arguments, null, 2)}`;\n\n\tthrow new Error(errorMessage);\n}\n"]}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAiRlD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,QAAQ,GAAG,GAAG,CAMvE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,GAAG,GAAG,CAmCzE","sourcesContent":["import { Compile } from \"typebox/compile\";\nimport type { TLocalizedValidationError } from \"typebox/error\";\nimport { Value } from \"typebox/value\";\nimport type { Tool, ToolCall } from \"../types.js\";\n\nconst validatorCache = new WeakMap<object, ReturnType<typeof Compile>>();\nconst TYPEBOX_KIND = Symbol.for(\"TypeBox.Kind\");\n\ninterface JsonSchemaObject {\n\ttype?: string | string[];\n\tproperties?: Record<string, JsonSchemaObject>;\n\titems?: JsonSchemaObject | JsonSchemaObject[];\n\tadditionalProperties?: boolean | JsonSchemaObject;\n\tallOf?: JsonSchemaObject[];\n\tanyOf?: JsonSchemaObject[];\n\toneOf?: JsonSchemaObject[];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction isJsonSchemaObject(value: unknown): value is JsonSchemaObject {\n\treturn isRecord(value);\n}\n\nfunction hasTypeBoxMetadata(schema: unknown): boolean {\n\treturn isRecord(schema) && Object.getOwnPropertySymbols(schema).includes(TYPEBOX_KIND);\n}\n\nfunction getSchemaTypes(schema: JsonSchemaObject): string[] {\n\tif (typeof schema.type === \"string\") {\n\t\treturn [schema.type];\n\t}\n\tif (Array.isArray(schema.type)) {\n\t\treturn schema.type.filter((type): type is string => typeof type === \"string\");\n\t}\n\treturn [];\n}\n\nfunction matchesJsonType(value: unknown, type: string): boolean {\n\tswitch (type) {\n\t\tcase \"number\":\n\t\t\treturn typeof value === \"number\";\n\t\tcase \"integer\":\n\t\t\treturn typeof value === \"number\" && Number.isInteger(value);\n\t\tcase \"boolean\":\n\t\t\treturn typeof value === \"boolean\";\n\t\tcase \"string\":\n\t\t\treturn typeof value === \"string\";\n\t\tcase \"null\":\n\t\t\treturn value === null;\n\t\tcase \"array\":\n\t\t\treturn Array.isArray(value);\n\t\tcase \"object\":\n\t\t\treturn isRecord(value) && !Array.isArray(value);\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nfunction isValidatorSchema(value: unknown): value is Tool[\"parameters\"] {\n\treturn isRecord(value);\n}\n\nfunction getSubSchemaValidator(schema: JsonSchemaObject): ReturnType<typeof Compile> | undefined {\n\tif (!isValidatorSchema(schema)) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\treturn getValidator(schema);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction coercePrimitiveByType(value: unknown, type: string): unknown {\n\tswitch (type) {\n\t\tcase \"number\": {\n\t\t\tif (value === null) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (typeof value === \"string\" && value.trim() !== \"\") {\n\t\t\t\tconst parsed = Number(value);\n\t\t\t\tif (Number.isFinite(parsed)) {\n\t\t\t\t\treturn parsed;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (typeof value === \"boolean\") {\n\t\t\t\treturn value ? 1 : 0;\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t\tcase \"integer\": {\n\t\t\tif (value === null) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (typeof value === \"string\" && value.trim() !== \"\") {\n\t\t\t\tconst parsed = Number(value);\n\t\t\t\tif (Number.isInteger(parsed)) {\n\t\t\t\t\treturn parsed;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (typeof value === \"boolean\") {\n\t\t\t\treturn value ? 1 : 0;\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t\tcase \"boolean\": {\n\t\t\tif (value === null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (typeof value === \"string\") {\n\t\t\t\tif (value === \"true\") {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tif (value === \"false\") {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (typeof value === \"number\") {\n\t\t\t\tif (value === 1) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tif (value === 0) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t\tcase \"string\": {\n\t\t\tif (value === null) {\n\t\t\t\treturn \"\";\n\t\t\t}\n\t\t\tif (typeof value === \"number\" || typeof value === \"boolean\") {\n\t\t\t\treturn String(value);\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t\tcase \"null\": {\n\t\t\tif (value === \"\" || value === 0 || value === false) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t\tdefault:\n\t\t\treturn value;\n\t}\n}\n\nfunction applySchemaObjectCoercion(value: Record<string, unknown>, schema: JsonSchemaObject): void {\n\tconst properties = schema.properties;\n\tconst definedKeys = new Set<string>(properties ? Object.keys(properties) : []);\n\n\tif (properties) {\n\t\tfor (const [key, propertySchema] of Object.entries(properties)) {\n\t\t\tif (!(key in value)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvalue[key] = coerceWithJsonSchema(value[key], propertySchema);\n\t\t}\n\t}\n\n\tif (schema.additionalProperties && isJsonSchemaObject(schema.additionalProperties)) {\n\t\tfor (const [key, propertyValue] of Object.entries(value)) {\n\t\t\tif (definedKeys.has(key)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvalue[key] = coerceWithJsonSchema(propertyValue, schema.additionalProperties);\n\t\t}\n\t}\n}\n\nfunction applySchemaArrayCoercion(value: unknown[], schema: JsonSchemaObject): void {\n\tif (Array.isArray(schema.items)) {\n\t\tfor (let index = 0; index < value.length; index++) {\n\t\t\tconst itemSchema = schema.items[index];\n\t\t\tif (!itemSchema) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvalue[index] = coerceWithJsonSchema(value[index], itemSchema);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (isJsonSchemaObject(schema.items)) {\n\t\tfor (let index = 0; index < value.length; index++) {\n\t\t\tvalue[index] = coerceWithJsonSchema(value[index], schema.items);\n\t\t}\n\t}\n}\n\nfunction coerceWithUnionSchema(value: unknown, schemas: JsonSchemaObject[]): unknown {\n\tfor (const schema of schemas) {\n\t\tconst validator = getSubSchemaValidator(schema);\n\t\tif (validator?.Check(value)) {\n\t\t\treturn value;\n\t\t}\n\t}\n\n\tfor (const schema of schemas) {\n\t\tconst candidate = structuredClone(value);\n\t\tconst coerced = coerceWithJsonSchema(candidate, schema);\n\t\tconst validator = getSubSchemaValidator(schema);\n\t\tif (validator?.Check(coerced)) {\n\t\t\treturn coerced;\n\t\t}\n\t}\n\treturn value;\n}\n\nfunction coerceWithJsonSchema(value: unknown, schema: JsonSchemaObject): unknown {\n\tlet nextValue = value;\n\n\tif (Array.isArray(schema.allOf)) {\n\t\tfor (const nested of schema.allOf) {\n\t\t\tnextValue = coerceWithJsonSchema(nextValue, nested);\n\t\t}\n\t}\n\n\tif (Array.isArray(schema.anyOf)) {\n\t\tnextValue = coerceWithUnionSchema(nextValue, schema.anyOf);\n\t}\n\n\tif (Array.isArray(schema.oneOf)) {\n\t\tnextValue = coerceWithUnionSchema(nextValue, schema.oneOf);\n\t}\n\n\tconst schemaTypes = getSchemaTypes(schema);\n\tconst matchesUnionMember =\n\t\tschemaTypes.length > 1 && schemaTypes.some((schemaType) => matchesJsonType(nextValue, schemaType));\n\tif (schemaTypes.length > 0 && !matchesUnionMember) {\n\t\tfor (const schemaType of schemaTypes) {\n\t\t\tconst candidate = coercePrimitiveByType(nextValue, schemaType);\n\t\t\tif (candidate !== nextValue) {\n\t\t\t\tnextValue = candidate;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (schemaTypes.includes(\"object\") && isRecord(nextValue) && !Array.isArray(nextValue)) {\n\t\tapplySchemaObjectCoercion(nextValue, schema);\n\t}\n\n\tif (schemaTypes.includes(\"array\") && Array.isArray(nextValue)) {\n\t\tapplySchemaArrayCoercion(nextValue, schema);\n\t}\n\n\treturn nextValue;\n}\n\nfunction getValidator(schema: Tool[\"parameters\"]): ReturnType<typeof Compile> {\n\tconst key = schema as object;\n\tconst cached = validatorCache.get(key);\n\tif (cached) {\n\t\treturn cached;\n\t}\n\tconst validator = Compile(schema);\n\tvalidatorCache.set(key, validator);\n\treturn validator;\n}\n\nfunction formatValidationPath(error: TLocalizedValidationError): string {\n\tif (error.keyword === \"required\") {\n\t\tconst requiredProperties = (error.params as { requiredProperties?: string[] }).requiredProperties;\n\t\tconst requiredProperty = requiredProperties?.[0];\n\t\tif (requiredProperty) {\n\t\t\tconst basePath = error.instancePath.replace(/^\\//, \"\").replace(/\\//g, \".\");\n\t\t\treturn basePath ? `${basePath}.${requiredProperty}` : requiredProperty;\n\t\t}\n\t}\n\tconst path = error.instancePath.replace(/^\\//, \"\").replace(/\\//g, \".\");\n\treturn path || \"root\";\n}\n\n/**\n * Finds a tool by name and validates the tool call arguments against its TypeBox schema\n * @param tools Array of tool definitions\n * @param toolCall The tool call from the LLM\n * @returns The validated arguments\n * @throws Error if tool is not found or validation fails\n */\nexport function validateToolCall(tools: Tool[], toolCall: ToolCall): any {\n\tconst tool = tools.find((t) => t.name === toolCall.name);\n\tif (!tool) {\n\t\tthrow new Error(`Tool \"${toolCall.name}\" not found`);\n\t}\n\treturn validateToolArguments(tool, toolCall);\n}\n\n/**\n * Validates tool call arguments against the tool's TypeBox schema\n * @param tool The tool definition with TypeBox schema\n * @param toolCall The tool call from the LLM\n * @returns The validated (and potentially coerced) arguments\n * @throws Error with formatted message if validation fails\n */\nexport function validateToolArguments(tool: Tool, toolCall: ToolCall): any {\n\tif (toolCall.argumentsError !== undefined) {\n\t\tthrow new Error(toolCall.argumentsError);\n\t}\n\tconst args = structuredClone(toolCall.arguments);\n\tValue.Convert(tool.parameters, args);\n\n\tconst validator = getValidator(tool.parameters);\n\tif (!hasTypeBoxMetadata(tool.parameters) && isJsonSchemaObject(tool.parameters)) {\n\t\tconst coerced = coerceWithJsonSchema(args, tool.parameters);\n\t\tif (coerced !== args) {\n\t\t\tif (isRecord(args) && isRecord(coerced)) {\n\t\t\t\tfor (const key of Object.keys(args)) {\n\t\t\t\t\tdelete args[key];\n\t\t\t\t}\n\t\t\t\tObject.assign(args, coerced);\n\t\t\t} else {\n\t\t\t\treturn validator.Check(coerced) ? coerced : args;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (validator.Check(args)) {\n\t\treturn args;\n\t}\n\n\tconst errors =\n\t\tvalidator\n\t\t\t.Errors(args)\n\t\t\t.map((error) => ` - ${formatValidationPath(error)}: ${error.message}`)\n\t\t\t.join(\"\\n\") || \"Unknown validation error\";\n\n\tconst errorMessage = `Validation failed for tool \"${toolCall.name}\":\\n${errors}\\n\\nReceived arguments:\\n${JSON.stringify(toolCall.arguments, null, 2)}`;\n\n\tthrow new Error(errorMessage);\n}\n"]}