bunnyquery 1.12.1 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/bunnyquery.css +31 -0
- package/bunnyquery.js +653 -147
- package/dist/engine.cjs +600 -157
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +309 -2
- package/dist/engine.d.ts +309 -2
- package/dist/engine.mjs +576 -158
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/budget.ts +43 -1
- package/src/engine/citations.ts +181 -0
- package/src/engine/config.ts +44 -0
- package/src/engine/history.ts +19 -1
- package/src/engine/host.ts +7 -0
- package/src/engine/index.ts +17 -0
- package/src/engine/project_settings.ts +40 -0
- package/src/engine/prompts/chat_system_prompt.ts +50 -1
- package/src/engine/requests.ts +64 -35
- package/src/engine/retrieval_memory.ts +348 -0
- package/src/engine/session.ts +71 -7
- package/styles/chat.css +31 -0
package/dist/engine.cjs
CHANGED
|
@@ -45,6 +45,134 @@ async function parseAttachmentContent(file, name, mime) {
|
|
|
45
45
|
return text;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// src/engine/project_settings.ts
|
|
49
|
+
var UPLOAD_ACCESS_GROUPS = ["public", "authorized", "private"];
|
|
50
|
+
var DEFAULT_UPLOAD_ACCESS_GROUP = "authorized";
|
|
51
|
+
var PROJECT_SETTINGS_TABLE = "__SETTINGS__";
|
|
52
|
+
var PROJECT_SETTINGS_UNIQUE_ID = "bq::settings";
|
|
53
|
+
var PROJECT_SETTINGS_ACCESS_GROUP = "public";
|
|
54
|
+
var UPLOAD_ACCESS_LABELS = {
|
|
55
|
+
public: "Public",
|
|
56
|
+
authorized: "Signed in users",
|
|
57
|
+
private: "Only me"
|
|
58
|
+
};
|
|
59
|
+
var UPLOAD_ACCESS_HINTS = {
|
|
60
|
+
public: "Anyone can ask about this file, including visitors who are not logged in.",
|
|
61
|
+
authorized: "Only users signed in to this project can ask about this file.",
|
|
62
|
+
private: "Only you can ask about this file."
|
|
63
|
+
};
|
|
64
|
+
var UPLOAD_ACCESS_OPTIONS = UPLOAD_ACCESS_GROUPS.map((value) => ({
|
|
65
|
+
value,
|
|
66
|
+
label: UPLOAD_ACCESS_LABELS[value],
|
|
67
|
+
hint: UPLOAD_ACCESS_HINTS[value]
|
|
68
|
+
}));
|
|
69
|
+
var DEFAULT_WEB_ACCESS = false;
|
|
70
|
+
function normalizeWebAccess(value) {
|
|
71
|
+
return value === true;
|
|
72
|
+
}
|
|
73
|
+
function webAccessFrom(data) {
|
|
74
|
+
return data ? normalizeWebAccess(data.web_access) : DEFAULT_WEB_ACCESS;
|
|
75
|
+
}
|
|
76
|
+
var CHAT_GREETING_MAX_LENGTH = 400;
|
|
77
|
+
function normalizeChatGreeting(value) {
|
|
78
|
+
if (typeof value !== "string") return "";
|
|
79
|
+
const trimmed = value.replace(/\r\n?/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
80
|
+
return trimmed.slice(0, CHAT_GREETING_MAX_LENGTH);
|
|
81
|
+
}
|
|
82
|
+
function chatGreetingFrom(data) {
|
|
83
|
+
return normalizeChatGreeting(data?.chat_greeting);
|
|
84
|
+
}
|
|
85
|
+
function normalizeUploadAccessGroup(value) {
|
|
86
|
+
return UPLOAD_ACCESS_GROUPS.indexOf(value) === -1 ? DEFAULT_UPLOAD_ACCESS_GROUP : value;
|
|
87
|
+
}
|
|
88
|
+
function normalizeProjectAccessSetting(value) {
|
|
89
|
+
if (value === "ask") return "ask";
|
|
90
|
+
return UPLOAD_ACCESS_GROUPS.indexOf(value) === -1 ? null : value;
|
|
91
|
+
}
|
|
92
|
+
function accessSettingFrom(data) {
|
|
93
|
+
return normalizeProjectAccessSetting(data?.upload_access_group);
|
|
94
|
+
}
|
|
95
|
+
function uploadAccessGroupFrom(data) {
|
|
96
|
+
const v = accessSettingFrom(data);
|
|
97
|
+
return v && v !== "ask" ? v : DEFAULT_UPLOAD_ACCESS_GROUP;
|
|
98
|
+
}
|
|
99
|
+
function asksUploadAccessFrom(data) {
|
|
100
|
+
return accessSettingFrom(data) === "ask";
|
|
101
|
+
}
|
|
102
|
+
var reader = null;
|
|
103
|
+
var cache = /* @__PURE__ */ new Map();
|
|
104
|
+
function configureProjectSettings(fn) {
|
|
105
|
+
reader = fn;
|
|
106
|
+
}
|
|
107
|
+
function entry(service) {
|
|
108
|
+
let e = cache.get(service);
|
|
109
|
+
if (!e) {
|
|
110
|
+
e = { data: null, settled: false, inflight: null };
|
|
111
|
+
cache.set(service, e);
|
|
112
|
+
}
|
|
113
|
+
return e;
|
|
114
|
+
}
|
|
115
|
+
function loadProjectSettings(service) {
|
|
116
|
+
if (!service) return Promise.resolve(null);
|
|
117
|
+
const e = entry(service);
|
|
118
|
+
if (e.settled) return Promise.resolve(e.data);
|
|
119
|
+
if (e.inflight) return e.inflight;
|
|
120
|
+
if (!reader) return Promise.resolve(null);
|
|
121
|
+
const run = reader(service).then((data) => data && typeof data === "object" ? data : null).catch(() => null).then((data) => {
|
|
122
|
+
const cur = entry(service);
|
|
123
|
+
if (cur.inflight === run) {
|
|
124
|
+
cur.data = data;
|
|
125
|
+
cur.settled = true;
|
|
126
|
+
cur.inflight = null;
|
|
127
|
+
}
|
|
128
|
+
return data;
|
|
129
|
+
});
|
|
130
|
+
e.inflight = run;
|
|
131
|
+
return run;
|
|
132
|
+
}
|
|
133
|
+
function primeProjectSettings(service) {
|
|
134
|
+
void loadProjectSettings(service);
|
|
135
|
+
}
|
|
136
|
+
function readyProjectSettings(service) {
|
|
137
|
+
return loadProjectSettings(service);
|
|
138
|
+
}
|
|
139
|
+
function cachedProjectSettings(service) {
|
|
140
|
+
const e = cache.get(service);
|
|
141
|
+
return e && e.settled ? e.data : null;
|
|
142
|
+
}
|
|
143
|
+
function projectSettingsSettled(service) {
|
|
144
|
+
const e = cache.get(service);
|
|
145
|
+
return !!e && e.settled;
|
|
146
|
+
}
|
|
147
|
+
function projectAccessSetting(service) {
|
|
148
|
+
return accessSettingFrom(cachedProjectSettings(service));
|
|
149
|
+
}
|
|
150
|
+
function projectUploadAccessGroup(service) {
|
|
151
|
+
return uploadAccessGroupFrom(cachedProjectSettings(service));
|
|
152
|
+
}
|
|
153
|
+
function projectAsksUploadAccess(service) {
|
|
154
|
+
return asksUploadAccessFrom(cachedProjectSettings(service));
|
|
155
|
+
}
|
|
156
|
+
function projectChatGreeting(service) {
|
|
157
|
+
return chatGreetingFrom(cachedProjectSettings(service));
|
|
158
|
+
}
|
|
159
|
+
function projectWebAccess(service) {
|
|
160
|
+
return webAccessFrom(cachedProjectSettings(service));
|
|
161
|
+
}
|
|
162
|
+
function setProjectSettings(service, data) {
|
|
163
|
+
if (!service) return;
|
|
164
|
+
cache.set(service, { data: data || null, settled: true, inflight: null });
|
|
165
|
+
}
|
|
166
|
+
function patchProjectSettings(service, patch) {
|
|
167
|
+
if (!service) return;
|
|
168
|
+
const cur = cachedProjectSettings(service) || {};
|
|
169
|
+
setProjectSettings(service, Object.assign({}, cur, patch));
|
|
170
|
+
}
|
|
171
|
+
function clearProjectSettings(service) {
|
|
172
|
+
if (service) cache.delete(service);
|
|
173
|
+
else cache.clear();
|
|
174
|
+
}
|
|
175
|
+
|
|
48
176
|
// src/engine/config.ts
|
|
49
177
|
var _config = null;
|
|
50
178
|
function configureChatEngine(config) {
|
|
@@ -110,6 +238,17 @@ function liveStreamingRealtimeEnabled() {
|
|
|
110
238
|
function liveStreamingEnabled() {
|
|
111
239
|
return _config?.liveStreaming === true;
|
|
112
240
|
}
|
|
241
|
+
function webAccessEnabled(service) {
|
|
242
|
+
const opt = _config ? _config.webAccess : void 0;
|
|
243
|
+
if (typeof opt === "boolean") return opt;
|
|
244
|
+
return webAccessFrom(service ? cachedProjectSettings(service) : null);
|
|
245
|
+
}
|
|
246
|
+
function resolveWebAccess(service) {
|
|
247
|
+
const opt = _config ? _config.webAccess : void 0;
|
|
248
|
+
if (typeof opt === "boolean") return Promise.resolve(opt);
|
|
249
|
+
if (!service) return Promise.resolve(false);
|
|
250
|
+
return loadProjectSettings(service).then(webAccessFrom, () => false);
|
|
251
|
+
}
|
|
113
252
|
function streamRecoveryEnabled() {
|
|
114
253
|
if (!_config) return false;
|
|
115
254
|
if (_config.streamRecovery === false) return false;
|
|
@@ -357,22 +496,30 @@ function groupAttachmentFailures(attachments) {
|
|
|
357
496
|
// src/engine/prompts/chat_system_prompt.ts
|
|
358
497
|
function buildChatSystemPrompt(params) {
|
|
359
498
|
const { projectId, serviceName, serviceDescription, greeting, canUpload, client } = params;
|
|
499
|
+
const webAccess = params.webAccess === true;
|
|
360
500
|
const g = params.indexAccessGroup;
|
|
361
501
|
const indexGroupLiteral = typeof g === "number" ? String(g) : g === "public" || g === "private" || g === "authorized" || g === "admin" ? `"${g}"` : '"authorized"';
|
|
502
|
+
const groundingRules = `ANSWER ONLY FROM THE DATA. Every fact you state about this project's data - a name, a number, a date, a status, what a document says, whether something exists - must come from one of four places: a tool result returned in THIS conversation, a file or image attached to it (including one you read through its attachment link), the project description at the end of this prompt, or what the user told you themselves in this chat. Your general knowledge, the open internet and a plausible guess are NOT sources. Never use them to answer a question about the user's data, to fill a gap in it, or to complete a record that came back partial. A value that is not in what you retrieved is unknown: say it is not in the data instead of supplying a likely one.
|
|
503
|
+
WHEN THE DATA DOES NOT HAVE IT, SAY SO. Once you have searched the way these rules require and nothing answers the question, tell the user plainly, in their language, that this project's data does not contain it, and say what you searched (the tables, files or terms) so they can tell missing data from a missed search. Do not answer from memory instead, and do not turn a missing answer into a general explanation of the topic. Offer what would let you answer it: ${canUpload === false ? `another name or file to look under, or asking the project's owner to add it` : `the document to upload, or another name or file to look under`}.
|
|
504
|
+
KEEP WHAT THE DATA SAYS APART FROM WHAT YOU WORKED OUT. A total you computed, a comparison, a trend, an interpretation or a recommendation is yours, not the data's: label it as such ("calculated from...", "this suggests...") and name the rows or files it was made from. Never present an estimate, an assumption or an inference as something the data states.
|
|
505
|
+
These three rules govern every claim about the project's data. They do not limit the About BunnyQuery section (answer product questions from it), a greeting, or text the user explicitly asks you to write.`;
|
|
506
|
+
const documentRules = `DOCUMENTS (a contract, a report, a manual, a letter, any uploaded file the user asks about the content of): FIRST FIND THE FILE, with getFileOutline when you know its storage path, otherwise with getUniqueId using unique_id "src::" and condition "gte" (narrow it by the name the user used). THEN FIND THE PAGE: call searchText with the file's path and the words the user asked about; it returns the file, the page and the matching excerpt. QUOTE THE EXACT TEXT it returned and CITE THE FILE AND THE PAGE ("report.pdf, page 7"); never present a paraphrase as a quote. If searchText answers no_page_text, the file was indexed before page text existed: read it with readFileContent instead, paging with the cursor until END OF FILE, and cite the file. When searchText and the file's records come back empty over files that HAVE page text, say that the data does not have it and name the files and the terms you searched. Never fill the gap from your general knowledge, and never from the web${webAccess ? ` without saying so. WEB ACCESS IS ON for this project: when the user asks for something that is not in the project's data, you may search the web for it, but every such fact must be marked as coming from the web (name the site), kept apart from what the project's data says, and never used to fill in or complete a document's content. This is the one exception to the four sources named above.` : `: this chat has no web tool.`}`;
|
|
362
507
|
let systemPrompt = `
|
|
363
508
|
You are a dedicated assistant for the project ID: "${projectId}".
|
|
364
509
|
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. The ONE exception is BunnyQuery itself - what this app is, what it can do, and how to use it - which is always in scope: answer it from the "About BunnyQuery" section at the end of this prompt.
|
|
365
510
|
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.
|
|
511
|
+
${groundingRules}
|
|
366
512
|
NUMBERS FROM A SPREADSHEET: use queryGrid, never mental arithmetic over records. A total, a count, an average, a "how many mention X", a "which one is biggest" - all of those are computed server-side over EVERY row of the file and come back with the sheet, the row count and the row numbers they were made from. Records are a SAMPLE, and a sample added up is a confident wrong number. Quote the row count and the sheet alongside the figure so the reader can check it.
|
|
367
513
|
CALL queryGrid describe FIRST, before any figure. Workbooks routinely state the same money more than once: a detail sheet, then per-song, per-album and per-artist sheets that each re-total it, plus a summary sheet whose bottom row is the file total. Those look like four different answers and are one. describe names which sheets restate which, and which rows are totals. Pick ONE sheet, say which you picked, and never add figures across a sheet and its summary. If the reply carries a warning about restatement, repeat it to the user.
|
|
368
514
|
A FILE TOTAL IS NOT A ROW'S TOTAL. The biggest number on a summary sheet is the whole file, not the thing that was asked about. Before quoting any figure, check it is scoped to what the question named: filter by the column that identifies it and report how many rows matched.
|
|
515
|
+
${documentRules}
|
|
369
516
|
Complete answers over stored data: The database holds one record per spreadsheet row, and each uploaded file becomes many records. ONE file is routinely SPLIT ACROSS SEVERAL TABLES - a summary row in one table, its page or row content in another, its extracted photos and other media in "__MEDIA__", and the indexer often invents a differently-named table on each pass. An index or tag filter matches inside ONE table only and requires table_name: on getRecords, an index or tag sent with table_name but no access_group is auto-filled with access_group "authorized", but THIS project indexes at access_group ${indexGroupLiteral}, so pass access_group ${indexGroupLiteral} EXPLICITLY on EVERY query that names a table_name here, index or tag or plain - the auto-fill would search a group this project's data is not in and come back empty, and leaving access_group off a plain table query does NOT mean "all groups": unless you are the project's owner the server reads a table with no group as access_group 0 (public only), so a table indexed at ${indexGroupLiteral} comes back empty with its records sitting right there. Files uploaded before the project's setting changed may sit at another group, so when a scoped query comes back empty, retry it across the other groups (0, 1, "private") before concluding there is nothing, while an index or tag WITHOUT table_name FAILS with an error instead of answering, so read the error rather than guessing. Reference is the exception: reference ALONE spans EVERY table and EVERY access group, so getRecords with reference "src::<the file's storage path>" is the one call that returns a whole file's records wherever the indexer put them. Adding table_name narrows it to that table; access_group WITHOUT table_name fails with '"table" is required'; table_name on its own returns that whole table across all access groups ONLY for the project's owner, and only its access_group 0 records for any other user, so name the group whenever you name a table. For anything NOT scoped to a single file, call getTables FIRST, run the query once per table that could hold the answer, and combine the results. For any request that counts, sums, totals, lists every match, compares across records, finds which one, or asks whether something is present or ABSENT (for example "how many", "total spent", "which card", "is there any", "\uC5C6\uC5B4?", "\uD558\uB098\uB3C4 \uC5C6\uB098?"), you MUST read the COMPLETE matching set before answering. Query with fetch_all set to true, or page through getToolResponsePage until pagination.complete is true, across EVERY table and EVERY relevant file. A single default query returns only the first page (about 50 records). That is a SAMPLE. Never treat it as the whole dataset. If you already answered from one table and then realise another table holds more, do not simply apologise: re-run the sweep and give the complete answer.
|
|
370
517
|
Never assert absence from a partial read. Do not say "there is no X", "none", "not found", or "\uC544\uB2C8\uC694, \uC5C6\uC2B5\uB2C8\uB2E4" until a complete scan has come back empty. If you have not finished scanning every relevant table and file, keep querying instead of guessing. A confident "no" that later turns out wrong is worse than telling the user you are still checking.
|
|
371
518
|
Embedded values: a search term is often stored inside a larger string. A merchant "BAKSA" appears as "DNH*BAKSA#4070277042", and a card as "5860****5173". Server-side index filters match only exact values, leading prefixes, or trailing suffixes, and tag filters only EXACT whole-tag values - never a partial or interior substring - so filtering on such a field silently drops rows. When the value you are looking for may be embedded, do not trust a narrow filter to be complete. Fetch the full set with fetch_all and match the substring yourself.
|
|
372
519
|
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.
|
|
373
520
|
- 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.
|
|
374
521
|
- Other attached files (office documents like .docx/.xlsx/.pptx/.hwp/.hwpx/.ods, email messages (.eml), and text/data/code files like .csv/.tsv/.json/.xml/.txt/.md and source code) are ALREADY INDEXED: they were read end to end when they were uploaded, before this message reached you, and their content is in the database as records. Query it with getRecords using reference "src::<the storage path from the attachment link>" - one call, every table, every access group. Do NOT call web_fetch on their URLs. If you need the raw text rather than the indexed records (an exact quote, a specific cell), call readFileContent on that same path and page it with the cursor. Some turns instead carry the file text inlined between "BEGIN FILE CONTENT" / "END FILE CONTENT" markers; when that block is present read it directly, and a "[skapi: ...]" note inside it means that file could not be extracted.
|
|
375
|
-
|
|
522
|
+
${webAccess ? `- For any file given to you as a URL instead of inline content (e.g. PDFs), 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.` : `- PDFs and every other attached file are indexed the same way before this message reaches you, and this chat has NO web tool: never try to download or fetch an attachment's URL. Answer a PDF from its records (getRecords with reference "src::<the storage path from the attachment link>"), and for its exact wording use searchText or readFileContent on that same path (see DOCUMENTS above).`}
|
|
376
523
|
Stored files and readFileContent: for a file ALREADY in this project's storage, its pages and rows were read at upload time and saved as records, so the database is your best source. Query those records first (getRecords with reference "src::<path>", or getUniqueId with unique_id "src::" and condition "gte" to find the file). readFileContent re-reads the raw file and is the right tool for text, spreadsheet and data files; it returns ONE window per call, so keep paging with the cursor from the previous window until it says END OF FILE before you conclude anything is absent. Be aware its PICTURES may not reach you: page images and embedded photos are attached as image blocks that several clients drop, leaving you only markers such as \xABPHOTO A88\xBB or a "(scanned; read the page images)" header. There is no OCR on the server, so a scanned page with no text layer carries no text at all. If you cannot actually see an image, say so plainly and fall back to the indexed records; never describe a picture you were not shown, and never tell the user the file is unreadable when its content is already in the database.
|
|
377
524
|
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 - do NOT show it. Format: [filename](db:path/to/file) for storage paths, or [filename](https://...) for external URLs. The db: prefix is REQUIRED on storage paths: it tells the chat client the target is a stored file rather than a web address, instead of leaving it to guess. Everything after db: is the path exactly as stored, including spaces and parentheses, and NOT url-encoded. Storage-path links render as clickable buttons in this chat client that fetch a fresh signed URL on demand - 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.
|
|
378
525
|
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; every file extracted out of a document has one too, in table "__MEDIA__" (access_group "authorized"). Present each result as a markdown link as described above. Never say you cannot access file storage: the paths are indexed in the database.
|
|
@@ -1071,6 +1218,224 @@ function truncateLabelForDisplay(label) {
|
|
|
1071
1218
|
return "\u2026" + label.slice(label.length - (LINK_LABEL_MAX_DISPLAY_CHARS - 1));
|
|
1072
1219
|
}
|
|
1073
1220
|
|
|
1221
|
+
// src/engine/retrieval_memory.ts
|
|
1222
|
+
var RETRIEVAL_NOTES_MAX = 6;
|
|
1223
|
+
var RETRIEVAL_NOTES_TOKEN_BUDGET = 1200;
|
|
1224
|
+
var RETRIEVAL_NOTE_MAX_ITEMS = 8;
|
|
1225
|
+
var RETRIEVAL_NOTE_MAX_CHARS = 600;
|
|
1226
|
+
var RETRIEVAL_NOTE_PREFIX = "[Retrieval note, not shown to the user";
|
|
1227
|
+
var FILE_KEYS = ["path", "storage_path", "storagePath", "file", "file_path", "filePath"];
|
|
1228
|
+
var TABLE_KEYS = ["table_name", "tableName"];
|
|
1229
|
+
var GROUP_KEYS = ["access_group", "accessGroup"];
|
|
1230
|
+
var SRC_PREFIX = "src::";
|
|
1231
|
+
var RESULT_WALK_MAX_NODES = 400;
|
|
1232
|
+
var RESULT_WALK_MAX_DEPTH = 5;
|
|
1233
|
+
function isWebToolName(name) {
|
|
1234
|
+
return typeof name === "string" && /^web_(search|fetch)/.test(name);
|
|
1235
|
+
}
|
|
1236
|
+
function pushUnique(list, value) {
|
|
1237
|
+
if (typeof value !== "string") return;
|
|
1238
|
+
var v = value.trim();
|
|
1239
|
+
if (!v || list.indexOf(v) !== -1 || list.length >= RETRIEVAL_NOTE_MAX_ITEMS) return;
|
|
1240
|
+
list.push(v);
|
|
1241
|
+
}
|
|
1242
|
+
function pushGroup(list, value) {
|
|
1243
|
+
if (typeof value === "number" && isFinite(value)) pushUnique(list, String(value));
|
|
1244
|
+
else pushUnique(list, value);
|
|
1245
|
+
}
|
|
1246
|
+
function pushHit(list, path, page) {
|
|
1247
|
+
if (typeof path !== "string" || !path.trim()) return;
|
|
1248
|
+
var n = typeof page === "number" ? page : Number(page);
|
|
1249
|
+
if (!isFinite(n) || n <= 0) return;
|
|
1250
|
+
n = Math.floor(n);
|
|
1251
|
+
for (var i = 0; i < list.length; i++) {
|
|
1252
|
+
if (list[i].path === path && list[i].page === n) return;
|
|
1253
|
+
}
|
|
1254
|
+
if (list.length >= RETRIEVAL_NOTE_MAX_ITEMS) return;
|
|
1255
|
+
list.push({ path: path.trim(), page: n });
|
|
1256
|
+
}
|
|
1257
|
+
function pathFromSrcId(value) {
|
|
1258
|
+
if (typeof value !== "string") return null;
|
|
1259
|
+
if (value.indexOf(SRC_PREFIX) !== 0) return null;
|
|
1260
|
+
var rest = value.slice(SRC_PREFIX.length).trim();
|
|
1261
|
+
return rest ? rest : null;
|
|
1262
|
+
}
|
|
1263
|
+
function parseJsonLoose(value) {
|
|
1264
|
+
if (typeof value !== "string") return value;
|
|
1265
|
+
var t = value.trim();
|
|
1266
|
+
if (!t || t[0] !== "{" && t[0] !== "[") return null;
|
|
1267
|
+
try {
|
|
1268
|
+
return JSON.parse(t);
|
|
1269
|
+
} catch (e) {
|
|
1270
|
+
return null;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
function readArguments(note, args) {
|
|
1274
|
+
if (!args || typeof args !== "object") return;
|
|
1275
|
+
var i;
|
|
1276
|
+
for (i = 0; i < FILE_KEYS.length; i++) pushUnique(note.files, args[FILE_KEYS[i]]);
|
|
1277
|
+
for (i = 0; i < TABLE_KEYS.length; i++) pushUnique(note.tables, args[TABLE_KEYS[i]]);
|
|
1278
|
+
for (i = 0; i < GROUP_KEYS.length; i++) pushGroup(note.accessGroups, args[GROUP_KEYS[i]]);
|
|
1279
|
+
if (typeof args.table === "string") pushUnique(note.tables, args.table);
|
|
1280
|
+
else if (args.table && typeof args.table === "object") {
|
|
1281
|
+
pushUnique(note.tables, args.table.name);
|
|
1282
|
+
pushGroup(note.accessGroups, args.table.access_group);
|
|
1283
|
+
}
|
|
1284
|
+
var ref = pathFromSrcId(args.reference);
|
|
1285
|
+
if (ref) pushUnique(note.files, ref);
|
|
1286
|
+
var uid = pathFromSrcId(args.unique_id);
|
|
1287
|
+
if (uid) pushUnique(note.files, uid);
|
|
1288
|
+
}
|
|
1289
|
+
function readHits(note, result) {
|
|
1290
|
+
var parsed = parseJsonLoose(result);
|
|
1291
|
+
if (!parsed || typeof parsed !== "object") return;
|
|
1292
|
+
var visited = 0;
|
|
1293
|
+
var walk = function(node, depth) {
|
|
1294
|
+
if (!node || typeof node !== "object" || depth > RESULT_WALK_MAX_DEPTH) return;
|
|
1295
|
+
if (++visited > RESULT_WALK_MAX_NODES) return;
|
|
1296
|
+
if (Array.isArray(node)) {
|
|
1297
|
+
for (var i = 0; i < node.length && visited <= RESULT_WALK_MAX_NODES; i++) walk(node[i], depth + 1);
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
if (typeof node.path === "string" && node.page !== void 0) {
|
|
1301
|
+
pushHit(note.hits, node.path, node.page);
|
|
1302
|
+
}
|
|
1303
|
+
for (var k in node) {
|
|
1304
|
+
if (!Object.prototype.hasOwnProperty.call(node, k)) continue;
|
|
1305
|
+
var v = node[k];
|
|
1306
|
+
if (v && typeof v === "object") walk(v, depth + 1);
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
walk(parsed, 0);
|
|
1310
|
+
}
|
|
1311
|
+
function claudeResultText(block) {
|
|
1312
|
+
var c = block && block.content;
|
|
1313
|
+
if (typeof c === "string") return c;
|
|
1314
|
+
if (Array.isArray(c)) {
|
|
1315
|
+
return c.map(function(part) {
|
|
1316
|
+
return part && typeof part.text === "string" ? part.text : "";
|
|
1317
|
+
}).join("\n");
|
|
1318
|
+
}
|
|
1319
|
+
return "";
|
|
1320
|
+
}
|
|
1321
|
+
function emptyNote(source) {
|
|
1322
|
+
return { files: [], tables: [], accessGroups: [], hits: [], tools: [], web: false, source };
|
|
1323
|
+
}
|
|
1324
|
+
function noteHasContent(note) {
|
|
1325
|
+
return note.files.length > 0 || note.tables.length > 0 || note.accessGroups.length > 0 || note.hits.length > 0 || note.tools.length > 0 || note.web;
|
|
1326
|
+
}
|
|
1327
|
+
function noteFromToolBlocks(body) {
|
|
1328
|
+
if (!body || typeof body !== "object") return null;
|
|
1329
|
+
var note = emptyNote("tools");
|
|
1330
|
+
var sawTool = false;
|
|
1331
|
+
var i;
|
|
1332
|
+
var content = body.content;
|
|
1333
|
+
if (Array.isArray(content)) {
|
|
1334
|
+
var callNames = {};
|
|
1335
|
+
for (i = 0; i < content.length; i++) {
|
|
1336
|
+
var block = content[i];
|
|
1337
|
+
if (!block || typeof block !== "object") continue;
|
|
1338
|
+
var type = block.type;
|
|
1339
|
+
if (type === "mcp_tool_use" || type === "tool_use") {
|
|
1340
|
+
sawTool = true;
|
|
1341
|
+
pushUnique(note.tools, block.name);
|
|
1342
|
+
if (typeof block.id === "string" && typeof block.name === "string") callNames[block.id] = block.name;
|
|
1343
|
+
readArguments(note, block.input);
|
|
1344
|
+
} else if (type === "server_tool_use" || type === "web_search_tool_use") {
|
|
1345
|
+
sawTool = true;
|
|
1346
|
+
if (isWebToolName(block.name) || type === "web_search_tool_use") note.web = true;
|
|
1347
|
+
pushUnique(note.tools, block.name || type);
|
|
1348
|
+
} else if (type === "mcp_tool_result" || type === "tool_result") {
|
|
1349
|
+
sawTool = true;
|
|
1350
|
+
var name = typeof block.tool_use_id === "string" ? callNames[block.tool_use_id] : void 0;
|
|
1351
|
+
if (!block.is_error && (name === void 0 || name === "searchText")) readHits(note, claudeResultText(block));
|
|
1352
|
+
} else if (type === "web_search_tool_result" || type === "web_fetch_tool_result") {
|
|
1353
|
+
sawTool = true;
|
|
1354
|
+
note.web = true;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
var output = body.output;
|
|
1359
|
+
if (Array.isArray(output)) {
|
|
1360
|
+
for (i = 0; i < output.length; i++) {
|
|
1361
|
+
var item = output[i];
|
|
1362
|
+
if (!item || typeof item !== "object") continue;
|
|
1363
|
+
var kind = typeof item.type === "string" ? item.type : "";
|
|
1364
|
+
if (kind === "mcp_call" || kind === "function_call") {
|
|
1365
|
+
sawTool = true;
|
|
1366
|
+
pushUnique(note.tools, item.name);
|
|
1367
|
+
readArguments(note, parseJsonLoose(item.arguments) || item.arguments);
|
|
1368
|
+
if (!item.error && item.name === "searchText") readHits(note, item.output);
|
|
1369
|
+
} else if (kind.indexOf("web_search") === 0 || kind.indexOf("web_fetch") === 0) {
|
|
1370
|
+
sawTool = true;
|
|
1371
|
+
note.web = true;
|
|
1372
|
+
pushUnique(note.tools, kind);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
return sawTool ? note : null;
|
|
1377
|
+
}
|
|
1378
|
+
function retrievalNoteFromText(text) {
|
|
1379
|
+
if (typeof text !== "string" || !text) return null;
|
|
1380
|
+
var note = emptyNote("answer");
|
|
1381
|
+
var link = /\]\(db:([^)\s"]+)/g;
|
|
1382
|
+
var m;
|
|
1383
|
+
while ((m = link.exec(text)) !== null) pushUnique(note.files, m[1]);
|
|
1384
|
+
var src = /src::([^\s"'`<>)\]]+)/g;
|
|
1385
|
+
while ((m = src.exec(text)) !== null) {
|
|
1386
|
+
pushUnique(note.files, m[1].replace(/[.,;:!?]+$/, ""));
|
|
1387
|
+
}
|
|
1388
|
+
return noteHasContent(note) ? note : null;
|
|
1389
|
+
}
|
|
1390
|
+
function extractRetrievalNote(body, answerText) {
|
|
1391
|
+
var fromTools = noteFromToolBlocks(body);
|
|
1392
|
+
var fromText = retrievalNoteFromText(answerText);
|
|
1393
|
+
if (!fromTools) {
|
|
1394
|
+
if (fromText) return fromText;
|
|
1395
|
+
return providerBodyWasRead(body) ? emptyNote("body") : null;
|
|
1396
|
+
}
|
|
1397
|
+
if (fromText) {
|
|
1398
|
+
for (var i = 0; i < fromText.files.length; i++) pushUnique(fromTools.files, fromText.files[i]);
|
|
1399
|
+
}
|
|
1400
|
+
return noteHasContent(fromTools) ? fromTools : emptyNote("body");
|
|
1401
|
+
}
|
|
1402
|
+
function providerBodyWasRead(body) {
|
|
1403
|
+
if (!body || typeof body !== "object") return false;
|
|
1404
|
+
return Array.isArray(body.content) || Array.isArray(body.output);
|
|
1405
|
+
}
|
|
1406
|
+
function isRetrievalNote(value) {
|
|
1407
|
+
return !!value && typeof value === "object" && Array.isArray(value.files) && Array.isArray(value.tables) && Array.isArray(value.accessGroups) && Array.isArray(value.hits) && Array.isArray(value.tools);
|
|
1408
|
+
}
|
|
1409
|
+
function formatRetrievalNote(note) {
|
|
1410
|
+
if (!note || !noteHasContent(note)) return "";
|
|
1411
|
+
var parts = [];
|
|
1412
|
+
if (note.files.length) parts.push("files " + note.files.join(", "));
|
|
1413
|
+
if (note.tables.length) parts.push("tables " + note.tables.join(", "));
|
|
1414
|
+
if (note.accessGroups.length) parts.push("access_group " + note.accessGroups.join(", "));
|
|
1415
|
+
if (note.hits.length) {
|
|
1416
|
+
var byFile = {};
|
|
1417
|
+
var order = [];
|
|
1418
|
+
for (var i = 0; i < note.hits.length; i++) {
|
|
1419
|
+
var h = note.hits[i];
|
|
1420
|
+
if (!byFile[h.path]) {
|
|
1421
|
+
byFile[h.path] = [];
|
|
1422
|
+
order.push(h.path);
|
|
1423
|
+
}
|
|
1424
|
+
byFile[h.path].push(h.page);
|
|
1425
|
+
}
|
|
1426
|
+
parts.push("searchText hits " + order.map(function(p) {
|
|
1427
|
+
return p + " p" + byFile[p].join(", p");
|
|
1428
|
+
}).join("; "));
|
|
1429
|
+
}
|
|
1430
|
+
if (note.tools.length) parts.push("tools " + note.tools.join(", "));
|
|
1431
|
+
if (note.web) parts.push("web tool used");
|
|
1432
|
+
var line = RETRIEVAL_NOTE_PREFIX + ": this turn used " + parts.join(" | ") + "]";
|
|
1433
|
+
if (line.length > RETRIEVAL_NOTE_MAX_CHARS) {
|
|
1434
|
+
line = line.slice(0, RETRIEVAL_NOTE_MAX_CHARS - 4) + "...]";
|
|
1435
|
+
}
|
|
1436
|
+
return line;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1074
1439
|
// src/engine/budget.ts
|
|
1075
1440
|
var CONTEXT_WINDOW_DEFAULT = { claude: 2e5, openai: 128e3 };
|
|
1076
1441
|
var CONTEXT_WINDOW_BY_MODEL = {
|
|
@@ -1242,6 +1607,23 @@ function stripFileBlocksFromHistory(content) {
|
|
|
1242
1607
|
if (!content) return content;
|
|
1243
1608
|
return content.replace(/```([^\n`]+?\.[^\s.`]+)\n[\s\S]*?```/g, "[file previously attached: $1]");
|
|
1244
1609
|
}
|
|
1610
|
+
function applyRetrievalNotes(messages, latestIndex) {
|
|
1611
|
+
var out = messages.slice();
|
|
1612
|
+
var used = 0, tokens = 0;
|
|
1613
|
+
for (var i = messages.length - 1; i >= 0; i--) {
|
|
1614
|
+
if (used >= RETRIEVAL_NOTES_MAX) break;
|
|
1615
|
+
var m = messages[i];
|
|
1616
|
+
if (i === latestIndex || !m || m.role !== "assistant" || !isRetrievalNote(m._retrieval)) continue;
|
|
1617
|
+
var line = formatRetrievalNote(m._retrieval);
|
|
1618
|
+
if (!line) continue;
|
|
1619
|
+
var cost = estimateTextTokens(line) + 2;
|
|
1620
|
+
if (tokens + cost > RETRIEVAL_NOTES_TOKEN_BUDGET) break;
|
|
1621
|
+
tokens += cost;
|
|
1622
|
+
used++;
|
|
1623
|
+
out[i] = Object.assign({}, m, { content: (m.content || "") + "\n\n" + line });
|
|
1624
|
+
}
|
|
1625
|
+
return { messages: out, notesUsed: used, noteTokens: tokens };
|
|
1626
|
+
}
|
|
1245
1627
|
function buildBoundedChatMessages(options) {
|
|
1246
1628
|
var contextBasedBudget = contextBasedBudgetFor(options.platform, options.model, options.projectId);
|
|
1247
1629
|
var availableInputBudget = getInputTokenBudget(options.platform, options.model, options.projectId);
|
|
@@ -1257,6 +1639,7 @@ function buildBoundedChatMessages(options) {
|
|
|
1257
1639
|
);
|
|
1258
1640
|
var windowed = options.history.slice(-maxHistoryMessages);
|
|
1259
1641
|
var latestIndex = windowed.length - 1;
|
|
1642
|
+
if (options.retrievalNotes !== false) windowed = applyRetrievalNotes(windowed, latestIndex).messages;
|
|
1260
1643
|
var trimmed = windowed.map(function(m, i2) {
|
|
1261
1644
|
if (i2 === latestIndex) return m;
|
|
1262
1645
|
var stripped = stripFileBlocksFromHistory(m.content);
|
|
@@ -1280,6 +1663,105 @@ function buildBoundedChatMessages(options) {
|
|
|
1280
1663
|
};
|
|
1281
1664
|
}
|
|
1282
1665
|
|
|
1666
|
+
// src/engine/citations.ts
|
|
1667
|
+
var DATA_TOOL_NAMES = [
|
|
1668
|
+
"getRecords",
|
|
1669
|
+
"queryGrid",
|
|
1670
|
+
"readFileContent",
|
|
1671
|
+
"searchText",
|
|
1672
|
+
"getFileOutline",
|
|
1673
|
+
"getUniqueId",
|
|
1674
|
+
"getTables",
|
|
1675
|
+
"getTags",
|
|
1676
|
+
"getIndexes"
|
|
1677
|
+
];
|
|
1678
|
+
var NO_DATA_TOOL_MARK = "No project data was read for this answer";
|
|
1679
|
+
function citationFileName(path) {
|
|
1680
|
+
var segs = String(path || "").split("/");
|
|
1681
|
+
for (var i = segs.length - 1; i >= 0; i--) {
|
|
1682
|
+
if (segs[i]) return segs[i];
|
|
1683
|
+
}
|
|
1684
|
+
return String(path || "");
|
|
1685
|
+
}
|
|
1686
|
+
function citationLabel(path, page) {
|
|
1687
|
+
return citationFileName(path) + (typeof page === "number" && page > 0 ? " p." + page : "");
|
|
1688
|
+
}
|
|
1689
|
+
function isMintablePath(path) {
|
|
1690
|
+
return !!path && !isHttpUrlLike(path);
|
|
1691
|
+
}
|
|
1692
|
+
function citationsFor(note) {
|
|
1693
|
+
if (!isRetrievalNote(note)) return [];
|
|
1694
|
+
var out = [];
|
|
1695
|
+
var hitPaths = [];
|
|
1696
|
+
var byPath = {};
|
|
1697
|
+
var i;
|
|
1698
|
+
for (i = 0; i < note.hits.length; i++) {
|
|
1699
|
+
var h = note.hits[i];
|
|
1700
|
+
if (!h || typeof h.path !== "string" || !isMintablePath(h.path)) continue;
|
|
1701
|
+
var page = typeof h.page === "number" ? h.page : Number(h.page);
|
|
1702
|
+
if (!isFinite(page) || page <= 0) continue;
|
|
1703
|
+
page = Math.floor(page);
|
|
1704
|
+
if (!byPath[h.path]) {
|
|
1705
|
+
byPath[h.path] = [];
|
|
1706
|
+
hitPaths.push(h.path);
|
|
1707
|
+
}
|
|
1708
|
+
if (byPath[h.path].indexOf(page) === -1) byPath[h.path].push(page);
|
|
1709
|
+
}
|
|
1710
|
+
for (i = 0; i < hitPaths.length; i++) {
|
|
1711
|
+
var p = hitPaths[i];
|
|
1712
|
+
var pages = byPath[p].slice().sort(function(a, b) {
|
|
1713
|
+
return a - b;
|
|
1714
|
+
});
|
|
1715
|
+
for (var j = 0; j < pages.length; j++) {
|
|
1716
|
+
out.push({ path: p, page: pages[j], label: citationLabel(p, pages[j]) });
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
for (i = 0; i < note.files.length; i++) {
|
|
1720
|
+
var f = note.files[i];
|
|
1721
|
+
if (typeof f !== "string" || !isMintablePath(f)) continue;
|
|
1722
|
+
if (byPath[f]) continue;
|
|
1723
|
+
var dup = false;
|
|
1724
|
+
for (var k = 0; k < out.length; k++) {
|
|
1725
|
+
if (out[k].path === f && out[k].page === void 0) {
|
|
1726
|
+
dup = true;
|
|
1727
|
+
break;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
if (dup) continue;
|
|
1731
|
+
out.push({ path: f, label: citationLabel(f) });
|
|
1732
|
+
}
|
|
1733
|
+
return out;
|
|
1734
|
+
}
|
|
1735
|
+
function isDataToolName(name) {
|
|
1736
|
+
return DATA_TOOL_NAMES.indexOf(name) !== -1;
|
|
1737
|
+
}
|
|
1738
|
+
function answerUsedNoDataTool(note) {
|
|
1739
|
+
if (!isRetrievalNote(note)) return false;
|
|
1740
|
+
if (note.web) return false;
|
|
1741
|
+
for (var i = 0; i < note.tools.length; i++) {
|
|
1742
|
+
if (isDataToolName(note.tools[i])) return false;
|
|
1743
|
+
}
|
|
1744
|
+
return true;
|
|
1745
|
+
}
|
|
1746
|
+
function retrievalNoteOfAnswer(msg) {
|
|
1747
|
+
if (!msg || msg.role !== "assistant") return null;
|
|
1748
|
+
if (msg.isError || msg.isPending || msg.isBackgroundTask || msg._streamPending) return null;
|
|
1749
|
+
return isRetrievalNote(msg._retrieval) ? msg._retrieval : null;
|
|
1750
|
+
}
|
|
1751
|
+
function citationLinkPart(citation, resolveFreshHref) {
|
|
1752
|
+
var expiredHref = buildDisplayExpiredAttachmentHref(citation.path, citation.label);
|
|
1753
|
+
var cached = resolveFreshHref ? resolveFreshHref(expiredHref) : void 0;
|
|
1754
|
+
return {
|
|
1755
|
+
type: "link",
|
|
1756
|
+
label: truncateLabelForDisplay(citation.label),
|
|
1757
|
+
fullLabel: citation.label,
|
|
1758
|
+
href: cached || expiredHref,
|
|
1759
|
+
expired: !cached,
|
|
1760
|
+
expiredHref,
|
|
1761
|
+
remotePath: citation.path
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1283
1765
|
// src/engine/download_encoding.ts
|
|
1284
1766
|
var BOM = "\uFEFF";
|
|
1285
1767
|
var BOM_EXTS = /* @__PURE__ */ new Set(["csv", "tsv", "tab", "txt", "text", "log"]);
|
|
@@ -2214,10 +2696,26 @@ var ANTHROPIC_PROMPT_CACHING_BETA = "prompt-caching-2024-07-31";
|
|
|
2214
2696
|
var ANTHROPIC_BETA_HEADER = `${ANTHROPIC_MCP_BETA},${ANTHROPIC_WEB_FETCH_BETA},${ANTHROPIC_PROMPT_CACHING_BETA}`;
|
|
2215
2697
|
var WEB_FETCH_MAX_USES = 40;
|
|
2216
2698
|
var WEB_FETCH_MAX_CONTENT_TOKENS = 2e5;
|
|
2699
|
+
var claudeWebFetchTool = () => ({
|
|
2700
|
+
type: "web_fetch_20250910",
|
|
2701
|
+
name: "web_fetch",
|
|
2702
|
+
max_uses: WEB_FETCH_MAX_USES,
|
|
2703
|
+
citations: { enabled: true },
|
|
2704
|
+
max_content_tokens: WEB_FETCH_MAX_CONTENT_TOKENS
|
|
2705
|
+
});
|
|
2217
2706
|
var OPENAI_RESPONSES_API_URL = "https://api.openai.com/v1/responses";
|
|
2218
2707
|
var OPENAI_MODELS_API_URL = "https://api.openai.com/v1/models";
|
|
2219
2708
|
var DEFAULT_OPENAI_IMAGE_DETAIL = "auto";
|
|
2220
2709
|
var OPENAI_WEB_SEARCH_EXTERNAL_WEB_ACCESS = true;
|
|
2710
|
+
var openaiWebSearchTool = () => ({
|
|
2711
|
+
type: "web_search",
|
|
2712
|
+
external_web_access: OPENAI_WEB_SEARCH_EXTERNAL_WEB_ACCESS
|
|
2713
|
+
});
|
|
2714
|
+
var webToolsForChat = (platform, webAccess) => {
|
|
2715
|
+
if (webAccess !== true) return [];
|
|
2716
|
+
if (platform === "claude") return [claudeWebFetchTool()];
|
|
2717
|
+
return [openaiWebSearchTool()] ;
|
|
2718
|
+
};
|
|
2221
2719
|
var MCP_NAME = "BunnyQuery";
|
|
2222
2720
|
var DEFAULT_CLAUDE_MODEL = "claude-sonnet-5";
|
|
2223
2721
|
var DEFAULT_OPENAI_MODEL = "gpt-5.6-luna";
|
|
@@ -2233,6 +2731,7 @@ function withMcpParams(base, params) {
|
|
|
2233
2731
|
}
|
|
2234
2732
|
var mcpContextParam = (platform, model) => getModelContextWindow(platform, model);
|
|
2235
2733
|
var mcpIndexingUrl = (platform = "openai", model) => withMcpParams(mcpUrl(), { profile: "index", ctx: mcpContextParam(platform, model) });
|
|
2734
|
+
var mcpChatUrl = (endpointUrl, platform, model) => withMcpParams(endpointUrl, { profile: "chat", ctx: mcpContextParam(platform, model) });
|
|
2236
2735
|
function mcpEndpointFor(anonymous, publicProjectId, service) {
|
|
2237
2736
|
if (!anonymous) return { url: mcpUrl(), token: "$ACCESS_TOKEN" };
|
|
2238
2737
|
const project = publicProjectId || service;
|
|
@@ -2418,8 +2917,10 @@ async function callClaudeWithMcp({
|
|
|
2418
2917
|
system,
|
|
2419
2918
|
mcpServer,
|
|
2420
2919
|
extractContent,
|
|
2421
|
-
fileUrls
|
|
2920
|
+
fileUrls,
|
|
2921
|
+
webAccess
|
|
2422
2922
|
}) {
|
|
2923
|
+
const web = typeof webAccess === "boolean" ? webAccess : await resolveWebAccess(service);
|
|
2423
2924
|
const mcpServerDefinition = {
|
|
2424
2925
|
type: "url",
|
|
2425
2926
|
name: mcpServer.name,
|
|
@@ -2478,13 +2979,10 @@ async function callClaudeWithMcp({
|
|
|
2478
2979
|
...mcpServer.defaultConfig ? { default_config: mcpServer.defaultConfig } : {},
|
|
2479
2980
|
...mcpServer.configs ? { configs: mcpServer.configs } : {}
|
|
2480
2981
|
},
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
citations: { enabled: true },
|
|
2486
|
-
max_content_tokens: WEB_FETCH_MAX_CONTENT_TOKENS
|
|
2487
|
-
}
|
|
2982
|
+
// `web_fetch` rides here only when the project's web switch is on. The
|
|
2983
|
+
// beta header above is left as it is: it only makes the tool TYPE
|
|
2984
|
+
// available, and a request that does not list the tool cannot use it.
|
|
2985
|
+
...webToolsForChat("claude", web)
|
|
2488
2986
|
]
|
|
2489
2987
|
}
|
|
2490
2988
|
});
|
|
@@ -2504,9 +3002,7 @@ async function callClaudeWithPublicMcp(prompt, service, owner, messages, system,
|
|
|
2504
3002
|
fileUrls,
|
|
2505
3003
|
mcpServer: {
|
|
2506
3004
|
name: MCP_NAME,
|
|
2507
|
-
url:
|
|
2508
|
-
ctx: mcpContextParam("claude", model || DEFAULT_CLAUDE_MODEL)
|
|
2509
|
-
}),
|
|
3005
|
+
url: mcpChatUrl(endpoint.url, "claude", model || DEFAULT_CLAUDE_MODEL),
|
|
2510
3006
|
// Omitted entirely for an anonymous turn; the `if (mcpServer.authorizationToken)`
|
|
2511
3007
|
// guard below drops the key rather than sending an empty one.
|
|
2512
3008
|
authorizationToken: endpoint.token
|
|
@@ -2516,6 +3012,7 @@ async function callOpenAIWithPublicMcp(prompt, service, owner, messages, system,
|
|
|
2516
3012
|
const endpoint = mcpEndpointFor(mcpScope?.anonymous, mcpScope?.publicProjectId, service);
|
|
2517
3013
|
const resolvedModel = model || DEFAULT_OPENAI_MODEL;
|
|
2518
3014
|
const imageDetail = getOpenAIImageDetail(resolvedModel);
|
|
3015
|
+
const web = await resolveWebAccess(service);
|
|
2519
3016
|
const messageList = messages && messages.length ? prepareOpenAIMessages(messages, imageDetail) : [
|
|
2520
3017
|
{
|
|
2521
3018
|
role: "user",
|
|
@@ -2561,19 +3058,15 @@ async function callOpenAIWithPublicMcp(prompt, service, owner, messages, system,
|
|
|
2561
3058
|
{
|
|
2562
3059
|
type: "mcp",
|
|
2563
3060
|
server_label: MCP_NAME,
|
|
2564
|
-
server_url:
|
|
3061
|
+
server_url: mcpChatUrl(endpoint.url, "openai", resolvedModel),
|
|
2565
3062
|
require_approval: "never",
|
|
2566
3063
|
// No `headers` at all for an anonymous turn: `Bearer ` with an
|
|
2567
3064
|
// empty token is a credential the MCP server rejects, and the
|
|
2568
3065
|
// project-scoped endpoint needs none.
|
|
2569
3066
|
...endpoint.token ? { headers: { Authorization: "Bearer " + endpoint.token } } : {}
|
|
2570
3067
|
},
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
type: "web_search",
|
|
2574
|
-
external_web_access: OPENAI_WEB_SEARCH_EXTERNAL_WEB_ACCESS
|
|
2575
|
-
}
|
|
2576
|
-
]
|
|
3068
|
+
// `web_search` rides here only when the project's web switch is on.
|
|
3069
|
+
...webToolsForChat("openai", web)
|
|
2577
3070
|
]
|
|
2578
3071
|
}
|
|
2579
3072
|
});
|
|
@@ -2721,12 +3214,9 @@ async function notifyAgentSaveAttachment(info) {
|
|
|
2721
3214
|
require_approval: "never",
|
|
2722
3215
|
headers: { Authorization: "Bearer $ACCESS_TOKEN" }
|
|
2723
3216
|
},
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
external_web_access: OPENAI_WEB_SEARCH_EXTERNAL_WEB_ACCESS
|
|
2728
|
-
}
|
|
2729
|
-
]
|
|
3217
|
+
// UNCONDITIONAL, unlike a chat turn: the project's web switch
|
|
3218
|
+
// governs answers, not indexing. Kept exactly as it was.
|
|
3219
|
+
...[openaiWebSearchTool()]
|
|
2730
3220
|
]
|
|
2731
3221
|
}
|
|
2732
3222
|
}));
|
|
@@ -2779,13 +3269,9 @@ async function notifyAgentSaveAttachment(info) {
|
|
|
2779
3269
|
type: "mcp_toolset",
|
|
2780
3270
|
mcp_server_name: MCP_NAME
|
|
2781
3271
|
},
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
max_uses: WEB_FETCH_MAX_USES,
|
|
2786
|
-
citations: { enabled: true },
|
|
2787
|
-
max_content_tokens: WEB_FETCH_MAX_CONTENT_TOKENS
|
|
2788
|
-
}
|
|
3272
|
+
// UNCONDITIONAL, unlike a chat turn: the project's web switch governs
|
|
3273
|
+
// answers, not indexing. Kept exactly as it was.
|
|
3274
|
+
claudeWebFetchTool()
|
|
2789
3275
|
]
|
|
2790
3276
|
}
|
|
2791
3277
|
}));
|
|
@@ -3255,8 +3741,9 @@ function mapHistoryListToMessages(list, platform, opts) {
|
|
|
3255
3741
|
var createdTs = Number(item && item.created);
|
|
3256
3742
|
var updatedTs = Number(item && item.updated);
|
|
3257
3743
|
var executedTs = Number(item && item.executed);
|
|
3744
|
+
var respondedTs = Number(item && item.responded);
|
|
3258
3745
|
var userTs = isFinite(createdTs) && createdTs > 0 ? createdTs : isFinite(updatedTs) && updatedTs > 0 ? updatedTs : void 0;
|
|
3259
|
-
var replyTs = isFinite(updatedTs) && updatedTs > 0 ? updatedTs : isFinite(createdTs) && createdTs > 0 ? createdTs : void 0;
|
|
3746
|
+
var replyTs = isFinite(respondedTs) && respondedTs > 0 ? respondedTs : isFinite(updatedTs) && updatedTs > 0 ? updatedTs : isFinite(createdTs) && createdTs > 0 ? createdTs : void 0;
|
|
3260
3747
|
if (userText) {
|
|
3261
3748
|
var displayContent;
|
|
3262
3749
|
var indexFile = void 0;
|
|
@@ -3323,6 +3810,10 @@ function mapHistoryListToMessages(list, platform, opts) {
|
|
|
3323
3810
|
if (item._fromBgChain) okm._fromBgChain = true;
|
|
3324
3811
|
if (item._isBgTask) okm.isBackgroundTask = true;
|
|
3325
3812
|
if (isCompact) okm._compact = true;
|
|
3813
|
+
if (!item._isBgTask && !isCompact) {
|
|
3814
|
+
var note = extractRetrievalNote(response, assistantText);
|
|
3815
|
+
if (note) okm._retrieval = note;
|
|
3816
|
+
}
|
|
3326
3817
|
if (serverItemId !== void 0) okm._serverItemId = serverItemId;
|
|
3327
3818
|
if (replyTs !== void 0) okm._ts = replyTs;
|
|
3328
3819
|
if (indexFile && isFinite(executedTs) && executedTs > 0) okm._tsStart = executedTs;
|
|
@@ -3883,6 +4374,13 @@ var ChatSession = class {
|
|
|
3883
4374
|
// the request budget MAX_CONCURRENT_BG_POLLS exists to protect.
|
|
3884
4375
|
/** Live streams by server item id. One per in-flight streamed turn. */
|
|
3885
4376
|
this.liveStreams = {};
|
|
4377
|
+
/** When skapi recorded a turn's response as having finished arriving, by server
|
|
4378
|
+
* item id. Read off the terminal envelope of a STREAMED turn (the only settle
|
|
4379
|
+
* that carries it, see _settleLiveStream) and used as the answer bubble's time
|
|
4380
|
+
* in place of this browser's clock. Entries are never removed: a settle reaches
|
|
4381
|
+
* here twice (the poll's onResponse, then the promise), and the value for an id
|
|
4382
|
+
* cannot change. */
|
|
4383
|
+
this._respondedAt = {};
|
|
3886
4384
|
// ─── compact-stub hydration ─────────────────────────────────────────────
|
|
3887
4385
|
// Split-fetch bg pages arrive as label stubs (no bodies). When the user
|
|
3888
4386
|
// expands a row, the real reply text is fetched per item (csr-poll point
|
|
@@ -4408,6 +4906,23 @@ var ChatSession = class {
|
|
|
4408
4906
|
});
|
|
4409
4907
|
return n;
|
|
4410
4908
|
}
|
|
4909
|
+
_noteRespondedAt(itemId, response) {
|
|
4910
|
+
if (!itemId || !isCsrStatusEnvelope(response)) return;
|
|
4911
|
+
var at = Number(response.responded);
|
|
4912
|
+
if (!isFinite(at) || at <= 0) return;
|
|
4913
|
+
if (!this._respondedAt) this._respondedAt = {};
|
|
4914
|
+
this._respondedAt[itemId] = at;
|
|
4915
|
+
}
|
|
4916
|
+
/** The time a live answer bubble is stamped with: when skapi received the
|
|
4917
|
+
* response, if the settle told us, else now. "Now" is what every bubble got
|
|
4918
|
+
* before, and it is still right for a buffered turn, whose settle hands back the
|
|
4919
|
+
* destination's body and nothing of skapi's; a history load then replaces it
|
|
4920
|
+
* with the server's value. */
|
|
4921
|
+
_replyStampFor(msg) {
|
|
4922
|
+
var id = msg ? msg._serverItemId : void 0;
|
|
4923
|
+
var at = typeof id === "string" && id && this._respondedAt ? this._respondedAt[id] : void 0;
|
|
4924
|
+
return typeof at === "number" ? at : wallClockNow();
|
|
4925
|
+
}
|
|
4411
4926
|
/**
|
|
4412
4927
|
* Open (or re-open) the live stream for `itemId`, or null when this poll must
|
|
4413
4928
|
* not carry one.
|
|
@@ -4597,6 +5112,7 @@ var ChatSession = class {
|
|
|
4597
5112
|
_settleLiveStream(st, response) {
|
|
4598
5113
|
this._closeLiveStream(st, true);
|
|
4599
5114
|
if (!isCsrStatusEnvelope(response)) return response;
|
|
5115
|
+
this._noteRespondedAt(st.id, response);
|
|
4600
5116
|
if (response.status !== "resolved") return response;
|
|
4601
5117
|
if (!this._mayFinalize(st)) this._rec().incomplete[st.id] = true;
|
|
4602
5118
|
if (st.finalBody == null) return response;
|
|
@@ -4993,6 +5509,10 @@ var ChatSession = class {
|
|
|
4993
5509
|
return;
|
|
4994
5510
|
}
|
|
4995
5511
|
var reply = isErr ? { role: "assistant", content: getErrorMessage(body), isError: true, _serverItemId: itemId } : { role: "assistant", content: text, _serverItemId: itemId };
|
|
5512
|
+
if (!isErr) {
|
|
5513
|
+
var rNote = extractRetrievalNote(body, text);
|
|
5514
|
+
if (rNote) reply._retrieval = rNote;
|
|
5515
|
+
}
|
|
4996
5516
|
if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
|
|
4997
5517
|
this._applyReplyToCache(ownerKey, reply, itemId);
|
|
4998
5518
|
} else {
|
|
@@ -5401,7 +5921,7 @@ var ChatSession = class {
|
|
|
5401
5921
|
if (isErrorResponseBody(response)) return { content: getErrorMessage(response), isError: true };
|
|
5402
5922
|
var answer = params.aiPlatform === "openai" ? extractOpenAIText(response) : extractClaudeText(response);
|
|
5403
5923
|
answer = (answer || "").trim();
|
|
5404
|
-
return { content: answer || "No text response received from AI provider.", isError: false };
|
|
5924
|
+
return { content: answer || "No text response received from AI provider.", isError: false, retrieval: extractRetrievalNote(response, answer) };
|
|
5405
5925
|
}).catch(function(err) {
|
|
5406
5926
|
return { content: getErrorMessage(err), isError: true };
|
|
5407
5927
|
}).then(function(result) {
|
|
@@ -5413,6 +5933,7 @@ var ChatSession = class {
|
|
|
5413
5933
|
}
|
|
5414
5934
|
var reply = { role: "assistant", content: result.content, isError: result.isError };
|
|
5415
5935
|
if (dispatchItemId) reply._serverItemId = dispatchItemId;
|
|
5936
|
+
if (result.retrieval) reply._retrieval = result.retrieval;
|
|
5416
5937
|
self._applyReplyToCache(params.key, reply, dispatchItemId);
|
|
5417
5938
|
return result;
|
|
5418
5939
|
});
|
|
@@ -6038,7 +6559,7 @@ var ChatSession = class {
|
|
|
6038
6559
|
return thinkingIdx !== -1 ? thinkingIdx : userIdx >= 0 ? userIdx + 1 : -1;
|
|
6039
6560
|
}
|
|
6040
6561
|
insertAtTarget(msg, targetIdx) {
|
|
6041
|
-
if (msg && msg.role === "assistant" && msg._ts === void 0) msg._ts =
|
|
6562
|
+
if (msg && msg.role === "assistant" && msg._ts === void 0) msg._ts = this._replyStampFor(msg);
|
|
6042
6563
|
var tgt = targetIdx >= 0 ? this.state.messages[targetIdx] : void 0;
|
|
6043
6564
|
var replaceable = !!tgt && !!tgt.isPending && !tgt.isBackgroundTask && this._isOwnPlaceholderOf(targetIdx, this._owningUserIndex(targetIdx));
|
|
6044
6565
|
if (replaceable) this.state.messages[targetIdx] = msg;
|
|
@@ -6076,6 +6597,10 @@ var ChatSession = class {
|
|
|
6076
6597
|
}
|
|
6077
6598
|
if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
|
|
6078
6599
|
var offReply = isErrorResponseBody(response) ? { role: "assistant", content: getErrorMessage(response), isError: true } : { role: "assistant", content: ((platform === "openai" ? extractOpenAIText(response) : extractClaudeText(response)) || "").trim() || "No text response received from AI provider." };
|
|
6600
|
+
if (!offReply.isError) {
|
|
6601
|
+
var offNote = extractRetrievalNote(response, offReply.content);
|
|
6602
|
+
if (offNote) offReply._retrieval = offNote;
|
|
6603
|
+
}
|
|
6079
6604
|
this._applyReplyToCache(ownerKey, offReply, serverId);
|
|
6080
6605
|
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
6081
6606
|
return;
|
|
@@ -6097,6 +6622,7 @@ var ChatSession = class {
|
|
|
6097
6622
|
} else {
|
|
6098
6623
|
var answer = platform === "openai" ? extractOpenAIText(response) : extractClaudeText(response);
|
|
6099
6624
|
answer = (answer || "").trim() || "No text response received from AI provider.";
|
|
6625
|
+
var qNote = extractRetrievalNote(response, answer);
|
|
6100
6626
|
var lid = this._newLocalId();
|
|
6101
6627
|
if (targetIdx >= 0 && this.state.messages[targetIdx] && this.state.messages[targetIdx].isPending) {
|
|
6102
6628
|
var qPainted = this._paintedTextAt(targetIdx);
|
|
@@ -6104,16 +6630,21 @@ var ChatSession = class {
|
|
|
6104
6630
|
var qSettled = { role: "assistant", content: qPainted, _localId: lid };
|
|
6105
6631
|
if (prevQ._serverItemId) qSettled._serverItemId = prevQ._serverItemId;
|
|
6106
6632
|
if (prevQ._ownerKey) qSettled._ownerKey = prevQ._ownerKey;
|
|
6633
|
+
if (qNote) qSettled._retrieval = qNote;
|
|
6107
6634
|
this.state.messages[targetIdx] = qSettled;
|
|
6108
6635
|
this.host.notify();
|
|
6109
6636
|
this.enqueueTypewrite(targetIdx, answer, lid, qPainted);
|
|
6110
6637
|
} else if (targetIdx >= 0) {
|
|
6111
|
-
|
|
6638
|
+
var qInsert = { role: "assistant", content: "", _localId: lid };
|
|
6639
|
+
if (qNote) qInsert._retrieval = qNote;
|
|
6640
|
+
this.state.messages.splice(targetIdx, 0, qInsert);
|
|
6112
6641
|
this.host.notify();
|
|
6113
6642
|
this.enqueueTypewrite(targetIdx, answer, lid);
|
|
6114
6643
|
} else {
|
|
6115
6644
|
var aiIdx = this.state.messages.length;
|
|
6116
|
-
|
|
6645
|
+
var qAppend = { role: "assistant", content: "", _localId: lid };
|
|
6646
|
+
if (qNote) qAppend._retrieval = qNote;
|
|
6647
|
+
this.state.messages.push(qAppend);
|
|
6117
6648
|
this.host.notify();
|
|
6118
6649
|
this.enqueueTypewrite(aiIdx, answer, lid);
|
|
6119
6650
|
}
|
|
@@ -6661,7 +7192,7 @@ var ChatSession = class {
|
|
|
6661
7192
|
enqueueTypewrite(idx, fullText, localId, paintedText) {
|
|
6662
7193
|
var self = this;
|
|
6663
7194
|
var target = this.state.messages[idx];
|
|
6664
|
-
if (target && target._ts === void 0) target._ts =
|
|
7195
|
+
if (target && target._ts === void 0) target._ts = this._replyStampFor(target);
|
|
6665
7196
|
if (!this.typewriterQueue) this.typewriterQueue = Promise.resolve();
|
|
6666
7197
|
this.typewriterQueue = this.typewriterQueue.then(function() {
|
|
6667
7198
|
return self.typewriteIntoIndex(idx, fullText, localId, paintedText);
|
|
@@ -6703,6 +7234,7 @@ var ChatSession = class {
|
|
|
6703
7234
|
var settled = { role: "assistant", content: painted, isPending: false, _localId: lid };
|
|
6704
7235
|
if (prevSettled._serverItemId) settled._serverItemId = prevSettled._serverItemId;
|
|
6705
7236
|
if (prevSettled._ownerKey) settled._ownerKey = prevSettled._ownerKey;
|
|
7237
|
+
if (latest._retrieval) settled._retrieval = latest._retrieval;
|
|
6706
7238
|
this.state.messages[pendingIdx] = settled;
|
|
6707
7239
|
this._removeStrayPendingAssistants();
|
|
6708
7240
|
this.host.notify();
|
|
@@ -6923,6 +7455,7 @@ var ChatSession = class {
|
|
|
6923
7455
|
var isErr = isErrorResponseBody(response);
|
|
6924
7456
|
var answer = isErr ? getErrorMessage(response) : ((platform === "openai" ? extractOpenAIText(response) : extractClaudeText(response)) || "").trim();
|
|
6925
7457
|
var reportedComplete = !isErr && !!answer && answer.indexOf(INDEXING_COMPLETE_MARKER) !== -1;
|
|
7458
|
+
var hNote = isErr ? null : extractRetrievalNote(response, answer);
|
|
6926
7459
|
var stripMarker = function(t) {
|
|
6927
7460
|
return reportedComplete ? t.split(INDEXING_COMPLETE_MARKER).join("").trim() : t;
|
|
6928
7461
|
};
|
|
@@ -6948,7 +7481,9 @@ var ChatSession = class {
|
|
|
6948
7481
|
}
|
|
6949
7482
|
var hPainted = this._paintedTextAt(idx);
|
|
6950
7483
|
var lid = this._newLocalId();
|
|
6951
|
-
|
|
7484
|
+
var hReply = { role: "assistant", content: hPainted, _localId: lid, _serverItemId: itemId };
|
|
7485
|
+
if (hNote) hReply._retrieval = hNote;
|
|
7486
|
+
this.state.messages[idx] = hReply;
|
|
6952
7487
|
this.host.notify();
|
|
6953
7488
|
this.enqueueTypewrite(idx, text, lid, hPainted);
|
|
6954
7489
|
this.updateHistoryCache();
|
|
@@ -6982,6 +7517,7 @@ var ChatSession = class {
|
|
|
6982
7517
|
}
|
|
6983
7518
|
var lid2 = this._newLocalId();
|
|
6984
7519
|
var reply = { role: "assistant", content: "", _localId: lid2, _serverItemId: itemId };
|
|
7520
|
+
if (hNote) reply._retrieval = hNote;
|
|
6985
7521
|
this.state.messages.splice(userIdx + 1, 0, reply);
|
|
6986
7522
|
this.host.notify();
|
|
6987
7523
|
this.enqueueTypewrite(userIdx + 1, text2, lid2);
|
|
@@ -8646,124 +9182,6 @@ function buildChatDisplayList(messages, opts) {
|
|
|
8646
9182
|
return out;
|
|
8647
9183
|
}
|
|
8648
9184
|
|
|
8649
|
-
// src/engine/project_settings.ts
|
|
8650
|
-
var UPLOAD_ACCESS_GROUPS = ["public", "authorized", "private"];
|
|
8651
|
-
var DEFAULT_UPLOAD_ACCESS_GROUP = "authorized";
|
|
8652
|
-
var PROJECT_SETTINGS_TABLE = "__SETTINGS__";
|
|
8653
|
-
var PROJECT_SETTINGS_UNIQUE_ID = "bq::settings";
|
|
8654
|
-
var PROJECT_SETTINGS_ACCESS_GROUP = "public";
|
|
8655
|
-
var UPLOAD_ACCESS_LABELS = {
|
|
8656
|
-
public: "Public",
|
|
8657
|
-
authorized: "Signed in users",
|
|
8658
|
-
private: "Only me"
|
|
8659
|
-
};
|
|
8660
|
-
var UPLOAD_ACCESS_HINTS = {
|
|
8661
|
-
public: "Anyone can ask about this file, including visitors who are not logged in.",
|
|
8662
|
-
authorized: "Only users signed in to this project can ask about this file.",
|
|
8663
|
-
private: "Only you can ask about this file."
|
|
8664
|
-
};
|
|
8665
|
-
var UPLOAD_ACCESS_OPTIONS = UPLOAD_ACCESS_GROUPS.map((value) => ({
|
|
8666
|
-
value,
|
|
8667
|
-
label: UPLOAD_ACCESS_LABELS[value],
|
|
8668
|
-
hint: UPLOAD_ACCESS_HINTS[value]
|
|
8669
|
-
}));
|
|
8670
|
-
var CHAT_GREETING_MAX_LENGTH = 400;
|
|
8671
|
-
function normalizeChatGreeting(value) {
|
|
8672
|
-
if (typeof value !== "string") return "";
|
|
8673
|
-
const trimmed = value.replace(/\r\n?/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
8674
|
-
return trimmed.slice(0, CHAT_GREETING_MAX_LENGTH);
|
|
8675
|
-
}
|
|
8676
|
-
function chatGreetingFrom(data) {
|
|
8677
|
-
return normalizeChatGreeting(data?.chat_greeting);
|
|
8678
|
-
}
|
|
8679
|
-
function normalizeUploadAccessGroup(value) {
|
|
8680
|
-
return UPLOAD_ACCESS_GROUPS.indexOf(value) === -1 ? DEFAULT_UPLOAD_ACCESS_GROUP : value;
|
|
8681
|
-
}
|
|
8682
|
-
function normalizeProjectAccessSetting(value) {
|
|
8683
|
-
if (value === "ask") return "ask";
|
|
8684
|
-
return UPLOAD_ACCESS_GROUPS.indexOf(value) === -1 ? null : value;
|
|
8685
|
-
}
|
|
8686
|
-
function accessSettingFrom(data) {
|
|
8687
|
-
return normalizeProjectAccessSetting(data?.upload_access_group);
|
|
8688
|
-
}
|
|
8689
|
-
function uploadAccessGroupFrom(data) {
|
|
8690
|
-
const v = accessSettingFrom(data);
|
|
8691
|
-
return v && v !== "ask" ? v : DEFAULT_UPLOAD_ACCESS_GROUP;
|
|
8692
|
-
}
|
|
8693
|
-
function asksUploadAccessFrom(data) {
|
|
8694
|
-
return accessSettingFrom(data) === "ask";
|
|
8695
|
-
}
|
|
8696
|
-
var reader = null;
|
|
8697
|
-
var cache = /* @__PURE__ */ new Map();
|
|
8698
|
-
function configureProjectSettings(fn) {
|
|
8699
|
-
reader = fn;
|
|
8700
|
-
}
|
|
8701
|
-
function entry(service) {
|
|
8702
|
-
let e = cache.get(service);
|
|
8703
|
-
if (!e) {
|
|
8704
|
-
e = { data: null, settled: false, inflight: null };
|
|
8705
|
-
cache.set(service, e);
|
|
8706
|
-
}
|
|
8707
|
-
return e;
|
|
8708
|
-
}
|
|
8709
|
-
function loadProjectSettings(service) {
|
|
8710
|
-
if (!service) return Promise.resolve(null);
|
|
8711
|
-
const e = entry(service);
|
|
8712
|
-
if (e.settled) return Promise.resolve(e.data);
|
|
8713
|
-
if (e.inflight) return e.inflight;
|
|
8714
|
-
if (!reader) return Promise.resolve(null);
|
|
8715
|
-
const run = reader(service).then((data) => data && typeof data === "object" ? data : null).catch(() => null).then((data) => {
|
|
8716
|
-
const cur = entry(service);
|
|
8717
|
-
if (cur.inflight === run) {
|
|
8718
|
-
cur.data = data;
|
|
8719
|
-
cur.settled = true;
|
|
8720
|
-
cur.inflight = null;
|
|
8721
|
-
}
|
|
8722
|
-
return data;
|
|
8723
|
-
});
|
|
8724
|
-
e.inflight = run;
|
|
8725
|
-
return run;
|
|
8726
|
-
}
|
|
8727
|
-
function primeProjectSettings(service) {
|
|
8728
|
-
void loadProjectSettings(service);
|
|
8729
|
-
}
|
|
8730
|
-
function readyProjectSettings(service) {
|
|
8731
|
-
return loadProjectSettings(service);
|
|
8732
|
-
}
|
|
8733
|
-
function cachedProjectSettings(service) {
|
|
8734
|
-
const e = cache.get(service);
|
|
8735
|
-
return e && e.settled ? e.data : null;
|
|
8736
|
-
}
|
|
8737
|
-
function projectSettingsSettled(service) {
|
|
8738
|
-
const e = cache.get(service);
|
|
8739
|
-
return !!e && e.settled;
|
|
8740
|
-
}
|
|
8741
|
-
function projectAccessSetting(service) {
|
|
8742
|
-
return accessSettingFrom(cachedProjectSettings(service));
|
|
8743
|
-
}
|
|
8744
|
-
function projectUploadAccessGroup(service) {
|
|
8745
|
-
return uploadAccessGroupFrom(cachedProjectSettings(service));
|
|
8746
|
-
}
|
|
8747
|
-
function projectAsksUploadAccess(service) {
|
|
8748
|
-
return asksUploadAccessFrom(cachedProjectSettings(service));
|
|
8749
|
-
}
|
|
8750
|
-
function projectChatGreeting(service) {
|
|
8751
|
-
return chatGreetingFrom(cachedProjectSettings(service));
|
|
8752
|
-
}
|
|
8753
|
-
function setProjectSettings(service, data) {
|
|
8754
|
-
if (!service) return;
|
|
8755
|
-
cache.set(service, { data: data || null, settled: true, inflight: null });
|
|
8756
|
-
}
|
|
8757
|
-
function patchProjectSettings(service, patch) {
|
|
8758
|
-
if (!service) return;
|
|
8759
|
-
const cur = cachedProjectSettings(service) || {};
|
|
8760
|
-
setProjectSettings(service, Object.assign({}, cur, patch));
|
|
8761
|
-
}
|
|
8762
|
-
function clearProjectSettings(service) {
|
|
8763
|
-
if (service) cache.delete(service);
|
|
8764
|
-
else cache.clear();
|
|
8765
|
-
}
|
|
8766
|
-
|
|
8767
9185
|
exports.BG_INDEXING_QUEUE_SUFFIX = BG_INDEXING_QUEUE_SUFFIX;
|
|
8768
9186
|
exports.BOM = BOM;
|
|
8769
9187
|
exports.BOM_EXTS = BOM_EXTS;
|
|
@@ -8773,10 +9191,12 @@ exports.CLAUDE_PER_REQUEST_INPUT_CAP = CLAUDE_PER_REQUEST_INPUT_CAP;
|
|
|
8773
9191
|
exports.CONTEXT_WINDOW_BY_MODEL = CONTEXT_WINDOW_BY_MODEL;
|
|
8774
9192
|
exports.CONTEXT_WINDOW_DEFAULT = CONTEXT_WINDOW_DEFAULT;
|
|
8775
9193
|
exports.ChatSession = ChatSession;
|
|
9194
|
+
exports.DATA_TOOL_NAMES = DATA_TOOL_NAMES;
|
|
8776
9195
|
exports.DEFAULT_CLAUDE_MODEL = DEFAULT_CLAUDE_MODEL;
|
|
8777
9196
|
exports.DEFAULT_CONTEXT_WINDOW = DEFAULT_CONTEXT_WINDOW;
|
|
8778
9197
|
exports.DEFAULT_OPENAI_MODEL = DEFAULT_OPENAI_MODEL;
|
|
8779
9198
|
exports.DEFAULT_UPLOAD_ACCESS_GROUP = DEFAULT_UPLOAD_ACCESS_GROUP;
|
|
9199
|
+
exports.DEFAULT_WEB_ACCESS = DEFAULT_WEB_ACCESS;
|
|
8780
9200
|
exports.EMPTY_INDEXING_REPLY = EMPTY_INDEXING_REPLY;
|
|
8781
9201
|
exports.EXPIRED_ATTACHMENT_URL_HOST = EXPIRED_ATTACHMENT_URL_HOST;
|
|
8782
9202
|
exports.EXPIRED_ATTACHMENT_URL_ORIGIN = EXPIRED_ATTACHMENT_URL_ORIGIN;
|
|
@@ -8806,6 +9226,7 @@ exports.MCP_NAME = MCP_NAME;
|
|
|
8806
9226
|
exports.MINT_CACHE_GENERATION = MINT_CACHE_GENERATION;
|
|
8807
9227
|
exports.MIN_INPUT_TOKEN_BUDGET = MIN_INPUT_TOKEN_BUDGET;
|
|
8808
9228
|
exports.MIN_PER_REQUEST_INPUT_CAP = MIN_PER_REQUEST_INPUT_CAP;
|
|
9229
|
+
exports.NO_DATA_TOOL_MARK = NO_DATA_TOOL_MARK;
|
|
8809
9230
|
exports.OUTPUT_TOKEN_RESERVE = OUTPUT_TOKEN_RESERVE;
|
|
8810
9231
|
exports.POLL_INTERVAL = POLL_INTERVAL;
|
|
8811
9232
|
exports.PRESIGN_SAFETY_MARGIN_MS = PRESIGN_SAFETY_MARGIN_MS;
|
|
@@ -8817,6 +9238,11 @@ exports.PROJECT_SETTINGS_ACCESS_GROUP = PROJECT_SETTINGS_ACCESS_GROUP;
|
|
|
8817
9238
|
exports.PROJECT_SETTINGS_TABLE = PROJECT_SETTINGS_TABLE;
|
|
8818
9239
|
exports.PROJECT_SETTINGS_UNIQUE_ID = PROJECT_SETTINGS_UNIQUE_ID;
|
|
8819
9240
|
exports.RENDER_FROM_TOKEN = RENDER_FROM_TOKEN;
|
|
9241
|
+
exports.RETRIEVAL_NOTES_MAX = RETRIEVAL_NOTES_MAX;
|
|
9242
|
+
exports.RETRIEVAL_NOTES_TOKEN_BUDGET = RETRIEVAL_NOTES_TOKEN_BUDGET;
|
|
9243
|
+
exports.RETRIEVAL_NOTE_MAX_CHARS = RETRIEVAL_NOTE_MAX_CHARS;
|
|
9244
|
+
exports.RETRIEVAL_NOTE_MAX_ITEMS = RETRIEVAL_NOTE_MAX_ITEMS;
|
|
9245
|
+
exports.RETRIEVAL_NOTE_PREFIX = RETRIEVAL_NOTE_PREFIX;
|
|
8820
9246
|
exports.RTF_EXTS = RTF_EXTS;
|
|
8821
9247
|
exports.RUN_RECORD_WORKING_STALE_MS = RUN_RECORD_WORKING_STALE_MS;
|
|
8822
9248
|
exports.STREAM_POLL_INTERVAL = STREAM_POLL_INTERVAL;
|
|
@@ -8829,7 +9255,9 @@ exports.XML_EXTS = XML_EXTS;
|
|
|
8829
9255
|
exports.__resetSplitHistoryState = __resetSplitHistoryState;
|
|
8830
9256
|
exports.accessSettingFrom = accessSettingFrom;
|
|
8831
9257
|
exports.adoptLocalAnswerIntoPage = adoptLocalAnswerIntoPage;
|
|
9258
|
+
exports.answerUsedNoDataTool = answerUsedNoDataTool;
|
|
8832
9259
|
exports.applyEncodingDeclaration = applyEncodingDeclaration;
|
|
9260
|
+
exports.applyRetrievalNotes = applyRetrievalNotes;
|
|
8833
9261
|
exports.asksUploadAccessFrom = asksUploadAccessFrom;
|
|
8834
9262
|
exports.bgIndexingQueueName = bgIndexingQueueName;
|
|
8835
9263
|
exports.buildAiAgentValue = buildAiAgentValue;
|
|
@@ -8854,6 +9282,9 @@ exports.chatCacheKey = chatCacheKey;
|
|
|
8854
9282
|
exports.chatEngineConfig = chatEngineConfig;
|
|
8855
9283
|
exports.chatGreetingFrom = chatGreetingFrom;
|
|
8856
9284
|
exports.chatStreamWiring = chatStreamWiring;
|
|
9285
|
+
exports.citationFileName = citationFileName;
|
|
9286
|
+
exports.citationLinkPart = citationLinkPart;
|
|
9287
|
+
exports.citationsFor = citationsFor;
|
|
8857
9288
|
exports.classifyInlineLink = classifyInlineLink;
|
|
8858
9289
|
exports.clearAttachmentParsers = clearAttachmentParsers;
|
|
8859
9290
|
exports.clearImagePreviewCache = clearImagePreviewCache;
|
|
@@ -8880,12 +9311,14 @@ exports.extractClaudeText = extractClaudeText;
|
|
|
8880
9311
|
exports.extractLastUserTextFromRequest = extractLastUserTextFromRequest;
|
|
8881
9312
|
exports.extractOpenAIText = extractOpenAIText;
|
|
8882
9313
|
exports.extractRemotePathFromAttachmentHref = extractRemotePathFromAttachmentHref;
|
|
9314
|
+
exports.extractRetrievalNote = extractRetrievalNote;
|
|
8883
9315
|
exports.fetchLiveIndexingKeys = fetchLiveIndexingKeys;
|
|
8884
9316
|
exports.fillHistoryViewport = fillHistoryViewport;
|
|
8885
9317
|
exports.filterListByClearHorizon = filterListByClearHorizon;
|
|
8886
9318
|
exports.findAttachmentParser = findAttachmentParser;
|
|
8887
9319
|
exports.formatChatTimestamp = formatChatTimestamp;
|
|
8888
9320
|
exports.formatDuration = formatDuration;
|
|
9321
|
+
exports.formatRetrievalNote = formatRetrievalNote;
|
|
8889
9322
|
exports.getAttachmentParsers = getAttachmentParsers;
|
|
8890
9323
|
exports.getChatHistory = getChatHistory;
|
|
8891
9324
|
exports.getContextWindow = getContextWindow;
|
|
@@ -8906,6 +9339,7 @@ exports.indexingAccessGroup = indexingAccessGroup;
|
|
|
8906
9339
|
exports.isAuthExpiredError = isAuthExpiredError;
|
|
8907
9340
|
exports.isBgIndexingQueue = isBgIndexingQueue;
|
|
8908
9341
|
exports.isCsrStatusEnvelope = isCsrStatusEnvelope;
|
|
9342
|
+
exports.isDataToolName = isDataToolName;
|
|
8909
9343
|
exports.isErrorResponseBody = isErrorResponseBody;
|
|
8910
9344
|
exports.isHttpUrlLike = isHttpUrlLike;
|
|
8911
9345
|
exports.isImageVisionFile = isImageVisionFile;
|
|
@@ -8917,6 +9351,7 @@ exports.isPagedReadFile = isPagedReadFile;
|
|
|
8917
9351
|
exports.isPreviewableImagePath = isPreviewableImagePath;
|
|
8918
9352
|
exports.isProviderApiKeyError = isProviderApiKeyError;
|
|
8919
9353
|
exports.isRetriedFailure = isRetriedFailure;
|
|
9354
|
+
exports.isRetrievalNote = isRetrievalNote;
|
|
8920
9355
|
exports.isServerExtractable = isServerExtractable;
|
|
8921
9356
|
exports.isServiceDbAttachmentHref = isServiceDbAttachmentHref;
|
|
8922
9357
|
exports.isWindowedReadFile = isWindowedReadFile;
|
|
@@ -8941,6 +9376,7 @@ exports.normalizeProjectAccessSetting = normalizeProjectAccessSetting;
|
|
|
8941
9376
|
exports.normalizeTextContent = normalizeTextContent;
|
|
8942
9377
|
exports.normalizeTrailingInlineToken = normalizeTrailingInlineToken;
|
|
8943
9378
|
exports.normalizeUploadAccessGroup = normalizeUploadAccessGroup;
|
|
9379
|
+
exports.normalizeWebAccess = normalizeWebAccess;
|
|
8944
9380
|
exports.notifyAgentSaveAttachment = notifyAgentSaveAttachment;
|
|
8945
9381
|
exports.parseAiAgentValue = parseAiAgentValue;
|
|
8946
9382
|
exports.parseAttachmentContent = parseAttachmentContent;
|
|
@@ -8960,6 +9396,8 @@ exports.projectAsksUploadAccess = projectAsksUploadAccess;
|
|
|
8960
9396
|
exports.projectChatGreeting = projectChatGreeting;
|
|
8961
9397
|
exports.projectSettingsSettled = projectSettingsSettled;
|
|
8962
9398
|
exports.projectUploadAccessGroup = projectUploadAccessGroup;
|
|
9399
|
+
exports.projectWebAccess = projectWebAccess;
|
|
9400
|
+
exports.providerBodyWasRead = providerBodyWasRead;
|
|
8963
9401
|
exports.readExpiredAttachmentHref = readExpiredAttachmentHref;
|
|
8964
9402
|
exports.readyProjectSettings = readyProjectSettings;
|
|
8965
9403
|
exports.registerAttachmentParser = registerAttachmentParser;
|
|
@@ -8968,6 +9406,9 @@ exports.renderInlineLinkHtml = renderInlineLinkHtml;
|
|
|
8968
9406
|
exports.repairUrlEntities = repairUrlEntities;
|
|
8969
9407
|
exports.repairUrlWhitespace = repairUrlWhitespace;
|
|
8970
9408
|
exports.resolveImagePreviewUrl = resolveImagePreviewUrl;
|
|
9409
|
+
exports.resolveWebAccess = resolveWebAccess;
|
|
9410
|
+
exports.retrievalNoteFromText = retrievalNoteFromText;
|
|
9411
|
+
exports.retrievalNoteOfAnswer = retrievalNoteOfAnswer;
|
|
8971
9412
|
exports.runIndexUniqueId = runIndexUniqueId;
|
|
8972
9413
|
exports.safeDecodeURIComponent = safeDecodeURIComponent;
|
|
8973
9414
|
exports.sanitizeAttachmentLinksForHistory = sanitizeAttachmentLinksForHistory;
|
|
@@ -8986,5 +9427,7 @@ exports.typewriterResumeIndex = typewriterResumeIndex;
|
|
|
8986
9427
|
exports.uploadAccessGroupFrom = uploadAccessGroupFrom;
|
|
8987
9428
|
exports.upsertIndexRunRecordSafe = upsertIndexRunRecordSafe;
|
|
8988
9429
|
exports.wallClockNow = wallClockNow;
|
|
9430
|
+
exports.webAccessEnabled = webAccessEnabled;
|
|
9431
|
+
exports.webAccessFrom = webAccessFrom;
|
|
8989
9432
|
//# sourceMappingURL=engine.cjs.map
|
|
8990
9433
|
//# sourceMappingURL=engine.cjs.map
|