@anuma/sdk 3.0.0 → 3.1.1

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.
@@ -0,0 +1,1859 @@
1
+ import {
2
+ BASE_URL
3
+ } from "./chunk-4ETHWSXJ.mjs";
4
+
5
+ // src/lib/memory/recallConstants.ts
6
+ var RECALL_TOOL_NAME = "recall_memory";
7
+
8
+ // src/lib/memoryEngine/generate.ts
9
+ import { postApiV1Embeddings } from "@anuma/sdk";
10
+
11
+ // src/lib/memoryEngine/constants.ts
12
+ var DEFAULT_API_EMBEDDING_MODEL = "qwen/qwen3-embedding-8b";
13
+
14
+ // src/lib/memoryEngine/generate.ts
15
+ var EMBED_MAX_ATTEMPTS = 4;
16
+ async function withEmbeddingRetry(call) {
17
+ let last;
18
+ let lastThrown;
19
+ let threw = false;
20
+ for (let attempt = 1; attempt <= EMBED_MAX_ATTEMPTS; attempt++) {
21
+ try {
22
+ threw = false;
23
+ last = await call();
24
+ if (!last.error) return last;
25
+ const status = last.response?.status;
26
+ const retryable = status === void 0 || status === 429 || status >= 500;
27
+ if (!retryable) return last;
28
+ } catch (err) {
29
+ threw = true;
30
+ lastThrown = err;
31
+ }
32
+ if (attempt < EMBED_MAX_ATTEMPTS) {
33
+ const base = 250 * 2 ** (attempt - 1);
34
+ await new Promise((r) => setTimeout(r, base + Math.random() * 0.4 * base));
35
+ }
36
+ }
37
+ if (threw) throw lastThrown;
38
+ return last;
39
+ }
40
+ var EmbeddingHttpError = class extends Error {
41
+ constructor(message, status) {
42
+ super(message);
43
+ this.name = "EmbeddingHttpError";
44
+ this.status = status;
45
+ }
46
+ };
47
+ function embeddingErrorFrom(response) {
48
+ const message = typeof response.error === "object" && response.error && "error" in response.error ? response.error.error : "API embedding failed";
49
+ return new EmbeddingHttpError(message, response.response?.status);
50
+ }
51
+ async function generateEmbedding(text, options) {
52
+ const { baseUrl = BASE_URL, getToken, apiKey, model, cache } = options;
53
+ if (cache) {
54
+ const cached = cache.get(text);
55
+ if (cached) return Array.from(cached);
56
+ }
57
+ let headers;
58
+ if (apiKey) {
59
+ headers = { "X-API-Key": apiKey };
60
+ } else if (getToken) {
61
+ const token = await getToken();
62
+ if (!token) {
63
+ throw new Error("No token available for embedding generation");
64
+ }
65
+ headers = { Authorization: `Bearer ${token}` };
66
+ } else {
67
+ throw new Error("Either apiKey or getToken must be provided");
68
+ }
69
+ const response = await withEmbeddingRetry(
70
+ () => postApiV1Embeddings({
71
+ baseUrl,
72
+ body: {
73
+ // Mask PII from the request body only — the cache above still keys on
74
+ // the original `text`, so callers keep their original values.
75
+ input: options.maskInput ? options.maskInput(text) : text,
76
+ model: model ?? DEFAULT_API_EMBEDDING_MODEL
77
+ },
78
+ headers
79
+ })
80
+ );
81
+ if (response.error) {
82
+ throw embeddingErrorFrom(response);
83
+ }
84
+ if (!response.data?.data?.[0]?.embedding) {
85
+ throw new Error("No embedding returned from API");
86
+ }
87
+ const embedding = response.data.data[0].embedding;
88
+ if (options.onUsage && response.data.usage) {
89
+ options.onUsage({
90
+ promptTokens: response.data.usage.prompt_tokens ?? 0,
91
+ totalTokens: response.data.usage.total_tokens ?? 0
92
+ });
93
+ }
94
+ const f32Embedding = Float32Array.from(embedding);
95
+ if (cache) {
96
+ cache.set(text, f32Embedding);
97
+ }
98
+ return Array.from(f32Embedding);
99
+ }
100
+ var DEFAULT_EMBEDDING_BATCH_SIZE = 100;
101
+ var DEFAULT_EMBEDDING_BATCH_CONCURRENCY = 3;
102
+ async function generateEmbeddingsBatch(texts, headers, baseUrl, model, onUsage, maskInput) {
103
+ const response = await withEmbeddingRetry(
104
+ () => postApiV1Embeddings({
105
+ baseUrl,
106
+ // Mask PII from the request body only; cache/order still key on originals.
107
+ body: { input: maskInput ? texts.map(maskInput) : texts, model },
108
+ headers
109
+ })
110
+ );
111
+ if (response.error) {
112
+ throw embeddingErrorFrom(response);
113
+ }
114
+ if (!response.data?.data) {
115
+ throw new Error("No embeddings returned from API");
116
+ }
117
+ if (onUsage && response.data.usage) {
118
+ onUsage({
119
+ promptTokens: response.data.usage.prompt_tokens ?? 0,
120
+ totalTokens: response.data.usage.total_tokens ?? 0
121
+ });
122
+ }
123
+ return response.data.data.map((item) => item.embedding ?? []);
124
+ }
125
+ async function generateEmbeddings(texts, options) {
126
+ if (texts.length === 0) return [];
127
+ const { baseUrl = BASE_URL, getToken, apiKey, model, batchSize, cache } = options;
128
+ const chunkSize = batchSize ?? DEFAULT_EMBEDDING_BATCH_SIZE;
129
+ const results = new Array(texts.length).fill(null);
130
+ const uncachedIndices = [];
131
+ const uncachedTexts = [];
132
+ for (let i = 0; i < texts.length; i++) {
133
+ if (cache) {
134
+ const cached = cache.get(texts[i]);
135
+ if (cached) {
136
+ results[i] = Array.from(cached);
137
+ continue;
138
+ }
139
+ }
140
+ uncachedIndices.push(i);
141
+ uncachedTexts.push(texts[i]);
142
+ }
143
+ if (uncachedTexts.length === 0) {
144
+ return results;
145
+ }
146
+ let headers;
147
+ if (apiKey) {
148
+ headers = { "X-API-Key": apiKey };
149
+ } else if (getToken) {
150
+ const token = await getToken();
151
+ if (!token) {
152
+ throw new Error("No token available for embedding generation");
153
+ }
154
+ headers = { Authorization: `Bearer ${token}` };
155
+ } else {
156
+ throw new Error("Either apiKey or getToken must be provided");
157
+ }
158
+ const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
159
+ let newEmbeddings;
160
+ if (uncachedTexts.length <= chunkSize) {
161
+ newEmbeddings = await generateEmbeddingsBatch(
162
+ uncachedTexts,
163
+ headers,
164
+ baseUrl,
165
+ embeddingModel,
166
+ options.onUsage,
167
+ options.maskInput
168
+ );
169
+ } else {
170
+ const chunks = [];
171
+ for (let i = 0; i < uncachedTexts.length; i += chunkSize) {
172
+ chunks.push(uncachedTexts.slice(i, i + chunkSize));
173
+ }
174
+ const allEmbeddings = new Array(chunks.length);
175
+ let nextIndex = 0;
176
+ const worker = async () => {
177
+ while (nextIndex < chunks.length) {
178
+ const idx = nextIndex++;
179
+ allEmbeddings[idx] = await generateEmbeddingsBatch(
180
+ chunks[idx],
181
+ headers,
182
+ baseUrl,
183
+ embeddingModel,
184
+ options.onUsage,
185
+ options.maskInput
186
+ );
187
+ }
188
+ };
189
+ const workers = Array.from(
190
+ { length: Math.min(DEFAULT_EMBEDDING_BATCH_CONCURRENCY, chunks.length) },
191
+ () => worker()
192
+ );
193
+ await Promise.all(workers);
194
+ newEmbeddings = allEmbeddings.flat();
195
+ }
196
+ for (let i = 0; i < uncachedIndices.length; i++) {
197
+ const f32Embedding = Float32Array.from(newEmbeddings[i]);
198
+ results[uncachedIndices[i]] = Array.from(f32Embedding);
199
+ if (cache) {
200
+ cache.set(uncachedTexts[i], f32Embedding);
201
+ }
202
+ }
203
+ return results;
204
+ }
205
+
206
+ // src/tools/appBuilderPrompt.ts
207
+ var APP_BUILDER_PROMPT = `App Builder tools (create_file, patch_file, and the rest described below) are available this turn, but they are OPTIONAL \u2014 having them does not mean you should build anything.
208
+
209
+ Build ONLY when the user gives an explicit instruction to build, create, generate, or change an app, tool, or UI \u2014 e.g. "build a todo app", "make a snake game", "add a footer to the app", "change the background to blue". A bare app name or feature reference is NOT a build instruction: if the user just says "todo app", "a snake game", or "qr code", or asks a question about an app, do NOT generate anything \u2014 reply normally, or ask what they'd like you to build. The same holds for greetings, questions, stories, explanations, advice, or code that isn't a runnable app: respond normally and do not create files. When it's unclear whether the user actually wants an app built, ask instead of building.
210
+
211
+ Once the user HAS given an explicit build instruction, you are in App Builder mode: you produce polished, production-quality React apps that feel designed \u2014 not generic.
212
+
213
+ DESIGN: on the initial build, before writing code, briefly state (2-3 sentences) what you want this app to feel like \u2014 name the specific fonts, the palette in hex, and one signature detail. Be concrete: "Fraunces display + Inter body + JetBrains Mono metadata, terracotta #b75432 accent on bone #f3efe6, paper-grain texture via inline SVG noise" is the kind of specificity that anchors the build. Default to nothing: don't pick "safe modern productivity" unless the brief literally asks for utilitarian. On later turns, state a design brief only when the change is visual (a restyle, new theme, or new UI); for a logic, data, or behavior-only change, skip the brief and just make the change.
214
+
215
+ WORKFLOW:
216
+ 1. NEVER output code as text or markdown. ALWAYS use tools.
217
+ 2. Initial build: state your design brief, then create_file with a "files" array writing ALL files in one call. The preview renders automatically.
218
+ 3. patch_file requires read_file for that path earlier in the conversation (or create_file in the same conversation, which counts). read_file output is numbered "42: <text>" \u2014 the prefix is display-only; never copy it into "find". Each "find" must match exactly one location \u2014 include 2-3 lines of surrounding context. Pass multiple patches per call to change distinct locations.
219
+ 4. Prefer patch_file for changes to existing files \u2014 surgical, reviewable, preserves surrounding code. Reserve create_file overwrites for substantial restructuring. Inserts: find the line before, replace with that line + the new line. Deletes: empty replace.
220
+ 5. After substantial changes, call critique_design first (taste, hierarchy, distinctiveness, weakest decision) and then audit_design (mechanical: tokens, focus states, aria-labels, alt, heading order). Patch what each surfaces. critique catches taste issues no checklist would; audit catches mechanics you'd miss. Run both; act on both.
221
+ 6. Before declaring done, call verify_app. The host's preview runtime reports whether your app actually mounted and surfaces any errors it captured \u2014 hallucinated imports, syntax the bundler rejected, undefined components, runtime crashes. If \`rendered\` is false or \`errors\` is non-empty, fix every error before the next message. If the response includes a \`note\` that verify_app isn't wired in this host, skip \u2014 audit + critique are your feedback in that case.
222
+ 7. Keep responses to a sentence or two. Exception: when responding to critique_design, write a substantive paragraph per rubric question.
223
+
224
+ STRUCTURE:
225
+ - App.js: default-export React component. For non-trivial apps, define small co-located helpers (Header, CardList, Card, EditDialog, \u2026) above the default export rather than one giant App.
226
+ - App.css: all styles, imported in App.js. No inline style objects.
227
+ - package.json: list ALL imported packages including react. No CDN script tags. Versions are auto-pinned.
228
+ - Do NOT create index.js or index.html (auto-generated).
229
+
230
+ STATE & PERSISTENCE:
231
+ - The code runs in the user's browser \u2014 standard browser APIs available (localStorage, URL hash, Date, fetch).
232
+ - Persist user-created data (todos, cards, notes, settings) with localStorage so it survives refresh. Use a versioned key ("kanban.v1") and JSON.parse inside try/catch on read.
233
+ - Treat persisted data as untrusted and possibly stale: a saved record may predate a field you just added (a timestamp, id, or setting), so default-fill or skip missing fields on read \u2014 never assume a loaded record has every field the current code expects. Bump the key version when the shape changes.
234
+ - Lift state to App when multiple children need it; keep it local otherwise. Extract non-trivial logic into custom hooks (useTodos, useTimer).
235
+ - useReducer for state shapes with several interrelated fields (cards + columns + filters + drag state).
236
+
237
+ FILE & IMAGE UPLOAD:
238
+ - Picker: \`<input type="file" accept="image/*" multiple>\` (vary accept by use case: \`.csv,text/csv\`, \`.txt,.md,text/plain\`, etc.).
239
+ - Drag-and-drop: call \`e.preventDefault()\` in onDragOver (otherwise drop never fires); read files from \`event.dataTransfer.files\`. Drop and picker should coexist.
240
+ - Previews: \`<img src={URL.createObjectURL(file)} />\`. Call \`URL.revokeObjectURL\` on unmount or before replacing \u2014 otherwise the browser leaks memory.
241
+ - localStorage cannot hold large binary. Persist only metadata for big files; re-upload on next visit (or use IndexedDB if you really need durable binary storage).
242
+ - Validate size and type before processing; reject files larger than the app can hold in memory.
243
+
244
+ AI CAPABILITIES \u2014 \`window.app.complete(prompt: string): Promise<string>\` is available in this environment. Build for the real call.
245
+ - When the app's core purpose is conversation, generation, grading, recommendation, or any natural-language task, this IS the implementation \u2014 call it. NEVER fake it: no hardcoded or canned replies, no random or \`setTimeout\` "thinking", no demo-mode stub. A chat app, tutor, or assistant that doesn't call \`window.app.complete\` is wrong, not a placeholder.
246
+ - Don't force it where it isn't needed \u2014 a calculator, todo list, or timer has no reason to call it. Match the implementation to what the app actually does.
247
+ - Async: wrap in try/catch, show a loading state, and disable dependent inputs while waiting. Surface failures gracefully ("Couldn't reach the AI \u2014 try again").
248
+ - Structure prompts with clear sections (Role / Context / Task / Format) and pass the relevant state (e.g. the running conversation) as context. Render the response as untrusted text: \`white-space: pre-wrap\`, never dangerouslySetInnerHTML.
249
+ - Skip the call only when the answer is genuinely static \u2014 no language or reasoning needed. Cache repeated prompts in component state when sensible.
250
+
251
+ CODE QUALITY \u2014 write as a senior engineer would:
252
+ - Handle edge cases (empty inputs, division by zero, invalid data). Show helpful error states, not crashes.
253
+ - Never let formatting throw during render: guard \`new Date(x)\` / \`Intl\` / \`toLocaleString\` / \`.toFixed\` against missing or invalid values (e.g. \`Number.isNaN(d.getTime())\`) and return a fallback \u2014 one bad value from storage or state must not blank the whole app.
254
+ - Semantic HTML: real headings, labels, buttons (not divs with onClick), form elements.
255
+ - Accessible: aria-labels on icon buttons, focus-visible styles, keyboard navigable.
256
+ - Extract into custom hooks or helpers when a component grows past ~80 lines.
257
+
258
+ STYLING \u2014 Tailwind is the CHASSIS, custom CSS carries the IDENTITY:
259
+ - Tailwind (via Play CDN) for layout, spacing, state (hover/focus), responsive prefixes \u2014 uniform structure.
260
+ - App.css carries the identity: @import the Google Fonts, declare CSS custom properties (--bg, --panel, --card, --ink, --muted, --accent, --hairline, --radius, --radius-sm, --shadow-card, --shadow-card-hi) in :root, gradients/textures/signature animations Tailwind can't express. Reference tokens from Tailwind via arbitrary values (\`className="bg-[var(--bg)]"\`) when needed. A well-designed app has 60-200 lines of App.css \u2014 not zero.
261
+ - Modern CSS is how apps stop reading as generic-AI-output: \`color-mix(in oklab, var(--accent) 14%, transparent)\` for tinting, \`oklch()\` / \`oklab()\` for perceptually-uniform color, \`:has()\`, \`conic-gradient\` / \`radial-gradient\`, \`backdrop-filter\`, \`@container\` queries, inline SVG \`feTurbulence\` for texture.
262
+ - Don't pull additional CSS frameworks. Tailwind + custom CSS is enough.
263
+
264
+ VISUAL DESIGN \u2014 every app should look like a real product, not a wireframe:
265
+ - Let the chosen aesthetic drive every decision. "Editorial" wants italic display type and restrained color; "retro arcade" wants chunky shadows and saturated gradients. No safe-modern defaults.
266
+ - Atmospheric depth: subtle radial wash, paper grain (inline SVG noise), warm/cool tint \u2014 every canvas should feel like a designed space, not a #fff rectangle.
267
+ - Three shadow tiers (rest / hover / active). Pair drop shadows with inset highlights (\`box-shadow: 0 1px 0 rgba(255,255,255,.9) inset, 0 6px 14px -10px ...\`) for tactile feel.
268
+ - Typography hierarchy: display font for headlines (italic accents on key words via \`<em>\`), body font for content, mono for metadata/labels \u2014 different families telegraph different information types.
269
+ - Interactive polish: hover/active/focus on every clickable element, 200-300ms cubic-bezier transitions, keyboard shortcuts where natural (Esc to cancel, Cmd/Ctrl+Enter to commit), focus-visible rings keyed to the accent.
270
+ - Light/dark mode if reasonable: a single CSS variable inversion under \`[data-theme="dark"]\`.
271
+ - Customization affordance for multi-session apps: small "Tweaks" panel with theme / density / accent. Skip for throwaway demos.
272
+ - Responsive: Tailwind responsive prefixes (\`sm:\`, \`md:\`, \`lg:\`) and \`@media\` in App.css. Mobile-first.`;
273
+
274
+ // src/tools/document/documentBuilderPrompt.ts
275
+ var DOCUMENT_BUILDER_PROMPT = `Document tools (create_document, read_document, patch_document) are available this turn, but they are OPTIONAL \u2014 having them does not mean you should produce a document.
276
+
277
+ Use them ONLY when the user explicitly asks you to draft, write, compose, or generate a document \u2014 a contract, agreement, NDA, letter, cover letter, memo, essay, report, brief, proposal, or resume \u2014 e.g. "draft me a service agreement", "write a cover letter for this job", "make a one-page memo about X". A bare topic, a question, a request for an explanation, or a request for a slide deck or an interactive app is NOT a document instruction: respond normally and do not call these tools. When it's unclear whether the user wants a finished document, ask instead.
278
+
279
+ When you DO author a document, the document body goes entirely into the tool \u2014 NEVER write the document text as chat prose. The tool renders your source to a real, paginated PDF and attaches it to your reply automatically. AFTER the tool returns, briefly confirm in ONE short sentence what you made (e.g. "Drafted a cover letter for the product manager role \u2014 see the attached PDF."). Do not restate the document's contents.
280
+
281
+ AUTHORING \u2014 write the "source" as a single @react-pdf/renderer JSX expression (this is react-pdf, NOT HTML and NOT a slide deck):
282
+ - Root is exactly one <Document> with at least one <Page>. Its only children are <Page> elements. Add more <Page> elements for longer documents \u2014 react-pdf also paginates overflow automatically.
283
+ - Inside a <Page>: use <View> as flex containers and <Text> for ALL text. Raw text is only allowed inside <Text> (and <Link>); a <View> or <Page> may not contain bare text.
284
+ - <Text> may nest <Text>, <Link>, or <Note> for inline styling, hyperlinks, and (rarely) a PDF comment annotation. A <Link> src must be an ordinary web link \u2014 http(s), mailto, tel, or a relative/anchor link; javascript:/data:/file: links are rejected. <Image> is self-closing and its src MUST be an inline "data:" URI \u2014 remote image URLs are rejected for privacy. Vector primitives (<Rect>/<Circle>/<Path>/etc.) must live inside an <Svg>.
285
+ - Styling is react-pdf style objects only: style={{ marginTop: 12, fontSize: 11, fontFamily: "Times-Roman", flexDirection: "column" }}. Flexbox only \u2014 there is NO CSS grid, float, or box-shadow. Use camelCase react-pdf keys (margin/padding, flex*, width/height, color/backgroundColor, border*, text*, etc.).
286
+ - fontFamily must be a built-in PDF font: Helvetica, Times-Roman, or Courier (with -Bold / -Oblique / -Italic / -BoldOblique variants), or Symbol / ZapfDingbats. Custom fonts are not supported.
287
+ - LITERAL VALUES ONLY: no variables, no JS expressions, no .map(), no event handlers (onClick, \u2026), no spreads. Every attribute is a literal string, number, boolean, or a style object of literals.
288
+
289
+ A minimal example:
290
+
291
+ <Document>
292
+ <Page size="A4" style={{ paddingVertical: 56, paddingHorizontal: 64, fontFamily: "Times-Roman", fontSize: 11, lineHeight: 1.5 }}>
293
+ <Text style={{ fontSize: 18, fontFamily: "Times-Bold", marginBottom: 16, textAlign: "center" }}>MUTUAL NON-DISCLOSURE AGREEMENT</Text>
294
+ <Text style={{ marginBottom: 10 }}>This Mutual Non-Disclosure Agreement (the "Agreement") is entered into as of the date of last signature below.</Text>
295
+ <View style={{ marginTop: 14 }}>
296
+ <Text style={{ fontFamily: "Times-Bold", marginBottom: 4 }}>1. Confidential Information</Text>
297
+ <Text>Each party may disclose certain confidential and proprietary information to the other party.</Text>
298
+ </View>
299
+ </Page>
300
+ </Document>
301
+
302
+ WORKFLOW:
303
+ 1. To create or fully restructure a document, call create_document with the complete source.
304
+ 2. To revise an existing document, call read_document first (its output is line-numbered "42: <text>" \u2014 the prefix is display-only, never copy it into a "find"), then patch_document with small find/replace edits. Each "find" must match exactly one location; add 2-3 lines of surrounding context if needed. Prefer patching over rewriting.
305
+ 3. To attach several separate documents in one reply, call create_document multiple times with distinct documentId slugs (e.g. "nda", "cover-letter"). Each renders to its own PDF.
306
+ 4. If a tool reports a DSL error (with line:col) or a render failure, fix the source and retry \u2014 do not give up or fall back to writing the document as chat text.`;
307
+
308
+ // src/lib/logger.ts
309
+ var consoleLogger = {
310
+ debug: console.log.bind(console),
311
+ info: console.info.bind(console),
312
+ warn: console.warn.bind(console),
313
+ error: console.error.bind(console)
314
+ };
315
+ var currentLogger = consoleLogger;
316
+ function getLogger() {
317
+ return currentLogger;
318
+ }
319
+
320
+ // src/lib/memoryEngine/chunking.ts
321
+ var DEFAULT_CHUNK_SIZE = 400;
322
+ var DEFAULT_CHUNK_OVERLAP = 50;
323
+ var DEFAULT_MIN_CHUNK_SIZE = 50;
324
+ function splitIntoSentences(text) {
325
+ const sentenceRegex = /[.!?]+[\s"')}\]]*(?=\s|$)/g;
326
+ const sentences = [];
327
+ let lastIndex = 0;
328
+ let match;
329
+ while ((match = sentenceRegex.exec(text)) !== null) {
330
+ const endIndex = match.index + match[0].length;
331
+ const sentence = text.slice(lastIndex, endIndex).trim();
332
+ if (sentence) {
333
+ sentences.push(sentence);
334
+ }
335
+ lastIndex = endIndex;
336
+ }
337
+ const remaining = text.slice(lastIndex).trim();
338
+ if (remaining) {
339
+ sentences.push(remaining);
340
+ }
341
+ return sentences;
342
+ }
343
+ function chunkText(text, options) {
344
+ const {
345
+ chunkSize = DEFAULT_CHUNK_SIZE,
346
+ chunkOverlap: requestedOverlap = DEFAULT_CHUNK_OVERLAP,
347
+ minChunkSize = DEFAULT_MIN_CHUNK_SIZE
348
+ } = options ?? {};
349
+ const chunkOverlap = Math.min(requestedOverlap, chunkSize - 1);
350
+ if (text.length <= chunkSize) {
351
+ return [
352
+ {
353
+ text: text.trim(),
354
+ startOffset: 0,
355
+ endOffset: text.length
356
+ }
357
+ ];
358
+ }
359
+ const sentences = splitIntoSentences(text);
360
+ const chunks = [];
361
+ let currentChunkSentences = [];
362
+ let currentChunkStart = 0;
363
+ let currentOffset = 0;
364
+ for (let i = 0; i < sentences.length; i++) {
365
+ const sentence = sentences[i];
366
+ let sentenceStart = text.indexOf(sentence, currentOffset);
367
+ if (sentenceStart === -1) {
368
+ sentenceStart = currentOffset;
369
+ }
370
+ const sentenceEnd = sentenceStart + sentence.length;
371
+ if (sentence.length > chunkSize) {
372
+ if (currentChunkSentences.length > 0) {
373
+ const chunkText2 = currentChunkSentences.join(" ");
374
+ chunks.push({
375
+ text: chunkText2,
376
+ startOffset: currentChunkStart,
377
+ endOffset: currentOffset
378
+ });
379
+ currentChunkSentences = [];
380
+ }
381
+ for (let j = 0; j < sentence.length; j += chunkSize - chunkOverlap) {
382
+ const chunkEnd = Math.min(j + chunkSize, sentence.length);
383
+ const chunk = sentence.slice(j, chunkEnd);
384
+ if (chunk.length >= minChunkSize) {
385
+ chunks.push({
386
+ text: chunk,
387
+ startOffset: sentenceStart + j,
388
+ endOffset: sentenceStart + chunkEnd
389
+ });
390
+ }
391
+ }
392
+ currentChunkStart = sentenceEnd;
393
+ currentOffset = sentenceEnd;
394
+ continue;
395
+ }
396
+ const currentText = currentChunkSentences.join(" ");
397
+ const potentialLength = currentText.length + (currentText ? 1 : 0) + sentence.length;
398
+ if (potentialLength > chunkSize && currentChunkSentences.length > 0) {
399
+ chunks.push({
400
+ text: currentText,
401
+ startOffset: currentChunkStart,
402
+ endOffset: currentOffset
403
+ });
404
+ const overlapSentences = [];
405
+ let overlapLength = 0;
406
+ for (let j = currentChunkSentences.length - 1; j >= 0 && overlapLength < chunkOverlap; j--) {
407
+ overlapSentences.unshift(currentChunkSentences[j]);
408
+ overlapLength += currentChunkSentences[j].length + 1;
409
+ }
410
+ if (overlapSentences.length > 0) {
411
+ currentChunkSentences = [...overlapSentences, sentence];
412
+ const overlapText = overlapSentences[0];
413
+ const overlapStart = text.lastIndexOf(overlapText, currentOffset);
414
+ currentChunkStart = overlapStart === -1 ? sentenceStart : overlapStart;
415
+ } else {
416
+ currentChunkSentences = [sentence];
417
+ currentChunkStart = sentenceStart;
418
+ }
419
+ } else {
420
+ if (currentChunkSentences.length === 0) {
421
+ currentChunkStart = sentenceStart;
422
+ }
423
+ currentChunkSentences.push(sentence);
424
+ }
425
+ currentOffset = sentenceEnd;
426
+ }
427
+ if (currentChunkSentences.length > 0) {
428
+ const chunkText2 = currentChunkSentences.join(" ");
429
+ if (chunkText2.length >= minChunkSize) {
430
+ chunks.push({
431
+ text: chunkText2,
432
+ startOffset: currentChunkStart,
433
+ endOffset: text.length
434
+ });
435
+ }
436
+ }
437
+ if (chunks.length === 0) {
438
+ const trimmedText = text.trim();
439
+ if (trimmedText.length > 0) {
440
+ return [
441
+ {
442
+ text: trimmedText,
443
+ startOffset: 0,
444
+ endOffset: text.length
445
+ }
446
+ ];
447
+ }
448
+ }
449
+ return chunks;
450
+ }
451
+ function shouldChunkMessage(content, chunkSize = DEFAULT_CHUNK_SIZE) {
452
+ return content.length > chunkSize;
453
+ }
454
+
455
+ // src/lib/memoryEngine/vector.ts
456
+ function cosineSimilarity(a, b) {
457
+ if (a.length !== b.length) return 0;
458
+ let dot = 0;
459
+ let normA = 0;
460
+ let normB = 0;
461
+ for (let i = 0; i < a.length; i++) {
462
+ dot += a[i] * b[i];
463
+ normA += a[i] * a[i];
464
+ normB += b[i] * b[i];
465
+ }
466
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
467
+ return denom === 0 ? 0 : dot / denom;
468
+ }
469
+
470
+ // src/lib/tools/serverTools.ts
471
+ var DEFAULT_CACHE_EXPIRATION_MS = 24 * 60 * 60 * 1e3;
472
+ var SERVER_TOOLS_CACHE_KEY = "sdk_server_tools_cache";
473
+ var CACHE_VERSION = "1.3";
474
+ var MIN_CONTENT_LENGTH_FOR_TOOLS = 5;
475
+ var MAX_CLIENT_TOOLS_AFTER_FILTER = 10;
476
+ var CLIENT_TOOLS_MIN_SIMILARITY = 0.53;
477
+ var CLIENT_TOOLS_RELEVANCE_RATIO = 0.75;
478
+ function isNewToolFormat(tool) {
479
+ return "schema" in tool && tool.schema !== void 0;
480
+ }
481
+ function isNewResponseFormat(response) {
482
+ return "checksum" in response && "tools" in response && typeof response.checksum === "string";
483
+ }
484
+ function convertServerToolsResponse(response) {
485
+ let toolsMap;
486
+ let checksum;
487
+ if (isNewResponseFormat(response)) {
488
+ toolsMap = response.tools;
489
+ checksum = response.checksum;
490
+ } else {
491
+ toolsMap = response;
492
+ }
493
+ const tools = Object.values(toolsMap).map((tool) => {
494
+ if (isNewToolFormat(tool)) {
495
+ return {
496
+ type: "function",
497
+ name: tool.schema.name,
498
+ description: tool.schema.description,
499
+ parameters: tool.schema.parameters,
500
+ ...tool.embedding && { embedding: tool.embedding }
501
+ };
502
+ }
503
+ return {
504
+ type: "function",
505
+ name: tool.name,
506
+ description: tool.description,
507
+ parameters: tool.parameters
508
+ };
509
+ });
510
+ return { tools, checksum };
511
+ }
512
+ function toCompletionsFormat(tool) {
513
+ return {
514
+ type: "function",
515
+ function: {
516
+ name: tool.name,
517
+ description: tool.description,
518
+ parameters: tool.parameters
519
+ }
520
+ };
521
+ }
522
+ function toResponsesFormat(tool) {
523
+ return {
524
+ type: "function",
525
+ name: tool.name,
526
+ description: tool.description,
527
+ parameters: tool.parameters
528
+ };
529
+ }
530
+ function getCachedServerTools() {
531
+ if (typeof localStorage === "undefined") return null;
532
+ try {
533
+ const cached = localStorage.getItem(SERVER_TOOLS_CACHE_KEY);
534
+ if (!cached) return null;
535
+ const parsed = JSON.parse(cached);
536
+ if (parsed.version !== CACHE_VERSION) {
537
+ removeLocalStorageCache();
538
+ return null;
539
+ }
540
+ return parsed;
541
+ } catch {
542
+ return null;
543
+ }
544
+ }
545
+ function isCacheExpired(cache, expirationMs = DEFAULT_CACHE_EXPIRATION_MS) {
546
+ if (!cache) return true;
547
+ return Date.now() - cache.timestamp > expirationMs;
548
+ }
549
+ function buildCacheEntry(tools, checksum) {
550
+ return {
551
+ tools,
552
+ timestamp: Date.now(),
553
+ version: CACHE_VERSION,
554
+ ...checksum && { checksum }
555
+ };
556
+ }
557
+ function writeLocalStorageCache(entry) {
558
+ if (typeof localStorage === "undefined") return;
559
+ try {
560
+ localStorage.setItem(SERVER_TOOLS_CACHE_KEY, JSON.stringify(entry));
561
+ } catch (error) {
562
+ getLogger().warn("[serverTools] Failed to cache tools:", error);
563
+ }
564
+ }
565
+ function removeLocalStorageCache() {
566
+ if (typeof localStorage === "undefined") return;
567
+ localStorage.removeItem(SERVER_TOOLS_CACHE_KEY);
568
+ }
569
+ var localStorageToolsCache = {
570
+ get: getCachedServerTools,
571
+ set: writeLocalStorageCache,
572
+ clear: removeLocalStorageCache
573
+ };
574
+ function clearServerToolsCache(cache = localStorageToolsCache) {
575
+ return cache.clear?.();
576
+ }
577
+ function getToolsChecksum(cache = localStorageToolsCache) {
578
+ const cached = cache.get();
579
+ return cached instanceof Promise ? cached.then((c) => c?.checksum) : cached?.checksum;
580
+ }
581
+ function shouldRefreshTools(responseChecksum, cache = localStorageToolsCache) {
582
+ if (!responseChecksum) {
583
+ return false;
584
+ }
585
+ const decide = (cachedChecksum2) => !cachedChecksum2 || cachedChecksum2 !== responseChecksum;
586
+ const cachedChecksum = getToolsChecksum(cache);
587
+ return cachedChecksum instanceof Promise ? cachedChecksum.then(decide) : decide(cachedChecksum);
588
+ }
589
+ async function fetchServerToolsFromApi(baseUrl, token) {
590
+ const response = await fetch(`${baseUrl}/api/v1/tools`, {
591
+ method: "GET",
592
+ headers: {
593
+ Authorization: `Bearer ${token}`,
594
+ "Content-Type": "application/json"
595
+ }
596
+ });
597
+ if (!response.ok) {
598
+ throw new Error(`Failed to fetch server tools: ${response.status}`);
599
+ }
600
+ const data = await response.json();
601
+ return convertServerToolsResponse(data);
602
+ }
603
+ async function getServerTools(options) {
604
+ const {
605
+ baseUrl,
606
+ cacheExpirationMs = DEFAULT_CACHE_EXPIRATION_MS,
607
+ forceRefresh = false,
608
+ getToken,
609
+ apiKey,
610
+ cache = localStorageToolsCache
611
+ } = options;
612
+ const persistCache = async (entry) => {
613
+ try {
614
+ await cache.set(entry);
615
+ } catch (error) {
616
+ getLogger().warn("[serverTools] Cache write failed (non-fatal):", error);
617
+ }
618
+ };
619
+ let rawCached = null;
620
+ try {
621
+ rawCached = await cache.get();
622
+ } catch (error) {
623
+ getLogger().warn("[serverTools] Cache read failed; falling through to server fetch:", error);
624
+ }
625
+ const cached = rawCached && rawCached.version === CACHE_VERSION ? rawCached : null;
626
+ const cacheValid = !isCacheExpired(cached, cacheExpirationMs);
627
+ if (cached && cacheValid && !forceRefresh) {
628
+ return cached.tools;
629
+ }
630
+ try {
631
+ const { BASE_URL: BASE_URL2 } = await import("./clientConfig-WITO4T6L.mjs");
632
+ const effectiveBaseUrl = baseUrl ?? BASE_URL2;
633
+ if (apiKey) {
634
+ const response = await fetch(`${effectiveBaseUrl}/api/v1/tools`, {
635
+ method: "GET",
636
+ headers: {
637
+ "X-API-Key": apiKey,
638
+ "Content-Type": "application/json"
639
+ }
640
+ });
641
+ if (!response.ok) {
642
+ throw new Error(`Failed to fetch server tools: ${response.status}`);
643
+ }
644
+ const data = await response.json();
645
+ const { tools: tools2, checksum: checksum2 } = convertServerToolsResponse(data);
646
+ await persistCache(buildCacheEntry(tools2, checksum2));
647
+ return tools2;
648
+ }
649
+ if (!getToken) {
650
+ getLogger().warn("[serverTools] No auth method available for fetching tools");
651
+ return cached?.tools ?? [];
652
+ }
653
+ const token = await getToken();
654
+ if (!token) {
655
+ getLogger().warn("[serverTools] No auth token available for fetching tools");
656
+ return cached?.tools ?? [];
657
+ }
658
+ const { tools, checksum } = await fetchServerToolsFromApi(effectiveBaseUrl, token);
659
+ await persistCache(buildCacheEntry(tools, checksum));
660
+ return tools;
661
+ } catch (error) {
662
+ getLogger().error("[serverTools] Failed to fetch server tools:", error);
663
+ if (cached?.tools) {
664
+ getLogger().warn("[serverTools] Using stale cached tools due to fetch error");
665
+ return cached.tools;
666
+ }
667
+ return [];
668
+ }
669
+ }
670
+ function filterServerTools(serverTools, includeNames) {
671
+ if (includeNames === void 0) {
672
+ return serverTools;
673
+ }
674
+ if (includeNames.length === 0) {
675
+ return [];
676
+ }
677
+ const includeSet = new Set(includeNames);
678
+ return serverTools.filter((tool) => includeSet.has(tool.name));
679
+ }
680
+ function getToolFunction(tool) {
681
+ const fn = tool.function;
682
+ if (fn && typeof fn === "object") {
683
+ return fn;
684
+ }
685
+ return void 0;
686
+ }
687
+ function clientToolToResponsesFormat(tool) {
688
+ const toolConfig = tool;
689
+ const func = getToolFunction(tool);
690
+ if (!func) {
691
+ return tool;
692
+ }
693
+ return {
694
+ type: "function",
695
+ name: func.name,
696
+ description: func.description,
697
+ // Handle both 'parameters' and 'arguments' field names
698
+ parameters: func.parameters ?? func.arguments,
699
+ // Preserve executor functions for client-side execution
700
+ ...toolConfig.executor && { executor: toolConfig.executor },
701
+ ...toolConfig.skipContinuation !== void 0 && {
702
+ skipContinuation: toolConfig.skipContinuation
703
+ },
704
+ ...toolConfig.removeAfterExecution !== void 0 && {
705
+ removeAfterExecution: toolConfig.removeAfterExecution
706
+ },
707
+ ...toolConfig.executorTimeout !== void 0 && {
708
+ executorTimeout: toolConfig.executorTimeout
709
+ },
710
+ ...toolConfig.dependsOn !== void 0 && { dependsOn: toolConfig.dependsOn },
711
+ ...toolConfig.deAnonymizeArgs !== void 0 && {
712
+ deAnonymizeArgs: toolConfig.deAnonymizeArgs
713
+ }
714
+ };
715
+ }
716
+ function clientToolToCompletionsFormat(tool) {
717
+ const toolConfig = tool;
718
+ const func = getToolFunction(tool);
719
+ if (!func) {
720
+ return tool;
721
+ }
722
+ if (func.parameters) {
723
+ return tool;
724
+ }
725
+ const { arguments: args, ...restFunc } = func;
726
+ return {
727
+ type: "function",
728
+ function: {
729
+ ...restFunc,
730
+ parameters: args ?? { type: "object", properties: {} }
731
+ },
732
+ // Preserve executor functions for client-side execution
733
+ ...toolConfig.executor && { executor: toolConfig.executor },
734
+ ...toolConfig.skipContinuation !== void 0 && {
735
+ skipContinuation: toolConfig.skipContinuation
736
+ },
737
+ ...toolConfig.removeAfterExecution !== void 0 && {
738
+ removeAfterExecution: toolConfig.removeAfterExecution
739
+ },
740
+ ...toolConfig.executorTimeout !== void 0 && {
741
+ executorTimeout: toolConfig.executorTimeout
742
+ },
743
+ ...toolConfig.dependsOn !== void 0 && { dependsOn: toolConfig.dependsOn },
744
+ ...toolConfig.deAnonymizeArgs !== void 0 && {
745
+ deAnonymizeArgs: toolConfig.deAnonymizeArgs
746
+ }
747
+ };
748
+ }
749
+ var TOOL_SEARCH_TOOL_TYPE = "tool_search_tool_regex_20251119";
750
+ var TOOL_SEARCH_TOOL_NAME = "tool_search_tool_regex";
751
+ function byNameAscending(a, b) {
752
+ if (a.name < b.name) return -1;
753
+ if (a.name > b.name) return 1;
754
+ return 0;
755
+ }
756
+ function formatServerToolsWithDefer(serverTools, config, apiType) {
757
+ if (serverTools.length === 0) {
758
+ return [];
759
+ }
760
+ const fmt = apiType === "completions" ? toCompletionsFormat : toResponsesFormat;
761
+ const hotSet = new Set(config.hotToolNames);
762
+ const hot = [...hotSet].map((name) => serverTools.find((t) => t.name === name)).filter((t) => t !== void 0);
763
+ const deferred = serverTools.filter((t) => !hotSet.has(t.name)).sort(byNameAscending);
764
+ const searchTool = {
765
+ type: TOOL_SEARCH_TOOL_TYPE,
766
+ name: TOOL_SEARCH_TOOL_NAME
767
+ };
768
+ return [
769
+ searchTool,
770
+ ...hot.map((t) => fmt(t)),
771
+ ...deferred.map((t) => ({ ...fmt(t), defer_loading: true }))
772
+ ];
773
+ }
774
+ function mergeTools(serverTools, clientTools, apiType = "responses", deferConfig) {
775
+ const useDefer = deferConfig?.enabled === true && apiType === "responses";
776
+ const formattedServerTools = useDefer ? formatServerToolsWithDefer(serverTools, deferConfig, apiType) : apiType === "completions" ? serverTools.map(toCompletionsFormat) : serverTools.map(toResponsesFormat);
777
+ if (!clientTools || clientTools.length === 0) {
778
+ return formattedServerTools;
779
+ }
780
+ const formattedClientTools = apiType === "responses" ? clientTools.map(clientToolToResponsesFormat) : clientTools.map(clientToolToCompletionsFormat);
781
+ if (formattedServerTools.length === 0) {
782
+ return formattedClientTools;
783
+ }
784
+ const clientToolNames = new Set(
785
+ clientTools.map((t) => {
786
+ const fn = getToolFunction(t);
787
+ return fn?.name ?? t.name;
788
+ }).filter((name) => typeof name === "string" && !!name)
789
+ );
790
+ const nonConflictingServerTools = formattedServerTools.filter((tool) => {
791
+ let toolName2;
792
+ if ("name" in tool && typeof tool.name === "string") {
793
+ toolName2 = tool.name;
794
+ } else if ("function" in tool && typeof tool.function === "object" && tool.function !== null) {
795
+ toolName2 = tool.function.name;
796
+ }
797
+ return !clientToolNames.has(toolName2 ?? "");
798
+ });
799
+ return [...nonConflictingServerTools, ...formattedClientTools];
800
+ }
801
+ var DEFAULT_TOOL_MATCH_OPTIONS = {
802
+ limit: 5,
803
+ minSimilarity: 0.3,
804
+ filterAmbiguous: false,
805
+ ambiguityThreshold: 0.55,
806
+ minLead: 0.04,
807
+ relevanceRatio: 0
808
+ };
809
+ function findMatchingTools(promptEmbeddings, tools, options) {
810
+ const { limit, minSimilarity, filterAmbiguous, ambiguityThreshold, minLead, relevanceRatio } = {
811
+ ...DEFAULT_TOOL_MATCH_OPTIONS,
812
+ ...options
813
+ };
814
+ if (!promptEmbeddings || promptEmbeddings.length === 0) {
815
+ return [];
816
+ }
817
+ if (!tools || tools.length === 0) {
818
+ return [];
819
+ }
820
+ const embeddings = Array.isArray(promptEmbeddings[0]) ? promptEmbeddings : [promptEmbeddings];
821
+ const results = [];
822
+ for (const tool of tools) {
823
+ if (!tool.embedding || tool.embedding.length === 0) {
824
+ continue;
825
+ }
826
+ try {
827
+ let maxSimilarity = -Infinity;
828
+ for (const embedding of embeddings) {
829
+ const similarity = cosineSimilarity(embedding, tool.embedding);
830
+ if (similarity > maxSimilarity) {
831
+ maxSimilarity = similarity;
832
+ }
833
+ }
834
+ if (maxSimilarity >= minSimilarity) {
835
+ results.push({ tool, similarity: maxSimilarity });
836
+ }
837
+ } catch {
838
+ continue;
839
+ }
840
+ }
841
+ let sorted = results.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
842
+ if (filterAmbiguous && sorted.length > 1) {
843
+ const topScore = sorted[0].similarity;
844
+ const runnerUpScore = sorted[1].similarity;
845
+ if (topScore < ambiguityThreshold && topScore - runnerUpScore < minLead) {
846
+ return [];
847
+ }
848
+ }
849
+ if (relevanceRatio > 0 && sorted.length > 1) {
850
+ const cutoff = sorted[0].similarity * relevanceRatio;
851
+ sorted = sorted.filter((r) => r.similarity >= cutoff);
852
+ }
853
+ return sorted;
854
+ }
855
+ function scoreTools(promptEmbeddings, tools) {
856
+ const scores = /* @__PURE__ */ new Map();
857
+ if (!promptEmbeddings || promptEmbeddings.length === 0) return scores;
858
+ if (!tools || tools.length === 0) return scores;
859
+ const embeddings = Array.isArray(promptEmbeddings[0]) ? promptEmbeddings : [promptEmbeddings];
860
+ for (const tool of tools) {
861
+ if (!tool.embedding || tool.embedding.length === 0) continue;
862
+ let maxSimilarity = -Infinity;
863
+ try {
864
+ for (const embedding of embeddings) {
865
+ const similarity = cosineSimilarity(embedding, tool.embedding);
866
+ if (similarity > maxSimilarity) maxSimilarity = similarity;
867
+ }
868
+ } catch {
869
+ continue;
870
+ }
871
+ if (maxSimilarity > -Infinity) scores.set(tool.name, maxSimilarity);
872
+ }
873
+ return scores;
874
+ }
875
+ var BUILT_IN_TOOL_SETS = [
876
+ {
877
+ name: "app-generation",
878
+ // Appended to the base prompt (not replacing it) whenever this set
879
+ // activates, so the App Builder guidance — including the window.app.complete
880
+ // runtime-AI contract — rides in with the app-gen tools via the same
881
+ // semantic selection that includes them. Collected by toolSetSystemPrompts.
882
+ systemPrompt: APP_BUILDER_PROMPT,
883
+ // Must stay in sync with APP_FILE_TOOL_NAMES in src/tools/appGeneration.ts.
884
+ // If a new app-gen tool ships there but isn't added here, semantic
885
+ // selection will exclude it on every request — the model literally
886
+ // doesn't see it. The quality tools (audit_design / critique_design /
887
+ // verify_app) are non-obvious to a user prompt ("build me a kanban")
888
+ // and rely entirely on set expansion to reach the model.
889
+ members: [
890
+ "create_file",
891
+ "patch_file",
892
+ "delete_file",
893
+ "read_file",
894
+ "list_files",
895
+ "audit_design",
896
+ "critique_design",
897
+ "verify_app"
898
+ ],
899
+ // Anchors stay limited to the primary entry-point tools. Users phrase
900
+ // app intent as "build / make / fix" → semantic match on create_file
901
+ // or patch_file → full set pulled in. The quality tools fire later in
902
+ // the workflow and don't anchor on their own; including them as
903
+ // anchors would risk pulling the set in on unrelated "audit my code"
904
+ // prompts that have nothing to do with app generation.
905
+ anchors: ["create_file", "patch_file"],
906
+ // 0.55: above the global filter floor (0.53) so a barely-relevant match
907
+ // can't pull in the whole toolkit, but no higher — and we tried higher.
908
+ // create_file scores chitchat ("what's up" ~0.56, "...about programming"
909
+ // ~0.57) in the SAME band as legitimate app-EDIT prompts: "Edit the app to
910
+ // change the background…" measured create_file 0.56 on one e2e run. Raising
911
+ // the floor to 0.575 to exclude the chitchat therefore also stripped the
912
+ // file tools from real edit requests — leaving the model unable to touch the
913
+ // app at all. The bands genuinely overlap; there is no clean cut. So we keep
914
+ // 0.55 (max recall) and rely on two safety nets instead: APP_BUILDER_PROMPT
915
+ // is conditional (it no-ops unless the user is actually asking for an app),
916
+ // and very short greetings ("hey") are filtered out earlier by the
917
+ // MIN_CONTENT_LENGTH_FOR_TOOLS length gate before embeddings even run.
918
+ anchorMinSimilarity: 0.55
919
+ },
920
+ {
921
+ name: "slides",
922
+ members: ["plan_deck", "add_slide", "read_slides", "patch_slides"],
923
+ anchors: ["plan_deck", "patch_slides"],
924
+ // Match the client-tool floor (0.53). Short colloquial prompts like
925
+ // "make me a powerpoint about X" score plan_deck around 0.535 — above
926
+ // the global floor but inside any anchor gap. plan_deck and patch_slides
927
+ // are specific enough names that 0.53 won't false-positive in practice.
928
+ anchorMinSimilarity: 0.53
929
+ },
930
+ {
931
+ name: "documents",
932
+ // Conditional rider (no-ops unless the user actually asks for a document),
933
+ // attached so the Document Builder guidance rides in with the document
934
+ // tools via the same semantic selection that includes them.
935
+ systemPrompt: DOCUMENT_BUILDER_PROMPT,
936
+ members: ["create_document", "read_document", "patch_document"],
937
+ // Entry-point tools anchor; read_document rides in via set expansion.
938
+ anchors: ["create_document", "patch_document"],
939
+ // 0.53: match the client-tool floor. "draft me a contract", "write a cover
940
+ // letter", "make a memo" should pull the set in. Like app-gen's create_file,
941
+ // document intent ("write me X") overlaps generic chitchat in this band, so
942
+ // the same two safety nets apply: DOCUMENT_BUILDER_PROMPT is conditional and
943
+ // the MIN_CONTENT_LENGTH_FOR_TOOLS gate drops bare greetings before
944
+ // embeddings run. Tune empirically against the tool-selection parity suite.
945
+ anchorMinSimilarity: 0.53
946
+ },
947
+ {
948
+ name: "github",
949
+ members: ["github_get_authenticated_user", "github_api"],
950
+ anchors: ["github_api"],
951
+ anchorMinSimilarity: 0.55
952
+ },
953
+ // Connector sets (#587). Connector chat tools belong to no set, so per-message
954
+ // semantic filtering silently prunes them on vague prompts ("check my email").
955
+ // Grouping each connector's tools behind a search/action anchor keeps the
956
+ // whole connector reachable once any one of its tools matches. Mirrors github:
957
+ // no systemPrompt rider — per-set prompt guidance is a follow-up.
958
+ {
959
+ name: "gmail",
960
+ members: [
961
+ "gmail_search_messages",
962
+ "gmail_get_message",
963
+ "gmail_create_draft",
964
+ "gmail_send_message"
965
+ ],
966
+ anchors: ["gmail_search_messages", "gmail_send_message"],
967
+ anchorMinSimilarity: 0.53
968
+ },
969
+ {
970
+ name: "google-calendar",
971
+ members: [
972
+ "google_calendar_list_events",
973
+ "google_calendar_create_event",
974
+ "google_calendar_update_event"
975
+ ],
976
+ anchors: ["google_calendar_list_events", "google_calendar_create_event"],
977
+ anchorMinSimilarity: 0.53
978
+ },
979
+ {
980
+ name: "google-drive",
981
+ members: [
982
+ "google_drive_search",
983
+ "google_drive_list_recent",
984
+ "google_drive_get_content",
985
+ "google_drive_create_file",
986
+ "google_drive_update_file"
987
+ ],
988
+ anchors: ["google_drive_search", "google_drive_get_content", "google_drive_create_file"],
989
+ anchorMinSimilarity: 0.53
990
+ },
991
+ {
992
+ name: "notion",
993
+ members: ["notion-search", "notion-fetch", "notion-create-pages", "notion-update-page"],
994
+ anchors: ["notion-search", "notion-create-pages"],
995
+ anchorMinSimilarity: 0.53
996
+ },
997
+ {
998
+ name: "x",
999
+ members: ["x_get_me", "x_get_my_posts"],
1000
+ anchors: ["x_get_me", "x_get_my_posts"],
1001
+ anchorMinSimilarity: 0.53
1002
+ },
1003
+ {
1004
+ name: "slack",
1005
+ members: [
1006
+ "slack_get_me",
1007
+ "slack_list_channels",
1008
+ "slack_search_messages",
1009
+ "slack_list_users",
1010
+ "slack_get_channel_history",
1011
+ "slack_get_thread_replies",
1012
+ "slack_post_message"
1013
+ ],
1014
+ // search is the primary entry point; list_channels also anchors so "what
1015
+ // channels am I in" reaches the set without going through search. post_message
1016
+ // anchors too so post-only prompts surface the write tool past the cutoff.
1017
+ anchors: ["slack_search_messages", "slack_list_channels", "slack_post_message"],
1018
+ anchorMinSimilarity: 0.53
1019
+ },
1020
+ {
1021
+ name: "dropbox",
1022
+ members: ["dropbox_list_folders", "dropbox_get_file_content", "dropbox_search"],
1023
+ anchors: ["dropbox_list_folders", "dropbox_search"],
1024
+ anchorMinSimilarity: 0.53
1025
+ }
1026
+ ];
1027
+ function applyToolSets(matchedNames, availableNames, scores, toolSets = BUILT_IN_TOOL_SETS, independentThreshold = 0.65) {
1028
+ const activatedSets = [];
1029
+ for (const ts of toolSets) {
1030
+ const minSim = ts.anchorMinSimilarity ?? 0.6;
1031
+ const triggered = ts.anchors.some(
1032
+ (anchor) => matchedNames.has(anchor) && (scores.get(anchor) ?? 0) >= minSim
1033
+ );
1034
+ if (triggered) activatedSets.push(ts);
1035
+ }
1036
+ if (activatedSets.length === 0) return matchedNames;
1037
+ const setMembers = /* @__PURE__ */ new Set();
1038
+ for (const ts of activatedSets) {
1039
+ for (const member of ts.members) setMembers.add(member);
1040
+ }
1041
+ const result = /* @__PURE__ */ new Set();
1042
+ for (const member of setMembers) {
1043
+ if (availableNames.has(member)) {
1044
+ result.add(member);
1045
+ }
1046
+ }
1047
+ for (const name of matchedNames) {
1048
+ if (setMembers.has(name)) continue;
1049
+ const score = scores.get(name) ?? 0;
1050
+ if (score >= independentThreshold) {
1051
+ result.add(name);
1052
+ }
1053
+ }
1054
+ return result;
1055
+ }
1056
+ function expandToolSetsAdditive(matchedNames, availableNames, scores, toolSets, activeSetNames) {
1057
+ const result = new Set(matchedNames);
1058
+ for (const ts of toolSets) {
1059
+ let triggered = activeSetNames?.has(ts.name) ?? false;
1060
+ if (!triggered) {
1061
+ const minSim = ts.anchorMinSimilarity ?? 0.6;
1062
+ triggered = ts.anchors.some((a) => (scores.get(a) ?? 0) >= minSim);
1063
+ }
1064
+ if (!triggered) continue;
1065
+ for (const member of ts.members) {
1066
+ if (availableNames.has(member)) {
1067
+ result.add(member);
1068
+ }
1069
+ }
1070
+ }
1071
+ return result;
1072
+ }
1073
+ function activatedToolSetNames(scores, toolSets = BUILT_IN_TOOL_SETS, activeSetNames) {
1074
+ const activated = /* @__PURE__ */ new Set();
1075
+ for (const ts of toolSets) {
1076
+ if (activeSetNames?.has(ts.name)) {
1077
+ activated.add(ts.name);
1078
+ continue;
1079
+ }
1080
+ const minSim = ts.anchorMinSimilarity ?? 0.6;
1081
+ if (ts.anchors.some((a) => (scores.get(a) ?? 0) >= minSim)) {
1082
+ activated.add(ts.name);
1083
+ }
1084
+ }
1085
+ return activated;
1086
+ }
1087
+ function toolSetSystemPrompts(selectedToolNames, toolSets = BUILT_IN_TOOL_SETS, activatedSetNames) {
1088
+ const selected = selectedToolNames instanceof Set ? selectedToolNames : new Set(selectedToolNames);
1089
+ const prompts = [];
1090
+ const seen = /* @__PURE__ */ new Set();
1091
+ for (const ts of toolSets) {
1092
+ const prompt = ts.systemPrompt;
1093
+ if (!prompt || seen.has(prompt)) continue;
1094
+ const active = activatedSetNames ? activatedSetNames.has(ts.name) : ts.anchors.some((anchor) => selected.has(anchor));
1095
+ if (active) {
1096
+ prompts.push(prompt);
1097
+ seen.add(prompt);
1098
+ }
1099
+ }
1100
+ return prompts;
1101
+ }
1102
+ function createServerToolsFilter(options = {}) {
1103
+ const exclude = new Set(options.excludeTools ?? []);
1104
+ const sets = options.toolSets ?? [];
1105
+ const matchOpts = options.matchOptions;
1106
+ return (embeddings, tools) => {
1107
+ const matches = findMatchingTools(embeddings, tools, matchOpts);
1108
+ if (matches.length === 0) return [];
1109
+ const matchedNames = new Set(matches.map((m) => m.tool.name));
1110
+ let finalNames;
1111
+ if (sets.length > 0) {
1112
+ const scores = scoreTools(embeddings, tools);
1113
+ const selectedScores = new Map(
1114
+ [...scores].filter(([name]) => matchedNames.has(name) && !exclude.has(name))
1115
+ );
1116
+ const availableNames = new Set(tools.map((t) => t.name));
1117
+ finalNames = expandToolSetsAdditive(matchedNames, availableNames, selectedScores, sets);
1118
+ } else {
1119
+ finalNames = matchedNames;
1120
+ }
1121
+ for (const name of exclude) finalNames.delete(name);
1122
+ return [...finalNames];
1123
+ };
1124
+ }
1125
+ var DEFAULT_EXCLUDED_SERVER_TOOLS = [
1126
+ "AnumaVisionMCP-anuma_analyze_image",
1127
+ "OpenMeteoMCP-weather_forecast",
1128
+ // Same native-capability argument as the vision tool: modern models reason
1129
+ // step-by-step natively (and reasoning-mode models do it structurally), so
1130
+ // a server-side sequential-thinking tool is a redundant hop. Its broad
1131
+ // description also embeds near virtually every prompt — observed live
1132
+ // riding into news-search, scheduling, and chitchat requests.
1133
+ "AnumaSequentialThinkingMCP-sequentialthinking"
1134
+ ];
1135
+ var DEFAULT_SERVER_TOOLS_MATCH_OPTIONS = {
1136
+ limit: 5,
1137
+ minSimilarity: 0.5
1138
+ };
1139
+ var SERVER_TOOL_DEPENDENCY_SETS = [
1140
+ {
1141
+ // search finds links; reading them is always the next step.
1142
+ name: "jina-research",
1143
+ members: [
1144
+ "AnumaJinaMCP-search_web",
1145
+ "AnumaJinaMCP-read_url",
1146
+ "AnumaJinaMCP-parallel_read_url",
1147
+ "AnumaJinaMCP-parallel_search_web"
1148
+ ],
1149
+ anchors: ["AnumaJinaMCP-search_web"],
1150
+ // Activation floor = the selection floor (0.5), not the 0.6 ToolSet
1151
+ // default. A dependency edge has different semantics from a persona-style
1152
+ // tool set: if the entry tool is plausibly offered at all, the chain must
1153
+ // ride along — "Search the web for recent news…" scores search_web in the
1154
+ // 0.5–0.6 band, and shipping search without read would dead-end the model.
1155
+ anchorMinSimilarity: 0.5
1156
+ },
1157
+ {
1158
+ // Every OpenMeteo data tool requires latitude/longitude (verified in the
1159
+ // catalog schemas), but users speak in place names — geocoding is the
1160
+ // mandatory first hop. Members deliberately contain ONLY the dependency:
1161
+ // the anchor that fired is already in the semantic matches, and pulling
1162
+ // sibling data tools in would re-create vendor-suite over-inclusion.
1163
+ // Excluded tools (e.g. weather_forecast under the default filter) can't
1164
+ // anchor this set — createServerToolsFilter drops them before expansion —
1165
+ // but the non-weather data tools still deliver geocoding when they match.
1166
+ name: "openmeteo-geocode",
1167
+ members: ["OpenMeteoMCP-geocoding"],
1168
+ anchors: [
1169
+ "OpenMeteoMCP-weather_forecast",
1170
+ "OpenMeteoMCP-air_quality",
1171
+ "OpenMeteoMCP-weather_archive",
1172
+ "OpenMeteoMCP-marine_weather",
1173
+ "OpenMeteoMCP-flood_forecast",
1174
+ "OpenMeteoMCP-climate_projection",
1175
+ "OpenMeteoMCP-elevation"
1176
+ ],
1177
+ anchorMinSimilarity: 0.5
1178
+ }
1179
+ ];
1180
+ var defaultServerToolsFilter = createServerToolsFilter({
1181
+ excludeTools: DEFAULT_EXCLUDED_SERVER_TOOLS,
1182
+ matchOptions: DEFAULT_SERVER_TOOLS_MATCH_OPTIONS,
1183
+ toolSets: SERVER_TOOL_DEPENDENCY_SETS
1184
+ });
1185
+ async function selectServerToolsForPrompt(options) {
1186
+ const {
1187
+ prompt,
1188
+ serverToolsFilter,
1189
+ getToken,
1190
+ baseUrl,
1191
+ embeddingModel,
1192
+ cacheExpirationMs,
1193
+ cache,
1194
+ deferLoading
1195
+ } = options;
1196
+ if (serverToolsFilter === void 0) return [];
1197
+ if (Array.isArray(serverToolsFilter) && serverToolsFilter.length === 0) return [];
1198
+ let allServerTools;
1199
+ try {
1200
+ allServerTools = await getServerTools({ baseUrl, cacheExpirationMs, getToken, cache });
1201
+ } catch {
1202
+ return [];
1203
+ }
1204
+ if (allServerTools.length === 0) return [];
1205
+ if (deferLoading?.enabled) return allServerTools;
1206
+ if (typeof serverToolsFilter === "function") {
1207
+ if (prompt.length < MIN_CONTENT_LENGTH_FOR_TOOLS) return [];
1208
+ let promptEmbedding;
1209
+ try {
1210
+ promptEmbedding = await generateEmbedding(prompt, {
1211
+ getToken,
1212
+ baseUrl,
1213
+ model: embeddingModel
1214
+ });
1215
+ } catch {
1216
+ return [];
1217
+ }
1218
+ const names = serverToolsFilter(promptEmbedding, allServerTools);
1219
+ return filterServerTools(allServerTools, names);
1220
+ }
1221
+ return filterServerTools(allServerTools, serverToolsFilter);
1222
+ }
1223
+ async function selectServerSideTools(options) {
1224
+ const {
1225
+ prompt,
1226
+ getToken,
1227
+ apiKey,
1228
+ baseUrl,
1229
+ cacheExpirationMs,
1230
+ forceRefresh,
1231
+ embeddingModel,
1232
+ limit,
1233
+ minSimilarity,
1234
+ apiType = "responses"
1235
+ } = options;
1236
+ if (!getToken && !apiKey) {
1237
+ throw new Error("Either getToken or apiKey must be provided");
1238
+ }
1239
+ if (!prompt || prompt.trim().length < MIN_CONTENT_LENGTH_FOR_TOOLS) {
1240
+ return [];
1241
+ }
1242
+ const tools = await getServerTools({
1243
+ getToken,
1244
+ apiKey,
1245
+ baseUrl,
1246
+ cacheExpirationMs,
1247
+ forceRefresh
1248
+ });
1249
+ if (tools.length === 0) {
1250
+ return [];
1251
+ }
1252
+ const embeddingOptions = {
1253
+ getToken,
1254
+ apiKey,
1255
+ baseUrl,
1256
+ model: embeddingModel
1257
+ };
1258
+ let promptEmbeddings;
1259
+ if (shouldChunkMessage(prompt, DEFAULT_CHUNK_SIZE)) {
1260
+ const chunks = chunkText(prompt);
1261
+ promptEmbeddings = await generateEmbeddings(
1262
+ chunks.map((c) => c.text),
1263
+ embeddingOptions
1264
+ );
1265
+ } else {
1266
+ promptEmbeddings = await generateEmbedding(prompt, embeddingOptions);
1267
+ }
1268
+ const matchOptions = { filterAmbiguous: true, relevanceRatio: 0.85 };
1269
+ if (limit !== void 0) matchOptions.limit = limit;
1270
+ if (minSimilarity !== void 0) matchOptions.minSimilarity = minSimilarity;
1271
+ const matches = findMatchingTools(promptEmbeddings, tools, matchOptions);
1272
+ if (matches.length === 0) {
1273
+ return [];
1274
+ }
1275
+ const matchedTools = matches.map((m) => m.tool);
1276
+ if (apiType === "completions") {
1277
+ return matchedTools.map((t) => toCompletionsFormat(t));
1278
+ }
1279
+ return matchedTools.map(toResponsesFormat);
1280
+ }
1281
+
1282
+ // src/lib/tools/clientToolSelection.ts
1283
+ function getToolName(t) {
1284
+ const fn = t.function;
1285
+ return fn?.name || t.name || "";
1286
+ }
1287
+ function getToolDescription(t) {
1288
+ const fn = t.function;
1289
+ return fn?.description || t.description || getToolName(t);
1290
+ }
1291
+ function computeToolGuidance(selectedServerTools, selectedClientTools, extraToolSets, activatedSetNames) {
1292
+ const names = [
1293
+ ...selectedServerTools.map((t) => t.name),
1294
+ ...(selectedClientTools ?? []).map(getToolName)
1295
+ ].filter(Boolean);
1296
+ const toolSets = extraToolSets.length > 0 ? [...BUILT_IN_TOOL_SETS, ...extraToolSets] : BUILT_IN_TOOL_SETS;
1297
+ const prompts = toolSetSystemPrompts(names, toolSets, activatedSetNames);
1298
+ return prompts.length > 0 ? prompts.join("\n\n") : void 0;
1299
+ }
1300
+ async function autoFilterClientTools(clientTools, promptEmbeddings, cache, embeddingOptions, extraToolSets = [], activeToolSets = [], noEmbeddingsReason = "short-prompt") {
1301
+ const isMemoryTool = (t) => {
1302
+ const name = getToolName(t);
1303
+ return name.startsWith("memory_vault_") || name === RECALL_TOOL_NAME;
1304
+ };
1305
+ const alwaysInclude = clientTools.filter(isMemoryTool);
1306
+ const filterCandidates = clientTools.filter((t) => !isMemoryTool(t));
1307
+ if (filterCandidates.length === 0) {
1308
+ return { tools: clientTools, activatedSetNames: /* @__PURE__ */ new Set() };
1309
+ }
1310
+ if (!promptEmbeddings && noEmbeddingsReason === "error") {
1311
+ return { tools: clientTools, activatedSetNames: /* @__PURE__ */ new Set() };
1312
+ }
1313
+ if (!promptEmbeddings) {
1314
+ if (activeToolSets.length === 0) {
1315
+ return { tools: [], activatedSetNames: /* @__PURE__ */ new Set() };
1316
+ }
1317
+ const allSets = extraToolSets.length > 0 ? [...BUILT_IN_TOOL_SETS, ...extraToolSets] : BUILT_IN_TOOL_SETS;
1318
+ const activeSets = allSets.filter((s) => activeToolSets.includes(s.name));
1319
+ const stickyMembers = new Set(activeSets.flatMap((s) => s.members));
1320
+ return {
1321
+ tools: [
1322
+ ...alwaysInclude,
1323
+ ...filterCandidates.filter((t) => stickyMembers.has(getToolName(t)))
1324
+ ],
1325
+ activatedSetNames: new Set(activeSets.map((s) => s.name))
1326
+ };
1327
+ }
1328
+ const toolsNeedingEmbeddings = [];
1329
+ for (const t of filterCandidates) {
1330
+ const name = getToolName(t);
1331
+ if (name && !cache.has(name)) {
1332
+ toolsNeedingEmbeddings.push({ name, description: getToolDescription(t) });
1333
+ }
1334
+ }
1335
+ if (toolsNeedingEmbeddings.length > 0) {
1336
+ try {
1337
+ const descriptions = toolsNeedingEmbeddings.map((t) => t.description);
1338
+ const embeddings = await generateEmbeddings(descriptions, embeddingOptions);
1339
+ for (let i = 0; i < toolsNeedingEmbeddings.length; i++) {
1340
+ cache.set(toolsNeedingEmbeddings[i].name, embeddings[i]);
1341
+ }
1342
+ } catch {
1343
+ return { tools: clientTools, activatedSetNames: /* @__PURE__ */ new Set() };
1344
+ }
1345
+ }
1346
+ const pseudoServerTools = [];
1347
+ for (const t of filterCandidates) {
1348
+ const name = getToolName(t);
1349
+ const embedding = cache.get(name);
1350
+ if (!embedding) continue;
1351
+ const fn = t.function;
1352
+ const params = fn?.parameters || fn?.arguments || {
1353
+ type: "object",
1354
+ properties: {},
1355
+ required: []
1356
+ };
1357
+ pseudoServerTools.push({
1358
+ type: "function",
1359
+ name,
1360
+ description: getToolDescription(t),
1361
+ parameters: params,
1362
+ embedding
1363
+ });
1364
+ }
1365
+ const matches = findMatchingTools(promptEmbeddings, pseudoServerTools, {
1366
+ limit: MAX_CLIENT_TOOLS_AFTER_FILTER,
1367
+ minSimilarity: CLIENT_TOOLS_MIN_SIMILARITY,
1368
+ filterAmbiguous: true,
1369
+ // See CLIENT_TOOLS_RELEVANCE_RATIO: 0.75 admits the second intent of a
1370
+ // multi-intent prompt (which lands ~75-80% of the dominant match), while
1371
+ // still trimming the loose tail. The earlier 0.9 made a second intent
1372
+ // structurally unselectable; the anchorMinSimilarity gates (0.53-0.55)
1373
+ // remain the guard against borderline anchors activating a whole set.
1374
+ relevanceRatio: CLIENT_TOOLS_RELEVANCE_RATIO
1375
+ });
1376
+ const matchedNames = new Set(matches.map((m) => m.tool.name));
1377
+ const scores = scoreTools(promptEmbeddings, pseudoServerTools);
1378
+ const availableNames = new Set(filterCandidates.map(getToolName));
1379
+ const toolSets = extraToolSets.length > 0 ? [...BUILT_IN_TOOL_SETS, ...extraToolSets] : BUILT_IN_TOOL_SETS;
1380
+ const activeSetNames = activeToolSets.length > 0 ? new Set(activeToolSets) : void 0;
1381
+ const finalNames = expandToolSetsAdditive(
1382
+ matchedNames,
1383
+ availableNames,
1384
+ scores,
1385
+ toolSets,
1386
+ activeSetNames
1387
+ );
1388
+ const activatedSetNames = activatedToolSetNames(scores, toolSets, activeSetNames);
1389
+ if (finalNames.size === 0) {
1390
+ return { tools: alwaysInclude, activatedSetNames };
1391
+ }
1392
+ const filtered = filterCandidates.filter((t) => {
1393
+ const name = getToolName(t);
1394
+ return name && finalNames.has(name);
1395
+ });
1396
+ return { tools: [...alwaysInclude, ...filtered], activatedSetNames };
1397
+ }
1398
+
1399
+ // src/lib/tools/toolCatalog.ts
1400
+ var TOOL_CATALOG = {
1401
+ // Gmail (src/tools/gmail.ts)
1402
+ gmail_search_messages: { label: "Search emails", provider: "gmail", connector: "Gmail" },
1403
+ gmail_get_message: { label: "Read an email", provider: "gmail", connector: "Gmail" },
1404
+ gmail_create_draft: { label: "Draft a reply", provider: "gmail", connector: "Gmail" },
1405
+ gmail_send_message: { label: "Send an email", provider: "gmail", connector: "Gmail" },
1406
+ // Google Calendar (src/tools/googleCalendar.ts)
1407
+ google_calendar_list_events: {
1408
+ label: "Search calendar events",
1409
+ provider: "gcalendar",
1410
+ connector: "Calendar"
1411
+ },
1412
+ google_calendar_create_event: {
1413
+ label: "Create calendar event",
1414
+ provider: "gcalendar",
1415
+ connector: "Calendar"
1416
+ },
1417
+ google_calendar_update_event: {
1418
+ label: "Update calendar event",
1419
+ provider: "gcalendar",
1420
+ connector: "Calendar"
1421
+ },
1422
+ // Google Drive (src/tools/googleDrive.ts)
1423
+ google_drive_search: { label: "Search files", provider: "gdrive", connector: "Drive" },
1424
+ google_drive_list_recent: { label: "List recent files", provider: "gdrive", connector: "Drive" },
1425
+ google_drive_get_content: { label: "Read file content", provider: "gdrive", connector: "Drive" },
1426
+ google_drive_create_file: {
1427
+ label: "Create a file in Drive",
1428
+ provider: "gdrive",
1429
+ connector: "Drive"
1430
+ },
1431
+ google_drive_update_file: {
1432
+ label: "Update a Drive file",
1433
+ provider: "gdrive",
1434
+ connector: "Drive"
1435
+ },
1436
+ // Notion (src/tools/notion.ts)
1437
+ "notion-search": { label: "Search", provider: "notion", connector: "Notion" },
1438
+ "notion-fetch": { label: "Read page", provider: "notion", connector: "Notion" },
1439
+ "notion-create-pages": { label: "Create page", provider: "notion", connector: "Notion" },
1440
+ "notion-update-page": { label: "Update page", provider: "notion", connector: "Notion" },
1441
+ "notion-move-pages": { label: "Move pages", provider: "notion", connector: "Notion" },
1442
+ "notion-duplicate-page": { label: "Duplicate page", provider: "notion", connector: "Notion" },
1443
+ "notion-create-database": { label: "Create database", provider: "notion", connector: "Notion" },
1444
+ "notion-update-data-source": {
1445
+ label: "Update data source",
1446
+ provider: "notion",
1447
+ connector: "Notion"
1448
+ },
1449
+ "notion-create-comment": { label: "Add a comment", provider: "notion", connector: "Notion" },
1450
+ "notion-get-comments": { label: "Read comments", provider: "notion", connector: "Notion" },
1451
+ "notion-get-users": {
1452
+ label: "List workspace members",
1453
+ provider: "notion",
1454
+ connector: "Notion"
1455
+ },
1456
+ "notion-get-teams": { label: "List teamspaces", provider: "notion", connector: "Notion" },
1457
+ // GitHub (src/tools/github.ts)
1458
+ github_api: { label: "Use GitHub", provider: "github", connector: "GitHub" },
1459
+ github_get_authenticated_user: {
1460
+ label: "Read GitHub profile",
1461
+ provider: "github",
1462
+ connector: "GitHub"
1463
+ },
1464
+ // X / Twitter (src/tools/x.ts)
1465
+ x_get_me: { label: "Get my X profile", provider: "x", connector: "X" },
1466
+ x_get_my_posts: { label: "Read my recent posts", provider: "x", connector: "X" },
1467
+ // Dropbox (src/tools/dropbox.ts)
1468
+ dropbox_list_folders: { label: "List files", provider: "dropbox", connector: "Dropbox" },
1469
+ dropbox_get_file_content: {
1470
+ label: "Read file content",
1471
+ provider: "dropbox",
1472
+ connector: "Dropbox"
1473
+ },
1474
+ dropbox_search: { label: "Search files", provider: "dropbox", connector: "Dropbox" },
1475
+ // Slack (src/tools/slack.ts)
1476
+ slack_get_me: { label: "Get my Slack profile", provider: "slack", connector: "Slack" },
1477
+ slack_list_channels: { label: "List channels", provider: "slack", connector: "Slack" },
1478
+ slack_list_dms: { label: "List direct messages", provider: "slack", connector: "Slack" },
1479
+ slack_search_messages: { label: "Search messages", provider: "slack", connector: "Slack" },
1480
+ slack_list_users: { label: "List members", provider: "slack", connector: "Slack" },
1481
+ slack_get_channel_history: {
1482
+ label: "Read channel history",
1483
+ provider: "slack",
1484
+ connector: "Slack"
1485
+ },
1486
+ slack_get_thread_replies: {
1487
+ label: "Read thread replies",
1488
+ provider: "slack",
1489
+ connector: "Slack"
1490
+ },
1491
+ slack_post_message: { label: "Post a message", provider: "slack", connector: "Slack" }
1492
+ };
1493
+ function buildDeniedToolsRider(deniedToolNames) {
1494
+ const byConnector = /* @__PURE__ */ new Map();
1495
+ for (const name of deniedToolNames) {
1496
+ const entry = TOOL_CATALOG[name];
1497
+ if (!entry) continue;
1498
+ const labels = byConnector.get(entry.connector) ?? [];
1499
+ labels.push(entry.label);
1500
+ byConnector.set(entry.connector, labels);
1501
+ }
1502
+ if (byConnector.size === 0) return "";
1503
+ const groups = [...byConnector.entries()].sort(([a], [b]) => a.localeCompare(b)).map(
1504
+ ([connector, labels]) => `- ${connector}: ${[...labels].sort((a, b) => a.localeCompare(b)).join(", ")}`
1505
+ ).join("\n");
1506
+ return [
1507
+ "The user has turned off the following connector capabilities:",
1508
+ groups,
1509
+ "If the user asks you to use one of these, do not attempt it. Tell them the specific capability is turned off and that they can re-enable that exact tool in their Connected Apps settings."
1510
+ ].join("\n");
1511
+ }
1512
+ function toolName(tool) {
1513
+ const record = tool;
1514
+ const func = record.function;
1515
+ if (typeof func?.name === "string") return func.name;
1516
+ if (typeof record.name === "string") return record.name;
1517
+ return void 0;
1518
+ }
1519
+ function catalogConnectorsByProvider() {
1520
+ const byProvider = /* @__PURE__ */ new Map();
1521
+ for (const entry of Object.values(TOOL_CATALOG)) {
1522
+ byProvider.set(entry.provider, entry.connector);
1523
+ }
1524
+ return byProvider;
1525
+ }
1526
+ function buildConnectorGuidance(input) {
1527
+ const denied = new Set(input.deniedToolNames);
1528
+ const connected = new Set(input.connectedProviders);
1529
+ const tools = input.tools.filter((tool) => {
1530
+ const name = toolName(tool);
1531
+ return name === void 0 || !denied.has(name);
1532
+ });
1533
+ const deniedByConnector = /* @__PURE__ */ new Map();
1534
+ for (const name of denied) {
1535
+ const entry = TOOL_CATALOG[name];
1536
+ if (!entry || !connected.has(entry.provider)) continue;
1537
+ const labels = deniedByConnector.get(entry.connector) ?? [];
1538
+ labels.push(entry.label);
1539
+ deniedByConnector.set(entry.connector, labels);
1540
+ }
1541
+ const unconnected = /* @__PURE__ */ new Set();
1542
+ for (const [provider, connector] of catalogConnectorsByProvider()) {
1543
+ if (!connected.has(provider)) unconnected.add(connector);
1544
+ }
1545
+ const sections = [];
1546
+ if (deniedByConnector.size > 0) {
1547
+ const groups = [...deniedByConnector.entries()].sort(([a], [b]) => a.localeCompare(b)).map(
1548
+ ([connector, labels]) => `On ${connector}, these are turned off: ${[...labels].sort((a, b) => a.localeCompare(b)).join(", ")}.`
1549
+ ).join("\n");
1550
+ sections.push(
1551
+ `${groups}
1552
+ If the user asks to use one of these, tell them it's turned off and they can re-enable it in Connected Apps.`
1553
+ );
1554
+ }
1555
+ if (unconnected.size > 0) {
1556
+ const list = [...unconnected].sort((a, b) => a.localeCompare(b)).join(", ");
1557
+ sections.push(
1558
+ `Not connected: ${list}. If the user asks to use them, tell them to connect them in Connected Apps.`
1559
+ );
1560
+ }
1561
+ if (sections.length > 0 && connected.size > 0) {
1562
+ sections.push(
1563
+ "Never tell the user you lack access to a connected app; if you can't find a tool for a connected app's request, say that specific capability isn't available yet rather than that the app isn't connected."
1564
+ );
1565
+ }
1566
+ return { tools, rider: sections.join("\n\n") };
1567
+ }
1568
+
1569
+ // src/lib/tools/selection/activeToolSets.ts
1570
+ function phaseMatchesMember(phaseId, member) {
1571
+ if (!phaseId) return false;
1572
+ return phaseId === member || phaseId.endsWith(`-${member}`) || phaseId.includes(`-${member}-`);
1573
+ }
1574
+ function eventMatchesMember(event, member) {
1575
+ if (event.kind === "tool-result") {
1576
+ return !!event.toolName && event.toolName === member;
1577
+ }
1578
+ return !!event.phaseId && phaseMatchesMember(event.phaseId, member);
1579
+ }
1580
+ function deriveActiveToolSets(events, sets = BUILT_IN_TOOL_SETS) {
1581
+ const active = /* @__PURE__ */ new Set();
1582
+ for (const event of events) {
1583
+ for (const ts of sets) {
1584
+ if (active.has(ts.name)) continue;
1585
+ if (ts.members.some((member) => eventMatchesMember(event, member))) {
1586
+ active.add(ts.name);
1587
+ }
1588
+ }
1589
+ if (active.size === sets.length) break;
1590
+ }
1591
+ return [...active];
1592
+ }
1593
+ function mergeActiveToolSets(current, derived) {
1594
+ if (derived.length === 0) return current;
1595
+ const merged = Array.from(/* @__PURE__ */ new Set([...current, ...derived]));
1596
+ return merged.length === current.length ? current : merged;
1597
+ }
1598
+
1599
+ // src/lib/tools/selection/assembleClientTools.ts
1600
+ function assembleClientTools(spec, adapters) {
1601
+ const out = [];
1602
+ for (const key of spec.clientFactories) {
1603
+ const build = adapters.builders[key];
1604
+ if (!build) continue;
1605
+ try {
1606
+ const tools = build();
1607
+ if (tools?.length) out.push(...tools);
1608
+ } catch (err) {
1609
+ adapters.logger?.error(`[assembleClientTools] factory "${key}" threw; skipping`, err);
1610
+ }
1611
+ }
1612
+ return out;
1613
+ }
1614
+ function filterAssembledClientTools(tools, mode, opts) {
1615
+ if (typeof mode === "function") return mode(tools);
1616
+ if (mode === "include-all" || mode === "auto") return tools;
1617
+ const names = opts?.slideEditorToolNames;
1618
+ if (!names) return [];
1619
+ const getName = opts?.getName ?? ((t) => getToolName(t));
1620
+ return tools.filter((t) => names.has(getName(t)));
1621
+ }
1622
+
1623
+ // src/lib/tools/selection/intents.ts
1624
+ var FULL_GENERATIVE_CLIENT_FACTORIES = [
1625
+ "memory",
1626
+ "connectors",
1627
+ "slides",
1628
+ "app",
1629
+ "document",
1630
+ "ui",
1631
+ "saved",
1632
+ "background",
1633
+ "schedule"
1634
+ ];
1635
+ var CREATION_INTENT_CLIENT_FACTORIES = {
1636
+ plain: FULL_GENERATIVE_CLIENT_FACTORIES,
1637
+ image: FULL_GENERATIVE_CLIENT_FACTORIES,
1638
+ audio: FULL_GENERATIVE_CLIENT_FACTORIES,
1639
+ music: FULL_GENERATIVE_CLIENT_FACTORIES,
1640
+ sfx: FULL_GENERATIVE_CLIENT_FACTORIES,
1641
+ video: FULL_GENERATIVE_CLIENT_FACTORIES,
1642
+ "remove-image-bg": FULL_GENERATIVE_CLIENT_FACTORIES,
1643
+ document: FULL_GENERATIVE_CLIENT_FACTORIES,
1644
+ "web-search": FULL_GENERATIVE_CLIENT_FACTORIES,
1645
+ "deep-research": FULL_GENERATIVE_CLIENT_FACTORIES,
1646
+ // Builder modes: restrict to their own toolkit; a `saved` toolkit rides
1647
+ // along on app builds (parity with web's app branch).
1648
+ app: ["app", "saved"],
1649
+ slides: ["slides"]
1650
+ };
1651
+ var CREATION_INTENT_CLIENT_FILTER = {
1652
+ plain: "auto",
1653
+ image: "auto",
1654
+ audio: "auto",
1655
+ music: "auto",
1656
+ sfx: "auto",
1657
+ video: "auto",
1658
+ "remove-image-bg": "auto",
1659
+ document: "auto",
1660
+ "web-search": "auto",
1661
+ "deep-research": "auto",
1662
+ app: "include-all",
1663
+ slides: "include-all"
1664
+ };
1665
+ var CREATION_INTENT_TOOL_SET = {
1666
+ app: "app-generation",
1667
+ slides: "slides",
1668
+ document: "documents"
1669
+ };
1670
+
1671
+ // src/lib/tools/selection/sendPolicy.ts
1672
+ var MEMORY_SAVE_TOOL_NAME = "memory_vault_save";
1673
+ function resolveToolChoice(serverTools, explicit) {
1674
+ if (explicit) return explicit;
1675
+ if (Array.isArray(serverTools)) return serverTools.length > 0 ? "required" : "auto";
1676
+ return "auto";
1677
+ }
1678
+ function getMaxToolRounds(entry, fallback) {
1679
+ return entry?.maxToolRounds ?? fallback;
1680
+ }
1681
+ function getThinkingMode(entry, fallback) {
1682
+ return entry?.thinkingMode ?? fallback;
1683
+ }
1684
+ function stripCoercedMemoryWrite(clientTools, toolChoice) {
1685
+ if (toolChoice !== "required") return clientTools;
1686
+ const filtered = clientTools.filter((t) => getToolName(t) !== MEMORY_SAVE_TOOL_NAME);
1687
+ return filtered.length === clientTools.length ? clientTools : filtered;
1688
+ }
1689
+
1690
+ // src/lib/tools/selection/serverToolsPolicy.ts
1691
+ function getCatalogEntry(descriptor, catalog) {
1692
+ if (descriptor.lane === "council" || descriptor.lane === "aggregation") {
1693
+ return catalog[descriptor.lane];
1694
+ }
1695
+ return catalog[descriptor.creation];
1696
+ }
1697
+ function resolveServerTools(descriptor, catalog, fallback = []) {
1698
+ const entry = getCatalogEntry(descriptor, catalog);
1699
+ if (!entry) return fallback;
1700
+ if (entry.resolveServerTools) {
1701
+ return entry.resolveServerTools({ attachments: descriptor.attachments });
1702
+ }
1703
+ return entry.serverTools ?? fallback;
1704
+ }
1705
+ function isCoercingIntent(creation, catalog) {
1706
+ const entry = catalog[creation];
1707
+ if (!entry) return false;
1708
+ const slot = entry.serverTools;
1709
+ return Array.isArray(slot) && slot.length > 0;
1710
+ }
1711
+
1712
+ // src/lib/tools/selection/plan.ts
1713
+ var BUILDER_INTENTS = /* @__PURE__ */ new Set(["app", "slides"]);
1714
+ function resolvePlan(descriptor, ctx) {
1715
+ const { catalog } = ctx;
1716
+ const entry = getCatalogEntry(descriptor, catalog);
1717
+ if (descriptor.lane === "council" || descriptor.lane === "aggregation") {
1718
+ const serverTools2 = resolveServerTools(descriptor, catalog);
1719
+ return {
1720
+ clientFactories: [],
1721
+ clientToolsFilter: "auto",
1722
+ serverTools: serverTools2,
1723
+ forcedServerTools: entry?.forcedServerTools,
1724
+ activeToolSets: descriptor.activeToolSets ? [...descriptor.activeToolSets] : [],
1725
+ toolChoice: resolveToolChoice(serverTools2, entry?.toolChoice),
1726
+ maxToolRounds: getMaxToolRounds(entry, ctx.defaultMaxToolRounds),
1727
+ thinkingMode: getThinkingMode(entry, ctx.defaultThinkingMode),
1728
+ systemPromptRiders: entry?.systemPrompt ? [entry.systemPrompt] : [],
1729
+ postFilters: []
1730
+ };
1731
+ }
1732
+ const { creation } = descriptor;
1733
+ const isBuilder = BUILDER_INTENTS.has(creation);
1734
+ const editorSlideOverlay = descriptor.editorPinned === "slides" && !isBuilder;
1735
+ const slideDeckIntent = descriptor.slideDeckIntent === true && descriptor.imageEditIntent !== true;
1736
+ let serverTools = resolveServerTools(descriptor, catalog);
1737
+ const clientFactories = [...CREATION_INTENT_CLIENT_FACTORIES[creation]];
1738
+ let clientToolsFilter = entry?.clientToolsFilter ?? CREATION_INTENT_CLIENT_FILTER[creation];
1739
+ const slideEntry = catalog.slides;
1740
+ if (editorSlideOverlay && slideEntry) {
1741
+ clientToolsFilter = "slide-editor";
1742
+ serverTools = resolveServerTools({ ...descriptor, creation: "slides" }, catalog);
1743
+ }
1744
+ const effectiveEntry = editorSlideOverlay && slideEntry ? slideEntry : entry;
1745
+ const activeToolSets = new Set(descriptor.activeToolSets ?? []);
1746
+ const builderSet = CREATION_INTENT_TOOL_SET[creation];
1747
+ if (builderSet) activeToolSets.add(builderSet);
1748
+ if (slideDeckIntent) activeToolSets.add("slides");
1749
+ if (editorSlideOverlay) activeToolSets.add("slides");
1750
+ const forceAuto = isBuilder || slideDeckIntent || editorSlideOverlay;
1751
+ const toolChoice = forceAuto ? "auto" : resolveToolChoice(serverTools, effectiveEntry?.toolChoice);
1752
+ const systemPromptRiders = [];
1753
+ if (effectiveEntry?.systemPrompt) systemPromptRiders.push(effectiveEntry.systemPrompt);
1754
+ if (slideDeckIntent && slideEntry?.systemPrompt && creation !== "slides" && !systemPromptRiders.includes(slideEntry.systemPrompt)) {
1755
+ systemPromptRiders.push(slideEntry.systemPrompt);
1756
+ }
1757
+ const postFilters = [];
1758
+ if (toolChoice === "required") postFilters.push("strip-memory-save-when-coerced");
1759
+ if (ctx.memoryIntent?.retrieval) postFilters.push("strip-memory-save-on-retrieval-intent");
1760
+ if (ctx.memoryIntent?.save) postFilters.push("strip-server-tools-on-save-intent");
1761
+ return {
1762
+ clientFactories,
1763
+ clientToolsFilter,
1764
+ serverTools,
1765
+ forcedServerTools: effectiveEntry?.forcedServerTools,
1766
+ activeToolSets: [...activeToolSets],
1767
+ toolChoice,
1768
+ maxToolRounds: getMaxToolRounds(effectiveEntry, ctx.defaultMaxToolRounds),
1769
+ thinkingMode: getThinkingMode(effectiveEntry, ctx.defaultThinkingMode),
1770
+ systemPromptRiders,
1771
+ postFilters
1772
+ };
1773
+ }
1774
+
1775
+ // src/lib/tools/selection/council.ts
1776
+ function resolveCouncilPlan(descriptor, ctx) {
1777
+ const lane = descriptor.lane === "aggregation" ? "aggregation" : "council";
1778
+ return resolvePlan({ ...descriptor, lane }, ctx);
1779
+ }
1780
+ function resolveCouncilServerTools(descriptor, ctx) {
1781
+ return resolveCouncilPlan(descriptor, ctx).serverTools;
1782
+ }
1783
+ function composeCouncilClientTools(input) {
1784
+ const memory = input.canPersistMemory ? input.memoryTools : input.memoryTools.filter((t) => {
1785
+ const fn = t.function;
1786
+ const name = fn?.name ?? t.name ?? "";
1787
+ return name !== MEMORY_SAVE_TOOL_NAME;
1788
+ });
1789
+ return [...memory, ...input.sharedTools ?? []];
1790
+ }
1791
+ async function selectCouncilClientTools(input, options) {
1792
+ const candidates = composeCouncilClientTools(input);
1793
+ return autoFilterClientTools(
1794
+ candidates,
1795
+ options.promptEmbeddings,
1796
+ options.cache,
1797
+ options.embeddingOptions,
1798
+ options.extraToolSets ?? [],
1799
+ options.activeToolSets ?? [],
1800
+ options.noEmbeddingsReason ?? "short-prompt"
1801
+ );
1802
+ }
1803
+ export {
1804
+ BUILT_IN_TOOL_SETS,
1805
+ CLIENT_TOOLS_MIN_SIMILARITY,
1806
+ CLIENT_TOOLS_RELEVANCE_RATIO,
1807
+ CREATION_INTENT_CLIENT_FACTORIES,
1808
+ CREATION_INTENT_CLIENT_FILTER,
1809
+ CREATION_INTENT_TOOL_SET,
1810
+ DEFAULT_CACHE_EXPIRATION_MS,
1811
+ DEFAULT_EXCLUDED_SERVER_TOOLS,
1812
+ DEFAULT_SERVER_TOOLS_MATCH_OPTIONS,
1813
+ FULL_GENERATIVE_CLIENT_FACTORIES,
1814
+ MAX_CLIENT_TOOLS_AFTER_FILTER,
1815
+ MEMORY_SAVE_TOOL_NAME,
1816
+ MIN_CONTENT_LENGTH_FOR_TOOLS,
1817
+ SERVER_TOOL_DEPENDENCY_SETS,
1818
+ TOOL_CATALOG,
1819
+ activatedToolSetNames,
1820
+ applyToolSets,
1821
+ assembleClientTools,
1822
+ autoFilterClientTools,
1823
+ buildConnectorGuidance,
1824
+ buildDeniedToolsRider,
1825
+ clearServerToolsCache,
1826
+ composeCouncilClientTools,
1827
+ computeToolGuidance,
1828
+ createServerToolsFilter,
1829
+ defaultServerToolsFilter,
1830
+ deriveActiveToolSets,
1831
+ expandToolSetsAdditive,
1832
+ filterAssembledClientTools,
1833
+ filterServerTools,
1834
+ findMatchingTools,
1835
+ getCachedServerTools,
1836
+ getCatalogEntry,
1837
+ getMaxToolRounds,
1838
+ getServerTools,
1839
+ getThinkingMode,
1840
+ getToolDescription,
1841
+ getToolName,
1842
+ getToolsChecksum,
1843
+ isCoercingIntent,
1844
+ localStorageToolsCache,
1845
+ mergeActiveToolSets,
1846
+ mergeTools,
1847
+ resolveCouncilPlan,
1848
+ resolveCouncilServerTools,
1849
+ resolvePlan,
1850
+ resolveServerTools,
1851
+ resolveToolChoice,
1852
+ scoreTools,
1853
+ selectCouncilClientTools,
1854
+ selectServerSideTools,
1855
+ selectServerToolsForPrompt,
1856
+ shouldRefreshTools,
1857
+ stripCoercedMemoryWrite,
1858
+ toolSetSystemPrompts
1859
+ };