@ryanfw/prompt-orchestration-pipeline 0.13.5 → 0.14.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/package.json +1 -1
- package/src/components/ui/RestartJobModal.jsx +24 -14
- package/src/core/pipeline-runner.js +30 -24
- package/src/llm/index.js +51 -6
- package/src/providers/anthropic.js +4 -1
- package/src/providers/base.js +27 -2
- package/src/providers/deepseek.js +8 -3
- package/src/providers/gemini.js +5 -2
- package/src/providers/openai.js +13 -5
- package/src/providers/zhipu.js +4 -1
- package/src/ui/dist/assets/{index-cjHV9mYW.js → index-B5HMRkR9.js} +22 -11
- package/src/ui/dist/assets/{index-cjHV9mYW.js.map → index-B5HMRkR9.js.map} +1 -1
- package/src/ui/dist/index.html +1 -1
- package/src/ui/endpoints/job-control-endpoints.js +3 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryanfw/prompt-orchestration-pipeline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "A Prompt-orchestration pipeline (POP) is a framework for building, running, and experimenting with complex chains of LLM tasks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/ui/server.js",
|
|
@@ -130,25 +130,35 @@ export function RestartJobModal({
|
|
|
130
130
|
Cancel
|
|
131
131
|
</Button>
|
|
132
132
|
|
|
133
|
-
{taskId
|
|
133
|
+
{taskId ? (
|
|
134
|
+
<>
|
|
135
|
+
<Button
|
|
136
|
+
variant="outline"
|
|
137
|
+
onClick={() => onConfirm({ singleTask: false })}
|
|
138
|
+
disabled={isSubmitting}
|
|
139
|
+
className="min-w-[120px]"
|
|
140
|
+
>
|
|
141
|
+
{isSubmitting ? "Restarting..." : "Restart entire pipeline"}
|
|
142
|
+
</Button>
|
|
143
|
+
<Button
|
|
144
|
+
variant="default"
|
|
145
|
+
onClick={() => onConfirm({ singleTask: true })}
|
|
146
|
+
disabled={isSubmitting}
|
|
147
|
+
className="min-w-[120px]"
|
|
148
|
+
>
|
|
149
|
+
{isSubmitting ? "Running..." : "Re-run this task"}
|
|
150
|
+
</Button>
|
|
151
|
+
</>
|
|
152
|
+
) : (
|
|
134
153
|
<Button
|
|
135
|
-
variant="
|
|
136
|
-
onClick={() => onConfirm({ singleTask:
|
|
154
|
+
variant="destructive"
|
|
155
|
+
onClick={() => onConfirm({ singleTask: false })}
|
|
137
156
|
disabled={isSubmitting}
|
|
138
|
-
className="min-w-[
|
|
157
|
+
className="min-w-[80px]"
|
|
139
158
|
>
|
|
140
|
-
{isSubmitting ? "
|
|
159
|
+
{isSubmitting ? "Restarting..." : "Restart"}
|
|
141
160
|
</Button>
|
|
142
161
|
)}
|
|
143
|
-
|
|
144
|
-
<Button
|
|
145
|
-
variant="destructive"
|
|
146
|
-
onClick={() => onConfirm({ singleTask: false })}
|
|
147
|
-
disabled={isSubmitting}
|
|
148
|
-
className="min-w-[80px]"
|
|
149
|
-
>
|
|
150
|
-
{isSubmitting ? "Restarting..." : "Restart"}
|
|
151
|
-
</Button>
|
|
152
162
|
</Flex>
|
|
153
163
|
</div>
|
|
154
164
|
</div>
|
|
@@ -18,6 +18,8 @@ import { createJobLogger } from "./logger.js";
|
|
|
18
18
|
import { LogEvent, LogFileExtension } from "../config/log-events.js";
|
|
19
19
|
import { decideTransition } from "./lifecycle-policy.js";
|
|
20
20
|
|
|
21
|
+
const getTaskName = (t) => (typeof t === "string" ? t : t.name);
|
|
22
|
+
|
|
21
23
|
const ROOT = process.env.PO_ROOT || process.cwd();
|
|
22
24
|
const DATA_DIR = path.join(ROOT, process.env.PO_DATA_DIR || "pipeline-data");
|
|
23
25
|
const CURRENT_DIR =
|
|
@@ -104,6 +106,8 @@ const pipeline = JSON.parse(await fs.readFile(PIPELINE_DEF_PATH, "utf8"));
|
|
|
104
106
|
// Validate pipeline format early with a friendly error message
|
|
105
107
|
validatePipelineOrThrow(pipeline, PIPELINE_DEF_PATH);
|
|
106
108
|
|
|
109
|
+
const taskNames = pipeline.tasks.map(getTaskName);
|
|
110
|
+
|
|
107
111
|
const tasks = (await loadFreshModule(TASK_REGISTRY)).default;
|
|
108
112
|
|
|
109
113
|
const status = JSON.parse(await fs.readFile(tasksStatusPath, "utf8"));
|
|
@@ -123,21 +127,21 @@ logger.group("Pipeline execution", {
|
|
|
123
127
|
|
|
124
128
|
// Helper function to check if all upstream dependencies are completed
|
|
125
129
|
function areDependenciesReady(taskName) {
|
|
126
|
-
const taskIndex =
|
|
130
|
+
const taskIndex = taskNames.indexOf(taskName);
|
|
127
131
|
if (taskIndex === -1) return false;
|
|
128
132
|
|
|
129
|
-
const upstreamTasks =
|
|
133
|
+
const upstreamTasks = taskNames.slice(0, taskIndex);
|
|
130
134
|
return upstreamTasks.every(
|
|
131
135
|
(upstreamTask) => status.tasks[upstreamTask]?.state === TaskState.DONE
|
|
132
136
|
);
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
try {
|
|
136
|
-
for (const taskName of
|
|
140
|
+
for (const taskName of taskNames) {
|
|
137
141
|
// Skip tasks before startFromTask when targeting a specific restart point
|
|
138
142
|
if (
|
|
139
143
|
startFromTask &&
|
|
140
|
-
|
|
144
|
+
taskNames.indexOf(taskName) < taskNames.indexOf(startFromTask)
|
|
141
145
|
) {
|
|
142
146
|
logger.log("Skipping task before restart point", {
|
|
143
147
|
taskName,
|
|
@@ -158,30 +162,32 @@ try {
|
|
|
158
162
|
continue;
|
|
159
163
|
}
|
|
160
164
|
|
|
161
|
-
// Check lifecycle policy before starting task
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const lifecycleDecision = decideTransition({
|
|
166
|
-
op: "start",
|
|
167
|
-
taskState: currentTaskState,
|
|
168
|
-
dependenciesReady,
|
|
169
|
-
});
|
|
165
|
+
// Check lifecycle policy before starting task (skip when startFromTask is set - user explicitly requested this task)
|
|
166
|
+
if (!startFromTask) {
|
|
167
|
+
const currentTaskState = status.tasks[taskName]?.state || "pending";
|
|
168
|
+
const dependenciesReady = areDependenciesReady(taskName);
|
|
170
169
|
|
|
171
|
-
|
|
172
|
-
logger.warn("lifecycle_block", {
|
|
173
|
-
jobId,
|
|
174
|
-
taskId: taskName,
|
|
170
|
+
const lifecycleDecision = decideTransition({
|
|
175
171
|
op: "start",
|
|
176
|
-
|
|
172
|
+
taskState: currentTaskState,
|
|
173
|
+
dependenciesReady,
|
|
177
174
|
});
|
|
178
175
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
176
|
+
if (!lifecycleDecision.ok) {
|
|
177
|
+
logger.warn("lifecycle_block", {
|
|
178
|
+
jobId,
|
|
179
|
+
taskId: taskName,
|
|
180
|
+
op: "start",
|
|
181
|
+
reason: lifecycleDecision.reason,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Create typed error for endpoints to handle
|
|
185
|
+
const lifecycleError = new Error(lifecycleDecision.reason);
|
|
186
|
+
lifecycleError.httpStatus = 409;
|
|
187
|
+
lifecycleError.error = "unsupported_lifecycle";
|
|
188
|
+
lifecycleError.reason = lifecycleDecision.reason;
|
|
189
|
+
throw lifecycleError;
|
|
190
|
+
}
|
|
185
191
|
}
|
|
186
192
|
|
|
187
193
|
logger.log("Starting task", { taskName });
|
package/src/llm/index.js
CHANGED
|
@@ -89,6 +89,18 @@ export function calculateCost(provider, model, usage) {
|
|
|
89
89
|
return promptCost + completionCost;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// Helper function to detect if messages indicate JSON response is needed
|
|
93
|
+
function shouldInferJsonFormat(messages) {
|
|
94
|
+
// Check first two messages for JSON keyword (case-insensitive)
|
|
95
|
+
const messagesToCheck = messages.slice(0, 2);
|
|
96
|
+
for (const msg of messagesToCheck) {
|
|
97
|
+
if (typeof msg?.content === "string" && /json/i.test(msg.content)) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
92
104
|
// Core chat function - no metrics handling needed!
|
|
93
105
|
export async function chat(options) {
|
|
94
106
|
console.log("[llm] chat() called with options:", {
|
|
@@ -199,6 +211,17 @@ export async function chat(options) {
|
|
|
199
211
|
};
|
|
200
212
|
} else if (provider === "openai") {
|
|
201
213
|
console.log("[llm] Using OpenAI provider");
|
|
214
|
+
|
|
215
|
+
// Infer JSON format if not explicitly provided
|
|
216
|
+
const effectiveResponseFormat =
|
|
217
|
+
responseFormat === undefined ||
|
|
218
|
+
responseFormat === null ||
|
|
219
|
+
responseFormat === ""
|
|
220
|
+
? shouldInferJsonFormat(messages)
|
|
221
|
+
? "json_object"
|
|
222
|
+
: undefined
|
|
223
|
+
: responseFormat;
|
|
224
|
+
|
|
202
225
|
const openaiArgs = {
|
|
203
226
|
messages,
|
|
204
227
|
model: model || "gpt-5-chat-latest",
|
|
@@ -211,8 +234,8 @@ export async function chat(options) {
|
|
|
211
234
|
hasMessages: !!openaiArgs.messages,
|
|
212
235
|
messageCount: openaiArgs.messages?.length,
|
|
213
236
|
});
|
|
214
|
-
if (
|
|
215
|
-
openaiArgs.responseFormat =
|
|
237
|
+
if (effectiveResponseFormat !== undefined) {
|
|
238
|
+
openaiArgs.responseFormat = effectiveResponseFormat;
|
|
216
239
|
}
|
|
217
240
|
if (topP !== undefined) openaiArgs.topP = topP;
|
|
218
241
|
if (frequencyPenalty !== undefined)
|
|
@@ -255,6 +278,17 @@ export async function chat(options) {
|
|
|
255
278
|
}
|
|
256
279
|
} else if (provider === "deepseek") {
|
|
257
280
|
console.log("[llm] Using DeepSeek provider");
|
|
281
|
+
|
|
282
|
+
// Infer JSON format if not explicitly provided
|
|
283
|
+
const effectiveResponseFormat =
|
|
284
|
+
responseFormat === undefined ||
|
|
285
|
+
responseFormat === null ||
|
|
286
|
+
responseFormat === ""
|
|
287
|
+
? shouldInferJsonFormat(messages)
|
|
288
|
+
? "json_object"
|
|
289
|
+
: undefined
|
|
290
|
+
: responseFormat;
|
|
291
|
+
|
|
258
292
|
const deepseekArgs = {
|
|
259
293
|
messages,
|
|
260
294
|
model: model || MODEL_CONFIG[DEFAULT_MODEL_BY_PROVIDER.deepseek].model,
|
|
@@ -274,8 +308,8 @@ export async function chat(options) {
|
|
|
274
308
|
if (presencePenalty !== undefined)
|
|
275
309
|
deepseekArgs.presencePenalty = presencePenalty;
|
|
276
310
|
if (stop !== undefined) deepseekArgs.stop = stop;
|
|
277
|
-
if (
|
|
278
|
-
deepseekArgs.responseFormat =
|
|
311
|
+
if (effectiveResponseFormat !== undefined) {
|
|
312
|
+
deepseekArgs.responseFormat = effectiveResponseFormat;
|
|
279
313
|
}
|
|
280
314
|
|
|
281
315
|
console.log("[llm] Calling deepseekChat()...");
|
|
@@ -373,6 +407,17 @@ export async function chat(options) {
|
|
|
373
407
|
}
|
|
374
408
|
} else if (provider === "gemini") {
|
|
375
409
|
console.log("[llm] Using Gemini provider");
|
|
410
|
+
|
|
411
|
+
// Infer JSON format if not explicitly provided
|
|
412
|
+
const effectiveResponseFormat =
|
|
413
|
+
responseFormat === undefined ||
|
|
414
|
+
responseFormat === null ||
|
|
415
|
+
responseFormat === ""
|
|
416
|
+
? shouldInferJsonFormat(messages)
|
|
417
|
+
? "json_object"
|
|
418
|
+
: undefined
|
|
419
|
+
: responseFormat;
|
|
420
|
+
|
|
376
421
|
const geminiArgs = {
|
|
377
422
|
messages,
|
|
378
423
|
model: model || "gemini-2.5-flash",
|
|
@@ -387,8 +432,8 @@ export async function chat(options) {
|
|
|
387
432
|
});
|
|
388
433
|
if (topP !== undefined) geminiArgs.topP = topP;
|
|
389
434
|
if (stop !== undefined) geminiArgs.stop = stop;
|
|
390
|
-
if (
|
|
391
|
-
geminiArgs.responseFormat =
|
|
435
|
+
if (effectiveResponseFormat !== undefined) {
|
|
436
|
+
geminiArgs.responseFormat = effectiveResponseFormat;
|
|
392
437
|
}
|
|
393
438
|
|
|
394
439
|
console.log("[llm] Calling geminiChat()...");
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
extractMessages,
|
|
3
3
|
isRetryableError,
|
|
4
4
|
sleep,
|
|
5
|
+
stripMarkdownFences,
|
|
5
6
|
tryParseJSON,
|
|
6
7
|
ensureJsonResponseFormat,
|
|
7
8
|
ProviderJsonParseError,
|
|
@@ -77,10 +78,12 @@ export async function anthropicChat({
|
|
|
77
78
|
|
|
78
79
|
// Extract text from response.content blocks
|
|
79
80
|
const blocks = Array.isArray(data?.content) ? data.content : [];
|
|
80
|
-
const
|
|
81
|
+
const rawText = blocks
|
|
81
82
|
.filter((b) => b?.type === "text" && typeof b.text === "string")
|
|
82
83
|
.map((b) => b.text)
|
|
83
84
|
.join("");
|
|
85
|
+
// Always strip markdown fences first to prevent parse failures
|
|
86
|
+
const text = stripMarkdownFences(rawText);
|
|
84
87
|
console.log("[Anthropic] Response text length:", text.length);
|
|
85
88
|
|
|
86
89
|
// Parse JSON - this is required for all calls
|
package/src/providers/base.js
CHANGED
|
@@ -33,6 +33,25 @@ export async function sleep(ms) {
|
|
|
33
33
|
return new Promise((r) => setTimeout(r, ms));
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Strip markdown code fences from text unconditionally.
|
|
38
|
+
* Handles ```json, ```JSON, and plain ``` with or without newlines.
|
|
39
|
+
* @param {string} text - The text to strip fences from
|
|
40
|
+
* @returns {string} The cleaned text, or original if not a string
|
|
41
|
+
*/
|
|
42
|
+
export function stripMarkdownFences(text) {
|
|
43
|
+
if (typeof text !== "string") return text;
|
|
44
|
+
const trimmed = text.trim();
|
|
45
|
+
if (trimmed.startsWith("```")) {
|
|
46
|
+
// Remove opening fence (```json, ```JSON, or just ```)
|
|
47
|
+
let cleaned = trimmed.replace(/^```(?:json|JSON)?\s*\n?/, "");
|
|
48
|
+
// Remove closing fence
|
|
49
|
+
cleaned = cleaned.replace(/\n?```\s*$/, "");
|
|
50
|
+
return cleaned.trim();
|
|
51
|
+
}
|
|
52
|
+
return text;
|
|
53
|
+
}
|
|
54
|
+
|
|
36
55
|
export function tryParseJSON(text) {
|
|
37
56
|
try {
|
|
38
57
|
return JSON.parse(text);
|
|
@@ -85,7 +104,12 @@ export class ProviderJsonModeError extends Error {
|
|
|
85
104
|
* Error thrown when JSON parsing fails and should not be retried
|
|
86
105
|
*/
|
|
87
106
|
export class ProviderJsonParseError extends Error {
|
|
88
|
-
constructor(
|
|
107
|
+
constructor(
|
|
108
|
+
provider,
|
|
109
|
+
model,
|
|
110
|
+
sample,
|
|
111
|
+
message = "Failed to parse JSON response"
|
|
112
|
+
) {
|
|
89
113
|
super(message);
|
|
90
114
|
this.name = "ProviderJsonParseError";
|
|
91
115
|
this.provider = provider;
|
|
@@ -109,8 +133,9 @@ export function ensureJsonResponseFormat(responseFormat, providerName) {
|
|
|
109
133
|
}
|
|
110
134
|
|
|
111
135
|
// Check for valid JSON format types
|
|
112
|
-
const isValidJsonFormat =
|
|
136
|
+
const isValidJsonFormat =
|
|
113
137
|
responseFormat === "json" ||
|
|
138
|
+
responseFormat === "json_object" ||
|
|
114
139
|
responseFormat?.type === "json_object" ||
|
|
115
140
|
responseFormat?.type === "json_schema";
|
|
116
141
|
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
extractMessages,
|
|
3
3
|
isRetryableError,
|
|
4
4
|
sleep,
|
|
5
|
+
stripMarkdownFences,
|
|
5
6
|
tryParseJSON,
|
|
6
7
|
ensureJsonResponseFormat,
|
|
7
8
|
ProviderJsonParseError,
|
|
@@ -24,11 +25,12 @@ export async function deepseekChat({
|
|
|
24
25
|
throw new Error("DeepSeek API key not configured");
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
// Determine if JSON mode is requested
|
|
28
|
+
// Determine if JSON mode is requested (handle both object and string formats)
|
|
28
29
|
const isJsonMode =
|
|
29
30
|
responseFormat?.type === "json_object" ||
|
|
30
31
|
responseFormat?.type === "json_schema" ||
|
|
31
|
-
responseFormat === "json"
|
|
32
|
+
responseFormat === "json" ||
|
|
33
|
+
responseFormat === "json_object";
|
|
32
34
|
|
|
33
35
|
const { systemMsg, userMsg } = extractMessages(messages);
|
|
34
36
|
|
|
@@ -84,7 +86,10 @@ export async function deepseekChat({
|
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
const data = await response.json();
|
|
87
|
-
const
|
|
89
|
+
const rawContent = data.choices[0].message.content;
|
|
90
|
+
|
|
91
|
+
// Always strip markdown fences first to prevent parse failures
|
|
92
|
+
const content = stripMarkdownFences(rawContent);
|
|
88
93
|
|
|
89
94
|
// Parse JSON only in JSON mode; return raw string for text mode
|
|
90
95
|
if (isJsonMode) {
|
package/src/providers/gemini.js
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
extractMessages,
|
|
3
3
|
isRetryableError,
|
|
4
4
|
sleep,
|
|
5
|
+
stripMarkdownFences,
|
|
5
6
|
tryParseJSON,
|
|
6
7
|
ensureJsonResponseFormat,
|
|
7
8
|
ProviderJsonParseError,
|
|
@@ -32,7 +33,7 @@ export async function geminiChat(options) {
|
|
|
32
33
|
frequencyPenalty,
|
|
33
34
|
presencePenalty,
|
|
34
35
|
stop,
|
|
35
|
-
maxRetries = 3
|
|
36
|
+
maxRetries = 3,
|
|
36
37
|
} = options;
|
|
37
38
|
|
|
38
39
|
// Validate response format (Gemini only supports JSON mode)
|
|
@@ -171,7 +172,9 @@ export async function geminiChat(options) {
|
|
|
171
172
|
throw new Error("No content returned from Gemini API");
|
|
172
173
|
}
|
|
173
174
|
|
|
174
|
-
const
|
|
175
|
+
const rawText = candidate.content.parts[0].text;
|
|
176
|
+
// Always strip markdown fences first to prevent parse failures
|
|
177
|
+
const text = stripMarkdownFences(rawText);
|
|
175
178
|
console.log(`[Gemini] Text length: ${text.length}`);
|
|
176
179
|
|
|
177
180
|
// Parse JSON if required
|
package/src/providers/openai.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
extractMessages,
|
|
4
4
|
isRetryableError,
|
|
5
5
|
sleep,
|
|
6
|
+
stripMarkdownFences,
|
|
6
7
|
tryParseJSON,
|
|
7
8
|
ensureJsonResponseFormat,
|
|
8
9
|
ProviderJsonParseError,
|
|
@@ -53,11 +54,12 @@ export async function openaiChat({
|
|
|
53
54
|
console.log("[OpenAI] System message length:", systemMsg.length);
|
|
54
55
|
console.log("[OpenAI] User message length:", userMsg.length);
|
|
55
56
|
|
|
56
|
-
// Determine if JSON mode is requested
|
|
57
|
+
// Determine if JSON mode is requested (handle both object and string formats)
|
|
57
58
|
const isJsonMode =
|
|
58
59
|
responseFormat?.json_schema ||
|
|
59
60
|
responseFormat?.type === "json_object" ||
|
|
60
|
-
responseFormat === "json"
|
|
61
|
+
responseFormat === "json" ||
|
|
62
|
+
responseFormat === "json_object";
|
|
61
63
|
|
|
62
64
|
let lastError;
|
|
63
65
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
@@ -100,7 +102,9 @@ export async function openaiChat({
|
|
|
100
102
|
|
|
101
103
|
console.log("[OpenAI] Calling responses.create...");
|
|
102
104
|
const resp = await openai.responses.create(responsesReq);
|
|
103
|
-
const
|
|
105
|
+
const rawText = resp.output_text ?? "";
|
|
106
|
+
// Always strip markdown fences first to prevent parse failures
|
|
107
|
+
const text = stripMarkdownFences(rawText);
|
|
104
108
|
console.log("[OpenAI] Response received, text length:", text.length);
|
|
105
109
|
|
|
106
110
|
// Approximate usage (tests don't assert exact values)
|
|
@@ -161,7 +165,9 @@ export async function openaiChat({
|
|
|
161
165
|
|
|
162
166
|
console.log("[OpenAI] Calling chat.completions.create...");
|
|
163
167
|
const classicRes = await openai.chat.completions.create(classicReq);
|
|
164
|
-
const
|
|
168
|
+
const rawClassicText = classicRes?.choices?.[0]?.message?.content ?? "";
|
|
169
|
+
// Always strip markdown fences first to prevent parse failures
|
|
170
|
+
const classicText = stripMarkdownFences(rawClassicText);
|
|
165
171
|
console.log(
|
|
166
172
|
"[OpenAI] Response received, text length:",
|
|
167
173
|
classicText.length
|
|
@@ -228,7 +234,9 @@ export async function openaiChat({
|
|
|
228
234
|
}
|
|
229
235
|
|
|
230
236
|
const classicRes = await openai.chat.completions.create(classicReq);
|
|
231
|
-
const
|
|
237
|
+
const rawText = classicRes?.choices?.[0]?.message?.content ?? "";
|
|
238
|
+
// Always strip markdown fences first to prevent parse failures
|
|
239
|
+
const text = stripMarkdownFences(rawText);
|
|
232
240
|
|
|
233
241
|
// Parse JSON only in JSON mode; return raw string for text mode
|
|
234
242
|
if (isJsonMode) {
|
package/src/providers/zhipu.js
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
extractMessages,
|
|
3
3
|
isRetryableError,
|
|
4
4
|
sleep,
|
|
5
|
+
stripMarkdownFences,
|
|
5
6
|
tryParseJSON,
|
|
6
7
|
ensureJsonResponseFormat,
|
|
7
8
|
ProviderJsonParseError,
|
|
@@ -104,7 +105,9 @@ export async function zhipuChat({
|
|
|
104
105
|
console.log("[Zhipu] Response received from Zhipu API");
|
|
105
106
|
|
|
106
107
|
// Extract text from response
|
|
107
|
-
const
|
|
108
|
+
const rawText = data?.choices?.[0]?.message?.content || "";
|
|
109
|
+
// Always strip markdown fences first to prevent parse failures
|
|
110
|
+
const text = stripMarkdownFences(rawText);
|
|
108
111
|
console.log("[Zhipu] Response text length:", text.length);
|
|
109
112
|
|
|
110
113
|
// Parse JSON - this is required for all calls
|
|
@@ -24711,17 +24711,28 @@ function RestartJobModal({
|
|
|
24711
24711
|
children: "Cancel"
|
|
24712
24712
|
}
|
|
24713
24713
|
),
|
|
24714
|
-
taskId
|
|
24715
|
-
|
|
24716
|
-
|
|
24717
|
-
|
|
24718
|
-
|
|
24719
|
-
|
|
24720
|
-
|
|
24721
|
-
|
|
24722
|
-
|
|
24723
|
-
|
|
24724
|
-
|
|
24714
|
+
taskId ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
24715
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
24716
|
+
Button,
|
|
24717
|
+
{
|
|
24718
|
+
variant: "outline",
|
|
24719
|
+
onClick: () => onConfirm({ singleTask: false }),
|
|
24720
|
+
disabled: isSubmitting,
|
|
24721
|
+
className: "min-w-[120px]",
|
|
24722
|
+
children: isSubmitting ? "Restarting..." : "Restart entire pipeline"
|
|
24723
|
+
}
|
|
24724
|
+
),
|
|
24725
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
24726
|
+
Button,
|
|
24727
|
+
{
|
|
24728
|
+
variant: "default",
|
|
24729
|
+
onClick: () => onConfirm({ singleTask: true }),
|
|
24730
|
+
disabled: isSubmitting,
|
|
24731
|
+
className: "min-w-[120px]",
|
|
24732
|
+
children: isSubmitting ? "Running..." : "Re-run this task"
|
|
24733
|
+
}
|
|
24734
|
+
)
|
|
24735
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
24725
24736
|
Button,
|
|
24726
24737
|
{
|
|
24727
24738
|
variant: "destructive",
|