onto-mcp 0.4.12 → 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 +137 -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 +169 -8
- 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/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 +575 -24
- 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 +75 -3
- package/dist/core-runtime/cli/claude-code-review-unit-executor.js +19 -8
- 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 +282 -29
- 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 +135 -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 +188 -47
- 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 +35 -0
- package/dist/core-runtime/reconstruct/claim-projection-validation.js +17 -0
- 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/dispatch-fallback-artifacts.js +640 -0
- package/dist/core-runtime/reconstruct/execution-telemetry.js +83 -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 +18 -0
- package/dist/core-runtime/reconstruct/material-profile-validation.js +14 -0
- package/dist/core-runtime/reconstruct/materialize-preparation.js +304 -18
- package/dist/core-runtime/reconstruct/maturation-validation.js +1231 -53
- package/dist/core-runtime/reconstruct/mock-llm-realization.js +107 -3
- package/dist/core-runtime/reconstruct/obligation-assertion.js +18 -0
- package/dist/core-runtime/reconstruct/ontology-seed-validation.js +27 -0
- package/dist/core-runtime/reconstruct/output-budget.js +6 -0
- package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +87 -3
- package/dist/core-runtime/reconstruct/post-seed-validation.js +118 -5
- package/dist/core-runtime/reconstruct/purpose-authority-validation.js +117 -2
- package/dist/core-runtime/reconstruct/record.js +42 -0
- package/dist/core-runtime/reconstruct/registry-verification-validation.js +16 -0
- package/dist/core-runtime/reconstruct/run-control-validation.js +1011 -34
- package/dist/core-runtime/reconstruct/run.js +5198 -536
- package/dist/core-runtime/reconstruct/seed-authoring-readiness-validation.js +54 -3
- package/dist/core-runtime/reconstruct/semantic-quality-gate.js +105 -2
- package/dist/core-runtime/reconstruct/source-observation-delta-validation.js +41 -0
- package/dist/core-runtime/reconstruct/source-observations.js +166 -0
- package/dist/core-runtime/reconstruct/source-safety-validation.js +71 -183
- package/dist/core-runtime/reconstruct/source-scout-pack-validation.js +37 -1
- package/dist/core-runtime/reconstruct/terminal-validation.js +132 -2
- 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 +217 -5
- 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 +12 -5
- 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 +18 -3
- package/settings.example.json +77 -16
|
@@ -20,6 +20,7 @@ import { DEFAULT_GROK_BASE_URL, DEFAULT_LMSTUDIO_BASE_URL, isExternalOauthWorker
|
|
|
20
20
|
import { resolveClaudeBin } from "./claude-bin.js";
|
|
21
21
|
import { appendRuntimeModelCallLogFromCurrentContext, appendRuntimeStreamChunkFromCurrentContextSync, } from "../observability/runtime-stream-observation.js";
|
|
22
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";
|
|
23
24
|
/**
|
|
24
25
|
* Bridge: OntoConfig + CLI overrides → Partial<LlmCallConfig> that callLlm consumes.
|
|
25
26
|
*
|
|
@@ -67,11 +68,39 @@ export function resolveLlmProviderConfig(args) {
|
|
|
67
68
|
out.service_tier = service_tier;
|
|
68
69
|
if (api_key_env)
|
|
69
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;
|
|
70
74
|
if (Object.keys(models_per_provider).length > 0) {
|
|
71
75
|
out.models_per_provider = models_per_provider;
|
|
72
76
|
}
|
|
73
77
|
return out;
|
|
74
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
|
+
}
|
|
75
104
|
// Phase 3 production found 30s too tight for large audit batches (37 items
|
|
76
105
|
// could time out then SDK-retry for 90s total). 120s is generous
|
|
77
106
|
// enough for ~50-item single-batch audits while still failing fast on real
|
|
@@ -94,6 +123,20 @@ const WORKER_SIGKILL_GRACE_MS = 5_000;
|
|
|
94
123
|
// faster (1 retry instead of the default 2) so operators see provider errors
|
|
95
124
|
// within ~2× timeout instead of ~3×.
|
|
96
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
|
+
}
|
|
97
140
|
/**
|
|
98
141
|
* Model-call observability — emits STDERR logs for each LLM API call, covering
|
|
99
142
|
* (a) pre-call model_id + provider + max_tokens, (b) post-call usage on success,
|
|
@@ -237,35 +280,47 @@ function assertNoUnsupportedReasoningEffort(provider, reasoningEffort) {
|
|
|
237
280
|
// ---------------------------------------------------------------------------
|
|
238
281
|
// Anthropic call
|
|
239
282
|
// ---------------------------------------------------------------------------
|
|
240
|
-
async function callAnthropic(systemPrompt, userPrompt, apiKey, modelId, maxTokens, reasoningEffort) {
|
|
283
|
+
async function callAnthropic(systemPrompt, userPrompt, apiKey, modelId, maxTokens, reasoningEffort, thinkingMode) {
|
|
241
284
|
const { default: Anthropic } = await import("@anthropic-ai/sdk");
|
|
242
285
|
const client = new Anthropic({
|
|
243
286
|
apiKey,
|
|
244
287
|
timeout: DEFAULT_TIMEOUT_MS,
|
|
245
288
|
maxRetries: DEFAULT_MAX_RETRIES,
|
|
246
289
|
});
|
|
247
|
-
emitModelCallLog(`anthropic call: model="${modelId}" max_tokens=${maxTokens} effort=${reasoningEffort ?? "(unset)"}`);
|
|
290
|
+
emitModelCallLog(`anthropic call: model="${modelId}" max_tokens=${maxTokens} effort=${reasoningEffort ?? "(unset)"} thinking=${thinkingMode ?? "(default)"}`);
|
|
248
291
|
let response;
|
|
249
292
|
try {
|
|
250
293
|
response = await client.messages.create({
|
|
251
294
|
model: modelId,
|
|
252
295
|
max_tokens: maxTokens,
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
// thinking
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
+
: {}),
|
|
269
324
|
system: systemPrompt,
|
|
270
325
|
messages: [{ role: "user", content: userPrompt }],
|
|
271
326
|
});
|
|
@@ -284,11 +339,19 @@ async function callAnthropic(systemPrompt, userPrompt, apiKey, modelId, maxToken
|
|
|
284
339
|
throw err;
|
|
285
340
|
}
|
|
286
341
|
emitModelCallLog(`anthropic success: model_id=${response.model ?? modelId} input_tokens=${response.usage.input_tokens} output_tokens=${response.usage.output_tokens}`);
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
|
|
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");
|
|
292
355
|
}
|
|
293
356
|
const text = response.content
|
|
294
357
|
.filter((block) => block.type === "text")
|
|
@@ -364,21 +427,44 @@ async function callOpenAI(systemPrompt, userPrompt, apiKey, modelId, maxTokens,
|
|
|
364
427
|
* models). grok/lmstudio stay on Chat Completions via `callOpenAI` — they are
|
|
365
428
|
* OpenAI-compatible Chat Completions endpoints with no Responses API.
|
|
366
429
|
*/
|
|
367
|
-
async function callOpenAIResponses(systemPrompt, userPrompt, apiKey, modelId,
|
|
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
|
+
});
|
|
368
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
|
+
};
|
|
369
450
|
const client = new OpenAI({
|
|
370
451
|
apiKey,
|
|
452
|
+
...(outputHeadroomTokens !== undefined
|
|
453
|
+
? { baseURL: OPENAI_RESPONSES_BASE_URL }
|
|
454
|
+
: {}),
|
|
371
455
|
timeout: DEFAULT_TIMEOUT_MS,
|
|
372
456
|
maxRetries: DEFAULT_MAX_RETRIES,
|
|
457
|
+
logLevel: "off",
|
|
458
|
+
logger: noOpLogger,
|
|
373
459
|
});
|
|
374
|
-
emitModelCallLog(`openai call: model="${modelId}" max_output_tokens=${
|
|
460
|
+
emitModelCallLog(`openai call: model="${modelId}" max_output_tokens=${budget.effectiveMaxOutputTokens} effort=${reasoningEffort ?? "(unset)"}`);
|
|
375
461
|
let response;
|
|
376
462
|
try {
|
|
377
463
|
response = await client.responses.create({
|
|
378
464
|
model: modelId,
|
|
379
465
|
instructions: systemPrompt,
|
|
380
466
|
input: userPrompt,
|
|
381
|
-
max_output_tokens:
|
|
467
|
+
max_output_tokens: budget.effectiveMaxOutputTokens,
|
|
382
468
|
// Transient single-turn authoring/review calls; the Responses API stores
|
|
383
469
|
// response objects provider-side by default (retrievable for ~30d). Opt
|
|
384
470
|
// out so these prompts/outputs are not persisted — matches the
|
|
@@ -406,8 +492,55 @@ async function callOpenAIResponses(systemPrompt, userPrompt, apiKey, modelId, ma
|
|
|
406
492
|
// artifacts. Fail loud so the caller retries or surfaces it.
|
|
407
493
|
if (response.status !== "completed") {
|
|
408
494
|
const detail = response.incomplete_details?.reason ?? response.error?.message ?? "no detail";
|
|
409
|
-
emitModelCallLog(`openai call NOT COMPLETED: model="${modelId}" status=${response.status} detail=${detail} max_output_tokens=${
|
|
410
|
-
|
|
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}`);
|
|
411
544
|
}
|
|
412
545
|
const usage = response.usage;
|
|
413
546
|
emitModelCallLog(`openai success: model_id=${response.model ?? modelId} input_tokens=${usage?.input_tokens ?? 0} output_tokens=${usage?.output_tokens ?? 0}`);
|
|
@@ -416,7 +549,7 @@ async function callOpenAIResponses(systemPrompt, userPrompt, apiKey, modelId, ma
|
|
|
416
549
|
input_tokens: usage?.input_tokens ?? 0,
|
|
417
550
|
output_tokens: usage?.output_tokens ?? 0,
|
|
418
551
|
model_id: modelId,
|
|
419
|
-
effective_base_url:
|
|
552
|
+
effective_base_url: effectiveBaseUrl,
|
|
420
553
|
declared_billing_mode: "per_token",
|
|
421
554
|
};
|
|
422
555
|
}
|
|
@@ -433,8 +566,9 @@ async function callOpenAIResponses(systemPrompt, userPrompt, apiKey, modelId, ma
|
|
|
433
566
|
* --skip-git-repo-check lets the worker run from non-repo cwd. No -C/-s/-o:
|
|
434
567
|
* this is single-turn, no agentic scaffold.
|
|
435
568
|
*/
|
|
436
|
-
async function callCodexCli(systemPrompt, userPrompt, modelId, reasoningEffort, serviceTier) {
|
|
569
|
+
async function callCodexCli(systemPrompt, userPrompt, modelId, reasoningEffort, serviceTier, timeoutMs) {
|
|
437
570
|
const { spawn } = await import("node:child_process");
|
|
571
|
+
const workerTimeoutMs = timeoutMs ?? DEFAULT_WORKER_TIMEOUT_MS;
|
|
438
572
|
const args = ["exec", "--skip-git-repo-check", "--ephemeral"];
|
|
439
573
|
if (modelId)
|
|
440
574
|
args.push("-m", modelId);
|
|
@@ -444,7 +578,7 @@ async function callCodexCli(systemPrompt, userPrompt, modelId, reasoningEffort,
|
|
|
444
578
|
args.push("-c", `service_tier="${serviceTier}"`);
|
|
445
579
|
args.push("-");
|
|
446
580
|
const combinedPrompt = `${systemPrompt}\n\n---\n\n${userPrompt}`;
|
|
447
|
-
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}`);
|
|
448
582
|
const child = spawn("codex", args, {
|
|
449
583
|
stdio: ["pipe", "pipe", "pipe"],
|
|
450
584
|
});
|
|
@@ -483,7 +617,7 @@ async function callCodexCli(systemPrompt, userPrompt, modelId, reasoningEffort,
|
|
|
483
617
|
}
|
|
484
618
|
catch { /* already exited */ }
|
|
485
619
|
}, WORKER_SIGKILL_GRACE_MS);
|
|
486
|
-
},
|
|
620
|
+
}, workerTimeoutMs);
|
|
487
621
|
const clearTimers = () => {
|
|
488
622
|
clearTimeout(timeoutHandle);
|
|
489
623
|
if (sigkillHandle)
|
|
@@ -505,8 +639,8 @@ async function callCodexCli(systemPrompt, userPrompt, modelId, reasoningEffort,
|
|
|
505
639
|
});
|
|
506
640
|
});
|
|
507
641
|
if (timedOut) {
|
|
508
|
-
emitModelCallLog(`codex call FAILED: model="${modelId ?? "(codex default)"}" reason=timeout timeout_ms=${
|
|
509
|
-
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`);
|
|
510
644
|
}
|
|
511
645
|
if (exitCode !== 0) {
|
|
512
646
|
const combined = [stderr.trim(), stdout.trim()]
|
|
@@ -610,8 +744,9 @@ function parseClaudeResultEvent(stdout) {
|
|
|
610
744
|
* is the positional arg (piped stdin is not treated as the prompt), and no
|
|
611
745
|
* project/user MCP servers are loaded so the worker has no side effects.
|
|
612
746
|
*/
|
|
613
|
-
async function callClaudeCli(systemPrompt, userPrompt, modelId, reasoningEffort) {
|
|
747
|
+
async function callClaudeCli(systemPrompt, userPrompt, modelId, reasoningEffort, timeoutMs) {
|
|
614
748
|
const { spawn } = await import("node:child_process");
|
|
749
|
+
const workerTimeoutMs = timeoutMs ?? DEFAULT_WORKER_TIMEOUT_MS;
|
|
615
750
|
const combinedPrompt = `${systemPrompt}\n\n---\n\n${userPrompt}`;
|
|
616
751
|
const args = ["-p", combinedPrompt, "--output-format", "json"];
|
|
617
752
|
if (modelId)
|
|
@@ -619,7 +754,7 @@ async function callClaudeCli(systemPrompt, userPrompt, modelId, reasoningEffort)
|
|
|
619
754
|
if (reasoningEffort)
|
|
620
755
|
args.push("--effort", reasoningEffort);
|
|
621
756
|
args.push("--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}');
|
|
622
|
-
emitModelCallLog(`claude call: model="${modelId ?? "(claude default)"}" effort="${reasoningEffort ?? "(unset)"}" timeout_ms=${
|
|
757
|
+
emitModelCallLog(`claude call: model="${modelId ?? "(claude default)"}" effort="${reasoningEffort ?? "(unset)"}" timeout_ms=${workerTimeoutMs}`);
|
|
623
758
|
const claudeBin = resolveClaudeBin();
|
|
624
759
|
const child = spawn(claudeBin, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
625
760
|
const claudeStreamSourceBase = {
|
|
@@ -650,7 +785,7 @@ async function callClaudeCli(systemPrompt, userPrompt, modelId, reasoningEffort)
|
|
|
650
785
|
}
|
|
651
786
|
catch { /* already exited */ }
|
|
652
787
|
}, WORKER_SIGKILL_GRACE_MS);
|
|
653
|
-
},
|
|
788
|
+
}, workerTimeoutMs);
|
|
654
789
|
const clearTimers = () => {
|
|
655
790
|
clearTimeout(timeoutHandle);
|
|
656
791
|
if (sigkillHandle)
|
|
@@ -672,8 +807,8 @@ async function callClaudeCli(systemPrompt, userPrompt, modelId, reasoningEffort)
|
|
|
672
807
|
});
|
|
673
808
|
});
|
|
674
809
|
if (timedOut) {
|
|
675
|
-
emitModelCallLog(`claude call FAILED: model="${modelId ?? "(claude default)"}" reason=timeout timeout_ms=${
|
|
676
|
-
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`);
|
|
677
812
|
}
|
|
678
813
|
if (exitCode !== 0) {
|
|
679
814
|
const combined = [stderr.trim(), stdout.trim()]
|
|
@@ -730,7 +865,7 @@ async function dispatchByPlan(systemPrompt, userPrompt, config) {
|
|
|
730
865
|
const maxTokens = config.max_tokens ?? 1024;
|
|
731
866
|
if (plan.provider_identity === "codex") {
|
|
732
867
|
const modelId = config.model_id ?? plan.model_id ?? config.models_per_provider?.codex;
|
|
733
|
-
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);
|
|
734
869
|
}
|
|
735
870
|
if (plan.provider_identity === "anthropic") {
|
|
736
871
|
const apiKey = readEnvApiKey(config.api_key_env ? [config.api_key_env] : ["ANTHROPIC_API_KEY"]);
|
|
@@ -740,7 +875,7 @@ async function dispatchByPlan(systemPrompt, userPrompt, config) {
|
|
|
740
875
|
const modelId = config.model_id ?? plan.model_id ?? config.models_per_provider?.anthropic;
|
|
741
876
|
if (!modelId)
|
|
742
877
|
throw missingModelError("anthropic");
|
|
743
|
-
return callAnthropic(systemPrompt, userPrompt, apiKey, modelId, maxTokens, config.reasoning_effort);
|
|
878
|
+
return callAnthropic(systemPrompt, userPrompt, apiKey, modelId, maxTokens, config.reasoning_effort, config.thinking_mode);
|
|
744
879
|
}
|
|
745
880
|
if (plan.provider_identity === "openai") {
|
|
746
881
|
const apiKey = readOpenAiApiKey(config.api_key_env);
|
|
@@ -750,7 +885,7 @@ async function dispatchByPlan(systemPrompt, userPrompt, config) {
|
|
|
750
885
|
const modelId = config.model_id ?? plan.model_id ?? config.models_per_provider?.openai;
|
|
751
886
|
if (!modelId)
|
|
752
887
|
throw missingModelError("openai");
|
|
753
|
-
return callOpenAIResponses(systemPrompt, userPrompt, apiKey, modelId, maxTokens, config.reasoning_effort);
|
|
888
|
+
return callOpenAIResponses(systemPrompt, userPrompt, apiKey, modelId, maxTokens, config.reasoning_effort, config.openai_responses_output_headroom_tokens, config.openai_responses_model_max_output_tokens);
|
|
754
889
|
}
|
|
755
890
|
if (plan.provider_identity === "grok") {
|
|
756
891
|
assertNoUnsupportedReasoningEffort("grok", config.reasoning_effort);
|
|
@@ -790,11 +925,17 @@ export async function callLlm(systemPrompt, userPrompt, config) {
|
|
|
790
925
|
config?.artifact_generation_realization === "semantic_mock") {
|
|
791
926
|
return callReviewMockLlm(systemPrompt, userPrompt, config);
|
|
792
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
|
+
}
|
|
793
934
|
if (config?.plan) {
|
|
794
935
|
return dispatchByPlan(systemPrompt, userPrompt, config);
|
|
795
936
|
}
|
|
796
937
|
if (isLegacyCodexCliProvider(config)) {
|
|
797
|
-
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);
|
|
798
939
|
}
|
|
799
940
|
// Anthropic OAuth → Claude Code CLI worker. The model-switcher keeps
|
|
800
941
|
// provider="anthropic" for OAuth (the brand lives in execution_adapter), so
|
|
@@ -803,7 +944,7 @@ export async function callLlm(systemPrompt, userPrompt, config) {
|
|
|
803
944
|
// and the api_key SDK route below (output_config.effort) honor effort.
|
|
804
945
|
if (config?.provider === "anthropic" &&
|
|
805
946
|
config?.execution_adapter === "claude_code") {
|
|
806
|
-
return callClaudeCli(systemPrompt, userPrompt, config.model_id ?? config.models_per_provider?.anthropic, config.reasoning_effort);
|
|
947
|
+
return callClaudeCli(systemPrompt, userPrompt, config.model_id ?? config.models_per_provider?.anthropic, config.reasoning_effort, config.timeout_ms);
|
|
807
948
|
}
|
|
808
949
|
if (config?.provider === "grok") {
|
|
809
950
|
assertNoUnsupportedReasoningEffort("grok", config.reasoning_effort);
|
|
@@ -831,19 +972,19 @@ export async function callLlm(systemPrompt, userPrompt, config) {
|
|
|
831
972
|
switch (resolved.provider) {
|
|
832
973
|
case "codex": {
|
|
833
974
|
const modelId = config?.model_id ?? perProviderModel;
|
|
834
|
-
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);
|
|
835
976
|
}
|
|
836
977
|
case "anthropic": {
|
|
837
978
|
const modelId = config?.model_id ?? perProviderModel;
|
|
838
979
|
if (!modelId)
|
|
839
980
|
throw missingModelError("anthropic");
|
|
840
|
-
return callAnthropic(systemPrompt, userPrompt, resolved.apiKey, modelId, maxTokens, config?.reasoning_effort);
|
|
981
|
+
return callAnthropic(systemPrompt, userPrompt, resolved.apiKey, modelId, maxTokens, config?.reasoning_effort, config?.thinking_mode);
|
|
841
982
|
}
|
|
842
983
|
case "openai": {
|
|
843
984
|
const modelId = config?.model_id ?? perProviderModel;
|
|
844
985
|
if (!modelId)
|
|
845
986
|
throw missingModelError("openai");
|
|
846
|
-
return callOpenAIResponses(systemPrompt, userPrompt, resolved.apiKey, modelId, maxTokens, config?.reasoning_effort);
|
|
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);
|
|
847
988
|
}
|
|
848
989
|
case "grok": {
|
|
849
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 = {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const OPENAI_RESPONSES_MAX_OUTPUT_TOKENS_FAILURE_CODE = "openai_responses_max_output_tokens";
|
|
2
|
+
export class OpenAIResponsesIncompleteError extends Error {
|
|
3
|
+
evidence;
|
|
4
|
+
constructor(evidence) {
|
|
5
|
+
super(`openai response not completed (status=${evidence.provider_status}: ` +
|
|
6
|
+
`${evidence.incomplete_reason}) at max_output_tokens=` +
|
|
7
|
+
`${evidence.effective_max_output_tokens}`);
|
|
8
|
+
this.name = "OpenAIResponsesIncompleteError";
|
|
9
|
+
this.evidence = evidence;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function readOpenAIResponsesIncompleteEvidence(error) {
|
|
13
|
+
return error instanceof OpenAIResponsesIncompleteError
|
|
14
|
+
? error.evidence
|
|
15
|
+
: null;
|
|
16
|
+
}
|
|
17
|
+
export function resolveOpenAIResponsesOutputBudget(args) {
|
|
18
|
+
if (args.headroomTokens === undefined) {
|
|
19
|
+
return {
|
|
20
|
+
baseOutputTokens: args.baseOutputTokens,
|
|
21
|
+
headroomTokens: 0,
|
|
22
|
+
effectiveMaxOutputTokens: args.baseOutputTokens,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (!Number.isSafeInteger(args.headroomTokens) || args.headroomTokens <= 0) {
|
|
26
|
+
throw new Error("openai Responses output headroom must be a positive safe integer.");
|
|
27
|
+
}
|
|
28
|
+
if (args.modelMaxOutputTokens === undefined ||
|
|
29
|
+
!Number.isSafeInteger(args.modelMaxOutputTokens) ||
|
|
30
|
+
args.modelMaxOutputTokens <= 0) {
|
|
31
|
+
throw new Error("openai Responses output headroom requires a positive model max_output_tokens capability.");
|
|
32
|
+
}
|
|
33
|
+
const effective = args.baseOutputTokens + args.headroomTokens;
|
|
34
|
+
if (!Number.isSafeInteger(effective)) {
|
|
35
|
+
throw new Error("openai Responses effective max_output_tokens is not a safe integer.");
|
|
36
|
+
}
|
|
37
|
+
if (effective > args.modelMaxOutputTokens) {
|
|
38
|
+
throw new Error(`openai Responses effective max_output_tokens=${effective} exceeds ` +
|
|
39
|
+
`model max_output_tokens=${args.modelMaxOutputTokens}.`);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
baseOutputTokens: args.baseOutputTokens,
|
|
43
|
+
headroomTokens: args.headroomTokens,
|
|
44
|
+
effectiveMaxOutputTokens: effective,
|
|
45
|
+
};
|
|
46
|
+
}
|