API do host
Use esta página quando precisar dos métodos exatos do connectHost, dos limites e dos códigos de erro. Para a estrutura da pasta, o manifesto e a instalação, comece em Crie uma extensão.
import { connectHost, HostRequestError } from "@openchamber/sdk";
const host = connectHost();connectHost lança HOST_UNAVAILABLE quando não há window. Fora do OpenChamber ele devolve um cliente cujas chamadas são todas rejeitadas com HOST_UNAVAILABLE. Chame dispose() quando desmontar a página; chamadas ainda em andamento são rejeitadas com o mesmo código.
O que o OpenChamber envia para você
onReady dispara com o primeiro snapshot e de novo sempre que o OpenChamber o atualiza.
Não é um callback de montagem única. Monte a UI e registre assinaturas uma vez; depois atualize tema e contexto sem substituir campos ou rascunhos. Veja o exemplo protegido em UI kit. Listeners como onConnection entregam o valor atual imediatamente e podem repeti-lo em novos snapshots. Compare os campos relevantes antes de iniciar outra requisição e ignore respostas de requisições substituídas.
| Campo | O que é |
|---|---|
theme.mode | light ou dark |
theme.tokens | as cores do app (superfícies, texto, estados de interação, primária, success/warning/error/info), font, mono e radius. Passe isso para applyHostReady antes de montar a UI. |
locale | a tag de idioma do app |
directory | o diretório do projeto atual, ou null |
session | { id, title, busy } ou null. Sem título, o id da sessão é usado no lugar. busy é o status ao vivo. model e agent (o agente do OpenCode) aparecem quando a sessão os tem. |
surface | panel na barra lateral, dialog na janela de anexar, page em tela cheia, status no painel Work Status, file como editor de arquivos no Files, block em um bloco de UI no chat |
item | para que esta superfície foi aberta, ou null: o item anexado em que o usuário clicou (os mesmos campos que você passou para attach, incluindo data), uma mensagem (kind: "message") ou uma sessão (kind: "session") vinda de uma das suas ações declaradas. Veja Ações. |
connection | { connected, account } da sua integração |
settings | os valores dos campos que você declarou em integration.settings |
Tokens de acesso nunca aparecem aqui nem no resultado de um request.
onDirectory, onSession, onSessionLifecycle, onConnection, onSettings e onItem repetem o último valor quando você assina tarde, e depois continuam disparando conforme ele muda.
theme.tokens inclui primaryText, successText, warningText, errorText e infoText. Esses campos obrigatórios contêm cores de texto calculadas pelo host para fundos neutros e controles com tons suaves do UI kit, não para preenchimentos de cor sólida. applyHostReady aplica essas cores a cada snapshot. Veja UI kit para as variáveis CSS.
Métodos
| Chamada | O que faz |
|---|---|
toast({ kind, message }) | mostra um aviso no app. kind é info, success ou error. |
openUrl(url) | abre uma URL no navegador do usuário |
openSurface(surfaceId) | leva o app para aquela tela |
writeClipboard(text) | copia texto. De 1 a 32000 caracteres. |
compose({ text, mode? }) | coloca texto na caixa de chat sem enviar. mode é append (padrão) ou replace. De 1 a 16000 caracteres após remover espaços. |
attach({ ... }) | coloca um chip na caixa de chat, no mesmo lugar onde caem os itens do GitHub e do Linear. Só um chip por vez. |
startSession({ ... }) | cria uma sessão com aquele item anexado. Devolve { sessionId, sent }. Precisa da capacidade sessions. |
prompt({ text, send? }) | escreve na sessão atual, ou envia nela. Devolve { sent }. Enviar precisa da capacidade prompt. |
sessionLink({ ... }) | anexa um item à sessão atual sem criar uma nova |
close() | fecha a janela de anexar. Não faz nada na barra lateral. |
oauthStart() | abre a página de autorização do provedor, ou a do Linear para uma integração host: { provider: "linear" } |
oauthDisconnect() | esquece o token guardado, ou a conexão com o Linear |
request({ method, path, query?, body? }) | chama o apiOrigin da sua integração com o token do usuário anexado |
serviceRequest({ method, path, query?, body? }) | chama o serviço local da sua extensão (veja GUEST_SERVICES.md) |
serviceStatus() | stopped, starting, ready ou failed |
readFile(path) | lê um arquivo de texto. Devolve { content }. |
writeFile(path, content) | escreve um arquivo de texto de forma atômica, criando as pastas acima. Devolve { written: true }. |
listDir(path) | lista uma pasta. Devolve { entries: [{ name, kind }] }, kind é file, directory ou other. |
stat(path) | { kind, size, mtime }, kind é file, directory, other ou missing. |
generate({ prompt, system?, maxOutputTokens? }) | texto avulso do Small Model do usuário. Retorna { text }. Precisa da capacidade model. |
onResolve(handler) | registra o handler dos seus comandos de barra. Ele recebe { command, args } e devolve um item para anexar ou null. |
setBadge(count) | marca o seu ícone da barra lateral com um ponto e mostra o número (0 a 999) na dica dele, ou null para limpá-lo |
openCommit(sha) | abrir a visualização Diff nesse commit do projeto atual. sha tem de 7 a 64 caracteres hexadecimais; o OpenChamber lê o commit sozinho. |
setHeight(px) | definir a altura da sua seção do Work Status. O OpenChamber a mantém entre 24 e 320 pixels; conteúdo mais alto rola dentro da sua página. Em outros lugares é ignorado. |
onFileOpen(listener) | editores de arquivos: receber o arquivo a editar, { path, name, readOnly } mais encoding: "text" com content, ou encoding: "binary" com bytes. Também faz Cmd/Ctrl+S salvar dentro da sua página. |
onFileSnapshot(handler) | editores de arquivos: devolver { content, version } (ou { bytes, version } num editor binário), o arquivo editado inteiro. O OpenChamber chama isso para salvar e antes de trocar para a visualização do código-fonte ou para tela cheia. |
onFileSaved(listener) | editores de arquivos: o snapshot com essa version está no disco. Responda com reportFileChange. |
reportFileChange({ dirty, edited }) | editores de arquivos: dizer ao OpenChamber se há alterações não salvas e se o documento acabou de mudar |
requestFileSave() | editores de arquivos: salvar agora, como Cmd/Ctrl+S |
reportFileUnsupported() | editores de arquivos: este arquivo não pode ser aberto aqui, mostrar o código-fonte no lugar |
attach, startSession e sessionLink
Os três recebem os mesmos campos de item:
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 é o id do seu painel; o OpenChamber o sobrescreve com o seu id de qualquer forma. id é o seu próprio identificador do item. kind é issue (padrão) ou pull. text é contexto opcional para o modelo, de 1 a 16000 caracteres após remover espaços. data é um JSON opcional seu (status, comentários, o que quiser), até 16000 caracteres depois de serializado. O OpenChamber o guarda junto com o chip e o devolve sem alterações em ctx.item quando o usuário clica no chip; ele nunca chega ao modelo.
startSession aceita projectId sem trocar o projeto ativo. Omitir worktree usa o diretório de destino; true cria um worktree com nome gerado; { kind: "existing", directory } escolhe um existente; { kind: "new", name?, baseBranch? } define um novo. O nome também vale para a branch. navigation é "preserve" por padrão; "open" abre o novo chat. Modelo, agente e variante da primeira mensagem são capturados no início.
O resultado inclui sessionId, directory, sent, linked e worktree opcional. sent é sent, no-model, skipped ou failed; linked: false indica falha ao salvar o item. Um worktree mantido após falha de preparação ou criação da sessão retorna sessionId: null e failure: "bootstrap-failed" | "session-create-failed". Confira antes de tentar novamente. A espera é de até 180 segundos; timeout não comprova que a criação foi desfeita.
sessionLink anexa o item à sessão aberta agora. Sem projeto ou sem sessão, o erro é NO_SESSION.
Limites que o cliente aplica antes de enviar qualquer coisa:
| Campo | Máximo de caracteres |
|---|---|
id | 128 |
title | 200 |
url | 2000 |
text | 16000 |
data (serializado) | 16000 |
author | 80 |
| cada nome de branch | 200 |
prompt e ciclo de vida da sessão
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;});Sem send, prompt substitui o texto da caixa de chat. Com send: true ele envia a mensagem usando o modelo e o agente que o usuário selecionou. A extensão nunca os escolhe. Sem sessão aberta, o erro é NO_SESSION. Enviar enquanto a sessão está ocupada é SESSION_BUSY; escrever na caixa funciona mesmo ocupada. O resultado é { sent } com os mesmos valores de startSession.
onSessionLifecycle diz o que a sessão está fazendo. phase é started enquanto o modelo trabalha, completed quando ele fica ocioso e failure em um status inesperado. Um ouvinte que assina tarde recebe a fase atual na hora.
Projetos, sessões ao vivo e armazenamento
listProjects(), listWorktrees(projectId) e listSessions(projectId) usam a permissão existente sessions e os stores compartilhados, sem varrer Git por chamada ou expor conversas. Projetos incluem ID, nome e diretório; worktrees também incluem branch e disponibilidade. Sessões incluem datas, pai, worktree e apenas os itens anexados por esta extensão. Sessões arquivadas conhecidas estão incluídas.
await onProjects(listener), await onWorktrees(projectId, listener) e await onSessions(projectId, listener) retornam uma função para cancelar a assinatura. Trate erros de registro. Primeiro chega um snapshot, depois mudanças. Até 32 assinaturas por iframe; dispose(), fechar, pausar, remover ou trocar de servidor as liberam. state é loading, ready ou error, com coverage por diretório nas sessões. Erros preservam dados; só ready confirma uma lista vazia completa.
activity é unknown, idle, running, retrying, waiting-permission ou waiting-question. outcome é completed, failed ou null observado, guardado apenas em memória para até 2.000 sessões. Idle após um erro mantém a falha até a próxima execução. Isso não conclui sua tarefa. openSession(sessionId) abre o chat explicitamente.
host.storage.get(key), set(key, value), delete(key) e keys() guardam JSON próprio no servidor conectado sem outra permissão. Chave ausente retorna undefined; null salvo continua null. Chaves têm 1 a 128 caracteres, cada valor até 64 KiB UTF-8, o total até 2 MiB e 2.000 chaves. Inclua o ID do projeto na chave para dados por projeto. Escritas são serializadas e atômicas; falhas preservam os dados. Desinstalar remove o armazenamento.
request
const user = await host.request({ method: "GET", path: "/api/v2/user" });path começa com / e não tem esquema nem host; o OpenChamber o junta ao apiOrigin do seu manifesto e adiciona o cabeçalho Authorization. Integrações com token enviam o token colado como está, como Bearer <token> quando o manifesto define token.scheme: "bearer", ou como Basic base64(username:token) quando define "basic". Integrações OAuth e do Linear sempre enviam Bearer. O resultado é { status, body }. O corpo é texto; faça o parse do JSON você mesmo.
Uma integração do Linear também pode fazer GET /api/linear/issues/get, que o OpenChamber responde pela própria rota do Linear.
Sem resposta em 20 segundos, o erro é 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
Quando o manifesto declara contributes.service, o OpenChamber inicia esse processo e sua página conversa com ele pelo mesmo tipo de chamada. A página nunca abre o socket por conta própria.
const status = await host.serviceStatus();const result = await host.serviceRequest({ method: "GET", path: "/containers" });path segue as mesmas regras de request. Declarar um serviço local adiciona service às capacidades que o usuário aprova na instalação; até lá, todo serviceRequest é NO_SERVICE. O contrato completo está no arquivo GUEST_SERVICES.md do pacote.
Eventos de sessão
Sua página só sabe de uma sessão enquanto ela está aberta. Seu serviço pode saber mais: declare contributes.sessionEvents ao lado de contributes.service, e o OpenChamber envia ao serviço os eventos que você lista, de todos os projetos, com ou sem uma janela aberta.
"service": { "entry": "service/main.js", "runtime": "host" },"sessionEvents": { "events": ["turn-finished", "question-asked"], "content": true }Os eventos são turn-started, session-created, session-archived, session-deleted, turn-finished (o agente terminou e não espera por nada: nenhum subagente rodando, nenhuma permissão ou pergunta em aberto, nenhum passo de objetivo), turn-failed, permission-asked e question-asked. Um turno que o usuário interrompeu não envia nada. Aprovar a extensão é a assinatura; o diálogo lista os eventos abaixo da linha do serviço. Sem content, o serviço recebe só identificadores: a sessão, a pasta do projeto dela, os itens que o usuário anexou a ela. Com "content": true, o manifesto também pede uma capacidade que o usuário vê como uma linha separada, Ler o que o agente diz em segundo plano, e cada evento carrega a resposta final do agente, os campos da pergunta ou o detalhe da permissão.
Cada evento chega como um POST /session-events com o bearer do serviço. Leia-o com readSessionEventRequest e responda { "ok": true }:
import { SESSION_EVENTS_PATH, readSessionEventRequest } from "@openchamber/sdk";
// inside your service's request handler, after the bearer checkif (url.pathname === SESSION_EVENTS_PATH) { const request = readSessionEventRequest(body); if (!request) return json(res, 400, { ok: false, error: "not a session event" }); const { event, content } = request; if (event.type === "turn-finished" && event.project.directory === myProject) { await postComment(event.linked, content?.type === "turn-finished" ? content.reply : ""); } return json(res, 200, { ok: true });}Os eventos de todos os projetos são enviados; compare event.project.directory com as suas próprias configurações e responda { "ok": true } para o resto. A entrega é no máximo uma vez e em ordem, uma requisição por vez; uma resposta com falha descarta seus eventos por 30 segundos, e nada é reenviado depois que o OpenChamber ou o seu serviço ficou fora do ar. O serviço não precisa de painel para isso. O exemplo session-events-log registra cada evento em um arquivo.
Chamar o OpenChamber a partir do seu serviço
Um serviço pode agir sem nenhuma janela aberta. createServiceHost é o caminho de volta dele para o OpenChamber:
import { createServiceHost } from '@openchamber/sdk';
const host = createServiceHost({ env: process.env });
const { sessionId } = await host.sessions.start({ prompt: 'Summarize the failed deploy', directory });await host.sessions.send(sessionId, 'Now write the incident note');await host.setInstructions(directory, 'Deploys run through the release branch.');await host.emit('deploy-finished', { id: 42 });sessions.start, sessions.send, sessions.retry, sessions.status e sessions.stop precisam de contributes.sessionControl, que o usuário aprova como uma linha separada. sessions.lastReply precisa de sessionControl: { content: true }. Uma mensagem de sessions.send fecha um formulário ou uma página que o agente espera nessa sessão, como faz uma mensagem digitada no chat.
Para levar os pedidos do agente para outro lugar, como um app de chat, adicione answers: true. O usuário aprova isso como mais uma linha, e então o serviço pode responder em nome do usuário em qualquer sessão: sessions.answerPermission(sessionId, requestId, { decision: 'allow' }) (ou 'deny' com uma message que o agente lê) para um evento permission-asked, e sessions.answerForm(sessionId, formId, values) para um evento question-asked cujo event.question.kind é form. O conteúdo do evento lista os campos do formulário com sua key e suas opções. null no lugar dos valores dispensa o formulário. Uma permissão concedida vale só para aquele pedido.
setInstructions precisa de contributes.instructions e complementa o que os agentes leem, em uma pasta ou, com null, em todo lugar.
emit chega aos seus frames abertos por meio de host.onServiceEvent, então um painel para de fazer polling. Nada é reenviado, por isso um frame que abre depois lê primeiro o estado atual. readSettings dá ao serviço as configurações da integração, incluindo os campos secret, e needsConfiguration mostra uma mensagem no cartão da extensão.
Rodar sem ninguém pedir
Um serviço inicia na primeira requisição, e o serviço de um papel para quando fica ocioso. service.keepRunning: true inicia o serviço junto com o OpenChamber e o traz de volta quando ele para. service.schedule acorda o serviço em horários definidos:
"service": { "entry": "service/main.js", "runtime": "host", "keepRunning": true, "schedule": [{ "id": "nightly", "cron": "0 3 * * *", "timezone": "Europe/Kyiv" }]}Uma execução é um POST /schedule. Leia-a com readServiceScheduleRequest e responda 200. Uma execução perdida enquanto o OpenChamber estava fora do ar não volta. Um serviço que precisa de eventos externos mantém a própria conexão ou faz polling.
Linhas no menu @
contributes.mentions: { label } adiciona uma seção ao menu @ da caixa de chat. Seu serviço responde a POST /mentions/search com { items: [{ id, title, subtitle?, url? }] } em até 2 segundos, e a POST /mentions/resolve com { text } para a linha que o usuário escolhe. O texto vai para o agente com a mensagem, e o usuário vê um chip. Um serviço lento não adiciona linhas e nunca segura os arquivos; uma linha que não pode ser lida não deixa chip e avisa isso.
Blocos no chat
contributes.blocks adiciona componentes que os agentes podem escrever em um bloco de UI. Sua própria página desenha cada um, no mesmo sandbox e com as mesmas permissões do seu painel.
"blocks": [{ "name": "JiraIssue", "description": "A Jira ticket", "args": ["key"], "entry": "blocks/issue.html" }]O agente escreve JiraIssue("OPE-1"), e sua página lê a chamada:
host.onBlock(({ args }) => draw(args.key));Chame host.setHeight(px) a partir de um ResizeObserver no seu elemento raiz. O frame preenche a caixa sem padding, então adicione o seu. Até 4 componentes por extensão, porque cada assinatura é texto que cada sessão lê.
Um bloco pode responder ao agente, como faz um botão dentro de um bloco. Chame host.answerBlock(text) a partir de um clique: o agente recebe o texto e começa o próximo turno. É uma resposta por bloco, e block.answered a guarda depois de recarregar, então nesse caso desenhe o bloco como respondido.
host.onBlock(({ args, answered }) => draw(args.key, answered));button.onclick = () => host.answerBlock('Close this ticket');Defina "answers": true em um componente que faz isso. Os agentes são orientados a perguntar com ele em vez da escolha integrada quando a descrição dele se encaixa, e uma página que espera uma resposta omite o botão Enviar. Abaixo de um bloco assim, o OpenChamber oferece o mesmo campo de nota dos próprios formulários: o agente lê a nota junto com sua resposta, e uma nota sozinha também é uma resposta.
Os argumentos são escritos por um modelo, então texto de uma página web ou de um ticket pode direcioná-los. Trate-os como não confiáveis e não faça nada com efeitos colaterais até o usuário clicar.
Canais de notificação
Um serviço pode entregar as notificações do usuário, além do desktop e do celular: declare provides: ["notifications"] e descreva o canal em contributes.notifications.
"service": { "entry": "service/main.js", "runtime": "host", "provides": ["notifications"] },"notifications": { "label": "Slack", "instances": { "kind": "fixed", "origin": "https://slack.com" }, "token": { "header": "authorization", "scheme": "bearer", "scopes": ["chat:write"] }}A partir dessa descrição, o OpenChamber desenha um card de conta para o canal em Configurações → Integrações e guarda a conta: o usuário a conecta ali, o token fica no OpenChamber e o seu serviço nunca o vê. Com uma conta conectada, toda notificação que o OpenChamber enviaria ao celular vai também para o seu serviço, depois das chaves de notificação do próprio usuário, e só enquanto nenhuma janela está visível, a menos que o usuário tenha escolhido ser avisado sempre. Pausar a extensão interrompe isso.
O OpenChamber encaminha três chamadas para POST /notifications: auth.probe (este servidor é um para o qual você entrega), auth.verify (a quem este token pertence) e notify (uma notificação). Leia cada uma com readNotificationsProviderRequest, e alcance o servidor do canal só pelo helper de saída, que envia a sua requisição ao OpenChamber com o handle da chamada; o OpenChamber adiciona o token e mantém a requisição no servidor da conta.
import { NOTIFICATIONS_PROVIDER_PATH, createNotificationsOutbound, readNotificationsProviderRequest } from "@openchamber/sdk";
const outbound = createNotificationsOutbound({ env: process.env });
// inside your service's request handler, after the bearer checkif (url.pathname === NOTIFICATIONS_PROVIDER_PATH) { const request = readNotificationsProviderRequest(body); if (!request) return json(res, 400, { ok: false, error: { message: "not a notifications call" } }); if (request.method === "notify") { const { notification } = request.input as { notification: GuestNotification }; const answer = await outbound.request(request.session.handle, { method: "POST", path: "/api/chat.postMessage", headers: { "content-type": "application/json" }, body: JSON.stringify({ channel: "#agents", text: `${notification.title}: ${notification.body}` }), }); return json(res, 200, answer.status === 200 ? { ok: true, data: { delivered: true } } : { ok: false, error: { message: `Slack answered ${answer.status}`, status: answer.status } }); } // auth.probe → { ok: true, data: { supported: true } }; auth.verify → { ok: true, data: { account: { id, login } } }}Para qual sala ou canal um projeto vai é escolha da sua extensão: a notificação carrega directory e sessionName. No modo empresarial, ela carrega só o título genérico, como o celular. O exemplo notification-webhook é um canal completo com um receptor para testá-lo.
Rastreadores
Um serviço pode ser um rastreador de itens de trabalho ao lado do Linear: declare provides: ["tracker"] e descreva o rastreador em contributes.tracker. O OpenChamber passa a listá-lo ao lado do Linear em todo lugar onde rastreadores aparecem, só a partir do manifesto, antes de o serviço sequer rodar.
"service": { "entry": "service/main.js", "runtime": "host", "provides": ["tracker"] },"tracker": { "wire": 1, "label": "YouTrack", "instances": { "kind": "user", "label": "Server address", "placeholder": "https://youtrack.example.com" }, "auth": { "token": { "header": "authorization", "scheme": "bearer", "createPath": "/users/me?tab=account-security" } }, "capabilities": { "stateUpdate": true, "labels": true, "priority": true, "projectMapping": true, "sessionComments": true }, "stateFilters": ["open", "all", "todo", "in-progress", "done"], "identifier": "key-number", "urls": { "item": ["/issue/{id}", "/{segments+}/issue/{id}"] }}O usuário conecta contas no card do rastreador sob Contas de extensões: um token, ou um login pelo navegador quando você declara auth.oauth (o OpenChamber conduz o login com o client id que o usuário informa e guarda os tokens). O seu serviço nunca vê uma credencial. urls.item são modelos para o endereço de um item, com {id} no lugar do identificador (também como ?name={id} quando ele viaja na query); o OpenChamber os compila para que links colados virem itens vinculados. identifier diz como a chave se parece: key-number (PROJ-42), number ou short-code.
O OpenChamber encaminha nove chamadas para POST /tracker: auth.probe e auth.verify, as leituras items.list, items.get, items.liveSummaries, states.list e containers.list, e as escritas items.updateState e comments.create. Leia cada uma com readTrackerProviderRequest, responda nas formas neutras de item e alcance o servidor do rastreador só pelo helper de saída, que envia a sua requisição ao OpenChamber com o handle da chamada; o OpenChamber adiciona a credencial e mantém a requisição no servidor da conta. O handle de uma leitura envia só GET.
import { TRACKER_PROVIDER_PATH, createTrackerOutbound, readTrackerProviderRequest } from "@openchamber/sdk";
const outbound = createTrackerOutbound({ env: process.env });
// inside your service's request handler, after the bearer checkif (url.pathname === TRACKER_PROVIDER_PATH) { const request = readTrackerProviderRequest(body); if (!request) return json(res, 400, { ok: false, error: { message: "not a tracker call" } }); if (request.method === "items.get") { const answer = await outbound.request(request.session.handle, { method: "GET", path: `/api/issues/${encodeURIComponent(String(request.input.id))}` }); if (answer.status === 404) return json(res, 200, { ok: true, data: { item: null } }); if (answer.status !== 200) return json(res, 200, { ok: false, error: { message: `YouTrack answered ${answer.status}`, status: answer.status } }); return json(res, 200, { ok: true, data: { item: toTrackerItem(JSON.parse(answer.body)) } }); }}Quando o usuário liga os comentários de sessão, o OpenChamber chama comments.create com “session started”, “completed” ou “failed” e um link para a sessão, uma vez cada; o comentário não carrega mais nada. O exemplo tracker-provider é um rastreador completo com um servidor falso para testá-lo.
Provedores de uso
Um serviço pode informar o uso ao lado dos provedores integrados: declare provides: ["quota"] e liste em contributes.quota.providers os provedores do OpenCode cujas credenciais ele precisa. O OpenChamber chama o provedor de ext-<panel.id>, desenha o cartão dele com o nome e o ícone da extensão e o consulta como consulta um provedor integrado.
"service": { "entry": "service/main.js", "runtime": "host", "provides": ["quota"] },"quota": { "providers": ["openrouter"] }O diálogo de aprovação lista esses provedores, e uma versão que muda a lista pergunta de novo. A cada atualização o OpenChamber envia uma chamada, usage.read, para POST /quota, com input.credentials trazendo a credencial que o OpenCode guarda para cada provedor listado para o qual ele tem uma (uma chave de API, ou os tokens de um login no navegador) e nada mais do armazenamento. Um provedor listado para o qual o OpenCode não guarda credencial aparece como não configurado e o serviço não é chamado; uma lista vazia é permitida, para um provedor cuja chave fica nas configurações da própria extensão.
Leia o corpo com readQuotaProviderRequest e responda { ok: true, data } com o resultado que um provedor integrado calcula: ok, configured, error, planLabel e usage.windows por nome de janela, cada uma com usedPercent, windowSeconds, resetAt (época em milissegundos) e um valueLabel ou value opcional, mais usage.models para as janelas por modelo. O OpenChamber deriva a parte restante, a contagem até o reinício e os textos formatados, então o cartão bate com o de um provedor integrado.
import { QUOTA_PROVIDER_PATH, readQuotaProviderRequest } from "@openchamber/sdk";
// inside your service's request handler, after the bearer checkif (url.pathname === QUOTA_PROVIDER_PATH) { const request = readQuotaProviderRequest(body); if (!request) return json(res, 400, { ok: false, error: { message: "not a usage call" } }); const credential = request.input.credentials.openrouter; const bearer = credential.type === "api" ? credential.key : credential.access; const answer = await fetch("https://openrouter.ai/api/v1/key", { headers: { authorization: `Bearer ${bearer}` } }); if (answer.status === 401) return json(res, 200, { ok: true, data: { ok: false, configured: true, error: "OpenRouter refused the key.", usage: null } }); return json(res, 200, { ok: true, data: toUsage(await answer.json()) });}O serviço responde em 20 s e para depois de dez minutos sem chamada; a página de uso atualiza a cada três minutos enquanto está aberta. O exemplo quota-provider é um provedor completo com uma API de uso falsa para testar.
Arquivos
Um caminho relativo fica dentro do projeto aberto e precisa da capacidade files. Um caminho que começa com / ou ~/ precisa bater com um padrão declarado em contributes.filesystem e precisa da capacidade filesystem. Veja Crie uma extensão para as regras.
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");Limites: caminho de até 1024 caracteres, conteúdo de até 2,000,000 caracteres, listagem de até 2000 entradas. Passar do limite de conteúdo é FILE_TOO_LARGE.
generate
const { text } = await host.generate({ prompt: notes, system: "Summarize in one sentence." });prompt tem de 1 a 64.000 caracteres após o trim, system até 8.000, maxOutputTokens de 1 a 4.000. Nada entra em uma sessão e nenhum histórico é mantido; a extensão nunca escolhe o modelo. A chamada espera até 90 segundos. Nenhum Small Model utilizável é NO_MODEL; um erro do modelo é MODEL_FAILED. Veja Gerar texto.
Códigos de erro
Chamadas que falham lançam HostRequestError. code é um destes; message traz mais detalhes.
| Código | Quando |
|---|---|
HOST_UNAVAILABLE | sem window, fora do OpenChamber, ou dispose() foi executado |
HOST_TIMEOUT | sem resposta por 20 segundos |
HOST_REJECTED | o OpenChamber recusou, ou respondeu com um código que este SDK não conhece |
DISCONNECTED | sem token nem conexão com o Linear para a sua integração |
BAD_PATH | path estava malformado ou tentou sair da origem permitida |
NO_INTEGRATION | o manifesto não tem integration |
NO_SESSION | prompt ou sessionLink sem sessão aberta |
SESSION_BUSY | prompt({ send: true }) enquanto a sessão estava ocupada |
DISABLED | o usuário pausou a extensão em Configurações → Extensões |
NO_SERVICE | nenhum serviço local declarado, não aprovado ou não está rodando |
NOT_GRANTED | o usuário não aprovou a capacidade que esta chamada precisa |
SERVICE_FAILED | o serviço local travou ou nunca ficou pronto |
NO_DIRECTORY | um caminho de arquivo relativo foi usado sem projeto aberto, ou openCommit sem projeto aberto |
NOT_FOUND | readFile em um arquivo que não existe, ou openCommit em um commit que não está no projeto |
FILE_TOO_LARGE | conteúdo do arquivo acima do limite, na leitura ou na escrita |
DENIED | o sistema operacional recusou a operação no arquivo |
NO_MODEL | generate sem Small Model disponível |
MODEL_FAILED | o Small Model retornou um erro |
UNSUPPORTED | este OpenChamber não consegue fazer a chamada, por exemplo openCommit onde não há visualização Diff |
Relacionado
- Crie uma extensão para a pasta, o manifesto, a instalação e as capacidades
- Kit de UI para botões, campos, listas e os demais componentes