agentic-assistant 0.4.0 → 0.5.1

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.
@@ -4,7 +4,7 @@ import {
4
4
  hasScope,
5
5
  parseScope,
6
6
  runAssistant
7
- } from "./chunk-C44QT474.js";
7
+ } from "./chunk-JNZPVHNN.js";
8
8
 
9
9
  // src/api.ts
10
10
  import { PRINCIPAL_KINDS } from "agentic-identity";
@@ -14,8 +14,8 @@ var MAX_MESSAGE_CHARS = 1e4;
14
14
  var MAX_REASON_CHARS = 500;
15
15
  var DEFAULT_MAX_CONCURRENT_RUNS = 4;
16
16
  var APPROVER_SCOPE = "assistant:admin";
17
- async function callerOf(request, auth, origins) {
18
- const found = await requirePrincipal(request, auth, { origins });
17
+ async function callerOf(request, auth, origins, scope) {
18
+ const found = await requirePrincipal(request, auth, { origins, scope });
19
19
  if (found instanceof Response) return found.status === 401 ? fail(401, "sign in with a valid key", { "www-authenticate": "Bearer" }) : found;
20
20
  return toCaller(found) ?? fail(401, "sign in with a valid key", { "www-authenticate": "Bearer" });
21
21
  }
@@ -205,7 +205,7 @@ function createAssistantApi(options) {
205
205
  const allowed = route === "chat" || verb !== void 0 ? "POST" : "GET";
206
206
  if (request.method !== allowed) return fail(405, "method not allowed", { allow: allowed });
207
207
  if (route !== "approvals" && id !== void 0) return fail(404, "not found");
208
- const caller = await callerOf(request, options.auth, options.origins);
208
+ const caller = await callerOf(request, options.auth, options.origins, options.routeScopes?.[route]);
209
209
  if (caller instanceof Response) return caller;
210
210
  if (route === "tools") {
211
211
  const tools = options.registry.list().filter((tool) => hasScope(caller.scopes, tool.scope)).map(({ name, description, scope, risk, inputSchema }) => ({ name, description, scope, risk, inputSchema }));
@@ -248,6 +248,7 @@ function createAssistantApi(options) {
248
248
  }
249
249
 
250
250
  // src/static-auth.ts
251
+ import { createMemoryStore, createStoreAuth } from "agentic-identity";
251
252
  var MIN_TOKEN_BYTES = 32;
252
253
  var encoder = new TextEncoder();
253
254
  async function digest(token) {
@@ -255,7 +256,8 @@ async function digest(token) {
255
256
  return [...new Uint8Array(hash)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
256
257
  }
257
258
  async function createStaticTokenAuth(tokens) {
258
- const byDigest = /* @__PURE__ */ new Map();
259
+ const store = createMemoryStore();
260
+ const ids = /* @__PURE__ */ new Set();
259
261
  for (const [token, entry] of Object.entries(tokens)) {
260
262
  if (encoder.encode(token).length < MIN_TOKEN_BYTES) throw new Error(`tokens must hold at least ${MIN_TOKEN_BYTES} bytes`);
261
263
  if (/\s/.test(token)) throw new Error("a token may not hold white space");
@@ -267,20 +269,12 @@ async function createStaticTokenAuth(tokens) {
267
269
  throw new Error("each token needs an id and a list of scopes");
268
270
  }
269
271
  for (const scope of entry.scopes) parseScope(scope);
270
- byDigest.set(await digest(token), {
271
- principalId: entry.id,
272
- kind: "human",
273
- chain: [{ id: entry.id, kind: "human" }],
274
- scopes: [...entry.scopes]
275
- });
272
+ if (ids.has(entry.id)) throw new Error("each token needs its own id");
273
+ ids.add(entry.id);
274
+ await store.put("principals", entry.id, { id: entry.id, kind: "human", scopes: [...entry.scopes] });
275
+ await store.put("credentials", await digest(token), { principalId: entry.id, kind: "api_key" });
276
276
  }
277
- return {
278
- async authenticate(request) {
279
- const match = /^Bearer ([^\s]+)$/.exec(request.headers.get("authorization") ?? "");
280
- const context = match ? byDigest.get(await digest(match[1])) : void 0;
281
- return context ? { ...context, chain: context.chain.map((step) => ({ ...step })), scopes: [...context.scopes] } : null;
282
- }
283
- };
277
+ return createStoreAuth(store);
284
278
  }
285
279
 
286
280
  // src/worker.ts
package/dist/cli.d.ts CHANGED
@@ -1,14 +1,35 @@
1
1
  #!/usr/bin/env node
2
- import { C as ChatConfig } from './chat-CXbhePC5.js';
3
- import './loop-D4qWbRu6.js';
2
+ import { C as ChatConfig } from './chat-CGITyrxg.js';
3
+ import './loop-B6aowbyz.js';
4
4
  import './types-DGH8FS56.js';
5
5
 
6
+ interface RemoteConfig {
7
+ api: string;
8
+ apiTokenEnv: string;
9
+ }
10
+ type RemoteCommand = {
11
+ name: "tools";
12
+ } | {
13
+ name: "approvals";
14
+ } | {
15
+ name: "approve";
16
+ id: string;
17
+ digest: string;
18
+ } | {
19
+ name: "deny";
20
+ id: string;
21
+ reason?: string;
22
+ };
23
+
6
24
  declare class UsageError extends Error {
7
25
  constructor(message: string);
8
26
  }
9
27
  interface Parsed {
10
- command: "chat" | "mcp" | "help" | "version";
28
+ command: "chat" | "mcp" | "help" | "version" | "remote";
11
29
  config?: ChatConfig;
30
+ remote?: RemoteConfig;
31
+ allowApprove?: boolean;
32
+ remoteCommand?: RemoteCommand;
12
33
  message?: string;
13
34
  json: boolean;
14
35
  }
package/dist/cli.js CHANGED
@@ -2,17 +2,21 @@
2
2
  import {
3
3
  ConfigError,
4
4
  buildSetup,
5
+ clientFor,
6
+ describeFailure,
5
7
  readTextFile,
6
- runChat
7
- } from "./chunk-MESH6X6N.js";
8
+ runChat,
9
+ runRemote
10
+ } from "./chunk-5BZENFYL.js";
8
11
  import "./chunk-HYT732TN.js";
9
- import "./chunk-FQD4OK2R.js";
12
+ import "./chunk-2RRM6OLC.js";
10
13
  import {
11
14
  TOOL_RISKS,
12
15
  VERSION,
13
16
  printable,
14
17
  provenanceLabel
15
- } from "./chunk-C44QT474.js";
18
+ } from "./chunk-JNZPVHNN.js";
19
+ import "./chunk-QTIEDY3V.js";
16
20
 
17
21
  // src/cli.ts
18
22
  import { realpathSync } from "fs";
@@ -30,9 +34,22 @@ An AI assistant that acts inside your apps with the user's own permissions.
30
34
 
31
35
  Usage:
32
36
  agentic-assistant chat [flags] [message] Send a message. Read stdin when there is no message.
33
- agentic-assistant mcp [flags] Start an MCP server with one tool, assistant_chat.
37
+ agentic-assistant mcp [flags] Start an MCP server with the tool assistant_chat. With --api it adds three more.
38
+ agentic-assistant tools [flags] List the tools of a running assistant API, with scope and risk.
39
+ agentic-assistant approvals [flags] List the calls that wait for a person.
40
+ agentic-assistant approve <id> --digest <d> [flags] Approve the exact call. Copy the digest from "approvals".
41
+ agentic-assistant deny <id> [--reason <text>] [flags] Deny the call. A denial is final.
42
+
43
+ Flags for tools, approvals, approve, and deny:
44
+ --api <url> The assistant API origin, like https://assistant.example.com. https, or localhost. Required.
45
+ --api-token-env <NAME> Read the bearer token from this environment variable. Required. Tokens never go in arguments.
46
+ --digest <digest> approve only: the digest of the pending call.
47
+ --reason <text> deny only: why.
48
+ --json Print the result as JSON on stdout.
34
49
 
35
50
  Flags for chat and mcp:
51
+ --api <url> --api-token-env <NAME> mcp only: also serve assistant_tools, assistant_approvals, assistant_deny.
52
+ --allow-approve mcp only, with --api: also serve assistant_approve. Use an agent token (it needs assistant:admin).
36
53
  --tools <target> An https MCP URL, or a command that starts an MCP server on stdio. Required.
37
54
  --tools-header-env <h=E> Send header h with the value of environment variable E (http only). Repeat it.
38
55
  --scope <scope> A scope the assistant may hold, like notes:read. Repeat it. Required.
@@ -82,6 +99,11 @@ var OPTIONS = {
82
99
  "max-tokens": { type: "string" },
83
100
  "max-cost": { type: "string" },
84
101
  "max-tool-calls": { type: "string" },
102
+ api: { type: "string" },
103
+ "api-token-env": { type: "string" },
104
+ "allow-approve": { type: "boolean" },
105
+ digest: { type: "string" },
106
+ reason: { type: "string" },
85
107
  json: { type: "boolean" },
86
108
  message: { type: "string" },
87
109
  help: { type: "boolean", short: "h" },
@@ -106,8 +128,27 @@ function parseCli(argv) {
106
128
  if (values.version) return { command: "version", json: false };
107
129
  const [command, ...rest] = positionals;
108
130
  if (values.help || command === void 0) return { command: "help", json: false };
109
- if (command !== "chat" && command !== "mcp") throw new UsageError(`unknown command "${command}"`);
110
- if (!values.tools) throw new UsageError("--tools is required");
131
+ const remoteNames = ["tools", "approvals", "approve", "deny"];
132
+ if (command !== "chat" && command !== "mcp" && !remoteNames.includes(command)) throw new UsageError(`unknown command "${command}"`);
133
+ if (values.api === "" || values["api-token-env"] === "") throw new UsageError("--api needs a URL, and --api-token-env a variable name");
134
+ if (values.api === void 0 !== (values["api-token-env"] === void 0)) throw new UsageError("--api and --api-token-env go together");
135
+ const remote = values.api !== void 0 && values["api-token-env"] !== void 0 ? { api: values.api, apiTokenEnv: values["api-token-env"] } : void 0;
136
+ if (command !== "mcp" && command !== "chat" && remote === void 0) throw new UsageError("--api and --api-token-env are required");
137
+ if (command === "chat" && remote !== void 0) throw new UsageError("--api works with mcp and the approval commands, not chat");
138
+ const wantsId = command === "approve" || command === "deny";
139
+ if (remoteNames.includes(command) && rest.length !== (wantsId ? 1 : 0)) throw new UsageError(wantsId ? `${command} needs one approval id` : `${command} takes no arguments`);
140
+ if (values["allow-approve"] && (command !== "mcp" || remote === void 0)) throw new UsageError("--allow-approve works with mcp and --api only");
141
+ if (command !== "approve" && values.digest !== void 0) throw new UsageError("--digest works with approve only");
142
+ if (command !== "deny" && values.reason !== void 0) throw new UsageError("--reason works with deny only");
143
+ if (remoteNames.includes(command)) {
144
+ const chatOnly = Object.keys(values).filter((key) => !["api", "api-token-env", "digest", "reason", "json", "help", "version"].includes(key));
145
+ if (chatOnly.length > 0) throw new UsageError(`--${chatOnly[0]} does not work with ${command}`);
146
+ const id = rest[0];
147
+ if (command === "approve" && !values.digest) throw new UsageError("approve needs --digest <digest>");
148
+ const remoteCommand = command === "approve" ? { name: "approve", id, digest: values.digest } : command === "deny" ? { name: "deny", id, ...values.reason !== void 0 ? { reason: values.reason } : {} } : { name: command };
149
+ return { command: "remote", remote, remoteCommand, json: values.json ?? false };
150
+ }
151
+ if (!values.tools) throw new UsageError(`--tools is required for ${command}: an https MCP URL, or a command that starts an MCP server on stdio`);
111
152
  if (!values["tool-scope"]) throw new UsageError("--tool-scope is required");
112
153
  const risk = values["tool-risk"] ?? "risky";
113
154
  if (!TOOL_RISKS.includes(risk)) throw new UsageError(`--tool-risk must be one of ${TOOL_RISKS.join(", ")}`);
@@ -159,7 +200,7 @@ function parseCli(argv) {
159
200
  preApproved,
160
201
  approve
161
202
  };
162
- return { command, config, message: values.message ?? (rest.length > 0 ? rest.join(" ") : void 0), json: values.json ?? false };
203
+ return { command, config, remote, allowApprove: values["allow-approve"] ?? false, message: values.message ?? (rest.length > 0 ? rest.join(" ") : void 0), json: values.json ?? false };
163
204
  }
164
205
  function defaultIo() {
165
206
  return {
@@ -264,16 +305,30 @@ async function runCli(argv, io = defaultIo()) {
264
305
  `);
265
306
  return 0;
266
307
  }
308
+ if (parsed.command === "remote") {
309
+ try {
310
+ const client = clientFor(parsed.remote, io.env);
311
+ const { data, text } = await runRemote(client, parsed.remoteCommand);
312
+ io.out(parsed.json ? `${JSON.stringify(data)}
313
+ ` : `${text}
314
+ `);
315
+ return 0;
316
+ } catch (error) {
317
+ io.err(error instanceof ConfigError ? `${error.name}: ${oneLine(error.message, 500)}` : describeFailure(error));
318
+ return error instanceof ConfigError ? 2 : 1;
319
+ }
320
+ }
267
321
  const config = parsed.config;
268
322
  const chatIo = { env: io.env, readFile: io.readFile, ...config.approve === "ask" && io.isTTY ? { ask: io.ask } : {} };
269
323
  let setup;
270
324
  try {
325
+ const remote = parsed.command === "mcp" && parsed.remote !== void 0 ? clientFor(parsed.remote, io.env) : void 0;
271
326
  setup = await buildSetup(config, chatIo);
272
327
  for (const skip of setup.skipped.slice(0, MAX_SKIPPED_LINES)) io.err(`skipped tool ${oneLine(skip.name, 100)}: ${oneLine(skip.reason, 200)}`);
273
328
  if (setup.skipped.length > MAX_SKIPPED_LINES) io.err(`skipped ${setup.skipped.length - MAX_SKIPPED_LINES} more tools`);
274
329
  if (parsed.command === "mcp") {
275
- const { startAssistantMcp } = await import("./mcp-server-LXGWKEHG.js");
276
- await startAssistantMcp(setup);
330
+ const { startAssistantMcp } = await import("./mcp-server-7R5WCSID.js");
331
+ await startAssistantMcp(setup, remote, { approve: parsed.allowApprove ?? false });
277
332
  return 0;
278
333
  }
279
334
  const text = parsed.message ?? (io.isTTY ? void 0 : (await io.readStdin()).trim());
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as AssistantEvent, a as ApprovalRequest, b as Actor } from './loop-D4qWbRu6.js';
2
- export { D as DeniedReason, c as DoneReason, R as RunTotals } from './loop-D4qWbRu6.js';
1
+ import { h as AssistantEvent, e as ApprovalRequest, b as Actor } from './loop-B6aowbyz.js';
2
+ export { j as DeniedReason, k as DoneReason, p as RunTotals } from './loop-B6aowbyz.js';
3
3
  import './types-DGH8FS56.js';
4
4
 
5
5
  /** The error code of a failed call. The client derives it from the HTTP status and the error body. */
package/dist/client.js CHANGED
@@ -1,194 +1,7 @@
1
- // src/client.ts
2
- var STATUS_CODES = {
3
- 400: "INVALID_INPUT",
4
- 401: "UNAUTHENTICATED",
5
- 403: "FORBIDDEN",
6
- 404: "NOT_FOUND",
7
- 405: "METHOD_NOT_ALLOWED",
8
- 409: "CONFLICT",
9
- 429: "RATE_LIMITED"
10
- };
11
- var AssistantApiError = class extends Error {
12
- code;
13
- status;
14
- /** Seconds to wait before a new try, from `retry-after` of a 429 reply. */
15
- retryAfter;
16
- constructor(code, status, message, retryAfter) {
17
- super(message);
18
- this.name = "AssistantApiError";
19
- this.code = code;
20
- this.status = status;
21
- this.retryAfter = retryAfter;
22
- }
23
- };
24
- var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
25
- var isString = (v) => typeof v === "string";
26
- var RISKS = ["safe", "risky", "irreversible"];
27
- var isTool = (v) => isObject(v) && isString(v.name) && isString(v.description) && isString(v.scope) && RISKS.includes(v.risk) && "inputSchema" in v;
28
- var isPending = (v) => isObject(v) && isString(v.id) && isString(v.tool) && RISKS.includes(v.risk) && isString(v.digest) && isObject(v.input) && isObject(v.proposedBy) && isString(v.requestedBy) && typeof v.canDecide === "boolean";
29
- var SHAPES = {
30
- health: (v) => isObject(v) && typeof v.ok === "boolean" && isString(v.pack) && isString(v.version),
31
- tools: (v) => isObject(v) && Array.isArray(v.tools) && v.tools.every(isTool),
32
- approvals: (v) => isObject(v) && Array.isArray(v.approvals) && v.approvals.every(isPending),
33
- decision: (v) => isObject(v) && isString(v.requestId) && typeof v.approved === "boolean" && isString(v.decidedBy) && typeof v.machineMade === "boolean"
34
- };
35
- var MAX_EVENT_CHARS = 4194304;
36
- var EVENT_TYPES = ["run_start", "turn_start", "text_delta", "tool_call", "approval_request", "approval_decision", "tool_result", "turn_end", "done", "error"];
37
- var isEvent = (v) => {
38
- if (!isObject(v) || typeof v.type !== "string" || !EVENT_TYPES.includes(v.type)) return false;
39
- switch (v.type) {
40
- case "done":
41
- return isString(v.reason) && isObject(v.totals);
42
- case "error":
43
- return isString(v.error);
44
- case "text_delta":
45
- return isString(v.text);
46
- case "tool_result":
47
- return isString(v.id) && isString(v.name) && isString(v.content) && typeof v.isError === "boolean";
48
- case "tool_call":
49
- return isString(v.id) && isString(v.name);
50
- case "approval_request":
51
- return isObject(v.request) && isString(v.request.id) && isString(v.request.digest);
52
- case "approval_decision":
53
- return isString(v.requestId) && typeof v.approved === "boolean";
54
- default:
55
- return true;
56
- }
57
- };
58
- function createClient(options) {
59
- if (typeof options.baseUrl !== "string" || options.baseUrl === "") throw new TypeError("createClient needs a baseUrl");
60
- if (typeof options.token !== "string" || options.token === "") throw new TypeError("createClient needs a token");
61
- let origin;
62
- try {
63
- origin = new URL(options.baseUrl);
64
- } catch {
65
- throw new TypeError("baseUrl must be an absolute URL");
66
- }
67
- const local = ["localhost", "127.0.0.1", "[::1]"].includes(origin.hostname);
68
- if (origin.protocol !== "https:" && !(origin.protocol === "http:" && local)) {
69
- throw new TypeError("baseUrl must be an https URL, or an http URL on localhost, 127.0.0.1, or [::1]");
70
- }
71
- if (origin.search !== "" || origin.hash !== "" || origin.username !== "" || origin.password !== "") {
72
- throw new TypeError("baseUrl must have no query, hash, or credentials");
73
- }
74
- const prefix = origin.pathname.replace(/\/+$/, "");
75
- const root = `${origin.origin}${prefix}/api/v1`;
76
- const token = options.token;
77
- const send = (input, init) => {
78
- const run = options.fetch ?? globalThis.fetch;
79
- return run(input, init);
80
- };
81
- const seg = (value) => {
82
- if (typeof value !== "string" || /^(?:\.|%2e){0,2}$/i.test(value)) throw new TypeError("an id must be a non-empty string that is not . or ..");
83
- return encodeURIComponent(value);
84
- };
85
- async function request(url, method, body, callOptions, authed = true) {
86
- const headers = authed ? { authorization: `Bearer ${token}` } : {};
87
- if (body !== void 0) headers["content-type"] = "application/json";
88
- return send(url, {
89
- method,
90
- headers,
91
- body: body === void 0 ? void 0 : JSON.stringify(body),
92
- signal: callOptions?.signal,
93
- // Never follow a redirect: it would send the token to another host.
94
- redirect: "manual"
95
- });
96
- }
97
- async function failure(response) {
98
- let parsed;
99
- try {
100
- parsed = JSON.parse(await response.text());
101
- } catch {
102
- parsed = void 0;
103
- }
104
- const error = isObject(parsed) ? parsed.error : void 0;
105
- const message = isObject(parsed) && typeof parsed.message === "string" ? parsed.message : typeof error === "string" ? error : `HTTP ${response.status}`;
106
- let code = STATUS_CODES[response.status] ?? (response.status >= 500 ? "INTERNAL" : "UNKNOWN");
107
- if (response.status === 503 && error === "not_configured") code = "NOT_CONFIGURED";
108
- if (typeof error !== "string") code = response.status >= 500 ? "INTERNAL" : "UNKNOWN";
109
- const after = response.headers.get("retry-after")?.trim() ?? "";
110
- return new AssistantApiError(code, response.status, message, /^\d{1,9}$/.test(after) ? Number(after) : void 0);
111
- }
112
- async function call(url, method, body, callOptions, shape, authed = true) {
113
- const response = await request(url, method, body, callOptions, authed);
114
- if (!response.ok) throw await failure(response);
115
- let parsed;
116
- try {
117
- parsed = JSON.parse(await response.text());
118
- } catch {
119
- parsed = void 0;
120
- }
121
- if (!SHAPES[shape](parsed)) throw new AssistantApiError("UNKNOWN", response.status, `the reply is not a ${shape}`);
122
- return parsed;
123
- }
124
- async function* chat(message, callOptions) {
125
- const response = await request(`${root}/chat`, "POST", { message }, callOptions);
126
- if (!response.ok) throw await failure(response);
127
- if (!(response.headers.get("content-type") ?? "").startsWith("text/event-stream") || response.body === null) {
128
- await response.body?.cancel();
129
- throw new AssistantApiError("UNKNOWN", response.status, "the reply is not an event stream");
130
- }
131
- const reader = response.body.getReader();
132
- const decoder = new TextDecoder();
133
- let buffer = "";
134
- let ended = false;
135
- function* drain(final) {
136
- const text = final ? buffer : buffer.replace(/\r$/, "");
137
- const normal = text.replace(/\r\n?/g, "\n");
138
- const rest = buffer.slice(text.length);
139
- let from = 0;
140
- for (let end = normal.indexOf("\n\n", from); end >= 0; end = normal.indexOf("\n\n", from)) {
141
- const event = parseBlock(normal.slice(from, end), response.status);
142
- from = end + 2;
143
- if (event === void 0) continue;
144
- if (ended) throw new AssistantApiError("UNKNOWN", response.status, "an event came after the end of the stream");
145
- ended = event.type === "done" || event.type === "error";
146
- yield event;
147
- }
148
- buffer = normal.slice(from) + rest;
149
- }
150
- try {
151
- for (; ; ) {
152
- const chunk = await reader.read();
153
- if (chunk.done) break;
154
- buffer += decoder.decode(chunk.value, { stream: true });
155
- yield* drain(false);
156
- if (buffer.length > MAX_EVENT_CHARS) throw new AssistantApiError("UNKNOWN", response.status, "an event is too long");
157
- }
158
- buffer += decoder.decode();
159
- yield* drain(true);
160
- if (!ended || buffer.trim() !== "") throw new AssistantApiError("UNKNOWN", response.status, "the stream ended before a done event");
161
- } finally {
162
- await reader.cancel().catch(() => void 0);
163
- }
164
- }
165
- function parseBlock(block, status) {
166
- const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).replace(/^ /, "")).join("\n");
167
- if (data === "") return void 0;
168
- let parsed;
169
- try {
170
- parsed = JSON.parse(data);
171
- } catch {
172
- parsed = void 0;
173
- }
174
- if (!isEvent(parsed)) throw new AssistantApiError("UNKNOWN", status, "an event is not valid");
175
- return parsed;
176
- }
177
- const decided = (id, decision) => {
178
- if (decision.requestId !== id) throw new AssistantApiError("UNKNOWN", 200, "the reply is for another approval");
179
- return decision;
180
- };
181
- const methods = {
182
- // /health is outside /api/v1 and needs no token, so the client sends none.
183
- health: async (o) => call(`${origin.origin}${prefix}/health`, "GET", void 0, o, "health", false),
184
- tools: async (o) => (await call(`${root}/tools`, "GET", void 0, o, "tools")).tools,
185
- chat,
186
- approvals: async (o) => (await call(`${root}/approvals`, "GET", void 0, o, "approvals")).approvals,
187
- approve: async (id, digest, o) => decided(id, await call(`${root}/approvals/${seg(id)}/approve`, "POST", { digest }, o, "decision")),
188
- deny: async (id, reason, o) => decided(id, await call(`${root}/approvals/${seg(id)}/deny`, "POST", reason === void 0 ? {} : { reason }, o, "decision"))
189
- };
190
- return methods;
191
- }
1
+ import {
2
+ AssistantApiError,
3
+ createClient
4
+ } from "./chunk-QTIEDY3V.js";
192
5
  export {
193
6
  AssistantApiError,
194
7
  createClient
package/dist/demo.js CHANGED
@@ -1,14 +1,15 @@
1
1
  import {
2
2
  createStaticTokenAuth,
3
3
  createWorker
4
- } from "./chunk-YMEM57LX.js";
4
+ } from "./chunk-TOQIHYDH.js";
5
5
  import {
6
6
  AnthropicProvider
7
- } from "./chunk-FQD4OK2R.js";
7
+ } from "./chunk-2RRM6OLC.js";
8
8
  import {
9
9
  ToolRegistry,
10
- VERSION
11
- } from "./chunk-C44QT474.js";
10
+ VERSION,
11
+ hasScope
12
+ } from "./chunk-JNZPVHNN.js";
12
13
 
13
14
  // src/demo-page.ts
14
15
  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'";
@@ -19,13 +20,13 @@ var DEMO_PAGE = `<!doctype html>
19
20
  <meta name="viewport" content="width=device-width, initial-scale=1">
20
21
  <title>agentic-assistant demo</title>
21
22
  <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
+ body{font:16px/1.5 system-ui,sans-serif;max-width:42rem;margin:2rem auto;padding:0 1rem;color:var(--ad-color-text,CanvasText);background:var(--ad-color-surface,Canvas)}
23
24
  label{display:block;margin-top:1rem;font-weight:600}
24
25
  input,textarea,button{font:inherit;padding:.5rem;width:100%;box-sizing:border-box}
25
26
  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}
27
+ :focus-visible{outline:3px solid var(--ad-color-focus,LinkText);outline-offset:2px}
28
+ #out{white-space:pre-wrap;border:1px solid var(--ad-color-border-strong,CanvasText);padding:1rem;margin-top:1rem;min-height:4rem}
29
+ .badge{display:inline-block;border:1px solid var(--ad-color-text,CanvasText);padding:0 .5rem;font-size:.875rem}
29
30
  </style>
30
31
  </head>
31
32
  <body>
@@ -103,6 +104,7 @@ var demoRegistry = () => new ToolRegistry().register({
103
104
  risk: "safe",
104
105
  handler: () => ({ now: (/* @__PURE__ */ new Date()).toISOString() })
105
106
  });
107
+ var DEMO_SCOPE = "demo:read";
106
108
  var DEMO_SYSTEM = "You are a demo assistant. You may use the demo_time tool. Answer in short, plain text.";
107
109
  function modelOptions(env) {
108
110
  if (!env.MODEL_KEY) return void 0;
@@ -117,6 +119,8 @@ async function buildDemo(env) {
117
119
  const auth = await createStaticTokenAuth(tokens);
118
120
  const worker = createWorker({
119
121
  auth,
122
+ // A token without `demo:read` signs in but may use no route.
123
+ routeScopes: { tools: DEMO_SCOPE, chat: DEMO_SCOPE, approvals: DEMO_SCOPE },
120
124
  registry: demoRegistry(),
121
125
  system: DEMO_SYSTEM,
122
126
  limits: { maxTurns: 4, maxTotalTokens: 2e4, maxOutputTokensPerTurn: 1024 },
@@ -148,7 +152,8 @@ var demo_default = {
148
152
  return notConfigured("this demo is not set up");
149
153
  }
150
154
  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");
155
+ const who = await demo.auth.authenticate(request.clone());
156
+ if (who !== null && hasScope(who.scopes, DEMO_SCOPE)) return notConfigured("this demo has no model key or model endpoint set");
152
157
  }
153
158
  return demo.worker.fetch(request, env);
154
159
  }
package/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
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';
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-B6aowbyz.js';
2
+ import { S as Setup } from './chat-CGITyrxg.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-CGITyrxg.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';
7
7
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
8
+ import { AssistantClient } from './client.js';
8
9
 
9
10
  declare const VERSION: string;
10
11
 
@@ -81,9 +82,16 @@ declare class FallbackProvider implements ModelProvider {
81
82
  stream(request: ModelRequest): AsyncGenerator<ModelEvent>;
82
83
  }
83
84
 
84
- /** An MCP server with one tool, `assistant_chat`. Each call starts a new conversation. */
85
- declare function createAssistantMcpServer(setup: Setup, shutdown?: AbortSignal): Server;
85
+ /**
86
+ * An MCP server with the tool `assistant_chat`. Each call starts a new conversation. With a `remote` client it
87
+ * also serves `assistant_tools`, `assistant_approvals`, and `assistant_deny`. `assistant_approve` needs `allow.approve`.
88
+ */
89
+ declare function createAssistantMcpServer(setup: Setup, shutdown?: AbortSignal, remote?: AssistantClient, allow?: {
90
+ approve?: boolean;
91
+ }): Server;
86
92
  /** Serves on stdio. Resolves when the client closes its input, or the transport closes. */
87
- declare function startAssistantMcp(setup: Setup): Promise<void>;
93
+ declare function startAssistantMcp(setup: Setup, remote?: AssistantClient, allow?: {
94
+ approve?: boolean;
95
+ }): Promise<void>;
88
96
 
89
97
  export { AnthropicProvider, type AnthropicProviderOptions, DEFAULT_ANTHROPIC_MODEL, FallbackProvider, ModelEvent, ModelProvider, ModelRequest, type OpenAICompatibleOptions, OpenAICompatibleProvider, PRICING_CHECKED_ON, Pricing, Setup, VERSION, createAssistantMcpServer, startAssistantMcp };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createAssistantMcpServer,
3
3
  startAssistantMcp
4
- } from "./chunk-KNUEYFX2.js";
4
+ } from "./chunk-E7TFMM2Z.js";
5
5
  import {
6
6
  ConfigError,
7
7
  FallbackProvider,
@@ -10,13 +10,13 @@ import {
10
10
  connectMcpTools,
11
11
  parseTarget,
12
12
  runChat
13
- } from "./chunk-MESH6X6N.js";
13
+ } from "./chunk-5BZENFYL.js";
14
14
  import "./chunk-HYT732TN.js";
15
15
  import {
16
16
  AnthropicProvider,
17
17
  DEFAULT_ANTHROPIC_MODEL,
18
18
  PRICING_CHECKED_ON
19
- } from "./chunk-FQD4OK2R.js";
19
+ } from "./chunk-2RRM6OLC.js";
20
20
  import {
21
21
  ACTIONS,
22
22
  DATA_NOTICE,
@@ -40,7 +40,8 @@ import {
40
40
  provenanceMarker,
41
41
  runAssistant,
42
42
  scopeCovers
43
- } from "./chunk-C44QT474.js";
43
+ } from "./chunk-JNZPVHNN.js";
44
+ import "./chunk-QTIEDY3V.js";
44
45
  export {
45
46
  ACTIONS,
46
47
  AnthropicProvider,
@@ -287,4 +287,4 @@ declare const DATA_NOTICE: string;
287
287
  */
288
288
  declare function runAssistant(options: RunOptions): AsyncGenerator<AssistantEvent>;
289
289
 
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 };
290
+ 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 };
@@ -0,0 +1,13 @@
1
+ import {
2
+ createAssistantMcpServer,
3
+ startAssistantMcp
4
+ } from "./chunk-E7TFMM2Z.js";
5
+ import "./chunk-5BZENFYL.js";
6
+ import "./chunk-HYT732TN.js";
7
+ import "./chunk-2RRM6OLC.js";
8
+ import "./chunk-JNZPVHNN.js";
9
+ import "./chunk-QTIEDY3V.js";
10
+ export {
11
+ createAssistantMcpServer,
12
+ startAssistantMcp
13
+ };