@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
@@ -0,0 +1,153 @@
1
+ /**
2
+ * The transport every sibling toolkit needs, once — REQ-052 (#224), task #225.
3
+ *
4
+ * `tools-github` grew one of these in #223 and it turned out to be four-fifths vendor-neutral: resolve the
5
+ * credential *per call*, pin the auth header to one validated host, parse JSON with an empty body tolerated,
6
+ * and map a failure onto the platform's closed error union. Only the last part is really vendor-specific, and
7
+ * only in its wording.
8
+ *
9
+ * Writing it twice more for Jira and Confluence — with nineteen toolkits after them — would mean copying four
10
+ * bugs along with it. #223 found two of them the hard way:
11
+ *
12
+ * - `JSON.parse("")` on a `204`. Two endpoints returned empty bodies on success and both tools failed outright
13
+ * while reporting a parse error about a correct response.
14
+ * - A plain-text body handled by catching the parse failure, which discarded the text and returned a
15
+ * placeholder. The tool typechecked and was entirely non-functional.
16
+ *
17
+ * Both are fixed here, so a toolkit gets them right by not writing them.
18
+ *
19
+ * **What stays with the vendor**: the failure vocabulary (`classify`), the base URL, and the fixed headers.
20
+ * A vendor that answers `200` with an error envelope — Slack's `ok: false`, GraphQL's `errors` — reads that
21
+ * envelope in its own module, because only it knows the shape.
22
+ */
23
+ import { AgentPlatformError } from "../core/errors.js";
24
+ import { credentialHeader } from "../tools/credentials.js";
25
+ import { createHttpClient } from "./http.js";
26
+ /**
27
+ * The default classification, and why each arm is what it is.
28
+ *
29
+ * A model reads these to decide whether trying again is sensible, so the distinction that matters is
30
+ * *retryable*, not how precisely the words describe the HTTP status. `unauthorized` on a `403` is the important
31
+ * one: told "forbidden", a model retries with different arguments, which is never the fix for a missing scope.
32
+ */
33
+ const defaultClassification = (failure, vendor) => {
34
+ const transport = failure.kind === "timeout" || failure.kind === "unreachable";
35
+ if (transport) {
36
+ return {
37
+ code: "provider_unavailable",
38
+ message: `${vendor} request failed (${failure.kind}): ${failure.reason}`,
39
+ retryable: true,
40
+ };
41
+ }
42
+ if (failure.status === 429) {
43
+ /**
44
+ * `Retry-After` is honoured when the vendor sent one — the default backoff is a guess, and a vendor that
45
+ * told you the number has removed the need to guess. Meta, X and Reddit all send it.
46
+ */
47
+ return {
48
+ code: "rate_limited",
49
+ message: `${vendor} rate limit reached: ${failure.reason}` +
50
+ (failure.retryAfterMs === undefined ? "" : ` Retry after ${Math.ceil(failure.retryAfterMs / 1000)}s.`),
51
+ retryable: true,
52
+ ...(failure.retryAfterMs === undefined ? {} : { retryAfterMs: failure.retryAfterMs }),
53
+ };
54
+ }
55
+ if (failure.status === 401 || failure.status === 403) {
56
+ return {
57
+ code: "unauthorized",
58
+ message: `${vendor} refused the credential (${failure.status}): ${failure.reason}. The credential may lack the permission this tool needs.`,
59
+ retryable: false,
60
+ };
61
+ }
62
+ return { code: "provider_error", message: `${vendor} request failed (${failure.kind}): ${failure.reason}`, retryable: false };
63
+ };
64
+ export const createVendorTransport = (config) => {
65
+ const base = config.baseUrl.replace(/\/$/, "");
66
+ const host = new URL(base).host;
67
+ /**
68
+ * One request, with the credential resolved now rather than at construction.
69
+ *
70
+ * Per call so a rotated token takes effect without a restart — a credential read once at startup is one that
71
+ * survives its own rotation, and the failure looks like the vendor rejecting a token that "has not changed".
72
+ *
73
+ * The header goes in through `headersFor`, which the runtime calls with the **validated** hostname only: a
74
+ * credential issued for one host cannot be sent to another by asking for a URL that merely mentions it.
75
+ */
76
+ const request = async (context, path, init = {}) => {
77
+ const credential = await config.resolver.resolve({ ref: config.credentialRef, context });
78
+ // One helper, so twenty toolkits do not each write their own base64 and get the padding wrong — #260.
79
+ const [headerName, headerValue] = credentialHeader(credential);
80
+ const client = createHttpClient({
81
+ ...(config.fetchImpl === undefined ? {} : { fetchImpl: config.fetchImpl }),
82
+ headersFor: (requested) => requested === host
83
+ ? {
84
+ [headerName.toLowerCase()]: headerValue,
85
+ ...(config.headers ?? {}),
86
+ // Overrides the vendor's default, which is JSON for every other call this transport makes.
87
+ ...(init.contentType === undefined ? {} : { "content-type": init.contentType }),
88
+ }
89
+ : undefined,
90
+ });
91
+ const outcome = await client.request({
92
+ url: `${base}${path}`,
93
+ ...(init.method === undefined ? {} : { method: init.method }),
94
+ ...(init.rawBody !== undefined
95
+ ? { body: init.rawBody }
96
+ : init.body === undefined
97
+ ? {}
98
+ : { body: JSON.stringify(init.body) }),
99
+ // Parsed here and never shown to the model verbatim, so the untrusted-content envelope would only corrupt
100
+ // the JSON. Anything rendered as prose keeps the default fence.
101
+ fence: false,
102
+ });
103
+ if (outcome.ok)
104
+ return outcome.body;
105
+ const failure = outcome;
106
+ const described = config.classify?.(failure) ?? defaultClassification(failure, config.vendor);
107
+ /**
108
+ * `Retry-After`, carried through — and it was being dropped.
109
+ *
110
+ * `HttpFailure` parses the header and `PlatformError` has a field for it, and this transport joined them by
111
+ * building an error that mentioned neither. Every toolkit on it therefore ignored a vendor that had said
112
+ * *exactly* how long to wait, and fell back to a generic backoff — which is both slower than necessary and,
113
+ * against a service that counts requests during the window, a way to stay throttled.
114
+ *
115
+ * A classifier may override it; otherwise the server's own number wins over any default, because the server
116
+ * is the only party that knows.
117
+ */
118
+ const retryAfterMs = described.retryAfterMs ?? failure.retryAfterMs;
119
+ /**
120
+ * `AgentPlatformError`, not a decorated `Error`.
121
+ *
122
+ * `toPlatformError` maps anything else to `{ code: "internal", retryable: false }` — so a rate limit thrown
123
+ * as `Object.assign(new Error(…), { retryable: true })` arrives at the model as permanently broken. The
124
+ * extra properties simply vanish and nothing warns you.
125
+ */
126
+ throw new AgentPlatformError(retryAfterMs === undefined ? described : { ...described, retryAfterMs });
127
+ };
128
+ return {
129
+ async json(context, path, init) {
130
+ const body = await request(context, path, init);
131
+ // **An empty body is a success.** A `204` from a delete, or a `201` from a fire-and-forget POST, has
132
+ // nothing to parse — and `JSON.parse("")` throws, which reported two correct responses as parse failures.
133
+ if (body.trim() === "")
134
+ return undefined;
135
+ try {
136
+ return JSON.parse(body);
137
+ }
138
+ catch {
139
+ throw new AgentPlatformError({
140
+ code: "provider_error",
141
+ message: `${config.vendor} returned a body that is not JSON`,
142
+ retryable: false,
143
+ });
144
+ }
145
+ },
146
+ text(context, path, init) {
147
+ // Not `json` with the parse failure caught: that discards the body, which is how a log-reading tool
148
+ // shipped returning a placeholder on every success.
149
+ return request(context, path, init ?? {});
150
+ },
151
+ };
152
+ };
153
+ //# sourceMappingURL=vendor.js.map
@@ -87,6 +87,19 @@ export type SearchProvider = {
87
87
  /** Build the request URL for a query. The key belongs in `headers`, not here, wherever the provider allows it. */
88
88
  readonly endpoint: (query: string, limit: number) => string;
89
89
  readonly headers?: Readonly<Record<string, string>>;
90
+ /**
91
+ * `POST`, for the providers that require it.
92
+ *
93
+ * Added when the first real adapters were written (#214): this contract was GET-only, and three of the four
94
+ * most-used providers — Tavily, Serper, Exa — are POST with a JSON body. A GET-only seam would have limited the
95
+ * catalogue to Brave and self-hosted SearXNG, which is not a "one contract, several providers" rule so much as
96
+ * a rule with two providers.
97
+ *
98
+ * Optional, so every existing GET provider is unchanged.
99
+ */
100
+ readonly method?: "GET" | "POST";
101
+ /** The request body for a `POST` provider. Ignored for `GET`. */
102
+ readonly body?: (query: string, limit: number) => unknown;
90
103
  /** Read the provider's JSON into hits. Returning `[]` means "searched, found nothing". */
91
104
  readonly parse: (payload: unknown) => readonly SearchHit[];
92
105
  };
@@ -97,10 +97,16 @@ export const createWebSearch = (config = {}) => {
97
97
  "directly.",
98
98
  };
99
99
  }
100
+ const method = provider.method ?? "GET";
100
101
  const outcome = await client.request({
101
102
  url: provider.endpoint(query, limit),
102
- headers: provider.headers,
103
+ ...(provider.headers === undefined ? {} : { headers: provider.headers }),
104
+ ...(method === "POST"
105
+ ? { method, body: JSON.stringify(provider.body?.(query, limit) ?? { query }) }
106
+ : {}),
103
107
  accept: "application/json",
108
+ // Parsed by the provider's own `parse` and never shown to the model verbatim, so the untrusted-content
109
+ // envelope would only corrupt the JSON. The *hits* are fenced downstream, where they are read as prose.
104
110
  fence: false,
105
111
  });
106
112
  if (!outcome.ok) {
@@ -0,0 +1,28 @@
1
+ /**
2
+ * What a tool costs a catalogue — REQ-045 (#204), task #210, AC-3.
3
+ *
4
+ * The budget itself is in `core/budget.ts`, shared with the skill catalogue. What is here is the part that knows
5
+ * about tools: how many tokens one entry is, which is the only thing the generic algorithm cannot know.
6
+ */
7
+ import type { ToolCatalogEntry, ToolDescriptor } from "./index.js";
8
+ /**
9
+ * Deliberately **not** re-exported here.
10
+ *
11
+ * `applyTokenBudget`, `TokenBudget` and `BudgetOutcome` live in `core/` and reach consumers through
12
+ * `@retinue/agentkit/runtime`. Re-exporting them from `./tools` as well gave one name two homes, which
13
+ * `public-surface.test.ts` fails on — and it is right to: a consumer reading two import paths for one function
14
+ * has to guess which is canonical, and the two will not stay in step.
15
+ */
16
+ /**
17
+ * What a compact catalogue entry costs.
18
+ *
19
+ * The fields a model actually reads, plus a small per-entry allowance for the JSON or Markdown scaffolding the
20
+ * assembler wraps them in — measured at ~35 tokens per entry in #221's harness, which is what this reproduces.
21
+ * An estimate rather than a tokenizer call: the point is a stable ceiling, and a budget that shifted with the
22
+ * model's tokenizer would make one deployment's 4,000 tokens another's 4,600.
23
+ */
24
+ export declare const ENTRY_OVERHEAD_TOKENS = 6;
25
+ export declare const entryTokens: (entry: ToolCatalogEntry) => number;
26
+ /** A preloaded tool carries its schemas, which is usually most of its cost. */
27
+ export declare const descriptorTokens: (descriptor: ToolDescriptor) => number;
28
+ //# sourceMappingURL=budget.d.ts.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * What a tool costs a catalogue — REQ-045 (#204), task #210, AC-3.
3
+ *
4
+ * The budget itself is in `core/budget.ts`, shared with the skill catalogue. What is here is the part that knows
5
+ * about tools: how many tokens one entry is, which is the only thing the generic algorithm cannot know.
6
+ */
7
+ import { estimateTokens } from "../core/tokens.js";
8
+ /**
9
+ * Deliberately **not** re-exported here.
10
+ *
11
+ * `applyTokenBudget`, `TokenBudget` and `BudgetOutcome` live in `core/` and reach consumers through
12
+ * `@retinue/agentkit/runtime`. Re-exporting them from `./tools` as well gave one name two homes, which
13
+ * `public-surface.test.ts` fails on — and it is right to: a consumer reading two import paths for one function
14
+ * has to guess which is canonical, and the two will not stay in step.
15
+ */
16
+ /**
17
+ * What a compact catalogue entry costs.
18
+ *
19
+ * The fields a model actually reads, plus a small per-entry allowance for the JSON or Markdown scaffolding the
20
+ * assembler wraps them in — measured at ~35 tokens per entry in #221's harness, which is what this reproduces.
21
+ * An estimate rather than a tokenizer call: the point is a stable ceiling, and a budget that shifted with the
22
+ * model's tokenizer would make one deployment's 4,000 tokens another's 4,600.
23
+ */
24
+ export const ENTRY_OVERHEAD_TOKENS = 6;
25
+ export const entryTokens = (entry) => estimateTokens(`${entry.name} ${entry.label} ${entry.category} ${entry.effect} ${entry.description}`) +
26
+ ENTRY_OVERHEAD_TOKENS;
27
+ /** A preloaded tool carries its schemas, which is usually most of its cost. */
28
+ export const descriptorTokens = (descriptor) => entryTokens({
29
+ name: descriptor.name,
30
+ label: descriptor.label,
31
+ description: descriptor.description,
32
+ category: descriptor.category,
33
+ effect: descriptor.effect,
34
+ }) + estimateTokens(JSON.stringify(descriptor.inputSchema ?? {}));
35
+ //# sourceMappingURL=budget.js.map
@@ -0,0 +1,316 @@
1
+ /**
2
+ * Credentials, referenced rather than held — REQ-047 (#206), task #214, AC-5; widened by REQ-063 (#259), #260.
3
+ *
4
+ * A toolkit needs a secret. The question is who holds it and when it is read, and getting that wrong is the
5
+ * decision that makes a multi-tenant deployment a rewrite of every package rather than a configuration change.
6
+ *
7
+ * ## Why a tool must not read the environment
8
+ *
9
+ * `process.env.GITHUB_TOKEN` inside a tool works perfectly for exactly one tenant. It is also the shape that
10
+ * twenty more toolkits will copy, because the first one did — and each of them then has to be rewritten when a
11
+ * second customer arrives, with their own token, for the same tool.
12
+ *
13
+ * So a tool takes a **reference** and the host resolves it. What the reference means — a row in a credential
14
+ * table, a secret manager path, an env var name in a single-tenant deployment — is the host's business and
15
+ * changes nothing about the tool.
16
+ *
17
+ * ## Why resolution happens at the point of use
18
+ *
19
+ * Not at construction. A credential read once at startup is a credential that survives its own rotation: the
20
+ * secret changes, every request keeps sending the old one, and the failure looks like the vendor rejecting a
21
+ * token that "has not changed". Resolving per call costs a lookup — cacheable by the host, which knows its own
22
+ * rotation window — and means a rotated secret takes effect without a restart.
23
+ *
24
+ * The corollary: **a resolved secret is never stored on the tool.** It lives in the local scope of one call.
25
+ *
26
+ * ## Why a miss throws
27
+ *
28
+ * Returning an empty string would send an unauthenticated request and surface as a vendor 401 several layers
29
+ * away, where the actual problem — nobody wired the credential — is invisible. A typed failure names the ref.
30
+ *
31
+ * ## Why a string was not enough — #260
32
+ *
33
+ * `resolve()` returned `Promise<string>`, which covers a bearer token and nothing else. Jira and Confluence
34
+ * want an account email *and* an API token as Basic auth; Atlassian's OAuth needs an access token *and* a cloud
35
+ * id discovered after consent; WhatsApp needs a token *and* a phone number id; a vendor using `X-Api-Key` needs
36
+ * a header name. Four of the fourteen integrations specified in `docs/23` cannot be expressed as a string, and
37
+ * every one of them would have grown its own side-channel.
38
+ *
39
+ * Two axes, deliberately kept apart, because they are answered by different people:
40
+ *
41
+ * - **`CredentialScheme`** — how the secret is presented on the wire. The toolkit knows this.
42
+ * - **`AuthMode`** — how the tenant *obtained* it: pasted a token, or completed an OAuth flow. The deployment
43
+ * knows this, and it is what decides whether an unconnected tool can pause a run for consent (#264) or must
44
+ * simply fail, since a token has no login URL to redirect to.
45
+ */
46
+ import type { ExecutionContext } from "../core/context.js";
47
+ import { type PlatformError } from "../core/errors.js";
48
+ /** An opaque handle. Its meaning belongs to the resolver, and no tool interprets it. */
49
+ export type CredentialRef = string;
50
+ /** How a secret is presented on the wire. */
51
+ export declare const CREDENTIAL_SCHEMES: readonly ["bearer", "basic", "custom-header"];
52
+ export type CredentialScheme = (typeof CREDENTIAL_SCHEMES)[number];
53
+ /**
54
+ * How a tenant supplied the credential.
55
+ *
56
+ * Not the same question as the scheme: an OAuth access token is presented as a bearer, so the two would collapse
57
+ * if they shared a type — and the collapse would lose exactly the fact #264 needs, which is whether there is a
58
+ * login URL to send someone to.
59
+ */
60
+ export declare const AUTH_MODES: readonly ["token", "oauth2"];
61
+ export type AuthMode = (typeof AUTH_MODES)[number];
62
+ /**
63
+ * Non-secret vendor identifiers that travel with the credential.
64
+ *
65
+ * Atlassian's cloud id, WhatsApp's phone number id, Slack's team id — discovered at connection time, needed on
66
+ * every request, and **not secrets**. They live here rather than in a toolkit's configuration because they are
67
+ * per *connection*: two tenants using the same toolkit have different ones, which is the same reason the token
68
+ * is not configuration either.
69
+ */
70
+ export type CredentialMetadata = Readonly<Record<string, string>>;
71
+ type WithMetadata = {
72
+ readonly metadata?: CredentialMetadata;
73
+ readonly mode?: AuthMode;
74
+ };
75
+ export type Credential = (WithMetadata & {
76
+ readonly scheme: "bearer";
77
+ readonly token: string;
78
+ }) | (WithMetadata & {
79
+ readonly scheme: "basic";
80
+ readonly username: string;
81
+ readonly password: string;
82
+ }) | (WithMetadata & {
83
+ readonly scheme: "custom-header";
84
+ readonly header: string;
85
+ readonly value: string;
86
+ });
87
+ /**
88
+ * Builds a credential whose secret is **not enumerable** — AC-7.
89
+ *
90
+ * A typed object is far more likely to reach a log line than a bare string was: it gets spread into an error's
91
+ * `details`, passed to a structured logger, or serialised into an audit row, and every one of those uses
92
+ * `JSON.stringify` or an object spread. So the secret is defined non-enumerably and `toJSON`/`toString`/
93
+ * `util.inspect` are overridden. `credential.token` still reads normally; `{ ...credential }`,
94
+ * `JSON.stringify(credential)` and `console.log(credential)` do not.
95
+ *
96
+ * This is defence in depth, not a licence: a caller that reads `.token` and logs *that* is still logging a
97
+ * secret, and no type can stop it.
98
+ */
99
+ export declare const createCredential: (input: Credential) => Credential;
100
+ /** A bearer credential, which is what most vendors want and what a plain string used to mean. */
101
+ export declare const bearer: (token: string, metadata?: CredentialMetadata, mode?: AuthMode) => Credential;
102
+ export interface CredentialResolver {
103
+ /**
104
+ * The credential behind a reference, for this caller.
105
+ *
106
+ * Takes the context because a reference is resolved *per tenant*: two tenants using the same toolkit name the
107
+ * same credential and mean different secrets, and a resolver that ignored the caller would hand one tenant
108
+ * another's token.
109
+ */
110
+ resolve(input: {
111
+ readonly ref: CredentialRef;
112
+ readonly context: ExecutionContext;
113
+ }): Promise<Credential>;
114
+ }
115
+ /**
116
+ * Told about every resolution — `docs/21`'s Connections section, "an audit record of every resolution".
117
+ *
118
+ * A sink rather than a return value, so a toolkit cannot forget to report and a host cannot be surprised by
119
+ * one. **Both outcomes are reported**: a refused resolution is the more interesting audit event, because a
120
+ * successful one is the normal case and a refused one is somebody asking for something they do not have.
121
+ *
122
+ * It never receives the credential. An audit trail that carries the secret is a second copy of the secret in a
123
+ * place designed to be kept for a long time.
124
+ */
125
+ export interface CredentialAudit {
126
+ onResolved(input: {
127
+ readonly ref: CredentialRef;
128
+ readonly context: ExecutionContext;
129
+ readonly scheme: CredentialScheme;
130
+ }): Promise<void> | void;
131
+ onRefused(input: {
132
+ readonly ref: CredentialRef;
133
+ readonly context: ExecutionContext;
134
+ readonly reason: string;
135
+ }): Promise<void> | void;
136
+ }
137
+ export declare const credentialMissing: (ref: CredentialRef, detail?: string) => PlatformError;
138
+ /**
139
+ * The credential resolved is not the shape this toolkit can present — #260 AC-2.
140
+ *
141
+ * Raised at **construction** where possible, and at resolution otherwise. A misconfiguration discovered as a
142
+ * vendor 401 an hour later is the failure this exists to prevent: the vendor's message says the token is
143
+ * invalid, which sends an operator to rotate a token that was never the problem.
144
+ */
145
+ export declare const credentialSchemeMismatch: (ref: CredentialRef, expected: readonly CredentialScheme[], got: CredentialScheme) => PlatformError;
146
+ /**
147
+ * A resolver over a plain map, for a single-tenant deployment and for tests.
148
+ *
149
+ * Shipped because the alternative is every host writing the same six lines, and the sixth one writing it with a
150
+ * fallback to `process.env` — which is the thing this module exists to prevent. Explicitly *not* environment
151
+ * backed: a host that wants that passes `{ github: process.env.GITHUB_TOKEN ?? "" }` and can see it doing so.
152
+ *
153
+ * **A bare string still works**, and stays the common case: it means a bearer token, which is what it meant
154
+ * before #260. The single-tenant path must not get harder because multi-tenant got possible.
155
+ */
156
+ export declare const createStaticCredentialResolver: (secrets: Readonly<Record<string, string | Credential>>) => CredentialResolver;
157
+ /**
158
+ * Wraps a resolver so every resolution is audited, and so a scheme mismatch is caught here rather than by the
159
+ * vendor — #260 AC-2 and AC-8.
160
+ *
161
+ * A wrapper rather than a change to every resolver: a host's own resolver stays a four-line object, and the
162
+ * cross-cutting obligations are added once, where they cannot be forgotten per toolkit.
163
+ */
164
+ export declare const withCredentialAudit: (resolver: CredentialResolver, audit: CredentialAudit, expected?: readonly CredentialScheme[]) => CredentialResolver;
165
+ /**
166
+ * The `Authorization`-style header a credential presents as.
167
+ *
168
+ * One function, so twenty toolkits do not each write their own base64 and get the padding wrong. Returns the
169
+ * header **name and value**, because `custom-header` does not use `Authorization`.
170
+ */
171
+ export declare const credentialHeader: (credential: Credential) => readonly [string, string];
172
+ /**
173
+ * What a toolkit accepts — #260 AC-2.
174
+ *
175
+ * Declared by the toolkit and checked at **construction**, so wiring a Basic-auth vendor with a bearer token is
176
+ * a startup error naming both, rather than a vendor 401 an hour later whose message says the token is invalid.
177
+ *
178
+ * `modes` is the other axis and is not the platform's business to validate — it is a fact about the vendor that
179
+ * a deployment reads: "GitHub takes a PAT or OAuth", "Google is OAuth only". #262 uses it to decide which
180
+ * connection flows to offer, and #264 uses it to decide whether an unconnected tool can pause a run for consent
181
+ * or must simply fail, since a token has no login URL.
182
+ */
183
+ export type ToolkitAuth = {
184
+ /** How a tenant may obtain the credential. */
185
+ readonly modes: readonly AuthMode[];
186
+ /** The scheme(s) this toolkit can present. A credential of any other scheme is refused. */
187
+ readonly schemes: readonly CredentialScheme[];
188
+ };
189
+ /**
190
+ * Refuses a toolkit configuration whose credential cannot be presented — at construction.
191
+ *
192
+ * Takes the *declared* scheme rather than resolving, because resolution needs a context and construction has
193
+ * none. A host that wires a static map can therefore be told immediately; a host whose resolver is dynamic is
194
+ * caught by `withCredentialAudit` at the first call instead. Both are before the vendor sees anything.
195
+ */
196
+ export declare const assertToolkitAuth: (ref: CredentialRef, auth: ToolkitAuth, declared: CredentialScheme | undefined) => void;
197
+ /**
198
+ * A credential that stops working at a known time.
199
+ *
200
+ * Additive by construction: `RefreshableCredential` is a `Credential` with one more field, so a resolver that
201
+ * returns a plain one is unchanged and the eight shipped toolkits compile untouched. That is AC-1, and it is a
202
+ * property of the *type* rather than a claim about the code.
203
+ *
204
+ * The refresh token is deliberately **not here.** It lives wherever the host keeps it — `ConnectionStore` seals
205
+ * it (#261) — and only the refresher ever sees it. Putting it on the credential would mean the longest-lived
206
+ * secret in an OAuth grant travelling through every toolkit that only needed the short-lived one.
207
+ */
208
+ export type RefreshableCredential = Credential & {
209
+ /** ISO 8601. When the vendor stops accepting this. */
210
+ readonly expiresAt: string;
211
+ };
212
+ /**
213
+ * Adds an expiry to a credential **without losing its secret protection** — AC-6.
214
+ *
215
+ * This exists because the obvious way to build one is wrong, and wrong invisibly. A host writing a
216
+ * `CredentialRefresher` reaches for:
217
+ *
218
+ * ```ts
219
+ * return { ...bearer(accessToken), expiresAt }; // ← the secret is now enumerable
220
+ * ```
221
+ *
222
+ * `createCredential` defines the secret **non-enumerably**, which is precisely what makes it survive a
223
+ * `JSON.stringify` into a log line — and precisely what a spread drops. The result looks identical, works
224
+ * identically, and serialises the token into the first structured log that touches it.
225
+ *
226
+ * Found by the AC-6 test failing against this repository's own test helper, which had made exactly that
227
+ * mistake. If the helper made it, a host will.
228
+ */
229
+ export declare const refreshable: (credential: Credential, expiresAt: string) => RefreshableCredential;
230
+ export declare const isRefreshable: (credential: Credential) => credential is RefreshableCredential;
231
+ /**
232
+ * How early a token is replaced.
233
+ *
234
+ * Sixty seconds, and the number is a *commitment* rather than a guess: a tool call can take tens of seconds —
235
+ * a slow vendor, a large upload, a retry — and a token that was valid when the call started must still be
236
+ * valid when it arrives. Refreshing exactly at expiry makes "expired mid-flight" the common case rather than
237
+ * the rare one, and that failure looks like an intermittent authentication bug.
238
+ *
239
+ * AC-5. Configurable because a deployment whose calls are slower than this needs more.
240
+ */
241
+ export declare const DEFAULT_REFRESH_SKEW_MS = 60000;
242
+ /** Whether a credential is expired, or close enough that it should be replaced before use. */
243
+ export declare const isExpiring: (credential: Credential, skewMs: number, now: number) => boolean;
244
+ /**
245
+ * Obtains a fresh credential for a reference.
246
+ *
247
+ * A port, because how a token is renewed differs entirely by vendor and by where the grant is stored. The
248
+ * host's implementation reads its own `ConnectionStore`, calls the vendor's token endpoint, re-seals the new
249
+ * refresh token and returns the new access credential — none of which the runtime should know about.
250
+ *
251
+ * It is given the ref and the context, and **not** the expired credential: it has to look the grant up anyway,
252
+ * and handing it a dead secret would be one more copy of one for no purpose.
253
+ */
254
+ export interface CredentialRefresher {
255
+ refresh(input: {
256
+ readonly ref: CredentialRef;
257
+ readonly context: ExecutionContext;
258
+ }): Promise<RefreshableCredential>;
259
+ }
260
+ /**
261
+ * A refresh that failed because the grant is gone, rather than because the network was.
262
+ *
263
+ * The distinction is the whole of AC-3, and it is not cosmetic: `invalid_grant` means a person must consent
264
+ * again and no amount of retrying will help, while a timeout means try again in a second. A runtime that
265
+ * conflates them either retries a dead grant forever or asks a user to re-authorise because of a blip.
266
+ */
267
+ export declare const REFRESH_GRANT_ERRORS: readonly ["invalid_grant", "invalid_client", "unauthorized_client", "access_denied"];
268
+ export declare const isGrantError: (error: unknown) => boolean;
269
+ export type RefreshingResolverOptions = {
270
+ readonly skewMs?: number;
271
+ /** Injectable so a test can move time without waiting for it. */
272
+ readonly now?: () => number;
273
+ /**
274
+ * Told that a refresh happened. Never told the token — see `CredentialAudit`, same reasoning.
275
+ *
276
+ * `expiresAt` is included because it is not a secret and it is the one thing an operator debugging a
277
+ * refresh loop actually needs.
278
+ */
279
+ readonly onRefreshed?: (input: {
280
+ readonly ref: CredentialRef;
281
+ readonly tenantId: string;
282
+ readonly expiresAt: string;
283
+ }) => void;
284
+ };
285
+ /**
286
+ * Wraps a resolver so an expiring credential is renewed before it is handed out.
287
+ *
288
+ * A wrapper, like `withCredentialAudit`, and for the same reason: the eight shipped toolkits already resolve
289
+ * **per call**, so they pick this up without a line changing. A toolkit that cached a credential at
290
+ * construction would defeat it, which is why `createGitHubToolkit` and every sibling resolve inside `call()`.
291
+ *
292
+ * ## Time-driven, never 401-driven — AC-7
293
+ *
294
+ * The obvious design is to refresh when the vendor returns 401. It is wrong, and worth stating plainly because
295
+ * it is what most integrations do:
296
+ *
297
+ * A 401 is what a vendor returns for an expired token, a **revoked** grant, a token for the wrong tenant, and a
298
+ * scope the grant never had. Refreshing on 401 therefore turns a revoked grant into an infinite refresh loop
299
+ * against the vendor's token endpoint, and turns a missing scope into a refresh that succeeds and a call that
300
+ * fails again identically. Neither is diagnosable from the outside.
301
+ *
302
+ * Time is the only signal that means what it says: a token with an expiry in the past is expired, and nothing
303
+ * else is inferred from it. A 401 on a freshly-refreshed token is a real error and is surfaced as one.
304
+ *
305
+ * ## One refresh, not N — AC-2
306
+ *
307
+ * Twenty concurrent tool calls hitting an expired token must produce **one** refresh. Refresh endpoints rate
308
+ * limit, and — worse — several vendors invalidate the previous refresh token when one is used, so N concurrent
309
+ * refreshes race to invalidate each other and log the deployment out permanently.
310
+ *
311
+ * The in-flight promise is stored *before* the first await, so a second caller entering the function
312
+ * synchronously after the first still finds it.
313
+ */
314
+ export declare const withRefreshingCredentials: (resolver: CredentialResolver, refresher: CredentialRefresher, options?: RefreshingResolverOptions) => CredentialResolver;
315
+ export {};
316
+ //# sourceMappingURL=credentials.d.ts.map