@retinue/agentkit 0.1.0 → 0.3.0
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 +59 -277
- package/dist/adapters/audio/openai.d.ts +36 -0
- package/dist/adapters/audio/openai.js +136 -0
- package/dist/adapters/bullmq/lock.d.ts +26 -2
- package/dist/adapters/embeddings/openai.d.ts +45 -0
- package/dist/adapters/embeddings/openai.js +109 -0
- package/dist/adapters/memory/connections.d.ts +10 -0
- package/dist/adapters/memory/connections.js +87 -0
- package/dist/adapters/memory/graph.d.ts +17 -0
- package/dist/adapters/memory/graph.js +267 -0
- package/dist/adapters/memory/index.d.ts +3 -0
- package/dist/adapters/memory/index.js +3 -0
- package/dist/adapters/memory/rate-limit.d.ts +14 -0
- package/dist/adapters/memory/rate-limit.js +28 -0
- package/dist/adapters/memory/runtime.js +0 -0
- package/dist/adapters/postgres/connections.d.ts +15 -0
- package/dist/adapters/postgres/connections.js +134 -0
- package/dist/adapters/postgres/graph.d.ts +24 -0
- package/dist/adapters/postgres/graph.js +325 -0
- package/dist/adapters/postgres/index.d.ts +2 -0
- package/dist/adapters/postgres/index.js +2 -0
- package/dist/adapters/postgres/migrations.js +198 -0
- package/dist/adapters/postgres/run-store.js +26 -2
- package/dist/adapters/postgres/schema.d.ts +61 -2
- package/dist/adapters/postgres/schema.js +95 -10
- package/dist/adapters/redis/index.d.ts +1 -0
- package/dist/adapters/redis/index.js +1 -0
- package/dist/adapters/redis/rate-limit.d.ts +46 -0
- package/dist/adapters/redis/rate-limit.js +55 -0
- package/dist/adapters/supabase/index.d.ts +15 -0
- package/dist/adapters/supabase/index.js +15 -0
- package/dist/adapters/supabase/rls.js +33 -0
- package/dist/agents/agent.d.ts +99 -1
- package/dist/agents/agent.js +215 -16
- package/dist/agents/define.js +24 -4
- package/dist/agents/engine.d.ts +48 -0
- package/dist/agents/engine.js +370 -13
- package/dist/audio/index.d.ts +151 -0
- package/dist/audio/index.js +183 -0
- package/dist/capabilities/index.d.ts +5 -1
- package/dist/capabilities/index.js +23 -0
- package/dist/capabilities/runtime.d.ts +8 -0
- package/dist/connections/cipher.d.ts +103 -0
- package/dist/connections/cipher.js +141 -0
- package/dist/connections/index.d.ts +156 -0
- package/dist/connections/index.js +72 -0
- package/dist/connections/oauth/client.d.ts +84 -0
- package/dist/connections/oauth/client.js +141 -0
- package/dist/connections/oauth/index.d.ts +166 -0
- package/dist/connections/oauth/index.js +226 -0
- package/dist/connections/oauth/service.d.ts +90 -0
- package/dist/connections/oauth/service.js +132 -0
- package/dist/connections/pause.d.ts +68 -0
- package/dist/connections/pause.js +97 -0
- package/dist/connections/resolver.d.ts +59 -0
- package/dist/connections/resolver.js +138 -0
- package/dist/connections/resume.d.ts +64 -0
- package/dist/connections/resume.js +99 -0
- package/dist/connections/vault-cipher.d.ts +95 -0
- package/dist/connections/vault-cipher.js +206 -0
- package/dist/core/budget.d.ts +55 -0
- package/dist/core/budget.js +56 -0
- package/dist/core/content-parts.d.ts +26 -2
- package/dist/core/content-parts.js +1 -0
- package/dist/core/context.d.ts +25 -0
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/errors.js +15 -0
- package/dist/core/events.d.ts +110 -2
- package/dist/core/events.js +33 -2
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/validation.js +19 -0
- package/dist/documents/index.d.ts +14 -0
- package/dist/documents/parsers/text.d.ts +16 -0
- package/dist/documents/parsers/text.js +54 -2
- package/dist/entries/connections.d.ts +10 -0
- package/dist/entries/connections.js +10 -0
- package/dist/entries/guardrails.d.ts +14 -0
- package/dist/entries/guardrails.js +14 -0
- package/dist/entries/knowledge.d.ts +17 -0
- package/dist/entries/knowledge.js +16 -0
- package/dist/entries/mcp-server.d.ts +9 -0
- package/dist/entries/mcp-server.js +9 -0
- package/dist/entries/runtime.d.ts +10 -0
- package/dist/entries/runtime.js +8 -0
- package/dist/entries/testing.d.ts +28 -0
- package/dist/entries/testing.js +28 -0
- package/dist/graphql/resolvers.d.ts +7 -1
- package/dist/graphql/resolvers.js +20 -0
- package/dist/graphql/schema.d.ts +1 -1
- package/dist/graphql/schema.js +44 -0
- package/dist/guardrails/index.d.ts +115 -0
- package/dist/guardrails/index.js +108 -0
- package/dist/guardrails/moderation.d.ts +53 -0
- package/dist/guardrails/moderation.js +75 -0
- package/dist/guardrails/pii.d.ts +75 -0
- package/dist/guardrails/pii.js +193 -0
- package/dist/knowledge/communities.d.ts +166 -0
- package/dist/knowledge/communities.js +377 -0
- package/dist/knowledge/graph-global.d.ts +153 -0
- package/dist/knowledge/graph-global.js +246 -0
- package/dist/knowledge/graph-retrieval.d.ts +150 -0
- package/dist/knowledge/graph-retrieval.js +303 -0
- package/dist/knowledge/graph.d.ts +223 -0
- package/dist/knowledge/graph.js +369 -0
- package/dist/knowledge/index.d.ts +25 -0
- package/dist/knowledge/index.js +37 -1
- package/dist/knowledge/navigate.d.ts +89 -0
- package/dist/knowledge/navigate.js +107 -0
- package/dist/knowledge/retrieval.d.ts +98 -5
- package/dist/knowledge/retrieval.js +158 -28
- package/dist/mcp-server/index.d.ts +168 -0
- package/dist/mcp-server/index.js +175 -0
- package/dist/models/extraction.d.ts +73 -0
- package/dist/models/extraction.js +118 -0
- package/dist/models/index.d.ts +40 -1
- package/dist/models/index.js +22 -1
- package/dist/models/pricing.d.ts +2 -0
- package/dist/models/pricing.js +17 -1
- package/dist/models/provider-factory.js +9 -7
- package/dist/models/streaming.d.ts +111 -1
- package/dist/models/streaming.js +184 -3
- package/dist/persistence/index.d.ts +230 -0
- package/dist/persistence/index.js +2 -0
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +12 -0
- package/dist/runtime/worker.d.ts +36 -1
- package/dist/runtime/worker.js +37 -4
- package/dist/security/checklist.js +55 -0
- package/dist/security/findings.js +18 -9
- package/dist/server/bin.d.ts +19 -0
- package/dist/server/bin.js +226 -0
- package/dist/server/boot.d.ts +20 -2
- package/dist/server/boot.js +29 -3
- package/dist/server/cli.d.ts +13 -0
- package/dist/server/cli.js +7 -4
- package/dist/server/doctor.d.ts +74 -0
- package/dist/server/doctor.js +280 -0
- package/dist/skills/catalogue.d.ts +49 -0
- package/dist/skills/catalogue.js +61 -0
- package/dist/skills/context.d.ts +61 -0
- package/dist/skills/context.js +107 -0
- package/dist/skills/index.d.ts +2 -0
- package/dist/skills/index.js +2 -0
- package/dist/telemetry/spans.js +17 -0
- package/dist/testing/conformance/artifact-exports.d.ts +20 -0
- package/dist/testing/conformance/artifact-exports.js +172 -0
- package/dist/testing/conformance/artifacts.d.ts +11 -0
- package/dist/testing/conformance/artifacts.js +316 -0
- package/dist/testing/conformance/audio.d.ts +38 -0
- package/dist/testing/conformance/audio.js +145 -0
- package/dist/testing/conformance/capability.d.ts +23 -0
- package/dist/testing/conformance/capability.js +27 -0
- package/dist/testing/conformance/checkpoint-store.d.ts +15 -0
- package/dist/testing/conformance/checkpoint-store.js +72 -0
- package/dist/testing/conformance/connections.d.ts +14 -0
- package/dist/testing/conformance/connections.js +171 -0
- package/dist/testing/conformance/conversation-store.d.ts +10 -0
- package/dist/testing/conformance/conversation-store.js +60 -0
- package/dist/testing/conformance/evaluation.d.ts +10 -0
- package/dist/testing/conformance/evaluation.js +295 -0
- package/dist/testing/conformance/files.d.ts +19 -0
- package/dist/testing/conformance/files.js +454 -0
- package/dist/testing/conformance/flows.d.ts +16 -0
- package/dist/testing/conformance/flows.js +193 -0
- package/dist/testing/conformance/graph.d.ts +22 -0
- package/dist/testing/conformance/graph.js +500 -0
- package/dist/testing/conformance/hitl.d.ts +25 -0
- package/dist/testing/conformance/hitl.js +523 -0
- package/dist/testing/conformance/index.d.ts +163 -0
- package/dist/testing/conformance/index.js +321 -0
- package/dist/testing/conformance/invariants.d.ts +23 -0
- package/dist/testing/conformance/invariants.js +80 -0
- package/dist/testing/conformance/knowledge.d.ts +41 -0
- package/dist/testing/conformance/knowledge.js +592 -0
- package/dist/testing/conformance/parents.d.ts +50 -0
- package/dist/testing/conformance/parents.js +39 -0
- package/dist/testing/conformance/rate-limit.d.ts +25 -0
- package/dist/testing/conformance/rate-limit.js +71 -0
- package/dist/testing/conformance/records.d.ts +36 -0
- package/dist/testing/conformance/records.js +400 -0
- package/dist/testing/conformance/rollups.d.ts +30 -0
- package/dist/testing/conformance/rollups.js +425 -0
- package/dist/testing/conformance/run-coordinator.d.ts +27 -0
- package/dist/testing/conformance/run-coordinator.js +150 -0
- package/dist/testing/conformance/run-event-log.d.ts +9 -0
- package/dist/testing/conformance/run-event-log.js +138 -0
- package/dist/testing/conformance/run-store.d.ts +12 -0
- package/dist/testing/conformance/run-store.js +294 -0
- package/dist/testing/conformance/session-state.d.ts +38 -0
- package/dist/testing/conformance/session-state.js +198 -0
- package/dist/testing/conformance/usage-limits.d.ts +10 -0
- package/dist/testing/conformance/usage-limits.js +309 -0
- package/dist/testing/conformance.d.ts +8 -0
- package/dist/testing/conformance.js +8 -0
- package/dist/testing/memory-backend.d.ts +39 -0
- package/dist/testing/memory-backend.js +41 -0
- package/dist/testing/pglite.d.ts +55 -0
- package/dist/testing/pglite.js +78 -0
- package/dist/testing/stub-model.d.ts +58 -0
- package/dist/testing/stub-model.js +71 -0
- package/dist/testing/supabase-storage-double.d.ts +35 -0
- package/dist/testing/supabase-storage-double.js +101 -0
- package/dist/toolkit/files.d.ts +125 -0
- package/dist/toolkit/files.js +320 -0
- package/dist/toolkit/http.d.ts +27 -0
- package/dist/toolkit/http.js +48 -0
- package/dist/toolkit/index.d.ts +31 -1
- package/dist/toolkit/index.js +26 -1
- package/dist/toolkit/mime.d.ts +97 -0
- package/dist/toolkit/mime.js +233 -0
- package/dist/toolkit/sandbox.d.ts +119 -0
- package/dist/toolkit/sandbox.js +239 -0
- package/dist/toolkit/ssrf.d.ts +139 -0
- package/dist/toolkit/ssrf.js +349 -0
- package/dist/toolkit/vendor.d.ts +91 -0
- package/dist/toolkit/vendor.js +153 -0
- package/dist/toolkit/web.d.ts +13 -0
- package/dist/toolkit/web.js +7 -1
- package/dist/tools/budget.d.ts +28 -0
- package/dist/tools/budget.js +35 -0
- package/dist/tools/credentials.d.ts +316 -0
- package/dist/tools/credentials.js +369 -0
- package/dist/tools/define.d.ts +45 -0
- package/dist/tools/define.js +26 -0
- package/dist/tools/find.d.ts +109 -0
- package/dist/tools/find.js +210 -0
- package/dist/tools/index.d.ts +35 -2
- package/dist/tools/index.js +11 -0
- package/dist/tools/library/audio.d.ts +63 -0
- package/dist/tools/library/audio.js +148 -0
- package/dist/tools/library/fs.d.ts +24 -0
- package/dist/tools/library/fs.js +102 -0
- package/dist/tools/library/index.d.ts +37 -2
- package/dist/tools/library/index.js +47 -0
- package/dist/tools/library/shell.d.ts +45 -0
- package/dist/tools/library/shell.js +70 -0
- package/dist/tools/meta-tools.js +8 -0
- package/dist/tools/registry.d.ts +146 -0
- package/dist/tools/registry.js +250 -4
- package/dist/usage/index.d.ts +21 -0
- package/dist/usage/index.js +1 -0
- package/dist/usage/rate-limit.d.ts +122 -0
- package/dist/usage/rate-limit.js +131 -0
- package/dist/usage/recorder.js +3 -0
- package/package.json +33 -2
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A shared PGlite instance with a fresh schema per caller.
|
|
3
|
+
*
|
|
4
|
+
* **Why.** Every test used to boot its own embedded Postgres, and boot is essentially the entire cost:
|
|
5
|
+
* measured on this machine, `new PGlite()` plus a first query is **432ms warm**, while running all
|
|
6
|
+
* eleven migrations is **20ms** — and creating a second schema and migrating into it inside an
|
|
7
|
+
* already-booted instance is also **20ms**. Boot dominates by more than twenty to one.
|
|
8
|
+
*
|
|
9
|
+
* The conformance entrypoints call their store factory once per test case, so between them they were
|
|
10
|
+
* booting roughly 250 embedded databases per run. That is the largest single cost in CI, and it grew
|
|
11
|
+
* with every port added — the `build` job roughly tripled over #98–#111.
|
|
12
|
+
*
|
|
13
|
+
* **Isolation is by schema, not by instance.** Each caller gets `CREATE SCHEMA` and an executor that
|
|
14
|
+
* sets `search_path` on every query, because the connection is shared and another caller's schema may
|
|
15
|
+
* be current by the time a query runs. Two in-process round trips instead of one is nothing next to a
|
|
16
|
+
* 432ms boot.
|
|
17
|
+
*
|
|
18
|
+
* **What this does not isolate**, and the reason some test files deliberately keep their own instance:
|
|
19
|
+
* connection-level state. Roles (`CREATE ROLE app_user`), `SET ROLE`, and session GUCs are properties
|
|
20
|
+
* of the connection, not the schema — so a file that creates a role per test would collide on the
|
|
21
|
+
* second one. The row-level-security tests are exactly that shape and are left alone on purpose.
|
|
22
|
+
*
|
|
23
|
+
* Also note: `information_schema` queries must filter on `table_schema = current_schema()` here. With
|
|
24
|
+
* one schema per instance an unqualified `WHERE table_name = 'runs'` matched one row; with many schemas
|
|
25
|
+
* in one instance it matches one per schema.
|
|
26
|
+
*/
|
|
27
|
+
import { PGlite } from "@electric-sql/pglite";
|
|
28
|
+
import type { SqlExecutor } from "../adapters/postgres/sql.js";
|
|
29
|
+
/**
|
|
30
|
+
* An executor pinned to `schema`.
|
|
31
|
+
*
|
|
32
|
+
* `search_path` is set per query rather than once, because the connection is shared: between two
|
|
33
|
+
* queries from this executor, another caller's executor may have pointed the path at its own schema.
|
|
34
|
+
* Setting it once at creation would make every test's isolation depend on execution order.
|
|
35
|
+
*
|
|
36
|
+
* `public` is deliberately **absent** from the path. With it present, `CREATE TABLE IF NOT EXISTS`
|
|
37
|
+
* would see a table of the same name in `public` and skip creating it here — so a stray table in
|
|
38
|
+
* `public` would silently make every later schema share it.
|
|
39
|
+
*/
|
|
40
|
+
export declare const schemaExecutor: (db: PGlite, schema: string) => SqlExecutor;
|
|
41
|
+
export type PgliteSchema = {
|
|
42
|
+
readonly sql: SqlExecutor;
|
|
43
|
+
readonly db: PGlite;
|
|
44
|
+
readonly schema: string;
|
|
45
|
+
};
|
|
46
|
+
/** A migrated, isolated schema on the shared instance. Lazily boots the instance on first use. */
|
|
47
|
+
export declare const freshPgliteSchema: () => Promise<PgliteSchema>;
|
|
48
|
+
/**
|
|
49
|
+
* A migrated schema, created lazily on first query so a caller can build stores synchronously.
|
|
50
|
+
*
|
|
51
|
+
* The conformance harnesses call their factory inside each test and expect a usable executor back
|
|
52
|
+
* immediately, so the work has to be deferred to the first query rather than done up front.
|
|
53
|
+
*/
|
|
54
|
+
export declare const lazyPgliteSchema: () => SqlExecutor;
|
|
55
|
+
//# sourceMappingURL=pglite.d.ts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A shared PGlite instance with a fresh schema per caller.
|
|
3
|
+
*
|
|
4
|
+
* **Why.** Every test used to boot its own embedded Postgres, and boot is essentially the entire cost:
|
|
5
|
+
* measured on this machine, `new PGlite()` plus a first query is **432ms warm**, while running all
|
|
6
|
+
* eleven migrations is **20ms** — and creating a second schema and migrating into it inside an
|
|
7
|
+
* already-booted instance is also **20ms**. Boot dominates by more than twenty to one.
|
|
8
|
+
*
|
|
9
|
+
* The conformance entrypoints call their store factory once per test case, so between them they were
|
|
10
|
+
* booting roughly 250 embedded databases per run. That is the largest single cost in CI, and it grew
|
|
11
|
+
* with every port added — the `build` job roughly tripled over #98–#111.
|
|
12
|
+
*
|
|
13
|
+
* **Isolation is by schema, not by instance.** Each caller gets `CREATE SCHEMA` and an executor that
|
|
14
|
+
* sets `search_path` on every query, because the connection is shared and another caller's schema may
|
|
15
|
+
* be current by the time a query runs. Two in-process round trips instead of one is nothing next to a
|
|
16
|
+
* 432ms boot.
|
|
17
|
+
*
|
|
18
|
+
* **What this does not isolate**, and the reason some test files deliberately keep their own instance:
|
|
19
|
+
* connection-level state. Roles (`CREATE ROLE app_user`), `SET ROLE`, and session GUCs are properties
|
|
20
|
+
* of the connection, not the schema — so a file that creates a role per test would collide on the
|
|
21
|
+
* second one. The row-level-security tests are exactly that shape and are left alone on purpose.
|
|
22
|
+
*
|
|
23
|
+
* Also note: `information_schema` queries must filter on `table_schema = current_schema()` here. With
|
|
24
|
+
* one schema per instance an unqualified `WHERE table_name = 'runs'` matched one row; with many schemas
|
|
25
|
+
* in one instance it matches one per schema.
|
|
26
|
+
*/
|
|
27
|
+
import { PGlite } from "@electric-sql/pglite";
|
|
28
|
+
import { migrate } from "../adapters/postgres/migrations.js";
|
|
29
|
+
/**
|
|
30
|
+
* One instance per module, which under vitest means one per test file — files run in separate workers,
|
|
31
|
+
* so this is not shared across them.
|
|
32
|
+
*/
|
|
33
|
+
let shared = null;
|
|
34
|
+
let schemas = 0;
|
|
35
|
+
const instance = () => (shared ??= new PGlite());
|
|
36
|
+
/**
|
|
37
|
+
* An executor pinned to `schema`.
|
|
38
|
+
*
|
|
39
|
+
* `search_path` is set per query rather than once, because the connection is shared: between two
|
|
40
|
+
* queries from this executor, another caller's executor may have pointed the path at its own schema.
|
|
41
|
+
* Setting it once at creation would make every test's isolation depend on execution order.
|
|
42
|
+
*
|
|
43
|
+
* `public` is deliberately **absent** from the path. With it present, `CREATE TABLE IF NOT EXISTS`
|
|
44
|
+
* would see a table of the same name in `public` and skip creating it here — so a stray table in
|
|
45
|
+
* `public` would silently make every later schema share it.
|
|
46
|
+
*/
|
|
47
|
+
export const schemaExecutor = (db, schema) => ({
|
|
48
|
+
async query(text, params) {
|
|
49
|
+
await db.exec(`SET search_path TO ${schema}`);
|
|
50
|
+
const result = await db.query(text, params ? [...params] : undefined);
|
|
51
|
+
return result.rows;
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
/** A migrated, isolated schema on the shared instance. Lazily boots the instance on first use. */
|
|
55
|
+
export const freshPgliteSchema = async () => {
|
|
56
|
+
const db = instance();
|
|
57
|
+
const schema = `s${(schemas += 1)}`;
|
|
58
|
+
await db.exec(`CREATE SCHEMA ${schema}`);
|
|
59
|
+
const sql = schemaExecutor(db, schema);
|
|
60
|
+
await migrate(sql);
|
|
61
|
+
return { sql, db, schema };
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* A migrated schema, created lazily on first query so a caller can build stores synchronously.
|
|
65
|
+
*
|
|
66
|
+
* The conformance harnesses call their factory inside each test and expect a usable executor back
|
|
67
|
+
* immediately, so the work has to be deferred to the first query rather than done up front.
|
|
68
|
+
*/
|
|
69
|
+
export const lazyPgliteSchema = () => {
|
|
70
|
+
let ready = null;
|
|
71
|
+
const init = () => (ready ??= freshPgliteSchema().then((created) => created.sql));
|
|
72
|
+
return {
|
|
73
|
+
async query(text, params) {
|
|
74
|
+
return (await init()).query(text, params);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=pglite.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A scripted model, for testing an agent without calling a provider — task #253 AC-4.
|
|
3
|
+
*
|
|
4
|
+
* Testing an agent currently means writing a `streamTurn` by hand, and every consumer writes the same one badly:
|
|
5
|
+
* a single `text-delta` and a `finish`, which exercises none of the paths that actually break. The interesting
|
|
6
|
+
* behaviour of this platform — retry, approval gates, tool errors, structured output, the catalogue budget — all
|
|
7
|
+
* live in what the model does *across steps*, and a one-chunk fake cannot express any of it.
|
|
8
|
+
*
|
|
9
|
+
* ## Scripted per turn, not per call
|
|
10
|
+
*
|
|
11
|
+
* A script is a list of **turns**, and each call consumes the next one. That is what lets a test say "the model
|
|
12
|
+
* calls the tool, sees the result, then answers" — three chunks in one turn — separately from "the first turn
|
|
13
|
+
* fails and the second succeeds", which is two turns and is how a retry test is written.
|
|
14
|
+
*
|
|
15
|
+
* Running past the end of the script is an **error**, not a silent empty turn. A test whose agent took one more
|
|
16
|
+
* turn than the author expected should fail loudly, because the alternative is an assertion passing against a
|
|
17
|
+
* turn that produced nothing.
|
|
18
|
+
*/
|
|
19
|
+
import { AgentPlatformError } from "../core/errors.js";
|
|
20
|
+
import type { ModelTurnRequest, NeutralStreamChunk, NeutralUsage } from "../models/streaming.js";
|
|
21
|
+
/** What the model does on one turn. */
|
|
22
|
+
export type ScriptedTurn =
|
|
23
|
+
/** Answers with text. */
|
|
24
|
+
{
|
|
25
|
+
readonly say: string;
|
|
26
|
+
readonly usage?: Partial<NeutralUsage>;
|
|
27
|
+
}
|
|
28
|
+
/** Calls tools, then answers — the shape almost every interesting test needs. */
|
|
29
|
+
| {
|
|
30
|
+
readonly call: readonly {
|
|
31
|
+
readonly tool: string;
|
|
32
|
+
readonly input?: unknown;
|
|
33
|
+
readonly id?: string;
|
|
34
|
+
}[];
|
|
35
|
+
readonly then?: string;
|
|
36
|
+
readonly usage?: Partial<NeutralUsage>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Fails, as a provider would.
|
|
40
|
+
*
|
|
41
|
+
* `retryable` defaults to **true**, because the case worth testing is the retry path — a non-retryable failure
|
|
42
|
+
* is just a thrown error and needs no scripting to produce.
|
|
43
|
+
*/
|
|
44
|
+
| {
|
|
45
|
+
readonly fail: string;
|
|
46
|
+
readonly code?: AgentPlatformError["code"];
|
|
47
|
+
readonly retryable?: boolean;
|
|
48
|
+
};
|
|
49
|
+
export type StubModel = {
|
|
50
|
+
/** Pass as `DefaultEngineDeps.streamTurn`. */
|
|
51
|
+
readonly streamTurn: (request: ModelTurnRequest) => AsyncIterable<NeutralStreamChunk>;
|
|
52
|
+
/** Every request the engine made, in order — so a test can assert what the model was *given*. */
|
|
53
|
+
readonly requests: readonly ModelTurnRequest[];
|
|
54
|
+
/** Turns consumed so far. */
|
|
55
|
+
readonly turns: () => number;
|
|
56
|
+
};
|
|
57
|
+
export declare const createStubModel: (script: readonly ScriptedTurn[]) => StubModel;
|
|
58
|
+
//# sourceMappingURL=stub-model.d.ts.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A scripted model, for testing an agent without calling a provider — task #253 AC-4.
|
|
3
|
+
*
|
|
4
|
+
* Testing an agent currently means writing a `streamTurn` by hand, and every consumer writes the same one badly:
|
|
5
|
+
* a single `text-delta` and a `finish`, which exercises none of the paths that actually break. The interesting
|
|
6
|
+
* behaviour of this platform — retry, approval gates, tool errors, structured output, the catalogue budget — all
|
|
7
|
+
* live in what the model does *across steps*, and a one-chunk fake cannot express any of it.
|
|
8
|
+
*
|
|
9
|
+
* ## Scripted per turn, not per call
|
|
10
|
+
*
|
|
11
|
+
* A script is a list of **turns**, and each call consumes the next one. That is what lets a test say "the model
|
|
12
|
+
* calls the tool, sees the result, then answers" — three chunks in one turn — separately from "the first turn
|
|
13
|
+
* fails and the second succeeds", which is two turns and is how a retry test is written.
|
|
14
|
+
*
|
|
15
|
+
* Running past the end of the script is an **error**, not a silent empty turn. A test whose agent took one more
|
|
16
|
+
* turn than the author expected should fail loudly, because the alternative is an assertion passing against a
|
|
17
|
+
* turn that produced nothing.
|
|
18
|
+
*/
|
|
19
|
+
import { AgentPlatformError } from "../core/errors.js";
|
|
20
|
+
const usageOf = (partial) => ({
|
|
21
|
+
inputTokens: 10,
|
|
22
|
+
outputTokens: 5,
|
|
23
|
+
cachedInputTokens: 0,
|
|
24
|
+
...partial,
|
|
25
|
+
});
|
|
26
|
+
export const createStubModel = (script) => {
|
|
27
|
+
const requests = [];
|
|
28
|
+
let index = 0;
|
|
29
|
+
async function* run(request) {
|
|
30
|
+
requests.push(request);
|
|
31
|
+
const turn = script[index];
|
|
32
|
+
index += 1;
|
|
33
|
+
if (turn === undefined) {
|
|
34
|
+
// Loud, not empty. An agent that took one more turn than the test expected is a finding, and an empty
|
|
35
|
+
// turn would let an assertion pass against a model that said nothing.
|
|
36
|
+
throw new AgentPlatformError({
|
|
37
|
+
code: "internal",
|
|
38
|
+
message: `the stub model's script has ${script.length} turn(s) and the agent asked for turn ${index}. ` +
|
|
39
|
+
"Either the agent is looping, or the script is short — both are worth knowing.",
|
|
40
|
+
retryable: false,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if ("fail" in turn) {
|
|
44
|
+
throw new AgentPlatformError({
|
|
45
|
+
code: turn.code ?? "provider_error",
|
|
46
|
+
message: turn.fail,
|
|
47
|
+
retryable: turn.retryable ?? true,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if ("say" in turn) {
|
|
51
|
+
yield { type: "text-delta", id: `t${index}`, text: turn.say };
|
|
52
|
+
yield { type: "finish", usage: usageOf(turn.usage) };
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
for (const [n, call] of turn.call.entries()) {
|
|
56
|
+
const id = call.id ?? `call-${index}-${n}`;
|
|
57
|
+
yield { type: "tool-call", toolCallId: id, toolName: call.tool, input: call.input ?? {} };
|
|
58
|
+
/**
|
|
59
|
+
* No `tool-result` is emitted here, deliberately.
|
|
60
|
+
*
|
|
61
|
+
* The engine runs the tool itself and produces the result — that is the path under test. A stub that
|
|
62
|
+
* emitted its own result would test nothing but the stub, and would hide an unwired tool entirely.
|
|
63
|
+
*/
|
|
64
|
+
}
|
|
65
|
+
if (turn.then !== undefined)
|
|
66
|
+
yield { type: "text-delta", id: `t${index}`, text: turn.then };
|
|
67
|
+
yield { type: "finish", usage: usageOf(turn.usage) };
|
|
68
|
+
}
|
|
69
|
+
return { streamTurn: run, requests, turns: () => index };
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=stub-model.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An in-process Supabase Storage, for the `FileContentStore` adapter (#129).
|
|
3
|
+
*
|
|
4
|
+
* Lives here rather than beside one test because two suites need it: `supabase-conformance.test.ts` is the
|
|
5
|
+
* single conformance entrypoint the matrix generator reads, and `supabase-storage.test.ts` holds the cases
|
|
6
|
+
* only an HTTP store has. A double copied into both would drift, and a drifting double is a suite that
|
|
7
|
+
* proves two different things while appearing to prove one.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately strict about what the adapter must get right: it requires the bearer token, it refuses an
|
|
10
|
+
* upload to an existing path unless `x-upsert` says otherwise, and it 404s an unknown object rather than
|
|
11
|
+
* returning an empty body. A lenient double would let every one of those bugs through to production.
|
|
12
|
+
*
|
|
13
|
+
* It can only prove the adapter is self-consistent about the API it *believes* Supabase has. What it proves
|
|
14
|
+
* well is everything on this side of the wire — URL construction, status handling, tenant prefixing — which
|
|
15
|
+
* is where the tenant boundary lives.
|
|
16
|
+
*/
|
|
17
|
+
import type { FileContentStore } from "../persistence/index.js";
|
|
18
|
+
export type RecordedCall = {
|
|
19
|
+
readonly method: string;
|
|
20
|
+
readonly url: string;
|
|
21
|
+
readonly headers: Record<string, string>;
|
|
22
|
+
};
|
|
23
|
+
export declare const STORAGE_BUCKET = "attachments";
|
|
24
|
+
export declare const STORAGE_URL = "https://project.supabase.test";
|
|
25
|
+
export declare const STORAGE_KEY = "service-role-key";
|
|
26
|
+
/** The adapter over the double, plus the double's innards for the assertions that need them. */
|
|
27
|
+
export declare const supabaseStorageDouble: () => {
|
|
28
|
+
readonly store: FileContentStore;
|
|
29
|
+
readonly objects: Map<string, {
|
|
30
|
+
bytes: Uint8Array;
|
|
31
|
+
mediaType: string;
|
|
32
|
+
}>;
|
|
33
|
+
readonly calls: RecordedCall[];
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=supabase-storage-double.d.ts.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An in-process Supabase Storage, for the `FileContentStore` adapter (#129).
|
|
3
|
+
*
|
|
4
|
+
* Lives here rather than beside one test because two suites need it: `supabase-conformance.test.ts` is the
|
|
5
|
+
* single conformance entrypoint the matrix generator reads, and `supabase-storage.test.ts` holds the cases
|
|
6
|
+
* only an HTTP store has. A double copied into both would drift, and a drifting double is a suite that
|
|
7
|
+
* proves two different things while appearing to prove one.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately strict about what the adapter must get right: it requires the bearer token, it refuses an
|
|
10
|
+
* upload to an existing path unless `x-upsert` says otherwise, and it 404s an unknown object rather than
|
|
11
|
+
* returning an empty body. A lenient double would let every one of those bugs through to production.
|
|
12
|
+
*
|
|
13
|
+
* It can only prove the adapter is self-consistent about the API it *believes* Supabase has. What it proves
|
|
14
|
+
* well is everything on this side of the wire — URL construction, status handling, tenant prefixing — which
|
|
15
|
+
* is where the tenant boundary lives.
|
|
16
|
+
*/
|
|
17
|
+
import { createSupabaseStorageFileContentStore } from "../adapters/supabase/storage.js";
|
|
18
|
+
export const STORAGE_BUCKET = "attachments";
|
|
19
|
+
export const STORAGE_URL = "https://project.supabase.test";
|
|
20
|
+
export const STORAGE_KEY = "service-role-key";
|
|
21
|
+
const fakeStorage = () => {
|
|
22
|
+
const objects = new Map();
|
|
23
|
+
const calls = [];
|
|
24
|
+
const reply = (status, body) => ({
|
|
25
|
+
status,
|
|
26
|
+
ok: status >= 200 && status < 300,
|
|
27
|
+
async text() {
|
|
28
|
+
return typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
29
|
+
},
|
|
30
|
+
async arrayBuffer() {
|
|
31
|
+
return (typeof body === "string" ? new TextEncoder().encode(body) : body)
|
|
32
|
+
.slice()
|
|
33
|
+
.buffer;
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
const fetchImpl = async (url, init) => {
|
|
37
|
+
const method = init?.method ?? "GET";
|
|
38
|
+
const headers = init?.headers ?? {};
|
|
39
|
+
calls.push({ method, url, headers });
|
|
40
|
+
if (headers["Authorization"] !== `Bearer ${STORAGE_KEY}`)
|
|
41
|
+
return reply(401, "missing bearer token");
|
|
42
|
+
const rest = url.slice(`${STORAGE_URL}/storage/v1/`.length);
|
|
43
|
+
if (rest.startsWith(`object/list/${STORAGE_BUCKET}`) && method === "POST") {
|
|
44
|
+
const body = JSON.parse(String(init?.body ?? "{}"));
|
|
45
|
+
const matched = [...objects.entries()]
|
|
46
|
+
.filter(([path]) => path.startsWith(body.prefix))
|
|
47
|
+
.map(([path, value]) => ({
|
|
48
|
+
// The API returns names relative to the searched prefix, which is what the adapter relies on.
|
|
49
|
+
name: path.slice(body.prefix.length),
|
|
50
|
+
metadata: { size: value.bytes.byteLength },
|
|
51
|
+
}))
|
|
52
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
53
|
+
.slice(body.offset, body.offset + body.limit);
|
|
54
|
+
return reply(200, JSON.stringify(matched));
|
|
55
|
+
}
|
|
56
|
+
if (rest.startsWith(`object/sign/${STORAGE_BUCKET}/`) && method === "POST") {
|
|
57
|
+
const path = rest.slice(`object/sign/${STORAGE_BUCKET}/`.length);
|
|
58
|
+
if (!objects.has(path))
|
|
59
|
+
return reply(404, "Object not found");
|
|
60
|
+
const { expiresIn } = JSON.parse(String(init?.body ?? "{}"));
|
|
61
|
+
return reply(200, JSON.stringify({ signedURL: `/object/sign/${STORAGE_BUCKET}/${path}?token=t&exp=${expiresIn}` }));
|
|
62
|
+
}
|
|
63
|
+
if (rest.startsWith(`object/${STORAGE_BUCKET}/`)) {
|
|
64
|
+
const path = rest.slice(`object/${STORAGE_BUCKET}/`.length);
|
|
65
|
+
if (method === "POST") {
|
|
66
|
+
if (objects.has(path) && headers["x-upsert"] !== "true")
|
|
67
|
+
return reply(409, "The resource already exists");
|
|
68
|
+
const body = init?.body;
|
|
69
|
+
if (!(body instanceof Uint8Array))
|
|
70
|
+
return reply(400, "expected a byte body");
|
|
71
|
+
objects.set(path, { bytes: body, mediaType: headers["Content-Type"] ?? "application/octet-stream" });
|
|
72
|
+
return reply(200, JSON.stringify({ Key: `${STORAGE_BUCKET}/${path}` }));
|
|
73
|
+
}
|
|
74
|
+
if (method === "DELETE") {
|
|
75
|
+
if (!objects.has(path))
|
|
76
|
+
return reply(404, "Object not found");
|
|
77
|
+
objects.delete(path);
|
|
78
|
+
return reply(200, JSON.stringify({ message: "Successfully deleted" }));
|
|
79
|
+
}
|
|
80
|
+
const stored = objects.get(path);
|
|
81
|
+
return stored === undefined ? reply(404, "Object not found") : reply(200, stored.bytes);
|
|
82
|
+
}
|
|
83
|
+
return reply(404, `unrouted ${method} ${rest}`);
|
|
84
|
+
};
|
|
85
|
+
return { fetchImpl, objects, calls };
|
|
86
|
+
};
|
|
87
|
+
/** The adapter over the double, plus the double's innards for the assertions that need them. */
|
|
88
|
+
export const supabaseStorageDouble = () => {
|
|
89
|
+
const storage = fakeStorage();
|
|
90
|
+
return {
|
|
91
|
+
store: createSupabaseStorageFileContentStore({
|
|
92
|
+
url: STORAGE_URL,
|
|
93
|
+
serviceKey: STORAGE_KEY,
|
|
94
|
+
bucket: STORAGE_BUCKET,
|
|
95
|
+
fetch: storage.fetchImpl,
|
|
96
|
+
}),
|
|
97
|
+
objects: storage.objects,
|
|
98
|
+
calls: storage.calls,
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
//# sourceMappingURL=supabase-storage-double.js.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading and writing files, path-scoped — REQ-047 (#206), task #215.
|
|
3
|
+
*
|
|
4
|
+
* In `toolkit/` rather than `tools/` because it performs I/O and boundary rule **R7** forbids that in the tools
|
|
5
|
+
* layer, the same arrangement `http.ts` has with the web tools. The tools in `tools/library/fs.ts` are envelopes
|
|
6
|
+
* over these functions, and every security property lives here — not in the envelope, and not in the schema.
|
|
7
|
+
*
|
|
8
|
+
* ## The one property that matters
|
|
9
|
+
*
|
|
10
|
+
* **A path a model produced must not be able to name a file outside the configured root**, and there are three
|
|
11
|
+
* ways it tries:
|
|
12
|
+
*
|
|
13
|
+
* - `../../etc/passwd` — normalised away by resolving against the root first.
|
|
14
|
+
* - `/etc/passwd` — an absolute path is **refused outright** rather than silently re-rooted. Re-rooting would
|
|
15
|
+
* answer a different question than the one asked, and the model would not know.
|
|
16
|
+
* - A **symlink** inside the root pointing out of it. This is the one that gets missed, because the path is
|
|
17
|
+
* inside the root right up until the filesystem resolves it. So the check is against the *real* path, after
|
|
18
|
+
* symlink resolution, on every call — not against the string.
|
|
19
|
+
*
|
|
20
|
+
* The root itself is resolved once at construction, also through `realpath`: a root that is itself a symlink
|
|
21
|
+
* would otherwise make every real path look like an escape.
|
|
22
|
+
*
|
|
23
|
+
* ## Bytes are bounded while reading
|
|
24
|
+
*
|
|
25
|
+
* A cap applied after `readFile` has already buffered a two-gigabyte file protects nothing. These read into a
|
|
26
|
+
* fixed buffer and report `truncated`, which is the same decision `http.ts` made for the same reason. Truncation
|
|
27
|
+
* rather than refusal, because "the first 200 KB of the log" is usually the answer, and a refusal leaves the
|
|
28
|
+
* model with nothing.
|
|
29
|
+
*/
|
|
30
|
+
/** Bytes returned from one read. Matches the HTTP client's ceiling, for the same reason. */
|
|
31
|
+
export declare const MAX_FILE_BYTES = 200000;
|
|
32
|
+
/** Entries returned from one listing. A directory of ten thousand files is not an answer. */
|
|
33
|
+
export declare const MAX_ENTRIES = 200;
|
|
34
|
+
/** Files examined by one search. Bounded work, so a search cannot become a filesystem crawl. */
|
|
35
|
+
export declare const MAX_SEARCHED_FILES = 2000;
|
|
36
|
+
/** Matches returned from one search. */
|
|
37
|
+
export declare const MAX_MATCHES = 100;
|
|
38
|
+
export type FileScope = {
|
|
39
|
+
/** Everything readable, and the only thing readable. Resolved through `realpath` at construction. */
|
|
40
|
+
readonly root: string;
|
|
41
|
+
/**
|
|
42
|
+
* Where writes may land, when writes are wanted at all.
|
|
43
|
+
*
|
|
44
|
+
* Separate from `root`, and absent by default. A deployment that pointed both at the same directory would let
|
|
45
|
+
* a model edit the material it also reads — which is how a corpus a model cites becomes a corpus a model wrote.
|
|
46
|
+
*/
|
|
47
|
+
readonly writableRoot?: string;
|
|
48
|
+
readonly maxBytes?: number;
|
|
49
|
+
readonly maxEntries?: number;
|
|
50
|
+
readonly maxMatches?: number;
|
|
51
|
+
readonly maxSearchedFiles?: number;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Why a file operation did not happen.
|
|
55
|
+
*
|
|
56
|
+
* A *reason*, not an exception, for the reason `HttpFailure` gives: a refused path is information the model can
|
|
57
|
+
* act on, and a thrown error reads as "something broke", which invites an identical retry.
|
|
58
|
+
*/
|
|
59
|
+
export type FileFailure = {
|
|
60
|
+
readonly ok: false;
|
|
61
|
+
readonly path: string;
|
|
62
|
+
readonly kind: "forbidden" | "not-found" | "not-a-file" | "not-a-directory" | "unreadable" | "too-many";
|
|
63
|
+
readonly reason: string;
|
|
64
|
+
};
|
|
65
|
+
export type FileRead = {
|
|
66
|
+
readonly ok: true;
|
|
67
|
+
readonly path: string;
|
|
68
|
+
readonly bytes: number;
|
|
69
|
+
readonly truncated: boolean;
|
|
70
|
+
readonly content: string;
|
|
71
|
+
};
|
|
72
|
+
export type FileEntry = {
|
|
73
|
+
readonly name: string;
|
|
74
|
+
readonly path: string;
|
|
75
|
+
readonly kind: "file" | "directory" | "other";
|
|
76
|
+
readonly bytes?: number;
|
|
77
|
+
};
|
|
78
|
+
export type FileList = {
|
|
79
|
+
readonly ok: true;
|
|
80
|
+
readonly path: string;
|
|
81
|
+
readonly entries: readonly FileEntry[];
|
|
82
|
+
readonly truncated: boolean;
|
|
83
|
+
};
|
|
84
|
+
export type FileMatch = {
|
|
85
|
+
readonly path: string;
|
|
86
|
+
readonly line: number;
|
|
87
|
+
/** The matching line, trimmed and capped. Untrusted content, like everything else read from disk. */
|
|
88
|
+
readonly text: string;
|
|
89
|
+
};
|
|
90
|
+
export type FileSearch = {
|
|
91
|
+
readonly ok: true;
|
|
92
|
+
readonly query: string;
|
|
93
|
+
readonly matches: readonly FileMatch[];
|
|
94
|
+
readonly filesSearched: number;
|
|
95
|
+
/** True when the file or match ceiling stopped the search early. */
|
|
96
|
+
readonly truncated: boolean;
|
|
97
|
+
};
|
|
98
|
+
export type FileWrite = {
|
|
99
|
+
readonly ok: true;
|
|
100
|
+
readonly path: string;
|
|
101
|
+
readonly bytes: number;
|
|
102
|
+
readonly created: boolean;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Is `candidate` inside `root`, both already real paths?
|
|
106
|
+
*
|
|
107
|
+
* `relative` rather than `startsWith`: `/srv/data-secrets` starts with `/srv/data`, and a prefix comparison would
|
|
108
|
+
* accept it. A relative path that begins with `..` or is absolute is outside.
|
|
109
|
+
*/
|
|
110
|
+
export declare const contains: (root: string, candidate: string) => boolean;
|
|
111
|
+
export type FileReader = {
|
|
112
|
+
read(path: string): FileRead | FileFailure;
|
|
113
|
+
list(path?: string): FileList | FileFailure;
|
|
114
|
+
search(input: {
|
|
115
|
+
readonly query: string;
|
|
116
|
+
readonly path?: string;
|
|
117
|
+
readonly namePattern?: string;
|
|
118
|
+
}): FileSearch | FileFailure;
|
|
119
|
+
write(input: {
|
|
120
|
+
readonly path: string;
|
|
121
|
+
readonly content: string;
|
|
122
|
+
}): FileWrite | FileFailure;
|
|
123
|
+
};
|
|
124
|
+
export declare const createFileReader: (scope: FileScope) => FileReader;
|
|
125
|
+
//# sourceMappingURL=files.d.ts.map
|