main
md 255 lines 15.4 KB
Rendered Raw
1 ---
2 name: sigit-code-cloud
3 description: "Reference for siGit Code Cloud, the hosted CHAT product for siGit Code (interactive, signed-in, no local model). Use when working on the cloud chat surface: the sigit login/logout/whoami account commands, the credential store, provider/backend selection (on-device vs siGit Code Cloud vs BYO endpoint), neutral quality tiers, the persisted Cloud Sessions API, or the onde-cloud API behind it. Covers the Copilot-Chat/Azure layering, the InferenceBackend engine, auth via sigit.si/api/v1, and what must never leak to the client. The autonomous task->PR product is siGit Code Cloud Agent; see the sigit-code-cloud-agent skill."
4 ---
5
6 # siGit Code Cloud
7
8 **siGit Code Cloud** is the hosted **chat** for **siGit Code**: interactive,
9 signed-in, model in the cloud instead of on the device. The product framing is
10 deliberate:
11
12 - **siGit Code Cloud is the product**, like Copilot **Chat**. The user signs in,
13 picks a quality tier, and it works. They never see an API key, a base URL, or
14 the name of any model provider.
15 - **Onde Cloud is the infrastructure**, like Azure. It is the OpenAI-compatible
16 inference service siGit Code Cloud runs on, and it is an implementation detail.
17 - **Onde Cloud, in turn, routes to upstream providers** (today Anthropic) and
18 hides them too, see the onde-cloud `router`/`anthropic` modules.
19
20 **This is the chat product, not the agent.** The autonomous, sandboxed
21 task -> pull request product is **siGit Code Cloud Agent** (see the
22 `sigit-code-cloud-agent` skill). Chat's work unit is a **session**; the agent's is
23 a **run**. Keep them separate. Full product map:
24 [`docs/product/product-overview.md`](../../../docs/product/product-overview.md).
25
26 Keep the names straight (see the `branding` skill): the local agent is
27 `siGit Code`, the hosted chat is `siGit Code Cloud`, the autonomous one is
28 `siGit Code Cloud Agent`, the CLI is `sigit`, the company is `smbCloud`, the
29 inference infrastructure is `Onde Cloud` / `Onde Inference`, and the on-device
30 Rust crate is `onde`. `sigit.si` is Git hosting; `code.sigit.si` is the home of
31 siGit Code.
32
33 ## The layering (why it's built this way)
34
35 The original instinct was right: abstract the infrastructure away entirely. The
36 engine underneath is intentionally generic so that "Onde Cloud = swappable infra"
37 is literally true, but that generality is an *internal* seam, not the product.
38
39 ```
40 ┌─ Product surface (Copilot) ────────────────────────────────────────┐
41 │ sigit login → pick a tier (Fast / Balanced / Large) → just works │
42 │ No key, no URL, no provider names. Token is the only credential. │
43 └────────────────────────────────────────────────────────────────────┘
44 ┌─ Engine (generic, swappable) ──────────────────────────────────────┐
45 │ InferenceBackend trait │
46 │ ├─ LocalBackend → on-device onde::ChatEngine │
47 │ └─ OpenAiBackend → any {base_url, api_key, model} over HTTP │
48 └────────────────────────────────────────────────────────────────────┘
49 ┌─ Infrastructure (hidden) ──────────────────────────────────────────┐
50 │ Onde Cloud (OpenAI-compatible) → router → Anthropic (never named) │
51 └────────────────────────────────────────────────────────────────────┘
52 ```
53
54 The generic engine is also what enables the **BYO-endpoint escape hatch**: an
55 advanced user or enterprise can point siGit Code at their own OpenAI/Azure
56 endpoint. That is a power-user override, **not** the front door.
57
58 ## Backend selection: identity and model choice are separate
59
60 Being signed in does **not** change which model runs. Login is auth; the model
61 is the user's explicit choice in `/models`. The startup default
62 (`provider::active_provider()`) is local-first, with one exception:
63
64 1. **Override (power user / BYO):** `OPENAI_BASE_URL` + `OPENAI_API_KEY`, or the
65 active profile in `~/.config/sigit/providers.toml`. This is the only thing that
66 changes the *startup* backend.
67 2. **On-device by default:** no override means local, signed in or not.
68
69 The user then picks a model in `/models` (see below), which swaps the running
70 backend in place. `OPENAI_BASE_URL` set without `OPENAI_API_KEY` logs a warning
71 and stays on-device, rather than silently looking like "the cloud didn't work."
72
73 ## `/models`: on-device models and cloud tiers together
74
75 `/models` always lists the on-device models **and** the siGit Code Cloud tiers
76 (`Fast`/`Balanced`/`Large`), so the cloud option is discoverable to everyone.
77 Access is gated at **selection**, not visibility:
78
79 - **Pick a cloud tier while signed in** → the backend hot-swaps to that tier
80 immediately (no restart). `provider::cloud_tier_provider(tier)` builds the
81 client from the stored token.
82 - **Pick a cloud tier while signed out** → a prompt: sign in with `/login`, or
83 create an account at sigit.si. The backend does not change.
84 - **Pick an on-device model** → loads and routes locally, as before.
85
86 The cloud tiers are real picker entries backed by a synthetic
87 `GgufModelConfig` whose id is `sigit-cloud:<tier>` (no `/`, so it never collides
88 with a HuggingFace id and on-device matching code stays inert). `cloud_tier:
89 Option<String>` on `ModelPickerItem` distinguishes them. The tier maps to a wire
90 model id via `provider::tier_to_model` (`fast``onde-fast`, etc.); the UI shows
91 `siGit Code Cloud · Balanced`, never the model id or "Anthropic".
92
93 Cloud tiers are a TUI-picker feature today. The ACP session-config paths use
94 `models::local_picker_items()` (cloud filtered out), because cloud inference over
95 ACP still needs the trait rewire (see Open items).
96
97 ## Account / auth (via sigit.si)
98
99 Account management is exposed as **slash commands** (`/login <email> <password>`,
100 `/logout`, `/whoami`) so it works inside an ACP session (Zed) and the terminal
101 TUI alike, since siGit Code has no separate place to run a CLI verb in those
102 contexts. Both surfaces parse `/`-prefixed input and call the I/O-free core
103 functions in `sigit/src/account.rs` (`authenticate`, `end_session`,
104 `status_line`). Slash input is not echoed to the transcript, so the password
105 does not persist there. `/login` only signs in; it does not change the active
106 model. To use the cloud after signing in, pick a tier in `/models`.
107
108 - **Trust boundary is `sigit.si`.** It holds the confidential smbCloud
109 `app_secret` server-side and returns the client only a **bearer token**. The
110 client must never hold `app_secret` (it is a public/shipped binary, see the
111 `smbcloud-auth` skill's public-client rule).
112 - **Base URL:** `$SIGIT_API_URL`, else `https://sigit.si` (dev:
113 `http://localhost:8088`).
114 - **Endpoints (sigit.si `/api/v1`, token-based):** `POST /api/v1/auth/sign_in`
115 returns an `AccountStatus` (`"NotFound"` | `{"Ready":{"access_token":…}}` |
116 `{"Incomplete":{"status":<u32>}}`); `GET /api/v1/me` returns
117 `{id,email,created_at,updated_at}`; `DELETE /api/v1/auth/sign_out` returns 204.
118 Failures return `ErrorResponse {error_code, message}`. These shapes are a shared
119 contract with the desktop client; keep them in sync (see `smbcloud-auth`). The
120 controllers live at `app/controllers/api/v1/{sessions,me}_controller.rb`.
121 - **Credential store:** `sigit/src/credentials.rs` writes
122 `~/.config/sigit/credentials.toml` (`$SIGIT_CONFIG_DIR` override), mode `0600`,
123 holding `access_token` (+ email for display).
124
125 ## Cloud Sessions (persisted chat) and the web app
126
127 siGit Code Cloud chat conversations are saved as **Cloud Sessions** (short:
128 sessions), so they sync across signed-in surfaces and can be resumed. "Session"
129 means a chat conversation and belongs to this product only; the agent's unit is a
130 "run" (see `sigit-code-cloud-agent`).
131
132 - **Models (sigit-si):** `CloudSession` (UUID primary key, since it appears in a
133 shareable URL) `has_many :cloud_messages`; `append_message!` auto-titles from
134 the first user turn and tracks activity. `CloudMessage` roles are
135 `user`/`assistant`/`system`.
136 - **API:** `Api::V1::CloudSessionsController`, token-scoped to `current_user`, at
137 `path: "sessions"` (kept distinct from the auth `SessionsController`):
138 `GET/POST /api/v1/sessions`, `GET/PATCH/DELETE /api/v1/sessions/:id`, and
139 `POST /api/v1/sessions/:id/messages`.
140 - **Streaming is unchanged:** clients still stream from `/api/v1/chat/completions`;
141 these endpoints only persist the transcript. The client appends the user message
142 on send and the assistant message when the stream finishes.
143 - **Web app:** `sigit-app/apps/code-cloud-web` is the SvelteKit client at
144 **code.sigit.si**. `/cloud` is the chat; `/cloud/sessions` and
145 `/cloud/sessions/<uuid>` are the saved history. Same `/api/v1` surface,
146 same-origin in production. (Root `/` is the on-device dashboard for signed-in
147 users and the marketing landing for signed-out ones.)
148
149 ## Onde Cloud side (the API the cloud tier calls)
150
151 Repo: `onde-cloud` (Rust/axum, OpenAI-compatible).
152
153 - **Endpoints:** `POST /v1/chat/completions`, `GET /v1/models`.
154 - **Routing:** `router.rs` resolves a request `model` to a backend. Neutral public
155 ids (`onde-fast/balanced/large`) map to upstream models; raw `claude-*` ids are
156 accepted as backward-compatible aliases but never advertised. `/v1/models`
157 reports `owned_by: "onde-inference"`: the upstream provider is never disclosed.
158 - **Tool calling:** `anthropic.rs` maps OpenAI function-calling ↔ Anthropic
159 `tool_use`/`tool_result` (assistant `tool_calls``tool_use` blocks; coalesced
160 `tool` messages → one `tool_result` user turn; `parameters``input_schema`;
161 `stop_reason``finish_reason`). This is what makes the cloud tier a real
162 *agent* backend, not just chat.
163 - **Error hygiene:** client-facing errors are neutral ("Onde Cloud upstream error
164 (NNN)"); full upstream detail is logged server-side only. Never echo provider
165 names or upstream error bodies to the client.
166 - **Auth:** validates `app_id:app_secret` against smbCloud (`DEV_MODE=true`
167 bypasses for local dev and accepts any bearer).
168
169 ## Local dev / testing
170
171 ```bash
172 # 1. Onde Cloud API (needs ANTHROPIC_API_KEY in onde-cloud/.env for the cloud tier)
173 cd ~/Repositories/onde-cloud && set -a && . ./.env && set +a && ./target/debug/onde-cloud
174 # → listens on $PORT (e.g. 8090); DEV_MODE=true accepts any bearer
175
176 # 2a. Product path: point siGit Code Cloud at the local API, start sigit
177 SIGIT_CLOUD_URL=http://localhost:8090/v1 SIGIT_API_URL=http://localhost:8088 sigit
178 # then inside the session:
179 # /login <email> <password> # sign in
180 # /models # pick a siGit Code Cloud tier (hot-swaps)
181
182 # 2b. BYO/override path (no login needed): explicit endpoint
183 OPENAI_BASE_URL=http://localhost:8090/v1 OPENAI_API_KEY=dev:dev SIGIT_TIER=fast sigit
184 # (or ~/.config/sigit/providers.toml with active = "<profile>")
185 ```
186
187 `SIGIT_CLOUD_URL` overrides the baked-in cloud endpoint for dev. Watch routing in
188 the server log: `chat: routing app_id=... to upstream provider`. Use the `fast` tier
189 while testing to keep upstream spend low.
190
191 ## Key files
192
193 - `sigit/src/account.rs`: account core (`authenticate`/`end_session`/`status_line`), sigit.si `/api/v1` client. Surfaced as `/login`, `/logout`, `/whoami`.
194 - `sigit/src/credentials.rs`: local token store.
195 - `sigit/src/provider.rs`: backend precedence, tiers, cloud default.
196 - `sigit/src/backend.rs`: `InferenceBackend` trait, `LocalBackend`, `OpenAiBackend`.
197 - `onde-cloud/src/{router,anthropic,chat,types}.rs`: the hosted API + mapping.
198
199 ## Open items / known gaps
200
201 - **Inference token ↔ Onde Cloud auth.** Onde Cloud currently validates
202 `app_id:app_secret`; the product needs it to accept the sigit.si-issued user
203 token (sigit.si fronts/mints inference auth, mirroring the `git_token` exchange
204 in `sigit-app`). Until then, dev relies on `DEV_MODE`.
205 - **`sigit.si/api/v1` auth routes exist** (`sessions`/`me` controllers). The
206 remaining gap is the inference-token link above, not the account endpoints.
207 - **Usage/billing:** `onde-cloud` returns zeroed `usage`; wire Anthropic's
208 returned token counts through for metering.
209 - **Tool-call id fingerprint:** Onde Cloud passes Anthropic's `toolu_…` tool-call
210 ids straight through (OpenAI uses `call_…`). Reversibly rewrite before any
211 external launch so the id format doesn't reveal the backend.
212 - **ACP path:** the Zed/ACP server in `sigit/src/main.rs` still calls `onde`
213 directly; route it through `InferenceBackend` too.
214
215 ## Billing — siGit's own, gating siGit Code Cloud
216
217 siGit Code is local-first and free; **siGit Code Cloud (the Fast/Balanced/Large
218 tiers) requires a paid plan.** This is siGit's *own* Stripe billing, separate from
219 Onde Inference's — siGit pays Onde as a customer; here siGit charges its end users.
220
221 - **Gate:** `Api::V1::ChatCompletionsController#enforce_cloud_entitlement!`
222 `CloudCatalog.cloud_tier?(model)` (the `onde-*`/`claude-*` ids) requires
223 `current_user.entitled_to_cloud?`, else **402**. On-device GGUF ids pass free.
224 - **Entitlement:** `User#entitled_to_cloud?``Subscription#entitled_to_cloud?`
225 (`pro`/`team` && `active`/`trialing`). No subscription = Free = local only.
226 - **Stripe:** `StripeService` (checkout/portal/`construct_event`/`sync_subscription`),
227 `Api::V1::BillingController` (`GET billing`, `POST billing/checkout|portal`),
228 `StripeWebhooksController` (`POST /stripe/webhooks`, signature-verified). Stripe is
229 the source of truth; the `subscriptions` table caches plan + status.
230 - **Allowance:** `enforce_cloud_allowance!` caps cloud requests per billing period
231 (`Subscription::CLOUD_ALLOWANCE` — pro 2000, team 6000; tunable). `CloudUsage`
232 counts per `(user, billing_period_key)`, so it resets each cycle; over the cap →
233 **429**. `GET /api/v1/billing` returns `cloud_requests_used` + `cloud_allowance`.
234 - **Plans:** product `siGit Code` in **siGit's own Stripe account**
235 (`acct_1TlaO9LwK8mGvn31`, Splitfire AB); prices `sigit_code_pro_monthly` ($20/mo)
236 and `sigit_code_team_monthly` ($40/mo), resolved by lookup key.
237 - **Env:** `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET` (siGit account). Checkout is
238 **web-initiated** (desktop/CLI open the returned URL) to avoid the App Store cut.
239 - **Remaining:** a web/desktop billing UI; metering by tokens (the `usage` Onde
240 Cloud returns) instead of request count, if finer cost control is needed.
241
242 ## Common mistakes
243
244 - Making BYO-endpoint (env / `providers.toml`) the default instead of the
245 `sigit login` product experience.
246 - Exposing a model id, base URL, or provider name in product UI/errors.
247 - Treating the account access token as a model API key, or shipping smbCloud
248 `app_secret` in the `sigit` binary (public-client violation).
249 - Letting a persisted local model name show in the title while routing to the
250 cloud (guard with `InferenceBackend::is_remote()`).
251 - Mixing the names: `siGit Code` (local agent), `siGit Code Cloud` (hosted chat),
252 `siGit Code Cloud Agent` (autonomous, task -> PR), `sigit` (CLI), `smbCloud`
253 (company), `Onde Cloud` (infrastructure).
254 - Calling a chat conversation a "run", or the agent's work a "session". Chat has
255 **sessions**; the agent has **runs**.