@zenera/neo 1.1.10 → 1.1.12

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.
Files changed (90) hide show
  1. package/README.md +3 -3
  2. package/dist/agent.d.ts +8 -7
  3. package/dist/agent.js +15 -8
  4. package/dist/architecture.d.ts +7 -5
  5. package/dist/architecture.js +20 -12
  6. package/dist/embedding.d.ts +20 -1
  7. package/dist/embedding.js +3 -2
  8. package/dist/embeddings/fanout.d.ts +51 -0
  9. package/dist/embeddings/fanout.js +76 -0
  10. package/dist/embeddings/gemini.d.ts +3 -10
  11. package/dist/embeddings/gemini.js +46 -30
  12. package/dist/embeddings/index.d.ts +3 -0
  13. package/dist/embeddings/index.js +3 -0
  14. package/dist/embeddings/limiter.d.ts +36 -0
  15. package/dist/embeddings/limiter.js +182 -0
  16. package/dist/embeddings/openai.d.ts +3 -2
  17. package/dist/embeddings/openai.js +54 -13
  18. package/dist/embeddings/openrouter.d.ts +2 -1
  19. package/dist/embeddings/openrouter.js +52 -23
  20. package/dist/embeddings/rate-limit.d.ts +16 -0
  21. package/dist/embeddings/rate-limit.js +131 -0
  22. package/dist/failure.d.ts +82 -0
  23. package/dist/failure.js +323 -0
  24. package/dist/index.d.ts +13 -2
  25. package/dist/index.js +13 -2
  26. package/dist/inspect/client.d.ts +1 -1
  27. package/dist/inspect/client.js +42 -26
  28. package/dist/kernel.d.ts +1 -1
  29. package/dist/kernel.js +40 -23
  30. package/dist/memory/files.d.ts +24 -0
  31. package/dist/memory/files.js +85 -0
  32. package/dist/memory/graph.d.ts +56 -0
  33. package/dist/memory/graph.js +159 -0
  34. package/dist/memory/index.d.ts +93 -0
  35. package/dist/memory/index.js +357 -0
  36. package/dist/memory/instructions.d.ts +15 -0
  37. package/dist/memory/instructions.js +103 -0
  38. package/dist/memory/page.d.ts +8 -0
  39. package/dist/memory/page.js +797 -0
  40. package/dist/memory/recall.d.ts +35 -0
  41. package/dist/memory/recall.js +161 -0
  42. package/dist/memory/render.d.ts +10 -0
  43. package/dist/memory/render.js +228 -0
  44. package/dist/memory/report.d.ts +57 -0
  45. package/dist/memory/report.js +113 -0
  46. package/dist/memory/store.d.ts +49 -0
  47. package/dist/memory/store.js +226 -0
  48. package/dist/memory/tools.d.ts +11 -0
  49. package/dist/memory/tools.js +310 -0
  50. package/dist/memory/types.d.ts +192 -0
  51. package/dist/memory/types.js +105 -0
  52. package/dist/memory/vectors.d.ts +29 -0
  53. package/dist/memory/vectors.js +152 -0
  54. package/dist/models/anthropic.d.ts +2 -1
  55. package/dist/models/anthropic.js +11 -4
  56. package/dist/models/factory.d.ts +15 -0
  57. package/dist/models/factory.js +49 -10
  58. package/dist/models/gemini.d.ts +2 -1
  59. package/dist/models/gemini.js +8 -3
  60. package/dist/models/openai-chat.d.ts +2 -1
  61. package/dist/models/openai-chat.js +10 -5
  62. package/dist/models/openai-responses.d.ts +2 -1
  63. package/dist/models/openai-responses.js +22 -7
  64. package/dist/models/openrouter.d.ts +2 -1
  65. package/dist/models/openrouter.js +14 -6
  66. package/dist/project/config.d.ts +55 -3
  67. package/dist/project/config.js +76 -1
  68. package/dist/project/index.d.ts +2 -2
  69. package/dist/project/index.js +1 -1
  70. package/dist/project/load.d.ts +16 -2
  71. package/dist/project/load.js +67 -1
  72. package/dist/runner.d.ts +4 -2
  73. package/dist/runner.js +29 -30
  74. package/dist/services.d.ts +23 -6
  75. package/dist/services.js +25 -15
  76. package/dist/tools/workspace.js +195 -17
  77. package/dist/trajectory.d.ts +14 -12
  78. package/dist/types.d.ts +5 -5
  79. package/dist/types.js +3 -3
  80. package/package.json +2 -5
  81. package/dist/memory-stores/factory.d.ts +0 -15
  82. package/dist/memory-stores/factory.js +0 -29
  83. package/dist/memory-stores/file.d.ts +0 -37
  84. package/dist/memory-stores/file.js +0 -174
  85. package/dist/memory-stores/in-memory.d.ts +0 -17
  86. package/dist/memory-stores/in-memory.js +0 -80
  87. package/dist/memory-stores/index.d.ts +0 -4
  88. package/dist/memory-stores/index.js +0 -5
  89. package/dist/memory.d.ts +0 -115
  90. package/dist/memory.js +0 -213
@@ -0,0 +1,192 @@
1
+ /**
2
+ * The built-in vocabulary. A kind earns its place only when ranking, rendering
3
+ * or loading branches on it — anything else is free text wearing an enum
4
+ * costume, and it costs the model accuracy when it has to choose one.
5
+ *
6
+ * A project may declare its own set (`memory.kinds` in the config), which
7
+ * *replaces* this one; the tool schema is built from the result, so the
8
+ * provider constrains decoding to labels that actually exist.
9
+ *
10
+ * `preference` is the exception, and `MemoryIndex` re-adds it to whatever a
11
+ * project declares: the engine itself reads that kind to build the system
12
+ * prompt, so a project able to define it away would silently lose the feature
13
+ * rather than opt out of it.
14
+ */
15
+ export declare const MEMORY_KINDS: readonly ["task", "plan", "fact", "snippet", "file", "operation", "preference"];
16
+ export type MemoryKind = (typeof MEMORY_KINDS)[number];
17
+ export declare const KIND_HELP: Readonly<Record<MemoryKind, string>>;
18
+ /**
19
+ * Four relations, and only `SUPERSEDES` changes a traversal: recall follows it
20
+ * forward and drops what it points at, so a corrected artifact hides the one it
21
+ * replaced without deleting the history. The other three shape which tier of
22
+ * the budget a node is pulled in on, and read as prose in the rendered graph.
23
+ *
24
+ * `SUPERSEDES` itself never reaches the renderer on the ordinary path — recall
25
+ * drops the node it points at, so the edge has no second endpoint to draw. It
26
+ * appears only under `stale`, which is the audit view.
27
+ */
28
+ export declare const MEMORY_RELATIONS: readonly ["PRODUCED", "INFORMED", "CALLS", "SUPERSEDES"];
29
+ export type Relation = (typeof MEMORY_RELATIONS)[number];
30
+ export declare const RELATION_HELP: Readonly<Record<Relation, string>>;
31
+ /**
32
+ * The relations that carry the work forward, as opposed to the ones that merely
33
+ * supply context. Recall walks these first when spending its budget, and the
34
+ * renderer prefers them when a node has more than one branch it could hang from.
35
+ */
36
+ export declare const SPINE_RELATIONS: ReadonlySet<string>;
37
+ /** An audience label every agent holds implicitly. */
38
+ export declare const ALL_AGENTS = "*";
39
+ /** Read by the engine, not just by the model: it becomes part of the system prompt. */
40
+ export declare const PREFERENCE_KIND = "preference";
41
+ /**
42
+ * `hint` is the recovery step, kept separate from the message because the tool
43
+ * layer returns both to the model as `{error, hint}` rather than throwing.
44
+ */
45
+ export declare class MemoryError extends Error {
46
+ readonly hint?: string;
47
+ constructor(message: string, hint?: string);
48
+ }
49
+ export interface MemoryFile {
50
+ /** what the agent sees, e.g. `/memory/01J8….py` — a mount path, not a host path */
51
+ path: string;
52
+ bytes: number;
53
+ sha256: string;
54
+ /** extension without the dot, '' when there was none */
55
+ format: string;
56
+ }
57
+ export interface MemoryNode {
58
+ id: string;
59
+ kind: string;
60
+ /** the searchable, embeddable summary — the only text that is ranked */
61
+ text: string;
62
+ /** who may see this node; `['*']` is everyone */
63
+ audience: string[];
64
+ createdAt: string;
65
+ updatedAt: string;
66
+ /** last time a `memory_load` returned it; the input to recency decay */
67
+ lastUsedAt: string;
68
+ useCount: number;
69
+ /** optimistic-concurrency token */
70
+ revision: number;
71
+ metadata?: Record<string, unknown>;
72
+ file?: MemoryFile;
73
+ }
74
+ /** Graphology holds source and target itself, so they are not attributes. */
75
+ export interface MemoryEdgeAttrs {
76
+ relation: Relation;
77
+ createdAt: string;
78
+ /** narrower than either endpoint when set; usually absent */
79
+ audience?: string[];
80
+ }
81
+ export interface MemoryEdge {
82
+ source: string;
83
+ target: string;
84
+ relation: Relation;
85
+ }
86
+ export interface MemoryQuery {
87
+ /** semantic query; omitted means a pure filter listing */
88
+ text?: string;
89
+ kinds?: string[];
90
+ /** how many seed nodes the ranker returns before the graph is stitched */
91
+ limit?: number;
92
+ maxHops?: number;
93
+ maxNodes?: number;
94
+ /** ISO timestamp; nodes created before it are excluded */
95
+ newerThan?: string;
96
+ minScore?: number;
97
+ }
98
+ /**
99
+ * The edge a stitched node was reached by. A recollection renders as a tree,
100
+ * and this is the branch each node hangs from; the edges left over once every
101
+ * node has used its own are the ones the tree cannot carry.
102
+ */
103
+ export interface NodeVia {
104
+ from: string;
105
+ relation: Relation;
106
+ /** the edge runs from `from` to this node rather than back the other way */
107
+ outbound: boolean;
108
+ }
109
+ export interface ScoredNode {
110
+ node: MemoryNode;
111
+ score: number;
112
+ /** the ranker found this one; everything else is here to connect it */
113
+ seed: boolean;
114
+ /** absent on a seed: nothing reached it, it was the reason for the walk */
115
+ via?: NodeVia;
116
+ }
117
+ export interface Recollection {
118
+ nodes: ScoredNode[];
119
+ edges: MemoryEdge[];
120
+ /** ids of the seeds, in rank order */
121
+ seeds: string[];
122
+ /** nodes were dropped to stay inside the budget */
123
+ truncated: boolean;
124
+ }
125
+ export type MemoryAccess = 'read' | 'read-write' | 'full';
126
+ /**
127
+ * `sees` and `writes` are the mask, and they are resolved from config before a
128
+ * tool is ever built. Neither is a tool parameter: an agent that could name its
129
+ * own audience could read another agent's slice just by asking.
130
+ */
131
+ export interface MemoryBinding<TCtx = unknown> {
132
+ access: MemoryAccess;
133
+ /** audience labels this agent may read; `'*'` is implicit */
134
+ sees?: string[] | ((ctx: TCtx) => string[]);
135
+ /** labels it may write; defaults to `['*']` */
136
+ writes?: string[];
137
+ autoRecall?: AutoRecall;
138
+ }
139
+ export interface AutoRecall {
140
+ query: 'last_user_input' | 'none';
141
+ limit: number;
142
+ }
143
+ export interface ResolvedMemoryBinding {
144
+ access: MemoryAccess;
145
+ sees: string[];
146
+ writes: string[];
147
+ autoRecall?: AutoRecall;
148
+ }
149
+ export declare const canWrite: (b: ResolvedMemoryBinding) => boolean;
150
+ export declare const canForget: (b: ResolvedMemoryBinding) => boolean;
151
+ /**
152
+ * What a tool did, in the form the trajectory keeps it. Ids and counts only —
153
+ * the prose lives in a payload, so a replay can be reasoned about without
154
+ * rehydrating every body.
155
+ */
156
+ export interface MemoryRecallSpec {
157
+ kind: 'recall';
158
+ query: MemoryQuery;
159
+ /** the matches; the rest of `nodes` is stitched context */
160
+ seeds: string[];
161
+ nodes: {
162
+ id: string;
163
+ kind: string;
164
+ score: number;
165
+ }[];
166
+ edges: MemoryEdge[];
167
+ /** the rendered block the model actually saw */
168
+ content: string;
169
+ }
170
+ export interface MemoryOpSpec {
171
+ kind: 'op';
172
+ op: 'commit' | 'forget';
173
+ /** sha256(runId, callId) — deterministic, so a replay is deduplicated */
174
+ opId: string;
175
+ /** nodes this op created, changed or removed */
176
+ nodes: {
177
+ id: string;
178
+ kind: string;
179
+ revision: number;
180
+ }[];
181
+ edges: MemoryEdge[];
182
+ /** remembered files copied in, or bytes dropped */
183
+ files: number;
184
+ }
185
+ /**
186
+ * Deny by default: a node is visible when it is public, or when the reader
187
+ * holds one of the labels it carries.
188
+ */
189
+ export declare function visible(node: MemoryNode, sees: readonly string[]): boolean;
190
+ /** An edge needs both endpoints visible before its own labels are consulted. */
191
+ export declare function edgeVisible(attrs: MemoryEdgeAttrs, sees: readonly string[]): boolean;
192
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,105 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Memory — knowledge that outlives a run (the trajectory is one run's record)
3
+ //
4
+ // A graph, not a list of records. The reason is the shape of what is worth
5
+ // remembering: a script an agent wrote is useless without the request that
6
+ // asked for it, the plan that shaped it and the endpoint it calls, and those
7
+ // four things are one memory even though they are four pieces of text. A store
8
+ // that can only return a ranked list of rows leaves the joining to the model,
9
+ // every single time.
10
+ //
11
+ // Every node is timed. `createdAt` is provenance, but `lastUsedAt` is what
12
+ // ranking actually leans on: memory that keeps proving useful should surface
13
+ // ahead of memory that was written once and never read, and neither an
14
+ // embedding nor an edge can tell you that.
15
+ //
16
+ // Visibility is per node, by label, denied by default. One graph serves every
17
+ // agent in a project; `audience` decides which of them may see a node at all.
18
+ // A second user is a second graph, not a second label: `audience` is about
19
+ // which agent may read a node, never about whose memory it is.
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * The built-in vocabulary. A kind earns its place only when ranking, rendering
23
+ * or loading branches on it — anything else is free text wearing an enum
24
+ * costume, and it costs the model accuracy when it has to choose one.
25
+ *
26
+ * A project may declare its own set (`memory.kinds` in the config), which
27
+ * *replaces* this one; the tool schema is built from the result, so the
28
+ * provider constrains decoding to labels that actually exist.
29
+ *
30
+ * `preference` is the exception, and `MemoryIndex` re-adds it to whatever a
31
+ * project declares: the engine itself reads that kind to build the system
32
+ * prompt, so a project able to define it away would silently lose the feature
33
+ * rather than opt out of it.
34
+ */
35
+ export const MEMORY_KINDS = [
36
+ 'task',
37
+ 'plan',
38
+ 'fact',
39
+ 'snippet',
40
+ 'file',
41
+ 'operation',
42
+ 'preference',
43
+ ];
44
+ export const KIND_HELP = {
45
+ task: 'a request that started a piece of work',
46
+ plan: 'an approach that worked, worth reusing on a similar request',
47
+ fact: 'a durable truth about the environment or the domain',
48
+ snippet: 'a short piece of code or configuration, too small to be a file',
49
+ file: 'an artifact kept whole under /memory and re-runnable',
50
+ operation: 'an external call that was made — an endpoint, command or query',
51
+ preference: 'a standing instruction from the user that applies to every run',
52
+ };
53
+ /**
54
+ * Four relations, and only `SUPERSEDES` changes a traversal: recall follows it
55
+ * forward and drops what it points at, so a corrected artifact hides the one it
56
+ * replaced without deleting the history. The other three shape which tier of
57
+ * the budget a node is pulled in on, and read as prose in the rendered graph.
58
+ *
59
+ * `SUPERSEDES` itself never reaches the renderer on the ordinary path — recall
60
+ * drops the node it points at, so the edge has no second endpoint to draw. It
61
+ * appears only under `stale`, which is the audit view.
62
+ */
63
+ export const MEMORY_RELATIONS = ['PRODUCED', 'INFORMED', 'CALLS', 'SUPERSEDES'];
64
+ export const RELATION_HELP = {
65
+ PRODUCED: 'the source led to the target being made (task -> artifact)',
66
+ INFORMED: 'the source was context the target was built from',
67
+ CALLS: 'the source invokes the target',
68
+ SUPERSEDES: 'the source replaces the target; the target is stale',
69
+ };
70
+ /**
71
+ * The relations that carry the work forward, as opposed to the ones that merely
72
+ * supply context. Recall walks these first when spending its budget, and the
73
+ * renderer prefers them when a node has more than one branch it could hang from.
74
+ */
75
+ export const SPINE_RELATIONS = new Set(['PRODUCED', 'CALLS', 'SUPERSEDES']);
76
+ /** An audience label every agent holds implicitly. */
77
+ export const ALL_AGENTS = '*';
78
+ /** Read by the engine, not just by the model: it becomes part of the system prompt. */
79
+ export const PREFERENCE_KIND = 'preference';
80
+ /**
81
+ * `hint` is the recovery step, kept separate from the message because the tool
82
+ * layer returns both to the model as `{error, hint}` rather than throwing.
83
+ */
84
+ export class MemoryError extends Error {
85
+ hint;
86
+ constructor(message, hint) {
87
+ super(message);
88
+ this.name = 'MemoryError';
89
+ this.hint = hint;
90
+ }
91
+ }
92
+ export const canWrite = (b) => b.access !== 'read';
93
+ export const canForget = (b) => b.access === 'full';
94
+ /**
95
+ * Deny by default: a node is visible when it is public, or when the reader
96
+ * holds one of the labels it carries.
97
+ */
98
+ export function visible(node, sees) {
99
+ return node.audience.some((a) => a === ALL_AGENTS || sees.includes(a));
100
+ }
101
+ /** An edge needs both endpoints visible before its own labels are consulted. */
102
+ export function edgeVisible(attrs, sees) {
103
+ return (!attrs.audience?.length || attrs.audience.some((a) => a === ALL_AGENTS || sees.includes(a)));
104
+ }
105
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,29 @@
1
+ export interface Scored {
2
+ id: string;
3
+ score: number;
4
+ }
5
+ export declare class VectorBlock {
6
+ #private;
7
+ constructor(dims: number, capacity?: number);
8
+ get dims(): number;
9
+ get rows(): number;
10
+ get ids(): readonly string[];
11
+ has(id: string): boolean;
12
+ set(id: string, vector: ArrayLike<number>): void;
13
+ remove(id: string): boolean;
14
+ /** Exact kNN over the rows `allow` accepts. Ties break toward the earlier row. */
15
+ topK(query: ArrayLike<number>, k: number, allow?: (id: string) => boolean): Scored[];
16
+ /** The raw block, trimmed to the rows in use — what goes to disk. */
17
+ bytes(): Float32Array;
18
+ static load(dims: number, ids: readonly string[], data: Float32Array): VectorBlock;
19
+ }
20
+ export declare function tokenize(v: string): string[];
21
+ export declare function overlap(a: readonly string[], b: readonly string[]): number;
22
+ /** Half-life in days: how fast a memory nobody reads falls behind one that is read. */
23
+ export declare const DECAY_HALF_LIFE_DAYS = 30;
24
+ /**
25
+ * Multiplied into similarity, never added: a stale exact match should still beat
26
+ * a fresh irrelevant one, so decay narrows the field rather than reordering it.
27
+ */
28
+ export declare function recencyDecay(lastUsedAt: string, now: number): number;
29
+ //# sourceMappingURL=vectors.d.ts.map
@@ -0,0 +1,152 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Vectors — a dense block and a linear scan
3
+ //
4
+ // No vector database. At the scale agent memory actually reaches — tens of
5
+ // thousands of nodes at the very outside — an exact scan over one contiguous
6
+ // Float32Array beats an approximate index that costs a native dependency, and
7
+ // it is exact, which an ANN index is not. `@zenera/neo` has two runtime
8
+ // dependencies for a reason, and none of them ship a `.node` binary.
9
+ //
10
+ // `Embedder` guarantees unit vectors (`embeddingResponse` normalizes before it
11
+ // returns), so cosine similarity here is a bare dot product. If that guarantee
12
+ // ever moves, this is the file that silently starts lying.
13
+ //
14
+ // Deletion is a swap-remove: the last row moves into the hole. That keeps the
15
+ // block dense — no tombstones, no compaction pass — at the cost of row indices
16
+ // not being stable, which is why nothing outside this file ever holds one.
17
+ // ---------------------------------------------------------------------------
18
+ const GROWTH = 2;
19
+ const INITIAL_ROWS = 64;
20
+ export class VectorBlock {
21
+ #dims;
22
+ #ids = [];
23
+ #index = new Map();
24
+ #data;
25
+ #rows = 0;
26
+ constructor(dims, capacity = INITIAL_ROWS) {
27
+ this.#dims = dims;
28
+ this.#data = new Float32Array(Math.max(capacity, 1) * dims);
29
+ }
30
+ get dims() {
31
+ return this.#dims;
32
+ }
33
+ get rows() {
34
+ return this.#rows;
35
+ }
36
+ get ids() {
37
+ return this.#ids;
38
+ }
39
+ has(id) {
40
+ return this.#index.has(id);
41
+ }
42
+ set(id, vector) {
43
+ if (vector.length !== this.#dims) {
44
+ throw new Error(`expected a ${this.#dims}-dimension vector, got ${vector.length}`);
45
+ }
46
+ let row = this.#index.get(id);
47
+ if (row === undefined) {
48
+ this.#grow(this.#rows + 1);
49
+ row = this.#rows++;
50
+ this.#ids[row] = id;
51
+ this.#index.set(id, row);
52
+ }
53
+ this.#data.set(vector, row * this.#dims);
54
+ }
55
+ remove(id) {
56
+ const row = this.#index.get(id);
57
+ if (row === undefined) {
58
+ return false;
59
+ }
60
+ const last = this.#rows - 1;
61
+ if (row !== last) {
62
+ const moved = this.#ids[last];
63
+ this.#data.copyWithin(row * this.#dims, last * this.#dims, (last + 1) * this.#dims);
64
+ this.#ids[row] = moved;
65
+ this.#index.set(moved, row);
66
+ }
67
+ this.#ids.length = last;
68
+ this.#index.delete(id);
69
+ this.#rows = last;
70
+ return true;
71
+ }
72
+ /** Exact kNN over the rows `allow` accepts. Ties break toward the earlier row. */
73
+ topK(query, k, allow) {
74
+ if (query.length !== this.#dims) {
75
+ throw new Error(`expected a ${this.#dims}-dimension query, got ${query.length}`);
76
+ }
77
+ const out = [];
78
+ for (let row = 0; row < this.#rows; row++) {
79
+ const id = this.#ids[row];
80
+ if (allow && !allow(id)) {
81
+ continue;
82
+ }
83
+ out.push({ id, score: this.#dot(query, row) });
84
+ }
85
+ out.sort((a, b) => b.score - a.score);
86
+ return out.slice(0, k);
87
+ }
88
+ /** The raw block, trimmed to the rows in use — what goes to disk. */
89
+ bytes() {
90
+ return this.#data.subarray(0, this.#rows * this.#dims);
91
+ }
92
+ static load(dims, ids, data) {
93
+ const block = new VectorBlock(dims, Math.max(ids.length, 1));
94
+ block.#ids = [...ids];
95
+ block.#rows = ids.length;
96
+ for (let row = 0; row < ids.length; row++) {
97
+ block.#index.set(ids[row], row);
98
+ }
99
+ block.#data.set(data.subarray(0, ids.length * dims), 0);
100
+ return block;
101
+ }
102
+ #dot(query, row) {
103
+ const base = row * this.#dims;
104
+ let sum = 0;
105
+ for (let i = 0; i < this.#dims; i++) {
106
+ sum += query[i] * this.#data[base + i];
107
+ }
108
+ return sum;
109
+ }
110
+ #grow(needed) {
111
+ const capacity = this.#data.length / this.#dims;
112
+ if (needed <= capacity) {
113
+ return;
114
+ }
115
+ const next = new Float32Array(Math.max(needed, capacity * GROWTH) * this.#dims);
116
+ next.set(this.#data, 0);
117
+ this.#data = next;
118
+ }
119
+ }
120
+ // ---------------------------------------------------------------------------
121
+ // Lexical fallback
122
+ //
123
+ // Used when a project configures no embedder. It is worse than embeddings and
124
+ // is meant to be: the alternative is memory that silently does nothing, and a
125
+ // term-overlap hit on a remembered task is still a hit.
126
+ // ---------------------------------------------------------------------------
127
+ export function tokenize(v) {
128
+ return v
129
+ .toLowerCase()
130
+ .split(/[^a-z0-9]+/)
131
+ .filter((t) => t.length > 2);
132
+ }
133
+ export function overlap(a, b) {
134
+ const set = new Set(b);
135
+ const shared = a.filter((t) => set.has(t)).length;
136
+ return a.length ? shared / a.length : 0;
137
+ }
138
+ /** Half-life in days: how fast a memory nobody reads falls behind one that is read. */
139
+ export const DECAY_HALF_LIFE_DAYS = 30;
140
+ const DAY_MS = 24 * 60 * 60 * 1000;
141
+ /**
142
+ * Multiplied into similarity, never added: a stale exact match should still beat
143
+ * a fresh irrelevant one, so decay narrows the field rather than reordering it.
144
+ */
145
+ export function recencyDecay(lastUsedAt, now) {
146
+ const age = now - Date.parse(lastUsedAt);
147
+ if (!Number.isFinite(age) || age <= 0) {
148
+ return 1;
149
+ }
150
+ return 2 ** (-age / DAY_MS / DECAY_HALF_LIFE_DAYS);
151
+ }
152
+ //# sourceMappingURL=vectors.js.map
@@ -1,5 +1,6 @@
1
1
  import type Anthropic from '@anthropic-ai/sdk';
2
2
  import type { StreamDelta } from '../events.ts';
3
+ import { type ProviderNamed } from '../failure.ts';
3
4
  import type { Model, ModelRequest, ModelResponse } from '../model.ts';
4
5
  /**
5
6
  * Provider-specific knobs.
@@ -9,7 +10,7 @@ import type { Model, ModelRequest, ModelResponse } from '../model.ts';
9
10
  * exist. 8192 is the largest value every current Claude model accepts, which
10
11
  * makes it the only safe default — raise it per model when you know better.
11
12
  */
12
- export interface AnthropicModelOptions {
13
+ export interface AnthropicModelOptions extends ProviderNamed {
13
14
  /** hard cap on output tokens; required by the API, so it always has a value */
14
15
  maxTokens?: number;
15
16
  /**
@@ -1,3 +1,4 @@
1
+ import { callSites, called, reading } from "../failure.js";
1
2
  import { zeroUsage, } from "../types.js";
2
3
  /** Every current Claude model accepts at least this many output tokens. */
3
4
  const DEFAULT_MAX_TOKENS = 8192;
@@ -16,9 +17,11 @@ export class AnthropicModel {
16
17
  #client;
17
18
  #maxTokens;
18
19
  #thinkingBudget;
20
+ #site;
19
21
  constructor(id, client, options = {}) {
20
22
  this.id = id;
21
23
  this.#client = client;
24
+ this.#site = callSites(id, options.provider);
22
25
  this.#thinkingBudget =
23
26
  options.thinkingBudgetTokens && options.thinkingBudgetTokens >= MIN_THINKING_BUDGET
24
27
  ? options.thinkingBudgetTokens
@@ -28,7 +31,7 @@ export class AnthropicModel {
28
31
  this.#maxTokens = Math.max(options.maxTokens ?? DEFAULT_MAX_TOKENS, (this.#thinkingBudget ?? 0) + MIN_THINKING_BUDGET);
29
32
  }
30
33
  async generate(req) {
31
- const res = await this.#client.messages.create(this.#params(req), { signal: req.signal });
34
+ const res = await called(this.#site('llm generation', 'messages.create'), () => this.#client.messages.create(this.#params(req), { signal: req.signal }));
32
35
  return {
33
36
  ...readBlocks(res.content),
34
37
  usage: toUsage(res.usage),
@@ -36,11 +39,15 @@ export class AnthropicModel {
36
39
  };
37
40
  }
38
41
  async stream(req, onDelta) {
39
- const stream = this.#client.messages.stream(this.#params(req), { signal: req.signal });
42
+ const site = this.#site('llm streaming', 'messages.stream');
43
+ // Reassigned when a broken stream is reopened, so that `finalMessage()`
44
+ // below is asked of whichever stream actually delivered the answer.
45
+ let stream = this.#client.messages.stream(this.#params(req), { signal: req.signal });
46
+ const open = async () => (stream = this.#client.messages.stream(this.#params(req), { signal: req.signal }));
40
47
  // Blocks are addressed by index, and a tool call's id and name arrive in
41
48
  // its `content_block_start` while the arguments trickle in afterwards.
42
49
  const calls = new Map();
43
- for await (const event of stream) {
50
+ for await (const event of reading(site, stream, open)) {
44
51
  if (event.type === 'content_block_start') {
45
52
  const block = event.content_block;
46
53
  if (block.type === 'tool_use') {
@@ -79,7 +86,7 @@ export class AnthropicModel {
79
86
  // The accumulated message is authoritative: it has the parsed tool
80
87
  // inputs and the final usage, so the deltas above are for the UI only
81
88
  // and nothing here depends on having reassembled them correctly.
82
- const final = await stream.finalMessage();
89
+ const final = await called(site, () => stream.finalMessage());
83
90
  return {
84
91
  ...readBlocks(final.content),
85
92
  usage: toUsage(final.usage),
@@ -64,6 +64,16 @@ export interface ProviderSpec extends Credentials {
64
64
  headers?: Record<string, string>;
65
65
  timeoutMs?: number;
66
66
  maxRetries?: number;
67
+ /**
68
+ * Ceiling on embedding requests in flight against this connection. The
69
+ * floor and the actual number are discovered from the provider's own
70
+ * refusals, so this only says how high the discovery may climb — set it
71
+ * when an account is shared with something else that also needs quota.
72
+ *
73
+ * On the provider rather than on a model because a rate limit belongs to
74
+ * the key: two embedders on one account are one budget, not two.
75
+ */
76
+ concurrency?: number;
67
77
  /**
68
78
  * Pre-built client — bypasses credential resolution entirely. The seam for
69
79
  * auth no configuration can express: Vertex service accounts, Bedrock
@@ -182,6 +192,11 @@ export declare class ModelRegistry {
182
192
  requirement(ref: ModelRef | EmbeddingRef): ModelRequirement;
183
193
  /** `[provider[/api]:]model` → spec. Exposed so a caller can validate a ref. */
184
194
  parse(ref: string): ModelSpec;
195
+ /**
196
+ * Splits `[provider:]model` into the spec it stands for, so a caller holding
197
+ * a shorthand can still set the knobs a shorthand has no room for.
198
+ */
199
+ parseEmbedding(ref: string): EmbeddingSpec;
185
200
  /** A declared kind wins; otherwise a built-in name is its own kind. */
186
201
  kindOf(name: string): ProviderKind;
187
202
  }