@modelprofile.com/browser-runtime 1.0.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.
Files changed (51) hide show
  1. package/.smartconfig.json +34 -0
  2. package/changelog.md +11 -0
  3. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  4. package/dist_ts/00_commitinfo_data.js +9 -0
  5. package/dist_ts/actions.d.ts +3 -0
  6. package/dist_ts/actions.js +215 -0
  7. package/dist_ts/classes.artifactstore.d.ts +38 -0
  8. package/dist_ts/classes.artifactstore.js +344 -0
  9. package/dist_ts/classes.egressproxy.d.ts +67 -0
  10. package/dist_ts/classes.egressproxy.js +830 -0
  11. package/dist_ts/classes.flexprovider.d.ts +9 -0
  12. package/dist_ts/classes.flexprovider.js +117 -0
  13. package/dist_ts/classes.framed.d.ts +52 -0
  14. package/dist_ts/classes.framed.js +557 -0
  15. package/dist_ts/classes.runtime.d.ts +202 -0
  16. package/dist_ts/classes.runtime.js +1667 -0
  17. package/dist_ts/confinement.d.ts +2 -0
  18. package/dist_ts/confinement.js +63 -0
  19. package/dist_ts/errors.d.ts +7 -0
  20. package/dist_ts/errors.js +40 -0
  21. package/dist_ts/index.d.ts +11 -0
  22. package/dist_ts/index.js +9 -0
  23. package/dist_ts/interfaces.d.ts +287 -0
  24. package/dist_ts/interfaces.js +2 -0
  25. package/dist_ts/internal.testing.d.ts +9 -0
  26. package/dist_ts/internal.testing.js +2 -0
  27. package/dist_ts/mcp.d.ts +4 -0
  28. package/dist_ts/mcp.js +196 -0
  29. package/dist_ts/plugins.d.ts +20 -0
  30. package/dist_ts/plugins.js +24 -0
  31. package/dist_ts/utils.d.ts +25 -0
  32. package/dist_ts/utils.js +143 -0
  33. package/license.md +21 -0
  34. package/package.json +59 -0
  35. package/readme.hints.md +35 -0
  36. package/readme.md +181 -0
  37. package/ts/00_commitinfo_data.ts +8 -0
  38. package/ts/actions.ts +241 -0
  39. package/ts/classes.artifactstore.ts +432 -0
  40. package/ts/classes.egressproxy.ts +1005 -0
  41. package/ts/classes.flexprovider.ts +134 -0
  42. package/ts/classes.framed.ts +649 -0
  43. package/ts/classes.runtime.ts +2135 -0
  44. package/ts/confinement.ts +90 -0
  45. package/ts/errors.ts +63 -0
  46. package/ts/index.ts +52 -0
  47. package/ts/interfaces.ts +375 -0
  48. package/ts/internal.testing.ts +18 -0
  49. package/ts/mcp.ts +230 -0
  50. package/ts/plugins.ts +28 -0
  51. package/ts/utils.ts +188 -0
package/readme.md ADDED
@@ -0,0 +1,181 @@
1
+ # @modelprofile.com/browser-runtime
2
+
3
+ Parent-owned, project-scoped Chromium runtime with authenticated human and agent control, fail-closed egress, bounded artifacts, and Flex/MCP adapters.
4
+
5
+ ## Issue Reporting and Security
6
+
7
+ For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ pnpm add @modelprofile.com/browser-runtime
13
+ ```
14
+
15
+ The runtime requires Node.js 24 or newer and a non-root Linux host. Production browser sessions require a sandbox-capable Chromium installation.
16
+
17
+ ## Runtime
18
+
19
+ `BrowserRuntime` owns its lock, profile root, artifact root, project slots, Chromium sessions, egress proxies, capabilities, leases, and cleanup work. The host must make the authorization decision; there is no permissive default.
20
+
21
+ ```typescript
22
+ import { BrowserRuntime } from '@modelprofile.com/browser-runtime';
23
+
24
+ const runtime = new BrowserRuntime({
25
+ runtimeDirectory: '/var/lib/example/browser-runtime',
26
+ authorizeCapability: async ({ projectId, actorId, role, peerId, source }) => {
27
+ return hostPolicy.authorizeBrowser({ projectId, actorId, role, peerId, source });
28
+ },
29
+ });
30
+
31
+ await runtime.start();
32
+
33
+ const issued = await runtime.issueCapability({
34
+ projectId: 'project-123',
35
+ actorId: 'agent-456',
36
+ role: 'agent',
37
+ peerId: 'worker-789',
38
+ source: 'mcp',
39
+ });
40
+
41
+ // Deliver capabilityToken once to the exact authenticated peer.
42
+ const lease = await runtime.acquireLease({
43
+ capabilityToken: issued.capabilityToken,
44
+ peerId: 'worker-789',
45
+ expectedRole: 'agent',
46
+ expectedSource: 'mcp',
47
+ });
48
+
49
+ try {
50
+ const observation = await lease.executeAgentAction({ action: 'snapshot' });
51
+ const screenshot = await lease.executeAgentAction({ action: 'screenshot' });
52
+ console.log(observation, screenshot);
53
+ } finally {
54
+ await lease.release();
55
+ await runtime.stop();
56
+ }
57
+ ```
58
+
59
+ Agent actions are limited to `navigate`, `snapshot`, `screenshot`, `click`, `fill`, and `press`. Screenshot results contain artifact metadata, never image bytes. Human leases additionally expose viewport, raw input, direct event/frame subscription, frame acknowledgement, and artifact read/delete methods. JavaScript evaluation is not part of either lease surface.
60
+
61
+ The first session for a project starts a private authenticated loopback proxy and runs the mandatory confinement probe before the session becomes available. The production probe verifies loopback denial, synthetic DNS traversal, and WebRTC candidate suppression. Browser/session and host-fact injection are not part of the published API.
62
+
63
+ ## Trusted Pipe
64
+
65
+ The parent attaches a trusted inherited pipe and supplies the peer, Flex scope, and Flex session identities out of band. Incoming frames cannot select a project, actor, role, source, scope, session, or peer. The capability used by the pipe must have been issued with the same immutable bindings.
66
+
67
+ ```typescript
68
+ import {
69
+ BrowserRuntimeFramedClient,
70
+ type BrowserRuntime,
71
+ } from '@modelprofile.com/browser-runtime';
72
+ import type { Readable, Writable } from 'node:stream';
73
+
74
+ declare const runtime: BrowserRuntime;
75
+ declare const childReadable: Readable;
76
+ declare const childWritable: Writable;
77
+
78
+ const flexCapability = await runtime.issueCapability({
79
+ projectId: 'project-123',
80
+ actorId: 'agent-456',
81
+ role: 'agent',
82
+ peerId: 'worker-789',
83
+ source: 'flex',
84
+ scopeId: 'project-123',
85
+ sessionId: 'flex-session-1',
86
+ });
87
+
88
+ runtime.attachTrustedFramedPeer({
89
+ peerId: 'worker-789',
90
+ scopeId: 'project-123',
91
+ sessionId: 'flex-session-1',
92
+ readable: childReadable,
93
+ writable: childWritable,
94
+ });
95
+
96
+ const childClient = new BrowserRuntimeFramedClient({
97
+ scopeId: 'project-123',
98
+ sessionId: 'flex-session-1',
99
+ readable: process.stdin,
100
+ writable: process.stdout,
101
+ });
102
+
103
+ await childClient.acquire(flexCapability.capabilityToken);
104
+ const result = await childClient.executeAgentAction({
105
+ action: 'navigate',
106
+ url: 'https://example.com/',
107
+ });
108
+ console.log(result);
109
+ await childClient.close();
110
+ ```
111
+
112
+ The framing protocol is versioned length-prefixed JSON with a 256 KiB hard frame ceiling, at most 16 pending requests, and a maximum 60-second request timeout. It supports only acquire, execute, cancel, and release.
113
+
114
+ ## Flex Provider
115
+
116
+ `BrowserRuntimeFlexToolProvider<TScope>` implements `IFlexToolProvider<TScope>` for one supervised Flex child run. Its resolver returns only the capability token; the framed server validates that token against the parent-bound peer, source, scope, and session. The provider exposes SmartAgent browser tools with only the six allowed agent actions and maps side-effect permissions to `browser.<action>` Flex permissions. Closing or aborting the run releases the lease and closes the inherited-pipe client.
117
+
118
+ ## MCP Handler
119
+
120
+ `createBrowserRuntimeMcpHttpHandler()` creates a stateless SmartMCP POST handler. Independent client proof is mandatory and the bearer capability must match the authenticated peer with role `agent` and source `mcp`.
121
+
122
+ ```typescript
123
+ import { createBrowserRuntimeMcpHttpHandler } from '@modelprofile.com/browser-runtime';
124
+
125
+ const mcp = createBrowserRuntimeMcpHttpHandler(runtime, {
126
+ authenticateMcpRequest: async (request) => {
127
+ const peerId = await verifyClientCertificateOrSignedRequest(request);
128
+ return { peerId };
129
+ },
130
+ allowedHosts: ['browser.example.com'],
131
+ trustedOrigins: ['https://browser.example.com'],
132
+ });
133
+
134
+ export const fetch = (request: Request): Promise<Response> => mcp.handleRequest(request);
135
+ ```
136
+
137
+ The MCP tool list is exactly `browser_navigate`, `browser_snapshot`, `browser_screenshot`, `browser_click`, `browser_fill`, and `browser_press`. It does not expose state-only helpers, tab lifecycle, history, evaluation, frame streaming, artifact reads, raw input, or image bytes.
138
+
139
+ ## Egress And Artifacts
140
+
141
+ `BrowserEgressProxy` is exported for testing and advanced composition. It uses Basic proxy authentication, one strict DNS/IP validation path for HTTP, WebSocket Upgrade, and CONNECT, public-global-only targets, numeric selected-IP dialing, default ports `80` and `443`, and bounded DNS, request, response, connection, and tunnel lifetimes. WebSocket tunneling starts only after a bounded valid `101` response.
142
+
143
+ `BrowserArtifactStore` exclusively creates and owns its root, writes generated artifact IDs below keyed project-digest directories, and removes the root on `stop()`. Directories use mode `0700`; files use mode `0600`. Serialized admission enforces per-file, per-project, project-count, aggregate-count, aggregate-byte, and TTL limits before writes. Reads use no-follow file handles and verify size plus SHA-256 integrity before returning trusted-host bytes. Agent, Flex, and MCP surfaces receive metadata only.
144
+
145
+ ## Verification
146
+
147
+ ```sh
148
+ pnpm install
149
+ pnpm run build
150
+ pnpm run check:test
151
+ pnpm test
152
+ ```
153
+
154
+ The real Chromium release check is intentionally separate:
155
+
156
+ ```sh
157
+ pnpm run test:real-chrome
158
+ ```
159
+
160
+ That command sets `BROWSER_RUNTIME_REAL_CHROME=1` and runs the production `LiveBrowserSession`, sandbox requirement, mandatory confinement probe, screenshot artifact path, confirmed shutdown, and profile deletion check.
161
+
162
+ ## License and Legal Information
163
+
164
+ This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.
165
+
166
+ **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
167
+
168
+ ### Trademarks
169
+
170
+ This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
171
+
172
+ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
173
+
174
+ ### Company Information
175
+
176
+ Task Venture Capital GmbH<br>
177
+ Registered at District Court Bremen HRB 35230 HB, Germany
178
+
179
+ For any legal inquiries or further information, please contact us via email at hello@task.vc.
180
+
181
+ By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
@@ -0,0 +1,8 @@
1
+ /**
2
+ * autocreated commitinfo by @push.rocks/commitinfo
3
+ */
4
+ export const commitinfo = {
5
+ name: '@modelprofile.com/browser-runtime',
6
+ version: '1.0.0',
7
+ description: 'Parent-owned, project-scoped Chromium runtime with authenticated human and agent control, fail-closed egress, bounded artifacts, and Flex/MCP adapters.'
8
+ }
package/ts/actions.ts ADDED
@@ -0,0 +1,241 @@
1
+ import type {
2
+ IBrowserClickAction,
3
+ IBrowserFillAction,
4
+ IBrowserNavigateAction,
5
+ IBrowserPressAction,
6
+ IBrowserScreenshotAction,
7
+ IBrowserSnapshotAction,
8
+ IBrowserRuntimeState,
9
+ TBrowserAgentAction,
10
+ TBrowserAgentActionResult,
11
+ } from './interfaces.js';
12
+ import { BrowserRuntimeError } from './errors.js';
13
+ import {
14
+ validateBoundedString,
15
+ validateExactKeys,
16
+ validateInteger,
17
+ } from './utils.js';
18
+
19
+ const optionalTabId = (value: unknown): string | undefined => value === undefined
20
+ ? undefined
21
+ : validateBoundedString(value, 'tabId', 1, 128);
22
+
23
+ const optionalTimeout = (value: unknown): number | undefined => value === undefined
24
+ ? undefined
25
+ : validateInteger(value, 'timeoutMs', 100, 120_000);
26
+
27
+ export const validateAgentAction = (value: unknown): TBrowserAgentAction => {
28
+ const base = validateExactKeys(value, [
29
+ 'action',
30
+ 'url',
31
+ 'selector',
32
+ 'text',
33
+ 'key',
34
+ 'tabId',
35
+ 'timeoutMs',
36
+ 'maxCharacters',
37
+ 'format',
38
+ 'quality',
39
+ ], 'agent action');
40
+ const action = base.action;
41
+ if (typeof action !== 'string') throw new BrowserRuntimeError('INVALID_INPUT');
42
+ switch (action) {
43
+ case 'navigate': {
44
+ validateExactKeys(value, ['action', 'url', 'tabId', 'timeoutMs'], 'navigate action');
45
+ const result: IBrowserNavigateAction = {
46
+ action,
47
+ url: validateBoundedString(base.url, 'url', 1, 8192),
48
+ };
49
+ const tabId = optionalTabId(base.tabId);
50
+ const timeoutMs = optionalTimeout(base.timeoutMs);
51
+ if (tabId !== undefined) result.tabId = tabId;
52
+ if (timeoutMs !== undefined) result.timeoutMs = timeoutMs;
53
+ return result;
54
+ }
55
+ case 'snapshot': {
56
+ validateExactKeys(value, ['action', 'tabId', 'maxCharacters'], 'snapshot action');
57
+ const result: IBrowserSnapshotAction = { action };
58
+ const tabId = optionalTabId(base.tabId);
59
+ const maxCharacters = base.maxCharacters === undefined
60
+ ? undefined
61
+ : validateInteger(base.maxCharacters, 'maxCharacters', 256, 50_000);
62
+ if (tabId !== undefined) result.tabId = tabId;
63
+ if (maxCharacters !== undefined) result.maxCharacters = maxCharacters;
64
+ return result;
65
+ }
66
+ case 'screenshot': {
67
+ validateExactKeys(value, ['action', 'tabId', 'format', 'quality'], 'screenshot action');
68
+ const result: IBrowserScreenshotAction = { action };
69
+ const tabId = optionalTabId(base.tabId);
70
+ if (tabId !== undefined) result.tabId = tabId;
71
+ if (base.format !== undefined) {
72
+ if (base.format !== 'jpeg' && base.format !== 'png') {
73
+ throw new BrowserRuntimeError('INVALID_INPUT');
74
+ }
75
+ result.format = base.format;
76
+ }
77
+ if (base.quality !== undefined) {
78
+ result.quality = validateInteger(base.quality, 'quality', 0, 100);
79
+ }
80
+ if (result.format === 'png' && result.quality !== undefined) {
81
+ throw new BrowserRuntimeError('INVALID_INPUT');
82
+ }
83
+ return result;
84
+ }
85
+ case 'click': {
86
+ validateExactKeys(value, ['action', 'selector', 'tabId', 'timeoutMs'], 'click action');
87
+ const result: IBrowserClickAction = {
88
+ action,
89
+ selector: validateBoundedString(base.selector, 'selector', 1, 4096),
90
+ };
91
+ const tabId = optionalTabId(base.tabId);
92
+ const timeoutMs = optionalTimeout(base.timeoutMs);
93
+ if (tabId !== undefined) result.tabId = tabId;
94
+ if (timeoutMs !== undefined) result.timeoutMs = timeoutMs;
95
+ return result;
96
+ }
97
+ case 'fill': {
98
+ validateExactKeys(value, ['action', 'selector', 'text', 'tabId', 'timeoutMs'], 'fill action');
99
+ const result: IBrowserFillAction = {
100
+ action,
101
+ selector: validateBoundedString(base.selector, 'selector', 1, 4096),
102
+ text: typeof base.text === 'string' && base.text.length <= 100_000
103
+ ? base.text
104
+ : (() => { throw new BrowserRuntimeError('INVALID_INPUT'); })(),
105
+ };
106
+ const tabId = optionalTabId(base.tabId);
107
+ const timeoutMs = optionalTimeout(base.timeoutMs);
108
+ if (tabId !== undefined) result.tabId = tabId;
109
+ if (timeoutMs !== undefined) result.timeoutMs = timeoutMs;
110
+ return result;
111
+ }
112
+ case 'press': {
113
+ validateExactKeys(value, ['action', 'selector', 'key', 'tabId', 'timeoutMs'], 'press action');
114
+ const result: IBrowserPressAction = {
115
+ action,
116
+ selector: validateBoundedString(base.selector, 'selector', 1, 4096),
117
+ key: validateBoundedString(base.key, 'key', 1, 64),
118
+ };
119
+ const tabId = optionalTabId(base.tabId);
120
+ const timeoutMs = optionalTimeout(base.timeoutMs);
121
+ if (tabId !== undefined) result.tabId = tabId;
122
+ if (timeoutMs !== undefined) result.timeoutMs = timeoutMs;
123
+ return result;
124
+ }
125
+ default:
126
+ throw new BrowserRuntimeError('INVALID_INPUT');
127
+ }
128
+ };
129
+
130
+ const validateRuntimeState = (value: unknown): IBrowserRuntimeState => {
131
+ const state = validateExactKeys(
132
+ value,
133
+ ['status', 'activeTabId', 'viewportRevision', 'viewport', 'tabs', 'lastError'],
134
+ 'browser state',
135
+ );
136
+ if (
137
+ state.status !== 'stopped'
138
+ && state.status !== 'starting'
139
+ && state.status !== 'running'
140
+ && state.status !== 'stopping'
141
+ ) throw new BrowserRuntimeError('PROTOCOL_ERROR');
142
+ if (state.activeTabId !== null) validateBoundedString(state.activeTabId, 'activeTabId', 1, 128);
143
+ validateInteger(state.viewportRevision, 'viewportRevision', 0, Number.MAX_SAFE_INTEGER);
144
+ const viewport = validateExactKeys(
145
+ state.viewport,
146
+ ['width', 'height', 'deviceScaleFactor'],
147
+ 'viewport',
148
+ );
149
+ validateInteger(viewport.width, 'viewport.width', 1, 16_384);
150
+ validateInteger(viewport.height, 'viewport.height', 1, 16_384);
151
+ if (
152
+ typeof viewport.deviceScaleFactor !== 'number'
153
+ || !Number.isFinite(viewport.deviceScaleFactor)
154
+ || viewport.deviceScaleFactor <= 0
155
+ || viewport.deviceScaleFactor > 8
156
+ ) throw new BrowserRuntimeError('PROTOCOL_ERROR');
157
+ if (!Array.isArray(state.tabs) || state.tabs.length > 256) {
158
+ throw new BrowserRuntimeError('PROTOCOL_ERROR');
159
+ }
160
+ for (const tabValue of state.tabs) {
161
+ const tab = validateExactKeys(tabValue, [
162
+ 'id',
163
+ 'url',
164
+ 'title',
165
+ 'active',
166
+ 'status',
167
+ 'generation',
168
+ 'appliedViewportRevision',
169
+ 'streaming',
170
+ ], 'tab');
171
+ validateBoundedString(tab.id, 'tab.id', 1, 128);
172
+ if (typeof tab.url !== 'string' || tab.url.length > 8192) throw new BrowserRuntimeError('PROTOCOL_ERROR');
173
+ if (typeof tab.title !== 'string' || tab.title.length > 2048) throw new BrowserRuntimeError('PROTOCOL_ERROR');
174
+ if (typeof tab.active !== 'boolean' || typeof tab.streaming !== 'boolean') {
175
+ throw new BrowserRuntimeError('PROTOCOL_ERROR');
176
+ }
177
+ if (tab.status !== 'open' && tab.status !== 'crashed') throw new BrowserRuntimeError('PROTOCOL_ERROR');
178
+ validateInteger(tab.generation, 'tab.generation', 0, Number.MAX_SAFE_INTEGER);
179
+ validateInteger(
180
+ tab.appliedViewportRevision,
181
+ 'tab.appliedViewportRevision',
182
+ 0,
183
+ Number.MAX_SAFE_INTEGER,
184
+ );
185
+ }
186
+ if (state.lastError !== undefined) {
187
+ const lastError = validateExactKeys(state.lastError, ['code', 'fatal', 'tabId'], 'lastError');
188
+ validateBoundedString(lastError.code, 'lastError.code', 1, 128);
189
+ if (typeof lastError.fatal !== 'boolean') throw new BrowserRuntimeError('PROTOCOL_ERROR');
190
+ if (lastError.tabId !== undefined) validateBoundedString(lastError.tabId, 'lastError.tabId', 1, 128);
191
+ }
192
+ return value as IBrowserRuntimeState;
193
+ };
194
+
195
+ export const validateAgentActionResult = (value: unknown): TBrowserAgentActionResult => {
196
+ const result = validateExactKeys(
197
+ value,
198
+ ['action', 'state', 'tabId', 'url', 'title', 'text', 'truncated', 'artifact'],
199
+ 'agent action result',
200
+ );
201
+ if (
202
+ result.action === 'navigate'
203
+ || result.action === 'click'
204
+ || result.action === 'fill'
205
+ || result.action === 'press'
206
+ ) {
207
+ validateExactKeys(value, ['action', 'state'], 'state action result');
208
+ validateRuntimeState(result.state);
209
+ return value as TBrowserAgentActionResult;
210
+ }
211
+ if (result.action === 'snapshot') {
212
+ validateExactKeys(
213
+ value,
214
+ ['action', 'tabId', 'url', 'title', 'text', 'truncated', 'state'],
215
+ 'snapshot result',
216
+ );
217
+ validateBoundedString(result.tabId, 'tabId', 1, 128);
218
+ if (typeof result.url !== 'string' || result.url.length > 8192) throw new BrowserRuntimeError('PROTOCOL_ERROR');
219
+ if (typeof result.title !== 'string' || result.title.length > 2048) throw new BrowserRuntimeError('PROTOCOL_ERROR');
220
+ if (typeof result.text !== 'string' || result.text.length > 50_000) throw new BrowserRuntimeError('PROTOCOL_ERROR');
221
+ if (typeof result.truncated !== 'boolean') throw new BrowserRuntimeError('PROTOCOL_ERROR');
222
+ validateRuntimeState(result.state);
223
+ return value as TBrowserAgentActionResult;
224
+ }
225
+ if (result.action === 'screenshot') {
226
+ validateExactKeys(value, ['action', 'artifact'], 'screenshot result');
227
+ const artifact = validateExactKeys(
228
+ result.artifact,
229
+ ['artifactId', 'projectId', 'mimeType', 'size', 'createdAt', 'expiresAt'],
230
+ 'artifact metadata',
231
+ );
232
+ validateBoundedString(artifact.artifactId, 'artifactId', 1, 128);
233
+ validateBoundedString(artifact.projectId, 'projectId', 1, 128);
234
+ validateBoundedString(artifact.mimeType, 'mimeType', 1, 128);
235
+ validateInteger(artifact.size, 'artifact.size', 0, 64 * 1024 * 1024);
236
+ validateInteger(artifact.createdAt, 'artifact.createdAt', 0, Number.MAX_SAFE_INTEGER);
237
+ validateInteger(artifact.expiresAt, 'artifact.expiresAt', 0, Number.MAX_SAFE_INTEGER);
238
+ return value as TBrowserAgentActionResult;
239
+ }
240
+ throw new BrowserRuntimeError('PROTOCOL_ERROR');
241
+ };