From 5d154399bd600fa1c8bd05f933977315a1a8d07a Mon Sep 17 00:00:00 2001 From: amitbardos Date: Sun, 18 Oct 2026 16:34:45 +0300 Subject: [PATCH 1/4] feat(platform)!: rename the builder module to chat client.builder follows the AI chat, so it is now client.chat, with ChatModule, ChatSession and ChatInitOptions. Base44's terminology keeps "builder" for the Builder plan, the Builder GitHub App and people who build apps; the AI is "the AI" and the chat is "the AI chat". The new names also match useBase44Chat and the Apps API's chat/* endpoints. client.builder and the Builder* types stay as deprecated, hidden aliases of the same module until 1.0, so existing code keeps working. Module files move from builder*.ts to chat*.ts and the reference page from interfaces/builder to interfaces/chat. Webhook event types (builder.turn_started.v1, ...) are unchanged. Co-Authored-By: Claude Opus 5.5 --- packages/platform/CHANGELOG.md | 18 ++++++ packages/platform/README.md | 56 +++++++++---------- packages/platform/package.json | 2 +- packages/platform/scripts/gen-events.mjs | 8 +-- .../appended-articles.json | 4 +- .../page-sections.json | 2 +- .../types-to-expose.json | 4 +- packages/platform/src/client/client.ts | 15 ++--- packages/platform/src/client/client.types.ts | 11 +++- packages/platform/src/client/errors.ts | 6 +- packages/platform/src/client/index.ts | 7 ++- .../platform/src/client/modules/builder.ts | 9 --- .../{builder-protocol.ts => chat-protocol.ts} | 6 +- .../{builder-socket.ts => chat-socket.ts} | 14 ++--- ...r-subscription.ts => chat-subscription.ts} | 4 +- ....generated.ts => chat.events.generated.ts} | 0 ...r.events.types.ts => chat.events.types.ts} | 2 +- packages/platform/src/client/modules/chat.ts | 9 +++ .../{builder.types.ts => chat.types.ts} | 54 ++++++++++++------ packages/platform/src/react/chat-items.ts | 10 ++-- packages/platform/src/react/chat.types.ts | 2 +- packages/platform/src/react/index.ts | 2 +- packages/platform/src/react/useBase44Chat.ts | 10 ++-- packages/platform/tests/package.test.mjs | 6 +- packages/platform/tests/types/client.types.ts | 20 ++++--- packages/platform/tests/unit/client.test.ts | 21 ++++--- .../tests/unit/react/chat-items.test.ts | 8 +-- .../tests/unit/react/useBase44Chat.test.ts | 12 ++-- packages/platform/tests/unit/socket.test.js | 8 +-- 29 files changed, 191 insertions(+), 139 deletions(-) delete mode 100644 packages/platform/src/client/modules/builder.ts rename packages/platform/src/client/modules/{builder-protocol.ts => chat-protocol.ts} (96%) rename packages/platform/src/client/modules/{builder-socket.ts => chat-socket.ts} (95%) rename packages/platform/src/client/modules/{builder-subscription.ts => chat-subscription.ts} (94%) rename packages/platform/src/client/modules/{builder.events.generated.ts => chat.events.generated.ts} (100%) rename packages/platform/src/client/modules/{builder.events.types.ts => chat.events.types.ts} (98%) create mode 100644 packages/platform/src/client/modules/chat.ts rename packages/platform/src/client/modules/{builder.types.ts => chat.types.ts} (84%) diff --git a/packages/platform/CHANGELOG.md b/packages/platform/CHANGELOG.md index ae1f67d1..7db789e0 100644 --- a/packages/platform/CHANGELOG.md +++ b/packages/platform/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## Unreleased + +### Renamed: `builder` is now `chat` + +The client follows the AI chat, so the module and its types say so. The old names still work and +are deprecated; they will be removed in 1.0. + +| Before | After | +| --- | --- | +| `client.builder` | `client.chat` | +| `BuilderModule` | `ChatModule` | +| `BuilderSession` | `ChatSession` | +| `BuilderInitOptions` | `ChatInitOptions` | + +```ts +const chat = client.chat.init({ getSession, onError }); +``` + ## 0.2.0 **Breaking.** The client, its options and the event type names change, and events the SDK doesn't diff --git a/packages/platform/README.md b/packages/platform/README.md index addad586..2da33029 100644 --- a/packages/platform/README.md +++ b/packages/platform/README.md @@ -7,8 +7,8 @@ npm install @base44/platform The Platform SDK for white-label platforms built on Base44. It is independent of the app SDK (`@base44/sdk`) and has two entrypoints: -- `@base44/platform`, for the browser: implements the read-only white-label builder socket - contract. A partner's browser watches the builder chat of up to eight apps over the platform's +- `@base44/platform`, for the browser: implements the read-only white-label live AI chat + contract. A partner's browser watches the AI chat of up to eight apps over the platform's shared socket. The workspace must have white-label sockets enabled; this SDK does not open sessions. - `@base44/platform/server`, for your backend: verify the webhooks Base44 sends when apps are created, published, unpublished, trashed or restored. See @@ -18,24 +18,24 @@ The Platform SDK for white-label platforms built on Base44. It is independent of import { createPlatformClient } from "@base44/platform"; const client = createPlatformClient(); -const builder = client.builder.init({ +const chat = client.chat.init({ // Returns the body of POST /api/service/socket-sessions, unchanged. async getSession() { - const response = await fetch("/api/builder-socket-session", { method: "POST" }); + const response = await fetch("/api/chat-socket-session", { method: "POST" }); if (!response.ok) throw new Error("Session request failed"); return response.json(); }, onError(error) { showConnectionError(error.code); }, }); -const subscription = builder.subscribe(appId, { +const subscription = chat.subscribe(appId, { onSnapshot(snapshot) { replaceAppState(snapshot); }, async onEvent(event) { await applyEvent(event); }, // awaited before the next event onError(error) { showSubscriptionError(error.code); }, }); -await builder.connect(); +await chat.connect(); // On view teardown: subscription.unsubscribe(); -builder.close(); +chat.close(); ``` ## Receiving webhooks (server) @@ -97,7 +97,7 @@ Delivery is at least once with no ordering guarantee: deduplicate on `event.id`, ## Sessions -`/api/builder-socket-session` is your backend's route, not an SDK endpoint. It opens a +`/api/chat-socket-session` is your backend's route, not an SDK endpoint. It opens a socket session with its workspace key (`apps:watch` scope) through `POST /api/service/socket-sessions` with `{ app_ids }`, and returns the response body to the browser. `getSession` returns that body unchanged: the SDK reads `session_token`, @@ -107,7 +107,7 @@ backend adds or removes apps with `PUT`/`DELETE /api/service/socket-sessions/{se A session lasts one hour and is never extended. The token goes exclusively in CONNECT `auth.session_token`, never a URL, and the SDK keeps it in memory only. It -is reused across automatic reconnects. `getSession` is called when a builder +is reused across automatic reconnects. `getSession` is called when a chat session first connects, and again only when the server rejects the cached token or expires the session; the SDK then reconnects once with the new session. If the new session has a different `socket_url` or `socket_path`, the SDK closes the old socket, @@ -123,26 +123,26 @@ older one, which stops with `session_replaced`. Open one session per page. `createPlatformClient()` creates a lightweight module container: no Socket.IO instance, timers, session request or network activity. It takes no options. Its -`builder` module follows the server SDK's module-factory pattern. +`chat` module follows the server SDK's module-factory pattern. -`client.builder.init({ getSession, onError })` synchronously creates an independent -`BuilderSession` without connecting or creating a socket. Repeated calls create separate sessions, each owning its own +`client.chat.init({ getSession, onError })` synchronously creates an independent +`ChatSession` without connecting or creating a socket. Repeated calls create separate sessions, each owning its own socket, listeners, subscriptions and cleanup. Close the returned session when its view is disposed; the root client and other sessions remain usable. -`builder.connect()` fetches the first session, creates the socket at its address and +`chat.connect()` fetches the first session, creates the socket at its address and resolves on CONNECT, before any snapshot. The socket uses the default namespace on the session's `socket_path`, WebSocket only, with a dedicated manager. Transport reconnection uses five attempts, starting at one second and capped at ten seconds with jitter; each attempt has a twenty-second timeout. A temporary server refusal is retried the same way. After exhaustion, a refusal or `session_replaced`, -fix the cause and call `builder.connect()`. +fix the cause and call `chat.connect()`. -`builder.subscribe(appId, options)` returns a handle with `appId`, `active` and +`chat.subscribe(appId, options)` returns a handle with `appId`, `active` and idempotent `unsubscribe()`. App IDs are 24 lowercase hexadecimal characters. One -subscription per app and at most eight subscriptions are allowed per builder session. -Subscriptions can be created before connecting. `builder.close()` is terminal for that -session; call `client.builder.init(...)` again to start another. +subscription per app and at most eight subscriptions are allowed per chat session. +Subscriptions can be created before connecting. `chat.close()` is terminal for that +session; call `client.chat.init(...)` again to start another. ## Snapshots and events @@ -161,15 +161,15 @@ their exceptions are isolated so they cannot interrupt another app's delivery. ## Public shapes -- `PlatformClient` (`client.types.ts`): the `builder` module. -- `BuilderModule` (`modules/builder.types.ts`): lazy `init(options)` factory. -- `BuilderInitOptions`: `getSession` and the connection-level `onError` observer. +- `PlatformClient` (`client.types.ts`): the `chat` module (`builder` is a deprecated alias). +- `ChatModule` (`modules/chat.types.ts`): lazy `init(options)` factory. +- `ChatInitOptions`: `getSession` and the connection-level `onError` observer. - `SocketSession`: the `session_token`, `socket_url` and `socket_path` that `getSession` returns. -- `BuilderSession`: `connect()`, `subscribe(appId, options)`, `close()`. +- `ChatSession`: `connect()`, `subscribe(appId, options)`, `close()`. - `SubscriptionOptions`: required `onSnapshot`, `onEvent` and `onError`. - `PlatformSubscription`: read-only `appId`, `active`, and `unsubscribe()`. - `PlatformEvent`, `PlatformEventMap`, `PlatformSnapshot`, `ToolCall`, `GuardApproval` - (`modules/builder.events.types.ts`), derived from the generated event types. + (`modules/chat.events.types.ts`), derived from the generated event types. - Error codes in `errors.types.ts`. `PlatformEvent` is a **discriminated union**: switch on `event.type` to narrow @@ -186,7 +186,7 @@ redaction of generated prose or user content. ## Event types The socket protocol is defined by the service's AsyncAPI document, committed as -`asyncapi.json`. `src/client/modules/builder.events.generated.ts` is generated from it with +`asyncapi.json`. `src/client/modules/chat.events.generated.ts` is generated from it with `json-schema-to-typescript`: one type per schema, named after the backend model (`Message`, `StatusObject`, `QueueState`, one `*Call` per tool, one `*Approval` per guard), plus `ServerEventMap` and `ClientMessageMap`. Never edit it by hand. @@ -229,16 +229,16 @@ exceptions, payloads and credentials are never attached. | `delivery_overflow` | 1,000 deliveries queued for the app; subscribe again for a fresh snapshot. | | `protocol_error` | Invalid routing or envelope. Stop and investigate. | | `handler_failed` | An application callback failed; fix it and subscribe again. | -| `client_closed` | The builder session was explicitly closed; initialize another session. | +| `client_closed` | The chat session was explicitly closed; initialize another session. | No SDK dependency, package version or lockfile changes are required. Socket.IO remains the existing SDK dependency. Chat commands and HTTP APIs are outside this entry point. ## React -`@base44/platform/react` adds one hook, `useBase44Chat`, that runs a builder chat for one +`@base44/platform/react` adds one hook, `useBase44Chat`, that runs the AI chat for one app on top of this client: it opens the live session and renews it, turns messages into items -made for rendering, binds every question the builder asks to the step it belongs to. It renders nothing. React is an optional peer dependency, loaded only by this +made for rendering, binds every question the AI asks to the step it belongs to. It renders nothing. React is an optional peer dependency, loaded only by this entry; the root entry stays framework-free. ```tsx @@ -258,5 +258,5 @@ call with your credentials, so the browser never holds a key. The hook returns ` the first, and the chat watches it while `appId` is `null`. A sent prompt shows at once as a `pending` item until Base44's own message arrives; a prompt sent mid-build waits in Base44's queue meanwhile. A question is a union on `kind` (`choice`, `secrets`, `approval`) and carries only the -actions its kind allows, and declining is always one of them. As in Base44's builder, only the +actions its kind allows, and declining is always one of them. As in Base44's app editor, only the last assistant message makes the chat wait; a kind this release doesn't know is declined for you. Every shape has JSDoc in `src/react/chat.types.ts`. diff --git a/packages/platform/package.json b/packages/platform/package.json index 33c7d1db..d009f2fe 100644 --- a/packages/platform/package.json +++ b/packages/platform/package.json @@ -1,7 +1,7 @@ { "name": "@base44/platform", "version": "0.3.0", - "description": "Base44 Platform SDK for white-label platforms: a browser client for watching the builder and a server entrypoint for webhooks", + "description": "Base44 Platform SDK for white-label platforms: a browser client for following the AI chat and a server entrypoint for webhooks", "main": "dist/client/index.js", "types": "dist/client/index.d.ts", "type": "module", diff --git a/packages/platform/scripts/gen-events.mjs b/packages/platform/scripts/gen-events.mjs index c9817795..942ba2fa 100644 --- a/packages/platform/scripts/gen-events.mjs +++ b/packages/platform/scripts/gen-events.mjs @@ -1,10 +1,10 @@ #!/usr/bin/env node -// Generates src/client/modules/builder.events.generated.ts from asyncapi.json. `--check` fails on drift instead. +// Generates src/client/modules/chat.events.generated.ts from asyncapi.json. `--check` fails on drift instead. import { readFile, writeFile } from "node:fs/promises"; import { compile } from "json-schema-to-typescript"; const source = new URL("../asyncapi.json", import.meta.url); -const target = new URL("../src/client/modules/builder.events.generated.ts", import.meta.url); +const target = new URL("../src/client/modules/chat.events.generated.ts", import.meta.url); const doc = JSON.parse(await readFile(source, "utf8")); const schemas = doc.components.schemas; const refName = (ref) => ref.split("/").pop(); @@ -85,10 +85,10 @@ export const serverEventNames = ${JSON.stringify(serverEvents.map((event) => eve if (process.argv.includes("--check")) { const committed = await readFile(target, "utf8").catch(() => ""); if (committed !== output) { - console.error("builder.events.generated.ts is out of date with asyncapi.json. Run `npm run gen:events -w @base44/platform` and commit the result."); + console.error("chat.events.generated.ts is out of date with asyncapi.json. Run `npm run gen:events -w @base44/platform` and commit the result."); process.exit(1); } - console.log("builder.events.generated.ts is up to date."); + console.log("chat.events.generated.ts is up to date."); } else { await writeFile(target, output); console.log(`Wrote ${Object.keys(schemas).length} schemas and ${serverEvents.length} server events.`); diff --git a/packages/platform/scripts/mintlify-post-processing/appended-articles.json b/packages/platform/scripts/mintlify-post-processing/appended-articles.json index 20874f6d..f5902ded 100644 --- a/packages/platform/scripts/mintlify-post-processing/appended-articles.json +++ b/packages/platform/scripts/mintlify-post-processing/appended-articles.json @@ -1,6 +1,6 @@ { - "interfaces/builder": [ - "interfaces/BuilderSession", + "interfaces/chat": [ + "interfaces/ChatSession", "interfaces/SocketSession", "type-aliases/PlatformEvent", "interfaces/PlatformSnapshot" diff --git a/packages/platform/scripts/mintlify-post-processing/page-sections.json b/packages/platform/scripts/mintlify-post-processing/page-sections.json index a0bc0155..a9124098 100644 --- a/packages/platform/scripts/mintlify-post-processing/page-sections.json +++ b/packages/platform/scripts/mintlify-post-processing/page-sections.json @@ -1,6 +1,6 @@ { "typesWithOwnMethods": { - "BuilderSession": "Session methods" + "ChatSession": "Session methods" }, "typeDefinitionGroups": [ ["SocketSession", "PlatformEvent", "PlatformSnapshot"], diff --git a/packages/platform/scripts/mintlify-post-processing/types-to-expose.json b/packages/platform/scripts/mintlify-post-processing/types-to-expose.json index 886ab129..69bd5166 100644 --- a/packages/platform/scripts/mintlify-post-processing/types-to-expose.json +++ b/packages/platform/scripts/mintlify-post-processing/types-to-expose.json @@ -1,7 +1,7 @@ [ "AppErrorCode", - "BuilderModule", - "BuilderSession", + "ChatModule", + "ChatSession", "PlatformEvent", "PlatformSnapshot", "PlatformSocketError", diff --git a/packages/platform/src/client/client.ts b/packages/platform/src/client/client.ts index a3cf6598..ba0af463 100644 --- a/packages/platform/src/client/client.ts +++ b/packages/platform/src/client/client.ts @@ -1,12 +1,12 @@ import type { PlatformClient } from "./client.types.js"; -import { createBuilder } from "./modules/builder.js"; +import { createChat } from "./modules/chat.js"; /** * Creates a platform client. * * This is the entry point of the Platform SDK. The client gives your white-label app editor - * access to the platform's modules, such as [`builder`](/developers/references/platform-sdk/docs/interfaces/builder). - * It takes no options and opens nothing: each builder session gets its socket session from the + * access to the platform's modules, such as [`chat`](/developers/references/platform-sdk/docs/interfaces/chat). + * It takes no options and opens nothing: each chat session gets its socket session from the * `getSession` you pass to `init()`. * * @returns A platform client with access to the platform's modules. @@ -19,16 +19,17 @@ import { createBuilder } from "./modules/builder.js"; * const client = createPlatformClient(); * * // Use the client to follow the AI chat in an app - * const builder = client.builder.init({ + * const chat = client.chat.init({ * async getSession() { - * const response = await fetch('/api/builder-socket-session', { method: 'POST' }); + * const response = await fetch('/api/chat-socket-session', { method: 'POST' }); * return response.json(); * }, * onError: (error) => console.error(error.code), * }); - * await builder.connect(); + * await chat.connect(); * ``` */ export function createPlatformClient(): PlatformClient { - return Object.freeze({ builder: Object.freeze(createBuilder()) }); + const chat = Object.freeze(createChat()); + return Object.freeze({ chat, builder: chat }); } diff --git a/packages/platform/src/client/client.types.ts b/packages/platform/src/client/client.types.ts index 6ed309d5..d460db94 100644 --- a/packages/platform/src/client/client.types.ts +++ b/packages/platform/src/client/client.types.ts @@ -1,4 +1,4 @@ -import type { BuilderModule } from "./modules/builder.types.js"; +import type { ChatModule } from "./modules/chat.types.js"; /** * The platform client. @@ -6,6 +6,11 @@ import type { BuilderModule } from "./modules/builder.types.js"; * Provides access to the platform's modules. */ export interface PlatformClient { - /** [Builder module](/developers/references/platform-sdk/docs/interfaces/builder) for following the AI chat in your users' apps live. */ - readonly builder: BuilderModule; + /** [Chat module](/developers/references/platform-sdk/docs/interfaces/chat) for following the AI chat in your users' apps live. */ + readonly chat: ChatModule; + /** + * @deprecated Renamed to {@linkcode chat}, the same module. Removed in 1.0. + * @hidden + */ + readonly builder: ChatModule; } diff --git a/packages/platform/src/client/errors.ts b/packages/platform/src/client/errors.ts index 56d0fdee..a020576c 100644 --- a/packages/platform/src/client/errors.ts +++ b/packages/platform/src/client/errors.ts @@ -1,7 +1,7 @@ import type { PlatformSocketErrorCode } from "./errors.types.js"; /** - * The error the SDK reports when something goes wrong in a builder session. + * The error the SDK reports when something goes wrong in a chat session. * * You don't create it. The SDK passes it to your `onError` callbacks, `connect()` rejects with it, * and `subscribe()` throws it. Check `code` to see what happened, then look the code up below: @@ -15,14 +15,14 @@ import type { PlatformSocketErrorCode } from "./errors.types.js"; * @example * ```typescript * // Handle session and app errors - * const builder = client.builder.init({ + * const chat = client.chat.init({ * // Session error codes: the whole session is affected * onError(error) { * if (error.code === 'session_revoked') showSignedOut(); * }, * }); * - * builder.subscribe(appId, { + * chat.subscribe(appId, { * onSnapshot: renderChat, * onEvent: applyEvent, * // App error codes: only this app is affected, and error.appId says which diff --git a/packages/platform/src/client/index.ts b/packages/platform/src/client/index.ts index b8f93fb1..d29eb1e0 100644 --- a/packages/platform/src/client/index.ts +++ b/packages/platform/src/client/index.ts @@ -3,6 +3,7 @@ export { createPlatformClient } from "./client.js"; export { PlatformSocketError } from "./errors.js"; export type { AppErrorCode, PlatformSocketErrorCode, SessionErrorCode } from "./errors.types.js"; export type { PlatformClient } from "./client.types.js"; -export type { BuilderModule, BuilderInitOptions, BuilderSession, PlatformSubscription, SocketSession, SubscriptionOptions } from "./modules/builder.types.js"; -export type { GuardApproval, PlatformEvent, PlatformEventMap, PlatformSnapshot, ToolCall } from "./modules/builder.events.types.js"; -export type * from "./modules/builder.events.generated.js"; +export type { ChatModule, ChatInitOptions, ChatSession, PlatformSubscription, SocketSession, SubscriptionOptions } from "./modules/chat.types.js"; +export type { BuilderInitOptions, BuilderModule, BuilderSession } from "./modules/chat.types.js"; +export type { GuardApproval, PlatformEvent, PlatformEventMap, PlatformSnapshot, ToolCall } from "./modules/chat.events.types.js"; +export type * from "./modules/chat.events.generated.js"; diff --git a/packages/platform/src/client/modules/builder.ts b/packages/platform/src/client/modules/builder.ts deleted file mode 100644 index 4e71199f..00000000 --- a/packages/platform/src/client/modules/builder.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { BuilderSocket } from "./builder-socket.js"; -import type { BuilderModule } from "./builder.types.js"; - -/** @internal */ -export function createBuilder(): BuilderModule { - return { - init(options) { return new BuilderSocket(options); }, - }; -} diff --git a/packages/platform/src/client/modules/builder-protocol.ts b/packages/platform/src/client/modules/chat-protocol.ts similarity index 96% rename from packages/platform/src/client/modules/builder-protocol.ts rename to packages/platform/src/client/modules/chat-protocol.ts index 7d979923..c0022f45 100644 --- a/packages/platform/src/client/modules/builder-protocol.ts +++ b/packages/platform/src/client/modules/chat-protocol.ts @@ -1,7 +1,7 @@ -import { serverEventNames, type ServerEventMap, type SessionEnded } from "./builder.events.generated.js"; +import { serverEventNames, type ServerEventMap, type SessionEnded } from "./chat.events.generated.js"; import type { PlatformSocketErrorCode } from "../errors.types.js"; -import type { PlatformEvent, PlatformEventMap, PlatformSnapshot } from "./builder.events.types.js"; -import type { SocketSession } from "./builder.types.js"; +import type { PlatformEvent, PlatformEventMap, PlatformSnapshot } from "./chat.events.types.js"; +import type { SocketSession } from "./chat.types.js"; const sessionEvents: readonly string[] = ["app.snapshot", "session.ended"]; /** App events delivered to `onEvent`: every server event except the session events the socket handles. */ diff --git a/packages/platform/src/client/modules/builder-socket.ts b/packages/platform/src/client/modules/chat-socket.ts similarity index 95% rename from packages/platform/src/client/modules/builder-socket.ts rename to packages/platform/src/client/modules/chat-socket.ts index e9f73b7c..2805c539 100644 --- a/packages/platform/src/client/modules/builder-socket.ts +++ b/packages/platform/src/client/modules/chat-socket.ts @@ -1,17 +1,17 @@ import { io, type Socket } from "socket.io-client"; import { PlatformSocketError } from "../errors.js"; -import { appFromRoom, appPattern, decode, decodeSession, decodeSnapshot, eventNames, isUnknownEvent, object, roomFor, roomNotices, sessionEndings } from "./builder-protocol.js"; -import { notify, Subscription } from "./builder-subscription.js"; -import type { BuilderInitOptions, BuilderSession, PlatformSubscription, SocketSession, SubscriptionOptions } from "./builder.types.js"; +import { appFromRoom, appPattern, decode, decodeSession, decodeSnapshot, eventNames, isUnknownEvent, object, roomFor, roomNotices, sessionEndings } from "./chat-protocol.js"; +import { notify, Subscription } from "./chat-subscription.js"; +import type { ChatInitOptions, ChatSession, PlatformSubscription, SocketSession, SubscriptionOptions } from "./chat.types.js"; const MAX_DENIAL_RETRIES = 5; /** @internal */ -export class BuilderSocket implements BuilderSession { +export class ChatSocket implements ChatSession { private socket?: Socket; private address?: string; private readonly subscriptions = new Map(); - private readonly options: BuilderInitOptions; + private readonly options: ChatInitOptions; private closed = false; // The session outlives reconnects; it is replaced only when the server rejects or expires its token. private session?: SocketSession; @@ -25,7 +25,7 @@ export class BuilderSocket implements BuilderSession { private resolveConnect?: () => void; private rejectConnect?: (error: PlatformSocketError) => void; - constructor(options: BuilderInitOptions) { + constructor(options: ChatInitOptions) { this.options = { getSession: options.getSession, onError: options.onError }; } @@ -55,7 +55,7 @@ export class BuilderSocket implements BuilderSession { subscribe(appId: string, options: SubscriptionOptions): PlatformSubscription { if (this.closed) throw new PlatformSocketError("client_closed"); if (!appPattern.test(appId)) throw new TypeError("appId must be 24 lowercase hexadecimal characters"); - if (this.subscriptions.has(appId)) throw new TypeError("An app may only have one subscription per builder session"); + if (this.subscriptions.has(appId)) throw new TypeError("An app may only have one subscription per chat session"); if (this.subscriptions.size >= 8) throw new PlatformSocketError("subscription_limit", appId); const subscription = new Subscription(appId, { ...options }, () => { this.subscriptions.delete(appId); diff --git a/packages/platform/src/client/modules/builder-subscription.ts b/packages/platform/src/client/modules/chat-subscription.ts similarity index 94% rename from packages/platform/src/client/modules/builder-subscription.ts rename to packages/platform/src/client/modules/chat-subscription.ts index 186ab251..dc4b46ff 100644 --- a/packages/platform/src/client/modules/builder-subscription.ts +++ b/packages/platform/src/client/modules/chat-subscription.ts @@ -1,7 +1,7 @@ -import type { PlatformEvent, PlatformSnapshot } from "./builder.events.types.js"; +import type { PlatformEvent, PlatformSnapshot } from "./chat.events.types.js"; import { PlatformSocketError } from "../errors.js"; import type { PlatformSocketErrorCode } from "../errors.types.js"; -import type { PlatformSubscription, SubscriptionOptions } from "./builder.types.js"; +import type { PlatformSubscription, SubscriptionOptions } from "./chat.types.js"; /** @internal */ export function notify(callback: (error: PlatformSocketError) => void, error: PlatformSocketError): void { diff --git a/packages/platform/src/client/modules/builder.events.generated.ts b/packages/platform/src/client/modules/chat.events.generated.ts similarity index 100% rename from packages/platform/src/client/modules/builder.events.generated.ts rename to packages/platform/src/client/modules/chat.events.generated.ts diff --git a/packages/platform/src/client/modules/builder.events.types.ts b/packages/platform/src/client/modules/chat.events.types.ts similarity index 98% rename from packages/platform/src/client/modules/builder.events.types.ts rename to packages/platform/src/client/modules/chat.events.types.ts index 60294450..9833d7ae 100644 --- a/packages/platform/src/client/modules/builder.events.types.ts +++ b/packages/platform/src/client/modules/chat.events.types.ts @@ -1,4 +1,4 @@ -import type { Message, QueueState, ServerEventMap, Snapshot } from "./builder.events.generated.js"; +import type { Message, QueueState, ServerEventMap, Snapshot } from "./chat.events.generated.js"; /** * Maps each live event name to the type of its `data` payload. diff --git a/packages/platform/src/client/modules/chat.ts b/packages/platform/src/client/modules/chat.ts new file mode 100644 index 00000000..3ef94f99 --- /dev/null +++ b/packages/platform/src/client/modules/chat.ts @@ -0,0 +1,9 @@ +import { ChatSocket } from "./chat-socket.js"; +import type { ChatModule } from "./chat.types.js"; + +/** @internal */ +export function createChat(): ChatModule { + return { + init(options) { return new ChatSocket(options); }, + }; +} diff --git a/packages/platform/src/client/modules/builder.types.ts b/packages/platform/src/client/modules/chat.types.ts similarity index 84% rename from packages/platform/src/client/modules/builder.types.ts rename to packages/platform/src/client/modules/chat.types.ts index 064ab695..37539d4a 100644 --- a/packages/platform/src/client/modules/builder.types.ts +++ b/packages/platform/src/client/modules/chat.types.ts @@ -1,4 +1,4 @@ -import type { PlatformEvent, PlatformSnapshot } from "./builder.events.types.js"; +import type { PlatformEvent, PlatformSnapshot } from "./chat.events.types.js"; import type { PlatformSocketError } from "../errors.js"; /** @@ -16,8 +16,8 @@ export interface SocketSession { socket_path: string; } -/** Options for {@linkcode BuilderModule.init | init()}. */ -export interface BuilderInitOptions { +/** Options for {@linkcode ChatModule.init | init()}. */ +export interface ChatInitOptions { /** * Returns a new socket session from your server. * @@ -39,7 +39,7 @@ export interface BuilderInitOptions { } /** - * Builder module for following the AI chat in your users' apps live. + * Chat module for following the AI chat in your users' apps live. * * Each session holds one socket and up to eight app subscriptions. For each app you get a * snapshot of its chat, then every change as it happens: @@ -49,11 +49,11 @@ export interface BuilderInitOptions { * * The module does nothing until you call {@linkcode init | init()}. */ -export interface BuilderModule { +export interface ChatModule { /** - * Creates a builder session without connecting it. + * Creates a chat session without connecting it. * - * Call {@linkcode BuilderSession.connect | connect()} on the returned session to open the socket. + * Call {@linkcode ChatSession.connect | connect()} on the returned session to open the socket. * Each session is independent, with its own socket, socket session and subscriptions. * * @param options - Session options. @@ -62,26 +62,26 @@ export interface BuilderModule { * @example * ```typescript * // Create and connect a session - * const builder = client.builder.init({ + * const chat = client.chat.init({ * async getSession() { - * const response = await fetch('/api/builder-socket-session', { method: 'POST' }); + * const response = await fetch('/api/chat-socket-session', { method: 'POST' }); * return response.json(); * }, * onError(error) { - * console.error('Builder session error', error.code); + * console.error('Chat session error', error.code); * }, * }); - * await builder.connect(); + * await chat.connect(); * ``` */ - init(options: BuilderInitOptions): BuilderSession; + init(options: ChatInitOptions): ChatSession; } /** - * A `BuilderSession`: one socket with its app subscriptions and its reconnection. Use its methods + * A `ChatSession`: one socket with its app subscriptions and its reconnection. Use its methods * to connect, follow apps and close. */ -export interface BuilderSession { +export interface ChatSession { /** * Connects the session's socket. * @@ -98,7 +98,7 @@ export interface BuilderSession { * ```typescript * // Connect and handle failure * try { - * await builder.connect(); + * await chat.connect(); * } catch (error) { * console.error('Could not connect', error.code); * } @@ -125,7 +125,7 @@ export interface BuilderSession { * @example * ```typescript * // Follow an app - * const subscription = builder.subscribe(appId, { + * const subscription = chat.subscribe(appId, { * onSnapshot(snapshot) { * renderChat(snapshot.messages); * }, @@ -143,13 +143,13 @@ export interface BuilderSession { * Closes the session. * * Ends every subscription, stops reconnecting and disconnects the socket. Calling it again does - * nothing. A closed session can't be reused, so call [`init()`](/developers/references/platform-sdk/docs/interfaces/builder#init) + * nothing. A closed session can't be reused, so call [`init()`](/developers/references/platform-sdk/docs/interfaces/chat#init) * for a new one. * * @example * ```typescript * // Close a session - * builder.close(); + * chat.close(); * ``` */ close(): void; @@ -197,3 +197,21 @@ export interface PlatformSubscription { */ unsubscribe(): void; } + +/** + * @deprecated Renamed to {@linkcode ChatModule}. Removed in 1.0. + * @hidden + */ +export type BuilderModule = ChatModule; + +/** + * @deprecated Renamed to {@linkcode ChatSession}. Removed in 1.0. + * @hidden + */ +export type BuilderSession = ChatSession; + +/** + * @deprecated Renamed to {@linkcode ChatInitOptions}. Removed in 1.0. + * @hidden + */ +export type BuilderInitOptions = ChatInitOptions; diff --git a/packages/platform/src/react/chat-items.ts b/packages/platform/src/react/chat-items.ts index f827ec28..fc3d0da8 100644 --- a/packages/platform/src/react/chat-items.ts +++ b/packages/platform/src/react/chat-items.ts @@ -1,5 +1,5 @@ -import type { Message, QuestionArguments, QuestionInput, SetSecretsArguments } from "../client/modules/builder.events.generated.js"; -import type { ToolCall } from "../client/modules/builder.events.types.js"; +import type { Message, QuestionArguments, QuestionInput, SetSecretsArguments } from "../client/modules/chat.events.generated.js"; +import type { ToolCall } from "../client/modules/chat.events.types.js"; import type { ApprovalDetails, ChatAnswer, ChatItem, ChatQuestion, ChatStep } from "./chat.types.js"; /** @internal Sends the answer to the one tool call a question belongs to. */ @@ -40,7 +40,7 @@ function isTurnReply(message: Message | undefined): boolean { return message?.role === "assistant" && !message.additional_message_params?.skip_ai_response; } -/** @internal The last assistant message that answers a turn, as Base44's own builder finds it. */ +/** @internal The last assistant message that answers a turn, as Base44's app editor finds it. */ function lastTurnReply(sortedMessages: Message[]): Message | undefined { return [...sortedMessages].reverse().find(isTurnReply); } @@ -120,7 +120,7 @@ export function stepStatus(toolCall: ToolCall): ChatStep["status"] { return "done"; } -/** @internal Whether the chat waits on the user. Like Base44's builder, only the last turn reply +/** @internal Whether the chat waits on the user. Like Base44's app editor, only the last turn reply * counts, and a call answered or declined here stops counting. */ export function isWaitingForUser(sortedMessages: Message[], answeredToolCallIds: string[]): boolean { const lastReply = lastTurnReply(sortedMessages); @@ -180,7 +180,7 @@ export function pendingPromptItems(pendingPrompts: PendingPrompt[]): ChatItem[] /** @internal Base44's question formats, as one simple shape per kind, each bound to the call that * answers it. Base44 keys choice answers by position, so `picked` and `texts` follow `questions`, - * and like Base44's builder a single pick goes in `selected_label`, several in `selected_labels`, + * and like Base44's app editor a single pick goes in `selected_label`, several in `selected_labels`, * and free text in `custom_text`. A kind this release doesn't know has no shape. */ export function toQuestion(toolCall: ToolCall, answerToolCall: AnswerToolCall): ChatQuestion | undefined { const kind = toolCall.waiting_on?.kind; diff --git a/packages/platform/src/react/chat.types.ts b/packages/platform/src/react/chat.types.ts index 9c0eb515..3535a820 100644 --- a/packages/platform/src/react/chat.types.ts +++ b/packages/platform/src/react/chat.types.ts @@ -1,4 +1,4 @@ -import type { SocketSession } from "../client/modules/builder.types.js"; +import type { SocketSession } from "../client/modules/chat.types.js"; /** An app the AI chat works on. */ export interface Base44App { diff --git a/packages/platform/src/react/index.ts b/packages/platform/src/react/index.ts index 0841fdaf..346c91cb 100644 --- a/packages/platform/src/react/index.ts +++ b/packages/platform/src/react/index.ts @@ -4,4 +4,4 @@ export type { ApprovalDetails, ApprovalQuestion, Base44App, Base44Chat, Base44ChatOptions, Base44ChatBackend, ChatAnswer, ChatConnection, ChatError, ChatItem, ChatQuestion, ChatStatus, ChatStep, ChoiceEntry, ChoiceQuestion, SecretField, SecretsQuestion, ToolCallAnswer, } from "./chat.types.js"; -export type { SocketSession } from "../client/modules/builder.types.js"; +export type { SocketSession } from "../client/modules/chat.types.js"; diff --git a/packages/platform/src/react/useBase44Chat.ts b/packages/platform/src/react/useBase44Chat.ts index fb95a437..b9ca6542 100644 --- a/packages/platform/src/react/useBase44Chat.ts +++ b/packages/platform/src/react/useBase44Chat.ts @@ -1,7 +1,7 @@ "use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { createPlatformClient } from "../client/client.js"; -import type { Message, StatusObject } from "../client/modules/builder.events.generated.js"; +import type { Message, StatusObject } from "../client/modules/chat.events.generated.js"; import { isWaitingForUser, mergeMessage, mergeSnapshot, pendingPromptItems, settlePendingPrompts, sortMessages, toItems, unknownKindToolCalls, waitingToolCallIds, type PendingPrompt } from "./chat-items.js"; import type { Base44Chat, Base44ChatOptions, ChatConnection, ChatError, ChatStatus } from "./chat.types.js"; @@ -169,7 +169,7 @@ export function useBase44Chat({ appId, backend, onAppCreated }: Base44ChatOption // The client asks for a session on the first connect and whenever one expires, and forwards // no detail of a failed call, so the backend's own error is kept to report. let lastSessionError: unknown; - const builderSession = createPlatformClient().builder.init({ + const chatSession = createPlatformClient().chat.init({ getSession: () => backendRef.current.openLiveSession(followedAppId).then( socketSession => { @@ -188,7 +188,7 @@ export function useBase44Chat({ appId, backend, onAppCreated }: Base44ChatOption setConnection("disconnected"); }, }); - builderSession.subscribe(followedAppId, { + chatSession.subscribe(followedAppId, { onSnapshot: snapshot => { // A snapshot holds old prompts too, so only an exact match settles one. handleIncomingMessages(snapshot.messages); @@ -219,11 +219,11 @@ export function useBase44Chat({ appId, backend, onAppCreated }: Base44ChatOption onError: error => ((error as { code?: string }).code === "snapshot_unavailable" ? reportConnectionError(error) : endLiveUpdates(error)), }); // A failed connect also reaches onError, which reports it. - builderSession.connect().catch(() => {}); + chatSession.connect().catch(() => {}); return () => { closed = true; - builderSession.close(); + chatSession.close(); }; }, [currentAppId]); diff --git a/packages/platform/tests/package.test.mjs b/packages/platform/tests/package.test.mjs index 0f227c74..7de74df2 100644 --- a/packages/platform/tests/package.test.mjs +++ b/packages/platform/tests/package.test.mjs @@ -36,8 +36,8 @@ test("the client resolves from the installed package without the runtime SDK", ( import assert from "node:assert/strict"; import { createPlatformClient } from "@base44/platform"; const client = createPlatformClient(); - assert.equal(typeof client.builder.init, "function"); - client.builder.init({ getSession: async () => ({ session_token: "t", socket_url: "https://example.test", socket_path: "/ws/socket.io/" }), onError() {} }).close(); + assert.equal(typeof client.chat.init, "function"); + client.chat.init({ getSession: async () => ({ session_token: "t", socket_url: "https://example.test", socket_path: "/ws/socket.io/" }), onError() {} }).close(); `]); }); @@ -106,7 +106,7 @@ void useBase44Chat; void backend; import { createPlatformClient, type PlatformEvent } from "@base44/platform"; const platform = createPlatformClient(); -platform.builder.init({ getSession: async () => (await fetch("/session")).json(), onError() {} }).subscribe("a".repeat(24), { +platform.chat.init({ getSession: async () => (await fetch("/session")).json(), onError() {} }).subscribe("a".repeat(24), { onSnapshot(snapshot) { void snapshot.messages; }, onEvent(event: PlatformEvent) { if (event.type === "message.updated") { diff --git a/packages/platform/tests/types/client.types.ts b/packages/platform/tests/types/client.types.ts index a16746b8..d63e8504 100644 --- a/packages/platform/tests/types/client.types.ts +++ b/packages/platform/tests/types/client.types.ts @@ -1,20 +1,20 @@ -import { createPlatformClient, type SocketSession, type PlatformEvent, type PlatformEventMap, type PlatformSnapshot, type ToolCall, type GuardApproval, type Message, type StatusObject } from "@base44/platform"; +import { createPlatformClient, type SocketSession, type PlatformEvent, type PlatformEventMap, type PlatformSnapshot, type ToolCall, type GuardApproval, type Message, type StatusObject, type BuilderSession, type ChatSession } from "@base44/platform"; const client = createPlatformClient(); -const builder = client.builder.init({ +const chat = client.chat.init({ // The Create socket session body passes through, extra keys included. getSession: async () => ({ session_id: "s1", session_token: "t", socket_url: "https://example.test", socket_path: "/ws/socket.io/", expires_in: 3600 }), onError: error => { void error.code; }, }); -client.builder.init({ getSession: async () => (await fetch("/session")).json(), onError() {} }); +client.chat.init({ getSession: async () => (await fetch("/session")).json(), onError() {} }); const body: SocketSession = { session_token: "t", socket_url: "https://example.test", socket_path: "/ws/socket.io/" }; void body; // @ts-expect-error The session needs its socket path. -client.builder.init({ getSession: async () => ({ session_token: "t", socket_url: "https://example.test" }), onError() {} }); +client.chat.init({ getSession: async () => ({ session_token: "t", socket_url: "https://example.test" }), onError() {} }); // @ts-expect-error The session comes from init, not the client. createPlatformClient({ socketUrl: "https://example.test", getSessionToken: async () => "token" }); // @ts-expect-error A session is required. -client.builder.init({ onError() {} }); -const subscription = builder.subscribe("a".repeat(24), { +client.chat.init({ onError() {} }); +const subscription = chat.subscribe("a".repeat(24), { onSnapshot(snapshot: PlatformSnapshot) { const state: "ready" | "processing" | "error" | null | undefined = snapshot.status?.state; const room: string = snapshot.room; @@ -58,12 +58,16 @@ void subscription.active; // @ts-expect-error There is no replay cursor. void subscription.cursor; // @ts-expect-error Subscriptions take snapshots, not cursors. -builder.subscribe("a".repeat(24), { afterSeq: "saved", onSnapshot() {}, onEvent() {}, onError() {} }); +chat.subscribe("a".repeat(24), { afterSeq: "saved", onSnapshot() {}, onEvent() {}, onError() {} }); // @ts-expect-error No browser mutation channel. client.send("write_file", {}); // @ts-expect-error API keys are not browser credentials. createPlatformClient({ apiKey: "private" }); -// @ts-expect-error Socket methods belong to the builder session. +// The deprecated names still type-check. +const legacy: BuilderSession = client.builder.init({ getSession: async () => (await fetch("/session")).json(), onError() {} }); +const renamed: ChatSession = legacy; +void renamed; +// @ts-expect-error Socket methods belong to the chat session. client.connect(); const tool: ToolCall = { diff --git a/packages/platform/tests/unit/client.test.ts b/packages/platform/tests/unit/client.test.ts index 6d884f6f..112b5cd6 100644 --- a/packages/platform/tests/unit/client.test.ts +++ b/packages/platform/tests/unit/client.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -import { createPlatformClient, PlatformSocketError, type BuilderSession } from "../../src/client/index.js"; +import { createPlatformClient, PlatformSocketError, type ChatSession } from "../../src/client/index.js"; const fake = vi.hoisted(() => { const handlers: Record void> = {}; @@ -16,7 +16,7 @@ const fake = vi.hoisted(() => { vi.mock("socket.io-client", () => ({ io: fake.io })); const app = "a".repeat(24), other = "b".repeat(24), room = `/apps/${app}`; -const clients: BuilderSession[] = []; +const clients: ChatSession[] = []; const settle = async () => { for (let i = 0; i < 100; i++) await Promise.resolve(); }; const snapshot = (messages: unknown[] = []) => fake.handlers["app.snapshot"]({ room, data: { status: { state: "ready" }, messages } }); const update = (data: unknown = { status: null }) => fake.handlers["app.status_changed"]({ room, data }); @@ -30,14 +30,14 @@ const auth = async () => { }; function setup(getSession = vi.fn(async (): Promise => session())) { const onError = vi.fn(); - const client = createPlatformClient().builder.init({ getSession, onError }); + const client = createPlatformClient().chat.init({ getSession, onError }); clients.push(client); return { client, getSession, onError }; } -async function begin(client: BuilderSession) { +async function begin(client: ChatSession) { client.connect().catch(() => {}); await settle(); } -async function connected(client: BuilderSession) { +async function connected(client: ChatSession) { const ready = client.connect(); await settle(); fake.socket.connected = true; fake.handlers.connect(); await ready; await settle(); } const joins = () => fake.socket.emit.mock.calls.filter(c => c[0] === "join"); @@ -48,15 +48,20 @@ describe("platform client", () => { test("creating the client and initializing sessions opens no socket and fetches no session", () => { const getSession = vi.fn(async () => session()); const platform = createPlatformClient(); - const first = platform.builder.init({ getSession, onError: vi.fn() }); - const second = platform.builder.init({ getSession, onError: vi.fn() }); + const first = platform.chat.init({ getSession, onError: vi.fn() }); + const second = platform.chat.init({ getSession, onError: vi.fn() }); clients.push(first, second); expect(first).not.toBe(second); expect(fake.io).not.toHaveBeenCalled(); expect(getSession).not.toHaveBeenCalled(); }); - test("closing one initialized builder does not close another or the root module", async () => { + test("the deprecated builder module is the chat module", () => { + const platform = createPlatformClient(); + expect(platform.builder).toBe(platform.chat); + }); + + test("closing one initialized chat session does not close another or the root module", async () => { const firstSocket = { ...fake.socket, disconnect: vi.fn() }; const secondSocket = { ...fake.socket, disconnect: vi.fn() }; fake.io.mockReturnValueOnce(firstSocket).mockReturnValueOnce(secondSocket); diff --git a/packages/platform/tests/unit/react/chat-items.test.ts b/packages/platform/tests/unit/react/chat-items.test.ts index 49c40f1f..dd5e9631 100644 --- a/packages/platform/tests/unit/react/chat-items.test.ts +++ b/packages/platform/tests/unit/react/chat-items.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test, vi } from "vitest"; -import type { Message } from "../../../src/client/modules/builder.events.generated.js"; -import type { ToolCall } from "../../../src/client/modules/builder.events.types.js"; +import type { Message } from "../../../src/client/modules/chat.events.generated.js"; +import type { ToolCall } from "../../../src/client/modules/chat.events.types.js"; import { toQuestion, isWaitingForUser, mergeMessage, mergeSnapshot, pendingPromptItems, settlePendingPrompts, SNAPSHOT_SIZE, sortMessages, stepStatus, toItems, unknownKindToolCalls, waitingToolCallIds } from "../../../src/react/chat-items.js"; const message = (id: string, created: string, extra: Partial = {}): Message => ({ id, role: "assistant", metadata: { created_date: created }, ...extra }); @@ -115,7 +115,7 @@ describe("items", () => { expect(waitingToolCallIds(sorted)).toEqual(new Set(["t0", "t1", "t2", "t3"])); }); - test("the chat waits only on the last assistant message, as in Base44's builder", () => { + test("the chat waits only on the last assistant message, as in Base44's app editor", () => { const sorted = [message("m1", "1", { tool_calls: [{ id: "t0", status: "running" }] }), message("m2", "2", { tool_calls: [waiting("t1"), waiting("t2")] })]; expect(isWaitingForUser(sorted, [])).toBe(true); expect(isWaitingForUser(sorted, ["t1"])).toBe(true); @@ -206,7 +206,7 @@ describe("questions", () => { { text: "Extras?", description: undefined, type: undefined, options: ["X", "Y"], multi: true }, { text: "Name it", description: undefined, type: "text_input", options: [], multi: false }, ]); - // As in Base44's builder: one pick in selected_label, several in selected_labels. + // As in Base44's app editor: one pick in selected_label, several in selected_labels. await question.answer([["A"], ["X", "Y"]]); expect(reply).toHaveBeenCalledWith(true, { answers: [ diff --git a/packages/platform/tests/unit/react/useBase44Chat.test.ts b/packages/platform/tests/unit/react/useBase44Chat.test.ts index 259afc9d..a7c65df9 100644 --- a/packages/platform/tests/unit/react/useBase44Chat.test.ts +++ b/packages/platform/tests/unit/react/useBase44Chat.test.ts @@ -2,9 +2,9 @@ import { createElement, act } from "react"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -import type { Message, QueueItem } from "../../../src/client/modules/builder.events.generated.js"; -import type { PlatformSnapshot } from "../../../src/client/modules/builder.events.types.js"; -import type { SocketSession } from "../../../src/client/modules/builder.types.js"; +import type { Message, QueueItem } from "../../../src/client/modules/chat.events.generated.js"; +import type { PlatformSnapshot } from "../../../src/client/modules/chat.events.types.js"; +import type { SocketSession } from "../../../src/client/modules/chat.types.js"; import { useBase44Chat } from "../../../src/react/index.js"; import type { Base44Chat, Base44ChatOptions, Base44ChatBackend } from "../../../src/react/index.js"; @@ -17,7 +17,7 @@ type FakeSession = { subs: Record; init: InitOptions; closed const fake = vi.hoisted(() => ({ sessions: [] as FakeSession[] })); vi.mock("../../../src/client/client.js", () => ({ createPlatformClient: () => ({ - builder: { + chat: { init(init: InitOptions) { const session: FakeSession & { subscribe(appId: string, opts: SubOptions): unknown; close(): void } = { subs: {}, @@ -43,10 +43,10 @@ vi.mock("../../../src/client/client.js", () => ({ })); vi.mock("../../../src/client.js", () => ({ Base44PlatformClient: class { - builder: { init(options: InitOptions): FakeSession }; + chat: { init(options: InitOptions): FakeSession }; constructor(options: ClientOptions) { fake.clients.push(options); - this.builder = { + this.chat = { init(init) { const session: FakeSession & { subscribe(appId: string, opts: SubOptions): unknown; close(): void } = { subs: {}, diff --git a/packages/platform/tests/unit/socket.test.js b/packages/platform/tests/unit/socket.test.js index b6b5accc..e9304ebb 100644 --- a/packages/platform/tests/unit/socket.test.js +++ b/packages/platform/tests/unit/socket.test.js @@ -30,7 +30,7 @@ test("real Socket.IO handshake, join, snapshot, reconnect and session renewal", }); }); await new Promise(resolve => http.listen(0, "127.0.0.1", resolve)); - const builder = createPlatformClient().builder.init({ + const chat = createPlatformClient().chat.init({ getSession: async () => ({ session_id: `id-${tokenCalls}`, session_token: `session-${++tokenCalls}`, expires_in: 3600, socket_url: `http://127.0.0.1:${http.address().port}`, socket_path: "/live/socket.io/", @@ -38,8 +38,8 @@ test("real Socket.IO handshake, join, snapshot, reconnect and session renewal", onError: error => errors.push(error), }); try { - builder.subscribe(appId, { onSnapshot: s => { snapshots.push(s.messages[0].id); }, onEvent: vi.fn(), onError: error => errors.push(error) }); - await builder.connect(); + chat.subscribe(appId, { onSnapshot: s => { snapshots.push(s.messages[0].id); }, onEvent: vi.fn(), onError: error => errors.push(error) }); + await chat.connect(); await vi.waitFor(() => expect(snapshots).toEqual(["m1"])); peers[0].terminate(); await vi.waitFor(() => expect(snapshots).toEqual(["m1", "m2"]), { timeout: 6000 }); @@ -56,7 +56,7 @@ test("real Socket.IO handshake, join, snapshot, reconnect and session renewal", } expect(errors).toEqual([]); } finally { - builder.close(); + chat.close(); for (const peer of peers) peer.terminate(); await new Promise(resolve => server.close(resolve)); await new Promise(resolve => http.close(resolve)); From d538f3f39d5ad68f56f1d7da3ec60c709708a3cc Mon Sep 17 00:00:00 2001 From: amitbardos Date: Sun, 18 Oct 2026 16:35:00 +0300 Subject: [PATCH 2/4] chore(platform): follow the chat rename in the asyncapi sync workflow and example Co-Authored-By: Claude Opus 5.5 --- .github/workflows/sync-asyncapi.yml | 6 +++--- packages/platform/examples/client.ts | 10 +++++----- .../file-processing/file-processing.js | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/sync-asyncapi.yml b/.github/workflows/sync-asyncapi.yml index 78f2b969..d5339878 100644 --- a/.github/workflows/sync-asyncapi.yml +++ b/.github/workflows/sync-asyncapi.yml @@ -62,7 +62,7 @@ jobs: - name: Check for changes id: changes run: | - if git diff --quiet -- packages/platform/asyncapi.json packages/platform/src/client/modules/builder.events.generated.ts; then + if git diff --quiet -- packages/platform/asyncapi.json packages/platform/src/client/modules/chat.events.generated.ts; then echo "changed=false" >> "$GITHUB_OUTPUT" echo "✅ asyncapi.json and the event types are up to date" else @@ -91,7 +91,7 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git checkout -B "$BRANCH" - git add packages/platform/asyncapi.json packages/platform/src/client/modules/builder.events.generated.ts + git add packages/platform/asyncapi.json packages/platform/src/client/modules/chat.events.generated.ts git commit -m "chore(platform): sync asyncapi.json and the event types from production" pr=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number') @@ -103,7 +103,7 @@ jobs: # Rebuilt from main every run, so the branch never drifts or conflicts. git push --force origin "$BRANCH" - body="Refreshes \`packages/platform/asyncapi.json\` from $ASYNCAPI_URL and regenerates \`builder.events.generated.ts\`. The docs build and the Platform SDK tests passed on this change. Checked against production every hour." + body="Refreshes \`packages/platform/asyncapi.json\` from $ASYNCAPI_URL and regenerates \`chat.events.generated.ts\`. The docs build and the Platform SDK tests passed on this change. Checked against production every hour." if [ -z "$pr" ]; then gh pr create --head "$BRANCH" --base main --title "Sync Platform SDK events from production" --body "$body" fi diff --git a/packages/platform/examples/client.ts b/packages/platform/examples/client.ts index 3eae6187..db4dc9d7 100644 --- a/packages/platform/examples/client.ts +++ b/packages/platform/examples/client.ts @@ -1,17 +1,17 @@ import { createPlatformClient, type PlatformEvent } from "@base44/platform"; const client = createPlatformClient(); -const builder = client.builder.init({ +const chat = client.chat.init({ // Your backend opens the session with its workspace key and returns the Create socket session body. async getSession() { - const response = await fetch("/api/builder-socket-session", { method: "POST" }); + const response = await fetch("/api/chat-socket-session", { method: "POST" }); if (!response.ok) throw new Error("Unable to open a socket session"); return response.json(); }, onError(error) { console.error(error.code); }, }); -const subscription = builder.subscribe("0123456789abcdef01234567", { +const subscription = chat.subscribe("0123456789abcdef01234567", { onSnapshot(snapshot) { // Replace this app's state; merge messages by id to keep history older than the last 50. console.log(snapshot.status?.state, snapshot.messages.length); @@ -27,7 +27,7 @@ const subscription = builder.subscribe("0123456789abcdef01234567", { }, }); -await builder.connect(); +await chat.connect(); // Later, during teardown: subscription.unsubscribe(); -builder.close(); +chat.close(); diff --git a/packages/sdk/scripts/mintlify-post-processing/file-processing/file-processing.js b/packages/sdk/scripts/mintlify-post-processing/file-processing/file-processing.js index 9f824775..b677e60a 100755 --- a/packages/sdk/scripts/mintlify-post-processing/file-processing/file-processing.js +++ b/packages/sdk/scripts/mintlify-post-processing/file-processing/file-processing.js @@ -2587,7 +2587,7 @@ function applyClassPageCleanup(dir) { } /** - * A Returns type line such as `BuilderSession` becomes a link when the same page documents that type: + * A Returns type line such as `ChatSession` becomes a link when the same page documents that type: * its methods section (page-sections.json `typesWithOwnMethods`) or its own ### heading. */ function linkReturnTypes(content) { From 4be40ad626028295dd34cf48c11a0c34c5fb4887 Mon Sep 17 00:00:00 2001 From: amitbardos Date: Sun, 18 Oct 2026 17:08:13 +0300 Subject: [PATCH 3/4] feat(platform)!: drop the builder aliases; webhooks are chat.turn_* The SDK has no users yet, so the rename is clean: client.builder and the Builder* types are gone rather than deprecated. The webhook event types follow Base44's rename to chat.turn_started.v1 and chat.turn_ended.v1 (ChatTurnStartedV1Data, ChatTurnEndedV1Data); the retired builder.turn_* names now verify as UnknownWebhookEvent. Co-Authored-By: Claude Opus 5.5 --- packages/platform/CHANGELOG.md | 13 +++++++--- packages/platform/README.md | 2 +- .../platform/scripts/webhook-event-types.json | 4 ++-- packages/platform/src/client/client.ts | 3 +-- packages/platform/src/client/client.types.ts | 5 ---- packages/platform/src/client/index.ts | 1 - .../platform/src/client/modules/chat.types.ts | 18 -------------- .../modules/webhooks.events.generated.ts | 24 +++++++++---------- packages/platform/tests/types/client.types.ts | 8 +++---- packages/platform/tests/unit/client.test.ts | 5 ---- 10 files changed, 29 insertions(+), 54 deletions(-) diff --git a/packages/platform/CHANGELOG.md b/packages/platform/CHANGELOG.md index 7db789e0..3d1d62b5 100644 --- a/packages/platform/CHANGELOG.md +++ b/packages/platform/CHANGELOG.md @@ -2,10 +2,10 @@ ## Unreleased -### Renamed: `builder` is now `chat` +**Breaking.** Base44 calls it the AI chat, so the client and the webhook events say `chat`, not +`builder`. -The client follows the AI chat, so the module and its types say so. The old names still work and -are deprecated; they will be removed in 1.0. +### Client | Before | After | | --- | --- | @@ -18,6 +18,13 @@ are deprecated; they will be removed in 1.0. const chat = client.chat.init({ getSession, onError }); ``` +### Webhooks + +`chat.turn_started.v1` and `chat.turn_ended.v1` replace `builder.turn_started.v1` and +`builder.turn_ended.v1`, with the same payloads. Their types are `ChatTurnStartedV1Data` and +`ChatTurnEndedV1Data`. Base44 still sends the old names to endpoints that selected them; this SDK +reports those as `UnknownWebhookEvent`. Select `chat.turn_*` on your endpoint to move. + ## 0.2.0 **Breaking.** The client, its options and the event type names change, and events the SDK doesn't diff --git a/packages/platform/README.md b/packages/platform/README.md index 2da33029..3d9e22ed 100644 --- a/packages/platform/README.md +++ b/packages/platform/README.md @@ -161,7 +161,7 @@ their exceptions are isolated so they cannot interrupt another app's delivery. ## Public shapes -- `PlatformClient` (`client.types.ts`): the `chat` module (`builder` is a deprecated alias). +- `PlatformClient` (`client.types.ts`): the `chat` module. - `ChatModule` (`modules/chat.types.ts`): lazy `init(options)` factory. - `ChatInitOptions`: `getSession` and the connection-level `onError` observer. - `SocketSession`: the `session_token`, `socket_url` and `socket_path` that `getSession` returns. diff --git a/packages/platform/scripts/webhook-event-types.json b/packages/platform/scripts/webhook-event-types.json index 86074785..d1b229ca 100644 --- a/packages/platform/scripts/webhook-event-types.json +++ b/packages/platform/scripts/webhook-event-types.json @@ -5,6 +5,6 @@ "app.deleted.v1", "app.restored.v1", "webhook.test.v1", - "builder.turn_started.v1", - "builder.turn_ended.v1" + "chat.turn_started.v1", + "chat.turn_ended.v1" ] diff --git a/packages/platform/src/client/client.ts b/packages/platform/src/client/client.ts index ba0af463..3b9e551a 100644 --- a/packages/platform/src/client/client.ts +++ b/packages/platform/src/client/client.ts @@ -30,6 +30,5 @@ import { createChat } from "./modules/chat.js"; * ``` */ export function createPlatformClient(): PlatformClient { - const chat = Object.freeze(createChat()); - return Object.freeze({ chat, builder: chat }); + return Object.freeze({ chat: Object.freeze(createChat()) }); } diff --git a/packages/platform/src/client/client.types.ts b/packages/platform/src/client/client.types.ts index d460db94..042e2520 100644 --- a/packages/platform/src/client/client.types.ts +++ b/packages/platform/src/client/client.types.ts @@ -8,9 +8,4 @@ import type { ChatModule } from "./modules/chat.types.js"; export interface PlatformClient { /** [Chat module](/developers/references/platform-sdk/docs/interfaces/chat) for following the AI chat in your users' apps live. */ readonly chat: ChatModule; - /** - * @deprecated Renamed to {@linkcode chat}, the same module. Removed in 1.0. - * @hidden - */ - readonly builder: ChatModule; } diff --git a/packages/platform/src/client/index.ts b/packages/platform/src/client/index.ts index d29eb1e0..0a10f78f 100644 --- a/packages/platform/src/client/index.ts +++ b/packages/platform/src/client/index.ts @@ -4,6 +4,5 @@ export { PlatformSocketError } from "./errors.js"; export type { AppErrorCode, PlatformSocketErrorCode, SessionErrorCode } from "./errors.types.js"; export type { PlatformClient } from "./client.types.js"; export type { ChatModule, ChatInitOptions, ChatSession, PlatformSubscription, SocketSession, SubscriptionOptions } from "./modules/chat.types.js"; -export type { BuilderInitOptions, BuilderModule, BuilderSession } from "./modules/chat.types.js"; export type { GuardApproval, PlatformEvent, PlatformEventMap, PlatformSnapshot, ToolCall } from "./modules/chat.events.types.js"; export type * from "./modules/chat.events.generated.js"; diff --git a/packages/platform/src/client/modules/chat.types.ts b/packages/platform/src/client/modules/chat.types.ts index 37539d4a..1f50702a 100644 --- a/packages/platform/src/client/modules/chat.types.ts +++ b/packages/platform/src/client/modules/chat.types.ts @@ -197,21 +197,3 @@ export interface PlatformSubscription { */ unsubscribe(): void; } - -/** - * @deprecated Renamed to {@linkcode ChatModule}. Removed in 1.0. - * @hidden - */ -export type BuilderModule = ChatModule; - -/** - * @deprecated Renamed to {@linkcode ChatSession}. Removed in 1.0. - * @hidden - */ -export type BuilderSession = ChatSession; - -/** - * @deprecated Renamed to {@linkcode ChatInitOptions}. Removed in 1.0. - * @hidden - */ -export type BuilderInitOptions = ChatInitOptions; diff --git a/packages/platform/src/server/modules/webhooks.events.generated.ts b/packages/platform/src/server/modules/webhooks.events.generated.ts index 1ced2735..bde78fcb 100644 --- a/packages/platform/src/server/modules/webhooks.events.generated.ts +++ b/packages/platform/src/server/modules/webhooks.events.generated.ts @@ -141,10 +141,10 @@ export interface WebhookTestV1Data { challenge: string; } /** - * Sent when the builder picks the message up, not when it is received: a + * Sent when the AI picks the message up, not when it is received: a * message sent while another turn runs waits in the queue until then. */ -export interface BuilderTurnStartedV1Data { +export interface ChatTurnStartedV1Data { /** * The app the event is about. */ @@ -166,7 +166,7 @@ export interface BuilderTurnStartedV1Data { */ conversation_id?: string; /** - * When the builder started working on the message (UTC). + * When the AI started working on the message (UTC). */ started_at: string; /** @@ -175,9 +175,9 @@ export interface BuilderTurnStartedV1Data { continues_message_id?: string; } /** - * The `data` object of `builder.turn_ended.v1`. The builder finished responding to a message. + * The `data` object of `chat.turn_ended.v1`. The AI finished responding to a message. */ -export interface BuilderTurnEndedV1Data { +export interface ChatTurnEndedV1Data { /** * The app the event is about. */ @@ -199,7 +199,7 @@ export interface BuilderTurnEndedV1Data { */ conversation_id?: string; /** - * When the builder started working on the message (UTC). + * When the AI started working on the message (UTC). */ started_at: string; /** @@ -207,7 +207,7 @@ export interface BuilderTurnEndedV1Data { */ continues_message_id?: string; /** - * How the turn ended. `finished`: the builder replied and has nothing pending. `waiting_for_user_input`: the builder stopped to ask something, such as a clarifying question or a plan to approve, and continues when the next message answers it. `failed`: the turn hit an error. `cancelled`: the turn was stopped before it ended. `interrupted`: the builder was interrupted, for example by a restart, and continues the work in a new turn whose `continues_message_id` is this message. + * How the turn ended. `finished`: the AI replied and has nothing pending. `waiting_for_user_input`: the AI stopped to ask something, such as a clarifying question or a plan to approve, and continues when the next message answers it. `failed`: the turn hit an error. `cancelled`: the turn was stopped before it ended. `interrupted`: the AI was interrupted, for example by a restart, and continues the work in a new turn whose `continues_message_id` is this message. */ status: "finished" | "waiting_for_user_input" | "failed" | "cancelled" | "interrupted"; /** @@ -229,11 +229,11 @@ export interface WebhookEventMap { "app.restored.v1": AppRestoredV1Data; /** Carries no domain data — its only job is to be verifiable and echoed. */ "webhook.test.v1": WebhookTestV1Data; - /** Sent when the builder picks the message up, not when it is received: a */ - "builder.turn_started.v1": BuilderTurnStartedV1Data; - /** The builder finished responding to a message. */ - "builder.turn_ended.v1": BuilderTurnEndedV1Data; + /** Sent when the AI picks the message up, not when it is received: a */ + "chat.turn_started.v1": ChatTurnStartedV1Data; + /** The AI finished responding to a message. */ + "chat.turn_ended.v1": ChatTurnEndedV1Data; } /** Every event type in {@link WebhookEventMap}, so events from a newer Base44 can be told apart at runtime. @internal */ -export const WEBHOOK_EVENT_TYPES = ["app.created.v1","app.published.v1","app.unpublished.v1","app.deleted.v1","app.restored.v1","webhook.test.v1","builder.turn_started.v1","builder.turn_ended.v1"] as const; +export const WEBHOOK_EVENT_TYPES = ["app.created.v1","app.published.v1","app.unpublished.v1","app.deleted.v1","app.restored.v1","webhook.test.v1","chat.turn_started.v1","chat.turn_ended.v1"] as const; diff --git a/packages/platform/tests/types/client.types.ts b/packages/platform/tests/types/client.types.ts index d63e8504..eac7d31e 100644 --- a/packages/platform/tests/types/client.types.ts +++ b/packages/platform/tests/types/client.types.ts @@ -1,4 +1,4 @@ -import { createPlatformClient, type SocketSession, type PlatformEvent, type PlatformEventMap, type PlatformSnapshot, type ToolCall, type GuardApproval, type Message, type StatusObject, type BuilderSession, type ChatSession } from "@base44/platform"; +import { createPlatformClient, type SocketSession, type PlatformEvent, type PlatformEventMap, type PlatformSnapshot, type ToolCall, type GuardApproval, type Message, type StatusObject } from "@base44/platform"; const client = createPlatformClient(); const chat = client.chat.init({ // The Create socket session body passes through, extra keys included. @@ -63,10 +63,8 @@ chat.subscribe("a".repeat(24), { afterSeq: "saved", onSnapshot() {}, onEvent() { client.send("write_file", {}); // @ts-expect-error API keys are not browser credentials. createPlatformClient({ apiKey: "private" }); -// The deprecated names still type-check. -const legacy: BuilderSession = client.builder.init({ getSession: async () => (await fetch("/session")).json(), onError() {} }); -const renamed: ChatSession = legacy; -void renamed; +// @ts-expect-error The module is `chat`; `builder` was its name before 0.4.0. +client.builder; // @ts-expect-error Socket methods belong to the chat session. client.connect(); diff --git a/packages/platform/tests/unit/client.test.ts b/packages/platform/tests/unit/client.test.ts index 112b5cd6..f2ae8e02 100644 --- a/packages/platform/tests/unit/client.test.ts +++ b/packages/platform/tests/unit/client.test.ts @@ -56,11 +56,6 @@ describe("platform client", () => { expect(getSession).not.toHaveBeenCalled(); }); - test("the deprecated builder module is the chat module", () => { - const platform = createPlatformClient(); - expect(platform.builder).toBe(platform.chat); - }); - test("closing one initialized chat session does not close another or the root module", async () => { const firstSocket = { ...fake.socket, disconnect: vi.fn() }; const secondSocket = { ...fake.socket, disconnect: vi.fn() }; From 069ba4864b59d2859fd4cfdd82e023dbd4e661c8 Mon Sep 17 00:00:00 2001 From: amitbardos Date: Sun, 18 Oct 2026 17:27:50 +0300 Subject: [PATCH 4/4] docs(platform): Base44 renamed the turn events in place Co-Authored-By: Claude Opus 5.5 --- packages/platform/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/platform/CHANGELOG.md b/packages/platform/CHANGELOG.md index 3d1d62b5..8c9855d2 100644 --- a/packages/platform/CHANGELOG.md +++ b/packages/platform/CHANGELOG.md @@ -22,8 +22,8 @@ const chat = client.chat.init({ getSession, onError }); `chat.turn_started.v1` and `chat.turn_ended.v1` replace `builder.turn_started.v1` and `builder.turn_ended.v1`, with the same payloads. Their types are `ChatTurnStartedV1Data` and -`ChatTurnEndedV1Data`. Base44 still sends the old names to endpoints that selected them; this SDK -reports those as `UnknownWebhookEvent`. Select `chat.turn_*` on your endpoint to move. +`ChatTurnEndedV1Data`. Base44 no longer sends the old names, so an endpoint that selected them +must select `chat.turn_*` instead. ## 0.2.0