@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.
- package/README.md +96 -0
- package/bin/intel.mjs +134 -0
- package/dist/adapters/cloudflare/cloudflare-flow-workflow.d.ts +21 -0
- package/dist/adapters/cloudflare/cloudflare-flow-workflow.js +39 -0
- package/dist/adapters/cloudflare/cloudflare.d.ts +8 -0
- package/dist/adapters/cloudflare/cloudflare.js +200 -0
- package/dist/adapters/cloudflare/cloudflare.types.d.ts +29 -0
- package/dist/adapters/cloudflare/cloudflare.types.js +1 -0
- package/dist/adapters/content/content.d.ts +3 -0
- package/dist/adapters/content/content.js +22 -0
- package/dist/adapters/content/content.types.d.ts +14 -0
- package/dist/adapters/content/content.types.js +1 -0
- package/dist/adapters/db/db-flows.d.ts +6 -0
- package/dist/adapters/db/db-flows.js +466 -0
- package/dist/adapters/db/db-indexing.d.ts +3 -0
- package/dist/adapters/db/db-indexing.js +70 -0
- package/dist/adapters/db/db-oauth.d.ts +3 -0
- package/dist/adapters/db/db-oauth.js +26 -0
- package/dist/adapters/db/db-tools.d.ts +6 -0
- package/dist/adapters/db/db-tools.js +200 -0
- package/dist/adapters/db/db.d.ts +6 -0
- package/dist/adapters/db/db.js +755 -0
- package/dist/adapters/db/db.types.d.ts +16 -0
- package/dist/adapters/db/db.types.js +1 -0
- package/dist/adapters/document-converter/document-converter.d.ts +2 -0
- package/dist/adapters/document-converter/document-converter.js +25 -0
- package/dist/adapters/document-converter/document-converter.types.d.ts +9 -0
- package/dist/adapters/document-converter/document-converter.types.js +1 -0
- package/dist/adapters/flow-runtime/flow-runtime.d.ts +3 -0
- package/dist/adapters/flow-runtime/flow-runtime.js +22 -0
- package/dist/adapters/flow-runtime/flow-runtime.types.d.ts +19 -0
- package/dist/adapters/flow-runtime/flow-runtime.types.js +1 -0
- package/dist/adapters/index-queue/index-queue.d.ts +10 -0
- package/dist/adapters/index-queue/index-queue.js +12 -0
- package/dist/adapters/index-queue/index-queue.types.d.ts +11 -0
- package/dist/adapters/index-queue/index-queue.types.js +1 -0
- package/dist/adapters/json-schema/json-schema.d.ts +2 -0
- package/dist/adapters/json-schema/json-schema.js +31 -0
- package/dist/adapters/openid/openid.d.ts +5 -0
- package/dist/adapters/openid/openid.js +159 -0
- package/dist/adapters/portal-tokens/portal-tokens.d.ts +8 -0
- package/dist/adapters/portal-tokens/portal-tokens.js +71 -0
- package/dist/adapters/remote-tools/remote-tools.d.ts +5 -0
- package/dist/adapters/remote-tools/remote-tools.js +109 -0
- package/dist/adapters/semantic-index/semantic-index.d.ts +2 -0
- package/dist/adapters/semantic-index/semantic-index.js +57 -0
- package/dist/adapters/semantic-index/semantic-index.types.d.ts +32 -0
- package/dist/adapters/semantic-index/semantic-index.types.js +1 -0
- package/dist/adapters/session-cookie/session-cookie.d.ts +5 -0
- package/dist/adapters/session-cookie/session-cookie.js +81 -0
- package/dist/adapters/tool-source-policy/tool-source-policy.d.ts +1 -0
- package/dist/adapters/tool-source-policy/tool-source-policy.js +22 -0
- package/dist/auth/auth.d.ts +2 -0
- package/dist/auth/auth.js +176 -0
- package/dist/auth/auth.types.d.ts +104 -0
- package/dist/auth/auth.types.js +1 -0
- package/dist/build/build.d.ts +2 -0
- package/dist/build/build.js +125 -0
- package/dist/build/build.types.d.ts +17 -0
- package/dist/build/build.types.js +1 -0
- package/dist/cli/cli.d.ts +4 -0
- package/dist/cli/cli.js +141 -0
- package/dist/cli/cli.types.d.ts +14 -0
- package/dist/cli/cli.types.js +1 -0
- package/dist/flows/flows.d.ts +4 -0
- package/dist/flows/flows.js +409 -0
- package/dist/flows/flows.types.d.ts +108 -0
- package/dist/flows/flows.types.js +1 -0
- package/dist/http/http.d.ts +8 -0
- package/dist/http/http.js +286 -0
- package/dist/http/http.types.d.ts +14 -0
- package/dist/http/http.types.js +1 -0
- package/dist/indexing/indexing.d.ts +6 -0
- package/dist/indexing/indexing.js +48 -0
- package/dist/indexing/indexing.types.d.ts +10 -0
- package/dist/indexing/indexing.types.js +1 -0
- package/dist/intel/intel.d.ts +3 -0
- package/dist/intel/intel.js +77 -0
- package/dist/intel/intel.types.d.ts +14 -0
- package/dist/intel/intel.types.js +1 -0
- package/dist/knowledge/knowledge.d.ts +2 -0
- package/dist/knowledge/knowledge.js +479 -0
- package/dist/knowledge/knowledge.types.d.ts +162 -0
- package/dist/knowledge/knowledge.types.js +1 -0
- package/dist/mcp/mcp.d.ts +2 -0
- package/dist/mcp/mcp.js +484 -0
- package/dist/mcp/mcp.types.d.ts +10 -0
- package/dist/mcp/mcp.types.js +1 -0
- package/dist/prepare/prepare.d.ts +4 -0
- package/dist/prepare/prepare.js +16 -0
- package/dist/prepare/prepare.types.d.ts +9 -0
- package/dist/prepare/prepare.types.js +1 -0
- package/dist/shared/gate-authorization/gate-authorization.d.ts +4 -0
- package/dist/shared/gate-authorization/gate-authorization.js +19 -0
- package/dist/shared/intel-error/intel-error.d.ts +5 -0
- package/dist/shared/intel-error/intel-error.js +10 -0
- package/dist/shared/problem-details/problem-details.d.ts +8 -0
- package/dist/shared/problem-details/problem-details.js +4 -0
- package/dist/shared/safe-return-path/safe-return-path.d.ts +2 -0
- package/dist/shared/safe-return-path/safe-return-path.js +11 -0
- package/dist/shared/sha256/sha256.d.ts +1 -0
- package/dist/shared/sha256/sha256.js +9 -0
- package/dist/tools/tools.d.ts +2 -0
- package/dist/tools/tools.js +107 -0
- package/dist/tools/tools.types.d.ts +56 -0
- package/dist/tools/tools.types.js +1 -0
- package/examples/branding/favicon.svg +5 -0
- package/examples/branding/logo.svg +5 -0
- package/examples/branding/theme.css +16 -0
- package/examples/dev.vars.example +7 -0
- package/examples/intel.json +9 -0
- package/migrations/0000_intel_foundation.sql +203 -0
- package/migrations/0001_portal_native_tools.sql +21 -0
- 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,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 {};
|