@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.
- 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/server/index.cjs
CHANGED
|
@@ -29893,21 +29893,63 @@ var DEFAULT_PROFILE_FACETS = [
|
|
|
29893
29893
|
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."
|
|
29894
29894
|
}
|
|
29895
29895
|
];
|
|
29896
|
+
var NEARBY_MAX_OCCUPATION_CODEPOINTS = 80;
|
|
29897
|
+
var NEARBY_MAX_INTERESTS = 12;
|
|
29898
|
+
var NEARBY_MAX_INTEREST_CODEPOINTS = 40;
|
|
29899
|
+
var MAX_RAW_INTERESTS = NEARBY_MAX_INTERESTS * 2;
|
|
29900
|
+
function codePointLength(value) {
|
|
29901
|
+
return [...value].length;
|
|
29902
|
+
}
|
|
29903
|
+
var FACET_BASE_PROPERTIES = {
|
|
29904
|
+
summary: {
|
|
29905
|
+
type: "string",
|
|
29906
|
+
description: "The synthesized section prose. Empty string if the memories don't cover it."
|
|
29907
|
+
},
|
|
29908
|
+
hasEvidence: {
|
|
29909
|
+
type: "boolean",
|
|
29910
|
+
description: "False when the supplied memories don't support any claim for this facet."
|
|
29911
|
+
}
|
|
29912
|
+
};
|
|
29896
29913
|
var FACET_RESPONSE_SCHEMA = {
|
|
29914
|
+
type: "object",
|
|
29915
|
+
properties: FACET_BASE_PROPERTIES,
|
|
29916
|
+
required: ["summary", "hasEvidence"],
|
|
29917
|
+
additionalProperties: false
|
|
29918
|
+
};
|
|
29919
|
+
var WORK_ROLE_RESPONSE_SCHEMA = {
|
|
29897
29920
|
type: "object",
|
|
29898
29921
|
properties: {
|
|
29899
|
-
|
|
29922
|
+
...FACET_BASE_PROPERTIES,
|
|
29923
|
+
occupation: {
|
|
29900
29924
|
type: "string",
|
|
29901
|
-
description:
|
|
29902
|
-
},
|
|
29903
|
-
hasEvidence: {
|
|
29904
|
-
type: "boolean",
|
|
29905
|
-
description: "False when the supplied memories don't support any claim for this facet."
|
|
29925
|
+
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.`
|
|
29906
29926
|
}
|
|
29907
29927
|
},
|
|
29908
|
-
required: ["summary", "hasEvidence"],
|
|
29928
|
+
required: ["summary", "hasEvidence", "occupation"],
|
|
29929
|
+
additionalProperties: false
|
|
29930
|
+
};
|
|
29931
|
+
var INTERESTS_RESPONSE_SCHEMA = {
|
|
29932
|
+
type: "object",
|
|
29933
|
+
properties: {
|
|
29934
|
+
...FACET_BASE_PROPERTIES,
|
|
29935
|
+
interests: {
|
|
29936
|
+
type: "array",
|
|
29937
|
+
items: { type: "string" },
|
|
29938
|
+
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.`
|
|
29939
|
+
}
|
|
29940
|
+
},
|
|
29941
|
+
required: ["summary", "hasEvidence", "interests"],
|
|
29909
29942
|
additionalProperties: false
|
|
29910
29943
|
};
|
|
29944
|
+
function facetResponseSchema(key) {
|
|
29945
|
+
if (key === "work_role") return WORK_ROLE_RESPONSE_SCHEMA;
|
|
29946
|
+
if (key === "interests") return INTERESTS_RESPONSE_SCHEMA;
|
|
29947
|
+
return FACET_RESPONSE_SCHEMA;
|
|
29948
|
+
}
|
|
29949
|
+
var FACET_STRUCTURED_RESPONSE_HINT = {
|
|
29950
|
+
work_role: `, "occupation": <the role as a standalone phrase of at most ${NEARBY_MAX_OCCUPATION_CODEPOINTS} characters, e.g. "Backend engineer, fintech", or "">`,
|
|
29951
|
+
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 []>`
|
|
29952
|
+
};
|
|
29911
29953
|
async function synthesizeProfile(ctx, options = {}) {
|
|
29912
29954
|
if (!ctx.vaultCtx) {
|
|
29913
29955
|
throw new Error("synthesizeProfile requires ctx.vaultCtx (vault-backed facts).");
|
|
@@ -30003,7 +30045,7 @@ function reviewedMemoryIdsSignature(ids) {
|
|
|
30003
30045
|
return [...new Set(ids)].sort().join("\n");
|
|
30004
30046
|
}
|
|
30005
30047
|
function facetsSignature(facets) {
|
|
30006
|
-
return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance])).sort().join("\n");
|
|
30048
|
+
return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance, facetResponseSchema(f.key)])).sort().join("\n");
|
|
30007
30049
|
}
|
|
30008
30050
|
function trendsEqual(a, b) {
|
|
30009
30051
|
if (!a) return false;
|
|
@@ -30151,7 +30193,7 @@ async function synthesizeFacet(facet, ctx, options, prior) {
|
|
|
30151
30193
|
types: ["fact"],
|
|
30152
30194
|
maxTokens: DEFAULT_FACET_MAX_TOKENS,
|
|
30153
30195
|
systemPrompt: buildFacetSystemPrompt(facet),
|
|
30154
|
-
responseSchema:
|
|
30196
|
+
responseSchema: facetResponseSchema(facet.key),
|
|
30155
30197
|
memories
|
|
30156
30198
|
});
|
|
30157
30199
|
const noEvidence = result.basedOn.memoryIds.length === 0;
|
|
@@ -30174,6 +30216,25 @@ async function synthesizeFacet(facet, ctx, options, prior) {
|
|
|
30174
30216
|
const redacted = await options.redactor.redactTextAsync(text12);
|
|
30175
30217
|
section.text = redacted.text;
|
|
30176
30218
|
}
|
|
30219
|
+
if (!legitimateEmpty && !noEvidence) {
|
|
30220
|
+
const values = extractStructuredValues(facet.key, result.structuredOutput);
|
|
30221
|
+
if (options.redactor) {
|
|
30222
|
+
if (values.occupation) {
|
|
30223
|
+
values.occupation = (await options.redactor.redactTextAsync(values.occupation)).text;
|
|
30224
|
+
}
|
|
30225
|
+
if (values.interests) {
|
|
30226
|
+
const gated = [];
|
|
30227
|
+
for (const interest of values.interests) {
|
|
30228
|
+
gated.push((await options.redactor.redactTextAsync(interest)).text);
|
|
30229
|
+
}
|
|
30230
|
+
values.interests = gated;
|
|
30231
|
+
}
|
|
30232
|
+
}
|
|
30233
|
+
const occupation = normalizeOccupation(values.occupation);
|
|
30234
|
+
if (occupation !== void 0) section.occupation = occupation;
|
|
30235
|
+
const interests = normalizeInterests(values.interests);
|
|
30236
|
+
if (interests !== void 0) section.interests = interests;
|
|
30237
|
+
}
|
|
30177
30238
|
return section;
|
|
30178
30239
|
}
|
|
30179
30240
|
function fallbackSection(facet, prior) {
|
|
@@ -30190,6 +30251,7 @@ function fallbackSection(facet, prior) {
|
|
|
30190
30251
|
};
|
|
30191
30252
|
}
|
|
30192
30253
|
function buildFacetSystemPrompt(facet) {
|
|
30254
|
+
const structuredField = FACET_STRUCTURED_RESPONSE_HINT[facet.key] ?? "";
|
|
30193
30255
|
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.
|
|
30194
30256
|
|
|
30195
30257
|
Task for this section:
|
|
@@ -30200,7 +30262,7 @@ Rules:
|
|
|
30200
30262
|
- If the memories don't cover this section, return an empty summary with hasEvidence=false. Do not pad or guess.
|
|
30201
30263
|
- Write in third person about the person, in a natural voice suitable for a public profile (no "I"/"you", no name repetition).
|
|
30202
30264
|
- Be concise and specific; no preamble, hedging, or meta-commentary.
|
|
30203
|
-
- Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false
|
|
30265
|
+
- Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false>${structuredField} }.`;
|
|
30204
30266
|
}
|
|
30205
30267
|
function extractFacetText(structured) {
|
|
30206
30268
|
if (structured && typeof structured === "object") {
|
|
@@ -30211,6 +30273,73 @@ function extractFacetText(structured) {
|
|
|
30211
30273
|
}
|
|
30212
30274
|
return { text: "", legitimateEmpty: false };
|
|
30213
30275
|
}
|
|
30276
|
+
function extractStructuredValues(key, structured) {
|
|
30277
|
+
if (!structured || typeof structured !== "object") return {};
|
|
30278
|
+
const obj = structured;
|
|
30279
|
+
if (key === "work_role") {
|
|
30280
|
+
return typeof obj.occupation === "string" ? { occupation: obj.occupation } : {};
|
|
30281
|
+
}
|
|
30282
|
+
if (key === "interests") {
|
|
30283
|
+
const list = coerceInterestList(obj.interests);
|
|
30284
|
+
if (list) return { interests: list.slice(0, MAX_RAW_INTERESTS) };
|
|
30285
|
+
}
|
|
30286
|
+
return {};
|
|
30287
|
+
}
|
|
30288
|
+
function coerceInterestList(value) {
|
|
30289
|
+
if (Array.isArray(value)) return value.filter((i) => typeof i === "string");
|
|
30290
|
+
if (typeof value !== "string") return void 0;
|
|
30291
|
+
const trimmed = value.trim();
|
|
30292
|
+
if (trimmed.startsWith("[")) {
|
|
30293
|
+
try {
|
|
30294
|
+
const parsed = JSON.parse(trimmed);
|
|
30295
|
+
if (Array.isArray(parsed)) return parsed.filter((i) => typeof i === "string");
|
|
30296
|
+
} catch {
|
|
30297
|
+
if (trimmed.endsWith("]")) return splitInterestFragments(trimmed.slice(1, -1));
|
|
30298
|
+
}
|
|
30299
|
+
}
|
|
30300
|
+
return splitInterestFragments(trimmed);
|
|
30301
|
+
}
|
|
30302
|
+
function splitInterestFragments(source) {
|
|
30303
|
+
return source.split(",").map((fragment) => {
|
|
30304
|
+
let entry = fragment.trim();
|
|
30305
|
+
if (entry.startsWith("[")) entry = entry.slice(1);
|
|
30306
|
+
if (entry.endsWith("]")) entry = entry.slice(0, -1);
|
|
30307
|
+
entry = entry.trim();
|
|
30308
|
+
const quote = entry[0];
|
|
30309
|
+
if ((quote === '"' || quote === "'") && entry.endsWith(quote)) entry = entry.slice(1, -1);
|
|
30310
|
+
return entry;
|
|
30311
|
+
});
|
|
30312
|
+
}
|
|
30313
|
+
function normalizeOccupation(value) {
|
|
30314
|
+
if (value === void 0) return void 0;
|
|
30315
|
+
const trimmed = value.trim();
|
|
30316
|
+
if (!trimmed) return void 0;
|
|
30317
|
+
const length = codePointLength(trimmed);
|
|
30318
|
+
if (length > NEARBY_MAX_OCCUPATION_CODEPOINTS) {
|
|
30319
|
+
getLogger().warn(
|
|
30320
|
+
"[memory/synthesizeProfile] occupation exceeded the publishable length; dropping it",
|
|
30321
|
+
{ length, max: NEARBY_MAX_OCCUPATION_CODEPOINTS }
|
|
30322
|
+
);
|
|
30323
|
+
return void 0;
|
|
30324
|
+
}
|
|
30325
|
+
return trimmed;
|
|
30326
|
+
}
|
|
30327
|
+
function normalizeInterests(values) {
|
|
30328
|
+
if (values === void 0) return void 0;
|
|
30329
|
+
const seen = /* @__PURE__ */ new Set();
|
|
30330
|
+
const out = [];
|
|
30331
|
+
for (const value of values) {
|
|
30332
|
+
const trimmed = value.trim();
|
|
30333
|
+
if (!trimmed) continue;
|
|
30334
|
+
if (codePointLength(trimmed) > NEARBY_MAX_INTEREST_CODEPOINTS) continue;
|
|
30335
|
+
const key = trimmed.toLowerCase();
|
|
30336
|
+
if (seen.has(key)) continue;
|
|
30337
|
+
seen.add(key);
|
|
30338
|
+
out.push(trimmed);
|
|
30339
|
+
if (out.length === NEARBY_MAX_INTERESTS) break;
|
|
30340
|
+
}
|
|
30341
|
+
return out.length > 0 ? out : void 0;
|
|
30342
|
+
}
|
|
30214
30343
|
|
|
30215
30344
|
// src/lib/memory/topicExtract.ts
|
|
30216
30345
|
init_operations2();
|
package/dist/server/index.d.mts
CHANGED
|
@@ -8277,6 +8277,23 @@ interface ProfileSection {
|
|
|
8277
8277
|
text: string;
|
|
8278
8278
|
/** Vault memory ids this section was grounded on — provenance + delta refresh. */
|
|
8279
8279
|
sourceMemoryIds: string[];
|
|
8280
|
+
/**
|
|
8281
|
+
* Structured occupation — the `work_role` facet only. A short role phrase
|
|
8282
|
+
* (at most 80 code points, PII-gated alongside {@link ProfileSection.text})
|
|
8283
|
+
* that a profile store's `occupation` column takes verbatim.
|
|
8284
|
+
*
|
|
8285
|
+
* Absent when the facet found no evidence, when the model didn't return one,
|
|
8286
|
+
* or when the value it returned couldn't be made publishable. `text` is
|
|
8287
|
+
* unaffected either way, so the prose is never blocked on this.
|
|
8288
|
+
*/
|
|
8289
|
+
occupation?: string;
|
|
8290
|
+
/**
|
|
8291
|
+
* Structured interests — the `interests` facet only. Discrete entries,
|
|
8292
|
+
* trimmed and deduped case- and space-insensitively (first spelling wins), at
|
|
8293
|
+
* most 12 items of at most 40 code points each, ready for a profile store's
|
|
8294
|
+
* `interests` column. Absent when nothing survived normalization.
|
|
8295
|
+
*/
|
|
8296
|
+
interests?: string[];
|
|
8280
8297
|
/** Unix ms this section was generated. */
|
|
8281
8298
|
generatedAt: number;
|
|
8282
8299
|
/** True when regeneration failed and a prior section value was carried
|
|
@@ -8293,10 +8310,12 @@ interface ProfileConfigFingerprint {
|
|
|
8293
8310
|
/** Facet keys present in the doc, sorted. */
|
|
8294
8311
|
facetKeys: ProfileFacetKey[];
|
|
8295
8312
|
/** Order-independent digest of each facet's full definition (key + label +
|
|
8296
|
-
* query + guidance)
|
|
8297
|
-
*
|
|
8298
|
-
*
|
|
8299
|
-
*
|
|
8313
|
+
* query + guidance) and the response schema it is synthesized under. Reuse
|
|
8314
|
+
* must invalidate when a facet's PROMPT changes, not just its key set —
|
|
8315
|
+
* otherwise reused sections carry text generated under the old definition,
|
|
8316
|
+
* or under an output schema that predates a field the caller now reads.
|
|
8317
|
+
* Facet display order does NOT invalidate (sections are rebuilt in facet
|
|
8318
|
+
* order and reused by key). */
|
|
8300
8319
|
facetsSignature: string;
|
|
8301
8320
|
/** Scopes the facts were drawn from, sorted. */
|
|
8302
8321
|
scopes: string[];
|
package/dist/server/index.d.ts
CHANGED
|
@@ -8277,6 +8277,23 @@ interface ProfileSection {
|
|
|
8277
8277
|
text: string;
|
|
8278
8278
|
/** Vault memory ids this section was grounded on — provenance + delta refresh. */
|
|
8279
8279
|
sourceMemoryIds: string[];
|
|
8280
|
+
/**
|
|
8281
|
+
* Structured occupation — the `work_role` facet only. A short role phrase
|
|
8282
|
+
* (at most 80 code points, PII-gated alongside {@link ProfileSection.text})
|
|
8283
|
+
* that a profile store's `occupation` column takes verbatim.
|
|
8284
|
+
*
|
|
8285
|
+
* Absent when the facet found no evidence, when the model didn't return one,
|
|
8286
|
+
* or when the value it returned couldn't be made publishable. `text` is
|
|
8287
|
+
* unaffected either way, so the prose is never blocked on this.
|
|
8288
|
+
*/
|
|
8289
|
+
occupation?: string;
|
|
8290
|
+
/**
|
|
8291
|
+
* Structured interests — the `interests` facet only. Discrete entries,
|
|
8292
|
+
* trimmed and deduped case- and space-insensitively (first spelling wins), at
|
|
8293
|
+
* most 12 items of at most 40 code points each, ready for a profile store's
|
|
8294
|
+
* `interests` column. Absent when nothing survived normalization.
|
|
8295
|
+
*/
|
|
8296
|
+
interests?: string[];
|
|
8280
8297
|
/** Unix ms this section was generated. */
|
|
8281
8298
|
generatedAt: number;
|
|
8282
8299
|
/** True when regeneration failed and a prior section value was carried
|
|
@@ -8293,10 +8310,12 @@ interface ProfileConfigFingerprint {
|
|
|
8293
8310
|
/** Facet keys present in the doc, sorted. */
|
|
8294
8311
|
facetKeys: ProfileFacetKey[];
|
|
8295
8312
|
/** Order-independent digest of each facet's full definition (key + label +
|
|
8296
|
-
* query + guidance)
|
|
8297
|
-
*
|
|
8298
|
-
*
|
|
8299
|
-
*
|
|
8313
|
+
* query + guidance) and the response schema it is synthesized under. Reuse
|
|
8314
|
+
* must invalidate when a facet's PROMPT changes, not just its key set —
|
|
8315
|
+
* otherwise reused sections carry text generated under the old definition,
|
|
8316
|
+
* or under an output schema that predates a field the caller now reads.
|
|
8317
|
+
* Facet display order does NOT invalidate (sections are rebuilt in facet
|
|
8318
|
+
* order and reused by key). */
|
|
8300
8319
|
facetsSignature: string;
|
|
8301
8320
|
/** Scopes the facts were drawn from, sorted. */
|
|
8302
8321
|
scopes: string[];
|
package/dist/server/index.mjs
CHANGED
|
@@ -6886,21 +6886,63 @@ var DEFAULT_PROFILE_FACETS = [
|
|
|
6886
6886
|
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."
|
|
6887
6887
|
}
|
|
6888
6888
|
];
|
|
6889
|
+
var NEARBY_MAX_OCCUPATION_CODEPOINTS = 80;
|
|
6890
|
+
var NEARBY_MAX_INTERESTS = 12;
|
|
6891
|
+
var NEARBY_MAX_INTEREST_CODEPOINTS = 40;
|
|
6892
|
+
var MAX_RAW_INTERESTS = NEARBY_MAX_INTERESTS * 2;
|
|
6893
|
+
function codePointLength(value) {
|
|
6894
|
+
return [...value].length;
|
|
6895
|
+
}
|
|
6896
|
+
var FACET_BASE_PROPERTIES = {
|
|
6897
|
+
summary: {
|
|
6898
|
+
type: "string",
|
|
6899
|
+
description: "The synthesized section prose. Empty string if the memories don't cover it."
|
|
6900
|
+
},
|
|
6901
|
+
hasEvidence: {
|
|
6902
|
+
type: "boolean",
|
|
6903
|
+
description: "False when the supplied memories don't support any claim for this facet."
|
|
6904
|
+
}
|
|
6905
|
+
};
|
|
6889
6906
|
var FACET_RESPONSE_SCHEMA = {
|
|
6907
|
+
type: "object",
|
|
6908
|
+
properties: FACET_BASE_PROPERTIES,
|
|
6909
|
+
required: ["summary", "hasEvidence"],
|
|
6910
|
+
additionalProperties: false
|
|
6911
|
+
};
|
|
6912
|
+
var WORK_ROLE_RESPONSE_SCHEMA = {
|
|
6890
6913
|
type: "object",
|
|
6891
6914
|
properties: {
|
|
6892
|
-
|
|
6915
|
+
...FACET_BASE_PROPERTIES,
|
|
6916
|
+
occupation: {
|
|
6893
6917
|
type: "string",
|
|
6894
|
-
description:
|
|
6895
|
-
},
|
|
6896
|
-
hasEvidence: {
|
|
6897
|
-
type: "boolean",
|
|
6898
|
-
description: "False when the supplied memories don't support any claim for this facet."
|
|
6918
|
+
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.`
|
|
6899
6919
|
}
|
|
6900
6920
|
},
|
|
6901
|
-
required: ["summary", "hasEvidence"],
|
|
6921
|
+
required: ["summary", "hasEvidence", "occupation"],
|
|
6922
|
+
additionalProperties: false
|
|
6923
|
+
};
|
|
6924
|
+
var INTERESTS_RESPONSE_SCHEMA = {
|
|
6925
|
+
type: "object",
|
|
6926
|
+
properties: {
|
|
6927
|
+
...FACET_BASE_PROPERTIES,
|
|
6928
|
+
interests: {
|
|
6929
|
+
type: "array",
|
|
6930
|
+
items: { type: "string" },
|
|
6931
|
+
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.`
|
|
6932
|
+
}
|
|
6933
|
+
},
|
|
6934
|
+
required: ["summary", "hasEvidence", "interests"],
|
|
6902
6935
|
additionalProperties: false
|
|
6903
6936
|
};
|
|
6937
|
+
function facetResponseSchema(key) {
|
|
6938
|
+
if (key === "work_role") return WORK_ROLE_RESPONSE_SCHEMA;
|
|
6939
|
+
if (key === "interests") return INTERESTS_RESPONSE_SCHEMA;
|
|
6940
|
+
return FACET_RESPONSE_SCHEMA;
|
|
6941
|
+
}
|
|
6942
|
+
var FACET_STRUCTURED_RESPONSE_HINT = {
|
|
6943
|
+
work_role: `, "occupation": <the role as a standalone phrase of at most ${NEARBY_MAX_OCCUPATION_CODEPOINTS} characters, e.g. "Backend engineer, fintech", or "">`,
|
|
6944
|
+
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 []>`
|
|
6945
|
+
};
|
|
6904
6946
|
async function synthesizeProfile(ctx, options = {}) {
|
|
6905
6947
|
if (!ctx.vaultCtx) {
|
|
6906
6948
|
throw new Error("synthesizeProfile requires ctx.vaultCtx (vault-backed facts).");
|
|
@@ -6996,7 +7038,7 @@ function reviewedMemoryIdsSignature(ids) {
|
|
|
6996
7038
|
return [...new Set(ids)].sort().join("\n");
|
|
6997
7039
|
}
|
|
6998
7040
|
function facetsSignature(facets) {
|
|
6999
|
-
return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance])).sort().join("\n");
|
|
7041
|
+
return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance, facetResponseSchema(f.key)])).sort().join("\n");
|
|
7000
7042
|
}
|
|
7001
7043
|
function trendsEqual(a, b) {
|
|
7002
7044
|
if (!a) return false;
|
|
@@ -7144,7 +7186,7 @@ async function synthesizeFacet(facet, ctx, options, prior) {
|
|
|
7144
7186
|
types: ["fact"],
|
|
7145
7187
|
maxTokens: DEFAULT_FACET_MAX_TOKENS,
|
|
7146
7188
|
systemPrompt: buildFacetSystemPrompt(facet),
|
|
7147
|
-
responseSchema:
|
|
7189
|
+
responseSchema: facetResponseSchema(facet.key),
|
|
7148
7190
|
memories
|
|
7149
7191
|
});
|
|
7150
7192
|
const noEvidence = result.basedOn.memoryIds.length === 0;
|
|
@@ -7167,6 +7209,25 @@ async function synthesizeFacet(facet, ctx, options, prior) {
|
|
|
7167
7209
|
const redacted = await options.redactor.redactTextAsync(text12);
|
|
7168
7210
|
section.text = redacted.text;
|
|
7169
7211
|
}
|
|
7212
|
+
if (!legitimateEmpty && !noEvidence) {
|
|
7213
|
+
const values = extractStructuredValues(facet.key, result.structuredOutput);
|
|
7214
|
+
if (options.redactor) {
|
|
7215
|
+
if (values.occupation) {
|
|
7216
|
+
values.occupation = (await options.redactor.redactTextAsync(values.occupation)).text;
|
|
7217
|
+
}
|
|
7218
|
+
if (values.interests) {
|
|
7219
|
+
const gated = [];
|
|
7220
|
+
for (const interest of values.interests) {
|
|
7221
|
+
gated.push((await options.redactor.redactTextAsync(interest)).text);
|
|
7222
|
+
}
|
|
7223
|
+
values.interests = gated;
|
|
7224
|
+
}
|
|
7225
|
+
}
|
|
7226
|
+
const occupation = normalizeOccupation(values.occupation);
|
|
7227
|
+
if (occupation !== void 0) section.occupation = occupation;
|
|
7228
|
+
const interests = normalizeInterests(values.interests);
|
|
7229
|
+
if (interests !== void 0) section.interests = interests;
|
|
7230
|
+
}
|
|
7170
7231
|
return section;
|
|
7171
7232
|
}
|
|
7172
7233
|
function fallbackSection(facet, prior) {
|
|
@@ -7183,6 +7244,7 @@ function fallbackSection(facet, prior) {
|
|
|
7183
7244
|
};
|
|
7184
7245
|
}
|
|
7185
7246
|
function buildFacetSystemPrompt(facet) {
|
|
7247
|
+
const structuredField = FACET_STRUCTURED_RESPONSE_HINT[facet.key] ?? "";
|
|
7186
7248
|
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.
|
|
7187
7249
|
|
|
7188
7250
|
Task for this section:
|
|
@@ -7193,7 +7255,7 @@ Rules:
|
|
|
7193
7255
|
- If the memories don't cover this section, return an empty summary with hasEvidence=false. Do not pad or guess.
|
|
7194
7256
|
- Write in third person about the person, in a natural voice suitable for a public profile (no "I"/"you", no name repetition).
|
|
7195
7257
|
- Be concise and specific; no preamble, hedging, or meta-commentary.
|
|
7196
|
-
- Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false
|
|
7258
|
+
- Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false>${structuredField} }.`;
|
|
7197
7259
|
}
|
|
7198
7260
|
function extractFacetText(structured) {
|
|
7199
7261
|
if (structured && typeof structured === "object") {
|
|
@@ -7204,6 +7266,73 @@ function extractFacetText(structured) {
|
|
|
7204
7266
|
}
|
|
7205
7267
|
return { text: "", legitimateEmpty: false };
|
|
7206
7268
|
}
|
|
7269
|
+
function extractStructuredValues(key, structured) {
|
|
7270
|
+
if (!structured || typeof structured !== "object") return {};
|
|
7271
|
+
const obj = structured;
|
|
7272
|
+
if (key === "work_role") {
|
|
7273
|
+
return typeof obj.occupation === "string" ? { occupation: obj.occupation } : {};
|
|
7274
|
+
}
|
|
7275
|
+
if (key === "interests") {
|
|
7276
|
+
const list = coerceInterestList(obj.interests);
|
|
7277
|
+
if (list) return { interests: list.slice(0, MAX_RAW_INTERESTS) };
|
|
7278
|
+
}
|
|
7279
|
+
return {};
|
|
7280
|
+
}
|
|
7281
|
+
function coerceInterestList(value) {
|
|
7282
|
+
if (Array.isArray(value)) return value.filter((i) => typeof i === "string");
|
|
7283
|
+
if (typeof value !== "string") return void 0;
|
|
7284
|
+
const trimmed = value.trim();
|
|
7285
|
+
if (trimmed.startsWith("[")) {
|
|
7286
|
+
try {
|
|
7287
|
+
const parsed = JSON.parse(trimmed);
|
|
7288
|
+
if (Array.isArray(parsed)) return parsed.filter((i) => typeof i === "string");
|
|
7289
|
+
} catch {
|
|
7290
|
+
if (trimmed.endsWith("]")) return splitInterestFragments(trimmed.slice(1, -1));
|
|
7291
|
+
}
|
|
7292
|
+
}
|
|
7293
|
+
return splitInterestFragments(trimmed);
|
|
7294
|
+
}
|
|
7295
|
+
function splitInterestFragments(source) {
|
|
7296
|
+
return source.split(",").map((fragment) => {
|
|
7297
|
+
let entry = fragment.trim();
|
|
7298
|
+
if (entry.startsWith("[")) entry = entry.slice(1);
|
|
7299
|
+
if (entry.endsWith("]")) entry = entry.slice(0, -1);
|
|
7300
|
+
entry = entry.trim();
|
|
7301
|
+
const quote = entry[0];
|
|
7302
|
+
if ((quote === '"' || quote === "'") && entry.endsWith(quote)) entry = entry.slice(1, -1);
|
|
7303
|
+
return entry;
|
|
7304
|
+
});
|
|
7305
|
+
}
|
|
7306
|
+
function normalizeOccupation(value) {
|
|
7307
|
+
if (value === void 0) return void 0;
|
|
7308
|
+
const trimmed = value.trim();
|
|
7309
|
+
if (!trimmed) return void 0;
|
|
7310
|
+
const length = codePointLength(trimmed);
|
|
7311
|
+
if (length > NEARBY_MAX_OCCUPATION_CODEPOINTS) {
|
|
7312
|
+
getLogger().warn(
|
|
7313
|
+
"[memory/synthesizeProfile] occupation exceeded the publishable length; dropping it",
|
|
7314
|
+
{ length, max: NEARBY_MAX_OCCUPATION_CODEPOINTS }
|
|
7315
|
+
);
|
|
7316
|
+
return void 0;
|
|
7317
|
+
}
|
|
7318
|
+
return trimmed;
|
|
7319
|
+
}
|
|
7320
|
+
function normalizeInterests(values) {
|
|
7321
|
+
if (values === void 0) return void 0;
|
|
7322
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7323
|
+
const out = [];
|
|
7324
|
+
for (const value of values) {
|
|
7325
|
+
const trimmed = value.trim();
|
|
7326
|
+
if (!trimmed) continue;
|
|
7327
|
+
if (codePointLength(trimmed) > NEARBY_MAX_INTEREST_CODEPOINTS) continue;
|
|
7328
|
+
const key = trimmed.toLowerCase();
|
|
7329
|
+
if (seen.has(key)) continue;
|
|
7330
|
+
seen.add(key);
|
|
7331
|
+
out.push(trimmed);
|
|
7332
|
+
if (out.length === NEARBY_MAX_INTERESTS) break;
|
|
7333
|
+
}
|
|
7334
|
+
return out.length > 0 ? out : void 0;
|
|
7335
|
+
}
|
|
7207
7336
|
|
|
7208
7337
|
// src/lib/memory/topicExtract.ts
|
|
7209
7338
|
var TOPIC_EXTRACTION_BATCH_SIZE = 10;
|