@ryanfw/prompt-orchestration-pipeline 1.3.3 → 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 +14 -4
- package/package.json +1 -4
- package/src/api/__tests__/index.test.ts +144 -41
- package/src/api/index.ts +15 -83
- 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/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 +115 -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 +485 -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 +4 -1
- package/src/core/agent-types.ts +5 -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 +162 -18
- 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__/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 +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-DN3-zvtP.js} +4299 -251
- 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__/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 +455 -1
- package/src/ui/server/__tests__/job-endpoints.test.ts +43 -1
- 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/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/job-control-endpoints.ts +122 -73
- 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-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 +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/providers/openai.ts
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
stripMarkdownFences,
|
|
11
11
|
tryParseJSON,
|
|
12
12
|
ensureJsonResponseFormat,
|
|
13
|
+
isJsonResponseFormat,
|
|
14
|
+
extractResponseSchema,
|
|
13
15
|
} from "./base.ts";
|
|
14
16
|
import { IdleTimeoutController } from "./stream-accumulator.ts";
|
|
15
17
|
import { ProviderJsonParseError } from "./types.ts";
|
|
@@ -18,7 +20,6 @@ import type { Response as OAIResponse } from "openai/resources/responses/respons
|
|
|
18
20
|
import type { ChatCompletion } from "openai/resources/chat/completions/completions.mjs";
|
|
19
21
|
|
|
20
22
|
const DEFAULT_MODEL = "gpt-5-chat-latest";
|
|
21
|
-
const DEFAULT_RESPONSE_FORMAT = "json_object";
|
|
22
23
|
const DEFAULT_MAX_RETRIES = 3;
|
|
23
24
|
const GPT5_PATTERN = /^gpt-5/i;
|
|
24
25
|
|
|
@@ -114,15 +115,14 @@ async function callResponsesAPI(
|
|
|
114
115
|
const stripped = stripMarkdownFences(rawText);
|
|
115
116
|
const parsed = tryParseJSON(stripped);
|
|
116
117
|
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
};
|
|
118
|
+
const u = response.usage;
|
|
119
|
+
const usage: AdapterUsage | undefined = u
|
|
120
|
+
? {
|
|
121
|
+
prompt_tokens: typeof u.input_tokens === "number" ? u.input_tokens : null,
|
|
122
|
+
completion_tokens: typeof u.output_tokens === "number" ? u.output_tokens : null,
|
|
123
|
+
total_tokens: typeof u.total_tokens === "number" ? u.total_tokens : null,
|
|
124
|
+
}
|
|
125
|
+
: undefined;
|
|
126
126
|
|
|
127
127
|
return {
|
|
128
128
|
content: typeof parsed === "string" ? parsed : (parsed as Record<string, unknown>),
|
|
@@ -161,7 +161,12 @@ async function callChatCompletionsAPI(
|
|
|
161
161
|
max_tokens: maxTokens,
|
|
162
162
|
};
|
|
163
163
|
|
|
164
|
-
if (responseSchema != null
|
|
164
|
+
if (responseSchema != null) {
|
|
165
|
+
params["response_format"] = {
|
|
166
|
+
type: "json_schema",
|
|
167
|
+
json_schema: { name: "Response", schema: responseSchema },
|
|
168
|
+
};
|
|
169
|
+
} else if (responseFormat === "json_object" || responseFormat === "json") {
|
|
165
170
|
params["response_format"] = { type: "json_object" };
|
|
166
171
|
}
|
|
167
172
|
|
|
@@ -180,11 +185,14 @@ async function callChatCompletionsAPI(
|
|
|
180
185
|
const stripped = stripMarkdownFences(rawText);
|
|
181
186
|
const parsed = tryParseJSON(stripped);
|
|
182
187
|
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
const u = completion.usage;
|
|
189
|
+
const usage: AdapterUsage | undefined = u
|
|
190
|
+
? {
|
|
191
|
+
prompt_tokens: typeof u.prompt_tokens === "number" ? u.prompt_tokens : null,
|
|
192
|
+
completion_tokens: typeof u.completion_tokens === "number" ? u.completion_tokens : null,
|
|
193
|
+
total_tokens: typeof u.total_tokens === "number" ? u.total_tokens : null,
|
|
194
|
+
}
|
|
195
|
+
: undefined;
|
|
188
196
|
|
|
189
197
|
return {
|
|
190
198
|
content: typeof parsed === "string" ? parsed : (parsed as Record<string, unknown>),
|
|
@@ -203,6 +211,7 @@ async function callStreamingResponsesAPI(
|
|
|
203
211
|
maxTokens: number,
|
|
204
212
|
responseFormat: string,
|
|
205
213
|
responseSchema: unknown | undefined,
|
|
214
|
+
timeoutMs: number,
|
|
206
215
|
): Promise<AdapterResponse> {
|
|
207
216
|
const params: Record<string, unknown> = {
|
|
208
217
|
model,
|
|
@@ -228,7 +237,7 @@ async function callStreamingResponsesAPI(
|
|
|
228
237
|
params["text"] = { format: { type: "json_object" } };
|
|
229
238
|
}
|
|
230
239
|
|
|
231
|
-
const idle = new IdleTimeoutController(
|
|
240
|
+
const idle = new IdleTimeoutController(timeoutMs);
|
|
232
241
|
|
|
233
242
|
const stream = await openai.responses.create({
|
|
234
243
|
...(params as Parameters<typeof openai.responses.create>[0]),
|
|
@@ -247,12 +256,19 @@ async function callStreamingResponsesAPI(
|
|
|
247
256
|
text += (event as { delta?: string }).delta ?? "";
|
|
248
257
|
} else if (type === "response.completed") {
|
|
249
258
|
const resp = event.response as Record<string, unknown> | undefined;
|
|
250
|
-
const u = resp?.usage as
|
|
251
|
-
|
|
259
|
+
const u = resp?.usage as
|
|
260
|
+
| { input_tokens?: number; output_tokens?: number; total_tokens?: number }
|
|
261
|
+
| undefined;
|
|
262
|
+
if (
|
|
263
|
+
u &&
|
|
264
|
+
(typeof u.input_tokens === "number" ||
|
|
265
|
+
typeof u.output_tokens === "number" ||
|
|
266
|
+
typeof u.total_tokens === "number")
|
|
267
|
+
) {
|
|
252
268
|
usage = {
|
|
253
|
-
prompt_tokens: u.input_tokens
|
|
254
|
-
completion_tokens: u.output_tokens
|
|
255
|
-
total_tokens:
|
|
269
|
+
prompt_tokens: typeof u.input_tokens === "number" ? u.input_tokens : null,
|
|
270
|
+
completion_tokens: typeof u.output_tokens === "number" ? u.output_tokens : null,
|
|
271
|
+
total_tokens: typeof u.total_tokens === "number" ? u.total_tokens : null,
|
|
256
272
|
};
|
|
257
273
|
}
|
|
258
274
|
}
|
|
@@ -261,16 +277,6 @@ async function callStreamingResponsesAPI(
|
|
|
261
277
|
idle.cleanup();
|
|
262
278
|
}
|
|
263
279
|
|
|
264
|
-
if (!usage) {
|
|
265
|
-
const inputTokens = Math.ceil(userMsg.length / 4);
|
|
266
|
-
const outputTokens = Math.ceil(text.length / 4);
|
|
267
|
-
usage = {
|
|
268
|
-
prompt_tokens: inputTokens,
|
|
269
|
-
completion_tokens: outputTokens,
|
|
270
|
-
total_tokens: inputTokens + outputTokens,
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
|
|
274
280
|
const stripped = stripMarkdownFences(text);
|
|
275
281
|
const parsed = tryParseJSON(stripped);
|
|
276
282
|
|
|
@@ -295,6 +301,7 @@ async function callStreamingChatCompletionsAPI(
|
|
|
295
301
|
presencePenalty: number | undefined,
|
|
296
302
|
topP: number | undefined,
|
|
297
303
|
stop: string | string[] | undefined,
|
|
304
|
+
timeoutMs: number,
|
|
298
305
|
): Promise<AdapterResponse> {
|
|
299
306
|
const messages: Array<{ role: "system" | "user"; content: string }> = [];
|
|
300
307
|
|
|
@@ -312,7 +319,12 @@ async function callStreamingChatCompletionsAPI(
|
|
|
312
319
|
stream_options: { include_usage: true },
|
|
313
320
|
};
|
|
314
321
|
|
|
315
|
-
if (responseSchema != null
|
|
322
|
+
if (responseSchema != null) {
|
|
323
|
+
params["response_format"] = {
|
|
324
|
+
type: "json_schema",
|
|
325
|
+
json_schema: { name: "Response", schema: responseSchema },
|
|
326
|
+
};
|
|
327
|
+
} else if (responseFormat === "json_object" || responseFormat === "json") {
|
|
316
328
|
params["response_format"] = { type: "json_object" };
|
|
317
329
|
}
|
|
318
330
|
|
|
@@ -322,7 +334,7 @@ async function callStreamingChatCompletionsAPI(
|
|
|
322
334
|
if (topP !== undefined) params["top_p"] = topP;
|
|
323
335
|
if (stop !== undefined) params["stop"] = stop;
|
|
324
336
|
|
|
325
|
-
const idle = new IdleTimeoutController(
|
|
337
|
+
const idle = new IdleTimeoutController(timeoutMs);
|
|
326
338
|
|
|
327
339
|
const stream = await openai.chat.completions.create({
|
|
328
340
|
...(params as unknown as Parameters<typeof openai.chat.completions.create>[0]),
|
|
@@ -341,12 +353,20 @@ async function callStreamingChatCompletionsAPI(
|
|
|
341
353
|
| undefined;
|
|
342
354
|
text += choices?.[0]?.delta?.content ?? "";
|
|
343
355
|
|
|
344
|
-
const u = chunk.usage as
|
|
345
|
-
|
|
356
|
+
const u = chunk.usage as
|
|
357
|
+
| { prompt_tokens?: number; completion_tokens?: number; total_tokens?: number }
|
|
358
|
+
| undefined;
|
|
359
|
+
if (
|
|
360
|
+
u &&
|
|
361
|
+
(typeof u.prompt_tokens === "number" ||
|
|
362
|
+
typeof u.completion_tokens === "number" ||
|
|
363
|
+
typeof u.total_tokens === "number")
|
|
364
|
+
) {
|
|
346
365
|
usage = {
|
|
347
|
-
prompt_tokens: u.prompt_tokens,
|
|
348
|
-
completion_tokens:
|
|
349
|
-
|
|
366
|
+
prompt_tokens: typeof u.prompt_tokens === "number" ? u.prompt_tokens : null,
|
|
367
|
+
completion_tokens:
|
|
368
|
+
typeof u.completion_tokens === "number" ? u.completion_tokens : null,
|
|
369
|
+
total_tokens: typeof u.total_tokens === "number" ? u.total_tokens : null,
|
|
350
370
|
};
|
|
351
371
|
}
|
|
352
372
|
}
|
|
@@ -354,10 +374,6 @@ async function callStreamingChatCompletionsAPI(
|
|
|
354
374
|
idle.cleanup();
|
|
355
375
|
}
|
|
356
376
|
|
|
357
|
-
if (!usage) {
|
|
358
|
-
usage = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
|
|
359
|
-
}
|
|
360
|
-
|
|
361
377
|
const stripped = stripMarkdownFences(text);
|
|
362
378
|
const parsed = tryParseJSON(stripped);
|
|
363
379
|
|
|
@@ -376,7 +392,7 @@ export async function openaiChat(
|
|
|
376
392
|
model = DEFAULT_MODEL,
|
|
377
393
|
temperature = 0.7,
|
|
378
394
|
maxTokens = 4096,
|
|
379
|
-
responseFormat
|
|
395
|
+
responseFormat,
|
|
380
396
|
maxRetries = DEFAULT_MAX_RETRIES,
|
|
381
397
|
seed,
|
|
382
398
|
frequencyPenalty,
|
|
@@ -390,18 +406,16 @@ export async function openaiChat(
|
|
|
390
406
|
...rest
|
|
391
407
|
} = options;
|
|
392
408
|
|
|
393
|
-
|
|
409
|
+
const jsonMode = isJsonResponseFormat(responseFormat);
|
|
410
|
+
if (jsonMode) {
|
|
411
|
+
ensureJsonResponseFormat(responseFormat, "openai");
|
|
412
|
+
}
|
|
394
413
|
|
|
395
414
|
const formatStr =
|
|
396
415
|
typeof responseFormat === "string"
|
|
397
416
|
? responseFormat
|
|
398
|
-
: (responseFormat as { type?: string })
|
|
399
|
-
const responseSchema =
|
|
400
|
-
typeof responseFormat === "object" &&
|
|
401
|
-
responseFormat !== null &&
|
|
402
|
-
"json_schema" in responseFormat
|
|
403
|
-
? (responseFormat as { json_schema?: unknown }).json_schema
|
|
404
|
-
: undefined;
|
|
417
|
+
: (responseFormat as { type?: string })?.type ?? "";
|
|
418
|
+
const responseSchema = extractResponseSchema(responseFormat);
|
|
405
419
|
|
|
406
420
|
const { systemMsg, userMsg } = extractMessages(messages);
|
|
407
421
|
const timeoutMs = requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
@@ -415,18 +429,18 @@ export async function openaiChat(
|
|
|
415
429
|
if (useResponsesAPI) {
|
|
416
430
|
try {
|
|
417
431
|
const result = await callStreamingResponsesAPI(
|
|
418
|
-
openai, model, systemMsg, userMsg, temperature, maxTokens, formatStr, responseSchema,
|
|
432
|
+
openai, model, systemMsg, userMsg, temperature, maxTokens, formatStr, responseSchema, timeoutMs,
|
|
419
433
|
);
|
|
420
|
-
checkJsonParse(result, model,
|
|
434
|
+
checkJsonParse(result, model, jsonMode);
|
|
421
435
|
return result;
|
|
422
436
|
} catch (responsesErr) {
|
|
423
437
|
// On "unsupported" error, fall back to streaming Chat Completions within same attempt
|
|
424
438
|
if (isUnsupportedError(responsesErr)) {
|
|
425
439
|
const result = await callStreamingChatCompletionsAPI(
|
|
426
440
|
openai, model, systemMsg, userMsg, temperature, maxTokens, formatStr, responseSchema,
|
|
427
|
-
seed, frequencyPenalty, presencePenalty, topP, stop,
|
|
441
|
+
seed, frequencyPenalty, presencePenalty, topP, stop, timeoutMs,
|
|
428
442
|
);
|
|
429
|
-
checkJsonParse(result, model,
|
|
443
|
+
checkJsonParse(result, model, jsonMode);
|
|
430
444
|
return result;
|
|
431
445
|
}
|
|
432
446
|
throw responsesErr;
|
|
@@ -435,9 +449,9 @@ export async function openaiChat(
|
|
|
435
449
|
|
|
436
450
|
const result = await callStreamingChatCompletionsAPI(
|
|
437
451
|
openai, model, systemMsg, userMsg, temperature, maxTokens, formatStr, responseSchema,
|
|
438
|
-
seed, frequencyPenalty, presencePenalty, topP, stop,
|
|
452
|
+
seed, frequencyPenalty, presencePenalty, topP, stop, timeoutMs,
|
|
439
453
|
);
|
|
440
|
-
checkJsonParse(result, model,
|
|
454
|
+
checkJsonParse(result, model, jsonMode);
|
|
441
455
|
return result;
|
|
442
456
|
} catch (err) {
|
|
443
457
|
lastError = err;
|
|
@@ -457,14 +471,18 @@ export async function openaiChat(
|
|
|
457
471
|
function checkJsonParse(
|
|
458
472
|
result: AdapterResponse,
|
|
459
473
|
model: string,
|
|
460
|
-
|
|
474
|
+
jsonMode: boolean,
|
|
461
475
|
): void {
|
|
462
|
-
|
|
463
|
-
if (isJsonMode && typeof result.content === "string") {
|
|
476
|
+
if (jsonMode && typeof result.content === "string") {
|
|
464
477
|
throw new ProviderJsonParseError(
|
|
465
478
|
"openai",
|
|
466
479
|
model,
|
|
467
480
|
(result.content as string).slice(0, 200),
|
|
481
|
+
undefined,
|
|
482
|
+
{
|
|
483
|
+
text: result.content as string,
|
|
484
|
+
usage: result.usage,
|
|
485
|
+
},
|
|
468
486
|
);
|
|
469
487
|
}
|
|
470
488
|
}
|
|
@@ -5,6 +5,8 @@ import { createOpencodeClient } from "@opencode-ai/sdk/v2";
|
|
|
5
5
|
import type { OpencodeClient } from "@opencode-ai/sdk/v2";
|
|
6
6
|
import {
|
|
7
7
|
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
8
|
+
isJsonResponseFormat,
|
|
9
|
+
extractResponseSchema,
|
|
8
10
|
isRetryableError,
|
|
9
11
|
sleep,
|
|
10
12
|
stripMarkdownFences,
|
|
@@ -23,6 +25,9 @@ import type {
|
|
|
23
25
|
ResponseFormatObject,
|
|
24
26
|
} from "./types.ts";
|
|
25
27
|
|
|
28
|
+
export const isJsonMode = isJsonResponseFormat;
|
|
29
|
+
export const jsonSchemaFromResponseFormat = extractResponseSchema;
|
|
30
|
+
|
|
26
31
|
export type ParsedOpenCodeModel =
|
|
27
32
|
| { providerID: string; modelID: string }
|
|
28
33
|
| null;
|
|
@@ -65,35 +70,6 @@ export function buildOpenCodePromptText(messages: ChatMessage[]): string {
|
|
|
65
70
|
.join("\n\n");
|
|
66
71
|
}
|
|
67
72
|
|
|
68
|
-
export function isJsonMode(
|
|
69
|
-
responseFormat: string | ResponseFormatObject | undefined,
|
|
70
|
-
): boolean {
|
|
71
|
-
if (responseFormat === undefined) return false;
|
|
72
|
-
|
|
73
|
-
if (typeof responseFormat === "string") {
|
|
74
|
-
return responseFormat === "json" || responseFormat === "json_object";
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
responseFormat.type === "json_object" ||
|
|
79
|
-
responseFormat.json_schema != null
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function jsonSchemaFromResponseFormat(
|
|
84
|
-
responseFormat: string | ResponseFormatObject | undefined,
|
|
85
|
-
): unknown | undefined {
|
|
86
|
-
if (
|
|
87
|
-
responseFormat !== undefined &&
|
|
88
|
-
typeof responseFormat === "object" &&
|
|
89
|
-
responseFormat.json_schema != null
|
|
90
|
-
) {
|
|
91
|
-
return responseFormat.json_schema;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return undefined;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
73
|
export function defaultOpenCodePermission(): OpenCodePermissionConfig {
|
|
98
74
|
return { "*": "deny" };
|
|
99
75
|
}
|
|
@@ -288,11 +264,12 @@ function extractOpenCodeContent(
|
|
|
288
264
|
raw: unknown,
|
|
289
265
|
responseFormat: string | ResponseFormatObject | undefined,
|
|
290
266
|
model: string,
|
|
267
|
+
context: { text?: string; usage?: AdapterUsage } = {},
|
|
291
268
|
): Record<string, unknown> | string {
|
|
292
269
|
const structured = extractOpenCodeStructuredOutput(raw);
|
|
293
270
|
if (structured != null) return structured;
|
|
294
271
|
|
|
295
|
-
const text = extractOpenCodeText(raw);
|
|
272
|
+
const text = context.text ?? extractOpenCodeText(raw);
|
|
296
273
|
const jsonMode = isJsonMode(responseFormat);
|
|
297
274
|
|
|
298
275
|
if (jsonMode) {
|
|
@@ -307,6 +284,11 @@ function extractOpenCodeContent(
|
|
|
307
284
|
"opencode",
|
|
308
285
|
model || "default",
|
|
309
286
|
cleaned.slice(0, 200),
|
|
287
|
+
undefined,
|
|
288
|
+
{
|
|
289
|
+
text,
|
|
290
|
+
usage: context.usage,
|
|
291
|
+
},
|
|
310
292
|
);
|
|
311
293
|
}
|
|
312
294
|
|
|
@@ -469,13 +451,14 @@ export async function opencodeChat(
|
|
|
469
451
|
}
|
|
470
452
|
|
|
471
453
|
const raw = result.data;
|
|
454
|
+
const text = extractOpenCodeText(raw);
|
|
455
|
+
const usage = normalizeOpenCodeUsage(raw);
|
|
472
456
|
const content = extractOpenCodeContent(
|
|
473
457
|
raw,
|
|
474
458
|
responseFormat,
|
|
475
459
|
modelString,
|
|
460
|
+
{ text, usage },
|
|
476
461
|
);
|
|
477
|
-
const text = extractOpenCodeText(raw);
|
|
478
|
-
const usage = normalizeOpenCodeUsage(raw);
|
|
479
462
|
|
|
480
463
|
return { content, text, usage, raw };
|
|
481
464
|
} catch (err) {
|
|
@@ -531,8 +514,8 @@ export async function opencodeChat(
|
|
|
531
514
|
|
|
532
515
|
const cliResult = await runOpenCodeCli(args, env, requestTimeoutMs);
|
|
533
516
|
const raw = { events: cliResult.events };
|
|
534
|
-
const content = extractOpenCodeContent(raw, responseFormat, modelString);
|
|
535
517
|
const text = extractOpenCodeText(raw);
|
|
518
|
+
const content = extractOpenCodeContent(raw, responseFormat, modelString, { text });
|
|
536
519
|
|
|
537
520
|
return { content, text, raw };
|
|
538
521
|
} catch (err) {
|
|
@@ -175,20 +175,20 @@ export async function* parseOpenAiSse(
|
|
|
175
175
|
function parseOpenAiUsage(raw: unknown): AdapterUsage | undefined {
|
|
176
176
|
if (!raw || typeof raw !== "object") return undefined;
|
|
177
177
|
const u = raw as Record<string, unknown>;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
};
|
|
178
|
+
const usage: AdapterUsage = {};
|
|
179
|
+
const prompt = u.prompt_tokens;
|
|
180
|
+
const completion = u.completion_tokens;
|
|
181
|
+
const total = u.total_tokens;
|
|
182
|
+
|
|
183
|
+
if (typeof prompt === "number") usage.prompt_tokens = prompt;
|
|
184
|
+
if (typeof completion === "number") usage.completion_tokens = completion;
|
|
185
|
+
if (typeof total === "number") {
|
|
186
|
+
usage.total_tokens = total;
|
|
187
|
+
} else if (typeof prompt === "number" && typeof completion === "number") {
|
|
188
|
+
usage.total_tokens = prompt + completion;
|
|
190
189
|
}
|
|
191
|
-
|
|
190
|
+
|
|
191
|
+
return Object.keys(usage).length > 0 ? usage : undefined;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
// ─── Step 3: Anthropic event parser ──────────────────────────────────────────
|
|
@@ -291,14 +291,20 @@ export async function* parseGeminiSse(
|
|
|
291
291
|
// Capture usage from usageMetadata (present on final chunk)
|
|
292
292
|
const meta = parsed.usageMetadata as Record<string, unknown> | undefined;
|
|
293
293
|
if (meta) {
|
|
294
|
-
const prompt =
|
|
295
|
-
const completion =
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
294
|
+
const prompt = meta.promptTokenCount;
|
|
295
|
+
const completion = meta.candidatesTokenCount;
|
|
296
|
+
const total = meta.totalTokenCount;
|
|
297
|
+
const usage: AdapterUsage = {};
|
|
298
|
+
|
|
299
|
+
if (typeof prompt === "number") usage.prompt_tokens = prompt;
|
|
300
|
+
if (typeof completion === "number") usage.completion_tokens = completion;
|
|
301
|
+
if (typeof total === "number") {
|
|
302
|
+
usage.total_tokens = total;
|
|
303
|
+
} else if (typeof prompt === "number" && typeof completion === "number") {
|
|
304
|
+
usage.total_tokens = prompt + completion;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
lastUsage = Object.keys(usage).length > 0 ? usage : undefined;
|
|
302
308
|
}
|
|
303
309
|
|
|
304
310
|
// Gemini signals completion when candidates have a finishReason
|
package/src/providers/types.ts
CHANGED
|
@@ -26,11 +26,15 @@ export interface ProviderOptions {
|
|
|
26
26
|
requestTimeoutMs?: number;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
/**
|
|
29
|
+
/** Classification of where a usage figure came from. */
|
|
30
|
+
export type UsageSource = "reported" | "estimated" | "unavailable" | "zero";
|
|
31
|
+
|
|
32
|
+
/** Token usage at the adapter level (snake_case). Fields are optional/null-tolerant
|
|
33
|
+
* because providers may report partial usage or omit fields entirely. */
|
|
30
34
|
export interface AdapterUsage {
|
|
31
|
-
prompt_tokens
|
|
32
|
-
completion_tokens
|
|
33
|
-
total_tokens
|
|
35
|
+
prompt_tokens?: number | null;
|
|
36
|
+
completion_tokens?: number | null;
|
|
37
|
+
total_tokens?: number | null;
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
/** Response returned by provider adapters. */
|
|
@@ -38,6 +42,7 @@ export interface AdapterResponse {
|
|
|
38
42
|
content: Record<string, unknown> | string;
|
|
39
43
|
text?: string;
|
|
40
44
|
usage?: AdapterUsage;
|
|
45
|
+
usageAvailable?: boolean;
|
|
41
46
|
raw?: unknown;
|
|
42
47
|
}
|
|
43
48
|
|
|
@@ -51,6 +56,7 @@ export interface NormalizedUsage {
|
|
|
51
56
|
promptTokens: number;
|
|
52
57
|
completionTokens: number;
|
|
53
58
|
totalTokens: number;
|
|
59
|
+
source: UsageSource;
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
/** Response returned by the chat() gateway. */
|
|
@@ -209,12 +215,20 @@ export interface LLMRequestCompleteEvent extends LLMRequestStartEvent {
|
|
|
209
215
|
completionTokens: number;
|
|
210
216
|
totalTokens: number;
|
|
211
217
|
cost: number;
|
|
218
|
+
usageSource: UsageSource;
|
|
219
|
+
costEstimated: boolean;
|
|
212
220
|
}
|
|
213
221
|
|
|
214
222
|
/** Telemetry event: request error. */
|
|
215
223
|
export interface LLMRequestErrorEvent extends LLMRequestStartEvent {
|
|
216
224
|
duration: number;
|
|
217
225
|
error: string;
|
|
226
|
+
promptTokens: number;
|
|
227
|
+
completionTokens: number;
|
|
228
|
+
totalTokens: number;
|
|
229
|
+
cost: number;
|
|
230
|
+
usageSource: UsageSource;
|
|
231
|
+
costEstimated: boolean;
|
|
218
232
|
}
|
|
219
233
|
|
|
220
234
|
/** Provider availability map. */
|
|
@@ -335,12 +349,20 @@ export class ProviderJsonParseError extends Error {
|
|
|
335
349
|
provider: string;
|
|
336
350
|
model: string;
|
|
337
351
|
sample: string;
|
|
352
|
+
text?: string;
|
|
353
|
+
usage?: AdapterUsage;
|
|
354
|
+
usageAvailable?: boolean;
|
|
338
355
|
|
|
339
356
|
constructor(
|
|
340
357
|
provider: string,
|
|
341
358
|
model: string,
|
|
342
359
|
sample: string,
|
|
343
360
|
message?: string,
|
|
361
|
+
options: {
|
|
362
|
+
text?: string;
|
|
363
|
+
usage?: AdapterUsage;
|
|
364
|
+
usageAvailable?: boolean;
|
|
365
|
+
} = {},
|
|
344
366
|
) {
|
|
345
367
|
super(
|
|
346
368
|
message ??
|
|
@@ -350,5 +372,8 @@ export class ProviderJsonParseError extends Error {
|
|
|
350
372
|
this.provider = provider;
|
|
351
373
|
this.model = model;
|
|
352
374
|
this.sample = sample;
|
|
375
|
+
this.text = options.text;
|
|
376
|
+
this.usage = options.usage;
|
|
377
|
+
this.usageAvailable = options.usageAvailable;
|
|
353
378
|
}
|
|
354
379
|
}
|
package/src/providers/zhipu.ts
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
tryParseJSON,
|
|
11
11
|
createProviderError,
|
|
12
12
|
ensureJsonResponseFormat,
|
|
13
|
+
isJsonResponseFormat,
|
|
14
|
+
extractResponseSchema,
|
|
13
15
|
} from "./base.ts";
|
|
14
16
|
import {
|
|
15
17
|
IdleTimeoutController,
|
|
@@ -18,43 +20,14 @@ import {
|
|
|
18
20
|
accumulateStream,
|
|
19
21
|
} from "./stream-accumulator.ts";
|
|
20
22
|
import { ProviderJsonParseError } from "./types.ts";
|
|
21
|
-
import type { ProviderOptions, AdapterResponse
|
|
23
|
+
import type { ProviderOptions, AdapterResponse } from "./types.ts";
|
|
22
24
|
|
|
23
25
|
const ZAI_API_URL = "https://api.z.ai/api/paas/v4/chat/completions";
|
|
24
26
|
const DEFAULT_MODEL = "glm-5";
|
|
25
27
|
const DEFAULT_TEMPERATURE = 0.7;
|
|
26
28
|
const DEFAULT_MAX_TOKENS = 8192;
|
|
27
|
-
const DEFAULT_RESPONSE_FORMAT = "json";
|
|
28
29
|
const DEFAULT_MAX_RETRIES = 3;
|
|
29
30
|
|
|
30
|
-
/** Returns true when responseFormat indicates JSON output. */
|
|
31
|
-
function isJsonMode(
|
|
32
|
-
responseFormat: string | ResponseFormatObject | undefined,
|
|
33
|
-
): boolean {
|
|
34
|
-
if (!responseFormat) return false;
|
|
35
|
-
if (typeof responseFormat === "string") {
|
|
36
|
-
return responseFormat === "json" || responseFormat === "json_object";
|
|
37
|
-
}
|
|
38
|
-
return (
|
|
39
|
-
responseFormat.type === "json_object" ||
|
|
40
|
-
responseFormat.json_schema != null
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Extracts a JSON schema object from responseFormat, if present. */
|
|
45
|
-
function extractJsonSchema(
|
|
46
|
-
responseFormat: string | ResponseFormatObject | undefined,
|
|
47
|
-
): unknown | undefined {
|
|
48
|
-
if (
|
|
49
|
-
responseFormat &&
|
|
50
|
-
typeof responseFormat === "object" &&
|
|
51
|
-
responseFormat.json_schema != null
|
|
52
|
-
) {
|
|
53
|
-
return responseFormat.json_schema;
|
|
54
|
-
}
|
|
55
|
-
return undefined;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
31
|
export async function zaiChat(
|
|
59
32
|
options: ProviderOptions,
|
|
60
33
|
): Promise<AdapterResponse> {
|
|
@@ -63,23 +36,22 @@ export async function zaiChat(
|
|
|
63
36
|
model = DEFAULT_MODEL,
|
|
64
37
|
temperature = DEFAULT_TEMPERATURE,
|
|
65
38
|
maxTokens = DEFAULT_MAX_TOKENS,
|
|
66
|
-
responseFormat
|
|
39
|
+
responseFormat,
|
|
67
40
|
topP,
|
|
68
41
|
stop,
|
|
69
42
|
maxRetries = DEFAULT_MAX_RETRIES,
|
|
70
43
|
requestTimeoutMs,
|
|
71
44
|
} = options;
|
|
72
45
|
|
|
73
|
-
const jsonMode =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
46
|
+
const jsonMode = isJsonResponseFormat(responseFormat);
|
|
47
|
+
if (jsonMode) {
|
|
48
|
+
ensureJsonResponseFormat(responseFormat, "zai");
|
|
49
|
+
}
|
|
77
50
|
|
|
78
51
|
const { systemMsg, userMessages, assistantMessages } =
|
|
79
52
|
extractMessages(messages);
|
|
80
53
|
|
|
81
|
-
|
|
82
|
-
const jsonSchema = extractJsonSchema(responseFormat);
|
|
54
|
+
const jsonSchema = extractResponseSchema(responseFormat);
|
|
83
55
|
let systemText = systemMsg;
|
|
84
56
|
if (jsonSchema) {
|
|
85
57
|
const schemaStr = JSON.stringify(jsonSchema, null, 2);
|
|
@@ -180,19 +152,18 @@ export async function zaiChat(
|
|
|
180
152
|
"zai",
|
|
181
153
|
model,
|
|
182
154
|
parsed.slice(0, 200),
|
|
155
|
+
undefined,
|
|
156
|
+
{
|
|
157
|
+
text: rawText,
|
|
158
|
+
usage: accumulated.usage,
|
|
159
|
+
},
|
|
183
160
|
);
|
|
184
161
|
}
|
|
185
162
|
|
|
186
|
-
const usage = {
|
|
187
|
-
prompt_tokens: accumulated.usage?.prompt_tokens ?? 0,
|
|
188
|
-
completion_tokens: accumulated.usage?.completion_tokens ?? 0,
|
|
189
|
-
total_tokens: accumulated.usage?.total_tokens ?? 0,
|
|
190
|
-
};
|
|
191
|
-
|
|
192
163
|
return {
|
|
193
164
|
content: typeof parsed === "string" ? parsed : (parsed as Record<string, unknown>),
|
|
194
165
|
text: rawText,
|
|
195
|
-
usage,
|
|
166
|
+
usage: accumulated.usage,
|
|
196
167
|
raw: accumulated,
|
|
197
168
|
};
|
|
198
169
|
} catch (err) {
|
|
Binary file
|