@zenera/gym 1.1.39 → 1.1.41

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 (51) hide show
  1. package/README.md +169 -104
  2. package/dist/agents.d.ts +25 -0
  3. package/dist/agents.js +52 -0
  4. package/dist/bindings.d.ts +25 -0
  5. package/dist/bindings.js +81 -0
  6. package/dist/box.d.ts +39 -36
  7. package/dist/box.js +183 -217
  8. package/dist/cache.d.ts +24 -14
  9. package/dist/cache.js +102 -66
  10. package/dist/catalog.d.ts +83 -0
  11. package/dist/catalog.js +293 -0
  12. package/dist/command.d.ts +1 -0
  13. package/dist/command.js +235 -68
  14. package/dist/fake.d.ts +4 -0
  15. package/dist/fake.js +142 -0
  16. package/dist/generate.d.ts +28 -24
  17. package/dist/generate.js +417 -120
  18. package/dist/inputs.d.ts +12 -0
  19. package/dist/inputs.js +132 -0
  20. package/dist/knowledge.d.ts +50 -0
  21. package/dist/knowledge.js +199 -0
  22. package/dist/logger.d.ts +10 -8
  23. package/dist/logger.js +4 -3
  24. package/dist/paging.d.ts +2 -2
  25. package/dist/paging.js +2 -2
  26. package/dist/probe.d.ts +5 -5
  27. package/dist/probe.js +15 -15
  28. package/dist/prompt.d.ts +31 -18
  29. package/dist/prompt.js +160 -146
  30. package/dist/quiet.d.ts +2 -0
  31. package/dist/quiet.js +7 -0
  32. package/dist/router.js +5 -1
  33. package/dist/runtime.d.ts +160 -0
  34. package/dist/runtime.js +229 -0
  35. package/dist/schema.js +1 -2
  36. package/dist/server.js +98 -57
  37. package/dist/setup.d.ts +47 -6
  38. package/dist/setup.js +157 -21
  39. package/dist/spec.d.ts +4 -3
  40. package/dist/spec.js +73 -22
  41. package/dist/store.d.ts +78 -0
  42. package/dist/store.js +253 -0
  43. package/dist/synth.d.ts +42 -0
  44. package/dist/synth.js +589 -0
  45. package/dist/worker.d.ts +41 -0
  46. package/dist/worker.js +99 -0
  47. package/package.json +5 -3
  48. package/dist/envelope.d.ts +0 -13
  49. package/dist/envelope.js +0 -9
  50. package/dist/image.d.ts +0 -25
  51. package/dist/image.js +0 -81
package/dist/box.js CHANGED
@@ -1,259 +1,225 @@
1
- import { SANDBOX_MOUNT, SandboxError, SandboxPool, pidAlive, runProcess, } from '@zenera/neo';
2
- import { randomUUID } from 'node:crypto';
3
- import { mkdirSync, rmSync } from 'node:fs';
4
- import { readFile, writeFile } from 'node:fs/promises';
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { writeFile } from 'node:fs/promises';
5
4
  import { join } from 'node:path';
5
+ import { Worker } from 'node:worker_threads';
6
6
  // ---------------------------------------------------------------------------
7
- // Where generators run
7
+ // Where handlers run
8
8
  //
9
- // One container for the whole process, offline, with the gym's own directory
10
- // bind-mounted at /workspace. That single mount is what makes the file contract
11
- // work in both directions: the host writes `input.json` and reads
12
- // `output.json`, the generator sees the same two paths from inside, and neither
13
- // side has to serialise anything through a pipe.
9
+ // A worker thread, not a container. The code is TypeScript that Node runs as
10
+ // it is, the world is a SQLite file the worker opens itself, and the only thing
11
+ // that crosses back to this thread is a request for content an agent has to
12
+ // write.
14
13
  //
15
- // Nothing the model wrote ever reaches an argument on this side. The generator
16
- // is invoked by a script built here out of paths derived here, and the script
17
- // itself travels on stdin — the same rule the sandbox tools follow.
14
+ // What a thread does not give is a wall between the handler and the network.
15
+ // The handler is written by the model this process asked, from documents the
16
+ // user named, and it is judged before it is kept; the worker gets an empty
17
+ // environment, so no credential is in reach either way.
18
18
  // ---------------------------------------------------------------------------
19
- export const GENERATORS = 'generators';
20
- const IO = 'io';
21
- const ENTRY = 'gen.py';
22
- /** Every gym container's name starts with this; `cache clear` relies on it. */
23
- export const CONTAINER_PREFIX = 'zn-gym-';
24
- /** `zn-gym-<pid>-<digest>`; a name without the pid predates per-process containers. */
25
- const OWNED_BY = /^zn-gym-(\d+)-[0-9a-f]+$/;
26
- /** Pause before the one retry of an engine failure, long enough for a concurrent rm to finish. */
27
- const ENGINE_RETRY_MS = 300;
19
+ export const HANDLERS = 'handlers';
20
+ const ENTRY = 'handler.ts';
28
21
  /**
29
- * The container engine could not run the generator at all. Nothing is known
30
- * about the generator from this, so it must never be fed back to a model as a
31
- * fault in the code: rewriting a working file cannot bring a container back.
22
+ * The worker could not be started at all. This says nothing about the handler
23
+ * and must never be fed back to a model as a fault in its code.
32
24
  */
33
- export class SandboxUnavailable extends Error {
25
+ export class WorkerUnavailable extends Error {
34
26
  hint;
35
- container;
36
27
  stderr;
37
28
  constructor(message, opts) {
38
29
  super(message);
39
- this.name = 'SandboxUnavailable';
30
+ this.name = 'WorkerUnavailable';
40
31
  this.hint = opts.hint;
41
- this.container = opts.container;
42
32
  this.stderr = opts.stderr;
43
33
  }
44
34
  }
45
35
  export class Box {
46
36
  root;
47
- #pool;
37
+ db;
38
+ synthesize;
39
+ #bindings;
48
40
  #timeout;
49
- #engine;
50
- #exec;
51
- #owner;
41
+ #calls = new Map();
42
+ #versions = new Map();
43
+ #worker;
44
+ #next = 0;
52
45
  constructor(opts) {
53
46
  this.root = opts.root;
54
- this.#timeout = opts.timeout ?? 30;
55
- this.#engine = opts.engine ?? 'podman';
56
- this.#exec = opts.exec ?? runProcess;
57
- this.#owner = opts.owner ?? process.pid;
58
- mkdirSync(join(opts.root, GENERATORS), { recursive: true });
59
- mkdirSync(join(opts.root, IO), { recursive: true });
60
- this.#pool = new SandboxPool({
61
- root: opts.root,
62
- // One container per process. The name is otherwise a pure function
63
- // of the configuration, so two servers on the same --cache shared
64
- // one container, and each start (`fresh`) or exit (`dispose`)
65
- // removed it from under the other: `exited 125, no such container`.
66
- key: `gym-${this.#owner}`,
67
- image: opts.image,
68
- // The one line that keeps model-written code from calling home.
69
- network: 'none',
70
- workdir: SANDBOX_MOUNT,
71
- timeout: this.#timeout,
72
- // Deliberately NOT persisted, unlike `zen`'s sandbox. A container's
73
- // name is a hash of its configuration, which includes the host
74
- // path but not the directory behind it — so a cache directory that
75
- // is deleted and recreated (`zen gym cache clear`, or any rm -rf)
76
- // gets a stopped container reattached whose bind mount still points
77
- // at the old inode. Everything written here is then invisible
78
- // inside, and every generator fails with "can't open file".
79
- // `zen` persists to keep `pip install`s; the libraries here are
80
- // baked into the image, so there is nothing to keep.
81
- persist: false,
82
- readOnly: false,
83
- // Deliberately empty: the keyring is in this process's environment
84
- // and none of it belongs in the container.
85
- env: {},
86
- engine: opts.engine,
87
- exec: opts.exec,
88
- });
89
- }
90
- get sandbox() {
91
- return this.#pool.for();
47
+ this.db = opts.db;
48
+ this.#timeout = (opts.timeout ?? 30) * 1000;
49
+ this.synthesize = opts.synthesize;
50
+ this.#bindings = opts.bindings ?? (() => ({}));
51
+ mkdirSync(join(opts.root, HANDLERS), { recursive: true });
52
+ // Handlers are ES modules whatever the directory above them says.
53
+ writeFileSync(join(opts.root, HANDLERS, 'package.json'), '{ "type": "module", "private": true }\n');
92
54
  }
93
- /**
94
- * Removes this process's container if a previous owner of the pid left one,
95
- * and every gym container whose owning process is gone. Belt to
96
- * `persist: false`'s braces: a hard kill never runs `dispose`, and the
97
- * leftover is exactly the stale-mount trap above. A container whose owner
98
- * is alive is another server's, and is never touched.
99
- */
100
- async fresh() {
101
- const listed = await this.#exec(this.#engine, ['ps', '--all', '--filter', `name=^${CONTAINER_PREFIX}`, '--format', '{{.Names}}'], { timeoutMs: 60_000 }).catch(() => undefined);
102
- const orphans = (listed?.code === 0 ? listed.stdout.split('\n') : [])
103
- .map((n) => n.trim())
104
- .filter((name) => {
105
- const pid = Number(OWNED_BY.exec(name)?.[1]);
106
- return Number.isInteger(pid) && pid !== this.#owner && !pidAlive(pid);
107
- });
108
- await this.#exec(this.#engine, ['rm', '--force', '--volumes', this.sandbox.name, ...orphans], { timeoutMs: 60_000 }).catch(() => undefined);
109
- }
110
- /** Host path of a generator's source file. */
55
+ /** Host path of a handler's source file. */
111
56
  sourceOf(key) {
112
- return join(this.root, GENERATORS, key, ENTRY);
57
+ return join(this.root, HANDLERS, key, ENTRY);
113
58
  }
114
59
  async write(key, source) {
115
- mkdirSync(join(this.root, GENERATORS, key), { recursive: true });
60
+ mkdirSync(join(this.root, HANDLERS, key), { recursive: true });
116
61
  await writeFile(this.sourceOf(key), source, 'utf8');
62
+ this.#versions.set(key, hash(source));
117
63
  }
118
- /**
119
- * One generator, one input, one output. The io directory is removed
120
- * afterwards whatever happened — a mock server left alone for a week must
121
- * not fill a disk with request envelopes.
122
- *
123
- * Throws `SandboxUnavailable` when the engine, not the generator, failed.
124
- */
125
- async run(key, input) {
126
- const id = randomUUID();
127
- const dir = join(this.root, IO, id);
128
- mkdirSync(dir, { recursive: true });
129
- const started = Date.now();
130
- try {
131
- await writeFile(join(dir, 'input.json'), JSON.stringify(input), 'utf8');
132
- const script = [
133
- `exec python3 ${inside(GENERATORS, key, ENTRY)}`,
134
- inside(IO, id, 'input.json'),
135
- inside(IO, id, 'output.json'),
136
- ].join(' ');
137
- let res = await this.#exec1(script);
138
- if (engineFault(res)) {
139
- // `exec` already brought a vanished container back once; a
140
- // second fault is usually a concurrent rm still settling.
141
- await new Promise((settle) => setTimeout(settle, ENGINE_RETRY_MS));
142
- res = await this.#exec1(script);
143
- }
144
- const fault = engineFault(res);
145
- if (fault) {
146
- throw this.#unavailable(fault, res.stderr.trim() || res.stdout.trim());
147
- }
148
- const stderr = res.stderr.trim();
149
- if (res.timed_out) {
150
- return {
151
- ok: false,
152
- fault: `took longer than ${this.#timeout}s`,
153
- stderr,
154
- durationMs: Date.now() - started,
155
- };
156
- }
157
- if (res.exit_code !== 0) {
158
- return {
159
- ok: false,
160
- fault: `exited ${res.exit_code}`,
161
- stderr: stderr || res.stdout.trim(),
162
- durationMs: Date.now() - started,
163
- };
164
- }
165
- let text;
166
- try {
167
- text = await readFile(join(dir, 'output.json'), 'utf8');
168
- }
169
- catch {
170
- return {
171
- ok: false,
172
- fault: 'wrote no output file',
173
- stderr,
174
- durationMs: Date.now() - started,
175
- };
176
- }
177
- try {
178
- return {
179
- ok: true,
180
- value: JSON.parse(text),
181
- stderr,
182
- durationMs: Date.now() - started,
183
- };
184
- }
185
- catch (err) {
186
- return {
187
- ok: false,
188
- fault: `output.json is not JSON: ${err instanceof Error ? err.message : String(err)}`,
189
- stderr,
190
- durationMs: Date.now() - started,
191
- };
192
- }
193
- }
194
- finally {
195
- rmSync(dir, { recursive: true, force: true });
196
- }
64
+ run(key, input, opts = {}) {
65
+ const worker = this.#ensure();
66
+ const id = ++this.#next;
67
+ const message = {
68
+ type: 'run',
69
+ id,
70
+ file: this.sourceOf(key),
71
+ version: this.#versions.get(key) ?? this.#version(key),
72
+ db: opts.db ?? this.db,
73
+ input,
74
+ bindings: opts.bindings ?? this.#bindings(),
75
+ };
76
+ return new Promise((settle) => {
77
+ const call = {
78
+ settle,
79
+ started: Date.now(),
80
+ left: this.#timeout,
81
+ waiting: 0,
82
+ probe: Boolean(opts.probe),
83
+ input,
84
+ };
85
+ this.#calls.set(id, call);
86
+ this.#tick(id, call);
87
+ worker.postMessage(message);
88
+ });
89
+ }
90
+ /** Closes the worker's handle on a borrowed world, so its file can be deleted. */
91
+ release(db) {
92
+ this.#worker?.postMessage({ type: 'close', db });
197
93
  }
198
94
  async dispose() {
199
- await this.#pool.dispose();
95
+ const worker = this.#worker;
96
+ this.#worker = undefined;
97
+ this.#failAll('the server is shutting down');
98
+ await worker?.terminate();
200
99
  }
201
- async #exec1(script) {
100
+ // -----------------------------------------------------------------------
101
+ #ensure() {
102
+ if (this.#worker) {
103
+ return this.#worker;
104
+ }
105
+ const entry = new URL(import.meta.url.endsWith('.ts') ? './worker.ts' : './worker.js', import.meta.url);
106
+ let worker;
202
107
  try {
203
- return await this.sandbox.exec(script, { timeout: this.#timeout });
108
+ worker = new Worker(entry, { stdout: true, stderr: true, env: {} });
204
109
  }
205
110
  catch (err) {
206
- if (err instanceof SandboxError) {
207
- throw new SandboxUnavailable(`${this.#engine} could not run the generator: ${err.message}`, {
208
- hint: err.hint ??
209
- `check \`${this.#engine} ps -a --filter name=${CONTAINER_PREFIX}\``,
210
- container: this.sandbox.name,
211
- });
111
+ throw new WorkerUnavailable(`could not start the handler worker: ${err instanceof Error ? err.message : String(err)}`, { hint: 'Node.js 24 or later is required to run TypeScript handlers' });
112
+ }
113
+ // Handler chatter is not the server's log; read it so it never backs up.
114
+ worker.stdout.resume();
115
+ worker.stderr.resume();
116
+ worker.on('message', (m) => void this.#message(worker, m));
117
+ worker.on('error', (err) => this.#lost(worker, `the worker crashed: ${err.message}`, err.stack));
118
+ worker.on('exit', () => this.#lost(worker, 'the worker exited'));
119
+ this.#worker = worker;
120
+ return worker;
121
+ }
122
+ async #message(worker, m) {
123
+ const call = this.#calls.get(m.id);
124
+ if (!call) {
125
+ return;
126
+ }
127
+ if (m.type === 'done') {
128
+ this.#finish(m.id, call, {
129
+ used: m.used,
130
+ ...(m.ok
131
+ ? { ok: true, value: m.value, status: m.status }
132
+ : call.agentError
133
+ ? { ok: false, fault: call.agentError, stderr: m.stderr, agentFault: true }
134
+ : { ok: false, fault: m.fault, stderr: m.stderr }),
135
+ durationMs: Date.now() - call.started,
136
+ });
137
+ return;
138
+ }
139
+ // An agent is about to work: the handler's clock stops until it is done.
140
+ this.#pause(call);
141
+ call.waiting++;
142
+ let reply;
143
+ try {
144
+ if (!this.synthesize) {
145
+ throw new Error('this server has no world agent to make content with');
212
146
  }
213
- throw err;
147
+ const records = await this.synthesize(m.request, {
148
+ db: m.db,
149
+ probe: call.probe,
150
+ input: call.input,
151
+ });
152
+ reply = { type: 'synthesized', id: m.id, call: m.call, records };
153
+ }
154
+ catch (err) {
155
+ const error = err instanceof Error ? err.message : String(err);
156
+ call.agentError = `the world agent could not make ${m.request?.collection ?? 'content'}: ${error}`;
157
+ reply = { type: 'synthesized', id: m.id, call: m.call, error };
158
+ }
159
+ call.waiting--;
160
+ if (this.#calls.get(m.id) === call && this.#worker === worker) {
161
+ worker.postMessage(reply);
162
+ this.#tick(m.id, call);
214
163
  }
215
164
  }
216
- #unavailable(fault, stderr) {
217
- const name = this.sandbox.name;
218
- const first = stderr.split('\n')[0] ?? '';
219
- const hint = fault === 'missing-file'
220
- ? `the generator is on the host but not inside ${name}, so its bind mount is stale ` +
221
- `(the --cache directory was deleted and recreated while the server ran). ` +
222
- `Restart \`zen gym serve\`.`
223
- : fault === 'no-python'
224
- ? `the image has no python3. Rebuild it: \`zen gym serve --rebuild …\`, or pass a working --image.`
225
- : `${name} was removed or stopped while this server was using it, twice in a row. ` +
226
- `Usual causes: \`zen sandbox clean\` or \`zen gym cache clear\` run meanwhile, ` +
227
- `a ${this.#engine} machine restart, or an older \`zen gym\` (before per-process ` +
228
- `containers) started on the same --cache. Check \`${this.#engine} ps -a --filter ` +
229
- `name=${CONTAINER_PREFIX}\` and \`${this.#engine} machine list\`; the next request ` +
230
- `recreates the container.`;
231
- return new SandboxUnavailable(`${this.#engine} could not run the generator in ${name} — ${first || fault} ` +
232
- `(an engine failure, not a fault in the generator; nothing was regenerated)`, { hint, container: name, stderr: stderr || undefined });
165
+ #tick(id, call) {
166
+ if (call.waiting > 0 || call.timer) {
167
+ return;
168
+ }
169
+ call.since = Date.now();
170
+ call.timer = setTimeout(() => {
171
+ call.timer = undefined;
172
+ this.#finish(id, call, {
173
+ ok: false,
174
+ fault: `took longer than ${this.#timeout / 1000}s`,
175
+ durationMs: Date.now() - call.started,
176
+ });
177
+ // The only way to stop a synchronous loop is to stop its thread.
178
+ const worker = this.#worker;
179
+ this.#worker = undefined;
180
+ this.#failAll('was interrupted: another handler timed out and the worker restarted');
181
+ void worker?.terminate();
182
+ }, Math.max(1, call.left));
233
183
  }
234
- }
235
- /**
236
- * A failure of the container rather than of the code in it. 125 is podman's
237
- * own exit code for "could not exec at all"; the other two are our command
238
- * line failing before a line of the generator ran.
239
- */
240
- function engineFault(res) {
241
- if (res.timed_out) {
242
- return undefined;
184
+ #pause(call) {
185
+ if (call.timer) {
186
+ clearTimeout(call.timer);
187
+ call.timer = undefined;
188
+ call.left -= Date.now() - (call.since ?? Date.now());
189
+ }
243
190
  }
244
- if (res.exit_code === 125) {
245
- return 'engine';
191
+ #finish(id, call, outcome) {
192
+ if (call.timer) {
193
+ clearTimeout(call.timer);
194
+ call.timer = undefined;
195
+ }
196
+ this.#calls.delete(id);
197
+ call.settle(outcome);
246
198
  }
247
- if (res.exit_code === 2 && /can't open file '\/workspace\/generators\//.test(res.stderr)) {
248
- return 'missing-file';
199
+ #lost(worker, why, stderr) {
200
+ if (this.#worker === worker) {
201
+ this.#worker = undefined;
202
+ this.#failAll(why, stderr);
203
+ }
249
204
  }
250
- if (res.exit_code === 127 && /python3: (not found|No such file)/.test(res.stderr)) {
251
- return 'no-python';
205
+ #failAll(fault, stderr) {
206
+ for (const [id, call] of [...this.#calls]) {
207
+ this.#finish(id, call, {
208
+ ok: false,
209
+ fault,
210
+ stderr,
211
+ durationMs: Date.now() - call.started,
212
+ });
213
+ }
214
+ }
215
+ #version(key) {
216
+ try {
217
+ return hash(readFileSync(this.sourceOf(key), 'utf8'));
218
+ }
219
+ catch {
220
+ return 'missing';
221
+ }
252
222
  }
253
- return undefined;
254
- }
255
- /** A path inside the container. Every segment is derived here, never given. */
256
- function inside(...parts) {
257
- return [SANDBOX_MOUNT, ...parts].join('/');
258
223
  }
224
+ const hash = (text) => createHash('sha256').update(text).digest('hex').slice(0, 12);
259
225
  //# sourceMappingURL=box.js.map
package/dist/cache.d.ts CHANGED
@@ -1,13 +1,16 @@
1
1
  import type { Model } from '@zenera/neo';
2
+ import type { Bindings } from './bindings.ts';
2
3
  import type { Box } from './box.ts';
3
- import type { GeneratorInput } from './envelope.ts';
4
+ import type { HandlerRequest } from './runtime.ts';
5
+ import type { Catalog } from './catalog.ts';
4
6
  import { BuildFailed } from './generate.ts';
5
- import type { ExampleRequest } from './prompt.ts';
7
+ import { type ExampleRequest } from './logger.ts';
6
8
  import { type Operation } from './spec.ts';
9
+ import type { World } from './synth.ts';
7
10
  import type { Checks } from './validate.ts';
8
- export declare const GYM_KIND = "gym-generator";
9
- /** What is kept about a generator besides the code, for `zen gym cache ls`. */
10
- export interface GeneratorMeta {
11
+ export declare const GYM_KIND = "gym-handler";
12
+ /** What is kept about a handler besides the code, for `zen gym cache ls`. */
13
+ export interface HandlerMeta {
11
14
  operationId?: string;
12
15
  method?: string;
13
16
  path?: string;
@@ -17,7 +20,7 @@ export interface GeneratorMeta {
17
20
  rulesVersion?: number;
18
21
  createdAt?: string;
19
22
  }
20
- export interface Generator {
23
+ export interface Handler {
21
24
  key: string;
22
25
  source: string;
23
26
  /** whether it came out of the cache rather than out of a model */
@@ -27,16 +30,23 @@ export interface CacheEvent {
27
30
  operation: Operation;
28
31
  attempt?: number;
29
32
  diagnostics?: readonly string[];
30
- /** where the rejected generator was written down, when it was */
33
+ /** where the rejected handler was written down, when it was */
31
34
  dump?: string;
32
35
  }
33
36
  export interface CacheOptions {
34
37
  box: Box;
35
38
  checks: Checks;
36
39
  model: Model;
40
+ catalog: Catalog;
41
+ world: World;
42
+ bindings: Bindings;
43
+ /** every operation being served, so a handler can be written knowing its siblings */
44
+ operations: readonly Operation[];
45
+ /** where HTML reports of agent runs go. Default `<box root>/reports` */
46
+ reportDir?: string;
37
47
  attempts?: number;
38
48
  /**
39
- * How many generators may be written at once. A client that walks every
49
+ * How many handlers may be written at once. A client that walks every
40
50
  * route — which is exactly what people do to a new mock — would otherwise
41
51
  * open one model request per operation simultaneously and be rate limited
42
52
  * on all of them.
@@ -44,11 +54,11 @@ export interface CacheOptions {
44
54
  concurrency?: number;
45
55
  /** ignore what is cached and write fresh */
46
56
  rebuild?: boolean;
47
- /** run generators but keep nothing */
57
+ /** run handlers but keep nothing */
48
58
  ephemeral?: boolean;
49
59
  /** keep them somewhere other than the shared store */
50
60
  cacheDir?: string;
51
- /** where rejected generators are written down. Default `<box root>/builds` */
61
+ /** where rejected handlers are written down. Default `<box root>/builds` */
52
62
  dumpDir?: string;
53
63
  onStart?: (e: CacheEvent) => void;
54
64
  onAttempt?: (e: CacheEvent) => void;
@@ -62,7 +72,7 @@ export interface CacheOptions {
62
72
  }
63
73
  export interface CacheRegenerateContext {
64
74
  currentSource: string;
65
- failingInput: GeneratorInput;
75
+ failingInput: HandlerRequest;
66
76
  fault: string;
67
77
  stderr?: string;
68
78
  examples?: readonly ExampleRequest[];
@@ -71,17 +81,17 @@ export declare class Cache {
71
81
  #private;
72
82
  constructor(opts: CacheOptions);
73
83
  /**
74
- * The generator for an operation, written if it does not exist yet. Callers
84
+ * The handler for an operation, written if it does not exist yet. Callers
75
85
  * that arrive during a build wait for that build rather than starting one.
76
86
  *
77
87
  * `cached` answers "did this call cost a model turn", which is what the
78
88
  * response header is for — so it is false for the caller that triggered the
79
89
  * build and for everyone who waited on it, and true from then on.
80
90
  */
81
- ensure(operation: Operation): Promise<Generator>;
91
+ ensure(operation: Operation): Promise<Handler>;
82
92
  get model(): Model;
83
93
  getSource(key: string): Promise<string | undefined>;
84
- regenerate(operation: Operation, ctx: CacheRegenerateContext): Promise<Generator>;
94
+ regenerate(operation: Operation, ctx: CacheRegenerateContext): Promise<Handler>;
85
95
  }
86
96
  export { BuildFailed };
87
97
  //# sourceMappingURL=cache.d.ts.map