@ryanfw/prompt-orchestration-pipeline 1.3.2 → 1.3.4
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 +80 -4
- package/package.json +1 -4
- package/src/api/__tests__/index.test.ts +443 -32
- package/src/api/index.ts +108 -127
- package/src/cli/__tests__/analyze-task.test.ts +40 -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__/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/__tests__/statuses.test.ts +41 -0
- 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/config/statuses.ts +20 -1
- package/src/core/__tests__/agent-step.test.ts +115 -0
- package/src/core/__tests__/config.test.ts +545 -105
- package/src/core/__tests__/core-boot-resolution.test.ts +181 -0
- package/src/core/__tests__/job-concurrency.test.ts +260 -0
- package/src/core/__tests__/job-submission.test.ts +396 -0
- package/src/core/__tests__/job-view.test.ts +1341 -0
- 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 +615 -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 +910 -0
- package/src/core/__tests__/seed-naming.test.ts +57 -0
- package/src/core/__tests__/single-derivation.test.ts +159 -0
- 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 +4 -1
- package/src/core/agent-types.ts +5 -4
- package/src/core/config.ts +134 -222
- package/src/core/control.ts +3 -0
- package/src/core/job-concurrency.ts +56 -20
- package/src/core/job-submission.ts +133 -0
- package/src/core/job-view.ts +473 -0
- 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 +200 -74
- package/src/core/pipeline-runner.ts +85 -53
- package/src/core/redact.ts +40 -0
- package/src/core/runner-liveness.ts +280 -0
- package/src/core/seed-naming.ts +9 -0
- package/src/core/status-writer.ts +27 -33
- 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__/index.test.ts +684 -33
- package/src/llm/index.ts +310 -67
- 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 +99 -14
- 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 +27 -0
- 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 +85 -0
- package/src/providers/__tests__/zero-fill-guard.test.ts +62 -0
- package/src/providers/__tests__/zhipu.test.ts +27 -14
- 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 +15 -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 +29 -4
- package/src/providers/zhipu.ts +15 -44
- package/src/task-analysis/__tests__/analyzer.test.ts +0 -0
- package/src/task-analysis/__tests__/enrichers-schema-deducer.test.ts +34 -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 +91 -0
- package/src/task-analysis/enrichers/schema-deducer.ts +13 -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 +203 -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 +41 -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 +16 -20
- package/src/ui/components/DAGGrid.tsx +6 -1
- package/src/ui/components/JobDetail.tsx +12 -4
- package/src/ui/components/JobTable.tsx +41 -13
- 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 +137 -1
- 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 +35 -15
- package/src/ui/dist/assets/{index--RH3sAt3.js → index-DN3-zvtP.js} +4324 -263
- package/src/ui/dist/assets/index-DN3-zvtP.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/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__/config-bridge.test.ts +9 -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__/index.test.ts +63 -0
- package/src/ui/server/__tests__/job-control-endpoints.test.ts +512 -2
- package/src/ui/server/__tests__/job-endpoints.test.ts +158 -2
- 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 +70 -0
- package/src/ui/server/config-bridge-node.ts +8 -26
- package/src/ui/server/config-bridge.ts +3 -4
- 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__/meta-endpoint.test.ts +1 -0
- 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 +2 -6
- package/src/ui/server/endpoints/job-control-endpoints.ts +129 -141
- package/src/ui/server/endpoints/job-endpoints.ts +7 -0
- package/src/ui/server/endpoints/meta-endpoint.ts +1 -1
- 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-reader.ts +14 -2
- 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 +120 -14
- package/src/ui/state/__tests__/types.test.ts +104 -5
- package/src/ui/state/snapshot.ts +2 -2
- package/src/ui/state/transformers/__tests__/list-transformer.test.ts +85 -2
- package/src/ui/state/transformers/__tests__/status-transformer.test.ts +250 -22
- package/src/ui/state/transformers/list-transformer.ts +13 -3
- package/src/ui/state/transformers/status-transformer.ts +36 -170
- package/src/ui/state/types.ts +15 -48
- 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--RH3sAt3.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
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
// ── src/task-analysis/enrichers/analysis-writer.ts ──
|
|
2
|
-
// Persists TaskAnalysis to disk as JSON.
|
|
3
|
-
|
|
4
|
-
import { mkdir } from "node:fs/promises";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import type { TaskAnalysis } from "../types.ts";
|
|
7
|
-
|
|
8
|
-
function validate(analysisData: TaskAnalysis): void {
|
|
9
|
-
const { taskFilePath, stages, models, artifacts } = analysisData;
|
|
10
|
-
|
|
11
|
-
if (taskFilePath === null || taskFilePath === undefined || taskFilePath === "") {
|
|
12
|
-
throw new Error(
|
|
13
|
-
`Invalid taskFilePath: expected a non-null, non-empty string, got ${taskFilePath === "" ? "empty string" : String(taskFilePath)}`,
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (!Array.isArray(stages)) {
|
|
18
|
-
throw new Error(
|
|
19
|
-
`Invalid stages: expected an array, got ${typeof stages}`,
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (!Array.isArray(models)) {
|
|
24
|
-
throw new Error(
|
|
25
|
-
`Invalid models: expected an array, got ${typeof models}`,
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
typeof artifacts !== "object" ||
|
|
31
|
-
artifacts === null ||
|
|
32
|
-
!Array.isArray(artifacts.reads) ||
|
|
33
|
-
!Array.isArray(artifacts.writes)
|
|
34
|
-
) {
|
|
35
|
-
throw new Error(
|
|
36
|
-
`Invalid artifacts: expected an object with reads and writes arrays`,
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (
|
|
41
|
-
"unresolvedReads" in artifacts &&
|
|
42
|
-
!Array.isArray(artifacts.unresolvedReads)
|
|
43
|
-
) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
`Invalid artifacts.unresolvedReads: expected an array, got ${typeof artifacts.unresolvedReads}`,
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
"unresolvedWrites" in artifacts &&
|
|
51
|
-
!Array.isArray(artifacts.unresolvedWrites)
|
|
52
|
-
) {
|
|
53
|
-
throw new Error(
|
|
54
|
-
`Invalid artifacts.unresolvedWrites: expected an array, got ${typeof artifacts.unresolvedWrites}`,
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function writeAnalysisFile(
|
|
60
|
-
pipelinePath: string,
|
|
61
|
-
taskName: string,
|
|
62
|
-
analysisData: TaskAnalysis,
|
|
63
|
-
): Promise<void> {
|
|
64
|
-
validate(analysisData);
|
|
65
|
-
|
|
66
|
-
const analysisDir = path.join(pipelinePath, "analysis");
|
|
67
|
-
await mkdir(analysisDir, { recursive: true });
|
|
68
|
-
|
|
69
|
-
const data = { ...analysisData, analyzedAt: new Date().toISOString() };
|
|
70
|
-
|
|
71
|
-
await Bun.write(
|
|
72
|
-
path.join(analysisDir, `${taskName}.analysis.json`),
|
|
73
|
-
JSON.stringify(data, null, 2),
|
|
74
|
-
);
|
|
75
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
// ── src/task-analysis/enrichers/artifact-resolver.ts ──
|
|
2
|
-
// LLM-powered resolution of dynamic artifact references.
|
|
3
|
-
|
|
4
|
-
import { chat } from "../../llm/index.ts";
|
|
5
|
-
import type { UnresolvedArtifactDescriptor, ArtifactResolution } from "../types.ts";
|
|
6
|
-
|
|
7
|
-
const FALLBACK: ArtifactResolution = {
|
|
8
|
-
resolvedFileName: null,
|
|
9
|
-
confidence: 0,
|
|
10
|
-
reasoning: "Failed to analyze artifact reference",
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
function clampConfidence(value: unknown): number {
|
|
14
|
-
if (typeof value !== "number" || !isFinite(value)) return 0;
|
|
15
|
-
if (value < 0) return 0;
|
|
16
|
-
if (value > 1) return 1;
|
|
17
|
-
return value;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function resolveArtifactReference(
|
|
21
|
-
taskCode: string,
|
|
22
|
-
unresolvedArtifact: UnresolvedArtifactDescriptor,
|
|
23
|
-
availableArtifacts: string[],
|
|
24
|
-
): Promise<ArtifactResolution> {
|
|
25
|
-
try {
|
|
26
|
-
const messages = [
|
|
27
|
-
{
|
|
28
|
-
role: "system" as const,
|
|
29
|
-
content:
|
|
30
|
-
"You are a code analysis expert. Given pipeline task source code, a dynamic artifact expression, and a list of available artifact filenames, determine which artifact filename the expression resolves to. Respond with a JSON object containing: resolvedFileName (string or null), confidence (number between 0 and 1), and reasoning (string).",
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
role: "user" as const,
|
|
34
|
-
content: `Analyze the following pipeline task source code and determine which artifact filename the dynamic expression resolves to.
|
|
35
|
-
|
|
36
|
-
Task source code:
|
|
37
|
-
\`\`\`
|
|
38
|
-
${taskCode}
|
|
39
|
-
\`\`\`
|
|
40
|
-
|
|
41
|
-
Dynamic expression: ${unresolvedArtifact.expression}
|
|
42
|
-
Code context: ${unresolvedArtifact.codeContext}
|
|
43
|
-
Stage: ${unresolvedArtifact.stage}
|
|
44
|
-
|
|
45
|
-
Available artifact filenames:
|
|
46
|
-
${availableArtifacts.map((f) => `- ${f}`).join("\n")}
|
|
47
|
-
|
|
48
|
-
Respond with a JSON object with these fields:
|
|
49
|
-
- resolvedFileName: the matching filename from the available list, or null if you cannot determine it
|
|
50
|
-
- confidence: a number between 0 and 1 indicating your confidence
|
|
51
|
-
- reasoning: a string explaining your reasoning`,
|
|
52
|
-
},
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
const response = await chat({
|
|
56
|
-
provider: "deepseek",
|
|
57
|
-
model: "deepseek-chat",
|
|
58
|
-
messages,
|
|
59
|
-
temperature: 0,
|
|
60
|
-
responseFormat: { type: "json_object" },
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const { content } = response;
|
|
64
|
-
|
|
65
|
-
if (typeof content !== "object" || content === null) {
|
|
66
|
-
throw new Error(
|
|
67
|
-
`Unexpected gateway response: content must be a non-null object, got ${typeof content}`,
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const { resolvedFileName, confidence, reasoning } = content as Record<string, unknown>;
|
|
72
|
-
|
|
73
|
-
let sanitizedFileName = typeof resolvedFileName === "string" ? resolvedFileName : null;
|
|
74
|
-
let sanitizedConfidence = clampConfidence(confidence);
|
|
75
|
-
|
|
76
|
-
if (sanitizedFileName !== null && !availableArtifacts.includes(sanitizedFileName)) {
|
|
77
|
-
sanitizedFileName = null;
|
|
78
|
-
sanitizedConfidence = 0;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
resolvedFileName: sanitizedFileName,
|
|
83
|
-
confidence: sanitizedConfidence,
|
|
84
|
-
reasoning: typeof reasoning === "string" ? reasoning : "",
|
|
85
|
-
};
|
|
86
|
-
} catch {
|
|
87
|
-
return FALLBACK;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import traverse from "@babel/traverse";
|
|
2
|
-
import generate from "@babel/generator";
|
|
3
|
-
import type { File as BabelFile } from "@babel/types";
|
|
4
|
-
import type { NodePath } from "@babel/traverse";
|
|
5
|
-
import type {
|
|
6
|
-
ArtifactRead,
|
|
7
|
-
ArtifactWrite,
|
|
8
|
-
UnresolvedRead,
|
|
9
|
-
UnresolvedWrite,
|
|
10
|
-
} from "../types.ts";
|
|
11
|
-
import { getStageName, isInsideTryCatch } from "../utils/ast.ts";
|
|
12
|
-
|
|
13
|
-
export function extractCodeContext(
|
|
14
|
-
path: NodePath,
|
|
15
|
-
sourceCode: string
|
|
16
|
-
): string {
|
|
17
|
-
if (!sourceCode || !path.node.loc) return "";
|
|
18
|
-
const lines = sourceCode.split("\n");
|
|
19
|
-
const line = path.node.loc.start.line - 1; // convert to 0-based
|
|
20
|
-
const start = Math.max(0, line - 2);
|
|
21
|
-
const end = Math.min(lines.length, line + 3);
|
|
22
|
-
return lines.slice(start, end).join("\n");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function resolveFileName(
|
|
26
|
-
argNode: import("@babel/types").Expression | import("@babel/types").SpreadElement
|
|
27
|
-
): string | null {
|
|
28
|
-
if (argNode.type === "StringLiteral") {
|
|
29
|
-
return argNode.value;
|
|
30
|
-
}
|
|
31
|
-
if (argNode.type === "TemplateLiteral") {
|
|
32
|
-
if (argNode.expressions.length === 0) {
|
|
33
|
-
return argNode.quasis.map((q) => q.value.cooked ?? "").join("");
|
|
34
|
-
}
|
|
35
|
-
// Template with expressions: generate source text, strip backticks
|
|
36
|
-
const src = generate(argNode).code;
|
|
37
|
-
return src.replace(/^`|`$/g, "");
|
|
38
|
-
}
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function extractArtifactReads(
|
|
43
|
-
ast: BabelFile,
|
|
44
|
-
sourceCode?: string
|
|
45
|
-
): { reads: ArtifactRead[]; unresolvedReads: UnresolvedRead[] } {
|
|
46
|
-
const reads: ArtifactRead[] = [];
|
|
47
|
-
const unresolvedReads: UnresolvedRead[] = [];
|
|
48
|
-
|
|
49
|
-
traverse(ast, {
|
|
50
|
-
CallExpression(path) {
|
|
51
|
-
const { callee } = path.node;
|
|
52
|
-
if (
|
|
53
|
-
callee.type !== "MemberExpression" ||
|
|
54
|
-
callee.object.type !== "Identifier" ||
|
|
55
|
-
callee.object.name !== "io" ||
|
|
56
|
-
callee.property.type !== "Identifier" ||
|
|
57
|
-
callee.property.name !== "readArtifact"
|
|
58
|
-
)
|
|
59
|
-
return;
|
|
60
|
-
|
|
61
|
-
const stage = getStageName(path);
|
|
62
|
-
if (stage === null) {
|
|
63
|
-
const loc = path.node.loc;
|
|
64
|
-
throw new Error(
|
|
65
|
-
`io.readArtifact call at line ${loc?.start.line ?? "unknown"}, column ${loc?.start.column ?? "unknown"} is not inside an exported function`
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const arg = path.node.arguments[0];
|
|
70
|
-
if (!arg || arg.type === "SpreadElement" || arg.type === "ArgumentPlaceholder") return;
|
|
71
|
-
|
|
72
|
-
const required = !isInsideTryCatch(path);
|
|
73
|
-
const fileName = resolveFileName(arg);
|
|
74
|
-
|
|
75
|
-
if (fileName !== null) {
|
|
76
|
-
reads.push({ fileName, stage, required });
|
|
77
|
-
} else {
|
|
78
|
-
const expression = generate(arg).code;
|
|
79
|
-
const codeContext = sourceCode
|
|
80
|
-
? extractCodeContext(path, sourceCode)
|
|
81
|
-
: "";
|
|
82
|
-
const location = {
|
|
83
|
-
line: arg.loc?.start.line ?? 0,
|
|
84
|
-
column: arg.loc?.start.column ?? 0,
|
|
85
|
-
};
|
|
86
|
-
unresolvedReads.push({ expression, codeContext, stage, required, location });
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
return { reads, unresolvedReads };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function extractArtifactWrites(
|
|
95
|
-
ast: BabelFile,
|
|
96
|
-
sourceCode?: string
|
|
97
|
-
): { writes: ArtifactWrite[]; unresolvedWrites: UnresolvedWrite[] } {
|
|
98
|
-
const writes: ArtifactWrite[] = [];
|
|
99
|
-
const unresolvedWrites: UnresolvedWrite[] = [];
|
|
100
|
-
|
|
101
|
-
traverse(ast, {
|
|
102
|
-
CallExpression(path) {
|
|
103
|
-
const { callee } = path.node;
|
|
104
|
-
if (
|
|
105
|
-
callee.type !== "MemberExpression" ||
|
|
106
|
-
callee.object.type !== "Identifier" ||
|
|
107
|
-
callee.object.name !== "io" ||
|
|
108
|
-
callee.property.type !== "Identifier" ||
|
|
109
|
-
callee.property.name !== "writeArtifact"
|
|
110
|
-
)
|
|
111
|
-
return;
|
|
112
|
-
|
|
113
|
-
const stage = getStageName(path);
|
|
114
|
-
if (stage === null) {
|
|
115
|
-
const loc = path.node.loc;
|
|
116
|
-
throw new Error(
|
|
117
|
-
`io.writeArtifact call at line ${loc?.start.line ?? "unknown"}, column ${loc?.start.column ?? "unknown"} is not inside an exported function`
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const arg = path.node.arguments[0];
|
|
122
|
-
if (!arg || arg.type === "SpreadElement" || arg.type === "ArgumentPlaceholder") return;
|
|
123
|
-
|
|
124
|
-
const fileName = resolveFileName(arg);
|
|
125
|
-
|
|
126
|
-
if (fileName !== null) {
|
|
127
|
-
writes.push({ fileName, stage });
|
|
128
|
-
} else {
|
|
129
|
-
const expression = generate(arg).code;
|
|
130
|
-
const codeContext = sourceCode
|
|
131
|
-
? extractCodeContext(path, sourceCode)
|
|
132
|
-
: "";
|
|
133
|
-
const location = {
|
|
134
|
-
line: arg.loc?.start.line ?? 0,
|
|
135
|
-
column: arg.loc?.start.column ?? 0,
|
|
136
|
-
};
|
|
137
|
-
unresolvedWrites.push({ expression, codeContext, stage, location });
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
return { writes, unresolvedWrites };
|
|
143
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import traverse from "@babel/traverse";
|
|
2
|
-
import type { NodePath } from "@babel/traverse";
|
|
3
|
-
import type { File as BabelFile } from "@babel/types";
|
|
4
|
-
import type { ModelCall } from "../types.ts";
|
|
5
|
-
import { getStageName } from "../utils/ast.ts";
|
|
6
|
-
|
|
7
|
-
function isLLMDirectAccess(
|
|
8
|
-
path: NodePath
|
|
9
|
-
): { provider: string; method: string } | null {
|
|
10
|
-
const { node } = path;
|
|
11
|
-
if (node.type !== "CallExpression") return null;
|
|
12
|
-
const { callee } = node;
|
|
13
|
-
if (callee.type !== "MemberExpression") return null;
|
|
14
|
-
if (callee.property.type !== "Identifier") return null;
|
|
15
|
-
|
|
16
|
-
const method = callee.property.name;
|
|
17
|
-
const obj = callee.object;
|
|
18
|
-
if (obj.type !== "MemberExpression") return null;
|
|
19
|
-
if (obj.object.type !== "Identifier") return null;
|
|
20
|
-
if (obj.object.name !== "llm") return null;
|
|
21
|
-
if (obj.property.type !== "Identifier") return null;
|
|
22
|
-
|
|
23
|
-
return { provider: obj.property.name, method };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function isLLMDestructuredAccess(
|
|
27
|
-
path: NodePath
|
|
28
|
-
): { provider: string; method: string } | null {
|
|
29
|
-
const { node } = path;
|
|
30
|
-
if (node.type !== "CallExpression") return null;
|
|
31
|
-
const { callee } = node;
|
|
32
|
-
if (callee.type !== "MemberExpression") return null;
|
|
33
|
-
if (callee.object.type !== "Identifier") return null;
|
|
34
|
-
if (callee.property.type !== "Identifier") return null;
|
|
35
|
-
|
|
36
|
-
const identName = callee.object.name;
|
|
37
|
-
const method = callee.property.name;
|
|
38
|
-
|
|
39
|
-
const binding = path.scope.getBinding(identName);
|
|
40
|
-
if (!binding) return null;
|
|
41
|
-
|
|
42
|
-
const bindingPath = binding.path;
|
|
43
|
-
|
|
44
|
-
// Pattern 2: const { provider } = llm;
|
|
45
|
-
if (bindingPath.node.type === "VariableDeclarator") {
|
|
46
|
-
const { id, init } = bindingPath.node;
|
|
47
|
-
if (
|
|
48
|
-
init?.type === "Identifier" &&
|
|
49
|
-
init.name === "llm" &&
|
|
50
|
-
id.type === "ObjectPattern"
|
|
51
|
-
) {
|
|
52
|
-
return { provider: identName, method };
|
|
53
|
-
}
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Pattern 3: ({ llm: { provider } }) => ...
|
|
58
|
-
// Babel registers the binding for the destructured identifier at the outer
|
|
59
|
-
// ObjectPattern level (the whole param pattern). We check that the outer
|
|
60
|
-
// ObjectPattern has a property keyed "llm" whose value is an ObjectPattern
|
|
61
|
-
// containing the identifier, and that the parent is a function node.
|
|
62
|
-
if (bindingPath.node.type === "ObjectPattern") {
|
|
63
|
-
const parentNode = bindingPath.parentPath?.node;
|
|
64
|
-
const isParam =
|
|
65
|
-
parentNode?.type === "FunctionDeclaration" ||
|
|
66
|
-
parentNode?.type === "FunctionExpression" ||
|
|
67
|
-
parentNode?.type === "ArrowFunctionExpression";
|
|
68
|
-
if (!isParam) return null;
|
|
69
|
-
|
|
70
|
-
const outerPattern = bindingPath.node;
|
|
71
|
-
for (const prop of outerPattern.properties) {
|
|
72
|
-
if (
|
|
73
|
-
prop.type === "ObjectProperty" &&
|
|
74
|
-
prop.key.type === "Identifier" &&
|
|
75
|
-
prop.key.name === "llm" &&
|
|
76
|
-
prop.value.type === "ObjectPattern"
|
|
77
|
-
) {
|
|
78
|
-
const innerPattern = prop.value;
|
|
79
|
-
const hasIdent = innerPattern.properties.some(
|
|
80
|
-
(p) =>
|
|
81
|
-
p.type === "ObjectProperty" &&
|
|
82
|
-
p.key.type === "Identifier" &&
|
|
83
|
-
p.key.name === identName
|
|
84
|
-
);
|
|
85
|
-
if (hasIdent) return { provider: identName, method };
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function extractLLMCalls(ast: BabelFile): ModelCall[] {
|
|
95
|
-
const calls: ModelCall[] = [];
|
|
96
|
-
|
|
97
|
-
traverse(ast, {
|
|
98
|
-
CallExpression(path) {
|
|
99
|
-
const direct = isLLMDirectAccess(path);
|
|
100
|
-
const destructured = direct === null ? isLLMDestructuredAccess(path) : null;
|
|
101
|
-
const match = direct ?? destructured;
|
|
102
|
-
if (!match) return;
|
|
103
|
-
|
|
104
|
-
const stage = getStageName(path);
|
|
105
|
-
if (stage === null) {
|
|
106
|
-
const loc = path.node.loc;
|
|
107
|
-
throw new Error(
|
|
108
|
-
`LLM call at line ${loc?.start.line ?? "unknown"}, column ${loc?.start.column ?? "unknown"} is not inside an exported function`
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
calls.push({ provider: match.provider, method: match.method, stage });
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
return calls;
|
|
117
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import traverse from "@babel/traverse";
|
|
2
|
-
import type { File as BabelFile } from "@babel/types";
|
|
3
|
-
import type { Stage } from "../types.ts";
|
|
4
|
-
|
|
5
|
-
export function extractStages(ast: BabelFile): Stage[] {
|
|
6
|
-
const stages: Stage[] = [];
|
|
7
|
-
|
|
8
|
-
traverse(ast, {
|
|
9
|
-
ExportNamedDeclaration(path) {
|
|
10
|
-
const { declaration } = path.node;
|
|
11
|
-
if (declaration == null) return;
|
|
12
|
-
|
|
13
|
-
if (declaration.type === "FunctionDeclaration") {
|
|
14
|
-
const name = declaration.id?.name;
|
|
15
|
-
if (name == null) return;
|
|
16
|
-
stages.push({
|
|
17
|
-
name,
|
|
18
|
-
order: path.node.loc?.start.line ?? 0,
|
|
19
|
-
isAsync: declaration.async ?? false,
|
|
20
|
-
});
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (declaration.type === "VariableDeclaration") {
|
|
25
|
-
const declarator = declaration.declarations[0];
|
|
26
|
-
if (declarator == null) return;
|
|
27
|
-
if (declarator.id.type !== "Identifier") return;
|
|
28
|
-
const init = declarator.init;
|
|
29
|
-
if (
|
|
30
|
-
init == null ||
|
|
31
|
-
(init.type !== "ArrowFunctionExpression" &&
|
|
32
|
-
init.type !== "FunctionExpression")
|
|
33
|
-
)
|
|
34
|
-
return;
|
|
35
|
-
stages.push({
|
|
36
|
-
name: declarator.id.name,
|
|
37
|
-
order: path.node.loc?.start.line ?? 0,
|
|
38
|
-
isAsync: init.async ?? false,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
return stages.sort((a, b) => a.order - b.order);
|
|
45
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { parse } from "@babel/parser";
|
|
2
|
-
import type { File as BabelFile } from "@babel/types";
|
|
3
|
-
|
|
4
|
-
export function parseTaskSource(code: string): BabelFile {
|
|
5
|
-
try {
|
|
6
|
-
return parse(code, { sourceType: "module", plugins: ["jsx"] });
|
|
7
|
-
} catch (err) {
|
|
8
|
-
const loc =
|
|
9
|
-
err != null &&
|
|
10
|
-
typeof err === "object" &&
|
|
11
|
-
"loc" in err &&
|
|
12
|
-
err.loc != null &&
|
|
13
|
-
typeof err.loc === "object" &&
|
|
14
|
-
"line" in err.loc &&
|
|
15
|
-
"column" in err.loc
|
|
16
|
-
? `line ${err.loc.line}, column ${err.loc.column}`
|
|
17
|
-
: "unknown location";
|
|
18
|
-
throw new Error(`Syntax error at ${loc}`, { cause: err });
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { NodePath } from "@babel/traverse";
|
|
2
|
-
import type {
|
|
3
|
-
TryStatement,
|
|
4
|
-
ExportNamedDeclaration,
|
|
5
|
-
FunctionDeclaration,
|
|
6
|
-
VariableDeclaration,
|
|
7
|
-
} from "@babel/types";
|
|
8
|
-
|
|
9
|
-
export function isInsideTryCatch(path: NodePath): boolean {
|
|
10
|
-
let current: NodePath | null = path.parentPath;
|
|
11
|
-
while (current !== null) {
|
|
12
|
-
if (current.isTryStatement()) {
|
|
13
|
-
const tryNode = current.node as TryStatement;
|
|
14
|
-
const blockPath = current.get("block") as NodePath;
|
|
15
|
-
if (blockPath.node === tryNode.block) {
|
|
16
|
-
return true;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
current = current.parentPath;
|
|
20
|
-
}
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function getStageName(path: NodePath): string | null {
|
|
25
|
-
let current: NodePath | null = path.parentPath;
|
|
26
|
-
while (current !== null) {
|
|
27
|
-
if (current.isExportNamedDeclaration()) {
|
|
28
|
-
const node = current.node as ExportNamedDeclaration;
|
|
29
|
-
const decl = node.declaration;
|
|
30
|
-
if (decl === null || decl === undefined) return null;
|
|
31
|
-
if (decl.type === "FunctionDeclaration") {
|
|
32
|
-
return (decl as FunctionDeclaration).id?.name ?? null;
|
|
33
|
-
}
|
|
34
|
-
if (decl.type === "VariableDeclaration") {
|
|
35
|
-
const declarator = (decl as VariableDeclaration).declarations[0];
|
|
36
|
-
if (declarator?.id.type === "Identifier") {
|
|
37
|
-
return declarator.id.name;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
current = current.parentPath;
|
|
43
|
-
}
|
|
44
|
-
return null;
|
|
45
|
-
}
|