@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.
Files changed (75) hide show
  1. package/API.md +529 -0
  2. package/DOCUMENTATION.md +81 -0
  3. package/GUEST_SERVICES.md +166 -0
  4. package/LICENSE +21 -0
  5. package/README.md +186 -0
  6. package/dist/api-version.d.ts +6 -0
  7. package/dist/api-version.js +5 -0
  8. package/dist/contract.d.ts +471 -0
  9. package/dist/contract.js +256 -0
  10. package/dist/host-version.d.ts +16 -0
  11. package/dist/host-version.js +50 -0
  12. package/dist/host.d.ts +101 -0
  13. package/dist/host.js +606 -0
  14. package/dist/index.d.ts +12 -0
  15. package/dist/index.js +7 -0
  16. package/dist/manifest.d.ts +298 -0
  17. package/dist/manifest.js +224 -0
  18. package/dist/parse.d.ts +279 -0
  19. package/dist/parse.js +379 -0
  20. package/dist/protocol.d.ts +1092 -0
  21. package/dist/protocol.js +450 -0
  22. package/dist/schemas.d.ts +4 -0
  23. package/dist/schemas.js +6 -0
  24. package/dist/ui/badge.d.ts +10 -0
  25. package/dist/ui/badge.js +26 -0
  26. package/dist/ui/banner.d.ts +13 -0
  27. package/dist/ui/banner.js +48 -0
  28. package/dist/ui/button.d.ts +14 -0
  29. package/dist/ui/button.js +49 -0
  30. package/dist/ui/checkbox.d.ts +12 -0
  31. package/dist/ui/checkbox.js +45 -0
  32. package/dist/ui/dom.d.ts +15 -0
  33. package/dist/ui/dom.js +60 -0
  34. package/dist/ui/empty.d.ts +11 -0
  35. package/dist/ui/empty.js +44 -0
  36. package/dist/ui/field.d.ts +18 -0
  37. package/dist/ui/field.js +49 -0
  38. package/dist/ui/icons.d.ts +10 -0
  39. package/dist/ui/icons.js +23 -0
  40. package/dist/ui/index.d.ts +35 -0
  41. package/dist/ui/index.js +17 -0
  42. package/dist/ui/list.d.ts +26 -0
  43. package/dist/ui/list.js +90 -0
  44. package/dist/ui/menu.d.ts +20 -0
  45. package/dist/ui/menu.js +111 -0
  46. package/dist/ui/navigation.d.ts +18 -0
  47. package/dist/ui/navigation.js +38 -0
  48. package/dist/ui/option.d.ts +16 -0
  49. package/dist/ui/option.js +35 -0
  50. package/dist/ui/popup.d.ts +5 -0
  51. package/dist/ui/popup.js +43 -0
  52. package/dist/ui/progress.d.ts +11 -0
  53. package/dist/ui/progress.js +44 -0
  54. package/dist/ui/search.d.ts +11 -0
  55. package/dist/ui/search.js +62 -0
  56. package/dist/ui/select.d.ts +22 -0
  57. package/dist/ui/select.js +164 -0
  58. package/dist/ui/separator.d.ts +6 -0
  59. package/dist/ui/separator.js +26 -0
  60. package/dist/ui/spinner.d.ts +8 -0
  61. package/dist/ui/spinner.js +29 -0
  62. package/dist/ui/style.d.ts +1 -0
  63. package/dist/ui/style.js +190 -0
  64. package/dist/ui/tabs.d.ts +15 -0
  65. package/dist/ui/tabs.js +61 -0
  66. package/dist/ui/text.d.ts +23 -0
  67. package/dist/ui/text.js +89 -0
  68. package/dist/ui/theme.d.ts +22 -0
  69. package/dist/ui/theme.js +69 -0
  70. package/dist/workspace-schemas.d.ts +136 -0
  71. package/dist/workspace-schemas.js +44 -0
  72. package/dist/workspace.d.ts +109 -0
  73. package/dist/workspace.js +4 -0
  74. package/package.json +55 -0
  75. package/scripts/bundle-guest.ts +44 -0
@@ -0,0 +1,279 @@
1
+ import { z } from 'zod';
2
+ import { type ServicePermissions, type ParseManifestResult, type SocketBinding } from './manifest.ts';
3
+ export declare const openChamberManifestSchema: z.ZodObject<{
4
+ apiVersion: z.ZodLiteral<1>;
5
+ engines: z.ZodOptional<z.ZodObject<{
6
+ openchamber: z.ZodString;
7
+ }, z.core.$strict>>;
8
+ contributes: z.ZodObject<{
9
+ panel: z.ZodObject<{
10
+ id: z.ZodString;
11
+ name: z.ZodString;
12
+ icon: z.ZodString;
13
+ entry: z.ZodOptional<z.ZodString>;
14
+ }, z.core.$strip>;
15
+ attach: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodEnum<{
16
+ panel: "panel";
17
+ dialog: "dialog";
18
+ }>, z.ZodObject<{
19
+ mode: z.ZodEnum<{
20
+ panel: "panel";
21
+ dialog: "dialog";
22
+ }>;
23
+ entry: z.ZodOptional<z.ZodString>;
24
+ }, z.core.$strip>]>>;
25
+ page: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodObject<{
26
+ entry: z.ZodString;
27
+ title: z.ZodOptional<z.ZodString>;
28
+ }, z.core.$strip>]>>;
29
+ capabilities: z.ZodOptional<z.ZodArray<z.ZodEnum<{
30
+ sessions: "sessions";
31
+ prompt: "prompt";
32
+ files: "files";
33
+ model: "model";
34
+ }>>>;
35
+ integration: z.ZodOptional<z.ZodObject<{
36
+ name: z.ZodString;
37
+ description: z.ZodString;
38
+ oauth: z.ZodOptional<z.ZodObject<{
39
+ authorizeUrl: z.ZodString;
40
+ tokenUrl: z.ZodString;
41
+ apiOrigin: z.ZodString;
42
+ scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
43
+ account: z.ZodOptional<z.ZodObject<{
44
+ path: z.ZodString;
45
+ name: z.ZodString;
46
+ }, z.core.$strip>>;
47
+ }, z.core.$strip>>;
48
+ token: z.ZodOptional<z.ZodObject<{
49
+ apiOrigin: z.ZodString;
50
+ account: z.ZodOptional<z.ZodObject<{
51
+ path: z.ZodString;
52
+ name: z.ZodString;
53
+ }, z.core.$strip>>;
54
+ scheme: z.ZodOptional<z.ZodEnum<{
55
+ raw: "raw";
56
+ bearer: "bearer";
57
+ basic: "basic";
58
+ }>>;
59
+ usernameLabel: z.ZodOptional<z.ZodString>;
60
+ }, z.core.$strip>>;
61
+ host: z.ZodOptional<z.ZodObject<{
62
+ provider: z.ZodEnum<{
63
+ linear: "linear";
64
+ }>;
65
+ }, z.core.$strip>>;
66
+ settings: z.ZodOptional<z.ZodArray<z.ZodObject<{
67
+ id: z.ZodString;
68
+ label: z.ZodString;
69
+ }, z.core.$strip>>>;
70
+ }, z.core.$strip>>;
71
+ service: z.ZodOptional<z.ZodObject<{
72
+ entry: z.ZodString;
73
+ runtime: z.ZodLiteral<"host">;
74
+ permissions: z.ZodOptional<z.ZodPipe<z.ZodObject<{
75
+ sockets: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodPipe<z.ZodString, z.ZodTransform<SocketBinding, string>>, z.ZodPipe<z.ZodObject<{
76
+ id: z.ZodString;
77
+ path: z.ZodOptional<z.ZodString>;
78
+ candidates: z.ZodOptional<z.ZodObject<{
79
+ linux: z.ZodOptional<z.ZodArray<z.ZodString>>;
80
+ darwin: z.ZodOptional<z.ZodArray<z.ZodString>>;
81
+ win32: z.ZodOptional<z.ZodArray<z.ZodString>>;
82
+ }, z.core.$strict>>;
83
+ }, z.core.$strict>, z.ZodTransform<SocketBinding, {
84
+ id: string;
85
+ path?: string | undefined;
86
+ candidates?: {
87
+ linux?: string[] | undefined;
88
+ darwin?: string[] | undefined;
89
+ win32?: string[] | undefined;
90
+ } | undefined;
91
+ }>>]>>>;
92
+ exec: z.ZodOptional<z.ZodArray<z.ZodString>>;
93
+ }, z.core.$strict>, z.ZodTransform<ServicePermissions, {
94
+ sockets?: SocketBinding[] | undefined;
95
+ exec?: string[] | undefined;
96
+ }>>>;
97
+ }, z.core.$strip>>;
98
+ filesystem: z.ZodOptional<z.ZodArray<z.ZodString>>;
99
+ actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
100
+ id: z.ZodString;
101
+ label: z.ZodString;
102
+ icon: z.ZodOptional<z.ZodString>;
103
+ where: z.ZodEnum<{
104
+ message: "message";
105
+ session: "session";
106
+ }>;
107
+ roles: z.ZodOptional<z.ZodArray<z.ZodEnum<{
108
+ user: "user";
109
+ assistant: "assistant";
110
+ }>>>;
111
+ payload: z.ZodOptional<z.ZodArray<z.ZodEnum<{
112
+ messages: "messages";
113
+ }>>>;
114
+ }, z.core.$strip>>>;
115
+ commands: z.ZodOptional<z.ZodArray<z.ZodObject<{
116
+ name: z.ZodString;
117
+ description: z.ZodOptional<z.ZodString>;
118
+ }, z.core.$strip>>>;
119
+ tools: z.ZodOptional<z.ZodArray<z.ZodObject<{
120
+ match: z.ZodString;
121
+ name: z.ZodOptional<z.ZodString>;
122
+ icon: z.ZodOptional<z.ZodString>;
123
+ title: z.ZodOptional<z.ZodString>;
124
+ subtitle: z.ZodOptional<z.ZodString>;
125
+ output: z.ZodOptional<z.ZodEnum<{
126
+ code: "code";
127
+ text: "text";
128
+ auto: "auto";
129
+ json: "json";
130
+ markdown: "markdown";
131
+ table: "table";
132
+ }>>;
133
+ language: z.ZodOptional<z.ZodString>;
134
+ columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
135
+ }, z.core.$strip>>>;
136
+ }, z.core.$strip>;
137
+ }, z.core.$strip>;
138
+ export declare const PACKAGE_VERSION_PATTERN: RegExp;
139
+ export declare const packageManifestSchema: z.ZodObject<{
140
+ version: z.ZodOptional<z.ZodString>;
141
+ openchamber: z.ZodObject<{
142
+ apiVersion: z.ZodLiteral<1>;
143
+ engines: z.ZodOptional<z.ZodObject<{
144
+ openchamber: z.ZodString;
145
+ }, z.core.$strict>>;
146
+ contributes: z.ZodObject<{
147
+ panel: z.ZodObject<{
148
+ id: z.ZodString;
149
+ name: z.ZodString;
150
+ icon: z.ZodString;
151
+ entry: z.ZodOptional<z.ZodString>;
152
+ }, z.core.$strip>;
153
+ attach: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodEnum<{
154
+ panel: "panel";
155
+ dialog: "dialog";
156
+ }>, z.ZodObject<{
157
+ mode: z.ZodEnum<{
158
+ panel: "panel";
159
+ dialog: "dialog";
160
+ }>;
161
+ entry: z.ZodOptional<z.ZodString>;
162
+ }, z.core.$strip>]>>;
163
+ page: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodObject<{
164
+ entry: z.ZodString;
165
+ title: z.ZodOptional<z.ZodString>;
166
+ }, z.core.$strip>]>>;
167
+ capabilities: z.ZodOptional<z.ZodArray<z.ZodEnum<{
168
+ sessions: "sessions";
169
+ prompt: "prompt";
170
+ files: "files";
171
+ model: "model";
172
+ }>>>;
173
+ integration: z.ZodOptional<z.ZodObject<{
174
+ name: z.ZodString;
175
+ description: z.ZodString;
176
+ oauth: z.ZodOptional<z.ZodObject<{
177
+ authorizeUrl: z.ZodString;
178
+ tokenUrl: z.ZodString;
179
+ apiOrigin: z.ZodString;
180
+ scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
181
+ account: z.ZodOptional<z.ZodObject<{
182
+ path: z.ZodString;
183
+ name: z.ZodString;
184
+ }, z.core.$strip>>;
185
+ }, z.core.$strip>>;
186
+ token: z.ZodOptional<z.ZodObject<{
187
+ apiOrigin: z.ZodString;
188
+ account: z.ZodOptional<z.ZodObject<{
189
+ path: z.ZodString;
190
+ name: z.ZodString;
191
+ }, z.core.$strip>>;
192
+ scheme: z.ZodOptional<z.ZodEnum<{
193
+ raw: "raw";
194
+ bearer: "bearer";
195
+ basic: "basic";
196
+ }>>;
197
+ usernameLabel: z.ZodOptional<z.ZodString>;
198
+ }, z.core.$strip>>;
199
+ host: z.ZodOptional<z.ZodObject<{
200
+ provider: z.ZodEnum<{
201
+ linear: "linear";
202
+ }>;
203
+ }, z.core.$strip>>;
204
+ settings: z.ZodOptional<z.ZodArray<z.ZodObject<{
205
+ id: z.ZodString;
206
+ label: z.ZodString;
207
+ }, z.core.$strip>>>;
208
+ }, z.core.$strip>>;
209
+ service: z.ZodOptional<z.ZodObject<{
210
+ entry: z.ZodString;
211
+ runtime: z.ZodLiteral<"host">;
212
+ permissions: z.ZodOptional<z.ZodPipe<z.ZodObject<{
213
+ sockets: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodPipe<z.ZodString, z.ZodTransform<SocketBinding, string>>, z.ZodPipe<z.ZodObject<{
214
+ id: z.ZodString;
215
+ path: z.ZodOptional<z.ZodString>;
216
+ candidates: z.ZodOptional<z.ZodObject<{
217
+ linux: z.ZodOptional<z.ZodArray<z.ZodString>>;
218
+ darwin: z.ZodOptional<z.ZodArray<z.ZodString>>;
219
+ win32: z.ZodOptional<z.ZodArray<z.ZodString>>;
220
+ }, z.core.$strict>>;
221
+ }, z.core.$strict>, z.ZodTransform<SocketBinding, {
222
+ id: string;
223
+ path?: string | undefined;
224
+ candidates?: {
225
+ linux?: string[] | undefined;
226
+ darwin?: string[] | undefined;
227
+ win32?: string[] | undefined;
228
+ } | undefined;
229
+ }>>]>>>;
230
+ exec: z.ZodOptional<z.ZodArray<z.ZodString>>;
231
+ }, z.core.$strict>, z.ZodTransform<ServicePermissions, {
232
+ sockets?: SocketBinding[] | undefined;
233
+ exec?: string[] | undefined;
234
+ }>>>;
235
+ }, z.core.$strip>>;
236
+ filesystem: z.ZodOptional<z.ZodArray<z.ZodString>>;
237
+ actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
238
+ id: z.ZodString;
239
+ label: z.ZodString;
240
+ icon: z.ZodOptional<z.ZodString>;
241
+ where: z.ZodEnum<{
242
+ message: "message";
243
+ session: "session";
244
+ }>;
245
+ roles: z.ZodOptional<z.ZodArray<z.ZodEnum<{
246
+ user: "user";
247
+ assistant: "assistant";
248
+ }>>>;
249
+ payload: z.ZodOptional<z.ZodArray<z.ZodEnum<{
250
+ messages: "messages";
251
+ }>>>;
252
+ }, z.core.$strip>>>;
253
+ commands: z.ZodOptional<z.ZodArray<z.ZodObject<{
254
+ name: z.ZodString;
255
+ description: z.ZodOptional<z.ZodString>;
256
+ }, z.core.$strip>>>;
257
+ tools: z.ZodOptional<z.ZodArray<z.ZodObject<{
258
+ match: z.ZodString;
259
+ name: z.ZodOptional<z.ZodString>;
260
+ icon: z.ZodOptional<z.ZodString>;
261
+ title: z.ZodOptional<z.ZodString>;
262
+ subtitle: z.ZodOptional<z.ZodString>;
263
+ output: z.ZodOptional<z.ZodEnum<{
264
+ code: "code";
265
+ text: "text";
266
+ auto: "auto";
267
+ json: "json";
268
+ markdown: "markdown";
269
+ table: "table";
270
+ }>>;
271
+ language: z.ZodOptional<z.ZodString>;
272
+ columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
273
+ }, z.core.$strip>>>;
274
+ }, z.core.$strip>;
275
+ }, z.core.$strip>;
276
+ }, z.core.$strip>;
277
+ export type ManifestDocument = z.input<typeof openChamberManifestSchema> | z.input<typeof packageManifestSchema>;
278
+ export declare const parseManifest: (document: ManifestDocument) => ParseManifestResult;
279
+ export declare const parseManifestJson: (json: string) => ParseManifestResult;
package/dist/parse.js ADDED
@@ -0,0 +1,379 @@
1
+ import { z } from 'zod';
2
+ import { OPENCHAMBER_SDK_MANIFEST_API_VERSIONS } from "./api-version.js";
3
+ import { OPENCHAMBER_ENGINE_PATTERN } from "./host-version.js";
4
+ import { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_FILESYSTEM_PATTERNS_MAX, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_TOOLS_MAX, GUEST_TOOL_COLUMNS_MAX, GUEST_TOOL_COLUMN_MAX, GUEST_TOOL_LANGUAGE_MAX, GUEST_TOOL_MATCH, GUEST_TOOL_MATCH_MAX, GUEST_TOOL_NAME_MAX, GUEST_TOOL_OUTPUTS, GUEST_TOOL_TEMPLATE_MAX, PANEL_ID, hasGuestPage, isGuestFilesystemPattern, isGuestPackageSvgIcon, isSafeAssetPath, resolveIntegrationAuth, } from "./manifest.js";
5
+ const isPanelIcon = (value) => (PANEL_ID.test(value) || isGuestPackageSvgIcon(value));
6
+ const isHttpsUrl = (value) => {
7
+ try {
8
+ const parsed = new URL(value);
9
+ return parsed.protocol === 'https:' && parsed.username === '' && parsed.password === '';
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ };
15
+ const isHttpsOrigin = (value) => {
16
+ try {
17
+ const parsed = new URL(value);
18
+ return parsed.protocol === 'https:'
19
+ && parsed.username === ''
20
+ && parsed.password === ''
21
+ && parsed.pathname === '/'
22
+ && parsed.search === ''
23
+ && parsed.hash === ''
24
+ && value === parsed.origin;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ };
30
+ const isSafeApiPath = (value) => {
31
+ if (!value.startsWith('/') || value.includes('\0') || value.includes('\\') || value.includes('://')) {
32
+ return false;
33
+ }
34
+ const segments = value.split('/');
35
+ return !segments.some((segment) => segment === '.' || segment === '..');
36
+ };
37
+ const ACCOUNT_NAME = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)*$/;
38
+ const attachModeSchema = z.enum(['panel', 'dialog']);
39
+ // `entry` only means something for the dialog; a panel-mode object naming one
40
+ // is a misconfiguration and fails closed rather than carrying a dead field.
41
+ const attachObjectSchema = z.object({
42
+ mode: attachModeSchema,
43
+ entry: z.string().trim().refine(isSafeAssetPath).optional(),
44
+ }).refine((value) => value.mode === 'dialog' || value.entry === undefined, { path: ['entry'] });
45
+ const attachSchema = z.union([z.boolean(), attachModeSchema, attachObjectSchema]);
46
+ const panelSchema = z.object({
47
+ id: z.string().trim().regex(PANEL_ID),
48
+ name: z.string().trim().min(1),
49
+ icon: z.string().trim().refine(isPanelIcon),
50
+ entry: z.string().trim().refine(isSafeAssetPath).optional(),
51
+ });
52
+ const integrationSettingSchema = z.object({
53
+ id: z.string().trim().regex(PANEL_ID),
54
+ label: z.string().trim().min(1),
55
+ });
56
+ const integrationOauthSchema = z.object({
57
+ authorizeUrl: z.string().trim().refine(isHttpsUrl),
58
+ tokenUrl: z.string().trim().refine(isHttpsUrl),
59
+ apiOrigin: z.string().trim().refine(isHttpsOrigin),
60
+ scopes: z.array(z.string().trim().min(1)).optional(),
61
+ account: z.object({
62
+ path: z.string().trim().refine(isSafeApiPath),
63
+ name: z.string().trim().regex(ACCOUNT_NAME),
64
+ }).optional(),
65
+ });
66
+ const integrationTokenSchema = z.object({
67
+ apiOrigin: z.string().trim().refine(isHttpsOrigin),
68
+ account: z.object({
69
+ path: z.string().trim().refine(isSafeApiPath),
70
+ name: z.string().trim().regex(ACCOUNT_NAME),
71
+ }).optional(),
72
+ scheme: z.enum(['raw', 'bearer', 'basic']).optional(),
73
+ usernameLabel: z.string().trim().min(1).max(64).optional(),
74
+ });
75
+ const integrationHostSchema = z.object({
76
+ provider: z.enum(['linear']),
77
+ });
78
+ const integrationSchema = z.object({
79
+ name: z.string().trim().min(1),
80
+ description: z.string().trim().min(1),
81
+ oauth: integrationOauthSchema.optional(),
82
+ token: integrationTokenSchema.optional(),
83
+ host: integrationHostSchema.optional(),
84
+ settings: z.array(integrationSettingSchema).optional(),
85
+ }).refine((value) => resolveIntegrationAuth(value) !== null, { path: ['oauth'] });
86
+ const SOCKET_PLATFORMS = ['linux', 'darwin', 'win32'];
87
+ const socketPathSchema = z.string().trim().min(1).max(512);
88
+ const socketCandidatesSchema = z.object({
89
+ linux: z.array(socketPathSchema).max(16).optional(),
90
+ darwin: z.array(socketPathSchema).max(16).optional(),
91
+ win32: z.array(socketPathSchema).max(16).optional(),
92
+ }).strict();
93
+ const socketBindingObjectSchema = z.object({
94
+ id: z.string().trim().regex(PANEL_ID).max(64),
95
+ path: socketPathSchema.optional(),
96
+ candidates: socketCandidatesSchema.optional(),
97
+ }).strict().refine((value) => Boolean(value.path) || Boolean(value.candidates), { message: 'socket binding needs path or candidates' });
98
+ const normalizeSocketObject = (entry) => {
99
+ const candidatesByPlatform = {};
100
+ if (entry.candidates) {
101
+ for (const platform of SOCKET_PLATFORMS) {
102
+ const list = entry.candidates[platform];
103
+ if (list && list.length > 0) {
104
+ candidatesByPlatform[platform] = [...list];
105
+ }
106
+ }
107
+ }
108
+ else if (entry.path) {
109
+ for (const platform of SOCKET_PLATFORMS) {
110
+ candidatesByPlatform[platform] = [entry.path];
111
+ }
112
+ }
113
+ return { id: entry.id, candidatesByPlatform };
114
+ };
115
+ const socketEntrySchema = z.union([
116
+ socketPathSchema.transform((path) => ({
117
+ id: path,
118
+ candidatesByPlatform: {
119
+ linux: [path],
120
+ darwin: [path],
121
+ win32: [path],
122
+ },
123
+ })),
124
+ socketBindingObjectSchema.transform(normalizeSocketObject),
125
+ ]);
126
+ const servicePermissionsSchema = z.object({
127
+ sockets: z.array(socketEntrySchema).max(32).optional(),
128
+ exec: z.array(z.string().trim().min(1).max(128)).max(32).optional(),
129
+ }).strict().transform((value) => {
130
+ const next = {};
131
+ if (value.sockets && value.sockets.length > 0) {
132
+ next.sockets = value.sockets;
133
+ }
134
+ if (value.exec && value.exec.length > 0) {
135
+ next.exec = [...value.exec];
136
+ }
137
+ return next;
138
+ });
139
+ const serviceSchema = z.object({
140
+ entry: z.string().trim().refine(isSafeAssetPath),
141
+ runtime: z.literal('host'),
142
+ permissions: servicePermissionsSchema.optional(),
143
+ });
144
+ const uniqueBy = (items, key) => (new Set(items.map(key)).size === items.length);
145
+ // `roles` only means something on a message action and `payload` only on a
146
+ // session action; a dead field is a misconfiguration and fails closed.
147
+ const actionSchema = z.object({
148
+ id: z.string().trim().regex(PANEL_ID).max(64),
149
+ label: z.string().trim().min(1).max(GUEST_ACTION_LABEL_MAX),
150
+ icon: z.string().trim().refine(isPanelIcon).optional(),
151
+ where: z.enum(['message', 'session']),
152
+ roles: z.array(z.enum(['user', 'assistant'])).min(1).max(2).optional(),
153
+ payload: z.array(z.enum(['messages'])).max(1).optional(),
154
+ }).refine((value) => value.where === 'message' || value.roles === undefined, { path: ['roles'] })
155
+ .refine((value) => value.where === 'session' || value.payload === undefined, { path: ['payload'] });
156
+ const actionsSchema = z.array(actionSchema).min(1).max(GUEST_ACTIONS_MAX)
157
+ .refine((actions) => uniqueBy(actions, (action) => action.id), { message: 'action ids must be unique' });
158
+ const commandSchema = z.object({
159
+ name: z.string().trim().regex(GUEST_COMMAND_NAME),
160
+ description: z.string().trim().min(1).max(GUEST_COMMAND_DESCRIPTION_MAX).optional(),
161
+ });
162
+ const commandsSchema = z.array(commandSchema).min(1).max(GUEST_COMMANDS_MAX)
163
+ .refine((commands) => uniqueBy(commands, (command) => command.name), { message: 'command names must be unique' });
164
+ // `language` only means something for `code` and `columns` only for `table`;
165
+ // a table without columns has nothing to draw. Each is a misconfiguration
166
+ // that fails closed instead of carrying a dead field.
167
+ const toolSchema = z.object({
168
+ match: z.string().trim().min(1).max(GUEST_TOOL_MATCH_MAX).regex(GUEST_TOOL_MATCH),
169
+ name: z.string().trim().min(1).max(GUEST_TOOL_NAME_MAX).optional(),
170
+ icon: z.string().trim().refine(isPanelIcon).optional(),
171
+ title: z.string().trim().min(1).max(GUEST_TOOL_TEMPLATE_MAX).optional(),
172
+ subtitle: z.string().trim().min(1).max(GUEST_TOOL_TEMPLATE_MAX).optional(),
173
+ output: z.enum(GUEST_TOOL_OUTPUTS).optional(),
174
+ language: z.string().trim().min(1).max(GUEST_TOOL_LANGUAGE_MAX).optional(),
175
+ columns: z.array(z.string().trim().min(1).max(GUEST_TOOL_COLUMN_MAX)).min(1).max(GUEST_TOOL_COLUMNS_MAX).optional(),
176
+ }).refine((value) => value.output === 'code' || value.language === undefined, { path: ['language'] })
177
+ .refine((value) => (value.output === 'table') === (value.columns !== undefined), { path: ['columns'] });
178
+ const toolsSchema = z.array(toolSchema).min(1).max(GUEST_TOOLS_MAX);
179
+ const contributesSchema = z.object({
180
+ panel: panelSchema,
181
+ attach: attachSchema.optional(),
182
+ page: z.union([z.literal(true), z.object({
183
+ entry: z.string().trim().refine(isSafeAssetPath),
184
+ title: z.string().trim().min(1).max(200).optional(),
185
+ })]).optional(),
186
+ capabilities: z.array(z.enum(DECLARED_GUEST_CAPABILITIES)).max(8).optional(),
187
+ integration: integrationSchema.optional(),
188
+ service: serviceSchema.optional(),
189
+ filesystem: z.array(z.string().max(GUEST_FILESYSTEM_PATTERN_MAX).refine(isGuestFilesystemPattern)).min(1).max(GUEST_FILESYSTEM_PATTERNS_MAX).optional(),
190
+ actions: actionsSchema.optional(),
191
+ commands: commandsSchema.optional(),
192
+ tools: toolsSchema.optional(),
193
+ });
194
+ /**
195
+ * Everything that only makes sense with an iframe to mount. Without
196
+ * `panel.entry` there is nothing to open from the rail, the + menu, a menu
197
+ * action, or a slash command, and nothing to hand a capability, a service,
198
+ * an integration, or a filesystem grant to; only `tools` stays meaningful.
199
+ */
200
+ const pageOnlyContributions = (contributes) => {
201
+ const declared = [];
202
+ if (contributes.page !== undefined)
203
+ declared.push('page');
204
+ if (contributes.attach !== undefined && contributes.attach !== false)
205
+ declared.push('attach');
206
+ if (contributes.capabilities && contributes.capabilities.length > 0)
207
+ declared.push('capabilities');
208
+ if (contributes.integration !== undefined)
209
+ declared.push('integration');
210
+ if (contributes.service !== undefined)
211
+ declared.push('service');
212
+ if (contributes.filesystem !== undefined)
213
+ declared.push('filesystem');
214
+ if (contributes.actions !== undefined)
215
+ declared.push('actions');
216
+ if (contributes.commands !== undefined)
217
+ declared.push('commands');
218
+ return declared;
219
+ };
220
+ export const openChamberManifestSchema = z.object({
221
+ apiVersion: z.literal(OPENCHAMBER_SDK_MANIFEST_API_VERSIONS[0]),
222
+ engines: z.object({
223
+ openchamber: z.string().trim().regex(OPENCHAMBER_ENGINE_PATTERN),
224
+ }).strict().optional(),
225
+ contributes: contributesSchema.superRefine((contributes, ctx) => {
226
+ if (hasGuestPage(contributes))
227
+ return;
228
+ const needsPage = pageOnlyContributions(contributes);
229
+ if (needsPage.length === 0)
230
+ return;
231
+ ctx.addIssue({
232
+ code: 'custom',
233
+ path: ['panel'],
234
+ message: `${needsPage.map((key) => `contributes.${key}`).join(', ')} needs panel.entry; an extension without a page may only declare tools.`,
235
+ });
236
+ }),
237
+ });
238
+ export const PACKAGE_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
239
+ const packageVersionSchema = z.string().trim().regex(PACKAGE_VERSION_PATTERN).max(64);
240
+ export const packageManifestSchema = z.object({
241
+ version: packageVersionSchema.optional(),
242
+ openchamber: openChamberManifestSchema,
243
+ });
244
+ const packageEnvelopeSchema = z.union([
245
+ z.object({ openchamber: z.null() }),
246
+ z.object({ openchamber: z.object({}).passthrough() }),
247
+ ]);
248
+ const fail = (code, message) => ({
249
+ ok: false,
250
+ code,
251
+ message,
252
+ });
253
+ const failureFromIssue = (issue) => {
254
+ // Paths are reported relative to the `openchamber` block whether the
255
+ // document was a bare manifest or a package.json envelope.
256
+ const segments = issue.path.map(String);
257
+ const path = (segments[0] === 'openchamber' ? segments.slice(1) : segments).join('.');
258
+ const isPackageEnvelope = segments[0] === 'openchamber';
259
+ if (!path && issue.code === 'invalid_type') {
260
+ return isPackageEnvelope
261
+ ? fail('missing-openchamber', 'package.json openchamber must be a plain object.')
262
+ : fail('not-object', 'Manifest must be a plain object.');
263
+ }
264
+ if (segments.length === 1 && segments[0] === 'version') {
265
+ return fail('invalid-version', 'package.json version must be semver like 1.0.0.');
266
+ }
267
+ if (path === 'apiVersion') {
268
+ return fail('unsupported-api-version', `Unsupported apiVersion. This host accepts ${OPENCHAMBER_SDK_MANIFEST_API_VERSIONS.join(' and ')}.`);
269
+ }
270
+ if (path === 'engines' || path.startsWith('engines.')) {
271
+ return fail('invalid-engines', 'engines.openchamber must be a version like 1.22.0 or >=1.22.0.');
272
+ }
273
+ if (path === 'contributes.panel' && issue.code === 'custom') {
274
+ return fail('invalid-panel', issue.message);
275
+ }
276
+ if (path === 'contributes' || path === 'contributes.panel') {
277
+ return fail('missing-panel', 'contributes.panel is required.');
278
+ }
279
+ switch (path) {
280
+ case 'contributes.panel.id':
281
+ return fail('invalid-panel-id', 'panel.id must be kebab-case starting with a letter.');
282
+ case 'contributes.panel.name':
283
+ return fail('invalid-panel-name', 'panel.name must be a non-empty string.');
284
+ case 'contributes.panel.icon':
285
+ return fail('invalid-panel-icon', 'panel.icon must be a Remixicon name or a package .svg path.');
286
+ case 'contributes.panel.entry':
287
+ return fail('invalid-panel-entry', 'panel.entry must be a relative path inside the package.');
288
+ case 'contributes.capabilities':
289
+ return fail('invalid-capabilities', 'contributes.capabilities may list "prompt", "sessions", and "files".');
290
+ default:
291
+ break;
292
+ }
293
+ if (path === 'contributes.page' || path.startsWith('contributes.page.')) {
294
+ return fail('invalid-page', 'contributes.page must be true or { entry: "<package HTML>", title?: "Page title" }.');
295
+ }
296
+ if (path === 'contributes.attach' || path.startsWith('contributes.attach.')) {
297
+ return fail('invalid-attach', 'contributes.attach must be true, false, "panel", "dialog", or { "mode": "panel" | "dialog", "entry"?: "<html inside the package, dialog only>" }.');
298
+ }
299
+ if (path.startsWith('contributes.capabilities')) {
300
+ return fail('invalid-capabilities', 'contributes.capabilities may list "prompt", "sessions", and "files".');
301
+ }
302
+ if (path.startsWith('contributes.filesystem')) {
303
+ return fail('invalid-filesystem', 'contributes.filesystem lists 1 to 16 patterns starting with "/" or "~/", without "..", empty segments, or backslashes.');
304
+ }
305
+ if (path.startsWith('contributes.actions')) {
306
+ return fail('invalid-actions', 'contributes.actions lists up to 8 entries with a unique kebab-case id, a label of 1 to 40 characters, where "message" or "session", optional roles (message only), and optional payload ["messages"] (session only).');
307
+ }
308
+ if (path.startsWith('contributes.commands')) {
309
+ return fail('invalid-commands', 'contributes.commands lists up to 8 entries with a unique name matching /^[a-z][a-z0-9-]{0,23}$/ and an optional description of 1 to 80 characters.');
310
+ }
311
+ if (path.startsWith('contributes.tools')) {
312
+ return fail('invalid-tools', 'contributes.tools lists up to 16 entries with a match of 1 to 128 characters ([A-Za-z0-9_.:-], "*" only at the end), optional name (1 to 40), icon (Remixicon name or package .svg path), title and subtitle templates (1 to 200), output "auto" | "text" | "json" | "markdown" | "code" | "table", language (code only), and columns (table only, 1 to 16).');
313
+ }
314
+ if (path.startsWith('contributes.integration')) {
315
+ return fail('invalid-integration', 'contributes.integration needs a name, description, and oauth, token, or host.');
316
+ }
317
+ if (path.startsWith('contributes.service')) {
318
+ return fail('invalid-service', 'contributes.service needs entry, runtime "host", and optional permissions.');
319
+ }
320
+ return fail('missing-panel', 'contributes.panel is required.');
321
+ };
322
+ const decodeManifest = (parsed) => {
323
+ if (parsed.success) {
324
+ return { ok: true, manifest: parsed.data };
325
+ }
326
+ const issue = parsed.error.issues[0];
327
+ if (!issue) {
328
+ return fail('not-object', 'Manifest must be a plain object.');
329
+ }
330
+ return failureFromIssue(issue);
331
+ };
332
+ export const parseManifest = (document) => {
333
+ if ('openchamber' in document) {
334
+ const parsed = packageManifestSchema.safeParse(document);
335
+ if (parsed.success) {
336
+ const success = {
337
+ ok: true,
338
+ manifest: parsed.data.openchamber,
339
+ };
340
+ if (parsed.data.version) {
341
+ success.version = parsed.data.version;
342
+ }
343
+ return success;
344
+ }
345
+ const issue = parsed.error.issues[0];
346
+ if (!issue) {
347
+ return fail('not-object', 'Manifest must be a plain object.');
348
+ }
349
+ return failureFromIssue(issue);
350
+ }
351
+ return decodeManifest(openChamberManifestSchema.safeParse(document));
352
+ };
353
+ export const parseManifestJson = (json) => {
354
+ try {
355
+ const raw = JSON.parse(json);
356
+ if (packageEnvelopeSchema.safeParse(raw).success) {
357
+ const parsed = packageManifestSchema.safeParse(raw);
358
+ if (parsed.success) {
359
+ const success = {
360
+ ok: true,
361
+ manifest: parsed.data.openchamber,
362
+ };
363
+ if (parsed.data.version) {
364
+ success.version = parsed.data.version;
365
+ }
366
+ return success;
367
+ }
368
+ const issue = parsed.error.issues[0];
369
+ if (!issue) {
370
+ return fail('not-object', 'Manifest must be a plain object.');
371
+ }
372
+ return failureFromIssue(issue);
373
+ }
374
+ return decodeManifest(openChamberManifestSchema.safeParse(raw));
375
+ }
376
+ catch {
377
+ return fail('not-object', 'Manifest must be a plain object.');
378
+ }
379
+ };