@anuma/sdk 4.0.2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18174,21 +18174,63 @@ var DEFAULT_PROFILE_FACETS = [
18174
18174
  guidance: "Summarize what the person has been up to lately in one line, favoring the most recently reinforced facts. Frame it as current/ongoing (e.g. 'Lately: training for a first half-marathon and learning Portuguese'). Omit if nothing recent stands out."
18175
18175
  }
18176
18176
  ];
18177
+ var NEARBY_MAX_OCCUPATION_CODEPOINTS = 80;
18178
+ var NEARBY_MAX_INTERESTS = 12;
18179
+ var NEARBY_MAX_INTEREST_CODEPOINTS = 40;
18180
+ var MAX_RAW_INTERESTS = NEARBY_MAX_INTERESTS * 2;
18181
+ function codePointLength(value) {
18182
+ return [...value].length;
18183
+ }
18184
+ var FACET_BASE_PROPERTIES = {
18185
+ summary: {
18186
+ type: "string",
18187
+ description: "The synthesized section prose. Empty string if the memories don't cover it."
18188
+ },
18189
+ hasEvidence: {
18190
+ type: "boolean",
18191
+ description: "False when the supplied memories don't support any claim for this facet."
18192
+ }
18193
+ };
18177
18194
  var FACET_RESPONSE_SCHEMA = {
18195
+ type: "object",
18196
+ properties: FACET_BASE_PROPERTIES,
18197
+ required: ["summary", "hasEvidence"],
18198
+ additionalProperties: false
18199
+ };
18200
+ var WORK_ROLE_RESPONSE_SCHEMA = {
18178
18201
  type: "object",
18179
18202
  properties: {
18180
- summary: {
18203
+ ...FACET_BASE_PROPERTIES,
18204
+ occupation: {
18181
18205
  type: "string",
18182
- description: "The synthesized section prose. Empty string if the memories don't cover it."
18183
- },
18184
- hasEvidence: {
18185
- type: "boolean",
18186
- description: "False when the supplied memories don't support any claim for this facet."
18206
+ description: `The same role as a standalone phrase for a profile field, at most ${NEARBY_MAX_OCCUPATION_CODEPOINTS} characters (e.g. "Backend engineer, fintech"). Empty string when hasEvidence is false.`
18187
18207
  }
18188
18208
  },
18189
- required: ["summary", "hasEvidence"],
18209
+ required: ["summary", "hasEvidence", "occupation"],
18210
+ additionalProperties: false
18211
+ };
18212
+ var INTERESTS_RESPONSE_SCHEMA = {
18213
+ type: "object",
18214
+ properties: {
18215
+ ...FACET_BASE_PROPERTIES,
18216
+ interests: {
18217
+ type: "array",
18218
+ items: { type: "string" },
18219
+ description: `The same interests as discrete strings \u2014 at most ${NEARBY_MAX_INTERESTS} items, each at most ${NEARBY_MAX_INTEREST_CODEPOINTS} characters (e.g. ["trail running", "film photography"]). Empty array when hasEvidence is false.`
18220
+ }
18221
+ },
18222
+ required: ["summary", "hasEvidence", "interests"],
18190
18223
  additionalProperties: false
18191
18224
  };
18225
+ function facetResponseSchema(key) {
18226
+ if (key === "work_role") return WORK_ROLE_RESPONSE_SCHEMA;
18227
+ if (key === "interests") return INTERESTS_RESPONSE_SCHEMA;
18228
+ return FACET_RESPONSE_SCHEMA;
18229
+ }
18230
+ var FACET_STRUCTURED_RESPONSE_HINT = {
18231
+ work_role: `, "occupation": <the role as a standalone phrase of at most ${NEARBY_MAX_OCCUPATION_CODEPOINTS} characters, e.g. "Backend engineer, fintech", or "">`,
18232
+ interests: `, "interests": <the same interests as an array of at most ${NEARBY_MAX_INTERESTS} strings of at most ${NEARBY_MAX_INTEREST_CODEPOINTS} characters each, e.g. ["trail running", "film photography"], or []>`
18233
+ };
18192
18234
  async function synthesizeProfile(ctx, options = {}) {
18193
18235
  if (!ctx.vaultCtx) {
18194
18236
  throw new Error("synthesizeProfile requires ctx.vaultCtx (vault-backed facts).");
@@ -18284,7 +18326,7 @@ function reviewedMemoryIdsSignature(ids) {
18284
18326
  return [...new Set(ids)].sort().join("\n");
18285
18327
  }
18286
18328
  function facetsSignature(facets) {
18287
- return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance])).sort().join("\n");
18329
+ return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance, facetResponseSchema(f.key)])).sort().join("\n");
18288
18330
  }
18289
18331
  function trendsEqual(a, b) {
18290
18332
  if (!a) return false;
@@ -18432,7 +18474,7 @@ async function synthesizeFacet(facet, ctx, options, prior) {
18432
18474
  types: ["fact"],
18433
18475
  maxTokens: DEFAULT_FACET_MAX_TOKENS,
18434
18476
  systemPrompt: buildFacetSystemPrompt(facet),
18435
- responseSchema: FACET_RESPONSE_SCHEMA,
18477
+ responseSchema: facetResponseSchema(facet.key),
18436
18478
  memories
18437
18479
  });
18438
18480
  const noEvidence = result.basedOn.memoryIds.length === 0;
@@ -18455,6 +18497,25 @@ async function synthesizeFacet(facet, ctx, options, prior) {
18455
18497
  const redacted = await options.redactor.redactTextAsync(text12);
18456
18498
  section.text = redacted.text;
18457
18499
  }
18500
+ if (!legitimateEmpty && !noEvidence) {
18501
+ const values = extractStructuredValues(facet.key, result.structuredOutput);
18502
+ if (options.redactor) {
18503
+ if (values.occupation) {
18504
+ values.occupation = (await options.redactor.redactTextAsync(values.occupation)).text;
18505
+ }
18506
+ if (values.interests) {
18507
+ const gated = [];
18508
+ for (const interest of values.interests) {
18509
+ gated.push((await options.redactor.redactTextAsync(interest)).text);
18510
+ }
18511
+ values.interests = gated;
18512
+ }
18513
+ }
18514
+ const occupation = normalizeOccupation(values.occupation);
18515
+ if (occupation !== void 0) section.occupation = occupation;
18516
+ const interests = normalizeInterests(values.interests);
18517
+ if (interests !== void 0) section.interests = interests;
18518
+ }
18458
18519
  return section;
18459
18520
  }
18460
18521
  function fallbackSection(facet, prior) {
@@ -18471,6 +18532,7 @@ function fallbackSection(facet, prior) {
18471
18532
  };
18472
18533
  }
18473
18534
  function buildFacetSystemPrompt(facet) {
18535
+ const structuredField = FACET_STRUCTURED_RESPONSE_HINT[facet.key] ?? "";
18474
18536
  return `You are writing the "${facet.label}" section of a person's shareable profile, using their private memories (supplied as evidence) as the only source of truth.
18475
18537
 
18476
18538
  Task for this section:
@@ -18481,7 +18543,7 @@ Rules:
18481
18543
  - If the memories don't cover this section, return an empty summary with hasEvidence=false. Do not pad or guess.
18482
18544
  - Write in third person about the person, in a natural voice suitable for a public profile (no "I"/"you", no name repetition).
18483
18545
  - Be concise and specific; no preamble, hedging, or meta-commentary.
18484
- - Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false> }.`;
18546
+ - Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false>${structuredField} }.`;
18485
18547
  }
18486
18548
  function extractFacetText(structured) {
18487
18549
  if (structured && typeof structured === "object") {
@@ -18492,6 +18554,73 @@ function extractFacetText(structured) {
18492
18554
  }
18493
18555
  return { text: "", legitimateEmpty: false };
18494
18556
  }
18557
+ function extractStructuredValues(key, structured) {
18558
+ if (!structured || typeof structured !== "object") return {};
18559
+ const obj = structured;
18560
+ if (key === "work_role") {
18561
+ return typeof obj.occupation === "string" ? { occupation: obj.occupation } : {};
18562
+ }
18563
+ if (key === "interests") {
18564
+ const list = coerceInterestList(obj.interests);
18565
+ if (list) return { interests: list.slice(0, MAX_RAW_INTERESTS) };
18566
+ }
18567
+ return {};
18568
+ }
18569
+ function coerceInterestList(value) {
18570
+ if (Array.isArray(value)) return value.filter((i) => typeof i === "string");
18571
+ if (typeof value !== "string") return void 0;
18572
+ const trimmed = value.trim();
18573
+ if (trimmed.startsWith("[")) {
18574
+ try {
18575
+ const parsed = JSON.parse(trimmed);
18576
+ if (Array.isArray(parsed)) return parsed.filter((i) => typeof i === "string");
18577
+ } catch {
18578
+ if (trimmed.endsWith("]")) return splitInterestFragments(trimmed.slice(1, -1));
18579
+ }
18580
+ }
18581
+ return splitInterestFragments(trimmed);
18582
+ }
18583
+ function splitInterestFragments(source) {
18584
+ return source.split(",").map((fragment) => {
18585
+ let entry = fragment.trim();
18586
+ if (entry.startsWith("[")) entry = entry.slice(1);
18587
+ if (entry.endsWith("]")) entry = entry.slice(0, -1);
18588
+ entry = entry.trim();
18589
+ const quote = entry[0];
18590
+ if ((quote === '"' || quote === "'") && entry.endsWith(quote)) entry = entry.slice(1, -1);
18591
+ return entry;
18592
+ });
18593
+ }
18594
+ function normalizeOccupation(value) {
18595
+ if (value === void 0) return void 0;
18596
+ const trimmed = value.trim();
18597
+ if (!trimmed) return void 0;
18598
+ const length = codePointLength(trimmed);
18599
+ if (length > NEARBY_MAX_OCCUPATION_CODEPOINTS) {
18600
+ getLogger().warn(
18601
+ "[memory/synthesizeProfile] occupation exceeded the publishable length; dropping it",
18602
+ { length, max: NEARBY_MAX_OCCUPATION_CODEPOINTS }
18603
+ );
18604
+ return void 0;
18605
+ }
18606
+ return trimmed;
18607
+ }
18608
+ function normalizeInterests(values) {
18609
+ if (values === void 0) return void 0;
18610
+ const seen = /* @__PURE__ */ new Set();
18611
+ const out = [];
18612
+ for (const value of values) {
18613
+ const trimmed = value.trim();
18614
+ if (!trimmed) continue;
18615
+ if (codePointLength(trimmed) > NEARBY_MAX_INTEREST_CODEPOINTS) continue;
18616
+ const key = trimmed.toLowerCase();
18617
+ if (seen.has(key)) continue;
18618
+ seen.add(key);
18619
+ out.push(trimmed);
18620
+ if (out.length === NEARBY_MAX_INTERESTS) break;
18621
+ }
18622
+ return out.length > 0 ? out : void 0;
18623
+ }
18495
18624
 
18496
18625
  // src/lib/memory/topicExtract.ts
18497
18626
  init_operations2();
@@ -8880,6 +8880,23 @@ interface ProfileSection {
8880
8880
  text: string;
8881
8881
  /** Vault memory ids this section was grounded on — provenance + delta refresh. */
8882
8882
  sourceMemoryIds: string[];
8883
+ /**
8884
+ * Structured occupation — the `work_role` facet only. A short role phrase
8885
+ * (at most 80 code points, PII-gated alongside {@link ProfileSection.text})
8886
+ * that a profile store's `occupation` column takes verbatim.
8887
+ *
8888
+ * Absent when the facet found no evidence, when the model didn't return one,
8889
+ * or when the value it returned couldn't be made publishable. `text` is
8890
+ * unaffected either way, so the prose is never blocked on this.
8891
+ */
8892
+ occupation?: string;
8893
+ /**
8894
+ * Structured interests — the `interests` facet only. Discrete entries,
8895
+ * trimmed and deduped case- and space-insensitively (first spelling wins), at
8896
+ * most 12 items of at most 40 code points each, ready for a profile store's
8897
+ * `interests` column. Absent when nothing survived normalization.
8898
+ */
8899
+ interests?: string[];
8883
8900
  /** Unix ms this section was generated. */
8884
8901
  generatedAt: number;
8885
8902
  /** True when regeneration failed and a prior section value was carried
@@ -8896,10 +8913,12 @@ interface ProfileConfigFingerprint {
8896
8913
  /** Facet keys present in the doc, sorted. */
8897
8914
  facetKeys: ProfileFacetKey[];
8898
8915
  /** Order-independent digest of each facet's full definition (key + label +
8899
- * query + guidance). Reuse must invalidate when a facet's PROMPT changes, not
8900
- * just its key set — otherwise reused sections carry text generated under the
8901
- * old definition. Facet display order does NOT invalidate (sections are
8902
- * rebuilt in facet order and reused by key). */
8916
+ * query + guidance) and the response schema it is synthesized under. Reuse
8917
+ * must invalidate when a facet's PROMPT changes, not just its key set —
8918
+ * otherwise reused sections carry text generated under the old definition,
8919
+ * or under an output schema that predates a field the caller now reads.
8920
+ * Facet display order does NOT invalidate (sections are rebuilt in facet
8921
+ * order and reused by key). */
8903
8922
  facetsSignature: string;
8904
8923
  /** Scopes the facts were drawn from, sorted. */
8905
8924
  scopes: string[];
@@ -8880,6 +8880,23 @@ interface ProfileSection {
8880
8880
  text: string;
8881
8881
  /** Vault memory ids this section was grounded on — provenance + delta refresh. */
8882
8882
  sourceMemoryIds: string[];
8883
+ /**
8884
+ * Structured occupation — the `work_role` facet only. A short role phrase
8885
+ * (at most 80 code points, PII-gated alongside {@link ProfileSection.text})
8886
+ * that a profile store's `occupation` column takes verbatim.
8887
+ *
8888
+ * Absent when the facet found no evidence, when the model didn't return one,
8889
+ * or when the value it returned couldn't be made publishable. `text` is
8890
+ * unaffected either way, so the prose is never blocked on this.
8891
+ */
8892
+ occupation?: string;
8893
+ /**
8894
+ * Structured interests — the `interests` facet only. Discrete entries,
8895
+ * trimmed and deduped case- and space-insensitively (first spelling wins), at
8896
+ * most 12 items of at most 40 code points each, ready for a profile store's
8897
+ * `interests` column. Absent when nothing survived normalization.
8898
+ */
8899
+ interests?: string[];
8883
8900
  /** Unix ms this section was generated. */
8884
8901
  generatedAt: number;
8885
8902
  /** True when regeneration failed and a prior section value was carried
@@ -8896,10 +8913,12 @@ interface ProfileConfigFingerprint {
8896
8913
  /** Facet keys present in the doc, sorted. */
8897
8914
  facetKeys: ProfileFacetKey[];
8898
8915
  /** Order-independent digest of each facet's full definition (key + label +
8899
- * query + guidance). Reuse must invalidate when a facet's PROMPT changes, not
8900
- * just its key set — otherwise reused sections carry text generated under the
8901
- * old definition. Facet display order does NOT invalidate (sections are
8902
- * rebuilt in facet order and reused by key). */
8916
+ * query + guidance) and the response schema it is synthesized under. Reuse
8917
+ * must invalidate when a facet's PROMPT changes, not just its key set —
8918
+ * otherwise reused sections carry text generated under the old definition,
8919
+ * or under an output schema that predates a field the caller now reads.
8920
+ * Facet display order does NOT invalidate (sections are rebuilt in facet
8921
+ * order and reused by key). */
8903
8922
  facetsSignature: string;
8904
8923
  /** Scopes the facts were drawn from, sorted. */
8905
8924
  scopes: string[];
@@ -8931,21 +8931,63 @@ var DEFAULT_PROFILE_FACETS = [
8931
8931
  guidance: "Summarize what the person has been up to lately in one line, favoring the most recently reinforced facts. Frame it as current/ongoing (e.g. 'Lately: training for a first half-marathon and learning Portuguese'). Omit if nothing recent stands out."
8932
8932
  }
8933
8933
  ];
8934
+ var NEARBY_MAX_OCCUPATION_CODEPOINTS = 80;
8935
+ var NEARBY_MAX_INTERESTS = 12;
8936
+ var NEARBY_MAX_INTEREST_CODEPOINTS = 40;
8937
+ var MAX_RAW_INTERESTS = NEARBY_MAX_INTERESTS * 2;
8938
+ function codePointLength(value) {
8939
+ return [...value].length;
8940
+ }
8941
+ var FACET_BASE_PROPERTIES = {
8942
+ summary: {
8943
+ type: "string",
8944
+ description: "The synthesized section prose. Empty string if the memories don't cover it."
8945
+ },
8946
+ hasEvidence: {
8947
+ type: "boolean",
8948
+ description: "False when the supplied memories don't support any claim for this facet."
8949
+ }
8950
+ };
8934
8951
  var FACET_RESPONSE_SCHEMA = {
8952
+ type: "object",
8953
+ properties: FACET_BASE_PROPERTIES,
8954
+ required: ["summary", "hasEvidence"],
8955
+ additionalProperties: false
8956
+ };
8957
+ var WORK_ROLE_RESPONSE_SCHEMA = {
8935
8958
  type: "object",
8936
8959
  properties: {
8937
- summary: {
8960
+ ...FACET_BASE_PROPERTIES,
8961
+ occupation: {
8938
8962
  type: "string",
8939
- description: "The synthesized section prose. Empty string if the memories don't cover it."
8940
- },
8941
- hasEvidence: {
8942
- type: "boolean",
8943
- description: "False when the supplied memories don't support any claim for this facet."
8963
+ description: `The same role as a standalone phrase for a profile field, at most ${NEARBY_MAX_OCCUPATION_CODEPOINTS} characters (e.g. "Backend engineer, fintech"). Empty string when hasEvidence is false.`
8944
8964
  }
8945
8965
  },
8946
- required: ["summary", "hasEvidence"],
8966
+ required: ["summary", "hasEvidence", "occupation"],
8967
+ additionalProperties: false
8968
+ };
8969
+ var INTERESTS_RESPONSE_SCHEMA = {
8970
+ type: "object",
8971
+ properties: {
8972
+ ...FACET_BASE_PROPERTIES,
8973
+ interests: {
8974
+ type: "array",
8975
+ items: { type: "string" },
8976
+ description: `The same interests as discrete strings \u2014 at most ${NEARBY_MAX_INTERESTS} items, each at most ${NEARBY_MAX_INTEREST_CODEPOINTS} characters (e.g. ["trail running", "film photography"]). Empty array when hasEvidence is false.`
8977
+ }
8978
+ },
8979
+ required: ["summary", "hasEvidence", "interests"],
8947
8980
  additionalProperties: false
8948
8981
  };
8982
+ function facetResponseSchema(key) {
8983
+ if (key === "work_role") return WORK_ROLE_RESPONSE_SCHEMA;
8984
+ if (key === "interests") return INTERESTS_RESPONSE_SCHEMA;
8985
+ return FACET_RESPONSE_SCHEMA;
8986
+ }
8987
+ var FACET_STRUCTURED_RESPONSE_HINT = {
8988
+ work_role: `, "occupation": <the role as a standalone phrase of at most ${NEARBY_MAX_OCCUPATION_CODEPOINTS} characters, e.g. "Backend engineer, fintech", or "">`,
8989
+ interests: `, "interests": <the same interests as an array of at most ${NEARBY_MAX_INTERESTS} strings of at most ${NEARBY_MAX_INTEREST_CODEPOINTS} characters each, e.g. ["trail running", "film photography"], or []>`
8990
+ };
8949
8991
  async function synthesizeProfile(ctx, options = {}) {
8950
8992
  if (!ctx.vaultCtx) {
8951
8993
  throw new Error("synthesizeProfile requires ctx.vaultCtx (vault-backed facts).");
@@ -9041,7 +9083,7 @@ function reviewedMemoryIdsSignature(ids) {
9041
9083
  return [...new Set(ids)].sort().join("\n");
9042
9084
  }
9043
9085
  function facetsSignature(facets) {
9044
- return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance])).sort().join("\n");
9086
+ return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance, facetResponseSchema(f.key)])).sort().join("\n");
9045
9087
  }
9046
9088
  function trendsEqual(a, b) {
9047
9089
  if (!a) return false;
@@ -9189,7 +9231,7 @@ async function synthesizeFacet(facet, ctx, options, prior) {
9189
9231
  types: ["fact"],
9190
9232
  maxTokens: DEFAULT_FACET_MAX_TOKENS,
9191
9233
  systemPrompt: buildFacetSystemPrompt(facet),
9192
- responseSchema: FACET_RESPONSE_SCHEMA,
9234
+ responseSchema: facetResponseSchema(facet.key),
9193
9235
  memories
9194
9236
  });
9195
9237
  const noEvidence = result.basedOn.memoryIds.length === 0;
@@ -9212,6 +9254,25 @@ async function synthesizeFacet(facet, ctx, options, prior) {
9212
9254
  const redacted = await options.redactor.redactTextAsync(text12);
9213
9255
  section.text = redacted.text;
9214
9256
  }
9257
+ if (!legitimateEmpty && !noEvidence) {
9258
+ const values = extractStructuredValues(facet.key, result.structuredOutput);
9259
+ if (options.redactor) {
9260
+ if (values.occupation) {
9261
+ values.occupation = (await options.redactor.redactTextAsync(values.occupation)).text;
9262
+ }
9263
+ if (values.interests) {
9264
+ const gated = [];
9265
+ for (const interest of values.interests) {
9266
+ gated.push((await options.redactor.redactTextAsync(interest)).text);
9267
+ }
9268
+ values.interests = gated;
9269
+ }
9270
+ }
9271
+ const occupation = normalizeOccupation(values.occupation);
9272
+ if (occupation !== void 0) section.occupation = occupation;
9273
+ const interests = normalizeInterests(values.interests);
9274
+ if (interests !== void 0) section.interests = interests;
9275
+ }
9215
9276
  return section;
9216
9277
  }
9217
9278
  function fallbackSection(facet, prior) {
@@ -9228,6 +9289,7 @@ function fallbackSection(facet, prior) {
9228
9289
  };
9229
9290
  }
9230
9291
  function buildFacetSystemPrompt(facet) {
9292
+ const structuredField = FACET_STRUCTURED_RESPONSE_HINT[facet.key] ?? "";
9231
9293
  return `You are writing the "${facet.label}" section of a person's shareable profile, using their private memories (supplied as evidence) as the only source of truth.
9232
9294
 
9233
9295
  Task for this section:
@@ -9238,7 +9300,7 @@ Rules:
9238
9300
  - If the memories don't cover this section, return an empty summary with hasEvidence=false. Do not pad or guess.
9239
9301
  - Write in third person about the person, in a natural voice suitable for a public profile (no "I"/"you", no name repetition).
9240
9302
  - Be concise and specific; no preamble, hedging, or meta-commentary.
9241
- - Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false> }.`;
9303
+ - Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false>${structuredField} }.`;
9242
9304
  }
9243
9305
  function extractFacetText(structured) {
9244
9306
  if (structured && typeof structured === "object") {
@@ -9249,6 +9311,73 @@ function extractFacetText(structured) {
9249
9311
  }
9250
9312
  return { text: "", legitimateEmpty: false };
9251
9313
  }
9314
+ function extractStructuredValues(key, structured) {
9315
+ if (!structured || typeof structured !== "object") return {};
9316
+ const obj = structured;
9317
+ if (key === "work_role") {
9318
+ return typeof obj.occupation === "string" ? { occupation: obj.occupation } : {};
9319
+ }
9320
+ if (key === "interests") {
9321
+ const list = coerceInterestList(obj.interests);
9322
+ if (list) return { interests: list.slice(0, MAX_RAW_INTERESTS) };
9323
+ }
9324
+ return {};
9325
+ }
9326
+ function coerceInterestList(value) {
9327
+ if (Array.isArray(value)) return value.filter((i) => typeof i === "string");
9328
+ if (typeof value !== "string") return void 0;
9329
+ const trimmed = value.trim();
9330
+ if (trimmed.startsWith("[")) {
9331
+ try {
9332
+ const parsed = JSON.parse(trimmed);
9333
+ if (Array.isArray(parsed)) return parsed.filter((i) => typeof i === "string");
9334
+ } catch {
9335
+ if (trimmed.endsWith("]")) return splitInterestFragments(trimmed.slice(1, -1));
9336
+ }
9337
+ }
9338
+ return splitInterestFragments(trimmed);
9339
+ }
9340
+ function splitInterestFragments(source) {
9341
+ return source.split(",").map((fragment) => {
9342
+ let entry = fragment.trim();
9343
+ if (entry.startsWith("[")) entry = entry.slice(1);
9344
+ if (entry.endsWith("]")) entry = entry.slice(0, -1);
9345
+ entry = entry.trim();
9346
+ const quote = entry[0];
9347
+ if ((quote === '"' || quote === "'") && entry.endsWith(quote)) entry = entry.slice(1, -1);
9348
+ return entry;
9349
+ });
9350
+ }
9351
+ function normalizeOccupation(value) {
9352
+ if (value === void 0) return void 0;
9353
+ const trimmed = value.trim();
9354
+ if (!trimmed) return void 0;
9355
+ const length = codePointLength(trimmed);
9356
+ if (length > NEARBY_MAX_OCCUPATION_CODEPOINTS) {
9357
+ getLogger().warn(
9358
+ "[memory/synthesizeProfile] occupation exceeded the publishable length; dropping it",
9359
+ { length, max: NEARBY_MAX_OCCUPATION_CODEPOINTS }
9360
+ );
9361
+ return void 0;
9362
+ }
9363
+ return trimmed;
9364
+ }
9365
+ function normalizeInterests(values) {
9366
+ if (values === void 0) return void 0;
9367
+ const seen = /* @__PURE__ */ new Set();
9368
+ const out = [];
9369
+ for (const value of values) {
9370
+ const trimmed = value.trim();
9371
+ if (!trimmed) continue;
9372
+ if (codePointLength(trimmed) > NEARBY_MAX_INTEREST_CODEPOINTS) continue;
9373
+ const key = trimmed.toLowerCase();
9374
+ if (seen.has(key)) continue;
9375
+ seen.add(key);
9376
+ out.push(trimmed);
9377
+ if (out.length === NEARBY_MAX_INTERESTS) break;
9378
+ }
9379
+ return out.length > 0 ? out : void 0;
9380
+ }
9252
9381
 
9253
9382
  // src/lib/memory/topicExtract.ts
9254
9383
  var TOPIC_EXTRACTION_BATCH_SIZE = 10;