@anchrd/intel-api 0.13.0 → 0.15.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/dist/adapters/cloudflare/cloudflare.js +1 -68
- package/dist/adapters/cloudflare/cloudflare.types.d.ts +0 -39
- package/dist/adapters/db/db-flows.js +1 -1
- package/dist/adapters/db/db-grants.js +1 -1
- package/dist/adapters/db/db-indexing.js +79 -0
- package/dist/adapters/db/db.js +81 -139
- package/dist/adapters/semantic-index/semantic-index.js +97 -17
- package/dist/adapters/semantic-index/semantic-index.types.d.ts +20 -1
- package/dist/bundle/bundle.js +42 -134
- package/dist/cli/cli.js +3 -9
- package/dist/http/http.js +5 -206
- package/dist/http/http.types.d.ts +0 -8
- package/dist/indexing/indexing.js +133 -55
- package/dist/indexing/indexing.types.d.ts +1 -0
- package/dist/intel/intel.js +4 -9
- package/dist/intel/intel.types.d.ts +0 -6
- package/dist/mcp/mcp.js +33 -308
- package/dist/mcp/mcp.types.d.ts +2 -7
- package/dist/nodes/document-links/document-links.d.ts +6 -8
- package/dist/nodes/document-links/document-links.js +8 -31
- package/dist/nodes/nodes.js +92 -826
- package/dist/nodes/nodes.types.d.ts +57 -158
- package/dist/tools/tools.js +37 -148
- package/dist/tools/tools.types.d.ts +0 -21
- package/migrations/0009_no_context_policy.sql +15 -0
- package/migrations/0017_a_vector_per_card.sql +38 -0
- package/migrations/0018_no_context_policy_at_last.sql +97 -0
- package/migrations/0019_one_name_for_the_grants.sql +52 -0
- package/package.json +2 -2
- package/dist/adapters/cloudflare-api/cloudflare-api.d.ts +0 -22
- package/dist/adapters/cloudflare-api/cloudflare-api.js +0 -214
- package/dist/adapters/cloudflare-api/cloudflare-api.types.d.ts +0 -64
- package/dist/adapters/cloudflare-api/cloudflare-api.types.js +0 -1
- package/dist/adapters/gate-applications/gate-applications.d.ts +0 -23
- package/dist/adapters/gate-applications/gate-applications.js +0 -88
- package/dist/adapters/tool-delegation/tool-delegation.d.ts +0 -22
- package/dist/adapters/tool-delegation/tool-delegation.js +0 -90
- package/dist/agent-costs/agent-costs.d.ts +0 -16
- package/dist/agent-costs/agent-costs.js +0 -105
- package/dist/agent-costs/agent-costs.types.d.ts +0 -30
- package/dist/agent-costs/agent-costs.types.js +0 -1
- package/dist/agent-runtime/agent-runtime.d.ts +0 -16
- package/dist/agent-runtime/agent-runtime.js +0 -150
- package/dist/agent-runtime/agent-runtime.types.d.ts +0 -122
- package/dist/agent-runtime/agent-runtime.types.js +0 -1
- package/dist/model-catalog/model-catalog.d.ts +0 -2
- package/dist/model-catalog/model-catalog.js +0 -99
- package/dist/model-catalog/model-catalog.types.d.ts +0 -15
- package/dist/model-catalog/model-catalog.types.js +0 -1
- package/dist/nodes/board/board.d.ts +0 -59
- package/dist/nodes/board/board.js +0 -528
- package/dist/nodes/board/board.types.d.ts +0 -31
- package/dist/nodes/board/board.types.js +0 -1
- package/migrations/0013_agents_in_the_tree.sql +0 -76
- package/migrations/0014_agent_applications.sql +0 -25
- package/migrations/0015_tools_delegated_from_a_connection.sql +0 -15
- package/migrations/0016_boards_in_the_tree.sql +0 -80
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import { createGateClient } from "@anchrd/gate-sdk";
|
|
2
2
|
import { ulid } from "ulid";
|
|
3
|
-
import { createAgentCosts } from "../../agent-costs/agent-costs.js";
|
|
4
|
-
import { createAgentRuntimeService } from "../../agent-runtime/agent-runtime.js";
|
|
5
3
|
import { createBrowserAuth } from "../../auth/auth.js";
|
|
6
4
|
import { createBundle } from "../../bundle/bundle.js";
|
|
7
5
|
import { createFlows } from "../../flows/flows.js";
|
|
8
6
|
import { createIndexing, PermanentIndexingError } from "../../indexing/indexing.js";
|
|
9
7
|
import { createIntel } from "../../intel/intel.js";
|
|
10
|
-
import { createModelCatalog } from "../../model-catalog/model-catalog.js";
|
|
11
8
|
import { createNodes } from "../../nodes/nodes.js";
|
|
12
9
|
import { IntelError } from "../../shared/intel-error/intel-error.js";
|
|
13
10
|
import { sha256Hex } from "../../shared/sha256/sha256.js";
|
|
14
11
|
import { createTools } from "../../tools/tools.js";
|
|
15
|
-
import { createCloudflareApi } from "../cloudflare-api/cloudflare-api.js";
|
|
16
12
|
import { createContentStore } from "../content/content.js";
|
|
17
13
|
import { createNodeRepository } from "../db/db.js";
|
|
18
14
|
import { createFlowRepository } from "../db/db-flows.js";
|
|
@@ -20,7 +16,6 @@ import { createNodeIndexRepository } from "../db/db-indexing.js";
|
|
|
20
16
|
import { createOAuthClientStore } from "../db/db-oauth.js";
|
|
21
17
|
import { createDocumentConverter } from "../document-converter/document-converter.js";
|
|
22
18
|
import { createFlowRuntime } from "../flow-runtime/flow-runtime.js";
|
|
23
|
-
import { createGateApplications } from "../gate-applications/gate-applications.js";
|
|
24
19
|
import { createIndexQueue, IndexMessage } from "../index-queue/index-queue.js";
|
|
25
20
|
import { createJsonSchemaValidator } from "../json-schema/json-schema.js";
|
|
26
21
|
import { createOpenId } from "../openid/openid.js";
|
|
@@ -28,7 +23,6 @@ import { createPortalTokenStore } from "../portal-tokens/portal-tokens.js";
|
|
|
28
23
|
import { createRemoteTools } from "../remote-tools/remote-tools.js";
|
|
29
24
|
import { createSemanticIndex } from "../semantic-index/semantic-index.js";
|
|
30
25
|
import { createSessionCodec } from "../session-cookie/session-cookie.js";
|
|
31
|
-
import { createToolDelegation } from "../tool-delegation/tool-delegation.js";
|
|
32
26
|
import { createToolSourcePolicy } from "../tool-source-policy/tool-source-policy.js";
|
|
33
27
|
// biome-ignore lint/performance/noBarrelFile: The package entry must expose Wrangler's named Workflow class.
|
|
34
28
|
export { IntelFlowWorkflow } from "./cloudflare-flow-workflow.js";
|
|
@@ -82,22 +76,10 @@ export default {
|
|
|
82
76
|
crypto,
|
|
83
77
|
now,
|
|
84
78
|
});
|
|
85
|
-
// Built before the tree since D30: saving an agent has to ask the portal, live, whether the
|
|
86
|
-
// person saving reaches every server they are delegating. The dependency runs one way — the
|
|
87
|
-
// tool service knows nothing about the tree, and the delegation lookup below reads D1 directly
|
|
88
|
-
// rather than through the node service.
|
|
89
|
-
const toolDelegation = createToolDelegation({
|
|
90
|
-
db: env.DB,
|
|
91
|
-
content: contentStore,
|
|
92
|
-
id: ulid,
|
|
93
|
-
now,
|
|
94
|
-
});
|
|
95
79
|
const tools = createTools({
|
|
96
80
|
portalUrl,
|
|
97
81
|
remote: createRemoteTools({ fetch: globalThis.fetch.bind(globalThis) }),
|
|
98
82
|
tokens: portalTokens,
|
|
99
|
-
delegation: toolDelegation.resolve,
|
|
100
|
-
audit: toolDelegation.audit,
|
|
101
83
|
refresh: async (token) => {
|
|
102
84
|
try {
|
|
103
85
|
const refreshed = await oauth.refresh({
|
|
@@ -126,56 +108,9 @@ export default {
|
|
|
126
108
|
return await sha256Hex(crypto, JSON.stringify(value));
|
|
127
109
|
},
|
|
128
110
|
});
|
|
129
|
-
// ⚠️ Built before `nodes` and not after, because creating an agent has to be able to hand the
|
|
130
|
-
// runtime its key inside the same call (D29). Its `visibleNode` closes over `nodes` and is only
|
|
131
|
-
// ever called later, which is what keeps the two from being a cycle.
|
|
132
|
-
const agents = createAgentRuntimeService({
|
|
133
|
-
...(env.AGENT ? { runtime: env.AGENT } : {}),
|
|
134
|
-
...(env.AGENT_HANDOVER_SECRET ? { handoverSecret: env.AGENT_HANDOVER_SECRET } : {}),
|
|
135
|
-
// The tree's own visibility lookup, unchanged: whether somebody may reach an agent is the
|
|
136
|
-
// same question as whether they may see the node, and no second answer is invented here.
|
|
137
|
-
visibleNode: async (actor, nodeId) => await nodes.visibleNode(actor, nodeId),
|
|
138
|
-
});
|
|
139
|
-
// ⚠️ One Cloudflare read client for both of the account's answers — the gateway's cost log
|
|
140
|
-
// (#251) and the Workers AI price list (#257) — because it is one token and one account. It is
|
|
141
|
-
// absent as a whole where any of the three variables is missing: a half-configured client would
|
|
142
|
-
// fail per call with a 401 that reads like a permission problem instead of saying, once, that
|
|
143
|
-
// this deployment was never given a token.
|
|
144
|
-
const gatewayAccountId = env.AI_GATEWAY_ACCOUNT_ID?.trim();
|
|
145
|
-
const gatewayId = env.AI_GATEWAY_ID?.trim();
|
|
146
|
-
const gatewayReadToken = env.AI_GATEWAY_READ_TOKEN?.trim();
|
|
147
|
-
const cloudflareApi = gatewayAccountId && gatewayId && gatewayReadToken
|
|
148
|
-
? createCloudflareApi({
|
|
149
|
-
accountId: gatewayAccountId,
|
|
150
|
-
gatewayId,
|
|
151
|
-
token: gatewayReadToken,
|
|
152
|
-
fetch: globalThis.fetch.bind(globalThis),
|
|
153
|
-
})
|
|
154
|
-
: undefined;
|
|
155
|
-
const agentCosts = createAgentCosts({
|
|
156
|
-
...(cloudflareApi ? { gateway: cloudflareApi } : {}),
|
|
157
|
-
visibleNode: async (actor, nodeId) => await nodes.visibleNode(actor, nodeId),
|
|
158
|
-
now,
|
|
159
|
-
});
|
|
160
|
-
const models = createModelCatalog({ ...(cloudflareApi ? { cloudflare: cloudflareApi } : {}) });
|
|
161
111
|
const nodes = createNodes({
|
|
162
112
|
repository: nodeRepository,
|
|
163
113
|
content: contentStore,
|
|
164
|
-
// ⚠️ Built from `GATE_URL` alone — no service key is handed to it, and none would help. The
|
|
165
|
-
// Applications surface is admin-gated on a real principal, so every call carries the bearer
|
|
166
|
-
// of the person making it, which the tree passes in per operation (#182).
|
|
167
|
-
applications: createGateApplications({
|
|
168
|
-
fetch: globalThis.fetch.bind(globalThis),
|
|
169
|
-
gateUrl: env.GATE_URL,
|
|
170
|
-
}),
|
|
171
|
-
// The binding's presence IS the signal (#190) — no separate configuration option exists.
|
|
172
|
-
agentRuntimeAvailable: () => env.AGENT !== undefined,
|
|
173
|
-
// Where the Application key goes, and the only place it goes (D29). The same service the
|
|
174
|
-
// browser paths are proxied through, so there is one door to the runtime and not two.
|
|
175
|
-
agentKeys: { store: async (input) => await agents.storeKey(input) },
|
|
176
|
-
// The same door again (#214): a definition that was written is a schedule that has to be
|
|
177
|
-
// armed, and the runtime only looks when it is told to.
|
|
178
|
-
agentSchedules: { sync: async (input) => await agents.syncSchedules(input) },
|
|
179
114
|
id: ulid,
|
|
180
115
|
now,
|
|
181
116
|
indexing: createIndexQueue(env.INDEXING),
|
|
@@ -247,9 +182,6 @@ export default {
|
|
|
247
182
|
},
|
|
248
183
|
indexing: createIndexQueue(env.INDEXING),
|
|
249
184
|
}),
|
|
250
|
-
agents,
|
|
251
|
-
agentCosts,
|
|
252
|
-
models,
|
|
253
185
|
auth,
|
|
254
186
|
}).fetch(request);
|
|
255
187
|
},
|
|
@@ -259,6 +191,7 @@ export default {
|
|
|
259
191
|
content: createContentStore(env.CONTENT),
|
|
260
192
|
semantic: env.AI && env.SEARCH ? createSemanticIndex({ ai: env.AI, index: env.SEARCH }) : undefined,
|
|
261
193
|
converter: env.AI ? createDocumentConverter(env.AI) : undefined,
|
|
194
|
+
hash: async (content) => await sha256Hex(crypto, content),
|
|
262
195
|
now: () => new Date(),
|
|
263
196
|
});
|
|
264
197
|
for (const message of batch.messages) {
|
|
@@ -9,17 +9,6 @@ export interface CloudflareEnv {
|
|
|
9
9
|
ASSETS?: {
|
|
10
10
|
fetch(request: Request): Promise<Response>;
|
|
11
11
|
};
|
|
12
|
-
/**
|
|
13
|
-
* The agent runtime Worker, bound as a service.
|
|
14
|
-
*
|
|
15
|
-
* ⚠️ Optional, and it has to be: a deployment may run Intel without agents, and a hard requirement
|
|
16
|
-
* here would make the whole installation fail to start over a feature nobody uses. Where it is
|
|
17
|
-
* absent the agent routes answer 503 by name; where it is present the browser reaches the runtime
|
|
18
|
-
* through Intel and never across a second origin (#178).
|
|
19
|
-
*/
|
|
20
|
-
AGENT?: {
|
|
21
|
-
fetch(request: Request): Promise<Response>;
|
|
22
|
-
};
|
|
23
12
|
DB: D1Database;
|
|
24
13
|
CONTENT: R2Bucket;
|
|
25
14
|
INDEXING: QueueProducer<IndexMessage>;
|
|
@@ -30,34 +19,6 @@ export interface CloudflareEnv {
|
|
|
30
19
|
GATE_SERVICE_KEY: string;
|
|
31
20
|
INTEL_URL: string;
|
|
32
21
|
INTEL_SESSION_SECRET: string;
|
|
33
|
-
/**
|
|
34
|
-
* The shared secret that proves a key handover reached the agent runtime through this Worker
|
|
35
|
-
* (D29, #207). The SAME value the agent Worker holds as `AGENT_HANDOVER_SECRET`.
|
|
36
|
-
*
|
|
37
|
-
* ⚠️ Optional exactly like `AGENT`: a deployment may run Intel without agents, and a hard
|
|
38
|
-
* requirement would make the whole installation fail to start over a feature nobody uses. Where
|
|
39
|
-
* it is absent, creating an agent refuses by name instead of leaving one without a key.
|
|
40
|
-
*/
|
|
41
|
-
AGENT_HANDOVER_SECRET?: string;
|
|
42
|
-
/**
|
|
43
|
-
* Where the AI Gateway lives and how to READ it (#251, #257).
|
|
44
|
-
*
|
|
45
|
-
* ⚠️ `AI_GATEWAY_READ_TOKEN` is not the agent runtime's `AI_GATEWAY_TOKEN` and must never be the
|
|
46
|
-
* same value. That one carries `AI Gateway: Run` and buys inference; this one carries
|
|
47
|
-
* `AI Gateway: Read` — plus `Workers AI: Read` for the model catalog — and buys nothing at all.
|
|
48
|
-
* One credential doing both would put a spending permission into the Worker that only reads.
|
|
49
|
-
*
|
|
50
|
-
* ⚠️ Cloudflare cannot scope `AI Gateway: Read` to a single gateway; it is account-wide, the same
|
|
51
|
-
* limitation `AI Gateway: Run` already had in #239. It is worth knowing when the token is created
|
|
52
|
-
* and is not a reason against the route: it reads and writes nothing.
|
|
53
|
-
*
|
|
54
|
-
* ⚠️ All three optional, together. A deployment that configures none of them gets a cost view that
|
|
55
|
-
* says "not configured" and a model catalog off the built-in table — both named states, neither an
|
|
56
|
-
* error. Intel without a Cloudflare token is a supported installation.
|
|
57
|
-
*/
|
|
58
|
-
AI_GATEWAY_ACCOUNT_ID?: string;
|
|
59
|
-
AI_GATEWAY_ID?: string;
|
|
60
|
-
AI_GATEWAY_READ_TOKEN?: string;
|
|
61
22
|
TOOL_SOURCE_ORIGINS: string;
|
|
62
23
|
MCP_PORTAL_URL?: string;
|
|
63
24
|
ALLOW_INSECURE_OAUTH?: string;
|
|
@@ -219,7 +219,7 @@ export function createFlowRepository(deps) {
|
|
|
219
219
|
SELECT
|
|
220
220
|
EXISTS (SELECT 1 FROM ancestors WHERE id = ?) AS in_subtree,
|
|
221
221
|
EXISTS (
|
|
222
|
-
SELECT 1 FROM
|
|
222
|
+
SELECT 1 FROM node_grants grant_row
|
|
223
223
|
JOIN ancestors ON ancestors.id = grant_row.node_id
|
|
224
224
|
WHERE grant_row.principal_type = 'organization'
|
|
225
225
|
AND grant_row.verb = 'execute'
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// How much of a chunk is kept as the passage a searcher is shown. The same 480 characters
|
|
2
|
+
// `hydrateVisibleCitations` cuts out of the full-text row, so a citation reads the same length
|
|
3
|
+
// whether it came from a card's own vector or from the fallback beside it.
|
|
4
|
+
const maxPassageCharacters = 480;
|
|
1
5
|
function mapTarget(row, contentKeys) {
|
|
2
6
|
return {
|
|
3
7
|
nodeId: row.node_id,
|
|
@@ -68,6 +72,81 @@ export function createNodeIndexRepository(db) {
|
|
|
68
72
|
.bind(target.versionId, target.nodeId, chunk.title, chunk.text, target.nodeId, target.versionId)),
|
|
69
73
|
]);
|
|
70
74
|
},
|
|
75
|
+
/**
|
|
76
|
+
* ⚠️ `archived_at IS NOT NULL` carries this on its own, and it is the whole guard
|
|
77
|
+
* (anchrd/intel#348). `getTarget` above answers null for two reasons and only one of them means
|
|
78
|
+
* "purge": the other is a queue message for a version a later save superseded, and Cloudflare
|
|
79
|
+
* Queues deliver at least once, so those arrive as a matter of course. Acting on one would empty
|
|
80
|
+
* the vector index of a live board behind the back of the save that had just filled it — and
|
|
81
|
+
* nothing would report it, because a search that finds less is not an error.
|
|
82
|
+
*
|
|
83
|
+
* ⚠️ Deliberately NOT `current_version_id = v.id` as well, although that is what would make this
|
|
84
|
+
* the mirror image of `getTarget`. Symmetry is not a reason to write a condition: once the node
|
|
85
|
+
* IS archived, every version of it says the same thing about that node's vectors, so the extra
|
|
86
|
+
* clause could only ever refuse a purge that was right. A condition no test can turn red is a
|
|
87
|
+
* condition the next reader has to guess the purpose of.
|
|
88
|
+
*/
|
|
89
|
+
async archivedNodeId(versionId) {
|
|
90
|
+
const row = await db
|
|
91
|
+
.prepare(`SELECT n.id AS node_id
|
|
92
|
+
FROM node_versions v
|
|
93
|
+
JOIN nodes n ON n.id = v.node_id
|
|
94
|
+
WHERE v.id = ? AND n.archived_at IS NOT NULL`)
|
|
95
|
+
.bind(versionId)
|
|
96
|
+
.first();
|
|
97
|
+
return row?.node_id ?? null;
|
|
98
|
+
},
|
|
99
|
+
async listVectors(nodeId) {
|
|
100
|
+
const result = await db
|
|
101
|
+
.prepare(`SELECT chunk_key, fingerprint, passage FROM node_vectors WHERE node_id = ?`)
|
|
102
|
+
.bind(nodeId)
|
|
103
|
+
.all();
|
|
104
|
+
return (result.results ?? []).map((row) => ({
|
|
105
|
+
chunkKey: row.chunk_key,
|
|
106
|
+
fingerprint: row.fingerprint,
|
|
107
|
+
passage: row.passage,
|
|
108
|
+
}));
|
|
109
|
+
},
|
|
110
|
+
/**
|
|
111
|
+
* The record of every vector this node has, replacing the record it had.
|
|
112
|
+
*
|
|
113
|
+
* ⚠️ The same delete-then-insert in ONE batch as `replace` above, and the same guard in front of
|
|
114
|
+
* every statement: a pass for a version that is no longer current, or for a node that has been
|
|
115
|
+
* archived meanwhile, writes nothing at all rather than half a record. What must not happen is
|
|
116
|
+
* the delete landing without the inserts — the next pass would then re-embed the whole board,
|
|
117
|
+
* which is exactly the cost anchrd/intel#301 exists to avoid.
|
|
118
|
+
*/
|
|
119
|
+
async replaceVectors(target, records) {
|
|
120
|
+
await db.batch([
|
|
121
|
+
db
|
|
122
|
+
.prepare(`DELETE FROM node_vectors WHERE node_id = ? AND EXISTS (
|
|
123
|
+
SELECT 1 FROM nodes
|
|
124
|
+
WHERE id = ? AND current_version_id = ? AND archived_at IS NULL
|
|
125
|
+
)`)
|
|
126
|
+
.bind(target.nodeId, target.nodeId, target.versionId),
|
|
127
|
+
...records.map((record) => db
|
|
128
|
+
.prepare(`INSERT INTO node_vectors (node_id, chunk_key, version_id, fingerprint, passage)
|
|
129
|
+
SELECT ?, ?, ?, ?, ? WHERE EXISTS (
|
|
130
|
+
SELECT 1 FROM nodes
|
|
131
|
+
WHERE id = ? AND current_version_id = ? AND archived_at IS NULL
|
|
132
|
+
)`)
|
|
133
|
+
.bind(target.nodeId, record.chunkKey, target.versionId, record.fingerprint, record.passage.slice(0, maxPassageCharacters), target.nodeId, target.versionId)),
|
|
134
|
+
]);
|
|
135
|
+
},
|
|
136
|
+
/**
|
|
137
|
+
* The record of one node, gone — because its vectors are (anchrd/intel#348).
|
|
138
|
+
*
|
|
139
|
+
* ⚠️ No guard at all, and the absence is deliberate in both directions. There is no
|
|
140
|
+
* `archived_at IS NOT NULL`: the state this write exists to prevent is a row that outlives the
|
|
141
|
+
* vector it names, and a node restored between the deletion and this statement would keep
|
|
142
|
+
* exactly that — a record claiming vectors that are gone, which the next pass reads as "already
|
|
143
|
+
* embedded" and skips, leaving those cards silently unfindable. And there is no version
|
|
144
|
+
* condition: `archivedNodeId` has already established which node this is, from that same
|
|
145
|
+
* version, and a second reading of a moving row is a second answer rather than a safer one.
|
|
146
|
+
*/
|
|
147
|
+
async deleteVectors(nodeId) {
|
|
148
|
+
await db.prepare("DELETE FROM node_vectors WHERE node_id = ?").bind(nodeId).run();
|
|
149
|
+
},
|
|
71
150
|
async markIndexed(versionId, occurredAt) {
|
|
72
151
|
await db
|
|
73
152
|
.prepare(`UPDATE node_index_state
|