@linxiraos/pi-ai 1.0.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 (639) hide show
  1. package/CHANGELOG.md +5066 -0
  2. package/README.md +1195 -0
  3. package/dist/types/api-registry.d.ts +30 -0
  4. package/dist/types/auth/sqlite-credential-store.d.ts +117 -0
  5. package/dist/types/auth-broker/client.d.ts +93 -0
  6. package/dist/types/auth-broker/discover.d.ts +39 -0
  7. package/dist/types/auth-broker/index.d.ts +7 -0
  8. package/dist/types/auth-broker/refresher.d.ts +25 -0
  9. package/dist/types/auth-broker/remote-store.d.ts +136 -0
  10. package/dist/types/auth-broker/server.d.ts +48 -0
  11. package/dist/types/auth-broker/snapshot-cache.d.ts +17 -0
  12. package/dist/types/auth-broker/types.d.ts +152 -0
  13. package/dist/types/auth-broker/wire-schema-resource.d.ts +53 -0
  14. package/dist/types/auth-broker/wire-schemas.d.ts +5 -0
  15. package/dist/types/auth-gateway/http.d.ts +56 -0
  16. package/dist/types/auth-gateway/index.d.ts +3 -0
  17. package/dist/types/auth-gateway/server.d.ts +36 -0
  18. package/dist/types/auth-gateway/types.d.ts +127 -0
  19. package/dist/types/auth-retry.d.ts +150 -0
  20. package/dist/types/auth-storage.d.ts +1258 -0
  21. package/dist/types/dialect/anthropic.d.ts +15 -0
  22. package/dist/types/dialect/catalog.d.ts +3 -0
  23. package/dist/types/dialect/coercion.d.ts +23 -0
  24. package/dist/types/dialect/deepseek.d.ts +14 -0
  25. package/dist/types/dialect/demotion.d.ts +30 -0
  26. package/dist/types/dialect/examples.d.ts +18 -0
  27. package/dist/types/dialect/factory.d.ts +3 -0
  28. package/dist/types/dialect/fenced-thinking.d.ts +53 -0
  29. package/dist/types/dialect/gemini.d.ts +17 -0
  30. package/dist/types/dialect/gemma.d.ts +15 -0
  31. package/dist/types/dialect/glm.d.ts +9 -0
  32. package/dist/types/dialect/harmony.d.ts +8 -0
  33. package/dist/types/dialect/hermes.d.ts +9 -0
  34. package/dist/types/dialect/history.d.ts +3 -0
  35. package/dist/types/dialect/index.d.ts +11 -0
  36. package/dist/types/dialect/inventory.d.ts +9 -0
  37. package/dist/types/dialect/kimi.d.ts +14 -0
  38. package/dist/types/dialect/minimax.d.ts +3 -0
  39. package/dist/types/dialect/owned-stream.d.ts +4 -0
  40. package/dist/types/dialect/qwen3.d.ts +9 -0
  41. package/dist/types/dialect/rendering.d.ts +54 -0
  42. package/dist/types/dialect/thinking.d.ts +6 -0
  43. package/dist/types/dialect/types.d.ts +68 -0
  44. package/dist/types/dialect/xml.d.ts +9 -0
  45. package/dist/types/error/abort.d.ts +14 -0
  46. package/dist/types/error/auth-classify.d.ts +20 -0
  47. package/dist/types/error/auth.d.ts +27 -0
  48. package/dist/types/error/aws.d.ts +27 -0
  49. package/dist/types/error/classes.d.ts +106 -0
  50. package/dist/types/error/finalize.d.ts +39 -0
  51. package/dist/types/error/flags.d.ts +94 -0
  52. package/dist/types/error/format.d.ts +20 -0
  53. package/dist/types/error/gateway.d.ts +20 -0
  54. package/dist/types/error/index.d.ts +14 -0
  55. package/dist/types/error/oauth.d.ts +43 -0
  56. package/dist/types/error/provider.d.ts +42 -0
  57. package/dist/types/error/rate-limit.d.ts +80 -0
  58. package/dist/types/error/retryable.d.ts +27 -0
  59. package/dist/types/error/validation.d.ts +32 -0
  60. package/dist/types/index.d.ts +51 -0
  61. package/dist/types/provider-details.d.ts +24 -0
  62. package/dist/types/providers/amazon-bedrock.d.ts +39 -0
  63. package/dist/types/providers/anthropic-client.d.ts +106 -0
  64. package/dist/types/providers/anthropic-messages-server-schema.d.ts +937 -0
  65. package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
  66. package/dist/types/providers/anthropic-wire.d.ts +345 -0
  67. package/dist/types/providers/anthropic.d.ts +261 -0
  68. package/dist/types/providers/aws-credentials.d.ts +48 -0
  69. package/dist/types/providers/aws-eventstream.d.ts +39 -0
  70. package/dist/types/providers/aws-sigv4.d.ts +55 -0
  71. package/dist/types/providers/azure-openai-responses.d.ts +16 -0
  72. package/dist/types/providers/bedrock-mantle.d.ts +13 -0
  73. package/dist/types/providers/claude-code-fingerprint.d.ts +19 -0
  74. package/dist/types/providers/cowork-fetch.d.ts +3 -0
  75. package/dist/types/providers/cursor/exec-modern.d.ts +98 -0
  76. package/dist/types/providers/cursor-pi-args.d.ts +105 -0
  77. package/dist/types/providers/cursor.d.ts +221 -0
  78. package/dist/types/providers/devin.d.ts +12 -0
  79. package/dist/types/providers/error-message.d.ts +25 -0
  80. package/dist/types/providers/github-copilot-headers.d.ts +40 -0
  81. package/dist/types/providers/gitlab-duo-workflow.d.ts +254 -0
  82. package/dist/types/providers/gitlab-duo.d.ts +27 -0
  83. package/dist/types/providers/google-auth.d.ts +24 -0
  84. package/dist/types/providers/google-gemini-cli.d.ts +120 -0
  85. package/dist/types/providers/google-shared.d.ts +203 -0
  86. package/dist/types/providers/google-types.d.ts +155 -0
  87. package/dist/types/providers/google-vertex.d.ts +7 -0
  88. package/dist/types/providers/google.d.ts +4 -0
  89. package/dist/types/providers/grammar.d.ts +1 -0
  90. package/dist/types/providers/kimi.d.ts +27 -0
  91. package/dist/types/providers/mock.d.ts +179 -0
  92. package/dist/types/providers/ollama.d.ts +8 -0
  93. package/dist/types/providers/openai-anthropic-shim.d.ts +35 -0
  94. package/dist/types/providers/openai-chat-server-schema.d.ts +1311 -0
  95. package/dist/types/providers/openai-chat-server.d.ts +16 -0
  96. package/dist/types/providers/openai-chat-wire.d.ts +669 -0
  97. package/dist/types/providers/openai-codex/request-transformer.d.ts +109 -0
  98. package/dist/types/providers/openai-codex/response-handler.d.ts +26 -0
  99. package/dist/types/providers/openai-codex-responses.d.ts +238 -0
  100. package/dist/types/providers/openai-completions.d.ts +48 -0
  101. package/dist/types/providers/openai-reasoning-fallback.d.ts +25 -0
  102. package/dist/types/providers/openai-responses-server-schema.d.ts +1314 -0
  103. package/dist/types/providers/openai-responses-server.d.ts +17 -0
  104. package/dist/types/providers/openai-responses-wire.d.ts +6099 -0
  105. package/dist/types/providers/openai-responses.d.ts +131 -0
  106. package/dist/types/providers/openai-shared.d.ts +624 -0
  107. package/dist/types/providers/pi-native-client.d.ts +13 -0
  108. package/dist/types/providers/pi-native-server.d.ts +69 -0
  109. package/dist/types/providers/register-builtins.d.ts +37 -0
  110. package/dist/types/providers/synthetic.d.ts +26 -0
  111. package/dist/types/providers/transform-messages.d.ts +32 -0
  112. package/dist/types/providers/vision-guard.d.ts +20 -0
  113. package/dist/types/registry/aiand.d.ts +7 -0
  114. package/dist/types/registry/aimlapi.d.ts +4 -0
  115. package/dist/types/registry/alibaba-coding-plan.d.ts +8 -0
  116. package/dist/types/registry/alibaba-token-plan.d.ts +18 -0
  117. package/dist/types/registry/amazon-bedrock.d.ts +5 -0
  118. package/dist/types/registry/anthropic.d.ts +10 -0
  119. package/dist/types/registry/api-key-login.d.ts +42 -0
  120. package/dist/types/registry/api-key-validation.d.ts +43 -0
  121. package/dist/types/registry/aws.d.ts +13 -0
  122. package/dist/types/registry/azure.d.ts +4 -0
  123. package/dist/types/registry/baseten.d.ts +7 -0
  124. package/dist/types/registry/bedrock-mantle.d.ts +22 -0
  125. package/dist/types/registry/cerebras.d.ts +7 -0
  126. package/dist/types/registry/cloudflare-ai-gateway.d.ts +13 -0
  127. package/dist/types/registry/coreweave.d.ts +7 -0
  128. package/dist/types/registry/cursor.d.ts +7 -0
  129. package/dist/types/registry/deepseek.d.ts +8 -0
  130. package/dist/types/registry/derived.d.ts +5 -0
  131. package/dist/types/registry/devin.d.ts +8 -0
  132. package/dist/types/registry/exa.d.ts +8 -0
  133. package/dist/types/registry/firepass.d.ts +16 -0
  134. package/dist/types/registry/fireworks.d.ts +7 -0
  135. package/dist/types/registry/github-copilot.d.ts +7 -0
  136. package/dist/types/registry/gitlab-duo-workflow.d.ts +10 -0
  137. package/dist/types/registry/gitlab-duo.d.ts +9 -0
  138. package/dist/types/registry/gmi-cloud.d.ts +7 -0
  139. package/dist/types/registry/google-antigravity.d.ts +9 -0
  140. package/dist/types/registry/google-gemini-cli.d.ts +9 -0
  141. package/dist/types/registry/google-vertex.d.ts +5 -0
  142. package/dist/types/registry/google.d.ts +4 -0
  143. package/dist/types/registry/groq.d.ts +4 -0
  144. package/dist/types/registry/huggingface.d.ts +7 -0
  145. package/dist/types/registry/index.d.ts +4 -0
  146. package/dist/types/registry/kagi.d.ts +14 -0
  147. package/dist/types/registry/kilo.d.ts +7 -0
  148. package/dist/types/registry/kimi-code.d.ts +7 -0
  149. package/dist/types/registry/litellm.d.ts +13 -0
  150. package/dist/types/registry/llama-cpp.d.ts +8 -0
  151. package/dist/types/registry/lm-studio.d.ts +8 -0
  152. package/dist/types/registry/meta.d.ts +7 -0
  153. package/dist/types/registry/minimax-code-cn.d.ts +6 -0
  154. package/dist/types/registry/minimax-code.d.ts +6 -0
  155. package/dist/types/registry/minimax.d.ts +4 -0
  156. package/dist/types/registry/mistral.d.ts +4 -0
  157. package/dist/types/registry/moonshot.d.ts +7 -0
  158. package/dist/types/registry/nanogpt.d.ts +7 -0
  159. package/dist/types/registry/novita.d.ts +6 -0
  160. package/dist/types/registry/nvidia.d.ts +7 -0
  161. package/dist/types/registry/oauth/anthropic-constants.d.ts +12 -0
  162. package/dist/types/registry/oauth/anthropic.d.ts +24 -0
  163. package/dist/types/registry/oauth/callback-server.d.ts +74 -0
  164. package/dist/types/registry/oauth/cursor.d.ts +16 -0
  165. package/dist/types/registry/oauth/device-code.d.ts +25 -0
  166. package/dist/types/registry/oauth/devin.d.ts +5 -0
  167. package/dist/types/registry/oauth/github-copilot.d.ts +30 -0
  168. package/dist/types/registry/oauth/gitlab-duo-workflow.d.ts +6 -0
  169. package/dist/types/registry/oauth/gitlab-duo.d.ts +3 -0
  170. package/dist/types/registry/oauth/google-antigravity.d.ts +11 -0
  171. package/dist/types/registry/oauth/google-gemini-cli.d.ts +22 -0
  172. package/dist/types/registry/oauth/google-oauth-shared.d.ts +56 -0
  173. package/dist/types/registry/oauth/index.d.ts +45 -0
  174. package/dist/types/registry/oauth/kimi.d.ts +21 -0
  175. package/dist/types/registry/oauth/minimax-code.d.ts +27 -0
  176. package/dist/types/registry/oauth/openai-codex.d.ts +33 -0
  177. package/dist/types/registry/oauth/opencode.d.ts +18 -0
  178. package/dist/types/registry/oauth/perplexity.d.ts +9 -0
  179. package/dist/types/registry/oauth/pkce.d.ts +8 -0
  180. package/dist/types/registry/oauth/types.d.ts +87 -0
  181. package/dist/types/registry/oauth/wafer.d.ts +1 -0
  182. package/dist/types/registry/oauth/xai-oauth.d.ts +46 -0
  183. package/dist/types/registry/oauth/xiaomi.d.ts +25 -0
  184. package/dist/types/registry/oauth/zai.d.ts +25 -0
  185. package/dist/types/registry/ollama-cloud.d.ts +7 -0
  186. package/dist/types/registry/ollama.d.ts +12 -0
  187. package/dist/types/registry/openai-codex-device.d.ts +8 -0
  188. package/dist/types/registry/openai-codex.d.ts +9 -0
  189. package/dist/types/registry/openai.d.ts +4 -0
  190. package/dist/types/registry/opencode-go.d.ts +6 -0
  191. package/dist/types/registry/opencode-zen.d.ts +6 -0
  192. package/dist/types/registry/openrouter.d.ts +13 -0
  193. package/dist/types/registry/parallel.d.ts +14 -0
  194. package/dist/types/registry/perplexity.d.ts +7 -0
  195. package/dist/types/registry/qianfan.d.ts +7 -0
  196. package/dist/types/registry/qwen-portal.d.ts +7 -0
  197. package/dist/types/registry/registry.d.ts +367 -0
  198. package/dist/types/registry/sakana.d.ts +7 -0
  199. package/dist/types/registry/siliconflow-cn.d.ts +7 -0
  200. package/dist/types/registry/siliconflow.d.ts +7 -0
  201. package/dist/types/registry/synthetic.d.ts +6 -0
  202. package/dist/types/registry/tavily.d.ts +14 -0
  203. package/dist/types/registry/together.d.ts +6 -0
  204. package/dist/types/registry/types.d.ts +75 -0
  205. package/dist/types/registry/umans.d.ts +7 -0
  206. package/dist/types/registry/venice.d.ts +13 -0
  207. package/dist/types/registry/vercel-ai-gateway.d.ts +7 -0
  208. package/dist/types/registry/vllm.d.ts +7 -0
  209. package/dist/types/registry/wafer-serverless.d.ts +6 -0
  210. package/dist/types/registry/xai-oauth.d.ts +7 -0
  211. package/dist/types/registry/xai.d.ts +7 -0
  212. package/dist/types/registry/xiaomi-token-plan-ams.d.ts +6 -0
  213. package/dist/types/registry/xiaomi-token-plan-cn.d.ts +6 -0
  214. package/dist/types/registry/xiaomi-token-plan-sgp.d.ts +6 -0
  215. package/dist/types/registry/xiaomi.d.ts +6 -0
  216. package/dist/types/registry/zai.d.ts +15 -0
  217. package/dist/types/registry/zenmux.d.ts +7 -0
  218. package/dist/types/registry/zhipu-coding-plan.d.ts +7 -0
  219. package/dist/types/stream.d.ts +46 -0
  220. package/dist/types/types.d.ts +1064 -0
  221. package/dist/types/usage/alibaba-token-plan.d.ts +3 -0
  222. package/dist/types/usage/claude.d.ts +4 -0
  223. package/dist/types/usage/cursor.d.ts +4 -0
  224. package/dist/types/usage/gemini.d.ts +2 -0
  225. package/dist/types/usage/github-copilot.d.ts +7 -0
  226. package/dist/types/usage/google-antigravity.d.ts +15 -0
  227. package/dist/types/usage/kimi.d.ts +2 -0
  228. package/dist/types/usage/minimax-code.d.ts +3 -0
  229. package/dist/types/usage/ollama.d.ts +5 -0
  230. package/dist/types/usage/openai-codex-base-url.d.ts +18 -0
  231. package/dist/types/usage/openai-codex-reset.d.ts +88 -0
  232. package/dist/types/usage/openai-codex.d.ts +10 -0
  233. package/dist/types/usage/opencode-go.d.ts +2 -0
  234. package/dist/types/usage/shared.d.ts +1 -0
  235. package/dist/types/usage/synthetic.d.ts +2 -0
  236. package/dist/types/usage/umans.d.ts +2 -0
  237. package/dist/types/usage/xai-oauth.d.ts +12 -0
  238. package/dist/types/usage/zai.d.ts +3 -0
  239. package/dist/types/usage.d.ts +527 -0
  240. package/dist/types/utils/abort.d.ts +25 -0
  241. package/dist/types/utils/anthropic-auth.d.ts +35 -0
  242. package/dist/types/utils/aws-profile.d.ts +17 -0
  243. package/dist/types/utils/block-symbols.d.ts +62 -0
  244. package/dist/types/utils/deterministic-id.d.ts +16 -0
  245. package/dist/types/utils/empty-completion-retry.d.ts +21 -0
  246. package/dist/types/utils/event-stream.d.ts +39 -0
  247. package/dist/types/utils/foundry.d.ts +1 -0
  248. package/dist/types/utils/google-validation.d.ts +2 -0
  249. package/dist/types/utils/harmony-leak.d.ts +135 -0
  250. package/dist/types/utils/http-inspector.d.ts +49 -0
  251. package/dist/types/utils/idle-iterator.d.ts +149 -0
  252. package/dist/types/utils/leaked-thinking-stream.d.ts +33 -0
  253. package/dist/types/utils/openai-http.d.ts +48 -0
  254. package/dist/types/utils/openrouter-headers.d.ts +1 -0
  255. package/dist/types/utils/parse-bind.d.ts +23 -0
  256. package/dist/types/utils/provider-response.d.ts +3 -0
  257. package/dist/types/utils/proxy.d.ts +39 -0
  258. package/dist/types/utils/request-debug.d.ts +29 -0
  259. package/dist/types/utils/retry-after.d.ts +4 -0
  260. package/dist/types/utils/retry.d.ts +14 -0
  261. package/dist/types/utils/schema/adapt.d.ts +24 -0
  262. package/dist/types/utils/schema/compatibility.d.ts +30 -0
  263. package/dist/types/utils/schema/dereference.d.ts +11 -0
  264. package/dist/types/utils/schema/draft.d.ts +10 -0
  265. package/dist/types/utils/schema/equality.d.ts +4 -0
  266. package/dist/types/utils/schema/fields.d.ts +54 -0
  267. package/dist/types/utils/schema/index.d.ts +14 -0
  268. package/dist/types/utils/schema/json-schema-validator.d.ts +20 -0
  269. package/dist/types/utils/schema/meta-validator.d.ts +2 -0
  270. package/dist/types/utils/schema/normalize.d.ts +153 -0
  271. package/dist/types/utils/schema/spill.d.ts +8 -0
  272. package/dist/types/utils/schema/stamps.d.ts +17 -0
  273. package/dist/types/utils/schema/strict-tool-validation.d.ts +16 -0
  274. package/dist/types/utils/schema/types.d.ts +4 -0
  275. package/dist/types/utils/schema/typescript.d.ts +24 -0
  276. package/dist/types/utils/schema/wire.d.ts +52 -0
  277. package/dist/types/utils/sdk-stream-timeout.d.ts +33 -0
  278. package/dist/types/utils/sse-debug.d.ts +5 -0
  279. package/dist/types/utils/stream-markup-healing.d.ts +87 -0
  280. package/dist/types/utils/thinking-loop.d.ts +102 -0
  281. package/dist/types/utils/tool-call-loop-guard.d.ts +26 -0
  282. package/dist/types/utils/tool-choice.d.ts +52 -0
  283. package/dist/types/utils/validation.d.ts +28 -0
  284. package/dist/types/utils.d.ts +57 -0
  285. package/package.json +138 -0
  286. package/src/api-registry.ts +109 -0
  287. package/src/auth/sqlite-credential-store.ts +2066 -0
  288. package/src/auth-broker/client.ts +471 -0
  289. package/src/auth-broker/discover.ts +310 -0
  290. package/src/auth-broker/index.ts +7 -0
  291. package/src/auth-broker/refresher.ts +117 -0
  292. package/src/auth-broker/remote-store.ts +1332 -0
  293. package/src/auth-broker/server.ts +898 -0
  294. package/src/auth-broker/snapshot-cache.ts +200 -0
  295. package/src/auth-broker/types.ts +193 -0
  296. package/src/auth-broker/wire-schema-resource.ts +487 -0
  297. package/src/auth-broker/wire-schemas.ts +43 -0
  298. package/src/auth-gateway/http.ts +227 -0
  299. package/src/auth-gateway/index.ts +3 -0
  300. package/src/auth-gateway/server.ts +836 -0
  301. package/src/auth-gateway/types.ts +153 -0
  302. package/src/auth-retry.ts +401 -0
  303. package/src/auth-storage.ts +6540 -0
  304. package/src/dialect/anthropic.md +31 -0
  305. package/src/dialect/anthropic.ts +608 -0
  306. package/src/dialect/catalog.ts +29 -0
  307. package/src/dialect/coercion.ts +136 -0
  308. package/src/dialect/deepseek.md +24 -0
  309. package/src/dialect/deepseek.ts +609 -0
  310. package/src/dialect/demotion.ts +40 -0
  311. package/src/dialect/examples.ts +71 -0
  312. package/src/dialect/factory.ts +34 -0
  313. package/src/dialect/fenced-thinking.ts +184 -0
  314. package/src/dialect/gemini.md +44 -0
  315. package/src/dialect/gemini.ts +583 -0
  316. package/src/dialect/gemma.md +33 -0
  317. package/src/dialect/gemma.ts +387 -0
  318. package/src/dialect/glm.md +32 -0
  319. package/src/dialect/glm.ts +579 -0
  320. package/src/dialect/harmony.md +31 -0
  321. package/src/dialect/harmony.ts +345 -0
  322. package/src/dialect/hermes.md +25 -0
  323. package/src/dialect/hermes.ts +206 -0
  324. package/src/dialect/history.ts +81 -0
  325. package/src/dialect/index.ts +15 -0
  326. package/src/dialect/inventory.ts +30 -0
  327. package/src/dialect/kimi.md +24 -0
  328. package/src/dialect/kimi.ts +340 -0
  329. package/src/dialect/minimax.md +31 -0
  330. package/src/dialect/minimax.ts +95 -0
  331. package/src/dialect/owned-stream.ts +481 -0
  332. package/src/dialect/prompt-template.md +12 -0
  333. package/src/dialect/qwen3.md +28 -0
  334. package/src/dialect/qwen3.ts +240 -0
  335. package/src/dialect/rendering.ts +304 -0
  336. package/src/dialect/thinking.ts +292 -0
  337. package/src/dialect/types.ts +56 -0
  338. package/src/dialect/xml.md +22 -0
  339. package/src/dialect/xml.ts +90 -0
  340. package/src/error/abort.ts +18 -0
  341. package/src/error/auth-classify.ts +47 -0
  342. package/src/error/auth.ts +48 -0
  343. package/src/error/aws.ts +35 -0
  344. package/src/error/classes.ts +281 -0
  345. package/src/error/finalize.ts +69 -0
  346. package/src/error/flags.ts +602 -0
  347. package/src/error/format.ts +45 -0
  348. package/src/error/gateway.ts +96 -0
  349. package/src/error/index.ts +14 -0
  350. package/src/error/oauth.ts +58 -0
  351. package/src/error/provider.ts +63 -0
  352. package/src/error/rate-limit.ts +303 -0
  353. package/src/error/retryable.ts +70 -0
  354. package/src/error/validation.ts +44 -0
  355. package/src/index.ts +51 -0
  356. package/src/provider-details.ts +90 -0
  357. package/src/providers/amazon-bedrock.ts +1064 -0
  358. package/src/providers/anthropic-client.ts +317 -0
  359. package/src/providers/anthropic-messages-server-schema.ts +252 -0
  360. package/src/providers/anthropic-messages-server.ts +818 -0
  361. package/src/providers/anthropic-wire.ts +359 -0
  362. package/src/providers/anthropic.ts +4539 -0
  363. package/src/providers/aws-credentials.ts +772 -0
  364. package/src/providers/aws-eventstream.ts +181 -0
  365. package/src/providers/aws-sigv4.ts +218 -0
  366. package/src/providers/azure-openai-responses.ts +438 -0
  367. package/src/providers/bedrock-mantle.ts +110 -0
  368. package/src/providers/claude-code-fingerprint.ts +20 -0
  369. package/src/providers/cowork-fetch.ts +201 -0
  370. package/src/providers/cursor/exec-modern.ts +496 -0
  371. package/src/providers/cursor/proto/agent.proto +4533 -0
  372. package/src/providers/cursor/proto/buf.gen.yaml +6 -0
  373. package/src/providers/cursor/proto/buf.yaml +17 -0
  374. package/src/providers/cursor-pi-args.ts +165 -0
  375. package/src/providers/cursor.ts +4689 -0
  376. package/src/providers/devin/proto/buf/validate/validate.proto +468 -0
  377. package/src/providers/devin/proto/buf.gen.yaml +33 -0
  378. package/src/providers/devin/proto/buf.yaml +17 -0
  379. package/src/providers/devin/proto/cel/expr/checked.proto +103 -0
  380. package/src/providers/devin/proto/cel/expr/eval.proto +38 -0
  381. package/src/providers/devin/proto/cel/expr/explain.proto +15 -0
  382. package/src/providers/devin/proto/cel/expr/syntax.proto +113 -0
  383. package/src/providers/devin/proto/cel/expr/value.proto +41 -0
  384. package/src/providers/devin/proto/connectext/grpc/status/v1/status.proto +11 -0
  385. package/src/providers/devin/proto/errorspb/errors.proto +56 -0
  386. package/src/providers/devin/proto/errorspb/hintdetail.proto +7 -0
  387. package/src/providers/devin/proto/errorspb/markers.proto +10 -0
  388. package/src/providers/devin/proto/errorspb/tags.proto +12 -0
  389. package/src/providers/devin/proto/errorspb/testing.proto +6 -0
  390. package/src/providers/devin/proto/exa/analytics_pb/analytics.proto +188 -0
  391. package/src/providers/devin/proto/exa/api_server_pb/api_server.proto +2461 -0
  392. package/src/providers/devin/proto/exa/auth_pb/auth.proto +19 -0
  393. package/src/providers/devin/proto/exa/auto_cascade_common_pb/auto_cascade_common.proto +79 -0
  394. package/src/providers/devin/proto/exa/browser_preview_pb/browser_preview.proto +32 -0
  395. package/src/providers/devin/proto/exa/bug_checker_pb/bug_checker.proto +22 -0
  396. package/src/providers/devin/proto/exa/cascade_plugins_pb/cascade_plugins.proto +262 -0
  397. package/src/providers/devin/proto/exa/chat_client_server_pb/chat_client_server.proto +57 -0
  398. package/src/providers/devin/proto/exa/chat_pb/chat.proto +449 -0
  399. package/src/providers/devin/proto/exa/code_edit/code_edit_pb/code_edit.proto +186 -0
  400. package/src/providers/devin/proto/exa/codeium_common_pb/codeium_common.proto +4157 -0
  401. package/src/providers/devin/proto/exa/context_module_pb/context_module.proto +175 -0
  402. package/src/providers/devin/proto/exa/cortex_pb/cortex.proto +3268 -0
  403. package/src/providers/devin/proto/exa/dev_pb/dev.proto +26 -0
  404. package/src/providers/devin/proto/exa/diff_action_pb/diff_action.proto +75 -0
  405. package/src/providers/devin/proto/exa/eval/pr_eval/datasets_pb/datasets.proto +103 -0
  406. package/src/providers/devin/proto/exa/eval_pb/eval.proto +1315 -0
  407. package/src/providers/devin/proto/exa/extension_server_pb/extension_server.proto +556 -0
  408. package/src/providers/devin/proto/exa/file_system_provider_pb/file_system_provider.proto +75 -0
  409. package/src/providers/devin/proto/exa/index_pb/index.proto +461 -0
  410. package/src/providers/devin/proto/exa/knowledge_base_pb/knowledge_base.proto +144 -0
  411. package/src/providers/devin/proto/exa/language_server_pb/language_server.proto +2385 -0
  412. package/src/providers/devin/proto/exa/model_management_pb/model_management.proto +186 -0
  413. package/src/providers/devin/proto/exa/opensearch_clients_pb/opensearch_clients.proto +503 -0
  414. package/src/providers/devin/proto/exa/product_analytics_pb/product_analytics.proto +37 -0
  415. package/src/providers/devin/proto/exa/prompt_pb/prompt.proto +92 -0
  416. package/src/providers/devin/proto/exa/reactive_component_pb/reactive_component.proto +96 -0
  417. package/src/providers/devin/proto/exa/seat_management_pb/seat_management.proto +2680 -0
  418. package/src/providers/devin/proto/exa/tokenizer_pb/tokenizer.proto +37 -0
  419. package/src/providers/devin/proto/exa/trainer_pb/config.proto +647 -0
  420. package/src/providers/devin/proto/exa/tree_sitter/language_data_pb/language_data.proto +14 -0
  421. package/src/providers/devin/proto/exa/trust_pb/trust.proto +157 -0
  422. package/src/providers/devin/proto/exa/user_analytics_pb/user_analytics.proto +519 -0
  423. package/src/providers/devin/proto/google.golang.org/appengine/internal/base/api_base.proto +28 -0
  424. package/src/providers/devin/proto/google.golang.org/appengine/internal/datastore/datastore_v3.proto +484 -0
  425. package/src/providers/devin/proto/google.golang.org/appengine/internal/log/log_service.proto +136 -0
  426. package/src/providers/devin/proto/google.golang.org/appengine/internal/remote_api/remote_api.proto +42 -0
  427. package/src/providers/devin/proto/google.golang.org/appengine/internal/urlfetch/urlfetch_service.proto +61 -0
  428. package/src/providers/devin/proto/grpc/binlog/v1/binarylog.proto +84 -0
  429. package/src/providers/devin/proto/io/prometheus/client/metrics.proto +98 -0
  430. package/src/providers/devin.ts +679 -0
  431. package/src/providers/error-message.ts +23 -0
  432. package/src/providers/github-copilot-headers.ts +141 -0
  433. package/src/providers/gitlab-duo-workflow-chatml-note.md +1 -0
  434. package/src/providers/gitlab-duo-workflow.ts +3135 -0
  435. package/src/providers/gitlab-duo.ts +399 -0
  436. package/src/providers/google-auth.ts +330 -0
  437. package/src/providers/google-gemini-cli.ts +1370 -0
  438. package/src/providers/google-shared.ts +1122 -0
  439. package/src/providers/google-types.ts +180 -0
  440. package/src/providers/google-vertex.ts +135 -0
  441. package/src/providers/google.ts +47 -0
  442. package/src/providers/grammar.ts +70 -0
  443. package/src/providers/kimi.ts +51 -0
  444. package/src/providers/mock.ts +514 -0
  445. package/src/providers/ollama.ts +776 -0
  446. package/src/providers/openai-anthropic-shim.ts +166 -0
  447. package/src/providers/openai-chat-server-schema.ts +243 -0
  448. package/src/providers/openai-chat-server.ts +752 -0
  449. package/src/providers/openai-chat-wire.ts +859 -0
  450. package/src/providers/openai-codex/request-transformer.ts +491 -0
  451. package/src/providers/openai-codex/response-handler.ts +102 -0
  452. package/src/providers/openai-codex-responses.ts +4716 -0
  453. package/src/providers/openai-completions.ts +2389 -0
  454. package/src/providers/openai-reasoning-fallback.ts +269 -0
  455. package/src/providers/openai-responses-server-schema.ts +397 -0
  456. package/src/providers/openai-responses-server.ts +1466 -0
  457. package/src/providers/openai-responses-wire.ts +6416 -0
  458. package/src/providers/openai-responses.ts +1393 -0
  459. package/src/providers/openai-shared.ts +3500 -0
  460. package/src/providers/pi-native-client.ts +275 -0
  461. package/src/providers/pi-native-server.ts +245 -0
  462. package/src/providers/register-builtins.ts +503 -0
  463. package/src/providers/synthetic.ts +50 -0
  464. package/src/providers/transform-messages.ts +1083 -0
  465. package/src/providers/vision-guard.ts +54 -0
  466. package/src/registry/aiand.ts +22 -0
  467. package/src/registry/aimlapi.ts +6 -0
  468. package/src/registry/alibaba-coding-plan.ts +104 -0
  469. package/src/registry/alibaba-token-plan.ts +125 -0
  470. package/src/registry/amazon-bedrock.ts +22 -0
  471. package/src/registry/anthropic.ts +26 -0
  472. package/src/registry/api-key-login.ts +115 -0
  473. package/src/registry/api-key-validation.ts +145 -0
  474. package/src/registry/aws.ts +57 -0
  475. package/src/registry/azure.ts +6 -0
  476. package/src/registry/baseten.ts +22 -0
  477. package/src/registry/bedrock-mantle.ts +34 -0
  478. package/src/registry/cerebras.ts +23 -0
  479. package/src/registry/cloudflare-ai-gateway.ts +45 -0
  480. package/src/registry/coreweave.ts +40 -0
  481. package/src/registry/cursor.ts +20 -0
  482. package/src/registry/deepseek.ts +46 -0
  483. package/src/registry/derived.ts +9 -0
  484. package/src/registry/devin.ts +15 -0
  485. package/src/registry/exa.ts +19 -0
  486. package/src/registry/firepass.ts +32 -0
  487. package/src/registry/fireworks.ts +28 -0
  488. package/src/registry/github-copilot.ts +22 -0
  489. package/src/registry/gitlab-duo-workflow.ts +20 -0
  490. package/src/registry/gitlab-duo.ts +19 -0
  491. package/src/registry/gmi-cloud.ts +22 -0
  492. package/src/registry/google-antigravity.ts +22 -0
  493. package/src/registry/google-gemini-cli.ts +22 -0
  494. package/src/registry/google-vertex.ts +38 -0
  495. package/src/registry/google.ts +6 -0
  496. package/src/registry/groq.ts +6 -0
  497. package/src/registry/huggingface.ts +29 -0
  498. package/src/registry/index.ts +4 -0
  499. package/src/registry/kagi.ts +46 -0
  500. package/src/registry/kilo.ts +114 -0
  501. package/src/registry/kimi-code.ts +17 -0
  502. package/src/registry/litellm.ts +45 -0
  503. package/src/registry/llama-cpp.ts +35 -0
  504. package/src/registry/lm-studio.ts +31 -0
  505. package/src/registry/meta.ts +22 -0
  506. package/src/registry/minimax-code-cn.ts +12 -0
  507. package/src/registry/minimax-code.ts +12 -0
  508. package/src/registry/minimax.ts +6 -0
  509. package/src/registry/mistral.ts +6 -0
  510. package/src/registry/moonshot.ts +28 -0
  511. package/src/registry/nanogpt.ts +22 -0
  512. package/src/registry/novita.ts +25 -0
  513. package/src/registry/nvidia.ts +61 -0
  514. package/src/registry/oauth/anthropic-constants.ts +12 -0
  515. package/src/registry/oauth/anthropic.ts +346 -0
  516. package/src/registry/oauth/callback-server.ts +438 -0
  517. package/src/registry/oauth/cursor.ts +187 -0
  518. package/src/registry/oauth/device-code.ts +92 -0
  519. package/src/registry/oauth/devin.ts +124 -0
  520. package/src/registry/oauth/github-copilot.ts +369 -0
  521. package/src/registry/oauth/gitlab-duo-workflow.ts +146 -0
  522. package/src/registry/oauth/gitlab-duo.ts +222 -0
  523. package/src/registry/oauth/google-antigravity.ts +225 -0
  524. package/src/registry/oauth/google-gemini-cli.ts +297 -0
  525. package/src/registry/oauth/google-oauth-shared.ts +211 -0
  526. package/src/registry/oauth/index.ts +187 -0
  527. package/src/registry/oauth/kimi.ts +297 -0
  528. package/src/registry/oauth/minimax-code.ts +53 -0
  529. package/src/registry/oauth/oauth.html +317 -0
  530. package/src/registry/oauth/openai-codex.ts +384 -0
  531. package/src/registry/oauth/opencode.ts +50 -0
  532. package/src/registry/oauth/perplexity.ts +228 -0
  533. package/src/registry/oauth/pkce.ts +18 -0
  534. package/src/registry/oauth/types.ts +96 -0
  535. package/src/registry/oauth/wafer.ts +24 -0
  536. package/src/registry/oauth/xai-oauth.ts +559 -0
  537. package/src/registry/oauth/xiaomi.ts +211 -0
  538. package/src/registry/oauth/zai.ts +285 -0
  539. package/src/registry/ollama-cloud.ts +36 -0
  540. package/src/registry/ollama.ts +43 -0
  541. package/src/registry/openai-codex-device.ts +18 -0
  542. package/src/registry/openai-codex.ts +19 -0
  543. package/src/registry/openai.ts +6 -0
  544. package/src/registry/opencode-go.ts +12 -0
  545. package/src/registry/opencode-zen.ts +12 -0
  546. package/src/registry/openrouter.ts +28 -0
  547. package/src/registry/parallel.ts +45 -0
  548. package/src/registry/perplexity.ts +13 -0
  549. package/src/registry/qianfan.ts +27 -0
  550. package/src/registry/qwen-portal.ts +50 -0
  551. package/src/registry/registry.ts +182 -0
  552. package/src/registry/sakana.ts +22 -0
  553. package/src/registry/siliconflow-cn.ts +22 -0
  554. package/src/registry/siliconflow.ts +22 -0
  555. package/src/registry/synthetic.ts +21 -0
  556. package/src/registry/tavily.ts +45 -0
  557. package/src/registry/together.ts +22 -0
  558. package/src/registry/types.ts +86 -0
  559. package/src/registry/umans.ts +23 -0
  560. package/src/registry/venice.ts +33 -0
  561. package/src/registry/vercel-ai-gateway.ts +38 -0
  562. package/src/registry/vllm.ts +34 -0
  563. package/src/registry/wafer-serverless.ts +12 -0
  564. package/src/registry/xai-oauth.ts +17 -0
  565. package/src/registry/xai.ts +22 -0
  566. package/src/registry/xiaomi-token-plan-ams.ts +12 -0
  567. package/src/registry/xiaomi-token-plan-cn.ts +12 -0
  568. package/src/registry/xiaomi-token-plan-sgp.ts +12 -0
  569. package/src/registry/xiaomi.ts +12 -0
  570. package/src/registry/zai.ts +41 -0
  571. package/src/registry/zenmux.ts +22 -0
  572. package/src/registry/zhipu-coding-plan.ts +27 -0
  573. package/src/stream.ts +1944 -0
  574. package/src/types.ts +1243 -0
  575. package/src/usage/alibaba-token-plan.ts +230 -0
  576. package/src/usage/claude.ts +830 -0
  577. package/src/usage/cursor.ts +335 -0
  578. package/src/usage/gemini.ts +258 -0
  579. package/src/usage/github-copilot.ts +424 -0
  580. package/src/usage/google-antigravity.ts +497 -0
  581. package/src/usage/kimi.ts +277 -0
  582. package/src/usage/minimax-code.ts +291 -0
  583. package/src/usage/ollama.ts +41 -0
  584. package/src/usage/openai-codex-base-url.ts +35 -0
  585. package/src/usage/openai-codex-reset.ts +205 -0
  586. package/src/usage/openai-codex.ts +627 -0
  587. package/src/usage/opencode-go.ts +89 -0
  588. package/src/usage/shared.ts +10 -0
  589. package/src/usage/synthetic.ts +180 -0
  590. package/src/usage/umans.ts +192 -0
  591. package/src/usage/xai-oauth.ts +414 -0
  592. package/src/usage/zai.ts +370 -0
  593. package/src/usage.ts +411 -0
  594. package/src/utils/abort.ts +67 -0
  595. package/src/utils/anthropic-auth.ts +93 -0
  596. package/src/utils/aws-profile.ts +88 -0
  597. package/src/utils/block-symbols.ts +78 -0
  598. package/src/utils/deterministic-id.ts +20 -0
  599. package/src/utils/empty-completion-retry.ts +161 -0
  600. package/src/utils/event-stream.ts +202 -0
  601. package/src/utils/foundry.ts +8 -0
  602. package/src/utils/google-validation.ts +25 -0
  603. package/src/utils/harmony-leak.ts +500 -0
  604. package/src/utils/http-inspector.ts +196 -0
  605. package/src/utils/idle-iterator.ts +531 -0
  606. package/src/utils/leaked-thinking-stream.ts +483 -0
  607. package/src/utils/openai-http.ts +119 -0
  608. package/src/utils/openrouter-headers.ts +12 -0
  609. package/src/utils/parse-bind.ts +56 -0
  610. package/src/utils/provider-response.ts +30 -0
  611. package/src/utils/proxy.ts +314 -0
  612. package/src/utils/request-debug.ts +351 -0
  613. package/src/utils/retry-after.ts +121 -0
  614. package/src/utils/retry.ts +77 -0
  615. package/src/utils/schema/CONSTRAINTS.md +168 -0
  616. package/src/utils/schema/adapt.ts +36 -0
  617. package/src/utils/schema/compatibility.ts +435 -0
  618. package/src/utils/schema/dereference.ts +98 -0
  619. package/src/utils/schema/draft.ts +341 -0
  620. package/src/utils/schema/equality.ts +97 -0
  621. package/src/utils/schema/fields.ts +210 -0
  622. package/src/utils/schema/index.ts +14 -0
  623. package/src/utils/schema/json-schema-validator.ts +595 -0
  624. package/src/utils/schema/meta-validator.ts +167 -0
  625. package/src/utils/schema/normalize.ts +2314 -0
  626. package/src/utils/schema/spill.ts +43 -0
  627. package/src/utils/schema/stamps.ts +109 -0
  628. package/src/utils/schema/strict-tool-validation.ts +117 -0
  629. package/src/utils/schema/types.ts +10 -0
  630. package/src/utils/schema/typescript.ts +212 -0
  631. package/src/utils/schema/wire.ts +662 -0
  632. package/src/utils/sdk-stream-timeout.ts +43 -0
  633. package/src/utils/sse-debug.ts +18 -0
  634. package/src/utils/stream-markup-healing.ts +247 -0
  635. package/src/utils/thinking-loop.ts +552 -0
  636. package/src/utils/tool-call-loop-guard.ts +107 -0
  637. package/src/utils/tool-choice.ts +101 -0
  638. package/src/utils/validation.ts +1932 -0
  639. package/src/utils.ts +492 -0
@@ -0,0 +1,500 @@
1
+ /**
2
+ * GPT-5 Harmony-header leakage detection and recovery.
3
+ *
4
+ * Background and policy: see `docs/ERRATA-GPT5-HARMONY.md`. This module
5
+ * implements §3 of that document: detection by signal fusion, plus a
6
+ * truncate-and-resume primitive for the `edit` tool when its input is in
7
+ * hashline DSL form. Other tools and surfaces fall through to
8
+ * abort-and-retry handled by the agent loop.
9
+ */
10
+ import { preferredDialect } from "@linxiraos/pi-catalog/identity";
11
+ import type { AssistantMessage, Model, ToolCall } from "../types";
12
+
13
+ // Single source of truth for the marker pattern. `M` in the errata.
14
+ // Use a fresh non-global instance for `.test()` to avoid lastIndex pitfalls.
15
+ const MARKER_RE = /\bto=functions\.[A-Za-z_]\w*/g;
16
+ const HARMONY_RE = /<\|(start|end|channel|message|call|return)\|>/g;
17
+
18
+ // Reserved Harmony control-token spellings. Escaping these to their inert
19
+ // backslash form lets untrusted data (user text, tool results) reach
20
+ // harmony-server models (gpt-5.x) without the provider's prompt validator
21
+ // rejecting the whole request (invalid_prompt / "Request blocked"). `constrain`
22
+ // is escaped too — it is a real control token even though it is not a leak
23
+ // signal on its own.
24
+ const HARMONY_CONTROL_TOKEN_ESCAPE_RE = /<\|(start|end|message|channel|constrain|return|call)\|>/g;
25
+
26
+ /**
27
+ * Escape reserved Harmony control tokens in arbitrary text so it can be
28
+ * transported as data to a harmony-dialect model. Returns the input unchanged
29
+ * when it carries no reserved spelling.
30
+ *
31
+ * Callers MUST gate on a harmony target and escape only the transport copy —
32
+ * the persisted transcript keeps the byte-for-byte original.
33
+ */
34
+ export function escapeHarmonyControlTokens(text: string): string {
35
+ return text.replace(HARMONY_CONTROL_TOKEN_ESCAPE_RE, "<\\|$1\\|>");
36
+ }
37
+
38
+ /**
39
+ * Escape reserved Harmony control tokens inside a JSON document string (e.g.
40
+ * `function_call.arguments`). Doubles the backslash so the document remains
41
+ * valid JSON whose *decoded* strings carry the inert `<\|token\|>` spelling.
42
+ * `<|` cannot occur outside a string literal in valid JSON, so the blanket
43
+ * replace never corrupts structure; malformed documents are escaped
44
+ * best-effort.
45
+ */
46
+ export function escapeHarmonyControlTokensInJson(text: string): string {
47
+ return text.replace(HARMONY_CONTROL_TOKEN_ESCAPE_RE, "<\\\\|$1\\\\|>");
48
+ }
49
+
50
+ /**
51
+ * Whether requests to `model` are served by a Harmony-dialect backend
52
+ * (gpt-5.x / gpt-oss), which rejects reserved control-token spellings appearing
53
+ * as data in the request. Resolves the wire model id (`requestModelId ?? id`)
54
+ * so deployment/catalog aliases — e.g. an Azure alias whose `requestModelId` is
55
+ * `gpt-5.4` — are detected even when the local id is opaque.
56
+ */
57
+ export function isHarmonyDialectModel(model: Model): boolean {
58
+ return preferredDialect(model.requestModelId ?? model.id) === "harmony";
59
+ }
60
+
61
+ // Channel-word adjacency (`C`): channel/role name appearing immediately before the marker.
62
+ const CHANNEL_WORD_RE = /\b(?:analysis|commentary|assistant|user|system|developer|tool)\s+to=functions\./;
63
+
64
+ // Glitch-token adjacency (`G`). The Japgolly literal is escaped so this regex
65
+ // source itself does not trip detection if the file is scanned (e.g. when
66
+ // editing this module via the same agent that detects).
67
+ const GLITCH_RE = /\b(?:changedFiles|RTLU|Jsii(?:_commentary)?|\x4aapgolly)\b/;
68
+
69
+ // Body-channel cascade (`B`): marker followed by ` code` then another marker
70
+ // within 200 chars. Single regex; no manual slicing needed.
71
+ const BODY_CASCADE_RE = /to=functions\.\w+\s+code\b[\s\S]{0,200}?to=functions\./;
72
+
73
+ // Fake-result framing (`R`): marker followed within 80 chars by Cell N: framing.
74
+ const FAKE_RESULT_RE = /to=functions\.\w+[\s\S]{0,80}?code_output\s*\nCell\s+\d+:/;
75
+
76
+ const FENCE_RE = /^\s*(?:```+|~~~+)/;
77
+
78
+ // Non-Latin scripts seen in the corpus: CJK + ext, Cyrillic, Thai, Georgian,
79
+ // Armenian, Kannada, Telugu, Devanagari, Arabic, Malayalam.
80
+ const SCRIPT_CLASS =
81
+ "\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\u0400-\u04FF\u0E00-\u0E7F\u10A0-\u10FF\u0530-\u058F\u0C80-\u0CFF\u0C00-\u0C7F\u0900-\u097F\u0600-\u06FF\u0D00-\u0D7F";
82
+ const SCRIPT_RUN_RE = new RegExp(`[${SCRIPT_CLASS}]{2,}`, "u");
83
+
84
+ // Recovery registry. Each entry's parser must recognize the configured
85
+ // sentinel (per-tool, see eval/parse.ts and hashline/executor.ts) and surface
86
+ // a warning to the model so it knows to re-issue any remaining work.
87
+ // `accepts` gates on input shape: tools whose contaminated input doesn't
88
+ // match the parser's expected DSL fall through to abort-and-retry.
89
+ //
90
+ // • `edit`: hashline DSL input begins with `@<path>`. Apply_patch envelopes
91
+ // (`*** Begin Patch …`) and JSON-schema variants are not recoverable —
92
+ // their parsers don't recognize `*** Abort`.
93
+ // • `eval`: any string is a parseable cell sequence (the parser is lenient
94
+ // and falls back to implicit-cell mode on bare strings).
95
+ interface RecoveryConfig {
96
+ sentinel: string;
97
+ accepts: (input: string) => boolean;
98
+ }
99
+ const RECOVERY_REGISTRY: Record<string, RecoveryConfig> = {
100
+ edit: {
101
+ sentinel: "\n*** Abort\n",
102
+ accepts: input => input.replace(/^\s+/, "").startsWith("@"),
103
+ },
104
+ eval: {
105
+ sentinel: "\n*** Abort\n",
106
+ accepts: () => true,
107
+ },
108
+ };
109
+
110
+ const SIGNAL_ORDER = ["M", "C", "G", "S", "B", "R", "T"] as const;
111
+
112
+ export type HarmonySignalClass = "H" | (typeof SIGNAL_ORDER)[number];
113
+
114
+ export type HarmonySurface = "assistant_text" | "assistant_thinking" | "tool_arg";
115
+
116
+ export interface HarmonySignal {
117
+ classes: HarmonySignalClass[];
118
+ start: number;
119
+ end: number;
120
+ text: string;
121
+ }
122
+
123
+ export interface HarmonyDetection {
124
+ surface: HarmonySurface;
125
+ contentIndex?: number;
126
+ toolName?: string;
127
+ toolCallId?: string;
128
+ signals: HarmonySignal[];
129
+ }
130
+
131
+ export interface HarmonyAuditEvent {
132
+ action: "truncate_resume" | "abort_retry" | "escalated";
133
+ surface: HarmonySurface;
134
+ signal: string;
135
+ retryN: number;
136
+ model: string;
137
+ provider: string;
138
+ toolName?: string;
139
+ removedLen: number;
140
+ removedSha8: string;
141
+ removedPreview: string;
142
+ removedBlob?: string;
143
+ }
144
+
145
+ export interface HarmonyRecoveredToolCall {
146
+ message: AssistantMessage;
147
+ removed: string;
148
+ }
149
+
150
+ /**
151
+ * Whether to run leak detection on responses from this model. We default-on
152
+ * for every openai-codex model rather than enumerating ids, so a future
153
+ * gpt-5.6 (or whatever) doesn't silently bypass the mitigation. Detection
154
+ * itself is cheap; the cost of missing a leak on a new model is not.
155
+ */
156
+ export function isHarmonyLeakMitigationTarget(model: Model): boolean {
157
+ return model.provider === "openai-codex";
158
+ }
159
+
160
+ export function signalListLabel(signals: readonly HarmonySignal[]): string {
161
+ const seen: string[] = [];
162
+ for (const signal of signals) {
163
+ const label = signal.classes.join("+");
164
+ if (!seen.includes(label)) seen.push(label);
165
+ }
166
+ return seen.join(",") || "none";
167
+ }
168
+
169
+ /**
170
+ * Detect harmony-protocol leakage in `text`. Returns undefined if clean.
171
+ *
172
+ * Trip rule: `H` alone, or `M` paired with at least one co-signal
173
+ * (`C`/`G`/`S`/`B`/`R`/`T`). Bare `M` does not trip — this document, its
174
+ * tests, and bug reports legitimately carry the marker.
175
+ *
176
+ * The `tool_arg` surface is held to a stricter rule. A tool argument is
177
+ * arbitrary file/data content that can legitimately carry the marker, a
178
+ * channel word, harmony control tokens, or a non-Latin script run (editing
179
+ * these very fixtures does exactly that). The only robust leak signal there
180
+ * is content trailing the structurally-valid parse, so a `tool_arg` detection
181
+ * additionally requires the `T` co-signal. Absent a `parsedEnd` boundary `T`
182
+ * is never set, so `tool_arg` scanning stays inert and a legitimate codex tool
183
+ * call is never hard-aborted. `assistant_text`/`assistant_thinking` keep the
184
+ * base rule.
185
+ *
186
+ * `parsedEnd`, when supplied, marks the byte at which a structurally valid
187
+ * tool-argument parse ends; markers at or past it set the `T` co-signal.
188
+ * `contentIndex`/`toolName`/`toolCallId` flow through to the returned
189
+ * detection for downstream auditing.
190
+ */
191
+ export function detectHarmonyLeak(
192
+ text: string,
193
+ surface: HarmonySurface,
194
+ options: {
195
+ parsedEnd?: number;
196
+ contentIndex?: number;
197
+ toolName?: string;
198
+ toolCallId?: string;
199
+ } = {},
200
+ ): HarmonyDetection | undefined {
201
+ const fences = computeFenceRanges(text);
202
+ const signals: HarmonySignal[] = [];
203
+
204
+ for (const match of text.matchAll(HARMONY_RE)) {
205
+ const start = match.index ?? 0;
206
+ if (isInsideFence(fences, start)) continue;
207
+ signals.push(makeSignal(["H"], start, start + match[0].length, match[0]));
208
+ }
209
+
210
+ for (const match of text.matchAll(MARKER_RE)) {
211
+ const start = match.index ?? 0;
212
+ if (isInsideFence(fences, start)) continue;
213
+ const end = start + match[0].length;
214
+ const classes: HarmonySignalClass[] = ["M"];
215
+
216
+ const adjacent = text.slice(Math.max(0, start - 64), Math.min(text.length, end + 16));
217
+ const near = text.slice(Math.max(0, start - 16), Math.min(text.length, end + 16));
218
+ const forward = text.slice(start, Math.min(text.length, start + 240));
219
+
220
+ if (CHANNEL_WORD_RE.test(adjacent)) classes.push("C");
221
+ if (GLITCH_RE.test(near)) classes.push("G");
222
+ if (hasScriptMismatchNear(text, start, end)) classes.push("S");
223
+ if (BODY_CASCADE_RE.test(forward)) classes.push("B");
224
+ if (FAKE_RESULT_RE.test(forward)) classes.push("R");
225
+ if (options.parsedEnd !== undefined && start >= options.parsedEnd) classes.push("T");
226
+
227
+ // `M` alone never trips: legitimate documentation/tests carry it.
228
+ if (classes.length > 1) {
229
+ signals.push(makeSignal(classes, start, end, match[0]));
230
+ }
231
+ }
232
+
233
+ if (signals.length === 0) return undefined;
234
+ // Tool arguments are data: they can legitimately embed the marker, a channel
235
+ // word, harmony control tokens, or a non-Latin script run. Only a marker
236
+ // trailing the structurally-valid parse (`T`) is a reliable leak signal, so
237
+ // refuse to trip a `tool_arg` detection without it. Without a `parsedEnd`
238
+ // boundary `T` is never set and the surface stays inert.
239
+ if (surface === "tool_arg" && !signals.some(s => s.classes.includes("T"))) return undefined;
240
+ signals.sort((a, b) => a.start - b.start || a.end - b.end);
241
+ return {
242
+ surface,
243
+ contentIndex: options.contentIndex,
244
+ toolName: options.toolName,
245
+ toolCallId: options.toolCallId,
246
+ signals,
247
+ };
248
+ }
249
+
250
+ /**
251
+ * Scan an assistant message's content blocks; return the first detection.
252
+ *
253
+ * `toolArgParseEnd`, when supplied, resolves the byte offset at which a tool
254
+ * call's structurally-valid argument parse ends (the `T` co-signal in
255
+ * {@link detectHarmonyLeak}). Callers that can parse a tool's argument DSL pass
256
+ * it to enable `tool_arg` leak detection; omitting it keeps that surface inert
257
+ * — the safe default the agent loop relies on, since it cannot bound a streamed
258
+ * tool DSL and must never hard-abort a legitimate tool call.
259
+ */
260
+ export function detectHarmonyLeakInAssistantMessage(
261
+ message: AssistantMessage,
262
+ toolArgParseEnd?: (toolCall: ToolCall) => number | undefined,
263
+ ): HarmonyDetection | undefined {
264
+ for (let i = 0; i < message.content.length; i++) {
265
+ const block = message.content[i];
266
+ if (block.type === "text") {
267
+ const d = detectHarmonyLeak(block.text, "assistant_text", { contentIndex: i });
268
+ if (d) return d;
269
+ } else if (block.type === "thinking") {
270
+ const d = detectHarmonyLeak(block.thinking, "assistant_thinking", { contentIndex: i });
271
+ if (d) return d;
272
+ } else if (block.type === "toolCall") {
273
+ const argText = getToolArgumentText(block);
274
+ if (argText !== undefined) {
275
+ const d = detectHarmonyLeak(argText, "tool_arg", {
276
+ contentIndex: i,
277
+ toolName: block.name,
278
+ toolCallId: block.id,
279
+ parsedEnd: toolArgParseEnd?.(block),
280
+ });
281
+ if (d) return d;
282
+ }
283
+ }
284
+ }
285
+ return undefined;
286
+ }
287
+
288
+ /**
289
+ * Truncate a contaminated tool call at the start of the contaminated line and
290
+ * append the tool's recovery sentinel. Returns a recovered AssistantMessage
291
+ * (containing only the cleaned tool call), a synthetic continuation user
292
+ * message asking the model to re-issue the rest, and the removed substring
293
+ * for auditing. Returns undefined when the tool is not recovery-eligible or
294
+ * the truncation would leave nothing meaningful to dispatch.
295
+ *
296
+ * `providerPayload` is dropped from the recovered message: for Codex the
297
+ * encrypted reasoning blob is opaque/signed and we cannot validate that it is
298
+ * uncontaminated. The model re-reasons on the next turn.
299
+ */
300
+ export function recoverHarmonyToolCall(
301
+ message: AssistantMessage,
302
+ detection: HarmonyDetection,
303
+ ): HarmonyRecoveredToolCall | undefined {
304
+ if (detection.surface !== "tool_arg" || detection.contentIndex === undefined) return undefined;
305
+ const block = message.content[detection.contentIndex];
306
+ if (block?.type !== "toolCall") return undefined;
307
+
308
+ const config = RECOVERY_REGISTRY[block.name];
309
+ if (!config) return undefined;
310
+
311
+ const input = block.arguments?.input;
312
+ if (typeof input !== "string") return undefined;
313
+ if (!config.accepts(input)) return undefined;
314
+
315
+ const offset = detection.signals[0]?.start;
316
+ if (offset === undefined) return undefined;
317
+
318
+ const truncated = truncateAtLineAndAppendSentinel(input, offset, config.sentinel);
319
+ if (truncated === undefined) return undefined;
320
+
321
+ const cleanToolCall: ToolCall = {
322
+ ...block,
323
+ arguments: { ...block.arguments, input: truncated.clean },
324
+ };
325
+ const cleanMessage: AssistantMessage = {
326
+ ...message,
327
+ content: [cleanToolCall],
328
+ // Drop encrypted reasoning blob: opaque, possibly carries the leak forward.
329
+ providerPayload: undefined,
330
+ stopReason: "toolUse",
331
+ errorMessage: undefined,
332
+ };
333
+ return { message: cleanMessage, removed: truncated.removed };
334
+ }
335
+
336
+ /**
337
+ * Return the contaminated substring from `message` for audit purposes when
338
+ * recovery is not applicable (abort path). Walks from the first detected
339
+ * signal to end-of-content within the relevant block. Returns "" if the
340
+ * detection cannot be resolved against the message.
341
+ */
342
+ export function extractHarmonyRemoved(message: AssistantMessage, detection: HarmonyDetection): string {
343
+ if (detection.contentIndex === undefined) return "";
344
+ const block = message.content[detection.contentIndex];
345
+ if (!block) return "";
346
+ const start = detection.signals[0]?.start ?? 0;
347
+ if (block.type === "text") return block.text.slice(start);
348
+ if (block.type === "thinking") return block.thinking.slice(start);
349
+ if (block.type === "toolCall") {
350
+ const text = getToolArgumentText(block);
351
+ return text ? text.slice(start) : "";
352
+ }
353
+ return "";
354
+ }
355
+
356
+ export function createHarmonyAuditEvent(params: {
357
+ action: HarmonyAuditEvent["action"];
358
+ detection: HarmonyDetection;
359
+ model: Model;
360
+ retryN: number;
361
+ removed: string;
362
+ }): HarmonyAuditEvent {
363
+ return {
364
+ action: params.action,
365
+ surface: params.detection.surface,
366
+ signal: signalListLabel(params.detection.signals),
367
+ retryN: params.retryN,
368
+ model: params.model.id,
369
+ provider: params.model.provider,
370
+ toolName: params.detection.toolName,
371
+ removedLen: params.removed.length,
372
+ removedSha8: sha8(params.removed),
373
+ removedPreview: redactedJunkPreview(params.removed),
374
+ removedBlob: Bun.env.OMP_HARMONY_DEBUG === "1" ? params.removed : undefined,
375
+ };
376
+ }
377
+
378
+ // ─── internals ──────────────────────────────────────────────────────────────
379
+
380
+ function makeSignal(classes: HarmonySignalClass[], start: number, end: number, text: string): HarmonySignal {
381
+ if (classes[0] === "H") return { classes: ["H"], start, end, text };
382
+ const sorted: HarmonySignalClass[] = [];
383
+ for (const cls of SIGNAL_ORDER) {
384
+ if (classes.includes(cls)) sorted.push(cls);
385
+ }
386
+ return { classes: sorted, start, end, text };
387
+ }
388
+
389
+ /**
390
+ * Precompute fenced-code-block ranges once per text. Each range is a
391
+ * [start, end) span of bytes inside any ```/~~~ fence. O(n) once instead of
392
+ * O(n) per detected match.
393
+ */
394
+ function computeFenceRanges(text: string): Array<[number, number]> {
395
+ const ranges: Array<[number, number]> = [];
396
+ let inFence = false;
397
+ let fenceStart = 0;
398
+ let lineStart = 0;
399
+ while (lineStart <= text.length) {
400
+ const newline = text.indexOf("\n", lineStart);
401
+ const lineEnd = newline === -1 ? text.length : newline;
402
+ const line = text.slice(lineStart, lineEnd);
403
+ if (FENCE_RE.test(line)) {
404
+ if (inFence) {
405
+ ranges.push([fenceStart, lineEnd]);
406
+ inFence = false;
407
+ } else {
408
+ fenceStart = lineStart;
409
+ inFence = true;
410
+ }
411
+ }
412
+ if (newline === -1) break;
413
+ lineStart = newline + 1;
414
+ }
415
+ if (inFence) ranges.push([fenceStart, text.length]);
416
+ return ranges;
417
+ }
418
+
419
+ function isInsideFence(ranges: Array<[number, number]>, position: number): boolean {
420
+ for (const [start, end] of ranges) {
421
+ if (position >= start && position < end) return true;
422
+ if (start > position) break;
423
+ }
424
+ return false;
425
+ }
426
+
427
+ function hasScriptMismatchNear(text: string, start: number, end: number): boolean {
428
+ const near = text.slice(Math.max(0, start - 32), Math.min(text.length, end + 32));
429
+ if (!SCRIPT_RUN_RE.test(near)) return false;
430
+ const surrounding = text.slice(Math.max(0, start - 200), Math.min(text.length, end + 200));
431
+ if (surrounding.length === 0) return false;
432
+ let ascii = 0;
433
+ for (let i = 0; i < surrounding.length; i++) {
434
+ if (surrounding.charCodeAt(i) < 128) ascii++;
435
+ }
436
+ return ascii / surrounding.length >= 0.85;
437
+ }
438
+
439
+ /**
440
+ * Tool-call argument text used for detection scanning. For tools whose args
441
+ * include a free-form `input` string we scan that directly so reported byte
442
+ * offsets line up with the original. For everything else we fall back to a
443
+ * JSON-stringified blob so detection still fires; that path's offsets are
444
+ * NOT meaningful for slicing the original args, but the recovery path gates
445
+ * on `block.arguments.input` being a string and only ever slices that.
446
+ */
447
+ function getToolArgumentText(toolCall: ToolCall): string | undefined {
448
+ if (typeof toolCall.arguments?.input === "string") return toolCall.arguments.input;
449
+ try {
450
+ return JSON.stringify(toolCall.arguments);
451
+ } catch {
452
+ return undefined;
453
+ }
454
+ }
455
+
456
+ function truncateAtLineAndAppendSentinel(
457
+ input: string,
458
+ offset: number,
459
+ sentinel: string,
460
+ ): { clean: string; removed: string } | undefined {
461
+ const lineStart = offset <= 0 ? 0 : input.lastIndexOf("\n", offset - 1) + 1;
462
+ if (lineStart === 0) return undefined; // would cut everything
463
+ const head = input.slice(0, lineStart).replace(/\s+$/, "");
464
+ if (head.length === 0) return undefined;
465
+ return {
466
+ clean: head + sentinel,
467
+ removed: input.slice(lineStart),
468
+ };
469
+ }
470
+
471
+ function sha8(text: string): string {
472
+ return Bun.sha(text, "hex").slice(0, 8);
473
+ }
474
+
475
+ const PREVIEW_KEEP_RE = new RegExp(`[${SCRIPT_CLASS}\\s】【”“…」「、。]`, "u");
476
+ const PREVIEW_TOKEN_RE =
477
+ /^(?:to=functions\.[A-Za-z_]\w*|analysis|commentary|assistant|user|system|developer|tool|changedFiles|RTLU|Jsii(?:_commentary)?|\x4aapgolly)/;
478
+
479
+ /**
480
+ * Privacy-safe preview for the audit log: keeps marker/channel/glitch tokens,
481
+ * non-Latin script chars, and CJK punctuation; replaces everything else
482
+ * (potential source/secrets) with `·`. Sufficient to grow the glitch-token
483
+ * denylist from logs without exposing source content. Capped at 64 chars.
484
+ */
485
+ function redactedJunkPreview(text: string): string {
486
+ const source = text.slice(0, 64);
487
+ let out = "";
488
+ for (let i = 0; i < source.length; ) {
489
+ const tok = PREVIEW_TOKEN_RE.exec(source.slice(i));
490
+ if (tok) {
491
+ out += tok[0];
492
+ i += tok[0].length;
493
+ continue;
494
+ }
495
+ const ch = source[i] ?? "";
496
+ out += PREVIEW_KEEP_RE.test(ch) ? ch : "·";
497
+ i++;
498
+ }
499
+ return out;
500
+ }
@@ -0,0 +1,196 @@
1
+ import * as path from "node:path";
2
+ import { getLogsDir, isBunTestRuntime } from "@linxiraos/pi-utils";
3
+ import * as AIError from "../error/flags";
4
+ import { isCopilotTransientModelError } from "./retry.js";
5
+ import { formatErrorMessageWithRetryAfter } from "./retry-after.js";
6
+
7
+ export type RawHttpRequestDump = {
8
+ provider: string;
9
+ api: string;
10
+ model: string;
11
+ method?: string;
12
+ url?: string;
13
+ headers?: Record<string, string>;
14
+ body?: unknown;
15
+ };
16
+
17
+ export type CapturedHttpErrorResponse = {
18
+ status: number;
19
+ headers?: Headers;
20
+ bodyText?: string;
21
+ bodyJson?: unknown;
22
+ };
23
+
24
+ const SENSITIVE_HEADERS = ["authorization", "x-api-key", "api-key", "cookie", "set-cookie", "proxy-authorization"];
25
+
26
+ /**
27
+ * Build the JSON persisted for a rejected request. Request fields stay at the
28
+ * top level (so existing dump parsers still read `body`); the provider's error
29
+ * is added under `errorResponse` so a failed request is diagnosable from the
30
+ * dump file rather than the request alone.
31
+ */
32
+ export function buildHttp400DumpPayload(
33
+ dump: RawHttpRequestDump,
34
+ error: unknown,
35
+ message: string,
36
+ ): RawHttpRequestDump & { errorResponse: { status: number | undefined; message: string } } {
37
+ return {
38
+ ...sanitizeDump(dump),
39
+ errorResponse: { status: AIError.status(error), message },
40
+ };
41
+ }
42
+
43
+ /** HTTP statuses whose rejected request we persist for post-hoc diagnosis: the
44
+ * request-content rejections that wedge a session. 400 (bad request) and 413
45
+ * (payload too large — an oversized image / snapcompact frame payload that 413s
46
+ * and empties the turn). Auth (401/403), not-found (404), rate limits and 5xx
47
+ * are excluded: 429/5xx are retried, so persisting them here would write one
48
+ * dump per attempt. */
49
+ export function shouldDumpRejectedRequest(error: unknown): boolean {
50
+ const status = AIError.status(error);
51
+ return status === 400 || status === 413;
52
+ }
53
+
54
+ export async function appendRawHttpRequestDumpFor400(
55
+ message: string,
56
+ error: unknown,
57
+ dump: RawHttpRequestDump | undefined,
58
+ ): Promise<string> {
59
+ // Never persist dumps under the test runner: providers exercise the 400 path
60
+ if (!dump || isBunTestRuntime() || !shouldDumpRejectedRequest(error)) {
61
+ return message;
62
+ }
63
+
64
+ const payload = buildHttp400DumpPayload(dump, error, message);
65
+ const fileName = `${Date.now()}-${Bun.hash(JSON.stringify(payload)).toString(36)}.json`;
66
+ const filePath = path.join(getLogsDir(), "http-400-requests", fileName);
67
+
68
+ try {
69
+ await Bun.write(filePath, `${JSON.stringify(payload, null, 2)}\n`);
70
+ return `${message}\nraw-http-request=${filePath}`;
71
+ } catch (writeError) {
72
+ const writeMessage = writeError instanceof Error ? writeError.message : String(writeError);
73
+ return `${message}\nraw-http-request-save-failed=${writeMessage}`;
74
+ }
75
+ }
76
+
77
+ export async function finalizeErrorMessage(
78
+ error: unknown,
79
+ rawRequestDump: RawHttpRequestDump | undefined,
80
+ capturedErrorResponse?: CapturedHttpErrorResponse,
81
+ ): Promise<string> {
82
+ let message = formatErrorMessageWithRetryAfter(error, capturedErrorResponse?.headers);
83
+ const capturedMessage = formatCapturedHttpError(capturedErrorResponse);
84
+ if (capturedMessage) {
85
+ if (/\bstatus code\s*\(no body\)/i.test(message)) {
86
+ message = `${capturedErrorResponse?.status ?? "HTTP"} status code: ${capturedMessage}`;
87
+ } else if (!message.includes(capturedMessage)) {
88
+ message = `${message}\n${capturedMessage}`;
89
+ }
90
+ }
91
+ return appendRawHttpRequestDumpFor400(message, error, rawRequestDump);
92
+ }
93
+
94
+ /**
95
+ * Rewrite error message for GitHub Copilot request failures.
96
+ * Must run AFTER finalizeErrorMessage since it replaces the message entirely.
97
+ *
98
+ * 400 `model_not_supported` = Copilot fleet skew. A model that `/models`
99
+ * advertises can flap between 200 and 400 because only part of
100
+ * Copilot's fleet has it in the integrator allowlist. After the
101
+ * in-request retry exhausts, surface guidance rather than the raw error.
102
+ * 401 = token invalid/expired → credential removal is safe, prompt re-login.
103
+ * 403 = token valid but access denied (plan, model policy, org restriction) →
104
+ * do NOT reuse the auth-failed string (which triggers credential removal).
105
+ */
106
+ export function rewriteCopilotError(errorMessage: string, error: unknown, provider: string): string {
107
+ if (provider !== "github-copilot") return errorMessage;
108
+ const status = AIError.status(error);
109
+ if (status === 401) {
110
+ return `GitHub Copilot authentication failed (HTTP 401). Your token may have been revoked. Please re-login with /login github-copilot`;
111
+ }
112
+ if (status === 403) {
113
+ return `GitHub Copilot access denied (HTTP 403). Your account may not have access to this model or feature. Check your Copilot plan or model policy settings.`;
114
+ }
115
+ if (isCopilotTransientModelError(error)) {
116
+ return `GitHub Copilot rejected this model (HTTP 400) after retries: only part of its fleet currently serves this model id, even though /models advertises it. Try again in a few seconds or switch to a model Copilot serves fleet-wide (claude-opus-4.7, claude-sonnet-4.5, gpt-4.1).`;
117
+ }
118
+ return errorMessage;
119
+ }
120
+
121
+ function sanitizeDump(dump: RawHttpRequestDump): RawHttpRequestDump {
122
+ return {
123
+ ...dump,
124
+ headers: redactHeaders(dump.headers),
125
+ };
126
+ }
127
+
128
+ function redactHeaders(headers: Record<string, string> | undefined): Record<string, string> | undefined {
129
+ if (!headers) {
130
+ return undefined;
131
+ }
132
+
133
+ const redacted: Record<string, string> = {};
134
+ for (const [key, value] of Object.entries(headers)) {
135
+ if (SENSITIVE_HEADERS.includes(key.toLowerCase())) {
136
+ redacted[key] = "[redacted]";
137
+ continue;
138
+ }
139
+ redacted[key] = value;
140
+ }
141
+ return redacted;
142
+ }
143
+
144
+ function formatCapturedHttpError(captured: CapturedHttpErrorResponse | undefined): string | undefined {
145
+ if (!captured) return undefined;
146
+ const bodyText = captured.bodyText?.trim();
147
+ if (!bodyText) return undefined;
148
+ const payload = parseCapturedErrorPayload(captured);
149
+ if (!payload) return bodyText;
150
+
151
+ const errorPayload = getObjectProperty(payload, "error") ?? payload;
152
+ // {"error": "string"} — the error value is a plain string, not a nested object.
153
+ // Fall back to it when the structured fields ("message", etc.) are absent.
154
+ const stringError = errorPayload === payload ? getStringProperty(payload, "error") : undefined;
155
+ const message =
156
+ getStringProperty(errorPayload, "message") ?? getStringProperty(payload, "message") ?? stringError ?? bodyText;
157
+ const extras = [
158
+ getStringProperty(errorPayload, "type") ?? getStringProperty(payload, "type"),
159
+ getStringProperty(errorPayload, "param") ?? getStringProperty(payload, "param"),
160
+ getStringProperty(errorPayload, "code") ?? getStringProperty(payload, "code"),
161
+ ]
162
+ .filter(Boolean)
163
+ .map((value, index) => {
164
+ if (index === 0) return `type=${value}`;
165
+ if (index === 1) return `param=${value}`;
166
+ return `code=${value}`;
167
+ });
168
+ return extras.length > 0 ? `${message} (${extras.join(" ")})` : message;
169
+ }
170
+
171
+ function parseCapturedErrorPayload(captured: CapturedHttpErrorResponse): Record<string, unknown> | undefined {
172
+ if (isObject(captured.bodyJson)) {
173
+ return captured.bodyJson;
174
+ }
175
+ if (!captured.bodyText) return undefined;
176
+ try {
177
+ const parsed = JSON.parse(captured.bodyText);
178
+ return isObject(parsed) ? parsed : undefined;
179
+ } catch {
180
+ return undefined;
181
+ }
182
+ }
183
+
184
+ function getObjectProperty(value: Record<string, unknown>, key: string): Record<string, unknown> | undefined {
185
+ const property = value[key];
186
+ return isObject(property) ? property : undefined;
187
+ }
188
+
189
+ function getStringProperty(value: Record<string, unknown>, key: string): string | undefined {
190
+ const property = value[key];
191
+ return typeof property === "string" && property.trim().length > 0 ? property : undefined;
192
+ }
193
+
194
+ function isObject(value: unknown): value is Record<string, unknown> {
195
+ return typeof value === "object" && value !== null && !Array.isArray(value);
196
+ }