@anchrd/intel-api 0.14.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.
Files changed (51) hide show
  1. package/dist/adapters/cloudflare/cloudflare.js +0 -68
  2. package/dist/adapters/cloudflare/cloudflare.types.d.ts +0 -39
  3. package/dist/adapters/db/db-flows.js +1 -1
  4. package/dist/adapters/db/db-grants.js +1 -1
  5. package/dist/adapters/db/db.js +16 -113
  6. package/dist/bundle/bundle.js +28 -141
  7. package/dist/cli/cli.js +3 -9
  8. package/dist/http/http.js +1 -206
  9. package/dist/http/http.types.d.ts +0 -8
  10. package/dist/indexing/indexing.js +18 -89
  11. package/dist/intel/intel.js +4 -9
  12. package/dist/intel/intel.types.d.ts +0 -6
  13. package/dist/mcp/mcp.js +3 -292
  14. package/dist/mcp/mcp.types.d.ts +2 -7
  15. package/dist/nodes/document-links/document-links.d.ts +6 -8
  16. package/dist/nodes/document-links/document-links.js +8 -31
  17. package/dist/nodes/nodes.js +20 -886
  18. package/dist/nodes/nodes.types.d.ts +10 -169
  19. package/dist/tools/tools.js +37 -148
  20. package/dist/tools/tools.types.d.ts +0 -21
  21. package/migrations/0018_no_context_policy_at_last.sql +13 -6
  22. package/migrations/0019_one_name_for_the_grants.sql +52 -0
  23. package/package.json +2 -2
  24. package/dist/adapters/cloudflare-api/cloudflare-api.d.ts +0 -22
  25. package/dist/adapters/cloudflare-api/cloudflare-api.js +0 -306
  26. package/dist/adapters/cloudflare-api/cloudflare-api.types.d.ts +0 -64
  27. package/dist/adapters/cloudflare-api/cloudflare-api.types.js +0 -1
  28. package/dist/adapters/gate-applications/gate-applications.d.ts +0 -23
  29. package/dist/adapters/gate-applications/gate-applications.js +0 -88
  30. package/dist/adapters/tool-delegation/tool-delegation.d.ts +0 -22
  31. package/dist/adapters/tool-delegation/tool-delegation.js +0 -90
  32. package/dist/agent-costs/agent-costs.d.ts +0 -16
  33. package/dist/agent-costs/agent-costs.js +0 -105
  34. package/dist/agent-costs/agent-costs.types.d.ts +0 -30
  35. package/dist/agent-costs/agent-costs.types.js +0 -1
  36. package/dist/agent-runtime/agent-runtime.d.ts +0 -16
  37. package/dist/agent-runtime/agent-runtime.js +0 -150
  38. package/dist/agent-runtime/agent-runtime.types.d.ts +0 -122
  39. package/dist/agent-runtime/agent-runtime.types.js +0 -1
  40. package/dist/model-catalog/model-catalog.d.ts +0 -2
  41. package/dist/model-catalog/model-catalog.js +0 -99
  42. package/dist/model-catalog/model-catalog.types.d.ts +0 -15
  43. package/dist/model-catalog/model-catalog.types.js +0 -1
  44. package/dist/nodes/board/board.d.ts +0 -61
  45. package/dist/nodes/board/board.js +0 -826
  46. package/dist/nodes/board/board.types.d.ts +0 -38
  47. package/dist/nodes/board/board.types.js +0 -1
  48. package/migrations/0013_agents_in_the_tree.sql +0 -76
  49. package/migrations/0014_agent_applications.sql +0 -25
  50. package/migrations/0015_tools_delegated_from_a_connection.sql +0 -15
  51. package/migrations/0016_boards_in_the_tree.sql +0 -80
@@ -1,90 +0,0 @@
1
- import { AgentDefinition } from "@anchrd/intel-contract";
2
- import { reportUnexpectedError } from "../../shared/report-unexpected-error/report-unexpected-error.js";
3
- /**
4
- * The two D1 statements the delegated tool path needs (D30): who is acting, and a record that they
5
- * did.
6
- *
7
- * ⚠️ Its own adapter rather than a method on the node repository, and deliberately so. The question
8
- * is asked with a **Gate Application id** and no Intel actor at all — there is no ACL to apply,
9
- * because the answer is not "may you read this agent" but "which agent are you". Hanging it off the
10
- * tree's repository would put an unauthorized read next to authorized ones, which is the shape a
11
- * later reader copies by accident.
12
- */
13
- export function createToolDelegation(deps) {
14
- return {
15
- /**
16
- * ⚠️ `null` has exactly ONE meaning here and it is a strong claim: "this caller is not an
17
- * agent", after which the tool service treats them as an ordinary person with a portal
18
- * connection of their own. Every other outcome — an archived agent, an agent with no definition
19
- * version yet, a body R2 lost, a body that will not parse, a definition with no `tools` — is an
20
- * agent that delegates NOTHING, and is answered as an empty delegation. That is the difference
21
- * between "act as yourself" and "act on nobody's connection", and only one of them is safe to
22
- * fall into by accident.
23
- *
24
- * The joins are therefore LEFT: a row in `agent_applications` is already the whole answer to
25
- * "is this an agent", and losing it to a missing version would turn a fresh agent into a user.
26
- */
27
- async resolve(applicationId) {
28
- const row = await deps.db
29
- .prepare(`SELECT a.node_id AS node_id, n.archived_at AS archived_at,
30
- v.content_key AS content_key
31
- FROM agent_applications a
32
- LEFT JOIN nodes n ON n.id = a.node_id
33
- LEFT JOIN node_versions v ON v.id = n.current_version_id
34
- WHERE a.application_id = ?`)
35
- .bind(applicationId)
36
- .first();
37
- if (!row)
38
- return null;
39
- const nothing = { agentId: row.node_id, delegatedBy: "", servers: [] };
40
- // Archiving an agent switches its Gate Application off, but Gate's own state is not what
41
- // Intel is allowed to depend on: an archived agent reaches nothing through Intel from the
42
- // moment it is archived, whatever another system still thinks.
43
- if (row.archived_at !== null || row.content_key === null)
44
- return nothing;
45
- const body = await deps.content.get(row.content_key);
46
- if (body === null) {
47
- reportUnexpectedError(new Error(`agent ${row.node_id} has no readable definition body`));
48
- return nothing;
49
- }
50
- let parsed;
51
- try {
52
- parsed = JSON.parse(body);
53
- }
54
- catch {
55
- parsed = null;
56
- }
57
- const definition = AgentDefinition.safeParse(parsed);
58
- if (!definition.success) {
59
- reportUnexpectedError(new Error(`agent ${row.node_id} has a definition Intel cannot parse`));
60
- return nothing;
61
- }
62
- const tools = definition.data.tools;
63
- if (!tools)
64
- return nothing;
65
- return {
66
- agentId: row.node_id,
67
- delegatedBy: tools.delegatedBy,
68
- servers: tools.servers,
69
- };
70
- },
71
- async audit(event) {
72
- // ⚠️ The agent's node is the resource and the Application is the actor, which is the only
73
- // pairing that reads correctly later: `actor_id` is what authenticated, `resource_id` is what
74
- // a person searches for. The tool and the server are metadata, and the ARGUMENTS are absent —
75
- // an append-only table is the last place a payload should be copied to.
76
- await deps.db
77
- .prepare(`INSERT INTO audit_events (
78
- id, actor_id, action, resource_type, resource_id, metadata_json, occurred_at
79
- ) VALUES (?, ?, 'tool.execute', 'agent', ?, ?, ?)`)
80
- .bind(deps.id(), event.applicationId, event.agentId, JSON.stringify({
81
- tool: event.tool,
82
- server: event.server,
83
- // Both principals, which is the whole point of the row: who acted, and whose portal
84
- // connection made it possible (D30).
85
- delegatedBy: event.delegatedBy,
86
- }), deps.now().toISOString())
87
- .run();
88
- },
89
- };
90
- }
@@ -1,16 +0,0 @@
1
- import type { AgentCostsDeps, AgentCostsService } from "./agent-costs.types.js";
2
- /**
3
- * What this agent has cost, read out of Cloudflare's AI Gateway log rather than computed here.
4
- *
5
- * ⚠️ Nothing in this file multiplies tokens by a price, and nothing may start to. The gateway
6
- * publishes the billed figure per call and it is the debit from the account balance, 1:1 — measured
7
- * on 2026-08-07 (#251). A price table beside it would be a second, self-maintained answer to a
8
- * question that already has a first one, and the day the two disagree the wrong one is the one
9
- * on screen.
10
- *
11
- * ⚠️ The three checks below are in the same order as the runtime proxy's, and for the same reasons:
12
- * the capability first so a refusal costs no storage read; the resource ACL second, because seeing
13
- * an agent and seeing its bill are the same question; the reader last, so nobody learns whether this
14
- * deployment holds a Cloudflare token before they are allowed to ask.
15
- */
16
- export declare function createAgentCosts(deps: AgentCostsDeps): AgentCostsService;
@@ -1,105 +0,0 @@
1
- import { IntelError } from "../shared/intel-error/intel-error.js";
2
- /**
3
- * The windows the profile shows, longest last.
4
- *
5
- * Two, and both of them, because they answer different questions: thirty days is what an agent
6
- * costs to keep, seven is whether that changed. One number alone cannot say "this got more
7
- * expensive last week".
8
- */
9
- const Windows = [7, 30];
10
- const DayMs = 24 * 60 * 60 * 1_000;
11
- /**
12
- * What this agent has cost, read out of Cloudflare's AI Gateway log rather than computed here.
13
- *
14
- * ⚠️ Nothing in this file multiplies tokens by a price, and nothing may start to. The gateway
15
- * publishes the billed figure per call and it is the debit from the account balance, 1:1 — measured
16
- * on 2026-08-07 (#251). A price table beside it would be a second, self-maintained answer to a
17
- * question that already has a first one, and the day the two disagree the wrong one is the one
18
- * on screen.
19
- *
20
- * ⚠️ The three checks below are in the same order as the runtime proxy's, and for the same reasons:
21
- * the capability first so a refusal costs no storage read; the resource ACL second, because seeing
22
- * an agent and seeing its bill are the same question; the reader last, so nobody learns whether this
23
- * deployment holds a Cloudflare token before they are allowed to ask.
24
- */
25
- export function createAgentCosts(deps) {
26
- return {
27
- async read(actor, agentId) {
28
- if (!actor.canRun) {
29
- throw new IntelError(403, "permission_required", "Permission required");
30
- }
31
- const node = await deps.visibleNode(actor, agentId);
32
- // Not found and not visible answer alike, as everywhere else in the tree.
33
- if (node?.kind !== "agent") {
34
- throw new IntelError(404, "agent_not_found", "Agent was not found");
35
- }
36
- if (!deps.gateway)
37
- return empty("not_configured");
38
- const until = deps.now();
39
- const longest = Windows[Windows.length - 1] ?? 30;
40
- const since = new Date(until.getTime() - longest * DayMs);
41
- let page;
42
- try {
43
- page = await deps.gateway.gatewayCalls({ agentId, since, until });
44
- }
45
- catch {
46
- // ⚠️ Swallowed on purpose, and named rather than rethrown. The run list beside these figures
47
- // is answered by the runtime and is perfectly readable; letting a Cloudflare outage take the
48
- // whole screen down would hide the tokens too — and the tokens are the half that never
49
- // depended on Cloudflare being reachable.
50
- return empty("unreadable");
51
- }
52
- return {
53
- status: "read",
54
- currency: "USD",
55
- runs: perRun(page.calls),
56
- windows: Windows.map((days) => window(days, page.calls, until)),
57
- partial: page.partial,
58
- };
59
- },
60
- };
61
- }
62
- function empty(status) {
63
- return {
64
- status,
65
- currency: "USD",
66
- // ⚠️ Empty, and the status is what stops it reading as "free". Nothing here invents a zero:
67
- // `runs: []` with `status: "read"` really does mean this agent has cost nothing yet.
68
- runs: [],
69
- windows: Windows.map((days) => ({ days, cost: 0, calls: 0, models: [] })),
70
- partial: false,
71
- };
72
- }
73
- function perRun(calls) {
74
- const byRun = new Map();
75
- for (const call of calls) {
76
- // A call the runtime could not stamp — anything from before #251 — belongs to no run and is
77
- // dropped from the per-run view. It still counts towards the windows below: it is the agent's
78
- // money either way, and only its place in the list is unknown.
79
- if (!call.runId)
80
- continue;
81
- const known = byRun.get(call.runId);
82
- if (known) {
83
- known.cost += call.cost;
84
- known.calls += 1;
85
- continue;
86
- }
87
- byRun.set(call.runId, { runId: call.runId, cost: call.cost, calls: 1 });
88
- }
89
- return [...byRun.values()];
90
- }
91
- function window(days, calls, until) {
92
- const from = until.getTime() - days * DayMs;
93
- const inside = calls.filter((call) => {
94
- const at = Date.parse(call.at);
95
- // An unparseable timestamp counts towards the longest window rather than being dropped: the
96
- // money was spent, and a total that quietly omits it is the wrong kind of wrong.
97
- return Number.isNaN(at) ? days === Windows[Windows.length - 1] : at >= from;
98
- });
99
- return {
100
- days,
101
- cost: inside.reduce((total, call) => total + call.cost, 0),
102
- calls: inside.length,
103
- models: [...new Set(inside.map((call) => call.model).filter((model) => model.length > 0))],
104
- };
105
- }
@@ -1,30 +0,0 @@
1
- import type { AgentCosts, Node } from "@anchrd/intel-contract";
2
- import type { CloudflareAccountApi } from "../adapters/cloudflare-api/cloudflare-api.types.js";
3
- import type { AgentActor } from "../agent-runtime/agent-runtime.types.js";
4
- import type { Actor } from "../nodes/nodes.types.js";
5
- export interface AgentCostsDeps {
6
- /**
7
- * Absent where the deployment configured no account id, gateway id or read token.
8
- *
9
- * ⚠️ Absent is a NAMED state and never an error: an installation that never wants Intel to hold a
10
- * Cloudflare token is a supported installation, and it should read "not configured" rather than
11
- * a 502 somebody spends an afternoon on.
12
- */
13
- gateway?: Pick<CloudflareAccountApi, "gatewayCalls">;
14
- /**
15
- * The tree's own visibility lookup, unchanged on the way through — the same one the runtime proxy
16
- * uses. What an agent spends is as sensitive as what it does, and neither answer may be kinder
17
- * than the other.
18
- */
19
- visibleNode(actor: Actor, nodeId: string): Promise<Node | null>;
20
- now(): Date;
21
- }
22
- export interface AgentCostsService {
23
- /**
24
- * ⚠️ The answer shape lives in `@anchrd/intel-contract` and not here, because the browser parses
25
- * it. A second declaration in this package is how the screen and the service start disagreeing
26
- * about what `status` can say — and `status` is the field that separates "cost nothing" from "not
27
- * known" (#251).
28
- */
29
- read(actor: AgentActor, agentId: string): Promise<AgentCosts>;
30
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,16 +0,0 @@
1
- import type { AgentRuntimeDeps, AgentRuntimeService } from "./agent-runtime.types.js";
2
- /**
3
- * Intel's door to the agent runtime.
4
- *
5
- * ⚠️ The browser never talks to the runtime. It talks to Intel, and Intel forwards — one origin for
6
- * the page, one session cookie, no CORS, and no Gate token in browser JavaScript. What travels on
7
- * the forwarded call is the caller's OWN bearer, which the runtime then checks for `agents/run`
8
- * exactly as it would from any other client. Intel impersonates nobody: there is no service key on
9
- * this path, and a request that arrives without a usable session never reaches the binding.
10
- *
11
- * ⚠️ The three checks below are in this order on purpose. The capability comes first, so a caller
12
- * who may not drive agents at all causes no storage read; the resource ACL comes second, because it
13
- * is the question the runtime cannot answer; the binding comes last, so nobody learns whether this
14
- * deployment has a runtime before they are allowed to ask.
15
- */
16
- export declare function createAgentRuntimeService(deps: AgentRuntimeDeps): AgentRuntimeService;
@@ -1,150 +0,0 @@
1
- import { ProblemDetails } from "@anchrd/intel-contract";
2
- import { IntelError } from "../shared/intel-error/intel-error.js";
3
- /**
4
- * The host a service binding never resolves.
5
- *
6
- * A `Request` needs an absolute URL and a binding routes on the path alone, so this name exists
7
- * only to make one. `.internal` is not a resolvable TLD: a call that ever left this Worker by
8
- * mistake fails to connect rather than reaching a stranger who registered the domain.
9
- */
10
- const RuntimeOrigin = "https://intel-agent.internal";
11
- /**
12
- * Intel's door to the agent runtime.
13
- *
14
- * ⚠️ The browser never talks to the runtime. It talks to Intel, and Intel forwards — one origin for
15
- * the page, one session cookie, no CORS, and no Gate token in browser JavaScript. What travels on
16
- * the forwarded call is the caller's OWN bearer, which the runtime then checks for `agents/run`
17
- * exactly as it would from any other client. Intel impersonates nobody: there is no service key on
18
- * this path, and a request that arrives without a usable session never reaches the binding.
19
- *
20
- * ⚠️ The three checks below are in this order on purpose. The capability comes first, so a caller
21
- * who may not drive agents at all causes no storage read; the resource ACL comes second, because it
22
- * is the question the runtime cannot answer; the binding comes last, so nobody learns whether this
23
- * deployment has a runtime before they are allowed to ask.
24
- */
25
- export function createAgentRuntimeService(deps) {
26
- async function reach(actor, agentId) {
27
- if (!actor.canRun) {
28
- throw new IntelError(403, "permission_required", "Permission required");
29
- }
30
- const node = await deps.visibleNode(actor, agentId);
31
- // Not found and not visible answer alike, as everywhere else in the tree: the difference would
32
- // tell somebody an agent exists that they may not see.
33
- if (node?.kind !== "agent") {
34
- throw new IntelError(404, "agent_not_found", "Agent was not found");
35
- }
36
- if (!deps.runtime) {
37
- throw new IntelError(503, "agent_runtime_not_configured", "This installation has no agent runtime");
38
- }
39
- return deps.runtime;
40
- }
41
- async function forward(input) {
42
- const runtime = await reach(input.actor, input.agentId);
43
- const headers = new Headers({ authorization: `Bearer ${input.token}` });
44
- if (input.body !== undefined)
45
- headers.set("content-type", "application/json");
46
- return await runtime.fetch(new Request(`${RuntimeOrigin}/agents/${encodeURIComponent(input.agentId)}${input.path}`, {
47
- method: input.method,
48
- headers,
49
- ...(input.body === undefined ? {} : { body: input.body }),
50
- ...(input.signal ? { signal: input.signal } : {}),
51
- }));
52
- }
53
- /**
54
- * The two routes Intel dials itself, rather than on a browser's behalf: `/key` and
55
- * `/schedules/sync`.
56
- *
57
- * ⚠️ Neither goes through `forward`, and that is deliberate. `forward` asks `canRun` — the
58
- * question for somebody driving an agent — while both of these belong to *maintaining* one, which
59
- * Intel has already authorized with `knowledge/create` or `knowledge/write` plus the resource ACL.
60
- * What travels instead is the caller's own bearer beside the handover secret: the bearer says who
61
- * is behind the call, the secret says it came down Intel's binding at all. The runtime cannot
62
- * check Intel's resource ACL — it knows Gate and nothing about the tree — so without the secret a
63
- * published runtime hostname would open both routes to anybody holding `knowledge/write` (D29).
64
- */
65
- async function handover(call) {
66
- if (!deps.runtime) {
67
- throw new IntelError(503, "agent_runtime_not_configured", "This installation has no agent runtime");
68
- }
69
- const handoverSecret = deps.handoverSecret?.trim();
70
- if (!handoverSecret) {
71
- // ⚠️ Refused here rather than dialled and refused there. The runtime would answer 403, and
72
- // a 403 reads like "this person may not" — which would send an operator looking at Gate
73
- // permissions for a secret that was never set.
74
- throw new IntelError(503, call.unconfigured.code, call.unconfigured.detail);
75
- }
76
- return await deps.runtime.fetch(new Request(`${RuntimeOrigin}/agents/${encodeURIComponent(call.agentId)}${call.path}`, {
77
- method: "POST",
78
- headers: {
79
- authorization: `Bearer ${call.token}`,
80
- "content-type": "application/json",
81
- // ⚠️ Its own header, never folded into `authorization`: that one already carries the
82
- // caller's Gate bearer on this very request, and two credentials in one header is how
83
- // one of them ends up read as the other.
84
- "x-intel-agent-handover": handoverSecret,
85
- },
86
- body: call.body,
87
- }));
88
- }
89
- return {
90
- forward,
91
- async syncSchedules(input) {
92
- const response = await handover({
93
- agentId: input.agentId,
94
- path: "/schedules/sync",
95
- token: input.token,
96
- // No schedules travel: the runtime re-reads the definition from Intel itself (#214).
97
- body: "{}",
98
- unconfigured: {
99
- code: "agent_schedule_sync_not_configured",
100
- detail: "This installation has no shared secret for reaching the agent runtime, so schedules cannot be armed",
101
- },
102
- });
103
- if (response.ok)
104
- return;
105
- // ⚠️ Loud, and named after the state it leaves behind rather than after the call that failed.
106
- // What the caller has to act on is not "the runtime answered 502" but "this agent's schedules
107
- // are not armed", and the sentence says the repair: write the definition again.
108
- throw new IntelError(502, "agent_schedules_not_armed", `The definition was saved, but the agent runtime did not arm its schedules (${response.status}). Saving the definition again arms them.`);
109
- },
110
- async storeKey(input) {
111
- const response = await handover({
112
- agentId: input.agentId,
113
- path: "/key",
114
- token: input.token,
115
- body: JSON.stringify({ key: input.key }),
116
- unconfigured: {
117
- code: "agent_key_handover_not_configured",
118
- detail: "This installation has no shared secret for handing an agent its application key",
119
- },
120
- });
121
- if (response.ok)
122
- return;
123
- // ⚠️ The runtime's answer is read for its status and thrown away. Its body cannot be quoted
124
- // here the way `request` quotes it: the call that produced it carried a credential, and an
125
- // echoed body is the shortest path from a refusal to a key in a log. What the caller is told
126
- // is that the agent has no usable key — which is the only thing they can act on anyway.
127
- throw new IntelError(502, "agent_key_not_stored", `The agent runtime did not take this agent's application key (${response.status}), so the agent cannot run yet`);
128
- },
129
- // A plain fact, not a probe: the binding is either configured or it is not, and nothing is
130
- // dialled to answer. Unlike `reach` this stands before any per-agent check — it names no agent.
131
- available: () => deps.runtime !== undefined,
132
- async request(input) {
133
- const response = await forward(input);
134
- const body = await response.json().catch(() => null);
135
- if (!response.ok) {
136
- const problem = ProblemDetails.safeParse(body);
137
- // ⚠️ The runtime's own words, and only those. Its refusals are written to be read — by a
138
- // person on the Log tab and by a model on the MCP surface alike — while anything it could
139
- // not explain arrives as a generic 500 body. Nothing about the forwarded call is added
140
- // here: the URL, the binding and above all the bearer stay out of every message.
141
- throw new IntelError(response.status, problem.success
142
- ? (problem.data.code ?? "agent_runtime_refused")
143
- : "agent_runtime_refused", problem.success
144
- ? (problem.data.detail ?? problem.data.title)
145
- : "The agent runtime refused this call");
146
- }
147
- return body;
148
- },
149
- };
150
- }
@@ -1,122 +0,0 @@
1
- import type { Node } from "@anchrd/intel-contract";
2
- import type { Actor } from "../nodes/nodes.types.js";
3
- /**
4
- * The agent runtime Worker, as Intel is allowed to see it.
5
- *
6
- * One method, because that is all a service binding is. The Cloudflare adapter hands the binding
7
- * over unchanged; nothing below this line knows it is one, so the whole seam is testable with a
8
- * plain object that records what it was asked.
9
- */
10
- export interface AgentRuntimeBinding {
11
- fetch(request: Request): Promise<Response>;
12
- }
13
- /**
14
- * Who is driving the agent.
15
- *
16
- * ⚠️ `canRun` is the caller's Gate capability, resolved once at the door — never something a body,
17
- * an MCP argument or a definition may say. It is spelled beside the actor rather than derived here
18
- * for the same reason `FlowActor` spells `canRun`: the service must not need a Gate client.
19
- */
20
- export interface AgentActor extends Actor {
21
- canRun: boolean;
22
- }
23
- export interface AgentRuntimeDeps {
24
- /**
25
- * The shared secret that proves a key handover came down this binding (D29, #207).
26
- *
27
- * ⚠️ The SAME value the agent Worker holds as `AGENT_HANDOVER_SECRET`, and it travels on exactly
28
- * one route. It is not a substitute for the caller's bearer, which travels beside it: the bearer
29
- * says who is asking, this says the request came through Intel at all. The runtime cannot check
30
- * Intel's resource ACLs — it knows Gate and nothing about the tree — so without this a published
31
- * runtime hostname would let anybody with `knowledge/write` write the key of any agent.
32
- *
33
- * Absent, `storeKey` refuses by name rather than dialling the binding and being refused there:
34
- * the deployment is misconfigured, and saying so beats a 403 that reads like a permission problem.
35
- */
36
- handoverSecret?: string;
37
- /**
38
- * Absent where a deployment runs no agent Worker. Every call then refuses by name instead of
39
- * failing somewhere unreadable — a customer may deploy Intel without agents.
40
- */
41
- runtime?: AgentRuntimeBinding;
42
- /**
43
- * The tree's own visibility lookup, unchanged on the way through. It is the check the runtime
44
- * cannot make: the runtime knows Gate capabilities and nothing about Intel's resource ACLs, so
45
- * "may this person drive agents at all" is its question and "may they reach THIS agent" is ours.
46
- */
47
- visibleNode(actor: Actor, nodeId: string): Promise<Node | null>;
48
- }
49
- export interface ForwardInput {
50
- actor: AgentActor;
51
- /** The caller's own Gate bearer, passed on unchanged. Never a service key (D19). */
52
- token: string;
53
- agentId: string;
54
- method: "GET" | "POST";
55
- /** The path below `/agents/:agentId`, exactly as the runtime mounts it. */
56
- path: string;
57
- body?: string;
58
- signal?: AbortSignal;
59
- }
60
- /**
61
- * One agent's Application key, on its way from Gate into the agent's Durable Object (D29, #207).
62
- *
63
- * ⚠️ It exists as a parameter and never as a field of anything stored. The value is in one local
64
- * variable from the moment Gate answers until this call returns, exactly as it was before D29 — what
65
- * changed is where it goes: into the runtime instead of into a response the browser reads.
66
- */
67
- export interface StoreKeyInput {
68
- /** The caller's own Gate bearer, passed on unchanged. Never a service key (D19). */
69
- token: string;
70
- agentId: string;
71
- key: string;
72
- }
73
- /**
74
- * Which agent's alarm has to be brought in line with the definition Intel just wrote (#214).
75
- *
76
- * ⚠️ It carries no schedules. The runtime re-reads the definition from Intel itself, with the
77
- * agent's own token, so this is a "look again" and never a second copy of the document — a schedule
78
- * pushed down this call would be one the agent could act on without it ever having been written.
79
- */
80
- export interface SyncSchedulesInput {
81
- /** The caller's own Gate bearer, passed on unchanged. Never a service key (D19). */
82
- token: string;
83
- agentId: string;
84
- }
85
- export interface AgentRuntimeService {
86
- /** The browser's way through: the runtime's answer, body and headers untouched. */
87
- forward(input: ForwardInput): Promise<Response>;
88
- /**
89
- * Hands an agent the key of the Gate Application it runs as.
90
- *
91
- * ⚠️ The one door here that does NOT ask `canRun`, and that is deliberate. Its two callers —
92
- * creating an agent and rotating its key — have already decided who may act: `knowledge/create`
93
- * with a writable parent, or `knowledge/write` with the resource ACL of the agent node. Requiring
94
- * `agents/run` on top would mean a person who may create agents but never run one creates keyless
95
- * agents, which is #200 arrived at from the other side. The runtime makes the same distinction at
96
- * its own door.
97
- *
98
- * ⚠️ It fails loudly. A handover that quietly did not happen is an agent that exists, looks
99
- * right, and dies at its first run — the whole failure this ticket removes.
100
- */
101
- storeKey(input: StoreKeyInput): Promise<void>;
102
- /**
103
- * Arms the agent's alarm for whatever the definition Intel just wrote schedules (#214).
104
- *
105
- * ⚠️ Like `storeKey` and unlike everything else here, it does NOT ask `canRun`. Its two callers —
106
- * creating an agent and saving its definition — have already decided who may act: `knowledge/
107
- * create` with a writable parent, or `knowledge/write` with the resource ACL of the agent node.
108
- * Requiring `agents/run` on top would mean an editor who may not drive agents saves a schedule
109
- * that is never armed, which is the failure this exists to remove.
110
- *
111
- * ⚠️ It fails loudly, for the same reason `storeKey` does. A sync that quietly did not happen is
112
- * an agent whose profile shows a schedule that will never fire.
113
- */
114
- syncSchedules(input: SyncSchedulesInput): Promise<void>;
115
- /** The same door for a caller that wants the answer, not the response — Intel MCP uses this. */
116
- request(input: ForwardInput): Promise<unknown>;
117
- /**
118
- * Whether this deployment has a runtime at all (#190). The answer `/capabilities` gives to an
119
- * authenticated caller so the UI can stop offering agents instead of guessing from a 503.
120
- */
121
- available(): boolean;
122
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,2 +0,0 @@
1
- import type { ModelCatalogDeps, ModelCatalogService } from "./model-catalog.types.js";
2
- export declare function createModelCatalog(deps: ModelCatalogDeps): ModelCatalogService;
@@ -1,99 +0,0 @@
1
- /**
2
- * The figures this package knows without asking anybody, and the ONLY place they are written down.
3
- *
4
- * ⚠️ It moved here out of `packages/ui` with #257, and the move is the point rather than the
5
- * tidying. The comment it used to carry accused itself correctly — *"it is a copy and it ages
6
- * silently"* — and named the reason it stayed a copy: the Cloudflare endpoint needs a token, and a
7
- * token does not belong in a SPA. It belongs here. What the browser gets is the answer, never the
8
- * credential, and the Workers AI half of this table is overwritten by the live one on every read.
9
- *
10
- * ⚠️ The Anthropic entries have no live source and will not get one. Cloudflare resells those models
11
- * through Unified Billing and publishes no price list for them, so they stay `builtin` even on a
12
- * perfectly healthy read — which is exactly why `source` is per entry and not per response.
13
- *
14
- * ⚠️ Workers AI models without `function_calling` are deliberately absent from the live merge. An
15
- * agent is a tool loop; a model that cannot call a tool cannot run one.
16
- *
17
- * Collected 2026-08-06 from `GET /accounts/{id}/ai/models/search` and from Anthropic's model list.
18
- */
19
- const builtin = [
20
- entry("anthropic", "claude-opus-5", "Opus 5", 1_000_000, 5, 25),
21
- entry("anthropic", "claude-sonnet-5", "Sonnet 5", 1_000_000, 3, 15),
22
- entry("anthropic", "claude-sonnet-4", "Sonnet 4", 200_000, 3, 15),
23
- entry("anthropic", "claude-haiku-4-5", "Haiku 4.5", 200_000, 1, 5),
24
- entry("workers-ai", "@cf/openai/gpt-oss-120b", "GPT-OSS 120B", 128_000, 0.35, 0.75),
25
- entry("workers-ai", "@cf/openai/gpt-oss-20b", "GPT-OSS 20B", 128_000, 0.2, 0.3),
26
- entry("workers-ai", "@cf/moonshotai/kimi-k2.6", "Kimi K2.6", 262_144, 0.95, 4),
27
- entry("workers-ai", "@cf/moonshotai/kimi-k2.7-code", "Kimi K2.7 Code", 262_144, 0.95, 4),
28
- entry("workers-ai", "@cf/zai-org/glm-5.2", "GLM 5.2", 262_144, 1.4, 4.4),
29
- entry("workers-ai", "@cf/zai-org/glm-4.7-flash", "GLM 4.7 Flash", 131_072, 0.0605, 0.4),
30
- entry("workers-ai", "@cf/google/gemma-4-26b-a4b-it", "Gemma 4 26B", 256_000, 0.1, 0.3),
31
- entry("workers-ai", "@cf/nvidia/nemotron-3-120b-a12b", "Nemotron 3 120B", 256_000, 0.5, 1.5),
32
- entry("workers-ai", "@cf/meta/llama-4-scout-17b-16e-instruct", "Llama 4 Scout", 131_000, 0.27, 0.85),
33
- entry("workers-ai", "@cf/meta/llama-3.3-70b-instruct-fp8-fast", "Llama 3.3 70B Fast", 24_000, 0.293, 2.253),
34
- entry("workers-ai", "@cf/mistralai/mistral-small-3.1-24b-instruct", "Small 3.1 24B", 128_000, 0.351, 0.555),
35
- entry("workers-ai", "@cf/ibm-granite/granite-4.0-h-micro", "Granite 4.0 Micro", 131_000, 0.017, 0.112),
36
- entry("workers-ai", "@cf/qwen/qwen3-30b-a3b-fp8", "Qwen3 30B", 32_768, 0.0509, 0.335),
37
- ];
38
- function entry(provider, model, name, contextTokens, inputPerMillion, outputPerMillion) {
39
- return {
40
- provider,
41
- model,
42
- name,
43
- contextTokens,
44
- price: { inputPerMillion, outputPerMillion },
45
- source: "builtin",
46
- };
47
- }
48
- /**
49
- * The model's own name, taken from its id rather than invented.
50
- *
51
- * A live Workers AI model that is not in the table above has no written-out name anywhere, and
52
- * "@cf/qwen/qwen3-30b-a3b-fp8" prettified by any rule short enough to write here comes out wrong —
53
- * so the last segment of the id is used as it stands. A raw name is easier to recognise as raw than
54
- * a wrong one is to recognise as wrong.
55
- */
56
- function nameFromId(model) {
57
- const segments = model.split("/");
58
- return segments[segments.length - 1] ?? model;
59
- }
60
- export function createModelCatalog(deps) {
61
- return {
62
- async list() {
63
- if (!deps.cloudflare)
64
- return { entries: builtin, liveStatus: "not_configured" };
65
- try {
66
- const live = await deps.cloudflare.workersAiModels();
67
- const merged = new Map(builtin.map((known) => [`${known.provider}:${known.model}`, known]));
68
- for (const model of live) {
69
- // ⚠️ A model that cannot call a tool is skipped rather than listed without figures. It
70
- // would be an offer that produces a broken agent for whoever took it.
71
- if (!model.functionCalling)
72
- continue;
73
- const key = `workers-ai:${model.name}`;
74
- const known = merged.get(key);
75
- merged.set(key, {
76
- provider: "workers-ai",
77
- model: model.name,
78
- name: known?.name ?? nameFromId(model.name),
79
- // ⚠️ The live figure wins, and a live figure that is MISSING wins too — falling back to
80
- // the table for one of the two halves would produce an entry that is half fresh and
81
- // half years old, labelled fresh. Either the account answered for this model or it did
82
- // not.
83
- contextTokens: model.contextTokens,
84
- price: model.price,
85
- source: "cloudflare",
86
- });
87
- }
88
- return { entries: [...merged.values()], liveStatus: "read" };
89
- }
90
- catch {
91
- // ⚠️ Named, not thrown. The select has to render either way — an agent whose model cannot be
92
- // chosen is an agent nobody can repair from that screen — and the alternative to a stale
93
- // price here is no price at all, which helps nobody choose. What the caller gets is the
94
- // table plus the reason it is the table, and the screen says so.
95
- return { entries: builtin, liveStatus: "unreadable" };
96
- }
97
- },
98
- };
99
- }