Command Palette

Search for a command to run...

AI Architecture

How AI is wired into Studio — the prompt-to-diff-to-apply loop, models, and approval gates.

Studio's AI is not a chat window glued to a code editor. It's a structured loop: every turn produces a strict shape of patches, each patch is classified, and each is staged or applied based on the active approval mode. This page explains the moving pieces.

The model#

Studio calls Claude (Anthropic) directly via @anthropic-ai/sdk. The default model in the editor is Sonnet 4.6. Workspaces can pick Haiku 4.5 for cheap iterative work or Opus 4.7 for the rare deep reasoning task. The default lives in workspace settings; per-project overrides live in project settings.

Studio does not run a router or a model-of-models setup. One call, one model, one response.

The loop#

Every chat turn follows the same five steps. Hold this sequence in your head and the rest of the system clicks into place.

  1. Prompt construction

    The server route at app/api/studio/route.ts builds the request from a fixed system prompt (around 340 lines of Liquid conventions, tool definitions, and Insites context) plus the live conversation history, the user's new message, and project state — current instance, connection status, selected element, list of dirty files. A repo snapshot is fetched lazily so the chat shows "Reading the repo…" while Studio inlines just enough source for the model to work.

  2. Streamed response

    The model emits a stream of events. Studio defines two tools — write_file (full-file create/replace) and edit_file (targeted find-and-replace) — and the response is a mix of plain text and tool calls. Server-Sent Events flow back to the browser as { type: "status" | "text" | "patch" | "error" | "stop" }. Both tool inputs are validated against Zod schemas as they arrive; malformed args become a tool_error event and the patch is skipped, but the stream continues.

  3. Patch classification

    Each patch arriving in the browser is classified before it can do anything. write_file is additive if the path doesn't exist yet (it's a creation), non-additive if it overwrites a file. edit_file is additive if the replacement is at least as long as the search string. Edits whose search string appears more than once in the file are rejected outright — Studio refuses to guess which occurrence you meant. A duplication scan attaches a warning if the patch repeats content that already exists nearby.

  4. Stage or apply

    Approval mode decides what happens next. In Confirm mode (the default) every patch lands in a Pending Patches card and waits for your Accept or Reject. In Auto mode, additive patches apply immediately and the rest stage. In Dangerous mode, every patch applies on arrival. When a patch applies, the in-browser file contents update — the original is preserved separately so reject is non-destructive and you can always see the diff.

  5. Deploy

    Accepted patches modify in-browser files. They reach your project only when you hit Deploy, which commits the changed files to your connected GitHub repo, then triggers a CloudShell deploy to the target instance. Both legs stream their progress back via SSE and surface in the deploy panel.

Why diffs, not direct writes#

The AI never writes to your filesystem in one motion. Each tool call produces a discrete patch — a before / after pair plus a kind tag — and the diff is computed in the browser using a Hunt–McIlroy LCS pass. That gives you four properties for free:

  • Reviewability. Every change is line-level inspectable before it lands.
  • Reversibility. Reject is a no-op on the file. Even Accept doesn't touch your repo until Deploy.
  • Composition. Multiple turns can edit the same file; each layer composes on the simulated content from the previous turn so the AI sees a coherent state.
  • Isolation. A bad patch doesn't poison adjacent ones — the stream skips and keeps going.

Streaming, not blocking#

The whole pipeline is asynchronous. The chat stays responsive while the model is still thinking. You can accept or reject already-arrived patches mid-turn, abort the request entirely (the request carries an AbortController), and start typing the next message before the previous turn has finished.

What can go wrong#

Three failure modes show up in the UI:

  • Tool input invalid. A schema error becomes a tool_error event. The patch is skipped. The text response continues.
  • Network or model error. The chat surfaces an error message with suggestions and offers a retry.
  • Budget exhausted. The pre-call gate at lib/usage.ts returns HTTP 402 before any tokens are spent. The chat shows "raise your cap" and a link to settings.

Where this lives in code#

  • Server route, system prompt, streaming pipeline: app/api/studio/route.ts
  • Patch classification and approval logic: lib/approvalMode.ts
  • Diff computation: lib/diff.ts
  • Pending Patches UI: components/PendingPatchesCard.tsx
  • Editor and turn state: app/studio/StudioApp.tsx