@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.
Files changed (201) hide show
  1. package/README.md +224 -0
  2. package/dist-lib/index.d.ts +55 -3
  3. package/dist-lib/index.js +721 -236
  4. package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.d.ts +1 -0
  5. package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.js +10 -0
  6. package/dist-lib/lib/capabilities/completion-engine/actors/answering.d.ts +3 -0
  7. package/dist-lib/lib/capabilities/completion-engine/actors/answering.js +17 -0
  8. package/dist-lib/lib/capabilities/completion-engine/actors/approval.d.ts +4 -0
  9. package/dist-lib/lib/capabilities/completion-engine/actors/approval.js +29 -0
  10. package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.d.ts +4 -0
  11. package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.js +73 -0
  12. package/dist-lib/lib/capabilities/completion-engine/actors/execute.d.ts +41 -0
  13. package/dist-lib/lib/capabilities/completion-engine/actors/execute.js +49 -0
  14. package/dist-lib/lib/capabilities/completion-engine/actors/executing.d.ts +3 -0
  15. package/dist-lib/lib/capabilities/completion-engine/actors/executing.js +102 -0
  16. package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.d.ts +47 -0
  17. package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.js +87 -0
  18. package/dist-lib/lib/capabilities/completion-engine/actors/finishing.d.ts +2 -0
  19. package/dist-lib/lib/capabilities/completion-engine/actors/finishing.js +32 -0
  20. package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.d.ts +3 -0
  21. package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.js +75 -0
  22. package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.d.ts +3 -0
  23. package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.js +152 -0
  24. package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.d.ts +3 -0
  25. package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.js +94 -0
  26. package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.d.ts +9 -0
  27. package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.js +11 -0
  28. package/dist-lib/lib/capabilities/completion-engine/actors/preloading.d.ts +3 -0
  29. package/dist-lib/lib/capabilities/completion-engine/actors/preloading.js +114 -0
  30. package/dist-lib/lib/capabilities/completion-engine/actors/preparing.d.ts +3 -0
  31. package/dist-lib/lib/capabilities/completion-engine/actors/preparing.js +198 -0
  32. package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.d.ts +3 -0
  33. package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.js +71 -0
  34. package/dist-lib/lib/capabilities/completion-engine/actors/rejection.d.ts +18 -0
  35. package/dist-lib/lib/capabilities/completion-engine/actors/rejection.js +70 -0
  36. package/dist-lib/lib/capabilities/completion-engine/actors/resolving.d.ts +3 -0
  37. package/dist-lib/lib/capabilities/completion-engine/actors/resolving.js +60 -0
  38. package/dist-lib/lib/capabilities/completion-engine/actors/stream.d.ts +21 -0
  39. package/dist-lib/lib/capabilities/completion-engine/actors/stream.js +48 -0
  40. package/dist-lib/lib/capabilities/completion-engine/actors/streaming.d.ts +3 -0
  41. package/dist-lib/lib/capabilities/completion-engine/actors/streaming.js +156 -0
  42. package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.d.ts +7 -0
  43. package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.js +61 -0
  44. package/dist-lib/lib/capabilities/completion-engine/driver.d.ts +25 -0
  45. package/dist-lib/lib/capabilities/completion-engine/driver.js +553 -0
  46. package/dist-lib/lib/capabilities/completion-engine/durable.d.ts +61 -0
  47. package/dist-lib/lib/capabilities/completion-engine/durable.js +150 -0
  48. package/dist-lib/lib/capabilities/completion-engine/operations.d.ts +6 -0
  49. package/dist-lib/lib/capabilities/completion-engine/operations.js +239 -0
  50. package/dist-lib/lib/capabilities/completion-engine/ports.d.ts +3 -0
  51. package/dist-lib/lib/capabilities/completion-engine/ports.js +267 -0
  52. package/dist-lib/lib/capabilities/completion-engine/session.d.ts +76 -0
  53. package/dist-lib/lib/capabilities/completion-engine/session.js +70 -0
  54. package/dist-lib/lib/capabilities/completion-loop/index.d.ts +80 -0
  55. package/dist-lib/lib/capabilities/completion-loop/index.js +158 -0
  56. package/dist-lib/lib/capabilities/completion-loop/metrics.d.ts +5 -0
  57. package/dist-lib/lib/capabilities/completion-loop/metrics.js +29 -0
  58. package/dist-lib/lib/capabilities/completion-loop/round.d.ts +33 -0
  59. package/dist-lib/lib/capabilities/completion-loop/round.js +353 -0
  60. package/dist-lib/lib/capabilities/completion-loop/tool-execution.d.ts +60 -0
  61. package/dist-lib/lib/capabilities/completion-loop/tool-execution.js +270 -0
  62. package/dist-lib/lib/capabilities/completion-loop/turns.d.ts +23 -0
  63. package/dist-lib/lib/capabilities/completion-loop/turns.js +112 -0
  64. package/dist-lib/lib/capabilities/completion-runtime.d.ts +46 -0
  65. package/dist-lib/lib/capabilities/completion-runtime.js +74 -0
  66. package/dist-lib/lib/capabilities/completion.d.ts +18 -335
  67. package/dist-lib/lib/capabilities/completion.js +20 -1301
  68. package/dist-lib/lib/capabilities/download.d.ts +1 -1
  69. package/dist-lib/lib/capabilities/download.js +7 -2
  70. package/dist-lib/lib/capabilities/invoke.js +5 -3
  71. package/dist-lib/lib/capabilities/tool-plan.d.ts +35 -0
  72. package/dist-lib/lib/capabilities/tool-plan.js +66 -0
  73. package/dist-lib/lib/capabilities/tool-turns.d.ts +11 -2
  74. package/dist-lib/lib/capabilities/tool-turns.js +30 -2
  75. package/dist-lib/lib/capabilities/types.d.ts +287 -0
  76. package/dist-lib/lib/capabilities/types.js +1 -0
  77. package/dist-lib/lib/completion-lifecycle.d.ts +14 -0
  78. package/dist-lib/lib/completion-lifecycle.js +126 -0
  79. package/dist-lib/lib/connect.d.ts +10 -3
  80. package/dist-lib/lib/connect.js +329 -23
  81. package/dist-lib/lib/contract-types.d.ts +19 -2
  82. package/dist-lib/lib/harness-lifecycle.d.ts +66 -0
  83. package/dist-lib/lib/harness-lifecycle.js +129 -0
  84. package/dist-lib/lib/helpers/detach-json.d.ts +1 -0
  85. package/dist-lib/lib/helpers/detach-json.js +146 -0
  86. package/dist-lib/lib/helpers/markup-scanner.d.ts +26 -0
  87. package/dist-lib/lib/helpers/markup-scanner.js +156 -0
  88. package/dist-lib/lib/helpers/markup-stream-filter.js +138 -35
  89. package/dist-lib/lib/helpers/race-abort.d.ts +9 -4
  90. package/dist-lib/lib/helpers/race-abort.js +2 -7
  91. package/dist-lib/lib/helpers/recovery-required.d.ts +1 -0
  92. package/dist-lib/lib/helpers/recovery-required.js +1 -0
  93. package/dist-lib/lib/helpers/run-outcome.d.ts +10 -2
  94. package/dist-lib/lib/helpers/run-outcome.js +8 -5
  95. package/dist-lib/lib/helpers/tool-call-markup.d.ts +2 -0
  96. package/dist-lib/lib/helpers/tool-call-markup.js +197 -82
  97. package/dist-lib/lib/hooks/built-in/arg-repair.d.ts +1 -0
  98. package/dist-lib/lib/hooks/built-in/arg-repair.js +1 -0
  99. package/dist-lib/lib/hooks/built-in/egress.d.ts +1 -0
  100. package/dist-lib/lib/hooks/built-in/egress.js +2 -1
  101. package/dist-lib/lib/hooks/built-in/sandbox.d.ts +1 -0
  102. package/dist-lib/lib/hooks/built-in/sandbox.js +6 -1
  103. package/dist-lib/lib/hooks/built-in/skill-catalog.d.ts +1 -0
  104. package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
  105. package/dist-lib/lib/hooks/pipeline.d.ts +4 -1
  106. package/dist-lib/lib/hooks/pipeline.js +24 -2
  107. package/dist-lib/lib/hooks/run.d.ts +2 -1
  108. package/dist-lib/lib/hooks/run.js +19 -7
  109. package/dist-lib/lib/hooks/types.d.ts +17 -6
  110. package/dist-lib/lib/machine/completion/actions/approval.d.ts +3 -0
  111. package/dist-lib/lib/machine/completion/actions/approval.js +16 -0
  112. package/dist-lib/lib/machine/completion/actions/completion.d.ts +23 -0
  113. package/dist-lib/lib/machine/completion/actions/completion.js +142 -0
  114. package/dist-lib/lib/machine/completion/actions/skills.d.ts +9 -0
  115. package/dist-lib/lib/machine/completion/actions/skills.js +83 -0
  116. package/dist-lib/lib/machine/completion/actions/tools.d.ts +16 -0
  117. package/dist-lib/lib/machine/completion/actions/tools.js +173 -0
  118. package/dist-lib/lib/machine/completion/context.d.ts +9 -0
  119. package/dist-lib/lib/machine/completion/context.js +44 -0
  120. package/dist-lib/lib/machine/completion/contract.d.ts +6 -0
  121. package/dist-lib/lib/machine/completion/contract.js +1 -0
  122. package/dist-lib/lib/machine/completion/guards/retry.d.ts +9 -0
  123. package/dist-lib/lib/machine/completion/guards/retry.js +25 -0
  124. package/dist-lib/lib/machine/completion/implementations.d.ts +81 -0
  125. package/dist-lib/lib/machine/completion/implementations.js +152 -0
  126. package/dist-lib/lib/machine/completion/machine.d.ts +375 -0
  127. package/dist-lib/lib/machine/completion/machine.js +511 -0
  128. package/dist-lib/lib/machine/completion/ports.d.ts +65 -0
  129. package/dist-lib/lib/machine/completion/ports.js +224 -0
  130. package/dist-lib/lib/machine/completion/rules.d.ts +39 -0
  131. package/dist-lib/lib/machine/completion/rules.js +115 -0
  132. package/dist-lib/lib/machine/completion/types.d.ts +493 -0
  133. package/dist-lib/lib/machine/completion/types.js +1 -0
  134. package/dist-lib/lib/machine/engine.d.ts +93 -0
  135. package/dist-lib/lib/machine/engine.js +483 -0
  136. package/dist-lib/lib/machine/errors.d.ts +44 -0
  137. package/dist-lib/lib/machine/errors.js +373 -0
  138. package/dist-lib/lib/machine/guards.d.ts +2 -0
  139. package/dist-lib/lib/machine/guards.js +6 -0
  140. package/dist-lib/lib/machine/index.d.ts +20 -0
  141. package/dist-lib/lib/machine/index.js +11 -0
  142. package/dist-lib/lib/machine/replay-output.d.ts +24 -0
  143. package/dist-lib/lib/machine/replay-output.js +50 -0
  144. package/dist-lib/lib/machine/stable-stringify.d.ts +1 -0
  145. package/dist-lib/lib/machine/stable-stringify.js +11 -0
  146. package/dist-lib/lib/models/model-pool.d.ts +14 -6
  147. package/dist-lib/lib/models/model-pool.js +95 -30
  148. package/dist-lib/lib/run-log/history-codec.d.ts +23 -0
  149. package/dist-lib/lib/run-log/history-codec.js +973 -0
  150. package/dist-lib/lib/run-log/patch.d.ts +6 -0
  151. package/dist-lib/lib/run-log/patch.js +93 -0
  152. package/dist-lib/lib/run-log/persisted-actor.d.ts +18 -0
  153. package/dist-lib/lib/run-log/persisted-actor.js +218 -0
  154. package/dist-lib/lib/run-log/protocol-types.d.ts +153 -0
  155. package/dist-lib/lib/run-log/protocol-types.js +1 -0
  156. package/dist-lib/lib/run-log/protocol.d.ts +27 -0
  157. package/dist-lib/lib/run-log/protocol.js +1056 -0
  158. package/dist-lib/lib/run-log/replay.d.ts +25 -0
  159. package/dist-lib/lib/run-log/replay.js +129 -0
  160. package/dist-lib/lib/run-log/run-journal.d.ts +125 -0
  161. package/dist-lib/lib/run-log/run-journal.js +783 -0
  162. package/dist-lib/lib/run-log-inspect.d.ts +25 -0
  163. package/dist-lib/lib/run-log-inspect.js +627 -0
  164. package/dist-lib/lib/run-log-protocol.d.ts +2 -0
  165. package/dist-lib/lib/run-log-protocol.js +1 -0
  166. package/dist-lib/lib/run-log-redact.d.ts +27 -0
  167. package/dist-lib/lib/run-log-redact.js +1107 -0
  168. package/dist-lib/lib/serve.js +307 -11
  169. package/dist-lib/lib/spawn.js +3 -2
  170. package/dist-lib/lib/tool-call-display.d.ts +1 -21
  171. package/dist-lib/lib/tools/exec/adapters/python/wheels.js +2 -1
  172. package/dist-lib/lib/tools/exec.d.ts +2 -1
  173. package/dist-lib/lib/tools/exec.js +57 -2
  174. package/dist-lib/lib/tools/generate-image.js +1 -1
  175. package/dist-lib/lib/tools/generate-music.js +1 -1
  176. package/dist-lib/lib/tools/http-request.d.ts +2 -1
  177. package/dist-lib/lib/tools/http-request.js +2 -1
  178. package/dist-lib/lib/tools/mcp-call.js +8 -1
  179. package/dist-lib/lib/tools/operations/builders.d.ts +1 -0
  180. package/dist-lib/lib/tools/operations/builders.js +1 -0
  181. package/dist-lib/lib/tools/operations/compile.js +11 -1
  182. package/dist-lib/lib/tools/operations/manifest.js +1 -0
  183. package/dist-lib/lib/tools/outbound-guard.d.ts +8 -6
  184. package/dist-lib/lib/tools/outbound-guard.js +25 -8
  185. package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
  186. package/dist-lib/lib/tools/skills/catalog.js +35 -0
  187. package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +1 -0
  188. package/dist-lib/lib/tools/skills/cli-schema.js +25 -0
  189. package/dist-lib/lib/wire.d.ts +5 -0
  190. package/dist-lib/lib/wire.js +128 -5
  191. package/dist-lib/schema/harness.js +20 -5
  192. package/dist-lib/spec/hrpc/messages.js +95 -15
  193. package/dist-lib/spec/hyperschema/index.js +95 -15
  194. package/dist-lib/spec/hyperschema/schema.json +69 -1
  195. package/dist-lib/spec/hyperschema/types.d.ts +10 -3
  196. package/package.json +20 -2
  197. package/patches/@qvac%2Finference@0.20.0.patch +67 -0
  198. package/patches/bare-stow@0.1.5.patch +68 -0
  199. package/patches/hypercore-blob-server@1.15.0.patch +15 -0
  200. package/patches/localwatch@1.4.3.patch +13 -0
  201. 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
+ }