agentic-assistant 0.3.0 → 0.5.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/CHANGELOG.md +37 -0
- package/README.md +20 -3
- package/SKILL.md +2 -1
- package/dist/{chat-CXbhePC5.d.ts → chat-CGITyrxg.d.ts} +1 -1
- package/dist/{chunk-3X6KU636.js → chunk-KSVH5NNA.js} +58 -12
- package/dist/{chunk-GLU222OQ.js → chunk-PRQTL5VS.js} +24 -27
- package/dist/chunk-Q3GGWNW5.js +167 -0
- package/dist/chunk-QTIEDY3V.js +196 -0
- package/dist/{chunk-3LKTJJ75.js → chunk-RHGVOR6N.js} +1 -1
- package/dist/{chunk-DXTIEMOT.js → chunk-XSRGG7SB.js} +1 -1
- package/dist/cli.d.ts +24 -3
- package/dist/cli.js +64 -9
- package/dist/client.d.ts +2 -2
- package/dist/client.js +4 -191
- package/dist/demo.js +10 -5
- package/dist/index.d.ts +14 -6
- package/dist/index.js +5 -4
- package/dist/{loop-D4qWbRu6.d.ts → loop-B6aowbyz.d.ts} +1 -1
- package/dist/mcp-server-U3UQIB7H.js +13 -0
- package/dist/react.css +18 -15
- package/dist/react.d.ts +1 -1
- package/dist/worker.d.ts +10 -2
- package/dist/worker.js +2 -2
- package/llms.txt +2 -2
- package/package.json +7 -2
- package/dist/chunk-T5WVSGYQ.js +0 -82
- package/dist/mcp-server-ZPCPYMCT.js +0 -12
|
@@ -0,0 +1,196 @@
|
|
|
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
|
+
}
|
|
192
|
+
|
|
193
|
+
export {
|
|
194
|
+
AssistantApiError,
|
|
195
|
+
createClient
|
|
196
|
+
};
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,14 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as ChatConfig } from './chat-
|
|
3
|
-
import './loop-
|
|
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
|
-
|
|
8
|
+
runChat,
|
|
9
|
+
runRemote
|
|
10
|
+
} from "./chunk-KSVH5NNA.js";
|
|
8
11
|
import "./chunk-HYT732TN.js";
|
|
9
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-XSRGG7SB.js";
|
|
10
13
|
import {
|
|
11
14
|
TOOL_RISKS,
|
|
12
15
|
VERSION,
|
|
13
16
|
printable,
|
|
14
17
|
provenanceLabel
|
|
15
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-RHGVOR6N.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
|
|
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,7 +128,26 @@ 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
|
-
|
|
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
|
+
}
|
|
110
151
|
if (!values.tools) throw new UsageError("--tools is required");
|
|
111
152
|
if (!values["tool-scope"]) throw new UsageError("--tool-scope is required");
|
|
112
153
|
const risk = values["tool-risk"] ?? "risky";
|
|
@@ -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-
|
|
276
|
-
await startAssistantMcp(setup);
|
|
330
|
+
const { startAssistantMcp } = await import("./mcp-server-U3UQIB7H.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 {
|
|
2
|
-
export {
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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-
|
|
4
|
+
} from "./chunk-PRQTL5VS.js";
|
|
5
5
|
import {
|
|
6
6
|
AnthropicProvider
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-XSRGG7SB.js";
|
|
8
8
|
import {
|
|
9
9
|
ToolRegistry,
|
|
10
|
-
VERSION
|
|
11
|
-
|
|
10
|
+
VERSION,
|
|
11
|
+
hasScope
|
|
12
|
+
} from "./chunk-RHGVOR6N.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'";
|
|
@@ -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
|
-
|
|
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
|
}
|