open-multi-agent-kit 0.90.8 → 0.91.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +21 -2
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +4 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/commands/doctor-provider-cli.d.ts +33 -0
  7. package/dist/commands/doctor-provider-cli.d.ts.map +1 -0
  8. package/dist/commands/doctor-provider-cli.js +215 -0
  9. package/dist/commands/doctor-provider-cli.js.map +1 -0
  10. package/dist/commands/doctor-provider-transport.d.ts +35 -0
  11. package/dist/commands/doctor-provider-transport.d.ts.map +1 -0
  12. package/dist/commands/doctor-provider-transport.js +185 -0
  13. package/dist/commands/doctor-provider-transport.js.map +1 -0
  14. package/dist/commands/doctor-provider.d.ts +121 -7
  15. package/dist/commands/doctor-provider.d.ts.map +1 -1
  16. package/dist/commands/doctor-provider.js +1295 -102
  17. package/dist/commands/doctor-provider.js.map +1 -1
  18. package/dist/commands/session-doctor-cli.d.ts +43 -0
  19. package/dist/commands/session-doctor-cli.d.ts.map +1 -0
  20. package/dist/commands/session-doctor-cli.js +494 -0
  21. package/dist/commands/session-doctor-cli.js.map +1 -0
  22. package/dist/commands/verify-ci.d.ts +21 -0
  23. package/dist/commands/verify-ci.d.ts.map +1 -0
  24. package/dist/commands/verify-ci.js +66 -0
  25. package/dist/commands/verify-ci.js.map +1 -0
  26. package/dist/core/agent-session-services.d.ts +3 -0
  27. package/dist/core/agent-session-services.d.ts.map +1 -1
  28. package/dist/core/agent-session-services.js +3 -0
  29. package/dist/core/agent-session-services.js.map +1 -1
  30. package/dist/core/agent-session.d.ts +140 -2
  31. package/dist/core/agent-session.d.ts.map +1 -1
  32. package/dist/core/agent-session.js +1179 -95
  33. package/dist/core/agent-session.js.map +1 -1
  34. package/dist/core/agent-tool-settings.d.ts +35 -0
  35. package/dist/core/agent-tool-settings.d.ts.map +1 -0
  36. package/dist/core/agent-tool-settings.js +155 -0
  37. package/dist/core/agent-tool-settings.js.map +1 -0
  38. package/dist/core/atomic-session-file.d.ts +14 -0
  39. package/dist/core/atomic-session-file.d.ts.map +1 -0
  40. package/dist/core/atomic-session-file.js +104 -0
  41. package/dist/core/atomic-session-file.js.map +1 -0
  42. package/dist/core/command-safety.d.ts.map +1 -1
  43. package/dist/core/command-safety.js +0 -8
  44. package/dist/core/command-safety.js.map +1 -1
  45. package/dist/core/compaction/compaction.d.ts +6 -0
  46. package/dist/core/compaction/compaction.d.ts.map +1 -1
  47. package/dist/core/compaction/compaction.js +12 -1
  48. package/dist/core/compaction/compaction.js.map +1 -1
  49. package/dist/core/compaction/hysteresis.d.ts +27 -0
  50. package/dist/core/compaction/hysteresis.d.ts.map +1 -0
  51. package/dist/core/compaction/hysteresis.js +84 -0
  52. package/dist/core/compaction/hysteresis.js.map +1 -0
  53. package/dist/core/compaction/index.d.ts +2 -0
  54. package/dist/core/compaction/index.d.ts.map +1 -1
  55. package/dist/core/compaction/index.js +2 -0
  56. package/dist/core/compaction/index.js.map +1 -1
  57. package/dist/core/compaction/transaction.d.ts +172 -0
  58. package/dist/core/compaction/transaction.d.ts.map +1 -0
  59. package/dist/core/compaction/transaction.js +824 -0
  60. package/dist/core/compaction/transaction.js.map +1 -0
  61. package/dist/core/context-budget-governor-v2.d.ts +2 -1
  62. package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
  63. package/dist/core/context-budget-governor-v2.js +2 -1
  64. package/dist/core/context-budget-governor-v2.js.map +1 -1
  65. package/dist/core/context-budget-reserved-tokens.d.ts +110 -0
  66. package/dist/core/context-budget-reserved-tokens.d.ts.map +1 -0
  67. package/dist/core/context-budget-reserved-tokens.js +188 -0
  68. package/dist/core/context-budget-reserved-tokens.js.map +1 -0
  69. package/dist/core/context-budget-v2-cache-invalidation.d.ts +133 -0
  70. package/dist/core/context-budget-v2-cache-invalidation.d.ts.map +1 -0
  71. package/dist/core/context-budget-v2-cache-invalidation.js +452 -0
  72. package/dist/core/context-budget-v2-cache-invalidation.js.map +1 -0
  73. package/dist/core/context-budget-v2-cache-keys.d.ts +3 -0
  74. package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
  75. package/dist/core/context-budget-v2-cache-keys.js +6 -0
  76. package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
  77. package/dist/core/context-budget-v2-cache-provider.d.ts +4 -0
  78. package/dist/core/context-budget-v2-cache-provider.d.ts.map +1 -1
  79. package/dist/core/context-budget-v2-cache-provider.js +19 -0
  80. package/dist/core/context-budget-v2-cache-provider.js.map +1 -1
  81. package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -1
  82. package/dist/core/context-budget-v2-plan-cache-keys.js +1 -0
  83. package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -1
  84. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  85. package/dist/core/context-budget-v2-planner.js +1 -0
  86. package/dist/core/context-budget-v2-planner.js.map +1 -1
  87. package/dist/core/context-budget-v2-types.d.ts +4 -0
  88. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  89. package/dist/core/context-budget-v2-types.js.map +1 -1
  90. package/dist/core/durable-file-directory.d.ts +4 -0
  91. package/dist/core/durable-file-directory.d.ts.map +1 -0
  92. package/dist/core/durable-file-directory.js +76 -0
  93. package/dist/core/durable-file-directory.js.map +1 -0
  94. package/dist/core/durable-file-identity.d.ts +26 -0
  95. package/dist/core/durable-file-identity.d.ts.map +1 -0
  96. package/dist/core/durable-file-identity.js +118 -0
  97. package/dist/core/durable-file-identity.js.map +1 -0
  98. package/dist/core/durable-file-io.d.ts +29 -0
  99. package/dist/core/durable-file-io.d.ts.map +1 -0
  100. package/dist/core/durable-file-io.js +179 -0
  101. package/dist/core/durable-file-io.js.map +1 -0
  102. package/dist/core/durable-file-lock-cleanup.d.ts +12 -0
  103. package/dist/core/durable-file-lock-cleanup.d.ts.map +1 -0
  104. package/dist/core/durable-file-lock-cleanup.js +39 -0
  105. package/dist/core/durable-file-lock-cleanup.js.map +1 -0
  106. package/dist/core/durable-file-lock-owner.d.ts +18 -0
  107. package/dist/core/durable-file-lock-owner.d.ts.map +1 -0
  108. package/dist/core/durable-file-lock-owner.js +78 -0
  109. package/dist/core/durable-file-lock-owner.js.map +1 -0
  110. package/dist/core/durable-file-lock-storage.d.ts +35 -0
  111. package/dist/core/durable-file-lock-storage.d.ts.map +1 -0
  112. package/dist/core/durable-file-lock-storage.js +263 -0
  113. package/dist/core/durable-file-lock-storage.js.map +1 -0
  114. package/dist/core/durable-file-mode.d.ts +8 -0
  115. package/dist/core/durable-file-mode.d.ts.map +1 -0
  116. package/dist/core/durable-file-mode.js +41 -0
  117. package/dist/core/durable-file-mode.js.map +1 -0
  118. package/dist/core/extensions/loader.d.ts +12 -0
  119. package/dist/core/extensions/loader.d.ts.map +1 -1
  120. package/dist/core/extensions/loader.js +46 -37
  121. package/dist/core/extensions/loader.js.map +1 -1
  122. package/dist/core/footer-data-provider.d.ts +7 -1
  123. package/dist/core/footer-data-provider.d.ts.map +1 -1
  124. package/dist/core/footer-data-provider.js +12 -0
  125. package/dist/core/footer-data-provider.js.map +1 -1
  126. package/dist/core/index.d.ts +7 -2
  127. package/dist/core/index.d.ts.map +1 -1
  128. package/dist/core/index.js +6 -0
  129. package/dist/core/index.js.map +1 -1
  130. package/dist/core/metrics-sampler.d.ts +20 -0
  131. package/dist/core/metrics-sampler.d.ts.map +1 -1
  132. package/dist/core/metrics-sampler.js +52 -0
  133. package/dist/core/metrics-sampler.js.map +1 -1
  134. package/dist/core/model-registry.d.ts.map +1 -1
  135. package/dist/core/model-registry.js +4 -1
  136. package/dist/core/model-registry.js.map +1 -1
  137. package/dist/core/model-resolver.d.ts.map +1 -1
  138. package/dist/core/model-resolver.js +16 -7
  139. package/dist/core/model-resolver.js.map +1 -1
  140. package/dist/core/provider-resilience.d.ts +51 -0
  141. package/dist/core/provider-resilience.d.ts.map +1 -0
  142. package/dist/core/provider-resilience.js +86 -0
  143. package/dist/core/provider-resilience.js.map +1 -0
  144. package/dist/core/redaction.d.ts +8 -0
  145. package/dist/core/redaction.d.ts.map +1 -0
  146. package/dist/core/redaction.js +31 -0
  147. package/dist/core/redaction.js.map +1 -0
  148. package/dist/core/resource-loader.d.ts.map +1 -1
  149. package/dist/core/resource-loader.js +36 -0
  150. package/dist/core/resource-loader.js.map +1 -1
  151. package/dist/core/run-journal-store.d.ts +53 -0
  152. package/dist/core/run-journal-store.d.ts.map +1 -0
  153. package/dist/core/run-journal-store.js +278 -0
  154. package/dist/core/run-journal-store.js.map +1 -0
  155. package/dist/core/run-journal.d.ts +172 -0
  156. package/dist/core/run-journal.d.ts.map +1 -0
  157. package/dist/core/run-journal.js +1045 -0
  158. package/dist/core/run-journal.js.map +1 -0
  159. package/dist/core/sdk.d.ts +7 -0
  160. package/dist/core/sdk.d.ts.map +1 -1
  161. package/dist/core/sdk.js +94 -3
  162. package/dist/core/sdk.js.map +1 -1
  163. package/dist/core/session-doctor-plan.d.ts +128 -0
  164. package/dist/core/session-doctor-plan.d.ts.map +1 -0
  165. package/dist/core/session-doctor-plan.js +833 -0
  166. package/dist/core/session-doctor-plan.js.map +1 -0
  167. package/dist/core/session-file-compaction.d.ts +6 -0
  168. package/dist/core/session-file-compaction.d.ts.map +1 -0
  169. package/dist/core/session-file-compaction.js +169 -0
  170. package/dist/core/session-file-compaction.js.map +1 -0
  171. package/dist/core/session-file-head.d.ts +10 -0
  172. package/dist/core/session-file-head.d.ts.map +1 -0
  173. package/dist/core/session-file-head.js +78 -0
  174. package/dist/core/session-file-head.js.map +1 -0
  175. package/dist/core/session-file-persistence.d.ts +30 -0
  176. package/dist/core/session-file-persistence.d.ts.map +1 -0
  177. package/dist/core/session-file-persistence.js +106 -0
  178. package/dist/core/session-file-persistence.js.map +1 -0
  179. package/dist/core/session-integrity.d.ts +43 -0
  180. package/dist/core/session-integrity.d.ts.map +1 -0
  181. package/dist/core/session-integrity.js +420 -0
  182. package/dist/core/session-integrity.js.map +1 -0
  183. package/dist/core/session-manager.d.ts +24 -4
  184. package/dist/core/session-manager.d.ts.map +1 -1
  185. package/dist/core/session-manager.js +242 -125
  186. package/dist/core/session-manager.js.map +1 -1
  187. package/dist/core/session-owner-lease.d.ts +22 -0
  188. package/dist/core/session-owner-lease.d.ts.map +1 -0
  189. package/dist/core/session-owner-lease.js +176 -0
  190. package/dist/core/session-owner-lease.js.map +1 -0
  191. package/dist/core/session-path-policy.d.ts +132 -0
  192. package/dist/core/session-path-policy.d.ts.map +1 -0
  193. package/dist/core/session-path-policy.js +780 -0
  194. package/dist/core/session-path-policy.js.map +1 -0
  195. package/dist/core/session-repair-plan.d.ts +49 -0
  196. package/dist/core/session-repair-plan.d.ts.map +1 -0
  197. package/dist/core/session-repair-plan.js +202 -0
  198. package/dist/core/session-repair-plan.js.map +1 -0
  199. package/dist/core/session-termination.d.ts +106 -0
  200. package/dist/core/session-termination.d.ts.map +1 -0
  201. package/dist/core/session-termination.js +445 -0
  202. package/dist/core/session-termination.js.map +1 -0
  203. package/dist/core/settings-manager.d.ts +52 -0
  204. package/dist/core/settings-manager.d.ts.map +1 -1
  205. package/dist/core/settings-manager.js +54 -5
  206. package/dist/core/settings-manager.js.map +1 -1
  207. package/dist/core/system-prompt.d.ts.map +1 -1
  208. package/dist/core/system-prompt.js +1 -1
  209. package/dist/core/system-prompt.js.map +1 -1
  210. package/dist/core/tools/grep.d.ts.map +1 -1
  211. package/dist/core/tools/grep.js +47 -0
  212. package/dist/core/tools/grep.js.map +1 -1
  213. package/dist/core/tools/omp-pure-seams.d.ts +41 -0
  214. package/dist/core/tools/omp-pure-seams.d.ts.map +1 -0
  215. package/dist/core/tools/omp-pure-seams.js +142 -0
  216. package/dist/core/tools/omp-pure-seams.js.map +1 -0
  217. package/dist/core/tools/omp-seam-runtime.d.ts +100 -0
  218. package/dist/core/tools/omp-seam-runtime.d.ts.map +1 -0
  219. package/dist/core/tools/omp-seam-runtime.js +40 -0
  220. package/dist/core/tools/omp-seam-runtime.js.map +1 -0
  221. package/dist/core/tools/read.d.ts.map +1 -1
  222. package/dist/core/tools/read.js +34 -1
  223. package/dist/core/tools/read.js.map +1 -1
  224. package/dist/core/verified-bash-adapter.d.ts +36 -0
  225. package/dist/core/verified-bash-adapter.d.ts.map +1 -0
  226. package/dist/core/verified-bash-adapter.js +90 -0
  227. package/dist/core/verified-bash-adapter.js.map +1 -0
  228. package/dist/guardrails/command-redaction.d.ts +32 -0
  229. package/dist/guardrails/command-redaction.d.ts.map +1 -0
  230. package/dist/guardrails/command-redaction.js +776 -0
  231. package/dist/guardrails/command-redaction.js.map +1 -0
  232. package/dist/guardrails/evidence-attestation.d.ts +31 -0
  233. package/dist/guardrails/evidence-attestation.d.ts.map +1 -0
  234. package/dist/guardrails/evidence-attestation.js +178 -0
  235. package/dist/guardrails/evidence-attestation.js.map +1 -0
  236. package/dist/guardrails/evidence-receipt-store.d.ts +33 -0
  237. package/dist/guardrails/evidence-receipt-store.d.ts.map +1 -0
  238. package/dist/guardrails/evidence-receipt-store.js +446 -0
  239. package/dist/guardrails/evidence-receipt-store.js.map +1 -0
  240. package/dist/guardrails/evidence-receipt.d.ts +58 -0
  241. package/dist/guardrails/evidence-receipt.d.ts.map +1 -0
  242. package/dist/guardrails/evidence-receipt.js +389 -0
  243. package/dist/guardrails/evidence-receipt.js.map +1 -0
  244. package/dist/guardrails/evidence-system.d.ts +39 -15
  245. package/dist/guardrails/evidence-system.d.ts.map +1 -1
  246. package/dist/guardrails/evidence-system.js +255 -97
  247. package/dist/guardrails/evidence-system.js.map +1 -1
  248. package/dist/guardrails/replay-ledger-io.d.ts +6 -0
  249. package/dist/guardrails/replay-ledger-io.d.ts.map +1 -0
  250. package/dist/guardrails/replay-ledger-io.js +42 -0
  251. package/dist/guardrails/replay-ledger-io.js.map +1 -0
  252. package/dist/guardrails/replay-ledger-lock-owner.d.ts +32 -0
  253. package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -0
  254. package/dist/guardrails/replay-ledger-lock-owner.js +138 -0
  255. package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -0
  256. package/dist/guardrails/replay-ledger-lock.d.ts +24 -0
  257. package/dist/guardrails/replay-ledger-lock.d.ts.map +1 -0
  258. package/dist/guardrails/replay-ledger-lock.js +135 -0
  259. package/dist/guardrails/replay-ledger-lock.js.map +1 -0
  260. package/dist/guardrails/replay-ledger-mutation-gate.d.ts +24 -0
  261. package/dist/guardrails/replay-ledger-mutation-gate.d.ts.map +1 -0
  262. package/dist/guardrails/replay-ledger-mutation-gate.js +205 -0
  263. package/dist/guardrails/replay-ledger-mutation-gate.js.map +1 -0
  264. package/dist/guardrails/replay-ledger-quarantine.d.ts +2 -0
  265. package/dist/guardrails/replay-ledger-quarantine.d.ts.map +1 -0
  266. package/dist/guardrails/replay-ledger-quarantine.js +19 -0
  267. package/dist/guardrails/replay-ledger-quarantine.js.map +1 -0
  268. package/dist/guardrails/replay-ledger-store.d.ts +32 -0
  269. package/dist/guardrails/replay-ledger-store.d.ts.map +1 -0
  270. package/dist/guardrails/replay-ledger-store.js +211 -0
  271. package/dist/guardrails/replay-ledger-store.js.map +1 -0
  272. package/dist/guardrails/shell-command-name-glob.d.ts +3 -0
  273. package/dist/guardrails/shell-command-name-glob.d.ts.map +1 -0
  274. package/dist/guardrails/shell-command-name-glob.js +233 -0
  275. package/dist/guardrails/shell-command-name-glob.js.map +1 -0
  276. package/dist/guardrails/shell-command-option.d.ts +7 -0
  277. package/dist/guardrails/shell-command-option.d.ts.map +1 -0
  278. package/dist/guardrails/shell-command-option.js +24 -0
  279. package/dist/guardrails/shell-command-option.js.map +1 -0
  280. package/dist/guardrails/shell-command-tokenizer.d.ts +29 -0
  281. package/dist/guardrails/shell-command-tokenizer.d.ts.map +1 -0
  282. package/dist/guardrails/shell-command-tokenizer.js +145 -0
  283. package/dist/guardrails/shell-command-tokenizer.js.map +1 -0
  284. package/dist/guardrails/shell-command-wrapper.d.ts +18 -0
  285. package/dist/guardrails/shell-command-wrapper.d.ts.map +1 -0
  286. package/dist/guardrails/shell-command-wrapper.js +42 -0
  287. package/dist/guardrails/shell-command-wrapper.js.map +1 -0
  288. package/dist/guardrails/verified-executor.d.ts +65 -0
  289. package/dist/guardrails/verified-executor.d.ts.map +1 -0
  290. package/dist/guardrails/verified-executor.js +171 -0
  291. package/dist/guardrails/verified-executor.js.map +1 -0
  292. package/dist/guardrails/workspace-fingerprint.d.ts +23 -0
  293. package/dist/guardrails/workspace-fingerprint.d.ts.map +1 -0
  294. package/dist/guardrails/workspace-fingerprint.js +634 -0
  295. package/dist/guardrails/workspace-fingerprint.js.map +1 -0
  296. package/dist/index.d.ts +15 -3
  297. package/dist/index.d.ts.map +1 -1
  298. package/dist/index.js +13 -1
  299. package/dist/index.js.map +1 -1
  300. package/dist/main.d.ts.map +1 -1
  301. package/dist/main.js +12 -0
  302. package/dist/main.js.map +1 -1
  303. package/dist/modes/interactive/components/footer.d.ts +1 -1
  304. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  305. package/dist/modes/interactive/components/footer.js +11 -8
  306. package/dist/modes/interactive/components/footer.js.map +1 -1
  307. package/dist/modes/interactive/interactive-mode.d.ts +3 -0
  308. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  309. package/dist/modes/interactive/interactive-mode.js +59 -9
  310. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  311. package/dist/modes/interactive/theme/omk-aurora-dark.json +80 -0
  312. package/dist/modes/interactive/theme/omk-aurora-light.json +80 -0
  313. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  314. package/dist/modes/interactive/theme/theme.js +5 -0
  315. package/dist/modes/interactive/theme/theme.js.map +1 -1
  316. package/dist/modes/print-mode.d.ts.map +1 -1
  317. package/dist/modes/print-mode.js +43 -2
  318. package/dist/modes/print-mode.js.map +1 -1
  319. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  320. package/dist/modes/rpc/rpc-mode.js +23 -5
  321. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  322. package/dist/modes/rpc/rpc-types.d.ts +3 -0
  323. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  324. package/dist/modes/rpc/rpc-types.js.map +1 -1
  325. package/dist/types/evidence.d.ts +211 -1
  326. package/dist/types/evidence.d.ts.map +1 -1
  327. package/dist/types/evidence.js.map +1 -1
  328. package/dist/verify-ci.d.ts +3 -0
  329. package/dist/verify-ci.d.ts.map +1 -0
  330. package/dist/verify-ci.js +30 -0
  331. package/dist/verify-ci.js.map +1 -0
  332. package/docs/adaptorch-preview.md +1 -1
  333. package/docs/compaction.md +38 -4
  334. package/docs/extensions.md +2 -0
  335. package/docs/provider-resilience.md +87 -0
  336. package/docs/sdk.md +238 -1
  337. package/docs/settings.md +80 -2
  338. package/docs/themes.md +2 -0
  339. package/docs/usage.md +38 -0
  340. package/examples/extensions/custom-provider-anthropic/package-lock.json +5 -5
  341. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  342. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  343. package/examples/extensions/gondolin/package-lock.json +2 -2
  344. package/examples/extensions/gondolin/package.json +1 -1
  345. package/examples/extensions/sandbox/package-lock.json +2 -2
  346. package/examples/extensions/sandbox/package.json +1 -1
  347. package/examples/extensions/with-deps/package-lock.json +4 -4
  348. package/examples/extensions/with-deps/package.json +1 -1
  349. package/npm-shrinkwrap.json +22 -13
  350. package/package.json +6 -5
@@ -1,136 +1,1329 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
+ import { BlockList, isIP } from "node:net";
2
3
  import { homedir } from "node:os";
3
4
  import { join } from "node:path";
5
+ import { findEnvKeys, getModels, getProviders } from "omk-ai";
6
+ import { getAgentDir as getConfiguredAgentDir } from "../config.js";
7
+ import { isConfigValueConfigured, resolveConfigValueOrThrow } from "../core/resolve-config-value.js";
8
+ import { stripJsonComments } from "../utils/json.js";
9
+ /** Fixed-message marker used to classify auth resolution without exposing its cause. */
10
+ export class ProviderDoctorAuthMaterializationError extends Error {
11
+ constructor() {
12
+ super("provider doctor authentication could not be materialized");
13
+ this.name = "ProviderDoctorAuthMaterializationError";
14
+ }
15
+ }
16
+ const DEFAULT_TIMEOUT_MS = 5_000;
17
+ const KNOWN_APIS = new Set([
18
+ "openai-completions",
19
+ "mistral-conversations",
20
+ "openai-responses",
21
+ "azure-openai-responses",
22
+ "openai-codex-responses",
23
+ "anthropic-messages",
24
+ "bedrock-converse-stream",
25
+ "google-generative-ai",
26
+ "google-vertex",
27
+ ]);
28
+ const OPENAI_COMPATIBLE_APIS = new Set(["openai-completions", "openai-responses"]);
29
+ const ABORTED = Symbol("provider-doctor-aborted");
30
+ // Family-specific lists: node BlockList canonicalizes IPv4 checks to the v4-mapped v6 form,
31
+ // so a combined list containing ::ffff:0:0/96 would block every IPv4 address.
32
+ const blockedPublicV4 = new BlockList();
33
+ for (const [address, prefix] of [
34
+ ["0.0.0.0", 8],
35
+ ["10.0.0.0", 8],
36
+ ["100.64.0.0", 10],
37
+ ["127.0.0.0", 8],
38
+ ["169.254.0.0", 16],
39
+ ["172.16.0.0", 12],
40
+ ["192.168.0.0", 16],
41
+ ["224.0.0.0", 4],
42
+ ["240.0.0.0", 4],
43
+ ]) {
44
+ blockedPublicV4.addSubnet(address, prefix, "ipv4");
45
+ }
46
+ // ::ffff:0:0/96 stays here on purpose: a v4-mapped IPv6 literal or DNS answer is anomalous
47
+ // for a public origin and is rejected wholesale (fail closed), covering hex-form mappings too.
48
+ const blockedPublicV6 = new BlockList();
49
+ for (const [address, prefix] of [
50
+ ["::", 128],
51
+ ["::1", 128],
52
+ ["::ffff:0:0", 96],
53
+ ["fc00::", 7],
54
+ ["fe80::", 10],
55
+ ["ff00::", 8],
56
+ ]) {
57
+ blockedPublicV6.addSubnet(address, prefix, "ipv6");
58
+ }
59
+ const loopbackAddresses = new BlockList();
60
+ loopbackAddresses.addSubnet("127.0.0.0", 8, "ipv4");
61
+ loopbackAddresses.addAddress("::1", "ipv6");
62
+ loopbackAddresses.addSubnet("::ffff:127.0.0.0", 104, "ipv6");
63
+ /** True when the literal IP address is a loopback address. Non-IP input is never loopback. */
64
+ export function isLoopbackAddress(address) {
65
+ const family = isIP(address);
66
+ if (family === 4)
67
+ return loopbackAddresses.check(address, "ipv4");
68
+ if (family === 6)
69
+ return loopbackAddresses.check(address, "ipv6");
70
+ return false;
71
+ }
72
+ /** True when the literal IP address must not be dialed for a public origin. Non-IP input fails closed. */
73
+ export function isBlockedPublicAddress(address) {
74
+ const family = isIP(address);
75
+ if (family === 4)
76
+ return blockedPublicV4.check(address, "ipv4");
77
+ if (family === 6)
78
+ return blockedPublicV6.check(address, "ipv6");
79
+ return true;
80
+ }
4
81
  export function getAgentDir() {
5
- return join(homedir(), ".omk", "agent");
82
+ return getConfiguredAgentDir();
6
83
  }
7
- function loadJson(path) {
8
- if (!existsSync(path))
84
+ function isRecord(value) {
85
+ return typeof value === "object" && value !== null && !Array.isArray(value);
86
+ }
87
+ function isOptionalNonemptyString(value) {
88
+ return value === undefined || (typeof value === "string" && value.length > 0);
89
+ }
90
+ function parseModelsConfig(value) {
91
+ if (!isRecord(value) || !isRecord(value.providers))
92
+ return undefined;
93
+ const providers = {};
94
+ for (const [providerId, rawProvider] of Object.entries(value.providers)) {
95
+ if (!isRecord(rawProvider))
96
+ return undefined;
97
+ if (!isOptionalNonemptyString(rawProvider.baseUrl) ||
98
+ !isOptionalNonemptyString(rawProvider.apiKey) ||
99
+ !isOptionalNonemptyString(rawProvider.api)) {
100
+ return undefined;
101
+ }
102
+ let headers;
103
+ if (rawProvider.headers !== undefined) {
104
+ if (!isRecord(rawProvider.headers))
105
+ return undefined;
106
+ headers = {};
107
+ for (const [name, headerValue] of Object.entries(rawProvider.headers)) {
108
+ if (typeof headerValue !== "string")
109
+ return undefined;
110
+ headers[name] = headerValue;
111
+ }
112
+ }
113
+ let models;
114
+ if (rawProvider.models !== undefined) {
115
+ if (!Array.isArray(rawProvider.models))
116
+ return undefined;
117
+ models = [];
118
+ for (const rawModel of rawProvider.models) {
119
+ if (!isRecord(rawModel) ||
120
+ typeof rawModel.id !== "string" ||
121
+ rawModel.id.length === 0 ||
122
+ !isOptionalNonemptyString(rawModel.baseUrl) ||
123
+ !isOptionalNonemptyString(rawModel.api)) {
124
+ return undefined;
125
+ }
126
+ models.push({ id: rawModel.id, baseUrl: rawModel.baseUrl, api: rawModel.api });
127
+ }
128
+ }
129
+ providers[providerId] = {
130
+ baseUrl: rawProvider.baseUrl,
131
+ apiKey: rawProvider.apiKey,
132
+ api: rawProvider.api,
133
+ headers,
134
+ models,
135
+ };
136
+ }
137
+ return { providers };
138
+ }
139
+ function parseAuthConfig(value) {
140
+ if (!isRecord(value))
9
141
  return undefined;
142
+ const auth = {};
143
+ for (const [providerId, rawEntry] of Object.entries(value)) {
144
+ if (!isRecord(rawEntry) || (rawEntry.type !== "api_key" && rawEntry.type !== "oauth"))
145
+ return undefined;
146
+ if (rawEntry.type === "api_key" && (typeof rawEntry.key !== "string" || rawEntry.key.length === 0)) {
147
+ return undefined;
148
+ }
149
+ auth[providerId] = { type: rawEntry.type };
150
+ }
151
+ return auth;
152
+ }
153
+ const KIMI_PROVIDER_ID = "kimi-coding";
154
+ const TOML_BASIC_ESCAPES = {
155
+ '"': '"',
156
+ "\\": "\\",
157
+ b: "\b",
158
+ f: "\f",
159
+ n: "\n",
160
+ r: "\r",
161
+ t: "\t",
162
+ };
163
+ function findBasicStringEnd(text, start, multiline) {
164
+ for (let index = start; index < text.length; index++) {
165
+ if (text[index] === "\\") {
166
+ index++;
167
+ continue;
168
+ }
169
+ if (multiline ? text.startsWith('"""', index) : text[index] === '"')
170
+ return index;
171
+ }
172
+ return -1;
173
+ }
174
+ function decodeTomlBasicString(body, multiline) {
175
+ let value = "";
176
+ for (let index = 0; index < body.length; index++) {
177
+ const char = body[index];
178
+ if (char !== "\\") {
179
+ const code = char.codePointAt(0) ?? 0;
180
+ if (code < 0x20 && char !== "\t" && !(multiline && (char === "\n" || char === "\r"))) {
181
+ return { error: "invalid control character in string value" };
182
+ }
183
+ value += char;
184
+ continue;
185
+ }
186
+ const escapeChar = body[index + 1];
187
+ if (escapeChar === undefined)
188
+ return { error: "unterminated escape sequence" };
189
+ if (multiline && (escapeChar === "\n" || escapeChar === "\r")) {
190
+ index++;
191
+ while (index + 1 < body.length && /\s/.test(body[index + 1]))
192
+ index++;
193
+ continue;
194
+ }
195
+ if (escapeChar === "u" || escapeChar === "U") {
196
+ const width = escapeChar === "u" ? 4 : 8;
197
+ const hex = body.slice(index + 2, index + 2 + width);
198
+ const codePoint = /^[0-9a-fA-F]+$/.test(hex) && hex.length === width ? Number.parseInt(hex, 16) : -1;
199
+ if (codePoint < 0 || codePoint > 0x10ffff || (codePoint >= 0xd800 && codePoint <= 0xdfff)) {
200
+ return { error: "invalid unicode escape" };
201
+ }
202
+ value += String.fromCodePoint(codePoint);
203
+ index += 1 + width;
204
+ continue;
205
+ }
206
+ const mapped = TOML_BASIC_ESCAPES[escapeChar];
207
+ if (mapped === undefined)
208
+ return { error: "invalid escape sequence" };
209
+ value += mapped;
210
+ index++;
211
+ }
212
+ return { value };
213
+ }
214
+ /** Parses one TOML string value. Errors are fixed and never include raw TOML content. */
215
+ function parseTomlStringValue(raw, requireNonempty = true) {
216
+ const text = raw.trim();
217
+ const multilineBasic = text.startsWith('"""');
218
+ const multilineLiteral = text.startsWith("'''");
219
+ const basic = !multilineBasic && text.startsWith('"');
220
+ const literal = !multilineLiteral && text.startsWith("'");
221
+ if (!multilineBasic && !multilineLiteral && !basic && !literal) {
222
+ return { error: "value must be a quoted string" };
223
+ }
224
+ const delimiterWidth = multilineBasic || multilineLiteral ? 3 : 1;
225
+ const end = multilineBasic || basic
226
+ ? findBasicStringEnd(text, delimiterWidth, multilineBasic)
227
+ : text.indexOf(multilineLiteral ? "'''" : "'", delimiterWidth);
228
+ if (end < 0) {
229
+ return {
230
+ error: multilineBasic || multilineLiteral ? "unterminated multiline string value" : "unterminated string value",
231
+ };
232
+ }
233
+ if (text.slice(end + delimiterWidth).trim().length > 0) {
234
+ return { error: "unexpected content after string value" };
235
+ }
236
+ let body = text.slice(delimiterWidth, end);
237
+ if ((multilineBasic || multilineLiteral) && body.startsWith("\n"))
238
+ body = body.slice(1);
239
+ const parsed = multilineBasic || basic
240
+ ? decodeTomlBasicString(body, multilineBasic)
241
+ : /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(body)
242
+ ? { error: "invalid control character in string value" }
243
+ : { value: body };
244
+ if ("error" in parsed)
245
+ return parsed;
246
+ return requireNonempty && parsed.value.length === 0 ? { error: "empty string value" } : parsed;
247
+ }
248
+ function scanTomlLine(text, state) {
249
+ let content = "";
250
+ for (let index = 0; index < text.length; index++) {
251
+ const char = text[index];
252
+ if (state.quote === "basic") {
253
+ content += char;
254
+ if (char === "\\" && index + 1 < text.length)
255
+ content += text[++index];
256
+ else if (char === '"')
257
+ state.quote = undefined;
258
+ continue;
259
+ }
260
+ if (state.quote === "literal") {
261
+ content += char;
262
+ if (char === "'")
263
+ state.quote = undefined;
264
+ continue;
265
+ }
266
+ if (state.quote === "multiline-basic") {
267
+ if (text.startsWith('"""', index)) {
268
+ content += '"""';
269
+ index += 2;
270
+ state.quote = undefined;
271
+ }
272
+ else {
273
+ content += char;
274
+ if (char === "\\" && index + 1 < text.length)
275
+ content += text[++index];
276
+ }
277
+ continue;
278
+ }
279
+ if (state.quote === "multiline-literal") {
280
+ if (text.startsWith("'''", index)) {
281
+ content += "'''";
282
+ index += 2;
283
+ state.quote = undefined;
284
+ }
285
+ else {
286
+ content += char;
287
+ }
288
+ continue;
289
+ }
290
+ if (char === "#")
291
+ break;
292
+ if (text.startsWith('"""', index)) {
293
+ content += '"""';
294
+ index += 2;
295
+ state.quote = "multiline-basic";
296
+ continue;
297
+ }
298
+ if (text.startsWith("'''", index)) {
299
+ content += "'''";
300
+ index += 2;
301
+ state.quote = "multiline-literal";
302
+ continue;
303
+ }
304
+ content += char;
305
+ if (char === '"')
306
+ state.quote = "basic";
307
+ else if (char === "'")
308
+ state.quote = "literal";
309
+ else if (char === "[" || char === "{")
310
+ state.containers.push(char);
311
+ else if (char === "]" || char === "}") {
312
+ const expected = char === "]" ? "[" : "{";
313
+ if (state.containers.pop() !== expected)
314
+ return { content, error: "mismatched value delimiter" };
315
+ }
316
+ }
317
+ if (state.quote === "basic" || state.quote === "literal") {
318
+ return { content, error: "unterminated string value" };
319
+ }
320
+ return { content };
321
+ }
322
+ function consumeTomlValue(lines, startLine, initial) {
323
+ const state = { containers: [] };
324
+ const chunks = [];
325
+ for (let line = startLine; line < lines.length; line++) {
326
+ const scanned = scanTomlLine(line === startLine ? initial : lines[line], state);
327
+ chunks.push(scanned.content);
328
+ if (scanned.error)
329
+ return { endLine: line, error: scanned.error };
330
+ if (!state.quote && state.containers.length === 0) {
331
+ const value = chunks.join("\n").trim();
332
+ return value.length > 0
333
+ ? { value, endLine: line }
334
+ : { endLine: startLine, error: "assignment value is missing" };
335
+ }
336
+ }
337
+ return {
338
+ endLine: startLine,
339
+ error: state.quote ? "unterminated multiline string value" : "unterminated compound value",
340
+ };
341
+ }
342
+ function findTomlTopLevelCharacter(text, wanted) {
343
+ const state = { containers: [] };
344
+ for (let index = 0; index < text.length; index++) {
345
+ const char = text[index];
346
+ if (state.quote === "basic" || state.quote === "multiline-basic") {
347
+ if (char === "\\")
348
+ index++;
349
+ else if (state.quote === "basic" && char === '"')
350
+ state.quote = undefined;
351
+ else if (state.quote === "multiline-basic" && text.startsWith('"""', index)) {
352
+ state.quote = undefined;
353
+ index += 2;
354
+ }
355
+ continue;
356
+ }
357
+ if (state.quote === "literal" || state.quote === "multiline-literal") {
358
+ const delimiter = state.quote === "literal" ? "'" : "'''";
359
+ if (text.startsWith(delimiter, index)) {
360
+ state.quote = undefined;
361
+ index += delimiter.length - 1;
362
+ }
363
+ continue;
364
+ }
365
+ if (char === "#")
366
+ return -1;
367
+ if (text.startsWith('"""', index)) {
368
+ state.quote = "multiline-basic";
369
+ index += 2;
370
+ }
371
+ else if (text.startsWith("'''", index)) {
372
+ state.quote = "multiline-literal";
373
+ index += 2;
374
+ }
375
+ else if (char === '"')
376
+ state.quote = "basic";
377
+ else if (char === "'")
378
+ state.quote = "literal";
379
+ else if (char === "[" || char === "{")
380
+ state.containers.push(char);
381
+ else if (char === "]" || char === "}")
382
+ state.containers.pop();
383
+ else if (char === wanted && state.containers.length === 0)
384
+ return index;
385
+ }
386
+ return -1;
387
+ }
388
+ function parseTomlDottedKey(raw) {
389
+ const components = [];
390
+ let index = 0;
391
+ while (index < raw.length) {
392
+ while (/\s/.test(raw[index] ?? ""))
393
+ index++;
394
+ let component;
395
+ if (raw[index] === '"') {
396
+ const end = findBasicStringEnd(raw, index + 1, false);
397
+ if (end < 0)
398
+ return undefined;
399
+ const parsed = parseTomlStringValue(raw.slice(index, end + 1), false);
400
+ if ("error" in parsed)
401
+ return undefined;
402
+ component = parsed.value;
403
+ index = end + 1;
404
+ }
405
+ else if (raw[index] === "'") {
406
+ const end = raw.indexOf("'", index + 1);
407
+ if (end < 0)
408
+ return undefined;
409
+ component = raw.slice(index + 1, end);
410
+ index = end + 1;
411
+ }
412
+ else {
413
+ const match = /^[A-Za-z0-9_-]+/.exec(raw.slice(index));
414
+ if (!match)
415
+ return undefined;
416
+ component = match[0];
417
+ index += match[0].length;
418
+ }
419
+ components.push(component);
420
+ while (/\s/.test(raw[index] ?? ""))
421
+ index++;
422
+ if (index === raw.length)
423
+ return components;
424
+ if (raw[index] !== ".")
425
+ return undefined;
426
+ index++;
427
+ if (index === raw.length)
428
+ return undefined;
429
+ }
430
+ return undefined;
431
+ }
432
+ function splitTomlTopLevel(raw) {
433
+ const parts = [];
434
+ let start = 0;
435
+ for (let index = 0; index < raw.length; index++) {
436
+ if (raw[index] !== ",")
437
+ continue;
438
+ const relative = findTomlTopLevelCharacter(raw.slice(start, index + 1), ",");
439
+ if (relative !== index - start)
440
+ continue;
441
+ parts.push(raw.slice(start, index).trim());
442
+ start = index + 1;
443
+ }
444
+ parts.push(raw.slice(start).trim());
445
+ return parts;
446
+ }
447
+ function isValidTomlScalar(raw) {
448
+ return (raw === "true" ||
449
+ raw === "false" ||
450
+ /^[+-]?(?:0|[1-9](?:_?\d)*)$/.test(raw) ||
451
+ /^[+-]?0x[0-9A-Fa-f](?:_?[0-9A-Fa-f])*$/.test(raw) ||
452
+ /^[+-]?0o[0-7](?:_?[0-7])*$/.test(raw) ||
453
+ /^[+-]?0b[01](?:_?[01])*$/.test(raw) ||
454
+ /^[+-]?(?:(?:0|[1-9](?:_?\d)*)\.(?:\d(?:_?\d)*)(?:[eE][+-]?\d(?:_?\d)*)?|(?:0|[1-9](?:_?\d)*)[eE][+-]?\d(?:_?\d)*|inf|nan)$/.test(raw) ||
455
+ /^\d{4}-\d{2}-\d{2}(?:[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2})?)?$/.test(raw) ||
456
+ /^\d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(raw));
457
+ }
458
+ function isValidTomlValue(raw) {
459
+ const text = raw.trim();
460
+ if (text.startsWith('"') || text.startsWith("'"))
461
+ return !("error" in parseTomlStringValue(text, false));
462
+ if (text.startsWith("[") && text.endsWith("]")) {
463
+ const parts = splitTomlTopLevel(text.slice(1, -1));
464
+ if (parts.at(-1) === "")
465
+ parts.pop();
466
+ return parts.every((part) => part.length > 0 && isValidTomlValue(part));
467
+ }
468
+ if (text.startsWith("{") && text.endsWith("}")) {
469
+ const body = text.slice(1, -1).trim();
470
+ if (body.length === 0)
471
+ return true;
472
+ return splitTomlTopLevel(body).every((part) => {
473
+ const equals = findTomlTopLevelCharacter(part, "=");
474
+ return equals > 0 && !!parseTomlDottedKey(part.slice(0, equals)) && isValidTomlValue(part.slice(equals + 1));
475
+ });
476
+ }
477
+ return isValidTomlScalar(text);
478
+ }
479
+ function parseTomlTable(line) {
480
+ const state = { containers: [] };
481
+ const scanned = scanTomlLine(line, state);
482
+ if (scanned.error || state.quote || state.containers.length > 0)
483
+ return { error: "malformed table header" };
484
+ const text = scanned.content.trim();
485
+ const array = text.startsWith("[[");
486
+ if (array ? !text.endsWith("]]") : !text.endsWith("]"))
487
+ return { error: "malformed table header" };
488
+ const inner = text.slice(array ? 2 : 1, array ? -2 : -1).trim();
489
+ const components = parseTomlDottedKey(inner);
490
+ return components && components.length > 0 ? { components, array } : { error: "malformed table header" };
491
+ }
492
+ /**
493
+ * Strict Kimi TOML subset reader. It validates every table/assignment and generic TOML value,
494
+ * but extracts only root `base_url`/`model_name` and `[providers.<name>]` string fields. Fixed
495
+ * line-number errors ensure credentials and other file content are never echoed.
496
+ */
497
+ function loadKimiConfig(path) {
498
+ if (!existsSync(path))
499
+ return { kind: "missing" };
500
+ let content;
10
501
  try {
11
- return JSON.parse(readFileSync(path, "utf-8"));
502
+ content = readFileSync(path, "utf-8");
503
+ }
504
+ catch {
505
+ return { kind: "invalid", line: 0, reason: "file is unreadable" };
506
+ }
507
+ const value = { providers: [] };
508
+ const lines = content.split(/\r?\n/);
509
+ const assignments = new Set();
510
+ let table = "root";
511
+ let scope = "root";
512
+ for (let index = 0; index < lines.length; index++) {
513
+ const line = lines[index].trim();
514
+ if (line.length === 0 || line.startsWith("#"))
515
+ continue;
516
+ if (line.startsWith("[")) {
517
+ const parsed = parseTomlTable(line);
518
+ if (parsed.error || !parsed.components) {
519
+ return { kind: "invalid", line: index + 1, reason: parsed.error ?? "malformed table header" };
520
+ }
521
+ const [first, name] = parsed.components;
522
+ if (first === "providers" && parsed.array) {
523
+ return { kind: "invalid", line: index + 1, reason: "provider table cannot be an array" };
524
+ }
525
+ if (first === "providers" && parsed.components.length === 2 && name !== undefined && name.length > 0) {
526
+ const entry = { name };
527
+ value.providers.push(entry);
528
+ table = entry;
529
+ }
530
+ else {
531
+ table = "other";
532
+ }
533
+ scope = `${parsed.array ? "array" : "table"}:${JSON.stringify(parsed.components)}`;
534
+ continue;
535
+ }
536
+ const assignmentLine = index;
537
+ const equals = findTomlTopLevelCharacter(line, "=");
538
+ const key = equals > 0 ? parseTomlDottedKey(line.slice(0, equals)) : undefined;
539
+ if (!key || key.length === 0)
540
+ return { kind: "invalid", line: index + 1, reason: "invalid assignment" };
541
+ const consumed = consumeTomlValue(lines, index, line.slice(equals + 1));
542
+ if (consumed.error || consumed.value === undefined) {
543
+ return { kind: "invalid", line: assignmentLine + 1, reason: consumed.error ?? "invalid assignment value" };
544
+ }
545
+ index = consumed.endLine;
546
+ if (!isValidTomlValue(consumed.value)) {
547
+ return { kind: "invalid", line: assignmentLine + 1, reason: "invalid assignment value" };
548
+ }
549
+ const assignmentId = `${scope}:${JSON.stringify(key)}`;
550
+ if (assignments.has(assignmentId)) {
551
+ return { kind: "invalid", line: assignmentLine + 1, reason: "duplicate assignment" };
552
+ }
553
+ assignments.add(assignmentId);
554
+ if (key.length !== 1 || table === "other")
555
+ continue;
556
+ const keyName = key[0];
557
+ const relevant = table === "root"
558
+ ? keyName === "base_url" || keyName === "model_name"
559
+ : keyName === "base_url" ||
560
+ keyName === "model_name" ||
561
+ keyName === "model" ||
562
+ keyName === "type" ||
563
+ keyName === "api_key";
564
+ if (!relevant)
565
+ continue;
566
+ const parsedValue = parseTomlStringValue(consumed.value);
567
+ if ("error" in parsedValue)
568
+ return { kind: "invalid", line: assignmentLine + 1, reason: parsedValue.error };
569
+ if (table === "root") {
570
+ if (keyName === "base_url")
571
+ value.baseUrl = parsedValue.value;
572
+ else
573
+ value.modelName = parsedValue.value;
574
+ }
575
+ else {
576
+ if (keyName === "base_url")
577
+ table.baseUrl = parsedValue.value;
578
+ else if (keyName === "type")
579
+ table.type = parsedValue.value;
580
+ else if (keyName === "api_key")
581
+ table.apiKey = parsedValue.value;
582
+ else
583
+ table.modelName = parsedValue.value;
584
+ }
585
+ }
586
+ return { kind: "ok", value };
587
+ }
588
+ function nonemptyEnv(name) {
589
+ const value = process.env[name];
590
+ return value && value.length > 0 ? value : undefined;
591
+ }
592
+ function resolveKimiConfig(kimiConfigPath) {
593
+ const path = kimiConfigPath ?? join(homedir(), ".kimi", "config.toml");
594
+ const loaded = loadKimiConfig(path);
595
+ if (loaded.kind === "invalid")
596
+ return { failure: { line: loaded.line, reason: loaded.reason } };
597
+ const fileValues = loaded.kind === "ok" ? loaded.value : { providers: [] };
598
+ const envBaseUrl = nonemptyEnv("KIMI_BASE_URL");
599
+ const envModelName = nonemptyEnv("KIMI_MODEL_NAME");
600
+ const custom = fileValues.providers.find((entry) => entry.type === "openai_legacy" && entry.baseUrl);
601
+ return {
602
+ baseUrl: envBaseUrl ?? fileValues.baseUrl,
603
+ modelName: envModelName ?? fileValues.modelName,
604
+ baseUrlSource: envBaseUrl ? "kimi-environment" : fileValues.baseUrl ? "kimi-config-toml" : undefined,
605
+ customProvider: custom
606
+ ? { name: custom.name, baseUrl: custom.baseUrl, modelName: custom.modelName, apiKey: custom.apiKey }
607
+ : undefined,
608
+ };
609
+ }
610
+ function loadConfig(path, parse, allowComments = false) {
611
+ if (!existsSync(path))
612
+ return { kind: "missing" };
613
+ try {
614
+ const content = readFileSync(path, "utf-8");
615
+ const parsed = JSON.parse(allowComments ? stripJsonComments(content) : content);
616
+ const value = parse(parsed);
617
+ return value ? { kind: "ok", value } : { kind: "invalid" };
618
+ }
619
+ catch {
620
+ return { kind: "invalid" };
621
+ }
622
+ }
623
+ export function normalizedUrlHostname(url) {
624
+ return normalizedHostname(url);
625
+ }
626
+ function normalizedHostname(url) {
627
+ const hostname = url.hostname.toLowerCase();
628
+ const unwrapped = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
629
+ return unwrapped.endsWith(".") ? unwrapped.slice(0, -1) : unwrapped;
630
+ }
631
+ function isLiteralLoopback(hostname) {
632
+ if (hostname === "localhost" || hostname === "::1")
633
+ return true;
634
+ if (isIP(hostname) !== 4)
635
+ return false;
636
+ return Number(hostname.split(".")[0]) === 127;
637
+ }
638
+ function isBlockedPublicHost(hostname) {
639
+ if (hostname === "localhost" || hostname.endsWith(".localhost"))
640
+ return true;
641
+ if (isIP(hostname) === 0)
642
+ return false;
643
+ return isBlockedPublicAddress(hostname);
644
+ }
645
+ function sanitizeUrl(rawUrl) {
646
+ try {
647
+ const url = new URL(rawUrl);
648
+ url.username = "";
649
+ url.password = "";
650
+ url.search = "";
651
+ url.hash = "";
652
+ return url;
12
653
  }
13
654
  catch {
14
655
  return undefined;
15
656
  }
16
657
  }
17
- function resolveConfig(providerId) {
18
- const agentDir = getAgentDir();
19
- const modelsPath = join(agentDir, "models.json");
20
- const models = loadJson(modelsPath);
21
- if (models?.providers?.[providerId]) {
22
- return { config: models.providers[providerId], source: "models.json" };
658
+ export function canonicalProviderEndpointUrl(rawUrl) {
659
+ if (!rawUrl)
660
+ return undefined;
661
+ const url = sanitizeUrl(rawUrl);
662
+ if (!url)
663
+ return undefined;
664
+ const hostname = normalizedHostname(url);
665
+ const host = isIP(hostname) === 6 ? `[${hostname}]` : hostname;
666
+ const pathname = url.pathname.replace(/\/+$/, "") || "/";
667
+ return `${url.protocol}//${host}${url.port ? `:${url.port}` : ""}${pathname}`;
668
+ }
669
+ function credentialBindingMatchesTarget(binding, target) {
670
+ return (binding.origin === target.origin &&
671
+ binding.source === target.source &&
672
+ binding.baseUrl === canonicalProviderEndpointUrl(target.endpoint.baseUrl) &&
673
+ binding.api === target.endpoint.api &&
674
+ binding.modelId === target.endpoint.modelId);
675
+ }
676
+ function appendPath(url, segment) {
677
+ const next = new URL(url.href);
678
+ next.pathname = `${next.pathname.replace(/\/$/, "")}/${segment}`;
679
+ return next;
680
+ }
681
+ function makeRootProbeUrl(url, origin) {
682
+ if (origin !== "local-proxy")
683
+ return new URL(url.href);
684
+ const health = new URL(url.href);
685
+ health.pathname = "/health";
686
+ return health;
687
+ }
688
+ function publicResult(target, level, checks, url, failure, costWarning) {
689
+ const targetUrl = url?.href;
690
+ return {
691
+ provider: target.providerId,
692
+ status: failure ? "fail" : "ok",
693
+ level,
694
+ origin: target.origin,
695
+ source: target.source,
696
+ targetUrl,
697
+ baseUrl: targetUrl,
698
+ api: target.endpoint.api,
699
+ modelId: target.endpoint.modelId,
700
+ authPresent: target.auth.present,
701
+ ...(costWarning ? { costWarning: true } : {}),
702
+ checks,
703
+ error: failure,
704
+ };
705
+ }
706
+ function unresolvedFailure(providerId, level, code, message) {
707
+ const check = { name: "config", status: "fail", category: "config", code, message };
708
+ return {
709
+ provider: providerId,
710
+ status: "fail",
711
+ level,
712
+ origin: "unknown",
713
+ source: "unresolved",
714
+ authPresent: false,
715
+ checks: [check],
716
+ error: { category: "config", code, message },
717
+ };
718
+ }
719
+ function staticChecks(target) {
720
+ const checks = [
721
+ { name: "config-present", status: "ok", code: "ok", message: "Provider configuration resolved" },
722
+ ];
723
+ if (target.origin === "custom-openai-compatible") {
724
+ checks.push({
725
+ name: "native-provider-checks",
726
+ status: "skipped",
727
+ code: "ok",
728
+ message: "Custom OpenAI-compatible endpoint; native-only provider checks skipped",
729
+ });
23
730
  }
24
- const authPath = join(agentDir, "auth.json");
25
- const auth = loadJson(authPath);
26
- if (auth?.[providerId]) {
27
- return {
28
- config: { apiKey: auth[providerId].key },
29
- source: "auth.json",
731
+ let failure;
732
+ const recordFailure = (next) => {
733
+ failure ??= next;
734
+ };
735
+ const requestedModel = target.endpoint.modelId;
736
+ const modelIds = target.endpoint.modelIds ?? [];
737
+ if (!requestedModel) {
738
+ checks.push({ name: "model-provider-relation", status: "skipped", code: "ok", message: "No model selected" });
739
+ }
740
+ else if (!modelIds.includes(requestedModel)) {
741
+ const next = {
742
+ category: "model",
743
+ code: "model-provider-mismatch",
744
+ message: "Selected model does not belong to the provider",
745
+ };
746
+ checks.push({ name: "model-provider-relation", status: "fail", ...next });
747
+ recordFailure(next);
748
+ }
749
+ else {
750
+ checks.push({ name: "model-provider-relation", status: "ok", code: "ok", message: "Model belongs to provider" });
751
+ }
752
+ const api = target.endpoint.api;
753
+ if (!api) {
754
+ const next = { category: "config", code: "api-missing", message: "Provider API type is missing" };
755
+ checks.push({ name: "api", status: "fail", ...next });
756
+ recordFailure(next);
757
+ }
758
+ else if (!KNOWN_APIS.has(api) ||
759
+ (target.origin === "custom-openai-compatible" && !OPENAI_COMPATIBLE_APIS.has(api))) {
760
+ const next = {
761
+ category: "config",
762
+ code: "api-unsupported",
763
+ message: "Provider API type is unsupported",
764
+ };
765
+ checks.push({ name: "api", status: "fail", ...next });
766
+ recordFailure(next);
767
+ }
768
+ else {
769
+ checks.push({ name: "api", status: "ok", code: "ok", message: "Provider API type is supported" });
770
+ }
771
+ let url;
772
+ if (!target.endpoint.baseUrl) {
773
+ const next = {
774
+ category: "config",
775
+ code: "base-url-missing",
776
+ message: "Provider base URL is missing",
777
+ };
778
+ checks.push({ name: "base-url", status: "fail", ...next });
779
+ recordFailure(next);
780
+ }
781
+ else {
782
+ url = sanitizeUrl(target.endpoint.baseUrl);
783
+ if (!url) {
784
+ const next = {
785
+ category: "config",
786
+ code: "base-url-invalid",
787
+ message: "Provider base URL is invalid",
788
+ };
789
+ checks.push({ name: "base-url", status: "fail", ...next });
790
+ recordFailure(next);
791
+ }
792
+ else {
793
+ const allowedScheme = target.origin === "local-proxy"
794
+ ? url.protocol === "http:" || url.protocol === "https:"
795
+ : url.protocol === "https:";
796
+ if (!allowedScheme) {
797
+ const next = {
798
+ category: "config",
799
+ code: "url-scheme-unsupported",
800
+ message: "Provider URL scheme is not allowed",
801
+ };
802
+ checks.push({ name: "base-url", status: "fail", ...next });
803
+ recordFailure(next);
804
+ }
805
+ else {
806
+ checks.push({ name: "base-url", status: "ok", code: "ok", message: "Provider base URL is valid" });
807
+ }
808
+ }
809
+ }
810
+ if (target.origin === "unknown") {
811
+ const next = {
812
+ category: "config",
813
+ code: "origin-unsupported",
814
+ message: "Provider origin is unsupported",
815
+ };
816
+ checks.push({ name: "origin-policy", status: "fail", ...next });
817
+ recordFailure(next);
818
+ }
819
+ else if (url) {
820
+ const hostname = normalizedHostname(url);
821
+ const blocked = target.origin === "local-proxy" ? !isLiteralLoopback(hostname) : isBlockedPublicHost(hostname);
822
+ if (blocked) {
823
+ const next = {
824
+ category: "config",
825
+ code: "address-policy-blocked",
826
+ message: "Provider address is blocked by origin policy",
827
+ };
828
+ checks.push({ name: "origin-policy", status: "fail", ...next });
829
+ recordFailure(next);
830
+ }
831
+ else {
832
+ checks.push({ name: "origin-policy", status: "ok", code: "ok", message: "Provider origin policy passed" });
833
+ }
834
+ }
835
+ else {
836
+ checks.push({ name: "origin-policy", status: "skipped", code: "ok", message: "No valid URL to inspect" });
837
+ }
838
+ if (target.auth.present) {
839
+ checks.push({ name: "auth-present", status: "ok", code: "ok", message: "Authentication is configured" });
840
+ }
841
+ else if (target.origin === "custom-openai-compatible" || target.origin === "local-proxy") {
842
+ // Native login checks do not apply: custom/local OpenAI-compatible endpoints may be keyless.
843
+ checks.push({
844
+ name: "auth-present",
845
+ status: "skipped",
846
+ code: "ok",
847
+ message: "No credential configured; custom endpoints may be keyless",
848
+ });
849
+ }
850
+ else {
851
+ const next = {
852
+ category: "auth",
853
+ code: "auth-missing",
854
+ message: "Authentication is not configured",
30
855
  };
856
+ checks.push({ name: "auth-present", status: "fail", ...next });
857
+ recordFailure(next);
858
+ }
859
+ return { checks, url, failure };
860
+ }
861
+ function classifyProbe(name, kind, status) {
862
+ const forModels = kind === "models";
863
+ if (Number.isInteger(status) && status >= 200 && status < 300) {
864
+ const probe = { reachable: true, authenticated: true, status, category: "ok" };
865
+ if (forModels)
866
+ probe.modelsSupported = true;
867
+ return { check: { name, status: "ok", code: "ok", message: `HTTP ${status}`, probe } };
31
868
  }
32
- // Built-in local Grok OAuth proxy defaults (no secrets; loopback only).
33
- if (providerId === "grok-oauth-proxy") {
869
+ if (status >= 300 && status < 400) {
870
+ const failure = {
871
+ category: "config",
872
+ code: "redirect-blocked",
873
+ message: "Provider probe returned a blocked redirect",
874
+ };
875
+ return { check: { name, status: "fail", ...failure }, failure };
876
+ }
877
+ if (status === 401 || status === 403) {
878
+ const failure = {
879
+ category: "auth",
880
+ code: "authentication-failed",
881
+ message: "Provider rejected authentication",
882
+ };
883
+ const probe = { reachable: true, authenticated: false, status, category: "auth" };
884
+ return { check: { name, status: "fail", ...failure, probe }, failure };
885
+ }
886
+ if (status === 404 || status === 405 || status === 501) {
887
+ // A root or /models 404 is neutral: OpenAI-compatible servers may not expose these paths.
888
+ const probe = { reachable: true, status, category: "unsupported-endpoint" };
889
+ if (forModels)
890
+ probe.modelsSupported = false;
34
891
  return {
35
- config: {
36
- baseUrl: "http://127.0.0.1:9996/v1",
37
- api: "openai-completions",
38
- apiKey: "dummy",
892
+ check: {
893
+ name,
894
+ status: "unsupported",
895
+ code: "endpoint-unsupported",
896
+ message: `HTTP ${status}; endpoint is not required`,
897
+ probe,
39
898
  },
40
- source: "built-in-grok-oauth-proxy-defaults",
41
899
  };
42
900
  }
43
- return undefined;
901
+ if (status >= 500 && status <= 599) {
902
+ const failure = {
903
+ category: "server",
904
+ code: "server-error",
905
+ message: "Provider endpoint returned a server error",
906
+ };
907
+ const probe = { reachable: true, status, category: "server" };
908
+ return { check: { name, status: "fail", ...failure, probe }, failure };
909
+ }
910
+ if (status === 408 || status === 429) {
911
+ const failure = {
912
+ category: "network",
913
+ code: "network-failure",
914
+ message: "Provider probe failed at the network boundary",
915
+ };
916
+ const probe = { reachable: true, status, category: "network" };
917
+ return { check: { name, status: "fail", ...failure, probe }, failure };
918
+ }
919
+ const failure = {
920
+ category: "config",
921
+ code: "unexpected-response",
922
+ message: "Provider probe returned an unexpected response",
923
+ };
924
+ return { check: { name, status: "fail", ...failure }, failure };
44
925
  }
45
- export async function diagnoseProvider(providerId) {
46
- const resolved = resolveConfig(providerId);
47
- if (!resolved) {
48
- return {
49
- provider: providerId,
50
- status: "fail",
51
- checks: [],
52
- error: { category: "config", message: `Provider "${providerId}" not found in models.json or auth.json` },
53
- };
54
- }
55
- const { config } = resolved;
56
- const checks = [];
57
- // 1. Config presence
58
- checks.push({
59
- name: "config-present",
60
- status: "ok",
61
- message: `Found provider config in ${resolved.source}`,
62
- });
63
- // 2. Base URL / health reachability
64
- // OpenAI-compatible roots often 404 on GET /v1; prefer /health for local Grok proxy.
65
- if (config.baseUrl) {
926
+ function createComposedController(signal, timeoutMs) {
927
+ const controller = new AbortController();
928
+ let kind;
929
+ const abortFromCaller = () => {
930
+ kind ??= "aborted";
931
+ controller.abort();
932
+ };
933
+ if (signal?.aborted)
934
+ abortFromCaller();
935
+ else
936
+ signal?.addEventListener("abort", abortFromCaller, { once: true });
937
+ const timeout = setTimeout(() => {
938
+ kind ??= "timeout";
939
+ controller.abort();
940
+ }, timeoutMs);
941
+ return {
942
+ controller,
943
+ abortKind: () => kind,
944
+ dispose: () => {
945
+ clearTimeout(timeout);
946
+ signal?.removeEventListener("abort", abortFromCaller);
947
+ },
948
+ };
949
+ }
950
+ function requestWithAbort(transport, request) {
951
+ return new Promise((resolve, reject) => {
952
+ if (request.signal.aborted) {
953
+ reject(ABORTED);
954
+ return;
955
+ }
956
+ const onAbort = () => reject(ABORTED);
957
+ request.signal.addEventListener("abort", onAbort, { once: true });
958
+ let pending;
66
959
  try {
67
- const probeUrl = providerId === "grok-oauth-proxy" ? new URL("/health", config.baseUrl).href : config.baseUrl;
68
- const res = await fetch(probeUrl, { method: "GET" });
69
- checks.push({
70
- name: "base-url-reachable",
71
- status: res.ok ? "ok" : "fail",
72
- message: `${probeUrl} → HTTP ${res.status}`,
73
- });
960
+ pending = transport.request(request);
74
961
  }
75
- catch (err) {
76
- checks.push({
77
- name: "base-url-reachable",
78
- status: "fail",
79
- message: err instanceof Error ? err.message : String(err),
80
- });
962
+ catch (error) {
963
+ request.signal.removeEventListener("abort", onAbort);
964
+ reject(error instanceof ProviderDoctorAuthMaterializationError ? error : undefined);
965
+ return;
81
966
  }
967
+ void pending.then((response) => {
968
+ request.signal.removeEventListener("abort", onAbort);
969
+ resolve(response);
970
+ }, (error) => {
971
+ request.signal.removeEventListener("abort", onAbort);
972
+ reject(error instanceof ProviderDoctorAuthMaterializationError ? error : undefined);
973
+ });
974
+ });
975
+ }
976
+ async function runProbe(name, kind, url, transport, addressPolicy, createHeaders, controller, payload) {
977
+ try {
978
+ const response = await requestWithAbort(transport, {
979
+ url,
980
+ method: payload ? "POST" : "GET",
981
+ redirect: "manual",
982
+ signal: controller.controller.signal,
983
+ addressPolicy,
984
+ createHeaders,
985
+ body: payload?.body,
986
+ contentType: payload?.contentType,
987
+ });
988
+ return classifyProbe(name, kind, response.status);
989
+ }
990
+ catch (error) {
991
+ if (error instanceof ProviderDoctorAuthMaterializationError) {
992
+ const failure = {
993
+ category: "auth",
994
+ code: "auth-materialization-failed",
995
+ message: "Configured authentication could not be materialized; verify auth and config references",
996
+ };
997
+ return {
998
+ check: {
999
+ name,
1000
+ status: "fail",
1001
+ ...failure,
1002
+ probe: { reachable: false, authenticated: false, category: "auth" },
1003
+ },
1004
+ failure,
1005
+ };
1006
+ }
1007
+ const abortKind = controller.abortKind();
1008
+ const failure = abortKind
1009
+ ? {
1010
+ category: "network",
1011
+ code: abortKind === "timeout" ? "request-timeout" : "request-aborted",
1012
+ message: abortKind === "timeout" ? "Provider probe timed out" : "Provider probe was aborted",
1013
+ }
1014
+ : { category: "network", code: "network-failure", message: "Provider transport failed" };
1015
+ return {
1016
+ check: { name, status: "fail", ...failure, probe: { reachable: false, category: "network" } },
1017
+ failure,
1018
+ };
1019
+ }
1020
+ }
1021
+ /** Minimal-token generative probe payload; never includes tools (no tool-call probe). */
1022
+ function modelProbeSpec(api, modelId) {
1023
+ if (api === "openai-completions") {
1024
+ return {
1025
+ path: "chat/completions",
1026
+ payload: {
1027
+ body: JSON.stringify({
1028
+ model: modelId,
1029
+ messages: [{ role: "user", content: "ping" }],
1030
+ max_tokens: 1,
1031
+ stream: false,
1032
+ }),
1033
+ contentType: "application/json",
1034
+ },
1035
+ };
1036
+ }
1037
+ if (api === "openai-responses") {
1038
+ return {
1039
+ path: "responses",
1040
+ payload: {
1041
+ // The responses API rejects max_output_tokens below 16; still the minimal request.
1042
+ body: JSON.stringify({ model: modelId, input: "ping", max_output_tokens: 16, stream: false }),
1043
+ contentType: "application/json",
1044
+ },
1045
+ };
1046
+ }
1047
+ return undefined;
1048
+ }
1049
+ function classifyOrigin(providerId, baseUrl, api, isNative) {
1050
+ const url = baseUrl ? sanitizeUrl(baseUrl) : undefined;
1051
+ if (providerId === "grok-oauth-proxy" || (url && isLiteralLoopback(normalizedHostname(url)))) {
1052
+ return "local-proxy";
1053
+ }
1054
+ if (isNative)
1055
+ return "native";
1056
+ if (api && OPENAI_COMPATIBLE_APIS.has(api))
1057
+ return "custom-openai-compatible";
1058
+ return "unknown";
1059
+ }
1060
+ function resolveProviderTarget(providerId, options) {
1061
+ const level = options.level ?? 0;
1062
+ const agentDir = options.agentDir ?? getConfiguredAgentDir();
1063
+ const modelsConfig = loadConfig(join(agentDir, "models.json"), parseModelsConfig, true);
1064
+ if (modelsConfig.kind === "invalid") {
1065
+ return {
1066
+ failure: unresolvedFailure(providerId, level, "models-config-invalid", "models.json is invalid"),
1067
+ };
1068
+ }
1069
+ const authConfig = loadConfig(join(agentDir, "auth.json"), parseAuthConfig);
1070
+ if (authConfig.kind === "invalid") {
1071
+ return { failure: unresolvedFailure(providerId, level, "auth-config-invalid", "auth.json is invalid") };
1072
+ }
1073
+ const kimi = providerId === KIMI_PROVIDER_ID ? resolveKimiConfig(options.kimiConfigPath) : {};
1074
+ if (kimi.failure) {
1075
+ return {
1076
+ failure: unresolvedFailure(providerId, level, "kimi-config-invalid", `Kimi config TOML is invalid (line ${kimi.failure.line}: ${kimi.failure.reason}); fix the Kimi config or set KIMI_BASE_URL/KIMI_MODEL_NAME`),
1077
+ };
1078
+ }
1079
+ const configured = modelsConfig.kind === "ok" ? modelsConfig.value.providers[providerId] : undefined;
1080
+ const nativeProviders = getProviders();
1081
+ const isNative = nativeProviders.includes(providerId);
1082
+ const nativeModels = isNative ? getModels(providerId) : [];
1083
+ const configuredModels = configured?.models ?? [];
1084
+ // At Level 2 the probe model is the paid target and therefore drives endpoint resolution.
1085
+ const requestedModel = level === 2 ? (options.probeModelId ?? options.modelId) : options.modelId;
1086
+ const configuredModel = requestedModel
1087
+ ? configuredModels.find((model) => model.id === requestedModel)
1088
+ : configuredModels[0];
1089
+ // §10.2: with no higher-precedence endpoint (models.json > KIMI_BASE_URL > root TOML), a
1090
+ // `[providers.<name>]` entry with `type = "openai_legacy"` selects a custom OpenAI-compatible
1091
+ // endpoint, so native-only checks and native credential/model inheritance are skipped downstream.
1092
+ const kimiCustomSelected = !(configuredModel?.baseUrl ?? configured?.baseUrl) && !kimi.baseUrl && kimi.customProvider !== undefined;
1093
+ const kimiModelName = kimi.modelName ?? (kimiCustomSelected ? kimi.customProvider?.modelName : undefined);
1094
+ const endpointModelId = requestedModel ?? kimiModelName;
1095
+ const modelIds = kimiCustomSelected
1096
+ ? kimiModelName
1097
+ ? [kimiModelName]
1098
+ : []
1099
+ : Array.from(new Set([
1100
+ ...nativeModels.map((model) => model.id),
1101
+ ...configuredModels.map((model) => model.id),
1102
+ ...(kimiModelName ? [kimiModelName] : []),
1103
+ ]));
1104
+ let nativeModel = kimiCustomSelected
1105
+ ? undefined
1106
+ : requestedModel
1107
+ ? nativeModels.find((model) => model.id === requestedModel)
1108
+ : nativeModels[0];
1109
+ if (!nativeModel && !kimiCustomSelected && requestedModel !== undefined && requestedModel === kimiModelName) {
1110
+ // A native Kimi TOML/env model rides on the provider defaults for endpoint metadata.
1111
+ nativeModel = nativeModels[0];
1112
+ }
1113
+ let baseUrl = configuredModel?.baseUrl ?? configured?.baseUrl;
1114
+ let api = configuredModel?.api ?? configured?.api ?? nativeModel?.api;
1115
+ let source = configured ? "models.json" : isNative ? "built-in-model-registry" : "unresolved";
1116
+ if (!baseUrl && kimi.baseUrl) {
1117
+ baseUrl = kimi.baseUrl;
1118
+ source = kimi.baseUrlSource ?? source;
1119
+ }
1120
+ else if (kimiCustomSelected && kimi.customProvider) {
1121
+ baseUrl = kimi.customProvider.baseUrl;
1122
+ api = "openai-completions";
1123
+ source = `kimi-config-toml:providers.${kimi.customProvider.name}`;
82
1124
  }
83
1125
  else {
84
- checks.push({ name: "base-url-reachable", status: "skipped", message: "No baseUrl configured" });
1126
+ baseUrl = baseUrl ?? nativeModel?.baseUrl;
85
1127
  }
86
- // 3. API key presence (never log the value)
87
- checks.push({
88
- name: "api-key-present",
89
- status: config.apiKey ? "ok" : "skipped",
90
- message: config.apiKey ? "API key is configured" : "No API key configured",
91
- });
92
- // 4. Models endpoint probe
93
- if (config.baseUrl) {
94
- try {
95
- const headers = {};
96
- if (config.apiKey)
97
- headers.Authorization = `Bearer ${config.apiKey}`;
98
- const res = await fetch(`${config.baseUrl.replace(/\/$/, "")}/models`, { headers });
99
- checks.push({
100
- name: "models-endpoint",
101
- status: res.ok ? "ok" : "fail",
102
- message: `HTTP ${res.status}`,
103
- });
1128
+ let builtInAuth = false;
1129
+ if (providerId === "grok-oauth-proxy" && !baseUrl) {
1130
+ baseUrl = "http://127.0.0.1:9996/v1";
1131
+ api = "openai-completions";
1132
+ source = "built-in-grok-oauth-proxy-defaults";
1133
+ builtInAuth = true;
1134
+ }
1135
+ const storedAuth = authConfig.kind === "ok" && authConfig.value[providerId] !== undefined;
1136
+ const environmentAuth = findEnvKeys(providerId) !== undefined;
1137
+ const configuredAuth = configured?.apiKey ? isConfigValueConfigured(configured.apiKey) : false;
1138
+ const headerAuth = configured?.headers !== undefined && Object.keys(configured.headers).length > 0;
1139
+ const customApiKey = kimiCustomSelected ? kimi.customProvider?.apiKey : undefined;
1140
+ const customAuthPresent = customApiKey !== undefined && isConfigValueConfigured(customApiKey);
1141
+ const authPresent = kimiCustomSelected
1142
+ ? customAuthPresent
1143
+ : storedAuth || environmentAuth || configuredAuth || headerAuth || builtInAuth;
1144
+ const authSource = kimiCustomSelected
1145
+ ? customAuthPresent
1146
+ ? source
1147
+ : undefined
1148
+ : storedAuth
1149
+ ? "auth.json"
1150
+ : environmentAuth
1151
+ ? "environment"
1152
+ : configuredAuth || headerAuth
1153
+ ? "models.json"
1154
+ : builtInAuth
1155
+ ? "built-in"
1156
+ : undefined;
1157
+ if (!configured && !isNative && !storedAuth && !environmentAuth && providerId !== "grok-oauth-proxy") {
1158
+ return { failure: unresolvedFailure(providerId, level, "provider-not-found", "Provider was not found") };
1159
+ }
1160
+ const origin = classifyOrigin(providerId, baseUrl, api, isNative && !kimiCustomSelected);
1161
+ const canonicalBaseUrl = canonicalProviderEndpointUrl(baseUrl);
1162
+ const binding = canonicalBaseUrl && api && endpointModelId
1163
+ ? { origin, source, baseUrl: canonicalBaseUrl, api, modelId: endpointModelId }
1164
+ : undefined;
1165
+ const createHeaders = kimiCustomSelected && binding
1166
+ ? () => {
1167
+ const headers = new Headers();
1168
+ if (customApiKey) {
1169
+ headers.set("Authorization", `Bearer ${resolveConfigValueOrThrow(customApiKey, "API key for custom Kimi endpoint")}`);
1170
+ }
1171
+ return headers;
104
1172
  }
105
- catch (err) {
106
- checks.push({
1173
+ : undefined;
1174
+ return {
1175
+ target: {
1176
+ providerId,
1177
+ origin,
1178
+ source,
1179
+ endpoint: { baseUrl, api, modelIds, modelId: endpointModelId },
1180
+ auth: { present: authPresent, source: authSource, createHeaders, binding },
1181
+ },
1182
+ };
1183
+ }
1184
+ export async function diagnoseResolvedProvider(target, options = {}, dependencies = {}) {
1185
+ const requestedLevel = options.level ?? 0;
1186
+ if (requestedLevel !== 0 && requestedLevel !== 1 && requestedLevel !== 2) {
1187
+ return unresolvedFailure(target.providerId, 0, "unsupported-level", "Provider doctor level is unsupported");
1188
+ }
1189
+ const level = requestedLevel;
1190
+ // Level 2 is opt-in only: probeModelId is ignored at levels 0/1, which stay GET-only.
1191
+ const probeModelId = level === 2 ? options.probeModelId : undefined;
1192
+ const effectiveModelId = probeModelId ?? options.modelId;
1193
+ const resolvedTarget = effectiveModelId
1194
+ ? { ...target, endpoint: { ...target.endpoint, modelId: effectiveModelId } }
1195
+ : target;
1196
+ if (level === 2 && options.modelId && probeModelId && options.modelId !== probeModelId) {
1197
+ const failure = {
1198
+ category: "model",
1199
+ code: "probe-model-conflict",
1200
+ message: "Validation model and paid probe model must match",
1201
+ };
1202
+ return publicResult(resolvedTarget, level, [{ name: "model-probe", status: "fail", ...failure }], undefined, failure);
1203
+ }
1204
+ if (level === 2 && !probeModelId) {
1205
+ const failure = {
1206
+ category: "config",
1207
+ code: "probe-model-required",
1208
+ message: "Level 2 requires an explicit probe model (--probe-model)",
1209
+ };
1210
+ return publicResult(resolvedTarget, level, [{ name: "model-probe", status: "fail", ...failure }], undefined, failure);
1211
+ }
1212
+ const validation = staticChecks(resolvedTarget);
1213
+ if (validation.failure || level === 0) {
1214
+ return publicResult(resolvedTarget, level, validation.checks, validation.url, validation.failure);
1215
+ }
1216
+ const transport = dependencies.transport ?? options.transport;
1217
+ if (!transport) {
1218
+ const failure = {
1219
+ category: "config",
1220
+ code: "transport-required",
1221
+ message: "Level 1 requires an injected provider transport",
1222
+ };
1223
+ validation.checks.push({ name: "transport", status: "fail", ...failure });
1224
+ return publicResult(resolvedTarget, level, validation.checks, validation.url, failure);
1225
+ }
1226
+ if (resolvedTarget.origin !== "local-proxy" && transport.pinsResolvedAddress !== true) {
1227
+ const failure = {
1228
+ category: "config",
1229
+ code: "address-pinning-required",
1230
+ message: "Public provider transport must pin resolved addresses",
1231
+ };
1232
+ validation.checks.push({ name: "transport", status: "fail", ...failure });
1233
+ return publicResult(resolvedTarget, level, validation.checks, validation.url, failure);
1234
+ }
1235
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1236
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
1237
+ const failure = {
1238
+ category: "config",
1239
+ code: "unexpected-response",
1240
+ message: "Provider probe timeout is invalid",
1241
+ };
1242
+ validation.checks.push({ name: "transport", status: "fail", ...failure });
1243
+ return publicResult(resolvedTarget, level, validation.checks, validation.url, failure);
1244
+ }
1245
+ const addressPolicy = resolvedTarget.origin === "local-proxy"
1246
+ ? { kind: "loopback-only", requireAddressPinning: false }
1247
+ : { kind: "public", requireAddressPinning: true };
1248
+ const resolveAuthHeaders = dependencies.resolveAuthHeaders ?? options.resolveAuthHeaders;
1249
+ const credentialBinding = resolvedTarget.auth.binding;
1250
+ const boundHeaderFactory = resolvedTarget.auth.createHeaders;
1251
+ const endpointBoundHeaders = credentialBinding && boundHeaderFactory
1252
+ ? async () => {
1253
+ if (!credentialBindingMatchesTarget(credentialBinding, resolvedTarget)) {
1254
+ throw new ProviderDoctorAuthMaterializationError();
1255
+ }
1256
+ try {
1257
+ return await boundHeaderFactory();
1258
+ }
1259
+ catch {
1260
+ throw new ProviderDoctorAuthMaterializationError();
1261
+ }
1262
+ }
1263
+ : undefined;
1264
+ const createHeaders = endpointBoundHeaders
1265
+ ? endpointBoundHeaders
1266
+ : level === 2 && resolveAuthHeaders
1267
+ ? async () => {
1268
+ if (credentialBinding && !credentialBindingMatchesTarget(credentialBinding, resolvedTarget)) {
1269
+ throw new ProviderDoctorAuthMaterializationError();
1270
+ }
1271
+ try {
1272
+ return await resolveAuthHeaders(resolvedTarget);
1273
+ }
1274
+ catch {
1275
+ throw new ProviderDoctorAuthMaterializationError();
1276
+ }
1277
+ }
1278
+ : (resolvedTarget.auth.createHeaders ?? (() => new Headers()));
1279
+ const controller = createComposedController(options.signal, timeoutMs);
1280
+ try {
1281
+ const root = await runProbe("root-endpoint", "root", makeRootProbeUrl(validation.url, resolvedTarget.origin), transport, addressPolicy, createHeaders, controller);
1282
+ validation.checks.push(root.check);
1283
+ if (root.failure) {
1284
+ validation.checks.push({
107
1285
  name: "models-endpoint",
108
- status: "fail",
109
- message: err instanceof Error ? err.message : String(err),
1286
+ status: "skipped",
1287
+ code: "ok",
1288
+ message: "Capability probe skipped after root failure",
110
1289
  });
1290
+ return publicResult(resolvedTarget, level, validation.checks, validation.url, root.failure);
1291
+ }
1292
+ const models = await runProbe("models-endpoint", "models", appendPath(validation.url, "models"), transport, addressPolicy, createHeaders, controller);
1293
+ validation.checks.push(models.check);
1294
+ if (level !== 2 || models.failure) {
1295
+ return publicResult(resolvedTarget, level, validation.checks, validation.url, models.failure);
111
1296
  }
1297
+ const spec = modelProbeSpec(resolvedTarget.endpoint.api ?? "", probeModelId);
1298
+ if (!spec) {
1299
+ const failure = {
1300
+ category: "config",
1301
+ code: "api-unsupported",
1302
+ message: "Model probe supports only OpenAI-compatible APIs",
1303
+ };
1304
+ validation.checks.push({ name: "model-probe", status: "fail", ...failure });
1305
+ return publicResult(resolvedTarget, level, validation.checks, validation.url, failure);
1306
+ }
1307
+ const probe = await runProbe("model-probe", "model-probe", appendPath(validation.url, spec.path), transport, addressPolicy, createHeaders, controller, spec.payload);
1308
+ const dispatched = probe.failure?.code !== "auth-materialization-failed";
1309
+ if (dispatched) {
1310
+ probe.check.message = `${probe.check.message}; minimal-token generative probe (may incur provider costs)`;
1311
+ }
1312
+ validation.checks.push(probe.check);
1313
+ return publicResult(resolvedTarget, level, validation.checks, validation.url, probe.failure, dispatched);
112
1314
  }
113
- else {
114
- checks.push({ name: "models-endpoint", status: "skipped", message: "No baseUrl configured" });
115
- }
116
- const failed = checks.find((c) => c.status === "fail");
117
- let error;
118
- if (failed) {
119
- const category = failed.name === "base-url-reachable"
120
- ? "network"
121
- : failed.name === "api-key-present"
122
- ? "auth"
123
- : failed.name === "models-endpoint"
124
- ? "model"
125
- : "unknown";
126
- error = { category, message: failed.message ?? `${failed.name} failed` };
1315
+ finally {
1316
+ controller.dispose();
127
1317
  }
128
- return {
129
- provider: providerId,
130
- status: failed ? "fail" : "ok",
131
- baseUrl: config.baseUrl,
132
- checks,
133
- error,
134
- };
1318
+ }
1319
+ export async function diagnoseProvider(providerId, options = {}) {
1320
+ const requestedLevel = options.level ?? 0;
1321
+ if (requestedLevel !== 0 && requestedLevel !== 1 && requestedLevel !== 2) {
1322
+ return unresolvedFailure(providerId, 0, "unsupported-level", "Provider doctor level is unsupported");
1323
+ }
1324
+ const resolved = resolveProviderTarget(providerId, options);
1325
+ if (resolved.failure)
1326
+ return resolved.failure;
1327
+ return diagnoseResolvedProvider(resolved.target, options);
135
1328
  }
136
1329
  //# sourceMappingURL=doctor-provider.js.map