broods 0.26.0 → 0.28.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/index.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;
@@ -562,7 +573,8 @@ type PolicyEffect = "allow" | "deny";
562
573
  */
563
574
  interface PolicyResourceSelector {
564
575
  toolNames?: string[];
565
- toolIds?: string[];
576
+ /** MCP registration ids, for scoping tool.call rules per server (#331). */
577
+ mcpIds?: string[];
566
578
  workspaceIds?: string[];
567
579
  workspaceNames?: string[];
568
580
  filePaths?: string[];
@@ -704,7 +716,7 @@ interface ZaloSource {
704
716
  * import it without pulling the Convex server module graph into its typecheck.
705
717
  */
706
718
  type CliManifestResource = {
707
- kind: "agent" | "workspace" | "sandbox" | "cron" | "skill" | "tool" | "hook" | "policy" | "channelRecord";
719
+ kind: "agent" | "workspace" | "sandbox" | "cron" | "skill" | "hook" | "mcp" | "policy" | "channelRecord";
708
720
  name: string;
709
721
  description?: string;
710
722
  config: unknown;
@@ -715,8 +727,8 @@ type GeneratedIds = {
715
727
  sandboxes: Record<string, string>;
716
728
  crons: Record<string, string>;
717
729
  skills: Record<string, string>;
718
- tools: Record<string, string>;
719
730
  hooks: Record<string, string>;
731
+ mcpServers: Record<string, string>;
720
732
  policies: Record<string, string>;
721
733
  channelRecords: Record<string, string>;
722
734
  };
@@ -745,7 +757,6 @@ type AgentConfigDoc = Doc<"agentConfigs">;
745
757
  type WorkspaceConfigDoc = Doc<"workspaceConfigs">;
746
758
  type SandboxConfigDoc = Doc<"sandboxConfigs">;
747
759
  type CronDoc = Doc<"crons">;
748
- type CliResourceKind = "agent" | "workspace" | "sandbox" | "cron" | "skill" | "tool" | "policy" | "channelRecord";
749
760
 
750
761
  /**
751
762
  * Wire types for the public account-manage and harness APIs. These mirror
@@ -839,14 +850,6 @@ interface Skill {
839
850
  size?: number;
840
851
  }>;
841
852
  }
842
- interface CustomTool {
843
- accountId: string;
844
- toolId: string;
845
- name: string;
846
- description: string;
847
- sha256: string;
848
- runtime?: "isolate" | "sandbox";
849
- }
850
853
 
851
854
  type StreamTextOptions = Parameters<typeof streamText>[0];
852
855
  type JsonCallSettings = Partial<LanguageModelCallOptions & Pick<RequestOptions, "maxRetries" | "timeout">>;
@@ -1114,7 +1117,6 @@ interface ResourceApi {
1114
1117
  readonly sandboxes?: Record<string, unknown>;
1115
1118
  readonly crons?: Record<string, unknown>;
1116
1119
  readonly skills?: Record<string, unknown>;
1117
- readonly tools?: Record<string, unknown>;
1118
1120
  readonly policies?: Record<string, unknown>;
1119
1121
  }
1120
1122
  interface BroodsClientOptions {
@@ -1396,45 +1398,50 @@ interface ChannelRecordConfig {
1396
1398
  userIds: string[];
1397
1399
  }>;
1398
1400
  }
1399
- /** The stage a tool belongs to. Tools with the same name in two stages are two tools. */
1400
- interface ToolScope {
1401
+ /** The stage a resource belongs to. Same name in two stages = two resources. */
1402
+ interface StageScope {
1401
1403
  project: string;
1402
1404
  stage: string;
1403
1405
  }
1404
- /** Public uploaded-tool record returned by the tools routes. */
1405
- interface AccountTool {
1406
+ /** Public MCP server registration returned by the `/v1/mcp` routes (#331). */
1407
+ interface AccountMcpServer {
1406
1408
  accountId: string;
1407
- toolId: string;
1409
+ serverId: string;
1408
1410
  projectId: string;
1409
1411
  stageId: string;
1410
1412
  name: string;
1411
- description: string;
1412
- inputSchema: unknown;
1413
- sha256: string;
1414
- runtime: "isolate" | "sandbox";
1415
- defaultConfig?: unknown;
1413
+ description?: string;
1414
+ transport: "http" | "hosted";
1415
+ /** External servers only; a hosted row has no endpoint of its own. */
1416
+ url?: string;
1417
+ /** Hosted servers only: content hash of the uploaded bundle. */
1418
+ sha256?: string;
1419
+ headers?: Record<string, string>;
1420
+ allowedTools?: string[];
1421
+ disabled: boolean;
1416
1422
  status: string;
1417
1423
  createdAt: string;
1418
1424
  updatedAt: string;
1419
1425
  deletedAt?: string;
1420
1426
  }
1421
- /** Fields accepted by `POST /v1/tools`. `bundle` is already-bundled JavaScript module source. */
1422
- interface CreateToolInput {
1427
+ /** Fields accepted by `POST /v1/mcp`: `url` connects, `bundle` uploads. */
1428
+ interface CreateMcpServerInput {
1423
1429
  name: string;
1424
- description: string;
1425
- inputSchema: unknown;
1426
- bundle: string;
1427
- runtime?: "isolate" | "sandbox";
1428
- defaultConfig?: unknown;
1430
+ description?: string;
1431
+ url?: string;
1432
+ bundle?: string;
1433
+ headers?: Record<string, string>;
1434
+ allowedTools?: string[];
1429
1435
  }
1430
- /** Fields accepted by `PATCH /v1/tools/{toolId}`; every field is optional. Omitting `bundle` keeps the stored one. */
1431
- interface UpdateToolInput {
1436
+ /** Fields accepted by `PATCH /v1/mcp/{serverId}`; every field is optional. */
1437
+ interface UpdateMcpServerInput {
1432
1438
  name?: string;
1433
1439
  description?: string;
1434
- inputSchema?: unknown;
1440
+ url?: string;
1435
1441
  bundle?: string;
1436
- runtime?: "isolate" | "sandbox";
1437
- defaultConfig?: unknown;
1442
+ headers?: Record<string, string>;
1443
+ allowedTools?: string[];
1444
+ disabled?: boolean;
1438
1445
  }
1439
1446
  /**
1440
1447
  * Body of a skill upload (`POST /v1/skills`, `PUT /v1/skills/{skillName}`).
@@ -1600,13 +1607,12 @@ declare class BroodsAccountClient {
1600
1607
  snapshotSandbox(sandboxId: string, reservationKey: string, name: string): Promise<SandboxSnapshotResult>;
1601
1608
  /** Mint a short-lived sealed ticket for an interactive PTY session on a persistent sandbox (`sandbox`/`lambda` providers). Throws on 404/403/409. */
1602
1609
  openSandboxTerminal(sandboxId: string, reservationKey: string): Promise<SandboxTerminalTicket>;
1603
- /** Tools live in one stage, so the collection routes need a scope. Both fields are required. */
1604
- listTools(scope: ToolScope): Promise<AccountTool[]>;
1605
- createTool(scope: ToolScope, input: CreateToolInput): Promise<AccountTool>;
1606
- getTool(toolId: string): Promise<AccountTool | null>;
1607
- /** PATCH an uploaded tool. Omitting `bundle` keeps the stored bundle and runtime. Returns null when the tool is gone. */
1608
- updateTool(toolId: string, patch: UpdateToolInput): Promise<AccountTool | null>;
1609
- deleteTool(toolId: string): Promise<boolean>;
1610
+ /** MCP servers live in one stage, so the collection routes need a scope. */
1611
+ listMcpServers(scope: StageScope): Promise<AccountMcpServer[]>;
1612
+ createMcpServer(scope: StageScope, input: CreateMcpServerInput): Promise<AccountMcpServer>;
1613
+ getMcpServer(serverId: string): Promise<AccountMcpServer | null>;
1614
+ updateMcpServer(serverId: string, patch: UpdateMcpServerInput): Promise<AccountMcpServer | null>;
1615
+ deleteMcpServer(serverId: string): Promise<boolean>;
1610
1616
  listPolicies(): Promise<AccountPolicy[]>;
1611
1617
  createPolicy(input: {
1612
1618
  name: string;
@@ -1798,7 +1804,7 @@ interface BroodsConfigDefinition {
1798
1804
  readonly [CONFIG_MARKER]: true;
1799
1805
  readonly config: BroodsProjectConfig;
1800
1806
  }
1801
- type ResourceKind = "agent" | "workspace" | "sandbox" | "cron" | "skill" | "tool" | "policy" | "channelRecord";
1807
+ type ResourceKind = "agent" | "workspace" | "sandbox" | "cron" | "skill" | "mcp" | "policy" | "channelRecord";
1802
1808
  interface ResourceDefinition<Kind extends ResourceKind, Name extends string, Config> {
1803
1809
  readonly [RESOURCE_MARKER]: true;
1804
1810
  readonly kind: Kind;
@@ -1845,52 +1851,36 @@ interface SkillDefinitionConfig {
1845
1851
  */
1846
1852
  path: string;
1847
1853
  }
1848
- /** Broods-side context on `ToolExecuteOptions`, alongside the AI SDK's own fields. */
1849
- interface ToolExecuteContext {
1850
- /** Resolved tool config, `env("NAME")` already substituted. Where secrets arrive. */
1851
- config: Record<string, unknown>;
1852
- /** SSRF-guarded fetch. Also installed as the global `fetch`. */
1853
- fetch: typeof fetch;
1854
- /** Per-run scratchpad. Read back after hooks; empty and unused for tools. */
1855
- state: Record<string, unknown>;
1856
- }
1857
- /** Call options an inline `execute` receives, mirroring the AI SDK's tool(). */
1858
- interface ToolExecuteOptions {
1859
- toolCallId?: string;
1860
- context: ToolExecuteContext;
1861
- abortSignal?: AbortSignal;
1862
- /** Conversation so far. Truncated from the front to the newest 512 KB. */
1863
- messages: ModelMessage[];
1864
- experimental_context?: unknown;
1865
- }
1866
- interface ToolDefinitionConfig<Input = Record<string, unknown>> {
1854
+ type PolicyDefinitionConfig = Omit<PolicyDocument, "version"> & {
1855
+ version?: PolicyDocument["version"];
1856
+ };
1857
+ /**
1858
+ * MCP server registration (#331) — external (`url`) or hosted (`path`).
1859
+ * Either way the server's tools are offered as `<name>__<tool>`; an external
1860
+ * row is dialed over the stateless HTTP transport (spec 2026-07-28) at agent
1861
+ * registration time. The name namespaces those tools, so it must be 1-32
1862
+ * lowercase letters, digits, or hyphens, starting with a letter.
1863
+ */
1864
+ interface McpServerDefinitionConfig {
1865
+ /** External server's MCP endpoint; http(s), no embedded credentials. */
1866
+ url?: string;
1867
1867
  /**
1868
- * Optional module file exporting the tool implementation, resolved from the
1869
- * `broods/` project directory. Omit it and declare `execute` inline instead —
1870
- * the CLI then bundles the module this tool is exported from.
1868
+ * Hosted alternative to `url`: a module (resolved from the `broods/`
1869
+ * directory) whose default export is a fetch-style MCP handler —
1870
+ * `export default createMcpHandler(...)` from @modelcontextprotocol/server.
1871
+ * The CLI bundles it and the tool-runner Lambda hosts it, one invoke per
1872
+ * request.
1871
1873
  */
1872
1874
  path?: string;
1873
1875
  /**
1874
- * Runs in the isolate or the sandbox runner, not locally. Shaped like the AI
1875
- * SDK's `tool({ execute })`: the input first, call options second. Annotate
1876
- * the parameter to type it — `inputSchema` is JSON Schema, so it cannot be
1877
- * inferred.
1876
+ * Extra request headers. Credential-bearing headers (Authorization,
1877
+ * X-Api-Key, ...) must reference an account env var — e.g.
1878
+ * `Bearer ${env("TOKEN")}` — never carry an inline secret.
1878
1879
  */
1879
- execute?: (input: Input, options: ToolExecuteOptions) => unknown;
1880
- description: string;
1881
- inputSchema: Record<string, unknown>;
1882
- runtime?: "isolate" | "sandbox";
1883
- /**
1884
- * Account-wide, non-secret defaults. Environment variable references are
1885
- * rejected here; put `env("NAME")` under the enabling agent's
1886
- * `tools.<tool>.config` so the value is resolved per stage and stored with
1887
- * encrypted agent config.
1888
- */
1889
- defaultConfig?: Record<string, unknown>;
1880
+ headers?: Record<string, string>;
1881
+ /** Tool names agents may use from this server; omit to allow all. */
1882
+ allowedTools?: string[];
1890
1883
  }
1891
- type PolicyDefinitionConfig = Omit<PolicyDocument, "version"> & {
1892
- version?: PolicyDocument["version"];
1893
- };
1894
1884
  type ChannelType = "telegram" | "github" | "slack" | "discord" | "pancake" | "zalo";
1895
1885
  /**
1896
1886
  * A connection is one app install: the credentials an agent needs before a
@@ -2192,7 +2182,7 @@ interface ProviderSettingsInput {
2192
2182
  }
2193
2183
  /** Per-provider settings; provider names stay synced with core's `AgentConfig`. */
2194
2184
  type ProviderConfigInput = Partial<Record<keyof NonNullable<AgentConfig["provider"]>, ProviderSettingsInput>>;
2195
- type AgentDefinitionConfig = EnvRefString<Pick<AgentConfig, "agent" | "model" | "scheduler" | "session" | "tools">> & {
2185
+ type AgentDefinitionConfig = EnvRefString<Pick<AgentConfig, "agent" | "model" | "scheduler" | "session" | "tools" | "mcpServers">> & {
2196
2186
  provider?: ProviderConfigInput;
2197
2187
  } & {
2198
2188
  harness?: HarnessDefinition;
@@ -2230,11 +2220,11 @@ type WorkspaceDefinitionConfig = Omit<WorkspaceConfig, "isolation"> & {
2230
2220
  type WorkspaceResource<Name extends string = string> = ResourceDefinition<"workspace", Name, WorkspaceDefinitionConfig>;
2231
2221
  type SandboxResource<Name extends string = string> = ResourceDefinition<"sandbox", Name, SandboxDefinitionConfig>;
2232
2222
  type SkillResource<Name extends string = string> = ResourceDefinition<"skill", Name, SkillDefinitionConfig>;
2233
- type ToolResource<Name extends string = string> = ResourceDefinition<"tool", Name, ToolDefinitionConfig>;
2223
+ type McpServerResource<Name extends string = string> = ResourceDefinition<"mcp", Name, McpServerDefinitionConfig>;
2234
2224
  type PolicyResource<Name extends string = string> = ResourceDefinition<"policy", Name, PolicyDefinitionConfig>;
2235
2225
  type CronResource<Name extends string = string> = ResourceDefinition<"cron", Name, CronDefinitionConfig>;
2236
2226
  type ChannelResource<Name extends string = string> = ResourceDefinition<"channelRecord", Name, ChannelDefinitionConfig>;
2237
- type AnyResource = AgentResource | WorkspaceResource | SandboxResource | CronResource | SkillResource | ToolResource | PolicyResource | ChannelResource;
2227
+ type AnyResource = AgentResource | WorkspaceResource | SandboxResource | CronResource | SkillResource | McpServerResource | PolicyResource | ChannelResource;
2238
2228
  /**
2239
2229
  * References an account/environment variable resolved on the SERVER at runtime —
2240
2230
  * set it with `broods env set <NAME>` or in the dashboard (the Convex-style
@@ -2271,14 +2261,12 @@ declare function defineHarness<const Definition extends HarnessDefinition>(defin
2271
2261
  declare function defineWorkspace<const Name extends string>(input: ResourceInput<Name, WorkspaceDefinitionConfig>): WorkspaceResource<Name>;
2272
2262
  declare function defineSandbox<const Name extends string>(input: ResourceInput<Name, SandboxDefinitionConfig>): SandboxResource<Name>;
2273
2263
  declare function defineSkill<const Name extends string>(input: ResourceInput<Name, SkillDefinitionConfig>): SkillResource<Name>;
2274
- declare function defineTool<const Name extends string, Input = Record<string, unknown>>(input: {
2275
- name: Name;
2276
- } & ToolDefinitionConfig<Input>): ToolResource<Name>;
2264
+ declare function defineMcp<const Name extends string>(input: ResourceInput<Name, McpServerDefinitionConfig>): McpServerResource<Name>;
2277
2265
  declare function definePolicy<const Name extends string>(input: ResourceInput<Name, PolicyDefinitionConfig>): PolicyResource<Name>;
2278
2266
  declare function defineCron<const Name extends string>(input: ResourceInput<Name, CronDefinitionConfig>): CronResource<Name>;
2279
2267
  declare function isResource(value: unknown): value is AnyResource;
2280
2268
  declare function isConnectionDefinition(value: unknown): value is AnyConnectionDefinition;
2281
2269
  declare function isBroodsConfig(value: unknown): value is BroodsConfigDefinition;
2282
2270
 
2283
- export { BroodsAccountApiError, BroodsAccountClient, BroodsClient, BroodsWebSocketClient, DEFAULT_CORE_BASE_URL, IngressAcceptedError, MAX_OBSERVABILITY_BACKFILL, BroodsWebSocketClient as WebSocketClient, BroodsWebSocketClient as WebsocketClient, defineAgent, defineBroods, defineCron, defineDiscordChannel, defineDiscordConnection, defineGitHubChannel, defineGitHubConnection, defineHarness, definePancakeChannel, definePancakeConnection, definePolicy, defineSandbox, defineSkill, defineSlackChannel, defineSlackConnection, defineTelegramChannel, defineTelegramConnection, defineTool, defineWorkspace, defineZaloChannel, defineZaloConnection, env, envPlaceholder, isBroodsConfig, isConnectionDefinition, isLogLevel, isObservabilityClientMessage, isResource, isRootSpanKind, normalizeHttpServiceUrl, readSseStream, resolveRunEvents, toWebSocketBaseUrl };
2284
- export type { Account, AccountAgent, AccountChannel, AccountEnvVar, AccountPolicy, AccountRole, AccountSandbox, AccountTool, AccountWorkspace, Agent, AgentChannelsConfig, AgentCodeHookConfig, AgentConfig, AgentConfigDoc, AgentDefinitionConfig, AgentDiscordChannelConfig, AgentGitHubChannelConfig, AgentHandle, AgentHookEventName, AgentHooks, AgentHooksConfig, AgentPancakeChannelConfig, AgentProviderSettings, AgentReference, AgentResource, AgentRunEventInput, AgentRunInput, AgentRunModelOverrides, AgentRunOverrides, AgentRunResult, AgentSkillsDefinitionConfig, AgentSlackChannelConfig, AgentStreamPart, AgentSubagentDefinitionConfig, AgentTelegramChannelConfig, AgentWebhookHookConfig, AgentWorkspaceInput, AgentWorkspaceRef, AgentWorkspaceRefInput, AgentZaloChannelConfig, AnyConnectionDefinition, AnyResource, AssumeRoleResult, AsyncAgentRun, AsyncPollOptions, AsyncRequestAccepted, AsyncStatus, BroodsAccount, BroodsAccountClientOptions, BroodsClientOptions, BroodsConfigDefinition, BroodsProjectConfig, BroodsWebSocketClientOptions, ChannelAgentInput, ChannelDefinitionConfig, ChannelMessageReceived, ChannelPartition, ChannelRecordConfig, ChannelReference, ChannelReplyIn, ChannelResource, ChannelType, CliManifest, CliManifestResource, CliResourceKind, ConnectionDefinition, CreateAgentResult, CreateClientCronInput, CreateCronInput, CreateToolInput, Cron, CronDefinitionConfig, CronDoc, CronLastStatus, CronResource, CronRun, CronStatus, CustomTool, DeleteAccountResult, DiscordChannelInput, DiscordConnectionDefinition, DiscordConnectionInput, DiscordMessageSource, DiscordSource, Doc, EnvAccessor, EnvRef, EnvRefString, GeneratedIds, GitHubChannelInput, GitHubConnectionDefinition, GitHubConnectionInput, GitHubMessageSource, GitHubSource, HarnessDefinition, HarnessType, HookContext, Id, IngressMode, IngressStatus, LogLevel, ObservabilityBackfillMessage, ObservabilityClientMessage, ObservabilityErrorMessage, ObservabilityLogEntry, ObservabilityLogMessage, ObservabilityReadyMessage, ObservabilityServerMessage, ObservabilitySpanMessage, ObservabilitySpanRow, ObservabilitySubscribeMessage, ObservabilityUnsubscribeMessage, PancakeChannelInput, PancakeConnectionDefinition, PancakeConnectionInput, PancakeMessageSource, PancakeSource, PolicyDefinitionConfig, PolicyDocument, PolicyResource, ProjectDoc, ProviderConfigInput, ProviderSettingsInput, ResourceApi, ResourceDefinition, ResourceInput, ResourceKind, RotateSecretResult, Sandbox, SandboxConfig, SandboxConfigDoc, SandboxDefinitionConfig, SandboxDefinitionOptions, SandboxLifecycleResult, SandboxResource, SandboxSnapshotResult, SandboxTerminalTicket, Skill, SkillDefinitionConfig, SkillResource, SkillUploadInput, SlackChannelInput, SlackConnectionDefinition, SlackConnectionInput, SlackMessageSource, SlackSource, StageDoc, TelegramChannelInput, TelegramConnectionDefinition, TelegramConnectionInput, TelegramMessageSource, TelegramSource, ToolApprovalSummary, ToolDefinitionConfig, ToolExecuteContext, ToolExecuteOptions, ToolResource, ToolScope, UpdateAgentInput, UpdateCronInput, UpdateToolInput, WebSocketAttachInput, WebSocketClientAttachMessage, WebSocketClientCancelMessage, WebSocketClientControlMessage, WebSocketClientExecuteMessage, WebSocketClientMessage, WebSocketConstructorLike, WebSocketHandlers, WebSocketLike, WebSocketOutputMessage, WebSocketRunInput, WebSocketServerMessage, WebSocketStreamMessage, WebSocketSubscription, Workspace, WorkspaceConfig, WorkspaceConfigDoc, WorkspaceDefinitionConfig, WorkspaceFileEntry, WorkspaceResource, ZaloChannelInput, ZaloConnectionDefinition, ZaloConnectionInput, ZaloMessageSource, ZaloSource };
2271
+ export { BroodsAccountApiError, BroodsAccountClient, BroodsClient, BroodsWebSocketClient, DEFAULT_CORE_BASE_URL, IngressAcceptedError, MAX_OBSERVABILITY_BACKFILL, BroodsWebSocketClient as WebSocketClient, BroodsWebSocketClient as WebsocketClient, defineAgent, defineBroods, defineCron, defineDiscordChannel, defineDiscordConnection, defineGitHubChannel, defineGitHubConnection, defineHarness, defineMcp, definePancakeChannel, definePancakeConnection, definePolicy, defineSandbox, defineSkill, defineSlackChannel, defineSlackConnection, defineTelegramChannel, defineTelegramConnection, defineWorkspace, defineZaloChannel, defineZaloConnection, env, envPlaceholder, isBroodsConfig, isConnectionDefinition, isLogLevel, isObservabilityClientMessage, isResource, isRootSpanKind, normalizeHttpServiceUrl, readSseStream, resolveRunEvents, toWebSocketBaseUrl };
2272
+ export type { Account, AccountAgent, AccountChannel, AccountEnvVar, AccountMcpServer, AccountPolicy, AccountRole, AccountSandbox, AccountWorkspace, Agent, AgentChannelsConfig, AgentCodeHookConfig, AgentConfig, AgentConfigDoc, AgentDefinitionConfig, AgentDiscordChannelConfig, AgentGitHubChannelConfig, AgentHandle, AgentHookEventName, AgentHooks, AgentHooksConfig, AgentPancakeChannelConfig, AgentProviderSettings, AgentReference, AgentResource, AgentRunEventInput, AgentRunInput, AgentRunModelOverrides, AgentRunOverrides, AgentRunResult, AgentSkillsDefinitionConfig, AgentSlackChannelConfig, AgentStreamPart, AgentSubagentDefinitionConfig, AgentTelegramChannelConfig, AgentWebhookHookConfig, AgentWorkspaceInput, AgentWorkspaceRef, AgentWorkspaceRefInput, AgentZaloChannelConfig, AnyConnectionDefinition, AnyResource, AssumeRoleResult, AsyncAgentRun, AsyncPollOptions, AsyncRequestAccepted, AsyncStatus, BroodsAccount, BroodsAccountClientOptions, BroodsClientOptions, BroodsConfigDefinition, BroodsProjectConfig, BroodsWebSocketClientOptions, ChannelAgentInput, ChannelDefinitionConfig, ChannelMessageReceived, ChannelPartition, ChannelRecordConfig, ChannelReference, ChannelReplyIn, ChannelResource, ChannelType, CliManifest, CliManifestResource, ConnectionDefinition, CreateAgentResult, CreateClientCronInput, CreateCronInput, CreateMcpServerInput, Cron, CronDefinitionConfig, CronDoc, CronLastStatus, CronResource, CronRun, CronStatus, DeleteAccountResult, DiscordChannelInput, DiscordConnectionDefinition, DiscordConnectionInput, DiscordMessageSource, DiscordSource, Doc, EnvAccessor, EnvRef, EnvRefString, GeneratedIds, GitHubChannelInput, GitHubConnectionDefinition, GitHubConnectionInput, GitHubMessageSource, GitHubSource, HarnessDefinition, HarnessType, HookContext, Id, IngressMode, IngressStatus, LogLevel, McpServerDefinitionConfig, McpServerResource, ObservabilityBackfillMessage, ObservabilityClientMessage, ObservabilityErrorMessage, ObservabilityLogEntry, ObservabilityLogMessage, ObservabilityReadyMessage, ObservabilityServerMessage, ObservabilitySpanMessage, ObservabilitySpanRow, ObservabilitySubscribeMessage, ObservabilityUnsubscribeMessage, PancakeChannelInput, PancakeConnectionDefinition, PancakeConnectionInput, PancakeMessageSource, PancakeSource, PolicyDefinitionConfig, PolicyDocument, PolicyResource, ProjectDoc, ProviderConfigInput, ProviderSettingsInput, ResourceApi, ResourceDefinition, ResourceInput, ResourceKind, RotateSecretResult, Sandbox, SandboxConfig, SandboxConfigDoc, SandboxDefinitionConfig, SandboxDefinitionOptions, SandboxLifecycleResult, SandboxResource, SandboxSnapshotResult, SandboxTerminalTicket, Skill, SkillDefinitionConfig, SkillResource, SkillUploadInput, SlackChannelInput, SlackConnectionDefinition, SlackConnectionInput, SlackMessageSource, SlackSource, StageDoc, StageScope, TelegramChannelInput, TelegramConnectionDefinition, TelegramConnectionInput, TelegramMessageSource, TelegramSource, ToolApprovalSummary, UpdateAgentInput, UpdateCronInput, UpdateMcpServerInput, WebSocketAttachInput, WebSocketClientAttachMessage, WebSocketClientCancelMessage, WebSocketClientControlMessage, WebSocketClientExecuteMessage, WebSocketClientMessage, WebSocketConstructorLike, WebSocketHandlers, WebSocketLike, WebSocketOutputMessage, WebSocketRunInput, WebSocketServerMessage, WebSocketStreamMessage, WebSocketSubscription, Workspace, WorkspaceConfig, WorkspaceConfigDoc, WorkspaceDefinitionConfig, WorkspaceFileEntry, WorkspaceResource, ZaloChannelInput, ZaloConnectionDefinition, ZaloConnectionInput, ZaloMessageSource, ZaloSource };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{S,t,y}from"./chunk-a6p0aaky.js";import{p,b,c,x,d,m,_,g,T,k,E,C,R,A,M,P,O,N,D,j,z,U,L,Z,F,B,W,q,K,V,r,i,v}from"./chunk-hkywabw6.js";var u=500;function f(e){if(typeof e!=="object"||e===null)return!1;let n=e;if(n.type==="subscribe"){let s=n.stream;if(s!=="logs"&&s!=="traces")return!1;let o=n.backfill;if(o!==void 0&&(typeof o!=="number"||!Number.isSafeInteger(o)||o<0||o>500))return!1;if(n.liveOnly!==void 0&&typeof n.liveOnly!=="boolean")return!1;let a=n.minLevel;if(a!==void 0&&!l(a))return!1;return!0}if(n.type==="unsubscribe"){let s=n.stream;return s==="logs"||s==="traces"}return!1}function l(e){return e==="DEBUG"||e==="INFO"||e==="WARN"||e==="ERROR"}function I(e){return e==="task"||e==="cron"||e==="subtask"}export{t as BroodsAccountApiError,y as BroodsAccountClient,d as BroodsClient,_ as BroodsWebSocketClient,c as DEFAULT_CORE_BASE_URL,x as IngressAcceptedError,u as MAX_OBSERVABILITY_BACKFILL,_ as WebSocketClient,_ as WebsocketClient,L as defineAgent,U as defineBroods,V as defineCron,P as defineDiscordChannel,k as defineDiscordConnection,O as defineGitHubChannel,E as defineGitHubConnection,Z as defineHarness,N as definePancakeChannel,C as definePancakeConnection,K as definePolicy,B as defineSandbox,W as defineSkill,D as defineSlackChannel,R as defineSlackConnection,j as defineTelegramChannel,A as defineTelegramConnection,q as defineTool,F as defineWorkspace,z as defineZaloChannel,M as defineZaloConnection,T as env,S as envPlaceholder,v as isBroodsConfig,i as isConnectionDefinition,l as isLogLevel,f as isObservabilityClientMessage,r as isResource,I as isRootSpanKind,m as normalizeHttpServiceUrl,b as readSseStream,p as resolveRunEvents,g as toWebSocketBaseUrl};
1
+ import{a as O,b as R,c as L}from"./chunk-mnc0tart.js";import{A as _,B as P,C as U,D as B,E as H,F as K,G as j,H as G,I as F,J as z,K as Z,L as V,M as X,N as Y,O as J,P as Q,Q as ee,R as te,S as re,T as ne,d as v,e as S,p as k,q as A,r as M,s as h,t as W,u as w,v as E,w as D,x as T,y as q,z as N}from"./chunk-2149yqc1.js";import"./chunk-3cvxks4m.js";var a=500;function i(e){if(typeof e!=="object"||e===null)return!1;let t=e;if(t.type==="subscribe"){let r=t.stream;if(r!=="logs"&&r!=="traces")return!1;let n=t.backfill;if(n!==void 0&&(typeof n!=="number"||!Number.isSafeInteger(n)||n<0||n>500))return!1;if(t.liveOnly!==void 0&&typeof t.liveOnly!=="boolean")return!1;let s=t.minLevel;if(s!==void 0&&!o(s))return!1;return!0}if(t.type==="unsubscribe"){let r=t.stream;return r==="logs"||r==="traces"}return!1}function o(e){return e==="DEBUG"||e==="INFO"||e==="WARN"||e==="ERROR"}function p(e){return e==="task"||e==="cron"||e==="subtask"}export{R as BroodsAccountApiError,L as BroodsAccountClient,M as BroodsClient,W as BroodsWebSocketClient,k as DEFAULT_CORE_BASE_URL,A as IngressAcceptedError,a as MAX_OBSERVABILITY_BACKFILL,W as WebSocketClient,W as WebsocketClient,z as defineAgent,F as defineBroods,ee as defineCron,U as defineDiscordChannel,D as defineDiscordConnection,B as defineGitHubChannel,T as defineGitHubConnection,Z as defineHarness,J as defineMcp,H as definePancakeChannel,q as definePancakeConnection,Q as definePolicy,X as defineSandbox,Y as defineSkill,K as defineSlackChannel,N as defineSlackConnection,j as defineTelegramChannel,_ as defineTelegramConnection,V as defineWorkspace,G as defineZaloChannel,P as defineZaloConnection,E as env,O as envPlaceholder,ne as isBroodsConfig,re as isConnectionDefinition,o as isLogLevel,i as isObservabilityClientMessage,te as isResource,p as isRootSpanKind,h as normalizeHttpServiceUrl,S as readSseStream,v as resolveRunEvents,w as toWebSocketBaseUrl};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "broods",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "CLI + TypeScript SDK for the broods agent platform.",
5
5
  "keywords": [
6
6
  "agents",
@@ -1,2 +0,0 @@
1
- var c=/^[A-Z][A-Z0-9_]*$/;function S(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 t extends Error{status;body;constructor(e,n,s,o){super(`${e} ${n} failed with ${s}: ${o}`);this.name="BroodsAccountApiError",this.status=s,this.body=o}}function r(e){return typeof process<"u"?process?.env?.[e]:void 0}function a(e){return`?${new URLSearchParams({project:e.project,stage:e.stage}).toString()}`}class y{baseUrl;bearerToken;fetchImpl;constructor(e={}){let n=e.baseUrl??r("BROODS_BASE_URL")??"https://gateway.broods.app",s=e.sessionToken??e.accountSecret??r("BROODS_SESSION_TOKEN")??r("BROODS_ACCOUNT_SECRET");if(!s)throw Error("BroodsAccountClient requires an accountSecret or sessionToken (or BROODS_ACCOUNT_SECRET / BROODS_SESSION_TOKEN).");let o=n.length;while(o>0&&n.charCodeAt(o-1)===47)o-=1;this.baseUrl=n.slice(0,o),this.bearerToken=s,this.fetchImpl=e.fetch??fetch}async getAccount(){let e=await this.request("GET","/v1/account");if(!e)throw new t("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,n={}){let s=await this.request("POST","/v1/account/assume-role",{roleId:e,...n.ttlSeconds!==void 0?{ttlSeconds:n.ttlSeconds}:{}});if(!s)throw new t("POST","/v1/account/assume-role",404,"Role not found");return s}async rotateSecret(){let e=await this.request("POST","/v1/account/rotate-secret");if(!e)throw new t("POST","/v1/account/rotate-secret",404,"Account not found");return e}async deleteAccount(){return await this.request("DELETE","/v1/account")??{deleted:!1}}webhookUrl(e,n){let s=[e,n].map(encodeURIComponent);return`${this.baseUrl}/webhooks/${s.join("/")}`}async listAgents(){return(await this.request("GET","/v1/agents"))?.agents??[]}async createAgent(e){let n=await this.request("POST","/v1/agents",e);if(!n)throw new t("POST","/v1/agents",404,"Not found");return n}async getAgent(e){return await this.request("GET",`/v1/agents/${encodeURIComponent(e)}`)}async updateAgent(e,n){return await this.request("PATCH",`/v1/agents/${encodeURIComponent(e)}`,n)}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,n){await this.request("PUT",`/v1/env/${encodeURIComponent(e)}`,{value:n})}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 n=await this.request("POST","/v1/crons",e);if(!n)throw new t("POST","/v1/crons",404,"Not found");return n}async getCron(e){return await this.request("GET",`/v1/crons/${encodeURIComponent(e)}`)}async updateCron(e,n){return await this.request("PATCH",`/v1/crons/${encodeURIComponent(e)}`,n)}async deleteCron(e){return(await this.request("DELETE",`/v1/crons/${encodeURIComponent(e)}`))?.deleted??!1}async listCronRuns(e,n={}){let s=n.limit!==void 0?`?limit=${n.limit}`:"";return(await this.request("GET",`/v1/crons/${encodeURIComponent(e)}/runs${s}`))?.runs??[]}async listWorkspaces(){return(await this.request("GET","/v1/workspaces"))?.workspaces??[]}async createWorkspace(e){let n=await this.request("POST","/v1/workspaces",e);if(!n)throw new t("POST","/v1/workspaces",404,"Not found");return n}async getWorkspace(e){return await this.request("GET",`/v1/workspaces/${encodeURIComponent(e)}`)}async updateWorkspace(e,n){return await this.request("PATCH",`/v1/workspaces/${encodeURIComponent(e)}`,n)}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,n){let s=`?path=${encodeURIComponent(n)}`;return(await this.request("GET",`/v1/workspaces/${encodeURIComponent(e)}/files${s}`))?.url??null}async uploadWorkspaceFile(e,n){let s=`/v1/workspaces/${encodeURIComponent(e)}/files`,o=await this.request("POST",s,n);if(!o)throw new t("POST",s,404,"Workspace not found");return o.file}async renameWorkspaceFile(e,n,s){return(await this.request("PATCH",`/v1/workspaces/${encodeURIComponent(e)}/files`,{path:n,newPath:s}))?.renamed??!1}async deleteWorkspaceFile(e,n){return(await this.request("DELETE",`/v1/workspaces/${encodeURIComponent(e)}/files`,{path:n}))?.deleted??!1}async listSandboxes(){return(await this.request("GET","/v1/sandboxes"))?.sandboxes??[]}async createSandbox(e){let n=await this.request("POST","/v1/sandboxes",e);if(!n)throw new t("POST","/v1/sandboxes",404,"Not found");return n}async getSandbox(e){return await this.request("GET",`/v1/sandboxes/${encodeURIComponent(e)}`)}async updateSandbox(e,n){return await this.request("PATCH",`/v1/sandboxes/${encodeURIComponent(e)}`,n)}async deleteSandbox(e){return(await this.request("DELETE",`/v1/sandboxes/${encodeURIComponent(e)}`))?.deleted??!1}async suspendSandbox(e,n){return await this.sandboxAction(e,"suspend",{reservationKey:n})}async resumeSandbox(e,n){return await this.sandboxAction(e,"resume",{reservationKey:n})}async terminateSandbox(e,n){return await this.sandboxAction(e,"terminate",{reservationKey:n})}async snapshotSandbox(e,n,s){return await this.sandboxAction(e,"snapshot",{reservationKey:n,name:s})}async openSandboxTerminal(e,n){return await this.sandboxAction(e,"terminal",{reservationKey:n})}async listTools(e){let n=`/v1/tools${a(e)}`,s=await this.request("GET",n);if(!s)throw new t("GET",n,404,"Not found");return s.tools??[]}async createTool(e,n){let s=`/v1/tools${a(e)}`,o=await this.request("POST",s,n);if(!o)throw new t("POST",s,404,"Not found");return o}async getTool(e){return await this.request("GET",`/v1/tools/${encodeURIComponent(e)}`)}async updateTool(e,n){return await this.request("PATCH",`/v1/tools/${encodeURIComponent(e)}`,n)}async deleteTool(e){return(await this.request("DELETE",`/v1/tools/${encodeURIComponent(e)}`))?.deleted??!1}async listPolicies(){return(await this.request("GET","/v1/policies"))?.policies??[]}async createPolicy(e){let n=await this.request("POST","/v1/policies",e);if(!n)throw new t("POST","/v1/policies",404,"Not found");return n}async getPolicy(e){return await this.request("GET",`/v1/policies/${encodeURIComponent(e)}`)}async updatePolicy(e,n){return await this.request("PATCH",`/v1/policies/${encodeURIComponent(e)}`,n)}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 n=await this.request("POST","/v1/roles",e);if(!n)throw new t("POST","/v1/roles",404,"Not found");return n}async getRole(e){return await this.request("GET",`/v1/roles/${encodeURIComponent(e)}`)}async updateRole(e,n){return await this.request("PATCH",`/v1/roles/${encodeURIComponent(e)}`,n)}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 n=await this.request("POST","/v1/channels",e);if(!n)throw new t("POST","/v1/channels",404,"Not found");return n}async getChannel(e){return await this.request("GET",`/v1/channels/${encodeURIComponent(e)}`)}async updateChannel(e,n){return await this.request("PATCH",`/v1/channels/${encodeURIComponent(e)}`,n)}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 n=await this.request("POST","/v1/skills",e);if(!n)throw new t("POST","/v1/skills",404,"Not found");return n}async getSkill(e){return await this.request("GET",`/v1/skills/${encodeURIComponent(e)}`)}async uploadSkill(e,n){let s=`/v1/skills/${encodeURIComponent(e)}`,o=await this.request("PUT",s,n);if(!o)throw new t("PUT",s,404,"Skill not found");return o}async deleteSkill(e){return(await this.request("DELETE",`/v1/skills/${encodeURIComponent(e)}`))?.deleted??!1}async sandboxAction(e,n,s){let o=`/v1/sandboxes/${encodeURIComponent(e)}/${n}`,i=await this.request("POST",o,s);if(!i)throw new t("POST",o,404,"Sandbox not found");return i}async request(e,n,s){let o=await this.fetchImpl(`${this.baseUrl}${n}`,{method:e,headers:{Authorization:`Bearer ${this.bearerToken}`,...s!==void 0?{"Content-Type":"application/json"}:{}},...s!==void 0?{body:JSON.stringify(s)}:{}});if(o.status===404)return null;if(!o.ok)throw new t(e,n,o.status,await o.text());return await o.json()}}
2
- export{S,t,y};
@@ -1,5 +0,0 @@
1
- function p(t){if(t.events&&t.input!==void 0)throw Error("Run input accepts either `input` or `events`, not both");if(t.events&&t.events.length>0)return t.events;if(typeof t.input==="string")return[{role:"user",content:[{type:"text",text:t.input}]}];throw Error("Run input requires `input` (string) or a non-empty `events` array")}async function*b(t){let y=t.getReader(),S=new TextDecoder,I="";try{while(!0){let{done:H,value:G}=await y.read();if(H)break;I+=S.decode(G,{stream:!0});let J=I.split(`
2
- `);I=J.pop()??"";for(let Y of J)if(Y.startsWith("data: "))yield Y.slice(6)}}finally{y.releaseLock()}}import{mkdir as Pe,readFile as Ue,writeFile as We}from"node:fs/promises";import{homedir as Me}from"node:os";import{dirname as $e,join as _e}from"node:path";var o="broods",s="_generated",l=_e(Me(),".broods","config.json"),pe="broods.app",fe="dashboard",Ke="gateway";async function w(){let t=process.env.BROODS_TOKEN,y=process.env.BROODS_BASE_URL;if(t&&y)return{baseUrl:n(y),token:t,createdAt:new Date().toISOString()};try{let S=JSON.parse(await Ue(l,"utf8"));if(typeof S.baseUrl!=="string"||!S.baseUrl)return null;return S}catch{return null}}async function f(t){await Pe($e(l),{recursive:!0}),await We(l,`${JSON.stringify(t,null,2)}
3
- `,"utf8")}function a(){return process.env.BROODS_STAGE}function n(t){let y=t.length;while(y>0&&t.charCodeAt(y-1)===47)y-=1;return t.slice(0,y)}function h(t){try{let y=new URL(t);if(!y.hostname.startsWith(`${fe}.`))return;let S=y.hostname.slice(fe.length+1);if(S!==pe&&!S.endsWith(`.${pe}`))return;return y.hostname=`${Ke}.${S}`,n(y.origin)}catch{return}}import{existsSync as ye,readFileSync as ge,statSync as He}from"node:fs";import{join as he,resolve as Ce}from"node:path";var le=new Map,Be=new Set(Object.keys(process.env)),me=new Set;function e(t=process.cwd()){je(t);let y=Ge();return{dashboardUrl:process.env.BROODS_DASHBOARD_URL??y?.dashboardUrl,baseUrl:process.env.BROODS_BASE_URL??y?.baseUrl,token:process.env.BROODS_TOKEN??y?.token,project:process.env.BROODS_PROJECT,stage:a()}}function u(t,y=process.cwd()){let S=process.env[t];if(S===void 0)return!1;let I=Ce(y);for(let H of[".env.local",".env"]){let G=he(I,H);if(!ye(G))continue;let J=Se(ge(G,"utf8"));if(J[t]!==void 0)return J[t]!==S}return!0}function je(t){let y=Ce(t),S=process.env.BROODS_RELOAD_ENV==="1";for(let I of[".env",".env.local"]){let H=he(y,I);if(!ye(H)){le.delete(H);continue}let G=He(H).mtimeMs,J=le.get(H);if(J&&J.mtimeMs===G)continue;le.set(H,{mtimeMs:G});for(let[Y,Q]of Object.entries(Se(ge(H,"utf8"))))if(S||!Be.has(Y)||me.has(Y))process.env[Y]=Q,me.add(Y)}}function Se(t){let y={};for(let S of t.split(/\r?\n/)){let I=S.trim();if(!I||I.startsWith("#"))continue;let H=I.startsWith("export ")?I.slice(7).trim():I,G=H.indexOf("=");if(G<=0)continue;let J=H.slice(0,G).trim();if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(J))continue;y[J]=Le(H.slice(G+1).trim())}return y}function Le(t){if(t.startsWith('"')&&t.endsWith('"'))return t.slice(1,-1).replace(/\\n/g,`
4
- `).replace(/\\"/g,'"');if(t.startsWith("'")&&t.endsWith("'"))return t.slice(1,-1);let y=t.indexOf(" #");return y>=0?t.slice(0,y).trimEnd():t}function Ge(){try{let t=JSON.parse(ge(l,"utf8"));if(typeof t.baseUrl!=="string"||typeof t.token!=="string")return null;return{baseUrl:n(t.baseUrl),...typeof t.dashboardUrl==="string"?{dashboardUrl:n(t.dashboardUrl)}:{},token:t.token}}catch{return null}}var c="https://gateway.broods.app";class x extends Error{accepted;constructor(t){super(`Ingress ${t.eventId} was accepted asynchronously with status ${t.status??"queued"}`);this.accepted=t;this.name="IngressAcceptedError"}}class d{baseUrl;apiKey;fetchImpl;constructor(t={}){e(),this.baseUrl=m(t.baseUrl||t.host||process.env.BROODS_BASE_URL||process.env.BROODS_HOST||c),this.apiKey=t.apiKey||process.env.BROODS_API_KEY||void 0,this.fetchImpl=t.fetch??fetch}channelWebhookUrl(t){return`${this.baseUrl}${t.webhookPath.startsWith("/")?"":"/"}${t.webhookPath}`}accountWebhookUrl(t,y){let S=[t,y].map(encodeURIComponent);return`${this.baseUrl}/webhooks/${S.join("/")}`}stageWebhookUrl(t,y,S){let I=[t,"dev",y,S].map(encodeURIComponent);return`${this.baseUrl}/webhooks/${I.join("/")}`}agent(t,y){if(typeof t==="string"){let I=t,H=y??"";if(!H)throw Error(`Agent ${I} is missing a generated id. Run broods dev --once or broods deploy to sync it first.`);return{id:H,run:(G)=>this.run({...G,agentName:I,agentId:H}),runAsync:(G)=>this.runAsync({...G,agentName:I,agentId:H}),stream:(G)=>this.stream({...G,agentName:I,agentId:H})}}let S=t;if(!S.id)throw Error(`Agent ${S.name} is missing a generated id. Run broods dev --once or broods deploy to sync it first.`);return{id:S.id,run:(I)=>this.run(S,I),runAsync:(I)=>this.runAsync(S,I),stream:(I)=>this.stream(S,I)}}async run(t,y){let S=[],I="",H=y?this.stream(t,y):this.stream(t);for await(let G of H)if(S.push(G),G.type==="text-delta")I+=G.text;return{text:I,events:S}}async*stream(t,y){let S=y?{...y,agentId:t.id,agentName:t.name}:t,I=Re(S,"cli"),H=y?this.scopedUrl(t):this.baseUrl,G=await this.openStream(I,H);if(!G.ok)throw Error(`Run failed: ${G.status} ${await G.text()}`);if(G.status===202)throw new x(be(await G.json(),I));if(!G.body)throw Error("Run response has no body");for await(let J of b(G.body)){let Y;try{Y=JSON.parse(J)}catch{continue}if(Y.type==="error")throw Error(`Agent run failed: ${Ve(Y.error)}`);yield Y}}async runAsync(t,y){let S=y?{...y,agentId:t.id,agentName:t.name}:t,I=Re(S,"async"),H=y?this.scopedUrl(t,"/async"):`${this.baseUrl}/async`,G=await this.fetchJson(H,{method:"POST",headers:this.apiKeyHeaders(),body:JSON.stringify(I)});if(G.status!==202)throw Error(`Async run failed: ${G.status} ${await Fe(G,"202 JSON")}`);let J=be(await G.json(),I);return{...J,conversationKey:I.conversationKey,poll:()=>this.getAsyncStatus(J),wait:(Y)=>this.waitForAsyncStatus(J,Y)}}async getAsyncStatus(t,y={}){let S=this.resolveStatusUrl(t,y),I=await this.fetchJson(S,{method:"GET",headers:this.apiKeyHeaders()});if(I.status===404)return{status:"not_found"};if(!I.ok)throw Error(`Status check failed: ${I.status} ${await I.text()}`);return await I.json()}async waitForAsyncStatus(t,y={}){let S=Date.now()+(y.timeoutMs??180000),I=y.intervalMs??2000;while(Date.now()<S){if(y.signal?.aborted)throw Error("Async status polling aborted.");let H=await this.getAsyncStatus(t,y);if(H.status==="awaiting_approval"||H.status==="completed"||H.status==="failed"||H.status==="expired"||H.status==="not_found")return H;await ze(I,y.signal)}throw Error("Polling timeout")}async createCron(t){let y=await this.fetchJson(`${this.baseUrl}/v1/crons`,{method:"POST",headers:this.apiKeyHeaders(),body:JSON.stringify(Ze(t))});if(y.status!==201)throw Error(`Create cron job failed: ${y.status} ${await te(y)}`);return await y.json()}async listCrons(){let t=await this.fetchJson(`${this.baseUrl}/v1/crons`,{method:"GET",headers:this.apiKeyHeaders()});if(!t.ok)throw Error(`List cron jobs failed: ${t.status} ${await te(t)}`);return(await t.json()).crons}async getCron(t){let y=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(t)}`,{method:"GET",headers:this.apiKeyHeaders()});if(y.status===404)return null;if(!y.ok)throw Error(`Get cron job failed: ${y.status} ${await te(y)}`);return await y.json()}async listCronRuns(t,y={}){let S=new URLSearchParams;if(y.limit!==void 0)S.set("limit",String(y.limit));let I=S.size>0?`?${S}`:"",H=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(t)}/runs${I}`,{method:"GET",headers:this.apiKeyHeaders()});if(!H.ok)throw Error(`List cron job runs failed: ${H.status} ${await te(H)}`);return(await H.json()).runs}async updateCron(t,y){let S=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(t)}`,{method:"PATCH",headers:this.apiKeyHeaders(),body:JSON.stringify(y)});if(!S.ok)throw Error(`Update cron job failed: ${S.status} ${await te(S)}`);return await S.json()}async deleteCron(t){let y=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(t)}`,{method:"DELETE",headers:this.apiKeyHeaders()});if(y.status===404)return!1;if(!y.ok)throw Error(`Delete cron job failed: ${y.status} ${await te(y)}`);return(await y.json()).deleted}scopedUrl(t,y=""){if(t.projectSlug&&t.stageSlug&&t.endpointId)return`${this.baseUrl}/v1/${encodeURIComponent(t.projectSlug)}/agents/${encodeURIComponent(t.stageSlug)}/${encodeURIComponent(t.endpointId)}${y}`;return`${this.baseUrl}${y}`}async openStream(t,y){if(this.apiKey)return await this.fetchCore(t,y,{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(t,y,S){try{return await this.fetchImpl(y,{method:"POST",headers:{"Content-Type":"application/json",Accept:"text/event-stream",...S},body:JSON.stringify(t)})}catch(I){throw Error(`Cannot access the broods core service at ${y}. The SDK uses ${c} 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: ${I instanceof Error?I.message:String(I)}`)}}apiKeyHeaders(){if(!this.apiKey)throw Error("BroodsClient requires apiKey or BROODS_API_KEY.");return{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}}async fetchJson(t,y){try{return await this.fetchImpl(t,{...y,headers:{"Content-Type":"application/json",...y.headers}})}catch(S){throw Error(`Cannot access the broods core service at ${t}. Cause: ${S instanceof Error?S.message:String(S)}`)}}resolveStatusUrl(t,y){if(typeof t!=="string")return t.statusUrl;if(/^https?:\/\//.test(t))return t;if(!y.agentId)throw Error("Polling by status id requires agentId.");return Je(this.baseUrl,t,y.agentId)}}function m(t){let y=t.trim(),S=/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(y)?y:`https://${y}`;return n(S)}function Re(t,y){let S=t.eventId??`${y}-${Date.now()}`;return{agentId:t.agentId,eventId:S,conversationKey:t.conversationKey??S,events:p(t),...t.mode!==void 0?{mode:t.mode}:{},...t.idempotencyKey!==void 0?{idempotencyKey:t.idempotencyKey}:{},...t.system!==void 0?{system:t.system}:{},...t.model!==void 0?{model:t.model}:{}}}function be(t,y){if(!t||typeof t!=="object")throw Error("Async response must be an object");let S=t.statusUrl;if(typeof S!=="string"||S.length===0)throw Error("Async response missing statusUrl");let I=qe(S);if(!I.statusId)throw Error("Async response statusUrl missing status id");let H=typeof t.eventId==="string"?t.eventId:I.statusId,G=t.status,J=t.requestedMode;return{statusUrl:S,statusId:I.statusId,eventId:H,agentId:I.agentId??y.agentId,...G==="accepted"||G==="queued"||G==="applied"||G==="processing"?{status:G}:{},...J==="reject"||J==="followup"||J==="collect"||J==="steer"?{requestedMode:J}:{}}}function qe(t){let y=new URL(t),S=y.pathname.match(/\/status\/([^/]+)$/);return{statusId:S?.[1]?decodeURIComponent(S[1]):void 0,agentId:y.searchParams.get("agentId")??void 0}}function Je(t,y,S){return`${m(t)}/status/${encodeURIComponent(y)}?agentId=${encodeURIComponent(S)}`}async function Fe(t,y){if((t.headers.get("content-type")??"").toLowerCase().includes("text/event-stream"))return await t.body?.cancel().catch(()=>{}),`expected ${y}, 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 I=await t.text();if(I.length<=2000)return I;return`${I.slice(0,2000)}... [truncated ${I.length-2000} chars]`}async function te(t){let y=await t.text();if(y.includes("Request body must include eventId and conversationKey"))return`${y}. 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 y}function Ze(t){if("agentId"in t)return t;let y=t.agent,S=typeof y==="string"?y:y.id,{agent:I,...H}=t;return{...H,agentId:S}}function ze(t,y){return new Promise((S,I)=>{if(y?.aborted){I(Error("Async status polling aborted."));return}let H=setTimeout(S,t);y?.addEventListener("abort",()=>{clearTimeout(H),I(Error("Async status polling aborted."))},{once:!0})})}function Ve(t){if(typeof t==="string")return t;if(!t||typeof t!=="object")return String(t);let y=t,S=y.data?.error?.message??y.message??y.responseBody??JSON.stringify(t),I=y.name?`${y.name}: `:"",H=y.statusCode?` (HTTP ${y.statusCode})`:"";return`${I}${S}${H}`}var Ye=2000,ke=0,ie=1;class _{baseUrl;apiKey;WebSocketImpl;connectTimeoutMs;constructor(t={}){if(e(),this.baseUrl=ve(t.baseUrl||t.host||process.env.BROODS_BASE_URL||process.env.BROODS_HOST||c),this.apiKey=t.apiKey||process.env.BROODS_API_KEY||"",!this.apiKey)throw Error("BroodsWebSocketClient requires apiKey or BROODS_API_KEY.");this.WebSocketImpl=t.WebSocket,this.connectTimeoutMs=t.connectTimeoutMs??Ye}subscribe(t,y={}){let S=this.resolveWebSocket(),I=this.buildUrl(t),H=Xe(t),G=Ie(this.baseUrl),J=new S(I),Y=!1,Q=!1,ee=setTimeout(()=>{if(!Y)se(G)},this.connectTimeoutMs),de=(X=1000,ce="client closed")=>{if(Q)return;if(Q=!0,clearTimeout(ee),t.signal?.removeEventListener("abort",ae),J.readyState===ie||J.readyState===ke)J.close(X,ce)},se=(X)=>{y.onError?.(X),de(1011,"websocket error")},Oe=()=>{y.onDone?.(),de(1000,"done")},ae=()=>{if(J.readyState===ie)J.send(JSON.stringify({type:"cancel"}));se(Error("WebSocket run aborted."))};return t.signal?.addEventListener("abort",ae,{once:!0}),J.onopen=()=>{if(Y=!0,clearTimeout(ee),t.signal?.aborted){ae();return}J.send(JSON.stringify({type:"execute",agentId:H,events:p(t),sessionId:t.sessionId,...t.eventId!==void 0?{eventId:t.eventId}:{},...t.mode!==void 0?{mode:t.mode}:{},...t.idempotencyKey!==void 0?{idempotencyKey:t.idempotencyKey}:{},...t.system!==void 0?{system:t.system}:{},...t.model!==void 0?{model:t.model}:{}}))},J.onmessage=(X)=>{if(typeof X.data!=="string")return;let ce=Ae(X.data);if(!ce)return;let ue=xe(ce,y);switch(y.onMessage?.(ue),ue.type){case"meta":y.onMeta?.(ue);break;case"done":Oe();break;case"error":se(Error(we(ue.error)));break}},J.onerror=()=>{se(G)},J.onclose=(X)=>{if(clearTimeout(ee),t.signal?.removeEventListener("abort",ae),Q||X.code===1000)return;se(Error(X.reason||"WebSocket connection closed."))},{url:I,sendControl:function(X){if(J.readyState!==ie)throw Error("WebSocket is not open");J.send(JSON.stringify({type:"control",...X}))},close:de}}attach(t,y={}){let S=this.resolveWebSocket(),I=this.buildUrl(t),H=new S(I),G=!1,J=(Y=1000,Q="client closed")=>{if(G)return;if(G=!0,H.readyState===ie||H.readyState===ke)H.close(Y,Q)};return H.onopen=()=>H.send(JSON.stringify({type:"attach",...t,signal:void 0,agent:void 0})),H.onmessage=(Y)=>{if(typeof Y.data!=="string")return;let Q=Ae(Y.data);if(!Q)return;let ee=xe(Q,y);if(y.onMessage?.(ee),ee.type==="done")y.onDone?.();if(ee.type==="error")y.onError?.(Error(we(ee.error)))},H.onerror=()=>y.onError?.(Ie(this.baseUrl)),H.onclose=(Y)=>{if(!G&&Y.code!==1000)y.onError?.(Error(Y.reason||"WebSocket connection closed."))},{url:I,sendControl:function(Y){if(H.readyState!==ie)throw Error("WebSocket is not open");H.send(JSON.stringify({type:"control",...Y}))},close:J}}async*stream(t){let y=[],S=!1,I=null,H=null,G=()=>{H?.(),H=null},J=this.subscribe(t,{onMessage:function(Y){y.push(Y),G()},onDone:function(){S=!0,G()},onError:function(Y){I=Y,G()}});try{while(!0){if(y.length>0){yield y.shift();continue}if(I)throw I;if(S)return;await new Promise((Y)=>{H=Y})}}finally{J.close()}}buildUrl(t){let y=Qe(t),S=t.projectSlug??t.agent?.projectSlug,I=t.stageSlug??t.agent?.stageSlug,H=S?`/${S}`:"",G=I?`/${I}`:"";return`${g(this.baseUrl)}/v1${H}/agents${G}/${encodeURIComponent(y)}/ws?token=${encodeURIComponent(this.apiKey)}`}resolveWebSocket(){let t=this.WebSocketImpl??globalThis.WebSocket;if(!t)throw Error("WebSocket is not available. Pass a WebSocket implementation in BroodsWebSocketClient options.");return t}}function Xe(t){let y=t.agentId??t.agent?.id??t.endpointId;if(!y)throw Error("WebSocket run requires agent, agentId, or endpointId.");return y}function Qe(t){let y=t.endpointId??t.agent?.endpointId??t.agent?.id;if(!y)throw Error("WebSocket run requires agent or endpointId.");return y}function g(t){let y=new URL(ve(t));if(y.protocol==="https:")y.protocol="wss:";if(y.protocol==="http:")y.protocol="ws:";return n(y.toString())}function ve(t){let y=t.trim();if(/^wss?:\/\//.test(y))return n(y);return m(y)}function Ie(t){return Error(`Cannot access the WebSocket service at ${t}.`)}function xe(t,y){if(t.type!=="output")return t;let S=t;return y.onOutput?.(S),S.data}function Ae(t){try{let y=JSON.parse(t);return typeof y==="object"&&y!==null&&typeof y.type==="string"?y:null}catch{return null}}function we(t){if(typeof t==="string")return t;if(t&&typeof t==="object"&&typeof t.message==="string")return t.message;return t===void 0?"WebSocket stream error.":JSON.stringify(t)}var De=Symbol.for("broods.resource"),Ne=Symbol.for("broods.config"),Te=Symbol.for("broods.connection"),Ee=/^[A-Z][A-Z0-9_]{0,63}$/,T=new Proxy((t)=>{if(!Ee.test(t))throw Error("env name must match /^[A-Z][A-Z0-9_]*$/ and be at most 64 characters.");return{__beeblastEnv:!0,name:t}},{get:function(t,y,S){if(typeof y==="string"&&Ee.test(y))throw Error(`env.${y} is not supported; use env("${y}")`);return Reflect.get(t,y,S)}});function ne(t,y,S,I){return{[De]:!0,kind:t,name:y,...S?{description:S}:{},config:I}}function oe(t,y){let{partition:S,...I}=y;return{[Te]:!0,kind:"connection",type:t,...S?{partition:S}:{},config:I}}function re(t,y,S,I,H){return ne("channelRecord",t,y,{...H,externalId:S,...I?{workspaceRef:I}:{}})}function k(t){return oe("discord",t)}function E(t){return oe("github",t)}function C(t){return oe("pancake",t)}function R(t){return oe("slack",t)}function A(t){return oe("telegram",t)}function M(t){return oe("zalo",t)}function P(t){let{name:y,description:S,channelId:I,guildId:H,...G}=t;return re(y,S,I,H,G)}function O(t){let{name:y,description:S,repo:I,...H}=t,[G,...J]=I.split("/");if(!G||J.length!==1||!J[0])throw Error(`Channel "${y}" repo must be "owner/name", not "${I}"`);return re(y,S,I,G,H)}function N(t){let{name:y,description:S,conversationId:I,...H}=t;return re(y,S,I,void 0,H)}function D(t){let{name:y,description:S,channelId:I,teamId:H,...G}=t;return re(y,S,I,H,G)}function j(t){let{name:y,description:S,chatId:I,...H}=t;return re(y,S,I,void 0,H)}function z(t){let{name:y,description:S,chatId:I,...H}=t;return re(y,S,I,void 0,H)}function U(t){return{[Ne]:!0,config:t}}function L(t){let{name:y,description:S,...I}=t;return ne("agent",y,S,I)}function Z(t){return t}function F(t){let{name:y,description:S,...I}=t;return ne("workspace",y,S,I)}function B(t){let{name:y,description:S,...I}=t;return ne("sandbox",y,S,I)}function W(t){let{name:y,description:S,...I}=t;return ne("skill",y,S,I)}function q(t){let{name:y,...S}=t;return ne("tool",y,void 0,S)}function K(t){let{name:y,description:S,...I}=t;return ne("policy",y,S,I)}function V(t){let{name:y,description:S,...I}=t;return ne("cron",y,S,I)}function r(t){return Boolean(t&&typeof t==="object"&&t[De])}function i(t){return Boolean(t&&typeof t==="object"&&t[Te])}function v(t){return Boolean(t&&typeof t==="object"&&t[Ne])}
5
- export{p,b,o,s,l,w,f,a,n,h,e,u,c,x,d,m,_,g,T,k,E,C,R,A,M,P,O,N,D,j,z,U,L,Z,F,B,W,q,K,V,r,i,v};