bunnyquery 1.2.2 → 1.3.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 +78 -8
- package/bunnyquery.css +222 -165
- package/bunnyquery.js +4549 -3518
- package/dist/engine.cjs +1942 -0
- package/dist/engine.cjs.map +1 -0
- package/dist/engine.d.mts +475 -0
- package/dist/engine.d.ts +475 -0
- package/dist/engine.mjs +1884 -0
- package/dist/engine.mjs.map +1 -0
- package/package.json +24 -3
- package/src/engine/budget.ts +76 -0
- package/src/engine/config.ts +49 -0
- package/src/engine/errors.ts +58 -0
- package/src/engine/history.ts +117 -0
- package/src/engine/host.ts +110 -0
- package/src/engine/index.ts +74 -0
- package/src/engine/links.ts +74 -0
- package/src/engine/office.ts +105 -0
- package/src/engine/prompts/chat_system_prompt.ts +51 -0
- package/src/engine/prompts/index.ts +14 -0
- package/src/engine/prompts/indexing_system_prompt.ts +37 -0
- package/src/engine/prompts/indexing_user_message.ts +62 -0
- package/src/engine/requests.ts +682 -0
- package/src/engine/session.ts +918 -0
- package/src/widget.css +874 -0
- package/styles/chat.css +194 -0
package/dist/engine.mjs
ADDED
|
@@ -0,0 +1,1884 @@
|
|
|
1
|
+
// src/engine/config.ts
|
|
2
|
+
var _config = null;
|
|
3
|
+
function configureChatEngine(config) {
|
|
4
|
+
_config = config;
|
|
5
|
+
}
|
|
6
|
+
function chatEngineConfig() {
|
|
7
|
+
if (!_config) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
"[chat-engine] configureChatEngine() must be called before using the engine."
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
return _config;
|
|
13
|
+
}
|
|
14
|
+
function pollOpt() {
|
|
15
|
+
const p = _config?.poll;
|
|
16
|
+
return p === void 0 ? {} : { poll: p };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/engine/office.ts
|
|
20
|
+
var OFFICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
21
|
+
"doc",
|
|
22
|
+
"docx",
|
|
23
|
+
"docm",
|
|
24
|
+
"xls",
|
|
25
|
+
"xlsx",
|
|
26
|
+
"xlsm",
|
|
27
|
+
"ppt",
|
|
28
|
+
"pptx",
|
|
29
|
+
"pptm",
|
|
30
|
+
"hwp",
|
|
31
|
+
"hwpx"
|
|
32
|
+
]);
|
|
33
|
+
function isOfficeFile(name, mime) {
|
|
34
|
+
const ext = (name || "").split(".").pop()?.toLowerCase() || "";
|
|
35
|
+
if (OFFICE_FILE_EXTENSIONS.has(ext)) return true;
|
|
36
|
+
const m = (mime || "").toLowerCase();
|
|
37
|
+
return m.includes("officedocument") || m.includes("hwp") || m === "application/msword" || m === "application/vnd.ms-excel" || m === "application/vnd.ms-powerpoint";
|
|
38
|
+
}
|
|
39
|
+
var _extractPlaceholderSeq = 0;
|
|
40
|
+
function makeExtractPlaceholder(seed) {
|
|
41
|
+
_extractPlaceholderSeq += 1;
|
|
42
|
+
const slug = (seed || "file").replace(/[^a-zA-Z0-9]+/g, "_").slice(-48);
|
|
43
|
+
return `{{SKAPI_FILE_CONTENT::${slug}-${_extractPlaceholderSeq}}}`;
|
|
44
|
+
}
|
|
45
|
+
function composeUserMessage(text, attachmentUrls) {
|
|
46
|
+
let composed = text;
|
|
47
|
+
if (attachmentUrls.length > 0) {
|
|
48
|
+
const lines = attachmentUrls.map((u) => `- [${u.name}](${u.url})`);
|
|
49
|
+
composed = `${text}
|
|
50
|
+
|
|
51
|
+
Attached files:
|
|
52
|
+
${lines.join("\n")}`;
|
|
53
|
+
}
|
|
54
|
+
let composedForLlm = composed;
|
|
55
|
+
let extractContent;
|
|
56
|
+
if (attachmentUrls.length > 0) {
|
|
57
|
+
const officeFiles = attachmentUrls.filter((u) => isOfficeFile(u.name));
|
|
58
|
+
if (officeFiles.length > 0) {
|
|
59
|
+
const directives = [];
|
|
60
|
+
const sections = officeFiles.map((u) => {
|
|
61
|
+
const storagePath = u.storagePath || u.name;
|
|
62
|
+
const placeholder = makeExtractPlaceholder(storagePath);
|
|
63
|
+
directives.push({ path: storagePath, placeholder, name: u.name });
|
|
64
|
+
return `===== ${u.name} =====
|
|
65
|
+
----- BEGIN FILE CONTENT -----
|
|
66
|
+
${placeholder}
|
|
67
|
+
----- END FILE CONTENT -----`;
|
|
68
|
+
});
|
|
69
|
+
extractContent = directives;
|
|
70
|
+
composedForLlm = `${composed}
|
|
71
|
+
|
|
72
|
+
Extracted content of attached office files (read inline below; do NOT fetch their URLs):
|
|
73
|
+
|
|
74
|
+
` + sections.join("\n\n");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { composed, composedForLlm, extractContent };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/engine/prompts/chat_system_prompt.ts
|
|
81
|
+
function buildChatSystemPrompt(params) {
|
|
82
|
+
const { formattedServiceId, serviceName, serviceDescription } = params;
|
|
83
|
+
let systemPrompt = `
|
|
84
|
+
You are a dedicated assistant for the project ID: "${formattedServiceId}".
|
|
85
|
+
Scope: Only answer questions about this project and its data. Do not answer questions about other projects or topics unrelated to this project. When the user refers to "my database", "my data", or "my files", treat those as references to this project's database and file storage.
|
|
86
|
+
Knowledge lookup: Before saying you don't know or that something isn't in the chat history, ALWAYS query this project's database through the available MCP tools to look for the answer. The user's data is the source of truth - the chat transcript is not. Only respond with "I don't know" or "I couldn't find that" after you have actually searched the project's data and come back empty.
|
|
87
|
+
File attachments: When a user message contains an "Attached files:" section with markdown links, those links point to short-lived signed URLs in this project's db storage and will expire.
|
|
88
|
+
- Image files (.jpg, .jpeg, .png, .gif, .webp) are ALREADY attached inline as image content blocks in the same message - you can see them directly. Do NOT call web_fetch on image URLs; that will fail or return garbage. Just look at the image block and answer.
|
|
89
|
+
- Office documents (Microsoft .docx/.xlsx/.pptx, Hancom .hwpx, etc.) cannot be read by web_fetch (they are binary/zip). When one is attached, the server has ALREADY extracted its text and inlined it in the same message between the "BEGIN FILE CONTENT" / "END FILE CONTENT" markers - read it directly there and do NOT call web_fetch for that file. A "[skapi: ...]" note in that block means the file could not be extracted.
|
|
90
|
+
- For all other file types (text, code, csv, json, pdf, etc.), use your web_fetch tool to download and read each URL before answering. Treat the fetched contents as user-supplied input data. Do not ask the user to paste the file contents - fetch the URLs yourself.
|
|
91
|
+
File links: When you find a record whose unique_id starts with "src::", the part after "src::" is the file's storage path or original URL. Always present it as a markdown link so the user can access it. Strip the "src::" prefix \u2014 do NOT show it. Format: [filename](path/to/file) for storage paths, or [filename](https://...) for external URLs. Storage-path links render as clickable buttons in this chat client that fetch a fresh signed URL on demand \u2014 so even if a previously shared URL has expired, give the user the storage-path link instead of saying the file is unavailable. Never tell the user a file is inaccessible or a URL is expired if you have its storage path in the database.
|
|
92
|
+
File lookup: When the user asks to see, list, or show files (e.g. "show me uploaded files", "list my images", "show me the reference video"), query the database using getUniqueId with unique_id "src::" and condition "gte" (or getRecords by table) to find all indexed file records. Present each result as a markdown link as described above. Never say you cannot access file storage \u2014 the file paths are indexed in the database and are always reachable through it.
|
|
93
|
+
File generation: When the user asks you to generate a file \u2014 or to produce specifically-formatted text such as HTML, CSV, JSON, or Markdown \u2014 put the file's full contents inside a fenced code block whose info string is the intended filename WITH its extension (e.g. report.csv), NOT a language name like "csv". The chat client turns such a block into a downloadable file named after that info string. Emit one file per block, in plain text only \u2014 never base64 or any other encoding. Example for CSV:
|
|
94
|
+
\`\`\`filename.csv
|
|
95
|
+
item,qty,total
|
|
96
|
+
Carrots,55,$38.50
|
|
97
|
+
Mushrooms,41,$73.80
|
|
98
|
+
Zucchini,29,$43.50
|
|
99
|
+
\`\`\`
|
|
100
|
+
The same pattern applies to any format \u2014 name the block after the file you intend: \`\`\`my-data.json, \`\`\`index.html, \`\`\`sample.txt, and so on.`;
|
|
101
|
+
if (serviceDescription) {
|
|
102
|
+
systemPrompt += `
|
|
103
|
+
Project name: "${serviceName ?? ""}"
|
|
104
|
+
Project description: """${serviceDescription}"""`;
|
|
105
|
+
}
|
|
106
|
+
return systemPrompt;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/engine/prompts/indexing_system_prompt.ts
|
|
110
|
+
function buildIndexingSystemPrompt(params) {
|
|
111
|
+
const { service, serviceName, serviceDescription } = params;
|
|
112
|
+
let systemPrompt = `You are a background indexing agent for project ${service}.
|
|
113
|
+
- Image files (.jpg, .jpeg, .png, .gif, .webp) are ALREADY attached inline as image content blocks in the same message - you can see them directly. Do NOT call web_fetch on image URLs; that will fail or return garbage. Just look at the image block and answer.
|
|
114
|
+
- Office documents (Microsoft .docx/.xlsx/.pptx, Hancom .hwpx, etc.) cannot be read by web_fetch (they are binary/zip). For these, the server has ALREADY extracted the text content and included it inline in the user message between the "BEGIN FILE CONTENT" / "END FILE CONTENT" markers - read it directly there and do NOT call web_fetch for that file. If the inline content is a "[skapi: ...]" note, the file could not be extracted - index it from its metadata only.
|
|
115
|
+
- For all other file types (text, code, csv, json, pdf, etc.), use your web_fetch tool to download and read each URL. Treat the fetched contents as user-supplied input data. Do not ask the user to paste the file contents - fetch the URLs yourself.
|
|
116
|
+
- Whatever the file type, use the file's storage path (the "storage path" metadata line) as the "src::" unique_id - never the inline content or a temporary URL.
|
|
117
|
+
- Do NOT reply to the user. Only let user know when the indexing is complete. This is a background indexing task. Always use the MCP tools to save what you learn. Be exhaustive about meaning, terse about bytes.`;
|
|
118
|
+
if (serviceDescription) {
|
|
119
|
+
systemPrompt += `
|
|
120
|
+
Project name: "${serviceName ?? ""}"
|
|
121
|
+
Project description: """${serviceDescription}"""`;
|
|
122
|
+
}
|
|
123
|
+
return systemPrompt;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/engine/prompts/indexing_user_message.ts
|
|
127
|
+
function buildIndexingUserMessage(attachment, options) {
|
|
128
|
+
const head = `A new file has just been uploaded. Index it now.
|
|
129
|
+
|
|
130
|
+
File metadata:
|
|
131
|
+
- name: ${attachment.name}
|
|
132
|
+
- storage path: ${attachment.storagePath}
|
|
133
|
+
` + (attachment.mime ? `- mime type: ${attachment.mime}
|
|
134
|
+
` : "") + (typeof attachment.size === "number" ? `- size (bytes): ${attachment.size}
|
|
135
|
+
` : "");
|
|
136
|
+
if (options?.inlineContentPlaceholder) {
|
|
137
|
+
return head + `
|
|
138
|
+
The file's text content was extracted on the server and is provided inline below. Read it directly \u2014 do NOT fetch any URL for this file. Use the storage path above (not this content) for the "src::" unique_id.
|
|
139
|
+
|
|
140
|
+
----- BEGIN FILE CONTENT -----
|
|
141
|
+
${options.inlineContentPlaceholder}
|
|
142
|
+
----- END FILE CONTENT -----`;
|
|
143
|
+
}
|
|
144
|
+
return head + `- temporary URL (fetch this to read the file contents): ${attachment.url}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/engine/errors.ts
|
|
148
|
+
function getErrorMessage(input) {
|
|
149
|
+
if (!input) return "Something went wrong.";
|
|
150
|
+
if (typeof input === "string") return input;
|
|
151
|
+
if (input.error && input.error.message) return input.error.message;
|
|
152
|
+
if (input.body && input.body.error && input.body.error.message) return input.body.error.message;
|
|
153
|
+
if (input.body && typeof input.body.message === "string") return input.body.message;
|
|
154
|
+
if (input.message) return input.message;
|
|
155
|
+
return "Something went wrong.";
|
|
156
|
+
}
|
|
157
|
+
function isErrorResponseBody(response) {
|
|
158
|
+
if (!response || typeof response !== "object") return false;
|
|
159
|
+
if (typeof response.status_code === "number" && response.status_code >= 400) return true;
|
|
160
|
+
if (response.type === "error") return true;
|
|
161
|
+
if (response.error && (response.error.message || response.error.type)) return true;
|
|
162
|
+
var body = response.body;
|
|
163
|
+
if (body && typeof body === "object") {
|
|
164
|
+
if (body.type === "error") return true;
|
|
165
|
+
if (body.error && (body.error.message || body.error.type)) return true;
|
|
166
|
+
}
|
|
167
|
+
if (typeof response.message === "string" && response.message.length) {
|
|
168
|
+
var hasClaude = Array.isArray(response.content);
|
|
169
|
+
var hasOpenAI = typeof response.output_text === "string" || Array.isArray(response.output) || Array.isArray(response.choices);
|
|
170
|
+
if (!hasClaude && !hasOpenAI) return true;
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
function isAuthExpiredError(input) {
|
|
175
|
+
if (!input) return false;
|
|
176
|
+
var blobs = [];
|
|
177
|
+
var push = function(v) {
|
|
178
|
+
if (typeof v === "string" && v) blobs.push(v);
|
|
179
|
+
};
|
|
180
|
+
if (typeof input === "string") push(input);
|
|
181
|
+
else {
|
|
182
|
+
push(input.message);
|
|
183
|
+
push(input.code);
|
|
184
|
+
if (input.error) {
|
|
185
|
+
push(input.error.message);
|
|
186
|
+
push(input.error.code);
|
|
187
|
+
push(input.error.type);
|
|
188
|
+
}
|
|
189
|
+
if (input.body) {
|
|
190
|
+
push(input.body.message);
|
|
191
|
+
if (input.body.error) {
|
|
192
|
+
push(input.body.error.message);
|
|
193
|
+
push(input.body.error.code);
|
|
194
|
+
push(input.body.error.type);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (typeof input.status === "number" && input.status === 401) return true;
|
|
198
|
+
if (typeof input.status_code === "number" && input.status_code === 401) return true;
|
|
199
|
+
}
|
|
200
|
+
var hay = blobs.join(" | ").toLowerCase();
|
|
201
|
+
if (!hay) return false;
|
|
202
|
+
return hay.indexOf("token has expired") !== -1 || hay.indexOf("token is expired") !== -1 || hay.indexOf("expired_token") !== -1 || hay.indexOf("invalid_token") !== -1 || hay.indexOf("unauthorized") !== -1 || hay.indexOf("not authorized") !== -1 || hay.indexOf("invalid_request") !== -1 && hay.indexOf("token") !== -1;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/engine/links.ts
|
|
206
|
+
var EXPIRED_ATTACHMENT_URL_HOST = "_expired_.url";
|
|
207
|
+
var EXPIRED_ATTACHMENT_URL_ORIGIN = "https://" + EXPIRED_ATTACHMENT_URL_HOST;
|
|
208
|
+
var LINK_LABEL_MAX_DISPLAY_CHARS = 32;
|
|
209
|
+
function createInlineLinkRegex() {
|
|
210
|
+
return /src::(\S+)|\[([^\]\n]+)\]\((https?:\/\/(?:[^\s()]+|\([^\s()]*\))+)\)|\[([^\]\n]+)\]\(((?:[^()\n]+|\([^()\n]*\))+)\)|(https?:\/\/[^\s<>"']+)/g;
|
|
211
|
+
}
|
|
212
|
+
function safeDecodeURIComponent(v) {
|
|
213
|
+
try {
|
|
214
|
+
return decodeURIComponent(v);
|
|
215
|
+
} catch (e) {
|
|
216
|
+
return v;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function encodePathSegments(path) {
|
|
220
|
+
return path.split("/").filter(Boolean).map(function(s) {
|
|
221
|
+
return encodeURIComponent(s);
|
|
222
|
+
}).join("/");
|
|
223
|
+
}
|
|
224
|
+
function normalizeAttachmentPathCandidate(value) {
|
|
225
|
+
return safeDecodeURIComponent((value || "").trim()).replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+/g, "/");
|
|
226
|
+
}
|
|
227
|
+
function extractRemotePathFromAttachmentHref(href, serviceId) {
|
|
228
|
+
try {
|
|
229
|
+
var parsed = new URL(href);
|
|
230
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
231
|
+
var path = normalizeAttachmentPathCandidate(parsed.pathname || "");
|
|
232
|
+
var segs = path.split("/").filter(Boolean);
|
|
233
|
+
if (!segs.length) return null;
|
|
234
|
+
var HEX = /^[a-f0-9]{32,}$/i;
|
|
235
|
+
var sid = serviceId || "";
|
|
236
|
+
var start = 0;
|
|
237
|
+
while (start < segs.length) {
|
|
238
|
+
var seg = segs[start];
|
|
239
|
+
if (seg === sid || HEX.test(seg)) {
|
|
240
|
+
start++;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
var real = segs.slice(start).join("/");
|
|
246
|
+
return real || null;
|
|
247
|
+
} catch (e) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function getExpiredAttachmentVisiblePath(remotePath, fallback) {
|
|
252
|
+
var n = normalizeAttachmentPathCandidate(remotePath);
|
|
253
|
+
if (n) return n;
|
|
254
|
+
return normalizeAttachmentPathCandidate(fallback || "file") || "file";
|
|
255
|
+
}
|
|
256
|
+
function buildDisplayExpiredAttachmentHref(remotePath, fallback) {
|
|
257
|
+
return EXPIRED_ATTACHMENT_URL_ORIGIN + "/" + encodePathSegments(getExpiredAttachmentVisiblePath(remotePath, fallback));
|
|
258
|
+
}
|
|
259
|
+
function sanitizeAttachmentLinksForHistory(content, serviceId) {
|
|
260
|
+
if (!content || content.indexOf("Attached files:") === -1) return content;
|
|
261
|
+
return content.replace(/\[([^\]\n]+)\]\((https?:\/\/[^\s)]+)\)/g, function(_m, label, href) {
|
|
262
|
+
var remotePath = extractRemotePathFromAttachmentHref(href, serviceId);
|
|
263
|
+
var labelPath = normalizeAttachmentPathCandidate(label);
|
|
264
|
+
var fullPath = remotePath || labelPath;
|
|
265
|
+
if (!fullPath) return "[" + label + "](" + EXPIRED_ATTACHMENT_URL_ORIGIN + "/file)";
|
|
266
|
+
return "[" + label + "](" + buildDisplayExpiredAttachmentHref(fullPath, label) + ")";
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
function truncateLabelForDisplay(label) {
|
|
270
|
+
if (!label) return label;
|
|
271
|
+
if (label.length <= LINK_LABEL_MAX_DISPLAY_CHARS) return label;
|
|
272
|
+
return "\u2026" + label.slice(label.length - (LINK_LABEL_MAX_DISPLAY_CHARS - 1));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/engine/budget.ts
|
|
276
|
+
var CONTEXT_WINDOW_DEFAULT = { claude: 2e5, openai: 128e3 };
|
|
277
|
+
var CONTEXT_WINDOW_BY_MODEL = {
|
|
278
|
+
"claude-opus-4-7": 2e5,
|
|
279
|
+
"claude-sonnet-4": 2e5,
|
|
280
|
+
"gpt-5.4": 128e3
|
|
281
|
+
};
|
|
282
|
+
var OUTPUT_TOKEN_RESERVE = 22e3;
|
|
283
|
+
var TOOL_AND_RESPONSE_BUFFER = 4e3;
|
|
284
|
+
var MIN_INPUT_TOKEN_BUDGET = 8e3;
|
|
285
|
+
var CLAUDE_PER_REQUEST_INPUT_CAP = 28e3;
|
|
286
|
+
var MAX_HISTORY_MESSAGES = 20;
|
|
287
|
+
var HISTORY_TOKEN_BUDGET = 8e3;
|
|
288
|
+
function estimateTextTokens(text) {
|
|
289
|
+
return Math.ceil((text || "").length / 3);
|
|
290
|
+
}
|
|
291
|
+
function estimateMessageTokens(msg) {
|
|
292
|
+
return estimateTextTokens(msg.content) + estimateTextTokens(msg.role) + 6;
|
|
293
|
+
}
|
|
294
|
+
function getContextWindow(platform, model) {
|
|
295
|
+
var normalized = (model || "").trim().toLowerCase();
|
|
296
|
+
if (normalized && CONTEXT_WINDOW_BY_MODEL[normalized]) return CONTEXT_WINDOW_BY_MODEL[normalized];
|
|
297
|
+
return CONTEXT_WINDOW_DEFAULT[platform];
|
|
298
|
+
}
|
|
299
|
+
function stripFileBlocksFromHistory(content) {
|
|
300
|
+
if (!content) return content;
|
|
301
|
+
return content.replace(/```([\w.-]+\.[a-zA-Z0-9]+)\n[\s\S]*?```/g, "[file previously attached: $1]");
|
|
302
|
+
}
|
|
303
|
+
function buildBoundedChatMessages(options) {
|
|
304
|
+
var contextWindow = getContextWindow(options.platform, options.model);
|
|
305
|
+
var contextBasedBudget = Math.max(
|
|
306
|
+
MIN_INPUT_TOKEN_BUDGET,
|
|
307
|
+
contextWindow - OUTPUT_TOKEN_RESERVE - TOOL_AND_RESPONSE_BUFFER
|
|
308
|
+
);
|
|
309
|
+
var availableInputBudget = options.platform === "claude" ? Math.min(contextBasedBudget, CLAUDE_PER_REQUEST_INPUT_CAP) : contextBasedBudget;
|
|
310
|
+
var systemCost = estimateTextTokens(options.systemPrompt) + 12;
|
|
311
|
+
var budgetForHistory = Math.max(1e3, Math.min(HISTORY_TOKEN_BUDGET, availableInputBudget - systemCost));
|
|
312
|
+
var windowed = options.history.slice(-MAX_HISTORY_MESSAGES);
|
|
313
|
+
var latestIndex = windowed.length - 1;
|
|
314
|
+
var trimmed = windowed.map(function(m, i2) {
|
|
315
|
+
if (i2 === latestIndex) return m;
|
|
316
|
+
var stripped = stripFileBlocksFromHistory(m.content);
|
|
317
|
+
var sanitized = m.role === "user" ? sanitizeAttachmentLinksForHistory(stripped, options.serviceId) : stripped;
|
|
318
|
+
return Object.assign({}, m, { content: sanitized });
|
|
319
|
+
});
|
|
320
|
+
var bounded = [], used = 0;
|
|
321
|
+
for (var i = trimmed.length - 1; i >= 0; i--) {
|
|
322
|
+
var cost = estimateMessageTokens(trimmed[i]);
|
|
323
|
+
if (used + cost > budgetForHistory && bounded.length > 0) break;
|
|
324
|
+
bounded.unshift(trimmed[i]);
|
|
325
|
+
used += cost;
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
messages: bounded.map(function(m) {
|
|
329
|
+
return { role: m.role, content: m.content };
|
|
330
|
+
}),
|
|
331
|
+
droppedCount: Math.max(0, options.history.length - bounded.length),
|
|
332
|
+
estimatedInputTokens: used + systemCost,
|
|
333
|
+
estimatedBudget: availableInputBudget
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// src/engine/requests.ts
|
|
338
|
+
var ANTHROPIC_MESSAGES_API_URL = "https://api.anthropic.com/v1/messages";
|
|
339
|
+
var ANTHROPIC_MODELS_API_URL = "https://api.anthropic.com/v1/models";
|
|
340
|
+
var ANTHROPIC_VERSION = "2023-06-01";
|
|
341
|
+
var ANTHROPIC_MCP_BETA = "mcp-client-2025-11-20";
|
|
342
|
+
var ANTHROPIC_WEB_FETCH_BETA = "web-fetch-2025-09-10";
|
|
343
|
+
var ANTHROPIC_PROMPT_CACHING_BETA = "prompt-caching-2024-07-31";
|
|
344
|
+
var ANTHROPIC_BETA_HEADER = `${ANTHROPIC_MCP_BETA},${ANTHROPIC_WEB_FETCH_BETA},${ANTHROPIC_PROMPT_CACHING_BETA}`;
|
|
345
|
+
var WEB_FETCH_MAX_USES = 40;
|
|
346
|
+
var WEB_FETCH_MAX_CONTENT_TOKENS = 2e5;
|
|
347
|
+
var OPENAI_RESPONSES_API_URL = "https://api.openai.com/v1/responses";
|
|
348
|
+
var OPENAI_MODELS_API_URL = "https://api.openai.com/v1/models";
|
|
349
|
+
var MAX_TOKENS = 25e3;
|
|
350
|
+
var DEFAULT_OPENAI_IMAGE_DETAIL = "auto";
|
|
351
|
+
var OPENAI_WEB_SEARCH_EXTERNAL_WEB_ACCESS = true;
|
|
352
|
+
var MCP_NAME = "BunnyQuery";
|
|
353
|
+
var DEFAULT_CLAUDE_MODEL = "claude-sonnet-4-6";
|
|
354
|
+
var DEFAULT_OPENAI_MODEL = "gpt-5.4";
|
|
355
|
+
var mcpUrl = () => chatEngineConfig().mcpBaseUrl;
|
|
356
|
+
var clientSecretRequest = (opts) => chatEngineConfig().clientSecretRequest(opts);
|
|
357
|
+
var getOpenAIImageDetail = (model) => {
|
|
358
|
+
const normalized = (model || DEFAULT_OPENAI_MODEL).trim().toLowerCase();
|
|
359
|
+
const match = normalized.match(/^gpt-(\d+)(?:\.(\d+))?$/);
|
|
360
|
+
if (!match) {
|
|
361
|
+
return DEFAULT_OPENAI_IMAGE_DETAIL;
|
|
362
|
+
}
|
|
363
|
+
const major = Number(match[1]);
|
|
364
|
+
const minor = match[2] === void 0 ? null : Number(match[2]);
|
|
365
|
+
if (major > 5) {
|
|
366
|
+
return "original";
|
|
367
|
+
}
|
|
368
|
+
if (major === 5 && minor !== null && minor >= 4) {
|
|
369
|
+
return "original";
|
|
370
|
+
}
|
|
371
|
+
return DEFAULT_OPENAI_IMAGE_DETAIL;
|
|
372
|
+
};
|
|
373
|
+
var IMAGE_URL_REGEX = /\bhttps?:\/\/[^\s<>"'()\[\]]+?\.(?:jpg|jpeg|png|gif|webp)(?:\?[^\s<>"'()\[\]]*)?/gi;
|
|
374
|
+
function transformContentWithImages(content) {
|
|
375
|
+
if (typeof content !== "string" || !content) {
|
|
376
|
+
return content;
|
|
377
|
+
}
|
|
378
|
+
const matches = content.match(IMAGE_URL_REGEX);
|
|
379
|
+
if (!matches || !matches.length) {
|
|
380
|
+
return content;
|
|
381
|
+
}
|
|
382
|
+
const seen = /* @__PURE__ */ new Set();
|
|
383
|
+
const imageBlocks = [];
|
|
384
|
+
for (const url of matches) {
|
|
385
|
+
if (seen.has(url)) continue;
|
|
386
|
+
seen.add(url);
|
|
387
|
+
imageBlocks.push({
|
|
388
|
+
type: "image",
|
|
389
|
+
source: { type: "url", url }
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
return [...imageBlocks, { type: "text", text: content }];
|
|
393
|
+
}
|
|
394
|
+
function prepareClaudeMessages(messages) {
|
|
395
|
+
if (!messages.length) return messages;
|
|
396
|
+
const lastIndex = messages.length - 1;
|
|
397
|
+
const last = messages[lastIndex];
|
|
398
|
+
if (last.role !== "user") return messages;
|
|
399
|
+
const content = transformContentWithImages(last.content);
|
|
400
|
+
if (content === last.content) return messages;
|
|
401
|
+
const next = messages.slice();
|
|
402
|
+
next[lastIndex] = { role: last.role, content };
|
|
403
|
+
return next;
|
|
404
|
+
}
|
|
405
|
+
function transformContentWithOpenAIImages(content, detail = DEFAULT_OPENAI_IMAGE_DETAIL) {
|
|
406
|
+
if (typeof content !== "string" || !content) {
|
|
407
|
+
return content;
|
|
408
|
+
}
|
|
409
|
+
const matches = content.match(IMAGE_URL_REGEX);
|
|
410
|
+
if (!matches || !matches.length) {
|
|
411
|
+
return content;
|
|
412
|
+
}
|
|
413
|
+
const seen = /* @__PURE__ */ new Set();
|
|
414
|
+
const imageBlocks = [];
|
|
415
|
+
for (const url of matches) {
|
|
416
|
+
if (seen.has(url)) continue;
|
|
417
|
+
seen.add(url);
|
|
418
|
+
imageBlocks.push({
|
|
419
|
+
type: "input_image",
|
|
420
|
+
image_url: url,
|
|
421
|
+
detail
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
return [{ type: "input_text", text: content }, ...imageBlocks];
|
|
425
|
+
}
|
|
426
|
+
function prepareOpenAIMessages(messages, detail = DEFAULT_OPENAI_IMAGE_DETAIL) {
|
|
427
|
+
if (!messages.length) return messages;
|
|
428
|
+
const lastIndex = messages.length - 1;
|
|
429
|
+
const last = messages[lastIndex];
|
|
430
|
+
if (last.role !== "user") return messages;
|
|
431
|
+
const content = transformContentWithOpenAIImages(last.content, detail);
|
|
432
|
+
if (content === last.content) return messages;
|
|
433
|
+
const next = messages.slice();
|
|
434
|
+
next[lastIndex] = { role: last.role, content };
|
|
435
|
+
return next;
|
|
436
|
+
}
|
|
437
|
+
function applyHistoryCacheBreakpoint(messages) {
|
|
438
|
+
if (messages.length < 2) return messages;
|
|
439
|
+
const breakpointIndex = messages.length - 2;
|
|
440
|
+
return messages.map((m, i) => {
|
|
441
|
+
if (i !== breakpointIndex) return m;
|
|
442
|
+
const blocks = Array.isArray(m.content) ? m.content.slice() : [{ type: "text", text: m.content }];
|
|
443
|
+
if (!blocks.length) return m;
|
|
444
|
+
const lastBlockIndex = blocks.length - 1;
|
|
445
|
+
blocks[lastBlockIndex] = {
|
|
446
|
+
...blocks[lastBlockIndex],
|
|
447
|
+
cache_control: { type: "ephemeral" }
|
|
448
|
+
};
|
|
449
|
+
return { ...m, content: blocks };
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
var POLL_INTERVAL = 1500;
|
|
453
|
+
async function callClaudeWithMcp({
|
|
454
|
+
prompt,
|
|
455
|
+
messages,
|
|
456
|
+
service,
|
|
457
|
+
owner,
|
|
458
|
+
userId,
|
|
459
|
+
model = DEFAULT_CLAUDE_MODEL,
|
|
460
|
+
maxTokens = 1e3,
|
|
461
|
+
system,
|
|
462
|
+
mcpServer,
|
|
463
|
+
extractContent
|
|
464
|
+
}) {
|
|
465
|
+
const mcpServerDefinition = {
|
|
466
|
+
type: "url",
|
|
467
|
+
name: mcpServer.name,
|
|
468
|
+
url: mcpServer.url
|
|
469
|
+
};
|
|
470
|
+
if (mcpServer.authorizationToken) {
|
|
471
|
+
mcpServerDefinition.authorization_token = mcpServer.authorizationToken;
|
|
472
|
+
}
|
|
473
|
+
return clientSecretRequest({
|
|
474
|
+
clientSecretName: "claude",
|
|
475
|
+
queue: userId || service,
|
|
476
|
+
service,
|
|
477
|
+
owner,
|
|
478
|
+
...pollOpt(),
|
|
479
|
+
url: ANTHROPIC_MESSAGES_API_URL,
|
|
480
|
+
method: "POST",
|
|
481
|
+
headers: {
|
|
482
|
+
"content-type": "application/json",
|
|
483
|
+
"x-api-key": "$CLIENT_SECRET",
|
|
484
|
+
"anthropic-version": ANTHROPIC_VERSION,
|
|
485
|
+
"anthropic-beta": ANTHROPIC_BETA_HEADER
|
|
486
|
+
},
|
|
487
|
+
data: {
|
|
488
|
+
model,
|
|
489
|
+
max_tokens: maxTokens,
|
|
490
|
+
...extractContent && extractContent.length ? { _skapi_extract: extractContent } : {},
|
|
491
|
+
...system ? {
|
|
492
|
+
system: [
|
|
493
|
+
{
|
|
494
|
+
type: "text",
|
|
495
|
+
text: system,
|
|
496
|
+
cache_control: { type: "ephemeral" }
|
|
497
|
+
}
|
|
498
|
+
]
|
|
499
|
+
} : {},
|
|
500
|
+
messages: (() => {
|
|
501
|
+
const prepared = messages && messages.length ? prepareClaudeMessages(messages) : [
|
|
502
|
+
{
|
|
503
|
+
role: "user",
|
|
504
|
+
content: transformContentWithImages(prompt)
|
|
505
|
+
}
|
|
506
|
+
];
|
|
507
|
+
return applyHistoryCacheBreakpoint(prepared);
|
|
508
|
+
})(),
|
|
509
|
+
mcp_servers: [mcpServerDefinition],
|
|
510
|
+
tools: [
|
|
511
|
+
{
|
|
512
|
+
type: "mcp_toolset",
|
|
513
|
+
mcp_server_name: mcpServer.name,
|
|
514
|
+
...mcpServer.defaultConfig ? { default_config: mcpServer.defaultConfig } : {},
|
|
515
|
+
...mcpServer.configs ? { configs: mcpServer.configs } : {}
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
type: "web_fetch_20250910",
|
|
519
|
+
name: "web_fetch",
|
|
520
|
+
max_uses: WEB_FETCH_MAX_USES,
|
|
521
|
+
citations: { enabled: true },
|
|
522
|
+
max_content_tokens: WEB_FETCH_MAX_CONTENT_TOKENS
|
|
523
|
+
}
|
|
524
|
+
]
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
async function callClaudeWithPublicMcp(prompt, service, owner, messages, system, model, userId, extractContent, onResponse, onError) {
|
|
529
|
+
return callClaudeWithMcp({
|
|
530
|
+
prompt,
|
|
531
|
+
messages,
|
|
532
|
+
service,
|
|
533
|
+
owner,
|
|
534
|
+
userId,
|
|
535
|
+
model: model || DEFAULT_CLAUDE_MODEL,
|
|
536
|
+
maxTokens: MAX_TOKENS,
|
|
537
|
+
system,
|
|
538
|
+
extractContent,
|
|
539
|
+
mcpServer: {
|
|
540
|
+
name: MCP_NAME,
|
|
541
|
+
url: mcpUrl(),
|
|
542
|
+
authorizationToken: "$ACCESS_TOKEN"
|
|
543
|
+
}});
|
|
544
|
+
}
|
|
545
|
+
async function callOpenAIWithPublicMcp(prompt, service, owner, messages, system, model, userId, extractContent, onResponse, onError) {
|
|
546
|
+
const resolvedModel = model || DEFAULT_OPENAI_MODEL;
|
|
547
|
+
const imageDetail = getOpenAIImageDetail(resolvedModel);
|
|
548
|
+
const messageList = messages && messages.length ? prepareOpenAIMessages(messages, imageDetail) : [
|
|
549
|
+
{
|
|
550
|
+
role: "user",
|
|
551
|
+
content: transformContentWithOpenAIImages(prompt, imageDetail)
|
|
552
|
+
}
|
|
553
|
+
];
|
|
554
|
+
const responseInput = [
|
|
555
|
+
...system ? [
|
|
556
|
+
{
|
|
557
|
+
role: "system",
|
|
558
|
+
content: system
|
|
559
|
+
}
|
|
560
|
+
] : [],
|
|
561
|
+
...messageList.map((m) => ({
|
|
562
|
+
role: m.role,
|
|
563
|
+
content: m.content
|
|
564
|
+
}))
|
|
565
|
+
];
|
|
566
|
+
return clientSecretRequest({
|
|
567
|
+
clientSecretName: "openai",
|
|
568
|
+
queue: userId || service,
|
|
569
|
+
service,
|
|
570
|
+
owner,
|
|
571
|
+
...pollOpt(),
|
|
572
|
+
url: OPENAI_RESPONSES_API_URL,
|
|
573
|
+
method: "POST",
|
|
574
|
+
headers: {
|
|
575
|
+
"content-type": "application/json",
|
|
576
|
+
Authorization: "Bearer $CLIENT_SECRET"
|
|
577
|
+
},
|
|
578
|
+
data: {
|
|
579
|
+
model: resolvedModel,
|
|
580
|
+
max_output_tokens: MAX_TOKENS,
|
|
581
|
+
...extractContent && extractContent.length ? { _skapi_extract: extractContent } : {},
|
|
582
|
+
input: responseInput,
|
|
583
|
+
tools: [
|
|
584
|
+
{
|
|
585
|
+
type: "mcp",
|
|
586
|
+
server_label: MCP_NAME,
|
|
587
|
+
server_url: mcpUrl(),
|
|
588
|
+
require_approval: "never",
|
|
589
|
+
headers: {
|
|
590
|
+
Authorization: "Bearer $ACCESS_TOKEN"
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
...[
|
|
594
|
+
{
|
|
595
|
+
type: "web_search",
|
|
596
|
+
external_web_access: OPENAI_WEB_SEARCH_EXTERNAL_WEB_ACCESS
|
|
597
|
+
}
|
|
598
|
+
]
|
|
599
|
+
]
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
async function notifyAgentSaveAttachment(info) {
|
|
604
|
+
const { platform, service, owner, attachment } = info;
|
|
605
|
+
const office = isOfficeFile(attachment.name, attachment.mime);
|
|
606
|
+
const placeholder = office ? makeExtractPlaceholder(attachment.storagePath) : void 0;
|
|
607
|
+
const extractContent = office && placeholder ? [{ path: attachment.storagePath, placeholder, name: attachment.name, mime: attachment.mime }] : void 0;
|
|
608
|
+
const skapiExtract = extractContent && extractContent.length ? { _skapi_extract: extractContent } : {};
|
|
609
|
+
const userMessage = buildIndexingUserMessage(
|
|
610
|
+
attachment,
|
|
611
|
+
placeholder ? { inlineContentPlaceholder: placeholder } : void 0
|
|
612
|
+
);
|
|
613
|
+
const systemPrompt = buildIndexingSystemPrompt({
|
|
614
|
+
service,
|
|
615
|
+
serviceName: info.serviceName,
|
|
616
|
+
serviceDescription: info.serviceDescription
|
|
617
|
+
});
|
|
618
|
+
if (platform === "openai") {
|
|
619
|
+
const resolvedModel2 = info.model || DEFAULT_OPENAI_MODEL;
|
|
620
|
+
const imageDetail = getOpenAIImageDetail(resolvedModel2);
|
|
621
|
+
return clientSecretRequest({
|
|
622
|
+
clientSecretName: "openai",
|
|
623
|
+
queue: (info.userId || service) + BG_INDEXING_QUEUE_SUFFIX,
|
|
624
|
+
service,
|
|
625
|
+
owner,
|
|
626
|
+
...pollOpt(),
|
|
627
|
+
url: OPENAI_RESPONSES_API_URL,
|
|
628
|
+
method: "POST",
|
|
629
|
+
headers: {
|
|
630
|
+
"content-type": "application/json",
|
|
631
|
+
Authorization: "Bearer $CLIENT_SECRET"
|
|
632
|
+
},
|
|
633
|
+
data: {
|
|
634
|
+
model: resolvedModel2,
|
|
635
|
+
max_output_tokens: MAX_TOKENS,
|
|
636
|
+
...skapiExtract,
|
|
637
|
+
input: [
|
|
638
|
+
{ role: "system", content: systemPrompt },
|
|
639
|
+
{
|
|
640
|
+
role: "user",
|
|
641
|
+
content: transformContentWithOpenAIImages(userMessage, imageDetail)
|
|
642
|
+
}
|
|
643
|
+
],
|
|
644
|
+
tools: [
|
|
645
|
+
{
|
|
646
|
+
type: "mcp",
|
|
647
|
+
server_label: MCP_NAME,
|
|
648
|
+
server_url: mcpUrl(),
|
|
649
|
+
require_approval: "never",
|
|
650
|
+
headers: { Authorization: "Bearer $ACCESS_TOKEN" }
|
|
651
|
+
},
|
|
652
|
+
...[
|
|
653
|
+
{
|
|
654
|
+
type: "web_search",
|
|
655
|
+
external_web_access: OPENAI_WEB_SEARCH_EXTERNAL_WEB_ACCESS
|
|
656
|
+
}
|
|
657
|
+
]
|
|
658
|
+
]
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
const resolvedModel = info.model || DEFAULT_CLAUDE_MODEL;
|
|
663
|
+
return clientSecretRequest({
|
|
664
|
+
clientSecretName: "claude",
|
|
665
|
+
queue: (info.userId || service) + BG_INDEXING_QUEUE_SUFFIX,
|
|
666
|
+
service,
|
|
667
|
+
owner,
|
|
668
|
+
...pollOpt(),
|
|
669
|
+
url: ANTHROPIC_MESSAGES_API_URL,
|
|
670
|
+
method: "POST",
|
|
671
|
+
headers: {
|
|
672
|
+
"content-type": "application/json",
|
|
673
|
+
"x-api-key": "$CLIENT_SECRET",
|
|
674
|
+
"anthropic-version": ANTHROPIC_VERSION,
|
|
675
|
+
"anthropic-beta": ANTHROPIC_BETA_HEADER
|
|
676
|
+
},
|
|
677
|
+
data: {
|
|
678
|
+
model: resolvedModel,
|
|
679
|
+
max_tokens: MAX_TOKENS,
|
|
680
|
+
...skapiExtract,
|
|
681
|
+
system: [
|
|
682
|
+
{
|
|
683
|
+
type: "text",
|
|
684
|
+
text: systemPrompt,
|
|
685
|
+
cache_control: { type: "ephemeral" }
|
|
686
|
+
}
|
|
687
|
+
],
|
|
688
|
+
messages: [
|
|
689
|
+
{
|
|
690
|
+
role: "user",
|
|
691
|
+
content: transformContentWithImages(userMessage)
|
|
692
|
+
}
|
|
693
|
+
],
|
|
694
|
+
mcp_servers: [
|
|
695
|
+
{
|
|
696
|
+
type: "url",
|
|
697
|
+
name: MCP_NAME,
|
|
698
|
+
url: mcpUrl(),
|
|
699
|
+
authorization_token: "$ACCESS_TOKEN"
|
|
700
|
+
}
|
|
701
|
+
],
|
|
702
|
+
tools: [
|
|
703
|
+
{
|
|
704
|
+
type: "mcp_toolset",
|
|
705
|
+
mcp_server_name: MCP_NAME
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
type: "web_fetch_20250910",
|
|
709
|
+
name: "web_fetch",
|
|
710
|
+
max_uses: WEB_FETCH_MAX_USES,
|
|
711
|
+
citations: { enabled: true },
|
|
712
|
+
max_content_tokens: WEB_FETCH_MAX_CONTENT_TOKENS
|
|
713
|
+
}
|
|
714
|
+
]
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
function extractClaudeText(response) {
|
|
719
|
+
if (!Array.isArray(response?.content)) {
|
|
720
|
+
return "";
|
|
721
|
+
}
|
|
722
|
+
return response.content.filter((block) => block?.type === "text").map((block) => block.text).join("\n");
|
|
723
|
+
}
|
|
724
|
+
function extractOpenAIText(response) {
|
|
725
|
+
if (typeof response?.output_text === "string" && response.output_text.length) {
|
|
726
|
+
return response.output_text;
|
|
727
|
+
}
|
|
728
|
+
if (Array.isArray(response?.output)) {
|
|
729
|
+
const text = response.output.flatMap((item) => item?.content || []).filter((part) => part?.type === "output_text").map((part) => part.text || "").join("\n").trim();
|
|
730
|
+
if (text) {
|
|
731
|
+
return text;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
const content = response?.choices?.[0]?.message?.content;
|
|
735
|
+
if (typeof content === "string") {
|
|
736
|
+
return content;
|
|
737
|
+
}
|
|
738
|
+
if (Array.isArray(content)) {
|
|
739
|
+
return content.map((part) => {
|
|
740
|
+
if (typeof part === "string") {
|
|
741
|
+
return part;
|
|
742
|
+
}
|
|
743
|
+
if (part?.type === "text") {
|
|
744
|
+
return part.text || "";
|
|
745
|
+
}
|
|
746
|
+
return "";
|
|
747
|
+
}).join("\n");
|
|
748
|
+
}
|
|
749
|
+
return "";
|
|
750
|
+
}
|
|
751
|
+
async function listClaudeModels(service, owner) {
|
|
752
|
+
return clientSecretRequest({
|
|
753
|
+
clientSecretName: "claude",
|
|
754
|
+
service,
|
|
755
|
+
owner,
|
|
756
|
+
url: ANTHROPIC_MODELS_API_URL,
|
|
757
|
+
method: "GET",
|
|
758
|
+
headers: {
|
|
759
|
+
"x-api-key": "$CLIENT_SECRET",
|
|
760
|
+
"anthropic-version": ANTHROPIC_VERSION
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
async function listOpenAIModels(service, owner) {
|
|
765
|
+
return clientSecretRequest({
|
|
766
|
+
clientSecretName: "openai",
|
|
767
|
+
service,
|
|
768
|
+
owner,
|
|
769
|
+
url: OPENAI_MODELS_API_URL,
|
|
770
|
+
method: "GET",
|
|
771
|
+
headers: {
|
|
772
|
+
Authorization: "Bearer $CLIENT_SECRET"
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
var BG_INDEXING_QUEUE_SUFFIX = "-bg";
|
|
777
|
+
async function getChatHistory(params, fetchOptions) {
|
|
778
|
+
const url = params.platform === "claude" ? ANTHROPIC_MESSAGES_API_URL : OPENAI_RESPONSES_API_URL;
|
|
779
|
+
const p = Object.assign(
|
|
780
|
+
{
|
|
781
|
+
url,
|
|
782
|
+
method: "POST"
|
|
783
|
+
},
|
|
784
|
+
{ service: params.service, owner: params.owner },
|
|
785
|
+
params.queue ? { queue: params.queue } : {}
|
|
786
|
+
);
|
|
787
|
+
return chatEngineConfig().clientSecretRequestHistory(
|
|
788
|
+
p,
|
|
789
|
+
Object.assign({ ascending: false }, fetchOptions)
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// src/engine/history.ts
|
|
794
|
+
function filterListByClearHorizon(list, clearedAt) {
|
|
795
|
+
if (!clearedAt) return list;
|
|
796
|
+
return list.filter(function(item) {
|
|
797
|
+
var updated = Number(item && item.updated);
|
|
798
|
+
return isFinite(updated) && updated > clearedAt;
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
function normalizeTextContent(content) {
|
|
802
|
+
if (typeof content === "string") return content;
|
|
803
|
+
if (Array.isArray(content)) {
|
|
804
|
+
return content.map(function(part) {
|
|
805
|
+
if (typeof part === "string") return part;
|
|
806
|
+
if (part && (part.type === "text" || part.type === "input_text" || part.type === "output_text")) return part.text || "";
|
|
807
|
+
return "";
|
|
808
|
+
}).join("\n").trim();
|
|
809
|
+
}
|
|
810
|
+
return "";
|
|
811
|
+
}
|
|
812
|
+
function extractLastUserTextFromRequest(requestBody) {
|
|
813
|
+
var arr = requestBody && Array.isArray(requestBody.messages) ? requestBody.messages : requestBody && Array.isArray(requestBody.input) ? requestBody.input : [];
|
|
814
|
+
for (var i = arr.length - 1; i >= 0; i--) {
|
|
815
|
+
if (arr[i] && arr[i].role === "user") {
|
|
816
|
+
var t = normalizeTextContent(arr[i].content);
|
|
817
|
+
if (t) return t;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return "";
|
|
821
|
+
}
|
|
822
|
+
function mapHistoryListToMessages(list, platform, opts) {
|
|
823
|
+
var mapped = [], runningItemIds = [];
|
|
824
|
+
var extractAssistantText = platform === "openai" ? extractOpenAIText : extractClaudeText;
|
|
825
|
+
var filtered = filterListByClearHorizon(list, opts.clearedAt);
|
|
826
|
+
filtered.slice().reverse().forEach(function(item) {
|
|
827
|
+
var requestBody = item && item.request_body;
|
|
828
|
+
var isInProcess = item && item.status === "running";
|
|
829
|
+
var isQueued = item && item.status === "pending";
|
|
830
|
+
var isCancelledItem = item && item.status === "cancelled";
|
|
831
|
+
var isPending = isInProcess || isQueued;
|
|
832
|
+
var isFailed = item && item.status === "failed";
|
|
833
|
+
var response = isFailed ? item.error != null ? item.error : item.response_body : item && item.response_body != null ? item.response_body : item && item.error;
|
|
834
|
+
var userText = extractLastUserTextFromRequest(requestBody);
|
|
835
|
+
var assistantText = isPending ? "" : (extractAssistantText(response) || "").trim() || "";
|
|
836
|
+
var isErrorResponse = !isPending && (isFailed || isErrorResponseBody(response));
|
|
837
|
+
var serverItemId = item && typeof item.id === "string" && item.id ? item.id : void 0;
|
|
838
|
+
if (userText) {
|
|
839
|
+
var displayContent;
|
|
840
|
+
if (item._isBgTask) {
|
|
841
|
+
var nameMatch = userText.match(/^- name: (.+)$/m);
|
|
842
|
+
if (nameMatch) {
|
|
843
|
+
var mimeMatch = userText.match(/^- mime type: (.+)$/m);
|
|
844
|
+
var sizeMatch = userText.match(/^- size \(bytes\): (\d+)$/m);
|
|
845
|
+
var pathMatch = userText.match(/^- storage path: (.+)$/m);
|
|
846
|
+
displayContent = opts.formatIndexingLabel(
|
|
847
|
+
nameMatch[1].trim(),
|
|
848
|
+
mimeMatch ? mimeMatch[1].trim() : "",
|
|
849
|
+
sizeMatch ? Number(sizeMatch[1]) : null,
|
|
850
|
+
pathMatch ? pathMatch[1].trim() : void 0
|
|
851
|
+
);
|
|
852
|
+
} else {
|
|
853
|
+
displayContent = userText;
|
|
854
|
+
}
|
|
855
|
+
} else {
|
|
856
|
+
displayContent = sanitizeAttachmentLinksForHistory(userText, opts.serviceId);
|
|
857
|
+
}
|
|
858
|
+
var userMsg = { role: "user", content: displayContent };
|
|
859
|
+
if (isInProcess) userMsg.isPendingInProcess = true;
|
|
860
|
+
if (isQueued) userMsg.isPendingQueued = true;
|
|
861
|
+
if (isCancelledItem) userMsg.isCancelled = true;
|
|
862
|
+
if (item._isBgTask) userMsg.isBackgroundTask = true;
|
|
863
|
+
if (item._isOnBgQueue) userMsg._useBgQueue = true;
|
|
864
|
+
if (serverItemId !== void 0) userMsg._serverItemId = serverItemId;
|
|
865
|
+
mapped.push(userMsg);
|
|
866
|
+
}
|
|
867
|
+
if (isCancelledItem) ; else if (isInProcess) {
|
|
868
|
+
var ph = { role: "assistant", content: "", isPending: true, isPendingInProcess: true };
|
|
869
|
+
if (item._isBgTask) ph.isBackgroundTask = true;
|
|
870
|
+
if (serverItemId !== void 0) {
|
|
871
|
+
ph._serverItemId = serverItemId;
|
|
872
|
+
runningItemIds.push(serverItemId);
|
|
873
|
+
}
|
|
874
|
+
mapped.push(ph);
|
|
875
|
+
} else if (isQueued) ; else if (isErrorResponse) {
|
|
876
|
+
var em = { role: "assistant", content: getErrorMessage(response), isError: true };
|
|
877
|
+
if (item._isBgTask) em.isBackgroundTask = true;
|
|
878
|
+
if (serverItemId !== void 0) em._serverItemId = serverItemId;
|
|
879
|
+
mapped.push(em);
|
|
880
|
+
} else if (assistantText) {
|
|
881
|
+
var okm = { role: "assistant", content: assistantText };
|
|
882
|
+
if (item._isBgTask) okm.isBackgroundTask = true;
|
|
883
|
+
if (serverItemId !== void 0) okm._serverItemId = serverItemId;
|
|
884
|
+
mapped.push(okm);
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
return { messages: mapped, runningItemIds };
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// src/engine/session.ts
|
|
891
|
+
function sleep(ms) {
|
|
892
|
+
return new Promise(function(r) {
|
|
893
|
+
setTimeout(r, ms);
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
var ChatSession = class {
|
|
897
|
+
constructor(host) {
|
|
898
|
+
this.typewriterQueue = Promise.resolve();
|
|
899
|
+
this.host = host;
|
|
900
|
+
this.state = {
|
|
901
|
+
messages: [],
|
|
902
|
+
attachments: [],
|
|
903
|
+
uploadingAttachments: false,
|
|
904
|
+
sending: false,
|
|
905
|
+
typing: false,
|
|
906
|
+
typingAbort: false,
|
|
907
|
+
loadingHistory: false,
|
|
908
|
+
loadingOlderHistory: false,
|
|
909
|
+
historyEndOfList: false,
|
|
910
|
+
historyStartKeyHistory: [],
|
|
911
|
+
historyRequestToken: 0,
|
|
912
|
+
gateRefreshToken: 0
|
|
913
|
+
};
|
|
914
|
+
this.bgTaskQueue = [];
|
|
915
|
+
this.cancelledServerIds = /* @__PURE__ */ new Set();
|
|
916
|
+
this.pendingAgentRequests = {};
|
|
917
|
+
this.aiChatHistoryCache = {};
|
|
918
|
+
this.historyItemPolls = /* @__PURE__ */ new Map();
|
|
919
|
+
this._lidSeq = 0;
|
|
920
|
+
}
|
|
921
|
+
_newLocalId() {
|
|
922
|
+
this._lidSeq += 1;
|
|
923
|
+
return "lid_" + this._lidSeq;
|
|
924
|
+
}
|
|
925
|
+
getHistoryCacheKey() {
|
|
926
|
+
var id = this.host.getIdentity();
|
|
927
|
+
if (!id.serviceId || id.platform === "none") return "";
|
|
928
|
+
return id.serviceId + "#" + id.platform;
|
|
929
|
+
}
|
|
930
|
+
updateHistoryCache() {
|
|
931
|
+
var key = this.getHistoryCacheKey();
|
|
932
|
+
if (!key) return;
|
|
933
|
+
this.aiChatHistoryCache[key] = {
|
|
934
|
+
messages: this.state.messages.slice(),
|
|
935
|
+
endOfList: this.state.historyEndOfList,
|
|
936
|
+
startKeyHistory: this.state.historyStartKeyHistory.slice()
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
_callProviderFor(platform, prompt, messages, system, model, userId, extractContent) {
|
|
940
|
+
var id = this.host.getIdentity();
|
|
941
|
+
return platform === "openai" ? callOpenAIWithPublicMcp(prompt, id.serviceId, id.owner, messages, system, model, userId, extractContent) : callClaudeWithPublicMcp(prompt, id.serviceId, id.owner, messages, system, model, userId, extractContent);
|
|
942
|
+
}
|
|
943
|
+
dispatchAgentRequest(params) {
|
|
944
|
+
var self = this;
|
|
945
|
+
var sendAndPoll = function() {
|
|
946
|
+
return Promise.resolve(
|
|
947
|
+
self._callProviderFor(params.aiPlatform, params.text, params.boundedMessages, params.systemPrompt, params.aiModel, params.userId, params.extractContent)
|
|
948
|
+
).then(function(initial) {
|
|
949
|
+
if (initial && initial.poll && (initial.status === "pending" || initial.status === "running")) {
|
|
950
|
+
return initial.poll({ latency: POLL_INTERVAL });
|
|
951
|
+
}
|
|
952
|
+
return initial;
|
|
953
|
+
});
|
|
954
|
+
};
|
|
955
|
+
var run = sendAndPoll().catch(function(err) {
|
|
956
|
+
if (isAuthExpiredError(err)) return self.host.refreshSession().then(sendAndPoll);
|
|
957
|
+
throw err;
|
|
958
|
+
}).then(function(response) {
|
|
959
|
+
if (isErrorResponseBody(response) && isAuthExpiredError(response)) {
|
|
960
|
+
return self.host.refreshSession().then(sendAndPoll);
|
|
961
|
+
}
|
|
962
|
+
return response;
|
|
963
|
+
}).then(function(response) {
|
|
964
|
+
if (isErrorResponseBody(response)) return { content: getErrorMessage(response), isError: true };
|
|
965
|
+
var answer = params.aiPlatform === "openai" ? extractOpenAIText(response) : extractClaudeText(response);
|
|
966
|
+
answer = (answer || "").trim();
|
|
967
|
+
return { content: answer || "No text response received from AI provider.", isError: false };
|
|
968
|
+
}).catch(function(err) {
|
|
969
|
+
return { content: getErrorMessage(err), isError: true };
|
|
970
|
+
}).then(function(result) {
|
|
971
|
+
var existing = self.aiChatHistoryCache[params.key] || { messages: [], endOfList: false, startKeyHistory: [] };
|
|
972
|
+
self.aiChatHistoryCache[params.key] = {
|
|
973
|
+
messages: existing.messages.concat([{ role: "assistant", content: result.content, isError: result.isError }]),
|
|
974
|
+
endOfList: existing.endOfList,
|
|
975
|
+
startKeyHistory: existing.startKeyHistory
|
|
976
|
+
};
|
|
977
|
+
delete self.pendingAgentRequests[params.key];
|
|
978
|
+
return result;
|
|
979
|
+
});
|
|
980
|
+
this.pendingAgentRequests[params.key] = run;
|
|
981
|
+
return run;
|
|
982
|
+
}
|
|
983
|
+
// composed = clean display text; composedForLlm carries office-extraction
|
|
984
|
+
// placeholders for the provider only. useBgQueue routes a post-attachment turn
|
|
985
|
+
// onto the "-bg" queue so it runs after indexing.
|
|
986
|
+
dispatchComposedMessage(composed, useBgQueue, composedForLlm, extractContent) {
|
|
987
|
+
var self = this;
|
|
988
|
+
if (!composed) return;
|
|
989
|
+
var id = this.host.getIdentity();
|
|
990
|
+
if (id.platform === "none") return;
|
|
991
|
+
var llmComposed = composedForLlm || composed;
|
|
992
|
+
var isQueuedSend = useBgQueue || this.state.sending || this.state.messages.some(function(m) {
|
|
993
|
+
return (m.isPending || m.isPendingQueued) && !m.isBackgroundTask && !m._useBgQueue;
|
|
994
|
+
});
|
|
995
|
+
var aiPlatform = id.platform;
|
|
996
|
+
var aiModel = id.model || void 0;
|
|
997
|
+
var systemPrompt = this.host.buildSystemPrompt();
|
|
998
|
+
var userId = id.userId || id.serviceId;
|
|
999
|
+
var chatQueue = useBgQueue ? userId + BG_INDEXING_QUEUE_SUFFIX : userId;
|
|
1000
|
+
if (isQueuedSend) {
|
|
1001
|
+
var resolvedHistory = this.state.messages.filter(function(m) {
|
|
1002
|
+
return !m.isPending && !m.isPendingQueued && !m.isPendingInProcess && !m.isPendingOlder && !m.isCancelled && !m.isBackgroundTask;
|
|
1003
|
+
});
|
|
1004
|
+
var boundedQ = buildBoundedChatMessages({
|
|
1005
|
+
platform: aiPlatform,
|
|
1006
|
+
model: aiModel,
|
|
1007
|
+
systemPrompt,
|
|
1008
|
+
serviceId: id.serviceId,
|
|
1009
|
+
history: resolvedHistory.concat([{ role: "user", content: llmComposed }])
|
|
1010
|
+
});
|
|
1011
|
+
var queuedBubble = { role: "user", content: composed, isPendingQueued: true, isSendingToServer: true };
|
|
1012
|
+
if (useBgQueue) queuedBubble._useBgQueue = true;
|
|
1013
|
+
this.state.messages.push(queuedBubble);
|
|
1014
|
+
this.host.notify();
|
|
1015
|
+
this.updateHistoryCache();
|
|
1016
|
+
this.host.scrollToBottom(true);
|
|
1017
|
+
var capturedComposed = composed, capturedPlatform = aiPlatform;
|
|
1018
|
+
Promise.resolve(this._callProviderFor(aiPlatform, composed, boundedQ.messages, systemPrompt, aiModel, chatQueue, extractContent)).then(function(result) {
|
|
1019
|
+
var sendingIdx = self.state.messages.findIndex(function(m) {
|
|
1020
|
+
return m.isSendingToServer && (m.isPendingQueued || m.isPendingInProcess) && m.role === "user";
|
|
1021
|
+
});
|
|
1022
|
+
var serverId = result && typeof result.id === "string" ? result.id : void 0;
|
|
1023
|
+
if (sendingIdx >= 0) {
|
|
1024
|
+
var upd = Object.assign({}, self.state.messages[sendingIdx], { isSendingToServer: false });
|
|
1025
|
+
if (serverId) upd._serverItemId = serverId;
|
|
1026
|
+
self.state.messages[sendingIdx] = upd;
|
|
1027
|
+
self.host.notify();
|
|
1028
|
+
}
|
|
1029
|
+
if (result && result.poll && (result.status === "pending" || result.status === "running")) {
|
|
1030
|
+
return result.poll({ latency: POLL_INTERVAL }).then(function(res) {
|
|
1031
|
+
return self.onQueuedSendResponse(capturedComposed, res, capturedPlatform, serverId);
|
|
1032
|
+
}).catch(function(err) {
|
|
1033
|
+
return self.onQueuedSendError(capturedComposed, err, serverId);
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
return self.onQueuedSendResponse(capturedComposed, result, capturedPlatform, serverId);
|
|
1037
|
+
}).catch(function(err) {
|
|
1038
|
+
return self.onQueuedSendError(capturedComposed, err, void 0);
|
|
1039
|
+
});
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
this.state.messages.push({ role: "user", content: composed });
|
|
1043
|
+
this.state.messages.push({ role: "assistant", content: "", isPending: true, isPendingInProcess: true });
|
|
1044
|
+
this.host.notify();
|
|
1045
|
+
this.updateHistoryCache();
|
|
1046
|
+
this.state.sending = true;
|
|
1047
|
+
this.host.scrollToBottom(true);
|
|
1048
|
+
var key = this.getHistoryCacheKey();
|
|
1049
|
+
var historyForLlm = this.state.messages.filter(function(m) {
|
|
1050
|
+
return !m.isCancelled && !m.isBackgroundTask;
|
|
1051
|
+
});
|
|
1052
|
+
if (llmComposed !== composed) {
|
|
1053
|
+
for (var li = historyForLlm.length - 1; li >= 0; li--) {
|
|
1054
|
+
if (historyForLlm[li].role === "user" && historyForLlm[li].content === composed) {
|
|
1055
|
+
historyForLlm[li] = Object.assign({}, historyForLlm[li], { content: llmComposed });
|
|
1056
|
+
break;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
var bounded = buildBoundedChatMessages({
|
|
1061
|
+
platform: aiPlatform,
|
|
1062
|
+
model: aiModel,
|
|
1063
|
+
systemPrompt,
|
|
1064
|
+
serviceId: id.serviceId,
|
|
1065
|
+
history: historyForLlm
|
|
1066
|
+
});
|
|
1067
|
+
var requestToken = this.state.gateRefreshToken;
|
|
1068
|
+
var run = this.dispatchAgentRequest({
|
|
1069
|
+
key,
|
|
1070
|
+
serviceId: id.serviceId,
|
|
1071
|
+
owner: id.owner,
|
|
1072
|
+
aiPlatform,
|
|
1073
|
+
aiModel,
|
|
1074
|
+
systemPrompt,
|
|
1075
|
+
text: composed,
|
|
1076
|
+
boundedMessages: bounded.messages,
|
|
1077
|
+
userId: chatQueue,
|
|
1078
|
+
extractContent
|
|
1079
|
+
});
|
|
1080
|
+
Promise.resolve(run).catch(function() {
|
|
1081
|
+
}).then(function() {
|
|
1082
|
+
if (requestToken !== self.state.gateRefreshToken || self.getHistoryCacheKey() !== key) return;
|
|
1083
|
+
self.state.sending = false;
|
|
1084
|
+
return Promise.resolve(self.typewriteLatestReply(key)).then(function() {
|
|
1085
|
+
self.host.scrollToBottom(true);
|
|
1086
|
+
});
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
promoteNextBgQueuedToRunning() {
|
|
1090
|
+
if (this.state.messages.some(function(m) {
|
|
1091
|
+
return m.isPending && m.role === "assistant" && m.isBackgroundTask;
|
|
1092
|
+
})) return;
|
|
1093
|
+
var nextIdx = this.state.messages.findIndex(function(m) {
|
|
1094
|
+
return m.isPendingQueued && m.role === "user" && m.isBackgroundTask;
|
|
1095
|
+
});
|
|
1096
|
+
if (nextIdx === -1) return;
|
|
1097
|
+
var existing = this.state.messages[nextIdx];
|
|
1098
|
+
var promoted = { role: "user", content: existing.content, isPendingInProcess: true, isBackgroundTask: true };
|
|
1099
|
+
if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
|
|
1100
|
+
this.state.messages[nextIdx] = promoted;
|
|
1101
|
+
var placeholder = { role: "assistant", content: "", isPending: true, isPendingInProcess: true, isBackgroundTask: true };
|
|
1102
|
+
if (existing._serverItemId !== void 0) placeholder._serverItemId = existing._serverItemId;
|
|
1103
|
+
this.state.messages.splice(nextIdx + 1, 0, placeholder);
|
|
1104
|
+
this.host.notify();
|
|
1105
|
+
}
|
|
1106
|
+
promoteNextQueuedToRunning() {
|
|
1107
|
+
if (this.state.messages.some(function(m) {
|
|
1108
|
+
return m.isPending && m.role === "assistant" && !m.isBackgroundTask;
|
|
1109
|
+
})) return;
|
|
1110
|
+
var nextIdx = this.state.messages.findIndex(function(m) {
|
|
1111
|
+
return m.isPendingQueued && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue;
|
|
1112
|
+
});
|
|
1113
|
+
if (nextIdx === -1) return;
|
|
1114
|
+
var existing = this.state.messages[nextIdx];
|
|
1115
|
+
var promoted = { role: "user", content: existing.content, isPendingInProcess: true };
|
|
1116
|
+
if (existing.isBackgroundTask) promoted.isBackgroundTask = true;
|
|
1117
|
+
if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
|
|
1118
|
+
if (existing.isSendingToServer) promoted.isSendingToServer = true;
|
|
1119
|
+
this.state.messages[nextIdx] = promoted;
|
|
1120
|
+
this.state.messages.splice(nextIdx + 1, 0, { role: "assistant", content: "", isPending: true });
|
|
1121
|
+
this.host.notify();
|
|
1122
|
+
}
|
|
1123
|
+
resolveQueuedUserBubble(serverId) {
|
|
1124
|
+
var userIdx = -1;
|
|
1125
|
+
if (serverId) {
|
|
1126
|
+
userIdx = this.state.messages.findIndex(function(m) {
|
|
1127
|
+
return m._serverItemId === serverId && (m.isPendingInProcess || m.isPendingQueued) && m.role === "user" && !m.isBackgroundTask;
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
if (userIdx === -1) {
|
|
1131
|
+
userIdx = this.state.messages.findIndex(function(m) {
|
|
1132
|
+
return m.isPendingInProcess && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue;
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
if (userIdx === -1) {
|
|
1136
|
+
userIdx = this.state.messages.findIndex(function(m) {
|
|
1137
|
+
return m.isPendingQueued && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue;
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
if (serverId && this.cancelledServerIds.has(serverId)) {
|
|
1141
|
+
this.cancelledServerIds.delete(serverId);
|
|
1142
|
+
if (userIdx >= 0) {
|
|
1143
|
+
var ex = this.state.messages[userIdx];
|
|
1144
|
+
this.state.messages[userIdx] = { role: "user", content: ex.content, isCancelled: true, _serverItemId: ex._serverItemId };
|
|
1145
|
+
var thIdx = this.state.messages.findIndex(function(m, i) {
|
|
1146
|
+
return i > userIdx && m.isPending && m.role === "assistant" && !m.isBackgroundTask;
|
|
1147
|
+
});
|
|
1148
|
+
if (thIdx !== -1) this.state.messages.splice(thIdx, 1);
|
|
1149
|
+
}
|
|
1150
|
+
this.promoteNextQueuedToRunning();
|
|
1151
|
+
return void 0;
|
|
1152
|
+
}
|
|
1153
|
+
if (userIdx >= 0) {
|
|
1154
|
+
var exist = this.state.messages[userIdx];
|
|
1155
|
+
var repl = { role: "user", content: exist.content };
|
|
1156
|
+
if (exist._serverItemId !== void 0) repl._serverItemId = exist._serverItemId;
|
|
1157
|
+
this.state.messages[userIdx] = repl;
|
|
1158
|
+
}
|
|
1159
|
+
var thinkingIdx = userIdx >= 0 ? this.state.messages.findIndex(function(m, i) {
|
|
1160
|
+
return i > userIdx && m.isPending && m.role === "assistant" && !m.isBackgroundTask;
|
|
1161
|
+
}) : -1;
|
|
1162
|
+
return thinkingIdx !== -1 ? thinkingIdx : userIdx >= 0 ? userIdx + 1 : -1;
|
|
1163
|
+
}
|
|
1164
|
+
insertAtTarget(msg, targetIdx) {
|
|
1165
|
+
if (targetIdx >= 0 && this.state.messages[targetIdx] && this.state.messages[targetIdx].isPending) this.state.messages[targetIdx] = msg;
|
|
1166
|
+
else if (targetIdx >= 0) this.state.messages.splice(targetIdx, 0, msg);
|
|
1167
|
+
else this.state.messages.push(msg);
|
|
1168
|
+
}
|
|
1169
|
+
onQueuedSendResponse(_composed, response, platform, serverId) {
|
|
1170
|
+
var targetIdx = this.resolveQueuedUserBubble(serverId);
|
|
1171
|
+
if (targetIdx === void 0) {
|
|
1172
|
+
this.host.notify();
|
|
1173
|
+
this.updateHistoryCache();
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
if (isErrorResponseBody(response)) {
|
|
1177
|
+
this.insertAtTarget({ role: "assistant", content: getErrorMessage(response), isError: true }, targetIdx);
|
|
1178
|
+
} else {
|
|
1179
|
+
var answer = platform === "openai" ? extractOpenAIText(response) : extractClaudeText(response);
|
|
1180
|
+
answer = (answer || "").trim() || "No text response received from AI provider.";
|
|
1181
|
+
var lid = this._newLocalId();
|
|
1182
|
+
if (targetIdx >= 0 && this.state.messages[targetIdx] && this.state.messages[targetIdx].isPending) {
|
|
1183
|
+
this.state.messages[targetIdx] = { role: "assistant", content: "", _localId: lid };
|
|
1184
|
+
this.host.notify();
|
|
1185
|
+
this.enqueueTypewrite(targetIdx, answer, lid);
|
|
1186
|
+
} else if (targetIdx >= 0) {
|
|
1187
|
+
this.state.messages.splice(targetIdx, 0, { role: "assistant", content: "", _localId: lid });
|
|
1188
|
+
this.host.notify();
|
|
1189
|
+
this.enqueueTypewrite(targetIdx, answer, lid);
|
|
1190
|
+
} else {
|
|
1191
|
+
var aiIdx = this.state.messages.length;
|
|
1192
|
+
this.state.messages.push({ role: "assistant", content: "", _localId: lid });
|
|
1193
|
+
this.host.notify();
|
|
1194
|
+
this.enqueueTypewrite(aiIdx, answer, lid);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
this.promoteNextQueuedToRunning();
|
|
1198
|
+
this.updateHistoryCache();
|
|
1199
|
+
this.host.notify();
|
|
1200
|
+
this.host.scrollToBottom(true);
|
|
1201
|
+
}
|
|
1202
|
+
onQueuedSendError(_composed, err, serverId) {
|
|
1203
|
+
var isNotExists = err && (err.code === "NOT_EXISTS" || err.body && err.body.code === "NOT_EXISTS");
|
|
1204
|
+
if (isNotExists) {
|
|
1205
|
+
var userIdx = serverId ? this.state.messages.findIndex(function(m) {
|
|
1206
|
+
return m._serverItemId === serverId && (m.isPendingInProcess || m.isPendingQueued) && m.role === "user" && !m.isBackgroundTask;
|
|
1207
|
+
}) : this.state.messages.findIndex(function(m) {
|
|
1208
|
+
return m.isPendingInProcess && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue;
|
|
1209
|
+
});
|
|
1210
|
+
if (!serverId && userIdx === -1) {
|
|
1211
|
+
userIdx = this.state.messages.findIndex(function(m) {
|
|
1212
|
+
return m.isPendingQueued && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue;
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
if (userIdx >= 0) {
|
|
1216
|
+
var ex = this.state.messages[userIdx];
|
|
1217
|
+
var repl = { role: "user", content: ex.content, isCancelled: true };
|
|
1218
|
+
if (ex._serverItemId !== void 0) repl._serverItemId = ex._serverItemId;
|
|
1219
|
+
this.state.messages[userIdx] = repl;
|
|
1220
|
+
}
|
|
1221
|
+
if (serverId) {
|
|
1222
|
+
var thById = this.state.messages.findIndex(function(m) {
|
|
1223
|
+
return m._serverItemId === serverId && m.isPending && m.role === "assistant" && !m.isBackgroundTask;
|
|
1224
|
+
});
|
|
1225
|
+
if (thById !== -1) this.state.messages.splice(thById, 1);
|
|
1226
|
+
else if (userIdx >= 0) {
|
|
1227
|
+
var thPos = this.state.messages.findIndex(function(m, i) {
|
|
1228
|
+
return i > userIdx && m.isPending && m.role === "assistant" && !m.isBackgroundTask;
|
|
1229
|
+
});
|
|
1230
|
+
if (thPos !== -1) this.state.messages.splice(thPos, 1);
|
|
1231
|
+
}
|
|
1232
|
+
} else if (userIdx >= 0) {
|
|
1233
|
+
var thPos2 = this.state.messages.findIndex(function(m, i) {
|
|
1234
|
+
return i > userIdx && m.isPending && m.role === "assistant" && !m.isBackgroundTask;
|
|
1235
|
+
});
|
|
1236
|
+
if (thPos2 !== -1) this.state.messages.splice(thPos2, 1);
|
|
1237
|
+
}
|
|
1238
|
+
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
1239
|
+
this.promoteNextQueuedToRunning();
|
|
1240
|
+
this.updateHistoryCache();
|
|
1241
|
+
this.host.notify();
|
|
1242
|
+
this.host.scrollToBottom(true);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
var targetIdx = this.resolveQueuedUserBubble(serverId);
|
|
1246
|
+
if (targetIdx === void 0) {
|
|
1247
|
+
this.host.notify();
|
|
1248
|
+
this.updateHistoryCache();
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
this.insertAtTarget({ role: "assistant", content: getErrorMessage(err), isError: true }, targetIdx);
|
|
1252
|
+
this.promoteNextQueuedToRunning();
|
|
1253
|
+
this.updateHistoryCache();
|
|
1254
|
+
this.host.notify();
|
|
1255
|
+
this.host.scrollToBottom(true);
|
|
1256
|
+
}
|
|
1257
|
+
cancelQueuedMessage(msg, idx) {
|
|
1258
|
+
var self = this;
|
|
1259
|
+
var id = this.host.getIdentity();
|
|
1260
|
+
var serverId = msg._serverItemId;
|
|
1261
|
+
if (!serverId || msg._cancelling) return;
|
|
1262
|
+
var platform = id.platform;
|
|
1263
|
+
if (platform !== "claude" && platform !== "openai") return;
|
|
1264
|
+
var url = platform === "claude" ? ANTHROPIC_MESSAGES_API_URL : OPENAI_RESPONSES_API_URL;
|
|
1265
|
+
var queueBase = id.userId || id.serviceId;
|
|
1266
|
+
var queue = msg.isBackgroundTask || msg._useBgQueue ? queueBase + BG_INDEXING_QUEUE_SUFFIX : queueBase;
|
|
1267
|
+
this.state.messages[idx] = Object.assign({}, msg, { _cancelling: true, _cancelError: void 0 });
|
|
1268
|
+
this.host.notify();
|
|
1269
|
+
Promise.resolve(this.host.cancelRequest({
|
|
1270
|
+
url,
|
|
1271
|
+
method: "POST",
|
|
1272
|
+
id: serverId,
|
|
1273
|
+
queue,
|
|
1274
|
+
service: id.serviceId,
|
|
1275
|
+
owner: id.owner
|
|
1276
|
+
})).then(function(result) {
|
|
1277
|
+
if (result && result.removed) {
|
|
1278
|
+
self.cancelledServerIds.add(serverId);
|
|
1279
|
+
var qi = self.bgTaskQueue.findIndex(function(e) {
|
|
1280
|
+
return e.id === serverId;
|
|
1281
|
+
});
|
|
1282
|
+
if (qi !== -1) self.bgTaskQueue.splice(qi, 1);
|
|
1283
|
+
var removeIdx = self.state.messages.findIndex(function(m) {
|
|
1284
|
+
return m._serverItemId === serverId && (m.isPendingQueued || m.isPendingInProcess) && m.role === "user";
|
|
1285
|
+
});
|
|
1286
|
+
if (removeIdx !== -1) {
|
|
1287
|
+
self.state.messages[removeIdx] = { role: "user", content: self.state.messages[removeIdx].content, isCancelled: true, _serverItemId: serverId };
|
|
1288
|
+
var thById = self.state.messages.findIndex(function(m) {
|
|
1289
|
+
return m._serverItemId === serverId && m.isPending && m.role === "assistant";
|
|
1290
|
+
});
|
|
1291
|
+
if (thById !== -1) self.state.messages.splice(thById, 1);
|
|
1292
|
+
else {
|
|
1293
|
+
var thPos = self.state.messages.findIndex(function(m, i) {
|
|
1294
|
+
return i > removeIdx && m.isPending && m.role === "assistant" && (msg.isBackgroundTask ? !!m.isBackgroundTask : !m.isBackgroundTask);
|
|
1295
|
+
});
|
|
1296
|
+
if (thPos !== -1) self.state.messages.splice(thPos, 1);
|
|
1297
|
+
}
|
|
1298
|
+
if (msg.isBackgroundTask) self.promoteNextBgQueuedToRunning();
|
|
1299
|
+
else self.promoteNextQueuedToRunning();
|
|
1300
|
+
self.updateHistoryCache();
|
|
1301
|
+
}
|
|
1302
|
+
self.host.notify();
|
|
1303
|
+
} else {
|
|
1304
|
+
var errMsg = result && typeof result.message === "string" && result.message ? result.message : "Could not remove from queue.";
|
|
1305
|
+
var ci = self.state.messages.findIndex(function(m) {
|
|
1306
|
+
return m._serverItemId === serverId && m.role === "user";
|
|
1307
|
+
});
|
|
1308
|
+
if (ci !== -1) {
|
|
1309
|
+
self.state.messages[ci] = Object.assign({}, self.state.messages[ci], { _cancelling: false, _cancelError: errMsg });
|
|
1310
|
+
self.host.notify();
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}).catch(function(err) {
|
|
1314
|
+
var errMsg = err && typeof err.message === "string" && err.message ? err.message : "Could not remove from queue.";
|
|
1315
|
+
var ci = self.state.messages.findIndex(function(m) {
|
|
1316
|
+
return m._serverItemId === serverId && m.role === "user";
|
|
1317
|
+
});
|
|
1318
|
+
if (ci !== -1) {
|
|
1319
|
+
self.state.messages[ci] = Object.assign({}, self.state.messages[ci], { _cancelling: false, _cancelError: errMsg });
|
|
1320
|
+
self.host.notify();
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
// --- typewriter -------------------------------------------------------
|
|
1325
|
+
typewriteIntoIndex(idx, fullText, localId) {
|
|
1326
|
+
var self = this;
|
|
1327
|
+
if (!fullText) return Promise.resolve();
|
|
1328
|
+
var TICK_MS = 16, charsPerTick = 3, FENCE_REVEAL_MS = 200;
|
|
1329
|
+
var fenceTicks = Math.max(1, Math.floor(FENCE_REVEAL_MS / TICK_MS));
|
|
1330
|
+
var fenceRegions = [], m;
|
|
1331
|
+
var fenceRegex = /```[\w.-]+\.[a-zA-Z0-9]+\n[\s\S]*?```/g;
|
|
1332
|
+
while ((m = fenceRegex.exec(fullText)) !== null) fenceRegions.push({ start: m.index, end: m.index + m[0].length });
|
|
1333
|
+
var linkRegions = [], lm;
|
|
1334
|
+
var linkRegex = createInlineLinkRegex();
|
|
1335
|
+
while ((lm = linkRegex.exec(fullText)) !== null) linkRegions.push({ start: lm.index, end: lm.index + lm[0].length });
|
|
1336
|
+
this.state.typing = true;
|
|
1337
|
+
this.state.typingAbort = false;
|
|
1338
|
+
var i = 0;
|
|
1339
|
+
return (function loop() {
|
|
1340
|
+
if (self.state.typingAbort || i >= fullText.length) return Promise.resolve();
|
|
1341
|
+
var step = charsPerTick;
|
|
1342
|
+
var region = fenceRegions.find(function(r) {
|
|
1343
|
+
return i >= r.start && i < r.end;
|
|
1344
|
+
});
|
|
1345
|
+
var linkRegion = linkRegions.find(function(r) {
|
|
1346
|
+
return i >= r.start && i < r.end;
|
|
1347
|
+
});
|
|
1348
|
+
if (region) step = Math.max(charsPerTick, Math.ceil((region.end - i) / fenceTicks));
|
|
1349
|
+
else if (linkRegion) step = Math.max(charsPerTick, linkRegion.end - i);
|
|
1350
|
+
else {
|
|
1351
|
+
var nextLink = linkRegions.find(function(r) {
|
|
1352
|
+
return i < r.start && i + step > r.start;
|
|
1353
|
+
});
|
|
1354
|
+
if (nextLink) step = nextLink.end - i;
|
|
1355
|
+
}
|
|
1356
|
+
i = Math.min(fullText.length, i + step);
|
|
1357
|
+
var currentIdx = localId ? self.state.messages.findIndex(function(mm) {
|
|
1358
|
+
return mm._localId === localId;
|
|
1359
|
+
}) : idx;
|
|
1360
|
+
if (currentIdx === -1) return Promise.resolve();
|
|
1361
|
+
var target = self.state.messages[currentIdx];
|
|
1362
|
+
if (!target) return Promise.resolve();
|
|
1363
|
+
target.content = fullText.slice(0, i);
|
|
1364
|
+
self.host.refreshMessageBubble(currentIdx);
|
|
1365
|
+
return Promise.resolve(self.host.scrollToBottomIfSticky()).then(function() {
|
|
1366
|
+
return sleep(TICK_MS);
|
|
1367
|
+
}).then(loop);
|
|
1368
|
+
})().then(function() {
|
|
1369
|
+
if (!self.state.typingAbort) {
|
|
1370
|
+
var fi = localId ? self.state.messages.findIndex(function(mm) {
|
|
1371
|
+
return mm._localId === localId;
|
|
1372
|
+
}) : idx;
|
|
1373
|
+
var t = fi !== -1 ? self.state.messages[fi] : self.state.messages[idx];
|
|
1374
|
+
if (t) {
|
|
1375
|
+
t.content = fullText;
|
|
1376
|
+
self.host.refreshMessageBubble(fi !== -1 ? fi : idx);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
self.state.typing = false;
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
enqueueTypewrite(idx, fullText, localId) {
|
|
1383
|
+
var self = this;
|
|
1384
|
+
this.typewriterQueue = this.typewriterQueue.then(function() {
|
|
1385
|
+
return self.typewriteIntoIndex(idx, fullText, localId);
|
|
1386
|
+
});
|
|
1387
|
+
return this.typewriterQueue;
|
|
1388
|
+
}
|
|
1389
|
+
// --- cache+resume immediate-send rendering -----------------------------
|
|
1390
|
+
// Render the just-resolved reply (read from aiChatHistoryCache) into the
|
|
1391
|
+
// pending assistant bubble, character-by-character. Runs AFTER the reply is
|
|
1392
|
+
// already in the cache (dispatchAgentRequest appended it); errors are shown
|
|
1393
|
+
// instantly (no typing). Promotes the next queued message immediately so its
|
|
1394
|
+
// "Thinking…" bubble appears without waiting for this typewriter to finish.
|
|
1395
|
+
typewriteLatestReply(key) {
|
|
1396
|
+
var cached = this.aiChatHistoryCache[key];
|
|
1397
|
+
if (!cached || !cached.messages.length) return Promise.resolve();
|
|
1398
|
+
var latest;
|
|
1399
|
+
for (var i = cached.messages.length - 1; i >= 0; i--) {
|
|
1400
|
+
var m = cached.messages[i];
|
|
1401
|
+
if (m.role === "assistant" && !m.isPending) {
|
|
1402
|
+
latest = m;
|
|
1403
|
+
break;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
if (!latest) return Promise.resolve();
|
|
1407
|
+
var pendingIdx = this.state.messages.findIndex(function(mm) {
|
|
1408
|
+
return mm.isPending && mm.role === "assistant" && !mm.isBackgroundTask;
|
|
1409
|
+
});
|
|
1410
|
+
if (pendingIdx === -1) return Promise.resolve();
|
|
1411
|
+
if (latest.isError || !latest.content) {
|
|
1412
|
+
this.state.messages[pendingIdx] = { role: "assistant", content: latest.content || "", isError: !!latest.isError };
|
|
1413
|
+
this.host.notify();
|
|
1414
|
+
this.promoteNextQueuedToRunning();
|
|
1415
|
+
return Promise.resolve();
|
|
1416
|
+
}
|
|
1417
|
+
var lid = this._newLocalId();
|
|
1418
|
+
this.state.messages[pendingIdx] = { role: "assistant", content: "", isPending: false, _localId: lid };
|
|
1419
|
+
this.host.notify();
|
|
1420
|
+
this.promoteNextQueuedToRunning();
|
|
1421
|
+
return this.enqueueTypewrite(pendingIdx, latest.content, lid);
|
|
1422
|
+
}
|
|
1423
|
+
// If an immediate-send request for the current cache key is still in flight
|
|
1424
|
+
// (e.g. the view unmounted then remounted mid-request), show the sending
|
|
1425
|
+
// state, await it, then render the reply from the cache. Skipped when the
|
|
1426
|
+
// list already has its own pending/queued bubbles (those resolve via their
|
|
1427
|
+
// own polls). The displayed reply also lands via dispatchComposedMessage's
|
|
1428
|
+
// own finally if the view never unmounted — this is the remount recovery.
|
|
1429
|
+
resumePendingRequest(token) {
|
|
1430
|
+
var self = this;
|
|
1431
|
+
var key = this.getHistoryCacheKey();
|
|
1432
|
+
var pending = key ? this.pendingAgentRequests[key] : void 0;
|
|
1433
|
+
if (!pending) return Promise.resolve();
|
|
1434
|
+
if (this.state.messages.some(function(m) {
|
|
1435
|
+
return (m.isPending || m.isPendingQueued) && !m.isBackgroundTask && !m._useBgQueue;
|
|
1436
|
+
})) return Promise.resolve();
|
|
1437
|
+
this.state.sending = true;
|
|
1438
|
+
this.host.scrollToBottom(true);
|
|
1439
|
+
return Promise.resolve(pending).catch(function() {
|
|
1440
|
+
}).then(function() {
|
|
1441
|
+
if (token !== self.state.gateRefreshToken) return;
|
|
1442
|
+
self.state.sending = false;
|
|
1443
|
+
return Promise.resolve(self.typewriteLatestReply(key)).then(function() {
|
|
1444
|
+
self.host.scrollToBottom(true);
|
|
1445
|
+
});
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
// --- background-task resolution + drain -------------------------------
|
|
1449
|
+
handleHistoryItemResolution(itemId, response, platform) {
|
|
1450
|
+
this.applyHistoryItemResolution(itemId, response, platform);
|
|
1451
|
+
this.promoteNextBgQueuedToRunning();
|
|
1452
|
+
}
|
|
1453
|
+
applyHistoryItemResolution(itemId, response, platform) {
|
|
1454
|
+
this.historyItemPolls.delete(itemId);
|
|
1455
|
+
var isErr = isErrorResponseBody(response);
|
|
1456
|
+
var answer = isErr ? getErrorMessage(response) : ((platform === "openai" ? extractOpenAIText(response) : extractClaudeText(response)) || "").trim();
|
|
1457
|
+
var idx = this.state.messages.findIndex(function(m) {
|
|
1458
|
+
return m.isPending && m._serverItemId === itemId;
|
|
1459
|
+
});
|
|
1460
|
+
if (idx !== -1) {
|
|
1461
|
+
if (isErr) {
|
|
1462
|
+
this.state.messages[idx] = { role: "assistant", content: answer, isError: true, _serverItemId: itemId };
|
|
1463
|
+
this.host.notify();
|
|
1464
|
+
this.updateHistoryCache();
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
var lid = this._newLocalId();
|
|
1468
|
+
this.state.messages[idx] = { role: "assistant", content: "", _localId: lid, _serverItemId: itemId };
|
|
1469
|
+
this.host.notify();
|
|
1470
|
+
this.enqueueTypewrite(idx, answer || "No text response received from AI provider.", lid);
|
|
1471
|
+
this.updateHistoryCache();
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
var userIdx = this.state.messages.findIndex(function(m) {
|
|
1475
|
+
return m.role === "user" && m._serverItemId === itemId && (m.isPendingQueued || m.isPendingInProcess);
|
|
1476
|
+
});
|
|
1477
|
+
if (userIdx === -1) return;
|
|
1478
|
+
var ex = this.state.messages[userIdx];
|
|
1479
|
+
this.state.messages[userIdx] = { role: "user", content: ex.content, _serverItemId: itemId };
|
|
1480
|
+
if (isErr) {
|
|
1481
|
+
this.state.messages.splice(userIdx + 1, 0, { role: "assistant", content: answer, isError: true, _serverItemId: itemId });
|
|
1482
|
+
this.host.notify();
|
|
1483
|
+
this.updateHistoryCache();
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
var lid2 = this._newLocalId();
|
|
1487
|
+
this.state.messages.splice(userIdx + 1, 0, { role: "assistant", content: "", _localId: lid2, _serverItemId: itemId });
|
|
1488
|
+
this.host.notify();
|
|
1489
|
+
this.enqueueTypewrite(userIdx + 1, answer || "No text response received from AI provider.", lid2);
|
|
1490
|
+
this.updateHistoryCache();
|
|
1491
|
+
}
|
|
1492
|
+
// Inject "Indexing: <file>" bubbles for queued bg tasks + attach their polls.
|
|
1493
|
+
drainBgTaskQueue() {
|
|
1494
|
+
var self = this;
|
|
1495
|
+
var id = this.host.getIdentity();
|
|
1496
|
+
var svcId = id.serviceId, plat = id.platform;
|
|
1497
|
+
if (!svcId || plat === "none" || !this.host.isViewMounted()) return;
|
|
1498
|
+
for (var i = this.bgTaskQueue.length - 1; i >= 0; i--) {
|
|
1499
|
+
var e = this.bgTaskQueue[i];
|
|
1500
|
+
if (e.serviceId !== svcId || e.platform !== plat) continue;
|
|
1501
|
+
var present = this.state.messages.some(function(m) {
|
|
1502
|
+
return m._serverItemId === e.id;
|
|
1503
|
+
});
|
|
1504
|
+
var stillPending = this.state.messages.some(function(m) {
|
|
1505
|
+
return m._serverItemId === e.id && (m.isPending || m.isPendingInProcess || m.isPendingQueued);
|
|
1506
|
+
});
|
|
1507
|
+
if (present && !stillPending) this.bgTaskQueue.splice(i, 1);
|
|
1508
|
+
}
|
|
1509
|
+
this.bgTaskQueue.forEach(function(entry) {
|
|
1510
|
+
if (entry.serviceId !== svcId || entry.platform !== plat) return;
|
|
1511
|
+
if (self.state.messages.some(function(m) {
|
|
1512
|
+
return m._serverItemId === entry.id;
|
|
1513
|
+
})) return;
|
|
1514
|
+
var isRunning = entry.status === "running";
|
|
1515
|
+
var userBubble = { role: "user", content: self.host.formatIndexingLabel(entry.filename, entry.mime, entry.size, entry.storagePath, entry.isReindex), isBackgroundTask: true, _serverItemId: entry.id };
|
|
1516
|
+
if (isRunning) userBubble.isPendingInProcess = true;
|
|
1517
|
+
else userBubble.isPendingQueued = true;
|
|
1518
|
+
self.state.messages.push(userBubble);
|
|
1519
|
+
if (isRunning) {
|
|
1520
|
+
self.state.messages.push({ role: "assistant", content: "", isPending: true, isPendingInProcess: true, isBackgroundTask: true, _serverItemId: entry.id });
|
|
1521
|
+
}
|
|
1522
|
+
self.host.notify();
|
|
1523
|
+
self.updateHistoryCache();
|
|
1524
|
+
self.host.scrollToBottom(false);
|
|
1525
|
+
if (!self.historyItemPolls.has(entry.id) && typeof entry.poll === "function") {
|
|
1526
|
+
self.historyItemPolls.set(entry.id, true);
|
|
1527
|
+
var capturedId = entry.id, capturedPlat = plat;
|
|
1528
|
+
entry.poll({ latency: POLL_INTERVAL }).then(function(response) {
|
|
1529
|
+
self.handleHistoryItemResolution(capturedId, response, capturedPlat);
|
|
1530
|
+
}).catch(function(err) {
|
|
1531
|
+
self.historyItemPolls.delete(capturedId);
|
|
1532
|
+
var isNotExists = err && (err.code === "NOT_EXISTS" || err.body && err.body.code === "NOT_EXISTS");
|
|
1533
|
+
var bi = self.state.messages.findIndex(function(m) {
|
|
1534
|
+
return m.isPending && m._serverItemId === capturedId;
|
|
1535
|
+
});
|
|
1536
|
+
if (bi !== -1) {
|
|
1537
|
+
if (isNotExists) self.state.messages.splice(bi, 1);
|
|
1538
|
+
else self.state.messages[bi] = { role: "assistant", content: getErrorMessage(err), isError: true, isBackgroundTask: true, _serverItemId: capturedId };
|
|
1539
|
+
self.host.notify();
|
|
1540
|
+
self.updateHistoryCache();
|
|
1541
|
+
}
|
|
1542
|
+
}).then(function() {
|
|
1543
|
+
var qi = self.bgTaskQueue.findIndex(function(q) {
|
|
1544
|
+
return q.id === capturedId;
|
|
1545
|
+
});
|
|
1546
|
+
if (qi !== -1) self.bgTaskQueue.splice(qi, 1);
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
});
|
|
1550
|
+
this.promoteNextBgQueuedToRunning();
|
|
1551
|
+
}
|
|
1552
|
+
// --- history fetch + pagination --------------------------------------
|
|
1553
|
+
// Initial load (fetchMore=false) replaces the list (with in-flight rescue +
|
|
1554
|
+
// cancelled-merge) and attaches polls to running/pending items; pagination
|
|
1555
|
+
// (fetchMore=true) prepends older messages. The DOM scroll-restore for the
|
|
1556
|
+
// older-prepend is the VIEW's job (it captures the pre-prepend scroll position
|
|
1557
|
+
// and restores after this resolves) — the engine never measures the DOM.
|
|
1558
|
+
loadHistory(fetchMore, token) {
|
|
1559
|
+
var self = this;
|
|
1560
|
+
var id = this.host.getIdentity();
|
|
1561
|
+
if (token === void 0) token = this.state.gateRefreshToken;
|
|
1562
|
+
if (this.state.loadingHistory && this.state.historyRequestToken === token || id.platform === "none" || !id.serviceId) {
|
|
1563
|
+
return Promise.resolve();
|
|
1564
|
+
}
|
|
1565
|
+
this.state.historyRequestToken = token;
|
|
1566
|
+
this.state.loadingHistory = true;
|
|
1567
|
+
if (fetchMore) this.state.loadingOlderHistory = true;
|
|
1568
|
+
this.host.notify();
|
|
1569
|
+
var platform = id.platform;
|
|
1570
|
+
var serviceId = id.serviceId, owner = id.owner;
|
|
1571
|
+
var options = { fetchMore };
|
|
1572
|
+
if (fetchMore && this.state.historyStartKeyHistory.length) options.startKeyHistory = this.state.historyStartKeyHistory.slice();
|
|
1573
|
+
var fetchHistory = function() {
|
|
1574
|
+
return getChatHistory({ service: serviceId, owner, platform }, options);
|
|
1575
|
+
};
|
|
1576
|
+
return Promise.resolve().then(fetchHistory).catch(function(err) {
|
|
1577
|
+
if (isAuthExpiredError(err)) return self.host.refreshSession().then(fetchHistory);
|
|
1578
|
+
throw err;
|
|
1579
|
+
}).then(function(history) {
|
|
1580
|
+
if (token !== self.state.gateRefreshToken) return;
|
|
1581
|
+
var chatList = history && Array.isArray(history.list) ? history.list : [];
|
|
1582
|
+
chatList.forEach(function(item) {
|
|
1583
|
+
if (typeof item.queue_name === "string" && item.queue_name.slice(-BG_INDEXING_QUEUE_SUFFIX.length) === BG_INDEXING_QUEUE_SUFFIX) {
|
|
1584
|
+
var userText = extractLastUserTextFromRequest(item.request_body);
|
|
1585
|
+
if (typeof userText === "string" && userText.indexOf("A new file has just been uploaded") === 0) item._isBgTask = true;
|
|
1586
|
+
else item._isOnBgQueue = true;
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
var list = chatList.sort(function(a, b) {
|
|
1590
|
+
var ai = typeof a.id === "string" ? a.id : "", bi = typeof b.id === "string" ? b.id : "";
|
|
1591
|
+
return ai > bi ? -1 : ai < bi ? 1 : 0;
|
|
1592
|
+
});
|
|
1593
|
+
var mapped = mapHistoryListToMessages(list, platform, {
|
|
1594
|
+
clearedAt: self.host.getClearedAt(),
|
|
1595
|
+
serviceId: id.serviceId,
|
|
1596
|
+
formatIndexingLabel: self.host.formatIndexingLabel
|
|
1597
|
+
}).messages;
|
|
1598
|
+
if (fetchMore) {
|
|
1599
|
+
self.state.messages = mapped.concat(self.state.messages);
|
|
1600
|
+
} else {
|
|
1601
|
+
if (self.state.typing) self.state.typingAbort = true;
|
|
1602
|
+
var serverIds = {};
|
|
1603
|
+
mapped.forEach(function(m) {
|
|
1604
|
+
if (m._serverItemId) serverIds[m._serverItemId] = 1;
|
|
1605
|
+
});
|
|
1606
|
+
var locallyCancelled = {};
|
|
1607
|
+
self.state.messages.forEach(function(m) {
|
|
1608
|
+
if (m.isCancelled && m._serverItemId) locallyCancelled[m._serverItemId] = 1;
|
|
1609
|
+
});
|
|
1610
|
+
var rescued = [];
|
|
1611
|
+
for (var ri = 0; ri < self.state.messages.length; ri++) {
|
|
1612
|
+
var mm = self.state.messages[ri];
|
|
1613
|
+
if (mm.isBackgroundTask) continue;
|
|
1614
|
+
if (mm._serverItemId && serverIds[mm._serverItemId]) continue;
|
|
1615
|
+
if (!mm._serverItemId) {
|
|
1616
|
+
if (mm.isSendingToServer || mm.isPendingQueued || mm.isPendingInProcess || mm.isPending) rescued.push(mm);
|
|
1617
|
+
else if (self.state.sending && mm.role === "user") {
|
|
1618
|
+
var next = self.state.messages[ri + 1];
|
|
1619
|
+
if (next && !next.isBackgroundTask && next.isPending && !next._serverItemId) rescued.push(mm);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
self.state.messages = mapped;
|
|
1624
|
+
rescued.forEach(function(m) {
|
|
1625
|
+
self.state.messages.push(m);
|
|
1626
|
+
});
|
|
1627
|
+
if (Object.keys(locallyCancelled).length) {
|
|
1628
|
+
for (var ci = 0; ci < self.state.messages.length; ci++) {
|
|
1629
|
+
var c = self.state.messages[ci];
|
|
1630
|
+
if (!c._serverItemId || !locallyCancelled[c._serverItemId] || c.isCancelled) continue;
|
|
1631
|
+
self.state.messages[ci] = { role: "user", content: c.content, isCancelled: true, _serverItemId: c._serverItemId };
|
|
1632
|
+
if (ci + 1 < self.state.messages.length && self.state.messages[ci + 1].isPending && self.state.messages[ci + 1]._serverItemId === c._serverItemId) {
|
|
1633
|
+
self.state.messages.splice(ci + 1, 1);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
self.state.historyEndOfList = !!(history && history.endOfList);
|
|
1639
|
+
self.state.historyStartKeyHistory = history && Array.isArray(history.startKeyHistory) ? history.startKeyHistory : [];
|
|
1640
|
+
var clearedAt = self.host.getClearedAt();
|
|
1641
|
+
if (clearedAt && chatList.length > 0) {
|
|
1642
|
+
var oldestUpdated = Number(chatList[chatList.length - 1] && chatList[chatList.length - 1].updated);
|
|
1643
|
+
if (isFinite(oldestUpdated) && oldestUpdated <= clearedAt) self.state.historyEndOfList = true;
|
|
1644
|
+
}
|
|
1645
|
+
if (self.state.historyRequestToken === token) {
|
|
1646
|
+
self.state.loadingHistory = false;
|
|
1647
|
+
self.state.loadingOlderHistory = false;
|
|
1648
|
+
}
|
|
1649
|
+
self.updateHistoryCache();
|
|
1650
|
+
self.host.notify();
|
|
1651
|
+
if (!fetchMore) {
|
|
1652
|
+
chatList.forEach(function(item) {
|
|
1653
|
+
if (item.status !== "running" && item.status !== "pending") return;
|
|
1654
|
+
if (!item.poll || !item.id) return;
|
|
1655
|
+
if (self.historyItemPolls.has(item.id)) return;
|
|
1656
|
+
if (item.status === "running" && self.pendingAgentRequests[self.getHistoryCacheKey()]) return;
|
|
1657
|
+
self.historyItemPolls.set(item.id, true);
|
|
1658
|
+
var capturedId = item.id;
|
|
1659
|
+
var pp = item.poll({
|
|
1660
|
+
latency: POLL_INTERVAL,
|
|
1661
|
+
onResponse: function(response) {
|
|
1662
|
+
self.handleHistoryItemResolution(capturedId, response, platform);
|
|
1663
|
+
},
|
|
1664
|
+
onError: function(err) {
|
|
1665
|
+
self.historyItemPolls.delete(capturedId);
|
|
1666
|
+
var isNotExists = err && (err.code === "NOT_EXISTS" || err.body && err.body.code === "NOT_EXISTS");
|
|
1667
|
+
var aIdx = self.state.messages.findIndex(function(m) {
|
|
1668
|
+
return m.isPending && m._serverItemId === capturedId;
|
|
1669
|
+
});
|
|
1670
|
+
if (isNotExists) {
|
|
1671
|
+
var isBg = aIdx !== -1 ? !!self.state.messages[aIdx].isBackgroundTask : false;
|
|
1672
|
+
if (aIdx !== -1) self.state.messages.splice(aIdx, 1);
|
|
1673
|
+
if (!isBg) {
|
|
1674
|
+
var uIdx = self.state.messages.findIndex(function(m) {
|
|
1675
|
+
return m.role === "user" && m._serverItemId === capturedId && !m.isCancelled;
|
|
1676
|
+
});
|
|
1677
|
+
if (uIdx !== -1) {
|
|
1678
|
+
var ex = self.state.messages[uIdx];
|
|
1679
|
+
self.state.messages[uIdx] = { role: "user", content: ex.content, isCancelled: true, _serverItemId: ex._serverItemId };
|
|
1680
|
+
}
|
|
1681
|
+
self.cancelledServerIds.delete(capturedId);
|
|
1682
|
+
self.promoteNextQueuedToRunning();
|
|
1683
|
+
}
|
|
1684
|
+
self.host.notify();
|
|
1685
|
+
self.updateHistoryCache();
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
if (aIdx !== -1) {
|
|
1689
|
+
var wasBg = self.state.messages[aIdx].isBackgroundTask;
|
|
1690
|
+
self.state.messages[aIdx] = { role: "assistant", content: getErrorMessage(err), isError: true };
|
|
1691
|
+
if (wasBg) self.state.messages[aIdx].isBackgroundTask = true;
|
|
1692
|
+
self.host.notify();
|
|
1693
|
+
self.updateHistoryCache();
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
});
|
|
1697
|
+
if (pp && pp.catch) pp.catch(function() {
|
|
1698
|
+
});
|
|
1699
|
+
});
|
|
1700
|
+
self.drainBgTaskQueue();
|
|
1701
|
+
}
|
|
1702
|
+
if (!fetchMore) return self.host.scrollToBottom();
|
|
1703
|
+
}).catch(function(err) {
|
|
1704
|
+
console.warn("[chat-engine] getChatHistory failed", err);
|
|
1705
|
+
}).then(function() {
|
|
1706
|
+
if (self.state.historyRequestToken === token) {
|
|
1707
|
+
var wasLoading = self.state.loadingHistory || self.state.loadingOlderHistory;
|
|
1708
|
+
self.state.loadingHistory = false;
|
|
1709
|
+
self.state.loadingOlderHistory = false;
|
|
1710
|
+
if (wasLoading) self.host.notify();
|
|
1711
|
+
}
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
// --- attachment upload orchestration ---------------------------------
|
|
1715
|
+
// Upload one attachment (a file = 1 member, a folder = N) to db storage and
|
|
1716
|
+
// queue indexing per member. The bytes I/O + chip rendering go through host
|
|
1717
|
+
// hooks; the overwrite/reindex flow, status lifecycle, and indexing live here.
|
|
1718
|
+
uploadSingleAttachment(att) {
|
|
1719
|
+
var self = this;
|
|
1720
|
+
var id = this.host.getIdentity();
|
|
1721
|
+
att.status = "uploading";
|
|
1722
|
+
att.progress = 0;
|
|
1723
|
+
att.errorMessage = "";
|
|
1724
|
+
this.host.renderAttachmentChips();
|
|
1725
|
+
var members = att.kind === "folder" ? (att.files || []).map(function(f) {
|
|
1726
|
+
return { file: f.file, relPath: f.path, storagePath: self.host.storagePathFor(f.path) };
|
|
1727
|
+
}) : [{ file: att.file, relPath: att.name, storagePath: this.host.storagePathFor(att.name) }];
|
|
1728
|
+
var total = members.length;
|
|
1729
|
+
if (!total) return Promise.reject(new Error("Empty attachment"));
|
|
1730
|
+
var urls = [];
|
|
1731
|
+
var anyIndexFailed = false;
|
|
1732
|
+
var chain = Promise.resolve();
|
|
1733
|
+
members.forEach(function(member, idx) {
|
|
1734
|
+
chain = chain.then(function() {
|
|
1735
|
+
var hadExists = false;
|
|
1736
|
+
var onProg = function(p) {
|
|
1737
|
+
if (p && p.total) {
|
|
1738
|
+
att.progress = Math.floor((idx + p.loaded / p.total) / total * 100);
|
|
1739
|
+
self.host.renderAttachmentChips();
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
1742
|
+
var doMemberUpload = function(checkExistence) {
|
|
1743
|
+
return self.host.uploadFile({
|
|
1744
|
+
file: member.file,
|
|
1745
|
+
storagePath: member.storagePath,
|
|
1746
|
+
checkExistence,
|
|
1747
|
+
onProgress: onProg,
|
|
1748
|
+
setAbort: function(abort) {
|
|
1749
|
+
att._abort = abort;
|
|
1750
|
+
}
|
|
1751
|
+
});
|
|
1752
|
+
};
|
|
1753
|
+
return doMemberUpload(true).catch(function(err) {
|
|
1754
|
+
var code = err && (err.code || err.body && err.body.code);
|
|
1755
|
+
var msg = err && (err.message || err.body && err.body.message || (typeof err === "string" ? err : ""));
|
|
1756
|
+
var isExists = code === "EXISTS" || msg && /exist/i.test(msg);
|
|
1757
|
+
if (!isExists) throw err;
|
|
1758
|
+
return self.host.promptOverwrite(member.file.name).then(function(choice) {
|
|
1759
|
+
if (choice === "overwrite") return doMemberUpload(false);
|
|
1760
|
+
hadExists = true;
|
|
1761
|
+
});
|
|
1762
|
+
}).then(function() {
|
|
1763
|
+
return self.host.getTemporaryUrl(member.storagePath);
|
|
1764
|
+
}).then(function(url) {
|
|
1765
|
+
urls.push({ name: member.relPath, url, storagePath: member.storagePath });
|
|
1766
|
+
if (att.kind !== "folder") {
|
|
1767
|
+
att.uploadedUrl = url;
|
|
1768
|
+
att.storagePath = member.storagePath;
|
|
1769
|
+
}
|
|
1770
|
+
var mime = member.file.type || self.host.getMimeType(member.file.name);
|
|
1771
|
+
return notifyAgentSaveAttachment({
|
|
1772
|
+
platform: id.platform,
|
|
1773
|
+
model: id.model,
|
|
1774
|
+
service: id.serviceId,
|
|
1775
|
+
owner: id.owner,
|
|
1776
|
+
userId: id.userId || id.serviceId,
|
|
1777
|
+
serviceName: id.serviceName,
|
|
1778
|
+
serviceDescription: id.serviceDescription,
|
|
1779
|
+
attachment: {
|
|
1780
|
+
name: member.file.name,
|
|
1781
|
+
storagePath: member.storagePath,
|
|
1782
|
+
mime: mime || void 0,
|
|
1783
|
+
size: member.file.size,
|
|
1784
|
+
url
|
|
1785
|
+
}
|
|
1786
|
+
}).then(function(ack) {
|
|
1787
|
+
if (ack && typeof ack.id === "string") {
|
|
1788
|
+
self.bgTaskQueue.push({
|
|
1789
|
+
serviceId: id.serviceId,
|
|
1790
|
+
platform: id.platform,
|
|
1791
|
+
id: ack.id,
|
|
1792
|
+
filename: member.file.name,
|
|
1793
|
+
storagePath: member.storagePath,
|
|
1794
|
+
isReindex: hadExists,
|
|
1795
|
+
mime: mime || void 0,
|
|
1796
|
+
size: member.file.size,
|
|
1797
|
+
status: ack.status === "running" ? "running" : "pending",
|
|
1798
|
+
poll: ack.poll
|
|
1799
|
+
});
|
|
1800
|
+
self.drainBgTaskQueue();
|
|
1801
|
+
}
|
|
1802
|
+
}, function(e) {
|
|
1803
|
+
console.error("[chat-engine] indexing request failed", e);
|
|
1804
|
+
anyIndexFailed = true;
|
|
1805
|
+
});
|
|
1806
|
+
});
|
|
1807
|
+
});
|
|
1808
|
+
});
|
|
1809
|
+
return chain.then(function() {
|
|
1810
|
+
att._abort = null;
|
|
1811
|
+
att.progress = 100;
|
|
1812
|
+
if (att.kind === "folder") att.uploadedUrls = urls.map(function(u) {
|
|
1813
|
+
return { path: u.name, url: u.url, storagePath: u.storagePath };
|
|
1814
|
+
});
|
|
1815
|
+
att.status = anyIndexFailed ? "indexError" : "done";
|
|
1816
|
+
if (att.status === "indexError") att.errorMessage = "File indexing failed";
|
|
1817
|
+
self.host.renderAttachmentChips();
|
|
1818
|
+
return urls;
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
// Upload all not-yet-done attachments sequentially. Resolves to the full
|
|
1822
|
+
// list of { name, url, storagePath } for composing the chat message.
|
|
1823
|
+
uploadPendingAttachments() {
|
|
1824
|
+
var self = this;
|
|
1825
|
+
this.host.resetOverwriteBatch();
|
|
1826
|
+
this.state.uploadingAttachments = true;
|
|
1827
|
+
this.host.updateComposerControls();
|
|
1828
|
+
this.host.renderAttachmentChips();
|
|
1829
|
+
var collected = [];
|
|
1830
|
+
var snapshot = this.state.attachments.slice();
|
|
1831
|
+
var chain = Promise.resolve();
|
|
1832
|
+
snapshot.forEach(function(att) {
|
|
1833
|
+
chain = chain.then(function() {
|
|
1834
|
+
if (!self.state.attachments.some(function(a) {
|
|
1835
|
+
return a.id === att.id;
|
|
1836
|
+
})) return;
|
|
1837
|
+
if (att.status === "done" || att.status === "indexError") {
|
|
1838
|
+
if (att.kind === "folder" && att.uploadedUrls) {
|
|
1839
|
+
att.uploadedUrls.forEach(function(u) {
|
|
1840
|
+
collected.push({ name: u.path, url: u.url, storagePath: u.storagePath });
|
|
1841
|
+
});
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
if (att.uploadedUrl) {
|
|
1845
|
+
collected.push({ name: att.name, url: att.uploadedUrl, storagePath: att.storagePath });
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
return self.uploadSingleAttachment(att).then(function(us) {
|
|
1850
|
+
collected.push.apply(collected, us);
|
|
1851
|
+
}).catch(function(err) {
|
|
1852
|
+
var removed = !self.state.attachments.some(function(a) {
|
|
1853
|
+
return a.id === att.id;
|
|
1854
|
+
});
|
|
1855
|
+
var aborted = err && (err.message === "Aborted" || err === "Aborted");
|
|
1856
|
+
if (removed || aborted) return;
|
|
1857
|
+
att.status = "error";
|
|
1858
|
+
att.errorMessage = "File upload has failed";
|
|
1859
|
+
self.host.renderAttachmentChips();
|
|
1860
|
+
});
|
|
1861
|
+
});
|
|
1862
|
+
});
|
|
1863
|
+
var done = function() {
|
|
1864
|
+
self.state.uploadingAttachments = false;
|
|
1865
|
+
self.host.updateComposerControls();
|
|
1866
|
+
self.host.renderAttachmentChips();
|
|
1867
|
+
return collected;
|
|
1868
|
+
};
|
|
1869
|
+
return chain.then(done, done);
|
|
1870
|
+
}
|
|
1871
|
+
// Stop timers / abort the typewriter (view teardown).
|
|
1872
|
+
stop() {
|
|
1873
|
+
this.state.typingAbort = true;
|
|
1874
|
+
}
|
|
1875
|
+
// Bump the gate token so any in-flight immediate-send result is dropped
|
|
1876
|
+
// (called by the view on a service/platform switch or history clear).
|
|
1877
|
+
bumpGate() {
|
|
1878
|
+
this.state.gateRefreshToken += 1;
|
|
1879
|
+
}
|
|
1880
|
+
};
|
|
1881
|
+
|
|
1882
|
+
export { BG_INDEXING_QUEUE_SUFFIX, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, ChatSession, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, HISTORY_TOKEN_BUDGET, LINK_LABEL_MAX_DISPLAY_CHARS, MAX_HISTORY_MESSAGES, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, OUTPUT_TOKEN_RESERVE, POLL_INTERVAL, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingSystemPrompt, buildIndexingUserMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, composeUserMessage, configureChatEngine, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, filterListByClearHorizon, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, isAuthExpiredError, isErrorResponseBody, isOfficeFile, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, notifyAgentSaveAttachment, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
|
|
1883
|
+
//# sourceMappingURL=engine.mjs.map
|
|
1884
|
+
//# sourceMappingURL=engine.mjs.map
|