@giselles-ai/browser-tool 0.1.17 → 0.1.19

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.
@@ -113,6 +113,13 @@ function requiredEnv(name) {
113
113
  function trimTrailingSlash(input) {
114
114
  return input.replace(/\/+$/, "");
115
115
  }
116
+ function safeJsonStringify(value) {
117
+ try {
118
+ return JSON.stringify(value);
119
+ } catch {
120
+ return String(value);
121
+ }
122
+ }
116
123
  var RelayClient = class {
117
124
  url;
118
125
  sessionId;
@@ -194,7 +201,14 @@ var RelayClient = class {
194
201
  }
195
202
  const success = dispatchSuccessSchema.safeParse(body);
196
203
  if (!success.success) {
197
- throw new Error("Relay dispatch returned an unexpected payload.");
204
+ throw new Error(
205
+ [
206
+ "Relay dispatch returned an unexpected payload.",
207
+ `status=${response.status}`,
208
+ `url=${this.url}`,
209
+ `body=${safeJsonStringify(body)}`
210
+ ].join(" ")
211
+ );
198
212
  }
199
213
  if (!response.ok) {
200
214
  throw new Error(`Relay dispatch failed with HTTP ${response.status}.`);
@@ -1,4 +1,5 @@
1
1
  import Redis from 'ioredis';
2
+ import { z } from 'zod';
2
3
 
3
4
  declare function createRelayHandler(): {
4
5
  GET: (request: Request) => Promise<Response>;
@@ -7,6 +8,79 @@ declare function createRelayHandler(): {
7
8
  };
8
9
 
9
10
  type RelayErrorCode = "UNAUTHORIZED" | "NO_BROWSER" | "TIMEOUT" | "INVALID_RESPONSE" | "NOT_FOUND" | "INTERNAL";
11
+ declare const relayRequestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
12
+ type: z.ZodLiteral<"snapshot_request">;
13
+ requestId: z.ZodString;
14
+ instruction: z.ZodString;
15
+ document: z.ZodOptional<z.ZodString>;
16
+ }, z.core.$strip>, z.ZodObject<{
17
+ type: z.ZodLiteral<"execute_request">;
18
+ requestId: z.ZodString;
19
+ actions: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
20
+ action: z.ZodLiteral<"fill">;
21
+ fieldId: z.ZodString;
22
+ value: z.ZodString;
23
+ }, z.core.$strip>, z.ZodObject<{
24
+ action: z.ZodLiteral<"click">;
25
+ fieldId: z.ZodString;
26
+ }, z.core.$strip>, z.ZodObject<{
27
+ action: z.ZodLiteral<"select">;
28
+ fieldId: z.ZodString;
29
+ value: z.ZodString;
30
+ }, z.core.$strip>], "action">>;
31
+ fields: z.ZodArray<z.ZodObject<{
32
+ fieldId: z.ZodString;
33
+ selector: z.ZodString;
34
+ kind: z.ZodEnum<{
35
+ text: "text";
36
+ textarea: "textarea";
37
+ select: "select";
38
+ checkbox: "checkbox";
39
+ radio: "radio";
40
+ }>;
41
+ label: z.ZodString;
42
+ name: z.ZodOptional<z.ZodString>;
43
+ required: z.ZodBoolean;
44
+ placeholder: z.ZodOptional<z.ZodString>;
45
+ currentValue: z.ZodUnion<readonly [z.ZodString, z.ZodBoolean]>;
46
+ options: z.ZodOptional<z.ZodArray<z.ZodString>>;
47
+ }, z.core.$strip>>;
48
+ }, z.core.$strip>], "type">;
49
+ declare const relayResponseSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
50
+ type: z.ZodLiteral<"snapshot_response">;
51
+ requestId: z.ZodString;
52
+ fields: z.ZodArray<z.ZodObject<{
53
+ fieldId: z.ZodString;
54
+ selector: z.ZodString;
55
+ kind: z.ZodEnum<{
56
+ text: "text";
57
+ textarea: "textarea";
58
+ select: "select";
59
+ checkbox: "checkbox";
60
+ radio: "radio";
61
+ }>;
62
+ label: z.ZodString;
63
+ name: z.ZodOptional<z.ZodString>;
64
+ required: z.ZodBoolean;
65
+ placeholder: z.ZodOptional<z.ZodString>;
66
+ currentValue: z.ZodUnion<readonly [z.ZodString, z.ZodBoolean]>;
67
+ options: z.ZodOptional<z.ZodArray<z.ZodString>>;
68
+ }, z.core.$strip>>;
69
+ }, z.core.$strip>, z.ZodObject<{
70
+ type: z.ZodLiteral<"execute_response">;
71
+ requestId: z.ZodString;
72
+ report: z.ZodObject<{
73
+ applied: z.ZodNumber;
74
+ skipped: z.ZodNumber;
75
+ warnings: z.ZodArray<z.ZodString>;
76
+ }, z.core.$strip>;
77
+ }, z.core.$strip>, z.ZodObject<{
78
+ type: z.ZodLiteral<"error_response">;
79
+ requestId: z.ZodString;
80
+ message: z.ZodString;
81
+ }, z.core.$strip>], "type">;
82
+ type RelayRequest = z.infer<typeof relayRequestSchema>;
83
+ type RelayResponse = z.infer<typeof relayResponseSchema>;
10
84
 
11
85
  declare global {
12
86
  var __browserToolRelayRedis: Redis | undefined;
@@ -23,4 +97,18 @@ declare function createRelaySession(): Promise<{
23
97
  }>;
24
98
  declare function toRelayError(error: unknown): RelayStoreError;
25
99
 
26
- export { createRelayHandler, createRelaySession, toRelayError };
100
+ type RelayRequestSubscription = {
101
+ nextRequest(): Promise<RelayRequest>;
102
+ close(): Promise<void>;
103
+ };
104
+ declare function createRelayRequestSubscription(input: {
105
+ sessionId: string;
106
+ token: string;
107
+ }): Promise<RelayRequestSubscription>;
108
+ declare function sendRelayResponse(input: {
109
+ sessionId: string;
110
+ token: string;
111
+ response: RelayResponse;
112
+ }): Promise<void>;
113
+
114
+ export { type RelayRequestSubscription, createRelayHandler, createRelayRequestSubscription, createRelaySession, sendRelayResponse, toRelayError };
@@ -581,6 +581,44 @@ var postBodySchema = z2.discriminatedUnion("type", [
581
581
  dispatchSchema,
582
582
  respondSchema
583
583
  ]);
584
+ function summarizeRelayRequestForLog(request) {
585
+ if (request.type === "snapshot_request") {
586
+ return {
587
+ type: request.type,
588
+ requestId: request.requestId,
589
+ instruction: request.instruction
590
+ };
591
+ }
592
+ return {
593
+ type: request.type,
594
+ requestId: request.requestId,
595
+ actionCount: request.actions.length,
596
+ fieldCount: request.fields.length
597
+ };
598
+ }
599
+ function summarizeRelayResponseForLog(response) {
600
+ if (response.type === "snapshot_response") {
601
+ return {
602
+ type: response.type,
603
+ requestId: response.requestId,
604
+ fieldCount: response.fields.length
605
+ };
606
+ }
607
+ if (response.type === "execute_response") {
608
+ return {
609
+ type: response.type,
610
+ requestId: response.requestId,
611
+ applied: response.report.applied,
612
+ skipped: response.report.skipped,
613
+ warningCount: response.report.warnings.length
614
+ };
615
+ }
616
+ return {
617
+ type: response.type,
618
+ requestId: response.requestId,
619
+ message: response.message
620
+ };
621
+ }
584
622
  function createRelayEventsRoute(request) {
585
623
  const cors = corsHeaders(request);
586
624
  const url = new URL(request.url);
@@ -751,6 +789,10 @@ async function createRelayPostRoute(request) {
751
789
  );
752
790
  }
753
791
  if (parsed.data.type === "relay.dispatch") {
792
+ console.info(`${LOG_PREFIX} post.dispatch`, {
793
+ sessionId: parsed.data.sessionId,
794
+ request: summarizeRelayRequestForLog(parsed.data.request)
795
+ });
754
796
  try {
755
797
  const response = await dispatchRelayRequest({
756
798
  sessionId: parsed.data.sessionId,
@@ -758,24 +800,46 @@ async function createRelayPostRoute(request) {
758
800
  request: parsed.data.request,
759
801
  timeoutMs: parsed.data.timeoutMs
760
802
  });
803
+ console.info(`${LOG_PREFIX} post.dispatch.ok`, {
804
+ sessionId: parsed.data.sessionId,
805
+ response: summarizeRelayResponseForLog(response)
806
+ });
761
807
  return Response.json({ ok: true, response }, { headers: cors });
762
808
  } catch (error) {
763
809
  const relayError = toRelayError(error);
810
+ console.error(`${LOG_PREFIX} post.dispatch.error`, {
811
+ sessionId: parsed.data.sessionId,
812
+ errorCode: relayError.code,
813
+ message: relayError.message
814
+ });
764
815
  return Response.json(
765
816
  { ok: false, errorCode: relayError.code, message: relayError.message },
766
817
  { status: relayError.status, headers: cors }
767
818
  );
768
819
  }
769
820
  }
821
+ console.info(`${LOG_PREFIX} post.respond`, {
822
+ sessionId: parsed.data.sessionId,
823
+ response: summarizeRelayResponseForLog(parsed.data.response)
824
+ });
770
825
  try {
771
826
  await resolveRelayResponse({
772
827
  sessionId: parsed.data.sessionId,
773
828
  token: parsed.data.token,
774
829
  response: parsed.data.response
775
830
  });
831
+ console.info(`${LOG_PREFIX} post.respond.ok`, {
832
+ sessionId: parsed.data.sessionId,
833
+ requestId: parsed.data.response.requestId
834
+ });
776
835
  return Response.json({ ok: true }, { headers: cors });
777
836
  } catch (error) {
778
837
  const relayError = toRelayError(error);
838
+ console.error(`${LOG_PREFIX} post.respond.error`, {
839
+ sessionId: parsed.data.sessionId,
840
+ errorCode: relayError.code,
841
+ message: relayError.message
842
+ });
779
843
  return Response.json(
780
844
  { ok: false, errorCode: relayError.code, message: relayError.message },
781
845
  { status: relayError.status, headers: cors }
@@ -789,8 +853,80 @@ function createRelayHandler() {
789
853
  OPTIONS: (request) => new Response(null, { status: 204, headers: corsHeaders(request) })
790
854
  };
791
855
  }
856
+
857
+ // src/relay/request-subscription.ts
858
+ async function createRelayRequestSubscription(input) {
859
+ await assertRelaySession(input.sessionId, input.token);
860
+ const subscriber = createRelaySubscriber();
861
+ const channel = relayRequestChannel(input.sessionId);
862
+ let keepaliveId = null;
863
+ try {
864
+ await subscriber.subscribe(channel);
865
+ await markBrowserConnected(input.sessionId, input.token);
866
+ keepaliveId = setInterval(() => {
867
+ void touchBrowserConnected(input.sessionId).catch(() => void 0);
868
+ }, RELAY_SSE_KEEPALIVE_INTERVAL_MS);
869
+ } catch (error) {
870
+ await subscriber.unsubscribe(channel).catch(() => void 0);
871
+ await subscriber.quit().catch(() => {
872
+ subscriber.disconnect();
873
+ });
874
+ throw error;
875
+ }
876
+ const nextRequest = () => {
877
+ return new Promise((resolve, reject) => {
878
+ const onMessage = (_channel, message) => {
879
+ if (_channel !== channel) {
880
+ return;
881
+ }
882
+ let parsed = null;
883
+ try {
884
+ const raw = JSON.parse(message);
885
+ const safe = relayRequestSchema.safeParse(raw);
886
+ if (!safe.success) {
887
+ return;
888
+ }
889
+ parsed = safe.data;
890
+ } catch {
891
+ return;
892
+ }
893
+ cleanup();
894
+ resolve(parsed);
895
+ };
896
+ const onError = (error) => {
897
+ cleanup();
898
+ reject(error);
899
+ };
900
+ const cleanup = () => {
901
+ subscriber.off("message", onMessage);
902
+ subscriber.off("error", onError);
903
+ };
904
+ subscriber.on("message", onMessage);
905
+ subscriber.on("error", onError);
906
+ });
907
+ };
908
+ const close = async () => {
909
+ if (keepaliveId) {
910
+ clearInterval(keepaliveId);
911
+ keepaliveId = null;
912
+ }
913
+ await subscriber.unsubscribe(channel).catch(() => void 0);
914
+ await subscriber.quit().catch(() => {
915
+ subscriber.disconnect();
916
+ });
917
+ };
918
+ return {
919
+ nextRequest,
920
+ close
921
+ };
922
+ }
923
+ async function sendRelayResponse(input) {
924
+ await resolveRelayResponse(input);
925
+ }
792
926
  export {
793
927
  createRelayHandler,
928
+ createRelayRequestSubscription,
794
929
  createRelaySession,
930
+ sendRelayResponse,
795
931
  toRelayError
796
932
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@giselles-ai/browser-tool",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "Apache-2.0",