@aparte/engine 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,6 +3,20 @@
3
3
  The **headless agent loop** behind `@aparte/*` — framework-agnostic, zero runtime
4
4
  dependencies, runs in the browser or Node.
5
5
 
6
+ ```bash
7
+ npm install @aparte/engine @aparte/core
8
+ ```
9
+
10
+ `@aparte/core` is an **optional peer**: install it only for the recommended path below.
11
+
12
+ ```ts
13
+ import { runStreamAgent } from '@aparte/engine';
14
+ import { AparteClient } from '@aparte/core';
15
+
16
+ // Inject the loop and core renders its events for you — a drop-in, not an approximation.
17
+ new AparteClient({ streamRunner: runStreamAgent }).start();
18
+ ```
19
+
6
20
  Its core export is **`runStreamAgent`**: a DOM-free structured-stream loop that turns a
7
21
  transport's token stream into high-level run events (text, thinking, tool calls, artifacts),
8
22
  drives the tool-calling loop (with optional human-in-the-loop approval), and reports usage.
@@ -34,7 +48,7 @@ const log = new PromptLog(); // your append-only transcrip
34
48
 
35
49
  await runStreamAgent({
36
50
  // …messageId, emitter, signal, toolLookup
37
- baseRequest: { messages: [] }, // unused by your transport
51
+ baseRequest: { messages: [], modelId: 'my-model' }, // your transport may ignore both
38
52
  onHistoryAppend: (turn) => log.append(turn), // tool_call · tool_result · phase reply
39
53
  transportCall: () => myCompletion(log.render()), // your own bytes, extended not rebuilt
40
54
  });
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * artifact-xml-state-machine.ts — streaming `<artifact>` XML parser (pure).
3
3
  *
4
- * The framework-free extraction of the XML-artifact branch inside
5
- * `AparteClient._streamLoop` (aparte-client.ts :1268-1392 + finalize :1658-1669).
4
+ * The framework-free extraction of core's `client/xml-artifact-feed.ts` (the
5
+ * scanning half) plus the `XML artifact finalize` block inside
6
+ * `AparteClient._streamLoop`.
6
7
  * Small models emit artifacts as `…chat text… <artifact mimeType="…" title="…">
7
8
  * BODY</artifact> …more text…`, streamed in arbitrary chunks that can split the
8
9
  * opening tag, the body, or the closing tag across delta boundaries.
@@ -76,8 +77,8 @@ export declare class ArtifactXmlStateMachine {
76
77
  feed(delta: string): XmlArtifactEvent[];
77
78
  /**
78
79
  * Flush a truncated artifact: if the stream ended mid-body (model cut off
79
- * before `</artifact>`), emit a close with whatever was buffered. Mirrors
80
- * `_streamLoop`'s finalize block (:1658-1669).
80
+ * before `</artifact>`), emit a close with whatever was buffered. Mirrors the
81
+ * `XML artifact finalize` block in `AparteClient._streamLoop`.
81
82
  */
82
83
  finalize(): XmlArtifactEvent[];
83
84
  /** Current parser state (for the adapter to decide finalize routing). */
@@ -1 +1 @@
1
- {"version":3,"file":"artifact-xml-state-machine.d.ts","sourceRoot":"","sources":["../../../src/agent/parsers/artifact-xml-state-machine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,uDAAuD;AACvD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,UAAU,GAAG,aAAa,CAAC;AAErE,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,yEAAyE;AACzE,MAAM,MAAM,gBAAgB,GAMtB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpF;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAO/E;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,SAAY,GAAG,MAAM,CAqBjF;AAED;;;;GAIG;AACH,qBAAa,uBAAuB;IAcpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAbjC,OAAO,CAAC,KAAK,CAA8B;IAC3C,uEAAuE;IACvE,OAAO,CAAC,OAAO,CAAM;IACrB,yEAAyE;IACzE,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,OAAO,CAAM;IACrB,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;gBAER,IAAI,EAAE,eAAe,EAAE,KAAK,CAAC,EAAE,MAAM,MAAM;IAIxE,yEAAyE;IACzE,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,EAAE;IA4DvC;;;;OAIG;IACH,QAAQ,IAAI,gBAAgB,EAAE;IAY9B,yEAAyE;IACzE,IAAI,YAAY,IAAI,gBAAgB,CAEnC;CACJ"}
1
+ {"version":3,"file":"artifact-xml-state-machine.d.ts","sourceRoot":"","sources":["../../../src/agent/parsers/artifact-xml-state-machine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,uDAAuD;AACvD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,UAAU,GAAG,aAAa,CAAC;AAErE,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,yEAAyE;AACzE,MAAM,MAAM,gBAAgB,GAOtB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpF;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAoB/E;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,SAAY,GAAG,MAAM,CAqBjF;AAED;;;;GAIG;AACH,qBAAa,uBAAuB;IAcpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAbjC,OAAO,CAAC,KAAK,CAA8B;IAC3C,uEAAuE;IACvE,OAAO,CAAC,OAAO,CAAM;IACrB,yEAAyE;IACzE,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,OAAO,CAAM;IACrB,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;gBAER,IAAI,EAAE,eAAe,EAAE,KAAK,CAAC,EAAE,MAAM,MAAM;IAIxE,yEAAyE;IACzE,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,EAAE;IA2FvC;;;;OAIG;IACH,QAAQ,IAAI,gBAAgB,EAAE;IAqB9B,yEAAyE;IACzE,IAAI,YAAY,IAAI,gBAAgB,CAEnC;CACJ"}
@@ -13,14 +13,23 @@
13
13
  * runtime zero-import rule still holds; core is present as engine's dev/peer dep at
14
14
  * typecheck time). It is not imported by the barrel, so it stays out of the bundle.
15
15
  *
16
- * The ONE intentional difference is `run-done.usage`: core carries the rich
17
- * `AparteUsage` (named provider-timing fields — ttft/decode/phases/…), engine an
18
- * opaque `StreamUsage` passthrough (five common fields + an index signature). We
19
- * normalize that single field to compare the rest of the contract for EXACT equality,
20
- * and separately assert the usage stays forwardable engine→core (the seam direction:
21
- * core's adapter consumes engine's `run-done` and forwards its usage to `setUsage`).
16
+ * The ONE remaining difference is `run-done.usage`: core carries the rich
17
+ * `AparteUsage` (extra named provider-timing fields — ttft/decode/phases/…), engine
18
+ * the five common ones. Those are mutually assignable — extra OPTIONAL fields never
19
+ * block an assignment — but not equal, so that field is normalized away before the
20
+ * exact-equality comparison of everything else.
21
+ *
22
+ * It used to differ for another reason: `StreamUsage` carried an
23
+ * `[key: string]: unknown` "opaque passthrough" signature. That signature was the
24
+ * last thing making `runStreamAgent` un-assignable to `AparteStreamRunner`, because
25
+ * `transportCall` is contravariant and core's `AparteUsage` has no index signature.
26
+ * Worth naming plainly: this guard normalized away the exact field that broke the
27
+ * seam, so it had been written AROUND the defect — every shape it compared matched,
28
+ * while the one composition both packages exist for did not compile. Assertion 4
29
+ * below is what closes that gap, and it is the one that checks the composition
30
+ * itself rather than a shape.
22
31
  */
23
- import type { AparteStreamRunEvent, AparteStreamRunEmitter } from '@aparte/core';
32
+ import type { AparteStreamRunEvent, AparteStreamRunEmitter, AparteStreamRunner } from '@aparte/core';
24
33
  import type { StreamRunEvent, StreamRunEmitter } from './stream-events.js';
25
34
  /** Invariant type-equality — distinguishes optional vs required and index signatures. */
26
35
  type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false;
@@ -45,11 +54,21 @@ type NormalizeRunDone<E> = E extends {
45
54
  * 2. The `run-done` usage stays forwardable engine→core (lets core's adapter treat it
46
55
  * as `AparteUsage`).
47
56
  * 3. The emitter core injects into `runStreamAgent` satisfies engine's emitter contract.
57
+ * 4. **`runStreamAgent` is actually assignable to `AparteStreamRunner`.** This was the
58
+ * hole: every mirror above compared event SHAPES, and all of them held, while the
59
+ * one composition the two packages exist to make —
60
+ * `new AparteClient({ streamRunner: runStreamAgent })` — did not compile.
61
+ * `StreamAgentMessage.content` was written to mirror `AparteChatMessage` and never
62
+ * followed it to `string | AparteContentPart[]`. Nothing in the repo assigned the
63
+ * two together (the seam test builds its own runner), so it had zero compile
64
+ * coverage anywhere, which is exactly why it shipped broken across five docs pages
65
+ * and a README. One line here is the whole guard.
48
66
  */
49
67
  export type StreamEventContract = [
50
68
  Expect<Equal<NormalizeRunDone<StreamRunEvent>, NormalizeRunDone<AparteStreamRunEvent>>>,
51
69
  Expect<Assignable<StreamRunEvent, AparteStreamRunEvent>>,
52
- Expect<Assignable<AparteStreamRunEmitter, StreamRunEmitter>>
70
+ Expect<Assignable<AparteStreamRunEmitter, StreamRunEmitter>>,
71
+ Expect<Assignable<typeof import('./stream-run.js')['runStreamAgent'], AparteStreamRunner>>
53
72
  ];
54
73
  export {};
55
74
  //# sourceMappingURL=stream-events.contract.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stream-events.contract.d.ts","sourceRoot":"","sources":["../../src/agent/stream-events.contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACjF,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3E,yFAAyF;AACzF,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC,IACX,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC;AACzF,yDAAyD;AACzD,KAAK,MAAM,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC;AAChC,kFAAkF;AAClF,KAAK,UAAU,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC;AAEnD,wFAAwF;AACxF,KAAK,gBAAgB,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,CAAC,CAAC;AAEtG;;;;;;;;;;GAUG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAC9B,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,cAAc,CAAC,EAAE,gBAAgB,CAAC,oBAAoB,CAAC,CAAC,CAAC;IACvF,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,oBAAoB,CAAC,CAAC;IACxD,MAAM,CAAC,UAAU,CAAC,sBAAsB,EAAE,gBAAgB,CAAC,CAAC;CAC/D,CAAC"}
1
+ {"version":3,"file":"stream-events.contract.d.ts","sourceRoot":"","sources":["../../src/agent/stream-events.contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACrG,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3E,yFAAyF;AACzF,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC,IACX,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC;AACzF,yDAAyD;AACzD,KAAK,MAAM,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC;AAChC,kFAAkF;AAClF,KAAK,UAAU,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC;AAEnD,wFAAwF;AACxF,KAAK,gBAAgB,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,CAAC,CAAC;AAEtG;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAC9B,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,cAAc,CAAC,EAAE,gBAAgB,CAAC,oBAAoB,CAAC,CAAC,CAAC;IACvF,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,oBAAoB,CAAC,CAAC;IACxD,MAAM,CAAC,UAAU,CAAC,sBAAsB,EAAE,gBAAgB,CAAC,CAAC;IAC5D,MAAM,CAAC,UAAU,CAAC,cAAc,iBAAiB,CAAC,CAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,CAAC;CAC7F,CAAC"}
@@ -33,13 +33,20 @@ export interface StreamUsage {
33
33
  totalTokens?: number;
34
34
  cacheReadTokens?: number;
35
35
  durationMs?: number;
36
- [key: string]: unknown;
37
36
  }
38
37
  /** One tool call as surfaced by the provider stream (mirrors `AparteToolCall`). */
39
38
  export interface StreamToolCall {
40
39
  id: string;
41
40
  name: string;
42
- input: unknown;
41
+ /**
42
+ * `Record<string, unknown>`, mirroring core exactly, not `unknown`.
43
+ *
44
+ * `unknown` looked safer — the loop does not read tool input, it forwards it —
45
+ * but the seam needs assignability in BOTH directions (`transportCall` is
46
+ * contravariant), and `unknown` is not assignable to `Record<string, unknown>`.
47
+ * Locked to core's shape by `stream-events.contract.ts`.
48
+ */
49
+ input: Record<string, unknown>;
43
50
  }
44
51
  /**
45
52
  * A structured stream event from the transport (mirrors `AparteStreamEvent`). The
@@ -51,12 +58,9 @@ export type StreamChatEvent = {
51
58
  } | {
52
59
  type: 'thinking';
53
60
  delta: string;
54
- } | {
61
+ } | ({
55
62
  type: 'tool_use';
56
- id: string;
57
- name: string;
58
- input: unknown;
59
- } | {
63
+ } & StreamToolCall) | {
60
64
  type: 'error';
61
65
  message: string;
62
66
  } | {
@@ -64,25 +68,117 @@ export type StreamChatEvent = {
64
68
  usage?: StreamUsage;
65
69
  };
66
70
  /**
67
- * A conversation message (mirrors `AparteChatMessage`). `role` is left open
68
- * (`string`) so the loop can push the `'tool_call'` / `'tool_result'` envelope
69
- * roles `_streamLoop` uses without importing core's union.
71
+ * A tool declaration, structurally mirroring core's `AparteTool`.
72
+ *
73
+ * The loop forwards the inventory to the transport and reads `maxTurns` and
74
+ * `needsApproval` from its own lookup — it never interprets the schema. Mirrored
75
+ * exactly all the same, because the seam needs assignability in both directions;
76
+ * `stream-events.contract.ts` locks it.
77
+ */
78
+ export interface StreamTool {
79
+ name: string;
80
+ description: string;
81
+ inputSchema: Record<string, unknown>;
82
+ systemPrompt?: string;
83
+ maxTurns?: number;
84
+ needsApproval?: boolean;
85
+ }
86
+ /**
87
+ * Multimodal content parts — a structural mirror of core's `AparteContentPart`.
88
+ *
89
+ * Mirrored rather than imported for the same reason as everything else in this
90
+ * file: engine stays zero-import at runtime. Mirrored EXACTLY rather than
91
+ * loosened to `unknown[]`, which was the first attempt: `transportCall` is
92
+ * contravariant, so the message type has to be assignable to core's AND core's to
93
+ * it, and `unknown[]` fails the second direction. `stream-events.contract.ts`
94
+ * asserts the equality, so a part added on either side is a typecheck error
95
+ * rather than a silent divergence.
96
+ *
97
+ * The loop never looks inside content — it carries messages to the transport and
98
+ * appends results. This exists to make the seam type-check, not to be read.
99
+ */
100
+ export interface StreamTextPart {
101
+ type: 'text';
102
+ text: string;
103
+ }
104
+ export interface StreamImagePart {
105
+ type: 'image';
106
+ image: string;
107
+ mimeType?: string;
108
+ }
109
+ export interface StreamFilePart {
110
+ type: 'file';
111
+ data: string;
112
+ mimeType: string;
113
+ name?: string;
114
+ }
115
+ export type StreamContentPart = StreamTextPart | StreamImagePart | StreamFilePart;
116
+ /**
117
+ * A conversation message (mirrors `AparteChatMessage`).
118
+ *
119
+ * `role` used to be a bare `string`, justified by a comment saying the loop needed
120
+ * to push the `'tool_call'` / `'tool_result'` envelope roles "without importing
121
+ * core's union". But core's union already contains both — so the reason had not
122
+ * been true for some time, and the looseness cost something real: `transportCall`
123
+ * is contravariant, so this message type must ALSO be assignable to core's, and a
124
+ * `string` role is not assignable to a closed union. That was the last link in the
125
+ * chain that stopped `streamRunner: runStreamAgent` from compiling.
126
+ *
127
+ * Mirrored structurally rather than imported, to keep this package zero-import at
128
+ * runtime; `stream-events.contract.ts` asserts the two line up.
70
129
  */
71
130
  export interface StreamAgentMessage {
72
- role: string;
73
- content: string;
131
+ role: 'user' | 'assistant' | 'system' | 'tool_call' | 'tool_result';
132
+ /**
133
+ * Message content — carried, never inspected by this loop.
134
+ *
135
+ * Was plain `string`, because core's `AparteChatMessage.content` became
136
+ * `string | AparteContentPart[]` for multimodal messages and this mirror never
137
+ * followed. The consequence was not subtle: `new AparteClient({ streamRunner:
138
+ * runStreamAgent })` — the one composition the two packages exist to make, and
139
+ * the headline of five docs pages — did not typecheck.
140
+ */
141
+ content: string | StreamContentPart[];
74
142
  /** Present on a `'tool_call'` envelope — the whole turn's calls, grouped. */
75
143
  toolCalls?: StreamToolCall[];
76
144
  /** Present on a `'tool_result'` message — which call it answers. */
77
145
  toolCallId?: string;
78
146
  /** Assistant text that preceded the tool call(s) this turn. */
79
147
  precedingText?: string;
80
- [key: string]: unknown;
81
148
  }
82
- /** The request handed to the transport each turn (mirrors `AparteChatRequest`). */
149
+ /**
150
+ * The request handed to the transport each turn (mirrors `AparteChatRequest`).
151
+ *
152
+ * The two fields the loop actually reads are DECLARED rather than reached through
153
+ * an index signature. They used to be dynamic (`[key: string]: unknown`), which
154
+ * read like "extra fields pass through" but is a requirement on the target of an
155
+ * assignment, not a permission — it is what made core's `AparteChatRequest`
156
+ * un-assignable here and broke `streamRunner: runStreamAgent`. Declaring them is
157
+ * both the fix and the more honest description: these are the fields the loop
158
+ * branches on. Anything else a host attaches still passes through untouched,
159
+ * because TypeScript only excess-property-checks object literals.
160
+ *
161
+ * Types kept structural (not imported from core) so this package stays
162
+ * zero-import at runtime; `stream-events.contract.ts` asserts they line up.
163
+ */
83
164
  export interface StreamChatRequest {
84
165
  messages: StreamAgentMessage[];
85
- [key: string]: unknown;
166
+ /**
167
+ * Required, because `transportCall` is contravariant: the runner hands this
168
+ * request to a transport typed against core's `AparteChatRequest`, where
169
+ * `modelId` is required. A mirror that omitted it made the whole runner
170
+ * un-assignable — from the opposite direction to the `messages` mismatch.
171
+ */
172
+ modelId: string;
173
+ /** Per-turn hints the loop branches on (pipeline phases, artifact modes, …). */
174
+ _meta?: Record<string, unknown>;
175
+ /** `'none'` makes the loop drop the tool inventory for that turn. */
176
+ toolChoice?: 'auto' | 'none' | {
177
+ name: string;
178
+ input?: Record<string, unknown>;
179
+ };
180
+ /** The inventory the loop sends, and clears when `toolChoice` is `'none'`. */
181
+ tools?: StreamTool[];
86
182
  }
87
183
  /** A tool handler (mirrors the resolved `AparteToolHandler`). */
88
184
  export type StreamToolHandler = (call: StreamToolCall, signal: AbortSignal) => Promise<{
@@ -1 +1 @@
1
- {"version":3,"file":"stream-events.d.ts","sourceRoot":"","sources":["../../src/agent/stream-events.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"stream-events.d.ts","sourceRoot":"","sources":["../../src/agent/stream-events.ts"],"names":[],"mappings":"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"}
@@ -26,7 +26,7 @@ export interface StreamRunOptions {
26
26
  * closed over by the adapter.
27
27
  */
28
28
  transportCall: (request: StreamChatRequest) => Promise<AsyncIterable<StreamChatEvent> | string>;
29
- /** Resolves a tool's handler by name (mirrors `AparteConfig.getToolHandler`). */
29
+ /** Resolves a tool's handler by name (mirrors `aparteGlobalConfig.getToolHandler`). */
30
30
  toolLookup: (name: string) => StreamToolHandler | undefined;
31
31
  /** Resolves a tool's loop config by name (maxTurns / needsApproval). */
32
32
  toolConfigLookup?: (name: string) => StreamToolConfig | undefined;
@@ -63,12 +63,5 @@ export interface StreamRunOptions {
63
63
  */
64
64
  idGen?: (prefix: string) => string;
65
65
  }
66
- /**
67
- * Run the structured-stream agent loop. Resolves the last turn's usage (the
68
- * `done{usage}` last-write-wins, mirroring `_streamLoop`'s return), or
69
- * `undefined`. Throws on a stream `error` event or a non-abort tool failure —
70
- * the caller (adapter) routes that to its lifecycle-error handler, exactly as
71
- * `_handleSend`/`_handleRetry`/`_handleEdit` catch `_streamLoop`.
72
- */
73
66
  export declare function runStreamAgent(opts: StreamRunOptions): Promise<StreamUsage | undefined>;
74
67
  //# sourceMappingURL=stream-run.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stream-run.d.ts","sourceRoot":"","sources":["../../src/agent/stream-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EACR,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAElB,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACtB,WAAW,EACd,MAAM,oBAAoB,CAAC;AAmB5B,MAAM,WAAW,gBAAgB;IAC7B,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,WAAW,EAAE,iBAAiB,CAAC;IAC/B;;;;;OAKG;IACH,aAAa,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,aAAa,CAAC,eAAe,CAAC,GAAG,MAAM,CAAC,CAAC;IAChG,iFAAiF;IACjF,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,iBAAiB,GAAG,SAAS,CAAC;IAC5D,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,gBAAgB,GAAG,SAAS,CAAC;IAClE,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IAC1C,+DAA+D;IAC/D,OAAO,EAAE,gBAAgB,CAAC;IAC1B,0EAA0E;IAC1E,MAAM,EAAE,WAAW,CAAC;IACpB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;OAaG;IACH,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACxD;;;;;OAKG;IACH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;CACtC;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAyU7F"}
1
+ {"version":3,"file":"stream-run.d.ts","sourceRoot":"","sources":["../../src/agent/stream-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EACR,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAElB,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACtB,WAAW,EACd,MAAM,oBAAoB,CAAC;AAmB5B,MAAM,WAAW,gBAAgB;IAC7B,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,WAAW,EAAE,iBAAiB,CAAC;IAC/B;;;;;OAKG;IACH,aAAa,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,aAAa,CAAC,eAAe,CAAC,GAAG,MAAM,CAAC,CAAC;IAChG,uFAAuF;IACvF,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,iBAAiB,GAAG,SAAS,CAAC;IAC5D,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,gBAAgB,GAAG,SAAS,CAAC;IAClE,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IAC1C,+DAA+D;IAC/D,OAAO,EAAE,gBAAgB,CAAC;IAC1B,0EAA0E;IAC1E,MAAM,EAAE,WAAW,CAAC;IACpB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;OAaG;IACH,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACxD;;;;;OAKG;IACH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;CACtC;AAsBD,wBAAsB,cAAc,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAuZ7F"}
@@ -1 +1 @@
1
- {"version":3,"file":"compactor.d.ts","sourceRoot":"","sources":["../../src/conversation/compactor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compactor.d.ts","sourceRoot":"","sources":["../../src/conversation/compactor.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -8,11 +8,12 @@
8
8
  * by the stream-parity suite.
9
9
  *
10
10
  * Deliberately just the loop core drives, plus the agnostic context compactor. Opt-in
11
- * *tools* (ask-question / RAG / skills / code) belong in `plugins/*`; product behaviour
11
+ * *tools* (ask-user / RAG / skills / code) belong in `plugins/*`; product behaviour
12
12
  * (memory, intent orchestration) and the not-yet-wired text agent loop live elsewhere.
13
13
  */
14
14
  export * from './agent/stream-events.js';
15
15
  export * from './agent/stream-run.js';
16
- export * from './agent/parsers/artifact-xml-state-machine.js';
16
+ export { ArtifactXmlStateMachine } from './agent/parsers/artifact-xml-state-machine.js';
17
+ export type { XmlArtifactEvent, XmlArtifactHint, XmlArtifactState } from './agent/parsers/artifact-xml-state-machine.js';
17
18
  export * from './conversation/compactor.js';
18
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,+CAA+C,CAAC;AAG9D,cAAc,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AAYtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AACxF,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,+CAA+C,CAAC;AAGzH,cAAc,6BAA6B,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,12 @@
1
1
  const CLOSE_TAG = "</artifact>";
2
2
  const OPEN_TAG = "<artifact";
3
+ function partialOpenTagLength(text) {
4
+ const max = Math.min(text.length, OPEN_TAG.length - 1);
5
+ for (let k = max; k > 0; k--) {
6
+ if (text.endsWith(OPEN_TAG.slice(0, k))) return k;
7
+ }
8
+ return 0;
9
+ }
3
10
  const INLINE_MAX_LINES = 15;
4
11
  function deriveArtifactKind(mimeType, fallback = "unknown") {
5
12
  const m = (mimeType || "").toLowerCase().trim();
@@ -48,18 +55,33 @@ class ArtifactXmlStateMachine {
48
55
  if (this.state === "normal") {
49
56
  const tagStart = remaining.indexOf(OPEN_TAG);
50
57
  if (tagStart === -1) {
51
- out.push({ type: "chat-text", text: remaining });
58
+ const held = partialOpenTagLength(remaining);
59
+ if (held > 0) {
60
+ const before = remaining.slice(0, remaining.length - held);
61
+ if (before) out.push({ type: "chat-text", text: before, reduced: true });
62
+ this.scanBuf = remaining.slice(remaining.length - held);
63
+ this.state = "scanning";
64
+ } else {
65
+ out.push({ type: "chat-text", text: remaining });
66
+ }
52
67
  remaining = "";
53
68
  } else {
54
69
  const before = remaining.slice(0, tagStart);
55
70
  if (before) out.push({ type: "chat-text", text: before, reduced: true });
56
- this.scanBuf = remaining.slice(tagStart);
57
- remaining = "";
71
+ this.scanBuf = "";
72
+ remaining = remaining.slice(tagStart);
58
73
  this.state = "scanning";
59
74
  }
60
75
  } else if (this.state === "scanning") {
61
76
  this.scanBuf += remaining;
62
77
  remaining = "";
78
+ const cmp = Math.min(this.scanBuf.length, OPEN_TAG.length);
79
+ if (this.scanBuf.slice(0, cmp) !== OPEN_TAG.slice(0, cmp)) {
80
+ out.push({ type: "chat-text", text: this.scanBuf });
81
+ this.scanBuf = "";
82
+ this.state = "normal";
83
+ continue;
84
+ }
63
85
  const gtIdx = this.scanBuf.indexOf(">");
64
86
  if (gtIdx !== -1) {
65
87
  const tag = this.scanBuf.slice(0, gtIdx + 1);
@@ -96,10 +118,16 @@ class ArtifactXmlStateMachine {
96
118
  }
97
119
  /**
98
120
  * Flush a truncated artifact: if the stream ended mid-body (model cut off
99
- * before `</artifact>`), emit a close with whatever was buffered. Mirrors
100
- * `_streamLoop`'s finalize block (:1658-1669).
121
+ * before `</artifact>`), emit a close with whatever was buffered. Mirrors the
122
+ * `XML artifact finalize` block in `AparteClient._streamLoop`.
101
123
  */
102
124
  finalize() {
125
+ if (this.state === "scanning") {
126
+ const text = this.scanBuf;
127
+ this.scanBuf = "";
128
+ this.state = "normal";
129
+ return text ? [{ type: "chat-text", text }] : [];
130
+ }
103
131
  if (this.state === "in-artifact" && this.segId) {
104
132
  this.content += this.closeBuf;
105
133
  const inline = this.content.split("\n").length < INLINE_MAX_LINES;
@@ -117,6 +145,15 @@ class ArtifactXmlStateMachine {
117
145
  }
118
146
  const DEFAULT_TOOL_TIMEOUT_MS = 5 * 60 * 1e3;
119
147
  const DEFAULT_MAX_TURNS = 10;
148
+ const warnedUnknownEvents = /* @__PURE__ */ new Set();
149
+ function warnUnknownStreamEvent(event) {
150
+ const type = String(event?.type ?? "undefined");
151
+ if (warnedUnknownEvents.has(type)) return;
152
+ warnedUnknownEvents.add(type);
153
+ console.warn(
154
+ `[runStreamAgent] Ignoring unrecognised stream event "${type}". The reply is unaffected; this usually means the provider or its SDK emits a part this version of aparté does not map yet.`
155
+ );
156
+ }
120
157
  async function runStreamAgent(opts) {
121
158
  const {
122
159
  transportCall,
@@ -189,9 +226,20 @@ async function runStreamAgent(opts) {
189
226
  }
190
227
  }
191
228
  const request = { ...baseRequest, messages: phaseMessages, _meta: phaseMeta };
192
- const response = await transportCall(request);
229
+ let response;
230
+ try {
231
+ response = await transportCall(request);
232
+ } catch (err) {
233
+ if (signal.aborted || err?.name === "AbortError") {
234
+ emitter({ type: "run-aborted" });
235
+ continueLoop = false;
236
+ break;
237
+ }
238
+ throw err;
239
+ }
193
240
  if (typeof response === "string") {
194
241
  emitter({ type: "text-delta", delta: response });
242
+ emitter({ type: "text-flush" });
195
243
  break;
196
244
  }
197
245
  emitter({ type: "turn-start" });
@@ -215,15 +263,18 @@ async function runStreamAgent(opts) {
215
263
  let precedingText = "";
216
264
  const toolCallsThisTurn = [];
217
265
  const iterator = response[Symbol.asyncIterator]();
266
+ const bailOnAbort = async () => {
267
+ if (!signal.aborted) return false;
268
+ await iterator.return?.(void 0);
269
+ emitter({ type: "run-aborted" });
270
+ continueLoop = false;
271
+ return true;
272
+ };
218
273
  try {
219
274
  while (true) {
220
- if (signal.aborted) {
221
- await iterator.return?.(void 0);
222
- emitter({ type: "run-aborted" });
223
- continueLoop = false;
224
- break;
225
- }
275
+ if (await bailOnAbort()) break;
226
276
  const step = await iterator.next();
277
+ if (await bailOnAbort()) break;
227
278
  if (step.done) break;
228
279
  const event = step.value;
229
280
  if (event.type === "thinking") {
@@ -251,6 +302,10 @@ async function runStreamAgent(opts) {
251
302
  if (event.type === "error") {
252
303
  throw new Error(event.message);
253
304
  }
305
+ if (event.type !== "tool_use") {
306
+ warnUnknownStreamEvent(event);
307
+ continue;
308
+ }
254
309
  toolCallsThisTurn.push({ id: event.id, name: event.name, input: event.input });
255
310
  if (event.name === "create_artifact") {
256
311
  const input = event.input ?? {};
@@ -302,18 +357,19 @@ async function runStreamAgent(opts) {
302
357
  if (outcome.status === "aborted") {
303
358
  emitter({ type: "tool-aborted", toolCallId: event.id });
304
359
  continueLoop = false;
360
+ break;
305
361
  } else {
306
362
  emitter({ type: "tool-resolved", toolCallId: event.id, result: outcome.content });
307
363
  pushToolCallEnvelope(messages, append, toolCallsThisTurn, precedingText);
308
364
  append({ role: "tool_result", content: outcome.content, toolCallId: event.id });
309
365
  }
310
366
  }
367
+ if (xmlMachine) emitXml(xmlMachine.finalize());
311
368
  emitter({ type: "text-flush" });
312
369
  if (rawSegId) {
313
370
  const inline = rawContent.split("\n").length < 15;
314
371
  emitter({ type: "artifact-close", id: rawSegId, content: rawContent, inline });
315
372
  }
316
- if (xmlMachine) emitXml(xmlMachine.finalize());
317
373
  } finally {
318
374
  await iterator.return?.(void 0).catch(() => {
319
375
  });
@@ -350,10 +406,19 @@ async function invokeToolHandler(handler, call, signal, toolTimeoutMs) {
350
406
  const controller = new AbortController();
351
407
  const onParentAbort = () => controller.abort();
352
408
  signal.addEventListener("abort", onParentAbort, { once: true });
353
- const timeout = setTimeout(() => controller.abort(), toolTimeoutMs);
409
+ let timeout;
410
+ const timedOut = new Promise((resolve) => {
411
+ timeout = setTimeout(() => {
412
+ controller.abort();
413
+ resolve({ status: "aborted" });
414
+ }, toolTimeoutMs);
415
+ });
354
416
  try {
355
- const result = await handler(call, controller.signal);
356
- return { status: "resolved", content: result.content };
417
+ const result = await Promise.race([
418
+ handler(call, controller.signal).then((r) => ({ status: "resolved", content: r.content })),
419
+ timedOut
420
+ ]);
421
+ return result;
357
422
  } catch (err) {
358
423
  if (err?.name === "AbortError") return { status: "aborted" };
359
424
  throw err;
@@ -503,6 +568,13 @@ ${truncated}…`;
503
568
  reversedWindow.unshift(m);
504
569
  windowToks += t;
505
570
  }
571
+ while (reversedWindow.length && reversedWindow[0]?.role === "tool") {
572
+ const orphan = reversedWindow.shift();
573
+ if (orphan) {
574
+ windowToks -= estimateTokens(orphan.content);
575
+ dropped.oldTurns += 1;
576
+ }
577
+ }
506
578
  out.push(...reversedWindow);
507
579
  used.window = windowToks;
508
580
  return { compactedMessages: out, used, dropped };
@@ -540,7 +612,6 @@ export {
540
612
  assembleCompacted,
541
613
  compactConversation,
542
614
  computeHistoryBudget,
543
- deriveArtifactKind,
544
615
  estimateTokens,
545
616
  estimateTokensJson,
546
617
  runStreamAgent,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/agent/parsers/artifact-xml-state-machine.ts","../src/agent/stream-run.ts","../src/conversation/compactor.ts"],"sourcesContent":["/**\n * artifact-xml-state-machine.ts — streaming `<artifact>` XML parser (pure).\n *\n * The framework-free extraction of the XML-artifact branch inside\n * `AparteClient._streamLoop` (aparte-client.ts :1268-1392 + finalize :1658-1669).\n * Small models emit artifacts as `…chat text… <artifact mimeType=\"…\" title=\"…\">\n * BODY</artifact> …more text…`, streamed in arbitrary chunks that can split the\n * opening tag, the body, or the closing tag across delta boundaries.\n *\n * This module owns only the PARSING (state + buffering); it emits DOM-free\n * micro-events. The `@aparte/core` adapter turns them into segment/lifecycle calls\n * (addSegment / updateSegment / _dispatchArtifactLifecycle) exactly as\n * `_streamLoop` does — so no DOM, no `@aparte/core` import here.\n *\n * Ported faithfully, including quirks: a `<artifact` split as `<arti`+`fact`\n * (so `indexOf('<artifact')` misses it) is treated as chat text, matching the\n * source. Providers emit the tag atomically in practice.\n */\n\n/** Where the streaming parser is between artifacts. */\nexport type XmlArtifactState = 'normal' | 'scanning' | 'in-artifact';\n\n/** Fallback mime/kind for an artifact whose open tag omits the attribute. */\nexport interface XmlArtifactHint {\n mimeType: string;\n kind: string;\n}\n\n/** DOM-free micro-events the machine emits; the adapter renders them. */\nexport type XmlArtifactEvent =\n // `reduced: true` marks chat text that precedes an `<artifact>` open tag in\n // the same delta. `_streamLoop` renders that text through a REDUCED path\n // (aparte-client.ts :1300-1313: only completed segments are added; the trailing\n // active segment is NOT rendered until a later, tag-free delta). The adapter\n // must honor it or the pre-artifact text streams one update too eagerly.\n | { type: 'chat-text'; text: string; reduced?: boolean }\n | { type: 'artifact-open'; id: string; mimeType: string; kind: string; title: string }\n | { type: 'artifact-chunk'; id: string; content: string }\n | { type: 'artifact-close'; id: string; content: string; inline: boolean };\n\nconst CLOSE_TAG = '</artifact>';\nconst OPEN_TAG = '<artifact';\n/** Artifacts under this many lines render inline (mirrors `lineCount < 15`). */\nconst INLINE_MAX_LINES = 15;\n\n/**\n * Map an artifact mimeType to a renderer kind. Byte-identical copy of core's\n * canonical `deriveArtifactKind` (parsers/aparte-stream-parser.ts) — the\n * duplicate stays because `@aparte/core` is an OPTIONAL peer of the engine, so\n * no runtime import is possible. Kept in sync mechanically by\n * `__tests__/derive-artifact-kind-parity.test.ts`.\n */\nexport function deriveArtifactKind(mimeType: string, fallback = 'unknown'): string {\n const m = (mimeType || '').toLowerCase().trim();\n const ant = m.match(/^application\\/vnd\\.ant\\.([a-z0-9-]+)/);\n if (ant) return ant[1]!;\n if (m === 'text/html' || m === 'application/xhtml+xml') return 'html';\n if (m === 'application/javascript' || m === 'text/javascript') return 'js';\n if (m === 'text/css') return 'css';\n if (m === 'image/svg+xml') return 'svg';\n if (m === 'application/json') return 'json';\n if (m === 'text/markdown') return 'markdown';\n if (m === 'text/csv') return 'csv';\n if (m === 'text/plain') return 'text';\n if (m.includes('react')) return 'react';\n if (m.includes('html')) return 'html';\n if (m.includes('javascript')) return 'js';\n if (m.includes('css')) return 'css';\n if (m.includes('svg')) return 'svg';\n if (m.includes('json')) return 'json';\n if (m.includes('csv')) return 'csv';\n if (m.includes('markdown')) return 'markdown';\n return fallback;\n}\n\n/**\n * A stateful streaming parser for one turn's `<artifact>` blocks. Feed it each\n * text delta; drain the returned events. Call {@link finalize} when the stream\n * ends to flush a truncated (unclosed) artifact.\n */\nexport class ArtifactXmlStateMachine {\n private state: XmlArtifactState = 'normal';\n /** Buffers the opening tag until its `>` arrives (may span deltas). */\n private scanBuf = '';\n /** Buffers the tail that might be the start of a split `</artifact>`. */\n private closeBuf = '';\n private segId: string | null = null;\n private content = '';\n private mime = '';\n private kind = '';\n private title = '';\n private seq = 0;\n private readonly idGen: () => string;\n\n constructor(private readonly hint: XmlArtifactHint, idGen?: () => string) {\n this.idGen = idGen ?? (() => `artifact-xml-${this.seq++}`);\n }\n\n /** Feed one text delta; returns the ordered micro-events it produced. */\n feed(delta: string): XmlArtifactEvent[] {\n const out: XmlArtifactEvent[] = [];\n let remaining = delta;\n\n while (remaining.length > 0) {\n if (this.state === 'normal') {\n const tagStart = remaining.indexOf(OPEN_TAG);\n if (tagStart === -1) {\n out.push({ type: 'chat-text', text: remaining });\n remaining = '';\n } else {\n const before = remaining.slice(0, tagStart);\n if (before) out.push({ type: 'chat-text', text: before, reduced: true });\n this.scanBuf = remaining.slice(tagStart);\n remaining = '';\n this.state = 'scanning';\n }\n } else if (this.state === 'scanning') {\n // Accumulate until we have the full opening tag (ends with `>`).\n this.scanBuf += remaining;\n remaining = '';\n const gtIdx = this.scanBuf.indexOf('>');\n if (gtIdx !== -1) {\n const tag = this.scanBuf.slice(0, gtIdx + 1);\n this.mime = /mimeType=['\"]([^'\"]+)['\"]/.exec(tag)?.[1] ?? this.hint.mimeType;\n this.title = /title=['\"]([^'\"]+)['\"]/.exec(tag)?.[1] ?? this.hint.kind;\n this.kind = deriveArtifactKind(this.mime, this.hint.kind);\n this.segId = this.idGen();\n this.content = '';\n out.push({ type: 'artifact-open', id: this.segId, mimeType: this.mime, kind: this.kind, title: this.title });\n this.state = 'in-artifact';\n remaining = this.scanBuf.slice(gtIdx + 1);\n this.scanBuf = '';\n }\n } else {\n // in-artifact — stream body until `</artifact>` (may be split).\n const combined = this.closeBuf + remaining;\n const closeIdx = combined.indexOf(CLOSE_TAG);\n if (closeIdx !== -1) {\n this.content += combined.slice(0, closeIdx);\n const inline = this.content.split('\\n').length < INLINE_MAX_LINES;\n out.push({ type: 'artifact-close', id: this.segId!, content: this.content, inline });\n this.state = 'normal';\n this.closeBuf = '';\n remaining = combined.slice(closeIdx + CLOSE_TAG.length);\n } else {\n // Keep the last (CLOSE_TAG.length - 1) chars buffered in case the\n // closing tag is split across this and the next delta.\n const safeLen = Math.max(0, combined.length - CLOSE_TAG.length + 1);\n this.content += combined.slice(0, safeLen);\n this.closeBuf = combined.slice(safeLen);\n remaining = '';\n if (this.segId) out.push({ type: 'artifact-chunk', id: this.segId, content: this.content });\n }\n }\n }\n\n return out;\n }\n\n /**\n * Flush a truncated artifact: if the stream ended mid-body (model cut off\n * before `</artifact>`), emit a close with whatever was buffered. Mirrors\n * `_streamLoop`'s finalize block (:1658-1669).\n */\n finalize(): XmlArtifactEvent[] {\n if (this.state === 'in-artifact' && this.segId) {\n this.content += this.closeBuf;\n const inline = this.content.split('\\n').length < INLINE_MAX_LINES;\n const ev: XmlArtifactEvent = { type: 'artifact-close', id: this.segId, content: this.content, inline };\n this.state = 'normal';\n this.closeBuf = '';\n return [ev];\n }\n return [];\n }\n\n /** Current parser state (for the adapter to decide finalize routing). */\n get currentState(): XmlArtifactState {\n return this.state;\n }\n}\n","/**\n * stream-run.ts — framework-free structured-stream agent loop.\n *\n * The headless extraction of `AparteClient._streamLoop`: a `while(tool_use)` loop\n * that consumes a structured `AsyncIterable<StreamChatEvent>` from a transport,\n * runs approved tools, feeds their results back into the history, and re-calls\n * the transport until the model stops asking for tools. It performs **no DOM\n * work** — it emits {@link StreamRunEvent}s (in `_streamLoop`'s exact order) that\n * an adapter in `@aparte/core` turns into `targetElement.*` calls.\n *\n * Parity target: `@aparte/core`'s `AparteClient._streamLoop`.\n * Scope: text · thinking · tool_use (+ HITL) · done · error · artifacts (raw /\n * XML / create_artifact) · multi-phase pipeline · synthetic toolChoice bypass.\n * Code-fence promotion stays adapter-side (it needs the core parser).\n */\n\nimport type {\n StreamRunEmitter,\n StreamChatEvent,\n StreamChatRequest,\n StreamAgentMessage,\n StreamToolCall,\n StreamToolHandler,\n StreamToolConfig,\n StreamApprovalResolver,\n StreamUsage,\n} from './stream-events.js';\nimport { ArtifactXmlStateMachine, deriveArtifactKind, type XmlArtifactEvent } from './parsers/artifact-xml-state-machine.js';\n\n/** Default per-tool-call handler timeout — mirrors `TOOL_HANDLER_TIMEOUT_MS`. */\nconst DEFAULT_TOOL_TIMEOUT_MS = 5 * 60 * 1000;\n/** Default global turn cap — mirrors `AparteClientOptions.maxTurns ?? 10`. */\nconst DEFAULT_MAX_TURNS = 10;\n\n/**\n * One phase of a multi-phase pipeline (mirrors the shape of `ApartePipelinePhase`,\n * the pipeline-phase type defined in core's `types/chat.ts`). Supplied via\n * `baseRequest._meta.pipeline`; each phase runs as one turn with its own system\n * message, and an `'artifact'` phase streams the whole turn into a raw artifact.\n */\ntype PipelinePhase =\n | { mode: 'text'; system: string }\n | { mode: 'thinking'; system: string; label?: string }\n | { mode: 'artifact'; system: string; mimeType: string; kind: string };\n\nexport interface StreamRunOptions {\n /** Id of the assistant message being streamed (opaque; carried in events). */\n messageId: string;\n /** Turn-1 request; the loop clones its `messages` and enriches them per turn. */\n baseRequest: StreamChatRequest;\n /**\n * Calls the transport with the (possibly enriched) request. Returns the\n * structured stream, or a plain string for a non-streaming provider. Mirrors\n * `getTransport().chat(provider, request, auth, ctx)` with provider/auth/ctx\n * closed over by the adapter.\n */\n transportCall: (request: StreamChatRequest) => Promise<AsyncIterable<StreamChatEvent> | string>;\n /** Resolves a tool's handler by name (mirrors `AparteConfig.getToolHandler`). */\n toolLookup: (name: string) => StreamToolHandler | undefined;\n /** Resolves a tool's loop config by name (maxTurns / needsApproval). */\n toolConfigLookup?: (name: string) => StreamToolConfig | undefined;\n /** HITL approval resolver for `needsApproval` tools (default: never called). */\n approvalResolver?: StreamApprovalResolver;\n /** Synchronous, ordered event sink consumed by the adapter. */\n emitter: StreamRunEmitter;\n /** Single abort signal composing `_isAborted` + the stream controller. */\n signal: AbortSignal;\n /** Global turn cap. @default 10 */\n maxTurns?: number;\n /** Per-tool-call handler timeout in ms. @default 300000 */\n toolTimeoutMs?: number;\n /**\n * Called for every turn the loop appends to the history — the grouped\n * `tool_call` envelope, each `tool_result` (resolved or rejected), and a\n * pipeline phase's reply — in order, and always before the transport call that\n * would carry it. Never called for the messages you passed in `baseRequest`.\n *\n * For hosts that own their own transcript. The loop re-sends its `messages`\n * array every turn, which suits stateless message APIs but not a **prefix\n * cache** (llama.cpp slots, vLLM), where turn N+1 must EXTEND turn N byte for\n * byte. Such a host builds its own request in {@link transportCall} — ignoring\n * `request.messages` — and mirrors these notifications into its append-only\n * log, instead of reimplementing the loop's tool bookkeeping. Synchronous and\n * ordered, like {@link emitter}.\n */\n onHistoryAppend?: (message: StreamAgentMessage) => void;\n /**\n * Generates artifact segment ids (`prefix` is e.g. `'artifact-raw'`). The\n * default is a deterministic per-run counter; the adapter injects a\n * crypto-based one to match `_streamLoop`'s `artifact-*-<uuid>`. (Tool ids\n * still flow from the stream; only artifacts need generated ids.)\n */\n idGen?: (prefix: string) => string;\n}\n\n/**\n * Run the structured-stream agent loop. Resolves the last turn's usage (the\n * `done{usage}` last-write-wins, mirroring `_streamLoop`'s return), or\n * `undefined`. Throws on a stream `error` event or a non-abort tool failure —\n * the caller (adapter) routes that to its lifecycle-error handler, exactly as\n * `_handleSend`/`_handleRetry`/`_handleEdit` catch `_streamLoop`.\n */\nexport async function runStreamAgent(opts: StreamRunOptions): Promise<StreamUsage | undefined> {\n const {\n transportCall,\n toolLookup,\n toolConfigLookup,\n approvalResolver,\n emitter,\n signal,\n maxTurns = DEFAULT_MAX_TURNS,\n toolTimeoutMs = DEFAULT_TOOL_TIMEOUT_MS,\n } = opts;\n\n // Reassignable: the synthetic-toolChoice bypass strips `toolChoice`/`tools`\n // from it after the forced turn-1 handler runs (mirrors `_streamLoop`'s\n // `baseRequest = { ...baseRequest, toolChoice: 'none', tools: undefined }`).\n let baseRequest = opts.baseRequest;\n\n let idSeq = 0;\n const idGen = opts.idGen ?? ((prefix: string) => `${prefix}-${idSeq++}`);\n\n // Mutable history the loop enriches with tool_call/tool_result turns. Every\n // enrichment goes through `append`, so a caller owning its own transcript sees\n // the same turns in the same order (see `onHistoryAppend`).\n const messages: StreamAgentMessage[] = [...baseRequest.messages];\n const append = (message: StreamAgentMessage): void => {\n messages.push(message);\n opts.onHistoryAppend?.(message);\n };\n\n // Pipeline mode (mirrors _streamLoop): each phase is one turn with\n // its own system message; phase N's reply is appended before phase N+1.\n const pipeline = (baseRequest['_meta'] as Record<string, unknown> | undefined)?.['pipeline'] as\n | PipelinePhase[]\n | undefined;\n let pipelineIndex = 0;\n\n let continueLoop = true;\n let turns = 0;\n let lastUsage: StreamUsage | undefined;\n\n // Leading write before turn 1: mark the message streaming.\n emitter({ type: 'run-start' });\n\n // ── outer (turn) loop — one iteration = one transport call ────────────────\n while (continueLoop) {\n // Abort at the top of a turn: no stream open yet, so no cancel needed.\n if (signal.aborted) {\n emitter({ type: 'run-aborted' });\n break;\n }\n\n turns++;\n if (turns > maxTurns) {\n emitter({ type: 'turn-limit-exceeded', scope: 'global', limit: maxTurns });\n break;\n }\n\n // ── Synthetic toolChoice bypass (mirrors _streamLoop) ──────\n // toolChoice = { name, input } (orchestrator-forced): skip the LLM for\n // turn 1, run the handler directly, inject its result as a tool_result,\n // then strip toolChoice/tools and fall through to the transport call in\n // the SAME turn so the model answers with the tool result already in\n // history. (The adapter's tool-start handler injects renderer CSS here\n // too — a benign, idempotent gain over _streamLoop's synthetic path,\n // which skipped it; reconciled in the parity test.)\n const toolChoice = baseRequest['toolChoice'];\n if (\n turns === 1 &&\n toolChoice &&\n typeof toolChoice === 'object' &&\n !Array.isArray(toolChoice) &&\n (toolChoice as { input?: unknown }).input !== undefined\n ) {\n const tc = toolChoice as { name: string; input: unknown };\n const syntheticId = idGen('synthetic-tool');\n emitter({ type: 'tool-start', toolCallId: syntheticId, name: tc.name, input: tc.input });\n\n const handler = toolLookup(tc.name);\n if (!handler) {\n emitter({ type: 'tool-aborted', toolCallId: syntheticId });\n continueLoop = false;\n continue;\n }\n\n const outcome = await invokeToolHandler(handler, { id: syntheticId, name: tc.name, input: tc.input }, signal, toolTimeoutMs);\n if (outcome.status === 'aborted') {\n emitter({ type: 'tool-aborted', toolCallId: syntheticId });\n continueLoop = false;\n continue;\n }\n emitter({ type: 'tool-resolved', toolCallId: syntheticId, result: outcome.content });\n append({ role: 'tool_call', content: '', toolCalls: [{ id: syntheticId, name: tc.name, input: tc.input }] });\n append({ role: 'tool_result', content: outcome.content, toolCallId: syntheticId });\n baseRequest = { ...baseRequest, toolChoice: 'none', tools: undefined };\n }\n\n // ── Per-phase request build when pipeline is active ──────\n // Prepend the current phase's system message and, for an 'artifact'\n // phase, inject the artifactRaw hint the streaming loop below reads.\n let phaseMessages = messages;\n let phaseMeta = baseRequest['_meta'] as Record<string, unknown> | undefined;\n if (pipeline && pipelineIndex < pipeline.length) {\n const phase = pipeline[pipelineIndex]!;\n phaseMessages = [{ role: 'system', content: phase.system }, ...messages];\n if (phase.mode === 'artifact') {\n phaseMeta = { ...phaseMeta, artifactRaw: { mimeType: phase.mimeType, kind: phase.kind } };\n } else {\n // Drop any stale artifactRaw / pipeline keys from a text phase.\n const rest = { ...(phaseMeta ?? {}) };\n delete rest['artifactRaw'];\n delete rest['pipeline'];\n phaseMeta = rest;\n }\n }\n\n const request: StreamChatRequest = { ...baseRequest, messages: phaseMessages, _meta: phaseMeta };\n const response = await transportCall(request);\n\n // Non-streaming provider: the string IS the full assistant message. The\n // adapter writes it and completes (no done{usage}); spike scenarios\n // always stream, so we finish the run here.\n if (typeof response === 'string') {\n emitter({ type: 'text-delta', delta: response });\n break;\n }\n\n // New turn: the adapter resets its per-turn parser / thinking / streaming\n // state here (mirrors `_streamLoop` creating a fresh AparteStreamParser +\n // sets each outer iteration). No DOM effect of its own.\n emitter({ type: 'turn-start' });\n\n // artifactRaw mode: the WHOLE turn's text streams into one artifact\n // segment (mirrors _streamLoop). Open it up-front, before the\n // first delta, exactly as _streamLoop does in its per-turn setup.\n const rawHint = (request['_meta'] as Record<string, unknown> | undefined)?.['artifactRaw'] as\n | { mimeType: string; kind: string }\n | undefined;\n let rawSegId: string | null = null;\n let rawContent = '';\n if (rawHint) {\n rawSegId = idGen('artifact-raw');\n emitter({ type: 'artifact-open', id: rawSegId, mimeType: rawHint.mimeType, kind: rawHint.kind, title: rawHint.kind });\n }\n\n // artifactXml mode (mutually exclusive with raw; raw wins, matching the\n // _streamLoop branch order). The E2 state machine parses inline\n // `<artifact>` tags; its micro-events map 1:1 to StreamRunEvents.\n const xmlHint = (request['_meta'] as Record<string, unknown> | undefined)?.['artifactXml'] as\n | { mimeType: string; kind: string }\n | undefined;\n const xmlMachine = (xmlHint && !rawHint)\n ? new ArtifactXmlStateMachine(xmlHint, () => idGen('artifact-xml'))\n : null;\n const emitXml = (events: XmlArtifactEvent[]): void => {\n for (const ev of events) {\n if (ev.type === 'chat-text') emitter({ type: 'text-delta', delta: ev.text, ...(ev.reduced ? { reduced: true } : {}) });\n else if (ev.type === 'artifact-open') emitter({ type: 'artifact-open', id: ev.id, mimeType: ev.mimeType, kind: ev.kind, title: ev.title });\n else if (ev.type === 'artifact-chunk') emitter({ type: 'artifact-chunk', id: ev.id, content: ev.content });\n else emitter({ type: 'artifact-close', id: ev.id, content: ev.content, inline: ev.inline });\n }\n };\n\n // Per-turn streaming state.\n let precedingText = '';\n const toolCallsThisTurn: StreamToolCall[] = [];\n\n // ── inner (SSE) loop — manual iteration so we can abort before each read\n const iterator = response[Symbol.asyncIterator]();\n try {\n while (true) {\n if (signal.aborted) {\n await iterator.return?.(undefined);\n emitter({ type: 'run-aborted' });\n continueLoop = false;\n break;\n }\n\n const step = await iterator.next();\n if (step.done) break;\n const event = step.value;\n\n if (event.type === 'thinking') {\n emitter({ type: 'thinking-delta', delta: event.delta });\n continue;\n }\n\n if (event.type === 'text') {\n precedingText += event.delta;\n if (rawSegId) {\n // artifactRaw: route the whole delta into the artifact,\n // never through the text parser (mirrors the raw-artifact path in _streamLoop).\n rawContent += event.delta;\n emitter({ type: 'artifact-chunk', id: rawSegId, content: rawContent });\n continue;\n }\n if (xmlMachine) {\n // artifactXml: run the delta through the state machine\n // (mirrors the XML-artifact path in _streamLoop); it splits chat text from artifacts.\n emitXml(xmlMachine.feed(event.delta));\n continue;\n }\n emitter({ type: 'text-delta', delta: event.delta });\n continue;\n }\n\n if (event.type === 'done') {\n if (event.usage) lastUsage = event.usage;\n continue;\n }\n\n if (event.type === 'error') {\n // Mirror `_streamLoop`: throw; the caller's catch handles it.\n throw new Error(event.message);\n }\n\n // event.type === 'tool_use'\n toolCallsThisTurn.push({ id: event.id, name: event.name, input: event.input });\n\n // Built-in create_artifact: bypass the generic tool path entirely\n // (no tool-start, no approval, no handler) — build the artifact\n // one-shot and inject a success tool_result (mirrors the create_artifact fast-path in _streamLoop).\n if (event.name === 'create_artifact') {\n const input = (event.input ?? {}) as { mimeType?: string; title?: string; content?: string };\n const mimeType = input.mimeType ?? 'text/plain';\n const kind = deriveArtifactKind(mimeType, 'text');\n emitter({ type: 'artifact-ready', id: `artifact-${event.id}`, mimeType, kind, title: input.title ?? kind, content: input.content ?? '' });\n append({ role: 'tool_call', content: '', toolCalls: [{ id: event.id, name: event.name, input: event.input }] });\n append({ role: 'tool_result', content: 'Artifact created successfully.', toolCallId: event.id });\n continue;\n }\n\n emitter({ type: 'tool-start', toolCallId: event.id, name: event.name, input: event.input });\n\n const cfg = toolConfigLookup?.(event.name);\n\n // Per-tool maxTurns (note: `>=`, stricter than the global `>`).\n const effectiveMaxTurns = cfg?.maxTurns ?? maxTurns;\n if (turns >= effectiveMaxTurns) {\n emitter({ type: 'turn-limit-exceeded', scope: 'tool', limit: effectiveMaxTurns, toolCallId: event.id });\n continueLoop = false;\n break;\n }\n\n const handler = toolLookup(event.name);\n if (!handler) {\n emitter({ type: 'tool-aborted', toolCallId: event.id });\n continueLoop = false;\n break;\n }\n\n let effectiveInput = event.input;\n\n // ── HITL approval gate ────────────────────────────────────────\n if (cfg?.needsApproval) {\n emitter({ type: 'tool-awaiting-approval', toolCallId: event.id, name: event.name, input: event.input });\n const resolve = approvalResolver ?? (async () => ({ approved: false }));\n const decision = await resolve(event.id, signal);\n\n if (!decision.approved) {\n const rejection = 'Tool execution was rejected by the user.';\n emitter({ type: 'tool-rejected', toolCallId: event.id, reason: rejection });\n pushToolCallEnvelope(messages, append, toolCallsThisTurn, precedingText);\n append({ role: 'tool_result', content: rejection, toolCallId: event.id });\n continueLoop = false;\n break;\n }\n if (decision.payload && typeof decision.payload === 'object' && !Array.isArray(decision.payload)) {\n effectiveInput = { ...(event.input as Record<string, unknown>), ...(decision.payload as Record<string, unknown>) };\n }\n emitter({ type: 'tool-approved', toolCallId: event.id });\n }\n\n // ── handler invocation with per-call timeout ──────────────────\n const outcome = await invokeToolHandler(\n handler,\n { id: event.id, name: event.name, input: effectiveInput },\n signal,\n toolTimeoutMs,\n );\n if (outcome.status === 'aborted') {\n emitter({ type: 'tool-aborted', toolCallId: event.id });\n continueLoop = false;\n } else {\n emitter({ type: 'tool-resolved', toolCallId: event.id, result: outcome.content });\n pushToolCallEnvelope(messages, append, toolCallsThisTurn, precedingText);\n append({ role: 'tool_result', content: outcome.content, toolCallId: event.id });\n }\n }\n\n // Turn boundary: finalize the parser (flush residual text). Mirrors\n // `_streamLoop`'s `textParser.finalize()` call — runs on normal\n // end AND abort-break, but NOT after a thrown `error` (which escapes\n // this try before reaching here, exactly like `_streamLoop`).\n emitter({ type: 'text-flush' });\n\n // artifactRaw close comes right after the parser flush, matching the\n // finalize-block order in _streamLoop (text-flush, then artifact-close).\n if (rawSegId) {\n const inline = rawContent.split('\\n').length < 15;\n emitter({ type: 'artifact-close', id: rawSegId, content: rawContent, inline });\n }\n // artifactXml finalize flushes a truncated (unclosed) artifact —\n // after text-flush, mirroring the finalize-block order in _streamLoop.\n if (xmlMachine) emitXml(xmlMachine.finalize());\n } finally {\n // Mirror `reader.releaseLock()` in the finally: settle the iterator.\n await iterator.return?.(undefined).catch(() => { /* best effort */ });\n }\n\n // No tool calls this turn → final answer, OR advance to the next pipeline\n // phase (mirrors _streamLoop).\n if (toolCallsThisTurn.length === 0) {\n if (pipeline && pipelineIndex < pipeline.length - 1) {\n // Feed this phase's reply into history as context for the next.\n if (precedingText.trim()) {\n append({ role: 'assistant', content: precedingText.trim() });\n }\n pipelineIndex++;\n emitter({ type: 'phase-advance', index: pipelineIndex });\n // continueLoop stays true — the next iteration runs the new phase.\n } else {\n continueLoop = false;\n }\n }\n }\n\n // Post-loop finalization runs on every exit path (normal / abort / maxTurns).\n emitter({ type: 'run-done', usage: lastUsage });\n return lastUsage;\n}\n\n/**\n * Push the single grouped `tool_call` envelope for the turn — but only once,\n * even when the turn has several tool calls (each call's `tool_result` is pushed\n * separately). Mirrors `_streamLoop`'s `existingToolCallMsg` guard. Reads\n * `messages` for that guard and writes through `append`, so the duplicate\n * suppression also applies to the `onHistoryAppend` notification.\n */\nfunction pushToolCallEnvelope(\n messages: StreamAgentMessage[],\n append: (message: StreamAgentMessage) => void,\n toolCallsThisTurn: StreamToolCall[],\n precedingText: string,\n): void {\n const exists = messages.some(\n m => m.role === 'tool_call' && m.toolCalls?.some(tc => toolCallsThisTurn.some(t => t.id === tc.id)),\n );\n if (exists) return;\n append({\n role: 'tool_call',\n content: '',\n toolCalls: toolCallsThisTurn,\n precedingText: precedingText.trim() || undefined,\n });\n}\n\n/**\n * Run a tool handler with a per-call timeout, linked to the run's abort signal.\n * Resolves `{ status: 'resolved', content }` on success, or `{ status: 'aborted' }`\n * if the handler aborts (timeout OR parent abort); any other error is re-thrown so\n * the caller routes it to lifecycle-error, exactly as `_streamLoop` does. Shared\n * by the generic tool path and the synthetic-toolChoice bypass (mirrors the\n * AbortController + `TOOL_HANDLER_TIMEOUT_MS` dance both use in `_streamLoop`).\n */\nasync function invokeToolHandler(\n handler: StreamToolHandler,\n call: StreamToolCall,\n signal: AbortSignal,\n toolTimeoutMs: number,\n): Promise<{ status: 'resolved'; content: string } | { status: 'aborted' }> {\n // If the run was already aborted before we got here, don't invoke the\n // handler at all: a past 'abort' event will never re-fire on the listener\n // below, so the handler would otherwise run to completion despite cancel.\n if (signal.aborted) return { status: 'aborted' };\n\n const controller = new AbortController();\n const onParentAbort = () => controller.abort();\n signal.addEventListener('abort', onParentAbort, { once: true });\n const timeout = setTimeout(() => controller.abort(), toolTimeoutMs);\n try {\n const result = await handler(call, controller.signal);\n return { status: 'resolved', content: result.content };\n } catch (err: unknown) {\n if ((err as { name?: string })?.name === 'AbortError') return { status: 'aborted' };\n throw err;\n } finally {\n clearTimeout(timeout);\n signal.removeEventListener('abort', onParentAbort);\n }\n}\n","/**\n * conversation/compactor.ts — adaptive budgeting + context-window assembly.\n *\n * Conversation history budgeting and sliding-window assembly —\n * framework-free and tokenizer-free (char-count heuristic).\n *\n * Pattern (cf. Claude Code /context, OpenAI Agents SDK TrimmingSession):\n * Budget = CONTEXT_WINDOW − systemPrompt − tools − reservedThinking\n * − reservedGeneration − autocompactBuffer − safetyMargin\n *\n * The history budget is then split into:\n * - summary (global summary, LLM-generated, ~10%)\n * - ragHist (older turns retrieved by cosine similarity, ~25%)\n * - window (verbatim sliding window, the rest)\n *\n * Drop priority on overflow:\n * 1. summary (async-regenerable)\n * 2. ragHist (retrievable on the next turn)\n * 3. window oldest turns\n * X. NEVER dropped: currentUser + lastAssistant (absolute working memory)\n *\n * Browser-portable: zero deps, just an estimateTokens heuristic.\n */\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport interface CompactionMessage {\n role: 'system' | 'user' | 'assistant' | 'tool';\n content: string;\n}\n\nexport interface RetrievedTurn {\n role: 'user' | 'assistant';\n content: string;\n score?: number;\n}\n\nexport interface CompactionConfig {\n /** Total context window of the active model, in tokens. */\n contextWindow: number;\n /** Reserved budget for the model's thinking/reasoning block, in tokens. */\n reservedThinking: number;\n /** Reserved budget for the assistant response (max_new_tokens cap). */\n reservedGeneration: number;\n /** Fraction of context_window kept as autocompact buffer (0..1). */\n autocompactBufferPct: number;\n /** Hard safety margin in tokens. */\n safetyMargin: number;\n /** Floor for history budget — never compact below this. */\n minHistoryBudget: number;\n /** Ratio of history budget allocated to the summary block. */\n summaryRatio: number;\n /** Hard cap for summary tokens. */\n summaryMaxTokens: number;\n /** Ratio of history budget allocated to the RAG-retrieved old turns. */\n ragHistRatio: number;\n /** Hard cap for ragHist tokens. */\n ragHistMaxTokens: number;\n /** Trigger summarization once history usage reaches this % of the budget. */\n triggerSummaryThresholdPct: number;\n /** Re-run summarization every N user turns. */\n summarizeEveryNTurns: number;\n /** Header prepended to the summary block injected into the model context. English\n * by default — override to localise (the compactor ships no locale system). */\n summaryLabel: string;\n /** Header prepended to the RAG-retrieved old turns injected into the context. */\n ragIntroLabel: string;\n}\n\nexport interface BudgetBreakdown {\n contextWindow: number;\n systemPrompt: number;\n tools: number;\n reservedThinking: number;\n reservedGeneration: number;\n autocompactBuffer: number;\n safetyMargin: number;\n historyAvailable: number;\n}\n\nexport interface BudgetResult {\n historyBudget: number;\n breakdown: BudgetBreakdown;\n config: CompactionConfig;\n}\n\nexport interface SplitBudget {\n summary: number;\n ragHist: number;\n window: number;\n}\n\nexport interface UsageBreakdown {\n system: number;\n summary: number;\n ragHist: number;\n window: number;\n}\n\nexport interface DroppedBreakdown {\n ragHits: number;\n oldTurns: number;\n summary: boolean;\n}\n\nexport interface FullBreakdown extends BudgetBreakdown {\n historyAllocated: SplitBudget;\n historyUsed: UsageBreakdown;\n totalUsed: number;\n /** Free tokens left in the context window after all reservations + actual use. */\n free: number;\n dropped: DroppedBreakdown;\n}\n\nexport interface CompactionInput {\n messages: CompactionMessage[];\n systemPrompt: string;\n /** Tools array passed to apply_chat_template (or null/undefined when no tools). */\n toolsArray?: unknown;\n /** Running summary text (regenerable). */\n summary?: string;\n /** RAG-retrieved old turns (older than the sliding window). */\n retrievedTurns?: RetrievedTurn[];\n /** Partial override of the default config. */\n config?: Partial<CompactionConfig>;\n}\n\nexport interface CompactionResult {\n compactedMessages: CompactionMessage[];\n breakdown: FullBreakdown;\n}\n\n// ─── Constants ──────────────────────────────────────────────────────────────\n\nexport const DEFAULT_COMPACTION_CONFIG: CompactionConfig = {\n // Conservative model-agnostic defaults — the consuming app overrides\n // `contextWindow` / `reservedThinking` with its model's real values.\n contextWindow: 8192,\n reservedThinking: 0,\n reservedGeneration: 2000,\n autocompactBufferPct: 0.10, // ~3300 tok\n safetyMargin: 500,\n minHistoryBudget: 1000,\n\n summaryRatio: 0.10,\n summaryMaxTokens: 400,\n ragHistRatio: 0.25,\n ragHistMaxTokens: 1000,\n\n triggerSummaryThresholdPct: 0.75,\n summarizeEveryNTurns: 5,\n\n summaryLabel: 'Conversation summary:',\n ragIntroLabel: 'Relevant excerpts from earlier in the conversation:',\n};\n\n// ─── Token estimation ──────────────────────────────────────────────────────\n\n/**\n * Token heuristic (FR ~3.5 chars/tok, EN ~4 chars/tok).\n * Accurate to ±10% — enough for budgeting, and it avoids `tokenizer.encode()`,\n * which costs ~5ms per message × N (too slow to run on every turn).\n */\nexport function estimateTokens(text: string | null | undefined): number {\n if (!text) return 0;\n return Math.ceil(text.length / 3.8);\n}\n\n/**\n * Estimate tokens for a JSON-serializable structure (e.g. tools array).\n */\nexport function estimateTokensJson(obj: unknown): number {\n if (!obj) return 0;\n try {\n return estimateTokens(JSON.stringify(obj));\n } catch {\n return 0;\n }\n}\n\n// ─── Budget computation ────────────────────────────────────────────────────\n\n/**\n * Compute the available history budget after subtracting fixed costs.\n */\nexport function computeHistoryBudget(input: {\n systemPrompt: string;\n toolsArray?: unknown;\n config?: Partial<CompactionConfig>;\n}): BudgetResult {\n const cfg: CompactionConfig = { ...DEFAULT_COMPACTION_CONFIG, ...(input.config ?? {}) };\n\n const systemTokens = estimateTokens(input.systemPrompt);\n const toolsTokens = estimateTokensJson(input.toolsArray);\n const autocompactBuffer = Math.floor(cfg.contextWindow * cfg.autocompactBufferPct);\n\n const fixed = systemTokens + toolsTokens\n + cfg.reservedThinking + cfg.reservedGeneration\n + autocompactBuffer + cfg.safetyMargin;\n\n const historyBudget = Math.max(cfg.minHistoryBudget, cfg.contextWindow - fixed);\n\n return {\n historyBudget,\n breakdown: {\n contextWindow: cfg.contextWindow,\n systemPrompt: systemTokens,\n tools: toolsTokens,\n reservedThinking: cfg.reservedThinking,\n reservedGeneration: cfg.reservedGeneration,\n autocompactBuffer,\n safetyMargin: cfg.safetyMargin,\n historyAvailable: historyBudget,\n },\n config: cfg,\n };\n}\n\n/**\n * Split history budget into summary / ragHist / window slots.\n */\nexport function splitHistoryBudget(\n historyBudget: number,\n cfg: CompactionConfig = DEFAULT_COMPACTION_CONFIG,\n): SplitBudget {\n const summary = Math.min(cfg.summaryMaxTokens, Math.floor(historyBudget * cfg.summaryRatio));\n const ragHist = Math.min(cfg.ragHistMaxTokens, Math.floor(historyBudget * cfg.ragHistRatio));\n const window = historyBudget - summary - ragHist;\n return { summary, ragHist, window };\n}\n\n// ─── Message assembly ──────────────────────────────────────────────────────\n\ninterface AssembleParams {\n messages: CompactionMessage[];\n summary?: string;\n retrievedTurns?: RetrievedTurn[];\n windowBudget: number;\n summaryBudget: number;\n ragBudget: number;\n systemContent?: string;\n /** Context-injection headers; default to English (DEFAULT_COMPACTION_CONFIG). */\n summaryLabel?: string;\n ragIntroLabel?: string;\n}\n\ninterface AssembleResult {\n compactedMessages: CompactionMessage[];\n used: UsageBreakdown;\n dropped: DroppedBreakdown;\n}\n\n/**\n * Assemble compacted message list given a budget and conversation state.\n *\n * Output order:\n * [system?] → [summary system msg?] → [ragHist system msg?] → window verbatim\n *\n * Always keeps the last 2 non-system messages verbatim (working memory),\n * regardless of windowBudget — these are the floor that can never be dropped.\n */\nexport function assembleCompacted(params: AssembleParams): AssembleResult {\n const {\n messages,\n summary = '',\n retrievedTurns = [],\n windowBudget,\n summaryBudget,\n ragBudget,\n systemContent,\n summaryLabel = DEFAULT_COMPACTION_CONFIG.summaryLabel,\n ragIntroLabel = DEFAULT_COMPACTION_CONFIG.ragIntroLabel,\n } = params;\n\n const out: CompactionMessage[] = [];\n const used: UsageBreakdown = { system: 0, summary: 0, ragHist: 0, window: 0 };\n const dropped: DroppedBreakdown = { ragHits: 0, oldTurns: 0, summary: false };\n\n // System message first\n if (systemContent) {\n out.push({ role: 'system', content: systemContent });\n used.system = estimateTokens(systemContent);\n }\n\n // Summary\n if (summary) {\n const summaryWrapped = `${summaryLabel}\\n${summary}`;\n const summaryToks = estimateTokens(summaryWrapped);\n if (summaryToks <= summaryBudget) {\n out.push({ role: 'system', content: summaryWrapped });\n used.summary = summaryToks;\n } else {\n const ratio = (summaryBudget / summaryToks) * 0.95;\n const truncated = summary.slice(0, Math.max(0, Math.floor(summary.length * ratio)));\n const wrapped = `${summaryLabel}\\n${truncated}…`;\n out.push({ role: 'system', content: wrapped });\n used.summary = estimateTokens(wrapped);\n dropped.summary = true;\n }\n }\n\n // RAG hist — old turns retrieved by cosine similarity, sorted by score desc\n if (retrievedTurns.length > 0) {\n const sorted = [...retrievedTurns].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));\n const lines: string[] = [];\n const intro = `${ragIntroLabel}\\n`;\n let toks = estimateTokens(intro);\n for (const t of sorted) {\n const line = `[${t.role}] ${t.content}`;\n const lt = estimateTokens(line);\n if (toks + lt > ragBudget) {\n dropped.ragHits++;\n continue;\n }\n lines.push(line);\n toks += lt;\n }\n if (lines.length > 0) {\n const content = intro + lines.join('\\n');\n out.push({ role: 'system', content });\n used.ragHist = estimateTokens(content);\n }\n }\n\n // Sliding window verbatim — newest → oldest, until windowBudget exhausted.\n // Always keep the last 2 non-system messages (working memory floor).\n const nonSystem = messages.filter(m => m.role !== 'system');\n if (nonSystem.length === 0) {\n return { compactedMessages: out, used, dropped };\n }\n\n const reversedWindow: CompactionMessage[] = [];\n let windowToks = 0;\n const minKeep = Math.min(2, nonSystem.length);\n\n for (let i = nonSystem.length - 1; i >= 0; i--) {\n const m = nonSystem[i];\n if (!m) continue;\n const t = estimateTokens(m.content);\n const idxFromEnd = nonSystem.length - 1 - i;\n\n if (idxFromEnd < minKeep) {\n // Working memory floor — kept no matter the budget.\n reversedWindow.unshift(m);\n windowToks += t;\n continue;\n }\n\n if (windowToks + t > windowBudget) {\n dropped.oldTurns += i + 1;\n break;\n }\n reversedWindow.unshift(m);\n windowToks += t;\n }\n\n out.push(...reversedWindow);\n used.window = windowToks;\n\n return { compactedMessages: out, used, dropped };\n}\n\n// ─── High-level helper ─────────────────────────────────────────────────────\n\n/**\n * Compute budget + assemble in one call. The primary entry point for\n * consumers (a browser request interceptor, mobile, Node tests).\n */\nexport function compactConversation(input: CompactionInput): CompactionResult {\n const { messages, systemPrompt, toolsArray, summary, retrievedTurns, config } = input;\n\n const budget = computeHistoryBudget({ systemPrompt, toolsArray, config });\n const split = splitHistoryBudget(budget.historyBudget, budget.config);\n\n const { compactedMessages, used, dropped } = assembleCompacted({\n messages,\n summary,\n retrievedTurns,\n windowBudget: split.window,\n summaryBudget: split.summary,\n ragBudget: split.ragHist,\n systemContent: systemPrompt,\n summaryLabel: budget.config.summaryLabel,\n ragIntroLabel: budget.config.ragIntroLabel,\n });\n\n const totalUsed = budget.breakdown.systemPrompt\n + budget.breakdown.tools\n + used.summary\n + used.ragHist\n + used.window;\n\n const free = budget.config.contextWindow - totalUsed\n - budget.config.reservedThinking - budget.config.reservedGeneration\n - budget.breakdown.autocompactBuffer - budget.config.safetyMargin;\n\n const breakdown: FullBreakdown = {\n ...budget.breakdown,\n historyAllocated: split,\n historyUsed: used,\n totalUsed,\n free,\n dropped,\n };\n\n return { compactedMessages, breakdown };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/agent/parsers/artifact-xml-state-machine.ts","../src/agent/stream-run.ts","../src/conversation/compactor.ts"],"sourcesContent":["/**\n * artifact-xml-state-machine.ts — streaming `<artifact>` XML parser (pure).\n *\n * The framework-free extraction of core's `client/xml-artifact-feed.ts` (the\n * scanning half) plus the `XML artifact finalize` block inside\n * `AparteClient._streamLoop`.\n * Small models emit artifacts as `…chat text… <artifact mimeType=\"…\" title=\"…\">\n * BODY</artifact> …more text…`, streamed in arbitrary chunks that can split the\n * opening tag, the body, or the closing tag across delta boundaries.\n *\n * This module owns only the PARSING (state + buffering); it emits DOM-free\n * micro-events. The `@aparte/core` adapter turns them into segment/lifecycle calls\n * (addSegment / updateSegment / _dispatchArtifactLifecycle) exactly as\n * `_streamLoop` does — so no DOM, no `@aparte/core` import here.\n *\n * Ported faithfully, including quirks: a `<artifact` split as `<arti`+`fact`\n * (so `indexOf('<artifact')` misses it) is treated as chat text, matching the\n * source. Providers emit the tag atomically in practice.\n */\n\n/** Where the streaming parser is between artifacts. */\nexport type XmlArtifactState = 'normal' | 'scanning' | 'in-artifact';\n\n/** Fallback mime/kind for an artifact whose open tag omits the attribute. */\nexport interface XmlArtifactHint {\n mimeType: string;\n kind: string;\n}\n\n/** DOM-free micro-events the machine emits; the adapter renders them. */\nexport type XmlArtifactEvent =\n // `reduced: true` marks chat text that precedes an `<artifact>` open tag in\n // the same delta. Core renders that text through a REDUCED path — its twin's\n // `emitChatText(text, syncActive = false)`: only completed segments are added,\n // the trailing active segment is NOT rendered until a later, tag-free delta.\n // The adapter must honor it or the pre-artifact text streams one update too\n // eagerly.\n | { type: 'chat-text'; text: string; reduced?: boolean }\n | { type: 'artifact-open'; id: string; mimeType: string; kind: string; title: string }\n | { type: 'artifact-chunk'; id: string; content: string }\n | { type: 'artifact-close'; id: string; content: string; inline: boolean };\n\nconst CLOSE_TAG = '</artifact>';\nconst OPEN_TAG = '<artifact';\n\n/**\n * Length of the longest suffix of `text` that is a PROPER prefix of `<artifact`\n * (0 when there is none). Lets the machine hold `…<arti` back instead of\n * emitting it as chat text and losing the artifact that follows it.\n */\nfunction partialOpenTagLength(text: string): number {\n const max = Math.min(text.length, OPEN_TAG.length - 1);\n for (let k = max; k > 0; k--) {\n if (text.endsWith(OPEN_TAG.slice(0, k))) return k;\n }\n return 0;\n}\n/** Artifacts under this many lines render inline (mirrors `lineCount < 15`). */\nconst INLINE_MAX_LINES = 15;\n\n/**\n * Map an artifact mimeType to a renderer kind. Byte-identical copy of core's\n * canonical `deriveArtifactKind` (parsers/aparte-stream-parser.ts) — the\n * duplicate stays because `@aparte/core` is an OPTIONAL peer of the engine, so\n * no runtime import is possible. Kept in sync mechanically by\n * `__tests__/derive-artifact-kind-parity.test.ts`.\n */\nexport function deriveArtifactKind(mimeType: string, fallback = 'unknown'): string {\n const m = (mimeType || '').toLowerCase().trim();\n const ant = m.match(/^application\\/vnd\\.ant\\.([a-z0-9-]+)/);\n if (ant) return ant[1]!;\n if (m === 'text/html' || m === 'application/xhtml+xml') return 'html';\n if (m === 'application/javascript' || m === 'text/javascript') return 'js';\n if (m === 'text/css') return 'css';\n if (m === 'image/svg+xml') return 'svg';\n if (m === 'application/json') return 'json';\n if (m === 'text/markdown') return 'markdown';\n if (m === 'text/csv') return 'csv';\n if (m === 'text/plain') return 'text';\n if (m.includes('react')) return 'react';\n if (m.includes('html')) return 'html';\n if (m.includes('javascript')) return 'js';\n if (m.includes('css')) return 'css';\n if (m.includes('svg')) return 'svg';\n if (m.includes('json')) return 'json';\n if (m.includes('csv')) return 'csv';\n if (m.includes('markdown')) return 'markdown';\n return fallback;\n}\n\n/**\n * A stateful streaming parser for one turn's `<artifact>` blocks. Feed it each\n * text delta; drain the returned events. Call {@link finalize} when the stream\n * ends to flush a truncated (unclosed) artifact.\n */\nexport class ArtifactXmlStateMachine {\n private state: XmlArtifactState = 'normal';\n /** Buffers the opening tag until its `>` arrives (may span deltas). */\n private scanBuf = '';\n /** Buffers the tail that might be the start of a split `</artifact>`. */\n private closeBuf = '';\n private segId: string | null = null;\n private content = '';\n private mime = '';\n private kind = '';\n private title = '';\n private seq = 0;\n private readonly idGen: () => string;\n\n constructor(private readonly hint: XmlArtifactHint, idGen?: () => string) {\n this.idGen = idGen ?? (() => `artifact-xml-${this.seq++}`);\n }\n\n /** Feed one text delta; returns the ordered micro-events it produced. */\n feed(delta: string): XmlArtifactEvent[] {\n const out: XmlArtifactEvent[] = [];\n let remaining = delta;\n\n while (remaining.length > 0) {\n if (this.state === 'normal') {\n const tagStart = remaining.indexOf(OPEN_TAG);\n if (tagStart === -1) {\n // No whole tag — but the delta may END on a piece of one. `<`\n // and `artifact` are separate tokens in most vocabularies, so\n // `<arti` closing a delta is routine, not exotic. Emitting it\n // as chat text loses the artifact entirely: the machine never\n // enters `scanning`, and the fallback path that then produces\n // the artifact emits no lifecycle events at all.\n const held = partialOpenTagLength(remaining);\n if (held > 0) {\n const before = remaining.slice(0, remaining.length - held);\n if (before) out.push({ type: 'chat-text', text: before, reduced: true });\n this.scanBuf = remaining.slice(remaining.length - held);\n this.state = 'scanning';\n } else {\n out.push({ type: 'chat-text', text: remaining });\n }\n remaining = '';\n } else {\n const before = remaining.slice(0, tagStart);\n if (before) out.push({ type: 'chat-text', text: before, reduced: true });\n // The tail continues through `remaining`, so a complete\n // `<artifact …>…</artifact>` arriving in ONE delta is parsed in the\n // same call. Parking it in `scanBuf` and clearing `remaining` ended\n // the loop, and `finalize()` then handed the buffer back as raw\n // chat text — so the artifact rendered as a literal tag instead of a\n // card. Core had the same parking bug with a different outcome: it\n // dropped the artifact AND the prose after it entirely.\n this.scanBuf = '';\n remaining = remaining.slice(tagStart);\n this.state = 'scanning';\n }\n } else if (this.state === 'scanning') {\n // Accumulate until we have the full opening tag (ends with `>`).\n this.scanBuf += remaining;\n remaining = '';\n // We may have entered on a partial prefix that turns out to be a\n // different tag (`<article>`). As soon as the buffer contradicts\n // OPEN_TAG, give the text back and return to normal.\n const cmp = Math.min(this.scanBuf.length, OPEN_TAG.length);\n if (this.scanBuf.slice(0, cmp) !== OPEN_TAG.slice(0, cmp)) {\n out.push({ type: 'chat-text', text: this.scanBuf });\n this.scanBuf = '';\n this.state = 'normal';\n continue;\n }\n const gtIdx = this.scanBuf.indexOf('>');\n if (gtIdx !== -1) {\n const tag = this.scanBuf.slice(0, gtIdx + 1);\n this.mime = /mimeType=['\"]([^'\"]+)['\"]/.exec(tag)?.[1] ?? this.hint.mimeType;\n this.title = /title=['\"]([^'\"]+)['\"]/.exec(tag)?.[1] ?? this.hint.kind;\n this.kind = deriveArtifactKind(this.mime, this.hint.kind);\n this.segId = this.idGen();\n this.content = '';\n out.push({ type: 'artifact-open', id: this.segId, mimeType: this.mime, kind: this.kind, title: this.title });\n this.state = 'in-artifact';\n remaining = this.scanBuf.slice(gtIdx + 1);\n this.scanBuf = '';\n }\n } else {\n // in-artifact — stream body until `</artifact>` (may be split).\n const combined = this.closeBuf + remaining;\n const closeIdx = combined.indexOf(CLOSE_TAG);\n if (closeIdx !== -1) {\n this.content += combined.slice(0, closeIdx);\n const inline = this.content.split('\\n').length < INLINE_MAX_LINES;\n out.push({ type: 'artifact-close', id: this.segId!, content: this.content, inline });\n this.state = 'normal';\n this.closeBuf = '';\n remaining = combined.slice(closeIdx + CLOSE_TAG.length);\n } else {\n // Keep the last (CLOSE_TAG.length - 1) chars buffered in case the\n // closing tag is split across this and the next delta.\n const safeLen = Math.max(0, combined.length - CLOSE_TAG.length + 1);\n this.content += combined.slice(0, safeLen);\n this.closeBuf = combined.slice(safeLen);\n remaining = '';\n if (this.segId) out.push({ type: 'artifact-chunk', id: this.segId, content: this.content });\n }\n }\n }\n\n return out;\n }\n\n /**\n * Flush a truncated artifact: if the stream ended mid-body (model cut off\n * before `</artifact>`), emit a close with whatever was buffered. Mirrors the\n * `XML artifact finalize` block in `AparteClient._streamLoop`.\n */\n finalize(): XmlArtifactEvent[] {\n // A stream that ends mid-tag — on a held `<arti`, or on an opening tag\n // that never reached its `>` — must give that text back rather than\n // swallow it. `scanBuf` is the only place it lives.\n if (this.state === 'scanning') {\n const text = this.scanBuf;\n this.scanBuf = '';\n this.state = 'normal';\n return text ? [{ type: 'chat-text', text }] : [];\n }\n if (this.state === 'in-artifact' && this.segId) {\n this.content += this.closeBuf;\n const inline = this.content.split('\\n').length < INLINE_MAX_LINES;\n const ev: XmlArtifactEvent = { type: 'artifact-close', id: this.segId, content: this.content, inline };\n this.state = 'normal';\n this.closeBuf = '';\n return [ev];\n }\n return [];\n }\n\n /** Current parser state (for the adapter to decide finalize routing). */\n get currentState(): XmlArtifactState {\n return this.state;\n }\n}\n","/**\n * stream-run.ts — framework-free structured-stream agent loop.\n *\n * The headless extraction of `AparteClient._streamLoop`: a `while(tool_use)` loop\n * that consumes a structured `AsyncIterable<StreamChatEvent>` from a transport,\n * runs approved tools, feeds their results back into the history, and re-calls\n * the transport until the model stops asking for tools. It performs **no DOM\n * work** — it emits {@link StreamRunEvent}s (in `_streamLoop`'s exact order) that\n * an adapter in `@aparte/core` turns into `targetElement.*` calls.\n *\n * Parity target: `@aparte/core`'s `AparteClient._streamLoop`.\n * Scope: text · thinking · tool_use (+ HITL) · done · error · artifacts (raw /\n * XML / create_artifact) · multi-phase pipeline · synthetic toolChoice bypass.\n * Code-fence promotion stays adapter-side (it needs the core parser).\n */\n\nimport type {\n StreamRunEmitter,\n StreamChatEvent,\n StreamChatRequest,\n StreamAgentMessage,\n StreamToolCall,\n StreamToolHandler,\n StreamToolConfig,\n StreamApprovalResolver,\n StreamUsage,\n} from './stream-events.js';\nimport { ArtifactXmlStateMachine, deriveArtifactKind, type XmlArtifactEvent } from './parsers/artifact-xml-state-machine.js';\n\n/** Default per-tool-call handler timeout — mirrors `TOOL_HANDLER_TIMEOUT_MS`. */\nconst DEFAULT_TOOL_TIMEOUT_MS = 5 * 60 * 1000;\n/** Default global turn cap — mirrors `AparteClientOptions.maxTurns ?? 10`. */\nconst DEFAULT_MAX_TURNS = 10;\n\n/**\n * One phase of a multi-phase pipeline (mirrors the shape of `ApartePipelinePhase`,\n * the pipeline-phase type defined in core's `types/chat.ts`). Supplied via\n * `baseRequest._meta.pipeline`; each phase runs as one turn with its own system\n * message, and an `'artifact'` phase streams the whole turn into a raw artifact.\n */\ntype PipelinePhase =\n | { mode: 'text'; system: string }\n | { mode: 'thinking'; system: string; label?: string }\n | { mode: 'artifact'; system: string; mimeType: string; kind: string };\n\nexport interface StreamRunOptions {\n /** Id of the assistant message being streamed (opaque; carried in events). */\n messageId: string;\n /** Turn-1 request; the loop clones its `messages` and enriches them per turn. */\n baseRequest: StreamChatRequest;\n /**\n * Calls the transport with the (possibly enriched) request. Returns the\n * structured stream, or a plain string for a non-streaming provider. Mirrors\n * `getTransport().chat(provider, request, auth, ctx)` with provider/auth/ctx\n * closed over by the adapter.\n */\n transportCall: (request: StreamChatRequest) => Promise<AsyncIterable<StreamChatEvent> | string>;\n /** Resolves a tool's handler by name (mirrors `aparteGlobalConfig.getToolHandler`). */\n toolLookup: (name: string) => StreamToolHandler | undefined;\n /** Resolves a tool's loop config by name (maxTurns / needsApproval). */\n toolConfigLookup?: (name: string) => StreamToolConfig | undefined;\n /** HITL approval resolver for `needsApproval` tools (default: never called). */\n approvalResolver?: StreamApprovalResolver;\n /** Synchronous, ordered event sink consumed by the adapter. */\n emitter: StreamRunEmitter;\n /** Single abort signal composing `_isAborted` + the stream controller. */\n signal: AbortSignal;\n /** Global turn cap. @default 10 */\n maxTurns?: number;\n /** Per-tool-call handler timeout in ms. @default 300000 */\n toolTimeoutMs?: number;\n /**\n * Called for every turn the loop appends to the history — the grouped\n * `tool_call` envelope, each `tool_result` (resolved or rejected), and a\n * pipeline phase's reply — in order, and always before the transport call that\n * would carry it. Never called for the messages you passed in `baseRequest`.\n *\n * For hosts that own their own transcript. The loop re-sends its `messages`\n * array every turn, which suits stateless message APIs but not a **prefix\n * cache** (llama.cpp slots, vLLM), where turn N+1 must EXTEND turn N byte for\n * byte. Such a host builds its own request in {@link transportCall} — ignoring\n * `request.messages` — and mirrors these notifications into its append-only\n * log, instead of reimplementing the loop's tool bookkeeping. Synchronous and\n * ordered, like {@link emitter}.\n */\n onHistoryAppend?: (message: StreamAgentMessage) => void;\n /**\n * Generates artifact segment ids (`prefix` is e.g. `'artifact-raw'`). The\n * default is a deterministic per-run counter; the adapter injects a\n * crypto-based one to match `_streamLoop`'s `artifact-*-<uuid>`. (Tool ids\n * still flow from the stream; only artifacts need generated ids.)\n */\n idGen?: (prefix: string) => string;\n}\n\n/**\n * Run the structured-stream agent loop. Resolves the last turn's usage (the\n * `done{usage}` last-write-wins, mirroring `_streamLoop`'s return), or\n * `undefined`. Throws on a stream `error` event or a non-abort tool failure —\n * the caller (adapter) routes that to its lifecycle-error handler, exactly as\n * `_handleSend`/`_handleRetry`/`_handleEdit` catch `_streamLoop`.\n */\n/** Warn once per event type for something this version does not map, then skip it. */\nconst warnedUnknownEvents = new Set<string>();\nfunction warnUnknownStreamEvent(event: unknown): void {\n const type = String((event as { type?: unknown })?.type ?? 'undefined');\n if (warnedUnknownEvents.has(type)) return;\n warnedUnknownEvents.add(type);\n console.warn(\n `[runStreamAgent] Ignoring unrecognised stream event \"${type}\". The reply is`\n + ' unaffected; this usually means the provider or its SDK emits a part this'\n + ' version of aparté does not map yet.',\n );\n}\n\nexport async function runStreamAgent(opts: StreamRunOptions): Promise<StreamUsage | undefined> {\n const {\n transportCall,\n toolLookup,\n toolConfigLookup,\n approvalResolver,\n emitter,\n signal,\n maxTurns = DEFAULT_MAX_TURNS,\n toolTimeoutMs = DEFAULT_TOOL_TIMEOUT_MS,\n } = opts;\n\n // Reassignable: the synthetic-toolChoice bypass strips `toolChoice`/`tools`\n // from it after the forced turn-1 handler runs (mirrors `_streamLoop`'s\n // `baseRequest = { ...baseRequest, toolChoice: 'none', tools: undefined }`).\n let baseRequest = opts.baseRequest;\n\n let idSeq = 0;\n const idGen = opts.idGen ?? ((prefix: string) => `${prefix}-${idSeq++}`);\n\n // Mutable history the loop enriches with tool_call/tool_result turns. Every\n // enrichment goes through `append`, so a caller owning its own transcript sees\n // the same turns in the same order (see `onHistoryAppend`).\n const messages: StreamAgentMessage[] = [...baseRequest.messages];\n const append = (message: StreamAgentMessage): void => {\n messages.push(message);\n opts.onHistoryAppend?.(message);\n };\n\n // Pipeline mode (mirrors _streamLoop): each phase is one turn with\n // its own system message; phase N's reply is appended before phase N+1.\n const pipeline = (baseRequest['_meta'] as Record<string, unknown> | undefined)?.['pipeline'] as\n | PipelinePhase[]\n | undefined;\n let pipelineIndex = 0;\n\n let continueLoop = true;\n let turns = 0;\n let lastUsage: StreamUsage | undefined;\n\n // Leading write before turn 1: mark the message streaming.\n emitter({ type: 'run-start' });\n\n // ── outer (turn) loop — one iteration = one transport call ────────────────\n while (continueLoop) {\n // Abort at the top of a turn: no stream open yet, so no cancel needed.\n if (signal.aborted) {\n emitter({ type: 'run-aborted' });\n break;\n }\n\n turns++;\n if (turns > maxTurns) {\n emitter({ type: 'turn-limit-exceeded', scope: 'global', limit: maxTurns });\n break;\n }\n\n // ── Synthetic toolChoice bypass (mirrors _streamLoop) ──────\n // toolChoice = { name, input } (orchestrator-forced): skip the LLM for\n // turn 1, run the handler directly, inject its result as a tool_result,\n // then strip toolChoice/tools and fall through to the transport call in\n // the SAME turn so the model answers with the tool result already in\n // history. (The adapter's tool-start handler injects renderer CSS here\n // too — a benign, idempotent gain over _streamLoop's synthetic path,\n // which skipped it; reconciled in the parity test.)\n const toolChoice = baseRequest['toolChoice'];\n if (\n turns === 1 &&\n toolChoice &&\n typeof toolChoice === 'object' &&\n !Array.isArray(toolChoice) &&\n (toolChoice as { input?: unknown }).input !== undefined\n ) {\n // Narrowed to the declared shape rather than `input: unknown`: now that\n // `StreamChatRequest.toolChoice` mirrors core's type, the cast can be exact.\n const tc = toolChoice as { name: string; input: Record<string, unknown> };\n const syntheticId = idGen('synthetic-tool');\n emitter({ type: 'tool-start', toolCallId: syntheticId, name: tc.name, input: tc.input });\n\n const handler = toolLookup(tc.name);\n if (!handler) {\n emitter({ type: 'tool-aborted', toolCallId: syntheticId });\n continueLoop = false;\n continue;\n }\n\n const outcome = await invokeToolHandler(handler, { id: syntheticId, name: tc.name, input: tc.input }, signal, toolTimeoutMs);\n if (outcome.status === 'aborted') {\n emitter({ type: 'tool-aborted', toolCallId: syntheticId });\n continueLoop = false;\n continue;\n }\n emitter({ type: 'tool-resolved', toolCallId: syntheticId, result: outcome.content });\n append({ role: 'tool_call', content: '', toolCalls: [{ id: syntheticId, name: tc.name, input: tc.input }] });\n append({ role: 'tool_result', content: outcome.content, toolCallId: syntheticId });\n baseRequest = { ...baseRequest, toolChoice: 'none', tools: undefined };\n }\n\n // ── Per-phase request build when pipeline is active ──────\n // Prepend the current phase's system message and, for an 'artifact'\n // phase, inject the artifactRaw hint the streaming loop below reads.\n let phaseMessages = messages;\n let phaseMeta = baseRequest['_meta'] as Record<string, unknown> | undefined;\n if (pipeline && pipelineIndex < pipeline.length) {\n const phase = pipeline[pipelineIndex]!;\n phaseMessages = [{ role: 'system', content: phase.system }, ...messages];\n if (phase.mode === 'artifact') {\n phaseMeta = { ...phaseMeta, artifactRaw: { mimeType: phase.mimeType, kind: phase.kind } };\n } else {\n // Drop any stale artifactRaw / pipeline keys from a text phase.\n const rest = { ...(phaseMeta ?? {}) };\n delete rest['artifactRaw'];\n delete rest['pipeline'];\n phaseMeta = rest;\n }\n }\n\n const request: StreamChatRequest = { ...baseRequest, messages: phaseMessages, _meta: phaseMeta };\n // A stop that lands before the first event surfaces here as a REJECTION,\n // not as an `error` event — the fetch is aborted while still in flight, so\n // it never reaches the stream the loop reads. Letting it propagate made a\n // deliberate stop indistinguishable from a transport failure for anyone\n // driving this loop directly. (Core's `_streamTurn` has the mirror of this\n // guard; the browser suite is what found the path, after the two\n // event-level guards were already closed.)\n let response: Awaited<ReturnType<typeof transportCall>>;\n try {\n response = await transportCall(request);\n } catch (err: unknown) {\n if (signal.aborted || (err as { name?: string } | undefined)?.name === 'AbortError') {\n // `break`, not `return`: the post-loop `run-done` below is documented\n // as running on EVERY exit path, and five of the six abort paths fall\n // through to it. Returning here skipped it, so a consumer wiring\n // `runStreamAgent` + `createStreamAdapter` directly never got the\n // `completed` status and the bubble stayed flagged streaming — which\n // is verbatim the defect the client-side guard was added to fix.\n emitter({ type: 'run-aborted' });\n continueLoop = false;\n break;\n }\n throw err;\n }\n\n // Non-streaming provider: the string IS the full assistant message. The\n // adapter writes it and completes (no done{usage}); spike scenarios\n // always stream, so we finish the run here.\n //\n // `text-flush` is not optional here. It is the adapter's ONLY caller of\n // `parser.finalize()`, and the parser deliberately withholds an ambiguous\n // tail — a lone backtick, a triple backtick, a `<`, or the safe window\n // inside an unterminated fence. Breaking out before the flush dropped all\n // of it, so a non-streaming reply ending on any of those characters lost\n // them and one consisting only of them rendered nothing at all. Core's\n // inline loop does `parse()` AND `finalize()` on its whole-response\n // branch; this is the twin.\n if (typeof response === 'string') {\n emitter({ type: 'text-delta', delta: response });\n emitter({ type: 'text-flush' });\n break;\n }\n\n // New turn: the adapter resets its per-turn parser / thinking / streaming\n // state here (mirrors `_streamLoop` creating a fresh AparteStreamParser +\n // sets each outer iteration). No DOM effect of its own.\n emitter({ type: 'turn-start' });\n\n // artifactRaw mode: the WHOLE turn's text streams into one artifact\n // segment (mirrors _streamLoop). Open it up-front, before the\n // first delta, exactly as _streamLoop does in its per-turn setup.\n const rawHint = (request['_meta'] as Record<string, unknown> | undefined)?.['artifactRaw'] as\n | { mimeType: string; kind: string }\n | undefined;\n let rawSegId: string | null = null;\n let rawContent = '';\n if (rawHint) {\n rawSegId = idGen('artifact-raw');\n emitter({ type: 'artifact-open', id: rawSegId, mimeType: rawHint.mimeType, kind: rawHint.kind, title: rawHint.kind });\n }\n\n // artifactXml mode (mutually exclusive with raw; raw wins, matching the\n // _streamLoop branch order). The E2 state machine parses inline\n // `<artifact>` tags; its micro-events map 1:1 to StreamRunEvents.\n const xmlHint = (request['_meta'] as Record<string, unknown> | undefined)?.['artifactXml'] as\n | { mimeType: string; kind: string }\n | undefined;\n const xmlMachine = (xmlHint && !rawHint)\n ? new ArtifactXmlStateMachine(xmlHint, () => idGen('artifact-xml'))\n : null;\n const emitXml = (events: XmlArtifactEvent[]): void => {\n for (const ev of events) {\n if (ev.type === 'chat-text') emitter({ type: 'text-delta', delta: ev.text, ...(ev.reduced ? { reduced: true } : {}) });\n else if (ev.type === 'artifact-open') emitter({ type: 'artifact-open', id: ev.id, mimeType: ev.mimeType, kind: ev.kind, title: ev.title });\n else if (ev.type === 'artifact-chunk') emitter({ type: 'artifact-chunk', id: ev.id, content: ev.content });\n else emitter({ type: 'artifact-close', id: ev.id, content: ev.content, inline: ev.inline });\n }\n };\n\n // Per-turn streaming state.\n let precedingText = '';\n const toolCallsThisTurn: StreamToolCall[] = [];\n\n // ── inner (SSE) loop — manual iteration so we can abort around each read.\n // Checked on BOTH sides of the read, and the second check is the one that\n // matters: the loop spends nearly all of its time parked on `next()`, so an\n // abort arriving while parked — the user pressing Stop while watching text\n // stream — is invisible to a check that only runs before the await. It also\n // covers both shapes a provider takes on abort: an `error` event or a quiet\n // close. Without it the `error` branch below throws and the caller reports a\n // deliberate stop as a failure. Mirrors `_streamLoop`'s `bailOnAbort`.\n const iterator = response[Symbol.asyncIterator]();\n const bailOnAbort = async (): Promise<boolean> => {\n if (!signal.aborted) return false;\n await iterator.return?.(undefined);\n emitter({ type: 'run-aborted' });\n continueLoop = false;\n return true;\n };\n try {\n while (true) {\n if (await bailOnAbort()) break;\n\n const step = await iterator.next();\n if (await bailOnAbort()) break;\n if (step.done) break;\n const event = step.value;\n\n if (event.type === 'thinking') {\n emitter({ type: 'thinking-delta', delta: event.delta });\n continue;\n }\n\n if (event.type === 'text') {\n precedingText += event.delta;\n if (rawSegId) {\n // artifactRaw: route the whole delta into the artifact,\n // never through the text parser (mirrors the raw-artifact path in _streamLoop).\n rawContent += event.delta;\n emitter({ type: 'artifact-chunk', id: rawSegId, content: rawContent });\n continue;\n }\n if (xmlMachine) {\n // artifactXml: run the delta through the state machine\n // (mirrors the XML-artifact path in _streamLoop); it splits chat text from artifacts.\n emitXml(xmlMachine.feed(event.delta));\n continue;\n }\n emitter({ type: 'text-delta', delta: event.delta });\n continue;\n }\n\n if (event.type === 'done') {\n if (event.usage) lastUsage = event.usage;\n continue;\n }\n\n if (event.type === 'error') {\n // Mirror `_streamLoop`: throw; the caller's catch handles it.\n throw new Error(event.message);\n }\n\n // A DISCRIMINANT GUARD, not a comment.\n //\n // This used to be the bare comment `// event.type === 'tool_use'`, so\n // anything the chain above did not recognise fell in here and was\n // treated as a tool call: a `tool-start` / `tool-aborted` pair with an\n // undefined id and name, and — worse — the rest of the stream was\n // never processed, so the reply was truncated too.\n //\n // Core's inline loop had the opposite failure on the same input (it\n // threw and replaced the reply with an error bubble). Both now ignore\n // the event and carry on, which is what forward compatibility with a\n // provider SDK actually requires.\n if (event.type !== 'tool_use') {\n warnUnknownStreamEvent(event);\n continue;\n }\n toolCallsThisTurn.push({ id: event.id, name: event.name, input: event.input });\n\n // Built-in create_artifact: bypass the generic tool path entirely\n // (no tool-start, no approval, no handler) — build the artifact\n // one-shot and inject a success tool_result (mirrors the create_artifact fast-path in _streamLoop).\n if (event.name === 'create_artifact') {\n const input = (event.input ?? {}) as { mimeType?: string; title?: string; content?: string };\n const mimeType = input.mimeType ?? 'text/plain';\n const kind = deriveArtifactKind(mimeType, 'text');\n emitter({ type: 'artifact-ready', id: `artifact-${event.id}`, mimeType, kind, title: input.title ?? kind, content: input.content ?? '' });\n append({ role: 'tool_call', content: '', toolCalls: [{ id: event.id, name: event.name, input: event.input }] });\n append({ role: 'tool_result', content: 'Artifact created successfully.', toolCallId: event.id });\n continue;\n }\n\n emitter({ type: 'tool-start', toolCallId: event.id, name: event.name, input: event.input });\n\n const cfg = toolConfigLookup?.(event.name);\n\n // Per-tool maxTurns (note: `>=`, stricter than the global `>`).\n const effectiveMaxTurns = cfg?.maxTurns ?? maxTurns;\n if (turns >= effectiveMaxTurns) {\n emitter({ type: 'turn-limit-exceeded', scope: 'tool', limit: effectiveMaxTurns, toolCallId: event.id });\n continueLoop = false;\n break;\n }\n\n const handler = toolLookup(event.name);\n if (!handler) {\n emitter({ type: 'tool-aborted', toolCallId: event.id });\n continueLoop = false;\n break;\n }\n\n let effectiveInput = event.input;\n\n // ── HITL approval gate ────────────────────────────────────────\n if (cfg?.needsApproval) {\n emitter({ type: 'tool-awaiting-approval', toolCallId: event.id, name: event.name, input: event.input });\n const resolve = approvalResolver ?? (async () => ({ approved: false }));\n const decision = await resolve(event.id, signal);\n\n if (!decision.approved) {\n const rejection = 'Tool execution was rejected by the user.';\n emitter({ type: 'tool-rejected', toolCallId: event.id, reason: rejection });\n pushToolCallEnvelope(messages, append, toolCallsThisTurn, precedingText);\n append({ role: 'tool_result', content: rejection, toolCallId: event.id });\n continueLoop = false;\n break;\n }\n if (decision.payload && typeof decision.payload === 'object' && !Array.isArray(decision.payload)) {\n effectiveInput = { ...(event.input as Record<string, unknown>), ...(decision.payload as Record<string, unknown>) };\n }\n emitter({ type: 'tool-approved', toolCallId: event.id });\n }\n\n // ── handler invocation with per-call timeout ──────────────────\n const outcome = await invokeToolHandler(\n handler,\n { id: event.id, name: event.name, input: effectiveInput },\n signal,\n toolTimeoutMs,\n );\n if (outcome.status === 'aborted') {\n // Same rule as the rejection / turn-limit / missing-handler\n // exits above, which this branch was missing: a stopped turn\n // must not go on running the tool calls that follow it.\n emitter({ type: 'tool-aborted', toolCallId: event.id });\n continueLoop = false;\n break;\n } else {\n emitter({ type: 'tool-resolved', toolCallId: event.id, result: outcome.content });\n pushToolCallEnvelope(messages, append, toolCallsThisTurn, precedingText);\n append({ role: 'tool_result', content: outcome.content, toolCallId: event.id });\n }\n }\n\n // artifactXml finalize comes FIRST, and the order is load-bearing.\n //\n // Its `scanning` branch hands back text the machine was holding — a\n // proper prefix of `<artifact` it could not yet classify. That text\n // travels as `chat-text` -> `text-delta`, and the adapter feeds every\n // `text-delta` into the parser. Emitted AFTER `text-flush` it reached a\n // parser that had already been finalized and never would be again, and\n // since every held value is a prefix of the open tag the parser\n // withholds all of them: the loss was total, not probabilistic.\n //\n // Core's twin sidesteps this by bypassing its parser entirely for held\n // text. This side cannot — the adapter owns the parser — so it flushes\n // in the right order instead.\n if (xmlMachine) emitXml(xmlMachine.finalize());\n\n // Turn boundary: finalize the parser (flush residual text). Mirrors\n // `_streamLoop`'s `textParser.finalize()` call — runs on normal\n // end AND abort-break, but NOT after a thrown `error` (which escapes\n // this try before reaching here, exactly like `_streamLoop`).\n emitter({ type: 'text-flush' });\n\n // artifactRaw close comes right after the parser flush, matching the\n // finalize-block order in _streamLoop (text-flush, then artifact-close).\n if (rawSegId) {\n const inline = rawContent.split('\\n').length < 15;\n emitter({ type: 'artifact-close', id: rawSegId, content: rawContent, inline });\n }\n // (artifactXml finalize ran above, before the parser flush — its held\n // text has to reach the parser while the parser can still be flushed.)\n } finally {\n // Mirror `reader.releaseLock()` in the finally: settle the iterator.\n await iterator.return?.(undefined).catch(() => { /* best effort */ });\n }\n\n // No tool calls this turn → final answer, OR advance to the next pipeline\n // phase (mirrors _streamLoop).\n if (toolCallsThisTurn.length === 0) {\n if (pipeline && pipelineIndex < pipeline.length - 1) {\n // Feed this phase's reply into history as context for the next.\n if (precedingText.trim()) {\n append({ role: 'assistant', content: precedingText.trim() });\n }\n pipelineIndex++;\n emitter({ type: 'phase-advance', index: pipelineIndex });\n // continueLoop stays true — the next iteration runs the new phase.\n } else {\n continueLoop = false;\n }\n }\n }\n\n // Post-loop finalization runs on every exit path (normal / abort / maxTurns).\n emitter({ type: 'run-done', usage: lastUsage });\n return lastUsage;\n}\n\n/**\n * Push the single grouped `tool_call` envelope for the turn — but only once,\n * even when the turn has several tool calls (each call's `tool_result` is pushed\n * separately). Mirrors `_streamLoop`'s `existingToolCallMsg` guard. Reads\n * `messages` for that guard and writes through `append`, so the duplicate\n * suppression also applies to the `onHistoryAppend` notification.\n */\nfunction pushToolCallEnvelope(\n messages: StreamAgentMessage[],\n append: (message: StreamAgentMessage) => void,\n toolCallsThisTurn: StreamToolCall[],\n precedingText: string,\n): void {\n const exists = messages.some(\n m => m.role === 'tool_call' && m.toolCalls?.some(tc => toolCallsThisTurn.some(t => t.id === tc.id)),\n );\n if (exists) return;\n append({\n role: 'tool_call',\n content: '',\n toolCalls: toolCallsThisTurn,\n precedingText: precedingText.trim() || undefined,\n });\n}\n\n/**\n * Run a tool handler with a per-call timeout, linked to the run's abort signal.\n * Resolves `{ status: 'resolved', content }` on success, or `{ status: 'aborted' }`\n * if the handler aborts (timeout OR parent abort); any other error is re-thrown so\n * the caller routes it to lifecycle-error, exactly as `_streamLoop` does. Shared\n * by the generic tool path and the synthetic-toolChoice bypass (mirrors the\n * AbortController + `TOOL_HANDLER_TIMEOUT_MS` dance both use in `_streamLoop`).\n */\nasync function invokeToolHandler(\n handler: StreamToolHandler,\n call: StreamToolCall,\n signal: AbortSignal,\n toolTimeoutMs: number,\n): Promise<{ status: 'resolved'; content: string } | { status: 'aborted' }> {\n // If the run was already aborted before we got here, don't invoke the\n // handler at all: a past 'abort' event will never re-fire on the listener\n // below, so the handler would otherwise run to completion despite cancel.\n if (signal.aborted) return { status: 'aborted' };\n\n const controller = new AbortController();\n const onParentAbort = () => controller.abort();\n signal.addEventListener('abort', onParentAbort, { once: true });\n\n /*\n * RACED, not just signalled.\n *\n * Aborting the controller is a request the handler is free to ignore, and the\n * default shape of a consumer tool ignores it —\n * `async () => ({ content: await fetch(...).then(r => r.text()) })` never reads\n * its signal. So the timeout fired, nothing rejected, and the loop waited\n * forever on an option whose JSDoc promises a timeout.\n *\n * The signal still fires first, because a handler that DOES honour it should\n * get the chance to clean up and reject on its own terms; the race is what\n * makes the promise true when it does not.\n */\n let timeout: ReturnType<typeof setTimeout> | undefined;\n const timedOut = new Promise<{ status: 'aborted' }>((resolve) => {\n timeout = setTimeout(() => {\n controller.abort();\n resolve({ status: 'aborted' });\n }, toolTimeoutMs);\n });\n\n try {\n const result = await Promise.race([\n handler(call, controller.signal).then((r) => ({ status: 'resolved' as const, content: r.content })),\n timedOut,\n ]);\n return result;\n } catch (err: unknown) {\n if ((err as { name?: string })?.name === 'AbortError') return { status: 'aborted' };\n throw err;\n } finally {\n clearTimeout(timeout);\n signal.removeEventListener('abort', onParentAbort);\n }\n}\n","/**\n * conversation/compactor.ts — adaptive budgeting + context-window assembly.\n *\n * Conversation history budgeting and sliding-window assembly —\n * framework-free and tokenizer-free (char-count heuristic).\n *\n * Pattern (cf. Claude Code /context, OpenAI Agents SDK TrimmingSession):\n * Budget = CONTEXT_WINDOW − systemPrompt − tools − reservedThinking\n * − reservedGeneration − autocompactBuffer − safetyMargin\n *\n * The history budget is then split into:\n * - summary (global summary, LLM-generated, ~10%)\n * - ragHist (older turns retrieved by cosine similarity, ~25%)\n * - window (verbatim sliding window, the rest)\n *\n * Drop priority on overflow:\n * 1. summary (async-regenerable)\n * 2. ragHist (retrievable on the next turn)\n * 3. window oldest turns\n * X. NEVER dropped: currentUser + lastAssistant (absolute working memory)\n *\n * Browser-portable: zero deps, just an estimateTokens heuristic.\n */\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport interface CompactionMessage {\n role: 'system' | 'user' | 'assistant' | 'tool';\n content: string;\n}\n\nexport interface RetrievedTurn {\n role: 'user' | 'assistant';\n content: string;\n score?: number;\n}\n\nexport interface CompactionConfig {\n /** Total context window of the active model, in tokens. */\n contextWindow: number;\n /** Reserved budget for the model's thinking/reasoning block, in tokens. */\n reservedThinking: number;\n /** Reserved budget for the assistant response (max_new_tokens cap). */\n reservedGeneration: number;\n /** Fraction of context_window kept as autocompact buffer (0..1). */\n autocompactBufferPct: number;\n /** Hard safety margin in tokens. */\n safetyMargin: number;\n /** Floor for history budget — never compact below this. */\n minHistoryBudget: number;\n /** Ratio of history budget allocated to the summary block. */\n summaryRatio: number;\n /** Hard cap for summary tokens. */\n summaryMaxTokens: number;\n /** Ratio of history budget allocated to the RAG-retrieved old turns. */\n ragHistRatio: number;\n /** Hard cap for ragHist tokens. */\n ragHistMaxTokens: number;\n /** Trigger summarization once history usage reaches this % of the budget. */\n triggerSummaryThresholdPct: number;\n /** Re-run summarization every N user turns. */\n summarizeEveryNTurns: number;\n /** Header prepended to the summary block injected into the model context. English\n * by default — override to localise (the compactor ships no locale system). */\n summaryLabel: string;\n /** Header prepended to the RAG-retrieved old turns injected into the context. */\n ragIntroLabel: string;\n}\n\nexport interface BudgetBreakdown {\n contextWindow: number;\n systemPrompt: number;\n tools: number;\n reservedThinking: number;\n reservedGeneration: number;\n autocompactBuffer: number;\n safetyMargin: number;\n historyAvailable: number;\n}\n\nexport interface BudgetResult {\n historyBudget: number;\n breakdown: BudgetBreakdown;\n config: CompactionConfig;\n}\n\nexport interface SplitBudget {\n summary: number;\n ragHist: number;\n window: number;\n}\n\nexport interface UsageBreakdown {\n system: number;\n summary: number;\n ragHist: number;\n window: number;\n}\n\nexport interface DroppedBreakdown {\n ragHits: number;\n oldTurns: number;\n summary: boolean;\n}\n\nexport interface FullBreakdown extends BudgetBreakdown {\n historyAllocated: SplitBudget;\n historyUsed: UsageBreakdown;\n totalUsed: number;\n /** Free tokens left in the context window after all reservations + actual use. */\n free: number;\n dropped: DroppedBreakdown;\n}\n\nexport interface CompactionInput {\n messages: CompactionMessage[];\n systemPrompt: string;\n /** Tools array passed to apply_chat_template (or null/undefined when no tools). */\n toolsArray?: unknown;\n /** Running summary text (regenerable). */\n summary?: string;\n /** RAG-retrieved old turns (older than the sliding window). */\n retrievedTurns?: RetrievedTurn[];\n /** Partial override of the default config. */\n config?: Partial<CompactionConfig>;\n}\n\nexport interface CompactionResult {\n compactedMessages: CompactionMessage[];\n breakdown: FullBreakdown;\n}\n\n// ─── Constants ──────────────────────────────────────────────────────────────\n\nexport const DEFAULT_COMPACTION_CONFIG: CompactionConfig = {\n // Conservative model-agnostic defaults — the consuming app overrides\n // `contextWindow` / `reservedThinking` with its model's real values.\n contextWindow: 8192,\n reservedThinking: 0,\n reservedGeneration: 2000,\n autocompactBufferPct: 0.10, // ~3300 tok\n safetyMargin: 500,\n minHistoryBudget: 1000,\n\n summaryRatio: 0.10,\n summaryMaxTokens: 400,\n ragHistRatio: 0.25,\n ragHistMaxTokens: 1000,\n\n triggerSummaryThresholdPct: 0.75,\n summarizeEveryNTurns: 5,\n\n summaryLabel: 'Conversation summary:',\n ragIntroLabel: 'Relevant excerpts from earlier in the conversation:',\n};\n\n// ─── Token estimation ──────────────────────────────────────────────────────\n\n/**\n * Token heuristic (FR ~3.5 chars/tok, EN ~4 chars/tok).\n * Accurate to ±10% — enough for budgeting, and it avoids `tokenizer.encode()`,\n * which costs ~5ms per message × N (too slow to run on every turn).\n */\nexport function estimateTokens(text: string | null | undefined): number {\n if (!text) return 0;\n return Math.ceil(text.length / 3.8);\n}\n\n/**\n * Estimate tokens for a JSON-serializable structure (e.g. tools array).\n */\nexport function estimateTokensJson(obj: unknown): number {\n if (!obj) return 0;\n try {\n return estimateTokens(JSON.stringify(obj));\n } catch {\n return 0;\n }\n}\n\n// ─── Budget computation ────────────────────────────────────────────────────\n\n/**\n * Compute the available history budget after subtracting fixed costs.\n */\nexport function computeHistoryBudget(input: {\n systemPrompt: string;\n toolsArray?: unknown;\n config?: Partial<CompactionConfig>;\n}): BudgetResult {\n const cfg: CompactionConfig = { ...DEFAULT_COMPACTION_CONFIG, ...(input.config ?? {}) };\n\n const systemTokens = estimateTokens(input.systemPrompt);\n const toolsTokens = estimateTokensJson(input.toolsArray);\n const autocompactBuffer = Math.floor(cfg.contextWindow * cfg.autocompactBufferPct);\n\n const fixed = systemTokens + toolsTokens\n + cfg.reservedThinking + cfg.reservedGeneration\n + autocompactBuffer + cfg.safetyMargin;\n\n const historyBudget = Math.max(cfg.minHistoryBudget, cfg.contextWindow - fixed);\n\n return {\n historyBudget,\n breakdown: {\n contextWindow: cfg.contextWindow,\n systemPrompt: systemTokens,\n tools: toolsTokens,\n reservedThinking: cfg.reservedThinking,\n reservedGeneration: cfg.reservedGeneration,\n autocompactBuffer,\n safetyMargin: cfg.safetyMargin,\n historyAvailable: historyBudget,\n },\n config: cfg,\n };\n}\n\n/**\n * Split history budget into summary / ragHist / window slots.\n */\nexport function splitHistoryBudget(\n historyBudget: number,\n cfg: CompactionConfig = DEFAULT_COMPACTION_CONFIG,\n): SplitBudget {\n const summary = Math.min(cfg.summaryMaxTokens, Math.floor(historyBudget * cfg.summaryRatio));\n const ragHist = Math.min(cfg.ragHistMaxTokens, Math.floor(historyBudget * cfg.ragHistRatio));\n const window = historyBudget - summary - ragHist;\n return { summary, ragHist, window };\n}\n\n// ─── Message assembly ──────────────────────────────────────────────────────\n\ninterface AssembleParams {\n messages: CompactionMessage[];\n summary?: string;\n retrievedTurns?: RetrievedTurn[];\n windowBudget: number;\n summaryBudget: number;\n ragBudget: number;\n systemContent?: string;\n /** Context-injection headers; default to English (DEFAULT_COMPACTION_CONFIG). */\n summaryLabel?: string;\n ragIntroLabel?: string;\n}\n\ninterface AssembleResult {\n compactedMessages: CompactionMessage[];\n used: UsageBreakdown;\n dropped: DroppedBreakdown;\n}\n\n/**\n * Assemble compacted message list given a budget and conversation state.\n *\n * Output order:\n * [system?] → [summary system msg?] → [ragHist system msg?] → window verbatim\n *\n * Always keeps the last 2 non-system messages verbatim (working memory),\n * regardless of windowBudget — these are the floor that can never be dropped.\n */\nexport function assembleCompacted(params: AssembleParams): AssembleResult {\n const {\n messages,\n summary = '',\n retrievedTurns = [],\n windowBudget,\n summaryBudget,\n ragBudget,\n systemContent,\n summaryLabel = DEFAULT_COMPACTION_CONFIG.summaryLabel,\n ragIntroLabel = DEFAULT_COMPACTION_CONFIG.ragIntroLabel,\n } = params;\n\n const out: CompactionMessage[] = [];\n const used: UsageBreakdown = { system: 0, summary: 0, ragHist: 0, window: 0 };\n const dropped: DroppedBreakdown = { ragHits: 0, oldTurns: 0, summary: false };\n\n // System message first\n if (systemContent) {\n out.push({ role: 'system', content: systemContent });\n used.system = estimateTokens(systemContent);\n }\n\n // Summary\n if (summary) {\n const summaryWrapped = `${summaryLabel}\\n${summary}`;\n const summaryToks = estimateTokens(summaryWrapped);\n if (summaryToks <= summaryBudget) {\n out.push({ role: 'system', content: summaryWrapped });\n used.summary = summaryToks;\n } else {\n const ratio = (summaryBudget / summaryToks) * 0.95;\n const truncated = summary.slice(0, Math.max(0, Math.floor(summary.length * ratio)));\n const wrapped = `${summaryLabel}\\n${truncated}…`;\n out.push({ role: 'system', content: wrapped });\n used.summary = estimateTokens(wrapped);\n dropped.summary = true;\n }\n }\n\n // RAG hist — old turns retrieved by cosine similarity, sorted by score desc\n if (retrievedTurns.length > 0) {\n const sorted = [...retrievedTurns].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));\n const lines: string[] = [];\n const intro = `${ragIntroLabel}\\n`;\n let toks = estimateTokens(intro);\n for (const t of sorted) {\n const line = `[${t.role}] ${t.content}`;\n const lt = estimateTokens(line);\n if (toks + lt > ragBudget) {\n dropped.ragHits++;\n continue;\n }\n lines.push(line);\n toks += lt;\n }\n if (lines.length > 0) {\n const content = intro + lines.join('\\n');\n out.push({ role: 'system', content });\n used.ragHist = estimateTokens(content);\n }\n }\n\n // Sliding window verbatim — newest → oldest, until windowBudget exhausted.\n // Always keep the last 2 non-system messages (working memory floor).\n const nonSystem = messages.filter(m => m.role !== 'system');\n if (nonSystem.length === 0) {\n return { compactedMessages: out, used, dropped };\n }\n\n const reversedWindow: CompactionMessage[] = [];\n let windowToks = 0;\n const minKeep = Math.min(2, nonSystem.length);\n\n for (let i = nonSystem.length - 1; i >= 0; i--) {\n const m = nonSystem[i];\n if (!m) continue;\n const t = estimateTokens(m.content);\n const idxFromEnd = nonSystem.length - 1 - i;\n\n if (idxFromEnd < minKeep) {\n // Working memory floor — kept no matter the budget.\n reversedWindow.unshift(m);\n windowToks += t;\n continue;\n }\n\n if (windowToks + t > windowBudget) {\n dropped.oldTurns += i + 1;\n break;\n }\n reversedWindow.unshift(m);\n windowToks += t;\n }\n\n /*\n * A window must never BEGIN on a `tool` result.\n *\n * The walk is newest-to-oldest and keeps the last `minKeep` messages whatever\n * the budget, so when the second-to-last message is a tool result the compacted\n * history opens with `role: 'tool'` — an orphan whose requesting assistant turn\n * was dropped. Every OpenAI-compatible provider rejects that with a 400\n * (\"messages with role 'tool' must be a response to a preceding message with\n * tool_calls\"), so compaction turned a long conversation into an unusable one.\n *\n * Dropping the orphan rather than reaching further back for its assistant turn\n * is deliberate: `CompactionMessage` is `{ role, content }` and cannot represent\n * the pairing at all — no `toolCallId`, no `toolCalls` — so there is nothing to\n * reconstruct WITH. Its content already survives in the summary the window sits\n * under. Carrying the pairing properly means widening the type, which is a\n * design change and not a bug fix.\n */\n while (reversedWindow.length && reversedWindow[0]?.role === 'tool') {\n const orphan = reversedWindow.shift();\n if (orphan) {\n windowToks -= estimateTokens(orphan.content);\n dropped.oldTurns += 1;\n }\n }\n\n out.push(...reversedWindow);\n used.window = windowToks;\n\n return { compactedMessages: out, used, dropped };\n}\n\n// ─── High-level helper ─────────────────────────────────────────────────────\n\n/**\n * Compute budget + assemble in one call. The primary entry point for\n * consumers (a browser request interceptor, mobile, Node tests).\n */\nexport function compactConversation(input: CompactionInput): CompactionResult {\n const { messages, systemPrompt, toolsArray, summary, retrievedTurns, config } = input;\n\n const budget = computeHistoryBudget({ systemPrompt, toolsArray, config });\n const split = splitHistoryBudget(budget.historyBudget, budget.config);\n\n const { compactedMessages, used, dropped } = assembleCompacted({\n messages,\n summary,\n retrievedTurns,\n windowBudget: split.window,\n summaryBudget: split.summary,\n ragBudget: split.ragHist,\n systemContent: systemPrompt,\n summaryLabel: budget.config.summaryLabel,\n ragIntroLabel: budget.config.ragIntroLabel,\n });\n\n const totalUsed = budget.breakdown.systemPrompt\n + budget.breakdown.tools\n + used.summary\n + used.ragHist\n + used.window;\n\n const free = budget.config.contextWindow - totalUsed\n - budget.config.reservedThinking - budget.config.reservedGeneration\n - budget.breakdown.autocompactBuffer - budget.config.safetyMargin;\n\n const breakdown: FullBreakdown = {\n ...budget.breakdown,\n historyAllocated: split,\n historyUsed: used,\n totalUsed,\n free,\n dropped,\n };\n\n return { compactedMessages, breakdown };\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aparte/engine",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "Framework-agnostic AI agent loop (runStreamAgent) + parsers/orchestrator — zero runtime deps, headless (browser or Node).",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -24,7 +24,7 @@
24
24
  "node": ">=18"
25
25
  },
26
26
  "peerDependencies": {
27
- "@aparte/core": ">=0.5.0-alpha.0 <1.0.0"
27
+ "@aparte/core": ">=0.7.0 <1.0.0"
28
28
  },
29
29
  "peerDependenciesMeta": {
30
30
  "@aparte/core": {
@@ -35,8 +35,7 @@
35
35
  "@types/node": "^22.0.0",
36
36
  "typescript": "^5.4.0",
37
37
  "vite": "^6.0.0",
38
- "vite-plugin-dts": "^4.5.4",
39
- "@aparte/core": "0.7.1"
38
+ "@aparte/core": "0.8.0"
40
39
  },
41
40
  "keywords": [
42
41
  "ai",