@retinue/agentkit 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/README.md +59 -277
  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/embeddings/openai.d.ts +45 -0
  6. package/dist/adapters/embeddings/openai.js +109 -0
  7. package/dist/adapters/memory/connections.d.ts +10 -0
  8. package/dist/adapters/memory/connections.js +87 -0
  9. package/dist/adapters/memory/graph.d.ts +17 -0
  10. package/dist/adapters/memory/graph.js +267 -0
  11. package/dist/adapters/memory/index.d.ts +3 -0
  12. package/dist/adapters/memory/index.js +3 -0
  13. package/dist/adapters/memory/rate-limit.d.ts +14 -0
  14. package/dist/adapters/memory/rate-limit.js +28 -0
  15. package/dist/adapters/memory/runtime.js +0 -0
  16. package/dist/adapters/postgres/connections.d.ts +15 -0
  17. package/dist/adapters/postgres/connections.js +134 -0
  18. package/dist/adapters/postgres/graph.d.ts +24 -0
  19. package/dist/adapters/postgres/graph.js +325 -0
  20. package/dist/adapters/postgres/index.d.ts +2 -0
  21. package/dist/adapters/postgres/index.js +2 -0
  22. package/dist/adapters/postgres/migrations.js +198 -0
  23. package/dist/adapters/postgres/run-store.js +26 -2
  24. package/dist/adapters/postgres/schema.d.ts +61 -2
  25. package/dist/adapters/postgres/schema.js +95 -10
  26. package/dist/adapters/redis/index.d.ts +1 -0
  27. package/dist/adapters/redis/index.js +1 -0
  28. package/dist/adapters/redis/rate-limit.d.ts +46 -0
  29. package/dist/adapters/redis/rate-limit.js +55 -0
  30. package/dist/adapters/supabase/index.d.ts +15 -0
  31. package/dist/adapters/supabase/index.js +15 -0
  32. package/dist/adapters/supabase/rls.js +33 -0
  33. package/dist/agents/agent.d.ts +99 -1
  34. package/dist/agents/agent.js +215 -16
  35. package/dist/agents/define.js +24 -4
  36. package/dist/agents/engine.d.ts +48 -0
  37. package/dist/agents/engine.js +370 -13
  38. package/dist/audio/index.d.ts +151 -0
  39. package/dist/audio/index.js +183 -0
  40. package/dist/capabilities/index.d.ts +5 -1
  41. package/dist/capabilities/index.js +23 -0
  42. package/dist/capabilities/runtime.d.ts +8 -0
  43. package/dist/connections/cipher.d.ts +103 -0
  44. package/dist/connections/cipher.js +141 -0
  45. package/dist/connections/index.d.ts +156 -0
  46. package/dist/connections/index.js +72 -0
  47. package/dist/connections/oauth/client.d.ts +84 -0
  48. package/dist/connections/oauth/client.js +141 -0
  49. package/dist/connections/oauth/index.d.ts +166 -0
  50. package/dist/connections/oauth/index.js +226 -0
  51. package/dist/connections/oauth/service.d.ts +90 -0
  52. package/dist/connections/oauth/service.js +132 -0
  53. package/dist/connections/pause.d.ts +68 -0
  54. package/dist/connections/pause.js +97 -0
  55. package/dist/connections/resolver.d.ts +59 -0
  56. package/dist/connections/resolver.js +138 -0
  57. package/dist/connections/resume.d.ts +64 -0
  58. package/dist/connections/resume.js +99 -0
  59. package/dist/connections/vault-cipher.d.ts +95 -0
  60. package/dist/connections/vault-cipher.js +206 -0
  61. package/dist/core/budget.d.ts +55 -0
  62. package/dist/core/budget.js +56 -0
  63. package/dist/core/content-parts.d.ts +26 -2
  64. package/dist/core/content-parts.js +1 -0
  65. package/dist/core/context.d.ts +25 -0
  66. package/dist/core/errors.d.ts +1 -1
  67. package/dist/core/errors.js +15 -0
  68. package/dist/core/events.d.ts +110 -2
  69. package/dist/core/events.js +33 -2
  70. package/dist/core/index.d.ts +1 -0
  71. package/dist/core/index.js +1 -0
  72. package/dist/core/validation.js +19 -0
  73. package/dist/documents/index.d.ts +14 -0
  74. package/dist/documents/parsers/text.d.ts +16 -0
  75. package/dist/documents/parsers/text.js +54 -2
  76. package/dist/entries/connections.d.ts +10 -0
  77. package/dist/entries/connections.js +10 -0
  78. package/dist/entries/guardrails.d.ts +14 -0
  79. package/dist/entries/guardrails.js +14 -0
  80. package/dist/entries/knowledge.d.ts +17 -0
  81. package/dist/entries/knowledge.js +16 -0
  82. package/dist/entries/mcp-server.d.ts +9 -0
  83. package/dist/entries/mcp-server.js +9 -0
  84. package/dist/entries/runtime.d.ts +10 -0
  85. package/dist/entries/runtime.js +8 -0
  86. package/dist/entries/testing.d.ts +28 -0
  87. package/dist/entries/testing.js +28 -0
  88. package/dist/graphql/resolvers.d.ts +7 -1
  89. package/dist/graphql/resolvers.js +20 -0
  90. package/dist/graphql/schema.d.ts +1 -1
  91. package/dist/graphql/schema.js +44 -0
  92. package/dist/guardrails/index.d.ts +115 -0
  93. package/dist/guardrails/index.js +108 -0
  94. package/dist/guardrails/moderation.d.ts +53 -0
  95. package/dist/guardrails/moderation.js +75 -0
  96. package/dist/guardrails/pii.d.ts +75 -0
  97. package/dist/guardrails/pii.js +193 -0
  98. package/dist/knowledge/communities.d.ts +166 -0
  99. package/dist/knowledge/communities.js +377 -0
  100. package/dist/knowledge/graph-global.d.ts +153 -0
  101. package/dist/knowledge/graph-global.js +246 -0
  102. package/dist/knowledge/graph-retrieval.d.ts +150 -0
  103. package/dist/knowledge/graph-retrieval.js +303 -0
  104. package/dist/knowledge/graph.d.ts +223 -0
  105. package/dist/knowledge/graph.js +369 -0
  106. package/dist/knowledge/index.d.ts +25 -0
  107. package/dist/knowledge/index.js +37 -1
  108. package/dist/knowledge/navigate.d.ts +89 -0
  109. package/dist/knowledge/navigate.js +107 -0
  110. package/dist/knowledge/retrieval.d.ts +98 -5
  111. package/dist/knowledge/retrieval.js +158 -28
  112. package/dist/mcp-server/index.d.ts +168 -0
  113. package/dist/mcp-server/index.js +175 -0
  114. package/dist/models/extraction.d.ts +73 -0
  115. package/dist/models/extraction.js +118 -0
  116. package/dist/models/index.d.ts +40 -1
  117. package/dist/models/index.js +22 -1
  118. package/dist/models/pricing.d.ts +2 -0
  119. package/dist/models/pricing.js +17 -1
  120. package/dist/models/provider-factory.js +9 -7
  121. package/dist/models/streaming.d.ts +111 -1
  122. package/dist/models/streaming.js +184 -3
  123. package/dist/persistence/index.d.ts +230 -0
  124. package/dist/persistence/index.js +2 -0
  125. package/dist/runtime/index.d.ts +1 -1
  126. package/dist/runtime/index.js +12 -0
  127. package/dist/runtime/worker.d.ts +36 -1
  128. package/dist/runtime/worker.js +37 -4
  129. package/dist/security/checklist.js +55 -0
  130. package/dist/security/findings.js +18 -9
  131. package/dist/server/bin.d.ts +19 -0
  132. package/dist/server/bin.js +226 -0
  133. package/dist/server/boot.d.ts +20 -2
  134. package/dist/server/boot.js +29 -3
  135. package/dist/server/cli.d.ts +13 -0
  136. package/dist/server/cli.js +7 -4
  137. package/dist/server/doctor.d.ts +74 -0
  138. package/dist/server/doctor.js +280 -0
  139. package/dist/skills/catalogue.d.ts +49 -0
  140. package/dist/skills/catalogue.js +61 -0
  141. package/dist/skills/context.d.ts +61 -0
  142. package/dist/skills/context.js +107 -0
  143. package/dist/skills/index.d.ts +2 -0
  144. package/dist/skills/index.js +2 -0
  145. package/dist/telemetry/spans.js +17 -0
  146. package/dist/testing/conformance/artifact-exports.d.ts +20 -0
  147. package/dist/testing/conformance/artifact-exports.js +172 -0
  148. package/dist/testing/conformance/artifacts.d.ts +11 -0
  149. package/dist/testing/conformance/artifacts.js +316 -0
  150. package/dist/testing/conformance/audio.d.ts +38 -0
  151. package/dist/testing/conformance/audio.js +145 -0
  152. package/dist/testing/conformance/capability.d.ts +23 -0
  153. package/dist/testing/conformance/capability.js +27 -0
  154. package/dist/testing/conformance/checkpoint-store.d.ts +15 -0
  155. package/dist/testing/conformance/checkpoint-store.js +72 -0
  156. package/dist/testing/conformance/connections.d.ts +14 -0
  157. package/dist/testing/conformance/connections.js +171 -0
  158. package/dist/testing/conformance/conversation-store.d.ts +10 -0
  159. package/dist/testing/conformance/conversation-store.js +60 -0
  160. package/dist/testing/conformance/evaluation.d.ts +10 -0
  161. package/dist/testing/conformance/evaluation.js +295 -0
  162. package/dist/testing/conformance/files.d.ts +19 -0
  163. package/dist/testing/conformance/files.js +454 -0
  164. package/dist/testing/conformance/flows.d.ts +16 -0
  165. package/dist/testing/conformance/flows.js +193 -0
  166. package/dist/testing/conformance/graph.d.ts +22 -0
  167. package/dist/testing/conformance/graph.js +500 -0
  168. package/dist/testing/conformance/hitl.d.ts +25 -0
  169. package/dist/testing/conformance/hitl.js +523 -0
  170. package/dist/testing/conformance/index.d.ts +163 -0
  171. package/dist/testing/conformance/index.js +321 -0
  172. package/dist/testing/conformance/invariants.d.ts +23 -0
  173. package/dist/testing/conformance/invariants.js +80 -0
  174. package/dist/testing/conformance/knowledge.d.ts +41 -0
  175. package/dist/testing/conformance/knowledge.js +592 -0
  176. package/dist/testing/conformance/parents.d.ts +50 -0
  177. package/dist/testing/conformance/parents.js +39 -0
  178. package/dist/testing/conformance/rate-limit.d.ts +25 -0
  179. package/dist/testing/conformance/rate-limit.js +71 -0
  180. package/dist/testing/conformance/records.d.ts +36 -0
  181. package/dist/testing/conformance/records.js +400 -0
  182. package/dist/testing/conformance/rollups.d.ts +30 -0
  183. package/dist/testing/conformance/rollups.js +425 -0
  184. package/dist/testing/conformance/run-coordinator.d.ts +27 -0
  185. package/dist/testing/conformance/run-coordinator.js +150 -0
  186. package/dist/testing/conformance/run-event-log.d.ts +9 -0
  187. package/dist/testing/conformance/run-event-log.js +138 -0
  188. package/dist/testing/conformance/run-store.d.ts +12 -0
  189. package/dist/testing/conformance/run-store.js +294 -0
  190. package/dist/testing/conformance/session-state.d.ts +38 -0
  191. package/dist/testing/conformance/session-state.js +198 -0
  192. package/dist/testing/conformance/usage-limits.d.ts +10 -0
  193. package/dist/testing/conformance/usage-limits.js +309 -0
  194. package/dist/testing/conformance.d.ts +8 -0
  195. package/dist/testing/conformance.js +8 -0
  196. package/dist/testing/memory-backend.d.ts +39 -0
  197. package/dist/testing/memory-backend.js +41 -0
  198. package/dist/testing/pglite.d.ts +55 -0
  199. package/dist/testing/pglite.js +78 -0
  200. package/dist/testing/stub-model.d.ts +58 -0
  201. package/dist/testing/stub-model.js +71 -0
  202. package/dist/testing/supabase-storage-double.d.ts +35 -0
  203. package/dist/testing/supabase-storage-double.js +101 -0
  204. package/dist/toolkit/files.d.ts +125 -0
  205. package/dist/toolkit/files.js +320 -0
  206. package/dist/toolkit/http.d.ts +27 -0
  207. package/dist/toolkit/http.js +48 -0
  208. package/dist/toolkit/index.d.ts +31 -1
  209. package/dist/toolkit/index.js +26 -1
  210. package/dist/toolkit/mime.d.ts +97 -0
  211. package/dist/toolkit/mime.js +233 -0
  212. package/dist/toolkit/sandbox.d.ts +119 -0
  213. package/dist/toolkit/sandbox.js +239 -0
  214. package/dist/toolkit/ssrf.d.ts +139 -0
  215. package/dist/toolkit/ssrf.js +349 -0
  216. package/dist/toolkit/vendor.d.ts +91 -0
  217. package/dist/toolkit/vendor.js +153 -0
  218. package/dist/toolkit/web.d.ts +13 -0
  219. package/dist/toolkit/web.js +7 -1
  220. package/dist/tools/budget.d.ts +28 -0
  221. package/dist/tools/budget.js +35 -0
  222. package/dist/tools/credentials.d.ts +316 -0
  223. package/dist/tools/credentials.js +369 -0
  224. package/dist/tools/define.d.ts +45 -0
  225. package/dist/tools/define.js +26 -0
  226. package/dist/tools/find.d.ts +109 -0
  227. package/dist/tools/find.js +210 -0
  228. package/dist/tools/index.d.ts +35 -2
  229. package/dist/tools/index.js +11 -0
  230. package/dist/tools/library/audio.d.ts +63 -0
  231. package/dist/tools/library/audio.js +148 -0
  232. package/dist/tools/library/fs.d.ts +24 -0
  233. package/dist/tools/library/fs.js +102 -0
  234. package/dist/tools/library/index.d.ts +37 -2
  235. package/dist/tools/library/index.js +47 -0
  236. package/dist/tools/library/shell.d.ts +45 -0
  237. package/dist/tools/library/shell.js +70 -0
  238. package/dist/tools/meta-tools.js +8 -0
  239. package/dist/tools/registry.d.ts +146 -0
  240. package/dist/tools/registry.js +250 -4
  241. package/dist/usage/index.d.ts +21 -0
  242. package/dist/usage/index.js +1 -0
  243. package/dist/usage/rate-limit.d.ts +122 -0
  244. package/dist/usage/rate-limit.js +131 -0
  245. package/dist/usage/recorder.js +3 -0
  246. package/package.json +33 -2
@@ -72,6 +72,13 @@ export const TENANT_SCOPED_TABLES = [
72
72
  { table: "idempotency_keys" },
73
73
  { table: "skills" },
74
74
  { table: "mcp_connections" },
75
+ /**
76
+ * #261. A tenant's third-party credentials, and therefore the table where a missing policy would be worst:
77
+ * every row is somebody's access to somebody else's system, and the plain `tenant_id` predicate is exactly
78
+ * the isolation the port already enforces in its `WHERE` clauses. Defence in depth, and #91 is the reminder
79
+ * that a method accepting a tenant scope and not using it typechecks perfectly.
80
+ */
81
+ { table: "connections" },
75
82
  { table: "principal_memory", extraPredicate: PRINCIPAL_PREDICATE },
76
83
  // `blobs`, not the SPEC's `blob_refs`: BlobStore stores the value, and the metadata-and-pointer
77
84
  // design belongs to FileMetadataStore (#129) / ArtifactStore (#133).
@@ -98,6 +105,32 @@ export const TENANT_SCOPED_TABLES = [
98
105
  */
99
106
  { table: "flow_definitions" },
100
107
  { table: "flow_executions" },
108
+ /**
109
+ * #271. The knowledge graph, all five tables, and they belong here rather than beside `knowledge_chunks`
110
+ * in the vector list: none of them needs pgvector, so they exist in every deployment and can be protected
111
+ * unconditionally.
112
+ *
113
+ * Worth stating why the *settings* table needs a policy as much as the content ones. `knowledge_graph_settings`
114
+ * holds one boolean, so it looks harmless — but it is the switch that decides whether a tenant is paying for
115
+ * extraction, and a missing policy would let one tenant read, and worse write, another's. Turning a
116
+ * neighbour's GraphRAG on is a bill they did not agree to.
117
+ */
118
+ { table: "knowledge_graph_settings" },
119
+ { table: "knowledge_graph_sources" },
120
+ /**
121
+ * The contributions table is the one to get right. It holds the entities and relationships each source
122
+ * asserted, as JSON — which is to say, the substance of the documents themselves in condensed form. A leak
123
+ * here is a leak of what a tenant's corpus says, not merely of which entities exist.
124
+ */
125
+ { table: "knowledge_graph_contributions" },
126
+ { table: "knowledge_graph_entities" },
127
+ { table: "knowledge_graph_relationships" },
128
+ /**
129
+ * #272. Community summaries are **generated prose about a tenant's corpus** — a paraphrase of what their
130
+ * documents say, which is if anything more sensitive than the entity names next door. A missing policy here
131
+ * would leak the substance of a corpus rather than its index.
132
+ */
133
+ { table: "knowledge_graph_communities" },
101
134
  ];
102
135
  /**
103
136
  * Tables that deliberately have no tenant policy, each with the reason.
@@ -15,11 +15,52 @@ import { type ProviderCredentials } from "../models/provider-factory.js";
15
15
  import type { ModelProvider } from "../models/index.js";
16
16
  import type { AuthorizationPolicy } from "../authorization/index.js";
17
17
  import { type ContextProvider } from "../context/index.js";
18
- import { type ToolProvider } from "../tools/index.js";
18
+ import { type ShadowRecorder, type ToolProvider, type ToolSearch, type ToolsetResolver } from "../tools/index.js";
19
+ import type { TokenBudget } from "../core/budget.js";
19
20
  import { type AgentEngine, type ProcessOutcome } from "../runtime/index.js";
21
+ import type { Guardrail } from "../guardrails/index.js";
22
+ import { type SkillResolver } from "../skills/index.js";
20
23
  import { type ResolvedModelInfo } from "./engine.js";
21
24
  import type { AgentManifest } from "./index.js";
22
25
  import { type AgentManifestInput } from "./define.js";
26
+ /**
27
+ * The policy this agent runs under — task #244, the interpreter for `authorizationPolicyId`.
28
+ *
29
+ * The field was declared and read by nothing, so an agent naming `"restricted"` ran under whatever the host had
30
+ * wired — very possibly the permissive default. Three rules, and the third is the one that matters:
31
+ *
32
+ * 1. A map is supplied and contains the id → that policy.
33
+ * 2. No map, and the id is `defineAgent`'s `"default"` → the single wired policy, or the permissive default.
34
+ * This is the normal case and stays a one-liner.
35
+ * 3. **Any other combination is an error.** An id the deployment cannot honour must never fall through to the
36
+ * permissive default: an agent asking to run under a narrow policy and silently getting allow-all is the
37
+ * worst possible reading of this field, and it is the reading it had. Failing at construction is loud, early
38
+ * and cheap.
39
+ *
40
+ * Note the platform does not check that a named policy is *narrower* than any other. It cannot: a policy is an
41
+ * interface the host implements, and composing two of them into an intersection would mean second-guessing a
42
+ * deployment's own authorization. What the platform guarantees is that the policy an agent named is the policy
43
+ * it got, or the run does not start.
44
+ */
45
+ export declare const selectAuthorization: (config: Pick<CreateAgentConfig, "authorization" | "authorizationPolicies">, manifest: Pick<AgentManifest, "authorizationPolicyId" | "id">) => AuthorizationPolicy;
46
+ /**
47
+ * The providers this agent asked for, in the order it asked — task #244.
48
+ *
49
+ * `AgentManifest.contextProviderIds` was declared and read by nothing, so a manifest naming two of four wired
50
+ * providers got all four, and naming none got all of them too. Two decisions make the field meaningful without
51
+ * making it a trap:
52
+ *
53
+ * - **An empty list means every wired provider**, not none. `defineAgent` defaults it to `[]`, so treating empty
54
+ * as "no context" would silently strip the memory, notes and attachments from every agent already written
55
+ * against the default. The field is a *selection*, and no selection means no narrowing.
56
+ * - **A named id that is not wired is an error**, not a silent omission. The failure it prevents is the one worth
57
+ * preventing: an agent whose manifest asks for `principal-memory`, a typo or a missing wire, and an assistant
58
+ * that quietly remembers nothing. That reads exactly like a model that chose not to use its memory.
59
+ *
60
+ * Order follows the manifest, because section order is prompt order and the manifest is where an author can see
61
+ * and control it.
62
+ */
63
+ export declare const selectContextProviders: (wired: readonly ContextProvider[], manifest: Pick<AgentManifest, "contextProviderIds" | "id">) => readonly ContextProvider[];
23
64
  export type CreateAgentConfig = {
24
65
  readonly manifest: AgentManifestInput;
25
66
  /** Model catalog; defaults to a small Anthropic catalog (smart/fast). */
@@ -38,7 +79,52 @@ export type CreateAgentConfig = {
38
79
  */
39
80
  readonly randomHex?: (bytes: number) => string;
40
81
  readonly authorization?: AuthorizationPolicy;
82
+ /**
83
+ * Named policies a manifest may select with `authorizationPolicyId` — task #244.
84
+ *
85
+ * Absent is the normal case: one `authorization` (or the permissive default), and every manifest carrying
86
+ * `defineAgent`'s `"default"`. Supplying a map is how a deployment runs several agents under different
87
+ * policies — a customer-facing agent under a narrow one, an internal agent under a wider one — without the
88
+ * host having to build a separate registry per agent.
89
+ */
90
+ readonly authorizationPolicies?: Readonly<Record<string, AuthorizationPolicy>>;
91
+ /**
92
+ * Checks to run before the model sees a turn and before anything leaves it — REQ-046 (#205), AC-5.
93
+ *
94
+ * Here so a host can add one without composing the runtime by hand: this facade exists to be the short path,
95
+ * and a guardrail that could only be wired through the long one would be a guardrail most deployments never
96
+ * add.
97
+ */
98
+ readonly guardrails?: readonly Guardrail[];
99
+ /**
100
+ * Search over the catalogue, which is what makes `find_tools` exist — REQ-045 (#204), task #210.
101
+ *
102
+ * Absent means no `find_tools`. Wire it with `createToolSearch()` for keyword search, or pass an
103
+ * `EmbeddingProvider` to it for hybrid — and see `tools/find.ts` on why keyword-only is the honest default.
104
+ */
105
+ readonly toolSearch?: ToolSearch;
106
+ /** A ceiling in tokens on the tool list handed to the model — task #210, AC-3. Absent means no ceiling. */
107
+ readonly catalogBudget?: TokenBudget;
108
+ /** A tenant's category switches, applied before authorization — task #210, AC-4. */
109
+ readonly toolsets?: ToolsetResolver;
110
+ /**
111
+ * A skill resolver — task #244, and what makes `manifest.skillPolicy` mean something.
112
+ *
113
+ * Wiring is the toggle, the rule `toolSearch` already follows. Supplying one adds a skills catalogue section
114
+ * to the prompt (names and descriptions, filtered by the manifest's `assigned`/`allowTenantSkills`) and makes
115
+ * `load_skill` real; omitting it means neither is advertised.
116
+ */
117
+ readonly skills?: SkillResolver;
41
118
  readonly tenantId?: string;
119
+ /**
120
+ * Where a shadow run's suppressed writes go — required to run one at all.
121
+ *
122
+ * Without it `createAgent` cannot run a shadow turn: the registry refuses a gated tool when
123
+ * `context.shadow` is true and no recorder is configured, which is the correct fail-closed rule but meant
124
+ * this runner had no way to satisfy it. Parity measurement needs a run driven to completion in one process,
125
+ * and this is that seam.
126
+ */
127
+ readonly shadow?: ShadowRecorder;
42
128
  /** Test/advanced seam: override how a manifest resolves to a model (e.g. a mock model). */
43
129
  readonly resolveModel?: (manifest: AgentManifest, context: ExecutionContext) => ResolvedModelInfo;
44
130
  /** Test/advanced seam: supply the engine directly instead of building the default one. */
@@ -50,6 +136,18 @@ export type RunInput = {
50
136
  readonly message: string;
51
137
  readonly principalId?: string;
52
138
  readonly roleIds?: readonly string[];
139
+ /**
140
+ * Run with every gated effect suppressed and recorded instead of performed.
141
+ *
142
+ * Per-run rather than per-agent, because the same agent serves both: a parity harness shadows one turn and
143
+ * runs the next for real. `shadow` without `config.shadow` is refused by the registry rather than performed
144
+ * — announcing a shadow run with nowhere to record it is not a licence to publish.
145
+ *
146
+ * Note what shadow mode does **not** suppress: an `internal-write` still happens, so a shadow run really
147
+ * does create drafts. It measures everything up to the external write and nothing after it, because what an
148
+ * agent does after publishing cannot be observed without publishing.
149
+ */
150
+ readonly shadow?: boolean;
53
151
  };
54
152
  export type RunResult = {
55
153
  readonly runId: string;
@@ -17,14 +17,95 @@ import { createProviderFactory, } from "../models/provider-factory.js";
17
17
  import { gatherSections } from "../context/index.js";
18
18
  import { randomBytes } from "node:crypto";
19
19
  import { makeNonce, renderContextBlock } from "../security/prompt-safety.js";
20
- import { createToolRegistry } from "../tools/index.js";
20
+ import { createToolRegistry, } from "../tools/index.js";
21
21
  import { createDurableWorker } from "../runtime/index.js";
22
22
  import { createMemoryCheckpointStore, createMemoryRunEventLog, createMemoryRunStore, } from "../adapters/memory/runtime.js";
23
23
  import { createMemoryConversationStore } from "../adapters/memory/index.js";
24
24
  import { createMemoryEventBus } from "../runtime/index.js";
25
25
  import { createMemoryMessageStore } from "../adapters/memory/message-store.js";
26
+ import { createSkillBodyLoader, createSkillCatalogueProvider, } from "../skills/index.js";
26
27
  import { createDefaultEngine } from "./engine.js";
27
28
  import { DEFAULT_MODEL_CATALOG, DEFAULT_ROLE_ASSIGNMENTS, defineAgent, } from "./define.js";
29
+ /**
30
+ * The policy this agent runs under — task #244, the interpreter for `authorizationPolicyId`.
31
+ *
32
+ * The field was declared and read by nothing, so an agent naming `"restricted"` ran under whatever the host had
33
+ * wired — very possibly the permissive default. Three rules, and the third is the one that matters:
34
+ *
35
+ * 1. A map is supplied and contains the id → that policy.
36
+ * 2. No map, and the id is `defineAgent`'s `"default"` → the single wired policy, or the permissive default.
37
+ * This is the normal case and stays a one-liner.
38
+ * 3. **Any other combination is an error.** An id the deployment cannot honour must never fall through to the
39
+ * permissive default: an agent asking to run under a narrow policy and silently getting allow-all is the
40
+ * worst possible reading of this field, and it is the reading it had. Failing at construction is loud, early
41
+ * and cheap.
42
+ *
43
+ * Note the platform does not check that a named policy is *narrower* than any other. It cannot: a policy is an
44
+ * interface the host implements, and composing two of them into an intersection would mean second-guessing a
45
+ * deployment's own authorization. What the platform guarantees is that the policy an agent named is the policy
46
+ * it got, or the run does not start.
47
+ */
48
+ export const selectAuthorization = (config, manifest) => {
49
+ const id = manifest.authorizationPolicyId ?? "default";
50
+ const named = config.authorizationPolicies;
51
+ if (named !== undefined) {
52
+ const chosen = named[id];
53
+ if (chosen !== undefined)
54
+ return chosen;
55
+ throw new AgentPlatformError({
56
+ code: "capability_unavailable",
57
+ message: `agent "${manifest.id}" runs under authorization policy "${id}" and no such policy is registered ` +
58
+ `(registered: ${Object.keys(named).map((k) => `"${k}"`).join(", ") || "none"}). Refusing rather than ` +
59
+ "falling back — an agent that asked for a narrow policy and silently got a permissive one is the " +
60
+ "failure this field exists to prevent.",
61
+ retryable: false,
62
+ });
63
+ }
64
+ if (id !== "default")
65
+ throw new AgentPlatformError({
66
+ code: "capability_unavailable",
67
+ message: `agent "${manifest.id}" runs under authorization policy "${id}" and none is registered. Pass ` +
68
+ "`authorizationPolicies: { \"" +
69
+ id +
70
+ "\": … }`, or leave the manifest's `authorizationPolicyId` at \"default\".",
71
+ retryable: false,
72
+ });
73
+ return config.authorization ?? allowAllAuthorization();
74
+ };
75
+ /**
76
+ * The providers this agent asked for, in the order it asked — task #244.
77
+ *
78
+ * `AgentManifest.contextProviderIds` was declared and read by nothing, so a manifest naming two of four wired
79
+ * providers got all four, and naming none got all of them too. Two decisions make the field meaningful without
80
+ * making it a trap:
81
+ *
82
+ * - **An empty list means every wired provider**, not none. `defineAgent` defaults it to `[]`, so treating empty
83
+ * as "no context" would silently strip the memory, notes and attachments from every agent already written
84
+ * against the default. The field is a *selection*, and no selection means no narrowing.
85
+ * - **A named id that is not wired is an error**, not a silent omission. The failure it prevents is the one worth
86
+ * preventing: an agent whose manifest asks for `principal-memory`, a typo or a missing wire, and an assistant
87
+ * that quietly remembers nothing. That reads exactly like a model that chose not to use its memory.
88
+ *
89
+ * Order follows the manifest, because section order is prompt order and the manifest is where an author can see
90
+ * and control it.
91
+ */
92
+ export const selectContextProviders = (wired, manifest) => {
93
+ const asked = manifest.contextProviderIds ?? [];
94
+ if (asked.length === 0)
95
+ return wired;
96
+ const byId = new Map(wired.map((p) => [p.id, p]));
97
+ const missing = asked.filter((id) => !byId.has(id));
98
+ if (missing.length > 0)
99
+ throw new AgentPlatformError({
100
+ code: "capability_unavailable",
101
+ message: `agent "${manifest.id}" asks for context provider(s) ${missing.map((m) => `"${m}"`).join(", ")} and ` +
102
+ `nothing wired supplies them (wired: ${wired.map((p) => `"${p.id}"`).join(", ") || "none"}). An agent ` +
103
+ "that silently runs without the context it declared is indistinguishable from a model choosing not to " +
104
+ "use it.",
105
+ retryable: false,
106
+ });
107
+ return asked.map((id) => byId.get(id));
108
+ };
28
109
  /** Permissive policy used by the embedded facade when a caller wires tools but no authorization. */
29
110
  const allowAllAuthorization = () => ({
30
111
  async can() {
@@ -55,8 +136,28 @@ export const createAgent = (config) => {
55
136
  roles: config.roleAssignments ?? DEFAULT_ROLE_ASSIGNMENTS,
56
137
  });
57
138
  const providerFactory = createProviderFactory({ credentials: config.providerCredentials ?? {} });
58
- const authorization = config.authorization ?? allowAllAuthorization();
59
- const toolRegistry = createToolRegistry({ providers: config.tools ?? [], authorization });
139
+ const authorization = selectAuthorization(config, manifest);
140
+ /**
141
+ * Skills, when a resolver is wired — #244.
142
+ *
143
+ * Both halves read the *same* policy, and that is the point: `createSkillBodyLoader` re-derives the catalogue
144
+ * before loading, so `assigned` and `allowTenantSkills` gate loading as well as listing. A policy that
145
+ * filtered the list but not the load would be no policy at all — a model that guessed a name would get it.
146
+ */
147
+ const skillPolicy = {
148
+ assigned: manifest.skillPolicy?.assigned ?? [],
149
+ allowTenantSkills: manifest.skillPolicy?.allowTenantSkills ?? false,
150
+ };
151
+ const skillLoader = config.skills === undefined ? undefined : createSkillBodyLoader({ resolver: config.skills, policy: skillPolicy });
152
+ const toolRegistry = createToolRegistry({
153
+ providers: config.tools ?? [],
154
+ authorization,
155
+ ...(config.toolSearch === undefined ? {} : { search: config.toolSearch }),
156
+ ...(config.toolsets === undefined ? {} : { toolsets: config.toolsets }),
157
+ ...(config.catalogBudget === undefined ? {} : { catalogBudget: config.catalogBudget }),
158
+ ...(skillLoader === undefined ? {} : { skills: skillLoader }),
159
+ ...(config.shadow === undefined ? {} : { shadow: config.shadow }),
160
+ });
60
161
  const resolveModel = config.resolveModel ??
61
162
  ((m) => {
62
163
  const def = registry.resolve(m.modelPolicy);
@@ -64,11 +165,25 @@ export const createAgent = (config) => {
64
165
  model: providerFactory.languageModel(def),
65
166
  modelId: def.modelId,
66
167
  currency: def.pricing.currency,
67
- price: (u) => computeModelCostMinorUnits(def.pricing, { inputTokens: u.inputTokens, outputTokens: u.outputTokens, cachedInputTokens: u.cachedInputTokens }),
168
+ price: (u) => computeModelCostMinorUnits(def.pricing, {
169
+ inputTokens: u.inputTokens,
170
+ outputTokens: u.outputTokens,
171
+ cachedInputTokens: u.cachedInputTokens,
172
+ // Dropped here before #247, so a cache write was billed as fresh input — and on a provider that
173
+ // charges a premium for a write, under-billed.
174
+ ...(u.cacheWriteTokens === undefined ? {} : { cacheWriteTokens: u.cacheWriteTokens }),
175
+ }),
68
176
  };
69
177
  });
70
- const contextProviders = config.contextProviders ?? [];
178
+ const contextProviders = [
179
+ ...(config.contextProviders ?? []),
180
+ ...(config.skills === undefined
181
+ ? []
182
+ : [createSkillCatalogueProvider({ resolver: config.skills, policy: skillPolicy })]),
183
+ ];
71
184
  const engine = config.engine ?? createDefaultEngine({
185
+ ...(config.guardrails === undefined ? {} : { guardrails: config.guardrails }),
186
+ ...(config.catalogBudget === undefined ? {} : { catalogBudget: config.catalogBudget }),
72
187
  async loadManifest() {
73
188
  return manifest; // single-manifest embedded agent
74
189
  },
@@ -88,7 +203,7 @@ export const createAgent = (config) => {
88
203
  * assembly, so content cannot learn the delimiter from a previous turn.
89
204
  */
90
205
  systemPrompt: async (m, context) => {
91
- const sections = await gatherSections(context, contextProviders);
206
+ const sections = await gatherSections(context, selectContextProviders(contextProviders, m));
92
207
  if (sections.length === 0)
93
208
  return m.instructions;
94
209
  const ctxText = renderContextBlock(sections, makeNonce(config.randomHex ?? defaultRandomHex));
@@ -120,19 +235,100 @@ export const createAgent = (config) => {
120
235
  },
121
236
  ...(config.tools && config.tools.length > 0
122
237
  ? {
238
+ /**
239
+ * The tools this turn, through the registry rather than around it — task #210.
240
+ *
241
+ * This used to gather the providers itself and filter them with `authorization.filterTools`, which was
242
+ * a second copy of what the registry does: no duplicate-name check, and — once tenant toolsets existed
243
+ * — no toolset either, so a switched-off category was hidden everywhere except in the list actually
244
+ * handed to the model.
245
+ */
123
246
  buildTools: async (context) => {
124
- const resolvedTools = [];
125
- const gathered = (await Promise.all((config.tools ?? []).map((p) => p.listTools(context)))).flat();
126
- const permitted = new Set((await authorization.filterTools(context, gathered.map((t) => t.descriptor))).map((d) => d.name));
127
- for (const t of gathered) {
128
- if (!permitted.has(t.descriptor.name))
129
- continue;
247
+ const descriptors = await toolRegistry.listAuthorized(context);
248
+ const resolvedTools = descriptors.map((descriptor) => ({
249
+ name: descriptor.name,
250
+ description: descriptor.description,
251
+ // Carried through so the engine can honour `toolPolicy.categories` without reaching back into
252
+ // the registry for a descriptor it already handed over — #244.
253
+ category: descriptor.category,
254
+ inputSchema: descriptor.inputSchema,
255
+ execute: async (input) => {
256
+ const result = await toolRegistry.execute(context, { name: descriptor.name, input });
257
+ if (!result.ok)
258
+ throw new AgentPlatformError(result.error);
259
+ return result.data;
260
+ },
261
+ }));
262
+ /**
263
+ * `find_tools`, when a search is wired — task #210, AC-1.
264
+ *
265
+ * Its schema is written here because a meta-tool descriptor carries none, and a model handed a
266
+ * permissive schema streams `{}` for every call. The registry validates the arguments itself.
267
+ */
268
+ /**
269
+ * `execute_tool`, alongside search — task #210.
270
+ *
271
+ * Without it `find_tools` is a dead end: the tool it names is not in this turn's list (that is why
272
+ * it had to be searched for), so the model learns a name it cannot call. Added whenever search or a
273
+ * budget is configured, which are exactly the cases where the list is partial.
274
+ */
275
+ if (config.toolSearch !== undefined || config.catalogBudget !== undefined) {
276
+ resolvedTools.push({
277
+ name: "execute_tool",
278
+ description: "Run a tool by name — including one that is not listed here, for example one you found with " +
279
+ "find_tools. Authorization and approval apply exactly as they would to a direct call.",
280
+ inputSchema: {
281
+ type: "object",
282
+ properties: {
283
+ name: { type: "string", description: "The tool's name." },
284
+ input: { type: "object", description: "The tool's arguments, matching its schema." },
285
+ },
286
+ required: ["name", "input"],
287
+ },
288
+ execute: async (input, options) => {
289
+ const result = await toolRegistry.execute(context, { name: "execute_tool", input });
290
+ // What actually ran, so the run event log names the action rather than the mechanism.
291
+ if (result.ranToolName !== undefined)
292
+ options?.report?.({ ranToolName: result.ranToolName });
293
+ if (!result.ok)
294
+ throw new AgentPlatformError(result.error);
295
+ return result.data;
296
+ },
297
+ });
298
+ }
299
+ if (config.toolSearch !== undefined || config.catalogBudget !== undefined) {
130
300
  resolvedTools.push({
131
- name: t.descriptor.name,
132
- description: t.descriptor.description,
133
- inputSchema: t.descriptor.inputSchema,
301
+ name: "learn_tools",
302
+ description: "Fetch the full input schemas for tools by name — including ones not listed here. Call this " +
303
+ "before running a tool you found with find_tools, so you know what arguments it takes.",
304
+ inputSchema: {
305
+ type: "object",
306
+ properties: { names: { type: "array", items: { type: "string" } } },
307
+ required: ["names"],
308
+ },
309
+ execute: async (input) => {
310
+ const result = await toolRegistry.execute(context, { name: "learn_tools", input });
311
+ if (!result.ok)
312
+ throw new AgentPlatformError(result.error);
313
+ return result.data;
314
+ },
315
+ });
316
+ }
317
+ if (config.toolSearch !== undefined) {
318
+ resolvedTools.push({
319
+ name: "find_tools",
320
+ description: "Search for a tool by describing what you need to do. Not all available tools are listed, so " +
321
+ "search before concluding that something cannot be done. Run what you find with execute_tool.",
322
+ inputSchema: {
323
+ type: "object",
324
+ properties: {
325
+ query: { type: "string", description: "What you are trying to do, in your own words." },
326
+ limit: { type: "number", description: "How many tools to return. Default 10." },
327
+ },
328
+ required: ["query"],
329
+ },
134
330
  execute: async (input) => {
135
- const result = await toolRegistry.execute(context, { name: t.descriptor.name, input });
331
+ const result = await toolRegistry.execute(context, { name: "find_tools", input });
136
332
  if (!result.ok)
137
333
  throw new AgentPlatformError(result.error);
138
334
  return result.data;
@@ -173,6 +369,9 @@ export const createAgent = (config) => {
173
369
  requestId: asId(`req-${seq}`),
174
370
  conversationId,
175
371
  runId,
372
+ // Only when asked. Defaulting to shadow would make every existing run a shadow run; the dangerous
373
+ // direction — claiming shadow with nowhere to record it — is closed in the registry instead.
374
+ ...(input.shadow === true ? { shadow: true } : {}),
176
375
  };
177
376
  contextByRun.set(runId, context);
178
377
  if (!(await conversations.findById({ tenantId, id: conversationId }))) {
@@ -39,9 +39,22 @@ export const DEFAULT_MODEL_CATALOG = [
39
39
  label: "Claude Sonnet 5",
40
40
  lifecycle: "generally-available",
41
41
  inputModalities: ["text", "image"],
42
- capabilities: { tools: true, structuredOutput: true, reasoning: true, nativeSearch: false },
42
+ capabilities: { tools: true, structuredOutput: true, reasoning: true, nativeSearch: false, promptCaching: "explicit" },
43
43
  limits: { contextTokens: 200_000, maxOutputTokens: 8_192 },
44
- pricing: { currency: "USD", inputPerMillion: 3_000, outputPerMillion: 15_000 },
44
+ /**
45
+ * Cache rates, added by #247.
46
+ *
47
+ * Anthropic's published ratios: a cache **read** is 0.1× a fresh input token and a cache **write** is
48
+ * **1.25×** — a write costs *more*. `cacheWritePerMillion` was in `ModelPricing` and read by nothing, so a
49
+ * cache write was billed as ordinary input and the first turn of every conversation was under-billed.
50
+ */
51
+ pricing: {
52
+ currency: "USD",
53
+ inputPerMillion: 3_000,
54
+ outputPerMillion: 15_000,
55
+ cacheReadPerMillion: 300,
56
+ cacheWritePerMillion: 3_750,
57
+ },
45
58
  dataResidency: ["us"],
46
59
  },
47
60
  {
@@ -50,9 +63,16 @@ export const DEFAULT_MODEL_CATALOG = [
50
63
  label: "Claude Haiku 4.5",
51
64
  lifecycle: "generally-available",
52
65
  inputModalities: ["text", "image"],
53
- capabilities: { tools: true, structuredOutput: true, reasoning: false, nativeSearch: false },
66
+ capabilities: { tools: true, structuredOutput: true, reasoning: false, nativeSearch: false, promptCaching: "explicit" },
54
67
  limits: { contextTokens: 200_000, maxOutputTokens: 8_192 },
55
- pricing: { currency: "USD", inputPerMillion: 800, outputPerMillion: 4_000 },
68
+ pricing: {
69
+ currency: "USD",
70
+ inputPerMillion: 800,
71
+ outputPerMillion: 4_000,
72
+ // Same ratios as Sonnet above: read 0.1×, write 1.25×.
73
+ cacheReadPerMillion: 80,
74
+ cacheWritePerMillion: 1_000,
75
+ },
56
76
  dataResidency: ["us"],
57
77
  },
58
78
  ];
@@ -18,12 +18,23 @@
18
18
  * on a refusal the model cannot resolve.
19
19
  */
20
20
  import type { ExecutionContext } from "../core/context.js";
21
+ import { type ConnectionNeed } from "../connections/pause.js";
21
22
  import type { RunId, TenantId } from "../core/ids.js";
22
23
  import type { ModelDefinition, ModelTurnRequest, ModelTurnTool, NeutralStreamChunk, NeutralUsage, ResolvedModel, TurnMessage } from "../models/index.js";
24
+ import { type Guardrail } from "../guardrails/index.js";
23
25
  import { type AgentEngine, type RetryPolicy, type Run } from "../runtime/index.js";
26
+ import { type TokenBudget } from "../core/budget.js";
24
27
  import type { PendingQuestion, RunApprovals } from "../hitl/index.js";
25
28
  import type { CitationEmitter } from "../citations/index.js";
26
29
  import type { AgentManifest } from "./index.js";
30
+ /**
31
+ * What one tool costs the model's context.
32
+ *
33
+ * A `ModelTurnTool` is not a catalogue entry — it carries the full input schema, because that is what a provider
34
+ * puts in the request — so this deliberately does *not* reuse `entryTokens`. Using the compact estimate here
35
+ * would understate a schema-heavy tool by an order of magnitude and produce a budget that never binds.
36
+ */
37
+ export declare const turnToolTokens: (tool: ModelTurnTool) => number;
27
38
  /** A model resolved for a turn: the opaque handle plus what the engine needs to attribute usage. */
28
39
  export type ResolvedModelInfo = {
29
40
  readonly model: ResolvedModel;
@@ -63,6 +74,24 @@ export type DefaultEngineDeps = {
63
74
  * pause on.
64
75
  */
65
76
  readonly approvals?: RunApprovals;
77
+ /**
78
+ * Checks a deployment adds — REQ-046 (#205).
79
+ *
80
+ * Absent means no inspection, which is the current behaviour and stays the default: a runtime that imposed a
81
+ * model call on every turn to moderate it would be making a cost decision that belongs to the host.
82
+ */
83
+ readonly guardrails?: readonly Guardrail[];
84
+ /**
85
+ * A ceiling in tokens on the tool list handed to the model — REQ-045 (#204), task #210, AC-3.
86
+ *
87
+ * Here rather than only in the registry because *this* is where the tokens are actually spent: the registry's
88
+ * catalogue is what a client renders, and `buildTools` is what reaches the provider. A budget enforced in one
89
+ * and not the other would be a budget a deployment believes it has.
90
+ *
91
+ * Absent means no ceiling, which stays the default — a runtime that silently withheld tools from a model
92
+ * nobody had asked it to withhold would be making a correctness decision on the host's behalf.
93
+ */
94
+ readonly catalogBudget?: TokenBudget;
66
95
  /**
67
96
  * The question side of resumption — #163.
68
97
  *
@@ -93,6 +122,25 @@ export type DefaultEngineDeps = {
93
122
  readonly now?: () => number;
94
123
  /** The streaming primitive. Defaults to the models-layer `streamModelTurn`; overridden in tests. */
95
124
  readonly streamTurn?: (req: ModelTurnRequest) => AsyncIterable<NeutralStreamChunk>;
125
+ /**
126
+ * Turns "this run needs a connection" into somewhere to send a person — task #264.
127
+ *
128
+ * **Optional, and its absence means today's behaviour**: the tool call fails and the run fails with it. That
129
+ * is the correct default, not a degraded one — a host with no OAuth flow wired has no login URL to offer, and
130
+ * pausing a run for a consent screen nobody can reach would hang it for ever.
131
+ *
132
+ * Returning `null` says the same thing for one provider: this one is token-only, so fail rather than pause.
133
+ * That decision comes from `ToolkitAuth.modes`, which is why #260 kept `AuthMode` separate from
134
+ * `CredentialScheme` — an OAuth token is presented as a bearer, so the wire format cannot answer it.
135
+ */
136
+ readonly connectionConsent?: (input: {
137
+ readonly context: ExecutionContext;
138
+ readonly need: ConnectionNeed;
139
+ }) => Promise<{
140
+ readonly loginUrl: string;
141
+ readonly scopes: readonly string[];
142
+ readonly expiresAt: string;
143
+ } | null>;
96
144
  };
97
145
  export declare const createDefaultEngine: (deps: DefaultEngineDeps) => AgentEngine;
98
146
  //# sourceMappingURL=engine.d.ts.map