@the-open-engine/zeroshot 6.39.1 → 6.40.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 +45 -1
- package/cli/agent-provider-boundary.js +19 -0
- package/cli/agent-provider-boundary.ts +79 -0
- package/cli/export-stream.js +94 -0
- package/cli/export-stream.ts +118 -0
- package/cli/index.js +132 -61
- package/cli/json-export.js +2 -38
- package/cli/json-export.ts +2 -56
- package/cli/semantic-canonical.js +60 -0
- package/cli/semantic-canonical.ts +59 -0
- package/cli/semantic-contract.js +97 -0
- package/cli/semantic-contract.ts +153 -0
- package/cli/semantic-events.js +53 -0
- package/cli/semantic-events.ts +65 -0
- package/cli/semantic-evidence.js +63 -0
- package/cli/semantic-evidence.ts +65 -0
- package/cli/semantic-export.js +186 -0
- package/cli/semantic-export.ts +245 -0
- package/cli/semantic-json.js +69 -0
- package/cli/semantic-json.ts +69 -0
- package/cli/semantic-line-scanner.js +56 -0
- package/cli/semantic-line-scanner.ts +54 -0
- package/cli/semantic-parser.js +78 -0
- package/cli/semantic-parser.ts +95 -0
- package/cli/semantic-provider-line.js +91 -0
- package/cli/semantic-provider-line.ts +106 -0
- package/cli/trace-evidence.js +86 -0
- package/cli/trace-evidence.ts +115 -0
- package/cli/trace-export.js +120 -0
- package/cli/trace-export.ts +164 -0
- package/cli/trace-output-record.js +15 -0
- package/cli/trace-output-record.ts +18 -0
- package/cli/trace-output.js +98 -0
- package/cli/trace-output.ts +119 -0
- package/cluster-templates/conductor-bootstrap.json +1 -1
- package/lib/agent-cli-provider/adapters/claude.js +1 -1
- package/lib/agent-cli-provider/adapters/claude.js.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +26 -1
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- package/lib/agent-cli-provider/adapters/omp.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/omp.js +1 -1
- package/lib/agent-cli-provider/adapters/omp.js.map +1 -1
- package/lib/agent-cli-provider/log-prefix.d.ts.map +1 -1
- package/lib/agent-cli-provider/log-prefix.js +8 -6
- package/lib/agent-cli-provider/log-prefix.js.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-environment-policy.d.ts +4 -0
- package/lib/agent-cli-provider/omp/sdk-environment-policy.d.ts.map +1 -0
- package/lib/agent-cli-provider/omp/sdk-environment-policy.js +37 -0
- package/lib/agent-cli-provider/omp/sdk-environment-policy.js.map +1 -0
- package/lib/agent-cli-provider/omp/sdk-process-private-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-process-private-runtime.js +4 -15
- package/lib/agent-cli-provider/omp/sdk-process-private-runtime.js.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-process-result.d.ts +2 -0
- package/lib/agent-cli-provider/omp/sdk-process-result.d.ts.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-process-result.js +2 -2
- package/lib/agent-cli-provider/omp/sdk-process-result.js.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-process-runner.d.ts.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-process-runner.js +6 -1
- package/lib/agent-cli-provider/omp/sdk-process-runner.js.map +1 -1
- package/lib/agent-cli-provider/provider-registry.d.ts +3 -1
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +9 -2
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts +4 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.js +18 -15
- package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +3 -0
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/lib/cluster-worker/engine-adapter-common.js +18 -0
- package/lib/cluster-worker/engine-adapter.js +2 -1
- package/lib/git-remote-utils.js +22 -5
- package/lib/stream-json-parser.js +6 -5
- package/npm-shrinkwrap.json +2 -2
- package/package.json +7 -4
- package/scripts/omp/runtime-identities.js +31 -18
- package/scripts/omp/runtime.js +2 -0
- package/src/agent/agent-lifecycle.js +17 -1
- package/src/agent/agent-task-executor.js +36 -40
- package/src/agent/output-extraction-failures.js +73 -0
- package/src/agent/output-extraction-failures.ts +100 -0
- package/src/agent/output-extraction-json.js +5 -6
- package/src/agent/output-extraction-json.ts +4 -7
- package/src/agent/output-extraction-types.ts +5 -0
- package/src/agent/provider-terminal-failure.js +2 -2
- package/src/agent/provider-terminal-failure.ts +4 -2
- package/src/agent-cli-provider/adapters/claude.ts +1 -1
- package/src/agent-cli-provider/adapters/codex.ts +36 -1
- package/src/agent-cli-provider/adapters/omp.ts +2 -1
- package/src/agent-cli-provider/log-prefix.ts +12 -7
- package/src/agent-cli-provider/omp/sdk-environment-policy.ts +38 -0
- package/src/agent-cli-provider/omp/sdk-process-private-runtime.ts +4 -16
- package/src/agent-cli-provider/omp/sdk-process-result.ts +4 -2
- package/src/agent-cli-provider/omp/sdk-process-runner.ts +7 -1
- package/src/agent-cli-provider/provider-registry.ts +10 -2
- package/src/agent-cli-provider/single-agent-runtime.ts +24 -15
- package/src/agent-cli-provider/types.ts +3 -0
- package/src/agents/git-pusher-template.js +61 -71
- package/src/attach/socket-paths.js +1 -1
- package/src/attach/socket-paths.ts +1 -1
- package/src/claude-task-runner.js +9 -9
- package/src/foreground-benchmark-run.js +24 -12
- package/src/legacy-lib/git-remote-utils.ts +23 -5
- package/src/legacy-lib/stream-json-parser.ts +10 -5
- package/src/omp-session-verifier.js +8 -5
- package/src/orchestrator.js +42 -0
- package/src/preflight.js +31 -6
- package/src/providers/index.js +4 -22
- package/src/task-execution-context.js +22 -0
- package/src/task-log-line.d.ts +20 -0
- package/src/task-log-line.js +80 -0
- package/src/watcher-prompt-channel.js +1 -1
- package/src/worktree-claude-config.js +25 -0
- package/src/worktree-claude-config.ts +28 -0
- package/task-lib/commands/logs.js +5 -8
- package/task-lib/runner.js +57 -22
- package/task-lib/sdk-watcher-output.js +31 -0
- package/task-lib/sdk-watcher.js +4 -11
- package/task-lib/watcher-output-runtime.js +30 -16
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.openEvidence = openEvidence;
|
|
4
|
+
exports.emptyEvidence = emptyEvidence;
|
|
5
|
+
const crypto = require("crypto");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const export_stream_1 = require("./export-stream");
|
|
9
|
+
const trace_evidence_1 = require("./trace-evidence");
|
|
10
|
+
const trace_output_1 = require("./trace-output");
|
|
11
|
+
function hashOpenedEvidence(fd, byteLength) {
|
|
12
|
+
const digest = crypto.createHash('sha256');
|
|
13
|
+
let bytes = 0;
|
|
14
|
+
while (bytes < byteLength) {
|
|
15
|
+
const buffer = Buffer.allocUnsafe(Math.min(trace_output_1.TRACE_OUTPUT_CHUNK_BYTES, byteLength - bytes));
|
|
16
|
+
const read = fs.readSync(fd, buffer, 0, buffer.length, bytes);
|
|
17
|
+
if (read === 0)
|
|
18
|
+
break;
|
|
19
|
+
digest.update(read === buffer.length ? buffer : buffer.subarray(0, read));
|
|
20
|
+
bytes += read;
|
|
21
|
+
}
|
|
22
|
+
return { bytes, sha256: digest.digest('hex') };
|
|
23
|
+
}
|
|
24
|
+
function unavailable(issue) {
|
|
25
|
+
return { fd: null, before: null, byteLength: null, sha256: null, issue };
|
|
26
|
+
}
|
|
27
|
+
function openEvidence(taskId, task, allowedLogRoot) {
|
|
28
|
+
const expected = (0, trace_evidence_1.expectedLogPath)(allowedLogRoot, taskId);
|
|
29
|
+
const recorded = (0, export_stream_1.nullableString)(task.logFile);
|
|
30
|
+
if (!expected || !recorded || path.resolve(recorded) !== expected) {
|
|
31
|
+
return unavailable('log_reference_invalid');
|
|
32
|
+
}
|
|
33
|
+
let fd;
|
|
34
|
+
try {
|
|
35
|
+
fd = fs.openSync(expected, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0));
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const code = typeof error === 'object' && error !== null && 'code' in error ? error.code : null;
|
|
39
|
+
return unavailable(code === 'ENOENT' ? 'log_missing' : 'log_unreadable');
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const before = fs.fstatSync(fd, { bigint: true });
|
|
43
|
+
if (!before.isFile() || before.size > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
44
|
+
fs.closeSync(fd);
|
|
45
|
+
return unavailable('log_not_regular');
|
|
46
|
+
}
|
|
47
|
+
const byteLength = Number(before.size);
|
|
48
|
+
const hashed = hashOpenedEvidence(fd, byteLength);
|
|
49
|
+
const afterHash = fs.fstatSync(fd, { bigint: true });
|
|
50
|
+
if (hashed.bytes !== byteLength || !(0, export_stream_1.sameFileSnapshot)(before, afterHash)) {
|
|
51
|
+
fs.closeSync(fd);
|
|
52
|
+
return { ...unavailable('log_changed_during_hash'), byteLength };
|
|
53
|
+
}
|
|
54
|
+
return { fd, before, byteLength, sha256: hashed.sha256, issue: null };
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
fs.closeSync(fd);
|
|
58
|
+
return unavailable('log_read_failed');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function emptyEvidence() {
|
|
62
|
+
return { fd: null, before: null, byteLength: null, sha256: null, issue: null };
|
|
63
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import crypto = require('crypto');
|
|
2
|
+
import fs = require('fs');
|
|
3
|
+
import path = require('path');
|
|
4
|
+
import { nullableString, sameFileSnapshot } from './export-stream';
|
|
5
|
+
import type { OpenedEvidence, SemanticTask } from './semantic-contract';
|
|
6
|
+
import { expectedLogPath } from './trace-evidence';
|
|
7
|
+
import { TRACE_OUTPUT_CHUNK_BYTES } from './trace-output';
|
|
8
|
+
|
|
9
|
+
function hashOpenedEvidence(fd: number, byteLength: number): { bytes: number; sha256: string } {
|
|
10
|
+
const digest = crypto.createHash('sha256');
|
|
11
|
+
let bytes = 0;
|
|
12
|
+
while (bytes < byteLength) {
|
|
13
|
+
const buffer = Buffer.allocUnsafe(Math.min(TRACE_OUTPUT_CHUNK_BYTES, byteLength - bytes));
|
|
14
|
+
const read = fs.readSync(fd, buffer, 0, buffer.length, bytes);
|
|
15
|
+
if (read === 0) break;
|
|
16
|
+
digest.update(read === buffer.length ? buffer : buffer.subarray(0, read));
|
|
17
|
+
bytes += read;
|
|
18
|
+
}
|
|
19
|
+
return { bytes, sha256: digest.digest('hex') };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function unavailable(issue: string): OpenedEvidence {
|
|
23
|
+
return { fd: null, before: null, byteLength: null, sha256: null, issue };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function openEvidence(
|
|
27
|
+
taskId: string,
|
|
28
|
+
task: SemanticTask,
|
|
29
|
+
allowedLogRoot: string
|
|
30
|
+
): OpenedEvidence {
|
|
31
|
+
const expected = expectedLogPath(allowedLogRoot, taskId);
|
|
32
|
+
const recorded = nullableString(task.logFile);
|
|
33
|
+
if (!expected || !recorded || path.resolve(recorded) !== expected) {
|
|
34
|
+
return unavailable('log_reference_invalid');
|
|
35
|
+
}
|
|
36
|
+
let fd: number;
|
|
37
|
+
try {
|
|
38
|
+
fd = fs.openSync(expected, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0));
|
|
39
|
+
} catch (error) {
|
|
40
|
+
const code = typeof error === 'object' && error !== null && 'code' in error ? error.code : null;
|
|
41
|
+
return unavailable(code === 'ENOENT' ? 'log_missing' : 'log_unreadable');
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const before = fs.fstatSync(fd, { bigint: true });
|
|
45
|
+
if (!before.isFile() || before.size > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
46
|
+
fs.closeSync(fd);
|
|
47
|
+
return unavailable('log_not_regular');
|
|
48
|
+
}
|
|
49
|
+
const byteLength = Number(before.size);
|
|
50
|
+
const hashed = hashOpenedEvidence(fd, byteLength);
|
|
51
|
+
const afterHash = fs.fstatSync(fd, { bigint: true });
|
|
52
|
+
if (hashed.bytes !== byteLength || !sameFileSnapshot(before, afterHash)) {
|
|
53
|
+
fs.closeSync(fd);
|
|
54
|
+
return { ...unavailable('log_changed_during_hash'), byteLength };
|
|
55
|
+
}
|
|
56
|
+
return { fd, before, byteLength, sha256: hashed.sha256, issue: null };
|
|
57
|
+
} catch {
|
|
58
|
+
fs.closeSync(fd);
|
|
59
|
+
return unavailable('log_read_failed');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function emptyEvidence(): OpenedEvidence {
|
|
64
|
+
return { fd: null, before: null, byteLength: null, sha256: null, issue: null };
|
|
65
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const agent_provider_boundary_1 = require("./agent-provider-boundary");
|
|
5
|
+
const export_stream_1 = require("./export-stream");
|
|
6
|
+
const semantic_contract_1 = require("./semantic-contract");
|
|
7
|
+
const semantic_evidence_1 = require("./semantic-evidence");
|
|
8
|
+
const semantic_parser_1 = require("./semantic-parser");
|
|
9
|
+
const trace_evidence_1 = require("./trace-evidence");
|
|
10
|
+
const trace_output_1 = require("./trace-output");
|
|
11
|
+
function assertOutputIsNotTaskLog(outputPath, allowedLogRoot, taskIds) {
|
|
12
|
+
if (!outputPath)
|
|
13
|
+
return;
|
|
14
|
+
const resolvedOutput = path.resolve(outputPath);
|
|
15
|
+
for (const taskId of taskIds) {
|
|
16
|
+
if ((0, trace_evidence_1.expectedLogPath)(allowedLogRoot, taskId) === resolvedOutput) {
|
|
17
|
+
throw new Error('Semantic export output cannot replace a source task log');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function resolveAdapter(provider) {
|
|
22
|
+
if (!provider)
|
|
23
|
+
return null;
|
|
24
|
+
try {
|
|
25
|
+
return (0, agent_provider_boundary_1.getProviderAdapter)(provider);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function taskPrompt(task) {
|
|
32
|
+
if (!task)
|
|
33
|
+
return null;
|
|
34
|
+
return (0, export_stream_1.nullableString)(task.fullPrompt) ?? (0, export_stream_1.nullableString)(task.prompt);
|
|
35
|
+
}
|
|
36
|
+
function writeTaskRecord(options) {
|
|
37
|
+
const { writer, taskId, agentId, task, adapter, evidence } = options;
|
|
38
|
+
writer.write({
|
|
39
|
+
record_type: 'task',
|
|
40
|
+
task_id: taskId,
|
|
41
|
+
agent_id: agentId,
|
|
42
|
+
provider: (0, export_stream_1.nullableString)(task?.provider),
|
|
43
|
+
adapter_id: adapter?.id ?? null,
|
|
44
|
+
adapter_version: adapter?.adapterVersion ?? null,
|
|
45
|
+
model: (0, export_stream_1.nullableString)(task?.model),
|
|
46
|
+
prompt_ref: (0, trace_evidence_1.logicalTaskRef)(taskId, 'prompt'),
|
|
47
|
+
prompt: taskPrompt(task),
|
|
48
|
+
raw_output_ref: (0, trace_evidence_1.logicalTaskRef)(taskId, 'output'),
|
|
49
|
+
raw_output_sha256: evidence.sha256,
|
|
50
|
+
raw_output_byte_length: evidence.byteLength,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function createTaskProjection(writer, taskId, provider, adapter, evidence) {
|
|
54
|
+
return {
|
|
55
|
+
writer,
|
|
56
|
+
taskId,
|
|
57
|
+
provider,
|
|
58
|
+
adapter,
|
|
59
|
+
promptRef: (0, trace_evidence_1.logicalTaskRef)(taskId, 'prompt'),
|
|
60
|
+
rawOutputRef: (0, trace_evidence_1.logicalTaskRef)(taskId, 'output'),
|
|
61
|
+
rawOutputSha256: evidence.sha256,
|
|
62
|
+
eventIndex: 0,
|
|
63
|
+
diagnosticIndex: 0,
|
|
64
|
+
diagnosticsOmitted: 0,
|
|
65
|
+
issueCodes: new Set(),
|
|
66
|
+
logFormat: provider === 'pi' ? 'legacy-pi' : null,
|
|
67
|
+
terminalResultEmitted: false,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function reportInitialIssues(context, options) {
|
|
71
|
+
if (options.agentIds.length > 1)
|
|
72
|
+
(0, semantic_contract_1.reportDiagnostic)(context, 'ambiguous_agent', null);
|
|
73
|
+
if (options.taskIssue) {
|
|
74
|
+
(0, semantic_contract_1.reportDiagnostic)(context, options.taskIssue.split(':').at(-1) || 'task_row_unreadable', null);
|
|
75
|
+
}
|
|
76
|
+
if (options.taskPresent && !options.taskTerminal) {
|
|
77
|
+
(0, semantic_contract_1.reportDiagnostic)(context, 'task_not_terminal', null);
|
|
78
|
+
}
|
|
79
|
+
if (options.evidenceIssue)
|
|
80
|
+
(0, semantic_contract_1.reportDiagnostic)(context, options.evidenceIssue, null);
|
|
81
|
+
if (options.taskPresent && !context.adapter)
|
|
82
|
+
(0, semantic_contract_1.reportDiagnostic)(context, 'unknown_provider', null);
|
|
83
|
+
}
|
|
84
|
+
function streamSemanticTask(options) {
|
|
85
|
+
const { writer, taskId, cause, readTask, allowedLogRoot, footerIssues } = options;
|
|
86
|
+
const taskRead = (0, trace_evidence_1.readTraceTask)(taskId, readTask);
|
|
87
|
+
const task = taskRead.task;
|
|
88
|
+
const provider = (0, export_stream_1.nullableString)(task?.provider);
|
|
89
|
+
const adapter = resolveAdapter(provider);
|
|
90
|
+
const evidence = task ? (0, semantic_evidence_1.openEvidence)(taskId, task, allowedLogRoot) : (0, semantic_evidence_1.emptyEvidence)();
|
|
91
|
+
const agentIds = [...cause.agentIds].sort(export_stream_1.compareText);
|
|
92
|
+
const taskTerminal = task !== null && (0, trace_evidence_1.hasTerminalTaskStatus)(task);
|
|
93
|
+
writeTaskRecord({
|
|
94
|
+
writer,
|
|
95
|
+
taskId,
|
|
96
|
+
agentId: agentIds.length === 1 ? (agentIds[0] ?? null) : null,
|
|
97
|
+
task,
|
|
98
|
+
adapter,
|
|
99
|
+
evidence,
|
|
100
|
+
});
|
|
101
|
+
const context = createTaskProjection(writer, taskId, provider, adapter, evidence);
|
|
102
|
+
reportInitialIssues(context, {
|
|
103
|
+
agentIds,
|
|
104
|
+
taskIssue: taskRead.issue,
|
|
105
|
+
taskPresent: task !== null,
|
|
106
|
+
taskTerminal,
|
|
107
|
+
evidenceIssue: evidence.issue,
|
|
108
|
+
});
|
|
109
|
+
let sourceStable = evidence.issue === null && evidence.fd !== null;
|
|
110
|
+
if (sourceStable && adapter)
|
|
111
|
+
sourceStable = (0, semantic_parser_1.parseOpenedEvidence)(evidence, context, adapter);
|
|
112
|
+
if (evidence.fd !== null)
|
|
113
|
+
fs.closeSync(evidence.fd);
|
|
114
|
+
const sourceComplete = sourceStable && taskTerminal;
|
|
115
|
+
const semanticComplete = sourceComplete && adapter !== null && context.issueCodes.size === 0;
|
|
116
|
+
for (const code of context.issueCodes)
|
|
117
|
+
footerIssues.push(`task:${taskId}:${code}`);
|
|
118
|
+
writer.write({
|
|
119
|
+
record_type: 'task_end',
|
|
120
|
+
task_id: taskId,
|
|
121
|
+
source_complete: sourceComplete,
|
|
122
|
+
semantic_complete: semanticComplete,
|
|
123
|
+
events: context.eventIndex,
|
|
124
|
+
diagnostics: context.diagnosticIndex,
|
|
125
|
+
diagnostics_omitted: context.diagnosticsOmitted,
|
|
126
|
+
});
|
|
127
|
+
return { events: context.eventIndex, diagnostics: context.diagnosticIndex };
|
|
128
|
+
}
|
|
129
|
+
function streamClusterSemanticExport(options) {
|
|
130
|
+
const { ledger, clusterId, readTask, allowedLogRoot, outputPath = null } = options;
|
|
131
|
+
const causes = (0, trace_evidence_1.collectTaskCauses)(ledger, clusterId);
|
|
132
|
+
assertOutputIsNotTaskLog(outputPath, allowedLogRoot, causes.keys());
|
|
133
|
+
const destination = (0, export_stream_1.createExclusiveDestination)(outputPath, options.stdout ?? process.stdout, 'Semantic');
|
|
134
|
+
const writer = (0, export_stream_1.createRecordWriter)(destination);
|
|
135
|
+
const footerIssues = [];
|
|
136
|
+
let events = 0;
|
|
137
|
+
let diagnostics = 0;
|
|
138
|
+
try {
|
|
139
|
+
writer.write({
|
|
140
|
+
record_type: 'header',
|
|
141
|
+
schema_version: semantic_contract_1.SEMANTIC_SCHEMA_VERSION,
|
|
142
|
+
media_type: semantic_contract_1.SEMANTIC_MEDIA_TYPE,
|
|
143
|
+
cluster_id: clusterId,
|
|
144
|
+
bounds: {
|
|
145
|
+
read_chunk_bytes: trace_output_1.TRACE_OUTPUT_CHUNK_BYTES,
|
|
146
|
+
max_line_bytes: semantic_contract_1.SEMANTIC_BOUNDS.maxLineBytes,
|
|
147
|
+
max_event_bytes: semantic_contract_1.SEMANTIC_BOUNDS.maxEventBytes,
|
|
148
|
+
max_string_bytes: semantic_contract_1.SEMANTIC_BOUNDS.maxStringBytes,
|
|
149
|
+
max_value_depth: semantic_contract_1.SEMANTIC_BOUNDS.maxValueDepth,
|
|
150
|
+
max_container_items: semantic_contract_1.SEMANTIC_BOUNDS.maxContainerItems,
|
|
151
|
+
max_value_nodes: semantic_contract_1.SEMANTIC_BOUNDS.maxValueNodes,
|
|
152
|
+
max_events_per_task: semantic_contract_1.SEMANTIC_BOUNDS.maxEventsPerTask,
|
|
153
|
+
max_diagnostics_per_task: semantic_contract_1.SEMANTIC_BOUNDS.maxDiagnosticsPerTask,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
const ordered = [...causes.entries()].sort(([left], [right]) => (0, export_stream_1.compareText)(left, right));
|
|
157
|
+
for (const [taskId, cause] of ordered) {
|
|
158
|
+
const summary = streamSemanticTask({
|
|
159
|
+
writer,
|
|
160
|
+
taskId,
|
|
161
|
+
cause,
|
|
162
|
+
readTask,
|
|
163
|
+
allowedLogRoot,
|
|
164
|
+
footerIssues,
|
|
165
|
+
});
|
|
166
|
+
events += summary.events;
|
|
167
|
+
diagnostics += summary.diagnostics;
|
|
168
|
+
}
|
|
169
|
+
footerIssues.sort(export_stream_1.compareText);
|
|
170
|
+
writer.finish({
|
|
171
|
+
record_type: 'footer',
|
|
172
|
+
complete: footerIssues.length === 0,
|
|
173
|
+
tasks: causes.size,
|
|
174
|
+
events,
|
|
175
|
+
diagnostics,
|
|
176
|
+
issues: footerIssues,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
destination.close();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
module.exports = {
|
|
184
|
+
SEMANTIC_EXPORT_BOUNDS: semantic_contract_1.SEMANTIC_BOUNDS,
|
|
185
|
+
streamClusterSemanticExport,
|
|
186
|
+
};
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import fs = require('fs');
|
|
2
|
+
import path = require('path');
|
|
3
|
+
import { getProviderAdapter, type ProviderAdapter } from './agent-provider-boundary';
|
|
4
|
+
import {
|
|
5
|
+
compareText,
|
|
6
|
+
createExclusiveDestination,
|
|
7
|
+
createRecordWriter,
|
|
8
|
+
nullableString,
|
|
9
|
+
type RecordWriter,
|
|
10
|
+
} from './export-stream';
|
|
11
|
+
import {
|
|
12
|
+
reportDiagnostic,
|
|
13
|
+
SEMANTIC_BOUNDS,
|
|
14
|
+
SEMANTIC_MEDIA_TYPE,
|
|
15
|
+
SEMANTIC_SCHEMA_VERSION,
|
|
16
|
+
type OpenedEvidence,
|
|
17
|
+
type SemanticExportOptions,
|
|
18
|
+
type SemanticTask,
|
|
19
|
+
type TaskProjection,
|
|
20
|
+
} from './semantic-contract';
|
|
21
|
+
import { emptyEvidence, openEvidence } from './semantic-evidence';
|
|
22
|
+
import { parseOpenedEvidence } from './semantic-parser';
|
|
23
|
+
import {
|
|
24
|
+
collectTaskCauses,
|
|
25
|
+
expectedLogPath,
|
|
26
|
+
hasTerminalTaskStatus,
|
|
27
|
+
logicalTaskRef,
|
|
28
|
+
readTraceTask,
|
|
29
|
+
type TaskCause,
|
|
30
|
+
} from './trace-evidence';
|
|
31
|
+
import { TRACE_OUTPUT_CHUNK_BYTES } from './trace-output';
|
|
32
|
+
|
|
33
|
+
interface StreamTaskOptions {
|
|
34
|
+
writer: RecordWriter;
|
|
35
|
+
taskId: string;
|
|
36
|
+
cause: TaskCause;
|
|
37
|
+
readTask: SemanticExportOptions['readTask'];
|
|
38
|
+
allowedLogRoot: string;
|
|
39
|
+
footerIssues: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface TaskRecordOptions {
|
|
43
|
+
writer: RecordWriter;
|
|
44
|
+
taskId: string;
|
|
45
|
+
agentId: string | null;
|
|
46
|
+
task: SemanticTask | null;
|
|
47
|
+
adapter: ProviderAdapter | null;
|
|
48
|
+
evidence: OpenedEvidence;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface InitialIssueOptions {
|
|
52
|
+
agentIds: readonly string[];
|
|
53
|
+
taskIssue: string | null;
|
|
54
|
+
taskPresent: boolean;
|
|
55
|
+
taskTerminal: boolean;
|
|
56
|
+
evidenceIssue: string | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function assertOutputIsNotTaskLog(
|
|
60
|
+
outputPath: string | null | undefined,
|
|
61
|
+
allowedLogRoot: string,
|
|
62
|
+
taskIds: Iterable<string>
|
|
63
|
+
): void {
|
|
64
|
+
if (!outputPath) return;
|
|
65
|
+
const resolvedOutput = path.resolve(outputPath);
|
|
66
|
+
for (const taskId of taskIds) {
|
|
67
|
+
if (expectedLogPath(allowedLogRoot, taskId) === resolvedOutput) {
|
|
68
|
+
throw new Error('Semantic export output cannot replace a source task log');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolveAdapter(provider: string | null): ProviderAdapter | null {
|
|
74
|
+
if (!provider) return null;
|
|
75
|
+
try {
|
|
76
|
+
return getProviderAdapter(provider);
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function taskPrompt(task: SemanticTask | null): string | null {
|
|
83
|
+
if (!task) return null;
|
|
84
|
+
return nullableString(task.fullPrompt) ?? nullableString(task.prompt);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function writeTaskRecord(options: TaskRecordOptions): void {
|
|
88
|
+
const { writer, taskId, agentId, task, adapter, evidence } = options;
|
|
89
|
+
writer.write({
|
|
90
|
+
record_type: 'task',
|
|
91
|
+
task_id: taskId,
|
|
92
|
+
agent_id: agentId,
|
|
93
|
+
provider: nullableString(task?.provider),
|
|
94
|
+
adapter_id: adapter?.id ?? null,
|
|
95
|
+
adapter_version: adapter?.adapterVersion ?? null,
|
|
96
|
+
model: nullableString(task?.model),
|
|
97
|
+
prompt_ref: logicalTaskRef(taskId, 'prompt'),
|
|
98
|
+
prompt: taskPrompt(task),
|
|
99
|
+
raw_output_ref: logicalTaskRef(taskId, 'output'),
|
|
100
|
+
raw_output_sha256: evidence.sha256,
|
|
101
|
+
raw_output_byte_length: evidence.byteLength,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function createTaskProjection(
|
|
106
|
+
writer: RecordWriter,
|
|
107
|
+
taskId: string,
|
|
108
|
+
provider: string | null,
|
|
109
|
+
adapter: ProviderAdapter | null,
|
|
110
|
+
evidence: OpenedEvidence
|
|
111
|
+
): TaskProjection {
|
|
112
|
+
return {
|
|
113
|
+
writer,
|
|
114
|
+
taskId,
|
|
115
|
+
provider,
|
|
116
|
+
adapter,
|
|
117
|
+
promptRef: logicalTaskRef(taskId, 'prompt'),
|
|
118
|
+
rawOutputRef: logicalTaskRef(taskId, 'output'),
|
|
119
|
+
rawOutputSha256: evidence.sha256,
|
|
120
|
+
eventIndex: 0,
|
|
121
|
+
diagnosticIndex: 0,
|
|
122
|
+
diagnosticsOmitted: 0,
|
|
123
|
+
issueCodes: new Set<string>(),
|
|
124
|
+
logFormat: provider === 'pi' ? 'legacy-pi' : null,
|
|
125
|
+
terminalResultEmitted: false,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function reportInitialIssues(context: TaskProjection, options: InitialIssueOptions): void {
|
|
130
|
+
if (options.agentIds.length > 1) reportDiagnostic(context, 'ambiguous_agent', null);
|
|
131
|
+
if (options.taskIssue) {
|
|
132
|
+
reportDiagnostic(context, options.taskIssue.split(':').at(-1) || 'task_row_unreadable', null);
|
|
133
|
+
}
|
|
134
|
+
if (options.taskPresent && !options.taskTerminal) {
|
|
135
|
+
reportDiagnostic(context, 'task_not_terminal', null);
|
|
136
|
+
}
|
|
137
|
+
if (options.evidenceIssue) reportDiagnostic(context, options.evidenceIssue, null);
|
|
138
|
+
if (options.taskPresent && !context.adapter) reportDiagnostic(context, 'unknown_provider', null);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function streamSemanticTask(options: StreamTaskOptions): { events: number; diagnostics: number } {
|
|
142
|
+
const { writer, taskId, cause, readTask, allowedLogRoot, footerIssues } = options;
|
|
143
|
+
const taskRead = readTraceTask(taskId, readTask);
|
|
144
|
+
const task = taskRead.task as SemanticTask | null;
|
|
145
|
+
const provider = nullableString(task?.provider);
|
|
146
|
+
const adapter = resolveAdapter(provider);
|
|
147
|
+
const evidence = task ? openEvidence(taskId, task, allowedLogRoot) : emptyEvidence();
|
|
148
|
+
const agentIds = [...cause.agentIds].sort(compareText);
|
|
149
|
+
const taskTerminal = task !== null && hasTerminalTaskStatus(task);
|
|
150
|
+
writeTaskRecord({
|
|
151
|
+
writer,
|
|
152
|
+
taskId,
|
|
153
|
+
agentId: agentIds.length === 1 ? (agentIds[0] ?? null) : null,
|
|
154
|
+
task,
|
|
155
|
+
adapter,
|
|
156
|
+
evidence,
|
|
157
|
+
});
|
|
158
|
+
const context = createTaskProjection(writer, taskId, provider, adapter, evidence);
|
|
159
|
+
reportInitialIssues(context, {
|
|
160
|
+
agentIds,
|
|
161
|
+
taskIssue: taskRead.issue,
|
|
162
|
+
taskPresent: task !== null,
|
|
163
|
+
taskTerminal,
|
|
164
|
+
evidenceIssue: evidence.issue,
|
|
165
|
+
});
|
|
166
|
+
let sourceStable = evidence.issue === null && evidence.fd !== null;
|
|
167
|
+
if (sourceStable && adapter) sourceStable = parseOpenedEvidence(evidence, context, adapter);
|
|
168
|
+
if (evidence.fd !== null) fs.closeSync(evidence.fd);
|
|
169
|
+
const sourceComplete = sourceStable && taskTerminal;
|
|
170
|
+
const semanticComplete = sourceComplete && adapter !== null && context.issueCodes.size === 0;
|
|
171
|
+
for (const code of context.issueCodes) footerIssues.push(`task:${taskId}:${code}`);
|
|
172
|
+
writer.write({
|
|
173
|
+
record_type: 'task_end',
|
|
174
|
+
task_id: taskId,
|
|
175
|
+
source_complete: sourceComplete,
|
|
176
|
+
semantic_complete: semanticComplete,
|
|
177
|
+
events: context.eventIndex,
|
|
178
|
+
diagnostics: context.diagnosticIndex,
|
|
179
|
+
diagnostics_omitted: context.diagnosticsOmitted,
|
|
180
|
+
});
|
|
181
|
+
return { events: context.eventIndex, diagnostics: context.diagnosticIndex };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function streamClusterSemanticExport(options: SemanticExportOptions): void {
|
|
185
|
+
const { ledger, clusterId, readTask, allowedLogRoot, outputPath = null } = options;
|
|
186
|
+
const causes = collectTaskCauses(ledger, clusterId);
|
|
187
|
+
assertOutputIsNotTaskLog(outputPath, allowedLogRoot, causes.keys());
|
|
188
|
+
const destination = createExclusiveDestination(
|
|
189
|
+
outputPath,
|
|
190
|
+
options.stdout ?? process.stdout,
|
|
191
|
+
'Semantic'
|
|
192
|
+
);
|
|
193
|
+
const writer = createRecordWriter(destination);
|
|
194
|
+
const footerIssues: string[] = [];
|
|
195
|
+
let events = 0;
|
|
196
|
+
let diagnostics = 0;
|
|
197
|
+
try {
|
|
198
|
+
writer.write({
|
|
199
|
+
record_type: 'header',
|
|
200
|
+
schema_version: SEMANTIC_SCHEMA_VERSION,
|
|
201
|
+
media_type: SEMANTIC_MEDIA_TYPE,
|
|
202
|
+
cluster_id: clusterId,
|
|
203
|
+
bounds: {
|
|
204
|
+
read_chunk_bytes: TRACE_OUTPUT_CHUNK_BYTES,
|
|
205
|
+
max_line_bytes: SEMANTIC_BOUNDS.maxLineBytes,
|
|
206
|
+
max_event_bytes: SEMANTIC_BOUNDS.maxEventBytes,
|
|
207
|
+
max_string_bytes: SEMANTIC_BOUNDS.maxStringBytes,
|
|
208
|
+
max_value_depth: SEMANTIC_BOUNDS.maxValueDepth,
|
|
209
|
+
max_container_items: SEMANTIC_BOUNDS.maxContainerItems,
|
|
210
|
+
max_value_nodes: SEMANTIC_BOUNDS.maxValueNodes,
|
|
211
|
+
max_events_per_task: SEMANTIC_BOUNDS.maxEventsPerTask,
|
|
212
|
+
max_diagnostics_per_task: SEMANTIC_BOUNDS.maxDiagnosticsPerTask,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
const ordered = [...causes.entries()].sort(([left], [right]) => compareText(left, right));
|
|
216
|
+
for (const [taskId, cause] of ordered) {
|
|
217
|
+
const summary = streamSemanticTask({
|
|
218
|
+
writer,
|
|
219
|
+
taskId,
|
|
220
|
+
cause,
|
|
221
|
+
readTask,
|
|
222
|
+
allowedLogRoot,
|
|
223
|
+
footerIssues,
|
|
224
|
+
});
|
|
225
|
+
events += summary.events;
|
|
226
|
+
diagnostics += summary.diagnostics;
|
|
227
|
+
}
|
|
228
|
+
footerIssues.sort(compareText);
|
|
229
|
+
writer.finish({
|
|
230
|
+
record_type: 'footer',
|
|
231
|
+
complete: footerIssues.length === 0,
|
|
232
|
+
tasks: causes.size,
|
|
233
|
+
events,
|
|
234
|
+
diagnostics,
|
|
235
|
+
issues: footerIssues,
|
|
236
|
+
});
|
|
237
|
+
} finally {
|
|
238
|
+
destination.close();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export = {
|
|
243
|
+
SEMANTIC_EXPORT_BOUNDS: SEMANTIC_BOUNDS,
|
|
244
|
+
streamClusterSemanticExport,
|
|
245
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SemanticProjectionError = void 0;
|
|
4
|
+
exports.normalizeUnknown = normalizeUnknown;
|
|
5
|
+
const export_stream_1 = require("./export-stream");
|
|
6
|
+
const semantic_contract_1 = require("./semantic-contract");
|
|
7
|
+
class SemanticProjectionError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
constructor(code) {
|
|
10
|
+
super(code);
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.SemanticProjectionError = SemanticProjectionError;
|
|
15
|
+
function isPlainRecord(value) {
|
|
16
|
+
const prototype = Reflect.getPrototypeOf(value);
|
|
17
|
+
return prototype === Object.prototype || prototype === null;
|
|
18
|
+
}
|
|
19
|
+
function normalizeJsonValue(value, depth, budget) {
|
|
20
|
+
budget.nodes += 1;
|
|
21
|
+
if (budget.nodes > semantic_contract_1.MAX_VALUE_NODES)
|
|
22
|
+
throw new SemanticProjectionError('event_shape_too_large');
|
|
23
|
+
if (value === null || value === undefined)
|
|
24
|
+
return null;
|
|
25
|
+
if (typeof value === 'string') {
|
|
26
|
+
if (Buffer.byteLength(value) > semantic_contract_1.MAX_STRING_BYTES) {
|
|
27
|
+
throw new SemanticProjectionError('event_string_too_large');
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === 'boolean')
|
|
32
|
+
return value;
|
|
33
|
+
if (typeof value === 'number') {
|
|
34
|
+
if (!Number.isFinite(value))
|
|
35
|
+
throw new SemanticProjectionError('event_value_not_json');
|
|
36
|
+
return Object.is(value, -0) ? 0 : value;
|
|
37
|
+
}
|
|
38
|
+
if (typeof value !== 'object')
|
|
39
|
+
throw new SemanticProjectionError('event_value_not_json');
|
|
40
|
+
if (depth >= semantic_contract_1.MAX_VALUE_DEPTH)
|
|
41
|
+
throw new SemanticProjectionError('event_shape_too_deep');
|
|
42
|
+
if (budget.seen.has(value))
|
|
43
|
+
throw new SemanticProjectionError('event_value_not_json');
|
|
44
|
+
budget.seen.add(value);
|
|
45
|
+
try {
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
if (value.length > semantic_contract_1.MAX_CONTAINER_ITEMS) {
|
|
48
|
+
throw new SemanticProjectionError('event_shape_too_wide');
|
|
49
|
+
}
|
|
50
|
+
return value.map((item) => normalizeJsonValue(item, depth + 1, budget));
|
|
51
|
+
}
|
|
52
|
+
if (!isPlainRecord(value))
|
|
53
|
+
throw new SemanticProjectionError('event_value_not_json');
|
|
54
|
+
const keys = Object.keys(value).sort(export_stream_1.compareText);
|
|
55
|
+
if (keys.length > semantic_contract_1.MAX_CONTAINER_ITEMS) {
|
|
56
|
+
throw new SemanticProjectionError('event_shape_too_wide');
|
|
57
|
+
}
|
|
58
|
+
const normalized = {};
|
|
59
|
+
for (const key of keys)
|
|
60
|
+
normalized[key] = normalizeJsonValue(value[key], depth + 1, budget);
|
|
61
|
+
return normalized;
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
budget.seen.delete(value);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function normalizeUnknown(value) {
|
|
68
|
+
return normalizeJsonValue(value, 0, { nodes: 0, seen: new WeakSet() });
|
|
69
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { compareText } from './export-stream';
|
|
2
|
+
import {
|
|
3
|
+
MAX_CONTAINER_ITEMS,
|
|
4
|
+
MAX_STRING_BYTES,
|
|
5
|
+
MAX_VALUE_DEPTH,
|
|
6
|
+
MAX_VALUE_NODES,
|
|
7
|
+
} from './semantic-contract';
|
|
8
|
+
|
|
9
|
+
interface JsonBudget {
|
|
10
|
+
nodes: number;
|
|
11
|
+
seen: WeakSet<object>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class SemanticProjectionError extends Error {
|
|
15
|
+
readonly code: string;
|
|
16
|
+
|
|
17
|
+
constructor(code: string) {
|
|
18
|
+
super(code);
|
|
19
|
+
this.code = code;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isPlainRecord(value: object): value is Record<string, unknown> {
|
|
24
|
+
const prototype: object | null = Reflect.getPrototypeOf(value);
|
|
25
|
+
return prototype === Object.prototype || prototype === null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeJsonValue(value: unknown, depth: number, budget: JsonBudget): unknown {
|
|
29
|
+
budget.nodes += 1;
|
|
30
|
+
if (budget.nodes > MAX_VALUE_NODES) throw new SemanticProjectionError('event_shape_too_large');
|
|
31
|
+
if (value === null || value === undefined) return null;
|
|
32
|
+
if (typeof value === 'string') {
|
|
33
|
+
if (Buffer.byteLength(value) > MAX_STRING_BYTES) {
|
|
34
|
+
throw new SemanticProjectionError('event_string_too_large');
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
if (typeof value === 'boolean') return value;
|
|
39
|
+
if (typeof value === 'number') {
|
|
40
|
+
if (!Number.isFinite(value)) throw new SemanticProjectionError('event_value_not_json');
|
|
41
|
+
return Object.is(value, -0) ? 0 : value;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value !== 'object') throw new SemanticProjectionError('event_value_not_json');
|
|
44
|
+
if (depth >= MAX_VALUE_DEPTH) throw new SemanticProjectionError('event_shape_too_deep');
|
|
45
|
+
if (budget.seen.has(value)) throw new SemanticProjectionError('event_value_not_json');
|
|
46
|
+
budget.seen.add(value);
|
|
47
|
+
try {
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
if (value.length > MAX_CONTAINER_ITEMS) {
|
|
50
|
+
throw new SemanticProjectionError('event_shape_too_wide');
|
|
51
|
+
}
|
|
52
|
+
return value.map((item) => normalizeJsonValue(item, depth + 1, budget));
|
|
53
|
+
}
|
|
54
|
+
if (!isPlainRecord(value)) throw new SemanticProjectionError('event_value_not_json');
|
|
55
|
+
const keys = Object.keys(value).sort(compareText);
|
|
56
|
+
if (keys.length > MAX_CONTAINER_ITEMS) {
|
|
57
|
+
throw new SemanticProjectionError('event_shape_too_wide');
|
|
58
|
+
}
|
|
59
|
+
const normalized: Record<string, unknown> = {};
|
|
60
|
+
for (const key of keys) normalized[key] = normalizeJsonValue(value[key], depth + 1, budget);
|
|
61
|
+
return normalized;
|
|
62
|
+
} finally {
|
|
63
|
+
budget.seen.delete(value);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function normalizeUnknown(value: unknown): unknown {
|
|
68
|
+
return normalizeJsonValue(value, 0, { nodes: 0, seen: new WeakSet<object>() });
|
|
69
|
+
}
|