@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/react/index.cjs
CHANGED
|
@@ -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
|
-
|
|
18203
|
+
...FACET_BASE_PROPERTIES,
|
|
18204
|
+
occupation: {
|
|
18181
18205
|
type: "string",
|
|
18182
|
-
description:
|
|
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:
|
|
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();
|
package/dist/react/index.d.mts
CHANGED
|
@@ -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)
|
|
8900
|
-
*
|
|
8901
|
-
*
|
|
8902
|
-
*
|
|
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[];
|
package/dist/react/index.d.ts
CHANGED
|
@@ -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)
|
|
8900
|
-
*
|
|
8901
|
-
*
|
|
8902
|
-
*
|
|
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[];
|
package/dist/react/index.mjs
CHANGED
|
@@ -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
|
-
|
|
8960
|
+
...FACET_BASE_PROPERTIES,
|
|
8961
|
+
occupation: {
|
|
8938
8962
|
type: "string",
|
|
8939
|
-
description:
|
|
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:
|
|
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;
|