authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-20 12:00:54-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-20 12:00:54-07:00
log336d176027d1079fb98554a9439b6a2412c7e2fe
tree0f7a4f9c2fd79f2e9c571372fa1f7f19ab263eca
parent204984bf8cc4c9b85ef4d100b355c6c0caf57a21
signature Signed by PGP key B5690EEEBB952194

[codex] Structure preview automation errors (#3272)

Co-authored-by: codex <codex@users.noreply.github.com>

3 files changed, 441 insertions(+), 70 deletions(-)

apps/server/src/mcp/PreviewAutomationBroker.test.ts+110-2
......@@ -1,8 +1,10 @@
11import { expect, it } from "@effect/vitest";
22import {
33 EnvironmentId,
4 PreviewAutomationClientDisconnectedError,
5 PreviewAutomationInvalidSelectorError,
6 PreviewAutomationMalformedResponseError,
47 PreviewAutomationNoFocusedOwnerError,
5 PreviewAutomationUnavailableError,
68 ProviderInstanceId,
79 ThreadId,
810 type PreviewAutomationOwner,
......@@ -59,6 +61,95 @@ it.effect("atomically registers a connected owner and correlates its response",
5961 ),
6062);
6163
64it.effect("preserves bounded request and remote selector diagnostics", () => {
65 const locator = "role=button[name='request-secret']";
66 const remoteMessage = "Unexpected token near remote-secret.";
67 const remoteError = {
68 _tag: "PreviewAutomationInvalidSelectorError",
69 message: remoteMessage,
70 detail: { selector: "role=button[name='remote-secret']" },
71 } as const;
72
73 return Effect.scoped(
74 Effect.gen(function* () {
75 const broker = yield* PreviewAutomationBroker.make;
76 const requests = yield* broker.connect(makeOwner({ tabId: "tab-1" }));
77 yield* Stream.runForEach(requests, (request) =>
78 broker.respond({
79 requestId: request.requestId,
80 ok: false,
81 error: remoteError,
82 }),
83 ).pipe(Effect.forkScoped);
84 yield* Effect.yieldNow;
85
86 const error = yield* broker
87 .invoke<void>({
88 scope,
89 operation: "click",
90 input: { locator },
91 timeoutMs: 1_234,
92 })
93 .pipe(Effect.flip);
94
95 expect(error).toBeInstanceOf(PreviewAutomationInvalidSelectorError);
96 expect(error).toMatchObject({
97 operation: "click",
98 environmentId: scope.environmentId,
99 threadId: scope.threadId,
100 providerSessionId: scope.providerSessionId,
101 providerInstanceId: scope.providerInstanceId,
102 clientId: "client-1",
103 requestId: "preview-0",
104 tabId: "tab-1",
105 timeoutMs: 1_234,
106 selectorKind: "locator",
107 selectorLength: locator.length,
108 remoteTag: "PreviewAutomationInvalidSelectorError",
109 remoteMessageLength: remoteMessage.length,
110 remoteDetailKind: "object",
111 });
112 expect(error.message).toBe(
113 `Preview automation click received an invalid locator (${locator.length} characters).`,
114 );
115 expect(error.message).not.toContain("secret");
116 expect(error.cause).toBe(remoteError);
117 expect("selector" in error).toBe(false);
118 expect("remoteMessage" in error).toBe(false);
119 expect("remoteDetail" in error).toBe(false);
120 }),
121 );
122});
123
124it.effect("distinguishes malformed remote failures", () =>
125 Effect.scoped(
126 Effect.gen(function* () {
127 const broker = yield* PreviewAutomationBroker.make;
128 const requests = yield* broker.connect(makeOwner());
129 yield* Stream.runForEach(requests, (request) =>
130 broker.respond({ requestId: request.requestId, ok: false }),
131 ).pipe(Effect.forkScoped);
132 yield* Effect.yieldNow;
133
134 const error = yield* broker
135 .invoke<void>({ scope, operation: "status", input: {}, timeoutMs: 2_000 })
136 .pipe(Effect.flip);
137
138 expect(error).toBeInstanceOf(PreviewAutomationMalformedResponseError);
139 expect(error).toMatchObject({
140 operation: "status",
141 environmentId: scope.environmentId,
142 threadId: scope.threadId,
143 providerSessionId: scope.providerSessionId,
144 providerInstanceId: scope.providerInstanceId,
145 clientId: "client-1",
146 requestId: "preview-0",
147 timeoutMs: 2_000,
148 });
149 }),
150 ),
151);
152
62153it.effect("rejects calls when no focused owner exists", () =>
63154 Effect.gen(function* () {
64155 const broker = yield* PreviewAutomationBroker.make;
......@@ -66,6 +157,13 @@ it.effect("rejects calls when no focused owner exists", () =>
66157 .invoke<void>({ scope, operation: "status", input: {} })
67158 .pipe(Effect.flip);
68159 expect(error).toBeInstanceOf(PreviewAutomationNoFocusedOwnerError);
160 expect(error).toMatchObject({
161 operation: "status",
162 environmentId: scope.environmentId,
163 threadId: scope.threadId,
164 providerSessionId: scope.providerSessionId,
165 providerInstanceId: scope.providerInstanceId,
166 });
69167 }),
70168);
71169
......@@ -162,7 +260,17 @@ it.effect("fails requests assigned to a browser stream when that stream reconnec
162260 const _replacementRequests = yield* broker.connect(makeOwner());
163261
164262 const error = yield* Fiber.join(pending);
165 expect(error).toBeInstanceOf(PreviewAutomationUnavailableError);
263 expect(error).toBeInstanceOf(PreviewAutomationClientDisconnectedError);
264 expect(error).toMatchObject({
265 operation: "status",
266 environmentId: scope.environmentId,
267 threadId: scope.threadId,
268 providerSessionId: scope.providerSessionId,
269 providerInstanceId: scope.providerInstanceId,
270 clientId: "client-1",
271 requestId: "preview-0",
272 timeoutMs: 15_000,
273 });
166274 }),
167275 ),
168276);
apps/server/src/mcp/PreviewAutomationBroker.ts+149-52
......@@ -1,12 +1,16 @@
11import {
2 PreviewAutomationClientDisconnectedError,
23 PreviewAutomationControlInterruptedError,
34 PreviewAutomationExecutionError,
5 PreviewAutomationHostNotConnectedError,
46 PreviewAutomationInvalidSelectorError,
7 PreviewAutomationMalformedResponseError,
58 PreviewAutomationNoFocusedOwnerError,
9 PreviewAutomationRemoteUnavailableError,
10 PreviewAutomationRequestQueueClosedError,
611 PreviewAutomationResultTooLargeError,
712 PreviewAutomationTabNotFoundError,
813 PreviewAutomationTimeoutError,
9 PreviewAutomationUnavailableError,
1014 PreviewAutomationUnsupportedClientError,
1115 type PreviewAutomationError,
1216 type PreviewAutomationOperation,
......@@ -62,6 +66,21 @@ interface ClientConnection {
6266interface PendingRequest {
6367 readonly queue: ClientConnection["queue"];
6468 readonly deferred: Deferred.Deferred<unknown, PreviewAutomationError>;
69 readonly context: PreviewAutomationRequestErrorContext;
70}
71
72interface PreviewAutomationRequestErrorContext {
73 readonly operation: PreviewAutomationOperation;
74 readonly environmentId: McpInvocationContext.McpInvocationScope["environmentId"];
75 readonly threadId: McpInvocationContext.McpInvocationScope["threadId"];
76 readonly providerSessionId: string;
77 readonly providerInstanceId: McpInvocationContext.McpInvocationScope["providerInstanceId"];
78 readonly clientId: string;
79 readonly requestId: string;
80 readonly tabId?: PreviewTabId;
81 readonly timeoutMs: number;
82 readonly selectorKind?: "locator" | "selector";
83 readonly selectorLength?: number;
6584}
6685
6786interface BrokerState {
......@@ -71,48 +90,104 @@ interface BrokerState {
7190 readonly requestSequence: number;
7291}
7392
74const makeResponseError = (
93const selectorDiagnosticsFromInput = (
94 input: unknown,
95): Pick<PreviewAutomationRequestErrorContext, "selectorKind" | "selectorLength"> => {
96 if (typeof input !== "object" || input === null) return {};
97 if ("locator" in input && typeof input.locator === "string") {
98 return { selectorKind: "locator", selectorLength: input.locator.length };
99 }
100 if ("selector" in input && typeof input.selector === "string") {
101 return { selectorKind: "selector", selectorLength: input.selector.length };
102 }
103 return {};
104};
105
106type RemoteDetailKind = "null" | "array" | "object" | "string" | "number" | "boolean";
107
108function remoteDetailKind(detail: unknown): RemoteDetailKind {
109 if (detail === null) return "null";
110 if (Array.isArray(detail)) return "array";
111 switch (typeof detail) {
112 case "string":
113 return "string";
114 case "number":
115 return "number";
116 case "boolean":
117 return "boolean";
118 default:
119 return "object";
120 }
121}
122
123const classifyResponseError = (
124 context: PreviewAutomationRequestErrorContext,
75125 error: NonNullable<PreviewAutomationResponse["error"]>,
76126): PreviewAutomationError => {
127 const remoteDiagnostics = {
128 remoteTag: error._tag,
129 remoteMessageLength: error.message.length,
130 ...(error.detail === undefined ? {} : { remoteDetailKind: remoteDetailKind(error.detail) }),
131 cause: error,
132 };
77133 switch (error._tag) {
78134 case "PreviewAutomationNoFocusedOwnerError":
79 return new PreviewAutomationNoFocusedOwnerError({ message: error.message });
135 return new PreviewAutomationNoFocusedOwnerError({
136 ...context,
137 ...remoteDiagnostics,
138 });
80139 case "PreviewAutomationUnsupportedClientError":
81 return new PreviewAutomationUnsupportedClientError({ message: error.message });
140 return new PreviewAutomationUnsupportedClientError({
141 ...context,
142 ...remoteDiagnostics,
143 });
82144 case "PreviewAutomationTabNotFoundError":
83 return new PreviewAutomationTabNotFoundError({ message: error.message });
145 return new PreviewAutomationTabNotFoundError({
146 ...context,
147 ...remoteDiagnostics,
148 });
84149 case "PreviewAutomationTimeoutError":
85 return new PreviewAutomationTimeoutError({ message: error.message });
150 return new PreviewAutomationTimeoutError({
151 ...context,
152 ...remoteDiagnostics,
153 });
86154 case "PreviewAutomationControlInterruptedError":
87 return new PreviewAutomationControlInterruptedError({ message: error.message });
155 return new PreviewAutomationControlInterruptedError({
156 ...context,
157 ...remoteDiagnostics,
158 });
88159 case "PreviewAutomationInvalidSelectorError": {
89 const detail =
90 typeof error.detail === "object" && error.detail !== null ? error.detail : undefined;
91160 return new PreviewAutomationInvalidSelectorError({
92 message: error.message,
93 selector:
94 detail && "selector" in detail && typeof detail.selector === "string"
95 ? detail.selector
96 : "",
161 ...context,
162 ...remoteDiagnostics,
97163 });
98164 }
99165 case "PreviewAutomationResultTooLargeError": {
100166 const detail =
101167 typeof error.detail === "object" && error.detail !== null ? error.detail : undefined;
168 const maximumBytes =
169 detail &&
170 "maximumBytes" in detail &&
171 typeof detail.maximumBytes === "number" &&
172 Number.isInteger(detail.maximumBytes) &&
173 detail.maximumBytes > 0
174 ? detail.maximumBytes
175 : undefined;
102176 return new PreviewAutomationResultTooLargeError({
103 message: error.message,
104 maximumBytes:
105 detail && "maximumBytes" in detail && typeof detail.maximumBytes === "number"
106 ? detail.maximumBytes
107 : 64_000,
177 ...context,
178 ...remoteDiagnostics,
179 ...(maximumBytes === undefined ? {} : { maximumBytes }),
108180 });
109181 }
110182 case "PreviewAutomationUnavailableError":
111 return new PreviewAutomationUnavailableError({ message: error.message });
183 return new PreviewAutomationRemoteUnavailableError({
184 ...context,
185 ...remoteDiagnostics,
186 });
112187 default:
113188 return new PreviewAutomationExecutionError({
114 message: error.message,
115 detail: error.detail,
189 ...context,
190 ...remoteDiagnostics,
116191 });
117192 }
118193};
......@@ -148,13 +223,8 @@ export const make = Effect.gen(function* PreviewAutomationBrokerMake() {
148223 });
149224 yield* Effect.forEach(
150225 toFail,
151 ({ deferred }) =>
152 Deferred.fail(
153 deferred,
154 new PreviewAutomationUnavailableError({
155 message: "The preview automation client disconnected.",
156 }),
157 ),
226 ({ deferred, context }) =>
227 Deferred.fail(deferred, new PreviewAutomationClientDisconnectedError(context)),
158228 { discard: true },
159229 );
160230 yield* Queue.shutdown(queue);
......@@ -228,10 +298,8 @@ export const make = Effect.gen(function* PreviewAutomationBrokerMake() {
228298 yield* Deferred.fail(
229299 pending.deferred,
230300 response.error
231 ? makeResponseError(response.error)
232 : new PreviewAutomationExecutionError({
233 message: "Preview automation failed without an error payload.",
234 }),
301 ? classifyResponseError(pending.context, response.error)
302 : new PreviewAutomationMalformedResponseError(pending.context),
235303 );
236304 }
237305 });
......@@ -250,28 +318,60 @@ export const make = Effect.gen(function* PreviewAutomationBrokerMake() {
250318 .sort((left, right) => right.focusedAt.localeCompare(left.focusedAt));
251319 const owner = candidates.find((candidate) => current.clients.has(candidate.clientId));
252320 if (!owner) {
253 if (candidates.length > 0) {
254 return yield* new PreviewAutomationUnavailableError({
255 message: "The browser host is not connected.",
321 const disconnectedOwner = candidates[0];
322 if (disconnectedOwner) {
323 return yield* new PreviewAutomationHostNotConnectedError({
324 operation: input.operation,
325 environmentId: input.scope.environmentId,
326 threadId: input.scope.threadId,
327 providerSessionId: input.scope.providerSessionId,
328 providerInstanceId: input.scope.providerInstanceId,
329 clientId: disconnectedOwner.clientId,
256330 });
257331 }
258332 return yield* new PreviewAutomationNoFocusedOwnerError({
259 message: "No desktop browser host is available for this thread.",
333 operation: input.operation,
334 environmentId: input.scope.environmentId,
335 threadId: input.scope.threadId,
336 providerSessionId: input.scope.providerSessionId,
337 providerInstanceId: input.scope.providerInstanceId,
260338 });
261339 }
262340 const connection = current.clients.get(owner.clientId);
263341 if (!connection) {
264 return yield* new PreviewAutomationUnavailableError({
265 message: "The browser host is not connected.",
342 return yield* new PreviewAutomationHostNotConnectedError({
343 operation: input.operation,
344 environmentId: input.scope.environmentId,
345 threadId: input.scope.threadId,
346 providerSessionId: input.scope.providerSessionId,
347 providerInstanceId: input.scope.providerInstanceId,
348 clientId: owner.clientId,
266349 });
267350 }
268351 const timeoutMs = input.timeoutMs ?? 15_000;
269352 const deferred = yield* Deferred.make<unknown, PreviewAutomationError>();
270 const requestId = yield* SynchronizedRef.modify(state, (next) => {
353 const [requestId, requestContext] = yield* SynchronizedRef.modify(state, (next) => {
271354 const requestId = `preview-${next.requestSequence}`;
355 const tabId = input.tabId ?? owner.tabId ?? undefined;
356 const selectorDiagnostics = selectorDiagnosticsFromInput(input.input);
357 const context: PreviewAutomationRequestErrorContext = {
358 operation: input.operation,
359 environmentId: input.scope.environmentId,
360 threadId: input.scope.threadId,
361 providerSessionId: input.scope.providerSessionId,
362 providerInstanceId: input.scope.providerInstanceId,
363 clientId: owner.clientId,
364 requestId,
365 ...(tabId === undefined ? {} : { tabId }),
366 timeoutMs,
367 ...selectorDiagnostics,
368 };
272369 const pending = new Map(next.pending);
273 pending.set(requestId, { queue: connection.queue, deferred });
274 return [requestId, { ...next, pending, requestSequence: next.requestSequence + 1 }] as const;
370 pending.set(requestId, { queue: connection.queue, deferred, context });
371 return [
372 [requestId, context] as const,
373 { ...next, pending, requestSequence: next.requestSequence + 1 },
374 ] as const;
275375 });
276376 const removePending = SynchronizedRef.update(state, (next) => {
277377 if (!next.pending.has(requestId)) return next;
......@@ -283,24 +383,21 @@ export const make = Effect.gen(function* PreviewAutomationBrokerMake() {
283383 const offered = yield* Queue.offer(connection.queue, {
284384 requestId,
285385 threadId: input.scope.threadId,
286 tabId: input.tabId ?? owner.tabId ?? undefined,
386 tabId: requestContext.tabId,
287387 operation: input.operation,
288388 input: input.input,
289389 timeoutMs,
290390 });
291391 if (!offered) {
292 return yield* new PreviewAutomationUnavailableError({
293 message: "The preview automation client is no longer accepting requests.",
294 });
392 const completion = yield* Deferred.poll(deferred);
393 if (Option.isSome(completion)) {
394 return (yield* completion.value) as A;
395 }
396 return yield* new PreviewAutomationRequestQueueClosedError(requestContext);
295397 }
296398 const result = yield* Deferred.await(deferred).pipe(Effect.timeoutOption(timeoutMs));
297399 return yield* Option.match(result, {
298 onNone: () =>
299 Effect.fail(
300 new PreviewAutomationTimeoutError({
301 message: `Preview automation timed out after ${timeoutMs}ms.`,
302 }),
303 ),
400 onNone: () => Effect.fail(new PreviewAutomationTimeoutError(requestContext)),
304401 onSome: (value) => Effect.succeed(value as A),
305402 });
306403 });
packages/contracts/src/previewAutomation.ts+182-16
......@@ -2,6 +2,7 @@ import { Schema } from "effect";
22
33import { EnvironmentId, ThreadId, TrimmedNonEmptyString } from "./baseSchemas.ts";
44import { PreviewTabId } from "./preview.ts";
5import { ProviderInstanceId } from "./providerInstance.ts";
56
67const BoundedUrl = Schema.String.check(Schema.isTrimmed())
78 .check(
......@@ -455,45 +456,205 @@ export class PreviewAutomationUnavailableError extends Schema.TaggedErrorClass<P
455456 { message: Schema.String },
456457) {}
457458
459const PreviewAutomationScopeErrorFields = {
460 operation: PreviewAutomationOperation,
461 environmentId: EnvironmentId,
462 threadId: ThreadId,
463 providerSessionId: TrimmedNonEmptyString,
464 providerInstanceId: ProviderInstanceId,
465};
466
467const PreviewAutomationRequestErrorFields = {
468 ...PreviewAutomationScopeErrorFields,
469 clientId: TrimmedNonEmptyString,
470 requestId: TrimmedNonEmptyString,
471 tabId: Schema.optional(PreviewTabId),
472 timeoutMs: Schema.Int.check(Schema.isGreaterThan(0)),
473};
474
475const PreviewAutomationRemoteDiagnosticFields = {
476 remoteTag: TrimmedNonEmptyString,
477 remoteMessageLength: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
478 remoteDetailKind: Schema.optional(
479 Schema.Literals(["null", "array", "object", "string", "number", "boolean"]),
480 ),
481 cause: Schema.Defect(),
482};
483
484const PreviewAutomationOptionalRemoteDiagnosticFields = {
485 remoteTag: Schema.optional(TrimmedNonEmptyString),
486 remoteMessageLength: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
487 remoteDetailKind: Schema.optional(
488 Schema.Literals(["null", "array", "object", "string", "number", "boolean"]),
489 ),
490 cause: Schema.optional(Schema.Defect()),
491};
492
458493export class PreviewAutomationNoFocusedOwnerError extends Schema.TaggedErrorClass<PreviewAutomationNoFocusedOwnerError>()(
459494 "PreviewAutomationNoFocusedOwnerError",
460 { message: Schema.String },
461) {}
495 {
496 ...PreviewAutomationScopeErrorFields,
497 clientId: Schema.optional(TrimmedNonEmptyString),
498 requestId: Schema.optional(TrimmedNonEmptyString),
499 tabId: Schema.optional(PreviewTabId),
500 timeoutMs: Schema.optional(Schema.Int.check(Schema.isGreaterThan(0))),
501 ...PreviewAutomationOptionalRemoteDiagnosticFields,
502 },
503) {
504 override get message(): string {
505 const summary = `No focused preview automation owner is available for ${this.operation} in thread ${this.threadId}.`;
506 return summary;
507 }
508}
462509
463510export class PreviewAutomationUnsupportedClientError extends Schema.TaggedErrorClass<PreviewAutomationUnsupportedClientError>()(
464511 "PreviewAutomationUnsupportedClientError",
465 { message: Schema.String },
466) {}
512 {
513 ...PreviewAutomationRequestErrorFields,
514 ...PreviewAutomationRemoteDiagnosticFields,
515 },
516) {
517 override get message(): string {
518 return `Preview automation client ${this.clientId} does not support ${this.operation}.`;
519 }
520}
467521
468522export class PreviewAutomationTabNotFoundError extends Schema.TaggedErrorClass<PreviewAutomationTabNotFoundError>()(
469523 "PreviewAutomationTabNotFoundError",
470 { message: Schema.String },
471) {}
524 {
525 ...PreviewAutomationRequestErrorFields,
526 ...PreviewAutomationRemoteDiagnosticFields,
527 },
528) {
529 override get message(): string {
530 const summary = this.tabId
531 ? `Preview tab ${this.tabId} was not found for ${this.operation}.`
532 : `No active preview tab was found for ${this.operation}.`;
533 return summary;
534 }
535}
472536
473537export class PreviewAutomationTimeoutError extends Schema.TaggedErrorClass<PreviewAutomationTimeoutError>()(
474538 "PreviewAutomationTimeoutError",
475 { message: Schema.String },
476) {}
539 {
540 ...PreviewAutomationRequestErrorFields,
541 ...PreviewAutomationOptionalRemoteDiagnosticFields,
542 },
543) {
544 override get message(): string {
545 const summary = `Preview automation ${this.operation} timed out after ${this.timeoutMs}ms.`;
546 return summary;
547 }
548}
477549
478550export class PreviewAutomationControlInterruptedError extends Schema.TaggedErrorClass<PreviewAutomationControlInterruptedError>()(
479551 "PreviewAutomationControlInterruptedError",
480 { message: Schema.String },
481) {}
552 {
553 ...PreviewAutomationRequestErrorFields,
554 ...PreviewAutomationRemoteDiagnosticFields,
555 },
556) {
557 override get message(): string {
558 return `Preview automation ${this.operation} was interrupted on client ${this.clientId}.`;
559 }
560}
482561
483562export class PreviewAutomationExecutionError extends Schema.TaggedErrorClass<PreviewAutomationExecutionError>()(
484563 "PreviewAutomationExecutionError",
485 { message: Schema.String, detail: Schema.optional(Schema.Unknown) },
486) {}
564 {
565 ...PreviewAutomationRequestErrorFields,
566 ...PreviewAutomationRemoteDiagnosticFields,
567 },
568) {
569 override get message(): string {
570 return `Preview automation ${this.operation} failed on client ${this.clientId}.`;
571 }
572}
487573
488574export class PreviewAutomationInvalidSelectorError extends Schema.TaggedErrorClass<PreviewAutomationInvalidSelectorError>()(
489575 "PreviewAutomationInvalidSelectorError",
490 { message: Schema.String, selector: Schema.String },
491) {}
576 {
577 ...PreviewAutomationRequestErrorFields,
578 ...PreviewAutomationRemoteDiagnosticFields,
579 selectorKind: Schema.optional(Schema.Literals(["locator", "selector"])),
580 selectorLength: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
581 },
582) {
583 override get message(): string {
584 if (this.selectorKind !== undefined && this.selectorLength !== undefined) {
585 return `Preview automation ${this.operation} received an invalid ${this.selectorKind} (${this.selectorLength} characters).`;
586 }
587 return `Preview automation ${this.operation} received an invalid selector.`;
588 }
589}
492590
493591export class PreviewAutomationResultTooLargeError extends Schema.TaggedErrorClass<PreviewAutomationResultTooLargeError>()(
494592 "PreviewAutomationResultTooLargeError",
495 { message: Schema.String, maximumBytes: Schema.Int },
496) {}
593 {
594 ...PreviewAutomationRequestErrorFields,
595 ...PreviewAutomationRemoteDiagnosticFields,
596 maximumBytes: Schema.optional(Schema.Int.check(Schema.isGreaterThan(0))),
597 },
598) {
599 override get message(): string {
600 const summary =
601 this.maximumBytes === undefined
602 ? `Preview automation ${this.operation} produced a result that is too large.`
603 : `Preview automation ${this.operation} produced a result larger than ${this.maximumBytes} bytes.`;
604 return summary;
605 }
606}
607
608export class PreviewAutomationHostNotConnectedError extends Schema.TaggedErrorClass<PreviewAutomationHostNotConnectedError>()(
609 "PreviewAutomationHostNotConnectedError",
610 {
611 ...PreviewAutomationScopeErrorFields,
612 clientId: TrimmedNonEmptyString,
613 },
614) {
615 override get message(): string {
616 return `Preview automation host ${this.clientId} is not connected for ${this.operation}.`;
617 }
618}
619
620export class PreviewAutomationClientDisconnectedError extends Schema.TaggedErrorClass<PreviewAutomationClientDisconnectedError>()(
621 "PreviewAutomationClientDisconnectedError",
622 PreviewAutomationRequestErrorFields,
623) {
624 override get message(): string {
625 return `Preview automation client ${this.clientId} disconnected during ${this.operation}.`;
626 }
627}
628
629export class PreviewAutomationRequestQueueClosedError extends Schema.TaggedErrorClass<PreviewAutomationRequestQueueClosedError>()(
630 "PreviewAutomationRequestQueueClosedError",
631 PreviewAutomationRequestErrorFields,
632) {
633 override get message(): string {
634 return `Preview automation client ${this.clientId} stopped accepting ${this.operation} requests.`;
635 }
636}
637
638export class PreviewAutomationRemoteUnavailableError extends Schema.TaggedErrorClass<PreviewAutomationRemoteUnavailableError>()(
639 "PreviewAutomationRemoteUnavailableError",
640 {
641 ...PreviewAutomationRequestErrorFields,
642 ...PreviewAutomationRemoteDiagnosticFields,
643 },
644) {
645 override get message(): string {
646 return `Preview automation ${this.operation} is unavailable on client ${this.clientId}.`;
647 }
648}
649
650export class PreviewAutomationMalformedResponseError extends Schema.TaggedErrorClass<PreviewAutomationMalformedResponseError>()(
651 "PreviewAutomationMalformedResponseError",
652 PreviewAutomationRequestErrorFields,
653) {
654 override get message(): string {
655 return `Preview automation client ${this.clientId} returned a malformed response for ${this.operation}.`;
656 }
657}
497658
498659export const PreviewAutomationError = Schema.Union([
499660 PreviewAutomationUnavailableError,
......@@ -505,6 +666,11 @@ export const PreviewAutomationError = Schema.Union([
505666 PreviewAutomationExecutionError,
506667 PreviewAutomationInvalidSelectorError,
507668 PreviewAutomationResultTooLargeError,
669 PreviewAutomationHostNotConnectedError,
670 PreviewAutomationClientDisconnectedError,
671 PreviewAutomationRequestQueueClosedError,
672 PreviewAutomationRemoteUnavailableError,
673 PreviewAutomationMalformedResponseError,
508674]);
509675export type PreviewAutomationError = typeof PreviewAutomationError.Type;
510676