@openchamber/sdk 1.23.2-preview.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +529 -0
- package/DOCUMENTATION.md +81 -0
- package/GUEST_SERVICES.md +166 -0
- package/LICENSE +21 -0
- package/README.md +186 -0
- package/dist/api-version.d.ts +6 -0
- package/dist/api-version.js +5 -0
- package/dist/contract.d.ts +471 -0
- package/dist/contract.js +256 -0
- package/dist/host-version.d.ts +16 -0
- package/dist/host-version.js +50 -0
- package/dist/host.d.ts +101 -0
- package/dist/host.js +606 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +7 -0
- package/dist/manifest.d.ts +298 -0
- package/dist/manifest.js +224 -0
- package/dist/parse.d.ts +279 -0
- package/dist/parse.js +379 -0
- package/dist/protocol.d.ts +1092 -0
- package/dist/protocol.js +450 -0
- package/dist/schemas.d.ts +4 -0
- package/dist/schemas.js +6 -0
- package/dist/ui/badge.d.ts +10 -0
- package/dist/ui/badge.js +26 -0
- package/dist/ui/banner.d.ts +13 -0
- package/dist/ui/banner.js +48 -0
- package/dist/ui/button.d.ts +14 -0
- package/dist/ui/button.js +49 -0
- package/dist/ui/checkbox.d.ts +12 -0
- package/dist/ui/checkbox.js +45 -0
- package/dist/ui/dom.d.ts +15 -0
- package/dist/ui/dom.js +60 -0
- package/dist/ui/empty.d.ts +11 -0
- package/dist/ui/empty.js +44 -0
- package/dist/ui/field.d.ts +18 -0
- package/dist/ui/field.js +49 -0
- package/dist/ui/icons.d.ts +10 -0
- package/dist/ui/icons.js +23 -0
- package/dist/ui/index.d.ts +35 -0
- package/dist/ui/index.js +17 -0
- package/dist/ui/list.d.ts +26 -0
- package/dist/ui/list.js +90 -0
- package/dist/ui/menu.d.ts +20 -0
- package/dist/ui/menu.js +111 -0
- package/dist/ui/navigation.d.ts +18 -0
- package/dist/ui/navigation.js +38 -0
- package/dist/ui/option.d.ts +16 -0
- package/dist/ui/option.js +35 -0
- package/dist/ui/popup.d.ts +5 -0
- package/dist/ui/popup.js +43 -0
- package/dist/ui/progress.d.ts +11 -0
- package/dist/ui/progress.js +44 -0
- package/dist/ui/search.d.ts +11 -0
- package/dist/ui/search.js +62 -0
- package/dist/ui/select.d.ts +22 -0
- package/dist/ui/select.js +164 -0
- package/dist/ui/separator.d.ts +6 -0
- package/dist/ui/separator.js +26 -0
- package/dist/ui/spinner.d.ts +8 -0
- package/dist/ui/spinner.js +29 -0
- package/dist/ui/style.d.ts +1 -0
- package/dist/ui/style.js +190 -0
- package/dist/ui/tabs.d.ts +15 -0
- package/dist/ui/tabs.js +61 -0
- package/dist/ui/text.d.ts +23 -0
- package/dist/ui/text.js +89 -0
- package/dist/ui/theme.d.ts +22 -0
- package/dist/ui/theme.js +69 -0
- package/dist/workspace-schemas.d.ts +136 -0
- package/dist/workspace-schemas.js +44 -0
- package/dist/workspace.d.ts +109 -0
- package/dist/workspace.js +4 -0
- package/package.json +55 -0
- package/scripts/bundle-guest.ts +44 -0
package/dist/contract.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { OPENCHAMBER_SDK_API_VERSION, OPENCHAMBER_SDK_CHANNEL } from "./api-version.js";
|
|
2
|
+
export const START_SESSION_SENT = ['sent', 'no-model', 'skipped', 'failed'];
|
|
3
|
+
export const SESSION_LIFECYCLE_PHASES = ['started', 'completed', 'failure'];
|
|
4
|
+
export const GUEST_FILE_ENTRY_KINDS = ['file', 'directory', 'other'];
|
|
5
|
+
export const GUEST_FILE_STAT_KINDS = ['file', 'directory', 'other', 'missing'];
|
|
6
|
+
export const isStartSessionResult = (value) => Boolean(value && 'sessionId' in value);
|
|
7
|
+
export const isPromptResult = (value) => Boolean(value && 'sent' in value && !('sessionId' in value));
|
|
8
|
+
export const EMPTY_GUEST_CONNECTION = {
|
|
9
|
+
connected: false,
|
|
10
|
+
account: '',
|
|
11
|
+
};
|
|
12
|
+
export const isGuestMessageItem = (item) => (item !== null && item.kind === 'message');
|
|
13
|
+
export const isGuestSessionItem = (item) => (item !== null && item.kind === 'session');
|
|
14
|
+
/** The composer chip: what the guest handed to `attach`, with `text` filled from the chip. */
|
|
15
|
+
export const isGuestAttachItem = (item) => (item !== null && item.kind !== 'message' && item.kind !== 'session');
|
|
16
|
+
export const GUEST_TOAST_MAX = 500;
|
|
17
|
+
export const GUEST_CLIPBOARD_TEXT_MAX = 32_000;
|
|
18
|
+
export const GUEST_COMPOSE_TEXT_MAX = 16_000;
|
|
19
|
+
export const GUEST_ATTACH_ID_MAX = 128;
|
|
20
|
+
export const GUEST_ATTACH_TITLE_MAX = 200;
|
|
21
|
+
export const GUEST_ATTACH_URL_MAX = 2_000;
|
|
22
|
+
export const GUEST_ATTACH_TEXT_MAX = 16_000;
|
|
23
|
+
export const GUEST_ATTACH_AUTHOR_MAX = 80;
|
|
24
|
+
export const GUEST_ATTACH_BRANCH_MAX = 200;
|
|
25
|
+
/** `JSON.stringify(data).length` ceiling for `AttachIssueRequest.data`. */
|
|
26
|
+
export const GUEST_ATTACH_DATA_MAX = 16_000;
|
|
27
|
+
export const GUEST_ACCOUNT_MAX = 200;
|
|
28
|
+
export const GUEST_SESSION_MODEL_MAX = 200;
|
|
29
|
+
export const GUEST_SESSION_AGENT_MAX = 80;
|
|
30
|
+
export const GUEST_SETTING_VALUE_MAX = 2_000;
|
|
31
|
+
export const GUEST_REQUEST_PATH_MAX = 2_000;
|
|
32
|
+
export const GUEST_REQUEST_BODY_MAX = 64_000;
|
|
33
|
+
export const GUEST_REQUEST_RESPONSE_MAX = 256_000;
|
|
34
|
+
export const GUEST_REQUEST_TIMEOUT_MS = 20_000;
|
|
35
|
+
/** Guest file path, in characters. */
|
|
36
|
+
export const GUEST_FILE_PATH_MAX = 1_024;
|
|
37
|
+
/** File content in characters, read and write alike. */
|
|
38
|
+
export const GUEST_FILE_CONTENT_MAX = 2_000_000;
|
|
39
|
+
/** Entries a `listDir` answer carries; longer directories are truncated. */
|
|
40
|
+
export const GUEST_FILE_LIST_MAX = 2_000;
|
|
41
|
+
/** Characters in a `generate` prompt. */
|
|
42
|
+
export const GUEST_GENERATE_PROMPT_MAX = 64_000;
|
|
43
|
+
/** Characters in a `generate` system prompt. */
|
|
44
|
+
export const GUEST_GENERATE_SYSTEM_MAX = 8_000;
|
|
45
|
+
/** Largest `maxOutputTokens` a guest may ask for. */
|
|
46
|
+
export const GUEST_GENERATE_OUTPUT_TOKENS_MAX = 4_000;
|
|
47
|
+
/** Characters in a `generate` answer. */
|
|
48
|
+
export const GUEST_GENERATE_TEXT_MAX = 256_000;
|
|
49
|
+
/** How long `generate` waits for the model before `HOST_TIMEOUT`. */
|
|
50
|
+
export const GUEST_GENERATE_TIMEOUT_MS = 90_000;
|
|
51
|
+
/** Characters of one message's text on a `GuestMessageItem` or a `GuestSessionItem` message. */
|
|
52
|
+
export const GUEST_ITEM_MESSAGE_TEXT_MAX = 200_000;
|
|
53
|
+
/** `JSON.stringify` length ceiling for a `GuestSessionItem`; the host drops the oldest messages to stay under it. */
|
|
54
|
+
export const GUEST_ITEM_SESSION_MAX = 2_000_000;
|
|
55
|
+
/** Largest count a rail badge shows. */
|
|
56
|
+
export const GUEST_BADGE_MAX = 999;
|
|
57
|
+
/** Characters in a `resolve-result` error string. */
|
|
58
|
+
export const GUEST_RESOLVE_ERROR_MAX = 500;
|
|
59
|
+
export const HOST_REQUEST_ERROR_CODES = [
|
|
60
|
+
'HOST_UNAVAILABLE',
|
|
61
|
+
'HOST_TIMEOUT',
|
|
62
|
+
'HOST_REJECTED',
|
|
63
|
+
'DISCONNECTED',
|
|
64
|
+
'DISABLED',
|
|
65
|
+
'BAD_PATH',
|
|
66
|
+
'NO_INTEGRATION',
|
|
67
|
+
'NO_SERVICE',
|
|
68
|
+
'SERVICE_FAILED',
|
|
69
|
+
'NO_SESSION',
|
|
70
|
+
'SESSION_BUSY',
|
|
71
|
+
'NOT_GRANTED',
|
|
72
|
+
'NO_DIRECTORY',
|
|
73
|
+
'NOT_FOUND',
|
|
74
|
+
'FILE_TOO_LARGE',
|
|
75
|
+
'DENIED',
|
|
76
|
+
'NO_MODEL',
|
|
77
|
+
'MODEL_FAILED',
|
|
78
|
+
];
|
|
79
|
+
export const SERVICE_STATUS_VALUES = ['stopped', 'starting', 'ready', 'failed'];
|
|
80
|
+
const hostRequestErrorCodeSet = new Set(HOST_REQUEST_ERROR_CODES);
|
|
81
|
+
export const isHostRequestErrorCode = (value) => (hostRequestErrorCodeSet.has(value));
|
|
82
|
+
/** Unknown or omitted wire codes become HOST_REJECTED. */
|
|
83
|
+
export const resolveHostRequestErrorCode = (value) => (value && isHostRequestErrorCode(value) ? value : 'HOST_REJECTED');
|
|
84
|
+
export const isJsonValue = (value) => {
|
|
85
|
+
if (value === undefined)
|
|
86
|
+
return false;
|
|
87
|
+
if (value === null || value === true || value === false)
|
|
88
|
+
return true;
|
|
89
|
+
if (String(value) === value)
|
|
90
|
+
return true;
|
|
91
|
+
if (Number(value) === value)
|
|
92
|
+
return Number.isFinite(value);
|
|
93
|
+
if (Array.isArray(value))
|
|
94
|
+
return value.every(isJsonValue);
|
|
95
|
+
if (Object(value) === value)
|
|
96
|
+
return Object.values(value).every(isJsonValue);
|
|
97
|
+
return false;
|
|
98
|
+
};
|
|
99
|
+
/** Whether `data` is plain JSON that serializes within `GUEST_ATTACH_DATA_MAX`. */
|
|
100
|
+
export const isAttachData = (value) => (isJsonValue(value) && JSON.stringify(value).length <= GUEST_ATTACH_DATA_MAX);
|
|
101
|
+
const clampBranch = (value) => (value?.trim().slice(0, GUEST_ATTACH_BRANCH_MAX) ?? '');
|
|
102
|
+
/** Guest attach is dropped by the host schema if these limits overflow. */
|
|
103
|
+
export const clampAttachRequest = (request) => {
|
|
104
|
+
const id = request.id.trim().slice(0, GUEST_ATTACH_ID_MAX);
|
|
105
|
+
const title = request.title.trim().slice(0, GUEST_ATTACH_TITLE_MAX);
|
|
106
|
+
const url = request.url.trim().slice(0, GUEST_ATTACH_URL_MAX);
|
|
107
|
+
const text = request.text?.trim().slice(0, GUEST_ATTACH_TEXT_MAX);
|
|
108
|
+
const author = request.author?.trim().slice(0, GUEST_ATTACH_AUTHOR_MAX);
|
|
109
|
+
const kind = request.kind === 'pull' ? 'pull' : 'issue';
|
|
110
|
+
const next = {
|
|
111
|
+
providerId: request.providerId.trim(),
|
|
112
|
+
id,
|
|
113
|
+
title: title || id,
|
|
114
|
+
url,
|
|
115
|
+
kind,
|
|
116
|
+
};
|
|
117
|
+
if (text) {
|
|
118
|
+
next.text = text;
|
|
119
|
+
}
|
|
120
|
+
if (author) {
|
|
121
|
+
next.author = author;
|
|
122
|
+
}
|
|
123
|
+
if (kind === 'pull') {
|
|
124
|
+
const head = clampBranch(request.branches?.head);
|
|
125
|
+
const base = clampBranch(request.branches?.base);
|
|
126
|
+
if (head && base) {
|
|
127
|
+
next.branches = { head, base };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (isAttachData(request.data)) {
|
|
131
|
+
next.data = request.data;
|
|
132
|
+
}
|
|
133
|
+
return next;
|
|
134
|
+
};
|
|
135
|
+
/** Same attach clamp. `worktree` stays only when the guest asked for one. */
|
|
136
|
+
export const clampStartSessionRequest = (request) => {
|
|
137
|
+
const next = clampAttachRequest(request);
|
|
138
|
+
if (request.projectId)
|
|
139
|
+
next.projectId = request.projectId;
|
|
140
|
+
if (request.navigation)
|
|
141
|
+
next.navigation = request.navigation;
|
|
142
|
+
if (request.worktree) {
|
|
143
|
+
next.worktree = request.worktree;
|
|
144
|
+
}
|
|
145
|
+
return next;
|
|
146
|
+
};
|
|
147
|
+
/** Prompt text uses the compose limit. `send` stays only when the guest asked. */
|
|
148
|
+
export const clampPromptRequest = (request) => {
|
|
149
|
+
const next = {
|
|
150
|
+
text: request.text.trim().slice(0, GUEST_COMPOSE_TEXT_MAX),
|
|
151
|
+
};
|
|
152
|
+
if (request.send) {
|
|
153
|
+
next.send = true;
|
|
154
|
+
}
|
|
155
|
+
return next;
|
|
156
|
+
};
|
|
157
|
+
/** Badge counts are whole numbers from 0 to `GUEST_BADGE_MAX`; anything else clears. */
|
|
158
|
+
export const clampBadgeCount = (count) => {
|
|
159
|
+
if (count === null || !Number.isFinite(count))
|
|
160
|
+
return null;
|
|
161
|
+
return Math.min(GUEST_BADGE_MAX, Math.max(0, Math.round(count)));
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Which grant a file path needs. `/…` and `~/…` are outside the project and
|
|
165
|
+
* go through the declared `filesystem` patterns; anything else is joined to
|
|
166
|
+
* the open project directory.
|
|
167
|
+
*/
|
|
168
|
+
export const guestFileScope = (path) => (path.startsWith('/') || path === '~' || path.startsWith('~/') ? 'filesystem' : 'project');
|
|
169
|
+
/**
|
|
170
|
+
* What the host schema accepts as a file path. Semantics (`..`, symlinks,
|
|
171
|
+
* declared patterns) are the server's call and come back as `BAD_PATH`; this
|
|
172
|
+
* only keeps a message from being dropped unanswered.
|
|
173
|
+
*/
|
|
174
|
+
export const isGuestFilePath = (value) => (value.length > 0
|
|
175
|
+
&& value.length <= GUEST_FILE_PATH_MAX
|
|
176
|
+
&& !value.includes('\0')
|
|
177
|
+
&& !value.includes('\\'));
|
|
178
|
+
export const ATTACH_PROVIDER_ID = /^[a-z][a-z0-9-]*$/;
|
|
179
|
+
export const SETTING_KEY = /^[a-z][a-z0-9-]*$/;
|
|
180
|
+
export const isGuestRequestPath = (value) => {
|
|
181
|
+
if (!value.startsWith('/') || value.includes('\0') || value.includes('\\') || value.includes('://')) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
if (value.length > GUEST_REQUEST_PATH_MAX) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
const segments = value.split('/');
|
|
188
|
+
return !segments.some((segment) => segment === '.' || segment === '..');
|
|
189
|
+
};
|
|
190
|
+
const serviceStatusSet = new Set(SERVICE_STATUS_VALUES);
|
|
191
|
+
export const isServiceStatusResult = (value) => Boolean(value && 'status' in value && serviceStatusSet.has(String(value.status)) && !('body' in value));
|
|
192
|
+
export const isGuestRequestResult = (value) => Boolean(value && 'status' in value && 'body' in value && Number.isInteger(value.status));
|
|
193
|
+
export const isFileReadResult = (value) => Boolean(value && 'content' in value && String(value.content) === value.content);
|
|
194
|
+
export const isFileWriteResult = (value) => Boolean(value && 'written' in value && value.written === true);
|
|
195
|
+
export const isFileListResult = (value) => Boolean(value && 'entries' in value && Array.isArray(value.entries));
|
|
196
|
+
const fileStatKindSet = new Set(GUEST_FILE_STAT_KINDS);
|
|
197
|
+
export const isFileStatResult = (value) => Boolean(value && 'kind' in value && 'size' in value && fileStatKindSet.has(String(value.kind)) && Number.isFinite(value.size));
|
|
198
|
+
export const isGenerateResult = (value) => Boolean(value && 'text' in value && String(value.text) === value.text && !('status' in value));
|
|
199
|
+
const HOST_PUSH_TYPES = new Set([
|
|
200
|
+
'workspace',
|
|
201
|
+
'ready', 'directory', 'session', 'connection', 'settings', 'session-lifecycle', 'item', 'resolve',
|
|
202
|
+
]);
|
|
203
|
+
const asWireRecord = (data) => (Object(data) === data ? data : null);
|
|
204
|
+
const isNonEmptyString = (value) => (String(value) === value && value.length > 0);
|
|
205
|
+
const readResultMessage = (wire) => {
|
|
206
|
+
if (!isNonEmptyString(wire.id))
|
|
207
|
+
return null;
|
|
208
|
+
if (wire.ok === true) {
|
|
209
|
+
const message = {
|
|
210
|
+
channel: OPENCHAMBER_SDK_CHANNEL,
|
|
211
|
+
v: OPENCHAMBER_SDK_API_VERSION,
|
|
212
|
+
type: 'result',
|
|
213
|
+
id: wire.id,
|
|
214
|
+
ok: true,
|
|
215
|
+
};
|
|
216
|
+
if (Object(wire.payload) === wire.payload) {
|
|
217
|
+
// SAFETY: a trusted host answered an id this client issued; the per-call
|
|
218
|
+
// guards in host.ts (isStartSessionResult and friends) narrow the payload
|
|
219
|
+
// before it reaches a caller.
|
|
220
|
+
message.payload = wire.payload;
|
|
221
|
+
}
|
|
222
|
+
return message;
|
|
223
|
+
}
|
|
224
|
+
if (wire.ok === false && isNonEmptyString(wire.error)) {
|
|
225
|
+
return {
|
|
226
|
+
channel: OPENCHAMBER_SDK_CHANNEL,
|
|
227
|
+
v: OPENCHAMBER_SDK_API_VERSION,
|
|
228
|
+
type: 'result',
|
|
229
|
+
id: wire.id,
|
|
230
|
+
ok: false,
|
|
231
|
+
error: wire.error,
|
|
232
|
+
code: resolveHostRequestErrorCode(isNonEmptyString(wire.code) ? wire.code : undefined),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* The guest's read of a host message. The host is the trusted parent frame
|
|
239
|
+
* (the client only listens to `event.source === parent`), so this checks the
|
|
240
|
+
* envelope and the discriminant rather than every field, and the guest bundle
|
|
241
|
+
* carries no schema library for it. The host side parses guest input with
|
|
242
|
+
* `guestMessageSchema` in `@openchamber/sdk/schemas`.
|
|
243
|
+
*/
|
|
244
|
+
export const readHostMessage = (data) => {
|
|
245
|
+
const wire = asWireRecord(data);
|
|
246
|
+
if (!wire || wire.channel !== OPENCHAMBER_SDK_CHANNEL || wire.v !== OPENCHAMBER_SDK_API_VERSION)
|
|
247
|
+
return null;
|
|
248
|
+
if (wire.type === 'result')
|
|
249
|
+
return readResultMessage(wire);
|
|
250
|
+
if (!HOST_PUSH_TYPES.has(String(wire.type)) || Object(wire.payload) !== wire.payload)
|
|
251
|
+
return null;
|
|
252
|
+
// SAFETY: envelope and discriminant verified above and the sender is the
|
|
253
|
+
// trusted parent frame; protocol.ts asserts these push shapes against the
|
|
254
|
+
// host's own schema at compile time.
|
|
255
|
+
return wire;
|
|
256
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** `1.22.0` or `>=1.22.0`. Ranges beyond a floor are not accepted. */
|
|
2
|
+
export declare const OPENCHAMBER_ENGINE_PATTERN: RegExp;
|
|
3
|
+
export type OpenChamberVersionParts = {
|
|
4
|
+
major: number;
|
|
5
|
+
minor: number;
|
|
6
|
+
patch: number;
|
|
7
|
+
};
|
|
8
|
+
export declare const parseOpenChamberVersion: (value: string) => OpenChamberVersionParts | null;
|
|
9
|
+
export declare const compareOpenChamberVersions: (left: string, right: string) => number;
|
|
10
|
+
/** Normalize `>=1.22.0` / `1.22.0` to the floor version string. */
|
|
11
|
+
export declare const openChamberEngineMinimum: (engine: string) => string | null;
|
|
12
|
+
/**
|
|
13
|
+
* True when this OpenChamber build is new enough for the guest's `engines.openchamber`.
|
|
14
|
+
* Unknown host versions fail closed when an engine is declared.
|
|
15
|
+
*/
|
|
16
|
+
export declare const hostMeetsOpenChamberEngine: (hostVersion: string, engine: string) => boolean;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const CORE_VERSION = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
2
|
+
/** `1.22.0` or `>=1.22.0`. Ranges beyond a floor are not accepted. */
|
|
3
|
+
export const OPENCHAMBER_ENGINE_PATTERN = /^(>=)?\d+\.\d+\.\d+$/;
|
|
4
|
+
export const parseOpenChamberVersion = (value) => {
|
|
5
|
+
const core = String(value || '').trim().replace(/^v/i, '').split(/[-+]/)[0] ?? '';
|
|
6
|
+
const match = CORE_VERSION.exec(core);
|
|
7
|
+
if (!match) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
major: Number(match[1]),
|
|
12
|
+
minor: Number(match[2]),
|
|
13
|
+
patch: Number(match[3]),
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export const compareOpenChamberVersions = (left, right) => {
|
|
17
|
+
const a = parseOpenChamberVersion(left);
|
|
18
|
+
const b = parseOpenChamberVersion(right);
|
|
19
|
+
if (!a || !b) {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
if (a.major !== b.major)
|
|
23
|
+
return a.major - b.major;
|
|
24
|
+
if (a.minor !== b.minor)
|
|
25
|
+
return a.minor - b.minor;
|
|
26
|
+
return a.patch - b.patch;
|
|
27
|
+
};
|
|
28
|
+
/** Normalize `>=1.22.0` / `1.22.0` to the floor version string. */
|
|
29
|
+
export const openChamberEngineMinimum = (engine) => {
|
|
30
|
+
const trimmed = engine.trim();
|
|
31
|
+
if (!OPENCHAMBER_ENGINE_PATTERN.test(trimmed)) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const floor = trimmed.startsWith('>=') ? trimmed.slice(2) : trimmed;
|
|
35
|
+
return parseOpenChamberVersion(floor) ? floor : null;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* True when this OpenChamber build is new enough for the guest's `engines.openchamber`.
|
|
39
|
+
* Unknown host versions fail closed when an engine is declared.
|
|
40
|
+
*/
|
|
41
|
+
export const hostMeetsOpenChamberEngine = (hostVersion, engine) => {
|
|
42
|
+
const minimum = openChamberEngineMinimum(engine);
|
|
43
|
+
if (!minimum) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (!parseOpenChamberVersion(hostVersion)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return compareOpenChamberVersions(hostVersion, minimum) >= 0;
|
|
50
|
+
};
|
package/dist/host.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { type GuestProjectsSnapshot, type GuestWorktreesSnapshot, type GuestSessionsSnapshot } from './workspace.ts';
|
|
2
|
+
import type { JsonValue } from './contract.ts';
|
|
3
|
+
import { type AttachIssueRequest, type ComposeRequest, type PromptRequest, type PromptResult, type SessionLifecycleEvent, type StartSessionRequest, type GuestConnection, type GuestItem, type GuestMessage, type GuestRequest, type GuestRequestResult, type GuestSettings, type HostReadyContext, type HostRequestErrorCode, type ResolveRequest, type SessionSnapshot, type StartSessionResult, type ToastRequest, type ServiceStatusResult, type FileListResult, type FileReadResult, type FileStatResult, type FileWriteResult, type GenerateRequest, type GenerateResult } from './contract.ts';
|
|
4
|
+
export type HostFrame = {
|
|
5
|
+
addEventListener: Window['addEventListener'];
|
|
6
|
+
removeEventListener: Window['removeEventListener'];
|
|
7
|
+
parent: {
|
|
8
|
+
postMessage: (message: GuestMessage, targetOrigin: string) => void;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export type HostClientOptions = {
|
|
12
|
+
/** Test seam. Defaults to `window`. */
|
|
13
|
+
target?: HostFrame;
|
|
14
|
+
/** Test seam. Defaults to `source === parent`. */
|
|
15
|
+
acceptSource?: (source: MessageEvent['source']) => boolean;
|
|
16
|
+
/** Test seam. Defaults to `GUEST_REQUEST_TIMEOUT_MS`. */
|
|
17
|
+
requestTimeoutMs?: number;
|
|
18
|
+
};
|
|
19
|
+
export type HostClient = {
|
|
20
|
+
listProjects: () => Promise<GuestProjectsSnapshot>;
|
|
21
|
+
listWorktrees: (projectId: string) => Promise<GuestWorktreesSnapshot>;
|
|
22
|
+
listSessions: (projectId: string) => Promise<GuestSessionsSnapshot>;
|
|
23
|
+
onProjects: (listener: (snapshot: GuestProjectsSnapshot) => void) => Promise<() => void>;
|
|
24
|
+
onWorktrees: (projectId: string, listener: (snapshot: GuestWorktreesSnapshot) => void) => Promise<() => void>;
|
|
25
|
+
onSessions: (projectId: string, listener: (snapshot: GuestSessionsSnapshot) => void) => Promise<() => void>;
|
|
26
|
+
openSession: (sessionId: string) => Promise<void>;
|
|
27
|
+
storage: {
|
|
28
|
+
get: (key: string) => Promise<JsonValue | undefined>;
|
|
29
|
+
set: (key: string, value: JsonValue) => Promise<void>;
|
|
30
|
+
delete: (key: string) => Promise<void>;
|
|
31
|
+
keys: () => Promise<string[]>;
|
|
32
|
+
};
|
|
33
|
+
onReady: (listener: (context: HostReadyContext) => void) => () => void;
|
|
34
|
+
onDirectory: (listener: (directory: string | null) => void) => () => void;
|
|
35
|
+
onSession: (listener: (session: SessionSnapshot | null) => void) => () => void;
|
|
36
|
+
onSessionLifecycle: (listener: (event: SessionLifecycleEvent) => void) => () => void;
|
|
37
|
+
onConnection: (listener: (connection: GuestConnection) => void) => () => void;
|
|
38
|
+
onSettings: (listener: (settings: GuestSettings) => void) => () => void;
|
|
39
|
+
/**
|
|
40
|
+
* The item this surface was opened for: a chip (`AttachIssueRequest`), a
|
|
41
|
+
* message (`GuestMessageItem`), or a session (`GuestSessionItem`). Replays
|
|
42
|
+
* the last value; `null` when there is none.
|
|
43
|
+
*/
|
|
44
|
+
onItem: (listener: (item: GuestItem | null) => void) => () => void;
|
|
45
|
+
/**
|
|
46
|
+
* Answer the host when the user submits one of this package's
|
|
47
|
+
* `contributes.commands`. Return the chip to attach, or `null` for nothing
|
|
48
|
+
* (the host shows a short notice). A thrown error reaches the user as a
|
|
49
|
+
* toast. One handler at a time; the returned function removes it.
|
|
50
|
+
*/
|
|
51
|
+
onResolve: (handler: (request: ResolveRequest) => Promise<AttachIssueRequest | null> | AttachIssueRequest | null) => () => void;
|
|
52
|
+
toast: (request: ToastRequest) => Promise<void>;
|
|
53
|
+
openUrl: (url: string) => Promise<void>;
|
|
54
|
+
openSurface: (surfaceId: string) => Promise<void>;
|
|
55
|
+
writeClipboard: (text: string) => Promise<void>;
|
|
56
|
+
compose: (request: ComposeRequest) => Promise<void>;
|
|
57
|
+
attach: (request: AttachIssueRequest) => Promise<void>;
|
|
58
|
+
startSession: (request: StartSessionRequest) => Promise<StartSessionResult>;
|
|
59
|
+
prompt: (request: PromptRequest) => Promise<PromptResult>;
|
|
60
|
+
sessionLink: (request: AttachIssueRequest) => Promise<void>;
|
|
61
|
+
close: () => Promise<void>;
|
|
62
|
+
oauthStart: () => Promise<void>;
|
|
63
|
+
oauthDisconnect: () => Promise<void>;
|
|
64
|
+
request: (request: GuestRequest) => Promise<GuestRequestResult>;
|
|
65
|
+
serviceRequest: (request: GuestRequest) => Promise<GuestRequestResult>;
|
|
66
|
+
serviceStatus: () => Promise<ServiceStatusResult>;
|
|
67
|
+
/**
|
|
68
|
+
* Read a UTF-8 text file. A relative path is inside the open project
|
|
69
|
+
* (capability `files`); `/…` or `~/…` must match a declared
|
|
70
|
+
* `contributes.filesystem` pattern (capability `filesystem`). Over
|
|
71
|
+
* `GUEST_FILE_CONTENT_MAX` characters is `FILE_TOO_LARGE`; a missing file
|
|
72
|
+
* is `NOT_FOUND`.
|
|
73
|
+
*/
|
|
74
|
+
readFile: (path: string) => Promise<FileReadResult>;
|
|
75
|
+
/**
|
|
76
|
+
* Write a UTF-8 text file atomically, creating parent directories. Same
|
|
77
|
+
* path rules as `readFile`. Content over `GUEST_FILE_CONTENT_MAX` is
|
|
78
|
+
* refused before sending.
|
|
79
|
+
*/
|
|
80
|
+
writeFile: (path: string, content: string) => Promise<FileWriteResult>;
|
|
81
|
+
/** Entries of a directory, sorted by name, capped at `GUEST_FILE_LIST_MAX`. Same path rules as `readFile`. */
|
|
82
|
+
listDir: (path: string) => Promise<FileListResult>;
|
|
83
|
+
/** Kind, size, and mtime of a path. A missing path is `kind: 'missing'`, not an error. Same path rules as `readFile`. */
|
|
84
|
+
stat: (path: string) => Promise<FileStatResult>;
|
|
85
|
+
/**
|
|
86
|
+
* One-off text generation with the user's Small Model (capability
|
|
87
|
+
* `model`). Nothing enters a session and no history is kept. `prompt` is
|
|
88
|
+
* 1 to `GUEST_GENERATE_PROMPT_MAX` characters, `system` up to
|
|
89
|
+
* `GUEST_GENERATE_SYSTEM_MAX`. No usable model is `NO_MODEL`; a model that
|
|
90
|
+
* failed is `MODEL_FAILED`. Waits up to `GUEST_GENERATE_TIMEOUT_MS`.
|
|
91
|
+
*/
|
|
92
|
+
generate: (request: GenerateRequest) => Promise<GenerateResult>;
|
|
93
|
+
/** Number on this guest's rail icon (0 to `GUEST_BADGE_MAX`); `null` clears it. Opening the panel clears it too. */
|
|
94
|
+
setBadge: (count: number | null) => Promise<void>;
|
|
95
|
+
dispose: () => void;
|
|
96
|
+
};
|
|
97
|
+
export declare class HostRequestError extends Error {
|
|
98
|
+
readonly code: HostRequestErrorCode;
|
|
99
|
+
constructor(code: HostRequestErrorCode, message: string);
|
|
100
|
+
}
|
|
101
|
+
export declare const connectHost: (options?: HostClientOptions) => HostClient;
|