@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.
Files changed (209) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/audio/openai.d.ts +36 -0
  3. package/dist/adapters/audio/openai.js +136 -0
  4. package/dist/adapters/bullmq/lock.d.ts +26 -2
  5. package/dist/adapters/memory/connections.d.ts +10 -0
  6. package/dist/adapters/memory/connections.js +87 -0
  7. package/dist/adapters/memory/graph.d.ts +17 -0
  8. package/dist/adapters/memory/graph.js +267 -0
  9. package/dist/adapters/memory/index.d.ts +3 -0
  10. package/dist/adapters/memory/index.js +3 -0
  11. package/dist/adapters/memory/rate-limit.d.ts +14 -0
  12. package/dist/adapters/memory/rate-limit.js +28 -0
  13. package/dist/adapters/memory/runtime.js +0 -0
  14. package/dist/adapters/postgres/connections.d.ts +15 -0
  15. package/dist/adapters/postgres/connections.js +134 -0
  16. package/dist/adapters/postgres/graph.d.ts +24 -0
  17. package/dist/adapters/postgres/graph.js +325 -0
  18. package/dist/adapters/postgres/index.d.ts +2 -0
  19. package/dist/adapters/postgres/index.js +2 -0
  20. package/dist/adapters/postgres/migrations.js +198 -0
  21. package/dist/adapters/postgres/run-store.js +26 -2
  22. package/dist/adapters/postgres/schema.d.ts +61 -2
  23. package/dist/adapters/postgres/schema.js +95 -10
  24. package/dist/adapters/redis/index.d.ts +1 -0
  25. package/dist/adapters/redis/index.js +1 -0
  26. package/dist/adapters/redis/rate-limit.d.ts +46 -0
  27. package/dist/adapters/redis/rate-limit.js +55 -0
  28. package/dist/adapters/supabase/index.d.ts +15 -0
  29. package/dist/adapters/supabase/index.js +15 -0
  30. package/dist/adapters/supabase/rls.js +33 -0
  31. package/dist/agents/agent.d.ts +78 -1
  32. package/dist/agents/agent.js +118 -5
  33. package/dist/agents/define.js +24 -4
  34. package/dist/agents/engine.d.ts +20 -0
  35. package/dist/agents/engine.js +177 -6
  36. package/dist/audio/index.d.ts +151 -0
  37. package/dist/audio/index.js +183 -0
  38. package/dist/connections/cipher.d.ts +103 -0
  39. package/dist/connections/cipher.js +141 -0
  40. package/dist/connections/index.d.ts +156 -0
  41. package/dist/connections/index.js +72 -0
  42. package/dist/connections/oauth/client.d.ts +84 -0
  43. package/dist/connections/oauth/client.js +141 -0
  44. package/dist/connections/oauth/index.d.ts +166 -0
  45. package/dist/connections/oauth/index.js +226 -0
  46. package/dist/connections/oauth/service.d.ts +90 -0
  47. package/dist/connections/oauth/service.js +132 -0
  48. package/dist/connections/pause.d.ts +68 -0
  49. package/dist/connections/pause.js +97 -0
  50. package/dist/connections/resolver.d.ts +59 -0
  51. package/dist/connections/resolver.js +138 -0
  52. package/dist/connections/resume.d.ts +64 -0
  53. package/dist/connections/resume.js +99 -0
  54. package/dist/connections/vault-cipher.d.ts +95 -0
  55. package/dist/connections/vault-cipher.js +206 -0
  56. package/dist/core/content-parts.d.ts +18 -2
  57. package/dist/core/content-parts.js +1 -0
  58. package/dist/core/context.d.ts +25 -0
  59. package/dist/core/errors.d.ts +1 -1
  60. package/dist/core/errors.js +15 -0
  61. package/dist/core/events.d.ts +44 -2
  62. package/dist/core/events.js +31 -2
  63. package/dist/core/validation.js +19 -0
  64. package/dist/entries/connections.d.ts +10 -0
  65. package/dist/entries/connections.js +10 -0
  66. package/dist/entries/knowledge.d.ts +8 -0
  67. package/dist/entries/knowledge.js +8 -0
  68. package/dist/entries/mcp-server.d.ts +9 -0
  69. package/dist/entries/mcp-server.js +9 -0
  70. package/dist/entries/runtime.d.ts +10 -0
  71. package/dist/entries/runtime.js +8 -0
  72. package/dist/entries/testing.d.ts +28 -0
  73. package/dist/entries/testing.js +28 -0
  74. package/dist/graphql/resolvers.d.ts +3 -1
  75. package/dist/graphql/resolvers.js +14 -0
  76. package/dist/knowledge/communities.d.ts +166 -0
  77. package/dist/knowledge/communities.js +377 -0
  78. package/dist/knowledge/graph-global.d.ts +153 -0
  79. package/dist/knowledge/graph-global.js +246 -0
  80. package/dist/knowledge/graph-retrieval.d.ts +150 -0
  81. package/dist/knowledge/graph-retrieval.js +303 -0
  82. package/dist/knowledge/graph.d.ts +223 -0
  83. package/dist/knowledge/graph.js +369 -0
  84. package/dist/knowledge/index.d.ts +24 -0
  85. package/dist/knowledge/index.js +36 -1
  86. package/dist/knowledge/retrieval.d.ts +26 -1
  87. package/dist/knowledge/retrieval.js +77 -1
  88. package/dist/mcp-server/index.d.ts +168 -0
  89. package/dist/mcp-server/index.js +175 -0
  90. package/dist/models/extraction.d.ts +73 -0
  91. package/dist/models/extraction.js +118 -0
  92. package/dist/models/index.d.ts +40 -1
  93. package/dist/models/index.js +22 -1
  94. package/dist/models/pricing.d.ts +2 -0
  95. package/dist/models/pricing.js +17 -1
  96. package/dist/models/provider-factory.js +9 -7
  97. package/dist/models/streaming.d.ts +89 -0
  98. package/dist/models/streaming.js +179 -2
  99. package/dist/persistence/index.d.ts +230 -0
  100. package/dist/persistence/index.js +2 -0
  101. package/dist/runtime/index.d.ts +1 -1
  102. package/dist/runtime/index.js +12 -0
  103. package/dist/runtime/worker.d.ts +36 -1
  104. package/dist/runtime/worker.js +37 -4
  105. package/dist/security/checklist.js +46 -0
  106. package/dist/server/bin.d.ts +72 -0
  107. package/dist/server/bin.js +304 -0
  108. package/dist/server/boot.d.ts +20 -2
  109. package/dist/server/boot.js +29 -3
  110. package/dist/server/cli-worker.js +6 -3
  111. package/dist/server/cli.d.ts +13 -0
  112. package/dist/server/cli.js +10 -5
  113. package/dist/server/config.d.ts +11 -1
  114. package/dist/server/config.js +18 -0
  115. package/dist/server/doctor.d.ts +85 -0
  116. package/dist/server/doctor.js +280 -0
  117. package/dist/server/pool.d.ts +47 -0
  118. package/dist/server/pool.js +73 -0
  119. package/dist/skills/context.d.ts +61 -0
  120. package/dist/skills/context.js +107 -0
  121. package/dist/skills/index.d.ts +1 -0
  122. package/dist/skills/index.js +1 -0
  123. package/dist/telemetry/spans.js +5 -0
  124. package/dist/testing/conformance/artifact-exports.d.ts +20 -0
  125. package/dist/testing/conformance/artifact-exports.js +172 -0
  126. package/dist/testing/conformance/artifacts.d.ts +11 -0
  127. package/dist/testing/conformance/artifacts.js +316 -0
  128. package/dist/testing/conformance/audio.d.ts +38 -0
  129. package/dist/testing/conformance/audio.js +145 -0
  130. package/dist/testing/conformance/capability.d.ts +23 -0
  131. package/dist/testing/conformance/capability.js +27 -0
  132. package/dist/testing/conformance/checkpoint-store.d.ts +15 -0
  133. package/dist/testing/conformance/checkpoint-store.js +72 -0
  134. package/dist/testing/conformance/connections.d.ts +14 -0
  135. package/dist/testing/conformance/connections.js +171 -0
  136. package/dist/testing/conformance/conversation-store.d.ts +10 -0
  137. package/dist/testing/conformance/conversation-store.js +60 -0
  138. package/dist/testing/conformance/evaluation.d.ts +10 -0
  139. package/dist/testing/conformance/evaluation.js +295 -0
  140. package/dist/testing/conformance/files.d.ts +19 -0
  141. package/dist/testing/conformance/files.js +454 -0
  142. package/dist/testing/conformance/flows.d.ts +16 -0
  143. package/dist/testing/conformance/flows.js +193 -0
  144. package/dist/testing/conformance/graph.d.ts +22 -0
  145. package/dist/testing/conformance/graph.js +500 -0
  146. package/dist/testing/conformance/hitl.d.ts +25 -0
  147. package/dist/testing/conformance/hitl.js +523 -0
  148. package/dist/testing/conformance/index.d.ts +163 -0
  149. package/dist/testing/conformance/index.js +321 -0
  150. package/dist/testing/conformance/invariants.d.ts +23 -0
  151. package/dist/testing/conformance/invariants.js +80 -0
  152. package/dist/testing/conformance/knowledge.d.ts +41 -0
  153. package/dist/testing/conformance/knowledge.js +592 -0
  154. package/dist/testing/conformance/parents.d.ts +50 -0
  155. package/dist/testing/conformance/parents.js +39 -0
  156. package/dist/testing/conformance/rate-limit.d.ts +25 -0
  157. package/dist/testing/conformance/rate-limit.js +71 -0
  158. package/dist/testing/conformance/records.d.ts +36 -0
  159. package/dist/testing/conformance/records.js +400 -0
  160. package/dist/testing/conformance/rollups.d.ts +30 -0
  161. package/dist/testing/conformance/rollups.js +425 -0
  162. package/dist/testing/conformance/run-coordinator.d.ts +27 -0
  163. package/dist/testing/conformance/run-coordinator.js +150 -0
  164. package/dist/testing/conformance/run-event-log.d.ts +9 -0
  165. package/dist/testing/conformance/run-event-log.js +138 -0
  166. package/dist/testing/conformance/run-store.d.ts +12 -0
  167. package/dist/testing/conformance/run-store.js +294 -0
  168. package/dist/testing/conformance/session-state.d.ts +38 -0
  169. package/dist/testing/conformance/session-state.js +198 -0
  170. package/dist/testing/conformance/usage-limits.d.ts +10 -0
  171. package/dist/testing/conformance/usage-limits.js +309 -0
  172. package/dist/testing/conformance.d.ts +8 -0
  173. package/dist/testing/conformance.js +8 -0
  174. package/dist/testing/memory-backend.d.ts +39 -0
  175. package/dist/testing/memory-backend.js +41 -0
  176. package/dist/testing/pglite.d.ts +55 -0
  177. package/dist/testing/pglite.js +78 -0
  178. package/dist/testing/stub-model.d.ts +58 -0
  179. package/dist/testing/stub-model.js +71 -0
  180. package/dist/testing/supabase-storage-double.d.ts +35 -0
  181. package/dist/testing/supabase-storage-double.js +101 -0
  182. package/dist/toolkit/http.d.ts +27 -0
  183. package/dist/toolkit/http.js +48 -0
  184. package/dist/toolkit/index.d.ts +27 -1
  185. package/dist/toolkit/index.js +24 -1
  186. package/dist/toolkit/mime.d.ts +97 -0
  187. package/dist/toolkit/mime.js +233 -0
  188. package/dist/toolkit/ssrf.d.ts +139 -0
  189. package/dist/toolkit/ssrf.js +349 -0
  190. package/dist/toolkit/vendor.d.ts +91 -0
  191. package/dist/toolkit/vendor.js +153 -0
  192. package/dist/tools/credentials.d.ts +265 -6
  193. package/dist/tools/credentials.js +318 -3
  194. package/dist/tools/define.d.ts +14 -0
  195. package/dist/tools/define.js +3 -0
  196. package/dist/tools/index.d.ts +21 -0
  197. package/dist/tools/index.js +7 -0
  198. package/dist/tools/library/audio.d.ts +63 -0
  199. package/dist/tools/library/audio.js +148 -0
  200. package/dist/tools/library/index.d.ts +8 -0
  201. package/dist/tools/library/index.js +7 -0
  202. package/dist/tools/registry.d.ts +33 -0
  203. package/dist/tools/registry.js +72 -2
  204. package/dist/usage/index.d.ts +21 -0
  205. package/dist/usage/index.js +1 -0
  206. package/dist/usage/rate-limit.d.ts +122 -0
  207. package/dist/usage/rate-limit.js +131 -0
  208. package/dist/usage/recorder.js +3 -0
  209. 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
@@ -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
- export declare const MODEL_PROVIDERS: readonly ["openai", "anthropic", "google", "mistral", "azure-openai", "bedrock", "openai-compatible"];
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;
@@ -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));
@@ -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;