@retinue/agentkit 0.2.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 +1 -1
- 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/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 +78 -1
- package/dist/agents/agent.js +118 -5
- package/dist/agents/define.js +24 -4
- package/dist/agents/engine.d.ts +20 -0
- package/dist/agents/engine.js +177 -6
- package/dist/audio/index.d.ts +151 -0
- package/dist/audio/index.js +183 -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/content-parts.d.ts +18 -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 +44 -2
- package/dist/core/events.js +31 -2
- package/dist/core/validation.js +19 -0
- package/dist/entries/connections.d.ts +10 -0
- package/dist/entries/connections.js +10 -0
- package/dist/entries/knowledge.d.ts +8 -0
- package/dist/entries/knowledge.js +8 -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 +3 -1
- package/dist/graphql/resolvers.js +14 -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 +24 -0
- package/dist/knowledge/index.js +36 -1
- package/dist/knowledge/retrieval.d.ts +26 -1
- package/dist/knowledge/retrieval.js +77 -1
- 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 +89 -0
- package/dist/models/streaming.js +179 -2
- 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 +46 -0
- 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/context.d.ts +61 -0
- package/dist/skills/context.js +107 -0
- package/dist/skills/index.d.ts +1 -0
- package/dist/skills/index.js +1 -0
- package/dist/telemetry/spans.js +5 -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/http.d.ts +27 -0
- package/dist/toolkit/http.js +48 -0
- package/dist/toolkit/index.d.ts +27 -1
- package/dist/toolkit/index.js +24 -1
- package/dist/toolkit/mime.d.ts +97 -0
- package/dist/toolkit/mime.js +233 -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/tools/credentials.d.ts +265 -6
- package/dist/tools/credentials.js +318 -3
- package/dist/tools/define.d.ts +14 -0
- package/dist/tools/define.js +3 -0
- package/dist/tools/index.d.ts +21 -0
- package/dist/tools/index.js +7 -0
- package/dist/tools/library/audio.d.ts +63 -0
- package/dist/tools/library/audio.js +148 -0
- package/dist/tools/library/index.d.ts +8 -0
- package/dist/tools/library/index.js +7 -0
- package/dist/tools/registry.d.ts +33 -0
- package/dist/tools/registry.js +72 -2
- 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 +29 -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
|
package/dist/toolkit/http.d.ts
CHANGED
|
@@ -57,6 +57,27 @@ export type HttpFailure = {
|
|
|
57
57
|
readonly kind: "forbidden" | "redirected" | "timeout" | "unreachable" | "http-error" | "unreadable";
|
|
58
58
|
readonly status?: number;
|
|
59
59
|
readonly reason: string;
|
|
60
|
+
/**
|
|
61
|
+
* `Retry-After`, in milliseconds, when the server sent one.
|
|
62
|
+
*
|
|
63
|
+
* Parsed here because it is the one response header a *failure* still needs to carry: a vendor that answers
|
|
64
|
+
* `429` with `Retry-After: 47` has told you exactly how long to wait, and a default backoff that ignores it
|
|
65
|
+
* either hammers the vendor or waits far too long. `PlatformError.retryAfterMs` already exists and the retry
|
|
66
|
+
* path already reads it — this is the missing link between them.
|
|
67
|
+
*
|
|
68
|
+
* Both wire formats: a delay in seconds, and an HTTP date.
|
|
69
|
+
*/
|
|
70
|
+
readonly retryAfterMs?: number;
|
|
71
|
+
/**
|
|
72
|
+
* The vendor's rate-limit headers, and **only** those — see `RATE_LIMIT_HEADERS`.
|
|
73
|
+
*
|
|
74
|
+
* An allowlist rather than every header, for two reasons. A failure travels into error messages and audit
|
|
75
|
+
* records, so carrying `set-cookie` or an auth echo would be a leak with no purpose. And a bounded set says
|
|
76
|
+
* what this field is *for*: `retryAfterMs` answers "how long", and these answer the question X forces —
|
|
77
|
+
* **which** limit was hit. A 15-minute burst limit and a 24-hour cap are both `429`, and treating the second
|
|
78
|
+
* as retryable makes a client sit in backoff until tomorrow.
|
|
79
|
+
*/
|
|
80
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
60
81
|
};
|
|
61
82
|
export type HttpSuccess = {
|
|
62
83
|
readonly ok: true;
|
|
@@ -97,6 +118,12 @@ export type HttpRequest = {
|
|
|
97
118
|
export type HttpClient = {
|
|
98
119
|
request(input: HttpRequest): Promise<HttpOutcome>;
|
|
99
120
|
};
|
|
121
|
+
export declare const rateLimitHeadersOf: (headers: Headers) => {
|
|
122
|
+
headers?: Readonly<Record<string, string>>;
|
|
123
|
+
};
|
|
124
|
+
export declare const retryAfterOf: (headers: Headers) => {
|
|
125
|
+
retryAfterMs?: number;
|
|
126
|
+
};
|
|
100
127
|
export declare const createHttpClient: (config?: HttpClientConfig) => HttpClient;
|
|
101
128
|
/**
|
|
102
129
|
* Read at most `maxBytes`, stopping as they arrive, and abandon the rest.
|
package/dist/toolkit/http.js
CHANGED
|
@@ -56,6 +56,52 @@ export const REQUEST_TIMEOUT_MS = 10_000;
|
|
|
56
56
|
* choose which credential to spend and where to send it. `host` forges the request target past the policy check.
|
|
57
57
|
*/
|
|
58
58
|
const RESERVED_HEADERS = new Set(["authorization", "cookie", "proxy-authorization", "host", "content-length"]);
|
|
59
|
+
/**
|
|
60
|
+
* `Retry-After` as milliseconds, in whichever of its two forms the server used.
|
|
61
|
+
*
|
|
62
|
+
* Returns a spreadable object rather than a number so an absent or unparseable header adds no key at all —
|
|
63
|
+
* `retryAfterMs: undefined` and "no retry hint" are different things to a consumer reading the field.
|
|
64
|
+
*
|
|
65
|
+
* A date in the past yields `0` rather than a negative delay, and an absurd value is capped: a vendor asking
|
|
66
|
+
* for a six-hour wait inside a request path is a vendor to give up on, not to obey.
|
|
67
|
+
*/
|
|
68
|
+
const MAX_RETRY_AFTER_MS = 5 * 60_000;
|
|
69
|
+
/**
|
|
70
|
+
* The response headers a failure carries, by prefix.
|
|
71
|
+
*
|
|
72
|
+
* These are the shapes vendors actually use: `x-ratelimit-*` (GitHub, Reddit), `x-rate-limit-*` (X's
|
|
73
|
+
* per-15-minute window), `x-user-limit-24hour-*` / `x-app-limit-24hour-*` (X's daily caps, which are a
|
|
74
|
+
* different limit with a different remedy), and `x-ms-ratelimit-*` (Azure Resource Manager, whose
|
|
75
|
+
* `remaining-subscription-reads` counts *down* rather than reporting a ceiling — the only one of these that
|
|
76
|
+
* lets a client see a throttle approaching instead of discovering it).
|
|
77
|
+
*/
|
|
78
|
+
const RATE_LIMIT_HEADERS = [
|
|
79
|
+
"retry-after",
|
|
80
|
+
"x-ratelimit-",
|
|
81
|
+
"x-rate-limit-",
|
|
82
|
+
"x-user-limit-",
|
|
83
|
+
"x-app-limit-",
|
|
84
|
+
"x-ms-ratelimit-",
|
|
85
|
+
];
|
|
86
|
+
export const rateLimitHeadersOf = (headers) => {
|
|
87
|
+
const kept = {};
|
|
88
|
+
headers.forEach((value, name) => {
|
|
89
|
+
const lower = name.toLowerCase();
|
|
90
|
+
if (RATE_LIMIT_HEADERS.some((prefix) => lower === prefix || lower.startsWith(prefix)))
|
|
91
|
+
kept[lower] = value;
|
|
92
|
+
});
|
|
93
|
+
return Object.keys(kept).length === 0 ? {} : { headers: kept };
|
|
94
|
+
};
|
|
95
|
+
export const retryAfterOf = (headers) => {
|
|
96
|
+
const raw = headers.get("retry-after");
|
|
97
|
+
if (raw === null || raw.trim() === "")
|
|
98
|
+
return {};
|
|
99
|
+
const seconds = Number(raw.trim());
|
|
100
|
+
const ms = Number.isFinite(seconds) ? seconds * 1000 : Date.parse(raw.trim()) - Date.now();
|
|
101
|
+
if (!Number.isFinite(ms))
|
|
102
|
+
return {};
|
|
103
|
+
return { retryAfterMs: Math.min(Math.max(Math.round(ms), 0), MAX_RETRY_AFTER_MS) };
|
|
104
|
+
};
|
|
59
105
|
export const createHttpClient = (config = {}) => {
|
|
60
106
|
const policy = config.policy ?? DEFAULT_EGRESS_POLICY;
|
|
61
107
|
const doFetch = config.fetchImpl ?? fetch;
|
|
@@ -124,6 +170,8 @@ export const createHttpClient = (config = {}) => {
|
|
|
124
170
|
kind: "http-error",
|
|
125
171
|
status: response.status,
|
|
126
172
|
reason: `That URL returned ${response.status}${text.trim() === "" ? "" : `: ${text.slice(0, 500)}`}`,
|
|
173
|
+
...retryAfterOf(response.headers),
|
|
174
|
+
...rateLimitHeadersOf(response.headers),
|
|
127
175
|
};
|
|
128
176
|
}
|
|
129
177
|
const body = input.fence === false
|
package/dist/toolkit/index.d.ts
CHANGED
|
@@ -10,8 +10,34 @@
|
|
|
10
10
|
* interesting behaviour of an outbound tool — what it refuses, where it stops reading — should be provable
|
|
11
11
|
* without constructing a run.
|
|
12
12
|
*/
|
|
13
|
-
export { DEFAULT_EGRESS_POLICY, MAX_RESPONSE_BYTES, REQUEST_TIMEOUT_MS, createHttpClient, readBounded, } from "./http.js";
|
|
13
|
+
export { DEFAULT_EGRESS_POLICY, MAX_RESPONSE_BYTES, REQUEST_TIMEOUT_MS, createHttpClient, readBounded, rateLimitHeadersOf, retryAfterOf, } from "./http.js";
|
|
14
14
|
export type { HttpClient, HttpClientConfig, HttpFailure, HttpOutcome, HttpRequest, HttpSuccess } from "./http.js";
|
|
15
|
+
export { createVendorTransport } from "./vendor.js";
|
|
16
|
+
/**
|
|
17
|
+
* RFC 5322 message building, shared — REQ-056 (#240), task #241.
|
|
18
|
+
*
|
|
19
|
+
* It began in `tools-google` and moved here when `tools-email` needed the same encoder. The header rules are
|
|
20
|
+
* the kind that fail *silently* — an unencoded non-ASCII subject arrives as mojibake, a header with a line
|
|
21
|
+
* break becomes two headers and the second copies the mail to somebody — so there is one implementation, and
|
|
22
|
+
* `tools-google` re-exports it rather than keeping a copy.
|
|
23
|
+
*/
|
|
24
|
+
export { assertHeaderSafe, boundaryFor, buildMessage, encodeHeader, HeaderInjectionError, headerOf, stripBcc, } from "./mime.js";
|
|
25
|
+
export type { Attachment, OutgoingMessage } from "./mime.js";
|
|
26
|
+
/**
|
|
27
|
+
* The SSRF-hardened fetch, shared — REQ-055 (#237), tasks #238 and #239.
|
|
28
|
+
*
|
|
29
|
+
* It was written for `tools-scrape` and lives here because `tools-browser` needs the *same* implementation
|
|
30
|
+
* rather than a second copy: a browser navigating to `169.254.169.254` is the same hole as a fetch doing it,
|
|
31
|
+
* and two copies of an address classifier is how one of them ends up missing the IPv6-mapped forms.
|
|
32
|
+
*
|
|
33
|
+
* **Not a replacement for `mcp/egress.ts`, which is deliberately stricter.** That one denies *every* IPv6
|
|
34
|
+
* literal unless explicitly allowed, which is right for an MCP endpoint an operator configures once and wrong
|
|
35
|
+
* for scraping the open web, where a great many real sites are v6-only. They differ because the questions
|
|
36
|
+
* differ, and merging them would mean picking one answer for both.
|
|
37
|
+
*/
|
|
38
|
+
export { BlockedError, DEFAULT_USER_AGENT, isPrivateAddress, isPrivateV4, isPrivateV6, nodeTransport, refuseUrl, resolvePublicly, safeFetch, systemResolve, } from "./ssrf.js";
|
|
39
|
+
export type { Refusal, Resolve, SafeFetchOptions, SafeResponse, SafeTransport } from "./ssrf.js";
|
|
40
|
+
export type { VendorClassifier, VendorFailure, VendorTransport, VendorTransportConfig } from "./vendor.js";
|
|
15
41
|
export { DEFAULT_SEARCH_LIMIT, MAX_SNIPPET_CHARS, createFetchJson, createFetchPage, createWebSearch, htmlToText, } from "./web.js";
|
|
16
42
|
export type { JsonResult, PageResult, SearchHit, SearchOutcome, SearchProvider } from "./web.js";
|
|
17
43
|
export { MAX_CELL_CHARS, MAX_CSV_ROWS, MAX_SQL_ROWS, createSqlQuery, createSqlSchema, parseCsv, queryJson, } from "./data.js";
|
package/dist/toolkit/index.js
CHANGED
|
@@ -10,7 +10,30 @@
|
|
|
10
10
|
* interesting behaviour of an outbound tool — what it refuses, where it stops reading — should be provable
|
|
11
11
|
* without constructing a run.
|
|
12
12
|
*/
|
|
13
|
-
export { DEFAULT_EGRESS_POLICY, MAX_RESPONSE_BYTES, REQUEST_TIMEOUT_MS, createHttpClient, readBounded, } from "./http.js";
|
|
13
|
+
export { DEFAULT_EGRESS_POLICY, MAX_RESPONSE_BYTES, REQUEST_TIMEOUT_MS, createHttpClient, readBounded, rateLimitHeadersOf, retryAfterOf, } from "./http.js";
|
|
14
|
+
export { createVendorTransport } from "./vendor.js";
|
|
15
|
+
/**
|
|
16
|
+
* RFC 5322 message building, shared — REQ-056 (#240), task #241.
|
|
17
|
+
*
|
|
18
|
+
* It began in `tools-google` and moved here when `tools-email` needed the same encoder. The header rules are
|
|
19
|
+
* the kind that fail *silently* — an unencoded non-ASCII subject arrives as mojibake, a header with a line
|
|
20
|
+
* break becomes two headers and the second copies the mail to somebody — so there is one implementation, and
|
|
21
|
+
* `tools-google` re-exports it rather than keeping a copy.
|
|
22
|
+
*/
|
|
23
|
+
export { assertHeaderSafe, boundaryFor, buildMessage, encodeHeader, HeaderInjectionError, headerOf, stripBcc, } from "./mime.js";
|
|
24
|
+
/**
|
|
25
|
+
* The SSRF-hardened fetch, shared — REQ-055 (#237), tasks #238 and #239.
|
|
26
|
+
*
|
|
27
|
+
* It was written for `tools-scrape` and lives here because `tools-browser` needs the *same* implementation
|
|
28
|
+
* rather than a second copy: a browser navigating to `169.254.169.254` is the same hole as a fetch doing it,
|
|
29
|
+
* and two copies of an address classifier is how one of them ends up missing the IPv6-mapped forms.
|
|
30
|
+
*
|
|
31
|
+
* **Not a replacement for `mcp/egress.ts`, which is deliberately stricter.** That one denies *every* IPv6
|
|
32
|
+
* literal unless explicitly allowed, which is right for an MCP endpoint an operator configures once and wrong
|
|
33
|
+
* for scraping the open web, where a great many real sites are v6-only. They differ because the questions
|
|
34
|
+
* differ, and merging them would mean picking one answer for both.
|
|
35
|
+
*/
|
|
36
|
+
export { BlockedError, DEFAULT_USER_AGENT, isPrivateAddress, isPrivateV4, isPrivateV6, nodeTransport, refuseUrl, resolvePublicly, safeFetch, systemResolve, } from "./ssrf.js";
|
|
14
37
|
export { DEFAULT_SEARCH_LIMIT, MAX_SNIPPET_CHARS, createFetchJson, createFetchPage, createWebSearch, htmlToText, } from "./web.js";
|
|
15
38
|
export { MAX_CELL_CHARS, MAX_CSV_ROWS, MAX_SQL_ROWS, createSqlQuery, createSqlSchema, parseCsv, queryJson, } from "./data.js";
|
|
16
39
|
export { MAX_EXPRESSION_CHARS, calculate, currentTime } from "./compute.js";
|