@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/prompt.d.ts
CHANGED
|
@@ -1,25 +1,38 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Catalog } from './catalog.ts';
|
|
2
|
+
import type { HandlerRequest } from './runtime.ts';
|
|
2
3
|
import { type Operation } from './spec.ts';
|
|
3
|
-
export declare const SYSTEM:
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
4
|
+
export declare const SYSTEM = "You write the request handler for one operation of a simulated HTTP API.\n\nTHE HANDLER is one TypeScript module, run by Node.js as it is:\n\n export default async function handle(req, ctx) { ... return body; }\n\n- `req`: { operationId, method, path, pathParams, query, headers, body, seed }.\n pathParams and query values are strings, as on the wire.\n- Return the response body. It must match the response type exactly.\n- No imports of any kind. Only erasable TypeScript: type annotations, interfaces and\n `type` aliases are fine; enums, namespaces and parameter properties are not.\n- No network, no files, no timers, no global state between calls.\n\nTHE WORLD. All content lives in a shared world of records, grouped by type. Several\nAPIs share it, so a message listed here is the message read there. Your handler owns\nthe mechanics \u2014 which type, which filter, what a write changes \u2014 and never invents\ncontent itself. Name records by their schema TYPE (a name with a capital: \"Message\",\n\"Tier0\"); the world knows which collection and id field each type has. When the world\nlacks what a request needs, ask for it and an agent writes it, consistent with\neverything else:\n\n ctx.db.collection(Type) sync: all(), get(id), has(id), find(fn), count(),\n put(id, record), putMany([{id, value}]), delete(id)\n await ctx.lookup(Type, id, { where?, hint?, idField? })\n the record with that id, made if missing\n await ctx.ensure(Type, { min?, count?, where?, hint? })\n all records (matching where), making some if fewer than min\n await ctx.synthesize({ collection: Type, count?, ids?, where?, hint? })\n make new records now; returns them, already stored\n await ctx.owner() the account the API is called as: { id, email, displayName, timeZone? }\n ctx.page(items, { token?, offset?, size?, defaultSize? })\n -> { items, nextToken, nextOffset, hasMore, total }\n ctx.fail(status, body?) answer with an HTTP error instead\n ctx.newId(pattern?) ctx.fromPattern(regex) ctx.now() ctx.random() ctx.int(a, b) ctx.pick(list)\n\n`idField` is for the first use of a type whose id property is not `id` (e.g. \"user_id\");\nafter that the binding remembers it. `where` pins values every made record must carry\n(e.g. { calendarId }); `hint` says in a sentence what the request is after (a search\nquery, a filter), so the made records actually answer it.\n\nHOW EACH KIND OF OPERATION WORKS\n- Read one (GET /things/{id}): `await ctx.lookup('Thing', req.pathParams.id)`.\n- List (GET /things): `await ctx.ensure('Thing', { where, hint })`, then filter by the\n query parameters that describe content (search text, dates, labels, owner\u2026) in code,\n sort as the API would, then `ctx.page`. If filtering leaves nothing for a query that\n plainly expects results, `await ctx.synthesize({ collection: 'Thing', hint: <the query> })` and filter again.\n- Create (POST): build the stored record from the request body plus the server-assigned\n fields (id via ctx.newId(<the id pattern>), timestamps via ctx.now(), status\u2026),\n `put` it, return it. Later reads must see it.\n- Update (PUT/PATCH): lookup, merge, put, return. Delete: delete, return what the schema asks.\n- Actions and computed answers (free/busy, search, stats\u2026): derive the answer from the\n collections they are about, ensuring those exist first.\n- Store whole records \u2014 the richest type the API has for the thing (e.g. a full\n `Message`, not a `MessageRef`) \u2014 and project to the response shape. Use the type\n sibling operations use: read their handlers first.\n- People come from the world, never from code. Ids that stand for the caller \u2014 \"me\",\n \"primary\", \"self\", \"default\" \u2014 mean `await ctx.owner()`: resolve them to the owner's\n email (or whatever id the other records use for that person) before you store or\n filter by them, so \"primary\" and the owner's address are one calendar. Fields like\n sender, organizer, author or owner on a record you create are the owner's.\n- Where a path parameter has the same name as a property of the response, the response\n carries the request's value.\n- Paging: always through ctx.page, passing the client's token or offset as given.\n The token is opaque and always advances; on the last page leave the next token out\n (or null, if the schema allows) and set any has-more flag false.\n\nREFERENCE DOCUMENTS. When the brief quotes documentation, or search_docs is among your\ntools, the documents describe behaviour the schema cannot: what a write changes\nelsewhere, what is refused and with which status, defaults, state transitions.\nImplement what they say. Content still comes from the world, never from the documents.\n\nWORKFLOW\n1. Look before writing: read_handler for the related operations already built,\n list_collections for what the world holds, describe_types for types you were not\n shown. In a large API, trace_api on the record type finds every operation that\n carries it, and grep_api / list_api find the rest. Read the reference documents\n when the operation's behaviour is not obvious from its schema.\n2. test_handler with the whole module. It runs the handler against probe requests in a\n copy of the world and reports every problem. Fix and test again until it passes.\n A passing test is the end of the job.";
|
|
5
|
+
export interface BriefOptions {
|
|
6
|
+
catalog: Catalog;
|
|
7
|
+
/** operations this one must agree with, closest first, and whether each has a handler */
|
|
8
|
+
siblings: readonly {
|
|
9
|
+
operation: Operation;
|
|
10
|
+
built: boolean;
|
|
11
|
+
type?: string;
|
|
12
|
+
}[];
|
|
13
|
+
collections: readonly {
|
|
14
|
+
name: string;
|
|
15
|
+
count: number;
|
|
16
|
+
}[];
|
|
17
|
+
/** where each type lives, fixed by the handlers before this one */
|
|
18
|
+
bindings: Readonly<Record<string, {
|
|
19
|
+
collection: string;
|
|
20
|
+
idField: string;
|
|
21
|
+
}>>;
|
|
22
|
+
/** reference passages about this operation, quoted; '' when there are none */
|
|
23
|
+
passages?: string;
|
|
15
24
|
}
|
|
16
|
-
export
|
|
17
|
-
|
|
25
|
+
export declare function brief(operation: Operation, opts: BriefOptions): string;
|
|
26
|
+
export interface Repair {
|
|
18
27
|
currentSource: string;
|
|
19
|
-
failingInput:
|
|
28
|
+
failingInput: HandlerRequest;
|
|
20
29
|
fault: string;
|
|
21
30
|
stderr?: string;
|
|
22
|
-
examples?: readonly
|
|
31
|
+
examples?: readonly {
|
|
32
|
+
input: HandlerRequest;
|
|
33
|
+
output?: unknown;
|
|
34
|
+
}[];
|
|
23
35
|
}
|
|
24
|
-
export declare function
|
|
36
|
+
export declare function repairBrief(repair: Repair): string;
|
|
37
|
+
export declare function retryNudge(diagnostics: readonly string[]): string;
|
|
25
38
|
//# sourceMappingURL=prompt.d.ts.map
|
package/dist/prompt.js
CHANGED
|
@@ -1,61 +1,97 @@
|
|
|
1
|
-
import { AVAILABLE } from "./image.js";
|
|
2
1
|
import { called } from "./spec.js";
|
|
3
2
|
// ---------------------------------------------------------------------------
|
|
4
|
-
// What the
|
|
3
|
+
// What the handler agent is told
|
|
5
4
|
//
|
|
6
|
-
// A contract, not a request. The
|
|
7
|
-
//
|
|
8
|
-
// the
|
|
9
|
-
// as suggestions, and everything discretionary is stated as taste.
|
|
5
|
+
// A contract, not a request. The handler is a module with a fixed shape that
|
|
6
|
+
// this package runs in three places — the judge, the server, the repair loop —
|
|
7
|
+
// so the shape is stated as rules; everything discretionary is taste.
|
|
10
8
|
//
|
|
11
|
-
// The
|
|
12
|
-
//
|
|
13
|
-
//
|
|
9
|
+
// The rule that earns its paragraph is the split between mechanics and
|
|
10
|
+
// content. A handler that writes `subject: 'Hello'` inline answers every story
|
|
11
|
+
// the same way; one that asks `ctx.lookup` for the message answers with the
|
|
12
|
+
// story's. So content is never written here, only fetched.
|
|
14
13
|
// ---------------------------------------------------------------------------
|
|
15
|
-
export const SYSTEM =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
14
|
+
export const SYSTEM = `You write the request handler for one operation of a simulated HTTP API.
|
|
15
|
+
|
|
16
|
+
THE HANDLER is one TypeScript module, run by Node.js as it is:
|
|
17
|
+
|
|
18
|
+
export default async function handle(req, ctx) { ... return body; }
|
|
19
|
+
|
|
20
|
+
- \`req\`: { operationId, method, path, pathParams, query, headers, body, seed }.
|
|
21
|
+
pathParams and query values are strings, as on the wire.
|
|
22
|
+
- Return the response body. It must match the response type exactly.
|
|
23
|
+
- No imports of any kind. Only erasable TypeScript: type annotations, interfaces and
|
|
24
|
+
\`type\` aliases are fine; enums, namespaces and parameter properties are not.
|
|
25
|
+
- No network, no files, no timers, no global state between calls.
|
|
26
|
+
|
|
27
|
+
THE WORLD. All content lives in a shared world of records, grouped by type. Several
|
|
28
|
+
APIs share it, so a message listed here is the message read there. Your handler owns
|
|
29
|
+
the mechanics — which type, which filter, what a write changes — and never invents
|
|
30
|
+
content itself. Name records by their schema TYPE (a name with a capital: "Message",
|
|
31
|
+
"Tier0"); the world knows which collection and id field each type has. When the world
|
|
32
|
+
lacks what a request needs, ask for it and an agent writes it, consistent with
|
|
33
|
+
everything else:
|
|
34
|
+
|
|
35
|
+
ctx.db.collection(Type) sync: all(), get(id), has(id), find(fn), count(),
|
|
36
|
+
put(id, record), putMany([{id, value}]), delete(id)
|
|
37
|
+
await ctx.lookup(Type, id, { where?, hint?, idField? })
|
|
38
|
+
the record with that id, made if missing
|
|
39
|
+
await ctx.ensure(Type, { min?, count?, where?, hint? })
|
|
40
|
+
all records (matching where), making some if fewer than min
|
|
41
|
+
await ctx.synthesize({ collection: Type, count?, ids?, where?, hint? })
|
|
42
|
+
make new records now; returns them, already stored
|
|
43
|
+
await ctx.owner() the account the API is called as: { id, email, displayName, timeZone? }
|
|
44
|
+
ctx.page(items, { token?, offset?, size?, defaultSize? })
|
|
45
|
+
-> { items, nextToken, nextOffset, hasMore, total }
|
|
46
|
+
ctx.fail(status, body?) answer with an HTTP error instead
|
|
47
|
+
ctx.newId(pattern?) ctx.fromPattern(regex) ctx.now() ctx.random() ctx.int(a, b) ctx.pick(list)
|
|
48
|
+
|
|
49
|
+
\`idField\` is for the first use of a type whose id property is not \`id\` (e.g. "user_id");
|
|
50
|
+
after that the binding remembers it. \`where\` pins values every made record must carry
|
|
51
|
+
(e.g. { calendarId }); \`hint\` says in a sentence what the request is after (a search
|
|
52
|
+
query, a filter), so the made records actually answer it.
|
|
53
|
+
|
|
54
|
+
HOW EACH KIND OF OPERATION WORKS
|
|
55
|
+
- Read one (GET /things/{id}): \`await ctx.lookup('Thing', req.pathParams.id)\`.
|
|
56
|
+
- List (GET /things): \`await ctx.ensure('Thing', { where, hint })\`, then filter by the
|
|
57
|
+
query parameters that describe content (search text, dates, labels, owner…) in code,
|
|
58
|
+
sort as the API would, then \`ctx.page\`. If filtering leaves nothing for a query that
|
|
59
|
+
plainly expects results, \`await ctx.synthesize({ collection: 'Thing', hint: <the query> })\` and filter again.
|
|
60
|
+
- Create (POST): build the stored record from the request body plus the server-assigned
|
|
61
|
+
fields (id via ctx.newId(<the id pattern>), timestamps via ctx.now(), status…),
|
|
62
|
+
\`put\` it, return it. Later reads must see it.
|
|
63
|
+
- Update (PUT/PATCH): lookup, merge, put, return. Delete: delete, return what the schema asks.
|
|
64
|
+
- Actions and computed answers (free/busy, search, stats…): derive the answer from the
|
|
65
|
+
collections they are about, ensuring those exist first.
|
|
66
|
+
- Store whole records — the richest type the API has for the thing (e.g. a full
|
|
67
|
+
\`Message\`, not a \`MessageRef\`) — and project to the response shape. Use the type
|
|
68
|
+
sibling operations use: read their handlers first.
|
|
69
|
+
- People come from the world, never from code. Ids that stand for the caller — "me",
|
|
70
|
+
"primary", "self", "default" — mean \`await ctx.owner()\`: resolve them to the owner's
|
|
71
|
+
email (or whatever id the other records use for that person) before you store or
|
|
72
|
+
filter by them, so "primary" and the owner's address are one calendar. Fields like
|
|
73
|
+
sender, organizer, author or owner on a record you create are the owner's.
|
|
74
|
+
- Where a path parameter has the same name as a property of the response, the response
|
|
75
|
+
carries the request's value.
|
|
76
|
+
- Paging: always through ctx.page, passing the client's token or offset as given.
|
|
77
|
+
The token is opaque and always advances; on the last page leave the next token out
|
|
78
|
+
(or null, if the schema allows) and set any has-more flag false.
|
|
79
|
+
|
|
80
|
+
REFERENCE DOCUMENTS. When the brief quotes documentation, or search_docs is among your
|
|
81
|
+
tools, the documents describe behaviour the schema cannot: what a write changes
|
|
82
|
+
elsewhere, what is refused and with which status, defaults, state transitions.
|
|
83
|
+
Implement what they say. Content still comes from the world, never from the documents.
|
|
84
|
+
|
|
85
|
+
WORKFLOW
|
|
86
|
+
1. Look before writing: read_handler for the related operations already built,
|
|
87
|
+
list_collections for what the world holds, describe_types for types you were not
|
|
88
|
+
shown. In a large API, trace_api on the record type finds every operation that
|
|
89
|
+
carries it, and grep_api / list_api find the rest. Read the reference documents
|
|
90
|
+
when the operation's behaviour is not obvious from its schema.
|
|
91
|
+
2. test_handler with the whole module. It runs the handler against probe requests in a
|
|
92
|
+
copy of the world and reports every problem. Fix and test again until it passes.
|
|
93
|
+
A passing test is the end of the job.`;
|
|
94
|
+
export function brief(operation, opts) {
|
|
59
95
|
const lines = [
|
|
60
96
|
`OPERATION ${operation.method.toUpperCase()} ${called(operation)}`,
|
|
61
97
|
`operationId: ${operation.operationId}`,
|
|
@@ -64,125 +100,103 @@ export function brief(operation) {
|
|
|
64
100
|
lines.push(`summary: ${operation.summary}`);
|
|
65
101
|
}
|
|
66
102
|
if (operation.description && operation.description !== operation.summary) {
|
|
67
|
-
lines.push(`description: ${operation.description.slice(0,
|
|
103
|
+
lines.push(`description: ${operation.description.slice(0, 800)}`);
|
|
68
104
|
}
|
|
69
|
-
lines.push('',
|
|
70
|
-
|
|
71
|
-
|
|
105
|
+
lines.push('', opts.catalog.describe(operation) || fallback(operation));
|
|
106
|
+
const record = opts.catalog.itemType(operation);
|
|
107
|
+
if (record) {
|
|
108
|
+
lines.push('', `This operation is about \`${record}\` records.`);
|
|
72
109
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
lines.push(` ${p.name} (in ${p.in}${p.required ? ', required' : ''}): ${JSON.stringify(p.schema)}${note}`);
|
|
76
|
-
}
|
|
77
|
-
if (operation.requestBody) {
|
|
78
|
-
lines.push('', 'REQUEST BODY SCHEMA (arrives as `body`)', json(operation.requestBody.schema));
|
|
110
|
+
if (!operation.success.schema) {
|
|
111
|
+
lines.push('', 'This operation answers with no body: return null.');
|
|
79
112
|
}
|
|
80
113
|
if (operation.paging) {
|
|
81
114
|
lines.push('', ...pagination(operation.paging));
|
|
82
115
|
}
|
|
83
|
-
|
|
84
|
-
|
|
116
|
+
if (opts.siblings.length > 0) {
|
|
117
|
+
lines.push('', 'RELATED OPERATIONS (read the built ones with read_handler and reuse their collections)', ...opts.siblings.map(({ operation: o, built, type }) => ` ${built ? '[built]' : '[ ]'} ${o.method.toUpperCase()} ${called(o)} ${o.operationId}` +
|
|
118
|
+
(type ? ` → ${type}` : '')));
|
|
119
|
+
}
|
|
120
|
+
const bound = Object.entries(opts.bindings).filter(([type]) => !type.startsWith('@'));
|
|
121
|
+
lines.push('', 'WHERE TYPES LIVE (fixed: name the type and the binding finds its collection)', ...(bound.length > 0
|
|
122
|
+
? bound.map(([type, b]) => ` ${type} → ${b.collection} (keyed by ${b.idField})`)
|
|
123
|
+
: [' (nothing yet — the first handler to use a type gives it its place)']));
|
|
124
|
+
if (opts.passages) {
|
|
125
|
+
lines.push('', 'FROM THE REFERENCE DOCUMENTS (what the API does, beyond its schema)', opts.passages.trimEnd());
|
|
126
|
+
}
|
|
127
|
+
lines.push('', 'THE WORLD SO FAR', ...(opts.collections.length > 0
|
|
128
|
+
? opts.collections.map((c) => ` ${c.name}: ${c.count} records`)
|
|
129
|
+
: [' (empty — collections are created by the first handler that asks for them)']));
|
|
85
130
|
return lines.join('\n');
|
|
86
131
|
}
|
|
132
|
+
/** For an operation the catalog could not match: its JSON, cut to a sane length. */
|
|
133
|
+
function fallback(operation) {
|
|
134
|
+
const out = ['REQUEST'];
|
|
135
|
+
for (const p of operation.params) {
|
|
136
|
+
out.push(` ${p.in} ${p.name}${p.required ? '' : '?'}: ${JSON.stringify(p.schema)}`);
|
|
137
|
+
}
|
|
138
|
+
if (operation.requestBody) {
|
|
139
|
+
out.push(` body: ${JSON.stringify(operation.requestBody.schema).slice(0, 4000)}`);
|
|
140
|
+
}
|
|
141
|
+
out.push('', `RESPONSE ${operation.success.status}: ${JSON.stringify(operation.success.schema ?? null).slice(0, 8000)}`);
|
|
142
|
+
return out.join('\n');
|
|
143
|
+
}
|
|
87
144
|
/**
|
|
88
|
-
* Said only to the operations that page,
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* and hangs every client that walks the list.
|
|
145
|
+
* Said only to the operations that page, in their own names. The rule that
|
|
146
|
+
* matters is that the token advances: a body offering back the token it was
|
|
147
|
+
* given passes the schema and hangs every client that walks the list.
|
|
92
148
|
*/
|
|
93
149
|
function pagination(paging) {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
` This operation is paged. ${where}\`${paging.param}\` asks for a page;`,
|
|
101
|
-
' absent or empty means the first one.',
|
|
102
|
-
' - Fabricate a random number of pages between 1 and 10 in total and no more.',
|
|
103
|
-
];
|
|
150
|
+
const where = paging.paramIn === 'body' ? 'req.body' : 'req.query';
|
|
151
|
+
const from = paging.style === 'cursor'
|
|
152
|
+
? `{ token: ${where}.${paging.param}`
|
|
153
|
+
: `{ offset: ${where}.${paging.param}`;
|
|
154
|
+
const size = paging.size ? `, size: ${where}.${paging.size}` : '';
|
|
155
|
+
const lines = ['PAGINATION', ` This operation pages: \`ctx.page(items, ${from}${size} })\`.`];
|
|
104
156
|
if (paging.next) {
|
|
105
|
-
|
|
157
|
+
const end = paging.nextNullable
|
|
158
|
+
? 'null'
|
|
159
|
+
: paging.nextRequired
|
|
160
|
+
? 'still present (the schema requires it), with the items empty past the end'
|
|
161
|
+
: 'left out';
|
|
162
|
+
lines.push(` \`${paging.next}\` carries ${paging.style === 'cursor' ? 'nextToken' : 'nextOffset'}; on the last page it is ${end}.`);
|
|
106
163
|
}
|
|
107
|
-
if (paging.more
|
|
108
|
-
lines.push(`
|
|
164
|
+
if (paging.more) {
|
|
165
|
+
lines.push(` \`${paging.more}\` is hasMore.`);
|
|
109
166
|
}
|
|
110
|
-
if (paging.
|
|
111
|
-
lines.push(
|
|
167
|
+
if (paging.items) {
|
|
168
|
+
lines.push(` \`${paging.items}\` holds the page's items.`);
|
|
112
169
|
}
|
|
113
|
-
lines.push(' - A token you cannot read, or one past the end, is the last page,', ` ended the same way and with ${paging.items ? `\`${paging.items}\` empty` : 'nothing listed'}.`, ' Never an error, and never the first page again.');
|
|
114
170
|
return lines;
|
|
115
171
|
}
|
|
116
|
-
|
|
117
|
-
* How the last page says so. A token that is required and cannot be null has
|
|
118
|
-
* nowhere to put the ending, so the ending has to be said some other way —
|
|
119
|
-
* telling the model to null it anyway would only ask for an invalid body.
|
|
120
|
-
*/
|
|
121
|
-
const stuck = (paging) => !paging.nextNullable && paging.nextRequired === true;
|
|
122
|
-
function last(paging) {
|
|
123
|
-
if (paging.nextNullable) {
|
|
124
|
-
return [` - On the last page set \`${paging.next}\` to null.`];
|
|
125
|
-
}
|
|
126
|
-
if (!stuck(paging)) {
|
|
127
|
-
return [` - On the last page leave \`${paging.next}\` out.`];
|
|
128
|
-
}
|
|
129
|
-
const otherwise = [paging.more && `\`${paging.more}\` false`, paging.items && 'nothing listed']
|
|
130
|
-
.filter(Boolean)
|
|
131
|
-
.join(' and ');
|
|
132
|
-
return [
|
|
133
|
-
` - The schema requires \`${paging.next}\` on every page, so the last page`,
|
|
134
|
-
` ends the list the other way: ${otherwise || 'an empty page'} (e.g. \`${paging.items || 'results'}\` as an empty list \`[]\`).`,
|
|
135
|
-
];
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Repeated in the file the model writes, so it is worth spelling out: the
|
|
139
|
-
* schema it validates against must be the one it was shown, embedded, not
|
|
140
|
-
* loaded from anywhere.
|
|
141
|
-
*/
|
|
142
|
-
export function instruction(operation) {
|
|
143
|
-
return [
|
|
144
|
-
brief(operation),
|
|
145
|
-
'',
|
|
146
|
-
'Embed the response schema in the file as a literal and validate against it.',
|
|
147
|
-
].join('\n');
|
|
148
|
-
}
|
|
149
|
-
export function retry(diagnostics) {
|
|
150
|
-
return [
|
|
151
|
-
'That file did not pass. Fix it and answer with the whole file again.',
|
|
152
|
-
'',
|
|
153
|
-
...diagnostics,
|
|
154
|
-
].join('\n');
|
|
155
|
-
}
|
|
156
|
-
export function regenerationInstruction(ctx) {
|
|
157
|
-
const { operation, currentSource, failingInput, fault, stderr, examples } = ctx;
|
|
172
|
+
export function repairBrief(repair) {
|
|
158
173
|
const lines = [
|
|
159
|
-
|
|
174
|
+
'THE CURRENT HANDLER FAILED ON A REAL REQUEST. Fix it; keep what works.',
|
|
160
175
|
'',
|
|
161
|
-
'
|
|
162
|
-
'',
|
|
163
|
-
|
|
164
|
-
'```python',
|
|
165
|
-
currentSource.trim(),
|
|
176
|
+
'CURRENT HANDLER:',
|
|
177
|
+
'```ts',
|
|
178
|
+
repair.currentSource.trim(),
|
|
166
179
|
'```',
|
|
167
180
|
'',
|
|
168
|
-
'
|
|
169
|
-
|
|
170
|
-
json(failingInput),
|
|
181
|
+
'FAILING REQUEST:',
|
|
182
|
+
json(repair.failingInput),
|
|
171
183
|
'',
|
|
172
|
-
`
|
|
184
|
+
`FAULT: ${repair.fault}`,
|
|
173
185
|
];
|
|
174
|
-
if (stderr
|
|
175
|
-
lines.push('', '
|
|
186
|
+
if (repair.stderr?.trim()) {
|
|
187
|
+
lines.push('', 'STACK:', '```', repair.stderr.trim().split('\n').slice(0, 15).join('\n'), '```');
|
|
176
188
|
}
|
|
177
|
-
|
|
178
|
-
lines.push('',
|
|
179
|
-
for (let i = 0; i < examples.length; i++) {
|
|
180
|
-
const ex = examples[i];
|
|
181
|
-
lines.push(`Example ${i + 1}:`, 'Input:', json(ex.input), 'Output produced:', json(ex.output), '');
|
|
182
|
-
}
|
|
189
|
+
for (const [i, ex] of (repair.examples ?? []).entries()) {
|
|
190
|
+
lines.push('', `EARLIER SUCCESSFUL REQUEST ${i + 1}:`, json(ex.input), 'answered:', json(ex.output).slice(0, 3000));
|
|
183
191
|
}
|
|
184
|
-
lines.push('TASK:', 'Fix the generator code so that it handles the failing request without error, produces valid output that matches the schema, and continues to produce valid output for the previous example requests.', 'ANSWER WITH THE COMPLETE REVISED PYTHON FILE AND NOTHING ELSE. No explanation, no markdown fence.');
|
|
185
192
|
return lines.join('\n');
|
|
186
193
|
}
|
|
194
|
+
export function retryNudge(diagnostics) {
|
|
195
|
+
return [
|
|
196
|
+
'The handler has not passed test_handler yet. Fix these and test again:',
|
|
197
|
+
'',
|
|
198
|
+
...diagnostics,
|
|
199
|
+
].join('\n');
|
|
200
|
+
}
|
|
187
201
|
const json = (value) => JSON.stringify(value, null, 1);
|
|
188
202
|
//# sourceMappingURL=prompt.js.map
|
package/dist/quiet.d.ts
ADDED
package/dist/quiet.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// LanceDB logs through env_logger, read once when its native module first
|
|
2
|
+
// loads, and at its default level it narrates routine work — "No existing
|
|
3
|
+
// dataset … it will be created" on every index gym builds. This runs before
|
|
4
|
+
// anything imports it; an explicit setting in the environment still wins.
|
|
5
|
+
process.env.LANCEDB_LOG ??= 'error';
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=quiet.js.map
|
package/dist/router.js
CHANGED
|
@@ -10,7 +10,11 @@ export class Router {
|
|
|
10
10
|
const id = `${operation.method} ${operation.path}${query(fixed)}`;
|
|
11
11
|
const clash = seen.get(id);
|
|
12
12
|
if (clash) {
|
|
13
|
-
throw new SpecError(`${id} is declared twice`,
|
|
13
|
+
throw new SpecError(`${id} is declared twice`, clash.source === operation.source
|
|
14
|
+
? `${operation.source} defines it twice`
|
|
15
|
+
: `${clash.source} and ${operation.source} both define it — two versions ` +
|
|
16
|
+
'of one API cannot be served by one process: name one of them, or run ' +
|
|
17
|
+
'two processes on different ports with the same --world');
|
|
14
18
|
}
|
|
15
19
|
seen.set(id, operation);
|
|
16
20
|
const segments = compile(operation.path);
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { Collection, WorldStore } from './store.ts';
|
|
2
|
+
export interface HandlerRequest {
|
|
3
|
+
operationId: string;
|
|
4
|
+
method: string;
|
|
5
|
+
/** the template, `/users/{userId}/messages/{id}` */
|
|
6
|
+
path: string;
|
|
7
|
+
pathParams: Record<string, string>;
|
|
8
|
+
query: Record<string, string>;
|
|
9
|
+
headers: Record<string, string>;
|
|
10
|
+
body: unknown;
|
|
11
|
+
seed: number;
|
|
12
|
+
}
|
|
13
|
+
/** What a handler may ask the world agent to make up. */
|
|
14
|
+
export interface SynthesisRequest {
|
|
15
|
+
collection: string;
|
|
16
|
+
/** the component schema every record must satisfy, e.g. `"Message"` */
|
|
17
|
+
type?: string;
|
|
18
|
+
/** how many to make when no ids are named. Default 5 */
|
|
19
|
+
count?: number;
|
|
20
|
+
/** nothing is made if this many records already match `where` */
|
|
21
|
+
min?: number;
|
|
22
|
+
/** ids that must exist afterwards */
|
|
23
|
+
ids?: (string | number)[];
|
|
24
|
+
/** the property that holds the id. Default `id` */
|
|
25
|
+
idField?: string;
|
|
26
|
+
/** values every new record must carry, e.g. `{ calendarId: 'primary' }` */
|
|
27
|
+
where?: Record<string, unknown>;
|
|
28
|
+
/** what the records are for, in a sentence: the query, the filter, the context */
|
|
29
|
+
hint?: string;
|
|
30
|
+
/** the first read of this collection (and `where`): fill it as the story has it */
|
|
31
|
+
populate?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export type Synthesize = (request: SynthesisRequest) => Promise<Record<string, unknown>[]>;
|
|
34
|
+
/** Where accounts live, and the id of the one the API is called as. */
|
|
35
|
+
export declare const ACCOUNTS = "accounts";
|
|
36
|
+
/**
|
|
37
|
+
* Whether a collection — or the part of it `where` selects — has ever been
|
|
38
|
+
* filled by the world agent. Counting records cannot tell: a mailbox whose
|
|
39
|
+
* only message is the one the client just sent is not an inbox yet.
|
|
40
|
+
*/
|
|
41
|
+
export declare function populatedKey(collection: string, where?: Record<string, unknown>): string;
|
|
42
|
+
export declare const OWNER = "@me";
|
|
43
|
+
export interface Account {
|
|
44
|
+
id: string;
|
|
45
|
+
email: string;
|
|
46
|
+
displayName?: string;
|
|
47
|
+
[key: string]: unknown;
|
|
48
|
+
}
|
|
49
|
+
export interface Page<T> {
|
|
50
|
+
items: T[];
|
|
51
|
+
/** the token for the next page, null on the last */
|
|
52
|
+
nextToken: string | null;
|
|
53
|
+
/** the offset of the next page, null on the last */
|
|
54
|
+
nextOffset: number | null;
|
|
55
|
+
hasMore: boolean;
|
|
56
|
+
total: number;
|
|
57
|
+
}
|
|
58
|
+
export interface PageOptions {
|
|
59
|
+
/** the token a previous page handed out, as the client sent it back */
|
|
60
|
+
token?: unknown;
|
|
61
|
+
/** for offset-style APIs, the offset the client asked for */
|
|
62
|
+
offset?: unknown;
|
|
63
|
+
/** page size the client asked for */
|
|
64
|
+
size?: unknown;
|
|
65
|
+
/** page size when the client did not say. Default 10 */
|
|
66
|
+
defaultSize?: number;
|
|
67
|
+
}
|
|
68
|
+
export interface HandlerContext {
|
|
69
|
+
/**
|
|
70
|
+
* The shared world, by type: `ctx.db.collection('Message').get(id)`. A
|
|
71
|
+
* lowercase name is taken as a collection name instead.
|
|
72
|
+
*/
|
|
73
|
+
db: {
|
|
74
|
+
collection<T = Record<string, unknown>>(name: string): Collection<T>;
|
|
75
|
+
};
|
|
76
|
+
/** a record by id, made up by the world agent if the world does not have it */
|
|
77
|
+
lookup<T = Record<string, unknown>>(type: string, id: string | number, options?: Omit<SynthesisRequest, 'collection' | 'ids' | 'count'>): Promise<T>;
|
|
78
|
+
/**
|
|
79
|
+
* Every record in a collection, after making sure there are at least
|
|
80
|
+
* `min` (default 1) matching `where`. The way a list endpoint starts.
|
|
81
|
+
*/
|
|
82
|
+
ensure<T = Record<string, unknown>>(type: string, options?: Omit<SynthesisRequest, 'collection' | 'ids'>): Promise<T[]>;
|
|
83
|
+
/** asks the world agent for new records directly; they are stored before this returns */
|
|
84
|
+
synthesize: Synthesize;
|
|
85
|
+
/**
|
|
86
|
+
* The account this API is called as — the person `me`, `primary` and
|
|
87
|
+
* `self` stand for. Made once, from the story, and shared by every API.
|
|
88
|
+
*/
|
|
89
|
+
owner(): Promise<Account>;
|
|
90
|
+
/** a page of `items` with a token or offset that always advances and always ends */
|
|
91
|
+
page<T>(items: readonly T[], options?: PageOptions): Page<T>;
|
|
92
|
+
/** answers with an HTTP error instead of the success body */
|
|
93
|
+
fail(status: number, body?: unknown): never;
|
|
94
|
+
/** a string matching a regular expression, e.g. a schema `pattern` */
|
|
95
|
+
fromPattern(pattern: string): string;
|
|
96
|
+
/** a fresh id: hex by default, or matching `pattern` when given */
|
|
97
|
+
newId(pattern?: string): string;
|
|
98
|
+
/** seeded randomness, so one request answers the same way twice under `--seed` */
|
|
99
|
+
random(): number;
|
|
100
|
+
int(min: number, max: number): number;
|
|
101
|
+
pick<T>(items: readonly T[]): T;
|
|
102
|
+
/** the current time as RFC 3339 */
|
|
103
|
+
now(): string;
|
|
104
|
+
}
|
|
105
|
+
export declare class HttpError extends Error {
|
|
106
|
+
readonly status: number;
|
|
107
|
+
readonly body: unknown;
|
|
108
|
+
constructor(status: number, body?: unknown);
|
|
109
|
+
}
|
|
110
|
+
/** Where a type's records live, and which property holds their id. */
|
|
111
|
+
export interface Binding {
|
|
112
|
+
collection: string;
|
|
113
|
+
idField: string;
|
|
114
|
+
}
|
|
115
|
+
export type Bindings = Readonly<Record<string, Binding>>;
|
|
116
|
+
/** One collection a handler touched, and as which type. */
|
|
117
|
+
export interface Use {
|
|
118
|
+
collection: string;
|
|
119
|
+
type?: string;
|
|
120
|
+
idField?: string;
|
|
121
|
+
}
|
|
122
|
+
/** The type gym gives the owner account; no document declares one. */
|
|
123
|
+
export declare const OWNER_TYPE = "@owner";
|
|
124
|
+
/** A name that starts with a capital is a type: `Message`, not `messages`. */
|
|
125
|
+
export declare const isTypeName: (name: string) => boolean;
|
|
126
|
+
/**
|
|
127
|
+
* The collection an unbound type gets: `Message` → `messages`,
|
|
128
|
+
* `PolicyTransportZone` → `policy_transport_zones`. Only ever a first guess —
|
|
129
|
+
* once bound, the binding is the answer.
|
|
130
|
+
*/
|
|
131
|
+
export declare function collectionFor(type: string): string;
|
|
132
|
+
/**
|
|
133
|
+
* A name as a handler wrote it, made concrete. A type name finds its
|
|
134
|
+
* collection through the bindings; a collection name finds its type the same
|
|
135
|
+
* way, backwards. Whatever was asked is reported to `record`, which is how the
|
|
136
|
+
* judge learns which types a handler stores where.
|
|
137
|
+
*/
|
|
138
|
+
export declare function resolve(name: string, options: {
|
|
139
|
+
type?: string;
|
|
140
|
+
idField?: string;
|
|
141
|
+
}, bindings: Bindings): Required<Pick<Use, 'collection'>> & Use;
|
|
142
|
+
export declare function createContext(input: HandlerRequest, store: WorldStore, synthesize: Synthesize, bindings?: Bindings, record?: (use: Use) => void): HandlerContext;
|
|
143
|
+
/** Every key in `where` present on the record with an equal value, compared as text. */
|
|
144
|
+
/** Every key in `where` matched by the record: see `same`. */
|
|
145
|
+
export declare function matches(record: Record<string, unknown>, where?: Record<string, unknown>): boolean;
|
|
146
|
+
/**
|
|
147
|
+
* Whether a value is what was asked for. Scalars compare as text (a path
|
|
148
|
+
* segment is text, the stored id may be a number); an object asks for a subset
|
|
149
|
+
* of the same shape; a list asks for every item it names; and a scalar asked
|
|
150
|
+
* of a list asks whether the list holds it — `{ labelIds: 'INBOX' }` finds the
|
|
151
|
+
* messages labelled INBOX.
|
|
152
|
+
*/
|
|
153
|
+
export declare function same(actual: unknown, wanted: unknown): boolean;
|
|
154
|
+
/**
|
|
155
|
+
* The one paging rule, written once rather than by every handler: the token is
|
|
156
|
+
* the next offset, it strictly advances, and it is absent on the last page. A
|
|
157
|
+
* token that cannot be read is the end of the list, never the start of it.
|
|
158
|
+
*/
|
|
159
|
+
export declare function page<T>(items: readonly T[], options: PageOptions): Page<T>;
|
|
160
|
+
//# sourceMappingURL=runtime.d.ts.map
|