onto-mcp 0.4.11 → 0.4.13
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/.onto/authority/core-lexicon.yaml +1 -1
- package/.onto/authority/supported-models.yaml +153 -0
- package/.onto/domains/accounting-kr/competency_qs.md +153 -153
- package/.onto/processes/reconstruct/obligation-coverage-ledger.yaml +842 -0
- package/.onto/processes/reconstruct/obligation-coverage-recorded.yaml +225 -0
- package/.onto/processes/reconstruct/ontology-seeding-and-maturation-design.md +93 -54
- package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +202 -49
- package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +72 -1
- package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +35 -0
- package/.onto/processes/review/nesting-batch-worker-contract.md +1 -1
- package/.onto/processes/review/productized-live-path.md +9 -5
- package/.onto/processes/review/prompt-execution-runner-contract.md +32 -8
- package/.onto/processes/review/review-target-profile-contract.md +47 -4
- package/.onto/processes/shared/pipeline-execution-ledger-contract.md +11 -0
- package/.onto/processes/shared/target-material-kind-contract.md +1 -1
- package/README.md +64 -1
- package/dist/cli.js +11 -1
- package/dist/core-api/reconstruct-api.js +679 -18
- package/dist/core-api/review-api.js +70 -5
- package/dist/core-api/review-progress.js +7 -0
- package/dist/core-api/runtime-observation.js +7 -0
- package/dist/core-runtime/artifact-io.js +131 -0
- package/dist/core-runtime/cli/claude-code-review-unit-executor.js +21 -9
- package/dist/core-runtime/cli/claude-nesting-batch-worker.js +4 -2
- package/dist/core-runtime/cli/materialize-review-prompt-packets.js +68 -14
- package/dist/core-runtime/cli/prepare-review-session.js +34 -1
- package/dist/core-runtime/cli/render-review-final-output.js +2 -1
- package/dist/core-runtime/cli/run-review-prompt-execution.js +758 -48
- package/dist/core-runtime/cli/run-seats.js +18 -0
- package/dist/core-runtime/cli/runtime-submit-context.js +10 -2
- package/dist/core-runtime/cli/unit-resubmit.js +269 -0
- package/dist/core-runtime/cli/worker-structured-output.js +80 -2
- package/dist/core-runtime/discovery/host-detection.js +71 -22
- package/dist/core-runtime/discovery/review-cert-assemble.js +208 -0
- package/dist/core-runtime/discovery/review-cert-record.js +442 -0
- package/dist/core-runtime/discovery/review-execution-units.js +22 -0
- package/dist/core-runtime/discovery/seat-inventory.js +167 -0
- package/dist/core-runtime/discovery/settings-chain.js +311 -52
- package/dist/core-runtime/discovery/supported-models.js +289 -25
- package/dist/core-runtime/discovery/synthesize-cert-assemble.js +197 -0
- package/dist/core-runtime/discovery/synthesize-cert-capsule.js +399 -0
- package/dist/core-runtime/discovery/synthesize-cert-judge.js +340 -0
- package/dist/core-runtime/discovery/synthesize-cert-loop.js +234 -0
- package/dist/core-runtime/discovery/synthesize-cert-mutation.js +117 -0
- package/dist/core-runtime/discovery/synthesize-cert-packet.js +182 -0
- package/dist/core-runtime/discovery/synthesize-cert-record.js +831 -0
- package/dist/core-runtime/discovery/synthesize-cert-sampler.js +336 -0
- package/dist/core-runtime/effort-calibration-ingest.js +326 -0
- package/dist/core-runtime/effort-calibration-reconstruct.js +47 -0
- package/dist/core-runtime/effort-calibration-review.js +22 -0
- package/dist/core-runtime/effort-calibration-sweep.js +110 -0
- package/dist/core-runtime/effort-frontier.js +134 -0
- package/dist/core-runtime/llm/claude-bin.js +77 -0
- package/dist/core-runtime/llm/dispatch-breaker.js +412 -0
- package/dist/core-runtime/llm/dispatch-fallback-adapter-capabilities.js +33 -0
- package/dist/core-runtime/llm/llm-caller.js +316 -35
- package/dist/core-runtime/llm/mock-llm-realization.js +35 -4
- package/dist/core-runtime/llm/model-switcher.js +1 -1
- package/dist/core-runtime/llm/openai-responses-incomplete-error.js +46 -0
- package/dist/core-runtime/llm/sealed-dispatch-capability.js +359 -0
- package/dist/core-runtime/llm/structured-dispatch-error.js +15 -0
- package/dist/core-runtime/onboard/claude-profile-scan.js +65 -0
- package/dist/core-runtime/onboard/cli-host.js +5 -2
- package/dist/core-runtime/onboard/configure-provider.js +33 -1
- package/dist/core-runtime/onboard/host-target.js +28 -3
- package/dist/core-runtime/onboard/register.js +54 -12
- package/dist/core-runtime/pipeline-execution-ledger.js +10 -0
- package/dist/core-runtime/reconstruct/artifact-types.js +37 -0
- package/dist/core-runtime/reconstruct/benchmark-evidence.js +16 -0
- package/dist/core-runtime/reconstruct/claim-projection-validation.js +19 -5
- package/dist/core-runtime/reconstruct/competency-projection-contract.js +63 -0
- package/dist/core-runtime/reconstruct/comprehension-artifact.js +321 -0
- package/dist/core-runtime/reconstruct/comprehension-reduce.js +367 -0
- package/dist/core-runtime/reconstruct/comprehension-semantic-map.js +771 -0
- package/dist/core-runtime/reconstruct/contract-registry.js +20 -0
- package/dist/core-runtime/reconstruct/directive-validation.js +4 -3
- package/dist/core-runtime/reconstruct/dispatch-fallback-artifacts.js +640 -0
- package/dist/core-runtime/reconstruct/execution-telemetry.js +84 -9
- package/dist/core-runtime/reconstruct/final-output-sections.js +89 -0
- package/dist/core-runtime/reconstruct/governing-snapshot.js +36 -1
- package/dist/core-runtime/reconstruct/leaf-reader.js +305 -0
- package/dist/core-runtime/reconstruct/llm-dispatch-failure.js +270 -0
- package/dist/core-runtime/reconstruct/llm-touch-fingerprint.js +94 -0
- package/dist/core-runtime/reconstruct/material-admission-validation.js +20 -5
- package/dist/core-runtime/reconstruct/material-profile-validation.js +24 -6
- package/dist/core-runtime/reconstruct/materialize-preparation.js +380 -23
- package/dist/core-runtime/reconstruct/maturation-validation.js +1522 -67
- package/dist/core-runtime/reconstruct/mock-llm-realization.js +120 -1
- package/dist/core-runtime/reconstruct/obligation-assertion.js +18 -0
- package/dist/core-runtime/reconstruct/ontology-seed-validation.js +39 -7
- package/dist/core-runtime/reconstruct/output-budget.js +6 -0
- package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +112 -5
- package/dist/core-runtime/reconstruct/post-seed-validation.js +122 -11
- package/dist/core-runtime/reconstruct/proof-authority-validation.js +2 -6
- package/dist/core-runtime/reconstruct/purpose-authority-validation.js +119 -7
- package/dist/core-runtime/reconstruct/record.js +49 -3
- package/dist/core-runtime/reconstruct/registry-verification-validation.js +41 -5
- package/dist/core-runtime/reconstruct/run-control-validation.js +1019 -37
- package/dist/core-runtime/reconstruct/run.js +5409 -517
- package/dist/core-runtime/reconstruct/seed-authoring-readiness-validation.js +56 -8
- package/dist/core-runtime/reconstruct/semantic-quality-gate.js +112 -7
- package/dist/core-runtime/reconstruct/source-observation-delta-validation.js +53 -5
- package/dist/core-runtime/reconstruct/source-observations.js +166 -0
- package/dist/core-runtime/reconstruct/source-safety-validation.js +77 -188
- package/dist/core-runtime/reconstruct/source-scout-pack-validation.js +50 -6
- package/dist/core-runtime/reconstruct/terminal-validation.js +145 -13
- package/dist/core-runtime/review/continuation-plan.js +12 -5
- package/dist/core-runtime/review/issue-artifact-runtime.js +4 -1
- package/dist/core-runtime/review/materializers.js +100 -11
- package/dist/core-runtime/review/nesting-batch.js +6 -2
- package/dist/core-runtime/review/obligation-shardability.js +138 -0
- package/dist/core-runtime/review/pipeline-execution-ledger.js +42 -2
- package/dist/core-runtime/review/review-artifact-utils.js +219 -7
- package/dist/core-runtime/review/review-execution-steps.js +32 -8
- package/dist/core-runtime/review/review-progress-contract.js +8 -0
- package/dist/core-runtime/review/review-prompt-budget.js +97 -0
- package/dist/core-runtime/review/semantic-quality-gate.js +33 -7
- package/dist/core-runtime/review/spreadsheet-review-disposition.js +94 -0
- package/dist/core-runtime/route-identity.js +182 -0
- package/dist/core-runtime/spreadsheet-header-escalation.js +204 -0
- package/dist/core-runtime/spreadsheet-structure-observer.js +2689 -0
- package/dist/core-runtime/target-material-kind.js +58 -0
- package/dist/mcp/server.js +27 -5
- package/dist/mcp/tool-schemas.js +8 -0
- package/dist/tui/app.js +167 -0
- package/dist/tui/data/event-follower.js +116 -0
- package/dist/tui/data/node-detail.js +42 -0
- package/dist/tui/data/projection-poll.js +26 -0
- package/dist/tui/data/session-discovery.js +65 -0
- package/dist/tui/index.js +108 -0
- package/dist/tui/screens/log.js +25 -0
- package/dist/tui/screens/node-detail.js +17 -0
- package/dist/tui/screens/session-selector.js +26 -0
- package/dist/tui/screens/workflow-tree.js +90 -0
- package/dist/tui/view-model/reconstruct-adapter.js +162 -0
- package/dist/tui/view-model/review-adapter.js +270 -0
- package/dist/tui/view-model/tree-view-model.js +19 -0
- package/package.json +20 -3
- package/settings.example.json +77 -16
|
@@ -17,8 +17,10 @@ import fs from "node:fs";
|
|
|
17
17
|
import path from "node:path";
|
|
18
18
|
import os from "node:os";
|
|
19
19
|
import { DEFAULT_GROK_BASE_URL, DEFAULT_LMSTUDIO_BASE_URL, isExternalOauthWorkerSelection, normalizeLlmModelSwitcher, } from "./model-switcher.js";
|
|
20
|
+
import { resolveClaudeBin } from "./claude-bin.js";
|
|
20
21
|
import { appendRuntimeModelCallLogFromCurrentContext, appendRuntimeStreamChunkFromCurrentContextSync, } from "../observability/runtime-stream-observation.js";
|
|
21
22
|
import { callReviewMockLlm, isReviewMockLlmRealizationEnabled, } from "./mock-llm-realization.js";
|
|
23
|
+
import { OPENAI_RESPONSES_MAX_OUTPUT_TOKENS_FAILURE_CODE, OpenAIResponsesIncompleteError, resolveOpenAIResponsesOutputBudget, } from "./openai-responses-incomplete-error.js";
|
|
22
24
|
/**
|
|
23
25
|
* Bridge: OntoConfig + CLI overrides → Partial<LlmCallConfig> that callLlm consumes.
|
|
24
26
|
*
|
|
@@ -66,20 +68,75 @@ export function resolveLlmProviderConfig(args) {
|
|
|
66
68
|
out.service_tier = service_tier;
|
|
67
69
|
if (api_key_env)
|
|
68
70
|
out.api_key_env = api_key_env;
|
|
71
|
+
// Transport timeout carried on the llm block (not a model-switch axis).
|
|
72
|
+
if (config.llm?.timeout_ms)
|
|
73
|
+
out.timeout_ms = config.llm.timeout_ms;
|
|
69
74
|
if (Object.keys(models_per_provider).length > 0) {
|
|
70
75
|
out.models_per_provider = models_per_provider;
|
|
71
76
|
}
|
|
72
77
|
return out;
|
|
73
78
|
}
|
|
79
|
+
export function applyOpenAIResponsesOutputHeadroom(args) {
|
|
80
|
+
if (args.headroomTokens === undefined)
|
|
81
|
+
return args.config;
|
|
82
|
+
if (args.selection.model_provider !== "openai" ||
|
|
83
|
+
args.selection.auth !== "api_key" ||
|
|
84
|
+
args.selection.execution_route !== "direct_model_call" ||
|
|
85
|
+
args.selection.execution_adapter !== "openai_sdk" ||
|
|
86
|
+
args.selection.wire_format !== "native_sdk" ||
|
|
87
|
+
args.selection.base_url !== undefined) {
|
|
88
|
+
throw new Error("openai Responses output headroom requires the official openai + api_key + openai_sdk + native_sdk route without a custom base_url.");
|
|
89
|
+
}
|
|
90
|
+
if (args.modelMaxOutputTokens === undefined) {
|
|
91
|
+
throw new Error("openai Responses output headroom requires registered max_output_tokens.");
|
|
92
|
+
}
|
|
93
|
+
resolveOpenAIResponsesOutputBudget({
|
|
94
|
+
baseOutputTokens: args.maxBaseOutputTokens,
|
|
95
|
+
headroomTokens: args.headroomTokens,
|
|
96
|
+
modelMaxOutputTokens: args.modelMaxOutputTokens,
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
...args.config,
|
|
100
|
+
openai_responses_output_headroom_tokens: args.headroomTokens,
|
|
101
|
+
openai_responses_model_max_output_tokens: args.modelMaxOutputTokens,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
74
104
|
// Phase 3 production found 30s too tight for large audit batches (37 items
|
|
75
105
|
// could time out then SDK-retry for 90s total). 120s is generous
|
|
76
106
|
// enough for ~50-item single-batch audits while still failing fast on real
|
|
77
107
|
// network problems.
|
|
78
108
|
const DEFAULT_TIMEOUT_MS = Number(process.env.ONTO_LLM_TIMEOUT_MS) || 120_000;
|
|
109
|
+
// External OAuth CLI workers (`codex exec`, `claude -p`) spawn a full agentic
|
|
110
|
+
// turn, not a single SDK request — a heavy single-turn authoring call (e.g.
|
|
111
|
+
// reconstruct seed authoring on a frontier model) routinely runs minutes. The
|
|
112
|
+
// 120s SDK default is too tight for that path and caused deterministic timeouts
|
|
113
|
+
// on the claude-opus-4-8 reconstruct route; give the CLI workers a longer
|
|
114
|
+
// default so supported models complete without an unencoded env override. The
|
|
115
|
+
// `ONTO_LLM_TIMEOUT_MS` override still applies to both paths when set.
|
|
116
|
+
const DEFAULT_WORKER_TIMEOUT_MS = Number(process.env.ONTO_LLM_TIMEOUT_MS) || 600_000;
|
|
117
|
+
// Grace period after a timeout SIGTERM before escalating to SIGKILL. A worker
|
|
118
|
+
// that traps/ignores SIGTERM would otherwise leave the run hanging indefinitely
|
|
119
|
+
// (the close-event promise never resolves); SIGKILL cannot be trapped, so this
|
|
120
|
+
// bounds the worst-case hang to timeout + grace.
|
|
121
|
+
const WORKER_SIGKILL_GRACE_MS = 5_000;
|
|
79
122
|
// SDK auto-retry hides failures behind a long stall. We surface failures
|
|
80
123
|
// faster (1 retry instead of the default 2) so operators see provider errors
|
|
81
124
|
// within ~2× timeout instead of ~3×.
|
|
82
125
|
const DEFAULT_MAX_RETRIES = 1;
|
|
126
|
+
const OPENAI_RESPONSES_BASE_URL = "https://api.openai.com/v1";
|
|
127
|
+
function sanitizeEffectiveBaseUrl(baseUrl) {
|
|
128
|
+
try {
|
|
129
|
+
const sanitized = new URL(baseUrl);
|
|
130
|
+
sanitized.username = "";
|
|
131
|
+
sanitized.password = "";
|
|
132
|
+
sanitized.search = "";
|
|
133
|
+
sanitized.hash = "";
|
|
134
|
+
return sanitized.toString();
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return "invalid-endpoint:redacted";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
83
140
|
/**
|
|
84
141
|
* Model-call observability — emits STDERR logs for each LLM API call, covering
|
|
85
142
|
* (a) pre-call model_id + provider + max_tokens, (b) post-call usage on success,
|
|
@@ -213,7 +270,7 @@ function missingModelError(provider) {
|
|
|
213
270
|
].join("\n"));
|
|
214
271
|
}
|
|
215
272
|
function unsupportedReasoningEffortError(provider) {
|
|
216
|
-
return new Error(`provider=${provider} cannot honor reasoning_effort; remove effort from this actor or switch to provider=openai/codex.`);
|
|
273
|
+
return new Error(`provider=${provider} cannot honor reasoning_effort; remove effort from this actor or switch to provider=openai/codex/anthropic.`);
|
|
217
274
|
}
|
|
218
275
|
function assertNoUnsupportedReasoningEffort(provider, reasoningEffort) {
|
|
219
276
|
if (reasoningEffort) {
|
|
@@ -223,19 +280,47 @@ function assertNoUnsupportedReasoningEffort(provider, reasoningEffort) {
|
|
|
223
280
|
// ---------------------------------------------------------------------------
|
|
224
281
|
// Anthropic call
|
|
225
282
|
// ---------------------------------------------------------------------------
|
|
226
|
-
async function callAnthropic(systemPrompt, userPrompt, apiKey, modelId, maxTokens) {
|
|
283
|
+
async function callAnthropic(systemPrompt, userPrompt, apiKey, modelId, maxTokens, reasoningEffort, thinkingMode) {
|
|
227
284
|
const { default: Anthropic } = await import("@anthropic-ai/sdk");
|
|
228
285
|
const client = new Anthropic({
|
|
229
286
|
apiKey,
|
|
230
287
|
timeout: DEFAULT_TIMEOUT_MS,
|
|
231
288
|
maxRetries: DEFAULT_MAX_RETRIES,
|
|
232
289
|
});
|
|
233
|
-
emitModelCallLog(`anthropic call: model="${modelId}" max_tokens=${maxTokens}`);
|
|
290
|
+
emitModelCallLog(`anthropic call: model="${modelId}" max_tokens=${maxTokens} effort=${reasoningEffort ?? "(unset)"} thinking=${thinkingMode ?? "(default)"}`);
|
|
234
291
|
let response;
|
|
235
292
|
try {
|
|
236
293
|
response = await client.messages.create({
|
|
237
294
|
model: modelId,
|
|
238
295
|
max_tokens: maxTokens,
|
|
296
|
+
// Thinking configuration is a three-way choice, and every non-default
|
|
297
|
+
// branch is additive over the legacy no-op tail so an absent opt-in emits
|
|
298
|
+
// byte-identical request bytes:
|
|
299
|
+
// thinking_mode="disabled" → thinking:{type:"disabled"} (explicit no
|
|
300
|
+
// extended thinking; accepted on opus-4.7/4.8 and sonnet-5, rejected
|
|
301
|
+
// by Fable 5). Takes precedence over effort: effort's only job on this
|
|
302
|
+
// route is to modulate *thinking* depth, which is moot once thinking
|
|
303
|
+
// is off, so output_config is not paired here.
|
|
304
|
+
// reasoningEffort set → adaptive thinking paired with output_config.
|
|
305
|
+
// effort (GA, no beta header). On opus-4.x effort only modulates
|
|
306
|
+
// thinking depth when adaptive is enabled — without adaptive the model
|
|
307
|
+
// runs without thinking, so configured effort would not be realized on
|
|
308
|
+
// this route. Pairing the two makes effort actually engage reasoning
|
|
309
|
+
// (matching the openai Responses and claude-CLI routes); effort also
|
|
310
|
+
// bounds overall token spend. The free-form seat string maps onto the
|
|
311
|
+
// API effort enum.
|
|
312
|
+
// neither → no thinking block (API default: high, no thinking — legacy
|
|
313
|
+
// behavior preserved).
|
|
314
|
+
...(thinkingMode === "disabled"
|
|
315
|
+
? { thinking: { type: "disabled" } }
|
|
316
|
+
: reasoningEffort
|
|
317
|
+
? {
|
|
318
|
+
thinking: { type: "adaptive" },
|
|
319
|
+
output_config: {
|
|
320
|
+
effort: reasoningEffort,
|
|
321
|
+
},
|
|
322
|
+
}
|
|
323
|
+
: {}),
|
|
239
324
|
system: systemPrompt,
|
|
240
325
|
messages: [{ role: "user", content: userPrompt }],
|
|
241
326
|
});
|
|
@@ -243,9 +328,31 @@ async function callAnthropic(systemPrompt, userPrompt, apiKey, modelId, maxToken
|
|
|
243
328
|
catch (err) {
|
|
244
329
|
const e = err;
|
|
245
330
|
emitModelCallLog(`anthropic call FAILED: model="${modelId}" status=${e.status ?? "?"} type=${e.error?.type ?? e.name ?? "?"} message="${e.error?.message ?? e.message ?? String(err)}" request_id=${e.request_id ?? "?"}`);
|
|
331
|
+
// Normalize SDK request timeouts (thrown after maxRetries is exhausted) to a
|
|
332
|
+
// message the timeout-recovery classifier recognizes — the SDK error's own
|
|
333
|
+
// message ("Request timed out.") does not match, so api_key-provider timeout
|
|
334
|
+
// recovery was previously dead. instanceof is the robust check (the class
|
|
335
|
+
// does not override Error.prototype.name).
|
|
336
|
+
if (err instanceof Anthropic.APIConnectionTimeoutError) {
|
|
337
|
+
throw new Error(`anthropic call timed out after ${DEFAULT_TIMEOUT_MS}ms`, { cause: err });
|
|
338
|
+
}
|
|
246
339
|
throw err;
|
|
247
340
|
}
|
|
248
341
|
emitModelCallLog(`anthropic success: model_id=${response.model ?? modelId} input_tokens=${response.usage.input_tokens} output_tokens=${response.usage.output_tokens}`);
|
|
342
|
+
// A max_tokens stop means the response was truncated and may carry
|
|
343
|
+
// partial/empty text — fail loud rather than record a truncated artifact.
|
|
344
|
+
// Both thinking-bearing branches are guarded: with adaptive thinking on
|
|
345
|
+
// (effort set) thinking shares the max_tokens budget; with thinking disabled
|
|
346
|
+
// the whole budget is the answer, so a cap still truncates it. The legacy
|
|
347
|
+
// no-opt path (neither set) stays unguarded, unchanged.
|
|
348
|
+
if ((reasoningEffort || thinkingMode === "disabled") &&
|
|
349
|
+
response.stop_reason === "max_tokens") {
|
|
350
|
+
throw new Error(`anthropic response truncated at max_tokens=${maxTokens}` +
|
|
351
|
+
(reasoningEffort
|
|
352
|
+
? ` (effort=${reasoningEffort} reasoning exhausted the budget)`
|
|
353
|
+
: " (thinking disabled; answer exceeded the budget)") +
|
|
354
|
+
"; raise max_tokens");
|
|
355
|
+
}
|
|
249
356
|
const text = response.content
|
|
250
357
|
.filter((block) => block.type === "text")
|
|
251
358
|
.map((block) => ("text" in block ? block.text : ""))
|
|
@@ -286,6 +393,12 @@ async function callOpenAI(systemPrompt, userPrompt, apiKey, modelId, maxTokens,
|
|
|
286
393
|
catch (err) {
|
|
287
394
|
const e = err;
|
|
288
395
|
emitModelCallLog(`${providerLabel} call FAILED: model="${modelId}" status=${e.status ?? "?"} type=${e.error?.type ?? e.name ?? "?"} message="${e.error?.message ?? e.message ?? String(err)}" request_id=${e.request_id ?? "?"}`);
|
|
396
|
+
// Normalize SDK request timeouts to a classifier-recognized message (see the
|
|
397
|
+
// anthropic path above) so OpenAI/grok/lmstudio api_key timeouts also trigger
|
|
398
|
+
// timeout recovery instead of failing hard.
|
|
399
|
+
if (err instanceof OpenAI.APIConnectionTimeoutError) {
|
|
400
|
+
throw new Error(`${providerLabel} call timed out after ${DEFAULT_TIMEOUT_MS}ms`, { cause: err });
|
|
401
|
+
}
|
|
289
402
|
throw err;
|
|
290
403
|
}
|
|
291
404
|
emitModelCallLog(`${providerLabel} success: model_id=${response.model ?? modelId} input_tokens=${response.usage?.prompt_tokens ?? 0} output_tokens=${response.usage?.completion_tokens ?? 0}`);
|
|
@@ -305,6 +418,142 @@ async function callOpenAI(systemPrompt, userPrompt, apiKey, modelId, maxTokens,
|
|
|
305
418
|
};
|
|
306
419
|
}
|
|
307
420
|
// ---------------------------------------------------------------------------
|
|
421
|
+
// OpenAI Responses API call (canonical OpenAI reasoning models, e.g. gpt-5.x)
|
|
422
|
+
// ---------------------------------------------------------------------------
|
|
423
|
+
/**
|
|
424
|
+
* Canonical OpenAI path. Reasoning models (gpt-5.x) are driven through the
|
|
425
|
+
* Responses API: reasoning depth is `reasoning.effort` and the output cap is
|
|
426
|
+
* `max_output_tokens` (Chat Completions' `max_tokens` is rejected by reasoning
|
|
427
|
+
* models). grok/lmstudio stay on Chat Completions via `callOpenAI` — they are
|
|
428
|
+
* OpenAI-compatible Chat Completions endpoints with no Responses API.
|
|
429
|
+
*/
|
|
430
|
+
async function callOpenAIResponses(systemPrompt, userPrompt, apiKey, modelId, baseMaxOutputTokens, reasoningEffort, outputHeadroomTokens, modelMaxOutputTokens) {
|
|
431
|
+
const budget = resolveOpenAIResponsesOutputBudget({
|
|
432
|
+
baseOutputTokens: baseMaxOutputTokens,
|
|
433
|
+
...(outputHeadroomTokens !== undefined
|
|
434
|
+
? { headroomTokens: outputHeadroomTokens }
|
|
435
|
+
: {}),
|
|
436
|
+
...(modelMaxOutputTokens !== undefined
|
|
437
|
+
? { modelMaxOutputTokens }
|
|
438
|
+
: {}),
|
|
439
|
+
});
|
|
440
|
+
const { default: OpenAI } = await import("openai");
|
|
441
|
+
const effectiveBaseUrl = sanitizeEffectiveBaseUrl(outputHeadroomTokens === undefined
|
|
442
|
+
? process.env.OPENAI_BASE_URL ?? OPENAI_RESPONSES_BASE_URL
|
|
443
|
+
: OPENAI_RESPONSES_BASE_URL);
|
|
444
|
+
const noOpLogger = {
|
|
445
|
+
error: () => { },
|
|
446
|
+
warn: () => { },
|
|
447
|
+
info: () => { },
|
|
448
|
+
debug: () => { },
|
|
449
|
+
};
|
|
450
|
+
const client = new OpenAI({
|
|
451
|
+
apiKey,
|
|
452
|
+
...(outputHeadroomTokens !== undefined
|
|
453
|
+
? { baseURL: OPENAI_RESPONSES_BASE_URL }
|
|
454
|
+
: {}),
|
|
455
|
+
timeout: DEFAULT_TIMEOUT_MS,
|
|
456
|
+
maxRetries: DEFAULT_MAX_RETRIES,
|
|
457
|
+
logLevel: "off",
|
|
458
|
+
logger: noOpLogger,
|
|
459
|
+
});
|
|
460
|
+
emitModelCallLog(`openai call: model="${modelId}" max_output_tokens=${budget.effectiveMaxOutputTokens} effort=${reasoningEffort ?? "(unset)"}`);
|
|
461
|
+
let response;
|
|
462
|
+
try {
|
|
463
|
+
response = await client.responses.create({
|
|
464
|
+
model: modelId,
|
|
465
|
+
instructions: systemPrompt,
|
|
466
|
+
input: userPrompt,
|
|
467
|
+
max_output_tokens: budget.effectiveMaxOutputTokens,
|
|
468
|
+
// Transient single-turn authoring/review calls; the Responses API stores
|
|
469
|
+
// response objects provider-side by default (retrievable for ~30d). Opt
|
|
470
|
+
// out so these prompts/outputs are not persisted — matches the
|
|
471
|
+
// chat.completions path, which never stored.
|
|
472
|
+
store: false,
|
|
473
|
+
...(reasoningEffort ? { reasoning: { effort: reasoningEffort } } : {}),
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
catch (err) {
|
|
477
|
+
const e = err;
|
|
478
|
+
emitModelCallLog(`openai call FAILED: model="${modelId}" status=${e.status ?? "?"} type=${e.error?.type ?? e.name ?? "?"} message="${e.error?.message ?? e.message ?? String(err)}" request_id=${e.request_id ?? "?"}`);
|
|
479
|
+
// Mirror the api_key timeout normalization on the chat.completions path so
|
|
480
|
+
// Responses-API timeouts also trigger timeout recovery.
|
|
481
|
+
if (err instanceof OpenAI.APIConnectionTimeoutError) {
|
|
482
|
+
throw new Error(`openai call timed out after ${DEFAULT_TIMEOUT_MS}ms`, {
|
|
483
|
+
cause: err,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
throw err;
|
|
487
|
+
}
|
|
488
|
+
// Only status="completed" carries a usable result. Anything else —
|
|
489
|
+
// "incomplete" (e.g. max_output_tokens exhausted, partial/empty output_text),
|
|
490
|
+
// "failed", "cancelled", "queued", "in_progress" — would otherwise be recorded
|
|
491
|
+
// as a normal result via `output_text ?? ""`, silently corrupting downstream
|
|
492
|
+
// artifacts. Fail loud so the caller retries or surfaces it.
|
|
493
|
+
if (response.status !== "completed") {
|
|
494
|
+
const detail = response.incomplete_details?.reason ?? response.error?.message ?? "no detail";
|
|
495
|
+
emitModelCallLog(`openai call NOT COMPLETED: model="${modelId}" status=${response.status} detail=${detail} max_output_tokens=${budget.effectiveMaxOutputTokens}`);
|
|
496
|
+
if (response.status === "incomplete" &&
|
|
497
|
+
response.incomplete_details?.reason === "max_output_tokens") {
|
|
498
|
+
const usage = response.usage;
|
|
499
|
+
const inputTokens = typeof usage?.input_tokens === "number"
|
|
500
|
+
? usage.input_tokens
|
|
501
|
+
: null;
|
|
502
|
+
const cachedInputTokens = typeof usage?.input_tokens_details?.cached_tokens === "number"
|
|
503
|
+
? usage.input_tokens_details.cached_tokens
|
|
504
|
+
: null;
|
|
505
|
+
const outputTokens = typeof usage?.output_tokens === "number"
|
|
506
|
+
? usage.output_tokens
|
|
507
|
+
: null;
|
|
508
|
+
const reasoningTokens = typeof usage?.output_tokens_details?.reasoning_tokens === "number"
|
|
509
|
+
? usage.output_tokens_details.reasoning_tokens
|
|
510
|
+
: null;
|
|
511
|
+
const nonReasoningOutputTokens = outputTokens !== null && reasoningTokens !== null
|
|
512
|
+
? Math.max(0, outputTokens - reasoningTokens)
|
|
513
|
+
: null;
|
|
514
|
+
const partialOutput = response.output_text ?? "";
|
|
515
|
+
throw new OpenAIResponsesIncompleteError({
|
|
516
|
+
failure_code: OPENAI_RESPONSES_MAX_OUTPUT_TOKENS_FAILURE_CODE,
|
|
517
|
+
provider_status: response.status,
|
|
518
|
+
incomplete_reason: "max_output_tokens",
|
|
519
|
+
base_output_ceiling_tokens: budget.baseOutputTokens,
|
|
520
|
+
configured_output_headroom_tokens: budget.headroomTokens,
|
|
521
|
+
effective_max_output_tokens: budget.effectiveMaxOutputTokens,
|
|
522
|
+
input_tokens: inputTokens,
|
|
523
|
+
cached_input_tokens: cachedInputTokens,
|
|
524
|
+
output_tokens: outputTokens,
|
|
525
|
+
reasoning_tokens: reasoningTokens,
|
|
526
|
+
non_reasoning_output_tokens: nonReasoningOutputTokens,
|
|
527
|
+
partial_output_chars: partialOutput.length,
|
|
528
|
+
partial_output_sha256: crypto
|
|
529
|
+
.createHash("sha256")
|
|
530
|
+
.update(partialOutput, "utf8")
|
|
531
|
+
.digest("hex"),
|
|
532
|
+
provider_model: response.model ?? modelId,
|
|
533
|
+
provider_response_id: typeof response.id === "string" ? response.id : null,
|
|
534
|
+
provider_request_id: typeof response._request_id === "string"
|
|
535
|
+
? response._request_id
|
|
536
|
+
: null,
|
|
537
|
+
effective_base_url: effectiveBaseUrl,
|
|
538
|
+
sdk_max_retries: DEFAULT_MAX_RETRIES,
|
|
539
|
+
actual_adapter_request_count: null,
|
|
540
|
+
request_count_observability: "unavailable",
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
throw new Error(`openai response not completed (status=${response.status}: ${detail}) at max_output_tokens=${budget.effectiveMaxOutputTokens}`);
|
|
544
|
+
}
|
|
545
|
+
const usage = response.usage;
|
|
546
|
+
emitModelCallLog(`openai success: model_id=${response.model ?? modelId} input_tokens=${usage?.input_tokens ?? 0} output_tokens=${usage?.output_tokens ?? 0}`);
|
|
547
|
+
return {
|
|
548
|
+
text: response.output_text ?? "",
|
|
549
|
+
input_tokens: usage?.input_tokens ?? 0,
|
|
550
|
+
output_tokens: usage?.output_tokens ?? 0,
|
|
551
|
+
model_id: modelId,
|
|
552
|
+
effective_base_url: effectiveBaseUrl,
|
|
553
|
+
declared_billing_mode: "per_token",
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
// ---------------------------------------------------------------------------
|
|
308
557
|
// codex CLI call (OAuth subscription path)
|
|
309
558
|
// ---------------------------------------------------------------------------
|
|
310
559
|
/**
|
|
@@ -317,8 +566,9 @@ async function callOpenAI(systemPrompt, userPrompt, apiKey, modelId, maxTokens,
|
|
|
317
566
|
* --skip-git-repo-check lets the worker run from non-repo cwd. No -C/-s/-o:
|
|
318
567
|
* this is single-turn, no agentic scaffold.
|
|
319
568
|
*/
|
|
320
|
-
async function callCodexCli(systemPrompt, userPrompt, modelId, reasoningEffort, serviceTier) {
|
|
569
|
+
async function callCodexCli(systemPrompt, userPrompt, modelId, reasoningEffort, serviceTier, timeoutMs) {
|
|
321
570
|
const { spawn } = await import("node:child_process");
|
|
571
|
+
const workerTimeoutMs = timeoutMs ?? DEFAULT_WORKER_TIMEOUT_MS;
|
|
322
572
|
const args = ["exec", "--skip-git-repo-check", "--ephemeral"];
|
|
323
573
|
if (modelId)
|
|
324
574
|
args.push("-m", modelId);
|
|
@@ -328,7 +578,7 @@ async function callCodexCli(systemPrompt, userPrompt, modelId, reasoningEffort,
|
|
|
328
578
|
args.push("-c", `service_tier="${serviceTier}"`);
|
|
329
579
|
args.push("-");
|
|
330
580
|
const combinedPrompt = `${systemPrompt}\n\n---\n\n${userPrompt}`;
|
|
331
|
-
emitModelCallLog(`codex call: model="${modelId ?? "(codex default)"}" effort="${reasoningEffort ?? "(unset)"}" service_tier="${serviceTier ?? "(unset)"}" timeout_ms=${
|
|
581
|
+
emitModelCallLog(`codex call: model="${modelId ?? "(codex default)"}" effort="${reasoningEffort ?? "(unset)"}" service_tier="${serviceTier ?? "(unset)"}" timeout_ms=${workerTimeoutMs}`);
|
|
332
582
|
const child = spawn("codex", args, {
|
|
333
583
|
stdio: ["pipe", "pipe", "pipe"],
|
|
334
584
|
});
|
|
@@ -350,15 +600,32 @@ async function callCodexCli(systemPrompt, userPrompt, modelId, reasoningEffort,
|
|
|
350
600
|
stderr += String(chunk);
|
|
351
601
|
appendRuntimeStreamChunkFromCurrentContextSync("stderr", chunk, codexStreamSource);
|
|
352
602
|
});
|
|
603
|
+
// Swallow stdin write errors (EPIPE): if the worker exits before reading the
|
|
604
|
+
// prompt (e.g. fast model-allowlist rejection), an unhandled 'error' on the
|
|
605
|
+
// stdin stream would crash the host process. The real failure surfaces via the
|
|
606
|
+
// exit code / stderr below.
|
|
607
|
+
child.stdin.on("error", () => { });
|
|
353
608
|
child.stdin.write(combinedPrompt);
|
|
354
609
|
child.stdin.end();
|
|
610
|
+
let sigkillHandle;
|
|
355
611
|
const timeoutHandle = setTimeout(() => {
|
|
356
612
|
timedOut = true;
|
|
357
613
|
child.kill("SIGTERM");
|
|
358
|
-
|
|
614
|
+
sigkillHandle = setTimeout(() => {
|
|
615
|
+
try {
|
|
616
|
+
child.kill("SIGKILL");
|
|
617
|
+
}
|
|
618
|
+
catch { /* already exited */ }
|
|
619
|
+
}, WORKER_SIGKILL_GRACE_MS);
|
|
620
|
+
}, workerTimeoutMs);
|
|
621
|
+
const clearTimers = () => {
|
|
622
|
+
clearTimeout(timeoutHandle);
|
|
623
|
+
if (sigkillHandle)
|
|
624
|
+
clearTimeout(sigkillHandle);
|
|
625
|
+
};
|
|
359
626
|
const exitCode = await new Promise((resolve, reject) => {
|
|
360
627
|
child.on("error", (err) => {
|
|
361
|
-
|
|
628
|
+
clearTimers();
|
|
362
629
|
if (err.code === "ENOENT") {
|
|
363
630
|
reject(new Error("codex CLI not found on PATH. Install codex to use the OAuth subscription path: https://github.com/openai/codex"));
|
|
364
631
|
}
|
|
@@ -367,13 +634,13 @@ async function callCodexCli(systemPrompt, userPrompt, modelId, reasoningEffort,
|
|
|
367
634
|
}
|
|
368
635
|
});
|
|
369
636
|
child.on("close", (code) => {
|
|
370
|
-
|
|
637
|
+
clearTimers();
|
|
371
638
|
resolve(code ?? 1);
|
|
372
639
|
});
|
|
373
640
|
});
|
|
374
641
|
if (timedOut) {
|
|
375
|
-
emitModelCallLog(`codex call FAILED: model="${modelId ?? "(codex default)"}" reason=timeout timeout_ms=${
|
|
376
|
-
throw new Error(`codex CLI call timed out after ${
|
|
642
|
+
emitModelCallLog(`codex call FAILED: model="${modelId ?? "(codex default)"}" reason=timeout timeout_ms=${workerTimeoutMs}`);
|
|
643
|
+
throw new Error(`codex CLI call timed out after ${workerTimeoutMs}ms`);
|
|
377
644
|
}
|
|
378
645
|
if (exitCode !== 0) {
|
|
379
646
|
const combined = [stderr.trim(), stdout.trim()]
|
|
@@ -418,7 +685,6 @@ async function callCodexCli(systemPrompt, userPrompt, modelId, reasoningEffort,
|
|
|
418
685
|
// ---------------------------------------------------------------------------
|
|
419
686
|
// claude CLI call (Claude Code OAuth subscription path)
|
|
420
687
|
// ---------------------------------------------------------------------------
|
|
421
|
-
const CLAUDE_BIN = process.env.ONTO_CLAUDE_BIN?.trim() || "claude";
|
|
422
688
|
/**
|
|
423
689
|
* Parse the `result` event from `claude -p --output-format json`. The CLI emits
|
|
424
690
|
* a JSON array of stream events (the final one is `type:"result"`), tolerating
|
|
@@ -478,8 +744,9 @@ function parseClaudeResultEvent(stdout) {
|
|
|
478
744
|
* is the positional arg (piped stdin is not treated as the prompt), and no
|
|
479
745
|
* project/user MCP servers are loaded so the worker has no side effects.
|
|
480
746
|
*/
|
|
481
|
-
async function callClaudeCli(systemPrompt, userPrompt, modelId, reasoningEffort) {
|
|
747
|
+
async function callClaudeCli(systemPrompt, userPrompt, modelId, reasoningEffort, timeoutMs) {
|
|
482
748
|
const { spawn } = await import("node:child_process");
|
|
749
|
+
const workerTimeoutMs = timeoutMs ?? DEFAULT_WORKER_TIMEOUT_MS;
|
|
483
750
|
const combinedPrompt = `${systemPrompt}\n\n---\n\n${userPrompt}`;
|
|
484
751
|
const args = ["-p", combinedPrompt, "--output-format", "json"];
|
|
485
752
|
if (modelId)
|
|
@@ -487,8 +754,9 @@ async function callClaudeCli(systemPrompt, userPrompt, modelId, reasoningEffort)
|
|
|
487
754
|
if (reasoningEffort)
|
|
488
755
|
args.push("--effort", reasoningEffort);
|
|
489
756
|
args.push("--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}');
|
|
490
|
-
emitModelCallLog(`claude call: model="${modelId ?? "(claude default)"}" effort="${reasoningEffort ?? "(unset)"}" timeout_ms=${
|
|
491
|
-
const
|
|
757
|
+
emitModelCallLog(`claude call: model="${modelId ?? "(claude default)"}" effort="${reasoningEffort ?? "(unset)"}" timeout_ms=${workerTimeoutMs}`);
|
|
758
|
+
const claudeBin = resolveClaudeBin();
|
|
759
|
+
const child = spawn(claudeBin, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
492
760
|
const claudeStreamSourceBase = {
|
|
493
761
|
kind: "process",
|
|
494
762
|
label: "claude-cli",
|
|
@@ -507,28 +775,40 @@ async function callClaudeCli(systemPrompt, userPrompt, modelId, reasoningEffort)
|
|
|
507
775
|
stderr += String(chunk);
|
|
508
776
|
appendRuntimeStreamChunkFromCurrentContextSync("stderr", chunk, claudeStreamSource);
|
|
509
777
|
});
|
|
778
|
+
let sigkillHandle;
|
|
510
779
|
const timeoutHandle = setTimeout(() => {
|
|
511
780
|
timedOut = true;
|
|
512
781
|
child.kill("SIGTERM");
|
|
513
|
-
|
|
782
|
+
sigkillHandle = setTimeout(() => {
|
|
783
|
+
try {
|
|
784
|
+
child.kill("SIGKILL");
|
|
785
|
+
}
|
|
786
|
+
catch { /* already exited */ }
|
|
787
|
+
}, WORKER_SIGKILL_GRACE_MS);
|
|
788
|
+
}, workerTimeoutMs);
|
|
789
|
+
const clearTimers = () => {
|
|
790
|
+
clearTimeout(timeoutHandle);
|
|
791
|
+
if (sigkillHandle)
|
|
792
|
+
clearTimeout(sigkillHandle);
|
|
793
|
+
};
|
|
514
794
|
const exitCode = await new Promise((resolve, reject) => {
|
|
515
795
|
child.on("error", (err) => {
|
|
516
|
-
|
|
796
|
+
clearTimers();
|
|
517
797
|
if (err.code === "ENOENT") {
|
|
518
|
-
reject(new Error(`Claude Code CLI not found (${
|
|
798
|
+
reject(new Error(`Claude Code CLI not found (${claudeBin}). Install and log in to claude, or set ONTO_CLAUDE_BIN, to use the Anthropic OAuth subscription path: https://docs.anthropic.com/en/docs/claude-code`));
|
|
519
799
|
}
|
|
520
800
|
else {
|
|
521
801
|
reject(err);
|
|
522
802
|
}
|
|
523
803
|
});
|
|
524
804
|
child.on("close", (code) => {
|
|
525
|
-
|
|
805
|
+
clearTimers();
|
|
526
806
|
resolve(code ?? 1);
|
|
527
807
|
});
|
|
528
808
|
});
|
|
529
809
|
if (timedOut) {
|
|
530
|
-
emitModelCallLog(`claude call FAILED: model="${modelId ?? "(claude default)"}" reason=timeout timeout_ms=${
|
|
531
|
-
throw new Error(`claude CLI call timed out after ${
|
|
810
|
+
emitModelCallLog(`claude call FAILED: model="${modelId ?? "(claude default)"}" reason=timeout timeout_ms=${workerTimeoutMs}`);
|
|
811
|
+
throw new Error(`claude CLI call timed out after ${workerTimeoutMs}ms`);
|
|
532
812
|
}
|
|
533
813
|
if (exitCode !== 0) {
|
|
534
814
|
const combined = [stderr.trim(), stdout.trim()]
|
|
@@ -585,10 +865,9 @@ async function dispatchByPlan(systemPrompt, userPrompt, config) {
|
|
|
585
865
|
const maxTokens = config.max_tokens ?? 1024;
|
|
586
866
|
if (plan.provider_identity === "codex") {
|
|
587
867
|
const modelId = config.model_id ?? plan.model_id ?? config.models_per_provider?.codex;
|
|
588
|
-
return callCodexCli(systemPrompt, userPrompt, modelId, config.reasoning_effort, config.service_tier);
|
|
868
|
+
return callCodexCli(systemPrompt, userPrompt, modelId, config.reasoning_effort, config.service_tier, config.timeout_ms);
|
|
589
869
|
}
|
|
590
870
|
if (plan.provider_identity === "anthropic") {
|
|
591
|
-
assertNoUnsupportedReasoningEffort("anthropic", config.reasoning_effort);
|
|
592
871
|
const apiKey = readEnvApiKey(config.api_key_env ? [config.api_key_env] : ["ANTHROPIC_API_KEY"]);
|
|
593
872
|
if (!apiKey) {
|
|
594
873
|
throw new Error(explicitProviderMissingCredentialError("anthropic", config.api_key_env));
|
|
@@ -596,7 +875,7 @@ async function dispatchByPlan(systemPrompt, userPrompt, config) {
|
|
|
596
875
|
const modelId = config.model_id ?? plan.model_id ?? config.models_per_provider?.anthropic;
|
|
597
876
|
if (!modelId)
|
|
598
877
|
throw missingModelError("anthropic");
|
|
599
|
-
return callAnthropic(systemPrompt, userPrompt, apiKey, modelId, maxTokens);
|
|
878
|
+
return callAnthropic(systemPrompt, userPrompt, apiKey, modelId, maxTokens, config.reasoning_effort, config.thinking_mode);
|
|
600
879
|
}
|
|
601
880
|
if (plan.provider_identity === "openai") {
|
|
602
881
|
const apiKey = readOpenAiApiKey(config.api_key_env);
|
|
@@ -606,7 +885,7 @@ async function dispatchByPlan(systemPrompt, userPrompt, config) {
|
|
|
606
885
|
const modelId = config.model_id ?? plan.model_id ?? config.models_per_provider?.openai;
|
|
607
886
|
if (!modelId)
|
|
608
887
|
throw missingModelError("openai");
|
|
609
|
-
return
|
|
888
|
+
return callOpenAIResponses(systemPrompt, userPrompt, apiKey, modelId, maxTokens, config.reasoning_effort, config.openai_responses_output_headroom_tokens, config.openai_responses_model_max_output_tokens);
|
|
610
889
|
}
|
|
611
890
|
if (plan.provider_identity === "grok") {
|
|
612
891
|
assertNoUnsupportedReasoningEffort("grok", config.reasoning_effort);
|
|
@@ -646,23 +925,26 @@ export async function callLlm(systemPrompt, userPrompt, config) {
|
|
|
646
925
|
config?.artifact_generation_realization === "semantic_mock") {
|
|
647
926
|
return callReviewMockLlm(systemPrompt, userPrompt, config);
|
|
648
927
|
}
|
|
928
|
+
if (config?.openai_responses_output_headroom_tokens !== undefined &&
|
|
929
|
+
(config.provider !== "openai" ||
|
|
930
|
+
config.execution_adapter !== "openai_sdk" ||
|
|
931
|
+
config.base_url !== undefined)) {
|
|
932
|
+
throw new Error("openai Responses output headroom reached an unsupported dispatch route.");
|
|
933
|
+
}
|
|
649
934
|
if (config?.plan) {
|
|
650
935
|
return dispatchByPlan(systemPrompt, userPrompt, config);
|
|
651
936
|
}
|
|
652
937
|
if (isLegacyCodexCliProvider(config)) {
|
|
653
|
-
return callCodexCli(systemPrompt, userPrompt, config.model_id ?? config.models_per_provider?.codex, config.reasoning_effort, config.service_tier);
|
|
938
|
+
return callCodexCli(systemPrompt, userPrompt, config.model_id ?? config.models_per_provider?.codex, config.reasoning_effort, config.service_tier, config.timeout_ms);
|
|
654
939
|
}
|
|
655
940
|
// Anthropic OAuth → Claude Code CLI worker. The model-switcher keeps
|
|
656
941
|
// provider="anthropic" for OAuth (the brand lives in execution_adapter), so
|
|
657
942
|
// dispatch on the adapter here, before the SDK/api-key path. This is the
|
|
658
|
-
// direct-call analog of openai OAuth → codex.
|
|
659
|
-
//
|
|
943
|
+
// direct-call analog of openai OAuth → codex. Both this CLI route (--effort)
|
|
944
|
+
// and the api_key SDK route below (output_config.effort) honor effort.
|
|
660
945
|
if (config?.provider === "anthropic" &&
|
|
661
946
|
config?.execution_adapter === "claude_code") {
|
|
662
|
-
return callClaudeCli(systemPrompt, userPrompt, config.model_id ?? config.models_per_provider?.anthropic, config.reasoning_effort);
|
|
663
|
-
}
|
|
664
|
-
if (config?.provider === "anthropic") {
|
|
665
|
-
assertNoUnsupportedReasoningEffort("anthropic", config.reasoning_effort);
|
|
947
|
+
return callClaudeCli(systemPrompt, userPrompt, config.model_id ?? config.models_per_provider?.anthropic, config.reasoning_effort, config.timeout_ms);
|
|
666
948
|
}
|
|
667
949
|
if (config?.provider === "grok") {
|
|
668
950
|
assertNoUnsupportedReasoningEffort("grok", config.reasoning_effort);
|
|
@@ -690,20 +972,19 @@ export async function callLlm(systemPrompt, userPrompt, config) {
|
|
|
690
972
|
switch (resolved.provider) {
|
|
691
973
|
case "codex": {
|
|
692
974
|
const modelId = config?.model_id ?? perProviderModel;
|
|
693
|
-
return callCodexCli(systemPrompt, userPrompt, modelId, config?.reasoning_effort, config?.service_tier);
|
|
975
|
+
return callCodexCli(systemPrompt, userPrompt, modelId, config?.reasoning_effort, config?.service_tier, config?.timeout_ms);
|
|
694
976
|
}
|
|
695
977
|
case "anthropic": {
|
|
696
|
-
assertNoUnsupportedReasoningEffort("anthropic", config?.reasoning_effort);
|
|
697
978
|
const modelId = config?.model_id ?? perProviderModel;
|
|
698
979
|
if (!modelId)
|
|
699
980
|
throw missingModelError("anthropic");
|
|
700
|
-
return callAnthropic(systemPrompt, userPrompt, resolved.apiKey, modelId, maxTokens);
|
|
981
|
+
return callAnthropic(systemPrompt, userPrompt, resolved.apiKey, modelId, maxTokens, config?.reasoning_effort, config?.thinking_mode);
|
|
701
982
|
}
|
|
702
983
|
case "openai": {
|
|
703
984
|
const modelId = config?.model_id ?? perProviderModel;
|
|
704
985
|
if (!modelId)
|
|
705
986
|
throw missingModelError("openai");
|
|
706
|
-
return
|
|
987
|
+
return callOpenAIResponses(systemPrompt, userPrompt, resolved.apiKey, modelId, maxTokens, config?.reasoning_effort, config?.openai_responses_output_headroom_tokens, config?.openai_responses_model_max_output_tokens);
|
|
707
988
|
}
|
|
708
989
|
case "grok": {
|
|
709
990
|
assertNoUnsupportedReasoningEffort("grok", config?.reasoning_effort);
|
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import YAML from "yaml";
|
|
4
4
|
import { parseRuntimeIssueDeliberationSchemaContext, parseRuntimeIssueStanceSchemaContext, parseRuntimeIssueSynthesisSchemaContext, parseRuntimeProblemFramingContext, } from "../cli/runtime-submit-context.js";
|
|
5
|
+
import { packetHasResubmitErrorSpec } from "../cli/unit-resubmit.js";
|
|
5
6
|
import { parseParticipatingLensPaths } from "../review/participating-lens-paths.js";
|
|
6
7
|
export const REVIEW_MOCK_REALIZATION_ENV = "ONTO_LLM_MOCK";
|
|
7
8
|
export function isReviewMockLlmRealizationEnabled(env = process.env) {
|
|
@@ -432,16 +433,46 @@ function issueArtifactArgs(unitId, packetText, toolCtx) {
|
|
|
432
433
|
throw new Error(`Unsupported mock issue artifact unit: ${unitId}`);
|
|
433
434
|
}
|
|
434
435
|
}
|
|
435
|
-
|
|
436
|
+
/**
|
|
437
|
+
* 설계 A 픽스처 스위치 (F-A1/F-A2): 지정 렌즈의 stance 유닛이 허용 집합 밖
|
|
438
|
+
* evidence_ref를 제출해 실제 `issue_evidence_refs` 화이트리스트 검증을
|
|
439
|
+
* 거부당하게 한다.
|
|
440
|
+
* - `ONTO_LLM_MOCK_STANCE_UNSUPPORTED_REF_LENSES`: csv 렌즈 id 또는 `*`.
|
|
441
|
+
* - `ONTO_LLM_MOCK_STANCE_UNSUPPORTED_REF_MODE`: `persist`(기본, cap 소진
|
|
442
|
+
* 경로) | `correct_on_resubmit`(packet에 resubmit 오류 명세가 주입되면
|
|
443
|
+
* 유효 ref로 치유 — 오류 명세 전달의 E2E 증거).
|
|
444
|
+
*/
|
|
445
|
+
function stanceUnsupportedRefTargets(env = process.env) {
|
|
446
|
+
const raw = env.ONTO_LLM_MOCK_STANCE_UNSUPPORTED_REF_LENSES;
|
|
447
|
+
if (!raw)
|
|
448
|
+
return null;
|
|
449
|
+
if (raw.trim() === "*")
|
|
450
|
+
return "*";
|
|
451
|
+
const lenses = raw
|
|
452
|
+
.split(",")
|
|
453
|
+
.map((value) => value.trim())
|
|
454
|
+
.filter((value) => value.length > 0);
|
|
455
|
+
return lenses.length > 0 ? new Set(lenses) : null;
|
|
456
|
+
}
|
|
457
|
+
function issueStanceArgs(unitId, packetText) {
|
|
436
458
|
const context = parseRuntimeIssueStanceSchemaContext(packetText);
|
|
459
|
+
const lensId = unitId.startsWith("issue-stance:")
|
|
460
|
+
? unitId.slice("issue-stance:".length)
|
|
461
|
+
: unitId;
|
|
462
|
+
const targets = stanceUnsupportedRefTargets();
|
|
463
|
+
const healed = process.env.ONTO_LLM_MOCK_STANCE_UNSUPPORTED_REF_MODE ===
|
|
464
|
+
"correct_on_resubmit" && packetHasResubmitErrorSpec(packetText);
|
|
465
|
+
const emitUnsupportedRef = targets !== null && (targets === "*" || targets.has(lensId)) && !healed;
|
|
437
466
|
return {
|
|
438
|
-
stances: Object.entries(context.issue_evidence_refs).map(([issueId, refs]) => ({
|
|
467
|
+
stances: Object.entries(context.issue_evidence_refs).map(([issueId, refs], index) => ({
|
|
439
468
|
issue_id: issueId,
|
|
440
469
|
stance: "support",
|
|
441
470
|
rationale: "Mock realization supports the low-severity issue framing.",
|
|
442
471
|
root_hypothesis_position: "accepts",
|
|
443
472
|
severity_position: "keeps",
|
|
444
|
-
evidence_refs:
|
|
473
|
+
evidence_refs: emitUnsupportedRef && index === 0
|
|
474
|
+
? [`mock-unsupported-ref:${lensId}`]
|
|
475
|
+
: refs.slice(0, 1),
|
|
445
476
|
})),
|
|
446
477
|
};
|
|
447
478
|
}
|
|
@@ -502,7 +533,7 @@ async function executeSubmitTool(args) {
|
|
|
502
533
|
await args.tool.execute(issueArtifactArgs(unitId, args.userPrompt, args.toolCtx), args.toolCtx);
|
|
503
534
|
return;
|
|
504
535
|
case "submit_issue_stance_response":
|
|
505
|
-
await args.tool.execute(issueStanceArgs(args.userPrompt), args.toolCtx);
|
|
536
|
+
await args.tool.execute(issueStanceArgs(unitId, args.userPrompt), args.toolCtx);
|
|
506
537
|
return;
|
|
507
538
|
case "submit_issue_deliberation_response":
|
|
508
539
|
await args.tool.execute(issueDeliberationArgs(args.userPrompt), args.toolCtx);
|
|
@@ -31,7 +31,7 @@ export function normalizeLlmModelSwitcher(config) {
|
|
|
31
31
|
throw new Error("auth=local currently requires provider=lmstudio.");
|
|
32
32
|
}
|
|
33
33
|
if (config.service_tier && !(provider === "openai" && auth === "oauth")) {
|
|
34
|
-
throw new Error(
|
|
34
|
+
throw new Error(`service_tier is only valid on the openai + auth=oauth (Codex) route; remove service_tier for provider=${provider} + auth=${auth}.`);
|
|
35
35
|
}
|
|
36
36
|
const model_id = config.model;
|
|
37
37
|
const common = {
|