@triplef/agent 0.1.5 → 0.1.7

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.
@@ -78,8 +78,8 @@ declare const IntentSchema: z.ZodObject<{
78
78
  "memory-cognition-forget": "memory-cognition-forget";
79
79
  }>>;
80
80
  getDate: z.ZodDefault<z.ZodBoolean>;
81
- imageCount: z.ZodPreprocess<z.ZodDefault<z.ZodNumber>>;
82
- videoCount: z.ZodPreprocess<z.ZodDefault<z.ZodNumber>>;
81
+ imageCount: z.ZodPreprocess<z.ZodDefault<z.ZodNumber>, unknown>;
82
+ videoCount: z.ZodPreprocess<z.ZodDefault<z.ZodNumber>, unknown>;
83
83
  reasoning: z.ZodString;
84
84
  contextSummary: z.ZodDefault<z.ZodString>;
85
85
  needsClarification: z.ZodDefault<z.ZodBoolean>;
@@ -1,5 +1,5 @@
1
- import { T as TemplateName, F as FormatZodShapeOptions } from '../intent.schema-CE2F1zLH.js';
2
- export { D as DEFAULT_VARIANT_ID } from '../intent.schema-CE2F1zLH.js';
1
+ import { T as TemplateName, F as FormatZodShapeOptions } from '../intent.schema-DnxaUhmz.js';
2
+ export { D as DEFAULT_VARIANT_ID } from '../intent.schema-DnxaUhmz.js';
3
3
  import * as zod from 'zod';
4
4
  import { z, ZodTypeAny, ZodType } from 'zod';
5
5
  import * as zod_v4_core from 'zod/v4/core';
@@ -109,7 +109,7 @@ declare function buildMemoryProfilePrompt(params: {
109
109
  maxPayloadChars?: number;
110
110
  }): string;
111
111
 
112
- declare const MEMORY_WRITE_INSTRUCTIONS = "MEMORY WRITE JOB \u2014 one purpose: decide whether THIS turn yielded anything worth persisting, and store it in the correct lane via the memory-partition-remember or memory-cognition-remember tool.\n\nTwo lanes, never confused:\n- memory-partition = the user's OWN statements (facts they stated or asked you to remember).\n- memory-cognition = YOUR derived understanding of the user (inferred traits, standing interests, connections).\n\nYou receive:\n- USER REQUEST: what the user asked.\n- PRIOR MEMORY: facts already stored for this user (may be empty).\n- PROBED THIS TURN: what memory-partition-recall already surfaced for this turn (may be empty) \u2014 already known, never re-store.\n- GATHERED DATA: summarized tool results from this turn (web searches, lookups).\n\nStore into the PARTITION lane (call memory-partition-remember) only when it is durable and user-specific:\n- A preference, interest, or durable detail the user states about themselves (favorite X, their setup, contact info, a decision \u2014 however phrased, any language).\n- A notable fact the user asks you to track or remember.\n- Knowledge about a subject the user cares about that was gathered this turn and extends what is already in PRIOR MEMORY.\n\nStore into the COGNITION lane (call memory-cognition-remember) only when it is something you LEARN about the user that they did not state outright:\n- An inferred trait, a standing interest, a working nuance, or a connection between facts the turn supports.\n- Never a stated fact \u2014 those belong in the partition lane.\n\nSTORAGE MECHANICS \u2014 how your memory works (write for the retriever):\n- Each stored record is embedded as a whole AND matched sentence-by-sentence at recall time (multi-variant retrieval). One self-contained fact per call; a single long, dense sentence is fine, but lead with the subject (\"Sam's phone number is 555-1234\", never \"His number is \u2026\").\n- Restating a record verbatim OVERWRITES it in place \u2014 updates are restatements of the full corrected statement, not diffs.\n- tags are the ONLY topic-filter vocabulary at recall \u2014 reuse stable, lowercase topics (partition lane only).\n\nDo NOT store:\n- Public facts merely fetched this turn that do not relate to the user (e.g. generic web results).\n- Anything already covered by PRIOR MEMORY or PROBED THIS TURN \u2014 extend or update it via the remember call; do not repeat.\n- Tool artifacts: URLs, search scores, image metadata, raw JSON keys.\n- Inferred, assumed, or extrapolated details about the user that neither their words nor GATHERED DATA support \u2014 if the user did not state it (or clearly imply it), it is not memory; when in doubt, answer \"none\".\n\nRules:\n- Each remember call stores ONE self-contained statement (stand-alone, understandable weeks later without this conversation's context).\n- Call each remember tool once per distinct durable item \u2014 no more.\n- If nothing durable surfaced, produce a one-word text answer (\"none\") and make NO tool call. An empty memory write is a correct outcome, never a failure.";
112
+ declare const MEMORY_WRITE_INSTRUCTIONS = "MEMORY WRITE JOB \u2014 one purpose: decide whether THIS turn yielded anything worth persisting, and store it in the correct lane via the memory-partition-remember or memory-cognition-remember tool.\n\nTwo lanes, never confused:\n- memory-partition = the user's OWN statements (facts they stated or asked you to remember).\n- memory-cognition = YOUR derived understanding of the user (inferred traits, standing interests, connections).\n\nYou receive:\n- USER REQUEST: what the user asked.\n- PRIOR MEMORY: facts already stored for this user (may be empty).\n- PROBED THIS TURN: what memory-partition-recall already surfaced for this turn (may be empty) \u2014 already known, never re-store.\n- GATHERED DATA: summarized tool results from this turn (web searches, lookups).\n\nStore into the PARTITION lane (call memory-partition-remember) only when it is durable and user-specific:\n- A preference, interest, or durable detail the user states about themselves (favorite X, their setup, contact info, a decision \u2014 however phrased, any language).\n- A notable fact the user asks you to track or remember.\n- Knowledge about a subject the user cares about that was gathered this turn and extends what is already in PRIOR MEMORY.\n\nStore into the COGNITION lane (call memory-cognition-remember) only when it is something you LEARN about the user that they did not state outright:\n- An inferred trait, a standing interest, a working nuance, or a connection between facts the turn supports.\n- Never a stated fact \u2014 those belong in the partition lane.\n\nSTORAGE MECHANICS \u2014 how your memory works (write for the retriever):\n- Each stored record is embedded as a whole AND matched sentence-by-sentence at recall time (multi-variant retrieval). One self-contained fact per call; a single long, dense sentence is fine, but lead with the subject (\"Sam's phone number is 555-1234\", never \"His number is \u2026\").\n- Restating a record verbatim OVERWRITES it in place \u2014 updates are restatements of the full corrected statement, not diffs.\n- tags are the ONLY topic-filter vocabulary at recall \u2014 reuse stable, lowercase topics (partition lane only).\n- category is the broad family the fact belongs to (partition lane only): ONE lowercase label per remember call \u2014 games, pets, work, health, finance, contacts \u2026 \u2014 chosen so narrow topics group into families (both the nte and the wuthering waves facts carry category \"games\"). Always include it; tags stay narrow, category stays broad.\n\nDo NOT store:\n- Public facts merely fetched this turn that do not relate to the user (e.g. generic web results).\n- Anything already covered by PRIOR MEMORY or PROBED THIS TURN \u2014 extend or update it via the remember call; do not repeat.\n- Tool artifacts: URLs, search scores, image metadata, raw JSON keys.\n- Inferred, assumed, or extrapolated details about the user that neither their words nor GATHERED DATA support \u2014 if the user did not state it (or clearly imply it), it is not memory; when in doubt, answer \"none\".\n\nRules:\n- Each remember call stores ONE self-contained statement (stand-alone, understandable weeks later without this conversation's context).\n- Call each remember tool once per distinct durable item \u2014 no more.\n- If nothing durable surfaced, produce a one-word text answer (\"none\") and make NO tool call. An empty memory write is a correct outcome, never a failure.";
113
113
  declare function buildMemoryWritePrompt(params: {
114
114
  userRequest: string;
115
115
  priorMemory?: string;
@@ -3135,6 +3135,7 @@ STORAGE MECHANICS \u2014 how your memory works (write for the retriever):
3135
3135
  - Each stored record is embedded as a whole AND matched sentence-by-sentence at recall time (multi-variant retrieval). One self-contained fact per call; a single long, dense sentence is fine, but lead with the subject ("Sam's phone number is 555-1234", never "His number is \u2026").
3136
3136
  - Restating a record verbatim OVERWRITES it in place \u2014 updates are restatements of the full corrected statement, not diffs.
3137
3137
  - tags are the ONLY topic-filter vocabulary at recall \u2014 reuse stable, lowercase topics (partition lane only).
3138
+ - category is the broad family the fact belongs to (partition lane only): ONE lowercase label per remember call \u2014 games, pets, work, health, finance, contacts \u2026 \u2014 chosen so narrow topics group into families (both the nte and the wuthering waves facts carry category "games"). Always include it; tags stay narrow, category stays broad.
3138
3139
 
3139
3140
  Do NOT store:
3140
3141
  - Public facts merely fetched this turn that do not relate to the user (e.g. generic web results).
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- export { D as DEFAULT_VARIANT_ID, F as FormatZodShapeOptions, I as IntentResult, a as IntentSchema, T as TemplateName, f as formatZodShape } from '../intent.schema-CE2F1zLH.js';
2
+ export { D as DEFAULT_VARIANT_ID, F as FormatZodShapeOptions, I as IntentResult, a as IntentSchema, T as TemplateName, f as formatZodShape } from '../intent.schema-DnxaUhmz.js';
3
3
 
4
4
  declare const BLOCKED_IMAGE_HOSTS: Set<string>;
5
5
 
@@ -264,7 +264,7 @@ declare const memoryProfileResponseSchema: z.ZodPreprocess<z.ZodObject<{
264
264
  path: z.ZodOptional<z.ZodString>;
265
265
  }, z.core.$strip>>;
266
266
  episode: z.ZodOptional<z.ZodNullable<z.ZodString>>;
267
- }, z.core.$strip>>;
267
+ }, z.core.$strip>, unknown>;
268
268
  type MemoryProfileResponse = z.infer<typeof memoryProfileResponseSchema>;
269
269
  declare const INSIGHT_TAGS: string[];
270
270
  declare const EPISODE_TAGS: string[];
@@ -417,6 +417,7 @@ declare function createMemoryPartitionRecallTool(deps: MemoryPartitionRecallDeps
417
417
 
418
418
  declare const memoryPartitionRememberSchema: z.ZodObject<{
419
419
  text: z.ZodString;
420
+ category: z.ZodOptional<z.ZodString>;
420
421
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
421
422
  }, z.core.$strip>;
422
423
  type MemoryPartitionRememberInput = z.infer<typeof memoryPartitionRememberSchema>;
@@ -430,6 +431,7 @@ interface MemoryPartitionRememberDeps {
430
431
  requestId?: string;
431
432
  text: string;
432
433
  tags?: string[];
434
+ category?: string;
433
435
  }) => Promise<string>;
434
436
  }
435
437
  declare function createMemoryPartitionRememberTool(deps: MemoryPartitionRememberDeps): Tool;
@@ -518,7 +518,7 @@ function extractYoutubeVideoId(url) {
518
518
  }
519
519
  function buildYoutubeThumbnailUrl(url) {
520
520
  const id = extractYoutubeVideoId(url);
521
- return id ? `https://i.ytimg.com/vi/${id}/hqdefault.jpg` : void 0;
521
+ return id ? `https://i.ytimg.com/vi/${id}/maxresdefault.jpg` : void 0;
522
522
  }
523
523
 
524
524
  // src/tools/helpers/localized-query-suffix.helper.ts
@@ -959,15 +959,18 @@ ${lines.join("\n")}`;
959
959
  }
960
960
  var memoryPartitionRememberSchema = z.object({
961
961
  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".'),
962
+ category: z.string().min(1).max(40).optional().describe(
963
+ 'One broad lowercase category the fact belongs to, e.g. "games", "pets", "work", "health" \u2014 groups narrow topics into one family. Always include it.'
964
+ ),
962
965
  tags: z.array(z.string().min(1).max(40)).max(8).optional().describe(
963
966
  'Optional topic labels (lowercase, reusable) so future recall can filter by topic, e.g. ["contacts", "sam"].'
964
967
  )
965
968
  });
966
969
  function createMemoryPartitionRememberTool(deps) {
967
970
  return tool({
968
- description: `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. This memory outlives the conversation and is recalled later with memory-partition-recall. STORAGE MECHANICS: each record is embedded as a whole and matched sentence-by-sentence at recall time \u2014 write ONE self-contained statement per call (a single dense sentence is fine, subject up front), restating a record verbatim updates it in place, and tags are the recall filter vocabulary (lowercase, reusable).`,
971
+ description: '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. This memory outlives the conversation and is recalled later with memory-partition-recall. STORAGE MECHANICS: each record is embedded as a whole and matched sentence-by-sentence at recall time \u2014 write ONE self-contained statement per call (a single dense sentence is fine, subject up front), restating a record verbatim updates it in place, tags are the recall filter vocabulary (lowercase, reusable), and `category` is one broad family label (games, pets, work, health \u2026) that groups narrow tags into one topic family \u2014 always include both.',
969
972
  inputSchema: memoryPartitionRememberSchema,
970
- execute: async ({ text, tags }) => {
973
+ execute: async ({ text, tags, category }) => {
971
974
  try {
972
975
  const id = await deps.storeRecord({
973
976
  memoryPartition: deps.scope.memoryPartition,
@@ -975,7 +978,8 @@ function createMemoryPartitionRememberTool(deps) {
975
978
  conversationId: deps.scope.conversationId,
976
979
  requestId: deps.scope.requestId,
977
980
  text,
978
- tags
981
+ tags,
982
+ category
979
983
  });
980
984
  return { stored: true, id };
981
985
  } catch (error) {
@@ -1314,6 +1318,22 @@ var serperVideoSearchSchema = z.object({
1314
1318
  recency: z.enum(["day", "week", "month", "year"]).optional().describe(RECENCY_DESCRIPTION),
1315
1319
  lang: z.string().optional().describe("Two-letter ISO language code for result preference (e.g. en, de, ja)")
1316
1320
  });
1321
+
1322
+ // src/tools/helpers/repair-video-link.helper.ts
1323
+ var YOUTUBE_LINK_PATTERN = /^https?:\/\/(?:[a-z0-9-]+\.)*youtube\.com\/(?:watch\?v=|shorts\/|embed\/|live\/|v\/|e\/)([A-Za-z0-9_-]{11})(?![A-Za-z0-9_-])/;
1324
+ var YOUTU_BE_LINK_PATTERN = /^https?:\/\/youtu\.be\/([A-Za-z0-9_-]{11})(?![A-Za-z0-9_-])/;
1325
+ var YOUTUBE_NOCOOKIE_LINK_PATTERN = /^https?:\/\/(?:www\.)?youtube-nocookie\.com\/(?:embed\/|v\/)([A-Za-z0-9_-]{11})(?![A-Za-z0-9_-])/;
1326
+ var YOUTUBE_HOST_PATTERN = /^https?:\/\/(?:[a-z0-9-]+\.)*youtube(?:-nocookie)?\.com\/|^https?:\/\/youtu\.be\//;
1327
+ var CONTAMINATION_PATTERN = /[\s"'<>\uFFFD]/;
1328
+ function repairVideoLink(link) {
1329
+ if (!link) return void 0;
1330
+ const id = YOUTUBE_LINK_PATTERN.exec(link)?.[1] ?? YOUTU_BE_LINK_PATTERN.exec(link)?.[1] ?? YOUTUBE_NOCOOKIE_LINK_PATTERN.exec(link)?.[1];
1331
+ if (id) return `https://www.youtube.com/watch?v=${id}`;
1332
+ if (YOUTUBE_HOST_PATTERN.test(link)) return void 0;
1333
+ return CONTAMINATION_PATTERN.test(link) ? void 0 : link;
1334
+ }
1335
+
1336
+ // src/tools/serper/video-search.tool.ts
1317
1337
  function createSerperVideoSearch(deps) {
1318
1338
  return tool({
1319
1339
  description: 'Search for videos using Serper.dev. Returns titles, links, channel names, duration, and publish dates. Only return URLs from supported embeddable providers: YouTube, Vimeo, Dailymotion, Loom, Wistia, or direct video files. Reject Instagram, Facebook, TikTok, Twitch, X/Twitter, and other unreliable platforms. Pass recency ("day"|"week"|"month"|"year") to restrict to recently uploaded videos. ' + STANDALONE_QUERY_TOOL_CLAUSE,
@@ -1345,16 +1365,24 @@ function createSerperVideoSearch(deps) {
1345
1365
  if (!res.ok) return { results: [] };
1346
1366
  const data = await res.json();
1347
1367
  if (!data.videos?.length) return { results: [] };
1348
- const results = data.videos.slice(0, num).map((r) => ({
1368
+ const results = data.videos.flatMap((r) => {
1369
+ const link = repairVideoLink(r.link);
1370
+ return link ? [{ r, link }] : [];
1371
+ }).slice(0, num).map(({ r, link }) => ({
1349
1372
  title: r.title,
1350
- link: r.link,
1373
+ link,
1374
+ // Keep the provider's original link when it had to be repaired —
1375
+ // auditability for provider-side payload corruption.
1376
+ ...link !== r.link ? { originalLink: r.link } : {},
1351
1377
  snippet: r.snippet || "",
1352
1378
  channel: r.channel || "",
1353
1379
  duration: r.duration || "",
1354
1380
  date: r.date || "",
1355
1381
  // Serper thumbnails are Google proxy images (blocked by our image
1356
1382
  // trust rules) — derive a direct thumbnail for YouTube instead.
1357
- thumbnailUrl: buildYoutubeThumbnailUrl(r.link) ?? "",
1383
+ // maxresdefault is not guaranteed to exist; consumers degrade to
1384
+ // hqdefault/mqdefault on failure.
1385
+ thumbnailUrl: buildYoutubeThumbnailUrl(link) ?? "",
1358
1386
  source: r.source || "",
1359
1387
  views: r.views ?? 0
1360
1388
  }));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@triplef/agent",
3
3
  "description": "tripleF (3F) agent domain — structured-output schemas, prompt builders, and model tools shared across the apps.",
4
- "version": "0.1.5",
4
+ "version": "0.1.7",
5
5
  "packageManager": "pnpm@11.24.0",
6
6
  "license": "MIT",
7
7
  "type": "module",
@@ -60,8 +60,8 @@
60
60
  "turndown": "^7.2.4"
61
61
  },
62
62
  "peerDependencies": {
63
- "ai": "^7.0.83",
64
- "zod": "^4.4.3"
63
+ "ai": "^7.0.84",
64
+ "zod": "^4.5.2"
65
65
  },
66
66
  "peerDependenciesMeta": {
67
67
  "ai": {