@ryanfw/prompt-orchestration-pipeline 1.3.3 → 1.3.5
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/docs/http-api.md +17 -4
- package/docs/pop-task-guide.md +8 -0
- package/package.json +5 -5
- package/src/api/__tests__/index.test.ts +144 -41
- package/src/api/index.ts +15 -83
- package/src/cli/__tests__/analyze-task.test.ts +39 -7
- package/src/cli/__tests__/index.test.ts +337 -17
- package/src/cli/__tests__/types.test.ts +0 -18
- package/src/cli/__tests__/update-pipeline-json.test.ts +19 -9
- package/src/cli/analyze-task.ts +3 -14
- package/src/cli/index.ts +73 -61
- package/src/cli/types.ts +0 -13
- package/src/cli/update-pipeline-json.ts +3 -14
- package/src/config/__tests__/config-schema.test.ts +42 -0
- package/src/config/__tests__/legacy-literal-guard.test.ts +156 -0
- package/src/config/__tests__/models.test.ts +139 -1
- package/src/config/__tests__/paths.test.ts +46 -1
- package/src/config/__tests__/pipeline-registry.test.ts +767 -0
- package/src/config/__tests__/sse-events.test.ts +470 -0
- package/src/config/models.ts +121 -19
- package/src/config/paths.ts +11 -2
- package/src/config/pipeline-registry.ts +258 -0
- package/src/config/sse-events.ts +122 -0
- package/src/core/__tests__/agent-step.test.ts +167 -0
- package/src/core/__tests__/config.test.ts +517 -107
- package/src/core/__tests__/core-boot-resolution.test.ts +181 -0
- package/src/core/__tests__/job-concurrency.test.ts +200 -0
- package/src/core/__tests__/job-submission.test.ts +396 -0
- package/src/core/__tests__/job-view.test.ts +583 -3
- package/src/core/__tests__/json-file.test.ts +111 -0
- package/src/core/__tests__/lifecycle-policy.test.ts +81 -7
- package/src/core/__tests__/logger.test.ts +22 -0
- package/src/core/__tests__/orchestrator-no-deprecated-exports.test.ts +41 -0
- package/src/core/__tests__/orchestrator.test.ts +373 -2
- package/src/core/__tests__/pipeline-runner.test.ts +946 -9
- package/src/core/__tests__/redact.test.ts +153 -0
- package/src/core/__tests__/runner-liveness.test.ts +45 -0
- package/src/core/__tests__/seed-naming.test.ts +57 -0
- package/src/core/__tests__/single-derivation.test.ts +1 -1
- package/src/core/__tests__/task-runner.test.ts +594 -3
- package/src/core/__tests__/task-telemetry.test.ts +241 -0
- package/src/core/__tests__/workspace-root-resolution-guard.test.ts +62 -0
- package/src/core/agent-step.ts +9 -2
- package/src/core/agent-types.ts +10 -4
- package/src/core/config.ts +127 -234
- package/src/core/control.ts +3 -0
- package/src/core/job-concurrency.ts +50 -19
- package/src/core/job-submission.ts +133 -0
- package/src/core/job-view.ts +183 -19
- package/src/core/json-file.ts +45 -0
- package/src/core/lifecycle-policy.ts +23 -8
- package/src/core/logger.ts +0 -29
- package/src/core/orchestrator.ts +124 -70
- package/src/core/pipeline-runner.ts +85 -53
- package/src/core/redact.ts +40 -0
- package/src/core/runner-liveness.ts +8 -4
- package/src/core/seed-naming.ts +9 -0
- package/src/core/status-writer.ts +3 -28
- package/src/core/task-runner.ts +356 -319
- package/src/core/task-telemetry.ts +107 -0
- package/src/harness/__tests__/subprocess.test.ts +112 -1
- package/src/harness/subprocess.ts +55 -16
- package/src/llm/__tests__/dispatch.test.ts +130 -0
- package/src/llm/__tests__/index.test.ts +693 -43
- package/src/llm/__tests__/legacy-normalization.test.ts +109 -0
- package/src/llm/index.ts +405 -165
- package/src/providers/__tests__/alibaba.test.ts +67 -6
- package/src/providers/__tests__/anthropic.test.ts +35 -14
- package/src/providers/__tests__/base.test.ts +62 -0
- package/src/providers/__tests__/claude-code.test.ts +108 -17
- package/src/providers/__tests__/deepseek.test.ts +16 -6
- package/src/providers/__tests__/gemini.test.ts +47 -25
- package/src/providers/__tests__/moonshot.test.ts +60 -3
- package/src/providers/__tests__/openai.test.ts +262 -74
- package/src/providers/__tests__/opencode.test.ts +77 -0
- package/src/providers/__tests__/request-timeout-contract.test.ts +226 -0
- package/src/providers/__tests__/stream-accumulator.test.ts +52 -0
- package/src/providers/__tests__/types.test.ts +128 -0
- package/src/providers/__tests__/zero-fill-guard.test.ts +62 -0
- package/src/providers/__tests__/zhipu.test.ts +49 -40
- package/src/providers/alibaba.ts +20 -39
- package/src/providers/anthropic.ts +23 -11
- package/src/providers/base.ts +19 -0
- package/src/providers/claude-code.ts +27 -18
- package/src/providers/deepseek.ts +9 -28
- package/src/providers/gemini.ts +20 -58
- package/src/providers/moonshot.ts +22 -11
- package/src/providers/openai.ts +79 -61
- package/src/providers/opencode.ts +16 -33
- package/src/providers/stream-accumulator.ts +27 -21
- package/src/providers/types.ts +36 -9
- package/src/providers/zhipu.ts +15 -46
- package/src/task-analysis/__tests__/analyzer.test.ts +0 -0
- package/src/task-analysis/__tests__/enrichers-schema-deducer.test.ts +69 -2
- package/src/task-analysis/__tests__/no-ast-imports.test.ts +52 -0
- package/src/task-analysis/__tests__/repository.test.ts +65 -0
- package/src/task-analysis/__tests__/types.test.ts +63 -130
- package/src/task-analysis/analyzer.ts +85 -0
- package/src/task-analysis/enrichers/schema-deducer.ts +12 -2
- package/src/task-analysis/index.ts +2 -36
- package/src/task-analysis/repository.ts +45 -0
- package/src/task-analysis/types.ts +42 -58
- package/src/ui/client/__tests__/api.test.ts +143 -1
- package/src/ui/client/__tests__/bootstrap.test.ts +178 -62
- package/src/ui/client/__tests__/job-adapter.test.ts +125 -2
- package/src/ui/client/__tests__/load-state.test.ts +70 -0
- package/src/ui/client/__tests__/types.test.ts +66 -3
- package/src/ui/client/__tests__/useJobDetailWithUpdates.test.ts +390 -77
- package/src/ui/client/__tests__/useJobList.test.ts +198 -23
- package/src/ui/client/__tests__/useJobListWithUpdates.test.ts +218 -7
- package/src/ui/client/adapters/__tests__/job-adapter.test.ts +186 -0
- package/src/ui/client/adapters/job-adapter.ts +31 -16
- package/src/ui/client/api.ts +38 -15
- package/src/ui/client/bootstrap.ts +19 -14
- package/src/ui/client/hooks/useJobDetailWithUpdates.ts +73 -97
- package/src/ui/client/hooks/useJobList.ts +26 -31
- package/src/ui/client/hooks/useJobListWithUpdates.ts +42 -76
- package/src/ui/client/load-state.ts +20 -0
- package/src/ui/client/reducers/__tests__/job-events.test.ts +568 -0
- package/src/ui/client/reducers/job-events.ts +137 -0
- package/src/ui/client/types.ts +14 -20
- package/src/ui/components/DAGGrid.tsx +6 -1
- package/src/ui/components/JobDetail.tsx +12 -4
- package/src/ui/components/JobTable.tsx +13 -2
- package/src/ui/components/StageTimeline.tsx +8 -20
- package/src/ui/components/TaskAnalysisDisplay.tsx +48 -6
- package/src/ui/components/__tests__/DAGGrid.test.tsx +36 -0
- package/src/ui/components/__tests__/JobDetail.test.tsx +112 -0
- package/src/ui/components/__tests__/JobTable.test.tsx +83 -0
- package/src/ui/components/__tests__/StageTimeline.test.tsx +5 -6
- package/src/ui/components/__tests__/TaskAnalysisDisplay.test.tsx +64 -0
- package/src/ui/components/types.ts +33 -15
- package/src/ui/dist/assets/{index-L6cvsCAx.js → index-lBbVeNZC.js} +4304 -253
- package/src/ui/dist/assets/index-lBbVeNZC.js.map +1 -0
- package/src/ui/dist/assets/style-CtZBnjlR.css +2 -0
- package/src/ui/dist/index.html +2 -2
- package/src/ui/pages/Code.tsx +5 -2
- package/src/ui/pages/PipelineDetail.tsx +60 -4
- package/src/ui/pages/PromptPipelineDashboard.tsx +59 -7
- package/src/ui/pages/__tests__/PromptPipelineDashboard.test.tsx +114 -32
- package/src/ui/pages/__tests__/pages.test.tsx +236 -42
- package/src/ui/server/__tests__/concurrency-endpoint.test.ts +54 -0
- package/src/ui/server/__tests__/config-bridge-node.test.ts +34 -1
- package/src/ui/server/__tests__/embedded-assets.test.ts +66 -0
- package/src/ui/server/__tests__/file-endpoints.test.ts +183 -5
- package/src/ui/server/__tests__/gate-endpoints.test.ts +55 -0
- package/src/ui/server/__tests__/http-api-contract.test.ts +838 -0
- package/src/ui/server/__tests__/index.test.ts +63 -0
- package/src/ui/server/__tests__/job-control-endpoints.test.ts +582 -21
- package/src/ui/server/__tests__/job-control-service.test.ts +282 -0
- package/src/ui/server/__tests__/job-endpoints.test.ts +43 -1
- package/src/ui/server/__tests__/job-process-registry.test.ts +151 -0
- package/src/ui/server/__tests__/job-repository.test.ts +142 -0
- package/src/ui/server/__tests__/read-static-path-guard.test.ts +94 -0
- package/src/ui/server/__tests__/router-root-isolation.test.ts +204 -0
- package/src/ui/server/__tests__/router-threading.test.ts +148 -0
- package/src/ui/server/__tests__/router.test.ts +104 -0
- package/src/ui/server/__tests__/sse-broadcast.test.ts +44 -0
- package/src/ui/server/__tests__/sse-enhancer.test.ts +39 -0
- package/src/ui/server/config-bridge-node.ts +8 -26
- package/src/ui/server/config-bridge.ts +9 -6
- package/src/ui/server/embedded-assets-imports.d.ts +44 -0
- package/src/ui/server/embedded-assets.ts +13 -2
- package/src/ui/server/endpoints/__tests__/pipeline-analysis-endpoint.test.ts +167 -0
- package/src/ui/server/endpoints/__tests__/schema-file-endpoint.test.ts +104 -0
- package/src/ui/server/endpoints/__tests__/task-analysis-endpoint.test.ts +103 -0
- package/src/ui/server/endpoints/__tests__/upload-endpoints.test.ts +162 -96
- package/src/ui/server/endpoints/concurrency-endpoint.ts +2 -1
- package/src/ui/server/endpoints/create-pipeline-endpoint.ts +14 -29
- package/src/ui/server/endpoints/file-endpoints.ts +15 -10
- package/src/ui/server/endpoints/gate-endpoints.ts +24 -12
- package/src/ui/server/endpoints/job-control-endpoints.ts +19 -714
- package/src/ui/server/endpoints/job-endpoints.ts +1 -0
- package/src/ui/server/endpoints/pipeline-analysis-endpoint.ts +99 -11
- package/src/ui/server/endpoints/schema-file-endpoint.ts +11 -3
- package/src/ui/server/endpoints/task-analysis-endpoint.ts +21 -5
- package/src/ui/server/endpoints/task-save-endpoint.ts +1 -2
- package/src/ui/server/endpoints/upload-endpoints.ts +5 -40
- package/src/ui/server/index.ts +19 -14
- package/src/ui/server/job-control-service.ts +520 -0
- package/src/ui/server/job-process-registry.ts +250 -0
- package/src/ui/server/job-reader.ts +14 -2
- package/src/ui/server/job-repository.ts +316 -0
- package/src/ui/server/router.ts +33 -10
- package/src/ui/server/sse-broadcast.ts +14 -3
- package/src/ui/server/sse-enhancer.ts +12 -2
- package/src/ui/state/__tests__/schema-loader.test.ts +11 -1
- package/src/ui/state/__tests__/snapshot.test.ts +70 -15
- package/src/ui/state/__tests__/types.test.ts +6 -0
- package/src/ui/state/snapshot.ts +1 -1
- package/src/ui/state/transformers/__tests__/list-transformer.test.ts +42 -0
- package/src/ui/state/transformers/__tests__/status-transformer.test.ts +45 -3
- package/src/ui/state/transformers/list-transformer.ts +6 -0
- package/src/ui/state/types.ts +6 -1
- package/src/utils/__tests__/path-containment.test.ts +160 -0
- package/src/{ui/server/utils → utils}/path-containment.ts +21 -0
- package/src/task-analysis/__tests__/enrichers-analysis-writer.test.ts +0 -86
- package/src/task-analysis/__tests__/enrichers-artifact-resolver.test.ts +0 -124
- package/src/task-analysis/__tests__/extractors-artifacts.test.ts +0 -133
- package/src/task-analysis/__tests__/extractors-llm-calls.test.ts +0 -46
- package/src/task-analysis/__tests__/extractors-stages.test.ts +0 -52
- package/src/task-analysis/__tests__/index.test.ts +0 -143
- package/src/task-analysis/__tests__/parser.test.ts +0 -41
- package/src/task-analysis/__tests__/utils-ast.test.ts +0 -82
- package/src/task-analysis/enrichers/analysis-writer.ts +0 -75
- package/src/task-analysis/enrichers/artifact-resolver.ts +0 -89
- package/src/task-analysis/extractors/artifacts.ts +0 -143
- package/src/task-analysis/extractors/llm-calls.ts +0 -117
- package/src/task-analysis/extractors/stages.ts +0 -45
- package/src/task-analysis/parser.ts +0 -20
- package/src/task-analysis/utils/ast.ts +0 -45
- package/src/ui/dist/assets/index-L6cvsCAx.js.map +0 -1
- package/src/ui/dist/assets/style-CSSKuMOe.css +0 -2
- package/src/ui/embedded-assets.js +0 -12
- package/src/ui/server/__tests__/path-containment.test.ts +0 -54
package/src/cli/index.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { mkdir, access } from "node:fs/promises";
|
|
4
|
-
import {
|
|
4
|
+
import { join } from "node:path";
|
|
5
5
|
|
|
6
6
|
import { STAGE_NAMES, getStagePurpose, KEBAB_CASE_REGEX } from "./constants.ts";
|
|
7
7
|
import { buildReexecArgs, isCompiledBinary } from "./self-reexec.ts";
|
|
8
8
|
import { updatePipelineJson } from "./update-pipeline-json.ts";
|
|
9
9
|
import { analyzeTaskFile } from "./analyze-task.ts";
|
|
10
10
|
import { submitJobWithValidation, PipelineOrchestrator } from "../api/index.ts";
|
|
11
|
-
import
|
|
11
|
+
import { resolveWorkspaceRoot } from "../config/paths.ts";
|
|
12
|
+
import {
|
|
13
|
+
writeRegistry,
|
|
14
|
+
registerPipeline,
|
|
15
|
+
REGISTRY_VERSION,
|
|
16
|
+
} from "../config/pipeline-registry.ts";
|
|
12
17
|
import pkg from "../../package.json";
|
|
13
18
|
|
|
14
19
|
// ─── init ─────────────────────────────────────────────────────────────────────
|
|
@@ -21,10 +26,7 @@ export async function handleInit(root: string): Promise<void> {
|
|
|
21
26
|
await mkdir(join(root, "pipeline-data", sub), { recursive: true });
|
|
22
27
|
await Bun.write(join(root, "pipeline-data", sub, ".gitkeep"), "");
|
|
23
28
|
}
|
|
24
|
-
await
|
|
25
|
-
join(root, "registry.json"),
|
|
26
|
-
JSON.stringify({ pipelines: {} }, null, 2) + "\n"
|
|
27
|
-
);
|
|
29
|
+
await writeRegistry(root, { version: REGISTRY_VERSION, pipelines: {} });
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
// ─── start ────────────────────────────────────────────────────────────────────
|
|
@@ -43,18 +45,49 @@ export function buildUiCorsEnv(opts: StartOptions): Record<string, string> {
|
|
|
43
45
|
return env;
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Build the child env objects passed to the spawned `_start-ui` and
|
|
50
|
+
* `_start-orchestrator` processes. The parent `handleStart` resolves the
|
|
51
|
+
* workspace root once via `resolveWorkspaceRoot` and transports the absolute
|
|
52
|
+
* value to the children via `PO_ROOT`; the children perform their own boot
|
|
53
|
+
* resolution from that transported value.
|
|
54
|
+
*/
|
|
55
|
+
export function buildStartChildEnv(
|
|
56
|
+
absoluteRoot: string,
|
|
57
|
+
port: string,
|
|
58
|
+
opts?: StartOptions,
|
|
59
|
+
): { uiEnv: Record<string, string>; orchEnv: Record<string, string> } {
|
|
60
|
+
const uiEnv: Record<string, string> = Object.fromEntries(
|
|
61
|
+
Object.entries({ ...process.env, NODE_ENV: "production", PO_ROOT: absoluteRoot, PORT: port }).filter(
|
|
62
|
+
(entry): entry is [string, string] => entry[1] !== undefined,
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
delete uiEnv["PO_UI_PORT"];
|
|
66
|
+
|
|
67
|
+
const corsEnv = opts ? buildUiCorsEnv(opts) : {};
|
|
68
|
+
Object.assign(uiEnv, corsEnv);
|
|
69
|
+
|
|
70
|
+
const orchEnv: Record<string, string> = Object.fromEntries(
|
|
71
|
+
Object.entries({ ...process.env, NODE_ENV: "production", PO_ROOT: absoluteRoot }).filter(
|
|
72
|
+
(entry): entry is [string, string] => entry[1] !== undefined,
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return { uiEnv, orchEnv };
|
|
77
|
+
}
|
|
78
|
+
|
|
46
79
|
export async function handleStart(
|
|
47
80
|
root: string | undefined,
|
|
48
81
|
port: string,
|
|
49
82
|
opts?: StartOptions,
|
|
50
83
|
): Promise<void> {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
84
|
+
let absoluteRoot: string;
|
|
85
|
+
try {
|
|
86
|
+
absoluteRoot = resolveWorkspaceRoot(root);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
54
89
|
process.exit(1);
|
|
55
90
|
}
|
|
56
|
-
|
|
57
|
-
const absoluteRoot = resolve(rawRoot);
|
|
58
91
|
process.env["PO_ROOT"] = absoluteRoot;
|
|
59
92
|
|
|
60
93
|
if (!isCompiledBinary()) {
|
|
@@ -69,21 +102,7 @@ export async function handleStart(
|
|
|
69
102
|
}
|
|
70
103
|
}
|
|
71
104
|
|
|
72
|
-
const uiEnv
|
|
73
|
-
Object.entries({ ...process.env, NODE_ENV: "production", PO_ROOT: absoluteRoot, PORT: port }).filter(
|
|
74
|
-
(entry): entry is [string, string] => entry[1] !== undefined
|
|
75
|
-
)
|
|
76
|
-
);
|
|
77
|
-
delete uiEnv["PO_UI_PORT"];
|
|
78
|
-
|
|
79
|
-
const corsEnv = opts ? buildUiCorsEnv(opts) : {};
|
|
80
|
-
Object.assign(uiEnv, corsEnv);
|
|
81
|
-
|
|
82
|
-
const orchEnv: Record<string, string> = Object.fromEntries(
|
|
83
|
-
Object.entries({ ...process.env, NODE_ENV: "production", PO_ROOT: absoluteRoot }).filter(
|
|
84
|
-
(entry): entry is [string, string] => entry[1] !== undefined
|
|
85
|
-
)
|
|
86
|
-
);
|
|
105
|
+
const { uiEnv, orchEnv } = buildStartChildEnv(absoluteRoot, port, opts);
|
|
87
106
|
|
|
88
107
|
const uiReexec = buildReexecArgs(["_start-ui"]);
|
|
89
108
|
const orchReexec = buildReexecArgs(["_start-orchestrator"]);
|
|
@@ -182,7 +201,10 @@ export async function handleStart(
|
|
|
182
201
|
|
|
183
202
|
// ─── submit ───────────────────────────────────────────────────────────────────
|
|
184
203
|
|
|
185
|
-
export async function handleSubmit(
|
|
204
|
+
export async function handleSubmit(
|
|
205
|
+
seedFile: string,
|
|
206
|
+
opts: { root?: string } = {},
|
|
207
|
+
): Promise<void> {
|
|
186
208
|
let seedObject: unknown;
|
|
187
209
|
try {
|
|
188
210
|
const text = await Bun.file(seedFile).text();
|
|
@@ -192,8 +214,9 @@ export async function handleSubmit(seedFile: string): Promise<void> {
|
|
|
192
214
|
process.exit(1);
|
|
193
215
|
}
|
|
194
216
|
|
|
217
|
+
const root = opts.root ?? process.env["PO_ROOT"] ?? process.cwd();
|
|
195
218
|
try {
|
|
196
|
-
const result = await submitJobWithValidation({
|
|
219
|
+
const result = await submitJobWithValidation({ root, seedObject });
|
|
197
220
|
if (result.success) {
|
|
198
221
|
console.log(`Job submitted: ${result.jobId} (${result.jobName})`);
|
|
199
222
|
} else {
|
|
@@ -208,8 +231,12 @@ export async function handleSubmit(seedFile: string): Promise<void> {
|
|
|
208
231
|
|
|
209
232
|
// ─── status ───────────────────────────────────────────────────────────────────
|
|
210
233
|
|
|
211
|
-
export async function handleStatus(
|
|
212
|
-
|
|
234
|
+
export async function handleStatus(
|
|
235
|
+
jobName: string | undefined,
|
|
236
|
+
opts: { root?: string } = {},
|
|
237
|
+
): Promise<void> {
|
|
238
|
+
const root = opts.root ?? process.env["PO_ROOT"] ?? process.cwd();
|
|
239
|
+
const orchestrator = new PipelineOrchestrator({ autoStart: false, root });
|
|
213
240
|
if (jobName) {
|
|
214
241
|
const result = await orchestrator.getStatus(jobName);
|
|
215
242
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -245,25 +272,7 @@ export async function handleAddPipeline(slug: string, root: string): Promise<voi
|
|
|
245
272
|
);
|
|
246
273
|
await Bun.write(join(tasksDir, "index.ts"), "export default {};\n");
|
|
247
274
|
|
|
248
|
-
|
|
249
|
-
let registry: Registry = { pipelines: {} };
|
|
250
|
-
try {
|
|
251
|
-
const text = await Bun.file(registryPath).text();
|
|
252
|
-
registry = JSON.parse(text) as Registry;
|
|
253
|
-
} catch {
|
|
254
|
-
// fallback to empty registry
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const pipelinePath = join(pipelineDir, "pipeline.json");
|
|
258
|
-
const taskRegistryPath = join(tasksDir, "index.ts");
|
|
259
|
-
registry.pipelines[slug] = {
|
|
260
|
-
name: slug,
|
|
261
|
-
description: "New pipeline",
|
|
262
|
-
pipelinePath,
|
|
263
|
-
taskRegistryPath,
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
await Bun.write(registryPath, JSON.stringify(registry, null, 2) + "\n");
|
|
275
|
+
await registerPipeline(root, slug, { name: slug, description: "New pipeline" });
|
|
267
276
|
} catch (err) {
|
|
268
277
|
console.error(`Error creating pipeline: ${err instanceof Error ? err.message : String(err)}`);
|
|
269
278
|
process.exit(1);
|
|
@@ -393,21 +402,21 @@ export async function handleAnalyze(taskPath: string): Promise<void> {
|
|
|
393
402
|
|
|
394
403
|
// ─── Hidden: _start-ui ───────────────────────────────────────────────────────
|
|
395
404
|
|
|
396
|
-
async function handleStartUi(): Promise<void> {
|
|
405
|
+
export async function handleStartUi(): Promise<void> {
|
|
406
|
+
// PO_ROOT is the inter-process transport set by `handleStart`; the boot
|
|
407
|
+
// function (startServer) owns the runtime resolution.
|
|
397
408
|
const { startServer } = await import("../ui/server/index.ts");
|
|
398
409
|
await startServer({
|
|
399
|
-
dataDir: process.env["PO_ROOT"]
|
|
410
|
+
dataDir: process.env["PO_ROOT"],
|
|
400
411
|
port: parseInt(process.env["PORT"] ?? "4000", 10),
|
|
401
412
|
});
|
|
402
413
|
}
|
|
403
414
|
|
|
404
415
|
// ─── Hidden: _start-orchestrator ─────────────────────────────────────────────
|
|
405
416
|
|
|
406
|
-
async function handleStartOrchestrator(): Promise<void> {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
process.exit(1);
|
|
410
|
-
}
|
|
417
|
+
export async function handleStartOrchestrator(): Promise<void> {
|
|
418
|
+
// PO_ROOT is the inter-process transport set by `handleStart`; the boot
|
|
419
|
+
// function (startOrchestrator) owns the runtime resolution.
|
|
411
420
|
const { startOrchestrator } = await import("../core/orchestrator.ts");
|
|
412
421
|
const handle = await startOrchestrator({ dataDir: process.env["PO_ROOT"] });
|
|
413
422
|
process.on("SIGINT", () => {
|
|
@@ -420,7 +429,8 @@ async function handleStartOrchestrator(): Promise<void> {
|
|
|
420
429
|
|
|
421
430
|
// ─── Hidden: _run-job ─────────────────────────────────────────────────────────
|
|
422
431
|
|
|
423
|
-
async function handleRunJob(jobId: string): Promise<void> {
|
|
432
|
+
export async function handleRunJob(jobId: string): Promise<void> {
|
|
433
|
+
// Boot resolution happens inside runPipelineJob; this handler only forwards.
|
|
424
434
|
const { runPipelineJob } = await import("../core/pipeline-runner.ts");
|
|
425
435
|
await runPipelineJob(jobId);
|
|
426
436
|
}
|
|
@@ -456,15 +466,17 @@ program
|
|
|
456
466
|
program
|
|
457
467
|
.command("submit <seed-file>")
|
|
458
468
|
.description("Submit a job from a seed JSON file")
|
|
459
|
-
.
|
|
460
|
-
|
|
469
|
+
.option("--root <path>", "Workspace root")
|
|
470
|
+
.action(async (seedFile: string, opts: { root?: string }) => {
|
|
471
|
+
await handleSubmit(seedFile, opts);
|
|
461
472
|
});
|
|
462
473
|
|
|
463
474
|
program
|
|
464
475
|
.command("status [job-name]")
|
|
465
476
|
.description("Query job status")
|
|
466
|
-
.
|
|
467
|
-
|
|
477
|
+
.option("--root <path>", "Workspace root")
|
|
478
|
+
.action(async (jobName: string | undefined, opts: { root?: string }) => {
|
|
479
|
+
await handleStatus(jobName, opts);
|
|
468
480
|
});
|
|
469
481
|
|
|
470
482
|
program
|
package/src/cli/types.ts
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
/** Central index of all pipelines in the workspace. */
|
|
2
|
-
export interface Registry {
|
|
3
|
-
pipelines: Record<string, PipelineRegistryEntry>;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
/** A single pipeline's entry in the registry. */
|
|
7
|
-
export interface PipelineRegistryEntry {
|
|
8
|
-
name: string;
|
|
9
|
-
description: string;
|
|
10
|
-
pipelinePath: string;
|
|
11
|
-
taskRegistryPath: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
1
|
/** A pipeline's configuration file. */
|
|
15
2
|
export interface PipelineConfig {
|
|
16
3
|
name: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readJsonFile, writeJsonFile } from "../core/json-file.ts";
|
|
1
2
|
import type { PipelineConfig } from "./types.ts";
|
|
2
3
|
|
|
3
4
|
export async function updatePipelineJson(
|
|
@@ -7,19 +8,7 @@ export async function updatePipelineJson(
|
|
|
7
8
|
): Promise<void> {
|
|
8
9
|
const filePath = `${root}/pipeline-config/${pipelineSlug}/pipeline.json`;
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
try {
|
|
12
|
-
const text = await Bun.file(filePath).text();
|
|
13
|
-
const parsed = JSON.parse(text) as PipelineConfig;
|
|
14
|
-
config = parsed;
|
|
15
|
-
} catch {
|
|
16
|
-
config = {
|
|
17
|
-
name: pipelineSlug,
|
|
18
|
-
version: "1.0.0",
|
|
19
|
-
description: "New pipeline",
|
|
20
|
-
tasks: [],
|
|
21
|
-
};
|
|
22
|
-
}
|
|
11
|
+
const config = await readJsonFile<PipelineConfig>(filePath);
|
|
23
12
|
|
|
24
13
|
if (!Array.isArray(config.tasks)) {
|
|
25
14
|
config.tasks = [];
|
|
@@ -29,5 +18,5 @@ export async function updatePipelineJson(
|
|
|
29
18
|
config.tasks.push(taskSlug);
|
|
30
19
|
}
|
|
31
20
|
|
|
32
|
-
await
|
|
21
|
+
await writeJsonFile(filePath, config);
|
|
33
22
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import Ajv from "ajv";
|
|
5
|
+
|
|
6
|
+
const schemaPath = join(process.cwd(), "config.schema.json");
|
|
7
|
+
const schema = JSON.parse(readFileSync(schemaPath, "utf-8")) as {
|
|
8
|
+
properties: { llm: { properties: { defaultProvider: { enum: string[] } } } };
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
12
|
+
const validate = ajv.compile(schema);
|
|
13
|
+
|
|
14
|
+
function llmConfig(defaultProvider: string): unknown {
|
|
15
|
+
return { llm: { defaultProvider } };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("config.schema.json defaultProvider enum", () => {
|
|
19
|
+
it("accepts canonical provider ids", () => {
|
|
20
|
+
expect(validate(llmConfig("openai"))).toBe(true);
|
|
21
|
+
expect(validate(llmConfig("zai"))).toBe(true);
|
|
22
|
+
expect(validate(llmConfig("claude-code"))).toBe(true);
|
|
23
|
+
expect(validate(llmConfig("moonshot"))).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("rejects legacy provider ids", () => {
|
|
27
|
+
expect(validate(llmConfig("zhipu"))).toBe(false);
|
|
28
|
+
expect(validate(llmConfig("claudecode"))).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("rejects unknown provider ids", () => {
|
|
32
|
+
expect(validate(llmConfig("neverheard"))).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("enum matches the canonical ProviderName set (no legacy spellings)", () => {
|
|
36
|
+
const enumValues = schema.properties.llm.properties.defaultProvider.enum;
|
|
37
|
+
expect(enumValues).not.toContain("zhipu");
|
|
38
|
+
expect(enumValues).not.toContain("claudecode");
|
|
39
|
+
expect(enumValues).toContain("zai");
|
|
40
|
+
expect(enumValues).toContain("claude-code");
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { join, relative, resolve, sep } from "node:path";
|
|
4
|
+
|
|
5
|
+
const FORBIDDEN_LITERALS: ReadonlySet<string> = new Set([
|
|
6
|
+
"zhipu",
|
|
7
|
+
"claudecode",
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
const STRING_LITERAL_RE = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g;
|
|
11
|
+
|
|
12
|
+
const ALLOWED_FILES: ReadonlySet<string> = new Set([
|
|
13
|
+
"src/config/models.ts",
|
|
14
|
+
"src/config/__tests__/models.test.ts",
|
|
15
|
+
"src/config/__tests__/config-schema.test.ts",
|
|
16
|
+
"src/config/__tests__/legacy-literal-guard.test.ts",
|
|
17
|
+
"src/llm/__tests__/legacy-normalization.test.ts",
|
|
18
|
+
"src/llm/__tests__/index.test.ts",
|
|
19
|
+
"src/providers/__tests__/types.test.ts",
|
|
20
|
+
"src/task-analysis/__tests__/analyzer.test.ts",
|
|
21
|
+
"src/task-analysis/__tests__/enrichers-schema-deducer.test.ts",
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
export interface LegacyLiteralViolation {
|
|
25
|
+
path: string;
|
|
26
|
+
literal: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SourceFile {
|
|
30
|
+
path: string;
|
|
31
|
+
content: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function extractStringLiterals(source: string): string[] {
|
|
35
|
+
const out: string[] = [];
|
|
36
|
+
for (const match of source.matchAll(STRING_LITERAL_RE)) {
|
|
37
|
+
const raw = match[0] as string;
|
|
38
|
+
out.push(raw.slice(1, -1));
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function scanFiles(
|
|
44
|
+
files: SourceFile[],
|
|
45
|
+
allowlist: ReadonlySet<string>,
|
|
46
|
+
): LegacyLiteralViolation[] {
|
|
47
|
+
const violations: LegacyLiteralViolation[] = [];
|
|
48
|
+
for (const file of files) {
|
|
49
|
+
if (allowlist.has(file.path)) continue;
|
|
50
|
+
for (const literal of extractStringLiterals(file.content)) {
|
|
51
|
+
if (FORBIDDEN_LITERALS.has(literal)) {
|
|
52
|
+
violations.push({ path: file.path, literal });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return violations;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function listSourceTsFiles(scanRoot: string, base: string): SourceFile[] {
|
|
60
|
+
const rootAbs = resolve(scanRoot);
|
|
61
|
+
const baseAbs = resolve(base);
|
|
62
|
+
const out: SourceFile[] = [];
|
|
63
|
+
|
|
64
|
+
function walk(dir: string): void {
|
|
65
|
+
for (const entry of readdirSync(dir)) {
|
|
66
|
+
const full = join(dir, entry);
|
|
67
|
+
const st = statSync(full);
|
|
68
|
+
if (st.isDirectory()) {
|
|
69
|
+
walk(full);
|
|
70
|
+
} else if (st.isFile() && /\.(ts|tsx)$/.test(entry)) {
|
|
71
|
+
const rel = relative(baseAbs, full).split(sep).join("/");
|
|
72
|
+
out.push({ path: rel, content: readFileSync(full, "utf-8") });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
walk(rootAbs);
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
describe("extractStringLiterals", () => {
|
|
82
|
+
it("captures a double-quoted forbidden literal", () => {
|
|
83
|
+
expect(extractStringLiterals('const p = "zhipu";')).toContain("zhipu");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("ignores the bare legacy word in a comment", () => {
|
|
87
|
+
expect(extractStringLiterals("// legacy zhipu id\nconst x = 1;")).toEqual([]);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("scanFiles — forbidden literal detection", () => {
|
|
92
|
+
it('flags "zhipu" outside the allowlist', () => {
|
|
93
|
+
const violations = scanFiles(
|
|
94
|
+
[{ path: "src/x.ts", content: 'const p = "zhipu";' }],
|
|
95
|
+
new Set(),
|
|
96
|
+
);
|
|
97
|
+
expect(violations).toEqual([{ path: "src/x.ts", literal: "zhipu" }]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('flags "claudecode" outside the allowlist', () => {
|
|
101
|
+
const violations = scanFiles(
|
|
102
|
+
[{ path: "src/y.ts", content: "const q = \"claudecode\";" }],
|
|
103
|
+
new Set(),
|
|
104
|
+
);
|
|
105
|
+
expect(violations).toEqual([{ path: "src/y.ts", literal: "claudecode" }]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("skips files listed in the allowlist", () => {
|
|
109
|
+
const violations = scanFiles(
|
|
110
|
+
[{ path: "src/config/models.ts", content: 'legacyAliases: ["zhipu"]' }],
|
|
111
|
+
new Set(["src/config/models.ts"]),
|
|
112
|
+
);
|
|
113
|
+
expect(violations).toEqual([]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("scanFiles — legitimate substrings are not flagged", () => {
|
|
118
|
+
it("ignores the ZHIPU_API_KEY env-var name", () => {
|
|
119
|
+
const violations = scanFiles(
|
|
120
|
+
[{ path: "src/env.ts", content: 'const k = "ZHIPU_API_KEY";' }],
|
|
121
|
+
new Set(),
|
|
122
|
+
);
|
|
123
|
+
expect(violations).toEqual([]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("ignores an import path that ends in zhipu.ts", () => {
|
|
127
|
+
const violations = scanFiles(
|
|
128
|
+
[
|
|
129
|
+
{
|
|
130
|
+
path: "src/llm/index.ts",
|
|
131
|
+
content: 'import { zaiChat } from "../providers/zhipu.ts";',
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
new Set(),
|
|
135
|
+
);
|
|
136
|
+
expect(violations).toEqual([]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("ignores the legacy word inside a comment", () => {
|
|
140
|
+
const violations = scanFiles(
|
|
141
|
+
[{ path: "src/cmt.ts", content: "// legacy zhipu id\nconst x = 1;" }],
|
|
142
|
+
new Set(),
|
|
143
|
+
);
|
|
144
|
+
expect(violations).toEqual([]);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("legacy-literal source guard (AC-3)", () => {
|
|
149
|
+
it("src/ has no forbidden legacy literals outside the allowlist", () => {
|
|
150
|
+
const cwd = process.cwd();
|
|
151
|
+
const files = listSourceTsFiles(join(cwd, "src"), cwd);
|
|
152
|
+
const violations = scanFiles(files, ALLOWED_FILES);
|
|
153
|
+
|
|
154
|
+
expect(violations).toEqual([]);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -6,17 +6,20 @@ import {
|
|
|
6
6
|
DEFAULT_MODEL_BY_PROVIDER,
|
|
7
7
|
FUNCTION_NAME_BY_ALIAS,
|
|
8
8
|
PROVIDER_FUNCTIONS,
|
|
9
|
+
PROVIDERS,
|
|
9
10
|
aliasToFunctionName,
|
|
10
11
|
getProviderFromAlias,
|
|
11
12
|
getModelFromAlias,
|
|
12
13
|
getModelConfig,
|
|
13
14
|
buildProviderFunctionsIndex,
|
|
15
|
+
normalizeLegacyProvider,
|
|
14
16
|
validateModelRegistry,
|
|
15
17
|
} from "../models";
|
|
16
|
-
import type { ModelConfigEntry } from "../models";
|
|
18
|
+
import type { ModelConfigEntry, ProviderName, ProviderMeta } from "../models";
|
|
17
19
|
|
|
18
20
|
const MODEL_COUNT = 51;
|
|
19
21
|
const PROVIDER_COUNT = 9;
|
|
22
|
+
const REGISTRY_PROVIDER_COUNT = 10;
|
|
20
23
|
|
|
21
24
|
describe("ModelAlias", () => {
|
|
22
25
|
it(`has exactly ${MODEL_COUNT} entries`, () => {
|
|
@@ -378,3 +381,138 @@ describe("validateModelRegistry — invariant failures", () => {
|
|
|
378
381
|
expect(() => validateModelRegistry(config, set)).not.toThrow();
|
|
379
382
|
});
|
|
380
383
|
});
|
|
384
|
+
|
|
385
|
+
describe("normalizeLegacyProvider", () => {
|
|
386
|
+
it('maps legacy "zhipu" to canonical "zai"', () => {
|
|
387
|
+
expect(normalizeLegacyProvider("zhipu")).toBe("zai");
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('maps legacy "claudecode" to canonical "claude-code"', () => {
|
|
391
|
+
expect(normalizeLegacyProvider("claudecode")).toBe("claude-code");
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('returns canonical "zai" for itself', () => {
|
|
395
|
+
expect(normalizeLegacyProvider("zai")).toBe("zai");
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('returns canonical "claude-code" for itself', () => {
|
|
399
|
+
expect(normalizeLegacyProvider("claude-code")).toBe("claude-code");
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('returns canonical "openai" for itself', () => {
|
|
403
|
+
expect(normalizeLegacyProvider("openai")).toBe("openai");
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("returns null for an unknown provider string", () => {
|
|
407
|
+
expect(normalizeLegacyProvider("unknown")).toBeNull();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("returns null for the empty string", () => {
|
|
411
|
+
expect(normalizeLegacyProvider("")).toBeNull();
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it("returns null for Object.prototype property names", () => {
|
|
415
|
+
expect(normalizeLegacyProvider("constructor")).toBeNull();
|
|
416
|
+
expect(normalizeLegacyProvider("toString")).toBeNull();
|
|
417
|
+
expect(normalizeLegacyProvider("valueOf")).toBeNull();
|
|
418
|
+
expect(normalizeLegacyProvider("hasOwnProperty")).toBeNull();
|
|
419
|
+
expect(normalizeLegacyProvider("__proto__")).toBeNull();
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
describe("PROVIDERS registry invariants", () => {
|
|
424
|
+
const CANONICAL_PROVIDER_IDS: ProviderName[] = [
|
|
425
|
+
"openai",
|
|
426
|
+
"anthropic",
|
|
427
|
+
"deepseek",
|
|
428
|
+
"gemini",
|
|
429
|
+
"zai",
|
|
430
|
+
"claude-code",
|
|
431
|
+
"moonshot",
|
|
432
|
+
"alibaba",
|
|
433
|
+
"opencode",
|
|
434
|
+
"mock",
|
|
435
|
+
];
|
|
436
|
+
|
|
437
|
+
it(`has exactly ${REGISTRY_PROVIDER_COUNT} rows, one per canonical ProviderName`, () => {
|
|
438
|
+
expect(Object.keys(PROVIDERS).length).toBe(REGISTRY_PROVIDER_COUNT);
|
|
439
|
+
for (const id of CANONICAL_PROVIDER_IDS) {
|
|
440
|
+
expect(id in PROVIDERS).toBe(true);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it("table is frozen", () => {
|
|
445
|
+
expect(Object.isFrozen(PROVIDERS)).toBe(true);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it("legacyAliases are unique across the registry (no alias maps to two providers)", () => {
|
|
449
|
+
const seen = new Map<string, ProviderName>();
|
|
450
|
+
for (const [canonical, meta] of Object.entries(PROVIDERS) as [ProviderName, ProviderMeta][]) {
|
|
451
|
+
for (const alias of meta.legacyAliases) {
|
|
452
|
+
const prior = seen.get(alias);
|
|
453
|
+
if (prior !== undefined) {
|
|
454
|
+
throw new Error(
|
|
455
|
+
`legacy alias "${alias}" is claimed by both "${prior}" and "${canonical}"`,
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
seen.set(alias, canonical);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it("no legacy alias collides with a canonical id", () => {
|
|
464
|
+
const canonicalSet = new Set<string>(CANONICAL_PROVIDER_IDS);
|
|
465
|
+
for (const meta of Object.values(PROVIDERS)) {
|
|
466
|
+
for (const alias of meta.legacyAliases) {
|
|
467
|
+
expect(canonicalSet.has(alias)).toBe(false);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it("MODEL_CONFIG providers are all registered catalog providers (no orphans, no mock)", () => {
|
|
473
|
+
for (const entry of Object.values(MODEL_CONFIG)) {
|
|
474
|
+
expect(entry.provider in PROVIDERS).toBe(true);
|
|
475
|
+
expect(entry.provider).not.toBe("mock");
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it("MODEL_CONFIG / DEFAULT_MODEL_BY_PROVIDER / PROVIDER_FUNCTIONS exclude only mock from the registry", () => {
|
|
480
|
+
const registryKeys = new Set<string>(Object.keys(PROVIDERS));
|
|
481
|
+
const catalogKeys = new Set<string>(Object.keys(PROVIDER_FUNCTIONS));
|
|
482
|
+
const defaultKeys = new Set<string>(
|
|
483
|
+
Object.keys(DEFAULT_MODEL_BY_PROVIDER) as string[],
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
for (const key of catalogKeys) expect(registryKeys.has(key)).toBe(true);
|
|
487
|
+
for (const key of defaultKeys) expect(registryKeys.has(key)).toBe(true);
|
|
488
|
+
|
|
489
|
+
const missingFromCatalog = [...registryKeys].filter(
|
|
490
|
+
(k) => !catalogKeys.has(k),
|
|
491
|
+
);
|
|
492
|
+
const missingFromDefaults = [...registryKeys].filter(
|
|
493
|
+
(k) => !defaultKeys.has(k),
|
|
494
|
+
);
|
|
495
|
+
expect(missingFromCatalog).toEqual(["mock"]);
|
|
496
|
+
expect(missingFromDefaults).toEqual(["mock"]);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it("canonical<->alias round-trip via normalizeLegacyProvider", () => {
|
|
500
|
+
for (const [canonical, meta] of Object.entries(PROVIDERS) as [ProviderName, ProviderMeta][]) {
|
|
501
|
+
expect(normalizeLegacyProvider(canonical)).toBe(canonical);
|
|
502
|
+
for (const alias of meta.legacyAliases) {
|
|
503
|
+
expect(normalizeLegacyProvider(alias)).toBe(canonical);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it("CLI providers declare sampling: []", () => {
|
|
509
|
+
expect(PROVIDERS["claude-code"]!.sampling).toEqual([]);
|
|
510
|
+
expect(PROVIDERS.opencode!.sampling).toEqual([]);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it("mock is runtime-only: empty envKeys/sampling, isCLI false", () => {
|
|
514
|
+
expect(PROVIDERS.mock!.envKeys).toEqual([]);
|
|
515
|
+
expect(PROVIDERS.mock!.sampling).toEqual([]);
|
|
516
|
+
expect(PROVIDERS.mock!.isCLI).toBe(false);
|
|
517
|
+
});
|
|
518
|
+
});
|