@notrealstudio/nr-ui-protocol
v0.12.1
Published
Универсальный протокол UI ⇄ бэкенд (v1): wire-типы + маппинг-хелперы, без движка. Модель сообщения ре-экспортируется из @notrealstudio/nr-chat-store.
Readme
@notrealstudio/nr-ui-protocol
Универсальный протокол UI ⇄ бэкенд (v1). Один провод между универсальным фронтом (dreams-web и наследники) и любым бэкендом: nr-engine/dreams, pi, opencode, будущие.
Канон: nr-engine/docs/specs/nr-ui-protocol-spec.md
Пакет содержит только типы + маппинг-хелперы. Без движка — адаптеры
pi/opencode подключают его, не таща nr-engine (спека §10.7). Единственная
зависимость — node builtins.
Установка
npm i @notrealstudio/nr-ui-protocolЧто внутри
Типы (спека §2–5)
- Session:
SessionInfo,Participant - Message:
Message,Usage,MessageFlags,MessageMeta - Part (закрытое ядро + escape hatch
custom):Part,TextPart,ThinkingPart,ToolUsePart,ToolResultPart,FilePart,ImagePart,ErrorPart,CustomPart; opaque-вариантыAnyPart/UnknownPart - Модели (models-spec §2, capability
models):ModelInfo,SessionModel(он жеModelSelectionвnr-chat-store),SessionInfo.model,SessionCreateOpts.model,ModelsOps(list/set) - Пространство (workspace-spec §2, capability
workspace):WorkspaceInfo,WorkspaceOps(current/list/switch),SessionInfo.workspace - Профили (profiles-spec §4, capability
profiles, протокол v1.4):ProfileInfo,ProfilesOps(list/set),SessionInfo.profile,SessionCreateOpts.profile - Конструктор профилей (profile-editor-spec §2–3, протокол v1.5):
ProfileDoc,ProfilesOps.get/save/delete/duplicate(capabilityprofiles.edit); каталогиCatalogsOps(tools/skills/extensions, capabilitycatalogs) —CatalogTool,CatalogSkill,CatalogExtension - Бот = сессия (bot-as-session-spec §1, протокол v1.6): встроенный профиль —
SessionCreateOpts.profileиProfilesOps.setпринимаютstring | ProfileDoc,SessionInfo.profile: 'inline'+SessionInfo.profileDoc,INLINE_PROFILE_ID,isProfileDoc;MessageAppendOpts.role: 'user' | 'assistant'(гритинг без рана) иMessageAppendOpts.swipeOf(альтернатива сообщению, capabilityswipes).ProfileDocтеперь живёт вnr-chat-storeи ре-экспортируется - Файлы пространства (workspace-files-spec §1, протокол v1.7): capability
workspace.files {write?, watch?},WorkspaceOps.files—WorkspaceFilesOps(list/read/stat;write/delete/rename— гейтwrite;watch(listener) → unsubscribe— гейтwatch),FileEntry,FileContent,FileEncoding,FileReadOpts,FileWriteOpts,WorkspaceUpdateEvent(workspace_update {paths}, отдельный SSE-канал, не событие рана) - Capabilities:
Capabilities - Envelope:
Envelope<T> - Run:
RunStartInput,RunEvent(run_start,message_start,part_start,part_delta,part_end,message_end,ask,activity,session_update,run_end); opaque-вариантыAnyRunEvent/UnknownRunEvent
Хелперы
import {
derivedText, matchToolPair,
isKnownPart, isKnownRunEvent, isRunEvent,
isTextPart, isToolUsePart, /* ... остальные guards */
serializeRunEvent, parseRunEvent, parseSSEStream,
} from '@notrealstudio/nr-ui-protocol'derivedText(message)— плоский текст = конкатенация text-parts (contentв протоколе нет, считает клиент).matchToolPair(parts)— матчtool_use ↔ tool_resultпоcallId→ToolPair[].- Type guards для
PartиRunEventс сужением поtype. isKnownPart/isKnownRunEvent— распознавание ядра; неизвестное = opaque, рендерится fallback'ом, клиент не падает (§1.2 graceful degradation).
Файлы пространства — правила для бэкендов (workspace-files-spec §1)
Общие для backend-fs и memory-FS фикстуры, чтобы оба отвечали одинаково:
normalizeFilePath(path)— относительный POSIX-путь без.; корень —''...в любом сегменте, абсолютный путь,\, NUL →FileBadRequestError(bad_request). Симлинки наружу проверяет бэкенд с диском.fileContent({path, data, revision})— UTF-8 без NUL текстом (BOM сохраняется), иначе base64;decodeFileContent(content, encoding)— обратно.checkFileReadSize(path, size, maxBytes?)— лимитread(DEFAULT_FILE_READ_MAX_BYTES, 2 МБ), отказ с размером.isIgnoredPath(path, isDir, rulesOf)+parseGitignore/matchGitignore—FileEntry.ignored:.git/node_modulesна любой глубине, корзина.nr-trashв корне, дальше.gitignoreкаталогов (подмножество git).FileNotFoundError(not_found),FileConflictError(conflict),fileMime,parentFilePath,isTrashPath,WORKSPACE_TRASH_DIR.serializeWorkspaceEvent/parseWorkspaceEvent/isWorkspaceUpdateEvent— кадрыGET /workspace/events.paths: ['']— «перечитай всё».
SSE-биндинг (§9)
Кадр event: <type>\ndata: {json}\n\n:
const frame = serializeRunEvent({ type: 'part_delta', runId: 'r1',
messageId: 'm1', part: 0, delta: 'hello' })
const event = parseRunEvent(frame) // AnyRunEvent | null (null на битом кадре)
const events = parseSSEStream(sseText) // AnyRunEvent[] — битые кадры отброшеныТранспорта (fetch/EventSource/сервер) здесь нет — только сериализация/парсинг.
Границы
- Никакой реализации транспорта/сервера/клиента — только типы, guards, сериализация.
- Маппинги dreams/pi/opencode/Marinara (спека §6–8b) — в будущих адаптерах, не здесь.
Разработка
npm test # vitest
npm run build # tsc → dist