@ryanfw/prompt-orchestration-pipeline 0.5.0 → 0.7.0

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.
Files changed (67) hide show
  1. package/README.md +1 -2
  2. package/package.json +1 -2
  3. package/src/api/validators/json.js +39 -0
  4. package/src/components/DAGGrid.jsx +392 -303
  5. package/src/components/JobCard.jsx +14 -12
  6. package/src/components/JobDetail.jsx +54 -51
  7. package/src/components/JobTable.jsx +72 -23
  8. package/src/components/Layout.jsx +145 -42
  9. package/src/components/LiveText.jsx +47 -0
  10. package/src/components/PageSubheader.jsx +75 -0
  11. package/src/components/TaskDetailSidebar.jsx +216 -0
  12. package/src/components/TimerText.jsx +82 -0
  13. package/src/components/UploadSeed.jsx +0 -70
  14. package/src/components/ui/Logo.jsx +16 -0
  15. package/src/components/ui/RestartJobModal.jsx +140 -0
  16. package/src/components/ui/toast.jsx +138 -0
  17. package/src/config/models.js +322 -0
  18. package/src/config/statuses.js +119 -0
  19. package/src/core/config.js +4 -34
  20. package/src/core/file-io.js +13 -28
  21. package/src/core/module-loader.js +54 -40
  22. package/src/core/pipeline-runner.js +65 -26
  23. package/src/core/status-writer.js +213 -58
  24. package/src/core/symlink-bridge.js +57 -0
  25. package/src/core/symlink-utils.js +94 -0
  26. package/src/core/task-runner.js +321 -437
  27. package/src/llm/index.js +258 -86
  28. package/src/pages/Code.jsx +351 -0
  29. package/src/pages/PipelineDetail.jsx +124 -15
  30. package/src/pages/PromptPipelineDashboard.jsx +20 -88
  31. package/src/providers/anthropic.js +83 -69
  32. package/src/providers/base.js +52 -0
  33. package/src/providers/deepseek.js +20 -21
  34. package/src/providers/gemini.js +226 -0
  35. package/src/providers/openai.js +36 -106
  36. package/src/providers/zhipu.js +136 -0
  37. package/src/ui/client/adapters/job-adapter.js +42 -28
  38. package/src/ui/client/api.js +134 -0
  39. package/src/ui/client/hooks/useJobDetailWithUpdates.js +65 -179
  40. package/src/ui/client/index.css +15 -0
  41. package/src/ui/client/index.html +2 -1
  42. package/src/ui/client/main.jsx +19 -14
  43. package/src/ui/client/time-store.js +161 -0
  44. package/src/ui/config-bridge.js +15 -24
  45. package/src/ui/config-bridge.node.js +15 -24
  46. package/src/ui/dist/assets/{index-CxcrauYR.js → index-DqkbzXZ1.js} +2132 -1086
  47. package/src/ui/dist/assets/style-DBF9NQGk.css +62 -0
  48. package/src/ui/dist/index.html +4 -3
  49. package/src/ui/job-reader.js +0 -108
  50. package/src/ui/public/favicon.svg +12 -0
  51. package/src/ui/server.js +252 -0
  52. package/src/ui/sse-enhancer.js +0 -1
  53. package/src/ui/transformers/list-transformer.js +32 -12
  54. package/src/ui/transformers/status-transformer.js +29 -42
  55. package/src/utils/dag.js +8 -4
  56. package/src/utils/duration.js +13 -19
  57. package/src/utils/formatters.js +27 -0
  58. package/src/utils/geometry-equality.js +83 -0
  59. package/src/utils/pipelines.js +5 -1
  60. package/src/utils/time-utils.js +40 -0
  61. package/src/utils/token-cost-calculator.js +294 -0
  62. package/src/utils/ui.jsx +18 -20
  63. package/src/components/ui/select.jsx +0 -27
  64. package/src/lib/utils.js +0 -6
  65. package/src/ui/client/hooks/useTicker.js +0 -26
  66. package/src/ui/config-bridge.browser.js +0 -149
  67. package/src/ui/dist/assets/style-D6K_oQ12.css +0 -62
@@ -1,17 +1,24 @@
1
1
  import { derivePipelineMetadata } from "../../../utils/pipelines.js";
2
-
3
- const ALLOWED_STATES = new Set(["pending", "running", "done", "failed"]);
2
+ import {
3
+ normalizeTaskState,
4
+ deriveJobStatusFromTasks,
5
+ } from "../../../config/statuses.js";
4
6
 
5
7
  /**
6
8
  * Normalize a raw task state into canonical enum.
7
9
  * Returns { state, warning? } where warning is a string if normalization occurred.
8
10
  */
9
- function normalizeTaskState(raw) {
11
+ function normalizeTaskStateWithWarning(raw) {
10
12
  if (!raw || typeof raw !== "string")
11
13
  return { state: "pending", warning: "missing_state" };
12
- const s = raw.toLowerCase();
13
- if (ALLOWED_STATES.has(s)) return { state: s };
14
- return { state: "pending", warning: `unknown_state:${raw}` };
14
+
15
+ const normalizedState = normalizeTaskState(raw);
16
+
17
+ if (raw !== normalizedState) {
18
+ return { state: normalizedState, warning: `unknown_state:${raw}` };
19
+ }
20
+
21
+ return { state: normalizedState };
15
22
  }
16
23
 
17
24
  /**
@@ -28,7 +35,7 @@ function normalizeTasks(rawTasks) {
28
35
  // Object shape - canonical format
29
36
  const tasks = {};
30
37
  Object.entries(rawTasks).forEach(([name, t]) => {
31
- const ns = normalizeTaskState(t && t.state);
38
+ const ns = normalizeTaskStateWithWarning(t && t.state);
32
39
  if (ns.warning) warnings.push(`${name}:${ns.warning}`);
33
40
  const taskObj = {
34
41
  name,
@@ -66,6 +73,8 @@ function normalizeTasks(rawTasks) {
66
73
  artifacts: Array.isArray(t && t.artifacts)
67
74
  ? t.artifacts.slice()
68
75
  : undefined,
76
+ // Preserve tokenUsage if present
77
+ ...(t && t.tokenUsage ? { tokenUsage: t.tokenUsage } : {}),
69
78
  };
70
79
  tasks[name] = taskObj;
71
80
  });
@@ -77,7 +86,7 @@ function normalizeTasks(rawTasks) {
77
86
  const tasks = {};
78
87
  rawTasks.forEach((t, idx) => {
79
88
  const name = t && t.name ? String(t.name) : `task-${idx}`;
80
- const ns = normalizeTaskState(t && t.state);
89
+ const ns = normalizeTaskStateWithWarning(t && t.state);
81
90
  if (ns.warning) warnings.push(`${name}:${ns.warning}`);
82
91
  tasks[name] = {
83
92
  name,
@@ -100,6 +109,8 @@ function normalizeTasks(rawTasks) {
100
109
  artifacts: Array.isArray(t && t.artifacts)
101
110
  ? t.artifacts.slice()
102
111
  : undefined,
112
+ // Preserve tokenUsage if present
113
+ ...(t && t.tokenUsage ? { tokenUsage: t.tokenUsage } : {}),
103
114
  };
104
115
  });
105
116
  return { tasks, warnings };
@@ -108,23 +119,6 @@ function normalizeTasks(rawTasks) {
108
119
  return { tasks: {}, warnings: ["invalid_tasks_shape"] };
109
120
  }
110
121
 
111
- /**
112
- * Derive status from tasks when status is missing/invalid.
113
- * Rules:
114
- * - failed if any task state === 'failed'
115
- * - running if >=1 running and none failed
116
- * - complete if all done
117
- * - pending otherwise
118
- */
119
- function deriveStatusFromTasks(tasks) {
120
- const taskList = Object.values(tasks);
121
- if (!Array.isArray(taskList) || taskList.length === 0) return "pending";
122
- if (taskList.some((t) => t.state === "failed")) return "failed";
123
- if (taskList.some((t) => t.state === "running")) return "running";
124
- if (taskList.every((t) => t.state === "done")) return "complete";
125
- return "pending";
126
- }
127
-
128
122
  /**
129
123
  * Clamp number to 0..100 and ensure integer.
130
124
  */
@@ -143,7 +137,7 @@ function computeJobSummaryStats(tasks) {
143
137
  (acc, t) => acc + (t.state === "done" ? 1 : 0),
144
138
  0
145
139
  );
146
- const status = deriveStatusFromTasks(tasks);
140
+ const status = deriveJobStatusFromTasks(Object.values(tasks));
147
141
  const progress =
148
142
  taskCount > 0 ? Math.round((doneCount / taskCount) * 100) : 0;
149
143
  return { status, progress, doneCount, taskCount };
@@ -158,7 +152,7 @@ export function adaptJobSummary(apiJob) {
158
152
  // Demo-only: read canonical fields strictly
159
153
  const id = apiJob.jobId;
160
154
  const name = apiJob.title || "";
161
- const rawTasks = apiJob.tasksStatus;
155
+ const rawTasks = apiJob.tasks;
162
156
  const location = apiJob.location;
163
157
 
164
158
  // Job-level stage metadata
@@ -197,6 +191,21 @@ export function adaptJobSummary(apiJob) {
197
191
  if (pipeline != null) job.pipeline = pipeline;
198
192
  if (pipelineLabel != null) job.pipelineLabel = pipelineLabel;
199
193
 
194
+ // Costs summary from API
195
+ if (apiJob.costsSummary) {
196
+ job.costsSummary = {
197
+ totalTokens: apiJob.costsSummary.totalTokens || 0,
198
+ totalInputTokens: apiJob.costsSummary.totalInputTokens || 0,
199
+ totalOutputTokens: apiJob.costsSummary.totalOutputTokens || 0,
200
+ totalCost: apiJob.costsSummary.totalCost || 0,
201
+ totalInputCost: apiJob.costsSummary.totalInputCost || 0,
202
+ totalOutputCost: apiJob.costsSummary.totalOutputCost || 0,
203
+ };
204
+ // Add top-level numeric mirrors for convenience
205
+ job.totalCost = job.costsSummary.totalCost;
206
+ job.totalTokens = job.costsSummary.totalTokens;
207
+ }
208
+
200
209
  // Include warnings for debugging
201
210
  if (warnings.length > 0) job.__warnings = warnings;
202
211
 
@@ -212,7 +221,7 @@ export function adaptJobDetail(apiDetail) {
212
221
  // Demo-only: read canonical fields strictly
213
222
  const id = apiDetail.jobId;
214
223
  const name = apiDetail.title || "";
215
- const rawTasks = apiDetail.tasksStatus;
224
+ const rawTasks = apiDetail.tasks;
216
225
  const location = apiDetail.location;
217
226
 
218
227
  // Job-level stage metadata
@@ -251,6 +260,11 @@ export function adaptJobDetail(apiDetail) {
251
260
  if (pipeline != null) detail.pipeline = pipeline;
252
261
  if (pipelineLabel != null) detail.pipelineLabel = pipelineLabel;
253
262
 
263
+ // Preserve job detail costs
264
+ if (apiDetail.costs) {
265
+ detail.costs = apiDetail.costs;
266
+ }
267
+
254
268
  // Include warnings for debugging
255
269
  if (warnings.length > 0) detail.__warnings = warnings;
256
270
 
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Client-side API helpers for making HTTP requests to the backend
3
+ */
4
+
5
+ /**
6
+ * Restart a job with clean-slate mode
7
+ *
8
+ * @param {string} jobId - The ID of the job to restart
9
+ * @param {Object} opts - Options object
10
+ * @param {Object} opts.options - Additional options for the restart
11
+ * @param {boolean} opts.options.clearTokenUsage - Whether to clear token usage (default: true)
12
+ * @returns {Promise<Object>} Parsed JSON response from the server
13
+ * @throws {Object} Structured error object with { code, message } for non-2xx responses
14
+ */
15
+ export async function restartJob(jobId, opts = {}) {
16
+ const options = {
17
+ clearTokenUsage: true,
18
+ ...opts.options,
19
+ };
20
+
21
+ const requestBody = opts.fromTask
22
+ ? { fromTask: opts.fromTask, options }
23
+ : { mode: "clean-slate", options };
24
+
25
+ try {
26
+ const response = await fetch(
27
+ `/api/jobs/${encodeURIComponent(jobId)}/restart`,
28
+ {
29
+ method: "POST",
30
+ headers: {
31
+ "Content-Type": "application/json",
32
+ },
33
+ body: JSON.stringify(requestBody),
34
+ }
35
+ );
36
+
37
+ if (!response.ok) {
38
+ // Try to parse error response, fall back to status text if parsing fails
39
+ let errorData;
40
+ try {
41
+ errorData = await response.json();
42
+ } catch {
43
+ errorData = { message: response.statusText };
44
+ }
45
+
46
+ // Throw structured error with code and message
47
+ throw {
48
+ code: errorData.code || getErrorCodeFromStatus(response.status),
49
+ message: getRestartErrorMessage(errorData, response.status),
50
+ status: response.status,
51
+ };
52
+ }
53
+
54
+ // Return parsed JSON for successful responses
55
+ return await response.json();
56
+ } catch (error) {
57
+ // Re-throw structured errors as-is
58
+ if (error.code && error.message) {
59
+ throw error;
60
+ }
61
+
62
+ // Handle network errors or other unexpected errors
63
+ throw {
64
+ code: "network_error",
65
+ message: error.message || "Failed to connect to server",
66
+ };
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Map HTTP status codes to error codes for structured error handling
72
+ */
73
+ function getErrorCodeFromStatus(status) {
74
+ switch (status) {
75
+ case 404:
76
+ return "job_not_found";
77
+ case 409:
78
+ return "conflict";
79
+ case 500:
80
+ return "spawn_failed";
81
+ default:
82
+ return "unknown_error";
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Map HTTP status codes to error messages for structured error handling
88
+ */
89
+ function getErrorMessageFromStatus(status) {
90
+ switch (status) {
91
+ case 404:
92
+ return "Job not found";
93
+ case 409:
94
+ return "Job restart conflict";
95
+ case 500:
96
+ return "Failed to start restart";
97
+ default:
98
+ return `Request failed with status ${status}`;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Get specific error message from error response for restart functionality
104
+ */
105
+ function getRestartErrorMessage(errorData, status) {
106
+ // Handle specific 409 conflict errors
107
+ if (status === 409) {
108
+ if (errorData.code === "job_running") {
109
+ return "Job is currently running; restart is unavailable.";
110
+ }
111
+ if (errorData.code === "unsupported_lifecycle") {
112
+ return "Job must be in current to restart.";
113
+ }
114
+ if (errorData.message?.includes("job_running")) {
115
+ return "Job is currently running; restart is unavailable.";
116
+ }
117
+ if (errorData.message?.includes("unsupported_lifecycle")) {
118
+ return "Job must be in current to restart.";
119
+ }
120
+ }
121
+
122
+ // Handle 404 errors
123
+ if (status === 404) {
124
+ return "Job not found.";
125
+ }
126
+
127
+ // Handle 500 errors
128
+ if (status === 500) {
129
+ return "Failed to start restart. Try again.";
130
+ }
131
+
132
+ // Fall back to provided message or default
133
+ return errorData.message || "Failed to restart job.";
134
+ }