@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
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # @anchrd/intel-api
2
+
3
+ Intel is a customer-deployed, model-agnostic operating layer for company knowledge and processes. It
4
+ gives people and MCP-capable AI clients the same governed access to three surfaces:
5
+
6
+ - **Knowledge** — company content with sharing, immutable versions, retrieval, citations and a graph
7
+ - **Flows** — versioned, durable processes that reference Knowledge and Tools
8
+ - **Tools** — MCP capabilities discovered through a Cloudflare MCP Portal
9
+
10
+ The UI, the HTTP API and the Intel MCP surface call the same application services, so authorization,
11
+ validation and side effects never differ by surface. You deploy it into your own Cloudflare account
12
+ against your own Gate; nothing runs on Anchrd infrastructure.
13
+
14
+ This package contains the portable server, the Cloudflare adapter, the D1 migrations and the
15
+ customer CLI.
16
+
17
+ ## Requirements
18
+
19
+ - A [Gate](https://www.npmjs.com/package/@anchrd/gate-sdk) instance — Gate owns identity, capabilities
20
+ and the encrypted connection vault, and is the only required Anchrd dependency
21
+ - A Cloudflare account with Workers, D1, R2, Queues and Workflows (Vectorize and Workers AI are
22
+ optional and only add semantic search and attachment conversion)
23
+ - Node 22 or newer
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm install @anchrd/intel-api @anchrd/intel-ui
29
+ npx intel prepare
30
+ npx intel bootstrap
31
+ npx intel build
32
+ npx intel doctor
33
+ ```
34
+
35
+ Use the Cloudflare export from a customer Worker:
36
+
37
+ ```ts
38
+ // biome-ignore lint/performance/noBarrelFile: Wrangler needs the named Workflow entrypoint.
39
+ export { default, IntelFlowWorkflow } from "@anchrd/intel-api/cloudflare";
40
+ ```
41
+
42
+ `intel build` reads an optional strict `intel.json`. Customer theme files override shadcn semantic
43
+ tokens; SVG logo/favicon and complete language catalogs are embedded at build time. Installed builds
44
+ land in `.intel/ui`, and migrations in `.intel/migrations`.
45
+
46
+ Put `intel.json` next to the customer project's `package.json`:
47
+
48
+ ```json
49
+ {
50
+ "ui": {
51
+ "theme": "./branding/theme.css",
52
+ "logo": "./branding/logo.svg",
53
+ "favicon": "./branding/favicon.svg",
54
+ "defaultLanguage": "en",
55
+ "languages": {}
56
+ }
57
+ }
58
+ ```
59
+
60
+ The theme is plain CSS containing shadcn semantic tokens such as `--primary`, `--background`,
61
+ `--sidebar`, and `--radius`. It is loaded after Intel's defaults, so customer tokens win without a
62
+ component fork. Unknown config fields, missing assets, non-SVG branding, and incomplete language
63
+ catalogs fail the build. A ready-to-copy starting point is included under
64
+ `node_modules/@anchrd/intel-api/examples`.
65
+
66
+ Configure customer-specific `GATE_URL`, `INTEL_URL`, and the comma-separated exact-origin allowlist
67
+ `TOOL_SOURCE_ORIGINS` in the deployment environment. The Worker requires `GATE_SERVICE_KEY` and a
68
+ random `INTEL_SESSION_SECRET` of at least 32 bytes as Wrangler secrets. `ALLOW_INSECURE_OAUTH=true`
69
+ is only supported for explicit local development; production OAuth issuers must use HTTPS. Gate OAuth
70
+ uses PKCE and dynamic public-client registration; browser access tokens stay inside an encrypted
71
+ `HttpOnly` cookie and are never exposed to the UI bundle or D1. Copy
72
+ `node_modules/@anchrd/intel-api/examples/dev.vars.example` to `.dev.vars` for a local customer Worker.
73
+
74
+ Approved MCP sources expose RFC 9728 metadata. The Tools UI follows that metadata, dynamically
75
+ registers with Gate or Cloudflare Access, and completes a separate PKCE flow. Source access and
76
+ refresh tokens are stored only in Gate's encrypted connection vault and refreshed just in time.
77
+ The Intel audience token is never forwarded to another OAuth resource.
78
+
79
+ The reference Wrangler deployment binds `DB`, `CONTENT`, `INDEXING`, `AI`, `SEARCH`, `FLOWS`, and
80
+ `ASSETS`. Create `SEARCH` as a 1024-dimension cosine Vectorize index for the default multilingual
81
+ Workers AI `@cf/baai/bge-m3` embedding adapter. `FLOWS` targets the exported
82
+ `IntelFlowWorkflow` class. Tool sources speak standard Streamable HTTP MCP; Intel caches schemas
83
+ and resolves the caller's Portal token just in time from Gate. Durable Flow state contains no
84
+ browser, Gate, or provider credentials.
85
+
86
+ ## Related packages
87
+
88
+ - [`@anchrd/intel-ui`](https://www.npmjs.com/package/@anchrd/intel-ui) — the browser application,
89
+ built from source with your own theme
90
+ - [`@anchrd/intel-contract`](https://www.npmjs.com/package/@anchrd/intel-contract) — the shared wire
91
+ schemas
92
+
93
+ ## License
94
+
95
+ Proprietary. Published publicly for installation convenience; this is not an open-source license and
96
+ grants no right to use, copy, modify or redistribute the software. Contact Anchrd for licensing.
package/bin/intel.mjs ADDED
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { cp, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
4
+ import { dirname, resolve } from "node:path";
5
+ import { createGateClient } from "@anchrd/gate-sdk";
6
+ import { createCli } from "../dist/cli/cli.js";
7
+
8
+ const uiCandidates = [
9
+ { dir: "node_modules/@anchrd/intel-ui", outDir: ".intel/ui" },
10
+ { dir: "packages/ui", outDir: "packages/ui/dist" },
11
+ ];
12
+ const migrationCandidates = [
13
+ "node_modules/@anchrd/intel-api/migrations",
14
+ "packages/api/migrations",
15
+ ];
16
+
17
+ // npm sets npm_execpath to its own JavaScript entry point. Running that through the current Node
18
+ // binary avoids the .cmd shim entirely, which matters on Windows: Node has refused to spawn a .cmd
19
+ // without a shell since CVE-2024-27980, and cmd.exe expands %NAME% even inside quotes, so a checkout
20
+ // path containing a percent sign would be rewritten.
21
+ function npmScriptPath(command) {
22
+ if (command !== "npm") return null;
23
+ const execPath = process.env.npm_execpath;
24
+ return execPath?.endsWith(".js") ? execPath : null;
25
+ }
26
+
27
+ // Only reached on the Windows shell fallback, where cmd.exe would otherwise split on whitespace.
28
+ function quoteForCmd(argument) {
29
+ if (!/[\s"^&|<>()]/.test(argument)) return argument;
30
+ return `"${argument.replaceAll('"', '""')}"`;
31
+ }
32
+
33
+ function spawnArguments(command, args) {
34
+ const script = npmScriptPath(command);
35
+ if (script) return [process.execPath, [script, ...args]];
36
+ if (process.platform !== "win32") return [command, args];
37
+ return [`${command}.cmd`, args.map(quoteForCmd)];
38
+ }
39
+
40
+ async function isFile(path) {
41
+ try {
42
+ return (await stat(path)).isFile();
43
+ } catch (error) {
44
+ if (error?.code === "ENOENT" || error?.code === "ENOTDIR") return false;
45
+ throw error;
46
+ }
47
+ }
48
+
49
+ async function isDirectory(path) {
50
+ try {
51
+ return (await stat(path)).isDirectory();
52
+ } catch (error) {
53
+ if (error?.code === "ENOENT" || error?.code === "ENOTDIR") return false;
54
+ throw error;
55
+ }
56
+ }
57
+
58
+ async function replaceDir(from, to) {
59
+ await mkdir(dirname(to), { recursive: true });
60
+ const backup = `${to}.old`;
61
+ await rm(backup, { recursive: true, force: true });
62
+ let hasBackup = false;
63
+ try {
64
+ await rename(to, backup);
65
+ hasBackup = true;
66
+ } catch (error) {
67
+ if (error?.code !== "ENOENT") throw error;
68
+ }
69
+ try {
70
+ await rename(from, to);
71
+ } catch (error) {
72
+ if (hasBackup) await rename(backup, to);
73
+ throw error;
74
+ }
75
+ if (hasBackup) await rm(backup, { recursive: true, force: true });
76
+ }
77
+
78
+ const cli = createCli({
79
+ env: process.env,
80
+ fetch: globalThis.fetch,
81
+ createGateInterfaces: (url, serviceKey) => createGateClient({ url, serviceKey }).interfaces,
82
+ readTextFile: async (path) => {
83
+ try {
84
+ return await readFile(path, "utf8");
85
+ } catch (error) {
86
+ if (error?.code === "ENOENT") return null;
87
+ throw error;
88
+ }
89
+ },
90
+ writeTextFile: async (path, content) => {
91
+ await mkdir(dirname(path), { recursive: true });
92
+ await writeFile(path, content, "utf8");
93
+ },
94
+ resolveUi: async () => {
95
+ for (const candidate of uiCandidates) {
96
+ if (await isFile(`${candidate.dir}/package.json`)) {
97
+ return { dir: candidate.dir, outDir: resolve(process.cwd(), candidate.outDir) };
98
+ }
99
+ }
100
+ return null;
101
+ },
102
+ resolveMigrations: async () => {
103
+ for (const dir of migrationCandidates) {
104
+ if (await isDirectory(dir)) {
105
+ return { dir, outDir: resolve(process.cwd(), ".intel/migrations") };
106
+ }
107
+ }
108
+ return null;
109
+ },
110
+ copyDir: async (from, to) => {
111
+ await rm(to, { recursive: true, force: true });
112
+ await mkdir(dirname(to), { recursive: true });
113
+ await cp(from, to, { recursive: true });
114
+ },
115
+ exec: (command, args, { cwd }) =>
116
+ new Promise((resolveCode, reject) => {
117
+ const child = spawn(...spawnArguments(command, args), {
118
+ cwd,
119
+ stdio: "inherit",
120
+ shell: process.platform === "win32" && !npmScriptPath(command),
121
+ });
122
+ child.on("error", reject);
123
+ child.on("close", (code) => resolveCode(code ?? 1));
124
+ }),
125
+ replaceDir,
126
+ log: (message) => console.log(message),
127
+ });
128
+
129
+ try {
130
+ process.exitCode = await cli.run(process.argv.slice(2));
131
+ } catch (error) {
132
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
133
+ process.exitCode = 1;
134
+ }
@@ -0,0 +1,21 @@
1
+ import type { CloudflareEnv } from "./cloudflare.types.js";
2
+ interface WorkflowEvent<Params> {
3
+ payload: Readonly<Params>;
4
+ }
5
+ interface WorkflowStep {
6
+ do<T>(name: string, callback: () => Promise<T>): Promise<T>;
7
+ waitForEvent(name: string, options: {
8
+ type: string;
9
+ timeout?: string | number;
10
+ }): Promise<unknown>;
11
+ }
12
+ interface FlowWorkflowParams {
13
+ runId: string;
14
+ }
15
+ interface IntelFlowWorkflowInstance {
16
+ run(event: Readonly<WorkflowEvent<FlowWorkflowParams>>, step: WorkflowStep): Promise<unknown>;
17
+ }
18
+ export declare const IntelFlowWorkflow: {
19
+ new (context: unknown, env: CloudflareEnv): IntelFlowWorkflowInstance;
20
+ };
21
+ export {};
@@ -0,0 +1,39 @@
1
+ // The runtime module is provided by workerd and intentionally has no Node package resolution path.
2
+ // @ts-expect-error cloudflare:workers is available inside the deployed Worker runtime.
3
+ import { WorkflowEntrypoint as RuntimeWorkflowEntrypoint } from "cloudflare:workers";
4
+ class WorkflowEntrypoint extends RuntimeWorkflowEntrypoint {
5
+ }
6
+ class IntelFlowWorkflowImplementation extends WorkflowEntrypoint {
7
+ async run(event, step) {
8
+ const fail = async (name, error) => await step.do(name, async () => {
9
+ const occurredAt = new Date().toISOString();
10
+ await this.env.DB.prepare(`UPDATE flow_runs
11
+ SET status = 'failed', error = ?, completed_at = ?, updated_at = ?
12
+ WHERE id = ? AND status NOT IN ('completed', 'failed', 'cancelled')`)
13
+ .bind(error, occurredAt, occurredAt, event.payload.runId)
14
+ .run();
15
+ return { status: "failed", error };
16
+ });
17
+ for (let iteration = 0; iteration < 500; iteration += 1) {
18
+ const state = await step.do(`inspect flow run ${iteration}`, async () => {
19
+ const row = await this.env.DB.prepare("SELECT status, error FROM flow_runs WHERE id = ?")
20
+ .bind(event.payload.runId)
21
+ .first();
22
+ return row ?? { status: "failed", error: "Flow run disappeared" };
23
+ });
24
+ if (["completed", "failed", "cancelled"].includes(state.status))
25
+ return state;
26
+ try {
27
+ await step.waitForEvent(`wait for flow run ${iteration}`, {
28
+ type: "advance",
29
+ timeout: "365 days",
30
+ });
31
+ }
32
+ catch {
33
+ return await fail(`expire flow run ${iteration}`, "Flow run timed out waiting for progress");
34
+ }
35
+ }
36
+ return await fail("fail flow step limit", "Flow exceeded the maximum durable step count");
37
+ }
38
+ }
39
+ export const IntelFlowWorkflow = IntelFlowWorkflowImplementation;
@@ -0,0 +1,8 @@
1
+ import type { CloudflareEnv, CloudflareExecutionContext, IndexQueueBatch } from "./cloudflare.types.js";
2
+ export { IntelFlowWorkflow } from "./cloudflare-flow-workflow.js";
3
+ export declare function isSpaNavigation(request: Request): boolean;
4
+ declare const _default: {
5
+ fetch(request: Request, env: CloudflareEnv, _context: CloudflareExecutionContext): Promise<Response>;
6
+ queue(batch: IndexQueueBatch, env: CloudflareEnv): Promise<void>;
7
+ };
8
+ export default _default;
@@ -0,0 +1,200 @@
1
+ import { createGateClient } from "@anchrd/gate-sdk";
2
+ import { ulid } from "ulid";
3
+ import { createBrowserAuth } from "../../auth/auth.js";
4
+ import { createFlows } from "../../flows/flows.js";
5
+ import { createIndexing, PermanentIndexingError } from "../../indexing/indexing.js";
6
+ import { createIntel } from "../../intel/intel.js";
7
+ import { createKnowledge } from "../../knowledge/knowledge.js";
8
+ import { IntelError } from "../../shared/intel-error/intel-error.js";
9
+ import { sha256Hex } from "../../shared/sha256/sha256.js";
10
+ import { createTools } from "../../tools/tools.js";
11
+ import { createContentStore } from "../content/content.js";
12
+ import { createKnowledgeRepository } from "../db/db.js";
13
+ import { createFlowRepository } from "../db/db-flows.js";
14
+ import { createKnowledgeIndexRepository } from "../db/db-indexing.js";
15
+ import { createOAuthClientStore } from "../db/db-oauth.js";
16
+ import { createDocumentConverter } from "../document-converter/document-converter.js";
17
+ import { createFlowRuntime } from "../flow-runtime/flow-runtime.js";
18
+ import { createIndexQueue, IndexMessage } from "../index-queue/index-queue.js";
19
+ import { createJsonSchemaValidator } from "../json-schema/json-schema.js";
20
+ import { createOpenId } from "../openid/openid.js";
21
+ import { createPortalTokenStore } from "../portal-tokens/portal-tokens.js";
22
+ import { createRemoteTools } from "../remote-tools/remote-tools.js";
23
+ import { createSemanticIndex } from "../semantic-index/semantic-index.js";
24
+ import { createSessionCodec } from "../session-cookie/session-cookie.js";
25
+ import { createToolSourcePolicy } from "../tool-source-policy/tool-source-policy.js";
26
+ // biome-ignore lint/performance/noBarrelFile: The package entry must expose Wrangler's named Workflow class.
27
+ export { IntelFlowWorkflow } from "./cloudflare-flow-workflow.js";
28
+ const workerPrefixes = ["/api", "/auth", "/.well-known", "/mcp", "/health"];
29
+ export function isSpaNavigation(request) {
30
+ if (request.method !== "GET" && request.method !== "HEAD")
31
+ return false;
32
+ if (!request.headers.get("accept")?.includes("text/html"))
33
+ return false;
34
+ const { pathname } = new URL(request.url);
35
+ return !workerPrefixes.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`));
36
+ }
37
+ // A missing variable would otherwise surface deep inside a request as an opaque URL or crypto
38
+ // error. Naming it once here keeps a misconfigured deployment diagnosable.
39
+ const requiredVariables = [
40
+ "GATE_URL",
41
+ "GATE_SERVICE_KEY",
42
+ "INTEL_URL",
43
+ "INTEL_SESSION_SECRET",
44
+ "TOOL_SOURCE_ORIGINS",
45
+ ];
46
+ function assertConfigured(env) {
47
+ const missing = requiredVariables.filter((name) => !env[name]?.trim());
48
+ if (missing.length) {
49
+ throw new IntelError(500, "configuration_missing", `Intel is missing required configuration: ${missing.join(", ")}`);
50
+ }
51
+ }
52
+ export default {
53
+ async fetch(request, env, _context) {
54
+ if (env.ASSETS && isSpaNavigation(request)) {
55
+ return await env.ASSETS.fetch(new Request(new URL("/", request.url), { method: "GET" }));
56
+ }
57
+ assertConfigured(env);
58
+ const now = () => new Date();
59
+ const semantic = env.AI && env.SEARCH ? createSemanticIndex({ ai: env.AI, index: env.SEARCH }) : undefined;
60
+ const knowledge = createKnowledge({
61
+ repository: createKnowledgeRepository({ db: env.DB, now }),
62
+ content: createContentStore(env.CONTENT),
63
+ id: ulid,
64
+ now,
65
+ indexing: createIndexQueue(env.INDEXING),
66
+ semantic,
67
+ hash: async (content) => {
68
+ const source = typeof content === "string"
69
+ ? new TextEncoder().encode(content)
70
+ : Uint8Array.from(content).buffer;
71
+ return await sha256Hex(crypto, source);
72
+ },
73
+ });
74
+ const gate = createGateClient({ url: env.GATE_URL, serviceKey: env.GATE_SERVICE_KEY });
75
+ const oauth = createOpenId({
76
+ fetch: globalThis.fetch.bind(globalThis),
77
+ allowInsecure: env.ALLOW_INSECURE_OAUTH === "true",
78
+ });
79
+ const intelResource = `${env.INTEL_URL.replace(/\/+$/, "")}/mcp`;
80
+ const portalUrl = env.MCP_PORTAL_URL?.trim() || null;
81
+ const portalTokens = createPortalTokenStore({
82
+ db: env.DB,
83
+ secret: env.INTEL_SESSION_SECRET,
84
+ crypto,
85
+ now,
86
+ });
87
+ const auth = createBrowserAuth({
88
+ baseUrl: env.INTEL_URL,
89
+ gateUrl: env.GATE_URL,
90
+ resource: intelResource,
91
+ oauth,
92
+ clients: createOAuthClientStore(env.DB),
93
+ sessions: createSessionCodec({ secret: env.INTEL_SESSION_SECRET, crypto }),
94
+ portalUrl,
95
+ portalTokens,
96
+ now,
97
+ });
98
+ const tools = createTools({
99
+ portalUrl,
100
+ remote: createRemoteTools({ fetch: globalThis.fetch.bind(globalThis) }),
101
+ tokens: portalTokens,
102
+ refresh: async (token) => {
103
+ try {
104
+ const refreshed = await oauth.refresh({
105
+ issuer: token.issuer,
106
+ clientId: token.clientId,
107
+ resource: token.resource,
108
+ refreshToken: token.refreshToken ?? "",
109
+ });
110
+ return {
111
+ ...token,
112
+ accessToken: refreshed.accessToken,
113
+ refreshToken: refreshed.refreshToken ?? token.refreshToken,
114
+ expiresAt: now().getTime() + Math.max(30, refreshed.expiresIn) * 1_000,
115
+ };
116
+ }
117
+ catch {
118
+ // A refusal from the portal's issuer means the grant is gone; the caller drops the token
119
+ // and asks the user to reconnect rather than retrying a dead credential.
120
+ return null;
121
+ }
122
+ },
123
+ now,
124
+ validate: createJsonSchemaValidator(),
125
+ sourceAllowed: createToolSourcePolicy(env.TOOL_SOURCE_ORIGINS),
126
+ fingerprint: async (value) => {
127
+ return await sha256Hex(crypto, JSON.stringify(value));
128
+ },
129
+ });
130
+ const flows = createFlows({
131
+ repository: createFlowRepository({ db: env.DB, now }),
132
+ runtime: createFlowRuntime(env.FLOWS),
133
+ id: ulid,
134
+ now,
135
+ knowledgeExists: async (actor, resourceId) => {
136
+ try {
137
+ await knowledge.get(actor, resourceId);
138
+ return true;
139
+ }
140
+ catch (error) {
141
+ if (error instanceof IntelError && error.status === 404)
142
+ return false;
143
+ throw error;
144
+ }
145
+ },
146
+ toolFingerprint: async (actor, toolName) => {
147
+ const catalog = await tools
148
+ .catalog({ id: actor.id, email: actor.email, canExecute: true })
149
+ .catch(() => null);
150
+ return catalog?.items.find((item) => item.name === toolName)?.fingerprint ?? null;
151
+ },
152
+ unavailableTools: async (actor, toolNames) => await tools.unavailable({ id: actor.id, email: actor.email, canExecute: true }, toolNames),
153
+ });
154
+ return await createIntel({
155
+ baseUrl: env.INTEL_URL,
156
+ gateUrl: env.GATE_URL,
157
+ gate,
158
+ knowledge,
159
+ flows,
160
+ tools,
161
+ auth,
162
+ }).fetch(request);
163
+ },
164
+ async queue(batch, env) {
165
+ const indexing = createIndexing({
166
+ repository: createKnowledgeIndexRepository(env.DB),
167
+ content: createContentStore(env.CONTENT),
168
+ semantic: env.AI && env.SEARCH ? createSemanticIndex({ ai: env.AI, index: env.SEARCH }) : undefined,
169
+ converter: env.AI ? createDocumentConverter(env.AI) : undefined,
170
+ now: () => new Date(),
171
+ });
172
+ for (const message of batch.messages) {
173
+ const parsed = IndexMessage.safeParse(message.body);
174
+ if (!parsed.success) {
175
+ console.warn("Intel discarded an invalid indexing queue message");
176
+ message.ack();
177
+ continue;
178
+ }
179
+ try {
180
+ await indexing.index(parsed.data.versionId);
181
+ message.ack();
182
+ }
183
+ catch (error) {
184
+ if (error instanceof PermanentIndexingError) {
185
+ console.warn("Intel discarded a permanently unindexable Knowledge version", {
186
+ versionId: parsed.data.versionId,
187
+ error: error.message,
188
+ });
189
+ message.ack();
190
+ continue;
191
+ }
192
+ console.error("Intel indexing failed; Cloudflare retry policy will apply", {
193
+ versionId: parsed.data.versionId,
194
+ error: error instanceof Error ? error.message : "indexing_failed",
195
+ });
196
+ message.retry();
197
+ }
198
+ }
199
+ },
200
+ };
@@ -0,0 +1,29 @@
1
+ import type { R2Bucket } from "../content/content.types.js";
2
+ import type { D1Database } from "../db/db.types.js";
3
+ import type { MarkdownAiBinding } from "../document-converter/document-converter.types.js";
4
+ import type { WorkflowBinding } from "../flow-runtime/flow-runtime.types.js";
5
+ import type { IndexMessage } from "../index-queue/index-queue.js";
6
+ import type { QueueBatch, QueueProducer } from "../index-queue/index-queue.types.js";
7
+ import type { VectorizeBinding, WorkersAiBinding } from "../semantic-index/semantic-index.types.js";
8
+ export interface CloudflareEnv {
9
+ ASSETS?: {
10
+ fetch(request: Request): Promise<Response>;
11
+ };
12
+ DB: D1Database;
13
+ CONTENT: R2Bucket;
14
+ INDEXING: QueueProducer<IndexMessage>;
15
+ FLOWS: WorkflowBinding;
16
+ AI?: WorkersAiBinding & MarkdownAiBinding;
17
+ SEARCH?: VectorizeBinding;
18
+ GATE_URL: string;
19
+ GATE_SERVICE_KEY: string;
20
+ INTEL_URL: string;
21
+ INTEL_SESSION_SECRET: string;
22
+ TOOL_SOURCE_ORIGINS: string;
23
+ MCP_PORTAL_URL?: string;
24
+ ALLOW_INSECURE_OAUTH?: string;
25
+ }
26
+ export interface CloudflareExecutionContext {
27
+ waitUntil(promise: Promise<unknown>): void;
28
+ }
29
+ export type IndexQueueBatch = QueueBatch<unknown>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { ContentStore } from "../../knowledge/knowledge.types.js";
2
+ import type { R2Bucket } from "./content.types.js";
3
+ export declare function createContentStore(bucket: R2Bucket): ContentStore;
@@ -0,0 +1,22 @@
1
+ export function createContentStore(bucket) {
2
+ return {
3
+ async get(key) {
4
+ return (await (await bucket.get(key))?.text()) ?? null;
5
+ },
6
+ async getBytes(key) {
7
+ return (await (await bucket.get(key))?.arrayBuffer()) ?? null;
8
+ },
9
+ async getStream(key) {
10
+ return (await bucket.get(key))?.body ?? null;
11
+ },
12
+ async put(key, content, mediaType) {
13
+ await bucket.put(key, content, { httpMetadata: { contentType: mediaType } });
14
+ },
15
+ async putBytes(key, content, mediaType) {
16
+ await bucket.put(key, content, { httpMetadata: { contentType: mediaType } });
17
+ },
18
+ async delete(key) {
19
+ await bucket.delete(key);
20
+ },
21
+ };
22
+ }
@@ -0,0 +1,14 @@
1
+ export interface R2ObjectBody {
2
+ body: ReadableStream<Uint8Array>;
3
+ text(): Promise<string>;
4
+ arrayBuffer(): Promise<ArrayBuffer>;
5
+ }
6
+ export interface R2Bucket {
7
+ get(key: string): Promise<R2ObjectBody | null>;
8
+ put(key: string, value: string | ArrayBuffer, options?: {
9
+ httpMetadata?: {
10
+ contentType?: string;
11
+ };
12
+ }): Promise<unknown>;
13
+ delete(key: string): Promise<void>;
14
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { FlowRepository } from "../../flows/flows.types.js";
2
+ import type { D1Database } from "./db.types.js";
3
+ export declare function createFlowRepository(deps: {
4
+ db: D1Database;
5
+ now(): Date;
6
+ }): FlowRepository;