oh-my-knowledge 0.48.0 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -18
- package/README.zh.md +55 -23
- package/dist/analysis/coverage-analyzer.d.ts +1 -0
- package/dist/analysis/coverage-analyzer.js +125 -62
- package/dist/analysis/failure-clusterer.js +2 -1
- package/dist/analysis/gap-analyzer.d.ts +2 -2
- package/dist/analysis/gap-analyzer.js +13 -3
- package/dist/analysis/hedging-classifier.d.ts +2 -2
- package/dist/analysis/hedging-classifier.js +3 -4
- package/dist/analysis/report-diagnostics.js +9 -7
- package/dist/analysis/sample-diagnostics.js +6 -6
- package/dist/artifact-graph/doctor.js +15 -7
- package/dist/assets/agent-skills/omk/SKILL.md +27 -7
- package/dist/assets/agent-skills/omk/references/commands.md +18 -17
- package/dist/authoring/evolver.d.ts +10 -6
- package/dist/authoring/evolver.js +496 -83
- package/dist/authoring/generator.d.ts +3 -3
- package/dist/authoring/generator.js +5 -10
- package/dist/authoring/sample-fixer.d.ts +8 -6
- package/dist/authoring/sample-fixer.js +76 -5
- package/dist/cli/commands/doctor.js +31 -14
- package/dist/cli/commands/eval/index.d.ts +3 -0
- package/dist/cli/commands/eval/index.js +163 -17
- package/dist/cli/commands/evolve.d.ts +4 -4
- package/dist/cli/commands/evolve.js +27 -13
- package/dist/cli/commands/init.js +16 -3
- package/dist/cli/commands/observe/inbox.js +28 -21
- package/dist/cli/commands/observe/index.js +20 -11
- package/dist/cli/commands/observe/ingest.d.ts +3 -0
- package/dist/cli/commands/observe/ingest.js +30 -2
- package/dist/cli/commands/sample.d.ts +6 -3
- package/dist/cli/commands/sample.js +72 -68
- package/dist/cli/lib/codex-model-hint.d.ts +9 -0
- package/dist/cli/lib/codex-model-hint.js +45 -0
- package/dist/cli/lib/generation-failure-hint.d.ts +2 -0
- package/dist/cli/lib/generation-failure-hint.js +61 -0
- package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/common.js +4 -0
- package/dist/cli/lib/i18n-dict/gen.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/gen.js +38 -6
- package/dist/cli/lib/i18n-dict/help.js +6 -6
- package/dist/cli/lib/i18n-dict/init.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/init.js +13 -9
- package/dist/cli/lib/i18n-dict/run.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/run.js +34 -2
- package/dist/cli/lib/llm-failure-classifier.d.ts +2 -0
- package/dist/cli/lib/llm-failure-classifier.js +8 -0
- package/dist/cli/lib/parse-run-config.d.ts +6 -5
- package/dist/cli/lib/parse-run-config.js +16 -9
- package/dist/cli/lib/runtime-defaults.d.ts +21 -0
- package/dist/cli/lib/runtime-defaults.js +79 -0
- package/dist/diagnosis/observe-mapper.js +14 -15
- package/dist/diagnosis/observe-producer.js +3 -1
- package/dist/diagnosis/studio-projection.js +14 -7
- package/dist/diagnosis/types.d.ts +2 -0
- package/dist/diagnosis/types.js +12 -0
- package/dist/doctor/endpoint-rule.js +2 -1
- package/dist/eval-core/artifact-file-names.js +18 -1
- package/dist/eval-core/artifact-index.d.ts +7 -11
- package/dist/eval-core/artifact-index.js +139 -80
- package/dist/eval-core/cache.d.ts +12 -3
- package/dist/eval-core/cache.js +89 -29
- package/dist/eval-core/comparability.js +10 -6
- package/dist/eval-core/evaluation-execution.d.ts +2 -1
- package/dist/eval-core/evaluation-execution.js +122 -37
- package/dist/eval-core/evaluation-job.d.ts +4 -1
- package/dist/eval-core/evaluation-job.js +4 -1
- package/dist/eval-core/evaluation-reporting.d.ts +15 -13
- package/dist/eval-core/evaluation-reporting.js +54 -52
- package/dist/eval-core/execution-strategy.d.ts +2 -0
- package/dist/eval-core/execution-strategy.js +11 -9
- package/dist/eval-core/fact-checker.js +15 -7
- package/dist/eval-core/holdout.js +3 -2
- package/dist/eval-core/judge-independence.d.ts +2 -2
- package/dist/eval-core/mock-hook.cjs +23 -6
- package/dist/eval-core/mocks-runtime.js +30 -8
- package/dist/eval-core/report-document.d.ts +12 -0
- package/dist/eval-core/report-document.js +1151 -0
- package/dist/eval-core/report-extensions.d.ts +4 -0
- package/dist/eval-core/report-extensions.js +500 -0
- package/dist/eval-core/report-file-migration.js +7 -2
- package/dist/eval-core/resume-compatibility.d.ts +31 -0
- package/dist/eval-core/resume-compatibility.js +141 -0
- package/dist/eval-core/sample-fingerprint.d.ts +12 -0
- package/dist/eval-core/sample-fingerprint.js +193 -0
- package/dist/eval-core/schema.js +86 -31
- package/dist/eval-core/verdict.d.ts +8 -4
- package/dist/eval-core/verdict.js +24 -10
- package/dist/eval-workflows/batch-evaluation-workflow.d.ts +2 -1
- package/dist/eval-workflows/batch-evaluation-workflow.js +25 -12
- package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.d.ts +10 -5
- package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.js +58 -21
- package/dist/eval-workflows/evaluation-pipeline/report-finalize.js +3 -1
- package/dist/eval-workflows/evaluation-pipeline/run-state.d.ts +4 -1
- package/dist/eval-workflows/evaluation-pipeline/run-state.js +4 -1
- package/dist/eval-workflows/evaluation-pipeline/test-set-hash.d.ts +6 -5
- package/dist/eval-workflows/evaluation-pipeline/test-set-hash.js +17 -10
- package/dist/eval-workflows/evaluation-pipeline.js +12 -7
- package/dist/eval-workflows/run-evaluation.d.ts +9 -7
- package/dist/eval-workflows/run-evaluation.js +79 -51
- package/dist/executors/anthropic-api.js +65 -9
- package/dist/executors/claude-cli.js +16 -79
- package/dist/executors/claude-protocol.d.ts +28 -0
- package/dist/executors/claude-protocol.js +180 -0
- package/dist/executors/claude-sdk-trace.js +56 -28
- package/dist/executors/claude-sdk.d.ts +1 -0
- package/dist/executors/claude-sdk.js +39 -93
- package/dist/executors/codex-cli-trace.js +166 -31
- package/dist/executors/codex-cli.d.ts +6 -8
- package/dist/executors/codex-cli.js +49 -151
- package/dist/executors/codex-protocol.d.ts +24 -0
- package/dist/executors/codex-protocol.js +234 -0
- package/dist/executors/codex-sdk.js +68 -120
- package/dist/executors/gemini.js +88 -13
- package/dist/executors/index.d.ts +2 -3
- package/dist/executors/index.js +5 -3
- package/dist/executors/openai-api.js +70 -9
- package/dist/executors/runtime-fingerprint.js +88 -11
- package/dist/executors/script-command.d.ts +8 -0
- package/dist/executors/script-command.js +87 -0
- package/dist/executors/script.js +202 -29
- package/dist/executors/shared.d.ts +35 -3
- package/dist/executors/shared.js +113 -15
- package/dist/grading/assertions.d.ts +1 -1
- package/dist/grading/assertions.js +19 -9
- package/dist/grading/diagnostic.d.ts +9 -2
- package/dist/grading/diagnostic.js +25 -2
- package/dist/grading/index.js +10 -4
- package/dist/grading/judge.js +19 -6
- package/dist/grading/layered-scores.d.ts +2 -3
- package/dist/grading/layered-scores.js +2 -3
- package/dist/inputs/load-samples.d.ts +1 -2
- package/dist/inputs/load-samples.js +23 -1
- package/dist/inputs/mcp-resolver.js +6 -3
- package/dist/inputs/sample-document.d.ts +11 -0
- package/dist/inputs/sample-document.js +96 -0
- package/dist/managed/evidence.d.ts +1 -0
- package/dist/managed/evidence.js +1 -1
- package/dist/managed/store.js +200 -91
- package/dist/observability/codex-trace-adapter.d.ts +5 -0
- package/dist/observability/codex-trace-adapter.js +850 -0
- package/dist/observability/experience.d.ts +32 -6
- package/dist/observability/experience.js +2695 -459
- package/dist/observability/feedback-matchers.js +16 -1
- package/dist/observability/inbox-view-model.d.ts +1 -1
- package/dist/observability/inbox-view-model.js +19 -14
- package/dist/observability/inbox.d.ts +7 -1
- package/dist/observability/inbox.js +632 -124
- package/dist/observability/problem-patterns.js +2 -0
- package/dist/observability/review-state.d.ts +6 -0
- package/dist/observability/review-state.js +235 -63
- package/dist/observability/skill-chain-advisories.js +1 -1
- package/dist/observability/skill-chain.js +17 -4
- package/dist/observability/skill-health-analyzer.d.ts +32 -7
- package/dist/observability/skill-health-analyzer.js +194 -121
- package/dist/observability/skill-health-report.d.ts +10 -0
- package/dist/observability/skill-health-report.js +620 -0
- package/dist/observability/soft-standards/constants.d.ts +0 -1
- package/dist/observability/soft-standards/constants.js +0 -1
- package/dist/observability/soft-standards/index.d.ts +1 -1
- package/dist/observability/soft-standards/index.js +1 -1
- package/dist/observability/soft-standards/llm-extractor.js +8 -10
- package/dist/observability/soft-standards/skill-standards-store.d.ts +2 -1
- package/dist/observability/soft-standards/skill-standards-store.js +59 -18
- package/dist/observability/soft-standards/types.d.ts +2 -2
- package/dist/observability/trace-adapter.d.ts +12 -7
- package/dist/observability/trace-adapter.js +11 -9
- package/dist/observability/trace-attribution.d.ts +13 -5
- package/dist/observability/trace-attribution.js +315 -21
- package/dist/observability/trace-ingestion.d.ts +9 -0
- package/dist/observability/trace-ingestion.js +80 -0
- package/dist/observability/trace-ir.d.ts +113 -0
- package/dist/observability/trace-ir.js +87 -0
- package/dist/observability/trace-segmenter.d.ts +19 -6
- package/dist/observability/trace-segmenter.js +377 -196
- package/dist/observability/trace-session-index.d.ts +19 -0
- package/dist/observability/trace-session-index.js +68 -0
- package/dist/observability/trace-source.d.ts +12 -4
- package/dist/observability/trace-source.js +939 -215
- package/dist/renderer/html-renderer.js +37 -6
- package/dist/renderer/icons.js +3 -0
- package/dist/renderer/observation-inbox-renderer.js +208 -90
- package/dist/renderer/skill-detail-renderer.js +452 -109
- package/dist/renderer/skill-health-renderer.js +69 -12
- package/dist/renderer/summary.js +28 -7
- package/dist/renderer/table.js +21 -4
- package/dist/renderer/test-view.d.ts +1 -0
- package/dist/renderer/test-view.js +44 -9
- package/dist/server/indexed-report-store.js +14 -18
- package/dist/server/job-store.js +64 -26
- package/dist/server/report-server.js +190 -78
- package/dist/server/report-store.js +57 -80
- package/dist/server/skill-index.js +143 -49
- package/dist/server/skill-insights.js +44 -5
- package/dist/shared/artifact-graph.d.ts +3 -0
- package/dist/shared/artifact-graph.js +224 -0
- package/dist/shared/assertion-types.d.ts +8 -0
- package/dist/shared/assertion-types.js +46 -0
- package/dist/shared/atomic-json.d.ts +8 -0
- package/dist/shared/atomic-json.js +33 -0
- package/dist/shared/diagnosis-schema.d.ts +9 -0
- package/dist/shared/diagnosis-schema.js +181 -0
- package/dist/shared/doctor-report.d.ts +3 -0
- package/dist/shared/doctor-report.js +103 -0
- package/dist/shared/evaluation-job.d.ts +6 -0
- package/dist/shared/evaluation-job.js +217 -0
- package/dist/shared/executor-result.d.ts +17 -0
- package/dist/shared/executor-result.js +221 -0
- package/dist/shared/file-lock.d.ts +12 -0
- package/dist/shared/file-lock.js +129 -0
- package/dist/shared/json-value.d.ts +5 -0
- package/dist/shared/json-value.js +36 -0
- package/dist/shared/keyed-mutex.d.ts +7 -0
- package/dist/shared/keyed-mutex.js +24 -0
- package/dist/shared/record-count.d.ts +8 -0
- package/dist/shared/record-count.js +43 -0
- package/dist/shared/sample-contract.d.ts +3 -0
- package/dist/shared/sample-contract.js +332 -0
- package/dist/shared/timestamp.d.ts +6 -0
- package/dist/shared/timestamp.js +64 -0
- package/dist/shared/token-usage.d.ts +19 -0
- package/dist/shared/token-usage.js +50 -0
- package/dist/shared/tool-call-status.d.ts +8 -0
- package/dist/shared/tool-call-status.js +28 -0
- package/dist/shared/tool-identity.d.ts +21 -0
- package/dist/shared/tool-identity.js +84 -0
- package/dist/shared/tool-search.js +73 -16
- package/dist/shared/trace-projection.d.ts +5 -0
- package/dist/shared/trace-projection.js +20 -0
- package/dist/shared/trace-source-kind.d.ts +3 -0
- package/dist/shared/trace-source-kind.js +12 -0
- package/dist/types/diagnosis.d.ts +2 -0
- package/dist/types/eval.d.ts +4 -0
- package/dist/types/executor.d.ts +32 -5
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/judge.d.ts +2 -0
- package/dist/types/observability.d.ts +116 -9
- package/dist/types/report.d.ts +58 -6
- package/dist/types/skill-index.d.ts +7 -0
- package/dist/types/trace.d.ts +2 -0
- package/dist/types/trace.js +1 -0
- package/package.json +9 -5
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { asErrorLike, DEFAULT_TIMEOUT_MS, errorMessage } from './shared.js';
|
|
1
|
+
import { asErrorLike, DEFAULT_TIMEOUT_MS, errorMessage, readJsonResponse, responseBodyPreview, } from './shared.js';
|
|
2
|
+
import { optionalTokenCount, splitInclusiveInputTokens } from '../shared/token-usage.js';
|
|
2
3
|
export async function openAiApiExecutor({ model, system, prompt, timeoutMs = DEFAULT_TIMEOUT_MS }) {
|
|
3
4
|
const apiKey = process.env.OPENAI_API_KEY;
|
|
4
5
|
if (!apiKey)
|
|
@@ -16,17 +17,77 @@ export async function openAiApiExecutor({ model, system, prompt, timeoutMs = DEF
|
|
|
16
17
|
body: JSON.stringify({ model: model || 'gpt-4o', messages }),
|
|
17
18
|
signal: AbortSignal.timeout(timeoutMs),
|
|
18
19
|
});
|
|
19
|
-
const data = await res
|
|
20
|
+
const { data, rawBody } = await readJsonResponse(res);
|
|
20
21
|
const durationMs = Date.now() - start;
|
|
21
22
|
if (!res.ok) {
|
|
22
|
-
|
|
23
|
+
const bodyPreview = responseBodyPreview(rawBody);
|
|
24
|
+
return { ok: false, error: data?.error?.message || `API error ${res.status}${bodyPreview ? `: ${bodyPreview}` : ''}`, durationMs, durationApiMs: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0, tokenUsageReportedByExecutor: false, costUSD: 0, costReportedByExecutor: false, output: null, stopReason: 'error', numTurns: 0 };
|
|
25
|
+
}
|
|
26
|
+
if (!data) {
|
|
27
|
+
return { ok: false, error: 'OpenAI API returned an empty or non-JSON response', durationMs, durationApiMs: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0, tokenUsageReportedByExecutor: false, costUSD: 0, costReportedByExecutor: false, output: null, stopReason: 'error', numTurns: 0 };
|
|
28
|
+
}
|
|
29
|
+
const usage = data.usage;
|
|
30
|
+
const rawInputTokens = optionalTokenCount(usage?.prompt_tokens);
|
|
31
|
+
const rawOutputTokens = optionalTokenCount(usage?.completion_tokens);
|
|
32
|
+
const rawCacheReadTokens = usage?.prompt_tokens_details?.cached_tokens === undefined
|
|
33
|
+
? 0
|
|
34
|
+
: optionalTokenCount(usage.prompt_tokens_details.cached_tokens);
|
|
35
|
+
if (rawInputTokens === undefined
|
|
36
|
+
|| rawOutputTokens === undefined
|
|
37
|
+
|| rawCacheReadTokens === undefined
|
|
38
|
+
|| rawCacheReadTokens > rawInputTokens) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
error: 'OpenAI response contained missing or invalid token usage',
|
|
42
|
+
durationMs,
|
|
43
|
+
durationApiMs: 0,
|
|
44
|
+
inputTokens: 0,
|
|
45
|
+
outputTokens: 0,
|
|
46
|
+
cacheReadTokens: 0,
|
|
47
|
+
cacheCreationTokens: 0,
|
|
48
|
+
tokenUsageReportedByExecutor: false,
|
|
49
|
+
costUSD: 0,
|
|
50
|
+
costReportedByExecutor: false,
|
|
51
|
+
output: null,
|
|
52
|
+
stopReason: 'error',
|
|
53
|
+
numTurns: 1,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const normalizedInput = splitInclusiveInputTokens(rawInputTokens, rawCacheReadTokens);
|
|
57
|
+
const inputTokens = normalizedInput.inputTokens;
|
|
58
|
+
const outputTokens = rawOutputTokens;
|
|
59
|
+
const cacheReadTokens = normalizedInput.cacheReadTokens;
|
|
60
|
+
const choice = data.choices?.[0];
|
|
61
|
+
const content = choice?.message?.content;
|
|
62
|
+
if (typeof content !== 'string' || content.trim().length === 0) {
|
|
63
|
+
const refusal = choice?.message?.refusal?.trim();
|
|
64
|
+
return {
|
|
65
|
+
ok: false,
|
|
66
|
+
error: refusal
|
|
67
|
+
? `OpenAI response refused the request: ${refusal}`
|
|
68
|
+
: 'OpenAI response did not contain assistant text',
|
|
69
|
+
durationMs,
|
|
70
|
+
durationApiMs: 0,
|
|
71
|
+
inputTokens,
|
|
72
|
+
outputTokens,
|
|
73
|
+
cacheReadTokens,
|
|
74
|
+
cacheCreationTokens: 0,
|
|
75
|
+
costUSD: 0,
|
|
76
|
+
costReportedByExecutor: false,
|
|
77
|
+
output: null,
|
|
78
|
+
stopReason: 'error',
|
|
79
|
+
numTurns: 1,
|
|
80
|
+
};
|
|
23
81
|
}
|
|
24
|
-
const usage = data.usage || {};
|
|
25
82
|
return {
|
|
26
|
-
ok: true, output:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
83
|
+
ok: true, output: content, durationMs, durationApiMs: 0,
|
|
84
|
+
// OpenAI reports cached tokens as a subset of prompt_tokens. Keep all
|
|
85
|
+
// executor buckets mutually exclusive so totals and cross-runtime trends
|
|
86
|
+
// do not count cache reads twice.
|
|
87
|
+
inputTokens, outputTokens,
|
|
88
|
+
cacheReadTokens, cacheCreationTokens: 0,
|
|
89
|
+
costUSD: 0, costReportedByExecutor: false,
|
|
90
|
+
stopReason: choice?.finish_reason || 'unknown', numTurns: 1,
|
|
30
91
|
};
|
|
31
92
|
}
|
|
32
93
|
catch (err) {
|
|
@@ -34,6 +95,6 @@ export async function openAiApiExecutor({ model, system, prompt, timeoutMs = DEF
|
|
|
34
95
|
const details = asErrorLike(err);
|
|
35
96
|
const stopReason = details.name === 'TimeoutError' ? 'timeout' : 'error';
|
|
36
97
|
const error = details.name === 'TimeoutError' ? `API request timed out after ${timeoutMs / 1000}s` : errorMessage(err);
|
|
37
|
-
return { ok: false, error, durationMs, durationApiMs: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0, costUSD: 0, output: null, stopReason, numTurns: 0 };
|
|
98
|
+
return { ok: false, error, durationMs, durationApiMs: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0, tokenUsageReportedByExecutor: false, costUSD: 0, costReportedByExecutor: false, output: null, stopReason, numTurns: 0 };
|
|
38
99
|
}
|
|
39
100
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
import { delimiter, dirname, isAbsolute, join } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { buildExecEnv } from './shared.js';
|
|
8
|
+
import { isScriptFileInterpreter, resolveScriptCommand, } from './script-command.js';
|
|
8
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const requireFromHere = createRequire(import.meta.url);
|
|
10
11
|
const RUNTIME_CACHE = new Map();
|
|
@@ -95,7 +96,7 @@ function readPackageField(packageName, field, from) {
|
|
|
95
96
|
return undefined;
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
|
-
function readCommand(command, args, env, timeout =
|
|
99
|
+
function readCommand(command, args, env, timeout = 3000) {
|
|
99
100
|
try {
|
|
100
101
|
const output = execFileSync(command, args, {
|
|
101
102
|
encoding: 'utf-8',
|
|
@@ -140,12 +141,13 @@ function withFingerprint(input) {
|
|
|
140
141
|
const stablePayload = {
|
|
141
142
|
executor: input.executor,
|
|
142
143
|
model: input.model,
|
|
143
|
-
|
|
144
|
+
runtimeKind: input.runtimeKind,
|
|
144
145
|
binary: input.binary
|
|
145
146
|
? {
|
|
146
147
|
name: input.binary.name,
|
|
147
148
|
source: input.binary.source,
|
|
148
149
|
version: input.binary.version,
|
|
150
|
+
contentHash: input.binary.contentHash,
|
|
149
151
|
status: input.binary.version ? 'ok' : input.binary.error ? 'error' : 'missing',
|
|
150
152
|
package: input.binary.package
|
|
151
153
|
? {
|
|
@@ -179,13 +181,91 @@ function runtime(executor, model, kind, capabilities, extra = {}) {
|
|
|
179
181
|
function runtimeEnv(options) {
|
|
180
182
|
return options?.env ?? buildExecEnv(options?.skillDir);
|
|
181
183
|
}
|
|
184
|
+
function hashRuntimeFiles(paths) {
|
|
185
|
+
const files = [...new Set(paths)]
|
|
186
|
+
.filter((path) => {
|
|
187
|
+
try {
|
|
188
|
+
return statSync(path).isFile();
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
.sort();
|
|
195
|
+
if (files.length === 0)
|
|
196
|
+
return {};
|
|
197
|
+
const hash = createHash('sha256');
|
|
198
|
+
const errors = [];
|
|
199
|
+
for (const path of files) {
|
|
200
|
+
hash.update(path);
|
|
201
|
+
hash.update('\0');
|
|
202
|
+
try {
|
|
203
|
+
hash.update(readFileSync(path));
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
207
|
+
errors.push(`${path}: ${message}`);
|
|
208
|
+
hash.update(`<unreadable:${message}>`);
|
|
209
|
+
}
|
|
210
|
+
hash.update('\0');
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
contentHash: hash.digest('hex'),
|
|
214
|
+
...(errors.length > 0 && { error: errors.join('; ') }),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function scriptRuntime(executorName, model, env) {
|
|
218
|
+
const resolved = resolveScriptCommand(executorName);
|
|
219
|
+
const executablePath = resolvePathBinary(resolved.command, env);
|
|
220
|
+
const interpreter = isScriptFileInterpreter(resolved.command);
|
|
221
|
+
const executable = interpreter
|
|
222
|
+
? readPathBinary(resolved.command, env)
|
|
223
|
+
: {
|
|
224
|
+
name: resolved.command,
|
|
225
|
+
source: executablePath ? 'path' : 'unknown',
|
|
226
|
+
...(executablePath && { path: executablePath }),
|
|
227
|
+
...(!executablePath && { error: 'executable not found on PATH' }),
|
|
228
|
+
};
|
|
229
|
+
const fileIdentity = hashRuntimeFiles([
|
|
230
|
+
...resolved.referencedFiles,
|
|
231
|
+
...(!interpreter && executablePath ? [executablePath] : []),
|
|
232
|
+
]);
|
|
233
|
+
const errors = [executable.error, fileIdentity.error].filter(Boolean).join('; ');
|
|
234
|
+
return runtime(executorName, model, 'script', {
|
|
235
|
+
...UNKNOWN_CAPABILITIES,
|
|
236
|
+
trace: 'best-effort',
|
|
237
|
+
skillIsolation: 'none',
|
|
238
|
+
}, {
|
|
239
|
+
binary: {
|
|
240
|
+
...executable,
|
|
241
|
+
...(fileIdentity.contentHash && {
|
|
242
|
+
contentHash: fileIdentity.contentHash,
|
|
243
|
+
}),
|
|
244
|
+
...(errors && { error: errors }),
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
}
|
|
182
248
|
export function getExecutorRuntimeFingerprint(executorName, model, options = {}) {
|
|
183
249
|
const env = runtimeEnv(options);
|
|
250
|
+
if (![
|
|
251
|
+
'claude',
|
|
252
|
+
'claude-sdk',
|
|
253
|
+
'codex',
|
|
254
|
+
'codex-sdk',
|
|
255
|
+
'gemini',
|
|
256
|
+
'anthropic-api',
|
|
257
|
+
'openai-api',
|
|
258
|
+
].includes(executorName)) {
|
|
259
|
+
// Custom executors are local code. Re-read their referenced files so a
|
|
260
|
+
// long-running Studio process cannot reuse stale outputs after the script
|
|
261
|
+
// changes while the command line remains identical.
|
|
262
|
+
return scriptRuntime(executorName, model, env);
|
|
263
|
+
}
|
|
184
264
|
const pathHash = hashString(env.PATH || '');
|
|
185
265
|
const cacheKey = `${executorName}\0${model}\0${pathHash}`;
|
|
186
266
|
const cached = RUNTIME_CACHE.get(cacheKey);
|
|
187
267
|
if (cached)
|
|
188
|
-
return cached;
|
|
268
|
+
return structuredClone(cached);
|
|
189
269
|
let fp;
|
|
190
270
|
switch (executorName) {
|
|
191
271
|
case 'claude': {
|
|
@@ -265,12 +345,9 @@ export function getExecutorRuntimeFingerprint(executorName, model, options = {})
|
|
|
265
345
|
}, { binary: { name: executorName, source: 'none' } });
|
|
266
346
|
break;
|
|
267
347
|
}
|
|
268
|
-
default:
|
|
269
|
-
|
|
270
|
-
binary: { name: executorName, source: 'unknown' },
|
|
271
|
-
});
|
|
272
|
-
}
|
|
348
|
+
default:
|
|
349
|
+
throw new Error(`unreachable executor runtime: ${executorName}`);
|
|
273
350
|
}
|
|
274
|
-
RUNTIME_CACHE.set(cacheKey, fp);
|
|
275
|
-
return fp;
|
|
351
|
+
RUNTIME_CACHE.set(cacheKey, structuredClone(fp));
|
|
352
|
+
return structuredClone(fp);
|
|
276
353
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface ResolvedScriptCommand {
|
|
2
|
+
command: string;
|
|
3
|
+
args: string[];
|
|
4
|
+
/** Existing local files that can change the command's behavior. */
|
|
5
|
+
referencedFiles: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function isScriptFileInterpreter(commandPath: string): boolean;
|
|
8
|
+
export declare function resolveScriptCommand(commandLine: string, baseCwd?: string): ResolvedScriptCommand;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { basename, isAbsolute, resolve } from 'node:path';
|
|
3
|
+
const SCRIPT_FILE_INTERPRETERS = new Set([
|
|
4
|
+
'node',
|
|
5
|
+
'nodejs',
|
|
6
|
+
'bash',
|
|
7
|
+
'sh',
|
|
8
|
+
'zsh',
|
|
9
|
+
'ruby',
|
|
10
|
+
'perl',
|
|
11
|
+
'php',
|
|
12
|
+
'bun',
|
|
13
|
+
'deno',
|
|
14
|
+
]);
|
|
15
|
+
const SCRIPTLESS_INTERPRETER_FLAGS_WITH_VALUE = new Set([
|
|
16
|
+
'-m',
|
|
17
|
+
'-c',
|
|
18
|
+
'-e',
|
|
19
|
+
'-p',
|
|
20
|
+
'--eval',
|
|
21
|
+
'--print',
|
|
22
|
+
]);
|
|
23
|
+
export function isScriptFileInterpreter(commandPath) {
|
|
24
|
+
const name = basename(commandPath).toLowerCase().replace(/\.(exe|cmd|bat)$/, '');
|
|
25
|
+
return SCRIPT_FILE_INTERPRETERS.has(name) || /^python(?:\d+(?:\.\d+)*)?$/.test(name);
|
|
26
|
+
}
|
|
27
|
+
function isBareScriptFileArg(part) {
|
|
28
|
+
return !part.includes('/')
|
|
29
|
+
&& !part.startsWith('.')
|
|
30
|
+
&& /\.(?:cjs|cts|js|jsx|mjs|mts|php|pl|py|pyw|rb|sh|ts|tsx)$/i.test(part);
|
|
31
|
+
}
|
|
32
|
+
function isInlineScriptlessInterpreterFlag(arg) {
|
|
33
|
+
return /^(?:--eval=|--print=|-[mcep].+)/.test(arg);
|
|
34
|
+
}
|
|
35
|
+
function resolveExecutorPath(part, baseCwd, options = {}) {
|
|
36
|
+
if (isAbsolute(part))
|
|
37
|
+
return part;
|
|
38
|
+
if (part.startsWith('-'))
|
|
39
|
+
return part;
|
|
40
|
+
const pathLike = part.includes('/') || part.startsWith('.');
|
|
41
|
+
if (!pathLike && !options.resolveBare)
|
|
42
|
+
return part;
|
|
43
|
+
const candidate = resolve(baseCwd, part);
|
|
44
|
+
return existsSync(candidate) ? candidate : part;
|
|
45
|
+
}
|
|
46
|
+
function resolveExecutorArgs(args, commandPath, baseCwd) {
|
|
47
|
+
if (!isScriptFileInterpreter(commandPath)) {
|
|
48
|
+
return args.map((arg) => resolveExecutorPath(arg, baseCwd));
|
|
49
|
+
}
|
|
50
|
+
let scriptResolved = false;
|
|
51
|
+
let scriptPositionClosed = false;
|
|
52
|
+
let skipNext = false;
|
|
53
|
+
return args.map((arg) => {
|
|
54
|
+
if (skipNext) {
|
|
55
|
+
skipNext = false;
|
|
56
|
+
return arg;
|
|
57
|
+
}
|
|
58
|
+
if (!scriptResolved && !scriptPositionClosed) {
|
|
59
|
+
if (SCRIPTLESS_INTERPRETER_FLAGS_WITH_VALUE.has(arg)) {
|
|
60
|
+
skipNext = true;
|
|
61
|
+
scriptPositionClosed = true;
|
|
62
|
+
return arg;
|
|
63
|
+
}
|
|
64
|
+
if (isInlineScriptlessInterpreterFlag(arg)) {
|
|
65
|
+
scriptPositionClosed = true;
|
|
66
|
+
return arg;
|
|
67
|
+
}
|
|
68
|
+
if (arg.startsWith('-') && arg !== '--')
|
|
69
|
+
return arg;
|
|
70
|
+
if (arg === '--')
|
|
71
|
+
return arg;
|
|
72
|
+
scriptResolved = true;
|
|
73
|
+
return resolveExecutorPath(arg, baseCwd, {
|
|
74
|
+
resolveBare: isBareScriptFileArg(arg),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return resolveExecutorPath(arg, baseCwd);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
export function resolveScriptCommand(commandLine, baseCwd = process.cwd()) {
|
|
81
|
+
const parts = commandLine.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [commandLine];
|
|
82
|
+
const command = resolveExecutorPath(parts[0].replace(/^["']|["']$/g, ''), baseCwd);
|
|
83
|
+
const rawArgs = parts.slice(1).map((arg) => arg.replace(/^["']|["']$/g, ''));
|
|
84
|
+
const args = resolveExecutorArgs(rawArgs, command, baseCwd);
|
|
85
|
+
const referencedFiles = [command, ...args].filter((part) => isAbsolute(part) && existsSync(part));
|
|
86
|
+
return { command, args, referencedFiles };
|
|
87
|
+
}
|
package/dist/executors/script.js
CHANGED
|
@@ -1,31 +1,102 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { isAbsolute, resolve } from 'node:path';
|
|
3
1
|
import { materializeForCliConfigDir } from '../eval-core/mocks-runtime.js';
|
|
2
|
+
import { executorResultValidationError, normalizeExecResultToolIdentities, } from '../shared/executor-result.js';
|
|
3
|
+
import { resolveScriptCommand } from './script-command.js';
|
|
4
4
|
import { DEFAULT_TIMEOUT_MS, interruptedExecResult, spawnWithSigintPropagation, timeoutExecResult, } from './shared.js';
|
|
5
5
|
// script executor 由用户自定义,omk 无法保证它实现 skill 隔离。
|
|
6
6
|
// 任何 allowedSkills(包括 [])下都 stderr 一次性 warn,不阻塞执行,
|
|
7
7
|
// 让用户知道 strict-baseline / 显式 allowedSkills 在 script executor 下静默无效。
|
|
8
8
|
let scriptIsolationWarned = false;
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
function nonNegativeNumber(value) {
|
|
10
|
+
return typeof value === 'number'
|
|
11
|
+
&& Number.isFinite(value)
|
|
12
|
+
&& value >= 0
|
|
13
|
+
&& value <= Number.MAX_SAFE_INTEGER
|
|
14
|
+
? value
|
|
15
|
+
: 0;
|
|
16
|
+
}
|
|
17
|
+
function nonNegativeInteger(value) {
|
|
18
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
19
|
+
}
|
|
20
|
+
function scriptProtocolMetricsError(protocol) {
|
|
21
|
+
const integerFields = [
|
|
22
|
+
'inputTokens',
|
|
23
|
+
'outputTokens',
|
|
24
|
+
'cacheReadTokens',
|
|
25
|
+
'cacheCreationTokens',
|
|
26
|
+
'numTurns',
|
|
27
|
+
];
|
|
28
|
+
for (const field of integerFields) {
|
|
29
|
+
if (protocol[field] !== undefined && nonNegativeInteger(protocol[field]) !== protocol[field]) {
|
|
30
|
+
return `"${field}" must be a non-negative safe integer`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
for (const field of ['durationApiMs', 'costUSD']) {
|
|
34
|
+
if (protocol[field] !== undefined && nonNegativeNumber(protocol[field]) !== protocol[field]) {
|
|
35
|
+
return `"${field}" must be a non-negative finite number`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
function scriptProtocolReportsTokenUsage(protocol) {
|
|
41
|
+
return [
|
|
42
|
+
'inputTokens',
|
|
43
|
+
'outputTokens',
|
|
44
|
+
'cacheReadTokens',
|
|
45
|
+
'cacheCreationTokens',
|
|
46
|
+
].every((field) => protocol[field] !== undefined);
|
|
47
|
+
}
|
|
48
|
+
function scriptProtocolTraceFields(protocol) {
|
|
49
|
+
return {
|
|
50
|
+
...(protocol.turns !== undefined && {
|
|
51
|
+
turns: protocol.turns,
|
|
52
|
+
}),
|
|
53
|
+
...(protocol.toolCalls !== undefined && {
|
|
54
|
+
toolCalls: protocol.toolCalls,
|
|
55
|
+
}),
|
|
56
|
+
...(protocol.fullNumTurns !== undefined && {
|
|
57
|
+
fullNumTurns: protocol.fullNumTurns,
|
|
58
|
+
}),
|
|
59
|
+
...(protocol.numSubAgents !== undefined && {
|
|
60
|
+
numSubAgents: protocol.numSubAgents,
|
|
61
|
+
}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function invalidScriptProtocolResult(message, durationMs, mockStats) {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
error: `script executor returned malformed protocol JSON: ${message}`,
|
|
68
|
+
output: null,
|
|
69
|
+
durationMs,
|
|
70
|
+
durationApiMs: 0,
|
|
71
|
+
inputTokens: 0,
|
|
72
|
+
outputTokens: 0,
|
|
73
|
+
cacheReadTokens: 0,
|
|
74
|
+
cacheCreationTokens: 0,
|
|
75
|
+
tokenUsageReportedByExecutor: false,
|
|
76
|
+
costUSD: 0,
|
|
77
|
+
costReportedByExecutor: false,
|
|
78
|
+
stopReason: 'error',
|
|
79
|
+
numTurns: 0,
|
|
80
|
+
...(mockStats && { mockStats }),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function validateScriptProtocolResult(result, mockStats) {
|
|
84
|
+
const validationError = executorResultValidationError(result);
|
|
85
|
+
if (validationError) {
|
|
86
|
+
return invalidScriptProtocolResult(validationError, result.durationMs, mockStats);
|
|
87
|
+
}
|
|
88
|
+
return normalizeExecResultToolIdentities(result);
|
|
16
89
|
}
|
|
17
90
|
export function createScriptExecutor(command) {
|
|
18
91
|
const baseCwd = process.cwd();
|
|
19
|
-
const
|
|
20
|
-
const cmd =
|
|
21
|
-
const args =
|
|
22
|
-
.map((a) => a.replace(/^["']|["']$/g, ''))
|
|
23
|
-
.map((a) => resolveExecutorPath(a, baseCwd));
|
|
92
|
+
const resolved = resolveScriptCommand(command, baseCwd);
|
|
93
|
+
const cmd = resolved.command;
|
|
94
|
+
const args = resolved.args;
|
|
24
95
|
return async function scriptExecutor({ model, system, prompt, cwd, timeoutMs = DEFAULT_TIMEOUT_MS, allowedSkills, mocks, mocksBaseDir, mocksStrict }) {
|
|
25
96
|
if (allowedSkills !== undefined && !scriptIsolationWarned) {
|
|
26
97
|
scriptIsolationWarned = true;
|
|
27
98
|
process.stderr.write(`[omk] script executor 不参与 skill isolation:allowedSkills=${JSON.stringify(allowedSkills)} 在自定义 executor "${command}" 下无效。\n`
|
|
28
|
-
+
|
|
99
|
+
+ ' 如需 baseline 真正隔离,请用 --executor codex。\n');
|
|
29
100
|
}
|
|
30
101
|
const input = JSON.stringify({ model, system: system || '', prompt });
|
|
31
102
|
const start = Date.now();
|
|
@@ -64,31 +135,132 @@ export function createScriptExecutor(command) {
|
|
|
64
135
|
timeoutMs,
|
|
65
136
|
...(cwd && { cwd }),
|
|
66
137
|
});
|
|
67
|
-
child.stdin?.
|
|
68
|
-
child.stdin?.end();
|
|
138
|
+
child.stdin?.on('error', () => undefined);
|
|
139
|
+
child.stdin?.end(input);
|
|
69
140
|
try {
|
|
70
141
|
const r = await done;
|
|
71
142
|
const durationMs = Date.now() - start;
|
|
72
143
|
const ms = captureMockStats();
|
|
144
|
+
let protocol;
|
|
73
145
|
try {
|
|
74
146
|
const data = JSON.parse(r.stdout);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
...(ms && { mockStats: ms }),
|
|
82
|
-
};
|
|
147
|
+
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
148
|
+
const candidate = data;
|
|
149
|
+
if (Object.hasOwn(candidate, 'ok') || Object.hasOwn(candidate, 'output')) {
|
|
150
|
+
protocol = candidate;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
83
153
|
}
|
|
84
154
|
catch {
|
|
155
|
+
// Non-JSON stdout is the executor's plain model output.
|
|
156
|
+
}
|
|
157
|
+
if (protocol) {
|
|
158
|
+
const metricsError = scriptProtocolMetricsError(protocol);
|
|
159
|
+
if (metricsError) {
|
|
160
|
+
return invalidScriptProtocolResult(metricsError, durationMs, ms);
|
|
161
|
+
}
|
|
162
|
+
const costReported = protocol.costUSD !== undefined;
|
|
163
|
+
const tokenUsageReported = scriptProtocolReportsTokenUsage(protocol);
|
|
164
|
+
if (protocol.ok !== undefined && typeof protocol.ok !== 'boolean') {
|
|
165
|
+
return invalidScriptProtocolResult('"ok" must be boolean', durationMs, ms);
|
|
166
|
+
}
|
|
167
|
+
if (protocol.output !== undefined
|
|
168
|
+
&& protocol.output !== null
|
|
169
|
+
&& typeof protocol.output !== 'string') {
|
|
170
|
+
return invalidScriptProtocolResult('"output" must be string or null', durationMs, ms);
|
|
171
|
+
}
|
|
172
|
+
if (protocol.error !== undefined && typeof protocol.error !== 'string') {
|
|
173
|
+
return invalidScriptProtocolResult('"error" must be string when present', durationMs, ms);
|
|
174
|
+
}
|
|
175
|
+
const traceFields = scriptProtocolTraceFields(protocol);
|
|
176
|
+
if (protocol.ok === false) {
|
|
177
|
+
return validateScriptProtocolResult({
|
|
178
|
+
ok: false,
|
|
179
|
+
error: typeof protocol.error === 'string' && protocol.error
|
|
180
|
+
? protocol.error
|
|
181
|
+
: 'script executor reported failure',
|
|
182
|
+
output: typeof protocol.output === 'string' ? protocol.output : null,
|
|
183
|
+
durationMs,
|
|
184
|
+
durationApiMs: nonNegativeNumber(protocol.durationApiMs),
|
|
185
|
+
inputTokens: nonNegativeInteger(protocol.inputTokens),
|
|
186
|
+
outputTokens: nonNegativeInteger(protocol.outputTokens),
|
|
187
|
+
cacheReadTokens: nonNegativeInteger(protocol.cacheReadTokens),
|
|
188
|
+
cacheCreationTokens: nonNegativeInteger(protocol.cacheCreationTokens),
|
|
189
|
+
...(!tokenUsageReported && { tokenUsageReportedByExecutor: false }),
|
|
190
|
+
costUSD: nonNegativeNumber(protocol.costUSD),
|
|
191
|
+
...(!costReported && { costReportedByExecutor: false }),
|
|
192
|
+
stopReason: typeof protocol.stopReason === 'string' ? protocol.stopReason : 'error',
|
|
193
|
+
numTurns: nonNegativeInteger(protocol.numTurns),
|
|
194
|
+
...traceFields,
|
|
195
|
+
...(ms && { mockStats: ms }),
|
|
196
|
+
}, ms);
|
|
197
|
+
}
|
|
198
|
+
if (typeof protocol.output !== 'string') {
|
|
199
|
+
return invalidScriptProtocolResult('"output" must be string', durationMs, ms);
|
|
200
|
+
}
|
|
201
|
+
if (!protocol.output.trim()) {
|
|
202
|
+
return {
|
|
203
|
+
ok: false,
|
|
204
|
+
error: 'script executor completed without model output',
|
|
205
|
+
output: null,
|
|
206
|
+
durationMs,
|
|
207
|
+
durationApiMs: nonNegativeNumber(protocol.durationApiMs),
|
|
208
|
+
inputTokens: nonNegativeInteger(protocol.inputTokens),
|
|
209
|
+
outputTokens: nonNegativeInteger(protocol.outputTokens),
|
|
210
|
+
cacheReadTokens: nonNegativeInteger(protocol.cacheReadTokens),
|
|
211
|
+
cacheCreationTokens: nonNegativeInteger(protocol.cacheCreationTokens),
|
|
212
|
+
...(!tokenUsageReported && { tokenUsageReportedByExecutor: false }),
|
|
213
|
+
costUSD: nonNegativeNumber(protocol.costUSD),
|
|
214
|
+
...(!costReported && { costReportedByExecutor: false }),
|
|
215
|
+
stopReason: 'error',
|
|
216
|
+
numTurns: protocol.numTurns === undefined ? 1 : nonNegativeInteger(protocol.numTurns),
|
|
217
|
+
...(ms && { mockStats: ms }),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return validateScriptProtocolResult({
|
|
221
|
+
ok: true, output: protocol.output, durationMs,
|
|
222
|
+
durationApiMs: nonNegativeNumber(protocol.durationApiMs),
|
|
223
|
+
inputTokens: nonNegativeInteger(protocol.inputTokens),
|
|
224
|
+
outputTokens: nonNegativeInteger(protocol.outputTokens),
|
|
225
|
+
cacheReadTokens: nonNegativeInteger(protocol.cacheReadTokens),
|
|
226
|
+
cacheCreationTokens: nonNegativeInteger(protocol.cacheCreationTokens),
|
|
227
|
+
...(!tokenUsageReported && { tokenUsageReportedByExecutor: false }),
|
|
228
|
+
costUSD: nonNegativeNumber(protocol.costUSD),
|
|
229
|
+
...(!costReported && { costReportedByExecutor: false }),
|
|
230
|
+
stopReason: typeof protocol.stopReason === 'string' ? protocol.stopReason : 'end',
|
|
231
|
+
numTurns: protocol.numTurns === undefined ? 1 : nonNegativeInteger(protocol.numTurns),
|
|
232
|
+
...traceFields,
|
|
233
|
+
...(ms && { mockStats: ms }),
|
|
234
|
+
}, ms);
|
|
235
|
+
}
|
|
236
|
+
const output = r.stdout.trim();
|
|
237
|
+
if (!output) {
|
|
85
238
|
return {
|
|
86
|
-
ok:
|
|
87
|
-
|
|
88
|
-
|
|
239
|
+
ok: false,
|
|
240
|
+
error: 'script executor completed without model output',
|
|
241
|
+
output: null,
|
|
242
|
+
durationMs,
|
|
243
|
+
durationApiMs: 0,
|
|
244
|
+
inputTokens: 0,
|
|
245
|
+
outputTokens: 0,
|
|
246
|
+
cacheReadTokens: 0,
|
|
247
|
+
cacheCreationTokens: 0,
|
|
248
|
+
tokenUsageReportedByExecutor: false,
|
|
249
|
+
costUSD: 0,
|
|
250
|
+
costReportedByExecutor: false,
|
|
251
|
+
stopReason: 'error',
|
|
252
|
+
numTurns: 1,
|
|
89
253
|
...(ms && { mockStats: ms }),
|
|
90
254
|
};
|
|
91
255
|
}
|
|
256
|
+
return {
|
|
257
|
+
ok: true, output, durationMs, durationApiMs: 0,
|
|
258
|
+
inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0,
|
|
259
|
+
tokenUsageReportedByExecutor: false,
|
|
260
|
+
costUSD: 0, costReportedByExecutor: false,
|
|
261
|
+
stopReason: 'end', numTurns: 1,
|
|
262
|
+
...(ms && { mockStats: ms }),
|
|
263
|
+
};
|
|
92
264
|
}
|
|
93
265
|
catch (err) {
|
|
94
266
|
const durationMs = Date.now() - start;
|
|
@@ -104,7 +276,8 @@ export function createScriptExecutor(command) {
|
|
|
104
276
|
error: stderr || details.message || `executor exited with code ${details.code ?? '?'}`,
|
|
105
277
|
durationMs, durationApiMs: 0,
|
|
106
278
|
inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0,
|
|
107
|
-
|
|
279
|
+
tokenUsageReportedByExecutor: false,
|
|
280
|
+
costUSD: 0, costReportedByExecutor: false, output: null, stopReason: 'error', numTurns: 0,
|
|
108
281
|
...(ms && { mockStats: ms }),
|
|
109
282
|
};
|
|
110
283
|
}
|