broods 0.14.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 +78 -57
- package/dist/account.js +1 -1
- package/dist/chunk-3cvxks4m.js +2 -0
- package/dist/{chunk-9v4kpqb1.js → chunk-qerk8v3m.js} +5 -5
- package/dist/cli/index.js +95 -87
- package/dist/index.d.ts +239 -132
- package/dist/index.js +1 -1
- package/package.json +11 -11
- 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
|
@@ -94,45 +94,6 @@ declare const MODEL_PROVIDERS: {
|
|
|
94
94
|
};
|
|
95
95
|
type AccountModelProviderName = keyof typeof MODEL_PROVIDERS;
|
|
96
96
|
|
|
97
|
-
/**
|
|
98
|
-
* Agent policy contracts and validation.
|
|
99
|
-
* Runtime decisions are made by OPA using the same document/input shape.
|
|
100
|
-
*/
|
|
101
|
-
declare const AGENT_POLICY_ACTIONS: readonly ["agent.invoke", "tool.call", "workspace.read", "workspace.write", "workspace.exec", "subagent.run", "skill.load"];
|
|
102
|
-
type AgentPolicyAction = (typeof AGENT_POLICY_ACTIONS)[number];
|
|
103
|
-
type AgentPolicyEffect = "allow" | "deny";
|
|
104
|
-
type AgentPolicyMode = "enforce" | "audit";
|
|
105
|
-
type AgentPolicyConditionOperator = "equals" | "notEquals" | "in" | "notIn" | "prefix" | "contains";
|
|
106
|
-
interface AgentPolicyCondition {
|
|
107
|
-
attribute: string;
|
|
108
|
-
operator: AgentPolicyConditionOperator;
|
|
109
|
-
value: string | number | boolean | string[] | number[] | boolean[];
|
|
110
|
-
}
|
|
111
|
-
interface AgentPolicyResourceSelector {
|
|
112
|
-
toolNames?: string[];
|
|
113
|
-
toolIds?: string[];
|
|
114
|
-
workspaceIds?: string[];
|
|
115
|
-
workspaceNames?: string[];
|
|
116
|
-
filePaths?: string[];
|
|
117
|
-
subagentIds?: string[];
|
|
118
|
-
skillPaths?: string[];
|
|
119
|
-
}
|
|
120
|
-
interface AgentPolicyRule {
|
|
121
|
-
id: string;
|
|
122
|
-
effect: AgentPolicyEffect;
|
|
123
|
-
actions: AgentPolicyAction[];
|
|
124
|
-
resources?: AgentPolicyResourceSelector;
|
|
125
|
-
conditions?: AgentPolicyCondition[];
|
|
126
|
-
}
|
|
127
|
-
interface AgentPolicyDocument {
|
|
128
|
-
version: 1;
|
|
129
|
-
rules: AgentPolicyRule[];
|
|
130
|
-
}
|
|
131
|
-
interface AgentPolicyConfig {
|
|
132
|
-
policyIds?: string[];
|
|
133
|
-
mode?: AgentPolicyMode;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
97
|
/**
|
|
137
98
|
* Agent configuration: types for the per-agent settings object, input
|
|
138
99
|
* normalization, encryption helpers, patch-merge, and redaction.
|
|
@@ -162,7 +123,8 @@ interface AgentConfig {
|
|
|
162
123
|
skills?: AgentSkillsConfig;
|
|
163
124
|
subagent?: AgentSubagentConfig;
|
|
164
125
|
scheduler?: AgentSchedulerConfig;
|
|
165
|
-
|
|
126
|
+
/** Policies that gate this agent. Each one carries its own enforcement mode. */
|
|
127
|
+
policies?: string[];
|
|
166
128
|
publicAccess?: boolean;
|
|
167
129
|
[key: string]: unknown;
|
|
168
130
|
}
|
|
@@ -338,7 +300,8 @@ interface AgentTelegramChannelConfig {
|
|
|
338
300
|
apiUrl?: string;
|
|
339
301
|
botToken?: string;
|
|
340
302
|
webhookSecret?: string;
|
|
341
|
-
|
|
303
|
+
allowedChannelIds?: string[];
|
|
304
|
+
allowedUserIds?: string[];
|
|
342
305
|
reactionEmoji?: string;
|
|
343
306
|
trace?: "enabled" | "disabled";
|
|
344
307
|
workspaceScope?: AgentChannelWorkspaceScope;
|
|
@@ -350,7 +313,8 @@ interface AgentGitHubChannelConfig {
|
|
|
350
313
|
webhookSecret?: string;
|
|
351
314
|
appId?: string;
|
|
352
315
|
privateKey?: string;
|
|
353
|
-
|
|
316
|
+
allowedChannelIds?: string[];
|
|
317
|
+
allowedUserIds?: string[];
|
|
354
318
|
/** Bot username for @-mention detection (e.g. "my-bot" or "my-bot[bot]"). */
|
|
355
319
|
userName?: string;
|
|
356
320
|
/** Bot's numeric GitHub user ID for self-message detection. */
|
|
@@ -369,6 +333,7 @@ interface AgentSlackChannelConfig {
|
|
|
369
333
|
botToken?: string;
|
|
370
334
|
signingSecret?: string;
|
|
371
335
|
allowedChannelIds?: string[];
|
|
336
|
+
allowedUserIds?: string[];
|
|
372
337
|
reactionEmoji?: string;
|
|
373
338
|
trace?: "enabled" | "disabled";
|
|
374
339
|
workspaceScope?: AgentChannelWorkspaceScope;
|
|
@@ -379,7 +344,8 @@ interface AgentDiscordChannelConfig {
|
|
|
379
344
|
apiUrl?: string;
|
|
380
345
|
botToken?: string;
|
|
381
346
|
publicKey?: string;
|
|
382
|
-
|
|
347
|
+
allowedChannelIds?: string[];
|
|
348
|
+
allowedUserIds?: string[];
|
|
383
349
|
/** Bot's Discord user id. Set it to answer only when the agent is mentioned. */
|
|
384
350
|
botUserId?: string;
|
|
385
351
|
/** Role ids that count as mentioning the agent, e.g. an on-call role. */
|
|
@@ -389,6 +355,8 @@ interface AgentDiscordChannelConfig {
|
|
|
389
355
|
[key: string]: unknown;
|
|
390
356
|
}
|
|
391
357
|
interface AgentPancakeChannelConfig {
|
|
358
|
+
allowedChannelIds?: string[];
|
|
359
|
+
allowedUserIds?: string[];
|
|
392
360
|
id?: string;
|
|
393
361
|
pageId?: string;
|
|
394
362
|
pageAccessToken?: string;
|
|
@@ -399,11 +367,11 @@ interface AgentPancakeChannelConfig {
|
|
|
399
367
|
[key: string]: unknown;
|
|
400
368
|
}
|
|
401
369
|
interface AgentZaloChannelConfig {
|
|
370
|
+
allowedChannelIds?: string[];
|
|
371
|
+
allowedUserIds?: string[];
|
|
402
372
|
id?: string;
|
|
403
373
|
botToken?: string;
|
|
404
374
|
webhookSecret?: string;
|
|
405
|
-
allowedUserIds?: string[];
|
|
406
|
-
allowedGroupIds?: string[];
|
|
407
375
|
trace?: "enabled" | "disabled";
|
|
408
376
|
workspaceScope?: AgentChannelWorkspaceScope;
|
|
409
377
|
[key: string]: unknown;
|
|
@@ -544,6 +512,60 @@ interface WorkspaceConfig {
|
|
|
544
512
|
harness?: WorkspaceHarnessConfig;
|
|
545
513
|
}
|
|
546
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
|
+
|
|
547
569
|
/**
|
|
548
570
|
* Wire types for the public account-manage and harness APIs. These mirror
|
|
549
571
|
* the deployed API contract (docs/api-reference/openapi.yaml is the source
|
|
@@ -690,7 +712,7 @@ interface AccountPolicy {
|
|
|
690
712
|
policyId: string;
|
|
691
713
|
name: string;
|
|
692
714
|
description?: string;
|
|
693
|
-
document:
|
|
715
|
+
document: PolicyDocument;
|
|
694
716
|
status: string;
|
|
695
717
|
createdAt: string;
|
|
696
718
|
updatedAt: string;
|
|
@@ -725,9 +747,8 @@ interface ChannelRecordConfig {
|
|
|
725
747
|
name: string;
|
|
726
748
|
workspaceId: string;
|
|
727
749
|
}>;
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
policyMode?: "enforce" | "audit";
|
|
750
|
+
/** Added to whatever the agent already carries. Each policy holds its own mode. */
|
|
751
|
+
policies?: string[];
|
|
731
752
|
/**
|
|
732
753
|
* Tool names withheld in this channel, applied after the tool set is built —
|
|
733
754
|
* so it also covers sandbox tools (`bash`, `read`, …) that `config.tools`
|
|
@@ -735,18 +756,18 @@ interface ChannelRecordConfig {
|
|
|
735
756
|
*/
|
|
736
757
|
denyTools?: string[];
|
|
737
758
|
/**
|
|
738
|
-
* Where the reply lands. `
|
|
739
|
-
* when the message already did. Slack only — no other provider
|
|
740
|
-
* 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.
|
|
741
762
|
*/
|
|
742
|
-
|
|
763
|
+
replyIn?: ChannelReplyIn;
|
|
743
764
|
workspaceScope?: AgentChannelWorkspaceScope;
|
|
744
765
|
/** Images the agent may stand a sandbox up from for a thread here. */
|
|
745
766
|
sandboxImages?: string[];
|
|
746
|
-
/** Named groups of people, readable from policy conditions as `
|
|
767
|
+
/** Named groups of people, readable from policy conditions as `userRoles`. */
|
|
747
768
|
tagRoles?: Array<{
|
|
748
769
|
roleId: string;
|
|
749
|
-
|
|
770
|
+
userIds: string[];
|
|
750
771
|
}>;
|
|
751
772
|
}
|
|
752
773
|
/** The stage a tool belongs to. Tools with the same name in two stages are two tools. */
|
|
@@ -955,14 +976,14 @@ declare class BroodsAccountClient {
|
|
|
955
976
|
createPolicy(input: {
|
|
956
977
|
name: string;
|
|
957
978
|
description?: string;
|
|
958
|
-
document:
|
|
979
|
+
document: PolicyDocument;
|
|
959
980
|
}): Promise<AccountPolicy>;
|
|
960
981
|
getPolicy(policyId: string): Promise<AccountPolicy | null>;
|
|
961
982
|
/** PATCH a policy. `description: null` clears it. Returns null when the policy is gone. */
|
|
962
983
|
updatePolicy(policyId: string, patch: {
|
|
963
984
|
name?: string;
|
|
964
985
|
description?: string | null;
|
|
965
|
-
document?:
|
|
986
|
+
document?: PolicyDocument;
|
|
966
987
|
status?: string;
|
|
967
988
|
}): Promise<AccountPolicy | null>;
|
|
968
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 O(){return process.env.BROODS_STAGE}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 de(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=ue(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 ue(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 de(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,u=setTimeout(()=>{if(!a)y(s)},this.connectTimeoutMs),I=(c=1000,R="client closed")=>{if(l)return;if(l=!0,clearTimeout(u),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(u),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(u),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 u=L(l,n);if(n.onMessage?.(u),u.type==="done")n.onDone?.();if(u.type==="error")n.onError?.(Error(q(u.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}$/,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 d(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 d("agent",n,t,o)}function on(e){return e}function rn(e){let{name:n,description:t,...o}=e;return d("workspace",n,t,o)}function sn(e){let{name:n,description:t,...o}=e;return d("sandbox",n,t,o)}function an(e){let{name:n,description:t,...o}=e;return d("skill",n,t,o)}function cn(e){let{name:n,...t}=e;return d("tool",n,void 0,t)}function ln(e){let{name:n,description:t,...o}=e;return d("policy",n,t,o)}function gn(e){let{name:n,description:t,...o}=e;return d("channelRecord",n,t,o)}function un(e){let{name:n,description:t,...o}=e;return d("cron",n,t,o)}function dn(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};
|