agentic-assistant 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { J as JsonSchema, U as Usage, c as Message, M as ModelProvider } from './types-C47SyHH5.js';
1
+ import { J as JsonSchema, U as Usage, A as AnsweredBy, c as Message, M as ModelProvider } from './types-DGH8FS56.js';
2
2
 
3
3
  declare const ACTIONS: readonly ["read", "comment", "write", "admin"];
4
4
  type Action = (typeof ACTIONS)[number];
@@ -65,11 +65,14 @@ interface ApprovalRequest {
65
65
  digest: string;
66
66
  actor: Actor;
67
67
  }
68
+ /** Who gave an approval. The gate says it. `human` means a signed-in person, `host` means a rule the host set up, `agent` means another agent. */
69
+ type ApproverKind = "human" | "agent" | "host";
68
70
  type ApprovalDecision = {
69
71
  requestId: string;
70
72
  approved: true;
71
73
  digest: string;
72
- decidedBy: string;
74
+ decidedBy: string; /** Leave it out when you do not know. The record then says "unknown". */
75
+ approverKind?: ApproverKind;
73
76
  } | {
74
77
  requestId: string;
75
78
  approved: false;
@@ -100,11 +103,57 @@ declare class QueueApprovalGate implements ApprovalGate {
100
103
  private readonly waiting;
101
104
  pending(): ApprovalRequest[];
102
105
  request(request: ApprovalRequest, signal: AbortSignal): Promise<ApprovalDecision>;
103
- approve(id: string, decidedBy: string): void;
106
+ approve(id: string, decidedBy: string, approverKind?: ApproverKind): void;
104
107
  reject(id: string, decidedBy?: string, reason?: string): void;
105
108
  private take;
106
109
  }
107
110
 
111
+ /** Says that a machine made an output, and which one. Attach it to every result and every edit. */
112
+ interface Provenance {
113
+ generator: "ai";
114
+ /** The model that answered. After a fallback, this is the fallback model. */
115
+ model: string;
116
+ provider: string;
117
+ /** ISO 8601 time, in UTC. */
118
+ createdAt: string;
119
+ /** The id of the assistant principal that acted. */
120
+ actor: string;
121
+ /** The person the assistant acts for. */
122
+ onBehalfOf: string;
123
+ /** The tool that ran. Set on a tool result. */
124
+ tool?: string;
125
+ /** The risk of that tool. A host may mark only `risky` and `irreversible` results as edits. */
126
+ risk?: "safe" | "risky" | "irreversible";
127
+ /** Who approved the action, and what kind of approver it was. Check `kind` before you say a person approved. */
128
+ approvedBy?: Approver[];
129
+ }
130
+ interface Approver {
131
+ id: string;
132
+ /** `unknown` when the approval gate did not say. */
133
+ kind: ApproverKind | "unknown";
134
+ }
135
+ interface ProvenanceInput {
136
+ model: string;
137
+ provider: string;
138
+ /** Epoch milliseconds. */
139
+ createdAt: number;
140
+ actor: string;
141
+ onBehalfOf: string;
142
+ tool?: string;
143
+ risk?: Provenance["risk"];
144
+ approvedBy?: readonly Approver[];
145
+ }
146
+ /** The IPTC digital source type for content made by a model. */
147
+ declare const TRAINED_ALGORITHMIC_MEDIA = "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia";
148
+ declare function createProvenance(input: ProvenanceInput): Provenance;
149
+ /**
150
+ * A JSON-LD marker for a document or a file. Store it next to the content the assistant made.
151
+ * It uses schema.org terms and the IPTC `trainedAlgorithmicMedia` source type.
152
+ */
153
+ declare function provenanceMarker(provenance: Provenance): Record<string, unknown>;
154
+ /** A short text for a person to read, such as "AI-generated by model-1 (acme), approved by ana". */
155
+ declare function provenanceLabel(provenance: Provenance): string;
156
+
108
157
  interface ToolContext {
109
158
  principal: AssistantPrincipal;
110
159
  toolUseId: string;
@@ -191,7 +240,8 @@ type AssistantEvent = {
191
240
  } | {
192
241
  type: "approval_decision";
193
242
  requestId: string;
194
- approved: boolean;
243
+ approved: boolean; /** True only when the approver said no. A timeout or a gate failure is not a decline. */
244
+ declined: boolean;
195
245
  decidedBy?: string;
196
246
  reason?: string;
197
247
  } | {
@@ -206,18 +256,22 @@ type AssistantEvent = {
206
256
  /** The raw failure text. It stays in the event and never goes to the model. */
207
257
  detail?: string;
208
258
  actor: Actor;
259
+ /** Marks the result as machine-made. Set when the tool ran. */
260
+ provenance?: Provenance;
209
261
  } | {
210
262
  type: "turn_end";
211
263
  turn: number;
212
264
  usage: Usage;
213
- totals: RunTotals;
265
+ totals: RunTotals; /** The model that answered this turn. */
266
+ answeredBy: AnsweredBy;
214
267
  } | {
215
268
  type: "done";
216
269
  reason: DoneReason;
217
270
  totals: RunTotals;
218
271
  actor: Actor;
219
272
  messages: Message[];
220
- error?: string;
273
+ error?: string; /** Marks the answer as machine-made. Set once a model has answered. */
274
+ provenance?: Provenance;
221
275
  };
222
276
  /** Sent with every request. Tool output is data, never an instruction. */
223
277
  declare const DATA_NOTICE: string;
@@ -228,4 +282,4 @@ declare const DATA_NOTICE: string;
228
282
  */
229
283
  declare function runAssistant(options: RunOptions): AsyncGenerator<AssistantEvent>;
230
284
 
231
- export { ACTIONS as A, DATA_NOTICE as D, PrincipalError as P, QueueApprovalGate as Q, type RunLimits as R, type Scope as S, type ToolRisk as T, type ToolDefinition as a, type Action as b, type Actor as c, type ApprovalDecision as d, type ApprovalGate as e, type ApprovalRequest as f, type AssistantEvent as g, type AssistantPrincipal as h, type DeniedReason as i, type DoneReason as j, type PrincipalInput as k, type RunOptions as l, type RunTotals as m, TOOL_RISKS as n, type ToolContext as o, ToolRegistry as p, actorOf as q, assertPrincipal as r, canonicalJson as s, createPrincipal as t, defineTool as u, digestCall as v, hasScope as w, parseScope as x, runAssistant as y, scopeCovers as z };
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 };
@@ -0,0 +1,11 @@
1
+ import {
2
+ createAssistantMcpServer,
3
+ startAssistantMcp
4
+ } from "./chunk-6UD2QQA4.js";
5
+ import "./chunk-BDXAUNMR.js";
6
+ import "./chunk-HYT732TN.js";
7
+ import "./chunk-T4ZMDOB4.js";
8
+ export {
9
+ createAssistantMcpServer,
10
+ startAssistantMcp
11
+ };
package/dist/react.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
- import { g as AssistantEvent, T as ToolRisk } from './loop-CDbbDwSs.js';
3
- import { c as Message } from './types-C47SyHH5.js';
2
+ import { h as AssistantEvent, u as ToolRisk } from './loop-42giKLVa.js';
3
+ import { c as Message } from './types-DGH8FS56.js';
4
4
 
5
5
  type PanelItem = {
6
6
  kind: "user";
package/dist/react.js CHANGED
@@ -41,6 +41,7 @@ function useAssistant(options) {
41
41
  const sent = [...history.current, userMessage];
42
42
  let streamingId = null;
43
43
  let finished = false;
44
+ let failed = false;
44
45
  const toolItems = /* @__PURE__ */ new Map();
45
46
  let lastToolItem = null;
46
47
  try {
@@ -81,7 +82,7 @@ function useAssistant(options) {
81
82
  event.requestId,
82
83
  (item) => item.kind === "approval" ? {
83
84
  ...item,
84
- status: event.approved ? "approved" : event.reason === "the person declined" ? "declined" : "unavailable",
85
+ status: event.approved ? "approved" : event.declined ? "declined" : "unavailable",
85
86
  busy: false,
86
87
  decidedBy: event.decidedBy
87
88
  } : item
@@ -109,11 +110,16 @@ function useAssistant(options) {
109
110
  }
110
111
  }
111
112
  } catch {
112
- add({ kind: "notice", id: nextId(), text: RUN_FAILED });
113
+ failed = true;
113
114
  } finally {
114
- if (!finished) history.current = sent;
115
+ if (!finished) {
116
+ history.current = sent;
117
+ if (!failed) failed = true;
118
+ }
119
+ if (failed) add({ kind: "notice", id: nextId(), text: RUN_FAILED });
115
120
  setItems(
116
121
  (current) => current.map((item) => {
122
+ if (item.kind === "assistant" && item.streaming) return { ...item, streaming: false };
117
123
  if (item.kind === "approval" && item.status === "pending") return { ...item, status: "expired", busy: false };
118
124
  if (item.kind === "tool" && (item.status === "running" || item.status === "waiting")) return { ...item, status: "not_run" };
119
125
  return item;
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { M as ModelProvider, P as Pricing, a as ModelRequest, S as StopReason, U as Usage, b as ModelEvent } from './types-C47SyHH5.js';
1
+ import { M as ModelProvider, P as Pricing, a as ModelRequest, S as StopReason, U as Usage, b as ModelEvent } from './types-DGH8FS56.js';
2
2
 
3
3
  interface ScriptedTurn {
4
4
  text?: string;
@@ -19,7 +19,7 @@ declare class ScriptedProvider implements ModelProvider {
19
19
  private readonly script;
20
20
  readonly pricing?: Pricing | undefined;
21
21
  readonly name = "scripted";
22
- readonly model = "scripted-1";
22
+ readonly model: string;
23
23
  readonly requests: ModelRequest[];
24
24
  private next;
25
25
  constructor(script: readonly ScriptedTurn[], pricing?: Pricing | undefined);
package/dist/testing.js CHANGED
@@ -1,35 +1,6 @@
1
- // src/testing.ts
2
- var ScriptedProvider = class {
3
- constructor(script, pricing) {
4
- this.script = script;
5
- this.pricing = pricing;
6
- }
7
- script;
8
- pricing;
9
- name = "scripted";
10
- model = "scripted-1";
11
- requests = [];
12
- next = 0;
13
- async *stream(request) {
14
- this.requests.push({ ...request, messages: structuredClone(request.messages) });
15
- const turn = this.script[this.next++];
16
- if (turn === void 0) throw new Error(`scripted provider ran out of turns at request ${this.next}`);
17
- const content = [];
18
- if (turn.text !== void 0) {
19
- for (const word of turn.text.split(/(?<= )/)) yield { type: "text_delta", text: word };
20
- content.push({ type: "text", text: turn.text });
21
- }
22
- for (const [index, call] of (turn.toolCalls ?? []).entries()) {
23
- content.push({ type: "tool_use", id: call.id ?? `call_${this.next}_${index}`, name: call.name, input: call.input });
24
- }
25
- yield {
26
- type: "message",
27
- content,
28
- stopReason: turn.stopReason ?? (turn.toolCalls?.length ? "tool_use" : "end_turn"),
29
- usage: turn.usage ?? { inputTokens: 10, outputTokens: 5 }
30
- };
31
- }
32
- };
1
+ import {
2
+ ScriptedProvider
3
+ } from "./chunk-HYT732TN.js";
33
4
  export {
34
5
  ScriptedProvider
35
6
  };
@@ -41,6 +41,15 @@ interface ModelRequest {
41
41
  maxOutputTokens: number;
42
42
  signal?: AbortSignal;
43
43
  }
44
+ /** Which model gave an answer. A provider that picks between models sets it on the final message. */
45
+ interface AnsweredBy {
46
+ provider: string;
47
+ model: string;
48
+ /** True when a fallback model answered. */
49
+ fallback: boolean;
50
+ /** Why the first model did not answer. Set only when `fallback` is true. */
51
+ fallbackFor?: "refusal" | "error";
52
+ }
44
53
  type ModelEvent = {
45
54
  type: "text_delta";
46
55
  text: string;
@@ -49,6 +58,7 @@ type ModelEvent = {
49
58
  content: ContentBlock[];
50
59
  stopReason: StopReason;
51
60
  usage: Usage;
61
+ answeredBy?: AnsweredBy;
52
62
  };
53
63
  /** Prices in US dollars per million tokens. */
54
64
  interface Pricing {
@@ -63,10 +73,12 @@ interface ModelProvider {
63
73
  readonly name: string;
64
74
  readonly model: string;
65
75
  readonly pricing?: Pricing;
76
+ /** Most requests one `stream` call may send to a model. Default 1. The loop counts the input of each in its caps. */
77
+ readonly maxAsks?: number;
66
78
  stream(request: ModelRequest): AsyncIterable<ModelEvent>;
67
79
  }
68
80
  declare class ProviderError extends Error {
69
81
  constructor(message: string);
70
82
  }
71
83
 
72
- export { type ContentBlock as C, type JsonSchema as J, type ModelProvider as M, type Pricing as P, type StopReason as S, type ToolSpec as T, type Usage as U, type ModelRequest as a, type ModelEvent as b, type Message as c, ProviderError as d };
84
+ export { type AnsweredBy as A, type ContentBlock as C, type JsonSchema as J, type ModelProvider as M, type Pricing as P, type StopReason as S, type ToolSpec as T, type Usage as U, type ModelRequest as a, type ModelEvent as b, type Message as c, ProviderError as d };
package/dist/worker.d.ts CHANGED
@@ -1,8 +1,41 @@
1
+ import { Auth } from 'agentic-identity';
2
+ import { t as ToolRegistry, R as RunLimits } from './loop-42giKLVa.js';
3
+ import { M as ModelProvider } from './types-DGH8FS56.js';
4
+
5
+ interface AssistantApiOptions {
6
+ /** Reads the credential on a request. Use an `agentic-identity` Auth, such as `createStoreAuth`. */
7
+ auth: Auth;
8
+ /** Makes the model for one chat turn. Called once per request, so a provider may keep state. */
9
+ provider(): ModelProvider;
10
+ /** The tools the assistant may use. Each one has a scope and a risk. */
11
+ registry: ToolRegistry;
12
+ system?: string;
13
+ limits?: RunLimits;
14
+ /** Most chat turns one principal may run at once. Default 4. */
15
+ maxConcurrentRuns?: number;
16
+ }
17
+ /** An agent needs this scope before it may decide on a tool call. A person needs no extra scope. */
18
+ declare const APPROVER_SCOPE = "assistant:admin";
19
+ /**
20
+ * The hosted API, under `/api/v1`: `GET /tools`, `POST /chat` (a stream of server-sent events),
21
+ * `GET /approvals`, and `POST /approvals/:id/approve` or `/deny`. Every route needs a principal.
22
+ *
23
+ * A pending approval lives in the memory of this object. Run chat and approval on one instance.
24
+ */
25
+ declare function createAssistantApi(options: AssistantApiOptions): {
26
+ fetch(request: Request): Promise<Response>;
27
+ };
28
+
1
29
  interface WorkerEnv {
2
30
  PACK_NAME?: string;
3
31
  }
32
+ /** A Worker that serves `/health` and the `/api/v1` routes. Build the options in your own entry file. */
33
+ declare function createWorker(options: AssistantApiOptions): {
34
+ fetch(request: Request, env?: WorkerEnv): Promise<Response>;
35
+ };
36
+ /** The sample Worker has no auth, tools, or model, so it serves `/health` only. Use `createWorker` for the API. */
4
37
  declare const _default: {
5
38
  fetch(request: Request, env: WorkerEnv): Promise<Response>;
6
39
  };
7
40
 
8
- export { type WorkerEnv, _default as default };
41
+ export { APPROVER_SCOPE, type AssistantApiOptions, type WorkerEnv, createAssistantApi, createWorker, _default as default };
package/dist/worker.js CHANGED
@@ -1,17 +1,276 @@
1
1
  import {
2
- VERSION
3
- } from "./chunk-4QE7E5UQ.js";
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
+ }
4
247
 
5
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
+ }
6
261
  var worker_default = {
7
262
  async fetch(request, env) {
8
263
  const url = new URL(request.url);
9
- if (request.method === "GET" && url.pathname === "/health") {
10
- return Response.json({ ok: true, pack: env.PACK_NAME ?? "agentic-assistant", version: VERSION });
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 });
11
267
  }
12
268
  return Response.json({ error: "not found" }, { status: 404 });
13
269
  }
14
270
  };
15
271
  export {
272
+ APPROVER_SCOPE,
273
+ createAssistantApi,
274
+ createWorker,
16
275
  worker_default as default
17
276
  };
package/llms.txt CHANGED
@@ -5,6 +5,9 @@ An AI assistant that acts inside your apps with the user's own permissions.
5
5
  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
+ - 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.
10
+ - AI marking: events carry `provenance`; use provenanceLabel and provenanceMarker (JSON-LD, IPTC trainedAlgorithmicMedia). Also in HTTP, CLI --json and MCP results.
8
11
  - Panel: import { AssistantPanel } from "agentic-assistant/react"; import "agentic-assistant/react.css".
9
12
  - Test provider: import { ScriptedProvider } from "agentic-assistant/testing".
10
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-assistant",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "An AI assistant that acts inside your apps with the user's own permissions.",
5
5
  "keywords": [
6
6
  "assistant",
@@ -28,7 +28,8 @@
28
28
  "files": [
29
29
  "dist",
30
30
  "llms.txt",
31
- "SKILL.md"
31
+ "SKILL.md",
32
+ "CHANGELOG.md"
32
33
  ],
33
34
  "publishConfig": {
34
35
  "access": "public"
@@ -40,6 +41,10 @@
40
41
  "types": "./dist/index.d.ts",
41
42
  "import": "./dist/index.js"
42
43
  },
44
+ "./worker": {
45
+ "types": "./dist/worker.d.ts",
46
+ "import": "./dist/worker.js"
47
+ },
43
48
  "./testing": {
44
49
  "types": "./dist/testing.d.ts",
45
50
  "import": "./dist/testing.js"
@@ -58,11 +63,13 @@
58
63
  "typecheck": "tsc --noEmit",
59
64
  "test": "vitest run",
60
65
  "clean": "rm -rf dist",
66
+ "prepack": "npm run build",
61
67
  "ci:local": "npm run build && npm run typecheck && npm test"
62
68
  },
63
69
  "dependencies": {
64
70
  "@anthropic-ai/sdk": "^1.3.0",
65
71
  "@modelcontextprotocol/sdk": "^1.32.2",
72
+ "agentic-identity": "^0.3.0",
66
73
  "ajv": "^8.20.0"
67
74
  },
68
75
  "devDependencies": {
@@ -1,7 +0,0 @@
1
- // src/version.ts
2
- import { createRequire } from "module";
3
- var VERSION = true ? "0.1.0" : readPackageVersion();
4
-
5
- export {
6
- VERSION
7
- };