@qvac/harness 0.7.1 → 1.0.0-rc.1
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 +224 -0
- package/dist-lib/index.d.ts +55 -3
- package/dist-lib/index.js +721 -236
- package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.d.ts +1 -0
- package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.js +10 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/answering.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/answering.js +17 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/approval.d.ts +4 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/approval.js +29 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.d.ts +4 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.js +73 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execute.d.ts +41 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execute.js +49 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/executing.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/executing.js +102 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.d.ts +47 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.js +87 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/finishing.d.ts +2 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/finishing.js +32 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.js +75 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.js +152 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.js +94 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.d.ts +9 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.js +11 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preloading.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preloading.js +114 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preparing.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preparing.js +198 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.js +71 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejection.d.ts +18 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejection.js +70 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/resolving.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/resolving.js +60 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/stream.d.ts +21 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/stream.js +48 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/streaming.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/streaming.js +156 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.d.ts +7 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.js +61 -0
- package/dist-lib/lib/capabilities/completion-engine/driver.d.ts +25 -0
- package/dist-lib/lib/capabilities/completion-engine/driver.js +553 -0
- package/dist-lib/lib/capabilities/completion-engine/durable.d.ts +61 -0
- package/dist-lib/lib/capabilities/completion-engine/durable.js +150 -0
- package/dist-lib/lib/capabilities/completion-engine/operations.d.ts +6 -0
- package/dist-lib/lib/capabilities/completion-engine/operations.js +239 -0
- package/dist-lib/lib/capabilities/completion-engine/ports.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/ports.js +267 -0
- package/dist-lib/lib/capabilities/completion-engine/session.d.ts +76 -0
- package/dist-lib/lib/capabilities/completion-engine/session.js +70 -0
- package/dist-lib/lib/capabilities/completion-loop/index.d.ts +80 -0
- package/dist-lib/lib/capabilities/completion-loop/index.js +158 -0
- package/dist-lib/lib/capabilities/completion-loop/metrics.d.ts +5 -0
- package/dist-lib/lib/capabilities/completion-loop/metrics.js +29 -0
- package/dist-lib/lib/capabilities/completion-loop/round.d.ts +33 -0
- package/dist-lib/lib/capabilities/completion-loop/round.js +353 -0
- package/dist-lib/lib/capabilities/completion-loop/tool-execution.d.ts +60 -0
- package/dist-lib/lib/capabilities/completion-loop/tool-execution.js +270 -0
- package/dist-lib/lib/capabilities/completion-loop/turns.d.ts +23 -0
- package/dist-lib/lib/capabilities/completion-loop/turns.js +112 -0
- package/dist-lib/lib/capabilities/completion-runtime.d.ts +46 -0
- package/dist-lib/lib/capabilities/completion-runtime.js +74 -0
- package/dist-lib/lib/capabilities/completion.d.ts +18 -335
- package/dist-lib/lib/capabilities/completion.js +20 -1301
- package/dist-lib/lib/capabilities/download.d.ts +1 -1
- package/dist-lib/lib/capabilities/download.js +7 -2
- package/dist-lib/lib/capabilities/invoke.js +5 -3
- package/dist-lib/lib/capabilities/tool-plan.d.ts +35 -0
- package/dist-lib/lib/capabilities/tool-plan.js +66 -0
- package/dist-lib/lib/capabilities/tool-turns.d.ts +11 -2
- package/dist-lib/lib/capabilities/tool-turns.js +30 -2
- package/dist-lib/lib/capabilities/types.d.ts +287 -0
- package/dist-lib/lib/capabilities/types.js +1 -0
- package/dist-lib/lib/completion-lifecycle.d.ts +14 -0
- package/dist-lib/lib/completion-lifecycle.js +126 -0
- package/dist-lib/lib/connect.d.ts +10 -3
- package/dist-lib/lib/connect.js +329 -23
- package/dist-lib/lib/contract-types.d.ts +19 -2
- package/dist-lib/lib/harness-lifecycle.d.ts +66 -0
- package/dist-lib/lib/harness-lifecycle.js +129 -0
- package/dist-lib/lib/helpers/detach-json.d.ts +1 -0
- package/dist-lib/lib/helpers/detach-json.js +146 -0
- package/dist-lib/lib/helpers/markup-scanner.d.ts +26 -0
- package/dist-lib/lib/helpers/markup-scanner.js +156 -0
- package/dist-lib/lib/helpers/markup-stream-filter.js +138 -35
- package/dist-lib/lib/helpers/race-abort.d.ts +9 -4
- package/dist-lib/lib/helpers/race-abort.js +2 -7
- package/dist-lib/lib/helpers/recovery-required.d.ts +1 -0
- package/dist-lib/lib/helpers/recovery-required.js +1 -0
- package/dist-lib/lib/helpers/run-outcome.d.ts +10 -2
- package/dist-lib/lib/helpers/run-outcome.js +8 -5
- package/dist-lib/lib/helpers/tool-call-markup.d.ts +2 -0
- package/dist-lib/lib/helpers/tool-call-markup.js +197 -82
- package/dist-lib/lib/hooks/built-in/arg-repair.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/arg-repair.js +1 -0
- package/dist-lib/lib/hooks/built-in/egress.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/egress.js +2 -1
- package/dist-lib/lib/hooks/built-in/sandbox.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/sandbox.js +6 -1
- package/dist-lib/lib/hooks/built-in/skill-catalog.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
- package/dist-lib/lib/hooks/pipeline.d.ts +4 -1
- package/dist-lib/lib/hooks/pipeline.js +24 -2
- package/dist-lib/lib/hooks/run.d.ts +2 -1
- package/dist-lib/lib/hooks/run.js +19 -7
- package/dist-lib/lib/hooks/types.d.ts +17 -6
- package/dist-lib/lib/machine/completion/actions/approval.d.ts +3 -0
- package/dist-lib/lib/machine/completion/actions/approval.js +16 -0
- package/dist-lib/lib/machine/completion/actions/completion.d.ts +23 -0
- package/dist-lib/lib/machine/completion/actions/completion.js +142 -0
- package/dist-lib/lib/machine/completion/actions/skills.d.ts +9 -0
- package/dist-lib/lib/machine/completion/actions/skills.js +83 -0
- package/dist-lib/lib/machine/completion/actions/tools.d.ts +16 -0
- package/dist-lib/lib/machine/completion/actions/tools.js +173 -0
- package/dist-lib/lib/machine/completion/context.d.ts +9 -0
- package/dist-lib/lib/machine/completion/context.js +44 -0
- package/dist-lib/lib/machine/completion/contract.d.ts +6 -0
- package/dist-lib/lib/machine/completion/contract.js +1 -0
- package/dist-lib/lib/machine/completion/guards/retry.d.ts +9 -0
- package/dist-lib/lib/machine/completion/guards/retry.js +25 -0
- package/dist-lib/lib/machine/completion/implementations.d.ts +81 -0
- package/dist-lib/lib/machine/completion/implementations.js +152 -0
- package/dist-lib/lib/machine/completion/machine.d.ts +375 -0
- package/dist-lib/lib/machine/completion/machine.js +511 -0
- package/dist-lib/lib/machine/completion/ports.d.ts +65 -0
- package/dist-lib/lib/machine/completion/ports.js +224 -0
- package/dist-lib/lib/machine/completion/rules.d.ts +39 -0
- package/dist-lib/lib/machine/completion/rules.js +115 -0
- package/dist-lib/lib/machine/completion/types.d.ts +493 -0
- package/dist-lib/lib/machine/completion/types.js +1 -0
- package/dist-lib/lib/machine/engine.d.ts +93 -0
- package/dist-lib/lib/machine/engine.js +483 -0
- package/dist-lib/lib/machine/errors.d.ts +44 -0
- package/dist-lib/lib/machine/errors.js +373 -0
- package/dist-lib/lib/machine/guards.d.ts +2 -0
- package/dist-lib/lib/machine/guards.js +6 -0
- package/dist-lib/lib/machine/index.d.ts +20 -0
- package/dist-lib/lib/machine/index.js +11 -0
- package/dist-lib/lib/machine/replay-output.d.ts +24 -0
- package/dist-lib/lib/machine/replay-output.js +50 -0
- package/dist-lib/lib/machine/stable-stringify.d.ts +1 -0
- package/dist-lib/lib/machine/stable-stringify.js +11 -0
- package/dist-lib/lib/models/model-pool.d.ts +14 -6
- package/dist-lib/lib/models/model-pool.js +95 -30
- package/dist-lib/lib/run-log/history-codec.d.ts +23 -0
- package/dist-lib/lib/run-log/history-codec.js +973 -0
- package/dist-lib/lib/run-log/patch.d.ts +6 -0
- package/dist-lib/lib/run-log/patch.js +93 -0
- package/dist-lib/lib/run-log/persisted-actor.d.ts +18 -0
- package/dist-lib/lib/run-log/persisted-actor.js +218 -0
- package/dist-lib/lib/run-log/protocol-types.d.ts +153 -0
- package/dist-lib/lib/run-log/protocol-types.js +1 -0
- package/dist-lib/lib/run-log/protocol.d.ts +27 -0
- package/dist-lib/lib/run-log/protocol.js +1056 -0
- package/dist-lib/lib/run-log/replay.d.ts +25 -0
- package/dist-lib/lib/run-log/replay.js +129 -0
- package/dist-lib/lib/run-log/run-journal.d.ts +125 -0
- package/dist-lib/lib/run-log/run-journal.js +783 -0
- package/dist-lib/lib/run-log-inspect.d.ts +25 -0
- package/dist-lib/lib/run-log-inspect.js +627 -0
- package/dist-lib/lib/run-log-protocol.d.ts +2 -0
- package/dist-lib/lib/run-log-protocol.js +1 -0
- package/dist-lib/lib/run-log-redact.d.ts +27 -0
- package/dist-lib/lib/run-log-redact.js +1107 -0
- package/dist-lib/lib/serve.js +307 -11
- package/dist-lib/lib/spawn.js +3 -2
- package/dist-lib/lib/tool-call-display.d.ts +1 -21
- package/dist-lib/lib/tools/exec/adapters/python/wheels.js +2 -1
- package/dist-lib/lib/tools/exec.d.ts +2 -1
- package/dist-lib/lib/tools/exec.js +57 -2
- package/dist-lib/lib/tools/generate-image.js +1 -1
- package/dist-lib/lib/tools/generate-music.js +1 -1
- package/dist-lib/lib/tools/http-request.d.ts +2 -1
- package/dist-lib/lib/tools/http-request.js +2 -1
- package/dist-lib/lib/tools/mcp-call.js +8 -1
- package/dist-lib/lib/tools/operations/builders.d.ts +1 -0
- package/dist-lib/lib/tools/operations/builders.js +1 -0
- package/dist-lib/lib/tools/operations/compile.js +11 -1
- package/dist-lib/lib/tools/operations/manifest.js +1 -0
- package/dist-lib/lib/tools/outbound-guard.d.ts +8 -6
- package/dist-lib/lib/tools/outbound-guard.js +25 -8
- package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
- package/dist-lib/lib/tools/skills/catalog.js +35 -0
- package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +1 -0
- package/dist-lib/lib/tools/skills/cli-schema.js +25 -0
- package/dist-lib/lib/wire.d.ts +5 -0
- package/dist-lib/lib/wire.js +128 -5
- package/dist-lib/schema/harness.js +20 -5
- package/dist-lib/spec/hrpc/messages.js +95 -15
- package/dist-lib/spec/hyperschema/index.js +95 -15
- package/dist-lib/spec/hyperschema/schema.json +69 -1
- package/dist-lib/spec/hyperschema/types.d.ts +10 -3
- package/package.json +20 -2
- package/patches/@qvac%2Finference@0.20.0.patch +67 -0
- package/patches/bare-stow@0.1.5.patch +68 -0
- package/patches/hypercore-blob-server@1.15.0.patch +15 -0
- package/patches/localwatch@1.4.3.patch +13 -0
- package/patches/pyodide@314.0.2.patch +144 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { contextCandidates } from '../../context-window.js';
|
|
2
|
+
import { bufferBurst } from '../../helpers/buffer-burst.js';
|
|
3
|
+
import { endsTurn, stopAtControlToken } from '../../helpers/control-tokens.js';
|
|
4
|
+
import { createMarkupStreamFilter } from '../../helpers/markup-stream-filter.js';
|
|
5
|
+
import { AbortedError, raceAbort } from '../../helpers/race-abort.js';
|
|
6
|
+
import { raceTimeout } from '../../helpers/race-timeout.js';
|
|
7
|
+
import { splitThinking } from '../../helpers/thinking.js';
|
|
8
|
+
import { errorMessage, isRecord } from '../../guards.js';
|
|
9
|
+
import { hlog } from '../../log.js';
|
|
10
|
+
import { isContextOverflowError, isSdkModelWedgedError } from '../../models/sdk-faults.js';
|
|
11
|
+
import { copy, isFailureResultReference, stableStringify } from '../../machine/index.js';
|
|
12
|
+
import { detachJson } from '../../helpers/detach-json.js';
|
|
13
|
+
import { materializeCompletionHistory } from '../tool-turns.js';
|
|
14
|
+
const BURST_WINDOW_MS = 100;
|
|
15
|
+
export const CONTEXT_OVERFLOW_MESSAGE = 'This message is too large for the model context window.';
|
|
16
|
+
const noop = () => { };
|
|
17
|
+
function unprovenReplay(state, final) {
|
|
18
|
+
if (!state.unproven)
|
|
19
|
+
return null;
|
|
20
|
+
if (state.stopToken) {
|
|
21
|
+
return endsTurn(state.stopToken) ? state.unproven : null;
|
|
22
|
+
}
|
|
23
|
+
if (final.stopReason === 'length' || final.stopReason === 'cancelled') {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return state.unproven;
|
|
27
|
+
}
|
|
28
|
+
function cancelOnce(sdk, requestId) {
|
|
29
|
+
let done = false;
|
|
30
|
+
let pending = Promise.resolve();
|
|
31
|
+
const cancel = () => {
|
|
32
|
+
if (done || !requestId)
|
|
33
|
+
return;
|
|
34
|
+
done = true;
|
|
35
|
+
pending = sdk.cancel?.({ requestId }).catch(noop) ?? Promise.resolve();
|
|
36
|
+
};
|
|
37
|
+
return Object.assign(cancel, { flush: () => pending });
|
|
38
|
+
}
|
|
39
|
+
async function* runRoundTokens({ result, state, cancelThisRound, signal, startInThink }) {
|
|
40
|
+
signal?.addEventListener('abort', cancelThisRound, { once: true });
|
|
41
|
+
const tokens = stopAtControlToken(result.tokenStream, (token) => {
|
|
42
|
+
state.stopToken = token;
|
|
43
|
+
});
|
|
44
|
+
let tokenEvents = 0;
|
|
45
|
+
const events = splitThinking(bufferBurst(tokens, BURST_WINDOW_MS), { startInThink });
|
|
46
|
+
const thinkingFilter = createMarkupStreamFilter();
|
|
47
|
+
const contentFilter = createMarkupStreamFilter();
|
|
48
|
+
try {
|
|
49
|
+
tokenEvents = yield* processRoundTokenEvents({
|
|
50
|
+
events,
|
|
51
|
+
state,
|
|
52
|
+
cancelThisRound,
|
|
53
|
+
signal,
|
|
54
|
+
thinkingFilter,
|
|
55
|
+
contentFilter
|
|
56
|
+
});
|
|
57
|
+
hlog('stream ended', { events: tokenEvents, stopToken: state.stopToken });
|
|
58
|
+
if (state.stopToken) {
|
|
59
|
+
cancelThisRound();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
if (signal?.aborted) {
|
|
64
|
+
cancelThisRound();
|
|
65
|
+
}
|
|
66
|
+
signal?.removeEventListener('abort', cancelThisRound);
|
|
67
|
+
await events.return(null);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function* processRoundTokenEvents({ events, state, cancelThisRound, signal, thinkingFilter, contentFilter }) {
|
|
71
|
+
let tokenEvents = 0;
|
|
72
|
+
while (true) {
|
|
73
|
+
const next = await events.next();
|
|
74
|
+
if (next.done) {
|
|
75
|
+
state.unproven = next.value;
|
|
76
|
+
yield* flushFilterTails(thinkingFilter, contentFilter);
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
if (signal?.aborted) {
|
|
80
|
+
cancelThisRound();
|
|
81
|
+
throw new AbortedError();
|
|
82
|
+
}
|
|
83
|
+
tokenEvents++;
|
|
84
|
+
if (next.value.type === 'content') {
|
|
85
|
+
state.rawContentText += next.value.text;
|
|
86
|
+
}
|
|
87
|
+
const filter = next.value.type === 'thinking' ? thinkingFilter : contentFilter;
|
|
88
|
+
const text = filter.push(next.value.text);
|
|
89
|
+
if (text) {
|
|
90
|
+
yield { type: next.value.type, text };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return tokenEvents;
|
|
94
|
+
}
|
|
95
|
+
async function* flushFilterTails(thinkingFilter, contentFilter) {
|
|
96
|
+
const thinkingTail = thinkingFilter.flush();
|
|
97
|
+
if (thinkingTail) {
|
|
98
|
+
yield { type: 'thinking', text: thinkingTail };
|
|
99
|
+
}
|
|
100
|
+
const contentTail = contentFilter.flush();
|
|
101
|
+
if (contentTail) {
|
|
102
|
+
yield { type: 'content', text: contentTail };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function sentMessages(stats) {
|
|
106
|
+
if (!stats || !('sentMessages' in stats) || !Array.isArray(stats.sentMessages)) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
if (!stats.sentMessages.every(isMessage))
|
|
110
|
+
return undefined;
|
|
111
|
+
return detachJson(stats.sentMessages);
|
|
112
|
+
}
|
|
113
|
+
function isMessage(value) {
|
|
114
|
+
return isRecord(value) && typeof value.role === 'string' && typeof value.content === 'string';
|
|
115
|
+
}
|
|
116
|
+
function roundTokens(stats) {
|
|
117
|
+
if (!stats)
|
|
118
|
+
return undefined;
|
|
119
|
+
const tokens = {
|
|
120
|
+
prompt: stats.promptTokens,
|
|
121
|
+
generated: stats.generatedTokens,
|
|
122
|
+
cached: stats.cacheTokens,
|
|
123
|
+
timeToFirstToken: stats.timeToFirstToken
|
|
124
|
+
};
|
|
125
|
+
return Object.values(tokens).some((value) => value !== undefined) ? tokens : undefined;
|
|
126
|
+
}
|
|
127
|
+
const TRUNCATED_FINAL_MS = 2000;
|
|
128
|
+
function truncatedFinal(final, contentText) {
|
|
129
|
+
const accepted = { contentText, toolCalls: [], raw: { fullText: contentText } };
|
|
130
|
+
const guarded = final.catch((err) => {
|
|
131
|
+
if (isSdkModelWedgedError(err))
|
|
132
|
+
throw err;
|
|
133
|
+
return accepted;
|
|
134
|
+
});
|
|
135
|
+
guarded.catch(noop);
|
|
136
|
+
return raceTimeout(guarded, { ms: TRUNCATED_FINAL_MS, fallback: accepted });
|
|
137
|
+
}
|
|
138
|
+
export async function* runRecoveredRound(sdk, args) {
|
|
139
|
+
const lease = args.acquireRound ? await args.acquireRound(args.signal) : null;
|
|
140
|
+
try {
|
|
141
|
+
const roundSdk = lease?.sdk ?? sdk;
|
|
142
|
+
if (!roundSdk) {
|
|
143
|
+
throw new Error('A completion round requires an SDK');
|
|
144
|
+
}
|
|
145
|
+
return yield* runRecoveredRoundWith(roundSdk, args, lease?.modelId ?? args.modelId);
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
try {
|
|
149
|
+
lease?.release();
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
hlog('completion round lease release failed', { error: errorMessage(error) });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function* runRecoveredRoundWith(sdk, args, modelId) {
|
|
157
|
+
let lastOverflow = null;
|
|
158
|
+
const history = materializeCompletionHistory(args.history, args.failureResults);
|
|
159
|
+
const origins = historyOrigins(args.history, history);
|
|
160
|
+
for (const candidate of contextCandidates(history)) {
|
|
161
|
+
if (args.signal?.aborted)
|
|
162
|
+
throw new AbortedError();
|
|
163
|
+
const progress = {
|
|
164
|
+
cancel: cancelOnce(sdk, undefined),
|
|
165
|
+
emitted: false
|
|
166
|
+
};
|
|
167
|
+
try {
|
|
168
|
+
const attempt = yield* runCandidate({
|
|
169
|
+
sdk,
|
|
170
|
+
args,
|
|
171
|
+
modelId,
|
|
172
|
+
candidate,
|
|
173
|
+
progress,
|
|
174
|
+
baseHistory: history
|
|
175
|
+
});
|
|
176
|
+
return recoveredRound({ candidate, attempt, origins });
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
if (err instanceof AbortedError || args.signal?.aborted) {
|
|
180
|
+
progress.cancel();
|
|
181
|
+
}
|
|
182
|
+
if (args.capture) {
|
|
183
|
+
yield candidateFailureEvent({ args, candidate, error: err });
|
|
184
|
+
}
|
|
185
|
+
if (!isContextOverflowError(err)) {
|
|
186
|
+
throw err;
|
|
187
|
+
}
|
|
188
|
+
if (progress.emitted) {
|
|
189
|
+
throw errorWithCause(CONTEXT_OVERFLOW_MESSAGE, err);
|
|
190
|
+
}
|
|
191
|
+
lastOverflow = err;
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
await flushCandidate(args, progress);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (lastOverflow) {
|
|
198
|
+
throw errorWithCause(CONTEXT_OVERFLOW_MESSAGE, lastOverflow);
|
|
199
|
+
}
|
|
200
|
+
throw new Error('context recovery produced no completion candidate');
|
|
201
|
+
}
|
|
202
|
+
async function flushCandidate(args, progress) {
|
|
203
|
+
if (args.signal?.aborted) {
|
|
204
|
+
progress.cancel();
|
|
205
|
+
}
|
|
206
|
+
await raceTimeout(progress.cancel.flush(), { ms: TRUNCATED_FINAL_MS, fallback: undefined });
|
|
207
|
+
}
|
|
208
|
+
function recoveredRound({ candidate, attempt, origins }) {
|
|
209
|
+
const replay = unprovenReplay(attempt.state, attempt.final);
|
|
210
|
+
logCandidateFinal(attempt.state, attempt.final, replay);
|
|
211
|
+
return {
|
|
212
|
+
contentText: attempt.contentText,
|
|
213
|
+
final: attempt.final,
|
|
214
|
+
history: restoreHistory(candidate.history, origins),
|
|
215
|
+
cacheable: attempt.cacheable,
|
|
216
|
+
unproven: attempt.state.unproven,
|
|
217
|
+
replay
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function historyOrigins(entries, messages) {
|
|
221
|
+
const origins = new WeakMap();
|
|
222
|
+
entries.forEach((entry, index) => {
|
|
223
|
+
if (!isFailureResultReference(entry))
|
|
224
|
+
return;
|
|
225
|
+
const message = messages[index];
|
|
226
|
+
if (!message) {
|
|
227
|
+
throw new Error('Materialized history lost an entry');
|
|
228
|
+
}
|
|
229
|
+
origins.set(message, { entry, fingerprint: stableStringify(message) });
|
|
230
|
+
});
|
|
231
|
+
return origins;
|
|
232
|
+
}
|
|
233
|
+
async function* runCandidate({ sdk, args, modelId, candidate, progress, baseHistory }) {
|
|
234
|
+
const cacheable = candidate.history === baseHistory;
|
|
235
|
+
const params = inferenceParams({ args, modelId, history: candidate.history, cacheable });
|
|
236
|
+
if (args.capture) {
|
|
237
|
+
yield inferenceCall({
|
|
238
|
+
args,
|
|
239
|
+
candidate,
|
|
240
|
+
modelId,
|
|
241
|
+
params,
|
|
242
|
+
cacheKey: cacheable ? args.cacheKey : undefined
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
const result = sdk.completion(params);
|
|
246
|
+
result.final.catch(noop);
|
|
247
|
+
progress.cancel = cancelOnce(sdk, result.requestId);
|
|
248
|
+
const state = { stopToken: null, unproven: null, rawContentText: '' };
|
|
249
|
+
const contentText = yield* streamCandidateTokens({ result, state, args, progress });
|
|
250
|
+
const final = state.stopToken
|
|
251
|
+
? await truncatedFinal(result.final, state.rawContentText)
|
|
252
|
+
: await raceAbort(result.final, args.signal);
|
|
253
|
+
const detachedFinal = {
|
|
254
|
+
...final,
|
|
255
|
+
toolCalls: copy(final.toolCalls ?? []),
|
|
256
|
+
stats: copy(final.stats)
|
|
257
|
+
};
|
|
258
|
+
if (args.capture) {
|
|
259
|
+
yield inferenceSent({ args, candidate, final: detachedFinal });
|
|
260
|
+
}
|
|
261
|
+
return { contentText, final: detachedFinal, state, cacheable };
|
|
262
|
+
}
|
|
263
|
+
function inferenceParams({ args, modelId, history, cacheable }) {
|
|
264
|
+
return {
|
|
265
|
+
modelId,
|
|
266
|
+
history: detachJson(history),
|
|
267
|
+
stream: true,
|
|
268
|
+
tools: args.tools ? detachJson(args.tools) : undefined,
|
|
269
|
+
generationParams: args.generationParams ? detachJson(args.generationParams) : undefined,
|
|
270
|
+
responseFormat: args.responseFormat ? detachJson(args.responseFormat) : undefined,
|
|
271
|
+
toolDialect: args.toolDialect,
|
|
272
|
+
kvCache: cacheable ? args.cacheKey : undefined
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function inferenceCall({ args, candidate, modelId, params, cacheKey }) {
|
|
276
|
+
return {
|
|
277
|
+
type: 'inference-call',
|
|
278
|
+
call: {
|
|
279
|
+
round: args.round,
|
|
280
|
+
droppedTurns: candidate.droppedTurns,
|
|
281
|
+
modelId,
|
|
282
|
+
messages: params.history,
|
|
283
|
+
tools: params.tools,
|
|
284
|
+
generationParams: params.generationParams,
|
|
285
|
+
responseFormat: params.responseFormat,
|
|
286
|
+
toolDialect: params.toolDialect,
|
|
287
|
+
cacheKey,
|
|
288
|
+
promptPrefix: args.promptPrefix
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function inferenceSent({ args, candidate, final }) {
|
|
293
|
+
return {
|
|
294
|
+
type: 'inference-sent',
|
|
295
|
+
round: args.round,
|
|
296
|
+
droppedTurns: candidate.droppedTurns,
|
|
297
|
+
messages: sentMessages(final.stats),
|
|
298
|
+
tokens: roundTokens(final.stats),
|
|
299
|
+
outcome: 'completed',
|
|
300
|
+
reason: undefined
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
async function* streamCandidateTokens({ result, state, args, progress }) {
|
|
304
|
+
let contentText = '';
|
|
305
|
+
const startInThink = args.generationParams?.reasoning_budget === -1;
|
|
306
|
+
for await (const event of runRoundTokens({
|
|
307
|
+
result,
|
|
308
|
+
state,
|
|
309
|
+
cancelThisRound: progress.cancel,
|
|
310
|
+
signal: args.signal,
|
|
311
|
+
startInThink
|
|
312
|
+
})) {
|
|
313
|
+
if (event.type === 'content') {
|
|
314
|
+
contentText += event.text;
|
|
315
|
+
}
|
|
316
|
+
progress.emitted = true;
|
|
317
|
+
yield event;
|
|
318
|
+
}
|
|
319
|
+
return contentText;
|
|
320
|
+
}
|
|
321
|
+
function candidateFailureEvent({ args, candidate, error }) {
|
|
322
|
+
return {
|
|
323
|
+
type: 'inference-sent',
|
|
324
|
+
round: args.round,
|
|
325
|
+
droppedTurns: candidate.droppedTurns,
|
|
326
|
+
messages: undefined,
|
|
327
|
+
tokens: undefined,
|
|
328
|
+
outcome: isContextOverflowError(error) ? 'overflowed' : 'failed',
|
|
329
|
+
reason: errorMessage(error)
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function logCandidateFinal(state, final, replay) {
|
|
333
|
+
hlog('final', {
|
|
334
|
+
stopReason: final.stopReason,
|
|
335
|
+
toolCalls: final.toolCalls.length,
|
|
336
|
+
stopToken: state.stopToken,
|
|
337
|
+
replay: replay !== null
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
function restoreHistory(messages, origins) {
|
|
341
|
+
return messages.map((message) => {
|
|
342
|
+
const origin = origins.get(message);
|
|
343
|
+
if (!origin || stableStringify(message) !== origin.fingerprint) {
|
|
344
|
+
return message;
|
|
345
|
+
}
|
|
346
|
+
return origin.entry;
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
function errorWithCause(message, cause) {
|
|
350
|
+
const error = new Error(message);
|
|
351
|
+
Object.defineProperty(error, 'cause', { value: cause, configurable: true });
|
|
352
|
+
return error;
|
|
353
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { MAX_BLOCKED_REPEATS, toolCallKey, type ToolPreflight, type ToolPreflightFacts, type TrustedSourceFacts } from '../../machine/index.ts';
|
|
2
|
+
import type { ToolCallWithCall } from '@qvac/sdk';
|
|
3
|
+
import type { AbortSignal as BareAbortSignal } from 'bare-abort-controller';
|
|
4
|
+
import { HookPipeline } from '../../hooks/pipeline.ts';
|
|
5
|
+
import type { HookRejection } from '../../hooks/types.ts';
|
|
6
|
+
import type { ToolCallDisplay, ToolExecutionOutcome } from '../../tool-call-display.ts';
|
|
7
|
+
import { type TrustedSource } from '../../tools/outbound-guard.ts';
|
|
8
|
+
import type { ApproveFn, ToolActivityFn, ToolDefinition, ToolEffect, ToolExecuteContext } from '../completion.ts';
|
|
9
|
+
export { MAX_BLOCKED_REPEATS, toolCallKey };
|
|
10
|
+
export declare function callSucceeded(result: unknown, outcome: ToolExecutionOutcome): boolean;
|
|
11
|
+
export declare function settledFailure(result: unknown, outcome: ToolExecutionOutcome): boolean;
|
|
12
|
+
export interface ExecuteCallOptions {
|
|
13
|
+
runId: string;
|
|
14
|
+
approve?: ApproveFn | undefined;
|
|
15
|
+
signal?: BareAbortSignal | undefined;
|
|
16
|
+
denied?: Set<string>;
|
|
17
|
+
sources?: readonly TrustedSource[];
|
|
18
|
+
sourceFacts?: readonly TrustedSourceFacts[];
|
|
19
|
+
pipeline: HookPipeline;
|
|
20
|
+
callId: string;
|
|
21
|
+
effect?: ToolEffect | undefined;
|
|
22
|
+
skipApprovals?: boolean;
|
|
23
|
+
preflight?: ToolPreflight;
|
|
24
|
+
onToolActivity?: ToolActivityFn;
|
|
25
|
+
intent?: string | undefined;
|
|
26
|
+
display?: ToolCallDisplay | undefined;
|
|
27
|
+
}
|
|
28
|
+
export interface ExecutedCall {
|
|
29
|
+
readonly result: unknown;
|
|
30
|
+
readonly outcome: ToolExecutionOutcome;
|
|
31
|
+
readonly executed: boolean;
|
|
32
|
+
}
|
|
33
|
+
export type { ToolPreflight };
|
|
34
|
+
interface ToolPolicyOptions extends Pick<ExecuteCallOptions, 'sources' | 'sourceFacts' | 'denied' | 'effect' | 'skipApprovals'> {
|
|
35
|
+
key?: string;
|
|
36
|
+
}
|
|
37
|
+
export declare function preflightToolCall(tool: ToolDefinition, args: Record<string, unknown>, ctx: ToolExecuteContext, options?: ToolPolicyOptions): ToolPreflight;
|
|
38
|
+
export declare function inspectToolPolicy(tool: ToolDefinition, args: Record<string, unknown>, ctx: ToolExecuteContext, options?: ToolPolicyOptions): ToolPreflightFacts;
|
|
39
|
+
type ReadyToolPreflight = Extract<ToolPreflight, {
|
|
40
|
+
status: 'ready';
|
|
41
|
+
}>;
|
|
42
|
+
export declare function readyPreflightFromFacts(facts: ToolPreflightFacts): ReadyToolPreflight;
|
|
43
|
+
export declare function executeCall(tool: ToolDefinition, args: Record<string, unknown>, ctx: ToolExecuteContext, { runId, approve, signal, denied, sources, sourceFacts, pipeline, callId, effect: classified, skipApprovals, preflight: prepared, onToolActivity, intent, display }: ExecuteCallOptions): Promise<ExecutedCall>;
|
|
44
|
+
export interface ResolvedCall {
|
|
45
|
+
call: ToolCallWithCall;
|
|
46
|
+
tool?: ToolDefinition;
|
|
47
|
+
args: Record<string, unknown>;
|
|
48
|
+
rawCall?: string;
|
|
49
|
+
intent?: string;
|
|
50
|
+
rejection?: HookRejection;
|
|
51
|
+
}
|
|
52
|
+
export interface ResolveToolCallsOptions {
|
|
53
|
+
runId: string;
|
|
54
|
+
agentId: string | null;
|
|
55
|
+
pipeline: HookPipeline;
|
|
56
|
+
skillNames: readonly string[];
|
|
57
|
+
sandboxMode: boolean;
|
|
58
|
+
signal?: BareAbortSignal | undefined;
|
|
59
|
+
}
|
|
60
|
+
export declare function resolveToolCalls(toolCalls: ToolCallWithCall[], enabled: readonly ToolDefinition[], { runId, agentId, pipeline, skillNames, sandboxMode, signal }: ResolveToolCallsOptions): Promise<ResolvedCall[]>;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { MAX_BLOCKED_REPEATS, ToolPolicyChanged, toolCallKey, toolDeniedResult } from '../../machine/index.js';
|
|
2
|
+
import { RecoveryRequired } from '../../helpers/recovery-required.js';
|
|
3
|
+
import { AbortedError, raceAbort } from '../../helpers/race-abort.js';
|
|
4
|
+
import { errorCode, errorMessage, isRecord } from '../../guards.js';
|
|
5
|
+
import { detachJson } from '../../helpers/detach-json.js';
|
|
6
|
+
import { clipRawCall, normalizeToolArgs } from '../../helpers/tool-call-markup.js';
|
|
7
|
+
import { takeToolIntent } from '../../helpers/tool-intent.js';
|
|
8
|
+
import { HookPipeline } from '../../hooks/pipeline.js';
|
|
9
|
+
import { hlog } from '../../log.js';
|
|
10
|
+
import { guardToolCallWithFacts } from '../../tools/outbound-guard.js';
|
|
11
|
+
export { MAX_BLOCKED_REPEATS, toolCallKey };
|
|
12
|
+
function validationMessage(result) {
|
|
13
|
+
return isRecord(result) && 'error' in result ? result.error : result;
|
|
14
|
+
}
|
|
15
|
+
const RETRYABLE_STATUS = new Set([408, 429]);
|
|
16
|
+
const RETRYABLE_CODE = new Set(['timeout', 'transport', 'busy']);
|
|
17
|
+
export function callSucceeded(result, outcome) {
|
|
18
|
+
if (outcome.kind !== 'success') {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const exitCode = result === null || result === undefined ? undefined : Reflect.get(Object(result), 'exitCode');
|
|
22
|
+
return typeof exitCode !== 'number' || exitCode === 0;
|
|
23
|
+
}
|
|
24
|
+
function retryHints(result) {
|
|
25
|
+
if (!isRecord(result) || Array.isArray(result)) {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
const hints = {};
|
|
29
|
+
if (typeof result.status === 'number') {
|
|
30
|
+
hints.status = result.status;
|
|
31
|
+
}
|
|
32
|
+
if (typeof result.code === 'string') {
|
|
33
|
+
hints.code = result.code;
|
|
34
|
+
}
|
|
35
|
+
return hints;
|
|
36
|
+
}
|
|
37
|
+
export function settledFailure(result, outcome) {
|
|
38
|
+
if (outcome.kind === 'denied' || callSucceeded(result, outcome)) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (outcome.kind === 'error' && outcome.error !== undefined) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const { code, status } = retryHints(result);
|
|
45
|
+
if (code !== undefined && RETRYABLE_CODE.has(code)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
return status === undefined || (status < 500 && !RETRYABLE_STATUS.has(status));
|
|
49
|
+
}
|
|
50
|
+
export function preflightToolCall(tool, args, ctx, options = {}) {
|
|
51
|
+
const facts = inspectToolPolicy(tool, args, ctx, options);
|
|
52
|
+
if (facts.validationError) {
|
|
53
|
+
return {
|
|
54
|
+
status: 'blocked',
|
|
55
|
+
reason: 'validation',
|
|
56
|
+
result: { error: facts.validationError, guidance: true },
|
|
57
|
+
effect: 'write'
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (facts.refusal) {
|
|
61
|
+
return {
|
|
62
|
+
status: 'blocked',
|
|
63
|
+
reason: 'refusal',
|
|
64
|
+
result: { error: facts.refusal, guidance: true },
|
|
65
|
+
effect: facts.effect
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (facts.deniedKey) {
|
|
69
|
+
return { status: 'denied', result: toolDeniedResult(), key: facts.deniedKey };
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
status: 'ready',
|
|
73
|
+
key: facts.key,
|
|
74
|
+
effect: facts.effect,
|
|
75
|
+
resource: facts.resource,
|
|
76
|
+
autoRun: facts.effect === 'read' || facts.autoApprove || facts.skipApprovals
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export function inspectToolPolicy(tool, args, ctx, options = {}) {
|
|
80
|
+
const validationError = tool.validate?.(detachJson(args), ctx) || null;
|
|
81
|
+
const key = options.key ?? toolCallKey(tool.name, args);
|
|
82
|
+
const refusal = validationError
|
|
83
|
+
? null
|
|
84
|
+
: guardToolCallWithFacts(tool.name, args, {
|
|
85
|
+
sources: options.sources ?? [],
|
|
86
|
+
sourceFacts: options.sourceFacts,
|
|
87
|
+
externalWrite: tool.externalWrite === true
|
|
88
|
+
});
|
|
89
|
+
const deniedKey = !validationError && !refusal && options.denied?.has(key) ? key : null;
|
|
90
|
+
const effect = options.effect ?? (validationError ? 'write' : tool.effect(detachJson(args), ctx));
|
|
91
|
+
const resource = validationError || deniedKey ? null : detachJson(tool.resource?.(detachJson(args), ctx) ?? null);
|
|
92
|
+
return {
|
|
93
|
+
key,
|
|
94
|
+
validationError,
|
|
95
|
+
refusal,
|
|
96
|
+
deniedKey,
|
|
97
|
+
effect,
|
|
98
|
+
autoApprove: tool.autoApprove === true,
|
|
99
|
+
skipApprovals: options.skipApprovals === true,
|
|
100
|
+
resource
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export function readyPreflightFromFacts(facts) {
|
|
104
|
+
if (facts.validationError || facts.refusal || facts.deniedKey) {
|
|
105
|
+
throw new Error('tool policy facts are not ready for execution');
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
status: 'ready',
|
|
109
|
+
key: facts.key,
|
|
110
|
+
effect: facts.effect,
|
|
111
|
+
resource: facts.resource,
|
|
112
|
+
autoRun: facts.effect === 'read' || facts.autoApprove || facts.skipApprovals
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function decisionFor(options) {
|
|
116
|
+
if (options.autoApprove || options.effect === 'read') {
|
|
117
|
+
return 'auto';
|
|
118
|
+
}
|
|
119
|
+
if (options.skipApprovals === true) {
|
|
120
|
+
return 'skip-all';
|
|
121
|
+
}
|
|
122
|
+
if (options.approved) {
|
|
123
|
+
return 'allowed';
|
|
124
|
+
}
|
|
125
|
+
return 'denied';
|
|
126
|
+
}
|
|
127
|
+
export async function executeCall(tool, args, ctx, { runId, approve, signal, denied, sources = [], sourceFacts = [], pipeline, callId, effect: classified, skipApprovals, preflight: prepared, onToolActivity, intent, display }) {
|
|
128
|
+
const preflight = prepared ??
|
|
129
|
+
preflightToolCall(tool, args, ctx, {
|
|
130
|
+
sources,
|
|
131
|
+
sourceFacts,
|
|
132
|
+
denied,
|
|
133
|
+
effect: classified,
|
|
134
|
+
skipApprovals
|
|
135
|
+
});
|
|
136
|
+
if (preflight.status === 'blocked' && preflight.reason === 'validation') {
|
|
137
|
+
hlog('tool rejected: arguments failed validation', { tool: tool.name });
|
|
138
|
+
return {
|
|
139
|
+
result: preflight.result,
|
|
140
|
+
outcome: { kind: 'guidance', message: String(validationMessage(preflight.result)) },
|
|
141
|
+
executed: false
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (preflight.status === 'blocked') {
|
|
145
|
+
hlog('tool refused: unverifiable value in payload', { tool: tool.name });
|
|
146
|
+
onToolActivity?.({
|
|
147
|
+
type: 'tool-activity',
|
|
148
|
+
name: tool.name,
|
|
149
|
+
args,
|
|
150
|
+
resource: detachJson(tool.resource?.(detachJson(args), ctx) ?? null),
|
|
151
|
+
effect: preflight.effect,
|
|
152
|
+
decision: 'blocked'
|
|
153
|
+
});
|
|
154
|
+
return {
|
|
155
|
+
result: preflight.result,
|
|
156
|
+
outcome: { kind: 'blocked', reason: String(validationMessage(preflight.result)) },
|
|
157
|
+
executed: false
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (preflight.status === 'denied') {
|
|
161
|
+
hlog('tool denied already, not asking again', { tool: tool.name });
|
|
162
|
+
return {
|
|
163
|
+
result: preflight.result,
|
|
164
|
+
outcome: { kind: 'denied', reason: toolDeniedResult().error },
|
|
165
|
+
executed: false
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const { key, effect, resource, autoRun } = preflight;
|
|
169
|
+
hlog('tool decision', {
|
|
170
|
+
tool: tool.name,
|
|
171
|
+
effect,
|
|
172
|
+
auto: autoRun,
|
|
173
|
+
asks: Boolean(approve)
|
|
174
|
+
});
|
|
175
|
+
let approved = autoRun;
|
|
176
|
+
if (!approved && approve) {
|
|
177
|
+
approved = await raceAbort(approve({
|
|
178
|
+
name: tool.name,
|
|
179
|
+
arguments: detachJson(args),
|
|
180
|
+
resource: detachJson(resource),
|
|
181
|
+
effect,
|
|
182
|
+
intent: intent ?? null,
|
|
183
|
+
display: detachJson(display ?? null)
|
|
184
|
+
}), signal);
|
|
185
|
+
}
|
|
186
|
+
hlog(`tool ${approved ? 'approved' : 'denied'}`, { tool: tool.name });
|
|
187
|
+
const decision = decisionFor({
|
|
188
|
+
autoApprove: tool.autoApprove === true,
|
|
189
|
+
effect,
|
|
190
|
+
skipApprovals,
|
|
191
|
+
approved
|
|
192
|
+
});
|
|
193
|
+
onToolActivity?.({ type: 'tool-activity', name: tool.name, args, resource, effect, decision });
|
|
194
|
+
if (!approved) {
|
|
195
|
+
denied?.add(key);
|
|
196
|
+
return {
|
|
197
|
+
result: toolDeniedResult(),
|
|
198
|
+
outcome: { kind: 'denied', reason: toolDeniedResult().error },
|
|
199
|
+
executed: false
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (signal?.aborted) {
|
|
203
|
+
throw new AbortedError();
|
|
204
|
+
}
|
|
205
|
+
let output;
|
|
206
|
+
let outcome = { kind: 'success' };
|
|
207
|
+
const toolArgs = detachJson(args);
|
|
208
|
+
try {
|
|
209
|
+
output = await tool.execute(toolArgs, { ...ctx, signal, runId, callId, approval: decision });
|
|
210
|
+
hlog('tool done', { tool: tool.name });
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
if (err instanceof RecoveryRequired || err instanceof ToolPolicyChanged) {
|
|
214
|
+
throw err;
|
|
215
|
+
}
|
|
216
|
+
if (err instanceof AbortedError || signal?.aborted) {
|
|
217
|
+
throw new AbortedError();
|
|
218
|
+
}
|
|
219
|
+
const message = errorMessage(err);
|
|
220
|
+
hlog('tool failed', { tool: tool.name, err: message });
|
|
221
|
+
output = { error: message, code: errorCode(err) };
|
|
222
|
+
outcome = { kind: 'error', message, error: err };
|
|
223
|
+
}
|
|
224
|
+
const folded = await pipeline.runPostToolFold({
|
|
225
|
+
runId,
|
|
226
|
+
callId,
|
|
227
|
+
toolName: tool.name,
|
|
228
|
+
args: toolArgs,
|
|
229
|
+
effect,
|
|
230
|
+
resource,
|
|
231
|
+
toolset: tool.toolset,
|
|
232
|
+
agentId: ctx.agentId ?? null,
|
|
233
|
+
result: output,
|
|
234
|
+
outcome,
|
|
235
|
+
executed: true
|
|
236
|
+
});
|
|
237
|
+
return { result: detachJson(folded.result), outcome: folded.outcome ?? outcome, executed: true };
|
|
238
|
+
}
|
|
239
|
+
export async function resolveToolCalls(toolCalls, enabled, { runId, agentId, pipeline, skillNames, sandboxMode, signal }) {
|
|
240
|
+
const resolved = [];
|
|
241
|
+
for (const call of toolCalls) {
|
|
242
|
+
if (signal?.aborted) {
|
|
243
|
+
throw new AbortedError();
|
|
244
|
+
}
|
|
245
|
+
const tool = enabled.find((t) => t.name === call.name);
|
|
246
|
+
const rawCall = call.raw === undefined ? {} : { rawCall: clipRawCall(call.raw) };
|
|
247
|
+
if (!tool) {
|
|
248
|
+
resolved.push({ call, args: detachJson(takeToolIntent(call.arguments).args), ...rawCall });
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const taken = takeToolIntent(call.arguments);
|
|
252
|
+
const intent = taken.intent ? { intent: taken.intent } : {};
|
|
253
|
+
const args = normalizeToolArgs(taken.args, Object.keys(tool.parameters.properties));
|
|
254
|
+
const fold = await pipeline.runPreToolFold({
|
|
255
|
+
runId,
|
|
256
|
+
callId: call.id,
|
|
257
|
+
toolName: tool.name,
|
|
258
|
+
args,
|
|
259
|
+
agentId,
|
|
260
|
+
skillNames,
|
|
261
|
+
parameters: tool.parameters,
|
|
262
|
+
sandboxMode,
|
|
263
|
+
signal
|
|
264
|
+
});
|
|
265
|
+
resolved.push(fold.ok
|
|
266
|
+
? { call, tool, args: detachJson(fold.args), ...rawCall, ...intent }
|
|
267
|
+
: { call, tool, args: detachJson(args), rejection: fold.rejection, ...rawCall, ...intent });
|
|
268
|
+
}
|
|
269
|
+
return resolved;
|
|
270
|
+
}
|