open-multi-agent-kit 0.90.8 → 0.90.9
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 +22 -0
- package/README.md +7 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/commands/doctor-provider-cli.d.ts +33 -0
- package/dist/commands/doctor-provider-cli.d.ts.map +1 -0
- package/dist/commands/doctor-provider-cli.js +215 -0
- package/dist/commands/doctor-provider-cli.js.map +1 -0
- package/dist/commands/doctor-provider-transport.d.ts +35 -0
- package/dist/commands/doctor-provider-transport.d.ts.map +1 -0
- package/dist/commands/doctor-provider-transport.js +185 -0
- package/dist/commands/doctor-provider-transport.js.map +1 -0
- package/dist/commands/doctor-provider.d.ts +121 -7
- package/dist/commands/doctor-provider.d.ts.map +1 -1
- package/dist/commands/doctor-provider.js +1295 -102
- package/dist/commands/doctor-provider.js.map +1 -1
- package/dist/commands/session-doctor-cli.d.ts +43 -0
- package/dist/commands/session-doctor-cli.d.ts.map +1 -0
- package/dist/commands/session-doctor-cli.js +494 -0
- package/dist/commands/session-doctor-cli.js.map +1 -0
- package/dist/commands/verify-ci.d.ts +21 -0
- package/dist/commands/verify-ci.d.ts.map +1 -0
- package/dist/commands/verify-ci.js +66 -0
- package/dist/commands/verify-ci.js.map +1 -0
- package/dist/core/agent-session-services.d.ts +3 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +3 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +113 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +993 -72
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agent-tool-settings.d.ts +35 -0
- package/dist/core/agent-tool-settings.d.ts.map +1 -0
- package/dist/core/agent-tool-settings.js +155 -0
- package/dist/core/agent-tool-settings.js.map +1 -0
- package/dist/core/atomic-session-file.d.ts +14 -0
- package/dist/core/atomic-session-file.d.ts.map +1 -0
- package/dist/core/atomic-session-file.js +104 -0
- package/dist/core/atomic-session-file.js.map +1 -0
- package/dist/core/compaction/compaction.d.ts +6 -0
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +12 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/hysteresis.d.ts +27 -0
- package/dist/core/compaction/hysteresis.d.ts.map +1 -0
- package/dist/core/compaction/hysteresis.js +84 -0
- package/dist/core/compaction/hysteresis.js.map +1 -0
- package/dist/core/compaction/index.d.ts +2 -0
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js +2 -0
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/compaction/transaction.d.ts +172 -0
- package/dist/core/compaction/transaction.d.ts.map +1 -0
- package/dist/core/compaction/transaction.js +824 -0
- package/dist/core/compaction/transaction.js.map +1 -0
- package/dist/core/context-budget-governor-v2.d.ts +2 -1
- package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
- package/dist/core/context-budget-governor-v2.js +2 -1
- package/dist/core/context-budget-governor-v2.js.map +1 -1
- package/dist/core/context-budget-reserved-tokens.d.ts +110 -0
- package/dist/core/context-budget-reserved-tokens.d.ts.map +1 -0
- package/dist/core/context-budget-reserved-tokens.js +188 -0
- package/dist/core/context-budget-reserved-tokens.js.map +1 -0
- package/dist/core/context-budget-v2-cache-invalidation.d.ts +133 -0
- package/dist/core/context-budget-v2-cache-invalidation.d.ts.map +1 -0
- package/dist/core/context-budget-v2-cache-invalidation.js +452 -0
- package/dist/core/context-budget-v2-cache-invalidation.js.map +1 -0
- package/dist/core/context-budget-v2-cache-keys.d.ts +3 -0
- package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache-keys.js +6 -0
- package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
- package/dist/core/context-budget-v2-cache-provider.d.ts +4 -0
- package/dist/core/context-budget-v2-cache-provider.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache-provider.js +19 -0
- package/dist/core/context-budget-v2-cache-provider.js.map +1 -1
- package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -1
- package/dist/core/context-budget-v2-plan-cache-keys.js +1 -0
- package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -1
- package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
- package/dist/core/context-budget-v2-planner.js +1 -0
- package/dist/core/context-budget-v2-planner.js.map +1 -1
- package/dist/core/context-budget-v2-types.d.ts +4 -0
- package/dist/core/context-budget-v2-types.d.ts.map +1 -1
- package/dist/core/context-budget-v2-types.js.map +1 -1
- package/dist/core/durable-file-directory.d.ts +4 -0
- package/dist/core/durable-file-directory.d.ts.map +1 -0
- package/dist/core/durable-file-directory.js +76 -0
- package/dist/core/durable-file-directory.js.map +1 -0
- package/dist/core/durable-file-identity.d.ts +26 -0
- package/dist/core/durable-file-identity.d.ts.map +1 -0
- package/dist/core/durable-file-identity.js +118 -0
- package/dist/core/durable-file-identity.js.map +1 -0
- package/dist/core/durable-file-io.d.ts +29 -0
- package/dist/core/durable-file-io.d.ts.map +1 -0
- package/dist/core/durable-file-io.js +179 -0
- package/dist/core/durable-file-io.js.map +1 -0
- package/dist/core/durable-file-lock-cleanup.d.ts +12 -0
- package/dist/core/durable-file-lock-cleanup.d.ts.map +1 -0
- package/dist/core/durable-file-lock-cleanup.js +39 -0
- package/dist/core/durable-file-lock-cleanup.js.map +1 -0
- package/dist/core/durable-file-lock-owner.d.ts +18 -0
- package/dist/core/durable-file-lock-owner.d.ts.map +1 -0
- package/dist/core/durable-file-lock-owner.js +78 -0
- package/dist/core/durable-file-lock-owner.js.map +1 -0
- package/dist/core/durable-file-lock-storage.d.ts +35 -0
- package/dist/core/durable-file-lock-storage.d.ts.map +1 -0
- package/dist/core/durable-file-lock-storage.js +263 -0
- package/dist/core/durable-file-lock-storage.js.map +1 -0
- package/dist/core/durable-file-mode.d.ts +8 -0
- package/dist/core/durable-file-mode.d.ts.map +1 -0
- package/dist/core/durable-file-mode.js +41 -0
- package/dist/core/durable-file-mode.js.map +1 -0
- package/dist/core/extensions/loader.d.ts +12 -0
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +46 -37
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/index.d.ts +6 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +5 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +1 -1
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/redaction.d.ts +8 -0
- package/dist/core/redaction.d.ts.map +1 -0
- package/dist/core/redaction.js +31 -0
- package/dist/core/redaction.js.map +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +36 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/run-journal-store.d.ts +53 -0
- package/dist/core/run-journal-store.d.ts.map +1 -0
- package/dist/core/run-journal-store.js +278 -0
- package/dist/core/run-journal-store.js.map +1 -0
- package/dist/core/run-journal.d.ts +172 -0
- package/dist/core/run-journal.d.ts.map +1 -0
- package/dist/core/run-journal.js +1053 -0
- package/dist/core/run-journal.js.map +1 -0
- package/dist/core/sdk.d.ts +7 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +94 -3
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-doctor-plan.d.ts +128 -0
- package/dist/core/session-doctor-plan.d.ts.map +1 -0
- package/dist/core/session-doctor-plan.js +833 -0
- package/dist/core/session-doctor-plan.js.map +1 -0
- package/dist/core/session-file-compaction.d.ts +6 -0
- package/dist/core/session-file-compaction.d.ts.map +1 -0
- package/dist/core/session-file-compaction.js +169 -0
- package/dist/core/session-file-compaction.js.map +1 -0
- package/dist/core/session-file-head.d.ts +10 -0
- package/dist/core/session-file-head.d.ts.map +1 -0
- package/dist/core/session-file-head.js +78 -0
- package/dist/core/session-file-head.js.map +1 -0
- package/dist/core/session-file-persistence.d.ts +30 -0
- package/dist/core/session-file-persistence.d.ts.map +1 -0
- package/dist/core/session-file-persistence.js +106 -0
- package/dist/core/session-file-persistence.js.map +1 -0
- package/dist/core/session-integrity.d.ts +43 -0
- package/dist/core/session-integrity.d.ts.map +1 -0
- package/dist/core/session-integrity.js +420 -0
- package/dist/core/session-integrity.js.map +1 -0
- package/dist/core/session-manager.d.ts +24 -4
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +242 -125
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-owner-lease.d.ts +22 -0
- package/dist/core/session-owner-lease.d.ts.map +1 -0
- package/dist/core/session-owner-lease.js +176 -0
- package/dist/core/session-owner-lease.js.map +1 -0
- package/dist/core/session-path-policy.d.ts +132 -0
- package/dist/core/session-path-policy.d.ts.map +1 -0
- package/dist/core/session-path-policy.js +780 -0
- package/dist/core/session-path-policy.js.map +1 -0
- package/dist/core/session-repair-plan.d.ts +49 -0
- package/dist/core/session-repair-plan.d.ts.map +1 -0
- package/dist/core/session-repair-plan.js +202 -0
- package/dist/core/session-repair-plan.js.map +1 -0
- package/dist/core/session-termination.d.ts +100 -0
- package/dist/core/session-termination.d.ts.map +1 -0
- package/dist/core/session-termination.js +403 -0
- package/dist/core/session-termination.js.map +1 -0
- package/dist/core/settings-manager.d.ts +22 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +37 -5
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/verified-bash-adapter.d.ts +36 -0
- package/dist/core/verified-bash-adapter.d.ts.map +1 -0
- package/dist/core/verified-bash-adapter.js +90 -0
- package/dist/core/verified-bash-adapter.js.map +1 -0
- package/dist/guardrails/command-redaction.d.ts +32 -0
- package/dist/guardrails/command-redaction.d.ts.map +1 -0
- package/dist/guardrails/command-redaction.js +776 -0
- package/dist/guardrails/command-redaction.js.map +1 -0
- package/dist/guardrails/evidence-attestation.d.ts +31 -0
- package/dist/guardrails/evidence-attestation.d.ts.map +1 -0
- package/dist/guardrails/evidence-attestation.js +178 -0
- package/dist/guardrails/evidence-attestation.js.map +1 -0
- package/dist/guardrails/evidence-receipt-store.d.ts +33 -0
- package/dist/guardrails/evidence-receipt-store.d.ts.map +1 -0
- package/dist/guardrails/evidence-receipt-store.js +446 -0
- package/dist/guardrails/evidence-receipt-store.js.map +1 -0
- package/dist/guardrails/evidence-receipt.d.ts +58 -0
- package/dist/guardrails/evidence-receipt.d.ts.map +1 -0
- package/dist/guardrails/evidence-receipt.js +389 -0
- package/dist/guardrails/evidence-receipt.js.map +1 -0
- package/dist/guardrails/evidence-system.d.ts +39 -15
- package/dist/guardrails/evidence-system.d.ts.map +1 -1
- package/dist/guardrails/evidence-system.js +255 -97
- package/dist/guardrails/evidence-system.js.map +1 -1
- package/dist/guardrails/replay-ledger-io.d.ts +6 -0
- package/dist/guardrails/replay-ledger-io.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-io.js +42 -0
- package/dist/guardrails/replay-ledger-io.js.map +1 -0
- package/dist/guardrails/replay-ledger-lock-owner.d.ts +32 -0
- package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-lock-owner.js +138 -0
- package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -0
- package/dist/guardrails/replay-ledger-lock.d.ts +24 -0
- package/dist/guardrails/replay-ledger-lock.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-lock.js +135 -0
- package/dist/guardrails/replay-ledger-lock.js.map +1 -0
- package/dist/guardrails/replay-ledger-mutation-gate.d.ts +24 -0
- package/dist/guardrails/replay-ledger-mutation-gate.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-mutation-gate.js +205 -0
- package/dist/guardrails/replay-ledger-mutation-gate.js.map +1 -0
- package/dist/guardrails/replay-ledger-quarantine.d.ts +2 -0
- package/dist/guardrails/replay-ledger-quarantine.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-quarantine.js +19 -0
- package/dist/guardrails/replay-ledger-quarantine.js.map +1 -0
- package/dist/guardrails/replay-ledger-store.d.ts +32 -0
- package/dist/guardrails/replay-ledger-store.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-store.js +211 -0
- package/dist/guardrails/replay-ledger-store.js.map +1 -0
- package/dist/guardrails/shell-command-name-glob.d.ts +3 -0
- package/dist/guardrails/shell-command-name-glob.d.ts.map +1 -0
- package/dist/guardrails/shell-command-name-glob.js +233 -0
- package/dist/guardrails/shell-command-name-glob.js.map +1 -0
- package/dist/guardrails/shell-command-option.d.ts +7 -0
- package/dist/guardrails/shell-command-option.d.ts.map +1 -0
- package/dist/guardrails/shell-command-option.js +24 -0
- package/dist/guardrails/shell-command-option.js.map +1 -0
- package/dist/guardrails/shell-command-tokenizer.d.ts +29 -0
- package/dist/guardrails/shell-command-tokenizer.d.ts.map +1 -0
- package/dist/guardrails/shell-command-tokenizer.js +145 -0
- package/dist/guardrails/shell-command-tokenizer.js.map +1 -0
- package/dist/guardrails/shell-command-wrapper.d.ts +18 -0
- package/dist/guardrails/shell-command-wrapper.d.ts.map +1 -0
- package/dist/guardrails/shell-command-wrapper.js +42 -0
- package/dist/guardrails/shell-command-wrapper.js.map +1 -0
- package/dist/guardrails/verified-executor.d.ts +65 -0
- package/dist/guardrails/verified-executor.d.ts.map +1 -0
- package/dist/guardrails/verified-executor.js +171 -0
- package/dist/guardrails/verified-executor.js.map +1 -0
- package/dist/guardrails/workspace-fingerprint.d.ts +23 -0
- package/dist/guardrails/workspace-fingerprint.d.ts.map +1 -0
- package/dist/guardrails/workspace-fingerprint.js +634 -0
- package/dist/guardrails/workspace-fingerprint.js.map +1 -0
- package/dist/index.d.ts +15 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +12 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +3 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +59 -9
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +43 -2
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +23 -5
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +3 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/types/evidence.d.ts +211 -1
- package/dist/types/evidence.d.ts.map +1 -1
- package/dist/types/evidence.js.map +1 -1
- package/dist/verify-ci.d.ts +3 -0
- package/dist/verify-ci.d.ts.map +1 -0
- package/dist/verify-ci.js +30 -0
- package/dist/verify-ci.js.map +1 -0
- package/docs/compaction.md +38 -4
- package/docs/extensions.md +2 -0
- package/docs/sdk.md +238 -1
- package/docs/settings.md +50 -2
- package/docs/usage.md +38 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +5 -5
- package/examples/extensions/custom-provider-anthropic/package.json +1 -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 +4 -4
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-provider-cli.js","sourceRoot":"","sources":["../../src/commands/doctor-provider-cli.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * CLI entry for the provider doctor.\n *\n * Canonical form: `omk provider doctor <provider-id>` with `--level`, `--model`,\n * `--timeout`, and the Level-2 opt-in `--probe-model`. The legacy flag form\n * `omk --doctor-provider <provider-id>` (with `--doctor-level`/`--doctor-model`/\n * `--doctor-timeout`) remains a supported alias.\n *\n * Prints exactly one sanitized JSON document to stdout and reports a stable exit code:\n * 0 = diagnosis ok, 1 = diagnosis failed, 2 = usage error. Usage errors never echo\n * argument values, so mistyped invocations cannot leak credentials.\n */\n\nimport { join } from \"node:path\";\nimport { AuthStorage } from \"../core/auth-storage.ts\";\nimport { ModelRegistry } from \"../core/model-registry.ts\";\nimport {\n\tcanonicalProviderEndpointUrl,\n\tdiagnoseProvider,\n\tgetAgentDir,\n\ttype ProviderDoctorAuthHeaderResolver,\n\ttype ProviderDoctorLevel,\n\ttype ProviderDoctorResult,\n\ttype ProviderDoctorTransport,\n} from \"./doctor-provider.ts\";\nimport { createProviderDoctorTransport } from \"./doctor-provider-transport.ts\";\n\nexport const DOCTOR_PROVIDER_FLAG = \"--doctor-provider\";\nconst USAGE = `Usage: omk provider doctor <provider-id> [--level <0|1>] [--model <model-id>] [--timeout <ms>] [--probe-model <model-id>] (legacy alias: omk ${DOCTOR_PROVIDER_FLAG} <provider-id>)`;\nconst HELP = [\n\t\"Provider doctor: diagnose one provider's configuration and endpoint reachability.\",\n\t\"\",\n\tUSAGE,\n\t\"\",\n\t\" --level <0|1> 0 = static checks only (default); 1 = adds GET-only reachability probes\",\n\t\" --model <model-id> validate a specific model against the provider\",\n\t\" --timeout <ms> network probe timeout in milliseconds\",\n\t\" --probe-model <model-id> Level 2 (opt-in): one minimal-token generative POST probe;\",\n\t\" may incur provider costs and sets costWarning in the output\",\n\t\"\",\n\t\"Levels 0 and 1 are non-generative and GET-only; no tool-call probe is ever sent.\",\n\t\"Output is one sanitized JSON document (credentials are never printed).\",\n\t\"Exit codes: 0 = ok, 1 = diagnosis failed, 2 = usage error.\",\n\t\"Legacy flags --doctor-level/--doctor-model/--doctor-timeout are aliases of --level/--model/--timeout.\",\n].join(\"\\n\");\n\nexport interface DoctorProviderCliOverrides {\n\tagentDir?: string;\n\tkimiConfigPath?: string;\n\ttransport?: ProviderDoctorTransport;\n\t/** Level-2 test seam; production uses AuthStorage + ModelRegistry lazily inside request dispatch. */\n\tresolveAuthHeaders?: ProviderDoctorAuthHeaderResolver;\n\twriteLine?: (line: string) => void;\n}\n\nexport interface DoctorProviderCliOutcome {\n\thandled: boolean;\n\texitCode: number;\n\tresult?: ProviderDoctorResult;\n}\n\ninterface ParsedDoctorArgs {\n\tproviderId: string;\n\tlevel: ProviderDoctorLevel;\n\tmodelId?: string;\n\ttimeoutMs?: number;\n\tprobeModelId?: string;\n}\n\ntype ParseOutcome =\n\t| { kind: \"absent\" }\n\t| { kind: \"help\" }\n\t| { kind: \"error\"; message: string }\n\t| { kind: \"ok\"; parsed: ParsedDoctorArgs };\n\nfunction parseDoctorProviderArgs(args: readonly string[]): ParseOutcome {\n\tconst canonical = args[0] === \"provider\" && args[1] === \"doctor\";\n\tif (!canonical && !args.includes(DOCTOR_PROVIDER_FLAG)) return { kind: \"absent\" };\n\tconst error = (message: string): ParseOutcome => ({ kind: \"error\", message });\n\n\tlet providerId: string | undefined;\n\tlet level: ProviderDoctorLevel = 0;\n\tlet modelId: string | undefined;\n\tlet timeoutMs: number | undefined;\n\tlet probeModelId: string | undefined;\n\tlet help = false;\n\tconst rest = canonical ? args.slice(2) : args;\n\tfor (let index = 0; index < rest.length; index++) {\n\t\tconst arg = rest[index];\n\t\tconst takeValue = (): string | undefined => {\n\t\t\tconst value = rest[index + 1];\n\t\t\tif (value === undefined || value.startsWith(\"--\")) return undefined;\n\t\t\tindex++;\n\t\t\treturn value;\n\t\t};\n\t\tif (!canonical && arg === DOCTOR_PROVIDER_FLAG) {\n\t\t\tproviderId = takeValue();\n\t\t\tif (!providerId) return error(`${DOCTOR_PROVIDER_FLAG} requires a provider id`);\n\t\t} else if (arg === \"--help\" || arg === \"-h\") {\n\t\t\thelp = true;\n\t\t} else if (arg === \"--doctor-level\" || arg === \"--level\") {\n\t\t\tconst value = takeValue();\n\t\t\tif (value !== \"0\" && value !== \"1\") {\n\t\t\t\treturn error(\"--level/--doctor-level must be 0 or 1; Level 2 runs only via --probe-model\");\n\t\t\t}\n\t\t\tlevel = Number(value) as ProviderDoctorLevel;\n\t\t} else if (arg === \"--doctor-model\" || arg === \"--model\") {\n\t\t\tmodelId = takeValue();\n\t\t\tif (!modelId) return error(\"--model/--doctor-model requires a model id\");\n\t\t} else if (arg === \"--doctor-timeout\" || arg === \"--timeout\") {\n\t\t\tconst value = takeValue();\n\t\t\tconst parsedTimeout = value === undefined ? Number.NaN : Number(value);\n\t\t\tif (!Number.isInteger(parsedTimeout) || parsedTimeout <= 0) {\n\t\t\t\treturn error(\"--timeout/--doctor-timeout requires a positive integer of milliseconds\");\n\t\t\t}\n\t\t\ttimeoutMs = parsedTimeout;\n\t\t} else if (arg === \"--probe-model\") {\n\t\t\tprobeModelId = takeValue();\n\t\t\tif (!probeModelId) return error(\"--probe-model requires a model id\");\n\t\t} else if (canonical && providerId === undefined && !arg.startsWith(\"-\")) {\n\t\t\tproviderId = arg;\n\t\t} else {\n\t\t\t// Never echo the offending argument: it may carry a credential value.\n\t\t\treturn error(\"unexpected extra arguments in provider doctor mode\");\n\t\t}\n\t}\n\tif (help) return { kind: \"help\" };\n\tif (!providerId) {\n\t\treturn error(\n\t\t\tcanonical ? \"provider doctor requires a provider id\" : `${DOCTOR_PROVIDER_FLAG} requires a provider id`,\n\t\t);\n\t}\n\tif (modelId !== undefined && probeModelId !== undefined && modelId !== probeModelId) {\n\t\treturn error(\"--model and --probe-model must name the same model\");\n\t}\n\t// Level 2 is reachable only through the explicit --probe-model opt-in.\n\tif (probeModelId !== undefined) level = 2;\n\treturn { kind: \"ok\", parsed: { providerId, level, modelId, timeoutMs, probeModelId } };\n}\n\nconst STRIPPED_REQUEST_HEADERS = new Set([\n\t\"connection\",\n\t\"content-length\",\n\t\"expect\",\n\t\"host\",\n\t\"keep-alive\",\n\t\"proxy-authenticate\",\n\t\"proxy-authorization\",\n\t\"proxy-connection\",\n\t\"te\",\n\t\"trailer\",\n\t\"transfer-encoding\",\n\t\"upgrade\",\n]);\nconst SINGLE_VALUE_CREDENTIAL_HEADERS = new Set([\"authorization\", \"cf-aig-authorization\"]);\n\nfunction safeResolvedHeaders(\n\tresolvedHeaders: Record<string, string> | undefined,\n\tapiKey: string | undefined,\n\tproviderId: string,\n): Headers {\n\tconst headers = new Headers();\n\tconst names = new Set<string>();\n\tfor (const [name, value] of Object.entries(resolvedHeaders ?? {})) {\n\t\tconst normalizedName = name.toLowerCase();\n\t\tif (STRIPPED_REQUEST_HEADERS.has(normalizedName)) continue;\n\t\tif (SINGLE_VALUE_CREDENTIAL_HEADERS.has(normalizedName) && names.has(normalizedName)) {\n\t\t\tthrow new Error(\"provider auth headers conflict\");\n\t\t}\n\t\tnames.add(normalizedName);\n\t\theaders.set(name, value);\n\t}\n\tif (apiKey) {\n\t\tconst authorizationName = providerId === \"cloudflare-ai-gateway\" ? \"cf-aig-authorization\" : \"authorization\";\n\t\tif (!headers.has(authorizationName)) headers.set(authorizationName, `Bearer ${apiKey}`);\n\t}\n\treturn headers;\n}\n\nfunction createProductionAuthHeaderResolver(agentDir: string): ProviderDoctorAuthHeaderResolver {\n\tlet registry: ModelRegistry | undefined;\n\treturn async (target) => {\n\t\tregistry ??= ModelRegistry.create(AuthStorage.create(join(agentDir, \"auth.json\")), join(agentDir, \"models.json\"));\n\t\tconst modelId = target.endpoint.modelId;\n\t\tconst model = modelId ? registry.find(target.providerId, modelId) : undefined;\n\t\tif (\n\t\t\t!model ||\n\t\t\tcanonicalProviderEndpointUrl(model.baseUrl) !== canonicalProviderEndpointUrl(target.endpoint.baseUrl) ||\n\t\t\tmodel.api !== target.endpoint.api\n\t\t) {\n\t\t\tthrow new Error(\"provider auth model unavailable\");\n\t\t}\n\n\t\tconst resolved = await registry.getApiKeyAndHeaders(model);\n\t\tif (!resolved.ok) throw new Error(\"provider auth unavailable\");\n\t\tconst headers = safeResolvedHeaders(resolved.headers, resolved.apiKey, target.providerId);\n\t\tif (target.auth.present && !resolved.apiKey && [...headers].length === 0) {\n\t\t\tthrow new Error(\"provider auth unavailable\");\n\t\t}\n\t\treturn headers;\n\t};\n}\n\n/**\n * Runs the provider doctor when argv is `provider doctor ...` or contains `--doctor-provider`.\n * Returns `{ handled: false }` untouched argv so the regular CLI flow continues.\n */\nexport async function runDoctorProviderCli(\n\targs: readonly string[],\n\toverrides: DoctorProviderCliOverrides = {},\n): Promise<DoctorProviderCliOutcome> {\n\tconst parsed = parseDoctorProviderArgs(args);\n\tif (parsed.kind === \"absent\") return { handled: false, exitCode: 0 };\n\n\tconst writeLine = overrides.writeLine ?? ((line: string) => console.log(line));\n\tif (parsed.kind === \"help\") {\n\t\twriteLine(HELP);\n\t\treturn { handled: true, exitCode: 0 };\n\t}\n\tif (parsed.kind === \"error\") {\n\t\twriteLine(\n\t\t\tJSON.stringify(\n\t\t\t\t{\n\t\t\t\t\tstatus: \"fail\",\n\t\t\t\t\terror: { category: \"config\", code: \"cli-usage\", message: `${parsed.message}. ${USAGE}` },\n\t\t\t\t},\n\t\t\t\tnull,\n\t\t\t\t2,\n\t\t\t),\n\t\t);\n\t\treturn { handled: true, exitCode: 2 };\n\t}\n\n\tconst { providerId, level, modelId, timeoutMs, probeModelId } = parsed.parsed;\n\tconst transport = overrides.transport ?? (level >= 1 ? createProviderDoctorTransport() : undefined);\n\tconst agentDir = overrides.agentDir ?? getAgentDir();\n\tconst resolveAuthHeaders =\n\t\tlevel === 2 ? (overrides.resolveAuthHeaders ?? createProductionAuthHeaderResolver(agentDir)) : undefined;\n\ttry {\n\t\tconst result = await diagnoseProvider(providerId, {\n\t\t\tlevel,\n\t\t\tmodelId,\n\t\t\tprobeModelId,\n\t\t\ttimeoutMs,\n\t\t\tagentDir,\n\t\t\tkimiConfigPath: overrides.kimiConfigPath,\n\t\t\ttransport,\n\t\t\tresolveAuthHeaders,\n\t\t});\n\t\twriteLine(JSON.stringify(result, null, 2));\n\t\treturn { handled: true, exitCode: result.status === \"ok\" ? 0 : 1, result };\n\t} catch {\n\t\t// diagnoseProvider maps every failure internally; this is fail-closed insurance\n\t\t// with a fixed message so nothing sensitive can surface.\n\t\twriteLine(\n\t\t\tJSON.stringify(\n\t\t\t\t{\n\t\t\t\t\tstatus: \"fail\",\n\t\t\t\t\terror: { category: \"unknown\", code: \"unexpected-response\", message: \"Provider doctor failed\" },\n\t\t\t\t},\n\t\t\t\tnull,\n\t\t\t\t2,\n\t\t\t),\n\t\t);\n\t\treturn { handled: true, exitCode: 1 };\n\t}\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-party Level-1/Level-2 transport for the provider doctor.
|
|
3
|
+
*
|
|
4
|
+
* Safety properties:
|
|
5
|
+
* - GET, plus bounded JSON POST for the opt-in Level-2 model probe; redirects are
|
|
6
|
+
* never followed (the raw 3xx status is returned).
|
|
7
|
+
* - POST bodies are capped, must be JSON, and are never echoed anywhere.
|
|
8
|
+
* - The response is dropped as soon as the status line arrives: no body, headers,
|
|
9
|
+
* or error details ever leave this module, and thrown errors use fixed messages.
|
|
10
|
+
* - Loopback-only policies require every resolved address to be loopback.
|
|
11
|
+
* - Public policies validate every resolved address against the doctor blocklist and
|
|
12
|
+
* pin the socket to one validated address; the hostname is never re-resolved
|
|
13
|
+
* (DNS-rebinding safe) and TLS identity is still checked against the original hostname.
|
|
14
|
+
*/
|
|
15
|
+
import { type Socket } from "node:net";
|
|
16
|
+
import { type ProviderDoctorTransport } from "./doctor-provider.ts";
|
|
17
|
+
/** Resolves a hostname to every address it maps to. */
|
|
18
|
+
export type ProviderDoctorLookup = (hostname: string) => Promise<readonly string[]>;
|
|
19
|
+
/** The validated connection target handed to the test-only socket seam. */
|
|
20
|
+
export interface ProviderDoctorPinnedConnection {
|
|
21
|
+
address: string;
|
|
22
|
+
port: number;
|
|
23
|
+
hostname: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ProviderDoctorTransportOptions {
|
|
26
|
+
/** DNS resolver returning every address for a hostname. Defaults to node:dns lookup({ all: true }). */
|
|
27
|
+
lookup?: ProviderDoctorLookup;
|
|
28
|
+
/**
|
|
29
|
+
* Test-only seam (plain http) that builds the socket for a validated, pinned address.
|
|
30
|
+
* Production requests always dial the pinned address directly.
|
|
31
|
+
*/
|
|
32
|
+
createConnection?: (pinned: ProviderDoctorPinnedConnection) => Socket;
|
|
33
|
+
}
|
|
34
|
+
export declare function createProviderDoctorTransport(options?: ProviderDoctorTransportOptions): ProviderDoctorTransport;
|
|
35
|
+
//# sourceMappingURL=doctor-provider-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-provider-transport.d.ts","sourceRoot":"","sources":["../../src/commands/doctor-provider-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,EAAQ,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAE7C,OAAO,EAKN,KAAK,uBAAuB,EAG5B,MAAM,sBAAsB,CAAC;AAE9B,uDAAuD;AACvD,MAAM,MAAM,oBAAoB,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;AAEpF,2EAA2E;AAC3E,MAAM,WAAW,8BAA8B;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,8BAA8B;IAC9C,uGAAuG;IACvG,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,8BAA8B,KAAK,MAAM,CAAC;CACtE;AA2DD,wBAAgB,6BAA6B,CAAC,OAAO,GAAE,8BAAmC,GAAG,uBAAuB,CA+GnH","sourcesContent":["/**\n * First-party Level-1/Level-2 transport for the provider doctor.\n *\n * Safety properties:\n * - GET, plus bounded JSON POST for the opt-in Level-2 model probe; redirects are\n * never followed (the raw 3xx status is returned).\n * - POST bodies are capped, must be JSON, and are never echoed anywhere.\n * - The response is dropped as soon as the status line arrives: no body, headers,\n * or error details ever leave this module, and thrown errors use fixed messages.\n * - Loopback-only policies require every resolved address to be loopback.\n * - Public policies validate every resolved address against the doctor blocklist and\n * pin the socket to one validated address; the hostname is never re-resolved\n * (DNS-rebinding safe) and TLS identity is still checked against the original hostname.\n */\n\nimport { lookup as dnsLookup } from \"node:dns/promises\";\nimport { request as httpRequest, type RequestOptions } from \"node:http\";\nimport { request as httpsRequest } from \"node:https\";\nimport { isIP, type Socket } from \"node:net\";\nimport { checkServerIdentity } from \"node:tls\";\nimport {\n\tisBlockedPublicAddress,\n\tisLoopbackAddress,\n\tnormalizedUrlHostname,\n\tProviderDoctorAuthMaterializationError,\n\ttype ProviderDoctorTransport,\n\ttype ProviderDoctorTransportRequest,\n\ttype ProviderDoctorTransportResponse,\n} from \"./doctor-provider.ts\";\n\n/** Resolves a hostname to every address it maps to. */\nexport type ProviderDoctorLookup = (hostname: string) => Promise<readonly string[]>;\n\n/** The validated connection target handed to the test-only socket seam. */\nexport interface ProviderDoctorPinnedConnection {\n\taddress: string;\n\tport: number;\n\thostname: string;\n}\n\nexport interface ProviderDoctorTransportOptions {\n\t/** DNS resolver returning every address for a hostname. Defaults to node:dns lookup({ all: true }). */\n\tlookup?: ProviderDoctorLookup;\n\t/**\n\t * Test-only seam (plain http) that builds the socket for a validated, pinned address.\n\t * Production requests always dial the pinned address directly.\n\t */\n\tcreateConnection?: (pinned: ProviderDoctorPinnedConnection) => Socket;\n}\n\nconst SCHEME_UNSUPPORTED = \"provider doctor transport rejected an unsupported URL scheme\";\nconst METHOD_UNSUPPORTED = \"provider doctor transport only performs GET and bounded POST requests\";\nconst REQUEST_SHAPE_INVALID = \"provider doctor transport rejected an invalid request shape\";\nconst MAX_POST_BODY_BYTES = 4_096;\nconst RESOLUTION_FAILED = \"provider doctor transport could not resolve the endpoint hostname\";\nconst RESOLUTION_BLOCKED = \"provider doctor transport blocked hostname re-resolution\";\nconst ADDRESS_BLOCKED = \"provider doctor transport blocked a resolved address by policy\";\nconst REQUEST_FAILED = \"provider doctor transport request failed\";\nconst REQUEST_ABORTED = \"provider doctor transport request was aborted\";\nconst STRIPPED_REQUEST_HEADERS = new Set([\n\t\"connection\",\n\t\"content-length\",\n\t\"expect\",\n\t\"host\",\n\t\"keep-alive\",\n\t\"proxy-authenticate\",\n\t\"proxy-authorization\",\n\t\"proxy-connection\",\n\t\"te\",\n\t\"trailer\",\n\t\"transfer-encoding\",\n\t\"upgrade\",\n]);\nconst SINGLE_VALUE_CREDENTIAL_HEADERS = new Set([\"authorization\", \"cf-aig-authorization\"]);\nconst ADDITIONAL_AUTHORIZATION_VALUE = /,\\s*[!#$%&'*+.^_`|~0-9A-Za-z-]+\\s+/;\n\nconst defaultLookup: ProviderDoctorLookup = async (hostname) => {\n\tconst results = await dnsLookup(hostname, { all: true, verbatim: true });\n\treturn results.map((result) => result.address);\n};\n\nasync function resolveAddresses(hostname: string, lookup: ProviderDoctorLookup): Promise<readonly string[]> {\n\tif (isIP(hostname) !== 0) return [hostname];\n\tlet addresses: readonly string[];\n\ttry {\n\t\taddresses = await lookup(hostname);\n\t} catch {\n\t\tthrow new Error(RESOLUTION_FAILED);\n\t}\n\tif (addresses.length === 0) throw new Error(RESOLUTION_FAILED);\n\treturn addresses;\n}\n\nfunction assertRequestActive(signal: AbortSignal): void {\n\tif (signal.aborted) throw new Error(REQUEST_ABORTED);\n}\n\nfunction assertAddressesAllowed(\n\taddresses: readonly string[],\n\tpolicy: ProviderDoctorTransportRequest[\"addressPolicy\"],\n): void {\n\tfor (const address of addresses) {\n\t\tconst allowed = policy.kind === \"loopback-only\" ? isLoopbackAddress(address) : !isBlockedPublicAddress(address);\n\t\tif (!allowed) throw new Error(ADDRESS_BLOCKED);\n\t}\n}\n\nexport function createProviderDoctorTransport(options: ProviderDoctorTransportOptions = {}): ProviderDoctorTransport {\n\tconst lookup = options.lookup ?? defaultLookup;\n\n\treturn {\n\t\tpinsResolvedAddress: true,\n\t\tasync request(request: ProviderDoctorTransportRequest): Promise<ProviderDoctorTransportResponse> {\n\t\t\tassertRequestActive(request.signal);\n\t\t\tconst isPost = request.method === \"POST\";\n\t\t\tif (request.method !== \"GET\" && !isPost) throw new Error(METHOD_UNSUPPORTED);\n\t\t\tif (!isPost && request.body !== undefined) throw new Error(REQUEST_SHAPE_INVALID);\n\t\t\tif (isPost) {\n\t\t\t\tif (typeof request.body !== \"string\" || request.contentType !== \"application/json\") {\n\t\t\t\t\tthrow new Error(REQUEST_SHAPE_INVALID);\n\t\t\t\t}\n\t\t\t\tif (Buffer.byteLength(request.body, \"utf-8\") > MAX_POST_BODY_BYTES) {\n\t\t\t\t\tthrow new Error(REQUEST_SHAPE_INVALID);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst url = request.url;\n\t\t\tconst isHttps = url.protocol === \"https:\";\n\t\t\tif (!isHttps && url.protocol !== \"http:\") throw new Error(SCHEME_UNSUPPORTED);\n\n\t\t\tconst hostname = normalizedUrlHostname(url);\n\t\t\tconst addresses = await resolveAddresses(hostname, lookup);\n\t\t\tassertAddressesAllowed(addresses, request.addressPolicy);\n\t\t\tassertRequestActive(request.signal);\n\t\t\tconst pinned = addresses[0];\n\t\t\tconst port = url.port.length > 0 ? Number(url.port) : isHttps ? 443 : 80;\n\n\t\t\tlet materializedHeaders: Headers;\n\t\t\ttry {\n\t\t\t\tmaterializedHeaders = await request.createHeaders();\n\t\t\t} catch {\n\t\t\t\tthrow new ProviderDoctorAuthMaterializationError();\n\t\t\t}\n\t\t\tassertRequestActive(request.signal);\n\t\t\tconst headers: Record<string, string> = {};\n\t\t\tfor (const [name, value] of materializedHeaders) {\n\t\t\t\tconst normalizedName = name.toLowerCase();\n\t\t\t\tif (STRIPPED_REQUEST_HEADERS.has(normalizedName)) continue;\n\t\t\t\tif (SINGLE_VALUE_CREDENTIAL_HEADERS.has(normalizedName) && ADDITIONAL_AUTHORIZATION_VALUE.test(value)) {\n\t\t\t\t\tthrow new ProviderDoctorAuthMaterializationError();\n\t\t\t\t}\n\t\t\t\theaders[normalizedName] = value;\n\t\t\t}\n\t\t\theaders.host = url.host;\n\t\t\tif (isPost) {\n\t\t\t\theaders[\"content-type\"] = \"application/json\";\n\t\t\t\theaders[\"content-length\"] = String(Buffer.byteLength(request.body as string, \"utf-8\"));\n\t\t\t}\n\n\t\t\tconst base: RequestOptions = {\n\t\t\t\tmethod: request.method,\n\t\t\t\tpath: `${url.pathname}${url.search}`,\n\t\t\t\theaders,\n\t\t\t\tsignal: request.signal,\n\t\t\t\tsetHost: false,\n\t\t\t};\n\t\t\tconst seam = options.createConnection;\n\t\t\tconst requestOptions: RequestOptions = seam\n\t\t\t\t? { ...base, createConnection: () => seam({ address: pinned, port, hostname }) }\n\t\t\t\t: {\n\t\t\t\t\t\t...base,\n\t\t\t\t\t\thost: pinned,\n\t\t\t\t\t\tport,\n\t\t\t\t\t\tagent: false,\n\t\t\t\t\t\t// Fail closed if anything below ever tries to resolve a name again.\n\t\t\t\t\t\tlookup: (_hostname, lookupOptions, callback) => {\n\t\t\t\t\t\t\tconst done = typeof lookupOptions === \"function\" ? lookupOptions : callback;\n\t\t\t\t\t\t\t(done as (error: Error) => void)(new Error(RESOLUTION_BLOCKED));\n\t\t\t\t\t\t},\n\t\t\t\t\t\t...(isHttps\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tservername: isIP(hostname) === 0 ? hostname : \"\",\n\t\t\t\t\t\t\t\t\tcheckServerIdentity: (\n\t\t\t\t\t\t\t\t\t\t_host: string,\n\t\t\t\t\t\t\t\t\t\tcertificate: Parameters<typeof checkServerIdentity>[1],\n\t\t\t\t\t\t\t\t\t) => checkServerIdentity(hostname, certificate),\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t};\n\n\t\t\tassertRequestActive(request.signal);\n\t\t\treturn new Promise<ProviderDoctorTransportResponse>((resolve, reject) => {\n\t\t\t\tlet settled = false;\n\t\t\t\tconst settle = (action: () => void): void => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\taction();\n\t\t\t\t};\n\t\t\t\tif (request.signal.aborted) {\n\t\t\t\t\tsettle(() => reject(new Error(REQUEST_ABORTED)));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst clientRequest = (isHttps ? httpsRequest : httpRequest)(requestOptions, (response) => {\n\t\t\t\t\tconst status = response.statusCode ?? 0;\n\t\t\t\t\t// Drop the response immediately: the doctor only needs the status,\n\t\t\t\t\t// so no body is ever read or buffered (bounded by construction).\n\t\t\t\t\tresponse.destroy();\n\t\t\t\t\tsettle(() => resolve({ status }));\n\t\t\t\t});\n\t\t\t\tclientRequest.on(\"error\", () => settle(() => reject(new Error(REQUEST_FAILED))));\n\t\t\t\tif (request.signal.aborted) {\n\t\t\t\t\tclientRequest.destroy();\n\t\t\t\t\tsettle(() => reject(new Error(REQUEST_ABORTED)));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tclientRequest.end(isPost ? (request.body as string) : undefined);\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-party Level-1/Level-2 transport for the provider doctor.
|
|
3
|
+
*
|
|
4
|
+
* Safety properties:
|
|
5
|
+
* - GET, plus bounded JSON POST for the opt-in Level-2 model probe; redirects are
|
|
6
|
+
* never followed (the raw 3xx status is returned).
|
|
7
|
+
* - POST bodies are capped, must be JSON, and are never echoed anywhere.
|
|
8
|
+
* - The response is dropped as soon as the status line arrives: no body, headers,
|
|
9
|
+
* or error details ever leave this module, and thrown errors use fixed messages.
|
|
10
|
+
* - Loopback-only policies require every resolved address to be loopback.
|
|
11
|
+
* - Public policies validate every resolved address against the doctor blocklist and
|
|
12
|
+
* pin the socket to one validated address; the hostname is never re-resolved
|
|
13
|
+
* (DNS-rebinding safe) and TLS identity is still checked against the original hostname.
|
|
14
|
+
*/
|
|
15
|
+
import { lookup as dnsLookup } from "node:dns/promises";
|
|
16
|
+
import { request as httpRequest } from "node:http";
|
|
17
|
+
import { request as httpsRequest } from "node:https";
|
|
18
|
+
import { isIP } from "node:net";
|
|
19
|
+
import { checkServerIdentity } from "node:tls";
|
|
20
|
+
import { isBlockedPublicAddress, isLoopbackAddress, normalizedUrlHostname, ProviderDoctorAuthMaterializationError, } from "./doctor-provider.js";
|
|
21
|
+
const SCHEME_UNSUPPORTED = "provider doctor transport rejected an unsupported URL scheme";
|
|
22
|
+
const METHOD_UNSUPPORTED = "provider doctor transport only performs GET and bounded POST requests";
|
|
23
|
+
const REQUEST_SHAPE_INVALID = "provider doctor transport rejected an invalid request shape";
|
|
24
|
+
const MAX_POST_BODY_BYTES = 4_096;
|
|
25
|
+
const RESOLUTION_FAILED = "provider doctor transport could not resolve the endpoint hostname";
|
|
26
|
+
const RESOLUTION_BLOCKED = "provider doctor transport blocked hostname re-resolution";
|
|
27
|
+
const ADDRESS_BLOCKED = "provider doctor transport blocked a resolved address by policy";
|
|
28
|
+
const REQUEST_FAILED = "provider doctor transport request failed";
|
|
29
|
+
const REQUEST_ABORTED = "provider doctor transport request was aborted";
|
|
30
|
+
const STRIPPED_REQUEST_HEADERS = new Set([
|
|
31
|
+
"connection",
|
|
32
|
+
"content-length",
|
|
33
|
+
"expect",
|
|
34
|
+
"host",
|
|
35
|
+
"keep-alive",
|
|
36
|
+
"proxy-authenticate",
|
|
37
|
+
"proxy-authorization",
|
|
38
|
+
"proxy-connection",
|
|
39
|
+
"te",
|
|
40
|
+
"trailer",
|
|
41
|
+
"transfer-encoding",
|
|
42
|
+
"upgrade",
|
|
43
|
+
]);
|
|
44
|
+
const SINGLE_VALUE_CREDENTIAL_HEADERS = new Set(["authorization", "cf-aig-authorization"]);
|
|
45
|
+
const ADDITIONAL_AUTHORIZATION_VALUE = /,\s*[!#$%&'*+.^_`|~0-9A-Za-z-]+\s+/;
|
|
46
|
+
const defaultLookup = async (hostname) => {
|
|
47
|
+
const results = await dnsLookup(hostname, { all: true, verbatim: true });
|
|
48
|
+
return results.map((result) => result.address);
|
|
49
|
+
};
|
|
50
|
+
async function resolveAddresses(hostname, lookup) {
|
|
51
|
+
if (isIP(hostname) !== 0)
|
|
52
|
+
return [hostname];
|
|
53
|
+
let addresses;
|
|
54
|
+
try {
|
|
55
|
+
addresses = await lookup(hostname);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
throw new Error(RESOLUTION_FAILED);
|
|
59
|
+
}
|
|
60
|
+
if (addresses.length === 0)
|
|
61
|
+
throw new Error(RESOLUTION_FAILED);
|
|
62
|
+
return addresses;
|
|
63
|
+
}
|
|
64
|
+
function assertRequestActive(signal) {
|
|
65
|
+
if (signal.aborted)
|
|
66
|
+
throw new Error(REQUEST_ABORTED);
|
|
67
|
+
}
|
|
68
|
+
function assertAddressesAllowed(addresses, policy) {
|
|
69
|
+
for (const address of addresses) {
|
|
70
|
+
const allowed = policy.kind === "loopback-only" ? isLoopbackAddress(address) : !isBlockedPublicAddress(address);
|
|
71
|
+
if (!allowed)
|
|
72
|
+
throw new Error(ADDRESS_BLOCKED);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function createProviderDoctorTransport(options = {}) {
|
|
76
|
+
const lookup = options.lookup ?? defaultLookup;
|
|
77
|
+
return {
|
|
78
|
+
pinsResolvedAddress: true,
|
|
79
|
+
async request(request) {
|
|
80
|
+
assertRequestActive(request.signal);
|
|
81
|
+
const isPost = request.method === "POST";
|
|
82
|
+
if (request.method !== "GET" && !isPost)
|
|
83
|
+
throw new Error(METHOD_UNSUPPORTED);
|
|
84
|
+
if (!isPost && request.body !== undefined)
|
|
85
|
+
throw new Error(REQUEST_SHAPE_INVALID);
|
|
86
|
+
if (isPost) {
|
|
87
|
+
if (typeof request.body !== "string" || request.contentType !== "application/json") {
|
|
88
|
+
throw new Error(REQUEST_SHAPE_INVALID);
|
|
89
|
+
}
|
|
90
|
+
if (Buffer.byteLength(request.body, "utf-8") > MAX_POST_BODY_BYTES) {
|
|
91
|
+
throw new Error(REQUEST_SHAPE_INVALID);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const url = request.url;
|
|
95
|
+
const isHttps = url.protocol === "https:";
|
|
96
|
+
if (!isHttps && url.protocol !== "http:")
|
|
97
|
+
throw new Error(SCHEME_UNSUPPORTED);
|
|
98
|
+
const hostname = normalizedUrlHostname(url);
|
|
99
|
+
const addresses = await resolveAddresses(hostname, lookup);
|
|
100
|
+
assertAddressesAllowed(addresses, request.addressPolicy);
|
|
101
|
+
assertRequestActive(request.signal);
|
|
102
|
+
const pinned = addresses[0];
|
|
103
|
+
const port = url.port.length > 0 ? Number(url.port) : isHttps ? 443 : 80;
|
|
104
|
+
let materializedHeaders;
|
|
105
|
+
try {
|
|
106
|
+
materializedHeaders = await request.createHeaders();
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
throw new ProviderDoctorAuthMaterializationError();
|
|
110
|
+
}
|
|
111
|
+
assertRequestActive(request.signal);
|
|
112
|
+
const headers = {};
|
|
113
|
+
for (const [name, value] of materializedHeaders) {
|
|
114
|
+
const normalizedName = name.toLowerCase();
|
|
115
|
+
if (STRIPPED_REQUEST_HEADERS.has(normalizedName))
|
|
116
|
+
continue;
|
|
117
|
+
if (SINGLE_VALUE_CREDENTIAL_HEADERS.has(normalizedName) && ADDITIONAL_AUTHORIZATION_VALUE.test(value)) {
|
|
118
|
+
throw new ProviderDoctorAuthMaterializationError();
|
|
119
|
+
}
|
|
120
|
+
headers[normalizedName] = value;
|
|
121
|
+
}
|
|
122
|
+
headers.host = url.host;
|
|
123
|
+
if (isPost) {
|
|
124
|
+
headers["content-type"] = "application/json";
|
|
125
|
+
headers["content-length"] = String(Buffer.byteLength(request.body, "utf-8"));
|
|
126
|
+
}
|
|
127
|
+
const base = {
|
|
128
|
+
method: request.method,
|
|
129
|
+
path: `${url.pathname}${url.search}`,
|
|
130
|
+
headers,
|
|
131
|
+
signal: request.signal,
|
|
132
|
+
setHost: false,
|
|
133
|
+
};
|
|
134
|
+
const seam = options.createConnection;
|
|
135
|
+
const requestOptions = seam
|
|
136
|
+
? { ...base, createConnection: () => seam({ address: pinned, port, hostname }) }
|
|
137
|
+
: {
|
|
138
|
+
...base,
|
|
139
|
+
host: pinned,
|
|
140
|
+
port,
|
|
141
|
+
agent: false,
|
|
142
|
+
// Fail closed if anything below ever tries to resolve a name again.
|
|
143
|
+
lookup: (_hostname, lookupOptions, callback) => {
|
|
144
|
+
const done = typeof lookupOptions === "function" ? lookupOptions : callback;
|
|
145
|
+
done(new Error(RESOLUTION_BLOCKED));
|
|
146
|
+
},
|
|
147
|
+
...(isHttps
|
|
148
|
+
? {
|
|
149
|
+
servername: isIP(hostname) === 0 ? hostname : "",
|
|
150
|
+
checkServerIdentity: (_host, certificate) => checkServerIdentity(hostname, certificate),
|
|
151
|
+
}
|
|
152
|
+
: {}),
|
|
153
|
+
};
|
|
154
|
+
assertRequestActive(request.signal);
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
let settled = false;
|
|
157
|
+
const settle = (action) => {
|
|
158
|
+
if (settled)
|
|
159
|
+
return;
|
|
160
|
+
settled = true;
|
|
161
|
+
action();
|
|
162
|
+
};
|
|
163
|
+
if (request.signal.aborted) {
|
|
164
|
+
settle(() => reject(new Error(REQUEST_ABORTED)));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const clientRequest = (isHttps ? httpsRequest : httpRequest)(requestOptions, (response) => {
|
|
168
|
+
const status = response.statusCode ?? 0;
|
|
169
|
+
// Drop the response immediately: the doctor only needs the status,
|
|
170
|
+
// so no body is ever read or buffered (bounded by construction).
|
|
171
|
+
response.destroy();
|
|
172
|
+
settle(() => resolve({ status }));
|
|
173
|
+
});
|
|
174
|
+
clientRequest.on("error", () => settle(() => reject(new Error(REQUEST_FAILED))));
|
|
175
|
+
if (request.signal.aborted) {
|
|
176
|
+
clientRequest.destroy();
|
|
177
|
+
settle(() => reject(new Error(REQUEST_ABORTED)));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
clientRequest.end(isPost ? request.body : undefined);
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=doctor-provider-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-provider-transport.js","sourceRoot":"","sources":["../../src/commands/doctor-provider-transport.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * First-party Level-1/Level-2 transport for the provider doctor.\n *\n * Safety properties:\n * - GET, plus bounded JSON POST for the opt-in Level-2 model probe; redirects are\n * never followed (the raw 3xx status is returned).\n * - POST bodies are capped, must be JSON, and are never echoed anywhere.\n * - The response is dropped as soon as the status line arrives: no body, headers,\n * or error details ever leave this module, and thrown errors use fixed messages.\n * - Loopback-only policies require every resolved address to be loopback.\n * - Public policies validate every resolved address against the doctor blocklist and\n * pin the socket to one validated address; the hostname is never re-resolved\n * (DNS-rebinding safe) and TLS identity is still checked against the original hostname.\n */\n\nimport { lookup as dnsLookup } from \"node:dns/promises\";\nimport { request as httpRequest, type RequestOptions } from \"node:http\";\nimport { request as httpsRequest } from \"node:https\";\nimport { isIP, type Socket } from \"node:net\";\nimport { checkServerIdentity } from \"node:tls\";\nimport {\n\tisBlockedPublicAddress,\n\tisLoopbackAddress,\n\tnormalizedUrlHostname,\n\tProviderDoctorAuthMaterializationError,\n\ttype ProviderDoctorTransport,\n\ttype ProviderDoctorTransportRequest,\n\ttype ProviderDoctorTransportResponse,\n} from \"./doctor-provider.ts\";\n\n/** Resolves a hostname to every address it maps to. */\nexport type ProviderDoctorLookup = (hostname: string) => Promise<readonly string[]>;\n\n/** The validated connection target handed to the test-only socket seam. */\nexport interface ProviderDoctorPinnedConnection {\n\taddress: string;\n\tport: number;\n\thostname: string;\n}\n\nexport interface ProviderDoctorTransportOptions {\n\t/** DNS resolver returning every address for a hostname. Defaults to node:dns lookup({ all: true }). */\n\tlookup?: ProviderDoctorLookup;\n\t/**\n\t * Test-only seam (plain http) that builds the socket for a validated, pinned address.\n\t * Production requests always dial the pinned address directly.\n\t */\n\tcreateConnection?: (pinned: ProviderDoctorPinnedConnection) => Socket;\n}\n\nconst SCHEME_UNSUPPORTED = \"provider doctor transport rejected an unsupported URL scheme\";\nconst METHOD_UNSUPPORTED = \"provider doctor transport only performs GET and bounded POST requests\";\nconst REQUEST_SHAPE_INVALID = \"provider doctor transport rejected an invalid request shape\";\nconst MAX_POST_BODY_BYTES = 4_096;\nconst RESOLUTION_FAILED = \"provider doctor transport could not resolve the endpoint hostname\";\nconst RESOLUTION_BLOCKED = \"provider doctor transport blocked hostname re-resolution\";\nconst ADDRESS_BLOCKED = \"provider doctor transport blocked a resolved address by policy\";\nconst REQUEST_FAILED = \"provider doctor transport request failed\";\nconst REQUEST_ABORTED = \"provider doctor transport request was aborted\";\nconst STRIPPED_REQUEST_HEADERS = new Set([\n\t\"connection\",\n\t\"content-length\",\n\t\"expect\",\n\t\"host\",\n\t\"keep-alive\",\n\t\"proxy-authenticate\",\n\t\"proxy-authorization\",\n\t\"proxy-connection\",\n\t\"te\",\n\t\"trailer\",\n\t\"transfer-encoding\",\n\t\"upgrade\",\n]);\nconst SINGLE_VALUE_CREDENTIAL_HEADERS = new Set([\"authorization\", \"cf-aig-authorization\"]);\nconst ADDITIONAL_AUTHORIZATION_VALUE = /,\\s*[!#$%&'*+.^_`|~0-9A-Za-z-]+\\s+/;\n\nconst defaultLookup: ProviderDoctorLookup = async (hostname) => {\n\tconst results = await dnsLookup(hostname, { all: true, verbatim: true });\n\treturn results.map((result) => result.address);\n};\n\nasync function resolveAddresses(hostname: string, lookup: ProviderDoctorLookup): Promise<readonly string[]> {\n\tif (isIP(hostname) !== 0) return [hostname];\n\tlet addresses: readonly string[];\n\ttry {\n\t\taddresses = await lookup(hostname);\n\t} catch {\n\t\tthrow new Error(RESOLUTION_FAILED);\n\t}\n\tif (addresses.length === 0) throw new Error(RESOLUTION_FAILED);\n\treturn addresses;\n}\n\nfunction assertRequestActive(signal: AbortSignal): void {\n\tif (signal.aborted) throw new Error(REQUEST_ABORTED);\n}\n\nfunction assertAddressesAllowed(\n\taddresses: readonly string[],\n\tpolicy: ProviderDoctorTransportRequest[\"addressPolicy\"],\n): void {\n\tfor (const address of addresses) {\n\t\tconst allowed = policy.kind === \"loopback-only\" ? isLoopbackAddress(address) : !isBlockedPublicAddress(address);\n\t\tif (!allowed) throw new Error(ADDRESS_BLOCKED);\n\t}\n}\n\nexport function createProviderDoctorTransport(options: ProviderDoctorTransportOptions = {}): ProviderDoctorTransport {\n\tconst lookup = options.lookup ?? defaultLookup;\n\n\treturn {\n\t\tpinsResolvedAddress: true,\n\t\tasync request(request: ProviderDoctorTransportRequest): Promise<ProviderDoctorTransportResponse> {\n\t\t\tassertRequestActive(request.signal);\n\t\t\tconst isPost = request.method === \"POST\";\n\t\t\tif (request.method !== \"GET\" && !isPost) throw new Error(METHOD_UNSUPPORTED);\n\t\t\tif (!isPost && request.body !== undefined) throw new Error(REQUEST_SHAPE_INVALID);\n\t\t\tif (isPost) {\n\t\t\t\tif (typeof request.body !== \"string\" || request.contentType !== \"application/json\") {\n\t\t\t\t\tthrow new Error(REQUEST_SHAPE_INVALID);\n\t\t\t\t}\n\t\t\t\tif (Buffer.byteLength(request.body, \"utf-8\") > MAX_POST_BODY_BYTES) {\n\t\t\t\t\tthrow new Error(REQUEST_SHAPE_INVALID);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst url = request.url;\n\t\t\tconst isHttps = url.protocol === \"https:\";\n\t\t\tif (!isHttps && url.protocol !== \"http:\") throw new Error(SCHEME_UNSUPPORTED);\n\n\t\t\tconst hostname = normalizedUrlHostname(url);\n\t\t\tconst addresses = await resolveAddresses(hostname, lookup);\n\t\t\tassertAddressesAllowed(addresses, request.addressPolicy);\n\t\t\tassertRequestActive(request.signal);\n\t\t\tconst pinned = addresses[0];\n\t\t\tconst port = url.port.length > 0 ? Number(url.port) : isHttps ? 443 : 80;\n\n\t\t\tlet materializedHeaders: Headers;\n\t\t\ttry {\n\t\t\t\tmaterializedHeaders = await request.createHeaders();\n\t\t\t} catch {\n\t\t\t\tthrow new ProviderDoctorAuthMaterializationError();\n\t\t\t}\n\t\t\tassertRequestActive(request.signal);\n\t\t\tconst headers: Record<string, string> = {};\n\t\t\tfor (const [name, value] of materializedHeaders) {\n\t\t\t\tconst normalizedName = name.toLowerCase();\n\t\t\t\tif (STRIPPED_REQUEST_HEADERS.has(normalizedName)) continue;\n\t\t\t\tif (SINGLE_VALUE_CREDENTIAL_HEADERS.has(normalizedName) && ADDITIONAL_AUTHORIZATION_VALUE.test(value)) {\n\t\t\t\t\tthrow new ProviderDoctorAuthMaterializationError();\n\t\t\t\t}\n\t\t\t\theaders[normalizedName] = value;\n\t\t\t}\n\t\t\theaders.host = url.host;\n\t\t\tif (isPost) {\n\t\t\t\theaders[\"content-type\"] = \"application/json\";\n\t\t\t\theaders[\"content-length\"] = String(Buffer.byteLength(request.body as string, \"utf-8\"));\n\t\t\t}\n\n\t\t\tconst base: RequestOptions = {\n\t\t\t\tmethod: request.method,\n\t\t\t\tpath: `${url.pathname}${url.search}`,\n\t\t\t\theaders,\n\t\t\t\tsignal: request.signal,\n\t\t\t\tsetHost: false,\n\t\t\t};\n\t\t\tconst seam = options.createConnection;\n\t\t\tconst requestOptions: RequestOptions = seam\n\t\t\t\t? { ...base, createConnection: () => seam({ address: pinned, port, hostname }) }\n\t\t\t\t: {\n\t\t\t\t\t\t...base,\n\t\t\t\t\t\thost: pinned,\n\t\t\t\t\t\tport,\n\t\t\t\t\t\tagent: false,\n\t\t\t\t\t\t// Fail closed if anything below ever tries to resolve a name again.\n\t\t\t\t\t\tlookup: (_hostname, lookupOptions, callback) => {\n\t\t\t\t\t\t\tconst done = typeof lookupOptions === \"function\" ? lookupOptions : callback;\n\t\t\t\t\t\t\t(done as (error: Error) => void)(new Error(RESOLUTION_BLOCKED));\n\t\t\t\t\t\t},\n\t\t\t\t\t\t...(isHttps\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tservername: isIP(hostname) === 0 ? hostname : \"\",\n\t\t\t\t\t\t\t\t\tcheckServerIdentity: (\n\t\t\t\t\t\t\t\t\t\t_host: string,\n\t\t\t\t\t\t\t\t\t\tcertificate: Parameters<typeof checkServerIdentity>[1],\n\t\t\t\t\t\t\t\t\t) => checkServerIdentity(hostname, certificate),\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t};\n\n\t\t\tassertRequestActive(request.signal);\n\t\t\treturn new Promise<ProviderDoctorTransportResponse>((resolve, reject) => {\n\t\t\t\tlet settled = false;\n\t\t\t\tconst settle = (action: () => void): void => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\taction();\n\t\t\t\t};\n\t\t\t\tif (request.signal.aborted) {\n\t\t\t\t\tsettle(() => reject(new Error(REQUEST_ABORTED)));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst clientRequest = (isHttps ? httpsRequest : httpRequest)(requestOptions, (response) => {\n\t\t\t\t\tconst status = response.statusCode ?? 0;\n\t\t\t\t\t// Drop the response immediately: the doctor only needs the status,\n\t\t\t\t\t// so no body is ever read or buffered (bounded by construction).\n\t\t\t\t\tresponse.destroy();\n\t\t\t\t\tsettle(() => resolve({ status }));\n\t\t\t\t});\n\t\t\t\tclientRequest.on(\"error\", () => settle(() => reject(new Error(REQUEST_FAILED))));\n\t\t\t\tif (request.signal.aborted) {\n\t\t\t\t\tclientRequest.destroy();\n\t\t\t\t\tsettle(() => reject(new Error(REQUEST_ABORTED)));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tclientRequest.end(isPost ? (request.body as string) : undefined);\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
|
|
@@ -1,19 +1,133 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type ProviderOrigin = "native" | "custom-openai-compatible" | "local-proxy" | "unknown";
|
|
2
|
+
export type ProviderDoctorLevel = 0 | 1 | 2;
|
|
3
|
+
export type ProviderDoctorCheckStatus = "ok" | "fail" | "skipped" | "unsupported";
|
|
4
|
+
export type DoctorErrorCategory = "network" | "auth" | "model" | "config" | "server" | "unknown";
|
|
5
|
+
/** Exact endpoint-probe category model from the v0.90.9 design (§10.4). */
|
|
6
|
+
export type EndpointProbeCategory = "ok" | "network" | "auth" | "unsupported-endpoint" | "server";
|
|
7
|
+
export type EndpointProbeResult = {
|
|
8
|
+
reachable: boolean;
|
|
9
|
+
authenticated?: boolean;
|
|
10
|
+
modelsSupported?: boolean;
|
|
11
|
+
status?: number;
|
|
12
|
+
category: EndpointProbeCategory;
|
|
13
|
+
};
|
|
14
|
+
export type ProviderDoctorCode = "ok" | "provider-not-found" | "models-config-invalid" | "auth-config-invalid" | "kimi-config-invalid" | "unsupported-level" | "model-provider-mismatch" | "api-missing" | "api-unsupported" | "base-url-missing" | "base-url-invalid" | "url-scheme-unsupported" | "origin-unsupported" | "address-policy-blocked" | "auth-missing" | "auth-materialization-failed" | "transport-required" | "address-pinning-required" | "redirect-blocked" | "authentication-failed" | "endpoint-unsupported" | "request-aborted" | "request-timeout" | "network-failure" | "server-error" | "probe-model-required" | "probe-model-conflict" | "unexpected-response";
|
|
15
|
+
export interface ProviderDoctorEndpoint {
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
api?: string;
|
|
18
|
+
modelIds?: readonly string[];
|
|
19
|
+
modelId?: string;
|
|
20
|
+
}
|
|
21
|
+
export type ProviderDoctorHeaderFactory = () => Headers | Promise<Headers>;
|
|
22
|
+
export type ProviderDoctorAuthHeaderResolver = (target: ResolvedProviderTarget) => Headers | Promise<Headers>;
|
|
23
|
+
/** Fixed-message marker used to classify auth resolution without exposing its cause. */
|
|
24
|
+
export declare class ProviderDoctorAuthMaterializationError extends Error {
|
|
25
|
+
constructor();
|
|
26
|
+
}
|
|
27
|
+
export interface ProviderDoctorCredentialBinding {
|
|
28
|
+
origin: ProviderOrigin;
|
|
29
|
+
source: string;
|
|
30
|
+
baseUrl: string;
|
|
31
|
+
api: string;
|
|
32
|
+
modelId: string;
|
|
33
|
+
}
|
|
34
|
+
export interface ProviderDoctorAuth {
|
|
35
|
+
present: boolean;
|
|
36
|
+
source?: string;
|
|
37
|
+
/** Materialized by an injected transport at request time only. */
|
|
38
|
+
createHeaders?: ProviderDoctorHeaderFactory;
|
|
39
|
+
/** Exact endpoint provenance that authorizes createHeaders. */
|
|
40
|
+
binding?: ProviderDoctorCredentialBinding;
|
|
41
|
+
}
|
|
42
|
+
export interface ResolvedProviderTarget {
|
|
43
|
+
providerId: string;
|
|
44
|
+
origin: ProviderOrigin;
|
|
45
|
+
source: string;
|
|
46
|
+
endpoint: ProviderDoctorEndpoint;
|
|
47
|
+
auth: ProviderDoctorAuth;
|
|
48
|
+
}
|
|
49
|
+
/** Compatibility name for callers using the design-document terminology. */
|
|
50
|
+
export type ResolvedProviderConfig = ResolvedProviderTarget;
|
|
51
|
+
export type ProviderDoctorAddressPolicy = {
|
|
52
|
+
kind: "public";
|
|
53
|
+
requireAddressPinning: true;
|
|
54
|
+
} | {
|
|
55
|
+
kind: "loopback-only";
|
|
56
|
+
requireAddressPinning: false;
|
|
57
|
+
};
|
|
58
|
+
export interface ProviderDoctorTransportRequest {
|
|
59
|
+
url: URL;
|
|
60
|
+
method: "GET" | "POST";
|
|
61
|
+
redirect: "manual";
|
|
62
|
+
signal: AbortSignal;
|
|
63
|
+
addressPolicy: ProviderDoctorAddressPolicy;
|
|
64
|
+
createHeaders: ProviderDoctorHeaderFactory;
|
|
65
|
+
/** Bounded JSON payload; present only for opt-in Level-2 POST model probes. */
|
|
66
|
+
body?: string;
|
|
67
|
+
contentType?: "application/json";
|
|
68
|
+
}
|
|
69
|
+
export interface ProviderDoctorTransportResponse {
|
|
70
|
+
status: number;
|
|
71
|
+
}
|
|
72
|
+
export interface ProviderDoctorTransport {
|
|
73
|
+
/** Public requests are rejected unless the transport enforces DNS/address pinning. */
|
|
74
|
+
pinsResolvedAddress?: boolean;
|
|
75
|
+
request(request: ProviderDoctorTransportRequest): Promise<ProviderDoctorTransportResponse>;
|
|
76
|
+
}
|
|
77
|
+
export interface ProviderDoctorDependencies {
|
|
78
|
+
transport?: ProviderDoctorTransport;
|
|
79
|
+
/** Level-2 only. Called by the transport immediately before each request is dispatched. */
|
|
80
|
+
resolveAuthHeaders?: ProviderDoctorAuthHeaderResolver;
|
|
81
|
+
}
|
|
82
|
+
export interface ProviderDoctorOptions extends ProviderDoctorDependencies {
|
|
83
|
+
level?: ProviderDoctorLevel;
|
|
84
|
+
modelId?: string;
|
|
85
|
+
/** Level-2 opt-in only: the model probed with one minimal-token generative request. */
|
|
86
|
+
probeModelId?: string;
|
|
87
|
+
timeoutMs?: number;
|
|
88
|
+
signal?: AbortSignal;
|
|
89
|
+
agentDir?: string;
|
|
90
|
+
/** Path to the Kimi CLI config TOML consulted for the kimi-coding provider. Defaults to ~/.kimi/config.toml. */
|
|
91
|
+
kimiConfigPath?: string;
|
|
92
|
+
}
|
|
93
|
+
export interface ProviderDoctorCheck {
|
|
94
|
+
name: string;
|
|
95
|
+
status: ProviderDoctorCheckStatus;
|
|
96
|
+
code: ProviderDoctorCode;
|
|
97
|
+
category?: DoctorErrorCategory;
|
|
98
|
+
message?: string;
|
|
99
|
+
/** Exact-category endpoint classification for network probe checks. */
|
|
100
|
+
probe?: EndpointProbeResult;
|
|
101
|
+
}
|
|
2
102
|
export interface ProviderDoctorResult {
|
|
3
103
|
provider: string;
|
|
4
104
|
status: "ok" | "fail";
|
|
105
|
+
level: ProviderDoctorLevel;
|
|
106
|
+
origin: ProviderOrigin;
|
|
107
|
+
source: string;
|
|
108
|
+
/** Sanitized URL: userinfo, query, and fragment are always removed. */
|
|
109
|
+
targetUrl?: string;
|
|
110
|
+
/** Backwards-compatible alias of targetUrl. */
|
|
5
111
|
baseUrl?: string;
|
|
112
|
+
api?: string;
|
|
113
|
+
modelId?: string;
|
|
114
|
+
authPresent: boolean;
|
|
115
|
+
/** True only when an opt-in Level-2 model probe was dispatched; the request may incur provider costs. */
|
|
116
|
+
costWarning?: boolean;
|
|
6
117
|
checks: ProviderDoctorCheck[];
|
|
7
118
|
error?: {
|
|
8
119
|
category: DoctorErrorCategory;
|
|
120
|
+
code: ProviderDoctorCode;
|
|
9
121
|
message: string;
|
|
10
122
|
};
|
|
11
123
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
124
|
+
/** True when the literal IP address is a loopback address. Non-IP input is never loopback. */
|
|
125
|
+
export declare function isLoopbackAddress(address: string): boolean;
|
|
126
|
+
/** True when the literal IP address must not be dialed for a public origin. Non-IP input fails closed. */
|
|
127
|
+
export declare function isBlockedPublicAddress(address: string): boolean;
|
|
17
128
|
export declare function getAgentDir(): string;
|
|
18
|
-
export declare function
|
|
129
|
+
export declare function normalizedUrlHostname(url: URL): string;
|
|
130
|
+
export declare function canonicalProviderEndpointUrl(rawUrl: string | undefined): string | undefined;
|
|
131
|
+
export declare function diagnoseResolvedProvider(target: ResolvedProviderTarget, options?: ProviderDoctorOptions, dependencies?: ProviderDoctorDependencies): Promise<ProviderDoctorResult>;
|
|
132
|
+
export declare function diagnoseProvider(providerId: string, options?: ProviderDoctorOptions): Promise<ProviderDoctorResult>;
|
|
19
133
|
//# sourceMappingURL=doctor-provider.d.ts.map
|