Zum Inhalt springen
Navigieren Öffnen escSchließen

Host API

Diese Seite ist für dich, wenn du die genauen connectHost-Methoden, Grenzen und Fehlercodes brauchst. Für Ordneraufbau, Manifest und Installation fang bei Eine Erweiterung bauen an.

import { connectHost, HostRequestError } from "@openchamber/sdk";
const host = connectHost();

connectHost wirft HOST_UNAVAILABLE, wenn es kein window gibt. Außerhalb von OpenChamber liefert es einen Client, bei dem jeder Aufruf mit HOST_UNAVAILABLE abgewiesen wird. Ruf dispose() auf, wenn du die Seite abbaust; noch laufende Aufrufe werden mit demselben Code abgewiesen.

Was OpenChamber an dich schickt

onReady feuert mit dem ersten Snapshot und danach jedes Mal, wenn OpenChamber ihn aktualisiert.

Das ist kein einmaliger Mount-Callback. Mounte die UI und registriere Abonnements einmal; aktualisiere danach Theme und Kontext, ohne Eingaben oder Entwürfe zu ersetzen. Ein Beispiel mit Mount-Schutz steht im UI kit. Listener wie onConnection liefern sofort den aktuellen Wert und können ihn bei neuen Snapshots wiederholen. Vergleiche die relevanten Felder vor einer neuen Anfrage und ignoriere Antworten überholter Anfragen.

FeldWas es ist
theme.modelight oder dark
theme.tokensdie Farben der App (Flächen, Text, Interaktionszustände, primary, success/warning/error/info), font, mono und radius. Gib das an applyHostReady weiter, bevor du UI mountest.
localedas Sprach-Tag der App
directoryaktuelles Projektverzeichnis oder null
session{ id, title, busy } oder null. Fehlt der Titel, steht dort die Session-ID. busy ist der Live-Status. model und agent (der OpenCode-Agent) erscheinen, wenn die Session sie hat.
surfacepanel in der Leiste, dialog im Anhängen-Fenster, page im Vollbild
itemwofür diese Oberfläche geöffnet wurde, oder null: der angehängte Eintrag, den der Nutzer angeklickt hat (dieselben Felder, die du an attach übergeben hast, einschließlich data), eine Nachricht (kind: "message") oder eine Session (kind: "session") aus einer deiner deklarierten Aktionen. Siehe Aktionen.
connection{ connected, account } für deine Integration
settingsWerte der Felder, die du in integration.settings deklariert hast

Zugriffstokens tauchen hier nie auf, auch nicht in einem request-Ergebnis.

onDirectory, onSession, onSessionLifecycle, onConnection, onSettings und onItem liefern den letzten Wert nach, wenn du dich spät anmeldest, und feuern danach bei jeder Änderung weiter.

theme.tokens enthält primaryText, successText, warningText, errorText und infoText. Diese Pflichtfelder enthalten vom Host berechnete Textfarben für neutrale Hintergründe und leicht getönte UI-Kit-Elemente, nicht für kräftige Farbfüllungen. applyHostReady wendet sie bei jedem Snapshot an. Die CSS-Variablen beschreibt das UI kit.

Methoden

AufrufWas er tut
toast({ kind, message })einen Toast in der App anzeigen. kind ist info, success oder error.
openUrl(url)eine URL im Browser des Nutzers öffnen
openSurface(surfaceId)die App auf diesen Bildschirm umschalten
writeClipboard(text)Text kopieren. 1 bis 32000 Zeichen.
compose({ text, mode? })Text ins Chatfeld setzen, ohne zu senden. mode ist append (Standard) oder replace. 1 bis 16000 Zeichen nach dem Trimmen.
attach({ ... })einen Chip ans Chatfeld hängen, an dieselbe Stelle, an der GitHub- und Linear-Einträge landen. Nur ein Chip auf einmal.
startSession({ ... })eine Session mit diesem Eintrag angehängt anlegen. Liefert { sessionId, sent }. Braucht die Berechtigung sessions.
prompt({ text, send? })in die aktuelle Session schreiben oder darin senden. Liefert { sent }. Senden braucht die Berechtigung prompt.
sessionLink({ ... })einen Eintrag an die aktuelle Session anhängen, ohne eine neue anzulegen
close()das Anhängen-Fenster schließen. In der Leiste ohne Wirkung.
oauthStart()die Authorize-Seite des Providers öffnen, oder die von Linear bei einer Integration mit host: { provider: "linear" }
oauthDisconnect()den gespeicherten Token oder die Linear-Verbindung vergessen
request({ method, path, query?, body? })den apiOrigin deiner Integration mit dem Token des Nutzers aufrufen
serviceRequest({ method, path, query?, body? })den lokalen Dienst deiner Erweiterung aufrufen (siehe GUEST_SERVICES.md)
serviceStatus()stopped, starting, ready oder failed
readFile(path)eine Textdatei lesen. Liefert { content }.
writeFile(path, content)eine Textdatei atomar schreiben, übergeordnete Ordner werden angelegt. Liefert { written: true }.
listDir(path)einen Ordner auflisten. Liefert { entries: [{ name, kind }] }, kind ist file, directory oder other.
stat(path){ kind, size, mtime }, kind ist file, directory, other oder missing.
generate({ prompt, system?, maxOutputTokens? })einmaliger Text vom Small Model des Nutzers. Liefert { text }. Braucht die Berechtigung model.
onResolve(handler)den Handler für deine Slash-Befehle registrieren. Er bekommt { command, args } und liefert einen Eintrag zum Anhängen oder null.
setBadge(count)eine Zahl (0 bis 999) auf deinem Symbol in der Leiste zeigen, oder null, um sie zu entfernen

Alle drei nehmen dieselben Eintragsfelder:

await host.attach({
providerId: "acme-hello",
id: "TICKET-1",
title: "Login is broken",
url: "https://example.com/TICKET-1",
});
await host.attach({
providerId: "acme-hello",
id: "!12",
title: "Fix login",
url: "https://example.com/merge_requests/12",
kind: "pull",
author: "ada",
branches: { head: "feature", base: "main" },
text: "Optional notes for the model",
});
await host.startSession({
providerId: "acme-hello",
id: "!12",
title: "Fix login",
url: "https://example.com/merge_requests/12",
kind: "pull",
worktree: true,
text: "Optional first message",
});

providerId ist deine Panel-ID; OpenChamber überschreibt sie ohnehin mit deiner ID. id ist deine eigene Kennung für den Eintrag. kind ist issue (Standard) oder pull. text ist optionaler Kontext für das Modell, 1 bis 16000 Zeichen nach dem Trimmen. data ist optionales JSON nach deinem Belieben (Status, Kommentare, was auch immer), serialisiert bis zu 16000 Zeichen. OpenChamber speichert es zusammen mit dem Chip und gibt es unverändert in ctx.item zurück, wenn der Nutzer den Chip anklickt; das Modell bekommt es nie zu sehen.

startSession akzeptiert projectId, ohne das aktive Projekt zu wechseln. worktree weglassen für das Zielverzeichnis, true für einen generierten Worktree, { kind: "existing", directory } für einen bekannten oder { kind: "new", name?, baseBranch? } für einen neuen. Der Name gilt für Branch und Worktree. navigation ist standardmäßig "preserve"; "open" öffnet den neuen Chat. Modell, Agent und Variante der ersten Nachricht werden beim Start festgehalten.

Das Ergebnis enthält sessionId, directory, sent, linked und optional worktree. sent ist sent, no-model, skipped oder failed; linked: false bedeutet, dass der Eintrag nicht gespeichert wurde. Bleibt ein Worktree nach einem Bootstrap- oder Sessionfehler zurück, ist sessionId: null und failure gleich bootstrap-failed oder session-create-failed. Prüfe dieses Ergebnis vor einem erneuten Versuch. Die Wartezeit beträgt bis zu 180 Sekunden; ein Timeout beweist keinen Rollback.

sessionLink hängt den Eintrag an die gerade offene Session. Kein Projekt oder keine Session ist ein NO_SESSION-Fehler.

Grenzen, die der Client prüft, bevor etwas gesendet wird:

FeldMax. Zeichen
id128
title200
url2000
text16000
data (serialisiert)16000
author80
jeder Branch-Name200

prompt und Session-Lebenszyklus

await host.prompt({ text: "Fix the login" });
await host.prompt({ text: "Fix the login", send: true });
host.onSessionLifecycle((event) => {
document.body.dataset.phase = event.phase;
});

Ohne send ersetzt prompt den Text im Chatfeld. Mit send: true sendet es die Nachricht mit dem Modell und dem Agenten, die der Nutzer ausgewählt hat. Die Erweiterung wählt sie nie selbst. Keine offene Session ist NO_SESSION. Senden, während die Session beschäftigt ist, ist SESSION_BUSY; ins Feld schreiben geht auch dann. Das Ergebnis ist { sent } mit denselben Werten wie bei startSession.

onSessionLifecycle sagt dir, was die Session gerade tut. phase ist started, solange das Modell arbeitet, completed, wenn sie in den Leerlauf geht, und failure bei einem unerwarteten Status. Ein später Listener bekommt die aktuelle Phase sofort.

Projekte, Live-Sitzungen und Speicher

listProjects(), listWorktrees(projectId) und listSessions(projectId) verwenden die vorhandene sessions-Berechtigung und gemeinsame Stores, ohne Git-Abfrage pro Aufruf oder Gesprächsinhalte. Projekte enthalten ID, Name und Verzeichnis; Worktrees zusätzlich Branch und Verfügbarkeit. Sitzungen enthalten Metadaten, Zeitstempel, Parent-ID, Worktree und nur die angehängten Einträge dieser Erweiterung. Bekannte archivierte Sitzungen sind enthalten.

await onProjects(listener), await onWorktrees(projectId, listener) und await onSessions(projectId, listener) liefern eine Abmeldefunktion. Fange Fehler bei der Anmeldung ab. Zuerst kommt ein Snapshot, danach Änderungen. Maximal 32 Abonnements pro Frame; dispose(), Schließen, Pause, Entfernen und Serverwechsel räumen sie auf. state ist loading, ready oder error; Sitzungen enthalten zusätzlich coverage je Verzeichnis. Fehler behalten vorhandene Daten. Nur ready bestätigt eine vollständige leere Liste.

activity ist unknown, idle, running, retrying, waiting-permission oder waiting-question. outcome ist ein beobachtetes completed/failed oder null, nur im Speicher für höchstens 2.000 beobachtete Sitzungen. Idle nach einem Fehler behält den Fehler bis zum nächsten Lauf. Das setzt keine Aufgabe auf Done. openSession(sessionId) öffnet den Chat ausdrücklich.

host.storage.get(key), set(key, value), delete(key) und keys() speichern eigenes JSON ohne weitere Berechtigung. Fehlende Schlüssel liefern undefined, gespeichertes null bleibt null. Schlüssel haben 1 bis 128 Zeichen, Werte höchstens 64 KiB UTF-8, der gesamte Speicher 2 MiB und 2.000 Schlüssel. Nutze Projekt-IDs im Schlüssel für projektspezifische Daten. Schreibvorgänge sind serialisiert und atomar; Fehler erhalten vorhandene Daten. Entfernen löscht den Speicher auf dem verbundenen Server.

request

const user = await host.request({ method: "GET", path: "/api/v2/user" });

path beginnt mit / und hat weder Schema noch Host; OpenChamber hängt ihn an den apiOrigin aus deinem Manifest und fügt den Authorization-Header hinzu. Token-Integrationen senden den eingefügten Token unverändert, als Bearer <token>, wenn das Manifest token.scheme: "bearer" setzt, oder als Basic base64(username:token), wenn es "basic" setzt. OAuth- und Linear-Integrationen senden immer Bearer. Das Ergebnis ist { status, body }. Der Body ist Text; JSON parst du selbst.

Eine Linear-Integration darf außerdem GET /api/linear/issues/get aufrufen, das OpenChamber aus seiner eigenen Linear-Route beantwortet.

Keine Antwort innerhalb von 20 Sekunden ist HOST_TIMEOUT.

try {
await host.request({ method: "GET", path: "/api/v2/user" });
} catch (error) {
if (error instanceof HostRequestError && error.code === "DISCONNECTED") {
await host.oauthStart();
}
}

serviceRequest

Deklariert das Manifest contributes.service, startet OpenChamber diesen Prozess, und deine Seite spricht über dieselbe Art von Aufruf mit ihm. Die Seite öffnet den Socket nie selbst.

const status = await host.serviceStatus();
const result = await host.serviceRequest({ method: "GET", path: "/containers" });

path folgt denselben Regeln wie bei request. Einen Dienst zu deklarieren fügt service zu den Berechtigungen hinzu, die der Nutzer bei der Installation bestätigt; bis dahin ist jeder serviceRequest ein NO_SERVICE. Vollständiger Vertrag: die Paketdatei GUEST_SERVICES.md.

Dateien

Ein relativer Pfad liegt innerhalb des offenen Projekts und braucht die Berechtigung files. Ein Pfad, der mit / oder ~/ beginnt, muss zu einem deklarierten contributes.filesystem-Muster passen und braucht die Berechtigung filesystem. Die Regeln stehen unter Eine Erweiterung bauen.

const { content } = await host.readFile("package.json");
await host.writeFile("notes/today.md", "# Today\n");
const { entries } = await host.listDir("src");
const info = await host.stat("~/.config/opencode/opencode.json");

Grenzen: Pfad bis 1024 Zeichen, Inhalt bis 2.000.000 Zeichen, Auflistung bis 2000 Einträge. Über der Inhaltsgrenze ist FILE_TOO_LARGE.

generate

const { text } = await host.generate({ prompt: notes, system: "Summarize in one sentence." });

prompt ist 1 bis 64000 Zeichen nach dem Trimmen, system bis 8000, maxOutputTokens 1 bis 4000. Nichts landet in einer Session, und es wird kein Verlauf gespeichert; die Erweiterung wählt das Modell nie selbst. Der Aufruf wartet bis zu 90 Sekunden. Kein nutzbares Small Model ist NO_MODEL; ein Modellfehler ist MODEL_FAILED. Siehe Text erzeugen.

Fehlercodes

Fehlgeschlagene Aufrufe werfen HostRequestError. code ist einer der folgenden; message sagt mehr.

CodeWann
HOST_UNAVAILABLEkein window, nicht innerhalb von OpenChamber, oder dispose() wurde aufgerufen
HOST_TIMEOUT20 Sekunden lang keine Antwort
HOST_REJECTEDOpenChamber hat abgelehnt oder mit einem Code geantwortet, den dieses SDK nicht kennt
DISCONNECTEDkein Token oder keine Linear-Verbindung für deine Integration
BAD_PATHpath war fehlerhaft oder wollte den erlaubten Origin verlassen
NO_INTEGRATIONdas Manifest hat keine integration
NO_SESSIONprompt oder sessionLink ohne offene Session
SESSION_BUSYprompt({ send: true }), während die Session beschäftigt war
DISABLEDder Nutzer hat die Erweiterung unter Einstellungen → Erweiterungen pausiert
NO_SERVICEkein Dienst deklariert, nicht bestätigt oder nicht gestartet
NOT_GRANTEDder Nutzer hat die für diesen Aufruf nötige Berechtigung nicht bestätigt
SERVICE_FAILEDder Dienst ist abgestürzt oder nie bereit geworden
NO_DIRECTORYein relativer Dateipfad wurde ohne offenes Projekt verwendet
NOT_FOUNDreadFile auf eine Datei, die nicht existiert
FILE_TOO_LARGEDateiinhalt über der Grenze, beim Lesen oder Schreiben
DENIEDdas Betriebssystem hat die Dateioperation verweigert
NO_MODELgenerate ohne verfügbares Small Model
MODEL_FAILEDdas Small Model hat einen Fehler zurückgegeben

Verwandt

  • Eine Erweiterung bauen für Ordner, Manifest, Installation und Berechtigungen
  • UI-Kit für Buttons, Felder, Listen und die anderen Bausteine