open-multi-agent-kit 0.96.0 → 0.96.1
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.
- package/CHANGELOG.md +40 -0
- package/README.md +24 -6
- package/dist/commands/doctor-provider.d.ts.map +1 -1
- package/dist/commands/doctor-provider.js +15 -20
- package/dist/commands/doctor-provider.js.map +1 -1
- package/dist/commands/sdk-session-cli.d.ts +24 -0
- package/dist/commands/sdk-session-cli.d.ts.map +1 -0
- package/dist/commands/sdk-session-cli.js +225 -0
- package/dist/commands/sdk-session-cli.js.map +1 -0
- package/dist/core/advisory-judge-model.d.ts +18 -0
- package/dist/core/advisory-judge-model.d.ts.map +1 -0
- package/dist/core/advisory-judge-model.js +168 -0
- package/dist/core/advisory-judge-model.js.map +1 -0
- package/dist/core/advisory-judge-response.d.ts +8 -0
- package/dist/core/advisory-judge-response.d.ts.map +1 -0
- package/dist/core/advisory-judge-response.js +61 -0
- package/dist/core/advisory-judge-response.js.map +1 -0
- package/dist/core/advisory-judge-types.d.ts +53 -0
- package/dist/core/advisory-judge-types.d.ts.map +1 -0
- package/dist/core/advisory-judge-types.js +2 -0
- package/dist/core/advisory-judge-types.js.map +1 -0
- package/dist/core/advisory-judge.d.ts +8 -0
- package/dist/core/advisory-judge.d.ts.map +1 -0
- package/dist/core/advisory-judge.js +178 -0
- package/dist/core/advisory-judge.js.map +1 -0
- package/dist/core/agent-session-services.d.ts +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +4 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +107 -174
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +28 -4
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction-gate.d.ts +28 -0
- package/dist/core/compaction-gate.d.ts.map +1 -0
- package/dist/core/compaction-gate.js +34 -0
- package/dist/core/compaction-gate.js.map +1 -0
- package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt-items.js +5 -3
- package/dist/core/context-budget-system-prompt-items.js.map +1 -1
- package/dist/core/domain-loadouts.d.ts.map +1 -1
- package/dist/core/domain-loadouts.js +2 -2
- package/dist/core/domain-loadouts.js.map +1 -1
- package/dist/core/durable-goal-checkpoint.d.ts +6 -0
- package/dist/core/durable-goal-checkpoint.d.ts.map +1 -0
- package/dist/core/durable-goal-checkpoint.js +195 -0
- package/dist/core/durable-goal-checkpoint.js.map +1 -0
- package/dist/core/durable-goal-parse.d.ts +3 -0
- package/dist/core/durable-goal-parse.d.ts.map +1 -0
- package/dist/core/durable-goal-parse.js +86 -0
- package/dist/core/durable-goal-parse.js.map +1 -0
- package/dist/core/durable-goal-reducer.d.ts +3 -0
- package/dist/core/durable-goal-reducer.d.ts.map +1 -0
- package/dist/core/durable-goal-reducer.js +175 -0
- package/dist/core/durable-goal-reducer.js.map +1 -0
- package/dist/core/durable-goal-store.d.ts +19 -0
- package/dist/core/durable-goal-store.d.ts.map +1 -0
- package/dist/core/durable-goal-store.js +126 -0
- package/dist/core/durable-goal-store.js.map +1 -0
- package/dist/core/durable-goal.d.ts +92 -0
- package/dist/core/durable-goal.d.ts.map +1 -0
- package/dist/core/durable-goal.js +62 -0
- package/dist/core/durable-goal.js.map +1 -0
- package/dist/core/extensions/builtin/goal-controller.d.ts +3 -0
- package/dist/core/extensions/builtin/goal-controller.d.ts.map +1 -0
- package/dist/core/extensions/builtin/goal-controller.js +125 -0
- package/dist/core/extensions/builtin/goal-controller.js.map +1 -0
- package/dist/core/extensions/builtin/identical-loop.d.ts +3 -0
- package/dist/core/extensions/builtin/identical-loop.d.ts.map +1 -0
- package/dist/core/extensions/builtin/identical-loop.js +31 -0
- package/dist/core/extensions/builtin/identical-loop.js.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset.d.ts +3 -0
- package/dist/core/extensions/builtin/prompt-preset.d.ts.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset.js +13 -0
- package/dist/core/extensions/builtin/prompt-preset.js.map +1 -0
- package/dist/core/extensions/builtin/tool-pair-repair.d.ts +3 -0
- package/dist/core/extensions/builtin/tool-pair-repair.d.ts.map +1 -0
- package/dist/core/extensions/builtin/tool-pair-repair.js +12 -0
- package/dist/core/extensions/builtin/tool-pair-repair.js.map +1 -0
- package/dist/core/extensions/types.d.ts +6 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/goal-continuation.d.ts +13 -0
- package/dist/core/goal-continuation.d.ts.map +1 -0
- package/dist/core/goal-continuation.js +10 -0
- package/dist/core/goal-continuation.js.map +1 -0
- package/dist/core/grok-harness.d.ts +1 -1
- package/dist/core/grok-harness.d.ts.map +1 -1
- package/dist/core/grok-harness.js +1 -1
- package/dist/core/grok-harness.js.map +1 -1
- package/dist/core/grok-playbook.d.ts +1 -1
- package/dist/core/grok-playbook.d.ts.map +1 -1
- package/dist/core/grok-playbook.js +1 -1
- package/dist/core/grok-playbook.js.map +1 -1
- package/dist/core/identical-loop.d.ts +15 -0
- package/dist/core/identical-loop.d.ts.map +1 -0
- package/dist/core/identical-loop.js +34 -0
- package/dist/core/identical-loop.js.map +1 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/model-prompt-policy.d.ts +11 -0
- package/dist/core/model-prompt-policy.d.ts.map +1 -0
- package/dist/core/model-prompt-policy.js +17 -0
- package/dist/core/model-prompt-policy.js.map +1 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +16 -4
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +2 -2
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/prompt-budget.d.ts +21 -0
- package/dist/core/prompt-budget.d.ts.map +1 -0
- package/dist/core/prompt-budget.js +51 -0
- package/dist/core/prompt-budget.js.map +1 -0
- package/dist/core/prompt-cache.d.ts +14 -0
- package/dist/core/prompt-cache.d.ts.map +1 -0
- package/dist/core/prompt-cache.js +16 -0
- package/dist/core/prompt-cache.js.map +1 -0
- package/dist/core/prompt-preset.d.ts +8 -0
- package/dist/core/prompt-preset.d.ts.map +1 -0
- package/dist/core/prompt-preset.js +56 -0
- package/dist/core/prompt-preset.js.map +1 -0
- package/dist/core/provider-resilience.d.ts +16 -0
- package/dist/core/provider-resilience.d.ts.map +1 -1
- package/dist/core/provider-resilience.js +23 -1
- package/dist/core/provider-resilience.js.map +1 -1
- package/dist/core/provider-retry.d.ts +29 -0
- package/dist/core/provider-retry.d.ts.map +1 -0
- package/dist/core/provider-retry.js +49 -0
- package/dist/core/provider-retry.js.map +1 -0
- package/dist/core/provider-usage.d.ts +2 -1
- package/dist/core/provider-usage.d.ts.map +1 -1
- package/dist/core/provider-usage.js +40 -6
- package/dist/core/provider-usage.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +32 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sandbox/backend.d.ts +12 -0
- package/dist/core/sandbox/backend.d.ts.map +1 -1
- package/dist/core/sandbox/backend.js +52 -42
- package/dist/core/sandbox/backend.js.map +1 -1
- package/dist/core/sandbox/policy.d.ts +2 -0
- package/dist/core/sandbox/policy.d.ts.map +1 -1
- package/dist/core/sandbox/policy.js +2 -1
- package/dist/core/sandbox/policy.js.map +1 -1
- package/dist/core/sdk.d.ts +2 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-system-prompt.d.ts +28 -0
- package/dist/core/session-system-prompt.d.ts.map +1 -0
- package/dist/core/session-system-prompt.js +37 -0
- package/dist/core/session-system-prompt.js.map +1 -0
- package/dist/core/strict-record.d.ts +2 -0
- package/dist/core/strict-record.d.ts.map +1 -0
- package/dist/core/strict-record.js +9 -0
- package/dist/core/strict-record.js.map +1 -0
- package/dist/core/tool-pair-repair.d.ts +11 -0
- package/dist/core/tool-pair-repair.d.ts.map +1 -0
- package/dist/core/tool-pair-repair.js +38 -0
- package/dist/core/tool-pair-repair.js.map +1 -0
- package/dist/core/tools/artifact-spill.d.ts +13 -0
- package/dist/core/tools/artifact-spill.d.ts.map +1 -0
- package/dist/core/tools/artifact-spill.js +26 -0
- package/dist/core/tools/artifact-spill.js.map +1 -0
- package/dist/core/tools/bash.d.ts +7 -4
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +8 -7
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/read.d.ts +1 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +10 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +2 -0
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +8 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +14 -3
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/docs/adaptorch-preview-spec.md +1 -1
- package/docs/adaptorch-preview.md +2 -2
- package/docs/book-to-skill.md +1 -1
- package/docs/containerization.md +9 -0
- package/docs/docs.json +11 -3
- package/docs/environment-variables.md +11 -4
- package/docs/extensions.md +14 -1
- package/docs/grok-harness.md +35 -12
- package/docs/index.md +7 -6
- package/docs/loadout-domains/grok-harness.md +2 -2
- package/docs/models.md +10 -9
- package/docs/provider-resilience.md +9 -3
- package/docs/providers.md +9 -4
- package/docs/run-protocol.md +32 -0
- package/docs/sdk.md +96 -3
- package/docs/security.md +22 -32
- package/docs/sessions.md +14 -0
- package/docs/usage.md +37 -15
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +6 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +18 -18
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.96.1] - 2026-08-20
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added built-in harness loop extensions: identical-loop detection, compaction tool-pair repair, Kimi/K3/GLM/Grok/Claude prompt presets, and `/goal` with automatic continuation plus explicit `pause`, `resume`, evidence-gated `complete`, and `clear` lifecycle commands.
|
|
10
|
+
- Added pass-gated advisory best-of-N selection with strict weighted judge responses, forced-redacted candidate material, deterministic fallback, evaluation-bound request digests, and an explicit `ModelRegistry`-backed LLM adapter.
|
|
11
|
+
- Added digest-bound `Goal / Core / Verified / Open / Next` seam checkpoints to the existing durable-goal journal, including `/goal checkpoint <json>` and checkpoint-aware continuation.
|
|
12
|
+
- Added `omk sdk session status|tail|inspect|send` for external session controllers. Ambiguous selectors now fail closed, writes require exact IDs, active owners block concurrent access, and credential-shaped transcript text is redacted from output.
|
|
13
|
+
- Legacy `read` mode (`OMK_OMP_SEAMS=0`) now reports a private `0700` temporary spill directory with an exclusive `0600` file for recoverable line or byte truncation; it never writes beside or through the source path. A first line that alone exceeds the byte cap remains preview-only.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Sandbox backend probing is now cached per local bash operations instance. All enforce-mode fallback verdicts share the concrete missing-backend diagnosis (`bwrap`, user namespaces, `sandbox-exec`, or unsupported platform). Policy semantics are unchanged.
|
|
18
|
+
- Extracted the session system-prompt assembly from `AgentSession._rebuildSystemPrompt` into the pure `assembleSessionSystemPrompt` module (`core/session-system-prompt.ts`). Provider playbook resolution stays at the call site; the assembly is now directly testable.
|
|
19
|
+
- Extracted the retry/failover decisions from `AgentSession._isRetryableError` and `_prepareRetry` into the pure `core/provider-retry.ts` module (`isRetryableAssistantError`, `nextRetryAttempt`, `computeRetryDelayMs`). Retry ordering, backoff, and failover semantics are unchanged.
|
|
20
|
+
- Extracted the compaction gates from `AgentSession._checkCompaction` into the pure `core/compaction-gate.ts` module (`shouldSkipCompactionCheck`, `isSessionModelOverflow`). Gate ordering and staleness semantics are unchanged.
|
|
21
|
+
- Extracted the failover trigger and refused-model bookkeeping from `AgentSession._maybeFailoverFromSafetyStop` into `core/provider-retry.ts` (`isFailoverTriggerError`, `failoverModelKey`). Chain ordering and blacklist semantics are unchanged.
|
|
22
|
+
- Extracted the context-budget arithmetic from `AgentSession._getContextBudgetOptions` into the pure `core/prompt-budget.ts` module (`computePromptTokenBudget`, `computeResponseReserveTokens`). Env parsing stays at the call site; budget values are unchanged.
|
|
23
|
+
- Extracted the prompt-cache key transition classification from `AgentSession._recordPromptCachePlan` into the pure `core/prompt-cache.ts` module (`classifyPromptCacheTransition`). Counter and break-reason semantics are unchanged.
|
|
24
|
+
- Grok 4.5 / 4.3 now expose `/think max` and `ultra` in the selector. Those aliases still send xAI `reasoning_effort: "high"` because those models have no upstream `xhigh`/`max` tier.
|
|
25
|
+
- Native xAI SuperGrok usage now polls `GET https://cli-chat-proxy.grok.com/v1/billing?format=credits` and shows the weekly SuperGrok pool from `creditUsagePercent`. Stale `grok-oauth-proxy` credentials are dropped from `/login` and `/logout`.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Durable-goal continuation now records and skips an unavailable WSL/project workspace instead of emitting an `ENODEV` extension stack after every failed provider attempt.
|
|
30
|
+
- Content/safety refusals are capped at one same-model retry when failover is unavailable, preventing the default transport retry budget from replaying the same refusal three times.
|
|
31
|
+
- Fable models remain visible in the model catalog, and a saved Fable default is honored when sticky-safety blocking is disabled.
|
|
32
|
+
- Claude models now omit discovered context files by default, avoiding provider false positives from unrelated instruction text; `OMK_CLAUDE_CONTEXT_FILES=1` restores the full context.
|
|
33
|
+
- Extension `resourceClaims` now survive both tool-definition adapters, allowing `dag-v2` to schedule non-conflicting custom tool calls concurrently instead of treating them as unclaimed exclusive work.
|
|
34
|
+
- Built-in tool-pair repair now uses the real `AgentMessage` contract (`toolCall` blocks and top-level `role: "toolResult"` messages), removing orphan pairs without unsafe message-shape casts.
|
|
35
|
+
- Pinned the transitive development dependency `nanoid` to 3.3.18, clearing GHSA-2v37-7h3g-55p8 from both full and production npm audits.
|
|
36
|
+
|
|
37
|
+
### Docs
|
|
38
|
+
|
|
39
|
+
- Redesigned the root README around the Scope → Route → Verify → Replay control loop with a WCAG-aware cyberpunk OMK Girl hero and slow feature GIF generated through GPT Image 2. The root `DESIGN.md` now defines public brand tokens, media budgets, and reduced-motion guidance.
|
|
40
|
+
|
|
41
|
+
### Removed
|
|
42
|
+
|
|
43
|
+
- Removed the `grok-oauth-proxy` provider path. Grok harness dispatch, failover, usage, and presets now use native `xai`. Stale `models.json` entries for `grok-oauth-proxy` are ignored instead of reappearing in `/login` and `/model`.
|
|
44
|
+
|
|
5
45
|
## [0.96.0] - 2026-08-16
|
|
6
46
|
|
|
7
47
|
### Added
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ OMK supports interactive terminal use, non-interactive output, RPC integration,
|
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
|
-
Requires Node.js 22.19 or newer.
|
|
9
|
+
Requires Node.js 22.19 or newer. Built-in local bash also requires `sandbox-exec` on macOS or `bwrap` plus unprivileged user namespaces on Linux. Enforcement is enabled by default and fails closed with `sandbox.backend_missing`; see [Containerization](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/containerization.md).
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npm install -g open-multi-agent-kit --ignore-scripts
|
|
@@ -45,19 +45,20 @@ OMK stores user configuration under `~/.omk/agent/` and project configuration un
|
|
|
45
45
|
Install `omk-book-to-skill` to compile documents into reusable skills without adding Python extractors to OMK core:
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
|
-
omk install npm:omk-book-to-skill@0.
|
|
48
|
+
omk install npm:omk-book-to-skill@0.96.1
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
It provides compile, update, and verification commands plus a local SHA-256 provenance manifest. See [Book to Skill](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/book-to-skill.md).
|
|
52
52
|
|
|
53
53
|
## Providers & Models
|
|
54
54
|
|
|
55
|
-
OMK includes provider adapters for Anthropic, OpenAI Codex, Google, OpenCode, Kimi, Qwen, ZAI/GLM, Grok, and other compatible services. Availability depends on the credentials and endpoints configured on the current machine.
|
|
55
|
+
OMK includes provider adapters for Anthropic, OpenAI Codex, Google, OpenCode, Kimi, Qwen, ZAI/GLM, native xAI/Grok, and other compatible services. Availability depends on the credentials and endpoints configured on the current machine.
|
|
56
56
|
|
|
57
57
|
- `/login` adds or selects credentials.
|
|
58
58
|
- `/logout` removes credentials for a selected provider.
|
|
59
59
|
- `/model` lists models available to the current credential set.
|
|
60
|
-
- NVIDIA NIM's `z-ai/glm-5.2` entry transmits reasoning effort, including `/
|
|
60
|
+
- NVIDIA NIM's `z-ai/glm-5.2` entry transmits reasoning effort, including `/think max`.
|
|
61
|
+
- Native `xai` accepts subscription OAuth from `/login` or `XAI_API_KEY` for API billing. Only OAuth exposes weekly SuperGrok usage/reset. See [Grok harness](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/grok-harness.md).
|
|
61
62
|
- The optional status rail shows independent provider quota windows when an official API or passive response signal is available.
|
|
62
63
|
- Missing quota values are shown as unavailable rather than estimated.
|
|
63
64
|
|
|
@@ -88,6 +89,7 @@ Type `/` in the editor to open command completion.
|
|
|
88
89
|
| `/resume` | Open a previous session |
|
|
89
90
|
| `/new` | Start a new session |
|
|
90
91
|
| `/session` | Show session path, messages, tokens, and cost |
|
|
92
|
+
| `/goal [objective]` | Show or set the durable goal; supports `checkpoint <json>`, `pause`, `resume`, evidence-gated `complete`, and `clear` |
|
|
91
93
|
| `/tree` | Navigate the current session tree |
|
|
92
94
|
| `/compact [prompt]` | Compact context, optionally with custom instructions |
|
|
93
95
|
| `/copy` | Copy the last assistant message |
|
|
@@ -97,6 +99,10 @@ Type `/` in the editor to open command completion.
|
|
|
97
99
|
| `/star` | Open the OMK GitHub repository |
|
|
98
100
|
| `/quit` | Exit OMK |
|
|
99
101
|
|
|
102
|
+
### Default Harness Safeguards
|
|
103
|
+
|
|
104
|
+
OMK warns from the third repeated identical tool call and blocks the sixth. It also repairs unmatched tool pairs before provider requests and adds guidance for supported Kimi, GLM, and Grok models. `OMK_IDENTICAL_LOOP`, `OMK_TOOL_PAIR_REPAIR`, and `OMK_PROMPT_PRESET` opt out; `OMK_GOAL_CONTROLLER` controls `/goal` and continuation.
|
|
105
|
+
|
|
100
106
|
See [Usage](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/usage.md) and [Keybindings](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/keybindings.md) for the complete interactive reference.
|
|
101
107
|
|
|
102
108
|
## Sessions
|
|
@@ -105,6 +111,17 @@ Sessions are append-only JSONL transcripts stored under `~/.omk/agent/sessions/`
|
|
|
105
111
|
|
|
106
112
|
Do not publish session files until you have reviewed them for source code, credentials, personal information, and private tool output.
|
|
107
113
|
|
|
114
|
+
### Scripted Session Inspection
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
omk sdk session status --json
|
|
118
|
+
omk sdk session tail [id] --limit 20
|
|
119
|
+
omk sdk session inspect [id]
|
|
120
|
+
omk sdk session send <id> "message"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
These commands inspect or append to stored transcripts. `send` does not wake or execute an agent.
|
|
124
|
+
|
|
108
125
|
### Context Compaction
|
|
109
126
|
|
|
110
127
|
Automatic and manual compaction reduce older context while retaining recent conversation state. Cancellation restores queued user input without committing a partial summary.
|
|
@@ -153,7 +170,7 @@ See [RPC](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/rpc
|
|
|
153
170
|
|
|
154
171
|
## SDK and Extensions
|
|
155
172
|
|
|
156
|
-
The TypeScript SDK exposes session creation, provider configuration, tools, and
|
|
173
|
+
The TypeScript SDK exposes session creation, provider configuration, tools, event streams, durable-goal reducers, Goal/Core/Verified/Open/Next seam checkpoints, an explicit pass-gated advisory best-of-N judge, and policy helpers for compaction, retries, prompt budgets, cache transitions, and system-prompt assembly. Extensions can add commands, tools, UI components, themes, and resource loaders without patching OMK core.
|
|
157
174
|
|
|
158
175
|
`omk-protocol` provides the versioned `TaskSpec -> ExecutionAttempt -> Observation -> EvaluationResult -> RuntimeDecision` contracts and pure semantic reducers. See [Run Protocol v1](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/run-protocol.md) for the implemented scope and migration boundaries.
|
|
159
176
|
|
|
@@ -165,6 +182,7 @@ The TypeScript SDK exposes session creation, provider configuration, tools, and
|
|
|
165
182
|
## Security and Privacy
|
|
166
183
|
|
|
167
184
|
- Tool access is explicit and can be scoped by path or runtime policy.
|
|
185
|
+
- Default OS isolation covers AgentSession's built-in local bash only; it does not isolate other tools, extensions, custom code, or the OMK process.
|
|
168
186
|
- Default tests scrub provider credentials; live provider tests require `LIVE_E2E=1`.
|
|
169
187
|
- Subscription quota requests use fixed HTTPS provider origins.
|
|
170
188
|
- Passive quota observers do not block model streams.
|
|
@@ -185,7 +203,7 @@ npm test
|
|
|
185
203
|
|
|
186
204
|
See [Development](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/development.md) and [CONTRIBUTING.md](https://github.com/dmae97/omk/blob/main/CONTRIBUTING.md).
|
|
187
205
|
|
|
188
|
-
Release notes: [v0.
|
|
206
|
+
Release notes: [v0.96.1](https://github.com/dmae97/omk/blob/main/.github/RELEASE_NOTES_v0.96.1.md).
|
|
189
207
|
|
|
190
208
|
## License
|
|
191
209
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor-provider.d.ts","sourceRoot":"","sources":["../../src/commands/doctor-provider.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { BlockList, isIP } from \"node:net\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { type Api, findEnvKeys, getModels, getProviders, type KnownProvider, type Model } from \"omk-ai\";\nimport { getAgentDir as getConfiguredAgentDir } from \"../config.ts\";\nimport { isConfigValueConfigured, resolveConfigValueOrThrow } from \"../core/resolve-config-value.ts\";\nimport { stripJsonComments } from \"../utils/json.ts\";\n\nexport type ProviderOrigin = \"native\" | \"custom-openai-compatible\" | \"local-proxy\" | \"unknown\";\nexport type ProviderDoctorLevel = 0 | 1 | 2;\nexport type ProviderDoctorCheckStatus = \"ok\" | \"fail\" | \"skipped\" | \"unsupported\";\nexport type DoctorErrorCategory = \"network\" | \"auth\" | \"model\" | \"config\" | \"server\" | \"unknown\";\n\n/** Exact endpoint-probe category model from the v0.90.9 design (§10.4). */\nexport type EndpointProbeCategory = \"ok\" | \"network\" | \"auth\" | \"unsupported-endpoint\" | \"server\";\n\nexport type EndpointProbeResult = {\n\treachable: boolean;\n\tauthenticated?: boolean;\n\tmodelsSupported?: boolean;\n\tstatus?: number;\n\tcategory: EndpointProbeCategory;\n};\n\nexport type ProviderDoctorCode =\n\t| \"ok\"\n\t| \"provider-not-found\"\n\t| \"models-config-invalid\"\n\t| \"auth-config-invalid\"\n\t| \"kimi-config-invalid\"\n\t| \"unsupported-level\"\n\t| \"model-provider-mismatch\"\n\t| \"api-missing\"\n\t| \"api-unsupported\"\n\t| \"base-url-missing\"\n\t| \"base-url-invalid\"\n\t| \"url-scheme-unsupported\"\n\t| \"origin-unsupported\"\n\t| \"address-policy-blocked\"\n\t| \"auth-missing\"\n\t| \"auth-materialization-failed\"\n\t| \"transport-required\"\n\t| \"address-pinning-required\"\n\t| \"redirect-blocked\"\n\t| \"authentication-failed\"\n\t| \"endpoint-unsupported\"\n\t| \"request-aborted\"\n\t| \"request-timeout\"\n\t| \"network-failure\"\n\t| \"server-error\"\n\t| \"probe-model-required\"\n\t| \"probe-model-conflict\"\n\t| \"unexpected-response\";\n\nexport interface ProviderDoctorEndpoint {\n\tbaseUrl?: string;\n\tapi?: string;\n\tmodelIds?: readonly string[];\n\tmodelId?: string;\n}\n\nexport type ProviderDoctorHeaderFactory = () => Headers | Promise<Headers>;\n\nexport type ProviderDoctorAuthHeaderResolver = (target: ResolvedProviderTarget) => Headers | Promise<Headers>;\n\n/** Fixed-message marker used to classify auth resolution without exposing its cause. */\nexport class ProviderDoctorAuthMaterializationError extends Error {\n\tconstructor() {\n\t\tsuper(\"provider doctor authentication could not be materialized\");\n\t\tthis.name = \"ProviderDoctorAuthMaterializationError\";\n\t}\n}\n\nexport interface ProviderDoctorCredentialBinding {\n\torigin: ProviderOrigin;\n\tsource: string;\n\tbaseUrl: string;\n\tapi: string;\n\tmodelId: string;\n}\n\nexport interface ProviderDoctorAuth {\n\tpresent: boolean;\n\tsource?: string;\n\t/** Materialized by an injected transport at request time only. */\n\tcreateHeaders?: ProviderDoctorHeaderFactory;\n\t/** Exact endpoint provenance that authorizes createHeaders. */\n\tbinding?: ProviderDoctorCredentialBinding;\n}\n\nexport interface ResolvedProviderTarget {\n\tproviderId: string;\n\torigin: ProviderOrigin;\n\tsource: string;\n\tendpoint: ProviderDoctorEndpoint;\n\tauth: ProviderDoctorAuth;\n}\n\n/** Compatibility name for callers using the design-document terminology. */\nexport type ResolvedProviderConfig = ResolvedProviderTarget;\n\nexport type ProviderDoctorAddressPolicy =\n\t| { kind: \"public\"; requireAddressPinning: true }\n\t| { kind: \"loopback-only\"; requireAddressPinning: false };\n\nexport interface ProviderDoctorTransportRequest {\n\turl: URL;\n\tmethod: \"GET\" | \"POST\";\n\tredirect: \"manual\";\n\tsignal: AbortSignal;\n\taddressPolicy: ProviderDoctorAddressPolicy;\n\tcreateHeaders: ProviderDoctorHeaderFactory;\n\t/** Bounded JSON payload; present only for opt-in Level-2 POST model probes. */\n\tbody?: string;\n\tcontentType?: \"application/json\";\n}\n\nexport interface ProviderDoctorTransportResponse {\n\tstatus: number;\n}\n\nexport interface ProviderDoctorTransport {\n\t/** Public requests are rejected unless the transport enforces DNS/address pinning. */\n\tpinsResolvedAddress?: boolean;\n\trequest(request: ProviderDoctorTransportRequest): Promise<ProviderDoctorTransportResponse>;\n}\n\nexport interface ProviderDoctorDependencies {\n\ttransport?: ProviderDoctorTransport;\n\t/** Level-2 only. Called by the transport immediately before each request is dispatched. */\n\tresolveAuthHeaders?: ProviderDoctorAuthHeaderResolver;\n}\n\nexport interface ProviderDoctorOptions extends ProviderDoctorDependencies {\n\tlevel?: ProviderDoctorLevel;\n\tmodelId?: string;\n\t/** Level-2 opt-in only: the model probed with one minimal-token generative request. */\n\tprobeModelId?: string;\n\ttimeoutMs?: number;\n\tsignal?: AbortSignal;\n\tagentDir?: string;\n\t/** Path to the Kimi CLI config TOML consulted for the kimi-coding provider. Defaults to ~/.kimi/config.toml. */\n\tkimiConfigPath?: string;\n}\n\nexport interface ProviderDoctorCheck {\n\tname: string;\n\tstatus: ProviderDoctorCheckStatus;\n\tcode: ProviderDoctorCode;\n\tcategory?: DoctorErrorCategory;\n\tmessage?: string;\n\t/** Exact-category endpoint classification for network probe checks. */\n\tprobe?: EndpointProbeResult;\n}\n\nexport interface ProviderDoctorResult {\n\tprovider: string;\n\tstatus: \"ok\" | \"fail\";\n\tlevel: ProviderDoctorLevel;\n\torigin: ProviderOrigin;\n\tsource: string;\n\t/** Sanitized URL: userinfo, query, and fragment are always removed. */\n\ttargetUrl?: string;\n\t/** Backwards-compatible alias of targetUrl. */\n\tbaseUrl?: string;\n\tapi?: string;\n\tmodelId?: string;\n\tauthPresent: boolean;\n\t/** True only when an opt-in Level-2 model probe was dispatched; the request may incur provider costs. */\n\tcostWarning?: boolean;\n\tchecks: ProviderDoctorCheck[];\n\terror?: {\n\t\tcategory: DoctorErrorCategory;\n\t\tcode: ProviderDoctorCode;\n\t\tmessage: string;\n\t};\n}\n\ninterface ModelEndpointConfig {\n\tid: string;\n\tbaseUrl?: string;\n\tapi?: string;\n}\n\ninterface ProviderConfig {\n\tbaseUrl?: string;\n\tapiKey?: string;\n\tapi?: string;\n\theaders?: Record<string, string>;\n\tmodels?: ModelEndpointConfig[];\n}\n\ninterface ModelsConfig {\n\tproviders: Record<string, ProviderConfig>;\n}\n\ninterface AuthConfigEntry {\n\ttype: \"api_key\" | \"oauth\";\n}\n\ntype AuthConfig = Record<string, AuthConfigEntry>;\n\ntype LoadedConfig<T> = { kind: \"missing\" } | { kind: \"invalid\" } | { kind: \"ok\"; value: T };\n\ntype ProbeFailure = {\n\tcategory: DoctorErrorCategory;\n\tcode: ProviderDoctorCode;\n\tmessage: string;\n};\n\ninterface ProbeOutcome {\n\tcheck: ProviderDoctorCheck;\n\tfailure?: ProbeFailure;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst KNOWN_APIS = new Set<string>([\n\t\"openai-completions\",\n\t\"mistral-conversations\",\n\t\"openai-responses\",\n\t\"azure-openai-responses\",\n\t\"openai-codex-responses\",\n\t\"anthropic-messages\",\n\t\"bedrock-converse-stream\",\n\t\"google-generative-ai\",\n\t\"google-vertex\",\n]);\nconst OPENAI_COMPATIBLE_APIS = new Set<string>([\"openai-completions\", \"openai-responses\"]);\nconst ABORTED = Symbol(\"provider-doctor-aborted\");\n\n// Family-specific lists: node BlockList canonicalizes IPv4 checks to the v4-mapped v6 form,\n// so a combined list containing ::ffff:0:0/96 would block every IPv4 address.\nconst blockedPublicV4 = new BlockList();\nfor (const [address, prefix] of [\n\t[\"0.0.0.0\", 8],\n\t[\"10.0.0.0\", 8],\n\t[\"100.64.0.0\", 10],\n\t[\"127.0.0.0\", 8],\n\t[\"169.254.0.0\", 16],\n\t[\"172.16.0.0\", 12],\n\t[\"192.168.0.0\", 16],\n\t[\"224.0.0.0\", 4],\n\t[\"240.0.0.0\", 4],\n] as const) {\n\tblockedPublicV4.addSubnet(address, prefix, \"ipv4\");\n}\n// ::ffff:0:0/96 stays here on purpose: a v4-mapped IPv6 literal or DNS answer is anomalous\n// for a public origin and is rejected wholesale (fail closed), covering hex-form mappings too.\nconst blockedPublicV6 = new BlockList();\nfor (const [address, prefix] of [\n\t[\"::\", 128],\n\t[\"::1\", 128],\n\t[\"::ffff:0:0\", 96],\n\t[\"fc00::\", 7],\n\t[\"fe80::\", 10],\n\t[\"ff00::\", 8],\n] as const) {\n\tblockedPublicV6.addSubnet(address, prefix, \"ipv6\");\n}\n\nconst loopbackAddresses = new BlockList();\nloopbackAddresses.addSubnet(\"127.0.0.0\", 8, \"ipv4\");\nloopbackAddresses.addAddress(\"::1\", \"ipv6\");\nloopbackAddresses.addSubnet(\"::ffff:127.0.0.0\", 104, \"ipv6\");\n\n/** True when the literal IP address is a loopback address. Non-IP input is never loopback. */\nexport function isLoopbackAddress(address: string): boolean {\n\tconst family = isIP(address);\n\tif (family === 4) return loopbackAddresses.check(address, \"ipv4\");\n\tif (family === 6) return loopbackAddresses.check(address, \"ipv6\");\n\treturn false;\n}\n\n/** True when the literal IP address must not be dialed for a public origin. Non-IP input fails closed. */\nexport function isBlockedPublicAddress(address: string): boolean {\n\tconst family = isIP(address);\n\tif (family === 4) return blockedPublicV4.check(address, \"ipv4\");\n\tif (family === 6) return blockedPublicV6.check(address, \"ipv6\");\n\treturn true;\n}\n\nexport function getAgentDir(): string {\n\treturn getConfiguredAgentDir();\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isOptionalNonemptyString(value: unknown): value is string | undefined {\n\treturn value === undefined || (typeof value === \"string\" && value.length > 0);\n}\n\nfunction parseModelsConfig(value: unknown): ModelsConfig | undefined {\n\tif (!isRecord(value) || !isRecord(value.providers)) return undefined;\n\tconst providers: Record<string, ProviderConfig> = {};\n\n\tfor (const [providerId, rawProvider] of Object.entries(value.providers)) {\n\t\tif (!isRecord(rawProvider)) return undefined;\n\t\tif (\n\t\t\t!isOptionalNonemptyString(rawProvider.baseUrl) ||\n\t\t\t!isOptionalNonemptyString(rawProvider.apiKey) ||\n\t\t\t!isOptionalNonemptyString(rawProvider.api)\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tlet headers: Record<string, string> | undefined;\n\t\tif (rawProvider.headers !== undefined) {\n\t\t\tif (!isRecord(rawProvider.headers)) return undefined;\n\t\t\theaders = {};\n\t\t\tfor (const [name, headerValue] of Object.entries(rawProvider.headers)) {\n\t\t\t\tif (typeof headerValue !== \"string\") return undefined;\n\t\t\t\theaders[name] = headerValue;\n\t\t\t}\n\t\t}\n\n\t\tlet models: ModelEndpointConfig[] | undefined;\n\t\tif (rawProvider.models !== undefined) {\n\t\t\tif (!Array.isArray(rawProvider.models)) return undefined;\n\t\t\tmodels = [];\n\t\t\tfor (const rawModel of rawProvider.models) {\n\t\t\t\tif (\n\t\t\t\t\t!isRecord(rawModel) ||\n\t\t\t\t\ttypeof rawModel.id !== \"string\" ||\n\t\t\t\t\trawModel.id.length === 0 ||\n\t\t\t\t\t!isOptionalNonemptyString(rawModel.baseUrl) ||\n\t\t\t\t\t!isOptionalNonemptyString(rawModel.api)\n\t\t\t\t) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tmodels.push({ id: rawModel.id, baseUrl: rawModel.baseUrl, api: rawModel.api });\n\t\t\t}\n\t\t}\n\n\t\tproviders[providerId] = {\n\t\t\tbaseUrl: rawProvider.baseUrl,\n\t\t\tapiKey: rawProvider.apiKey,\n\t\t\tapi: rawProvider.api,\n\t\t\theaders,\n\t\t\tmodels,\n\t\t};\n\t}\n\n\treturn { providers };\n}\n\nfunction parseAuthConfig(value: unknown): AuthConfig | undefined {\n\tif (!isRecord(value)) return undefined;\n\tconst auth: AuthConfig = {};\n\tfor (const [providerId, rawEntry] of Object.entries(value)) {\n\t\tif (!isRecord(rawEntry) || (rawEntry.type !== \"api_key\" && rawEntry.type !== \"oauth\")) return undefined;\n\t\tif (rawEntry.type === \"api_key\" && (typeof rawEntry.key !== \"string\" || rawEntry.key.length === 0)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tauth[providerId] = { type: rawEntry.type };\n\t}\n\treturn auth;\n}\n\nconst KIMI_PROVIDER_ID = \"kimi-coding\";\nconst TOML_BASIC_ESCAPES: Record<string, string> = {\n\t'\"': '\"',\n\t\"\\\\\": \"\\\\\",\n\tb: \"\\b\",\n\tf: \"\\f\",\n\tn: \"\\n\",\n\tr: \"\\r\",\n\tt: \"\\t\",\n};\n\ntype TomlQuote = \"basic\" | \"literal\" | \"multiline-basic\" | \"multiline-literal\";\n\ninterface TomlScanState {\n\tquote?: TomlQuote;\n\tcontainers: (\"[\" | \"{\")[];\n}\n\ninterface KimiTomlProviderEntry {\n\tname: string;\n\ttype?: string;\n\tbaseUrl?: string;\n\tmodelName?: string;\n\tapiKey?: string;\n}\n\ninterface KimiConfigValues {\n\tbaseUrl?: string;\n\tmodelName?: string;\n\tproviders: KimiTomlProviderEntry[];\n}\n\ntype KimiConfigLoad =\n\t| { kind: \"missing\" }\n\t| { kind: \"ok\"; value: KimiConfigValues }\n\t| { kind: \"invalid\"; line: number; reason: string };\n\nfunction findBasicStringEnd(text: string, start: number, multiline: boolean): number {\n\tfor (let index = start; index < text.length; index++) {\n\t\tif (text[index] === \"\\\\\") {\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (multiline ? text.startsWith('\"\"\"', index) : text[index] === '\"') return index;\n\t}\n\treturn -1;\n}\n\nfunction decodeTomlBasicString(body: string, multiline: boolean): { value: string } | { error: string } {\n\tlet value = \"\";\n\tfor (let index = 0; index < body.length; index++) {\n\t\tconst char = body[index];\n\t\tif (char !== \"\\\\\") {\n\t\t\tconst code = char.codePointAt(0) ?? 0;\n\t\t\tif (code < 0x20 && char !== \"\\t\" && !(multiline && (char === \"\\n\" || char === \"\\r\"))) {\n\t\t\t\treturn { error: \"invalid control character in string value\" };\n\t\t\t}\n\t\t\tvalue += char;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst escapeChar = body[index + 1];\n\t\tif (escapeChar === undefined) return { error: \"unterminated escape sequence\" };\n\t\tif (multiline && (escapeChar === \"\\n\" || escapeChar === \"\\r\")) {\n\t\t\tindex++;\n\t\t\twhile (index + 1 < body.length && /\\s/.test(body[index + 1])) index++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (escapeChar === \"u\" || escapeChar === \"U\") {\n\t\t\tconst width = escapeChar === \"u\" ? 4 : 8;\n\t\t\tconst hex = body.slice(index + 2, index + 2 + width);\n\t\t\tconst codePoint = /^[0-9a-fA-F]+$/.test(hex) && hex.length === width ? Number.parseInt(hex, 16) : -1;\n\t\t\tif (codePoint < 0 || codePoint > 0x10ffff || (codePoint >= 0xd800 && codePoint <= 0xdfff)) {\n\t\t\t\treturn { error: \"invalid unicode escape\" };\n\t\t\t}\n\t\t\tvalue += String.fromCodePoint(codePoint);\n\t\t\tindex += 1 + width;\n\t\t\tcontinue;\n\t\t}\n\t\tconst mapped = TOML_BASIC_ESCAPES[escapeChar];\n\t\tif (mapped === undefined) return { error: \"invalid escape sequence\" };\n\t\tvalue += mapped;\n\t\tindex++;\n\t}\n\treturn { value };\n}\n\n/** Parses one TOML string value. Errors are fixed and never include raw TOML content. */\nfunction parseTomlStringValue(raw: string, requireNonempty = true): { value: string } | { error: string } {\n\tconst text = raw.trim();\n\tconst multilineBasic = text.startsWith('\"\"\"');\n\tconst multilineLiteral = text.startsWith(\"'''\");\n\tconst basic = !multilineBasic && text.startsWith('\"');\n\tconst literal = !multilineLiteral && text.startsWith(\"'\");\n\tif (!multilineBasic && !multilineLiteral && !basic && !literal) {\n\t\treturn { error: \"value must be a quoted string\" };\n\t}\n\n\tconst delimiterWidth = multilineBasic || multilineLiteral ? 3 : 1;\n\tconst end =\n\t\tmultilineBasic || basic\n\t\t\t? findBasicStringEnd(text, delimiterWidth, multilineBasic)\n\t\t\t: text.indexOf(multilineLiteral ? \"'''\" : \"'\", delimiterWidth);\n\tif (end < 0) {\n\t\treturn {\n\t\t\terror:\n\t\t\t\tmultilineBasic || multilineLiteral ? \"unterminated multiline string value\" : \"unterminated string value\",\n\t\t};\n\t}\n\tif (text.slice(end + delimiterWidth).trim().length > 0) {\n\t\treturn { error: \"unexpected content after string value\" };\n\t}\n\n\tlet body = text.slice(delimiterWidth, end);\n\tif ((multilineBasic || multilineLiteral) && body.startsWith(\"\\n\")) body = body.slice(1);\n\tconst parsed =\n\t\tmultilineBasic || basic\n\t\t\t? decodeTomlBasicString(body, multilineBasic)\n\t\t\t: /[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f\\u007f]/.test(body)\n\t\t\t\t? { error: \"invalid control character in string value\" as const }\n\t\t\t\t: { value: body };\n\tif (\"error\" in parsed) return parsed;\n\treturn requireNonempty && parsed.value.length === 0 ? { error: \"empty string value\" } : parsed;\n}\n\nfunction scanTomlLine(text: string, state: TomlScanState): { content: string; error?: string } {\n\tlet content = \"\";\n\tfor (let index = 0; index < text.length; index++) {\n\t\tconst char = text[index];\n\t\tif (state.quote === \"basic\") {\n\t\t\tcontent += char;\n\t\t\tif (char === \"\\\\\" && index + 1 < text.length) content += text[++index];\n\t\t\telse if (char === '\"') state.quote = undefined;\n\t\t\tcontinue;\n\t\t}\n\t\tif (state.quote === \"literal\") {\n\t\t\tcontent += char;\n\t\t\tif (char === \"'\") state.quote = undefined;\n\t\t\tcontinue;\n\t\t}\n\t\tif (state.quote === \"multiline-basic\") {\n\t\t\tif (text.startsWith('\"\"\"', index)) {\n\t\t\t\tcontent += '\"\"\"';\n\t\t\t\tindex += 2;\n\t\t\t\tstate.quote = undefined;\n\t\t\t} else {\n\t\t\t\tcontent += char;\n\t\t\t\tif (char === \"\\\\\" && index + 1 < text.length) content += text[++index];\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (state.quote === \"multiline-literal\") {\n\t\t\tif (text.startsWith(\"'''\", index)) {\n\t\t\t\tcontent += \"'''\";\n\t\t\t\tindex += 2;\n\t\t\t\tstate.quote = undefined;\n\t\t\t} else {\n\t\t\t\tcontent += char;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (char === \"#\") break;\n\t\tif (text.startsWith('\"\"\"', index)) {\n\t\t\tcontent += '\"\"\"';\n\t\t\tindex += 2;\n\t\t\tstate.quote = \"multiline-basic\";\n\t\t\tcontinue;\n\t\t}\n\t\tif (text.startsWith(\"'''\", index)) {\n\t\t\tcontent += \"'''\";\n\t\t\tindex += 2;\n\t\t\tstate.quote = \"multiline-literal\";\n\t\t\tcontinue;\n\t\t}\n\t\tcontent += char;\n\t\tif (char === '\"') state.quote = \"basic\";\n\t\telse if (char === \"'\") state.quote = \"literal\";\n\t\telse if (char === \"[\" || char === \"{\") state.containers.push(char);\n\t\telse if (char === \"]\" || char === \"}\") {\n\t\t\tconst expected = char === \"]\" ? \"[\" : \"{\";\n\t\t\tif (state.containers.pop() !== expected) return { content, error: \"mismatched value delimiter\" };\n\t\t}\n\t}\n\tif (state.quote === \"basic\" || state.quote === \"literal\") {\n\t\treturn { content, error: \"unterminated string value\" };\n\t}\n\treturn { content };\n}\n\nfunction consumeTomlValue(\n\tlines: readonly string[],\n\tstartLine: number,\n\tinitial: string,\n): { value?: string; endLine: number; error?: string } {\n\tconst state: TomlScanState = { containers: [] };\n\tconst chunks: string[] = [];\n\tfor (let line = startLine; line < lines.length; line++) {\n\t\tconst scanned = scanTomlLine(line === startLine ? initial : lines[line], state);\n\t\tchunks.push(scanned.content);\n\t\tif (scanned.error) return { endLine: line, error: scanned.error };\n\t\tif (!state.quote && state.containers.length === 0) {\n\t\t\tconst value = chunks.join(\"\\n\").trim();\n\t\t\treturn value.length > 0\n\t\t\t\t? { value, endLine: line }\n\t\t\t\t: { endLine: startLine, error: \"assignment value is missing\" };\n\t\t}\n\t}\n\treturn {\n\t\tendLine: startLine,\n\t\terror: state.quote ? \"unterminated multiline string value\" : \"unterminated compound value\",\n\t};\n}\n\nfunction findTomlTopLevelCharacter(text: string, wanted: string): number {\n\tconst state: TomlScanState = { containers: [] };\n\tfor (let index = 0; index < text.length; index++) {\n\t\tconst char = text[index];\n\t\tif (state.quote === \"basic\" || state.quote === \"multiline-basic\") {\n\t\t\tif (char === \"\\\\\") index++;\n\t\t\telse if (state.quote === \"basic\" && char === '\"') state.quote = undefined;\n\t\t\telse if (state.quote === \"multiline-basic\" && text.startsWith('\"\"\"', index)) {\n\t\t\t\tstate.quote = undefined;\n\t\t\t\tindex += 2;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (state.quote === \"literal\" || state.quote === \"multiline-literal\") {\n\t\t\tconst delimiter = state.quote === \"literal\" ? \"'\" : \"'''\";\n\t\t\tif (text.startsWith(delimiter, index)) {\n\t\t\t\tstate.quote = undefined;\n\t\t\t\tindex += delimiter.length - 1;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (char === \"#\") return -1;\n\t\tif (text.startsWith('\"\"\"', index)) {\n\t\t\tstate.quote = \"multiline-basic\";\n\t\t\tindex += 2;\n\t\t} else if (text.startsWith(\"'''\", index)) {\n\t\t\tstate.quote = \"multiline-literal\";\n\t\t\tindex += 2;\n\t\t} else if (char === '\"') state.quote = \"basic\";\n\t\telse if (char === \"'\") state.quote = \"literal\";\n\t\telse if (char === \"[\" || char === \"{\") state.containers.push(char);\n\t\telse if (char === \"]\" || char === \"}\") state.containers.pop();\n\t\telse if (char === wanted && state.containers.length === 0) return index;\n\t}\n\treturn -1;\n}\n\nfunction parseTomlDottedKey(raw: string): string[] | undefined {\n\tconst components: string[] = [];\n\tlet index = 0;\n\twhile (index < raw.length) {\n\t\twhile (/\\s/.test(raw[index] ?? \"\")) index++;\n\t\tlet component: string | undefined;\n\t\tif (raw[index] === '\"') {\n\t\t\tconst end = findBasicStringEnd(raw, index + 1, false);\n\t\t\tif (end < 0) return undefined;\n\t\t\tconst parsed = parseTomlStringValue(raw.slice(index, end + 1), false);\n\t\t\tif (\"error\" in parsed) return undefined;\n\t\t\tcomponent = parsed.value;\n\t\t\tindex = end + 1;\n\t\t} else if (raw[index] === \"'\") {\n\t\t\tconst end = raw.indexOf(\"'\", index + 1);\n\t\t\tif (end < 0) return undefined;\n\t\t\tcomponent = raw.slice(index + 1, end);\n\t\t\tindex = end + 1;\n\t\t} else {\n\t\t\tconst match = /^[A-Za-z0-9_-]+/.exec(raw.slice(index));\n\t\t\tif (!match) return undefined;\n\t\t\tcomponent = match[0];\n\t\t\tindex += match[0].length;\n\t\t}\n\t\tcomponents.push(component);\n\t\twhile (/\\s/.test(raw[index] ?? \"\")) index++;\n\t\tif (index === raw.length) return components;\n\t\tif (raw[index] !== \".\") return undefined;\n\t\tindex++;\n\t\tif (index === raw.length) return undefined;\n\t}\n\treturn undefined;\n}\n\nfunction splitTomlTopLevel(raw: string): string[] {\n\tconst parts: string[] = [];\n\tlet start = 0;\n\tfor (let index = 0; index < raw.length; index++) {\n\t\tif (raw[index] !== \",\") continue;\n\t\tconst relative = findTomlTopLevelCharacter(raw.slice(start, index + 1), \",\");\n\t\tif (relative !== index - start) continue;\n\t\tparts.push(raw.slice(start, index).trim());\n\t\tstart = index + 1;\n\t}\n\tparts.push(raw.slice(start).trim());\n\treturn parts;\n}\n\nfunction isValidTomlScalar(raw: string): boolean {\n\treturn (\n\t\traw === \"true\" ||\n\t\traw === \"false\" ||\n\t\t/^[+-]?(?:0|[1-9](?:_?\\d)*)$/.test(raw) ||\n\t\t/^[+-]?0x[0-9A-Fa-f](?:_?[0-9A-Fa-f])*$/.test(raw) ||\n\t\t/^[+-]?0o[0-7](?:_?[0-7])*$/.test(raw) ||\n\t\t/^[+-]?0b[01](?:_?[01])*$/.test(raw) ||\n\t\t/^[+-]?(?:(?:0|[1-9](?:_?\\d)*)\\.(?:\\d(?:_?\\d)*)(?:[eE][+-]?\\d(?:_?\\d)*)?|(?:0|[1-9](?:_?\\d)*)[eE][+-]?\\d(?:_?\\d)*|inf|nan)$/.test(\n\t\t\traw,\n\t\t) ||\n\t\t/^\\d{4}-\\d{2}-\\d{2}(?:[Tt ]\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:[Zz]|[+-]\\d{2}:\\d{2})?)?$/.test(raw) ||\n\t\t/^\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?$/.test(raw)\n\t);\n}\n\nfunction isValidTomlValue(raw: string): boolean {\n\tconst text = raw.trim();\n\tif (text.startsWith('\"') || text.startsWith(\"'\")) return !(\"error\" in parseTomlStringValue(text, false));\n\tif (text.startsWith(\"[\") && text.endsWith(\"]\")) {\n\t\tconst parts = splitTomlTopLevel(text.slice(1, -1));\n\t\tif (parts.at(-1) === \"\") parts.pop();\n\t\treturn parts.every((part) => part.length > 0 && isValidTomlValue(part));\n\t}\n\tif (text.startsWith(\"{\") && text.endsWith(\"}\")) {\n\t\tconst body = text.slice(1, -1).trim();\n\t\tif (body.length === 0) return true;\n\t\treturn splitTomlTopLevel(body).every((part) => {\n\t\t\tconst equals = findTomlTopLevelCharacter(part, \"=\");\n\t\t\treturn equals > 0 && !!parseTomlDottedKey(part.slice(0, equals)) && isValidTomlValue(part.slice(equals + 1));\n\t\t});\n\t}\n\treturn isValidTomlScalar(text);\n}\n\nfunction parseTomlTable(line: string): { components?: string[]; array?: boolean; error?: string } {\n\tconst state: TomlScanState = { containers: [] };\n\tconst scanned = scanTomlLine(line, state);\n\tif (scanned.error || state.quote || state.containers.length > 0) return { error: \"malformed table header\" };\n\tconst text = scanned.content.trim();\n\tconst array = text.startsWith(\"[[\");\n\tif (array ? !text.endsWith(\"]]\") : !text.endsWith(\"]\")) return { error: \"malformed table header\" };\n\tconst inner = text.slice(array ? 2 : 1, array ? -2 : -1).trim();\n\tconst components = parseTomlDottedKey(inner);\n\treturn components && components.length > 0 ? { components, array } : { error: \"malformed table header\" };\n}\n\n/**\n * Strict Kimi TOML subset reader. It validates every table/assignment and generic TOML value,\n * but extracts only root `base_url`/`model_name` and `[providers.<name>]` string fields. Fixed\n * line-number errors ensure credentials and other file content are never echoed.\n */\nfunction loadKimiConfig(path: string): KimiConfigLoad {\n\tif (!existsSync(path)) return { kind: \"missing\" };\n\tlet content: string;\n\ttry {\n\t\tcontent = readFileSync(path, \"utf-8\");\n\t} catch {\n\t\treturn { kind: \"invalid\", line: 0, reason: \"file is unreadable\" };\n\t}\n\tconst value: KimiConfigValues = { providers: [] };\n\tconst lines = content.split(/\\r?\\n/);\n\tconst assignments = new Set<string>();\n\tlet table: KimiTomlProviderEntry | \"root\" | \"other\" = \"root\";\n\tlet scope = \"root\";\n\tfor (let index = 0; index < lines.length; index++) {\n\t\tconst line = lines[index].trim();\n\t\tif (line.length === 0 || line.startsWith(\"#\")) continue;\n\t\tif (line.startsWith(\"[\")) {\n\t\t\tconst parsed = parseTomlTable(line);\n\t\t\tif (parsed.error || !parsed.components) {\n\t\t\t\treturn { kind: \"invalid\", line: index + 1, reason: parsed.error ?? \"malformed table header\" };\n\t\t\t}\n\t\t\tconst [first, name] = parsed.components;\n\t\t\tif (first === \"providers\" && parsed.array) {\n\t\t\t\treturn { kind: \"invalid\", line: index + 1, reason: \"provider table cannot be an array\" };\n\t\t\t}\n\t\t\tif (first === \"providers\" && parsed.components.length === 2 && name !== undefined && name.length > 0) {\n\t\t\t\tconst entry: KimiTomlProviderEntry = { name };\n\t\t\t\tvalue.providers.push(entry);\n\t\t\t\ttable = entry;\n\t\t\t} else {\n\t\t\t\ttable = \"other\";\n\t\t\t}\n\t\t\tscope = `${parsed.array ? \"array\" : \"table\"}:${JSON.stringify(parsed.components)}`;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst assignmentLine = index;\n\t\tconst equals = findTomlTopLevelCharacter(line, \"=\");\n\t\tconst key = equals > 0 ? parseTomlDottedKey(line.slice(0, equals)) : undefined;\n\t\tif (!key || key.length === 0) return { kind: \"invalid\", line: index + 1, reason: \"invalid assignment\" };\n\t\tconst consumed = consumeTomlValue(lines, index, line.slice(equals + 1));\n\t\tif (consumed.error || consumed.value === undefined) {\n\t\t\treturn { kind: \"invalid\", line: assignmentLine + 1, reason: consumed.error ?? \"invalid assignment value\" };\n\t\t}\n\t\tindex = consumed.endLine;\n\t\tif (!isValidTomlValue(consumed.value)) {\n\t\t\treturn { kind: \"invalid\", line: assignmentLine + 1, reason: \"invalid assignment value\" };\n\t\t}\n\t\tconst assignmentId = `${scope}:${JSON.stringify(key)}`;\n\t\tif (assignments.has(assignmentId)) {\n\t\t\treturn { kind: \"invalid\", line: assignmentLine + 1, reason: \"duplicate assignment\" };\n\t\t}\n\t\tassignments.add(assignmentId);\n\t\tif (key.length !== 1 || table === \"other\") continue;\n\n\t\tconst keyName = key[0];\n\t\tconst relevant =\n\t\t\ttable === \"root\"\n\t\t\t\t? keyName === \"base_url\" || keyName === \"model_name\"\n\t\t\t\t: keyName === \"base_url\" ||\n\t\t\t\t\tkeyName === \"model_name\" ||\n\t\t\t\t\tkeyName === \"model\" ||\n\t\t\t\t\tkeyName === \"type\" ||\n\t\t\t\t\tkeyName === \"api_key\";\n\t\tif (!relevant) continue;\n\t\tconst parsedValue = parseTomlStringValue(consumed.value);\n\t\tif (\"error\" in parsedValue) return { kind: \"invalid\", line: assignmentLine + 1, reason: parsedValue.error };\n\t\tif (table === \"root\") {\n\t\t\tif (keyName === \"base_url\") value.baseUrl = parsedValue.value;\n\t\t\telse value.modelName = parsedValue.value;\n\t\t} else {\n\t\t\tif (keyName === \"base_url\") table.baseUrl = parsedValue.value;\n\t\t\telse if (keyName === \"type\") table.type = parsedValue.value;\n\t\t\telse if (keyName === \"api_key\") table.apiKey = parsedValue.value;\n\t\t\telse table.modelName = parsedValue.value;\n\t\t}\n\t}\n\treturn { kind: \"ok\", value };\n}\n\nfunction nonemptyEnv(name: string): string | undefined {\n\tconst value = process.env[name];\n\treturn value && value.length > 0 ? value : undefined;\n}\n\ninterface KimiResolution {\n\tbaseUrl?: string;\n\tmodelName?: string;\n\tbaseUrlSource?: \"kimi-environment\" | \"kimi-config-toml\";\n\t/** First `[providers.<name>]` entry with `type = \"openai_legacy\"` and a base URL (§10.2). */\n\tcustomProvider?: { name: string; baseUrl: string; modelName?: string; apiKey?: string };\n\tfailure?: { line: number; reason: string };\n}\n\nfunction resolveKimiConfig(kimiConfigPath: string | undefined): KimiResolution {\n\tconst path = kimiConfigPath ?? join(homedir(), \".kimi\", \"config.toml\");\n\tconst loaded = loadKimiConfig(path);\n\tif (loaded.kind === \"invalid\") return { failure: { line: loaded.line, reason: loaded.reason } };\n\tconst fileValues: KimiConfigValues = loaded.kind === \"ok\" ? loaded.value : { providers: [] };\n\tconst envBaseUrl = nonemptyEnv(\"KIMI_BASE_URL\");\n\tconst envModelName = nonemptyEnv(\"KIMI_MODEL_NAME\");\n\tconst custom = fileValues.providers.find((entry) => entry.type === \"openai_legacy\" && entry.baseUrl);\n\treturn {\n\t\tbaseUrl: envBaseUrl ?? fileValues.baseUrl,\n\t\tmodelName: envModelName ?? fileValues.modelName,\n\t\tbaseUrlSource: envBaseUrl ? \"kimi-environment\" : fileValues.baseUrl ? \"kimi-config-toml\" : undefined,\n\t\tcustomProvider: custom\n\t\t\t? { name: custom.name, baseUrl: custom.baseUrl as string, modelName: custom.modelName, apiKey: custom.apiKey }\n\t\t\t: undefined,\n\t};\n}\n\nfunction loadConfig<T>(path: string, parse: (value: unknown) => T | undefined, allowComments = false): LoadedConfig<T> {\n\tif (!existsSync(path)) return { kind: \"missing\" };\n\ttry {\n\t\tconst content = readFileSync(path, \"utf-8\");\n\t\tconst parsed = JSON.parse(allowComments ? stripJsonComments(content) : content) as unknown;\n\t\tconst value = parse(parsed);\n\t\treturn value ? { kind: \"ok\", value } : { kind: \"invalid\" };\n\t} catch {\n\t\treturn { kind: \"invalid\" };\n\t}\n}\n\nexport function normalizedUrlHostname(url: URL): string {\n\treturn normalizedHostname(url);\n}\n\nfunction normalizedHostname(url: URL): string {\n\tconst hostname = url.hostname.toLowerCase();\n\tconst unwrapped = hostname.startsWith(\"[\") && hostname.endsWith(\"]\") ? hostname.slice(1, -1) : hostname;\n\treturn unwrapped.endsWith(\".\") ? unwrapped.slice(0, -1) : unwrapped;\n}\n\nfunction isLiteralLoopback(hostname: string): boolean {\n\tif (hostname === \"localhost\" || hostname === \"::1\") return true;\n\tif (isIP(hostname) !== 4) return false;\n\treturn Number(hostname.split(\".\")[0]) === 127;\n}\n\nfunction isBlockedPublicHost(hostname: string): boolean {\n\tif (hostname === \"localhost\" || hostname.endsWith(\".localhost\")) return true;\n\tif (isIP(hostname) === 0) return false;\n\treturn isBlockedPublicAddress(hostname);\n}\n\nfunction sanitizeUrl(rawUrl: string): URL | undefined {\n\ttry {\n\t\tconst url = new URL(rawUrl);\n\t\turl.username = \"\";\n\t\turl.password = \"\";\n\t\turl.search = \"\";\n\t\turl.hash = \"\";\n\t\treturn url;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function canonicalProviderEndpointUrl(rawUrl: string | undefined): string | undefined {\n\tif (!rawUrl) return undefined;\n\tconst url = sanitizeUrl(rawUrl);\n\tif (!url) return undefined;\n\tconst hostname = normalizedHostname(url);\n\tconst host = isIP(hostname) === 6 ? `[${hostname}]` : hostname;\n\tconst pathname = url.pathname.replace(/\\/+$/, \"\") || \"/\";\n\treturn `${url.protocol}//${host}${url.port ? `:${url.port}` : \"\"}${pathname}`;\n}\n\nfunction credentialBindingMatchesTarget(\n\tbinding: ProviderDoctorCredentialBinding,\n\ttarget: ResolvedProviderTarget,\n): boolean {\n\treturn (\n\t\tbinding.origin === target.origin &&\n\t\tbinding.source === target.source &&\n\t\tbinding.baseUrl === canonicalProviderEndpointUrl(target.endpoint.baseUrl) &&\n\t\tbinding.api === target.endpoint.api &&\n\t\tbinding.modelId === target.endpoint.modelId\n\t);\n}\n\nfunction appendPath(url: URL, segment: string): URL {\n\tconst next = new URL(url.href);\n\tnext.pathname = `${next.pathname.replace(/\\/$/, \"\")}/${segment}`;\n\treturn next;\n}\n\nfunction makeRootProbeUrl(url: URL, origin: ProviderOrigin): URL {\n\tif (origin !== \"local-proxy\") return new URL(url.href);\n\tconst health = new URL(url.href);\n\thealth.pathname = \"/health\";\n\treturn health;\n}\n\nfunction publicResult(\n\ttarget: ResolvedProviderTarget,\n\tlevel: ProviderDoctorLevel,\n\tchecks: ProviderDoctorCheck[],\n\turl: URL | undefined,\n\tfailure?: ProbeFailure,\n\tcostWarning?: boolean,\n): ProviderDoctorResult {\n\tconst targetUrl = url?.href;\n\treturn {\n\t\tprovider: target.providerId,\n\t\tstatus: failure ? \"fail\" : \"ok\",\n\t\tlevel,\n\t\torigin: target.origin,\n\t\tsource: target.source,\n\t\ttargetUrl,\n\t\tbaseUrl: targetUrl,\n\t\tapi: target.endpoint.api,\n\t\tmodelId: target.endpoint.modelId,\n\t\tauthPresent: target.auth.present,\n\t\t...(costWarning ? { costWarning: true } : {}),\n\t\tchecks,\n\t\terror: failure,\n\t};\n}\n\nfunction unresolvedFailure(\n\tproviderId: string,\n\tlevel: ProviderDoctorLevel,\n\tcode: ProviderDoctorCode,\n\tmessage: string,\n): ProviderDoctorResult {\n\tconst check: ProviderDoctorCheck = { name: \"config\", status: \"fail\", category: \"config\", code, message };\n\treturn {\n\t\tprovider: providerId,\n\t\tstatus: \"fail\",\n\t\tlevel,\n\t\torigin: \"unknown\",\n\t\tsource: \"unresolved\",\n\t\tauthPresent: false,\n\t\tchecks: [check],\n\t\terror: { category: \"config\", code, message },\n\t};\n}\n\nfunction staticChecks(target: ResolvedProviderTarget): {\n\tchecks: ProviderDoctorCheck[];\n\turl?: URL;\n\tfailure?: ProbeFailure;\n} {\n\tconst checks: ProviderDoctorCheck[] = [\n\t\t{ name: \"config-present\", status: \"ok\", code: \"ok\", message: \"Provider configuration resolved\" },\n\t];\n\tif (target.origin === \"custom-openai-compatible\") {\n\t\tchecks.push({\n\t\t\tname: \"native-provider-checks\",\n\t\t\tstatus: \"skipped\",\n\t\t\tcode: \"ok\",\n\t\t\tmessage: \"Custom OpenAI-compatible endpoint; native-only provider checks skipped\",\n\t\t});\n\t}\n\tlet failure: ProbeFailure | undefined;\n\tconst recordFailure = (next: ProbeFailure): void => {\n\t\tfailure ??= next;\n\t};\n\n\tconst requestedModel = target.endpoint.modelId;\n\tconst modelIds = target.endpoint.modelIds ?? [];\n\tif (!requestedModel) {\n\t\tchecks.push({ name: \"model-provider-relation\", status: \"skipped\", code: \"ok\", message: \"No model selected\" });\n\t} else if (!modelIds.includes(requestedModel)) {\n\t\tconst next: ProbeFailure = {\n\t\t\tcategory: \"model\",\n\t\t\tcode: \"model-provider-mismatch\",\n\t\t\tmessage: \"Selected model does not belong to the provider\",\n\t\t};\n\t\tchecks.push({ name: \"model-provider-relation\", status: \"fail\", ...next });\n\t\trecordFailure(next);\n\t} else {\n\t\tchecks.push({ name: \"model-provider-relation\", status: \"ok\", code: \"ok\", message: \"Model belongs to provider\" });\n\t}\n\n\tconst api = target.endpoint.api;\n\tif (!api) {\n\t\tconst next: ProbeFailure = { category: \"config\", code: \"api-missing\", message: \"Provider API type is missing\" };\n\t\tchecks.push({ name: \"api\", status: \"fail\", ...next });\n\t\trecordFailure(next);\n\t} else if (\n\t\t!KNOWN_APIS.has(api) ||\n\t\t(target.origin === \"custom-openai-compatible\" && !OPENAI_COMPATIBLE_APIS.has(api))\n\t) {\n\t\tconst next: ProbeFailure = {\n\t\t\tcategory: \"config\",\n\t\t\tcode: \"api-unsupported\",\n\t\t\tmessage: \"Provider API type is unsupported\",\n\t\t};\n\t\tchecks.push({ name: \"api\", status: \"fail\", ...next });\n\t\trecordFailure(next);\n\t} else {\n\t\tchecks.push({ name: \"api\", status: \"ok\", code: \"ok\", message: \"Provider API type is supported\" });\n\t}\n\n\tlet url: URL | undefined;\n\tif (!target.endpoint.baseUrl) {\n\t\tconst next: ProbeFailure = {\n\t\t\tcategory: \"config\",\n\t\t\tcode: \"base-url-missing\",\n\t\t\tmessage: \"Provider base URL is missing\",\n\t\t};\n\t\tchecks.push({ name: \"base-url\", status: \"fail\", ...next });\n\t\trecordFailure(next);\n\t} else {\n\t\turl = sanitizeUrl(target.endpoint.baseUrl);\n\t\tif (!url) {\n\t\t\tconst next: ProbeFailure = {\n\t\t\t\tcategory: \"config\",\n\t\t\t\tcode: \"base-url-invalid\",\n\t\t\t\tmessage: \"Provider base URL is invalid\",\n\t\t\t};\n\t\t\tchecks.push({ name: \"base-url\", status: \"fail\", ...next });\n\t\t\trecordFailure(next);\n\t\t} else {\n\t\t\tconst allowedScheme =\n\t\t\t\ttarget.origin === \"local-proxy\"\n\t\t\t\t\t? url.protocol === \"http:\" || url.protocol === \"https:\"\n\t\t\t\t\t: url.protocol === \"https:\";\n\t\t\tif (!allowedScheme) {\n\t\t\t\tconst next: ProbeFailure = {\n\t\t\t\t\tcategory: \"config\",\n\t\t\t\t\tcode: \"url-scheme-unsupported\",\n\t\t\t\t\tmessage: \"Provider URL scheme is not allowed\",\n\t\t\t\t};\n\t\t\t\tchecks.push({ name: \"base-url\", status: \"fail\", ...next });\n\t\t\t\trecordFailure(next);\n\t\t\t} else {\n\t\t\t\tchecks.push({ name: \"base-url\", status: \"ok\", code: \"ok\", message: \"Provider base URL is valid\" });\n\t\t\t}\n\t\t}\n\t}\n\n\tif (target.origin === \"unknown\") {\n\t\tconst next: ProbeFailure = {\n\t\t\tcategory: \"config\",\n\t\t\tcode: \"origin-unsupported\",\n\t\t\tmessage: \"Provider origin is unsupported\",\n\t\t};\n\t\tchecks.push({ name: \"origin-policy\", status: \"fail\", ...next });\n\t\trecordFailure(next);\n\t} else if (url) {\n\t\tconst hostname = normalizedHostname(url);\n\t\tconst blocked = target.origin === \"local-proxy\" ? !isLiteralLoopback(hostname) : isBlockedPublicHost(hostname);\n\t\tif (blocked) {\n\t\t\tconst next: ProbeFailure = {\n\t\t\t\tcategory: \"config\",\n\t\t\t\tcode: \"address-policy-blocked\",\n\t\t\t\tmessage: \"Provider address is blocked by origin policy\",\n\t\t\t};\n\t\t\tchecks.push({ name: \"origin-policy\", status: \"fail\", ...next });\n\t\t\trecordFailure(next);\n\t\t} else {\n\t\t\tchecks.push({ name: \"origin-policy\", status: \"ok\", code: \"ok\", message: \"Provider origin policy passed\" });\n\t\t}\n\t} else {\n\t\tchecks.push({ name: \"origin-policy\", status: \"skipped\", code: \"ok\", message: \"No valid URL to inspect\" });\n\t}\n\n\tif (target.auth.present) {\n\t\tchecks.push({ name: \"auth-present\", status: \"ok\", code: \"ok\", message: \"Authentication is configured\" });\n\t} else if (target.origin === \"custom-openai-compatible\" || target.origin === \"local-proxy\") {\n\t\t// Native login checks do not apply: custom/local OpenAI-compatible endpoints may be keyless.\n\t\tchecks.push({\n\t\t\tname: \"auth-present\",\n\t\t\tstatus: \"skipped\",\n\t\t\tcode: \"ok\",\n\t\t\tmessage: \"No credential configured; custom endpoints may be keyless\",\n\t\t});\n\t} else {\n\t\tconst next: ProbeFailure = {\n\t\t\tcategory: \"auth\",\n\t\t\tcode: \"auth-missing\",\n\t\t\tmessage: \"Authentication is not configured\",\n\t\t};\n\t\tchecks.push({ name: \"auth-present\", status: \"fail\", ...next });\n\t\trecordFailure(next);\n\t}\n\n\treturn { checks, url, failure };\n}\n\ntype ProbeEndpointKind = \"root\" | \"models\" | \"model-probe\";\n\nfunction classifyProbe(name: string, kind: ProbeEndpointKind, status: number): ProbeOutcome {\n\tconst forModels = kind === \"models\";\n\tif (Number.isInteger(status) && status >= 200 && status < 300) {\n\t\tconst probe: EndpointProbeResult = { reachable: true, authenticated: true, status, category: \"ok\" };\n\t\tif (forModels) probe.modelsSupported = true;\n\t\treturn { check: { name, status: \"ok\", code: \"ok\", message: `HTTP ${status}`, probe } };\n\t}\n\tif (status >= 300 && status < 400) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"config\",\n\t\t\tcode: \"redirect-blocked\",\n\t\t\tmessage: \"Provider probe returned a blocked redirect\",\n\t\t};\n\t\treturn { check: { name, status: \"fail\", ...failure }, failure };\n\t}\n\tif (status === 401 || status === 403) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"auth\",\n\t\t\tcode: \"authentication-failed\",\n\t\t\tmessage: \"Provider rejected authentication\",\n\t\t};\n\t\tconst probe: EndpointProbeResult = { reachable: true, authenticated: false, status, category: \"auth\" };\n\t\treturn { check: { name, status: \"fail\", ...failure, probe }, failure };\n\t}\n\tif (status === 404 || status === 405 || status === 501) {\n\t\t// A root or /models 404 is neutral: OpenAI-compatible servers may not expose these paths.\n\t\tconst probe: EndpointProbeResult = { reachable: true, status, category: \"unsupported-endpoint\" };\n\t\tif (forModels) probe.modelsSupported = false;\n\t\treturn {\n\t\t\tcheck: {\n\t\t\t\tname,\n\t\t\t\tstatus: \"unsupported\",\n\t\t\t\tcode: \"endpoint-unsupported\",\n\t\t\t\tmessage: `HTTP ${status}; endpoint is not required`,\n\t\t\t\tprobe,\n\t\t\t},\n\t\t};\n\t}\n\tif (status >= 500 && status <= 599) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"server\",\n\t\t\tcode: \"server-error\",\n\t\t\tmessage: \"Provider endpoint returned a server error\",\n\t\t};\n\t\tconst probe: EndpointProbeResult = { reachable: true, status, category: \"server\" };\n\t\treturn { check: { name, status: \"fail\", ...failure, probe }, failure };\n\t}\n\tif (status === 408 || status === 429) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"network\",\n\t\t\tcode: \"network-failure\",\n\t\t\tmessage: \"Provider probe failed at the network boundary\",\n\t\t};\n\t\tconst probe: EndpointProbeResult = { reachable: true, status, category: \"network\" };\n\t\treturn { check: { name, status: \"fail\", ...failure, probe }, failure };\n\t}\n\n\tconst failure: ProbeFailure = {\n\t\tcategory: \"config\",\n\t\tcode: \"unexpected-response\",\n\t\tmessage: \"Provider probe returned an unexpected response\",\n\t};\n\treturn { check: { name, status: \"fail\", ...failure }, failure };\n}\n\nfunction createComposedController(\n\tsignal: AbortSignal | undefined,\n\ttimeoutMs: number,\n): {\n\tcontroller: AbortController;\n\tabortKind: () => \"aborted\" | \"timeout\" | undefined;\n\tdispose: () => void;\n} {\n\tconst controller = new AbortController();\n\tlet kind: \"aborted\" | \"timeout\" | undefined;\n\tconst abortFromCaller = (): void => {\n\t\tkind ??= \"aborted\";\n\t\tcontroller.abort();\n\t};\n\tif (signal?.aborted) abortFromCaller();\n\telse signal?.addEventListener(\"abort\", abortFromCaller, { once: true });\n\tconst timeout = setTimeout(() => {\n\t\tkind ??= \"timeout\";\n\t\tcontroller.abort();\n\t}, timeoutMs);\n\n\treturn {\n\t\tcontroller,\n\t\tabortKind: () => kind,\n\t\tdispose: () => {\n\t\t\tclearTimeout(timeout);\n\t\t\tsignal?.removeEventListener(\"abort\", abortFromCaller);\n\t\t},\n\t};\n}\n\nfunction requestWithAbort(\n\ttransport: ProviderDoctorTransport,\n\trequest: ProviderDoctorTransportRequest,\n): Promise<ProviderDoctorTransportResponse> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (request.signal.aborted) {\n\t\t\treject(ABORTED);\n\t\t\treturn;\n\t\t}\n\t\tconst onAbort = (): void => reject(ABORTED);\n\t\trequest.signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\tlet pending: Promise<ProviderDoctorTransportResponse>;\n\t\ttry {\n\t\t\tpending = transport.request(request);\n\t\t} catch (error) {\n\t\t\trequest.signal.removeEventListener(\"abort\", onAbort);\n\t\t\treject(error instanceof ProviderDoctorAuthMaterializationError ? error : undefined);\n\t\t\treturn;\n\t\t}\n\t\tvoid pending.then(\n\t\t\t(response) => {\n\t\t\t\trequest.signal.removeEventListener(\"abort\", onAbort);\n\t\t\t\tresolve(response);\n\t\t\t},\n\t\t\t(error: unknown) => {\n\t\t\t\trequest.signal.removeEventListener(\"abort\", onAbort);\n\t\t\t\treject(error instanceof ProviderDoctorAuthMaterializationError ? error : undefined);\n\t\t\t},\n\t\t);\n\t});\n}\n\ninterface ProbePayload {\n\tbody: string;\n\tcontentType: \"application/json\";\n}\n\nasync function runProbe(\n\tname: string,\n\tkind: ProbeEndpointKind,\n\turl: URL,\n\ttransport: ProviderDoctorTransport,\n\taddressPolicy: ProviderDoctorAddressPolicy,\n\tcreateHeaders: ProviderDoctorHeaderFactory,\n\tcontroller: ReturnType<typeof createComposedController>,\n\tpayload?: ProbePayload,\n): Promise<ProbeOutcome> {\n\ttry {\n\t\tconst response = await requestWithAbort(transport, {\n\t\t\turl,\n\t\t\tmethod: payload ? \"POST\" : \"GET\",\n\t\t\tredirect: \"manual\",\n\t\t\tsignal: controller.controller.signal,\n\t\t\taddressPolicy,\n\t\t\tcreateHeaders,\n\t\t\tbody: payload?.body,\n\t\t\tcontentType: payload?.contentType,\n\t\t});\n\t\treturn classifyProbe(name, kind, response.status);\n\t} catch (error) {\n\t\tif (error instanceof ProviderDoctorAuthMaterializationError) {\n\t\t\tconst failure: ProbeFailure = {\n\t\t\t\tcategory: \"auth\",\n\t\t\t\tcode: \"auth-materialization-failed\",\n\t\t\t\tmessage: \"Configured authentication could not be materialized; verify auth and config references\",\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tcheck: {\n\t\t\t\t\tname,\n\t\t\t\t\tstatus: \"fail\",\n\t\t\t\t\t...failure,\n\t\t\t\t\tprobe: { reachable: false, authenticated: false, category: \"auth\" },\n\t\t\t\t},\n\t\t\t\tfailure,\n\t\t\t};\n\t\t}\n\t\tconst abortKind = controller.abortKind();\n\t\tconst failure: ProbeFailure = abortKind\n\t\t\t? {\n\t\t\t\t\tcategory: \"network\",\n\t\t\t\t\tcode: abortKind === \"timeout\" ? \"request-timeout\" : \"request-aborted\",\n\t\t\t\t\tmessage: abortKind === \"timeout\" ? \"Provider probe timed out\" : \"Provider probe was aborted\",\n\t\t\t\t}\n\t\t\t: { category: \"network\", code: \"network-failure\", message: \"Provider transport failed\" };\n\t\treturn {\n\t\t\tcheck: { name, status: \"fail\", ...failure, probe: { reachable: false, category: \"network\" } },\n\t\t\tfailure,\n\t\t};\n\t}\n}\n\n/** Minimal-token generative probe payload; never includes tools (no tool-call probe). */\nfunction modelProbeSpec(api: string, modelId: string): { path: string; payload: ProbePayload } | undefined {\n\tif (api === \"openai-completions\") {\n\t\treturn {\n\t\t\tpath: \"chat/completions\",\n\t\t\tpayload: {\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel: modelId,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \"ping\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t\tstream: false,\n\t\t\t\t}),\n\t\t\t\tcontentType: \"application/json\",\n\t\t\t},\n\t\t};\n\t}\n\tif (api === \"openai-responses\") {\n\t\treturn {\n\t\t\tpath: \"responses\",\n\t\t\tpayload: {\n\t\t\t\t// The responses API rejects max_output_tokens below 16; still the minimal request.\n\t\t\t\tbody: JSON.stringify({ model: modelId, input: \"ping\", max_output_tokens: 16, stream: false }),\n\t\t\t\tcontentType: \"application/json\",\n\t\t\t},\n\t\t};\n\t}\n\treturn undefined;\n}\n\nfunction classifyOrigin(\n\tproviderId: string,\n\tbaseUrl: string | undefined,\n\tapi: string | undefined,\n\tisNative: boolean,\n): ProviderOrigin {\n\tconst url = baseUrl ? sanitizeUrl(baseUrl) : undefined;\n\tif (providerId === \"grok-oauth-proxy\" || (url && isLiteralLoopback(normalizedHostname(url)))) {\n\t\treturn \"local-proxy\";\n\t}\n\tif (isNative) return \"native\";\n\tif (api && OPENAI_COMPATIBLE_APIS.has(api)) return \"custom-openai-compatible\";\n\treturn \"unknown\";\n}\n\nfunction resolveProviderTarget(\n\tproviderId: string,\n\toptions: ProviderDoctorOptions,\n): { target?: ResolvedProviderTarget; failure?: ProviderDoctorResult } {\n\tconst level = options.level ?? 0;\n\tconst agentDir = options.agentDir ?? getConfiguredAgentDir();\n\tconst modelsConfig = loadConfig(join(agentDir, \"models.json\"), parseModelsConfig, true);\n\tif (modelsConfig.kind === \"invalid\") {\n\t\treturn {\n\t\t\tfailure: unresolvedFailure(providerId, level, \"models-config-invalid\", \"models.json is invalid\"),\n\t\t};\n\t}\n\tconst authConfig = loadConfig(join(agentDir, \"auth.json\"), parseAuthConfig);\n\tif (authConfig.kind === \"invalid\") {\n\t\treturn { failure: unresolvedFailure(providerId, level, \"auth-config-invalid\", \"auth.json is invalid\") };\n\t}\n\n\tconst kimi = providerId === KIMI_PROVIDER_ID ? resolveKimiConfig(options.kimiConfigPath) : {};\n\tif (kimi.failure) {\n\t\treturn {\n\t\t\tfailure: unresolvedFailure(\n\t\t\t\tproviderId,\n\t\t\t\tlevel,\n\t\t\t\t\"kimi-config-invalid\",\n\t\t\t\t`Kimi config TOML is invalid (line ${kimi.failure.line}: ${kimi.failure.reason}); fix the Kimi config or set KIMI_BASE_URL/KIMI_MODEL_NAME`,\n\t\t\t),\n\t\t};\n\t}\n\n\tconst configured = modelsConfig.kind === \"ok\" ? modelsConfig.value.providers[providerId] : undefined;\n\tconst nativeProviders = getProviders();\n\tconst isNative = nativeProviders.includes(providerId as KnownProvider);\n\tconst nativeModels = isNative ? (getModels(providerId as KnownProvider) as Model<Api>[]) : [];\n\tconst configuredModels = configured?.models ?? [];\n\t// At Level 2 the probe model is the paid target and therefore drives endpoint resolution.\n\tconst requestedModel = level === 2 ? (options.probeModelId ?? options.modelId) : options.modelId;\n\tconst configuredModel = requestedModel\n\t\t? configuredModels.find((model) => model.id === requestedModel)\n\t\t: configuredModels[0];\n\n\t// §10.2: with no higher-precedence endpoint (models.json > KIMI_BASE_URL > root TOML), a\n\t// `[providers.<name>]` entry with `type = \"openai_legacy\"` selects a custom OpenAI-compatible\n\t// endpoint, so native-only checks and native credential/model inheritance are skipped downstream.\n\tconst kimiCustomSelected =\n\t\t!(configuredModel?.baseUrl ?? configured?.baseUrl) && !kimi.baseUrl && kimi.customProvider !== undefined;\n\tconst kimiModelName = kimi.modelName ?? (kimiCustomSelected ? kimi.customProvider?.modelName : undefined);\n\tconst endpointModelId = requestedModel ?? kimiModelName;\n\tconst modelIds = kimiCustomSelected\n\t\t? kimiModelName\n\t\t\t? [kimiModelName]\n\t\t\t: []\n\t\t: Array.from(\n\t\t\t\tnew Set([\n\t\t\t\t\t...nativeModels.map((model) => model.id),\n\t\t\t\t\t...configuredModels.map((model) => model.id),\n\t\t\t\t\t...(kimiModelName ? [kimiModelName] : []),\n\t\t\t\t]),\n\t\t\t);\n\tlet nativeModel = kimiCustomSelected\n\t\t? undefined\n\t\t: requestedModel\n\t\t\t? nativeModels.find((model) => model.id === requestedModel)\n\t\t\t: nativeModels[0];\n\tif (!nativeModel && !kimiCustomSelected && requestedModel !== undefined && requestedModel === kimiModelName) {\n\t\t// A native Kimi TOML/env model rides on the provider defaults for endpoint metadata.\n\t\tnativeModel = nativeModels[0];\n\t}\n\n\tlet baseUrl = configuredModel?.baseUrl ?? configured?.baseUrl;\n\tlet api = configuredModel?.api ?? configured?.api ?? nativeModel?.api;\n\tlet source = configured ? \"models.json\" : isNative ? \"built-in-model-registry\" : \"unresolved\";\n\tif (!baseUrl && kimi.baseUrl) {\n\t\tbaseUrl = kimi.baseUrl;\n\t\tsource = kimi.baseUrlSource ?? source;\n\t} else if (kimiCustomSelected && kimi.customProvider) {\n\t\tbaseUrl = kimi.customProvider.baseUrl;\n\t\tapi = \"openai-completions\";\n\t\tsource = `kimi-config-toml:providers.${kimi.customProvider.name}`;\n\t} else {\n\t\tbaseUrl = baseUrl ?? nativeModel?.baseUrl;\n\t}\n\tlet builtInAuth = false;\n\tif (providerId === \"grok-oauth-proxy\" && !baseUrl) {\n\t\tbaseUrl = \"http://127.0.0.1:9996/v1\";\n\t\tapi = \"openai-completions\";\n\t\tsource = \"built-in-grok-oauth-proxy-defaults\";\n\t\tbuiltInAuth = true;\n\t}\n\n\tconst storedAuth = authConfig.kind === \"ok\" && authConfig.value[providerId] !== undefined;\n\tconst environmentAuth = findEnvKeys(providerId) !== undefined;\n\tconst configuredAuth = configured?.apiKey ? isConfigValueConfigured(configured.apiKey) : false;\n\tconst headerAuth = configured?.headers !== undefined && Object.keys(configured.headers).length > 0;\n\tconst customApiKey = kimiCustomSelected ? kimi.customProvider?.apiKey : undefined;\n\tconst customAuthPresent = customApiKey !== undefined && isConfigValueConfigured(customApiKey);\n\tconst authPresent = kimiCustomSelected\n\t\t? customAuthPresent\n\t\t: storedAuth || environmentAuth || configuredAuth || headerAuth || builtInAuth;\n\tconst authSource = kimiCustomSelected\n\t\t? customAuthPresent\n\t\t\t? source\n\t\t\t: undefined\n\t\t: storedAuth\n\t\t\t? \"auth.json\"\n\t\t\t: environmentAuth\n\t\t\t\t? \"environment\"\n\t\t\t\t: configuredAuth || headerAuth\n\t\t\t\t\t? \"models.json\"\n\t\t\t\t\t: builtInAuth\n\t\t\t\t\t\t? \"built-in\"\n\t\t\t\t\t\t: undefined;\n\n\tif (!configured && !isNative && !storedAuth && !environmentAuth && providerId !== \"grok-oauth-proxy\") {\n\t\treturn { failure: unresolvedFailure(providerId, level, \"provider-not-found\", \"Provider was not found\") };\n\t}\n\n\tconst origin = classifyOrigin(providerId, baseUrl, api, isNative && !kimiCustomSelected);\n\tconst canonicalBaseUrl = canonicalProviderEndpointUrl(baseUrl);\n\tconst binding =\n\t\tcanonicalBaseUrl && api && endpointModelId\n\t\t\t? { origin, source, baseUrl: canonicalBaseUrl, api, modelId: endpointModelId }\n\t\t\t: undefined;\n\tconst createHeaders: ProviderDoctorHeaderFactory | undefined =\n\t\tkimiCustomSelected && binding\n\t\t\t? () => {\n\t\t\t\t\tconst headers = new Headers();\n\t\t\t\t\tif (customApiKey) {\n\t\t\t\t\t\theaders.set(\n\t\t\t\t\t\t\t\"Authorization\",\n\t\t\t\t\t\t\t`Bearer ${resolveConfigValueOrThrow(customApiKey, \"API key for custom Kimi endpoint\")}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn headers;\n\t\t\t\t}\n\t\t\t: undefined;\n\n\treturn {\n\t\ttarget: {\n\t\t\tproviderId,\n\t\t\torigin,\n\t\t\tsource,\n\t\t\tendpoint: { baseUrl, api, modelIds, modelId: endpointModelId },\n\t\t\tauth: { present: authPresent, source: authSource, createHeaders, binding },\n\t\t},\n\t};\n}\n\nexport async function diagnoseResolvedProvider(\n\ttarget: ResolvedProviderTarget,\n\toptions: ProviderDoctorOptions = {},\n\tdependencies: ProviderDoctorDependencies = {},\n): Promise<ProviderDoctorResult> {\n\tconst requestedLevel = options.level ?? 0;\n\tif (requestedLevel !== 0 && requestedLevel !== 1 && requestedLevel !== 2) {\n\t\treturn unresolvedFailure(target.providerId, 0, \"unsupported-level\", \"Provider doctor level is unsupported\");\n\t}\n\tconst level: ProviderDoctorLevel = requestedLevel;\n\t// Level 2 is opt-in only: probeModelId is ignored at levels 0/1, which stay GET-only.\n\tconst probeModelId = level === 2 ? options.probeModelId : undefined;\n\tconst effectiveModelId = probeModelId ?? options.modelId;\n\tconst resolvedTarget = effectiveModelId\n\t\t? { ...target, endpoint: { ...target.endpoint, modelId: effectiveModelId } }\n\t\t: target;\n\tif (level === 2 && options.modelId && probeModelId && options.modelId !== probeModelId) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"model\",\n\t\t\tcode: \"probe-model-conflict\",\n\t\t\tmessage: \"Validation model and paid probe model must match\",\n\t\t};\n\t\treturn publicResult(\n\t\t\tresolvedTarget,\n\t\t\tlevel,\n\t\t\t[{ name: \"model-probe\", status: \"fail\", ...failure }],\n\t\t\tundefined,\n\t\t\tfailure,\n\t\t);\n\t}\n\tif (level === 2 && !probeModelId) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"config\",\n\t\t\tcode: \"probe-model-required\",\n\t\t\tmessage: \"Level 2 requires an explicit probe model (--probe-model)\",\n\t\t};\n\t\treturn publicResult(\n\t\t\tresolvedTarget,\n\t\t\tlevel,\n\t\t\t[{ name: \"model-probe\", status: \"fail\", ...failure }],\n\t\t\tundefined,\n\t\t\tfailure,\n\t\t);\n\t}\n\tconst validation = staticChecks(resolvedTarget);\n\tif (validation.failure || level === 0) {\n\t\treturn publicResult(resolvedTarget, level, validation.checks, validation.url, validation.failure);\n\t}\n\n\tconst transport = dependencies.transport ?? options.transport;\n\tif (!transport) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"config\",\n\t\t\tcode: \"transport-required\",\n\t\t\tmessage: \"Level 1 requires an injected provider transport\",\n\t\t};\n\t\tvalidation.checks.push({ name: \"transport\", status: \"fail\", ...failure });\n\t\treturn publicResult(resolvedTarget, level, validation.checks, validation.url, failure);\n\t}\n\tif (resolvedTarget.origin !== \"local-proxy\" && transport.pinsResolvedAddress !== true) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"config\",\n\t\t\tcode: \"address-pinning-required\",\n\t\t\tmessage: \"Public provider transport must pin resolved addresses\",\n\t\t};\n\t\tvalidation.checks.push({ name: \"transport\", status: \"fail\", ...failure });\n\t\treturn publicResult(resolvedTarget, level, validation.checks, validation.url, failure);\n\t}\n\n\tconst timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\tif (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"config\",\n\t\t\tcode: \"unexpected-response\",\n\t\t\tmessage: \"Provider probe timeout is invalid\",\n\t\t};\n\t\tvalidation.checks.push({ name: \"transport\", status: \"fail\", ...failure });\n\t\treturn publicResult(resolvedTarget, level, validation.checks, validation.url, failure);\n\t}\n\n\tconst addressPolicy: ProviderDoctorAddressPolicy =\n\t\tresolvedTarget.origin === \"local-proxy\"\n\t\t\t? { kind: \"loopback-only\", requireAddressPinning: false }\n\t\t\t: { kind: \"public\", requireAddressPinning: true };\n\tconst resolveAuthHeaders = dependencies.resolveAuthHeaders ?? options.resolveAuthHeaders;\n\tconst credentialBinding = resolvedTarget.auth.binding;\n\tconst boundHeaderFactory = resolvedTarget.auth.createHeaders;\n\tconst endpointBoundHeaders: ProviderDoctorHeaderFactory | undefined =\n\t\tcredentialBinding && boundHeaderFactory\n\t\t\t? async () => {\n\t\t\t\t\tif (!credentialBindingMatchesTarget(credentialBinding, resolvedTarget)) {\n\t\t\t\t\t\tthrow new ProviderDoctorAuthMaterializationError();\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn await boundHeaderFactory();\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tthrow new ProviderDoctorAuthMaterializationError();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t: undefined;\n\tconst createHeaders: ProviderDoctorHeaderFactory = endpointBoundHeaders\n\t\t? endpointBoundHeaders\n\t\t: level === 2 && resolveAuthHeaders\n\t\t\t? async () => {\n\t\t\t\t\tif (credentialBinding && !credentialBindingMatchesTarget(credentialBinding, resolvedTarget)) {\n\t\t\t\t\t\tthrow new ProviderDoctorAuthMaterializationError();\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn await resolveAuthHeaders(resolvedTarget);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tthrow new ProviderDoctorAuthMaterializationError();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t: (resolvedTarget.auth.createHeaders ?? (() => new Headers()));\n\tconst controller = createComposedController(options.signal, timeoutMs);\n\ttry {\n\t\tconst root = await runProbe(\n\t\t\t\"root-endpoint\",\n\t\t\t\"root\",\n\t\t\tmakeRootProbeUrl(validation.url!, resolvedTarget.origin),\n\t\t\ttransport,\n\t\t\taddressPolicy,\n\t\t\tcreateHeaders,\n\t\t\tcontroller,\n\t\t);\n\t\tvalidation.checks.push(root.check);\n\t\tif (root.failure) {\n\t\t\tvalidation.checks.push({\n\t\t\t\tname: \"models-endpoint\",\n\t\t\t\tstatus: \"skipped\",\n\t\t\t\tcode: \"ok\",\n\t\t\t\tmessage: \"Capability probe skipped after root failure\",\n\t\t\t});\n\t\t\treturn publicResult(resolvedTarget, level, validation.checks, validation.url, root.failure);\n\t\t}\n\n\t\tconst models = await runProbe(\n\t\t\t\"models-endpoint\",\n\t\t\t\"models\",\n\t\t\tappendPath(validation.url!, \"models\"),\n\t\t\ttransport,\n\t\t\taddressPolicy,\n\t\t\tcreateHeaders,\n\t\t\tcontroller,\n\t\t);\n\t\tvalidation.checks.push(models.check);\n\t\tif (level !== 2 || models.failure) {\n\t\t\treturn publicResult(resolvedTarget, level, validation.checks, validation.url, models.failure);\n\t\t}\n\n\t\tconst spec = modelProbeSpec(resolvedTarget.endpoint.api ?? \"\", probeModelId as string);\n\t\tif (!spec) {\n\t\t\tconst failure: ProbeFailure = {\n\t\t\t\tcategory: \"config\",\n\t\t\t\tcode: \"api-unsupported\",\n\t\t\t\tmessage: \"Model probe supports only OpenAI-compatible APIs\",\n\t\t\t};\n\t\t\tvalidation.checks.push({ name: \"model-probe\", status: \"fail\", ...failure });\n\t\t\treturn publicResult(resolvedTarget, level, validation.checks, validation.url, failure);\n\t\t}\n\t\tconst probe = await runProbe(\n\t\t\t\"model-probe\",\n\t\t\t\"model-probe\",\n\t\t\tappendPath(validation.url!, spec.path),\n\t\t\ttransport,\n\t\t\taddressPolicy,\n\t\t\tcreateHeaders,\n\t\t\tcontroller,\n\t\t\tspec.payload,\n\t\t);\n\t\tconst dispatched = probe.failure?.code !== \"auth-materialization-failed\";\n\t\tif (dispatched) {\n\t\t\tprobe.check.message = `${probe.check.message}; minimal-token generative probe (may incur provider costs)`;\n\t\t}\n\t\tvalidation.checks.push(probe.check);\n\t\treturn publicResult(resolvedTarget, level, validation.checks, validation.url, probe.failure, dispatched);\n\t} finally {\n\t\tcontroller.dispose();\n\t}\n}\n\nexport async function diagnoseProvider(\n\tproviderId: string,\n\toptions: ProviderDoctorOptions = {},\n): Promise<ProviderDoctorResult> {\n\tconst requestedLevel = options.level ?? 0;\n\tif (requestedLevel !== 0 && requestedLevel !== 1 && requestedLevel !== 2) {\n\t\treturn unresolvedFailure(providerId, 0, \"unsupported-level\", \"Provider doctor level is unsupported\");\n\t}\n\tconst resolved = resolveProviderTarget(providerId, options);\n\tif (resolved.failure) return resolved.failure;\n\treturn diagnoseResolvedProvider(resolved.target!, options);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"doctor-provider.d.ts","sourceRoot":"","sources":["../../src/commands/doctor-provider.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { BlockList, isIP } from \"node:net\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { type Api, findEnvKeys, getModels, getProviders, type KnownProvider, type Model } from \"omk-ai\";\nimport { getAgentDir as getConfiguredAgentDir } from \"../config.ts\";\nimport { isConfigValueConfigured, resolveConfigValueOrThrow } from \"../core/resolve-config-value.ts\";\nimport { stripJsonComments } from \"../utils/json.ts\";\n\nexport type ProviderOrigin = \"native\" | \"custom-openai-compatible\" | \"local-proxy\" | \"unknown\";\nexport type ProviderDoctorLevel = 0 | 1 | 2;\nexport type ProviderDoctorCheckStatus = \"ok\" | \"fail\" | \"skipped\" | \"unsupported\";\nexport type DoctorErrorCategory = \"network\" | \"auth\" | \"model\" | \"config\" | \"server\" | \"unknown\";\n\n/** Exact endpoint-probe category model from the v0.90.9 design (§10.4). */\nexport type EndpointProbeCategory = \"ok\" | \"network\" | \"auth\" | \"unsupported-endpoint\" | \"server\";\n\nexport type EndpointProbeResult = {\n\treachable: boolean;\n\tauthenticated?: boolean;\n\tmodelsSupported?: boolean;\n\tstatus?: number;\n\tcategory: EndpointProbeCategory;\n};\n\nexport type ProviderDoctorCode =\n\t| \"ok\"\n\t| \"provider-not-found\"\n\t| \"models-config-invalid\"\n\t| \"auth-config-invalid\"\n\t| \"kimi-config-invalid\"\n\t| \"unsupported-level\"\n\t| \"model-provider-mismatch\"\n\t| \"api-missing\"\n\t| \"api-unsupported\"\n\t| \"base-url-missing\"\n\t| \"base-url-invalid\"\n\t| \"url-scheme-unsupported\"\n\t| \"origin-unsupported\"\n\t| \"address-policy-blocked\"\n\t| \"auth-missing\"\n\t| \"auth-materialization-failed\"\n\t| \"transport-required\"\n\t| \"address-pinning-required\"\n\t| \"redirect-blocked\"\n\t| \"authentication-failed\"\n\t| \"endpoint-unsupported\"\n\t| \"request-aborted\"\n\t| \"request-timeout\"\n\t| \"network-failure\"\n\t| \"server-error\"\n\t| \"probe-model-required\"\n\t| \"probe-model-conflict\"\n\t| \"unexpected-response\";\n\nexport interface ProviderDoctorEndpoint {\n\tbaseUrl?: string;\n\tapi?: string;\n\tmodelIds?: readonly string[];\n\tmodelId?: string;\n}\n\nexport type ProviderDoctorHeaderFactory = () => Headers | Promise<Headers>;\n\nexport type ProviderDoctorAuthHeaderResolver = (target: ResolvedProviderTarget) => Headers | Promise<Headers>;\n\n/** Fixed-message marker used to classify auth resolution without exposing its cause. */\nexport class ProviderDoctorAuthMaterializationError extends Error {\n\tconstructor() {\n\t\tsuper(\"provider doctor authentication could not be materialized\");\n\t\tthis.name = \"ProviderDoctorAuthMaterializationError\";\n\t}\n}\n\nexport interface ProviderDoctorCredentialBinding {\n\torigin: ProviderOrigin;\n\tsource: string;\n\tbaseUrl: string;\n\tapi: string;\n\tmodelId: string;\n}\n\nexport interface ProviderDoctorAuth {\n\tpresent: boolean;\n\tsource?: string;\n\t/** Materialized by an injected transport at request time only. */\n\tcreateHeaders?: ProviderDoctorHeaderFactory;\n\t/** Exact endpoint provenance that authorizes createHeaders. */\n\tbinding?: ProviderDoctorCredentialBinding;\n}\n\nexport interface ResolvedProviderTarget {\n\tproviderId: string;\n\torigin: ProviderOrigin;\n\tsource: string;\n\tendpoint: ProviderDoctorEndpoint;\n\tauth: ProviderDoctorAuth;\n}\n\n/** Compatibility name for callers using the design-document terminology. */\nexport type ResolvedProviderConfig = ResolvedProviderTarget;\n\nexport type ProviderDoctorAddressPolicy =\n\t| { kind: \"public\"; requireAddressPinning: true }\n\t| { kind: \"loopback-only\"; requireAddressPinning: false };\n\nexport interface ProviderDoctorTransportRequest {\n\turl: URL;\n\tmethod: \"GET\" | \"POST\";\n\tredirect: \"manual\";\n\tsignal: AbortSignal;\n\taddressPolicy: ProviderDoctorAddressPolicy;\n\tcreateHeaders: ProviderDoctorHeaderFactory;\n\t/** Bounded JSON payload; present only for opt-in Level-2 POST model probes. */\n\tbody?: string;\n\tcontentType?: \"application/json\";\n}\n\nexport interface ProviderDoctorTransportResponse {\n\tstatus: number;\n}\n\nexport interface ProviderDoctorTransport {\n\t/** Public requests are rejected unless the transport enforces DNS/address pinning. */\n\tpinsResolvedAddress?: boolean;\n\trequest(request: ProviderDoctorTransportRequest): Promise<ProviderDoctorTransportResponse>;\n}\n\nexport interface ProviderDoctorDependencies {\n\ttransport?: ProviderDoctorTransport;\n\t/** Level-2 only. Called by the transport immediately before each request is dispatched. */\n\tresolveAuthHeaders?: ProviderDoctorAuthHeaderResolver;\n}\n\nexport interface ProviderDoctorOptions extends ProviderDoctorDependencies {\n\tlevel?: ProviderDoctorLevel;\n\tmodelId?: string;\n\t/** Level-2 opt-in only: the model probed with one minimal-token generative request. */\n\tprobeModelId?: string;\n\ttimeoutMs?: number;\n\tsignal?: AbortSignal;\n\tagentDir?: string;\n\t/** Path to the Kimi CLI config TOML consulted for the kimi-coding provider. Defaults to ~/.kimi/config.toml. */\n\tkimiConfigPath?: string;\n}\n\nexport interface ProviderDoctorCheck {\n\tname: string;\n\tstatus: ProviderDoctorCheckStatus;\n\tcode: ProviderDoctorCode;\n\tcategory?: DoctorErrorCategory;\n\tmessage?: string;\n\t/** Exact-category endpoint classification for network probe checks. */\n\tprobe?: EndpointProbeResult;\n}\n\nexport interface ProviderDoctorResult {\n\tprovider: string;\n\tstatus: \"ok\" | \"fail\";\n\tlevel: ProviderDoctorLevel;\n\torigin: ProviderOrigin;\n\tsource: string;\n\t/** Sanitized URL: userinfo, query, and fragment are always removed. */\n\ttargetUrl?: string;\n\t/** Backwards-compatible alias of targetUrl. */\n\tbaseUrl?: string;\n\tapi?: string;\n\tmodelId?: string;\n\tauthPresent: boolean;\n\t/** True only when an opt-in Level-2 model probe was dispatched; the request may incur provider costs. */\n\tcostWarning?: boolean;\n\tchecks: ProviderDoctorCheck[];\n\terror?: {\n\t\tcategory: DoctorErrorCategory;\n\t\tcode: ProviderDoctorCode;\n\t\tmessage: string;\n\t};\n}\n\ninterface ModelEndpointConfig {\n\tid: string;\n\tbaseUrl?: string;\n\tapi?: string;\n}\n\ninterface ProviderConfig {\n\tbaseUrl?: string;\n\tapiKey?: string;\n\tapi?: string;\n\theaders?: Record<string, string>;\n\tmodels?: ModelEndpointConfig[];\n}\n\ninterface ModelsConfig {\n\tproviders: Record<string, ProviderConfig>;\n}\n\ninterface AuthConfigEntry {\n\ttype: \"api_key\" | \"oauth\";\n}\n\ntype AuthConfig = Record<string, AuthConfigEntry>;\n\ntype LoadedConfig<T> = { kind: \"missing\" } | { kind: \"invalid\" } | { kind: \"ok\"; value: T };\n\ntype ProbeFailure = {\n\tcategory: DoctorErrorCategory;\n\tcode: ProviderDoctorCode;\n\tmessage: string;\n};\n\ninterface ProbeOutcome {\n\tcheck: ProviderDoctorCheck;\n\tfailure?: ProbeFailure;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst KNOWN_APIS = new Set<string>([\n\t\"openai-completions\",\n\t\"mistral-conversations\",\n\t\"openai-responses\",\n\t\"azure-openai-responses\",\n\t\"openai-codex-responses\",\n\t\"anthropic-messages\",\n\t\"bedrock-converse-stream\",\n\t\"google-generative-ai\",\n\t\"google-vertex\",\n]);\nconst OPENAI_COMPATIBLE_APIS = new Set<string>([\"openai-completions\", \"openai-responses\"]);\nconst ABORTED = Symbol(\"provider-doctor-aborted\");\n\n// Family-specific lists: node BlockList canonicalizes IPv4 checks to the v4-mapped v6 form,\n// so a combined list containing ::ffff:0:0/96 would block every IPv4 address.\nconst blockedPublicV4 = new BlockList();\nfor (const [address, prefix] of [\n\t[\"0.0.0.0\", 8],\n\t[\"10.0.0.0\", 8],\n\t[\"100.64.0.0\", 10],\n\t[\"127.0.0.0\", 8],\n\t[\"169.254.0.0\", 16],\n\t[\"172.16.0.0\", 12],\n\t[\"192.168.0.0\", 16],\n\t[\"224.0.0.0\", 4],\n\t[\"240.0.0.0\", 4],\n] as const) {\n\tblockedPublicV4.addSubnet(address, prefix, \"ipv4\");\n}\n// ::ffff:0:0/96 stays here on purpose: a v4-mapped IPv6 literal or DNS answer is anomalous\n// for a public origin and is rejected wholesale (fail closed), covering hex-form mappings too.\nconst blockedPublicV6 = new BlockList();\nfor (const [address, prefix] of [\n\t[\"::\", 128],\n\t[\"::1\", 128],\n\t[\"::ffff:0:0\", 96],\n\t[\"fc00::\", 7],\n\t[\"fe80::\", 10],\n\t[\"ff00::\", 8],\n] as const) {\n\tblockedPublicV6.addSubnet(address, prefix, \"ipv6\");\n}\n\nconst loopbackAddresses = new BlockList();\nloopbackAddresses.addSubnet(\"127.0.0.0\", 8, \"ipv4\");\nloopbackAddresses.addAddress(\"::1\", \"ipv6\");\nloopbackAddresses.addSubnet(\"::ffff:127.0.0.0\", 104, \"ipv6\");\n\n/** True when the literal IP address is a loopback address. Non-IP input is never loopback. */\nexport function isLoopbackAddress(address: string): boolean {\n\tconst family = isIP(address);\n\tif (family === 4) return loopbackAddresses.check(address, \"ipv4\");\n\tif (family === 6) return loopbackAddresses.check(address, \"ipv6\");\n\treturn false;\n}\n\n/** True when the literal IP address must not be dialed for a public origin. Non-IP input fails closed. */\nexport function isBlockedPublicAddress(address: string): boolean {\n\tconst family = isIP(address);\n\tif (family === 4) return blockedPublicV4.check(address, \"ipv4\");\n\tif (family === 6) return blockedPublicV6.check(address, \"ipv6\");\n\treturn true;\n}\n\nexport function getAgentDir(): string {\n\treturn getConfiguredAgentDir();\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isOptionalNonemptyString(value: unknown): value is string | undefined {\n\treturn value === undefined || (typeof value === \"string\" && value.length > 0);\n}\n\nfunction parseModelsConfig(value: unknown): ModelsConfig | undefined {\n\tif (!isRecord(value) || !isRecord(value.providers)) return undefined;\n\tconst providers: Record<string, ProviderConfig> = {};\n\n\tfor (const [providerId, rawProvider] of Object.entries(value.providers)) {\n\t\tif (!isRecord(rawProvider)) return undefined;\n\t\tif (\n\t\t\t!isOptionalNonemptyString(rawProvider.baseUrl) ||\n\t\t\t!isOptionalNonemptyString(rawProvider.apiKey) ||\n\t\t\t!isOptionalNonemptyString(rawProvider.api)\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tlet headers: Record<string, string> | undefined;\n\t\tif (rawProvider.headers !== undefined) {\n\t\t\tif (!isRecord(rawProvider.headers)) return undefined;\n\t\t\theaders = {};\n\t\t\tfor (const [name, headerValue] of Object.entries(rawProvider.headers)) {\n\t\t\t\tif (typeof headerValue !== \"string\") return undefined;\n\t\t\t\theaders[name] = headerValue;\n\t\t\t}\n\t\t}\n\n\t\tlet models: ModelEndpointConfig[] | undefined;\n\t\tif (rawProvider.models !== undefined) {\n\t\t\tif (!Array.isArray(rawProvider.models)) return undefined;\n\t\t\tmodels = [];\n\t\t\tfor (const rawModel of rawProvider.models) {\n\t\t\t\tif (\n\t\t\t\t\t!isRecord(rawModel) ||\n\t\t\t\t\ttypeof rawModel.id !== \"string\" ||\n\t\t\t\t\trawModel.id.length === 0 ||\n\t\t\t\t\t!isOptionalNonemptyString(rawModel.baseUrl) ||\n\t\t\t\t\t!isOptionalNonemptyString(rawModel.api)\n\t\t\t\t) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tmodels.push({ id: rawModel.id, baseUrl: rawModel.baseUrl, api: rawModel.api });\n\t\t\t}\n\t\t}\n\n\t\tproviders[providerId] = {\n\t\t\tbaseUrl: rawProvider.baseUrl,\n\t\t\tapiKey: rawProvider.apiKey,\n\t\t\tapi: rawProvider.api,\n\t\t\theaders,\n\t\t\tmodels,\n\t\t};\n\t}\n\n\treturn { providers };\n}\n\nfunction parseAuthConfig(value: unknown): AuthConfig | undefined {\n\tif (!isRecord(value)) return undefined;\n\tconst auth: AuthConfig = {};\n\tfor (const [providerId, rawEntry] of Object.entries(value)) {\n\t\tif (!isRecord(rawEntry) || (rawEntry.type !== \"api_key\" && rawEntry.type !== \"oauth\")) return undefined;\n\t\tif (rawEntry.type === \"api_key\" && (typeof rawEntry.key !== \"string\" || rawEntry.key.length === 0)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tauth[providerId] = { type: rawEntry.type };\n\t}\n\treturn auth;\n}\n\nconst KIMI_PROVIDER_ID = \"kimi-coding\";\nconst TOML_BASIC_ESCAPES: Record<string, string> = {\n\t'\"': '\"',\n\t\"\\\\\": \"\\\\\",\n\tb: \"\\b\",\n\tf: \"\\f\",\n\tn: \"\\n\",\n\tr: \"\\r\",\n\tt: \"\\t\",\n};\n\ntype TomlQuote = \"basic\" | \"literal\" | \"multiline-basic\" | \"multiline-literal\";\n\ninterface TomlScanState {\n\tquote?: TomlQuote;\n\tcontainers: (\"[\" | \"{\")[];\n}\n\ninterface KimiTomlProviderEntry {\n\tname: string;\n\ttype?: string;\n\tbaseUrl?: string;\n\tmodelName?: string;\n\tapiKey?: string;\n}\n\ninterface KimiConfigValues {\n\tbaseUrl?: string;\n\tmodelName?: string;\n\tproviders: KimiTomlProviderEntry[];\n}\n\ntype KimiConfigLoad =\n\t| { kind: \"missing\" }\n\t| { kind: \"ok\"; value: KimiConfigValues }\n\t| { kind: \"invalid\"; line: number; reason: string };\n\nfunction findBasicStringEnd(text: string, start: number, multiline: boolean): number {\n\tfor (let index = start; index < text.length; index++) {\n\t\tif (text[index] === \"\\\\\") {\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (multiline ? text.startsWith('\"\"\"', index) : text[index] === '\"') return index;\n\t}\n\treturn -1;\n}\n\nfunction decodeTomlBasicString(body: string, multiline: boolean): { value: string } | { error: string } {\n\tlet value = \"\";\n\tfor (let index = 0; index < body.length; index++) {\n\t\tconst char = body[index];\n\t\tif (char !== \"\\\\\") {\n\t\t\tconst code = char.codePointAt(0) ?? 0;\n\t\t\tif (code < 0x20 && char !== \"\\t\" && !(multiline && (char === \"\\n\" || char === \"\\r\"))) {\n\t\t\t\treturn { error: \"invalid control character in string value\" };\n\t\t\t}\n\t\t\tvalue += char;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst escapeChar = body[index + 1];\n\t\tif (escapeChar === undefined) return { error: \"unterminated escape sequence\" };\n\t\tif (multiline && (escapeChar === \"\\n\" || escapeChar === \"\\r\")) {\n\t\t\tindex++;\n\t\t\twhile (index + 1 < body.length && /\\s/.test(body[index + 1])) index++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (escapeChar === \"u\" || escapeChar === \"U\") {\n\t\t\tconst width = escapeChar === \"u\" ? 4 : 8;\n\t\t\tconst hex = body.slice(index + 2, index + 2 + width);\n\t\t\tconst codePoint = /^[0-9a-fA-F]+$/.test(hex) && hex.length === width ? Number.parseInt(hex, 16) : -1;\n\t\t\tif (codePoint < 0 || codePoint > 0x10ffff || (codePoint >= 0xd800 && codePoint <= 0xdfff)) {\n\t\t\t\treturn { error: \"invalid unicode escape\" };\n\t\t\t}\n\t\t\tvalue += String.fromCodePoint(codePoint);\n\t\t\tindex += 1 + width;\n\t\t\tcontinue;\n\t\t}\n\t\tconst mapped = TOML_BASIC_ESCAPES[escapeChar];\n\t\tif (mapped === undefined) return { error: \"invalid escape sequence\" };\n\t\tvalue += mapped;\n\t\tindex++;\n\t}\n\treturn { value };\n}\n\n/** Parses one TOML string value. Errors are fixed and never include raw TOML content. */\nfunction parseTomlStringValue(raw: string, requireNonempty = true): { value: string } | { error: string } {\n\tconst text = raw.trim();\n\tconst multilineBasic = text.startsWith('\"\"\"');\n\tconst multilineLiteral = text.startsWith(\"'''\");\n\tconst basic = !multilineBasic && text.startsWith('\"');\n\tconst literal = !multilineLiteral && text.startsWith(\"'\");\n\tif (!multilineBasic && !multilineLiteral && !basic && !literal) {\n\t\treturn { error: \"value must be a quoted string\" };\n\t}\n\n\tconst delimiterWidth = multilineBasic || multilineLiteral ? 3 : 1;\n\tconst end =\n\t\tmultilineBasic || basic\n\t\t\t? findBasicStringEnd(text, delimiterWidth, multilineBasic)\n\t\t\t: text.indexOf(multilineLiteral ? \"'''\" : \"'\", delimiterWidth);\n\tif (end < 0) {\n\t\treturn {\n\t\t\terror:\n\t\t\t\tmultilineBasic || multilineLiteral ? \"unterminated multiline string value\" : \"unterminated string value\",\n\t\t};\n\t}\n\tif (text.slice(end + delimiterWidth).trim().length > 0) {\n\t\treturn { error: \"unexpected content after string value\" };\n\t}\n\n\tlet body = text.slice(delimiterWidth, end);\n\tif ((multilineBasic || multilineLiteral) && body.startsWith(\"\\n\")) body = body.slice(1);\n\tconst parsed =\n\t\tmultilineBasic || basic\n\t\t\t? decodeTomlBasicString(body, multilineBasic)\n\t\t\t: /[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f\\u007f]/.test(body)\n\t\t\t\t? { error: \"invalid control character in string value\" as const }\n\t\t\t\t: { value: body };\n\tif (\"error\" in parsed) return parsed;\n\treturn requireNonempty && parsed.value.length === 0 ? { error: \"empty string value\" } : parsed;\n}\n\nfunction scanTomlLine(text: string, state: TomlScanState): { content: string; error?: string } {\n\tlet content = \"\";\n\tfor (let index = 0; index < text.length; index++) {\n\t\tconst char = text[index];\n\t\tif (state.quote === \"basic\") {\n\t\t\tcontent += char;\n\t\t\tif (char === \"\\\\\" && index + 1 < text.length) content += text[++index];\n\t\t\telse if (char === '\"') state.quote = undefined;\n\t\t\tcontinue;\n\t\t}\n\t\tif (state.quote === \"literal\") {\n\t\t\tcontent += char;\n\t\t\tif (char === \"'\") state.quote = undefined;\n\t\t\tcontinue;\n\t\t}\n\t\tif (state.quote === \"multiline-basic\") {\n\t\t\tif (text.startsWith('\"\"\"', index)) {\n\t\t\t\tcontent += '\"\"\"';\n\t\t\t\tindex += 2;\n\t\t\t\tstate.quote = undefined;\n\t\t\t} else {\n\t\t\t\tcontent += char;\n\t\t\t\tif (char === \"\\\\\" && index + 1 < text.length) content += text[++index];\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (state.quote === \"multiline-literal\") {\n\t\t\tif (text.startsWith(\"'''\", index)) {\n\t\t\t\tcontent += \"'''\";\n\t\t\t\tindex += 2;\n\t\t\t\tstate.quote = undefined;\n\t\t\t} else {\n\t\t\t\tcontent += char;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (char === \"#\") break;\n\t\tif (text.startsWith('\"\"\"', index)) {\n\t\t\tcontent += '\"\"\"';\n\t\t\tindex += 2;\n\t\t\tstate.quote = \"multiline-basic\";\n\t\t\tcontinue;\n\t\t}\n\t\tif (text.startsWith(\"'''\", index)) {\n\t\t\tcontent += \"'''\";\n\t\t\tindex += 2;\n\t\t\tstate.quote = \"multiline-literal\";\n\t\t\tcontinue;\n\t\t}\n\t\tcontent += char;\n\t\tif (char === '\"') state.quote = \"basic\";\n\t\telse if (char === \"'\") state.quote = \"literal\";\n\t\telse if (char === \"[\" || char === \"{\") state.containers.push(char);\n\t\telse if (char === \"]\" || char === \"}\") {\n\t\t\tconst expected = char === \"]\" ? \"[\" : \"{\";\n\t\t\tif (state.containers.pop() !== expected) return { content, error: \"mismatched value delimiter\" };\n\t\t}\n\t}\n\tif (state.quote === \"basic\" || state.quote === \"literal\") {\n\t\treturn { content, error: \"unterminated string value\" };\n\t}\n\treturn { content };\n}\n\nfunction consumeTomlValue(\n\tlines: readonly string[],\n\tstartLine: number,\n\tinitial: string,\n): { value?: string; endLine: number; error?: string } {\n\tconst state: TomlScanState = { containers: [] };\n\tconst chunks: string[] = [];\n\tfor (let line = startLine; line < lines.length; line++) {\n\t\tconst scanned = scanTomlLine(line === startLine ? initial : lines[line], state);\n\t\tchunks.push(scanned.content);\n\t\tif (scanned.error) return { endLine: line, error: scanned.error };\n\t\tif (!state.quote && state.containers.length === 0) {\n\t\t\tconst value = chunks.join(\"\\n\").trim();\n\t\t\treturn value.length > 0\n\t\t\t\t? { value, endLine: line }\n\t\t\t\t: { endLine: startLine, error: \"assignment value is missing\" };\n\t\t}\n\t}\n\treturn {\n\t\tendLine: startLine,\n\t\terror: state.quote ? \"unterminated multiline string value\" : \"unterminated compound value\",\n\t};\n}\n\nfunction findTomlTopLevelCharacter(text: string, wanted: string): number {\n\tconst state: TomlScanState = { containers: [] };\n\tfor (let index = 0; index < text.length; index++) {\n\t\tconst char = text[index];\n\t\tif (state.quote === \"basic\" || state.quote === \"multiline-basic\") {\n\t\t\tif (char === \"\\\\\") index++;\n\t\t\telse if (state.quote === \"basic\" && char === '\"') state.quote = undefined;\n\t\t\telse if (state.quote === \"multiline-basic\" && text.startsWith('\"\"\"', index)) {\n\t\t\t\tstate.quote = undefined;\n\t\t\t\tindex += 2;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (state.quote === \"literal\" || state.quote === \"multiline-literal\") {\n\t\t\tconst delimiter = state.quote === \"literal\" ? \"'\" : \"'''\";\n\t\t\tif (text.startsWith(delimiter, index)) {\n\t\t\t\tstate.quote = undefined;\n\t\t\t\tindex += delimiter.length - 1;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (char === \"#\") return -1;\n\t\tif (text.startsWith('\"\"\"', index)) {\n\t\t\tstate.quote = \"multiline-basic\";\n\t\t\tindex += 2;\n\t\t} else if (text.startsWith(\"'''\", index)) {\n\t\t\tstate.quote = \"multiline-literal\";\n\t\t\tindex += 2;\n\t\t} else if (char === '\"') state.quote = \"basic\";\n\t\telse if (char === \"'\") state.quote = \"literal\";\n\t\telse if (char === \"[\" || char === \"{\") state.containers.push(char);\n\t\telse if (char === \"]\" || char === \"}\") state.containers.pop();\n\t\telse if (char === wanted && state.containers.length === 0) return index;\n\t}\n\treturn -1;\n}\n\nfunction parseTomlDottedKey(raw: string): string[] | undefined {\n\tconst components: string[] = [];\n\tlet index = 0;\n\twhile (index < raw.length) {\n\t\twhile (/\\s/.test(raw[index] ?? \"\")) index++;\n\t\tlet component: string | undefined;\n\t\tif (raw[index] === '\"') {\n\t\t\tconst end = findBasicStringEnd(raw, index + 1, false);\n\t\t\tif (end < 0) return undefined;\n\t\t\tconst parsed = parseTomlStringValue(raw.slice(index, end + 1), false);\n\t\t\tif (\"error\" in parsed) return undefined;\n\t\t\tcomponent = parsed.value;\n\t\t\tindex = end + 1;\n\t\t} else if (raw[index] === \"'\") {\n\t\t\tconst end = raw.indexOf(\"'\", index + 1);\n\t\t\tif (end < 0) return undefined;\n\t\t\tcomponent = raw.slice(index + 1, end);\n\t\t\tindex = end + 1;\n\t\t} else {\n\t\t\tconst match = /^[A-Za-z0-9_-]+/.exec(raw.slice(index));\n\t\t\tif (!match) return undefined;\n\t\t\tcomponent = match[0];\n\t\t\tindex += match[0].length;\n\t\t}\n\t\tcomponents.push(component);\n\t\twhile (/\\s/.test(raw[index] ?? \"\")) index++;\n\t\tif (index === raw.length) return components;\n\t\tif (raw[index] !== \".\") return undefined;\n\t\tindex++;\n\t\tif (index === raw.length) return undefined;\n\t}\n\treturn undefined;\n}\n\nfunction splitTomlTopLevel(raw: string): string[] {\n\tconst parts: string[] = [];\n\tlet start = 0;\n\tfor (let index = 0; index < raw.length; index++) {\n\t\tif (raw[index] !== \",\") continue;\n\t\tconst relative = findTomlTopLevelCharacter(raw.slice(start, index + 1), \",\");\n\t\tif (relative !== index - start) continue;\n\t\tparts.push(raw.slice(start, index).trim());\n\t\tstart = index + 1;\n\t}\n\tparts.push(raw.slice(start).trim());\n\treturn parts;\n}\n\nfunction isValidTomlScalar(raw: string): boolean {\n\treturn (\n\t\traw === \"true\" ||\n\t\traw === \"false\" ||\n\t\t/^[+-]?(?:0|[1-9](?:_?\\d)*)$/.test(raw) ||\n\t\t/^[+-]?0x[0-9A-Fa-f](?:_?[0-9A-Fa-f])*$/.test(raw) ||\n\t\t/^[+-]?0o[0-7](?:_?[0-7])*$/.test(raw) ||\n\t\t/^[+-]?0b[01](?:_?[01])*$/.test(raw) ||\n\t\t/^[+-]?(?:(?:0|[1-9](?:_?\\d)*)\\.(?:\\d(?:_?\\d)*)(?:[eE][+-]?\\d(?:_?\\d)*)?|(?:0|[1-9](?:_?\\d)*)[eE][+-]?\\d(?:_?\\d)*|inf|nan)$/.test(\n\t\t\traw,\n\t\t) ||\n\t\t/^\\d{4}-\\d{2}-\\d{2}(?:[Tt ]\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:[Zz]|[+-]\\d{2}:\\d{2})?)?$/.test(raw) ||\n\t\t/^\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?$/.test(raw)\n\t);\n}\n\nfunction isValidTomlValue(raw: string): boolean {\n\tconst text = raw.trim();\n\tif (text.startsWith('\"') || text.startsWith(\"'\")) return !(\"error\" in parseTomlStringValue(text, false));\n\tif (text.startsWith(\"[\") && text.endsWith(\"]\")) {\n\t\tconst parts = splitTomlTopLevel(text.slice(1, -1));\n\t\tif (parts.at(-1) === \"\") parts.pop();\n\t\treturn parts.every((part) => part.length > 0 && isValidTomlValue(part));\n\t}\n\tif (text.startsWith(\"{\") && text.endsWith(\"}\")) {\n\t\tconst body = text.slice(1, -1).trim();\n\t\tif (body.length === 0) return true;\n\t\treturn splitTomlTopLevel(body).every((part) => {\n\t\t\tconst equals = findTomlTopLevelCharacter(part, \"=\");\n\t\t\treturn equals > 0 && !!parseTomlDottedKey(part.slice(0, equals)) && isValidTomlValue(part.slice(equals + 1));\n\t\t});\n\t}\n\treturn isValidTomlScalar(text);\n}\n\nfunction parseTomlTable(line: string): { components?: string[]; array?: boolean; error?: string } {\n\tconst state: TomlScanState = { containers: [] };\n\tconst scanned = scanTomlLine(line, state);\n\tif (scanned.error || state.quote || state.containers.length > 0) return { error: \"malformed table header\" };\n\tconst text = scanned.content.trim();\n\tconst array = text.startsWith(\"[[\");\n\tif (array ? !text.endsWith(\"]]\") : !text.endsWith(\"]\")) return { error: \"malformed table header\" };\n\tconst inner = text.slice(array ? 2 : 1, array ? -2 : -1).trim();\n\tconst components = parseTomlDottedKey(inner);\n\treturn components && components.length > 0 ? { components, array } : { error: \"malformed table header\" };\n}\n\n/**\n * Strict Kimi TOML subset reader. It validates every table/assignment and generic TOML value,\n * but extracts only root `base_url`/`model_name` and `[providers.<name>]` string fields. Fixed\n * line-number errors ensure credentials and other file content are never echoed.\n */\nfunction loadKimiConfig(path: string): KimiConfigLoad {\n\tif (!existsSync(path)) return { kind: \"missing\" };\n\tlet content: string;\n\ttry {\n\t\tcontent = readFileSync(path, \"utf-8\");\n\t} catch {\n\t\treturn { kind: \"invalid\", line: 0, reason: \"file is unreadable\" };\n\t}\n\tconst value: KimiConfigValues = { providers: [] };\n\tconst lines = content.split(/\\r?\\n/);\n\tconst assignments = new Set<string>();\n\tlet table: KimiTomlProviderEntry | \"root\" | \"other\" = \"root\";\n\tlet scope = \"root\";\n\tfor (let index = 0; index < lines.length; index++) {\n\t\tconst line = lines[index].trim();\n\t\tif (line.length === 0 || line.startsWith(\"#\")) continue;\n\t\tif (line.startsWith(\"[\")) {\n\t\t\tconst parsed = parseTomlTable(line);\n\t\t\tif (parsed.error || !parsed.components) {\n\t\t\t\treturn { kind: \"invalid\", line: index + 1, reason: parsed.error ?? \"malformed table header\" };\n\t\t\t}\n\t\t\tconst [first, name] = parsed.components;\n\t\t\tif (first === \"providers\" && parsed.array) {\n\t\t\t\treturn { kind: \"invalid\", line: index + 1, reason: \"provider table cannot be an array\" };\n\t\t\t}\n\t\t\tif (first === \"providers\" && parsed.components.length === 2 && name !== undefined && name.length > 0) {\n\t\t\t\tconst entry: KimiTomlProviderEntry = { name };\n\t\t\t\tvalue.providers.push(entry);\n\t\t\t\ttable = entry;\n\t\t\t} else {\n\t\t\t\ttable = \"other\";\n\t\t\t}\n\t\t\tscope = `${parsed.array ? \"array\" : \"table\"}:${JSON.stringify(parsed.components)}`;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst assignmentLine = index;\n\t\tconst equals = findTomlTopLevelCharacter(line, \"=\");\n\t\tconst key = equals > 0 ? parseTomlDottedKey(line.slice(0, equals)) : undefined;\n\t\tif (!key || key.length === 0) return { kind: \"invalid\", line: index + 1, reason: \"invalid assignment\" };\n\t\tconst consumed = consumeTomlValue(lines, index, line.slice(equals + 1));\n\t\tif (consumed.error || consumed.value === undefined) {\n\t\t\treturn { kind: \"invalid\", line: assignmentLine + 1, reason: consumed.error ?? \"invalid assignment value\" };\n\t\t}\n\t\tindex = consumed.endLine;\n\t\tif (!isValidTomlValue(consumed.value)) {\n\t\t\treturn { kind: \"invalid\", line: assignmentLine + 1, reason: \"invalid assignment value\" };\n\t\t}\n\t\tconst assignmentId = `${scope}:${JSON.stringify(key)}`;\n\t\tif (assignments.has(assignmentId)) {\n\t\t\treturn { kind: \"invalid\", line: assignmentLine + 1, reason: \"duplicate assignment\" };\n\t\t}\n\t\tassignments.add(assignmentId);\n\t\tif (key.length !== 1 || table === \"other\") continue;\n\n\t\tconst keyName = key[0];\n\t\tconst relevant =\n\t\t\ttable === \"root\"\n\t\t\t\t? keyName === \"base_url\" || keyName === \"model_name\"\n\t\t\t\t: keyName === \"base_url\" ||\n\t\t\t\t\tkeyName === \"model_name\" ||\n\t\t\t\t\tkeyName === \"model\" ||\n\t\t\t\t\tkeyName === \"type\" ||\n\t\t\t\t\tkeyName === \"api_key\";\n\t\tif (!relevant) continue;\n\t\tconst parsedValue = parseTomlStringValue(consumed.value);\n\t\tif (\"error\" in parsedValue) return { kind: \"invalid\", line: assignmentLine + 1, reason: parsedValue.error };\n\t\tif (table === \"root\") {\n\t\t\tif (keyName === \"base_url\") value.baseUrl = parsedValue.value;\n\t\t\telse value.modelName = parsedValue.value;\n\t\t} else {\n\t\t\tif (keyName === \"base_url\") table.baseUrl = parsedValue.value;\n\t\t\telse if (keyName === \"type\") table.type = parsedValue.value;\n\t\t\telse if (keyName === \"api_key\") table.apiKey = parsedValue.value;\n\t\t\telse table.modelName = parsedValue.value;\n\t\t}\n\t}\n\treturn { kind: \"ok\", value };\n}\n\nfunction nonemptyEnv(name: string): string | undefined {\n\tconst value = process.env[name];\n\treturn value && value.length > 0 ? value : undefined;\n}\n\ninterface KimiResolution {\n\tbaseUrl?: string;\n\tmodelName?: string;\n\tbaseUrlSource?: \"kimi-environment\" | \"kimi-config-toml\";\n\t/** First `[providers.<name>]` entry with `type = \"openai_legacy\"` and a base URL (§10.2). */\n\tcustomProvider?: { name: string; baseUrl: string; modelName?: string; apiKey?: string };\n\tfailure?: { line: number; reason: string };\n}\n\nfunction resolveKimiConfig(kimiConfigPath: string | undefined): KimiResolution {\n\tconst path = kimiConfigPath ?? join(homedir(), \".kimi\", \"config.toml\");\n\tconst loaded = loadKimiConfig(path);\n\tif (loaded.kind === \"invalid\") return { failure: { line: loaded.line, reason: loaded.reason } };\n\tconst fileValues: KimiConfigValues = loaded.kind === \"ok\" ? loaded.value : { providers: [] };\n\tconst envBaseUrl = nonemptyEnv(\"KIMI_BASE_URL\");\n\tconst envModelName = nonemptyEnv(\"KIMI_MODEL_NAME\");\n\tconst custom = fileValues.providers.find((entry) => entry.type === \"openai_legacy\" && entry.baseUrl);\n\treturn {\n\t\tbaseUrl: envBaseUrl ?? fileValues.baseUrl,\n\t\tmodelName: envModelName ?? fileValues.modelName,\n\t\tbaseUrlSource: envBaseUrl ? \"kimi-environment\" : fileValues.baseUrl ? \"kimi-config-toml\" : undefined,\n\t\tcustomProvider: custom\n\t\t\t? { name: custom.name, baseUrl: custom.baseUrl as string, modelName: custom.modelName, apiKey: custom.apiKey }\n\t\t\t: undefined,\n\t};\n}\n\nfunction loadConfig<T>(path: string, parse: (value: unknown) => T | undefined, allowComments = false): LoadedConfig<T> {\n\tif (!existsSync(path)) return { kind: \"missing\" };\n\ttry {\n\t\tconst content = readFileSync(path, \"utf-8\");\n\t\tconst parsed = JSON.parse(allowComments ? stripJsonComments(content) : content) as unknown;\n\t\tconst value = parse(parsed);\n\t\treturn value ? { kind: \"ok\", value } : { kind: \"invalid\" };\n\t} catch {\n\t\treturn { kind: \"invalid\" };\n\t}\n}\n\nexport function normalizedUrlHostname(url: URL): string {\n\treturn normalizedHostname(url);\n}\n\nfunction normalizedHostname(url: URL): string {\n\tconst hostname = url.hostname.toLowerCase();\n\tconst unwrapped = hostname.startsWith(\"[\") && hostname.endsWith(\"]\") ? hostname.slice(1, -1) : hostname;\n\treturn unwrapped.endsWith(\".\") ? unwrapped.slice(0, -1) : unwrapped;\n}\n\nfunction isLiteralLoopback(hostname: string): boolean {\n\tif (hostname === \"localhost\" || hostname === \"::1\") return true;\n\tif (isIP(hostname) !== 4) return false;\n\treturn Number(hostname.split(\".\")[0]) === 127;\n}\n\nfunction isBlockedPublicHost(hostname: string): boolean {\n\tif (hostname === \"localhost\" || hostname.endsWith(\".localhost\")) return true;\n\tif (isIP(hostname) === 0) return false;\n\treturn isBlockedPublicAddress(hostname);\n}\n\nfunction sanitizeUrl(rawUrl: string): URL | undefined {\n\ttry {\n\t\tconst url = new URL(rawUrl);\n\t\turl.username = \"\";\n\t\turl.password = \"\";\n\t\turl.search = \"\";\n\t\turl.hash = \"\";\n\t\treturn url;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function canonicalProviderEndpointUrl(rawUrl: string | undefined): string | undefined {\n\tif (!rawUrl) return undefined;\n\tconst url = sanitizeUrl(rawUrl);\n\tif (!url) return undefined;\n\tconst hostname = normalizedHostname(url);\n\tconst host = isIP(hostname) === 6 ? `[${hostname}]` : hostname;\n\tconst pathname = url.pathname.replace(/\\/+$/, \"\") || \"/\";\n\treturn `${url.protocol}//${host}${url.port ? `:${url.port}` : \"\"}${pathname}`;\n}\n\nfunction credentialBindingMatchesTarget(\n\tbinding: ProviderDoctorCredentialBinding,\n\ttarget: ResolvedProviderTarget,\n): boolean {\n\treturn (\n\t\tbinding.origin === target.origin &&\n\t\tbinding.source === target.source &&\n\t\tbinding.baseUrl === canonicalProviderEndpointUrl(target.endpoint.baseUrl) &&\n\t\tbinding.api === target.endpoint.api &&\n\t\tbinding.modelId === target.endpoint.modelId\n\t);\n}\n\nfunction appendPath(url: URL, segment: string): URL {\n\tconst next = URL.parse(url.href);\n\tif (!next) throw new Error(\"validated provider URL could not be cloned\");\n\tnext.pathname = `${next.pathname.replace(/\\/$/, \"\")}/${segment}`;\n\treturn next;\n}\n\nfunction makeRootProbeUrl(url: URL, origin: ProviderOrigin): URL {\n\tconst root = URL.parse(url.href);\n\tif (!root) throw new Error(\"validated provider URL could not be cloned\");\n\tif (origin !== \"local-proxy\") return root;\n\troot.pathname = \"/health\";\n\treturn root;\n}\n\nfunction publicResult(\n\ttarget: ResolvedProviderTarget,\n\tlevel: ProviderDoctorLevel,\n\tchecks: ProviderDoctorCheck[],\n\turl: URL | undefined,\n\tfailure?: ProbeFailure,\n\tcostWarning?: boolean,\n): ProviderDoctorResult {\n\tconst targetUrl = url?.href;\n\treturn {\n\t\tprovider: target.providerId,\n\t\tstatus: failure ? \"fail\" : \"ok\",\n\t\tlevel,\n\t\torigin: target.origin,\n\t\tsource: target.source,\n\t\ttargetUrl,\n\t\tbaseUrl: targetUrl,\n\t\tapi: target.endpoint.api,\n\t\tmodelId: target.endpoint.modelId,\n\t\tauthPresent: target.auth.present,\n\t\t...(costWarning ? { costWarning: true } : {}),\n\t\tchecks,\n\t\terror: failure,\n\t};\n}\n\nfunction unresolvedFailure(\n\tproviderId: string,\n\tlevel: ProviderDoctorLevel,\n\tcode: ProviderDoctorCode,\n\tmessage: string,\n): ProviderDoctorResult {\n\tconst check: ProviderDoctorCheck = { name: \"config\", status: \"fail\", category: \"config\", code, message };\n\treturn {\n\t\tprovider: providerId,\n\t\tstatus: \"fail\",\n\t\tlevel,\n\t\torigin: \"unknown\",\n\t\tsource: \"unresolved\",\n\t\tauthPresent: false,\n\t\tchecks: [check],\n\t\terror: { category: \"config\", code, message },\n\t};\n}\n\nfunction staticChecks(target: ResolvedProviderTarget): {\n\tchecks: ProviderDoctorCheck[];\n\turl?: URL;\n\tfailure?: ProbeFailure;\n} {\n\tconst checks: ProviderDoctorCheck[] = [\n\t\t{ name: \"config-present\", status: \"ok\", code: \"ok\", message: \"Provider configuration resolved\" },\n\t];\n\tif (target.origin === \"custom-openai-compatible\") {\n\t\tchecks.push({\n\t\t\tname: \"native-provider-checks\",\n\t\t\tstatus: \"skipped\",\n\t\t\tcode: \"ok\",\n\t\t\tmessage: \"Custom OpenAI-compatible endpoint; native-only provider checks skipped\",\n\t\t});\n\t}\n\tlet failure: ProbeFailure | undefined;\n\tconst recordFailure = (next: ProbeFailure): void => {\n\t\tfailure ??= next;\n\t};\n\n\tconst requestedModel = target.endpoint.modelId;\n\tconst modelIds = target.endpoint.modelIds ?? [];\n\tif (!requestedModel) {\n\t\tchecks.push({ name: \"model-provider-relation\", status: \"skipped\", code: \"ok\", message: \"No model selected\" });\n\t} else if (!modelIds.includes(requestedModel)) {\n\t\tconst next: ProbeFailure = {\n\t\t\tcategory: \"model\",\n\t\t\tcode: \"model-provider-mismatch\",\n\t\t\tmessage: \"Selected model does not belong to the provider\",\n\t\t};\n\t\tchecks.push({ name: \"model-provider-relation\", status: \"fail\", ...next });\n\t\trecordFailure(next);\n\t} else {\n\t\tchecks.push({ name: \"model-provider-relation\", status: \"ok\", code: \"ok\", message: \"Model belongs to provider\" });\n\t}\n\n\tconst api = target.endpoint.api;\n\tif (!api) {\n\t\tconst next: ProbeFailure = { category: \"config\", code: \"api-missing\", message: \"Provider API type is missing\" };\n\t\tchecks.push({ name: \"api\", status: \"fail\", ...next });\n\t\trecordFailure(next);\n\t} else if (\n\t\t!KNOWN_APIS.has(api) ||\n\t\t(target.origin === \"custom-openai-compatible\" && !OPENAI_COMPATIBLE_APIS.has(api))\n\t) {\n\t\tconst next: ProbeFailure = {\n\t\t\tcategory: \"config\",\n\t\t\tcode: \"api-unsupported\",\n\t\t\tmessage: \"Provider API type is unsupported\",\n\t\t};\n\t\tchecks.push({ name: \"api\", status: \"fail\", ...next });\n\t\trecordFailure(next);\n\t} else {\n\t\tchecks.push({ name: \"api\", status: \"ok\", code: \"ok\", message: \"Provider API type is supported\" });\n\t}\n\n\tlet url: URL | undefined;\n\tif (!target.endpoint.baseUrl) {\n\t\tconst next: ProbeFailure = {\n\t\t\tcategory: \"config\",\n\t\t\tcode: \"base-url-missing\",\n\t\t\tmessage: \"Provider base URL is missing\",\n\t\t};\n\t\tchecks.push({ name: \"base-url\", status: \"fail\", ...next });\n\t\trecordFailure(next);\n\t} else {\n\t\turl = sanitizeUrl(target.endpoint.baseUrl);\n\t\tif (!url) {\n\t\t\tconst next: ProbeFailure = {\n\t\t\t\tcategory: \"config\",\n\t\t\t\tcode: \"base-url-invalid\",\n\t\t\t\tmessage: \"Provider base URL is invalid\",\n\t\t\t};\n\t\t\tchecks.push({ name: \"base-url\", status: \"fail\", ...next });\n\t\t\trecordFailure(next);\n\t\t} else {\n\t\t\tconst allowedScheme =\n\t\t\t\ttarget.origin === \"local-proxy\"\n\t\t\t\t\t? url.protocol === \"http:\" || url.protocol === \"https:\"\n\t\t\t\t\t: url.protocol === \"https:\";\n\t\t\tif (!allowedScheme) {\n\t\t\t\tconst next: ProbeFailure = {\n\t\t\t\t\tcategory: \"config\",\n\t\t\t\t\tcode: \"url-scheme-unsupported\",\n\t\t\t\t\tmessage: \"Provider URL scheme is not allowed\",\n\t\t\t\t};\n\t\t\t\tchecks.push({ name: \"base-url\", status: \"fail\", ...next });\n\t\t\t\trecordFailure(next);\n\t\t\t} else {\n\t\t\t\tchecks.push({ name: \"base-url\", status: \"ok\", code: \"ok\", message: \"Provider base URL is valid\" });\n\t\t\t}\n\t\t}\n\t}\n\n\tif (target.origin === \"unknown\") {\n\t\tconst next: ProbeFailure = {\n\t\t\tcategory: \"config\",\n\t\t\tcode: \"origin-unsupported\",\n\t\t\tmessage: \"Provider origin is unsupported\",\n\t\t};\n\t\tchecks.push({ name: \"origin-policy\", status: \"fail\", ...next });\n\t\trecordFailure(next);\n\t} else if (url) {\n\t\tconst hostname = normalizedHostname(url);\n\t\tconst blocked = target.origin === \"local-proxy\" ? !isLiteralLoopback(hostname) : isBlockedPublicHost(hostname);\n\t\tif (blocked) {\n\t\t\tconst next: ProbeFailure = {\n\t\t\t\tcategory: \"config\",\n\t\t\t\tcode: \"address-policy-blocked\",\n\t\t\t\tmessage: \"Provider address is blocked by origin policy\",\n\t\t\t};\n\t\t\tchecks.push({ name: \"origin-policy\", status: \"fail\", ...next });\n\t\t\trecordFailure(next);\n\t\t} else {\n\t\t\tchecks.push({ name: \"origin-policy\", status: \"ok\", code: \"ok\", message: \"Provider origin policy passed\" });\n\t\t}\n\t} else {\n\t\tchecks.push({ name: \"origin-policy\", status: \"skipped\", code: \"ok\", message: \"No valid URL to inspect\" });\n\t}\n\n\tif (target.auth.present) {\n\t\tchecks.push({ name: \"auth-present\", status: \"ok\", code: \"ok\", message: \"Authentication is configured\" });\n\t} else if (target.origin === \"custom-openai-compatible\" || target.origin === \"local-proxy\") {\n\t\t// Native login checks do not apply: custom/local OpenAI-compatible endpoints may be keyless.\n\t\tchecks.push({\n\t\t\tname: \"auth-present\",\n\t\t\tstatus: \"skipped\",\n\t\t\tcode: \"ok\",\n\t\t\tmessage: \"No credential configured; custom endpoints may be keyless\",\n\t\t});\n\t} else {\n\t\tconst next: ProbeFailure = {\n\t\t\tcategory: \"auth\",\n\t\t\tcode: \"auth-missing\",\n\t\t\tmessage: \"Authentication is not configured\",\n\t\t};\n\t\tchecks.push({ name: \"auth-present\", status: \"fail\", ...next });\n\t\trecordFailure(next);\n\t}\n\n\treturn { checks, url, failure };\n}\n\ntype ProbeEndpointKind = \"root\" | \"models\" | \"model-probe\";\n\nfunction classifyProbe(name: string, kind: ProbeEndpointKind, status: number): ProbeOutcome {\n\tconst forModels = kind === \"models\";\n\tif (Number.isInteger(status) && status >= 200 && status < 300) {\n\t\tconst probe: EndpointProbeResult = { reachable: true, authenticated: true, status, category: \"ok\" };\n\t\tif (forModels) probe.modelsSupported = true;\n\t\treturn { check: { name, status: \"ok\", code: \"ok\", message: `HTTP ${status}`, probe } };\n\t}\n\tif (status >= 300 && status < 400) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"config\",\n\t\t\tcode: \"redirect-blocked\",\n\t\t\tmessage: \"Provider probe returned a blocked redirect\",\n\t\t};\n\t\treturn { check: { name, status: \"fail\", ...failure }, failure };\n\t}\n\tif (status === 401 || status === 403) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"auth\",\n\t\t\tcode: \"authentication-failed\",\n\t\t\tmessage: \"Provider rejected authentication\",\n\t\t};\n\t\tconst probe: EndpointProbeResult = { reachable: true, authenticated: false, status, category: \"auth\" };\n\t\treturn { check: { name, status: \"fail\", ...failure, probe }, failure };\n\t}\n\tif (status === 404 || status === 405 || status === 501) {\n\t\t// A root or /models 404 is neutral: OpenAI-compatible servers may not expose these paths.\n\t\tconst probe: EndpointProbeResult = { reachable: true, status, category: \"unsupported-endpoint\" };\n\t\tif (forModels) probe.modelsSupported = false;\n\t\treturn {\n\t\t\tcheck: {\n\t\t\t\tname,\n\t\t\t\tstatus: \"unsupported\",\n\t\t\t\tcode: \"endpoint-unsupported\",\n\t\t\t\tmessage: `HTTP ${status}; endpoint is not required`,\n\t\t\t\tprobe,\n\t\t\t},\n\t\t};\n\t}\n\tif (status >= 500 && status <= 599) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"server\",\n\t\t\tcode: \"server-error\",\n\t\t\tmessage: \"Provider endpoint returned a server error\",\n\t\t};\n\t\tconst probe: EndpointProbeResult = { reachable: true, status, category: \"server\" };\n\t\treturn { check: { name, status: \"fail\", ...failure, probe }, failure };\n\t}\n\tif (status === 408 || status === 429) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"network\",\n\t\t\tcode: \"network-failure\",\n\t\t\tmessage: \"Provider probe failed at the network boundary\",\n\t\t};\n\t\tconst probe: EndpointProbeResult = { reachable: true, status, category: \"network\" };\n\t\treturn { check: { name, status: \"fail\", ...failure, probe }, failure };\n\t}\n\n\tconst failure: ProbeFailure = {\n\t\tcategory: \"config\",\n\t\tcode: \"unexpected-response\",\n\t\tmessage: \"Provider probe returned an unexpected response\",\n\t};\n\treturn { check: { name, status: \"fail\", ...failure }, failure };\n}\n\nfunction createComposedController(\n\tsignal: AbortSignal | undefined,\n\ttimeoutMs: number,\n): {\n\tcontroller: AbortController;\n\tabortKind: () => \"aborted\" | \"timeout\" | undefined;\n\tdispose: () => void;\n} {\n\tconst controller = new AbortController();\n\tlet kind: \"aborted\" | \"timeout\" | undefined;\n\tconst abortFromCaller = (): void => {\n\t\tkind ??= \"aborted\";\n\t\tcontroller.abort();\n\t};\n\tif (signal?.aborted) abortFromCaller();\n\telse signal?.addEventListener(\"abort\", abortFromCaller, { once: true });\n\tconst timeout = setTimeout(() => {\n\t\tkind ??= \"timeout\";\n\t\tcontroller.abort();\n\t}, timeoutMs);\n\n\treturn {\n\t\tcontroller,\n\t\tabortKind: () => kind,\n\t\tdispose: () => {\n\t\t\tclearTimeout(timeout);\n\t\t\tsignal?.removeEventListener(\"abort\", abortFromCaller);\n\t\t},\n\t};\n}\n\nfunction requestWithAbort(\n\ttransport: ProviderDoctorTransport,\n\trequest: ProviderDoctorTransportRequest,\n): Promise<ProviderDoctorTransportResponse> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (request.signal.aborted) {\n\t\t\treject(ABORTED);\n\t\t\treturn;\n\t\t}\n\t\tconst onAbort = (): void => reject(ABORTED);\n\t\trequest.signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\tlet pending: Promise<ProviderDoctorTransportResponse>;\n\t\ttry {\n\t\t\tpending = transport.request(request);\n\t\t} catch (error) {\n\t\t\trequest.signal.removeEventListener(\"abort\", onAbort);\n\t\t\treject(error instanceof ProviderDoctorAuthMaterializationError ? error : undefined);\n\t\t\treturn;\n\t\t}\n\t\tvoid pending.then(\n\t\t\t(response) => {\n\t\t\t\trequest.signal.removeEventListener(\"abort\", onAbort);\n\t\t\t\tresolve(response);\n\t\t\t},\n\t\t\t(error: unknown) => {\n\t\t\t\trequest.signal.removeEventListener(\"abort\", onAbort);\n\t\t\t\treject(error instanceof ProviderDoctorAuthMaterializationError ? error : undefined);\n\t\t\t},\n\t\t);\n\t});\n}\n\ninterface ProbePayload {\n\tbody: string;\n\tcontentType: \"application/json\";\n}\n\nasync function runProbe(\n\tname: string,\n\tkind: ProbeEndpointKind,\n\turl: URL,\n\ttransport: ProviderDoctorTransport,\n\taddressPolicy: ProviderDoctorAddressPolicy,\n\tcreateHeaders: ProviderDoctorHeaderFactory,\n\tcontroller: ReturnType<typeof createComposedController>,\n\tpayload?: ProbePayload,\n): Promise<ProbeOutcome> {\n\ttry {\n\t\tconst response = await requestWithAbort(transport, {\n\t\t\turl,\n\t\t\tmethod: payload ? \"POST\" : \"GET\",\n\t\t\tredirect: \"manual\",\n\t\t\tsignal: controller.controller.signal,\n\t\t\taddressPolicy,\n\t\t\tcreateHeaders,\n\t\t\tbody: payload?.body,\n\t\t\tcontentType: payload?.contentType,\n\t\t});\n\t\treturn classifyProbe(name, kind, response.status);\n\t} catch (error) {\n\t\tif (error instanceof ProviderDoctorAuthMaterializationError) {\n\t\t\tconst failure: ProbeFailure = {\n\t\t\t\tcategory: \"auth\",\n\t\t\t\tcode: \"auth-materialization-failed\",\n\t\t\t\tmessage: \"Configured authentication could not be materialized; verify auth and config references\",\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tcheck: {\n\t\t\t\t\tname,\n\t\t\t\t\tstatus: \"fail\",\n\t\t\t\t\t...failure,\n\t\t\t\t\tprobe: { reachable: false, authenticated: false, category: \"auth\" },\n\t\t\t\t},\n\t\t\t\tfailure,\n\t\t\t};\n\t\t}\n\t\tconst abortKind = controller.abortKind();\n\t\tconst failure: ProbeFailure = abortKind\n\t\t\t? {\n\t\t\t\t\tcategory: \"network\",\n\t\t\t\t\tcode: abortKind === \"timeout\" ? \"request-timeout\" : \"request-aborted\",\n\t\t\t\t\tmessage: abortKind === \"timeout\" ? \"Provider probe timed out\" : \"Provider probe was aborted\",\n\t\t\t\t}\n\t\t\t: { category: \"network\", code: \"network-failure\", message: \"Provider transport failed\" };\n\t\treturn {\n\t\t\tcheck: { name, status: \"fail\", ...failure, probe: { reachable: false, category: \"network\" } },\n\t\t\tfailure,\n\t\t};\n\t}\n}\n\n/** Minimal-token generative probe payload; never includes tools (no tool-call probe). */\nfunction modelProbeSpec(api: string, modelId: string): { path: string; payload: ProbePayload } | undefined {\n\tif (api === \"openai-completions\") {\n\t\treturn {\n\t\t\tpath: \"chat/completions\",\n\t\t\tpayload: {\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel: modelId,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \"ping\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t\tstream: false,\n\t\t\t\t}),\n\t\t\t\tcontentType: \"application/json\",\n\t\t\t},\n\t\t};\n\t}\n\tif (api === \"openai-responses\") {\n\t\treturn {\n\t\t\tpath: \"responses\",\n\t\t\tpayload: {\n\t\t\t\t// The responses API rejects max_output_tokens below 16; still the minimal request.\n\t\t\t\tbody: JSON.stringify({ model: modelId, input: \"ping\", max_output_tokens: 16, stream: false }),\n\t\t\t\tcontentType: \"application/json\",\n\t\t\t},\n\t\t};\n\t}\n\treturn undefined;\n}\n\nfunction classifyOrigin(baseUrl: string | undefined, api: string | undefined, isNative: boolean): ProviderOrigin {\n\tconst url = baseUrl ? sanitizeUrl(baseUrl) : undefined;\n\tif (url && isLiteralLoopback(normalizedHostname(url))) {\n\t\treturn \"local-proxy\";\n\t}\n\tif (isNative) return \"native\";\n\tif (api && OPENAI_COMPATIBLE_APIS.has(api)) return \"custom-openai-compatible\";\n\treturn \"unknown\";\n}\n\nfunction resolveProviderTarget(\n\tproviderId: string,\n\toptions: ProviderDoctorOptions,\n): { target?: ResolvedProviderTarget; failure?: ProviderDoctorResult } {\n\tconst level = options.level ?? 0;\n\tconst agentDir = options.agentDir ?? getConfiguredAgentDir();\n\tconst modelsConfig = loadConfig(join(agentDir, \"models.json\"), parseModelsConfig, true);\n\tif (modelsConfig.kind === \"invalid\") {\n\t\treturn {\n\t\t\tfailure: unresolvedFailure(providerId, level, \"models-config-invalid\", \"models.json is invalid\"),\n\t\t};\n\t}\n\tconst authConfig = loadConfig(join(agentDir, \"auth.json\"), parseAuthConfig);\n\tif (authConfig.kind === \"invalid\") {\n\t\treturn { failure: unresolvedFailure(providerId, level, \"auth-config-invalid\", \"auth.json is invalid\") };\n\t}\n\n\tconst kimi = providerId === KIMI_PROVIDER_ID ? resolveKimiConfig(options.kimiConfigPath) : {};\n\tif (kimi.failure) {\n\t\treturn {\n\t\t\tfailure: unresolvedFailure(\n\t\t\t\tproviderId,\n\t\t\t\tlevel,\n\t\t\t\t\"kimi-config-invalid\",\n\t\t\t\t`Kimi config TOML is invalid (line ${kimi.failure.line}: ${kimi.failure.reason}); fix the Kimi config or set KIMI_BASE_URL/KIMI_MODEL_NAME`,\n\t\t\t),\n\t\t};\n\t}\n\n\tconst configured = modelsConfig.kind === \"ok\" ? modelsConfig.value.providers[providerId] : undefined;\n\tconst nativeProviders = getProviders();\n\tconst isNative = nativeProviders.includes(providerId as KnownProvider);\n\tconst nativeModels = isNative ? (getModels(providerId as KnownProvider) as Model<Api>[]) : [];\n\tconst configuredModels = configured?.models ?? [];\n\t// At Level 2 the probe model is the paid target and therefore drives endpoint resolution.\n\tconst requestedModel = level === 2 ? (options.probeModelId ?? options.modelId) : options.modelId;\n\tconst configuredModel = requestedModel\n\t\t? configuredModels.find((model) => model.id === requestedModel)\n\t\t: configuredModels[0];\n\n\t// §10.2: with no higher-precedence endpoint (models.json > KIMI_BASE_URL > root TOML), a\n\t// `[providers.<name>]` entry with `type = \"openai_legacy\"` selects a custom OpenAI-compatible\n\t// endpoint, so native-only checks and native credential/model inheritance are skipped downstream.\n\tconst kimiCustomSelected =\n\t\t!(configuredModel?.baseUrl ?? configured?.baseUrl) && !kimi.baseUrl && kimi.customProvider !== undefined;\n\tconst kimiModelName = kimi.modelName ?? (kimiCustomSelected ? kimi.customProvider?.modelName : undefined);\n\tconst endpointModelId = requestedModel ?? kimiModelName;\n\tconst modelIds = kimiCustomSelected\n\t\t? kimiModelName\n\t\t\t? [kimiModelName]\n\t\t\t: []\n\t\t: Array.from(\n\t\t\t\tnew Set([\n\t\t\t\t\t...nativeModels.map((model) => model.id),\n\t\t\t\t\t...configuredModels.map((model) => model.id),\n\t\t\t\t\t...(kimiModelName ? [kimiModelName] : []),\n\t\t\t\t]),\n\t\t\t);\n\tlet nativeModel = kimiCustomSelected\n\t\t? undefined\n\t\t: requestedModel\n\t\t\t? nativeModels.find((model) => model.id === requestedModel)\n\t\t\t: nativeModels[0];\n\tif (!nativeModel && !kimiCustomSelected && requestedModel !== undefined && requestedModel === kimiModelName) {\n\t\t// A native Kimi TOML/env model rides on the provider defaults for endpoint metadata.\n\t\tnativeModel = nativeModels[0];\n\t}\n\n\tlet baseUrl = configuredModel?.baseUrl ?? configured?.baseUrl;\n\tlet api = configuredModel?.api ?? configured?.api ?? nativeModel?.api;\n\tlet source = configured ? \"models.json\" : isNative ? \"built-in-model-registry\" : \"unresolved\";\n\tif (!baseUrl && kimi.baseUrl) {\n\t\tbaseUrl = kimi.baseUrl;\n\t\tsource = kimi.baseUrlSource ?? source;\n\t} else if (kimiCustomSelected && kimi.customProvider) {\n\t\tbaseUrl = kimi.customProvider.baseUrl;\n\t\tapi = \"openai-completions\";\n\t\tsource = `kimi-config-toml:providers.${kimi.customProvider.name}`;\n\t} else {\n\t\tbaseUrl = baseUrl ?? nativeModel?.baseUrl;\n\t}\n\n\tconst storedAuth = authConfig.kind === \"ok\" && authConfig.value[providerId] !== undefined;\n\tconst environmentAuth = findEnvKeys(providerId) !== undefined;\n\tconst configuredAuth = configured?.apiKey ? isConfigValueConfigured(configured.apiKey) : false;\n\tconst headerAuth = configured?.headers !== undefined && Object.keys(configured.headers).length > 0;\n\tconst customApiKey = kimiCustomSelected ? kimi.customProvider?.apiKey : undefined;\n\tconst customAuthPresent = customApiKey !== undefined && isConfigValueConfigured(customApiKey);\n\tconst authPresent = kimiCustomSelected\n\t\t? customAuthPresent\n\t\t: storedAuth || environmentAuth || configuredAuth || headerAuth;\n\tconst authSource = kimiCustomSelected\n\t\t? customAuthPresent\n\t\t\t? source\n\t\t\t: undefined\n\t\t: storedAuth\n\t\t\t? \"auth.json\"\n\t\t\t: environmentAuth\n\t\t\t\t? \"environment\"\n\t\t\t\t: configuredAuth || headerAuth\n\t\t\t\t\t? \"models.json\"\n\t\t\t\t\t: undefined;\n\n\tif (!configured && !isNative && !storedAuth && !environmentAuth) {\n\t\treturn { failure: unresolvedFailure(providerId, level, \"provider-not-found\", \"Provider was not found\") };\n\t}\n\n\tconst origin = classifyOrigin(baseUrl, api, isNative && !kimiCustomSelected);\n\tconst canonicalBaseUrl = canonicalProviderEndpointUrl(baseUrl);\n\tconst binding =\n\t\tcanonicalBaseUrl && api && endpointModelId\n\t\t\t? { origin, source, baseUrl: canonicalBaseUrl, api, modelId: endpointModelId }\n\t\t\t: undefined;\n\tconst createHeaders: ProviderDoctorHeaderFactory | undefined =\n\t\tkimiCustomSelected && binding\n\t\t\t? () => {\n\t\t\t\t\tconst headers = new Headers();\n\t\t\t\t\tif (customApiKey) {\n\t\t\t\t\t\theaders.set(\n\t\t\t\t\t\t\t\"Authorization\",\n\t\t\t\t\t\t\t`Bearer ${resolveConfigValueOrThrow(customApiKey, \"API key for custom Kimi endpoint\")}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn headers;\n\t\t\t\t}\n\t\t\t: undefined;\n\n\treturn {\n\t\ttarget: {\n\t\t\tproviderId,\n\t\t\torigin,\n\t\t\tsource,\n\t\t\tendpoint: { baseUrl, api, modelIds, modelId: endpointModelId },\n\t\t\tauth: { present: authPresent, source: authSource, createHeaders, binding },\n\t\t},\n\t};\n}\n\nexport async function diagnoseResolvedProvider(\n\ttarget: ResolvedProviderTarget,\n\toptions: ProviderDoctorOptions = {},\n\tdependencies: ProviderDoctorDependencies = {},\n): Promise<ProviderDoctorResult> {\n\tconst requestedLevel = options.level ?? 0;\n\tif (requestedLevel !== 0 && requestedLevel !== 1 && requestedLevel !== 2) {\n\t\treturn unresolvedFailure(target.providerId, 0, \"unsupported-level\", \"Provider doctor level is unsupported\");\n\t}\n\tconst level: ProviderDoctorLevel = requestedLevel;\n\t// Level 2 is opt-in only: probeModelId is ignored at levels 0/1, which stay GET-only.\n\tconst probeModelId = level === 2 ? options.probeModelId : undefined;\n\tconst effectiveModelId = probeModelId ?? options.modelId;\n\tconst resolvedTarget = effectiveModelId\n\t\t? { ...target, endpoint: { ...target.endpoint, modelId: effectiveModelId } }\n\t\t: target;\n\tif (level === 2 && options.modelId && probeModelId && options.modelId !== probeModelId) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"model\",\n\t\t\tcode: \"probe-model-conflict\",\n\t\t\tmessage: \"Validation model and paid probe model must match\",\n\t\t};\n\t\treturn publicResult(\n\t\t\tresolvedTarget,\n\t\t\tlevel,\n\t\t\t[{ name: \"model-probe\", status: \"fail\", ...failure }],\n\t\t\tundefined,\n\t\t\tfailure,\n\t\t);\n\t}\n\tif (level === 2 && !probeModelId) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"config\",\n\t\t\tcode: \"probe-model-required\",\n\t\t\tmessage: \"Level 2 requires an explicit probe model (--probe-model)\",\n\t\t};\n\t\treturn publicResult(\n\t\t\tresolvedTarget,\n\t\t\tlevel,\n\t\t\t[{ name: \"model-probe\", status: \"fail\", ...failure }],\n\t\t\tundefined,\n\t\t\tfailure,\n\t\t);\n\t}\n\tconst validation = staticChecks(resolvedTarget);\n\tif (validation.failure || level === 0) {\n\t\treturn publicResult(resolvedTarget, level, validation.checks, validation.url, validation.failure);\n\t}\n\n\tconst transport = dependencies.transport ?? options.transport;\n\tif (!transport) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"config\",\n\t\t\tcode: \"transport-required\",\n\t\t\tmessage: \"Level 1 requires an injected provider transport\",\n\t\t};\n\t\tvalidation.checks.push({ name: \"transport\", status: \"fail\", ...failure });\n\t\treturn publicResult(resolvedTarget, level, validation.checks, validation.url, failure);\n\t}\n\tif (resolvedTarget.origin !== \"local-proxy\" && transport.pinsResolvedAddress !== true) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"config\",\n\t\t\tcode: \"address-pinning-required\",\n\t\t\tmessage: \"Public provider transport must pin resolved addresses\",\n\t\t};\n\t\tvalidation.checks.push({ name: \"transport\", status: \"fail\", ...failure });\n\t\treturn publicResult(resolvedTarget, level, validation.checks, validation.url, failure);\n\t}\n\n\tconst timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\tif (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n\t\tconst failure: ProbeFailure = {\n\t\t\tcategory: \"config\",\n\t\t\tcode: \"unexpected-response\",\n\t\t\tmessage: \"Provider probe timeout is invalid\",\n\t\t};\n\t\tvalidation.checks.push({ name: \"transport\", status: \"fail\", ...failure });\n\t\treturn publicResult(resolvedTarget, level, validation.checks, validation.url, failure);\n\t}\n\n\tconst addressPolicy: ProviderDoctorAddressPolicy =\n\t\tresolvedTarget.origin === \"local-proxy\"\n\t\t\t? { kind: \"loopback-only\", requireAddressPinning: false }\n\t\t\t: { kind: \"public\", requireAddressPinning: true };\n\tconst resolveAuthHeaders = dependencies.resolveAuthHeaders ?? options.resolveAuthHeaders;\n\tconst credentialBinding = resolvedTarget.auth.binding;\n\tconst boundHeaderFactory = resolvedTarget.auth.createHeaders;\n\tconst endpointBoundHeaders: ProviderDoctorHeaderFactory | undefined =\n\t\tcredentialBinding && boundHeaderFactory\n\t\t\t? async () => {\n\t\t\t\t\tif (!credentialBindingMatchesTarget(credentialBinding, resolvedTarget)) {\n\t\t\t\t\t\tthrow new ProviderDoctorAuthMaterializationError();\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn await boundHeaderFactory();\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tthrow new ProviderDoctorAuthMaterializationError();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t: undefined;\n\tconst createHeaders: ProviderDoctorHeaderFactory = endpointBoundHeaders\n\t\t? endpointBoundHeaders\n\t\t: level === 2 && resolveAuthHeaders\n\t\t\t? async () => {\n\t\t\t\t\tif (credentialBinding && !credentialBindingMatchesTarget(credentialBinding, resolvedTarget)) {\n\t\t\t\t\t\tthrow new ProviderDoctorAuthMaterializationError();\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn await resolveAuthHeaders(resolvedTarget);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tthrow new ProviderDoctorAuthMaterializationError();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t: (resolvedTarget.auth.createHeaders ?? (() => new Headers()));\n\tconst controller = createComposedController(options.signal, timeoutMs);\n\ttry {\n\t\tconst root = await runProbe(\n\t\t\t\"root-endpoint\",\n\t\t\t\"root\",\n\t\t\tmakeRootProbeUrl(validation.url!, resolvedTarget.origin),\n\t\t\ttransport,\n\t\t\taddressPolicy,\n\t\t\tcreateHeaders,\n\t\t\tcontroller,\n\t\t);\n\t\tvalidation.checks.push(root.check);\n\t\tif (root.failure) {\n\t\t\tvalidation.checks.push({\n\t\t\t\tname: \"models-endpoint\",\n\t\t\t\tstatus: \"skipped\",\n\t\t\t\tcode: \"ok\",\n\t\t\t\tmessage: \"Capability probe skipped after root failure\",\n\t\t\t});\n\t\t\treturn publicResult(resolvedTarget, level, validation.checks, validation.url, root.failure);\n\t\t}\n\n\t\tconst models = await runProbe(\n\t\t\t\"models-endpoint\",\n\t\t\t\"models\",\n\t\t\tappendPath(validation.url!, \"models\"),\n\t\t\ttransport,\n\t\t\taddressPolicy,\n\t\t\tcreateHeaders,\n\t\t\tcontroller,\n\t\t);\n\t\tvalidation.checks.push(models.check);\n\t\tif (level !== 2 || models.failure) {\n\t\t\treturn publicResult(resolvedTarget, level, validation.checks, validation.url, models.failure);\n\t\t}\n\n\t\tconst spec = modelProbeSpec(resolvedTarget.endpoint.api ?? \"\", probeModelId as string);\n\t\tif (!spec) {\n\t\t\tconst failure: ProbeFailure = {\n\t\t\t\tcategory: \"config\",\n\t\t\t\tcode: \"api-unsupported\",\n\t\t\t\tmessage: \"Model probe supports only OpenAI-compatible APIs\",\n\t\t\t};\n\t\t\tvalidation.checks.push({ name: \"model-probe\", status: \"fail\", ...failure });\n\t\t\treturn publicResult(resolvedTarget, level, validation.checks, validation.url, failure);\n\t\t}\n\t\tconst probe = await runProbe(\n\t\t\t\"model-probe\",\n\t\t\t\"model-probe\",\n\t\t\tappendPath(validation.url!, spec.path),\n\t\t\ttransport,\n\t\t\taddressPolicy,\n\t\t\tcreateHeaders,\n\t\t\tcontroller,\n\t\t\tspec.payload,\n\t\t);\n\t\tconst dispatched = probe.failure?.code !== \"auth-materialization-failed\";\n\t\tif (dispatched) {\n\t\t\tprobe.check.message = `${probe.check.message}; minimal-token generative probe (may incur provider costs)`;\n\t\t}\n\t\tvalidation.checks.push(probe.check);\n\t\treturn publicResult(resolvedTarget, level, validation.checks, validation.url, probe.failure, dispatched);\n\t} finally {\n\t\tcontroller.dispose();\n\t}\n}\n\nexport async function diagnoseProvider(\n\tproviderId: string,\n\toptions: ProviderDoctorOptions = {},\n): Promise<ProviderDoctorResult> {\n\tconst requestedLevel = options.level ?? 0;\n\tif (requestedLevel !== 0 && requestedLevel !== 1 && requestedLevel !== 2) {\n\t\treturn unresolvedFailure(providerId, 0, \"unsupported-level\", \"Provider doctor level is unsupported\");\n\t}\n\tconst resolved = resolveProviderTarget(providerId, options);\n\tif (resolved.failure) return resolved.failure;\n\treturn diagnoseResolvedProvider(resolved.target!, options);\n}\n"]}
|
|
@@ -674,16 +674,20 @@ function credentialBindingMatchesTarget(binding, target) {
|
|
|
674
674
|
binding.modelId === target.endpoint.modelId);
|
|
675
675
|
}
|
|
676
676
|
function appendPath(url, segment) {
|
|
677
|
-
const next =
|
|
677
|
+
const next = URL.parse(url.href);
|
|
678
|
+
if (!next)
|
|
679
|
+
throw new Error("validated provider URL could not be cloned");
|
|
678
680
|
next.pathname = `${next.pathname.replace(/\/$/, "")}/${segment}`;
|
|
679
681
|
return next;
|
|
680
682
|
}
|
|
681
683
|
function makeRootProbeUrl(url, origin) {
|
|
684
|
+
const root = URL.parse(url.href);
|
|
685
|
+
if (!root)
|
|
686
|
+
throw new Error("validated provider URL could not be cloned");
|
|
682
687
|
if (origin !== "local-proxy")
|
|
683
|
-
return
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
return health;
|
|
688
|
+
return root;
|
|
689
|
+
root.pathname = "/health";
|
|
690
|
+
return root;
|
|
687
691
|
}
|
|
688
692
|
function publicResult(target, level, checks, url, failure, costWarning) {
|
|
689
693
|
const targetUrl = url?.href;
|
|
@@ -1046,9 +1050,9 @@ function modelProbeSpec(api, modelId) {
|
|
|
1046
1050
|
}
|
|
1047
1051
|
return undefined;
|
|
1048
1052
|
}
|
|
1049
|
-
function classifyOrigin(
|
|
1053
|
+
function classifyOrigin(baseUrl, api, isNative) {
|
|
1050
1054
|
const url = baseUrl ? sanitizeUrl(baseUrl) : undefined;
|
|
1051
|
-
if (
|
|
1055
|
+
if (url && isLiteralLoopback(normalizedHostname(url))) {
|
|
1052
1056
|
return "local-proxy";
|
|
1053
1057
|
}
|
|
1054
1058
|
if (isNative)
|
|
@@ -1125,13 +1129,6 @@ function resolveProviderTarget(providerId, options) {
|
|
|
1125
1129
|
else {
|
|
1126
1130
|
baseUrl = baseUrl ?? nativeModel?.baseUrl;
|
|
1127
1131
|
}
|
|
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
1132
|
const storedAuth = authConfig.kind === "ok" && authConfig.value[providerId] !== undefined;
|
|
1136
1133
|
const environmentAuth = findEnvKeys(providerId) !== undefined;
|
|
1137
1134
|
const configuredAuth = configured?.apiKey ? isConfigValueConfigured(configured.apiKey) : false;
|
|
@@ -1140,7 +1137,7 @@ function resolveProviderTarget(providerId, options) {
|
|
|
1140
1137
|
const customAuthPresent = customApiKey !== undefined && isConfigValueConfigured(customApiKey);
|
|
1141
1138
|
const authPresent = kimiCustomSelected
|
|
1142
1139
|
? customAuthPresent
|
|
1143
|
-
: storedAuth || environmentAuth || configuredAuth || headerAuth
|
|
1140
|
+
: storedAuth || environmentAuth || configuredAuth || headerAuth;
|
|
1144
1141
|
const authSource = kimiCustomSelected
|
|
1145
1142
|
? customAuthPresent
|
|
1146
1143
|
? source
|
|
@@ -1151,13 +1148,11 @@ function resolveProviderTarget(providerId, options) {
|
|
|
1151
1148
|
? "environment"
|
|
1152
1149
|
: configuredAuth || headerAuth
|
|
1153
1150
|
? "models.json"
|
|
1154
|
-
:
|
|
1155
|
-
|
|
1156
|
-
: undefined;
|
|
1157
|
-
if (!configured && !isNative && !storedAuth && !environmentAuth && providerId !== "grok-oauth-proxy") {
|
|
1151
|
+
: undefined;
|
|
1152
|
+
if (!configured && !isNative && !storedAuth && !environmentAuth) {
|
|
1158
1153
|
return { failure: unresolvedFailure(providerId, level, "provider-not-found", "Provider was not found") };
|
|
1159
1154
|
}
|
|
1160
|
-
const origin = classifyOrigin(
|
|
1155
|
+
const origin = classifyOrigin(baseUrl, api, isNative && !kimiCustomSelected);
|
|
1161
1156
|
const canonicalBaseUrl = canonicalProviderEndpointUrl(baseUrl);
|
|
1162
1157
|
const binding = canonicalBaseUrl && api && endpointModelId
|
|
1163
1158
|
? { origin, source, baseUrl: canonicalBaseUrl, api, modelId: endpointModelId }
|