@zenera/gym 1.1.40 → 1.1.42

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/worker.js ADDED
@@ -0,0 +1,99 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { pathToFileURL } from 'node:url';
10
+ import { parentPort } from 'node:worker_threads';
11
+ import { createContext, HttpError, } from "./runtime.js";
12
+ import { WorldStore } from "./store.js";
13
+ const port = parentPort;
14
+ if (!port) {
15
+ throw new Error('worker.ts must be started as a worker thread');
16
+ }
17
+ const stores = new Map();
18
+ const pending = new Map();
19
+ let calls = 0;
20
+ const storeFor = (path) => {
21
+ let store = stores.get(path);
22
+ if (!store) {
23
+ store = new WorldStore(path);
24
+ stores.set(path, store);
25
+ }
26
+ return store;
27
+ };
28
+ port.on('message', (message) => {
29
+ if (message.type === 'close') {
30
+ stores.get(message.db)?.close();
31
+ stores.delete(message.db);
32
+ return;
33
+ }
34
+ if (message.type === 'synthesized') {
35
+ const key = `${message.id}:${message.call}`;
36
+ const waiter = pending.get(key);
37
+ pending.delete(key);
38
+ if (message.error !== undefined) {
39
+ waiter?.reject(new Error(message.error));
40
+ }
41
+ else {
42
+ waiter?.resolve(message.records ?? []);
43
+ }
44
+ return;
45
+ }
46
+ void run(message);
47
+ });
48
+ async function run(message) {
49
+ const { id } = message;
50
+ const used = new Map();
51
+ const record = (use) => {
52
+ used.set(`${use.collection}\u0000${use.type ?? ''}\u0000${use.idField ?? ''}`, use);
53
+ };
54
+ const reply = (m) => port.postMessage({ ...m, used: [...used.values()] });
55
+ try {
56
+ const mod = (await import(__rewriteRelativeImportExtension(`${pathToFileURL(message.file).href}?v=${message.version}`)));
57
+ const handle = (mod.default ?? mod.handle);
58
+ if (typeof handle !== 'function') {
59
+ reply({
60
+ type: 'done',
61
+ id,
62
+ ok: false,
63
+ fault: 'exports no handler: write `export default async function handle(req, ctx)`',
64
+ });
65
+ return;
66
+ }
67
+ const store = storeFor(message.db);
68
+ const ctx = createContext(message.input, store, (request) => {
69
+ const call = ++calls;
70
+ return new Promise((resolve, reject) => {
71
+ pending.set(`${id}:${call}`, { resolve, reject });
72
+ port.postMessage({
73
+ type: 'synthesize',
74
+ id,
75
+ call,
76
+ db: message.db,
77
+ request,
78
+ });
79
+ });
80
+ }, message.bindings, record);
81
+ const value = await handle(structuredClone(message.input), ctx);
82
+ reply({ type: 'done', id, ok: true, value: value === undefined ? null : value });
83
+ }
84
+ catch (err) {
85
+ if (err instanceof HttpError) {
86
+ reply({ type: 'done', id, ok: true, value: err.body, status: err.status });
87
+ return;
88
+ }
89
+ const e = err instanceof Error ? err : new Error(String(err));
90
+ reply({
91
+ type: 'done',
92
+ id,
93
+ ok: false,
94
+ fault: `threw ${e.name}: ${e.message.split('\n')[0]}`,
95
+ stderr: e.stack,
96
+ });
97
+ }
98
+ }
99
+ //# sourceMappingURL=worker.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenera/gym",
3
- "version": "1.1.40",
3
+ "version": "1.1.42",
4
4
  "description": "Simulated API environments for agent training and evaluation, powered by OpenAPI and model-written generators.",
5
5
  "keywords": [
6
6
  "openapi",
@@ -46,9 +46,11 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@apidevtools/swagger-parser": "^12.0.0",
49
+ "@zenera/cli": "^1.1.42",
50
+ "@zenera/neo": "^1.1.42",
51
+ "@zenera/rag": "^1.1.42",
49
52
  "ajv": "^8.17.1",
50
53
  "ajv-formats": "^3.0.1",
51
- "@zenera/cli": "^1.1.40",
52
- "@zenera/neo": "^1.1.40"
54
+ "randexp": "^0.5.3"
53
55
  }
54
56
  }
@@ -1,13 +0,0 @@
1
- export interface GeneratorInput {
2
- operationId: string;
3
- method: string;
4
- /** the template, not the resolved path — `/users/{user_id}` */
5
- path: string;
6
- pathParams: Record<string, unknown>;
7
- query: Record<string, unknown>;
8
- headers: Record<string, string>;
9
- body: unknown;
10
- /** seeds `random` and `Faker` inside the generator */
11
- seed: number;
12
- }
13
- //# sourceMappingURL=envelope.d.ts.map
package/dist/envelope.js DELETED
@@ -1,9 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // What a generator is handed
3
- //
4
- // One shape, written by the server for a real request and by the build loop for
5
- // a synthetic probe. That they are the same shape is the whole reason a
6
- // generator that satisfies its probes also satisfies traffic.
7
- // ---------------------------------------------------------------------------
8
- export {};
9
- //# sourceMappingURL=envelope.js.map
package/dist/image.d.ts DELETED
@@ -1,25 +0,0 @@
1
- import { type Runner } from '@zenera/neo';
2
- /**
3
- * Lower bounds rather than exact pins: the tag is a function of this list, so
4
- * an edit here yields a new image and a new container either way, and pinning a
5
- * patch release would only mean a rebuild every time one is published.
6
- */
7
- export declare const REQUIREMENTS: readonly ["Faker>=37", "exrex>=0.11", "jsonschema>=4.23", "python-dateutil>=2.9"];
8
- /** What the generator may import. Quoted verbatim in the prompt. */
9
- export declare const AVAILABLE: readonly ["faker", "exrex", "jsonschema", "dateutil"];
10
- export declare const BASE_IMAGE = "docker.io/library/python:3.14-slim-bookworm";
11
- export declare function imageTag(base?: string): string;
12
- export interface ImageOptions {
13
- /** where the empty build context is created */
14
- root: string;
15
- base?: string;
16
- engine?: string;
17
- exec?: Runner;
18
- onBuild?: (tag: string) => void;
19
- }
20
- /**
21
- * The tag to run, built if it is not there yet. Returns without touching podman
22
- * when the image already exists, which is every start after the first.
23
- */
24
- export declare function ensureImage(opts: ImageOptions): Promise<string>;
25
- //# sourceMappingURL=image.d.ts.map
package/dist/image.js DELETED
@@ -1,81 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { mkdirSync, writeFileSync } from 'node:fs';
3
- import { join } from 'node:path';
4
- import { DEFAULT_SANDBOX_IMAGE, runProcess } from '@zenera/neo';
5
- // ---------------------------------------------------------------------------
6
- // The image
7
- //
8
- // The generators are Python, and Python that has to invent a plausible name out
9
- // of `random.choice` writes output that looks like test data because it is. So
10
- // the container gets real libraries.
11
- //
12
- // They cannot be installed into the *running* box. `containerName()` in
13
- // @zenera/neo hashes the image and the network, so "start online, install, then
14
- // switch to `network: none`" resolves to two different containers and the
15
- // install goes with the first one. Baking them into an image instead is one
16
- // build, cached by podman's own layers, and leaves the serving container
17
- // offline — which is the property that matters, because the code it runs was
18
- // written by a model.
19
- // ---------------------------------------------------------------------------
20
- /**
21
- * Lower bounds rather than exact pins: the tag is a function of this list, so
22
- * an edit here yields a new image and a new container either way, and pinning a
23
- * patch release would only mean a rebuild every time one is published.
24
- */
25
- export const REQUIREMENTS = [
26
- 'Faker>=37',
27
- 'exrex>=0.11',
28
- 'jsonschema>=4.23',
29
- 'python-dateutil>=2.9',
30
- ];
31
- /** What the generator may import. Quoted verbatim in the prompt. */
32
- export const AVAILABLE = ['faker', 'exrex', 'jsonschema', 'dateutil'];
33
- export const BASE_IMAGE = DEFAULT_SANDBOX_IMAGE;
34
- export function imageTag(base = BASE_IMAGE) {
35
- const digest = createHash('sha256')
36
- .update(JSON.stringify([base, [...REQUIREMENTS].sort()]))
37
- .digest('hex')
38
- .slice(0, 12);
39
- return `localhost/zenera-gym:${digest}`;
40
- }
41
- function containerfile(base) {
42
- return [
43
- `FROM ${base}`,
44
- `RUN pip install --no-cache-dir --disable-pip-version-check ${REQUIREMENTS.join(' ')}`,
45
- '',
46
- ].join('\n');
47
- }
48
- /**
49
- * The tag to run, built if it is not there yet. Returns without touching podman
50
- * when the image already exists, which is every start after the first.
51
- */
52
- export async function ensureImage(opts) {
53
- const base = opts.base ?? BASE_IMAGE;
54
- const tag = imageTag(base);
55
- const engine = opts.engine ?? 'podman';
56
- const run = opts.exec ?? runProcess;
57
- const exists = await run(engine, ['image', 'exists', tag], { timeoutMs: 60_000 });
58
- if (exists.code === 0) {
59
- return tag;
60
- }
61
- opts.onBuild?.(tag);
62
- // An empty directory, because there is nothing to COPY: handing podman the
63
- // working directory instead would tar up whatever happened to be in it.
64
- const context = join(opts.root, 'build');
65
- mkdirSync(context, { recursive: true });
66
- // A real file rather than `--file -`: a spawned child's stdin is a
67
- // socketpair, and a socket cannot be reopened through /dev/stdin, so the
68
- // engine fails with ENXIO. It sits beside the context, not inside it, so
69
- // the context stays empty.
70
- const file = join(opts.root, 'Containerfile');
71
- writeFileSync(file, containerfile(base));
72
- const built = await run(engine, ['build', '--tag', tag, '--file', file, context], {
73
- timeoutMs: 900_000,
74
- maxBytes: 256 * 1024,
75
- });
76
- if (built.code !== 0) {
77
- throw new Error(`could not build ${tag}: ${(built.stderr.trim() || built.stdout.trim()).split('\n').slice(-3).join(' ')}`);
78
- }
79
- return tag;
80
- }
81
- //# sourceMappingURL=image.js.map