agentic-assistant 0.2.0 → 0.4.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/demo.js ADDED
@@ -0,0 +1,158 @@
1
+ import {
2
+ createStaticTokenAuth,
3
+ createWorker
4
+ } from "./chunk-YMEM57LX.js";
5
+ import {
6
+ AnthropicProvider
7
+ } from "./chunk-FQD4OK2R.js";
8
+ import {
9
+ ToolRegistry,
10
+ VERSION
11
+ } from "./chunk-C44QT474.js";
12
+
13
+ // src/demo-page.ts
14
+ var DEMO_CSP = "default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'";
15
+ var DEMO_PAGE = `<!doctype html>
16
+ <html lang="en">
17
+ <head>
18
+ <meta charset="utf-8">
19
+ <meta name="viewport" content="width=device-width, initial-scale=1">
20
+ <title>agentic-assistant demo</title>
21
+ <style>
22
+ body{font:16px/1.5 system-ui,sans-serif;max-width:42rem;margin:2rem auto;padding:0 1rem;color:#1a1a1a;background:#fff}
23
+ label{display:block;margin-top:1rem;font-weight:600}
24
+ input,textarea,button{font:inherit;padding:.5rem;width:100%;box-sizing:border-box}
25
+ button{width:auto;min-height:2.5rem;margin-top:1rem;cursor:pointer}
26
+ :focus-visible{outline:3px solid #0b57d0;outline-offset:2px}
27
+ #out{white-space:pre-wrap;border:1px solid #767676;padding:1rem;margin-top:1rem;min-height:4rem}
28
+ .badge{display:inline-block;border:1px solid #1a1a1a;padding:0 .5rem;font-size:.875rem}
29
+ </style>
30
+ </head>
31
+ <body>
32
+ <main>
33
+ <h1>agentic-assistant demo</h1>
34
+ <p>Send a message to the hosted API. You need a bearer key. The key stays in this page and goes only to this Worker.</p>
35
+ <form id="f">
36
+ <label for="key">Bearer key</label>
37
+ <input id="key" type="password" autocomplete="off" required>
38
+ <label for="msg">Message</label>
39
+ <textarea id="msg" rows="3" required></textarea>
40
+ <button type="submit">Send</button>
41
+ </form>
42
+ <h2>Answer <span class="badge">AI-generated</span></h2>
43
+ <div id="out" role="log" aria-live="polite" aria-label="Assistant answer, AI-generated"></div>
44
+ </main>
45
+ <script>
46
+ const out = document.getElementById("out");
47
+ document.getElementById("f").addEventListener("submit", async (e) => {
48
+ e.preventDefault();
49
+ out.textContent = "";
50
+ let res;
51
+ try {
52
+ res = await fetch("/api/v1/chat", {
53
+ method: "POST",
54
+ headers: { authorization: "Bearer " + document.getElementById("key").value, "content-type": "application/json" },
55
+ body: JSON.stringify({ message: document.getElementById("msg").value }),
56
+ });
57
+ } catch {
58
+ out.textContent = "The request failed. Check the network and try again.";
59
+ return;
60
+ }
61
+ if (!res.ok || !res.body) {
62
+ const body = await res.json().catch(() => ({}));
63
+ out.textContent = "Error " + res.status + ": " + (body.message || body.error || "request failed");
64
+ return;
65
+ }
66
+ const reader = res.body.getReader();
67
+ const decoder = new TextDecoder();
68
+ let buffer = "";
69
+ let finished = false;
70
+ try {
71
+ for (;;) {
72
+ const { done, value } = await reader.read();
73
+ if (done) break;
74
+ buffer += decoder.decode(value, { stream: true });
75
+ let end;
76
+ while ((end = buffer.indexOf("\\n\\n")) >= 0) {
77
+ const block = buffer.slice(0, end);
78
+ buffer = buffer.slice(end + 2);
79
+ const data = block.split("\\n").find((l) => l.startsWith("data: "));
80
+ if (!data) continue;
81
+ let event;
82
+ try { event = JSON.parse(data.slice(6)); } catch { continue; }
83
+ if (event.type === "text_delta" && typeof event.text === "string") out.textContent += event.text;
84
+ if (event.type === "error") { finished = true; out.textContent += "\\n[stopped: " + event.error + "]"; }
85
+ if (event.type === "done") { finished = true; if (event.reason !== "end_turn") out.textContent += "\\n[stopped: " + event.reason + (event.error ? ", " + event.error : "") + "]"; }
86
+ }
87
+ }
88
+ } catch {
89
+ // The stream broke. The check below reports it.
90
+ }
91
+ if (!finished) out.textContent += "\\n[stopped: the connection closed before the answer was complete]";
92
+ });
93
+ </script>
94
+ </body>
95
+ </html>`;
96
+
97
+ // src/demo.ts
98
+ var demoRegistry = () => new ToolRegistry().register({
99
+ name: "demo_time",
100
+ description: "Return the current UTC time as an ISO 8601 text.",
101
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
102
+ scope: "demo:read",
103
+ risk: "safe",
104
+ handler: () => ({ now: (/* @__PURE__ */ new Date()).toISOString() })
105
+ });
106
+ var DEMO_SYSTEM = "You are a demo assistant. You may use the demo_time tool. Answer in short, plain text.";
107
+ function modelOptions(env) {
108
+ if (!env.MODEL_KEY) return void 0;
109
+ if (env.MODEL_URL) return { apiKey: env.MODEL_KEY, baseUrl: env.MODEL_URL, model: env.MODEL_NAME || void 0 };
110
+ if (env.MODEL_USE_DEFAULT_ENDPOINT === "1") return { apiKey: env.MODEL_KEY, useDefaultEndpoint: true, model: env.MODEL_NAME || void 0 };
111
+ return void 0;
112
+ }
113
+ var notConfigured = (message) => Response.json({ error: "not_configured", message }, { status: 503, headers: { "cache-control": "no-store" } });
114
+ var demos = /* @__PURE__ */ new WeakMap();
115
+ async function buildDemo(env) {
116
+ const tokens = env.ASSISTANT_TOKENS ? JSON.parse(env.ASSISTANT_TOKENS) : {};
117
+ const auth = await createStaticTokenAuth(tokens);
118
+ const worker = createWorker({
119
+ auth,
120
+ registry: demoRegistry(),
121
+ system: DEMO_SYSTEM,
122
+ limits: { maxTurns: 4, maxTotalTokens: 2e4, maxOutputTokensPerTurn: 1024 },
123
+ provider: () => {
124
+ const options = modelOptions(env);
125
+ if (options === void 0) throw new Error("no model");
126
+ return new AnthropicProvider(options);
127
+ }
128
+ });
129
+ return { worker, auth };
130
+ }
131
+ var demo_default = {
132
+ async fetch(request, env) {
133
+ const url = new URL(request.url);
134
+ if (request.method === "GET" && url.pathname === "/health") return Response.json({ ok: true, pack: env.PACK_NAME ?? "agentic-assistant", version: VERSION });
135
+ if (request.method === "GET" && url.pathname === "/") {
136
+ return new Response(DEMO_PAGE, {
137
+ headers: { "content-type": "text/html; charset=utf-8", "content-security-policy": DEMO_CSP, "cache-control": "no-store", "x-content-type-options": "nosniff" }
138
+ });
139
+ }
140
+ if (!url.pathname.startsWith("/api/v1/")) return Response.json({ error: "not found" }, { status: 404 });
141
+ let demo;
142
+ try {
143
+ let built = demos.get(env);
144
+ if (built === void 0) demos.set(env, built = buildDemo(env));
145
+ demo = await built;
146
+ } catch {
147
+ demos.delete(env);
148
+ return notConfigured("this demo is not set up");
149
+ }
150
+ if (request.method === "POST" && url.pathname.replace(/\/$/, "") === "/api/v1/chat" && modelOptions(env) === void 0) {
151
+ if (await demo.auth.authenticate(request.clone()) !== null) return notConfigured("this demo has no model key or model endpoint set");
152
+ }
153
+ return demo.worker.fetch(request, env);
154
+ }
155
+ };
156
+ export {
157
+ demo_default as default
158
+ };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { A as ACTIONS, a as Action, b as Actor, c as ApprovalDecision, d as ApprovalGate, e as ApprovalRequest, f as Approver, g as ApproverKind, h as AssistantEvent, i as AssistantPrincipal, D as DATA_NOTICE, j as DeniedReason, k as DoneReason, P as PrincipalError, l as PrincipalInput, m as Provenance, n as ProvenanceInput, Q as QueueApprovalGate, R as RunLimits, o as RunOptions, p as RunTotals, S as Scope, T as TOOL_RISKS, q as TRAINED_ALGORITHMIC_MEDIA, r as ToolContext, s as ToolDefinition, t as ToolRegistry, u as ToolRisk, v as actorOf, w as assertPrincipal, x as canonicalJson, y as createPrincipal, z as createProvenance, B as defineTool, C as digestCall, E as hasScope, F as parseScope, G as provenanceLabel, H as provenanceMarker, I as runAssistant, J as scopeCovers } from './loop-42giKLVa.js';
2
- import { S as Setup } from './chat-pBg4wRjQ.js';
3
- export { C as ChatConfig, a as ChatIo, b as ConfigError, c as ConnectMcpOptions, M as McpToolInfo, d as McpTools, e as McpTransportConfig, f as buildSetup, g as connectMcpTools, p as parseTarget, r as runChat } from './chat-pBg4wRjQ.js';
1
+ export { d as ACTIONS, e as Action, b as Actor, f as ApprovalDecision, g as ApprovalGate, a as ApprovalRequest, h as Approver, i as ApproverKind, A as AssistantEvent, j as AssistantPrincipal, k as DATA_NOTICE, D as DeniedReason, c as DoneReason, P as PrincipalError, l as PrincipalInput, m as Provenance, n as ProvenanceInput, Q as QueueApprovalGate, o as RunLimits, p as RunOptions, R as RunTotals, S as Scope, T as TOOL_RISKS, q as TRAINED_ALGORITHMIC_MEDIA, r as ToolContext, s as ToolDefinition, t as ToolRegistry, u as ToolRisk, v as actorOf, w as assertPrincipal, x as canonicalJson, y as createPrincipal, z as createProvenance, B as defineTool, C as digestCall, E as hasScope, F as parseScope, G as provenanceLabel, H as provenanceMarker, I as runAssistant, J as scopeCovers } from './loop-D4qWbRu6.js';
2
+ import { S as Setup } from './chat-CXbhePC5.js';
3
+ export { C as ChatConfig, a as ChatIo, b as ConfigError, c as ConnectMcpOptions, M as McpToolInfo, d as McpTools, e as McpTransportConfig, f as buildSetup, g as connectMcpTools, p as parseTarget, r as runChat } from './chat-CXbhePC5.js';
4
4
  import Anthropic from '@anthropic-ai/sdk';
5
5
  import { M as ModelProvider, P as Pricing, a as ModelRequest, b as ModelEvent } from './types-DGH8FS56.js';
6
6
  export { A as AnsweredBy, C as ContentBlock, J as JsonSchema, c as Message, d as ProviderError, S as StopReason, T as ToolSpec, U as Usage } from './types-DGH8FS56.js';
package/dist/index.js CHANGED
@@ -1,20 +1,22 @@
1
1
  import {
2
2
  createAssistantMcpServer,
3
3
  startAssistantMcp
4
- } from "./chunk-6UD2QQA4.js";
4
+ } from "./chunk-KNUEYFX2.js";
5
5
  import {
6
- AnthropicProvider,
7
6
  ConfigError,
8
- DEFAULT_ANTHROPIC_MODEL,
9
7
  FallbackProvider,
10
8
  OpenAICompatibleProvider,
11
- PRICING_CHECKED_ON,
12
9
  buildSetup,
13
10
  connectMcpTools,
14
11
  parseTarget,
15
12
  runChat
16
- } from "./chunk-BDXAUNMR.js";
13
+ } from "./chunk-MESH6X6N.js";
17
14
  import "./chunk-HYT732TN.js";
15
+ import {
16
+ AnthropicProvider,
17
+ DEFAULT_ANTHROPIC_MODEL,
18
+ PRICING_CHECKED_ON
19
+ } from "./chunk-FQD4OK2R.js";
18
20
  import {
19
21
  ACTIONS,
20
22
  DATA_NOTICE,
@@ -38,7 +40,7 @@ import {
38
40
  provenanceMarker,
39
41
  runAssistant,
40
42
  scopeCovers
41
- } from "./chunk-T4ZMDOB4.js";
43
+ } from "./chunk-C44QT474.js";
42
44
  export {
43
45
  ACTIONS,
44
46
  AnthropicProvider,
@@ -175,6 +175,11 @@ declare function defineTool<Input>(tool: ToolDefinition<Input>): ToolDefinition;
175
175
  declare class ToolRegistry {
176
176
  private readonly tools;
177
177
  private readonly ajv;
178
+ /**
179
+ * Ajv builds validators with `new Function`. Workers refuse that inside a request, though not while a module loads.
180
+ * So try Ajv first, and use the small checker when this host refuses code generation. A bad schema still throws.
181
+ */
182
+ private compile;
178
183
  register(tool: ToolDefinition): this;
179
184
  registerAll(tools: readonly ToolDefinition[]): this;
180
185
  get(name: string): ToolDefinition | undefined;
@@ -282,4 +287,4 @@ declare const DATA_NOTICE: string;
282
287
  */
283
288
  declare function runAssistant(options: RunOptions): AsyncGenerator<AssistantEvent>;
284
289
 
285
- export { ACTIONS as A, defineTool as B, digestCall as C, DATA_NOTICE as D, hasScope as E, parseScope as F, provenanceLabel as G, provenanceMarker as H, runAssistant as I, scopeCovers as J, PrincipalError as P, QueueApprovalGate as Q, type RunLimits as R, type Scope as S, TOOL_RISKS as T, type Action as a, type Actor as b, type ApprovalDecision as c, type ApprovalGate as d, type ApprovalRequest as e, type Approver as f, type ApproverKind as g, type AssistantEvent as h, type AssistantPrincipal as i, type DeniedReason as j, type DoneReason as k, type PrincipalInput as l, type Provenance as m, type ProvenanceInput as n, type RunOptions as o, type RunTotals as p, TRAINED_ALGORITHMIC_MEDIA as q, type ToolContext as r, type ToolDefinition as s, ToolRegistry as t, type ToolRisk as u, actorOf as v, assertPrincipal as w, canonicalJson as x, createPrincipal as y, createProvenance as z };
290
+ export { type AssistantEvent as A, defineTool as B, digestCall as C, type DeniedReason as D, hasScope as E, parseScope as F, provenanceLabel as G, provenanceMarker as H, runAssistant as I, scopeCovers as J, PrincipalError as P, QueueApprovalGate as Q, type RunTotals as R, type Scope as S, TOOL_RISKS as T, type ApprovalRequest as a, type Actor as b, type DoneReason as c, ACTIONS as d, type Action as e, type ApprovalDecision as f, type ApprovalGate as g, type Approver as h, type ApproverKind as i, type AssistantPrincipal as j, DATA_NOTICE as k, type PrincipalInput as l, type Provenance as m, type ProvenanceInput as n, type RunLimits as o, type RunOptions as p, TRAINED_ALGORITHMIC_MEDIA as q, type ToolContext as r, type ToolDefinition as s, ToolRegistry as t, type ToolRisk as u, actorOf as v, assertPrincipal as w, canonicalJson as x, createPrincipal as y, createProvenance as z };
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  createAssistantMcpServer,
3
3
  startAssistantMcp
4
- } from "./chunk-6UD2QQA4.js";
5
- import "./chunk-BDXAUNMR.js";
4
+ } from "./chunk-KNUEYFX2.js";
5
+ import "./chunk-MESH6X6N.js";
6
6
  import "./chunk-HYT732TN.js";
7
- import "./chunk-T4ZMDOB4.js";
7
+ import "./chunk-FQD4OK2R.js";
8
+ import "./chunk-C44QT474.js";
8
9
  export {
9
10
  createAssistantMcpServer,
10
11
  startAssistantMcp
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { h as AssistantEvent, u as ToolRisk } from './loop-42giKLVa.js';
2
+ import { A as AssistantEvent, u as ToolRisk } from './loop-D4qWbRu6.js';
3
3
  import { c as Message } from './types-DGH8FS56.js';
4
4
 
5
5
  type PanelItem = {
package/dist/worker.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Auth } from 'agentic-identity';
2
- import { t as ToolRegistry, R as RunLimits } from './loop-42giKLVa.js';
2
+ import { t as ToolRegistry, o as RunLimits } from './loop-D4qWbRu6.js';
3
3
  import { M as ModelProvider } from './types-DGH8FS56.js';
4
4
 
5
5
  interface AssistantApiOptions {
@@ -11,6 +11,8 @@ interface AssistantApiOptions {
11
11
  registry: ToolRegistry;
12
12
  system?: string;
13
13
  limits?: RunLimits;
14
+ /** Origins (such as `https://app.example.org`) that may send a write signed in with a session cookie. A write with a cookie from any other origin is a 403. A Bearer key needs no origin. */
15
+ origins?: readonly string[];
14
16
  /** Most chat turns one principal may run at once. Default 4. */
15
17
  maxConcurrentRuns?: number;
16
18
  }
@@ -26,6 +28,18 @@ declare function createAssistantApi(options: AssistantApiOptions): {
26
28
  fetch(request: Request): Promise<Response>;
27
29
  };
28
30
 
31
+ /** What one bearer token stands for. The caller is always a person: `kind: "human"`. */
32
+ interface StaticTokenEntry {
33
+ id: string;
34
+ scopes: string[];
35
+ }
36
+ /**
37
+ * An `agentic-identity` Auth that reads `Authorization: Bearer <token>` against a fixed map.
38
+ * It keeps only SHA-256 digests. It throws on a token shorter than 32 bytes or an entry of the wrong shape.
39
+ * An empty map is allowed: every request is then a 401.
40
+ */
41
+ declare function createStaticTokenAuth(tokens: Record<string, StaticTokenEntry>): Promise<Auth>;
42
+
29
43
  interface WorkerEnv {
30
44
  PACK_NAME?: string;
31
45
  }
@@ -33,9 +47,5 @@ interface WorkerEnv {
33
47
  declare function createWorker(options: AssistantApiOptions): {
34
48
  fetch(request: Request, env?: WorkerEnv): Promise<Response>;
35
49
  };
36
- /** The sample Worker has no auth, tools, or model, so it serves `/health` only. Use `createWorker` for the API. */
37
- declare const _default: {
38
- fetch(request: Request, env: WorkerEnv): Promise<Response>;
39
- };
40
50
 
41
- export { APPROVER_SCOPE, type AssistantApiOptions, type WorkerEnv, createAssistantApi, createWorker, _default as default };
51
+ export { APPROVER_SCOPE, type AssistantApiOptions, type StaticTokenEntry, type WorkerEnv, createAssistantApi, createStaticTokenAuth, createWorker };
package/dist/worker.js CHANGED
@@ -1,276 +1,13 @@
1
1
  import {
2
- VERSION,
3
- createPrincipal,
4
- hasScope,
5
- parseScope,
6
- runAssistant
7
- } from "./chunk-T4ZMDOB4.js";
8
-
9
- // src/api.ts
10
- var MAX_BODY_BYTES = 65536;
11
- var MAX_MESSAGE_CHARS = 1e4;
12
- var MAX_REASON_CHARS = 500;
13
- var DEFAULT_MAX_CONCURRENT_RUNS = 4;
14
- var KINDS = ["human", "agent_of_human", "agent_of_agent", "agent", "user_token"];
15
- var APPROVER_SCOPE = "assistant:admin";
16
- var json = (body, status = 200, headers = {}) => Response.json(body, { status, headers: { "cache-control": "no-store", ...headers } });
17
- var fail = (status, error, headers = {}) => json({ error }, status, headers);
18
- function toCaller(context) {
19
- if (typeof context !== "object" || context === null) return void 0;
20
- const { principalId, kind, chain, scopes } = context;
21
- if (typeof principalId !== "string" || principalId === "" || !KINDS.includes(kind)) return void 0;
22
- if (!Array.isArray(chain) || chain.length === 0 || chain[chain.length - 1]?.id !== principalId) return void 0;
23
- if (chain[chain.length - 1]?.kind !== kind || kind === "human" && chain.length !== 1) return void 0;
24
- const ids = chain.map((step) => step?.id);
25
- if (ids.some((id) => typeof id !== "string" || id === "")) return void 0;
26
- const rootId = ids[0];
27
- if (!Array.isArray(scopes)) return void 0;
28
- try {
29
- for (const scope of scopes) parseScope(scope);
30
- } catch {
31
- return void 0;
32
- }
33
- return { id: principalId, kind, rootId, chain: ids, scopes: [...scopes] };
34
- }
35
- var assistantId = (caller) => `assistant:${caller.id}`;
36
- async function readJson(request) {
37
- if (Number(request.headers.get("content-length") ?? 0) > MAX_BODY_BYTES) return void 0;
38
- let text = "";
39
- if (request.body !== null) {
40
- const reader = request.body.getReader();
41
- const decoder = new TextDecoder();
42
- let bytes = 0;
43
- for (; ; ) {
44
- const chunk = await reader.read();
45
- if (chunk.done) break;
46
- bytes += chunk.value.byteLength;
47
- if (bytes > MAX_BODY_BYTES) {
48
- await reader.cancel();
49
- return void 0;
50
- }
51
- text += decoder.decode(chunk.value, { stream: true });
52
- }
53
- text += decoder.decode();
54
- }
55
- try {
56
- const body = JSON.parse(text);
57
- return typeof body === "object" && body !== null && !Array.isArray(body) ? body : void 0;
58
- } catch {
59
- return void 0;
60
- }
61
- }
62
- function sseChunk(event) {
63
- return new TextEncoder().encode(`event: ${event.type}
64
- data: ${JSON.stringify(event)}
65
-
66
- `);
67
- }
68
- function createAssistantApi(options) {
69
- const pending = /* @__PURE__ */ new Map();
70
- const running = /* @__PURE__ */ new Map();
71
- const maxRuns = options.maxConcurrentRuns ?? DEFAULT_MAX_CONCURRENT_RUNS;
72
- function whyNotDecide(caller, entry) {
73
- const { proposer } = entry;
74
- if (caller.id === assistantId(proposer)) return "an agent cannot decide on its own action";
75
- if (proposer.kind !== "human") {
76
- if (caller.chain.includes(proposer.id) || caller.kind !== "human" && proposer.chain.includes(caller.id)) {
77
- return "an agent cannot decide on its own action, or on one from an agent it made or that made it";
78
- }
79
- }
80
- if (caller.kind !== "human" && !hasScope(caller.scopes, APPROVER_SCOPE)) {
81
- return `an agent needs the ${APPROVER_SCOPE} scope to decide`;
82
- }
83
- const tool = options.registry.get(entry.request.tool);
84
- if (tool === void 0 || !hasScope(caller.scopes, tool.scope)) return "the approver does not hold the scope of this tool";
85
- return void 0;
86
- }
87
- const gateFor = (proposer) => ({
88
- request(request, signal) {
89
- return new Promise((resolve, reject) => {
90
- if (signal.aborted) return reject(new Error("the request was cancelled"));
91
- pending.set(request.id, { request, proposer, resolve });
92
- signal.addEventListener(
93
- "abort",
94
- () => {
95
- pending.delete(request.id);
96
- reject(new Error("the request was cancelled"));
97
- },
98
- { once: true }
99
- );
100
- });
101
- }
102
- });
103
- function chat(request, caller, message) {
104
- const abort = new AbortController();
105
- running.set(caller.id, (running.get(caller.id) ?? 0) + 1);
106
- let released = false;
107
- const release = () => {
108
- if (released) return;
109
- released = true;
110
- const left = (running.get(caller.id) ?? 1) - 1;
111
- if (left <= 0) running.delete(caller.id);
112
- else running.set(caller.id, left);
113
- };
114
- request.signal.addEventListener(
115
- "abort",
116
- () => {
117
- abort.abort();
118
- release();
119
- },
120
- { once: true }
121
- );
122
- let events;
123
- try {
124
- events = runAssistant({
125
- provider: options.provider(),
126
- registry: options.registry,
127
- // The assistant holds the caller's scopes and no more.
128
- principal: createPrincipal({
129
- id: assistantId(caller),
130
- kind: "agent_of_human",
131
- parentId: caller.id,
132
- scopes: caller.scopes,
133
- // Each check asks the Auth again. A key revoked during a run, or one with other scopes, stops it.
134
- isRevoked: async () => {
135
- const now = toCaller(await options.auth.authenticate(request));
136
- return now?.id !== caller.id || [...now.scopes].sort().join(" ") !== [...caller.scopes].sort().join(" ");
137
- }
138
- }),
139
- approvals: gateFor(caller),
140
- messages: [{ role: "user", content: [{ type: "text", text: message }] }],
141
- system: options.system,
142
- limits: options.limits,
143
- signal: abort.signal
144
- });
145
- } catch {
146
- release();
147
- return fail(500, "the assistant could not start");
148
- }
149
- const body = new ReadableStream({
150
- async pull(controller) {
151
- try {
152
- const next = await events.next();
153
- if (next.done) {
154
- release();
155
- controller.close();
156
- } else controller.enqueue(sseChunk(next.value));
157
- } catch {
158
- release();
159
- controller.enqueue(sseChunk({ type: "error", error: "the run failed" }));
160
- controller.close();
161
- }
162
- },
163
- cancel() {
164
- abort.abort();
165
- release();
166
- void events.return(void 0).catch(() => void 0);
167
- }
168
- });
169
- return new Response(body, {
170
- headers: { "content-type": "text/event-stream", "cache-control": "no-store", "x-accel-buffering": "no" }
171
- });
172
- }
173
- async function decide(request, caller, id, approve) {
174
- const body = await readJson(request);
175
- if (body === void 0) return fail(400, "the body must be a JSON object");
176
- const entry = pending.get(id);
177
- if (entry === void 0 || entry.proposer.rootId !== caller.rootId) return fail(404, "no pending approval with this id");
178
- const why = whyNotDecide(caller, entry);
179
- if (why !== void 0) return fail(403, why);
180
- let decision;
181
- if (approve) {
182
- if (body.digest !== entry.request.digest) return fail(409, "digest does not match the pending call");
183
- decision = { requestId: id, approved: true, digest: entry.request.digest, decidedBy: caller.id, approverKind: caller.kind === "human" ? "human" : "agent" };
184
- } else {
185
- const reason = typeof body.reason === "string" ? body.reason.slice(0, MAX_REASON_CHARS) : void 0;
186
- decision = { requestId: id, approved: false, decidedBy: caller.id, reason };
187
- }
188
- pending.delete(id);
189
- entry.resolve(decision);
190
- return json({ requestId: id, approved: approve, decidedBy: caller.id, machineMade: caller.kind !== "human" });
191
- }
192
- return {
193
- async fetch(request) {
194
- const url = new URL(request.url);
195
- const match = /^\/api\/v1\/(tools|chat|approvals)(?:\/([^/]+)\/(approve|deny))?\/?$/.exec(url.pathname);
196
- if (match === null) return fail(404, "not found");
197
- const [, route, id, verb] = match;
198
- const allowed = route === "chat" || verb !== void 0 ? "POST" : "GET";
199
- if (request.method !== allowed) return fail(405, "method not allowed", { allow: allowed });
200
- if (route !== "approvals" && id !== void 0) return fail(404, "not found");
201
- let caller;
202
- try {
203
- caller = toCaller(await options.auth.authenticate(request));
204
- } catch {
205
- caller = void 0;
206
- }
207
- if (caller === void 0) return fail(401, "sign in with a valid key", { "www-authenticate": "Bearer" });
208
- if (route === "tools") {
209
- const tools = options.registry.list().filter((tool) => hasScope(caller.scopes, tool.scope)).map(({ name, description, scope, risk, inputSchema }) => ({ name, description, scope, risk, inputSchema }));
210
- return json({ tools });
211
- }
212
- if (route === "approvals" && verb === void 0) {
213
- const list = [...pending.values()].filter((entry) => entry.proposer.rootId === caller.rootId).filter((entry) => {
214
- const tool = options.registry.get(entry.request.tool);
215
- return tool !== void 0 && hasScope(caller.scopes, tool.scope);
216
- }).map((entry) => ({
217
- id: entry.request.id,
218
- tool: entry.request.tool,
219
- risk: entry.request.risk,
220
- input: entry.request.input,
221
- digest: entry.request.digest,
222
- proposedBy: entry.request.actor,
223
- requestedBy: entry.proposer.id,
224
- canDecide: whyNotDecide(caller, entry) === void 0
225
- }));
226
- return json({ approvals: list });
227
- }
228
- if (route === "approvals" && id !== void 0) {
229
- let requestId;
230
- try {
231
- requestId = decodeURIComponent(id);
232
- } catch {
233
- return fail(404, "no pending approval with this id");
234
- }
235
- return decide(request, caller, requestId, verb === "approve");
236
- }
237
- const body = await readJson(request);
238
- const message = body?.message;
239
- if (typeof message !== "string" || message.trim() === "" || message.length > MAX_MESSAGE_CHARS) {
240
- return fail(400, `message must be a text of 1 to ${MAX_MESSAGE_CHARS} characters`);
241
- }
242
- if ((running.get(caller.id) ?? 0) >= maxRuns) return fail(429, "too many chat turns at once", { "retry-after": "5" });
243
- return chat(request, caller, message);
244
- }
245
- };
246
- }
247
-
248
- // src/worker.ts
249
- var health = (env) => Response.json({ ok: true, pack: env.PACK_NAME ?? "agentic-assistant", version: VERSION });
250
- function createWorker(options) {
251
- const api = createAssistantApi(options);
252
- return {
253
- async fetch(request, env = {}) {
254
- const url = new URL(request.url);
255
- if (request.method === "GET" && url.pathname === "/health") return health(env);
256
- if (url.pathname.startsWith("/api/v1/")) return api.fetch(request);
257
- return Response.json({ error: "not found" }, { status: 404 });
258
- }
259
- };
260
- }
261
- var worker_default = {
262
- async fetch(request, env) {
263
- const url = new URL(request.url);
264
- if (request.method === "GET" && url.pathname === "/health") return health(env);
265
- if (url.pathname.startsWith("/api/v1/")) {
266
- return Response.json({ error: "the API is not set up: build the Worker with createWorker" }, { status: 503 });
267
- }
268
- return Response.json({ error: "not found" }, { status: 404 });
269
- }
270
- };
2
+ APPROVER_SCOPE,
3
+ createAssistantApi,
4
+ createStaticTokenAuth,
5
+ createWorker
6
+ } from "./chunk-YMEM57LX.js";
7
+ import "./chunk-C44QT474.js";
271
8
  export {
272
9
  APPROVER_SCOPE,
273
10
  createAssistantApi,
274
- createWorker,
275
- worker_default as default
11
+ createStaticTokenAuth,
12
+ createWorker
276
13
  };
package/llms.txt CHANGED
@@ -6,7 +6,8 @@ Keywords: assistant,ai,agent,tools,mcp,sdk,side-panel,approvals,eu,model-port.
6
6
 
7
7
  - SDK: import { runAssistant, ToolRegistry, createPrincipal } from "agentic-assistant".
8
8
  - CLI: agentic-assistant chat --tools <mcp-url> --scope <s> --tool-scope <s> "message". MCP server: agentic-assistant mcp.
9
- - HTTP API: import { createWorker } from "agentic-assistant/worker". Routes under /api/v1: GET /tools, POST /chat (SSE), GET /approvals, POST /approvals/:id/approve|deny. Sign-in with agentic-identity principals.
9
+ - HTTP API: import { createWorker } from "agentic-assistant/worker". Routes under /api/v1: GET /tools, POST /chat (SSE), GET /approvals, POST /approvals/:id/approve|deny. Sign-in with agentic-identity principals. Chat responses carry x-ai-generated: true. Demo: https://agentic-assistant-demo.hello-fd1.workers.dev (needs a bearer key; no model key set, so chat answers 503 not_configured).
10
+ - HTTP client: import { createClient, AssistantApiError } from "agentic-assistant/client"; createClient({ baseUrl, token, fetch? }) gives health, tools, chat (async generator of events), approvals, approve(id, digest), deny(id, reason?). Typed errors with code and status.
10
11
  - AI marking: events carry `provenance`; use provenanceLabel and provenanceMarker (JSON-LD, IPTC trainedAlgorithmicMedia). Also in HTTP, CLI --json and MCP results.
11
12
  - Panel: import { AssistantPanel } from "agentic-assistant/react"; import "agentic-assistant/react.css".
12
13
  - Test provider: import { ScriptedProvider } from "agentic-assistant/testing".
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-assistant",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "An AI assistant that acts inside your apps with the user's own permissions.",
5
5
  "keywords": [
6
6
  "assistant",
@@ -23,7 +23,7 @@
23
23
  "type": "module",
24
24
  "private": false,
25
25
  "engines": {
26
- "node": ">=18"
26
+ "node": ">=22"
27
27
  },
28
28
  "files": [
29
29
  "dist",
@@ -45,6 +45,10 @@
45
45
  "types": "./dist/worker.d.ts",
46
46
  "import": "./dist/worker.js"
47
47
  },
48
+ "./client": {
49
+ "types": "./dist/client.d.ts",
50
+ "import": "./dist/client.js"
51
+ },
48
52
  "./testing": {
49
53
  "types": "./dist/testing.d.ts",
50
54
  "import": "./dist/testing.js"
@@ -63,13 +67,14 @@
63
67
  "typecheck": "tsc --noEmit",
64
68
  "test": "vitest run",
65
69
  "clean": "rm -rf dist",
70
+ "deploy:demo": "npm run build && WRANGLER_SEND_METRICS=false wrangler deploy --config wrangler.demo.jsonc",
66
71
  "prepack": "npm run build",
67
72
  "ci:local": "npm run build && npm run typecheck && npm test"
68
73
  },
69
74
  "dependencies": {
70
75
  "@anthropic-ai/sdk": "^1.3.0",
71
76
  "@modelcontextprotocol/sdk": "^1.32.2",
72
- "agentic-identity": "^0.3.0",
77
+ "agentic-identity": "^0.6.0",
73
78
  "ajv": "^8.20.0"
74
79
  },
75
80
  "devDependencies": {
@@ -84,7 +89,8 @@
84
89
  "react-dom": "^19.3.1",
85
90
  "tsup": "^8.0.0",
86
91
  "typescript": "^5.6.0",
87
- "vitest": "^3.0.0"
92
+ "vitest": "^3.0.0",
93
+ "wrangler": "^4.155.0"
88
94
  },
89
95
  "peerDependencies": {
90
96
  "react": ">=18"