skydive-cli 0.1.0-beta.106 → 0.1.0-beta.122
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/js/bin.mjs
CHANGED
|
@@ -11,9 +11,27 @@ import { spawnSync } from "node:child_process";
|
|
|
11
11
|
import { createHash } from "node:crypto";
|
|
12
12
|
import fs from "node:fs";
|
|
13
13
|
import zlib from "node:zlib";
|
|
14
|
+
import { createParser } from "eventsource-parser";
|
|
14
15
|
|
|
16
|
+
//#region \0rolldown/runtime.js
|
|
17
|
+
var __defProp = Object.defineProperty;
|
|
18
|
+
var __exportAll = (all, no_symbols) => {
|
|
19
|
+
let target = {};
|
|
20
|
+
for (var name in all) {
|
|
21
|
+
__defProp(target, name, {
|
|
22
|
+
get: all[name],
|
|
23
|
+
enumerable: true
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (!no_symbols) {
|
|
27
|
+
__defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
28
|
+
}
|
|
29
|
+
return target;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
15
33
|
//#region package.json
|
|
16
|
-
var version$1 = "0.1.0-beta.
|
|
34
|
+
var version$1 = "0.1.0-beta.122";
|
|
17
35
|
|
|
18
36
|
//#endregion
|
|
19
37
|
//#region src/types.ts
|
|
@@ -553,7 +571,7 @@ async function loginWithDevice({ appUrl, openBrowser = true }) {
|
|
|
553
571
|
let intervalMs = (code.interval ?? 5) * 1e3 || DEFAULT_INTERVAL_MS;
|
|
554
572
|
const expiresAt = Date.now() + code.expires_in * 1e3;
|
|
555
573
|
while (Date.now() < expiresAt) {
|
|
556
|
-
await sleep(intervalMs);
|
|
574
|
+
await sleep$1(intervalMs);
|
|
557
575
|
const result = await pollDeviceToken({
|
|
558
576
|
appUrl,
|
|
559
577
|
deviceCode: code.device_code,
|
|
@@ -601,7 +619,7 @@ function formatUserCode(code) {
|
|
|
601
619
|
if (code.length !== 8) return code;
|
|
602
620
|
return `${code.slice(0, 4)}-${code.slice(4)}`;
|
|
603
621
|
}
|
|
604
|
-
function sleep(ms) {
|
|
622
|
+
function sleep$1(ms) {
|
|
605
623
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
606
624
|
}
|
|
607
625
|
|
|
@@ -794,7 +812,7 @@ function requireClient$2(argv) {
|
|
|
794
812
|
}
|
|
795
813
|
return new SkydiveApiClient(result.value);
|
|
796
814
|
}
|
|
797
|
-
const listCommand$
|
|
815
|
+
const listCommand$4 = {
|
|
798
816
|
command: "list",
|
|
799
817
|
describe: "List agents",
|
|
800
818
|
builder: (y) => y.option("limit", {
|
|
@@ -824,17 +842,50 @@ const listCommand$3 = {
|
|
|
824
842
|
console.log("No agents found.");
|
|
825
843
|
return;
|
|
826
844
|
}
|
|
827
|
-
|
|
845
|
+
const { headers, rows } = buildAgentTable(agents);
|
|
846
|
+
printTable(headers, rows);
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
const DESCRIPTION_MAX = 48;
|
|
850
|
+
/**
|
|
851
|
+
* Build the `agents list` table. A Description column is added only when at
|
|
852
|
+
* least one agent actually has a description, so an all-empty column doesn't
|
|
853
|
+
* add noise for accounts that never set them. Descriptions can be long, so
|
|
854
|
+
* they are truncated to keep one verbose agent from blowing out the width.
|
|
855
|
+
*/
|
|
856
|
+
function buildAgentTable(agents) {
|
|
857
|
+
if (agents.some((a) => (a.description ?? "").trim().length > 0)) return {
|
|
858
|
+
headers: [
|
|
828
859
|
"Name",
|
|
860
|
+
"Description",
|
|
829
861
|
"URL",
|
|
830
862
|
"Model"
|
|
831
|
-
],
|
|
863
|
+
],
|
|
864
|
+
rows: agents.map((a) => [
|
|
832
865
|
a.name,
|
|
866
|
+
truncate(a.description ?? "", DESCRIPTION_MAX),
|
|
833
867
|
a.url ?? "-",
|
|
834
868
|
a.model ?? "default"
|
|
835
|
-
])
|
|
836
|
-
}
|
|
837
|
-
|
|
869
|
+
])
|
|
870
|
+
};
|
|
871
|
+
return {
|
|
872
|
+
headers: [
|
|
873
|
+
"Name",
|
|
874
|
+
"URL",
|
|
875
|
+
"Model"
|
|
876
|
+
],
|
|
877
|
+
rows: agents.map((a) => [
|
|
878
|
+
a.name,
|
|
879
|
+
a.url ?? "-",
|
|
880
|
+
a.model ?? "default"
|
|
881
|
+
])
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
function truncate(value, max) {
|
|
885
|
+
const trimmed = value.trim();
|
|
886
|
+
if (trimmed.length <= max) return trimmed || "-";
|
|
887
|
+
return `${trimmed.slice(0, max - 1)}\u2026`;
|
|
888
|
+
}
|
|
838
889
|
const getCommand = {
|
|
839
890
|
command: "get <id>",
|
|
840
891
|
describe: "Get agent details",
|
|
@@ -905,7 +956,7 @@ const createCommand$1 = {
|
|
|
905
956
|
const agentsCommand = {
|
|
906
957
|
command: "agents",
|
|
907
958
|
describe: "Manage agents",
|
|
908
|
-
builder: (y) => y.command(listCommand$
|
|
959
|
+
builder: (y) => y.command(listCommand$4).command(getCommand).command(createCommand$1).demandCommand(1, "Specify a subcommand: list, get, create"),
|
|
909
960
|
handler: () => {}
|
|
910
961
|
};
|
|
911
962
|
|
|
@@ -919,7 +970,7 @@ function requireClient$1(argv) {
|
|
|
919
970
|
}
|
|
920
971
|
return new SkydiveApiClient(result.value);
|
|
921
972
|
}
|
|
922
|
-
const listCommand$
|
|
973
|
+
const listCommand$3 = {
|
|
923
974
|
command: "list",
|
|
924
975
|
describe: "List API keys for an agent",
|
|
925
976
|
handler: async (argv) => {
|
|
@@ -1008,7 +1059,7 @@ const keysCommand = {
|
|
|
1008
1059
|
type: "string",
|
|
1009
1060
|
demandOption: true,
|
|
1010
1061
|
describe: "Agent ID"
|
|
1011
|
-
}).command(listCommand$
|
|
1062
|
+
}).command(listCommand$3).command(createCommand).command(revokeCommand).demandCommand(1, "Specify a subcommand: list, create, revoke"),
|
|
1012
1063
|
handler: () => {}
|
|
1013
1064
|
};
|
|
1014
1065
|
|
|
@@ -1023,12 +1074,12 @@ function requireClient(argv) {
|
|
|
1023
1074
|
return new SkydiveApiClient(result.value);
|
|
1024
1075
|
}
|
|
1025
1076
|
/** Read all of stdin as UTF-8, trimming a single trailing newline. */
|
|
1026
|
-
async function readStdin() {
|
|
1077
|
+
async function readStdin$1() {
|
|
1027
1078
|
const chunks = [];
|
|
1028
1079
|
for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1029
1080
|
return Buffer.concat(chunks).toString("utf8").replace(/\n$/, "");
|
|
1030
1081
|
}
|
|
1031
|
-
const listCommand$
|
|
1082
|
+
const listCommand$2 = {
|
|
1032
1083
|
command: "list",
|
|
1033
1084
|
describe: "List secret names for an agent (values are never shown)",
|
|
1034
1085
|
handler: async (argv) => {
|
|
@@ -1067,7 +1118,7 @@ const setCommand = {
|
|
|
1067
1118
|
printError("No value given. Pass it as an argument or pipe it on stdin, e.g. `echo -n \"$TOKEN\" | skydive secrets set MY_KEY --agent-id <id>`.");
|
|
1068
1119
|
process.exit(1);
|
|
1069
1120
|
}
|
|
1070
|
-
value = await readStdin();
|
|
1121
|
+
value = await readStdin$1();
|
|
1071
1122
|
}
|
|
1072
1123
|
if (!value) {
|
|
1073
1124
|
printError("Empty secret value.");
|
|
@@ -1114,7 +1165,7 @@ const secretsCommand = {
|
|
|
1114
1165
|
type: "string",
|
|
1115
1166
|
demandOption: true,
|
|
1116
1167
|
describe: "Agent ID"
|
|
1117
|
-
}).command(listCommand$
|
|
1168
|
+
}).command(listCommand$2).command(setCommand).command(rmCommand).demandCommand(1, "Specify a subcommand: list, set, rm"),
|
|
1118
1169
|
handler: () => {}
|
|
1119
1170
|
};
|
|
1120
1171
|
|
|
@@ -2101,7 +2152,7 @@ const chatCommand = {
|
|
|
2101
2152
|
printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
|
|
2102
2153
|
process.exit(1);
|
|
2103
2154
|
}
|
|
2104
|
-
const { runChat } = await import("./boot-
|
|
2155
|
+
const { runChat } = await import("./boot-BvkqTAxQ.mjs");
|
|
2105
2156
|
await runChat({
|
|
2106
2157
|
appUrl,
|
|
2107
2158
|
sessionToken: session.value.sessionToken,
|
|
@@ -2119,7 +2170,7 @@ async function runPrintMode({ argv, appUrl }) {
|
|
|
2119
2170
|
printError(`${session.error.message} For non-interactive use, run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
|
|
2120
2171
|
process.exit(1);
|
|
2121
2172
|
}
|
|
2122
|
-
const { runPrint, readStdin } = await
|
|
2173
|
+
const { runPrint, readStdin } = await Promise.resolve().then(() => print_exports);
|
|
2123
2174
|
let prompt = (argv.print ?? "").trim();
|
|
2124
2175
|
if (!prompt) {
|
|
2125
2176
|
if (process.stdin.isTTY) {
|
|
@@ -2149,15 +2200,875 @@ async function runPrintMode({ argv, appUrl }) {
|
|
|
2149
2200
|
}
|
|
2150
2201
|
|
|
2151
2202
|
//#endregion
|
|
2152
|
-
//#region src/
|
|
2203
|
+
//#region src/chat/api/rest.ts
|
|
2204
|
+
var HttpError = class extends Error {
|
|
2205
|
+
constructor(status, body) {
|
|
2206
|
+
super(`HTTP ${status}: ${body.slice(0, 200)}`);
|
|
2207
|
+
this.status = status;
|
|
2208
|
+
this.body = body;
|
|
2209
|
+
this.name = "HttpError";
|
|
2210
|
+
}
|
|
2211
|
+
};
|
|
2212
|
+
const MAX_STREAM_RECONNECTS = 5;
|
|
2213
|
+
function createRestClient({ appUrl, sessionToken }) {
|
|
2214
|
+
const baseHeaders = {
|
|
2215
|
+
authorization: `Bearer ${sessionToken}`,
|
|
2216
|
+
accept: "application/json"
|
|
2217
|
+
};
|
|
2218
|
+
async function get(path, schema) {
|
|
2219
|
+
const res = await fetch(`${appUrl}${path}`, { headers: baseHeaders });
|
|
2220
|
+
if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
|
|
2221
|
+
return schema.parse(await res.json());
|
|
2222
|
+
}
|
|
2223
|
+
async function post(path, body, schema, method = "POST") {
|
|
2224
|
+
const res = await fetch(`${appUrl}${path}`, {
|
|
2225
|
+
method,
|
|
2226
|
+
headers: {
|
|
2227
|
+
...baseHeaders,
|
|
2228
|
+
"content-type": "application/json"
|
|
2229
|
+
},
|
|
2230
|
+
body: JSON.stringify(body)
|
|
2231
|
+
});
|
|
2232
|
+
if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
|
|
2233
|
+
return schema.parse(await res.json());
|
|
2234
|
+
}
|
|
2235
|
+
async function del(path) {
|
|
2236
|
+
const res = await fetch(`${appUrl}${path}`, {
|
|
2237
|
+
method: "DELETE",
|
|
2238
|
+
headers: baseHeaders
|
|
2239
|
+
});
|
|
2240
|
+
if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
|
|
2241
|
+
}
|
|
2242
|
+
return {
|
|
2243
|
+
listAgents: async ({ scope, onPage }) => {
|
|
2244
|
+
const all = [];
|
|
2245
|
+
let cursor;
|
|
2246
|
+
const maxAgents = 2e3;
|
|
2247
|
+
do {
|
|
2248
|
+
const params = new URLSearchParams({
|
|
2249
|
+
limit: "100",
|
|
2250
|
+
scope,
|
|
2251
|
+
sort: "mine_first_usage",
|
|
2252
|
+
includeStats: "false"
|
|
2253
|
+
});
|
|
2254
|
+
if (cursor) params.set("cursor", cursor);
|
|
2255
|
+
const page = await get(`/api/v1/agents?${params.toString()}`, listAgentsResponseSchema);
|
|
2256
|
+
all.push(...page.agents);
|
|
2257
|
+
cursor = page.nextCursor ?? void 0;
|
|
2258
|
+
onPage?.([...all]);
|
|
2259
|
+
} while (cursor && all.length < maxAgents);
|
|
2260
|
+
return all;
|
|
2261
|
+
},
|
|
2262
|
+
createAgent: async ({ name }) => {
|
|
2263
|
+
const { agent } = await post("/api/v1/agents", { name }, createAgentResponseSchema);
|
|
2264
|
+
return agent;
|
|
2265
|
+
},
|
|
2266
|
+
getConversation: async ({ conversationId }) => {
|
|
2267
|
+
const { conversation } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}`, getConversationResponseSchema);
|
|
2268
|
+
return conversation;
|
|
2269
|
+
},
|
|
2270
|
+
listModels: async () => {
|
|
2271
|
+
const { models } = await get("/api/v1/models", listModelsResponseSchema);
|
|
2272
|
+
return models;
|
|
2273
|
+
},
|
|
2274
|
+
updateAgentModel: async ({ agentId, model }) => {
|
|
2275
|
+
const { agent } = await post(`/api/v1/agents/${encodeURIComponent(agentId)}`, { model }, updateAgentResponseSchema, "PATCH");
|
|
2276
|
+
return { model: agent.model ?? null };
|
|
2277
|
+
},
|
|
2278
|
+
listConversations: async ({ agentId, limit }) => {
|
|
2279
|
+
const all = [];
|
|
2280
|
+
const maxConversations = limit ?? 5e3;
|
|
2281
|
+
let cursor;
|
|
2282
|
+
do {
|
|
2283
|
+
const remaining = maxConversations - all.length;
|
|
2284
|
+
const params = new URLSearchParams({
|
|
2285
|
+
agentId,
|
|
2286
|
+
includeTotal: "false"
|
|
2287
|
+
});
|
|
2288
|
+
params.set("limit", String(Math.min(remaining, 100)));
|
|
2289
|
+
if (cursor) params.set("cursor", cursor);
|
|
2290
|
+
const page = await get(`/api/v1/conversations?${params.toString()}`, listConversationsResponseSchema);
|
|
2291
|
+
all.push(...page.conversations);
|
|
2292
|
+
cursor = page.nextCursor ?? void 0;
|
|
2293
|
+
} while (cursor && all.length < maxConversations);
|
|
2294
|
+
return limit ? all.slice(0, limit) : all;
|
|
2295
|
+
},
|
|
2296
|
+
listMessages: async ({ conversationId }) => {
|
|
2297
|
+
const { messages } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}/messages`, listMessagesResponseSchema);
|
|
2298
|
+
return messages;
|
|
2299
|
+
},
|
|
2300
|
+
getRecap: async ({ conversationId }) => {
|
|
2301
|
+
const { recap } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}/recap`, recapResponseSchema);
|
|
2302
|
+
return recap?.text ?? null;
|
|
2303
|
+
},
|
|
2304
|
+
uploadAttachment: async ({ agentId, fileName, mediaType, data }) => {
|
|
2305
|
+
const size = data.byteLength;
|
|
2306
|
+
const presign = await post("/api/v1/attachments/presign", {
|
|
2307
|
+
agentId,
|
|
2308
|
+
fileName,
|
|
2309
|
+
mediaType,
|
|
2310
|
+
size
|
|
2311
|
+
}, presignResponseSchema);
|
|
2312
|
+
const putRes = await fetch(presign.uploadUrl, {
|
|
2313
|
+
method: "PUT",
|
|
2314
|
+
headers: { "content-type": mediaType },
|
|
2315
|
+
body: new Uint8Array(data)
|
|
2316
|
+
});
|
|
2317
|
+
if (!putRes.ok) throw new HttpError(putRes.status, await putRes.text().catch(() => ""));
|
|
2318
|
+
const finalized = await post(`/api/v1/attachments/${encodeURIComponent(presign.id)}/finalize`, {
|
|
2319
|
+
agentId,
|
|
2320
|
+
fileName: presign.fileName,
|
|
2321
|
+
mediaType: presign.mediaType,
|
|
2322
|
+
size
|
|
2323
|
+
}, finalizeResponseSchema);
|
|
2324
|
+
return {
|
|
2325
|
+
id: presign.id,
|
|
2326
|
+
fileName: finalized.fileName,
|
|
2327
|
+
mediaType: finalized.mediaType,
|
|
2328
|
+
sizeBytes: finalized.sizeBytes ?? size
|
|
2329
|
+
};
|
|
2330
|
+
},
|
|
2331
|
+
deleteConversation: async ({ conversationId }) => {
|
|
2332
|
+
await del(`/api/v1/conversations/${encodeURIComponent(conversationId)}`);
|
|
2333
|
+
},
|
|
2334
|
+
sendMessage: async ({ clientSurface, ...input }) => post("/api/v1/chat/send", {
|
|
2335
|
+
...input,
|
|
2336
|
+
clientSurface
|
|
2337
|
+
}, sendResultSchema),
|
|
2338
|
+
activeRun: async ({ conversationId }) => {
|
|
2339
|
+
const { run } = await get(`/api/v1/chat/active-run?${new URLSearchParams({ conversationId }).toString()}`, activeRunResponseSchema);
|
|
2340
|
+
return run;
|
|
2341
|
+
},
|
|
2342
|
+
cancelRun: async ({ runId }) => {
|
|
2343
|
+
await post(`/api/v1/chat/runs/${encodeURIComponent(runId)}/cancel`, {}, z.object({ ok: z.boolean() }));
|
|
2344
|
+
},
|
|
2345
|
+
cancelSteer: async ({ directiveId }) => {
|
|
2346
|
+
await post(`/api/v1/chat/steer/${encodeURIComponent(directiveId)}/cancel`, {}, z.object({ ok: z.boolean() }));
|
|
2347
|
+
},
|
|
2348
|
+
oauthConnect: async (input) => {
|
|
2349
|
+
const { connectLink } = await post("/api/v1/oauth/connect", input, oauthConnectResponseSchema);
|
|
2350
|
+
return { connectLink };
|
|
2351
|
+
},
|
|
2352
|
+
externalOauthConnect: async (input) => {
|
|
2353
|
+
const { authorizationUrl } = await post("/api/v1/external-oauth/connect", input, externalOauthConnectResponseSchema);
|
|
2354
|
+
return { authorizationUrl: authorizationUrl ?? null };
|
|
2355
|
+
},
|
|
2356
|
+
fulfillCredential: async ({ url, body }) => {
|
|
2357
|
+
const target = new URL(url, appUrl).toString();
|
|
2358
|
+
const res = await fetch(target, {
|
|
2359
|
+
method: "POST",
|
|
2360
|
+
headers: {
|
|
2361
|
+
...baseHeaders,
|
|
2362
|
+
"content-type": "application/json"
|
|
2363
|
+
},
|
|
2364
|
+
body: JSON.stringify(body)
|
|
2365
|
+
});
|
|
2366
|
+
if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
|
|
2367
|
+
},
|
|
2368
|
+
streamRun: async ({ runId, signal, onEvent }) => {
|
|
2369
|
+
let lastEventId = null;
|
|
2370
|
+
let finished = false;
|
|
2371
|
+
let reconnects = 0;
|
|
2372
|
+
for (;;) {
|
|
2373
|
+
if (signal.aborted) return;
|
|
2374
|
+
try {
|
|
2375
|
+
const headers = {
|
|
2376
|
+
authorization: `Bearer ${sessionToken}`,
|
|
2377
|
+
accept: "text/event-stream"
|
|
2378
|
+
};
|
|
2379
|
+
if (lastEventId) headers["last-event-id"] = lastEventId;
|
|
2380
|
+
const res = await fetch(`${appUrl}/api/v1/chat/runs/${encodeURIComponent(runId)}/stream`, {
|
|
2381
|
+
headers,
|
|
2382
|
+
signal
|
|
2383
|
+
});
|
|
2384
|
+
if (!res.ok || !res.body) throw new HttpError(res.status, await res.text().catch(() => ""));
|
|
2385
|
+
reconnects = 0;
|
|
2386
|
+
const parser = createParser({ onEvent: (message) => {
|
|
2387
|
+
if (message.id) lastEventId = message.id;
|
|
2388
|
+
if (message.event === "error") {
|
|
2389
|
+
const { error } = streamErrorSchema.parse(JSON.parse(message.data));
|
|
2390
|
+
throw new Error(error);
|
|
2391
|
+
}
|
|
2392
|
+
const event = runStreamEventSchema.parse(JSON.parse(message.data));
|
|
2393
|
+
if (event.kind === "finished") finished = true;
|
|
2394
|
+
onEvent(event.kind === "finished" ? {
|
|
2395
|
+
...event,
|
|
2396
|
+
error: event.error ?? null
|
|
2397
|
+
} : event);
|
|
2398
|
+
} });
|
|
2399
|
+
const decoder = new TextDecoder();
|
|
2400
|
+
const reader = res.body.getReader();
|
|
2401
|
+
try {
|
|
2402
|
+
for (;;) {
|
|
2403
|
+
const { done, value } = await reader.read();
|
|
2404
|
+
if (done) break;
|
|
2405
|
+
parser.feed(decoder.decode(value, { stream: true }));
|
|
2406
|
+
if (finished) return;
|
|
2407
|
+
}
|
|
2408
|
+
} finally {
|
|
2409
|
+
try {
|
|
2410
|
+
await reader.cancel();
|
|
2411
|
+
} catch (_error) {}
|
|
2412
|
+
}
|
|
2413
|
+
} catch (err) {
|
|
2414
|
+
if (signal.aborted) return;
|
|
2415
|
+
if (err instanceof HttpError && err.status >= 400 && err.status < 500) throw err;
|
|
2416
|
+
reconnects += 1;
|
|
2417
|
+
if (reconnects > MAX_STREAM_RECONNECTS) throw err;
|
|
2418
|
+
await sleep(Math.min(500 * 2 ** reconnects, 5e3));
|
|
2419
|
+
continue;
|
|
2420
|
+
}
|
|
2421
|
+
if (finished) return;
|
|
2422
|
+
reconnects += 1;
|
|
2423
|
+
if (reconnects > MAX_STREAM_RECONNECTS) throw new Error("run stream ended unexpectedly");
|
|
2424
|
+
await sleep(Math.min(500 * 2 ** reconnects, 5e3));
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
function sleep(ms) {
|
|
2430
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2431
|
+
}
|
|
2432
|
+
const agentSummarySchema = z.object({
|
|
2433
|
+
id: z.string().uuid(),
|
|
2434
|
+
name: z.string(),
|
|
2435
|
+
slug: z.string().nullable().optional(),
|
|
2436
|
+
title: z.string().nullable().optional(),
|
|
2437
|
+
description: z.string().nullable().optional(),
|
|
2438
|
+
createdAt: z.string(),
|
|
2439
|
+
creatorName: z.string().nullable().optional(),
|
|
2440
|
+
model: z.string().nullable().optional(),
|
|
2441
|
+
modelLocked: z.boolean().optional()
|
|
2442
|
+
});
|
|
2443
|
+
const platformModelSchema = z.object({
|
|
2444
|
+
id: z.string(),
|
|
2445
|
+
displayName: z.string(),
|
|
2446
|
+
providerDisplay: z.string().optional(),
|
|
2447
|
+
reasoning: z.boolean().optional(),
|
|
2448
|
+
compliant: z.boolean().optional()
|
|
2449
|
+
}).passthrough();
|
|
2450
|
+
const listModelsResponseSchema = z.object({ models: z.array(platformModelSchema) });
|
|
2451
|
+
const updateAgentResponseSchema = z.object({ agent: z.object({ model: z.string().nullable().optional() }).passthrough() });
|
|
2452
|
+
const listAgentsResponseSchema = z.object({
|
|
2453
|
+
agents: z.array(agentSummarySchema),
|
|
2454
|
+
nextCursor: z.string().nullable().optional(),
|
|
2455
|
+
totalCount: z.number().nullable().optional()
|
|
2456
|
+
});
|
|
2457
|
+
const createAgentResponseSchema = z.object({ agent: agentSummarySchema });
|
|
2458
|
+
const conversationSummarySchema = z.object({
|
|
2459
|
+
id: z.string().uuid(),
|
|
2460
|
+
title: z.string().nullable(),
|
|
2461
|
+
createdAt: z.string(),
|
|
2462
|
+
updatedAt: z.string(),
|
|
2463
|
+
preview: z.string().nullable(),
|
|
2464
|
+
channel: z.string().nullable(),
|
|
2465
|
+
channelLabel: z.string().nullable(),
|
|
2466
|
+
agent: z.object({
|
|
2467
|
+
id: z.string().uuid(),
|
|
2468
|
+
name: z.string(),
|
|
2469
|
+
slug: z.string().nullable().optional(),
|
|
2470
|
+
title: z.string().nullable().optional()
|
|
2471
|
+
})
|
|
2472
|
+
});
|
|
2473
|
+
const conversationTitleSchema = z.object({
|
|
2474
|
+
id: z.string().uuid(),
|
|
2475
|
+
title: z.string().nullable()
|
|
2476
|
+
});
|
|
2477
|
+
const getConversationResponseSchema = z.object({ conversation: conversationTitleSchema });
|
|
2478
|
+
const listConversationsResponseSchema = z.object({
|
|
2479
|
+
conversations: z.array(conversationSummarySchema),
|
|
2480
|
+
nextCursor: z.string().nullable().optional(),
|
|
2481
|
+
totalCount: z.number().optional()
|
|
2482
|
+
});
|
|
2483
|
+
const uiMessagePartSchema = z.union([
|
|
2484
|
+
z.object({
|
|
2485
|
+
type: z.literal("text"),
|
|
2486
|
+
text: z.string()
|
|
2487
|
+
}),
|
|
2488
|
+
z.object({
|
|
2489
|
+
type: z.literal("reasoning"),
|
|
2490
|
+
text: z.string().optional()
|
|
2491
|
+
}),
|
|
2492
|
+
z.object({
|
|
2493
|
+
type: z.literal("dynamic-tool"),
|
|
2494
|
+
toolCallId: z.string(),
|
|
2495
|
+
toolName: z.string(),
|
|
2496
|
+
input: z.unknown().optional(),
|
|
2497
|
+
output: z.unknown().optional(),
|
|
2498
|
+
state: z.string().optional(),
|
|
2499
|
+
errorText: z.string().optional()
|
|
2500
|
+
}),
|
|
2501
|
+
z.object({ type: z.string() }).passthrough()
|
|
2502
|
+
]);
|
|
2503
|
+
const uiMessageSchema = z.object({
|
|
2504
|
+
id: z.string(),
|
|
2505
|
+
role: z.string(),
|
|
2506
|
+
parts: z.array(uiMessagePartSchema)
|
|
2507
|
+
});
|
|
2508
|
+
const recapResponseSchema = z.object({ recap: z.object({ text: z.string() }).nullable() });
|
|
2509
|
+
const listMessagesResponseSchema = z.object({ messages: z.array(uiMessageSchema) });
|
|
2510
|
+
const sendResultSchema = z.object({
|
|
2511
|
+
runId: z.string(),
|
|
2512
|
+
conversationId: z.string().uuid(),
|
|
2513
|
+
isNewConversation: z.boolean(),
|
|
2514
|
+
steered: z.boolean().optional(),
|
|
2515
|
+
directive: z.object({ id: z.string() }).passthrough().optional()
|
|
2516
|
+
});
|
|
2517
|
+
const presignResponseSchema = z.object({
|
|
2518
|
+
id: z.string(),
|
|
2519
|
+
uploadUrl: z.string(),
|
|
2520
|
+
fileName: z.string(),
|
|
2521
|
+
mediaType: z.string()
|
|
2522
|
+
});
|
|
2523
|
+
const finalizeResponseSchema = z.object({
|
|
2524
|
+
fileName: z.string(),
|
|
2525
|
+
mediaType: z.string(),
|
|
2526
|
+
sizeBytes: z.number().nullable().optional()
|
|
2527
|
+
});
|
|
2528
|
+
const activeRunResponseSchema = z.object({ run: z.object({ runId: z.string() }).nullable() });
|
|
2529
|
+
const oauthConnectResponseSchema = z.object({ connectLink: z.string() }).passthrough();
|
|
2530
|
+
const externalOauthConnectResponseSchema = z.object({ authorizationUrl: z.string().optional() }).passthrough();
|
|
2531
|
+
const runStreamEventSchema = z.union([z.object({
|
|
2532
|
+
kind: z.literal("chunk"),
|
|
2533
|
+
chunk: z.record(z.unknown())
|
|
2534
|
+
}), z.object({
|
|
2535
|
+
kind: z.literal("finished"),
|
|
2536
|
+
status: z.string(),
|
|
2537
|
+
error: z.string().nullish()
|
|
2538
|
+
})]);
|
|
2539
|
+
const streamErrorSchema = z.object({ error: z.string() });
|
|
2540
|
+
|
|
2541
|
+
//#endregion
|
|
2542
|
+
//#region src/chat/util.ts
|
|
2543
|
+
/** Narrowing helper for the many `unknown` payloads the chat stream and
|
|
2544
|
+
* tool inputs/outputs carry. A type predicate (not an `as` cast), so call
|
|
2545
|
+
* sites can read properties without asserting. */
|
|
2546
|
+
function isRecord(value) {
|
|
2547
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2548
|
+
}
|
|
2549
|
+
/** Best-effort message from an unknown thrown value. */
|
|
2550
|
+
function errorMessage(err) {
|
|
2551
|
+
return err instanceof Error ? err.message : String(err);
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
//#endregion
|
|
2555
|
+
//#region src/chat/tui/chat/card.ts
|
|
2556
|
+
const urlActionKinds = [
|
|
2557
|
+
"open_oauth",
|
|
2558
|
+
"open_external_oauth",
|
|
2559
|
+
"open_github_app",
|
|
2560
|
+
"submit_credential"
|
|
2561
|
+
];
|
|
2562
|
+
function isUrlActionKind(value) {
|
|
2563
|
+
return typeof value === "string" && urlActionKinds.includes(value);
|
|
2564
|
+
}
|
|
2565
|
+
function optionalString(value) {
|
|
2566
|
+
return typeof value === "string" && value ? value : null;
|
|
2567
|
+
}
|
|
2568
|
+
function bindStateKey(props) {
|
|
2569
|
+
const value = props.value;
|
|
2570
|
+
if (!isRecord(value)) return null;
|
|
2571
|
+
const pointer = value.$bindState;
|
|
2572
|
+
if (typeof pointer !== "string") return null;
|
|
2573
|
+
return pointer.startsWith("/") ? pointer.slice(1) : pointer;
|
|
2574
|
+
}
|
|
2575
|
+
function parseButton(element) {
|
|
2576
|
+
const props = isRecord(element.props) ? element.props : {};
|
|
2577
|
+
const label = optionalString(props.label) ?? "Connect";
|
|
2578
|
+
const on = isRecord(element.on) ? element.on : null;
|
|
2579
|
+
const press = on && isRecord(on.press) ? on.press : null;
|
|
2580
|
+
if (!press) return null;
|
|
2581
|
+
const params = isRecord(press.params) ? press.params : {};
|
|
2582
|
+
const primary = props.variant === "primary";
|
|
2583
|
+
if (press.action === "approve_portal_access") {
|
|
2584
|
+
const agentId = params.agentId;
|
|
2585
|
+
if (typeof agentId !== "string" || !agentId) return null;
|
|
2586
|
+
return {
|
|
2587
|
+
label,
|
|
2588
|
+
action: {
|
|
2589
|
+
kind: "grant_portal",
|
|
2590
|
+
agentId,
|
|
2591
|
+
deviceId: typeof params.deviceId === "string" && params.deviceId ? params.deviceId : null
|
|
2592
|
+
},
|
|
2593
|
+
primary
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
if (!isUrlActionKind(press.action)) return null;
|
|
2597
|
+
const url = params.url;
|
|
2598
|
+
if (typeof url !== "string" || !url) return null;
|
|
2599
|
+
return {
|
|
2600
|
+
label,
|
|
2601
|
+
action: {
|
|
2602
|
+
kind: press.action,
|
|
2603
|
+
url
|
|
2604
|
+
},
|
|
2605
|
+
primary
|
|
2606
|
+
};
|
|
2607
|
+
}
|
|
2608
|
+
function parseConnectCard(spec) {
|
|
2609
|
+
if (!isRecord(spec)) return null;
|
|
2610
|
+
const { root, elements } = spec;
|
|
2611
|
+
if (typeof root !== "string" || !isRecord(elements)) return null;
|
|
2612
|
+
const rootEl = elements[root];
|
|
2613
|
+
if (!isRecord(rootEl) || rootEl.type !== "Card") return null;
|
|
2614
|
+
const rootProps = isRecord(rootEl.props) ? rootEl.props : {};
|
|
2615
|
+
const title = optionalString(rootProps.title);
|
|
2616
|
+
if (!title) return null;
|
|
2617
|
+
const fields = [];
|
|
2618
|
+
const buttons = [];
|
|
2619
|
+
const children = Array.isArray(rootEl.children) ? rootEl.children : [];
|
|
2620
|
+
for (const childId of children) {
|
|
2621
|
+
if (typeof childId !== "string") continue;
|
|
2622
|
+
const el = elements[childId];
|
|
2623
|
+
if (!isRecord(el)) continue;
|
|
2624
|
+
if (el.type === "TextInput" || el.type === "SecretInput") {
|
|
2625
|
+
const props = isRecord(el.props) ? el.props : {};
|
|
2626
|
+
const key = bindStateKey(props);
|
|
2627
|
+
if (!key) continue;
|
|
2628
|
+
fields.push({
|
|
2629
|
+
key,
|
|
2630
|
+
label: optionalString(props.label) ?? key,
|
|
2631
|
+
placeholder: optionalString(props.placeholder),
|
|
2632
|
+
secret: el.type === "SecretInput"
|
|
2633
|
+
});
|
|
2634
|
+
continue;
|
|
2635
|
+
}
|
|
2636
|
+
if (el.type === "Button") {
|
|
2637
|
+
const button = parseButton(el);
|
|
2638
|
+
if (button) buttons.push(button);
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
const preferred = buttons.find((b) => b.primary) ?? buttons[0] ?? null;
|
|
2642
|
+
return {
|
|
2643
|
+
title,
|
|
2644
|
+
subtitle: optionalString(rootProps.subtitle),
|
|
2645
|
+
description: optionalString(rootProps.description),
|
|
2646
|
+
fields,
|
|
2647
|
+
button: preferred ? {
|
|
2648
|
+
label: preferred.label,
|
|
2649
|
+
action: preferred.action
|
|
2650
|
+
} : null,
|
|
2651
|
+
state: isRecord(spec.state) ? spec.state : {}
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
//#endregion
|
|
2656
|
+
//#region src/chat/tui/chat/card-actions.ts
|
|
2657
|
+
function safeUrl(url) {
|
|
2658
|
+
try {
|
|
2659
|
+
return new URL(url, "http://localhost");
|
|
2660
|
+
} catch (_error) {
|
|
2661
|
+
return null;
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
/**
|
|
2665
|
+
* Resolve a connect URL to an absolute one the OS can open in a browser.
|
|
2666
|
+
*
|
|
2667
|
+
* The server builds lazy connect links as server-relative paths (e.g.
|
|
2668
|
+
* `/api/v1/oauth/start/slack?connect_session_token=...`). The web client
|
|
2669
|
+
* resolves these against its own origin implicitly; the TUI runs outside a
|
|
2670
|
+
* browser, so `open()` on a scheme-less path is a silent no-op — the browser
|
|
2671
|
+
* never launches and the connect card sits in `launched` forever (the Slack
|
|
2672
|
+
* channel-connect deadlock). Resolve against `appUrl` first, mirroring the
|
|
2673
|
+
* `open_github_app` / `fulfillCredential` branches, so both the browser we
|
|
2674
|
+
* launch and the URL shown on the card are absolute. An already-absolute URL
|
|
2675
|
+
* passes through unchanged; if `appUrl` is missing we return the input as-is.
|
|
2676
|
+
*/
|
|
2677
|
+
function resolveConnectUrl(url, appUrl) {
|
|
2678
|
+
if (!appUrl) return url;
|
|
2679
|
+
try {
|
|
2680
|
+
return new URL(url, appUrl).toString();
|
|
2681
|
+
} catch (_error) {
|
|
2682
|
+
return url;
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
function parseOauthConnectParams(url) {
|
|
2686
|
+
const parsed = safeUrl(url);
|
|
2687
|
+
if (!parsed) return null;
|
|
2688
|
+
const integrationKey = parsed.searchParams.get("integration");
|
|
2689
|
+
const agentId = parsed.searchParams.get("agent_id");
|
|
2690
|
+
const authConfigId = parsed.searchParams.get("auth_config_id");
|
|
2691
|
+
const conversationId = parsed.searchParams.get("conversation_id");
|
|
2692
|
+
if (!integrationKey || !agentId || !authConfigId || !conversationId) return null;
|
|
2693
|
+
return {
|
|
2694
|
+
integrationKey,
|
|
2695
|
+
agentId,
|
|
2696
|
+
authConfigId,
|
|
2697
|
+
conversationId,
|
|
2698
|
+
scopes: parsed.searchParams.get("scopes")
|
|
2699
|
+
};
|
|
2700
|
+
}
|
|
2701
|
+
function parseExternalOauthConnectParams(url) {
|
|
2702
|
+
const parsed = safeUrl(url);
|
|
2703
|
+
if (!parsed) return null;
|
|
2704
|
+
const agentId = parsed.searchParams.get("agent_id");
|
|
2705
|
+
const conversationId = parsed.searchParams.get("conversation_id");
|
|
2706
|
+
const serverUrl = parsed.searchParams.get("server_url");
|
|
2707
|
+
if (!agentId || !conversationId || !serverUrl) return null;
|
|
2708
|
+
return {
|
|
2709
|
+
agentId,
|
|
2710
|
+
conversationId,
|
|
2711
|
+
serverUrl
|
|
2712
|
+
};
|
|
2713
|
+
}
|
|
2714
|
+
/**
|
|
2715
|
+
* A short human line for a failed card action. Fulfill/connect endpoints
|
|
2716
|
+
* return `{ error: string }` bodies (e.g. "already fulfilled") — prefer that
|
|
2717
|
+
* over the generic HttpError message.
|
|
2718
|
+
*/
|
|
2719
|
+
function cardActionErrorMessage(err) {
|
|
2720
|
+
if (err instanceof HttpError) {
|
|
2721
|
+
try {
|
|
2722
|
+
const parsed = JSON.parse(err.body);
|
|
2723
|
+
if (isRecord(parsed) && typeof parsed.error === "string") return parsed.error;
|
|
2724
|
+
} catch (_error) {}
|
|
2725
|
+
return `request failed (HTTP ${err.status})`;
|
|
2726
|
+
}
|
|
2727
|
+
return errorMessage(err);
|
|
2728
|
+
}
|
|
2729
|
+
const MASK_CHAR = "•";
|
|
2730
|
+
/**
|
|
2731
|
+
* Recover the real secret from the masked input's displayed text. The input
|
|
2732
|
+
* is controlled: after every edit we render bullets, which forces the cursor
|
|
2733
|
+
* to the end, so the next edit is always a tail edit — the displayed text is
|
|
2734
|
+
* some prefix of the old mask (kept characters) followed by newly typed or
|
|
2735
|
+
* pasted plaintext. Characters beyond the retained bullets are the new tail.
|
|
2736
|
+
*/
|
|
2737
|
+
function reconcileMaskedInput(previousValue, displayed) {
|
|
2738
|
+
let kept = 0;
|
|
2739
|
+
while (kept < displayed.length && kept < previousValue.length && displayed[kept] === MASK_CHAR) kept++;
|
|
2740
|
+
return previousValue.slice(0, kept) + displayed.slice(kept);
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
//#endregion
|
|
2744
|
+
//#region src/chat/connect-cards.ts
|
|
2745
|
+
/**
|
|
2746
|
+
* Turn a parsed ConnectCard into the headless summary, resolving any relative
|
|
2747
|
+
* connect URL against the app origin so the emitted URL is directly openable.
|
|
2748
|
+
*/
|
|
2749
|
+
function summarizeConnectCard(card, appUrl) {
|
|
2750
|
+
const base = {
|
|
2751
|
+
title: card.title,
|
|
2752
|
+
subtitle: card.subtitle,
|
|
2753
|
+
description: card.description
|
|
2754
|
+
};
|
|
2755
|
+
const button = card.button;
|
|
2756
|
+
if (!button) return {
|
|
2757
|
+
...base,
|
|
2758
|
+
action: { kind: "unsupported" }
|
|
2759
|
+
};
|
|
2760
|
+
const act = button.action;
|
|
2761
|
+
switch (act.kind) {
|
|
2762
|
+
case "open_oauth":
|
|
2763
|
+
case "open_external_oauth":
|
|
2764
|
+
case "open_github_app": return {
|
|
2765
|
+
...base,
|
|
2766
|
+
action: {
|
|
2767
|
+
kind: "open_url",
|
|
2768
|
+
url: resolveConnectUrl(act.url, appUrl)
|
|
2769
|
+
}
|
|
2770
|
+
};
|
|
2771
|
+
case "submit_credential": return {
|
|
2772
|
+
...base,
|
|
2773
|
+
action: {
|
|
2774
|
+
kind: "submit_credential",
|
|
2775
|
+
url: resolveConnectUrl(act.url, appUrl),
|
|
2776
|
+
fields: card.fields.map((f) => f.label)
|
|
2777
|
+
}
|
|
2778
|
+
};
|
|
2779
|
+
case "grant_portal": return {
|
|
2780
|
+
...base,
|
|
2781
|
+
action: {
|
|
2782
|
+
kind: "approve_portal",
|
|
2783
|
+
agentId: act.agentId
|
|
2784
|
+
}
|
|
2785
|
+
};
|
|
2786
|
+
default: return {
|
|
2787
|
+
...base,
|
|
2788
|
+
action: { kind: "unsupported" }
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
/**
|
|
2793
|
+
* Parse a `data-anyone-render-spec` stream chunk into a connect-card summary,
|
|
2794
|
+
* or null if the chunk isn't a connect card (other render specs — training,
|
|
2795
|
+
* deep-learn, compute-request — parse to null, same as the TUI).
|
|
2796
|
+
*/
|
|
2797
|
+
function connectCardFromChunk(chunk, appUrl) {
|
|
2798
|
+
if (chunk["type"] !== "data-anyone-render-spec") return null;
|
|
2799
|
+
const data = chunk["data"];
|
|
2800
|
+
if (!isRecord(data)) return null;
|
|
2801
|
+
const card = parseConnectCard(data["spec"]);
|
|
2802
|
+
if (!card) return null;
|
|
2803
|
+
return summarizeConnectCard(card, appUrl);
|
|
2804
|
+
}
|
|
2805
|
+
/** Render a connect-card summary as a human-readable action block. */
|
|
2806
|
+
function formatConnectCard(card) {
|
|
2807
|
+
const lines = [];
|
|
2808
|
+
lines.push(`\n[action needed] ${card.title}`);
|
|
2809
|
+
if (card.subtitle) lines.push(card.subtitle);
|
|
2810
|
+
if (card.description) lines.push(card.description);
|
|
2811
|
+
switch (card.action.kind) {
|
|
2812
|
+
case "open_url":
|
|
2813
|
+
lines.push(`Open to continue: ${card.action.url}`);
|
|
2814
|
+
break;
|
|
2815
|
+
case "submit_credential":
|
|
2816
|
+
lines.push(`Provide credential (${card.action.fields.join(", ") || "value"}) at: ${card.action.url}`);
|
|
2817
|
+
break;
|
|
2818
|
+
case "approve_portal":
|
|
2819
|
+
lines.push(`Approve local-machine access for agent ${card.action.agentId} in the TUI or web app.`);
|
|
2820
|
+
break;
|
|
2821
|
+
case "unsupported":
|
|
2822
|
+
lines.push("Open this conversation in the web app to continue.");
|
|
2823
|
+
break;
|
|
2824
|
+
}
|
|
2825
|
+
return lines.join("\n");
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
//#endregion
|
|
2829
|
+
//#region src/chat/print.ts
|
|
2830
|
+
var print_exports = /* @__PURE__ */ __exportAll({
|
|
2831
|
+
readStdin: () => readStdin,
|
|
2832
|
+
resolveAgent: () => resolveAgent,
|
|
2833
|
+
runPrint: () => runPrint
|
|
2834
|
+
});
|
|
2835
|
+
/**
|
|
2836
|
+
* Non-interactive chat, à la `claude -p`. Sends a single prompt to an
|
|
2837
|
+
* agent, streams the run, and prints the assistant's reply to stdout
|
|
2838
|
+
* before exiting. No OpenTUI, no Bun requirement — this rides the same
|
|
2839
|
+
* Node-friendly REST client the TUI uses, so it runs anywhere the
|
|
2840
|
+
* management commands do (CI, pipes, scripts).
|
|
2841
|
+
*
|
|
2842
|
+
* Resolution rules kept deliberately strict because there's no human to
|
|
2843
|
+
* disambiguate: an `--agent` selector must match exactly one agent, and
|
|
2844
|
+
* when it's omitted we only auto-pick if the account has exactly one.
|
|
2845
|
+
*/
|
|
2846
|
+
async function runPrint({ appUrl, sessionToken, prompt, agentSelector, conversationId, json }) {
|
|
2847
|
+
const client = createRestClient({
|
|
2848
|
+
appUrl,
|
|
2849
|
+
sessionToken
|
|
2850
|
+
});
|
|
2851
|
+
const agent = resolveAgent(await client.listAgents({
|
|
2852
|
+
scope: "org",
|
|
2853
|
+
onPage: null
|
|
2854
|
+
}), agentSelector);
|
|
2855
|
+
const send = await client.sendMessage({
|
|
2856
|
+
agentId: agent.id,
|
|
2857
|
+
conversationId,
|
|
2858
|
+
content: prompt,
|
|
2859
|
+
attachmentIds: [],
|
|
2860
|
+
clientSurface: "cli"
|
|
2861
|
+
});
|
|
2862
|
+
let text = "";
|
|
2863
|
+
const controller = new AbortController();
|
|
2864
|
+
let streamError = null;
|
|
2865
|
+
const connectCards = [];
|
|
2866
|
+
await client.streamRun({
|
|
2867
|
+
runId: send.runId,
|
|
2868
|
+
signal: controller.signal,
|
|
2869
|
+
onEvent: (event) => {
|
|
2870
|
+
if (event.kind === "finished") {
|
|
2871
|
+
if (event.error) streamError = event.error;
|
|
2872
|
+
return;
|
|
2873
|
+
}
|
|
2874
|
+
const chunk = event.chunk;
|
|
2875
|
+
if (chunk["type"] === "text-delta") {
|
|
2876
|
+
const delta = typeof chunk["delta"] === "string" ? chunk["delta"] : typeof chunk["text"] === "string" ? chunk["text"] : "";
|
|
2877
|
+
if (delta) {
|
|
2878
|
+
text += delta;
|
|
2879
|
+
if (!json) process.stdout.write(delta);
|
|
2880
|
+
}
|
|
2881
|
+
} else if (chunk["type"] === "error") streamError = typeof chunk["errorText"] === "string" ? chunk["errorText"] : "unknown error";
|
|
2882
|
+
else {
|
|
2883
|
+
const card = connectCardFromChunk(chunk, appUrl);
|
|
2884
|
+
if (card) connectCards.push(card);
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
});
|
|
2888
|
+
if (streamError) throw new Error(streamError);
|
|
2889
|
+
if (!json && text && !text.endsWith("\n")) process.stdout.write("\n");
|
|
2890
|
+
if (!json) for (const card of connectCards) process.stdout.write(`${formatConnectCard(card)}\n`);
|
|
2891
|
+
return {
|
|
2892
|
+
agentId: agent.id,
|
|
2893
|
+
agentName: agent.name,
|
|
2894
|
+
conversationId: send.conversationId,
|
|
2895
|
+
isNewConversation: send.isNewConversation,
|
|
2896
|
+
runId: send.runId,
|
|
2897
|
+
text,
|
|
2898
|
+
connectCards
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
/**
|
|
2902
|
+
* Pick the target agent. With no selector, auto-pick only when the
|
|
2903
|
+
* account has exactly one agent; otherwise the user must name one (there's
|
|
2904
|
+
* no picker in non-interactive mode). A selector matches by id first, then
|
|
2905
|
+
* a unique case-insensitive slug/name; ambiguous or missing matches throw
|
|
2906
|
+
* with the candidate list so the caller knows what to pass.
|
|
2907
|
+
*/
|
|
2908
|
+
function resolveAgent(agents, selector) {
|
|
2909
|
+
if (!selector) {
|
|
2910
|
+
const [only, ...rest] = agents;
|
|
2911
|
+
if (!only) throw new Error("No agents on this account.");
|
|
2912
|
+
if (rest.length === 0) return only;
|
|
2913
|
+
throw new Error(`Multiple agents on this account — pass --agent <id|slug|name>. Candidates:\n${formatCandidates(agents)}`);
|
|
2914
|
+
}
|
|
2915
|
+
const byId = agents.find((a) => a.id === selector);
|
|
2916
|
+
if (byId) return byId;
|
|
2917
|
+
const needle = selector.toLowerCase();
|
|
2918
|
+
const matches = agents.filter((a) => a.slug && a.slug.toLowerCase() === needle || a.name.toLowerCase() === needle);
|
|
2919
|
+
const [firstMatch, ...restMatches] = matches;
|
|
2920
|
+
if (firstMatch && restMatches.length === 0) return firstMatch;
|
|
2921
|
+
if (restMatches.length > 0) throw new Error(`Multiple agents match "${selector}" — pass the id instead. Candidates:\n${formatCandidates(matches)}`);
|
|
2922
|
+
throw new Error(`No agent matches "${selector}". Candidates:\n${formatCandidates(agents)}`);
|
|
2923
|
+
}
|
|
2924
|
+
function formatCandidates(agents) {
|
|
2925
|
+
return agents.slice(0, 25).map((a) => ` ${a.id} ${a.slug ?? a.name}`).join("\n");
|
|
2926
|
+
}
|
|
2927
|
+
/** Read all of stdin as UTF-8. Used when `-p` is passed with no value. */
|
|
2928
|
+
async function readStdin() {
|
|
2929
|
+
const chunks = [];
|
|
2930
|
+
for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
2931
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
//#endregion
|
|
2935
|
+
//#region src/commands/session.ts
|
|
2936
|
+
/**
|
|
2937
|
+
* Resolve the signed-in chat session or exit with a friendly hint. Shared by
|
|
2938
|
+
* every command that talks to the authenticated REST API so the
|
|
2939
|
+
* resolve-or-exit block isn't copy-pasted per command.
|
|
2940
|
+
*/
|
|
2153
2941
|
function requireSession(argv) {
|
|
2154
|
-
const session = resolveSession({ appUrl: argv["api-url"] });
|
|
2942
|
+
const session = resolveSession({ appUrl: resolveAppUrl({ appUrl: argv["api-url"] }) });
|
|
2155
2943
|
if (session.isErr()) {
|
|
2156
|
-
printError(
|
|
2944
|
+
printError(`${session.error.message} Run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
|
|
2157
2945
|
process.exit(1);
|
|
2158
2946
|
}
|
|
2159
2947
|
return session.value;
|
|
2160
2948
|
}
|
|
2949
|
+
/** Resolve the session (see {@link requireSession}) and build a REST client. */
|
|
2950
|
+
function requireRestClient(argv) {
|
|
2951
|
+
const session = requireSession(argv);
|
|
2952
|
+
return createRestClient({
|
|
2953
|
+
appUrl: session.appUrl,
|
|
2954
|
+
sessionToken: session.sessionToken
|
|
2955
|
+
});
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
//#endregion
|
|
2959
|
+
//#region src/commands/conversations.ts
|
|
2960
|
+
/**
|
|
2961
|
+
* Flatten a UiMessage's parts into plain text for the non-JSON view. Text and
|
|
2962
|
+
* reasoning parts are concatenated; tool calls are summarized to one line so a
|
|
2963
|
+
* transcript stays readable without the rich TUI rendering. This is a
|
|
2964
|
+
* lossy-but-legible view; `--json` carries the full structured parts for a
|
|
2965
|
+
* driving agent that wants everything.
|
|
2966
|
+
*/
|
|
2967
|
+
function messageToText(message) {
|
|
2968
|
+
const chunks = [];
|
|
2969
|
+
for (const part of message.parts) if (part.type === "text" && typeof part.text === "string") chunks.push(part.text);
|
|
2970
|
+
else if (part.type === "reasoning" && typeof part.text === "string") chunks.push(part.text);
|
|
2971
|
+
else if (part.type === "dynamic-tool" && "toolName" in part) {
|
|
2972
|
+
const toolName = typeof part.toolName === "string" ? part.toolName : "tool";
|
|
2973
|
+
chunks.push(`[tool: ${toolName}]`);
|
|
2974
|
+
}
|
|
2975
|
+
return chunks.join("\n").trim();
|
|
2976
|
+
}
|
|
2977
|
+
const listCommand$1 = {
|
|
2978
|
+
command: "list",
|
|
2979
|
+
describe: "List an agent's conversations",
|
|
2980
|
+
builder: (y) => y.option("agent", {
|
|
2981
|
+
type: "string",
|
|
2982
|
+
describe: "Agent whose conversations to list, by id, slug, or name. Optional when the account has exactly one agent."
|
|
2983
|
+
}).option("limit", {
|
|
2984
|
+
type: "number",
|
|
2985
|
+
describe: "Max conversations to return"
|
|
2986
|
+
}),
|
|
2987
|
+
handler: async (argv) => {
|
|
2988
|
+
const client = requireRestClient(argv);
|
|
2989
|
+
const agent = resolveAgent(await client.listAgents({
|
|
2990
|
+
scope: "org",
|
|
2991
|
+
onPage: null
|
|
2992
|
+
}), argv.agent ?? null);
|
|
2993
|
+
const conversations = await client.listConversations({
|
|
2994
|
+
agentId: agent.id,
|
|
2995
|
+
limit: argv.limit
|
|
2996
|
+
});
|
|
2997
|
+
if (argv.json) {
|
|
2998
|
+
output(argv, conversations);
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
if (conversations.length === 0) {
|
|
3002
|
+
console.log("No conversations found.");
|
|
3003
|
+
return;
|
|
3004
|
+
}
|
|
3005
|
+
printTable([
|
|
3006
|
+
"ID",
|
|
3007
|
+
"Title",
|
|
3008
|
+
"Updated",
|
|
3009
|
+
"Preview"
|
|
3010
|
+
], conversations.map((c) => [
|
|
3011
|
+
c.id,
|
|
3012
|
+
c.title ?? "-",
|
|
3013
|
+
c.updatedAt,
|
|
3014
|
+
(c.preview ?? "").replace(/\s+/g, " ").slice(0, 48) || "-"
|
|
3015
|
+
]));
|
|
3016
|
+
}
|
|
3017
|
+
};
|
|
3018
|
+
const showCommand = {
|
|
3019
|
+
command: "show <conversation-id>",
|
|
3020
|
+
describe: "Print a conversation transcript",
|
|
3021
|
+
builder: (y) => y.positional("conversation-id", {
|
|
3022
|
+
type: "string",
|
|
3023
|
+
demandOption: true,
|
|
3024
|
+
describe: "Conversation id"
|
|
3025
|
+
}).option("recap", {
|
|
3026
|
+
type: "boolean",
|
|
3027
|
+
default: false,
|
|
3028
|
+
describe: "Print the conversation recap instead of the full transcript"
|
|
3029
|
+
}),
|
|
3030
|
+
handler: async (argv) => {
|
|
3031
|
+
const client = requireRestClient(argv);
|
|
3032
|
+
const conversationId = argv["conversation-id"];
|
|
3033
|
+
if (argv.recap) {
|
|
3034
|
+
const recap = await client.getRecap({ conversationId });
|
|
3035
|
+
if (argv.json) {
|
|
3036
|
+
output(argv, {
|
|
3037
|
+
conversationId,
|
|
3038
|
+
recap
|
|
3039
|
+
});
|
|
3040
|
+
return;
|
|
3041
|
+
}
|
|
3042
|
+
console.log(recap ?? "(no recap available)");
|
|
3043
|
+
return;
|
|
3044
|
+
}
|
|
3045
|
+
const messages = await client.listMessages({ conversationId });
|
|
3046
|
+
if (argv.json) {
|
|
3047
|
+
output(argv, messages);
|
|
3048
|
+
return;
|
|
3049
|
+
}
|
|
3050
|
+
if (messages.length === 0) {
|
|
3051
|
+
console.log("No messages in this conversation.");
|
|
3052
|
+
return;
|
|
3053
|
+
}
|
|
3054
|
+
for (const message of messages) {
|
|
3055
|
+
const text = messageToText(message);
|
|
3056
|
+
if (!text) continue;
|
|
3057
|
+
console.log(`\n[${message.role}]`);
|
|
3058
|
+
console.log(text);
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
};
|
|
3062
|
+
const conversationsCommand = {
|
|
3063
|
+
command: "conversations",
|
|
3064
|
+
aliases: ["conv"],
|
|
3065
|
+
describe: "List and read agent conversations",
|
|
3066
|
+
builder: (y) => y.command(listCommand$1).command(showCommand).demandCommand(1, "Specify a subcommand: list, show"),
|
|
3067
|
+
handler: () => {}
|
|
3068
|
+
};
|
|
3069
|
+
|
|
3070
|
+
//#endregion
|
|
3071
|
+
//#region src/commands/workspace.ts
|
|
2161
3072
|
/**
|
|
2162
3073
|
* List the account's workspaces, marking the active one. Shared by the `list`
|
|
2163
3074
|
* subcommand and the bare `workspace` invocation. When `hint` is true (the bare
|
|
@@ -2218,7 +3129,7 @@ const switchCommand = {
|
|
|
2218
3129
|
printError("The workspace picker needs the Bun runtime and it could not be set up automatically. Pass a workspace slug instead, or install Bun and retry.");
|
|
2219
3130
|
process.exit(1);
|
|
2220
3131
|
}
|
|
2221
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
3132
|
+
const { runWorkspacePicker } = await import("./boot-BvkqTAxQ.mjs");
|
|
2222
3133
|
await runWorkspacePicker(session);
|
|
2223
3134
|
return;
|
|
2224
3135
|
}
|
|
@@ -2272,7 +3183,7 @@ function createCli(argv) {
|
|
|
2272
3183
|
type: "string",
|
|
2273
3184
|
global: true,
|
|
2274
3185
|
describe: "Override API base URL"
|
|
2275
|
-
}).command(authCommand).command(chatCommand).command(agentsCommand).command(keysCommand).command(secretsCommand).command(workspaceCommand).demandCommand(1, "Specify a command. Run --help for usage.").strict().wrap(null).version(version$1).alias("v", "version").alias("h", "help").help().fail((msg, err) => {
|
|
3186
|
+
}).command(authCommand).command(chatCommand).command(conversationsCommand).command(agentsCommand).command(keysCommand).command(secretsCommand).command(workspaceCommand).demandCommand(1, "Specify a command. Run --help for usage.").strict().wrap(null).version(version$1).alias("v", "version").alias("h", "help").help().fail((msg, err) => {
|
|
2276
3187
|
printError(err ? err instanceof Error ? err.message : String(err) : msg ?? "Unknown error");
|
|
2277
3188
|
process.exit(1);
|
|
2278
3189
|
});
|
|
@@ -2345,4 +3256,4 @@ function resolveArgv(args, tty = {
|
|
|
2345
3256
|
createCli(resolveArgv(hideBin(process.argv))).parse();
|
|
2346
3257
|
|
|
2347
3258
|
//#endregion
|
|
2348
|
-
export {
|
|
3259
|
+
export { resolveWebUrl as A, themesForMode as C, DEFAULT_API_URL as D, setActiveWorkspace as E, DEFAULT_APP_URL as O, themeVersion as S, listWorkspaces as T, noColorRequested as _, parseOauthConnectParams as a, themeMode as b, parseConnectCard as c, HttpError as d, createRestClient as f, monoTheme as g, findTheme as h, parseExternalOauthConnectParams as i, saveTheme as j, getSavedTheme as k, errorMessage as l, applyTheme as m, MASK_CHAR as n, reconcileMaskedInput as o, DEFAULT_THEME_ID as p, cardActionErrorMessage as r, resolveConnectUrl as s, resolveAgent as t, isRecord as u, theme as v, getActiveWorkspaceId as w, themeModeFromColorFgBg as x, themeForMode as y };
|