@linxiraos/pi-ai 1.0.0 → 1.0.2

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 (355) hide show
  1. package/CHANGELOG.md +123 -30
  2. package/package.json +135 -136
  3. package/src/auth/sqlite-credential-store.ts +0 -56
  4. package/src/auth-retry.ts +5 -4
  5. package/src/auth-storage.ts +122 -112
  6. package/src/dialect/owned-stream.ts +3 -0
  7. package/src/error/auth-classify.ts +7 -6
  8. package/src/error/aws.ts +5 -1
  9. package/src/error/flags.ts +25 -1
  10. package/src/error/provider.ts +6 -5
  11. package/src/error/rate-limit.ts +71 -1
  12. package/src/providers/amazon-bedrock.ts +38 -0
  13. package/src/providers/anthropic-client.ts +6 -10
  14. package/src/providers/anthropic.ts +107 -259
  15. package/src/providers/aws-credentials.ts +222 -29
  16. package/src/providers/azure-openai-responses.ts +1 -0
  17. package/src/providers/cursor/exec-modern.ts +1 -0
  18. package/src/providers/cursor-pi-args.ts +22 -0
  19. package/src/providers/cursor.ts +81 -1
  20. package/src/providers/devin.ts +3 -3
  21. package/src/providers/google-antigravity-forced-tool.md +1 -0
  22. package/src/providers/google-gemini-cli.ts +83 -40
  23. package/src/providers/google-shared.ts +22 -8
  24. package/src/providers/ollama.ts +15 -3
  25. package/src/providers/openai-codex/request-transformer.ts +47 -21
  26. package/src/providers/openai-codex-responses.ts +3 -3
  27. package/src/providers/openai-reasoning-fallback.ts +21 -8
  28. package/src/providers/openai-responses.ts +77 -12
  29. package/src/providers/openai-shared.ts +114 -15
  30. package/src/providers/pi-native-server.ts +1 -0
  31. package/src/providers/register-builtins.ts +18 -14
  32. package/src/providers/vision-guard.ts +15 -3
  33. package/src/registry/amazon-bedrock.ts +9 -14
  34. package/src/registry/api-key-login.ts +26 -12
  35. package/src/registry/aws.ts +16 -8
  36. package/src/registry/cloudflare-ai-gateway.ts +10 -29
  37. package/src/registry/kagi.ts +11 -29
  38. package/src/registry/litellm.ts +11 -29
  39. package/src/registry/llama-cpp.ts +11 -21
  40. package/src/registry/lm-studio.ts +9 -20
  41. package/src/registry/oauth/callback-server.ts +93 -5
  42. package/src/registry/oauth/google-antigravity.ts +99 -44
  43. package/src/registry/oauth/perplexity.ts +32 -4
  44. package/src/registry/parallel.ts +10 -28
  45. package/src/registry/tavily.ts +9 -27
  46. package/src/registry/together.ts +7 -3
  47. package/src/registry/vercel-ai-gateway.ts +10 -28
  48. package/src/registry/vllm.ts +11 -21
  49. package/src/stream.ts +459 -53
  50. package/src/types.ts +23 -0
  51. package/src/usage/alibaba-token-plan.ts +8 -15
  52. package/src/usage/claude.ts +12 -19
  53. package/src/usage/cursor.ts +179 -59
  54. package/src/usage/gemini.ts +4 -3
  55. package/src/usage/github-copilot.ts +3 -2
  56. package/src/usage/google-antigravity.ts +10 -17
  57. package/src/usage/kimi.ts +35 -17
  58. package/src/usage/minimax-code.ts +7 -27
  59. package/src/usage/openai-codex-reset.ts +3 -2
  60. package/src/usage/openai-codex.ts +5 -5
  61. package/src/usage/opencode-go.ts +170 -60
  62. package/src/usage/shared.ts +33 -10
  63. package/src/usage/synthetic.ts +4 -11
  64. package/src/usage/umans.ts +2 -2
  65. package/src/usage/xai-oauth.ts +62 -34
  66. package/src/usage/zai.ts +4 -6
  67. package/src/usage.ts +0 -20
  68. package/src/utils/aws-profile.ts +39 -1
  69. package/src/utils/block-symbols.ts +18 -0
  70. package/src/utils/empty-completion-retry.ts +4 -1
  71. package/src/utils/leaked-thinking-stream.ts +3 -0
  72. package/src/utils/openrouter-headers.ts +4 -4
  73. package/src/utils/thinking-loop.ts +17 -38
  74. package/dist/types/api-registry.d.ts +0 -30
  75. package/dist/types/auth/sqlite-credential-store.d.ts +0 -117
  76. package/dist/types/auth-broker/client.d.ts +0 -93
  77. package/dist/types/auth-broker/discover.d.ts +0 -39
  78. package/dist/types/auth-broker/index.d.ts +0 -7
  79. package/dist/types/auth-broker/refresher.d.ts +0 -25
  80. package/dist/types/auth-broker/remote-store.d.ts +0 -136
  81. package/dist/types/auth-broker/server.d.ts +0 -48
  82. package/dist/types/auth-broker/snapshot-cache.d.ts +0 -17
  83. package/dist/types/auth-broker/types.d.ts +0 -152
  84. package/dist/types/auth-broker/wire-schema-resource.d.ts +0 -53
  85. package/dist/types/auth-broker/wire-schemas.d.ts +0 -5
  86. package/dist/types/auth-gateway/http.d.ts +0 -56
  87. package/dist/types/auth-gateway/index.d.ts +0 -3
  88. package/dist/types/auth-gateway/server.d.ts +0 -36
  89. package/dist/types/auth-gateway/types.d.ts +0 -127
  90. package/dist/types/auth-retry.d.ts +0 -150
  91. package/dist/types/auth-storage.d.ts +0 -1258
  92. package/dist/types/dialect/anthropic.d.ts +0 -15
  93. package/dist/types/dialect/catalog.d.ts +0 -3
  94. package/dist/types/dialect/coercion.d.ts +0 -23
  95. package/dist/types/dialect/deepseek.d.ts +0 -14
  96. package/dist/types/dialect/demotion.d.ts +0 -30
  97. package/dist/types/dialect/examples.d.ts +0 -18
  98. package/dist/types/dialect/factory.d.ts +0 -3
  99. package/dist/types/dialect/fenced-thinking.d.ts +0 -53
  100. package/dist/types/dialect/gemini.d.ts +0 -17
  101. package/dist/types/dialect/gemma.d.ts +0 -15
  102. package/dist/types/dialect/glm.d.ts +0 -9
  103. package/dist/types/dialect/harmony.d.ts +0 -8
  104. package/dist/types/dialect/hermes.d.ts +0 -9
  105. package/dist/types/dialect/history.d.ts +0 -3
  106. package/dist/types/dialect/index.d.ts +0 -11
  107. package/dist/types/dialect/inventory.d.ts +0 -9
  108. package/dist/types/dialect/kimi.d.ts +0 -14
  109. package/dist/types/dialect/minimax.d.ts +0 -3
  110. package/dist/types/dialect/owned-stream.d.ts +0 -4
  111. package/dist/types/dialect/qwen3.d.ts +0 -9
  112. package/dist/types/dialect/rendering.d.ts +0 -54
  113. package/dist/types/dialect/thinking.d.ts +0 -6
  114. package/dist/types/dialect/types.d.ts +0 -68
  115. package/dist/types/dialect/xml.d.ts +0 -9
  116. package/dist/types/error/abort.d.ts +0 -14
  117. package/dist/types/error/auth-classify.d.ts +0 -20
  118. package/dist/types/error/auth.d.ts +0 -27
  119. package/dist/types/error/aws.d.ts +0 -27
  120. package/dist/types/error/classes.d.ts +0 -106
  121. package/dist/types/error/finalize.d.ts +0 -39
  122. package/dist/types/error/flags.d.ts +0 -94
  123. package/dist/types/error/format.d.ts +0 -20
  124. package/dist/types/error/gateway.d.ts +0 -20
  125. package/dist/types/error/index.d.ts +0 -14
  126. package/dist/types/error/oauth.d.ts +0 -43
  127. package/dist/types/error/provider.d.ts +0 -42
  128. package/dist/types/error/rate-limit.d.ts +0 -80
  129. package/dist/types/error/retryable.d.ts +0 -27
  130. package/dist/types/error/validation.d.ts +0 -32
  131. package/dist/types/index.d.ts +0 -51
  132. package/dist/types/provider-details.d.ts +0 -24
  133. package/dist/types/providers/amazon-bedrock.d.ts +0 -39
  134. package/dist/types/providers/anthropic-client.d.ts +0 -106
  135. package/dist/types/providers/anthropic-messages-server-schema.d.ts +0 -937
  136. package/dist/types/providers/anthropic-messages-server.d.ts +0 -17
  137. package/dist/types/providers/anthropic-wire.d.ts +0 -345
  138. package/dist/types/providers/anthropic.d.ts +0 -261
  139. package/dist/types/providers/aws-credentials.d.ts +0 -48
  140. package/dist/types/providers/aws-eventstream.d.ts +0 -39
  141. package/dist/types/providers/aws-sigv4.d.ts +0 -55
  142. package/dist/types/providers/azure-openai-responses.d.ts +0 -16
  143. package/dist/types/providers/bedrock-mantle.d.ts +0 -13
  144. package/dist/types/providers/claude-code-fingerprint.d.ts +0 -19
  145. package/dist/types/providers/cowork-fetch.d.ts +0 -3
  146. package/dist/types/providers/cursor/exec-modern.d.ts +0 -98
  147. package/dist/types/providers/cursor-pi-args.d.ts +0 -105
  148. package/dist/types/providers/cursor.d.ts +0 -221
  149. package/dist/types/providers/devin.d.ts +0 -12
  150. package/dist/types/providers/error-message.d.ts +0 -25
  151. package/dist/types/providers/github-copilot-headers.d.ts +0 -40
  152. package/dist/types/providers/gitlab-duo-workflow.d.ts +0 -254
  153. package/dist/types/providers/gitlab-duo.d.ts +0 -27
  154. package/dist/types/providers/google-auth.d.ts +0 -24
  155. package/dist/types/providers/google-gemini-cli.d.ts +0 -120
  156. package/dist/types/providers/google-shared.d.ts +0 -203
  157. package/dist/types/providers/google-types.d.ts +0 -155
  158. package/dist/types/providers/google-vertex.d.ts +0 -7
  159. package/dist/types/providers/google.d.ts +0 -4
  160. package/dist/types/providers/grammar.d.ts +0 -1
  161. package/dist/types/providers/kimi.d.ts +0 -27
  162. package/dist/types/providers/mock.d.ts +0 -179
  163. package/dist/types/providers/ollama.d.ts +0 -8
  164. package/dist/types/providers/openai-anthropic-shim.d.ts +0 -35
  165. package/dist/types/providers/openai-chat-server-schema.d.ts +0 -1311
  166. package/dist/types/providers/openai-chat-server.d.ts +0 -16
  167. package/dist/types/providers/openai-chat-wire.d.ts +0 -669
  168. package/dist/types/providers/openai-codex/request-transformer.d.ts +0 -109
  169. package/dist/types/providers/openai-codex/response-handler.d.ts +0 -26
  170. package/dist/types/providers/openai-codex-responses.d.ts +0 -238
  171. package/dist/types/providers/openai-completions.d.ts +0 -48
  172. package/dist/types/providers/openai-reasoning-fallback.d.ts +0 -25
  173. package/dist/types/providers/openai-responses-server-schema.d.ts +0 -1314
  174. package/dist/types/providers/openai-responses-server.d.ts +0 -17
  175. package/dist/types/providers/openai-responses-wire.d.ts +0 -6099
  176. package/dist/types/providers/openai-responses.d.ts +0 -131
  177. package/dist/types/providers/openai-shared.d.ts +0 -624
  178. package/dist/types/providers/pi-native-client.d.ts +0 -13
  179. package/dist/types/providers/pi-native-server.d.ts +0 -69
  180. package/dist/types/providers/register-builtins.d.ts +0 -37
  181. package/dist/types/providers/synthetic.d.ts +0 -26
  182. package/dist/types/providers/transform-messages.d.ts +0 -32
  183. package/dist/types/providers/vision-guard.d.ts +0 -20
  184. package/dist/types/registry/aiand.d.ts +0 -7
  185. package/dist/types/registry/aimlapi.d.ts +0 -4
  186. package/dist/types/registry/alibaba-coding-plan.d.ts +0 -8
  187. package/dist/types/registry/alibaba-token-plan.d.ts +0 -18
  188. package/dist/types/registry/amazon-bedrock.d.ts +0 -5
  189. package/dist/types/registry/anthropic.d.ts +0 -10
  190. package/dist/types/registry/api-key-login.d.ts +0 -42
  191. package/dist/types/registry/api-key-validation.d.ts +0 -43
  192. package/dist/types/registry/aws.d.ts +0 -13
  193. package/dist/types/registry/azure.d.ts +0 -4
  194. package/dist/types/registry/baseten.d.ts +0 -7
  195. package/dist/types/registry/bedrock-mantle.d.ts +0 -22
  196. package/dist/types/registry/cerebras.d.ts +0 -7
  197. package/dist/types/registry/cloudflare-ai-gateway.d.ts +0 -13
  198. package/dist/types/registry/coreweave.d.ts +0 -7
  199. package/dist/types/registry/cursor.d.ts +0 -7
  200. package/dist/types/registry/deepseek.d.ts +0 -8
  201. package/dist/types/registry/derived.d.ts +0 -5
  202. package/dist/types/registry/devin.d.ts +0 -8
  203. package/dist/types/registry/exa.d.ts +0 -8
  204. package/dist/types/registry/firepass.d.ts +0 -16
  205. package/dist/types/registry/fireworks.d.ts +0 -7
  206. package/dist/types/registry/github-copilot.d.ts +0 -7
  207. package/dist/types/registry/gitlab-duo-workflow.d.ts +0 -10
  208. package/dist/types/registry/gitlab-duo.d.ts +0 -9
  209. package/dist/types/registry/gmi-cloud.d.ts +0 -7
  210. package/dist/types/registry/google-antigravity.d.ts +0 -9
  211. package/dist/types/registry/google-gemini-cli.d.ts +0 -9
  212. package/dist/types/registry/google-vertex.d.ts +0 -5
  213. package/dist/types/registry/google.d.ts +0 -4
  214. package/dist/types/registry/groq.d.ts +0 -4
  215. package/dist/types/registry/huggingface.d.ts +0 -7
  216. package/dist/types/registry/index.d.ts +0 -4
  217. package/dist/types/registry/kagi.d.ts +0 -14
  218. package/dist/types/registry/kilo.d.ts +0 -7
  219. package/dist/types/registry/kimi-code.d.ts +0 -7
  220. package/dist/types/registry/litellm.d.ts +0 -13
  221. package/dist/types/registry/llama-cpp.d.ts +0 -8
  222. package/dist/types/registry/lm-studio.d.ts +0 -8
  223. package/dist/types/registry/meta.d.ts +0 -7
  224. package/dist/types/registry/minimax-code-cn.d.ts +0 -6
  225. package/dist/types/registry/minimax-code.d.ts +0 -6
  226. package/dist/types/registry/minimax.d.ts +0 -4
  227. package/dist/types/registry/mistral.d.ts +0 -4
  228. package/dist/types/registry/moonshot.d.ts +0 -7
  229. package/dist/types/registry/nanogpt.d.ts +0 -7
  230. package/dist/types/registry/novita.d.ts +0 -6
  231. package/dist/types/registry/nvidia.d.ts +0 -7
  232. package/dist/types/registry/oauth/anthropic-constants.d.ts +0 -12
  233. package/dist/types/registry/oauth/anthropic.d.ts +0 -24
  234. package/dist/types/registry/oauth/callback-server.d.ts +0 -74
  235. package/dist/types/registry/oauth/cursor.d.ts +0 -16
  236. package/dist/types/registry/oauth/device-code.d.ts +0 -25
  237. package/dist/types/registry/oauth/devin.d.ts +0 -5
  238. package/dist/types/registry/oauth/github-copilot.d.ts +0 -30
  239. package/dist/types/registry/oauth/gitlab-duo-workflow.d.ts +0 -6
  240. package/dist/types/registry/oauth/gitlab-duo.d.ts +0 -3
  241. package/dist/types/registry/oauth/google-antigravity.d.ts +0 -11
  242. package/dist/types/registry/oauth/google-gemini-cli.d.ts +0 -22
  243. package/dist/types/registry/oauth/google-oauth-shared.d.ts +0 -56
  244. package/dist/types/registry/oauth/index.d.ts +0 -45
  245. package/dist/types/registry/oauth/kimi.d.ts +0 -21
  246. package/dist/types/registry/oauth/minimax-code.d.ts +0 -27
  247. package/dist/types/registry/oauth/openai-codex.d.ts +0 -33
  248. package/dist/types/registry/oauth/opencode.d.ts +0 -18
  249. package/dist/types/registry/oauth/perplexity.d.ts +0 -9
  250. package/dist/types/registry/oauth/pkce.d.ts +0 -8
  251. package/dist/types/registry/oauth/types.d.ts +0 -87
  252. package/dist/types/registry/oauth/wafer.d.ts +0 -1
  253. package/dist/types/registry/oauth/xai-oauth.d.ts +0 -46
  254. package/dist/types/registry/oauth/xiaomi.d.ts +0 -25
  255. package/dist/types/registry/oauth/zai.d.ts +0 -25
  256. package/dist/types/registry/ollama-cloud.d.ts +0 -7
  257. package/dist/types/registry/ollama.d.ts +0 -12
  258. package/dist/types/registry/openai-codex-device.d.ts +0 -8
  259. package/dist/types/registry/openai-codex.d.ts +0 -9
  260. package/dist/types/registry/openai.d.ts +0 -4
  261. package/dist/types/registry/opencode-go.d.ts +0 -6
  262. package/dist/types/registry/opencode-zen.d.ts +0 -6
  263. package/dist/types/registry/openrouter.d.ts +0 -13
  264. package/dist/types/registry/parallel.d.ts +0 -14
  265. package/dist/types/registry/perplexity.d.ts +0 -7
  266. package/dist/types/registry/qianfan.d.ts +0 -7
  267. package/dist/types/registry/qwen-portal.d.ts +0 -7
  268. package/dist/types/registry/registry.d.ts +0 -367
  269. package/dist/types/registry/sakana.d.ts +0 -7
  270. package/dist/types/registry/siliconflow-cn.d.ts +0 -7
  271. package/dist/types/registry/siliconflow.d.ts +0 -7
  272. package/dist/types/registry/synthetic.d.ts +0 -6
  273. package/dist/types/registry/tavily.d.ts +0 -14
  274. package/dist/types/registry/together.d.ts +0 -6
  275. package/dist/types/registry/types.d.ts +0 -75
  276. package/dist/types/registry/umans.d.ts +0 -7
  277. package/dist/types/registry/venice.d.ts +0 -13
  278. package/dist/types/registry/vercel-ai-gateway.d.ts +0 -7
  279. package/dist/types/registry/vllm.d.ts +0 -7
  280. package/dist/types/registry/wafer-serverless.d.ts +0 -6
  281. package/dist/types/registry/xai-oauth.d.ts +0 -7
  282. package/dist/types/registry/xai.d.ts +0 -7
  283. package/dist/types/registry/xiaomi-token-plan-ams.d.ts +0 -6
  284. package/dist/types/registry/xiaomi-token-plan-cn.d.ts +0 -6
  285. package/dist/types/registry/xiaomi-token-plan-sgp.d.ts +0 -6
  286. package/dist/types/registry/xiaomi.d.ts +0 -6
  287. package/dist/types/registry/zai.d.ts +0 -15
  288. package/dist/types/registry/zenmux.d.ts +0 -7
  289. package/dist/types/registry/zhipu-coding-plan.d.ts +0 -7
  290. package/dist/types/stream.d.ts +0 -46
  291. package/dist/types/types.d.ts +0 -1064
  292. package/dist/types/usage/alibaba-token-plan.d.ts +0 -3
  293. package/dist/types/usage/claude.d.ts +0 -4
  294. package/dist/types/usage/cursor.d.ts +0 -4
  295. package/dist/types/usage/gemini.d.ts +0 -2
  296. package/dist/types/usage/github-copilot.d.ts +0 -7
  297. package/dist/types/usage/google-antigravity.d.ts +0 -15
  298. package/dist/types/usage/kimi.d.ts +0 -2
  299. package/dist/types/usage/minimax-code.d.ts +0 -3
  300. package/dist/types/usage/ollama.d.ts +0 -5
  301. package/dist/types/usage/openai-codex-base-url.d.ts +0 -18
  302. package/dist/types/usage/openai-codex-reset.d.ts +0 -88
  303. package/dist/types/usage/openai-codex.d.ts +0 -10
  304. package/dist/types/usage/opencode-go.d.ts +0 -2
  305. package/dist/types/usage/shared.d.ts +0 -1
  306. package/dist/types/usage/synthetic.d.ts +0 -2
  307. package/dist/types/usage/umans.d.ts +0 -2
  308. package/dist/types/usage/xai-oauth.d.ts +0 -12
  309. package/dist/types/usage/zai.d.ts +0 -3
  310. package/dist/types/usage.d.ts +0 -527
  311. package/dist/types/utils/abort.d.ts +0 -25
  312. package/dist/types/utils/anthropic-auth.d.ts +0 -35
  313. package/dist/types/utils/aws-profile.d.ts +0 -17
  314. package/dist/types/utils/block-symbols.d.ts +0 -62
  315. package/dist/types/utils/deterministic-id.d.ts +0 -16
  316. package/dist/types/utils/empty-completion-retry.d.ts +0 -21
  317. package/dist/types/utils/event-stream.d.ts +0 -39
  318. package/dist/types/utils/foundry.d.ts +0 -1
  319. package/dist/types/utils/google-validation.d.ts +0 -2
  320. package/dist/types/utils/harmony-leak.d.ts +0 -135
  321. package/dist/types/utils/http-inspector.d.ts +0 -49
  322. package/dist/types/utils/idle-iterator.d.ts +0 -149
  323. package/dist/types/utils/leaked-thinking-stream.d.ts +0 -33
  324. package/dist/types/utils/openai-http.d.ts +0 -48
  325. package/dist/types/utils/openrouter-headers.d.ts +0 -1
  326. package/dist/types/utils/parse-bind.d.ts +0 -23
  327. package/dist/types/utils/provider-response.d.ts +0 -3
  328. package/dist/types/utils/proxy.d.ts +0 -39
  329. package/dist/types/utils/request-debug.d.ts +0 -29
  330. package/dist/types/utils/retry-after.d.ts +0 -4
  331. package/dist/types/utils/retry.d.ts +0 -14
  332. package/dist/types/utils/schema/adapt.d.ts +0 -24
  333. package/dist/types/utils/schema/compatibility.d.ts +0 -30
  334. package/dist/types/utils/schema/dereference.d.ts +0 -11
  335. package/dist/types/utils/schema/draft.d.ts +0 -10
  336. package/dist/types/utils/schema/equality.d.ts +0 -4
  337. package/dist/types/utils/schema/fields.d.ts +0 -54
  338. package/dist/types/utils/schema/index.d.ts +0 -14
  339. package/dist/types/utils/schema/json-schema-validator.d.ts +0 -20
  340. package/dist/types/utils/schema/meta-validator.d.ts +0 -2
  341. package/dist/types/utils/schema/normalize.d.ts +0 -153
  342. package/dist/types/utils/schema/spill.d.ts +0 -8
  343. package/dist/types/utils/schema/stamps.d.ts +0 -17
  344. package/dist/types/utils/schema/strict-tool-validation.d.ts +0 -16
  345. package/dist/types/utils/schema/types.d.ts +0 -4
  346. package/dist/types/utils/schema/typescript.d.ts +0 -24
  347. package/dist/types/utils/schema/wire.d.ts +0 -52
  348. package/dist/types/utils/sdk-stream-timeout.d.ts +0 -33
  349. package/dist/types/utils/sse-debug.d.ts +0 -5
  350. package/dist/types/utils/stream-markup-healing.d.ts +0 -87
  351. package/dist/types/utils/thinking-loop.d.ts +0 -102
  352. package/dist/types/utils/tool-call-loop-guard.d.ts +0 -26
  353. package/dist/types/utils/tool-choice.d.ts +0 -52
  354. package/dist/types/utils/validation.d.ts +0 -28
  355. package/dist/types/utils.d.ts +0 -57
@@ -7,7 +7,6 @@ import { createHash, randomBytes, randomUUID } from "node:crypto";
7
7
  import { scheduler } from "node:timers/promises";
8
8
  import { calculateCost } from "@linxiraos/pi-catalog/models";
9
9
  import {
10
- ANTIGRAVITY_SYSTEM_INSTRUCTION,
11
10
  getAntigravityModelWireProfile,
12
11
  getAntigravityUserAgent,
13
12
  getGeminiCliHeaders,
@@ -31,11 +30,12 @@ import { normalizeSystemPrompts } from "../utils";
31
30
  import { AssistantMessageEventStream } from "../utils/event-stream";
32
31
  import { extractGoogleValidationUrl, formatGoogleValidationRequiredMessage } from "../utils/google-validation";
33
32
  import type { RawHttpRequestDump } from "../utils/http-inspector";
34
- import { armPreResponseTimeout, getStreamFirstEventTimeoutMs } from "../utils/idle-iterator";
33
+ import { armPreResponseTimeout, getStreamFirstEventTimeoutMs, iterateWithIdleTimeout } from "../utils/idle-iterator";
35
34
  // Refresh is the sole responsibility of AuthStorage (broker-aware, single-flighted);
36
35
  // the stream provider trusts the access token threaded through `options.apiKey`.
37
36
  import { normalizeSchemaForCCA } from "../utils/schema";
38
37
  import { StreamMarkupHealing, type StreamMarkupHealingEvent } from "../utils/stream-markup-healing";
38
+ import forcedToolDirective from "./google-antigravity-forced-tool.md" with { type: "text" };
39
39
  import type { Content, FunctionCallingConfigMode, ThinkingConfig } from "./google-shared";
40
40
  import {
41
41
  convertMessages,
@@ -315,16 +315,12 @@ const ANTIGRAVITY_DAILY_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com";
315
315
  const ANTIGRAVITY_SANDBOX_ENDPOINT = "https://daily-cloudcode-pa.sandbox.googleapis.com";
316
316
  const ANTIGRAVITY_ENDPOINT_FALLBACKS = [ANTIGRAVITY_DAILY_ENDPOINT, ANTIGRAVITY_SANDBOX_ENDPOINT] as const;
317
317
 
318
- export {
319
- ANTIGRAVITY_SYSTEM_INSTRUCTION,
320
- getAntigravityUserAgent,
321
- getGeminiCliHeaders,
322
- getGeminiCliUserAgent,
323
- } from "@linxiraos/pi-catalog/wire/gemini-headers";
324
-
325
318
  // Retry configuration
326
319
  const MAX_RETRIES = 3;
327
320
  const BASE_DELAY_MS = 1000;
321
+ const FLASH_FIRST_EVENT_TIMEOUT_MS = 60_000;
322
+ const DEFAULT_FIRST_EVENT_TIMEOUT_MS = 300_000;
323
+ const FIRST_EVENT_TIMEOUT_ERROR = "Cloud Code Assist stream timed out while waiting for the first event";
328
324
  const RATE_LIMIT_BUDGET_MS = 5 * 60 * 1000;
329
325
  const CLAUDE_THINKING_BETA_HEADER = "interleaved-thinking-2025-05-14";
330
326
  const GOOGLE_GEMINI_REFRESH_SKEW_MS = 60_000;
@@ -338,11 +334,6 @@ function needsClaudeThinkingBetaHeader(model: Model<"google-gemini-cli">): boole
338
334
  return model.provider === "google-antigravity" && model.id.startsWith("claude-") && model.reasoning;
339
335
  }
340
336
 
341
- function shouldInjectAntigravitySystemInstruction(modelId: string): boolean {
342
- const normalized = modelId.toLowerCase();
343
- return normalized.includes("claude") || normalized.includes("gemini-3");
344
- }
345
-
346
337
  const optionalCredentialString = type("unknown").pipe(raw => {
347
338
  const out = type("string")(raw);
348
339
  return out instanceof type.errors ? undefined : out;
@@ -616,17 +607,23 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
616
607
  headers: requestHeaders,
617
608
  };
618
609
 
619
- // Direct callers that skip `register-builtins` (which installs the
620
- // iterator-level watchdog) need a pre-response timer alongside
621
- // `timeout: false`; otherwise a stalled Cloud Code Assist proxy
622
- // would hang forever. Floor matches the lazy wrapper's 5min default.
610
+ // The provider owns the first-event watchdog so a silent successful
611
+ // response can fail over to the alternate Antigravity endpoint before
612
+ // anything user-visible has streamed. Flash should not inherit the
613
+ // five-minute allowance reserved for cold Pro reasoning starts.
623
614
  const firstEventTimeoutMs =
624
- options?.streamFirstEventTimeoutMs ?? getStreamFirstEventTimeoutMs(undefined, 300_000);
615
+ options?.streamFirstEventTimeoutMs ??
616
+ getStreamFirstEventTimeoutMs(
617
+ undefined,
618
+ model.id.includes("flash") ? FLASH_FIRST_EVENT_TIMEOUT_MS : DEFAULT_FIRST_EVENT_TIMEOUT_MS,
619
+ );
625
620
  const callerSignal = options?.signal;
626
621
  const toolNames = new Set(context.tools?.map(t => t.name) ?? []);
627
622
  const isFlashLeakModel = model.id.includes("flash");
628
623
 
629
624
  let started = false;
625
+ // Once any stream event starts, the endpoint is committed downstream.
626
+ // Failover remains safe only while `started` is false.
630
627
  let sawFinishReason = false;
631
628
  let lastResponseId: string | undefined;
632
629
  const ensureStarted = () => {
@@ -653,7 +650,9 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
653
650
  sawFinishReason = false;
654
651
  };
655
652
 
656
- const streamResponse = async (activeResponse: Response): Promise<boolean> => {
653
+ const streamResponse = async (
654
+ activeResponse: Response,
655
+ ): Promise<{ meaningful: boolean; strippedPlanningLeak: boolean }> => {
657
656
  if (!activeResponse.body) {
658
657
  throw new AIError.ProviderResponseError("No response body", {
659
658
  provider: model.provider,
@@ -673,6 +672,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
673
672
  let isBuffering = false;
674
673
  let textBuffer = "";
675
674
  let bufferedTextSignature: string | undefined;
675
+ let strippedPlanningLeak = false;
676
676
 
677
677
  const endCurrentBlock = (): void => {
678
678
  if (!currentBlock) return;
@@ -755,11 +755,24 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
755
755
  }
756
756
  };
757
757
 
758
- for await (const chunk of readSseJson<CloudCodeAssistResponseChunk>(
759
- activeResponse.body!,
760
- options?.signal,
761
- event => options?.onSseEvent?.({ event: event.event, data: event.data, raw: [...event.raw] }, model),
762
- )) {
758
+ const responseAbortController = new AbortController();
759
+ const responseSignal = options?.signal
760
+ ? AbortSignal.any([options.signal, responseAbortController.signal])
761
+ : responseAbortController.signal;
762
+ const chunks = iterateWithIdleTimeout(
763
+ readSseJson<CloudCodeAssistResponseChunk>(activeResponse.body, responseSignal, event =>
764
+ options?.onSseEvent?.({ event: event.event, data: event.data, raw: [...event.raw] }, model),
765
+ ),
766
+ {
767
+ firstItemTimeoutMs: firstEventTimeoutMs,
768
+ errorMessage: FIRST_EVENT_TIMEOUT_ERROR,
769
+ firstItemErrorMessage: FIRST_EVENT_TIMEOUT_ERROR,
770
+ onFirstItemTimeout: () =>
771
+ responseAbortController.abort(new AIError.StreamTimeoutError(FIRST_EVENT_TIMEOUT_ERROR)),
772
+ abortSignal: options?.signal,
773
+ },
774
+ );
775
+ for await (const chunk of chunks) {
763
776
  if (chunk.error) {
764
777
  const detail = chunk.error.message || chunk.error.status || "unknown error";
765
778
  const message = `Cloud Code Assist stream error: ${detail}`;
@@ -815,6 +828,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
815
828
  if (isBuffering) {
816
829
  const buffered = consumePlanningBuffer(textBuffer, toolNames);
817
830
  if (buffered.kind !== "incomplete") {
831
+ if (buffered.kind === "leak") strippedPlanningLeak = true;
818
832
  const visibleSignature = bufferedTextSignature;
819
833
  isBuffering = false;
820
834
  textBuffer = "";
@@ -895,6 +909,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
895
909
  const buffered = consumePlanningBuffer(textBuffer, toolNames, true);
896
910
 
897
911
  if (buffered.kind !== "incomplete") {
912
+ if (buffered.kind === "leak") strippedPlanningLeak = true;
898
913
  feedVisibleText(buffered.visibleText, bufferedTextSignature);
899
914
  }
900
915
  bufferedTextSignature = undefined;
@@ -905,10 +920,18 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
905
920
  flushVisibleText(bufferedTextSignature);
906
921
  endCurrentBlock();
907
922
 
908
- return hasMeaningfulGoogleContent(output);
923
+ return {
924
+ meaningful: hasMeaningfulGoogleContent(output),
925
+ strippedPlanningLeak,
926
+ };
909
927
  };
910
928
 
911
929
  let receivedContent = false;
930
+ const hasThinkingOutput = () =>
931
+ output.content.some(
932
+ block =>
933
+ block.type === "thinking" && (block.thinking.trim().length > 0 || Boolean(block.thinkingSignature)),
934
+ );
912
935
 
913
936
  for (let i = 0; i < endpoints.length; i++) {
914
937
  const endpoint = endpoints[i];
@@ -998,11 +1021,26 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
998
1021
  }
999
1022
 
1000
1023
  const streamed = await streamResponse(currentResponse);
1001
- if (output.stopReason !== "stop" || streamed) {
1002
- receivedContent = streamed;
1024
+ // Eventless silence may fail over to the alternate Antigravity
1025
+ // endpoint. Once thinking has streamed, the endpoint is already
1026
+ // committed downstream; Advisor mode may accept that silence,
1027
+ // while normal sessions surface it to final-output recovery.
1028
+ const thoughtOnly = hasThinkingOutput();
1029
+ const acceptedSilence =
1030
+ options?.acceptEmptyResponse === true &&
1031
+ !streamed.strippedPlanningLeak &&
1032
+ (isLastEndpoint || thoughtOnly);
1033
+ if (output.stopReason !== "stop" || streamed.meaningful || acceptedSilence) {
1034
+ receivedContent = streamed.meaningful || acceptedSilence;
1003
1035
  break;
1004
1036
  }
1005
1037
 
1038
+ // A thought-only STOP is a complete provider response, not a
1039
+ // transiently empty transport. Replaying the identical request
1040
+ // burns another full reasoning pass; let session recovery add
1041
+ // an explicit final-output reminder instead.
1042
+ if (thoughtOnly) break;
1043
+
1006
1044
  if (emptyAttempt < MAX_EMPTY_STREAM_RETRIES) {
1007
1045
  resetOutput();
1008
1046
  }
@@ -1016,10 +1054,16 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
1016
1054
  }
1017
1055
 
1018
1056
  if (!receivedContent) {
1019
- throw new AIError.ProviderResponseError("Cloud Code Assist API returned an empty response", {
1020
- provider: model.provider,
1021
- kind: "empty-body",
1022
- });
1057
+ const thoughtOnly = hasThinkingOutput();
1058
+ throw new AIError.ProviderResponseError(
1059
+ thoughtOnly
1060
+ ? "Cloud Code Assist API returned a thought-only response without final output"
1061
+ : "Cloud Code Assist API returned an empty response",
1062
+ {
1063
+ provider: model.provider,
1064
+ kind: thoughtOnly ? "empty-output" : "empty-body",
1065
+ },
1066
+ );
1023
1067
  }
1024
1068
 
1025
1069
  if (options?.signal?.aborted) {
@@ -1285,14 +1329,6 @@ export function buildRequest(
1285
1329
  };
1286
1330
  }
1287
1331
 
1288
- if (isAntigravity && shouldInjectAntigravitySystemInstruction(model.id)) {
1289
- const existingParts = request.systemInstruction?.parts ?? [];
1290
- request.systemInstruction = {
1291
- role: "user",
1292
- parts: [{ text: ANTIGRAVITY_SYSTEM_INSTRUCTION }, ...existingParts],
1293
- };
1294
- }
1295
-
1296
1332
  if (context.tools && context.tools.length > 0) {
1297
1333
  const convertedTools = convertTools(context.tools, model);
1298
1334
  request.tools = isAntigravity ? normalizeAntigravityTools(convertedTools) : convertedTools;
@@ -1313,6 +1349,13 @@ export function buildRequest(
1313
1349
  },
1314
1350
  };
1315
1351
  }
1352
+ // Cloud Code Assist drops `toolConfig` on Antigravity's Gemini routes:
1353
+ // the backend answers in text under `mode: "ANY"` and still emits calls
1354
+ // under `"NONE"`. Claude routes implement it, so only Gemini needs the
1355
+ // forced choice restated in the transcript.
1356
+ if (isAntigravity && !isClaudeModel(model.id) && request.toolConfig?.functionCallingConfig.mode === "ANY") {
1357
+ contents.push({ role: "user", parts: [{ text: forcedToolDirective }] });
1358
+ }
1316
1359
  }
1317
1360
  // Antigravity's default tool mode is VALIDATED (verified for Gemini and
1318
1361
  // Claude); an explicit non-auto tool choice above wins.
@@ -233,6 +233,8 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
233
233
  const parts: Part[] = [];
234
234
  // Check if message is from same provider and model - only then keep thinking blocks
235
235
  const isSameProviderAndModel = msg.provider === model.provider && msg.model === model.id;
236
+ const dropsUnsignedThinking =
237
+ model.provider === "google-antigravity" && model.id.toLowerCase().includes("claude");
236
238
 
237
239
  for (const block of msg.content) {
238
240
  if (block.type === "text") {
@@ -247,6 +249,7 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
247
249
  // Skip empty thinking blocks
248
250
  if (!block.thinking || block.thinking.trim() === "") continue;
249
251
  const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.thinkingSignature);
252
+ if (dropsUnsignedThinking && !thoughtSignature) continue;
250
253
  if (thoughtSignature) {
251
254
  parts.push({
252
255
  thought: true,
@@ -858,13 +861,18 @@ export function buildGoogleGenerateContentParams<T extends "google-generative-ai
858
861
  config.toolConfig = undefined;
859
862
  }
860
863
 
861
- if (options.thinking?.enabled && model.reasoning) {
862
- const cfg: ThinkingConfig = { includeThoughts: !options.hideThinkingSummary };
863
- if (options.thinking.level !== undefined) {
864
- // GoogleThinkingLevel mirrors the SDK's `ThinkingLevel` string enum values 1:1.
865
- cfg.thinkingLevel = options.thinking.level as ThinkingLevel;
866
- } else if (options.thinking.budgetTokens !== undefined) {
867
- cfg.thinkingBudget = options.thinking.budgetTokens;
864
+ const thinking = options.thinking;
865
+ if (
866
+ thinking &&
867
+ model.reasoning &&
868
+ (thinking.enabled || thinking.level !== undefined || thinking.budgetTokens !== undefined)
869
+ ) {
870
+ const cfg: ThinkingConfig = { includeThoughts: thinking.enabled && !options.hideThinkingSummary };
871
+ if (thinking.level !== undefined) {
872
+ // GoogleThinkingLevel mirrors the SDK's ThinkingLevel string enum values 1:1.
873
+ cfg.thinkingLevel = thinking.level as ThinkingLevel;
874
+ } else if (thinking.budgetTokens !== undefined) {
875
+ cfg.thinkingBudget = thinking.budgetTokens;
868
876
  }
869
877
  config.thinkingConfig = cfg;
870
878
  }
@@ -1031,7 +1039,13 @@ export function streamGoogleGenAI<T extends "google-generative-ai" | "google-ver
1031
1039
  },
1032
1040
  });
1033
1041
 
1034
- if (output.stopReason !== "stop" || hasMeaningfulGoogleContent(output)) break;
1042
+ if (
1043
+ output.stopReason !== "stop" ||
1044
+ hasMeaningfulGoogleContent(output) ||
1045
+ options?.acceptEmptyResponse === true
1046
+ ) {
1047
+ break;
1048
+ }
1035
1049
  if (emptyAttempt >= MAX_EMPTY_STREAM_RETRIES) {
1036
1050
  throw new AIError.ProviderResponseError(
1037
1051
  `Google API returned an empty response (finishReason STOP with no content) after ${MAX_EMPTY_STREAM_RETRIES + 1} attempts`,
@@ -328,15 +328,27 @@ function createChatBody(model: Model<"ollama-chat">, context: Context, options:
328
328
  const toolChoice = mapToolChoice(options?.toolChoice);
329
329
  const selectedTools = selectToolsForToolChoice(context.tools, options?.toolChoice);
330
330
  const tools = convertTools(selectedTools);
331
+ const runtimeOptions: { num_predict?: number; temperature?: number; top_p?: number } = {};
332
+ let hasRuntimeOptions = false;
333
+ if (options?.maxTokens !== undefined && !model.omitMaxOutputTokens) {
334
+ runtimeOptions.num_predict = resolveNumPredict(model, options.maxTokens);
335
+ hasRuntimeOptions = true;
336
+ }
337
+ if (options?.temperature !== undefined) {
338
+ runtimeOptions.temperature = options.temperature;
339
+ hasRuntimeOptions = true;
340
+ }
341
+ if (options?.topP !== undefined) {
342
+ runtimeOptions.top_p = options.topP;
343
+ hasRuntimeOptions = true;
344
+ }
331
345
  return {
332
346
  model: model.id,
333
347
  messages: convertMessages(model, context),
334
348
  ...(tools ? { tools } : {}),
335
349
  ...(think !== undefined ? { think } : {}),
336
350
  ...(toolChoice !== undefined ? { tool_choice: toolChoice } : {}),
337
- ...(options?.maxTokens !== undefined && !model.omitMaxOutputTokens
338
- ? { options: { num_predict: resolveNumPredict(model, options.maxTokens) } }
339
- : {}),
351
+ ...(hasRuntimeOptions ? { options: runtimeOptions } : {}),
340
352
  stream: true,
341
353
  };
342
354
  }
@@ -32,6 +32,8 @@ export interface ReasoningConfig {
32
32
  export interface CodexRequestOptions {
33
33
  /** User-facing effort; maps 1:1 onto the wire tier of the same name. */
34
34
  reasoningEffort?: CodexCallerEffort | "none";
35
+ /** Suppress native reasoning by sending `reasoning.effort: "none"`. */
36
+ reasoningOff?: boolean;
35
37
  reasoningSummary?: ReasoningConfig["summary"] | null;
36
38
  /** Explicit `reasoning.context` override. Omitted by default; Responses Lite forces `all_turns` as required by that transport. */
37
39
  reasoningContext?: CodexReasoningContext;
@@ -109,6 +111,22 @@ export function resolveCodexResponsesLite(
109
111
  return model.useResponsesLite === true;
110
112
  }
111
113
 
114
+ /**
115
+ * Whether to request `stream_options.reasoning_summary_delivery =
116
+ * "sequential_cutoff"` (codex-rs `concurrent_reasoning_summaries`), enabled by
117
+ * `PI_CODEX_CONCURRENT_SUMMARIES=1`.
118
+ *
119
+ * Off by default because the mode cancels summary sections still in flight when
120
+ * the reasoning item closes: measured over 12 interleaved turns it halved
121
+ * visible thinking (0.83 vs 1.67 summary parts, 37 vs 69 chars per turn) and
122
+ * produced no summary at all on 3 of 12 turns. codex-rs ships it disabled too
123
+ * (`Stage::UnderDevelopment`, `default_enabled: false`).
124
+ */
125
+ function concurrentSummariesEnabled(): boolean {
126
+ const env = $env.PI_CODEX_CONCURRENT_SUMMARIES?.trim().toLowerCase();
127
+ return env === "1" || env === "true";
128
+ }
129
+
112
130
  /**
113
131
  * Clamp a user-facing effort to the model's ladder, then remap to the wire
114
132
  * tier. User efforts map 1:1 onto wire tiers; the effort map only covers
@@ -145,12 +163,14 @@ function getReasoningConfig(
145
163
  const config: ReasoningConfig = {
146
164
  effort: effort === "none" ? "none" : mapCodexWireEffort(model, effort),
147
165
  };
148
- if (
149
- options.reasoningSummary !== undefined &&
150
- options.reasoningSummary !== null &&
151
- supportsCodexReasoningSummary(model.id)
152
- ) {
153
- config.summary = options.reasoningSummary;
166
+ // The backend only emits reasoning summaries when `reasoning.summary` is
167
+ // present: omitting it yields zero `response.reasoning_summary_text.*`
168
+ // events (measured against gpt-5.5, gpt-5.6-sol and gpt-5.6-terra). So
169
+ // `undefined` means "default on" — matching `applyResponsesCompatPolicy`
170
+ // on the plain Responses path — and only an explicit `null` (the caller
171
+ // hiding thinking) opts out.
172
+ if (options.reasoningSummary !== null && supportsCodexReasoningSummary(model.id)) {
173
+ config.summary = options.reasoningSummary ?? "auto";
154
174
  }
155
175
  return config;
156
176
  }
@@ -436,20 +456,25 @@ export async function transformRequestBody(
436
456
  applyCodexResponsesLiteShape(body);
437
457
  }
438
458
 
439
- if (options.reasoningEffort !== undefined || responsesLite) {
440
- const reasoningConfig =
441
- options.reasoningEffort !== undefined ? getReasoningConfig(model, options.reasoningEffort, options) : {};
459
+ if (options.reasoningOff || options.reasoningEffort !== undefined || responsesLite) {
460
+ const reasoningConfig: Partial<ReasoningConfig> = options.reasoningOff
461
+ ? { effort: "none" }
462
+ : options.reasoningEffort !== undefined
463
+ ? getReasoningConfig(model, options.reasoningEffort, options)
464
+ : {};
442
465
  body.reasoning = {
443
466
  ...body.reasoning,
444
467
  ...reasoningConfig,
445
468
  };
446
- // Responses Lite requires `all_turns`; the full transport leaves context to the server unless explicitly set.
447
- const context = responsesLite ? "all_turns" : options.reasoningContext;
448
- if (context !== undefined) {
449
- if (context === "all_turns" && !supportsAllTurnsReasoningContext(model.id)) {
469
+ // Lite requires `all_turns` even for opaque/codenamed model ids. Only explicit
470
+ // full-transport overrides are gated by the known model wire generation.
471
+ if (responsesLite) {
472
+ body.reasoning.context = "all_turns";
473
+ } else if (options.reasoningContext !== undefined) {
474
+ if (options.reasoningContext === "all_turns" && !supportsAllTurnsReasoningContext(model.id)) {
450
475
  delete body.reasoning.context;
451
476
  } else {
452
- body.reasoning.context = context;
477
+ body.reasoning.context = options.reasoningContext;
453
478
  }
454
479
  }
455
480
  } else {
@@ -458,16 +483,17 @@ export async function transformRequestBody(
458
483
  // Catalog pro aliases (`gpt-5.6-*-pro`): applied after the effort branch so
459
484
  // the mode is sent even when no effort is set (the branch above deletes
460
485
  // `body.reasoning` in that case) — mode and effort are independent fields.
461
- if (model.reasoningMode) {
486
+ if (model.reasoningMode && !options.reasoningOff) {
462
487
  body.reasoning = { ...body.reasoning, mode: model.reasoningMode };
463
488
  }
464
489
 
465
- // Concurrent reasoning summaries (codex-rs `concurrent_reasoning_summaries`
466
- // feature): `sequential_cutoff` lets the server stream output without
467
- // blocking on summary generation. Only meaningful when a summary is
468
- // requested; codex-rs additionally gates on its OpenAI provider check,
469
- // which is inherent here.
470
- if (body.reasoning?.summary !== undefined) {
490
+ // Concurrent reasoning summaries (codex-rs `concurrent_reasoning_summaries`):
491
+ // `sequential_cutoff` lets the server stream output without blocking on
492
+ // summary generation, delivering each completed section as an atomic
493
+ // `response.reasoning_summary_text.done`. Opt-in only — see
494
+ // {@link concurrentSummariesEnabled} for why. Requires a requested summary;
495
+ // codex-rs additionally gates on its OpenAI provider check, inherent here.
496
+ if (body.reasoning?.summary !== undefined && concurrentSummariesEnabled()) {
471
497
  body.stream_options = { reasoning_summary_delivery: "sequential_cutoff" };
472
498
  } else {
473
499
  delete body.stream_options;
@@ -1,4 +1,3 @@
1
- import * as os from "node:os";
2
1
  import { scheduler } from "node:timers/promises";
3
2
  import { calculateCost } from "@linxiraos/pi-catalog/models";
4
3
  import {
@@ -19,8 +18,8 @@ import {
19
18
  parseStreamingJson,
20
19
  readSseJson,
21
20
  structuredCloneJSON,
21
+ USER_AGENT,
22
22
  } from "@linxiraos/pi-utils";
23
- import packageJson from "../../package.json" with { type: "json" };
24
23
  import * as AIError from "../error";
25
24
  import { getEnvApiKey, isOfficialCodexApiUrl } from "../stream";
26
25
  import type {
@@ -1530,6 +1529,7 @@ export async function buildTransformedCodexRequestBody(
1530
1529
  }
1531
1530
  const codexOptions: CodexRequestOptions = {
1532
1531
  reasoningEffort: options?.reasoning,
1532
+ reasoningOff: options?.forceReasoningOff,
1533
1533
  reasoningSummary: options?.reasoningSummary,
1534
1534
  reasoningContext: options?.reasoningContext,
1535
1535
  textVerbosity: options?.textVerbosity,
@@ -4250,7 +4250,7 @@ function createCodexHeaders(
4250
4250
  headers.set(OPENAI_HEADERS.BETA, betaHeader);
4251
4251
  headers.set(OPENAI_HEADERS.ORIGINATOR, OPENAI_HEADER_VALUES.ORIGINATOR_CODEX);
4252
4252
  headers.set(OPENAI_HEADERS.VERSION, codexClientVersion);
4253
- headers.set("User-Agent", `pi/${packageJson.version} (${os.platform()} ${os.release()}; ${os.arch()})`);
4253
+ headers.set("User-Agent", USER_AGENT);
4254
4254
  if (sessionId) {
4255
4255
  headers.set(OPENAI_HEADERS.CONVERSATION_ID, sessionId);
4256
4256
  headers.set(OPENAI_HEADERS.SESSION_ID, sessionId);
@@ -132,7 +132,13 @@ function collectMessageParts(error: unknown, captured: CapturedHttpErrorResponse
132
132
  return parts.join("\n");
133
133
  }
134
134
 
135
- const REASONING_EFFORT_FIELD_PATTERN = /reasoning[_. ]effort|reasoning value/i;
135
+ /**
136
+ * Text that identifies a 400 as being about the reasoning-effort field.
137
+ * OpenAI-compatible gateways (cliproxy, …) never name the field — they reject
138
+ * the value alone with `level "none" not supported, valid levels: low, …` — so
139
+ * the allowed-level phrasing counts as a mention too.
140
+ */
141
+ const REASONING_EFFORT_FIELD_PATTERN = /reasoning[_. ]effort|reasoning value|(?:valid|supported|allowed) levels?/i;
136
142
 
137
143
  function mentionsReasoningEffort(error: unknown, captured: CapturedHttpErrorResponse | undefined): boolean {
138
144
  const param = capturedStringField(captured, "param");
@@ -168,10 +174,13 @@ function isInvalidReasoningEffortError(
168
174
  if (/(?:unsupported|not supported)[^\n]*(?:reasoning[_. ]effort|reasoning value)/i.test(message)) {
169
175
  return true;
170
176
  }
171
- return new RegExp(
172
- `(?:invalid|unsupported|not supported)[^\\n]*["'\`]${escapeRegExp(currentEffort)}["'\`]`,
173
- "i",
174
- ).test(message);
177
+ // Gateways put the rejected value first (`level "none" not supported`), the
178
+ // official API puts the verdict first (`Unsupported value: 'none'`).
179
+ const quoted = `["'\`]${escapeRegExp(currentEffort)}["'\`]`;
180
+ return (
181
+ new RegExp(`(?:invalid|unsupported|not supported)[^\\n]*${quoted}`, "i").test(message) ||
182
+ new RegExp(`${quoted}[^\\n]*(?:invalid|unsupported|not supported)`, "i").test(message)
183
+ );
175
184
  }
176
185
 
177
186
  function escapeRegExp(value: string): string {
@@ -186,9 +195,12 @@ function parseKnownReasoningValues(text: string): Set<string> {
186
195
  values.add(quotedMatch[1]!.toLowerCase());
187
196
  quotedMatch = quotedPattern.exec(text);
188
197
  }
189
- const allowedMatch = /(?:must be|one of|allowed values?|supported values?(?: are)?|expected)([^.\n]+)/i.exec(text);
198
+ const allowedMatch =
199
+ /(?:must be|one of|allowed values?|supported values?(?: are)?|expected|(?:valid|supported|allowed) levels?(?: are)?)[^.\n]+/i.exec(
200
+ text,
201
+ );
190
202
  if (allowedMatch) {
191
- const allowedText = allowedMatch[1]!;
203
+ const allowedText = allowedMatch[0]!;
192
204
  const barePattern = /\b(none|minimal|low|medium|high|xhigh|max)\b/gi;
193
205
  let bareMatch = barePattern.exec(allowedText);
194
206
  while (bareMatch !== null) {
@@ -201,7 +213,8 @@ function parseKnownReasoningValues(text: string): Set<string> {
201
213
 
202
214
  function parseAllowedReasoningValues(message: string, currentEffort: string): Set<string> | undefined {
203
215
  const values = parseKnownReasoningValues(message);
204
- const hasAllowedCue = /must be|one of|allowed values?|supported values?|expected/i.test(message);
216
+ const hasAllowedCue =
217
+ /must be|one of|allowed values?|supported values?|expected|(?:valid|supported|allowed) levels?/i.test(message);
205
218
  values.delete(currentEffort.toLowerCase());
206
219
  if (!hasAllowedCue && values.size === 0) return undefined;
207
220
  return values;