clixad 0.0.1-beta.48 → 0.0.1-beta.49
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.
|
@@ -468,6 +468,13 @@ var MaintenanceError = class extends Error {
|
|
|
468
468
|
}
|
|
469
469
|
};
|
|
470
470
|
var MAINTENANCE = "maintenance";
|
|
471
|
+
var PromptTooLongError = class extends Error {
|
|
472
|
+
constructor(message) {
|
|
473
|
+
super(message);
|
|
474
|
+
this.name = "PromptTooLongError";
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
var PROMPT_TOO_LONG = "prompt_too_long";
|
|
471
478
|
async function refusal503(res, what) {
|
|
472
479
|
const text = await res.text().catch(() => "");
|
|
473
480
|
try {
|
|
@@ -537,7 +544,7 @@ async function refusal400(res, what) {
|
|
|
537
544
|
const text = await res.text().catch(() => "");
|
|
538
545
|
try {
|
|
539
546
|
const body = JSON.parse(text);
|
|
540
|
-
if (body.error ===
|
|
547
|
+
if (body.error === PROMPT_TOO_LONG && typeof body.message === "string") return new PromptTooLongError(body.message);
|
|
541
548
|
} catch {
|
|
542
549
|
}
|
|
543
550
|
const detail = safeDetail(text);
|
|
@@ -1482,6 +1489,7 @@ var GatewayClient = class {
|
|
|
1482
1489
|
}
|
|
1483
1490
|
if (premiumRefusal) throw premiumRefusal;
|
|
1484
1491
|
if (streamError && streamErrorType === MAINTENANCE) throw new MaintenanceError(streamError);
|
|
1492
|
+
if (streamError && streamErrorType === PROMPT_TOO_LONG) throw new PromptTooLongError(streamError);
|
|
1485
1493
|
if (streamError && streamErrorType && LIMIT_REFUSALS.has(streamErrorType)) {
|
|
1486
1494
|
throw new LimitRefusalError(streamError, streamErrorType, spendable ? { figures: spendable, balance } : void 0);
|
|
1487
1495
|
}
|
|
@@ -6830,6 +6838,24 @@ var COMPACT_THRESHOLD = 0.75;
|
|
|
6830
6838
|
function shouldCompact(messages, contextTokens, threshold = COMPACT_THRESHOLD) {
|
|
6831
6839
|
return conversationTokens(messages) > contextTokens * threshold;
|
|
6832
6840
|
}
|
|
6841
|
+
function compactBudget(contextTokens) {
|
|
6842
|
+
return Math.floor(contextTokens * COMPACT_THRESHOLD);
|
|
6843
|
+
}
|
|
6844
|
+
function messageWeight(message) {
|
|
6845
|
+
let images = 0;
|
|
6846
|
+
const content = Array.isArray(message.content) ? message.content.map((part) => {
|
|
6847
|
+
if (part.type !== "image_url") return part;
|
|
6848
|
+
images++;
|
|
6849
|
+
return { ...part, image_url: { url: "" } };
|
|
6850
|
+
}) : message.content;
|
|
6851
|
+
const bytes = Buffer.byteLength(JSON.stringify({ ...message, content }));
|
|
6852
|
+
return Math.ceil(bytes / 4) + images * IMAGE_TOKEN_ESTIMATE;
|
|
6853
|
+
}
|
|
6854
|
+
function requestTokens(messages) {
|
|
6855
|
+
let total = 0;
|
|
6856
|
+
for (const m of messages) total += messageWeight(m);
|
|
6857
|
+
return total;
|
|
6858
|
+
}
|
|
6833
6859
|
function cutPoint(messages, keepTurns = 2) {
|
|
6834
6860
|
let seen = 0;
|
|
6835
6861
|
for (let i = messages.length - 1; i > 0; i--) {
|
|
@@ -6839,37 +6865,187 @@ function cutPoint(messages, keepTurns = 2) {
|
|
|
6839
6865
|
}
|
|
6840
6866
|
return 0;
|
|
6841
6867
|
}
|
|
6868
|
+
function safeCuts(messages) {
|
|
6869
|
+
const open = /* @__PURE__ */ new Set();
|
|
6870
|
+
const cuts = [];
|
|
6871
|
+
for (let i = 1; i <= messages.length; i++) {
|
|
6872
|
+
const prev = messages[i - 1];
|
|
6873
|
+
if (prev.role === "assistant") for (const call of prev.tool_calls ?? []) open.add(call.id);
|
|
6874
|
+
if (prev.role === "tool" && prev.tool_call_id) open.delete(prev.tool_call_id);
|
|
6875
|
+
if (open.size === 0 && (i === messages.length || messages[i].role !== "tool")) cuts.push(i);
|
|
6876
|
+
}
|
|
6877
|
+
return cuts;
|
|
6878
|
+
}
|
|
6842
6879
|
var SUMMARY_LEAD = "Summary of the earlier conversation:\n\n";
|
|
6843
6880
|
var SUMMARY_ACK = "Understood \u2014 continuing from that summary.";
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6881
|
+
function withSummary(summary, rest) {
|
|
6882
|
+
const lead = { role: "user", content: `${SUMMARY_LEAD}${summary}` };
|
|
6883
|
+
return rest[0]?.role === "assistant" ? [lead, ...rest] : [lead, { role: "assistant", content: SUMMARY_ACK }, ...rest];
|
|
6884
|
+
}
|
|
6885
|
+
function summaryReserve(budget) {
|
|
6886
|
+
return Math.min(1e4, Math.max(1e3, Math.floor(budget / 6)));
|
|
6887
|
+
}
|
|
6888
|
+
function summaryPrompt(words) {
|
|
6889
|
+
return `Summarise the conversation above for your future self, which will continue the work without seeing it. Cover: what the user asked for, what was already changed (files and the reasoning), what was learned about the codebase, and what is still open. Be specific about file paths and names. If it opens with a summary of earlier messages, carry what that summary says forward. At most ${words.toLocaleString("en-US")} words. No preamble.`;
|
|
6890
|
+
}
|
|
6891
|
+
function planCompaction(messages, contextTokens, keepTurns = 2) {
|
|
6892
|
+
if (messages.length < 2) return void 0;
|
|
6893
|
+
const budget = compactBudget(contextTokens);
|
|
6894
|
+
const suffix = new Array(messages.length + 1).fill(0);
|
|
6895
|
+
for (let i = messages.length - 1; i >= 0; i--) suffix[i] = suffix[i + 1] + messageWeight(messages[i]);
|
|
6896
|
+
const total = suffix[0];
|
|
6897
|
+
const tail = (from) => suffix[from];
|
|
6898
|
+
const fixed = total - suffix[1] + summaryReserve(budget) + messageWeight({ role: "assistant", content: SUMMARY_ACK });
|
|
6899
|
+
const preferred = cutPoint(messages, keepTurns);
|
|
6900
|
+
const over = total > budget;
|
|
6901
|
+
if (!over) return preferred > 1 ? { cut: preferred, fits: fixed + tail(preferred) <= budget, over } : void 0;
|
|
6902
|
+
const cuts = safeCuts(messages).filter((c) => c >= Math.max(preferred, 2));
|
|
6903
|
+
if (cuts.length === 0) return void 0;
|
|
6904
|
+
const keep = Math.min(budget - fixed, Math.floor(budget / 2));
|
|
6905
|
+
const cut2 = cuts.find((c) => tail(c) <= keep) ?? cuts.at(-1);
|
|
6906
|
+
return { cut: cut2, fits: fixed + tail(cut2) <= budget, over };
|
|
6907
|
+
}
|
|
6908
|
+
function cutNote(left) {
|
|
6909
|
+
return `
|
|
6910
|
+
\u2026 [${left.toLocaleString("en-US")} more characters left out here]`;
|
|
6911
|
+
}
|
|
6912
|
+
function capText(text, limit) {
|
|
6913
|
+
return text.length > limit ? `${text.slice(0, limit)}${cutNote(text.length - limit)}` : text;
|
|
6914
|
+
}
|
|
6915
|
+
function capArguments(json, limit) {
|
|
6916
|
+
const walk2 = (v) => {
|
|
6917
|
+
if (typeof v === "string") return capText(v, limit);
|
|
6918
|
+
if (Array.isArray(v)) return v.map(walk2);
|
|
6919
|
+
if (v && typeof v === "object") return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, walk2(x)]));
|
|
6920
|
+
return v;
|
|
6921
|
+
};
|
|
6922
|
+
try {
|
|
6923
|
+
return JSON.stringify(walk2(JSON.parse(json)));
|
|
6924
|
+
} catch {
|
|
6925
|
+
return json.length > limit ? JSON.stringify({ arguments: capText(json, limit) }) : json;
|
|
6926
|
+
}
|
|
6927
|
+
}
|
|
6928
|
+
function capMessage(message, limit) {
|
|
6929
|
+
const content = typeof message.content === "string" ? capText(message.content, limit) : Array.isArray(message.content) ? message.content.map((part) => part.type === "text" ? { ...part, text: capText(part.text, limit) } : part) : message.content;
|
|
6930
|
+
const out = { ...message, content };
|
|
6931
|
+
if (message.tool_calls) {
|
|
6932
|
+
out.tool_calls = message.tool_calls.map((c) => ({
|
|
6933
|
+
...c,
|
|
6934
|
+
function: { ...c.function, arguments: capArguments(c.function.arguments, limit) }
|
|
6935
|
+
}));
|
|
6936
|
+
}
|
|
6937
|
+
return out;
|
|
6938
|
+
}
|
|
6939
|
+
function withoutImages2(message) {
|
|
6940
|
+
if (!Array.isArray(message.content)) return message;
|
|
6941
|
+
return {
|
|
6942
|
+
...message,
|
|
6943
|
+
content: message.content.map(
|
|
6944
|
+
(part) => part.type === "image_url" ? { type: "text", text: "[an image, not shown here]" } : part
|
|
6945
|
+
)
|
|
6946
|
+
};
|
|
6947
|
+
}
|
|
6948
|
+
function longestText(messages) {
|
|
6949
|
+
let longest = 0;
|
|
6950
|
+
for (const m of messages) {
|
|
6951
|
+
longest = Math.max(longest, messageText(m).length);
|
|
6952
|
+
for (const c of m.tool_calls ?? []) longest = Math.max(longest, c.function.arguments.length);
|
|
6953
|
+
}
|
|
6954
|
+
return longest;
|
|
6955
|
+
}
|
|
6956
|
+
function shrinkToFit(messages, room) {
|
|
6957
|
+
if (requestTokens(messages) <= room) return messages;
|
|
6958
|
+
const plain2 = messages.map(withoutImages2);
|
|
6959
|
+
if (requestTokens(plain2) <= room) return plain2;
|
|
6960
|
+
const capped = (limit) => plain2.map((m) => capMessage(m, limit));
|
|
6961
|
+
let lo = 0;
|
|
6962
|
+
let hi = longestText(plain2);
|
|
6963
|
+
while (lo < hi) {
|
|
6964
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
6965
|
+
if (requestTokens(capped(mid)) <= room) lo = mid;
|
|
6966
|
+
else hi = mid - 1;
|
|
6967
|
+
}
|
|
6968
|
+
return capped(lo);
|
|
6969
|
+
}
|
|
6970
|
+
async function compact(client, model, messages, contextTokens, opts = {}) {
|
|
6971
|
+
const plan = planCompaction(messages, contextTokens, opts.keepTurns ?? 2);
|
|
6972
|
+
if (!plan) return void 0;
|
|
6973
|
+
const { cut: cut2 } = plan;
|
|
6974
|
+
const budget = compactBudget(contextTokens);
|
|
6975
|
+
const system = messages[0];
|
|
6976
|
+
const prompt = { role: "user", content: summaryPrompt(Math.floor(summaryReserve(budget) / 2)) };
|
|
6977
|
+
const bounds = [1, ...safeCuts(messages).filter((c) => c > 1 && c < cut2), cut2];
|
|
6978
|
+
const rounds = [];
|
|
6979
|
+
for (let i = 0; i + 1 < bounds.length; i++) rounds.push(messages.slice(bounds[i], bounds[i + 1]));
|
|
6848
6980
|
const before = conversationTokens(messages);
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
)
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6981
|
+
let summary;
|
|
6982
|
+
let creditsCharged = 0;
|
|
6983
|
+
let balance = 0;
|
|
6984
|
+
let spendable;
|
|
6985
|
+
let calls = 0;
|
|
6986
|
+
let next = 0;
|
|
6987
|
+
while (next < rounds.length) {
|
|
6988
|
+
const room = Math.max(
|
|
6989
|
+
1e3,
|
|
6990
|
+
budget - requestTokens([system, prompt]) - (summary === void 0 ? 0 : requestTokens(withSummary(summary, [])))
|
|
6991
|
+
);
|
|
6992
|
+
let chunk = shrinkToFit(rounds[next++], room);
|
|
6993
|
+
while (next < rounds.length) {
|
|
6994
|
+
const round = rounds[next];
|
|
6995
|
+
const left = room - requestTokens(chunk);
|
|
6996
|
+
if (requestTokens(round) <= left) {
|
|
6997
|
+
chunk = [...chunk, ...round];
|
|
6998
|
+
next++;
|
|
6999
|
+
continue;
|
|
7000
|
+
}
|
|
7001
|
+
if (requestTokens(round) > room && left >= room / 2) {
|
|
7002
|
+
chunk = [...chunk, ...shrinkToFit(round, left)];
|
|
7003
|
+
next++;
|
|
7004
|
+
}
|
|
7005
|
+
break;
|
|
7006
|
+
}
|
|
7007
|
+
const request = [system, ...summary === void 0 ? chunk : withSummary(summary, chunk), prompt];
|
|
7008
|
+
let text = "";
|
|
7009
|
+
for (let attempt = 0; attempt < 2 && !text; attempt++) {
|
|
7010
|
+
const turn = await client.chatStream(request, model, { signal: opts.signal, effort: opts.effort });
|
|
7011
|
+
calls++;
|
|
7012
|
+
creditsCharged += turn.creditsCharged;
|
|
7013
|
+
balance = turn.balance;
|
|
7014
|
+
spendable = turn.spendable;
|
|
7015
|
+
text = messageText(turn.message).trim();
|
|
7016
|
+
}
|
|
7017
|
+
if (!text) throw new Error("the model returned an empty summary, so the conversation was left as it was");
|
|
7018
|
+
summary = text;
|
|
7019
|
+
}
|
|
7020
|
+
const tailMessages = messages.slice(cut2);
|
|
7021
|
+
let final = summary ?? "";
|
|
7022
|
+
const excess = requestTokens([system, ...withSummary(final, tailMessages)]) - budget;
|
|
7023
|
+
if (excess > 0) {
|
|
7024
|
+
const lead = { role: "user", content: final };
|
|
7025
|
+
final = messageText(shrinkToFit([lead], Math.max(0, messageWeight(lead) - excess - 1))[0]);
|
|
7026
|
+
}
|
|
7027
|
+
const compacted = [system, ...withSummary(final, tailMessages)];
|
|
6863
7028
|
return {
|
|
6864
7029
|
messages: compacted,
|
|
6865
|
-
summary,
|
|
6866
|
-
creditsCharged
|
|
6867
|
-
balance
|
|
6868
|
-
...
|
|
7030
|
+
summary: final,
|
|
7031
|
+
creditsCharged,
|
|
7032
|
+
balance,
|
|
7033
|
+
...spendable ? { spendable } : {},
|
|
6869
7034
|
before,
|
|
6870
|
-
after: conversationTokens(compacted)
|
|
7035
|
+
after: conversationTokens(compacted),
|
|
7036
|
+
calls
|
|
6871
7037
|
};
|
|
6872
7038
|
}
|
|
7039
|
+
var COMPACT_ADVICE = "The work done so far is kept. Run /compact to summarise the conversation, this task's steps included, then send a message to carry on. /clear starts over, and /model switches to a model that takes longer conversations.";
|
|
7040
|
+
function withCompactAdvice(refusal) {
|
|
7041
|
+
const lines = refusal.split("\n");
|
|
7042
|
+
if (lines.length === 3) return [lines[0], COMPACT_ADVICE, lines[2]].join("\n");
|
|
7043
|
+
return [...lines, COMPACT_ADVICE].join("\n");
|
|
7044
|
+
}
|
|
7045
|
+
function compactionHelps(messages, contextTokens) {
|
|
7046
|
+
const plan = planCompaction(messages, contextTokens);
|
|
7047
|
+
return plan !== void 0 && plan.over && plan.fits;
|
|
7048
|
+
}
|
|
6873
7049
|
|
|
6874
7050
|
// src/version.ts
|
|
6875
7051
|
import { readFileSync as readFileSync6 } from "node:fs";
|
|
@@ -6979,6 +7155,7 @@ export {
|
|
|
6979
7155
|
PremiumLockedError,
|
|
6980
7156
|
LimitRefusalError,
|
|
6981
7157
|
MaintenanceError,
|
|
7158
|
+
PromptTooLongError,
|
|
6982
7159
|
shortfallHeadline,
|
|
6983
7160
|
AuthError,
|
|
6984
7161
|
GatewayClient,
|
|
@@ -7063,6 +7240,8 @@ export {
|
|
|
7063
7240
|
SUMMARY_LEAD,
|
|
7064
7241
|
SUMMARY_ACK,
|
|
7065
7242
|
compact,
|
|
7243
|
+
withCompactAdvice,
|
|
7244
|
+
compactionHelps,
|
|
7066
7245
|
VERSION,
|
|
7067
7246
|
VERSION_LABEL,
|
|
7068
7247
|
isNewerVersion,
|
package/dist/clixad.mjs
CHANGED
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
takeConfigNotices,
|
|
55
55
|
toolSchemaFor,
|
|
56
56
|
usd
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-E7SAKAVQ.mjs";
|
|
58
58
|
|
|
59
59
|
// src/tui/load.ts
|
|
60
60
|
import * as nodeModule from "node:module";
|
|
@@ -133,7 +133,7 @@ async function withProductionReact(load) {
|
|
|
133
133
|
}
|
|
134
134
|
function loadTui() {
|
|
135
135
|
boundInkCaches();
|
|
136
|
-
return withProductionReact(() => import("./tui-
|
|
136
|
+
return withProductionReact(() => import("./tui-2H2XRALC.mjs"));
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
// src/title.ts
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
NO_SEARCH_INSTRUCTION,
|
|
19
19
|
PaywallError,
|
|
20
20
|
PremiumLockedError,
|
|
21
|
+
PromptTooLongError,
|
|
21
22
|
SPINNER_TICKS_PER_FRAME,
|
|
22
23
|
SUMMARY_ACK,
|
|
23
24
|
SUMMARY_LEAD,
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
colorEnabled,
|
|
36
37
|
compact,
|
|
37
38
|
compactBanner,
|
|
39
|
+
compactionHelps,
|
|
38
40
|
contextTokensFor,
|
|
39
41
|
conversationTokens,
|
|
40
42
|
createPermit,
|
|
@@ -104,8 +106,9 @@ import {
|
|
|
104
106
|
usd,
|
|
105
107
|
visibleLength,
|
|
106
108
|
webSearchCostLine,
|
|
107
|
-
welcomeBanner
|
|
108
|
-
|
|
109
|
+
welcomeBanner,
|
|
110
|
+
withCompactAdvice
|
|
111
|
+
} from "./chunk-E7SAKAVQ.mjs";
|
|
109
112
|
|
|
110
113
|
// src/tui/index.tsx
|
|
111
114
|
import "react";
|
|
@@ -2738,10 +2741,13 @@ function App({
|
|
|
2738
2741
|
try {
|
|
2739
2742
|
const window = contextWindow(model2);
|
|
2740
2743
|
if (shouldCompact([{ role: "system", content: contextRef.current.systemPrompt }, ...history], window)) {
|
|
2741
|
-
const res = await compact(
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2744
|
+
const res = await compact(
|
|
2745
|
+
client,
|
|
2746
|
+
model2,
|
|
2747
|
+
[{ role: "system", content: contextRef.current.systemPrompt }, ...history],
|
|
2748
|
+
window,
|
|
2749
|
+
{ signal: ac.signal, effort: savedEffort(config, model2) }
|
|
2750
|
+
);
|
|
2745
2751
|
if (res) {
|
|
2746
2752
|
history = res.messages.slice(1);
|
|
2747
2753
|
setMessages(history);
|
|
@@ -2899,6 +2905,7 @@ ${preferencesText}` : ""),
|
|
|
2899
2905
|
setMessages(kept);
|
|
2900
2906
|
persist(kept);
|
|
2901
2907
|
}
|
|
2908
|
+
const compactAdvised = err instanceof PromptTooLongError && kept !== void 0 && compactionHelps([{ role: "system", content: contextRef.current.systemPrompt }, ...kept], contextWindow(model2));
|
|
2902
2909
|
if ((err instanceof LimitRefusalError || err instanceof PremiumLockedError) && err.spendable) {
|
|
2903
2910
|
balanceSeqRef.current++;
|
|
2904
2911
|
takeSpendable(err.spendable.figures, err.spendable.balance);
|
|
@@ -2955,10 +2962,12 @@ ${preferencesText}` : ""),
|
|
|
2955
2962
|
push({ kind: "notice", tone: "error", text: NOT_SIGNED_IN });
|
|
2956
2963
|
} else if (err instanceof MaintenanceError) {
|
|
2957
2964
|
push({ kind: "notice", tone: "warn", text: ` ${errorNotice(err.message, void 0, " ")}` });
|
|
2965
|
+
} else if (compactAdvised) {
|
|
2966
|
+
push({ kind: "notice", tone: "error", text: ` error: ${errorNotice(withCompactAdvice(err.message), void 0, " ")}` });
|
|
2958
2967
|
} else {
|
|
2959
2968
|
push({ kind: "notice", tone: "error", text: ` error: ${errorNotice(err.message, void 0, " ")}` });
|
|
2960
2969
|
}
|
|
2961
|
-
if (kept && !(err instanceof PaywallError) && !(err instanceof PremiumLockedError)) {
|
|
2970
|
+
if (kept && !(err instanceof PaywallError) && !(err instanceof PremiumLockedError) && !compactAdvised) {
|
|
2962
2971
|
push({ kind: "notice", text: KEPT_AFTER_ERROR });
|
|
2963
2972
|
}
|
|
2964
2973
|
} finally {
|
|
@@ -3399,15 +3408,17 @@ ${preferencesText}` : ""),
|
|
|
3399
3408
|
push({ kind: "notice", text: ` mode \u2192 ${MODE_LABEL[permRef.current.mode]}` });
|
|
3400
3409
|
return;
|
|
3401
3410
|
}
|
|
3402
|
-
//
|
|
3403
|
-
//
|
|
3404
|
-
//
|
|
3411
|
+
// Model calls like any other, so esc has to stop them like any other.
|
|
3412
|
+
// Stopping leaves the conversation as it was, since the summary only
|
|
3413
|
+
// replaces it once every call has come back whole; a summary that took
|
|
3414
|
+
// several calls has been charged for the ones already finished.
|
|
3405
3415
|
case "compact":
|
|
3406
3416
|
await runBusy(COMPACTING, async (signal) => {
|
|
3407
3417
|
const res = await compact(
|
|
3408
3418
|
client,
|
|
3409
3419
|
model,
|
|
3410
3420
|
[{ role: "system", content: contextRef.current.systemPrompt }, ...messagesRef.current],
|
|
3421
|
+
contextWindow(model),
|
|
3411
3422
|
{ signal, effort: savedEffort(config, model) }
|
|
3412
3423
|
);
|
|
3413
3424
|
if (!res) return void push({ kind: "notice", text: " nothing to compact yet" });
|