@qvac/harness 0.7.0 → 1.0.0-rc.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.
Files changed (204) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +239 -2
  3. package/dist-lib/index.d.ts +58 -3
  4. package/dist-lib/index.js +736 -239
  5. package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.d.ts +1 -0
  6. package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.js +10 -0
  7. package/dist-lib/lib/capabilities/completion-engine/actors/answering.d.ts +3 -0
  8. package/dist-lib/lib/capabilities/completion-engine/actors/answering.js +17 -0
  9. package/dist-lib/lib/capabilities/completion-engine/actors/approval.d.ts +4 -0
  10. package/dist-lib/lib/capabilities/completion-engine/actors/approval.js +29 -0
  11. package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.d.ts +4 -0
  12. package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.js +73 -0
  13. package/dist-lib/lib/capabilities/completion-engine/actors/execute.d.ts +41 -0
  14. package/dist-lib/lib/capabilities/completion-engine/actors/execute.js +49 -0
  15. package/dist-lib/lib/capabilities/completion-engine/actors/executing.d.ts +3 -0
  16. package/dist-lib/lib/capabilities/completion-engine/actors/executing.js +102 -0
  17. package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.d.ts +47 -0
  18. package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.js +87 -0
  19. package/dist-lib/lib/capabilities/completion-engine/actors/finishing.d.ts +2 -0
  20. package/dist-lib/lib/capabilities/completion-engine/actors/finishing.js +32 -0
  21. package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.d.ts +3 -0
  22. package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.js +75 -0
  23. package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.d.ts +3 -0
  24. package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.js +152 -0
  25. package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.d.ts +3 -0
  26. package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.js +94 -0
  27. package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.d.ts +9 -0
  28. package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.js +11 -0
  29. package/dist-lib/lib/capabilities/completion-engine/actors/preloading.d.ts +3 -0
  30. package/dist-lib/lib/capabilities/completion-engine/actors/preloading.js +114 -0
  31. package/dist-lib/lib/capabilities/completion-engine/actors/preparing.d.ts +3 -0
  32. package/dist-lib/lib/capabilities/completion-engine/actors/preparing.js +198 -0
  33. package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.d.ts +3 -0
  34. package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.js +71 -0
  35. package/dist-lib/lib/capabilities/completion-engine/actors/rejection.d.ts +18 -0
  36. package/dist-lib/lib/capabilities/completion-engine/actors/rejection.js +70 -0
  37. package/dist-lib/lib/capabilities/completion-engine/actors/resolving.d.ts +3 -0
  38. package/dist-lib/lib/capabilities/completion-engine/actors/resolving.js +60 -0
  39. package/dist-lib/lib/capabilities/completion-engine/actors/stream.d.ts +21 -0
  40. package/dist-lib/lib/capabilities/completion-engine/actors/stream.js +48 -0
  41. package/dist-lib/lib/capabilities/completion-engine/actors/streaming.d.ts +3 -0
  42. package/dist-lib/lib/capabilities/completion-engine/actors/streaming.js +156 -0
  43. package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.d.ts +7 -0
  44. package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.js +61 -0
  45. package/dist-lib/lib/capabilities/completion-engine/driver.d.ts +25 -0
  46. package/dist-lib/lib/capabilities/completion-engine/driver.js +553 -0
  47. package/dist-lib/lib/capabilities/completion-engine/durable.d.ts +61 -0
  48. package/dist-lib/lib/capabilities/completion-engine/durable.js +150 -0
  49. package/dist-lib/lib/capabilities/completion-engine/operations.d.ts +6 -0
  50. package/dist-lib/lib/capabilities/completion-engine/operations.js +239 -0
  51. package/dist-lib/lib/capabilities/completion-engine/ports.d.ts +3 -0
  52. package/dist-lib/lib/capabilities/completion-engine/ports.js +267 -0
  53. package/dist-lib/lib/capabilities/completion-engine/session.d.ts +76 -0
  54. package/dist-lib/lib/capabilities/completion-engine/session.js +70 -0
  55. package/dist-lib/lib/capabilities/completion-loop/index.d.ts +80 -0
  56. package/dist-lib/lib/capabilities/completion-loop/index.js +158 -0
  57. package/dist-lib/lib/capabilities/completion-loop/metrics.d.ts +5 -0
  58. package/dist-lib/lib/capabilities/completion-loop/metrics.js +29 -0
  59. package/dist-lib/lib/capabilities/completion-loop/round.d.ts +33 -0
  60. package/dist-lib/lib/capabilities/completion-loop/round.js +353 -0
  61. package/dist-lib/lib/capabilities/completion-loop/tool-execution.d.ts +60 -0
  62. package/dist-lib/lib/capabilities/completion-loop/tool-execution.js +270 -0
  63. package/dist-lib/lib/capabilities/completion-loop/turns.d.ts +23 -0
  64. package/dist-lib/lib/capabilities/completion-loop/turns.js +112 -0
  65. package/dist-lib/lib/capabilities/completion-runtime.d.ts +46 -0
  66. package/dist-lib/lib/capabilities/completion-runtime.js +74 -0
  67. package/dist-lib/lib/capabilities/completion.d.ts +18 -335
  68. package/dist-lib/lib/capabilities/completion.js +20 -1301
  69. package/dist-lib/lib/capabilities/download.d.ts +1 -1
  70. package/dist-lib/lib/capabilities/download.js +7 -2
  71. package/dist-lib/lib/capabilities/invoke.js +5 -3
  72. package/dist-lib/lib/capabilities/tool-plan.d.ts +35 -0
  73. package/dist-lib/lib/capabilities/tool-plan.js +66 -0
  74. package/dist-lib/lib/capabilities/tool-turns.d.ts +11 -2
  75. package/dist-lib/lib/capabilities/tool-turns.js +30 -2
  76. package/dist-lib/lib/capabilities/types.d.ts +287 -0
  77. package/dist-lib/lib/capabilities/types.js +1 -0
  78. package/dist-lib/lib/completion-lifecycle.d.ts +14 -0
  79. package/dist-lib/lib/completion-lifecycle.js +126 -0
  80. package/dist-lib/lib/connect.d.ts +10 -3
  81. package/dist-lib/lib/connect.js +329 -23
  82. package/dist-lib/lib/contract-types.d.ts +19 -2
  83. package/dist-lib/lib/harness-lifecycle.d.ts +66 -0
  84. package/dist-lib/lib/harness-lifecycle.js +129 -0
  85. package/dist-lib/lib/helpers/detach-json.d.ts +1 -0
  86. package/dist-lib/lib/helpers/detach-json.js +146 -0
  87. package/dist-lib/lib/helpers/markup-scanner.d.ts +26 -0
  88. package/dist-lib/lib/helpers/markup-scanner.js +156 -0
  89. package/dist-lib/lib/helpers/markup-stream-filter.js +138 -35
  90. package/dist-lib/lib/helpers/race-abort.d.ts +9 -4
  91. package/dist-lib/lib/helpers/race-abort.js +2 -7
  92. package/dist-lib/lib/helpers/recovery-required.d.ts +1 -0
  93. package/dist-lib/lib/helpers/recovery-required.js +1 -0
  94. package/dist-lib/lib/helpers/run-outcome.d.ts +10 -2
  95. package/dist-lib/lib/helpers/run-outcome.js +8 -5
  96. package/dist-lib/lib/helpers/tool-call-markup.d.ts +2 -0
  97. package/dist-lib/lib/helpers/tool-call-markup.js +197 -82
  98. package/dist-lib/lib/hooks/built-in/arg-repair.d.ts +1 -0
  99. package/dist-lib/lib/hooks/built-in/arg-repair.js +1 -0
  100. package/dist-lib/lib/hooks/built-in/egress.d.ts +1 -0
  101. package/dist-lib/lib/hooks/built-in/egress.js +2 -1
  102. package/dist-lib/lib/hooks/built-in/sandbox.d.ts +1 -0
  103. package/dist-lib/lib/hooks/built-in/sandbox.js +6 -1
  104. package/dist-lib/lib/hooks/built-in/skill-catalog.d.ts +1 -0
  105. package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
  106. package/dist-lib/lib/hooks/pipeline.d.ts +4 -1
  107. package/dist-lib/lib/hooks/pipeline.js +24 -2
  108. package/dist-lib/lib/hooks/run.d.ts +2 -1
  109. package/dist-lib/lib/hooks/run.js +19 -7
  110. package/dist-lib/lib/hooks/types.d.ts +17 -6
  111. package/dist-lib/lib/machine/completion/actions/approval.d.ts +3 -0
  112. package/dist-lib/lib/machine/completion/actions/approval.js +16 -0
  113. package/dist-lib/lib/machine/completion/actions/completion.d.ts +23 -0
  114. package/dist-lib/lib/machine/completion/actions/completion.js +142 -0
  115. package/dist-lib/lib/machine/completion/actions/skills.d.ts +9 -0
  116. package/dist-lib/lib/machine/completion/actions/skills.js +83 -0
  117. package/dist-lib/lib/machine/completion/actions/tools.d.ts +16 -0
  118. package/dist-lib/lib/machine/completion/actions/tools.js +173 -0
  119. package/dist-lib/lib/machine/completion/context.d.ts +9 -0
  120. package/dist-lib/lib/machine/completion/context.js +44 -0
  121. package/dist-lib/lib/machine/completion/contract.d.ts +6 -0
  122. package/dist-lib/lib/machine/completion/contract.js +1 -0
  123. package/dist-lib/lib/machine/completion/guards/retry.d.ts +9 -0
  124. package/dist-lib/lib/machine/completion/guards/retry.js +25 -0
  125. package/dist-lib/lib/machine/completion/implementations.d.ts +81 -0
  126. package/dist-lib/lib/machine/completion/implementations.js +152 -0
  127. package/dist-lib/lib/machine/completion/machine.d.ts +375 -0
  128. package/dist-lib/lib/machine/completion/machine.js +511 -0
  129. package/dist-lib/lib/machine/completion/ports.d.ts +65 -0
  130. package/dist-lib/lib/machine/completion/ports.js +224 -0
  131. package/dist-lib/lib/machine/completion/rules.d.ts +39 -0
  132. package/dist-lib/lib/machine/completion/rules.js +115 -0
  133. package/dist-lib/lib/machine/completion/types.d.ts +493 -0
  134. package/dist-lib/lib/machine/completion/types.js +1 -0
  135. package/dist-lib/lib/machine/engine.d.ts +93 -0
  136. package/dist-lib/lib/machine/engine.js +483 -0
  137. package/dist-lib/lib/machine/errors.d.ts +44 -0
  138. package/dist-lib/lib/machine/errors.js +373 -0
  139. package/dist-lib/lib/machine/guards.d.ts +2 -0
  140. package/dist-lib/lib/machine/guards.js +6 -0
  141. package/dist-lib/lib/machine/index.d.ts +20 -0
  142. package/dist-lib/lib/machine/index.js +11 -0
  143. package/dist-lib/lib/machine/replay-output.d.ts +24 -0
  144. package/dist-lib/lib/machine/replay-output.js +50 -0
  145. package/dist-lib/lib/machine/stable-stringify.d.ts +1 -0
  146. package/dist-lib/lib/machine/stable-stringify.js +11 -0
  147. package/dist-lib/lib/models/model-handle.d.ts +5 -1
  148. package/dist-lib/lib/models/model-handle.js +8 -3
  149. package/dist-lib/lib/models/model-list.d.ts +5 -3
  150. package/dist-lib/lib/models/model-pool.d.ts +18 -7
  151. package/dist-lib/lib/models/model-pool.js +126 -38
  152. package/dist-lib/lib/run-log/history-codec.d.ts +23 -0
  153. package/dist-lib/lib/run-log/history-codec.js +973 -0
  154. package/dist-lib/lib/run-log/patch.d.ts +6 -0
  155. package/dist-lib/lib/run-log/patch.js +93 -0
  156. package/dist-lib/lib/run-log/persisted-actor.d.ts +18 -0
  157. package/dist-lib/lib/run-log/persisted-actor.js +218 -0
  158. package/dist-lib/lib/run-log/protocol-types.d.ts +153 -0
  159. package/dist-lib/lib/run-log/protocol-types.js +1 -0
  160. package/dist-lib/lib/run-log/protocol.d.ts +27 -0
  161. package/dist-lib/lib/run-log/protocol.js +1056 -0
  162. package/dist-lib/lib/run-log/replay.d.ts +25 -0
  163. package/dist-lib/lib/run-log/replay.js +129 -0
  164. package/dist-lib/lib/run-log/run-journal.d.ts +125 -0
  165. package/dist-lib/lib/run-log/run-journal.js +783 -0
  166. package/dist-lib/lib/run-log-inspect.d.ts +25 -0
  167. package/dist-lib/lib/run-log-inspect.js +627 -0
  168. package/dist-lib/lib/run-log-protocol.d.ts +2 -0
  169. package/dist-lib/lib/run-log-protocol.js +1 -0
  170. package/dist-lib/lib/run-log-redact.d.ts +27 -0
  171. package/dist-lib/lib/run-log-redact.js +1107 -0
  172. package/dist-lib/lib/serve.js +308 -12
  173. package/dist-lib/lib/spawn.js +3 -2
  174. package/dist-lib/lib/tool-call-display.d.ts +1 -21
  175. package/dist-lib/lib/tools/exec.d.ts +2 -1
  176. package/dist-lib/lib/tools/exec.js +57 -2
  177. package/dist-lib/lib/tools/generate-image.js +1 -1
  178. package/dist-lib/lib/tools/generate-music.js +1 -1
  179. package/dist-lib/lib/tools/http-request.d.ts +2 -1
  180. package/dist-lib/lib/tools/http-request.js +2 -1
  181. package/dist-lib/lib/tools/mcp-call.js +8 -1
  182. package/dist-lib/lib/tools/operations/builders.d.ts +1 -0
  183. package/dist-lib/lib/tools/operations/builders.js +1 -0
  184. package/dist-lib/lib/tools/operations/compile.js +11 -1
  185. package/dist-lib/lib/tools/operations/manifest.js +1 -0
  186. package/dist-lib/lib/tools/outbound-guard.d.ts +8 -6
  187. package/dist-lib/lib/tools/outbound-guard.js +25 -8
  188. package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
  189. package/dist-lib/lib/tools/skills/catalog.js +35 -0
  190. package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +1 -0
  191. package/dist-lib/lib/tools/skills/cli-schema.js +25 -0
  192. package/dist-lib/lib/wire.d.ts +5 -0
  193. package/dist-lib/lib/wire.js +128 -5
  194. package/dist-lib/schema/harness.js +22 -6
  195. package/dist-lib/spec/hrpc/messages.js +100 -18
  196. package/dist-lib/spec/hyperschema/index.js +100 -18
  197. package/dist-lib/spec/hyperschema/schema.json +74 -1
  198. package/dist-lib/spec/hyperschema/types.d.ts +11 -3
  199. package/package.json +19 -1
  200. package/patches/@qvac%2Finference@0.20.0.patch +67 -0
  201. package/patches/bare-stow@0.1.5.patch +68 -0
  202. package/patches/hypercore-blob-server@1.15.0.patch +15 -0
  203. package/patches/localwatch@1.4.3.patch +13 -0
  204. package/patches/pyodide@314.0.2.patch +144 -0
@@ -0,0 +1,129 @@
1
+ import { assign, createActor, fromPromise, setup, waitFor } from 'xstate';
2
+ import { errorMessage, isRecord } from './guards.js';
3
+ import { hlog } from './log.js';
4
+ function errorFromEvent(event) {
5
+ return isRecord(event) ? event.error : undefined;
6
+ }
7
+ function requestFromEvent(event) {
8
+ return event.type === 'SUSPEND' || event.type === 'RESUME' || event.type === 'WAKEUP'
9
+ ? event.type
10
+ : null;
11
+ }
12
+ export const harnessLifecycle = setup({
13
+ types: {},
14
+ actors: {
15
+ suspend: fromPromise(async () => { }),
16
+ resume: fromPromise(async () => { })
17
+ },
18
+ actions: {
19
+ hold: assign(({ event }) => ({ pending: requestFromEvent(event) })),
20
+ clear: assign({ pending: null }),
21
+ report: ({ event }) => {
22
+ hlog('lifecycle step failed', { err: errorMessage(errorFromEvent(event)) });
23
+ },
24
+ recordError: assign(({ context, event }) => {
25
+ const message = errorMessage(errorFromEvent(event));
26
+ return {
27
+ failureCount: context.failureCount + 1,
28
+ failureMessage: message
29
+ };
30
+ })
31
+ },
32
+ guards: {
33
+ resumeHeld: ({ context }) => context.pending === 'RESUME',
34
+ wakeupHeld: ({ context }) => context.pending === 'WAKEUP',
35
+ suspendHeld: ({ context }) => context.pending === 'SUSPEND'
36
+ }
37
+ }).createMachine({
38
+ id: 'harness',
39
+ context: { pending: null, failureCount: 0, failureMessage: null },
40
+ initial: 'running',
41
+ states: {
42
+ running: {
43
+ always: { guard: 'suspendHeld', target: 'suspending', actions: 'clear' },
44
+ on: { PRESUSPEND: 'presuspended', SUSPEND: 'suspending' }
45
+ },
46
+ presuspended: {
47
+ on: { RESUME: 'running', WAKEUP: 'running', SUSPEND: 'suspending' }
48
+ },
49
+ suspending: {
50
+ tags: ['busy', 'suspended'],
51
+ invoke: {
52
+ src: 'suspend',
53
+ onDone: 'suspended',
54
+ onError: { target: 'suspended', actions: ['report', 'recordError'] }
55
+ },
56
+ on: {
57
+ RESUME: { actions: 'hold' },
58
+ WAKEUP: { actions: 'hold' },
59
+ SUSPEND: { actions: 'clear' },
60
+ CLOSE: { actions: 'clear' }
61
+ }
62
+ },
63
+ suspended: {
64
+ tags: ['suspended'],
65
+ always: [
66
+ { guard: 'resumeHeld', target: 'resuming', actions: 'clear' },
67
+ { guard: 'wakeupHeld', target: 'waking', actions: 'clear' }
68
+ ],
69
+ on: { RESUME: 'resuming', WAKEUP: 'waking' }
70
+ },
71
+ resuming: {
72
+ tags: ['busy'],
73
+ invoke: {
74
+ src: 'resume',
75
+ onDone: 'running',
76
+ onError: { target: 'suspended', actions: ['report', 'recordError', 'clear'] }
77
+ },
78
+ on: {
79
+ SUSPEND: { actions: 'hold' },
80
+ RESUME: { actions: 'clear' },
81
+ WAKEUP: { actions: 'clear' },
82
+ CLOSE: { actions: 'clear' }
83
+ }
84
+ },
85
+ waking: {
86
+ tags: ['busy'],
87
+ invoke: {
88
+ src: 'resume',
89
+ onDone: 'running',
90
+ onError: { target: 'suspended', actions: ['report', 'recordError', 'clear'] }
91
+ },
92
+ on: {
93
+ SUSPEND: { actions: 'hold' },
94
+ RESUME: { actions: 'clear' },
95
+ WAKEUP: { actions: 'clear' },
96
+ CLOSE: { actions: 'clear' }
97
+ }
98
+ }
99
+ }
100
+ });
101
+ export function createHarnessLifecycle(work) {
102
+ const actor = createActor(harnessLifecycle.provide({
103
+ actors: {
104
+ suspend: fromPromise(() => work.suspend()),
105
+ resume: fromPromise(() => work.resume())
106
+ }
107
+ })).start();
108
+ const settled = () => waitFor(actor, (snapshot) => !snapshot.hasTag('busy'));
109
+ return {
110
+ get suspended() {
111
+ return actor.getSnapshot().hasTag('suspended');
112
+ },
113
+ get state() {
114
+ return actor.getSnapshot().value;
115
+ },
116
+ async send(type) {
117
+ const failureCount = actor.getSnapshot().context.failureCount;
118
+ actor.send({ type });
119
+ await settled();
120
+ const { context } = actor.getSnapshot();
121
+ if (context.failureCount !== failureCount) {
122
+ throw new Error(context.failureMessage ?? 'Harness lifecycle step failed');
123
+ }
124
+ },
125
+ settled,
126
+ cancelPending: () => actor.send({ type: 'CLOSE' }),
127
+ stop: () => actor.stop()
128
+ };
129
+ }
@@ -0,0 +1 @@
1
+ export declare function detachJson<Value>(value: Value): Value;
@@ -0,0 +1,146 @@
1
+ export function detachJson(value) {
2
+ const detached = detachValue(value);
3
+ return detached;
4
+ }
5
+ function detachValue(value) {
6
+ if (!isStructuralJson(value, new WeakSet())) {
7
+ const encoded = JSON.stringify(value);
8
+ if (encoded === undefined) {
9
+ return undefined;
10
+ }
11
+ return JSON.parse(encoded);
12
+ }
13
+ return cloneStructural(value, { arrayItem: false });
14
+ }
15
+ function isStructuralJson(value, ancestors) {
16
+ if (isJsonPrimitive(value)) {
17
+ return true;
18
+ }
19
+ if (typeof value !== 'object') {
20
+ return false;
21
+ }
22
+ if (ancestors.has(value) || hasJsonHook(value)) {
23
+ return false;
24
+ }
25
+ ancestors.add(value);
26
+ try {
27
+ if (Array.isArray(value)) {
28
+ return isStructuralArray(value, ancestors);
29
+ }
30
+ if (!isPlainRecord(value)) {
31
+ return false;
32
+ }
33
+ return isStructuralRecord(value, ancestors);
34
+ }
35
+ finally {
36
+ ancestors.delete(value);
37
+ }
38
+ }
39
+ function isJsonPrimitive(value) {
40
+ return (value === null ||
41
+ value === undefined ||
42
+ typeof value === 'string' ||
43
+ typeof value === 'boolean' ||
44
+ typeof value === 'number');
45
+ }
46
+ function isStructuralArray(value, ancestors) {
47
+ if (Object.getPrototypeOf(value) !== Array.prototype) {
48
+ return false;
49
+ }
50
+ for (let index = 0; index < value.length; index++) {
51
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
52
+ if (!descriptor) {
53
+ if (index in value) {
54
+ return false;
55
+ }
56
+ continue;
57
+ }
58
+ if (!('value' in descriptor)) {
59
+ return false;
60
+ }
61
+ if (!isStructuralJson(descriptor.value, ancestors)) {
62
+ return false;
63
+ }
64
+ }
65
+ return true;
66
+ }
67
+ function isStructuralRecord(value, ancestors) {
68
+ for (const key of Object.keys(value)) {
69
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
70
+ if (!descriptor || !('value' in descriptor)) {
71
+ return false;
72
+ }
73
+ if (!isStructuralJson(descriptor.value, ancestors)) {
74
+ return false;
75
+ }
76
+ }
77
+ return true;
78
+ }
79
+ function cloneStructural(value, position) {
80
+ if (value === null || typeof value === 'string' || typeof value === 'boolean') {
81
+ return value;
82
+ }
83
+ if (typeof value === 'number') {
84
+ if (!Number.isFinite(value)) {
85
+ return null;
86
+ }
87
+ return Object.is(value, -0) ? 0 : value;
88
+ }
89
+ if (value === undefined) {
90
+ if (position.arrayItem) {
91
+ return null;
92
+ }
93
+ return undefined;
94
+ }
95
+ if (Array.isArray(value)) {
96
+ return cloneArray(value);
97
+ }
98
+ if (!isPlainRecord(value)) {
99
+ throw new TypeError('Expected a plain JSON object');
100
+ }
101
+ return cloneRecord(value);
102
+ }
103
+ function cloneArray(value) {
104
+ const output = [];
105
+ for (let index = 0; index < value.length; index++) {
106
+ output.push(cloneStructural(value[index], { arrayItem: true }));
107
+ }
108
+ return output;
109
+ }
110
+ function cloneRecord(value) {
111
+ const output = {};
112
+ for (const key of Object.keys(value)) {
113
+ const child = cloneStructural(value[key], { arrayItem: false });
114
+ if (child !== undefined) {
115
+ Object.defineProperty(output, key, {
116
+ value: child,
117
+ enumerable: true,
118
+ configurable: true,
119
+ writable: true
120
+ });
121
+ }
122
+ }
123
+ return output;
124
+ }
125
+ function isPlainRecord(value) {
126
+ if (!value || typeof value !== 'object') {
127
+ return false;
128
+ }
129
+ const prototype = Object.getPrototypeOf(value);
130
+ return prototype === Object.prototype || prototype === null;
131
+ }
132
+ function hasJsonHook(value) {
133
+ const own = Object.getOwnPropertyDescriptor(value, 'toJSON');
134
+ if (own) {
135
+ return !('value' in own) || typeof own.value === 'function';
136
+ }
137
+ let prototype = Object.getPrototypeOf(value);
138
+ while (prototype) {
139
+ const inherited = Object.getOwnPropertyDescriptor(prototype, 'toJSON');
140
+ if (inherited) {
141
+ return !('value' in inherited) || typeof inherited.value === 'function';
142
+ }
143
+ prototype = Object.getPrototypeOf(prototype);
144
+ }
145
+ return false;
146
+ }
@@ -0,0 +1,26 @@
1
+ export type NamedMarkupTag = 'function' | 'parameter';
2
+ export declare const MAX_MARKUP_NAME_LENGTH = 64;
3
+ export interface NamedMarkupBlock {
4
+ readonly name: string;
5
+ readonly body: string;
6
+ readonly start: number;
7
+ readonly end: number;
8
+ readonly nested: boolean;
9
+ }
10
+ export interface NamedMarkupToken {
11
+ readonly kind: 'open' | 'close';
12
+ readonly start: number;
13
+ readonly end: number;
14
+ readonly name?: string;
15
+ }
16
+ export interface NamedMarkupScan {
17
+ readonly token: NamedMarkupToken | null;
18
+ readonly partial: number;
19
+ }
20
+ export interface ScanNamedMarkupOptions {
21
+ readonly from?: number;
22
+ readonly maxNameLength?: number;
23
+ readonly haystack?: string;
24
+ }
25
+ export declare function scanNamedMarkup(text: string, tag: NamedMarkupTag, { from, maxNameLength, haystack }?: ScanNamedMarkupOptions): NamedMarkupScan;
26
+ export declare function namedMarkupBlocks(text: string, tag: NamedMarkupTag, maxNameLength?: number): Generator<NamedMarkupBlock>;
@@ -0,0 +1,156 @@
1
+ const ASCII_WHITESPACE = new Set([' ', '\t', '\n', '\r', '\f']);
2
+ export const MAX_MARKUP_NAME_LENGTH = 64;
3
+ function lowerAscii(char) {
4
+ return char.toLowerCase();
5
+ }
6
+ function isWhitespace(char) {
7
+ return char !== undefined && ASCII_WHITESPACE.has(char);
8
+ }
9
+ function skipBoundedWhitespace(text, start, bound) {
10
+ let cursor = start;
11
+ while (isWhitespace(text[cursor]) && cursor - start < bound) {
12
+ cursor++;
13
+ }
14
+ return isWhitespace(text[cursor]) ? -1 : cursor;
15
+ }
16
+ function validNameEnd(text, start, maxLength) {
17
+ const limit = Math.min(text.length, start + maxLength + 1);
18
+ for (let index = start; index < limit; index++) {
19
+ const char = text[index];
20
+ if (char === '>') {
21
+ return index === start ? -2 : { nameEnd: index, closerAt: index };
22
+ }
23
+ if (char === '<') {
24
+ return -2;
25
+ }
26
+ if (isWhitespace(char)) {
27
+ if (index === start) {
28
+ return -2;
29
+ }
30
+ const closerAt = skipBoundedWhitespace(text, index, MAX_MARKUP_NAME_LENGTH);
31
+ if (closerAt === -1) {
32
+ return -2;
33
+ }
34
+ if (closerAt === text.length) {
35
+ return -1;
36
+ }
37
+ return text[closerAt] === '>' ? { nameEnd: index, closerAt } : -2;
38
+ }
39
+ }
40
+ return -1;
41
+ }
42
+ function partialPrefix(haystack, start, value) {
43
+ const available = haystack.slice(start);
44
+ return available.length < value.length && value.startsWith(available);
45
+ }
46
+ function parseOpen(ctx, start) {
47
+ const prefix = `<${ctx.tag}`;
48
+ if (partialPrefix(ctx.haystack, start, prefix)) {
49
+ return 'partial';
50
+ }
51
+ if (!ctx.haystack.startsWith(prefix, start)) {
52
+ return null;
53
+ }
54
+ const cursor = skipBoundedWhitespace(ctx.haystack, start + prefix.length, MAX_MARKUP_NAME_LENGTH);
55
+ if (cursor === -1) {
56
+ return null;
57
+ }
58
+ if (cursor === ctx.text.length) {
59
+ return 'partial';
60
+ }
61
+ if (ctx.haystack[cursor] !== '=') {
62
+ return null;
63
+ }
64
+ return parseName(ctx, start, cursor + 1);
65
+ }
66
+ function parseName(ctx, start, nameStart) {
67
+ const cursor = skipBoundedWhitespace(ctx.haystack, nameStart, MAX_MARKUP_NAME_LENGTH);
68
+ if (cursor === -1) {
69
+ return null;
70
+ }
71
+ if (cursor === ctx.text.length) {
72
+ return 'partial';
73
+ }
74
+ const end = validNameEnd(ctx.text, cursor, ctx.maxNameLength);
75
+ if (end === -2) {
76
+ return null;
77
+ }
78
+ if (end === -1) {
79
+ return ctx.text.length - cursor <= ctx.maxNameLength ? 'partial' : null;
80
+ }
81
+ return { kind: 'open', start, end: end.closerAt + 1, name: ctx.text.slice(cursor, end.nameEnd) };
82
+ }
83
+ function parseAt(ctx, start) {
84
+ const close = `</${ctx.tag}>`;
85
+ if (ctx.haystack.startsWith(close, start)) {
86
+ return { kind: 'close', start, end: start + close.length };
87
+ }
88
+ if (partialPrefix(ctx.haystack, start, close)) {
89
+ return 'partial';
90
+ }
91
+ return parseOpen(ctx, start);
92
+ }
93
+ function scanNamedMarkupIn(ctx, from = 0) {
94
+ let cursor = from;
95
+ for (;;) {
96
+ const start = ctx.haystack.indexOf('<', cursor);
97
+ if (start === -1) {
98
+ return { token: null, partial: -1 };
99
+ }
100
+ const parsed = parseAt(ctx, start);
101
+ if (parsed === 'partial') {
102
+ return { token: null, partial: start };
103
+ }
104
+ if (parsed) {
105
+ return { token: parsed, partial: -1 };
106
+ }
107
+ cursor = start + 1;
108
+ }
109
+ }
110
+ function makeContext(text, tag, { maxNameLength, haystack }) {
111
+ return { text, haystack: haystack ?? text.replace(/[A-Z]/g, lowerAscii), tag, maxNameLength };
112
+ }
113
+ export function scanNamedMarkup(text, tag, { from = 0, maxNameLength = MAX_MARKUP_NAME_LENGTH, haystack } = {}) {
114
+ return scanNamedMarkupIn(makeContext(text, tag, { maxNameLength, haystack }), from);
115
+ }
116
+ function nextBlock(ctx, from) {
117
+ let scan = scanNamedMarkupIn(ctx, from);
118
+ while (scan.token?.kind === 'close') {
119
+ scan = scanNamedMarkupIn(ctx, scan.token.end);
120
+ }
121
+ const open = scan.token;
122
+ if (!open || open.kind !== 'open') {
123
+ return null;
124
+ }
125
+ let nested = false;
126
+ let cursor = open.end;
127
+ for (;;) {
128
+ const next = scanNamedMarkupIn(ctx, cursor).token;
129
+ if (!next) {
130
+ return null;
131
+ }
132
+ if (next.kind === 'close') {
133
+ return {
134
+ name: open.name ?? '',
135
+ body: ctx.text.slice(open.end, next.start),
136
+ start: open.start,
137
+ end: next.end,
138
+ nested
139
+ };
140
+ }
141
+ nested = true;
142
+ cursor = next.end;
143
+ }
144
+ }
145
+ export function* namedMarkupBlocks(text, tag, maxNameLength = MAX_MARKUP_NAME_LENGTH) {
146
+ const ctx = makeContext(text, tag, { maxNameLength });
147
+ let cursor = 0;
148
+ for (;;) {
149
+ const block = nextBlock(ctx, cursor);
150
+ if (!block) {
151
+ return;
152
+ }
153
+ yield block;
154
+ cursor = block.end;
155
+ }
156
+ }
@@ -1,50 +1,122 @@
1
- import { firstMarker } from 'asenion/guards';
2
1
  import { holdback } from 'asenion/scan';
3
2
  import { createJoinGuard } from './join-guard.js';
4
- const MARKUP_OPENERS = ['<tool_call>', '<function=', '<tool_response>'];
3
+ import { scanNamedMarkup } from './markup-scanner.js';
4
+ const MARKUP_OPENERS = ['<tool_call>', '<tool_response>'];
5
5
  const MARKUP_CLOSERS = {
6
6
  '<tool_call>': '</tool_call>',
7
- '<function=': '</function>',
8
7
  '<tool_response>': '</tool_response>'
9
8
  };
10
9
  const MARKUP_MARKERS = [...MARKUP_OPENERS, ...Object.values(MARKUP_CLOSERS)];
11
- export function createMarkupStreamFilter() {
12
- const state = {
10
+ const FUNCTION_CLOSE = '</function>';
11
+ const JOIN_MARKERS = [...MARKUP_MARKERS, '<function', '</function>', '<FUNCTION', '</FUNCTION>'];
12
+ function initialState() {
13
+ return {
13
14
  buffer: '',
15
+ lower: '',
16
+ markerAt: MARKUP_MARKERS.map(() => -1),
17
+ namedFrom: 0,
14
18
  closer: null,
19
+ candidate: null,
20
+ candidateTail: '',
15
21
  stripNewline: false,
16
- guard: createJoinGuard(MARKUP_MARKERS)
22
+ guard: createJoinGuard(JOIN_MARKERS)
17
23
  };
24
+ }
25
+ function markerHit(state) {
26
+ let hit = null;
27
+ for (let i = 0; i < MARKUP_MARKERS.length; i++) {
28
+ const index = state.markerAt[i];
29
+ if (index !== -1 && (!hit || index < hit.index)) {
30
+ hit = { index, marker: MARKUP_MARKERS[i] };
31
+ }
32
+ }
33
+ return hit;
34
+ }
35
+ function appendBuffer(state, token) {
36
+ const oldLength = state.buffer.length;
37
+ state.buffer += token;
38
+ state.lower += token.replace(/[A-Z]/g, (char) => char.toLowerCase());
39
+ state.markerAt = state.markerAt.map((index, i) => index === -1
40
+ ? state.buffer.indexOf(MARKUP_MARKERS[i], Math.max(0, oldLength - MARKUP_MARKERS[i].length + 1))
41
+ : index);
42
+ }
43
+ function setBuffer(state, next) {
44
+ const removed = state.buffer.length - next.length;
45
+ state.lower = state.lower.slice(removed);
46
+ state.buffer = next;
47
+ state.namedFrom = Math.max(0, state.namedFrom - removed);
48
+ state.markerAt = state.markerAt.map((index, i) => {
49
+ if (index === -1)
50
+ return -1;
51
+ return index >= removed ? index - removed : next.indexOf(MARKUP_MARKERS[i]);
52
+ });
53
+ }
54
+ function drain(state) {
55
+ let out = '';
56
+ for (;;) {
57
+ const step = nextStep(state);
58
+ out += step.out;
59
+ if (step.done) {
60
+ return out;
61
+ }
62
+ }
63
+ }
64
+ function nextStep(state) {
65
+ if (state.candidate) {
66
+ return consumeCandidate(state);
67
+ }
68
+ return state.closer ? consumeSuppressed(state) : emitUntilOpener(state);
69
+ }
70
+ export function createMarkupStreamFilter() {
71
+ const state = initialState();
18
72
  return {
19
73
  push(token) {
20
- state.buffer += token;
21
- let out = '';
22
- for (;;) {
23
- const step = state.closer ? consumeSuppressed(state) : emitUntilOpener(state);
24
- out += step.out;
25
- if (step.done) {
26
- return out;
27
- }
28
- }
74
+ appendBuffer(state, token);
75
+ return drain(state);
29
76
  },
30
77
  flush() {
31
78
  dropResumeNewline(state);
32
- const rest = state.guard.push(state.closer ? '' : state.buffer) + state.guard.flush();
33
- state.buffer = '';
34
- state.closer = null;
35
- state.stripNewline = false;
79
+ const candidate = state.candidate ? state.candidate.join('') : '';
80
+ const visible = state.closer ? '' : candidate + state.buffer;
81
+ const rest = state.guard.push(visible) + state.guard.flush();
82
+ resetPending(state);
36
83
  return rest;
37
84
  }
38
85
  };
39
86
  }
87
+ function resetPending(state) {
88
+ setBuffer(state, '');
89
+ state.closer = null;
90
+ state.candidate = null;
91
+ state.candidateTail = '';
92
+ state.stripNewline = false;
93
+ }
94
+ function consumeCandidate(state) {
95
+ const lower = state.candidateTail + state.lower;
96
+ const closeAt = lower.indexOf(FUNCTION_CLOSE);
97
+ if (closeAt === -1) {
98
+ state.candidate?.push(state.buffer);
99
+ state.candidateTail = lower.slice(-FUNCTION_CLOSE.length + 1);
100
+ setBuffer(state, '');
101
+ return { out: '', done: true };
102
+ }
103
+ const consumed = closeAt + FUNCTION_CLOSE.length - state.candidateTail.length;
104
+ setBuffer(state, state.buffer.slice(consumed));
105
+ state.candidate = null;
106
+ state.candidateTail = '';
107
+ state.stripNewline = true;
108
+ state.guard.cut();
109
+ return { out: '', done: false };
110
+ }
40
111
  function consumeSuppressed(state) {
41
112
  const closer = state.closer ?? '';
42
113
  const end = state.buffer.indexOf(closer);
43
114
  if (end === -1) {
44
- state.buffer = state.buffer.slice(state.buffer.length - holdback(state.buffer, [closer]));
115
+ const held = holdback(state.buffer, [closer]);
116
+ setBuffer(state, state.buffer.slice(state.buffer.length - held));
45
117
  return { out: '', done: true };
46
118
  }
47
- state.buffer = state.buffer.slice(end + closer.length);
119
+ setBuffer(state, state.buffer.slice(end + closer.length));
48
120
  state.stripNewline = true;
49
121
  state.closer = null;
50
122
  return { out: '', done: false };
@@ -54,25 +126,56 @@ function dropResumeNewline(state) {
54
126
  return;
55
127
  state.stripNewline = false;
56
128
  if (state.buffer[0] === '\n') {
57
- state.buffer = state.buffer.slice(1);
129
+ setBuffer(state, state.buffer.slice(1));
130
+ }
131
+ }
132
+ function beginFunctionCandidate(state, start, end) {
133
+ const out = state.guard.push(state.buffer.slice(0, start));
134
+ state.candidate = [state.buffer.slice(start, end)];
135
+ setBuffer(state, state.buffer.slice(end));
136
+ state.candidateTail = '';
137
+ return out;
138
+ }
139
+ function consumeFixedMarker(state, index, marker) {
140
+ const out = state.guard.push(state.buffer.slice(0, index));
141
+ state.guard.cut();
142
+ setBuffer(state, state.buffer.slice(index + marker.length));
143
+ const closer = MARKUP_CLOSERS[marker];
144
+ state.stripNewline = closer === undefined;
145
+ state.closer = closer ?? null;
146
+ return { out, done: false };
147
+ }
148
+ function scanNamedOpen(buffer, haystack, start) {
149
+ let from = start;
150
+ for (;;) {
151
+ const scan = scanNamedMarkup(buffer, 'function', { from, haystack });
152
+ if (!scan.token || scan.token.kind === 'open') {
153
+ return scan;
154
+ }
155
+ from = scan.token.end;
58
156
  }
59
157
  }
60
158
  function emitUntilOpener(state) {
61
159
  dropResumeNewline(state);
62
- const marker = firstMarker(state.buffer, MARKUP_MARKERS);
63
- if (marker) {
64
- const out = state.guard.push(state.buffer.slice(0, marker.index));
65
- state.guard.cut();
66
- state.buffer = state.buffer.slice(marker.index + marker.marker.length);
67
- const closer = MARKUP_CLOSERS[marker.marker];
68
- if (closer === undefined) {
69
- state.stripNewline = true;
70
- }
71
- state.closer = closer ?? null;
72
- return { out, done: false };
160
+ const marker = markerHit(state);
161
+ const scan = scanNamedOpen(state.buffer, state.lower, state.namedFrom);
162
+ const namedIndex = scan.token?.start ?? scan.partial;
163
+ state.namedFrom = namedIndex === -1 ? state.buffer.length : namedIndex;
164
+ const useNamed = namedIndex !== -1 && (!marker || namedIndex < marker.index);
165
+ if (useNamed && scan.token) {
166
+ return { out: beginFunctionCandidate(state, scan.token.start, scan.token.end), done: false };
167
+ }
168
+ if (marker && !useNamed) {
169
+ return consumeFixedMarker(state, marker.index, marker.marker);
170
+ }
171
+ if (useNamed) {
172
+ const out = state.guard.push(state.buffer.slice(0, namedIndex));
173
+ setBuffer(state, state.buffer.slice(namedIndex));
174
+ return { out, done: true };
73
175
  }
74
- const hold = holdback(state.buffer, MARKUP_MARKERS);
176
+ const functionHold = scan.partial === -1 ? 0 : state.buffer.length - scan.partial;
177
+ const hold = Math.max(holdback(state.buffer, MARKUP_MARKERS), functionHold);
75
178
  const out = state.guard.push(state.buffer.slice(0, state.buffer.length - hold));
76
- state.buffer = state.buffer.slice(state.buffer.length - hold);
179
+ setBuffer(state, state.buffer.slice(state.buffer.length - hold));
77
180
  return { out, done: true };
78
181
  }
@@ -1,5 +1,10 @@
1
- import type { AbortSignal as BareAbortSignal } from 'bare-abort-controller';
2
- export declare class AbortedError extends Error {
3
- constructor();
1
+ import { AbortedError } from '../machine/index.ts';
2
+ export { AbortedError };
3
+ export interface AbortSignalLike {
4
+ readonly aborted: boolean;
5
+ addEventListener(type: 'abort', listener: () => void, options?: {
6
+ once?: boolean;
7
+ }): void;
8
+ removeEventListener(type: 'abort', listener: () => void): void;
4
9
  }
5
- export declare function raceAbort<T>(promise: Promise<T>, signal?: BareAbortSignal): Promise<T>;
10
+ export declare function raceAbort<T>(promise: Promise<T>, signal?: AbortSignalLike): Promise<T>;