@iowarp/clio-coder 0.3.2 → 0.3.4
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/CHANGELOG.md +269 -458
- package/CONTRIBUTING.md +1 -1
- package/README.md +3 -3
- package/dist/{acp-BIYHVZIM.js → acp-S5R4RR5B.js} +7 -6
- package/dist/{agents-YT6SSRIT.js → agents-P6DMMVZY.js} +24 -21
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-5TWEIYDN.js → auth-2XCZLPKS.js} +12 -8
- package/dist/{chunk-GGXXDWE4.js → chunk-22NAGB7X.js} +2 -2
- package/dist/{chunk-WMSVI4G2.js → chunk-2LZI5CAG.js} +133 -13
- package/dist/{chunk-OAO4GE4M.js → chunk-2TZWSW76.js} +2 -2
- package/dist/{chunk-OOJYHWRB.js → chunk-34475P3I.js} +2 -2
- package/dist/{chunk-WVO7V2QY.js → chunk-35MKKU5R.js} +4 -4
- package/dist/{chunk-LBNRH5WM.js → chunk-3HZ5RWN2.js} +5 -5
- package/dist/{chunk-AGYYIBLL.js → chunk-3JLKSKD7.js} +2 -2
- package/dist/{chunk-MBS4V7ZP.js → chunk-4JUF2NNX.js} +7 -7
- package/dist/{chunk-ZDOOVTXZ.js → chunk-4OC57DA6.js} +27 -4
- package/dist/chunk-5M54SPOL.js +926 -0
- package/dist/{chunk-STBPMHSX.js → chunk-7RXG6QRZ.js} +51 -11
- package/dist/{chunk-77VKQEHF.js → chunk-A2GZF7DC.js} +5 -5
- package/dist/{chunk-A3CYT5EX.js → chunk-AD2SYQYC.js} +55 -2
- package/dist/chunk-AOCYTWAV.js +449 -0
- package/dist/chunk-BEY543CS.js +258 -0
- package/dist/{chunk-6N5PTWMY.js → chunk-BP4OYD6A.js} +32 -13
- package/dist/chunk-BPGS2WCQ.js +612 -0
- package/dist/{chunk-J5HN4RYU.js → chunk-BRXQQJFP.js} +8 -8
- package/dist/chunk-CFGTUFWB.js +67 -0
- package/dist/{chunk-CBCAPZAA.js → chunk-E25LMLRW.js} +2 -2
- package/dist/{chunk-G2DE3C7R.js → chunk-EDRHSCIE.js} +4 -4
- package/dist/{chunk-4KLWL3UC.js → chunk-EFADSJET.js} +2 -2
- package/dist/{chunk-M6SHUN7Q.js → chunk-FO5ZOVUY.js} +2 -2
- package/dist/chunk-FYYLNIL5.js +313 -0
- package/dist/{chunk-EPVUXGXG.js → chunk-HV5X7OR2.js} +14 -12
- package/dist/{chunk-TZTZS7QK.js → chunk-HXG4IURW.js} +5 -3
- package/dist/{chunk-IGLFWIYI.js → chunk-K6WL7QZT.js} +3 -3
- package/dist/chunk-K7VKOLQQ.js +15 -0
- package/dist/{chunk-BMEMKKIT.js → chunk-KOHPCX4K.js} +2 -2
- package/dist/{chunk-V4RXGQ5Q.js → chunk-KRPY7NTG.js} +10 -7
- package/dist/chunk-LL4KHSZI.js +22 -0
- package/dist/{chunk-KJ5LWLOE.js → chunk-MEQ45TQ4.js} +15 -9
- package/dist/{chunk-5UUP6MWO.js → chunk-MV3K5QF2.js} +5 -436
- package/dist/{chunk-AO4RKG4M.js → chunk-N4CZJQRK.js} +5 -5
- package/dist/{chunk-ARBGF5F7.js → chunk-NILBFAPG.js} +14 -8
- package/dist/chunk-OZNBF4L3.js +23 -0
- package/dist/{verify-G6V4D2G7.js → chunk-PCZJO5TI.js} +127 -42
- package/dist/chunk-QQK64KLB.js +1360 -0
- package/dist/{chunk-6EJV5X2W.js → chunk-QQL5RT5M.js} +979 -1619
- package/dist/{chunk-LZSJBIVT.js → chunk-QWU7ZBO7.js} +70 -720
- package/dist/{chunk-2EHAIA3X.js → chunk-RD5U66HV.js} +3 -3
- package/dist/{chunk-OKGUZO2U.js → chunk-SPULKLCF.js} +4 -3
- package/dist/{chunk-OQ33BKR3.js → chunk-TTNYS3EA.js} +3 -60
- package/dist/chunk-TW3WDMVS.js +677 -0
- package/dist/chunk-TZSKNMZG.js +434 -0
- package/dist/{chunk-7MNJORFF.js → chunk-UL3WSD3F.js} +6 -1
- package/dist/{chunk-7EYHLWU7.js → chunk-UZHIZC5S.js} +7 -7
- package/dist/{chunk-QTYWRVRA.js → chunk-VAWWTKDP.js} +8 -8
- package/dist/{chunk-X75S7HFS.js → chunk-VEZEGCGW.js} +214 -20
- package/dist/{chunk-OHHN2SO4.js → chunk-VMNQ6OZA.js} +98 -202
- package/dist/chunk-VSNATDE6.js +122 -0
- package/dist/chunk-W6GROXXM.js +69 -0
- package/dist/chunk-WPQLXFOZ.js +375 -0
- package/dist/{chunk-ORBHGJC5.js → chunk-WR67VIZY.js} +3 -3
- package/dist/{chunk-3ZXDFGR5.js → chunk-X6COSD2O.js} +5 -5
- package/dist/chunk-ZGVHUX3M.js +66 -0
- package/dist/{chunk-MAW544W2.js → chunk-ZWMF7253.js} +4 -4
- package/dist/{chunk-MQSRRFWA.js → chunk-ZYKPLLNQ.js} +563 -546
- package/dist/cli/index.js +27 -23
- package/dist/{clio-4LY5K2AC.js → clio-J5JIOIDS.js} +7 -6
- package/dist/{code-nav-7AX6FYE6.js → code-nav-AXCXSBHX.js} +5 -3
- package/dist/{config-GTLUW2PR.js → config-OEBMIN2U.js} +37 -27
- package/dist/{configure-R6A64DHX.js → configure-PUQOSIXQ.js} +16 -13
- package/dist/{context-5VKGUVJJ.js → context-EKDCKUUZ.js} +82 -7
- package/dist/{context-RW5HC47S.js → context-MGSE4Z2T.js} +33 -23
- package/dist/{context-JFZEJ7W5.js → context-URSXPBCK.js} +17 -9
- package/dist/{context-clear-6ZHBAZZT.js → context-clear-KDAJRNUK.js} +33 -23
- package/dist/context-working-set-SBKMPPI2.js +1552 -0
- package/dist/{dispatch-runner-VKBRCWQC.js → dispatch-runner-MSWN72NK.js} +43 -29
- package/dist/{doctor-KI767GSN.js → doctor-7BSE27PJ.js} +10 -10
- package/dist/{eval-XSSNATB4.js → eval-IZGDOO4H.js} +9 -8
- package/dist/{evidence-UA6AWDQQ.js → evidence-SR7WXB5B.js} +51 -23
- package/dist/{evolve-QNTFGV6Z.js → evolve-K7VE2CBX.js} +30 -20
- package/dist/{fleet-Q7UOMUSG.js → fleet-7XMJNQNF.js} +48 -38
- package/dist/{fleet-preflight-DDN536IT.js → fleet-preflight-AQNAH644.js} +3 -3
- package/dist/{init-WBB65ZHQ.js → init-JGNPAYXT.js} +41 -31
- package/dist/{memory-MD3O64RI.js → memory-4ALKDJ4Q.js} +32 -22
- package/dist/{models-BZU34YWD.js → models-ZMMLFJNN.js} +22 -19
- package/dist/{monitor-MEQA5C3I.js → monitor-2F3T5KHP.js} +55 -43
- package/dist/{orchestrator-CGFKEP27.js → orchestrator-ORHT43JB.js} +2507 -1896
- package/dist/{reset-L2FQEE3E.js → reset-NXGTYNUO.js} +4 -3
- package/dist/{run-IV4Q6RLN.js → run-RF4WJGMT.js} +51 -41
- package/dist/{share-S5BZQC5I.js → share-UT3W6E4M.js} +5 -4
- package/dist/{skills-LQEKRDTN.js → skills-PSACKC5Q.js} +2 -2
- package/dist/{skills-eval-3DC4HEWS.js → skills-eval-WJSI55RZ.js} +34 -24
- package/dist/{targets-C4SSGQOB.js → targets-PIIRAOYS.js} +23 -20
- package/dist/{terminal-lease-IT5JW2NR.js → terminal-lease-ULWXWNVY.js} +5 -4
- package/dist/{upgrade-7TT7SQ3G.js → upgrade-346TZ6AV.js} +18 -17
- package/dist/{usage-GV4PKT3M.js → usage-6KKXR32N.js} +34 -24
- package/dist/verifiers-4UUM6TEE.js +1214 -0
- package/dist/verify-X5HDROLA.js +25 -0
- package/dist/{wiki-generate-DQF6Z66B.js → wiki-generate-7STOCIFZ.js} +42 -31
- package/dist/worker/entry.js +33 -24
- package/docs/README.md +8 -7
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-versions.md +1 -1
- package/docs/built-in-agents.md +1 -1
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +53 -21
- package/docs/config-knobs-audit.md +1 -2
- package/docs/configuration-and-targets.md +15 -1
- package/docs/context-engine.md +64 -12
- package/docs/context-working-set.md +194 -0
- package/docs/development-pipeline.md +1 -1
- package/docs/documentation-coverage.md +5 -5
- package/docs/documentation-guide.md +6 -5
- package/docs/environment-variables.md +2 -1
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +14 -1
- package/docs/evidence-and-memory.md +74 -2
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +1 -1
- package/docs/extensions-and-sharing.md +2 -2
- package/docs/fleet-dispatch.md +22 -7
- package/docs/glossary.md +21 -1
- package/docs/installation-and-lifecycle.md +6 -6
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +7 -9
- package/docs/observability.md +4 -4
- package/docs/performance-methodology.md +2 -2
- package/docs/proactive-memory.md +1 -1
- package/docs/prompt-envelope-and-tools.md +4 -4
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +35 -35
- package/docs/safety-model.md +23 -4
- package/docs/scientific-validation.md +21 -3
- package/docs/session-lifecycle.md +3 -3
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +79 -12
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +2 -2
- package/docs/worker-dispatch-mechanics.md +11 -1
- package/package.json +8 -11
- package/skills/meta/clio-test/SKILL.md +20 -17
- package/skills/meta/clio-test/evals.md +3 -3
- package/skills/meta/clio-test/references/harness.md +35 -6
- package/skills/meta/clio-test/references/test-map.md +20 -10
- package/skills/registry.yaml +2 -2
- package/skills/skill-marketplace.json +1 -1
- package/src/cli/context-working-set.ts +513 -0
- package/src/cli/context.ts +8 -0
- package/src/cli/evidence.ts +20 -2
- package/src/cli/index.ts +4 -0
- package/src/cli/verifiers.ts +325 -0
- package/src/core/bash-exec.ts +39 -14
- package/src/core/bus-events.ts +19 -4
- package/src/core/config.ts +54 -0
- package/src/core/defaults.ts +50 -3
- package/src/core/git-commit-attribution.ts +46 -21
- package/src/core/verification-scripts.ts +6 -0
- package/src/domains/agents/builtins/verifier.md +3 -0
- package/src/domains/config/classify.ts +1 -0
- package/src/domains/config/keybindings.ts +3 -3
- package/src/domains/context/working-set/contract.ts +161 -0
- package/src/domains/context/working-set/defaults.ts +28 -0
- package/src/domains/context/working-set/engine.ts +203 -0
- package/src/domains/context/working-set/fold.ts +62 -0
- package/src/domains/context/working-set/horizon.ts +38 -0
- package/src/domains/context/working-set/marker.ts +103 -0
- package/src/domains/context/working-set/path-index.ts +436 -0
- package/src/domains/context/working-set/payload.ts +152 -0
- package/src/domains/context/working-set/policies/age-horizon.ts +55 -0
- package/src/domains/context/working-set/policies/index.ts +21 -0
- package/src/domains/context/working-set/policies/structural.ts +160 -0
- package/src/domains/context/working-set/project.ts +132 -0
- package/src/domains/context/working-set/protect.ts +109 -0
- package/src/domains/context/working-set/recall.ts +177 -0
- package/src/domains/context/working-set/replay/controls.ts +112 -0
- package/src/domains/context/working-set/replay/load-clio.ts +199 -0
- package/src/domains/context/working-set/replay/metrics.ts +185 -0
- package/src/domains/context/working-set/replay/reference-graph.ts +79 -0
- package/src/domains/context/working-set/replay/report.ts +139 -0
- package/src/domains/context/working-set/replay/runner.ts +325 -0
- package/src/domains/context/working-set/replay/synthetic.ts +422 -0
- package/src/domains/context/working-set/replay/trace.ts +21 -0
- package/src/domains/context/working-set/visible.ts +54 -0
- package/src/domains/evidence/build.ts +112 -45
- package/src/domains/evidence/eval.ts +24 -7
- package/src/domains/evidence/index.ts +53 -0
- package/src/domains/evidence/ordering.ts +12 -0
- package/src/domains/evidence/run-trust.ts +221 -0
- package/src/domains/evidence/store.ts +46 -6
- package/src/domains/evidence/trust-status.ts +854 -0
- package/src/domains/evidence/types.ts +26 -0
- package/src/domains/middleware/memory-intervention.ts +3 -0
- package/src/domains/middleware/stalled-turn.ts +165 -4
- package/src/domains/safety/autonomy.ts +1 -1
- package/src/domains/safety/default-path-policy.ts +8 -0
- package/src/domains/safety/finish-contract.ts +4 -3
- package/src/domains/safety/policy-engine.ts +48 -6
- package/src/domains/session/compaction/compact.ts +23 -1
- package/src/domains/session/compaction/cut-point.ts +2 -0
- package/src/domains/session/compaction/tokens.ts +16 -1
- package/src/domains/session/context-ledger.ts +2 -0
- package/src/domains/session/entries.ts +107 -1
- package/src/domains/session/manager.ts +9 -2
- package/src/domains/session/migrations/index.ts +22 -3
- package/src/engine/acp/server.ts +3 -0
- package/src/engine/agent.ts +18 -1
- package/src/engine/session.ts +9 -3
- package/src/entry/orchestrator.ts +16 -4
- package/src/interactive/chat-loop-messages.ts +18 -6
- package/src/interactive/chat-panel.ts +571 -244
- package/src/interactive/chat-renderer.ts +79 -39
- package/src/interactive/context-meter.ts +10 -0
- package/src/interactive/context-overlay.ts +81 -6
- package/src/interactive/context-recall-command.ts +110 -0
- package/src/interactive/editor-submit.ts +26 -1
- package/src/interactive/footer/widgets.ts +22 -20
- package/src/interactive/footer-panel.ts +6 -1
- package/src/interactive/interactive-application.ts +2 -0
- package/src/interactive/interactive-event-projection.ts +12 -0
- package/src/interactive/interactive-slash-runtime.ts +49 -8
- package/src/interactive/model-session-replay.ts +21 -0
- package/src/interactive/overlay-general-openers.ts +6 -0
- package/src/interactive/overlay-session-lifecycle.ts +8 -4
- package/src/interactive/overlays/ask-user.ts +146 -24
- package/src/interactive/renderers/tool-execution.ts +167 -56
- package/src/interactive/session-transcript.ts +2 -2
- package/src/interactive/slash-commands.ts +29 -2
- package/src/interactive/status/index.ts +12 -1
- package/src/interactive/status/reasoning.ts +87 -0
- package/src/interactive/status/summary.ts +13 -2
- package/src/interactive/transcript-detail.ts +120 -0
- package/src/interactive/turn-context.ts +238 -88
- package/src/interactive/turn-middleware.ts +6 -6
- package/src/tools/agent-tools.ts +11 -4
- package/src/tools/bash.ts +144 -82
- package/src/tools/builtin-tool-catalog.ts +18 -6
- package/src/tools/context/index.ts +105 -3
- package/src/tools/context/surface.ts +3 -2
- package/src/tools/core-bootstrap.ts +21 -0
- package/src/tools/dispatch-runner.ts +9 -7
- package/src/tools/monitor.ts +28 -20
- package/src/tools/presentation.ts +107 -0
- package/src/tools/registry.ts +65 -7
- package/src/tools/result-disposition.ts +550 -0
- package/src/tools/result-shaping.ts +262 -19
- package/src/tools/safe-exec.ts +2 -0
- package/src/tools/verify/authoring.ts +1119 -0
- package/src/tools/verify/catalog.ts +346 -0
- package/src/tools/verify/index.ts +13 -3
- package/src/tools/verify/scripts.ts +135 -37
- package/src/tools/verify/surface.ts +9 -5
- package/src/tools/worker-evidence.ts +35 -12
- package/dist/chunk-MNA4JGU4.js +0 -255
- package/dist/chunk-SRF2PJNW.js +0 -184
- package/dist/chunk-T6YILFSB.js +0 -80
- package/dist/chunk-VAKQQHWR.js +0 -434
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createRedactionTally, redactSecretsText } from "../domains/evidence/redact.js";
|
|
3
|
+
import type { ToolPresentationPolicy } from "./presentation.js";
|
|
4
|
+
import type { ToolResult, ToolResultDetails, ToolSpec } from "./registry.js";
|
|
5
|
+
import { byteLength } from "./truncate-utf8.js";
|
|
6
|
+
|
|
7
|
+
export type ToolResultExcerptBias = "head-tail" | "tail";
|
|
8
|
+
|
|
9
|
+
/** How much of a captured tool result is inserted into the model conversation. */
|
|
10
|
+
export type ToolResultContextDisposition =
|
|
11
|
+
| { mode: "full"; maxBytes?: number; downgradeExcerpt?: ToolResultExcerptBias }
|
|
12
|
+
| { mode: "bounded"; maxBytes: number; excerpt?: ToolResultExcerptBias }
|
|
13
|
+
| { mode: "summary"; maxBytes: number; strategy?: "head-tail" | "diagnostic"; redact?: boolean }
|
|
14
|
+
| { mode: "metadata-only"; maxBytes: number };
|
|
15
|
+
|
|
16
|
+
/** Operator-facing policy for one result, independent of its model projection. */
|
|
17
|
+
export interface ToolResultPresentationDisposition extends ToolPresentationPolicy {
|
|
18
|
+
/** Registry display backstop. Overflow is retained in scratch when possible. */
|
|
19
|
+
maxBytes?: number;
|
|
20
|
+
/** Which edge of textual overflow the canonical shaper preserves. */
|
|
21
|
+
overflow?: "head" | "tail";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Why a disposition is not the one the tool asked for. Today the only cause is
|
|
26
|
+
* a throwing `resolveResultDisposition`: the registry then fails closed to
|
|
27
|
+
* metadata-only, and this record is what keeps that narrowing visible to the
|
|
28
|
+
* operator and the model instead of reading as a deliberate request.
|
|
29
|
+
*/
|
|
30
|
+
export interface ToolResultDispositionFallback {
|
|
31
|
+
reason: "resolver-error";
|
|
32
|
+
message: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The canonical tool-result disposition vocabulary. Presentation controls the
|
|
37
|
+
* operator surface; context independently controls what the model receives.
|
|
38
|
+
*/
|
|
39
|
+
export interface ToolResultDisposition {
|
|
40
|
+
presentation: ToolResultPresentationDisposition;
|
|
41
|
+
context: ToolResultContextDisposition;
|
|
42
|
+
fallback?: ToolResultDispositionFallback;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type AppliedToolResultContextMode = ToolResultContextDisposition["mode"];
|
|
46
|
+
|
|
47
|
+
export interface ToolResultSummaryProvenance {
|
|
48
|
+
producer: "code";
|
|
49
|
+
algorithm: "sha256-head-tail-v1" | "sha256-diagnostic-v1";
|
|
50
|
+
sourceSha256: string;
|
|
51
|
+
sourceLines: number;
|
|
52
|
+
redactions?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ToolResultDispositionMetadata {
|
|
56
|
+
version: 1;
|
|
57
|
+
applications: 1;
|
|
58
|
+
presentation: ToolResultPresentationDisposition & { content: string };
|
|
59
|
+
context: {
|
|
60
|
+
requestedMode: AppliedToolResultContextMode;
|
|
61
|
+
appliedMode: AppliedToolResultContextMode;
|
|
62
|
+
maxBytes: number;
|
|
63
|
+
excerpt?: ToolResultExcerptBias;
|
|
64
|
+
summaryStrategy?: "head-tail" | "diagnostic";
|
|
65
|
+
};
|
|
66
|
+
capturedBytes: number;
|
|
67
|
+
displayedBytes: number;
|
|
68
|
+
contextBytes: number;
|
|
69
|
+
presentationTruncated: boolean;
|
|
70
|
+
contextTruncated: boolean;
|
|
71
|
+
downgrade?: {
|
|
72
|
+
from: "full";
|
|
73
|
+
to: "bounded";
|
|
74
|
+
reason: "hard-budget";
|
|
75
|
+
};
|
|
76
|
+
offloadPath?: string;
|
|
77
|
+
retrieval: string;
|
|
78
|
+
summaryProvenance?: ToolResultSummaryProvenance;
|
|
79
|
+
fallback?: ToolResultDispositionFallback;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface NormalizedToolResultDisposition {
|
|
83
|
+
presentation: ToolResultPresentationDisposition & { maxBytes: number };
|
|
84
|
+
context: ToolResultContextDisposition & { maxBytes: number };
|
|
85
|
+
fallback?: ToolResultDispositionFallback;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ToolResultContextProjection {
|
|
89
|
+
text: string;
|
|
90
|
+
appliedMode: AppliedToolResultContextMode;
|
|
91
|
+
/**
|
|
92
|
+
* True when the projection omits captured content, which is exactly when a
|
|
93
|
+
* retrieval artifact earns its place. Whitespace-only differences (trimmed
|
|
94
|
+
* line ends, dropped blank lines) do not count as omitted content.
|
|
95
|
+
*/
|
|
96
|
+
truncated: boolean;
|
|
97
|
+
downgrade?: ToolResultDispositionMetadata["downgrade"];
|
|
98
|
+
summaryProvenance?: ToolResultSummaryProvenance;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface ProjectToolResultContextInput {
|
|
102
|
+
text: string;
|
|
103
|
+
kind: ToolResult["kind"];
|
|
104
|
+
details: ToolResultDetails | undefined;
|
|
105
|
+
disposition: NormalizedToolResultDisposition;
|
|
106
|
+
capturedBytes: number;
|
|
107
|
+
displayedBytes: number;
|
|
108
|
+
offloadPath: string | null;
|
|
109
|
+
followUpHint: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const ESSENTIAL_DETAIL_KEYS = new Set([
|
|
113
|
+
"aborted",
|
|
114
|
+
"continuation",
|
|
115
|
+
"continuationHint",
|
|
116
|
+
"decision",
|
|
117
|
+
"error",
|
|
118
|
+
"errors",
|
|
119
|
+
"evidence",
|
|
120
|
+
"exitCode",
|
|
121
|
+
"exitStatus",
|
|
122
|
+
"outputBytes",
|
|
123
|
+
"outputCapped",
|
|
124
|
+
"next",
|
|
125
|
+
"outcome",
|
|
126
|
+
"safety",
|
|
127
|
+
"safetyDecision",
|
|
128
|
+
"status",
|
|
129
|
+
"signal",
|
|
130
|
+
"terminate",
|
|
131
|
+
"timedOut",
|
|
132
|
+
"retainedBytes",
|
|
133
|
+
"stderrBytes",
|
|
134
|
+
"stdoutBytes",
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
function positiveByteCap(value: number | undefined, fallback: number): number {
|
|
138
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Normalize one declared disposition against the registry's non-negotiable hard cap. */
|
|
142
|
+
export function normalizeToolResultDisposition(
|
|
143
|
+
spec: ToolSpec,
|
|
144
|
+
hardMaxBytes: number,
|
|
145
|
+
requested: ToolResultDisposition | undefined = spec.metadata?.resultDisposition,
|
|
146
|
+
): NormalizedToolResultDisposition | null {
|
|
147
|
+
const declared = requested;
|
|
148
|
+
if (declared === undefined) return null;
|
|
149
|
+
const hardCap = positiveByteCap(hardMaxBytes, 1);
|
|
150
|
+
const presentationCap = Math.min(positiveByteCap(declared.presentation.maxBytes, hardCap), hardCap);
|
|
151
|
+
const contextCap = Math.min(positiveByteCap(declared.context.maxBytes, hardCap), hardCap);
|
|
152
|
+
return {
|
|
153
|
+
presentation: {
|
|
154
|
+
foldDefault: declared.presentation.foldDefault,
|
|
155
|
+
showDiffWhenFolded: declared.presentation.showDiffWhenFolded,
|
|
156
|
+
failureExcerpt: declared.presentation.failureExcerpt,
|
|
157
|
+
maxBytes: presentationCap,
|
|
158
|
+
...(declared.presentation.overflow === undefined ? {} : { overflow: declared.presentation.overflow }),
|
|
159
|
+
},
|
|
160
|
+
context: { ...declared.context, maxBytes: contextCap },
|
|
161
|
+
...(declared.fallback === undefined ? {} : { fallback: declared.fallback }),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
166
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function stableValue(value: unknown, seen: Set<object>): unknown {
|
|
170
|
+
if (value === null || typeof value !== "object") return value;
|
|
171
|
+
if (seen.has(value)) return "[circular]";
|
|
172
|
+
seen.add(value);
|
|
173
|
+
if (Array.isArray(value)) {
|
|
174
|
+
const items = value.map((item) => stableValue(item, seen));
|
|
175
|
+
seen.delete(value);
|
|
176
|
+
return items;
|
|
177
|
+
}
|
|
178
|
+
const record = value as Record<string, unknown>;
|
|
179
|
+
const out: Record<string, unknown> = {};
|
|
180
|
+
for (const key of Object.keys(record).sort()) out[key] = stableValue(record[key], seen);
|
|
181
|
+
seen.delete(value);
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function stableJson(value: unknown): string {
|
|
186
|
+
try {
|
|
187
|
+
return JSON.stringify(stableValue(value, new Set())) ?? "null";
|
|
188
|
+
} catch {
|
|
189
|
+
return JSON.stringify(String(value));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function essentialFacts(details: ToolResultDetails | undefined): Record<string, unknown> | null {
|
|
194
|
+
if (details === undefined) return null;
|
|
195
|
+
const facts: Record<string, unknown> = {};
|
|
196
|
+
for (const key of Object.keys(details).sort()) {
|
|
197
|
+
if (ESSENTIAL_DETAIL_KEYS.has(key)) facts[key] = details[key];
|
|
198
|
+
}
|
|
199
|
+
const observation = isRecord(details.observation) ? details.observation : null;
|
|
200
|
+
if (observation !== null && observation.next !== undefined) facts.next = observation.next;
|
|
201
|
+
return Object.keys(facts).length > 0 ? facts : null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function utf8Prefix(text: string, maxBytes: number): string {
|
|
205
|
+
if (maxBytes <= 0) return "";
|
|
206
|
+
const buffer = Buffer.from(text, "utf8");
|
|
207
|
+
if (buffer.byteLength <= maxBytes) return text;
|
|
208
|
+
let end = Math.min(maxBytes, buffer.byteLength);
|
|
209
|
+
while (end > 0 && end < buffer.byteLength && (buffer[end] ?? 0) >>> 6 === 2) end -= 1;
|
|
210
|
+
return buffer.subarray(0, end).toString("utf8");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function utf8Suffix(text: string, maxBytes: number): string {
|
|
214
|
+
if (maxBytes <= 0) return "";
|
|
215
|
+
const buffer = Buffer.from(text, "utf8");
|
|
216
|
+
if (buffer.byteLength <= maxBytes) return text;
|
|
217
|
+
let start = Math.max(0, buffer.byteLength - maxBytes);
|
|
218
|
+
while (start < buffer.byteLength && (buffer[start] ?? 0) >>> 6 === 2) start += 1;
|
|
219
|
+
return buffer.subarray(start).toString("utf8");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function capUtf8(text: string, maxBytes: number): string {
|
|
223
|
+
return byteLength(text) <= maxBytes ? text : utf8Prefix(text, maxBytes);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** A deterministic, UTF-8-safe excerpt whose total bytes never exceed maxBytes. */
|
|
227
|
+
function boundedToolResultExcerpt(text: string, maxBytes: number, bias: ToolResultExcerptBias = "head-tail"): string {
|
|
228
|
+
if (maxBytes <= 0) return "";
|
|
229
|
+
if (byteLength(text) <= maxBytes) return text;
|
|
230
|
+
if (bias === "tail") return utf8Suffix(text, maxBytes);
|
|
231
|
+
const marker = "\n[… omitted …]\n";
|
|
232
|
+
const markerBytes = byteLength(marker);
|
|
233
|
+
if (markerBytes >= maxBytes) return utf8Prefix(text, maxBytes);
|
|
234
|
+
const bodyBudget = maxBytes - markerBytes;
|
|
235
|
+
const headBudget = Math.floor(bodyBudget / 2);
|
|
236
|
+
const tailBudget = bodyBudget - headBudget;
|
|
237
|
+
return `${utf8Prefix(text, headBudget)}${marker}${utf8Suffix(text, tailBudget)}`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function sourceLineCount(text: string): number {
|
|
241
|
+
if (text.length === 0) return 0;
|
|
242
|
+
return text.replace(/\r\n/gu, "\n").replace(/\r/gu, "\n").split("\n").length;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function summaryProvenance(
|
|
246
|
+
text: string,
|
|
247
|
+
algorithm: ToolResultSummaryProvenance["algorithm"] = "sha256-head-tail-v1",
|
|
248
|
+
redactions = 0,
|
|
249
|
+
): ToolResultSummaryProvenance {
|
|
250
|
+
return {
|
|
251
|
+
producer: "code",
|
|
252
|
+
algorithm,
|
|
253
|
+
sourceSha256: createHash("sha256").update(text).digest("hex"),
|
|
254
|
+
sourceLines: sourceLineCount(text),
|
|
255
|
+
...(redactions > 0 ? { redactions } : {}),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const ERROR_LIKE_LINE =
|
|
260
|
+
/(?:\b(?:abort(?:ed)?|assert(?:ion)?|error|exception|fail(?:ed|ure)?|fatal|killed|panic|signal|timed?\s*out|timeout)\b|\bnot ok\b|\bERR!\b)/iu;
|
|
261
|
+
const SUMMARY_LINE_MAX_BYTES = 768;
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* A selected summary body plus the two facts the caller needs to stay honest:
|
|
265
|
+
* how many secrets the redactor replaced, and whether every nonempty captured
|
|
266
|
+
* line survived verbatim. `complete` is the input to the retrieval decision;
|
|
267
|
+
* it is false whenever any line was dropped, cut, or redacted.
|
|
268
|
+
*/
|
|
269
|
+
export interface ToolResultSummarySelection {
|
|
270
|
+
text: string;
|
|
271
|
+
redactions: number;
|
|
272
|
+
complete: boolean;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function summaryLine(line: string): string {
|
|
276
|
+
return boundedToolResultExcerpt(line, SUMMARY_LINE_MAX_BYTES);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function normalizeNewlines(text: string): string {
|
|
280
|
+
return text.replace(/\r\n/gu, "\n").replace(/\r/gu, "\n");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** The trimmed nonempty lines that carry a text's content, in source order. */
|
|
284
|
+
function contentLines(text: string): string[] {
|
|
285
|
+
return normalizeNewlines(text)
|
|
286
|
+
.split("\n")
|
|
287
|
+
.map((line) => line.trim())
|
|
288
|
+
.filter((line) => line.length > 0);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* The non-diagnostic summary strategy: every nonempty line, trimmed, in order.
|
|
293
|
+
* Redaction is applied here too, so `redact` is honored whichever strategy the
|
|
294
|
+
* caller declared.
|
|
295
|
+
*/
|
|
296
|
+
function deterministicHeadTailSummary(text: string, redact = true): ToolResultSummarySelection {
|
|
297
|
+
const tally = createRedactionTally();
|
|
298
|
+
const safeText = redact ? redactSecretsText(text, tally) : text;
|
|
299
|
+
return { text: contentLines(safeText).join("\n"), redactions: tally.count, complete: tally.count === 0 };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function renderSummarySection(label: string, lines: ReadonlyArray<string>): string {
|
|
303
|
+
return lines.length === 0 ? "" : `[${label}]\n${lines.map(summaryLine).join("\n")}`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Stable Bash-style diagnostic summary. It keeps a bounded head and tail plus
|
|
308
|
+
* error-like lines from the middle, and applies the repository secret redactor
|
|
309
|
+
* before any selected content enters model context.
|
|
310
|
+
*/
|
|
311
|
+
export function deterministicDiagnosticSummary(
|
|
312
|
+
text: string,
|
|
313
|
+
maxBytes: number,
|
|
314
|
+
redact = true,
|
|
315
|
+
): ToolResultSummarySelection {
|
|
316
|
+
if (maxBytes <= 0 || text.length === 0) return { text: "", redactions: 0, complete: text.length === 0 };
|
|
317
|
+
const tally = createRedactionTally();
|
|
318
|
+
const safeText = redact ? redactSecretsText(text, tally) : text;
|
|
319
|
+
const lines = normalizeNewlines(safeText).split("\n");
|
|
320
|
+
const nonempty = lines.map((line, index) => ({ line: line.trim(), index })).filter((entry) => entry.line.length > 0);
|
|
321
|
+
// Whitespace-only output carries no content to omit, so dropping it is
|
|
322
|
+
// exactly the whitespace-only difference the completeness contract ignores.
|
|
323
|
+
if (nonempty.length === 0) return { text: "", redactions: tally.count, complete: tally.count === 0 };
|
|
324
|
+
if (nonempty.length === 1) {
|
|
325
|
+
const only = nonempty[0]?.line ?? "";
|
|
326
|
+
return {
|
|
327
|
+
text: boundedToolResultExcerpt(only, maxBytes),
|
|
328
|
+
redactions: tally.count,
|
|
329
|
+
complete: tally.count === 0 && byteLength(only) <= maxBytes,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Head and tail are disjoint by construction. Overlapping slices used to
|
|
334
|
+
// emit every line of a short output twice, so a two-line result summarized
|
|
335
|
+
// larger than the output it summarized.
|
|
336
|
+
const head = nonempty.slice(0, 4);
|
|
337
|
+
const tail = nonempty.slice(Math.max(head.length, nonempty.length - 6));
|
|
338
|
+
const edgeIndexes = new Set([...head, ...tail].map((entry) => entry.index));
|
|
339
|
+
const diagnostics = nonempty.filter((entry) => !edgeIndexes.has(entry.index) && ERROR_LIKE_LINE.test(entry.line));
|
|
340
|
+
const sections = [
|
|
341
|
+
renderSummarySection(
|
|
342
|
+
"diagnostic head",
|
|
343
|
+
head.map((entry) => entry.line),
|
|
344
|
+
),
|
|
345
|
+
renderSummarySection(
|
|
346
|
+
"error-like lines",
|
|
347
|
+
diagnostics.map((entry) => entry.line),
|
|
348
|
+
),
|
|
349
|
+
renderSummarySection(
|
|
350
|
+
"diagnostic tail",
|
|
351
|
+
tail.map((entry) => entry.line),
|
|
352
|
+
),
|
|
353
|
+
].filter((section) => section.length > 0);
|
|
354
|
+
const selected = [...head, ...diagnostics, ...tail];
|
|
355
|
+
const keepsEveryLine =
|
|
356
|
+
selected.length === nonempty.length && selected.every((entry) => byteLength(entry.line) <= SUMMARY_LINE_MAX_BYTES);
|
|
357
|
+
// Section labels earn their bytes by telling the model which lines were
|
|
358
|
+
// elided. When nothing was elided they are pure overhead, and on a short
|
|
359
|
+
// output they made the summary larger than the output itself.
|
|
360
|
+
const rendered = keepsEveryLine ? selected.map((entry) => summaryLine(entry.line)).join("\n") : sections.join("\n");
|
|
361
|
+
if (byteLength(rendered) <= maxBytes) {
|
|
362
|
+
return { text: rendered, redactions: tally.count, complete: keepsEveryLine && tally.count === 0 };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Preserve all three signal classes under pressure. The middle allocation is
|
|
366
|
+
// deliberately largest because those error-like lines are the summary's
|
|
367
|
+
// value over an ordinary head/tail excerpt.
|
|
368
|
+
const weights = diagnostics.length > 0 ? [0.25, 0.45, 0.3] : [0.4, 0, 0.6];
|
|
369
|
+
const boundedSections: string[] = [];
|
|
370
|
+
for (const [index, section] of sections.entries()) {
|
|
371
|
+
const weight = diagnostics.length > 0 ? (weights[index] ?? 0) : index === 0 ? 0.4 : 0.6;
|
|
372
|
+
const budget = Math.floor(maxBytes * weight);
|
|
373
|
+
if (budget > 0) boundedSections.push(boundedToolResultExcerpt(section ?? "", budget));
|
|
374
|
+
}
|
|
375
|
+
return { text: capUtf8(boundedSections.join("\n"), maxBytes), redactions: tally.count, complete: false };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function retrievalLine(offloadPath: string | null, followUpHint: string): string {
|
|
379
|
+
return offloadPath === null
|
|
380
|
+
? `retrieve=${JSON.stringify(followUpHint)}`
|
|
381
|
+
: `retrieve=${JSON.stringify(`read ${offloadPath} with offset and limit`)}`;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function contextHeader(
|
|
385
|
+
input: ProjectToolResultContextInput,
|
|
386
|
+
appliedMode: AppliedToolResultContextMode,
|
|
387
|
+
truncated: boolean,
|
|
388
|
+
provenance?: ToolResultSummaryProvenance,
|
|
389
|
+
): string {
|
|
390
|
+
const lines = [
|
|
391
|
+
`[tool-result ${appliedMode}]`,
|
|
392
|
+
`kind=${input.kind} capturedBytes=${input.capturedBytes} displayedBytes=${input.displayedBytes} truncated=${String(truncated)}`,
|
|
393
|
+
retrievalLine(input.offloadPath, input.followUpHint),
|
|
394
|
+
`followUp=${JSON.stringify(input.followUpHint)}`,
|
|
395
|
+
];
|
|
396
|
+
if (provenance !== undefined) {
|
|
397
|
+
lines.push(`summary=code/${provenance.algorithm} sha256=${provenance.sourceSha256} lines=${provenance.sourceLines}`);
|
|
398
|
+
}
|
|
399
|
+
if (input.disposition.fallback !== undefined) {
|
|
400
|
+
lines.push(`fallback=${input.disposition.fallback.reason} ${JSON.stringify(input.disposition.fallback.message)}`);
|
|
401
|
+
}
|
|
402
|
+
const facts = essentialFacts(input.details);
|
|
403
|
+
if (facts !== null) lines.push(`facts=${stableJson(facts)}`);
|
|
404
|
+
return lines.join("\n");
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function joinHeaderAndBody(
|
|
408
|
+
header: string,
|
|
409
|
+
body: string,
|
|
410
|
+
maxBytes: number,
|
|
411
|
+
bias: ToolResultExcerptBias = "head-tail",
|
|
412
|
+
): string {
|
|
413
|
+
if (body.length === 0) return capUtf8(header, maxBytes);
|
|
414
|
+
const separator = "\n";
|
|
415
|
+
const headerBytes = byteLength(header);
|
|
416
|
+
const separatorBytes = byteLength(separator);
|
|
417
|
+
if (headerBytes + separatorBytes >= maxBytes) return capUtf8(header, maxBytes);
|
|
418
|
+
const bodyBudget = maxBytes - headerBytes - separatorBytes;
|
|
419
|
+
return `${header}${separator}${boundedToolResultExcerpt(body, bodyBudget, bias)}`;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function fullTrailer(input: ProjectToolResultContextInput): string {
|
|
423
|
+
const facts = essentialFacts(input.details);
|
|
424
|
+
const lines: string[] = [];
|
|
425
|
+
if (facts !== null) lines.push(`facts=${stableJson(facts)}`);
|
|
426
|
+
if (input.followUpHint.length > 0) lines.push(`followUp=${JSON.stringify(input.followUpHint)}`);
|
|
427
|
+
return lines.length === 0 ? "" : `[tool-result metadata]\n${lines.join("\n")}`;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const NUL = "\u0000";
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* A raw NUL cannot survive the provider conversation as content: it is not
|
|
434
|
+
* printable, it terminates strings in most consumers downstream of the model,
|
|
435
|
+
* and it carries no diagnostic value. It is removed from the model projection
|
|
436
|
+
* only. Removal is byte-exact and touches no other byte, so multi-byte code
|
|
437
|
+
* points stay whole and ANSI escape sequences are never cut mid-sequence.
|
|
438
|
+
* Presentation text and the retrieval artifact keep the captured bytes.
|
|
439
|
+
*/
|
|
440
|
+
function stripNulForContext(text: string): { text: string; stripped: number } {
|
|
441
|
+
const parts = text.split(NUL);
|
|
442
|
+
return parts.length === 1 ? { text, stripped: 0 } : { text: parts.join(""), stripped: parts.length - 1 };
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** Build the deterministic model projection for a normalized disposition. */
|
|
446
|
+
export function projectToolResultContext(input: ProjectToolResultContextInput): ToolResultContextProjection {
|
|
447
|
+
const requestedMode = input.disposition.context.mode;
|
|
448
|
+
const maxBytes = input.disposition.context.maxBytes;
|
|
449
|
+
const sanitized = stripNulForContext(input.text);
|
|
450
|
+
const body = sanitized.text;
|
|
451
|
+
// The captured text can already be a partial capture, and NUL removal drops
|
|
452
|
+
// bytes the operator surface still holds. Either way content is omitted.
|
|
453
|
+
const capturedTextIsComplete = input.capturedBytes <= byteLength(input.text) && sanitized.stripped === 0;
|
|
454
|
+
if (requestedMode === "full") {
|
|
455
|
+
const trailer = fullTrailer(input);
|
|
456
|
+
const candidate = trailer.length === 0 ? body : `${body}\n\n${trailer}`;
|
|
457
|
+
if (capturedTextIsComplete && byteLength(candidate) <= maxBytes) {
|
|
458
|
+
return { text: candidate, appliedMode: "full", truncated: false };
|
|
459
|
+
}
|
|
460
|
+
const header = contextHeader(input, "bounded", true);
|
|
461
|
+
return {
|
|
462
|
+
text: joinHeaderAndBody(header, body, maxBytes, input.disposition.context.downgradeExcerpt),
|
|
463
|
+
appliedMode: "bounded",
|
|
464
|
+
truncated: true,
|
|
465
|
+
downgrade: { from: "full", to: "bounded", reason: "hard-budget" },
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
if (requestedMode === "metadata-only") {
|
|
469
|
+
return {
|
|
470
|
+
text: capUtf8(contextHeader(input, "metadata-only", input.capturedBytes > 0), maxBytes),
|
|
471
|
+
appliedMode: "metadata-only",
|
|
472
|
+
truncated: input.capturedBytes > 0,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
if (requestedMode === "summary") {
|
|
476
|
+
const diagnostic = input.disposition.context.strategy === "diagnostic";
|
|
477
|
+
const algorithm = diagnostic ? "sha256-diagnostic-v1" : "sha256-head-tail-v1";
|
|
478
|
+
const redact = input.disposition.context.redact !== false;
|
|
479
|
+
const budgetHeader = contextHeader(input, "summary", true, summaryProvenance(input.text, algorithm));
|
|
480
|
+
const bodyBudget = Math.max(0, maxBytes - byteLength(budgetHeader) - 1);
|
|
481
|
+
const selected = diagnostic
|
|
482
|
+
? deterministicDiagnosticSummary(body, bodyBudget, redact)
|
|
483
|
+
: deterministicHeadTailSummary(body, redact);
|
|
484
|
+
const provenance = summaryProvenance(input.text, algorithm, selected.redactions);
|
|
485
|
+
// "Nothing omitted" is only true if the whole selection also survives the
|
|
486
|
+
// header it would then render, so the claim can never itself be truncated.
|
|
487
|
+
const completeHeader = contextHeader(input, "summary", false, provenance);
|
|
488
|
+
const fits = byteLength(completeHeader) + 1 + byteLength(selected.text) <= maxBytes;
|
|
489
|
+
const truncated = input.capturedBytes > 0 && !(capturedTextIsComplete && selected.complete && fits);
|
|
490
|
+
const header = contextHeader(input, "summary", truncated, provenance);
|
|
491
|
+
return {
|
|
492
|
+
text: joinHeaderAndBody(header, selected.text, maxBytes),
|
|
493
|
+
appliedMode: "summary",
|
|
494
|
+
truncated,
|
|
495
|
+
summaryProvenance: provenance,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
const header = contextHeader(input, "bounded", input.capturedBytes > maxBytes);
|
|
499
|
+
const text = joinHeaderAndBody(header, body, maxBytes, input.disposition.context.excerpt);
|
|
500
|
+
return {
|
|
501
|
+
text,
|
|
502
|
+
appliedMode: "bounded",
|
|
503
|
+
truncated: byteLength(header) + 1 + byteLength(body) > maxBytes || !capturedTextIsComplete,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** The text inserted into the provider conversation for a registry result. */
|
|
508
|
+
export function toolResultContextText(result: ToolResult): string {
|
|
509
|
+
if (result.modelContext !== undefined) return result.modelContext;
|
|
510
|
+
return result.kind === "ok" ? result.output : result.message;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/** Whether a registry or agent-tool result is an applied canonical failure envelope. */
|
|
514
|
+
export function isDispositionedToolResultError(result: unknown): boolean {
|
|
515
|
+
if (!isRecord(result)) return false;
|
|
516
|
+
const details = isRecord(result.details) ? result.details : null;
|
|
517
|
+
const metadata = details !== null && isRecord(details.resultDisposition) ? details.resultDisposition : null;
|
|
518
|
+
const kind = result.kind ?? details?.kind;
|
|
519
|
+
return kind === "error" && metadata?.version === 1 && metadata.applications === 1;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/** Operator text retained beside an independently projected AgentToolResult. */
|
|
523
|
+
export function toolResultPresentationText(result: unknown): string | null {
|
|
524
|
+
if (!isRecord(result)) return null;
|
|
525
|
+
const details = isRecord(result.details) ? result.details : null;
|
|
526
|
+
const metadata = details !== null && isRecord(details.resultDisposition) ? details.resultDisposition : null;
|
|
527
|
+
const presentation = metadata !== null && isRecord(metadata.presentation) ? metadata.presentation : null;
|
|
528
|
+
return presentation !== null && typeof presentation.content === "string" ? presentation.content : null;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** Applied fold/excerpt policy retained with a projected result for operator surfaces. */
|
|
532
|
+
export function toolResultPresentationPolicy(result: unknown): ToolPresentationPolicy | null {
|
|
533
|
+
if (!isRecord(result)) return null;
|
|
534
|
+
const details = isRecord(result.details) ? result.details : null;
|
|
535
|
+
const metadata = details !== null && isRecord(details.resultDisposition) ? details.resultDisposition : null;
|
|
536
|
+
const presentation = metadata !== null && isRecord(metadata.presentation) ? metadata.presentation : null;
|
|
537
|
+
if (
|
|
538
|
+
presentation === null ||
|
|
539
|
+
(presentation.foldDefault !== "folded" && presentation.foldDefault !== "expanded") ||
|
|
540
|
+
typeof presentation.showDiffWhenFolded !== "boolean" ||
|
|
541
|
+
typeof presentation.failureExcerpt !== "boolean"
|
|
542
|
+
) {
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
foldDefault: presentation.foldDefault,
|
|
547
|
+
showDiffWhenFolded: presentation.showDiffWhenFolded,
|
|
548
|
+
failureExcerpt: presentation.failureExcerpt,
|
|
549
|
+
};
|
|
550
|
+
}
|