@aparte/engine 0.13.1 → 0.15.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 +11 -12
- package/dist/agent/parsers/artifact-kind.d.ts +19 -0
- package/dist/agent/parsers/artifact-kind.d.ts.map +1 -0
- package/dist/agent/stream-events.d.ts +17 -38
- package/dist/agent/stream-events.d.ts.map +1 -1
- package/dist/agent/stream-run.d.ts +18 -6
- package/dist/agent/stream-run.d.ts.map +1 -1
- package/dist/conversation/selector.d.ts +78 -0
- package/dist/conversation/selector.d.ts.map +1 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +57 -203
- package/dist/index.js.map +1 -1
- package/package.json +2 -6
- package/dist/agent/parsers/artifact-xml-state-machine.d.ts +0 -87
- package/dist/agent/parsers/artifact-xml-state-machine.d.ts.map +0 -1
- package/dist/agent/stream-events.contract.d.ts +0 -74
- package/dist/agent/stream-events.contract.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
const CLOSE_TAG = "</artifact>";
|
|
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
|
-
}
|
|
10
|
-
const INLINE_MAX_LINES = 15;
|
|
11
1
|
function deriveArtifactKind(mimeType, fallback = "unknown") {
|
|
12
2
|
const m = (mimeType || "").toLowerCase().trim();
|
|
13
3
|
const ant = m.match(/^application\/vnd\.ant\.([a-z0-9-]+)/);
|
|
@@ -30,119 +20,6 @@ function deriveArtifactKind(mimeType, fallback = "unknown") {
|
|
|
30
20
|
if (m.includes("markdown")) return "markdown";
|
|
31
21
|
return fallback;
|
|
32
22
|
}
|
|
33
|
-
class ArtifactXmlStateMachine {
|
|
34
|
-
constructor(hint, idGen) {
|
|
35
|
-
this.hint = hint;
|
|
36
|
-
this.idGen = idGen ?? (() => `artifact-xml-${this.seq++}`);
|
|
37
|
-
}
|
|
38
|
-
state = "normal";
|
|
39
|
-
/** Buffers the opening tag until its `>` arrives (may span deltas). */
|
|
40
|
-
scanBuf = "";
|
|
41
|
-
/** Buffers the tail that might be the start of a split `</artifact>`. */
|
|
42
|
-
closeBuf = "";
|
|
43
|
-
segId = null;
|
|
44
|
-
content = "";
|
|
45
|
-
mime = "";
|
|
46
|
-
kind = "";
|
|
47
|
-
title = "";
|
|
48
|
-
seq = 0;
|
|
49
|
-
idGen;
|
|
50
|
-
/** Feed one text delta; returns the ordered micro-events it produced. */
|
|
51
|
-
feed(delta) {
|
|
52
|
-
const out = [];
|
|
53
|
-
let remaining = delta;
|
|
54
|
-
while (remaining.length > 0) {
|
|
55
|
-
if (this.state === "normal") {
|
|
56
|
-
const tagStart = remaining.indexOf(OPEN_TAG);
|
|
57
|
-
if (tagStart === -1) {
|
|
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
|
-
}
|
|
67
|
-
remaining = "";
|
|
68
|
-
} else {
|
|
69
|
-
const before = remaining.slice(0, tagStart);
|
|
70
|
-
if (before) out.push({ type: "chat-text", text: before, reduced: true });
|
|
71
|
-
this.scanBuf = "";
|
|
72
|
-
remaining = remaining.slice(tagStart);
|
|
73
|
-
this.state = "scanning";
|
|
74
|
-
}
|
|
75
|
-
} else if (this.state === "scanning") {
|
|
76
|
-
this.scanBuf += remaining;
|
|
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
|
-
}
|
|
85
|
-
const gtIdx = this.scanBuf.indexOf(">");
|
|
86
|
-
if (gtIdx !== -1) {
|
|
87
|
-
const tag = this.scanBuf.slice(0, gtIdx + 1);
|
|
88
|
-
this.mime = /mimeType=['"]([^'"]+)['"]/.exec(tag)?.[1] ?? this.hint.mimeType;
|
|
89
|
-
this.title = /title=['"]([^'"]+)['"]/.exec(tag)?.[1] ?? this.hint.kind;
|
|
90
|
-
this.kind = deriveArtifactKind(this.mime, this.hint.kind);
|
|
91
|
-
this.segId = this.idGen();
|
|
92
|
-
this.content = "";
|
|
93
|
-
out.push({ type: "artifact-open", id: this.segId, mimeType: this.mime, kind: this.kind, title: this.title });
|
|
94
|
-
this.state = "in-artifact";
|
|
95
|
-
remaining = this.scanBuf.slice(gtIdx + 1);
|
|
96
|
-
this.scanBuf = "";
|
|
97
|
-
}
|
|
98
|
-
} else {
|
|
99
|
-
const combined = this.closeBuf + remaining;
|
|
100
|
-
const closeIdx = combined.indexOf(CLOSE_TAG);
|
|
101
|
-
if (closeIdx !== -1) {
|
|
102
|
-
this.content += combined.slice(0, closeIdx);
|
|
103
|
-
const inline = this.content.split("\n").length < INLINE_MAX_LINES;
|
|
104
|
-
out.push({ type: "artifact-close", id: this.segId, content: this.content, inline });
|
|
105
|
-
this.state = "normal";
|
|
106
|
-
this.closeBuf = "";
|
|
107
|
-
remaining = combined.slice(closeIdx + CLOSE_TAG.length);
|
|
108
|
-
} else {
|
|
109
|
-
const safeLen = Math.max(0, combined.length - CLOSE_TAG.length + 1);
|
|
110
|
-
this.content += combined.slice(0, safeLen);
|
|
111
|
-
this.closeBuf = combined.slice(safeLen);
|
|
112
|
-
remaining = "";
|
|
113
|
-
if (this.segId) out.push({ type: "artifact-chunk", id: this.segId, content: this.content });
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return out;
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Flush a truncated artifact: if the stream ended mid-body (model cut off
|
|
121
|
-
* before `</artifact>`), emit a close with whatever was buffered. Mirrors the
|
|
122
|
-
* `XML artifact finalize` block in `AparteClient._streamLoop`.
|
|
123
|
-
*/
|
|
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
|
-
}
|
|
131
|
-
if (this.state === "in-artifact" && this.segId) {
|
|
132
|
-
this.content += this.closeBuf;
|
|
133
|
-
const inline = this.content.split("\n").length < INLINE_MAX_LINES;
|
|
134
|
-
const ev = { type: "artifact-close", id: this.segId, content: this.content, inline };
|
|
135
|
-
this.state = "normal";
|
|
136
|
-
this.closeBuf = "";
|
|
137
|
-
return [ev];
|
|
138
|
-
}
|
|
139
|
-
return [];
|
|
140
|
-
}
|
|
141
|
-
/** Current parser state (for the adapter to decide finalize routing). */
|
|
142
|
-
get currentState() {
|
|
143
|
-
return this.state;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
23
|
const DEFAULT_TOOL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
147
24
|
const DEFAULT_MAX_TURNS = 10;
|
|
148
25
|
const warnedUnknownEvents = /* @__PURE__ */ new Set();
|
|
@@ -173,8 +50,6 @@ async function runStreamAgent(opts) {
|
|
|
173
50
|
messages.push(message);
|
|
174
51
|
opts.onHistoryAppend?.(message);
|
|
175
52
|
};
|
|
176
|
-
const pipeline = baseRequest["_meta"]?.["pipeline"];
|
|
177
|
-
let pipelineIndex = 0;
|
|
178
53
|
let continueLoop = true;
|
|
179
54
|
let turns = 0;
|
|
180
55
|
let lastUsage;
|
|
@@ -211,21 +86,7 @@ async function runStreamAgent(opts) {
|
|
|
211
86
|
append({ role: "tool_result", content: outcome.content, toolCallId: syntheticId });
|
|
212
87
|
baseRequest = { ...baseRequest, toolChoice: "none", tools: void 0 };
|
|
213
88
|
}
|
|
214
|
-
|
|
215
|
-
let phaseMeta = baseRequest["_meta"];
|
|
216
|
-
if (pipeline && pipelineIndex < pipeline.length) {
|
|
217
|
-
const phase = pipeline[pipelineIndex];
|
|
218
|
-
phaseMessages = [{ role: "system", content: phase.system }, ...messages];
|
|
219
|
-
if (phase.mode === "artifact") {
|
|
220
|
-
phaseMeta = { ...phaseMeta, artifactRaw: { mimeType: phase.mimeType, kind: phase.kind } };
|
|
221
|
-
} else {
|
|
222
|
-
const rest = { ...phaseMeta ?? {} };
|
|
223
|
-
delete rest["artifactRaw"];
|
|
224
|
-
delete rest["pipeline"];
|
|
225
|
-
phaseMeta = rest;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
const request = { ...baseRequest, messages: phaseMessages, _meta: phaseMeta };
|
|
89
|
+
const request = { ...baseRequest, messages };
|
|
229
90
|
let response;
|
|
230
91
|
try {
|
|
231
92
|
response = await transportCall(request);
|
|
@@ -243,25 +104,14 @@ async function runStreamAgent(opts) {
|
|
|
243
104
|
break;
|
|
244
105
|
}
|
|
245
106
|
emitter({ type: "turn-start" });
|
|
246
|
-
const rawHint = request["_meta"]?.["artifactRaw"];
|
|
247
|
-
let rawSegId = null;
|
|
248
|
-
let rawContent = "";
|
|
249
|
-
if (rawHint) {
|
|
250
|
-
rawSegId = idGen("artifact-raw");
|
|
251
|
-
emitter({ type: "artifact-open", id: rawSegId, mimeType: rawHint.mimeType, kind: rawHint.kind, title: rawHint.kind });
|
|
252
|
-
}
|
|
253
|
-
const xmlHint = request["_meta"]?.["artifactXml"];
|
|
254
|
-
const xmlMachine = xmlHint && !rawHint ? new ArtifactXmlStateMachine(xmlHint, () => idGen("artifact-xml")) : null;
|
|
255
|
-
const emitXml = (events) => {
|
|
256
|
-
for (const ev of events) {
|
|
257
|
-
if (ev.type === "chat-text") emitter({ type: "text-delta", delta: ev.text, ...ev.reduced ? { reduced: true } : {} });
|
|
258
|
-
else if (ev.type === "artifact-open") emitter({ type: "artifact-open", id: ev.id, mimeType: ev.mimeType, kind: ev.kind, title: ev.title });
|
|
259
|
-
else if (ev.type === "artifact-chunk") emitter({ type: "artifact-chunk", id: ev.id, content: ev.content });
|
|
260
|
-
else emitter({ type: "artifact-close", id: ev.id, content: ev.content, inline: ev.inline });
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
107
|
let precedingText = "";
|
|
264
108
|
const toolCallsThisTurn = [];
|
|
109
|
+
let envelope = null;
|
|
110
|
+
const ensureEnvelope = () => {
|
|
111
|
+
if (envelope) return;
|
|
112
|
+
envelope = { role: "tool_call", content: "", toolCalls: toolCallsThisTurn, precedingText: precedingText.trim() || void 0 };
|
|
113
|
+
append(envelope);
|
|
114
|
+
};
|
|
265
115
|
const iterator = response[Symbol.asyncIterator]();
|
|
266
116
|
const bailOnAbort = async () => {
|
|
267
117
|
if (!signal.aborted) return false;
|
|
@@ -283,15 +133,6 @@ async function runStreamAgent(opts) {
|
|
|
283
133
|
}
|
|
284
134
|
if (event.type === "text") {
|
|
285
135
|
precedingText += event.delta;
|
|
286
|
-
if (rawSegId) {
|
|
287
|
-
rawContent += event.delta;
|
|
288
|
-
emitter({ type: "artifact-chunk", id: rawSegId, content: rawContent });
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
if (xmlMachine) {
|
|
292
|
-
emitXml(xmlMachine.feed(event.delta));
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
136
|
emitter({ type: "text-delta", delta: event.delta });
|
|
296
137
|
continue;
|
|
297
138
|
}
|
|
@@ -312,7 +153,7 @@ async function runStreamAgent(opts) {
|
|
|
312
153
|
const mimeType = input.mimeType ?? "text/plain";
|
|
313
154
|
const kind = deriveArtifactKind(mimeType, "text");
|
|
314
155
|
emitter({ type: "artifact-ready", id: `artifact-${event.id}`, mimeType, kind, title: input.title ?? kind, content: input.content ?? "" });
|
|
315
|
-
|
|
156
|
+
ensureEnvelope();
|
|
316
157
|
append({ role: "tool_result", content: "Artifact created successfully.", toolCallId: event.id });
|
|
317
158
|
continue;
|
|
318
159
|
}
|
|
@@ -338,11 +179,18 @@ async function runStreamAgent(opts) {
|
|
|
338
179
|
continueLoop = false;
|
|
339
180
|
break;
|
|
340
181
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
182
|
+
let decision;
|
|
183
|
+
if (!signal.aborted) {
|
|
184
|
+
try {
|
|
185
|
+
decision = await approvalResolver(
|
|
186
|
+
{ id: event.id, name: event.name, input: event.input },
|
|
187
|
+
signal
|
|
188
|
+
);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
if (!signal.aborted && err?.name !== "AbortError") throw err;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (signal.aborted || !decision) {
|
|
346
194
|
emitter({ type: "tool-aborted", toolCallId: event.id });
|
|
347
195
|
continueLoop = false;
|
|
348
196
|
break;
|
|
@@ -350,7 +198,7 @@ async function runStreamAgent(opts) {
|
|
|
350
198
|
if (!decision.approved) {
|
|
351
199
|
const rejection = decision.instruction ? `The user rejected this tool call and said: ${decision.instruction}` : "Tool execution was rejected by the user.";
|
|
352
200
|
emitter({ type: "tool-rejected", toolCallId: event.id, reason: rejection });
|
|
353
|
-
|
|
201
|
+
ensureEnvelope();
|
|
354
202
|
append({ role: "tool_result", content: rejection, toolCallId: event.id });
|
|
355
203
|
break;
|
|
356
204
|
}
|
|
@@ -371,47 +219,20 @@ async function runStreamAgent(opts) {
|
|
|
371
219
|
break;
|
|
372
220
|
} else {
|
|
373
221
|
emitter({ type: "tool-resolved", toolCallId: event.id, result: outcome.content });
|
|
374
|
-
|
|
222
|
+
ensureEnvelope();
|
|
375
223
|
append({ role: "tool_result", content: outcome.content, toolCallId: event.id });
|
|
376
224
|
}
|
|
377
225
|
}
|
|
378
|
-
if (xmlMachine) emitXml(xmlMachine.finalize());
|
|
379
226
|
emitter({ type: "text-flush" });
|
|
380
|
-
if (rawSegId) {
|
|
381
|
-
const inline = rawContent.split("\n").length < 15;
|
|
382
|
-
emitter({ type: "artifact-close", id: rawSegId, content: rawContent, inline });
|
|
383
|
-
}
|
|
384
227
|
} finally {
|
|
385
228
|
await iterator.return?.(void 0).catch(() => {
|
|
386
229
|
});
|
|
387
230
|
}
|
|
388
|
-
if (toolCallsThisTurn.length === 0)
|
|
389
|
-
if (pipeline && pipelineIndex < pipeline.length - 1) {
|
|
390
|
-
if (precedingText.trim()) {
|
|
391
|
-
append({ role: "assistant", content: precedingText.trim() });
|
|
392
|
-
}
|
|
393
|
-
pipelineIndex++;
|
|
394
|
-
emitter({ type: "phase-advance", index: pipelineIndex });
|
|
395
|
-
} else {
|
|
396
|
-
continueLoop = false;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
231
|
+
if (toolCallsThisTurn.length === 0) continueLoop = false;
|
|
399
232
|
}
|
|
400
233
|
emitter({ type: "run-done", usage: lastUsage });
|
|
401
234
|
return lastUsage;
|
|
402
235
|
}
|
|
403
|
-
function pushToolCallEnvelope(messages, append, toolCallsThisTurn, precedingText) {
|
|
404
|
-
const exists = messages.some(
|
|
405
|
-
(m) => m.role === "tool_call" && m.toolCalls?.some((tc) => toolCallsThisTurn.some((t) => t.id === tc.id))
|
|
406
|
-
);
|
|
407
|
-
if (exists) return;
|
|
408
|
-
append({
|
|
409
|
-
role: "tool_call",
|
|
410
|
-
content: "",
|
|
411
|
-
toolCalls: toolCallsThisTurn,
|
|
412
|
-
precedingText: precedingText.trim() || void 0
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
236
|
async function invokeToolHandler(handler, call, signal, toolTimeoutMs) {
|
|
416
237
|
if (signal.aborted) return { status: "aborted" };
|
|
417
238
|
const controller = new AbortController();
|
|
@@ -617,12 +438,45 @@ function compactConversation(input) {
|
|
|
617
438
|
};
|
|
618
439
|
return { compactedMessages, breakdown };
|
|
619
440
|
}
|
|
441
|
+
const read = (value) => typeof value === "function" ? value() : value;
|
|
442
|
+
const textOf = (message) => {
|
|
443
|
+
if (typeof message.content === "string" && message.content.length > 0) return message.content;
|
|
444
|
+
return (message.segments ?? []).map((segment) => {
|
|
445
|
+
const content = segment.content;
|
|
446
|
+
return typeof content === "string" ? content : "";
|
|
447
|
+
}).join("\n");
|
|
448
|
+
};
|
|
449
|
+
function createCompactionSelector(options) {
|
|
450
|
+
const minKeep = Math.max(0, options.minKeep ?? 2);
|
|
451
|
+
return (messages) => {
|
|
452
|
+
const contextWindow = read(options.contextWindow);
|
|
453
|
+
if (!contextWindow || contextWindow <= 0) return { keep: messages, drop: [] };
|
|
454
|
+
const budget = computeHistoryBudget({
|
|
455
|
+
systemPrompt: read(options.systemPrompt) ?? "",
|
|
456
|
+
toolsArray: read(options.tools),
|
|
457
|
+
config: { ...options.config, contextWindow }
|
|
458
|
+
});
|
|
459
|
+
const windowBudget = splitHistoryBudget(budget.historyBudget, budget.config).window;
|
|
460
|
+
let used = 0;
|
|
461
|
+
let cut = messages.length;
|
|
462
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
463
|
+
const cost = estimateTokens(textOf(messages[i]));
|
|
464
|
+
const kept = messages.length - 1 - i;
|
|
465
|
+
if (used + cost > windowBudget && kept >= minKeep) break;
|
|
466
|
+
used += cost;
|
|
467
|
+
cut = i;
|
|
468
|
+
}
|
|
469
|
+
if (cut === 0) return { keep: messages, drop: [] };
|
|
470
|
+
return { keep: messages.slice(cut), drop: messages.slice(0, cut) };
|
|
471
|
+
};
|
|
472
|
+
}
|
|
620
473
|
export {
|
|
621
|
-
ArtifactXmlStateMachine,
|
|
622
474
|
DEFAULT_COMPACTION_CONFIG,
|
|
623
475
|
assembleCompacted,
|
|
624
476
|
compactConversation,
|
|
625
477
|
computeHistoryBudget,
|
|
478
|
+
createCompactionSelector,
|
|
479
|
+
deriveArtifactKind,
|
|
626
480
|
estimateTokens,
|
|
627
481
|
estimateTokensJson,
|
|
628
482
|
runStreamAgent,
|