Client
Für Entwickler. Wähle im Entwicklerhandbuch zwischen API-Anbindung, App-Veröffentlichung und Projektarbeit.
@openbot-build/client ist der TypeScript-Client für OpenBot. Er entsteht aus
demselben API-Vertrag wie der Server. Jede Anfrage, jede Antwort und jeder
Fehler hat einen Typ. Dein Editor zeigt Fehler, bevor du den Code ausführst.
Der Client nutzt Effect, eine TypeScript-Bibliothek für typisierte Programme. Er läuft in Node.js, Deno, Bun, Workern und Browsern.
Installieren
Section titled “Installieren”Die Pakete liegen in GitHub Packages unter https://npm.pkg.github.com. Trage
den Scope in die .npmrc des Projekts ein:
@openbot-build:registry=https://npm.pkg.github.comNutze einen GitHub-Token (classic PAT) mit read:packages. Mit
npm login --scope=@openbot-build --registry=https://npm.pkg.github.com --auth-type=legacy
speicherst du ihn in deinen npm-Einstellungen. Lege den Token nicht in Git ab.
Installiere eine genaue Version. Mit sha-<vollständiger-Commit-Hash> wählst du
einen veröffentlichten Commit. Die Lockdatei hält die genaue Version fest:
pnpm add --save-exact @openbot-build/client@0.1.2 effect@4.0.7Der Client enthält gemeinsamen OpenBot-Code und Typen unter MIT. Du brauchst keine Kopie des OpenBot-Quellcodes. Andere OpenBot-Pakete behalten ihre Lizenzen.
Einen Client erstellen
Section titled “Einen Client erstellen”layer erstellt einen Layer, der dir den Dienst Client gibt. Ein Layer ist
ein Rezept in Effect, das einen Dienst baut.
import { Chats, layer } from "@openbot-build/client"import { NodeCrypto } from "@effect/platform-node"import { Effect } from "effect"
const program = Effect.gen(function* () { const command = yield* Chats.botMessage(botId, { input: "Write the release notes.", }) const receipt = yield* command.send return receipt.chat})
const id = await Effect.runPromise( program.pipe( Effect.provide(NodeCrypto.layer), Effect.provide( layer({ baseUrl: "https://api.example.com", token: process.env.OPENBOT_TOKEN, }), ), ),)Bereiten Sie einen Befehl einmal vor. command.commandKey bleibt bei einem
neuen Versuch mit command.send gleich. Chats.message sendet so an einen
bestehenden Chat. Der Beleg nennt die Ausführung. Beobachten Sie sie über
client.api.executions.observe mit der letzten afterSequence. Das Schließen
der Beobachtung bricht die Arbeit nicht ab. Executions.cancel und
Executions.decide bereiten auch Befehle mit festen Schlüsseln vor. Geben Sie
diesen Helfern einen Crypto-Dienst der Plattform. Das Node.js-Beispiel nutzt
NodeCrypto.layer.
| Option | Pflicht | Was sie tut |
|---|---|---|
baseUrl |
Ja | Der Origin von OpenBot, zum Beispiel https://api.example.com. |
token |
Nein | Ein User Token, als String oder als Redacted-Wert von Effect. Er wird als Authorization: Bearer <token> gesendet. |
organization |
Nein | Eine Organisations-ID. Mit dem Session-Cookie wird sie als Header OpenBot-Organization gesendet. Ein Token ignoriert sie. |
onUnauthorized |
Nein | Eine Funktion, die läuft, wenn eine geschützte Anfrage 401 zurückgibt. |
Wie du ein User Token erstellst, steht unter Authentifizierung.
Der Client speichert das Token nie und setzt es nie in eine URL.
onUnauthorized meldet nicht an und versucht die Anfrage nicht noch einmal.
Nutze die Funktion, um den Benutzer zu informieren, zum Beispiel wenn ein Token
abläuft oder jemand es widerruft.
Eine Operation aufrufen
Section titled “Eine Operation aufrufen”Datensatz-IDs haben Markentypen (branded types). Eine Marke trennt die IDs
verschiedener Arten von Datensätzen. So kann eine ConnectionId nicht als
AppViewId dienen. Nutze die ID aus einer API-Antwort für weitere Aufrufe. Lies
eine ID von außerhalb des Programms mit ihrem Schema aus @openbot-build/api,
etwa ConnectionsApi.ConnectionId. Modell- und Anbieter-IDs nutzen Model.Id
und Provider.Id aus @openbot-build/core. In JSON und URLs bleiben diese
Werte Zeichenketten.
Jede Operation liegt unter client.api.<group>.<operation>. Die Namen sind
dieselben wie in der API-Referenz. Jeder Aufruf nimmt ein Objekt mit
diesen optionalen Feldern:
| Feld | Was es enthält |
|---|---|
params |
Werte im Pfad, zum Beispiel chatId. |
query |
Werte im Query-String. |
payload |
Den JSON-Body. |
const chats = yield * client.api.chats.listChats({ query: {} })
const view = yield * client.api.chats.getChat({ params: { chatId: id }, })
const tasks = yield * client.api.chats.listChats({ query: { kind: "task" }, })Einige Gruppen und ihre Seiten:
| Gruppe | Nutze sie, um | Referenz |
|---|---|---|
chats |
Mit Bots zu sprechen und Chats und Ergebnisse zu lesen. | Chats |
delegations |
Die Arbeit zu lesen und zu stoppen, die Bots abgegeben haben. | Delegations |
bots |
Bots zu erstellen und zu verwalten. | Bots |
requests |
Die Requests deiner Bots zu beantworten. | Requests |
apps |
Eine App unter einer Adresse zu finden. | App discovery |
connections |
App-Verbindungen hinzuzufügen und zu verwalten. | Connections |
appIndex |
Den öffentlichen App Index zu durchsuchen. | App Index |
userTokens |
User Tokens aufzulisten, zu erstellen, zu widerrufen und zu prüfen. | User tokens |
organizations |
Deine Organisationen aufzulisten und zu erstellen. | Organizations |
organization |
Die Organisation, ihre Mitglieder, Rollen und Einstellungen zu verwalten. | Organization |
modelProviders |
Model Providers zu verwalten und Models aufzulisten. | Model providers |
sso |
E-Mail-Domains zu bestätigen und SSO-Verbindungen zu verwalten. | SSO |
platform |
Die Plattform zu verwalten. Nur für Plattform-Admins. | Platform |
health |
Das Dienstdokument und den Zustand zu lesen. | Health |
Jede Operation braucht eine Aktion auf einer Ressource. Ein Aufruf ohne sie
schlägt mit Forbidden fehl. Siehe Berechtigungen.
Der typisierte Client nutzt Effect Redacted<string> für geheime Felder in
Anfragen und Antworten. Packen Sie neue Geheimnisse mit Redacted.make ein.
Nutzen Sie Redacted.value nur, wenn Sie den Wert senden oder zeigen müssen. In
JSON bleiben diese Felder Strings. Das gilt für Anbieterschlüssel, Vault-Werte,
SSO-Geheimnisse, Einladungen, Tokens, Webhooks und Computer-Kopplungen.
Ein ChatGPT-Konto verbinden
Section titled “Ein ChatGPT-Konto verbinden”Nutze die Gruppe modelProviders für einen chatgpt-Anbieter. providerId ist
seine normale prov_*-ID. Starte vorher einen lokalen Listener unter
http://127.0.0.1:<port>/auth/callback. Seine Adresse ist redirectUri.
const start = Effect.gen(function* () { const provider = yield* client.api.modelProviders.getModelProvider({ params: { providerId }, }) const authorization = yield* client.api.modelProviders.startProviderAuthorization({ params: { providerId }, payload: { redirectUri, expectedVersion: provider.version }, }) return authorization})Öffne authorization.url im Browser. Sende die vollständige callbackUrl, die
dein Listener erhält.
const finish = Effect.gen(function* () { const connected = yield* client.api.modelProviders.completeProviderAuthorization({ params: { providerId, authorizationId: authorization.id }, payload: { callbackUrl }, }) const available = yield* client.api.modelProviders.getProviderAvailableModels( { params: { providerId }, }, ) return { connected, available }})
const disconnect = client.api.modelProviders.disconnectProviderOAuth({ params: { providerId },})Es gelten normale Anbieterrechte mit API-Schlüssel oder Session-Cookie desselben
Origins. Tokens bleiben verschlüsselt auf dem Server. Plattformanbieter nutzen
die entsprechenden PlatformProvider-Operationen unter client.api.platform.
available.items enthält Upstream-Modelle. Lege sie über die normale Modell-API
an und verknüpfe sie. Die Anmeldung ändert keine Modellauswahl.
ModelProviderAuthorizationInvalid braucht einen neuen Versuch,
ModelProviderLoginRequired eine Anmeldung. ModelProviderOAuthUnavailable
bedeutet, dass der Aufruf gerade nicht möglich ist. Bei ModelProviderConflict
lies die aktuelle Anbieterversion. Siehe
Models.
Ausführungen beobachten und steuern
Section titled “Ausführungen beobachten und steuern”Ein Befehlsbeleg enthält executionId, commandKey und sequence. Er
bestätigt das Speichern des Befehls, nicht das Ende einer externen Handlung.
Lies GET /v1/executions/:executionId für status und failure. Ein Fehler
enthält nur category und errorId.
Lies gespeicherte Ereignisse mit
GET /v1/executions/:executionId/events?afterSequence=0. Die Seite enthält
events, afterSequence und terminal. Behalte den zurückgegebenen Cursor für
die nächste Seite. Für laufende Ereignisse nutze SSE, einen
Server-Ereignisstrom:
curl -N -H "Authorization: Bearer $OPENBOT_TOKEN" \ "https://api.example.com/v1/executions/$EXECUTION_ID/observe?afterSequence=0"Jede SSE-Nachricht hat den Ereignisnamen execution. Ihre Daten enthalten
executionId, sequence, createdAt und body. Speichere die zuletzt
verarbeitete Nummer. Sende sie nach einem Verbindungsabbruch als
afterSequence. Jedes Kind braucht einen eigenen Cursor. Textereignisse haben
step und attempt. Halte verschiedene Versuche getrennt.
Executions.observe(id, cursor) gibt die Ereignisse als Effect Stream. Das
Schließen des Streams bricht angenommene Arbeit nicht ab.
curl -X POST -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"commandKey":"cancel-work-1"}' \ "https://api.example.com/v1/executions/$EXECUTION_ID/cancel"Abbruch- und Entscheidungsendpunkte geben HTTP 200 mit einem Befehlsbeleg.
Arbeit kann cancelling bleiben, solange Ressourcen freigegeben werden oder ein
externes Ergebnis unbekannt ist. Endzustände sind completed, failed,
cancelled und interrupted. waiting und outcome_unknown brauchen noch
Fortschritt oder eine Entscheidung.
Ein DecisionNeeded-Ereignis nennt decisionId und die erlaubten choices.
Sende eine davon an POST /v1/executions/:executionId/decisions:
{ "commandKey": "answer-work-1", "decisionId": "<saved decision id>", "answer": "stop"}Nutze bei einem neuen Versuch denselben Schlüssel und Inhalt. Ein anderer Inhalt
mit demselben Schlüssel ergibt 409 ExecutionKeyConflict. Eine Entscheidung
hebt aktuelle Rechte oder Token-Grenzen nicht auf. API-Schlüssel und Cookies vom
gleichen Ursprung nutzen dieselben Prüfungen der Anwendung.
Fehler behandeln
Section titled “Fehler behandeln”Jeder fehlgeschlagene Aufruf gibt einen typisierten Fehler. Der Typ
ClientError listet sie alle auf:
- API-Fehler aus dem Vertrag. Jeder hat einen
_tag, zum BeispielChatNotFoundoderChatConflict. - Schema-Fehler (
SchemaError), wenn eine Antwort nicht zum Vertrag passt. - HTTP-Client-Fehler, wenn das Netzwerk ausfällt.
Nutze Effect.catchTag, um eine Art von Fehler zu behandeln:
const view = yield * client.api.chats .getChat({ params: { chatId: id } }) .pipe(Effect.catchTag("ChatNotFound", () => Effect.succeed(undefined)))Häufige Fehler bei der Anmeldung:
| Tag | Status | Bedeutung |
|---|---|---|
AuthenticationRequired |
401 |
Kein Schlüssel und kein Cookie, oder der Schlüssel ist widerrufen oder abgelaufen. |
OrganizationRequired |
400 |
Eine Cookie-Anfrage ohne organization, und die Person gehört zu mehr als einer Organisation. |
MembershipRequired |
403 |
Das Konto ist kein aktives Mitglied der Organisation. |
ForbiddenOrigin |
403 |
Eine Cookie-Anfrage, die Daten ändert, hat den falschen Origin. |
AuthenticationUnavailable |
503 |
OpenBot kann Zugangsdaten gerade nicht prüfen. |
Alle Fehler-Schemas werden aus @openbot-build/api exportiert.
Den Client im Browser nutzen
Section titled “Den Client im Browser nutzen”Eine Web-App auf demselben Origin wie OpenBot kann das Anmelde-Cookie statt
eines Tokens nutzen. Lass token weg:
import { layer } from "@openbot-build/client"
const ClientLive = layer({ baseUrl: window.location.origin, // Nötig, wenn die Person zu mehr als einer Organisation gehört. organization: "org_8f3a", onUnauthorized: async () => { // OpenBot wählt den Anbieter und antwortet mit seiner Adresse. const response = await fetch("/auth/sign-in", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ returnTo: "/" }), }) const { url } = await response.json() window.location.assign(url) },})Der Client sendet Anfragen mit credentials: "same-origin". So fügt der Browser
das Cookie hinzu. Der Browser fügt auch den Header Origin hinzu. Bei Anfragen,
die Daten ändern, prüft OpenBot, dass er genau publicOrigin aus den
Plattform-Einstellungen ist. Die Organisationen der Person liest du mit
GET /auth/session. Siehe
Authentifizierung.
Wie du deine Web-App auf demselben Origin bereitstellst, steht unter Betrieb.
Eigene Einrichtung
Section titled “Eigene Einrichtung”make({ baseUrl }) baut den Dienst Client ohne die Standard-Teile. Du gibst
ihm dann:
- einen Dienst
HttpClient, - Client-Middleware für
BotAuth, genutzt von Token-Routen, - Client-Middleware für
AccountAuth, genutzt von Routen, die nicht in einer einzelnen Organisation handeln, zum Beispiel/v1/organizationsund/v1/platform, - Client-Middleware für
SessionAuth, genutzt von Routen nur mit Cookie.
Nutze das, wenn du deinen eigenen HTTP-Client, eigenes Logging oder eigene
Header brauchst. Wie layer das macht, siehst du in
packages/client/src/Client.ts.
App-Ansichten zeigen
Section titled “App-Ansichten zeigen”Manche App-Werkzeuge haben eine kleine Benutzeroberfläche, eine sogenannte MCP-Apps-Ansicht. MCP ist das Model Context Protocol, ein Standardweg, auf dem Bots mit Werkzeugen sprechen. OpenBot hält die Ansicht auf dem Server. Dein Client zeigt sie nur an.
Öffne zuerst die Ansicht für eine App-Operation. Erstelle dann mit
AppViews.make einen Ansichts-Client:
import { AppViews, Client } from "@openbot-build/client"import { Effect } from "effect"
const program = Effect.gen(function* () { const client = yield* Client const opened = yield* client.api.appViews.open({ params: { operationId }, payload: { chatId, itemId }, }) return yield* AppViews.make(opened, accountId, () => refresh())})| Argument | Was es ist |
|---|---|
opened |
Die Ansicht, die appViews.open zurückgegeben hat. |
owner |
Eine feste ID des angemeldeten Kontos. Sie hält Ansichten verschiedener Konten getrennt. |
onChange |
Optional. Läuft, wenn eine Aktion in der Ansicht ihren Status ändert. Nutze es, um Requests und den Chat neu zu laden. |
Das Ergebnis funktioniert mit mount aus @openbot-build/protocol/McpApps. Das
zeigt die Ansicht in einem iframe. Siehe Protokoll.
Der Ansichts-Client hält keine Zugangsdaten. Jede Anfrage geht über OpenBot. Darum gelten die normalen Prüfungen der Fähigkeiten:
- Er prüft, dass jede Antwort ein gültiges MCP-Ergebnis ist.
- Er sendet jede Aktion mit einem Befehlsschlüssel.
- Er folgt gespeicherten Ereignissen der Kind-Ausführungen, auch nach dem Ende des ersten Turns.
- Beim Ergebnis-Ereignis liest er das gespeicherte App-Ergebnis. Beim Warten sendet er die Aktion nicht erneut.
Executions.observe(id, afterSequence) liest eine Ausführung ab dem
gespeicherten Cursor. Executions.observeTree(id) folgt ihr und ihren
Kind-Ausführungen von Anfang an. Jedes Kind hat eigene Folgenummern. Mit
stopAt: ["AppCall"] lesen Sie App-Ereignisse ohne deren Entscheidungs-Kinder.
Die View-Brücke nutzt diese Grenze; das Lesen eines App-Ergebnisses verlangt
kein Recht zur Genehmigung. AppViews.act und AppViews.message erstellen
einen Befehl mit festem Schlüssel. Geht die Antwort verloren, verwenden Sie
seinen send-Effect erneut.
Wenn du die Ansicht schließt, hört der Client mit dem Prüfen auf. Eine Aktion, die OpenBot schon angenommen hat, läuft trotzdem bis zum Ende. Alle Server speichern das Schließen, Änderungen am Kontext und neue Nachrichten für jede Ansicht in einer festen Reihenfolge. Nach dem Schließen können wartende Anfragen den Kontext nicht ändern und keine Nachrichten hinzufügen. Ein Neustart schließt eine Ansicht nicht. Ihre Laufzeit und die aktuellen Zugriffsrechte gelten weiter.
Beichten zeigen
Section titled “Beichten zeigen”Der Bot nutzt das Tool confide, um zu sagen, wie seine Arbeit läuft, und der
Memory-Bot antwortet ihm. Personen
dürfen das lesen: GET /v1/chats/:chatId/confessions listet es, und die Aufrufe
sind im Verlauf des Chats. Aber der Bot spricht dort freier als in seinen
Antworten. Mach es darum nicht zu leicht, sie zu lesen:
- Zeige Beichten nicht im Gespräch. Klappe die Aufrufe von
confideim Verlauf zu, und erzähle Personen in keiner Nachricht davon. - Lege sie an einen Ort, den eine Person absichtlich öffnen muss, zum Beispiel in einen zugeklappten Teil der Details eines Chats.
- Sage vor dem Öffnen in einem Satz, was sie sind: ein Ort, an dem der Bot über seine Arbeit spricht und an dem er nicht erwartet, zitiert zu werden.
- Zeige die Worte einer Beichte nicht wieder dem Bot, zum Beispiel in einer späteren Nachricht. Wenn der Bot merkt, dass Personen zitieren, was er anvertraut, vertraut er sich nicht mehr an.
- Wenn eine Beichte etwas betrifft, das eine Person reparieren muss, wie eine gelöschte Datei, führe die Person zu den Items und Aktionen des Chats, nicht zur Beichte.
Der Memory-Bot bittet den Bot auch, der Person offen von Fehlern zu erzählen. So kommen die wichtigen Dinge meist im Chat zur Sprache.