broods 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/account.d.ts CHANGED
@@ -121,6 +121,8 @@ interface AgentConfig {
121
121
  hooks?: AgentHooksConfig;
122
122
  channels?: AgentChannelsConfig;
123
123
  tools?: AgentToolsConfig;
124
+ /** Connected MCP servers, keyed by their config-plane row id (#331). */
125
+ mcpServers?: AgentMcpServersConfig;
124
126
  /**
125
127
  * Tool names withheld for this run, applied after the tool set is built.
126
128
  * Set by a channel record; a channel can take a tool away, never add one.
@@ -285,6 +287,15 @@ interface AgentToolConfig {
285
287
  config?: Record<string, unknown>;
286
288
  [key: string]: unknown;
287
289
  }
290
+ type AgentMcpServersConfig = Record<string, AgentMcpServerConfig>;
291
+ interface AgentMcpServerConfig {
292
+ enabled?: boolean;
293
+ /** Applies to every tool the server exposes. */
294
+ needsApproval?: boolean;
295
+ /** Extra request headers; values resolved from account env vars at sync. */
296
+ headers?: Record<string, string>;
297
+ [key: string]: unknown;
298
+ }
288
299
  interface AgentChannelsConfig {
289
300
  telegram?: AgentTelegramChannelConfig;
290
301
  github?: AgentGitHubChannelConfig;
@@ -563,6 +574,8 @@ type PolicyEffect = "allow" | "deny";
563
574
  interface PolicyResourceSelector {
564
575
  toolNames?: string[];
565
576
  toolIds?: string[];
577
+ /** MCP registration ids, for scoping tool.call rules per server (#331). */
578
+ mcpIds?: string[];
566
579
  workspaceIds?: string[];
567
580
  workspaceNames?: string[];
568
581
  filePaths?: string[];
@@ -867,6 +880,46 @@ interface UpdateToolInput {
867
880
  runtime?: "isolate" | "sandbox";
868
881
  defaultConfig?: unknown;
869
882
  }
883
+ /** Public MCP server registration returned by the `/v1/mcp` routes (#331). */
884
+ interface AccountMcpServer {
885
+ accountId: string;
886
+ serverId: string;
887
+ projectId: string;
888
+ stageId: string;
889
+ name: string;
890
+ description?: string;
891
+ transport: "http" | "hosted";
892
+ /** External servers only; a hosted row has no endpoint of its own. */
893
+ url?: string;
894
+ /** Hosted servers only: content hash of the uploaded bundle. */
895
+ sha256?: string;
896
+ headers?: Record<string, string>;
897
+ allowedTools?: string[];
898
+ disabled: boolean;
899
+ status: string;
900
+ createdAt: string;
901
+ updatedAt: string;
902
+ deletedAt?: string;
903
+ }
904
+ /** Fields accepted by `POST /v1/mcp`: `url` connects, `bundle` uploads. */
905
+ interface CreateMcpServerInput {
906
+ name: string;
907
+ description?: string;
908
+ url?: string;
909
+ bundle?: string;
910
+ headers?: Record<string, string>;
911
+ allowedTools?: string[];
912
+ }
913
+ /** Fields accepted by `PATCH /v1/mcp/{serverId}`; every field is optional. */
914
+ interface UpdateMcpServerInput {
915
+ name?: string;
916
+ description?: string;
917
+ url?: string;
918
+ bundle?: string;
919
+ headers?: Record<string, string>;
920
+ allowedTools?: string[];
921
+ disabled?: boolean;
922
+ }
870
923
  /**
871
924
  * Body of a skill upload (`POST /v1/skills`, `PUT /v1/skills/{skillName}`).
872
925
  * `json` needs `name`/`description`/`content`; `files` needs base64 `files`
@@ -1038,6 +1091,12 @@ declare class BroodsAccountClient {
1038
1091
  /** PATCH an uploaded tool. Omitting `bundle` keeps the stored bundle and runtime. Returns null when the tool is gone. */
1039
1092
  updateTool(toolId: string, patch: UpdateToolInput): Promise<AccountTool | null>;
1040
1093
  deleteTool(toolId: string): Promise<boolean>;
1094
+ /** MCP servers are stage-scoped like tools; both scope fields are required. */
1095
+ listMcpServers(scope: ToolScope): Promise<AccountMcpServer[]>;
1096
+ createMcpServer(scope: ToolScope, input: CreateMcpServerInput): Promise<AccountMcpServer>;
1097
+ getMcpServer(serverId: string): Promise<AccountMcpServer | null>;
1098
+ updateMcpServer(serverId: string, patch: UpdateMcpServerInput): Promise<AccountMcpServer | null>;
1099
+ deleteMcpServer(serverId: string): Promise<boolean>;
1041
1100
  listPolicies(): Promise<AccountPolicy[]>;
1042
1101
  createPolicy(input: {
1043
1102
  name: string;
@@ -1104,4 +1163,4 @@ declare class BroodsAccountClient {
1104
1163
  }
1105
1164
 
1106
1165
  export { BroodsAccountApiError, BroodsAccountClient, envPlaceholder };
1107
- export type { AccountAgent, AccountChannel, AccountEnvVar, AccountPolicy, AccountRole, AccountSandbox, AccountTool, AccountWorkspace, AssumeRoleResult, BroodsAccount, BroodsAccountClientOptions, ChannelRecordConfig, CreateAgentResult, CreateToolInput, DeleteAccountResult, RotateSecretResult, SandboxLifecycleResult, SandboxSnapshotResult, SandboxTerminalTicket, SkillUploadInput, ToolScope, UpdateAgentInput, UpdateToolInput, WorkspaceFileEntry };
1166
+ export type { AccountAgent, AccountChannel, AccountEnvVar, AccountMcpServer, AccountPolicy, AccountRole, AccountSandbox, AccountTool, AccountWorkspace, AssumeRoleResult, BroodsAccount, BroodsAccountClientOptions, ChannelRecordConfig, CreateAgentResult, CreateMcpServerInput, CreateToolInput, DeleteAccountResult, RotateSecretResult, SandboxLifecycleResult, SandboxSnapshotResult, SandboxTerminalTicket, SkillUploadInput, ToolScope, UpdateAgentInput, UpdateMcpServerInput, UpdateToolInput, WorkspaceFileEntry };
package/dist/account.js CHANGED
@@ -1 +1 @@
1
- import{S,t,y}from"./chunk-a6p0aaky.js";export{t as BroodsAccountApiError,y as BroodsAccountClient,S as envPlaceholder};
1
+ import{a,b,c}from"./chunk-36yhe6qb.js";import"./chunk-4sdsq7gb.js";export{b as BroodsAccountApiError,c as BroodsAccountClient,a as envPlaceholder};
@@ -0,0 +1,2 @@
1
+ var c=/^[A-Z][A-Z0-9_]*$/;function u(e){if(!c.test(e)||e.length>64)throw Error("envPlaceholder name must match /^[A-Z][A-Z0-9_]*$/ and be at most 64 characters.");return`\${${e}}`}class r extends Error{status;body;constructor(e,t,n,s){super(`${e} ${t} failed with ${n}: ${s}`);this.name="BroodsAccountApiError",this.status=n,this.body=s}}function i(e){return typeof process<"u"?process?.env?.[e]:void 0}function o(e){return`?${new URLSearchParams({project:e.project,stage:e.stage}).toString()}`}class l{baseUrl;bearerToken;fetchImpl;constructor(e={}){let t=e.baseUrl??i("BROODS_BASE_URL")??"https://gateway.broods.app",n=e.sessionToken??e.accountSecret??i("BROODS_SESSION_TOKEN")??i("BROODS_ACCOUNT_SECRET");if(!n)throw Error("BroodsAccountClient requires an accountSecret or sessionToken (or BROODS_ACCOUNT_SECRET / BROODS_SESSION_TOKEN).");let s=t.length;while(s>0&&t.charCodeAt(s-1)===47)s-=1;this.baseUrl=t.slice(0,s),this.bearerToken=n,this.fetchImpl=e.fetch??fetch}async getAccount(){let e=await this.request("GET","/v1/account");if(!e)throw new r("GET","/v1/account",404,"Account not found");return e.account}async updateAccount(e){return(await this.request("PATCH","/v1/account",e))?.account??null}async assumeRole(e,t={}){let n=await this.request("POST","/v1/account/assume-role",{roleId:e,...t.ttlSeconds!==void 0?{ttlSeconds:t.ttlSeconds}:{}});if(!n)throw new r("POST","/v1/account/assume-role",404,"Role not found");return n}async rotateSecret(){let e=await this.request("POST","/v1/account/rotate-secret");if(!e)throw new r("POST","/v1/account/rotate-secret",404,"Account not found");return e}async deleteAccount(){return await this.request("DELETE","/v1/account")??{deleted:!1}}webhookUrl(e,t){let n=[e,t].map(encodeURIComponent);return`${this.baseUrl}/webhooks/${n.join("/")}`}async listAgents(){return(await this.request("GET","/v1/agents"))?.agents??[]}async createAgent(e){let t=await this.request("POST","/v1/agents",e);if(!t)throw new r("POST","/v1/agents",404,"Not found");return t}async getAgent(e){return await this.request("GET",`/v1/agents/${encodeURIComponent(e)}`)}async updateAgent(e,t){return await this.request("PATCH",`/v1/agents/${encodeURIComponent(e)}`,t)}async deleteAgent(e){return(await this.request("DELETE",`/v1/agents/${encodeURIComponent(e)}`))?.deleted??!1}async listEnvVars(){return(await this.request("GET","/v1/env"))?.env??[]}async setEnvVar(e,t){await this.request("PUT",`/v1/env/${encodeURIComponent(e)}`,{value:t})}async deleteEnvVar(e){return(await this.request("DELETE",`/v1/env/${encodeURIComponent(e)}`))?.deleted??!1}async listCrons(){return(await this.request("GET","/v1/crons"))?.crons??[]}async createCron(e){let t=await this.request("POST","/v1/crons",e);if(!t)throw new r("POST","/v1/crons",404,"Not found");return t}async getCron(e){return await this.request("GET",`/v1/crons/${encodeURIComponent(e)}`)}async updateCron(e,t){return await this.request("PATCH",`/v1/crons/${encodeURIComponent(e)}`,t)}async deleteCron(e){return(await this.request("DELETE",`/v1/crons/${encodeURIComponent(e)}`))?.deleted??!1}async listCronRuns(e,t={}){let n=t.limit!==void 0?`?limit=${t.limit}`:"";return(await this.request("GET",`/v1/crons/${encodeURIComponent(e)}/runs${n}`))?.runs??[]}async listWorkspaces(){return(await this.request("GET","/v1/workspaces"))?.workspaces??[]}async createWorkspace(e){let t=await this.request("POST","/v1/workspaces",e);if(!t)throw new r("POST","/v1/workspaces",404,"Not found");return t}async getWorkspace(e){return await this.request("GET",`/v1/workspaces/${encodeURIComponent(e)}`)}async updateWorkspace(e,t){return await this.request("PATCH",`/v1/workspaces/${encodeURIComponent(e)}`,t)}async deleteWorkspace(e){return(await this.request("DELETE",`/v1/workspaces/${encodeURIComponent(e)}`))?.deleted??!1}async listWorkspaceFiles(e){return(await this.request("GET",`/v1/workspaces/${encodeURIComponent(e)}/files`))?.files??[]}async getWorkspaceFileUrl(e,t){let n=`?path=${encodeURIComponent(t)}`;return(await this.request("GET",`/v1/workspaces/${encodeURIComponent(e)}/files${n}`))?.url??null}async uploadWorkspaceFile(e,t){let n=`/v1/workspaces/${encodeURIComponent(e)}/files`,s=await this.request("POST",n,t);if(!s)throw new r("POST",n,404,"Workspace not found");return s.file}async renameWorkspaceFile(e,t,n){return(await this.request("PATCH",`/v1/workspaces/${encodeURIComponent(e)}/files`,{path:t,newPath:n}))?.renamed??!1}async deleteWorkspaceFile(e,t){return(await this.request("DELETE",`/v1/workspaces/${encodeURIComponent(e)}/files`,{path:t}))?.deleted??!1}async listSandboxes(){return(await this.request("GET","/v1/sandboxes"))?.sandboxes??[]}async createSandbox(e){let t=await this.request("POST","/v1/sandboxes",e);if(!t)throw new r("POST","/v1/sandboxes",404,"Not found");return t}async getSandbox(e){return await this.request("GET",`/v1/sandboxes/${encodeURIComponent(e)}`)}async updateSandbox(e,t){return await this.request("PATCH",`/v1/sandboxes/${encodeURIComponent(e)}`,t)}async deleteSandbox(e){return(await this.request("DELETE",`/v1/sandboxes/${encodeURIComponent(e)}`))?.deleted??!1}async suspendSandbox(e,t){return await this.sandboxAction(e,"suspend",{reservationKey:t})}async resumeSandbox(e,t){return await this.sandboxAction(e,"resume",{reservationKey:t})}async terminateSandbox(e,t){return await this.sandboxAction(e,"terminate",{reservationKey:t})}async snapshotSandbox(e,t,n){return await this.sandboxAction(e,"snapshot",{reservationKey:t,name:n})}async openSandboxTerminal(e,t){return await this.sandboxAction(e,"terminal",{reservationKey:t})}async listTools(e){let t=`/v1/tools${o(e)}`,n=await this.request("GET",t);if(!n)throw new r("GET",t,404,"Not found");return n.tools??[]}async createTool(e,t){let n=`/v1/tools${o(e)}`,s=await this.request("POST",n,t);if(!s)throw new r("POST",n,404,"Not found");return s}async getTool(e){return await this.request("GET",`/v1/tools/${encodeURIComponent(e)}`)}async updateTool(e,t){return await this.request("PATCH",`/v1/tools/${encodeURIComponent(e)}`,t)}async deleteTool(e){return(await this.request("DELETE",`/v1/tools/${encodeURIComponent(e)}`))?.deleted??!1}async listMcpServers(e){let t=`/v1/mcp${o(e)}`,n=await this.request("GET",t);if(!n)throw new r("GET",t,404,"Not found");return n.servers??[]}async createMcpServer(e,t){let n=`/v1/mcp${o(e)}`,s=await this.request("POST",n,t);if(!s)throw new r("POST",n,404,"Not found");return s}async getMcpServer(e){return await this.request("GET",`/v1/mcp/${encodeURIComponent(e)}`)}async updateMcpServer(e,t){return await this.request("PATCH",`/v1/mcp/${encodeURIComponent(e)}`,t)}async deleteMcpServer(e){return(await this.request("DELETE",`/v1/mcp/${encodeURIComponent(e)}`))?.deleted??!1}async listPolicies(){return(await this.request("GET","/v1/policies"))?.policies??[]}async createPolicy(e){let t=await this.request("POST","/v1/policies",e);if(!t)throw new r("POST","/v1/policies",404,"Not found");return t}async getPolicy(e){return await this.request("GET",`/v1/policies/${encodeURIComponent(e)}`)}async updatePolicy(e,t){return await this.request("PATCH",`/v1/policies/${encodeURIComponent(e)}`,t)}async deletePolicy(e){return(await this.request("DELETE",`/v1/policies/${encodeURIComponent(e)}`))?.deleted??!1}async listRoles(){return(await this.request("GET","/v1/roles"))?.roles??[]}async createRole(e){let t=await this.request("POST","/v1/roles",e);if(!t)throw new r("POST","/v1/roles",404,"Not found");return t}async getRole(e){return await this.request("GET",`/v1/roles/${encodeURIComponent(e)}`)}async updateRole(e,t){return await this.request("PATCH",`/v1/roles/${encodeURIComponent(e)}`,t)}async deleteRole(e){return(await this.request("DELETE",`/v1/roles/${encodeURIComponent(e)}`))?.deleted??!1}async listChannels(){return(await this.request("GET","/v1/channels"))?.channels??[]}async createChannel(e){let t=await this.request("POST","/v1/channels",e);if(!t)throw new r("POST","/v1/channels",404,"Not found");return t}async getChannel(e){return await this.request("GET",`/v1/channels/${encodeURIComponent(e)}`)}async updateChannel(e,t){return await this.request("PATCH",`/v1/channels/${encodeURIComponent(e)}`,t)}async deleteChannel(e){return(await this.request("DELETE",`/v1/channels/${encodeURIComponent(e)}`))?.deleted??!1}async listSkills(){return(await this.request("GET","/v1/skills"))?.skills??[]}async createSkill(e){let t=await this.request("POST","/v1/skills",e);if(!t)throw new r("POST","/v1/skills",404,"Not found");return t}async getSkill(e){return await this.request("GET",`/v1/skills/${encodeURIComponent(e)}`)}async uploadSkill(e,t){let n=`/v1/skills/${encodeURIComponent(e)}`,s=await this.request("PUT",n,t);if(!s)throw new r("PUT",n,404,"Skill not found");return s}async deleteSkill(e){return(await this.request("DELETE",`/v1/skills/${encodeURIComponent(e)}`))?.deleted??!1}async sandboxAction(e,t,n){let s=`/v1/sandboxes/${encodeURIComponent(e)}/${t}`,a=await this.request("POST",s,n);if(!a)throw new r("POST",s,404,"Sandbox not found");return a}async request(e,t,n){let s=await this.fetchImpl(`${this.baseUrl}${t}`,{method:e,headers:{Authorization:`Bearer ${this.bearerToken}`,...n!==void 0?{"Content-Type":"application/json"}:{}},...n!==void 0?{body:JSON.stringify(n)}:{}});if(s.status===404)return null;if(!s.ok)throw new r(e,t,s.status,await s.text());return await s.json()}}
2
+ export{u as a,r as b,l as c};
@@ -1,2 +1,2 @@
1
- var d=Object.defineProperty;var e=(a)=>a;function f(a,b){this[a]=e.bind(null,b)}var I=(a,b)=>{for(var c in b)d(a,c,{get:b[c],enumerable:!0,configurable:!0,set:f.bind(b,c)})};
2
- export{I};
1
+ var d=Object.defineProperty;var e=(a)=>a;function f(a,b){this[a]=e.bind(null,b)}var g=(a,b)=>{for(var c in b)d(a,c,{get:b[c],enumerable:!0,configurable:!0,set:f.bind(b,c)})};
2
+ export{g as V};
@@ -0,0 +1,5 @@
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,g=setTimeout(()=>{if(!a)y(s)},this.connectTimeoutMs),w=(c=1000,R="client closed")=>{if(u)return;if(u=!0,clearTimeout(g),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(g),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(g),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 g=G(u,n);if(n.onMessage?.(g),g.type==="done")n.onDone?.();if(g.type==="error")n.onError?.(Error(J(g.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 l(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 l("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 l("agent",n,t,o)}function ln(e){return e}function gn(e){let{name:n,description:t,...o}=e;return l("workspace",n,t,o)}function pn(e){let{name:n,description:t,...o}=e;return l("sandbox",n,t,o)}function fn(e){let{name:n,description:t,...o}=e;return l("skill",n,t,o)}function mn(e){let{name:n,...t}=e;return l("tool",n,void 0,t)}function yn(e){let{name:n,description:t,...o}=e;return l("mcp",n,t,o)}function hn(e){let{name:n,description:t,...o}=e;return l("policy",n,t,o)}function Cn(e){let{name:n,description:t,...o}=e;return l("cron",n,t,o)}function Sn(e){return Boolean(e&&typeof e==="object"&&e[z])}function Rn(e){return Boolean(e&&typeof e==="object"&&e[Y])}function bn(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,bn as U};