@aparte/core 0.13.1 → 0.14.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 +2 -2
- package/dist/client/aparte-client.d.ts +36 -35
- package/dist/client/aparte-client.d.ts.map +1 -1
- package/dist/client/stream-adapter.d.ts +37 -145
- package/dist/client/stream-adapter.d.ts.map +1 -1
- package/dist/components/bubble/aparte-chat-bubble.d.ts +10 -2
- package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-add-attachment.d.ts +6 -6
- package/dist/components/composer/aparte-composer-add-attachment.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-input.d.ts +9 -1
- package/dist/components/composer/aparte-composer-input.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer.d.ts +12 -3
- package/dist/components/composer/aparte-composer.d.ts.map +1 -1
- package/dist/components/context/aparte-context.d.ts +81 -0
- package/dist/components/context/aparte-context.d.ts.map +1 -0
- package/dist/components/context/index.d.ts +3 -0
- package/dist/components/context/index.d.ts.map +1 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/suggestions/aparte-suggestions.d.ts +95 -0
- package/dist/components/suggestions/aparte-suggestions.d.ts.map +1 -0
- package/dist/components/suggestions/index.d.ts +3 -0
- package/dist/components/suggestions/index.d.ts.map +1 -0
- package/dist/components/viewport/aparte-chat-viewport.d.ts +35 -7
- package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
- package/dist/config/aparte-config.d.ts +1 -21
- package/dist/config/aparte-config.d.ts.map +1 -1
- package/dist/config/index.d.ts +0 -2
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/locale.d.ts +30 -1
- package/dist/config/locale.d.ts.map +1 -1
- package/dist/conversations/index.d.ts +1 -1
- package/dist/conversations/index.d.ts.map +1 -1
- package/dist/conversations/types.d.ts +9 -57
- package/dist/conversations/types.d.ts.map +1 -1
- package/dist/custom-elements.json +7818 -7479
- package/dist/generated/element-attributes.d.ts +34 -6
- package/dist/generated/element-attributes.d.ts.map +1 -1
- package/dist/{glyphs-QXfbL3n_.js → glyphs-B3BZ2TIb.js} +21 -21
- package/dist/glyphs-B3BZ2TIb.js.map +1 -0
- package/dist/host/aparte-chat-host.d.ts +1 -1
- package/dist/host/aparte-chat-host.d.ts.map +1 -1
- package/dist/icons/extended.d.ts +87 -85
- package/dist/icons/extended.d.ts.map +1 -1
- package/dist/icons/glyphs.d.ts +53 -50
- package/dist/icons/glyphs.d.ts.map +1 -1
- package/dist/icons.js +41 -41
- package/dist/icons.js.map +1 -1
- package/dist/index.css +512 -170
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +593 -183
- package/dist/index.js.map +1 -1
- package/dist/index.node.d.ts +6 -4
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +56 -56
- package/dist/index.node.js.map +1 -1
- package/dist/{is-awaiting-reply-CWp1ffHh.js → is-awaiting-reply-BZ_iJ3s3.js} +175 -994
- package/dist/is-awaiting-reply-BZ_iJ3s3.js.map +1 -0
- package/dist/parsers/aparte-stream-parser.d.ts +6 -17
- package/dist/parsers/aparte-stream-parser.d.ts.map +1 -1
- package/dist/primitives/select/aparte-select.d.ts +2 -2
- package/dist/primitives/select/aparte-select.d.ts.map +1 -1
- package/dist/renderers/segment-renderers.d.ts.map +1 -1
- package/dist/renderers/segments/tool-call.d.ts.map +1 -1
- package/dist/runtime/message-repository.d.ts +7 -0
- package/dist/runtime/message-repository.d.ts.map +1 -1
- package/dist/transport/types.d.ts +8 -0
- package/dist/transport/types.d.ts.map +1 -1
- package/dist/types/chat.d.ts +32 -38
- package/dist/types/chat.d.ts.map +1 -1
- package/dist/types/errors.d.ts +16 -1
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/event-map.d.ts +4 -0
- package/dist/types/event-map.d.ts.map +1 -1
- package/dist/types/events.d.ts +15 -0
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/models.d.ts +1 -8
- package/dist/types/models.d.ts.map +1 -1
- package/dist/types/segments.d.ts +1 -13
- package/dist/types/segments.d.ts.map +1 -1
- package/dist/types/tools.d.ts +1 -1
- package/dist/utils/chat-boundary.d.ts +8 -4
- package/dist/utils/chat-boundary.d.ts.map +1 -1
- package/package.json +5 -2
- package/dist/client/xml-artifact-feed.d.ts +0 -76
- package/dist/client/xml-artifact-feed.d.ts.map +0 -1
- package/dist/config/skeleton-provider.d.ts +0 -26
- package/dist/config/skeleton-provider.d.ts.map +0 -1
- package/dist/glyphs-QXfbL3n_.js.map +0 -1
- package/dist/is-awaiting-reply-CWp1ffHh.js.map +0 -1
- package/dist/renderers/segments/pipeline-waiting.d.ts +0 -3
- package/dist/renderers/segments/pipeline-waiting.d.ts.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { A as APARTE_ICON_GLYPHS } from "./glyphs-
|
|
1
|
+
import { A as APARTE_ICON_GLYPHS } from "./glyphs-B3BZ2TIb.js";
|
|
2
|
+
import { deriveArtifactKind, runStreamAgent } from "@aparte/engine";
|
|
2
3
|
function contentToText(content) {
|
|
3
4
|
if (typeof content === "string") return content;
|
|
4
5
|
return content.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
@@ -6,6 +7,7 @@ function contentToText(content) {
|
|
|
6
7
|
var AparteErrorCode = /* @__PURE__ */ ((AparteErrorCode2) => {
|
|
7
8
|
AparteErrorCode2["CONFIG_NO_PROVIDER"] = "CONFIG_NO_PROVIDER";
|
|
8
9
|
AparteErrorCode2["CONFIG_MISSING_KEY"] = "CONFIG_MISSING_KEY";
|
|
10
|
+
AparteErrorCode2["CONFIG_INVALID_KEY"] = "CONFIG_INVALID_KEY";
|
|
9
11
|
AparteErrorCode2["CONFIG_INVALID_MODEL"] = "CONFIG_INVALID_MODEL";
|
|
10
12
|
AparteErrorCode2["USAGE_RATE_LIMIT"] = "USAGE_RATE_LIMIT";
|
|
11
13
|
AparteErrorCode2["USAGE_CONTEXT_EXCEEDED"] = "USAGE_CONTEXT_EXCEEDED";
|
|
@@ -29,11 +31,50 @@ class AparteError extends Error {
|
|
|
29
31
|
this.httpStatus = httpStatus;
|
|
30
32
|
this.name = "AparteError";
|
|
31
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The code an HTTP status stands for — the table the enum's own comments promise.
|
|
36
|
+
* `undefined` for a status the enum does not name (a 404 can be a wrong model or a
|
|
37
|
+
* wrong URL; the message says which, a code would guess).
|
|
38
|
+
*/
|
|
39
|
+
static codeForStatus(status) {
|
|
40
|
+
switch (status) {
|
|
41
|
+
case 400:
|
|
42
|
+
return "USAGE_BAD_REQUEST";
|
|
43
|
+
case 401:
|
|
44
|
+
case 403:
|
|
45
|
+
return "CONFIG_INVALID_KEY";
|
|
46
|
+
case 408:
|
|
47
|
+
return "NET_TIMEOUT";
|
|
48
|
+
case 429:
|
|
49
|
+
return "USAGE_RATE_LIMIT";
|
|
50
|
+
case 503:
|
|
51
|
+
return "PROVIDER_UNAVAILABLE";
|
|
52
|
+
default:
|
|
53
|
+
return status !== void 0 && status >= 500 ? "PROVIDER_ERROR" : void 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
32
56
|
static from(error, defaultCode = "UNKNOWN_ERROR", defaultStatus) {
|
|
33
57
|
if (error instanceof AparteError) return error;
|
|
34
58
|
const message = error instanceof Error ? error.message : String(error);
|
|
35
59
|
const status = error?.status || error?.statusCode || defaultStatus;
|
|
36
|
-
|
|
60
|
+
const code = defaultCode !== "UNKNOWN_ERROR" ? defaultCode : AparteError.codeForStatus(status) ?? AparteError.codeForFailure(error) ?? defaultCode;
|
|
61
|
+
return new AparteError(message, code, void 0, error, status);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* What a thrown, statusless error says about itself. `fetch` rejects a network
|
|
65
|
+
* failure with a TypeError whose message names the fetch ("Failed to fetch",
|
|
66
|
+
* "NetworkError when attempting to fetch resource.", "Load failed") — any other
|
|
67
|
+
* TypeError is a bug, not the network, and stays unclassified. `AbortSignal.timeout()`
|
|
68
|
+
* rejects with a TimeoutError.
|
|
69
|
+
*/
|
|
70
|
+
static codeForFailure(error) {
|
|
71
|
+
if (!(error instanceof Error)) return void 0;
|
|
72
|
+
if (error.name === "TimeoutError") return "NET_TIMEOUT";
|
|
73
|
+
if (error instanceof TypeError && /fetch|network|load failed/i.test(error.message)) {
|
|
74
|
+
const offline = typeof navigator !== "undefined" && navigator.onLine === false;
|
|
75
|
+
return offline ? "NET_OFFLINE" : "NET_ERROR";
|
|
76
|
+
}
|
|
77
|
+
return void 0;
|
|
37
78
|
}
|
|
38
79
|
}
|
|
39
80
|
const APARTE_DEFAULT_ICON_FALLBACKS = APARTE_ICON_GLYPHS;
|
|
@@ -57,6 +98,10 @@ const APARTE_DEFAULT_LOCALE = {
|
|
|
57
98
|
approvalInstructionPlaceholder: "Or tell it what to do instead…",
|
|
58
99
|
approvalAsk: "Run {tool}?",
|
|
59
100
|
approvalWaiting: "waiting for you",
|
|
101
|
+
scrollToBottom: "Scroll to bottom",
|
|
102
|
+
suggestionsLabel: "Suggested prompts",
|
|
103
|
+
contextLabel: "Context window",
|
|
104
|
+
compactionSummaryTitle: "Conversation summary",
|
|
60
105
|
toolRunning: "Running",
|
|
61
106
|
toolCompleted: "Done",
|
|
62
107
|
toolRejected: "Rejected",
|
|
@@ -101,14 +146,6 @@ const APARTE_DEFAULT_LOCALE = {
|
|
|
101
146
|
unarchiveConversation: "Unarchive conversation",
|
|
102
147
|
direction: "ltr"
|
|
103
148
|
};
|
|
104
|
-
const APARTE_DEFAULT_SKELETON_FALLBACKS = {
|
|
105
|
-
message: '<div class="aparte-skeleton-fallback">Loading...</div>',
|
|
106
|
-
code: '<div class="aparte-skeleton-fallback aparte-skeleton-fallback--code">Loading code...</div>',
|
|
107
|
-
thinking: '<div class="aparte-skeleton-fallback aparte-skeleton-fallback--tight">Thinking...</div>',
|
|
108
|
-
input: '<div class="aparte-skeleton-fallback aparte-skeleton-fallback--snug">...</div>',
|
|
109
|
-
list: '<div class="aparte-skeleton-fallback">Loading items...</div>',
|
|
110
|
-
text: '<div class="aparte-skeleton-fallback aparte-skeleton-fallback--tight">...</div>'
|
|
111
|
-
};
|
|
112
149
|
function isFormatAdapter(p) {
|
|
113
150
|
return typeof p.buildRequest === "function" && typeof p.parseStream === "function" && typeof p.defaultEndpoint === "string" && (typeof p.authHeaders === "function" || typeof p.authQuery === "function");
|
|
114
151
|
}
|
|
@@ -123,6 +160,11 @@ async function vendorErrorMessage(response, label = "HTTP") {
|
|
|
123
160
|
const msg = typeof raw === "object" ? raw?.message : raw;
|
|
124
161
|
return typeof msg === "string" && msg ? msg : `${label} ${response.status}`;
|
|
125
162
|
}
|
|
163
|
+
async function vendorError(response, label = "HTTP") {
|
|
164
|
+
const message = await vendorErrorMessage(response, label);
|
|
165
|
+
const code = AparteError.codeForStatus(response.status) ?? AparteErrorCode.UNKNOWN_ERROR;
|
|
166
|
+
return new AparteError(message, code, void 0, void 0, response.status);
|
|
167
|
+
}
|
|
126
168
|
async function parseNonStreamText(provider, response, providerId) {
|
|
127
169
|
const json = await response.json();
|
|
128
170
|
if (typeof provider.parseText !== "function") {
|
|
@@ -163,7 +205,7 @@ class AparteDirectTransport {
|
|
|
163
205
|
signal: ctx.signal
|
|
164
206
|
});
|
|
165
207
|
if (!response.ok) {
|
|
166
|
-
throw
|
|
208
|
+
throw await vendorError(response);
|
|
167
209
|
}
|
|
168
210
|
if (request.stream === false) {
|
|
169
211
|
return parseNonStreamText(provider, response, ctx.providerId);
|
|
@@ -241,7 +283,7 @@ class AparteStreamParser {
|
|
|
241
283
|
if ("content" in this._state.activeSegment) {
|
|
242
284
|
const seg = this._state.activeSegment;
|
|
243
285
|
let content = seg.content + this._state.buffer;
|
|
244
|
-
if (seg.type === "text") content = stripTrailingFence(content);
|
|
286
|
+
if (seg.type === "text" || seg.type === "code") content = stripTrailingFence(content);
|
|
245
287
|
seg.content = content;
|
|
246
288
|
}
|
|
247
289
|
finalSegments.push(this._closed(this._state.activeSegment));
|
|
@@ -362,16 +404,6 @@ class AparteStreamParser {
|
|
|
362
404
|
_parseCodeMode(buffer) {
|
|
363
405
|
const closeIndex = buffer.indexOf("\n```");
|
|
364
406
|
if (closeIndex === -1) {
|
|
365
|
-
if (buffer.endsWith("```")) {
|
|
366
|
-
const codeContent2 = buffer.slice(0, -3);
|
|
367
|
-
if (this._state.activeSegment && "content" in this._state.activeSegment) {
|
|
368
|
-
this._state.activeSegment.content += codeContent2;
|
|
369
|
-
}
|
|
370
|
-
const segment2 = this._closed(this._state.activeSegment);
|
|
371
|
-
this._state.mode = "text";
|
|
372
|
-
this._state.activeSegment = null;
|
|
373
|
-
return { segment: segment2, remaining: "" };
|
|
374
|
-
}
|
|
375
407
|
if (buffer.length > 4) {
|
|
376
408
|
const safeContent = buffer.slice(0, -4);
|
|
377
409
|
if (this._state.activeSegment && "content" in this._state.activeSegment) {
|
|
@@ -594,28 +626,6 @@ function parseMarkdownToSegments(content, options) {
|
|
|
594
626
|
const finalSegments = parser.finalize();
|
|
595
627
|
return [...result.segments, ...finalSegments];
|
|
596
628
|
}
|
|
597
|
-
function deriveArtifactKind(mimeType, fallback = "unknown") {
|
|
598
|
-
const m = (mimeType || "").toLowerCase().trim();
|
|
599
|
-
const ant = m.match(/^application\/vnd\.ant\.([a-z0-9-]+)/);
|
|
600
|
-
if (ant) return ant[1];
|
|
601
|
-
if (m === "text/html" || m === "application/xhtml+xml") return "html";
|
|
602
|
-
if (m === "application/javascript" || m === "text/javascript") return "js";
|
|
603
|
-
if (m === "text/css") return "css";
|
|
604
|
-
if (m === "image/svg+xml") return "svg";
|
|
605
|
-
if (m === "application/json") return "json";
|
|
606
|
-
if (m === "text/markdown") return "markdown";
|
|
607
|
-
if (m === "text/csv") return "csv";
|
|
608
|
-
if (m === "text/plain") return "text";
|
|
609
|
-
if (m.includes("react")) return "react";
|
|
610
|
-
if (m.includes("html")) return "html";
|
|
611
|
-
if (m.includes("javascript")) return "js";
|
|
612
|
-
if (m.includes("css")) return "css";
|
|
613
|
-
if (m.includes("svg")) return "svg";
|
|
614
|
-
if (m.includes("json")) return "json";
|
|
615
|
-
if (m.includes("csv")) return "csv";
|
|
616
|
-
if (m.includes("markdown")) return "markdown";
|
|
617
|
-
return fallback;
|
|
618
|
-
}
|
|
619
629
|
function parseAparteEventStream(stream) {
|
|
620
630
|
const decoder = new TextDecoder();
|
|
621
631
|
let buffer = "";
|
|
@@ -668,7 +678,7 @@ class AparteBackendTransport {
|
|
|
668
678
|
signal: ctx.signal
|
|
669
679
|
});
|
|
670
680
|
if (!response.ok) {
|
|
671
|
-
throw
|
|
681
|
+
throw await vendorError(response, "Backend HTTP");
|
|
672
682
|
}
|
|
673
683
|
if (request.stream === false) {
|
|
674
684
|
const json = await response.json();
|
|
@@ -1056,7 +1066,7 @@ const escapeAttr = escapeHtml;
|
|
|
1056
1066
|
function isChatBoundary(el2) {
|
|
1057
1067
|
if (!el2) return false;
|
|
1058
1068
|
const tag = el2.tagName?.toLowerCase();
|
|
1059
|
-
return tag === "aparte-chat" ||
|
|
1069
|
+
return tag === "aparte-chat" || el2.hasAttribute?.("data-aparte-chat") === true;
|
|
1060
1070
|
}
|
|
1061
1071
|
function chatBoundaryOf(el2) {
|
|
1062
1072
|
let node = el2 ?? null;
|
|
@@ -1090,7 +1100,6 @@ class AparteConfig {
|
|
|
1090
1100
|
_sanitizer = defaultSanitizer;
|
|
1091
1101
|
_systemPromptTemplate;
|
|
1092
1102
|
_systemPromptVarsProvider;
|
|
1093
|
-
_skeletonProvider;
|
|
1094
1103
|
_statusRenderer;
|
|
1095
1104
|
_errorRenderer;
|
|
1096
1105
|
_attachmentRenderer;
|
|
@@ -1367,13 +1376,6 @@ class AparteConfig {
|
|
|
1367
1376
|
const resolved = this._systemPromptTemplate.replace(/\{\{([^}]+)\}\}/g, (_, key) => vars[key.trim()] ?? "");
|
|
1368
1377
|
return resolved.trim() || null;
|
|
1369
1378
|
}
|
|
1370
|
-
/**
|
|
1371
|
-
* Set a custom Skeleton generator for loading states
|
|
1372
|
-
* @param provider Object implementing AparteSkeletonProvider interface
|
|
1373
|
-
*/
|
|
1374
|
-
setSkeletonProvider(provider) {
|
|
1375
|
-
this._skeletonProvider = provider;
|
|
1376
|
-
}
|
|
1377
1379
|
/**
|
|
1378
1380
|
* Set a custom Icon provider (e.g., Lucide, FontAwesome, Material)
|
|
1379
1381
|
* @param provider Object implementing AparteIconProvider interface
|
|
@@ -1831,16 +1833,6 @@ class AparteConfig {
|
|
|
1831
1833
|
}
|
|
1832
1834
|
return this._defaultHighlightRenderer(code);
|
|
1833
1835
|
}
|
|
1834
|
-
/**
|
|
1835
|
-
* Get HTML for a skeleton loader
|
|
1836
|
-
* Fallback: Simple CSS-animated box
|
|
1837
|
-
*/
|
|
1838
|
-
getSkeleton(type) {
|
|
1839
|
-
if (this._skeletonProvider) {
|
|
1840
|
-
return this._skeletonProvider.getSkeleton(type);
|
|
1841
|
-
}
|
|
1842
|
-
return this._defaultSkeletonRenderer(type);
|
|
1843
|
-
}
|
|
1844
1836
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1845
1837
|
// Tool Registry
|
|
1846
1838
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -2087,7 +2079,6 @@ class AparteConfig {
|
|
|
2087
2079
|
this._highlightProvider = void 0;
|
|
2088
2080
|
this._systemPromptTemplate = void 0;
|
|
2089
2081
|
this._systemPromptVarsProvider = void 0;
|
|
2090
|
-
this._skeletonProvider = void 0;
|
|
2091
2082
|
this._statusRenderer = void 0;
|
|
2092
2083
|
this._errorRenderer = void 0;
|
|
2093
2084
|
this._attachmentRenderer = void 0;
|
|
@@ -2128,16 +2119,6 @@ class AparteConfig {
|
|
|
2128
2119
|
_defaultHighlightRenderer(code) {
|
|
2129
2120
|
return `<pre><code>${escapeHtml(code)}</code></pre>`;
|
|
2130
2121
|
}
|
|
2131
|
-
/**
|
|
2132
|
-
* ONE owner. This used to hold a second, hand-written copy of
|
|
2133
|
-
* `APARTE_DEFAULT_SKELETON_FALLBACKS`, and the two had already drifted — `message`
|
|
2134
|
-
* said "Loading message…" here and "Loading…" there, `text` the reverse. A duplicate
|
|
2135
|
-
* that disagrees with its original is worse than either: whichever one you read, you
|
|
2136
|
-
* cannot tell which one ships.
|
|
2137
|
-
*/
|
|
2138
|
-
_defaultSkeletonRenderer(type) {
|
|
2139
|
-
return APARTE_DEFAULT_SKELETON_FALLBACKS[type] ?? APARTE_DEFAULT_SKELETON_FALLBACKS.text;
|
|
2140
|
-
}
|
|
2141
2122
|
}
|
|
2142
2123
|
const GLOBAL_CONFIG_KEY = Symbol.for("aparte.globalConfig.1");
|
|
2143
2124
|
function getGlobalConfig() {
|
|
@@ -2475,7 +2456,7 @@ function stateBadge(segment) {
|
|
|
2475
2456
|
if (status === "pending") return word("toolRunning");
|
|
2476
2457
|
if (status === "resolved") return `${cfg.getIcon("check")}${word("toolCompleted")}`;
|
|
2477
2458
|
if (status === "rejected") return `${cfg.getIcon("close")}${word("toolRejected")}`;
|
|
2478
|
-
if (status === "aborted") return `${cfg.getIcon("
|
|
2459
|
+
if (status === "aborted") return `${cfg.getIcon("stop")}${word("toolStopped")}`;
|
|
2479
2460
|
return "";
|
|
2480
2461
|
}
|
|
2481
2462
|
function setPart(element, part, text) {
|
|
@@ -2636,44 +2617,6 @@ const toolCallRenderer = {
|
|
|
2636
2617
|
// has no other way onto the page.
|
|
2637
2618
|
getStyles: () => ""
|
|
2638
2619
|
};
|
|
2639
|
-
const waitingLabel = () => contextConfig().t("generating");
|
|
2640
|
-
const pipelineWaitingRenderer = {
|
|
2641
|
-
type: "pipeline-waiting",
|
|
2642
|
-
render: (segment) => {
|
|
2643
|
-
return `
|
|
2644
|
-
<div class="aparte-segment aparte-segment-pipeline-waiting" data-segment-id="${escapeHtml(segment.id)}" aria-label="${escapeAttr(waitingLabel())}" role="status">
|
|
2645
|
-
<span class="aparte-dot aparte-pw-dot"></span>
|
|
2646
|
-
<span class="aparte-dot aparte-pw-dot"></span>
|
|
2647
|
-
<span class="aparte-dot aparte-pw-dot"></span>
|
|
2648
|
-
</div>`;
|
|
2649
|
-
},
|
|
2650
|
-
update: () => {
|
|
2651
|
-
},
|
|
2652
|
-
/**
|
|
2653
|
-
* Three dots and an accessible name. The dots are CSS; the name is the whole
|
|
2654
|
-
* content as far as a screen reader is concerned, and it was hardcoded English in
|
|
2655
|
-
* every locale — the one string in this library where being untranslated is
|
|
2656
|
-
* invisible to everyone who can see the screen.
|
|
2657
|
-
*/
|
|
2658
|
-
relabel: (element) => {
|
|
2659
|
-
element.setAttribute("aria-label", waitingLabel());
|
|
2660
|
-
},
|
|
2661
|
-
setup: (el2) => {
|
|
2662
|
-
const parent = el2.parentElement;
|
|
2663
|
-
if (!parent) return;
|
|
2664
|
-
const observer = new MutationObserver(() => {
|
|
2665
|
-
if (el2.nextElementSibling) {
|
|
2666
|
-
observer.disconnect();
|
|
2667
|
-
el2.remove();
|
|
2668
|
-
}
|
|
2669
|
-
});
|
|
2670
|
-
observer.observe(parent, { childList: true });
|
|
2671
|
-
},
|
|
2672
|
-
// CSS lives in styles/segment/ — see that folder for why a
|
|
2673
|
-
// built-in's rules belong there. `getStyles` stays for a CONSUMER's renderer, which
|
|
2674
|
-
// has no other way onto the page.
|
|
2675
|
-
getStyles: () => ""
|
|
2676
|
-
};
|
|
2677
2620
|
function stripCodeFences(content) {
|
|
2678
2621
|
let s = content;
|
|
2679
2622
|
if (s.startsWith("```") || s.startsWith("~~~")) {
|
|
@@ -3467,8 +3410,7 @@ const DEFAULT_RENDERERS = [
|
|
|
3467
3410
|
codeRenderer,
|
|
3468
3411
|
errorRenderer,
|
|
3469
3412
|
toolCallRenderer,
|
|
3470
|
-
artifactRenderer
|
|
3471
|
-
pipelineWaitingRenderer
|
|
3413
|
+
artifactRenderer
|
|
3472
3414
|
];
|
|
3473
3415
|
const OPEN_TOOL_STATUSES = /* @__PURE__ */ new Set(["pending", "awaiting-approval"]);
|
|
3474
3416
|
const ACTIVITY_KEYS = ["content", "output", "result"];
|
|
@@ -4428,7 +4370,7 @@ class AparteChatHost {
|
|
|
4428
4370
|
this.binding.setMessages(next);
|
|
4429
4371
|
this.binding.onMessagesChange?.(next);
|
|
4430
4372
|
}
|
|
4431
|
-
/** Remove a transient segment (
|
|
4373
|
+
/** Remove a transient segment (a status row the host added itself). */
|
|
4432
4374
|
removeSegment(segmentId) {
|
|
4433
4375
|
this._flushStreamState();
|
|
4434
4376
|
this._lastBubble()?.removeSegment?.(segmentId);
|
|
@@ -4607,7 +4549,7 @@ class AparteChatHost {
|
|
|
4607
4549
|
this._bufferToken(messageId, res.value);
|
|
4608
4550
|
}
|
|
4609
4551
|
this._flushStreamState();
|
|
4610
|
-
|
|
4552
|
+
this._vp()?.completeMessage?.(messageId);
|
|
4611
4553
|
this._setStreamingId(null);
|
|
4612
4554
|
} catch (err) {
|
|
4613
4555
|
this._flushStreamState();
|
|
@@ -4864,176 +4806,6 @@ function dispatchArtifactLifecycle(target, messageId, segment, progress, isFinal
|
|
|
4864
4806
|
}));
|
|
4865
4807
|
}
|
|
4866
4808
|
}
|
|
4867
|
-
const INLINE_MAX_LINES = 15;
|
|
4868
|
-
const XML_OPEN_TAG = "<artifact";
|
|
4869
|
-
function partialXmlOpenTagLength(text) {
|
|
4870
|
-
const max = Math.min(text.length, XML_OPEN_TAG.length - 1);
|
|
4871
|
-
for (let k = max; k > 0; k--) {
|
|
4872
|
-
if (text.endsWith(XML_OPEN_TAG.slice(0, k))) return k;
|
|
4873
|
-
}
|
|
4874
|
-
return 0;
|
|
4875
|
-
}
|
|
4876
|
-
function feedXmlArtifactDelta(delta, xml, ctx) {
|
|
4877
|
-
const { targetElement, messageId, textParser, streamingSegmentIds, artifactProgress, artifactXmlHint } = ctx;
|
|
4878
|
-
let remaining = delta;
|
|
4879
|
-
const emitChatText = (text, syncActive = true) => {
|
|
4880
|
-
if (!text) return;
|
|
4881
|
-
const r = textParser.parse(text);
|
|
4882
|
-
for (const seg of r.segments) {
|
|
4883
|
-
if (!streamingSegmentIds.has(seg.id)) {
|
|
4884
|
-
targetElement.addSegment?.(seg);
|
|
4885
|
-
streamingSegmentIds.add(seg.id);
|
|
4886
|
-
} else if ("content" in seg) {
|
|
4887
|
-
targetElement.updateSegment?.(seg.id, segmentContentUpdate(seg));
|
|
4888
|
-
}
|
|
4889
|
-
}
|
|
4890
|
-
const active = textParser.getState().activeSegment;
|
|
4891
|
-
if (syncActive && active) {
|
|
4892
|
-
if (!streamingSegmentIds.has(active.id)) {
|
|
4893
|
-
targetElement.addSegment?.(active);
|
|
4894
|
-
streamingSegmentIds.add(active.id);
|
|
4895
|
-
} else {
|
|
4896
|
-
targetElement.updateSegment?.(active.id, segmentContentUpdate(active));
|
|
4897
|
-
}
|
|
4898
|
-
} else if (!r.segments.length && !active) ;
|
|
4899
|
-
};
|
|
4900
|
-
while (remaining.length > 0) {
|
|
4901
|
-
if (xml.state === "normal") {
|
|
4902
|
-
const tagStart = remaining.indexOf(XML_OPEN_TAG);
|
|
4903
|
-
if (tagStart === -1) {
|
|
4904
|
-
const held = partialXmlOpenTagLength(remaining);
|
|
4905
|
-
if (held > 0) {
|
|
4906
|
-
emitChatText(remaining.slice(0, remaining.length - held), false);
|
|
4907
|
-
xml.scanBuf = remaining.slice(remaining.length - held);
|
|
4908
|
-
xml.state = "scanning";
|
|
4909
|
-
} else {
|
|
4910
|
-
emitChatText(remaining);
|
|
4911
|
-
}
|
|
4912
|
-
remaining = "";
|
|
4913
|
-
} else {
|
|
4914
|
-
emitChatText(remaining.slice(0, tagStart), false);
|
|
4915
|
-
xml.scanBuf = "";
|
|
4916
|
-
remaining = remaining.slice(tagStart);
|
|
4917
|
-
xml.state = "scanning";
|
|
4918
|
-
}
|
|
4919
|
-
} else if (xml.state === "scanning") {
|
|
4920
|
-
xml.scanBuf += remaining;
|
|
4921
|
-
remaining = "";
|
|
4922
|
-
const cmp = Math.min(xml.scanBuf.length, XML_OPEN_TAG.length);
|
|
4923
|
-
if (xml.scanBuf.slice(0, cmp) !== XML_OPEN_TAG.slice(0, cmp)) {
|
|
4924
|
-
emitChatText(xml.scanBuf);
|
|
4925
|
-
xml.scanBuf = "";
|
|
4926
|
-
xml.state = "normal";
|
|
4927
|
-
continue;
|
|
4928
|
-
}
|
|
4929
|
-
const gtIdx = xml.scanBuf.indexOf(">");
|
|
4930
|
-
if (gtIdx !== -1) {
|
|
4931
|
-
const tag = xml.scanBuf.slice(0, gtIdx + 1);
|
|
4932
|
-
const mimeMatch = /mimeType=['"]([^'"]+)['"]/.exec(tag);
|
|
4933
|
-
const titleMatch = /title=['"]([^'"]+)['"]/.exec(tag);
|
|
4934
|
-
xml.mime = mimeMatch?.[1] ?? artifactXmlHint.mimeType;
|
|
4935
|
-
xml.title = titleMatch?.[1] ?? artifactXmlHint.kind;
|
|
4936
|
-
xml.kind = deriveArtifactKind(xml.mime, artifactXmlHint.kind);
|
|
4937
|
-
xml.segId = `artifact-xml-${uuid()}`;
|
|
4938
|
-
xml.content = "";
|
|
4939
|
-
const openSeg = {
|
|
4940
|
-
id: xml.segId,
|
|
4941
|
-
type: "artifact",
|
|
4942
|
-
mimeType: xml.mime,
|
|
4943
|
-
artifactType: xml.kind,
|
|
4944
|
-
title: xml.title,
|
|
4945
|
-
content: ""
|
|
4946
|
-
};
|
|
4947
|
-
targetElement.addSegment?.(openSeg);
|
|
4948
|
-
streamingSegmentIds.add(xml.segId);
|
|
4949
|
-
dispatchArtifactLifecycle(targetElement, messageId, openSeg, artifactProgress, false);
|
|
4950
|
-
xml.state = "in-artifact";
|
|
4951
|
-
remaining = xml.scanBuf.slice(gtIdx + 1);
|
|
4952
|
-
xml.scanBuf = "";
|
|
4953
|
-
}
|
|
4954
|
-
} else {
|
|
4955
|
-
const CLOSE = "</artifact>";
|
|
4956
|
-
const combined = xml.closeBuf + remaining;
|
|
4957
|
-
const closeIdx = combined.indexOf(CLOSE);
|
|
4958
|
-
if (closeIdx !== -1) {
|
|
4959
|
-
xml.content += combined.slice(0, closeIdx);
|
|
4960
|
-
const lineCount = xml.content.split("\n").length;
|
|
4961
|
-
const isInline = lineCount < INLINE_MAX_LINES;
|
|
4962
|
-
const finalSeg = {
|
|
4963
|
-
id: xml.segId,
|
|
4964
|
-
mimeType: xml.mime,
|
|
4965
|
-
artifactType: xml.kind,
|
|
4966
|
-
title: xml.title,
|
|
4967
|
-
content: xml.content
|
|
4968
|
-
};
|
|
4969
|
-
targetElement.updateSegment?.(xml.segId, { content: xml.content, inline: isInline });
|
|
4970
|
-
dispatchArtifactLifecycle(targetElement, messageId, finalSeg, artifactProgress, true);
|
|
4971
|
-
xml.state = "normal";
|
|
4972
|
-
xml.closeBuf = "";
|
|
4973
|
-
remaining = combined.slice(closeIdx + CLOSE.length);
|
|
4974
|
-
} else {
|
|
4975
|
-
const safeLen = Math.max(0, combined.length - CLOSE.length + 1);
|
|
4976
|
-
const safe = combined.slice(0, safeLen);
|
|
4977
|
-
xml.content += safe;
|
|
4978
|
-
xml.closeBuf = combined.slice(safeLen);
|
|
4979
|
-
remaining = "";
|
|
4980
|
-
if (xml.segId) {
|
|
4981
|
-
targetElement.updateSegment?.(xml.segId, { content: xml.content });
|
|
4982
|
-
dispatchArtifactLifecycle(targetElement, messageId, {
|
|
4983
|
-
id: xml.segId,
|
|
4984
|
-
mimeType: xml.mime,
|
|
4985
|
-
artifactType: xml.kind,
|
|
4986
|
-
title: xml.title,
|
|
4987
|
-
content: xml.content
|
|
4988
|
-
}, artifactProgress, false);
|
|
4989
|
-
}
|
|
4990
|
-
}
|
|
4991
|
-
}
|
|
4992
|
-
}
|
|
4993
|
-
}
|
|
4994
|
-
function finalizeXmlArtifact(xml, ctx) {
|
|
4995
|
-
const { targetElement, messageId, artifactProgress, artifactXmlHint } = ctx;
|
|
4996
|
-
if (xml.state === "scanning") {
|
|
4997
|
-
const held = xml.scanBuf;
|
|
4998
|
-
xml.scanBuf = "";
|
|
4999
|
-
xml.state = "normal";
|
|
5000
|
-
if (held) {
|
|
5001
|
-
const r = ctx.textParser.parse(held);
|
|
5002
|
-
for (const seg of r.segments) {
|
|
5003
|
-
if (!ctx.streamingSegmentIds.has(seg.id)) {
|
|
5004
|
-
targetElement.addSegment?.(seg);
|
|
5005
|
-
ctx.streamingSegmentIds.add(seg.id);
|
|
5006
|
-
} else if ("content" in seg) {
|
|
5007
|
-
targetElement.updateSegment?.(seg.id, segmentContentUpdate(seg));
|
|
5008
|
-
}
|
|
5009
|
-
}
|
|
5010
|
-
const active = ctx.textParser.getState().activeSegment;
|
|
5011
|
-
if (active) {
|
|
5012
|
-
if (!ctx.streamingSegmentIds.has(active.id)) {
|
|
5013
|
-
targetElement.addSegment?.(active);
|
|
5014
|
-
ctx.streamingSegmentIds.add(active.id);
|
|
5015
|
-
} else {
|
|
5016
|
-
targetElement.updateSegment?.(active.id, segmentContentUpdate(active));
|
|
5017
|
-
}
|
|
5018
|
-
}
|
|
5019
|
-
}
|
|
5020
|
-
return;
|
|
5021
|
-
}
|
|
5022
|
-
if (artifactXmlHint && xml.state === "in-artifact" && xml.segId) {
|
|
5023
|
-
xml.content += xml.closeBuf;
|
|
5024
|
-
const lineCount = xml.content.split("\n").length;
|
|
5025
|
-
const isInline = lineCount < INLINE_MAX_LINES;
|
|
5026
|
-
targetElement.updateSegment?.(xml.segId, { content: xml.content, inline: isInline });
|
|
5027
|
-
dispatchArtifactLifecycle(targetElement, messageId, {
|
|
5028
|
-
id: xml.segId,
|
|
5029
|
-
mimeType: xml.mime,
|
|
5030
|
-
artifactType: xml.kind,
|
|
5031
|
-
title: xml.title,
|
|
5032
|
-
content: xml.content
|
|
5033
|
-
}, artifactProgress, true);
|
|
5034
|
-
console.warn("[AparteClient] XML artifact finalized without closing tag — content may be partial.");
|
|
5035
|
-
}
|
|
5036
|
-
}
|
|
5037
4809
|
function createStreamAdapter(opts) {
|
|
5038
4810
|
const { target, config, messageId, artifactHint } = opts;
|
|
5039
4811
|
let parser = new AparteStreamParser();
|
|
@@ -5043,7 +4815,6 @@ function createStreamAdapter(opts) {
|
|
|
5043
4815
|
let thinkingCollapsed = false;
|
|
5044
4816
|
let artifactProgress = /* @__PURE__ */ new Map();
|
|
5045
4817
|
let artifactPromoted = false;
|
|
5046
|
-
let artifactMeta = /* @__PURE__ */ new Map();
|
|
5047
4818
|
return (e) => {
|
|
5048
4819
|
switch (e.type) {
|
|
5049
4820
|
case "run-start":
|
|
@@ -5057,7 +4828,6 @@ function createStreamAdapter(opts) {
|
|
|
5057
4828
|
thinkingCollapsed = false;
|
|
5058
4829
|
artifactProgress = /* @__PURE__ */ new Map();
|
|
5059
4830
|
artifactPromoted = false;
|
|
5060
|
-
artifactMeta = /* @__PURE__ */ new Map();
|
|
5061
4831
|
break;
|
|
5062
4832
|
case "thinking-delta": {
|
|
5063
4833
|
thinkingContent += e.delta;
|
|
@@ -5082,18 +4852,6 @@ function createStreamAdapter(opts) {
|
|
|
5082
4852
|
target.updateSegment?.(thinkingId, { collapsed: true, isStreaming: false });
|
|
5083
4853
|
thinkingCollapsed = true;
|
|
5084
4854
|
}
|
|
5085
|
-
if (e.reduced) {
|
|
5086
|
-
const r = parser.parse(e.delta);
|
|
5087
|
-
for (const segment of r.segments) {
|
|
5088
|
-
if (!streaming.has(segment.id)) {
|
|
5089
|
-
target.addSegment?.(segment);
|
|
5090
|
-
streaming.add(segment.id);
|
|
5091
|
-
} else if ("content" in segment) {
|
|
5092
|
-
target.updateSegment?.(segment.id, segmentContentUpdate(segment));
|
|
5093
|
-
}
|
|
5094
|
-
}
|
|
5095
|
-
break;
|
|
5096
|
-
}
|
|
5097
4855
|
const result = parser.parse(e.delta);
|
|
5098
4856
|
for (let segment of result.segments) {
|
|
5099
4857
|
if (artifactHint && !artifactPromoted && segment.type === "code") {
|
|
@@ -5166,33 +4924,6 @@ function createStreamAdapter(opts) {
|
|
|
5166
4924
|
}
|
|
5167
4925
|
break;
|
|
5168
4926
|
}
|
|
5169
|
-
case "artifact-open": {
|
|
5170
|
-
const seg = {
|
|
5171
|
-
id: e.id,
|
|
5172
|
-
type: "artifact",
|
|
5173
|
-
mimeType: e.mimeType,
|
|
5174
|
-
artifactType: e.kind,
|
|
5175
|
-
title: e.title,
|
|
5176
|
-
content: ""
|
|
5177
|
-
};
|
|
5178
|
-
target.addSegment?.(seg);
|
|
5179
|
-
streaming.add(e.id);
|
|
5180
|
-
artifactMeta.set(e.id, { mimeType: e.mimeType, artifactType: e.kind, title: e.title });
|
|
5181
|
-
dispatchArtifactLifecycle(target, messageId, seg, artifactProgress, false);
|
|
5182
|
-
break;
|
|
5183
|
-
}
|
|
5184
|
-
case "artifact-chunk": {
|
|
5185
|
-
const meta = artifactMeta.get(e.id);
|
|
5186
|
-
target.updateSegment?.(e.id, { content: e.content });
|
|
5187
|
-
dispatchArtifactLifecycle(target, messageId, { id: e.id, content: e.content, ...meta }, artifactProgress, false);
|
|
5188
|
-
break;
|
|
5189
|
-
}
|
|
5190
|
-
case "artifact-close": {
|
|
5191
|
-
const meta = artifactMeta.get(e.id);
|
|
5192
|
-
target.updateSegment?.(e.id, { content: e.content, inline: e.inline });
|
|
5193
|
-
dispatchArtifactLifecycle(target, messageId, { id: e.id, content: e.content, ...meta }, artifactProgress, true);
|
|
5194
|
-
break;
|
|
5195
|
-
}
|
|
5196
4927
|
case "artifact-ready": {
|
|
5197
4928
|
const seg = {
|
|
5198
4929
|
id: e.id,
|
|
@@ -5251,9 +4982,6 @@ function createStreamAdapter(opts) {
|
|
|
5251
4982
|
target.updateSegment?.(`tool-${e.toolCallId}`, { status: "aborted" });
|
|
5252
4983
|
}
|
|
5253
4984
|
break;
|
|
5254
|
-
case "phase-advance":
|
|
5255
|
-
target.addSegment?.({ id: `pw-${uuid()}`, type: "pipeline-waiting" });
|
|
5256
|
-
break;
|
|
5257
4985
|
case "run-aborted":
|
|
5258
4986
|
dispatchLifecycleEvent(target, "aparte-message-aborted", { messageId });
|
|
5259
4987
|
break;
|
|
@@ -5791,20 +5519,6 @@ function buildApprovalPanel(message, options, onChange) {
|
|
|
5791
5519
|
function requestUserInput(request) {
|
|
5792
5520
|
return resolveConfig(request.target ?? null).requestUserInput(request);
|
|
5793
5521
|
}
|
|
5794
|
-
const DEFAULT_TOOL_HANDLER_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
5795
|
-
function withToolTimeout(work, controller, timeoutMs) {
|
|
5796
|
-
let timer;
|
|
5797
|
-
const timedOut = new Promise((_resolve, reject) => {
|
|
5798
|
-
timer = setTimeout(() => {
|
|
5799
|
-
controller.abort();
|
|
5800
|
-
reject(Object.assign(new Error(`tool handler exceeded ${timeoutMs}ms`), { name: "AbortError" }));
|
|
5801
|
-
}, timeoutMs);
|
|
5802
|
-
});
|
|
5803
|
-
return {
|
|
5804
|
-
raced: Promise.race([work(controller.signal), timedOut]),
|
|
5805
|
-
done: () => clearTimeout(timer)
|
|
5806
|
-
};
|
|
5807
|
-
}
|
|
5808
5522
|
class AparteClient {
|
|
5809
5523
|
_boundHandler = null;
|
|
5810
5524
|
_boundAbortHandler = null;
|
|
@@ -5839,6 +5553,8 @@ class AparteClient {
|
|
|
5839
5553
|
}
|
|
5840
5554
|
/** Aborts the in-flight vendor/transport fetch when the user stops a stream. */
|
|
5841
5555
|
_streamController = null;
|
|
5556
|
+
/** Aborts an in-flight `compact()` summarisation — its own slot, see `compact`. */
|
|
5557
|
+
_compactController = null;
|
|
5842
5558
|
options;
|
|
5843
5559
|
/** Config read by this client — an instance config, or the global default. */
|
|
5844
5560
|
_config;
|
|
@@ -5964,6 +5680,7 @@ class AparteClient {
|
|
|
5964
5680
|
abort() {
|
|
5965
5681
|
this._isAborted = true;
|
|
5966
5682
|
this._streamController?.abort();
|
|
5683
|
+
this._compactController?.abort();
|
|
5967
5684
|
for (const controller of this._activeToolControllers) {
|
|
5968
5685
|
controller.abort();
|
|
5969
5686
|
}
|
|
@@ -6071,6 +5788,7 @@ class AparteClient {
|
|
|
6071
5788
|
}
|
|
6072
5789
|
if (this._isAborted) {
|
|
6073
5790
|
dispatchLifecycleEvent(targetElement, "aparte-message-aborted", { messageId });
|
|
5791
|
+
this._updateMessage(targetElement, messageId, { status: "completed" });
|
|
6074
5792
|
return;
|
|
6075
5793
|
}
|
|
6076
5794
|
dispatchLifecycleEvent(targetElement, "aparte-message-start", { messageId, role: "assistant" });
|
|
@@ -6150,7 +5868,7 @@ class AparteClient {
|
|
|
6150
5868
|
stream: false
|
|
6151
5869
|
};
|
|
6152
5870
|
const compactController = new AbortController();
|
|
6153
|
-
this.
|
|
5871
|
+
this._compactController = compactController;
|
|
6154
5872
|
const response = await this._config.getTransport().chat(
|
|
6155
5873
|
provider,
|
|
6156
5874
|
summarizeRequest,
|
|
@@ -6181,7 +5899,11 @@ class AparteClient {
|
|
|
6181
5899
|
target.appendMessage?.({
|
|
6182
5900
|
id: uuid(),
|
|
6183
5901
|
role: "assistant",
|
|
6184
|
-
|
|
5902
|
+
// Through the locale, like every string the user reads — this was a
|
|
5903
|
+
// hardcoded English title (with an emoji) in an otherwise localised
|
|
5904
|
+
// transcript. The engine compactor's `summaryLabel` is a separate,
|
|
5905
|
+
// per-call knob; this is the UI's own title.
|
|
5906
|
+
content: `**${this._config.t("compactionSummaryTitle")}**
|
|
6185
5907
|
|
|
6186
5908
|
${summary}`,
|
|
6187
5909
|
timestamp: Date.now(),
|
|
@@ -6635,67 +6357,6 @@ ${content}
|
|
|
6635
6357
|
);
|
|
6636
6358
|
return parts.filter((p) => p !== null);
|
|
6637
6359
|
}
|
|
6638
|
-
/**
|
|
6639
|
-
* Turn-1 forced tool call. When `toolChoice = { name, input }`
|
|
6640
|
-
* (orchestrator-driven), execute the handler directly instead of consulting
|
|
6641
|
-
* the LLM, render the tool segment, inject the result as `tool_result`, and
|
|
6642
|
-
* strip `toolChoice` for the follow-up turn. Returns the (possibly-updated)
|
|
6643
|
-
* request and whether the loop should skip to the next turn (handler missing
|
|
6644
|
-
* or aborted). Extracted from `_streamLoop`. `messages` is mutated in place.
|
|
6645
|
-
*/
|
|
6646
|
-
async _maybeRunSyntheticTool(baseRequest, turns, messages, targetElement) {
|
|
6647
|
-
const toolChoice = baseRequest.toolChoice;
|
|
6648
|
-
if (!(turns === 1 && toolChoice && typeof toolChoice === "object" && toolChoice.input !== void 0)) {
|
|
6649
|
-
return { baseRequest, skip: false };
|
|
6650
|
-
}
|
|
6651
|
-
const syntheticId = uuid();
|
|
6652
|
-
const syntheticCall = { id: syntheticId, name: toolChoice.name, input: toolChoice.input };
|
|
6653
|
-
const toolSeg = {
|
|
6654
|
-
id: `tool-${syntheticId}`,
|
|
6655
|
-
type: "tool_call",
|
|
6656
|
-
toolCall: syntheticCall,
|
|
6657
|
-
status: "pending"
|
|
6658
|
-
};
|
|
6659
|
-
const toolRenderer = this._config.getToolRenderer(toolChoice.name);
|
|
6660
|
-
if (toolRenderer) {
|
|
6661
|
-
const html = toolRenderer.render(toolSeg);
|
|
6662
|
-
if (html) targetElement.addSegment?.(toolSeg);
|
|
6663
|
-
} else {
|
|
6664
|
-
targetElement.addSegment?.(toolSeg);
|
|
6665
|
-
}
|
|
6666
|
-
const handler = this._config.getToolHandler(toolChoice.name);
|
|
6667
|
-
if (!handler) {
|
|
6668
|
-
console.warn(`[AparteClient] No handler for synthetic tool "${toolChoice.name}"`);
|
|
6669
|
-
targetElement.updateSegment?.(toolSeg.id, { status: "aborted" });
|
|
6670
|
-
return { baseRequest, skip: true };
|
|
6671
|
-
}
|
|
6672
|
-
const controller = new AbortController();
|
|
6673
|
-
this._activeToolControllers.add(controller);
|
|
6674
|
-
const { raced, done: clearToolTimeout } = withToolTimeout(
|
|
6675
|
-
(sig) => handler(syntheticCall, sig, {
|
|
6676
|
-
target: targetElement,
|
|
6677
|
-
config: this._config
|
|
6678
|
-
}),
|
|
6679
|
-
controller,
|
|
6680
|
-
this.options.toolTimeoutMs ?? DEFAULT_TOOL_HANDLER_TIMEOUT_MS
|
|
6681
|
-
);
|
|
6682
|
-
try {
|
|
6683
|
-
const result = await raced;
|
|
6684
|
-
targetElement.updateSegment?.(toolSeg.id, { status: "resolved", result: result.content });
|
|
6685
|
-
messages.push({ role: "tool_call", content: "", toolCalls: [syntheticCall] });
|
|
6686
|
-
messages.push({ role: "tool_result", content: result.content, toolCallId: syntheticId });
|
|
6687
|
-
return { baseRequest: { ...baseRequest, toolChoice: "none", tools: void 0 }, skip: false };
|
|
6688
|
-
} catch (err) {
|
|
6689
|
-
if (err?.name === "AbortError") {
|
|
6690
|
-
targetElement.updateSegment?.(toolSeg.id, { status: "aborted" });
|
|
6691
|
-
return { baseRequest, skip: true };
|
|
6692
|
-
}
|
|
6693
|
-
throw err;
|
|
6694
|
-
} finally {
|
|
6695
|
-
clearToolTimeout();
|
|
6696
|
-
this._activeToolControllers.delete(controller);
|
|
6697
|
-
}
|
|
6698
|
-
}
|
|
6699
6360
|
/**
|
|
6700
6361
|
* What the loop does after one tool call. THREE outcomes, because a boolean
|
|
6701
6362
|
* conflated two questions that a refusal answers differently.
|
|
@@ -6708,525 +6369,41 @@ ${content}
|
|
|
6708
6369
|
* - `'halt'` — the run is over: an abort, a turn limit, a missing handler.
|
|
6709
6370
|
*/
|
|
6710
6371
|
/**
|
|
6711
|
-
*
|
|
6712
|
-
*
|
|
6713
|
-
* approval gate, and running the registered handler (timeout / abort).
|
|
6714
|
-
* Mutates the shared `messages` / `toolCallsThisTurn` history in place and
|
|
6715
|
-
* returns what the loop should do next.
|
|
6716
|
-
*/
|
|
6717
|
-
async _handleToolUseEvent(event, ctx) {
|
|
6718
|
-
const { targetElement, messageId, messages, toolCallsThisTurn, precedingText, artifactProgress, turns, globalMaxTurns } = ctx;
|
|
6719
|
-
let flow = "continue";
|
|
6720
|
-
toolCallsThisTurn.push({ id: event.id, name: event.name, input: event.input });
|
|
6721
|
-
if (event.name === "create_artifact") {
|
|
6722
|
-
const input = event.input;
|
|
6723
|
-
const mimeType = input.mimeType ?? "text/plain";
|
|
6724
|
-
const kind = deriveArtifactKind(mimeType, "text");
|
|
6725
|
-
const artifactSeg = {
|
|
6726
|
-
id: `artifact-${event.id}`,
|
|
6727
|
-
type: "artifact",
|
|
6728
|
-
mimeType,
|
|
6729
|
-
artifactType: kind,
|
|
6730
|
-
title: input.title ?? kind,
|
|
6731
|
-
content: input.content ?? ""
|
|
6732
|
-
};
|
|
6733
|
-
targetElement.addSegment?.(artifactSeg);
|
|
6734
|
-
dispatchArtifactLifecycle(targetElement, messageId, artifactSeg, artifactProgress, true);
|
|
6735
|
-
messages.push({
|
|
6736
|
-
role: "tool_call",
|
|
6737
|
-
content: "",
|
|
6738
|
-
toolCalls: [{ id: event.id, name: event.name, input: event.input }]
|
|
6739
|
-
});
|
|
6740
|
-
messages.push({
|
|
6741
|
-
role: "tool_result",
|
|
6742
|
-
content: "Artifact created successfully.",
|
|
6743
|
-
toolCallId: event.id
|
|
6744
|
-
});
|
|
6745
|
-
return { flow: "continue" };
|
|
6746
|
-
}
|
|
6747
|
-
const toolSeg = {
|
|
6748
|
-
id: `tool-${event.id}`,
|
|
6749
|
-
type: "tool_call",
|
|
6750
|
-
toolCall: { id: event.id, name: event.name, input: event.input },
|
|
6751
|
-
status: "pending"
|
|
6752
|
-
};
|
|
6753
|
-
const toolRenderer = this._config.getToolRenderer(event.name);
|
|
6754
|
-
if (toolRenderer) {
|
|
6755
|
-
injectToolRendererStyles(event.name, toolRenderer);
|
|
6756
|
-
const html = toolRenderer.render(toolSeg);
|
|
6757
|
-
if (html) {
|
|
6758
|
-
targetElement.addSegment?.(toolSeg);
|
|
6759
|
-
}
|
|
6760
|
-
} else {
|
|
6761
|
-
targetElement.addSegment?.(toolSeg);
|
|
6762
|
-
}
|
|
6763
|
-
const toolDef = this._config.getTools().find((t) => t.name === event.name);
|
|
6764
|
-
const effectiveMaxTurns = toolDef?.maxTurns ?? globalMaxTurns;
|
|
6765
|
-
if (turns >= effectiveMaxTurns) {
|
|
6766
|
-
console.warn(`[AparteClient] Tool "${event.name}" maxTurns (${effectiveMaxTurns}) reached.`);
|
|
6767
|
-
targetElement.updateSegment?.(toolSeg.id, { status: "aborted" });
|
|
6768
|
-
return { flow: "halt" };
|
|
6769
|
-
}
|
|
6770
|
-
const handler = this._config.getToolHandler(event.name);
|
|
6771
|
-
if (handler) {
|
|
6772
|
-
let effectiveInput = event.input;
|
|
6773
|
-
if (toolDef?.needsApproval) {
|
|
6774
|
-
const approvalController = new AbortController();
|
|
6775
|
-
this._activeToolControllers.add(approvalController);
|
|
6776
|
-
targetElement.updateSegment?.(toolSeg.id, { status: "awaiting-approval" });
|
|
6777
|
-
dispatchLifecycleEvent(targetElement, "aparte-tool-approval-request", {
|
|
6778
|
-
toolCallId: event.id,
|
|
6779
|
-
toolName: event.name,
|
|
6780
|
-
input: event.input
|
|
6781
|
-
});
|
|
6782
|
-
let decision;
|
|
6783
|
-
const resolveApproval = this.options.approvalResolver ?? ((call, sig) => this._askForApproval(call, sig, targetElement));
|
|
6784
|
-
try {
|
|
6785
|
-
decision = await resolveApproval(event, approvalController.signal);
|
|
6786
|
-
} catch (err) {
|
|
6787
|
-
if (err?.name === "AbortError") {
|
|
6788
|
-
targetElement.updateSegment?.(toolSeg.id, { status: "aborted" });
|
|
6789
|
-
return { flow: "halt" };
|
|
6790
|
-
}
|
|
6791
|
-
throw err;
|
|
6792
|
-
} finally {
|
|
6793
|
-
this._activeToolControllers.delete(approvalController);
|
|
6794
|
-
}
|
|
6795
|
-
if (approvalController.signal.aborted) {
|
|
6796
|
-
targetElement.updateSegment?.(toolSeg.id, { status: "aborted" });
|
|
6797
|
-
return { flow: "halt" };
|
|
6798
|
-
}
|
|
6799
|
-
if (!decision.approved) {
|
|
6800
|
-
const rejection = decision.instruction ? `The user rejected this tool call and said: ${decision.instruction}` : "Tool execution was rejected by the user.";
|
|
6801
|
-
targetElement.updateSegment?.(toolSeg.id, { status: "rejected", result: rejection });
|
|
6802
|
-
const existingToolCallMsg = messages.find(
|
|
6803
|
-
(m) => m.role === "tool_call" && m.toolCalls?.some((tc) => tc.id === event.id)
|
|
6804
|
-
);
|
|
6805
|
-
if (!existingToolCallMsg) {
|
|
6806
|
-
messages.push({
|
|
6807
|
-
role: "tool_call",
|
|
6808
|
-
content: "",
|
|
6809
|
-
toolCalls: toolCallsThisTurn,
|
|
6810
|
-
precedingText: precedingText.trim() || void 0
|
|
6811
|
-
});
|
|
6812
|
-
}
|
|
6813
|
-
messages.push({ role: "tool_result", content: rejection, toolCallId: event.id });
|
|
6814
|
-
return { flow: "respond" };
|
|
6815
|
-
}
|
|
6816
|
-
if (decision.payload && typeof decision.payload === "object" && !Array.isArray(decision.payload)) {
|
|
6817
|
-
effectiveInput = { ...event.input, ...decision.payload };
|
|
6818
|
-
}
|
|
6819
|
-
targetElement.updateSegment?.(toolSeg.id, { status: "pending" });
|
|
6820
|
-
}
|
|
6821
|
-
const controller = new AbortController();
|
|
6822
|
-
this._activeToolControllers.add(controller);
|
|
6823
|
-
const { raced, done: clearToolTimeout } = withToolTimeout(
|
|
6824
|
-
(sig) => handler(
|
|
6825
|
-
{ id: event.id, name: event.name, input: effectiveInput },
|
|
6826
|
-
sig,
|
|
6827
|
-
{ target: targetElement, config: this._config }
|
|
6828
|
-
),
|
|
6829
|
-
controller,
|
|
6830
|
-
this.options.toolTimeoutMs ?? DEFAULT_TOOL_HANDLER_TIMEOUT_MS
|
|
6831
|
-
);
|
|
6832
|
-
try {
|
|
6833
|
-
const result = await raced;
|
|
6834
|
-
targetElement.updateSegment?.(toolSeg.id, { status: "resolved", result: result.content });
|
|
6835
|
-
const existingToolCallMsg = messages.find(
|
|
6836
|
-
(m) => m.role === "tool_call" && m.toolCalls?.some((tc) => tc.id === event.id)
|
|
6837
|
-
);
|
|
6838
|
-
if (!existingToolCallMsg) {
|
|
6839
|
-
messages.push({
|
|
6840
|
-
role: "tool_call",
|
|
6841
|
-
content: "",
|
|
6842
|
-
toolCalls: toolCallsThisTurn,
|
|
6843
|
-
precedingText: precedingText.trim() || void 0
|
|
6844
|
-
});
|
|
6845
|
-
}
|
|
6846
|
-
messages.push({
|
|
6847
|
-
role: "tool_result",
|
|
6848
|
-
content: result.content,
|
|
6849
|
-
toolCallId: event.id
|
|
6850
|
-
});
|
|
6851
|
-
} catch (err) {
|
|
6852
|
-
if (err?.name === "AbortError") {
|
|
6853
|
-
targetElement.updateSegment?.(toolSeg.id, { status: "aborted" });
|
|
6854
|
-
flow = "halt";
|
|
6855
|
-
} else {
|
|
6856
|
-
throw err;
|
|
6857
|
-
}
|
|
6858
|
-
} finally {
|
|
6859
|
-
clearToolTimeout();
|
|
6860
|
-
this._activeToolControllers.delete(controller);
|
|
6861
|
-
}
|
|
6862
|
-
} else {
|
|
6863
|
-
console.warn(`[AparteClient] No handler registered for tool "${event.name}"`);
|
|
6864
|
-
targetElement.updateSegment?.(toolSeg.id, { status: "aborted" });
|
|
6865
|
-
flow = "halt";
|
|
6866
|
-
}
|
|
6867
|
-
return { flow };
|
|
6868
|
-
}
|
|
6869
|
-
/**
|
|
6870
|
-
* Stream loop: runs one provider.chat() call and repeats if a tool was called.
|
|
6871
|
-
* Maintains a running messages array to inject tool_call / tool_result turns.
|
|
6372
|
+
* The turn's agent loop — the engine's `runStreamAgent` rendered through the core
|
|
6373
|
+
* adapter, or the runner a host injected — after the fetch-level abort bookkeeping.
|
|
6872
6374
|
*/
|
|
6873
6375
|
async _streamLoop(targetElement, messageId, provider, baseRequest, authConfig) {
|
|
6874
6376
|
this._streamController?.abort();
|
|
6875
6377
|
const streamController = new AbortController();
|
|
6876
6378
|
this._streamController = streamController;
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
}
|
|
6880
|
-
const messages = [...baseRequest.messages];
|
|
6881
|
-
let continueLoop = true;
|
|
6882
|
-
let turns = 0;
|
|
6883
|
-
const globalMaxTurns = this.options.maxTurns ?? 10;
|
|
6884
|
-
let lastUsage;
|
|
6885
|
-
const pipeline = baseRequest._meta?.pipeline;
|
|
6886
|
-
let pipelineIndex = 0;
|
|
6887
|
-
this._updateMessage(targetElement, messageId, { status: "streaming" });
|
|
6888
|
-
for (const seg of baseRequest._meta?.prefixSegments ?? []) {
|
|
6889
|
-
targetElement.addSegment?.(seg);
|
|
6890
|
-
}
|
|
6891
|
-
while (continueLoop) {
|
|
6892
|
-
let endTurnEarly = false;
|
|
6893
|
-
if (this._isAborted) {
|
|
6894
|
-
dispatchLifecycleEvent(targetElement, "aparte-message-aborted", { messageId });
|
|
6895
|
-
break;
|
|
6896
|
-
}
|
|
6897
|
-
turns++;
|
|
6898
|
-
if (turns > globalMaxTurns) {
|
|
6899
|
-
console.warn(`[AparteClient] maxTurns (${globalMaxTurns}) exceeded — stopping loop.`);
|
|
6900
|
-
targetElement.addSegment?.({
|
|
6901
|
-
id: `max-turns-${uuid()}`,
|
|
6902
|
-
type: "error",
|
|
6903
|
-
content: `Stopped after ${globalMaxTurns} tool calls to prevent an infinite loop.`,
|
|
6904
|
-
details: "MAX_TURNS_EXCEEDED"
|
|
6905
|
-
});
|
|
6906
|
-
break;
|
|
6907
|
-
}
|
|
6908
|
-
const synthetic = await this._maybeRunSyntheticTool(baseRequest, turns, messages, targetElement);
|
|
6909
|
-
baseRequest = synthetic.baseRequest;
|
|
6910
|
-
if (synthetic.skip) {
|
|
6911
|
-
continueLoop = false;
|
|
6912
|
-
continue;
|
|
6913
|
-
}
|
|
6914
|
-
let phaseMessages = messages;
|
|
6915
|
-
let phaseMeta = baseRequest._meta;
|
|
6916
|
-
if (pipeline && pipelineIndex < pipeline.length) {
|
|
6917
|
-
const phase = pipeline[pipelineIndex];
|
|
6918
|
-
phaseMessages = [{ role: "system", content: phase.system }, ...messages];
|
|
6919
|
-
if (phase.mode === "artifact") {
|
|
6920
|
-
phaseMeta = { ...phaseMeta, artifactRaw: { mimeType: phase.mimeType, kind: phase.kind } };
|
|
6921
|
-
} else {
|
|
6922
|
-
const { artifactRaw: _dropped, pipeline: _p, ...restMeta } = phaseMeta ?? {};
|
|
6923
|
-
phaseMeta = restMeta;
|
|
6924
|
-
}
|
|
6925
|
-
}
|
|
6926
|
-
const request = { ...baseRequest, messages: phaseMessages, _meta: phaseMeta };
|
|
6927
|
-
const response = await this._config.getTransport().chat(provider, request, authConfig, { providerId: provider.id, signal: streamController.signal });
|
|
6928
|
-
if (typeof response === "string") {
|
|
6929
|
-
const wholeParser = new AparteStreamParser();
|
|
6930
|
-
const parsed = [
|
|
6931
|
-
...wholeParser.parse(response).segments,
|
|
6932
|
-
...wholeParser.finalize()
|
|
6933
|
-
];
|
|
6934
|
-
const stringHint = baseRequest._meta?.artifactHint;
|
|
6935
|
-
if (stringHint) {
|
|
6936
|
-
const codeIdx = parsed.findIndex((s) => s.type === "code");
|
|
6937
|
-
if (codeIdx !== -1) {
|
|
6938
|
-
const codeSeg = parsed[codeIdx];
|
|
6939
|
-
parsed[codeIdx] = {
|
|
6940
|
-
id: codeSeg.id,
|
|
6941
|
-
type: "artifact",
|
|
6942
|
-
mimeType: stringHint.mimeType,
|
|
6943
|
-
artifactType: stringHint.kind,
|
|
6944
|
-
title: codeSeg.filename ?? stringHint.kind,
|
|
6945
|
-
content: codeSeg.content
|
|
6946
|
-
};
|
|
6947
|
-
}
|
|
6948
|
-
}
|
|
6949
|
-
if (parsed.length > 0) {
|
|
6950
|
-
for (const segment of parsed) targetElement.addSegment?.(segment);
|
|
6951
|
-
this._updateMessage(targetElement, messageId, { status: "completed" });
|
|
6952
|
-
} else {
|
|
6953
|
-
this._updateMessage(targetElement, messageId, { content: response, status: "completed" });
|
|
6954
|
-
}
|
|
6955
|
-
return void 0;
|
|
6956
|
-
}
|
|
6957
|
-
const reader = response.getReader();
|
|
6958
|
-
const textParser = new AparteStreamParser();
|
|
6959
|
-
const streamingSegmentIds = /* @__PURE__ */ new Set();
|
|
6960
|
-
const artifactProgress = /* @__PURE__ */ new Map();
|
|
6961
|
-
let thinkingSegmentId = null;
|
|
6962
|
-
let thinkingContent = "";
|
|
6963
|
-
let thinkingCollapsed = false;
|
|
6964
|
-
const artifactHint = baseRequest._meta?.artifactHint;
|
|
6965
|
-
let artifactPromoted = false;
|
|
6966
|
-
const artifactRawHint = request._meta?.artifactRaw;
|
|
6967
|
-
let rawSegId = null;
|
|
6968
|
-
let rawContent = "";
|
|
6969
|
-
if (artifactRawHint) {
|
|
6970
|
-
rawSegId = `artifact-raw-${uuid()}`;
|
|
6971
|
-
const rawSeg = {
|
|
6972
|
-
id: rawSegId,
|
|
6973
|
-
type: "artifact",
|
|
6974
|
-
mimeType: artifactRawHint.mimeType,
|
|
6975
|
-
artifactType: artifactRawHint.kind,
|
|
6976
|
-
title: artifactRawHint.kind,
|
|
6977
|
-
content: ""
|
|
6978
|
-
};
|
|
6979
|
-
targetElement.addSegment?.(rawSeg);
|
|
6980
|
-
streamingSegmentIds.add(rawSegId);
|
|
6981
|
-
dispatchArtifactLifecycle(targetElement, messageId, rawSeg, artifactProgress, false);
|
|
6982
|
-
}
|
|
6983
|
-
const artifactXmlHint = baseRequest._meta?.artifactXml;
|
|
6984
|
-
const xmlCtx = {
|
|
6985
|
-
state: "normal",
|
|
6986
|
-
scanBuf: "",
|
|
6987
|
-
closeBuf: "",
|
|
6988
|
-
segId: null,
|
|
6989
|
-
content: "",
|
|
6990
|
-
mime: "",
|
|
6991
|
-
kind: "",
|
|
6992
|
-
title: ""
|
|
6993
|
-
};
|
|
6994
|
-
let precedingText = "";
|
|
6995
|
-
const toolCallsThisTurn = [];
|
|
6996
|
-
const bailOnAbort = () => {
|
|
6997
|
-
if (!this._isAborted) return false;
|
|
6998
|
-
try {
|
|
6999
|
-
void reader.cancel();
|
|
7000
|
-
} catch {
|
|
7001
|
-
}
|
|
7002
|
-
dispatchLifecycleEvent(targetElement, "aparte-message-aborted", { messageId });
|
|
7003
|
-
continueLoop = false;
|
|
7004
|
-
return true;
|
|
7005
|
-
};
|
|
7006
|
-
let streamDrained = false;
|
|
7007
|
-
try {
|
|
7008
|
-
while (true) {
|
|
7009
|
-
if (bailOnAbort()) break;
|
|
7010
|
-
const { done, value: event } = await reader.read();
|
|
7011
|
-
if (bailOnAbort()) break;
|
|
7012
|
-
if (done) {
|
|
7013
|
-
streamDrained = true;
|
|
7014
|
-
break;
|
|
7015
|
-
}
|
|
7016
|
-
switch (event.type) {
|
|
7017
|
-
case "thinking": {
|
|
7018
|
-
thinkingContent += event.delta;
|
|
7019
|
-
if (!thinkingSegmentId) {
|
|
7020
|
-
const seg = {
|
|
7021
|
-
id: `think-${uuid()}`,
|
|
7022
|
-
type: "thinking",
|
|
7023
|
-
content: thinkingContent,
|
|
7024
|
-
collapsed: true,
|
|
7025
|
-
label: "Thinking"
|
|
7026
|
-
};
|
|
7027
|
-
thinkingSegmentId = seg.id;
|
|
7028
|
-
streamingSegmentIds.add(seg.id);
|
|
7029
|
-
targetElement.addSegment?.(seg);
|
|
7030
|
-
} else {
|
|
7031
|
-
targetElement.updateSegment?.(thinkingSegmentId, { content: thinkingContent });
|
|
7032
|
-
}
|
|
7033
|
-
break;
|
|
7034
|
-
}
|
|
7035
|
-
case "text": {
|
|
7036
|
-
if (thinkingSegmentId && !thinkingCollapsed) {
|
|
7037
|
-
targetElement.updateSegment?.(thinkingSegmentId, { collapsed: true, isStreaming: false });
|
|
7038
|
-
thinkingCollapsed = true;
|
|
7039
|
-
}
|
|
7040
|
-
precedingText += event.delta;
|
|
7041
|
-
if (artifactRawHint && rawSegId) {
|
|
7042
|
-
rawContent += event.delta;
|
|
7043
|
-
targetElement.updateSegment?.(rawSegId, { content: rawContent });
|
|
7044
|
-
dispatchArtifactLifecycle(targetElement, messageId, {
|
|
7045
|
-
id: rawSegId,
|
|
7046
|
-
type: "artifact",
|
|
7047
|
-
mimeType: artifactRawHint.mimeType,
|
|
7048
|
-
artifactType: artifactRawHint.kind,
|
|
7049
|
-
title: artifactRawHint.kind,
|
|
7050
|
-
content: rawContent
|
|
7051
|
-
}, artifactProgress, false);
|
|
7052
|
-
break;
|
|
7053
|
-
}
|
|
7054
|
-
if (artifactXmlHint) {
|
|
7055
|
-
feedXmlArtifactDelta(event.delta, xmlCtx, {
|
|
7056
|
-
targetElement,
|
|
7057
|
-
messageId,
|
|
7058
|
-
textParser,
|
|
7059
|
-
streamingSegmentIds,
|
|
7060
|
-
artifactProgress,
|
|
7061
|
-
artifactXmlHint
|
|
7062
|
-
});
|
|
7063
|
-
break;
|
|
7064
|
-
}
|
|
7065
|
-
const result = textParser.parse(event.delta);
|
|
7066
|
-
for (let segment of result.segments) {
|
|
7067
|
-
if (artifactHint && !artifactPromoted && segment.type === "code") {
|
|
7068
|
-
const codeSeg = segment;
|
|
7069
|
-
const promoted = {
|
|
7070
|
-
id: codeSeg.id,
|
|
7071
|
-
type: "artifact",
|
|
7072
|
-
mimeType: artifactHint.mimeType,
|
|
7073
|
-
artifactType: artifactHint.kind,
|
|
7074
|
-
title: codeSeg.filename ?? artifactHint.kind,
|
|
7075
|
-
content: codeSeg.content
|
|
7076
|
-
};
|
|
7077
|
-
segment = promoted;
|
|
7078
|
-
artifactPromoted = true;
|
|
7079
|
-
}
|
|
7080
|
-
if (!streamingSegmentIds.has(segment.id)) {
|
|
7081
|
-
targetElement.addSegment?.(segment);
|
|
7082
|
-
streamingSegmentIds.add(segment.id);
|
|
7083
|
-
} else if ("content" in segment) {
|
|
7084
|
-
targetElement.updateSegment?.(segment.id, segmentContentUpdate(segment));
|
|
7085
|
-
}
|
|
7086
|
-
if (segment.type === "artifact") {
|
|
7087
|
-
dispatchArtifactLifecycle(targetElement, messageId, segment, artifactProgress, true);
|
|
7088
|
-
}
|
|
7089
|
-
}
|
|
7090
|
-
const active = textParser.getState().activeSegment;
|
|
7091
|
-
if (active) {
|
|
7092
|
-
if (!streamingSegmentIds.has(active.id)) {
|
|
7093
|
-
targetElement.addSegment?.(active);
|
|
7094
|
-
streamingSegmentIds.add(active.id);
|
|
7095
|
-
if (active.type === "artifact") {
|
|
7096
|
-
dispatchArtifactLifecycle(targetElement, messageId, active, artifactProgress, false);
|
|
7097
|
-
}
|
|
7098
|
-
} else {
|
|
7099
|
-
targetElement.updateSegment?.(active.id, segmentContentUpdate(active));
|
|
7100
|
-
if (active.type === "artifact") {
|
|
7101
|
-
dispatchArtifactLifecycle(targetElement, messageId, active, artifactProgress, false);
|
|
7102
|
-
}
|
|
7103
|
-
}
|
|
7104
|
-
} else if (result.segments.length === 0) {
|
|
7105
|
-
}
|
|
7106
|
-
break;
|
|
7107
|
-
}
|
|
7108
|
-
case "tool_use": {
|
|
7109
|
-
const toolResult = await this._handleToolUseEvent(
|
|
7110
|
-
{ id: event.id, name: event.name, input: event.input },
|
|
7111
|
-
{ targetElement, messageId, messages, toolCallsThisTurn, precedingText, artifactProgress, turns, globalMaxTurns }
|
|
7112
|
-
);
|
|
7113
|
-
if (toolResult.flow !== "continue") endTurnEarly = true;
|
|
7114
|
-
if (toolResult.flow === "halt") continueLoop = false;
|
|
7115
|
-
break;
|
|
7116
|
-
}
|
|
7117
|
-
case "error":
|
|
7118
|
-
throw new Error(event.message);
|
|
7119
|
-
case "done":
|
|
7120
|
-
if (event.usage) lastUsage = event.usage;
|
|
7121
|
-
break;
|
|
7122
|
-
default: {
|
|
7123
|
-
const exhaustive = event;
|
|
7124
|
-
void exhaustive;
|
|
7125
|
-
this._warnUnknownStreamEvent(event);
|
|
7126
|
-
break;
|
|
7127
|
-
}
|
|
7128
|
-
}
|
|
7129
|
-
if (endTurnEarly) break;
|
|
7130
|
-
}
|
|
7131
|
-
finalizeXmlArtifact(xmlCtx, { targetElement, messageId, artifactProgress, artifactXmlHint, textParser, streamingSegmentIds });
|
|
7132
|
-
const finals = textParser.finalize();
|
|
7133
|
-
if (artifactRawHint && rawSegId) {
|
|
7134
|
-
const lineCount = rawContent.split("\n").length;
|
|
7135
|
-
const isInline = lineCount < 15;
|
|
7136
|
-
targetElement.updateSegment?.(rawSegId, { content: rawContent, inline: isInline });
|
|
7137
|
-
dispatchArtifactLifecycle(targetElement, messageId, {
|
|
7138
|
-
id: rawSegId,
|
|
7139
|
-
type: "artifact",
|
|
7140
|
-
mimeType: artifactRawHint.mimeType,
|
|
7141
|
-
artifactType: artifactRawHint.kind,
|
|
7142
|
-
title: artifactRawHint.kind,
|
|
7143
|
-
content: rawContent,
|
|
7144
|
-
inline: isInline
|
|
7145
|
-
}, artifactProgress, true);
|
|
7146
|
-
}
|
|
7147
|
-
if (artifactHint && !artifactPromoted) {
|
|
7148
|
-
const codeIdx = finals.findIndex((s) => s.type === "code");
|
|
7149
|
-
if (codeIdx !== -1) {
|
|
7150
|
-
const codeSeg = finals[codeIdx];
|
|
7151
|
-
const promoted = {
|
|
7152
|
-
id: codeSeg.id,
|
|
7153
|
-
type: "artifact",
|
|
7154
|
-
mimeType: artifactHint.mimeType,
|
|
7155
|
-
artifactType: artifactHint.kind,
|
|
7156
|
-
title: codeSeg.filename ?? artifactHint.kind,
|
|
7157
|
-
content: codeSeg.content
|
|
7158
|
-
};
|
|
7159
|
-
finals[codeIdx] = promoted;
|
|
7160
|
-
artifactPromoted = true;
|
|
7161
|
-
if (streamingSegmentIds.has(promoted.id)) {
|
|
7162
|
-
targetElement.updateSegment?.(promoted.id, promoted);
|
|
7163
|
-
}
|
|
7164
|
-
}
|
|
7165
|
-
}
|
|
7166
|
-
for (const s of finals) {
|
|
7167
|
-
if (!streamingSegmentIds.has(s.id)) {
|
|
7168
|
-
targetElement.addSegment?.(s);
|
|
7169
|
-
} else if ("content" in s) {
|
|
7170
|
-
targetElement.updateSegment?.(s.id, segmentContentUpdate(s));
|
|
7171
|
-
}
|
|
7172
|
-
if (s.type === "artifact") {
|
|
7173
|
-
dispatchArtifactLifecycle(targetElement, messageId, s, artifactProgress, true);
|
|
7174
|
-
}
|
|
7175
|
-
}
|
|
7176
|
-
if (toolCallsThisTurn.length === 0) {
|
|
7177
|
-
if (pipeline && pipelineIndex < pipeline.length - 1) {
|
|
7178
|
-
if (precedingText.trim()) {
|
|
7179
|
-
messages.push({ role: "assistant", content: precedingText.trim() });
|
|
7180
|
-
}
|
|
7181
|
-
pipelineIndex++;
|
|
7182
|
-
const pwId = `pw-${uuid()}`;
|
|
7183
|
-
targetElement.addSegment?.({ id: pwId, type: "pipeline-waiting" });
|
|
7184
|
-
} else {
|
|
7185
|
-
continueLoop = false;
|
|
7186
|
-
}
|
|
7187
|
-
}
|
|
7188
|
-
} finally {
|
|
7189
|
-
if (!streamDrained) {
|
|
7190
|
-
try {
|
|
7191
|
-
await reader.cancel();
|
|
7192
|
-
} catch {
|
|
7193
|
-
}
|
|
7194
|
-
}
|
|
7195
|
-
reader.releaseLock();
|
|
7196
|
-
}
|
|
7197
|
-
}
|
|
7198
|
-
this._updateMessage(targetElement, messageId, { status: "completed" });
|
|
7199
|
-
if (lastUsage) {
|
|
7200
|
-
try {
|
|
7201
|
-
targetElement.setUsage?.(messageId, lastUsage);
|
|
7202
|
-
} catch {
|
|
7203
|
-
}
|
|
7204
|
-
}
|
|
7205
|
-
return lastUsage;
|
|
6379
|
+
const runner = this.options.streamRunner ?? runStreamAgent;
|
|
6380
|
+
return this._runViaStreamRunner(runner, targetElement, messageId, provider, baseRequest, authConfig, streamController);
|
|
7206
6381
|
}
|
|
7207
6382
|
/**
|
|
7208
|
-
*
|
|
7209
|
-
* rendering its DOM-free events through {@link createStreamAdapter}.
|
|
7210
|
-
*
|
|
7211
|
-
*
|
|
7212
|
-
*
|
|
7213
|
-
*
|
|
7214
|
-
*
|
|
6383
|
+
* Run the loop through a {@link AparteStreamRunner} — the engine's by default —
|
|
6384
|
+
* rendering its DOM-free events through {@link createStreamAdapter}. Builds the
|
|
6385
|
+
* runner's dependencies from this client's config / provider / transport. The
|
|
6386
|
+
* leading writes (status streaming, prefix segments) happen here; the runner's
|
|
6387
|
+
* `run-start` re-asserts `streaming` idempotently. The call sequence this
|
|
6388
|
+
* produces is pinned by the `stream-parity` suite's snapshots, recorded while
|
|
6389
|
+
* the inline loop it replaced still ran beside it.
|
|
7215
6390
|
*/
|
|
7216
6391
|
async _runViaStreamRunner(streamRunner, targetElement, messageId, provider, baseRequest, authConfig, streamController) {
|
|
7217
6392
|
const signal = streamController.signal;
|
|
7218
|
-
this._updateMessage(targetElement, messageId, { status: "streaming" });
|
|
7219
|
-
const prefixSegments = baseRequest._meta?.["prefixSegments"];
|
|
7220
|
-
if (prefixSegments?.length) {
|
|
7221
|
-
for (const seg of prefixSegments) targetElement.addSegment?.(seg);
|
|
7222
|
-
}
|
|
7223
6393
|
const artifactHint = baseRequest._meta?.artifactHint;
|
|
7224
|
-
const
|
|
6394
|
+
const adapter = createStreamAdapter({
|
|
7225
6395
|
target: targetElement,
|
|
7226
6396
|
config: this._config,
|
|
7227
6397
|
messageId,
|
|
7228
6398
|
artifactHint
|
|
7229
6399
|
});
|
|
6400
|
+
const prefixSegments = baseRequest._meta?.["prefixSegments"];
|
|
6401
|
+
const emitter = (event) => {
|
|
6402
|
+
adapter(event);
|
|
6403
|
+
if (event.type === "run-start" && prefixSegments?.length) {
|
|
6404
|
+
for (const seg of prefixSegments) targetElement.addSegment?.(seg);
|
|
6405
|
+
}
|
|
6406
|
+
};
|
|
7230
6407
|
const transportCall = async (request) => {
|
|
7231
6408
|
const response = await this._config.getTransport().chat(provider, request, authConfig, { providerId: provider.id, signal });
|
|
7232
6409
|
return typeof response === "string" ? response : readableToAsyncIterable(response, signal);
|
|
@@ -7257,7 +6434,7 @@ ${content}
|
|
|
7257
6434
|
// inline path, so setting it was silently ignored here. Exactly the
|
|
7258
6435
|
// asymmetry the option was added to remove.
|
|
7259
6436
|
toolTimeoutMs: this.options.toolTimeoutMs,
|
|
7260
|
-
//
|
|
6437
|
+
// The id conventions the loop always had: prefixed artifact ids, but a
|
|
7261
6438
|
// BARE uuid for the synthetic tool (the adapter renders `tool-<id>`).
|
|
7262
6439
|
idGen: (prefix) => prefix === "synthetic-tool" ? uuid() : `${prefix}-${uuid()}`
|
|
7263
6440
|
});
|
|
@@ -7298,16 +6475,6 @@ ${content}
|
|
|
7298
6475
|
}
|
|
7299
6476
|
dispatchLifecycleEvent(target, "aparte-message-error", { messageId, error });
|
|
7300
6477
|
}
|
|
7301
|
-
/** Warn once per client for an unrecognised stream event, then carry on. */
|
|
7302
|
-
_warnedUnknownEvents = /* @__PURE__ */ new Set();
|
|
7303
|
-
_warnUnknownStreamEvent(event) {
|
|
7304
|
-
const type = String(event?.type ?? "undefined");
|
|
7305
|
-
if (this._warnedUnknownEvents.has(type)) return;
|
|
7306
|
-
this._warnedUnknownEvents.add(type);
|
|
7307
|
-
console.warn(
|
|
7308
|
-
`[AparteClient] 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.`
|
|
7309
|
-
);
|
|
7310
|
-
}
|
|
7311
6478
|
}
|
|
7312
6479
|
function findHead(node) {
|
|
7313
6480
|
if (node.next) return findHead(node.next);
|
|
@@ -7479,6 +6646,12 @@ class AparteMessageRepository {
|
|
|
7479
6646
|
import(exported) {
|
|
7480
6647
|
this.clear();
|
|
7481
6648
|
for (const { message, parentId } of exported.messages) {
|
|
6649
|
+
if (parentId !== null && !this._nodes.has(parentId)) {
|
|
6650
|
+
console.warn(
|
|
6651
|
+
`[AparteMessageRepository] import: message "${message.id}" names a parent "${parentId}" the snapshot does not hold before it — skipped.`
|
|
6652
|
+
);
|
|
6653
|
+
continue;
|
|
6654
|
+
}
|
|
7482
6655
|
this.addOrUpdateMessage(parentId, message);
|
|
7483
6656
|
}
|
|
7484
6657
|
if (exported.headId) {
|
|
@@ -7488,15 +6661,25 @@ class AparteMessageRepository {
|
|
|
7488
6661
|
/**
|
|
7489
6662
|
* Export the full tree for serialisation or debugging.
|
|
7490
6663
|
* Compatible with the assistant-ui ExportedMessageRepository format.
|
|
6664
|
+
*
|
|
6665
|
+
* Walked from the root through each node's `children`, so a parent is always
|
|
6666
|
+
* written before its children. It used to iterate `_nodes` in insertion order and
|
|
6667
|
+
* call that topological — which holds until `_relink` re-parents an EXISTING node
|
|
6668
|
+
* under one inserted after it (`addOrUpdateMessage` with a known id and a newer
|
|
6669
|
+
* parent). The snapshot then listed the child first, and `import()` threw "parent
|
|
6670
|
+
* not found" on the very tree this method had just produced.
|
|
7491
6671
|
*/
|
|
7492
6672
|
export() {
|
|
7493
6673
|
const messages = [];
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
6674
|
+
const walk = (children, parentId) => {
|
|
6675
|
+
for (const id of children) {
|
|
6676
|
+
const node = this._nodes.get(id);
|
|
6677
|
+
if (!node) continue;
|
|
6678
|
+
messages.push({ message: node.current, parentId });
|
|
6679
|
+
walk(node.children, id);
|
|
6680
|
+
}
|
|
6681
|
+
};
|
|
6682
|
+
walk(this._root.children, null);
|
|
7500
6683
|
return { headId: this.headId, messages };
|
|
7501
6684
|
}
|
|
7502
6685
|
// ─── Private helpers ────────────────────────────────────────────────────
|
|
@@ -7602,75 +6785,73 @@ function isAwaitingReply(message) {
|
|
|
7602
6785
|
return message.status === void 0 && message.role === "assistant" && !message.content?.trim() && !message.segments?.length;
|
|
7603
6786
|
}
|
|
7604
6787
|
export {
|
|
7605
|
-
|
|
6788
|
+
runWithConfig as $,
|
|
7606
6789
|
APARTE_CONFIG_CHANGE as A,
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
6790
|
+
contextConfig as B,
|
|
6791
|
+
createAparteChatHandler as C,
|
|
6792
|
+
createStreamAdapter as D,
|
|
6793
|
+
cssEscape as E,
|
|
6794
|
+
declineDefaultRenderers as F,
|
|
6795
|
+
defaultSanitizer as G,
|
|
6796
|
+
detachConfig as H,
|
|
6797
|
+
escapeAttr as I,
|
|
6798
|
+
escapeHtml as J,
|
|
6799
|
+
filesToAttachments as K,
|
|
6800
|
+
getAllRenderers as L,
|
|
6801
|
+
getSegmentRenderer as M,
|
|
6802
|
+
installDefaultRenderersOnce as N,
|
|
6803
|
+
isAwaitingReply as O,
|
|
6804
|
+
isFormatAdapter as P,
|
|
6805
|
+
isSafeUrl as Q,
|
|
6806
|
+
isSegmentSettled as R,
|
|
6807
|
+
parseAparteEventStream as S,
|
|
6808
|
+
parseMarkdownToSegments as T,
|
|
6809
|
+
populateBubbleFromMessage as U,
|
|
6810
|
+
readableToAsyncIterable as V,
|
|
6811
|
+
registerDefaultRenderers as W,
|
|
6812
|
+
registerSegmentRenderer as X,
|
|
6813
|
+
requestUserInput as Y,
|
|
6814
|
+
resolveConfig as Z,
|
|
6815
|
+
revokeAttachmentUrls as _,
|
|
7633
6816
|
APARTE_CONVERSATION_SCHEMA_VERSION as a,
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
isTerminalStatus as ae,
|
|
7649
|
-
adoptMessageSegments as af,
|
|
6817
|
+
segmentDuration as a0,
|
|
6818
|
+
segmentTiming as a1,
|
|
6819
|
+
subscribeConfigChange as a2,
|
|
6820
|
+
unregisterSegmentRenderer as a3,
|
|
6821
|
+
uuid as a4,
|
|
6822
|
+
mergeSegmentUpdate as a5,
|
|
6823
|
+
writeStreamedMarkdown as a6,
|
|
6824
|
+
stampSegmentActivity as a7,
|
|
6825
|
+
stampSegmentOnInsert as a8,
|
|
6826
|
+
stampSegmentOnUpdate as a9,
|
|
6827
|
+
renumberSegments as aa,
|
|
6828
|
+
openSegmentIds as ab,
|
|
6829
|
+
isTerminalStatus as ac,
|
|
6830
|
+
adoptMessageSegments as ad,
|
|
7650
6831
|
APARTE_DEFAULT_BUBBLE_ACTIONS as b,
|
|
7651
6832
|
APARTE_DEFAULT_HOST_HANDLERS as c,
|
|
7652
6833
|
APARTE_DEFAULT_ICON_FALLBACKS as d,
|
|
7653
6834
|
APARTE_DEFAULT_LOCALE as e,
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
6835
|
+
APARTE_DEFAULT_UI_EVENTS as f,
|
|
6836
|
+
APARTE_HOST_ATTR as g,
|
|
6837
|
+
AparteBackendTransport as h,
|
|
6838
|
+
AparteChatHost as i,
|
|
6839
|
+
AparteClient as j,
|
|
6840
|
+
AparteConfig as k,
|
|
6841
|
+
AparteConversationController as l,
|
|
6842
|
+
AparteConversationManager as m,
|
|
6843
|
+
AparteDirectTransport as n,
|
|
6844
|
+
AparteElicitationAbortError as o,
|
|
6845
|
+
AparteError as p,
|
|
6846
|
+
AparteErrorCode as q,
|
|
6847
|
+
AparteMessageRepository as r,
|
|
6848
|
+
AparteStreamParser as s,
|
|
6849
|
+
aparteGlobalConfig as t,
|
|
6850
|
+
applyElementProps as u,
|
|
6851
|
+
attachConfig as v,
|
|
6852
|
+
buildApprovalPanel as w,
|
|
6853
|
+
buildElicitationPanel as x,
|
|
6854
|
+
collectRendererStyles as y,
|
|
6855
|
+
contentToText as z
|
|
7675
6856
|
};
|
|
7676
|
-
//# sourceMappingURL=is-awaiting-reply-
|
|
6857
|
+
//# sourceMappingURL=is-awaiting-reply-BZ_iJ3s3.js.map
|