@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
@@ -1,5 +1,6 @@
1
1
  import { scheduler } from "node:timers/promises";
2
2
  import { hostMatchesUrl } from "@linxiraos/pi-catalog/hosts";
3
+ import { bareModelId, parseOpenAIModel, semverGte } from "@linxiraos/pi-catalog/identity";
3
4
  import { $flag, logger, structuredCloneJSON } from "@linxiraos/pi-utils";
4
5
  import * as AIError from "../error";
5
6
  import { getEnvApiKey } from "../stream";
@@ -80,6 +81,7 @@ import {
80
81
  createInitialResponsesAssistantMessage,
81
82
  createOpenAIStrictToolsState,
82
83
  disableStrictToolsForScope,
84
+ getJuiceValue,
83
85
  getOpenAIPromptCacheKey,
84
86
  getOpenAIResponsesRoutingSessionId,
85
87
  getOpenAIStrictToolsScope,
@@ -298,14 +300,23 @@ interface OpenAIResponsesChainedParams {
298
300
  */
299
301
  function buildOpenAIResponsesChainedParams(
300
302
  params: OpenAIResponsesSamplingParams,
303
+ trailingScaffoldingItems: number,
301
304
  chain: OpenAIResponsesChainState,
302
305
  ): OpenAIResponsesChainedParams {
306
+ const historyParams =
307
+ trailingScaffoldingItems > 0 && Array.isArray(params.input)
308
+ ? { ...params, input: params.input.slice(0, params.input.length - trailingScaffoldingItems) }
309
+ : params;
303
310
  const deltaInput = chain.canAppend
304
- ? buildResponsesDeltaInput(chain.lastParams, chain.lastResponseItems, params)
311
+ ? buildResponsesDeltaInput(chain.lastParams, chain.lastResponseItems, historyParams)
305
312
  : null;
306
313
  if (deltaInput && deltaInput.length > 0 && chain.lastResponseId) {
314
+ const scaffolding =
315
+ historyParams !== params && Array.isArray(params.input)
316
+ ? params.input.slice(params.input.length - trailingScaffoldingItems)
317
+ : [];
307
318
  return {
308
- params: { ...params, previous_response_id: chain.lastResponseId, input: deltaInput },
319
+ params: { ...params, previous_response_id: chain.lastResponseId, input: [...deltaInput, ...scaffolding] },
309
320
  previousResponseId: chain.lastResponseId,
310
321
  };
311
322
  }
@@ -462,8 +473,9 @@ const streamOpenAIResponsesOnce = (
462
473
  false,
463
474
  chainState?.canAppend ? chainState.lastParams?.input : undefined,
464
475
  );
465
- const params = builtParams.params;
476
+ const { params, trailingScaffoldingItems } = builtParams;
466
477
  let activeParams = params;
478
+ let activeTrailingScaffoldingItems = trailingScaffoldingItems;
467
479
  const resolvedBaseUrl = (baseUrl ?? "https://api.openai.com/v1").replace(/\/+$/, "");
468
480
  const requestReasoningEffortFallbacks = new Map<string, OpenAIReasoningEffortFallback>();
469
481
  const attemptedReasoningEffortFallbacks = new Set<string>();
@@ -490,7 +502,9 @@ const streamOpenAIResponsesOnce = (
490
502
  }
491
503
  applyReasoningEffortFallbackForRequest(params);
492
504
  let chained: OpenAIResponsesChainedParams =
493
- chainState && !chainState.disabled ? buildOpenAIResponsesChainedParams(params, chainState) : { params };
505
+ chainState && !chainState.disabled
506
+ ? buildOpenAIResponsesChainedParams(params, trailingScaffoldingItems, chainState)
507
+ : { params };
494
508
  sentPreviousResponseId = chained.previousResponseId;
495
509
  const idleTimeoutMs =
496
510
  options?.streamIdleTimeoutMs ?? getOpenAIStreamIdleTimeoutMs(model.compat.streamIdleTimeoutMs);
@@ -586,7 +600,9 @@ const streamOpenAIResponsesOnce = (
586
600
  const reasoningEffortFallback =
587
601
  activeReasoningEffortFallbackKey && activeRequestParams && !requestSignal.aborted
588
602
  ? resolveOpenAIReasoningEffortFallback(error, capturedErrorResponse, activeRequestParams, {
589
- explicitDisable: options?.disableReasoning === true && options.reasoning === undefined,
603
+ explicitDisable:
604
+ options?.forceReasoningOff === true ||
605
+ (options?.disableReasoning === true && options.reasoning === undefined),
590
606
  })
591
607
  : undefined;
592
608
  if (reasoningEffortFallback !== undefined && activeReasoningEffortFallbackKey) {
@@ -632,7 +648,11 @@ const streamOpenAIResponsesOnce = (
632
648
  if (chainState && !chainState.disabled) fallbackParams.store = true;
633
649
  let fallbackChained: OpenAIResponsesChainedParams =
634
650
  chainState && !chainState.disabled
635
- ? buildOpenAIResponsesChainedParams(fallbackParams, chainState)
651
+ ? buildOpenAIResponsesChainedParams(
652
+ fallbackParams,
653
+ fallbackBuilt.trailingScaffoldingItems,
654
+ chainState,
655
+ )
636
656
  : { params: fallbackParams };
637
657
  sentPreviousResponseId = fallbackChained.previousResponseId;
638
658
  fallbackChained = {
@@ -642,7 +662,7 @@ const streamOpenAIResponsesOnce = (
642
662
  chained = fallbackChained;
643
663
  activeRawRequestDump.body = chained.params;
644
664
  activeParams = fallbackParams;
645
- activeStrictToolsApplied = fallbackBuilt.strictToolsApplied;
665
+ activeTrailingScaffoldingItems = fallbackBuilt.trailingScaffoldingItems;
646
666
  continue;
647
667
  }
648
668
  if (!chainState || !sentPreviousResponseId || requestSignal.aborted) {
@@ -688,6 +708,7 @@ const streamOpenAIResponsesOnce = (
688
708
  chained = { params: retryParams };
689
709
  activeRawRequestDump.body = retryParams;
690
710
  activeParams = currentParams;
711
+ activeTrailingScaffoldingItems = currentBuilt.trailingScaffoldingItems;
691
712
  activeStrictToolsApplied = currentBuilt.strictToolsApplied;
692
713
  }
693
714
  }
@@ -824,7 +845,17 @@ const streamOpenAIResponsesOnce = (
824
845
  if (replayableResponseItems) {
825
846
  if (providerSessionState) providerSessionState.nativeHistoryReplayWarmed = true;
826
847
  if (chainState) {
827
- chainState.lastParams = structuredCloneJSON(activeParams);
848
+ chainState.lastParams = structuredCloneJSON(
849
+ activeTrailingScaffoldingItems > 0 && Array.isArray(activeParams.input)
850
+ ? {
851
+ ...activeParams,
852
+ input: activeParams.input.slice(
853
+ 0,
854
+ activeParams.input.length - activeTrailingScaffoldingItems,
855
+ ),
856
+ }
857
+ : activeParams,
858
+ );
828
859
  chainState.lastPromptCacheBreakpointPolicy = promptCacheBreakpointPolicy;
829
860
  if (output.responseId) {
830
861
  chainState.lastResponseId = output.responseId;
@@ -843,7 +874,14 @@ const streamOpenAIResponsesOnce = (
843
874
  // baseline, but `lastParams` still records the successful wire controls
844
875
  // without re-enabling `previous_response_id` chaining.
845
876
  chainState.canAppend = false;
846
- chainState.lastParams = structuredCloneJSON(activeParams);
877
+ chainState.lastParams = structuredCloneJSON(
878
+ activeTrailingScaffoldingItems > 0 && Array.isArray(activeParams.input)
879
+ ? {
880
+ ...activeParams,
881
+ input: activeParams.input.slice(0, activeParams.input.length - activeTrailingScaffoldingItems),
882
+ }
883
+ : activeParams,
884
+ );
847
885
  chainState.lastPromptCacheBreakpointPolicy = promptCacheBreakpointPolicy;
848
886
  chainState.lastResponseId = undefined;
849
887
  chainState.lastResponseItems = undefined;
@@ -899,6 +937,17 @@ function isOfficialOpenAIResponsesEndpoint(model: Model<"openai-responses">): bo
899
937
  }
900
938
  }
901
939
 
940
+ /**
941
+ * GPT-5.6+ family check for Responses routes. The model id classifies the
942
+ * reasoning family regardless of the provider/host serving it — a cliproxy or
943
+ * other OpenAI-compatible gateway carrying `gpt-5.6-sol` gets the same
944
+ * scaffolding as the official endpoint.
945
+ */
946
+ function isGpt56PlusResponsesModel(model: Model<"openai-responses">): boolean {
947
+ const parsed = parseOpenAIModel(bareModelId(model.requestModelId ?? model.id));
948
+ return parsed !== null && semverGte(parsed.version, "5.6");
949
+ }
950
+
902
951
  function isResponsesPromptCacheableContentBlock(block: unknown): block is ResponseInputContent {
903
952
  if (typeof block !== "object" || block === null || !("type" in block)) return false;
904
953
  return block.type === "input_text" || block.type === "input_image" || block.type === "input_file";
@@ -1090,7 +1139,7 @@ export function buildParams(
1090
1139
  strictToolsScope?: OpenAIStrictToolsScope,
1091
1140
  disableStrictToolsOverride = false,
1092
1141
  statefulCacheBaseline?: ResponseInput,
1093
- ): { params: OpenAIResponsesSamplingParams; strictToolsApplied: boolean } {
1142
+ ): { params: OpenAIResponsesSamplingParams; trailingScaffoldingItems: number; strictToolsApplied: boolean } {
1094
1143
  const policy = resolveOpenAICompatPolicy(model, {
1095
1144
  endpoint: "responses",
1096
1145
  reasoning: options?.reasoning,
@@ -1113,6 +1162,10 @@ export function buildParams(
1113
1162
  filterReasoning: policy.reasoning.filterReasoningHistory,
1114
1163
  },
1115
1164
  includeThinkingSignatures: shouldReplayNativeHistory && !policy.reasoning.filterReasoningHistory,
1165
+ requiresReasoningReplayForAllTurns:
1166
+ policy.reasoning.enabled && policy.reasoning.requiresReasoningContentForAllAssistantTurns,
1167
+ requiresReasoningReplayForToolCalls:
1168
+ policy.reasoning.enabled && policy.reasoning.requiresReasoningContentForToolCalls,
1116
1169
  repairOrphanOutputs: true,
1117
1170
  });
1118
1171
 
@@ -1240,6 +1293,7 @@ export function buildParams(
1240
1293
  : options?.reasoningSummary;
1241
1294
  applyResponsesCompatPolicy(params, reasoningPolicy, {
1242
1295
  reasoningSummary,
1296
+ forceReasoningOff: options?.forceReasoningOff,
1243
1297
  mapEffort: effort =>
1244
1298
  model.compat.reasoningEffortMap?.[effort as NonNullable<OpenAIResponsesOptions["reasoning"]>] ??
1245
1299
  model.thinking?.effortMap?.[effort as NonNullable<OpenAIResponsesOptions["reasoning"]>] ??
@@ -1249,7 +1303,7 @@ export function buildParams(
1249
1303
  // mode survives every policy branch (disabled/omitted effort included) while
1250
1304
  // keeping whatever effort/summary the policy produced — mode and effort are
1251
1305
  // independent wire fields.
1252
- if (model.reasoningMode) {
1306
+ if (model.reasoningMode && !options?.forceReasoningOff) {
1253
1307
  params.reasoning = { ...params.reasoning, mode: model.reasoningMode };
1254
1308
  }
1255
1309
 
@@ -1262,7 +1316,18 @@ export function buildParams(
1262
1316
  applyOpenAIExtraBody(params, options?.extraBody);
1263
1317
  applyOpenAIResponsesPromptCachePolicy(params, model, options, statefulCacheBaseline);
1264
1318
 
1265
- return { params, strictToolsApplied };
1319
+ let trailingScaffoldingItems = 0;
1320
+ if (options?.forceReasoningOff && isGpt56PlusResponsesModel(model)) {
1321
+ const effort = options.reasoning ?? "medium";
1322
+ const juice = getJuiceValue(effort);
1323
+ messages.push({
1324
+ role: "developer",
1325
+ content: [{ type: "input_text", text: `# Juice: ${juice} !important` }],
1326
+ });
1327
+ trailingScaffoldingItems = 1;
1328
+ }
1329
+
1330
+ return { params, trailingScaffoldingItems, strictToolsApplied };
1266
1331
  }
1267
1332
 
1268
1333
  /**
@@ -77,6 +77,7 @@ import {
77
77
  kStreamingLastParseLen,
78
78
  kStreamingPartialJson,
79
79
  } from "../utils/block-symbols";
80
+ import { hasVisibleAssistantContent } from "../utils/empty-completion-retry";
80
81
  import type { AssistantMessageEventStream } from "../utils/event-stream";
81
82
  import {
82
83
  escapeHarmonyControlTokens,
@@ -889,19 +890,18 @@ export function resolveOpenAICompatPolicy<TApi extends Api>(
889
890
  conflictDisableReason !== undefined ||
890
891
  (modelSupported && disabledWithoutRequest) ||
891
892
  disabledByNoneEffort;
892
- if (
893
- disabled &&
894
- disableReason === "caller" &&
895
- requestedEffort === undefined &&
896
- disableMode === "lowest-effort" &&
897
- compat.supportsReasoningEffort &&
898
- !omitReasoningEffort
899
- ) {
900
- const minEffort = getSupportedEfforts(model)[0];
901
- if (minEffort === undefined) {
902
- throw new AIError.ConfigurationError(`Model ${model.provider}/${model.id} has no supported reasoning efforts`);
893
+ if (disabled && compat.supportsReasoningEffort && !omitReasoningEffort) {
894
+ if (disableMode === "none-effort") {
895
+ wireEffort = "none";
896
+ } else if (disableReason === "caller" && requestedEffort === undefined && disableMode === "lowest-effort") {
897
+ const minEffort = getSupportedEfforts(model)[0];
898
+ if (minEffort === undefined) {
899
+ throw new AIError.ConfigurationError(
900
+ `Model ${model.provider}/${model.id} has no supported reasoning efforts`,
901
+ );
902
+ }
903
+ wireEffort = mapOpenAIReasoningEffort(model, compat, minEffort);
903
904
  }
904
- wireEffort = mapOpenAIReasoningEffort(model, compat, minEffort);
905
905
  }
906
906
 
907
907
  return {
@@ -954,6 +954,9 @@ function encodeChatCompletionsDisabledReasoning(
954
954
  ): void {
955
955
  delete params.reasoning_effort;
956
956
  switch (disableMode) {
957
+ case "none-effort":
958
+ params.reasoning_effort = "none";
959
+ break;
957
960
  case "zai-thinking-disabled":
958
961
  params.thinking = { type: "disabled" };
959
962
  break;
@@ -1067,7 +1070,7 @@ export function applyChatCompletionsCompatPolicy(params: OpenAICompletionsParams
1067
1070
  if (
1068
1071
  reasoning.disableReason === "caller" &&
1069
1072
  reasoning.requestedEffort === undefined &&
1070
- reasoning.disableMode === "lowest-effort" &&
1073
+ (reasoning.disableMode === "lowest-effort" || reasoning.disableMode === "none-effort") &&
1071
1074
  reasoning.wireEffort !== undefined
1072
1075
  ) {
1073
1076
  params.reasoning_effort = reasoning.wireEffort as Effort;
@@ -1635,6 +1638,14 @@ export interface BuildResponsesInputOptions<TApi extends Api> {
1635
1638
  repairOrphanOutputs?: boolean;
1636
1639
  /** Preserve assistant message item IDs from text signatures during fallback replay. */
1637
1640
  preserveAssistantMessageIds?: boolean;
1641
+ /**
1642
+ * Synthesize a reasoning item for every replayed assistant turn that carries
1643
+ * content but no reasoning item. Set for DeepSeek-family Responses targets
1644
+ * that reject a thinking-mode continuation lacking `reasoning_text`.
1645
+ */
1646
+ requiresReasoningReplayForAllTurns?: boolean;
1647
+ /** As {@link requiresReasoningReplayForAllTurns}, but only for turns that contain a tool call. */
1648
+ requiresReasoningReplayForToolCalls?: boolean;
1638
1649
  }
1639
1650
 
1640
1651
  /**
@@ -1861,6 +1872,8 @@ export function buildResponsesInput<TApi extends Api>(options: BuildResponsesInp
1861
1872
  supportsCustomToolCalls,
1862
1873
  customToolWireNameMap,
1863
1874
  computerCallIds,
1875
+ options.requiresReasoningReplayForAllTurns ?? false,
1876
+ options.requiresReasoningReplayForToolCalls ?? false,
1864
1877
  );
1865
1878
  const outputItems = suppressHiddenEmptyFallback
1866
1879
  ? sanitizeOpenAIResponsesAssistantFallbackItemsForReplay(convertedOutputItems)
@@ -1914,6 +1927,8 @@ export function convertResponsesAssistantMessage<TApi extends Api>(
1914
1927
  supportsCustomToolCalls = true,
1915
1928
  customToolWireNameMap?: ReadonlyMap<string, string>,
1916
1929
  computerCallIds?: Set<string>,
1930
+ requiresReasoningReplayForAllTurns = false,
1931
+ requiresReasoningReplayForToolCalls = false,
1917
1932
  ): ResponseInput {
1918
1933
  const outputItems: ResponseInput = [];
1919
1934
  let unsignedTextBlocks = 0;
@@ -1925,14 +1940,36 @@ export function convertResponsesAssistantMessage<TApi extends Api>(
1925
1940
  );
1926
1941
  const isDifferentModel =
1927
1942
  assistantMsg.model !== model.id && assistantMsg.provider === model.provider && assistantMsg.api === model.api;
1943
+ // DeepSeek-family Responses targets (e.g. opencode-go) reject a thinking-mode
1944
+ // continuation whose replayed assistant turns carry no reasoning item: "The
1945
+ // reasoning_text in the thinking mode must be passed back to the API." After a
1946
+ // cross-model prewalk hand-off or a compaction that drops the native replay
1947
+ // payload, the block re-encode below demotes reasoning to text and emits no
1948
+ // reasoning item. Track reasoning emission so a placeholder can be synthesized,
1949
+ // mirroring the chat-completions `requiresReasoningContentForAllAssistantTurns`
1950
+ // empty-`reasoning_content` safety net.
1951
+ const requiresReasoningItem =
1952
+ assistantMsg.stopReason !== "error" &&
1953
+ (requiresReasoningReplayForAllTurns ||
1954
+ (requiresReasoningReplayForToolCalls && assistantMsg.content.some(block => block.type === "toolCall")));
1955
+ let reasoningItemEmitted = false;
1956
+ const carriedReasoningTexts: string[] = [];
1957
+ let synthesizedReasoningItemId: string | undefined;
1928
1958
 
1929
1959
  for (const block of assistantMsg.content) {
1930
1960
  if (block.type === "thinking" && assistantMsg.stopReason !== "error") {
1961
+ if (requiresReasoningItem) {
1962
+ if (block.itemId) synthesizedReasoningItemId ??= block.itemId;
1963
+ if (block.thinking.trim().length > 0) carriedReasoningTexts.push(block.thinking);
1964
+ }
1931
1965
  if (!includeThinkingSignatures) {
1932
1966
  continue;
1933
1967
  }
1934
1968
  const reasoningItem = parseResponseReasoningReplayItem(block.thinkingSignature);
1935
- if (reasoningItem) outputItems.push(reasoningItem);
1969
+ if (reasoningItem) {
1970
+ outputItems.push(reasoningItem);
1971
+ reasoningItemEmitted = true;
1972
+ }
1936
1973
  continue;
1937
1974
  }
1938
1975
 
@@ -2033,6 +2070,26 @@ export function convertResponsesAssistantMessage<TApi extends Api>(
2033
2070
  });
2034
2071
  }
2035
2072
 
2073
+ if (requiresReasoningItem && !reasoningItemEmitted && outputItems.length > 0) {
2074
+ // Replay the demoted reasoning (already present in `content` as visible
2075
+ // text) as a structured reasoning item so the thinking-mode continuation
2076
+ // carries the `reasoning_text` the provider requires. The text may be empty
2077
+ // when the source turn was minted by another model and its reasoning is
2078
+ // already folded into the message text; the item's presence is what
2079
+ // satisfies the provider contract, mirroring the empty `reasoning_content`
2080
+ // placeholder used on the chat-completions path.
2081
+ const reasoningText = carriedReasoningTexts.join("\n");
2082
+ const reasoningId =
2083
+ synthesizedReasoningItemId ?? `rs_${Bun.hash(`${model.id}:${msgIndex}:${reasoningText}`).toString(36)}`;
2084
+ const reasoningItem: ResponseReasoningItem = {
2085
+ type: "reasoning",
2086
+ id: reasoningId,
2087
+ summary: [],
2088
+ content: [{ type: "reasoning_text", text: reasoningText }],
2089
+ };
2090
+ outputItems.unshift(reasoningItem);
2091
+ }
2092
+
2036
2093
  return outputItems;
2037
2094
  }
2038
2095
 
@@ -2374,6 +2431,20 @@ export function finalizeMessageText(item: ResponseOutputMessage, streamedText: s
2374
2431
  if (!item.content?.length) return streamedText || "";
2375
2432
  return item.content.map(part => (part.type === "output_text" ? (part.text ?? "") : (part.refusal ?? ""))).join("");
2376
2433
  }
2434
+ export const JUICE_EFFORT_MAP: Record<string, number> = {
2435
+ none: 0,
2436
+ minimal: 2,
2437
+ low: 4,
2438
+ medium: 8,
2439
+ high: 48,
2440
+ xhigh: 112,
2441
+ max: 960,
2442
+ };
2443
+
2444
+ export function getJuiceValue(effort?: string): number {
2445
+ if (!effort) return 8;
2446
+ return JUICE_EFFORT_MAP[effort] ?? 8;
2447
+ }
2377
2448
 
2378
2449
  export function accumulateToolCallArgumentsDelta(
2379
2450
  block: ResponsesToolCallBlock,
@@ -2705,6 +2776,10 @@ export async function processResponsesStream<TApi extends Api>(
2705
2776
  output.content.indexOf(block);
2706
2777
 
2707
2778
  let sawFirstToken = false;
2779
+ // Whether the current stream produced a completed native `web_search_call`
2780
+ // output item. A provider-hosted search that finishes without yield is
2781
+ // progress evidence: the turn should pause for continuation rather than end.
2782
+ let sawCompletedWebSearchCall = false;
2708
2783
 
2709
2784
  for await (const event of openaiStream) {
2710
2785
  const terminalEvent = getOpenAIResponsesTerminalEvent(event);
@@ -2991,6 +3066,10 @@ export async function processResponsesStream<TApi extends Api>(
2991
3066
  }
2992
3067
  closeOpenItem(event.output_index, item.id, entry, item.call_id, prefixedFunctionCallItemKey(item.call_id));
2993
3068
  stream.push({ type: "toolcall_end", contentIndex, toolCall, partial: output });
3069
+ } else if (item.type === "web_search_call" && (item.status === undefined || item.status === "completed")) {
3070
+ // A completed provider-hosted web search is progress evidence even when
3071
+ // the model never surfaced an answer; the agent loop continues from it.
3072
+ sawCompletedWebSearchCall = true;
2994
3073
  } else if (item.type === "image_generation_call" && item.status === "completed" && item.result) {
2995
3074
  appendResponsesImageResult(output, stream, item.result);
2996
3075
  }
@@ -3041,6 +3120,14 @@ export async function processResponsesStream<TApi extends Api>(
3041
3120
  (response as { end_turn?: boolean } | undefined)?.end_turn,
3042
3121
  shouldPromoteIncompleteToolUse,
3043
3122
  );
3123
+ // A completed provider-hosted web search that yielded no visible answer
3124
+ // (no text, image, or client tool call) is progress, not a dead end:
3125
+ // pause the turn so the agent loop re-samples with the search results
3126
+ // instead of silently ending. Reasoning/native output items are preserved
3127
+ // for replay. A search followed by visible output stays a normal stop.
3128
+ if (sawCompletedWebSearchCall && output.stopReason === "stop" && !hasVisibleAssistantContent(output)) {
3129
+ output.stopDetails = { type: "pause_turn" };
3130
+ }
3044
3131
  options?.onCompleted?.();
3045
3132
  // `response.completed`/`response.incomplete`/`response.done` is the last event of a
3046
3133
  // Responses stream. Stop pulling instead of waiting for the server to
@@ -3254,6 +3341,14 @@ type ReasoningOptions = {
3254
3341
  export interface ApplyResponsesCompatPolicyOptions {
3255
3342
  reasoningSummary?: "auto" | "detailed" | "concise" | null;
3256
3343
  mapEffort?: (effort: string) => string;
3344
+ /**
3345
+ * Suppress native reasoning by sending `reasoning.effort: "none"` — the only
3346
+ * disable level the Responses API defines (`"off"` is not a wire value and
3347
+ * 400s everywhere). Gateways that reject `none` for a given model are
3348
+ * handled by the reasoning-effort fallback retry, which clamps to the
3349
+ * lowest level the error reports as allowed.
3350
+ */
3351
+ forceReasoningOff?: boolean;
3257
3352
  }
3258
3353
 
3259
3354
  export function applyResponsesCompatPolicy<P extends ResponseCreateParamsStreaming>(
@@ -3262,6 +3357,10 @@ export function applyResponsesCompatPolicy<P extends ResponseCreateParamsStreami
3262
3357
  options: ApplyResponsesCompatPolicyOptions | undefined,
3263
3358
  ): void {
3264
3359
  const reasoning = policy.reasoning;
3360
+ if (options?.forceReasoningOff) {
3361
+ params.reasoning = { effort: "none" } as P["reasoning"];
3362
+ return;
3363
+ }
3265
3364
  if (!reasoning.modelSupported) return;
3266
3365
  if (reasoning.includeEncryptedReasoning) {
3267
3366
  const include = params.include ?? [];
@@ -3275,7 +3374,7 @@ export function applyResponsesCompatPolicy<P extends ResponseCreateParamsStreami
3275
3374
  return;
3276
3375
  }
3277
3376
  if (
3278
- reasoning.disableMode === "lowest-effort" &&
3377
+ (reasoning.disableMode === "lowest-effort" || reasoning.disableMode === "none-effort") &&
3279
3378
  reasoning.wireEffort !== undefined &&
3280
3379
  !reasoning.omitReasoningEffort
3281
3380
  ) {
@@ -78,6 +78,7 @@ const ALLOWED_OPTION_KEYS: ReadonlySet<keyof SimpleStreamOptions> = new Set([
78
78
  "preferWebsockets",
79
79
  "openrouterVariant",
80
80
  "loopGuard",
81
+ "acceptEmptyResponse",
81
82
  ] as const satisfies readonly (keyof SimpleStreamOptions)[]);
82
83
 
83
84
  // ---------------------------------------------------------------------------
@@ -202,6 +202,11 @@ interface LazyStreamLimits {
202
202
  * stream timeouts. Keep the lazy loader from racing it with generic errors.
203
203
  */
204
204
  providerHandlesStreamTimeouts?: boolean;
205
+ /**
206
+ * The provider retries or fails over when no first event arrives, while the
207
+ * lazy wrapper continues to own steady-state idle detection.
208
+ */
209
+ providerHandlesFirstEventTimeouts?: boolean;
205
210
  /**
206
211
  * Apply OpenAI-family idle timeout precedence in the lazy wrapper. Used by
207
212
  * local backends whose users historically tune slow prompt-processing gaps
@@ -210,16 +215,13 @@ interface LazyStreamLimits {
210
215
  openAIIdleEnvFloorsFirstEvent?: boolean;
211
216
  }
212
217
  /**
213
- * Cloud Code Assist (google-gemini-cli / google-antigravity) routinely takes
214
- * longer than the global 100s default to emit its first SSE event when serving
215
- * the heavier Gemini 3.x Pro tiers at high thinking levels. Bump the first-event
216
- * floor to five minutes so callers stop seeing spurious "stream timed out while
217
- * waiting for the first event" aborts on legitimate cold reasoning starts.
218
- * The steady-state idle watchdog stays on the global default since the upstream
219
- * emits thinking tokens frequently once it gets going.
218
+ * Cloud Code Assist owns first-event detection because Antigravity can return
219
+ * successful headers and then never emit an SSE event. Keeping the watchdog in
220
+ * the provider lets it fail over before surfacing an error; the lazy wrapper
221
+ * still catches post-first-event stalls.
220
222
  */
221
223
  const GOOGLE_GEMINI_CLI_LAZY_STREAM_LIMITS: LazyStreamLimits = {
222
- defaultFirstEventTimeoutMs: 300_000,
224
+ providerHandlesFirstEventTimeouts: true,
223
225
  };
224
226
 
225
227
  const PROVIDER_HANDLED_STREAM_TIMEOUTS: LazyStreamLimits = {
@@ -241,6 +243,7 @@ function forwardStream<TApi extends Api>(
241
243
  (async () => {
242
244
  try {
243
245
  const providerHandlesStreamTimeouts = limits?.providerHandlesStreamTimeouts === true;
246
+ const providerHandlesFirstEventTimeouts = limits?.providerHandlesFirstEventTimeouts === true;
244
247
  // Per-model catalog compat can widen the fallback watchdog for hosts
245
248
  // with no keepalive events (e.g. Bedrock reasoning models that go
246
249
  // quiet for minutes mid-thinking, issue #4758). Caller options and
@@ -258,12 +261,13 @@ function forwardStream<TApi extends Api>(
258
261
  (limits?.openAIIdleEnvFloorsFirstEvent
259
262
  ? getOpenAIStreamIdleTimeoutMs(idleTimeoutFallbackMs)
260
263
  : getStreamIdleTimeoutMs(idleTimeoutFallbackMs)));
261
- const firstItemTimeoutMs = providerHandlesStreamTimeouts
262
- ? 0
263
- : (options.streamFirstEventTimeoutMs ??
264
- (limits?.openAIIdleEnvFloorsFirstEvent
265
- ? getOpenAIStreamFirstEventTimeoutMs(idleTimeoutMs, limits.defaultFirstEventTimeoutMs)
266
- : getStreamFirstEventTimeoutMs(idleTimeoutMs, limits?.defaultFirstEventTimeoutMs)));
264
+ const firstItemTimeoutMs =
265
+ providerHandlesStreamTimeouts || providerHandlesFirstEventTimeouts
266
+ ? 0
267
+ : (options.streamFirstEventTimeoutMs ??
268
+ (limits?.openAIIdleEnvFloorsFirstEvent
269
+ ? getOpenAIStreamFirstEventTimeoutMs(idleTimeoutMs, limits.defaultFirstEventTimeoutMs)
270
+ : getStreamFirstEventTimeoutMs(idleTimeoutMs, limits?.defaultFirstEventTimeoutMs)));
267
271
  // Providers with a server-driven local tool bridge (e.g. the Cursor
268
272
  // exec channel) mark their stream busy while a local tool runs; the
269
273
  // watchdog must not read that silence as a provider stall (#4593).
@@ -39,7 +39,11 @@ export function joinTextWithImagePlaceholder(text: string, omittedImages: boolea
39
39
  * multimodal content arrays for. The compatible-mode endpoint also serves
40
40
  * multimodal Qwen SKUs without `vl` in the id (e.g. `qwen3.7-plus`), so this
41
41
  * guard only covers families verified to be text-only for issue #1859:
42
- * `qwen*-max` and `qwen*-coder*`.
42
+ * `qwen*-coder*` and `qwen*-max` up to and including `qwen3.7-max`.
43
+ *
44
+ * Qwen-Max became multimodal at `qwen3.8-max` (image input, issue #8019), so
45
+ * `-max` SKUs at version 3.8 or newer are excluded — otherwise the override
46
+ * would strip images from a genuinely vision-capable flagship (issue #8305).
43
47
  *
44
48
  * Used as a defensive override in `convertMessages` so a misconfigured custom
45
49
  * provider (issue #1859) can't drive the request into an unrecoverable 400.
@@ -48,7 +52,15 @@ export function isDashscopeCompatibleModeTextOnlyQwen(model: Model<"openai-compl
48
52
  if (!isDashscopeCompatibleModeUrl(model.baseUrl)) {
49
53
  return false;
50
54
  }
51
- const id = model.id.toLowerCase();
52
55
  if (!isQwenModelId(model.id)) return false;
53
- return /\bqwen(?:[\d.]+)?-max\b/.test(id) || /\bqwen(?:[\d.]+)?-coder\b/.test(id);
56
+ const id = model.id.toLowerCase();
57
+ if (/\bqwen(?:[\d.]+)?-coder\b/.test(id)) return true;
58
+ const maxMatch = id.match(/\bqwen(?:(\d+)(?:\.(\d+))?)?-max\b/);
59
+ if (!maxMatch) return false;
60
+ // Bare `qwen-max` (no version) is the text-only 2.5-era flagship. Compare
61
+ // major/minor component-wise, not as a decimal float, so `qwen3.10-max`
62
+ // sorts after `qwen3.8-max`: text-only through 3.7, multimodal from 3.8 on.
63
+ const major = maxMatch[1] ? Number.parseInt(maxMatch[1], 10) : 0;
64
+ const minor = maxMatch[2] ? Number.parseInt(maxMatch[2], 10) : 0;
65
+ return major < 3 || (major === 3 && minor < 8);
54
66
  }
@@ -1,22 +1,17 @@
1
- import { $env } from "@linxiraos/pi-utils";
1
+ import { type AwsBedrockProviderOptions, resolveAwsRegistryApiKey } from "./aws";
2
2
  import type { ProviderDefinition } from "./types";
3
3
 
4
4
  export const amazonBedrockProvider = {
5
5
  id: "amazon-bedrock",
6
6
  name: "Amazon Bedrock",
7
7
  // Amazon Bedrock accepts bearer tokens, IAM keys, profiles, ECS/IRSA credential chains.
8
- envKeys: () => {
9
- const hasEcsCredentials =
10
- !!$env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || !!$env.AWS_CONTAINER_CREDENTIALS_FULL_URI;
11
- const hasWebIdentity = !!$env.AWS_WEB_IDENTITY_TOKEN_FILE && !!$env.AWS_ROLE_ARN;
12
- if (
13
- $env.AWS_PROFILE ||
14
- ($env.AWS_ACCESS_KEY_ID && $env.AWS_SECRET_ACCESS_KEY) ||
15
- $env.AWS_BEARER_TOKEN_BEDROCK ||
16
- hasEcsCredentials ||
17
- hasWebIdentity
18
- ) {
19
- return "<authenticated>";
20
- }
8
+ envKeys: () => resolveAwsRegistryApiKey({ allowSkipAuth: true }),
9
+ mapSimpleOptions: options => {
10
+ const awsOptions = options.providerOptions as AwsBedrockProviderOptions | undefined;
11
+ return {
12
+ region: awsOptions?.region,
13
+ profile: awsOptions?.profile,
14
+ bearerToken: awsOptions?.bearerToken,
15
+ };
21
16
  },
22
17
  } as const satisfies ProviderDefinition;
@@ -38,16 +38,18 @@ type ModelsEndpointValidation = {
38
38
  export type ApiKeyLoginConfig = {
39
39
  /** Display name used in error messages, e.g. "Cerebras", "NanoGPT". */
40
40
  providerLabel: string;
41
- /** URL opened in browser for the user to grab their key. */
42
- authUrl: string;
43
- /** Instructions shown with the onAuth callback. */
44
- instructions: string;
41
+ /** URL opened in browser for the user to grab their key, or omitted to skip onAuth. */
42
+ authUrl?: string;
43
+ /** Instructions shown with the onAuth callback, or omitted to skip onAuth. */
44
+ instructions?: string;
45
45
  /** Prompt message shown when asking for the key paste. */
46
46
  promptMessage: string;
47
47
  /** Placeholder string for the prompt (e.g. "sk-...", "csk-..."). */
48
48
  placeholder: string;
49
49
  /** Validation strategy, or `null` to skip validation. */
50
50
  validation: ChatCompletionsValidation | AnthropicMessagesValidation | ModelsEndpointValidation | null;
51
+ /** Value returned for an empty key; also allows an empty prompt response. */
52
+ emptyKeyFallback?: string;
51
53
  };
52
54
 
53
55
  export function createApiKeyLogin(config: ApiKeyLoginConfig): (options: OAuthController) => Promise<string> {
@@ -56,15 +58,24 @@ export function createApiKeyLogin(config: ApiKeyLoginConfig): (options: OAuthCon
56
58
  throw new AIError.OnPromptRequiredError(config.providerLabel);
57
59
  }
58
60
 
59
- options.onAuth?.({
60
- url: config.authUrl,
61
- instructions: config.instructions,
62
- });
61
+ if (config.authUrl && config.instructions) {
62
+ options.onAuth?.({
63
+ url: config.authUrl,
64
+ instructions: config.instructions,
65
+ });
66
+ }
63
67
 
64
- const apiKey = await options.onPrompt({
65
- message: config.promptMessage,
66
- placeholder: config.placeholder,
67
- });
68
+ const apiKey =
69
+ config.emptyKeyFallback === undefined
70
+ ? await options.onPrompt({
71
+ message: config.promptMessage,
72
+ placeholder: config.placeholder,
73
+ })
74
+ : await options.onPrompt({
75
+ message: config.promptMessage,
76
+ placeholder: config.placeholder,
77
+ allowEmpty: true,
78
+ });
68
79
 
69
80
  if (options.signal?.aborted) {
70
81
  throw new AIError.LoginCancelledError();
@@ -72,6 +83,9 @@ export function createApiKeyLogin(config: ApiKeyLoginConfig): (options: OAuthCon
72
83
 
73
84
  const trimmed = apiKey.trim();
74
85
  if (!trimmed) {
86
+ if (config.emptyKeyFallback !== undefined) {
87
+ return config.emptyKeyFallback;
88
+ }
75
89
  throw new AIError.ApiKeyRequiredError();
76
90
  }
77
91