@modelprofile.com/browser-runtime 1.0.1 → 2.1.0

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/ts/errors.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export type TBrowserRuntimeErrorCode =
2
2
  | 'ABORTED'
3
3
  | 'AUTHORIZATION_DENIED'
4
+ | 'ATTACHMENT_CONFLICT'
4
5
  | 'BUSY'
5
6
  | 'CAPABILITY_EXPIRED'
6
7
  | 'CAPABILITY_INVALID'
@@ -14,24 +15,29 @@ export type TBrowserRuntimeErrorCode =
14
15
  | 'NOT_RUNNING'
15
16
  | 'PROTOCOL_ERROR'
16
17
  | 'QUOTA_EXCEEDED'
18
+ | 'RESOURCE_NOT_FOUND'
19
+ | 'RESOURCE_RETIRED'
17
20
  | 'TIMEOUT';
18
21
 
19
22
  const publicMessages: Record<TBrowserRuntimeErrorCode, string> = {
20
23
  ABORTED: 'The operation was aborted.',
21
24
  AUTHORIZATION_DENIED: 'Capability authorization was denied.',
22
- BUSY: 'The browser project is busy.',
25
+ ATTACHMENT_CONFLICT: 'The attachment binding conflicts with the current revision.',
26
+ BUSY: 'The browser resource is busy.',
23
27
  CAPABILITY_EXPIRED: 'The capability has expired.',
24
28
  CAPABILITY_INVALID: 'The capability is invalid.',
25
29
  CAPABILITY_REVOKED: 'The capability has been revoked.',
26
30
  CONFINEMENT_FAILED: 'Browser confinement could not be confirmed.',
27
31
  EGRESS_DENIED: 'The egress target was denied.',
28
- FENCED: 'The browser project is permanently fenced.',
32
+ FENCED: 'The browser resource is permanently fenced.',
29
33
  FRAME_TOO_LARGE: 'The browser frame exceeded its limit.',
30
34
  INVALID_INPUT: 'The request is invalid.',
31
35
  LOCKED: 'The browser runtime is already locked.',
32
36
  NOT_RUNNING: 'The browser runtime is not running.',
33
37
  PROTOCOL_ERROR: 'The framed protocol request is invalid.',
34
38
  QUOTA_EXCEEDED: 'A browser runtime quota was exceeded.',
39
+ RESOURCE_NOT_FOUND: 'The browser resource is not registered.',
40
+ RESOURCE_RETIRED: 'The browser resource has been retired.',
35
41
  TIMEOUT: 'The operation timed out.',
36
42
  };
37
43
 
package/ts/index.ts CHANGED
@@ -10,37 +10,49 @@ export { BrowserRuntime, BrowserRuntimeLease } from './classes.runtime.js';
10
10
  export { BrowserRuntimeError } from './errors.js';
11
11
  export { createBrowserRuntimeMcpHttpHandler } from './mcp.js';
12
12
 
13
- export type { IAcquireBrowserRuntimeLeaseOptions } from './classes.runtime.js';
13
+ export type { TAcquireBrowserRuntimeLeaseOptions } from './classes.runtime.js';
14
14
  export type { TBrowserRuntimeErrorCode } from './errors.js';
15
15
  export type {
16
16
  IAttachTrustedFramedPeerOptions,
17
+ IApplyBrowserAttachmentBindingRequest,
18
+ IBrowserAttachmentBinding,
17
19
  IBrowserActionStateResult,
18
20
  IBrowserArtifactMetadata,
19
21
  IBrowserArtifactStoreOptions,
20
- IBrowserCapabilityAuthorizationRequest,
21
- IBrowserCapabilityDescriptor,
22
+ IBrowserMcpAuthenticatedBinding,
23
+ TBrowserCapabilityAuthorizationRequest,
24
+ TBrowserCapabilityDescriptor,
22
25
  IBrowserClickAction,
23
26
  IBrowserEgressConnectionOptions,
24
27
  IBrowserEgressProxyOptions,
25
28
  IBrowserEgressProxyStats,
29
+ IBrowserFlexCapabilityBinding,
30
+ IBrowserFlexSessionId,
26
31
  IBrowserFillAction,
27
32
  IBrowserNavigateAction,
28
33
  IBrowserObservationResult,
34
+ IBrowserOpenCodeSessionId,
29
35
  IBrowserPressAction,
30
- IBrowserRuntimeAuditEvent,
36
+ TBrowserRuntimeAuditEvent,
37
+ TBrowserRuntimeOperationIdentity,
31
38
  IBrowserRuntimeFlexToolProviderOptions,
32
39
  IBrowserRuntimeFrameSubscription,
33
40
  IBrowserRuntimeFramedClientOptions,
34
41
  IBrowserRuntimeMcpOptions,
35
42
  IBrowserRuntimeOperationOptions,
36
43
  IBrowserRuntimeOptions,
44
+ IBrowserResourceKey,
45
+ IBrowserResourceRegistration,
37
46
  IBrowserRuntimeState,
38
47
  IBrowserRuntimeTabState,
39
48
  IBrowserScreenshotAction,
40
49
  IBrowserScreenshotResult,
41
50
  IBrowserSnapshotAction,
42
- IIssueBrowserCapabilityRequest,
43
- IIssuedBrowserCapability,
51
+ ICreateBrowserResourceRequest,
52
+ TIssueBrowserCapabilityRequest,
53
+ TIssuedBrowserCapability,
54
+ TQualifiedBrowserSessionId,
55
+ IRegisterBrowserResourceRequest,
44
56
  IResolvedBrowserFlexCapability,
45
57
  TBrowserActorRole,
46
58
  TBrowserAgentAction,
@@ -48,5 +60,6 @@ export type {
48
60
  TBrowserCapabilitySource,
49
61
  TBrowserDnsResolver,
50
62
  TBrowserEgressConnector,
63
+ TBrowserHarnessId,
51
64
  TBrowserRuntimeEvent,
52
65
  } from './interfaces.js';
package/ts/interfaces.ts CHANGED
@@ -1,49 +1,131 @@
1
1
  import type * as plugins from './plugins.js';
2
+ import type { BrowserEgressProxy } from './classes.egressproxy.js';
3
+ import type { BrowserRuntimeFramedClient } from './classes.framed.js';
2
4
 
3
5
  export type TBrowserActorRole = 'human' | 'agent';
4
6
  export type TBrowserCapabilitySource = 'human' | 'flex' | 'mcp';
7
+ export type TBrowserHarnessId = 'opencode' | 'flex';
5
8
 
6
- export interface IBrowserCapabilityAuthorizationRequest {
9
+ export interface IBrowserOpenCodeSessionId {
10
+ harnessId: 'opencode';
11
+ nativeId: string;
12
+ }
13
+
14
+ export interface IBrowserFlexSessionId {
15
+ harnessId: 'flex';
16
+ nativeId: string;
17
+ }
18
+
19
+ export type TQualifiedBrowserSessionId = IBrowserOpenCodeSessionId | IBrowserFlexSessionId;
20
+
21
+ export interface IBrowserResourceKey {
7
22
  projectId: string;
23
+ browserResourceId: string;
24
+ }
25
+
26
+ export interface IBrowserAttachmentBinding {
27
+ attachmentAuthorityId: string;
28
+ attachmentRevision: number;
29
+ sessionId: TQualifiedBrowserSessionId | null;
30
+ }
31
+
32
+ export interface ICreateBrowserResourceRequest {
33
+ projectId: string;
34
+ attachmentBinding: IBrowserAttachmentBinding;
35
+ }
36
+
37
+ export interface IRegisterBrowserResourceRequest extends IBrowserResourceKey {
38
+ attachmentBinding: IBrowserAttachmentBinding;
39
+ }
40
+
41
+ export interface IApplyBrowserAttachmentBindingRequest extends IBrowserResourceKey {
42
+ attachmentBinding: IBrowserAttachmentBinding;
43
+ }
44
+
45
+ export interface IBrowserResourceRegistration extends IBrowserResourceKey {
46
+ registeredAt: number;
47
+ attachmentBinding: IBrowserAttachmentBinding;
48
+ incarnationStatus: 'absent' | 'running' | 'fenced';
49
+ incarnationGeneration: number;
50
+ retirementPending: boolean;
51
+ }
52
+
53
+ export interface IBrowserCapabilityBindingBase extends IBrowserResourceKey {
54
+ attachmentAuthorityId: string;
55
+ attachmentRevision: number;
8
56
  actorId: string;
9
- role: TBrowserActorRole;
10
57
  peerId: string;
11
- source: TBrowserCapabilitySource;
12
- scopeId?: string;
13
- sessionId?: string;
14
58
  }
15
59
 
16
- export interface IIssueBrowserCapabilityRequest extends IBrowserCapabilityAuthorizationRequest {
60
+ export interface IBrowserHumanCapabilityBinding extends IBrowserCapabilityBindingBase {
61
+ role: 'human';
62
+ source: 'human';
63
+ sessionId?: never;
64
+ scopeId?: never;
65
+ channelId?: never;
66
+ }
67
+
68
+ export interface IBrowserFlexCapabilityBinding extends IBrowserCapabilityBindingBase {
69
+ role: 'agent';
70
+ source: 'flex';
71
+ sessionId: IBrowserFlexSessionId;
72
+ scopeId: string;
73
+ channelId: string;
74
+ }
75
+
76
+ export interface IBrowserMcpCapabilityBinding extends IBrowserCapabilityBindingBase {
77
+ role: 'agent';
78
+ source: 'mcp';
79
+ sessionId: IBrowserOpenCodeSessionId;
80
+ scopeId?: never;
81
+ channelId?: never;
82
+ }
83
+
84
+ export type TBrowserCapabilityAuthorizationRequest =
85
+ | IBrowserHumanCapabilityBinding
86
+ | IBrowserFlexCapabilityBinding
87
+ | IBrowserMcpCapabilityBinding;
88
+
89
+ export type TIssueBrowserCapabilityRequest = TBrowserCapabilityAuthorizationRequest & {
17
90
  expiresInMs?: number;
18
91
  disconnect?: (signal: AbortSignal) => Promise<void> | void;
19
- }
92
+ };
20
93
 
21
- export interface IBrowserCapabilityDescriptor extends IBrowserCapabilityAuthorizationRequest {
94
+ export type TBrowserCapabilityDescriptor = TBrowserCapabilityAuthorizationRequest & {
22
95
  capabilityId: string;
23
96
  expiresAt: number;
24
- }
97
+ };
25
98
 
26
- export interface IIssuedBrowserCapability extends IBrowserCapabilityDescriptor {
99
+ export type TIssuedBrowserCapability = TBrowserCapabilityDescriptor & {
27
100
  capabilityToken: string;
28
- }
101
+ };
29
102
 
30
103
  export interface IResolvedBrowserFlexCapability {
31
104
  capabilityToken: string;
32
105
  }
33
106
 
34
- export interface IBrowserRuntimeAuditEvent {
35
- operationId: string;
36
- capabilityId: string;
37
- projectId: string;
38
- actorId: string;
39
- role: TBrowserActorRole;
40
- source: TBrowserCapabilitySource;
41
- action: string;
42
- phase: 'completed' | 'failed';
43
- startedAt: number;
44
- finishedAt: number;
45
- errorCode?: string;
46
- }
107
+ type TReadonlyBrowserCapabilityAuthorizationRequest =
108
+ | Readonly<IBrowserHumanCapabilityBinding>
109
+ | (Omit<Readonly<IBrowserFlexCapabilityBinding>, 'sessionId'> & {
110
+ readonly sessionId: Readonly<IBrowserFlexSessionId>;
111
+ })
112
+ | (Omit<Readonly<IBrowserMcpCapabilityBinding>, 'sessionId'> & {
113
+ readonly sessionId: Readonly<IBrowserOpenCodeSessionId>;
114
+ });
115
+
116
+ export type TBrowserRuntimeOperationIdentity = TReadonlyBrowserCapabilityAuthorizationRequest & {
117
+ readonly operationId: string;
118
+ readonly capabilityId: string;
119
+ readonly leaseId: string;
120
+ readonly action: string;
121
+ readonly startedAt: number;
122
+ };
123
+
124
+ export type TBrowserRuntimeAuditEvent = TBrowserRuntimeOperationIdentity & {
125
+ readonly phase: 'completed' | 'failed';
126
+ readonly finishedAt: number;
127
+ readonly errorCode?: string;
128
+ };
47
129
 
48
130
  export interface ILiveBrowserSessionLike {
49
131
  start(options?: plugins.smartpuppeteer.ILiveBrowserOperationOptions): Promise<void>;
@@ -120,9 +202,9 @@ export interface ILiveBrowserSessionLike {
120
202
  ): Promise<void>;
121
203
  }
122
204
 
123
- export interface IBrowserConfinementProbeContext {
205
+ export interface IBrowserConfinementProbeContext extends IBrowserResourceKey {
124
206
  session: ILiveBrowserSessionLike;
125
- proxy: import('./classes.egressproxy.js').BrowserEgressProxy;
207
+ proxy: BrowserEgressProxy;
126
208
  signal: AbortSignal;
127
209
  }
128
210
 
@@ -134,27 +216,36 @@ export interface IBrowserRuntimeEnvironment {
134
216
  export interface IBrowserRuntimeOptions {
135
217
  runtimeDirectory: string;
136
218
  authorizeCapability(
137
- request: IBrowserCapabilityAuthorizationRequest,
219
+ request: TBrowserCapabilityAuthorizationRequest,
138
220
  signal: AbortSignal,
139
221
  ): Promise<boolean> | boolean;
140
- audit?(event: IBrowserRuntimeAuditEvent, signal: AbortSignal): Promise<void> | void;
141
- maxProjectSlots?: number;
222
+ beforeOperation?(
223
+ operation: TBrowserRuntimeOperationIdentity,
224
+ signal: AbortSignal,
225
+ ): Promise<void> | void;
226
+ audit?(event: TBrowserRuntimeAuditEvent, signal: AbortSignal): Promise<void> | void;
227
+ maxResources?: number;
228
+ maxResourcesPerProject?: number;
229
+ maxRunningResources?: number;
230
+ maxRunningResourcesPerProject?: number;
142
231
  maxCapabilities?: number;
232
+ maxCapabilitiesPerResource?: number;
143
233
  maxCapabilitiesPerProject?: number;
144
234
  capabilityDefaultTtlMs?: number;
145
235
  capabilityMaximumTtlMs?: number;
146
236
  authorizationTimeoutMs?: number;
147
- maxTabsPerProject?: number;
237
+ beforeOperationTimeoutMs?: number;
238
+ maxTabsPerResource?: number;
148
239
  operationTimeoutMs?: number;
149
240
  quiescenceTimeoutMs?: number;
150
241
  terminationGraceMs?: number;
151
242
  terminationForceMs?: number;
152
243
  auditTimeoutMs?: number;
153
244
  confinementProbeTimeoutMs?: number;
154
- idleRetirementMs?: number;
245
+ idleTerminationMs?: number;
155
246
  maxFrameBytes?: number;
156
247
  frameAcknowledgementTimeoutMs?: number;
157
- egress?: Omit<IBrowserEgressProxyOptions, 'projectId'>;
248
+ egress?: Omit<IBrowserEgressProxyOptions, 'projectId' | 'browserResourceId'>;
158
249
  artifacts?: Omit<IBrowserArtifactStoreOptions, 'rootDirectory'>;
159
250
  }
160
251
 
@@ -186,9 +277,8 @@ export interface IBrowserRuntimeState {
186
277
  };
187
278
  }
188
279
 
189
- export interface IBrowserArtifactMetadata {
280
+ export interface IBrowserArtifactMetadata extends IBrowserResourceKey {
190
281
  artifactId: string;
191
- projectId: string;
192
282
  mimeType: string;
193
283
  size: number;
194
284
  createdAt: number;
@@ -198,6 +288,8 @@ export interface IBrowserArtifactMetadata {
198
288
  export interface IBrowserArtifactStoreOptions {
199
289
  rootDirectory: string;
200
290
  maxFileBytes?: number;
291
+ maxArtifactsPerResource?: number;
292
+ maxResourceBytes?: number;
201
293
  maxArtifactsPerProject?: number;
202
294
  maxProjectBytes?: number;
203
295
  maxProjects?: number;
@@ -315,8 +407,7 @@ export type TBrowserEgressConnector = (
315
407
  options: IBrowserEgressConnectionOptions,
316
408
  ) => plugins.net.Socket;
317
409
 
318
- export interface IBrowserEgressProxyOptions {
319
- projectId: string;
410
+ export interface IBrowserEgressProxyOptions extends IBrowserResourceKey {
320
411
  allowedPorts?: number[];
321
412
  maxConnections?: number;
322
413
  maxActiveRequests?: number;
@@ -340,17 +431,24 @@ export interface IBrowserEgressProxyStats {
340
431
  activeConnections: number;
341
432
  }
342
433
 
343
- export interface IAttachTrustedFramedPeerOptions {
344
- peerId: string;
345
- scopeId: string;
346
- sessionId: string;
434
+ export interface IAttachTrustedFramedPeerOptions
435
+ extends Omit<
436
+ IBrowserFlexCapabilityBinding,
437
+ 'role' | 'source'
438
+ > {
439
+ role: 'agent';
440
+ source: 'flex';
347
441
  readable: plugins.stream.Readable;
348
442
  writable: plugins.stream.Writable;
349
443
  }
350
444
 
351
- export interface IBrowserRuntimeFramedClientOptions {
352
- scopeId: string;
353
- sessionId: string;
445
+ export interface IBrowserRuntimeFramedClientOptions
446
+ extends Omit<
447
+ IBrowserFlexCapabilityBinding,
448
+ 'role' | 'source'
449
+ > {
450
+ role: 'agent';
451
+ source: 'flex';
354
452
  readable: plugins.stream.Readable;
355
453
  writable: plugins.stream.Writable;
356
454
  maxFrameBytes?: number;
@@ -358,8 +456,16 @@ export interface IBrowserRuntimeFramedClientOptions {
358
456
  requestTimeoutMs?: number;
359
457
  }
360
458
 
459
+ export interface IBrowserMcpAuthenticatedBinding extends IBrowserCapabilityBindingBase {
460
+ role: 'agent';
461
+ source: 'mcp';
462
+ sessionId: IBrowserOpenCodeSessionId;
463
+ scopeId?: never;
464
+ channelId?: never;
465
+ }
466
+
361
467
  export interface IBrowserRuntimeMcpOptions {
362
- authenticateMcpRequest(request: Request): Promise<{ peerId: string }>;
468
+ authenticateMcpRequest(request: Request): Promise<IBrowserMcpAuthenticatedBinding>;
363
469
  trustedOrigins?: string[];
364
470
  allowedHosts?: string[];
365
471
  allowMissingOrigin?: boolean;
@@ -368,7 +474,7 @@ export interface IBrowserRuntimeMcpOptions {
368
474
  }
369
475
 
370
476
  export interface IBrowserRuntimeFlexToolProviderOptions<TScope> {
371
- client: import('./classes.framed.js').BrowserRuntimeFramedClient;
477
+ client: BrowserRuntimeFramedClient;
372
478
  resolveCapability(
373
479
  context: plugins.flexharness.IFlexToolProviderContext<TScope>,
374
480
  ): Promise<IResolvedBrowserFlexCapability> | IResolvedBrowserFlexCapability;
@@ -1,6 +1,7 @@
1
1
  import type * as plugins from './plugins.js';
2
2
  import type {
3
3
  IBrowserConfinementProbeContext,
4
+ IBrowserRuntimeFramedClientOptions,
4
5
  IBrowserRuntimeOptions,
5
6
  IBrowserRuntimeEnvironment,
6
7
  ILiveBrowserSessionLike,
@@ -15,4 +16,15 @@ export interface IBrowserRuntimeTestingOptions extends IBrowserRuntimeOptions {
15
16
  options: plugins.smartpuppeteer.ILiveBrowserSessionOptions,
16
17
  ): ILiveBrowserSessionLike;
17
18
  confinementProbe(context: IBrowserConfinementProbeContext): Promise<void>;
19
+ beforeLeasePublication?(): Promise<void>;
20
+ beforeIdleTermination?(): Promise<void>;
21
+ beforeFrameFailureTermination?(): Promise<void>;
22
+ afterFrameFailureFence?(): Promise<void>;
23
+ beforeFramedPeerLeasePublication?(): Promise<void>;
24
+ }
25
+
26
+ export interface IBrowserRuntimeFramedClientTestingOptions
27
+ extends IBrowserRuntimeFramedClientOptions {
28
+ [browserRuntimeTesting]: true;
29
+ afterAcquireResponse?(): Promise<void>;
18
30
  }
package/ts/mcp.ts CHANGED
@@ -2,7 +2,10 @@ import * as plugins from './plugins.js';
2
2
  import type { BrowserRuntime, BrowserRuntimeLease } from './classes.runtime.js';
3
3
  import { BrowserRuntimeError } from './errors.js';
4
4
  import { commitinfo } from './00_commitinfo_data.js';
5
- import type { IBrowserRuntimeMcpOptions } from './interfaces.js';
5
+ import type {
6
+ IBrowserMcpAuthenticatedBinding,
7
+ IBrowserRuntimeMcpOptions,
8
+ } from './interfaces.js';
6
9
  import { validateAgentActionResult } from './actions.js';
7
10
  import {
8
11
  validateBoundedString,
@@ -51,19 +54,17 @@ export const createBrowserRuntimeMcpHttpHandler = (
51
54
  context: plugins.smartmcp.ISmartMcpToolContext,
52
55
  action: (lease: BrowserRuntimeLease, signal: AbortSignal) => Promise<T>,
53
56
  ): Promise<T> => {
54
- const peerId = validateBoundedString(context.authInfo?.clientId, 'peerId', 1, 128);
57
+ const binding = parseMcpAuthBinding(context.authInfo?.scopes);
55
58
  const capabilityToken = parseBearerToken(context.request);
56
59
  const signal = AbortSignal.any([context.extra.signal, context.request.signal]);
57
60
  signal.throwIfAborted();
58
- const descriptor = runtime.authorizeCapability(capabilityToken, peerId, 'agent', 'mcp');
61
+ const descriptor = runtime.authorizeCapability(capabilityToken, binding);
59
62
  if (descriptor.capabilityId !== context.authInfo?.token) {
60
63
  throw new BrowserRuntimeError('CAPABILITY_INVALID');
61
64
  }
62
65
  const lease = await runtime.acquireLease({
63
66
  capabilityToken,
64
- peerId,
65
- expectedRole: 'agent',
66
- expectedSource: 'mcp',
67
+ ...binding,
67
68
  signal,
68
69
  });
69
70
  try {
@@ -203,22 +204,16 @@ export const createBrowserRuntimeMcpHttpHandler = (
203
204
  maxToolResultBytes,
204
205
  redactError: () => 'Browser tool execution failed.',
205
206
  authorize: async ({ request }) => {
206
- let peerId: string;
207
207
  let capabilityToken: string;
208
208
  try {
209
209
  const authentication = await options.authenticateMcpRequest(request);
210
- peerId = validateBoundedString(authentication?.peerId, 'peerId', 1, 128);
210
+ const binding = validateMcpBinding(authentication);
211
211
  capabilityToken = parseBearerToken(request);
212
- const descriptor = runtime.authorizeCapability(
213
- capabilityToken,
214
- peerId,
215
- 'agent',
216
- 'mcp',
217
- );
212
+ const descriptor = runtime.authorizeCapability(capabilityToken, binding);
218
213
  return {
219
214
  token: descriptor.capabilityId,
220
- clientId: peerId,
221
- scopes: ['browser'],
215
+ clientId: binding.peerId,
216
+ scopes: [serializeMcpAuthBinding(binding)],
222
217
  expiresAt: descriptor.expiresAt,
223
218
  };
224
219
  } catch {
@@ -228,3 +223,59 @@ export const createBrowserRuntimeMcpHttpHandler = (
228
223
  tools,
229
224
  });
230
225
  };
226
+
227
+ const validateMcpBinding = (value: unknown): IBrowserMcpAuthenticatedBinding => {
228
+ if (!value || typeof value !== 'object') throw new BrowserRuntimeError('CAPABILITY_INVALID');
229
+ const binding = value as Record<string, unknown>;
230
+ if (binding.role !== 'agent' || binding.source !== 'mcp') {
231
+ throw new BrowserRuntimeError('CAPABILITY_INVALID');
232
+ }
233
+ const session = binding.sessionId as Record<string, unknown> | undefined;
234
+ if (session?.harnessId !== 'opencode') throw new BrowserRuntimeError('CAPABILITY_INVALID');
235
+ return {
236
+ projectId: validateBoundedString(binding.projectId, 'projectId', 1, 256),
237
+ browserResourceId: validateBoundedString(
238
+ binding.browserResourceId,
239
+ 'browserResourceId',
240
+ 16,
241
+ 256,
242
+ ),
243
+ attachmentAuthorityId: validateBoundedString(
244
+ binding.attachmentAuthorityId,
245
+ 'attachmentAuthorityId',
246
+ 1,
247
+ 256,
248
+ ),
249
+ attachmentRevision: validateInteger(
250
+ binding.attachmentRevision,
251
+ 'attachmentRevision',
252
+ 0,
253
+ Number.MAX_SAFE_INTEGER,
254
+ ),
255
+ actorId: validateBoundedString(binding.actorId, 'actorId', 1, 256),
256
+ role: 'agent',
257
+ peerId: validateBoundedString(binding.peerId, 'peerId', 1, 256),
258
+ source: 'mcp',
259
+ sessionId: {
260
+ harnessId: 'opencode',
261
+ nativeId: validateBoundedString(session.nativeId, 'sessionId.nativeId', 1, 256),
262
+ },
263
+ };
264
+ };
265
+
266
+ const serializeMcpAuthBinding = (
267
+ binding: IBrowserMcpAuthenticatedBinding,
268
+ ): string => plugins.Buffer.from(JSON.stringify(binding), 'utf8').toString('base64url');
269
+
270
+ const parseMcpAuthBinding = (scopes: string[] | undefined): IBrowserMcpAuthenticatedBinding => {
271
+ if (!Array.isArray(scopes) || scopes.length !== 1) {
272
+ throw new BrowserRuntimeError('CAPABILITY_INVALID');
273
+ }
274
+ try {
275
+ return validateMcpBinding(JSON.parse(
276
+ plugins.Buffer.from(scopes[0]!, 'base64url').toString('utf8'),
277
+ ));
278
+ } catch {
279
+ throw new BrowserRuntimeError('CAPABILITY_INVALID');
280
+ }
281
+ };