@evomap/evolver-core 2.0.0-beta.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/dist/algo/antiDistill.d.ts +35 -0
- package/dist/algo/antiDistill.js +167 -0
- package/dist/algo/bans.d.ts +6 -0
- package/dist/algo/bans.js +33 -0
- package/dist/algo/candidateAssembly.d.ts +65 -0
- package/dist/algo/candidateAssembly.js +196 -0
- package/dist/algo/capabilityCandidates.d.ts +38 -0
- package/dist/algo/capabilityCandidates.js +131 -0
- package/dist/algo/confidence.d.ts +106 -0
- package/dist/algo/confidence.js +196 -0
- package/dist/algo/cycleEngine.d.ts +143 -0
- package/dist/algo/cycleEngine.js +417 -0
- package/dist/algo/cycleFailureClassifier.d.ts +52 -0
- package/dist/algo/cycleFailureClassifier.js +104 -0
- package/dist/algo/epigenetics.d.ts +14 -0
- package/dist/algo/epigenetics.js +41 -0
- package/dist/algo/evolutionEvent.d.ts +23 -0
- package/dist/algo/evolutionEvent.js +24 -0
- package/dist/algo/exploration.d.ts +34 -0
- package/dist/algo/exploration.js +55 -0
- package/dist/algo/geneHealth.d.ts +24 -0
- package/dist/algo/geneHealth.js +16 -0
- package/dist/algo/geneIntake.d.ts +37 -0
- package/dist/algo/geneIntake.js +100 -0
- package/dist/algo/genePromotion.d.ts +48 -0
- package/dist/algo/genePromotion.js +53 -0
- package/dist/algo/geneSelection.d.ts +128 -0
- package/dist/algo/geneSelection.js +222 -0
- package/dist/algo/index.d.ts +18 -0
- package/dist/algo/index.js +18 -0
- package/dist/algo/mutation.d.ts +12 -0
- package/dist/algo/mutation.js +22 -0
- package/dist/algo/orchestrator.d.ts +65 -0
- package/dist/algo/orchestrator.js +63 -0
- package/dist/algo/solidify.d.ts +43 -0
- package/dist/algo/solidify.js +63 -0
- package/dist/algo/strategyPresets.d.ts +32 -0
- package/dist/algo/strategyPresets.js +61 -0
- package/dist/assetstore/index.d.ts +8 -0
- package/dist/assetstore/index.js +8 -0
- package/dist/assetstore/learningHistory.d.ts +25 -0
- package/dist/assetstore/learningHistory.js +38 -0
- package/dist/assetstore/localJsonl.d.ts +34 -0
- package/dist/assetstore/localJsonl.js +182 -0
- package/dist/assetstore/pendingSignals.d.ts +22 -0
- package/dist/assetstore/pendingSignals.js +171 -0
- package/dist/assetstore/provenance.d.ts +39 -0
- package/dist/assetstore/provenance.js +109 -0
- package/dist/assetstore/provider.d.ts +48 -0
- package/dist/assetstore/provider.js +36 -0
- package/dist/assetstore/remoteStub.d.ts +32 -0
- package/dist/assetstore/remoteStub.js +36 -0
- package/dist/assetstore/reviewFilter.d.ts +20 -0
- package/dist/assetstore/reviewFilter.js +44 -0
- package/dist/assetstore/reviewLedger.d.ts +55 -0
- package/dist/assetstore/reviewLedger.js +121 -0
- package/dist/benchmark/antiGeneBenchmark.d.ts +79 -0
- package/dist/benchmark/antiGeneBenchmark.js +241 -0
- package/dist/benchmark/antiGeneRollout.d.ts +80 -0
- package/dist/benchmark/antiGeneRollout.js +256 -0
- package/dist/benchmark/benchmark.d.ts +32 -0
- package/dist/benchmark/benchmark.js +26 -0
- package/dist/benchmark/evolutionThesisSolver.d.ts +25 -0
- package/dist/benchmark/evolutionThesisSolver.js +41 -0
- package/dist/benchmark/index.d.ts +5 -0
- package/dist/benchmark/index.js +5 -0
- package/dist/benchmark/thesis.d.ts +85 -0
- package/dist/benchmark/thesis.js +150 -0
- package/dist/bootstrap/envFingerprint.d.ts +45 -0
- package/dist/bootstrap/envFingerprint.js +72 -0
- package/dist/bootstrap/index.d.ts +2 -0
- package/dist/bootstrap/index.js +2 -0
- package/dist/bootstrap/selfEvolve.d.ts +36 -0
- package/dist/bootstrap/selfEvolve.js +26 -0
- package/dist/cycle/cycleTimeline.d.ts +35 -0
- package/dist/cycle/cycleTimeline.js +74 -0
- package/dist/cycle/index.d.ts +2 -0
- package/dist/cycle/index.js +2 -0
- package/dist/cycle/stateMachine.d.ts +6 -0
- package/dist/cycle/stateMachine.js +25 -0
- package/dist/daemon/cycleContext.d.ts +12 -0
- package/dist/daemon/cycleContext.js +13 -0
- package/dist/daemon/drain.d.ts +11 -0
- package/dist/daemon/drain.js +16 -0
- package/dist/daemon/guards.d.ts +87 -0
- package/dist/daemon/guards.js +142 -0
- package/dist/daemon/idempotency.d.ts +11 -0
- package/dist/daemon/idempotency.js +36 -0
- package/dist/daemon/idleScheduler.d.ts +55 -0
- package/dist/daemon/idleScheduler.js +181 -0
- package/dist/daemon/index.d.ts +8 -0
- package/dist/daemon/index.js +8 -0
- package/dist/daemon/stepRunner.d.ts +12 -0
- package/dist/daemon/stepRunner.js +31 -0
- package/dist/daemon/unhandledRejectionWindow.d.ts +21 -0
- package/dist/daemon/unhandledRejectionWindow.js +52 -0
- package/dist/daemon/watchdog.d.ts +11 -0
- package/dist/daemon/watchdog.js +14 -0
- package/dist/events/eventSchema.d.ts +132 -0
- package/dist/events/eventSchema.js +30 -0
- package/dist/events/eventStore.d.ts +32 -0
- package/dist/events/eventStore.js +137 -0
- package/dist/events/ingest.d.ts +34 -0
- package/dist/events/ingest.js +61 -0
- package/dist/events/paths.d.ts +22 -0
- package/dist/events/paths.js +44 -0
- package/dist/events/projectors.d.ts +9 -0
- package/dist/events/projectors.js +11 -0
- package/dist/events/public.d.ts +14 -0
- package/dist/events/public.js +8 -0
- package/dist/events/replayer.d.ts +26 -0
- package/dist/events/replayer.js +67 -0
- package/dist/events/reports.d.ts +90 -0
- package/dist/events/reports.js +244 -0
- package/dist/events/retention.d.ts +70 -0
- package/dist/events/retention.js +177 -0
- package/dist/events/sink.d.ts +5 -0
- package/dist/events/sink.js +1 -0
- package/dist/exec/autoExec.d.ts +137 -0
- package/dist/exec/autoExec.js +221 -0
- package/dist/exec/autonomousCycle.d.ts +48 -0
- package/dist/exec/autonomousCycle.js +78 -0
- package/dist/exec/claudeBridge.d.ts +112 -0
- package/dist/exec/claudeBridge.js +219 -0
- package/dist/exec/index.d.ts +9 -0
- package/dist/exec/index.js +11 -0
- package/dist/exec/openPrRegistry.d.ts +60 -0
- package/dist/exec/openPrRegistry.js +130 -0
- package/dist/exec/policy/blastRadius.d.ts +31 -0
- package/dist/exec/policy/blastRadius.js +67 -0
- package/dist/exec/policy/constraints.d.ts +26 -0
- package/dist/exec/policy/constraints.js +62 -0
- package/dist/exec/policy/destructive.d.ts +45 -0
- package/dist/exec/policy/destructive.js +108 -0
- package/dist/exec/policy/failureMode.d.ts +33 -0
- package/dist/exec/policy/failureMode.js +49 -0
- package/dist/exec/policy/index.d.ts +24 -0
- package/dist/exec/policy/index.js +30 -0
- package/dist/exec/policy/protectedPaths.d.ts +17 -0
- package/dist/exec/policy/protectedPaths.js +65 -0
- package/dist/exec/policyCheck.d.ts +2 -0
- package/dist/exec/policyCheck.js +7 -0
- package/dist/exec/prompt.d.ts +34 -0
- package/dist/exec/prompt.js +108 -0
- package/dist/exec/proofOfWork.d.ts +9 -0
- package/dist/exec/proofOfWork.js +11 -0
- package/dist/exec/runnerRegistry.d.ts +123 -0
- package/dist/exec/runnerRegistry.js +237 -0
- package/dist/exec/selfPr.d.ts +83 -0
- package/dist/exec/selfPr.js +104 -0
- package/dist/exec/selfPrObfuscation.d.ts +21 -0
- package/dist/exec/selfPrObfuscation.js +85 -0
- package/dist/hooks/hooks.d.ts +117 -0
- package/dist/hooks/hooks.js +89 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hub/assetCallLog.d.ts +43 -0
- package/dist/hub/assetCallLog.js +72 -0
- package/dist/hub/bindings.d.ts +28 -0
- package/dist/hub/bindings.js +90 -0
- package/dist/hub/capability.d.ts +278 -0
- package/dist/hub/capability.js +1 -0
- package/dist/hub/conversationDistiller.d.ts +88 -0
- package/dist/hub/conversationDistiller.js +264 -0
- package/dist/hub/fake.d.ts +45 -0
- package/dist/hub/fake.js +69 -0
- package/dist/hub/hubReview.d.ts +91 -0
- package/dist/hub/hubReview.js +106 -0
- package/dist/hub/index.d.ts +10 -0
- package/dist/hub/index.js +10 -0
- package/dist/hub/ingest.d.ts +15 -0
- package/dist/hub/ingest.js +15 -0
- package/dist/hub/questionGenerator.d.ts +59 -0
- package/dist/hub/questionGenerator.js +406 -0
- package/dist/hub/reuseDecision.d.ts +87 -0
- package/dist/hub/reuseDecision.js +127 -0
- package/dist/hub/sanitize.d.ts +52 -0
- package/dist/hub/sanitize.js +321 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +27 -0
- package/dist/mailbox/catalog.d.ts +17 -0
- package/dist/mailbox/catalog.js +53 -0
- package/dist/mailbox/daemon.d.ts +48 -0
- package/dist/mailbox/daemon.js +61 -0
- package/dist/mailbox/dispatch.d.ts +46 -0
- package/dist/mailbox/dispatch.js +62 -0
- package/dist/mailbox/envelope.d.ts +43 -0
- package/dist/mailbox/envelope.js +52 -0
- package/dist/mailbox/index.d.ts +8 -0
- package/dist/mailbox/index.js +8 -0
- package/dist/mailbox/ipcServer.d.ts +46 -0
- package/dist/mailbox/ipcServer.js +168 -0
- package/dist/mailbox/requestReply.d.ts +40 -0
- package/dist/mailbox/requestReply.js +77 -0
- package/dist/mailbox/store.d.ts +74 -0
- package/dist/mailbox/store.js +308 -0
- package/dist/mailbox/wake.d.ts +45 -0
- package/dist/mailbox/wake.js +60 -0
- package/dist/material/boundary.d.ts +7 -0
- package/dist/material/boundary.js +14 -0
- package/dist/material/consumer.d.ts +21 -0
- package/dist/material/consumer.js +52 -0
- package/dist/material/emit.d.ts +55 -0
- package/dist/material/emit.js +52 -0
- package/dist/material/factory.d.ts +27 -0
- package/dist/material/factory.js +25 -0
- package/dist/material/index.d.ts +8 -0
- package/dist/material/index.js +8 -0
- package/dist/material/materialStore.d.ts +24 -0
- package/dist/material/materialStore.js +76 -0
- package/dist/material/sampling.d.ts +21 -0
- package/dist/material/sampling.js +39 -0
- package/dist/material/sources.d.ts +25 -0
- package/dist/material/sources.js +33 -0
- package/dist/material/watermark.d.ts +21 -0
- package/dist/material/watermark.js +76 -0
- package/dist/observers/cursorRewriteObserver.d.ts +46 -0
- package/dist/observers/cursorRewriteObserver.js +103 -0
- package/dist/observers/distillObserver.d.ts +31 -0
- package/dist/observers/distillObserver.js +93 -0
- package/dist/observers/index.d.ts +5 -0
- package/dist/observers/index.js +5 -0
- package/dist/observers/observerBus.d.ts +44 -0
- package/dist/observers/observerBus.js +73 -0
- package/dist/observers/reflectionObserver.d.ts +31 -0
- package/dist/observers/reflectionObserver.js +124 -0
- package/dist/observers/valueDigestObserver.d.ts +50 -0
- package/dist/observers/valueDigestObserver.js +70 -0
- package/dist/ops/cleanup.d.ts +23 -0
- package/dist/ops/cleanup.js +68 -0
- package/dist/ops/health.d.ts +37 -0
- package/dist/ops/health.js +96 -0
- package/dist/ops/index.d.ts +7 -0
- package/dist/ops/index.js +7 -0
- package/dist/ops/recall.d.ts +65 -0
- package/dist/ops/recall.js +152 -0
- package/dist/ops/reuseOutcomes.d.ts +68 -0
- package/dist/ops/reuseOutcomes.js +126 -0
- package/dist/ops/savingsCore.d.ts +47 -0
- package/dist/ops/savingsCore.js +112 -0
- package/dist/ops/selfUpdate.d.ts +106 -0
- package/dist/ops/selfUpdate.js +294 -0
- package/dist/ops/valueLedger.d.ts +205 -0
- package/dist/ops/valueLedger.js +238 -0
- package/dist/ops/valueOutreach.d.ts +70 -0
- package/dist/ops/valueOutreach.js +244 -0
- package/dist/personality/drift.d.ts +45 -0
- package/dist/personality/drift.js +106 -0
- package/dist/personality/events.d.ts +25 -0
- package/dist/personality/events.js +37 -0
- package/dist/personality/evolveOps.d.ts +20 -0
- package/dist/personality/evolveOps.js +93 -0
- package/dist/personality/index.d.ts +11 -0
- package/dist/personality/index.js +11 -0
- package/dist/personality/mutate.d.ts +21 -0
- package/dist/personality/mutate.js +39 -0
- package/dist/personality/pivot.d.ts +23 -0
- package/dist/personality/pivot.js +22 -0
- package/dist/personality/prompt.d.ts +6 -0
- package/dist/personality/prompt.js +62 -0
- package/dist/personality/riskGate.d.ts +42 -0
- package/dist/personality/riskGate.js +80 -0
- package/dist/personality/schema.d.ts +255 -0
- package/dist/personality/schema.js +119 -0
- package/dist/personality/select.d.ts +46 -0
- package/dist/personality/select.js +71 -0
- package/dist/personality/stats.d.ts +46 -0
- package/dist/personality/stats.js +82 -0
- package/dist/personality/store.d.ts +35 -0
- package/dist/personality/store.js +92 -0
- package/dist/schema/common.d.ts +28 -0
- package/dist/schema/common.js +17 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.js +5 -0
- package/dist/schema/material.d.ts +201 -0
- package/dist/schema/material.js +50 -0
- package/dist/schema/problem.d.ts +160 -0
- package/dist/schema/problem.js +45 -0
- package/dist/schema/proofOfWork.d.ts +97 -0
- package/dist/schema/proofOfWork.js +9 -0
- package/dist/schema/signal.d.ts +41 -0
- package/dist/schema/signal.js +19 -0
- package/dist/shadow/index.d.ts +6 -0
- package/dist/shadow/index.js +6 -0
- package/dist/shadow/jsonlSink.d.ts +17 -0
- package/dist/shadow/jsonlSink.js +27 -0
- package/dist/shadow/redact.d.ts +11 -0
- package/dist/shadow/redact.js +18 -0
- package/dist/shadow/shadowHub.d.ts +12 -0
- package/dist/shadow/shadowHub.js +42 -0
- package/dist/shadow/shadowMailbox.d.ts +17 -0
- package/dist/shadow/shadowMailbox.js +25 -0
- package/dist/shadow/shadowStore.d.ts +18 -0
- package/dist/shadow/shadowStore.js +55 -0
- package/dist/shadow/sink.d.ts +37 -0
- package/dist/shadow/sink.js +18 -0
- package/dist/signals/cycleHistoryFromEvents.d.ts +11 -0
- package/dist/signals/cycleHistoryFromEvents.js +96 -0
- package/dist/signals/expand.d.ts +34 -0
- package/dist/signals/expand.js +125 -0
- package/dist/signals/extractor.d.ts +25 -0
- package/dist/signals/extractor.js +57 -0
- package/dist/signals/index.d.ts +6 -0
- package/dist/signals/index.js +6 -0
- package/dist/signals/metaSignals.d.ts +57 -0
- package/dist/signals/metaSignals.js +158 -0
- package/dist/signals/signalGate.d.ts +15 -0
- package/dist/signals/signalGate.js +40 -0
- package/dist/signals/traceSignals.d.ts +44 -0
- package/dist/signals/traceSignals.js +123 -0
- package/dist/signatures/classifier.d.ts +16 -0
- package/dist/signatures/classifier.js +29 -0
- package/dist/signatures/index.d.ts +2 -0
- package/dist/signatures/index.js +2 -0
- package/dist/signatures/signatures.d.ts +22 -0
- package/dist/signatures/signatures.js +42 -0
- package/dist/strategy/experiment.d.ts +48 -0
- package/dist/strategy/experiment.js +63 -0
- package/dist/strategy/index.d.ts +2 -0
- package/dist/strategy/index.js +2 -0
- package/dist/strategy/strategyPoint.d.ts +30 -0
- package/dist/strategy/strategyPoint.js +23 -0
- package/dist/trace/index.d.ts +2 -0
- package/dist/trace/index.js +2 -0
- package/dist/trace/trajectory.d.ts +82 -0
- package/dist/trace/trajectory.js +117 -0
- package/dist/trace/trajectoryExport.d.ts +237 -0
- package/dist/trace/trajectoryExport.js +1818 -0
- package/dist/trigger/budget.d.ts +16 -0
- package/dist/trigger/budget.js +20 -0
- package/dist/trigger/engine.d.ts +12 -0
- package/dist/trigger/engine.js +23 -0
- package/dist/trigger/index.d.ts +6 -0
- package/dist/trigger/index.js +6 -0
- package/dist/trigger/problemBuilder.d.ts +24 -0
- package/dist/trigger/problemBuilder.js +32 -0
- package/dist/trigger/trigger.d.ts +22 -0
- package/dist/trigger/trigger.js +27 -0
- package/dist/trigger/valueModel.d.ts +26 -0
- package/dist/trigger/valueModel.js +25 -0
- package/dist/trigger/wfq.d.ts +13 -0
- package/dist/trigger/wfq.js +25 -0
- package/dist/util/fileLock.d.ts +24 -0
- package/dist/util/fileLock.js +227 -0
- package/dist/util/index.d.ts +2 -0
- package/dist/util/index.js +2 -0
- package/dist/util/version.d.ts +12 -0
- package/dist/util/version.js +51 -0
- package/dist/verify/index.d.ts +4 -0
- package/dist/verify/index.js +4 -0
- package/dist/verify/rollback.d.ts +22 -0
- package/dist/verify/rollback.js +33 -0
- package/dist/verify/sandboxRunner.d.ts +39 -0
- package/dist/verify/sandboxRunner.js +116 -0
- package/dist/verify/sandboxedValidation.d.ts +35 -0
- package/dist/verify/sandboxedValidation.js +56 -0
- package/dist/verify/validation.d.ts +41 -0
- package/dist/verify/validation.js +85 -0
- package/dist/wire/canonicalize.golden.d.ts +23 -0
- package/dist/wire/canonicalize.golden.js +46 -0
- package/dist/wire/geneHints.d.ts +64 -0
- package/dist/wire/geneHints.js +100 -0
- package/dist/wire/index.d.ts +91 -0
- package/dist/wire/index.js +3 -0
- package/dist/wire/schemaGate.d.ts +11 -0
- package/dist/wire/schemaGate.js +49 -0
- package/dist/workflow/dsl.d.ts +54 -0
- package/dist/workflow/dsl.js +15 -0
- package/dist/workflow/engine.d.ts +30 -0
- package/dist/workflow/engine.js +94 -0
- package/dist/workflow/index.d.ts +2 -0
- package/dist/workflow/index.js +2 -0
- package/package.json +29 -0
|
@@ -0,0 +1,1818 @@
|
|
|
1
|
+
import { createDecipheriv, createHash, privateDecrypt, constants } from 'node:crypto';
|
|
2
|
+
import { REDACTED, redactString } from '../hub/sanitize.js';
|
|
3
|
+
export const CODING_TRAJECTORY_SCHEMA = 'evomap.coding_trajectory.v1';
|
|
4
|
+
const RUNTIME_SESSION_REDACTION = 'evolver-redact-v2';
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function runtimeKeyParts(key) {
|
|
9
|
+
return key
|
|
10
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
11
|
+
.split(/[^A-Za-z0-9]+/)
|
|
12
|
+
.filter(Boolean)
|
|
13
|
+
.map((part) => part.toLowerCase());
|
|
14
|
+
}
|
|
15
|
+
const RUNTIME_SENSITIVE_KEY_PARTS = new Set([
|
|
16
|
+
'authentication',
|
|
17
|
+
'authorization',
|
|
18
|
+
'auth',
|
|
19
|
+
'cookie',
|
|
20
|
+
'cookies',
|
|
21
|
+
'credentials',
|
|
22
|
+
'dsn',
|
|
23
|
+
'password',
|
|
24
|
+
'secret',
|
|
25
|
+
'token',
|
|
26
|
+
]);
|
|
27
|
+
const RUNTIME_SENSITIVE_KEY_COMPOUNDS = [
|
|
28
|
+
'apikey',
|
|
29
|
+
'clientsecret',
|
|
30
|
+
'nodesecret',
|
|
31
|
+
'privatekey',
|
|
32
|
+
];
|
|
33
|
+
const RUNTIME_STABLE_IDENTITY_KEY_RE = /(?:^|[_-])(?:user|session|device)[_-]?id(?:$|[_-])/i;
|
|
34
|
+
function isRuntimeSensitiveKey(key) {
|
|
35
|
+
const parts = runtimeKeyParts(key);
|
|
36
|
+
const normalized = parts.join('');
|
|
37
|
+
if (parts.some((part) => RUNTIME_SENSITIVE_KEY_PARTS.has(part)))
|
|
38
|
+
return true;
|
|
39
|
+
return RUNTIME_SENSITIVE_KEY_COMPOUNDS.some((sensitive) => normalized.includes(sensitive));
|
|
40
|
+
}
|
|
41
|
+
function isRuntimeStableIdentityKey(key) {
|
|
42
|
+
return RUNTIME_STABLE_IDENTITY_KEY_RE.test(key);
|
|
43
|
+
}
|
|
44
|
+
// Claude Code's metadata.user_id has the shape
|
|
45
|
+
// `user_<accountHash>_account__session_<sessionUuid>`. The session uuid changes
|
|
46
|
+
// every session, so hashing the whole value yields a different id per session,
|
|
47
|
+
// which breaks downstream cross-session de-duplication / anti-sybil. We strip
|
|
48
|
+
// the `__session_<uuid>` suffix and hash only the stable account portion so the
|
|
49
|
+
// same real account maps to the same redacted id across sessions. Non-Claude
|
|
50
|
+
// values (no `__session_` marker) fall back to hashing the whole value.
|
|
51
|
+
function stableIdentityHashSource(value) {
|
|
52
|
+
let serialized;
|
|
53
|
+
try {
|
|
54
|
+
serialized = typeof value === 'string' ? value : JSON.stringify(value);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
serialized = String(value);
|
|
58
|
+
}
|
|
59
|
+
const sessionMarker = serialized.indexOf('__session_');
|
|
60
|
+
if (sessionMarker > 0)
|
|
61
|
+
return serialized.slice(0, sessionMarker);
|
|
62
|
+
return serialized;
|
|
63
|
+
}
|
|
64
|
+
function stableIdentityPlaceholder(value) {
|
|
65
|
+
const source = stableIdentityHashSource(value);
|
|
66
|
+
const hash = createHash('sha256').update(`evomap-stable-identity-v1:${source}`, 'utf8').digest('hex').slice(0, 16);
|
|
67
|
+
return `[REDACTED_ID_SHA256:${hash}]`;
|
|
68
|
+
}
|
|
69
|
+
function redactRuntimeStableIdentity(value) {
|
|
70
|
+
if (value === undefined || value === null)
|
|
71
|
+
return value;
|
|
72
|
+
return stableIdentityPlaceholder(value);
|
|
73
|
+
}
|
|
74
|
+
// Deterministic, cross-session-stable hash of an account identity, used to emit
|
|
75
|
+
// an explicit `user_id_hash` sibling field so buyers get a stable de-dup key
|
|
76
|
+
// without ever seeing the plaintext account id. Reuses stableIdentityHashSource
|
|
77
|
+
// so it stays consistent with the in-place redacted user_id token.
|
|
78
|
+
export function stableUserIdHash(value) {
|
|
79
|
+
const source = stableIdentityHashSource(value);
|
|
80
|
+
return createHash('sha256').update(`evomap-stable-identity-v1:${source}`, 'utf8').digest('hex').slice(0, 16);
|
|
81
|
+
}
|
|
82
|
+
const USER_ID_KEY_RE = /(?:^|[_-])user[_-]?id(?:$|[_-])/i;
|
|
83
|
+
function redactRuntimeStructuredValue(value) {
|
|
84
|
+
if (typeof value === 'string')
|
|
85
|
+
return redactString(value);
|
|
86
|
+
if (Array.isArray(value))
|
|
87
|
+
return value.map((entry) => redactRuntimeStructuredValue(entry));
|
|
88
|
+
if (isRecord(value)) {
|
|
89
|
+
const out = {};
|
|
90
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
91
|
+
out[key] = isRuntimeSensitiveKey(key)
|
|
92
|
+
? REDACTED
|
|
93
|
+
: (isRuntimeStableIdentityKey(key) ? redactRuntimeStableIdentity(entry) : redactRuntimeStructuredValue(entry));
|
|
94
|
+
// Emit a stable, cross-session user_id_hash alongside a redacted user_id so
|
|
95
|
+
// downstream de-dup/anti-sybil has a deterministic per-account key.
|
|
96
|
+
if (USER_ID_KEY_RE.test(key) && (typeof entry === 'string' || typeof entry === 'number') && out['user_id_hash'] === undefined) {
|
|
97
|
+
out['user_id_hash'] = stableUserIdHash(entry);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
function safeJsonParse(text, fallback) {
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(text);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return fallback;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function newReadStats() {
|
|
113
|
+
return {
|
|
114
|
+
rowsScanned: 0,
|
|
115
|
+
rowsRead: 0,
|
|
116
|
+
invalidJson: 0,
|
|
117
|
+
encryptedRows: 0,
|
|
118
|
+
skippedMissingSecret: 0,
|
|
119
|
+
decryptFailures: 0,
|
|
120
|
+
nonTraceSkipped: 0,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function parseNodeSecret(value) {
|
|
124
|
+
const raw = String(value ?? '').trim();
|
|
125
|
+
return /^[a-f0-9]{64}$/i.test(raw) ? raw : undefined;
|
|
126
|
+
}
|
|
127
|
+
function versionKey(value) {
|
|
128
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
129
|
+
return String(value);
|
|
130
|
+
if (typeof value === 'string')
|
|
131
|
+
return value.trim();
|
|
132
|
+
return '';
|
|
133
|
+
}
|
|
134
|
+
function nodeSecretKeyringMap(input) {
|
|
135
|
+
const out = new Map();
|
|
136
|
+
if (!input)
|
|
137
|
+
return out;
|
|
138
|
+
if (Array.isArray(input)) {
|
|
139
|
+
for (const entry of input) {
|
|
140
|
+
if (!isRecord(entry))
|
|
141
|
+
continue;
|
|
142
|
+
const version = versionKey(entry['version'] ?? entry['node_secret_version'] ?? entry['nodeSecretVersion']);
|
|
143
|
+
const secret = parseNodeSecret(String(entry['node_secret'] ?? entry['nodeSecret'] ?? entry['secret'] ?? ''));
|
|
144
|
+
if (version && secret)
|
|
145
|
+
out.set(version, secret);
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
for (const [version, value] of Object.entries(input)) {
|
|
150
|
+
const secret = parseNodeSecret(value);
|
|
151
|
+
if (version && secret)
|
|
152
|
+
out.set(version, secret);
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
function nodeSecretsForEnvelope(envelope, opts) {
|
|
157
|
+
const secrets = [];
|
|
158
|
+
const version = versionKey(envelope['secret_version'] ?? envelope['node_secret_version']);
|
|
159
|
+
if (version) {
|
|
160
|
+
const fromKeyring = nodeSecretKeyringMap(opts.nodeSecretKeyring).get(version);
|
|
161
|
+
if (fromKeyring)
|
|
162
|
+
secrets.push(fromKeyring);
|
|
163
|
+
}
|
|
164
|
+
const fallback = parseNodeSecret(opts.nodeSecret);
|
|
165
|
+
if (fallback && !secrets.includes(fallback))
|
|
166
|
+
secrets.push(fallback);
|
|
167
|
+
return secrets;
|
|
168
|
+
}
|
|
169
|
+
function traceKeyFromNodeSecret(nodeSecret) {
|
|
170
|
+
return createHash('sha256').update(`evomap-proxy-trace-v1:${nodeSecret}`, 'utf8').digest();
|
|
171
|
+
}
|
|
172
|
+
function decryptWithKey(envelope, key) {
|
|
173
|
+
const iv = Buffer.from(String(envelope['iv'] ?? ''), 'base64');
|
|
174
|
+
const tag = Buffer.from(String(envelope['tag'] ?? ''), 'base64');
|
|
175
|
+
const ciphertext = Buffer.from(String(envelope['ciphertext'] ?? ''), 'base64');
|
|
176
|
+
const decipher = createDecipheriv('aes-256-gcm', key, iv, { authTagLength: 16 });
|
|
177
|
+
decipher.setAuthTag(tag);
|
|
178
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
179
|
+
return JSON.parse(plaintext.toString('utf8'));
|
|
180
|
+
}
|
|
181
|
+
function decryptTraceEnvelope(envelope, opts) {
|
|
182
|
+
let hubError;
|
|
183
|
+
if (opts.hubPrivateKey && isRecord(envelope['hub_key_envelope'])) {
|
|
184
|
+
try {
|
|
185
|
+
const hub = envelope['hub_key_envelope'];
|
|
186
|
+
const wrapped = Buffer.from(String(hub['wrapped_key'] ?? ''), 'base64');
|
|
187
|
+
const key = privateDecrypt({
|
|
188
|
+
key: opts.hubPrivateKey,
|
|
189
|
+
padding: constants.RSA_PKCS1_OAEP_PADDING,
|
|
190
|
+
oaepHash: 'sha256',
|
|
191
|
+
}, wrapped);
|
|
192
|
+
return decryptWithKey(envelope, key);
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
hubError = err;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const nodeSecrets = nodeSecretsForEnvelope(envelope, opts);
|
|
199
|
+
if (nodeSecrets.length === 0)
|
|
200
|
+
throw (hubError instanceof Error ? hubError : new Error('missing node secret'));
|
|
201
|
+
let lastError = hubError;
|
|
202
|
+
for (const nodeSecret of nodeSecrets) {
|
|
203
|
+
try {
|
|
204
|
+
return decryptWithKey(envelope, traceKeyFromNodeSecret(nodeSecret));
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
lastError = err;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
throw lastError instanceof Error ? lastError : new Error('failed to decrypt encrypted trace row');
|
|
211
|
+
}
|
|
212
|
+
function hasDecryptKey(envelope, opts) {
|
|
213
|
+
if (opts.hubPrivateKey && isRecord(envelope['hub_key_envelope']))
|
|
214
|
+
return true;
|
|
215
|
+
return nodeSecretsForEnvelope(envelope, opts).length > 0;
|
|
216
|
+
}
|
|
217
|
+
function isTraceTurn(row) {
|
|
218
|
+
if (!isRecord(row))
|
|
219
|
+
return false;
|
|
220
|
+
if (row['event'] === 'llm_turn')
|
|
221
|
+
return true;
|
|
222
|
+
return row['prism_compatible'] === true;
|
|
223
|
+
}
|
|
224
|
+
export function readTraceRowsFromJsonl(text, opts = {}) {
|
|
225
|
+
const rows = [];
|
|
226
|
+
const stats = newReadStats();
|
|
227
|
+
for (const line of text.split('\n')) {
|
|
228
|
+
const trimmed = line.trim();
|
|
229
|
+
if (!trimmed)
|
|
230
|
+
continue;
|
|
231
|
+
stats.rowsScanned += 1;
|
|
232
|
+
let row = safeJsonParse(trimmed, null);
|
|
233
|
+
if (!isRecord(row)) {
|
|
234
|
+
stats.invalidJson += 1;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (row['encrypted'] === true || row['event'] === 'llm_trace_envelope') {
|
|
238
|
+
stats.encryptedRows += 1;
|
|
239
|
+
if (!hasDecryptKey(row, opts)) {
|
|
240
|
+
stats.skippedMissingSecret += 1;
|
|
241
|
+
if (!opts.allowPartial)
|
|
242
|
+
throw new Error('encrypted trace row cannot be exported without a matching key');
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
row = decryptTraceEnvelope(row, opts);
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
stats.decryptFailures += 1;
|
|
250
|
+
if (!opts.allowPartial)
|
|
251
|
+
throw new Error('failed to decrypt encrypted trace row');
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (isTraceTurn(row)) {
|
|
256
|
+
rows.push(row);
|
|
257
|
+
stats.rowsRead += 1;
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
stats.nonTraceSkipped += 1;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (stats.encryptedRows > 0 && stats.rowsRead === 0 && stats.decryptFailures > 0 && !opts.allowPartial) {
|
|
264
|
+
throw new Error('encrypted trace row cannot be exported without a matching key');
|
|
265
|
+
}
|
|
266
|
+
return { rows, stats };
|
|
267
|
+
}
|
|
268
|
+
function stringValue(value) {
|
|
269
|
+
return typeof value === 'string' ? value : '';
|
|
270
|
+
}
|
|
271
|
+
function numberOrNull(value) {
|
|
272
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
273
|
+
}
|
|
274
|
+
function tokenCount(value) {
|
|
275
|
+
const count = numberOrNull(value);
|
|
276
|
+
return count !== null && count >= 0 ? count : 0;
|
|
277
|
+
}
|
|
278
|
+
function booleanValue(value) {
|
|
279
|
+
return typeof value === 'boolean' ? value : false;
|
|
280
|
+
}
|
|
281
|
+
function normalizeBody(raw) {
|
|
282
|
+
if (raw === undefined || raw === null || raw === '')
|
|
283
|
+
return null;
|
|
284
|
+
if (typeof raw === 'string')
|
|
285
|
+
return safeJsonParse(raw, raw);
|
|
286
|
+
return raw;
|
|
287
|
+
}
|
|
288
|
+
function rawBodyIsInvalidJson(raw) {
|
|
289
|
+
if (typeof raw !== 'string')
|
|
290
|
+
return false;
|
|
291
|
+
const text = raw.trim();
|
|
292
|
+
if (!text)
|
|
293
|
+
return false;
|
|
294
|
+
try {
|
|
295
|
+
JSON.parse(text);
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function bodyIsEmpty(value) {
|
|
303
|
+
if (value === undefined || value === null)
|
|
304
|
+
return true;
|
|
305
|
+
if (typeof value === 'string')
|
|
306
|
+
return value.trim().length === 0;
|
|
307
|
+
if (Array.isArray(value))
|
|
308
|
+
return value.length === 0;
|
|
309
|
+
if (isRecord(value))
|
|
310
|
+
return Object.keys(value).length === 0;
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
function usageTokens(row, key) {
|
|
314
|
+
const direct = row[key];
|
|
315
|
+
if (typeof direct === 'number' && Number.isFinite(direct))
|
|
316
|
+
return direct;
|
|
317
|
+
const usage = isRecord(row['usage']) ? row['usage'] : {};
|
|
318
|
+
const value = usage[key];
|
|
319
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
320
|
+
}
|
|
321
|
+
const BEDROCK_PROVIDER_ALIASES = new Set([
|
|
322
|
+
'bedrock',
|
|
323
|
+
'aws-bedrock',
|
|
324
|
+
'aws-bedrock-anthropic',
|
|
325
|
+
'bedrock-anthropic',
|
|
326
|
+
]);
|
|
327
|
+
function normalizeBedrockProviderAlias(value) {
|
|
328
|
+
return BEDROCK_PROVIDER_ALIASES.has(value.toLowerCase()) ? 'aws-bedrock-anthropic' : value;
|
|
329
|
+
}
|
|
330
|
+
function providerForRow(row) {
|
|
331
|
+
const provider = stringValue(row['provider']);
|
|
332
|
+
if (provider)
|
|
333
|
+
return normalizeBedrockProviderAlias(provider);
|
|
334
|
+
const upstream = stringValue(row['upstream']).toLowerCase();
|
|
335
|
+
const normalizedUpstream = normalizeBedrockProviderAlias(upstream);
|
|
336
|
+
if (normalizedUpstream === 'aws-bedrock-anthropic')
|
|
337
|
+
return normalizedUpstream;
|
|
338
|
+
if (upstream === 'vertex')
|
|
339
|
+
return 'vertex-gemini';
|
|
340
|
+
return upstream || 'anthropic';
|
|
341
|
+
}
|
|
342
|
+
function normalizeEndpoint(value) {
|
|
343
|
+
return value.replace(/^(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+/i, '').trim();
|
|
344
|
+
}
|
|
345
|
+
function endpointForRow(row) {
|
|
346
|
+
const endpoint = stringValue(row['route']) || stringValue(row['path']);
|
|
347
|
+
return endpoint ? normalizeEndpoint(endpoint) : '';
|
|
348
|
+
}
|
|
349
|
+
function timestampForRow(row) {
|
|
350
|
+
return stringValue(row['ts']) || stringValue(row['createdAtIso']) || stringValue(row['timestamp']);
|
|
351
|
+
}
|
|
352
|
+
function modelForRow(row) {
|
|
353
|
+
return stringValue(row['chosen_model']) || stringValue(row['chosenModel']) || stringValue(row['model']) || stringValue(row['original_model']);
|
|
354
|
+
}
|
|
355
|
+
function chosenModelForRow(row) {
|
|
356
|
+
return stringValue(row['chosen_model']) || stringValue(row['chosenModel']);
|
|
357
|
+
}
|
|
358
|
+
function originalModelForRow(row) {
|
|
359
|
+
return stringValue(row['original_model']) || stringValue(row['originalModel']);
|
|
360
|
+
}
|
|
361
|
+
function sessionIdForRow(row) {
|
|
362
|
+
return stringValue(row['session_id']) || stringValue(row['sessionId']);
|
|
363
|
+
}
|
|
364
|
+
function responseIdForRow(row) {
|
|
365
|
+
return stringValue(row['response_id']) || stringValue(row['responseId']);
|
|
366
|
+
}
|
|
367
|
+
function previousResponseIdForRow(row) {
|
|
368
|
+
return stringValue(row['previous_response_id']) || stringValue(row['previousResponseId']);
|
|
369
|
+
}
|
|
370
|
+
function requestIdForRow(row) {
|
|
371
|
+
return stringValue(row['request_id']) || stringValue(row['requestId']) || stringValue(row['id']);
|
|
372
|
+
}
|
|
373
|
+
function statusForRow(row) {
|
|
374
|
+
return numberOrNull(row['status']);
|
|
375
|
+
}
|
|
376
|
+
function durationForRow(row) {
|
|
377
|
+
return numberOrNull(row['latency_ms']) ?? numberOrNull(row['durationMs']);
|
|
378
|
+
}
|
|
379
|
+
function errorForRow(row) {
|
|
380
|
+
return stringValue(row['error']) || stringValue(row['errorMessage']);
|
|
381
|
+
}
|
|
382
|
+
function streamErrorForRow(row) {
|
|
383
|
+
return stringValue(row['stream_error']) || stringValue(row['streamError']);
|
|
384
|
+
}
|
|
385
|
+
function streamCancelledForRow(row) {
|
|
386
|
+
return booleanValue(row['stream_cancelled']) || booleanValue(row['streamCancelled']);
|
|
387
|
+
}
|
|
388
|
+
function isStreamRow(row) {
|
|
389
|
+
return booleanValue(row['stream']) || booleanValue(row['isStream']);
|
|
390
|
+
}
|
|
391
|
+
function finishReasonForRow(row) {
|
|
392
|
+
return stringValue(row['stop_reason']) || stringValue(row['finish_reason']) || stringValue(row['finishReason']);
|
|
393
|
+
}
|
|
394
|
+
function textFromContent(content) {
|
|
395
|
+
if (typeof content === 'string')
|
|
396
|
+
return content;
|
|
397
|
+
if (!Array.isArray(content))
|
|
398
|
+
return '';
|
|
399
|
+
return content.map((part) => {
|
|
400
|
+
if (!isRecord(part))
|
|
401
|
+
return '';
|
|
402
|
+
return stringValue(part['text']) || stringValue(part['content']);
|
|
403
|
+
}).filter(Boolean).join('\n');
|
|
404
|
+
}
|
|
405
|
+
function extractTask(body) {
|
|
406
|
+
if (!isRecord(body))
|
|
407
|
+
return '';
|
|
408
|
+
if (typeof body['instructions'] === 'string')
|
|
409
|
+
return body['instructions'].slice(0, 1000);
|
|
410
|
+
if (typeof body['input'] === 'string')
|
|
411
|
+
return body['input'].slice(0, 1000);
|
|
412
|
+
if (Array.isArray(body['input'])) {
|
|
413
|
+
return body['input'].map((item) => isRecord(item) ? textFromContent(item['content']) : '')
|
|
414
|
+
.filter(Boolean).join('\n').slice(0, 1000);
|
|
415
|
+
}
|
|
416
|
+
const messages = Array.isArray(body['messages']) ? body['messages'] : [];
|
|
417
|
+
const firstUser = messages.find((m) => isRecord(m) && m['role'] === 'user');
|
|
418
|
+
return isRecord(firstUser) ? textFromContent(firstUser['content']).slice(0, 1000) : '';
|
|
419
|
+
}
|
|
420
|
+
function byteLength(value) {
|
|
421
|
+
try {
|
|
422
|
+
return Buffer.byteLength(JSON.stringify(value), 'utf8');
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
return 0;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function isToolCallType(value) {
|
|
429
|
+
return value === 'tool_use' || value === 'function_call';
|
|
430
|
+
}
|
|
431
|
+
function isToolResultType(value) {
|
|
432
|
+
return value === 'tool_result' || value === 'function_call_output';
|
|
433
|
+
}
|
|
434
|
+
function streamKeyPart(value) {
|
|
435
|
+
if (typeof value === 'number' && Number.isInteger(value))
|
|
436
|
+
return String(value);
|
|
437
|
+
if (typeof value === 'string' && value)
|
|
438
|
+
return value;
|
|
439
|
+
return '';
|
|
440
|
+
}
|
|
441
|
+
function collectToolCalls(body) {
|
|
442
|
+
const calls = [];
|
|
443
|
+
const anthropicBlocks = new Map();
|
|
444
|
+
const openAiToolCalls = new Map();
|
|
445
|
+
const openAiLastToolKeyByChoice = new Map();
|
|
446
|
+
const responsesFunctionCalls = new Map();
|
|
447
|
+
const addCall = (call, opts = {}) => {
|
|
448
|
+
if (!isRecord(call))
|
|
449
|
+
return;
|
|
450
|
+
const fn = isRecord(call['function']) ? call['function'] : undefined;
|
|
451
|
+
const normalized = {
|
|
452
|
+
id: stringValue(call['id']) || stringValue(call['tool_use_id']) || stringValue(call['call_id']) || stringValue(call['tool_call_id']),
|
|
453
|
+
name: stringValue(call['name']) || (fn ? stringValue(fn['name']) : '') || stringValue(call['type']) || 'unknown',
|
|
454
|
+
...(call['input'] !== undefined ? { input: call['input'] } : {}),
|
|
455
|
+
...(call['arguments'] !== undefined ? { arguments: call['arguments'] } : {}),
|
|
456
|
+
...(fn !== undefined ? { function: fn } : {}),
|
|
457
|
+
bytes: opts.bytes ?? byteLength(call),
|
|
458
|
+
...(opts.declared === true || call['declared'] === true ? { declared: true } : {}),
|
|
459
|
+
};
|
|
460
|
+
const existing = normalized.id && !normalized.declared
|
|
461
|
+
? calls.find((candidate) => candidate.id === normalized.id && !candidate.declared)
|
|
462
|
+
: undefined;
|
|
463
|
+
if (existing) {
|
|
464
|
+
if ((!existing.name || existing.name === 'unknown') && normalized.name)
|
|
465
|
+
existing.name = normalized.name;
|
|
466
|
+
if (existing.input === undefined && normalized.input !== undefined)
|
|
467
|
+
existing.input = normalized.input;
|
|
468
|
+
if ((existing.arguments === undefined || existing.arguments === '') && normalized.arguments !== undefined && normalized.arguments !== '') {
|
|
469
|
+
existing.arguments = normalized.arguments;
|
|
470
|
+
}
|
|
471
|
+
if (isRecord(existing.function) && isRecord(normalized.function)) {
|
|
472
|
+
const mergedFunction = { ...existing.function };
|
|
473
|
+
for (const [key, value] of Object.entries(normalized.function)) {
|
|
474
|
+
if ((mergedFunction[key] === undefined || mergedFunction[key] === '') && value !== undefined && value !== '') {
|
|
475
|
+
mergedFunction[key] = value;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
existing.function = mergedFunction;
|
|
479
|
+
}
|
|
480
|
+
else if (existing.function === undefined && normalized.function !== undefined) {
|
|
481
|
+
existing.function = normalized.function;
|
|
482
|
+
}
|
|
483
|
+
existing.bytes = Math.max(existing.bytes, normalized.bytes);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
calls.push(normalized);
|
|
487
|
+
};
|
|
488
|
+
const addToolResult = (call) => {
|
|
489
|
+
if (!isRecord(call))
|
|
490
|
+
return;
|
|
491
|
+
calls.push({
|
|
492
|
+
id: stringValue(call['tool_use_id']) || stringValue(call['call_id']) || stringValue(call['tool_call_id']) || stringValue(call['id']),
|
|
493
|
+
name: 'tool_result',
|
|
494
|
+
...(call['content'] !== undefined ? { input: call['content'] } : {}),
|
|
495
|
+
...(call['output'] !== undefined ? { arguments: call['output'] } : {}),
|
|
496
|
+
bytes: byteLength(call),
|
|
497
|
+
});
|
|
498
|
+
};
|
|
499
|
+
const anthropicKeys = (evt, block) => {
|
|
500
|
+
const keys = [];
|
|
501
|
+
const index = streamKeyPart(evt['index']);
|
|
502
|
+
if (index)
|
|
503
|
+
keys.push(`index:${index}`);
|
|
504
|
+
const id = block ? stringValue(block['id']) : '';
|
|
505
|
+
if (id)
|
|
506
|
+
keys.push(`id:${id}`);
|
|
507
|
+
return keys;
|
|
508
|
+
};
|
|
509
|
+
const getAnthropicBlock = (keys) => {
|
|
510
|
+
for (const key of keys) {
|
|
511
|
+
const existing = anthropicBlocks.get(key);
|
|
512
|
+
if (existing) {
|
|
513
|
+
for (const alias of keys)
|
|
514
|
+
anthropicBlocks.set(alias, existing);
|
|
515
|
+
return existing;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
const fallbackKey = keys[0] ?? `anonymous:${anthropicBlocks.size}`;
|
|
519
|
+
const created = { id: '', name: '', type: 'tool_use', partialJson: '', bytes: 0 };
|
|
520
|
+
anthropicBlocks.set(fallbackKey, created);
|
|
521
|
+
for (const alias of keys)
|
|
522
|
+
anthropicBlocks.set(alias, created);
|
|
523
|
+
return created;
|
|
524
|
+
};
|
|
525
|
+
const accumulateAnthropicStart = (evt, contentBlock) => {
|
|
526
|
+
if (!isToolCallType(contentBlock['type']))
|
|
527
|
+
return;
|
|
528
|
+
const block = getAnthropicBlock(anthropicKeys(evt, contentBlock));
|
|
529
|
+
block.id = block.id || stringValue(contentBlock['id']);
|
|
530
|
+
block.name = block.name || stringValue(contentBlock['name']);
|
|
531
|
+
block.type = stringValue(contentBlock['type']) || block.type;
|
|
532
|
+
if (block.input === undefined && contentBlock['input'] !== undefined)
|
|
533
|
+
block.input = contentBlock['input'];
|
|
534
|
+
block.bytes += byteLength(contentBlock);
|
|
535
|
+
};
|
|
536
|
+
const accumulateAnthropicDelta = (evt, delta) => {
|
|
537
|
+
if (delta['type'] !== 'input_json_delta')
|
|
538
|
+
return;
|
|
539
|
+
const partialJson = stringValue(delta['partial_json']);
|
|
540
|
+
if (!partialJson)
|
|
541
|
+
return;
|
|
542
|
+
const block = getAnthropicBlock(anthropicKeys(evt));
|
|
543
|
+
block.partialJson += partialJson;
|
|
544
|
+
block.bytes += byteLength(delta);
|
|
545
|
+
};
|
|
546
|
+
const openAiChoiceKey = (choice) => {
|
|
547
|
+
return streamKeyPart(choice['index']) || '0';
|
|
548
|
+
};
|
|
549
|
+
const openAiToolKey = (choice, toolCall) => {
|
|
550
|
+
const choiceKey = openAiChoiceKey(choice);
|
|
551
|
+
const toolIndex = streamKeyPart(toolCall['index']);
|
|
552
|
+
if (toolIndex)
|
|
553
|
+
return `choice:${choiceKey}:tool:${toolIndex}`;
|
|
554
|
+
const id = stringValue(toolCall['id']);
|
|
555
|
+
if (id)
|
|
556
|
+
return `choice:${choiceKey}:id:${id}`;
|
|
557
|
+
return openAiLastToolKeyByChoice.get(choiceKey) ?? `choice:${choiceKey}:anonymous:${openAiToolCalls.size}`;
|
|
558
|
+
};
|
|
559
|
+
const accumulateOpenAiToolCall = (choice, toolCall) => {
|
|
560
|
+
if (!isRecord(toolCall))
|
|
561
|
+
return;
|
|
562
|
+
const key = openAiToolKey(choice, toolCall);
|
|
563
|
+
const existing = openAiToolCalls.get(key) ?? { id: '', type: '', functionName: '', arguments: '', bytes: 0 };
|
|
564
|
+
const fn = isRecord(toolCall['function']) ? toolCall['function'] : undefined;
|
|
565
|
+
existing.id = existing.id || stringValue(toolCall['id']);
|
|
566
|
+
existing.type = existing.type || stringValue(toolCall['type']);
|
|
567
|
+
if (fn) {
|
|
568
|
+
existing.functionName = existing.functionName || stringValue(fn['name']);
|
|
569
|
+
existing.arguments += stringValue(fn['arguments']);
|
|
570
|
+
}
|
|
571
|
+
existing.bytes += byteLength(toolCall);
|
|
572
|
+
openAiToolCalls.set(key, existing);
|
|
573
|
+
openAiLastToolKeyByChoice.set(openAiChoiceKey(choice), key);
|
|
574
|
+
};
|
|
575
|
+
const responsesKeys = (evt, item) => {
|
|
576
|
+
const keys = [];
|
|
577
|
+
const itemId = stringValue(evt['item_id']) || (item ? stringValue(item['id']) : '');
|
|
578
|
+
const outputIndex = streamKeyPart(evt['output_index']) || (item ? streamKeyPart(item['output_index']) : '');
|
|
579
|
+
const callId = stringValue(evt['call_id']) || stringValue(evt['id']) || (item ? stringValue(item['call_id']) || stringValue(item['id']) : '');
|
|
580
|
+
if (itemId)
|
|
581
|
+
keys.push(`item:${itemId}`);
|
|
582
|
+
if (outputIndex)
|
|
583
|
+
keys.push(`output:${outputIndex}`);
|
|
584
|
+
if (callId)
|
|
585
|
+
keys.push(`call:${callId}`);
|
|
586
|
+
return keys;
|
|
587
|
+
};
|
|
588
|
+
const getResponsesFunctionCall = (keys) => {
|
|
589
|
+
for (const key of keys) {
|
|
590
|
+
const existing = responsesFunctionCalls.get(key);
|
|
591
|
+
if (existing) {
|
|
592
|
+
for (const alias of keys)
|
|
593
|
+
responsesFunctionCalls.set(alias, existing);
|
|
594
|
+
return existing;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const fallbackKey = keys[0] ?? `anonymous:${responsesFunctionCalls.size}`;
|
|
598
|
+
const created = { id: '', callId: '', name: '', arguments: '', bytes: 0 };
|
|
599
|
+
responsesFunctionCalls.set(fallbackKey, created);
|
|
600
|
+
for (const alias of keys)
|
|
601
|
+
responsesFunctionCalls.set(alias, created);
|
|
602
|
+
return created;
|
|
603
|
+
};
|
|
604
|
+
const accumulateResponsesOutputItem = (evt, item) => {
|
|
605
|
+
if (item['type'] !== 'function_call')
|
|
606
|
+
return;
|
|
607
|
+
const call = getResponsesFunctionCall(responsesKeys(evt, item));
|
|
608
|
+
call.id = call.id || stringValue(item['id']);
|
|
609
|
+
call.callId = call.callId || stringValue(item['call_id']);
|
|
610
|
+
call.name = call.name || stringValue(item['name']);
|
|
611
|
+
const itemArguments = item['arguments'];
|
|
612
|
+
if (typeof itemArguments === 'string' && itemArguments !== '')
|
|
613
|
+
call.arguments = itemArguments;
|
|
614
|
+
call.bytes += byteLength(item);
|
|
615
|
+
};
|
|
616
|
+
const accumulateResponsesArguments = (evt) => {
|
|
617
|
+
if (evt['type'] !== 'response.function_call_arguments.delta' && evt['type'] !== 'response.function_call_arguments.done')
|
|
618
|
+
return;
|
|
619
|
+
const call = getResponsesFunctionCall(responsesKeys(evt));
|
|
620
|
+
if (evt['type'] === 'response.function_call_arguments.done') {
|
|
621
|
+
const doneArguments = evt['arguments'];
|
|
622
|
+
if (typeof doneArguments === 'string' && doneArguments !== '')
|
|
623
|
+
call.arguments = doneArguments;
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
call.arguments += stringValue(evt['delta']);
|
|
627
|
+
}
|
|
628
|
+
call.bytes += byteLength(evt);
|
|
629
|
+
};
|
|
630
|
+
// Gemini/Vertex parts are camelCase and carry no `type` field:
|
|
631
|
+
// { functionCall: { name, args } } -> tool_use / tool_call
|
|
632
|
+
// { functionResponse: { name, response } } -> tool_result (paired by name/id)
|
|
633
|
+
const addGeminiFunctionCall = (fc) => {
|
|
634
|
+
if (!isRecord(fc))
|
|
635
|
+
return;
|
|
636
|
+
const name = stringValue(fc['name']);
|
|
637
|
+
const call = { type: 'tool_use', name: name || 'unknown' };
|
|
638
|
+
const id = stringValue(fc['id']);
|
|
639
|
+
if (id)
|
|
640
|
+
call['id'] = id;
|
|
641
|
+
if (fc['args'] !== undefined)
|
|
642
|
+
call['input'] = fc['args'];
|
|
643
|
+
addCall(call);
|
|
644
|
+
};
|
|
645
|
+
const addGeminiFunctionResponse = (fr) => {
|
|
646
|
+
if (!isRecord(fr))
|
|
647
|
+
return;
|
|
648
|
+
calls.push({
|
|
649
|
+
id: stringValue(fr['id']) || stringValue(fr['name']),
|
|
650
|
+
name: 'tool_result',
|
|
651
|
+
...(fr['response'] !== undefined ? { input: fr['response'] } : {}),
|
|
652
|
+
bytes: byteLength(fr),
|
|
653
|
+
});
|
|
654
|
+
};
|
|
655
|
+
const scanGeminiPart = (part) => {
|
|
656
|
+
let matched = false;
|
|
657
|
+
if (isRecord(part['functionCall'])) {
|
|
658
|
+
addGeminiFunctionCall(part['functionCall']);
|
|
659
|
+
matched = true;
|
|
660
|
+
}
|
|
661
|
+
if (isRecord(part['functionResponse'])) {
|
|
662
|
+
addGeminiFunctionResponse(part['functionResponse']);
|
|
663
|
+
matched = true;
|
|
664
|
+
}
|
|
665
|
+
return matched;
|
|
666
|
+
};
|
|
667
|
+
const scanGeminiCandidates = (candidates) => {
|
|
668
|
+
if (!Array.isArray(candidates))
|
|
669
|
+
return;
|
|
670
|
+
for (const candidate of candidates) {
|
|
671
|
+
if (!isRecord(candidate))
|
|
672
|
+
continue;
|
|
673
|
+
const content = candidate['content'];
|
|
674
|
+
if (!isRecord(content))
|
|
675
|
+
continue;
|
|
676
|
+
const parts = Array.isArray(content['parts']) ? content['parts'] : [];
|
|
677
|
+
for (const part of parts)
|
|
678
|
+
if (isRecord(part))
|
|
679
|
+
scanGeminiPart(part);
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
const scanContent = (content) => {
|
|
683
|
+
const parts = Array.isArray(content) ? content : [];
|
|
684
|
+
for (const part of parts) {
|
|
685
|
+
if (!isRecord(part))
|
|
686
|
+
continue;
|
|
687
|
+
if (isToolCallType(part['type']))
|
|
688
|
+
addCall(part);
|
|
689
|
+
if (isToolResultType(part['type']))
|
|
690
|
+
addToolResult(part);
|
|
691
|
+
// Gemini message content (non-streaming request `contents[].parts[]`).
|
|
692
|
+
scanGeminiPart(part);
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
const scanMessage = (msg, opts = {}) => {
|
|
696
|
+
if (!isRecord(msg))
|
|
697
|
+
return;
|
|
698
|
+
const shouldScanToolCalls = opts.scanToolCalls !== false;
|
|
699
|
+
scanContent(msg['content']);
|
|
700
|
+
if (shouldScanToolCalls && Array.isArray(msg['tool_calls']))
|
|
701
|
+
for (const call of msg['tool_calls'])
|
|
702
|
+
addCall(call);
|
|
703
|
+
if (shouldScanToolCalls && isRecord(msg['function_call']))
|
|
704
|
+
addCall(msg['function_call']);
|
|
705
|
+
if (msg['role'] === 'tool' || msg['tool_call_id'])
|
|
706
|
+
addToolResult(msg);
|
|
707
|
+
};
|
|
708
|
+
const scanEvent = (evt) => {
|
|
709
|
+
if (!isRecord(evt))
|
|
710
|
+
return;
|
|
711
|
+
// Gemini/Vertex streaming chunks carry candidates[].content.parts[].
|
|
712
|
+
if (Array.isArray(evt['candidates']))
|
|
713
|
+
scanGeminiCandidates(evt['candidates']);
|
|
714
|
+
scanMessage(evt);
|
|
715
|
+
scanMessage(evt['message']);
|
|
716
|
+
scanMessage(evt['delta'], { scanToolCalls: false });
|
|
717
|
+
scanMessage(evt['item']);
|
|
718
|
+
scanContent(evt['content']);
|
|
719
|
+
const delta = isRecord(evt['delta']) ? evt['delta'] : {};
|
|
720
|
+
scanContent(delta['content']);
|
|
721
|
+
accumulateAnthropicDelta(evt, delta);
|
|
722
|
+
accumulateResponsesArguments(evt);
|
|
723
|
+
const contentBlock = isRecord(evt['content_block']) ? evt['content_block'] : undefined;
|
|
724
|
+
if (contentBlock) {
|
|
725
|
+
accumulateAnthropicStart(evt, contentBlock);
|
|
726
|
+
scanContent(contentBlock['content']);
|
|
727
|
+
}
|
|
728
|
+
const item = isRecord(evt['item']) ? evt['item'] : undefined;
|
|
729
|
+
if (item) {
|
|
730
|
+
accumulateResponsesOutputItem(evt, item);
|
|
731
|
+
if (isToolCallType(item['type']))
|
|
732
|
+
addCall(item);
|
|
733
|
+
if (isToolResultType(item['type']))
|
|
734
|
+
addToolResult(item);
|
|
735
|
+
scanContent(item['content']);
|
|
736
|
+
}
|
|
737
|
+
const response = isRecord(evt['response']) ? evt['response'] : undefined;
|
|
738
|
+
if (response)
|
|
739
|
+
for (const call of collectToolCalls(response))
|
|
740
|
+
calls.push(call);
|
|
741
|
+
const choices = Array.isArray(evt['choices']) ? evt['choices'] : [];
|
|
742
|
+
for (const choice of choices) {
|
|
743
|
+
if (!isRecord(choice))
|
|
744
|
+
continue;
|
|
745
|
+
scanMessage(choice['message']);
|
|
746
|
+
scanMessage(choice['delta'], { scanToolCalls: false });
|
|
747
|
+
const choiceDelta = isRecord(choice['delta']) ? choice['delta'] : {};
|
|
748
|
+
const toolCalls = Array.isArray(choiceDelta['tool_calls']) ? choiceDelta['tool_calls'] : [];
|
|
749
|
+
for (const toolCall of toolCalls)
|
|
750
|
+
accumulateOpenAiToolCall(choice, toolCall);
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
const flushAnthropicStreamBlocks = () => {
|
|
754
|
+
for (const block of new Set(anthropicBlocks.values())) {
|
|
755
|
+
if (!block.id && !block.name && !block.partialJson)
|
|
756
|
+
continue;
|
|
757
|
+
const call = { type: block.type || 'tool_use' };
|
|
758
|
+
if (block.id)
|
|
759
|
+
call['id'] = block.id;
|
|
760
|
+
if (block.name)
|
|
761
|
+
call['name'] = block.name;
|
|
762
|
+
if (block.partialJson) {
|
|
763
|
+
try {
|
|
764
|
+
call['input'] = JSON.parse(block.partialJson);
|
|
765
|
+
}
|
|
766
|
+
catch {
|
|
767
|
+
call['arguments'] = block.partialJson;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
else if (block.input !== undefined) {
|
|
771
|
+
call['input'] = block.input;
|
|
772
|
+
}
|
|
773
|
+
addCall(call, { bytes: block.bytes });
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
const flushOpenAiStreamToolCalls = () => {
|
|
777
|
+
for (const toolCall of openAiToolCalls.values()) {
|
|
778
|
+
if (!toolCall.id && !toolCall.functionName && !toolCall.arguments)
|
|
779
|
+
continue;
|
|
780
|
+
const fn = {};
|
|
781
|
+
if (toolCall.functionName)
|
|
782
|
+
fn['name'] = toolCall.functionName;
|
|
783
|
+
if (toolCall.arguments)
|
|
784
|
+
fn['arguments'] = toolCall.arguments;
|
|
785
|
+
const call = {};
|
|
786
|
+
if (toolCall.id)
|
|
787
|
+
call['id'] = toolCall.id;
|
|
788
|
+
if (toolCall.type)
|
|
789
|
+
call['type'] = toolCall.type;
|
|
790
|
+
if (Object.keys(fn).length > 0)
|
|
791
|
+
call['function'] = fn;
|
|
792
|
+
addCall(call, { bytes: toolCall.bytes });
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
const flushResponsesFunctionCalls = () => {
|
|
796
|
+
for (const functionCall of new Set(responsesFunctionCalls.values())) {
|
|
797
|
+
if (!functionCall.id && !functionCall.callId && !functionCall.name && !functionCall.arguments)
|
|
798
|
+
continue;
|
|
799
|
+
const call = { type: 'function_call' };
|
|
800
|
+
if (functionCall.id)
|
|
801
|
+
call['id'] = functionCall.id;
|
|
802
|
+
if (functionCall.callId)
|
|
803
|
+
call['call_id'] = functionCall.callId;
|
|
804
|
+
if (functionCall.name)
|
|
805
|
+
call['name'] = functionCall.name;
|
|
806
|
+
if (functionCall.arguments)
|
|
807
|
+
call['arguments'] = functionCall.arguments;
|
|
808
|
+
addCall(call, { bytes: functionCall.bytes });
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
if (!isRecord(body))
|
|
812
|
+
return calls;
|
|
813
|
+
if (Array.isArray(body['messages']))
|
|
814
|
+
for (const msg of body['messages'])
|
|
815
|
+
scanMessage(msg);
|
|
816
|
+
// Gemini/Vertex non-streaming response: { candidates: [{ content: { parts: [...] } }] }.
|
|
817
|
+
if (Array.isArray(body['candidates']))
|
|
818
|
+
scanGeminiCandidates(body['candidates']);
|
|
819
|
+
// Gemini/Vertex request: { contents: [{ role, parts: [...] }] } (functionCall/functionResponse turns).
|
|
820
|
+
if (Array.isArray(body['contents'])) {
|
|
821
|
+
for (const turn of body['contents']) {
|
|
822
|
+
if (isRecord(turn))
|
|
823
|
+
scanContent(turn['parts']);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
if (Array.isArray(body['input'])) {
|
|
827
|
+
for (const item of body['input']) {
|
|
828
|
+
if (isRecord(item) && isToolCallType(item['type']))
|
|
829
|
+
addCall(item);
|
|
830
|
+
if (isRecord(item) && isToolResultType(item['type']))
|
|
831
|
+
addToolResult(item);
|
|
832
|
+
scanEvent(item);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
scanContent(body['content']);
|
|
836
|
+
if (Array.isArray(body['output'])) {
|
|
837
|
+
for (const item of body['output']) {
|
|
838
|
+
if (isRecord(item) && isToolCallType(item['type']))
|
|
839
|
+
addCall(item);
|
|
840
|
+
if (isRecord(item) && isToolResultType(item['type']))
|
|
841
|
+
addToolResult(item);
|
|
842
|
+
scanEvent(item);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (Array.isArray(body['choices']))
|
|
846
|
+
for (const choice of body['choices'])
|
|
847
|
+
scanEvent(choice);
|
|
848
|
+
if (Array.isArray(body['events']))
|
|
849
|
+
for (const evt of body['events'])
|
|
850
|
+
scanEvent(evt);
|
|
851
|
+
if (Array.isArray(body['semantic_tail_events']))
|
|
852
|
+
for (const evt of body['semantic_tail_events'])
|
|
853
|
+
scanEvent(evt);
|
|
854
|
+
if (Array.isArray(body['chunks']))
|
|
855
|
+
for (const evt of body['chunks'])
|
|
856
|
+
scanEvent(evt);
|
|
857
|
+
flushAnthropicStreamBlocks();
|
|
858
|
+
flushOpenAiStreamToolCalls();
|
|
859
|
+
flushResponsesFunctionCalls();
|
|
860
|
+
if (Array.isArray(body['tools']))
|
|
861
|
+
for (const tool of body['tools'])
|
|
862
|
+
addCall(tool, { declared: true });
|
|
863
|
+
return calls;
|
|
864
|
+
}
|
|
865
|
+
function collectRowLevelToolCalls(row) {
|
|
866
|
+
const raw = row['tool_calls'];
|
|
867
|
+
if (!Array.isArray(raw))
|
|
868
|
+
return [];
|
|
869
|
+
const calls = [];
|
|
870
|
+
for (const call of raw) {
|
|
871
|
+
if (!isRecord(call))
|
|
872
|
+
continue;
|
|
873
|
+
const fn = isRecord(call['function']) ? call['function'] : undefined;
|
|
874
|
+
calls.push({
|
|
875
|
+
id: stringValue(call['id']) || stringValue(call['tool_use_id']) || stringValue(call['call_id']) || stringValue(call['tool_call_id']),
|
|
876
|
+
name: stringValue(call['name']) || (fn ? stringValue(fn['name']) : '') || stringValue(call['type']) || 'unknown',
|
|
877
|
+
...(call['input'] !== undefined ? { input: call['input'] } : {}),
|
|
878
|
+
...(call['arguments'] !== undefined ? { arguments: call['arguments'] } : {}),
|
|
879
|
+
...(fn !== undefined ? { function: fn } : {}),
|
|
880
|
+
bytes: numberOrNull(call['bytes']) ?? byteLength(call),
|
|
881
|
+
...(call['declared'] === true ? { declared: true } : {}),
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
return calls;
|
|
885
|
+
}
|
|
886
|
+
function redactNormalizedToolCall(call) {
|
|
887
|
+
const input = call.input !== undefined ? redactRuntimeStructuredValue(call.input) : undefined;
|
|
888
|
+
const args = call.arguments !== undefined ? redactRuntimeStructuredValue(call.arguments) : undefined;
|
|
889
|
+
const fn = call.function !== undefined ? redactRuntimeStructuredValue(call.function) : undefined;
|
|
890
|
+
return {
|
|
891
|
+
id: redactString(call.id),
|
|
892
|
+
name: redactString(call.name),
|
|
893
|
+
...(input !== undefined ? { input } : {}),
|
|
894
|
+
...(args !== undefined ? { arguments: args } : {}),
|
|
895
|
+
...(fn !== undefined ? { function: fn } : {}),
|
|
896
|
+
bytes: call.bytes,
|
|
897
|
+
...(call.declared === true ? { declared: true } : {}),
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
function getToolArguments(call) {
|
|
901
|
+
if (call.input !== undefined)
|
|
902
|
+
return call.input;
|
|
903
|
+
if (call.arguments !== undefined)
|
|
904
|
+
return call.arguments;
|
|
905
|
+
if (isRecord(call.function))
|
|
906
|
+
return call.function['arguments'];
|
|
907
|
+
return undefined;
|
|
908
|
+
}
|
|
909
|
+
function extractCommandText(call) {
|
|
910
|
+
const args = getToolArguments(call);
|
|
911
|
+
if (typeof args === 'string') {
|
|
912
|
+
const parsed = safeJsonParse(args, null);
|
|
913
|
+
return isRecord(parsed) ? extractCommandText({ id: '', name: '', input: parsed, bytes: 0 }) : args;
|
|
914
|
+
}
|
|
915
|
+
if (!isRecord(args))
|
|
916
|
+
return '';
|
|
917
|
+
for (const key of ['cmd', 'command', 'script', 'input']) {
|
|
918
|
+
const value = args[key];
|
|
919
|
+
if (typeof value === 'string')
|
|
920
|
+
return value;
|
|
921
|
+
}
|
|
922
|
+
return '';
|
|
923
|
+
}
|
|
924
|
+
function isEditToolName(name) {
|
|
925
|
+
const normalized = name.toLowerCase();
|
|
926
|
+
if (!normalized)
|
|
927
|
+
return false;
|
|
928
|
+
if (/(^|[^a-z0-9])apply[_-]?patch([^a-z0-9]|$)/.test(normalized))
|
|
929
|
+
return true;
|
|
930
|
+
if (/(^|[^a-z0-9])multi[_-]?edit([^a-z0-9]|$)/.test(normalized))
|
|
931
|
+
return true;
|
|
932
|
+
if (/(^|[^a-z0-9])notebook[_-]?edit([^a-z0-9]|$)/.test(normalized))
|
|
933
|
+
return true;
|
|
934
|
+
if (/(^|[^a-z0-9])(edit|write|replace|patch)([^a-z0-9]|$)/.test(normalized))
|
|
935
|
+
return true;
|
|
936
|
+
return false;
|
|
937
|
+
}
|
|
938
|
+
function commandLooksLikeCodeEdit(command) {
|
|
939
|
+
const cmd = command.toLowerCase();
|
|
940
|
+
if (!cmd.trim())
|
|
941
|
+
return false;
|
|
942
|
+
if (/\bapply_patch\b/.test(cmd) || /\bgit\s+apply\b/.test(cmd))
|
|
943
|
+
return true;
|
|
944
|
+
if (/\bsed\b[^;\n]*\s-i(?:\b|['"])/.test(cmd))
|
|
945
|
+
return true;
|
|
946
|
+
if (/\bperl\b[^;\n]*(?:\s-pi\b|\s-i(?:\b|['"]))/.test(cmd))
|
|
947
|
+
return true;
|
|
948
|
+
if (/\b(?:python|python3|node)\b[^;\n]*(?:writefilesync|writefile|open\([^)]*,\s*['"]w|path\.write_text|pathlib|fs\.)/.test(cmd)) {
|
|
949
|
+
return true;
|
|
950
|
+
}
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
function toolCallLooksLikeCodeEdit(call) {
|
|
954
|
+
if (call.declared)
|
|
955
|
+
return false;
|
|
956
|
+
if (isEditToolName(call.name))
|
|
957
|
+
return true;
|
|
958
|
+
return commandLooksLikeCodeEdit(extractCommandText(call));
|
|
959
|
+
}
|
|
960
|
+
function commandLooksLikeTestExecution(command) {
|
|
961
|
+
const cmd = command.toLowerCase();
|
|
962
|
+
if (!cmd.trim())
|
|
963
|
+
return false;
|
|
964
|
+
return [
|
|
965
|
+
/\b(?:pnpm|npm|yarn)\s+(?:run\s+)?(?:test|vitest|jest)(?:\b|:)/,
|
|
966
|
+
/\b(?:vitest|jest|pytest)\b/,
|
|
967
|
+
/\bgo\s+test\b/,
|
|
968
|
+
/\bcargo\s+test\b/,
|
|
969
|
+
/\bmvn(?:w)?\s+(?:[^;\n]*\s)?test\b/,
|
|
970
|
+
/\bgradle(?:w)?\s+(?:[^;\n]*\s)?test\b/,
|
|
971
|
+
/\bdotnet\s+test\b/,
|
|
972
|
+
/\bswift\s+test\b/,
|
|
973
|
+
/\bzig\s+test\b/,
|
|
974
|
+
/\bnode\s+--test\b/,
|
|
975
|
+
].some((probe) => probe.test(cmd));
|
|
976
|
+
}
|
|
977
|
+
function toolCallTestCommand(call) {
|
|
978
|
+
if (call.declared)
|
|
979
|
+
return '';
|
|
980
|
+
if (!/(bash|exec_command|shell_command|run_terminal_cmd|terminal|shell|command)/i.test(call.name))
|
|
981
|
+
return '';
|
|
982
|
+
const command = extractCommandText(call).trim();
|
|
983
|
+
return commandLooksLikeTestExecution(command) ? command : '';
|
|
984
|
+
}
|
|
985
|
+
const FAILURE_OUTPUT_RE = /\b(?:assertionerror|traceback|test(?:s)? failed|failed tests?|exit(?:ed)? with code [1-9]\d*|exit code [1-9]\d*|non[- ]?zero|command failed|npm (?:test|run) failed|pytest\b[\s\S]{0,200}\bfailed|error: test failed)\b/i;
|
|
986
|
+
function textFromValue(value, depth = 0) {
|
|
987
|
+
if (value == null || depth > 10)
|
|
988
|
+
return '';
|
|
989
|
+
if (typeof value === 'string')
|
|
990
|
+
return value;
|
|
991
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
992
|
+
return String(value);
|
|
993
|
+
if (Array.isArray(value))
|
|
994
|
+
return value.map((item) => textFromValue(item, depth + 1)).filter(Boolean).join('\n');
|
|
995
|
+
if (!isRecord(value))
|
|
996
|
+
return '';
|
|
997
|
+
return Object.entries(value)
|
|
998
|
+
.filter(([key]) => !/^(?:id|call_id|tool_call_id|type|name)$/i.test(key))
|
|
999
|
+
.map(([, child]) => textFromValue(child, depth + 1))
|
|
1000
|
+
.filter(Boolean)
|
|
1001
|
+
.join('\n');
|
|
1002
|
+
}
|
|
1003
|
+
function hasToolFailureSignal(body) {
|
|
1004
|
+
if (!isRecord(body))
|
|
1005
|
+
return false;
|
|
1006
|
+
for (const call of collectToolCalls(body)) {
|
|
1007
|
+
if (call.declared)
|
|
1008
|
+
continue;
|
|
1009
|
+
if (FAILURE_OUTPUT_RE.test(textFromValue(call)))
|
|
1010
|
+
return true;
|
|
1011
|
+
}
|
|
1012
|
+
return FAILURE_OUTPUT_RE.test(textFromValue(body));
|
|
1013
|
+
}
|
|
1014
|
+
function hasToolCallFailureSignal(body) {
|
|
1015
|
+
if (!isRecord(body))
|
|
1016
|
+
return false;
|
|
1017
|
+
for (const call of collectToolCalls(body)) {
|
|
1018
|
+
if (call.declared)
|
|
1019
|
+
continue;
|
|
1020
|
+
if (FAILURE_OUTPUT_RE.test(textFromValue(call)))
|
|
1021
|
+
return true;
|
|
1022
|
+
}
|
|
1023
|
+
return false;
|
|
1024
|
+
}
|
|
1025
|
+
function bodyIncompleteReasons(name, raw, body, row) {
|
|
1026
|
+
const missing = raw === undefined || raw === null || (typeof raw === 'string' && raw.trim() === '');
|
|
1027
|
+
if (missing) {
|
|
1028
|
+
if (name === 'response' && isStreamRow(row))
|
|
1029
|
+
return ['stream_response_body_missing'];
|
|
1030
|
+
return [`${name}_body_missing`];
|
|
1031
|
+
}
|
|
1032
|
+
if (rawBodyIsInvalidJson(raw))
|
|
1033
|
+
return [`${name}_body_invalid_json`];
|
|
1034
|
+
if (bodyIsEmpty(body))
|
|
1035
|
+
return [`${name}_body_empty`];
|
|
1036
|
+
return [];
|
|
1037
|
+
}
|
|
1038
|
+
function streamTransportIncompleteReasons(row) {
|
|
1039
|
+
if (!isStreamRow(row))
|
|
1040
|
+
return [];
|
|
1041
|
+
const error = errorForRow(row);
|
|
1042
|
+
const streamError = streamErrorForRow(row);
|
|
1043
|
+
const finish = finishReasonForRow(row);
|
|
1044
|
+
const cancelled = streamCancelledForRow(row)
|
|
1045
|
+
|| booleanValue(row['cancelled'])
|
|
1046
|
+
|| booleanValue(row['canceled'])
|
|
1047
|
+
|| /\bcancel(?:led|ed|lation)?\b/i.test(`${finish} ${error} ${streamError}`);
|
|
1048
|
+
if (cancelled)
|
|
1049
|
+
return ['stream_cancelled'];
|
|
1050
|
+
if (error || streamError)
|
|
1051
|
+
return ['stream_error'];
|
|
1052
|
+
if (finish === 'stream_forwarded_unobserved')
|
|
1053
|
+
return ['stream_forwarded_unobserved'];
|
|
1054
|
+
if (row['finished'] === false)
|
|
1055
|
+
return ['stream_unfinished'];
|
|
1056
|
+
return [];
|
|
1057
|
+
}
|
|
1058
|
+
function turnIncompleteReasons(row, request, response) {
|
|
1059
|
+
const reasons = [
|
|
1060
|
+
...bodyIncompleteReasons('request', requestBodyRawForRow(row), request, row),
|
|
1061
|
+
...bodyIncompleteReasons('response', responseBodyRawForRow(row), response, row),
|
|
1062
|
+
...streamTransportIncompleteReasons(row),
|
|
1063
|
+
];
|
|
1064
|
+
if (row['body_truncated'] === true)
|
|
1065
|
+
reasons.push('body_truncated');
|
|
1066
|
+
if (responseEventsTruncated(response))
|
|
1067
|
+
reasons.push('response_body_truncated');
|
|
1068
|
+
return [...new Set(reasons)];
|
|
1069
|
+
}
|
|
1070
|
+
function collectLanguagesFromText(text) {
|
|
1071
|
+
const languages = new Set();
|
|
1072
|
+
const haystack = text.toLowerCase();
|
|
1073
|
+
const probes = [
|
|
1074
|
+
['javascript', /\b(js|javascript|node|npm|pnpm|react|vue)\b/],
|
|
1075
|
+
['typescript', /\b(ts|typescript|tsx)\b/],
|
|
1076
|
+
['python', /\b(python|py|pytest|django|fastapi)\b/],
|
|
1077
|
+
['go', /\b(golang|go test|go\.mod|go (?:code|file|files|parser|package|module))\b/],
|
|
1078
|
+
['rust', /\b(rust|cargo|crates?)\b/],
|
|
1079
|
+
['java', /\b(java|maven|gradle)\b/],
|
|
1080
|
+
];
|
|
1081
|
+
for (const [name, re] of probes)
|
|
1082
|
+
if (re.test(haystack))
|
|
1083
|
+
languages.add(name);
|
|
1084
|
+
return [...languages];
|
|
1085
|
+
}
|
|
1086
|
+
function responseEventsTruncated(response) {
|
|
1087
|
+
return isRecord(response) && (response['events_truncated'] === true
|
|
1088
|
+
|| response['content_truncated'] === true
|
|
1089
|
+
|| response['provider_stream_truncated'] === true
|
|
1090
|
+
|| response['raw_stream_truncated'] === true
|
|
1091
|
+
|| response['truncated'] === true
|
|
1092
|
+
|| typeof response['dropped_event_count'] === 'number');
|
|
1093
|
+
}
|
|
1094
|
+
function capturedRequestBodyTruncated(value) {
|
|
1095
|
+
return isRecord(value)
|
|
1096
|
+
&& value['truncated'] === true
|
|
1097
|
+
&& (value['capture_complete'] === false
|
|
1098
|
+
|| value['body_omitted'] === true
|
|
1099
|
+
|| typeof value['omitted_bytes'] === 'number'
|
|
1100
|
+
|| typeof value['omitted_chars'] === 'number'
|
|
1101
|
+
|| value['reason'] === 'body_exceeds_trace_body_max_bytes'
|
|
1102
|
+
|| value['reason'] === 'body_exceeds_trace_body_max_chars');
|
|
1103
|
+
}
|
|
1104
|
+
function normalizeTraceAttempts(raw) {
|
|
1105
|
+
if (!Array.isArray(raw))
|
|
1106
|
+
return [];
|
|
1107
|
+
const attempts = [];
|
|
1108
|
+
raw.forEach((entry, fallbackIndex) => {
|
|
1109
|
+
if (!isRecord(entry))
|
|
1110
|
+
return;
|
|
1111
|
+
const hasRequestBody = rowHasField(entry, 'requestBody') || rowHasField(entry, 'request_body');
|
|
1112
|
+
const hasResponseBody = rowHasField(entry, 'responseBody') || rowHasField(entry, 'response_body');
|
|
1113
|
+
const requestBody = normalizeBody(rowHasField(entry, 'requestBody') ? entry['requestBody'] : entry['request_body']);
|
|
1114
|
+
const responseBody = normalizeBody(rowHasField(entry, 'responseBody') ? entry['responseBody'] : entry['response_body']);
|
|
1115
|
+
const redactedRequestBody = redactRuntimeStructuredValue(requestBody);
|
|
1116
|
+
const redactedResponseBody = redactRuntimeStructuredValue(responseBody);
|
|
1117
|
+
const upstreamMode = typeof entry['upstream_mode'] === 'string'
|
|
1118
|
+
? entry['upstream_mode']
|
|
1119
|
+
: (typeof entry['upstreamMode'] === 'string' ? entry['upstreamMode'] : undefined);
|
|
1120
|
+
const error = typeof entry['error'] === 'string'
|
|
1121
|
+
? entry['error']
|
|
1122
|
+
: (typeof entry['error_message'] === 'string'
|
|
1123
|
+
? entry['error_message']
|
|
1124
|
+
: (typeof entry['errorMessage'] === 'string' ? entry['errorMessage'] : undefined));
|
|
1125
|
+
const attemptBodyTruncated = entry['body_truncated'] === true
|
|
1126
|
+
|| capturedRequestBodyTruncated(requestBody)
|
|
1127
|
+
|| responseEventsTruncated(responseBody);
|
|
1128
|
+
attempts.push({
|
|
1129
|
+
attempt_index: numberOrNull(entry['attempt_index']) ?? numberOrNull(entry['attemptIndex']) ?? fallbackIndex,
|
|
1130
|
+
...(entry['model'] === null || typeof entry['model'] === 'string' ? { model: entry['model'] } : {}),
|
|
1131
|
+
...(typeof entry['provider'] === 'string' ? { provider: entry['provider'] } : {}),
|
|
1132
|
+
...(upstreamMode !== undefined ? { upstream_mode: upstreamMode } : {}),
|
|
1133
|
+
...(rowHasField(entry, 'status') ? { status: numberOrNull(entry['status']) } : {}),
|
|
1134
|
+
...(error !== undefined ? { error } : {}),
|
|
1135
|
+
...(hasRequestBody ? { request_body: redactedRequestBody } : {}),
|
|
1136
|
+
...(hasResponseBody ? { response_body: redactedResponseBody } : {}),
|
|
1137
|
+
...(attemptBodyTruncated ? { body_truncated: true } : {}),
|
|
1138
|
+
});
|
|
1139
|
+
});
|
|
1140
|
+
return attempts;
|
|
1141
|
+
}
|
|
1142
|
+
function requestBodyRawForRow(row) {
|
|
1143
|
+
return rowHasField(row, 'requestBody') ? row['requestBody'] : row['request_body'];
|
|
1144
|
+
}
|
|
1145
|
+
function responseBodyRawForRow(row) {
|
|
1146
|
+
return rowHasField(row, 'responseBody') ? row['responseBody'] : row['response_body'];
|
|
1147
|
+
}
|
|
1148
|
+
function rowHasField(row, key) {
|
|
1149
|
+
return Object.prototype.hasOwnProperty.call(row, key);
|
|
1150
|
+
}
|
|
1151
|
+
function firstPresent(row, keys) {
|
|
1152
|
+
for (const key of keys)
|
|
1153
|
+
if (rowHasField(row, key))
|
|
1154
|
+
return row[key];
|
|
1155
|
+
return undefined;
|
|
1156
|
+
}
|
|
1157
|
+
function firstPresentFrom(records, keys) {
|
|
1158
|
+
for (const record of records) {
|
|
1159
|
+
const value = firstPresent(record, keys);
|
|
1160
|
+
if (value !== undefined)
|
|
1161
|
+
return value;
|
|
1162
|
+
}
|
|
1163
|
+
return undefined;
|
|
1164
|
+
}
|
|
1165
|
+
function parsedObject(value) {
|
|
1166
|
+
const parsed = normalizeBody(value);
|
|
1167
|
+
return isRecord(parsed) ? parsed : undefined;
|
|
1168
|
+
}
|
|
1169
|
+
function createRawRecordStore() {
|
|
1170
|
+
const objectIndexes = new WeakMap();
|
|
1171
|
+
const primitiveIndexes = new Map();
|
|
1172
|
+
const values = [];
|
|
1173
|
+
return {
|
|
1174
|
+
add(value) {
|
|
1175
|
+
if (value === undefined)
|
|
1176
|
+
return undefined;
|
|
1177
|
+
if (value && typeof value === 'object') {
|
|
1178
|
+
if (objectIndexes.has(value))
|
|
1179
|
+
return objectIndexes.get(value);
|
|
1180
|
+
const index = values.length;
|
|
1181
|
+
objectIndexes.set(value, index);
|
|
1182
|
+
values.push(redactRuntimeStructuredValue(value));
|
|
1183
|
+
return index;
|
|
1184
|
+
}
|
|
1185
|
+
const key = `${typeof value}:${String(value)}`;
|
|
1186
|
+
if (primitiveIndexes.has(key))
|
|
1187
|
+
return primitiveIndexes.get(key);
|
|
1188
|
+
const index = values.length;
|
|
1189
|
+
primitiveIndexes.set(key, index);
|
|
1190
|
+
values.push(redactRuntimeStructuredValue(value));
|
|
1191
|
+
return index;
|
|
1192
|
+
},
|
|
1193
|
+
values() {
|
|
1194
|
+
return values;
|
|
1195
|
+
},
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
function traceMetadataSources(row) {
|
|
1199
|
+
return [
|
|
1200
|
+
row,
|
|
1201
|
+
parsedObject(row['metadata']),
|
|
1202
|
+
parsedObject(row['meta']),
|
|
1203
|
+
].filter((value) => isRecord(value));
|
|
1204
|
+
}
|
|
1205
|
+
function tracePassthroughFields(row) {
|
|
1206
|
+
const sources = traceMetadataSources(row);
|
|
1207
|
+
const metadata = rowHasField(row, 'metadata')
|
|
1208
|
+
? normalizeBody(row['metadata'])
|
|
1209
|
+
: (rowHasField(row, 'meta') ? normalizeBody(row['meta']) : undefined);
|
|
1210
|
+
const usage = firstPresentFrom(sources, ['usage']);
|
|
1211
|
+
const risk = firstPresentFrom(sources, ['risk']);
|
|
1212
|
+
const fidelity = firstPresentFrom(sources, ['fidelity']);
|
|
1213
|
+
const confidentiality = firstPresentFrom(sources, ['confidentiality']);
|
|
1214
|
+
const requestHeaders = firstPresent(row, ['request_headers', 'requestHeaders']);
|
|
1215
|
+
const responseHeaders = firstPresent(row, ['response_headers', 'responseHeaders']);
|
|
1216
|
+
const transport = firstPresent(row, ['transport']);
|
|
1217
|
+
const transportMetadata = firstPresent(row, ['transport_metadata', 'transportMetadata']);
|
|
1218
|
+
const ttfb = numberOrNull(row['ttfb_ms']) ?? numberOrNull(row['ttfbMs']);
|
|
1219
|
+
return {
|
|
1220
|
+
...(ttfb !== null ? { ttfb_ms: ttfb } : {}),
|
|
1221
|
+
...(metadata !== undefined ? { metadata: redactRuntimeStructuredValue(metadata) } : {}),
|
|
1222
|
+
...(usage !== undefined ? { usage: redactRuntimeStructuredValue(usage) } : {}),
|
|
1223
|
+
...(risk !== undefined ? { risk: redactRuntimeStructuredValue(risk) } : {}),
|
|
1224
|
+
...(fidelity !== undefined ? { fidelity: redactRuntimeStructuredValue(fidelity) } : {}),
|
|
1225
|
+
...(confidentiality !== undefined ? { confidentiality: redactRuntimeStructuredValue(confidentiality) } : {}),
|
|
1226
|
+
...(requestHeaders !== undefined ? { request_headers: redactRuntimeStructuredValue(normalizeBody(requestHeaders)) } : {}),
|
|
1227
|
+
...(responseHeaders !== undefined ? { response_headers: redactRuntimeStructuredValue(normalizeBody(responseHeaders)) } : {}),
|
|
1228
|
+
...(transport !== undefined ? { transport: redactRuntimeStructuredValue(normalizeBody(transport)) } : {}),
|
|
1229
|
+
...(transportMetadata !== undefined ? { transport_metadata: redactRuntimeStructuredValue(normalizeBody(transportMetadata)) } : {}),
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
function toolCallStatsKey(call) {
|
|
1233
|
+
if (call.declared)
|
|
1234
|
+
return '';
|
|
1235
|
+
const name = call.name || 'unknown';
|
|
1236
|
+
if (call.id)
|
|
1237
|
+
return `${name}:${call.id}`;
|
|
1238
|
+
return '';
|
|
1239
|
+
}
|
|
1240
|
+
function dedupeToolCalls(calls) {
|
|
1241
|
+
const seen = new Set();
|
|
1242
|
+
const out = [];
|
|
1243
|
+
for (const call of calls) {
|
|
1244
|
+
const key = toolCallStatsKey(call);
|
|
1245
|
+
if (key) {
|
|
1246
|
+
if (seen.has(key))
|
|
1247
|
+
continue;
|
|
1248
|
+
seen.add(key);
|
|
1249
|
+
}
|
|
1250
|
+
out.push(call);
|
|
1251
|
+
}
|
|
1252
|
+
return out;
|
|
1253
|
+
}
|
|
1254
|
+
function buildTrajectoryFromRows(sessionId, rows) {
|
|
1255
|
+
const sorted = [...rows].sort((a, b) => (Date.parse(timestampForRow(a)) || 0) - (Date.parse(timestampForRow(b)) || 0));
|
|
1256
|
+
const turns = [];
|
|
1257
|
+
const toolTypes = {};
|
|
1258
|
+
const testCommands = new Set();
|
|
1259
|
+
const languages = new Set();
|
|
1260
|
+
const providers = new Set();
|
|
1261
|
+
const endpoints = new Set();
|
|
1262
|
+
let inputTokens = 0;
|
|
1263
|
+
let outputTokens = 0;
|
|
1264
|
+
let hasToolCalls = false;
|
|
1265
|
+
let hasCodeEdit = false;
|
|
1266
|
+
let sawFailureSignal = false;
|
|
1267
|
+
let sawCorrectionAfterFailure = false;
|
|
1268
|
+
let hasTruncatedStream = false;
|
|
1269
|
+
let hasIncompleteTurns = false;
|
|
1270
|
+
let task = '';
|
|
1271
|
+
let trajectoryMetadata;
|
|
1272
|
+
let trajectoryUsage;
|
|
1273
|
+
let trajectoryRisk;
|
|
1274
|
+
let trajectoryFidelity;
|
|
1275
|
+
let trajectoryConfidentiality;
|
|
1276
|
+
const sourceRecordStore = createRawRecordStore();
|
|
1277
|
+
const rawRowStore = createRawRecordStore();
|
|
1278
|
+
const countedToolCalls = new Set();
|
|
1279
|
+
sorted.forEach((row, idx) => {
|
|
1280
|
+
const passthrough = tracePassthroughFields(row);
|
|
1281
|
+
const sourceRecordIndex = sourceRecordStore.add(row);
|
|
1282
|
+
const rawRowIndex = rawRowStore.add(row);
|
|
1283
|
+
if (trajectoryMetadata === undefined && passthrough['metadata'] !== undefined)
|
|
1284
|
+
trajectoryMetadata = passthrough['metadata'];
|
|
1285
|
+
if (trajectoryUsage === undefined && passthrough['usage'] !== undefined)
|
|
1286
|
+
trajectoryUsage = passthrough['usage'];
|
|
1287
|
+
if (trajectoryRisk === undefined && passthrough['risk'] !== undefined)
|
|
1288
|
+
trajectoryRisk = passthrough['risk'];
|
|
1289
|
+
if (trajectoryFidelity === undefined && passthrough['fidelity'] !== undefined)
|
|
1290
|
+
trajectoryFidelity = passthrough['fidelity'];
|
|
1291
|
+
if (trajectoryConfidentiality === undefined && passthrough['confidentiality'] !== undefined)
|
|
1292
|
+
trajectoryConfidentiality = passthrough['confidentiality'];
|
|
1293
|
+
const request = normalizeBody(requestBodyRawForRow(row));
|
|
1294
|
+
const response = normalizeBody(responseBodyRawForRow(row));
|
|
1295
|
+
const redactedRequest = redactRuntimeStructuredValue(request);
|
|
1296
|
+
const redactedResponse = redactRuntimeStructuredValue(response);
|
|
1297
|
+
const attempts = normalizeTraceAttempts(row['attempts']);
|
|
1298
|
+
const attemptsBodyTruncated = attempts.some((attempt) => attempt.body_truncated === true);
|
|
1299
|
+
const incompleteReasons = turnIncompleteReasons(row, request, response);
|
|
1300
|
+
if (attemptsBodyTruncated && !incompleteReasons.includes('body_truncated'))
|
|
1301
|
+
incompleteReasons.push('body_truncated');
|
|
1302
|
+
if (incompleteReasons.length > 0)
|
|
1303
|
+
hasIncompleteTurns = true;
|
|
1304
|
+
const requestToolCalls = collectToolCalls(request);
|
|
1305
|
+
const requestDeclaredTools = requestToolCalls.filter((call) => call.declared);
|
|
1306
|
+
const requestActualToolCalls = requestToolCalls.filter((call) => !call.declared);
|
|
1307
|
+
const rowLevelToolCalls = collectRowLevelToolCalls(row);
|
|
1308
|
+
const rowDeclaredTools = rowLevelToolCalls.filter((call) => call.declared);
|
|
1309
|
+
const rowActualToolCalls = rowLevelToolCalls.filter((call) => !call.declared);
|
|
1310
|
+
const responseActualToolCalls = collectToolCalls(response).filter((call) => !call.declared);
|
|
1311
|
+
const actualToolCalls = dedupeToolCalls(responseActualToolCalls.concat(rowActualToolCalls, requestActualToolCalls));
|
|
1312
|
+
const toolCalls = requestDeclaredTools.concat(rowDeclaredTools, actualToolCalls);
|
|
1313
|
+
const redactedToolCalls = toolCalls.map(redactNormalizedToolCall);
|
|
1314
|
+
const previousFailureSignal = sawFailureSignal;
|
|
1315
|
+
const requestFailureSignal = hasToolCallFailureSignal(request);
|
|
1316
|
+
const responseFailureSignal = hasToolFailureSignal(response);
|
|
1317
|
+
let correctionToolThisTurn = false;
|
|
1318
|
+
for (const call of toolCalls) {
|
|
1319
|
+
if (call.declared)
|
|
1320
|
+
continue;
|
|
1321
|
+
const statsKey = toolCallStatsKey(call);
|
|
1322
|
+
if (statsKey) {
|
|
1323
|
+
if (countedToolCalls.has(statsKey))
|
|
1324
|
+
continue;
|
|
1325
|
+
countedToolCalls.add(statsKey);
|
|
1326
|
+
}
|
|
1327
|
+
hasToolCalls = true;
|
|
1328
|
+
toolTypes[call.name] = (toolTypes[call.name] ?? 0) + 1;
|
|
1329
|
+
const looksLikeCodeEdit = toolCallLooksLikeCodeEdit(call);
|
|
1330
|
+
if (looksLikeCodeEdit)
|
|
1331
|
+
hasCodeEdit = true;
|
|
1332
|
+
const testCommand = toolCallTestCommand(call);
|
|
1333
|
+
if (testCommand) {
|
|
1334
|
+
const redactedTestCommand = toolCallTestCommand(redactNormalizedToolCall(call));
|
|
1335
|
+
testCommands.add(redactedTestCommand || redactString(testCommand));
|
|
1336
|
+
}
|
|
1337
|
+
if (looksLikeCodeEdit || testCommand)
|
|
1338
|
+
correctionToolThisTurn = true;
|
|
1339
|
+
}
|
|
1340
|
+
const provider = providerForRow(row);
|
|
1341
|
+
const endpoint = endpointForRow(row);
|
|
1342
|
+
providers.add(provider);
|
|
1343
|
+
endpoints.add(endpoint);
|
|
1344
|
+
if (!task)
|
|
1345
|
+
task = redactString(extractTask(request));
|
|
1346
|
+
for (const lang of collectLanguagesFromText(`${JSON.stringify(request ?? {})}\n${JSON.stringify(response ?? {})}`))
|
|
1347
|
+
languages.add(lang);
|
|
1348
|
+
const input = usageTokens(row, 'input_tokens');
|
|
1349
|
+
const output = usageTokens(row, 'output_tokens');
|
|
1350
|
+
inputTokens += input;
|
|
1351
|
+
outputTokens += output;
|
|
1352
|
+
const status = statusForRow(row);
|
|
1353
|
+
const failureSignal = ((status !== null && status >= 400)
|
|
1354
|
+
|| stringValue(row['error'])
|
|
1355
|
+
|| stringValue(row['errorMessage'])
|
|
1356
|
+
|| requestFailureSignal
|
|
1357
|
+
|| responseFailureSignal);
|
|
1358
|
+
const responseText = textFromValue(response).trim();
|
|
1359
|
+
const correctionTextThisTurn = requestFailureSignal && !responseFailureSignal && responseText.length > 0;
|
|
1360
|
+
if ((previousFailureSignal && (correctionToolThisTurn || (!failureSignal && responseText.length > 0)))
|
|
1361
|
+
|| (requestFailureSignal && (correctionToolThisTurn || correctionTextThisTurn))) {
|
|
1362
|
+
sawCorrectionAfterFailure = true;
|
|
1363
|
+
}
|
|
1364
|
+
if (failureSignal)
|
|
1365
|
+
sawFailureSignal = true;
|
|
1366
|
+
const truncatedStream = responseEventsTruncated(response) || row['body_truncated'] === true || attemptsBodyTruncated;
|
|
1367
|
+
if (truncatedStream)
|
|
1368
|
+
hasTruncatedStream = true;
|
|
1369
|
+
turns.push({
|
|
1370
|
+
turn_index: idx,
|
|
1371
|
+
request_id: requestIdForRow(row),
|
|
1372
|
+
timestamp: timestampForRow(row),
|
|
1373
|
+
provider,
|
|
1374
|
+
endpoint,
|
|
1375
|
+
model: modelForRow(row),
|
|
1376
|
+
...(chosenModelForRow(row) ? { chosen_model: chosenModelForRow(row) } : {}),
|
|
1377
|
+
...(originalModelForRow(row) ? { original_model: originalModelForRow(row) } : {}),
|
|
1378
|
+
status,
|
|
1379
|
+
duration_ms: durationForRow(row),
|
|
1380
|
+
is_stream: isStreamRow(row),
|
|
1381
|
+
finish_reason: finishReasonForRow(row),
|
|
1382
|
+
response_id: responseIdForRow(row),
|
|
1383
|
+
previous_response_id: previousResponseIdForRow(row),
|
|
1384
|
+
input_tokens: input,
|
|
1385
|
+
output_tokens: output,
|
|
1386
|
+
...passthrough,
|
|
1387
|
+
...(sourceRecordIndex !== undefined ? { source_record_index: sourceRecordIndex } : {}),
|
|
1388
|
+
...(rawRowIndex !== undefined ? { raw_row_index: rawRowIndex } : {}),
|
|
1389
|
+
request_body: redactedRequest,
|
|
1390
|
+
response_body: redactedResponse,
|
|
1391
|
+
...(rowHasField(row, 'reasoning') ? { reasoning: row['reasoning'] } : {}),
|
|
1392
|
+
...(rowHasField(row, 'diff') ? { diff: row['diff'] } : {}),
|
|
1393
|
+
...(rowHasField(row, 'validation') ? { validation: row['validation'] } : {}),
|
|
1394
|
+
tool_calls: redactedToolCalls,
|
|
1395
|
+
...(typeof row['redaction'] === 'string' ? { redaction: row['redaction'] } : {}),
|
|
1396
|
+
...(row['body_truncated'] === true || attemptsBodyTruncated ? { body_truncated: true } : {}),
|
|
1397
|
+
...(attempts.length > 0 ? { attempts } : {}),
|
|
1398
|
+
...(truncatedStream ? { response_events_truncated: true } : {}),
|
|
1399
|
+
...(errorForRow(row) ? { error: redactString(errorForRow(row)) } : {}),
|
|
1400
|
+
...(streamErrorForRow(row) ? { stream_error: redactString(streamErrorForRow(row)) } : {}),
|
|
1401
|
+
...(streamCancelledForRow(row) ? { stream_cancelled: true } : {}),
|
|
1402
|
+
complete: incompleteReasons.length === 0,
|
|
1403
|
+
...(incompleteReasons.length > 0 ? { incomplete_reasons: incompleteReasons } : {}),
|
|
1404
|
+
});
|
|
1405
|
+
});
|
|
1406
|
+
return {
|
|
1407
|
+
schema: CODING_TRAJECTORY_SCHEMA,
|
|
1408
|
+
session_id: sessionId || 'unknown',
|
|
1409
|
+
source_kind: 'proxy_trace',
|
|
1410
|
+
...(trajectoryMetadata !== undefined ? { metadata: trajectoryMetadata } : {}),
|
|
1411
|
+
...(trajectoryUsage !== undefined ? { usage: trajectoryUsage } : {}),
|
|
1412
|
+
...(trajectoryRisk !== undefined ? { risk: trajectoryRisk } : {}),
|
|
1413
|
+
...(trajectoryFidelity !== undefined ? { fidelity: trajectoryFidelity } : {}),
|
|
1414
|
+
...(trajectoryConfidentiality !== undefined ? { confidentiality: trajectoryConfidentiality } : {}),
|
|
1415
|
+
...(sourceRecordStore.values().length > 0 ? { source_record: sourceRecordStore.values()[0], source_records: sourceRecordStore.values() } : {}),
|
|
1416
|
+
...(rawRowStore.values().length > 0 ? { raw_rows: rawRowStore.values() } : {}),
|
|
1417
|
+
task,
|
|
1418
|
+
providers: [...providers].filter(Boolean),
|
|
1419
|
+
endpoints: [...endpoints].filter(Boolean),
|
|
1420
|
+
languages: [...languages],
|
|
1421
|
+
stats: {
|
|
1422
|
+
turns: turns.length,
|
|
1423
|
+
input_tokens: inputTokens,
|
|
1424
|
+
output_tokens: outputTokens,
|
|
1425
|
+
total_tokens: inputTokens + outputTokens,
|
|
1426
|
+
tool_call_count: Object.values(toolTypes).reduce((sum, n) => sum + n, 0),
|
|
1427
|
+
tool_types: toolTypes,
|
|
1428
|
+
has_tool_calls: hasToolCalls,
|
|
1429
|
+
has_code_edit: hasCodeEdit,
|
|
1430
|
+
has_test_execution: testCommands.size > 0,
|
|
1431
|
+
test_commands: [...testCommands],
|
|
1432
|
+
has_failure_correction: sawFailureSignal && sawCorrectionAfterFailure,
|
|
1433
|
+
has_truncated_stream: hasTruncatedStream,
|
|
1434
|
+
has_incomplete_turns: hasIncompleteTurns,
|
|
1435
|
+
},
|
|
1436
|
+
turns,
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
function runtimeBody(turn, sessionModel, sessionTools) {
|
|
1440
|
+
const body = {
|
|
1441
|
+
source: 'runtime_session',
|
|
1442
|
+
role: turn.role,
|
|
1443
|
+
};
|
|
1444
|
+
const model = stringValue(turn.model) || sessionModel;
|
|
1445
|
+
if (model)
|
|
1446
|
+
body['model'] = model;
|
|
1447
|
+
if ((turn.role === 'user' || turn.role === 'system') && sessionTools !== undefined) {
|
|
1448
|
+
body['tools_ref'] = 'session_tools';
|
|
1449
|
+
}
|
|
1450
|
+
if (turn.text)
|
|
1451
|
+
body['text'] = redactString(turn.text);
|
|
1452
|
+
if (turn.reasoning === true)
|
|
1453
|
+
body['reasoning'] = true;
|
|
1454
|
+
if (turn.reasoningSignature)
|
|
1455
|
+
body['reasoning_signature'] = turn.reasoningSignature;
|
|
1456
|
+
if (turn.encryptedSignature)
|
|
1457
|
+
body['encrypted_signature'] = turn.encryptedSignature;
|
|
1458
|
+
if (turn.encryptedContent !== undefined)
|
|
1459
|
+
body['encrypted_content'] = turn.encryptedContent;
|
|
1460
|
+
if (turn.metadata !== undefined)
|
|
1461
|
+
body['metadata'] = redactRuntimeStructuredValue(turn.metadata);
|
|
1462
|
+
if (turn.usage !== undefined)
|
|
1463
|
+
body['usage_raw'] = redactRuntimeStructuredValue(turn.usage);
|
|
1464
|
+
if (turn.risk !== undefined)
|
|
1465
|
+
body['risk'] = redactRuntimeStructuredValue(turn.risk);
|
|
1466
|
+
if (turn.fidelity !== undefined)
|
|
1467
|
+
body['fidelity'] = redactRuntimeStructuredValue(turn.fidelity);
|
|
1468
|
+
if (turn.confidentiality !== undefined)
|
|
1469
|
+
body['confidentiality'] = redactRuntimeStructuredValue(turn.confidentiality);
|
|
1470
|
+
if (turn.toolName)
|
|
1471
|
+
body['tool_name'] = turn.toolName;
|
|
1472
|
+
if (turn.toolUseId)
|
|
1473
|
+
body['tool_use_id'] = turn.toolUseId;
|
|
1474
|
+
if (turn.toolInput !== undefined)
|
|
1475
|
+
body['tool_input'] = redactRuntimeStructuredValue(turn.toolInput);
|
|
1476
|
+
if (turn.toolResult !== undefined)
|
|
1477
|
+
body['tool_result'] = redactRuntimeStructuredValue(turn.toolResult);
|
|
1478
|
+
if (turn.errorMessage)
|
|
1479
|
+
body['error'] = redactString(turn.errorMessage);
|
|
1480
|
+
const usage = {};
|
|
1481
|
+
if (turn.inputTokens !== undefined)
|
|
1482
|
+
usage['input_tokens'] = tokenCount(turn.inputTokens);
|
|
1483
|
+
if (turn.outputTokens !== undefined)
|
|
1484
|
+
usage['output_tokens'] = tokenCount(turn.outputTokens);
|
|
1485
|
+
if (Object.keys(usage).length > 0)
|
|
1486
|
+
body['usage'] = usage;
|
|
1487
|
+
return body;
|
|
1488
|
+
}
|
|
1489
|
+
function runtimeTurnPassthroughFields(turn, sourceRecordStore, rawRowStore) {
|
|
1490
|
+
const sourceRecordIndex = sourceRecordStore.add(turn.sourceRecord);
|
|
1491
|
+
const rawRowIndex = rawRowStore.add(turn.rawRow);
|
|
1492
|
+
return {
|
|
1493
|
+
...(turn.metadata !== undefined ? { metadata: redactRuntimeStructuredValue(turn.metadata) } : {}),
|
|
1494
|
+
...(turn.usage !== undefined ? { usage: redactRuntimeStructuredValue(turn.usage) } : {}),
|
|
1495
|
+
...(turn.risk !== undefined ? { risk: redactRuntimeStructuredValue(turn.risk) } : {}),
|
|
1496
|
+
...(turn.fidelity !== undefined ? { fidelity: redactRuntimeStructuredValue(turn.fidelity) } : {}),
|
|
1497
|
+
...(turn.confidentiality !== undefined ? { confidentiality: redactRuntimeStructuredValue(turn.confidentiality) } : {}),
|
|
1498
|
+
...(sourceRecordIndex !== undefined ? { source_record_index: sourceRecordIndex } : {}),
|
|
1499
|
+
...(rawRowIndex !== undefined ? { raw_row_index: rawRowIndex } : {}),
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
function runtimeSessionPassthroughFields(input, sourceRecordStore, rawRowStore) {
|
|
1503
|
+
return {
|
|
1504
|
+
...(input.clientSource ? { client_source: input.clientSource } : {}),
|
|
1505
|
+
...(input.systemPrompt ? { system_prompt: redactString(input.systemPrompt) } : {}),
|
|
1506
|
+
...(input.metadata !== undefined ? { metadata: redactRuntimeStructuredValue(input.metadata) } : {}),
|
|
1507
|
+
...(input.usage !== undefined ? { usage: redactRuntimeStructuredValue(input.usage) } : {}),
|
|
1508
|
+
...(input.risk !== undefined ? { risk: redactRuntimeStructuredValue(input.risk) } : {}),
|
|
1509
|
+
...(input.fidelity !== undefined ? { fidelity: redactRuntimeStructuredValue(input.fidelity) } : {}),
|
|
1510
|
+
...(input.confidentiality !== undefined ? { confidentiality: redactRuntimeStructuredValue(input.confidentiality) } : {}),
|
|
1511
|
+
...(sourceRecordStore.values().length > 0 ? { source_record: sourceRecordStore.values()[0], source_records: sourceRecordStore.values() } : {}),
|
|
1512
|
+
...(rawRowStore.values().length > 0 ? { raw_rows: rawRowStore.values() } : {}),
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
function runtimeTurnHasContent(turn) {
|
|
1516
|
+
return Boolean(turn.text
|
|
1517
|
+
|| turn.toolName
|
|
1518
|
+
|| turn.toolInput !== undefined
|
|
1519
|
+
|| turn.toolResult !== undefined
|
|
1520
|
+
|| turn.errorMessage
|
|
1521
|
+
|| turn.reasoning === true
|
|
1522
|
+
|| turn.model
|
|
1523
|
+
|| turn.inputTokens !== undefined
|
|
1524
|
+
|| turn.outputTokens !== undefined
|
|
1525
|
+
|| turn.reasoningSignature
|
|
1526
|
+
|| turn.encryptedSignature
|
|
1527
|
+
|| turn.encryptedContent !== undefined
|
|
1528
|
+
|| turn.metadata !== undefined
|
|
1529
|
+
|| turn.usage !== undefined
|
|
1530
|
+
|| turn.risk !== undefined
|
|
1531
|
+
|| turn.fidelity !== undefined
|
|
1532
|
+
|| turn.confidentiality !== undefined
|
|
1533
|
+
|| turn.sourceRecord !== undefined
|
|
1534
|
+
|| turn.rawRow !== undefined);
|
|
1535
|
+
}
|
|
1536
|
+
function runtimeToolCall(turn) {
|
|
1537
|
+
if (turn.role !== 'assistant' || !turn.toolName)
|
|
1538
|
+
return null;
|
|
1539
|
+
const input = turn.toolInput !== undefined ? redactRuntimeStructuredValue(turn.toolInput) : undefined;
|
|
1540
|
+
return {
|
|
1541
|
+
id: turn.toolUseId ?? '',
|
|
1542
|
+
name: turn.toolName,
|
|
1543
|
+
...(input !== undefined ? { input } : {}),
|
|
1544
|
+
bytes: byteLength({ name: turn.toolName, input }),
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
function runtimeTextForSignals(turn) {
|
|
1548
|
+
return [
|
|
1549
|
+
turn.text,
|
|
1550
|
+
typeof turn.toolInput === 'string' ? turn.toolInput : JSON.stringify(turn.toolInput ?? ''),
|
|
1551
|
+
typeof turn.toolResult === 'string' ? turn.toolResult : JSON.stringify(turn.toolResult ?? ''),
|
|
1552
|
+
turn.errorMessage,
|
|
1553
|
+
].filter(Boolean).join('\n');
|
|
1554
|
+
}
|
|
1555
|
+
function runtimeSessionId(input) {
|
|
1556
|
+
const explicit = stringValue(input.sessionId);
|
|
1557
|
+
if (explicit)
|
|
1558
|
+
return explicit;
|
|
1559
|
+
const sourcePath = stringValue(input.sourcePath);
|
|
1560
|
+
const base = sourcePath.split(/[\\/]/).filter(Boolean).pop() ?? '';
|
|
1561
|
+
return base.replace(/\.jsonl?$/i, '') || 'unknown';
|
|
1562
|
+
}
|
|
1563
|
+
function nativeCallHasField(call, key) {
|
|
1564
|
+
return Object.prototype.hasOwnProperty.call(call, key);
|
|
1565
|
+
}
|
|
1566
|
+
function runtimeNativeCalls(input) {
|
|
1567
|
+
const calls = [];
|
|
1568
|
+
(input.nativeCalls ?? []).forEach((call) => {
|
|
1569
|
+
if (!isRecord(call))
|
|
1570
|
+
return;
|
|
1571
|
+
const hasRequestBody = nativeCallHasField(call, 'request_body')
|
|
1572
|
+
|| nativeCallHasField(call, 'request');
|
|
1573
|
+
const hasResponseBody = nativeCallHasField(call, 'response_body')
|
|
1574
|
+
|| nativeCallHasField(call, 'response');
|
|
1575
|
+
if (!hasRequestBody && !hasResponseBody)
|
|
1576
|
+
return;
|
|
1577
|
+
const requestBody = nativeCallHasField(call, 'request_body') ? call.request_body : call.request;
|
|
1578
|
+
const responseBody = nativeCallHasField(call, 'response_body') ? call.response_body : call.response;
|
|
1579
|
+
calls.push({
|
|
1580
|
+
call_index: calls.length,
|
|
1581
|
+
...(stringValue(call.provider) ? { provider: stringValue(call.provider) } : {}),
|
|
1582
|
+
...(stringValue(call.timestamp) ? { timestamp: stringValue(call.timestamp) } : {}),
|
|
1583
|
+
...(stringValue(call.request_time) ? { request_time: stringValue(call.request_time) } : {}),
|
|
1584
|
+
...(stringValue(call.response_time) ? { response_time: stringValue(call.response_time) } : {}),
|
|
1585
|
+
...(numberOrNull(call.ttfb_ms) !== null ? { ttfb_ms: numberOrNull(call.ttfb_ms) } : {}),
|
|
1586
|
+
...(call.request_headers !== undefined ? { request_headers: redactRuntimeStructuredValue(normalizeBody(call.request_headers)) } : {}),
|
|
1587
|
+
...(call.response_headers !== undefined ? { response_headers: redactRuntimeStructuredValue(normalizeBody(call.response_headers)) } : {}),
|
|
1588
|
+
...(call.transport !== undefined ? { transport: redactRuntimeStructuredValue(normalizeBody(call.transport)) } : {}),
|
|
1589
|
+
...(call.transport_metadata !== undefined ? { transport_metadata: redactRuntimeStructuredValue(normalizeBody(call.transport_metadata)) } : {}),
|
|
1590
|
+
...(hasRequestBody ? { request_body: redactRuntimeStructuredValue(normalizeBody(requestBody)) } : {}),
|
|
1591
|
+
...(hasResponseBody ? { response_body: redactRuntimeStructuredValue(normalizeBody(responseBody)) } : {}),
|
|
1592
|
+
...(call.metadata !== undefined ? { metadata: redactRuntimeStructuredValue(call.metadata) } : {}),
|
|
1593
|
+
...(call.usage !== undefined ? { usage: redactRuntimeStructuredValue(call.usage) } : {}),
|
|
1594
|
+
...(call.risk !== undefined ? { risk: redactRuntimeStructuredValue(call.risk) } : {}),
|
|
1595
|
+
...(call.fidelity !== undefined ? { fidelity: redactRuntimeStructuredValue(call.fidelity) } : {}),
|
|
1596
|
+
...(call.confidentiality !== undefined ? { confidentiality: redactRuntimeStructuredValue(call.confidentiality) } : {}),
|
|
1597
|
+
...(call.sourceRecord !== undefined ? { source_record: redactRuntimeStructuredValue(call.sourceRecord) } : {}),
|
|
1598
|
+
...(call.rawRow !== undefined ? { raw_row: redactRuntimeStructuredValue(call.rawRow) } : {}),
|
|
1599
|
+
redaction: RUNTIME_SESSION_REDACTION,
|
|
1600
|
+
});
|
|
1601
|
+
});
|
|
1602
|
+
return calls;
|
|
1603
|
+
}
|
|
1604
|
+
export function buildCodingTrajectoryFromSessionLog(input) {
|
|
1605
|
+
const sourceAgent = stringValue(input.sourceAgent) || 'unknown-runtime';
|
|
1606
|
+
const provider = stringValue(input.provider) || sourceAgent;
|
|
1607
|
+
const sessionModel = stringValue(input.model);
|
|
1608
|
+
const sessionTools = input.tools;
|
|
1609
|
+
const sourcePath = stringValue(input.sourcePath);
|
|
1610
|
+
const sessionId = runtimeSessionId(input);
|
|
1611
|
+
const sessionIncompleteReasons = [...new Set((input.incompleteReasons ?? [])
|
|
1612
|
+
.map((reason) => stringValue(reason).trim())
|
|
1613
|
+
.filter(Boolean))];
|
|
1614
|
+
const sessionDeclaredTools = sessionTools !== undefined
|
|
1615
|
+
? collectToolCalls({ tools: redactRuntimeStructuredValue(sessionTools) }).filter((call) => call.declared)
|
|
1616
|
+
: [];
|
|
1617
|
+
const turns = [];
|
|
1618
|
+
const toolTypes = {};
|
|
1619
|
+
const testCommands = new Set();
|
|
1620
|
+
const languages = new Set();
|
|
1621
|
+
let task = '';
|
|
1622
|
+
let hasToolCalls = false;
|
|
1623
|
+
let hasCodeEdit = false;
|
|
1624
|
+
let hasFailureCorrection = false;
|
|
1625
|
+
let hasIncompleteTurns = false;
|
|
1626
|
+
let inputTokens = 0;
|
|
1627
|
+
let outputTokens = 0;
|
|
1628
|
+
const nativeCalls = runtimeNativeCalls(input);
|
|
1629
|
+
const sourceRecordStore = createRawRecordStore();
|
|
1630
|
+
const rawRowStore = createRawRecordStore();
|
|
1631
|
+
sourceRecordStore.add(input.sourceRecord);
|
|
1632
|
+
for (const row of input.rawRows ?? [])
|
|
1633
|
+
rawRowStore.add(row);
|
|
1634
|
+
input.turns.forEach((turn, idx) => {
|
|
1635
|
+
const isSignalTurn = turn.isMeta !== true;
|
|
1636
|
+
if (isSignalTurn && !task && turn.role === 'user' && turn.text)
|
|
1637
|
+
task = redactString(turn.text).slice(0, 1000);
|
|
1638
|
+
if (isSignalTurn)
|
|
1639
|
+
for (const lang of collectLanguagesFromText(runtimeTextForSignals(turn)))
|
|
1640
|
+
languages.add(lang);
|
|
1641
|
+
const inputTokenCount = tokenCount(turn.inputTokens);
|
|
1642
|
+
const outputTokenCount = tokenCount(turn.outputTokens);
|
|
1643
|
+
inputTokens += inputTokenCount;
|
|
1644
|
+
outputTokens += outputTokenCount;
|
|
1645
|
+
const call = runtimeToolCall(turn);
|
|
1646
|
+
const isAssistantSide = turn.role === 'assistant' || turn.role === 'tool';
|
|
1647
|
+
const toolCalls = (!isAssistantSide ? sessionDeclaredTools : []).concat(call ? [call] : []);
|
|
1648
|
+
if (isSignalTurn && call) {
|
|
1649
|
+
hasToolCalls = true;
|
|
1650
|
+
toolTypes[call.name] = (toolTypes[call.name] ?? 0) + 1;
|
|
1651
|
+
if (toolCallLooksLikeCodeEdit(call))
|
|
1652
|
+
hasCodeEdit = true;
|
|
1653
|
+
const testCommand = toolCallTestCommand(call);
|
|
1654
|
+
if (testCommand)
|
|
1655
|
+
testCommands.add(testCommand);
|
|
1656
|
+
}
|
|
1657
|
+
if (isSignalTurn && (turn.errorMessage || FAILURE_OUTPUT_RE.test(runtimeTextForSignals(turn))))
|
|
1658
|
+
hasFailureCorrection = true;
|
|
1659
|
+
const incompleteReasons = [
|
|
1660
|
+
...sessionIncompleteReasons,
|
|
1661
|
+
...(runtimeTurnHasContent(turn) ? [] : ['runtime_turn_empty']),
|
|
1662
|
+
];
|
|
1663
|
+
if (incompleteReasons.length > 0)
|
|
1664
|
+
hasIncompleteTurns = true;
|
|
1665
|
+
const body = runtimeBody(turn, sessionModel, sessionTools);
|
|
1666
|
+
const passthrough = runtimeTurnPassthroughFields(turn, sourceRecordStore, rawRowStore);
|
|
1667
|
+
const model = stringValue(turn.model) || sessionModel;
|
|
1668
|
+
const reasoningSignature = stringValue(turn.reasoningSignature);
|
|
1669
|
+
const encryptedSignature = stringValue(turn.encryptedSignature);
|
|
1670
|
+
turns.push({
|
|
1671
|
+
turn_index: idx,
|
|
1672
|
+
request_id: turn.toolUseId ? `${sessionId}:${turn.toolUseId}` : `${sessionId}:turn:${idx}`,
|
|
1673
|
+
timestamp: stringValue(turn.timestamp) || stringValue(input.startedAt),
|
|
1674
|
+
provider,
|
|
1675
|
+
endpoint: 'runtime_session',
|
|
1676
|
+
model,
|
|
1677
|
+
status: null,
|
|
1678
|
+
duration_ms: null,
|
|
1679
|
+
is_stream: false,
|
|
1680
|
+
finish_reason: turn.errorMessage ? 'error' : '',
|
|
1681
|
+
response_id: '',
|
|
1682
|
+
previous_response_id: '',
|
|
1683
|
+
input_tokens: inputTokenCount,
|
|
1684
|
+
output_tokens: outputTokenCount,
|
|
1685
|
+
...passthrough,
|
|
1686
|
+
request_body: isAssistantSide ? null : body,
|
|
1687
|
+
response_body: isAssistantSide ? body : null,
|
|
1688
|
+
...(turn.reasoning === true ? { reasoning: redactString(turn.text) } : {}),
|
|
1689
|
+
...(turn.thinkingEmpty === true ? { thinking_empty: true } : {}),
|
|
1690
|
+
...(reasoningSignature ? { reasoning_signature: reasoningSignature } : {}),
|
|
1691
|
+
...(encryptedSignature ? { encrypted_signature: encryptedSignature } : {}),
|
|
1692
|
+
...(turn.encryptedContent !== undefined ? { encrypted_content: turn.encryptedContent } : {}),
|
|
1693
|
+
tool_calls: toolCalls,
|
|
1694
|
+
redaction: RUNTIME_SESSION_REDACTION,
|
|
1695
|
+
...(turn.errorMessage ? { error: redactString(turn.errorMessage) } : {}),
|
|
1696
|
+
complete: incompleteReasons.length === 0,
|
|
1697
|
+
...(incompleteReasons.length > 0 ? { incomplete_reasons: incompleteReasons } : {}),
|
|
1698
|
+
});
|
|
1699
|
+
});
|
|
1700
|
+
if (turns.length === 0)
|
|
1701
|
+
return null;
|
|
1702
|
+
return {
|
|
1703
|
+
schema: CODING_TRAJECTORY_SCHEMA,
|
|
1704
|
+
session_id: sessionId,
|
|
1705
|
+
source_kind: 'runtime_session',
|
|
1706
|
+
source_agent: sourceAgent,
|
|
1707
|
+
source_path: sourcePath,
|
|
1708
|
+
...(sessionModel ? { session_model: sessionModel } : {}),
|
|
1709
|
+
...(provider !== sourceAgent ? { session_provider: provider } : {}),
|
|
1710
|
+
...(sessionTools !== undefined ? { session_tools: redactRuntimeStructuredValue(sessionTools) } : {}),
|
|
1711
|
+
...runtimeSessionPassthroughFields(input, sourceRecordStore, rawRowStore),
|
|
1712
|
+
task,
|
|
1713
|
+
providers: [provider],
|
|
1714
|
+
endpoints: ['runtime_session'],
|
|
1715
|
+
languages: [...languages],
|
|
1716
|
+
stats: {
|
|
1717
|
+
turns: turns.length,
|
|
1718
|
+
input_tokens: inputTokens,
|
|
1719
|
+
output_tokens: outputTokens,
|
|
1720
|
+
total_tokens: inputTokens + outputTokens,
|
|
1721
|
+
tool_call_count: Object.values(toolTypes).reduce((sum, n) => sum + n, 0),
|
|
1722
|
+
tool_types: toolTypes,
|
|
1723
|
+
has_tool_calls: hasToolCalls,
|
|
1724
|
+
has_code_edit: hasCodeEdit,
|
|
1725
|
+
has_test_execution: testCommands.size > 0,
|
|
1726
|
+
test_commands: [...testCommands],
|
|
1727
|
+
has_failure_correction: hasFailureCorrection,
|
|
1728
|
+
has_truncated_stream: false,
|
|
1729
|
+
has_incomplete_turns: hasIncompleteTurns,
|
|
1730
|
+
},
|
|
1731
|
+
...(nativeCalls.length > 0 ? { native_calls: nativeCalls } : {}),
|
|
1732
|
+
turns,
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
export function buildCodingTrajectories(rows) {
|
|
1736
|
+
const list = [...rows];
|
|
1737
|
+
const parent = list.map((_, idx) => idx);
|
|
1738
|
+
const rootSession = list.map((row) => sessionIdForRow(row));
|
|
1739
|
+
const find = (idx) => {
|
|
1740
|
+
let cur = idx;
|
|
1741
|
+
while (parent[cur] !== cur) {
|
|
1742
|
+
parent[cur] = parent[parent[cur]];
|
|
1743
|
+
cur = parent[cur];
|
|
1744
|
+
}
|
|
1745
|
+
return cur;
|
|
1746
|
+
};
|
|
1747
|
+
const union = (a, b) => {
|
|
1748
|
+
const ra = find(a);
|
|
1749
|
+
const rb = find(b);
|
|
1750
|
+
if (ra === rb)
|
|
1751
|
+
return;
|
|
1752
|
+
const leftSession = rootSession[ra] ?? '';
|
|
1753
|
+
const rightSession = rootSession[rb] ?? '';
|
|
1754
|
+
if (leftSession && rightSession && leftSession !== rightSession)
|
|
1755
|
+
return;
|
|
1756
|
+
parent[rb] = ra;
|
|
1757
|
+
rootSession[ra] = leftSession || rightSession;
|
|
1758
|
+
};
|
|
1759
|
+
const explicitOwner = new Map();
|
|
1760
|
+
const responseOwners = new Map();
|
|
1761
|
+
const rememberResponseOwner = (responseId, idx) => {
|
|
1762
|
+
if (!responseId)
|
|
1763
|
+
return;
|
|
1764
|
+
const owners = responseOwners.get(responseId) ?? [];
|
|
1765
|
+
owners.push(idx);
|
|
1766
|
+
responseOwners.set(responseId, owners);
|
|
1767
|
+
};
|
|
1768
|
+
const compatibleResponseOwnerRoots = (responseId, rowIdx) => {
|
|
1769
|
+
const owners = responseOwners.get(responseId) ?? [];
|
|
1770
|
+
const rowRoot = find(rowIdx);
|
|
1771
|
+
const rowSessionId = rootSession[rowRoot] ?? '';
|
|
1772
|
+
const roots = new Set();
|
|
1773
|
+
for (const ownerIdx of owners) {
|
|
1774
|
+
if (ownerIdx === rowIdx)
|
|
1775
|
+
continue;
|
|
1776
|
+
const ownerRoot = find(ownerIdx);
|
|
1777
|
+
const ownerSessionId = rootSession[ownerRoot] ?? '';
|
|
1778
|
+
if (ownerSessionId && rowSessionId && ownerSessionId !== rowSessionId)
|
|
1779
|
+
continue;
|
|
1780
|
+
roots.add(ownerRoot);
|
|
1781
|
+
}
|
|
1782
|
+
return [...roots];
|
|
1783
|
+
};
|
|
1784
|
+
list.forEach((row, idx) => {
|
|
1785
|
+
const explicit = sessionIdForRow(row);
|
|
1786
|
+
if (explicit) {
|
|
1787
|
+
const key = `session:${explicit}`;
|
|
1788
|
+
const owner = explicitOwner.get(key);
|
|
1789
|
+
if (owner !== undefined)
|
|
1790
|
+
union(owner, idx);
|
|
1791
|
+
else
|
|
1792
|
+
explicitOwner.set(key, idx);
|
|
1793
|
+
}
|
|
1794
|
+
const responseId = responseIdForRow(row);
|
|
1795
|
+
rememberResponseOwner(responseId, idx);
|
|
1796
|
+
});
|
|
1797
|
+
list.forEach((row, idx) => {
|
|
1798
|
+
const previous = previousResponseIdForRow(row);
|
|
1799
|
+
if (!previous)
|
|
1800
|
+
return;
|
|
1801
|
+
const roots = compatibleResponseOwnerRoots(previous, idx);
|
|
1802
|
+
if (roots.length !== 1)
|
|
1803
|
+
return;
|
|
1804
|
+
union(roots[0], idx);
|
|
1805
|
+
});
|
|
1806
|
+
const groups = new Map();
|
|
1807
|
+
list.forEach((row, idx) => {
|
|
1808
|
+
const root = find(idx);
|
|
1809
|
+
const owner = list[root] ?? row;
|
|
1810
|
+
const explicitSessionId = rootSession[root] || sessionIdForRow(owner);
|
|
1811
|
+
const fallbackId = requestIdForRow(owner) || responseIdForRow(owner) || 'unknown';
|
|
1812
|
+
const id = explicitSessionId || `${fallbackId}#${root}`;
|
|
1813
|
+
const bucket = groups.get(root) ?? { sessionId: id, rows: [] };
|
|
1814
|
+
bucket.rows.push(row);
|
|
1815
|
+
groups.set(root, bucket);
|
|
1816
|
+
});
|
|
1817
|
+
return [...groups.values()].map(({ sessionId, rows: groupRows }) => buildTrajectoryFromRows(sessionId, groupRows));
|
|
1818
|
+
}
|