broods 0.13.0 → 0.17.3
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 +8 -1
- package/dist/account.d.ts +171 -81
- package/dist/account.js +1 -1
- package/dist/chunk-3cvxks4m.js +2 -0
- package/dist/{chunk-rr4srzb6.js → chunk-qerk8v3m.js} +5 -5
- package/dist/cli/index.js +95 -87
- package/dist/index.d.ts +338 -170
- package/dist/index.js +1 -1
- package/package.json +23 -14
- package/dist/chunk-1wahfz6h.js +0 -1
- package/dist/chunk-4gvdgqrg.js +0 -2
- package/dist/chunk-pfhy5049.js +0 -2
- package/dist/chunk-vmgd45s5.js +0 -1
package/README.md
CHANGED
|
@@ -10,13 +10,20 @@ bun add broods
|
|
|
10
10
|
npm install broods
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Install it globally to get the `broods` command on your PATH. It runs on Bun
|
|
14
|
+
1.2+ or on Node 22.15+:
|
|
14
15
|
|
|
15
16
|
```bash
|
|
16
17
|
bun add -g broods
|
|
18
|
+
# or
|
|
19
|
+
npm install -g broods
|
|
20
|
+
|
|
17
21
|
broods dev
|
|
18
22
|
```
|
|
19
23
|
|
|
24
|
+
`broods update` installs the newest release over the copy you are running, and
|
|
25
|
+
`broods dev` says so when one is out.
|
|
26
|
+
|
|
20
27
|
`ai` is a peer dependency. npm and bun install it for you; add it explicitly on
|
|
21
28
|
package managers that do not auto-install peers:
|
|
22
29
|
|
package/dist/account.d.ts
CHANGED
|
@@ -1,58 +1,98 @@
|
|
|
1
1
|
import { SystemModelMessage, LanguageModelCallOptions, RequestOptions, streamText, JSONSchema7, ModelMessage } from 'ai';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Every Vercel AI SDK provider that ships language models, plus `custom`
|
|
5
|
+
* (any OpenAI-compatible endpoint) and `minimax`. Image-, speech- and
|
|
6
|
+
* transcription-only providers are deliberately absent: they cannot back
|
|
7
|
+
* `config.model`.
|
|
6
8
|
*/
|
|
7
|
-
declare const
|
|
8
|
-
readonly
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
readonly
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
declare const MODEL_PROVIDERS: {
|
|
10
|
+
readonly anthropic: {
|
|
11
|
+
readonly label: "Anthropic";
|
|
12
|
+
readonly modelPlaceholder: "claude-sonnet-4-5-20250929";
|
|
13
|
+
};
|
|
14
|
+
readonly azure: {
|
|
15
|
+
readonly label: "Azure OpenAI";
|
|
16
|
+
readonly modelPlaceholder: "gpt-4.1-mini";
|
|
17
|
+
};
|
|
18
|
+
readonly baseten: {
|
|
19
|
+
readonly label: "Baseten";
|
|
20
|
+
readonly modelPlaceholder: "deepseek-ai/DeepSeek-V3";
|
|
21
|
+
};
|
|
22
|
+
readonly bedrock: {
|
|
23
|
+
readonly label: "Amazon Bedrock";
|
|
24
|
+
readonly modelPlaceholder: "anthropic.claude-sonnet-4-5-20250929-v1:0";
|
|
25
|
+
};
|
|
26
|
+
readonly cerebras: {
|
|
27
|
+
readonly label: "Cerebras";
|
|
28
|
+
readonly modelPlaceholder: "llama3.1-8b";
|
|
29
|
+
};
|
|
30
|
+
readonly cohere: {
|
|
31
|
+
readonly label: "Cohere";
|
|
32
|
+
readonly modelPlaceholder: "command-a-03-2025";
|
|
33
|
+
};
|
|
34
|
+
readonly custom: {
|
|
35
|
+
readonly label: "Custom OpenAI-compatible";
|
|
36
|
+
readonly modelPlaceholder: "gpt-oss-120b";
|
|
37
|
+
};
|
|
38
|
+
readonly deepinfra: {
|
|
39
|
+
readonly label: "DeepInfra";
|
|
40
|
+
readonly modelPlaceholder: "deepseek-ai/DeepSeek-V3";
|
|
41
|
+
};
|
|
42
|
+
readonly deepseek: {
|
|
43
|
+
readonly label: "DeepSeek";
|
|
44
|
+
readonly modelPlaceholder: "deepseek-chat";
|
|
45
|
+
};
|
|
46
|
+
readonly fireworks: {
|
|
47
|
+
readonly label: "Fireworks";
|
|
48
|
+
readonly modelPlaceholder: "accounts/fireworks/models/deepseek-v3";
|
|
49
|
+
};
|
|
50
|
+
readonly google: {
|
|
51
|
+
readonly label: "Google";
|
|
52
|
+
readonly modelPlaceholder: "gemini-2.5-flash";
|
|
53
|
+
};
|
|
54
|
+
readonly groq: {
|
|
55
|
+
readonly label: "Groq";
|
|
56
|
+
readonly modelPlaceholder: "llama-3.3-70b-versatile";
|
|
57
|
+
};
|
|
58
|
+
readonly minimax: {
|
|
59
|
+
readonly label: "MiniMax";
|
|
60
|
+
readonly modelPlaceholder: "MiniMax-M2.7";
|
|
61
|
+
};
|
|
62
|
+
readonly mistral: {
|
|
63
|
+
readonly label: "Mistral";
|
|
64
|
+
readonly modelPlaceholder: "mistral-large-latest";
|
|
65
|
+
};
|
|
66
|
+
readonly openai: {
|
|
67
|
+
readonly label: "OpenAI";
|
|
68
|
+
readonly modelPlaceholder: "gpt-4.1-mini";
|
|
69
|
+
};
|
|
70
|
+
readonly perplexity: {
|
|
71
|
+
readonly label: "Perplexity";
|
|
72
|
+
readonly modelPlaceholder: "sonar-pro";
|
|
73
|
+
};
|
|
74
|
+
readonly togetherai: {
|
|
75
|
+
readonly label: "Together.ai";
|
|
76
|
+
readonly modelPlaceholder: "deepseek-ai/DeepSeek-V3";
|
|
77
|
+
};
|
|
78
|
+
readonly v0: {
|
|
79
|
+
readonly label: "Vercel v0";
|
|
80
|
+
readonly modelPlaceholder: "v0-1.0-md";
|
|
81
|
+
};
|
|
82
|
+
readonly vercel: {
|
|
83
|
+
readonly label: "Vercel AI Gateway";
|
|
84
|
+
readonly modelPlaceholder: "openai/gpt-4.1-mini";
|
|
85
|
+
};
|
|
86
|
+
readonly vertex: {
|
|
87
|
+
readonly label: "Google Vertex AI";
|
|
88
|
+
readonly modelPlaceholder: "gemini-2.5-flash";
|
|
89
|
+
};
|
|
90
|
+
readonly xai: {
|
|
91
|
+
readonly label: "xAI Grok";
|
|
92
|
+
readonly modelPlaceholder: "grok-4";
|
|
93
|
+
};
|
|
15
94
|
};
|
|
16
|
-
type AccountModelProviderName = keyof typeof
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Agent policy contracts and validation.
|
|
20
|
-
* Runtime decisions are made by OPA using the same document/input shape.
|
|
21
|
-
*/
|
|
22
|
-
declare const AGENT_POLICY_ACTIONS: readonly ["agent.invoke", "tool.call", "workspace.read", "workspace.write", "workspace.exec", "subagent.run", "skill.load"];
|
|
23
|
-
type AgentPolicyAction = (typeof AGENT_POLICY_ACTIONS)[number];
|
|
24
|
-
type AgentPolicyEffect = "allow" | "deny";
|
|
25
|
-
type AgentPolicyMode = "enforce" | "audit";
|
|
26
|
-
type AgentPolicyConditionOperator = "equals" | "notEquals" | "in" | "notIn" | "prefix" | "contains";
|
|
27
|
-
interface AgentPolicyCondition {
|
|
28
|
-
attribute: string;
|
|
29
|
-
operator: AgentPolicyConditionOperator;
|
|
30
|
-
value: string | number | boolean | string[] | number[] | boolean[];
|
|
31
|
-
}
|
|
32
|
-
interface AgentPolicyResourceSelector {
|
|
33
|
-
toolNames?: string[];
|
|
34
|
-
toolIds?: string[];
|
|
35
|
-
workspaceIds?: string[];
|
|
36
|
-
workspaceNames?: string[];
|
|
37
|
-
filePaths?: string[];
|
|
38
|
-
subagentIds?: string[];
|
|
39
|
-
skillPaths?: string[];
|
|
40
|
-
}
|
|
41
|
-
interface AgentPolicyRule {
|
|
42
|
-
id: string;
|
|
43
|
-
effect: AgentPolicyEffect;
|
|
44
|
-
actions: AgentPolicyAction[];
|
|
45
|
-
resources?: AgentPolicyResourceSelector;
|
|
46
|
-
conditions?: AgentPolicyCondition[];
|
|
47
|
-
}
|
|
48
|
-
interface AgentPolicyDocument {
|
|
49
|
-
version: 1;
|
|
50
|
-
rules: AgentPolicyRule[];
|
|
51
|
-
}
|
|
52
|
-
interface AgentPolicyConfig {
|
|
53
|
-
policyIds?: string[];
|
|
54
|
-
mode?: AgentPolicyMode;
|
|
55
|
-
}
|
|
95
|
+
type AccountModelProviderName = keyof typeof MODEL_PROVIDERS;
|
|
56
96
|
|
|
57
97
|
/**
|
|
58
98
|
* Agent configuration: types for the per-agent settings object, input
|
|
@@ -83,7 +123,8 @@ interface AgentConfig {
|
|
|
83
123
|
skills?: AgentSkillsConfig;
|
|
84
124
|
subagent?: AgentSubagentConfig;
|
|
85
125
|
scheduler?: AgentSchedulerConfig;
|
|
86
|
-
|
|
126
|
+
/** Policies that gate this agent. Each one carries its own enforcement mode. */
|
|
127
|
+
policies?: string[];
|
|
87
128
|
publicAccess?: boolean;
|
|
88
129
|
[key: string]: unknown;
|
|
89
130
|
}
|
|
@@ -166,14 +207,6 @@ type AgentModelOutputMetadata = {
|
|
|
166
207
|
[key: string]: unknown;
|
|
167
208
|
};
|
|
168
209
|
type AgentProviderConfig = Partial<Record<AccountModelProviderName, AgentProviderSettings>>;
|
|
169
|
-
/**
|
|
170
|
-
* Constructor settings for a model provider. The keys are an explicit allow-list
|
|
171
|
-
* (no open index signature) so a misspelled option — most commonly the camel
|
|
172
|
-
* `baseUrl` instead of the canonical `base_url`/`baseURL` — is a compile-time
|
|
173
|
-
* error in the SDK and is caught by `normalizeProviderSettings` at runtime.
|
|
174
|
-
* Keep this list in sync with `normalizeProviderSettings` and the SDK's
|
|
175
|
-
* `KNOWN_PROVIDER_SETTING_KEYS`.
|
|
176
|
-
*/
|
|
177
210
|
interface AgentProviderSettings {
|
|
178
211
|
apiKey?: string;
|
|
179
212
|
/** OpenAI-compatible endpoint (`custom`). Snake form, as documented. */
|
|
@@ -181,13 +214,11 @@ interface AgentProviderSettings {
|
|
|
181
214
|
/** OpenAI-compatible endpoint (`custom`). AI-SDK form; the dashboard writes both. */
|
|
182
215
|
baseURL?: string;
|
|
183
216
|
headers?: Record<string, string>;
|
|
217
|
+
/** Endpoint label; becomes the provider id and the pi harness env prefix. */
|
|
218
|
+
name?: string;
|
|
184
219
|
organization?: string;
|
|
185
220
|
project?: string;
|
|
186
|
-
|
|
187
|
-
region?: string;
|
|
188
|
-
accessKeyId?: string;
|
|
189
|
-
secretAccessKey?: string;
|
|
190
|
-
sessionToken?: string;
|
|
221
|
+
[key: string]: unknown;
|
|
191
222
|
}
|
|
192
223
|
interface AgentWorkspaceRef {
|
|
193
224
|
name: string;
|
|
@@ -269,7 +300,8 @@ interface AgentTelegramChannelConfig {
|
|
|
269
300
|
apiUrl?: string;
|
|
270
301
|
botToken?: string;
|
|
271
302
|
webhookSecret?: string;
|
|
272
|
-
|
|
303
|
+
allowedChannelIds?: string[];
|
|
304
|
+
allowedUserIds?: string[];
|
|
273
305
|
reactionEmoji?: string;
|
|
274
306
|
trace?: "enabled" | "disabled";
|
|
275
307
|
workspaceScope?: AgentChannelWorkspaceScope;
|
|
@@ -281,7 +313,8 @@ interface AgentGitHubChannelConfig {
|
|
|
281
313
|
webhookSecret?: string;
|
|
282
314
|
appId?: string;
|
|
283
315
|
privateKey?: string;
|
|
284
|
-
|
|
316
|
+
allowedChannelIds?: string[];
|
|
317
|
+
allowedUserIds?: string[];
|
|
285
318
|
/** Bot username for @-mention detection (e.g. "my-bot" or "my-bot[bot]"). */
|
|
286
319
|
userName?: string;
|
|
287
320
|
/** Bot's numeric GitHub user ID for self-message detection. */
|
|
@@ -300,6 +333,7 @@ interface AgentSlackChannelConfig {
|
|
|
300
333
|
botToken?: string;
|
|
301
334
|
signingSecret?: string;
|
|
302
335
|
allowedChannelIds?: string[];
|
|
336
|
+
allowedUserIds?: string[];
|
|
303
337
|
reactionEmoji?: string;
|
|
304
338
|
trace?: "enabled" | "disabled";
|
|
305
339
|
workspaceScope?: AgentChannelWorkspaceScope;
|
|
@@ -310,7 +344,8 @@ interface AgentDiscordChannelConfig {
|
|
|
310
344
|
apiUrl?: string;
|
|
311
345
|
botToken?: string;
|
|
312
346
|
publicKey?: string;
|
|
313
|
-
|
|
347
|
+
allowedChannelIds?: string[];
|
|
348
|
+
allowedUserIds?: string[];
|
|
314
349
|
/** Bot's Discord user id. Set it to answer only when the agent is mentioned. */
|
|
315
350
|
botUserId?: string;
|
|
316
351
|
/** Role ids that count as mentioning the agent, e.g. an on-call role. */
|
|
@@ -320,6 +355,8 @@ interface AgentDiscordChannelConfig {
|
|
|
320
355
|
[key: string]: unknown;
|
|
321
356
|
}
|
|
322
357
|
interface AgentPancakeChannelConfig {
|
|
358
|
+
allowedChannelIds?: string[];
|
|
359
|
+
allowedUserIds?: string[];
|
|
323
360
|
id?: string;
|
|
324
361
|
pageId?: string;
|
|
325
362
|
pageAccessToken?: string;
|
|
@@ -330,11 +367,11 @@ interface AgentPancakeChannelConfig {
|
|
|
330
367
|
[key: string]: unknown;
|
|
331
368
|
}
|
|
332
369
|
interface AgentZaloChannelConfig {
|
|
370
|
+
allowedChannelIds?: string[];
|
|
371
|
+
allowedUserIds?: string[];
|
|
333
372
|
id?: string;
|
|
334
373
|
botToken?: string;
|
|
335
374
|
webhookSecret?: string;
|
|
336
|
-
allowedUserIds?: string[];
|
|
337
|
-
allowedGroupIds?: string[];
|
|
338
375
|
trace?: "enabled" | "disabled";
|
|
339
376
|
workspaceScope?: AgentChannelWorkspaceScope;
|
|
340
377
|
[key: string]: unknown;
|
|
@@ -475,6 +512,60 @@ interface WorkspaceConfig {
|
|
|
475
512
|
harness?: WorkspaceHarnessConfig;
|
|
476
513
|
}
|
|
477
514
|
|
|
515
|
+
/**
|
|
516
|
+
* Agent policy contracts and validation.
|
|
517
|
+
* Runtime decisions are made by OPA using the same document/input shape.
|
|
518
|
+
*/
|
|
519
|
+
declare const POLICY_ACTIONS: readonly ["agent.invoke", "tool.call", "workspace.read", "workspace.write", "workspace.exec", "subagent.run", "skill.load"];
|
|
520
|
+
type PolicyAction = (typeof POLICY_ACTIONS)[number];
|
|
521
|
+
type PolicyEffect = "allow" | "deny";
|
|
522
|
+
type PolicyMode = "enforce" | "audit";
|
|
523
|
+
type PolicyConditionOperator = "equals" | "notEquals" | "in" | "notIn" | "prefix" | "contains";
|
|
524
|
+
interface PolicyCondition {
|
|
525
|
+
attribute: string;
|
|
526
|
+
operator: PolicyConditionOperator;
|
|
527
|
+
value: string | number | boolean | string[] | number[] | boolean[];
|
|
528
|
+
}
|
|
529
|
+
interface PolicyResourceSelector {
|
|
530
|
+
toolNames?: string[];
|
|
531
|
+
toolIds?: string[];
|
|
532
|
+
workspaceIds?: string[];
|
|
533
|
+
workspaceNames?: string[];
|
|
534
|
+
filePaths?: string[];
|
|
535
|
+
subagentIds?: string[];
|
|
536
|
+
skillPaths?: string[];
|
|
537
|
+
}
|
|
538
|
+
interface PolicyRule {
|
|
539
|
+
id: string;
|
|
540
|
+
effect: PolicyEffect;
|
|
541
|
+
actions: PolicyAction[];
|
|
542
|
+
resources?: PolicyResourceSelector;
|
|
543
|
+
conditions?: PolicyCondition[];
|
|
544
|
+
}
|
|
545
|
+
interface PolicyDocument {
|
|
546
|
+
version: 1;
|
|
547
|
+
/**
|
|
548
|
+
* How hard this policy bites. `audit` records what it would have done and
|
|
549
|
+
* blocks nothing; `enforce` lets its deny rules refuse, and switches the
|
|
550
|
+
* places it is attached to over to default-deny. Omitted reads as `audit`,
|
|
551
|
+
* so a freshly written policy can never break a running agent.
|
|
552
|
+
*/
|
|
553
|
+
mode?: PolicyMode;
|
|
554
|
+
rules: PolicyRule[];
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Channel records: an account-scoped row per real place a team talks — one Slack
|
|
559
|
+
* channel, one Discord channel, one repository. It binds that place to an agent
|
|
560
|
+
* and adds instructions, workspaces, policies and roles scoped to it.
|
|
561
|
+
* Distinct from `config.channels`, which holds one adapter's credentials.
|
|
562
|
+
* A record narrows and adds; it never grants capability the agent lacks.
|
|
563
|
+
*/
|
|
564
|
+
|
|
565
|
+
/** Where a reply lands: its own thread, or wherever the message came from. */
|
|
566
|
+
declare const CHANNEL_REPLY_TARGETS: readonly ["thread", "source"];
|
|
567
|
+
type ChannelReplyIn = (typeof CHANNEL_REPLY_TARGETS)[number];
|
|
568
|
+
|
|
478
569
|
/**
|
|
479
570
|
* Wire types for the public account-manage and harness APIs. These mirror
|
|
480
571
|
* the deployed API contract (docs/api-reference/openapi.yaml is the source
|
|
@@ -621,7 +712,7 @@ interface AccountPolicy {
|
|
|
621
712
|
policyId: string;
|
|
622
713
|
name: string;
|
|
623
714
|
description?: string;
|
|
624
|
-
document:
|
|
715
|
+
document: PolicyDocument;
|
|
625
716
|
status: string;
|
|
626
717
|
createdAt: string;
|
|
627
718
|
updatedAt: string;
|
|
@@ -656,9 +747,8 @@ interface ChannelRecordConfig {
|
|
|
656
747
|
name: string;
|
|
657
748
|
workspaceId: string;
|
|
658
749
|
}>;
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
policyMode?: "enforce" | "audit";
|
|
750
|
+
/** Added to whatever the agent already carries. Each policy holds its own mode. */
|
|
751
|
+
policies?: string[];
|
|
662
752
|
/**
|
|
663
753
|
* Tool names withheld in this channel, applied after the tool set is built —
|
|
664
754
|
* so it also covers sandbox tools (`bash`, `read`, …) that `config.tools`
|
|
@@ -666,18 +756,18 @@ interface ChannelRecordConfig {
|
|
|
666
756
|
*/
|
|
667
757
|
denyTools?: string[];
|
|
668
758
|
/**
|
|
669
|
-
* Where the reply lands. `
|
|
670
|
-
* when the message already did. Slack only — no other provider
|
|
671
|
-
* runtime a second place to reply.
|
|
759
|
+
* Where the reply lands. `source` answers wherever the message came from, and
|
|
760
|
+
* threads only when the message already did. Slack only — no other provider
|
|
761
|
+
* gives the runtime a second place to reply.
|
|
672
762
|
*/
|
|
673
|
-
|
|
763
|
+
replyIn?: ChannelReplyIn;
|
|
674
764
|
workspaceScope?: AgentChannelWorkspaceScope;
|
|
675
765
|
/** Images the agent may stand a sandbox up from for a thread here. */
|
|
676
766
|
sandboxImages?: string[];
|
|
677
|
-
/** Named groups of people, readable from policy conditions as `
|
|
767
|
+
/** Named groups of people, readable from policy conditions as `userRoles`. */
|
|
678
768
|
tagRoles?: Array<{
|
|
679
769
|
roleId: string;
|
|
680
|
-
|
|
770
|
+
userIds: string[];
|
|
681
771
|
}>;
|
|
682
772
|
}
|
|
683
773
|
/** The stage a tool belongs to. Tools with the same name in two stages are two tools. */
|
|
@@ -886,14 +976,14 @@ declare class BroodsAccountClient {
|
|
|
886
976
|
createPolicy(input: {
|
|
887
977
|
name: string;
|
|
888
978
|
description?: string;
|
|
889
|
-
document:
|
|
979
|
+
document: PolicyDocument;
|
|
890
980
|
}): Promise<AccountPolicy>;
|
|
891
981
|
getPolicy(policyId: string): Promise<AccountPolicy | null>;
|
|
892
982
|
/** PATCH a policy. `description: null` clears it. Returns null when the policy is gone. */
|
|
893
983
|
updatePolicy(policyId: string, patch: {
|
|
894
984
|
name?: string;
|
|
895
985
|
description?: string | null;
|
|
896
|
-
document?:
|
|
986
|
+
document?: PolicyDocument;
|
|
897
987
|
status?: string;
|
|
898
988
|
}): Promise<AccountPolicy | null>;
|
|
899
989
|
deletePolicy(policyId: string): Promise<boolean>;
|
package/dist/account.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a,b,c}from"./chunk-ce3yb3yz.js";import"./chunk-
|
|
1
|
+
import{a,b,c}from"./chunk-ce3yb3yz.js";import"./chunk-3cvxks4m.js";export{b as BroodsAccountApiError,c as BroodsAccountClient,a as envPlaceholder};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
function
|
|
2
|
-
`);o=i.pop()??"";for(let a of i)if(a.startsWith("data: "))yield a.slice(6)}}finally{n.releaseLock()}}import{mkdir as
|
|
3
|
-
`,"utf8")}function
|
|
4
|
-
`).replace(/\\"/g,'"');if(e.startsWith("'")&&e.endsWith("'"))return e.slice(1,-1);let n=e.indexOf(" #");return n>=0?e.slice(0,n).trimEnd():e}function le(){try{let e=JSON.parse(v(m,"utf8"));if(typeof e.baseUrl!=="string"||typeof e.token!=="string")return null;return{baseUrl:g(e.baseUrl),...typeof e.dashboardUrl==="string"?{dashboardUrl:g(e.dashboardUrl)}:{},token:e.token}}catch{return null}}var A="https://gateway.broods.app";class H extends Error{accepted;constructor(e){super(`Ingress ${e.eventId} was accepted asynchronously with status ${e.status??"queued"}`);this.accepted=e;this.name="IngressAcceptedError"}}class ge{baseUrl;apiKey;fetchImpl;constructor(e={}){k(),this.baseUrl=w(e.baseUrl||e.host||process.env.BROODS_BASE_URL||process.env.BROODS_HOST||A),this.apiKey=e.apiKey||process.env.BROODS_API_KEY||void 0,this.fetchImpl=e.fetch??fetch}channelWebhookUrl(e){return`${this.baseUrl}${e.webhookPath.startsWith("/")?"":"/"}${e.webhookPath}`}accountWebhookUrl(e,n){let t=[e,n].map(encodeURIComponent);return`${this.baseUrl}/webhooks/${t.join("/")}`}stageWebhookUrl(e,n,t){let o=[e,"dev",n,t].map(encodeURIComponent);return`${this.baseUrl}/webhooks/${o.join("/")}`}agent(e,n){if(typeof e==="string"){let o=e,r=n??"";if(!r)throw Error(`Agent ${o} is missing a generated id. Run broods dev --once or broods deploy to sync it first.`);return{id:r,run:(s)=>this.run({...s,agentName:o,agentId:r}),runAsync:(s)=>this.runAsync({...s,agentName:o,agentId:r}),stream:(s)=>this.stream({...s,agentName:o,agentId:r})}}let t=e;if(!t.id)throw Error(`Agent ${t.name} is missing a generated id. Run broods dev --once or broods deploy to sync it first.`);return{id:t.id,run:(o)=>this.run(t,o),runAsync:(o)=>this.runAsync(t,o),stream:(o)=>this.stream(t,o)}}async run(e,n){let t=[],o="",r=n?this.stream(e,n):this.stream(e);for await(let s of r)if(t.push(s),s.type==="text-delta")o+=s.text;return{text:o,events:t}}async*stream(e,n){let t=n?{...n,agentId:e.id,agentName:e.name}:e,o=_(t,"cli"),r=n?this.scopedUrl(e):this.baseUrl,s=await this.openStream(o,r);if(!s.ok)throw Error(`Run failed: ${s.status} ${await s.text()}`);if(s.status===202)throw new H(K(await s.json(),o));if(!s.body)throw Error("Run response has no body");for await(let i of E(s.body)){let a;try{a=JSON.parse(i)}catch{continue}if(a.type==="error")throw Error(`Agent run failed: ${me(a.error)}`);yield a}}async runAsync(e,n){let t=n?{...n,agentId:e.id,agentName:e.name}:e,o=_(t,"async"),r=n?this.scopedUrl(e,"/async"):`${this.baseUrl}/async`,s=await this.fetchJson(r,{method:"POST",headers:this.apiKeyHeaders(),body:JSON.stringify(o)});if(s.status!==202)throw Error(`Async run failed: ${s.status} ${await pe(s,"202 JSON")}`);let i=K(await s.json(),o);return{...i,conversationKey:o.conversationKey,poll:()=>this.getAsyncStatus(i),wait:(a)=>this.waitForAsyncStatus(i,a)}}async getAsyncStatus(e,n={}){let t=this.resolveStatusUrl(e,n),o=await this.fetchJson(t,{method:"GET",headers:this.apiKeyHeaders()});if(o.status===404)return{status:"not_found"};if(!o.ok)throw Error(`Status check failed: ${o.status} ${await o.text()}`);return await o.json()}async waitForAsyncStatus(e,n={}){let t=Date.now()+(n.timeoutMs??180000),o=n.intervalMs??2000;while(Date.now()<t){if(n.signal?.aborted)throw Error("Async status polling aborted.");let r=await this.getAsyncStatus(e,n);if(r.status==="awaiting_approval"||r.status==="completed"||r.status==="failed"||r.status==="expired"||r.status==="not_found")return r;await ye(o,n.signal)}throw Error("Polling timeout")}async createCron(e){let n=await this.fetchJson(`${this.baseUrl}/v1/crons`,{method:"POST",headers:this.apiKeyHeaders(),body:JSON.stringify(fe(e))});if(n.status!==201)throw Error(`Create cron job failed: ${n.status} ${await p(n)}`);return await n.json()}async listCrons(){let e=await this.fetchJson(`${this.baseUrl}/v1/crons`,{method:"GET",headers:this.apiKeyHeaders()});if(!e.ok)throw Error(`List cron jobs failed: ${e.status} ${await p(e)}`);return(await e.json()).crons}async getCron(e){let n=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(e)}`,{method:"GET",headers:this.apiKeyHeaders()});if(n.status===404)return null;if(!n.ok)throw Error(`Get cron job failed: ${n.status} ${await p(n)}`);return await n.json()}async listCronRuns(e,n={}){let t=new URLSearchParams;if(n.limit!==void 0)t.set("limit",String(n.limit));let o=t.size>0?`?${t}`:"",r=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(e)}/runs${o}`,{method:"GET",headers:this.apiKeyHeaders()});if(!r.ok)throw Error(`List cron job runs failed: ${r.status} ${await p(r)}`);return(await r.json()).runs}async updateCron(e,n){let t=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(e)}`,{method:"PATCH",headers:this.apiKeyHeaders(),body:JSON.stringify(n)});if(!t.ok)throw Error(`Update cron job failed: ${t.status} ${await p(t)}`);return await t.json()}async deleteCron(e){let n=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(e)}`,{method:"DELETE",headers:this.apiKeyHeaders()});if(n.status===404)return!1;if(!n.ok)throw Error(`Delete cron job failed: ${n.status} ${await p(n)}`);return(await n.json()).deleted}scopedUrl(e,n=""){if(e.projectSlug&&e.stageSlug&&e.endpointId)return`${this.baseUrl}/v1/${encodeURIComponent(e.projectSlug)}/agents/${encodeURIComponent(e.stageSlug)}/${encodeURIComponent(e.endpointId)}${n}`;return`${this.baseUrl}${n}`}async openStream(e,n){if(this.apiKey)return await this.fetchCore(e,n,{Authorization:`Bearer ${this.apiKey}`});throw Error(`BroodsClient streams directly from the core service at ${this.baseUrl}. Provide apiKey. For a self-hosted core service, set host/baseUrl or BROODS_HOST/BROODS_BASE_URL.`)}async fetchCore(e,n,t){try{return await this.fetchImpl(n,{method:"POST",headers:{"Content-Type":"application/json",Accept:"text/event-stream",...t},body:JSON.stringify(e)})}catch(o){throw Error(`Cannot access the broods core service at ${n}. The SDK uses ${A} by default; set host/baseUrl or BROODS_HOST/BROODS_BASE_URL to your own core service URL if your account uses a custom deployment. Cause: ${o instanceof Error?o.message:String(o)}`)}}apiKeyHeaders(){if(!this.apiKey)throw Error("BroodsClient requires apiKey or BROODS_API_KEY.");return{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}}async fetchJson(e,n){try{return await this.fetchImpl(e,{...n,headers:{"Content-Type":"application/json",...n.headers??{}}})}catch(t){throw Error(`Cannot access the broods core service at ${e}. Cause: ${t instanceof Error?t.message:String(t)}`)}}resolveStatusUrl(e,n){if(typeof e!=="string")return e.statusUrl;if(/^https?:\/\//.test(e))return e;if(!n.agentId)throw Error("Polling by status id requires agentId.");return ue(this.baseUrl,e,n.agentId)}}function w(e){let n=e.trim(),t=/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(n)?n:`https://${n}`;return g(t)}function _(e,n){let t=e.eventId??`${n}-${Date.now()}`;return{agentId:e.agentId,eventId:t,conversationKey:e.conversationKey??t,events:b(e),...e.mode!==void 0?{mode:e.mode}:{},...e.idempotencyKey!==void 0?{idempotencyKey:e.idempotencyKey}:{},...e.system!==void 0?{system:e.system}:{},...e.model!==void 0?{model:e.model}:{}}}function K(e,n){if(!e||typeof e!=="object")throw Error("Async response must be an object");let t=e.statusUrl;if(typeof t!=="string"||t.length===0)throw Error("Async response missing statusUrl");let o=de(t);if(!o.statusId)throw Error("Async response statusUrl missing status id");let r=typeof e.eventId==="string"?e.eventId:o.statusId,s=e.status,i=e.requestedMode;return{statusUrl:t,statusId:o.statusId,eventId:r,agentId:o.agentId??n.agentId,...s==="accepted"||s==="queued"||s==="applied"||s==="processing"?{status:s}:{},...i==="reject"||i==="followup"||i==="collect"||i==="steer"?{requestedMode:i}:{}}}function de(e){let n=new URL(e),t=n.pathname.match(/\/status\/([^/]+)$/);return{statusId:t?.[1]?decodeURIComponent(t[1]):void 0,agentId:n.searchParams.get("agentId")??void 0}}function ue(e,n,t){return`${w(e)}/status/${encodeURIComponent(n)}?agentId=${encodeURIComponent(t)}`}async function pe(e,n){if((e.headers.get("content-type")??"").toLowerCase().includes("text/event-stream"))return await e.body?.cancel().catch(()=>{}),`expected ${n}, but the server returned an SSE stream. This usually means the core deployment routed /async to the direct streaming runner instead of the async handler.`;let o=await e.text();if(o.length<=2000)return o;return`${o.slice(0,2000)}... [truncated ${o.length-2000} chars]`}async function p(e){let n=await e.text();if(n.includes("Request body must include eventId and conversationKey"))return`${n}. Cron job APIs must be served by the configured baseUrl. Prefer defining stable cron jobs with defineCron(...) in broods/ and syncing with \`broods dev\` or \`broods deploy\`.`;return n}function fe(e){if("agentId"in e)return e;let n=e.agent,t=typeof n==="string"?n:n.id,{agent:o,...r}=e;return{...r,agentId:t}}function ye(e,n){return new Promise((t,o)=>{if(n?.aborted){o(Error("Async status polling aborted."));return}let r=setTimeout(t,e);n?.addEventListener("abort",()=>{clearTimeout(r),o(Error("Async status polling aborted."))},{once:!0})})}function me(e){if(typeof e==="string")return e;if(!e||typeof e!=="object")return String(e);let n=e,t=n.data?.error?.message??n.message??n.responseBody??JSON.stringify(e),o=n.name?`${n.name}: `:"",r=n.statusCode?` (HTTP ${n.statusCode})`:"";return`${o}${t}${r}`}var he=2000,B=0,h=1;class Se{baseUrl;apiKey;WebSocketImpl;connectTimeoutMs;constructor(e={}){if(k(),this.baseUrl=J(e.baseUrl||e.host||process.env.BROODS_BASE_URL||process.env.BROODS_HOST||A),this.apiKey=e.apiKey||process.env.BROODS_API_KEY||"",!this.apiKey)throw Error("BroodsWebSocketClient requires apiKey or BROODS_API_KEY.");this.WebSocketImpl=e.WebSocket,this.connectTimeoutMs=e.connectTimeoutMs??he}subscribe(e,n={}){let t=this.resolveWebSocket(),o=this.buildUrl(e),r=Re(e),s=j(this.baseUrl),i=new t(o),a=!1,l=!1,d=setTimeout(()=>{if(!a)y(s)},this.connectTimeoutMs),I=(c=1000,R="client closed")=>{if(l)return;if(l=!0,clearTimeout(d),e.signal?.removeEventListener("abort",S),i.readyState===h||i.readyState===B)i.close(c,R)},y=(c)=>{n.onError?.(c),I(1011,"websocket error")},Y=()=>{n.onDone?.(),I(1000,"done")},S=()=>{if(i.readyState===h)i.send(JSON.stringify({type:"cancel"}));y(Error("WebSocket run aborted."))};return e.signal?.addEventListener("abort",S,{once:!0}),i.onopen=()=>{if(a=!0,clearTimeout(d),e.signal?.aborted){S();return}i.send(JSON.stringify({type:"execute",agentId:r,events:b(e),sessionId:e.sessionId,...e.eventId!==void 0?{eventId:e.eventId}:{},...e.mode!==void 0?{mode:e.mode}:{},...e.idempotencyKey!==void 0?{idempotencyKey:e.idempotencyKey}:{},...e.system!==void 0?{system:e.system}:{},...e.model!==void 0?{model:e.model}:{}}))},i.onmessage=(c)=>{if(typeof c.data!=="string")return;let R=G(c.data);if(!R)return;let C=L(R,n);switch(n.onMessage?.(C),C.type){case"meta":n.onMeta?.(C);break;case"done":Y();break;case"error":y(Error(q(C.error)));break}},i.onerror=()=>{y(s)},i.onclose=(c)=>{if(clearTimeout(d),e.signal?.removeEventListener("abort",S),l||c.code===1000)return;y(Error(c.reason||"WebSocket connection closed."))},{url:o,sendControl:function(c){if(i.readyState!==h)throw Error("WebSocket is not open");i.send(JSON.stringify({type:"control",...c}))},close:I}}attach(e,n={}){let t=this.resolveWebSocket(),o=this.buildUrl(e),r=new t(o),s=!1,i=(a=1000,l="client closed")=>{if(s)return;if(s=!0,r.readyState===h||r.readyState===B)r.close(a,l)};return r.onopen=()=>r.send(JSON.stringify({type:"attach",...e,signal:void 0,agent:void 0})),r.onmessage=(a)=>{if(typeof a.data!=="string")return;let l=G(a.data);if(!l)return;let d=L(l,n);if(n.onMessage?.(d),d.type==="done")n.onDone?.();if(d.type==="error")n.onError?.(Error(q(d.error)))},r.onerror=()=>n.onError?.(j(this.baseUrl)),r.onclose=(a)=>{if(!s&&a.code!==1000)n.onError?.(Error(a.reason||"WebSocket connection closed."))},{url:o,sendControl:function(a){if(r.readyState!==h)throw Error("WebSocket is not open");r.send(JSON.stringify({type:"control",...a}))},close:i}}async*stream(e){let n=[],t=!1,o=null,r=null,s=()=>{r?.(),r=null},i=this.subscribe(e,{onMessage:function(a){n.push(a),s()},onDone:function(){t=!0,s()},onError:function(a){o=a,s()}});try{while(!0){if(n.length>0){yield n.shift();continue}if(o)throw o;if(t)return;await new Promise((a)=>{r=a})}}finally{i.close()}}buildUrl(e){let n=Ce(e),t=e.projectSlug??e.agent?.projectSlug,o=e.stageSlug??e.agent?.stageSlug,r=t?`/${t}`:"",s=o?`/${o}`:"";return`${be(this.baseUrl)}/v1${r}/agents${s}/${encodeURIComponent(n)}/ws?token=${encodeURIComponent(this.apiKey)}`}resolveWebSocket(){let e=this.WebSocketImpl??globalThis.WebSocket;if(!e)throw Error("WebSocket is not available. Pass a WebSocket implementation in BroodsWebSocketClient options.");return e}}function Re(e){let n=e.agentId??e.agent?.id??e.endpointId;if(!n)throw Error("WebSocket run requires agent, agentId, or endpointId.");return n}function Ce(e){let n=e.endpointId??e.agent?.endpointId??e.agent?.id;if(!n)throw Error("WebSocket run requires agent or endpointId.");return n}function be(e){let n=new URL(J(e));if(n.protocol==="https:")n.protocol="wss:";if(n.protocol==="http:")n.protocol="ws:";return g(n.toString())}function J(e){let n=e.trim();if(/^wss?:\/\//.test(n))return g(n);return w(n)}function j(e){return Error(`Cannot access the WebSocket service at ${e}.`)}function L(e,n){if(e.type!=="output")return e;let t=e;return n.onOutput?.(t),t.data}function G(e){try{let n=JSON.parse(e);return typeof n==="object"&&n!==null&&typeof n.type==="string"?n:null}catch{return null}}function q(e){if(typeof e==="string")return e;if(e&&typeof e==="object"&&typeof e.message==="string")return e.message;return e===void 0?"WebSocket stream error.":JSON.stringify(e)}var Z=Symbol.for("broods.resource"),z=Symbol.for("broods.config"),V=Symbol.for("broods.channel"),F=/^[A-Z][A-Z0-9_]{0,63}$/;var Ze=new Proxy((e)=>{if(!F.test(e))throw Error("env name must match /^[A-Z][A-Z0-9_]*$/ and be at most 64 characters.");return{__beeblastEnv:!0,name:e}},{get:function(e,n,t){if(typeof n==="string"&&F.test(n))throw Error(`env.${n} is not supported; use env("${n}")`);return Reflect.get(e,n,t)}});function u(e,n,t,o){return{[Z]:!0,kind:e,name:n,...t?{description:t}:{},config:o}}function f(e,n){let{workspaceScope:t,...o}=n;return{[V]:!0,kind:"channel",type:e,...t?{workspaceScope:t}:{},config:{...o,...t?{workspaceScope:t}:{}}}}function ze(e){return f("telegram",e)}function Ve(e){return f("github",e)}function Ye(e){return f("slack",e)}function Xe(e){return f("discord",e)}function Qe(e){return f("pancake",e)}function en(e){return f("zalo",e)}function nn(e){return{[z]:!0,config:e}}function tn(e){let{name:n,description:t,...o}=e;return u("agent",n,t,o)}function on(e){return e}function rn(e){let{name:n,description:t,...o}=e;return u("workspace",n,t,o)}function sn(e){let{name:n,description:t,...o}=e;return u("sandbox",n,t,o)}function an(e){let{name:n,description:t,...o}=e;return u("skill",n,t,o)}function cn(e){let{name:n,...t}=e;return u("tool",n,void 0,t)}function ln(e){let{name:n,description:t,...o}=e;return u("policy",n,t,o)}function gn(e){let{name:n,description:t,...o}=e;return u("channelRecord",n,t,o)}function dn(e){let{name:n,description:t,...o}=e;return u("cron",n,t,o)}function un(e){return Boolean(e&&typeof e==="object"&&e[Z])}function pn(e){return Boolean(e&&typeof e==="object"&&e[V])}function fn(e){return Boolean(e&&typeof e==="object"&&e[z])}
|
|
5
|
-
export{
|
|
1
|
+
function k(e){if(e.events&&e.input!==void 0)throw Error("Run input accepts either `input` or `events`, not both");if(e.events&&e.events.length>0)return e.events;if(typeof e.input==="string")return[{role:"user",content:[{type:"text",text:e.input}]}];throw Error("Run input requires `input` (string) or a non-empty `events` array")}async function*D(e){let n=e.getReader(),t=new TextDecoder,o="";try{while(!0){let{done:r,value:s}=await n.read();if(r)break;o+=t.decode(s,{stream:!0});let i=o.split(`
|
|
2
|
+
`);o=i.pop()??"";for(let a of i)if(a.startsWith("data: "))yield a.slice(6)}}finally{n.releaseLock()}}import{mkdir as Q,readFile as ee,writeFile as ne}from"node:fs/promises";import{homedir as te}from"node:os";import{dirname as oe,join as re}from"node:path";var Ee="broods",De="_generated",h=re(te(),".broods","config.json"),N="broods.app",T="dashboard",se="gateway";async function Ne(){let e=process.env.BROODS_TOKEN,n=process.env.BROODS_BASE_URL;if(e&&n)return{baseUrl:d(n),token:e,createdAt:new Date().toISOString()};try{let t=JSON.parse(await ee(h,"utf8"));if(typeof t.baseUrl!=="string"||!t.baseUrl)return null;return t}catch{return null}}async function Te(e){await Q(oe(h),{recursive:!0}),await ne(h,`${JSON.stringify(e,null,2)}
|
|
3
|
+
`,"utf8")}function O(){return process.env.BROODS_STAGE}function d(e){let n=e.length;while(n>0&&e.charCodeAt(n-1)===47)n-=1;return e.slice(0,n)}function Oe(e){try{let n=new URL(e);if(!n.hostname.startsWith(`${T}.`))return;let t=n.hostname.slice(T.length+1);if(t!==N&&!t.endsWith(`.${N}`))return;return n.hostname=`${se}.${t}`,d(n.origin)}catch{return}}import{existsSync as U,readFileSync as E,statSync as ie}from"node:fs";import{join as W,resolve as M}from"node:path";var v=new Map,ae=new Set(Object.keys(process.env)),P=new Set;function I(e=process.cwd()){ce(e);let n=de();return{dashboardUrl:process.env.BROODS_DASHBOARD_URL??n?.dashboardUrl,baseUrl:process.env.BROODS_BASE_URL??n?.baseUrl,token:process.env.BROODS_TOKEN??n?.token,project:process.env.BROODS_PROJECT,stage:O()}}function $e(e,n=process.cwd()){let t=process.env[e];if(t===void 0)return!1;let o=M(n);for(let r of[".env.local",".env"]){let s=W(o,r);if(!U(s))continue;let i=_(E(s,"utf8"));if(i[e]!==void 0)return i[e]!==t}return!0}function ce(e){let n=M(e),t=process.env.BROODS_RELOAD_ENV==="1";for(let o of[".env",".env.local"]){let r=W(n,o);if(!U(r)){v.delete(r);continue}let s=ie(r).mtimeMs,i=v.get(r);if(i&&i.mtimeMs===s)continue;v.set(r,{mtimeMs:s});for(let[a,u]of Object.entries(_(E(r,"utf8"))))if(t||!ae.has(a)||P.has(a))process.env[a]=u,P.add(a)}}function _(e){let n={};for(let t of e.split(/\r?\n/)){let o=t.trim();if(!o||o.startsWith("#"))continue;let r=o.startsWith("export ")?o.slice(7).trim():o,s=r.indexOf("=");if(s<=0)continue;let i=r.slice(0,s).trim();if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(i))continue;n[i]=ue(r.slice(s+1).trim())}return n}function ue(e){if(e.startsWith('"')&&e.endsWith('"'))return e.slice(1,-1).replace(/\\n/g,`
|
|
4
|
+
`).replace(/\\"/g,'"');if(e.startsWith("'")&&e.endsWith("'"))return e.slice(1,-1);let n=e.indexOf(" #");return n>=0?e.slice(0,n).trimEnd():e}function de(){try{let e=JSON.parse(E(h,"utf8"));if(typeof e.baseUrl!=="string"||typeof e.token!=="string")return null;return{baseUrl:d(e.baseUrl),...typeof e.dashboardUrl==="string"?{dashboardUrl:d(e.dashboardUrl)}:{},token:e.token}}catch{return null}}var x="https://gateway.broods.app";class B extends Error{accepted;constructor(e){super(`Ingress ${e.eventId} was accepted asynchronously with status ${e.status??"queued"}`);this.accepted=e;this.name="IngressAcceptedError"}}class le{baseUrl;apiKey;fetchImpl;constructor(e={}){I(),this.baseUrl=A(e.baseUrl||e.host||process.env.BROODS_BASE_URL||process.env.BROODS_HOST||x),this.apiKey=e.apiKey||process.env.BROODS_API_KEY||void 0,this.fetchImpl=e.fetch??fetch}channelWebhookUrl(e){return`${this.baseUrl}${e.webhookPath.startsWith("/")?"":"/"}${e.webhookPath}`}accountWebhookUrl(e,n){let t=[e,n].map(encodeURIComponent);return`${this.baseUrl}/webhooks/${t.join("/")}`}stageWebhookUrl(e,n,t){let o=[e,"dev",n,t].map(encodeURIComponent);return`${this.baseUrl}/webhooks/${o.join("/")}`}agent(e,n){if(typeof e==="string"){let o=e,r=n??"";if(!r)throw Error(`Agent ${o} is missing a generated id. Run broods dev --once or broods deploy to sync it first.`);return{id:r,run:(s)=>this.run({...s,agentName:o,agentId:r}),runAsync:(s)=>this.runAsync({...s,agentName:o,agentId:r}),stream:(s)=>this.stream({...s,agentName:o,agentId:r})}}let t=e;if(!t.id)throw Error(`Agent ${t.name} is missing a generated id. Run broods dev --once or broods deploy to sync it first.`);return{id:t.id,run:(o)=>this.run(t,o),runAsync:(o)=>this.runAsync(t,o),stream:(o)=>this.stream(t,o)}}async run(e,n){let t=[],o="",r=n?this.stream(e,n):this.stream(e);for await(let s of r)if(t.push(s),s.type==="text-delta")o+=s.text;return{text:o,events:t}}async*stream(e,n){let t=n?{...n,agentId:e.id,agentName:e.name}:e,o=K(t,"cli"),r=n?this.scopedUrl(e):this.baseUrl,s=await this.openStream(o,r);if(!s.ok)throw Error(`Run failed: ${s.status} ${await s.text()}`);if(s.status===202)throw new B(H(await s.json(),o));if(!s.body)throw Error("Run response has no body");for await(let i of D(s.body)){let a;try{a=JSON.parse(i)}catch{continue}if(a.type==="error")throw Error(`Agent run failed: ${he(a.error)}`);yield a}}async runAsync(e,n){let t=n?{...n,agentId:e.id,agentName:e.name}:e,o=K(t,"async"),r=n?this.scopedUrl(e,"/async"):`${this.baseUrl}/async`,s=await this.fetchJson(r,{method:"POST",headers:this.apiKeyHeaders(),body:JSON.stringify(o)});if(s.status!==202)throw Error(`Async run failed: ${s.status} ${await fe(s,"202 JSON")}`);let i=H(await s.json(),o);return{...i,conversationKey:o.conversationKey,poll:()=>this.getAsyncStatus(i),wait:(a)=>this.waitForAsyncStatus(i,a)}}async getAsyncStatus(e,n={}){let t=this.resolveStatusUrl(e,n),o=await this.fetchJson(t,{method:"GET",headers:this.apiKeyHeaders()});if(o.status===404)return{status:"not_found"};if(!o.ok)throw Error(`Status check failed: ${o.status} ${await o.text()}`);return await o.json()}async waitForAsyncStatus(e,n={}){let t=Date.now()+(n.timeoutMs??180000),o=n.intervalMs??2000;while(Date.now()<t){if(n.signal?.aborted)throw Error("Async status polling aborted.");let r=await this.getAsyncStatus(e,n);if(r.status==="awaiting_approval"||r.status==="completed"||r.status==="failed"||r.status==="expired"||r.status==="not_found")return r;await ye(o,n.signal)}throw Error("Polling timeout")}async createCron(e){let n=await this.fetchJson(`${this.baseUrl}/v1/crons`,{method:"POST",headers:this.apiKeyHeaders(),body:JSON.stringify(me(e))});if(n.status!==201)throw Error(`Create cron job failed: ${n.status} ${await p(n)}`);return await n.json()}async listCrons(){let e=await this.fetchJson(`${this.baseUrl}/v1/crons`,{method:"GET",headers:this.apiKeyHeaders()});if(!e.ok)throw Error(`List cron jobs failed: ${e.status} ${await p(e)}`);return(await e.json()).crons}async getCron(e){let n=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(e)}`,{method:"GET",headers:this.apiKeyHeaders()});if(n.status===404)return null;if(!n.ok)throw Error(`Get cron job failed: ${n.status} ${await p(n)}`);return await n.json()}async listCronRuns(e,n={}){let t=new URLSearchParams;if(n.limit!==void 0)t.set("limit",String(n.limit));let o=t.size>0?`?${t}`:"",r=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(e)}/runs${o}`,{method:"GET",headers:this.apiKeyHeaders()});if(!r.ok)throw Error(`List cron job runs failed: ${r.status} ${await p(r)}`);return(await r.json()).runs}async updateCron(e,n){let t=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(e)}`,{method:"PATCH",headers:this.apiKeyHeaders(),body:JSON.stringify(n)});if(!t.ok)throw Error(`Update cron job failed: ${t.status} ${await p(t)}`);return await t.json()}async deleteCron(e){let n=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(e)}`,{method:"DELETE",headers:this.apiKeyHeaders()});if(n.status===404)return!1;if(!n.ok)throw Error(`Delete cron job failed: ${n.status} ${await p(n)}`);return(await n.json()).deleted}scopedUrl(e,n=""){if(e.projectSlug&&e.stageSlug&&e.endpointId)return`${this.baseUrl}/v1/${encodeURIComponent(e.projectSlug)}/agents/${encodeURIComponent(e.stageSlug)}/${encodeURIComponent(e.endpointId)}${n}`;return`${this.baseUrl}${n}`}async openStream(e,n){if(this.apiKey)return await this.fetchCore(e,n,{Authorization:`Bearer ${this.apiKey}`});throw Error(`BroodsClient streams directly from the core service at ${this.baseUrl}. Provide apiKey. For a self-hosted core service, set host/baseUrl or BROODS_HOST/BROODS_BASE_URL.`)}async fetchCore(e,n,t){try{return await this.fetchImpl(n,{method:"POST",headers:{"Content-Type":"application/json",Accept:"text/event-stream",...t},body:JSON.stringify(e)})}catch(o){throw Error(`Cannot access the broods core service at ${n}. The SDK uses ${x} by default; set host/baseUrl or BROODS_HOST/BROODS_BASE_URL to your own core service URL if your account uses a custom deployment. Cause: ${o instanceof Error?o.message:String(o)}`)}}apiKeyHeaders(){if(!this.apiKey)throw Error("BroodsClient requires apiKey or BROODS_API_KEY.");return{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}}async fetchJson(e,n){try{return await this.fetchImpl(e,{...n,headers:{"Content-Type":"application/json",...n.headers??{}}})}catch(t){throw Error(`Cannot access the broods core service at ${e}. Cause: ${t instanceof Error?t.message:String(t)}`)}}resolveStatusUrl(e,n){if(typeof e!=="string")return e.statusUrl;if(/^https?:\/\//.test(e))return e;if(!n.agentId)throw Error("Polling by status id requires agentId.");return pe(this.baseUrl,e,n.agentId)}}function A(e){let n=e.trim(),t=/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(n)?n:`https://${n}`;return d(t)}function K(e,n){let t=e.eventId??`${n}-${Date.now()}`;return{agentId:e.agentId,eventId:t,conversationKey:e.conversationKey??t,events:k(e),...e.mode!==void 0?{mode:e.mode}:{},...e.idempotencyKey!==void 0?{idempotencyKey:e.idempotencyKey}:{},...e.system!==void 0?{system:e.system}:{},...e.model!==void 0?{model:e.model}:{}}}function H(e,n){if(!e||typeof e!=="object")throw Error("Async response must be an object");let t=e.statusUrl;if(typeof t!=="string"||t.length===0)throw Error("Async response missing statusUrl");let o=ge(t);if(!o.statusId)throw Error("Async response statusUrl missing status id");let r=typeof e.eventId==="string"?e.eventId:o.statusId,s=e.status,i=e.requestedMode;return{statusUrl:t,statusId:o.statusId,eventId:r,agentId:o.agentId??n.agentId,...s==="accepted"||s==="queued"||s==="applied"||s==="processing"?{status:s}:{},...i==="reject"||i==="followup"||i==="collect"||i==="steer"?{requestedMode:i}:{}}}function ge(e){let n=new URL(e),t=n.pathname.match(/\/status\/([^/]+)$/);return{statusId:t?.[1]?decodeURIComponent(t[1]):void 0,agentId:n.searchParams.get("agentId")??void 0}}function pe(e,n,t){return`${A(e)}/status/${encodeURIComponent(n)}?agentId=${encodeURIComponent(t)}`}async function fe(e,n){if((e.headers.get("content-type")??"").toLowerCase().includes("text/event-stream"))return await e.body?.cancel().catch(()=>{}),`expected ${n}, but the server returned an SSE stream. This usually means the core deployment routed /async to the direct streaming runner instead of the async handler.`;let o=await e.text();if(o.length<=2000)return o;return`${o.slice(0,2000)}... [truncated ${o.length-2000} chars]`}async function p(e){let n=await e.text();if(n.includes("Request body must include eventId and conversationKey"))return`${n}. Cron job APIs must be served by the configured baseUrl. Prefer defining stable cron jobs with defineCron(...) in broods/ and syncing with \`broods dev\` or \`broods deploy\`.`;return n}function me(e){if("agentId"in e)return e;let n=e.agent,t=typeof n==="string"?n:n.id,{agent:o,...r}=e;return{...r,agentId:t}}function ye(e,n){return new Promise((t,o)=>{if(n?.aborted){o(Error("Async status polling aborted."));return}let r=setTimeout(t,e);n?.addEventListener("abort",()=>{clearTimeout(r),o(Error("Async status polling aborted."))},{once:!0})})}function he(e){if(typeof e==="string")return e;if(!e||typeof e!=="object")return String(e);let n=e,t=n.data?.error?.message??n.message??n.responseBody??JSON.stringify(e),o=n.name?`${n.name}: `:"",r=n.statusCode?` (HTTP ${n.statusCode})`:"";return`${o}${t}${r}`}var Ce=2000,j=0,C=1;class Se{baseUrl;apiKey;WebSocketImpl;connectTimeoutMs;constructor(e={}){if(I(),this.baseUrl=F(e.baseUrl||e.host||process.env.BROODS_BASE_URL||process.env.BROODS_HOST||x),this.apiKey=e.apiKey||process.env.BROODS_API_KEY||"",!this.apiKey)throw Error("BroodsWebSocketClient requires apiKey or BROODS_API_KEY.");this.WebSocketImpl=e.WebSocket,this.connectTimeoutMs=e.connectTimeoutMs??Ce}subscribe(e,n={}){let t=this.resolveWebSocket(),o=this.buildUrl(e),r=Re(e),s=L(this.baseUrl),i=new t(o),a=!1,u=!1,l=setTimeout(()=>{if(!a)y(s)},this.connectTimeoutMs),w=(c=1000,R="client closed")=>{if(u)return;if(u=!0,clearTimeout(l),e.signal?.removeEventListener("abort",S),i.readyState===C||i.readyState===j)i.close(c,R)},y=(c)=>{n.onError?.(c),w(1011,"websocket error")},X=()=>{n.onDone?.(),w(1000,"done")},S=()=>{if(i.readyState===C)i.send(JSON.stringify({type:"cancel"}));y(Error("WebSocket run aborted."))};return e.signal?.addEventListener("abort",S,{once:!0}),i.onopen=()=>{if(a=!0,clearTimeout(l),e.signal?.aborted){S();return}i.send(JSON.stringify({type:"execute",agentId:r,events:k(e),sessionId:e.sessionId,...e.eventId!==void 0?{eventId:e.eventId}:{},...e.mode!==void 0?{mode:e.mode}:{},...e.idempotencyKey!==void 0?{idempotencyKey:e.idempotencyKey}:{},...e.system!==void 0?{system:e.system}:{},...e.model!==void 0?{model:e.model}:{}}))},i.onmessage=(c)=>{if(typeof c.data!=="string")return;let R=q(c.data);if(!R)return;let b=G(R,n);switch(n.onMessage?.(b),b.type){case"meta":n.onMeta?.(b);break;case"done":X();break;case"error":y(Error(J(b.error)));break}},i.onerror=()=>{y(s)},i.onclose=(c)=>{if(clearTimeout(l),e.signal?.removeEventListener("abort",S),u||c.code===1000)return;y(Error(c.reason||"WebSocket connection closed."))},{url:o,sendControl:function(c){if(i.readyState!==C)throw Error("WebSocket is not open");i.send(JSON.stringify({type:"control",...c}))},close:w}}attach(e,n={}){let t=this.resolveWebSocket(),o=this.buildUrl(e),r=new t(o),s=!1,i=(a=1000,u="client closed")=>{if(s)return;if(s=!0,r.readyState===C||r.readyState===j)r.close(a,u)};return r.onopen=()=>r.send(JSON.stringify({type:"attach",...e,signal:void 0,agent:void 0})),r.onmessage=(a)=>{if(typeof a.data!=="string")return;let u=q(a.data);if(!u)return;let l=G(u,n);if(n.onMessage?.(l),l.type==="done")n.onDone?.();if(l.type==="error")n.onError?.(Error(J(l.error)))},r.onerror=()=>n.onError?.(L(this.baseUrl)),r.onclose=(a)=>{if(!s&&a.code!==1000)n.onError?.(Error(a.reason||"WebSocket connection closed."))},{url:o,sendControl:function(a){if(r.readyState!==C)throw Error("WebSocket is not open");r.send(JSON.stringify({type:"control",...a}))},close:i}}async*stream(e){let n=[],t=!1,o=null,r=null,s=()=>{r?.(),r=null},i=this.subscribe(e,{onMessage:function(a){n.push(a),s()},onDone:function(){t=!0,s()},onError:function(a){o=a,s()}});try{while(!0){if(n.length>0){yield n.shift();continue}if(o)throw o;if(t)return;await new Promise((a)=>{r=a})}}finally{i.close()}}buildUrl(e){let n=be(e),t=e.projectSlug??e.agent?.projectSlug,o=e.stageSlug??e.agent?.stageSlug,r=t?`/${t}`:"",s=o?`/${o}`:"";return`${ke(this.baseUrl)}/v1${r}/agents${s}/${encodeURIComponent(n)}/ws?token=${encodeURIComponent(this.apiKey)}`}resolveWebSocket(){let e=this.WebSocketImpl??globalThis.WebSocket;if(!e)throw Error("WebSocket is not available. Pass a WebSocket implementation in BroodsWebSocketClient options.");return e}}function Re(e){let n=e.agentId??e.agent?.id??e.endpointId;if(!n)throw Error("WebSocket run requires agent, agentId, or endpointId.");return n}function be(e){let n=e.endpointId??e.agent?.endpointId??e.agent?.id;if(!n)throw Error("WebSocket run requires agent or endpointId.");return n}function ke(e){let n=new URL(F(e));if(n.protocol==="https:")n.protocol="wss:";if(n.protocol==="http:")n.protocol="ws:";return d(n.toString())}function F(e){let n=e.trim();if(/^wss?:\/\//.test(n))return d(n);return A(n)}function L(e){return Error(`Cannot access the WebSocket service at ${e}.`)}function G(e,n){if(e.type!=="output")return e;let t=e;return n.onOutput?.(t),t.data}function q(e){try{let n=JSON.parse(e);return typeof n==="object"&&n!==null&&typeof n.type==="string"?n:null}catch{return null}}function J(e){if(typeof e==="string")return e;if(e&&typeof e==="object"&&typeof e.message==="string")return e.message;return e===void 0?"WebSocket stream error.":JSON.stringify(e)}var z=Symbol.for("broods.resource"),V=Symbol.for("broods.config"),Y=Symbol.for("broods.connection"),Z=/^[A-Z][A-Z0-9_]{0,63}$/,ze=new Proxy((e)=>{if(!Z.test(e))throw Error("env name must match /^[A-Z][A-Z0-9_]*$/ and be at most 64 characters.");return{__beeblastEnv:!0,name:e}},{get:function(e,n,t){if(typeof n==="string"&&Z.test(n))throw Error(`env.${n} is not supported; use env("${n}")`);return Reflect.get(e,n,t)}});function g(e,n,t,o){return{[z]:!0,kind:e,name:n,...t?{description:t}:{},config:o}}function f(e,n){let{partition:t,...o}=n;return{[Y]:!0,kind:"connection",type:e,...t?{partition:t}:{},config:o}}function m(e,n,t,o,r){return g("channelRecord",e,n,{...r,externalId:t,...o?{workspaceRef:o}:{}})}function Ve(e){return f("discord",e)}function Ye(e){return f("github",e)}function Xe(e){return f("pancake",e)}function Qe(e){return f("slack",e)}function en(e){return f("telegram",e)}function nn(e){return f("zalo",e)}function tn(e){let{name:n,description:t,channelId:o,guildId:r,...s}=e;return m(n,t,o,r,s)}function on(e){let{name:n,description:t,repo:o,...r}=e,[s,...i]=o.split("/");if(!s||i.length!==1||!i[0])throw Error(`Channel "${n}" repo must be "owner/name", not "${o}"`);return m(n,t,o,s,r)}function rn(e){let{name:n,description:t,conversationId:o,...r}=e;return m(n,t,o,void 0,r)}function sn(e){let{name:n,description:t,channelId:o,teamId:r,...s}=e;return m(n,t,o,r,s)}function an(e){let{name:n,description:t,chatId:o,...r}=e;return m(n,t,o,void 0,r)}function cn(e){let{name:n,description:t,chatId:o,...r}=e;return m(n,t,o,void 0,r)}function un(e){return{[V]:!0,config:e}}function dn(e){let{name:n,description:t,...o}=e;return g("agent",n,t,o)}function ln(e){return e}function gn(e){let{name:n,description:t,...o}=e;return g("workspace",n,t,o)}function pn(e){let{name:n,description:t,...o}=e;return g("sandbox",n,t,o)}function fn(e){let{name:n,description:t,...o}=e;return g("skill",n,t,o)}function mn(e){let{name:n,...t}=e;return g("tool",n,void 0,t)}function yn(e){let{name:n,description:t,...o}=e;return g("policy",n,t,o)}function hn(e){let{name:n,description:t,...o}=e;return g("cron",n,t,o)}function Cn(e){return Boolean(e&&typeof e==="object"&&e[z])}function Sn(e){return Boolean(e&&typeof e==="object"&&e[Y])}function Rn(e){return Boolean(e&&typeof e==="object"&&e[V])}
|
|
5
|
+
export{k as d,D as e,Ee as f,De as g,h,Ne as i,Te as j,O as k,d as l,Oe as m,I as n,$e as o,x as p,B as q,le as r,A as s,Se as t,ke as u,ze as v,Ve as w,Ye as x,Xe as y,Qe as z,en as A,nn as B,tn as C,on as D,rn as E,sn as F,an as G,cn as H,un as I,dn as J,ln as K,gn as L,pn as M,fn as N,mn as O,yn as P,hn as Q,Cn as R,Sn as S,Rn as T};
|