@aparte/core 0.22.0 → 0.23.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/dist/client/aparte-client.d.ts +5 -22
- package/dist/client/aparte-client.d.ts.map +1 -1
- package/dist/client/stream-adapter.d.ts +16 -0
- package/dist/client/stream-adapter.d.ts.map +1 -1
- package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts +10 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
- package/dist/config/aparte-config.d.ts +66 -19
- package/dist/config/aparte-config.d.ts.map +1 -1
- package/dist/config/error-renderer.d.ts +3 -1
- package/dist/config/error-renderer.d.ts.map +1 -1
- package/dist/config/locale.d.ts +45 -4
- package/dist/config/locale.d.ts.map +1 -1
- package/dist/config/tool-table.d.ts +28 -0
- package/dist/config/tool-table.d.ts.map +1 -0
- package/dist/custom-elements.json +12019 -11500
- package/dist/{dialog-triggers-3Rk7PuZa.js → dialog-triggers-3G9TqG9g.js} +473 -165
- package/dist/dialog-triggers-3G9TqG9g.js.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -16
- package/dist/index.js.map +1 -1
- package/dist/index.node.d.ts +2 -1
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +4 -3
- package/dist/index.node.js.map +1 -1
- package/dist/renderers/copy-button.d.ts +12 -0
- package/dist/renderers/copy-button.d.ts.map +1 -0
- package/dist/renderers/segment-renderers.d.ts +6 -3
- package/dist/renderers/segment-renderers.d.ts.map +1 -1
- package/dist/renderers/segments/code.d.ts.map +1 -1
- package/dist/renderers/segments/error.d.ts.map +1 -1
- package/dist/renderers/segments/thinking.d.ts.map +1 -1
- package/dist/renderers/segments/tool-call.d.ts.map +1 -1
- package/dist/transport/agent-handler.d.ts.map +1 -1
- package/dist/types/errors.d.ts +2 -0
- package/dist/types/errors.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/segments.d.ts +42 -2
- package/dist/types/segments.d.ts.map +1 -1
- package/dist/types/tools.d.ts +38 -0
- package/dist/types/tools.d.ts.map +1 -1
- package/dist/utils/format-duration.d.ts +9 -0
- package/dist/utils/format-duration.d.ts.map +1 -0
- package/dist/utils/tool-line.d.ts +23 -0
- package/dist/utils/tool-line.d.ts.map +1 -0
- package/package.json +2 -2
- package/dist/dialog-triggers-3Rk7PuZa.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { unansweredTail, runStreamAgent } from "@aparte/engine";
|
|
1
|
+
import { toolRefusal as toolRefusal$1, unansweredTail, runStreamAgent } from "@aparte/engine";
|
|
2
2
|
import { A as APARTE_ICON_GLYPHS } from "./glyphs-C8z2oJUg.js";
|
|
3
3
|
function contentToText(content) {
|
|
4
4
|
if (typeof content === "string") return content;
|
|
@@ -18,6 +18,7 @@ var AparteErrorCode = /* @__PURE__ */ ((AparteErrorCode2) => {
|
|
|
18
18
|
AparteErrorCode2["PROVIDER_ERROR"] = "PROVIDER_ERROR";
|
|
19
19
|
AparteErrorCode2["PROVIDER_UNAVAILABLE"] = "PROVIDER_UNAVAILABLE";
|
|
20
20
|
AparteErrorCode2["PROVIDER_POLICY"] = "PROVIDER_POLICY";
|
|
21
|
+
AparteErrorCode2["MAX_TURNS_EXCEEDED"] = "MAX_TURNS_EXCEEDED";
|
|
21
22
|
AparteErrorCode2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
|
|
22
23
|
return AparteErrorCode2;
|
|
23
24
|
})(AparteErrorCode || {});
|
|
@@ -98,7 +99,7 @@ const APARTE_DEFAULT_LOCALE = {
|
|
|
98
99
|
elicitationNo: "No",
|
|
99
100
|
elicitationAnswerLabel: "Your answer",
|
|
100
101
|
approvalInstructionPlaceholder: "Or tell it what to do instead…",
|
|
101
|
-
approvalAsk: "
|
|
102
|
+
approvalAsk: "Allow {tool}?",
|
|
102
103
|
approvalWaiting: "Waiting",
|
|
103
104
|
scrollToBottom: "Scroll to bottom",
|
|
104
105
|
suggestionsLabel: "Suggested prompts",
|
|
@@ -111,6 +112,18 @@ const APARTE_DEFAULT_LOCALE = {
|
|
|
111
112
|
toolFailed: "Failed",
|
|
112
113
|
toolInput: "Input",
|
|
113
114
|
toolOutput: "Output",
|
|
115
|
+
toolRejectedSaid: "You said: “{instruction}”",
|
|
116
|
+
toolRefusedByPolicy: "Refused by policy",
|
|
117
|
+
toolNotRunAfterRefusal: "Not run: an earlier call was refused",
|
|
118
|
+
toolTimedOut: "Timed out",
|
|
119
|
+
toolUnavailable: "Not available here",
|
|
120
|
+
toolNoApprover: "No one could approve it",
|
|
121
|
+
toolTurnLimit: "Turn limit reached",
|
|
122
|
+
maxTurnsReached: "Stopped after {n} turns to avoid an endless loop.",
|
|
123
|
+
questionDeclined: "You declined to answer.",
|
|
124
|
+
artifactNotCreated: "Not created",
|
|
125
|
+
thoughtFor: "Thought for {duration}",
|
|
126
|
+
thought: "Thought",
|
|
114
127
|
approvalOptionsLabel: "Your decision",
|
|
115
128
|
approvalArgsLabel: "Arguments",
|
|
116
129
|
actionUpload: "Attach file",
|
|
@@ -1076,13 +1089,13 @@ function dispatchLifecycleEvent(target, name, detail, targetId) {
|
|
|
1076
1089
|
const stamped = detail && typeof detail === "object" ? { targetId: id, ...detail } : detail;
|
|
1077
1090
|
target.dispatchEvent(new CustomEvent(name, { bubbles: true, composed: true, detail: stamped }));
|
|
1078
1091
|
}
|
|
1092
|
+
const toolRefusal = toolRefusal$1;
|
|
1079
1093
|
function createStreamAdapter(opts) {
|
|
1080
1094
|
const { target, config, messageId, targetId } = opts;
|
|
1081
1095
|
let parser = new AparteStreamParser({ blocks: config.getStreamBlocks() });
|
|
1082
1096
|
let streaming = /* @__PURE__ */ new Set();
|
|
1083
1097
|
let thinkingId = null;
|
|
1084
1098
|
const toolSegments = /* @__PURE__ */ new Map();
|
|
1085
|
-
const addedTools = /* @__PURE__ */ new Set();
|
|
1086
1099
|
const settle = (toolCallId, update) => {
|
|
1087
1100
|
const id = `tool-${toolCallId}`;
|
|
1088
1101
|
target.updateSegment?.(id, toolSegments.has(id) ? update : { ...update, deferred: void 0 });
|
|
@@ -1161,17 +1174,8 @@ function createStreamAdapter(opts) {
|
|
|
1161
1174
|
};
|
|
1162
1175
|
toolSegments.set(toolSeg.id, toolSeg);
|
|
1163
1176
|
const renderer = config.getToolRenderer(e.name);
|
|
1164
|
-
if (renderer)
|
|
1165
|
-
|
|
1166
|
-
const html = renderer.render(toolSeg);
|
|
1167
|
-
if (html) {
|
|
1168
|
-
target.addSegment?.(toolSeg);
|
|
1169
|
-
addedTools.add(toolSeg.id);
|
|
1170
|
-
}
|
|
1171
|
-
} else {
|
|
1172
|
-
target.addSegment?.(toolSeg);
|
|
1173
|
-
addedTools.add(toolSeg.id);
|
|
1174
|
-
}
|
|
1177
|
+
if (renderer) injectToolRendererStyles(e.name, renderer);
|
|
1178
|
+
target.addSegment?.(toolSeg);
|
|
1175
1179
|
break;
|
|
1176
1180
|
}
|
|
1177
1181
|
case "tool-awaiting-approval":
|
|
@@ -1182,7 +1186,11 @@ function createStreamAdapter(opts) {
|
|
|
1182
1186
|
settle(e.toolCallId, { status: "pending" });
|
|
1183
1187
|
break;
|
|
1184
1188
|
case "tool-rejected":
|
|
1185
|
-
settle(e.toolCallId, {
|
|
1189
|
+
settle(e.toolCallId, {
|
|
1190
|
+
status: "rejected",
|
|
1191
|
+
result: e.reason,
|
|
1192
|
+
...e.by ? { refusal: e.instruction ? { by: e.by, instruction: e.instruction } : { by: e.by } } : {}
|
|
1193
|
+
});
|
|
1186
1194
|
break;
|
|
1187
1195
|
case "tool-resolved":
|
|
1188
1196
|
settle(e.toolCallId, {
|
|
@@ -1192,7 +1200,7 @@ function createStreamAdapter(opts) {
|
|
|
1192
1200
|
});
|
|
1193
1201
|
break;
|
|
1194
1202
|
case "tool-aborted":
|
|
1195
|
-
settle(e.toolCallId, { status: "aborted" });
|
|
1203
|
+
settle(e.toolCallId, e.cause ? { status: "aborted", abortCause: e.cause } : { status: "aborted" });
|
|
1196
1204
|
break;
|
|
1197
1205
|
case "tool-failed":
|
|
1198
1206
|
settle(e.toolCallId, { status: "failed", result: e.error });
|
|
@@ -1202,11 +1210,14 @@ function createStreamAdapter(opts) {
|
|
|
1202
1210
|
target.addSegment?.({
|
|
1203
1211
|
id: `max-turns-${uuid()}`,
|
|
1204
1212
|
type: "error",
|
|
1205
|
-
|
|
1206
|
-
|
|
1213
|
+
// The CHAT's text, in its language — resolved from the target, as the
|
|
1214
|
+
// reasoning default is: a client on the global config also answers a
|
|
1215
|
+
// chat with its own.
|
|
1216
|
+
content: (typeof Element !== "undefined" && target instanceof Element ? resolveConfig(target) : config).t("maxTurnsReached").replace("{n}", () => String(e.limit)),
|
|
1217
|
+
code: AparteErrorCode.MAX_TURNS_EXCEEDED
|
|
1207
1218
|
});
|
|
1208
1219
|
} else if (e.toolCallId) {
|
|
1209
|
-
settle(e.toolCallId, { status: "aborted" });
|
|
1220
|
+
settle(e.toolCallId, { status: "aborted", abortCause: "turn-limit" });
|
|
1210
1221
|
}
|
|
1211
1222
|
break;
|
|
1212
1223
|
case "run-aborted":
|
|
@@ -1217,15 +1228,7 @@ function createStreamAdapter(opts) {
|
|
|
1217
1228
|
for (const p of e.pending) {
|
|
1218
1229
|
const id = `tool-${p.toolCallId}`;
|
|
1219
1230
|
const deferred = { reason: p.reason, ...typeof ticket === "string" ? { ticket } : {} };
|
|
1220
|
-
|
|
1221
|
-
target.updateSegment?.(id, { deferred });
|
|
1222
|
-
} else {
|
|
1223
|
-
const seg = toolSegments.get(id);
|
|
1224
|
-
if (seg) {
|
|
1225
|
-
target.addSegment?.({ ...seg, deferred });
|
|
1226
|
-
addedTools.add(id);
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1231
|
+
target.updateSegment?.(id, { deferred });
|
|
1229
1232
|
}
|
|
1230
1233
|
target.updateMessage?.(messageId, { status: "completed" });
|
|
1231
1234
|
if (e.usage) {
|
|
@@ -1494,7 +1497,8 @@ function createAparteAgentHandler(options) {
|
|
|
1494
1497
|
// Reached only for a server `deny`: an `ask` is deferred to the page.
|
|
1495
1498
|
approvalResolver: async (call) => ({
|
|
1496
1499
|
approved: false,
|
|
1497
|
-
reason: rule(call)?.reason?.trim() || "Tool execution was refused by the server policy."
|
|
1500
|
+
reason: rule(call)?.reason?.trim() || "Tool execution was refused by the server policy.",
|
|
1501
|
+
by: "policy"
|
|
1498
1502
|
}),
|
|
1499
1503
|
deferral: { outcomes },
|
|
1500
1504
|
emitter,
|
|
@@ -1848,6 +1852,50 @@ function chatBoundaryOf(el2) {
|
|
|
1848
1852
|
}
|
|
1849
1853
|
return null;
|
|
1850
1854
|
}
|
|
1855
|
+
class ToolTable {
|
|
1856
|
+
exact = /* @__PURE__ */ new Map();
|
|
1857
|
+
// Keyed by the pattern's spelling, so two RegExp instances of one pattern are one
|
|
1858
|
+
// entry; a Map keeps first-insertion order, which is the order patterns are tried in.
|
|
1859
|
+
patterns = /* @__PURE__ */ new Map();
|
|
1860
|
+
any;
|
|
1861
|
+
/** The value registered under exactly this matcher — not a resolution. */
|
|
1862
|
+
get(match) {
|
|
1863
|
+
if (match instanceof RegExp) return this.patterns.get(String(match))?.value;
|
|
1864
|
+
return match === "*" ? this.any : this.exact.get(match);
|
|
1865
|
+
}
|
|
1866
|
+
set(match, value) {
|
|
1867
|
+
if (match instanceof RegExp) {
|
|
1868
|
+
const key = String(match);
|
|
1869
|
+
const entry = this.patterns.get(key);
|
|
1870
|
+
if (entry) entry.value = value;
|
|
1871
|
+
else this.patterns.set(key, { re: match, value });
|
|
1872
|
+
} else if (match === "*") {
|
|
1873
|
+
this.any = value;
|
|
1874
|
+
} else {
|
|
1875
|
+
this.exact.set(match, value);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
delete(match) {
|
|
1879
|
+
if (match instanceof RegExp) this.patterns.delete(String(match));
|
|
1880
|
+
else if (match === "*") this.any = void 0;
|
|
1881
|
+
else this.exact.delete(match);
|
|
1882
|
+
}
|
|
1883
|
+
/** The value that applies to a tool named `name`, and how specifically it names it. */
|
|
1884
|
+
resolve(name) {
|
|
1885
|
+
if (typeof name !== "string") return void 0;
|
|
1886
|
+
const own = this.exact.get(name);
|
|
1887
|
+
if (own !== void 0) return { value: own, level: "exact" };
|
|
1888
|
+
for (const { re, value } of this.patterns.values()) {
|
|
1889
|
+
if (name.search(re) !== -1) return { value, level: "pattern" };
|
|
1890
|
+
}
|
|
1891
|
+
return this.any !== void 0 ? { value: this.any, level: "any" } : void 0;
|
|
1892
|
+
}
|
|
1893
|
+
clear() {
|
|
1894
|
+
this.exact.clear();
|
|
1895
|
+
this.patterns.clear();
|
|
1896
|
+
this.any = void 0;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1851
1899
|
const APARTE_DEFAULT_BUBBLE_ACTIONS = {
|
|
1852
1900
|
copy: true,
|
|
1853
1901
|
retry: false,
|
|
@@ -1975,9 +2023,9 @@ class AparteConfig {
|
|
|
1975
2023
|
_elicitationQueue = /* @__PURE__ */ new Map();
|
|
1976
2024
|
// Tool Registry
|
|
1977
2025
|
_tools = /* @__PURE__ */ new Map();
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
2026
|
+
// By exact name, pattern or '*' — see `tool-table.ts` for the one resolution rule.
|
|
2027
|
+
_toolRenderers = new ToolTable();
|
|
2028
|
+
_toolDisplays = new ToolTable();
|
|
1981
2029
|
/** Tagged blocks the stream parser recognises in the prose — see {@link registerStreamBlock}. */
|
|
1982
2030
|
_streamBlocks = /* @__PURE__ */ new Map();
|
|
1983
2031
|
_segmentDefaults = /* @__PURE__ */ new Map();
|
|
@@ -2810,16 +2858,20 @@ class AparteConfig {
|
|
|
2810
2858
|
* instead — see [Custom tool
|
|
2811
2859
|
* renderer](https://apartejs.dev/guides/tools/#custom-tool-renderer).
|
|
2812
2860
|
*
|
|
2813
|
-
*
|
|
2814
|
-
*
|
|
2815
|
-
*
|
|
2861
|
+
* The matcher is an exact name, a RegExp, or `'*'` — the same rule as
|
|
2862
|
+
* {@link registerToolDisplay}: the exact name wins, then the first pattern registered,
|
|
2863
|
+
* then `'*'`, which is one renderer for every tool without its own.
|
|
2816
2864
|
*
|
|
2817
2865
|
* @example
|
|
2818
2866
|
* // Custom pill for a web-search tool
|
|
2819
2867
|
* aparteGlobalConfig.registerToolRenderer('web_search', { render: (seg) => `<div class="aparte-tool-label">Searching...</div>` });
|
|
2868
|
+
*
|
|
2869
|
+
* @example
|
|
2870
|
+
* // One look for every tool an MCP server brings
|
|
2871
|
+
* aparteGlobalConfig.registerToolRenderer(/^mcp__/, { render: (seg) => myMcpCard(seg) });
|
|
2820
2872
|
*/
|
|
2821
|
-
registerToolRenderer(
|
|
2822
|
-
this._toolRenderers.set(
|
|
2873
|
+
registerToolRenderer(match, renderer) {
|
|
2874
|
+
this._toolRenderers.set(match, renderer);
|
|
2823
2875
|
}
|
|
2824
2876
|
/**
|
|
2825
2877
|
* Field defaults for a segment TYPE, filled in when a segment enters a message.
|
|
@@ -2859,13 +2911,79 @@ class AparteConfig {
|
|
|
2859
2911
|
clearSegmentDefaults(type) {
|
|
2860
2912
|
this._segmentDefaults.delete(type);
|
|
2861
2913
|
}
|
|
2862
|
-
/** Unregister
|
|
2863
|
-
unregisterToolRenderer(
|
|
2864
|
-
this._toolRenderers.delete(
|
|
2914
|
+
/** Unregister the renderer registered under this matcher. */
|
|
2915
|
+
unregisterToolRenderer(match) {
|
|
2916
|
+
this._toolRenderers.delete(match);
|
|
2865
2917
|
}
|
|
2866
|
-
/**
|
|
2918
|
+
/** The renderer that draws a tool named `toolName` — exact, pattern or `'*'` — or undefined. */
|
|
2867
2919
|
getToolRenderer(toolName) {
|
|
2868
|
-
return this._toolRenderers.
|
|
2920
|
+
return this._toolRenderers.resolve(toolName)?.value;
|
|
2921
|
+
}
|
|
2922
|
+
/**
|
|
2923
|
+
* Say how a tool's calls are shown: a label in place of its identifier, an icon, what
|
|
2924
|
+
* the call targets, how its arguments read, whether its row is drawn at all, and
|
|
2925
|
+
* whether it opens.
|
|
2926
|
+
*
|
|
2927
|
+
* The matcher is an exact name, a RegExp, or `'*'` — "every tool nothing more
|
|
2928
|
+
* specific describes". One entry per matcher, and calls MERGE into it; the entry that
|
|
2929
|
+
* applies to a tool is the exact name's, else the first pattern registered that
|
|
2930
|
+
* matches, else `'*'`'s — whole, never a mix of two. Registering tells the chats on
|
|
2931
|
+
* the page, so a label reaches rows already drawn.
|
|
2932
|
+
*
|
|
2933
|
+
* Core never makes a label up: without one the row shows the identifier, which is the
|
|
2934
|
+
* app's wire format.
|
|
2935
|
+
*
|
|
2936
|
+
* @example
|
|
2937
|
+
* aparteGlobalConfig.registerToolDisplay('web_search', {
|
|
2938
|
+
* label: 'Web search',
|
|
2939
|
+
* icon: 'search',
|
|
2940
|
+
* summary: (segment) => String(segment.toolCall.input['query'] ?? ''),
|
|
2941
|
+
* });
|
|
2942
|
+
*
|
|
2943
|
+
* @example
|
|
2944
|
+
* // A consumer chat: the reply is what matters; the calls stay in the history.
|
|
2945
|
+
* aparteGlobalConfig.registerToolDisplay('*', { hidden: true });
|
|
2946
|
+
*/
|
|
2947
|
+
registerToolDisplay(match, display) {
|
|
2948
|
+
const before = this._toolDisplays.get(match);
|
|
2949
|
+
const next = { ...before, ...display };
|
|
2950
|
+
this._toolDisplays.set(match, next);
|
|
2951
|
+
if (before?.hidden !== next.hidden || before?.open !== next.open) bumpRenderingVersion(this);
|
|
2952
|
+
this._notify();
|
|
2953
|
+
}
|
|
2954
|
+
/** Forget the display registered under this matcher. */
|
|
2955
|
+
unregisterToolDisplay(match) {
|
|
2956
|
+
const before = this._toolDisplays.get(match);
|
|
2957
|
+
this._toolDisplays.delete(match);
|
|
2958
|
+
if (before?.hidden !== void 0 || before?.open !== void 0) bumpRenderingVersion(this);
|
|
2959
|
+
this._notify();
|
|
2960
|
+
}
|
|
2961
|
+
/** How a tool named `toolName` is shown — the entry that applies to it — or undefined. */
|
|
2962
|
+
getToolDisplay(toolName) {
|
|
2963
|
+
return this._toolDisplays.resolve(toolName)?.value;
|
|
2964
|
+
}
|
|
2965
|
+
/**
|
|
2966
|
+
* Whether a tool's calls draw nothing: its display says `hidden`, and no renderer is
|
|
2967
|
+
* registered for it MORE specifically. A `'*'` hide therefore leaves a tool with its
|
|
2968
|
+
* own renderer drawn (a plugin's receipt), while a hide as specific as the renderer
|
|
2969
|
+
* wins — the app said so for that tool. A hidden call stays in the message, in storage
|
|
2970
|
+
* and in the model's history; only its row is empty.
|
|
2971
|
+
*/
|
|
2972
|
+
isToolHidden(toolName) {
|
|
2973
|
+
const display = this._toolDisplays.resolve(toolName);
|
|
2974
|
+
if (!display?.value.hidden) return false;
|
|
2975
|
+
const renderer = this._toolRenderers.resolve(toolName);
|
|
2976
|
+
const rank = { exact: 2, pattern: 1, any: 0 };
|
|
2977
|
+
return !renderer || rank[renderer.level] <= rank[display.level];
|
|
2978
|
+
}
|
|
2979
|
+
/** Drop one field of the entry under `match`, and the entry when nothing is left. */
|
|
2980
|
+
_dropToolDisplayField(match, field) {
|
|
2981
|
+
const entry = this._toolDisplays.get(match);
|
|
2982
|
+
if (!entry || !(field in entry)) return;
|
|
2983
|
+
const { [field]: _dropped, ...rest } = entry;
|
|
2984
|
+
if (Object.keys(rest).length) this._toolDisplays.set(match, rest);
|
|
2985
|
+
else this._toolDisplays.delete(match);
|
|
2986
|
+
this._notify();
|
|
2869
2987
|
}
|
|
2870
2988
|
/**
|
|
2871
2989
|
* Say what a call targets, in the default row, after the tool's name.
|
|
@@ -2882,42 +3000,46 @@ class AparteConfig {
|
|
|
2882
3000
|
* on every config change — so registering one after a row is on screen shows up on
|
|
2883
3001
|
* that row's next update.
|
|
2884
3002
|
*
|
|
3003
|
+
* The `summary` field of {@link registerToolDisplay}, on its own: same matcher, same
|
|
3004
|
+
* entry.
|
|
3005
|
+
*
|
|
2885
3006
|
* @example
|
|
2886
3007
|
* // read_file skills.json
|
|
2887
3008
|
* aparteGlobalConfig.registerToolSummary('read_file', (segment) =>
|
|
2888
3009
|
* String(segment.toolCall.input['path'] ?? ''));
|
|
2889
3010
|
*/
|
|
2890
|
-
registerToolSummary(
|
|
2891
|
-
this.
|
|
3011
|
+
registerToolSummary(match, summary) {
|
|
3012
|
+
this.registerToolDisplay(match, { summary });
|
|
2892
3013
|
}
|
|
2893
|
-
/**
|
|
2894
|
-
unregisterToolSummary(
|
|
2895
|
-
this.
|
|
3014
|
+
/** Drop the summary registered under this matcher; the rest of its display stays. */
|
|
3015
|
+
unregisterToolSummary(match) {
|
|
3016
|
+
this._dropToolDisplayField(match, "summary");
|
|
2896
3017
|
}
|
|
2897
|
-
/**
|
|
3018
|
+
/** The summary that applies to a tool named `toolName`, or undefined. */
|
|
2898
3019
|
getToolSummary(toolName) {
|
|
2899
|
-
return this.
|
|
3020
|
+
return this.getToolDisplay(toolName)?.summary;
|
|
2900
3021
|
}
|
|
2901
3022
|
/**
|
|
2902
3023
|
* Show a tool's arguments as a person reads them, in the approval panel and in the
|
|
2903
3024
|
* default tool row's disclosure — one formatter for both. Without one, both show the
|
|
2904
|
-
* JSON the model sent.
|
|
3025
|
+
* JSON the model sent. The `formatInput` field of {@link registerToolDisplay}, on its
|
|
3026
|
+
* own: same matcher, same entry.
|
|
2905
3027
|
*
|
|
2906
3028
|
* @example
|
|
2907
3029
|
* // Approve "write_file notes.md (2 KB)" rather than a JSON object.
|
|
2908
3030
|
* aparteGlobalConfig.registerToolInputFormatter('write_file', (input) =>
|
|
2909
3031
|
* `${String(input['path'] ?? '')} (${Math.ceil(String(input['content'] ?? '').length / 1024)} KB)`);
|
|
2910
3032
|
*/
|
|
2911
|
-
registerToolInputFormatter(
|
|
2912
|
-
this.
|
|
3033
|
+
registerToolInputFormatter(match, formatter) {
|
|
3034
|
+
this.registerToolDisplay(match, { formatInput: formatter });
|
|
2913
3035
|
}
|
|
2914
|
-
/**
|
|
2915
|
-
unregisterToolInputFormatter(
|
|
2916
|
-
this.
|
|
3036
|
+
/** Drop the input formatter registered under this matcher; the rest of its display stays. */
|
|
3037
|
+
unregisterToolInputFormatter(match) {
|
|
3038
|
+
this._dropToolDisplayField(match, "formatInput");
|
|
2917
3039
|
}
|
|
2918
|
-
/**
|
|
3040
|
+
/** The input formatter that applies to a tool named `toolName`, or undefined. */
|
|
2919
3041
|
getToolInputFormatter(toolName) {
|
|
2920
|
-
return this.
|
|
3042
|
+
return this.getToolDisplay(toolName)?.formatInput;
|
|
2921
3043
|
}
|
|
2922
3044
|
/**
|
|
2923
3045
|
* Teach the stream parser a tagged block: `<tag attr="…">…</tag>` in the model's
|
|
@@ -3128,8 +3250,7 @@ class AparteConfig {
|
|
|
3128
3250
|
this._fetchedModels.clear();
|
|
3129
3251
|
this._tools.clear();
|
|
3130
3252
|
this._toolRenderers.clear();
|
|
3131
|
-
this.
|
|
3132
|
-
this._toolInputFormatters.clear();
|
|
3253
|
+
this._toolDisplays.clear();
|
|
3133
3254
|
this._streamBlocks.clear();
|
|
3134
3255
|
this._modelConfig = {};
|
|
3135
3256
|
this._requireModelSelection = false;
|
|
@@ -3262,9 +3383,34 @@ const textRenderer = {
|
|
|
3262
3383
|
},
|
|
3263
3384
|
getStyles: () => ``
|
|
3264
3385
|
};
|
|
3386
|
+
function formatDuration(ms, tag) {
|
|
3387
|
+
const total = Math.max(1, Math.round(ms / 1e3));
|
|
3388
|
+
try {
|
|
3389
|
+
const unit = (n, u) => new Intl.NumberFormat(tag, { style: "unit", unit: u, unitDisplay: "narrow" }).format(n);
|
|
3390
|
+
if (total < 60) return unit(total, "second");
|
|
3391
|
+
const minutes = Math.floor(total / 60);
|
|
3392
|
+
const seconds = total % 60;
|
|
3393
|
+
if (!seconds) return unit(minutes, "minute");
|
|
3394
|
+
return new Intl.ListFormat(tag, { style: "narrow", type: "unit" }).format([unit(minutes, "minute"), unit(seconds, "second")]);
|
|
3395
|
+
} catch {
|
|
3396
|
+
return `${total}s`;
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3265
3399
|
const THINKING_ANCHOR_SLACK = 24;
|
|
3266
3400
|
function labelOf(segment) {
|
|
3267
|
-
|
|
3401
|
+
if (segment.label && segment.label !== "Thinking") return segment.label;
|
|
3402
|
+
const cfg = contextConfig();
|
|
3403
|
+
if (!isSegmentSettled(segment)) return cfg.t("thinking");
|
|
3404
|
+
const show = segment.showDuration ?? cfg.getSegmentDefaults("thinking")?.["showDuration"] === true;
|
|
3405
|
+
const ms = show ? segmentDuration(segment) : void 0;
|
|
3406
|
+
if (ms === void 0) return cfg.t("thought");
|
|
3407
|
+
return cfg.t("thoughtFor").replace("{duration}", () => formatDuration(ms, cfg.getLocale().tag || void 0));
|
|
3408
|
+
}
|
|
3409
|
+
function setLabel(el2, segment) {
|
|
3410
|
+
const text = labelOf(segment);
|
|
3411
|
+
const label = el2.querySelector(".aparte-thinking-label");
|
|
3412
|
+
if (label && label.textContent !== text) label.textContent = text;
|
|
3413
|
+
el2.querySelector(".aparte-thinking-content")?.setAttribute("aria-label", text);
|
|
3268
3414
|
}
|
|
3269
3415
|
const thinkingRenderer = {
|
|
3270
3416
|
type: "thinking",
|
|
@@ -3301,13 +3447,9 @@ const thinkingRenderer = {
|
|
|
3301
3447
|
* The default label is the only config-derived text here — and `segment.label`
|
|
3302
3448
|
* still wins, exactly as in `render`, because that string is the app's.
|
|
3303
3449
|
*/
|
|
3304
|
-
relabel: (el2, segment) =>
|
|
3305
|
-
const text = labelOf(segment);
|
|
3306
|
-
const label = el2.querySelector(".aparte-thinking-label");
|
|
3307
|
-
if (label) label.textContent = text;
|
|
3308
|
-
el2.querySelector(".aparte-thinking-content")?.setAttribute("aria-label", text);
|
|
3309
|
-
},
|
|
3450
|
+
relabel: (el2, segment) => setLabel(el2, segment),
|
|
3310
3451
|
update: (el2, segment) => {
|
|
3452
|
+
setLabel(el2, segment);
|
|
3311
3453
|
const contentEl = el2.querySelector(".aparte-thinking-content");
|
|
3312
3454
|
if (!contentEl) return;
|
|
3313
3455
|
const wasAtBottom = contentEl.scrollHeight - contentEl.scrollTop - contentEl.clientHeight <= THINKING_ANCHOR_SLACK;
|
|
@@ -3352,6 +3494,35 @@ function copyText(text) {
|
|
|
3352
3494
|
else reject(new Error("[aparte] Clipboard write failed"));
|
|
3353
3495
|
});
|
|
3354
3496
|
}
|
|
3497
|
+
const CONFIRM_MS = 1500;
|
|
3498
|
+
function nameButton(button, label) {
|
|
3499
|
+
button.setAttribute("aria-label", label);
|
|
3500
|
+
button.setAttribute("title", label);
|
|
3501
|
+
}
|
|
3502
|
+
function copyButtonMarkup(className) {
|
|
3503
|
+
const label = escapeAttr(contextConfig().t("copy"));
|
|
3504
|
+
const icon = contextConfig().getIcon("copy");
|
|
3505
|
+
return `<button type="button" class="aparte-btn aparte-btn--icon aparte-btn--sm ${escapeAttr(className)}" data-action="copy" aria-label="${label}" title="${label}">${icon}</button>`;
|
|
3506
|
+
}
|
|
3507
|
+
function wireCopyButton(button, read) {
|
|
3508
|
+
button.addEventListener("click", () => {
|
|
3509
|
+
void copyText(read()).catch(() => {
|
|
3510
|
+
});
|
|
3511
|
+
button.innerHTML = contextConfig(button).getIcon("check");
|
|
3512
|
+
nameButton(button, contextConfig(button).t("copied"));
|
|
3513
|
+
button.dataset["copied"] = "1";
|
|
3514
|
+
setTimeout(() => {
|
|
3515
|
+
button.innerHTML = contextConfig(button).getIcon("copy");
|
|
3516
|
+
nameButton(button, contextConfig(button).t("copy"));
|
|
3517
|
+
delete button.dataset["copied"];
|
|
3518
|
+
}, CONFIRM_MS);
|
|
3519
|
+
});
|
|
3520
|
+
}
|
|
3521
|
+
function relabelCopyButton(button) {
|
|
3522
|
+
if (button.dataset["copied"]) return;
|
|
3523
|
+
nameButton(button, contextConfig().t("copy"));
|
|
3524
|
+
button.innerHTML = contextConfig().getIcon("copy");
|
|
3525
|
+
}
|
|
3355
3526
|
const HIGHLIGHT_DEBOUNCE_MS = 400;
|
|
3356
3527
|
const MAX_THROTTLE_ENTRIES = 256;
|
|
3357
3528
|
const _lastHighlightAt = /* @__PURE__ */ new Map();
|
|
@@ -3399,32 +3570,27 @@ function streamHighlight(element, paneSelector, content, lang, segId) {
|
|
|
3399
3570
|
}).catch(() => {
|
|
3400
3571
|
});
|
|
3401
3572
|
}
|
|
3402
|
-
function nameButton(button, label) {
|
|
3403
|
-
button.setAttribute("aria-label", label);
|
|
3404
|
-
button.setAttribute("title", label);
|
|
3405
|
-
}
|
|
3406
3573
|
const codeRenderer = {
|
|
3407
3574
|
type: "code",
|
|
3408
|
-
render: (segment) =>
|
|
3575
|
+
render: (segment) => {
|
|
3576
|
+
const copy = copyButtonMarkup("aparte-code-copy");
|
|
3577
|
+
return `
|
|
3409
3578
|
<div class="aparte-segment aparte-segment-code" data-segment-id="${escapeHtml(segment.id)}">
|
|
3410
3579
|
<div class="aparte-code-header">
|
|
3411
3580
|
${segment.filename ? `<span class="aparte-code-filename">${escapeHtml(segment.filename)}</span>` : `<span class="aparte-code-header-filler"></span>`}
|
|
3412
3581
|
<span class="aparte-code-language">${escapeHtml(segment.language || "")}</span>
|
|
3413
|
-
|
|
3414
|
-
${contextConfig().getIcon("copy")}
|
|
3415
|
-
</button>
|
|
3582
|
+
${copy}
|
|
3416
3583
|
</div>
|
|
3417
3584
|
<div class="aparte-code-content-wrapper">
|
|
3418
3585
|
<pre><code class="language-${escapeHtml(segment.language || "text")}">${escapeHtml(segment.content)}</code></pre>
|
|
3419
3586
|
</div>
|
|
3420
3587
|
</div>
|
|
3421
|
-
|
|
3588
|
+
`;
|
|
3589
|
+
},
|
|
3422
3590
|
/** The copy button's icon and tooltip — nothing else here comes from config. */
|
|
3423
3591
|
relabel: (element) => {
|
|
3424
3592
|
const copyBtn = element.querySelector(".aparte-code-copy");
|
|
3425
|
-
if (
|
|
3426
|
-
nameButton(copyBtn, contextConfig().t("copy"));
|
|
3427
|
-
copyBtn.innerHTML = contextConfig().getIcon("copy");
|
|
3593
|
+
if (copyBtn) relabelCopyButton(copyBtn);
|
|
3428
3594
|
},
|
|
3429
3595
|
setup: (element, segment) => {
|
|
3430
3596
|
const wrapper = element.querySelector(".aparte-code-content-wrapper");
|
|
@@ -3435,21 +3601,7 @@ const codeRenderer = {
|
|
|
3435
3601
|
});
|
|
3436
3602
|
}
|
|
3437
3603
|
const copyBtn = element.querySelector(".aparte-code-copy");
|
|
3438
|
-
if (copyBtn)
|
|
3439
|
-
copyBtn.addEventListener("click", () => {
|
|
3440
|
-
const source = element.querySelector(".aparte-code-content-wrapper")?.textContent ?? "";
|
|
3441
|
-
void copyText(source).catch(() => {
|
|
3442
|
-
});
|
|
3443
|
-
copyBtn.innerHTML = contextConfig(copyBtn).getIcon("check");
|
|
3444
|
-
nameButton(copyBtn, contextConfig(copyBtn).t("copied"));
|
|
3445
|
-
copyBtn.dataset.copied = "1";
|
|
3446
|
-
setTimeout(() => {
|
|
3447
|
-
copyBtn.innerHTML = contextConfig(copyBtn).getIcon("copy");
|
|
3448
|
-
nameButton(copyBtn, contextConfig(copyBtn).t("copy"));
|
|
3449
|
-
delete copyBtn.dataset.copied;
|
|
3450
|
-
}, 1500);
|
|
3451
|
-
});
|
|
3452
|
-
}
|
|
3604
|
+
if (copyBtn) wireCopyButton(copyBtn, () => element.querySelector(".aparte-code-content-wrapper")?.textContent ?? "");
|
|
3453
3605
|
},
|
|
3454
3606
|
update: (element, segment) => {
|
|
3455
3607
|
if (segment.isStreaming) {
|
|
@@ -3471,7 +3623,11 @@ const errorRenderer = {
|
|
|
3471
3623
|
render: (segment) => {
|
|
3472
3624
|
const custom = contextConfig().getErrorRenderer?.();
|
|
3473
3625
|
if (custom) {
|
|
3474
|
-
const out = custom({
|
|
3626
|
+
const out = custom({
|
|
3627
|
+
message: segment.content,
|
|
3628
|
+
...segment.details !== void 0 ? { details: segment.details } : {},
|
|
3629
|
+
...segment.code !== void 0 ? { code: segment.code } : {}
|
|
3630
|
+
});
|
|
3475
3631
|
if (out instanceof HTMLElement) {
|
|
3476
3632
|
out.setAttribute("data-segment-id", segment.id);
|
|
3477
3633
|
return out;
|
|
@@ -3479,7 +3635,7 @@ const errorRenderer = {
|
|
|
3479
3635
|
return out;
|
|
3480
3636
|
}
|
|
3481
3637
|
return `
|
|
3482
|
-
<div class="aparte-segment aparte-alert aparte-alert--danger aparte-segment-error" data-segment-id="${escapeHtml(segment.id)}">
|
|
3638
|
+
<div class="aparte-segment aparte-alert aparte-alert--danger aparte-segment-error" data-segment-id="${escapeHtml(segment.id)}"${segment.code ? ` data-error-code="${escapeAttr(segment.code)}"` : ""}>
|
|
3483
3639
|
<span class="aparte-alert__icon">${contextConfig().getIcon("error") || "⚠"}</span>
|
|
3484
3640
|
<div class="aparte-alert__body">
|
|
3485
3641
|
<div class="aparte-alert__title">${escapeHtml(contextConfig().t("error"))}</div>
|
|
@@ -3518,22 +3674,84 @@ function describeToolInput(input, format) {
|
|
|
3518
3674
|
return "";
|
|
3519
3675
|
}
|
|
3520
3676
|
}
|
|
3677
|
+
function toolNameOf(segment, config) {
|
|
3678
|
+
const identifier = segment.toolCall?.name ?? "tool";
|
|
3679
|
+
const label = config.getToolDisplay(identifier)?.label;
|
|
3680
|
+
if (label !== void 0) {
|
|
3681
|
+
try {
|
|
3682
|
+
const text = typeof label === "function" ? label(segment) : label;
|
|
3683
|
+
if (typeof text === "string" && text.trim()) return { text: text.trim(), isLabel: true };
|
|
3684
|
+
} catch {
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
return { text: identifier, isLabel: false };
|
|
3688
|
+
}
|
|
3689
|
+
function toolTargetOf(segment, config) {
|
|
3690
|
+
const summary = config.getToolSummary(segment.toolCall?.name ?? "");
|
|
3691
|
+
if (!summary) return "";
|
|
3692
|
+
try {
|
|
3693
|
+
const text = summary(segment);
|
|
3694
|
+
return typeof text === "string" ? text.trim() : "";
|
|
3695
|
+
} catch {
|
|
3696
|
+
return "";
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3521
3699
|
function describeInput(segment) {
|
|
3522
3700
|
return describeToolInput(segment.toolCall?.input, contextConfig().getToolInputFormatter(segment.toolCall?.name ?? ""));
|
|
3523
3701
|
}
|
|
3524
3702
|
function stateBadge(segment) {
|
|
3525
3703
|
const cfg = contextConfig();
|
|
3526
3704
|
const status = segment.status ?? "pending";
|
|
3527
|
-
const word = (
|
|
3528
|
-
if (status === "awaiting-approval") return `${cfg.getIcon("pause")}${word("approvalWaiting")}`;
|
|
3529
|
-
if (status === "pending") return word("toolRunning");
|
|
3530
|
-
if (status === "resolved") return `${cfg.getIcon("check")}${word("toolCompleted")}`;
|
|
3531
|
-
if (status === "rejected") return `${cfg.getIcon("close")}${word("toolRejected")}`;
|
|
3532
|
-
if (status === "aborted") return `${cfg.getIcon("stop")}${word("toolStopped")}`;
|
|
3533
|
-
if (status === "failed") return `${cfg.getIcon("close")}${word("toolFailed")}`;
|
|
3705
|
+
const word = (text) => escapeHtml(text);
|
|
3706
|
+
if (status === "awaiting-approval") return `${cfg.getIcon("pause")}${word(cfg.t("approvalWaiting"))}`;
|
|
3707
|
+
if (status === "pending") return word(cfg.t("toolRunning"));
|
|
3708
|
+
if (status === "resolved") return `${cfg.getIcon("check")}${word(cfg.t("toolCompleted"))}`;
|
|
3709
|
+
if (status === "rejected") return `${cfg.getIcon("close")}${word(cfg.t("toolRejected"))}`;
|
|
3710
|
+
if (status === "aborted") return `${cfg.getIcon("stop")}${word(cfg.t("toolStopped"))}`;
|
|
3711
|
+
if (status === "failed") return `${cfg.getIcon("close")}${word(cfg.t("toolFailed"))}`;
|
|
3712
|
+
return "";
|
|
3713
|
+
}
|
|
3714
|
+
function noteOf(segment) {
|
|
3715
|
+
const cfg = contextConfig();
|
|
3716
|
+
if (segment.status === "rejected") {
|
|
3717
|
+
const by = segment.refusal?.by;
|
|
3718
|
+
if (by === "policy") return cfg.t("toolRefusedByPolicy");
|
|
3719
|
+
if (by === "earlier-refusal") return cfg.t("toolNotRunAfterRefusal");
|
|
3720
|
+
const said = segment.refusal?.instruction?.trim();
|
|
3721
|
+
return said ? cfg.t("toolRejectedSaid").replace("{instruction}", () => said) : "";
|
|
3722
|
+
}
|
|
3723
|
+
if (segment.status === "aborted") {
|
|
3724
|
+
switch (segment.abortCause) {
|
|
3725
|
+
case "timeout":
|
|
3726
|
+
return cfg.t("toolTimedOut");
|
|
3727
|
+
case "no-handler":
|
|
3728
|
+
return cfg.t("toolUnavailable");
|
|
3729
|
+
case "no-resolver":
|
|
3730
|
+
return cfg.t("toolNoApprover");
|
|
3731
|
+
case "turn-limit":
|
|
3732
|
+
return cfg.t("toolTurnLimit");
|
|
3733
|
+
default:
|
|
3734
|
+
return "";
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3534
3737
|
return "";
|
|
3535
3738
|
}
|
|
3536
|
-
function
|
|
3739
|
+
function outcomeOf(segment) {
|
|
3740
|
+
const text = displayText(segment.result ?? "");
|
|
3741
|
+
if (!text) return null;
|
|
3742
|
+
if (segment.status === "rejected" && segment.refusal) return null;
|
|
3743
|
+
return { key: "toolOutput", text };
|
|
3744
|
+
}
|
|
3745
|
+
function setNote(element, segment) {
|
|
3746
|
+
const el2 = element.querySelector(".aparte-tool-note");
|
|
3747
|
+
if (!el2) return;
|
|
3748
|
+
const text = noteOf(segment);
|
|
3749
|
+
if (el2.textContent !== text) el2.textContent = text;
|
|
3750
|
+
el2.toggleAttribute("hidden", !text);
|
|
3751
|
+
if (text) el2.setAttribute("title", text);
|
|
3752
|
+
else el2.removeAttribute("title");
|
|
3753
|
+
}
|
|
3754
|
+
function setPart(element, part, text, labelKey) {
|
|
3537
3755
|
const existing = element.querySelector(`[data-part="${part}"]`);
|
|
3538
3756
|
if (!text) {
|
|
3539
3757
|
existing?.remove();
|
|
@@ -3542,6 +3760,10 @@ function setPart(element, part, text) {
|
|
|
3542
3760
|
if (existing) {
|
|
3543
3761
|
const code2 = existing.querySelector("code");
|
|
3544
3762
|
if (code2 && code2.textContent !== text) code2.textContent = text;
|
|
3763
|
+
const label2 = existing.querySelector(".aparte-tool-part-label");
|
|
3764
|
+
const word = contextConfig().t(labelKey);
|
|
3765
|
+
if (label2 && label2.textContent !== word) label2.textContent = word;
|
|
3766
|
+
existing.querySelector(".aparte-tool-part-body")?.setAttribute("aria-label", word);
|
|
3545
3767
|
return;
|
|
3546
3768
|
}
|
|
3547
3769
|
const detail = element.querySelector(".aparte-tool-detail");
|
|
@@ -3550,25 +3772,57 @@ function setPart(element, part, text) {
|
|
|
3550
3772
|
const wrap = document.createElement("div");
|
|
3551
3773
|
wrap.className = "aparte-tool-part";
|
|
3552
3774
|
wrap.dataset["part"] = part;
|
|
3775
|
+
const head = document.createElement("div");
|
|
3776
|
+
head.className = "aparte-tool-part-head";
|
|
3553
3777
|
const label = document.createElement("span");
|
|
3554
3778
|
label.className = "aparte-tool-part-label";
|
|
3555
|
-
label.textContent = cfg.t(
|
|
3779
|
+
label.textContent = cfg.t(labelKey);
|
|
3780
|
+
const tpl = document.createElement("template");
|
|
3781
|
+
tpl.innerHTML = copyButtonMarkup("aparte-tool-copy");
|
|
3782
|
+
head.append(label, tpl.content);
|
|
3556
3783
|
const body = document.createElement("div");
|
|
3557
3784
|
body.className = "aparte-tool-part-body";
|
|
3785
|
+
body.setAttribute("role", "region");
|
|
3786
|
+
body.tabIndex = 0;
|
|
3787
|
+
body.setAttribute("aria-label", label.textContent);
|
|
3558
3788
|
const pre = document.createElement("pre");
|
|
3559
3789
|
const code = document.createElement("code");
|
|
3560
3790
|
code.textContent = text;
|
|
3561
3791
|
pre.appendChild(code);
|
|
3562
3792
|
body.appendChild(pre);
|
|
3563
|
-
wrap.append(
|
|
3793
|
+
wrap.append(head, body);
|
|
3564
3794
|
if (part === "input") detail.prepend(wrap);
|
|
3565
3795
|
else detail.appendChild(wrap);
|
|
3796
|
+
wireParts(wrap);
|
|
3797
|
+
}
|
|
3798
|
+
function wireParts(root) {
|
|
3799
|
+
for (const button of root.querySelectorAll(".aparte-tool-copy")) {
|
|
3800
|
+
if (button.dataset["wired"]) continue;
|
|
3801
|
+
button.dataset["wired"] = "1";
|
|
3802
|
+
const part = button.closest(".aparte-tool-part");
|
|
3803
|
+
wireCopyButton(button, () => part?.querySelector(".aparte-tool-part-body")?.textContent ?? "");
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
const JSON_PART_LIMIT = 2e5;
|
|
3807
|
+
function jsonStructureOf(text) {
|
|
3808
|
+
const trimmed = text.trim();
|
|
3809
|
+
if (trimmed.length > JSON_PART_LIMIT || !(trimmed.startsWith("{") || trimmed.startsWith("["))) return void 0;
|
|
3810
|
+
try {
|
|
3811
|
+
const value = JSON.parse(trimmed);
|
|
3812
|
+
return value !== null && typeof value === "object" ? value : void 0;
|
|
3813
|
+
} catch {
|
|
3814
|
+
return void 0;
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
function displayText(raw) {
|
|
3818
|
+
const value = jsonStructureOf(raw);
|
|
3819
|
+
return value === void 0 ? raw : JSON.stringify(value, null, 2);
|
|
3566
3820
|
}
|
|
3567
3821
|
function highlightParts(element) {
|
|
3568
3822
|
const cfg = contextConfig();
|
|
3569
3823
|
for (const body of element.querySelectorAll(".aparte-tool-part-body")) {
|
|
3570
3824
|
const text = body.querySelector("code")?.textContent ?? "";
|
|
3571
|
-
if (!text) continue;
|
|
3825
|
+
if (!text || jsonStructureOf(text) === void 0) continue;
|
|
3572
3826
|
void cfg.highlightCode(text, "json").then((html) => {
|
|
3573
3827
|
if ((body.querySelector("code")?.textContent ?? "") !== text) return;
|
|
3574
3828
|
body.innerHTML = html;
|
|
@@ -3577,14 +3831,7 @@ function highlightParts(element) {
|
|
|
3577
3831
|
}
|
|
3578
3832
|
}
|
|
3579
3833
|
function toolTarget(segment) {
|
|
3580
|
-
|
|
3581
|
-
if (!summary) return "";
|
|
3582
|
-
try {
|
|
3583
|
-
const text = summary(segment);
|
|
3584
|
-
return typeof text === "string" ? text.trim() : "";
|
|
3585
|
-
} catch {
|
|
3586
|
-
return "";
|
|
3587
|
-
}
|
|
3834
|
+
return toolTargetOf(segment, contextConfig());
|
|
3588
3835
|
}
|
|
3589
3836
|
function setTarget(element, segment) {
|
|
3590
3837
|
const el2 = element.querySelector(".aparte-tool-target");
|
|
@@ -3593,54 +3840,79 @@ function setTarget(element, segment) {
|
|
|
3593
3840
|
if (el2.textContent !== text) el2.textContent = text;
|
|
3594
3841
|
el2.toggleAttribute("hidden", !text);
|
|
3595
3842
|
}
|
|
3596
|
-
function
|
|
3843
|
+
function nameOf(segment) {
|
|
3844
|
+
return toolNameOf(segment, contextConfig());
|
|
3845
|
+
}
|
|
3846
|
+
function iconOf(segment) {
|
|
3847
|
+
const cfg = contextConfig();
|
|
3848
|
+
const key = cfg.getToolDisplay(segment.toolCall?.name ?? "")?.icon;
|
|
3849
|
+
return (key ? cfg.getIcon(key) : void 0) || cfg.getIcon("tool");
|
|
3850
|
+
}
|
|
3851
|
+
function setName(element, segment) {
|
|
3852
|
+
const el2 = element.querySelector(".aparte-tool-name");
|
|
3853
|
+
if (!el2) return;
|
|
3854
|
+
const { text, isLabel } = nameOf(segment);
|
|
3855
|
+
if (el2.textContent !== text) el2.textContent = text;
|
|
3856
|
+
el2.classList.toggle("aparte-tool-name--label", isLabel);
|
|
3857
|
+
}
|
|
3858
|
+
function pillMarkup(segment, status) {
|
|
3597
3859
|
const badge = stateBadge(segment);
|
|
3860
|
+
const icon = iconOf(segment);
|
|
3598
3861
|
const target = toolTarget(segment);
|
|
3862
|
+
const note = noteOf(segment);
|
|
3863
|
+
const name = nameOf(segment);
|
|
3599
3864
|
return `
|
|
3600
3865
|
<span class="aparte-tool-label">
|
|
3601
|
-
<span class="aparte-tool-icon">${
|
|
3602
|
-
<span class="aparte-tool-name">${escapeHtml(name)}</span>
|
|
3866
|
+
<span class="aparte-tool-icon">${icon}</span>
|
|
3867
|
+
<span class="aparte-tool-name${name.isLabel ? " aparte-tool-name--label" : ""}">${escapeHtml(name.text)}</span>
|
|
3603
3868
|
<span class="aparte-tool-target"${target ? "" : " hidden"}>${escapeHtml(target)}</span>
|
|
3604
3869
|
</span>
|
|
3605
3870
|
<span class="aparte-spinner aparte-tool-spinner" aria-hidden="true"${status === "pending" ? "" : " hidden"}></span>
|
|
3606
|
-
<span class="aparte-tool-state">${badge}</span
|
|
3871
|
+
<span class="aparte-tool-state">${badge}</span>
|
|
3872
|
+
<span class="aparte-tool-note"${note ? ` title="${escapeAttr(note)}"` : " hidden"}>${escapeHtml(note)}</span>`;
|
|
3607
3873
|
}
|
|
3874
|
+
function placeholderMarkup(segment) {
|
|
3875
|
+
return `<div class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(segment.status ?? "pending")}" data-tool-call-id="${escapeAttr(segment.toolCall?.id ?? "")}" data-tool-name="${escapeAttr(segment.toolCall?.name ?? "tool")}" hidden></div>`;
|
|
3876
|
+
}
|
|
3877
|
+
const isPlaceholder = (element) => element.hidden && element.classList.contains("aparte-segment-tool-call") && element.childElementCount === 0;
|
|
3608
3878
|
const toolCallRenderer = {
|
|
3609
3879
|
type: "tool_call",
|
|
3610
3880
|
render: (segment) => {
|
|
3611
3881
|
const name = segment.toolCall?.name ?? "tool";
|
|
3612
3882
|
const status = segment.status ?? "pending";
|
|
3613
3883
|
const toolCallId = segment.toolCall?.id ?? "";
|
|
3614
|
-
|
|
3884
|
+
if (contextConfig().isToolHidden(name)) return placeholderMarkup(segment);
|
|
3885
|
+
const customRenderer = contextConfig().getToolRenderer(name);
|
|
3615
3886
|
if (customRenderer) {
|
|
3616
|
-
injectToolRendererStyles(
|
|
3617
|
-
|
|
3618
|
-
if (out) return out;
|
|
3887
|
+
injectToolRendererStyles(name, customRenderer);
|
|
3888
|
+
return customRenderer.render(segment) || placeholderMarkup(segment);
|
|
3619
3889
|
}
|
|
3620
|
-
const pill = pillMarkup(segment,
|
|
3890
|
+
const pill = pillMarkup(segment, status);
|
|
3621
3891
|
const toggle = contextConfig().getIcon("expand");
|
|
3622
3892
|
const input = describeInput(segment);
|
|
3623
|
-
const
|
|
3624
|
-
if (!input && !
|
|
3893
|
+
const outcome = outcomeOf(segment);
|
|
3894
|
+
if (!input && !outcome) {
|
|
3625
3895
|
return `
|
|
3626
|
-
<div class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}">${pill}
|
|
3896
|
+
<div class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}" data-tool-name="${escapeAttr(name)}">${pill}
|
|
3627
3897
|
</div>
|
|
3628
3898
|
`;
|
|
3629
3899
|
}
|
|
3630
3900
|
const cfg = contextConfig();
|
|
3901
|
+
const copy = copyButtonMarkup("aparte-tool-copy");
|
|
3902
|
+
const open = segment.collapsed !== void 0 ? !segment.collapsed : cfg.getToolDisplay(name)?.open === true;
|
|
3631
3903
|
return `
|
|
3632
|
-
<details class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}">
|
|
3904
|
+
<details class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}" data-tool-name="${escapeAttr(name)}"${open ? " open" : ""}>
|
|
3633
3905
|
<summary class="aparte-tool-summary"><span class="aparte-tool-toggle">${toggle}</span>${pill}</summary>
|
|
3634
3906
|
<div class="aparte-tool-detail">
|
|
3635
3907
|
${input ? `
|
|
3636
3908
|
<div class="aparte-tool-part" data-part="input">
|
|
3637
|
-
<span class="aparte-tool-part-label">${escapeHtml(cfg.t("toolInput"))}</span>
|
|
3638
|
-
<div class="aparte-tool-part-body"><pre><code>${escapeHtml(input)}</code></pre></div>
|
|
3909
|
+
<div class="aparte-tool-part-head"><span class="aparte-tool-part-label">${escapeHtml(cfg.t("toolInput"))}</span>${copy}</div>
|
|
3910
|
+
<div class="aparte-tool-part-body" role="region" tabindex="0" aria-label="${escapeAttr(cfg.t("toolInput"))}"><pre><code>${escapeHtml(input)}</code></pre></div>
|
|
3639
3911
|
</div>` : ""}
|
|
3640
|
-
${
|
|
3912
|
+
${outcome ? `
|
|
3641
3913
|
<div class="aparte-tool-part" data-part="output">
|
|
3642
|
-
<span class="aparte-tool-part-label">${escapeHtml(cfg.t(
|
|
3643
|
-
<div class="aparte-tool-part-body"><pre><code>${escapeHtml(
|
|
3914
|
+
<div class="aparte-tool-part-head"><span class="aparte-tool-part-label">${escapeHtml(cfg.t(outcome.key))}</span>${copy}</div>
|
|
3915
|
+
<div class="aparte-tool-part-body" role="region" tabindex="0" aria-label="${escapeAttr(cfg.t(outcome.key))}"><pre><code>${escapeHtml(outcome.text)}</code></pre></div>
|
|
3644
3916
|
</div>` : ""}
|
|
3645
3917
|
</div>
|
|
3646
3918
|
</details>
|
|
@@ -3657,6 +3929,7 @@ const toolCallRenderer = {
|
|
|
3657
3929
|
* No child node is added or removed, per the relabel contract.
|
|
3658
3930
|
*/
|
|
3659
3931
|
relabel: (element, segment) => {
|
|
3932
|
+
if (isPlaceholder(element)) return;
|
|
3660
3933
|
const cfg = contextConfig();
|
|
3661
3934
|
const custom = cfg.getToolRenderer(segment.toolCall?.name);
|
|
3662
3935
|
if (custom) {
|
|
@@ -3664,8 +3937,18 @@ const toolCallRenderer = {
|
|
|
3664
3937
|
return;
|
|
3665
3938
|
}
|
|
3666
3939
|
const icon = element.querySelector(".aparte-tool-icon");
|
|
3667
|
-
if (icon) icon.innerHTML =
|
|
3940
|
+
if (icon) icon.innerHTML = iconOf(segment);
|
|
3941
|
+
setName(element, segment);
|
|
3668
3942
|
setTarget(element, segment);
|
|
3943
|
+
setNote(element, segment);
|
|
3944
|
+
const inputLabel = element.querySelector('[data-part="input"] .aparte-tool-part-label');
|
|
3945
|
+
if (inputLabel) inputLabel.textContent = cfg.t("toolInput");
|
|
3946
|
+
element.querySelector('[data-part="input"] .aparte-tool-part-body')?.setAttribute("aria-label", cfg.t("toolInput"));
|
|
3947
|
+
const outcome = outcomeOf(segment);
|
|
3948
|
+
const outputLabel = element.querySelector('[data-part="output"] .aparte-tool-part-label');
|
|
3949
|
+
if (outputLabel && outcome) outputLabel.textContent = cfg.t(outcome.key);
|
|
3950
|
+
if (outcome) element.querySelector('[data-part="output"] .aparte-tool-part-body')?.setAttribute("aria-label", cfg.t(outcome.key));
|
|
3951
|
+
for (const button of element.querySelectorAll(".aparte-tool-copy")) relabelCopyButton(button);
|
|
3669
3952
|
const statusEl = element.querySelector(".aparte-tool-state");
|
|
3670
3953
|
if (statusEl) statusEl.innerHTML = stateBadge(segment);
|
|
3671
3954
|
},
|
|
@@ -3684,14 +3967,20 @@ const toolCallRenderer = {
|
|
|
3684
3967
|
*/
|
|
3685
3968
|
update: (element, segment) => {
|
|
3686
3969
|
const status = segment.status ?? "pending";
|
|
3970
|
+
const hidden = contextConfig().isToolHidden(segment.toolCall?.name ?? "tool");
|
|
3971
|
+
if (hidden && isPlaceholder(element)) {
|
|
3972
|
+
element.setAttribute("data-status", status);
|
|
3973
|
+
return;
|
|
3974
|
+
}
|
|
3687
3975
|
const input = describeInput(segment);
|
|
3688
|
-
const
|
|
3976
|
+
const outcome = outcomeOf(segment);
|
|
3977
|
+
const wantsDetail = !!input || !!outcome;
|
|
3689
3978
|
const custom = contextConfig().getToolRenderer(segment.toolCall?.name);
|
|
3690
|
-
if (custom?.update) {
|
|
3979
|
+
if (custom?.update && !hidden && !isPlaceholder(element)) {
|
|
3691
3980
|
custom.update(element, segment);
|
|
3692
3981
|
return;
|
|
3693
3982
|
}
|
|
3694
|
-
if (custom || wantsDetail !== (element.tagName === "DETAILS")) {
|
|
3983
|
+
if (custom || hidden || isPlaceholder(element) || wantsDetail !== (element.tagName === "DETAILS")) {
|
|
3695
3984
|
const out = toolCallRenderer.render(segment);
|
|
3696
3985
|
const tpl = document.createElement("template");
|
|
3697
3986
|
tpl.innerHTML = typeof out === "string" ? out.trim() : "";
|
|
@@ -3706,12 +3995,16 @@ const toolCallRenderer = {
|
|
|
3706
3995
|
const badge = element.querySelector(".aparte-tool-state");
|
|
3707
3996
|
if (badge) badge.innerHTML = stateBadge(segment);
|
|
3708
3997
|
element.querySelector(".aparte-tool-spinner")?.toggleAttribute("hidden", status !== "pending");
|
|
3998
|
+
setName(element, segment);
|
|
3709
3999
|
setTarget(element, segment);
|
|
3710
|
-
|
|
3711
|
-
setPart(element, "
|
|
4000
|
+
setNote(element, segment);
|
|
4001
|
+
setPart(element, "input", input, "toolInput");
|
|
4002
|
+
setPart(element, "output", outcome?.text ?? "", outcome?.key ?? "toolOutput");
|
|
3712
4003
|
highlightParts(element);
|
|
3713
4004
|
},
|
|
3714
4005
|
setup: (element, segment) => {
|
|
4006
|
+
if (isPlaceholder(element)) return;
|
|
4007
|
+
wireParts(element);
|
|
3715
4008
|
highlightParts(element);
|
|
3716
4009
|
const customRenderer = contextConfig().getToolRenderer(segment.toolCall?.name);
|
|
3717
4010
|
customRenderer?.setup?.(element, segment);
|
|
@@ -3784,11 +4077,16 @@ function injectRendererStyles(config = contextConfig()) {
|
|
|
3784
4077
|
}
|
|
3785
4078
|
if (added) styleElement.textContent = Array.from(injectedRendererStyles).join("\n");
|
|
3786
4079
|
}
|
|
4080
|
+
const toolRendererSheetIds = /* @__PURE__ */ new WeakMap();
|
|
3787
4081
|
function injectToolRendererStyles(toolName, renderer) {
|
|
3788
4082
|
if (typeof document === "undefined") return;
|
|
3789
4083
|
const css = renderer.getStyles?.();
|
|
3790
4084
|
if (!css) return;
|
|
3791
|
-
|
|
4085
|
+
let id = toolRendererSheetIds.get(renderer);
|
|
4086
|
+
if (!id) {
|
|
4087
|
+
id = `aparte-tool-renderer-${toolName}`;
|
|
4088
|
+
toolRendererSheetIds.set(renderer, id);
|
|
4089
|
+
}
|
|
3792
4090
|
if (document.getElementById(id)) return;
|
|
3793
4091
|
const el2 = document.createElement("style");
|
|
3794
4092
|
el2.id = id;
|
|
@@ -6291,7 +6589,16 @@ class AparteClient {
|
|
|
6291
6589
|
*/
|
|
6292
6590
|
message: () => {
|
|
6293
6591
|
const loc = this._config.getLocale();
|
|
6294
|
-
|
|
6592
|
+
const chat = resolveConfig(target);
|
|
6593
|
+
const call = {
|
|
6594
|
+
id: `tool-${event.id}`,
|
|
6595
|
+
type: "tool_call",
|
|
6596
|
+
status: "awaiting-approval",
|
|
6597
|
+
toolCall: { id: event.id, name: event.name, input: event.input }
|
|
6598
|
+
};
|
|
6599
|
+
const name = toolNameOf(call, chat).text;
|
|
6600
|
+
const what = toolTargetOf(call, chat);
|
|
6601
|
+
return (loc.approvalAsk ?? "Allow {tool}?").replace("{tool}", () => what ? `${name} ${what}` : name);
|
|
6295
6602
|
},
|
|
6296
6603
|
// A plain string, not a function: the arguments are the model's — its JSON, or
|
|
6297
6604
|
// the tool's own formatter's reading of it — and do not change with the
|
|
@@ -6308,11 +6615,11 @@ class AparteClient {
|
|
|
6308
6615
|
signal,
|
|
6309
6616
|
target
|
|
6310
6617
|
});
|
|
6311
|
-
if (answer.action !== "accept") return { approved: false };
|
|
6618
|
+
if (answer.action !== "accept") return { approved: false, by: "person" };
|
|
6312
6619
|
const content = answer.content;
|
|
6313
6620
|
const instruction = content?.instruction?.trim();
|
|
6314
|
-
if (instruction) return { approved: false, instruction };
|
|
6315
|
-
return
|
|
6621
|
+
if (instruction) return { approved: false, instruction, by: "person" };
|
|
6622
|
+
return content?.option === "allow" ? { approved: true } : { approved: false, by: "person" };
|
|
6316
6623
|
}
|
|
6317
6624
|
/**
|
|
6318
6625
|
* Resolve the auth for a provider: `options.keyResolver` takes precedence,
|
|
@@ -7132,7 +7439,7 @@ ${content}
|
|
|
7132
7439
|
const ruling = gatedElsewhere ? this._config.getApprovalPolicy()?.(call, this._config.getTools().find((t) => t.name === call.name)) ?? { verdict: "ask" } : this._config.ruleOnToolCall(call);
|
|
7133
7440
|
if (ruling.verdict === "allow") return { approved: true };
|
|
7134
7441
|
if (ruling.verdict === "deny") {
|
|
7135
|
-
return { approved: false, reason: ruling.reason?.trim() || "Tool execution was refused by the approval policy." };
|
|
7442
|
+
return { approved: false, reason: ruling.reason?.trim() || "Tool execution was refused by the approval policy.", by: "policy" };
|
|
7136
7443
|
}
|
|
7137
7444
|
return this._askForApproval(call, sig, targetElement);
|
|
7138
7445
|
});
|
|
@@ -7150,7 +7457,7 @@ ${content}
|
|
|
7150
7457
|
const resolver = this._approvalResolverFor(targetElement, true);
|
|
7151
7458
|
const refuseTheRest = (after) => {
|
|
7152
7459
|
for (const rest of pending.slice(after + 1)) {
|
|
7153
|
-
outcomes[rest.toolCallId] = { kind: "decision", approved: false, reason: NOT_RUN_AFTER_A_REFUSAL };
|
|
7460
|
+
outcomes[rest.toolCallId] = { kind: "decision", approved: false, reason: NOT_RUN_AFTER_A_REFUSAL, by: "earlier-refusal" };
|
|
7154
7461
|
}
|
|
7155
7462
|
};
|
|
7156
7463
|
for (const [index, p] of pending.entries()) {
|
|
@@ -7367,7 +7674,7 @@ ${content}
|
|
|
7367
7674
|
id: `error-${uuid()}`,
|
|
7368
7675
|
type: "error",
|
|
7369
7676
|
content: error.message,
|
|
7370
|
-
|
|
7677
|
+
code: error.code
|
|
7371
7678
|
};
|
|
7372
7679
|
if (target.updateMessage) {
|
|
7373
7680
|
const rendered = target.getMessage?.(messageId)?.segments ?? target.getMessages?.()?.find((m) => m.id === messageId)?.segments ?? [];
|
|
@@ -7864,24 +8171,25 @@ export {
|
|
|
7864
8171
|
segmentDuration as a4,
|
|
7865
8172
|
segmentTiming as a5,
|
|
7866
8173
|
subscribeConfigChange as a6,
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
8174
|
+
toolRefusal as a7,
|
|
8175
|
+
unregisterSegmentRenderer as a8,
|
|
8176
|
+
uuid as a9,
|
|
8177
|
+
eventOrigin as aa,
|
|
8178
|
+
activeElementIn as ab,
|
|
8179
|
+
gateGlide as ac,
|
|
8180
|
+
renderingVersionOf as ad,
|
|
8181
|
+
mergeSegmentUpdate as ae,
|
|
8182
|
+
writeStreamedMarkdown as af,
|
|
8183
|
+
stampSegmentActivity as ag,
|
|
8184
|
+
stampSegmentOnInsert as ah,
|
|
8185
|
+
stampSegmentOnUpdate as ai,
|
|
8186
|
+
renumberSegments as aj,
|
|
8187
|
+
openSegmentIds as ak,
|
|
8188
|
+
isTerminalStatus as al,
|
|
8189
|
+
adoptMessageSegments as am,
|
|
8190
|
+
APARTE_DERIVED_SEGMENTS as an,
|
|
8191
|
+
chatBoundaryOf as ao,
|
|
8192
|
+
rootOf as ap,
|
|
7885
8193
|
APARTE_DEFAULT_BUBBLE_ACTIONS as b,
|
|
7886
8194
|
APARTE_DEFAULT_HOST_HANDLERS as c,
|
|
7887
8195
|
APARTE_DEFAULT_ICON_FALLBACKS as d,
|
|
@@ -7908,4 +8216,4 @@ export {
|
|
|
7908
8216
|
collectRendererStyles as y,
|
|
7909
8217
|
contentToText as z
|
|
7910
8218
|
};
|
|
7911
|
-
//# sourceMappingURL=dialog-triggers-
|
|
8219
|
+
//# sourceMappingURL=dialog-triggers-3G9TqG9g.js.map
|