broods 0.3.0 → 0.4.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/README.md +20 -5
- package/dist/account.d.ts +56 -53
- package/dist/account.js +19 -0
- package/dist/cli/index.js +410 -157
- package/dist/index.d.ts +217 -80
- package/dist/index.js +150 -24
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// src/account.ts
|
|
3
3
|
var DEFAULT_ACCOUNT_BASE_URL = "https://gateway.broods.app";
|
|
4
|
+
var ACCOUNT_ENV_VAR_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/;
|
|
5
|
+
function envPlaceholder(name) {
|
|
6
|
+
if (!ACCOUNT_ENV_VAR_NAME_PATTERN.test(name) || name.length > 64) {
|
|
7
|
+
throw new Error("envPlaceholder name must match /^[A-Z][A-Z0-9_]*$/ and be at most 64 characters.");
|
|
8
|
+
}
|
|
9
|
+
return `\${${name}}`;
|
|
10
|
+
}
|
|
4
11
|
|
|
5
12
|
class BroodsAccountApiError extends Error {
|
|
6
13
|
status;
|
|
@@ -73,6 +80,17 @@ class BroodsAccountClient {
|
|
|
73
80
|
const result = await this.request("DELETE", `/v1/agents/${encodeURIComponent(agentId)}`);
|
|
74
81
|
return result?.deleted ?? false;
|
|
75
82
|
}
|
|
83
|
+
async listEnvVars() {
|
|
84
|
+
const result = await this.request("GET", "/v1/env");
|
|
85
|
+
return result?.env ?? [];
|
|
86
|
+
}
|
|
87
|
+
async setEnvVar(name, value) {
|
|
88
|
+
await this.request("PUT", `/v1/env/${encodeURIComponent(name)}`, { value });
|
|
89
|
+
}
|
|
90
|
+
async deleteEnvVar(name) {
|
|
91
|
+
const result = await this.request("DELETE", `/v1/env/${encodeURIComponent(name)}`);
|
|
92
|
+
return result?.deleted ?? false;
|
|
93
|
+
}
|
|
76
94
|
async listCrons() {
|
|
77
95
|
const result = await this.request("GET", "/v1/crons");
|
|
78
96
|
return result?.crons ?? [];
|
|
@@ -337,6 +355,9 @@ import { dirname, join } from "path";
|
|
|
337
355
|
var PROJECT_DIR = "broods";
|
|
338
356
|
var GENERATED_DIR = "_generated";
|
|
339
357
|
var USER_CONFIG_PATH = join(homedir(), ".broods", "config.json");
|
|
358
|
+
var BROODS_APEX_DOMAIN = "broods.app";
|
|
359
|
+
var DASHBOARD_HOST_PREFIX = "dashboard";
|
|
360
|
+
var GATEWAY_HOST_PREFIX = "gateway";
|
|
340
361
|
async function readStoredAuth() {
|
|
341
362
|
const envToken = process.env.BROODS_TOKEN;
|
|
342
363
|
const envConvexUrl = process.env.BROODS_BASE_URL;
|
|
@@ -364,6 +385,20 @@ async function writeStoredAuth(config) {
|
|
|
364
385
|
function stripTrailingSlash(value) {
|
|
365
386
|
return value.replace(/\/+$/, "");
|
|
366
387
|
}
|
|
388
|
+
function gatewayUrlForDashboard(dashboardUrl) {
|
|
389
|
+
try {
|
|
390
|
+
const url = new URL(dashboardUrl);
|
|
391
|
+
if (!url.hostname.startsWith(`${DASHBOARD_HOST_PREFIX}.`))
|
|
392
|
+
return;
|
|
393
|
+
const rest = url.hostname.slice(DASHBOARD_HOST_PREFIX.length + 1);
|
|
394
|
+
if (rest !== BROODS_APEX_DOMAIN && !rest.endsWith(`.${BROODS_APEX_DOMAIN}`))
|
|
395
|
+
return;
|
|
396
|
+
url.hostname = `${GATEWAY_HOST_PREFIX}.${rest}`;
|
|
397
|
+
return stripTrailingSlash(url.origin);
|
|
398
|
+
} catch {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
367
402
|
|
|
368
403
|
// src/runtime-config.ts
|
|
369
404
|
var envFileCache = new Map;
|
|
@@ -448,6 +483,15 @@ function readStoredAuthSync() {
|
|
|
448
483
|
// src/client.ts
|
|
449
484
|
var DEFAULT_CORE_BASE_URL = "https://gateway.broods.app";
|
|
450
485
|
|
|
486
|
+
class IngressAcceptedError extends Error {
|
|
487
|
+
accepted;
|
|
488
|
+
constructor(accepted) {
|
|
489
|
+
super(`Ingress ${accepted.eventId} was accepted asynchronously with status ${accepted.status ?? "queued"}`);
|
|
490
|
+
this.accepted = accepted;
|
|
491
|
+
this.name = "IngressAcceptedError";
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
451
495
|
class BroodsClient {
|
|
452
496
|
baseUrl;
|
|
453
497
|
apiKey;
|
|
@@ -506,6 +550,9 @@ class BroodsClient {
|
|
|
506
550
|
const response = await this.openStream(body, targetUrl);
|
|
507
551
|
if (!response.ok)
|
|
508
552
|
throw new Error(`Run failed: ${response.status} ${await response.text()}`);
|
|
553
|
+
if (response.status === 202) {
|
|
554
|
+
throw new IngressAcceptedError(normalizeAsyncAccepted(await response.json(), body));
|
|
555
|
+
}
|
|
509
556
|
if (!response.body)
|
|
510
557
|
throw new Error("Run response has no body");
|
|
511
558
|
for await (const data of readSseStream(response.body)) {
|
|
@@ -563,7 +610,7 @@ class BroodsClient {
|
|
|
563
610
|
if (options.signal?.aborted)
|
|
564
611
|
throw new Error("Async status polling aborted.");
|
|
565
612
|
const payload = await this.getAsyncStatus(status, options);
|
|
566
|
-
if (payload.status === "awaiting_approval" || payload.status === "completed" || payload.status === "failed" || payload.status === "not_found") {
|
|
613
|
+
if (payload.status === "awaiting_approval" || payload.status === "completed" || payload.status === "failed" || payload.status === "expired" || payload.status === "not_found") {
|
|
567
614
|
return payload;
|
|
568
615
|
}
|
|
569
616
|
await sleep(intervalMs, options.signal);
|
|
@@ -710,6 +757,8 @@ function directRunBody(input, prefix) {
|
|
|
710
757
|
eventId,
|
|
711
758
|
conversationKey: input.conversationKey ?? eventId,
|
|
712
759
|
events: resolveRunEvents(input),
|
|
760
|
+
...input.mode !== undefined ? { mode: input.mode } : {},
|
|
761
|
+
...input.idempotencyKey !== undefined ? { idempotencyKey: input.idempotencyKey } : {},
|
|
713
762
|
...input.system !== undefined ? { system: input.system } : {},
|
|
714
763
|
...input.model !== undefined ? { model: input.model } : {}
|
|
715
764
|
};
|
|
@@ -725,11 +774,16 @@ function normalizeAsyncAccepted(payload, requestBody) {
|
|
|
725
774
|
const status = parseStatusUrl(statusUrl);
|
|
726
775
|
if (!status.statusId)
|
|
727
776
|
throw new Error("Async response statusUrl missing status id");
|
|
777
|
+
const eventId = typeof payload.eventId === "string" ? payload.eventId : status.statusId;
|
|
778
|
+
const acceptedStatus = payload.status;
|
|
779
|
+
const requestedMode = payload.requestedMode;
|
|
728
780
|
return {
|
|
729
781
|
statusUrl,
|
|
730
782
|
statusId: status.statusId,
|
|
731
|
-
eventId
|
|
732
|
-
agentId: status.agentId ?? requestBody.agentId
|
|
783
|
+
eventId,
|
|
784
|
+
agentId: status.agentId ?? requestBody.agentId,
|
|
785
|
+
...acceptedStatus === "accepted" || acceptedStatus === "queued" || acceptedStatus === "applied" || acceptedStatus === "processing" ? { status: acceptedStatus } : {},
|
|
786
|
+
...requestedMode === "reject" || requestedMode === "followup" || requestedMode === "collect" || requestedMode === "steer" ? { requestedMode } : {}
|
|
733
787
|
};
|
|
734
788
|
}
|
|
735
789
|
function parseStatusUrl(statusUrl) {
|
|
@@ -862,6 +916,8 @@ class BroodsWebSocketClient {
|
|
|
862
916
|
events: resolveRunEvents(input),
|
|
863
917
|
sessionId: input.sessionId,
|
|
864
918
|
...input.eventId !== undefined ? { eventId: input.eventId } : {},
|
|
919
|
+
...input.mode !== undefined ? { mode: input.mode } : {},
|
|
920
|
+
...input.idempotencyKey !== undefined ? { idempotencyKey: input.idempotencyKey } : {},
|
|
865
921
|
...input.system !== undefined ? { system: input.system } : {},
|
|
866
922
|
...input.model !== undefined ? { model: input.model } : {}
|
|
867
923
|
}));
|
|
@@ -872,16 +928,17 @@ class BroodsWebSocketClient {
|
|
|
872
928
|
const payload = parseServerMessage(event.data);
|
|
873
929
|
if (!payload)
|
|
874
930
|
return;
|
|
875
|
-
|
|
876
|
-
|
|
931
|
+
const effective = unwrapServerMessage(payload, handlers);
|
|
932
|
+
handlers.onMessage?.(effective);
|
|
933
|
+
switch (effective.type) {
|
|
877
934
|
case "meta":
|
|
878
|
-
handlers.onMeta?.(
|
|
935
|
+
handlers.onMeta?.(effective);
|
|
879
936
|
break;
|
|
880
937
|
case "done":
|
|
881
938
|
finish();
|
|
882
939
|
break;
|
|
883
940
|
case "error":
|
|
884
|
-
fail(new Error(formatWireError(
|
|
941
|
+
fail(new Error(formatWireError(effective.error)));
|
|
885
942
|
break;
|
|
886
943
|
}
|
|
887
944
|
};
|
|
@@ -897,6 +954,57 @@ class BroodsWebSocketClient {
|
|
|
897
954
|
};
|
|
898
955
|
return {
|
|
899
956
|
url,
|
|
957
|
+
sendControl(message) {
|
|
958
|
+
if (socket.readyState !== WS_OPEN)
|
|
959
|
+
throw new Error("WebSocket is not open");
|
|
960
|
+
socket.send(JSON.stringify({ type: "control", ...message }));
|
|
961
|
+
},
|
|
962
|
+
close
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
attach(input, handlers = {}) {
|
|
966
|
+
const WebSocketImpl = this.resolveWebSocket();
|
|
967
|
+
const url = this.buildUrl(input);
|
|
968
|
+
const socket = new WebSocketImpl(url);
|
|
969
|
+
let closed = false;
|
|
970
|
+
const close = (code = 1000, reason = "client closed") => {
|
|
971
|
+
if (closed)
|
|
972
|
+
return;
|
|
973
|
+
closed = true;
|
|
974
|
+
if (socket.readyState === WS_OPEN || socket.readyState === WS_CONNECTING)
|
|
975
|
+
socket.close(code, reason);
|
|
976
|
+
};
|
|
977
|
+
socket.onopen = () => socket.send(JSON.stringify({
|
|
978
|
+
type: "attach",
|
|
979
|
+
...input,
|
|
980
|
+
signal: undefined,
|
|
981
|
+
agent: undefined
|
|
982
|
+
}));
|
|
983
|
+
socket.onmessage = (event) => {
|
|
984
|
+
if (typeof event.data !== "string")
|
|
985
|
+
return;
|
|
986
|
+
const payload = parseServerMessage(event.data);
|
|
987
|
+
if (!payload)
|
|
988
|
+
return;
|
|
989
|
+
const effective = unwrapServerMessage(payload, handlers);
|
|
990
|
+
handlers.onMessage?.(effective);
|
|
991
|
+
if (effective.type === "done")
|
|
992
|
+
handlers.onDone?.();
|
|
993
|
+
if (effective.type === "error")
|
|
994
|
+
handlers.onError?.(new Error(formatWireError(effective.error)));
|
|
995
|
+
};
|
|
996
|
+
socket.onerror = () => handlers.onError?.(webSocketAccessError(this.baseUrl));
|
|
997
|
+
socket.onclose = (event) => {
|
|
998
|
+
if (!closed && event.code !== 1000)
|
|
999
|
+
handlers.onError?.(new Error(event.reason || "WebSocket connection closed."));
|
|
1000
|
+
};
|
|
1001
|
+
return {
|
|
1002
|
+
url,
|
|
1003
|
+
sendControl(message) {
|
|
1004
|
+
if (socket.readyState !== WS_OPEN)
|
|
1005
|
+
throw new Error("WebSocket is not open");
|
|
1006
|
+
socket.send(JSON.stringify({ type: "control", ...message }));
|
|
1007
|
+
},
|
|
900
1008
|
close
|
|
901
1009
|
};
|
|
902
1010
|
}
|
|
@@ -989,6 +1097,13 @@ function normalizeWebSocketServiceUrl(value) {
|
|
|
989
1097
|
function webSocketAccessError(baseUrl) {
|
|
990
1098
|
return new Error(`Cannot access the WebSocket service at ${baseUrl}.`);
|
|
991
1099
|
}
|
|
1100
|
+
function unwrapServerMessage(payload, handlers) {
|
|
1101
|
+
if (payload.type !== "output")
|
|
1102
|
+
return payload;
|
|
1103
|
+
const output = payload;
|
|
1104
|
+
handlers.onOutput?.(output);
|
|
1105
|
+
return output.data;
|
|
1106
|
+
}
|
|
992
1107
|
function parseServerMessage(data) {
|
|
993
1108
|
try {
|
|
994
1109
|
const value = JSON.parse(data);
|
|
@@ -1009,25 +1124,27 @@ function formatWireError(error) {
|
|
|
1009
1124
|
var RESOURCE_MARKER = Symbol.for("broods.resource");
|
|
1010
1125
|
var CONFIG_MARKER = Symbol.for("broods.config");
|
|
1011
1126
|
var CHANNEL_MARKER = Symbol.for("broods.channel");
|
|
1012
|
-
var
|
|
1127
|
+
var ENV_NAME_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
1128
|
+
var env = new Proxy((name) => {
|
|
1129
|
+
if (!ENV_NAME_PATTERN.test(name)) {
|
|
1130
|
+
throw new Error("env name must match /^[A-Z][A-Z0-9_]*$/ and be at most 64 characters.");
|
|
1131
|
+
}
|
|
1013
1132
|
return { __beeblastEnv: true, name };
|
|
1014
1133
|
}, {
|
|
1015
1134
|
get(target, property, receiver) {
|
|
1016
|
-
if (typeof property === "string")
|
|
1017
|
-
|
|
1135
|
+
if (typeof property === "string" && ENV_NAME_PATTERN.test(property)) {
|
|
1136
|
+
throw new Error(`env.${property} is not supported; use env("${property}")`);
|
|
1137
|
+
}
|
|
1018
1138
|
return Reflect.get(target, property, receiver);
|
|
1019
1139
|
}
|
|
1020
1140
|
});
|
|
1021
|
-
function defineResource(kind,
|
|
1022
|
-
if (input.config === undefined) {
|
|
1023
|
-
throw new Error(`Resource "${input.name}" must include config`);
|
|
1024
|
-
}
|
|
1141
|
+
function defineResource(kind, name, description, config) {
|
|
1025
1142
|
return {
|
|
1026
1143
|
[RESOURCE_MARKER]: true,
|
|
1027
1144
|
kind,
|
|
1028
|
-
name
|
|
1029
|
-
...
|
|
1030
|
-
config
|
|
1145
|
+
name,
|
|
1146
|
+
...description ? { description } : {},
|
|
1147
|
+
config
|
|
1031
1148
|
};
|
|
1032
1149
|
}
|
|
1033
1150
|
function defineChannel(type, config) {
|
|
@@ -1065,25 +1182,32 @@ function defineBroods(config) {
|
|
|
1065
1182
|
return { [CONFIG_MARKER]: true, config };
|
|
1066
1183
|
}
|
|
1067
1184
|
function defineAgent(input) {
|
|
1068
|
-
|
|
1185
|
+
const { name, description, ...config } = input;
|
|
1186
|
+
return defineResource("agent", name, description, config);
|
|
1069
1187
|
}
|
|
1070
1188
|
function defineWorkspace(input) {
|
|
1071
|
-
|
|
1189
|
+
const { name, description, ...config } = input;
|
|
1190
|
+
return defineResource("workspace", name, description, config);
|
|
1072
1191
|
}
|
|
1073
1192
|
function defineSandbox(input) {
|
|
1074
|
-
|
|
1193
|
+
const { name, description, ...config } = input;
|
|
1194
|
+
return defineResource("sandbox", name, description, config);
|
|
1075
1195
|
}
|
|
1076
1196
|
function defineSkill(input) {
|
|
1077
|
-
|
|
1197
|
+
const { name, description, ...config } = input;
|
|
1198
|
+
return defineResource("skill", name, description, config);
|
|
1078
1199
|
}
|
|
1079
1200
|
function defineTool(input) {
|
|
1080
|
-
|
|
1201
|
+
const { name, ...config } = input;
|
|
1202
|
+
return defineResource("tool", name, undefined, config);
|
|
1081
1203
|
}
|
|
1082
1204
|
function definePolicy(input) {
|
|
1083
|
-
|
|
1205
|
+
const { name, description, ...config } = input;
|
|
1206
|
+
return defineResource("policy", name, description, config);
|
|
1084
1207
|
}
|
|
1085
1208
|
function defineCron(input) {
|
|
1086
|
-
|
|
1209
|
+
const { name, description, ...config } = input;
|
|
1210
|
+
return defineResource("cron", name, description, config);
|
|
1087
1211
|
}
|
|
1088
1212
|
function isResource(value) {
|
|
1089
1213
|
return Boolean(value && typeof value === "object" && value[RESOURCE_MARKER]);
|
|
@@ -1103,6 +1227,7 @@ export {
|
|
|
1103
1227
|
isObservabilityClientMessage,
|
|
1104
1228
|
isChannelDefinition,
|
|
1105
1229
|
isBroodsConfig,
|
|
1230
|
+
envPlaceholder,
|
|
1106
1231
|
env,
|
|
1107
1232
|
defineZaloChannel,
|
|
1108
1233
|
defineWorkspace,
|
|
@@ -1121,6 +1246,7 @@ export {
|
|
|
1121
1246
|
BroodsWebSocketClient as WebsocketClient,
|
|
1122
1247
|
BroodsWebSocketClient as WebSocketClient,
|
|
1123
1248
|
MAX_OBSERVABILITY_BACKFILL,
|
|
1249
|
+
IngressAcceptedError,
|
|
1124
1250
|
DEFAULT_CORE_BASE_URL,
|
|
1125
1251
|
BroodsWebSocketClient,
|
|
1126
1252
|
BroodsClient,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "broods",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "CLI + TypeScript SDK for the broods agent platform.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,16 +50,16 @@
|
|
|
50
50
|
"node": ">=22"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@chat-adapter/discord": "^4.
|
|
54
|
-
"@chat-adapter/github": "^4.
|
|
55
|
-
"@chat-adapter/slack": "^4.
|
|
56
|
-
"@chat-adapter/telegram": "^4.
|
|
57
|
-
"ai": "^7.0.
|
|
53
|
+
"@chat-adapter/discord": "^4.35.0",
|
|
54
|
+
"@chat-adapter/github": "^4.35.0",
|
|
55
|
+
"@chat-adapter/slack": "^4.35.0",
|
|
56
|
+
"@chat-adapter/telegram": "^4.35.0",
|
|
57
|
+
"ai": "^7.0.37"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/bun": "^1.3.14",
|
|
61
|
-
"rollup": "^4.
|
|
61
|
+
"rollup": "^4.62.2",
|
|
62
62
|
"typescript": "^6.0.3",
|
|
63
|
-
"rollup-plugin-dts": "^6.
|
|
63
|
+
"rollup-plugin-dts": "^6.4.1"
|
|
64
64
|
}
|
|
65
65
|
}
|