Перейти до вмісту
Навігація Відкрити escЗакрити

Зробити розширення

Використовуйте @openchamber/sdk, щоб додати власну панель в OpenChamber. Розширення - це невелика вебсторінка, яку OpenChamber показує на правій рейці. Із застосунком вона спілкується через connectHost(): може читати поточний проєкт і сесію, показувати тости, вставляти текст у поле чату, прикріплювати задачу до сесії, а зі згоди користувача ще й створювати сесії та надсилати повідомлення.

Розширення працюють в OpenChamber у вебі та на десктопі. VS Code і мобільний застосунок їх поки не завантажують.

Що таке розширення

Тека з трьома файлами:

  • package.json з блоком openchamber (маніфест)
  • panel/index.html, сторінка, яку показує OpenChamber
  • panel/main.js, ваш скрипт, зібраний в один класичний файл (IIFE, не ES-модуль, бо сторінка завантажується в ізольованому iframe)

OpenChamber ніколи не компілює ваш код. Віддавайте вже зібраний .js. У SDK є команда збірки; підійде й будь-який інший бандлер, який видає IIFE.

Terminal window
npm install @openchamber/sdk
bunx openchamber-guest-bundle panel/main.ts panel/main.js

Команда збірки працює на Bun. Без Bun беріть esbuild або будь-який інший бандлер з --format=iife --platform=browser.

Повне робоче розширення, усі три файли, є на сторінці Приклад. Скопіюйте його і замініть API та список.

Підключіть main.js в index.html звичайним <script src="main.js"></script>.

Встановити під час роботи

  1. Запустіть OpenChamber у вебі або на десктопі.
  2. Відкрийте Налаштування → Розширення.
  3. Вставте абсолютний шлях до своєї теки і натисніть Додати.

OpenChamber читає маніфест і показує діалог зі списком того, що просить розширення (див. Дозволи). Підтвердьте, і ваша іконка з’явиться на рейці. Натисніть її, і сторінка завантажиться.

Можна також додати локальний .zip або https-посилання на git-репозиторій чи zip-файл. Оновлюватися вміє лише встановлення з git: підніміть version у package.json і запуште, і OpenChamber запропонує оновлення, коли користувач наступного разу відкриє Налаштування → Розширення. #tag чи #branch в URL закріплює, за чим саме воно стежить. Їх OpenChamber копіює у свою теку даних і запускає з тієї копії, тому віддавайте зібрані файли, а не node_modules чи TypeScript-джерела. Видалення розширення видаляє копію. Розширення з теки працює прямо з вашої теки, тож можна редагувати, перезбирати й перезавантажувати.

Маніфест

{
"name": "@acme/hello",
"version": "1.0.0",
"openchamber": {
"apiVersion": 1,
"engines": { "openchamber": ">=1.24.0" },
"contributes": {
"panel": {
"id": "acme-hello",
"name": "Hello",
"icon": "window",
"entry": "panel/index.html"
},
"attach": "dialog",
"page": true,
"capabilities": ["prompt", "sessions"],
"integration": {
"name": "Acme",
"description": "Tasks from Acme",
"token": {
"apiOrigin": "https://api.acme.example",
"account": { "path": "/me", "name": "login" },
"scheme": "bearer"
},
"settings": [{ "id": "list-id", "label": "List ID" }]
}
}
}
}

version обов’язкова і має бути semver. Налаштування → Розширення показує її на картці.

apiVersion дорівнює 1. Будь-яке інше значення OpenChamber відхиляє.

engines.openchamber необов’язкове. Вкажіть найстарішу версію OpenChamber, з якою працює ваше розширення, як 1.24.0 або >=1.24.0. Старіші збірки відмовляться його встановлювати замість того, щоб ламатися пізніше.

contributes.panel описує пункт на рейці. id пишеться в kebab-case і має бути унікальним серед встановлених розширень. icon - це назва іконки Remixicon (RiWindowLine стає window) або SVG-файл у вашій теці, наприклад icon.svg. entry - HTML-файл у вашій теці; пропустіть його для розширення, яке оголошує лише tools (див. Ваші інструменти в чаті).

contributes.attach необов’язкове. Воно додає ваше розширення в меню + біля поля чату, щоб користувач міг вибрати задачу і прикріпити її до сесії.

  • "dialog" відкриває вашу сторінку у вікні
  • true або "panel" замість цього відкриває панель на рейці
  • { "mode": "dialog", "entry": "panel/attach.html" } відкриває у вікні окрему вашу сторінку, тож вибір не мусить ділити код із панеллю на рейці
  • без цього поля розширення в тому меню не з’являється

ctx.surface має значення panel, dialog або page. Натискання прикріпленого елемента передає його дані в ctx.item; в інших випадках це null.

contributes.page: true додає повноекранну версію панелі в меню Сторінки розширень над списком сесій. { "entry": "panel/page.html", "title": "Board" } задає окремий HTML і необов’язковий заголовок. panel.entry залишається обов’язковим, ізоляція та дозволи спільні з панеллю. Сторінку відкриває лише користувач. Перезавантаження, пауза, видалення чи зміна сервера закривають її.

Для дошки є host.storage та списки проєктів, worktree і сесій із живими станами. startSession може працювати в іншому проєкті, не закриваючи дошку. Деталі в Host API.

contributes.integration необов’язкове. Воно додає картку в Налаштування → Інтеграції, де користувач підключає обліковий запис. Див. Облікові записи і мережа.

contributes.service необов’язкове. Воно оголошує локальний сервіс (процес, який OpenChamber запускає поруч із розширенням) для речей, до яких вебсторінка не дотягнеться, наприклад сокета Docker. Цей процес працює з повним доступом користувача і без пісочниці, тому діалог підтвердження попереджає про нього; оголошуйте його лише тоді, коли сторінка не може впоратися із завданням. Див. файл пакета GUEST_SERVICES.md. Сервіс із provides: ["browser"] може також замінити браузер агента: він відповідає на дії browser.* на сервері, тож агенти працюють з вебом без відкритого десктопного застосунку, а користувач обирає його в Налаштування → Інструменти OpenChamber. Такому сервісу панель не потрібна.

Дозволи

Щоб намалювати панель і прочитати поточну сесію, дозвіл не потрібен. Він потрібен для всього, що діє від імені користувача. Перелічіть такі речі в contributes.capabilities:

ДозвілЩо дозволяє
promptнадсилати повідомлення в сесію користувача (prompt({ send: true }), startSession з text)
sessionsпереглядати проєкти, worktree та стани сесій; створювати й відкривати сесії в доданих проєктах
filesчитати й писати файли всередині відкритого проєкту (readFile, writeFile, listDir, stat з відносним шляхом)
modelодноразова генерація тексту через Small Model користувача (generate), поза будь-якою сесією

Ще чотири додаються автоматично: network, коли ви оголошуєте integration, service, коли оголошуєте service, filesystem, коли оголошуєте шаблони filesystem, і conversation, коли дія сесії просить повідомлення (див. Дії, команди та бейдж).

Користувач бачить повний список один раз, під час встановлення розширення, і або підтверджує його, або видаляє розширення. Якщо нова версія просить більше, діалог з’являється знову. Виклик, якому потрібен непідтверджений дозвіл, завершується помилкою NOT_GRANTED.

Генерація тексту

З дозволом model host.generate просить Small Model користувача дати одноразову відповідь: резюме, заголовок, чернетку. Нічого не потрапляє в сесію, історія не зберігається, і розширення ніколи не обирає провайдера. OpenChamber використовує ту саму модель, яку використовує для власної фонової роботи, обрану в Налаштування → Сесії → Small Model, або підібрану автоматично серед провайдерів, до яких увійшов користувач.

const { text } = await host.generate({
prompt: task.description,
system: "Write a one-line summary. Return only the summary.",
maxOutputTokens: 200,
});

Коли жодна модель недоступна, виклик завершується помилкою NO_MODEL; модель, що повернула помилку, — MODEL_FAILED. Промпти обмежені 64 000 символів, а виклик чекає до 90 секунд. Користувач бачить цей дозвіл як «Використовувати ваш Small Model», і це коштує йому токенів, тож тримайте промпти короткими й викликайте це по кліку, а не на кожне натискання клавіші.

Облікові записи і мережа

Ваша сторінка працює в пісочниці і не може ходити в інтернет напряму. Оголосіть integration, і OpenChamber робитиме запити за вас через host.request, додаючи токен користувача. Токен ніколи не потрапляє на вашу сторінку.

  • token: користувач вставляє API-токен на картці в Налаштування → Інтеграції. apiOrigin - єдине джерело, яке може викликати request. account необов’язкове: GET-шлях і назва поля, щоб картка могла показати, хто підключений. scheme каже, як надсилати токен; див. таблицю нижче. Перевірте в документації API провайдера, який заголовок він очікує.
  • oauth: користувач вставляє client id, а OpenChamber проводить авторизацію. Потрібні authorizeUrl, tokenUrl і apiOrigin.
  • host: { "provider": "linear" }: використати обліковий запис Linear, який уже підключено в OpenChamber. Client id не потрібен.

Що надсилає кожен scheme:

schemeЗаголовок, який надсилає OpenChamberКоли використовувати
не вказаноAuthorization: <token>API документує голий токен у заголовку
"bearer"Authorization: Bearer <token>API документує bearer або personal access token
"basic"Authorization: Basic base64(username:token)API документує HTTP Basic auth з іменем користувача (часто це email) і API-токеном; картка запитує обидва, а usernameLabel задає підпис першого поля

settings додає на картку прості текстові поля. Їхні значення приходять у ctx.settings.

Файли

Ваша сторінка не може торкатися диска сама. OpenChamber читає і пише за неї, у межах, які підтвердив користувач.

  • Відносний шлях (README.md, src/index.ts, .) означає відкритий проєкт. Потрібен дозвіл files. Немає відкритого проєкту - NO_DIRECTORY.
  • Шлях, що починається з / або ~/, означає будь-яке інше місце. Він має збігатися з одним із шаблонів, які ви оголошуєте в contributes.filesystem, і користувач бачить саме ці шаблони в діалозі підтвердження:
"filesystem": ["~/.config/opencode/opencode.json", "~/notes/**"]

У шаблонах * означає один сегмент шляху, а ** - будь-яку глибину. Усе поза ними - BAD_PATH, навіть після підтвердження. .. не дозволяється ніколи.

const config = await host.readFile("~/.config/opencode/opencode.json");
await host.writeFile("~/.config/opencode/opencode.json", nextJson);
const { entries } = await host.listDir(".");

Запис атомарний: OpenChamber пише тимчасовий файл і перейменовує його, тож читач ніколи не побачить напівзаписаний файл. Файли читаються і пишуться як текст UTF-8, до 2 МБ.

Дії, команди та бейдж

Крім рейки і меню +, розширення може з’являтися ще в трьох місцях. Усі вони передають розширенню item так само, як клік по чипу.

Дії на повідомленнях і сесіях. Оголосіть пункти меню, і OpenChamber покаже їх поруч із вбудованими:

"actions": [
{ "id": "create-task", "label": "Create task", "icon": "add-line", "where": "message", "roles": ["assistant"] },
{ "id": "summarize", "label": "Summarize session", "where": "session", "payload": ["messages"] }
]

Дія на повідомленні відкриває ваше розширення з ctx.item виду kind: "message": id і назва сесії, тека проєкту, id повідомлення, його роль і текст. Дія на сесії відкриває його з kind: "session" та id, назвою і текою сесії. Додайте "payload": ["messages"], і елемент також несе всю розмову, від найстарішого повідомлення, той самий текст, який дає експорт у Markdown. Для цього потрібен дозвіл conversation, який користувач бачить окремим рядком у діалозі схвалення. item.action каже, який пункт натиснули.

Слеш-команди, які прикріплюють. Оголосіть команду і обробіть її на сторінці:

"commands": [{ "name": "task", "description": "Attach a task by id" }]
host.onResolve(async ({ command, args }) => {
const task = await findTask(args.trim());
return task ? { providerId: "acme-tasks", id: task.id, title: task.title, url: task.url } : null;
});

Коли користувач вводить /task ABC-12 у полі чату, OpenChamber просить ваше розширення розв’язати її і прикріплює те, що ви повернули, як чип, нічого не відкриваючи. Поверніть null, якщо нічого не знайдено. Якщо ваша панель закрита, OpenChamber завантажує її у фоні для цього виклику. Ім’я, яке вже використовує OpenChamber або OpenCode, ігнорується.

Бейдж на іконці в рейці. host.setBadge(3) показує число на вашій іконці, наприклад кількість відкритих задач; host.setBadge(null) прибирає його. Відкриття панелі теж його прибирає.

Ваші інструменти в чаті

Коли ваш плагін OpenCode або MCP-сервер додає інструмент, чат показує його виклики з типовою іконкою і сирим виводом. Натомість опишіть, як вони мають виглядати, без жодного коду:

"tools": [
{
"match": "mcp.jira.*",
"name": "Jira",
"icon": "task-line",
"title": "{input.key}",
"subtitle": "{output.status}",
"output": "table",
"columns": ["key", "summary", "status"]
}
]

match це ім’я інструмента так, як його повідомляє OpenCode; * наприкінці збігається з префіксом. icon - це назва іконки Remixicon або .svg-файл у вашому пакеті, як і panel.icon. title і subtitle це шаблони над input, output і metadata виклику. output визначає, як малюється тіло: text, json (дерево), markdown, code з language, table з columns (рядки беруться з масиву виводу або з output.items) або auto для типового вигляду. Точний match перемагає шаблон із зірочкою, а за нічиєї виграє перше встановлене розширення. Дозвіл не потрібен: це змінює лише те, як малюються дані, що вже є в чаті.

Розширенню, яке лише оформлює інструменти, сторінка взагалі не потрібна: пропустіть panel.entry, і воно не матиме іконки на рейці, лише картку в Налаштування → Розширення. Без сторінки воно може оголошувати tools і більше нічого.

Перші рядки в панелі

import { connectHost } from "@openchamber/sdk";
const host = connectHost();
host.onReady((ctx) => {
document.body.dataset.theme = ctx.theme.mode;
document.body.dataset.surface = ctx.surface;
});

connectHost працює лише всередині OpenChamber. Якщо відкрити сторінку як звичайний файл, кожен виклик відхиляється з HOST_UNAVAILABLE.

Спершу збирайте панель з готових елементів @openchamber/sdk/ui: кнопки, поля, випадні списки, вкладки, списки тощо, оформлені кольорами і шрифтами застосунку, щоб панель відчувалася частиною OpenChamber. Власний HTML і CSS пишіть лише для того, чого в наборі немає. Див. UI-набір.

Дивіться також

  • Розширення про встановлення через SSH і вибір Git identity сервера
  • Host API: кожен метод connectHost, його обмеження і коди помилок
  • UI-набір: готові елементи
  • Приклад: повне розширення з трьох файлів, яке можна скопіювати