busabase-sdk 0.14.1 → 0.16.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.js CHANGED
@@ -29,6 +29,109 @@ var toFilesOnlyGrepResult = (result) => ({
29
29
  truncated: result.truncated
30
30
  });
31
31
  var grepAssets = async (client, input) => toFilesOnlyGrepResult(await client.grep(toUnifiedFilesGrepInput(input)));
32
+ var AgentTransportSchema = z.enum(["local-subprocess", "remote-websocket"]);
33
+ var AgentCatalogEntryVOSchema = z.object({
34
+ /** Stable slug. For registry agents this is the official ACP registry id. */
35
+ slug: z.string(),
36
+ name: z.string(),
37
+ description: z.string().default(""),
38
+ transport: AgentTransportSchema,
39
+ /** Present for registry-sourced entries; absent for built-ins like Buda. */
40
+ version: z.string().nullable().default(null),
41
+ /** Whether this entry can be launched right now (binary present / URL configured). */
42
+ available: z.boolean().default(false),
43
+ /** Human-readable reason when `available` is false — never a bare "failed". */
44
+ unavailableReason: z.string().nullable().default(null)
45
+ });
46
+ var AgentSessionStatusSchema = z.enum([
47
+ "connecting",
48
+ "idle",
49
+ "busy",
50
+ "waiting_permission",
51
+ "ended",
52
+ "failed"
53
+ ]);
54
+ var AgentPermissionOptionVOSchema = z.object({
55
+ optionId: z.string(),
56
+ name: z.string(),
57
+ kind: z.string().optional()
58
+ });
59
+ var AgentPermissionRequestVOSchema = z.object({
60
+ requestId: z.string(),
61
+ title: z.string().optional(),
62
+ description: z.string().optional(),
63
+ options: z.array(AgentPermissionOptionVOSchema)
64
+ });
65
+ var AgentSessionVOSchema = z.object({
66
+ /** Busabase's own id for the session; not the agent's ACP sessionId. */
67
+ id: z.string(),
68
+ slug: z.string(),
69
+ agentName: z.string(),
70
+ transport: AgentTransportSchema,
71
+ status: AgentSessionStatusSchema,
72
+ /** ISO 8601 — never a Date at the transport boundary. */
73
+ createdAt: z.string(),
74
+ lastActivityAt: z.string(),
75
+ /** Set when status is "failed"; surfaced verbatim to the user. */
76
+ error: z.string().nullable().default(null)
77
+ });
78
+ var AgentSessionEventVOSchema = z.object({
79
+ sessionId: z.string(),
80
+ /** Monotonic per session, so a reconnecting client can tell what it missed. */
81
+ seq: z.number().int(),
82
+ kind: z.enum(["acpUpdate", "status", "error", "permissionRequest", "permissionResolved"]),
83
+ acpUpdate: z.unknown().optional(),
84
+ status: AgentSessionStatusSchema.optional(),
85
+ message: z.string().optional(),
86
+ permissionRequest: AgentPermissionRequestVOSchema.optional(),
87
+ /** Present on a `permissionResolved` event — which request, and what the user picked. */
88
+ permissionRequestId: z.string().optional(),
89
+ permissionOptionId: z.string().optional(),
90
+ at: z.string()
91
+ });
92
+ var CreateAgentSessionInputSchema = z.object({
93
+ /** Must name a catalog entry. Deliberately NOT a command line — see below. */
94
+ slug: z.string().min(1)
95
+ });
96
+ var PromptAgentSessionInputSchema = z.object({
97
+ sessionId: z.string().min(1),
98
+ text: z.string().min(1)
99
+ });
100
+ var AgentSessionIdInputSchema = z.object({ sessionId: z.string().min(1) });
101
+ var RespondToAgentPermissionInputSchema = z.object({
102
+ sessionId: z.string().min(1),
103
+ requestId: z.string().min(1),
104
+ optionId: z.string().min(1)
105
+ });
106
+
107
+ // ../../packages/busabase-contract/src/domains/agents/contract.ts
108
+ var agentsContract = {
109
+ /** Connectable backends. Availability is resolved per request, not cached in the client. */
110
+ catalog: oc.output(AgentCatalogEntryVOSchema.array()),
111
+ sessions: {
112
+ list: oc.output(AgentSessionVOSchema.array()),
113
+ create: oc.input(CreateAgentSessionInputSchema).output(AgentSessionVOSchema),
114
+ /**
115
+ * Send a message. Returns as soon as the turn is accepted — the reply arrives
116
+ * on `subscribe`, not here, so a slow agent never blocks the caller.
117
+ */
118
+ prompt: oc.input(PromptAgentSessionInputSchema).output(z.object({ accepted: z.boolean(), sessionId: z.string() })),
119
+ cancel: oc.input(AgentSessionIdInputSchema).output(z.object({ ok: z.boolean() })),
120
+ close: oc.input(AgentSessionIdInputSchema).output(z.object({ ok: z.boolean() })),
121
+ /**
122
+ * Answer a pending `session/request_permission`. There is no auto-approve
123
+ * and no "remember this choice" in this pass (deliberate, see spec) — every
124
+ * request blocks the turn until a human calls this.
125
+ */
126
+ respondToPermission: oc.input(RespondToAgentPermissionInputSchema).output(z.object({ ok: z.boolean() })),
127
+ /**
128
+ * Live event stream for one session. Replays buffered events from `afterSeq`
129
+ * first so a client that reconnects mid-turn does not lose the tokens it
130
+ * missed, then follows live.
131
+ */
132
+ subscribe: oc.input(z.object({ sessionId: z.string().min(1), afterSeq: z.number().int().default(-1) })).output(eventIterator(AgentSessionEventVOSchema))
133
+ }
134
+ };
32
135
  var i18n = {
33
136
  locales: ["en", "zh-CN", "zh-TW", "ja", "ko", "de", "fr", "es", "pt"]};
34
137
  var LocaleSchema = z.enum(i18n.locales);
@@ -3275,6 +3378,7 @@ var busabaseContractRoutes = {
3275
3378
  forms: formContract,
3276
3379
  assets: assetsContract,
3277
3380
  vault: vaultContract,
3381
+ agents: agentsContract,
3278
3382
  webhooks: webhookContract,
3279
3383
  dump: dumpContract,
3280
3384
  install: installContract,
@@ -10,6 +10,11 @@ interface BusabaseAirAppOAuthCredential {
10
10
  expiresAt: string;
11
11
  scope: string[];
12
12
  tokenType: string;
13
+ /** Validated target for this local AirApp. Tokens remain user-scoped. */
14
+ selectedSpace?: {
15
+ id: string;
16
+ name: string;
17
+ };
13
18
  }
14
19
  interface BusabaseAirAppCredentialStoreOptions {
15
20
  /** Override only for tests or an explicitly isolated installation. */
@@ -18,16 +23,42 @@ interface BusabaseAirAppCredentialStoreOptions {
18
23
  /** Directory containing one owner-only OAuth registration per local AirApp. */
19
24
  declare const busabaseAirAppCredentialsDir: (options?: BusabaseAirAppCredentialStoreOptions) => string;
20
25
  declare const busabaseAirAppCredentialPath: (appId: string, options?: BusabaseAirAppCredentialStoreOptions) => string;
26
+ /**
27
+ * Dynamically registered public client ids, keyed by `${baseUrl}|${redirectUri}`.
28
+ *
29
+ * Kept out of the credential file because registration happens before any token exists, and the
30
+ * mapping must outlive both the process and a logout — re-registering on every restart would
31
+ * churn server-side client records and burn the registration rate limit.
32
+ */
33
+ declare const busabaseAirAppDynamicClientsPath: (appId: string, options?: BusabaseAirAppCredentialStoreOptions) => string;
21
34
  declare function loadBusabaseAirAppOAuthCredential(appId: string, options?: BusabaseAirAppCredentialStoreOptions): BusabaseAirAppOAuthCredential | null;
22
35
  declare function storeBusabaseAirAppOAuthCredential(input: {
23
36
  appId: string;
24
37
  baseUrl: string;
25
38
  tokenSet: BusabaseOAuthTokenSet;
26
39
  clientId?: string;
40
+ selectedSpace?: BusabaseAirAppOAuthCredential["selectedSpace"];
27
41
  }, options?: BusabaseAirAppCredentialStoreOptions): BusabaseAirAppOAuthCredential;
42
+ /** Reuse a previously registered public client for this exact origin and callback. */
43
+ declare function loadBusabaseAirAppDynamicClientId(input: {
44
+ appId: string;
45
+ baseUrl: string;
46
+ redirectUri: string;
47
+ }, options?: BusabaseAirAppCredentialStoreOptions): string | null;
48
+ declare function storeBusabaseAirAppDynamicClientId(input: {
49
+ appId: string;
50
+ baseUrl: string;
51
+ redirectUri: string;
52
+ clientId: string;
53
+ }, options?: BusabaseAirAppCredentialStoreOptions): void;
28
54
  /** Load a valid access token, rotating and persisting the token set when needed. */
29
55
  declare function getBusabaseAirAppAccessToken(appId: string, options?: BusabaseAirAppCredentialStoreOptions, fetchImpl?: typeof fetch): Promise<BusabaseAirAppOAuthCredential | null>;
56
+ /** Persist a Space only after the caller has verified membership through `/api/v1/auth`. */
57
+ declare function storeBusabaseAirAppSelectedSpace(appId: string, selectedSpace: {
58
+ id: string;
59
+ name: string;
60
+ } | null, options?: BusabaseAirAppCredentialStoreOptions): BusabaseAirAppOAuthCredential;
30
61
  declare function clearBusabaseAirAppOAuthCredential(appId: string, options?: BusabaseAirAppCredentialStoreOptions): void;
31
62
  declare function revokeBusabaseAirAppOAuthCredential(appId: string, options?: BusabaseAirAppCredentialStoreOptions, fetchImpl?: typeof fetch): Promise<void>;
32
63
 
33
- export { type BusabaseAirAppCredentialStoreOptions, type BusabaseAirAppOAuthCredential, busabaseAirAppCredentialPath, busabaseAirAppCredentialsDir, clearBusabaseAirAppOAuthCredential, getBusabaseAirAppAccessToken, loadBusabaseAirAppOAuthCredential, revokeBusabaseAirAppOAuthCredential, storeBusabaseAirAppOAuthCredential };
64
+ export { type BusabaseAirAppCredentialStoreOptions, type BusabaseAirAppOAuthCredential, busabaseAirAppCredentialPath, busabaseAirAppCredentialsDir, busabaseAirAppDynamicClientsPath, clearBusabaseAirAppOAuthCredential, getBusabaseAirAppAccessToken, loadBusabaseAirAppDynamicClientId, loadBusabaseAirAppOAuthCredential, revokeBusabaseAirAppOAuthCredential, storeBusabaseAirAppDynamicClientId, storeBusabaseAirAppOAuthCredential, storeBusabaseAirAppSelectedSpace };
@@ -1,147 +1,3 @@
1
- import { BusabaseOAuthError, BUSABASE_AIRAPP_CLIENT_ID, refreshBusabaseOAuthToken, revokeBusabaseOAuthToken } from './chunk-J2DZKX7A.js';
2
- import { normalizeBaseUrl } from './chunk-5NYQX65A.js';
3
- import { randomUUID } from 'crypto';
4
- import { readFileSync, mkdirSync, chmodSync, writeFileSync, renameSync, rmSync } from 'fs';
5
- import { homedir } from 'os';
6
- import { join, dirname } from 'path';
7
-
8
- var STORE_VERSION = 1;
9
- var APP_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
10
- var REFRESH_WINDOW_MS = 6e4;
11
- var refreshesByCredentialPath = /* @__PURE__ */ new Map();
12
- var assertAppId = (appId) => {
13
- if (!APP_ID_RE.test(appId)) {
14
- throw new BusabaseOAuthError(
15
- "invalid_airapp_id",
16
- "AirApp id must use letters, digits, dot, dash, or underscore"
17
- );
18
- }
19
- return appId;
20
- };
21
- var storeRoot = (options = {}) => options.rootDir ?? join(homedir(), ".busabase");
22
- var busabaseAirAppCredentialsDir = (options = {}) => join(storeRoot(options), "airapps");
23
- var busabaseAirAppCredentialPath = (appId, options = {}) => join(busabaseAirAppCredentialsDir(options), `${assertAppId(appId)}.json`);
24
- var normalizeOrigin = (raw) => {
25
- const url = new URL(normalizeBaseUrl(raw));
26
- if (url.username || url.password || url.search || url.hash) {
27
- throw new BusabaseOAuthError("invalid_base_url", "Busabase base URL must be an origin");
28
- }
29
- return url.origin;
30
- };
31
- var parseCredential = (raw, expectedAppId) => {
32
- let value;
33
- try {
34
- value = JSON.parse(raw);
35
- } catch {
36
- throw new BusabaseOAuthError("invalid_local_credential", "AirApp OAuth credential is invalid");
37
- }
38
- const item = value;
39
- if (item.version !== STORE_VERSION || item.appId !== expectedAppId || typeof item.baseUrl !== "string" || typeof item.clientId !== "string" || typeof item.accessToken !== "string" || typeof item.refreshToken !== "string" || typeof item.expiresAt !== "string" || !Array.isArray(item.scope) || item.scope.some((scope) => typeof scope !== "string") || typeof item.tokenType !== "string") {
40
- throw new BusabaseOAuthError("invalid_local_credential", "AirApp OAuth credential is invalid");
41
- }
42
- return item;
43
- };
44
- function loadBusabaseAirAppOAuthCredential(appId, options = {}) {
45
- const path = busabaseAirAppCredentialPath(appId, options);
46
- try {
47
- return parseCredential(readFileSync(path, "utf8"), appId);
48
- } catch (error) {
49
- if (error.code === "ENOENT") return null;
50
- throw error;
51
- }
52
- }
53
- function storeBusabaseAirAppOAuthCredential(input, options = {}) {
54
- if (!input.tokenSet.refreshToken) {
55
- throw new BusabaseOAuthError(
56
- "missing_refresh_token",
57
- "A refresh token is required for a persistent local AirApp login"
58
- );
59
- }
60
- const credential = {
61
- version: STORE_VERSION,
62
- appId: assertAppId(input.appId),
63
- baseUrl: normalizeOrigin(input.baseUrl),
64
- clientId: input.clientId ?? BUSABASE_AIRAPP_CLIENT_ID,
65
- accessToken: input.tokenSet.accessToken,
66
- refreshToken: input.tokenSet.refreshToken,
67
- expiresAt: input.tokenSet.expiresAt,
68
- scope: input.tokenSet.scope,
69
- tokenType: input.tokenSet.tokenType
70
- };
71
- const path = busabaseAirAppCredentialPath(input.appId, options);
72
- const directory = dirname(path);
73
- mkdirSync(directory, { recursive: true, mode: 448 });
74
- try {
75
- chmodSync(directory, 448);
76
- } catch {
77
- }
78
- const temporaryPath = `${path}.${randomUUID()}.tmp`;
79
- writeFileSync(temporaryPath, `${JSON.stringify(credential, null, 2)}
80
- `, { mode: 384 });
81
- try {
82
- chmodSync(temporaryPath, 384);
83
- } catch {
84
- }
85
- renameSync(temporaryPath, path);
86
- return credential;
87
- }
88
- async function getBusabaseAirAppAccessToken(appId, options = {}, fetchImpl = fetch) {
89
- const credential = loadBusabaseAirAppOAuthCredential(appId, options);
90
- if (!credential) return null;
91
- const expiresAt = Date.parse(credential.expiresAt);
92
- if (Number.isFinite(expiresAt) && expiresAt > Date.now() + REFRESH_WINDOW_MS) return credential;
93
- const credentialPath = busabaseAirAppCredentialPath(appId, options);
94
- const activeRefresh = refreshesByCredentialPath.get(credentialPath);
95
- if (activeRefresh) return activeRefresh;
96
- const refresh = (async () => {
97
- const tokenSet = await refreshBusabaseOAuthToken(
98
- {
99
- baseUrl: credential.baseUrl,
100
- refreshToken: credential.refreshToken,
101
- clientId: credential.clientId
102
- },
103
- fetchImpl
104
- );
105
- return storeBusabaseAirAppOAuthCredential(
106
- {
107
- appId,
108
- baseUrl: credential.baseUrl,
109
- clientId: credential.clientId,
110
- tokenSet: {
111
- ...tokenSet,
112
- refreshToken: tokenSet.refreshToken ?? credential.refreshToken
113
- }
114
- },
115
- options
116
- );
117
- })();
118
- refreshesByCredentialPath.set(credentialPath, refresh);
119
- try {
120
- return await refresh;
121
- } finally {
122
- if (refreshesByCredentialPath.get(credentialPath) === refresh) {
123
- refreshesByCredentialPath.delete(credentialPath);
124
- }
125
- }
126
- }
127
- function clearBusabaseAirAppOAuthCredential(appId, options = {}) {
128
- rmSync(busabaseAirAppCredentialPath(appId, options), { force: true });
129
- }
130
- async function revokeBusabaseAirAppOAuthCredential(appId, options = {}, fetchImpl = fetch) {
131
- const credential = loadBusabaseAirAppOAuthCredential(appId, options);
132
- if (!credential) return;
133
- try {
134
- await revokeBusabaseOAuthToken(
135
- {
136
- baseUrl: credential.baseUrl,
137
- token: credential.refreshToken,
138
- clientId: credential.clientId
139
- },
140
- fetchImpl
141
- );
142
- } finally {
143
- clearBusabaseAirAppOAuthCredential(appId, options);
144
- }
145
- }
146
-
147
- export { busabaseAirAppCredentialPath, busabaseAirAppCredentialsDir, clearBusabaseAirAppOAuthCredential, getBusabaseAirAppAccessToken, loadBusabaseAirAppOAuthCredential, revokeBusabaseAirAppOAuthCredential, storeBusabaseAirAppOAuthCredential };
1
+ export { busabaseAirAppCredentialPath, busabaseAirAppCredentialsDir, busabaseAirAppDynamicClientsPath, clearBusabaseAirAppOAuthCredential, getBusabaseAirAppAccessToken, loadBusabaseAirAppDynamicClientId, loadBusabaseAirAppOAuthCredential, revokeBusabaseAirAppOAuthCredential, storeBusabaseAirAppDynamicClientId, storeBusabaseAirAppOAuthCredential, storeBusabaseAirAppSelectedSpace } from './chunk-C23JVY2Y.js';
2
+ import './chunk-WSHJMHUS.js';
3
+ import './chunk-5NYQX65A.js';
package/dist/oauth.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  /** Public client identifier used by local Hono/AirApp development servers. */
2
2
  declare const BUSABASE_AIRAPP_CLIENT_ID = "busabase-airapp";
3
3
 
4
+ /** The only scope an AirApp ever requests; bound to the `/api/v1` resource. */
5
+ declare const AIRAPP_OAUTH_SCOPE = "api";
4
6
  interface BusabaseOAuthRequest {
5
7
  authorizeUrl: string;
6
8
  baseUrl: string;
@@ -31,11 +33,21 @@ interface BusabaseOAuthTokenSet {
31
33
  image: string | null;
32
34
  };
33
35
  }
36
+ interface RegisterBusabaseAirAppOAuthClientInput {
37
+ appId: string;
38
+ baseUrl: string;
39
+ redirectUri: string;
40
+ }
34
41
  declare class BusabaseOAuthError extends Error {
35
42
  readonly code: string;
36
43
  readonly status?: number;
37
44
  constructor(code: string, message: string, status?: number);
38
45
  }
46
+ /** Register an exact HTTPS callback for a public AirApp OAuth client. */
47
+ declare function registerBusabaseAirAppOAuthClient(input: RegisterBusabaseAirAppOAuthClientInput, fetchImpl?: typeof fetch): Promise<{
48
+ clientId: string;
49
+ redirectUri: string;
50
+ }>;
39
51
  /** Build a public-client OAuth 2.1 authorization request with PKCE S256. */
40
52
  declare function createBusabaseOAuthRequest(input: CreateBusabaseOAuthRequestInput): Promise<BusabaseOAuthRequest>;
41
53
  /** Validate state and issuer before accepting the authorization code. */
@@ -52,4 +64,4 @@ declare function revokeBusabaseOAuthToken(input: {
52
64
  clientId?: string;
53
65
  }, fetchImpl?: typeof fetch): Promise<void>;
54
66
 
55
- export { BUSABASE_AIRAPP_CLIENT_ID, BusabaseOAuthError, type BusabaseOAuthRequest, type BusabaseOAuthTokenSet, type CreateBusabaseOAuthRequestInput, createBusabaseOAuthRequest, exchangeBusabaseOAuthCode, parseBusabaseOAuthCallback, refreshBusabaseOAuthToken, revokeBusabaseOAuthToken };
67
+ export { AIRAPP_OAUTH_SCOPE, BUSABASE_AIRAPP_CLIENT_ID, BusabaseOAuthError, type BusabaseOAuthRequest, type BusabaseOAuthTokenSet, type CreateBusabaseOAuthRequestInput, type RegisterBusabaseAirAppOAuthClientInput, createBusabaseOAuthRequest, exchangeBusabaseOAuthCode, parseBusabaseOAuthCallback, refreshBusabaseOAuthToken, registerBusabaseAirAppOAuthClient, revokeBusabaseOAuthToken };
package/dist/oauth.js CHANGED
@@ -1,2 +1,2 @@
1
- export { BUSABASE_AIRAPP_CLIENT_ID, BusabaseOAuthError, createBusabaseOAuthRequest, exchangeBusabaseOAuthCode, parseBusabaseOAuthCallback, refreshBusabaseOAuthToken, revokeBusabaseOAuthToken } from './chunk-J2DZKX7A.js';
1
+ export { AIRAPP_OAUTH_SCOPE, BUSABASE_AIRAPP_CLIENT_ID, BusabaseOAuthError, createBusabaseOAuthRequest, exchangeBusabaseOAuthCode, parseBusabaseOAuthCallback, refreshBusabaseOAuthToken, registerBusabaseAirAppOAuthClient, revokeBusabaseOAuthToken } from './chunk-WSHJMHUS.js';
2
2
  import './chunk-5NYQX65A.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "busabase-sdk",
3
- "version": "0.14.1",
3
+ "version": "0.16.0",
4
4
  "description": "Typed TypeScript/JavaScript SDK for the Busabase OpenAPI REST API. Talks to a local or remote `busabase server` (or Busabase Cloud).",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/busabase/busabase/tree/main/apps/busabase-sdk",
@@ -30,7 +30,20 @@
30
30
  "./oauth-node": {
31
31
  "types": "./dist/oauth-node.d.ts",
32
32
  "default": "./dist/oauth-node.js"
33
- }
33
+ },
34
+ "./airapp-node": {
35
+ "types": "./dist/airapp-node.d.ts",
36
+ "default": "./dist/airapp-node.js"
37
+ },
38
+ "./airapp": {
39
+ "types": "./dist/airapp.d.ts",
40
+ "default": "./dist/airapp.js"
41
+ },
42
+ "./airapp-gate": {
43
+ "types": "./dist/airapp-gate.d.ts",
44
+ "default": "./dist/airapp-gate.js"
45
+ },
46
+ "./airapp-gate.css": "./dist/airapp-gate.css"
34
47
  },
35
48
  "files": [
36
49
  "dist",
@@ -45,12 +58,13 @@
45
58
  },
46
59
  "devDependencies": {
47
60
  "@types/node": "^24.13.2",
61
+ "jsdom": "^27.0.0",
48
62
  "tsup": "^8.5.0",
49
63
  "tsx": "^4.20.5",
50
64
  "typescript": "^5.9.3",
51
65
  "vitest": "^2.1.8",
52
- "busabase-contract": "0.14.1",
53
66
  "open-domains": "0.0.2",
67
+ "busabase-contract": "0.16.0",
54
68
  "openlib": "0.1.1"
55
69
  },
56
70
  "engines": {