@anchrd/intel-api 0.2.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 (114) hide show
  1. package/README.md +96 -0
  2. package/bin/intel.mjs +134 -0
  3. package/dist/adapters/cloudflare/cloudflare-flow-workflow.d.ts +21 -0
  4. package/dist/adapters/cloudflare/cloudflare-flow-workflow.js +39 -0
  5. package/dist/adapters/cloudflare/cloudflare.d.ts +8 -0
  6. package/dist/adapters/cloudflare/cloudflare.js +200 -0
  7. package/dist/adapters/cloudflare/cloudflare.types.d.ts +29 -0
  8. package/dist/adapters/cloudflare/cloudflare.types.js +1 -0
  9. package/dist/adapters/content/content.d.ts +3 -0
  10. package/dist/adapters/content/content.js +22 -0
  11. package/dist/adapters/content/content.types.d.ts +14 -0
  12. package/dist/adapters/content/content.types.js +1 -0
  13. package/dist/adapters/db/db-flows.d.ts +6 -0
  14. package/dist/adapters/db/db-flows.js +466 -0
  15. package/dist/adapters/db/db-indexing.d.ts +3 -0
  16. package/dist/adapters/db/db-indexing.js +70 -0
  17. package/dist/adapters/db/db-oauth.d.ts +3 -0
  18. package/dist/adapters/db/db-oauth.js +26 -0
  19. package/dist/adapters/db/db-tools.d.ts +6 -0
  20. package/dist/adapters/db/db-tools.js +200 -0
  21. package/dist/adapters/db/db.d.ts +6 -0
  22. package/dist/adapters/db/db.js +755 -0
  23. package/dist/adapters/db/db.types.d.ts +16 -0
  24. package/dist/adapters/db/db.types.js +1 -0
  25. package/dist/adapters/document-converter/document-converter.d.ts +2 -0
  26. package/dist/adapters/document-converter/document-converter.js +25 -0
  27. package/dist/adapters/document-converter/document-converter.types.d.ts +9 -0
  28. package/dist/adapters/document-converter/document-converter.types.js +1 -0
  29. package/dist/adapters/flow-runtime/flow-runtime.d.ts +3 -0
  30. package/dist/adapters/flow-runtime/flow-runtime.js +22 -0
  31. package/dist/adapters/flow-runtime/flow-runtime.types.d.ts +19 -0
  32. package/dist/adapters/flow-runtime/flow-runtime.types.js +1 -0
  33. package/dist/adapters/index-queue/index-queue.d.ts +10 -0
  34. package/dist/adapters/index-queue/index-queue.js +12 -0
  35. package/dist/adapters/index-queue/index-queue.types.d.ts +11 -0
  36. package/dist/adapters/index-queue/index-queue.types.js +1 -0
  37. package/dist/adapters/json-schema/json-schema.d.ts +2 -0
  38. package/dist/adapters/json-schema/json-schema.js +31 -0
  39. package/dist/adapters/openid/openid.d.ts +5 -0
  40. package/dist/adapters/openid/openid.js +159 -0
  41. package/dist/adapters/portal-tokens/portal-tokens.d.ts +8 -0
  42. package/dist/adapters/portal-tokens/portal-tokens.js +71 -0
  43. package/dist/adapters/remote-tools/remote-tools.d.ts +5 -0
  44. package/dist/adapters/remote-tools/remote-tools.js +109 -0
  45. package/dist/adapters/semantic-index/semantic-index.d.ts +2 -0
  46. package/dist/adapters/semantic-index/semantic-index.js +57 -0
  47. package/dist/adapters/semantic-index/semantic-index.types.d.ts +32 -0
  48. package/dist/adapters/semantic-index/semantic-index.types.js +1 -0
  49. package/dist/adapters/session-cookie/session-cookie.d.ts +5 -0
  50. package/dist/adapters/session-cookie/session-cookie.js +81 -0
  51. package/dist/adapters/tool-source-policy/tool-source-policy.d.ts +1 -0
  52. package/dist/adapters/tool-source-policy/tool-source-policy.js +22 -0
  53. package/dist/auth/auth.d.ts +2 -0
  54. package/dist/auth/auth.js +176 -0
  55. package/dist/auth/auth.types.d.ts +104 -0
  56. package/dist/auth/auth.types.js +1 -0
  57. package/dist/build/build.d.ts +2 -0
  58. package/dist/build/build.js +125 -0
  59. package/dist/build/build.types.d.ts +17 -0
  60. package/dist/build/build.types.js +1 -0
  61. package/dist/cli/cli.d.ts +4 -0
  62. package/dist/cli/cli.js +141 -0
  63. package/dist/cli/cli.types.d.ts +14 -0
  64. package/dist/cli/cli.types.js +1 -0
  65. package/dist/flows/flows.d.ts +4 -0
  66. package/dist/flows/flows.js +409 -0
  67. package/dist/flows/flows.types.d.ts +108 -0
  68. package/dist/flows/flows.types.js +1 -0
  69. package/dist/http/http.d.ts +8 -0
  70. package/dist/http/http.js +286 -0
  71. package/dist/http/http.types.d.ts +14 -0
  72. package/dist/http/http.types.js +1 -0
  73. package/dist/indexing/indexing.d.ts +6 -0
  74. package/dist/indexing/indexing.js +48 -0
  75. package/dist/indexing/indexing.types.d.ts +10 -0
  76. package/dist/indexing/indexing.types.js +1 -0
  77. package/dist/intel/intel.d.ts +3 -0
  78. package/dist/intel/intel.js +77 -0
  79. package/dist/intel/intel.types.d.ts +14 -0
  80. package/dist/intel/intel.types.js +1 -0
  81. package/dist/knowledge/knowledge.d.ts +2 -0
  82. package/dist/knowledge/knowledge.js +479 -0
  83. package/dist/knowledge/knowledge.types.d.ts +162 -0
  84. package/dist/knowledge/knowledge.types.js +1 -0
  85. package/dist/mcp/mcp.d.ts +2 -0
  86. package/dist/mcp/mcp.js +484 -0
  87. package/dist/mcp/mcp.types.d.ts +10 -0
  88. package/dist/mcp/mcp.types.js +1 -0
  89. package/dist/prepare/prepare.d.ts +4 -0
  90. package/dist/prepare/prepare.js +16 -0
  91. package/dist/prepare/prepare.types.d.ts +9 -0
  92. package/dist/prepare/prepare.types.js +1 -0
  93. package/dist/shared/gate-authorization/gate-authorization.d.ts +4 -0
  94. package/dist/shared/gate-authorization/gate-authorization.js +19 -0
  95. package/dist/shared/intel-error/intel-error.d.ts +5 -0
  96. package/dist/shared/intel-error/intel-error.js +10 -0
  97. package/dist/shared/problem-details/problem-details.d.ts +8 -0
  98. package/dist/shared/problem-details/problem-details.js +4 -0
  99. package/dist/shared/safe-return-path/safe-return-path.d.ts +2 -0
  100. package/dist/shared/safe-return-path/safe-return-path.js +11 -0
  101. package/dist/shared/sha256/sha256.d.ts +1 -0
  102. package/dist/shared/sha256/sha256.js +9 -0
  103. package/dist/tools/tools.d.ts +2 -0
  104. package/dist/tools/tools.js +107 -0
  105. package/dist/tools/tools.types.d.ts +56 -0
  106. package/dist/tools/tools.types.js +1 -0
  107. package/examples/branding/favicon.svg +5 -0
  108. package/examples/branding/logo.svg +5 -0
  109. package/examples/branding/theme.css +16 -0
  110. package/examples/dev.vars.example +7 -0
  111. package/examples/intel.json +9 -0
  112. package/migrations/0000_intel_foundation.sql +203 -0
  113. package/migrations/0001_portal_native_tools.sql +21 -0
  114. package/package.json +62 -0
@@ -0,0 +1,8 @@
1
+ import type { Authorized } from "@anchrd/gate-sdk";
2
+ import { Hono } from "hono";
3
+ import type { HttpDeps } from "./http.types.js";
4
+ export declare function createHttp(deps: HttpDeps): Hono<{
5
+ Variables: {
6
+ authorization: Authorized;
7
+ };
8
+ }, import("hono/types").BlankSchema, "/">;
@@ -0,0 +1,286 @@
1
+ import { ArchiveKnowledgeNodeInput, CompleteFlowRunStepInput, CreateFlowInput, CreateKnowledgeLinkInput, CreateKnowledgeNodeInput, DeleteKnowledgeLinkInput, ExecuteToolInput, KnowledgeGraphInput, ListKnowledgeNodesInput, PublishFlowInput, RevokeFlowGrantInput, RevokeKnowledgeGrantInput, SaveFlowVersionInput, SaveKnowledgeAttachmentInput, SaveKnowledgeVersionInput, SearchKnowledgeInput, ShareFlowInput, ShareKnowledgeInput, StartFlowRunInput, TestToolInput, UpdateKnowledgeNodeInput, } from "@anchrd/intel-contract";
2
+ import { Hono } from "hono";
3
+ import { z } from "zod";
4
+ import { authorizeBearer, bearer } from "../shared/gate-authorization/gate-authorization.js";
5
+ import { IntelError } from "../shared/intel-error/intel-error.js";
6
+ import { problemDetails as problem } from "../shared/problem-details/problem-details.js";
7
+ function asActor(authorization) {
8
+ return {
9
+ id: authorization.identity.id,
10
+ email: authorization.identity.email,
11
+ canReindex: authorization.can("intel", "admin"),
12
+ };
13
+ }
14
+ function asToolActor(authorization) {
15
+ return {
16
+ id: authorization.identity.id,
17
+ email: authorization.identity.email,
18
+ canExecute: authorization.can("tools", "execute"),
19
+ };
20
+ }
21
+ // RFC 5987 ext-value: encodeURIComponent leaves ' * ! ( ) unescaped, and a bare apostrophe is a
22
+ // delimiter that would make the header ambiguous. The ASCII filename stays as a fallback.
23
+ function contentDisposition(title) {
24
+ const encoded = encodeURIComponent(title).replace(/['()*!]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
25
+ const ascii = title.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_");
26
+ return `attachment; filename="${ascii}"; filename*=UTF-8''${encoded}`;
27
+ }
28
+ function asFlowActor(authorization) {
29
+ return {
30
+ id: authorization.identity.id,
31
+ email: authorization.identity.email,
32
+ canRun: authorization.can("flows", "run"),
33
+ canApprove: authorization.can("flows", "approve"),
34
+ };
35
+ }
36
+ export function createHttp(deps) {
37
+ const app = new Hono();
38
+ async function authenticated(headers) {
39
+ const token = bearer(headers) ?? (await deps.auth?.resolve(headers))?.bearer ?? null;
40
+ return token ? await authorizeBearer(deps.gate, token, deps.resource) : null;
41
+ }
42
+ app.onError((error, context) => {
43
+ if (error instanceof IntelError) {
44
+ return context.json(problem(error.status, error.code, error.message), error.status);
45
+ }
46
+ if (error instanceof z.ZodError) {
47
+ return context.json(problem(400, "invalid_request", "Request validation failed", z.prettifyError(error)), 400);
48
+ }
49
+ return context.json(problem(500, "internal_error", "Internal server error"), 500);
50
+ });
51
+ app.use("*", async (context, next) => {
52
+ const authorization = await authenticated(context.req.raw.headers);
53
+ if (!authorization) {
54
+ context.header("WWW-Authenticate", `Bearer resource_metadata="${deps.resourceMetadataUrl}"`);
55
+ return context.json(problem(401, "authentication_required", "Authentication required"), 401);
56
+ }
57
+ context.set("authorization", authorization);
58
+ await next();
59
+ });
60
+ function requireCapability(context, handle, fn) {
61
+ const authorization = context.get("authorization");
62
+ if (!authorization.can(handle, fn)) {
63
+ throw new IntelError(403, "permission_required", "Permission required");
64
+ }
65
+ return authorization;
66
+ }
67
+ app.get("/knowledge", async (context) => {
68
+ const auth = requireCapability(context, "knowledge", "read");
69
+ const url = new URL(context.req.url);
70
+ const unknownQuery = [];
71
+ url.searchParams.forEach((_value, key) => {
72
+ if (key !== "parentId" && key !== "includeArchived")
73
+ unknownQuery.push(key);
74
+ });
75
+ if (unknownQuery.length) {
76
+ throw new IntelError(400, "invalid_request", `Unknown query parameters: ${unknownQuery.join(", ")}`);
77
+ }
78
+ const input = ListKnowledgeNodesInput.parse({
79
+ parentId: url.searchParams.get("parentId"),
80
+ includeArchived: url.searchParams.get("includeArchived") === "true",
81
+ });
82
+ return context.json(await deps.knowledge.list(asActor(auth), input));
83
+ });
84
+ app.get("/knowledge/graph", async (context) => {
85
+ const auth = requireCapability(context, "knowledge", "read");
86
+ const url = new URL(context.req.url);
87
+ const input = KnowledgeGraphInput.parse({
88
+ limit: url.searchParams.has("limit") ? Number(url.searchParams.get("limit")) : undefined,
89
+ });
90
+ return context.json(await deps.knowledge.graph(asActor(auth), input));
91
+ });
92
+ app.get("/knowledge/:nodeId", async (context) => {
93
+ const auth = requireCapability(context, "knowledge", "read");
94
+ return context.json(await deps.knowledge.get(asActor(auth), context.req.param("nodeId")));
95
+ });
96
+ app.get("/knowledge/:nodeId/versions", async (context) => {
97
+ const auth = requireCapability(context, "knowledge", "read");
98
+ return context.json(await deps.knowledge.listVersions(asActor(auth), context.req.param("nodeId")));
99
+ });
100
+ app.get("/knowledge/:nodeId/attachment", async (context) => {
101
+ const auth = requireCapability(context, "knowledge", "read");
102
+ const { attachment, body } = await deps.knowledge.readAttachment(asActor(auth), context.req.param("nodeId"));
103
+ return new Response(body, {
104
+ headers: {
105
+ "cache-control": "private, no-store",
106
+ "content-disposition": contentDisposition(attachment.node.title),
107
+ "content-type": attachment.version.mediaType,
108
+ "x-content-type-options": "nosniff",
109
+ },
110
+ });
111
+ });
112
+ app.get("/knowledge/:nodeId/links", async (context) => {
113
+ const auth = requireCapability(context, "knowledge", "read");
114
+ return context.json(await deps.knowledge.listLinks(asActor(auth), context.req.param("nodeId")));
115
+ });
116
+ app.post("/knowledge/search", async (context) => {
117
+ const auth = requireCapability(context, "knowledge", "read");
118
+ const input = SearchKnowledgeInput.parse(await context.req.json().catch(() => null));
119
+ return context.json(await deps.knowledge.search(asActor(auth), input));
120
+ });
121
+ app.post("/search/reindex", async (context) => {
122
+ const auth = requireCapability(context, "intel", "admin");
123
+ z.strictObject({}).parse(await context.req.json().catch(() => null));
124
+ return context.json(await deps.knowledge.reindex(asActor(auth)), 202);
125
+ });
126
+ app.post("/knowledge", async (context) => {
127
+ const auth = requireCapability(context, "knowledge", "create");
128
+ const input = CreateKnowledgeNodeInput.parse(await context.req.json().catch(() => null));
129
+ return context.json(await deps.knowledge.create(asActor(auth), input), 201);
130
+ });
131
+ app.post("/knowledge/:nodeId/versions", async (context) => {
132
+ const auth = requireCapability(context, "knowledge", "write");
133
+ const input = SaveKnowledgeVersionInput.parse(await context.req.json().catch(() => null));
134
+ if (input.nodeId !== context.req.param("nodeId")) {
135
+ throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
136
+ }
137
+ return context.json(await deps.knowledge.save(asActor(auth), input), 201);
138
+ });
139
+ app.post("/knowledge/:nodeId/attachment", async (context) => {
140
+ const auth = requireCapability(context, "knowledge", "write");
141
+ const input = SaveKnowledgeAttachmentInput.parse(await context.req.json().catch(() => null));
142
+ if (input.nodeId !== context.req.param("nodeId")) {
143
+ throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
144
+ }
145
+ return context.json(await deps.knowledge.saveAttachment(asActor(auth), input), 201);
146
+ });
147
+ app.patch("/knowledge/:nodeId", async (context) => {
148
+ const auth = requireCapability(context, "knowledge", "write");
149
+ const input = UpdateKnowledgeNodeInput.parse(await context.req.json().catch(() => null));
150
+ if (input.nodeId !== context.req.param("nodeId")) {
151
+ throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
152
+ }
153
+ return context.json(await deps.knowledge.update(asActor(auth), input));
154
+ });
155
+ app.post("/knowledge/:nodeId/archive", async (context) => {
156
+ const auth = requireCapability(context, "knowledge", "write");
157
+ const input = ArchiveKnowledgeNodeInput.parse(await context.req.json().catch(() => null));
158
+ if (input.nodeId !== context.req.param("nodeId")) {
159
+ throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
160
+ }
161
+ return context.json(await deps.knowledge.archive(asActor(auth), input));
162
+ });
163
+ app.post("/knowledge/:nodeId/links", async (context) => {
164
+ const auth = requireCapability(context, "knowledge", "write");
165
+ const input = CreateKnowledgeLinkInput.parse(await context.req.json().catch(() => null));
166
+ if (input.sourceNodeId !== context.req.param("nodeId")) {
167
+ throw new IntelError(400, "node_id_mismatch", "Path and body source node IDs differ");
168
+ }
169
+ return context.json(await deps.knowledge.createLink(asActor(auth), input), 201);
170
+ });
171
+ app.post("/knowledge/:nodeId/links/:linkId/revoke", async (context) => {
172
+ const auth = requireCapability(context, "knowledge", "write");
173
+ const input = DeleteKnowledgeLinkInput.parse(await context.req.json().catch(() => null));
174
+ if (input.sourceNodeId !== context.req.param("nodeId") ||
175
+ input.linkId !== context.req.param("linkId")) {
176
+ throw new IntelError(400, "link_id_mismatch", "Path and body Knowledge link IDs differ");
177
+ }
178
+ return context.json(await deps.knowledge.deleteLink(asActor(auth), input));
179
+ });
180
+ app.get("/knowledge/:nodeId/grants", async (context) => {
181
+ const auth = requireCapability(context, "knowledge", "share");
182
+ return context.json(await deps.knowledge.listGrants(asActor(auth), context.req.param("nodeId")));
183
+ });
184
+ app.post("/knowledge/:nodeId/grants", async (context) => {
185
+ const auth = requireCapability(context, "knowledge", "share");
186
+ const input = ShareKnowledgeInput.parse(await context.req.json().catch(() => null));
187
+ if (input.resourceId !== context.req.param("nodeId")) {
188
+ throw new IntelError(400, "node_id_mismatch", "Path and body resource IDs differ");
189
+ }
190
+ return context.json(await deps.knowledge.share(asActor(auth), input), 201);
191
+ });
192
+ app.post("/knowledge/:nodeId/grants/:grantId/revoke", async (context) => {
193
+ const auth = requireCapability(context, "knowledge", "share");
194
+ const input = RevokeKnowledgeGrantInput.parse(await context.req.json().catch(() => null));
195
+ if (input.resourceId !== context.req.param("nodeId") ||
196
+ input.grantId !== context.req.param("grantId")) {
197
+ throw new IntelError(400, "grant_id_mismatch", "Path and body grant IDs differ");
198
+ }
199
+ return context.json(await deps.knowledge.revokeGrant(asActor(auth), input));
200
+ });
201
+ app.get("/flows", async (context) => {
202
+ const auth = requireCapability(context, "flows", "read");
203
+ return context.json(await deps.flows.list(asFlowActor(auth)));
204
+ });
205
+ app.get("/flows/:flowId", async (context) => {
206
+ const auth = requireCapability(context, "flows", "read");
207
+ return context.json(await deps.flows.get(asFlowActor(auth), context.req.param("flowId")));
208
+ });
209
+ app.get("/flows/:flowId/grants", async (context) => {
210
+ const auth = requireCapability(context, "flows", "share");
211
+ return context.json(await deps.flows.listGrants(asFlowActor(auth), context.req.param("flowId")));
212
+ });
213
+ app.post("/flows/:flowId/grants", async (context) => {
214
+ const auth = requireCapability(context, "flows", "share");
215
+ const input = ShareFlowInput.parse(await context.req.json().catch(() => null));
216
+ if (input.resourceId !== context.req.param("flowId")) {
217
+ throw new IntelError(400, "flow_id_mismatch", "Path and body flow IDs differ");
218
+ }
219
+ return context.json(await deps.flows.share(asFlowActor(auth), input), 201);
220
+ });
221
+ app.post("/flows/:flowId/grants/:grantId/revoke", async (context) => {
222
+ const auth = requireCapability(context, "flows", "share");
223
+ const input = RevokeFlowGrantInput.parse(await context.req.json().catch(() => null));
224
+ if (input.resourceId !== context.req.param("flowId") ||
225
+ input.grantId !== context.req.param("grantId")) {
226
+ throw new IntelError(400, "grant_id_mismatch", "Path and body grant IDs differ");
227
+ }
228
+ return context.json(await deps.flows.revokeGrant(asFlowActor(auth), input));
229
+ });
230
+ app.post("/flows", async (context) => {
231
+ const auth = requireCapability(context, "flows", "create");
232
+ const input = CreateFlowInput.parse(await context.req.json().catch(() => null));
233
+ return context.json(await deps.flows.create(asFlowActor(auth), input), 201);
234
+ });
235
+ app.post("/flows/:flowId/versions", async (context) => {
236
+ const auth = requireCapability(context, "flows", "write");
237
+ const input = SaveFlowVersionInput.parse(await context.req.json().catch(() => null));
238
+ if (input.flowId !== context.req.param("flowId")) {
239
+ throw new IntelError(400, "flow_id_mismatch", "Path and body flow IDs differ");
240
+ }
241
+ return context.json(await deps.flows.save(asFlowActor(auth), input), 201);
242
+ });
243
+ app.post("/flows/:flowId/publish", async (context) => {
244
+ const auth = requireCapability(context, "flows", "publish");
245
+ const input = PublishFlowInput.parse(await context.req.json().catch(() => null));
246
+ if (input.flowId !== context.req.param("flowId")) {
247
+ throw new IntelError(400, "flow_id_mismatch", "Path and body flow IDs differ");
248
+ }
249
+ return context.json(await deps.flows.publish(asFlowActor(auth), input));
250
+ });
251
+ app.post("/flows/:flowId/runs", async (context) => {
252
+ const auth = requireCapability(context, "flows", "run");
253
+ const input = StartFlowRunInput.parse(await context.req.json().catch(() => null));
254
+ if (input.flowId !== context.req.param("flowId")) {
255
+ throw new IntelError(400, "flow_id_mismatch", "Path and body flow IDs differ");
256
+ }
257
+ return context.json(await deps.flows.start(asFlowActor(auth), input), 201);
258
+ });
259
+ app.get("/flow-runs/:runId", async (context) => {
260
+ const auth = requireCapability(context, "flows", "read");
261
+ return context.json(await deps.flows.getRun(asFlowActor(auth), context.req.param("runId")));
262
+ });
263
+ app.post("/flow-runs/:runId/complete", async (context) => {
264
+ const auth = requireCapability(context, "flows", "run");
265
+ const input = CompleteFlowRunStepInput.parse(await context.req.json().catch(() => null));
266
+ if (input.runId !== context.req.param("runId")) {
267
+ throw new IntelError(400, "run_id_mismatch", "Path and body run IDs differ");
268
+ }
269
+ return context.json(await deps.flows.completeStep(asFlowActor(auth), input));
270
+ });
271
+ app.get("/tools", async (context) => {
272
+ const auth = requireCapability(context, "tools", "read");
273
+ return context.json(await deps.tools.catalog(asToolActor(auth)));
274
+ });
275
+ app.post("/tools/test", async (context) => {
276
+ const auth = requireCapability(context, "tools", "test");
277
+ const input = TestToolInput.parse(await context.req.json().catch(() => null));
278
+ return context.json(await deps.tools.test(asToolActor(auth), input));
279
+ });
280
+ app.post("/tools/execute", async (context) => {
281
+ const auth = requireCapability(context, "tools", "execute");
282
+ const input = ExecuteToolInput.parse(await context.req.json().catch(() => null));
283
+ return context.json(await deps.tools.execute(asToolActor(auth), input));
284
+ });
285
+ return app;
286
+ }
@@ -0,0 +1,14 @@
1
+ import type { GateClient } from "@anchrd/gate-sdk";
2
+ import type { BrowserAuth } from "../auth/auth.types.js";
3
+ import type { FlowService } from "../flows/flows.types.js";
4
+ import type { KnowledgeService } from "../knowledge/knowledge.types.js";
5
+ import type { ToolService } from "../tools/tools.types.js";
6
+ export interface HttpDeps {
7
+ gate: Pick<GateClient, "authorize">;
8
+ knowledge: KnowledgeService;
9
+ flows: FlowService;
10
+ tools: ToolService;
11
+ resource: string;
12
+ resourceMetadataUrl: string;
13
+ auth?: Pick<BrowserAuth, "resolve">;
14
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { IndexingDeps } from "./indexing.types.js";
2
+ export declare class PermanentIndexingError extends Error {
3
+ }
4
+ export declare function createIndexing(deps: IndexingDeps): {
5
+ index(versionId: string): Promise<void>;
6
+ };
@@ -0,0 +1,48 @@
1
+ import { BlockNoteDocument, BlockNoteMediaType } from "@anchrd/intel-contract";
2
+ export class PermanentIndexingError extends Error {
3
+ }
4
+ function indexText(mediaType, content) {
5
+ if (mediaType !== BlockNoteMediaType)
6
+ return content;
7
+ try {
8
+ return BlockNoteDocument.parse(JSON.parse(content)).markdown;
9
+ }
10
+ catch (error) {
11
+ // Immutable content never becomes parsable on a retry, so treating this as transient would
12
+ // requeue the version until the queue gives up and would bury the real cause.
13
+ throw new PermanentIndexingError(`Knowledge version content is not a valid BlockNote document: ${error instanceof Error ? error.message : "unknown parse failure"}`);
14
+ }
15
+ }
16
+ export function createIndexing(deps) {
17
+ return {
18
+ async index(versionId) {
19
+ const target = await deps.repository.getTarget(versionId);
20
+ if (!target)
21
+ return;
22
+ const canonical = target.kind === "attachment"
23
+ ? await deps.content.getBytes(target.contentKey)
24
+ : await deps.content.get(target.contentKey);
25
+ if (canonical === null) {
26
+ await deps.repository.markError(versionId, "content_missing", deps.now().toISOString());
27
+ throw new Error("Knowledge version content is missing");
28
+ }
29
+ try {
30
+ const text = typeof canonical === "string"
31
+ ? indexText(target.mediaType, canonical)
32
+ : target.mediaType.startsWith("text/")
33
+ ? new TextDecoder().decode(canonical)
34
+ : await deps.converter?.convert(target.title, target.mediaType, canonical);
35
+ if (text === undefined) {
36
+ throw new PermanentIndexingError(`No document converter is configured for ${target.mediaType}`);
37
+ }
38
+ await deps.repository.replace(target, text);
39
+ await deps.semantic?.replace(target, text);
40
+ await deps.repository.markIndexed(versionId, deps.now().toISOString());
41
+ }
42
+ catch (error) {
43
+ await deps.repository.markError(versionId, error instanceof Error ? error.message : "indexing_failed", deps.now().toISOString());
44
+ throw error;
45
+ }
46
+ },
47
+ };
48
+ }
@@ -0,0 +1,10 @@
1
+ import type { DocumentConverter } from "../adapters/document-converter/document-converter.types.js";
2
+ import type { SemanticIndex } from "../adapters/semantic-index/semantic-index.types.js";
3
+ import type { ContentStore, KnowledgeIndexRepository } from "../knowledge/knowledge.types.js";
4
+ export interface IndexingDeps {
5
+ repository: KnowledgeIndexRepository;
6
+ content: ContentStore;
7
+ semantic?: SemanticIndex | undefined;
8
+ converter?: DocumentConverter | undefined;
9
+ now(): Date;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { Hono } from "hono";
2
+ import type { IntelDeps } from "./intel.types.js";
3
+ export declare function createIntel(deps: IntelDeps): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
@@ -0,0 +1,77 @@
1
+ import { Hono } from "hono";
2
+ import { createHttp } from "../http/http.js";
3
+ import { handleMcp } from "../mcp/mcp.js";
4
+ import { authorize, authorizeBearer } from "../shared/gate-authorization/gate-authorization.js";
5
+ import { IntelError } from "../shared/intel-error/intel-error.js";
6
+ import { problemDetails } from "../shared/problem-details/problem-details.js";
7
+ export function createIntel(deps) {
8
+ const baseUrl = deps.baseUrl.replace(/\/+$/, "");
9
+ const resource = `${baseUrl}/mcp`;
10
+ const resourceMetadataUrl = `${baseUrl}/.well-known/oauth-protected-resource`;
11
+ const app = new Hono();
12
+ app.onError((error, context) => {
13
+ if (error instanceof IntelError) {
14
+ return context.json(problemDetails(error.status, error.code, error.message), error.status);
15
+ }
16
+ return context.json(problemDetails(500, "internal_error", "Internal server error"), 500);
17
+ });
18
+ app.get("/health", (context) => context.json({ status: "ok" }));
19
+ app.get("/.well-known/oauth-protected-resource", (context) => context.json({
20
+ resource,
21
+ authorization_servers: [deps.gateUrl.replace(/\/+$/, "")],
22
+ bearer_methods_supported: ["header"],
23
+ }));
24
+ const browserAuth = deps.auth;
25
+ if (browserAuth) {
26
+ app.get("/auth/login", async (context) => await browserAuth.login(new URL(context.req.url)));
27
+ // One connect route for the one portal: there are no per-source connections any more.
28
+ app.get("/auth/connect", async (context) => {
29
+ const connectReturnTo = encodeURIComponent("/auth/connect?returnTo=/tools");
30
+ const session = await browserAuth.resolve(context.req.raw.headers);
31
+ if (!session) {
32
+ return context.redirect(`/auth/login?returnTo=${connectReturnTo}`);
33
+ }
34
+ const authorization = await authorizeBearer(deps.gate, session.bearer, resource);
35
+ if (!authorization) {
36
+ return context.redirect(`/auth/login?returnTo=${connectReturnTo}`);
37
+ }
38
+ if (!authorization.can("tools", "read")) {
39
+ return context.redirect("/tools?connectError=permission_required");
40
+ }
41
+ try {
42
+ return await browserAuth.connect(new URL(context.req.url), context.req.raw.headers, authorization.identity.id);
43
+ }
44
+ catch (error) {
45
+ if (error instanceof IntelError) {
46
+ return context.redirect(`/tools?connectError=${encodeURIComponent(error.code)}`);
47
+ }
48
+ throw error;
49
+ }
50
+ });
51
+ app.get("/auth/callback", async (context) => await browserAuth.callback(new URL(context.req.url), context.req.raw.headers));
52
+ app.post("/auth/logout", () => browserAuth.logout());
53
+ }
54
+ app.route("/api/v1", createHttp({
55
+ gate: deps.gate,
56
+ knowledge: deps.knowledge,
57
+ flows: deps.flows,
58
+ tools: deps.tools,
59
+ resource,
60
+ resourceMetadataUrl,
61
+ ...(deps.auth ? { auth: deps.auth } : {}),
62
+ }));
63
+ app.all("/mcp", async (context) => {
64
+ const authorization = await authorize(deps.gate, context.req.raw.headers, resource);
65
+ if (!authorization) {
66
+ context.header("WWW-Authenticate", `Bearer resource_metadata="${resourceMetadataUrl}"`);
67
+ return context.json({ jsonrpc: "2.0", id: null, error: { code: -32003, message: "authentication required" } }, 401);
68
+ }
69
+ return await handleMcp(context.req.raw, {
70
+ authorization,
71
+ flows: deps.flows,
72
+ knowledge: deps.knowledge,
73
+ tools: deps.tools,
74
+ });
75
+ });
76
+ return app;
77
+ }
@@ -0,0 +1,14 @@
1
+ import type { GateClient } from "@anchrd/gate-sdk";
2
+ import type { BrowserAuth } from "../auth/auth.types.js";
3
+ import type { FlowService } from "../flows/flows.types.js";
4
+ import type { KnowledgeService } from "../knowledge/knowledge.types.js";
5
+ import type { ToolService } from "../tools/tools.types.js";
6
+ export interface IntelDeps {
7
+ baseUrl: string;
8
+ gateUrl: string;
9
+ gate: Pick<GateClient, "authorize">;
10
+ knowledge: KnowledgeService;
11
+ flows: FlowService;
12
+ tools: ToolService;
13
+ auth?: BrowserAuth;
14
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { KnowledgeDeps, KnowledgeService } from "./knowledge.types.js";
2
+ export declare function createKnowledge(deps: KnowledgeDeps): KnowledgeService;