broods 0.25.0 → 0.26.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
@@ -1,5 +1,12 @@
1
1
  import { SystemModelMessage, LanguageModelCallOptions, RequestOptions, streamText, JSONSchema7, ModelMessage, TextStreamPart, ToolSet } from 'ai';
2
2
 
3
+ /**
4
+ * Account-owned code hook upload validation for Convex config-plane sync.
5
+ * Mirrors core's account hook upload contract without requiring the Node runtime.
6
+ */
7
+ declare const AGENT_HOOK_EVENT_NAMES: readonly ["agent.started", "agent.step.finished", "agent.finished", "agent.failed", "agent.approval.required", "tool.call.started", "tool.call.finished", "tool.result", "subagent.task.started", "subagent.task.finished", "channel.message.received", "channel.message.sending"];
8
+ type AgentHookEventName = (typeof AGENT_HOOK_EVENT_NAMES)[number];
9
+
3
10
  /**
4
11
  * Every Vercel AI SDK provider that ships language models, plus `custom`
5
12
  * (any OpenAI-compatible endpoint) and `minimax`. Image-, speech- and
@@ -269,8 +276,7 @@ interface AgentWebhookHookConfig {
269
276
  [key: string]: unknown;
270
277
  }
271
278
  type AgentLifecycleEventName = "agent.started" | "agent.step.finished" | "agent.finished" | "agent.failed" | "agent.approval.required" | "tool.call.started" | "tool.call.finished" | "tool.result" | "subagent.task.started" | "subagent.task.finished";
272
- type AgentChannelHookEventName = "channel.message.received" | "channel.message.sending";
273
- type AgentHookEventName = AgentLifecycleEventName | AgentChannelHookEventName;
279
+
274
280
  type AgentToolsConfig = Record<string, AgentToolConfig>;
275
281
  interface AgentToolConfig {
276
282
  enabled?: boolean;
@@ -385,7 +391,9 @@ interface AgentZaloChannelConfig {
385
391
  }
386
392
 
387
393
  /**
388
- * Cron-job records, input normalization, and patch-merge helpers.
394
+ * Cron-job records and patch-merge helpers for the runtime. Input
395
+ * normalization lives in the config plane (packages/convex/model/cronRules.ts,
396
+ * the single home of those rules).
389
397
  */
390
398
 
391
399
  type CronStatus = "active" | "paused";
@@ -427,48 +435,45 @@ type UpdateCronInput = {
427
435
  });
428
436
 
429
437
  /**
430
- * Predefined sandbox sizes — the canonical (vcpu, memoryMb, storageGb) catalog
431
- * shared by sandbox config validation, the workdir resource mapping, and the
432
- * Convex `sandboxInstances` mirror. Sizes are the user-facing knob (`config.size`)
433
- * that reconciles issue #78's tiers with each backend's real limits.
434
- *
435
- * The specs are canonical/advisory: workdir applies them as create-time resources
436
- * (clamping vcpu to its allowed set); MicroVM bakes size into the image so the
437
- * specs are display-only there; daytona/e2b/vercel size natively. The control-plane
438
- * mirror type lives here too so the Convex writer and the executors share one shape
439
- * without importing across the _shared/harness boundary.
438
+ * Sandbox-config validation for the Convex config plane. Ports core's former
439
+ * storage/sandbox-config.ts normalizer so the public /v1/sandboxes contract
440
+ * is unchanged. The public projection lives in ./responses.ts.
440
441
  */
441
-
442
- type SandboxSize = "tiny" | "xsmall" | "small" | "medium" | "large";
443
-
442
+ declare const SANDBOX_PROVIDERS: readonly ["sandbox", "lambda", "e2b", "daytona", "vercel"];
443
+ declare const SANDBOX_RUNTIMES: readonly ["bash", "python", "node"];
444
+ declare const SANDBOX_PERMISSION_MODES: readonly ["edit", "ask", "bypass"];
445
+ declare const SANDBOX_NETWORK_MODES: readonly ["allow-all", "deny-all", "restricted"];
446
+ declare const SANDBOX_SIZE_NAMES: readonly ["tiny", "xsmall", "small", "medium", "large"];
447
+ type SandboxProvider = (typeof SANDBOX_PROVIDERS)[number];
448
+ type RuntimeName = (typeof SANDBOX_RUNTIMES)[number];
449
+ type PermissionMode = (typeof SANDBOX_PERMISSION_MODES)[number];
450
+ type NetworkMode = (typeof SANDBOX_NETWORK_MODES)[number];
451
+ type SandboxSize = (typeof SANDBOX_SIZE_NAMES)[number];
444
452
  /**
445
- * Sandbox config: account-scoped, reusable sandbox definitions referenced by
446
- * agents via `config.sandbox`. A sandbox is a collection of Claude-Code-style
447
- * tools (bash/read/write/edit/glob/grep) backed by a provider. Validation and
448
- * the public projection live here.
449
- * Stored encrypted at rest because `envVars`/`options` may hold secrets.
453
+ * Idle and maximum lifetime controls for a persistent sandbox.
450
454
  */
451
-
452
- type SandboxProvider = "sandbox" | "lambda" | "e2b" | "daytona" | "vercel";
453
- type SandboxRuntimeName = "bash" | "python" | "node";
454
- type SandboxPermissionMode = "edit" | "ask" | "bypass";
455
- type SandboxNetworkMode = "allow-all" | "deny-all" | "restricted";
456
455
  interface SandboxLifecycleConfig {
457
456
  idleTimeoutSeconds?: number;
458
457
  maxLifetimeSeconds?: number;
459
458
  }
459
+ /**
460
+ * Provider-normalized sandbox network policy.
461
+ */
460
462
  interface SandboxNetworkConfig {
461
- mode: SandboxNetworkMode;
463
+ mode: NetworkMode;
462
464
  allowDomains?: string[];
463
465
  allowCidrs?: string[];
464
466
  }
467
+ /**
468
+ * Account-scoped reusable sandbox configuration referenced by agents.
469
+ */
465
470
  interface SandboxConfig {
466
471
  provider: SandboxProvider;
467
472
  size?: SandboxSize;
468
473
  snapshot?: string;
469
- runtimes?: SandboxRuntimeName[];
474
+ runtimes?: RuntimeName[];
470
475
  network?: SandboxNetworkConfig;
471
- permissionMode?: SandboxPermissionMode;
476
+ permissionMode?: PermissionMode;
472
477
  persistent?: boolean;
473
478
  lifecycle?: SandboxLifecycleConfig;
474
479
  onCreate?: string[];
@@ -481,12 +486,12 @@ interface SandboxConfig {
481
486
  }
482
487
 
483
488
  /**
484
- * Workspace config: account-scoped, reusable workspace definitions referenced by
485
- * agents via `config.workspaces[].workspaceId`. A workspace is the persistent
486
- * S3-backed filesystem mounted into a sandbox; agents referencing the same
487
- * workspaceId share the same files. Holds no secrets, so it is stored in
488
- * plaintext (unlike sandbox config). Validation and the public projection live
489
- * here.
489
+ * Workspace-config validation for the Convex config plane. Ports core's
490
+ * former storage/workspace-config.ts normalizer so the public /v1/workspaces
491
+ * contract is unchanged. Workspace config holds no secrets (a roleArn is not
492
+ * a secret), so it is stored and returned in plaintext. Pure module — safe
493
+ * for the default Convex runtime. The public projection lives in
494
+ * ./responses.ts.
490
495
  */
491
496
  declare const WORKSPACE_STORAGE_PROVIDERS: readonly ["s3"];
492
497
  type WorkspaceStorageProvider = (typeof WORKSPACE_STORAGE_PROVIDERS)[number];
@@ -505,34 +510,56 @@ interface WorkspaceStorageConfig {
505
510
  prefix?: string;
506
511
  auth?: WorkspaceStorageAuth;
507
512
  }
508
- interface WorkspaceHarnessConfig {
509
- workspace?: {
510
- enabled?: boolean;
511
- };
512
- memory?: {
513
- enabled?: boolean;
514
- };
515
- }
516
513
  interface WorkspaceConfig {
517
514
  storage: WorkspaceStorageConfig;
518
515
  isolation?: boolean;
519
- harness?: WorkspaceHarnessConfig;
516
+ harness?: {
517
+ workspace?: {
518
+ enabled?: boolean;
519
+ };
520
+ memory?: {
521
+ enabled?: boolean;
522
+ };
523
+ };
520
524
  }
521
525
 
522
526
  /**
523
- * Agent policy contracts and validation.
524
- * Runtime decisions are made by OPA using the same document/input shape.
527
+ * Agent-policy validation for the Convex config plane. Ports core's public
528
+ * CRUD normalizer so policy documents keep the account-management API
529
+ * contract. The public projection lives in ./responses.ts.
530
+ */
531
+ declare const AGENT_POLICY_ACTIONS: readonly ["agent.invoke", "tool.call", "workspace.read", "workspace.write", "workspace.exec", "subagent.run", "skill.load"];
532
+ /**
533
+ * API action namespace for account roles: one read/write pair per config-plane
534
+ * resource route. Roles carry the same PolicyDocument shape as agent policies;
535
+ * each caller passes its action set to `normalizePolicyDocument`.
536
+ */
537
+ declare const API_POLICY_ACTIONS: readonly ["account:read", "account:write", "agents:read", "agents:write", "channels:read", "channels:write", "crons:read", "crons:write", "env:read", "env:write", "hooks:read", "hooks:write", "mcp:read", "mcp:write", "policies:read", "policies:write", "sandboxes:read", "sandboxes:write", "skills:read", "skills:write", "tools:read", "tools:write", "workspaces:read", "workspaces:write"];
538
+ type AgentPolicyAction = (typeof AGENT_POLICY_ACTIONS)[number];
539
+ type ApiPolicyAction = (typeof API_POLICY_ACTIONS)[number];
540
+ type PolicyAction = AgentPolicyAction | ApiPolicyAction;
541
+ /**
542
+ * One optional predicate on a policy rule.
525
543
  */
526
- declare const POLICY_ACTIONS: readonly ["agent.invoke", "tool.call", "workspace.read", "workspace.write", "workspace.exec", "subagent.run", "skill.load"];
527
- type PolicyAction = (typeof POLICY_ACTIONS)[number];
528
- type PolicyEffect = "allow" | "deny";
529
- type PolicyMode = "enforce" | "audit";
530
- type PolicyConditionOperator = "equals" | "notEquals" | "in" | "notIn" | "prefix" | "contains";
531
544
  interface PolicyCondition {
532
545
  attribute: string;
533
546
  operator: PolicyConditionOperator;
534
547
  value: string | number | boolean | string[] | number[] | boolean[];
535
548
  }
549
+ type PolicyConditionOperator = "equals" | "notEquals" | "in" | "notIn" | "prefix" | "contains";
550
+ /**
551
+ * Versioned policy document accepted by account-management CRUD.
552
+ */
553
+ interface PolicyDocument {
554
+ version: 1;
555
+ /** How hard this policy bites where it is attached. Omitted reads as `audit`. */
556
+ mode?: "enforce" | "audit";
557
+ rules: PolicyRule[];
558
+ }
559
+ type PolicyEffect = "allow" | "deny";
560
+ /**
561
+ * Resource selector fields supported by policy rules.
562
+ */
536
563
  interface PolicyResourceSelector {
537
564
  toolNames?: string[];
538
565
  toolIds?: string[];
@@ -541,7 +568,12 @@ interface PolicyResourceSelector {
541
568
  filePaths?: string[];
542
569
  subagentIds?: string[];
543
570
  skillPaths?: string[];
571
+ /** Config-plane resource ids for API-action rules; "*" matches every id. */
572
+ resourceIds?: string[];
544
573
  }
574
+ /**
575
+ * One allow/deny rule inside a policy document.
576
+ */
545
577
  interface PolicyRule {
546
578
  id: string;
547
579
  effect: PolicyEffect;
@@ -549,26 +581,13 @@ interface PolicyRule {
549
581
  resources?: PolicyResourceSelector;
550
582
  conditions?: PolicyCondition[];
551
583
  }
552
- interface PolicyDocument {
553
- version: 1;
554
- /**
555
- * How hard this policy bites. `audit` records what it would have done and
556
- * blocks nothing; `enforce` lets its deny rules refuse, and switches the
557
- * places it is attached to over to default-deny. Omitted reads as `audit`,
558
- * so a freshly written policy can never break a running agent.
559
- */
560
- mode?: PolicyMode;
561
- rules: PolicyRule[];
562
- }
563
584
 
564
585
  /**
565
- * Channel records: an account-scoped row per real place a team talks — one Slack
566
- * channel, one Discord channel, one repository. It binds that place to an agent
567
- * and adds instructions, workspaces, policies and roles scoped to it.
568
- * Distinct from `config.channels`, which holds one adapter's credentials.
569
- * A record narrows and adds; it never grants capability the agent lacks.
586
+ * Channel record validation for the config plane — the single home of the
587
+ * normalizers core's `shared/domain/channel-record.ts` re-exports. Kept free
588
+ * of Convex imports so the rules stay unit-testable; the public projection
589
+ * lives in ./responses.ts.
570
590
  */
571
-
572
591
  /** Where a reply lands: its own thread, or wherever the message came from. */
573
592
  declare const CHANNEL_REPLY_TARGETS: readonly ["thread", "source"];
574
593
  type ChannelReplyIn = (typeof CHANNEL_REPLY_TARGETS)[number];
@@ -1205,8 +1224,10 @@ declare function normalizeHttpServiceUrl(value: string): string;
1205
1224
  * server runtimes, as well as Node and Bun.
1206
1225
  *
1207
1226
  * Auth: every call sends `Authorization: Bearer {accountSecret}` to
1208
- * `{baseUrl}/v1/...`. Secrets inside agent configs are encrypted at rest by
1209
- * the platform and come back redacted (`********`) on reads.
1227
+ * `{baseUrl}/v1/...` — or a short-lived `fp_sts_` role session token from
1228
+ * `assumeRole()`, limited to what the role's policy allows. Secrets inside
1229
+ * agent configs are encrypted at rest by the platform and come back redacted
1230
+ * (`********`) on reads.
1210
1231
  */
1211
1232
 
1212
1233
  interface BroodsAccountClientOptions {
@@ -1214,6 +1235,12 @@ interface BroodsAccountClientOptions {
1214
1235
  baseUrl?: string;
1215
1236
  /** Account secret used as the Bearer token. Falls back to `BROODS_ACCOUNT_SECRET`. */
1216
1237
  accountSecret?: string;
1238
+ /**
1239
+ * Short-lived `fp_sts_` role session token (from {@link BroodsAccountClient.assumeRole})
1240
+ * used as the Bearer instead of the account secret. The session can only do
1241
+ * what its role's policy allows. Falls back to `BROODS_SESSION_TOKEN`.
1242
+ */
1243
+ sessionToken?: string;
1217
1244
  fetch?: typeof fetch;
1218
1245
  }
1219
1246
  /** Public account record returned by `GET /v1/account`. */
@@ -1293,6 +1320,29 @@ interface AccountPolicy {
1293
1320
  createdAt: string;
1294
1321
  updatedAt: string;
1295
1322
  }
1323
+ /**
1324
+ * Public account-role record returned by the roles routes. The policy uses the
1325
+ * API action namespace (`"agents:read"`, `"crons:write"`, ...); `projectId` and
1326
+ * `stageId` bound which stage runtime keys may assume the role.
1327
+ */
1328
+ interface AccountRole {
1329
+ accountId: string;
1330
+ roleId: string;
1331
+ name: string;
1332
+ projectId?: string;
1333
+ stageId?: string;
1334
+ status: "active" | "disabled";
1335
+ policy: PolicyDocument;
1336
+ createdAt: string;
1337
+ updatedAt: string;
1338
+ }
1339
+ /** Short-lived role session minted by `POST /v1/account/assume-role`. */
1340
+ interface AssumeRoleResult {
1341
+ /** `fp_sts_` bearer token; pass it as `sessionToken` to a new client. */
1342
+ token: string;
1343
+ /** ISO timestamp when the session stops working. */
1344
+ expiresAt: string;
1345
+ }
1296
1346
  /**
1297
1347
  * One real place a team talks — a Slack channel, a Discord channel, a repo —
1298
1348
  * bound to an agent. The runtime reads it on the inbound webhook to decide who
@@ -1443,7 +1493,7 @@ declare class BroodsAccountApiError extends Error {
1443
1493
  */
1444
1494
  declare class BroodsAccountClient {
1445
1495
  private readonly baseUrl;
1446
- private readonly accountSecret;
1496
+ private readonly bearerToken;
1447
1497
  private readonly fetchImpl;
1448
1498
  constructor(options?: BroodsAccountClientOptions);
1449
1499
  /** The account this secret belongs to. Its `accountId` is the first segment of channel webhook URLs. */
@@ -1453,6 +1503,15 @@ declare class BroodsAccountClient {
1453
1503
  username?: string;
1454
1504
  description?: string | null;
1455
1505
  }): Promise<BroodsAccount | null>;
1506
+ /**
1507
+ * Exchange a role for a short-lived `fp_sts_` session token. Callable with
1508
+ * the account secret, a CLI login token, or a stage runtime key (the latter
1509
+ * only into roles scoped to the key's own project/stage). Construct a new
1510
+ * client with `{ sessionToken: result.token }` to act as the role.
1511
+ */
1512
+ assumeRole(roleId: string, options?: {
1513
+ ttlSeconds?: number;
1514
+ }): Promise<AssumeRoleResult>;
1456
1515
  /** Rotate the account secret. The returned `secret` is shown once and the current secret stops working immediately, so persist it before the process exits. */
1457
1516
  rotateSecret(): Promise<RotateSecretResult>;
1458
1517
  /** Delete this account and cascade-clean every account-scoped resource. `cleanup` reports per-resource deletion counts. */
@@ -1563,6 +1622,23 @@ declare class BroodsAccountClient {
1563
1622
  status?: string;
1564
1623
  }): Promise<AccountPolicy | null>;
1565
1624
  deletePolicy(policyId: string): Promise<boolean>;
1625
+ /** Roles are account-secret only: a session cannot list, mint, or edit roles. */
1626
+ listRoles(): Promise<AccountRole[]>;
1627
+ /** Create a role whose policy uses the API action namespace. `projectId`/`stageId` must be provided together. */
1628
+ createRole(input: {
1629
+ name: string;
1630
+ policy: PolicyDocument;
1631
+ projectId?: string;
1632
+ stageId?: string;
1633
+ }): Promise<AccountRole>;
1634
+ getRole(roleId: string): Promise<AccountRole | null>;
1635
+ /** PATCH a role. `status: "disabled"` kills every live session of the role. Returns null when the role is gone. */
1636
+ updateRole(roleId: string, patch: {
1637
+ name?: string;
1638
+ policy?: PolicyDocument;
1639
+ status?: "active" | "disabled";
1640
+ }): Promise<AccountRole | null>;
1641
+ deleteRole(roleId: string): Promise<boolean>;
1566
1642
  listChannels(): Promise<AccountChannel[]>;
1567
1643
  /** Bind one real chat channel to an agent. One active record per place. */
1568
1644
  createChannel(input: {
@@ -1738,14 +1814,24 @@ type ResourceInput<Name extends string, Config> = {
1738
1814
  name: Name;
1739
1815
  description?: string;
1740
1816
  } & Config;
1817
+ /**
1818
+ * Provider-specific sandbox knobs. `reservationKey` names the reserved machine a
1819
+ * `persistent` sandbox reconnects to when no workspace is mounted; unset, each
1820
+ * agent gets its own, and pinning one string on two sandboxes shares a machine.
1821
+ * Keys are scoped to the account, so they cannot reach another account's machine.
1822
+ */
1823
+ type SandboxDefinitionOptions = Record<string, unknown> & {
1824
+ reservationKey?: string;
1825
+ };
1741
1826
  /**
1742
1827
  * Code-first sandbox config surface. Mirrors core's `SandboxConfig` but lets
1743
1828
  * `envVars` values be `env("NAME")` references (compiled to `${NAME}` placeholders
1744
1829
  * at sync time, exactly like provider `apiKey`). Add overrides here if more
1745
1830
  * sandbox fields should accept env refs.
1746
1831
  */
1747
- type SandboxDefinitionConfig = Omit<SandboxConfig, "envVars"> & {
1832
+ type SandboxDefinitionConfig = Omit<SandboxConfig, "envVars" | "options"> & {
1748
1833
  envVars?: Record<string, string | EnvRef | undefined>;
1834
+ options?: SandboxDefinitionOptions;
1749
1835
  };
1750
1836
  type HarnessType = NonNullable<AgentConfig["harness"]>["type"];
1751
1837
  type HarnessDefinition = Omit<NonNullable<AgentConfig["harness"]>, "type"> & {
@@ -2195,4 +2281,4 @@ declare function isConnectionDefinition(value: unknown): value is AnyConnectionD
2195
2281
  declare function isBroodsConfig(value: unknown): value is BroodsConfigDefinition;
2196
2282
 
2197
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 };
2198
- export type { Account, AccountAgent, AccountChannel, AccountEnvVar, AccountPolicy, 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, 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, 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 };
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 };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{S,r,y}from"./chunk-vyvy543y.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,t,i,v}from"./chunk-t6xktzr3.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{r 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,t as isResource,I as isRootSpanKind,m as normalizeHttpServiceUrl,b as readSseStream,p as resolveRunEvents,g as toWebSocketBaseUrl};
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};
package/package.json CHANGED
@@ -1,28 +1,42 @@
1
1
  {
2
2
  "name": "broods",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "CLI + TypeScript SDK for the broods agent platform.",
5
- "type": "module",
6
- "license": "MIT",
5
+ "keywords": [
6
+ "agents",
7
+ "ai",
8
+ "broods",
9
+ "cli",
10
+ "sdk"
11
+ ],
7
12
  "homepage": "https://github.com/beeblastco/broods#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/beeblastco/broods/issues"
15
+ },
16
+ "license": "MIT",
8
17
  "repository": {
9
18
  "type": "git",
10
19
  "url": "git+https://github.com/beeblastco/broods.git",
11
20
  "directory": "packages/broods"
12
21
  },
13
- "bugs": {
14
- "url": "https://github.com/beeblastco/broods/issues"
15
- },
16
- "keywords": [
17
- "agents",
18
- "ai",
19
- "sdk",
20
- "cli",
21
- "broods"
22
- ],
23
22
  "bin": {
24
23
  "broods": "dist/cli/index.js"
25
24
  },
25
+ "files": [
26
+ "dist",
27
+ "README.md",
28
+ "LICENSE.md"
29
+ ],
30
+ "type": "module",
31
+ "main": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "typesVersions": {
34
+ "*": {
35
+ "account": [
36
+ "./dist/account.d.ts"
37
+ ]
38
+ }
39
+ },
26
40
  "exports": {
27
41
  ".": {
28
42
  "types": "./dist/index.d.ts",
@@ -33,35 +47,15 @@
33
47
  "import": "./dist/account.js"
34
48
  }
35
49
  },
36
- "typesVersions": {
37
- "*": {
38
- "account": [
39
- "./dist/account.d.ts"
40
- ]
41
- }
42
- },
43
- "main": "./dist/index.js",
44
- "types": "./dist/index.d.ts",
45
- "files": [
46
- "dist",
47
- "README.md",
48
- "LICENSE.md"
49
- ],
50
50
  "scripts": {
51
51
  "build": "bun scripts/build.ts && rollup -c rollup.dts.config.mjs && rm -rf .dts-temp",
52
52
  "check": "tsc --noEmit",
53
53
  "pack:check": "bun run build && publint && attw --pack .",
54
54
  "test": "bun test"
55
55
  },
56
- "engines": {
57
- "node": ">=22.15"
58
- },
59
56
  "dependencies": {
60
57
  "esbuild": "^0.28.2"
61
58
  },
62
- "peerDependencies": {
63
- "ai": "^7.0.77"
64
- },
65
59
  "devDependencies": {
66
60
  "@ai-sdk/tui": "1.0.78",
67
61
  "@arethetypeswrong/cli": "^0.18.5",
@@ -76,5 +70,11 @@
76
70
  "rollup": "^4.62.4",
77
71
  "rollup-plugin-dts": "^6.5.1",
78
72
  "typescript": "^7.0.2"
73
+ },
74
+ "peerDependencies": {
75
+ "ai": "^7.0.77"
76
+ },
77
+ "engines": {
78
+ "node": ">=22.15"
79
79
  }
80
80
  }
@@ -1,5 +0,0 @@
1
- function p(r){if(r.events&&r.input!==void 0)throw Error("Run input accepts either `input` or `events`, not both");if(r.events&&r.events.length>0)return r.events;if(typeof r.input==="string")return[{role:"user",content:[{type:"text",text:r.input}]}];throw Error("Run input requires `input` (string) or a non-empty `events` array")}async function*b(r){let y=r.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 r=process.env.BROODS_TOKEN,y=process.env.BROODS_BASE_URL;if(r&&y)return{baseUrl:n(y),token:r,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(r){await Pe($e(l),{recursive:!0}),await We(l,`${JSON.stringify(r,null,2)}
3
- `,"utf8")}function a(){return process.env.BROODS_STAGE}function n(r){let y=r.length;while(y>0&&r.charCodeAt(y-1)===47)y-=1;return r.slice(0,y)}function h(r){try{let y=new URL(r);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(r=process.cwd()){je(r);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(r,y=process.cwd()){let S=process.env[r];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[r]!==void 0)return J[r]!==S}return!0}function je(r){let y=Ce(r),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(r){let y={};for(let S of r.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(r){if(r.startsWith('"')&&r.endsWith('"'))return r.slice(1,-1).replace(/\\n/g,`
4
- `).replace(/\\"/g,'"');if(r.startsWith("'")&&r.endsWith("'"))return r.slice(1,-1);let y=r.indexOf(" #");return y>=0?r.slice(0,y).trimEnd():r}function Ge(){try{let r=JSON.parse(ge(l,"utf8"));if(typeof r.baseUrl!=="string"||typeof r.token!=="string")return null;return{baseUrl:n(r.baseUrl),...typeof r.dashboardUrl==="string"?{dashboardUrl:n(r.dashboardUrl)}:{},token:r.token}}catch{return null}}var c="https://gateway.broods.app";class x extends Error{accepted;constructor(r){super(`Ingress ${r.eventId} was accepted asynchronously with status ${r.status??"queued"}`);this.accepted=r;this.name="IngressAcceptedError"}}class d{baseUrl;apiKey;fetchImpl;constructor(r={}){e(),this.baseUrl=m(r.baseUrl||r.host||process.env.BROODS_BASE_URL||process.env.BROODS_HOST||c),this.apiKey=r.apiKey||process.env.BROODS_API_KEY||void 0,this.fetchImpl=r.fetch??fetch}channelWebhookUrl(r){return`${this.baseUrl}${r.webhookPath.startsWith("/")?"":"/"}${r.webhookPath}`}accountWebhookUrl(r,y){let S=[r,y].map(encodeURIComponent);return`${this.baseUrl}/webhooks/${S.join("/")}`}stageWebhookUrl(r,y,S){let I=[r,"dev",y,S].map(encodeURIComponent);return`${this.baseUrl}/webhooks/${I.join("/")}`}agent(r,y){if(typeof r==="string"){let I=r,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=r;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(r,y){let S=[],I="",H=y?this.stream(r,y):this.stream(r);for await(let G of H)if(S.push(G),G.type==="text-delta")I+=G.text;return{text:I,events:S}}async*stream(r,y){let S=y?{...y,agentId:r.id,agentName:r.name}:r,I=Re(S,"cli"),H=y?this.scopedUrl(r):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(r,y){let S=y?{...y,agentId:r.id,agentName:r.name}:r,I=Re(S,"async"),H=y?this.scopedUrl(r,"/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(r,y={}){let S=this.resolveStatusUrl(r,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(r,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(r,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(r){let y=await this.fetchJson(`${this.baseUrl}/v1/crons`,{method:"POST",headers:this.apiKeyHeaders(),body:JSON.stringify(Ze(r))});if(y.status!==201)throw Error(`Create cron job failed: ${y.status} ${await te(y)}`);return await y.json()}async listCrons(){let r=await this.fetchJson(`${this.baseUrl}/v1/crons`,{method:"GET",headers:this.apiKeyHeaders()});if(!r.ok)throw Error(`List cron jobs failed: ${r.status} ${await te(r)}`);return(await r.json()).crons}async getCron(r){let y=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(r)}`,{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(r,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(r)}/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(r,y){let S=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(r)}`,{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(r){let y=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(r)}`,{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(r,y=""){if(r.projectSlug&&r.stageSlug&&r.endpointId)return`${this.baseUrl}/v1/${encodeURIComponent(r.projectSlug)}/agents/${encodeURIComponent(r.stageSlug)}/${encodeURIComponent(r.endpointId)}${y}`;return`${this.baseUrl}${y}`}async openStream(r,y){if(this.apiKey)return await this.fetchCore(r,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(r,y,S){try{return await this.fetchImpl(y,{method:"POST",headers:{"Content-Type":"application/json",Accept:"text/event-stream",...S},body:JSON.stringify(r)})}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(r,y){try{return await this.fetchImpl(r,{...y,headers:{"Content-Type":"application/json",...y.headers}})}catch(S){throw Error(`Cannot access the broods core service at ${r}. Cause: ${S instanceof Error?S.message:String(S)}`)}}resolveStatusUrl(r,y){if(typeof r!=="string")return r.statusUrl;if(/^https?:\/\//.test(r))return r;if(!y.agentId)throw Error("Polling by status id requires agentId.");return Je(this.baseUrl,r,y.agentId)}}function m(r){let y=r.trim(),S=/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(y)?y:`https://${y}`;return n(S)}function Re(r,y){let S=r.eventId??`${y}-${Date.now()}`;return{agentId:r.agentId,eventId:S,conversationKey:r.conversationKey??S,events:p(r),...r.mode!==void 0?{mode:r.mode}:{},...r.idempotencyKey!==void 0?{idempotencyKey:r.idempotencyKey}:{},...r.system!==void 0?{system:r.system}:{},...r.model!==void 0?{model:r.model}:{}}}function be(r,y){if(!r||typeof r!=="object")throw Error("Async response must be an object");let S=r.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 r.eventId==="string"?r.eventId:I.statusId,G=r.status,J=r.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(r){let y=new URL(r),S=y.pathname.match(/\/status\/([^/]+)$/);return{statusId:S?.[1]?decodeURIComponent(S[1]):void 0,agentId:y.searchParams.get("agentId")??void 0}}function Je(r,y,S){return`${m(r)}/status/${encodeURIComponent(y)}?agentId=${encodeURIComponent(S)}`}async function Fe(r,y){if((r.headers.get("content-type")??"").toLowerCase().includes("text/event-stream"))return await r.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 r.text();if(I.length<=2000)return I;return`${I.slice(0,2000)}... [truncated ${I.length-2000} chars]`}async function te(r){let y=await r.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(r){if("agentId"in r)return r;let y=r.agent,S=typeof y==="string"?y:y.id,{agent:I,...H}=r;return{...H,agentId:S}}function ze(r,y){return new Promise((S,I)=>{if(y?.aborted){I(Error("Async status polling aborted."));return}let H=setTimeout(S,r);y?.addEventListener("abort",()=>{clearTimeout(H),I(Error("Async status polling aborted."))},{once:!0})})}function Ve(r){if(typeof r==="string")return r;if(!r||typeof r!=="object")return String(r);let y=r,S=y.data?.error?.message??y.message??y.responseBody??JSON.stringify(r),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(r={}){if(e(),this.baseUrl=ve(r.baseUrl||r.host||process.env.BROODS_BASE_URL||process.env.BROODS_HOST||c),this.apiKey=r.apiKey||process.env.BROODS_API_KEY||"",!this.apiKey)throw Error("BroodsWebSocketClient requires apiKey or BROODS_API_KEY.");this.WebSocketImpl=r.WebSocket,this.connectTimeoutMs=r.connectTimeoutMs??Ye}subscribe(r,y={}){let S=this.resolveWebSocket(),I=this.buildUrl(r),H=Xe(r),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),r.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 r.signal?.addEventListener("abort",ae,{once:!0}),J.onopen=()=>{if(Y=!0,clearTimeout(ee),r.signal?.aborted){ae();return}J.send(JSON.stringify({type:"execute",agentId:H,events:p(r),sessionId:r.sessionId,...r.eventId!==void 0?{eventId:r.eventId}:{},...r.mode!==void 0?{mode:r.mode}:{},...r.idempotencyKey!==void 0?{idempotencyKey:r.idempotencyKey}:{},...r.system!==void 0?{system:r.system}:{},...r.model!==void 0?{model:r.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),r.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(r,y={}){let S=this.resolveWebSocket(),I=this.buildUrl(r),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",...r,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(r){let y=[],S=!1,I=null,H=null,G=()=>{H?.(),H=null},J=this.subscribe(r,{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(r){let y=Qe(r),S=r.projectSlug??r.agent?.projectSlug,I=r.stageSlug??r.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 r=this.WebSocketImpl??globalThis.WebSocket;if(!r)throw Error("WebSocket is not available. Pass a WebSocket implementation in BroodsWebSocketClient options.");return r}}function Xe(r){let y=r.agentId??r.agent?.id??r.endpointId;if(!y)throw Error("WebSocket run requires agent, agentId, or endpointId.");return y}function Qe(r){let y=r.endpointId??r.agent?.endpointId??r.agent?.id;if(!y)throw Error("WebSocket run requires agent or endpointId.");return y}function g(r){let y=new URL(ve(r));if(y.protocol==="https:")y.protocol="wss:";if(y.protocol==="http:")y.protocol="ws:";return n(y.toString())}function ve(r){let y=r.trim();if(/^wss?:\/\//.test(y))return n(y);return m(y)}function Ie(r){return Error(`Cannot access the WebSocket service at ${r}.`)}function xe(r,y){if(r.type!=="output")return r;let S=r;return y.onOutput?.(S),S.data}function Ae(r){try{let y=JSON.parse(r);return typeof y==="object"&&y!==null&&typeof y.type==="string"?y:null}catch{return null}}function we(r){if(typeof r==="string")return r;if(r&&typeof r==="object"&&typeof r.message==="string")return r.message;return r===void 0?"WebSocket stream error.":JSON.stringify(r)}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((r)=>{if(!Ee.test(r))throw Error("env name must match /^[A-Z][A-Z0-9_]*$/ and be at most 64 characters.");return{__beeblastEnv:!0,name:r}},{get:function(r,y,S){if(typeof y==="string"&&Ee.test(y))throw Error(`env.${y} is not supported; use env("${y}")`);return Reflect.get(r,y,S)}});function ne(r,y,S,I){return{[De]:!0,kind:r,name:y,...S?{description:S}:{},config:I}}function oe(r,y){let{partition:S,...I}=y;return{[Te]:!0,kind:"connection",type:r,...S?{partition:S}:{},config:I}}function re(r,y,S,I,H){return ne("channelRecord",r,y,{...H,externalId:S,...I?{workspaceRef:I}:{}})}function k(r){return oe("discord",r)}function E(r){return oe("github",r)}function C(r){return oe("pancake",r)}function R(r){return oe("slack",r)}function A(r){return oe("telegram",r)}function M(r){return oe("zalo",r)}function P(r){let{name:y,description:S,channelId:I,guildId:H,...G}=r;return re(y,S,I,H,G)}function O(r){let{name:y,description:S,repo:I,...H}=r,[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(r){let{name:y,description:S,conversationId:I,...H}=r;return re(y,S,I,void 0,H)}function D(r){let{name:y,description:S,channelId:I,teamId:H,...G}=r;return re(y,S,I,H,G)}function j(r){let{name:y,description:S,chatId:I,...H}=r;return re(y,S,I,void 0,H)}function z(r){let{name:y,description:S,chatId:I,...H}=r;return re(y,S,I,void 0,H)}function U(r){return{[Ne]:!0,config:r}}function L(r){let{name:y,description:S,...I}=r;return ne("agent",y,S,I)}function Z(r){return r}function F(r){let{name:y,description:S,...I}=r;return ne("workspace",y,S,I)}function B(r){let{name:y,description:S,...I}=r;return ne("sandbox",y,S,I)}function W(r){let{name:y,description:S,...I}=r;return ne("skill",y,S,I)}function q(r){let{name:y,...S}=r;return ne("tool",y,void 0,S)}function K(r){let{name:y,description:S,...I}=r;return ne("policy",y,S,I)}function V(r){let{name:y,description:S,...I}=r;return ne("cron",y,S,I)}function t(r){return Boolean(r&&typeof r==="object"&&r[De])}function i(r){return Boolean(r&&typeof r==="object"&&r[Te])}function v(r){return Boolean(r&&typeof r==="object"&&r[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,t,i,v};
@@ -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 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 a(e){return`?${new URLSearchParams({project:e.project,stage:e.stage}).toString()}`}class y{baseUrl;accountSecret;fetchImpl;constructor(e={}){let t=e.baseUrl??i("BROODS_BASE_URL")??"https://gateway.broods.app",n=e.accountSecret??i("BROODS_ACCOUNT_SECRET");if(!n)throw Error("BroodsAccountClient requires an accountSecret (or BROODS_ACCOUNT_SECRET).");let s=t.length;while(s>0&&t.charCodeAt(s-1)===47)s-=1;this.baseUrl=t.slice(0,s),this.accountSecret=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 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${a(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${a(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 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 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}`,o=await this.request("POST",s,n);if(!o)throw new r("POST",s,404,"Sandbox not found");return o}async request(e,t,n){let s=await this.fetchImpl(`${this.baseUrl}${t}`,{method:e,headers:{Authorization:`Bearer ${this.accountSecret}`,...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{S,r,y};