@anuma/sdk 4.1.0 → 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.
- package/dist/expo/index.cjs +139 -10
- package/dist/expo/index.d.mts +23 -4
- package/dist/expo/index.d.ts +23 -4
- package/dist/expo/index.mjs +139 -10
- package/dist/react/index.cjs +139 -10
- package/dist/react/index.d.mts +23 -4
- package/dist/react/index.d.ts +23 -4
- package/dist/react/index.mjs +139 -10
- package/dist/server/index.cjs +139 -10
- package/dist/server/index.d.mts +23 -4
- package/dist/server/index.d.ts +23 -4
- package/dist/server/index.mjs +139 -10
- package/package.json +1 -1
package/dist/expo/index.cjs
CHANGED
|
@@ -16875,21 +16875,63 @@ var DEFAULT_PROFILE_FACETS = [
|
|
|
16875
16875
|
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."
|
|
16876
16876
|
}
|
|
16877
16877
|
];
|
|
16878
|
+
var NEARBY_MAX_OCCUPATION_CODEPOINTS = 80;
|
|
16879
|
+
var NEARBY_MAX_INTERESTS = 12;
|
|
16880
|
+
var NEARBY_MAX_INTEREST_CODEPOINTS = 40;
|
|
16881
|
+
var MAX_RAW_INTERESTS = NEARBY_MAX_INTERESTS * 2;
|
|
16882
|
+
function codePointLength(value) {
|
|
16883
|
+
return [...value].length;
|
|
16884
|
+
}
|
|
16885
|
+
var FACET_BASE_PROPERTIES = {
|
|
16886
|
+
summary: {
|
|
16887
|
+
type: "string",
|
|
16888
|
+
description: "The synthesized section prose. Empty string if the memories don't cover it."
|
|
16889
|
+
},
|
|
16890
|
+
hasEvidence: {
|
|
16891
|
+
type: "boolean",
|
|
16892
|
+
description: "False when the supplied memories don't support any claim for this facet."
|
|
16893
|
+
}
|
|
16894
|
+
};
|
|
16878
16895
|
var FACET_RESPONSE_SCHEMA = {
|
|
16896
|
+
type: "object",
|
|
16897
|
+
properties: FACET_BASE_PROPERTIES,
|
|
16898
|
+
required: ["summary", "hasEvidence"],
|
|
16899
|
+
additionalProperties: false
|
|
16900
|
+
};
|
|
16901
|
+
var WORK_ROLE_RESPONSE_SCHEMA = {
|
|
16879
16902
|
type: "object",
|
|
16880
16903
|
properties: {
|
|
16881
|
-
|
|
16904
|
+
...FACET_BASE_PROPERTIES,
|
|
16905
|
+
occupation: {
|
|
16882
16906
|
type: "string",
|
|
16883
|
-
description:
|
|
16884
|
-
},
|
|
16885
|
-
hasEvidence: {
|
|
16886
|
-
type: "boolean",
|
|
16887
|
-
description: "False when the supplied memories don't support any claim for this facet."
|
|
16907
|
+
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.`
|
|
16888
16908
|
}
|
|
16889
16909
|
},
|
|
16890
|
-
required: ["summary", "hasEvidence"],
|
|
16910
|
+
required: ["summary", "hasEvidence", "occupation"],
|
|
16911
|
+
additionalProperties: false
|
|
16912
|
+
};
|
|
16913
|
+
var INTERESTS_RESPONSE_SCHEMA = {
|
|
16914
|
+
type: "object",
|
|
16915
|
+
properties: {
|
|
16916
|
+
...FACET_BASE_PROPERTIES,
|
|
16917
|
+
interests: {
|
|
16918
|
+
type: "array",
|
|
16919
|
+
items: { type: "string" },
|
|
16920
|
+
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.`
|
|
16921
|
+
}
|
|
16922
|
+
},
|
|
16923
|
+
required: ["summary", "hasEvidence", "interests"],
|
|
16891
16924
|
additionalProperties: false
|
|
16892
16925
|
};
|
|
16926
|
+
function facetResponseSchema(key) {
|
|
16927
|
+
if (key === "work_role") return WORK_ROLE_RESPONSE_SCHEMA;
|
|
16928
|
+
if (key === "interests") return INTERESTS_RESPONSE_SCHEMA;
|
|
16929
|
+
return FACET_RESPONSE_SCHEMA;
|
|
16930
|
+
}
|
|
16931
|
+
var FACET_STRUCTURED_RESPONSE_HINT = {
|
|
16932
|
+
work_role: `, "occupation": <the role as a standalone phrase of at most ${NEARBY_MAX_OCCUPATION_CODEPOINTS} characters, e.g. "Backend engineer, fintech", or "">`,
|
|
16933
|
+
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 []>`
|
|
16934
|
+
};
|
|
16893
16935
|
async function synthesizeProfile(ctx, options = {}) {
|
|
16894
16936
|
if (!ctx.vaultCtx) {
|
|
16895
16937
|
throw new Error("synthesizeProfile requires ctx.vaultCtx (vault-backed facts).");
|
|
@@ -16985,7 +17027,7 @@ function reviewedMemoryIdsSignature(ids) {
|
|
|
16985
17027
|
return [...new Set(ids)].sort().join("\n");
|
|
16986
17028
|
}
|
|
16987
17029
|
function facetsSignature(facets) {
|
|
16988
|
-
return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance])).sort().join("\n");
|
|
17030
|
+
return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance, facetResponseSchema(f.key)])).sort().join("\n");
|
|
16989
17031
|
}
|
|
16990
17032
|
function trendsEqual(a, b) {
|
|
16991
17033
|
if (!a) return false;
|
|
@@ -17133,7 +17175,7 @@ async function synthesizeFacet(facet, ctx, options, prior) {
|
|
|
17133
17175
|
types: ["fact"],
|
|
17134
17176
|
maxTokens: DEFAULT_FACET_MAX_TOKENS,
|
|
17135
17177
|
systemPrompt: buildFacetSystemPrompt(facet),
|
|
17136
|
-
responseSchema:
|
|
17178
|
+
responseSchema: facetResponseSchema(facet.key),
|
|
17137
17179
|
memories
|
|
17138
17180
|
});
|
|
17139
17181
|
const noEvidence = result.basedOn.memoryIds.length === 0;
|
|
@@ -17156,6 +17198,25 @@ async function synthesizeFacet(facet, ctx, options, prior) {
|
|
|
17156
17198
|
const redacted = await options.redactor.redactTextAsync(text12);
|
|
17157
17199
|
section.text = redacted.text;
|
|
17158
17200
|
}
|
|
17201
|
+
if (!legitimateEmpty && !noEvidence) {
|
|
17202
|
+
const values = extractStructuredValues(facet.key, result.structuredOutput);
|
|
17203
|
+
if (options.redactor) {
|
|
17204
|
+
if (values.occupation) {
|
|
17205
|
+
values.occupation = (await options.redactor.redactTextAsync(values.occupation)).text;
|
|
17206
|
+
}
|
|
17207
|
+
if (values.interests) {
|
|
17208
|
+
const gated = [];
|
|
17209
|
+
for (const interest of values.interests) {
|
|
17210
|
+
gated.push((await options.redactor.redactTextAsync(interest)).text);
|
|
17211
|
+
}
|
|
17212
|
+
values.interests = gated;
|
|
17213
|
+
}
|
|
17214
|
+
}
|
|
17215
|
+
const occupation = normalizeOccupation(values.occupation);
|
|
17216
|
+
if (occupation !== void 0) section.occupation = occupation;
|
|
17217
|
+
const interests = normalizeInterests(values.interests);
|
|
17218
|
+
if (interests !== void 0) section.interests = interests;
|
|
17219
|
+
}
|
|
17159
17220
|
return section;
|
|
17160
17221
|
}
|
|
17161
17222
|
function fallbackSection(facet, prior) {
|
|
@@ -17172,6 +17233,7 @@ function fallbackSection(facet, prior) {
|
|
|
17172
17233
|
};
|
|
17173
17234
|
}
|
|
17174
17235
|
function buildFacetSystemPrompt(facet) {
|
|
17236
|
+
const structuredField = FACET_STRUCTURED_RESPONSE_HINT[facet.key] ?? "";
|
|
17175
17237
|
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.
|
|
17176
17238
|
|
|
17177
17239
|
Task for this section:
|
|
@@ -17182,7 +17244,7 @@ Rules:
|
|
|
17182
17244
|
- If the memories don't cover this section, return an empty summary with hasEvidence=false. Do not pad or guess.
|
|
17183
17245
|
- Write in third person about the person, in a natural voice suitable for a public profile (no "I"/"you", no name repetition).
|
|
17184
17246
|
- Be concise and specific; no preamble, hedging, or meta-commentary.
|
|
17185
|
-
- Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false
|
|
17247
|
+
- Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false>${structuredField} }.`;
|
|
17186
17248
|
}
|
|
17187
17249
|
function extractFacetText(structured) {
|
|
17188
17250
|
if (structured && typeof structured === "object") {
|
|
@@ -17193,6 +17255,73 @@ function extractFacetText(structured) {
|
|
|
17193
17255
|
}
|
|
17194
17256
|
return { text: "", legitimateEmpty: false };
|
|
17195
17257
|
}
|
|
17258
|
+
function extractStructuredValues(key, structured) {
|
|
17259
|
+
if (!structured || typeof structured !== "object") return {};
|
|
17260
|
+
const obj = structured;
|
|
17261
|
+
if (key === "work_role") {
|
|
17262
|
+
return typeof obj.occupation === "string" ? { occupation: obj.occupation } : {};
|
|
17263
|
+
}
|
|
17264
|
+
if (key === "interests") {
|
|
17265
|
+
const list = coerceInterestList(obj.interests);
|
|
17266
|
+
if (list) return { interests: list.slice(0, MAX_RAW_INTERESTS) };
|
|
17267
|
+
}
|
|
17268
|
+
return {};
|
|
17269
|
+
}
|
|
17270
|
+
function coerceInterestList(value) {
|
|
17271
|
+
if (Array.isArray(value)) return value.filter((i) => typeof i === "string");
|
|
17272
|
+
if (typeof value !== "string") return void 0;
|
|
17273
|
+
const trimmed = value.trim();
|
|
17274
|
+
if (trimmed.startsWith("[")) {
|
|
17275
|
+
try {
|
|
17276
|
+
const parsed = JSON.parse(trimmed);
|
|
17277
|
+
if (Array.isArray(parsed)) return parsed.filter((i) => typeof i === "string");
|
|
17278
|
+
} catch {
|
|
17279
|
+
if (trimmed.endsWith("]")) return splitInterestFragments(trimmed.slice(1, -1));
|
|
17280
|
+
}
|
|
17281
|
+
}
|
|
17282
|
+
return splitInterestFragments(trimmed);
|
|
17283
|
+
}
|
|
17284
|
+
function splitInterestFragments(source) {
|
|
17285
|
+
return source.split(",").map((fragment) => {
|
|
17286
|
+
let entry = fragment.trim();
|
|
17287
|
+
if (entry.startsWith("[")) entry = entry.slice(1);
|
|
17288
|
+
if (entry.endsWith("]")) entry = entry.slice(0, -1);
|
|
17289
|
+
entry = entry.trim();
|
|
17290
|
+
const quote = entry[0];
|
|
17291
|
+
if ((quote === '"' || quote === "'") && entry.endsWith(quote)) entry = entry.slice(1, -1);
|
|
17292
|
+
return entry;
|
|
17293
|
+
});
|
|
17294
|
+
}
|
|
17295
|
+
function normalizeOccupation(value) {
|
|
17296
|
+
if (value === void 0) return void 0;
|
|
17297
|
+
const trimmed = value.trim();
|
|
17298
|
+
if (!trimmed) return void 0;
|
|
17299
|
+
const length = codePointLength(trimmed);
|
|
17300
|
+
if (length > NEARBY_MAX_OCCUPATION_CODEPOINTS) {
|
|
17301
|
+
getLogger().warn(
|
|
17302
|
+
"[memory/synthesizeProfile] occupation exceeded the publishable length; dropping it",
|
|
17303
|
+
{ length, max: NEARBY_MAX_OCCUPATION_CODEPOINTS }
|
|
17304
|
+
);
|
|
17305
|
+
return void 0;
|
|
17306
|
+
}
|
|
17307
|
+
return trimmed;
|
|
17308
|
+
}
|
|
17309
|
+
function normalizeInterests(values) {
|
|
17310
|
+
if (values === void 0) return void 0;
|
|
17311
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17312
|
+
const out = [];
|
|
17313
|
+
for (const value of values) {
|
|
17314
|
+
const trimmed = value.trim();
|
|
17315
|
+
if (!trimmed) continue;
|
|
17316
|
+
if (codePointLength(trimmed) > NEARBY_MAX_INTEREST_CODEPOINTS) continue;
|
|
17317
|
+
const key = trimmed.toLowerCase();
|
|
17318
|
+
if (seen.has(key)) continue;
|
|
17319
|
+
seen.add(key);
|
|
17320
|
+
out.push(trimmed);
|
|
17321
|
+
if (out.length === NEARBY_MAX_INTERESTS) break;
|
|
17322
|
+
}
|
|
17323
|
+
return out.length > 0 ? out : void 0;
|
|
17324
|
+
}
|
|
17196
17325
|
|
|
17197
17326
|
// src/lib/memory/topicExtract.ts
|
|
17198
17327
|
init_operations2();
|
package/dist/expo/index.d.mts
CHANGED
|
@@ -7631,6 +7631,23 @@ interface ProfileSection {
|
|
|
7631
7631
|
text: string;
|
|
7632
7632
|
/** Vault memory ids this section was grounded on — provenance + delta refresh. */
|
|
7633
7633
|
sourceMemoryIds: string[];
|
|
7634
|
+
/**
|
|
7635
|
+
* Structured occupation — the `work_role` facet only. A short role phrase
|
|
7636
|
+
* (at most 80 code points, PII-gated alongside {@link ProfileSection.text})
|
|
7637
|
+
* that a profile store's `occupation` column takes verbatim.
|
|
7638
|
+
*
|
|
7639
|
+
* Absent when the facet found no evidence, when the model didn't return one,
|
|
7640
|
+
* or when the value it returned couldn't be made publishable. `text` is
|
|
7641
|
+
* unaffected either way, so the prose is never blocked on this.
|
|
7642
|
+
*/
|
|
7643
|
+
occupation?: string;
|
|
7644
|
+
/**
|
|
7645
|
+
* Structured interests — the `interests` facet only. Discrete entries,
|
|
7646
|
+
* trimmed and deduped case- and space-insensitively (first spelling wins), at
|
|
7647
|
+
* most 12 items of at most 40 code points each, ready for a profile store's
|
|
7648
|
+
* `interests` column. Absent when nothing survived normalization.
|
|
7649
|
+
*/
|
|
7650
|
+
interests?: string[];
|
|
7634
7651
|
/** Unix ms this section was generated. */
|
|
7635
7652
|
generatedAt: number;
|
|
7636
7653
|
/** True when regeneration failed and a prior section value was carried
|
|
@@ -7647,10 +7664,12 @@ interface ProfileConfigFingerprint {
|
|
|
7647
7664
|
/** Facet keys present in the doc, sorted. */
|
|
7648
7665
|
facetKeys: ProfileFacetKey[];
|
|
7649
7666
|
/** Order-independent digest of each facet's full definition (key + label +
|
|
7650
|
-
* query + guidance)
|
|
7651
|
-
*
|
|
7652
|
-
*
|
|
7653
|
-
*
|
|
7667
|
+
* query + guidance) and the response schema it is synthesized under. Reuse
|
|
7668
|
+
* must invalidate when a facet's PROMPT changes, not just its key set —
|
|
7669
|
+
* otherwise reused sections carry text generated under the old definition,
|
|
7670
|
+
* or under an output schema that predates a field the caller now reads.
|
|
7671
|
+
* Facet display order does NOT invalidate (sections are rebuilt in facet
|
|
7672
|
+
* order and reused by key). */
|
|
7654
7673
|
facetsSignature: string;
|
|
7655
7674
|
/** Scopes the facts were drawn from, sorted. */
|
|
7656
7675
|
scopes: string[];
|
package/dist/expo/index.d.ts
CHANGED
|
@@ -7631,6 +7631,23 @@ interface ProfileSection {
|
|
|
7631
7631
|
text: string;
|
|
7632
7632
|
/** Vault memory ids this section was grounded on — provenance + delta refresh. */
|
|
7633
7633
|
sourceMemoryIds: string[];
|
|
7634
|
+
/**
|
|
7635
|
+
* Structured occupation — the `work_role` facet only. A short role phrase
|
|
7636
|
+
* (at most 80 code points, PII-gated alongside {@link ProfileSection.text})
|
|
7637
|
+
* that a profile store's `occupation` column takes verbatim.
|
|
7638
|
+
*
|
|
7639
|
+
* Absent when the facet found no evidence, when the model didn't return one,
|
|
7640
|
+
* or when the value it returned couldn't be made publishable. `text` is
|
|
7641
|
+
* unaffected either way, so the prose is never blocked on this.
|
|
7642
|
+
*/
|
|
7643
|
+
occupation?: string;
|
|
7644
|
+
/**
|
|
7645
|
+
* Structured interests — the `interests` facet only. Discrete entries,
|
|
7646
|
+
* trimmed and deduped case- and space-insensitively (first spelling wins), at
|
|
7647
|
+
* most 12 items of at most 40 code points each, ready for a profile store's
|
|
7648
|
+
* `interests` column. Absent when nothing survived normalization.
|
|
7649
|
+
*/
|
|
7650
|
+
interests?: string[];
|
|
7634
7651
|
/** Unix ms this section was generated. */
|
|
7635
7652
|
generatedAt: number;
|
|
7636
7653
|
/** True when regeneration failed and a prior section value was carried
|
|
@@ -7647,10 +7664,12 @@ interface ProfileConfigFingerprint {
|
|
|
7647
7664
|
/** Facet keys present in the doc, sorted. */
|
|
7648
7665
|
facetKeys: ProfileFacetKey[];
|
|
7649
7666
|
/** Order-independent digest of each facet's full definition (key + label +
|
|
7650
|
-
* query + guidance)
|
|
7651
|
-
*
|
|
7652
|
-
*
|
|
7653
|
-
*
|
|
7667
|
+
* query + guidance) and the response schema it is synthesized under. Reuse
|
|
7668
|
+
* must invalidate when a facet's PROMPT changes, not just its key set —
|
|
7669
|
+
* otherwise reused sections carry text generated under the old definition,
|
|
7670
|
+
* or under an output schema that predates a field the caller now reads.
|
|
7671
|
+
* Facet display order does NOT invalidate (sections are rebuilt in facet
|
|
7672
|
+
* order and reused by key). */
|
|
7654
7673
|
facetsSignature: string;
|
|
7655
7674
|
/** Scopes the facts were drawn from, sorted. */
|
|
7656
7675
|
scopes: string[];
|
package/dist/expo/index.mjs
CHANGED
|
@@ -8114,21 +8114,63 @@ var DEFAULT_PROFILE_FACETS = [
|
|
|
8114
8114
|
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."
|
|
8115
8115
|
}
|
|
8116
8116
|
];
|
|
8117
|
+
var NEARBY_MAX_OCCUPATION_CODEPOINTS = 80;
|
|
8118
|
+
var NEARBY_MAX_INTERESTS = 12;
|
|
8119
|
+
var NEARBY_MAX_INTEREST_CODEPOINTS = 40;
|
|
8120
|
+
var MAX_RAW_INTERESTS = NEARBY_MAX_INTERESTS * 2;
|
|
8121
|
+
function codePointLength(value) {
|
|
8122
|
+
return [...value].length;
|
|
8123
|
+
}
|
|
8124
|
+
var FACET_BASE_PROPERTIES = {
|
|
8125
|
+
summary: {
|
|
8126
|
+
type: "string",
|
|
8127
|
+
description: "The synthesized section prose. Empty string if the memories don't cover it."
|
|
8128
|
+
},
|
|
8129
|
+
hasEvidence: {
|
|
8130
|
+
type: "boolean",
|
|
8131
|
+
description: "False when the supplied memories don't support any claim for this facet."
|
|
8132
|
+
}
|
|
8133
|
+
};
|
|
8117
8134
|
var FACET_RESPONSE_SCHEMA = {
|
|
8135
|
+
type: "object",
|
|
8136
|
+
properties: FACET_BASE_PROPERTIES,
|
|
8137
|
+
required: ["summary", "hasEvidence"],
|
|
8138
|
+
additionalProperties: false
|
|
8139
|
+
};
|
|
8140
|
+
var WORK_ROLE_RESPONSE_SCHEMA = {
|
|
8118
8141
|
type: "object",
|
|
8119
8142
|
properties: {
|
|
8120
|
-
|
|
8143
|
+
...FACET_BASE_PROPERTIES,
|
|
8144
|
+
occupation: {
|
|
8121
8145
|
type: "string",
|
|
8122
|
-
description:
|
|
8123
|
-
},
|
|
8124
|
-
hasEvidence: {
|
|
8125
|
-
type: "boolean",
|
|
8126
|
-
description: "False when the supplied memories don't support any claim for this facet."
|
|
8146
|
+
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.`
|
|
8127
8147
|
}
|
|
8128
8148
|
},
|
|
8129
|
-
required: ["summary", "hasEvidence"],
|
|
8149
|
+
required: ["summary", "hasEvidence", "occupation"],
|
|
8150
|
+
additionalProperties: false
|
|
8151
|
+
};
|
|
8152
|
+
var INTERESTS_RESPONSE_SCHEMA = {
|
|
8153
|
+
type: "object",
|
|
8154
|
+
properties: {
|
|
8155
|
+
...FACET_BASE_PROPERTIES,
|
|
8156
|
+
interests: {
|
|
8157
|
+
type: "array",
|
|
8158
|
+
items: { type: "string" },
|
|
8159
|
+
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.`
|
|
8160
|
+
}
|
|
8161
|
+
},
|
|
8162
|
+
required: ["summary", "hasEvidence", "interests"],
|
|
8130
8163
|
additionalProperties: false
|
|
8131
8164
|
};
|
|
8165
|
+
function facetResponseSchema(key) {
|
|
8166
|
+
if (key === "work_role") return WORK_ROLE_RESPONSE_SCHEMA;
|
|
8167
|
+
if (key === "interests") return INTERESTS_RESPONSE_SCHEMA;
|
|
8168
|
+
return FACET_RESPONSE_SCHEMA;
|
|
8169
|
+
}
|
|
8170
|
+
var FACET_STRUCTURED_RESPONSE_HINT = {
|
|
8171
|
+
work_role: `, "occupation": <the role as a standalone phrase of at most ${NEARBY_MAX_OCCUPATION_CODEPOINTS} characters, e.g. "Backend engineer, fintech", or "">`,
|
|
8172
|
+
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 []>`
|
|
8173
|
+
};
|
|
8132
8174
|
async function synthesizeProfile(ctx, options = {}) {
|
|
8133
8175
|
if (!ctx.vaultCtx) {
|
|
8134
8176
|
throw new Error("synthesizeProfile requires ctx.vaultCtx (vault-backed facts).");
|
|
@@ -8224,7 +8266,7 @@ function reviewedMemoryIdsSignature(ids) {
|
|
|
8224
8266
|
return [...new Set(ids)].sort().join("\n");
|
|
8225
8267
|
}
|
|
8226
8268
|
function facetsSignature(facets) {
|
|
8227
|
-
return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance])).sort().join("\n");
|
|
8269
|
+
return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance, facetResponseSchema(f.key)])).sort().join("\n");
|
|
8228
8270
|
}
|
|
8229
8271
|
function trendsEqual(a, b) {
|
|
8230
8272
|
if (!a) return false;
|
|
@@ -8372,7 +8414,7 @@ async function synthesizeFacet(facet, ctx, options, prior) {
|
|
|
8372
8414
|
types: ["fact"],
|
|
8373
8415
|
maxTokens: DEFAULT_FACET_MAX_TOKENS,
|
|
8374
8416
|
systemPrompt: buildFacetSystemPrompt(facet),
|
|
8375
|
-
responseSchema:
|
|
8417
|
+
responseSchema: facetResponseSchema(facet.key),
|
|
8376
8418
|
memories
|
|
8377
8419
|
});
|
|
8378
8420
|
const noEvidence = result.basedOn.memoryIds.length === 0;
|
|
@@ -8395,6 +8437,25 @@ async function synthesizeFacet(facet, ctx, options, prior) {
|
|
|
8395
8437
|
const redacted = await options.redactor.redactTextAsync(text12);
|
|
8396
8438
|
section.text = redacted.text;
|
|
8397
8439
|
}
|
|
8440
|
+
if (!legitimateEmpty && !noEvidence) {
|
|
8441
|
+
const values = extractStructuredValues(facet.key, result.structuredOutput);
|
|
8442
|
+
if (options.redactor) {
|
|
8443
|
+
if (values.occupation) {
|
|
8444
|
+
values.occupation = (await options.redactor.redactTextAsync(values.occupation)).text;
|
|
8445
|
+
}
|
|
8446
|
+
if (values.interests) {
|
|
8447
|
+
const gated = [];
|
|
8448
|
+
for (const interest of values.interests) {
|
|
8449
|
+
gated.push((await options.redactor.redactTextAsync(interest)).text);
|
|
8450
|
+
}
|
|
8451
|
+
values.interests = gated;
|
|
8452
|
+
}
|
|
8453
|
+
}
|
|
8454
|
+
const occupation = normalizeOccupation(values.occupation);
|
|
8455
|
+
if (occupation !== void 0) section.occupation = occupation;
|
|
8456
|
+
const interests = normalizeInterests(values.interests);
|
|
8457
|
+
if (interests !== void 0) section.interests = interests;
|
|
8458
|
+
}
|
|
8398
8459
|
return section;
|
|
8399
8460
|
}
|
|
8400
8461
|
function fallbackSection(facet, prior) {
|
|
@@ -8411,6 +8472,7 @@ function fallbackSection(facet, prior) {
|
|
|
8411
8472
|
};
|
|
8412
8473
|
}
|
|
8413
8474
|
function buildFacetSystemPrompt(facet) {
|
|
8475
|
+
const structuredField = FACET_STRUCTURED_RESPONSE_HINT[facet.key] ?? "";
|
|
8414
8476
|
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.
|
|
8415
8477
|
|
|
8416
8478
|
Task for this section:
|
|
@@ -8421,7 +8483,7 @@ Rules:
|
|
|
8421
8483
|
- If the memories don't cover this section, return an empty summary with hasEvidence=false. Do not pad or guess.
|
|
8422
8484
|
- Write in third person about the person, in a natural voice suitable for a public profile (no "I"/"you", no name repetition).
|
|
8423
8485
|
- Be concise and specific; no preamble, hedging, or meta-commentary.
|
|
8424
|
-
- Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false
|
|
8486
|
+
- Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false>${structuredField} }.`;
|
|
8425
8487
|
}
|
|
8426
8488
|
function extractFacetText(structured) {
|
|
8427
8489
|
if (structured && typeof structured === "object") {
|
|
@@ -8432,6 +8494,73 @@ function extractFacetText(structured) {
|
|
|
8432
8494
|
}
|
|
8433
8495
|
return { text: "", legitimateEmpty: false };
|
|
8434
8496
|
}
|
|
8497
|
+
function extractStructuredValues(key, structured) {
|
|
8498
|
+
if (!structured || typeof structured !== "object") return {};
|
|
8499
|
+
const obj = structured;
|
|
8500
|
+
if (key === "work_role") {
|
|
8501
|
+
return typeof obj.occupation === "string" ? { occupation: obj.occupation } : {};
|
|
8502
|
+
}
|
|
8503
|
+
if (key === "interests") {
|
|
8504
|
+
const list = coerceInterestList(obj.interests);
|
|
8505
|
+
if (list) return { interests: list.slice(0, MAX_RAW_INTERESTS) };
|
|
8506
|
+
}
|
|
8507
|
+
return {};
|
|
8508
|
+
}
|
|
8509
|
+
function coerceInterestList(value) {
|
|
8510
|
+
if (Array.isArray(value)) return value.filter((i) => typeof i === "string");
|
|
8511
|
+
if (typeof value !== "string") return void 0;
|
|
8512
|
+
const trimmed = value.trim();
|
|
8513
|
+
if (trimmed.startsWith("[")) {
|
|
8514
|
+
try {
|
|
8515
|
+
const parsed = JSON.parse(trimmed);
|
|
8516
|
+
if (Array.isArray(parsed)) return parsed.filter((i) => typeof i === "string");
|
|
8517
|
+
} catch {
|
|
8518
|
+
if (trimmed.endsWith("]")) return splitInterestFragments(trimmed.slice(1, -1));
|
|
8519
|
+
}
|
|
8520
|
+
}
|
|
8521
|
+
return splitInterestFragments(trimmed);
|
|
8522
|
+
}
|
|
8523
|
+
function splitInterestFragments(source) {
|
|
8524
|
+
return source.split(",").map((fragment) => {
|
|
8525
|
+
let entry = fragment.trim();
|
|
8526
|
+
if (entry.startsWith("[")) entry = entry.slice(1);
|
|
8527
|
+
if (entry.endsWith("]")) entry = entry.slice(0, -1);
|
|
8528
|
+
entry = entry.trim();
|
|
8529
|
+
const quote = entry[0];
|
|
8530
|
+
if ((quote === '"' || quote === "'") && entry.endsWith(quote)) entry = entry.slice(1, -1);
|
|
8531
|
+
return entry;
|
|
8532
|
+
});
|
|
8533
|
+
}
|
|
8534
|
+
function normalizeOccupation(value) {
|
|
8535
|
+
if (value === void 0) return void 0;
|
|
8536
|
+
const trimmed = value.trim();
|
|
8537
|
+
if (!trimmed) return void 0;
|
|
8538
|
+
const length = codePointLength(trimmed);
|
|
8539
|
+
if (length > NEARBY_MAX_OCCUPATION_CODEPOINTS) {
|
|
8540
|
+
getLogger().warn(
|
|
8541
|
+
"[memory/synthesizeProfile] occupation exceeded the publishable length; dropping it",
|
|
8542
|
+
{ length, max: NEARBY_MAX_OCCUPATION_CODEPOINTS }
|
|
8543
|
+
);
|
|
8544
|
+
return void 0;
|
|
8545
|
+
}
|
|
8546
|
+
return trimmed;
|
|
8547
|
+
}
|
|
8548
|
+
function normalizeInterests(values) {
|
|
8549
|
+
if (values === void 0) return void 0;
|
|
8550
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8551
|
+
const out = [];
|
|
8552
|
+
for (const value of values) {
|
|
8553
|
+
const trimmed = value.trim();
|
|
8554
|
+
if (!trimmed) continue;
|
|
8555
|
+
if (codePointLength(trimmed) > NEARBY_MAX_INTEREST_CODEPOINTS) continue;
|
|
8556
|
+
const key = trimmed.toLowerCase();
|
|
8557
|
+
if (seen.has(key)) continue;
|
|
8558
|
+
seen.add(key);
|
|
8559
|
+
out.push(trimmed);
|
|
8560
|
+
if (out.length === NEARBY_MAX_INTERESTS) break;
|
|
8561
|
+
}
|
|
8562
|
+
return out.length > 0 ? out : void 0;
|
|
8563
|
+
}
|
|
8435
8564
|
|
|
8436
8565
|
// src/lib/memory/topicExtract.ts
|
|
8437
8566
|
var TOPIC_EXTRACTION_BATCH_SIZE = 10;
|