onto-mcp 0.3.2 → 0.4.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/.onto/processes/reconstruct/actionable-ontology-seed-recomposition-design.md +447 -0
- package/.onto/processes/reconstruct/foundry-style-ontology-seed-contract.md +934 -0
- package/.onto/processes/reconstruct/reconstruct-boundary-contract.md +303 -725
- package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +1645 -0
- package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +26 -22
- package/.onto/processes/reconstruct/source-profile-contract.md +49 -23
- package/.onto/processes/reconstruct/source-profiles/code.md +6 -3
- package/.onto/processes/reconstruct/source-profiles/database.md +5 -2
- package/.onto/processes/reconstruct/source-profiles/document.md +5 -2
- package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +5 -4
- package/.onto/processes/review/review-execution-ux-contract.md +40 -0
- package/.onto/processes/shared/pipeline-execution-ledger-contract.md +26 -10
- package/.onto/processes/shared/target-material-kind-contract.md +29 -16
- package/AGENTS.md +6 -4
- package/README.md +149 -76
- package/dist/cli.js +8 -8
- package/dist/core-api/reconstruct-api.js +117 -31
- package/dist/core-api/review-api.js +47 -0
- package/dist/core-runtime/cli/codex-review-unit-executor.js +39 -2
- package/dist/core-runtime/cli/complete-review-session.js +2 -2
- package/dist/core-runtime/cli/mock-review-unit-executor.js +1 -1
- package/dist/core-runtime/cli/review-invoke.js +9 -9
- package/dist/core-runtime/cli/run-review-prompt-execution.js +39 -5
- package/dist/core-runtime/cli/spawn-watcher.js +266 -47
- package/dist/core-runtime/cli/start-review-session.js +3 -3
- package/dist/core-runtime/llm/llm-caller.js +11 -0
- package/dist/core-runtime/llm/llm-tool-loop.js +2 -0
- package/dist/core-runtime/observability/runtime-stream-observation.js +118 -0
- package/dist/core-runtime/onboard/cli-host.js +174 -0
- package/dist/core-runtime/onboard/host-target.js +22 -0
- package/dist/core-runtime/onboard/json-config-host.js +122 -0
- package/dist/core-runtime/onboard/path-scan.js +26 -0
- package/dist/core-runtime/onboard/prompt.js +51 -0
- package/dist/core-runtime/onboard/register.js +214 -0
- package/dist/core-runtime/onboard/types.js +27 -0
- package/dist/core-runtime/reconstruct/actionable-seed-validation.js +1777 -0
- package/dist/core-runtime/reconstruct/artifact-types.js +10 -4
- package/dist/core-runtime/reconstruct/contract-registry.js +623 -0
- package/dist/core-runtime/reconstruct/domain-id.js +10 -0
- package/dist/core-runtime/reconstruct/governing-snapshot.js +716 -0
- package/dist/core-runtime/reconstruct/material-profile-validation.js +191 -0
- package/dist/core-runtime/reconstruct/materialize-preparation.js +49 -11
- package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +269 -79
- package/dist/core-runtime/reconstruct/post-seed-validation.js +1194 -51
- package/dist/core-runtime/reconstruct/record.js +104 -20
- package/dist/core-runtime/reconstruct/run.js +2107 -413
- package/dist/core-runtime/reconstruct/seed-claim-projections.js +268 -0
- package/dist/core-runtime/reconstruct/source-profiles.js +93 -4
- package/dist/core-runtime/reconstruct/terminal-validation.js +807 -0
- package/dist/core-runtime/review/review-invocation-runner.js +4 -4
- package/dist/mcp/server.js +110 -38
- package/dist/mcp/tool-schemas.js +20 -6
- package/package.json +8 -17
- package/scripts/onto-review-watch.sh +486 -0
- package/scripts/onto-runtime-watch.sh +122 -0
- package/scripts/postinstall-hint.js +22 -0
- package/.onto/processes/reconstruct/top-level-concept-discovery-contract.md +0 -387
- package/dist/core-runtime/cli/bootstrap-review-binding.js +0 -186
- package/dist/core-runtime/cli/codex-nested-dispatch.test.js +0 -390
- package/dist/core-runtime/cli/codex-nested-teamlead-executor.test.js +0 -335
- package/dist/core-runtime/cli/coordinator-helpers.js +0 -583
- package/dist/core-runtime/cli/coordinator-state-machine-deliberation.test.js +0 -167
- package/dist/core-runtime/cli/coordinator-state-machine.js +0 -794
- package/dist/core-runtime/cli/e2e-codex-multi-agent-fixes.test.js +0 -615
- package/dist/core-runtime/cli/e2e-start-review-session.test.js +0 -312
- package/dist/core-runtime/cli/health.js +0 -44
- package/dist/core-runtime/cli/inline-http-review-unit-executor.test.js +0 -567
- package/dist/core-runtime/cli/materialize-review-execution-preparation.js +0 -104
- package/dist/core-runtime/cli/migrate-session-roots.js +0 -118
- package/dist/core-runtime/cli/repo-layout-migration-replace.smoke.test.js +0 -106
- package/dist/core-runtime/cli/review-invoke-auto-resolution.test.js +0 -268
- package/dist/core-runtime/cli/review-invoke-coordinator-topology.test.js +0 -136
- package/dist/core-runtime/cli/review-invoke-resolver-caching.test.js +0 -201
- package/dist/core-runtime/cli/review-invoke-topology-dispatch.test.js +0 -192
- package/dist/core-runtime/cli/session-root-guard.js +0 -168
- package/dist/core-runtime/cli/spawn-watcher.test.js +0 -457
- package/dist/core-runtime/cli/strip-wrapping-code-fence.test.js +0 -79
- package/dist/core-runtime/cli/teamcreate-lens-deliberation-executor.js +0 -412
- package/dist/core-runtime/cli/teamcreate-lens-deliberation-executor.test.js +0 -351
- package/dist/core-runtime/cli/topology-executor-mapping.js +0 -139
- package/dist/core-runtime/cli/topology-executor-mapping.test.js +0 -173
- package/dist/core-runtime/cli/write-review-interpretation.js +0 -81
- package/dist/core-runtime/config/onto-config-cli.js +0 -278
- package/dist/core-runtime/config/onto-config-key-path.js +0 -288
- package/dist/core-runtime/config/onto-config-key-path.test.js +0 -195
- package/dist/core-runtime/config/onto-config-preview.js +0 -108
- package/dist/core-runtime/config/onto-config-preview.test.js +0 -132
- package/dist/core-runtime/discovery/config-chain.js +0 -118
- package/dist/core-runtime/discovery/config-chain.test.js +0 -103
- package/dist/core-runtime/discovery/config-profile.js +0 -199
- package/dist/core-runtime/discovery/config-profile.test.js +0 -233
- package/dist/core-runtime/discovery/host-detection.test.js +0 -186
- package/dist/core-runtime/discovery/installation-paths.test.js +0 -65
- package/dist/core-runtime/discovery/lens-registry.test.js +0 -81
- package/dist/core-runtime/discovery/path-normalization.test.js +0 -22
- package/dist/core-runtime/discovery/plugin-path.js +0 -72
- package/dist/core-runtime/discovery/plugin-path.test.js +0 -95
- package/dist/core-runtime/evolve/adapters/code-product/compile/compile-defense.js +0 -344
- package/dist/core-runtime/evolve/adapters/code-product/compile/compile-defense.test.js +0 -915
- package/dist/core-runtime/evolve/adapters/code-product/compile/compile.js +0 -564
- package/dist/core-runtime/evolve/adapters/code-product/compile/compile.test.js +0 -708
- package/dist/core-runtime/evolve/adapters/code-product/parsers/brief-parser.js +0 -165
- package/dist/core-runtime/evolve/adapters/code-product/parsers/brief-parser.test.js +0 -227
- package/dist/core-runtime/evolve/adapters/code-product/validators/validate.js +0 -59
- package/dist/core-runtime/evolve/adapters/code-product/validators/validate.test.js +0 -205
- package/dist/core-runtime/evolve/adapters/methodology/adapter.js +0 -16
- package/dist/core-runtime/evolve/adapters/methodology/adapter.test.js +0 -9
- package/dist/core-runtime/evolve/adapters/methodology/perspectives/authority-consistency.js +0 -298
- package/dist/core-runtime/evolve/adapters/methodology/perspectives/authority-consistency.test.js +0 -70
- package/dist/core-runtime/evolve/adapters/methodology/scope-types/process.js +0 -46
- package/dist/core-runtime/evolve/adapters/methodology/scope-types/process.test.js +0 -73
- package/dist/core-runtime/evolve/adapters/registry.js +0 -47
- package/dist/core-runtime/evolve/adapters/registry.test.js +0 -67
- package/dist/core-runtime/evolve/cli.js +0 -256
- package/dist/core-runtime/evolve/commands/align.js +0 -194
- package/dist/core-runtime/evolve/commands/align.test.js +0 -82
- package/dist/core-runtime/evolve/commands/apply.js +0 -161
- package/dist/core-runtime/evolve/commands/apply.test.js +0 -138
- package/dist/core-runtime/evolve/commands/close.js +0 -39
- package/dist/core-runtime/evolve/commands/close.test.js +0 -99
- package/dist/core-runtime/evolve/commands/defer.js +0 -40
- package/dist/core-runtime/evolve/commands/defer.test.js +0 -134
- package/dist/core-runtime/evolve/commands/draft.js +0 -323
- package/dist/core-runtime/evolve/commands/draft.test.js +0 -178
- package/dist/core-runtime/evolve/commands/e2e-evolve-full-cycle.test.js +0 -208
- package/dist/core-runtime/evolve/commands/error-messages.js +0 -125
- package/dist/core-runtime/evolve/commands/error-messages.test.js +0 -167
- package/dist/core-runtime/evolve/commands/propose-align.js +0 -222
- package/dist/core-runtime/evolve/commands/propose-align.test.js +0 -136
- package/dist/core-runtime/evolve/commands/reconstruct.js +0 -330
- package/dist/core-runtime/evolve/commands/reconstruct.test.js +0 -278
- package/dist/core-runtime/evolve/commands/shared.js +0 -22
- package/dist/core-runtime/evolve/commands/stale-check.js +0 -103
- package/dist/core-runtime/evolve/commands/stale-check.test.js +0 -84
- package/dist/core-runtime/evolve/commands/start.js +0 -887
- package/dist/core-runtime/evolve/commands/start.test.js +0 -396
- package/dist/core-runtime/evolve/config/project-config.js +0 -99
- package/dist/core-runtime/evolve/config/project-config.test.js +0 -170
- package/dist/core-runtime/evolve/renderers/align-packet.js +0 -280
- package/dist/core-runtime/evolve/renderers/align-packet.test.js +0 -332
- package/dist/core-runtime/evolve/renderers/draft-packet.js +0 -303
- package/dist/core-runtime/evolve/renderers/draft-packet.test.js +0 -377
- package/dist/core-runtime/evolve/renderers/format.js +0 -5
- package/dist/core-runtime/evolve/renderers/scope-md.js +0 -237
- package/dist/core-runtime/evolve/renderers/scope-md.test.js +0 -306
- package/dist/core-runtime/govern/cli.js +0 -369
- package/dist/core-runtime/govern/cli.test.js +0 -314
- package/dist/core-runtime/govern/drift-engine.js +0 -103
- package/dist/core-runtime/govern/drift-engine.test.js +0 -319
- package/dist/core-runtime/govern/promote-principle.js +0 -206
- package/dist/core-runtime/govern/promote-principle.test.js +0 -368
- package/dist/core-runtime/govern/queue.js +0 -81
- package/dist/core-runtime/govern/types.js +0 -16
- package/dist/core-runtime/install/cli.js +0 -530
- package/dist/core-runtime/install/detect.js +0 -128
- package/dist/core-runtime/install/detect.test.js +0 -155
- package/dist/core-runtime/install/gitignore-update.js +0 -74
- package/dist/core-runtime/install/gitignore-update.test.js +0 -64
- package/dist/core-runtime/install/install-integration.test.js +0 -373
- package/dist/core-runtime/install/prompts.js +0 -389
- package/dist/core-runtime/install/prompts.test.js +0 -293
- package/dist/core-runtime/install/types.js +0 -26
- package/dist/core-runtime/install/validation.js +0 -295
- package/dist/core-runtime/install/validation.test.js +0 -313
- package/dist/core-runtime/install/writer.js +0 -254
- package/dist/core-runtime/install/writer.test.js +0 -218
- package/dist/core-runtime/learning/extractor.js +0 -461
- package/dist/core-runtime/learning/feedback.js +0 -179
- package/dist/core-runtime/learning/health-report.js +0 -165
- package/dist/core-runtime/learning/health-report.test.js +0 -169
- package/dist/core-runtime/learning/loader.js +0 -388
- package/dist/core-runtime/learning/loader.test.js +0 -102
- package/dist/core-runtime/learning/promote/apply-state.js +0 -240
- package/dist/core-runtime/learning/promote/audit-obligation.js +0 -195
- package/dist/core-runtime/learning/promote/collector.js +0 -432
- package/dist/core-runtime/learning/promote/degraded-state.js +0 -125
- package/dist/core-runtime/learning/promote/domain-doc-proposer.js +0 -166
- package/dist/core-runtime/learning/promote/e2e-promote.test.js +0 -6385
- package/dist/core-runtime/learning/promote/health-snapshot.js +0 -150
- package/dist/core-runtime/learning/promote/insight-reclassifier.js +0 -544
- package/dist/core-runtime/learning/promote/judgment-auditor.js +0 -517
- package/dist/core-runtime/learning/promote/panel-reviewer.js +0 -1158
- package/dist/core-runtime/learning/promote/promote-executor.js +0 -1675
- package/dist/core-runtime/learning/promote/promoter.js +0 -307
- package/dist/core-runtime/learning/promote/retirement.js +0 -122
- package/dist/core-runtime/learning/promote/types.js +0 -23
- package/dist/core-runtime/learning/prompt-sections.js +0 -51
- package/dist/core-runtime/learning/shared/artifact-registry-init.js +0 -45
- package/dist/core-runtime/learning/shared/artifact-registry.js +0 -254
- package/dist/core-runtime/learning/shared/audit-obligation-kernel.js +0 -73
- package/dist/core-runtime/learning/shared/audit-state.js +0 -99
- package/dist/core-runtime/learning/shared/duplicate-check.js +0 -28
- package/dist/core-runtime/learning/shared/llm-caller.js +0 -831
- package/dist/core-runtime/learning/shared/llm-caller.test.js +0 -601
- package/dist/core-runtime/learning/shared/llm-tool-loop.js +0 -393
- package/dist/core-runtime/learning/shared/mode.js +0 -25
- package/dist/core-runtime/learning/shared/paths.js +0 -84
- package/dist/core-runtime/learning/shared/paths.test.js +0 -79
- package/dist/core-runtime/learning/shared/patterns.js +0 -37
- package/dist/core-runtime/learning/shared/recoverability.js +0 -355
- package/dist/core-runtime/learning/shared/recovery-context.js +0 -374
- package/dist/core-runtime/learning/shared/scope.js +0 -1
- package/dist/core-runtime/learning/shared/semantic-classifier.js +0 -94
- package/dist/core-runtime/learning/shared/specs/apply-execution-state-spec.js +0 -42
- package/dist/core-runtime/learning/shared/specs/audit-state-spec.js +0 -37
- package/dist/core-runtime/learning/shared/specs/backup-metadata-spec.js +0 -39
- package/dist/core-runtime/learning/shared/specs/emergency-log-spec.js +0 -41
- package/dist/core-runtime/learning/shared/specs/layout-version-spec.js +0 -38
- package/dist/core-runtime/learning/shared/specs/promote-decisions-spec.js +0 -43
- package/dist/core-runtime/learning/shared/specs/promote-report-spec.js +0 -113
- package/dist/core-runtime/learning/shared/specs/prune-log-spec.js +0 -36
- package/dist/core-runtime/learning/shared/specs/recovery-resolution-spec.js +0 -48
- package/dist/core-runtime/learning/shared/specs/restore-manifest-spec.js +0 -43
- package/dist/core-runtime/learning/shared/specs/spec-helpers.js +0 -64
- package/dist/core-runtime/learning/usage-tracker.js +0 -190
- package/dist/core-runtime/learning/usage-tracker.test.js +0 -176
- package/dist/core-runtime/onboard/detect-review-axes.js +0 -122
- package/dist/core-runtime/onboard/detect-review-axes.test.js +0 -127
- package/dist/core-runtime/onboard/write-review-block.js +0 -188
- package/dist/core-runtime/onboard/write-review-block.test.js +0 -240
- package/dist/core-runtime/readers/brownfield-builder.js +0 -150
- package/dist/core-runtime/readers/brownfield-builder.test.js +0 -136
- package/dist/core-runtime/readers/code-chunk-collector.js +0 -53
- package/dist/core-runtime/readers/code-chunk-collector.test.js +0 -136
- package/dist/core-runtime/readers/file-utils.js +0 -240
- package/dist/core-runtime/readers/file-utils.test.js +0 -146
- package/dist/core-runtime/readers/lexicon-citation-check.js +0 -93
- package/dist/core-runtime/readers/lexicon-citation-check.test.js +0 -77
- package/dist/core-runtime/readers/mcp-figma.js +0 -30
- package/dist/core-runtime/readers/mcp-figma.test.js +0 -82
- package/dist/core-runtime/readers/mcp-generic.js +0 -31
- package/dist/core-runtime/readers/mcp-generic.test.js +0 -76
- package/dist/core-runtime/readers/ontology-index.js +0 -148
- package/dist/core-runtime/readers/ontology-index.test.js +0 -245
- package/dist/core-runtime/readers/ontology-query.js +0 -168
- package/dist/core-runtime/readers/ontology-query.test.js +0 -311
- package/dist/core-runtime/readers/ontology-resolve.js +0 -48
- package/dist/core-runtime/readers/ontology-resolve.test.js +0 -48
- package/dist/core-runtime/readers/patterns/index.js +0 -7
- package/dist/core-runtime/readers/review-log.js +0 -213
- package/dist/core-runtime/readers/review-log.test.js +0 -313
- package/dist/core-runtime/readers/scan-local.js +0 -102
- package/dist/core-runtime/readers/scan-local.test.js +0 -102
- package/dist/core-runtime/readers/scan-tarball.js +0 -121
- package/dist/core-runtime/readers/scan-tarball.test.js +0 -283
- package/dist/core-runtime/readers/scan-vault.js +0 -34
- package/dist/core-runtime/readers/scan-vault.test.js +0 -81
- package/dist/core-runtime/readers/types.js +0 -42
- package/dist/core-runtime/readers/types.test.js +0 -94
- package/dist/core-runtime/readers/viewpoint-collectors.js +0 -229
- package/dist/core-runtime/reconstruct/seed-candidate-validation.js +0 -385
- package/dist/core-runtime/review/citation-audit.test.js +0 -165
- package/dist/core-runtime/review/execution-plan-resolver.js +0 -247
- package/dist/core-runtime/review/execution-plan-resolver.test.js +0 -243
- package/dist/core-runtime/review/execution-topology-resolver-axis-first.test.js +0 -246
- package/dist/core-runtime/review/execution-topology-resolver.js +0 -401
- package/dist/core-runtime/review/execution-topology-resolver.test.js +0 -315
- package/dist/core-runtime/review/inline-context-embedder.test.js +0 -154
- package/dist/core-runtime/review/legacy-mode-policy.js +0 -88
- package/dist/core-runtime/review/materializers-effort-persist.test.js +0 -79
- package/dist/core-runtime/review/ontology-path-classifier.js +0 -179
- package/dist/core-runtime/review/ontology-path-classifier.test.js +0 -216
- package/dist/core-runtime/review/packet-boundary-policy.test.js +0 -107
- package/dist/core-runtime/review/participating-lens-paths.test.js +0 -73
- package/dist/core-runtime/review/review-config-legacy-translate.js +0 -244
- package/dist/core-runtime/review/review-config-legacy-translate.test.js +0 -161
- package/dist/core-runtime/review/review-config-validator.js +0 -289
- package/dist/core-runtime/review/review-config-validator.test.js +0 -236
- package/dist/core-runtime/review/shape-pipeline-audit.test.js +0 -311
- package/dist/core-runtime/review/shape-to-topology-id.js +0 -117
- package/dist/core-runtime/review/shape-to-topology-id.test.js +0 -132
- package/dist/core-runtime/review/topology-shape-derivation.js +0 -155
- package/dist/core-runtime/review/topology-shape-derivation.test.js +0 -195
- package/dist/core-runtime/scope-runtime/constants.js +0 -12
- package/dist/core-runtime/scope-runtime/constraint-pool.js +0 -166
- package/dist/core-runtime/scope-runtime/constraint-pool.test.js +0 -674
- package/dist/core-runtime/scope-runtime/domain-validation-log.js +0 -135
- package/dist/core-runtime/scope-runtime/domain-validation-log.test.js +0 -156
- package/dist/core-runtime/scope-runtime/eval-persistence.js +0 -65
- package/dist/core-runtime/scope-runtime/eval-persistence.test.js +0 -84
- package/dist/core-runtime/scope-runtime/event-pipeline.js +0 -64
- package/dist/core-runtime/scope-runtime/event-pipeline.test.js +0 -450
- package/dist/core-runtime/scope-runtime/event-store.js +0 -39
- package/dist/core-runtime/scope-runtime/event-store.test.js +0 -95
- package/dist/core-runtime/scope-runtime/gate-guard.js +0 -348
- package/dist/core-runtime/scope-runtime/gate-guard.test.js +0 -1047
- package/dist/core-runtime/scope-runtime/hash.js +0 -4
- package/dist/core-runtime/scope-runtime/hash.test.js +0 -33
- package/dist/core-runtime/scope-runtime/id.js +0 -4
- package/dist/core-runtime/scope-runtime/id.test.js +0 -17
- package/dist/core-runtime/scope-runtime/reducer.js +0 -297
- package/dist/core-runtime/scope-runtime/reducer.test.js +0 -759
- package/dist/core-runtime/scope-runtime/scope-manager.js +0 -161
- package/dist/core-runtime/scope-runtime/state-machine.js +0 -309
- package/dist/core-runtime/scope-runtime/state-machine.test.js +0 -704
- package/dist/core-runtime/scope-runtime/types.js +0 -116
- package/dist/core-runtime/scope-runtime/types.test.js +0 -69
- package/dist/core-runtime/translate/render-for-user.js +0 -169
- package/dist/core-runtime/translate/render-for-user.test.js +0 -122
- package/dist/providers/capability-contract.js +0 -1
|
@@ -1,601 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
|
-
import * as os from "node:os";
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
import { resolveLearningProviderConfig, logLiteLLMIssue, __resetNoticeFlagsForTests, } from "./llm-caller.js";
|
|
6
|
-
// ─── Helpers ───
|
|
7
|
-
const originalEnv = { ...process.env };
|
|
8
|
-
function clearLlmEnv() {
|
|
9
|
-
delete process.env.ANTHROPIC_API_KEY;
|
|
10
|
-
delete process.env.OPENAI_API_KEY;
|
|
11
|
-
delete process.env.LITELLM_BASE_URL;
|
|
12
|
-
delete process.env.LITELLM_API_KEY;
|
|
13
|
-
delete process.env.ONTO_LLM_MOCK;
|
|
14
|
-
delete process.env.ONTO_SUPPRESS_CODEX_INSTALL_NOTICE;
|
|
15
|
-
}
|
|
16
|
-
function restoreEnv() {
|
|
17
|
-
for (const k of Object.keys(process.env)) {
|
|
18
|
-
if (!(k in originalEnv))
|
|
19
|
-
delete process.env[k];
|
|
20
|
-
}
|
|
21
|
-
for (const [k, v] of Object.entries(originalEnv)) {
|
|
22
|
-
process.env[k] = v;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Create a temp HOME dir containing a writable ~/.codex so we can vary auth.json
|
|
27
|
-
* without touching the real user directory.
|
|
28
|
-
*/
|
|
29
|
-
function createTmpHome() {
|
|
30
|
-
const home = fs.mkdtempSync(path.join(os.tmpdir(), "onto-llm-caller-test-"));
|
|
31
|
-
fs.mkdirSync(path.join(home, ".codex"), { recursive: true });
|
|
32
|
-
return {
|
|
33
|
-
home,
|
|
34
|
-
cleanup: () => {
|
|
35
|
-
fs.rmSync(home, { recursive: true, force: true });
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
function writeAuthJson(home, content) {
|
|
40
|
-
fs.writeFileSync(path.join(home, ".codex", "auth.json"), JSON.stringify(content), "utf8");
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Exercise resolveProvider indirectly via the exported surface that callLlm uses.
|
|
44
|
-
* We can't import resolveProvider directly (it's private), so we trigger
|
|
45
|
-
* resolution paths by calling callLlm with a guaranteed-to-fail-but-observable
|
|
46
|
-
* provider, or by hitting the no-provider error path which surfaces guidance.
|
|
47
|
-
*
|
|
48
|
-
* Most behavior tests route through a separate module that re-exports the
|
|
49
|
-
* resolver for test purposes, but here we use the module's public throw paths.
|
|
50
|
-
*/
|
|
51
|
-
async function attemptResolve() {
|
|
52
|
-
// We import dynamically to pick up env changes; callLlm mocks would need fetch stubs.
|
|
53
|
-
// Use ONTO_LLM_MOCK=1 to short-circuit actual API calls — but that short-circuits
|
|
54
|
-
// BEFORE resolveProvider, which defeats the test. Instead we rely on the throw.
|
|
55
|
-
const { callLlm } = await import("./llm-caller.js");
|
|
56
|
-
try {
|
|
57
|
-
await callLlm("sys", "user", { max_tokens: 1 });
|
|
58
|
-
return { ok: true, text: "unreachable — no real credential should be valid in tests" };
|
|
59
|
-
}
|
|
60
|
-
catch (err) {
|
|
61
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
// ─── resolveLearningProviderConfig (bridge, pure function) ───
|
|
65
|
-
describe("resolveLearningProviderConfig", () => {
|
|
66
|
-
it("T16: CLI overrides beat OntoConfig for every field", () => {
|
|
67
|
-
const config = {
|
|
68
|
-
external_http_provider: "litellm",
|
|
69
|
-
llm_base_url: "http://config:4000/v1",
|
|
70
|
-
model: "config-model",
|
|
71
|
-
};
|
|
72
|
-
const cli = {
|
|
73
|
-
provider: "anthropic",
|
|
74
|
-
llm_base_url: "http://cli:4000/v1",
|
|
75
|
-
model: "cli-model",
|
|
76
|
-
};
|
|
77
|
-
const out = resolveLearningProviderConfig({ config, cliOverrides: cli });
|
|
78
|
-
expect(out.provider).toBe("anthropic");
|
|
79
|
-
expect(out.base_url).toBe("http://cli:4000/v1");
|
|
80
|
-
expect(out.model_id).toBe("cli-model");
|
|
81
|
-
});
|
|
82
|
-
it("falls back to config when CLI overrides absent", () => {
|
|
83
|
-
const out = resolveLearningProviderConfig({
|
|
84
|
-
config: { external_http_provider: "codex", model: "gpt-5", codex: { model: "codex-x", effort: "high" } },
|
|
85
|
-
});
|
|
86
|
-
expect(out.provider).toBe("codex");
|
|
87
|
-
// codex uses config.codex.model override over config.model
|
|
88
|
-
expect(out.model_id).toBe("codex-x");
|
|
89
|
-
expect(out.reasoning_effort).toBe("high");
|
|
90
|
-
});
|
|
91
|
-
it("per-provider model override applies for anthropic", () => {
|
|
92
|
-
const out = resolveLearningProviderConfig({
|
|
93
|
-
config: {
|
|
94
|
-
external_http_provider: "anthropic",
|
|
95
|
-
anthropic: { model: "claude-sonnet-4" },
|
|
96
|
-
model: "generic-fallback",
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
expect(out.provider).toBe("anthropic");
|
|
100
|
-
expect(out.model_id).toBe("claude-sonnet-4");
|
|
101
|
-
});
|
|
102
|
-
it("per-provider model override applies for openai", () => {
|
|
103
|
-
const out = resolveLearningProviderConfig({
|
|
104
|
-
config: { external_http_provider: "openai", openai: { model: "gpt-4o" }, model: "other" },
|
|
105
|
-
});
|
|
106
|
-
expect(out.model_id).toBe("gpt-4o");
|
|
107
|
-
});
|
|
108
|
-
it("per-provider model override applies for litellm", () => {
|
|
109
|
-
const out = resolveLearningProviderConfig({
|
|
110
|
-
config: { external_http_provider: "litellm", litellm: { model: "local-llama" }, model: "other" },
|
|
111
|
-
});
|
|
112
|
-
expect(out.model_id).toBe("local-llama");
|
|
113
|
-
});
|
|
114
|
-
it("falls back to top-level model when per-provider absent", () => {
|
|
115
|
-
const out = resolveLearningProviderConfig({
|
|
116
|
-
config: { external_http_provider: "openai", model: "fallback-model" },
|
|
117
|
-
});
|
|
118
|
-
expect(out.model_id).toBe("fallback-model");
|
|
119
|
-
});
|
|
120
|
-
it("auto-resolution (no provider) emits models_per_provider for dispatch-time lookup", () => {
|
|
121
|
-
// Per-provider models are carried through to dispatch so they apply after
|
|
122
|
-
// resolveProvider picks. This closes the earlier UX gap where
|
|
123
|
-
// `anthropic: { model: X }` was silently ignored under auto-resolution.
|
|
124
|
-
const out = resolveLearningProviderConfig({
|
|
125
|
-
config: {
|
|
126
|
-
anthropic: { model: "ant-model" },
|
|
127
|
-
openai: { model: "oa-model" },
|
|
128
|
-
model: "top-level-fallback",
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
expect(out.provider).toBeUndefined();
|
|
132
|
-
expect(out.model_id).toBe("top-level-fallback");
|
|
133
|
-
expect(out.models_per_provider).toEqual({
|
|
134
|
-
anthropic: "ant-model",
|
|
135
|
-
openai: "oa-model",
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
it("auto-resolution with only per-provider set → models_per_provider carries, top-level absent", () => {
|
|
139
|
-
const out = resolveLearningProviderConfig({
|
|
140
|
-
config: { anthropic: { model: "claude-opus-4" } },
|
|
141
|
-
});
|
|
142
|
-
expect(out.provider).toBeUndefined();
|
|
143
|
-
expect(out.model_id).toBeUndefined();
|
|
144
|
-
expect(out.models_per_provider).toEqual({ anthropic: "claude-opus-4" });
|
|
145
|
-
});
|
|
146
|
-
it("reasoning_effort passes through regardless of provider being explicit", () => {
|
|
147
|
-
// codex.effort must reach dispatch even when external_http_provider isn't set,
|
|
148
|
-
// since cost-order may still pick codex.
|
|
149
|
-
const out = resolveLearningProviderConfig({
|
|
150
|
-
config: { codex: { effort: "high" } },
|
|
151
|
-
});
|
|
152
|
-
expect(out.reasoning_effort).toBe("high");
|
|
153
|
-
});
|
|
154
|
-
it("env LITELLM_BASE_URL beats config.llm_base_url when CLI absent", () => {
|
|
155
|
-
process.env.LITELLM_BASE_URL = "http://env:4000/v1";
|
|
156
|
-
try {
|
|
157
|
-
const out = resolveLearningProviderConfig({
|
|
158
|
-
config: { llm_base_url: "http://config:4000/v1" },
|
|
159
|
-
});
|
|
160
|
-
expect(out.base_url).toBe("http://env:4000/v1");
|
|
161
|
-
}
|
|
162
|
-
finally {
|
|
163
|
-
delete process.env.LITELLM_BASE_URL;
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
it("narrows invalid external_http_provider values to undefined", () => {
|
|
167
|
-
const out = resolveLearningProviderConfig({ config: { external_http_provider: "main-model" } });
|
|
168
|
-
expect(out.provider).toBeUndefined();
|
|
169
|
-
});
|
|
170
|
-
it("returns empty object when nothing provided", () => {
|
|
171
|
-
const out = resolveLearningProviderConfig({});
|
|
172
|
-
expect(Object.keys(out).length).toBe(0);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
// ─── resolveProvider (via throw paths and observable outputs) ───
|
|
176
|
-
describe("callLlm resolveProvider cost-order", () => {
|
|
177
|
-
let tmp = null;
|
|
178
|
-
beforeEach(() => {
|
|
179
|
-
clearLlmEnv();
|
|
180
|
-
tmp = createTmpHome();
|
|
181
|
-
process.env.HOME = tmp.home;
|
|
182
|
-
__resetNoticeFlagsForTests();
|
|
183
|
-
});
|
|
184
|
-
afterEach(() => {
|
|
185
|
-
if (tmp)
|
|
186
|
-
tmp.cleanup();
|
|
187
|
-
tmp = null;
|
|
188
|
-
restoreEnv();
|
|
189
|
-
});
|
|
190
|
-
it("T14: completely empty — fail-fast with cost-order guidance", async () => {
|
|
191
|
-
const result = await attemptResolve();
|
|
192
|
-
expect(result.ok).toBe(false);
|
|
193
|
-
if (!result.ok) {
|
|
194
|
-
expect(result.error).toContain("background task용 LLM provider를 해소할 수 없습니다");
|
|
195
|
-
expect(result.error).toContain("codex OAuth");
|
|
196
|
-
expect(result.error).toContain("LiteLLM");
|
|
197
|
-
expect(result.error).toContain("ANTHROPIC_API_KEY");
|
|
198
|
-
expect(result.error).toContain("OPENAI_API_KEY");
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
it("explicit anthropic without ANTHROPIC_API_KEY → fail-fast with clear guidance", async () => {
|
|
202
|
-
const { callLlm } = await import("./llm-caller.js");
|
|
203
|
-
let caught = null;
|
|
204
|
-
try {
|
|
205
|
-
await callLlm("s", "u", { provider: "anthropic", max_tokens: 8 });
|
|
206
|
-
}
|
|
207
|
-
catch (err) {
|
|
208
|
-
caught = err instanceof Error ? err : new Error(String(err));
|
|
209
|
-
}
|
|
210
|
-
expect(caught).not.toBeNull();
|
|
211
|
-
expect(caught.message).toContain("external_http_provider=anthropic");
|
|
212
|
-
expect(caught.message).toContain("ANTHROPIC_API_KEY");
|
|
213
|
-
});
|
|
214
|
-
it("explicit anthropic + key + no model → missing-model error (hardcoded defaults removed)", async () => {
|
|
215
|
-
process.env.ANTHROPIC_API_KEY = "sk-fake";
|
|
216
|
-
try {
|
|
217
|
-
const { callLlm } = await import("./llm-caller.js");
|
|
218
|
-
let caught = null;
|
|
219
|
-
try {
|
|
220
|
-
await callLlm("s", "u", { provider: "anthropic", max_tokens: 8 });
|
|
221
|
-
}
|
|
222
|
-
catch (err) {
|
|
223
|
-
caught = err instanceof Error ? err : new Error(String(err));
|
|
224
|
-
}
|
|
225
|
-
expect(caught).not.toBeNull();
|
|
226
|
-
expect(caught.message).toContain("model 지정이 필요");
|
|
227
|
-
expect(caught.message).toContain("anthropic.model");
|
|
228
|
-
}
|
|
229
|
-
finally {
|
|
230
|
-
delete process.env.ANTHROPIC_API_KEY;
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
it("T5b: codex OAuth + no binary + no credential → fail-fast with install emphasis", async () => {
|
|
234
|
-
writeAuthJson(tmp.home, { auth_mode: "chatgpt", tokens: { access_token: "tok" } });
|
|
235
|
-
// Clear PATH so codex binary not found
|
|
236
|
-
const originalPath = process.env.PATH;
|
|
237
|
-
process.env.PATH = "/tmp/none-existing-dir";
|
|
238
|
-
try {
|
|
239
|
-
const result = await attemptResolve();
|
|
240
|
-
expect(result.ok).toBe(false);
|
|
241
|
-
if (!result.ok) {
|
|
242
|
-
expect(result.error).toContain("chatgpt OAuth");
|
|
243
|
-
expect(result.error).toContain("codex 바이너리");
|
|
244
|
-
expect(result.error).toContain("https://github.com/openai/codex");
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
finally {
|
|
248
|
-
process.env.PATH = originalPath;
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
it("bridge narrows main-model to undefined (execution realization axis, not provider)", () => {
|
|
252
|
-
const out = resolveLearningProviderConfig({ config: { external_http_provider: "main-model" } });
|
|
253
|
-
expect(out.provider).toBeUndefined();
|
|
254
|
-
});
|
|
255
|
-
// B1: cost-order transition notice — auto-resolution picks codex, user also has ANTHROPIC_API_KEY.
|
|
256
|
-
it("B1: auto-resolution picks codex while ANTHROPIC_API_KEY present → STDERR transition notice", async () => {
|
|
257
|
-
writeAuthJson(tmp.home, { auth_mode: "chatgpt", tokens: { access_token: "tok" } });
|
|
258
|
-
// ensure codex binary IS findable — use the real PATH so `codex` is resolvable
|
|
259
|
-
// (the test machine has codex installed per §1.4 verification).
|
|
260
|
-
// We don't actually invoke codex — we intercept STDERR before the call.
|
|
261
|
-
process.env.ANTHROPIC_API_KEY = "sk-fake-anthropic";
|
|
262
|
-
const captured = [];
|
|
263
|
-
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
264
|
-
process.stderr.write = (chunk) => {
|
|
265
|
-
captured.push(String(chunk));
|
|
266
|
-
return true;
|
|
267
|
-
};
|
|
268
|
-
try {
|
|
269
|
-
const { callLlm } = await import("./llm-caller.js");
|
|
270
|
-
// callLlm will try to spawn codex. We don't care if it succeeds/fails —
|
|
271
|
-
// we only care that the STDERR notice is emitted BEFORE dispatch.
|
|
272
|
-
await callLlm("s", "u", { max_tokens: 1 }).catch(() => { });
|
|
273
|
-
const combined = captured.join("");
|
|
274
|
-
expect(combined).toContain("provider resolution changed by cost-order");
|
|
275
|
-
expect(combined).toContain("would have used anthropic");
|
|
276
|
-
expect(combined).toContain("now using codex");
|
|
277
|
-
}
|
|
278
|
-
finally {
|
|
279
|
-
process.stderr.write = originalWrite;
|
|
280
|
-
delete process.env.ANTHROPIC_API_KEY;
|
|
281
|
-
}
|
|
282
|
-
});
|
|
283
|
-
it("B1: no notice when user sets external_http_provider explicitly", async () => {
|
|
284
|
-
writeAuthJson(tmp.home, { auth_mode: "chatgpt", tokens: { access_token: "tok" } });
|
|
285
|
-
process.env.ANTHROPIC_API_KEY = "sk-fake-anthropic";
|
|
286
|
-
const captured = [];
|
|
287
|
-
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
288
|
-
process.stderr.write = (chunk) => {
|
|
289
|
-
captured.push(String(chunk));
|
|
290
|
-
return true;
|
|
291
|
-
};
|
|
292
|
-
try {
|
|
293
|
-
const { callLlm } = await import("./llm-caller.js");
|
|
294
|
-
// explicit provider=anthropic — user chose, no transition surprise
|
|
295
|
-
await callLlm("s", "u", { provider: "anthropic", max_tokens: 1 }).catch(() => { });
|
|
296
|
-
const combined = captured.join("");
|
|
297
|
-
expect(combined).not.toContain("provider resolution changed by cost-order");
|
|
298
|
-
}
|
|
299
|
-
finally {
|
|
300
|
-
process.stderr.write = originalWrite;
|
|
301
|
-
delete process.env.ANTHROPIC_API_KEY;
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
it("B1: notice suppressed by ONTO_SUPPRESS_COST_ORDER_NOTICE=1", async () => {
|
|
305
|
-
writeAuthJson(tmp.home, { auth_mode: "chatgpt", tokens: { access_token: "tok" } });
|
|
306
|
-
process.env.ANTHROPIC_API_KEY = "sk-fake-anthropic";
|
|
307
|
-
process.env.ONTO_SUPPRESS_COST_ORDER_NOTICE = "1";
|
|
308
|
-
const captured = [];
|
|
309
|
-
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
310
|
-
process.stderr.write = (chunk) => {
|
|
311
|
-
captured.push(String(chunk));
|
|
312
|
-
return true;
|
|
313
|
-
};
|
|
314
|
-
try {
|
|
315
|
-
const { callLlm } = await import("./llm-caller.js");
|
|
316
|
-
await callLlm("s", "u", { max_tokens: 1 }).catch(() => { });
|
|
317
|
-
const combined = captured.join("");
|
|
318
|
-
expect(combined).not.toContain("provider resolution changed by cost-order");
|
|
319
|
-
}
|
|
320
|
-
finally {
|
|
321
|
-
process.stderr.write = originalWrite;
|
|
322
|
-
delete process.env.ANTHROPIC_API_KEY;
|
|
323
|
-
delete process.env.ONTO_SUPPRESS_COST_ORDER_NOTICE;
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
// ─── LlmCallResult audit fields (provenance) ───
|
|
328
|
-
describe("LlmCallResult audit fields", () => {
|
|
329
|
-
// Full SDK-level calls cannot run in unit tests without real credentials.
|
|
330
|
-
// Provenance coverage is via integration/e2e (see e2e-promote.test.ts).
|
|
331
|
-
// This block documents what each provider path SHOULD record.
|
|
332
|
-
//
|
|
333
|
-
// T15 mapping:
|
|
334
|
-
// anthropic → effective_base_url="https://api.anthropic.com", declared_billing_mode="per_token"
|
|
335
|
-
// openai → effective_base_url="https://api.openai.com/v1", declared_billing_mode="per_token"
|
|
336
|
-
// litellm → effective_base_url=<resolved URL>, declared_billing_mode="per_token"
|
|
337
|
-
// codex → effective_base_url="codex-cli://oauth", declared_billing_mode="subscription"
|
|
338
|
-
// mock → effective_base_url="mock://deterministic", declared_billing_mode="per_token"
|
|
339
|
-
it("mock provider records audit fields (ONTO_LLM_MOCK=1)", async () => {
|
|
340
|
-
const orig = process.env.ONTO_LLM_MOCK;
|
|
341
|
-
process.env.ONTO_LLM_MOCK = "1";
|
|
342
|
-
try {
|
|
343
|
-
const { callLlm } = await import("./llm-caller.js");
|
|
344
|
-
const result = await callLlm("You are reviewing promotion candidates for x", "candidate_id=test type=fact tags=[methodology] role=foundation", { max_tokens: 10 });
|
|
345
|
-
expect(result.effective_base_url).toBe("mock://deterministic");
|
|
346
|
-
expect(result.declared_billing_mode).toBe("per_token");
|
|
347
|
-
}
|
|
348
|
-
finally {
|
|
349
|
-
if (orig === undefined)
|
|
350
|
-
delete process.env.ONTO_LLM_MOCK;
|
|
351
|
-
else
|
|
352
|
-
process.env.ONTO_LLM_MOCK = orig;
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
|
-
// ─── logLiteLLMIssue (opt-in LiteLLM issue log) ───
|
|
357
|
-
describe("logLiteLLMIssue", () => {
|
|
358
|
-
let tmpDir;
|
|
359
|
-
let logPath;
|
|
360
|
-
let savedEnv;
|
|
361
|
-
beforeEach(() => {
|
|
362
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "onto-litellm-log-"));
|
|
363
|
-
logPath = path.join(tmpDir, "ISSUE-LOG.md");
|
|
364
|
-
savedEnv = process.env.ONTO_LITELLM_ISSUE_LOG;
|
|
365
|
-
});
|
|
366
|
-
afterEach(() => {
|
|
367
|
-
if (savedEnv === undefined)
|
|
368
|
-
delete process.env.ONTO_LITELLM_ISSUE_LOG;
|
|
369
|
-
else
|
|
370
|
-
process.env.ONTO_LITELLM_ISSUE_LOG = savedEnv;
|
|
371
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
372
|
-
});
|
|
373
|
-
it("is a no-op when ONTO_LITELLM_ISSUE_LOG is unset", () => {
|
|
374
|
-
delete process.env.ONTO_LITELLM_ISSUE_LOG;
|
|
375
|
-
logLiteLLMIssue("title", "symptom", { model_id: "gpt-4o-mini" });
|
|
376
|
-
expect(fs.existsSync(logPath)).toBe(false);
|
|
377
|
-
});
|
|
378
|
-
it("appends a structured entry when the env var points to a file", () => {
|
|
379
|
-
process.env.ONTO_LITELLM_ISSUE_LOG = logPath;
|
|
380
|
-
logLiteLLMIssue("call failed (503)", "upstream unavailable", {
|
|
381
|
-
model_id: "gpt-4o-mini",
|
|
382
|
-
base_url: "http://127.0.0.1:4000/v1",
|
|
383
|
-
status: 503,
|
|
384
|
-
error_name: "APIError",
|
|
385
|
-
error_message: "Service Unavailable",
|
|
386
|
-
prompt_hash: "abc123def456",
|
|
387
|
-
});
|
|
388
|
-
const content = fs.readFileSync(logPath, "utf8");
|
|
389
|
-
expect(content).toMatch(/### \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}\] call failed \(503\)/);
|
|
390
|
-
expect(content).toContain("- **모듈**: onto / LiteLLM");
|
|
391
|
-
expect(content).toContain("- **증상**: upstream unavailable");
|
|
392
|
-
expect(content).toContain("- **모델**: gpt-4o-mini");
|
|
393
|
-
expect(content).toContain("- **엔드포인트**: http://127.0.0.1:4000/v1");
|
|
394
|
-
expect(content).toContain("- **상태 코드**: 503");
|
|
395
|
-
expect(content).toContain("- **에러**: APIError: Service Unavailable");
|
|
396
|
-
expect(content).toContain("- **프롬프트 해시**: abc123def456");
|
|
397
|
-
});
|
|
398
|
-
it("appends successive entries without overwriting prior ones", () => {
|
|
399
|
-
process.env.ONTO_LITELLM_ISSUE_LOG = logPath;
|
|
400
|
-
logLiteLLMIssue("first", "first symptom", { model_id: "a" });
|
|
401
|
-
logLiteLLMIssue("second", "second symptom", { model_id: "b" });
|
|
402
|
-
const content = fs.readFileSync(logPath, "utf8");
|
|
403
|
-
expect(content.match(/] first\b/g)?.length ?? 0).toBe(1);
|
|
404
|
-
expect(content.match(/] second\b/g)?.length ?? 0).toBe(1);
|
|
405
|
-
});
|
|
406
|
-
it("renders em-dash placeholders for missing context fields", () => {
|
|
407
|
-
process.env.ONTO_LITELLM_ISSUE_LOG = logPath;
|
|
408
|
-
logLiteLLMIssue("minimal", "no context", {});
|
|
409
|
-
const content = fs.readFileSync(logPath, "utf8");
|
|
410
|
-
expect(content).toContain("- **모델**: —");
|
|
411
|
-
expect(content).toContain("- **엔드포인트**: —");
|
|
412
|
-
expect(content).toContain("- **상태 코드**: —");
|
|
413
|
-
expect(content).toContain("- **에러**: —");
|
|
414
|
-
expect(content).toContain("- **프롬프트 해시**: —");
|
|
415
|
-
});
|
|
416
|
-
it("does not throw when the log path is unwritable", () => {
|
|
417
|
-
process.env.ONTO_LITELLM_ISSUE_LOG = path.join(tmpDir, "missing-dir", "log.md");
|
|
418
|
-
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
419
|
-
process.stderr.write = () => true;
|
|
420
|
-
try {
|
|
421
|
-
expect(() => logLiteLLMIssue("unwritable", "should degrade gracefully", {})).not.toThrow();
|
|
422
|
-
}
|
|
423
|
-
finally {
|
|
424
|
-
process.stderr.write = originalWrite;
|
|
425
|
-
}
|
|
426
|
-
});
|
|
427
|
-
});
|
|
428
|
-
// ─── Provider ladder observability ([provider-ladder] STDERR) ───
|
|
429
|
-
describe("model-call observability ([model-call] STDERR)", () => {
|
|
430
|
-
let captured = [];
|
|
431
|
-
let originalWrite = null;
|
|
432
|
-
beforeEach(() => {
|
|
433
|
-
clearLlmEnv();
|
|
434
|
-
__resetNoticeFlagsForTests();
|
|
435
|
-
captured = [];
|
|
436
|
-
originalWrite = process.stderr.write.bind(process.stderr);
|
|
437
|
-
process.stderr.write = (chunk) => {
|
|
438
|
-
captured.push(String(chunk));
|
|
439
|
-
return true;
|
|
440
|
-
};
|
|
441
|
-
});
|
|
442
|
-
afterEach(() => {
|
|
443
|
-
if (originalWrite)
|
|
444
|
-
process.stderr.write = originalWrite;
|
|
445
|
-
originalWrite = null;
|
|
446
|
-
restoreEnv();
|
|
447
|
-
});
|
|
448
|
-
function modelCallLines() {
|
|
449
|
-
return captured.filter((l) => l.includes("[model-call]"));
|
|
450
|
-
}
|
|
451
|
-
it("anthropic API failure → [model-call] FAILED log includes status / type / message / request_id", async () => {
|
|
452
|
-
// Mock @anthropic-ai/sdk to throw an APIError-shaped object.
|
|
453
|
-
vi.doMock("@anthropic-ai/sdk", () => ({
|
|
454
|
-
default: class {
|
|
455
|
-
messages = {
|
|
456
|
-
create: vi.fn().mockRejectedValue({
|
|
457
|
-
status: 400,
|
|
458
|
-
name: "BadRequestError",
|
|
459
|
-
message: "400 Bad Request",
|
|
460
|
-
error: { type: "invalid_request_error", message: "model: claude-fake-id not found" },
|
|
461
|
-
request_id: "req_test_123",
|
|
462
|
-
}),
|
|
463
|
-
};
|
|
464
|
-
},
|
|
465
|
-
}));
|
|
466
|
-
vi.resetModules();
|
|
467
|
-
process.env.ANTHROPIC_API_KEY = "sk-test";
|
|
468
|
-
const { callLlm } = await import("./llm-caller.js");
|
|
469
|
-
try {
|
|
470
|
-
await callLlm("s", "u", { provider: "anthropic", model_id: "claude-fake-id", max_tokens: 8 });
|
|
471
|
-
}
|
|
472
|
-
catch {
|
|
473
|
-
// expected
|
|
474
|
-
}
|
|
475
|
-
const lines = modelCallLines();
|
|
476
|
-
expect(lines.some((l) => l.includes("anthropic call:") && l.includes('model="claude-fake-id"'))).toBe(true);
|
|
477
|
-
expect(lines.some((l) => l.includes("anthropic call FAILED") && l.includes("status=400"))).toBe(true);
|
|
478
|
-
expect(lines.some((l) => l.includes("invalid_request_error"))).toBe(true);
|
|
479
|
-
expect(lines.some((l) => l.includes("claude-fake-id not found"))).toBe(true);
|
|
480
|
-
expect(lines.some((l) => l.includes("req_test_123"))).toBe(true);
|
|
481
|
-
vi.doUnmock("@anthropic-ai/sdk");
|
|
482
|
-
vi.resetModules();
|
|
483
|
-
delete process.env.ANTHROPIC_API_KEY;
|
|
484
|
-
});
|
|
485
|
-
it("pre-call log always emits before failure — principal sees attempted model id even on throw", async () => {
|
|
486
|
-
vi.doMock("@anthropic-ai/sdk", () => ({
|
|
487
|
-
default: class {
|
|
488
|
-
messages = {
|
|
489
|
-
create: vi.fn().mockRejectedValue(new Error("network blip")),
|
|
490
|
-
};
|
|
491
|
-
},
|
|
492
|
-
}));
|
|
493
|
-
vi.resetModules();
|
|
494
|
-
process.env.ANTHROPIC_API_KEY = "sk-test";
|
|
495
|
-
const { callLlm } = await import("./llm-caller.js");
|
|
496
|
-
try {
|
|
497
|
-
await callLlm("s", "u", { provider: "anthropic", model_id: "claude-x", max_tokens: 8 });
|
|
498
|
-
}
|
|
499
|
-
catch { /* expected */ }
|
|
500
|
-
const lines = modelCallLines();
|
|
501
|
-
const preIdx = lines.findIndex((l) => l.includes("anthropic call:"));
|
|
502
|
-
const failIdx = lines.findIndex((l) => l.includes("anthropic call FAILED"));
|
|
503
|
-
expect(preIdx).toBeGreaterThanOrEqual(0);
|
|
504
|
-
expect(failIdx).toBeGreaterThan(preIdx);
|
|
505
|
-
vi.doUnmock("@anthropic-ai/sdk");
|
|
506
|
-
vi.resetModules();
|
|
507
|
-
delete process.env.ANTHROPIC_API_KEY;
|
|
508
|
-
});
|
|
509
|
-
});
|
|
510
|
-
describe("resolveProvider ladder observability", () => {
|
|
511
|
-
let tmp = null;
|
|
512
|
-
let captured = [];
|
|
513
|
-
let originalWrite = null;
|
|
514
|
-
beforeEach(() => {
|
|
515
|
-
clearLlmEnv();
|
|
516
|
-
tmp = createTmpHome();
|
|
517
|
-
process.env.HOME = tmp.home;
|
|
518
|
-
__resetNoticeFlagsForTests();
|
|
519
|
-
captured = [];
|
|
520
|
-
originalWrite = process.stderr.write.bind(process.stderr);
|
|
521
|
-
process.stderr.write = (chunk) => {
|
|
522
|
-
captured.push(String(chunk));
|
|
523
|
-
return true;
|
|
524
|
-
};
|
|
525
|
-
});
|
|
526
|
-
afterEach(() => {
|
|
527
|
-
if (originalWrite)
|
|
528
|
-
process.stderr.write = originalWrite;
|
|
529
|
-
originalWrite = null;
|
|
530
|
-
if (tmp)
|
|
531
|
-
tmp.cleanup();
|
|
532
|
-
tmp = null;
|
|
533
|
-
restoreEnv();
|
|
534
|
-
});
|
|
535
|
-
function ladderLines() {
|
|
536
|
-
return captured.filter((l) => l.includes("[provider-ladder]"));
|
|
537
|
-
}
|
|
538
|
-
it("empty env → step 3 codex skipped (auth.json 부재) + step 4/5/6 skipped + final throw log", async () => {
|
|
539
|
-
await attemptResolve();
|
|
540
|
-
const lines = ladderLines();
|
|
541
|
-
expect(lines.some((l) => l.includes("step 3 codex: skipped") && l.includes("auth.json 부재"))).toBe(true);
|
|
542
|
-
expect(lines.some((l) => l.includes("step 4 litellm: skipped"))).toBe(true);
|
|
543
|
-
expect(lines.some((l) => l.includes("step 5 anthropic: skipped"))).toBe(true);
|
|
544
|
-
expect(lines.some((l) => l.includes("step 6 openai: skipped"))).toBe(true);
|
|
545
|
-
expect(lines.some((l) => l.includes("final:") && l.includes("no provider viable"))).toBe(true);
|
|
546
|
-
});
|
|
547
|
-
it("LITELLM_BASE_URL set → step 4 matched (env source) log, step 5/6 not evaluated", async () => {
|
|
548
|
-
process.env.LITELLM_BASE_URL = "https://litellm.example.com";
|
|
549
|
-
try {
|
|
550
|
-
await attemptResolve();
|
|
551
|
-
}
|
|
552
|
-
catch { /* ignore */ }
|
|
553
|
-
const lines = ladderLines();
|
|
554
|
-
expect(lines.some((l) => l.includes("step 4 litellm: matched") && l.includes("LITELLM_BASE_URL env"))).toBe(true);
|
|
555
|
-
expect(lines.some((l) => l.includes("step 5 anthropic:"))).toBe(false);
|
|
556
|
-
expect(lines.some((l) => l.includes("step 6 openai:"))).toBe(false);
|
|
557
|
-
});
|
|
558
|
-
it("ANTHROPIC_API_KEY only → step 3/4 skipped + step 5 matched log", async () => {
|
|
559
|
-
process.env.ANTHROPIC_API_KEY = "sk-fake";
|
|
560
|
-
try {
|
|
561
|
-
await attemptResolve();
|
|
562
|
-
}
|
|
563
|
-
catch { /* ignore */ }
|
|
564
|
-
const lines = ladderLines();
|
|
565
|
-
expect(lines.some((l) => l.includes("step 3 codex: skipped"))).toBe(true);
|
|
566
|
-
expect(lines.some((l) => l.includes("step 4 litellm: skipped"))).toBe(true);
|
|
567
|
-
expect(lines.some((l) => l.includes("step 5 anthropic: matched"))).toBe(true);
|
|
568
|
-
expect(lines.some((l) => l.includes("step 6 openai:"))).toBe(false);
|
|
569
|
-
});
|
|
570
|
-
it("codex auth.json exists but auth_mode != 'chatgpt' → step 3 skip with 'OAuth 조건 미충족'", async () => {
|
|
571
|
-
writeAuthJson(tmp.home, { auth_mode: "api" });
|
|
572
|
-
await attemptResolve();
|
|
573
|
-
const lines = ladderLines();
|
|
574
|
-
expect(lines.some((l) => l.includes("step 3 codex: skipped") && l.includes("OAuth 조건 미충족"))).toBe(true);
|
|
575
|
-
});
|
|
576
|
-
it("codex OAuth present + binary missing → step 3 falls-through log with binary NOT on PATH", async () => {
|
|
577
|
-
writeAuthJson(tmp.home, { auth_mode: "chatgpt", tokens: { access_token: "tok" } });
|
|
578
|
-
const originalPath = process.env.PATH;
|
|
579
|
-
process.env.PATH = "/tmp/none-existing-dir-xyz";
|
|
580
|
-
process.env.ANTHROPIC_API_KEY = "sk-fake";
|
|
581
|
-
try {
|
|
582
|
-
await attemptResolve();
|
|
583
|
-
}
|
|
584
|
-
catch { /* ignore */ }
|
|
585
|
-
finally {
|
|
586
|
-
process.env.PATH = originalPath ?? "";
|
|
587
|
-
}
|
|
588
|
-
const lines = ladderLines();
|
|
589
|
-
expect(lines.some((l) => l.includes("step 3 codex: OAuth detected") && l.includes("binary NOT on PATH"))).toBe(true);
|
|
590
|
-
expect(lines.some((l) => l.includes("step 5 anthropic: matched"))).toBe(true);
|
|
591
|
-
});
|
|
592
|
-
it("explicit anthropic without key → explicit fail-fast log", async () => {
|
|
593
|
-
const { callLlm } = await import("./llm-caller.js");
|
|
594
|
-
try {
|
|
595
|
-
await callLlm("s", "u", { provider: "anthropic", max_tokens: 8 });
|
|
596
|
-
}
|
|
597
|
-
catch { /* ignore */ }
|
|
598
|
-
const lines = ladderLines();
|
|
599
|
-
expect(lines.some((l) => l.includes("explicit anthropic") && l.includes("fail-fast"))).toBe(true);
|
|
600
|
-
});
|
|
601
|
-
});
|