@retinue/agentkit 0.2.0 → 0.3.1
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 +72 -0
- package/dist/server/bin.js +304 -0
- package/dist/server/boot.d.ts +20 -2
- package/dist/server/boot.js +29 -3
- package/dist/server/cli-worker.js +6 -3
- package/dist/server/cli.d.ts +13 -0
- package/dist/server/cli.js +10 -5
- package/dist/server/config.d.ts +11 -1
- package/dist/server/config.js +18 -0
- package/dist/server/doctor.d.ts +85 -0
- package/dist/server/doctor.js +280 -0
- package/dist/server/pool.d.ts +47 -0
- package/dist/server/pool.js +73 -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,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An inbound MCP server — REQ-059 (#249), task #250.
|
|
3
|
+
*
|
|
4
|
+
* `backend/src/mcp/` is the **outbound** direction: a tenant registers their MCP server and this platform
|
|
5
|
+
* consumes it. Its own header says so, and points at an inbound server that lives in the *old Chorus
|
|
6
|
+
* repository* — not in this package. So `@retinue/agentkit` could consume an MCP server and could not be one,
|
|
7
|
+
* and a deployment's tools were unreachable from Claude Code, Claude Desktop, Cursor and every other MCP client.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here re-implements a capability. The registry already does authorization, the tenant's toolset, the
|
|
10
|
+
* approval gate, validation, idempotency and audit attribution; this is a protocol surface in front of it, and
|
|
11
|
+
* every call goes through `registry.execute` exactly as an agent's would.
|
|
12
|
+
*
|
|
13
|
+
* ## The trust direction inverts, and that is the thing to get right
|
|
14
|
+
*
|
|
15
|
+
* Outbound, `mcp/index.ts` establishes the rule: a remote server's `readOnlyHint`/`destructiveHint` are
|
|
16
|
+
* *advisory and untrusted*, and "a remote server cannot talk its way down to" a weaker effect. Inbound, **this
|
|
17
|
+
* package is the remote server**. Two consequences, and both are enforced rather than intended:
|
|
18
|
+
*
|
|
19
|
+
* 1. What it advertises must be what it enforces. The annotations are derived from `ToolEffect` by one
|
|
20
|
+
* function, so they cannot drift from the effect the registry acts on.
|
|
21
|
+
* 2. Nothing the client sends is trusted — not an effect, not a hint, not a claim about a prior approval. The
|
|
22
|
+
* client supplies a tool name and arguments; everything else comes from the context the *host* built.
|
|
23
|
+
*
|
|
24
|
+
* ## Authentication is structural
|
|
25
|
+
*
|
|
26
|
+
* `createMcpToolServer` takes a resolved `ExecutionContext`. There is no `authenticate` callback to forget and
|
|
27
|
+
* no default: a host that has not authenticated has nothing to pass, so it cannot construct a server. That is
|
|
28
|
+
* the same decision `server/main.ts` makes — "a permissive default would serve an open API to anyone who forgot
|
|
29
|
+
* to set it" — expressed in the type instead of in a runtime check, because this surface gets exposed to the
|
|
30
|
+
* internet and a runtime check can be skipped.
|
|
31
|
+
*
|
|
32
|
+
* One server per authenticated session: a stdio server is launched per client and carries that client's
|
|
33
|
+
* identity for its lifetime; an HTTP mount builds one per authenticated request.
|
|
34
|
+
*/
|
|
35
|
+
import type { ExecutionContext } from "../core/context.js";
|
|
36
|
+
import type { ToolDescriptor, ToolEffect, ToolRegistry } from "../tools/index.js";
|
|
37
|
+
/**
|
|
38
|
+
* The MCP SDK, imported for its types only where possible.
|
|
39
|
+
*
|
|
40
|
+
* An **optional peer dependency**, following `vitest` on `./testing`: a consumer who never mounts an MCP server
|
|
41
|
+
* never installs it, and the only path to this module is its own subpath.
|
|
42
|
+
*/
|
|
43
|
+
export type McpServerLike = {
|
|
44
|
+
/**
|
|
45
|
+
* `any`, and it is the correct choice rather than a shortcut.
|
|
46
|
+
*
|
|
47
|
+
* The SDK's real signature is generic —
|
|
48
|
+
* `<T extends AnyObjectSchema>(schema: T, handler: (request: SchemaOutput<T>, extra: RequestHandlerExtra) => …)`
|
|
49
|
+
* — and this package cannot restate those generics without importing the SDK, which would stop it being an
|
|
50
|
+
* optional peer. A structural type with `unknown`/`never` parameters looks stricter and is **not assignable
|
|
51
|
+
* from** the SDK's `Server`, so the first version of this failed to typecheck at the only call site anybody
|
|
52
|
+
* will write. `check:consumer` caught it by compiling the documented sample against the packed tarball:
|
|
53
|
+
*
|
|
54
|
+
* Argument of type 'Server<…>' is not assignable to parameter of type 'McpServerLike'
|
|
55
|
+
* Type 'any' is not assignable to type 'never'
|
|
56
|
+
*
|
|
57
|
+
* A type nothing real satisfies is worse than a permissive one.
|
|
58
|
+
*/
|
|
59
|
+
setRequestHandler(schema: any,
|
|
60
|
+
/**
|
|
61
|
+
* The result is an object, not `unknown` — the second half of the same lesson.
|
|
62
|
+
*
|
|
63
|
+
* The SDK's handler must return a `ServerResult`, and `Promise<unknown>` is *wider* than that, so a `Server`
|
|
64
|
+
* was still not assignable even after the parameters were relaxed. A permissive object shape satisfies it
|
|
65
|
+
* in both directions.
|
|
66
|
+
*/
|
|
67
|
+
handler: (request: any, extra?: any) => Promise<Record<string, unknown>>): void;
|
|
68
|
+
connect(transport: any): Promise<void>;
|
|
69
|
+
};
|
|
70
|
+
export type McpToolAnnotations = {
|
|
71
|
+
readonly readOnlyHint: boolean;
|
|
72
|
+
readonly destructiveHint: boolean;
|
|
73
|
+
readonly idempotentHint: boolean;
|
|
74
|
+
/** True when the tool reaches something outside this deployment. */
|
|
75
|
+
readonly openWorldHint: boolean;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* `ToolEffect` → MCP annotations, in **one** place — AC-7.
|
|
79
|
+
*
|
|
80
|
+
* One function, so the advertised hint and the enforced effect cannot drift: a test asserts this mapping over
|
|
81
|
+
* every effect, and the exposed set is checked against it. Two tables would be two chances to disagree, and the
|
|
82
|
+
* disagreement would be a client told a tool is read-only calling something that writes.
|
|
83
|
+
*
|
|
84
|
+
* `destructive` is the only `destructiveHint`, and `read` the only `readOnlyHint` — deliberately narrow.
|
|
85
|
+
* Over-claiming read-only is the dangerous direction: a client may skip a confirmation on it.
|
|
86
|
+
*/
|
|
87
|
+
export declare const annotationsFor: (effect: ToolEffect) => McpToolAnnotations;
|
|
88
|
+
/**
|
|
89
|
+
* A tool's input schema, in the shape MCP requires — found by a real client.
|
|
90
|
+
*
|
|
91
|
+
* MCP's `tools/list` demands `inputSchema.type === "object"`, and the SDK's *client* validates it: a tool whose
|
|
92
|
+
* descriptor holds a Zod schema made the whole listing fail with `expected "object"` at one index. So the
|
|
93
|
+
* failure was not "one tool looks odd", it was **no tools at all**, from one non-conforming entry.
|
|
94
|
+
*
|
|
95
|
+
* Three cases, in order of fidelity:
|
|
96
|
+
*
|
|
97
|
+
* 1. Already a JSON-Schema object — pass it through unchanged. It is what execution validates against, so
|
|
98
|
+
* advertising anything else would advertise a contract nothing enforces.
|
|
99
|
+
* 2. A Zod schema — convert it. `defineTool` documents `inputSchema` as "Zod schema or JSON-schema object", so
|
|
100
|
+
* both are legitimate and a client deserves the real one either way.
|
|
101
|
+
* 3. Anything else — `{ type: "object" }`, permissive. Deliberately the *weaker* advertisement rather than a
|
|
102
|
+
* refusal to list: the registry still validates the real schema on execution, so the failure direction is a
|
|
103
|
+
* rejected call rather than an accepted bad one.
|
|
104
|
+
*/
|
|
105
|
+
export declare const toMcpInputSchema: (schema: unknown) => Record<string, unknown>;
|
|
106
|
+
/** What a client is shown for one tool. */
|
|
107
|
+
export declare const describeForMcp: (descriptor: ToolDescriptor) => {
|
|
108
|
+
name: string;
|
|
109
|
+
title: string;
|
|
110
|
+
description: string;
|
|
111
|
+
inputSchema: Record<string, unknown>;
|
|
112
|
+
annotations: McpToolAnnotations;
|
|
113
|
+
};
|
|
114
|
+
export type McpToolServerDeps = {
|
|
115
|
+
readonly registry: ToolRegistry;
|
|
116
|
+
/**
|
|
117
|
+
* The authenticated caller. **Required, and there is no default.**
|
|
118
|
+
*
|
|
119
|
+
* A host that has not authenticated cannot construct a server, which is the guarantee expressed in the type
|
|
120
|
+
* rather than in a check somebody can skip.
|
|
121
|
+
*/
|
|
122
|
+
readonly context: ExecutionContext;
|
|
123
|
+
readonly serverInfo?: {
|
|
124
|
+
readonly name: string;
|
|
125
|
+
readonly version: string;
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* The result of a `tools/call`, in MCP's shape.
|
|
130
|
+
*
|
|
131
|
+
* A refusal is `isError: true` with the reason as text — **not** a protocol error. That distinction matters: a
|
|
132
|
+
* protocol error tells the client the server is broken, and a tool refusal is a normal outcome the model should
|
|
133
|
+
* see and respond to. It is the same decision `streamModelTurn` makes for `tool-error`.
|
|
134
|
+
*/
|
|
135
|
+
export declare const toMcpResult: (outcome: {
|
|
136
|
+
readonly ok: true;
|
|
137
|
+
readonly data: unknown;
|
|
138
|
+
} | {
|
|
139
|
+
readonly ok: false;
|
|
140
|
+
readonly error: {
|
|
141
|
+
readonly code: string;
|
|
142
|
+
readonly message: string;
|
|
143
|
+
};
|
|
144
|
+
}) => {
|
|
145
|
+
content: {
|
|
146
|
+
type: "text";
|
|
147
|
+
text: string;
|
|
148
|
+
}[];
|
|
149
|
+
isError?: undefined;
|
|
150
|
+
} | {
|
|
151
|
+
content: {
|
|
152
|
+
type: "text";
|
|
153
|
+
text: string;
|
|
154
|
+
}[];
|
|
155
|
+
isError: true;
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Registers the two handlers on an SDK `Server`.
|
|
159
|
+
*
|
|
160
|
+
* Takes the server rather than constructing it, so the SDK stays an optional peer of this package: the host
|
|
161
|
+
* imports `@modelcontextprotocol/sdk`, builds a `Server`, and hands it here. That also leaves the host in
|
|
162
|
+
* charge of the transport, which is where the authentication story differs between stdio and HTTP.
|
|
163
|
+
*/
|
|
164
|
+
export declare const registerRetinueTools: (server: McpServerLike, schemas: {
|
|
165
|
+
readonly listTools: unknown;
|
|
166
|
+
readonly callTool: unknown;
|
|
167
|
+
}, deps: McpToolServerDeps) => void;
|
|
168
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An inbound MCP server — REQ-059 (#249), task #250.
|
|
3
|
+
*
|
|
4
|
+
* `backend/src/mcp/` is the **outbound** direction: a tenant registers their MCP server and this platform
|
|
5
|
+
* consumes it. Its own header says so, and points at an inbound server that lives in the *old Chorus
|
|
6
|
+
* repository* — not in this package. So `@retinue/agentkit` could consume an MCP server and could not be one,
|
|
7
|
+
* and a deployment's tools were unreachable from Claude Code, Claude Desktop, Cursor and every other MCP client.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here re-implements a capability. The registry already does authorization, the tenant's toolset, the
|
|
10
|
+
* approval gate, validation, idempotency and audit attribution; this is a protocol surface in front of it, and
|
|
11
|
+
* every call goes through `registry.execute` exactly as an agent's would.
|
|
12
|
+
*
|
|
13
|
+
* ## The trust direction inverts, and that is the thing to get right
|
|
14
|
+
*
|
|
15
|
+
* Outbound, `mcp/index.ts` establishes the rule: a remote server's `readOnlyHint`/`destructiveHint` are
|
|
16
|
+
* *advisory and untrusted*, and "a remote server cannot talk its way down to" a weaker effect. Inbound, **this
|
|
17
|
+
* package is the remote server**. Two consequences, and both are enforced rather than intended:
|
|
18
|
+
*
|
|
19
|
+
* 1. What it advertises must be what it enforces. The annotations are derived from `ToolEffect` by one
|
|
20
|
+
* function, so they cannot drift from the effect the registry acts on.
|
|
21
|
+
* 2. Nothing the client sends is trusted — not an effect, not a hint, not a claim about a prior approval. The
|
|
22
|
+
* client supplies a tool name and arguments; everything else comes from the context the *host* built.
|
|
23
|
+
*
|
|
24
|
+
* ## Authentication is structural
|
|
25
|
+
*
|
|
26
|
+
* `createMcpToolServer` takes a resolved `ExecutionContext`. There is no `authenticate` callback to forget and
|
|
27
|
+
* no default: a host that has not authenticated has nothing to pass, so it cannot construct a server. That is
|
|
28
|
+
* the same decision `server/main.ts` makes — "a permissive default would serve an open API to anyone who forgot
|
|
29
|
+
* to set it" — expressed in the type instead of in a runtime check, because this surface gets exposed to the
|
|
30
|
+
* internet and a runtime check can be skipped.
|
|
31
|
+
*
|
|
32
|
+
* One server per authenticated session: a stdio server is launched per client and carries that client's
|
|
33
|
+
* identity for its lifetime; an HTTP mount builds one per authenticated request.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* `zod/v4`'s converter. A static import, and it costs nothing: `zod` is already a hard dependency of this
|
|
37
|
+
* package, and the only path to this module is its own optional subpath.
|
|
38
|
+
*/
|
|
39
|
+
import { toJSONSchema } from "zod/v4/core";
|
|
40
|
+
/**
|
|
41
|
+
* `ToolEffect` → MCP annotations, in **one** place — AC-7.
|
|
42
|
+
*
|
|
43
|
+
* One function, so the advertised hint and the enforced effect cannot drift: a test asserts this mapping over
|
|
44
|
+
* every effect, and the exposed set is checked against it. Two tables would be two chances to disagree, and the
|
|
45
|
+
* disagreement would be a client told a tool is read-only calling something that writes.
|
|
46
|
+
*
|
|
47
|
+
* `destructive` is the only `destructiveHint`, and `read` the only `readOnlyHint` — deliberately narrow.
|
|
48
|
+
* Over-claiming read-only is the dangerous direction: a client may skip a confirmation on it.
|
|
49
|
+
*/
|
|
50
|
+
export const annotationsFor = (effect) => ({
|
|
51
|
+
readOnlyHint: effect === "read",
|
|
52
|
+
destructiveHint: effect === "destructive",
|
|
53
|
+
// Only a read is safely repeatable without a key. Everything else declares itself non-idempotent, which is
|
|
54
|
+
// what makes a client ask before retrying.
|
|
55
|
+
idempotentHint: effect === "read",
|
|
56
|
+
openWorldHint: effect === "external-write" || effect === "destructive",
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* A tool's input schema, in the shape MCP requires — found by a real client.
|
|
60
|
+
*
|
|
61
|
+
* MCP's `tools/list` demands `inputSchema.type === "object"`, and the SDK's *client* validates it: a tool whose
|
|
62
|
+
* descriptor holds a Zod schema made the whole listing fail with `expected "object"` at one index. So the
|
|
63
|
+
* failure was not "one tool looks odd", it was **no tools at all**, from one non-conforming entry.
|
|
64
|
+
*
|
|
65
|
+
* Three cases, in order of fidelity:
|
|
66
|
+
*
|
|
67
|
+
* 1. Already a JSON-Schema object — pass it through unchanged. It is what execution validates against, so
|
|
68
|
+
* advertising anything else would advertise a contract nothing enforces.
|
|
69
|
+
* 2. A Zod schema — convert it. `defineTool` documents `inputSchema` as "Zod schema or JSON-schema object", so
|
|
70
|
+
* both are legitimate and a client deserves the real one either way.
|
|
71
|
+
* 3. Anything else — `{ type: "object" }`, permissive. Deliberately the *weaker* advertisement rather than a
|
|
72
|
+
* refusal to list: the registry still validates the real schema on execution, so the failure direction is a
|
|
73
|
+
* rejected call rather than an accepted bad one.
|
|
74
|
+
*/
|
|
75
|
+
export const toMcpInputSchema = (schema) => {
|
|
76
|
+
if (schema !== null && typeof schema === "object") {
|
|
77
|
+
const asJson = schema;
|
|
78
|
+
if (asJson.type === "object")
|
|
79
|
+
return schema;
|
|
80
|
+
const zodLike = schema;
|
|
81
|
+
if (typeof zodLike.safeParse === "function" || zodLike["~standard"] !== undefined) {
|
|
82
|
+
try {
|
|
83
|
+
const converted = toJSONSchema(schema);
|
|
84
|
+
if (converted.type === "object")
|
|
85
|
+
return converted;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// A schema the converter cannot express — fall through to the permissive object rather than failing the
|
|
89
|
+
// whole listing for one tool.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { type: "object" };
|
|
94
|
+
};
|
|
95
|
+
/** What a client is shown for one tool. */
|
|
96
|
+
export const describeForMcp = (descriptor) => ({
|
|
97
|
+
name: descriptor.name,
|
|
98
|
+
title: descriptor.label,
|
|
99
|
+
description: descriptor.description,
|
|
100
|
+
inputSchema: toMcpInputSchema(descriptor.inputSchema),
|
|
101
|
+
annotations: annotationsFor(descriptor.effect),
|
|
102
|
+
});
|
|
103
|
+
/**
|
|
104
|
+
* The result of a `tools/call`, in MCP's shape.
|
|
105
|
+
*
|
|
106
|
+
* A refusal is `isError: true` with the reason as text — **not** a protocol error. That distinction matters: a
|
|
107
|
+
* protocol error tells the client the server is broken, and a tool refusal is a normal outcome the model should
|
|
108
|
+
* see and respond to. It is the same decision `streamModelTurn` makes for `tool-error`.
|
|
109
|
+
*/
|
|
110
|
+
export const toMcpResult = (outcome) => outcome.ok
|
|
111
|
+
? { content: [{ type: "text", text: JSON.stringify(outcome.data) }] }
|
|
112
|
+
: {
|
|
113
|
+
content: [{ type: "text", text: `${outcome.error.code}: ${outcome.error.message}` }],
|
|
114
|
+
isError: true,
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Registers the two handlers on an SDK `Server`.
|
|
118
|
+
*
|
|
119
|
+
* Takes the server rather than constructing it, so the SDK stays an optional peer of this package: the host
|
|
120
|
+
* imports `@modelcontextprotocol/sdk`, builds a `Server`, and hands it here. That also leaves the host in
|
|
121
|
+
* charge of the transport, which is where the authentication story differs between stdio and HTTP.
|
|
122
|
+
*/
|
|
123
|
+
export const registerRetinueTools = (server, schemas, deps) => {
|
|
124
|
+
server.setRequestHandler(schemas.listTools, async () => {
|
|
125
|
+
/**
|
|
126
|
+
* The caller's **authorized** tools — never the whole registry.
|
|
127
|
+
*
|
|
128
|
+
* `listAuthorized` applies the tenant's toolset and the agent policy before authorization, so a tool this
|
|
129
|
+
* principal may not use is absent rather than present-and-refused. A client that cannot see a tool cannot
|
|
130
|
+
* be tempted by it, and the list is also the only place this platform states what it can do.
|
|
131
|
+
*/
|
|
132
|
+
const descriptors = await deps.registry.listAuthorized(deps.context);
|
|
133
|
+
return { tools: descriptors.map(describeForMcp) };
|
|
134
|
+
});
|
|
135
|
+
server.setRequestHandler(schemas.callTool, async (request) => {
|
|
136
|
+
const params = request.params ?? {};
|
|
137
|
+
const name = typeof params.name === "string" ? params.name : "";
|
|
138
|
+
/**
|
|
139
|
+
* Straight through `registry.execute` — AC-3.
|
|
140
|
+
*
|
|
141
|
+
* Everything the registry does for an agent's call happens here for the same reason: authorization is
|
|
142
|
+
* re-checked, the tenant's toolset applies, the approval gate refuses a gated tool, arguments are validated
|
|
143
|
+
* against the advertised schema, idempotency is enforced and the audit row is written. A branch that called
|
|
144
|
+
* a tool directly would be a way around all of it, reachable by name from a client.
|
|
145
|
+
*/
|
|
146
|
+
try {
|
|
147
|
+
const outcome = await deps.registry.execute(deps.context, { name, input: params.arguments ?? {} });
|
|
148
|
+
return toMcpResult(outcome);
|
|
149
|
+
}
|
|
150
|
+
catch (thrown) {
|
|
151
|
+
/**
|
|
152
|
+
* A refusal that **throws** is still a tool refusal — found by a real client, not by a fake.
|
|
153
|
+
*
|
|
154
|
+
* `registry.execute` returns `{ ok: false }` for most failures and *throws* for an unauthorized tool
|
|
155
|
+
* (`assertToolAuthorized`). Without this catch the SDK turned that into JSON-RPC `-32603`, so a client
|
|
156
|
+
* asking for a tool it may not have was told **the server is broken** rather than that it was refused.
|
|
157
|
+
* The distinction is the whole reason `isError` exists: a protocol error is not something a model can
|
|
158
|
+
* respond to, and it invites a retry against a server that is working correctly.
|
|
159
|
+
*
|
|
160
|
+
* The message only, never the thrown object — a stack or a cause chain routinely carries a URL with a
|
|
161
|
+
* token in it, and this crosses a process boundary to a client we do not control. Same rule as #143's
|
|
162
|
+
* `recordError` and `streamModelTurn`'s `tool-error`.
|
|
163
|
+
*/
|
|
164
|
+
const error = thrown;
|
|
165
|
+
return toMcpResult({
|
|
166
|
+
ok: false,
|
|
167
|
+
error: {
|
|
168
|
+
code: typeof error.code === "string" ? error.code : "internal",
|
|
169
|
+
message: typeof error.message === "string" ? error.message : "the call was refused",
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity and relationship extraction through a language model — REQ-064 (#270), task #271.
|
|
3
|
+
*
|
|
4
|
+
* Lives in `models/` for the reason `vision.ts` does: boundary rule R3 keeps the Vercel AI SDK here, so the
|
|
5
|
+
* knowledge layer calls `extractGraph` and never sees `generateText`.
|
|
6
|
+
*
|
|
7
|
+
* ## Why the output is parsed leniently rather than schema-constrained
|
|
8
|
+
*
|
|
9
|
+
* The obvious approach is structured output with a JSON schema. Two measured facts in this repository say
|
|
10
|
+
* otherwise: `experimental_output` is silently ignored by `ai@7` (the option is `output`), and
|
|
11
|
+
* `jsonSchema().validate` is `undefined`, so a JSON schema is refused for structured output outright. Both were
|
|
12
|
+
* found by live calls after typechecking cleanly — the exact failure mode where a wrong option name does
|
|
13
|
+
* nothing at all.
|
|
14
|
+
*
|
|
15
|
+
* So the prompt asks for JSON, and `parseExtraction` is deliberately forgiving: it finds the JSON in whatever
|
|
16
|
+
* the model wrapped it in. That is not a workaround for a broken schema mechanism, it is the honest shape for
|
|
17
|
+
* a call whose output cannot be constrained — and it is *safe* because `sanitiseExtraction` downstream treats
|
|
18
|
+
* everything here as untrusted anyway. A chunk whose extraction is unusable contributes nothing; it never
|
|
19
|
+
* corrupts the graph.
|
|
20
|
+
*/
|
|
21
|
+
import { type LanguageModel } from "ai";
|
|
22
|
+
/**
|
|
23
|
+
* What the model is asked for.
|
|
24
|
+
*
|
|
25
|
+
* Three things in this prompt are load-bearing and should not be trimmed:
|
|
26
|
+
*
|
|
27
|
+
* - **"only what this text states"** — extraction's characteristic failure is inventing plausible relationships
|
|
28
|
+
* between things that merely co-occur, and those are the edges a traversal follows most confidently.
|
|
29
|
+
* - **"use exactly the names you listed"** — an edge naming an endpoint that was not extracted is dropped by
|
|
30
|
+
* `sanitiseExtraction`, because repairing it would invent a provenance nobody asserted. Asking for
|
|
31
|
+
* consistency up front is much cheaper than losing the edge.
|
|
32
|
+
* - **"JSON and nothing else"** — it does not always work, which is why `parseExtraction` exists, but it works
|
|
33
|
+
* often enough to matter at one call per chunk.
|
|
34
|
+
*/
|
|
35
|
+
export declare const DEFAULT_EXTRACTION_PROMPT: string;
|
|
36
|
+
export type ExtractGraphRequest = {
|
|
37
|
+
readonly model: LanguageModel;
|
|
38
|
+
readonly text: string;
|
|
39
|
+
readonly prompt?: string;
|
|
40
|
+
readonly maxOutputTokens?: number;
|
|
41
|
+
readonly abortSignal?: AbortSignal;
|
|
42
|
+
};
|
|
43
|
+
export type ExtractGraphResult = {
|
|
44
|
+
/** Whatever the model produced, already parsed but **not** validated. `sanitiseExtraction` does that. */
|
|
45
|
+
readonly extraction: {
|
|
46
|
+
readonly entities?: readonly unknown[];
|
|
47
|
+
readonly relationships?: readonly unknown[];
|
|
48
|
+
};
|
|
49
|
+
readonly usage: {
|
|
50
|
+
readonly inputTokens: number;
|
|
51
|
+
readonly outputTokens: number;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* The JSON inside whatever the model said.
|
|
56
|
+
*
|
|
57
|
+
* Three attempts, cheapest first: the whole string, a fenced block, then the outermost braces. Returns an empty
|
|
58
|
+
* extraction rather than throwing, because a chunk that produced prose is a chunk that contributes nothing —
|
|
59
|
+
* not an error that should fail an index of a document that embedded perfectly well.
|
|
60
|
+
*
|
|
61
|
+
* Exported for its own test: this is the function that decides whether a real model's output is usable, and
|
|
62
|
+
* "the model wrapped it in a fence" is not a hypothetical.
|
|
63
|
+
*/
|
|
64
|
+
export declare const parseExtraction: (text: string) => ExtractGraphResult["extraction"];
|
|
65
|
+
/**
|
|
66
|
+
* One extraction call.
|
|
67
|
+
*
|
|
68
|
+
* The usage comes back rather than being recorded here, exactly as in `describeImage`: this module has no
|
|
69
|
+
* `ExecutionContext` and no `UsageRecorder`, and reaching for either would put a billing decision inside a
|
|
70
|
+
* provider call. AC-9 needs the number, and the caller is what has the context to attribute it to.
|
|
71
|
+
*/
|
|
72
|
+
export declare const extractGraph: (req: ExtractGraphRequest) => Promise<ExtractGraphResult>;
|
|
73
|
+
//# sourceMappingURL=extraction.d.ts.map
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity and relationship extraction through a language model — REQ-064 (#270), task #271.
|
|
3
|
+
*
|
|
4
|
+
* Lives in `models/` for the reason `vision.ts` does: boundary rule R3 keeps the Vercel AI SDK here, so the
|
|
5
|
+
* knowledge layer calls `extractGraph` and never sees `generateText`.
|
|
6
|
+
*
|
|
7
|
+
* ## Why the output is parsed leniently rather than schema-constrained
|
|
8
|
+
*
|
|
9
|
+
* The obvious approach is structured output with a JSON schema. Two measured facts in this repository say
|
|
10
|
+
* otherwise: `experimental_output` is silently ignored by `ai@7` (the option is `output`), and
|
|
11
|
+
* `jsonSchema().validate` is `undefined`, so a JSON schema is refused for structured output outright. Both were
|
|
12
|
+
* found by live calls after typechecking cleanly — the exact failure mode where a wrong option name does
|
|
13
|
+
* nothing at all.
|
|
14
|
+
*
|
|
15
|
+
* So the prompt asks for JSON, and `parseExtraction` is deliberately forgiving: it finds the JSON in whatever
|
|
16
|
+
* the model wrapped it in. That is not a workaround for a broken schema mechanism, it is the honest shape for
|
|
17
|
+
* a call whose output cannot be constrained — and it is *safe* because `sanitiseExtraction` downstream treats
|
|
18
|
+
* everything here as untrusted anyway. A chunk whose extraction is unusable contributes nothing; it never
|
|
19
|
+
* corrupts the graph.
|
|
20
|
+
*/
|
|
21
|
+
import { generateText } from "ai";
|
|
22
|
+
import { AgentPlatformError } from "../core/errors.js";
|
|
23
|
+
/**
|
|
24
|
+
* What the model is asked for.
|
|
25
|
+
*
|
|
26
|
+
* Three things in this prompt are load-bearing and should not be trimmed:
|
|
27
|
+
*
|
|
28
|
+
* - **"only what this text states"** — extraction's characteristic failure is inventing plausible relationships
|
|
29
|
+
* between things that merely co-occur, and those are the edges a traversal follows most confidently.
|
|
30
|
+
* - **"use exactly the names you listed"** — an edge naming an endpoint that was not extracted is dropped by
|
|
31
|
+
* `sanitiseExtraction`, because repairing it would invent a provenance nobody asserted. Asking for
|
|
32
|
+
* consistency up front is much cheaper than losing the edge.
|
|
33
|
+
* - **"JSON and nothing else"** — it does not always work, which is why `parseExtraction` exists, but it works
|
|
34
|
+
* often enough to matter at one call per chunk.
|
|
35
|
+
*/
|
|
36
|
+
export const DEFAULT_EXTRACTION_PROMPT = [
|
|
37
|
+
"Extract the named entities and the relationships between them from the text below.",
|
|
38
|
+
"",
|
|
39
|
+
"Report only what this text states. Do not infer a relationship from two things merely appearing together,",
|
|
40
|
+
"and do not add anything you know from elsewhere.",
|
|
41
|
+
"",
|
|
42
|
+
"For each entity give a short name, a lowercase type such as person, organisation, system, concept or place,",
|
|
43
|
+
"and a one-sentence description drawn from this text.",
|
|
44
|
+
"For each relationship give the two entity names, a short lowercase type such as depends-on, owns, part-of or",
|
|
45
|
+
"reports-to, and a one-sentence description. Use exactly the names you listed as entities.",
|
|
46
|
+
"",
|
|
47
|
+
'Answer with JSON and nothing else, in this shape:',
|
|
48
|
+
'{"entities":[{"name":"","type":"","description":""}],',
|
|
49
|
+
' "relationships":[{"from":"","to":"","type":"","description":""}]}',
|
|
50
|
+
].join("\n");
|
|
51
|
+
const num = (v) => (typeof v === "number" && Number.isFinite(v) ? v : 0);
|
|
52
|
+
/**
|
|
53
|
+
* The JSON inside whatever the model said.
|
|
54
|
+
*
|
|
55
|
+
* Three attempts, cheapest first: the whole string, a fenced block, then the outermost braces. Returns an empty
|
|
56
|
+
* extraction rather than throwing, because a chunk that produced prose is a chunk that contributes nothing —
|
|
57
|
+
* not an error that should fail an index of a document that embedded perfectly well.
|
|
58
|
+
*
|
|
59
|
+
* Exported for its own test: this is the function that decides whether a real model's output is usable, and
|
|
60
|
+
* "the model wrapped it in a fence" is not a hypothetical.
|
|
61
|
+
*/
|
|
62
|
+
export const parseExtraction = (text) => {
|
|
63
|
+
const attempts = [text.trim()];
|
|
64
|
+
const fenced = /```(?:json)?\s*([\s\S]*?)```/i.exec(text);
|
|
65
|
+
if (fenced?.[1] !== undefined)
|
|
66
|
+
attempts.push(fenced[1].trim());
|
|
67
|
+
const first = text.indexOf("{");
|
|
68
|
+
const last = text.lastIndexOf("}");
|
|
69
|
+
if (first !== -1 && last > first)
|
|
70
|
+
attempts.push(text.slice(first, last + 1));
|
|
71
|
+
for (const attempt of attempts) {
|
|
72
|
+
if (attempt === "")
|
|
73
|
+
continue;
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(attempt);
|
|
76
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
77
|
+
return parsed;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Next attempt. A parse failure here is expected often enough that logging each one would be noise.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {};
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* One extraction call.
|
|
88
|
+
*
|
|
89
|
+
* The usage comes back rather than being recorded here, exactly as in `describeImage`: this module has no
|
|
90
|
+
* `ExecutionContext` and no `UsageRecorder`, and reaching for either would put a billing decision inside a
|
|
91
|
+
* provider call. AC-9 needs the number, and the caller is what has the context to attribute it to.
|
|
92
|
+
*/
|
|
93
|
+
export const extractGraph = async (req) => {
|
|
94
|
+
try {
|
|
95
|
+
const result = await generateText({
|
|
96
|
+
model: req.model,
|
|
97
|
+
messages: [{ role: "user", content: `${req.prompt ?? DEFAULT_EXTRACTION_PROMPT}\n\n---\n\n${req.text}` }],
|
|
98
|
+
...(req.maxOutputTokens === undefined ? {} : { maxOutputTokens: req.maxOutputTokens }),
|
|
99
|
+
...(req.abortSignal ? { abortSignal: req.abortSignal } : {}),
|
|
100
|
+
});
|
|
101
|
+
const usage = result.usage;
|
|
102
|
+
return {
|
|
103
|
+
extraction: parseExtraction(result.text),
|
|
104
|
+
usage: { inputTokens: num(usage?.inputTokens), outputTokens: num(usage?.outputTokens) },
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
// Wrapped so a provider's error shape does not reach the knowledge layer, which would then have to know
|
|
109
|
+
// about three SDKs to decide whether a failure is retryable. The graph indexer catches this and the chunk
|
|
110
|
+
// contributes nothing — see AC-7.
|
|
111
|
+
throw new AgentPlatformError({
|
|
112
|
+
code: "provider_unavailable",
|
|
113
|
+
message: "The model could not extract entities from that text.",
|
|
114
|
+
retryable: true,
|
|
115
|
+
}, { cause: error });
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=extraction.js.map
|
package/dist/models/index.d.ts
CHANGED
|
@@ -3,7 +3,29 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provider-neutral. Agents state a policy and a role; they never hardcode model IDs.
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* The providers that actually resolve — REQ-061 (#255), task #256.
|
|
8
|
+
*
|
|
9
|
+
* `"bedrock"` was here and threw `capability_unavailable` when selected. It is gone, and removing it rather
|
|
10
|
+
* than wiring it is the decision:
|
|
11
|
+
*
|
|
12
|
+
* - **A declared provider that throws is worse than an absent one.** It typechecks, satisfies the exhaustive
|
|
13
|
+
* `switch`, and fails at runtime for whoever selects it first — and it survived precisely because a closed
|
|
14
|
+
* union reads as complete coverage. The type system guarantees every member is *mentioned*; only a call
|
|
15
|
+
* finds out whether it is *served*.
|
|
16
|
+
* - **Wiring it could not be verified.** #256's AC-5 requires one real turn as evidence, and constructing a
|
|
17
|
+
* Bedrock model needs no network — so an unverified wiring would satisfy "resolves" while leaving its first
|
|
18
|
+
* user as its first tester. #268 settled that precedent.
|
|
19
|
+
*
|
|
20
|
+
* Nothing is lost today: nothing could select it and get a model. Adding it back is a one-line change plus a
|
|
21
|
+
* `provider-coverage.test.ts` case, and that test is what will force the verification then rather than allow
|
|
22
|
+
* the same gap again.
|
|
23
|
+
*
|
|
24
|
+
* Vertex is absent for a different reason, argued in `docs/03`: it serves the same Gemini models the `google`
|
|
25
|
+
* provider does, differing in credentials rather than model family, so a second member would make every
|
|
26
|
+
* `allowedProviders` policy name both to mean "Gemini".
|
|
27
|
+
*/
|
|
28
|
+
export declare const MODEL_PROVIDERS: readonly ["openai", "anthropic", "google", "mistral", "azure-openai", "openai-compatible"];
|
|
7
29
|
export type ModelProvider = (typeof MODEL_PROVIDERS)[number];
|
|
8
30
|
export type ModelLifecycle = "preview" | "generally-available" | "deprecated" | "retired";
|
|
9
31
|
export type InputModality = "text" | "image" | "audio" | "video" | "pdf";
|
|
@@ -12,6 +34,23 @@ export type ModelCapabilities = {
|
|
|
12
34
|
readonly structuredOutput: boolean;
|
|
13
35
|
readonly reasoning: boolean;
|
|
14
36
|
readonly nativeSearch: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* How this model caches a repeated prompt prefix — task #247.
|
|
39
|
+
*
|
|
40
|
+
* Three values because the providers genuinely differ in a way that changes what this platform must *send*,
|
|
41
|
+
* not merely what it can expect back:
|
|
42
|
+
*
|
|
43
|
+
* - `"automatic"` — the provider caches a matching prefix on its own, with no directive. OpenAI. Nothing to
|
|
44
|
+
* emit; the only thing that matters is that the prefix is byte-stable.
|
|
45
|
+
* - `"explicit"` — the provider caches only what is marked. Anthropic, via `cache_control` breakpoints. A
|
|
46
|
+
* platform that emits nothing gets no caching at all here, which is how this was losing the discount.
|
|
47
|
+
* - `"none"` — no prompt caching. Emitting a directive would be an unknown field at best and an error at worst.
|
|
48
|
+
*
|
|
49
|
+
* Optional, and absent means `"none"`: an existing catalogue entry keeps behaving exactly as it did, and a
|
|
50
|
+
* provider that does cache has to be declared rather than assumed. Assuming the other way would send
|
|
51
|
+
* breakpoints to providers that reject them.
|
|
52
|
+
*/
|
|
53
|
+
readonly promptCaching?: "automatic" | "explicit" | "none";
|
|
15
54
|
};
|
|
16
55
|
export type ModelLimits = {
|
|
17
56
|
readonly contextTokens: number;
|
package/dist/models/index.js
CHANGED
|
@@ -4,13 +4,34 @@
|
|
|
4
4
|
* Provider-neutral. Agents state a policy and a role; they never hardcode model IDs.
|
|
5
5
|
*/
|
|
6
6
|
import { AgentPlatformError } from "../core/errors.js";
|
|
7
|
+
/**
|
|
8
|
+
* The providers that actually resolve — REQ-061 (#255), task #256.
|
|
9
|
+
*
|
|
10
|
+
* `"bedrock"` was here and threw `capability_unavailable` when selected. It is gone, and removing it rather
|
|
11
|
+
* than wiring it is the decision:
|
|
12
|
+
*
|
|
13
|
+
* - **A declared provider that throws is worse than an absent one.** It typechecks, satisfies the exhaustive
|
|
14
|
+
* `switch`, and fails at runtime for whoever selects it first — and it survived precisely because a closed
|
|
15
|
+
* union reads as complete coverage. The type system guarantees every member is *mentioned*; only a call
|
|
16
|
+
* finds out whether it is *served*.
|
|
17
|
+
* - **Wiring it could not be verified.** #256's AC-5 requires one real turn as evidence, and constructing a
|
|
18
|
+
* Bedrock model needs no network — so an unverified wiring would satisfy "resolves" while leaving its first
|
|
19
|
+
* user as its first tester. #268 settled that precedent.
|
|
20
|
+
*
|
|
21
|
+
* Nothing is lost today: nothing could select it and get a model. Adding it back is a one-line change plus a
|
|
22
|
+
* `provider-coverage.test.ts` case, and that test is what will force the verification then rather than allow
|
|
23
|
+
* the same gap again.
|
|
24
|
+
*
|
|
25
|
+
* Vertex is absent for a different reason, argued in `docs/03`: it serves the same Gemini models the `google`
|
|
26
|
+
* provider does, differing in credentials rather than model family, so a second member would make every
|
|
27
|
+
* `allowedProviders` policy name both to mean "Gemini".
|
|
28
|
+
*/
|
|
7
29
|
export const MODEL_PROVIDERS = [
|
|
8
30
|
"openai",
|
|
9
31
|
"anthropic",
|
|
10
32
|
"google",
|
|
11
33
|
"mistral",
|
|
12
34
|
"azure-openai",
|
|
13
|
-
"bedrock",
|
|
14
35
|
"openai-compatible",
|
|
15
36
|
];
|
|
16
37
|
const covers = (have, need) => need.every((n) => have.includes(n));
|
package/dist/models/pricing.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export declare const computeModelCostMinorUnits: (pricing: ModelPricing, usage:
|
|
|
17
17
|
readonly inputTokens: number;
|
|
18
18
|
readonly outputTokens: number;
|
|
19
19
|
readonly cachedInputTokens?: number;
|
|
20
|
+
/** Tokens written into the cache — a subset of `inputTokens`, priced separately. See below. */
|
|
21
|
+
readonly cacheWriteTokens?: number;
|
|
20
22
|
/** Images sent with the turn — counted from the parts, not from the provider. See `NeutralUsage`. */
|
|
21
23
|
readonly imageCount?: number;
|
|
22
24
|
readonly audioSeconds?: number;
|