@inflexa-ai/harness 0.45.1 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/agents/conversation-agent.d.ts +9 -3
- package/dist/agents/conversation-agent.js +34 -33
- package/dist/agents/report-session-agent.d.ts +15 -15
- package/dist/agents/report-session-agent.js +23 -15
- package/dist/agents/sandbox/index.d.ts +1 -1
- package/dist/agents/sandbox/index.js +1 -1
- package/dist/agents/sandbox/shared.d.ts +12 -14
- package/dist/agents/sandbox/shared.js +18 -12
- package/dist/agents/sandbox/types.d.ts +4 -4
- package/dist/agents/sandbox/validate-skills.d.ts +2 -2
- package/dist/agents/sandbox/validate-skills.js +3 -1
- package/dist/agents/sandbox-catalog.d.ts +2 -2
- package/dist/agents/sandbox-catalog.js +3 -3
- package/dist/agents/system-prompt.d.ts +2 -2
- package/dist/app/chat-turn-workflow.d.ts +114 -0
- package/dist/app/chat-turn-workflow.js +307 -0
- package/dist/app/chat-turn.d.ts +85 -57
- package/dist/app/chat-turn.js +123 -100
- package/dist/app/data-profile-orientation.js +10 -8
- package/dist/app/data-profile-view.d.ts +4 -4
- package/dist/app/data-profile-view.js +9 -8
- package/dist/app/in-process-turn.d.ts +42 -0
- package/dist/app/in-process-turn.js +139 -0
- package/dist/app/message-assembly.d.ts +10 -4
- package/dist/app/message-assembly.js +13 -8
- package/dist/app/report-session-runtime.d.ts +4 -4
- package/dist/app/report-session-runtime.js +36 -21
- package/dist/app/spawn-report-session.d.ts +33 -28
- package/dist/app/spawn-report-session.js +25 -40
- package/dist/app/synthesize-run.d.ts +22 -16
- package/dist/app/synthesize-run.js +41 -35
- package/dist/app/work.d.ts +96 -0
- package/dist/app/work.js +147 -0
- package/dist/auth/local-auth-context.d.ts +1 -3
- package/dist/auth/local-auth-context.js +1 -3
- package/dist/auth/local-run-authorizer.d.ts +1 -1
- package/dist/auth/local-run-authorizer.js +5 -3
- package/dist/auth/types.d.ts +26 -26
- package/dist/billing/cached-resolver.d.ts +4 -4
- package/dist/billing/run-charge.d.ts +5 -8
- package/dist/billing/run-charge.js +2 -2
- package/dist/billing/usage-recorder.d.ts +9 -9
- package/dist/citations/aggregate.js +1 -1
- package/dist/citations/clients/arxiv.d.ts +2 -2
- package/dist/citations/clients/arxiv.js +12 -4
- package/dist/citations/clients/common.js +4 -2
- package/dist/citations/clients/crossref.d.ts +2 -2
- package/dist/citations/clients/crossref.js +37 -37
- package/dist/citations/clients/doi-registry.js +15 -16
- package/dist/citations/clients/pubmed.d.ts +2 -2
- package/dist/citations/clients/pubmed.js +20 -13
- package/dist/citations/clients/semantic-scholar.d.ts +2 -2
- package/dist/citations/clients/semantic-scholar.js +9 -9
- package/dist/citations/compare.js +2 -2
- package/dist/citations/match.d.ts +2 -2
- package/dist/citations/match.js +12 -7
- package/dist/citations/normalize.js +35 -26
- package/dist/citations/plan.js +6 -2
- package/dist/citations/resolve.d.ts +6 -6
- package/dist/citations/resolve.js +34 -25
- package/dist/citations/types.d.ts +77 -77
- package/dist/config/environment-stores.d.ts +6 -6
- package/dist/config/resource-limits.js +1 -3
- package/dist/contracts/analogy-report.d.ts +7 -7
- package/dist/contracts/analogy-report.js +2 -2
- package/dist/contracts/chat-events.d.ts +38 -24
- package/dist/contracts/chat-frame.d.ts +15 -5
- package/dist/contracts/chat-frame.js +64 -41
- package/dist/contracts/chat-parts.d.ts +130 -67
- package/dist/contracts/chat-parts.js +1 -0
- package/dist/contracts/content-url.js +16 -2
- package/dist/contracts/data-profile.d.ts +38 -38
- package/dist/contracts/index.d.ts +2 -2
- package/dist/contracts/index.js +1 -1
- package/dist/contracts/message.d.ts +6 -6
- package/dist/contracts/organ-system.d.ts +11 -11
- package/dist/contracts/part-registry.d.ts +5 -4
- package/dist/contracts/part-registry.js +5 -0
- package/dist/contracts/profile-vocabulary.d.ts +11 -10
- package/dist/contracts/profile-vocabulary.js +10 -3
- package/dist/contracts/report-blocks.d.ts +329 -329
- package/dist/contracts/report-reference.d.ts +105 -81
- package/dist/contracts/report-reference.js +30 -13
- package/dist/contracts/schemas/chat-events.d.ts +30 -16
- package/dist/contracts/schemas/chat-events.js +15 -6
- package/dist/contracts/schemas/chat-parts.d.ts +175 -102
- package/dist/contracts/schemas/chat-parts.js +33 -7
- package/dist/contracts/severity.d.ts +1 -1
- package/dist/contracts/structure-source.d.ts +3 -3
- package/dist/contracts/structure-source.js +6 -2
- package/dist/contracts/subagent-activity.d.ts +2 -2
- package/dist/contracts/subagent-activity.js +5 -3
- package/dist/contracts/usage.d.ts +2 -2
- package/dist/data/safety-panel-schema.d.ts +25 -25
- package/dist/data/safety-panel-schema.js +13 -4
- package/dist/execution/artifact-metadata.d.ts +23 -15
- package/dist/execution/artifact-metadata.js +33 -10
- package/dist/execution/artifact-record.d.ts +2 -2
- package/dist/execution/artifact-registration.d.ts +5 -5
- package/dist/execution/artifact-registry.d.ts +8 -8
- package/dist/execution/dbos-run-launcher.d.ts +24 -4
- package/dist/execution/dbos-run-launcher.js +58 -4
- package/dist/execution/dbos-workflow-purger.d.ts +5 -6
- package/dist/execution/dbos-workflow-purger.js +8 -7
- package/dist/execution/input-signature.d.ts +2 -2
- package/dist/execution/post-step-pipeline.d.ts +9 -9
- package/dist/execution/post-step-pipeline.js +1 -1
- package/dist/execution/post-step.js +2 -2
- package/dist/execution/reconcile-manifest.d.ts +4 -4
- package/dist/execution/reconcile-manifest.js +3 -2
- package/dist/execution/run-authorizer.d.ts +46 -37
- package/dist/execution/run-authorizer.js +8 -8
- package/dist/execution/run-canceler.d.ts +7 -7
- package/dist/execution/run-canceler.js +14 -7
- package/dist/execution/run-end-record.d.ts +2 -3
- package/dist/execution/run-event-parts.d.ts +0 -8
- package/dist/execution/run-event-parts.js +29 -24
- package/dist/execution/run-event-stream.d.ts +8 -8
- package/dist/execution/run-event-stream.js +3 -4
- package/dist/execution/run-launcher.d.ts +4 -4
- package/dist/execution/run-synthesis.d.ts +49 -48
- package/dist/execution/run-synthesis.js +96 -255
- package/dist/execution/staged-input.d.ts +2 -2
- package/dist/execution/step-summary.d.ts +6 -6
- package/dist/execution/step-summary.js +4 -8
- package/dist/execution/topo-sort.js +4 -3
- package/dist/execution/workflow-purger.d.ts +2 -2
- package/dist/index.d.ts +17 -20
- package/dist/index.js +25 -36
- package/dist/input-sanitization.js +9 -2
- package/dist/input-scan/clustering.d.ts +6 -6
- package/dist/input-scan/clustering.js +11 -5
- package/dist/input-scan/companions.js +3 -2
- package/dist/input-scan/detect-sets.d.ts +2 -2
- package/dist/input-scan/detect-sets.js +25 -21
- package/dist/input-scan/enrich.d.ts +6 -6
- package/dist/input-scan/enrich.js +29 -12
- package/dist/input-scan/formats.d.ts +2 -2
- package/dist/input-scan/formats.js +8 -2
- package/dist/input-scan/markers.d.ts +2 -2
- package/dist/input-scan/markers.js +14 -5
- package/dist/input-scan/menu.d.ts +10 -10
- package/dist/input-scan/menu.js +0 -0
- package/dist/input-scan/quarantine.d.ts +2 -2
- package/dist/input-scan/quarantine.js +23 -18
- package/dist/input-scan/readout-budget.d.ts +2 -2
- package/dist/input-scan/readout-budget.js +1 -1
- package/dist/input-scan/readout.d.ts +2 -2
- package/dist/input-scan/readout.js +47 -34
- package/dist/input-scan/scan.d.ts +2 -2
- package/dist/input-scan/scan.js +7 -5
- package/dist/input-scan/set-types.d.ts +28 -28
- package/dist/input-scan/shapes.d.ts +4 -4
- package/dist/input-scan/shapes.js +0 -0
- package/dist/input-scan/templates.d.ts +10 -10
- package/dist/input-scan/templates.js +30 -22
- package/dist/input-scan/tokens.d.ts +6 -4
- package/dist/input-scan/tokens.js +100 -38
- package/dist/input-scan/types.d.ts +22 -22
- package/dist/lib/async-utils.d.ts +3 -7
- package/dist/lib/async-utils.js +5 -9
- package/dist/lib/cache.js +1 -1
- package/dist/lib/chrome.d.ts +46 -6
- package/dist/lib/console-logger.js +2 -2
- package/dist/lib/db-result.d.ts +22 -6
- package/dist/lib/db-result.js +9 -13
- package/dist/lib/eyes.d.ts +4 -4
- package/dist/lib/eyes.js +1 -1
- package/dist/lib/fs-helpers.js +18 -6
- package/dist/lib/fs-result.d.ts +1 -0
- package/dist/lib/fs-result.js +5 -6
- package/dist/lib/hooks.d.ts +23 -7
- package/dist/lib/hooks.js +17 -3
- package/dist/lib/host-concurrency.js +3 -2
- package/dist/lib/logger.d.ts +2 -2
- package/dist/lib/logger.js +2 -1
- package/dist/lib/metrics.d.ts +1 -1
- package/dist/lib/null-proto.d.ts +6 -0
- package/dist/lib/null-proto.js +17 -0
- package/dist/lib/otel-spans.d.ts +1 -1
- package/dist/lib/otel-spans.js +9 -5
- package/dist/lib/otel.d.ts +4 -2
- package/dist/lib/otel.js +10 -2
- package/dist/lib/page-capture.d.ts +8 -8
- package/dist/lib/page-capture.js +9 -4
- package/dist/lib/parse-json.d.ts +5 -0
- package/dist/lib/parse-json.js +12 -0
- package/dist/lib/present.d.ts +12 -0
- package/dist/lib/present.js +39 -0
- package/dist/lib/rate-limit.d.ts +4 -4
- package/dist/lib/rate-limit.js +12 -13
- package/dist/lib/regulatory-corpus/fetch-fda-guidance.d.ts +4 -4
- package/dist/lib/regulatory-corpus/fetch-fda-guidance.js +4 -10
- package/dist/lib/regulatory-corpus/fetch-ich.d.ts +4 -4
- package/dist/lib/regulatory-corpus/fetch-ich.js +18 -14
- package/dist/lib/regulatory-corpus/normalize-whitespace.d.ts +6 -0
- package/dist/lib/regulatory-corpus/normalize-whitespace.js +41 -0
- package/dist/lib/regulatory-corpus.d.ts +10 -10
- package/dist/lib/regulatory-corpus.js +3 -1
- package/dist/lib/result.d.ts +10 -11
- package/dist/lib/result.js +15 -13
- package/dist/lib/storage.d.ts +2 -2
- package/dist/lib/test/async-iterable.d.ts +9 -0
- package/dist/lib/test/async-iterable.js +28 -0
- package/dist/lib/test/request-url.d.ts +5 -0
- package/dist/lib/test/request-url.js +7 -0
- package/dist/lib/test/result-assert.d.ts +12 -0
- package/dist/lib/test/result-assert.js +17 -0
- package/dist/lib/test/text-shapes.d.ts +24 -0
- package/dist/lib/test/text-shapes.js +63 -0
- package/dist/lib/text.d.ts +22 -0
- package/dist/lib/text.js +68 -0
- package/dist/lib/thrown.d.ts +18 -0
- package/dist/lib/thrown.js +30 -0
- package/dist/lib/unknown-array.d.ts +6 -0
- package/dist/lib/unknown-array.js +8 -0
- package/dist/lib/unknown-containers.d.ts +14 -0
- package/dist/lib/unknown-containers.js +17 -0
- package/dist/lib/unknown-text.d.ts +8 -0
- package/dist/lib/unknown-text.js +28 -0
- package/dist/lib/zod-issue-shape.d.ts +1 -1
- package/dist/lib/zod-issue-shape.js +31 -31
- package/dist/lib/zod-issues.d.ts +3 -3
- package/dist/lib/zod-issues.js +30 -17
- package/dist/literature/sources/arxiv.d.ts +6 -6
- package/dist/literature/sources/arxiv.js +92 -35
- package/dist/literature/sources/http.d.ts +2 -2
- package/dist/literature/sources/http.js +16 -15
- package/dist/literature/sources/pubmed.d.ts +16 -16
- package/dist/literature/sources/pubmed.js +96 -61
- package/dist/literature/sources/semantic-scholar.d.ts +8 -8
- package/dist/literature/sources/semantic-scholar.js +22 -13
- package/dist/loop/accounting.d.ts +63 -0
- package/dist/loop/accounting.js +141 -0
- package/dist/loop/call-policy.d.ts +73 -0
- package/dist/loop/call-policy.js +109 -0
- package/dist/loop/chat-stream.d.ts +62 -0
- package/dist/loop/chat-stream.js +237 -0
- package/dist/loop/compaction.d.ts +9 -9
- package/dist/loop/compaction.js +1 -1
- package/dist/loop/metrics.d.ts +12 -15
- package/dist/loop/metrics.js +14 -26
- package/dist/loop/run-loop.d.ts +233 -0
- package/dist/loop/run-loop.js +770 -0
- package/dist/loop/sub-agent.d.ts +61 -0
- package/dist/loop/sub-agent.js +161 -0
- package/dist/loop/tool-adapter.d.ts +98 -0
- package/dist/loop/tool-adapter.js +364 -0
- package/dist/loop/tool-detail.d.ts +1 -1
- package/dist/loop/tool-detail.js +6 -5
- package/dist/loop/tool-outcome.d.ts +1 -1
- package/dist/loop/tool-output.d.ts +4 -4
- package/dist/loop/tool-output.js +2 -2
- package/dist/loop/turn-credentials.d.ts +34 -0
- package/dist/loop/turn-credentials.js +73 -0
- package/dist/loop/types.d.ts +29 -38
- package/dist/loop/types.js +2 -7
- package/dist/memory/ai-sdk-message-storage.d.ts +5 -5
- package/dist/memory/ai-sdk-message-storage.js +13 -11
- package/dist/memory/card-builders.d.ts +14 -14
- package/dist/memory/card-builders.js +19 -15
- package/dist/memory/conversation-display-recorder.d.ts +22 -4
- package/dist/memory/conversation-display-recorder.js +43 -24
- package/dist/memory/conversation-display-replay.js +3 -2
- package/dist/memory/conversation-display-storage.d.ts +15 -7
- package/dist/memory/conversation-display-storage.js +141 -36
- package/dist/memory/conversation-view.d.ts +4 -4
- package/dist/memory/conversation-view.js +5 -4
- package/dist/memory/count-tokens.js +10 -8
- package/dist/memory/derive-thread-title.d.ts +0 -13
- package/dist/memory/derive-thread-title.js +2 -1
- package/dist/memory/message-backfill.d.ts +12 -5
- package/dist/memory/message-backfill.js +5 -3
- package/dist/memory/thread-history.d.ts +36 -21
- package/dist/memory/thread-history.js +78 -54
- package/dist/memory/thread-store.d.ts +26 -17
- package/dist/memory/thread-store.js +15 -9
- package/dist/memory/tool-call-integrity.d.ts +2 -2
- package/dist/memory/tool-call-integrity.js +4 -3
- package/dist/memory/working-memory.d.ts +16 -12
- package/dist/memory/working-memory.js +72 -61
- package/dist/prompts/briefing.d.ts +12 -12
- package/dist/prompts/briefing.js +1 -1
- package/dist/prompts/synthesis-agent.d.ts +1 -1
- package/dist/prompts/synthesis-agent.js +6 -14
- package/dist/provenance/collector.d.ts +6 -6
- package/dist/provenance/collector.js +15 -8
- package/dist/provenance/exec-frame.d.ts +2 -2
- package/dist/provenance/exec-frame.js +6 -1
- package/dist/provenance/seam.d.ts +1 -4
- package/dist/provenance/types.d.ts +6 -6
- package/dist/providers/ai-sdk.d.ts +113 -62
- package/dist/providers/ai-sdk.js +283 -518
- package/dist/providers/embedding.d.ts +2 -2
- package/dist/providers/embedding.js +1 -1
- package/dist/providers/errors.d.ts +22 -2
- package/dist/providers/errors.js +98 -50
- package/dist/providers/prompt-cache.d.ts +8 -8
- package/dist/providers/prompt-cache.js +13 -11
- package/dist/providers/reasoning.d.ts +2 -2
- package/dist/providers/reasoning.js +2 -2
- package/dist/providers/types.d.ts +55 -94
- package/dist/reference-data/catalog.d.ts +2 -15
- package/dist/reference-data/catalog.js +25 -8
- package/dist/reference-data/receipt.d.ts +2 -2
- package/dist/report-model/assert-rules.js +1 -1
- package/dist/report-model/authoring-grammar.d.ts +211 -233
- package/dist/report-model/block-walk.d.ts +10 -10
- package/dist/report-model/block-walk.js +2 -0
- package/dist/report-model/draft-finish.d.ts +8 -8
- package/dist/report-model/draft-finish.js +1 -1
- package/dist/report-model/draft-hash.js +8 -7
- package/dist/report-model/draft-operations.d.ts +22 -10
- package/dist/report-model/draft-operations.js +48 -30
- package/dist/report-model/draft-read.d.ts +19 -7
- package/dist/report-model/draft-read.js +16 -6
- package/dist/report-model/draft.d.ts +844 -2
- package/dist/report-model/draft.js +3 -1
- package/dist/report-model/fixture-resolver.js +39 -16
- package/dist/report-model/pin-snapshot.d.ts +2 -2
- package/dist/report-model/pin-snapshot.js +15 -15
- package/dist/report-model/production-resolver.d.ts +18 -18
- package/dist/report-model/production-resolver.js +50 -42
- package/dist/report-model/reference-resolver.d.ts +26 -10
- package/dist/report-model/reference-resolver.js +68 -5
- package/dist/report-model/reference-stamp.js +30 -38
- package/dist/report-model/structural-validation.js +12 -4
- package/dist/report-model/validate.d.ts +7 -7
- package/dist/report-model/validate.js +6 -6
- package/dist/report-render/assets.d.ts +2 -2
- package/dist/report-render/assets.js +27 -3
- package/dist/report-render/chart-export.d.ts +10 -2
- package/dist/report-render/chart-export.js +37 -11
- package/dist/report-render/chart-presets.d.ts +8 -8
- package/dist/report-render/chart-presets.js +3 -3
- package/dist/report-render/chart-renderers.d.ts +23 -16
- package/dist/report-render/chart-renderers.js +74 -53
- package/dist/report-render/chart-toolbox.d.ts +2 -2
- package/dist/report-render/chart-toolbox.js +7 -6
- package/dist/report-render/chart.d.ts +15 -16
- package/dist/report-render/chart.js +369 -252
- package/dist/report-render/design.d.ts +36 -297
- package/dist/report-render/design.js +0 -12
- package/dist/report-render/figures/common.d.ts +12 -12
- package/dist/report-render/figures/common.js +42 -31
- package/dist/report-render/figures/dendrogram.d.ts +3 -3
- package/dist/report-render/figures/dendrogram.js +10 -10
- package/dist/report-render/figures/dense.js +15 -8
- package/dist/report-render/figures/dotplot.js +46 -33
- package/dist/report-render/figures/embedding.js +52 -43
- package/dist/report-render/figures/equal-units.d.ts +10 -10
- package/dist/report-render/figures/equal-units.js +27 -27
- package/dist/report-render/figures/forest.js +9 -7
- package/dist/report-render/figures/gsea.js +21 -11
- package/dist/report-render/figures/heatmap.js +13 -9
- package/dist/report-render/figures/index.d.ts +10 -10
- package/dist/report-render/figures/index.js +4 -2
- package/dist/report-render/figures/km.js +10 -9
- package/dist/report-render/figures/label-room.d.ts +12 -12
- package/dist/report-render/figures/label-room.js +7 -2
- package/dist/report-render/figures/locuszoom.d.ts +2 -2
- package/dist/report-render/figures/locuszoom.js +59 -31
- package/dist/report-render/figures/lollipop.js +3 -2
- package/dist/report-render/figures/ma.js +8 -4
- package/dist/report-render/figures/manhattan.d.ts +1 -2
- package/dist/report-render/figures/manhattan.js +52 -34
- package/dist/report-render/figures/oncoprint.js +4 -5
- package/dist/report-render/figures/pca.js +25 -11
- package/dist/report-render/figures/qq.js +20 -9
- package/dist/report-render/figures/roc.js +1 -1
- package/dist/report-render/figures/sankey.d.ts +2 -2
- package/dist/report-render/figures/sankey.js +11 -10
- package/dist/report-render/figures/upset.d.ts +6 -6
- package/dist/report-render/figures/upset.js +7 -4
- package/dist/report-render/figures/volcano.js +12 -10
- package/dist/report-render/hybrid-svg.d.ts +4 -4
- package/dist/report-render/hybrid-svg.js +7 -5
- package/dist/report-render/number-format.d.ts +4 -4
- package/dist/report-render/number-format.js +62 -27
- package/dist/report-render/page.js +2 -1
- package/dist/report-render/provenance-data.d.ts +2 -2
- package/dist/report-render/references.d.ts +4 -4
- package/dist/report-render/render.d.ts +2 -2
- package/dist/report-render/render.js +10 -11
- package/dist/report-render/script-json.js +1 -1
- package/dist/report-render/table-data.d.ts +6 -6
- package/dist/report-render/test/options.d.ts +49 -0
- package/dist/report-render/test/options.js +143 -0
- package/dist/report-render/test/page-function.d.ts +19 -0
- package/dist/report-render/test/page-function.js +33 -0
- package/dist/report-render/types.d.ts +10 -10
- package/dist/report-render/value-bridge.d.ts +4 -4
- package/dist/report-render/value-bridge.js +27 -14
- package/dist/report-render/views/chart-view.d.ts +2 -2
- package/dist/report-render/views/chart-view.js +2 -2
- package/dist/report-render/views/lineage.d.ts +6 -6
- package/dist/report-render/views/lineage.js +2 -0
- package/dist/report-render/views/page-view.d.ts +2 -2
- package/dist/report-render/views/page-view.js +1 -0
- package/dist/report-render/views/prose.js +25 -8
- package/dist/report-render/views/references-view.js +12 -1
- package/dist/report-render/views/values.js +1 -1
- package/dist/runtime/assemble.d.ts +53 -26
- package/dist/runtime/assemble.js +103 -56
- package/dist/runtime/boot.d.ts +8 -7
- package/dist/runtime/boot.js +1 -1
- package/dist/runtime/connection-budget.d.ts +6 -6
- package/dist/runtime/connection-budget.js +1 -1
- package/dist/runtime/dbos.d.ts +29 -19
- package/dist/runtime/dbos.js +35 -20
- package/dist/runtime/result-serialization.js +18 -5
- package/dist/runtime/shutdown.d.ts +2 -2
- package/dist/sandbox/client.d.ts +7 -3
- package/dist/sandbox/cpu-files.d.ts +2 -2
- package/dist/sandbox/cpu-files.js +21 -4
- package/dist/sandbox/create-sandbox.d.ts +7 -8
- package/dist/sandbox/create-sandbox.js +6 -12
- package/dist/sandbox/docker-client.d.ts +48 -3
- package/dist/sandbox/docker-client.js +10 -6
- package/dist/sandbox/estimate-data-profile-resources.js +2 -7
- package/dist/sandbox/exec-outcome.d.ts +2 -2
- package/dist/sandbox/exec-outcome.js +1 -1
- package/dist/sandbox/exec.d.ts +10 -6
- package/dist/sandbox/exec.js +27 -12
- package/dist/sandbox/execution-id.js +14 -6
- package/dist/sandbox/farm.d.ts +2 -2
- package/dist/sandbox/farm.js +22 -27
- package/dist/sandbox/identity.d.ts +7 -0
- package/dist/sandbox/identity.js +14 -3
- package/dist/sandbox/image-packages.js +15 -27
- package/dist/sandbox/k8s-client.d.ts +35 -8
- package/dist/sandbox/k8s-client.js +6 -5
- package/dist/sandbox/labels.js +1 -0
- package/dist/sandbox/liveness.js +3 -1
- package/dist/sandbox/mount-plan.d.ts +10 -10
- package/dist/sandbox/mount-plan.js +13 -14
- package/dist/sandbox/notification-sweep.d.ts +4 -4
- package/dist/sandbox/notification-sweep.js +4 -7
- package/dist/sandbox/package-identity.d.ts +6 -13
- package/dist/sandbox/package-identity.js +46 -28
- package/dist/sandbox/reaper.d.ts +10 -10
- package/dist/sandbox/reaper.js +4 -6
- package/dist/sandbox/sandbox-error.d.ts +2 -9
- package/dist/sandbox/sandbox-error.js +21 -8
- package/dist/sandbox/sandbox-step-translate.d.ts +8 -5
- package/dist/sandbox/sandbox-step-translate.js +22 -8
- package/dist/sandbox/step-sandbox.d.ts +47 -0
- package/dist/sandbox/step-sandbox.js +53 -0
- package/dist/sandbox/types.d.ts +18 -19
- package/dist/sandbox/types.js +1 -1
- package/dist/schemas/artifact-manifest.d.ts +4 -4
- package/dist/schemas/data-profile-schemas.d.ts +343 -298
- package/dist/schemas/file-metadata.js +2 -4
- package/dist/schemas/plan-schemas.d.ts +31 -31
- package/dist/schemas/run-synthesis.d.ts +95 -26
- package/dist/schemas/run-synthesis.js +3 -1
- package/dist/schemas/validate-plan.d.ts +4 -4
- package/dist/schemas/validate-plan.js +17 -5
- package/dist/schemas/workflow-state.d.ts +19 -19
- package/dist/schemas/workflow-state.js +2 -2
- package/dist/state/active-turns.d.ts +36 -0
- package/dist/state/active-turns.js +67 -0
- package/dist/state/analyses-metrics.d.ts +8 -8
- package/dist/state/analyses-metrics.js +4 -4
- package/dist/state/analyses.d.ts +0 -14
- package/dist/state/analyses.js +0 -32
- package/dist/state/artifacts.d.ts +32 -19
- package/dist/state/artifacts.js +28 -5
- package/dist/state/data-profile.d.ts +2 -2
- package/dist/state/db.d.ts +12 -2
- package/dist/state/db.js +5 -0
- package/dist/state/edit-intents.d.ts +4 -0
- package/dist/state/edit-intents.js +71 -0
- package/dist/state/index.d.ts +4 -4
- package/dist/state/index.js +2 -2
- package/dist/state/init.d.ts +2 -1
- package/dist/state/init.js +5 -2
- package/dist/state/migrations/20260927120000_baseline.js +3 -1
- package/dist/state/migrations/20261026120000_add_the_edit_intents.d.ts +7 -0
- package/dist/state/migrations/20261026120000_add_the_edit_intents.js +21 -0
- package/dist/state/migrations/20261027120000_record_the_applied_memory_edits.d.ts +7 -0
- package/dist/state/migrations/20261027120000_record_the_applied_memory_edits.js +16 -0
- package/dist/state/migrations/20261027120100_store_the_authoring_results.d.ts +8 -0
- package/dist/state/migrations/20261027120100_store_the_authoring_results.js +20 -0
- package/dist/state/migrations/20261027120200_key_the_groups_of_a_turn.d.ts +7 -0
- package/dist/state/migrations/20261027120200_key_the_groups_of_a_turn.js +12 -0
- package/dist/state/migrations/20261027120300_mark_the_active_turn_of_a_thread.d.ts +7 -0
- package/dist/state/migrations/20261027120300_mark_the_active_turn_of_a_thread.js +11 -0
- package/dist/state/migrations/20261028120000_record_the_paused_work.d.ts +7 -0
- package/dist/state/migrations/20261028120000_record_the_paused_work.js +25 -0
- package/dist/state/migrations/20261028120100_end_the_suspended_runs.d.ts +8 -0
- package/dist/state/migrations/20261028120100_end_the_suspended_runs.js +13 -0
- package/dist/state/migrations/20261029120000_reference_the_run_mandate.d.ts +7 -0
- package/dist/state/migrations/20261029120000_reference_the_run_mandate.js +23 -0
- package/dist/state/migrations/20261029120100_record_the_run_session_of_a_pause.d.ts +7 -0
- package/dist/state/migrations/20261029120100_record_the_run_session_of_a_pause.js +11 -0
- package/dist/state/migrations/index.js +18 -0
- package/dist/state/paused-work.d.ts +52 -0
- package/dist/state/paused-work.js +105 -0
- package/dist/state/plans.d.ts +27 -8
- package/dist/state/plans.js +14 -11
- package/dist/state/purge-analysis.d.ts +6 -6
- package/dist/state/purge-analysis.js +10 -2
- package/dist/state/report-session-state.d.ts +43 -18
- package/dist/state/report-session-state.js +43 -4
- package/dist/state/report-versions.d.ts +16 -11
- package/dist/state/report-versions.js +5 -3
- package/dist/state/runs.d.ts +30 -19
- package/dist/state/runs.js +43 -51
- package/dist/state/schema.d.ts +18 -21
- package/dist/state/schema.js +4 -5
- package/dist/state/snapshot-parse.d.ts +2 -2
- package/dist/state/snapshot-parse.js +14 -8
- package/dist/state/step-executions.d.ts +20 -8
- package/dist/state/step-executions.js +6 -4
- package/dist/state/tool-outputs.d.ts +2 -2
- package/dist/state/tool-outputs.js +2 -1
- package/dist/state/vector-store.d.ts +16 -16
- package/dist/state/vector-store.js +11 -7
- package/dist/tasks/data-profile-absorb.d.ts +3 -3
- package/dist/tasks/data-profile-absorb.js +21 -16
- package/dist/tasks/data-profile-activity.d.ts +5 -5
- package/dist/tasks/data-profile-activity.js +5 -5
- package/dist/tasks/data-profile-index.d.ts +4 -4
- package/dist/tasks/data-profile-index.js +32 -17
- package/dist/tasks/data-profile-resolve.d.ts +8 -8
- package/dist/tasks/data-profile-resolve.js +88 -62
- package/dist/tasks/data-profile.d.ts +40 -31
- package/dist/tasks/data-profile.js +138 -95
- package/dist/tasks/extract-values.d.ts +26 -79
- package/dist/tasks/extract-values.js +48 -108
- package/dist/tasks/refresh-regulatory-corpus.d.ts +4 -20
- package/dist/tasks/refresh-regulatory-corpus.js +12 -8
- package/dist/tasks/regulatory-corpus-boot-refresh.d.ts +2 -2
- package/dist/tasks/regulatory-corpus-boot-refresh.js +12 -10
- package/dist/testing.d.ts +60 -0
- package/dist/testing.js +96 -0
- package/dist/tools/ad-hoc-router.d.ts +9 -9
- package/dist/tools/ad-hoc-router.js +65 -52
- package/dist/tools/analysis-invocation.d.ts +7 -0
- package/dist/tools/analysis-invocation.js +29 -5
- package/dist/tools/approval/contract.d.ts +8 -52
- package/dist/tools/approval/contract.js +7 -36
- package/dist/tools/approval/gateway.d.ts +27 -28
- package/dist/tools/approval/gateway.js +33 -130
- package/dist/tools/approval/queries.d.ts +6 -11
- package/dist/tools/approval/queries.js +43 -29
- package/dist/tools/approval/uuidv7.js +2 -2
- package/dist/tools/bio/chembl.d.ts +3 -3
- package/dist/tools/bio/chembl.js +10 -0
- package/dist/tools/bio/comptox.d.ts +25 -8
- package/dist/tools/bio/comptox.js +4 -2
- package/dist/tools/bio/drug-gene-interactions.d.ts +27 -17
- package/dist/tools/bio/drug-gene-interactions.js +11 -8
- package/dist/tools/bio/gene-disease-evidence.d.ts +61 -15
- package/dist/tools/bio/gene-disease-evidence.js +10 -6
- package/dist/tools/bio/gene-preclinical-profile.d.ts +11 -11
- package/dist/tools/bio/keys.d.ts +2 -2
- package/dist/tools/bio/keys.js +3 -3
- package/dist/tools/bio/lookup-annotation.d.ts +5 -5
- package/dist/tools/bio/lookup-annotation.js +3 -0
- package/dist/tools/bio/opentargets.d.ts +1 -1
- package/dist/tools/bio/opentargets.js +6 -0
- package/dist/tools/bio/pubchem.d.ts +2 -2
- package/dist/tools/bio/pubchem.js +6 -0
- package/dist/tools/bio/pubmed.d.ts +26 -16
- package/dist/tools/bio/pubmed.js +9 -3
- package/dist/tools/bio/search-clinical-trials.d.ts +4 -4
- package/dist/tools/bio/search-clinical-trials.js +5 -1
- package/dist/tools/bio/search-faers.d.ts +2 -2
- package/dist/tools/bio/search-faers.js +1 -1
- package/dist/tools/bio/search-gene.d.ts +2 -2
- package/dist/tools/bio/search-gene.js +1 -0
- package/dist/tools/bio/search-geo-datasets.d.ts +4 -4
- package/dist/tools/bio/search-geo-datasets.js +5 -5
- package/dist/tools/bio/search-interactions.d.ts +1 -1
- package/dist/tools/bio/search-patents.d.ts +28 -22
- package/dist/tools/bio/search-patents.js +20 -17
- package/dist/tools/bio/search-protein.d.ts +2 -2
- package/dist/tools/bio/search-protein.js +1 -1
- package/dist/tools/bio/search-regulatory-approvals.d.ts +64 -18
- package/dist/tools/bio/search-regulatory-approvals.js +21 -16
- package/dist/tools/bio/target-safety.d.ts +8 -8
- package/dist/tools/bio/target-safety.js +11 -6
- package/dist/tools/capability-data.d.ts +9 -5
- package/dist/tools/capability-data.js +2 -1
- package/dist/tools/define-tool.d.ts +55 -46
- package/dist/tools/define-tool.js +39 -27
- package/dist/tools/display/normalize-echart-spec.d.ts +2 -2
- package/dist/tools/display/normalize-echart-spec.js +51 -27
- package/dist/tools/display/show-user.d.ts +2 -2
- package/dist/tools/display/show-user.js +1 -1
- package/dist/tools/execute-analysis.d.ts +19 -10
- package/dist/tools/execute-analysis.js +83 -62
- package/dist/tools/knowledge/check.d.ts +166 -44
- package/dist/tools/knowledge/check.js +5 -3
- package/dist/tools/knowledge/client.d.ts +31 -62
- package/dist/tools/knowledge/client.js +44 -27
- package/dist/tools/knowledge/environment.d.ts +6 -6
- package/dist/tools/knowledge/environment.js +18 -7
- package/dist/tools/knowledge/index.d.ts +2 -2
- package/dist/tools/knowledge/index.js +9 -9
- package/dist/tools/knowledge/local-slots.d.ts +4 -4
- package/dist/tools/knowledge/local-slots.js +11 -4
- package/dist/tools/knowledge/pin.d.ts +2 -2
- package/dist/tools/knowledge/recommend.d.ts +20 -45
- package/dist/tools/knowledge/recommend.js +27 -19
- package/dist/tools/knowledge/situation.d.ts +40 -40
- package/dist/tools/knowledge/situation.js +2 -9
- package/dist/tools/knowledge/skeleton.d.ts +4 -4
- package/dist/tools/knowledge/skeleton.js +10 -11
- package/dist/tools/knowledge/template.d.ts +20 -18
- package/dist/tools/knowledge/template.js +38 -32
- package/dist/tools/lib/alphafold-client.js +2 -1
- package/dist/tools/lib/amass-client.d.ts +28 -28
- package/dist/tools/lib/amass-client.js +20 -10
- package/dist/tools/lib/api-utils.d.ts +13 -3
- package/dist/tools/lib/api-utils.js +17 -3
- package/dist/tools/lib/bgee-client.d.ts +6 -6
- package/dist/tools/lib/bgee-client.js +16 -10
- package/dist/tools/lib/cbioportal-client.d.ts +4 -4
- package/dist/tools/lib/cbioportal-client.js +9 -8
- package/dist/tools/lib/chembl-client.d.ts +14 -14
- package/dist/tools/lib/chembl-client.js +11 -4
- package/dist/tools/lib/clinical-benchmarks-client.d.ts +4 -4
- package/dist/tools/lib/clinical-benchmarks-client.js +4 -7
- package/dist/tools/lib/clinical-trials-client.d.ts +18 -18
- package/dist/tools/lib/clinical-trials-client.js +10 -10
- package/dist/tools/lib/clinvar-client.d.ts +2 -2
- package/dist/tools/lib/clinvar-client.js +6 -6
- package/dist/tools/lib/ctx-ops.d.ts +23 -23
- package/dist/tools/lib/ctx-ops.js +13 -10
- package/dist/tools/lib/dgidb-client.d.ts +6 -6
- package/dist/tools/lib/dgidb-client.js +10 -12
- package/dist/tools/lib/disgenet-client.d.ts +4 -4
- package/dist/tools/lib/ema-client.d.ts +2 -2
- package/dist/tools/lib/ensembl-client.d.ts +4 -4
- package/dist/tools/lib/ensembl-client.js +5 -4
- package/dist/tools/lib/gwas-catalog-client.d.ts +4 -4
- package/dist/tools/lib/gwas-catalog-client.js +17 -17
- package/dist/tools/lib/html-text.d.ts +6 -0
- package/dist/tools/lib/html-text.js +9 -0
- package/dist/tools/lib/identifier-resolver.d.ts +4 -4
- package/dist/tools/lib/identifier-resolver.js +7 -3
- package/dist/tools/lib/impc-client.d.ts +8 -8
- package/dist/tools/lib/impc-client.js +46 -35
- package/dist/tools/lib/iuphar-client.d.ts +2 -2
- package/dist/tools/lib/iuphar-client.js +7 -16
- package/dist/tools/lib/monarch-client.d.ts +4 -4
- package/dist/tools/lib/openfda-client.d.ts +22 -22
- package/dist/tools/lib/openfda-client.js +23 -22
- package/dist/tools/lib/opentargets-client.d.ts +12 -12
- package/dist/tools/lib/pathway-client.d.ts +4 -4
- package/dist/tools/lib/pathway-client.js +15 -19
- package/dist/tools/lib/pharmgkb-client.d.ts +2 -2
- package/dist/tools/lib/pharmgkb-client.js +1 -1
- package/dist/tools/lib/protein-family-modality.d.ts +2 -2
- package/dist/tools/lib/protein-family-modality.js +0 -2
- package/dist/tools/lib/protein-family-normalize.js +7 -0
- package/dist/tools/lib/pubchem-client.d.ts +2 -2
- package/dist/tools/lib/pubchem-client.js +1 -0
- package/dist/tools/lib/pubchem-ops.d.ts +4 -4
- package/dist/tools/lib/pubchem-ops.js +8 -0
- package/dist/tools/lib/pubmed-client.d.ts +9 -9
- package/dist/tools/lib/pubmed-client.js +1 -1
- package/dist/tools/lib/string-client.d.ts +6 -6
- package/dist/tools/lib/uniprot-client.d.ts +8 -8
- package/dist/tools/lib/uniprot-client.js +5 -1
- package/dist/tools/memory/update-working-memory.d.ts +16 -13
- package/dist/tools/memory/update-working-memory.js +21 -3
- package/dist/tools/read-tool-output.js +8 -2
- package/dist/tools/registry.d.ts +2 -2
- package/dist/tools/report-authoring/authoring-tools.d.ts +54 -23
- package/dist/tools/report-authoring/authoring-tools.js +128 -62
- package/dist/tools/report-session/derive-table.d.ts +19 -59
- package/dist/tools/report-session/derive-table.js +120 -122
- package/dist/tools/report-session/examine-page.d.ts +6 -6
- package/dist/tools/report-session/examine-page.js +14 -12
- package/dist/tools/report-session/list-artifacts.d.ts +6 -6
- package/dist/tools/report-session/list-artifacts.js +5 -5
- package/dist/tools/report-session/page-faults.d.ts +6 -6
- package/dist/tools/report-session/page-faults.js +3 -1
- package/dist/tools/report-session/preview-report.d.ts +4 -3
- package/dist/tools/report-session/preview-report.js +26 -7
- package/dist/tools/report-session/record-version.d.ts +9 -4
- package/dist/tools/report-session/record-version.js +16 -9
- package/dist/tools/report-session/session-page-publisher.d.ts +6 -6
- package/dist/tools/research/generate-analogy-report.d.ts +14 -7
- package/dist/tools/research/generate-analogy-report.js +80 -76
- package/dist/tools/research/generate-plan.d.ts +16 -16
- package/dist/tools/research/generate-plan.js +410 -374
- package/dist/tools/research/guarded-address.js +3 -2
- package/dist/tools/research/inspect-data-profile.d.ts +27 -20
- package/dist/tools/research/inspect-data-profile.js +26 -25
- package/dist/tools/research/inspect-run.d.ts +54 -27
- package/dist/tools/research/inspect-run.js +50 -40
- package/dist/tools/research/literature-reviewer.d.ts +8 -7
- package/dist/tools/research/literature-reviewer.js +29 -28
- package/dist/tools/research/search-arxiv.d.ts +1 -1
- package/dist/tools/research/search-arxiv.js +1 -1
- package/dist/tools/research/search-github-repos.d.ts +10 -7
- package/dist/tools/research/search-github-repos.js +25 -22
- package/dist/tools/research/search-semantic-scholar.js +1 -1
- package/dist/tools/research/searxng-engine.d.ts +2 -2
- package/dist/tools/research/searxng-engine.js +6 -4
- package/dist/tools/research/web-fetch.d.ts +24 -21
- package/dist/tools/research/web-fetch.js +65 -74
- package/dist/tools/research/web-search.d.ts +26 -17
- package/dist/tools/research/web-search.js +26 -21
- package/dist/tools/sandbox/link-packages.d.ts +11 -7
- package/dist/tools/sandbox/link-packages.js +11 -8
- package/dist/tools/sandbox/list-available-packages.d.ts +17 -8
- package/dist/tools/sandbox/list-available-packages.js +31 -25
- package/dist/tools/sandbox/list-available-refs.d.ts +17 -14
- package/dist/tools/sandbox/list-available-refs.js +61 -36
- package/dist/tools/sandbox/report-blocker.d.ts +30 -37
- package/dist/tools/sandbox/report-blocker.js +23 -73
- package/dist/tools/sandbox/skills.d.ts +16 -12
- package/dist/tools/sandbox/skills.js +16 -12
- package/dist/tools/sandbox/submit-file-metadata.d.ts +12 -9
- package/dist/tools/sandbox/submit-file-metadata.js +18 -21
- package/dist/tools/start-report-session.d.ts +13 -5
- package/dist/tools/start-report-session.js +35 -8
- package/dist/tools/terminal-text.d.ts +5 -2
- package/dist/tools/terminal-text.js +35 -3
- package/dist/tools/workspace/decision-record.d.ts +4 -4
- package/dist/tools/workspace/decision-record.js +23 -13
- package/dist/tools/workspace/edit-file.d.ts +59 -20
- package/dist/tools/workspace/edit-file.js +81 -16
- package/dist/tools/workspace/execute-command.d.ts +15 -13
- package/dist/tools/workspace/execute-command.js +11 -9
- package/dist/tools/workspace/file-stat.d.ts +6 -3
- package/dist/tools/workspace/grep.d.ts +10 -7
- package/dist/tools/workspace/grep.js +9 -3
- package/dist/tools/workspace/list-files.d.ts +6 -3
- package/dist/tools/workspace/mutator.d.ts +28 -15
- package/dist/tools/workspace/mutator.js +107 -53
- package/dist/tools/workspace/read-file.d.ts +8 -5
- package/dist/tools/workspace/result-bounds.d.ts +5 -5
- package/dist/tools/workspace/result-bounds.js +4 -2
- package/dist/tools/workspace/run-exec.d.ts +2 -2
- package/dist/tools/workspace/run-exec.js +3 -3
- package/dist/tools/workspace/scan-inputs.d.ts +8 -5
- package/dist/tools/workspace/scan-inputs.js +17 -5
- package/dist/tools/workspace/show-file.d.ts +1 -1
- package/dist/tools/workspace/show-file.js +2 -1
- package/dist/tools/workspace/show-plan.d.ts +8 -5
- package/dist/tools/workspace/show-plan.js +9 -8
- package/dist/tools/workspace/workspace-search.d.ts +41 -13
- package/dist/tools/workspace/workspace-search.js +27 -14
- package/dist/tools/workspace/write-file.d.ts +9 -7
- package/dist/workflows/execute-analysis-scheduler.d.ts +9 -9
- package/dist/workflows/execute-analysis-scheduler.js +6 -3
- package/dist/workflows/execute-analysis.d.ts +46 -62
- package/dist/workflows/execute-analysis.js +137 -219
- package/dist/workflows/pause.d.ts +71 -0
- package/dist/workflows/pause.js +173 -0
- package/dist/workflows/sandbox-step.d.ts +38 -61
- package/dist/workflows/sandbox-step.js +99 -106
- package/dist/workflows/test/sql-writes.d.ts +21 -0
- package/dist/workflows/test/sql-writes.js +36 -0
- package/dist/workspace/filesystem.d.ts +6 -6
- package/dist/workspace/filesystem.js +10 -8
- package/dist/workspace/paths.d.ts +1 -1
- package/dist/workspace/paths.js +15 -4
- package/dist/workspace/search-config.d.ts +2 -2
- package/dist/workspace/search-config.js +1 -1
- package/package.json +21 -22
- package/dist/loop/call-guard.d.ts +0 -50
- package/dist/loop/call-guard.js +0 -90
- package/dist/loop/continue-agent.d.ts +0 -40
- package/dist/loop/continue-agent.js +0 -38
- package/dist/loop/genai-spans.d.ts +0 -47
- package/dist/loop/genai-spans.js +0 -134
- package/dist/loop/run-agent.d.ts +0 -245
- package/dist/loop/run-agent.js +0 -1124
- package/dist/loop/run-step.d.ts +0 -29
- package/dist/loop/run-step.js +0 -34
- package/dist/loop/run-to-terminal.d.ts +0 -66
- package/dist/loop/run-to-terminal.js +0 -93
- package/dist/loop/tool-mask.d.ts +0 -23
- package/dist/loop/tool-mask.js +0 -46
- package/dist/providers/streaming-chat.d.ts +0 -32
- package/dist/providers/streaming-chat.js +0 -91
- package/dist/tasks/derive-table-exec.d.ts +0 -54
- package/dist/tasks/derive-table-exec.js +0 -86
- package/dist/workflows/suspension.d.ts +0 -32
- package/dist/workflows/suspension.js +0 -38
package/dist/loop/run-agent.js
DELETED
|
@@ -1,1124 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { jsonSchema, tool as aiTool, } from "ai";
|
|
3
|
-
import { createNoopUsageRecorder } from "../billing/noop-usage-recorder.js";
|
|
4
|
-
import { stripNulCharacters } from "../input-sanitization.js";
|
|
5
|
-
import { createNoopLogger } from "../lib/console-logger.js";
|
|
6
|
-
import { deliverNotice } from "../lib/hooks.js";
|
|
7
|
-
import { passThroughSpan } from "../lib/otel-spans.js";
|
|
8
|
-
import { ResultError, unwrapOrThrow } from "../lib/result.js";
|
|
9
|
-
import { describeZodIssueShapes } from "../lib/zod-issue-shape.js";
|
|
10
|
-
import { hintForZodIssue, repairToolInput } from "../lib/zod-issues.js";
|
|
11
|
-
import { contextRecordOf, markCompactionExchange, markInterruptedMessage, summaryMarkerMessage, syntheticUserMessage, withRequestInputTokens, } from "../memory/ai-sdk-message-storage.js";
|
|
12
|
-
import { conversationView, lastRequestInputTokens } from "../memory/conversation-view.js";
|
|
13
|
-
import { answerUnansweredToolCalls } from "../memory/tool-call-integrity.js";
|
|
14
|
-
import { classifyProviderError, extractStatus } from "../providers/errors.js";
|
|
15
|
-
import { DEFAULT_PROMPT_CACHE, withPromptCacheBreakpoint, withSystemPromptBreakpoint } from "../providers/prompt-cache.js";
|
|
16
|
-
import { COMPACTION_MAX_REQUESTS } from "./compaction.js";
|
|
17
|
-
import { continueAgent } from "./continue-agent.js";
|
|
18
|
-
import { resultStep } from "./run-step.js";
|
|
19
|
-
import { AskRejectedError, UnavailableAsk } from "../tools/approval/contract.js";
|
|
20
|
-
import { isToolError, readToolCallRecord, readToolResultImages, } from "../tools/define-tool.js";
|
|
21
|
-
import { labelToolFailure, labelToolValidationFailure, recordToolException, traceAgentRun, traceToolCall } from "./genai-spans.js";
|
|
22
|
-
import { addChatUsage, countChatTokens, hasReportedUsage, recordAgentRun } from "./metrics.js";
|
|
23
|
-
import { computeDetail, computeResultDetail } from "./tool-detail.js";
|
|
24
|
-
import { refusalsFor } from "./tool-mask.js";
|
|
25
|
-
import { toolOutcomeForOutputType } from "./tool-outcome.js";
|
|
26
|
-
import { excerptOf, keptTextOf, READ_TOOL_OUTPUT_TOOL_ID, resultTextOf, TOOL_RESULT_CAP, toolOutputRef, withExcerpt, } from "./tool-output.js";
|
|
27
|
-
const TRUNCATED_PROSE_STEER = "Your previous reply was cut off at the output-token limit; continue concisely, or finish via your terminal tool.";
|
|
28
|
-
const TRUNCATED_TOOL_USE_ERROR = "Your previous tool call was cut off at the output-token limit and was not executed. Retry with a smaller payload, writing in incremental pieces.";
|
|
29
|
-
export const DEFAULT_STEP_NAME_FORMATTER = {
|
|
30
|
-
llm: (i) => `llm-${i}`,
|
|
31
|
-
tool: (name, id) => `tool-${name}-${id}`,
|
|
32
|
-
};
|
|
33
|
-
/** A run that receives no turn accumulator is the turn's root. See `RunAgentOptions.turnUsage`. */
|
|
34
|
-
export const isTurnRoot = (opts) => opts.turnUsage === undefined;
|
|
35
|
-
export function runAgent(agent, initial, session, opts) {
|
|
36
|
-
return traceAgentRun(agent.id, session, isTurnRoot(opts), () => runAgentLoop(agent, initial, session, opts));
|
|
37
|
-
}
|
|
38
|
-
/** The harness request of the wrap-up at the iteration cap. */
|
|
39
|
-
export const WRAP_UP_REQUEST = "You reached the limit of tool rounds for this run, thus no tool can run now. Answer in text with the results that you have.";
|
|
40
|
-
/** The cap of wrap-up requests: one answer, and one more request after a refused call. */
|
|
41
|
-
export const WRAP_UP_MAX_REQUESTS = 2;
|
|
42
|
-
async function runAgentLoop(agent, initial, session, opts) {
|
|
43
|
-
const formatStepName = opts.formatStepName ?? DEFAULT_STEP_NAME_FORMATTER;
|
|
44
|
-
const loop = openLoop(agent, initial, session, opts, agent.id);
|
|
45
|
-
if (opts.turnInput !== undefined) {
|
|
46
|
-
const ended = await loop.startTurn(opts.turnInput);
|
|
47
|
-
if (ended !== undefined)
|
|
48
|
-
return ended;
|
|
49
|
-
}
|
|
50
|
-
const task = await loop.runSegment({ mask: opts.toolMask, maxRequests: agent.maxIterations, stepNames: formatStepName, firstIndex: 0, checksBudget: true });
|
|
51
|
-
if (task !== "capped")
|
|
52
|
-
return task;
|
|
53
|
-
// The wrap-up refuses calls through the mask, not `toolChoice` (see its
|
|
54
|
-
// CAUTION), and keeps step name `llm(maxIterations + k)` so replay finds the
|
|
55
|
-
// reply an earlier, single-call wrap-up stored under the same name.
|
|
56
|
-
const wrapUp = await loop.runSegment({
|
|
57
|
-
mask: "none",
|
|
58
|
-
maxRequests: WRAP_UP_MAX_REQUESTS,
|
|
59
|
-
stepNames: { llm: (k) => formatStepName.llm(agent.maxIterations + k), tool: formatStepName.tool },
|
|
60
|
-
firstIndex: agent.maxIterations,
|
|
61
|
-
requestText: WRAP_UP_REQUEST,
|
|
62
|
-
closesCappedRun: true,
|
|
63
|
-
});
|
|
64
|
-
return wrapUp === "capped" ? loop.endCapped() : wrapUp;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Open the loop over one conversation. `metricAgentId` is the id that usage
|
|
68
|
-
* and run metrics count under, which is not always `agent.id`.
|
|
69
|
-
*/
|
|
70
|
-
export function openLoop(agent, initial, session, opts, metricAgentId) {
|
|
71
|
-
const { provider, signal, emit, runStep } = opts;
|
|
72
|
-
// The step of a model call or a tool call writes no span. The `chat` span and
|
|
73
|
-
// the `execute_tool` span under it describe the call already, and they attach
|
|
74
|
-
// to the `invoke_agent` span of this run.
|
|
75
|
-
const callStep = (name, fn) => passThroughSpan(name, () => runStep(name, fn));
|
|
76
|
-
const configuredFatalLoopError = opts.isFatalLoopError ?? (() => false);
|
|
77
|
-
// AbortError is control flow, not a tool failure. Always compose it with the
|
|
78
|
-
// host's fatal predicate so request cancellation cannot be converted into a
|
|
79
|
-
// model-visible error result and retried by the loop.
|
|
80
|
-
const isFatalLoopError = (err) => ((err instanceof Error || err instanceof DOMException) && err.name === "AbortError") || configuredFatalLoopError(err);
|
|
81
|
-
if (agent.tools.length > 0 && !provider.capabilities.toolCalling) {
|
|
82
|
-
throw new Error(`Provider/model cannot run tool-required agent "${agent.id}"`);
|
|
83
|
-
}
|
|
84
|
-
const messages = [...initial];
|
|
85
|
-
// The loop never changes a message before `givenCount`, because the sink can hold it already.
|
|
86
|
-
let givenCount = initial.length;
|
|
87
|
-
// Stays true when the sink rejects, thus the throw path does not call the sink again.
|
|
88
|
-
let sinkPending = false;
|
|
89
|
-
const giveRound = async () => {
|
|
90
|
-
if (opts.onRound === undefined || messages.length === givenCount)
|
|
91
|
-
return;
|
|
92
|
-
sinkPending = true;
|
|
93
|
-
await opts.onRound({ messages: messages.slice(givenCount) });
|
|
94
|
-
sinkPending = false;
|
|
95
|
-
givenCount = messages.length;
|
|
96
|
-
};
|
|
97
|
-
const source = {
|
|
98
|
-
agentId: session.provenance.agentId ?? agent.id,
|
|
99
|
-
callPath: session.provenance.callPath,
|
|
100
|
-
...(opts.invocationId === undefined ? {} : { invocationId: opts.invocationId }),
|
|
101
|
-
};
|
|
102
|
-
const policy = opts.compaction;
|
|
103
|
-
const viewOptions = { keepFirstTurn: policy?.keepFirstTurn === true };
|
|
104
|
-
const viewOf = () => (policy === undefined ? messages : conversationView(messages, viewOptions).messages);
|
|
105
|
-
// The steer of a truncated prose reply and the reply after it complete one reply, thus no exchange goes between them.
|
|
106
|
-
let continuesTruncation = false;
|
|
107
|
-
let compactionStopped = false;
|
|
108
|
-
let compactions = 0;
|
|
109
|
-
// The first request after a summary shows if the summary brought the conversation under the budget.
|
|
110
|
-
let summarizedLast = false;
|
|
111
|
-
// Bound from the same `source` the emitted events carry: one derivation feeding
|
|
112
|
-
// both sinks, so a record and an event cannot disagree about who produced them.
|
|
113
|
-
// `callPath` rides as an array rather than a joined string — the queryable form;
|
|
114
|
-
// rendering `parent > child` is the sink's choice, not a published package's.
|
|
115
|
-
const log = (opts.logger ?? createNoopLogger()).named("loop").with({ agentId: source.agentId, callPath: source.callPath });
|
|
116
|
-
// The picture of a tool result rides where the wire renders it. Absent flags
|
|
117
|
-
// mean "cannot carry", thus the loop degrades to text and records the drop.
|
|
118
|
-
// The collector belongs to this run, thus two loops never share one.
|
|
119
|
-
const encoding = { placement: imagePlacementFor(provider.capabilities), deferredImages: [], log };
|
|
120
|
-
const toolsById = new Map(agent.tools.map((t) => [t.id, t]));
|
|
121
|
-
// Only the read tool of the agent can read a kept text, thus an agent without it keeps nothing.
|
|
122
|
-
const cut = {
|
|
123
|
-
store: toolsById.has(READ_TOOL_OUTPUT_TOOL_ID) ? opts.toolOutputStore : undefined,
|
|
124
|
-
session,
|
|
125
|
-
invocationId: opts.invocationId,
|
|
126
|
-
log,
|
|
127
|
-
};
|
|
128
|
-
const toolDefs = Object.fromEntries(agent.tools.map((t) => [
|
|
129
|
-
t.id,
|
|
130
|
-
aiTool({
|
|
131
|
-
description: t.description,
|
|
132
|
-
inputSchema: jsonSchema(t.jsonSchema),
|
|
133
|
-
}),
|
|
134
|
-
]));
|
|
135
|
-
// Approval resolves once to the caller's seam, or the deny-by-default one
|
|
136
|
-
// when it wires none: a tool that pauses for a user decision where no
|
|
137
|
-
// interactive surface is present is denied rather than left waiting on an
|
|
138
|
-
// answer that cannot come.
|
|
139
|
-
const unavailableAsk = new UnavailableAsk();
|
|
140
|
-
const ask = opts.ask ?? ((request) => unavailableAsk.ask(request));
|
|
141
|
-
// A run handed no accumulator is the turn's root: it creates the one every
|
|
142
|
-
// descendant loop folds into, and it is the only loop entitled to report a
|
|
143
|
-
// turn total. Descendants receive the root's object and mutate it in place.
|
|
144
|
-
const turnUsage = opts.turnUsage ?? {};
|
|
145
|
-
const turnRoot = isTurnRoot(opts);
|
|
146
|
-
const toolCtx = (tu) => ({
|
|
147
|
-
invocationId: tu.toolCallId,
|
|
148
|
-
session,
|
|
149
|
-
signal,
|
|
150
|
-
emit,
|
|
151
|
-
ask,
|
|
152
|
-
turnUsage,
|
|
153
|
-
});
|
|
154
|
-
let iterations = 0;
|
|
155
|
-
let truncationRecoveries = 0;
|
|
156
|
-
let stoppedEarly = false;
|
|
157
|
-
// Counted rather than listed: the finish record stays one bounded line for a run of
|
|
158
|
-
// any length, and "50 iterations, 49 tool calls, 47 of them errored" already separates
|
|
159
|
-
// a productive long run from a loop stuck retrying one failing call. The per-iteration
|
|
160
|
-
// `debug` records name the tools when that distinction is not enough.
|
|
161
|
-
let toolCallCount = 0;
|
|
162
|
-
let toolErrorCount = 0;
|
|
163
|
-
// Resolved once, not per iteration: an identical policy across every call is
|
|
164
|
-
// itself part of the cache contract — the request prefix has to be
|
|
165
|
-
// byte-identical to be read back. The message breakpoint is re-derived per
|
|
166
|
-
// call because it rides the transcript. The system prompt depends only on
|
|
167
|
-
// the agent type, so it is computed once.
|
|
168
|
-
const promptCache = opts.promptCache ?? DEFAULT_PROMPT_CACHE;
|
|
169
|
-
const system = withSystemPromptBreakpoint(agent.systemPrompt, promptCache);
|
|
170
|
-
const reasoningField = opts.reasoning === undefined ? {} : { reasoning: opts.reasoning };
|
|
171
|
-
const usage = {};
|
|
172
|
-
// Exactly one record per completed run — never one per iteration. That bound is
|
|
173
|
-
// what keeps the default level affordable for an agent that runs long; the
|
|
174
|
-
// per-iteration detail lives at `debug`, where paying per iteration is the point.
|
|
175
|
-
const logFinish = (level, reason, cappedOut) => {
|
|
176
|
-
log[level]("run finished", { iterations, reason, cappedOut, truncationRecoveries, toolCalls: toolCallCount, toolErrors: toolErrorCount, usage });
|
|
177
|
-
};
|
|
178
|
-
/**
|
|
179
|
-
* Settle the transcript on the way out — called at every return. A reply can
|
|
180
|
-
* carry a complete tool call beside ANY finish reason, because the call
|
|
181
|
-
* streams before the stop reason arrives, and only the dispatching branches
|
|
182
|
-
* (`tool-calls`, a call-carrying `stop`, and `length`) run one. A call this
|
|
183
|
-
* run never dispatched gets a not-run result instead of being executed or
|
|
184
|
-
* dropped: every tool call needs a result, or the provider refuses the
|
|
185
|
-
* transcript on the next turn. The fix only appends; it never edits or
|
|
186
|
-
* removes a message. Scoped past
|
|
187
|
-
* `initial`: the caller's prefix is not this run's to repair.
|
|
188
|
-
*/
|
|
189
|
-
const settleTranscript = () => {
|
|
190
|
-
const answeredCalls = answerUnansweredToolCalls(messages, initial.length);
|
|
191
|
-
if (answeredCalls.length === 0)
|
|
192
|
-
return;
|
|
193
|
-
log.warn("unanswered tool calls answered at run exit", {
|
|
194
|
-
toolCallIds: answeredCalls.map((d) => d.toolCallId),
|
|
195
|
-
tools: answeredCalls.map((d) => d.toolName),
|
|
196
|
-
});
|
|
197
|
-
};
|
|
198
|
-
const usageRecorder = opts.usageRecorder ?? createNoopUsageRecorder();
|
|
199
|
-
const accounting = {
|
|
200
|
-
session,
|
|
201
|
-
agentId: source.agentId,
|
|
202
|
-
callPath: source.callPath,
|
|
203
|
-
...(opts.invocationId === undefined ? {} : { invocationId: opts.invocationId }),
|
|
204
|
-
usageRecorder,
|
|
205
|
-
logger: log,
|
|
206
|
-
rollups: [usage, turnUsage],
|
|
207
|
-
};
|
|
208
|
-
/** The rollups this loop stamps on its finish — each absent when nothing reported. */
|
|
209
|
-
const finishUsage = () => ({
|
|
210
|
-
...(hasReportedUsage(usage) ? { usage: { ...usage } } : {}),
|
|
211
|
-
...(turnRoot && hasReportedUsage(turnUsage) ? { turnUsage: { ...turnUsage } } : {}),
|
|
212
|
-
});
|
|
213
|
-
/**
|
|
214
|
-
* One model request over the view of the transcript. Token counters grow inside
|
|
215
|
-
* the step body, so a replayed step (a stored reply) does not double-count.
|
|
216
|
-
*/
|
|
217
|
-
const callModel = async (stepName, endsOnRequestRefusal) => {
|
|
218
|
-
const request = {
|
|
219
|
-
system,
|
|
220
|
-
messages: withPromptCacheBreakpoint(viewOf(), promptCache),
|
|
221
|
-
tools: toolDefs,
|
|
222
|
-
...(opts.toolChoice !== undefined ? { toolChoice: opts.toolChoice } : {}),
|
|
223
|
-
...reasoningField,
|
|
224
|
-
};
|
|
225
|
-
const chat = () => provider.chat(request, session, signal).map(countChatTokens(metricAgentId));
|
|
226
|
-
if (!endsOnRequestRefusal) {
|
|
227
|
-
const reply = await resultStep(callStep)(stepName, chat);
|
|
228
|
-
accountForChatCall(reply, { ...accounting, stepName });
|
|
229
|
-
return withRequestAnchor(reply);
|
|
230
|
-
}
|
|
231
|
-
// The step gives the `Result` as its value, thus a refusal ends the segment and no step throws for it.
|
|
232
|
-
const result = await callStep(stepName, async () => await chat());
|
|
233
|
-
const refusal = result.isErr() ? requestRefusalOf(result.error) : undefined;
|
|
234
|
-
if (refusal !== undefined)
|
|
235
|
-
return { refusal };
|
|
236
|
-
const reply = unwrapOrThrow(result);
|
|
237
|
-
accountForChatCall(reply, { ...accounting, stepName });
|
|
238
|
-
return withRequestAnchor(reply);
|
|
239
|
-
};
|
|
240
|
-
// The user said no. A subsequent model call would only let the agent argue
|
|
241
|
-
// with the decision, or spend a call acknowledging it; the denial tool result
|
|
242
|
-
// is itself what the surface renders, so the turn ends the moment a denial
|
|
243
|
-
// lands in a dispatch round — after the concurrent siblings in that same round
|
|
244
|
-
// have completed and been appended. Mirrors the clean-stop terminal path.
|
|
245
|
-
const stopOnDenial = async (index) => {
|
|
246
|
-
settleTranscript();
|
|
247
|
-
await emit({ type: "iteration", source, index, final: true });
|
|
248
|
-
recordAgentRun({ agentId: metricAgentId, iterations, cappedOut: false });
|
|
249
|
-
logFinish("warn", "denied", false);
|
|
250
|
-
await giveRound();
|
|
251
|
-
return { messages, finish: { reason: "denied", cappedOut: false, truncationRecoveries, ...finishUsage() } };
|
|
252
|
-
};
|
|
253
|
-
/**
|
|
254
|
-
* The call details for one dispatch round, positionally aligned with `calls`.
|
|
255
|
-
*
|
|
256
|
-
* Computed once per round and carried onto both the `tool-started` and the
|
|
257
|
-
* `tool-finished` event, so the pair a host renders as one chip cannot show
|
|
258
|
-
* two different descriptions of the same call. A tool that describes its own
|
|
259
|
-
* result replaces the second half of that pair with the outcome it produced.
|
|
260
|
-
*/
|
|
261
|
-
const roundDetails = (calls) => calls.map((tu) => {
|
|
262
|
-
const tool = toolsById.get(tu.toolName);
|
|
263
|
-
return tool === undefined ? undefined : computeDetail(tool, tu.input, log);
|
|
264
|
-
});
|
|
265
|
-
/**
|
|
266
|
-
* Fold one dispatch round into the run's counters and the event sink, returning the
|
|
267
|
-
* tools whose results the model will read as errors.
|
|
268
|
-
*
|
|
269
|
-
* Shared by both dispatch paths so a truncated round and a normal one cannot drift
|
|
270
|
-
* in what they count. An error tool result is otherwise invisible to every sink —
|
|
271
|
-
* it is not a thrown failure, so nothing reports it and the loop simply feeds it
|
|
272
|
-
* back — and a run that ends badly is usually a run whose tool calls were failing.
|
|
273
|
-
*
|
|
274
|
-
* `details`, `resultDetails` and `durations` are positionally aligned with `calls`.
|
|
275
|
-
* `dispatchTools` measures a duration around the call itself, because this sink emits
|
|
276
|
-
* every finish event only after the whole round settles.
|
|
277
|
-
*/
|
|
278
|
-
const settleRound = async (calls, results, details, resultDetails, durations) => {
|
|
279
|
-
const errored = [];
|
|
280
|
-
for (let idx = 0; idx < calls.length; idx++) {
|
|
281
|
-
const tu = calls[idx];
|
|
282
|
-
const outcome = outcomeOf(results[idx]);
|
|
283
|
-
toolCallCount++;
|
|
284
|
-
// A denial counts here with the errors: the model reads it as a result
|
|
285
|
-
// it did not get to act on, which is what these counters describe. The
|
|
286
|
-
// event keeps the two apart for the user-facing surface.
|
|
287
|
-
if (outcome !== "ok") {
|
|
288
|
-
toolErrorCount++;
|
|
289
|
-
errored.push(tu.toolName);
|
|
290
|
-
}
|
|
291
|
-
await emit({
|
|
292
|
-
type: "tool-finished",
|
|
293
|
-
source,
|
|
294
|
-
toolUseId: tu.toolCallId,
|
|
295
|
-
name: tu.toolName,
|
|
296
|
-
outcome,
|
|
297
|
-
// The result detail wins where there is one, and the call detail
|
|
298
|
-
// stands otherwise. Only the ok branch of a dispatch computes a
|
|
299
|
-
// result detail, thus an error and a denial reach here with none
|
|
300
|
-
// and keep the line the `tool-started` carried.
|
|
301
|
-
...detailField(resultDetails[idx] ?? details[idx]),
|
|
302
|
-
...durationField(durations[idx]),
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
return errored;
|
|
306
|
-
};
|
|
307
|
-
// The calls of each tool that passed the mask and the budget in this run.
|
|
308
|
-
const used = new Map();
|
|
309
|
-
/**
|
|
310
|
-
* Dispatch one round under the run's mask and budget. A refused call still
|
|
311
|
-
* gets its own step slot, with an error result instead of running.
|
|
312
|
-
*/
|
|
313
|
-
const dispatchRound = (calls, mask, names) => {
|
|
314
|
-
const refusals = refusalsFor(calls, mask, opts.toolBudget, used);
|
|
315
|
-
return dispatchTools(calls, refusals, toolsById, toolCtx, isFatalLoopError, callStep, names.tool, encoding, cut);
|
|
316
|
-
};
|
|
317
|
-
const stopOnResolved = async (index) => {
|
|
318
|
-
settleTranscript();
|
|
319
|
-
await emit({ type: "iteration", source, index, final: true });
|
|
320
|
-
recordAgentRun({ agentId: metricAgentId, iterations, cappedOut: false });
|
|
321
|
-
await giveRound();
|
|
322
|
-
return { messages, finish: { reason: "stop", cappedOut: false, truncationRecoveries } };
|
|
323
|
-
};
|
|
324
|
-
const endOnRefusal = async (refusal) => {
|
|
325
|
-
settleTranscript();
|
|
326
|
-
recordAgentRun({ agentId: metricAgentId, iterations, cappedOut: false });
|
|
327
|
-
logFinish("warn", "error", false);
|
|
328
|
-
await giveRound();
|
|
329
|
-
return { messages, finish: { reason: "error", cappedOut: false, truncationRecoveries, ...finishUsage() }, refusal };
|
|
330
|
-
};
|
|
331
|
-
/** The result of the run when an abort ends the exchange, or `undefined` when the run continues. */
|
|
332
|
-
const compact = async (active, tokensBefore, index, trigger) => {
|
|
333
|
-
const id = randomUUID();
|
|
334
|
-
const stepNamespace = `compaction-${compactions++}`;
|
|
335
|
-
const startedAt = performance.now();
|
|
336
|
-
const emitPart = (data) => emit({ type: "data-compaction", source, data: { ...data, trigger } });
|
|
337
|
-
await emitPart({ id, status: "running", tokensBefore });
|
|
338
|
-
const { onRound: _onRound, compaction: _compaction, turnInput: _turnInput, ...conversationOptions } = opts;
|
|
339
|
-
let settled = false;
|
|
340
|
-
try {
|
|
341
|
-
const exchange = await continueAgent(agent, viewOf(), {
|
|
342
|
-
text: active.request,
|
|
343
|
-
mask: active.mask,
|
|
344
|
-
maxRequests: COMPACTION_MAX_REQUESTS,
|
|
345
|
-
stepNamespace,
|
|
346
|
-
accountingAgentId: `${agent.id}-compaction`,
|
|
347
|
-
endOnRequestRefusal: true,
|
|
348
|
-
}, session, { ...conversationOptions, provider: active.provider, turnUsage });
|
|
349
|
-
messages.push(...exchange.messages.map((message) => markCompactionExchange(message, id)));
|
|
350
|
-
await giveRound();
|
|
351
|
-
if (exchange.finish.reason === "aborted") {
|
|
352
|
-
settled = true;
|
|
353
|
-
await emitPart({ id, status: "failed", tokensBefore, durationMs: Math.round(performance.now() - startedAt) });
|
|
354
|
-
settleTranscript();
|
|
355
|
-
await emit({ type: "iteration", source, index, final: true });
|
|
356
|
-
recordAgentRun({ agentId: metricAgentId, iterations, cappedOut: false });
|
|
357
|
-
logFinish("info", "aborted", false);
|
|
358
|
-
await giveRound();
|
|
359
|
-
return { messages, finish: { reason: "aborted", cappedOut: false, truncationRecoveries, ...finishUsage() } };
|
|
360
|
-
}
|
|
361
|
-
const summary = summaryOf(exchange);
|
|
362
|
-
if (summary === undefined) {
|
|
363
|
-
settled = true;
|
|
364
|
-
const durationMs = Math.round(performance.now() - startedAt);
|
|
365
|
-
await emitPart({ id, status: "failed", tokensBefore, durationMs });
|
|
366
|
-
const refusal = exchange.refusal === undefined ? {} : { status: exchange.refusal.status, providerError: exchange.refusal.message };
|
|
367
|
-
log.error("compaction gave no summary, thus the run compacts no more", { compactionId: id, trigger, tokensBefore, durationMs, ...refusal });
|
|
368
|
-
compactionStopped = true;
|
|
369
|
-
return undefined;
|
|
370
|
-
}
|
|
371
|
-
const markerOf = (durationMs) => summaryMarkerMessage(summary, { kind: "summary", id, tokensBefore, durationMs, trigger });
|
|
372
|
-
// The figures of a marker do not change the view, thus a draft marker gives the view of the real one.
|
|
373
|
-
const records = await active.recordsAfter(conversationView([...messages, markerOf(0)], viewOptions).messages);
|
|
374
|
-
const durationMs = Math.round(performance.now() - startedAt);
|
|
375
|
-
messages.push(markerOf(durationMs), ...records);
|
|
376
|
-
settled = true;
|
|
377
|
-
// The `done` part goes out before the marker round: a chat turn gives the rounds after a marker a new
|
|
378
|
-
// assistant message, and the `running` and `done` parts of one compaction must stream into the same message.
|
|
379
|
-
await emitPart({ id, status: "done", tokensBefore, durationMs });
|
|
380
|
-
await giveRound();
|
|
381
|
-
log.info("conversation compacted", { compactionId: id, trigger, tokensBefore, durationMs });
|
|
382
|
-
summarizedLast = true;
|
|
383
|
-
return undefined;
|
|
384
|
-
}
|
|
385
|
-
finally {
|
|
386
|
-
if (!settled)
|
|
387
|
-
await emitPart({ id, status: "failed", tokensBefore, durationMs: Math.round(performance.now() - startedAt) });
|
|
388
|
-
}
|
|
389
|
-
};
|
|
390
|
-
const startTurn = async (input) => {
|
|
391
|
-
const budget = policy?.turnStartBudget;
|
|
392
|
-
const tokens = lastRequestInputTokens(messages);
|
|
393
|
-
if (policy === undefined || budget === undefined || tokens === undefined || tokens <= budget) {
|
|
394
|
-
messages.push(...input);
|
|
395
|
-
return undefined;
|
|
396
|
-
}
|
|
397
|
-
const ended = await compact(policy, tokens, 0, "turn-start");
|
|
398
|
-
if (ended !== undefined)
|
|
399
|
-
return ended;
|
|
400
|
-
// The records after a summary give the current context, thus a record of the input would only repeat one.
|
|
401
|
-
messages.push(...(summarizedLast ? input.filter((message) => contextRecordOf(message) === undefined) : input));
|
|
402
|
-
return undefined;
|
|
403
|
-
};
|
|
404
|
-
const compactOverBudget = async (index) => {
|
|
405
|
-
if (policy === undefined || continuesTruncation || compactionStopped)
|
|
406
|
-
return undefined;
|
|
407
|
-
const tokens = lastRequestInputTokens(messages);
|
|
408
|
-
if (tokens === undefined)
|
|
409
|
-
return undefined;
|
|
410
|
-
const afterSummary = summarizedLast;
|
|
411
|
-
summarizedLast = false;
|
|
412
|
-
if (tokens <= policy.budget)
|
|
413
|
-
return undefined;
|
|
414
|
-
if (afterSummary) {
|
|
415
|
-
log.warn("the first request after the summary still exceeds the budget, thus the run compacts no more", { tokens, budget: policy.budget });
|
|
416
|
-
compactionStopped = true;
|
|
417
|
-
return undefined;
|
|
418
|
-
}
|
|
419
|
-
return compact(policy, tokens, index, "mid-turn");
|
|
420
|
-
};
|
|
421
|
-
const runSegment = async (segment) => {
|
|
422
|
-
const names = segment.stepNames;
|
|
423
|
-
if (segment.requestText !== undefined)
|
|
424
|
-
messages.push(syntheticUserMessage(segment.requestText));
|
|
425
|
-
for (let k = 0; k < segment.maxRequests; k++) {
|
|
426
|
-
// The early cap ends a segment before the cap as if the cap were reached, thus a run takes its
|
|
427
|
-
// tool-less wrap-up. The segment that closes a capped run is that wrap-up, and it runs to its end.
|
|
428
|
-
if (segment.closesCappedRun !== true && opts.stopWhen?.()) {
|
|
429
|
-
stoppedEarly = true;
|
|
430
|
-
log.warn("run stopped early by the host policy, taking the wrap-up path", { iterations });
|
|
431
|
-
return "capped";
|
|
432
|
-
}
|
|
433
|
-
const index = segment.firstIndex + k;
|
|
434
|
-
// The last request of a segment that closes a capped run ends the run,
|
|
435
|
-
// whatever its reply, thus its `iteration` event is the final one.
|
|
436
|
-
const endsRun = segment.closesCappedRun === true && k === segment.maxRequests - 1;
|
|
437
|
-
await giveRound();
|
|
438
|
-
if (segment.checksBudget === true) {
|
|
439
|
-
const ended = await compactOverBudget(index);
|
|
440
|
-
if (ended !== undefined)
|
|
441
|
-
return ended;
|
|
442
|
-
}
|
|
443
|
-
if (segment.closesCappedRun !== true)
|
|
444
|
-
iterations++;
|
|
445
|
-
const roundStart = messages.length;
|
|
446
|
-
const called = await callModel(names.llm(k), segment.endsOnRequestRefusal === true);
|
|
447
|
-
continuesTruncation = false;
|
|
448
|
-
if ("refusal" in called)
|
|
449
|
-
return endOnRefusal(called.refusal);
|
|
450
|
-
const reply = called;
|
|
451
|
-
if (reply.finishReason === "aborted") {
|
|
452
|
-
// An interrupted turn keeps whatever the model produced before the cut, but
|
|
453
|
-
// never an empty shell: a partial with no content adds no message. Only the
|
|
454
|
-
// partial carries the marker, because the sink can hold each earlier message.
|
|
455
|
-
// "aborted" is not "tool-calls", so this falls into the terminal return below.
|
|
456
|
-
if (assistantHasContent(reply.message))
|
|
457
|
-
messages.push(reply.message);
|
|
458
|
-
markLastLoopAssistant(messages, roundStart);
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
messages.push(reply.message);
|
|
462
|
-
}
|
|
463
|
-
const toolCalls = toolCallParts(reply.message);
|
|
464
|
-
if (reply.finishReason === "length") {
|
|
465
|
-
truncationRecoveries++;
|
|
466
|
-
await emit({ type: "iteration", source, index, final: endsRun });
|
|
467
|
-
// `tools` is what the model asked for; the trailing call was cut off at the
|
|
468
|
-
// output limit and is never dispatched, which the distinct message records.
|
|
469
|
-
log.debug("iteration truncated at output limit", { iteration: index, tools: toolCalls.map((t) => t.toolName), truncationRecoveries });
|
|
470
|
-
if (toolCalls.length === 0) {
|
|
471
|
-
// Stamped synthetic, not left as a bare `user` message: the wire format needs a user turn
|
|
472
|
-
// after a truncated assistant message, but this one is the loop's own nudge, and thread
|
|
473
|
-
// storage treats a genuine `user` message as the start of a conversation turn. Unmarked, it
|
|
474
|
-
// would split this turn in two everywhere that boundary is read.
|
|
475
|
-
messages.push(syntheticUserMessage(TRUNCATED_PROSE_STEER));
|
|
476
|
-
continuesTruncation = true;
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
const trailing = toolCalls[toolCalls.length - 1];
|
|
480
|
-
const earlier = toolCalls.slice(0, -1);
|
|
481
|
-
const earlierDetails = roundDetails(earlier);
|
|
482
|
-
for (const [idx, tu] of earlier.entries()) {
|
|
483
|
-
await emit({
|
|
484
|
-
type: "tool-started",
|
|
485
|
-
source,
|
|
486
|
-
toolUseId: tu.toolCallId,
|
|
487
|
-
name: tu.toolName,
|
|
488
|
-
input: tu.input,
|
|
489
|
-
...detailField(earlierDetails[idx]),
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
const { results, durations, resultDetails } = await dispatchRound(earlier, segment.mask, names);
|
|
493
|
-
const errored = await settleRound(earlier, results, earlierDetails, resultDetails, durations);
|
|
494
|
-
results.push(errorResult(trailing, TRUNCATED_TOOL_USE_ERROR));
|
|
495
|
-
// The trailing call was never dispatched, but it reaches the model as an error
|
|
496
|
-
// result like any other — so it counts, or `toolErrors` reports a cleaner run
|
|
497
|
-
// than the model actually read. It gets no `tool-finished` event: no
|
|
498
|
-
// `tool-started` was emitted for it either, and the pair must stay balanced.
|
|
499
|
-
toolCallCount++;
|
|
500
|
-
toolErrorCount++;
|
|
501
|
-
errored.push(trailing.toolName);
|
|
502
|
-
log.debug("tool results returned errors", { iteration: index, tools: errored });
|
|
503
|
-
messages.push({ role: "tool", content: results });
|
|
504
|
-
appendDeferredImages(messages, results, encoding.deferredImages);
|
|
505
|
-
if (hasDenial(results))
|
|
506
|
-
return stopOnDenial(index);
|
|
507
|
-
if (opts.resolved?.())
|
|
508
|
-
return stopOnResolved(index);
|
|
509
|
-
continue;
|
|
510
|
-
}
|
|
511
|
-
// A local model behind an OpenAI-compatible server routinely labels a
|
|
512
|
-
// tool-calling reply `stop`. The calls are complete and the model waits
|
|
513
|
-
// on their results, thus the mislabeled round dispatches like a
|
|
514
|
-
// `tool-calls` one. Every other terminal answers its calls with the
|
|
515
|
-
// not-run result: a filtered or aborted reply must not act.
|
|
516
|
-
const dispatchesRound = reply.finishReason === "tool-calls" || (reply.finishReason === "stop" && toolCalls.length > 0);
|
|
517
|
-
if (!dispatchesRound) {
|
|
518
|
-
settleTranscript();
|
|
519
|
-
await emit({ type: "iteration", source, index, final: true });
|
|
520
|
-
if (segment.closesCappedRun === true) {
|
|
521
|
-
// The cap was already reached; `reason` still distinguishes an
|
|
522
|
-
// abort (user cut the turn) from a plain `max_iterations` cap-out.
|
|
523
|
-
const reason = reply.finishReason === "aborted" ? "aborted" : "max_iterations";
|
|
524
|
-
recordAgentRun({ agentId: metricAgentId, iterations, cappedOut: true });
|
|
525
|
-
if (stoppedEarly)
|
|
526
|
-
log.warn("run finished on the early cap", { iterations });
|
|
527
|
-
logFinish("warn", reason, true);
|
|
528
|
-
await giveRound();
|
|
529
|
-
return { messages, finish: { reason, cappedOut: true, truncationRecoveries, ...finishUsage() } };
|
|
530
|
-
}
|
|
531
|
-
recordAgentRun({ agentId: metricAgentId, iterations, cappedOut: false });
|
|
532
|
-
logFinish("info", reply.finishReason, false);
|
|
533
|
-
await giveRound();
|
|
534
|
-
return {
|
|
535
|
-
messages,
|
|
536
|
-
finish: {
|
|
537
|
-
reason: reply.finishReason,
|
|
538
|
-
...(reply.rawFinishReason === undefined ? {} : { rawFinishReason: reply.rawFinishReason }),
|
|
539
|
-
cappedOut: false,
|
|
540
|
-
truncationRecoveries,
|
|
541
|
-
...finishUsage(),
|
|
542
|
-
},
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
await emit({ type: "iteration", source, index, final: endsRun });
|
|
546
|
-
log.debug("iteration", { iteration: index, tools: toolCalls.map((t) => t.toolName) });
|
|
547
|
-
const details = roundDetails(toolCalls);
|
|
548
|
-
for (const [idx, tu] of toolCalls.entries()) {
|
|
549
|
-
await emit({ type: "tool-started", source, toolUseId: tu.toolCallId, name: tu.toolName, input: tu.input, ...detailField(details[idx]) });
|
|
550
|
-
}
|
|
551
|
-
const { results, durations, resultDetails } = await dispatchRound(toolCalls, segment.mask, names);
|
|
552
|
-
const errored = await settleRound(toolCalls, results, details, resultDetails, durations);
|
|
553
|
-
if (errored.length > 0)
|
|
554
|
-
log.debug("tool results returned errors", { iteration: index, tools: errored });
|
|
555
|
-
messages.push({ role: "tool", content: results });
|
|
556
|
-
appendDeferredImages(messages, results, encoding.deferredImages);
|
|
557
|
-
if (hasDenial(results))
|
|
558
|
-
return stopOnDenial(index);
|
|
559
|
-
if (opts.resolved?.())
|
|
560
|
-
return stopOnResolved(index);
|
|
561
|
-
}
|
|
562
|
-
return "capped";
|
|
563
|
-
};
|
|
564
|
-
const endCapped = async () => {
|
|
565
|
-
settleTranscript();
|
|
566
|
-
recordAgentRun({ agentId: metricAgentId, iterations, cappedOut: true });
|
|
567
|
-
if (stoppedEarly)
|
|
568
|
-
log.warn("run finished on the early cap", { iterations });
|
|
569
|
-
logFinish("warn", "max_iterations", true);
|
|
570
|
-
await giveRound();
|
|
571
|
-
return { messages, finish: { reason: "max_iterations", cappedOut: true, truncationRecoveries, ...finishUsage() } };
|
|
572
|
-
};
|
|
573
|
-
// A throw keeps the open round, and the not-run results keep that stored round valid on the wire.
|
|
574
|
-
const giveRoundBeforeThrow = async (body) => {
|
|
575
|
-
if (opts.onRound === undefined)
|
|
576
|
-
return body();
|
|
577
|
-
try {
|
|
578
|
-
return await body();
|
|
579
|
-
}
|
|
580
|
-
catch (err) {
|
|
581
|
-
if (sinkPending)
|
|
582
|
-
throw err;
|
|
583
|
-
answerUnansweredToolCalls(messages, givenCount);
|
|
584
|
-
// A rejection of this call must not hide the error of the run.
|
|
585
|
-
await giveRound().catch(() => undefined);
|
|
586
|
-
throw err;
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
return {
|
|
590
|
-
startTurn: (input) => giveRoundBeforeThrow(() => startTurn(input)),
|
|
591
|
-
runSegment: (segment) => giveRoundBeforeThrow(() => runSegment(segment)),
|
|
592
|
-
endCapped: () => giveRoundBeforeThrow(endCapped),
|
|
593
|
-
};
|
|
594
|
-
}
|
|
595
|
-
/** The statuses of a request that the provider refuses for its content, for example a request past the context window. */
|
|
596
|
-
const REFUSAL_STATUSES = new Set([400, 413]);
|
|
597
|
-
function requestRefusalOf(error) {
|
|
598
|
-
if (error.type !== "provider")
|
|
599
|
-
return undefined;
|
|
600
|
-
const status = extractStatus(error);
|
|
601
|
-
return status !== undefined && REFUSAL_STATUSES.has(status) ? { status, message: error.message } : undefined;
|
|
602
|
-
}
|
|
603
|
-
/** The text of the last reply of an exchange, when that reply ends the exchange with the finish reason `stop`. */
|
|
604
|
-
function summaryOf(exchange) {
|
|
605
|
-
if (exchange.finish.reason !== "stop")
|
|
606
|
-
return undefined;
|
|
607
|
-
const text = finalText(exchange.messages).trim();
|
|
608
|
-
return text.length > 0 ? text : undefined;
|
|
609
|
-
}
|
|
610
|
-
/** The reply whose message holds the input tokens of its request, which the budget checks read. */
|
|
611
|
-
function withRequestAnchor(reply) {
|
|
612
|
-
const tokens = reply.usage?.inputTokens;
|
|
613
|
-
return tokens === undefined ? reply : { ...reply, message: withRequestInputTokens(reply.message, tokens) };
|
|
614
|
-
}
|
|
615
|
-
/**
|
|
616
|
-
* Called at the fold point, before any run-ending branch, so an aborted or
|
|
617
|
-
* dead run is still accounted. Token counters grow inside the call's step
|
|
618
|
-
* body, not here, because a replay re-delivers the stored reply to this
|
|
619
|
-
* function too; the record re-sends under the same idempotency key, which an
|
|
620
|
-
* upserting sink counts once.
|
|
621
|
-
*/
|
|
622
|
-
export function accountForChatCall(reply, call) {
|
|
623
|
-
for (const rollup of call.rollups)
|
|
624
|
-
addChatUsage(rollup, reply.usage);
|
|
625
|
-
const reported = reply.usage;
|
|
626
|
-
if (reported === undefined || !hasReportedUsage(reported))
|
|
627
|
-
return;
|
|
628
|
-
const { session } = call;
|
|
629
|
-
void deliverNotice(call.logger, "UsageRecorder.record", call.usageRecorder.record({
|
|
630
|
-
recordKey: recordKeyFor(session, call.invocationId, call.stepName),
|
|
631
|
-
agentId: call.agentId,
|
|
632
|
-
callPath: call.callPath,
|
|
633
|
-
scope: session.scope,
|
|
634
|
-
...(session.runFrame?.runId === undefined ? {} : { runId: session.runFrame.runId }),
|
|
635
|
-
...(session.runFrame?.stepId === undefined ? {} : { stepId: session.runFrame.stepId }),
|
|
636
|
-
...(reply.requestedModelId === undefined ? {} : { requestedModelId: reply.requestedModelId }),
|
|
637
|
-
...(reply.servedModelId === undefined ? {} : { servedModelId: reply.servedModelId }),
|
|
638
|
-
usage: reported,
|
|
639
|
-
}));
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* Delimiter joining the call path into one key segment.
|
|
643
|
-
*
|
|
644
|
-
* Deliberately not the `:` that separates the key's segments, so a call path
|
|
645
|
-
* can never be read as one: agent ids are kebab-case identifiers, run ids are
|
|
646
|
-
* UUIDs or the profiler's `data-profile` literal, step ids are path-safe plan
|
|
647
|
-
* ids (`T1S1`, `synthesis`, `profile`), tool-call ids are provider-issued
|
|
648
|
-
* opaque tokens, a profile attempt's workflow id is
|
|
649
|
-
* `dataprofile:{analysisId}:{nonce}`, and the loop's step names are `llm-{n}`
|
|
650
|
-
* / `tool-{name}-{id}` with an optional continuation namespace (`salvage:`,
|
|
651
|
-
* `file-metadata:`, `step-summary:`) — none of them can contain `>`.
|
|
652
|
-
*/
|
|
653
|
-
const CALL_PATH_DELIMITER = ">";
|
|
654
|
-
/**
|
|
655
|
-
* Idempotency key for one call's usage record, and for the kept text of one cut
|
|
656
|
-
* tool result under the tool step name of its call:
|
|
657
|
-
* `{runId}[:{stepId}]:{callPath}[:{invocationId}]:{stepName}`.
|
|
658
|
-
*
|
|
659
|
-
* Every component is a fact the same call carries again on every replay, so a
|
|
660
|
-
* re-executed body re-fires `record` with the byte-identical key and an
|
|
661
|
-
* upserting sink counts the call once. Each earns its place by a collision it
|
|
662
|
-
* alone resolves:
|
|
663
|
-
*
|
|
664
|
-
* - `stepId` — `executeAnalysis` runs one child workflow per step under a
|
|
665
|
-
* single shared `runId`, and each step's loop names its first call `llm-0`.
|
|
666
|
-
* - `callPath` — a step name is unique only within ONE loop invocation (every
|
|
667
|
-
* `runAgent` restarts its names at `llm-0`), and several loops routinely run
|
|
668
|
-
* under one frame: a step's post-step describer and its summary writer share
|
|
669
|
-
* `{runId, stepId}`, and the run-synthesis loop shares a bare `{runId}` with
|
|
670
|
-
* the sub-agents its tools run. The provenance path is what separates those
|
|
671
|
-
* agent chains.
|
|
672
|
-
* - `invocationId` — the same sub-agent dispatched twice in one round has an
|
|
673
|
-
* identical call path, so only the dispatching tool-call id tells the two
|
|
674
|
-
* child loops apart. It is replay-stable by the harness-tools contract: a
|
|
675
|
-
* redelivered call carries the id it was issued under. Each data profile
|
|
676
|
-
* runs under the same constant frame, thus the workflow id of its attempt
|
|
677
|
-
* takes this place, and a replay of the attempt keeps that id.
|
|
678
|
-
* - `stepName` — the loop's own deterministic name, reused rather than
|
|
679
|
-
* re-derived. Minting a second *naming* scheme here would create two things
|
|
680
|
-
* that must agree about what "the same call" is, and they would drift.
|
|
681
|
-
*
|
|
682
|
-
* Outside a `RunFrame` there is no replay to be safe against (the HTTP chat
|
|
683
|
-
* path runs a call exactly once), so a fresh id is enough and is what keeps two
|
|
684
|
-
* calls in one turn distinct.
|
|
685
|
-
*/
|
|
686
|
-
function recordKeyFor(session, invocationId, stepName) {
|
|
687
|
-
const frame = session.runFrame;
|
|
688
|
-
if (frame === undefined)
|
|
689
|
-
return randomUUID();
|
|
690
|
-
return [
|
|
691
|
-
frame.runId,
|
|
692
|
-
...(frame.stepId === undefined ? [] : [frame.stepId]),
|
|
693
|
-
session.provenance.callPath.join(CALL_PATH_DELIMITER),
|
|
694
|
-
...(invocationId === undefined ? [] : [invocationId]),
|
|
695
|
-
stepName,
|
|
696
|
-
].join(":");
|
|
697
|
-
}
|
|
698
|
-
function toolCallParts(message) {
|
|
699
|
-
if (typeof message.content === "string")
|
|
700
|
-
return [];
|
|
701
|
-
return message.content.filter((part) => part.type === "tool-call");
|
|
702
|
-
}
|
|
703
|
-
/** Whether an aborted partial carries any content worth persisting — an empty partial contributes no message. */
|
|
704
|
-
function assistantHasContent(message) {
|
|
705
|
-
return message.content.length > 0;
|
|
706
|
-
}
|
|
707
|
-
/**
|
|
708
|
-
* Mark the aborted partial of the last request. A message before `roundStart` stays unmarked,
|
|
709
|
-
* because the round sink can hold it already.
|
|
710
|
-
*/
|
|
711
|
-
function markLastLoopAssistant(messages, roundStart) {
|
|
712
|
-
for (let idx = messages.length - 1; idx >= roundStart; idx--) {
|
|
713
|
-
const message = messages[idx];
|
|
714
|
-
if (message.role === "assistant") {
|
|
715
|
-
messages[idx] = markInterruptedMessage(message);
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
/**
|
|
721
|
-
* Where a tool picture goes for this wire. The tool result wins, because it is
|
|
722
|
-
* the native place and the correlation to the tool call stays implicit. The user
|
|
723
|
-
* message is the fallback. When the wire renders neither, the loop drops the
|
|
724
|
-
* picture.
|
|
725
|
-
*/
|
|
726
|
-
function imagePlacementFor(capabilities) {
|
|
727
|
-
if (capabilities.imageToolResults === true)
|
|
728
|
-
return "tool-result";
|
|
729
|
-
if (capabilities.imageUserMessages === true)
|
|
730
|
-
return "user-message";
|
|
731
|
-
return "drop";
|
|
732
|
-
}
|
|
733
|
-
/**
|
|
734
|
-
* The text part that names one deferred picture. The wire holds no structural
|
|
735
|
-
* link between a user message and a tool call, thus the line names the call. A
|
|
736
|
-
* result with several pictures numbers each one, thus the model reads which
|
|
737
|
-
* slice it looks at.
|
|
738
|
-
*/
|
|
739
|
-
function deferredImageLabel(deferred, index) {
|
|
740
|
-
const call = `the tool result ${deferred.toolCallId} of ${deferred.toolName}`;
|
|
741
|
-
return deferred.images.length === 1 ? `The picture of ${call}.` : `The picture ${index + 1} of ${deferred.images.length} of ${call}.`;
|
|
742
|
-
}
|
|
743
|
-
/**
|
|
744
|
-
* Append one user message that carries each deferred picture of the round, then
|
|
745
|
-
* empty the collector.
|
|
746
|
-
*
|
|
747
|
-
* The tool message must come directly after the assistant message with the
|
|
748
|
-
* tool calls. Thus a picture rides a separate message after the whole tool
|
|
749
|
-
* message, and one message batches the round. The parts obey the order of
|
|
750
|
-
* `results`, which is the order of the tool calls, and the pictures of one
|
|
751
|
-
* result keep the order that the tool gave. Each deferred picture has a result
|
|
752
|
-
* of this round, because the collector fills during the dispatch and it empties
|
|
753
|
-
* here. A text part names the tool call of each picture, because the wire holds
|
|
754
|
-
* no structural link between a user message and a tool call.
|
|
755
|
-
*
|
|
756
|
-
* The message carries the synthetic marker, because a `user` message opens a
|
|
757
|
-
* conversation turn. An unmarked one is loop machinery that reads as a turn
|
|
758
|
-
* boundary, and it splits one stored turn in two.
|
|
759
|
-
*/
|
|
760
|
-
function appendDeferredImages(messages, results, deferredImages) {
|
|
761
|
-
if (deferredImages.length === 0)
|
|
762
|
-
return;
|
|
763
|
-
const byToolCallId = new Map(deferredImages.map((deferred) => [deferred.toolCallId, deferred]));
|
|
764
|
-
const content = [];
|
|
765
|
-
for (const result of results) {
|
|
766
|
-
const deferred = byToolCallId.get(result.toolCallId);
|
|
767
|
-
if (deferred === undefined)
|
|
768
|
-
continue;
|
|
769
|
-
for (const [index, image] of deferred.images.entries()) {
|
|
770
|
-
content.push({ type: "text", text: deferredImageLabel(deferred, index) });
|
|
771
|
-
content.push({ type: "file", mediaType: image.mediaType, data: { type: "data", data: image.base64 } });
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
messages.push(syntheticUserMessage(content));
|
|
775
|
-
deferredImages.length = 0;
|
|
776
|
-
}
|
|
777
|
-
/**
|
|
778
|
-
* Dispatch one round of tool calls, measure the time of each call, and fold the
|
|
779
|
-
* call record of each call in call order.
|
|
780
|
-
*
|
|
781
|
-
* `refusals`, `results`, `durations` and `resultDetails` are positionally aligned
|
|
782
|
-
* with `toolUses`; `refusals[i]` is the refusal text, or `undefined` if the call runs.
|
|
783
|
-
*
|
|
784
|
-
* A refused call still runs inside the same step wrapper and step name as a
|
|
785
|
-
* dispatched one, keeping a round's step sequence identical for replay.
|
|
786
|
-
*
|
|
787
|
-
* Each measurement brackets the same unit that the loop awaits for that call. For a
|
|
788
|
-
* step-mode call that unit is `runStep`, thus the figure includes the durable-step
|
|
789
|
-
* wrapper. The wrapper is part of what the call cost, and a cached replay of a step
|
|
790
|
-
* is genuinely fast. A bracket inside the step would report a body that did not run.
|
|
791
|
-
*/
|
|
792
|
-
async function dispatchTools(toolUses, refusals, toolsById, toolCtx, isFatalLoopError, runStep, toolStepName, encoding, cut) {
|
|
793
|
-
// A refusal writes no tool span, because the tool does not run.
|
|
794
|
-
const dispatch = (tu, refusal) => refusal !== undefined
|
|
795
|
-
? Promise.resolve({ result: errorResult(tu, refusal) })
|
|
796
|
-
: traceToolCall(tu, () => dispatchTool(tu, toolsById, toolCtx(tu), isFatalLoopError, encoding, cut, toolStepName(tu.toolName, tu.toolCallId)), (dispatched) => outcomeOf(dispatched.result));
|
|
797
|
-
const results = new Array(toolUses.length);
|
|
798
|
-
// The array starts with holes, which read as `undefined`. The element type
|
|
799
|
-
// says so, thus it agrees with what `settleRound` accepts. Every index is
|
|
800
|
-
// assigned except the index of a refused call, because the mode partition
|
|
801
|
-
// below covers each call.
|
|
802
|
-
const durations = new Array(toolUses.length);
|
|
803
|
-
// A hole stays a hole for a call that described no result: an error, a
|
|
804
|
-
// denial, and a tool with no result hook each leave the index unassigned,
|
|
805
|
-
// and `settleRound` then reads the call detail.
|
|
806
|
-
const resultDetails = new Array(toolUses.length);
|
|
807
|
-
const records = new Array(toolUses.length);
|
|
808
|
-
const stepTools = [];
|
|
809
|
-
const inlineTools = [];
|
|
810
|
-
for (const [idx, tu] of toolUses.entries()) {
|
|
811
|
-
const mode = toolsById.get(tu.toolName)?.executionMode ?? "step";
|
|
812
|
-
if (mode === "inline")
|
|
813
|
-
inlineTools.push({ tu, idx });
|
|
814
|
-
else
|
|
815
|
-
stepTools.push({ tu, idx });
|
|
816
|
-
}
|
|
817
|
-
await Promise.all(stepTools.map(({ tu, idx }) => {
|
|
818
|
-
const refusal = refusals[idx];
|
|
819
|
-
const startedAt = performance.now();
|
|
820
|
-
return runStep(toolStepName(tu.toolName, tu.toolCallId), () => dispatch(tu, refusal)).then((settled) => {
|
|
821
|
-
if (refusal === undefined)
|
|
822
|
-
durations[idx] = elapsedMs(startedAt);
|
|
823
|
-
const dispatched = readDispatchedStep(settled);
|
|
824
|
-
results[idx] = dispatched.result;
|
|
825
|
-
if (dispatched.detail !== undefined)
|
|
826
|
-
resultDetails[idx] = dispatched.detail;
|
|
827
|
-
records[idx] = dispatched.record;
|
|
828
|
-
});
|
|
829
|
-
}));
|
|
830
|
-
for (const { tu, idx } of inlineTools) {
|
|
831
|
-
const refusal = refusals[idx];
|
|
832
|
-
const startedAt = performance.now();
|
|
833
|
-
const dispatched = await dispatch(tu, refusal);
|
|
834
|
-
results[idx] = dispatched.result;
|
|
835
|
-
if (dispatched.detail !== undefined)
|
|
836
|
-
resultDetails[idx] = dispatched.detail;
|
|
837
|
-
records[idx] = dispatched.record;
|
|
838
|
-
if (refusal === undefined)
|
|
839
|
-
durations[idx] = elapsedMs(startedAt);
|
|
840
|
-
}
|
|
841
|
-
// After the whole round, in call order: the parallel step calls settle in any
|
|
842
|
-
// order, and a replay must fold the same records in the same sequence.
|
|
843
|
-
for (const [idx, tu] of toolUses.entries()) {
|
|
844
|
-
const record = records[idx];
|
|
845
|
-
if (record !== undefined)
|
|
846
|
-
foldCallRecord(toolsById.get(tu.toolName), tu, record, encoding.log);
|
|
847
|
-
}
|
|
848
|
-
return { results, durations, resultDetails };
|
|
849
|
-
}
|
|
850
|
-
/** Hand one call record to the tool that made it. A throw is logged, because the call has settled already. */
|
|
851
|
-
function foldCallRecord(tool, tu, record, log) {
|
|
852
|
-
if (tool?.foldCallRecord === undefined)
|
|
853
|
-
return;
|
|
854
|
-
try {
|
|
855
|
-
tool.foldCallRecord(record);
|
|
856
|
-
}
|
|
857
|
-
catch (err) {
|
|
858
|
-
log.error("a tool call record did not fold", { tool: tool.id, toolCallId: tu.toolCallId, ...log.errorFields(err) });
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
/** Whole milliseconds since `startedAt`, on the monotonic clock that took that mark. */
|
|
862
|
-
function elapsedMs(startedAt) {
|
|
863
|
-
return Math.round(performance.now() - startedAt);
|
|
864
|
-
}
|
|
865
|
-
/**
|
|
866
|
-
* Read one settled step-mode dispatch, tolerating the bare result beside the pair.
|
|
867
|
-
*
|
|
868
|
-
* A step-mode call goes through `runStep`, which caches what the body returned and hands
|
|
869
|
-
* that cached value back on a replay. The durable step store therefore holds values that
|
|
870
|
-
* a DIFFERENT build of this file wrote: a workflow in flight when the shape of the return
|
|
871
|
-
* widened replays the bare `ToolResultPart` its own run cached, and it never re-enters the
|
|
872
|
-
* body that would produce the pair. The replay boundary is the whole reason for the test:
|
|
873
|
-
* a bare value carries no detail, thus the call keeps the line its `tool-started` showed.
|
|
874
|
-
*/
|
|
875
|
-
function readDispatchedStep(settled) {
|
|
876
|
-
return "result" in settled ? settled : { result: settled };
|
|
877
|
-
}
|
|
878
|
-
/** For a step-mode tool this runs inside the durable step of the call, thus a replay gives the stored excerpt. */
|
|
879
|
-
async function dispatchTool(tu, toolsById, ctx, isFatalLoopError, encoding, cut, stepName) {
|
|
880
|
-
const dispatched = await callTool(tu, toolsById, ctx, isFatalLoopError, encoding);
|
|
881
|
-
return { ...dispatched, result: await cutLongResult(dispatched.result, cut, stepName) };
|
|
882
|
-
}
|
|
883
|
-
/** The excerpt does not depend on the outcome of the put, thus a replay gives the same bytes. */
|
|
884
|
-
async function cutLongResult(result, cut, stepName) {
|
|
885
|
-
const text = resultTextOf(result.output);
|
|
886
|
-
if (text === undefined || text.length <= TOOL_RESULT_CAP)
|
|
887
|
-
return result;
|
|
888
|
-
if (cut.store === undefined)
|
|
889
|
-
return withExcerpt(result, excerptOf(text, undefined));
|
|
890
|
-
const { session } = cut;
|
|
891
|
-
const ref = toolOutputRef(recordKeyFor(session, cut.invocationId, stepName));
|
|
892
|
-
const content = keptTextOf(text);
|
|
893
|
-
const put = await cut.store.put({
|
|
894
|
-
analysisId: session.scope.analysisId,
|
|
895
|
-
ref,
|
|
896
|
-
toolName: result.toolName,
|
|
897
|
-
toolCallId: result.toolCallId,
|
|
898
|
-
// A run session carries the thread of the chat that started the run, but a run belongs to its analysis.
|
|
899
|
-
...(session.runFrame === undefined && session.scope.threadId !== undefined ? { threadId: session.scope.threadId } : {}),
|
|
900
|
-
content,
|
|
901
|
-
totalLength: text.length,
|
|
902
|
-
});
|
|
903
|
-
if (put.isErr()) {
|
|
904
|
-
cut.log.warn("tool output not kept", { toolName: result.toolName, toolCallId: result.toolCallId, ref, errorType: put.error.type });
|
|
905
|
-
}
|
|
906
|
-
return withExcerpt(result, excerptOf(text, { ref, keptLength: content.length }));
|
|
907
|
-
}
|
|
908
|
-
async function callTool(tu, toolsById, ctx, isFatalLoopError, encoding) {
|
|
909
|
-
const tool = toolsById.get(tu.toolName);
|
|
910
|
-
if (tool === undefined) {
|
|
911
|
-
labelToolFailure("unknown_tool");
|
|
912
|
-
return { result: errorResult(tu, `unknown tool: ${tu.toolName}`) };
|
|
913
|
-
}
|
|
914
|
-
const parsed = tool.inputSchema.safeParse(tu.input);
|
|
915
|
-
if (parsed.success)
|
|
916
|
-
return execute(tu, tool, parsed.data, ctx, isFatalLoopError, encoding);
|
|
917
|
-
// A complete JSON argument can arrive as a string behind function-call
|
|
918
|
-
// markup or a code fence. Repair only makes the schema reachable — the
|
|
919
|
-
// repaired value is validated in full, and the tool's own semantic checks
|
|
920
|
-
// still run, so nothing here weakens validation.
|
|
921
|
-
const repairedInput = repairToolInput(tu.input, parsed.error);
|
|
922
|
-
const repaired = repairedInput === undefined ? undefined : tool.inputSchema.safeParse(repairedInput);
|
|
923
|
-
if (repaired?.success === true)
|
|
924
|
-
return execute(tu, tool, repaired.data, ctx, isFatalLoopError, encoding);
|
|
925
|
-
// The Zod text can quote the input, thus the span and the log get only the shape of the failure.
|
|
926
|
-
const issues = describeZodIssueShapes(parsed.error, tool.inputSchema);
|
|
927
|
-
labelToolValidationFailure(issues);
|
|
928
|
-
encoding.log.warn("tool call rejected", { tool: tool.id, toolCallId: tu.toolCallId, issues });
|
|
929
|
-
return { result: errorResult(tu, `input validation failed: ${formatZodIssues(parsed.error, tu.input)}`) };
|
|
930
|
-
}
|
|
931
|
-
async function execute(tu, tool, input, ctx, isFatalLoopError, encoding) {
|
|
932
|
-
try {
|
|
933
|
-
const output = await tool.execute(input, ctx);
|
|
934
|
-
if (output.isErr()) {
|
|
935
|
-
markToolFailure(tu, tool, output.error, encoding.log);
|
|
936
|
-
return { result: errorResult(tu, toolErrorContent(output.error)) };
|
|
937
|
-
}
|
|
938
|
-
// The one place a result description runs: the ok value in hand, the
|
|
939
|
-
// input already validated, and the guard inside the compute.
|
|
940
|
-
const detail = computeResultDetail(tool, input, output.value, encoding.log);
|
|
941
|
-
const record = readToolCallRecord(output.value);
|
|
942
|
-
const result = successResult(tu, output.value, encoding);
|
|
943
|
-
return { result, ...(detail === undefined ? {} : { detail }), ...(record === undefined ? {} : { record }) };
|
|
944
|
-
}
|
|
945
|
-
catch (err) {
|
|
946
|
-
if (isFatalLoopError(err))
|
|
947
|
-
throw err;
|
|
948
|
-
if (isAskRejected(err))
|
|
949
|
-
return { result: deniedResult(tu, err.feedback) };
|
|
950
|
-
markToolFailure(tu, tool, err, encoding.log);
|
|
951
|
-
return { result: errorResult(tu, toolErrorContent(err)) };
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
/**
|
|
955
|
-
* Label the span of a tool call that our tool code failed. A `ToolError` is a
|
|
956
|
-
* failure that the tool modeled, thus its own text is the account, with no
|
|
957
|
-
* exception and no log. Any other `Error` is a fault that the tool did not
|
|
958
|
-
* model: the span records the exception, and the log gets one warn.
|
|
959
|
-
*/
|
|
960
|
-
function markToolFailure(tu, tool, failure, log) {
|
|
961
|
-
const toolError = modeledToolError(failure);
|
|
962
|
-
if (toolError !== undefined) {
|
|
963
|
-
labelToolFailure("tool_error", toolError.error);
|
|
964
|
-
}
|
|
965
|
-
else if (failure instanceof Error) {
|
|
966
|
-
recordToolException(failure);
|
|
967
|
-
log.warn("tool call threw", { tool: tool.id, toolCallId: tu.toolCallId, ...log.errorFields(failure) });
|
|
968
|
-
}
|
|
969
|
-
else {
|
|
970
|
-
labelToolFailure("_OTHER");
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
/** The `ToolError` of a failure: the value itself, or the value that `unwrapOrThrow` wrapped in a `ResultError`. */
|
|
974
|
-
function modeledToolError(failure) {
|
|
975
|
-
if (isToolError(failure))
|
|
976
|
-
return failure;
|
|
977
|
-
return failure instanceof ResultError && isToolError(failure.value) ? failure.value : undefined;
|
|
978
|
-
}
|
|
979
|
-
/**
|
|
980
|
-
* Normalize a tool's return value to plain JSON, stripping NUL from every string
|
|
981
|
-
* it holds ({@link stripNulCharacters}). The reviver sees values, not keys — a
|
|
982
|
-
* NUL in an object KEY would still reach the row, but keys come from tool result
|
|
983
|
-
* types rather than scanned bytes, so nothing produces one.
|
|
984
|
-
*/
|
|
985
|
-
function jsonValue(value) {
|
|
986
|
-
return JSON.parse(JSON.stringify(value ?? null), (_key, v) => (typeof v === "string" ? stripNulCharacters(v) : v));
|
|
987
|
-
}
|
|
988
|
-
/**
|
|
989
|
-
* Encode a tool ok value onto a tool-result message. A value with no picture
|
|
990
|
-
* stays a plain JSON result, byte-identical to a value that never carried one.
|
|
991
|
-
*
|
|
992
|
-
* A value that carries pictures splits: the JSON data goes to a text part, and
|
|
993
|
-
* the bytes ride as image content blocks after it, in the order that the tool
|
|
994
|
-
* gave. Thus the model sees each picture, and the JSON text holds no bytes. When
|
|
995
|
-
* the wire carries no picture, the blocks drop and the text stays, because
|
|
996
|
-
* base64 text floods the context and the model cannot see it. One warn with the
|
|
997
|
-
* count rides the log, thus an operator sees how many pictures did not reach the
|
|
998
|
-
* model.
|
|
999
|
-
*
|
|
1000
|
-
* A wire that renders a picture on a user message only gets the fallback: the
|
|
1001
|
-
* result keeps its JSON text, and the pictures go to the collector of the round.
|
|
1002
|
-
* The round then sends the bytes after the tool message.
|
|
1003
|
-
*/
|
|
1004
|
-
function successResult(toolCall, value, encoding) {
|
|
1005
|
-
const jsonData = jsonValue(value);
|
|
1006
|
-
const jsonOnly = {
|
|
1007
|
-
type: "tool-result",
|
|
1008
|
-
toolCallId: toolCall.toolCallId,
|
|
1009
|
-
toolName: toolCall.toolName,
|
|
1010
|
-
output: { type: "json", value: jsonData },
|
|
1011
|
-
};
|
|
1012
|
-
const images = readToolResultImages(value);
|
|
1013
|
-
if (images.length === 0)
|
|
1014
|
-
return jsonOnly;
|
|
1015
|
-
if (encoding.placement === "drop") {
|
|
1016
|
-
encoding.log.warn("the wired provider carries no picture in a tool result, thus the pictures are dropped", {
|
|
1017
|
-
toolName: toolCall.toolName,
|
|
1018
|
-
imageCount: images.length,
|
|
1019
|
-
});
|
|
1020
|
-
return jsonOnly;
|
|
1021
|
-
}
|
|
1022
|
-
if (encoding.placement === "user-message") {
|
|
1023
|
-
encoding.deferredImages.push({ toolCallId: toolCall.toolCallId, toolName: toolCall.toolName, images });
|
|
1024
|
-
return jsonOnly;
|
|
1025
|
-
}
|
|
1026
|
-
return {
|
|
1027
|
-
type: "tool-result",
|
|
1028
|
-
toolCallId: toolCall.toolCallId,
|
|
1029
|
-
toolName: toolCall.toolName,
|
|
1030
|
-
output: {
|
|
1031
|
-
type: "content",
|
|
1032
|
-
value: [
|
|
1033
|
-
{ type: "text", text: JSON.stringify(jsonData) },
|
|
1034
|
-
...images.map((image) => ({ type: "file", mediaType: image.mediaType, data: { type: "data", data: image.base64 } })),
|
|
1035
|
-
],
|
|
1036
|
-
},
|
|
1037
|
-
};
|
|
1038
|
-
}
|
|
1039
|
-
// NUL is stripped from the prose BEFORE it is serialized: past the stringify it
|
|
1040
|
-
// is the six-character escape \u0000 — legitimate JSON text, and no longer
|
|
1041
|
-
// distinguishable from an error that happens to quote that escape.
|
|
1042
|
-
function toolErrorContent(value) {
|
|
1043
|
-
const toolError = modeledToolError(value);
|
|
1044
|
-
if (toolError !== undefined) {
|
|
1045
|
-
return JSON.stringify({ error: stripNulCharacters(toolError.error), retryable: toolError.retryable });
|
|
1046
|
-
}
|
|
1047
|
-
const { retryable } = classifyProviderError(value);
|
|
1048
|
-
const error = stripNulCharacters(value instanceof Error ? value.message : String(value));
|
|
1049
|
-
return JSON.stringify({ error, retryable });
|
|
1050
|
-
}
|
|
1051
|
-
function errorResult(toolCall, content) {
|
|
1052
|
-
return {
|
|
1053
|
-
type: "tool-result",
|
|
1054
|
-
toolCallId: toolCall.toolCallId,
|
|
1055
|
-
toolName: toolCall.toolName,
|
|
1056
|
-
// A thrown tool error routinely quotes stderr verbatim — as exposed to NUL as a success payload.
|
|
1057
|
-
output: { type: "error-text", value: stripNulCharacters(content) },
|
|
1058
|
-
};
|
|
1059
|
-
}
|
|
1060
|
-
function isAskRejected(err) {
|
|
1061
|
-
// Name-based fallback recognizes a rejection thrown from a different module
|
|
1062
|
-
// realm, where `instanceof` against this file's class reference would miss it.
|
|
1063
|
-
return err instanceof AskRejectedError || (err instanceof Error && err.name === "AskRejectedError");
|
|
1064
|
-
}
|
|
1065
|
-
/**
|
|
1066
|
-
* Map a rejected approval to a model-visible `execution-denied` tool result. The
|
|
1067
|
-
* prose is the model's only account of the denial; `outcomeOf` reports it as
|
|
1068
|
-
* `denied`, distinct from a fault.
|
|
1069
|
-
*/
|
|
1070
|
-
function deniedResult(toolCall, feedback) {
|
|
1071
|
-
const reason = feedback === undefined || feedback.length === 0
|
|
1072
|
-
? "The user rejected this action."
|
|
1073
|
-
: `The user rejected this action with the following feedback: ${feedback}`;
|
|
1074
|
-
return {
|
|
1075
|
-
type: "tool-result",
|
|
1076
|
-
toolCallId: toolCall.toolCallId,
|
|
1077
|
-
toolName: toolCall.toolName,
|
|
1078
|
-
output: { type: "execution-denied", reason },
|
|
1079
|
-
};
|
|
1080
|
-
}
|
|
1081
|
-
function outcomeOf(result) {
|
|
1082
|
-
return toolOutcomeForOutputType(result.output.type);
|
|
1083
|
-
}
|
|
1084
|
-
/**
|
|
1085
|
-
* The optional `detail` field, present only when there is one. Spread rather
|
|
1086
|
-
* than assigned so an undescribed call emits an event with no `detail` key at
|
|
1087
|
-
* all, which is what the contract promises: absent, never empty.
|
|
1088
|
-
*/
|
|
1089
|
-
function detailField(detail) {
|
|
1090
|
-
return detail === undefined ? {} : { detail };
|
|
1091
|
-
}
|
|
1092
|
-
/**
|
|
1093
|
-
* The optional `durationMs` field, present only for a call that the loop measured.
|
|
1094
|
-
*
|
|
1095
|
-
* Spread rather than assigned, for the same reason as {@link detailField}: an
|
|
1096
|
-
* unmeasured call emits no `durationMs` key. A zero here is a real measurement of a
|
|
1097
|
-
* call that took under half a millisecond, and it is never a stand-in for no figure.
|
|
1098
|
-
*/
|
|
1099
|
-
function durationField(durationMs) {
|
|
1100
|
-
return durationMs === undefined ? {} : { durationMs };
|
|
1101
|
-
}
|
|
1102
|
-
function hasDenial(results) {
|
|
1103
|
-
return results.some((r) => r.output.type === "execution-denied");
|
|
1104
|
-
}
|
|
1105
|
-
export function finalText(messages) {
|
|
1106
|
-
const last = messages.at(-1);
|
|
1107
|
-
if (last === undefined || last.role !== "assistant")
|
|
1108
|
-
return "";
|
|
1109
|
-
if (typeof last.content === "string")
|
|
1110
|
-
return last.content;
|
|
1111
|
-
return last.content
|
|
1112
|
-
.filter((block) => block.type === "text")
|
|
1113
|
-
.map((block) => block.text)
|
|
1114
|
-
.join("");
|
|
1115
|
-
}
|
|
1116
|
-
function formatZodIssues(error, input) {
|
|
1117
|
-
return error.issues
|
|
1118
|
-
.map((issue) => {
|
|
1119
|
-
const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
1120
|
-
const hint = hintForZodIssue(issue, input);
|
|
1121
|
-
return hint === undefined ? `${path}: ${issue.message}` : `${path}: ${issue.message} — ${hint}`;
|
|
1122
|
-
})
|
|
1123
|
-
.join("; ");
|
|
1124
|
-
}
|