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/CHANGELOG.md b/packages/platform/CHANGELOG.md index ae1f67d1..8c9855d2 100644 --- a/packages/platform/CHANGELOG.md +++ b/packages/platform/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## Unreleased + +**Breaking.** Base44 calls it the AI chat, so the client and the webhook events say `chat`, not +`builder`. + +### Client + +| Before | After | +| --- | --- | +| `client.builder` | `client.chat` | +| `BuilderModule` | `ChatModule` | +| `BuilderSession` | `ChatSession` | +| `BuilderInitOptions` | `ChatInitOptions` | + +```ts +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 no longer sends the old names, so an endpoint that selected them +must select `chat.turn_*` instead. + ## 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..3d9e22ed 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. +- `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/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/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/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 a3cf6598..3b9e551a 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,16 @@ 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()) }); + 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 6ed309d5..042e2520 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,6 @@ 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; } 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..0a10f78f 100644 --- a/packages/platform/src/client/index.ts +++ b/packages/platform/src/client/index.ts @@ -3,6 +3,6 @@ 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 { 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 87% rename from packages/platform/src/client/modules/builder.types.ts rename to packages/platform/src/client/modules/chat.types.ts index 064ab695..1f50702a 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; 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/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/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..eac7d31e 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"; 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,14 @@ 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. +// @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(); const tool: ToolCall = { diff --git a/packages/platform/tests/unit/client.test.ts b/packages/platform/tests/unit/client.test.ts index 6d884f6f..f2ae8e02 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,15 @@ 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("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)); 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) {