agentic-assistant 0.1.0 → 0.3.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.
@@ -0,0 +1,142 @@
1
+ import {
2
+ ProviderError
3
+ } from "./chunk-3LKTJJ75.js";
4
+
5
+ // src/providers/anthropic.ts
6
+ import Anthropic from "@anthropic-ai/sdk";
7
+
8
+ // src/providers/url.ts
9
+ function isLoopback(url) {
10
+ return ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
11
+ }
12
+ function checkBaseUrl(baseUrl) {
13
+ let url;
14
+ try {
15
+ url = new URL(baseUrl);
16
+ } catch {
17
+ throw new ProviderError("baseUrl is not a valid URL");
18
+ }
19
+ if (url.username !== "" || url.password !== "") {
20
+ throw new ProviderError("baseUrl must not carry credentials; pass apiKey instead");
21
+ }
22
+ if (url.search !== "" || url.hash !== "") {
23
+ throw new ProviderError("baseUrl must not carry a query string or a fragment");
24
+ }
25
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopback(url))) {
26
+ throw new ProviderError("baseUrl must use https, except for localhost");
27
+ }
28
+ return { url, text: baseUrl.replace(/\/+$/, "") };
29
+ }
30
+
31
+ // src/providers/anthropic.ts
32
+ var DEFAULT_ANTHROPIC_MODEL = "claude-sonnet-5-5";
33
+ var PRICING_CHECKED_ON = "2026-10-29";
34
+ var KNOWN_PRICING = Object.freeze({
35
+ "claude-sonnet-5-5": Object.freeze({ inputPerMTok: 2, outputPerMTok: 10 }),
36
+ "claude-opus-5-5": Object.freeze({ inputPerMTok: 4, outputPerMTok: 20 })
37
+ });
38
+ var STOP_REASONS = {
39
+ end_turn: "end_turn",
40
+ stop_sequence: "end_turn",
41
+ tool_use: "tool_use",
42
+ max_tokens: "max_tokens",
43
+ refusal: "refusal"
44
+ };
45
+ function toAnthropicMessages(messages) {
46
+ return messages.map((message) => ({
47
+ role: message.role,
48
+ content: message.content.flatMap((block) => {
49
+ switch (block.type) {
50
+ case "text":
51
+ return block.text.length > 0 ? [{ type: "text", text: block.text }] : [];
52
+ case "tool_use":
53
+ return [{ type: "tool_use", id: block.id, name: block.name, input: block.input }];
54
+ case "tool_result":
55
+ return [{ type: "tool_result", tool_use_id: block.toolUseId, content: block.content, is_error: block.isError ?? false }];
56
+ case "provider_block":
57
+ return block.provider === "anthropic" ? [block.raw] : [];
58
+ }
59
+ })
60
+ }));
61
+ }
62
+ function fromAnthropicContent(content) {
63
+ return content.map((block) => {
64
+ if (block.type === "text") return { type: "text", text: block.text };
65
+ if (block.type === "tool_use") return { type: "tool_use", id: block.id, name: block.name, input: block.input };
66
+ return { type: "provider_block", provider: "anthropic", raw: block };
67
+ });
68
+ }
69
+ var AnthropicProvider = class {
70
+ name = "anthropic";
71
+ model;
72
+ pricing;
73
+ client;
74
+ effort;
75
+ constructor(options = {}) {
76
+ this.model = options.model ?? DEFAULT_ANTHROPIC_MODEL;
77
+ this.pricing = options.pricing ?? KNOWN_PRICING[this.model];
78
+ this.effort = options.effort;
79
+ if (options.client !== void 0) {
80
+ this.client = options.client;
81
+ return;
82
+ }
83
+ if (options.baseUrl === void 0 && options.useDefaultEndpoint !== true) {
84
+ throw new ProviderError("set baseUrl, or set useDefaultEndpoint: true to call api.anthropic.com, a US-hosted endpoint");
85
+ }
86
+ const apiKey = options.apiKey ?? process.env.ANTHROPIC_API_KEY;
87
+ if (typeof apiKey !== "string" || apiKey.length === 0) {
88
+ throw new ProviderError("apiKey is required: pass it, or set ANTHROPIC_API_KEY");
89
+ }
90
+ this.client = new Anthropic({
91
+ apiKey,
92
+ // Always set, so ANTHROPIC_BASE_URL in the environment cannot redirect the key.
93
+ baseURL: options.baseUrl !== void 0 ? checkBaseUrl(options.baseUrl).text : "https://api.anthropic.com"
94
+ });
95
+ }
96
+ async *stream(request) {
97
+ const stream = this.client.messages.stream(
98
+ {
99
+ model: this.model,
100
+ max_tokens: request.maxOutputTokens,
101
+ ...request.system !== void 0 ? { system: request.system } : {},
102
+ messages: toAnthropicMessages(request.messages),
103
+ ...request.tools.length > 0 ? {
104
+ tools: request.tools.map((tool) => ({
105
+ name: tool.name,
106
+ description: tool.description,
107
+ input_schema: tool.inputSchema
108
+ }))
109
+ } : {},
110
+ ...this.effort !== void 0 ? { output_config: { effort: this.effort } } : {}
111
+ },
112
+ { signal: request.signal }
113
+ );
114
+ for await (const event of stream) {
115
+ if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
116
+ yield { type: "text_delta", text: event.delta.text };
117
+ }
118
+ }
119
+ const message = await stream.finalMessage();
120
+ const usage = message.usage;
121
+ if (typeof usage?.input_tokens !== "number" || typeof usage.output_tokens !== "number") {
122
+ throw new ProviderError("anthropic response carried no token usage");
123
+ }
124
+ yield {
125
+ type: "message",
126
+ content: fromAnthropicContent(message.content),
127
+ stopReason: STOP_REASONS[message.stop_reason ?? ""] ?? "other",
128
+ usage: {
129
+ inputTokens: usage.input_tokens + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0),
130
+ outputTokens: usage.output_tokens
131
+ }
132
+ };
133
+ }
134
+ };
135
+
136
+ export {
137
+ isLoopback,
138
+ checkBaseUrl,
139
+ DEFAULT_ANTHROPIC_MODEL,
140
+ PRICING_CHECKED_ON,
141
+ AnthropicProvider
142
+ };
@@ -0,0 +1,302 @@
1
+ import {
2
+ VERSION,
3
+ createPrincipal,
4
+ hasScope,
5
+ parseScope,
6
+ runAssistant
7
+ } from "./chunk-3LKTJJ75.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", "x-ai-generated": "true" }
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/static-auth.ts
249
+ var MIN_TOKEN_BYTES = 32;
250
+ var encoder = new TextEncoder();
251
+ async function digest(token) {
252
+ const hash = await crypto.subtle.digest("SHA-256", encoder.encode(token));
253
+ return [...new Uint8Array(hash)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
254
+ }
255
+ async function createStaticTokenAuth(tokens) {
256
+ const byDigest = /* @__PURE__ */ new Map();
257
+ for (const [token, entry] of Object.entries(tokens)) {
258
+ if (encoder.encode(token).length < MIN_TOKEN_BYTES) throw new Error(`tokens must hold at least ${MIN_TOKEN_BYTES} bytes`);
259
+ if (/\s/.test(token)) throw new Error("a token may not hold white space");
260
+ if (typeof entry !== "object" || entry === null || Object.keys(entry).some((key) => key !== "id" && key !== "scopes")) {
261
+ throw new Error("a token entry has only an id and scopes: every caller is a person");
262
+ }
263
+ if (typeof entry?.id !== "string" || entry.id === "" || !Array.isArray(entry.scopes) || entry.scopes.some((s) => typeof s !== "string")) {
264
+ throw new Error("each token needs an id and a list of scopes");
265
+ }
266
+ for (const scope of entry.scopes) parseScope(scope);
267
+ byDigest.set(await digest(token), {
268
+ principalId: entry.id,
269
+ kind: "human",
270
+ chain: [{ id: entry.id, kind: "human" }],
271
+ scopes: [...entry.scopes]
272
+ });
273
+ }
274
+ return {
275
+ async authenticate(request) {
276
+ const match = /^Bearer ([^\s]+)$/.exec(request.headers.get("authorization") ?? "");
277
+ const context = match ? byDigest.get(await digest(match[1])) : void 0;
278
+ return context ? { ...context, chain: context.chain.map((step) => ({ ...step })), scopes: [...context.scopes] } : null;
279
+ }
280
+ };
281
+ }
282
+
283
+ // src/worker.ts
284
+ var health = (env) => Response.json({ ok: true, pack: env.PACK_NAME ?? "agentic-assistant", version: VERSION });
285
+ function createWorker(options) {
286
+ const api = createAssistantApi(options);
287
+ return {
288
+ async fetch(request, env = {}) {
289
+ const url = new URL(request.url);
290
+ if (request.method === "GET" && url.pathname === "/health") return health(env);
291
+ if (url.pathname.startsWith("/api/v1/")) return api.fetch(request);
292
+ return Response.json({ error: "not found" }, { status: 404 });
293
+ }
294
+ };
295
+ }
296
+
297
+ export {
298
+ APPROVER_SCOPE,
299
+ createAssistantApi,
300
+ createStaticTokenAuth,
301
+ createWorker
302
+ };
@@ -0,0 +1,36 @@
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
+ };
33
+
34
+ export {
35
+ ScriptedProvider
36
+ };
@@ -0,0 +1,82 @@
1
+ import {
2
+ runChat
3
+ } from "./chunk-3X6KU636.js";
4
+ import {
5
+ VERSION,
6
+ provenanceLabel,
7
+ provenanceMarker
8
+ } from "./chunk-3LKTJJ75.js";
9
+
10
+ // src/mcp-server.ts
11
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
12
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
14
+ var MAX_MESSAGE_CHARS = 1e4;
15
+ var TOOL = {
16
+ name: "assistant_chat",
17
+ description: "Send one message to the assistant. It acts for a person with the scopes the host gave it. A risky action runs only if the host pre-approved that exact call. The reply says it is machine-made.",
18
+ inputSchema: {
19
+ type: "object",
20
+ properties: { message: { type: "string", minLength: 1, maxLength: MAX_MESSAGE_CHARS, description: "What to ask the assistant." } },
21
+ required: ["message"],
22
+ additionalProperties: false
23
+ },
24
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
25
+ };
26
+ var fail = (text) => ({ isError: true, content: [{ type: "text", text }] });
27
+ function createAssistantMcpServer(setup, shutdown) {
28
+ const server = new Server({ name: "agentic-assistant", version: VERSION }, { capabilities: { tools: {} } });
29
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [TOOL] }));
30
+ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
31
+ if (request.params.name !== TOOL.name) return fail(`unknown tool "${request.params.name}"`);
32
+ const message = request.params.arguments?.message;
33
+ if (typeof message !== "string" || message.trim() === "" || message.length > MAX_MESSAGE_CHARS) {
34
+ return fail(`message must be a text of 1 to ${MAX_MESSAGE_CHARS} characters`);
35
+ }
36
+ const call = new AbortController();
37
+ const stop = () => call.abort();
38
+ extra.signal.addEventListener("abort", stop, { once: true });
39
+ shutdown?.addEventListener("abort", stop, { once: true });
40
+ if (extra.signal.aborted || shutdown?.aborted) stop();
41
+ let reply = "";
42
+ const toolCalls = [];
43
+ let outcome;
44
+ for await (const event of runChat(setup, [{ role: "user", content: [{ type: "text", text: message }] }], call.signal)) {
45
+ if (event.type === "text_delta") reply += event.text;
46
+ else if (event.type === "turn_start") reply = "";
47
+ else if (event.type === "tool_result") toolCalls.push({ name: event.name, isError: event.isError, ...event.denied ? { denied: event.denied } : {}, ...event.provenance ? { provenance: event.provenance } : {} });
48
+ else if (event.type === "done") outcome = { reason: event.reason, totals: event.totals, provenance: event.provenance };
49
+ }
50
+ extra.signal.removeEventListener("abort", stop);
51
+ shutdown?.removeEventListener("abort", stop);
52
+ const provenance = outcome?.provenance;
53
+ const result = {
54
+ machineMade: true,
55
+ reply,
56
+ reason: outcome?.reason ?? "unknown",
57
+ toolCalls,
58
+ totals: outcome?.totals,
59
+ ...provenance ? { provenance, label: provenanceLabel(provenance), marker: provenanceMarker(provenance) } : {}
60
+ };
61
+ return { ...outcome?.reason === "end_turn" ? {} : { isError: true }, content: [{ type: "text", text: JSON.stringify(result) }] };
62
+ });
63
+ return server;
64
+ }
65
+ async function startAssistantMcp(setup) {
66
+ const shutdown = new AbortController();
67
+ const server = createAssistantMcpServer(setup, shutdown.signal);
68
+ const ended = new Promise((resolve) => {
69
+ server.onclose = resolve;
70
+ process.stdin.once("end", resolve);
71
+ process.stdin.once("close", resolve);
72
+ });
73
+ await server.connect(new StdioServerTransport());
74
+ await ended;
75
+ shutdown.abort();
76
+ await server.close().catch(() => void 0);
77
+ }
78
+
79
+ export {
80
+ createAssistantMcpServer,
81
+ startAssistantMcp
82
+ };
package/dist/cli.d.ts CHANGED
@@ -1,4 +1,29 @@
1
1
  #!/usr/bin/env node
2
- declare function runCli(argv: string[], out?: (line: string) => void, err?: (line: string) => void): number;
2
+ import { C as ChatConfig } from './chat-CXbhePC5.js';
3
+ import './loop-D4qWbRu6.js';
4
+ import './types-DGH8FS56.js';
3
5
 
4
- export { runCli };
6
+ declare class UsageError extends Error {
7
+ constructor(message: string);
8
+ }
9
+ interface Parsed {
10
+ command: "chat" | "mcp" | "help" | "version";
11
+ config?: ChatConfig;
12
+ message?: string;
13
+ json: boolean;
14
+ }
15
+ declare function parseCli(argv: string[]): Parsed;
16
+ interface CliIo {
17
+ /** Final answer text and JSON events go here. */
18
+ out(text: string): void;
19
+ /** Progress, tool lines, and the summary go here. */
20
+ err(line: string): void;
21
+ env: Record<string, string | undefined>;
22
+ readFile(path: string): string;
23
+ readStdin(): Promise<string>;
24
+ isTTY: boolean;
25
+ ask(question: string): Promise<string>;
26
+ }
27
+ declare function runCli(argv: string[], io?: CliIo): Promise<number>;
28
+
29
+ export { type CliIo, type Parsed, UsageError, parseCli, runCli };