@zenera/gym 1.1.40 → 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.
- package/README.md +169 -104
- package/dist/agents.d.ts +25 -0
- package/dist/agents.js +52 -0
- package/dist/bindings.d.ts +25 -0
- package/dist/bindings.js +81 -0
- package/dist/box.d.ts +39 -36
- package/dist/box.js +183 -217
- package/dist/cache.d.ts +24 -14
- package/dist/cache.js +102 -66
- package/dist/catalog.d.ts +83 -0
- package/dist/catalog.js +293 -0
- package/dist/command.d.ts +1 -0
- package/dist/command.js +235 -68
- package/dist/fake.d.ts +4 -0
- package/dist/fake.js +142 -0
- package/dist/generate.d.ts +28 -24
- package/dist/generate.js +417 -120
- package/dist/inputs.d.ts +12 -0
- package/dist/inputs.js +132 -0
- package/dist/knowledge.d.ts +50 -0
- package/dist/knowledge.js +199 -0
- package/dist/logger.d.ts +10 -8
- package/dist/logger.js +4 -3
- package/dist/paging.d.ts +2 -2
- package/dist/paging.js +2 -2
- package/dist/probe.d.ts +5 -5
- package/dist/probe.js +15 -15
- package/dist/prompt.d.ts +31 -18
- package/dist/prompt.js +160 -146
- package/dist/quiet.d.ts +2 -0
- package/dist/quiet.js +7 -0
- package/dist/router.js +5 -1
- package/dist/runtime.d.ts +160 -0
- package/dist/runtime.js +229 -0
- package/dist/schema.js +1 -2
- package/dist/server.js +98 -57
- package/dist/setup.d.ts +47 -6
- package/dist/setup.js +157 -21
- package/dist/spec.d.ts +4 -3
- package/dist/spec.js +73 -22
- package/dist/store.d.ts +78 -0
- package/dist/store.js +253 -0
- package/dist/synth.d.ts +42 -0
- package/dist/synth.js +589 -0
- package/dist/worker.d.ts +41 -0
- package/dist/worker.js +99 -0
- package/package.json +5 -3
- package/dist/envelope.d.ts +0 -13
- package/dist/envelope.js +0 -9
- package/dist/image.d.ts +0 -25
- package/dist/image.js +0 -81
package/dist/box.js
CHANGED
|
@@ -1,259 +1,225 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
|
7
|
+
// Where handlers run
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
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
|
-
//
|
|
16
|
-
// is
|
|
17
|
-
//
|
|
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
|
|
20
|
-
const
|
|
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
|
|
30
|
-
*
|
|
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
|
|
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 = '
|
|
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
|
-
|
|
37
|
+
db;
|
|
38
|
+
synthesize;
|
|
39
|
+
#bindings;
|
|
48
40
|
#timeout;
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
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
|
|
55
|
-
this.#
|
|
56
|
-
this
|
|
57
|
-
this.#
|
|
58
|
-
mkdirSync(join(opts.root,
|
|
59
|
-
|
|
60
|
-
|
|
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,
|
|
57
|
+
return join(this.root, HANDLERS, key, ENTRY);
|
|
113
58
|
}
|
|
114
59
|
async write(key, source) {
|
|
115
|
-
mkdirSync(join(this.root,
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
95
|
+
const worker = this.#worker;
|
|
96
|
+
this.#worker = undefined;
|
|
97
|
+
this.#failAll('the server is shutting down');
|
|
98
|
+
await worker?.terminate();
|
|
200
99
|
}
|
|
201
|
-
|
|
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
|
-
|
|
108
|
+
worker = new Worker(entry, { stdout: true, stderr: true, env: {} });
|
|
204
109
|
}
|
|
205
110
|
catch (err) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
:
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
245
|
-
|
|
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
|
-
|
|
248
|
-
|
|
199
|
+
#lost(worker, why, stderr) {
|
|
200
|
+
if (this.#worker === worker) {
|
|
201
|
+
this.#worker = undefined;
|
|
202
|
+
this.#failAll(why, stderr);
|
|
203
|
+
}
|
|
249
204
|
}
|
|
250
|
-
|
|
251
|
-
|
|
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 {
|
|
4
|
+
import type { HandlerRequest } from './runtime.ts';
|
|
5
|
+
import type { Catalog } from './catalog.ts';
|
|
4
6
|
import { BuildFailed } from './generate.ts';
|
|
5
|
-
import type
|
|
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-
|
|
9
|
-
/** What is kept about a
|
|
10
|
-
export interface
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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<
|
|
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<
|
|
94
|
+
regenerate(operation: Operation, ctx: CacheRegenerateContext): Promise<Handler>;
|
|
85
95
|
}
|
|
86
96
|
export { BuildFailed };
|
|
87
97
|
//# sourceMappingURL=cache.d.ts.map
|