@soba-so/react 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../connect-ui/src/client.ts", "../../connect-ui/src/ComputeStatus.tsx", "../../connect-ui/src/ConnectPanel.tsx", "../../connect-ui/src/runtimes.ts", "../src/context.ts", "../src/ComputeStatus.tsx", "../src/internal.ts", "../src/ConnectCompute.tsx", "../src/events.ts", "../src/SobaProvider.tsx", "../src/session.ts", "../src/styles.ts"],
4
+ "sourcesContent": ["/**\n * The connect API, as a plain object with no framework in it.\n *\n * Framework-free on purpose: the same calls are made by the approval page in\n * Next and by the desktop app's window, and later by `<ConnectCompute />` inside\n * a customer's own React tree. One implementation, three consumers.\n */\n\nexport type MachineState = \"online\" | \"busy\" | \"offline\"\n\nexport interface Runtime {\n id: string\n display_name: string\n version: string | null\n models: string[]\n cost_class: \"user-hardware\" | \"user-subscription\" | \"open-weights\" | \"frontier\"\n supports_tools: boolean\n /** TRI-STATE all the way to the screen. See `authLabel`. */\n authenticated: boolean | null\n auth_hint: string | null\n /** Appeared on a machine that was already paired, so it waits to be let in.\n * What arrived with the pairing was approved by the pairing. */\n quarantined?: boolean\n first_seen_at?: string\n}\n\nexport interface Machine {\n id: string\n label: string | null\n platform: string | null\n arch: string | null\n version: string | null\n state: MachineState\n active: number\n capacity: number\n always_on: boolean\n last_seen_at: string | null\n runtimes: Runtime[]\n}\n\nexport interface ConnectContext {\n app: { name: string }\n publishable_key: string | null\n command: string | null\n}\n\nexport interface PendingCode {\n user_code: string\n client: string | null\n hostname: string | null\n platform: string | null\n arch: string | null\n labels: string[]\n requested_at: string\n expires_at: string\n approved: boolean\n denied: boolean\n}\n\nexport class ConnectError extends Error {\n readonly status: number\n constructor(message: string, status: number) {\n super(message)\n this.name = \"ConnectError\"\n this.status = status\n }\n}\n\nexport interface ConnectClientOptions {\n /** The `est_` token. Held in memory only: it came out of a URL fragment and\n * putting it in storage would outlive the ten minutes it is good for. */\n session: string\n /** Defaults to the page's own origin, which is what the hosted page wants.\n * The desktop app passes soba.so explicitly. */\n baseUrl?: string\n fetchImpl?: typeof fetch\n}\n\nexport class ConnectClient {\n private readonly session: string\n private readonly baseUrl: string\n private readonly doFetch: typeof fetch\n\n constructor(opts: ConnectClientOptions) {\n this.session = opts.session\n this.baseUrl = (opts.baseUrl ?? \"\").replace(/\\/+$/, \"\")\n this.doFetch = opts.fetchImpl ?? fetch.bind(globalThis)\n }\n\n context(): Promise<ConnectContext> {\n return this.request<ConnectContext>(\"GET\", \"/v1/connect\")\n }\n\n async machines(): Promise<Machine[]> {\n const body = await this.request<{ machines: Machine[] }>(\"GET\", \"/v1/machines\")\n return body.machines\n }\n\n lookup(code: string): Promise<PendingCode> {\n return this.request<PendingCode>(\"GET\", `/v1/device/lookup?code=${encodeURIComponent(code)}`)\n }\n\n approve(code: string): Promise<{ ok: true }> {\n return this.request(\"POST\", \"/v1/device/approve\", { user_code: code })\n }\n\n deny(code: string): Promise<{ ok: true }> {\n return this.request(\"POST\", \"/v1/device/deny\", { user_code: code })\n }\n\n /** Let a runtime that appeared after pairing start serving. It can only ever\n * widen this machine's own advertisement, never its grant. */\n approveRuntime(machineId: string, runtimeId: string): Promise<{ ok: true }> {\n return this.request(\"POST\", \"/v1/runtimes/approve\", {\n machine_id: machineId,\n runtime_id: runtimeId,\n })\n }\n\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n const res = await this.doFetch(`${this.baseUrl}${path}`, {\n method,\n headers: {\n // In a header, never a query string. The session came out of a URL\n // fragment for the same reason: a query string reaches access logs,\n // proxy logs and the `Referer` of every link on the page.\n authorization: `Bearer ${this.session}`,\n ...(body ? { \"content-type\": \"application/json\" } : {}),\n },\n ...(body ? { body: JSON.stringify(body) } : {}),\n })\n let payload: Record<string, unknown> = {}\n try {\n payload = (await res.json()) as Record<string, unknown>\n } catch {\n /* an empty body is normal on some errors */\n }\n if (!res.ok) {\n const message =\n typeof payload.error === \"string\" ? payload.error : `Request failed (${res.status})`\n throw new ConnectError(message, res.status)\n }\n return payload as T\n }\n}\n\n/**\n * The three states of `authenticated`, rendered as three.\n *\n * `false` is a positive finding that the runtime is signed OUT, and it is the\n * one a person can fix. `null` means the probe could not tell, and showing that\n * as \"signed out\" would tell someone their working setup is broken.\n */\nexport function authLabel(runtime: Runtime): { tone: \"ok\" | \"warn\" | \"unknown\"; text: string } {\n if (runtime.authenticated === true) return { tone: \"ok\", text: \"signed in\" }\n if (runtime.authenticated === false) {\n return { tone: \"warn\", text: runtime.auth_hint ?? \"signed out on that machine\" }\n }\n return { tone: \"unknown\", text: \"could not tell\" }\n}\n\nexport function stateLabel(machine: Machine): { tone: \"ok\" | \"busy\" | \"off\"; text: string } {\n if (machine.state === \"busy\")\n return { tone: \"busy\", text: `serving ${machine.active} of ${machine.capacity}` }\n if (machine.state === \"online\") return { tone: \"ok\", text: \"connected\" }\n return { tone: \"off\", text: \"asleep or offline\" }\n}\n", "import type { Machine } from \"./client.ts\"\nimport { authLabel, stateLabel } from \"./client.ts\"\n\n/**\n * The component with no equivalent in an auth product, and the one that matters\n * most: when the compute is someone's own machine, the state of that machine is\n * user-facing. Asleep, offline, signed out of its CLI, or serving a run right\n * now are all things the person in front of it is the only one who can fix.\n */\nexport function ComputeStatus({\n machines,\n onApproveRuntime,\n}: {\n machines: Machine[]\n /** Given when the caller can act on a quarantined runtime. Without it the\n * state is still shown, because a person who cannot see why their new CLI is\n * idle has no way to work it out. */\n onApproveRuntime?: (machineId: string, runtimeId: string) => void\n}) {\n if (!machines.length) {\n return <p className=\"sc-note\">Nothing connected yet.</p>\n }\n return (\n <div>\n {machines.map((m) => {\n const state = stateLabel(m)\n return (\n <div className=\"sc-machine\" key={m.id}>\n <div className=\"sc-row-between\">\n <div className=\"sc-row\">\n <span className={`sc-dot sc-dot-${state.tone}`} aria-hidden=\"true\" />\n <strong>{m.label ?? \"This machine\"}</strong>\n </div>\n <span className=\"sc-note\">{state.text}</span>\n </div>\n <p className=\"sc-note\">\n {[m.platform, m.arch, m.version && `worker ${m.version}`, m.always_on && \"always on\"]\n .filter(Boolean)\n .join(\" \u00B7 \")}\n </p>\n <ul className=\"sc-runtimes\">\n {m.runtimes.map((r) => {\n const auth = authLabel(r)\n if (r.quarantined) {\n return (\n <li className=\"sc-row-between\" key={r.id}>\n <span>{r.display_name}</span>\n {onApproveRuntime ? (\n <button\n type=\"button\"\n className=\"sc-btn\"\n onClick={() => onApproveRuntime(m.id, r.id)}\n >\n New here. Let it serve\n </button>\n ) : (\n <span className=\"sc-tone-warn\">new, not serving yet</span>\n )}\n </li>\n )\n }\n return (\n <li className=\"sc-row-between\" key={r.id}>\n <span>{r.display_name}</span>\n <span className={`sc-tone-${auth.tone}`}>{auth.text}</span>\n </li>\n )\n })}\n {!m.runtimes.length && (\n <li className=\"sc-note\">\n No runtime advertised. Install Claude Code, Codex or Ollama on that machine.\n </li>\n )}\n </ul>\n </div>\n )\n })}\n </div>\n )\n}\n", "import { useCallback, useEffect, useMemo, useRef, useState } from \"react\"\nimport { ComputeStatus } from \"./ComputeStatus.tsx\"\nimport type { ConnectClient, ConnectContext, Machine, PendingCode } from \"./client.ts\"\nimport { RUNTIME_CHOICES, type RuntimeChoice } from \"./runtimes.ts\"\n\n/**\n * THE CONNECT STEP, and the two things it refuses to fake.\n *\n * It does not stop at \"approved\". A pairing that never dials the gateway looks\n * exactly like a successful one right up until the first run fails, so the panel\n * waits for the machine to actually appear before it says connected. Detected,\n * not asserted.\n *\n * And it does not skip the code. A pending device code has no owner yet, so a\n * page that simply approved \"whatever is pending for this app\" would let\n * whoever loaded it first attach someone else's laptop to their own account.\n * Reading the code off the terminal that produced it is the consent step, which\n * is exactly what RFC 8628 uses it for. What is never displayed or pasted is the\n * TOKEN, and that remains true.\n */\n\ntype Phase =\n | \"loading\"\n | \"manage\"\n | \"choose\"\n | \"code\"\n | \"confirm\"\n | \"waiting\"\n | \"connected\"\n | \"denied\"\n\nconst POLL_MS = 2_000\n/** Nobody is waiting on this one, so it is gentler on the tab and the API. */\nconst STATUS_POLL_MS = 10_000\n\nexport interface ConnectPanelProps {\n client: ConnectClient\n /** From `?code=` on `verification_uri_complete`, when they came from a\n * terminal that already has one. */\n initialCode?: string\n onConnected?: (machines: Machine[]) => void\n /**\n * Whether this panel owns the steady state as well as the connect step.\n *\n * Left true it opens on what the person already has, which is what someone\n * arriving for the second time came to see. Set it false when the surrounding\n * app places `<ComputeStatus />` itself, so the machines are not told twice.\n */\n showStatus?: boolean\n}\n\nexport function ConnectPanel({\n client,\n initialCode,\n onConnected,\n showStatus = true,\n}: ConnectPanelProps) {\n const [phase, setPhase] = useState<Phase>(\"loading\")\n const [context, setContext] = useState<ConnectContext | null>(null)\n const [machines, setMachines] = useState<Machine[]>([])\n const [choice, setChoice] = useState<RuntimeChoice | null>(null)\n const [code, setCode] = useState(initialCode ?? \"\")\n const [pending, setPending] = useState<PendingCode | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n\n /** Machines that were already connected when we started, so \"it worked\" means\n * a NEW one arrived rather than an old one still being there. */\n const baseline = useRef<Set<string> | null>(null)\n\n useEffect(() => {\n let live = true\n Promise.all([client.context(), client.machines()])\n .then(([ctx, list]) => {\n if (!live) return\n setContext(ctx)\n setMachines(list)\n baseline.current = new Set(list.map((m) => m.id))\n /**\n * Three openings, ordered by what the person actually came to do. A\n * code in hand means they came from their own terminal, so skip to the\n * thing they are here to answer. Failing that, someone who has already\n * paired is here to CHECK something, not to be sold the idea a second\n * time: lead with what they have and keep connecting one click away.\n */\n if (initialCode) setPhase(\"code\")\n else if (showStatus && list.length > 0) setPhase(\"manage\")\n else setPhase(\"choose\")\n })\n .catch((err: Error) => {\n if (!live) return\n setError(err.message)\n setPhase(\"choose\")\n })\n return () => {\n live = false\n }\n }, [client, initialCode, showStatus])\n\n /**\n * The steady state has to be live, or it is worse than nothing.\n *\n * A machine sleeps, drops off wifi, or gets signed out of its CLI without\n * telling anyone, and the person reading this page is the only one who can\n * fix any of it. A card that still says \"connected\" ten minutes after the lid\n * closed is the exact failure this view exists to catch. Slower than the\n * pairing poll below: nobody is waiting on it, they are just looking.\n */\n useEffect(() => {\n if (phase !== \"manage\") return\n let live = true\n const timer = setInterval(async () => {\n try {\n const list = await client.machines()\n if (live) setMachines(list)\n } catch {\n // Keep showing the last known state. A failed refresh is not evidence\n // that anything changed, and blanking the view would say it was.\n }\n }, STATUS_POLL_MS)\n return () => {\n live = false\n clearInterval(timer)\n }\n }, [phase, client])\n\n /** Live success detection: poll until a machine that was not here before is. */\n useEffect(() => {\n if (phase !== \"waiting\") return\n let live = true\n const timer = setInterval(async () => {\n try {\n const list = await client.machines()\n if (!live) return\n setMachines(list)\n const fresh = list.filter((m) => !baseline.current?.has(m.id))\n const arrived = fresh.find((m) => m.state !== \"offline\") ?? fresh[0]\n if (arrived) {\n setPhase(\"connected\")\n onConnected?.(list)\n }\n } catch {\n // A failed poll while waiting is not worth surfacing: the next one is\n // two seconds away and the person can do nothing about it either way.\n }\n }, POLL_MS)\n return () => {\n live = false\n clearInterval(timer)\n }\n }, [phase, client, onConnected])\n\n const lookup = useCallback(async () => {\n setError(null)\n setBusy(true)\n try {\n const found = await client.lookup(code)\n setPending(found)\n setPhase(\"confirm\")\n } catch (err) {\n setError((err as Error).message)\n } finally {\n setBusy(false)\n }\n }, [client, code])\n\n const approve = useCallback(async () => {\n setError(null)\n setBusy(true)\n try {\n await client.approve(code)\n setPhase(\"waiting\")\n } catch (err) {\n setError((err as Error).message)\n } finally {\n setBusy(false)\n }\n }, [client, code])\n\n const deny = useCallback(async () => {\n setBusy(true)\n try {\n await client.deny(code)\n setPhase(\"denied\")\n } catch (err) {\n setError((err as Error).message)\n } finally {\n setBusy(false)\n }\n }, [client, code])\n\n const command = useMemo(() => context?.command ?? null, [context])\n\n const approveRuntime = useCallback(\n async (machineId: string, runtimeId: string) => {\n try {\n await client.approveRuntime(machineId, runtimeId)\n setMachines(await client.machines())\n } catch (err) {\n setError((err as Error).message)\n }\n },\n [client],\n )\n\n if (phase === \"loading\") {\n return (\n <div className=\"soba-connect\">\n <div className=\"sc-card\">\n <p className=\"sc-note\">Loading\u2026</p>\n </div>\n </div>\n )\n }\n\n return (\n <div className=\"soba-connect\">\n {phase === \"manage\" && (\n <div className=\"sc-card\">\n <h2 className=\"sc-title\">Your compute</h2>\n <p className=\"sc-sub\">\n {context?.app.name ?? \"This app\"} runs on these machines, while they are awake.\n </p>\n <ComputeStatus machines={machines} onApproveRuntime={approveRuntime} />\n {error && <p className=\"sc-error\">{error}</p>}\n <button\n type=\"button\"\n className=\"sc-btn\"\n style={{ marginTop: 16 }}\n onClick={() => setPhase(\"choose\")}\n >\n Connect another machine\n </button>\n </div>\n )}\n\n {phase === \"choose\" && (\n <div className=\"sc-card\">\n <h2 className=\"sc-title\">Use the AI you already pay for</h2>\n <p className=\"sc-sub\">\n {context?.app.name ?? \"This app\"} can run on your machine instead of selling you\n credits. Pick what you already have.\n </p>\n <div className=\"sc-choices\">\n {RUNTIME_CHOICES.map((c) => (\n <button\n type=\"button\"\n key={c.id}\n className=\"sc-choice\"\n aria-pressed={choice?.id === c.id}\n onClick={() => setChoice(c)}\n >\n <span className=\"sc-choice-title\">{c.title}</span>\n <br />\n <span className=\"sc-choice-meta\">\n {c.cost} \u00B7 {c.prerequisite}\n </span>\n </button>\n ))}\n </div>\n\n {choice && (\n <div className=\"sc-stack\" style={{ marginTop: 16 }}>\n <p className=\"sc-note\">{choice.hint}</p>\n {command ? (\n <>\n <p className=\"sc-label\">Run this on the machine you want to use</p>\n <code className=\"sc-code\">{command}</code>\n <p className=\"sc-note\">\n It will show you a short code. Nothing is pasted back: the credential is written\n straight to that machine.\n </p>\n <button\n type=\"button\"\n className=\"sc-btn sc-btn-primary\"\n onClick={() => setPhase(\"code\")}\n >\n I have a code\n </button>\n </>\n ) : (\n <p className=\"sc-error\">\n This app has no publishable key yet, so there is nothing to pair against.\n </p>\n )}\n </div>\n )}\n </div>\n )}\n\n {phase === \"code\" && (\n <div className=\"sc-card\">\n <h2 className=\"sc-title\">Enter the code from your terminal</h2>\n <p className=\"sc-sub\">Eight characters, shown after you ran the command.</p>\n <form\n className=\"sc-stack\"\n onSubmit={(e) => {\n e.preventDefault()\n void lookup()\n }}\n >\n <input\n className=\"sc-input\"\n value={code}\n onChange={(e) => setCode(e.target.value)}\n placeholder=\"ACDE-F234\"\n autoComplete=\"off\"\n // biome-ignore lint/a11y/noAutofocus: the field is the only thing on this step\n autoFocus\n aria-label=\"Pairing code\"\n />\n {error && <p className=\"sc-error\">{error}</p>}\n <button\n type=\"submit\"\n className=\"sc-btn sc-btn-primary\"\n disabled={busy || code.length < 8}\n >\n {busy ? \"Checking\u2026\" : \"Continue\"}\n </button>\n </form>\n </div>\n )}\n\n {phase === \"confirm\" && pending && (\n <div className=\"sc-card\">\n <h2 className=\"sc-title\">Attach this machine to your account?</h2>\n <p className=\"sc-sub\">\n Once you approve, {context?.app.name ?? \"this app\"} may send your runs to it. It can\n never see the account or the keys on it.\n </p>\n <ul className=\"sc-facts\">\n <Fact k=\"Code\" v={pending.user_code} />\n <Fact k=\"Machine\" v={pending.hostname ?? \"not stated\"} />\n <Fact\n k=\"System\"\n v={[pending.platform, pending.arch].filter(Boolean).join(\" / \") || \"not stated\"}\n />\n <Fact k=\"Asked by\" v={pending.client ?? \"not stated\"} />\n {pending.labels.length > 0 && <Fact k=\"Labels\" v={pending.labels.join(\", \")} />}\n </ul>\n <p className=\"sc-note\" style={{ marginTop: 12 }}>\n Every line above is what that machine said about itself. If none of it looks like a\n computer of yours, say no.\n </p>\n {error && <p className=\"sc-error\">{error}</p>}\n <div className=\"sc-row\" style={{ marginTop: 16 }}>\n <button\n type=\"button\"\n className=\"sc-btn sc-btn-primary\"\n onClick={() => void approve()}\n disabled={busy}\n >\n Approve\n </button>\n <button type=\"button\" className=\"sc-btn\" onClick={() => void deny()} disabled={busy}>\n No, deny it\n </button>\n </div>\n </div>\n )}\n\n {phase === \"waiting\" && (\n <div className=\"sc-card\">\n <h2 className=\"sc-title\">Approved. Waiting for it to connect\u2026</h2>\n <p className=\"sc-sub\">\n The worker is starting on that machine. This page will change on its own the moment it\n arrives.\n </p>\n <p className=\"sc-note\">\n If nothing happens, look at the terminal you ran the command in: it says what it is\n doing.\n </p>\n </div>\n )}\n\n {phase === \"connected\" && (\n <div className=\"sc-card\">\n <h2 className=\"sc-title\">Connected</h2>\n <p className=\"sc-sub\">\n {context?.app.name ?? \"This app\"} will use this machine from now on, while it is awake.\n </p>\n {showStatus && (\n <>\n <ComputeStatus machines={machines} onApproveRuntime={approveRuntime} />\n <button\n type=\"button\"\n className=\"sc-btn\"\n style={{ marginTop: 16 }}\n onClick={() => setPhase(\"manage\")}\n >\n Done\n </button>\n </>\n )}\n </div>\n )}\n\n {phase === \"denied\" && (\n <div className=\"sc-card\">\n <h2 className=\"sc-title\">Declined</h2>\n <p className=\"sc-sub\">\n That machine was not attached to your account, and the terminal has been told so.\n </p>\n </div>\n )}\n </div>\n )\n}\n\nfunction Fact({ k, v }: { k: string; v: string }) {\n return (\n <li className=\"sc-fact\">\n <span className=\"sc-fact-key\">{k}</span>\n <span>{v}</span>\n </li>\n )\n}\n", "/**\n * The chooser, and why there is one.\n *\n * One generic command was always the wrong shape: the three ways to bring your\n * own compute have genuinely different prerequisites, and the pairing command\n * cannot tell you that you are signed out of a CLI you have not installed. So\n * the choice comes first and it sets expectations, which is also how the\n * successful onboarding flows in this category work \u2014 pick your client, get the\n * one artifact for that choice, watch it connect.\n *\n * The command itself is the same for all three, and saying so is better than\n * implying three integrations exist. What differs is what has to be true on the\n * machine before it will serve anything.\n */\n\nexport interface RuntimeChoice {\n id: \"claude\" | \"chatgpt\" | \"local\"\n title: string\n /** What it costs the person, which is the whole pitch. */\n cost: string\n /** What must already be true on their machine. */\n prerequisite: string\n /** How to make it true, when it is not. */\n hint: string\n /** Runtime ids this choice expects to see advertised in `hello`. */\n expects: string[]\n}\n\nexport const RUNTIME_CHOICES: RuntimeChoice[] = [\n {\n id: \"claude\",\n title: \"The Claude plan you already pay for\",\n cost: \"Nothing beyond the plan\",\n prerequisite: \"Claude Code installed, and signed in\",\n hint: \"Install it from claude.ai/code, then run `claude` once and sign in.\",\n expects: [\"claude-code\"],\n },\n {\n id: \"chatgpt\",\n title: \"The ChatGPT plan you already pay for\",\n cost: \"Nothing beyond the plan\",\n prerequisite: \"Codex installed, and signed in\",\n hint: \"Install Codex, then run `codex` once and sign in.\",\n expects: [\"codex\"],\n },\n {\n id: \"local\",\n title: \"A model on your own machine\",\n cost: \"Free. You bought the hardware\",\n prerequisite: \"Ollama running, with a model pulled\",\n hint: \"Install Ollama, then `ollama pull` a model before pairing.\",\n expects: [\"ollama\", \"openai-api\"],\n },\n]\n", "import { createContext, useContext } from \"react\"\nimport type { AgentEvent, ConnectApi, Machine, Message } from \"./types.js\"\n\n/** What your endpoint is asked for. Everything but `signal` is forwarded to it\n * verbatim, because the run's shape is `soba.run()`'s and yours to widen. */\nexport interface RunRequest {\n prompt?: string\n messages?: Message[]\n model?: string\n system?: string\n signal?: AbortSignal\n [key: string]: unknown\n}\n\nexport interface ComputeState {\n /** Every machine this person has paired, in whatever state it is in. */\n machines: Machine[]\n /** They have paired something. It may still be asleep. */\n connected: boolean\n /** Something of theirs could serve a run right now. */\n online: boolean\n loading: boolean\n error: Error | null\n refresh(): Promise<void>\n}\n\nexport interface ConnectState {\n /** Your app's name, as the person connecting will see it. */\n app: { name: string } | null\n /** The exact command to run on the machine being paired. */\n command: string | null\n publishableKey: string | null\n loading: boolean\n error: Error | null\n}\n\nexport interface SobaContextValue {\n /** The `est_` session, once there is one. */\n session: string | null\n /** The framework-free client, for anything the hook does not cover. */\n client: ConnectApi | null\n error: Error | null\n compute: ComputeState\n connect: ConnectState\n /** Start a run through YOUR endpoint and read its events. Throws when the\n * provider was given no `runEndpoint`, since a browser cannot start one. */\n run(request: RunRequest): Promise<AsyncIterable<AgentEvent>>\n}\n\nexport const SobaContext = createContext<SobaContextValue | null>(null)\n\nexport function useSoba(): SobaContextValue {\n const value = useContext(SobaContext)\n if (!value) {\n throw new Error(\"useSoba() must be used inside a <SobaProvider>.\")\n }\n return value\n}\n", "import { ComputeStatus as Machines } from \"@soba-so/connect-ui\"\nimport { useSoba } from \"./context.js\"\n\nexport interface ComputeStatusProps {\n className?: string\n}\n\n/**\n * What this person has connected, and what it is doing right now.\n *\n * The component with no equivalent in an auth product. When the compute is\n * someone's own laptop, the state of that laptop is user-facing: asleep,\n * offline, signed out of its CLI or serving a run are all things only the\n * person in front of it can do anything about, so all of them are shown.\n *\n * It reads the provider's polling rather than starting its own, so three of\n * these on one page is still one request.\n */\nexport function ComputeStatus({ className }: ComputeStatusProps) {\n const { compute } = useSoba()\n const classes = className ? `soba-connect ${className}` : \"soba-connect\"\n\n if (compute.loading && !compute.machines.length) {\n return (\n <div className={classes}>\n <p className=\"sc-note\">Loading\u2026</p>\n </div>\n )\n }\n if (compute.error && !compute.machines.length) {\n return (\n <div className={classes}>\n <p className=\"sc-error\">{compute.error.message}</p>\n </div>\n )\n }\n return (\n <div className={classes}>\n <Machines machines={compute.machines} />\n </div>\n )\n}\n", "import type { ConnectClient } from \"@soba-so/connect-ui\"\nimport { createContext, useContext } from \"react\"\n\n/**\n * The real client, held apart from the public context.\n *\n * `ConnectPanel` takes the CLASS, which has private fields and so is nominal:\n * a structural stand-in will not do. Nothing here is exported from the package\n * entry, so no published declaration ever names @soba-so/connect-ui.\n */\nexport const ClientContext = createContext<ConnectClient | null>(null)\n\nexport function useConnectClient(): ConnectClient | null {\n return useContext(ClientContext)\n}\n", "import { ConnectPanel } from \"@soba-so/connect-ui\"\nimport { useSoba } from \"./context.js\"\nimport { useConnectClient } from \"./internal.js\"\nimport type { Machine } from \"./types.js\"\n\nexport interface ConnectComputeProps {\n /** From `?code=` when they arrived from a terminal that already has one. */\n initialCode?: string\n onConnected?: (machines: Machine[]) => void\n className?: string\n /** False when you place `<ComputeStatus />` yourself, so the machines are not\n * told twice. Left true the panel opens on what this person already has. */\n showStatus?: boolean\n}\n\n/**\n * The connect step, and the component that carries the real friction.\n *\n * It does not stop at \"approved\": a pairing that never dials the gateway looks\n * exactly like a successful one right up until the first run fails, so it waits\n * for the machine to actually appear. Detected, not asserted.\n *\n * This is the same panel the hosted page uses. The only difference is where the\n * session comes from, which is what <SobaProvider> is for.\n */\nexport function ConnectCompute({\n initialCode,\n onConnected,\n className,\n showStatus,\n}: ConnectComputeProps) {\n const { error } = useSoba()\n const client = useConnectClient()\n\n if (error) {\n return (\n <div className={wrapper(className)}>\n <div className=\"sc-card\">\n <p className=\"sc-error\">{error.message}</p>\n </div>\n </div>\n )\n }\n if (!client) {\n return (\n <div className={wrapper(className)}>\n <div className=\"sc-card\">\n <p className=\"sc-note\">Loading\u2026</p>\n </div>\n </div>\n )\n }\n return (\n <div className={className}>\n <ConnectPanel\n client={client}\n {...(initialCode ? { initialCode } : {})}\n {...(onConnected ? { onConnected } : {})}\n {...(showStatus === undefined ? {} : { showStatus })}\n />\n </div>\n )\n}\n\n/** Every state needs the scoping class, since that is where the tokens live. */\nfunction wrapper(className: string | undefined): string {\n return className ? `soba-connect ${className}` : \"soba-connect\"\n}\n", "import type { AgentEvent, AgentEventType } from \"./types.js\"\n\n/**\n * READING A RUN IN A BROWSER.\n *\n * A run is started by YOUR server, because starting one takes the app's secret\n * key. What reaches the browser is the stream that server forwards, and the only\n * thing on it is the five frozen event types: a tool call never arrives here,\n * because your server already executed it.\n *\n * That is why this is not `@soba-so/sdk`'s parser. The SDK's is bigger on purpose,\n * since it also answers tool calls and approvals. Here, an event is all there is.\n */\n\nconst EVENT_TYPES = new Set<string>([\"delta\", \"thinking\", \"status\", \"done\", \"error\"])\n\nexport function parseEvent(raw: string): AgentEvent | null {\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return null\n }\n if (!parsed || typeof parsed !== \"object\") return null\n const frame = parsed as Record<string, unknown>\n // Both the bare event and the `{type:\"event\", event}` envelope, for the same\n // reason the SDK takes both: which one you get depends on how far up the chain\n // your server forwards from, and that should not be a breaking difference.\n const body = frame.type === \"event\" ? frame.event : frame\n if (!body || typeof body !== \"object\") return null\n const o = body as Record<string, unknown>\n if (typeof o.type !== \"string\" || !EVENT_TYPES.has(o.type)) return null\n\n const event: AgentEvent = { type: o.type as AgentEventType }\n if (typeof o.text === \"string\") event.text = o.text\n if (typeof o.message === \"string\") event.message = o.message\n if (typeof o.tool === \"string\") event.tool = o.tool\n if (typeof o.tier === \"string\") event.tier = o.tier as AgentEvent[\"tier\"]\n if (o.usage && typeof o.usage === \"object\") event.usage = o.usage as AgentEvent[\"usage\"]\n return event\n}\n\n/** Server-sent events off a response body. Also reads newline-delimited JSON,\n * since a hand-written proxy route usually forwards lines rather than frames. */\nexport async function* readEvents(response: Response): AsyncGenerator<AgentEvent> {\n const body = response.body\n if (!body) throw new Error(\"That run response had no body to stream.\")\n const ndjson = (response.headers.get(\"content-type\") ?? \"\").includes(\"ndjson\")\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffer = \"\"\n let data: string[] = []\n\n try {\n for (;;) {\n const chunk = await reader.read()\n if (chunk.done) break\n buffer += decoder.decode(chunk.value, { stream: true })\n for (;;) {\n const nl = buffer.indexOf(\"\\n\")\n if (nl === -1) break\n const line = stripCr(buffer.slice(0, nl))\n buffer = buffer.slice(nl + 1)\n if (ndjson) {\n const event = line.trim() ? parseEvent(line) : null\n if (event) yield event\n continue\n }\n if (line === \"\") {\n const payload = data.join(\"\\n\")\n data = []\n if (payload === \"[DONE]\") return\n const event = payload ? parseEvent(payload) : null\n if (event) yield event\n continue\n }\n collect(line, data)\n }\n }\n // A last frame that never got its blank line, and a last line that never\n // got its newline, both still count.\n if (!ndjson && buffer) collect(stripCr(buffer), data)\n const tail = ndjson ? buffer.trim() : data.join(\"\\n\")\n if (tail && tail !== \"[DONE]\") {\n const event = parseEvent(tail)\n if (event) yield event\n }\n } finally {\n reader.cancel().catch(() => {})\n }\n}\n\nfunction collect(line: string, data: string[]): void {\n if (!line || line.startsWith(\":\")) return\n const colon = line.indexOf(\":\")\n if (colon === -1 || line.slice(0, colon) !== \"data\") return\n const value = line.slice(colon + 1)\n data.push(value.startsWith(\" \") ? value.slice(1) : value)\n}\n\nfunction stripCr(line: string): string {\n return line.endsWith(\"\\r\") ? line.slice(0, -1) : line\n}\n\n/** Everything a run said, for a caller that only wants the answer. */\nexport async function collectText(events: AsyncIterable<AgentEvent>): Promise<string> {\n let text = \"\"\n for await (const event of events) {\n if (event.type === \"delta\" && event.text) text += event.text\n if (event.type === \"error\") throw new Error(event.message ?? \"The run ended with an error.\")\n }\n return text\n}\n", "import { ConnectClient } from \"@soba-so/connect-ui\"\nimport { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from \"react\"\nimport { type RunRequest, SobaContext, type SobaContextValue } from \"./context.js\"\nimport { readEvents } from \"./events.js\"\nimport { ClientContext } from \"./internal.js\"\nimport { createSessionFetch, SessionHolder } from \"./session.js\"\nimport { injectStyles } from \"./styles.js\"\nimport type { AgentEvent, ConnectContext, Machine } from \"./types.js\"\n\n/** Where the connect API lives. Not the run endpoint: a browser never calls that. */\nconst DEFAULT_BASE_URL = \"https://soba.so\"\n\n/** How often to re-read what this person has connected. The API allows one call\n * every two seconds per session; a settings page does not need that, and a\n * pairing in progress has its own faster poll inside the panel. */\nconst DEFAULT_POLL_MS = 15_000\n\nexport interface SobaProviderProps {\n children: ReactNode\n /**\n * An `est_` session you minted on your server. Cannot be refreshed, so prefer\n * `sessionEndpoint` for anything a person leaves open.\n */\n session?: string\n /**\n * Your own route. The provider POSTs `{ user }` to it with your cookies, and\n * expects `{ session, expires_in }` back. Four lines on your server:\n *\n * ```ts\n * const res = await fetch(\"https://soba.so/v1/end_users/session\", {\n * method: \"POST\",\n * headers: { authorization: `Bearer ${process.env.SOBA_KEY}` },\n * body: JSON.stringify({ user: session.user.id }),\n * })\n * return Response.json(await res.json())\n * ```\n */\n sessionEndpoint?: string\n /** The same id you pass as `user` on a run. It is what makes the person whose\n * machine this is and the person a run is attributed to the same row. */\n user?: string\n /** Your route that starts a run with `@soba-so/sdk` and forwards the stream. */\n runEndpoint?: string\n baseUrl?: string\n pollMs?: number\n /** The stylesheet injects itself once. Turn it off to ship your own. */\n injectStyles?: boolean\n fetchImpl?: typeof fetch\n onError?: (error: Error) => void\n}\n\n/**\n * Holds the one thing everything else needs: a live session for one of your\n * users. Everything below it, component or hook, reads from here.\n */\nexport function SobaProvider(props: SobaProviderProps) {\n const {\n children,\n session: given,\n sessionEndpoint,\n user,\n runEndpoint,\n baseUrl = DEFAULT_BASE_URL,\n pollMs = DEFAULT_POLL_MS,\n injectStyles: withStyles = true,\n fetchImpl,\n onError,\n } = props\n\n const [session, setSession] = useState<string | null>(given ?? null)\n const [error, setError] = useState<Error | null>(null)\n const [context, setContext] = useState<ConnectContext | null>(null)\n const [contextError, setContextError] = useState<Error | null>(null)\n const [contextLoading, setContextLoading] = useState(true)\n const [machines, setMachines] = useState<Machine[]>([])\n const [machinesError, setMachinesError] = useState<Error | null>(null)\n const [machinesLoading, setMachinesLoading] = useState(true)\n\n const report = useRef(onError)\n report.current = onError\n const fail = useCallback((err: Error) => {\n setError(err)\n report.current?.(err)\n }, [])\n\n useEffect(() => {\n if (withStyles) injectStyles()\n }, [withStyles])\n\n // One holder per source. Rebuilding it would mint a second session for the\n // same person, which is wasteful and makes the ten-minute clock unreadable.\n const holder = useMemo(\n () =>\n new SessionHolder({\n ...(given ? { token: given } : {}),\n ...(sessionEndpoint ? { endpoint: sessionEndpoint } : {}),\n ...(user ? { user } : {}),\n ...(fetchImpl ? { fetchImpl } : {}),\n }),\n [given, sessionEndpoint, user, fetchImpl],\n )\n\n useEffect(() => {\n let live = true\n const unsubscribe = holder.subscribe(() => {\n if (live) setSession(holder.token)\n })\n if (!holder.token && !holder.canRefresh) {\n fail(\n new Error(\n \"<SobaProvider> needs a `session` or a `sessionEndpoint`. A browser cannot mint one: that takes your secret key.\",\n ),\n )\n } else {\n holder.ensure().then(\n () => live && setSession(holder.token),\n (err: Error) => live && fail(err),\n )\n }\n return () => {\n live = false\n unsubscribe()\n }\n }, [holder, fail])\n\n // Refresh before the clock runs out rather than after a request has already\n // failed. A page left open for an hour is the ordinary case here.\n //\n // `session` is in the deps on purpose, and the rule cannot see why: the effect\n // reads `holder.refreshAt`, a getter that moves when a new token lands. The\n // token changing is the signal to schedule the NEXT refresh, and without it\n // this runs once and the chain stops after the first hour.\n // biome-ignore lint/correctness/useExhaustiveDependencies: session is the re-arm signal, see above\n useEffect(() => {\n const at = holder.refreshAt\n if (at === null) return\n const timer = setTimeout(\n () => {\n holder.refresh().catch((err: Error) => fail(err))\n },\n Math.max(0, at - Date.now()),\n )\n return () => clearTimeout(timer)\n }, [holder, fail, session])\n\n const client = useMemo(() => {\n if (!session) return null\n return new ConnectClient({\n session,\n baseUrl,\n // Heals a session that died under a request, transparently, for every\n // consumer of this client, including the connect panel's own polling.\n fetchImpl: createSessionFetch(holder, fetchImpl ?? fetch.bind(globalThis)),\n })\n }, [session, baseUrl, holder, fetchImpl])\n\n useEffect(() => {\n if (!client) return\n let live = true\n setContextLoading(true)\n client.context().then(\n (value) => {\n if (!live) return\n setContext(value)\n setContextError(null)\n setContextLoading(false)\n },\n (err: Error) => {\n if (!live) return\n setContextError(err)\n setContextLoading(false)\n },\n )\n return () => {\n live = false\n }\n }, [client])\n\n const load = useCallback(async () => {\n if (!client) return\n try {\n const list = await client.machines()\n setMachines(list)\n setMachinesError(null)\n } catch (err) {\n setMachinesError(err as Error)\n } finally {\n setMachinesLoading(false)\n }\n }, [client])\n\n useEffect(() => {\n if (!client) return\n let live = true\n void load()\n const timer = setInterval(() => {\n // A hidden tab is a tab nobody is reading. Polling it spends someone\n // else's rate limit to redraw a screen that is not on.\n if (typeof document !== \"undefined\" && document.hidden) return\n if (live) void load()\n }, pollMs)\n return () => {\n live = false\n clearInterval(timer)\n }\n }, [client, load, pollMs])\n\n const run = useCallback(\n async (request: RunRequest): Promise<AsyncIterable<AgentEvent>> => {\n if (!runEndpoint) {\n throw new Error(\n \"Pass `runEndpoint` to <SobaProvider> to start runs from the browser. It is your own route: it holds your secret key, calls soba.run(), and forwards the stream.\",\n )\n }\n const { signal, ...body } = request\n const doFetch = fetchImpl ?? fetch.bind(globalThis)\n const response = await doFetch(runEndpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", accept: \"text/event-stream\" },\n // Your route, your auth. The Soba session is not sent: it says who is\n // connecting a machine, never who may spend your key.\n credentials: \"same-origin\",\n body: JSON.stringify(body),\n ...(signal ? { signal } : {}),\n })\n if (!response.ok) {\n throw new Error(`${runEndpoint} answered ${response.status}.`)\n }\n return readEvents(response)\n },\n [runEndpoint, fetchImpl],\n )\n\n const value = useMemo<SobaContextValue>(\n () => ({\n session,\n client,\n error,\n compute: {\n machines,\n connected: machines.length > 0,\n online: machines.some((m) => m.state !== \"offline\"),\n loading: machinesLoading,\n error: machinesError,\n refresh: load,\n },\n connect: {\n app: context?.app ?? null,\n command: context?.command ?? null,\n publishableKey: context?.publishable_key ?? null,\n loading: contextLoading,\n error: contextError,\n },\n run,\n }),\n [\n session,\n client,\n error,\n machines,\n machinesLoading,\n machinesError,\n load,\n context,\n contextLoading,\n contextError,\n run,\n ],\n )\n\n return (\n <ClientContext.Provider value={client}>\n <SobaContext.Provider value={value}>{children}</SobaContext.Provider>\n </ClientContext.Provider>\n )\n}\n", "/**\n * THE PIECE THE BROWSER CANNOT DO FOR ITSELF.\n *\n * `ConnectClient` takes an `est_` session token and uses it. Nothing in a\n * browser can MINT one: that takes the app's `sk_soba_` secret key, which must\n * never leave the customer's server. So this holds a session, asks the\n * customer's own endpoint for a new one when there is none, and refreshes it\n * before and after it expires.\n *\n * Sessions live ten minutes. A settings page left open for an hour is the\n * ordinary case, not the edge case, so refresh is not optional: without it every\n * component in the tree would quietly start failing at minute eleven.\n *\n * Framework-free, so the same logic is testable without a renderer.\n */\n\nexport interface SessionResponse {\n /** The `est_` token. `session` is the platform's own field name, so a\n * customer can proxy `POST /v1/end_users/session` verbatim. */\n session?: string\n /** Seconds. `token` and `expires_in` are accepted as aliases because a\n * hand-rolled endpoint tends to invent one of them. */\n token?: string\n expires_in?: number\n expiresIn?: number\n}\n\nexport interface SessionSourceOptions {\n /** A token you minted yourself. Cannot be refreshed: when it expires, the\n * holder reports it rather than pretending. */\n token?: string\n /** Your route. POSTed `{ user }`, expected to answer `{ session, expires_in }`. */\n endpoint?: string\n /** Passed to your endpoint so it knows whose session to mint. */\n user?: string\n fetchImpl?: typeof fetch\n}\n\n/** Refresh this long before the stated expiry, so a request in flight when the\n * clock runs out is not the thing that discovers it. */\nconst REFRESH_MARGIN_MS = 60_000\nconst MIN_REFRESH_MS = 10_000\n\nexport class SessionHolder {\n #token: string | null\n #expiresAt: number | null = null\n #inFlight: Promise<string> | null = null\n readonly #options: SessionSourceOptions\n readonly #listeners = new Set<() => void>()\n\n constructor(options: SessionSourceOptions) {\n this.#options = options\n this.#token = options.token ?? null\n }\n\n get token(): string | null {\n return this.#token\n }\n\n /** When the current token stops working, if the endpoint said. */\n get expiresAt(): number | null {\n return this.#expiresAt\n }\n\n /** False for a token handed in directly: there is nowhere to get another. */\n get canRefresh(): boolean {\n return Boolean(this.#options.endpoint)\n }\n\n /** When to ask for the next one, or null when there is nothing to schedule. */\n get refreshAt(): number | null {\n if (!this.canRefresh || this.#expiresAt === null) return null\n return Math.max(Date.now() + MIN_REFRESH_MS, this.#expiresAt - REFRESH_MARGIN_MS)\n }\n\n subscribe(listener: () => void): () => void {\n this.#listeners.add(listener)\n return () => this.#listeners.delete(listener)\n }\n\n /** The token, minting one if there is none. Concurrent callers share a\n * request: three components mounting together must not mint three sessions. */\n async ensure(): Promise<string> {\n if (this.#token) return this.#token\n return this.refresh()\n }\n\n async refresh(): Promise<string> {\n if (this.#inFlight) return this.#inFlight\n const endpoint = this.#options.endpoint\n if (!endpoint) {\n throw new Error(\n \"This Soba session cannot be refreshed. Pass `sessionEndpoint` so the provider can mint a new one.\",\n )\n }\n const doFetch = this.#options.fetchImpl ?? fetch.bind(globalThis)\n this.#inFlight = (async () => {\n const response = await doFetch(endpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n // The endpoint is the customer's own, so it authenticates the person the\n // way the rest of their app does. Their cookies, not our credential.\n credentials: \"same-origin\",\n body: JSON.stringify(this.#options.user ? { user: this.#options.user } : {}),\n })\n if (!response.ok) {\n throw new Error(`Could not start a Soba session (${response.status} from ${endpoint}).`)\n }\n const body = (await response.json()) as SessionResponse\n const token = body.session ?? body.token\n if (!token) {\n throw new Error(`${endpoint} answered without a \\`session\\`. See @soba-so/react's README.`)\n }\n const ttl = body.expires_in ?? body.expiresIn\n this.#token = token\n this.#expiresAt = typeof ttl === \"number\" ? Date.now() + ttl * 1000 : null\n for (const listener of this.#listeners) listener()\n return token\n })()\n try {\n return await this.#inFlight\n } finally {\n this.#inFlight = null\n }\n }\n}\n\n/**\n * A `fetch` that heals a dead session.\n *\n * `ConnectClient` writes the `Authorization` header itself from the token it was\n * built with, so a refresh has to rewrite the header on the retry rather than\n * hope the client picks it up. One retry only: a 401 that survives a fresh\n * session is a real 401, and retrying it forever would hammer the endpoint.\n */\nexport function createSessionFetch(\n holder: SessionHolder,\n base: typeof fetch = fetch.bind(globalThis),\n): typeof fetch {\n return async (input, init) => {\n const response = await base(input, init)\n if (response.status !== 401 || !holder.canRefresh) return response\n let fresh: string\n try {\n fresh = await holder.refresh()\n } catch {\n // Nothing better to offer than the 401 the caller already has.\n return response\n }\n return base(input, withAuthorization(init, fresh))\n }\n}\n\nfunction withAuthorization(init: RequestInit | undefined, token: string): RequestInit {\n const headers = new Headers(init?.headers)\n headers.set(\"authorization\", `Bearer ${token}`)\n return { ...init, headers }\n}\n", "// The stylesheet, compiled in.\n//\n// A drop-in component that needs a separate CSS import is not drop-in: the\n// import path differs per bundler, and in a React Server Components tree it is\n// one more thing to get wrong. So the styles inject themselves once, and\n// `@soba-so/react/styles.css` still exists for anyone who would rather own it.\n//\n// Substituted at build time from packages/connect-ui/src/connect-ui.css, so\n// there is one stylesheet and not a copy of one.\ndeclare const __SOBA_CONNECT_CSS__: string\n\nconst MARKER = \"data-soba-react\"\nlet injected = false\n\nexport function injectStyles(): void {\n if (injected || typeof document === \"undefined\") return\n injected = true\n if (document.querySelector(`style[${MARKER}]`)) return\n const style = document.createElement(\"style\")\n style.setAttribute(MARKER, \"\")\n style.textContent = __SOBA_CONNECT_CSS__\n document.head.appendChild(style)\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["jsx", "jsxs", "jsx", "ComputeStatus", "createContext", "useContext", "jsx", "useCallback", "useEffect", "useMemo", "useRef", "useState", "jsx", "useState", "useRef", "useCallback", "useEffect", "useMemo", "value"]
7
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * THE PIECE THE BROWSER CANNOT DO FOR ITSELF.
3
+ *
4
+ * `ConnectClient` takes an `est_` session token and uses it. Nothing in a
5
+ * browser can MINT one: that takes the app's `sk_soba_` secret key, which must
6
+ * never leave the customer's server. So this holds a session, asks the
7
+ * customer's own endpoint for a new one when there is none, and refreshes it
8
+ * before and after it expires.
9
+ *
10
+ * Sessions live ten minutes. A settings page left open for an hour is the
11
+ * ordinary case, not the edge case, so refresh is not optional: without it every
12
+ * component in the tree would quietly start failing at minute eleven.
13
+ *
14
+ * Framework-free, so the same logic is testable without a renderer.
15
+ */
16
+ export interface SessionResponse {
17
+ /** The `est_` token. `session` is the platform's own field name, so a
18
+ * customer can proxy `POST /v1/end_users/session` verbatim. */
19
+ session?: string;
20
+ /** Seconds. `token` and `expires_in` are accepted as aliases because a
21
+ * hand-rolled endpoint tends to invent one of them. */
22
+ token?: string;
23
+ expires_in?: number;
24
+ expiresIn?: number;
25
+ }
26
+ export interface SessionSourceOptions {
27
+ /** A token you minted yourself. Cannot be refreshed: when it expires, the
28
+ * holder reports it rather than pretending. */
29
+ token?: string;
30
+ /** Your route. POSTed `{ user }`, expected to answer `{ session, expires_in }`. */
31
+ endpoint?: string;
32
+ /** Passed to your endpoint so it knows whose session to mint. */
33
+ user?: string;
34
+ fetchImpl?: typeof fetch;
35
+ }
36
+ export declare class SessionHolder {
37
+ #private;
38
+ constructor(options: SessionSourceOptions);
39
+ get token(): string | null;
40
+ /** When the current token stops working, if the endpoint said. */
41
+ get expiresAt(): number | null;
42
+ /** False for a token handed in directly: there is nowhere to get another. */
43
+ get canRefresh(): boolean;
44
+ /** When to ask for the next one, or null when there is nothing to schedule. */
45
+ get refreshAt(): number | null;
46
+ subscribe(listener: () => void): () => void;
47
+ /** The token, minting one if there is none. Concurrent callers share a
48
+ * request: three components mounting together must not mint three sessions. */
49
+ ensure(): Promise<string>;
50
+ refresh(): Promise<string>;
51
+ }
52
+ /**
53
+ * A `fetch` that heals a dead session.
54
+ *
55
+ * `ConnectClient` writes the `Authorization` header itself from the token it was
56
+ * built with, so a refresh has to rewrite the header on the retry rather than
57
+ * hope the client picks it up. One retry only: a 401 that survives a fresh
58
+ * session is a real 401, and retrying it forever would hammer the endpoint.
59
+ */
60
+ export declare function createSessionFetch(holder: SessionHolder, base?: typeof fetch): typeof fetch;
@@ -0,0 +1,252 @@
1
+ /*
2
+ * Self-contained on purpose.
3
+ *
4
+ * This renders inside the Next app, inside a Tauri window, and eventually inside
5
+ * a customer's own page, which will have its own reset, its own font stack and
6
+ * quite possibly Tailwind. So: one class prefix, no element selectors, no
7
+ * assumptions about a reset, and every colour behind a token a host can
8
+ * override by redefining it on `.soba-connect`.
9
+ */
10
+
11
+ .soba-connect {
12
+ --sc-ground: #ffffff;
13
+ --sc-sheet: #ffffff;
14
+ --sc-raised: #f6f6f7;
15
+ --sc-line: #e9e9eb;
16
+ --sc-line-2: #dcdce0;
17
+ --sc-text: #1a1a1c;
18
+ --sc-text-2: #3f3f46;
19
+ --sc-muted: #6b6b73;
20
+ --sc-accent: #18181b;
21
+ --sc-accent-fg: #ffffff;
22
+ --sc-ok: #22a06b;
23
+ --sc-warn: #b06f14;
24
+ --sc-r: 10px;
25
+
26
+ box-sizing: border-box;
27
+ max-width: 560px;
28
+ margin: 0 auto;
29
+ color: var(--sc-text);
30
+ font-family: "Inter", ui-sans-serif, -apple-system, "Helvetica Neue", Arial, sans-serif;
31
+ font-size: 15px;
32
+ line-height: 1.5;
33
+ letter-spacing: -0.005em;
34
+ }
35
+ .soba-connect *,
36
+ .soba-connect *::before,
37
+ .soba-connect *::after {
38
+ box-sizing: inherit;
39
+ }
40
+
41
+ .sc-card {
42
+ background: var(--sc-sheet);
43
+ border: 1px solid var(--sc-line);
44
+ border-radius: var(--sc-r);
45
+ padding: 20px;
46
+ }
47
+ .sc-card + .sc-card {
48
+ margin-top: 12px;
49
+ }
50
+
51
+ .sc-title {
52
+ font-size: 20px;
53
+ font-weight: 650;
54
+ letter-spacing: -0.02em;
55
+ margin: 0 0 4px;
56
+ }
57
+ .sc-sub {
58
+ color: var(--sc-muted);
59
+ margin: 0 0 16px;
60
+ }
61
+ .sc-label {
62
+ font-size: 13px;
63
+ color: var(--sc-muted);
64
+ margin: 0 0 6px;
65
+ letter-spacing: -0.005em;
66
+ }
67
+
68
+ .sc-choices {
69
+ display: grid;
70
+ gap: 8px;
71
+ }
72
+ .sc-choice {
73
+ display: block;
74
+ width: 100%;
75
+ text-align: left;
76
+ background: var(--sc-sheet);
77
+ border: 1px solid var(--sc-line-2);
78
+ border-radius: var(--sc-r);
79
+ padding: 12px 14px;
80
+ cursor: pointer;
81
+ font: inherit;
82
+ color: inherit;
83
+ }
84
+ .sc-choice:hover {
85
+ background: var(--sc-raised);
86
+ }
87
+ .sc-choice[aria-pressed="true"] {
88
+ border-color: var(--sc-accent);
89
+ box-shadow: inset 0 0 0 1px var(--sc-accent);
90
+ }
91
+ .sc-choice-title {
92
+ font-weight: 550;
93
+ }
94
+ .sc-choice-meta {
95
+ color: var(--sc-muted);
96
+ font-size: 13px;
97
+ }
98
+
99
+ .sc-code {
100
+ display: block;
101
+ background: var(--sc-raised);
102
+ border: 1px solid var(--sc-line);
103
+ border-radius: 8px;
104
+ padding: 12px 14px;
105
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
106
+ font-size: 13px;
107
+ overflow-x: auto;
108
+ white-space: pre;
109
+ }
110
+
111
+ .sc-row {
112
+ display: flex;
113
+ gap: 8px;
114
+ align-items: center;
115
+ }
116
+ .sc-row-between {
117
+ display: flex;
118
+ gap: 12px;
119
+ align-items: center;
120
+ justify-content: space-between;
121
+ }
122
+ .sc-stack {
123
+ display: grid;
124
+ gap: 12px;
125
+ }
126
+
127
+ .sc-btn {
128
+ font: inherit;
129
+ font-weight: 550;
130
+ border-radius: 8px;
131
+ padding: 9px 14px;
132
+ border: 1px solid var(--sc-line-2);
133
+ background: var(--sc-sheet);
134
+ color: var(--sc-text);
135
+ cursor: pointer;
136
+ }
137
+ .sc-btn:hover {
138
+ background: var(--sc-raised);
139
+ }
140
+ .sc-btn-primary {
141
+ background: var(--sc-accent);
142
+ color: var(--sc-accent-fg);
143
+ border-color: var(--sc-accent);
144
+ }
145
+ .sc-btn-primary:hover {
146
+ opacity: 0.9;
147
+ background: var(--sc-accent);
148
+ }
149
+ .sc-btn:disabled {
150
+ opacity: 0.5;
151
+ cursor: default;
152
+ }
153
+
154
+ .sc-input {
155
+ font: inherit;
156
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
157
+ letter-spacing: 0.08em;
158
+ text-transform: uppercase;
159
+ padding: 10px 12px;
160
+ border: 1px solid var(--sc-line-2);
161
+ border-radius: 8px;
162
+ background: var(--sc-sheet);
163
+ color: inherit;
164
+ width: 100%;
165
+ }
166
+
167
+ .sc-dot {
168
+ width: 8px;
169
+ height: 8px;
170
+ border-radius: 50%;
171
+ flex: none;
172
+ background: var(--sc-muted);
173
+ }
174
+ .sc-dot-ok {
175
+ background: var(--sc-ok);
176
+ }
177
+ .sc-dot-busy {
178
+ background: var(--sc-warn);
179
+ }
180
+ .sc-dot-off {
181
+ background: var(--sc-line-2);
182
+ }
183
+
184
+ .sc-tone-ok {
185
+ color: var(--sc-ok);
186
+ }
187
+ .sc-tone-warn {
188
+ color: var(--sc-warn);
189
+ }
190
+ .sc-tone-unknown {
191
+ color: var(--sc-muted);
192
+ }
193
+
194
+ .sc-error {
195
+ color: var(--sc-warn);
196
+ font-size: 14px;
197
+ }
198
+ .sc-note {
199
+ color: var(--sc-muted);
200
+ font-size: 13px;
201
+ }
202
+
203
+ .sc-facts {
204
+ display: grid;
205
+ gap: 4px;
206
+ margin: 0;
207
+ padding: 0;
208
+ list-style: none;
209
+ }
210
+ .sc-fact {
211
+ display: flex;
212
+ gap: 10px;
213
+ font-size: 14px;
214
+ }
215
+ .sc-fact-key {
216
+ color: var(--sc-muted);
217
+ min-width: 92px;
218
+ }
219
+
220
+ .sc-machine {
221
+ border-top: 1px solid var(--sc-line);
222
+ padding-top: 12px;
223
+ margin-top: 12px;
224
+ }
225
+ .sc-machine:first-child {
226
+ border-top: 0;
227
+ padding-top: 0;
228
+ margin-top: 0;
229
+ }
230
+ .sc-runtimes {
231
+ display: grid;
232
+ gap: 4px;
233
+ margin: 8px 0 0;
234
+ padding: 0;
235
+ list-style: none;
236
+ font-size: 14px;
237
+ }
238
+
239
+ @media (prefers-color-scheme: dark) {
240
+ .soba-connect:not([data-theme="light"]) {
241
+ --sc-ground: #0f0f11;
242
+ --sc-sheet: #161618;
243
+ --sc-raised: #1d1d20;
244
+ --sc-line: #26262a;
245
+ --sc-line-2: #34343a;
246
+ --sc-text: #f2f2f3;
247
+ --sc-text-2: #c9c9cd;
248
+ --sc-muted: #8d8d95;
249
+ --sc-accent: #f2f2f3;
250
+ --sc-accent-fg: #131316;
251
+ }
252
+ }
@@ -0,0 +1 @@
1
+ export declare function injectStyles(): void;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * THE BROWSER-FACING SHAPES, mirrored from @soba-so/connect-ui.
3
+ *
4
+ * That package is private and is BUNDLED into this one rather than installed
5
+ * beside it, so a published declaration must never name it. Same reasoning as
6
+ * @soba-so/sdk's vendored protocol, and the same guard: test/types-parity.ts fails
7
+ * the typecheck the moment the two disagree.
8
+ *
9
+ * These are the JSON shapes of `GET /v1/machines`, so they are snake_case. That
10
+ * is deliberate: renaming fields on the way through would make the network tab
11
+ * and the code disagree about what a thing is called.
12
+ */
13
+ export type MachineState = "online" | "busy" | "offline";
14
+ export type CostClass = "user-hardware" | "user-subscription" | "open-weights" | "frontier";
15
+ export interface Runtime {
16
+ id: string;
17
+ display_name: string;
18
+ version: string | null;
19
+ models: string[];
20
+ cost_class: CostClass;
21
+ supports_tools: boolean;
22
+ /**
23
+ * TRI-STATE, and it stays tri-state all the way to the screen. `true` is
24
+ * signed in, `false` is a positive finding that it is signed OUT and the one
25
+ * a person can fix, and `null` means the probe could not tell. Rendering the
26
+ * third as the second tells someone their working setup is broken.
27
+ */
28
+ authenticated: boolean | null;
29
+ auth_hint: string | null;
30
+ }
31
+ export interface Machine {
32
+ id: string;
33
+ label: string | null;
34
+ platform: string | null;
35
+ arch: string | null;
36
+ version: string | null;
37
+ state: MachineState;
38
+ active: number;
39
+ capacity: number;
40
+ always_on: boolean;
41
+ last_seen_at: string | null;
42
+ runtimes: Runtime[];
43
+ }
44
+ /** A device code someone is being asked to approve. */
45
+ export interface PendingCode {
46
+ user_code: string;
47
+ client: string | null;
48
+ hostname: string | null;
49
+ platform: string | null;
50
+ arch: string | null;
51
+ labels: string[];
52
+ requested_at: string;
53
+ expires_at: string;
54
+ approved: boolean;
55
+ denied: boolean;
56
+ }
57
+ /** Everything the approval page needs, in one request. */
58
+ export interface ConnectContext {
59
+ app: {
60
+ name: string;
61
+ };
62
+ publishable_key: string | null;
63
+ /** The pairing command, given whole rather than assembled in the browser. */
64
+ command: string | null;
65
+ }
66
+ /**
67
+ * The escape hatch, as an interface rather than the class behind it. Structural
68
+ * on purpose: you can stub it in a test, and nothing about the class leaks into
69
+ * this package's public types.
70
+ */
71
+ export interface ConnectApi {
72
+ context(): Promise<ConnectContext>;
73
+ machines(): Promise<Machine[]>;
74
+ lookup(code: string): Promise<PendingCode>;
75
+ approve(code: string): Promise<{
76
+ ok: true;
77
+ }>;
78
+ deny(code: string): Promise<{
79
+ ok: true;
80
+ }>;
81
+ }
82
+ /**
83
+ * THE EVENT STREAM, mirrored.
84
+ *
85
+ * The five type values are frozen. New information arrives as optional fields
86
+ * on the existing types, never as a new type.
87
+ *
88
+ * Vendored rather than imported from @soba-so/sdk, because that package is a
89
+ * SERVER client: making a browser app install it to read five field names would
90
+ * be a dependency nobody asked for. @soba-so/sdk mirrors @soba-so/protocol, this
91
+ * mirrors @soba-so/sdk, and test/types-parity.ts fails when the chain breaks.
92
+ */
93
+ export type AgentEventType = "delta" | "thinking" | "status" | "done" | "error";
94
+ export interface RunUsage {
95
+ costClass?: CostClass;
96
+ costMicros?: number;
97
+ model: string;
98
+ provider?: string;
99
+ inputTokens: number;
100
+ outputTokens: number;
101
+ cacheReadTokens: number;
102
+ cacheWriteTokens: number;
103
+ webSearches: number;
104
+ }
105
+ export interface AgentEvent {
106
+ type: AgentEventType;
107
+ /** `delta`: the NEW text, append it. `thinking`: the RUNNING TOTAL, replace it. */
108
+ text?: string;
109
+ /** `status` / `error`: human-readable copy, safe to show a user. */
110
+ message?: string;
111
+ /** On a `status` raised by a tool: a stable slug to map to an icon. */
112
+ tool?: string;
113
+ /** On `done`: what the run consumed, when the runtime could report it. */
114
+ usage?: RunUsage;
115
+ /** Which economics is serving this run, announced before any output. */
116
+ tier?: CostClass;
117
+ }
118
+ export interface Message {
119
+ role: "user" | "assistant";
120
+ content: string;
121
+ }