@triplef/agent 0.1.16 → 0.1.18

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.
@@ -1,9 +1,4 @@
1
1
  import { z } from 'zod';
2
- import '@triplef/helpers/object-io';
3
- import 'ai';
4
- import '@mozilla/readability';
5
- import 'linkedom';
6
- import 'turndown';
7
2
  import { limitText } from '@triplef/helpers/limit-text';
8
3
 
9
4
  // src/prompts/shared/multimodal-policy.prompt.ts
@@ -162,841 +157,9 @@ function buildLocalizationRule(language) {
162
157
  - Do not translate internal markers, tool arguments, or your reasoning.`;
163
158
  }
164
159
 
165
- // src/schemas/constants/embeddable-video-providers.constant.ts
166
- var EMBEDDABLE_VIDEO_PROVIDER_LABELS = "YouTube, Vimeo, Dailymotion, Loom, Wistia";
167
- var EMBEDDABLE_VIDEO_PROVIDER_CLAUSE = `Only return URLs from supported embeddable providers: ${EMBEDDABLE_VIDEO_PROVIDER_LABELS}, or direct video files. Reject Instagram, Facebook, TikTok, Twitch, X/Twitter, and other unreliable platforms.`;
168
-
169
- // src/schemas/constants/media-counts.constant.ts
170
- var DEFAULT_MEDIA_COUNT = 6;
171
- var MORE_MEDIA_COUNT = 12;
172
-
173
- // src/schemas/constants/url-trust.constants.ts
174
- var BLOCKED_URL_HOSTS = /* @__PURE__ */ new Set([
175
- // Google thumbnail / static asset proxies
176
- "encrypted-tbn0.gstatic.com",
177
- "encrypted-tbn1.gstatic.com",
178
- "encrypted-tbn2.gstatic.com",
179
- "encrypted-tbn3.gstatic.com",
180
- "t0.gstatic.com",
181
- "t1.gstatic.com",
182
- "t2.gstatic.com",
183
- "t3.gstatic.com",
184
- "t4.gstatic.com",
185
- "t5.gstatic.com",
186
- "t6.gstatic.com",
187
- "t7.gstatic.com",
188
- "t8.gstatic.com",
189
- "t9.gstatic.com",
190
- "t10.gstatic.com",
191
- "news.gstatic.com",
192
- "books.gstatic.com",
193
- "maps.gstatic.com",
194
- "lh1.googleusercontent.com",
195
- "lh2.googleusercontent.com",
196
- "lh3.googleusercontent.com",
197
- "lh4.googleusercontent.com",
198
- "lh5.googleusercontent.com",
199
- "lh6.googleusercontent.com"
200
- ]);
201
- var NON_PAGE_EXTENSIONS = /\.(js|css|json|xml|svg|png|jpg|jpeg|gif|webp|ico|mp4|webm|ogg|mov|mkv|avi|flv|m3u8|mpd|pdf|zip|tar|gz|rar|exe|dmg|pkg|deb|rpm|woff|woff2|ttf|otf|eot)(\?.*)?$/i;
202
- var EncyclopediaClassifySchema = z.object({
203
- /**
204
- * One broad lowercase PLURAL family label for the document (e.g. `games`,
205
- * `work`, `health`) — the constellation's cluster tier. Never a specific
206
- * entity, product, company, or title.
207
- */
208
- category: z.string(),
209
- /**
210
- * The narrow topic the document is about (e.g. `wuthering waves`,
211
- * `q3 budget`) — the constellation's cluster tier. A short, specific,
212
- * reusable label, not a sentence and not a URL.
213
- */
214
- topic: z.string()
215
- });
216
-
217
- // src/schemas/helpers/tools/tool-registry.constants.ts
218
- var VARIANT_NAMES = ["grayscale", "denoised", "sharpened", "clahe"];
219
- var TOOL_DESCRIPTIONS = {
220
- brightDataWebSearch: "Search the web using Bright Data SERP API (Google). Returns organic results with titles, snippets, and links. Supports an optional recency window (day/week/month/year) for fresh results.",
221
- brightDataImageSearch: "Search for images using Bright Data SERP API (Google Images). Returns image URLs, source pages, and dimensions. Enforces a minimum of 1280\xD7720 (720p).",
222
- brightDataNewsSearch: "Search latest news using Bright Data SERP API. Returns headlines, sources, dates, and snippets. Supports an optional recency window (day/week/month/year).",
223
- brightDataPlacesSearch: "Search places and businesses using Bright Data SERP API (Google Maps). Returns addresses, phone numbers, ratings, review counts, and coordinates. Query with a business name or business type plus location.",
224
- brightDataShoppingSearch: "Search for products using Bright Data SERP API (Google Shopping). Returns prices, sellers, images, and ratings. Query with the bare product name and model number.",
225
- brightDataVideoSearch: `Search for videos using Bright Data SERP API. Returns titles, links, channel names, duration, and publish dates. Supports an optional recency window (day/week/month/year) for fresh results. ${EMBEDDABLE_VIDEO_PROVIDER_CLAUSE}`,
226
- brightDataWebpageScrape: "Fetch and render a full webpage using Bright Data Web Unlocker API. Returns clean Markdown text. Use for pages behind anti-bot protection that plain fetch cannot reach.",
227
- serperWebSearch: "Search the web using Serper.dev (Google). Returns organic results with titles, snippets, and links. Supports an optional recency window (day/week/month/year) for fresh results.",
228
- serperImageSearch: "Search for images using Serper.dev (Google Images). Prefers 2560\xD71440 (1440p) images and enforces a minimum of 1280\xD7720 (720p) via the Google Images tbs size filter and a client-side dimension filter. Supports an optional recency window (day/week/month/year).",
229
- serperNewsSearch: "Search latest news using Serper.dev. Returns headlines, sources, dates, and snippets. Supports an optional recency window (day/week/month/year).",
230
- serperPlacesSearch: "Search places and businesses using Serper.dev (Google Maps). Returns addresses, phone numbers, ratings, review counts, and coordinates. Query with a business name or business type plus location.",
231
- serperShoppingSearch: "Search for products using Serper.dev (Google Shopping). Returns prices, sellers, delivery info, images, and per-offer ratings. Query with the bare product name and model number.",
232
- serperBusinessReviewsSearch: "Fetch Google Maps reviews for a specific business or place using Serper.dev. Returns reviewer snippets with author names, star ratings, and dates. Use for seller/business reputation, not editorial product reviews.",
233
- serperVideoSearch: `Search for videos using Serper.dev. Returns titles, links, channel names, duration, and publish dates. Supports an optional recency window (day/week/month/year) for fresh results. ${EMBEDDABLE_VIDEO_PROVIDER_CLAUSE}`,
234
- serperWebpageScrape: "Fetch and render a full webpage using Serper.dev scrape API. Returns clean rendered text with its title.",
235
- youtubeVideoSearch: "Search YouTube using the official YouTube Data API. Returns titles, links, channel names, durations, view counts, upload dates, and direct thumbnails. All results are embeddable YouTube videos.",
236
- eodhdSearch: 'Resolve a company, ETF, or index name to an EODHD ticker code (e.g. "Nvidia" \u2192 NVDA.US). Use before fetching quotes, history, technicals, or news for a named entity.',
237
- eodhdQuote: "Fetch the current (delayed) quote for one or more EODHD tickers \u2014 last price, change, change %, open/high/low, volume, previous close.",
238
- eodhdHistory: "Fetch end-of-day OHLCV price history for an EODHD ticker. Returns a compact summary plus the full time series as chartData for the client chart. Use for the time-value chart and buy/sell pressure (volume).",
239
- eodhdTechnical: "Fetch a technical indicator series (RSI, MACD, ADX, SMA, EMA, BBANDS, ATR, \u2026) for an EODHD ticker. Use to gauge buy/sell pressure and momentum.",
240
- eodhdIntraday: "Fetch intraday OHLCV bars for an EODHD ticker and return a per-day, per-price-band volume profile as chartData for the client heatmap. Use to render a volume heatmap across fixed price bands.",
241
- eodhdNews: "Fetch recent financial news for an EODHD ticker \u2014 headlines, links, sources, publish dates. Use to ground a stock-market answer in what is happening around a company.",
242
- eodhdFundamentals: "Fetch company fundamentals for an EODHD ticker \u2014 general info, valuation, and key financial highlights (sector, market cap, P/E, revenue, margins).",
243
- webFetch: "Fetch the full content of a specific URL (e.g. a search result page) as clean Markdown. Prefer fetching the most relevant search results to ground the answer in full source text rather than relying on snippets alone.",
244
- browser_navigate: "Control a real browser: navigate to a URL. Use for interactive browsing \u2014 JS-heavy pages, content behind clicks, tabs, scrolling, or forms \u2014 that static search/fetch cannot reach. Follow up with browser_snapshot to read the page before acting on it.",
245
- browser_navigate_back: "Go back to the previous page in the browser history.",
246
- browser_snapshot: "Read the current browser page as an accessibility snapshot (structured text with element refs). The primary way to see page content and obtain the refs that browser_click/browser_type need.",
247
- browser_click: "Click an element in the browser, referenced by a ref from browser_snapshot.",
248
- browser_type: "Type text into an editable browser element, referenced by a ref from browser_snapshot, optionally submitting with Enter.",
249
- browser_fill_form: "Fill multiple form fields in the browser in one call.",
250
- browser_select_option: "Select an option in a dropdown in the browser.",
251
- browser_press_key: "Press a keyboard key in the browser (Enter, Tab, arrows).",
252
- browser_wait_for: "Wait for text to appear or disappear, or for a time period, in the browser.",
253
- browser_take_screenshot: "Take a screenshot of the current browser page or a specific element.",
254
- browser_tabs: "List, create, close, or switch browser tabs.",
255
- browser_console_messages: "Get console messages from the browser. Use to diagnose JavaScript errors on a page (e.g. when testing a web app).",
256
- browser_network_requests: "List network requests the browser made since page load. Use to diagnose failed or slow requests (e.g. when testing a web app).",
257
- browser_verify_element_visible: "Assert that an element is visible on the current browser page.",
258
- browser_verify_text_visible: "Assert that text is visible on the current browser page.",
259
- requestGrayscale: "Request a grayscale version of the images. Use when color noise or color information is irrelevant, for example when reading text or analyzing shapes.",
260
- requestDenoised: "Request a denoised (blurred) version of the images. Use when the original has noise, grain, or artifacts that hide details.",
261
- requestSharpened: "Request a sharpened version of the images. Use when edges or fine details are blurry.",
262
- requestClahe: "Request a CLAHE (contrast-enhanced) version of the images. Use when details are hidden in shadows or highlights.",
263
- "memory-partition-remember": `Store into the user's fact partition (memory-partition): notable facts about subjects they care about (favorites, interests, projects, followed stocks, people, past topics), preferences and durable details they state, and anything they explicitly ask you to remember. Storing gathered knowledge and noticed preferences is expected \u2014 do not wait for an explicit "remember" instruction.`,
264
- "memory-partition-recall": "Retrieve from the user's fact partition (memory-partition) \u2014 things they told you in past conversations or asked you to remember. These are trusted user statements, not public facts; attribute them to the user and prefer them over web results for anything personal. Check this tool whenever a request touches a subject this user has cared about before.",
265
- "memory-partition-delete": "Delete one exact fact record from the user's fact partition (memory-partition), quoted verbatim from a memory-partition-recall result. Never delete on a guess: recall first, delete the verbatim statement. Needs memory-partition-recall alongside it.",
266
- "memory-cognition-remember": "Store one derived insight into your cognition space (memory-cognition) \u2014 your own understanding of the user (inferred traits, standing interests, working nuances, connections between facts). Use for what you LEARN about the user, not what they stated; stated facts belong in memory-partition-remember.",
267
- "memory-cognition-forget": "Wipe your entire cognition space (memory-cognition) of the user \u2014 the structured profile AND every derived insight \u2014 only when the user asks you to forget your learned understanding of them or to start over. Fact records in the partition are untouched."
268
- };
269
- var BROWSER_TOOL_NAMES = [
270
- "browser_navigate",
271
- "browser_navigate_back",
272
- "browser_snapshot",
273
- "browser_click",
274
- "browser_type",
275
- "browser_fill_form",
276
- "browser_select_option",
277
- "browser_press_key",
278
- "browser_wait_for",
279
- "browser_take_screenshot",
280
- "browser_tabs",
281
- "browser_console_messages",
282
- "browser_network_requests",
283
- "browser_verify_element_visible",
284
- "browser_verify_text_visible"
285
- ];
286
- var TOOL_NAMES = [
287
- "webFetch",
288
- "brightDataWebSearch",
289
- "brightDataImageSearch",
290
- "brightDataNewsSearch",
291
- "brightDataPlacesSearch",
292
- "brightDataShoppingSearch",
293
- "brightDataVideoSearch",
294
- "brightDataWebpageScrape",
295
- "serperWebSearch",
296
- "serperImageSearch",
297
- "serperNewsSearch",
298
- "serperPlacesSearch",
299
- "serperShoppingSearch",
300
- "serperBusinessReviewsSearch",
301
- "serperVideoSearch",
302
- "serperWebpageScrape",
303
- "youtubeVideoSearch",
304
- "eodhdSearch",
305
- "eodhdQuote",
306
- "eodhdHistory",
307
- "eodhdTechnical",
308
- "eodhdIntraday",
309
- "eodhdNews",
310
- "eodhdFundamentals",
311
- "requestGrayscale",
312
- "requestDenoised",
313
- "requestSharpened",
314
- "requestClahe",
315
- "memory-partition-remember",
316
- "memory-partition-recall",
317
- "memory-partition-delete",
318
- "memory-cognition-remember",
319
- "memory-cognition-forget",
320
- ...BROWSER_TOOL_NAMES
321
- ];
322
-
323
- // src/schemas/helpers/url-trust/is-private-or-localhost.helper.ts
324
- function isPrivateOrLocalhost(hostname) {
325
- const lower = hostname.toLowerCase();
326
- if (lower === "localhost" || lower.endsWith(".localhost")) return true;
327
- if (lower === "127.0.0.1" || lower === "0.0.0.0") return true;
328
- if (lower.startsWith("10.")) return true;
329
- if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(lower)) return true;
330
- if (lower.startsWith("192.168.")) return true;
331
- if (lower.startsWith("169.254.")) return true;
332
- return false;
333
- }
334
-
335
- // src/schemas/helpers/url-trust/is-trusted-url.helper.ts
336
- function isYouTubeNonVideoPath(hostname, pathname) {
337
- const lowerHost = hostname.toLowerCase();
338
- if (lowerHost !== "youtube.com" && lowerHost !== "www.youtube.com" && lowerHost !== "m.youtube.com") {
339
- return false;
340
- }
341
- const lowerPath = pathname.toLowerCase();
342
- if (lowerPath.startsWith("/s/") || lowerPath.startsWith("/static/") || lowerPath.startsWith("/js/") || lowerPath.startsWith("/css/") || lowerPath.startsWith("/fonts/") || lowerPath.startsWith("/yts/") || lowerPath.startsWith("/iframe_api") || lowerPath.startsWith("/sw.js") || lowerPath.startsWith("/embed_config") || lowerPath.startsWith("/get_video_info") || lowerPath.startsWith("/api/")) {
343
- return true;
344
- }
345
- return false;
346
- }
347
- function isTrustedUrl(url, options = {}) {
348
- if (!url) return false;
349
- let parsed;
350
- try {
351
- parsed = new URL(url);
352
- } catch {
353
- return false;
354
- }
355
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
356
- return false;
357
- }
358
- const hostname = parsed.hostname.toLowerCase();
359
- const pathname = parsed.pathname;
360
- if (BLOCKED_URL_HOSTS.has(hostname)) {
361
- return false;
362
- }
363
- if (isYouTubeNonVideoPath(hostname, pathname)) {
364
- return false;
365
- }
366
- if (NON_PAGE_EXTENSIONS.test(pathname)) {
367
- return false;
368
- }
369
- if (!options.allowPrivate && isPrivateOrLocalhost(hostname)) {
370
- return false;
371
- }
372
- return true;
373
- }
374
- function safeUrl(message = "must be a safe URL") {
375
- const text = typeof message === "string" ? message : message.message;
376
- return z.string().url({ message: "must be a valid URL" }).refine((value) => isTrustedUrl(value, { allowPrivate: false }), {
377
- message: text
378
- });
379
- }
380
- var LOCAL_STORAGE_URL_PATTERN = /^\/api\/v1\/storage\/[A-Za-z0-9_-]+\/[A-Za-z0-9_-]+\/[A-Za-z0-9]+$/;
381
- function isAbsoluteHttpUrl(value) {
382
- try {
383
- const protocol = new URL(value).protocol;
384
- return protocol === "http:" || protocol === "https:";
385
- } catch {
386
- return false;
387
- }
388
- }
389
- function safeVideoUrl(message = "must be a safe video URL") {
390
- const text = typeof message === "string" ? message : message.message;
391
- return z.string().refine(isAbsoluteHttpUrl, { message: text });
392
- }
393
- function safeVideoUrlOrEmpty(message = "must be a safe video URL") {
394
- return safeVideoUrl(message).optional().or(z.literal(""));
395
- }
396
- function safeMediaUrl(message = "must be a safe media URL") {
397
- const text = typeof message === "string" ? message : message.message;
398
- return z.string().refine((value) => LOCAL_STORAGE_URL_PATTERN.test(value) || isAbsoluteHttpUrl(value), { message: text });
399
- }
400
- function safeMediaUrlOrEmpty(message = "must be a safe media URL") {
401
- return safeMediaUrl(message).optional().or(z.literal(""));
402
- }
403
- var INDENT = " ";
404
- function formatZodShape(schema, options = {}) {
405
- const jsonSchema = z.toJSONSchema(schema);
406
- return renderNode(jsonSchema, 0, options);
407
- }
408
- function renderNode(node, depth, options) {
409
- if (node.type === "object" && node.properties) return renderObject(node, depth, options);
410
- if (node.type === "object" && node.additionalProperties) {
411
- return `{ [key]: ${renderNode(node.additionalProperties, depth, options)} }`;
412
- }
413
- if (node.type === "array" && node.items) return `[${renderNode(node.items, depth, options)}]`;
414
- const anyOfBranches = node.anyOf?.filter((branch) => branch.type !== "null");
415
- if (anyOfBranches?.length) return anyOfBranches.map((branch) => renderNode(branch, depth, options)).join(" | ");
416
- if (node.enum) return node.enum.map((value) => JSON.stringify(value)).join(" | ");
417
- if (node.type === "integer" || node.type === "number") return "number";
418
- if (node.type === "boolean") return "boolean";
419
- if (node.type === "string") return "string";
420
- if (node.type === "null") return "null";
421
- return "unknown";
422
- }
423
- function renderObject(node, depth, options) {
424
- const properties = node.properties ?? {};
425
- const required = new Set(node.required ?? []);
426
- const entries = Object.entries(properties);
427
- if (entries.length === 0) return "{}";
428
- const pad = INDENT.repeat(depth);
429
- const innerPad = INDENT.repeat(depth + 1);
430
- const lines = entries.map(([key, value]) => {
431
- const optional = required.has(key) ? "" : "?";
432
- const hint = options.overrides?.[key] ?? renderNode(value, depth + 1, options);
433
- return `${innerPad}${JSON.stringify(key)}${optional}: ${hint}`;
434
- });
435
- return `{
436
- ${lines.join(",\n")}
437
- ${pad}}`;
438
- }
439
- var DEFAULT_VARIANT_ID = "default";
440
- var TEMPLATES = [
441
- "article",
442
- "news",
443
- "describe",
444
- "compare",
445
- "ocr",
446
- "summary",
447
- "evaluation",
448
- "product",
449
- "shoplist",
450
- "imagelist",
451
- "videolist",
452
- "stockmarketitem",
453
- "stockmarketlist",
454
- "merge",
455
- "text"
456
- ];
457
- var ImagePlanSchema = z.object({
458
- resize: z.boolean().default(true).describe(
459
- "Whether to resize the original images before sending them to the response model. Default true when images are present."
460
- ),
461
- variants: z.array(z.enum(VARIANT_NAMES)).default([]).describe(
462
- "Optional preprocessing variants to generate for the images (grayscale, denoised, sharpened, clahe). Only use when they would materially improve analysis."
463
- )
464
- });
465
- var IntentSchema = z.object({
466
- template: z.enum(TEMPLATES).describe(
467
- 'Template name: "article" (in-depth research/report \u2014 extensive long-form composed from available snippets), "news" (current events \u2014 a compact brief composed from available snippets), "describe" (single/multi image description), "compare" (compare uploaded images only \u2014 information comparisons use "evaluation"), "ocr" (extract text from images), "summary" (recap prior conversation or topic without new images), "evaluation" (critique/assess something from the conversation), "product" (product details with shop offers and prices), "shoplist" (compact product/shop list for follow-up shopping questions about an already-introduced product), "imagelist" (a pure collection of images about a topic, no article), "videolist" (a pure list/playlist of videos about a topic, no article), "stockmarketitem" (a single stock/ETF/index with price chart, technicals, news, and a recommendation), "stockmarketlist" (a selection of stocks/indices with a market overview), "text" (free chat).'
468
- ),
469
- prompt: z.string().default(DEFAULT_VARIANT_ID).describe(
470
- 'Selected prompt variant for the template. Use "default" unless the user explicitly asks for a specific style.'
471
- ),
472
- tools: z.array(z.enum(TOOL_NAMES)).describe("List of tool names the model decided to invoke."),
473
- getDate: z.boolean().default(true).describe(
474
- 'Whether search queries should be anchored on the current date so results bias toward recent information. Default true. Set to false ONLY for timeless general-knowledge, historical, or conceptual requests where recency does not matter (e.g. "how does photosynthesis work", "history of the Roman Empire", "explain quantum entanglement", creative writing).'
475
- ),
476
- imageCount: z.preprocess((val) => val === null ? 0 : val, z.number().int().min(0).max(50).default(0)).describe(
477
- `Number of images to retrieve when an imageSearch tool is selected. Only set when the user explicitly requests a specific number; otherwise omit or set to 0 and the system will default to ${DEFAULT_MEDIA_COUNT}.`
478
- ),
479
- videoCount: z.preprocess((val) => val === null ? 0 : val, z.number().int().min(0).max(50).default(0)).describe(
480
- `Number of videos to retrieve when a videoSearch tool is selected. Only set when the user explicitly requests a specific number; otherwise omit or set to 0 and the system will default to ${DEFAULT_MEDIA_COUNT}.`
481
- ),
482
- reasoning: z.string().describe("Short explanation of why this template, prompt, and these tools were chosen."),
483
- contextSummary: z.string().default("").describe(
484
- "Query-focused extraction of the prior conversation context that the latest user message references or depends on: established topics/entities, key facts from prior answers the follow-up builds on, user constraints, and \u2014 for imagelist/videolist follow-ups \u2014 the previously shown image/video URLs verbatim. Empty if there is no relevant prior context."
485
- ),
486
- needsClarification: z.boolean().default(false).describe("When true, the request is too ambiguous to classify \u2014 set this instead of picking template/tools."),
487
- clarificationQuestion: z.string().nullable().optional().describe(
488
- "Concise, human-friendly question to ask the user when needsClarification is true. Ask what the user might have meant and offer the most likely interpretations as options. Do not hardcode wording; adapt tone and language to the user."
489
- ),
490
- language: z.string().nullable().optional().describe(
491
- "Two-letter ISO language code of the latest user message (e.g. 'en', 'de', 'ja', 'es'). Detect from the user's text."
492
- ),
493
- plan: z.object({
494
- images: ImagePlanSchema.optional().describe("Image processing plan. Only present when images are attached.")
495
- }).default({}).describe("Execution plan for the response step.")
496
- });
497
- var MemoryClusterSummarySchema = z.object({
498
- title: z.string().describe('A short noun-phrase label for the cluster (2\u20136 words), e.g. "gaming preferences" or "work projects".'),
499
- summary: z.string().describe(
500
- "One or two sentences summarizing what this cluster of memories is about \u2014 the shared theme, the key facts, and how they relate."
501
- )
502
- });
503
- var ConsolidationVerdictSchema = z.object({
504
- verdict: z.enum(["keep", "redundant", "merge"]).describe(
505
- "keep = the new fact adds information not covered by the candidates; redundant = fully covered already (same claim, no new detail, same polarity); merge = it refines/corrects/completes a candidate and mergedText carries the fuller statement."
506
- ),
507
- mergedText: z.string().optional().describe("Required with verdict=merge: one fuller self-contained statement (full restatement, never a diff).")
508
- });
509
- var MemoryEnrichmentSchema = z.object({
510
- /**
511
- * 2–6 stable, reusable lowercase topic labels for the record — the existing
512
- * tags kept plus any missing labels that would help topic-filtered recall.
513
- */
514
- tags: z.array(z.string())
515
- });
516
- var ExtractionSchema = z.object({
517
- /**
518
- * Durable, self-contained facts worth remembering in a later, unrelated
519
- * conversation (preferences, decisions, contact details, project facts).
520
- * Empty when nothing in the text is worth remembering.
521
- */
522
- facts: z.array(z.string()),
523
- /**
524
- * 2–6 stable, reusable lowercase topic labels describing the text; the open
525
- * vocabulary that powers topic-filtered recall. Tags are NARROW and
526
- * specific — entity names, product names, game titles (e.g. `amd`,
527
- * `stellar blade`, `stellar blade blood rain`).
528
- */
529
- tags: z.array(z.string()),
530
- /**
531
- * One broad lowercase PLURAL family label for the whole turn-side (e.g.
532
- * `stocks`, `pets`, `games`) — groups the narrow tags into one topic family
533
- * for the constellation's community tier and the relink job's per-category
534
- * passes. Never a specific entity, product, company, or game title: `amd`
535
- * belongs under `stocks`; `stellar blade` belongs under `games`. Optional:
536
- * a turn with nothing durable may omit it.
537
- */
538
- category: z.string().optional()
539
- });
540
- var nullishText = z.string().nullish();
541
- var nullishTopics = z.array(z.string()).nullish();
542
- var memoryCognitionProfileSchema = z.object({
543
- /** The user's name or preferred handle, when known. */
544
- name: nullishText,
545
- /** Primary conversation language (BCP-47-ish, e.g. "en", "de"). */
546
- language: nullishText,
547
- /** Coarse location the user works from (IANA name or city), when known. */
548
- timezone: nullishText,
549
- /** Demonstrated skills and domains (e.g. ["TypeScript", "NestJS"]). */
550
- expertise: nullishTopics,
551
- /** Active goals and aspirations — the most durable cognition there is. */
552
- goals: nullishTopics,
553
- /** How the user wants to be answered. */
554
- communication: z.object({
555
- style: nullishText,
556
- detailLevel: nullishText,
557
- formality: nullishText
558
- }).nullish(),
559
- /** Tooling / environment / format preferences (free-form key-value). */
560
- preferences: z.record(z.string(), z.string()).nullish(),
561
- likes: nullishTopics,
562
- dislikes: nullishTopics,
563
- /** Topics the user keeps returning to. */
564
- interests: nullishTopics,
565
- /**
566
- * Durable CONVICTIONS the AI synthesized about the user's world — the
567
- * routing-map topics of its conviction records (depth lives in the
568
- * conviction points at paths `convictions.<slug>`). Convictions are the
569
- * AI's own derived conclusions, never statements the user made.
570
- */
571
- convictions: nullishTopics,
572
- /** The AI's own identity as the user has shaped it (name, role, voice). */
573
- persona: z.object({
574
- /** The name the user gave the AI. */
575
- name: nullishText,
576
- /** The role the user assigned the AI (e.g. "coding assistant"). */
577
- role: nullishText,
578
- /** Short character description the user gave the AI. */
579
- personality: nullishText,
580
- /** How the AI introduces itself, when the user set one. */
581
- greeting: nullishText,
582
- /** How the AI should speak. */
583
- voice: z.object({
584
- tone: nullishText,
585
- formality: nullishText
586
- }).nullish()
587
- }).nullish(),
588
- /**
589
- * Learned corrections — behavioral rules the user taught the AI after it
590
- * got something wrong. Keyed by a short slug (a stable handle for
591
- * update/remove); the value is the imperative directive ("always …" /
592
- * "never …"), optionally with a brief why. Deep-merged like preferences.
593
- */
594
- corrections: z.record(z.string(), z.string()).nullish()
595
- });
596
- var memoryProfileInsightSchema = z.object({
597
- text: z.string(),
598
- path: z.string().optional()
599
- });
600
- var memoryProfileResponseSchema = z.preprocess(
601
- (value) => {
602
- if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
603
- const record = value;
604
- if (record.insights == null) record.insights = [];
605
- else if (Array.isArray(record.insights)) {
606
- record.insights = record.insights.map((item) => typeof item === "string" ? { text: item } : item);
607
- }
608
- return record;
609
- },
610
- z.object({
611
- profile: memoryCognitionProfileSchema.nullish(),
612
- insights: z.array(memoryProfileInsightSchema).max(8),
613
- /**
614
- * One sentence recording what THIS turn was about — the short-term
615
- * conversation memory (episode), recalled later by recency-blended probe.
616
- * Omitted (or null) when the turn had no substance.
617
- */
618
- episode: z.string().nullish()
619
- })
620
- );
621
- var cardSchema = z.object(
622
- {
623
- url: safeUrl({ message: "cards entries must have a valid url" }),
624
- title: z.string().optional(),
625
- description: z.string().optional(),
626
- linkLabel: z.string().optional()
627
- },
628
- { message: "cards entries must be objects with url" }
629
- );
630
- var referenceLineSchema = z.object(
631
- {
632
- value: z.number(),
633
- label: z.string().optional(),
634
- /** A theme token name, e.g. "accent-primary" or "status-error". */
635
- color: z.string().optional()
636
- },
637
- { message: "referenceLines entries must be objects with a numeric value" }
638
- );
639
- var markerSchema = z.object(
640
- {
641
- time: z.string().min(1, { message: "markers entries must have a time" }),
642
- position: z.enum(["aboveBar", "belowBar"]),
643
- /** A theme token name, e.g. "harmony-3" or "status-error". */
644
- color: z.string().optional(),
645
- shape: z.enum(["circle", "arrowUp", "arrowDown", "square"]),
646
- text: z.string().optional()
647
- },
648
- {
649
- message: "markers entries must be objects with time, position, and shape"
650
- }
651
- );
652
- var discardedReferenceSchema = z.discriminatedUnion("type", [
653
- z.object({
654
- type: z.literal("image"),
655
- imageUrl: z.string().min(1),
656
- title: z.string(),
657
- reason: z.string().min(1, { message: "reason must not be empty" })
658
- }),
659
- z.object({
660
- type: z.literal("link"),
661
- url: z.string().min(1),
662
- title: z.string(),
663
- reason: z.string().min(1, { message: "reason must not be empty" })
664
- })
665
- ]);
666
- var internationalCoverageSchema = z.array(
667
- z.object(
668
- {
669
- title: z.string().optional(),
670
- url: safeUrl({
671
- message: "internationalCoverage entries must have a valid url"
672
- }),
673
- sourceName: z.string().optional(),
674
- language: z.string().optional(),
675
- summary: z.string().optional()
676
- },
677
- { message: "internationalCoverage entries must be objects" }
678
- )
679
- );
680
- var referenceGalleryItemSchema = z.object(
681
- {
682
- imageUrl: z.string(),
683
- imageAlt: z.string().optional(),
684
- title: z.string().optional(),
685
- caption: z.string().optional()
686
- },
687
- { message: "galleryItems entries must be objects with imageUrl" }
688
- );
689
- var sourceSchema = z.object(
690
- {
691
- url: safeUrl({ message: "sources entries must have a valid url" }),
692
- title: z.string().optional(),
693
- sourceName: z.string().optional(),
694
- date: z.string().optional(),
695
- snippet: z.string().optional()
696
- },
697
- { message: "sources entries must be objects with url" }
698
- );
699
- function createTextItemSchema(fieldName) {
700
- return z.object(
701
- {
702
- text: z.string().min(1, {
703
- message: `${fieldName} entries must have a non-empty text field`
704
- })
705
- },
706
- { message: `${fieldName} entries must be objects with text` }
707
- );
708
- }
709
- var videoGalleryItemSchema = z.object(
710
- {
711
- videoUrl: safeVideoUrl({
712
- message: "videoGalleryItems.videoUrl must be a valid URL"
713
- }),
714
- title: z.string().min(1, {
715
- message: "videoGalleryItems.title must not be empty"
716
- }),
717
- caption: z.string().min(1, {
718
- message: "videoGalleryItems.caption must not be empty"
719
- }),
720
- duration: z.string().optional(),
721
- channel: z.string().optional(),
722
- date: z.string().optional(),
723
- views: z.number().int().min(0).optional(),
724
- thumbnailUrl: safeMediaUrlOrEmpty({
725
- message: "videoGalleryItems.thumbnailUrl must be a valid URL"
726
- }),
727
- description: z.string().optional()
728
- },
729
- { message: "videoGalleryItems entries must be objects with videoUrl" }
730
- );
731
- function heroVideoHasTitle(data) {
732
- return !data.heroVideoUrl?.trim() || Boolean(data.heroVideoTitle?.trim());
733
- }
734
- var HERO_VIDEO_TITLE_ISSUE = {
735
- message: "heroVideoTitle must not be empty when heroVideoUrl is set",
736
- path: ["heroVideoTitle"]
737
- };
738
-
739
- // src/schemas/response/compare-json.schema.ts
740
- var keyFindingSchema = createTextItemSchema("keyFindings");
741
- z.object({
742
- category: z.string(),
743
- title: z.string().min(1, { message: "title must not be empty" }),
744
- subtitle: z.string(),
745
- sectionContent: z.string(),
746
- galleryTitle: z.string().optional(),
747
- galleryItems: z.array(referenceGalleryItemSchema).optional(),
748
- videoGalleryTitle: z.string().optional(),
749
- videoGalleryItems: z.array(videoGalleryItemSchema).optional(),
750
- keyFindings: z.array(keyFindingSchema).optional(),
751
- sources: z.array(sourceSchema).optional(),
752
- discardedReferences: z.array(discardedReferenceSchema).optional(),
753
- note: z.string().optional(),
754
- internationalCoverage: internationalCoverageSchema.optional()
755
- });
756
- var keyFindingSchema2 = createTextItemSchema("keyFindings");
757
- z.object({
758
- category: z.string(),
759
- title: z.string().min(1, { message: "title must not be empty" }),
760
- subtitle: z.string(),
761
- sectionContent: z.string(),
762
- galleryTitle: z.string().optional(),
763
- galleryItems: z.array(referenceGalleryItemSchema).optional(),
764
- videoGalleryTitle: z.string().optional(),
765
- videoGalleryItems: z.array(videoGalleryItemSchema).optional(),
766
- keyFindings: z.array(keyFindingSchema2).optional(),
767
- sources: z.array(sourceSchema).optional(),
768
- discardedReferences: z.array(discardedReferenceSchema).optional(),
769
- note: z.string().optional(),
770
- internationalCoverage: internationalCoverageSchema.optional()
771
- });
772
- var galleryItemSchema = z.object(
773
- {
774
- imageUrl: safeMediaUrl({
775
- message: "galleryItems.imageUrl must be a valid URL"
776
- }),
777
- imageAlt: z.string().min(1, {
778
- message: "galleryItems.imageAlt must not be empty"
779
- }),
780
- title: z.string().min(1, {
781
- message: "galleryItems.title must not be empty"
782
- }),
783
- caption: z.string().optional()
784
- },
785
- { message: "galleryItems entries must be objects with imageUrl" }
786
- );
787
- var imagelistGalleryItemSchema = galleryItemSchema.extend({
788
- width: z.number().int().positive().optional(),
789
- height: z.number().int().positive().optional(),
790
- source: z.string().optional()
791
- });
792
- z.object({
793
- category: z.string(),
794
- title: z.string().min(1, { message: "title must not be empty" }),
795
- subtitle: z.string(),
796
- galleryItems: z.array(imagelistGalleryItemSchema),
797
- sources: z.array(sourceSchema).optional(),
798
- internationalCoverage: internationalCoverageSchema.optional()
799
- });
800
- var keyFindingSchema3 = createTextItemSchema("keyFindings");
801
- z.object({
802
- category: z.string(),
803
- title: z.string().min(1, { message: "title must not be empty" }),
804
- subtitle: z.string(),
805
- sectionContent: z.string(),
806
- keyFindings: z.array(keyFindingSchema3).optional(),
807
- // Reference material, only when the model researched visible clues online
808
- galleryTitle: z.string().optional(),
809
- galleryItems: z.array(referenceGalleryItemSchema).optional(),
810
- videoGalleryTitle: z.string().optional(),
811
- videoGalleryItems: z.array(videoGalleryItemSchema).optional(),
812
- sources: z.array(sourceSchema).optional(),
813
- discardedReferences: z.array(discardedReferenceSchema).optional(),
814
- internationalCoverage: internationalCoverageSchema.optional()
815
- });
816
- var shopOfferSchema = z.object(
817
- {
818
- title: z.string().optional(),
819
- price: z.string().optional(),
820
- source: z.string().optional(),
821
- link: safeUrl({ message: "shopOffers entries must have a valid link" }),
822
- imageUrl: z.string().url().optional().or(z.literal("")),
823
- delivery: z.string().optional(),
824
- rating: z.number().optional(),
825
- ratingCount: z.number().optional()
826
- },
827
- { message: "shopOffers entries must be objects with a link" }
828
- );
829
- var statHighlightSchema = z.object(
830
- {
831
- label: z.string().min(1, { message: "statHighlights entries must have a label" }),
832
- value: z.string().min(1, { message: "statHighlights entries must have a value" })
833
- },
834
- { message: "statHighlights entries must be objects with label and value" }
835
- );
836
- z.object({
837
- category: z.string(),
838
- title: z.string().min(1, { message: "title must not be empty" }),
839
- subtitle: z.string(),
840
- shortDescription: z.string(),
841
- // Purchase-decision fields
842
- aggregateRating: z.number().min(0).max(5).optional(),
843
- aggregateRatingCount: z.number().int().min(0).optional(),
844
- aggregateRatingLabel: z.string().optional(),
845
- statHighlights: z.array(statHighlightSchema).optional(),
846
- keyPoints: z.array(createTextItemSchema("keyPoints")).optional(),
847
- pros: z.array(createTextItemSchema("pros")).optional(),
848
- cons: z.array(createTextItemSchema("cons")).optional(),
849
- shopOffers: z.array(shopOfferSchema).optional(),
850
- // Media — the product banner is image-only, there is no hero video
851
- heroImageUrl: safeMediaUrlOrEmpty(),
852
- heroImageAlt: z.string().optional(),
853
- heroCaption: z.string().optional(),
854
- galleryTitle: z.string().optional(),
855
- galleryItems: z.array(galleryItemSchema).optional(),
856
- videoGalleryTitle: z.string().optional(),
857
- videoGalleryItems: z.array(videoGalleryItemSchema).optional(),
858
- // Attribution
859
- sources: z.array(sourceSchema).optional(),
860
- internationalCoverage: internationalCoverageSchema.optional()
861
- });
862
- var relatedStorySchema = z.object(
863
- {
864
- title: z.string().min(1, { message: "relatedStories entries must have a title" }),
865
- url: safeUrl({ message: "relatedStories entries must have a valid url" }),
866
- sourceName: z.string().optional(),
867
- imageUrl: safeMediaUrl({
868
- message: "relatedStories entries must have a valid imageUrl"
869
- }),
870
- date: z.string().optional()
871
- },
872
- { message: "relatedStories entries must have title, url, and imageUrl" }
873
- );
874
- var shopOfferSchema2 = z.object(
875
- {
876
- title: z.string().optional(),
877
- price: z.string().optional(),
878
- source: z.string().optional(),
879
- link: safeUrl({ message: "shopOffers entries must have a valid link" }),
880
- imageUrl: safeMediaUrlOrEmpty("shopOffers.imageUrl must be a valid URL"),
881
- delivery: z.string().optional(),
882
- rating: z.number().optional(),
883
- ratingCount: z.number().optional()
884
- },
885
- { message: "shopOffers entries must be objects with a link" }
886
- );
887
- z.object({
888
- category: z.string(),
889
- title: z.string().min(1, { message: "title must not be empty" }),
890
- subtitle: z.string(),
891
- shortDescription: z.string().optional(),
892
- shopOffers: z.array(shopOfferSchema2).optional(),
893
- sources: z.array(sourceSchema).optional(),
894
- internationalCoverage: internationalCoverageSchema.optional()
895
- });
896
- var newsItemSchema = z.object(
897
- {
898
- title: z.string().min(1, {
899
- message: "news entries must have a non-empty title"
900
- }),
901
- url: safeUrl({ message: "news entries must have a valid url" }),
902
- source: z.string().optional(),
903
- date: z.string().optional(),
904
- snippet: z.string().optional()
905
- },
906
- { message: "news entries must be objects with title and url" }
907
- );
908
- var fundamentalsSchema = z.object({
909
- name: z.string().optional(),
910
- sector: z.string().optional(),
911
- industry: z.string().optional(),
912
- marketCap: z.union([z.number(), z.string()]).optional(),
913
- peRatio: z.union([z.number(), z.string()]).optional(),
914
- revenue: z.union([z.number(), z.string()]).optional(),
915
- profitMargin: z.union([z.number(), z.string()]).optional()
916
- });
917
- z.object({
918
- category: z.string(),
919
- title: z.string().min(1, { message: "title must not be empty" }),
920
- subtitle: z.string(),
921
- shortDescription: z.string(),
922
- // Quote
923
- currentPrice: z.number().optional(),
924
- change: z.number().optional(),
925
- changeP: z.number().optional(),
926
- // Recommendation
927
- recommendation: z.string().optional(),
928
- recommendationReasoning: z.string().optional(),
929
- // Stats / context
930
- keyPoints: z.array(createTextItemSchema("keyPoints")).optional(),
931
- fundamentals: fundamentalsSchema.optional(),
932
- // News + attribution
933
- news: z.array(newsItemSchema).optional(),
934
- sources: z.array(sourceSchema).optional(),
935
- internationalCoverage: internationalCoverageSchema.optional(),
936
- // Chart overlays
937
- referenceLines: z.array(referenceLineSchema).optional(),
938
- markers: z.array(markerSchema).optional(),
939
- // Videos
940
- videoGalleryTitle: z.string().optional(),
941
- videoGalleryItems: z.array(videoGalleryItemSchema).optional()
942
- });
943
- var listItemSchema = z.object(
944
- {
945
- name: z.string().min(1, {
946
- message: "items entries must have a non-empty name"
947
- }),
948
- ticker: z.string().min(1, {
949
- message: "items entries must have a non-empty ticker"
950
- }),
951
- price: z.number().optional(),
952
- change: z.number().optional(),
953
- changeP: z.number().optional()
954
- },
955
- { message: "items entries must be objects with name and ticker" }
956
- );
957
- z.object({
958
- category: z.string(),
959
- title: z.string().min(1, { message: "title must not be empty" }),
960
- subtitle: z.string(),
961
- summary: z.string(),
962
- items: z.array(listItemSchema).optional(),
963
- sources: z.array(sourceSchema).optional(),
964
- internationalCoverage: internationalCoverageSchema.optional(),
965
- // Chart overlays
966
- referenceLines: z.array(referenceLineSchema).optional(),
967
- markers: z.array(markerSchema).optional(),
968
- // Videos
969
- videoGalleryTitle: z.string().optional(),
970
- videoGalleryItems: z.array(videoGalleryItemSchema).optional()
971
- });
972
- var keyFindingSchema4 = createTextItemSchema("keyFindings");
973
- z.object({
974
- category: z.string(),
975
- title: z.string().min(1, { message: "title must not be empty" }),
976
- subtitle: z.string(),
977
- summary: z.string(),
978
- keyFindings: z.array(keyFindingSchema4).optional(),
979
- sources: z.array(sourceSchema).optional(),
980
- // Media from online research
981
- heroImageUrl: safeMediaUrlOrEmpty(),
982
- heroImageAlt: z.string().optional(),
983
- heroCaption: z.string().optional(),
984
- heroVideoUrl: safeVideoUrlOrEmpty(),
985
- heroVideoTitle: z.string().optional(),
986
- heroVideoCaption: z.string().optional(),
987
- galleryTitle: z.string().optional(),
988
- galleryItems: z.array(galleryItemSchema).optional(),
989
- videoGalleryTitle: z.string().optional(),
990
- videoGalleryItems: z.array(videoGalleryItemSchema).optional(),
991
- internationalCoverage: internationalCoverageSchema.optional()
992
- }).refine(heroVideoHasTitle, HERO_VIDEO_TITLE_ISSUE);
993
- z.object({
994
- category: z.string(),
995
- title: z.string().min(1, { message: "title must not be empty" }),
996
- subtitle: z.string(),
997
- videoGalleryItems: z.array(videoGalleryItemSchema),
998
- internationalCoverage: internationalCoverageSchema.optional()
999
- });
160
+ // src/schemas/constants/media-counts.constant.ts
161
+ var DEFAULT_MEDIA_COUNT = 6;
162
+ var MORE_MEDIA_COUNT = 12;
1000
163
 
1001
164
  // src/prompts/shared/media-counts.prompt.ts
1002
165
  var MEDIA_COUNTS = `MEDIA COUNTS
@@ -1006,6 +169,10 @@ var MEDIA_COUNTS = `MEDIA COUNTS
1006
169
  - hero media counts toward the total.
1007
170
  - Never exceed available URLs.`;
1008
171
 
172
+ // src/schemas/constants/embeddable-video-providers.constant.ts
173
+ var EMBEDDABLE_VIDEO_PROVIDER_LABELS = "YouTube, Vimeo, Dailymotion, Loom, Wistia";
174
+ var EMBEDDABLE_VIDEO_PROVIDER_CLAUSE = `Only return URLs from supported embeddable providers: ${EMBEDDABLE_VIDEO_PROVIDER_LABELS}, or direct video files. Reject Instagram, Facebook, TikTok, Twitch, X/Twitter, and other unreliable platforms.`;
175
+
1009
176
  // src/prompts/shared/media-rules.prompt.ts
1010
177
  var MEDIA_RULES = `MEDIA RULES
1011
178
  - Media-list templates (imagelist, videolist, shoplist) have NO hero media, and product has an image-only banner with NO hero video \u2014 their mode rules override every hero-related rule below.
@@ -1134,13 +301,157 @@ var TOOL_RESULTS_RULES = `TOOL RESULTS
1134
301
  - If a tool returns nothing, leave the corresponding fields empty.
1135
302
  - Never invent missing information.`;
1136
303
 
1137
- // src/prompts/snippets/helpers/compose-snippet-keys.helper.ts
1138
- function composeSnippetKeys(preset) {
1139
- const requiredKeys = ["layout", ...preset.spineKeys];
1140
- const required = new Set(requiredKeys);
1141
- const optionalKeys = preset.snippets.flatMap((snippet) => Object.keys(snippet.fields)).filter((key) => !required.has(key));
1142
- return { requiredKeys, optionalKeys };
304
+ // src/prompts/snippets/helpers/compose-snippet-keys.helper.ts
305
+ function composeSnippetKeys(preset) {
306
+ const requiredKeys = ["layout", ...preset.spineKeys];
307
+ const required = new Set(requiredKeys);
308
+ const optionalKeys = preset.snippets.flatMap((snippet) => Object.keys(snippet.fields)).filter((key) => !required.has(key));
309
+ return { requiredKeys, optionalKeys };
310
+ }
311
+
312
+ // src/schemas/constants/url-trust.constants.ts
313
+ var BLOCKED_URL_HOSTS = /* @__PURE__ */ new Set([
314
+ // Google thumbnail / static asset proxies
315
+ "encrypted-tbn0.gstatic.com",
316
+ "encrypted-tbn1.gstatic.com",
317
+ "encrypted-tbn2.gstatic.com",
318
+ "encrypted-tbn3.gstatic.com",
319
+ "t0.gstatic.com",
320
+ "t1.gstatic.com",
321
+ "t2.gstatic.com",
322
+ "t3.gstatic.com",
323
+ "t4.gstatic.com",
324
+ "t5.gstatic.com",
325
+ "t6.gstatic.com",
326
+ "t7.gstatic.com",
327
+ "t8.gstatic.com",
328
+ "t9.gstatic.com",
329
+ "t10.gstatic.com",
330
+ "news.gstatic.com",
331
+ "books.gstatic.com",
332
+ "maps.gstatic.com",
333
+ "lh1.googleusercontent.com",
334
+ "lh2.googleusercontent.com",
335
+ "lh3.googleusercontent.com",
336
+ "lh4.googleusercontent.com",
337
+ "lh5.googleusercontent.com",
338
+ "lh6.googleusercontent.com"
339
+ ]);
340
+ var NON_PAGE_EXTENSIONS = /\.(js|css|json|xml|svg|png|jpg|jpeg|gif|webp|ico|mp4|webm|ogg|mov|mkv|avi|flv|m3u8|mpd|pdf|zip|tar|gz|rar|exe|dmg|pkg|deb|rpm|woff|woff2|ttf|otf|eot)(\?.*)?$/i;
341
+
342
+ // src/schemas/helpers/url-trust/is-private-or-localhost.helper.ts
343
+ function isPrivateOrLocalhost(hostname) {
344
+ const lower = hostname.toLowerCase();
345
+ if (lower === "localhost" || lower.endsWith(".localhost")) return true;
346
+ if (lower === "127.0.0.1" || lower === "0.0.0.0") return true;
347
+ if (lower.startsWith("10.")) return true;
348
+ if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(lower)) return true;
349
+ if (lower.startsWith("192.168.")) return true;
350
+ if (lower.startsWith("169.254.")) return true;
351
+ return false;
352
+ }
353
+
354
+ // src/schemas/helpers/url-trust/is-trusted-url.helper.ts
355
+ function isYouTubeNonVideoPath(hostname, pathname) {
356
+ const lowerHost = hostname.toLowerCase();
357
+ if (lowerHost !== "youtube.com" && lowerHost !== "www.youtube.com" && lowerHost !== "m.youtube.com") {
358
+ return false;
359
+ }
360
+ const lowerPath = pathname.toLowerCase();
361
+ if (lowerPath.startsWith("/s/") || lowerPath.startsWith("/static/") || lowerPath.startsWith("/js/") || lowerPath.startsWith("/css/") || lowerPath.startsWith("/fonts/") || lowerPath.startsWith("/yts/") || lowerPath.startsWith("/iframe_api") || lowerPath.startsWith("/sw.js") || lowerPath.startsWith("/embed_config") || lowerPath.startsWith("/get_video_info") || lowerPath.startsWith("/api/")) {
362
+ return true;
363
+ }
364
+ return false;
365
+ }
366
+ function isTrustedUrl(url, options = {}) {
367
+ if (!url) return false;
368
+ let parsed;
369
+ try {
370
+ parsed = new URL(url);
371
+ } catch {
372
+ return false;
373
+ }
374
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
375
+ return false;
376
+ }
377
+ const hostname = parsed.hostname.toLowerCase();
378
+ const pathname = parsed.pathname;
379
+ if (BLOCKED_URL_HOSTS.has(hostname)) {
380
+ return false;
381
+ }
382
+ if (isYouTubeNonVideoPath(hostname, pathname)) {
383
+ return false;
384
+ }
385
+ if (NON_PAGE_EXTENSIONS.test(pathname)) {
386
+ return false;
387
+ }
388
+ if (!options.allowPrivate && isPrivateOrLocalhost(hostname)) {
389
+ return false;
390
+ }
391
+ return true;
392
+ }
393
+
394
+ // src/schemas/helpers/url-trust/url-schema.helper.ts
395
+ function safeUrl(message = "must be a safe URL") {
396
+ const text = typeof message === "string" ? message : message.message;
397
+ return z.string().url({ message: "must be a valid URL" }).refine((value) => isTrustedUrl(value, { allowPrivate: false }), {
398
+ message: text
399
+ });
400
+ }
401
+ var LOCAL_STORAGE_URL_PATTERN = /^\/api\/v1\/storage\/[A-Za-z0-9_-]+\/[A-Za-z0-9_-]+\/[A-Za-z0-9]+$/;
402
+ function isAbsoluteHttpUrl(value) {
403
+ try {
404
+ const protocol = new URL(value).protocol;
405
+ return protocol === "http:" || protocol === "https:";
406
+ } catch {
407
+ return false;
408
+ }
409
+ }
410
+ function safeVideoUrl(message = "must be a safe video URL") {
411
+ const text = typeof message === "string" ? message : message.message;
412
+ return z.string().refine(isAbsoluteHttpUrl, { message: text });
413
+ }
414
+ function safeVideoUrlOrEmpty(message = "must be a safe video URL") {
415
+ return safeVideoUrl(message).optional().or(z.literal(""));
416
+ }
417
+ function safeMediaUrl(message = "must be a safe media URL") {
418
+ const text = typeof message === "string" ? message : message.message;
419
+ return z.string().refine((value) => LOCAL_STORAGE_URL_PATTERN.test(value) || isAbsoluteHttpUrl(value), { message: text });
420
+ }
421
+ function safeMediaUrlOrEmpty(message = "must be a safe media URL") {
422
+ return safeMediaUrl(message).optional().or(z.literal(""));
423
+ }
424
+
425
+ // src/schemas/response/video-gallery-item-json.schema.ts
426
+ var videoGalleryItemSchema = z.object(
427
+ {
428
+ videoUrl: safeVideoUrl({
429
+ message: "videoGalleryItems.videoUrl must be a valid URL"
430
+ }),
431
+ title: z.string().min(1, {
432
+ message: "videoGalleryItems.title must not be empty"
433
+ }),
434
+ caption: z.string().min(1, {
435
+ message: "videoGalleryItems.caption must not be empty"
436
+ }),
437
+ duration: z.string().optional(),
438
+ channel: z.string().optional(),
439
+ date: z.string().optional(),
440
+ views: z.number().int().min(0).optional(),
441
+ thumbnailUrl: safeMediaUrlOrEmpty({
442
+ message: "videoGalleryItems.thumbnailUrl must be a valid URL"
443
+ }),
444
+ description: z.string().optional()
445
+ },
446
+ { message: "videoGalleryItems entries must be objects with videoUrl" }
447
+ );
448
+ function heroVideoHasTitle(data) {
449
+ return !data.heroVideoUrl?.trim() || Boolean(data.heroVideoTitle?.trim());
1143
450
  }
451
+ var HERO_VIDEO_TITLE_ISSUE = {
452
+ message: "heroVideoTitle must not be empty when heroVideoUrl is set",
453
+ path: ["heroVideoTitle"]
454
+ };
1144
455
  var RESPONSE_LAYOUTS = ["classic", "editorial", "split", "mosaic"];
1145
456
  var responseLayoutSchema = z.enum(RESPONSE_LAYOUTS);
1146
457
 
@@ -1174,6 +485,17 @@ var bodyExtensiveSnippet = {
1174
485
  - sectionContent: the main body text as one plain-text string of 4-8 paragraphs: context, quotes, reactions, and background. This is the article's core \u2014 write it in full.
1175
486
  - sectionTitle: a heading for the main body section; omit only when the article needs none.`
1176
487
  };
488
+ var cardSchema = z.object(
489
+ {
490
+ url: safeUrl({ message: "cards entries must have a valid url" }),
491
+ title: z.string().optional(),
492
+ description: z.string().optional(),
493
+ linkLabel: z.string().optional()
494
+ },
495
+ { message: "cards entries must be objects with url" }
496
+ );
497
+
498
+ // src/prompts/snippets/cards.snippet.ts
1177
499
  var cardsSnippet = {
1178
500
  fields: {
1179
501
  cardsTitle: z.string().optional(),
@@ -1192,6 +514,23 @@ var conclusionSnippet = {
1192
514
  instruction: `SNIPPET conclusion (client closing section):
1193
515
  - conclusion: a brief closing summary (1-3 sentences); omit when the article already lands cleanly.`
1194
516
  };
517
+ var galleryItemSchema = z.object(
518
+ {
519
+ imageUrl: safeMediaUrl({
520
+ message: "galleryItems.imageUrl must be a valid URL"
521
+ }),
522
+ imageAlt: z.string().min(1, {
523
+ message: "galleryItems.imageAlt must not be empty"
524
+ }),
525
+ title: z.string().min(1, {
526
+ message: "galleryItems.title must not be empty"
527
+ }),
528
+ caption: z.string().optional()
529
+ },
530
+ { message: "galleryItems entries must be objects with imageUrl" }
531
+ );
532
+
533
+ // src/prompts/snippets/gallery.snippet.ts
1195
534
  var gallerySnippet = {
1196
535
  fields: {
1197
536
  galleryTitle: z.string().optional(),
@@ -1229,6 +568,20 @@ var heroMediaSnippet = {
1229
568
  - heroImageUrl set \u21D2 heroImageAlt MUST be a non-empty descriptive label; heroCaption stays optional.
1230
569
  - Leave empty ONLY when the pools hold no usable media \u2014 never invent URLs.`
1231
570
  };
571
+ var internationalCoverageSchema = z.array(
572
+ z.object(
573
+ {
574
+ title: z.string().optional(),
575
+ url: safeUrl({
576
+ message: "internationalCoverage entries must have a valid url"
577
+ }),
578
+ sourceName: z.string().optional(),
579
+ language: z.string().optional(),
580
+ summary: z.string().optional()
581
+ },
582
+ { message: "internationalCoverage entries must be objects" }
583
+ )
584
+ );
1232
585
 
1233
586
  // src/prompts/instructions/international-coverage.instruction.ts
1234
587
  var INTERNATIONAL_COVERAGE_INSTRUCTIONS = `INTERNATIONAL COVERAGE ASIDE:
@@ -1248,6 +601,18 @@ var internationalCoverageSnippet = {
1248
601
  },
1249
602
  instruction: INTERNATIONAL_COVERAGE_INSTRUCTIONS
1250
603
  };
604
+ function createTextItemSchema(fieldName) {
605
+ return z.object(
606
+ {
607
+ text: z.string().min(1, {
608
+ message: `${fieldName} entries must have a non-empty text field`
609
+ })
610
+ },
611
+ { message: `${fieldName} entries must be objects with text` }
612
+ );
613
+ }
614
+
615
+ // src/prompts/snippets/key-findings.snippet.ts
1251
616
  var keyFindingsSnippet = {
1252
617
  fields: {
1253
618
  keyFindings: z.array(createTextItemSchema("keyFindings")).optional()
@@ -1264,6 +629,18 @@ var quoteSnippet = {
1264
629
  - Needs: a quote that exists verbatim in the retrieved results.
1265
630
  - quote: one notable quote from the sources; omit when none exists.`
1266
631
  };
632
+ var sourceSchema = z.object(
633
+ {
634
+ url: safeUrl({ message: "sources entries must have a valid url" }),
635
+ title: z.string().optional(),
636
+ sourceName: z.string().optional(),
637
+ date: z.string().optional(),
638
+ snippet: z.string().optional()
639
+ },
640
+ { message: "sources entries must be objects with url" }
641
+ );
642
+
643
+ // src/prompts/snippets/sources.snippet.ts
1267
644
  var sourcesSnippet = {
1268
645
  fields: {
1269
646
  sources: z.array(sourceSchema).optional()
@@ -1603,6 +980,20 @@ var leadSnippet = {
1603
980
  - Needs: the core facts from the retrieved results.
1604
981
  - lead: 2-4 sentences answering who, what, when, where, why and how, strongest facts first (inverted pyramid). REQUIRED, non-empty.`
1605
982
  };
983
+ var relatedStorySchema = z.object(
984
+ {
985
+ title: z.string().min(1, { message: "relatedStories entries must have a title" }),
986
+ url: safeUrl({ message: "relatedStories entries must have a valid url" }),
987
+ sourceName: z.string().optional(),
988
+ imageUrl: safeMediaUrl({
989
+ message: "relatedStories entries must have a valid imageUrl"
990
+ }),
991
+ date: z.string().optional()
992
+ },
993
+ { message: "relatedStories entries must have title, url, and imageUrl" }
994
+ );
995
+
996
+ // src/prompts/snippets/related-stories.snippet.ts
1606
997
  var relatedStoriesSnippet = {
1607
998
  fields: {
1608
999
  relatedStories: z.array(relatedStorySchema).optional()
@@ -1721,46 +1112,6 @@ ${params.instructions}`);
1721
1112
  return sections.filter(Boolean).join("\n\n");
1722
1113
  }
1723
1114
 
1724
- // src/tools/constants/recency.constants.ts
1725
- var RECENCY_DESCRIPTION = "Restrict results to the given past period (day=24 hours, week=7 days, month=1 month, year=1 year). Use for fresh content such as news, recent releases, or trending topics; leave unset for evergreen, historical, or general queries.";
1726
-
1727
- // src/tools/constants/standalone-query.constants.ts
1728
- var STANDALONE_QUERY_DESCRIPTION = 'A standalone, self-contained search query that explicitly names the subject (title, entity, brand, person, place, or topic). Never copy the user message verbatim: rewrite short follow-ups (e.g. "what do the reviews say?") into a full query that names the established subject from the conversation (e.g. "Neverness to Everness NTE reviews").';
1729
-
1730
- // src/tools/bright-data/image-search.schema.ts
1731
- z.object({
1732
- query: z.string().describe(`${STANDALONE_QUERY_DESCRIPTION} Add short visual qualifiers describing the subject.`),
1733
- count: z.number().optional().describe("Number of results (max 100)"),
1734
- minWidth: z.number().optional().describe("Minimum image width in pixels (floor 1280 / 720p)."),
1735
- minHeight: z.number().optional().describe("Minimum image height in pixels (floor 720 / 720p)."),
1736
- lang: z.string().optional().describe("Two-letter ISO language code for result preference (e.g. en, de, ja)"),
1737
- recency: z.enum(["day", "week", "month", "year"]).optional().describe(RECENCY_DESCRIPTION)
1738
- });
1739
- z.object({
1740
- query: z.string().describe(`${STANDALONE_QUERY_DESCRIPTION} Include the newsworthy angle (announcement, release, event, update).`),
1741
- count: z.number().optional().describe("Number of results (max 100)"),
1742
- recency: z.enum(["day", "week", "month", "year"]).optional().describe(RECENCY_DESCRIPTION),
1743
- lang: z.string().optional().describe("Two-letter ISO language code for result preference (e.g. en, de, ja)")
1744
- });
1745
- z.object({
1746
- query: z.string().describe(
1747
- 'A standalone places search query that explicitly names the business or business type plus location (e.g. "MediaMarkt Berlin", "coffee shops in Munich").'
1748
- ),
1749
- count: z.number().optional().describe("Number of results (max 100)"),
1750
- lang: z.string().optional().describe("Two-letter ISO language code for result preference (e.g. en, de, ja)")
1751
- });
1752
- z.object({
1753
- query: z.string().describe("The exact product name with model number, kept short and standalone."),
1754
- count: z.number().optional().describe("Number of results (max 100)"),
1755
- lang: z.string().optional().describe("Two-letter ISO language code for result preference (e.g. en, de, ja)")
1756
- });
1757
- z.object({
1758
- query: z.string().describe(`${STANDALONE_QUERY_DESCRIPTION} Add the video type (e.g. review, trailer, tutorial, gameplay).`),
1759
- count: z.number().optional().describe("Number of results (max 100)"),
1760
- recency: z.enum(["day", "week", "month", "year"]).optional().describe(RECENCY_DESCRIPTION),
1761
- lang: z.string().optional().describe("Two-letter ISO language code for result preference (e.g. en, de, ja)")
1762
- });
1763
-
1764
1115
  // src/tools/helpers/localized-query-suffix.helper.ts
1765
1116
  function localizedQuerySuffix(lang) {
1766
1117
  if (!lang) return "";
@@ -1778,111 +1129,6 @@ function localizedQuerySuffix(lang) {
1778
1129
  return "";
1779
1130
  }
1780
1131
  }
1781
- z.object({
1782
- query: z.string().describe(STANDALONE_QUERY_DESCRIPTION),
1783
- recency: z.enum(["day", "week", "month", "year"]).optional().describe(RECENCY_DESCRIPTION),
1784
- lang: z.string().optional().describe("Two-letter ISO language code for result preference (e.g. en, de, ja)")
1785
- });
1786
- z.object({
1787
- url: z.string().describe("The URL to fetch and render")
1788
- });
1789
- z.object({});
1790
- z.object({});
1791
- z.object({
1792
- text: z.string().min(1).max(2e3).describe(
1793
- 'The derived insight to store, as one self-contained third-person sentence, e.g. "The user prefers single-line if statements".'
1794
- ),
1795
- path: z.string().min(1).max(200).optional().describe(
1796
- 'Optional profile facet this insight deepens, e.g. "likes.cars" \u2014 only when it deepens a stored profile value.'
1797
- )
1798
- });
1799
- z.object({
1800
- text: z.string().min(3).max(2e3).describe(
1801
- "The exact stored statement to delete, quoted verbatim from a memory-partition-recall result. Record texts are the record identity \u2014 no ids needed. Never paraphrase: recall first, then delete the exact text."
1802
- )
1803
- });
1804
- z.object({
1805
- query: z.string().min(1).max(1e3).describe('What to recall as a natural-language question, e.g. "What is Sams phone number?"'),
1806
- tags: z.array(z.string().min(1).max(40)).max(8).optional().describe('Restrict to records tagged with ANY of these topics, e.g. ["work"].'),
1807
- contains: z.string().max(200).optional().describe('Restrict to records whose text contains this exact phrase, e.g. "phone number".'),
1808
- topK: z.number().int().min(1).max(10).optional().describe("Maximum number of results to return (default 5).")
1809
- });
1810
- z.object({
1811
- text: z.string().min(1).max(2e3).describe('The fact to remember, as a self-contained statement, e.g. "Sams phone number is 555-1234".'),
1812
- category: z.string().min(1).max(40).optional().describe(
1813
- 'One broad lowercase PLURAL category the fact belongs to, e.g. "stocks", "games", "pets", "work" \u2014 a family noun, never a specific entity/product/company/game name ("amd" \u2192 "stocks"; "stellar blade" \u2192 "games"). Always include it.'
1814
- ),
1815
- tags: z.array(z.string().min(1).max(40)).max(8).optional().describe(
1816
- 'Optional topic labels (lowercase, reusable, NARROW \u2014 entity/product/game names) so future recall can filter by topic, e.g. ["contacts", "sam"], ["amd"], ["stellar blade"].'
1817
- )
1818
- });
1819
- z.object({
1820
- query: z.string().optional().describe(
1821
- "The exact business or place name, ideally with its location, named explicitly and resolved from the conversation. Used when neither placeId nor cid is known."
1822
- ),
1823
- placeId: z.string().optional().describe("Google Place ID of the business. Most precise identifier \u2014 prefer it when available."),
1824
- cid: z.string().optional().describe("Google CID of the business, e.g. from a places search result."),
1825
- lang: z.string().optional().describe("Two-letter ISO language code for result preference (e.g. en, de, ja)")
1826
- });
1827
- z.object({
1828
- query: z.string().describe(`${STANDALONE_QUERY_DESCRIPTION} Add short visual qualifiers describing the subject.`),
1829
- count: z.number().optional().describe("Number of results (max 100)"),
1830
- minWidth: z.number().optional().describe(
1831
- "Minimum image width in pixels. Use 1920 when the user wants 1080p-quality images, 2560 for 1440p, 3840 for 4K. The tool always enforces a floor of 1280 (720p)."
1832
- ),
1833
- minHeight: z.number().optional().describe(
1834
- "Minimum image height in pixels. Use 1080 when the user wants 1080p-quality images, 1440 for 1440p, 2160 for 4K. The tool always enforces a floor of 720 (720p)."
1835
- ),
1836
- lang: z.string().optional().describe("Two-letter ISO language code for result preference (e.g. en, de, ja)"),
1837
- recency: z.enum(["day", "week", "month", "year"]).optional().describe(RECENCY_DESCRIPTION)
1838
- });
1839
- z.object({
1840
- query: z.string().describe(`${STANDALONE_QUERY_DESCRIPTION} Include the newsworthy angle (announcement, release, event, update).`),
1841
- count: z.number().optional().describe("Number of results (max 100)"),
1842
- recency: z.enum(["day", "week", "month", "year"]).optional().describe(RECENCY_DESCRIPTION),
1843
- lang: z.string().optional().describe("Two-letter ISO language code for result preference (e.g. en, de, ja)")
1844
- });
1845
- z.object({
1846
- query: z.string().describe(
1847
- 'A standalone places search query that explicitly names the business or business type plus location (e.g. "MediaMarkt Berlin", "coffee shops in Munich") \u2014 resolve the subject from the conversation; never copy the user message verbatim.'
1848
- ),
1849
- count: z.number().optional().describe("Number of results (max 100)"),
1850
- lang: z.string().optional().describe("Two-letter ISO language code for result preference (e.g. en, de, ja)")
1851
- });
1852
- z.object({
1853
- query: z.string().describe(
1854
- 'The exact product name with model number, kept short and standalone \u2014 resolve product references from the conversation (e.g. "the headphones we discussed" becomes "Sony WH-1000XM5"). No extra words like "buy", "price", or "review".'
1855
- ),
1856
- count: z.number().optional().describe("Number of results (max 100)"),
1857
- lang: z.string().optional().describe("Two-letter ISO language code for result preference (e.g. en, de, ja)")
1858
- });
1859
- z.object({
1860
- query: z.string().describe(
1861
- `${STANDALONE_QUERY_DESCRIPTION} Add the video type (e.g. review, trailer, tutorial, gameplay). When the conversation language is not English, phrase the descriptive words in that language and append the language's own name (e.g. "Review Deutsch") to pull localized results.`
1862
- ),
1863
- count: z.number().optional().describe("Number of results (max 100)"),
1864
- recency: z.enum(["day", "week", "month", "year"]).optional().describe(RECENCY_DESCRIPTION),
1865
- lang: z.string().optional().describe("Two-letter ISO language code for result preference (e.g. en, de, ja)")
1866
- });
1867
- z.object({
1868
- query: z.string().describe(STANDALONE_QUERY_DESCRIPTION),
1869
- recency: z.enum(["day", "week", "month", "year"]).optional().describe(RECENCY_DESCRIPTION),
1870
- lang: z.string().optional().describe("Two-letter ISO language code for result preference (e.g. en, de, ja)")
1871
- });
1872
- z.object({
1873
- url: z.string().describe("The URL to fetch and render")
1874
- });
1875
- z.object({
1876
- url: z.string().describe("The URL to fetch content from")
1877
- });
1878
- z.object({
1879
- query: z.string().describe(`${STANDALONE_QUERY_DESCRIPTION} Add the video type (e.g. review, trailer, tutorial, gameplay).`),
1880
- count: z.number().optional().describe("Number of results (max 50)"),
1881
- recency: z.enum(["day", "week", "month", "year"]).optional().describe(
1882
- "Restrict results to the given past period (day=24 hours, week=7 days, month=1 month, year=1 year). Use for fresh content such as news, recent releases, or trending topics; leave unset for evergreen, historical, or general queries."
1883
- ),
1884
- lang: z.string().optional().describe("Two-letter ISO language code for result preference (e.g. en, de, ja)")
1885
- });
1886
1132
 
1887
1133
  // src/prompts/harness/execute/execute-language.helper.ts
1888
1134
  function buildExecuteLanguageInstruction(language, options) {
@@ -2021,6 +1267,112 @@ FINAL REMINDER:
2021
1267
  - Every query names its subject explicitly; never pass the user message verbatim as a query.`;
2022
1268
  }
2023
1269
 
1270
+ // src/schemas/helpers/tools/tool-registry.constants.ts
1271
+ var VARIANT_NAMES = ["grayscale", "denoised", "sharpened", "clahe"];
1272
+ var TOOL_DESCRIPTIONS = {
1273
+ brightDataWebSearch: "Search the web using Bright Data SERP API (Google). Returns organic results with titles, snippets, and links. Supports an optional recency window (day/week/month/year) for fresh results.",
1274
+ brightDataImageSearch: "Search for images using Bright Data SERP API (Google Images). Returns image URLs, source pages, and dimensions. Enforces a minimum of 1280\xD7720 (720p).",
1275
+ brightDataNewsSearch: "Search latest news using Bright Data SERP API. Returns headlines, sources, dates, and snippets. Supports an optional recency window (day/week/month/year).",
1276
+ brightDataPlacesSearch: "Search places and businesses using Bright Data SERP API (Google Maps). Returns addresses, phone numbers, ratings, review counts, and coordinates. Query with a business name or business type plus location.",
1277
+ brightDataShoppingSearch: "Search for products using Bright Data SERP API (Google Shopping). Returns prices, sellers, images, and ratings. Query with the bare product name and model number.",
1278
+ brightDataVideoSearch: `Search for videos using Bright Data SERP API. Returns titles, links, channel names, duration, and publish dates. Supports an optional recency window (day/week/month/year) for fresh results. ${EMBEDDABLE_VIDEO_PROVIDER_CLAUSE}`,
1279
+ brightDataWebpageScrape: "Fetch and render a full webpage using Bright Data Web Unlocker API. Returns clean Markdown text. Use for pages behind anti-bot protection that plain fetch cannot reach.",
1280
+ serperWebSearch: "Search the web using Serper.dev (Google). Returns organic results with titles, snippets, and links. Supports an optional recency window (day/week/month/year) for fresh results.",
1281
+ serperImageSearch: "Search for images using Serper.dev (Google Images). Prefers 2560\xD71440 (1440p) images and enforces a minimum of 1280\xD7720 (720p) via the Google Images tbs size filter and a client-side dimension filter. Supports an optional recency window (day/week/month/year).",
1282
+ serperNewsSearch: "Search latest news using Serper.dev. Returns headlines, sources, dates, and snippets. Supports an optional recency window (day/week/month/year).",
1283
+ serperPlacesSearch: "Search places and businesses using Serper.dev (Google Maps). Returns addresses, phone numbers, ratings, review counts, and coordinates. Query with a business name or business type plus location.",
1284
+ serperShoppingSearch: "Search for products using Serper.dev (Google Shopping). Returns prices, sellers, delivery info, images, and per-offer ratings. Query with the bare product name and model number.",
1285
+ serperBusinessReviewsSearch: "Fetch Google Maps reviews for a specific business or place using Serper.dev. Returns reviewer snippets with author names, star ratings, and dates. Use for seller/business reputation, not editorial product reviews.",
1286
+ serperVideoSearch: `Search for videos using Serper.dev. Returns titles, links, channel names, duration, and publish dates. Supports an optional recency window (day/week/month/year) for fresh results. ${EMBEDDABLE_VIDEO_PROVIDER_CLAUSE}`,
1287
+ serperWebpageScrape: "Fetch and render a full webpage using Serper.dev scrape API. Returns clean rendered text with its title.",
1288
+ youtubeVideoSearch: "Search YouTube using the official YouTube Data API. Returns titles, links, channel names, durations, view counts, upload dates, and direct thumbnails. All results are embeddable YouTube videos.",
1289
+ eodhdSearch: 'Resolve a company, ETF, or index name to an EODHD ticker code (e.g. "Nvidia" \u2192 NVDA.US). Use before fetching quotes, history, technicals, or news for a named entity.',
1290
+ eodhdQuote: "Fetch the current (delayed) quote for one or more EODHD tickers \u2014 last price, change, change %, open/high/low, volume, previous close.",
1291
+ eodhdHistory: "Fetch end-of-day OHLCV price history for an EODHD ticker. Returns a compact summary plus the full time series as chartData for the client chart. Use for the time-value chart and buy/sell pressure (volume).",
1292
+ eodhdTechnical: "Fetch a technical indicator series (RSI, MACD, ADX, SMA, EMA, BBANDS, ATR, \u2026) for an EODHD ticker. Use to gauge buy/sell pressure and momentum.",
1293
+ eodhdIntraday: "Fetch intraday OHLCV bars for an EODHD ticker and return a per-day, per-price-band volume profile as chartData for the client heatmap. Use to render a volume heatmap across fixed price bands.",
1294
+ eodhdNews: "Fetch recent financial news for an EODHD ticker \u2014 headlines, links, sources, publish dates. Use to ground a stock-market answer in what is happening around a company.",
1295
+ eodhdFundamentals: "Fetch company fundamentals for an EODHD ticker \u2014 general info, valuation, and key financial highlights (sector, market cap, P/E, revenue, margins).",
1296
+ webFetch: "Fetch the full content of a specific URL (e.g. a search result page) as clean Markdown. Prefer fetching the most relevant search results to ground the answer in full source text rather than relying on snippets alone.",
1297
+ browser_navigate: "Control a real browser: navigate to a URL. Use for interactive browsing \u2014 JS-heavy pages, content behind clicks, tabs, scrolling, or forms \u2014 that static search/fetch cannot reach. Follow up with browser_snapshot to read the page before acting on it.",
1298
+ browser_navigate_back: "Go back to the previous page in the browser history.",
1299
+ browser_snapshot: "Read the current browser page as an accessibility snapshot (structured text with element refs). The primary way to see page content and obtain the refs that browser_click/browser_type need.",
1300
+ browser_click: "Click an element in the browser, referenced by a ref from browser_snapshot.",
1301
+ browser_type: "Type text into an editable browser element, referenced by a ref from browser_snapshot, optionally submitting with Enter.",
1302
+ browser_fill_form: "Fill multiple form fields in the browser in one call.",
1303
+ browser_select_option: "Select an option in a dropdown in the browser.",
1304
+ browser_press_key: "Press a keyboard key in the browser (Enter, Tab, arrows).",
1305
+ browser_wait_for: "Wait for text to appear or disappear, or for a time period, in the browser.",
1306
+ browser_take_screenshot: "Take a screenshot of the current browser page or a specific element.",
1307
+ browser_tabs: "List, create, close, or switch browser tabs.",
1308
+ browser_console_messages: "Get console messages from the browser. Use to diagnose JavaScript errors on a page (e.g. when testing a web app).",
1309
+ browser_network_requests: "List network requests the browser made since page load. Use to diagnose failed or slow requests (e.g. when testing a web app).",
1310
+ browser_verify_element_visible: "Assert that an element is visible on the current browser page.",
1311
+ browser_verify_text_visible: "Assert that text is visible on the current browser page.",
1312
+ requestGrayscale: "Request a grayscale version of the images. Use when color noise or color information is irrelevant, for example when reading text or analyzing shapes.",
1313
+ requestDenoised: "Request a denoised (blurred) version of the images. Use when the original has noise, grain, or artifacts that hide details.",
1314
+ requestSharpened: "Request a sharpened version of the images. Use when edges or fine details are blurry.",
1315
+ requestClahe: "Request a CLAHE (contrast-enhanced) version of the images. Use when details are hidden in shadows or highlights.",
1316
+ "memory-partition-remember": `Store into the user's fact partition (memory-partition): notable facts about subjects they care about (favorites, interests, projects, followed stocks, people, past topics), preferences and durable details they state, and anything they explicitly ask you to remember. Storing gathered knowledge and noticed preferences is expected \u2014 do not wait for an explicit "remember" instruction.`,
1317
+ "memory-partition-recall": "Retrieve from the user's fact partition (memory-partition) \u2014 things they told you in past conversations or asked you to remember. These are trusted user statements, not public facts; attribute them to the user and prefer them over web results for anything personal. Check this tool whenever a request touches a subject this user has cared about before.",
1318
+ "memory-partition-delete": "Delete one exact fact record from the user's fact partition (memory-partition), quoted verbatim from a memory-partition-recall result. Never delete on a guess: recall first, delete the verbatim statement. Needs memory-partition-recall alongside it.",
1319
+ "memory-cognition-remember": "Store one derived insight into your cognition space (memory-cognition) \u2014 your own understanding of the user (inferred traits, standing interests, working nuances, connections between facts). Use for what you LEARN about the user, not what they stated; stated facts belong in memory-partition-remember.",
1320
+ "memory-cognition-forget": "Wipe your entire cognition space (memory-cognition) of the user \u2014 the structured profile AND every derived insight \u2014 only when the user asks you to forget your learned understanding of them or to start over. Fact records in the partition are untouched."
1321
+ };
1322
+ var BROWSER_TOOL_NAMES = [
1323
+ "browser_navigate",
1324
+ "browser_navigate_back",
1325
+ "browser_snapshot",
1326
+ "browser_click",
1327
+ "browser_type",
1328
+ "browser_fill_form",
1329
+ "browser_select_option",
1330
+ "browser_press_key",
1331
+ "browser_wait_for",
1332
+ "browser_take_screenshot",
1333
+ "browser_tabs",
1334
+ "browser_console_messages",
1335
+ "browser_network_requests",
1336
+ "browser_verify_element_visible",
1337
+ "browser_verify_text_visible"
1338
+ ];
1339
+ var TOOL_NAMES = [
1340
+ "webFetch",
1341
+ "brightDataWebSearch",
1342
+ "brightDataImageSearch",
1343
+ "brightDataNewsSearch",
1344
+ "brightDataPlacesSearch",
1345
+ "brightDataShoppingSearch",
1346
+ "brightDataVideoSearch",
1347
+ "brightDataWebpageScrape",
1348
+ "serperWebSearch",
1349
+ "serperImageSearch",
1350
+ "serperNewsSearch",
1351
+ "serperPlacesSearch",
1352
+ "serperShoppingSearch",
1353
+ "serperBusinessReviewsSearch",
1354
+ "serperVideoSearch",
1355
+ "serperWebpageScrape",
1356
+ "youtubeVideoSearch",
1357
+ "eodhdSearch",
1358
+ "eodhdQuote",
1359
+ "eodhdHistory",
1360
+ "eodhdTechnical",
1361
+ "eodhdIntraday",
1362
+ "eodhdNews",
1363
+ "eodhdFundamentals",
1364
+ "requestGrayscale",
1365
+ "requestDenoised",
1366
+ "requestSharpened",
1367
+ "requestClahe",
1368
+ "memory-partition-remember",
1369
+ "memory-partition-recall",
1370
+ "memory-partition-delete",
1371
+ "memory-cognition-remember",
1372
+ "memory-cognition-forget",
1373
+ ...BROWSER_TOOL_NAMES
1374
+ ];
1375
+
2024
1376
  // src/prompts/harness/helpers/tool-catalog.helper.ts
2025
1377
  function formatToolAvailabilityCatalog(enabledToolNames) {
2026
1378
  const enabledSet = new Set(enabledToolNames);
@@ -2032,6 +1384,64 @@ function formatToolAvailabilityCatalog(enabledToolNames) {
2032
1384
  }
2033
1385
  return lines;
2034
1386
  }
1387
+ var DEFAULT_VARIANT_ID = "default";
1388
+ var TEMPLATES = [
1389
+ "article",
1390
+ "news",
1391
+ "describe",
1392
+ "compare",
1393
+ "ocr",
1394
+ "summary",
1395
+ "evaluation",
1396
+ "product",
1397
+ "shoplist",
1398
+ "imagelist",
1399
+ "videolist",
1400
+ "stockmarketitem",
1401
+ "stockmarketlist",
1402
+ "merge",
1403
+ "text"
1404
+ ];
1405
+ var ImagePlanSchema = z.object({
1406
+ resize: z.boolean().default(true).describe(
1407
+ "Whether to resize the original images before sending them to the response model. Default true when images are present."
1408
+ ),
1409
+ variants: z.array(z.enum(VARIANT_NAMES)).default([]).describe(
1410
+ "Optional preprocessing variants to generate for the images (grayscale, denoised, sharpened, clahe). Only use when they would materially improve analysis."
1411
+ )
1412
+ });
1413
+ var IntentSchema = z.object({
1414
+ template: z.enum(TEMPLATES).describe(
1415
+ 'Template name: "article" (in-depth research/report \u2014 extensive long-form composed from available snippets), "news" (current events \u2014 a compact brief composed from available snippets), "describe" (single/multi image description), "compare" (compare uploaded images only \u2014 information comparisons use "evaluation"), "ocr" (extract text from images), "summary" (recap prior conversation or topic without new images), "evaluation" (critique/assess something from the conversation), "product" (product details with shop offers and prices), "shoplist" (compact product/shop list for follow-up shopping questions about an already-introduced product), "imagelist" (a pure collection of images about a topic, no article), "videolist" (a pure list/playlist of videos about a topic, no article), "stockmarketitem" (a single stock/ETF/index with price chart, technicals, news, and a recommendation), "stockmarketlist" (a selection of stocks/indices with a market overview), "text" (free chat).'
1416
+ ),
1417
+ prompt: z.string().default(DEFAULT_VARIANT_ID).describe(
1418
+ 'Selected prompt variant for the template. Use "default" unless the user explicitly asks for a specific style.'
1419
+ ),
1420
+ tools: z.array(z.enum(TOOL_NAMES)).describe("List of tool names the model decided to invoke."),
1421
+ getDate: z.boolean().default(true).describe(
1422
+ 'Whether search queries should be anchored on the current date so results bias toward recent information. Default true. Set to false ONLY for timeless general-knowledge, historical, or conceptual requests where recency does not matter (e.g. "how does photosynthesis work", "history of the Roman Empire", "explain quantum entanglement", creative writing).'
1423
+ ),
1424
+ imageCount: z.preprocess((val) => val === null ? 0 : val, z.number().int().min(0).max(50).default(0)).describe(
1425
+ `Number of images to retrieve when an imageSearch tool is selected. Only set when the user explicitly requests a specific number; otherwise omit or set to 0 and the system will default to ${DEFAULT_MEDIA_COUNT}.`
1426
+ ),
1427
+ videoCount: z.preprocess((val) => val === null ? 0 : val, z.number().int().min(0).max(50).default(0)).describe(
1428
+ `Number of videos to retrieve when a videoSearch tool is selected. Only set when the user explicitly requests a specific number; otherwise omit or set to 0 and the system will default to ${DEFAULT_MEDIA_COUNT}.`
1429
+ ),
1430
+ reasoning: z.string().describe("Short explanation of why this template, prompt, and these tools were chosen."),
1431
+ contextSummary: z.string().default("").describe(
1432
+ "Query-focused extraction of the prior conversation context that the latest user message references or depends on: established topics/entities, key facts from prior answers the follow-up builds on, user constraints, and \u2014 for imagelist/videolist follow-ups \u2014 the previously shown image/video URLs verbatim. Empty if there is no relevant prior context."
1433
+ ),
1434
+ needsClarification: z.boolean().default(false).describe("When true, the request is too ambiguous to classify \u2014 set this instead of picking template/tools."),
1435
+ clarificationQuestion: z.string().nullable().optional().describe(
1436
+ "Concise, human-friendly question to ask the user when needsClarification is true. Ask what the user might have meant and offer the most likely interpretations as options. Do not hardcode wording; adapt tone and language to the user."
1437
+ ),
1438
+ language: z.string().nullable().optional().describe(
1439
+ "Two-letter ISO language code of the latest user message (e.g. 'en', 'de', 'ja', 'es'). Detect from the user's text."
1440
+ ),
1441
+ plan: z.object({
1442
+ images: ImagePlanSchema.optional().describe("Image processing plan. Only present when images are attached.")
1443
+ }).default({}).describe("Execution plan for the response step.")
1444
+ });
2035
1445
 
2036
1446
  // src/prompts/instructions/compare.instruction.ts
2037
1447
  var COMPARE_VERIFICATION_RULE = `When imageSearch tools ran, availableImages holds cloud reference candidates as search-result DATA (titles, snippets, page sources) \u2014 their pixels are never attached to you. Pick a candidate ONLY when its search result corroborates the subject you identified in the uploaded image(s) (a named entity, visible text, a brand, a character, an authoritative source). galleryItems MUST contain ONLY the picked candidates \u2014 NEVER the uploaded user image(s), which are already visible to the user as attachments; every unpicked candidate belongs in discardedReferences with { "type": "image", imageUrl, title, reason } and a one-line reason (e.g. "result covers a different game"). Retrieved links that failed to corroborate the match MAY be listed with { "type": "link", url, title, reason }.`;
@@ -3411,6 +2821,42 @@ FINAL REMINDER:
3411
2821
  function buildCorrectionPrompt(error, template, options) {
3412
2822
  return correctionTemplate(error, template, options?.finalAttempt);
3413
2823
  }
2824
+ var INDENT = " ";
2825
+ function formatZodShape(schema, options = {}) {
2826
+ const jsonSchema = z.toJSONSchema(schema);
2827
+ return renderNode(jsonSchema, 0, options);
2828
+ }
2829
+ function renderNode(node, depth, options) {
2830
+ if (node.type === "object" && node.properties) return renderObject(node, depth, options);
2831
+ if (node.type === "object" && node.additionalProperties) {
2832
+ return `{ [key]: ${renderNode(node.additionalProperties, depth, options)} }`;
2833
+ }
2834
+ if (node.type === "array" && node.items) return `[${renderNode(node.items, depth, options)}]`;
2835
+ const anyOfBranches = node.anyOf?.filter((branch) => branch.type !== "null");
2836
+ if (anyOfBranches?.length) return anyOfBranches.map((branch) => renderNode(branch, depth, options)).join(" | ");
2837
+ if (node.enum) return node.enum.map((value) => JSON.stringify(value)).join(" | ");
2838
+ if (node.type === "integer" || node.type === "number") return "number";
2839
+ if (node.type === "boolean") return "boolean";
2840
+ if (node.type === "string") return "string";
2841
+ if (node.type === "null") return "null";
2842
+ return "unknown";
2843
+ }
2844
+ function renderObject(node, depth, options) {
2845
+ const properties = node.properties ?? {};
2846
+ const required = new Set(node.required ?? []);
2847
+ const entries = Object.entries(properties);
2848
+ if (entries.length === 0) return "{}";
2849
+ const pad = INDENT.repeat(depth);
2850
+ const innerPad = INDENT.repeat(depth + 1);
2851
+ const lines = entries.map(([key, value]) => {
2852
+ const optional = required.has(key) ? "" : "?";
2853
+ const hint = options.overrides?.[key] ?? renderNode(value, depth + 1, options);
2854
+ return `${innerPad}${JSON.stringify(key)}${optional}: ${hint}`;
2855
+ });
2856
+ return `{
2857
+ ${lines.join(",\n")}
2858
+ ${pad}}`;
2859
+ }
3414
2860
 
3415
2861
  // src/prompts/helpers/build-structured-prompt.helper.ts
3416
2862
  function buildStructuredPrompt(schema, template, options) {
@@ -3478,42 +2924,71 @@ Ensure the "language" field is present and has the correct value.
3478
2924
  FINAL REMINDER:
3479
2925
  - Return ONLY a single valid JSON object with all required keys, including "language". No markdown code fences, no explanations, no extra text.`;
3480
2926
 
3481
- // src/prompts/memory/belief-synthesis-prompt.constant.ts
3482
- var BELIEF_INSTRUCTIONS = `You synthesize higher-level beliefs from a user's memory facts.
2927
+ // src/prompts/memory/format-fact-metadata.helper.ts
2928
+ function formatFactMetadata(fact) {
2929
+ const parts = [
2930
+ fact.subject ? `subject: ${fact.subject}` : "",
2931
+ fact.category ? `category: ${fact.category}` : "",
2932
+ fact.kind ? `kind: ${fact.kind}` : "",
2933
+ fact.stability ? `stability: ${fact.stability}` : ""
2934
+ ].filter(Boolean);
2935
+ return parts.length ? ` (${parts.join("; ")})` : "";
2936
+ }
2937
+
2938
+ // src/prompts/memory/conviction-synthesis-prompt.constant.ts
2939
+ var CONVICTION_INSTRUCTIONS = `You synthesize higher-level convictions and bridges from a user's memory facts.
3483
2940
 
3484
- Given a numbered list of EVIDENCE (facts the user stated or asked to remember), synthesize durable, higher-level beliefs \u2014 conclusions that are true of the evidence but not merely restated from it. Every belief picks its lane ("target"), and the choice is PURPOSE-based, never cosmetic:
2941
+ Given a numbered list of EVIDENCE (facts the user stated or asked to remember), synthesize durable, higher-level statements \u2014 conclusions that are true of the evidence but not merely restated from it. Every statement picks its lane ("target"), and the choice is PURPOSE-based, never cosmetic:
3485
2942
 
3486
2943
  - "conviction" \u2014 a durable conclusion about THE USER (or your own working relationship with them): a pattern in their behavior, a standing judgment the facts support, a trait or tendency you now hold true. Convictions deepen your understanding of the user and yourself; they are YOURS, never statements the user made. ("the user bought a dog" + "the user keeps comparing dog food brands" + "the user researches every purchase for days" \u2192 conviction: "the user is a deliberate, research-first buyer")
3487
2944
  - "bridge" \u2014 a synthesized claim that CLOSES A GAP between facts: without it, two stored facts look unrelated; with it, they form one coherent story about the user's world. Bridges are the connective tissue of the fact graph. ("the user bought dog food X" + "the user's dog refuses to eat" \u2192 bridge: "the dog refuses the brand-X food the user bought")
3488
2945
 
3489
- A belief is a synthesis, not a restatement:
2946
+ A synthesized statement is a synthesis, not a restatement:
3490
2947
  - combine multiple facts into one conclusion ("learning Rust" + "rewriting the payments service" \u2192 "the user is migrating the payments service to Rust")
3491
- - a belief MUST cite the evidence indices that support it (the [n] numbers)
2948
+ - every statement MUST cite the evidence indices that support it (the [n] numbers)
3492
2949
  - never invent facts not present in the evidence
3493
- - never restate a single evidence item verbatim as a belief \u2014 that is not synthesis
3494
- - a belief with no supporting evidence is invalid \u2014 omit it
3495
- - when neither a conviction nor a bridge clearly fits, emit nothing for it \u2014 a forced target blurs the lanes and is worse than no belief
2950
+ - never restate a single evidence item verbatim \u2014 that is not synthesis
2951
+ - a statement with no supporting evidence is invalid \u2014 omit it
2952
+ - when neither a conviction nor a bridge clearly fits, emit nothing for it \u2014 a forced target blurs the lanes and is worse than no statement
2953
+
2954
+ EVIDENCE METADATA \u2014 each line may show "(subject: \u2026; category: \u2026; kind: \u2026; stability: \u2026)":
2955
+ - subject groups the evidence lines about one entity \u2014 convictions about a subject cite that subject's lines
2956
+ - durable evidence carries more weight for a conviction; a volatile state alone rarely supports a durable conclusion
2957
+ - kind sharpens conclusions: a pattern of "preference" evidence supports a taste conclusion; "decision" evidence supports a commitment conclusion
3496
2958
 
3497
2959
  Respond with JSON only:
3498
2960
  {
3499
- "beliefs": [
2961
+ "convictions": [
3500
2962
  { "text": "one self-contained sentence", "target": "conviction", "evidence": [0, 2, 5] }
3501
2963
  ]
3502
2964
  }
3503
2965
 
3504
2966
  Rules:
3505
- - target is exactly "conviction" or "bridge" \u2014 every belief carries one
2967
+ - target is exactly "conviction" or "bridge" \u2014 every statement carries one
3506
2968
  - evidence indices are the [n] numbers from the EVIDENCE list \u2014 never invent an index
3507
- - return an empty beliefs array when the evidence is too thin to synthesize anything
3508
- - keep each belief to one sentence`;
3509
- function buildBeliefSynthesisPrompt(evidence) {
2969
+ - return an empty convictions array when the evidence is too thin to synthesize anything
2970
+ - keep each statement to one sentence`;
2971
+ function buildConvictionSynthesisPrompt(evidence) {
3510
2972
  const lines = ["EVIDENCE (each item is a fact the user stated or asked to remember):"];
3511
2973
  for (const [index, item] of evidence.entries()) {
3512
- const category = item.category ? ` (category: ${item.category})` : "";
3513
- lines.push(`[${index}] ${item.text}${category}`);
2974
+ lines.push(`[${index}] ${item.text}${formatFactMetadata(item)}`);
3514
2975
  }
3515
2976
  return lines.join("\n");
3516
2977
  }
2978
+ var EncyclopediaClassifySchema = z.object({
2979
+ /**
2980
+ * One broad lowercase PLURAL family label for the document (e.g. `games`,
2981
+ * `work`, `health`) — the constellation's cluster tier. Never a specific
2982
+ * entity, product, company, or title.
2983
+ */
2984
+ category: z.string(),
2985
+ /**
2986
+ * The narrow topic the document is about (e.g. `wuthering waves`,
2987
+ * `q3 budget`) — the constellation's cluster tier. A short, specific,
2988
+ * reusable label, not a sentence and not a URL.
2989
+ */
2990
+ topic: z.string()
2991
+ });
3517
2992
 
3518
2993
  // src/prompts/memory/vocabulary-section.helper.ts
3519
2994
  function buildVocabularySection(knownCategories = [], knownTags = []) {
@@ -3531,15 +3006,16 @@ function buildVocabularySection(knownCategories = [], knownTags = []) {
3531
3006
  }
3532
3007
 
3533
3008
  // src/prompts/memory/encyclopedia-classify-prompt.constant.ts
3534
- function buildEncyclopediaClassifyPrompt(knownCategories = []) {
3009
+ function buildEncyclopediaClassifyPrompt(knownCategories = [], knownTopics = []) {
3535
3010
  return buildStructuredPrompt(EncyclopediaClassifySchema, {
3536
3011
  before: "OUTPUT FORMAT \u2014 output ONLY valid JSON matching this exact schema:",
3537
3012
  after: `
3538
3013
  YOUR TASK \u2014 label one stored source document with its broad category and the topic it is about:
3539
3014
  - The document may be a fetched web page, an uploaded file, or a search-result snippet. Classify by its CONTENT, never by its source shape \u2014 a file has no domain, and that must not matter.
3540
3015
  - category: ONE broad lowercase PLURAL family noun (e.g. "games", "work", "health", "finance") that groups the topic into a family. Never a specific entity, product, company, or title.
3541
- - topic: the narrow subject the document is about (e.g. "wuthering waves", "q3 budget", "rust borrow checker"). A short, specific, reusable label \u2014 not a sentence, not a URL, not a filename.
3542
- ${buildVocabularySection(knownCategories)}
3016
+ - topic: the narrow subject the document is about (e.g. "wuthering waves", "q3 budget", "rust borrow checker"). A short, specific, reusable label \u2014 not a sentence, not a URL, not a filename, never a domain or a site name.
3017
+ - When the document is about a KNOWN TOPIC, output that label VERBATIM \u2014 prefer "neverness to everness" over minting a variant like "nte".
3018
+ ${buildVocabularySection(knownCategories, knownTopics)}
3543
3019
  RULES:
3544
3020
  - Return ONLY a single valid JSON object matching the exact schema above.
3545
3021
  - No markdown code fences, no explanations, preamble, or postscript.
@@ -3565,6 +3041,12 @@ NOT a contradiction:
3565
3041
  - different subjects or unrelated facts
3566
3042
  - a more specific statement that does not actually conflict with a general one
3567
3043
 
3044
+ METADATA \u2014 each record may show "(subject: \u2026; category: \u2026; kind: \u2026; stability: \u2026)":
3045
+ - subject: a contradiction requires the SAME subject. Candidates about a different subject are never conflicts, however similar they read.
3046
+ - stability: "volatile" records describe a current state \u2014 a NEWER volatile statement supersedes the older one (name the newer as winner). "durable" statements rarely supersede each other: when neither side is clearly right, omit winnerId so the conflict stays open.
3047
+ - kind: polarity flips are the expected contradiction for "preference" and "relationship" records. For "fact", "project", "contact" and "possession" records a conflict means one statement is outdated or wrong \u2014 prefer the newer statement when the dates say which is current.
3048
+ - category: conflicts almost always live inside one category family; treat a candidate from a clearly different family skeptically, but never dismiss it on category alone.
3049
+
3568
3050
  Respond with JSON only:
3569
3051
  {
3570
3052
  "contradicts": boolean,
@@ -3578,7 +3060,7 @@ Rules:
3578
3060
  - Never invent a conflictingId or winnerId that is not in the input.`;
3579
3061
  function buildFrictionPrompt(input) {
3580
3062
  const lines = [];
3581
- lines.push(`RECORD (id: ${input.record.id}):`);
3063
+ lines.push(`RECORD (id: ${input.record.id})${formatFactMetadata(input.record)}:`);
3582
3064
  lines.push(input.record.text);
3583
3065
  if (input.record.createdAt) {
3584
3066
  lines.push(`(created: ${input.record.createdAt})`);
@@ -3589,7 +3071,7 @@ function buildFrictionPrompt(input) {
3589
3071
  lines.push("(none)");
3590
3072
  } else {
3591
3073
  for (const candidate of input.candidates) {
3592
- lines.push(`- id: ${candidate.id}`);
3074
+ lines.push(`- id: ${candidate.id}${formatFactMetadata(candidate)}`);
3593
3075
  lines.push(` text: ${candidate.text}`);
3594
3076
  if (candidate.createdAt) {
3595
3077
  lines.push(` created: ${candidate.createdAt}`);
@@ -3598,6 +3080,14 @@ function buildFrictionPrompt(input) {
3598
3080
  }
3599
3081
  return lines.join("\n");
3600
3082
  }
3083
+ var MemoryClusterSummarySchema = z.object({
3084
+ title: z.string().describe('A short noun-phrase label for the cluster (2\u20136 words), e.g. "gaming preferences" or "work projects".'),
3085
+ summary: z.string().describe(
3086
+ "One or two sentences summarizing what this cluster of memories is about \u2014 the shared theme, the key facts, and how they relate."
3087
+ )
3088
+ });
3089
+
3090
+ // src/prompts/memory/memory-cluster-prompt.constant.ts
3601
3091
  var MEMORY_CLUSTER_INSTRUCTIONS = buildStructuredPrompt(MemoryClusterSummarySchema, {
3602
3092
  before: `MEMORY CLUSTER SUMMARY \u2014 one purpose: write a short title + summary for ONE cluster of related memory records, so the cluster can be recalled as a whole without reading every member.
3603
3093
 
@@ -3623,6 +3113,12 @@ ${limitText(members, params.maxPayloadChars)}`,
3623
3113
  "Write the title + summary JSON object now."
3624
3114
  ].join("\n\n");
3625
3115
  }
3116
+ var ConsolidationVerdictSchema = z.object({
3117
+ verdict: z.enum(["keep", "redundant", "merge"]).describe(
3118
+ "keep = the new fact adds information not covered by the candidates; redundant = fully covered already (same claim, no new detail, same polarity); merge = it refines/corrects/completes a candidate and mergedText carries the fuller statement."
3119
+ ),
3120
+ mergedText: z.string().optional().describe("Required with verdict=merge: one fuller self-contained statement (full restatement, never a diff).")
3121
+ });
3626
3122
 
3627
3123
  // src/prompts/memory/memory-consolidate-prompt.constant.ts
3628
3124
  var MEMORY_CONSOLIDATE_INSTRUCTIONS = buildStructuredPrompt(ConsolidationVerdictSchema, {
@@ -3640,19 +3136,31 @@ Decide exactly one verdict:
3640
3136
  POLARITY RULE (ABSOLUTE): a flip or addition of negation ("allergic" vs "not allergic", "likes" vs "dislikes") is NEW information \u2014 it is never "redundant"; if it corrects a stored statement, merge with the corrected statement.
3641
3137
  PROVENANCE RULE (ABSOLUTE): a statement the user made outweighs assistant-derived wording \u2014 mergedText preserves the user's claim; when candidate origins conflict on facts, the user's version wins.
3642
3138
 
3139
+ METADATA \u2014 each record may show "(subject: \u2026; category: \u2026; kind: \u2026; stability: \u2026)":
3140
+ - subject: merge requires the SAME subject; facts about different subjects are always "keep".
3141
+ - kind: two "contact" or "state" records about the same subject usually merge into the fuller one. Two "decision" or "preference" records with different content are different facts \u2014 keep both.
3142
+ - stability: a newer "volatile" state normally replaces the older record via merge.
3143
+
3643
3144
  OUTPUT FORMAT \u2014 output ONLY valid JSON:`,
3644
3145
  after: 'No markdown fences, no explanations. mergedText is required with verdict "merge" and omitted otherwise.'
3645
3146
  });
3646
3147
  function buildConsolidatePrompt(params) {
3647
- const candidates = params.candidates.length ? params.candidates.map((c) => `- ${formatProvenanceLine(c)}`).join("\n") : "(none)";
3148
+ const candidates = params.candidates.length ? params.candidates.map((c) => `- ${formatProvenanceLine(c)}${formatFactMetadata(c)}`).join("\n") : "(none)";
3648
3149
  return [
3649
3150
  `NEW FACT:
3650
- - ${formatProvenanceLine(params.newFact)}`,
3151
+ - ${formatProvenanceLine(params.newFact)}${formatFactMetadata(params.newFact)}`,
3651
3152
  `EXISTING CANDIDATES:
3652
3153
  ${candidates}`,
3653
3154
  "Decide exactly one verdict (keep / redundant / merge) and output ONLY the JSON object."
3654
3155
  ].join("\n\n");
3655
3156
  }
3157
+ var MemoryEnrichmentSchema = z.object({
3158
+ /**
3159
+ * 2–6 stable, reusable lowercase topic labels for the record — the existing
3160
+ * tags kept plus any missing labels that would help topic-filtered recall.
3161
+ */
3162
+ tags: z.array(z.string())
3163
+ });
3656
3164
 
3657
3165
  // src/prompts/memory/memory-enrich-prompt.constant.ts
3658
3166
  function buildEnrichPrompt() {
@@ -3674,6 +3182,89 @@ FINAL REMINDER:
3674
3182
  - Output ONLY valid JSON matching the exact schema above. No markdown code fences, no explanations, preamble, or postscript.`
3675
3183
  });
3676
3184
  }
3185
+ var nullishText = z.string().nullish();
3186
+ var nullishTopics = z.array(z.string()).nullish();
3187
+ var memoryCognitionProfileSchema = z.object({
3188
+ /** The user's name or preferred handle, when known. */
3189
+ name: nullishText,
3190
+ /** Primary conversation language (BCP-47-ish, e.g. "en", "de"). */
3191
+ language: nullishText,
3192
+ /** Coarse location the user works from (IANA name or city), when known. */
3193
+ timezone: nullishText,
3194
+ /** Demonstrated skills and domains (e.g. ["TypeScript", "NestJS"]). */
3195
+ expertise: nullishTopics,
3196
+ /** Active goals and aspirations — the most durable cognition there is. */
3197
+ goals: nullishTopics,
3198
+ /** How the user wants to be answered. */
3199
+ communication: z.object({
3200
+ style: nullishText,
3201
+ detailLevel: nullishText,
3202
+ formality: nullishText
3203
+ }).nullish(),
3204
+ /** Tooling / environment / format preferences (free-form key-value). */
3205
+ preferences: z.record(z.string(), z.string()).nullish(),
3206
+ likes: nullishTopics,
3207
+ dislikes: nullishTopics,
3208
+ /** Topics the user keeps returning to. */
3209
+ interests: nullishTopics,
3210
+ /**
3211
+ * Durable CONVICTIONS the AI synthesized about the user's world — the
3212
+ * routing-map topics of its conviction records (depth lives in the
3213
+ * conviction points at paths `convictions.<slug>`). Convictions are the
3214
+ * AI's own derived conclusions, never statements the user made.
3215
+ */
3216
+ convictions: nullishTopics,
3217
+ /** The AI's own identity as the user has shaped it (name, role, voice). */
3218
+ persona: z.object({
3219
+ /** The name the user gave the AI. */
3220
+ name: nullishText,
3221
+ /** The role the user assigned the AI (e.g. "coding assistant"). */
3222
+ role: nullishText,
3223
+ /** Short character description the user gave the AI. */
3224
+ personality: nullishText,
3225
+ /** How the AI introduces itself, when the user set one. */
3226
+ greeting: nullishText,
3227
+ /** How the AI should speak. */
3228
+ voice: z.object({
3229
+ tone: nullishText,
3230
+ formality: nullishText
3231
+ }).nullish()
3232
+ }).nullish(),
3233
+ /**
3234
+ * Learned corrections — behavioral rules the user taught the AI after it
3235
+ * got something wrong. Keyed by a short slug (a stable handle for
3236
+ * update/remove); the value is the imperative directive ("always …" /
3237
+ * "never …"), optionally with a brief why. Deep-merged like preferences.
3238
+ */
3239
+ corrections: z.record(z.string(), z.string()).nullish()
3240
+ });
3241
+ var memoryProfileInsightSchema = z.object({
3242
+ text: z.string(),
3243
+ path: z.string().optional()
3244
+ });
3245
+ var memoryProfileResponseSchema = z.preprocess(
3246
+ (value) => {
3247
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
3248
+ const record = value;
3249
+ if (record.insights == null) record.insights = [];
3250
+ else if (Array.isArray(record.insights)) {
3251
+ record.insights = record.insights.map((item) => typeof item === "string" ? { text: item } : item);
3252
+ }
3253
+ return record;
3254
+ },
3255
+ z.object({
3256
+ profile: memoryCognitionProfileSchema.nullish(),
3257
+ insights: z.array(memoryProfileInsightSchema).max(8),
3258
+ /**
3259
+ * One sentence recording what THIS turn was about — the short-term
3260
+ * conversation memory (episode), recalled later by recency-blended probe.
3261
+ * Omitted (or null) when the turn had no substance.
3262
+ */
3263
+ episode: z.string().nullish()
3264
+ })
3265
+ );
3266
+
3267
+ // src/prompts/memory/memory-profile-prompt.constant.ts
3677
3268
  var MEMORY_PROFILE_INSTRUCTIONS = buildStructuredPrompt(memoryProfileResponseSchema, {
3678
3269
  before: `COGNITION JOB \u2014 maintain YOUR evolving understanding of THIS user.
3679
3270
 
@@ -3797,6 +3388,66 @@ function buildMemoryWritePrompt(params) {
3797
3388
  ].filter(Boolean).join("\n\n");
3798
3389
  }
3799
3390
  var MEMORY_WRITE_VERDICT = 'Decide: store each durable user-specific fact with one memory-partition-remember call, each derived understanding with one memory-cognition-remember call, or answer "none" if the turn surfaced nothing durable about this user.';
3391
+ var FACT_KINDS = [
3392
+ "preference",
3393
+ "decision",
3394
+ "state",
3395
+ "contact",
3396
+ "project",
3397
+ "possession",
3398
+ "relationship",
3399
+ "fact"
3400
+ ];
3401
+ var FACT_STABILITIES = ["durable", "volatile"];
3402
+ var ExtractedFactSchema = z.object({
3403
+ /**
3404
+ * The self-contained durable statement — third person, subject up front,
3405
+ * no "this"/"that" references.
3406
+ */
3407
+ text: z.string(),
3408
+ /**
3409
+ * The lowercase entity the fact is about (default `user`; a person,
3410
+ * product, or project name). Maintenance adjudication only ever compares
3411
+ * facts about the SAME subject.
3412
+ */
3413
+ subject: z.string().optional(),
3414
+ /**
3415
+ * One broad lowercase PLURAL family label for THIS fact (e.g. `stocks`,
3416
+ * `pets`, `games`) — inherits the turn-side category when omitted. Never
3417
+ * a specific entity, product, company, or game title.
3418
+ */
3419
+ category: z.string().optional(),
3420
+ /** What kind of durable thing this is (see FACT_KINDS). */
3421
+ kind: z.enum(FACT_KINDS),
3422
+ /** Whether a newer statement is expected to replace this one (see FACT_STABILITIES). */
3423
+ stability: z.enum(FACT_STABILITIES)
3424
+ });
3425
+ var ExtractionSchema = z.object({
3426
+ /**
3427
+ * Durable, self-contained facts worth remembering in a later, unrelated
3428
+ * conversation (preferences, decisions, contact details, project facts),
3429
+ * each carrying its maintenance metadata (subject, category, kind,
3430
+ * stability). Empty when nothing in the text is worth remembering.
3431
+ */
3432
+ facts: z.array(ExtractedFactSchema),
3433
+ /**
3434
+ * 2–6 stable, reusable lowercase topic labels describing the text; the open
3435
+ * vocabulary that powers topic-filtered recall. Tags are NARROW and
3436
+ * specific — entity names, product names, game titles (e.g. `amd`,
3437
+ * `stellar blade`, `stellar blade blood rain`).
3438
+ */
3439
+ tags: z.array(z.string()),
3440
+ /**
3441
+ * One broad lowercase PLURAL family label for the whole turn-side (e.g.
3442
+ * `stocks`, `pets`, `games`) — groups the narrow tags into one topic family
3443
+ * for the constellation's community tier and the relink job's per-category
3444
+ * passes, and backstops facts that omit their own `category`. Never a
3445
+ * specific entity, product, company, or game title: `amd` belongs under
3446
+ * `stocks`; `stellar blade` belongs under `games`. Optional: a turn with
3447
+ * nothing durable may omit it.
3448
+ */
3449
+ category: z.string().optional()
3450
+ });
3800
3451
 
3801
3452
  // src/prompts/memory/vectorize-prompt.constant.ts
3802
3453
  function buildExtractionPrompt(knownCategories = [], knownTags = []) {
@@ -3809,6 +3460,21 @@ YOUR TASK \u2014 decide what is worth remembering:
3809
3460
  - Facts must be self-contained \u2014 no "this"/"that" references; write them as third-person statements.
3810
3461
  - Storage mechanics: each fact is embedded as a whole and matched sentence-by-sentence at recall time (multi-variant retrieval). One dense sentence is fine \u2014 put the subject up front ("User prefers single-line if statements", not "They prefer that style").
3811
3462
  - Skip transient content: greetings, small talk, one-off instructions, filler \u2014 anything with no future recall value. When in doubt about whether a detail is durable, keep it: a durable detail is cheaper to store than to lose.
3463
+
3464
+ FACT METADATA \u2014 every fact object carries the fields the maintenance passes (consolidate/reflect/conviction) interpret:
3465
+ - text: the statement itself.
3466
+ - subject (optional): the lowercase entity the fact is about \u2014 "user" by default, or a person, product, or project name ("sam", "stellar blade", "payments service"). Maintenance only ever compares facts about the SAME subject, so name it whenever the fact is about a specific entity.
3467
+ - category (optional): ONE broad lowercase PLURAL family label for this fact, reusing the known vocabulary \u2014 inherits the turn-side category when omitted.
3468
+ - kind (required): what kind of durable thing it is:
3469
+ - preference \u2014 likes, dislikes, wants, style choices
3470
+ - decision \u2014 a choice that was made (adoptions, migrations, purchases committed to)
3471
+ - state \u2014 the CURRENT, changeable situation (lives in X, uses version Y, runs Z) \u2014 newer statements supersede these
3472
+ - contact \u2014 contact details of a person (phone, email, address)
3473
+ - project \u2014 facts about ongoing work or projects
3474
+ - possession \u2014 things owned
3475
+ - relationship \u2014 how people relate ("sam is the user's brother")
3476
+ - fact \u2014 any other durable fact
3477
+ - stability (required): "durable" \u2014 a long-term truth that should survive until contradicted (decisions, traits, history) \u2014 or "volatile" \u2014 a current state a newer statement is EXPECTED to replace (location, tooling, versions). When in doubt, choose durable.
3812
3478
  - Tags: 2 to 6 stable, reusable, lowercase topic labels describing what the text is about (e.g. "work", "rust", "contacts", "amd", "stellar blade"). Tags are NARROW and specific \u2014 entity names, product names, game titles. They are the vocabulary for topic-filtered recall later.
3813
3479
  - Category: ONE broad lowercase PLURAL family noun for the whole text (e.g. "stocks", "pets", "games", "health") that groups the narrow tags into one topic family. A category is NEVER a specific entity, product, company, or game title: "amd" belongs under "stocks"; "stellar blade" and "stellar blade blood rain" belong under "games". Always include it when facts are emitted; omit it when nothing durable is found.
3814
3480
  - If nothing durable is found, return an empty facts array; tags may still label the topic when useful.
@@ -3833,8 +3499,8 @@ function buildExtractionCorrectionPrompt(error) {
3833
3499
  Error: ${error}
3834
3500
 
3835
3501
  Return ONLY a single valid JSON object matching the extraction schema exactly:
3836
- {"facts": [string, ...], "tags": [string, ...], "category": "string"}
3837
- All object keys must be quoted with double quotes.
3502
+ {"facts": [{"text": "string", "subject": "string", "category": "string", "kind": "preference|decision|state|contact|project|possession|relationship|fact", "stability": "durable|volatile"}, ...], "tags": [string, ...], "category": "string"}
3503
+ All object keys must be quoted with double quotes. "subject" and the per-fact "category" are optional; "kind" and "stability" are required on every fact.
3838
3504
  Do not add markdown code fences, explanations, or extra text.
3839
3505
 
3840
3506
  FINAL REMINDER:
@@ -3868,4 +3534,4 @@ Compose the response from the snippets below. Include every snippet you can subs
3868
3534
  ${parts.join("\n\n")}`;
3869
3535
  }
3870
3536
 
3871
- export { BELIEF_INSTRUCTIONS, COMMONMARK_FORMAT, COMPARE_INSTRUCTIONS, COMPARE_VISUAL_INSTRUCTIONS, DEFAULT_VARIANT_ID, DESCRIBE_CONCISE_INSTRUCTIONS, DESCRIBE_DETAILED_INSTRUCTIONS, DESCRIBE_INSTRUCTIONS, FINAL_REMINDER, FRICTION_INSTRUCTIONS, HISTORY_URLS_RULES, IMAGELIST_INSTRUCTIONS, IMAGE_TASK_RULE, INTERNATIONAL_COVERAGE_INSTRUCTIONS, ITEM_SHAPES, JSON_RULES, MEDIA_COUNTS, MEDIA_RULES, MEMORY_CLUSTER_INSTRUCTIONS, MEMORY_CONSOLIDATE_INSTRUCTIONS, MEMORY_PROFILE_INSTRUCTIONS, MEMORY_WRITE_INSTRUCTIONS, MERGE_MEDIA_RULES, MERGE_TOPIC_RULE, MULTIMODAL_POLICY, NOISE_RULES, OCR_INSTRUCTIONS, OCR_VERBATIM_INSTRUCTIONS, PRECEDENCE_RULES, PRODUCT_INSTRUCTIONS, RESPONSE_LAYOUTS, SECURITY_RULES, SHOPLIST_INSTRUCTIONS, SNIPPET_TEMPLATE_PRESETS, SOURCE_TRUTH_RULES, SOURCE_VOICE_RULES, STOCKMARKET_ITEM_INSTRUCTIONS, STOCKMARKET_LIST_INSTRUCTIONS, SUMMARY_INSTRUCTIONS, TEMPLATE_VARIANTS, TEXT_CODING_INSTRUCTIONS, TEXT_FAMILIARITY_INSTRUCTIONS, TEXT_INSTRUCTIONS, TOOL_RESULTS_RULES, VIDEOLIST_INSTRUCTIONS, articlePreset, assessmentListsSnippet, authorMetaSnippet, bodyBriefSnippet, bodyExtensiveSnippet, buildBaseSystemPrompt, buildBeliefSynthesisPrompt, buildClarificationTranslationSystemPrompt, buildClarificationTranslationUserPrompt, buildClassifyTranscript, buildClusterSummaryPrompt, buildConsolidatePrompt, buildContentSystemPrompt, buildContextSummarySection, buildCorrectionPrompt, buildEncyclopediaClassifyPrompt, buildEnrichPrompt, buildExecuteLanguageInstruction, buildExtractionCorrectionPrompt, buildExtractionPrompt, buildFrictionPrompt, buildImageExecutePrompt, buildIntentCorrectionPrompt, buildIntentSelectionPrompt, buildLanguageRule, buildLayoutInstruction, buildLocalizationRule, buildMemoryProbeSection, buildMemoryProfilePrompt, buildMemoryWritePrompt, buildMergeDirective, buildMissingToolsPrompt, buildOutputContract, buildSnippetInstruction, buildSourcePolicyPrompt, buildStockmarketNote, buildStructuredJsonPrompt, buildStructuredPrompt, buildToolExecutePrompt, buildVocabularySection, bylineDatelineSnippet, cardsSnippet, comparisonSchema, comparisonSnippet, composeSnippetKeys, composeSnippetSchema, conclusionSnippet, evaluationPreset, formatCurrentTimestamp, formatProvenanceLine, formatToolAvailabilityCatalog, formatVariantCatalog, gallerySnippet, getSnippetTemplateKeys, getSnippetTemplateSchema, headerArticleSnippet, headerNewsSnippet, heroMediaSnippet, internationalCoverageSnippet, introductionSnippet, isSnippetTemplate, keyFindingsSnippet, languageCorrectionPrompt, leadSnippet, mergePreset, mergedEvaluationsSnippet, newsKeyFindingsSnippet, newsPreset, quoteSnippet, reasoningSnippet, relatedStoriesSnippet, resolveLanguageName, resolveVariantInstructions, responseLayoutSchema, sourcesSnippet, subjectSchema, subjectsSnippet, summarySnippet, verdictSpineSnippet, videoGallerySnippet };
3537
+ export { COMMONMARK_FORMAT, COMPARE_INSTRUCTIONS, COMPARE_VISUAL_INSTRUCTIONS, CONVICTION_INSTRUCTIONS, DEFAULT_VARIANT_ID, DESCRIBE_CONCISE_INSTRUCTIONS, DESCRIBE_DETAILED_INSTRUCTIONS, DESCRIBE_INSTRUCTIONS, FINAL_REMINDER, FRICTION_INSTRUCTIONS, HISTORY_URLS_RULES, IMAGELIST_INSTRUCTIONS, IMAGE_TASK_RULE, INTERNATIONAL_COVERAGE_INSTRUCTIONS, ITEM_SHAPES, JSON_RULES, MEDIA_COUNTS, MEDIA_RULES, MEMORY_CLUSTER_INSTRUCTIONS, MEMORY_CONSOLIDATE_INSTRUCTIONS, MEMORY_PROFILE_INSTRUCTIONS, MEMORY_WRITE_INSTRUCTIONS, MERGE_MEDIA_RULES, MERGE_TOPIC_RULE, MULTIMODAL_POLICY, NOISE_RULES, OCR_INSTRUCTIONS, OCR_VERBATIM_INSTRUCTIONS, PRECEDENCE_RULES, PRODUCT_INSTRUCTIONS, RESPONSE_LAYOUTS, SECURITY_RULES, SHOPLIST_INSTRUCTIONS, SNIPPET_TEMPLATE_PRESETS, SOURCE_TRUTH_RULES, SOURCE_VOICE_RULES, STOCKMARKET_ITEM_INSTRUCTIONS, STOCKMARKET_LIST_INSTRUCTIONS, SUMMARY_INSTRUCTIONS, TEMPLATE_VARIANTS, TEXT_CODING_INSTRUCTIONS, TEXT_FAMILIARITY_INSTRUCTIONS, TEXT_INSTRUCTIONS, TOOL_RESULTS_RULES, VIDEOLIST_INSTRUCTIONS, articlePreset, assessmentListsSnippet, authorMetaSnippet, bodyBriefSnippet, bodyExtensiveSnippet, buildBaseSystemPrompt, buildClarificationTranslationSystemPrompt, buildClarificationTranslationUserPrompt, buildClassifyTranscript, buildClusterSummaryPrompt, buildConsolidatePrompt, buildContentSystemPrompt, buildContextSummarySection, buildConvictionSynthesisPrompt, buildCorrectionPrompt, buildEncyclopediaClassifyPrompt, buildEnrichPrompt, buildExecuteLanguageInstruction, buildExtractionCorrectionPrompt, buildExtractionPrompt, buildFrictionPrompt, buildImageExecutePrompt, buildIntentCorrectionPrompt, buildIntentSelectionPrompt, buildLanguageRule, buildLayoutInstruction, buildLocalizationRule, buildMemoryProbeSection, buildMemoryProfilePrompt, buildMemoryWritePrompt, buildMergeDirective, buildMissingToolsPrompt, buildOutputContract, buildSnippetInstruction, buildSourcePolicyPrompt, buildStockmarketNote, buildStructuredJsonPrompt, buildStructuredPrompt, buildToolExecutePrompt, buildVocabularySection, bylineDatelineSnippet, cardsSnippet, comparisonSchema, comparisonSnippet, composeSnippetKeys, composeSnippetSchema, conclusionSnippet, evaluationPreset, formatCurrentTimestamp, formatProvenanceLine, formatToolAvailabilityCatalog, formatVariantCatalog, gallerySnippet, getSnippetTemplateKeys, getSnippetTemplateSchema, headerArticleSnippet, headerNewsSnippet, heroMediaSnippet, internationalCoverageSnippet, introductionSnippet, isSnippetTemplate, keyFindingsSnippet, languageCorrectionPrompt, leadSnippet, mergePreset, mergedEvaluationsSnippet, newsKeyFindingsSnippet, newsPreset, quoteSnippet, reasoningSnippet, relatedStoriesSnippet, resolveLanguageName, resolveVariantInstructions, responseLayoutSchema, sourcesSnippet, subjectSchema, subjectsSnippet, summarySnippet, verdictSpineSnippet, videoGallerySnippet };