@graphorin/memory 0.6.0 → 0.7.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/CHANGELOG.md +83 -0
- package/README.md +24 -7
- package/dist/conflict/index.js +0 -1
- package/dist/conflict/pipeline.js +0 -1
- package/dist/conflict/pipeline.js.map +1 -1
- package/dist/conflict/stages/stage3-heuristic-regex.js +0 -1
- package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -1
- package/dist/conflict/types.d.ts +1 -1
- package/dist/conflict/types.js.map +1 -1
- package/dist/consolidator/index.js +2 -0
- package/dist/consolidator/index.js.map +1 -1
- package/dist/consolidator/lock.js +7 -1
- package/dist/consolidator/lock.js.map +1 -1
- package/dist/consolidator/phases/deep.js +19 -12
- package/dist/consolidator/phases/deep.js.map +1 -1
- package/dist/consolidator/phases/induce.d.ts.map +1 -1
- package/dist/consolidator/phases/induce.js +2 -3
- package/dist/consolidator/phases/induce.js.map +1 -1
- package/dist/consolidator/phases/reflect.js +35 -8
- package/dist/consolidator/phases/reflect.js.map +1 -1
- package/dist/consolidator/phases/standard.js +199 -28
- package/dist/consolidator/phases/standard.js.map +1 -1
- package/dist/consolidator/reconcile.js +16 -15
- package/dist/consolidator/reconcile.js.map +1 -1
- package/dist/consolidator/runtime.d.ts.map +1 -1
- package/dist/consolidator/runtime.js +129 -47
- package/dist/consolidator/runtime.js.map +1 -1
- package/dist/consolidator/scheduler.d.ts +17 -3
- package/dist/consolidator/scheduler.d.ts.map +1 -1
- package/dist/consolidator/scheduler.js.map +1 -1
- package/dist/consolidator/types.d.ts +30 -1
- package/dist/consolidator/types.d.ts.map +1 -1
- package/dist/consolidator/types.js +10 -0
- package/dist/consolidator/types.js.map +1 -1
- package/dist/context-engine/compaction/clear-tool-results.d.ts +1 -1
- package/dist/context-engine/compaction/clear-tool-results.js +1 -1
- package/dist/context-engine/compaction/clear-tool-results.js.map +1 -1
- package/dist/context-engine/compaction/compactor.d.ts +1 -0
- package/dist/context-engine/compaction/compactor.d.ts.map +1 -1
- package/dist/context-engine/compaction/compactor.js +32 -8
- package/dist/context-engine/compaction/compactor.js.map +1 -1
- package/dist/context-engine/compaction/hooks/types.d.ts +1 -1
- package/dist/context-engine/compaction/index.d.ts +1 -0
- package/dist/context-engine/compaction/templates/marker.d.ts +33 -0
- package/dist/context-engine/compaction/templates/marker.d.ts.map +1 -0
- package/dist/context-engine/compaction/templates/marker.js +34 -0
- package/dist/context-engine/compaction/templates/marker.js.map +1 -0
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts +1 -1
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -1
- package/dist/context-engine/compaction/templates/summary-9-section.js +4 -3
- package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -1
- package/dist/context-engine/engine.d.ts +7 -176
- package/dist/context-engine/engine.d.ts.map +1 -1
- package/dist/context-engine/engine.js +0 -2
- package/dist/context-engine/engine.js.map +1 -1
- package/dist/context-engine/index.d.ts +8 -49
- package/dist/context-engine/io-types.d.ts +144 -0
- package/dist/context-engine/io-types.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/types.d.ts +1 -1
- package/dist/context-engine/locale-packs/types.js.map +1 -1
- package/dist/context-engine/privacy-filter.d.ts +1 -2
- package/dist/context-engine/privacy-filter.d.ts.map +1 -1
- package/dist/context-engine/privacy-filter.js +1 -2
- package/dist/context-engine/privacy-filter.js.map +1 -1
- package/dist/errors/index.d.ts +2 -2
- package/dist/errors/index.js +2 -2
- package/dist/errors/index.js.map +1 -1
- package/dist/facade.d.ts +43 -52
- package/dist/facade.d.ts.map +1 -1
- package/dist/facade.js +5 -3
- package/dist/facade.js.map +1 -1
- package/dist/graph/entity-resolver.js +1 -1
- package/dist/graph/entity-resolver.js.map +1 -1
- package/dist/index.d.ts +17 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -7
- package/dist/index.js.map +1 -1
- package/dist/internal/envelope.js +57 -0
- package/dist/internal/envelope.js.map +1 -0
- package/dist/internal/injection-heuristics.js +0 -0
- package/dist/internal/injection-heuristics.js.map +1 -1
- package/dist/internal/llm-json.js +40 -0
- package/dist/internal/llm-json.js.map +1 -0
- package/dist/internal/storage-adapter.d.ts +23 -10
- package/dist/internal/storage-adapter.d.ts.map +1 -1
- package/dist/memory-interface.d.ts +131 -0
- package/dist/memory-interface.d.ts.map +1 -0
- package/dist/package.js +6 -0
- package/dist/package.js.map +1 -0
- package/dist/search/iterative.d.ts.map +1 -1
- package/dist/search/iterative.js +2 -3
- package/dist/search/iterative.js.map +1 -1
- package/dist/search/query-transform.d.ts.map +1 -1
- package/dist/search/query-transform.js +2 -3
- package/dist/search/query-transform.js.map +1 -1
- package/dist/tiers/episodic-memory.js +3 -3
- package/dist/tiers/episodic-memory.js.map +1 -1
- package/dist/tiers/index.d.ts +2 -2
- package/dist/tiers/insight-memory.js +1 -1
- package/dist/tiers/insight-memory.js.map +1 -1
- package/dist/tiers/procedural-memory.d.ts +1 -1
- package/dist/tiers/procedural-memory.js +2 -2
- package/dist/tiers/procedural-memory.js.map +1 -1
- package/dist/tiers/semantic-memory.d.ts +58 -4
- package/dist/tiers/semantic-memory.d.ts.map +1 -1
- package/dist/tiers/semantic-memory.js +103 -20
- package/dist/tiers/semantic-memory.js.map +1 -1
- package/dist/tools/block-tools.d.ts +19 -57
- package/dist/tools/block-tools.d.ts.map +1 -1
- package/dist/tools/block-tools.js.map +1 -1
- package/dist/tools/fact-tools.d.ts +53 -214
- package/dist/tools/fact-tools.d.ts.map +1 -1
- package/dist/tools/fact-tools.js +3 -2
- package/dist/tools/fact-tools.js.map +1 -1
- package/dist/tools/recall-tools.d.ts +34 -175
- package/dist/tools/recall-tools.d.ts.map +1 -1
- package/dist/tools/recall-tools.js +5 -4
- package/dist/tools/recall-tools.js.map +1 -1
- package/dist/tools/runbook-tools.d.ts +15 -58
- package/dist/tools/runbook-tools.d.ts.map +1 -1
- package/dist/tools/runbook-tools.js.map +1 -1
- package/dist/tools/types.d.ts +1 -1
- package/package.json +18 -17
- package/src/conflict/index.ts +29 -0
- package/src/conflict/locale-packs/en.ts +142 -0
- package/src/conflict/locale-packs/index.ts +17 -0
- package/src/conflict/locale-packs/types.ts +138 -0
- package/src/conflict/pipeline.ts +371 -0
- package/src/conflict/stages/helpers.ts +65 -0
- package/src/conflict/stages/stage1-exact-dedup.ts +40 -0
- package/src/conflict/stages/stage2-embedding-three-zone.ts +54 -0
- package/src/conflict/stages/stage3-heuristic-regex.ts +51 -0
- package/src/conflict/stages/stage4-subject-predicate.ts +46 -0
- package/src/conflict/stages/stage5-defer-to-deep.ts +34 -0
- package/src/conflict/types.ts +263 -0
- package/src/consolidator/budget.ts +312 -0
- package/src/consolidator/decay.ts +246 -0
- package/src/consolidator/dlq.ts +61 -0
- package/src/consolidator/errors.ts +87 -0
- package/src/consolidator/idempotency.ts +27 -0
- package/src/consolidator/index.ts +269 -0
- package/src/consolidator/lock.ts +141 -0
- package/src/consolidator/noise-filter.ts +235 -0
- package/src/consolidator/phases/deep.ts +308 -0
- package/src/consolidator/phases/induce.ts +423 -0
- package/src/consolidator/phases/learned-context.ts +233 -0
- package/src/consolidator/phases/light.ts +166 -0
- package/src/consolidator/phases/reflect.ts +504 -0
- package/src/consolidator/phases/standard.ts +1002 -0
- package/src/consolidator/reconcile.ts +273 -0
- package/src/consolidator/runtime.ts +1049 -0
- package/src/consolidator/scheduler.ts +182 -0
- package/src/consolidator/triggers.ts +134 -0
- package/src/consolidator/types.ts +622 -0
- package/src/context-engine/annotations.ts +161 -0
- package/src/context-engine/auto-recall.ts +92 -0
- package/src/context-engine/compaction/clear-tool-results.ts +232 -0
- package/src/context-engine/compaction/compactor.ts +403 -0
- package/src/context-engine/compaction/hooks/reanchor-persona-block.ts +55 -0
- package/src/context-engine/compaction/hooks/reanchor-pinned-facts.ts +66 -0
- package/src/context-engine/compaction/hooks/reanchor-project-rules.ts +57 -0
- package/src/context-engine/compaction/hooks/reanchor-recent-results.ts +116 -0
- package/src/context-engine/compaction/hooks/types.ts +68 -0
- package/src/context-engine/compaction/index.ts +60 -0
- package/src/context-engine/compaction/templates/marker.ts +33 -0
- package/src/context-engine/compaction/templates/summary-9-section.ts +223 -0
- package/src/context-engine/compaction/thresholds.ts +107 -0
- package/src/context-engine/compaction/types.ts +255 -0
- package/src/context-engine/engine.ts +970 -0
- package/src/context-engine/index.ts +118 -0
- package/src/context-engine/io-types.ts +162 -0
- package/src/context-engine/locale-packs/en.ts +107 -0
- package/src/context-engine/locale-packs/index.ts +19 -0
- package/src/context-engine/locale-packs/resolver.ts +197 -0
- package/src/context-engine/locale-packs/types.ts +177 -0
- package/src/context-engine/metadata.ts +114 -0
- package/src/context-engine/preambles/inbound-en.ts +19 -0
- package/src/context-engine/privacy-filter.ts +190 -0
- package/src/context-engine/templates/base-en.ts +19 -0
- package/src/context-engine/templates/composer.ts +91 -0
- package/src/context-engine/token-budget.ts +319 -0
- package/src/context-engine/token-counter.ts +157 -0
- package/src/errors/index.ts +273 -0
- package/src/facade.ts +750 -0
- package/src/graph/entity-resolver.ts +364 -0
- package/src/graph/index.ts +24 -0
- package/src/index.ts +259 -0
- package/src/internal/contextualize.ts +173 -0
- package/src/internal/embedder-binding.ts +53 -0
- package/src/internal/envelope.ts +70 -0
- package/src/internal/id.ts +23 -0
- package/src/internal/injection-heuristics.ts +0 -0
- package/src/internal/llm-json.ts +38 -0
- package/src/internal/spans.ts +35 -0
- package/src/internal/storage-adapter.ts +963 -0
- package/src/memory-interface.ts +158 -0
- package/src/migration/embedder-migration.ts +237 -0
- package/src/migration/index.ts +13 -0
- package/src/search/explain.ts +123 -0
- package/src/search/fit-weights.ts +109 -0
- package/src/search/graph-ppr.ts +38 -0
- package/src/search/index.ts +55 -0
- package/src/search/iterative.ts +658 -0
- package/src/search/query-transform.ts +285 -0
- package/src/search/rrf.ts +225 -0
- package/src/search/trust.ts +55 -0
- package/src/search/types.ts +50 -0
- package/src/tiers/episodic-memory.ts +463 -0
- package/src/tiers/index.ts +50 -0
- package/src/tiers/insight-memory.ts +208 -0
- package/src/tiers/procedural-memory.ts +482 -0
- package/src/tiers/semantic-memory.ts +1729 -0
- package/src/tiers/session-memory.ts +271 -0
- package/src/tiers/shared-memory.ts +62 -0
- package/src/tiers/working-memory.ts +368 -0
- package/src/tools/block-tools.ts +172 -0
- package/src/tools/fact-tools.ts +473 -0
- package/src/tools/index.ts +109 -0
- package/src/tools/recall-tools.ts +303 -0
- package/src/tools/runbook-tools.ts +107 -0
- package/src/tools/taint.ts +39 -0
- package/src/tools/types.ts +37 -0
- package/dist/conflict/locale-packs/index.js +0 -4
- package/dist/context-engine/compaction/index.js +0 -10
- package/dist/context-engine/index.d.ts.map +0 -1
- package/dist/context-engine/index.js +0 -25
- package/dist/context-engine/locale-packs/index.js +0 -4
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contextual retrieval (P1-3). Before a memory is embedded / FTS-indexed
|
|
3
|
+
* the framework can prepend a short *situating context* (entities,
|
|
4
|
+
* timeframe, topics) so a terse fact like "moved there in March" stays
|
|
5
|
+
* findable by "Where did Anna relocate?". This mirrors Anthropic's
|
|
6
|
+
* Contextual Retrieval, applied to both the vector embedding *and* the
|
|
7
|
+
* lexical index, while the canonical `text` shown to the user / audit
|
|
8
|
+
* trail is left untouched.
|
|
9
|
+
*
|
|
10
|
+
* Three modes:
|
|
11
|
+
* - `'off'` - index the bare canonical text (pre-P1-3 behaviour).
|
|
12
|
+
* - `'late-chunk'` - the **offline default**: prepend a context derived
|
|
13
|
+
* deterministically from the memory's own structured metadata. No LLM
|
|
14
|
+
* call, so the local-first write path is unchanged in cost.
|
|
15
|
+
* - `'llm'` - an opt-in, **consolidator-only** enrichment: one cheap
|
|
16
|
+
* provider call writes a 1-2 sentence situating prefix. Confined to the
|
|
17
|
+
* background consolidator (never the hot write path); a failure or empty
|
|
18
|
+
* completion degrades to {@link contextualize} so a write never wedges.
|
|
19
|
+
*
|
|
20
|
+
* @packageDocumentation
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { Provider, ProviderRequest, Usage } from '@graphorin/core';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Contextual-retrieval mode. `'late-chunk'` is the offline default;
|
|
27
|
+
* `'llm'` is the opt-in, consolidator-only enrichment. P1-3.
|
|
28
|
+
*
|
|
29
|
+
* @stable
|
|
30
|
+
*/
|
|
31
|
+
export type ContextualRetrievalMode = 'off' | 'late-chunk' | 'llm';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Structural subset of a fact / candidate consumed by the
|
|
35
|
+
* contextualizers. Only **author / extraction-supplied** signals are
|
|
36
|
+
* used - never framework-defaulted fields (e.g. a `validFrom` defaulted
|
|
37
|
+
* to write-time) and never opaque scope ids (random uuids would only
|
|
38
|
+
* add noise to the embedding). A candidate with no structured signals
|
|
39
|
+
* yields an empty context, so plain `remember({ text })` writes are
|
|
40
|
+
* left byte-identical.
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
export interface ContextualizeInput {
|
|
45
|
+
readonly text: string;
|
|
46
|
+
readonly subject?: string;
|
|
47
|
+
readonly predicate?: string;
|
|
48
|
+
readonly object?: string;
|
|
49
|
+
readonly tags?: ReadonlyArray<string>;
|
|
50
|
+
/** Author-set validity start (ISO-8601). Framework-defaulted values are *not* passed. */
|
|
51
|
+
readonly validFrom?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const SITUATING_CONTEXT_SYSTEM_PROMPT = [
|
|
55
|
+
'You are a situating-context assistant for a long-running memory store.',
|
|
56
|
+
'Given one memory and any structured hints, write a single short sentence (≤ 25 words) that situates it - the entities, timeframe, and topic it concerns - so it can be retrieved later by a vaguely-worded question.',
|
|
57
|
+
'Do not restate the memory verbatim and do not add facts that are not implied. Return only the sentence, no preamble.',
|
|
58
|
+
].join(' ');
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Build the deterministic situating-context prefix from a candidate's
|
|
62
|
+
* structured signals. Returns `''` when nothing useful is available so
|
|
63
|
+
* the caller can short-circuit to the canonical text.
|
|
64
|
+
*
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
export function buildSituatingContext(input: ContextualizeInput): string {
|
|
68
|
+
const parts: string[] = [];
|
|
69
|
+
const subject = trimToUndefined(input.subject);
|
|
70
|
+
const predicate = trimToUndefined(input.predicate);
|
|
71
|
+
const object = trimToUndefined(input.object);
|
|
72
|
+
if (subject !== undefined && predicate !== undefined && object !== undefined) {
|
|
73
|
+
parts.push(`${subject} ${predicate} ${object}`);
|
|
74
|
+
} else {
|
|
75
|
+
if (subject !== undefined) parts.push(subject);
|
|
76
|
+
if (object !== undefined) parts.push(object);
|
|
77
|
+
}
|
|
78
|
+
const validFrom = trimToUndefined(input.validFrom);
|
|
79
|
+
if (validFrom !== undefined) {
|
|
80
|
+
parts.push(`as of ${validFrom.length >= 10 ? validFrom.slice(0, 10) : validFrom}`);
|
|
81
|
+
}
|
|
82
|
+
const tags = (input.tags ?? []).map((t) => t.trim()).filter((t) => t.length > 0);
|
|
83
|
+
if (tags.length > 0) {
|
|
84
|
+
parts.push(`topics: ${tags.join(', ')}`);
|
|
85
|
+
}
|
|
86
|
+
if (parts.length === 0) return '';
|
|
87
|
+
return `[Context: ${parts.join('; ')}]`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Late-chunking contextualizer (offline default). Prepends the
|
|
92
|
+
* deterministic situating context to the canonical text; a no-op when
|
|
93
|
+
* the candidate carries no structured signals. Pure - never calls a
|
|
94
|
+
* provider.
|
|
95
|
+
*
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
export function contextualize(input: ContextualizeInput): string {
|
|
99
|
+
const ctx = buildSituatingContext(input);
|
|
100
|
+
return ctx === '' ? input.text : `${ctx}\n${input.text}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const ZERO_USAGE: Usage = Object.freeze({
|
|
104
|
+
promptTokens: 0,
|
|
105
|
+
completionTokens: 0,
|
|
106
|
+
totalTokens: 0,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/** Result of the LLM-context enrichment - the index text plus call usage for budgeting. */
|
|
110
|
+
export interface ContextualizeLlmResult {
|
|
111
|
+
readonly indexText: string;
|
|
112
|
+
readonly usage: Usage;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* LLM-context contextualizer (opt-in, consolidator-only). Spends one
|
|
117
|
+
* cheap provider call to write a situating prefix, then prepends it to
|
|
118
|
+
* the canonical text. Resilient by design: an empty completion or a
|
|
119
|
+
* provider error degrades to {@link contextualize} (deterministic
|
|
120
|
+
* late-chunk) so the enrichment can never wedge a write. Returns the
|
|
121
|
+
* usage so the caller can record it against the budget.
|
|
122
|
+
*
|
|
123
|
+
* @internal
|
|
124
|
+
*/
|
|
125
|
+
export async function contextualizeWithLlm(
|
|
126
|
+
input: ContextualizeInput,
|
|
127
|
+
provider: Provider,
|
|
128
|
+
opts: { readonly signal?: AbortSignal } = {},
|
|
129
|
+
): Promise<ContextualizeLlmResult> {
|
|
130
|
+
const request: ProviderRequest = {
|
|
131
|
+
messages: [{ role: 'user', content: buildSituatingUserPrompt(input) }],
|
|
132
|
+
systemMessage: SITUATING_CONTEXT_SYSTEM_PROMPT,
|
|
133
|
+
temperature: 0,
|
|
134
|
+
// MCON-14: per-call output cap - a one-sentence situating prefix.
|
|
135
|
+
maxTokens: 256,
|
|
136
|
+
...(opts.signal !== undefined ? { signal: opts.signal } : {}),
|
|
137
|
+
};
|
|
138
|
+
try {
|
|
139
|
+
const response = await provider.generate(request);
|
|
140
|
+
const prefix = (response.text ?? '').trim();
|
|
141
|
+
if (prefix.length === 0) {
|
|
142
|
+
return { indexText: contextualize(input), usage: response.usage };
|
|
143
|
+
}
|
|
144
|
+
return { indexText: `${prefix}\n${input.text}`, usage: response.usage };
|
|
145
|
+
} catch {
|
|
146
|
+
// Contextualization is an enhancement, not a correctness requirement -
|
|
147
|
+
// a provider failure must never wedge the underlying write. Fall back
|
|
148
|
+
// to the deterministic late-chunk prefix at zero cost.
|
|
149
|
+
return { indexText: contextualize(input), usage: ZERO_USAGE };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function buildSituatingUserPrompt(input: ContextualizeInput): string {
|
|
154
|
+
const hints: string[] = [];
|
|
155
|
+
const subject = trimToUndefined(input.subject);
|
|
156
|
+
const predicate = trimToUndefined(input.predicate);
|
|
157
|
+
const object = trimToUndefined(input.object);
|
|
158
|
+
if (subject !== undefined) hints.push(`subject=${subject}`);
|
|
159
|
+
if (predicate !== undefined) hints.push(`predicate=${predicate}`);
|
|
160
|
+
if (object !== undefined) hints.push(`object=${object}`);
|
|
161
|
+
const tags = (input.tags ?? []).map((t) => t.trim()).filter((t) => t.length > 0);
|
|
162
|
+
if (tags.length > 0) hints.push(`tags=${tags.join(', ')}`);
|
|
163
|
+
const validFrom = trimToUndefined(input.validFrom);
|
|
164
|
+
if (validFrom !== undefined) hints.push(`validFrom=${validFrom}`);
|
|
165
|
+
const hintLine = hints.length > 0 ? `\nStructured hints: ${hints.join('; ')}` : '';
|
|
166
|
+
return `Memory: ${input.text}${hintLine}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function trimToUndefined(value: string | undefined): string | undefined {
|
|
170
|
+
if (value === undefined) return undefined;
|
|
171
|
+
const trimmed = value.trim();
|
|
172
|
+
return trimmed.length === 0 ? undefined : trimmed;
|
|
173
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { EmbedderProvider } from '@graphorin/core';
|
|
2
|
+
import type { EmbeddingMetaRegistryLike } from './storage-adapter.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Infer the canonical adapter family from the embedder id. The
|
|
6
|
+
* canonical id format is `'<adapter>:<model>@<dim>'`; we slice the
|
|
7
|
+
* adapter name off the front so the registry can index by family
|
|
8
|
+
* without needing the embedder to expose an extra property.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export function inferAdapterKind(id: string): string {
|
|
13
|
+
const colonIndex = id.indexOf(':');
|
|
14
|
+
if (colonIndex < 0) return 'custom';
|
|
15
|
+
return id.slice(0, colonIndex);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Infer the model id portion of a canonical embedder id.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export function inferModelName(id: string): string {
|
|
24
|
+
const colonIndex = id.indexOf(':');
|
|
25
|
+
const atIndex = id.lastIndexOf('@');
|
|
26
|
+
if (colonIndex < 0) return id;
|
|
27
|
+
if (atIndex < 0 || atIndex < colonIndex) return id.slice(colonIndex + 1);
|
|
28
|
+
return id.slice(colonIndex + 1, atIndex);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Register the supplied embedder with the storage layer's
|
|
33
|
+
* `embedding_meta` registry. Returns the canonical id surfaced to the
|
|
34
|
+
* memory writers. Idempotent - a registry that already knows the id
|
|
35
|
+
* with a matching `configHash` returns the existing row.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export function bindEmbedder(
|
|
40
|
+
embedder: EmbedderProvider,
|
|
41
|
+
registry: EmbeddingMetaRegistryLike,
|
|
42
|
+
): string {
|
|
43
|
+
const id = embedder.id();
|
|
44
|
+
registry.registerOrReturn({
|
|
45
|
+
id,
|
|
46
|
+
embedderKind: inferAdapterKind(id),
|
|
47
|
+
model: inferModelName(id),
|
|
48
|
+
dim: embedder.dim(),
|
|
49
|
+
configHash: embedder.configHash(),
|
|
50
|
+
distanceMetric: 'cosine',
|
|
51
|
+
});
|
|
52
|
+
return id;
|
|
53
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Untrusted-content envelope helpers shared across the memory package.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from the CE-15 compaction-summary neutralization
|
|
5
|
+
* (`wrapSummaryAsDerived` in `context-engine/compaction/compactor.ts`)
|
|
6
|
+
* so every prompt that interpolates untrusted memory text (compaction
|
|
7
|
+
* summaries, reconcile neighbours, deep-phase judge candidates,
|
|
8
|
+
* reflection evidence) delimits it with ONE substitution scheme. The
|
|
9
|
+
* scheme is the same visible bracket-substitution `@graphorin/tools`
|
|
10
|
+
* applies in `wrapEnvelope` (W-030): the closing marker
|
|
11
|
+
* `<<</untrusted_content>>>` becomes `[[/untrusted_content]]` and the
|
|
12
|
+
* opening prefix `<<<untrusted_content` becomes `[[untrusted_content`,
|
|
13
|
+
* tolerant to case and whitespace. Zero-width insertion is not used -
|
|
14
|
+
* models read straight through zero-width splits (the reason
|
|
15
|
+
* `normalizeForMatching` strips them before injection scans).
|
|
16
|
+
*
|
|
17
|
+
* The helper is deliberately local: `@graphorin/memory` does not
|
|
18
|
+
* depend on `@graphorin/tools`, and the substitution scheme is a
|
|
19
|
+
* cross-package SPEC pinned by tests on literal-marker inputs.
|
|
20
|
+
*
|
|
21
|
+
* @packageDocumentation
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** Opening delimiter prefix of the untrusted-content envelope. */
|
|
25
|
+
export const UNTRUSTED_CONTENT_OPEN_PREFIX = '<<<untrusted_content';
|
|
26
|
+
|
|
27
|
+
/** Closing delimiter of the untrusted-content envelope. */
|
|
28
|
+
export const UNTRUSTED_CONTENT_CLOSE = '<<</untrusted_content>>>';
|
|
29
|
+
|
|
30
|
+
// Tolerant embedded-marker matching, mirroring the tools package: the
|
|
31
|
+
// closing form optionally consumes its `>>>` terminator so the full
|
|
32
|
+
// marker collapses to `[[/untrusted_content]]`.
|
|
33
|
+
const EMBEDDED_CLOSING_RE = /<<<\s*\/\s*untrusted_content(\s*>>>)?/gi;
|
|
34
|
+
const EMBEDDED_OPENING_RE = /<<<\s*untrusted_content/gi;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Replace envelope markers embedded in `body` with the visible `[[` /
|
|
38
|
+
* `]]` bracket-substitution so the body cannot prematurely close (or
|
|
39
|
+
* spoof a nested opening of) the envelope it is being wrapped into.
|
|
40
|
+
* Marker-free bodies are returned bytes-equal.
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
export function neutralizeEnvelopeMarkers(body: string): string {
|
|
45
|
+
if (!body.includes('<<<')) return body;
|
|
46
|
+
return body
|
|
47
|
+
.replace(EMBEDDED_CLOSING_RE, (_match, terminator) =>
|
|
48
|
+
terminator === undefined ? '[[/untrusted_content' : '[[/untrusted_content]]',
|
|
49
|
+
)
|
|
50
|
+
.replace(EMBEDDED_OPENING_RE, '[[untrusted_content');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Wrap `body` in the untrusted-content envelope with the supplied
|
|
55
|
+
* attributes, neutralizing embedded markers first. Attribute insertion
|
|
56
|
+
* order is preserved, so
|
|
57
|
+
* `wrapUntrusted(body, { trust: 'derived', tool: 'compaction-summarizer' })`
|
|
58
|
+
* reproduces the historical CE-15 output byte-for-byte.
|
|
59
|
+
*
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
export function wrapUntrusted(body: string, attrs?: Readonly<Record<string, string>>): string {
|
|
63
|
+
const attrStr =
|
|
64
|
+
attrs === undefined
|
|
65
|
+
? ''
|
|
66
|
+
: Object.entries(attrs)
|
|
67
|
+
.map(([key, value]) => ` ${key}="${value.replace(/"/g, '"')}"`)
|
|
68
|
+
.join('');
|
|
69
|
+
return `${UNTRUSTED_CONTENT_OPEN_PREFIX}${attrStr}>>>\n${neutralizeEnvelopeMarkers(body)}\n${UNTRUSTED_CONTENT_CLOSE}`;
|
|
70
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable id generator. Combines `Date.now()` (millisecond resolution),
|
|
3
|
+
* a per-process monotonic counter (resists collision inside the same
|
|
4
|
+
* millisecond), and a 36-bit crypto-quality random suffix (ties the id
|
|
5
|
+
* to the host so different runs do not collide either).
|
|
6
|
+
*
|
|
7
|
+
* The format is intentionally NOT a UUID - UUIDs are 16 bytes once
|
|
8
|
+
* decoded; these ids are 18 chars on average which is what the
|
|
9
|
+
* `audit.db` hash chain (Phase 03b) was sized for.
|
|
10
|
+
*
|
|
11
|
+
* @stable
|
|
12
|
+
*/
|
|
13
|
+
import { randomBytes } from 'node:crypto';
|
|
14
|
+
|
|
15
|
+
let counter = 0;
|
|
16
|
+
|
|
17
|
+
export function newMemoryId(prefix: string): string {
|
|
18
|
+
counter = (counter + 1) & 0xffff;
|
|
19
|
+
const time = Date.now().toString(36);
|
|
20
|
+
const c = counter.toString(36).padStart(3, '0');
|
|
21
|
+
const rand = randomBytes(4).toString('hex');
|
|
22
|
+
return `${prefix}_${time}${c}${rand}`;
|
|
23
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared tolerant-parsing primitives for LLM text completions that are
|
|
3
|
+
* expected to carry JSON. Every consolidator phase and search transformer
|
|
4
|
+
* that parses a model response uses these two helpers; they used to be
|
|
5
|
+
* copy-pasted per module (7 copies of {@link stripFence}, 3 of
|
|
6
|
+
* {@link sliceJsonObject}) until the CodeQL ReDoS fix (06b7552) had to be
|
|
7
|
+
* applied to all of them at once - the canonical demonstration of why the
|
|
8
|
+
* single definition lives here now.
|
|
9
|
+
*
|
|
10
|
+
* Package-local (`internal/`): not part of the public `@graphorin/memory`
|
|
11
|
+
* API surface.
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Strip a single surrounding Markdown code fence, if present. The
|
|
18
|
+
* info-string is matched with `[^\n]*` (never across lines) so a hostile
|
|
19
|
+
* megabyte-long first line cannot trigger polynomial backtracking; an
|
|
20
|
+
* unterminated fence leaves the text untouched and the caller's JSON
|
|
21
|
+
* parsing fails soft instead.
|
|
22
|
+
*/
|
|
23
|
+
export function stripFence(text: string): string {
|
|
24
|
+
const match = /^```[^\n]*\n([\s\S]*?)\n```/u.exec(text.trim());
|
|
25
|
+
return match?.[1] ?? text;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Best-effort recovery slice for completions that wrap a JSON object in
|
|
30
|
+
* prose: everything from the first `{` to the last `}`. Returns `null`
|
|
31
|
+
* when no plausible object span exists.
|
|
32
|
+
*/
|
|
33
|
+
export function sliceJsonObject(text: string): string | null {
|
|
34
|
+
const start = text.indexOf('{');
|
|
35
|
+
const end = text.lastIndexOf('}');
|
|
36
|
+
if (start < 0 || end < start) return null;
|
|
37
|
+
return text.slice(start, end + 1);
|
|
38
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { SessionScope, SpanAttributes, SpanType, Tracer } from '@graphorin/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sanitize a `SessionScope` for span attributes - the raw user id is
|
|
5
|
+
* surfaced as-is (it is a stable opaque identifier per DEC-005), but
|
|
6
|
+
* session and agent ids are emitted only when defined to avoid
|
|
7
|
+
* polluting the trace with `null` strings.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export function scopeAttrs(scope: SessionScope): SpanAttributes {
|
|
12
|
+
const attrs: Record<string, string> = {
|
|
13
|
+
'memory.scope.user_id': scope.userId,
|
|
14
|
+
};
|
|
15
|
+
if (scope.sessionId !== undefined) attrs['memory.scope.session_id'] = scope.sessionId;
|
|
16
|
+
if (scope.agentId !== undefined) attrs['memory.scope.agent_id'] = scope.agentId;
|
|
17
|
+
return Object.freeze(attrs);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Run an async function inside a `memory.*` span with the standard
|
|
22
|
+
* scope attributes already populated. Adds the span result count and
|
|
23
|
+
* status automatically.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export async function withMemorySpan<T extends SpanType, R>(
|
|
28
|
+
tracer: Tracer,
|
|
29
|
+
type: T,
|
|
30
|
+
scope: SessionScope,
|
|
31
|
+
extras: SpanAttributes,
|
|
32
|
+
fn: (span: import('@graphorin/core').AISpan<T>) => Promise<R>,
|
|
33
|
+
): Promise<R> {
|
|
34
|
+
return tracer.span({ type, attrs: { ...scopeAttrs(scope), ...extras } }, fn);
|
|
35
|
+
}
|