@zenera/neo 1.1.23 → 1.1.25
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/embeddings/rate-limit.d.ts +7 -0
- package/dist/embeddings/rate-limit.js +33 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/inspect/client.d.ts +1 -1
- package/dist/inspect/client.js +7 -2
- package/dist/lock.d.ts +27 -0
- package/dist/lock.js +66 -0
- package/dist/match.d.ts +44 -0
- package/dist/match.js +100 -0
- package/dist/memory/diff.d.ts +39 -0
- package/dist/memory/diff.js +95 -0
- package/dist/memory/files.d.ts +1 -1
- package/dist/memory/grep.d.ts +57 -0
- package/dist/memory/grep.js +182 -0
- package/dist/memory/index.d.ts +10 -2
- package/dist/memory/index.js +15 -4
- package/dist/memory/merge.js +1 -31
- package/dist/memory/recall.d.ts +1 -1
- package/dist/memory/recall.js +1 -1
- package/dist/memory/store.d.ts +8 -5
- package/dist/memory/store.js +26 -55
- package/dist/memory/tools.js +105 -9
- package/dist/memory/twin.d.ts +6 -0
- package/dist/memory/twin.js +40 -0
- package/dist/memory/types.d.ts +7 -2
- package/dist/models/factory.d.ts +67 -2
- package/dist/models/factory.js +256 -10
- package/dist/project/config.js +1 -1
- package/dist/project/load.d.ts +7 -0
- package/dist/project/load.js +29 -6
- package/dist/runner.js +7 -0
- package/dist/tools/exa.js +1 -1
- package/dist/tools/files.d.ts +81 -0
- package/dist/tools/files.js +1472 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +1 -1
- package/dist/tools/sandbox.d.ts +44 -0
- package/dist/tools/sandbox.js +162 -5
- package/dist/tools/workspace.js +81 -3
- package/dist/trajectory.d.ts +19 -1
- package/dist/trajectory.js +53 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +1 -0
- package/package.json +1 -1
package/dist/memory/store.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Lock } from '../lock.ts';
|
|
1
2
|
import { MemoryGraph } from './graph.ts';
|
|
2
3
|
import { VectorBlock } from './vectors.ts';
|
|
3
4
|
export declare const MEMORY_VERSION = 1;
|
|
@@ -19,21 +20,23 @@ export interface MemoryManifest {
|
|
|
19
20
|
edges: number;
|
|
20
21
|
updatedAt: string;
|
|
21
22
|
}
|
|
22
|
-
export interface Lock {
|
|
23
|
-
pid: number;
|
|
24
|
-
host: string;
|
|
25
|
-
startedAt: string;
|
|
26
|
-
}
|
|
27
23
|
export interface OpenOptions {
|
|
28
24
|
/** the embedder the caller intends to use; a mismatch with the manifest is fatal */
|
|
29
25
|
embedding?: MemoryEmbedding;
|
|
30
26
|
/** take the directory lock; off for read-only inspection (`zen memory ls`) */
|
|
31
27
|
lock?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Recall, and nothing else. Writing is refused rather than dropped, the
|
|
30
|
+
* directory is never created, and no lock is taken — so any number of runs
|
|
31
|
+
* may share one graph at once, which is the whole point of it.
|
|
32
|
+
*/
|
|
33
|
+
readOnly?: boolean;
|
|
32
34
|
}
|
|
33
35
|
export declare class MemoryStore {
|
|
34
36
|
#private;
|
|
35
37
|
readonly dir: string;
|
|
36
38
|
readonly graph: MemoryGraph;
|
|
39
|
+
readonly readOnly: boolean;
|
|
37
40
|
private constructor();
|
|
38
41
|
static open(dir: string, opts?: OpenOptions): Promise<MemoryStore>;
|
|
39
42
|
get embedding(): MemoryEmbedding | undefined;
|
package/dist/memory/store.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { unlinkSync } from 'node:fs';
|
|
2
2
|
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { hostname } from 'node:os';
|
|
4
3
|
import { join } from 'node:path';
|
|
4
|
+
import { claimLock, liveHolder, ownLock } from "../lock.js";
|
|
5
5
|
import { MemoryGraph } from "./graph.js";
|
|
6
6
|
import { MemoryError } from "./types.js";
|
|
7
7
|
import { VectorBlock } from "./vectors.js";
|
|
@@ -34,18 +34,29 @@ export const LOCK_FILE = '.lock';
|
|
|
34
34
|
export class MemoryStore {
|
|
35
35
|
dir;
|
|
36
36
|
graph;
|
|
37
|
+
readOnly;
|
|
37
38
|
#embedding;
|
|
38
39
|
#vectors;
|
|
39
40
|
#locked = false;
|
|
40
|
-
constructor(dir, graph, vectors, embedding) {
|
|
41
|
+
constructor(dir, graph, vectors, embedding, readOnly) {
|
|
41
42
|
this.dir = dir;
|
|
42
43
|
this.graph = graph;
|
|
43
44
|
this.#vectors = vectors;
|
|
44
45
|
this.#embedding = embedding;
|
|
46
|
+
this.readOnly = readOnly;
|
|
45
47
|
}
|
|
46
48
|
static async open(dir, opts = {}) {
|
|
47
|
-
|
|
49
|
+
const readOnly = opts.readOnly === true;
|
|
50
|
+
if (!readOnly) {
|
|
51
|
+
await mkdir(join(dir, FILES_DIR), { recursive: true });
|
|
52
|
+
}
|
|
48
53
|
const manifest = await readManifest(dir);
|
|
54
|
+
// Opening for writing brings a memory into existence; opening one to
|
|
55
|
+
// read from has to find it, or a mistyped path is an empty graph that
|
|
56
|
+
// answers every question with silence and reports no error.
|
|
57
|
+
if (readOnly && !manifest) {
|
|
58
|
+
throw new MemoryError(`${dir} is not a memory`, `no ${MANIFEST_FILE} in it, and a read-only open cannot create one`);
|
|
59
|
+
}
|
|
49
60
|
if (manifest?.embedding && opts.embedding) {
|
|
50
61
|
const a = manifest.embedding;
|
|
51
62
|
const b = opts.embedding;
|
|
@@ -59,8 +70,10 @@ export class MemoryStore {
|
|
|
59
70
|
: new MemoryGraph();
|
|
60
71
|
const embedding = opts.embedding ?? manifest?.embedding;
|
|
61
72
|
const vectors = manifest && embedding ? await readVectors(dir, embedding) : undefined;
|
|
62
|
-
const store = new MemoryStore(dir, graph, vectors, embedding);
|
|
63
|
-
|
|
73
|
+
const store = new MemoryStore(dir, graph, vectors, embedding, readOnly);
|
|
74
|
+
// A reader that claimed the lock would only be refusing itself, and the
|
|
75
|
+
// readers of one graph are meant to be able to refuse each other least.
|
|
76
|
+
if (!readOnly && opts.lock !== false) {
|
|
64
77
|
store.#claim();
|
|
65
78
|
}
|
|
66
79
|
return store;
|
|
@@ -97,6 +110,11 @@ export class MemoryStore {
|
|
|
97
110
|
}
|
|
98
111
|
/** Manifest last: a torn commit reads as no memory, never as a partial one. */
|
|
99
112
|
async commit() {
|
|
113
|
+
// Loudly, not quietly. A write that is silently dropped is a run that
|
|
114
|
+
// believes it remembered something and did not.
|
|
115
|
+
if (this.readOnly) {
|
|
116
|
+
throw new MemoryError('this memory is open read-only', 'nothing this run learns can be kept — point it at a memory of its own');
|
|
117
|
+
}
|
|
100
118
|
await atomic(join(this.dir, GRAPH_FILE), JSON.stringify(this.graph.export()));
|
|
101
119
|
const vectors = this.#vectors;
|
|
102
120
|
if (vectors) {
|
|
@@ -125,33 +143,8 @@ export class MemoryStore {
|
|
|
125
143
|
// Already gone, or taken over as stale: neither is worth failing a run for.
|
|
126
144
|
}
|
|
127
145
|
}
|
|
128
|
-
/**
|
|
129
|
-
* `wx` makes the create and the check one operation, so two runs racing for
|
|
130
|
-
* the same project cannot both win.
|
|
131
|
-
*/
|
|
132
146
|
#claim() {
|
|
133
|
-
|
|
134
|
-
const lock = {
|
|
135
|
-
pid: process.pid,
|
|
136
|
-
host: hostname(),
|
|
137
|
-
startedAt: new Date().toISOString(),
|
|
138
|
-
};
|
|
139
|
-
const body = `${JSON.stringify(lock, null, 4)}\n`;
|
|
140
|
-
try {
|
|
141
|
-
writeFileSync(path, body, { flag: 'wx' });
|
|
142
|
-
this.#locked = true;
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
catch (err) {
|
|
146
|
-
if (err.code !== 'EEXIST') {
|
|
147
|
-
throw err;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
const held = readLock(path);
|
|
151
|
-
if (held && held.host === hostname() && alive(held.pid)) {
|
|
152
|
-
throw new MemoryError(`this memory is in use (pid ${held.pid}, since ${held.startedAt})`, 'wait for that run to finish, or remove .lock if it crashed');
|
|
153
|
-
}
|
|
154
|
-
writeFileSync(path, body);
|
|
147
|
+
claimLock(join(this.dir, LOCK_FILE), ownLock(), (held) => new MemoryError(`this memory is in use (pid ${held.pid}, since ${held.startedAt})`, 'wait for that run to finish, or remove .lock if it crashed'));
|
|
155
148
|
this.#locked = true;
|
|
156
149
|
}
|
|
157
150
|
}
|
|
@@ -208,28 +201,6 @@ async function atomic(path, body) {
|
|
|
208
201
|
* directory this process has no intention of opening for writing.
|
|
209
202
|
*/
|
|
210
203
|
export function lockHolder(dir) {
|
|
211
|
-
|
|
212
|
-
return held && held.host === hostname() && alive(held.pid) ? held : undefined;
|
|
213
|
-
}
|
|
214
|
-
function readLock(path) {
|
|
215
|
-
try {
|
|
216
|
-
return JSON.parse(readFileSync(path, 'utf8'));
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
return undefined;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* `kill(pid, 0)` sends no signal and only asks whether the process exists.
|
|
224
|
-
* EPERM means it exists and belongs to someone else, which still counts.
|
|
225
|
-
*/
|
|
226
|
-
function alive(pid) {
|
|
227
|
-
try {
|
|
228
|
-
process.kill(pid, 0);
|
|
229
|
-
return true;
|
|
230
|
-
}
|
|
231
|
-
catch (err) {
|
|
232
|
-
return err.code === 'EPERM';
|
|
233
|
-
}
|
|
204
|
+
return liveHolder(join(dir, LOCK_FILE));
|
|
234
205
|
}
|
|
235
206
|
//# sourceMappingURL=store.js.map
|
package/dist/memory/tools.js
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
2
|
+
* The five tools an agent gets. `search` and `load` are split because finding
|
|
3
|
+
* is not reading — search returns stitched context the model never asked for,
|
|
4
|
+
* and counting that as use would poison recency. `grep` is separate from
|
|
5
|
+
* `search` because exactness is not a tuning of nearness: a ranking returns the
|
|
6
|
+
* top of a list, so it can say what is closest but never that nothing is there.
|
|
7
|
+
* `commit` is one transaction because a remembered thing is a subgraph, and
|
|
8
|
+
* building it with three calls leaves the graph half-formed if the model stops
|
|
9
|
+
* early. `forget` is separate because deleting rarely happens in the same
|
|
10
|
+
* breath as creating, and a `forget` buried inside a truncated commit preview
|
|
11
|
+
* would be both irreversible and invisible.
|
|
10
12
|
*
|
|
11
13
|
* `audience` reaches the schema only when the binding grants more than one
|
|
12
14
|
* label, and `sees` never does: an agent that could name its own audience
|
|
13
15
|
* could read another agent's slice just by asking for it.
|
|
14
16
|
*/
|
|
15
17
|
import { hash } from "../payload.js";
|
|
16
|
-
import { MEMORY_COMMIT_TOOL, MEMORY_FORGET_TOOL, MEMORY_LOAD_TOOL, MEMORY_SEARCH_TOOL, tool, withEffects, } from "../types.js";
|
|
18
|
+
import { MEMORY_COMMIT_TOOL, MEMORY_FORGET_TOOL, MEMORY_GREP_TOOL, MEMORY_LOAD_TOOL, MEMORY_SEARCH_TOOL, tool, withEffects, } from "../types.js";
|
|
19
|
+
import { GREP_FIELDS, grepMemory } from "./grep.js";
|
|
17
20
|
import { renderRecollection } from "./render.js";
|
|
18
21
|
import { canForget, canWrite, KIND_HELP, MemoryError, RELATION_HELP, } from "./types.js";
|
|
19
22
|
/** Deterministic, so a replayed call is the same op and not a second one. */
|
|
20
23
|
export function memoryOpId(runId, callId) {
|
|
21
24
|
return hash(`${runId}\u0000${callId}`);
|
|
22
25
|
}
|
|
26
|
+
/** Enough nodes to settle a question, few enough not to be a context dump. */
|
|
27
|
+
const GREPPED = 20;
|
|
23
28
|
export function memoryTools(opts) {
|
|
24
29
|
const { index, binding } = opts;
|
|
25
30
|
const kinds = [...index.kinds];
|
|
@@ -131,6 +136,97 @@ export function memoryTools(opts) {
|
|
|
131
136
|
})), { kind: 'memory_op', spec });
|
|
132
137
|
},
|
|
133
138
|
}),
|
|
139
|
+
tool({
|
|
140
|
+
name: MEMORY_GREP_TOOL,
|
|
141
|
+
description: 'Find an exact string in memory, the way grep does: every node whose text, ' +
|
|
142
|
+
'metadata or remembered file contains it, with the matching lines. Use this ' +
|
|
143
|
+
`instead of ${MEMORY_SEARCH_TOOL} when the answer has to be complete rather ` +
|
|
144
|
+
'than close — whether something was already recorded, or where a name, path, ' +
|
|
145
|
+
'id or command appears. This is the only correct way to search the memory ' +
|
|
146
|
+
'directly; never run a shell grep over /memory. Read a whole node with ' +
|
|
147
|
+
`${MEMORY_LOAD_TOOL}.`,
|
|
148
|
+
parameters: {
|
|
149
|
+
type: 'object',
|
|
150
|
+
properties: {
|
|
151
|
+
pattern: {
|
|
152
|
+
type: 'string',
|
|
153
|
+
description: 'the exact text to look for; matched case-insensitively',
|
|
154
|
+
},
|
|
155
|
+
regex: {
|
|
156
|
+
type: 'boolean',
|
|
157
|
+
description: 'read the pattern as a regular expression, matched per line',
|
|
158
|
+
},
|
|
159
|
+
case_sensitive: { type: 'boolean', description: 'match case exactly' },
|
|
160
|
+
kinds: {
|
|
161
|
+
type: 'array',
|
|
162
|
+
items: { type: 'string', enum: kinds },
|
|
163
|
+
description: 'restrict to these kinds',
|
|
164
|
+
},
|
|
165
|
+
in: {
|
|
166
|
+
type: 'array',
|
|
167
|
+
items: { type: 'string', enum: [...GREP_FIELDS] },
|
|
168
|
+
description: 'which parts to look in; all of them by default',
|
|
169
|
+
},
|
|
170
|
+
include_superseded: {
|
|
171
|
+
type: 'boolean',
|
|
172
|
+
description: 'also search nodes that have been corrected by a newer one; they come back marked stale',
|
|
173
|
+
},
|
|
174
|
+
limit: { type: 'integer', description: `nodes to return; default ${GREPPED}` },
|
|
175
|
+
},
|
|
176
|
+
required: ['pattern'],
|
|
177
|
+
additionalProperties: false,
|
|
178
|
+
},
|
|
179
|
+
execute: async (args, tc) => {
|
|
180
|
+
const opId = memoryOpId(tc.state.runId, tc.callId);
|
|
181
|
+
let res;
|
|
182
|
+
try {
|
|
183
|
+
res = await grepMemory(index.store, args.pattern, {
|
|
184
|
+
sees: binding.sees,
|
|
185
|
+
regex: args.regex,
|
|
186
|
+
caseSensitive: args.case_sensitive,
|
|
187
|
+
kinds: args.kinds,
|
|
188
|
+
in: args.in,
|
|
189
|
+
stale: args.include_superseded ? 'include' : 'exclude',
|
|
190
|
+
limit: args.limit ?? GREPPED,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
return refusal(err);
|
|
195
|
+
}
|
|
196
|
+
// A complete answer, so nothing found is information rather than
|
|
197
|
+
// a failed lookup — that is the whole reason this tool exists.
|
|
198
|
+
if (!res.found && !res.skipped.length) {
|
|
199
|
+
return `nothing in memory contains ${JSON.stringify(args.pattern)}`;
|
|
200
|
+
}
|
|
201
|
+
const spec = {
|
|
202
|
+
kind: 'op',
|
|
203
|
+
op: 'grep',
|
|
204
|
+
opId,
|
|
205
|
+
nodes: res.matches.map((m) => ({
|
|
206
|
+
id: m.node.id,
|
|
207
|
+
kind: m.node.kind,
|
|
208
|
+
revision: m.node.revision,
|
|
209
|
+
})),
|
|
210
|
+
edges: [],
|
|
211
|
+
files: 0,
|
|
212
|
+
};
|
|
213
|
+
return withEffects({
|
|
214
|
+
found: res.found,
|
|
215
|
+
truncated: res.truncated || undefined,
|
|
216
|
+
matches: res.matches.map((m) => ({
|
|
217
|
+
id: m.node.id,
|
|
218
|
+
kind: m.node.kind,
|
|
219
|
+
stale: m.stale || undefined,
|
|
220
|
+
file: m.node.file?.path,
|
|
221
|
+
hits: m.hits.map((h) => ({ in: h.where, line: h.line, text: h.text })),
|
|
222
|
+
more: m.more || undefined,
|
|
223
|
+
})),
|
|
224
|
+
// Named, because a file that was not read means a hit in
|
|
225
|
+
// it would not have been reported either.
|
|
226
|
+
unsearched: res.skipped.length ? res.skipped : undefined,
|
|
227
|
+
}, { kind: 'memory_op', spec });
|
|
228
|
+
},
|
|
229
|
+
}),
|
|
134
230
|
];
|
|
135
231
|
if (!canWrite(binding)) {
|
|
136
232
|
return tools;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { MemoryNode } from './types.ts';
|
|
2
|
+
export declare function comparable(a: MemoryNode, b: MemoryNode): boolean;
|
|
3
|
+
export declare function twinKey(node: MemoryNode): string;
|
|
4
|
+
export declare function audienceKey(node: MemoryNode): string;
|
|
5
|
+
export declare function sameContent(a: MemoryNode, b: MemoryNode): boolean;
|
|
6
|
+
//# sourceMappingURL=twin.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { flatten } from "./index.js";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// When two memories are the same memory
|
|
4
|
+
//
|
|
5
|
+
// Shared by `merge`, which folds a twin onto the node already held, and `diff`,
|
|
6
|
+
// which reports it. One rule, so what diff calls a re-commit is exactly what
|
|
7
|
+
// merge would have folded.
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
export function comparable(a, b) {
|
|
10
|
+
return a.id !== b.id && a.kind === b.kind && !a.file && audienceKey(a) === audienceKey(b);
|
|
11
|
+
}
|
|
12
|
+
export function twinKey(node) {
|
|
13
|
+
return [node.kind, audienceKey(node), flatten(node.text)].join('\u0000');
|
|
14
|
+
}
|
|
15
|
+
export function audienceKey(node) {
|
|
16
|
+
return [...node.audience].sort().join('|');
|
|
17
|
+
}
|
|
18
|
+
export function sameContent(a, b) {
|
|
19
|
+
return (a.kind === b.kind &&
|
|
20
|
+
a.text === b.text &&
|
|
21
|
+
audienceKey(a) === audienceKey(b) &&
|
|
22
|
+
a.file?.sha256 === b.file?.sha256 &&
|
|
23
|
+
deep(a.metadata, b.metadata));
|
|
24
|
+
}
|
|
25
|
+
function deep(a, b) {
|
|
26
|
+
if (a === b) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(a) !== Array.isArray(b)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const left = a;
|
|
36
|
+
const right = b;
|
|
37
|
+
const keys = Object.keys(left);
|
|
38
|
+
return keys.length === Object.keys(right).length && keys.every((k) => deep(left[k], right[k]));
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=twin.js.map
|
package/dist/memory/types.d.ts
CHANGED
|
@@ -169,8 +169,13 @@ export interface MemoryRecallSpec {
|
|
|
169
169
|
}
|
|
170
170
|
export interface MemoryOpSpec {
|
|
171
171
|
kind: 'op';
|
|
172
|
-
/**
|
|
173
|
-
|
|
172
|
+
/**
|
|
173
|
+
* `load` is here rather than under recall: it hands the model whole bodies,
|
|
174
|
+
* and it writes. `grep` is here because it is not a recall either — there is
|
|
175
|
+
* no query vector, no subgraph and no ranking to record, only the nodes a
|
|
176
|
+
* pattern happened to hit.
|
|
177
|
+
*/
|
|
178
|
+
op: 'commit' | 'forget' | 'load' | 'grep';
|
|
174
179
|
/** sha256(runId, callId) — deterministic, so a replay is deduplicated */
|
|
175
180
|
opId: string;
|
|
176
181
|
/** nodes this op created, changed, removed or read in full */
|
package/dist/models/factory.d.ts
CHANGED
|
@@ -83,8 +83,9 @@ export interface ProviderSpec extends Credentials {
|
|
|
83
83
|
}
|
|
84
84
|
/**
|
|
85
85
|
* What to ask for, and of whom. Vendor differences live in the *provider*, so
|
|
86
|
-
* there is nothing here to discriminate on: the option interfaces are unioned
|
|
87
|
-
*
|
|
86
|
+
* there is nothing here to discriminate on: the option interfaces are unioned,
|
|
87
|
+
* which makes every vendor's knobs type-legal on every model. `model()` rejects
|
|
88
|
+
* the ones the resolved adapter would not read — see `KNOBS`.
|
|
88
89
|
*
|
|
89
90
|
* `Credentials` are still accepted inline, for a one-off model that does not
|
|
90
91
|
* warrant a declared provider. Using them opts out of the shared client.
|
|
@@ -149,6 +150,48 @@ export interface ModelRequirement {
|
|
|
149
150
|
}
|
|
150
151
|
export declare const providerKinds: ProviderKind[];
|
|
151
152
|
export declare function isProviderKind(name: string): name is ProviderKind;
|
|
153
|
+
/**
|
|
154
|
+
* Vertex serves three request formats behind one account, and the model id
|
|
155
|
+
* says which: Google's own models through the GenAI API, Claude through
|
|
156
|
+
* Anthropic's Messages format at `rawPredict`, and every other publisher's
|
|
157
|
+
* (`xai/grok-4.1-fast-reasoning`, `qwen/qwen3-…-maas`) through the
|
|
158
|
+
* OpenAI-compatible endpoint, which names its models `publisher/model`.
|
|
159
|
+
*/
|
|
160
|
+
export type VertexRoute = 'gemini' | 'anthropic' | 'openai';
|
|
161
|
+
export declare function vertexRoute(model: string): VertexRoute;
|
|
162
|
+
/**
|
|
163
|
+
* Which knobs each adapter actually reads.
|
|
164
|
+
*
|
|
165
|
+
* `ModelSpec` unions every vendor's option interface, so `reasoningEffort` on a
|
|
166
|
+
* Gemini model is type-legal and the yaml schema accepts it too — and until
|
|
167
|
+
* this table it was dropped in silence, which is the worst outcome available:
|
|
168
|
+
* the config claims a setting is in force, the request never carries it, and
|
|
169
|
+
* the run looks healthy. No static type could have caught it either, because
|
|
170
|
+
* `provider` is a free name and its kind is not known until resolution.
|
|
171
|
+
*
|
|
172
|
+
* Translating instead was the alternative, and it is worse. The vendors do not
|
|
173
|
+
* mean the same thing by the same word: effort is a coarse level on OpenAI, a
|
|
174
|
+
* token budget on Anthropic, and *both* on Google depending on the model
|
|
175
|
+
* generation. A silent remap would make one line mean four things and bury the
|
|
176
|
+
* 2.5/3 split that `gemini.ts` deliberately refuses to paper over.
|
|
177
|
+
*/
|
|
178
|
+
declare const KNOBS: {
|
|
179
|
+
readonly 'openai/chat': readonly ["reasoningEffort"];
|
|
180
|
+
readonly 'openai/responses': readonly ["reasoningEffort", "reasoningSummary", "store"];
|
|
181
|
+
readonly anthropic: readonly ["maxTokens", "thinkingBudgetTokens"];
|
|
182
|
+
readonly gemini: readonly ["maxTokens", "thinkingBudget", "thinkingLevel", "includeThoughts"];
|
|
183
|
+
readonly openrouter: readonly ["maxTokens", "reasoningEffort", "reasoningSummary", "routing", "fallbacks", "plugins", "serviceTier"];
|
|
184
|
+
};
|
|
185
|
+
/** An adapter, in the terms `KNOBS` is keyed by: a protocol, split by api where there are two. */
|
|
186
|
+
export type KnobTarget = keyof typeof KNOBS;
|
|
187
|
+
/** A knob a spec carries that the adapter it resolves to will never read. */
|
|
188
|
+
export interface KnobIssue {
|
|
189
|
+
knob: string;
|
|
190
|
+
/** the adapter that would have had to read it */
|
|
191
|
+
target: KnobTarget;
|
|
192
|
+
/** what that adapter does read */
|
|
193
|
+
supported: readonly string[];
|
|
194
|
+
}
|
|
152
195
|
/**
|
|
153
196
|
* Substitutes `${VAR}` references. An unset variable with no fallback is an
|
|
154
197
|
* error naming both the variable and where it was written, because the
|
|
@@ -176,6 +219,12 @@ export declare class ModelRegistry {
|
|
|
176
219
|
names(): string[];
|
|
177
220
|
/** The shared client for a provider, built on first use. */
|
|
178
221
|
client(name: string): ProviderClient;
|
|
222
|
+
/**
|
|
223
|
+
* Knobs a ref carries that its adapter will never read — the same check
|
|
224
|
+
* `model()` refuses on, minus the client, so a report can name them before
|
|
225
|
+
* a credential is resolved or anything is contacted.
|
|
226
|
+
*/
|
|
227
|
+
knobs(ref: ModelRef): KnobIssue[];
|
|
179
228
|
/** Turns a shorthand or a spec into a `Model`. */
|
|
180
229
|
model(ref: ModelRef): Model;
|
|
181
230
|
/**
|
|
@@ -200,6 +249,21 @@ export declare class ModelRegistry {
|
|
|
200
249
|
/** A declared kind wins; otherwise a built-in name is its own kind. */
|
|
201
250
|
kindOf(name: string): ProviderKind;
|
|
202
251
|
}
|
|
252
|
+
export interface VertexEndpoint {
|
|
253
|
+
project: string;
|
|
254
|
+
location: string;
|
|
255
|
+
/** `https://…/v1/projects/<project>/locations/<location>` */
|
|
256
|
+
base: string;
|
|
257
|
+
/** a fresh access token from Application Default Credentials, or `spec.token` */
|
|
258
|
+
token: () => Promise<string>;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Where a Vertex provider's partner models are served, and how to authenticate
|
|
262
|
+
* there. The GenAI SDK does both for Gemini; nothing does them for Claude or
|
|
263
|
+
* the OpenAI-compatible endpoint, so they are resolved here the same way:
|
|
264
|
+
* `project`, then GOOGLE_CLOUD_PROJECT, then the service-account file.
|
|
265
|
+
*/
|
|
266
|
+
export declare function vertexEndpoint(spec?: ProviderSpec, name?: string): VertexEndpoint;
|
|
203
267
|
/**
|
|
204
268
|
* The registry behind the bare `createModel`. An application that never
|
|
205
269
|
* declares a provider still gets shared clients through it, because the
|
|
@@ -215,4 +279,5 @@ export declare const defaultModels: ModelRegistry;
|
|
|
215
279
|
export declare function createModel(ref: ModelRef, registry?: ModelRegistry): Model;
|
|
216
280
|
/** `createModel`'s counterpart for vectors, over the same registry and clients. */
|
|
217
281
|
export declare function createEmbedder(ref: EmbeddingRef, registry?: ModelRegistry): Embedder;
|
|
282
|
+
export {};
|
|
218
283
|
//# sourceMappingURL=factory.d.ts.map
|