@apollo/client-ai-apps 0.2.3 → 0.3.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.
@@ -2,6 +2,9 @@ import { expect, test, describe, vi } from "vitest";
2
2
  import { ExtendedApolloClient } from "./client";
3
3
  import { ApplicationManifest } from "../types/application-manifest";
4
4
  import { parse } from "graphql";
5
+ import { ApolloLink, HttpLink, InMemoryCache } from "@apollo/client";
6
+ import { ApolloClient } from "..";
7
+ import { ToolCallLink } from "./link/ToolCallLink";
5
8
 
6
9
  describe("Client Basics", () => {
7
10
  test("Should execute tool call when client.query is called", async () => {
@@ -34,7 +37,8 @@ describe("Client Basics", () => {
34
37
  format: "apollo-ai-app-manifest",
35
38
  version: "1",
36
39
  name: "the-store",
37
- description: "An online store selling a variety of high quality products across many different categories.",
40
+ description:
41
+ "An online store selling a variety of high quality products across many different categories.",
38
42
  hash: "f6a24922f6ad6ed8c2aa57baf3b8242ae5f38a09a6df3f2693077732434c4256",
39
43
  operations: [
40
44
  {
@@ -44,20 +48,32 @@ describe("Client Basics", () => {
44
48
  body: "query Product($id: ID!) {\n product(id: $id) {\n id\n title\n rating\n price\n description\n images\n __typename\n }\n}",
45
49
  variables: { id: "ID" },
46
50
  prefetch: false,
47
- tools: [{ name: "Get Product", description: "Shows the details page for a specific product." }],
51
+ tools: [
52
+ {
53
+ name: "Get Product",
54
+ description: "Shows the details page for a specific product.",
55
+ },
56
+ ],
48
57
  },
49
58
  ],
50
59
  resource: "index.html",
51
60
  };
52
61
 
53
62
  const client = new ExtendedApolloClient({
63
+ cache: new InMemoryCache(),
54
64
  manifest: manifest as ApplicationManifest,
55
65
  });
56
66
 
57
67
  const variables = { id: "1" };
58
- await client.query({ query: parse(manifest.operations[0].body), variables });
68
+ await client.query({
69
+ query: parse(manifest.operations[0].body),
70
+ variables,
71
+ });
59
72
 
60
- expect(window.openai.callTool).toBeCalledWith("execute", { query: manifest.operations[0].body, variables });
73
+ expect(window.openai.callTool).toBeCalledWith("execute", {
74
+ query: manifest.operations[0].body,
75
+ variables,
76
+ });
61
77
  expect(client.extract()).toMatchInlineSnapshot(`
62
78
  {
63
79
  "Product:1": {
@@ -110,7 +126,8 @@ describe("prefetchData", () => {
110
126
  format: "apollo-ai-app-manifest",
111
127
  version: "1",
112
128
  name: "the-store",
113
- description: "An online store selling a variety of high quality products across many different categories.",
129
+ description:
130
+ "An online store selling a variety of high quality products across many different categories.",
114
131
  hash: "f6a24922f6ad6ed8c2aa57baf3b8242ae5f38a09a6df3f2693077732434c4256",
115
132
  operations: [
116
133
  {
@@ -120,13 +137,19 @@ describe("prefetchData", () => {
120
137
  body: "query Product($id: ID!) {\n product(id: $id) {\n id\n title\n rating\n price\n description\n images\n __typename\n }\n}",
121
138
  variables: { id: "ID" },
122
139
  prefetch: false,
123
- tools: [{ name: "Get Product", description: "Shows the details page for a specific product." }],
140
+ tools: [
141
+ {
142
+ name: "Get Product",
143
+ description: "Shows the details page for a specific product.",
144
+ },
145
+ ],
124
146
  },
125
147
  ],
126
148
  resource: "index.html",
127
149
  };
128
150
 
129
151
  const client = new ExtendedApolloClient({
152
+ cache: new InMemoryCache(),
130
153
  manifest: manifest as ApplicationManifest,
131
154
  });
132
155
  await client.prefetchData();
@@ -184,7 +207,8 @@ describe("prefetchData", () => {
184
207
  format: "apollo-ai-app-manifest",
185
208
  version: "1",
186
209
  name: "the-store",
187
- description: "An online store selling a variety of high quality products across many different categories.",
210
+ description:
211
+ "An online store selling a variety of high quality products across many different categories.",
188
212
  hash: "f6a24922f6ad6ed8c2aa57baf3b8242ae5f38a09a6df3f2693077732434c4256",
189
213
  operations: [
190
214
  {
@@ -195,13 +219,19 @@ describe("prefetchData", () => {
195
219
  variables: {},
196
220
  prefetch: true,
197
221
  prefetchID: "__anonymous",
198
- tools: [{ name: "Top Products", description: "Shows the currently highest rated products." }],
222
+ tools: [
223
+ {
224
+ name: "Top Products",
225
+ description: "Shows the currently highest rated products.",
226
+ },
227
+ ],
199
228
  },
200
229
  ],
201
230
  resource: "index.html",
202
231
  };
203
232
 
204
233
  const client = new ExtendedApolloClient({
234
+ cache: new InMemoryCache(),
205
235
  manifest: manifest as ApplicationManifest,
206
236
  });
207
237
  await client.prefetchData();
@@ -273,7 +303,8 @@ describe("prefetchData", () => {
273
303
  format: "apollo-ai-app-manifest",
274
304
  version: "1",
275
305
  name: "the-store",
276
- description: "An online store selling a variety of high quality products across many different categories.",
306
+ description:
307
+ "An online store selling a variety of high quality products across many different categories.",
277
308
  hash: "f6a24922f6ad6ed8c2aa57baf3b8242ae5f38a09a6df3f2693077732434c4256",
278
309
  operations: [
279
310
  {
@@ -283,7 +314,12 @@ describe("prefetchData", () => {
283
314
  body: "query Product($id: ID!) {\n product(id: $id) {\n id\n title\n rating\n price\n description\n images\n __typename\n }\n}",
284
315
  variables: { id: "ID" },
285
316
  prefetch: false,
286
- tools: [{ name: "Get Product", description: "Shows the details page for a specific product." }],
317
+ tools: [
318
+ {
319
+ name: "Get Product",
320
+ description: "Shows the details page for a specific product.",
321
+ },
322
+ ],
287
323
  },
288
324
  {
289
325
  id: "cd0d52159b9003e791de97c6a76efa03d34fe00cee278d1a3f4bfcec5fb3e1e6",
@@ -293,13 +329,19 @@ describe("prefetchData", () => {
293
329
  variables: {},
294
330
  prefetch: true,
295
331
  prefetchID: "__anonymous",
296
- tools: [{ name: "Top Products", description: "Shows the currently highest rated products." }],
332
+ tools: [
333
+ {
334
+ name: "Top Products",
335
+ description: "Shows the currently highest rated products.",
336
+ },
337
+ ],
297
338
  },
298
339
  ],
299
340
  resource: "index.html",
300
341
  };
301
342
 
302
343
  const client = new ExtendedApolloClient({
344
+ cache: new InMemoryCache(),
303
345
  manifest: manifest as ApplicationManifest,
304
346
  });
305
347
  await client.prefetchData();
@@ -367,7 +409,8 @@ describe("prefetchData", () => {
367
409
  format: "apollo-ai-app-manifest",
368
410
  version: "1",
369
411
  name: "the-store",
370
- description: "An online store selling a variety of high quality products across many different categories.",
412
+ description:
413
+ "An online store selling a variety of high quality products across many different categories.",
371
414
  hash: "f6a24922f6ad6ed8c2aa57baf3b8242ae5f38a09a6df3f2693077732434c4256",
372
415
  operations: [
373
416
  {
@@ -377,13 +420,19 @@ describe("prefetchData", () => {
377
420
  body: "query Product($id: ID!) {\n product(id: $id) {\n id\n title\n rating\n price\n description\n images\n __typename\n }\n}",
378
421
  variables: { id: "ID" },
379
422
  prefetch: false,
380
- tools: [{ name: "Get Product", description: "Shows the details page for a specific product." }],
423
+ tools: [
424
+ {
425
+ name: "Get Product",
426
+ description: "Shows the details page for a specific product.",
427
+ },
428
+ ],
381
429
  },
382
430
  ],
383
431
  resource: "index.html",
384
432
  };
385
433
 
386
434
  const client = new ExtendedApolloClient({
435
+ cache: new InMemoryCache(),
387
436
  manifest: manifest as ApplicationManifest,
388
437
  });
389
438
  await client.prefetchData();
@@ -409,3 +458,167 @@ describe("prefetchData", () => {
409
458
  `);
410
459
  });
411
460
  });
461
+
462
+ describe("custom links", () => {
463
+ test("allows for custom links provided to the constructor", async () => {
464
+ vi.stubGlobal("openai", {
465
+ toolInput: {},
466
+ toolOutput: {},
467
+ toolResponseMetadata: {
468
+ toolName: "the-store--Get Product",
469
+ },
470
+ callTool: vi.fn(async (name: string, args: Record<string, unknown>) => {
471
+ return {
472
+ structuredContent: {
473
+ data: {
474
+ product: {
475
+ id: "1",
476
+ title: "Pen",
477
+ rating: 5,
478
+ price: 1.0,
479
+ description: "Awesome pen",
480
+ images: [],
481
+ __typename: "Product",
482
+ },
483
+ },
484
+ },
485
+ };
486
+ }),
487
+ });
488
+
489
+ const manifest = createManifest();
490
+ const linkHandler = vi.fn<ApolloLink.RequestHandler>((operation, forward) =>
491
+ forward(operation)
492
+ );
493
+
494
+ const client = new ApolloClient({
495
+ manifest,
496
+ cache: new InMemoryCache(),
497
+ link: ApolloLink.from([new ApolloLink(linkHandler), new ToolCallLink()]),
498
+ });
499
+
500
+ const variables = { id: "1" };
501
+ const query = parse(manifest.operations[0].body);
502
+
503
+ await expect(client.query({ query, variables })).resolves.toStrictEqual({
504
+ data: {
505
+ product: {
506
+ id: "1",
507
+ title: "Pen",
508
+ rating: 5,
509
+ price: 1.0,
510
+ description: "Awesome pen",
511
+ images: [],
512
+ __typename: "Product",
513
+ },
514
+ },
515
+ });
516
+
517
+ expect(linkHandler).toHaveBeenCalledOnce();
518
+ expect(linkHandler).toHaveBeenCalledWith(
519
+ expect.objectContaining({ query, variables, operationType: "query" }),
520
+ expect.any(Function)
521
+ );
522
+ });
523
+
524
+ test("enforces ToolCallLink as terminating link", async () => {
525
+ const manifest = createManifest();
526
+ const expectedError = new Error(
527
+ "The terminating link must be a `ToolCallLink`. If you are using a `split` link, ensure the `right` branch uses a `ToolCallLink` as the terminating link."
528
+ );
529
+
530
+ expect(() => {
531
+ new ApolloClient({
532
+ manifest,
533
+ cache: new InMemoryCache(),
534
+ link: new HttpLink(),
535
+ });
536
+ }).toThrow(expectedError);
537
+
538
+ expect(() => {
539
+ new ApolloClient({
540
+ manifest,
541
+ cache: new InMemoryCache(),
542
+ link: new ApolloLink(),
543
+ });
544
+ }).toThrow(expectedError);
545
+
546
+ expect(() => {
547
+ new ApolloClient({
548
+ manifest,
549
+ cache: new InMemoryCache(),
550
+ link: ApolloLink.from([new ApolloLink(), new HttpLink()]),
551
+ });
552
+ }).toThrow(expectedError);
553
+
554
+ expect(() => {
555
+ new ApolloClient({
556
+ manifest,
557
+ cache: new InMemoryCache(),
558
+ link: ApolloLink.split(() => true, new ApolloLink(), new HttpLink()),
559
+ });
560
+ }).toThrow(expectedError);
561
+
562
+ expect(() => {
563
+ new ApolloClient({
564
+ manifest,
565
+ cache: new InMemoryCache(),
566
+ link: ApolloLink.split(() => true, new ToolCallLink(), new HttpLink()),
567
+ });
568
+ }).toThrow(expectedError);
569
+
570
+ // Allow you to use a custom terminating link for `split` links if the
571
+ // custom link is the `left` branch link.
572
+ expect(() => {
573
+ new ApolloClient({
574
+ manifest,
575
+ cache: new InMemoryCache(),
576
+ link: ApolloLink.split(() => true, new HttpLink(), new ToolCallLink()),
577
+ });
578
+ }).not.toThrow(expectedError);
579
+
580
+ expect(() => {
581
+ new ApolloClient({
582
+ manifest,
583
+ cache: new InMemoryCache(),
584
+ link: ApolloLink.split(
585
+ () => true,
586
+ new ToolCallLink(),
587
+ new ToolCallLink()
588
+ ),
589
+ });
590
+ }).not.toThrow();
591
+ });
592
+ });
593
+
594
+ function createManifest(
595
+ overrides?: Partial<ApplicationManifest>
596
+ ): ApplicationManifest {
597
+ return {
598
+ format: "apollo-ai-app-manifest",
599
+ version: "1",
600
+ name: "the-store",
601
+ description:
602
+ "An online store selling a variety of high quality products across many different categories.",
603
+ hash: "f6a24922f6ad6ed8c2aa57baf3b8242ae5f38a09a6df3f2693077732434c4256",
604
+ operations: [
605
+ {
606
+ id: "c43af26552874026c3fb346148c5795896aa2f3a872410a0a2621cffee25291c",
607
+ name: "Product",
608
+ type: "query",
609
+ body: "query Product($id: ID!) {\n product(id: $id) {\n id\n title\n rating\n price\n description\n images\n __typename\n }\n}",
610
+ variables: { id: "ID" },
611
+ prefetch: false,
612
+ tools: [
613
+ {
614
+ name: "Get Product",
615
+ description: "Shows the details page for a specific product.",
616
+ },
617
+ ],
618
+ },
619
+ ],
620
+ resource: "index.html",
621
+ csp: { resourceDomains: [], connectDomains: [] },
622
+ ...overrides,
623
+ };
624
+ }
@@ -1,12 +1,12 @@
1
- import { ApolloClient, ApolloLink, InMemoryCache } from "@apollo/client";
2
- import * as Observable from "rxjs";
3
- import { selectHttpOptionsAndBody } from "@apollo/client/link/http";
4
- import { fallbackHttpConfig } from "@apollo/client/link/http";
1
+ import type { ApolloLink } from "@apollo/client";
2
+ import { ApolloClient } from "@apollo/client";
5
3
  import { DocumentTransform } from "@apollo/client";
6
4
  import { removeDirectivesFromDocument } from "@apollo/client/utilities/internal";
7
5
  import { parse } from "graphql";
6
+ import { __DEV__ } from "@apollo/client/utilities/environment";
8
7
  import "../types/openai";
9
8
  import { ApplicationManifest } from "../types/application-manifest";
9
+ import { ToolCallLink } from "./link/ToolCallLink";
10
10
 
11
11
  // TODO: In the future if/when we support PQs again, do pqLink.concat(toolCallLink)
12
12
  // Commenting this out for now.
@@ -16,27 +16,9 @@ import { ApplicationManifest } from "../types/application-manifest";
16
16
  // sha256: (queryString) => sha256(queryString),
17
17
  // });
18
18
 
19
- // Normally, ApolloClient uses an HttpLink and sends the graphql request over HTTP
20
- // In our case, we're are sending the graphql request over the "execute" tool call
21
- const toolCallLink = new ApolloLink((operation) => {
22
- const context = operation.getContext();
23
- const contextConfig = {
24
- http: context.http,
25
- options: context.fetchOptions,
26
- credentials: context.credentials,
27
- headers: context.headers,
28
- };
29
- const { query, variables } = selectHttpOptionsAndBody(operation, fallbackHttpConfig, contextConfig).body;
30
-
31
- return Observable.from(window.openai.callTool("execute", { query, variables })).pipe(
32
- Observable.map((result) => ({ data: result.structuredContent.data }))
33
- );
34
- });
35
-
36
- // This allows us to extend the options with the "manifest" option AND make link/cache optional (they are normally required)
37
- type ExtendedApolloClientOptions = Omit<ApolloClient.Options, "link" | "cache"> & {
19
+ // This allows us to extend the options with the "manifest" option AND make link optional (it is normally required)
20
+ type ExtendedApolloClientOptions = Omit<ApolloClient.Options, "link"> & {
38
21
  link?: ApolloClient.Options["link"];
39
- cache?: ApolloClient.Options["cache"];
40
22
  manifest: ApplicationManifest;
41
23
  };
42
24
 
@@ -44,12 +26,21 @@ export class ExtendedApolloClient extends ApolloClient {
44
26
  manifest: ApplicationManifest;
45
27
 
46
28
  constructor(options: ExtendedApolloClientOptions) {
29
+ const link = options.link ?? new ToolCallLink();
30
+
31
+ if (__DEV__) {
32
+ validateTerminatingLink(link);
33
+ }
34
+
47
35
  super({
48
- link: toolCallLink,
49
- cache: options.cache ?? new InMemoryCache(),
36
+ ...options,
37
+ link,
50
38
  // Strip out the prefetch/tool directives so they don't get sent with the operation to the server
51
39
  documentTransform: new DocumentTransform((document) => {
52
- return removeDirectivesFromDocument([{ name: "prefetch" }, { name: "tool" }], document)!;
40
+ return removeDirectivesFromDocument(
41
+ [{ name: "prefetch" }, { name: "tool" }],
42
+ document
43
+ )!;
53
44
  }),
54
45
  });
55
46
 
@@ -59,7 +50,11 @@ export class ExtendedApolloClient extends ApolloClient {
59
50
  async prefetchData() {
60
51
  // Write prefetched data to the cache
61
52
  this.manifest.operations.forEach((operation) => {
62
- if (operation.prefetch && operation.prefetchID && window.openai.toolOutput.prefetch?.[operation.prefetchID]) {
53
+ if (
54
+ operation.prefetch &&
55
+ operation.prefetchID &&
56
+ window.openai.toolOutput.prefetch?.[operation.prefetchID]
57
+ ) {
63
58
  this.writeQuery({
64
59
  query: parse(operation.body),
65
60
  data: window.openai.toolOutput.prefetch[operation.prefetchID].data,
@@ -69,13 +64,18 @@ export class ExtendedApolloClient extends ApolloClient {
69
64
  // If this operation has the tool that matches up with the tool that was executed, write the tool result to the cache
70
65
  if (
71
66
  operation.tools?.find(
72
- (tool) => `${this.manifest.name}--${tool.name}` === window.openai.toolResponseMetadata.toolName
67
+ (tool) =>
68
+ `${this.manifest.name}--${tool.name}` ===
69
+ window.openai.toolResponseMetadata.toolName
73
70
  )
74
71
  ) {
75
72
  // We need to include the variables that were used as part of the tool call so that we get a proper cache entry
76
73
  // However, we only want to include toolInput's that were graphql operation (ignore extraInputs)
77
74
  const variables = Object.keys(window.openai.toolInput).reduce(
78
- (obj, key) => (operation.variables[key] ? { ...obj, [key]: window.openai.toolInput[key] } : obj),
75
+ (obj, key) =>
76
+ operation.variables[key] ?
77
+ { ...obj, [key]: window.openai.toolInput[key] }
78
+ : obj,
79
79
  {}
80
80
  );
81
81
 
@@ -88,3 +88,17 @@ export class ExtendedApolloClient extends ApolloClient {
88
88
  });
89
89
  }
90
90
  }
91
+
92
+ function validateTerminatingLink(link: ApolloLink) {
93
+ let terminatingLink = link;
94
+
95
+ while (terminatingLink.right) {
96
+ terminatingLink = terminatingLink.right;
97
+ }
98
+
99
+ if (terminatingLink.constructor.name !== "ToolCallLink") {
100
+ throw new Error(
101
+ "The terminating link must be a `ToolCallLink`. If you are using a `split` link, ensure the `right` branch uses a `ToolCallLink` as the terminating link."
102
+ );
103
+ }
104
+ }
@@ -0,0 +1,49 @@
1
+ import { ApolloLink, Observable } from "@apollo/client";
2
+ import { from, map } from "rxjs";
3
+ import {
4
+ fallbackHttpConfig,
5
+ selectHttpOptionsAndBody,
6
+ } from "@apollo/client/link/http";
7
+
8
+ /**
9
+ * A terminating link that sends a GraphQL request through an agent tool call.
10
+ * When providing a custom link chain to `ApolloClient`, `ApolloClient` will
11
+ * validate that the terminating link is an instance of this link.
12
+ *
13
+ * @example Provding a custom link chain
14
+ *
15
+ * ```ts
16
+ * import { ApolloLink } from "@apollo/client";
17
+ * import { ApolloClient, ToolCallLink } from "@apollo/client-ai-apps";
18
+ *
19
+ * const link = ApolloLink.from([
20
+ * ...otherLinks,
21
+ * new ToolCallLink()
22
+ * ]);
23
+ *
24
+ * const client = new ApolloClient({
25
+ * link,
26
+ * // ...
27
+ * });
28
+ * ```
29
+ */
30
+ export class ToolCallLink extends ApolloLink {
31
+ request(operation: ApolloLink.Operation): Observable<ApolloLink.Result> {
32
+ const context = operation.getContext();
33
+ const contextConfig = {
34
+ http: context.http,
35
+ options: context.fetchOptions,
36
+ credentials: context.credentials,
37
+ headers: context.headers,
38
+ };
39
+ const { query, variables } = selectHttpOptionsAndBody(
40
+ operation,
41
+ fallbackHttpConfig,
42
+ contextConfig
43
+ ).body;
44
+
45
+ return from(window.openai.callTool("execute", { query, variables })).pipe(
46
+ map((result) => ({ data: result.structuredContent.data }))
47
+ );
48
+ }
49
+ }
@@ -26,7 +26,13 @@ export const ExtendedApolloProvider = ({
26
26
  if (window.openai?.toolOutput) {
27
27
  window.dispatchEvent(new CustomEvent(SET_GLOBALS_EVENT_TYPE));
28
28
  }
29
- }, [setHasPreloaded]);
30
29
 
31
- return hasPreloaded ? <ApolloProvider client={client}>{children}</ApolloProvider> : null;
30
+ return () => {
31
+ window.removeEventListener(SET_GLOBALS_EVENT_TYPE, prefetchData);
32
+ };
33
+ }, []);
34
+
35
+ return hasPreloaded ?
36
+ <ApolloProvider client={client}>{children}</ApolloProvider>
37
+ : null;
32
38
  };
@@ -1,8 +1,13 @@
1
- type UseCallToolResult = <K>(toolId: string, variables?: Record<string, unknown> | undefined) => Promise<K>;
1
+ type UseCallToolResult = <K>(
2
+ toolId: string,
3
+ variables?: Record<string, unknown> | undefined
4
+ ) => Promise<K>;
2
5
 
3
6
  export const useCallTool = (): UseCallToolResult => {
4
- const callTool = async (toolId: string, variables: Record<string, unknown> | undefined = {}) =>
5
- await window.openai?.callTool(toolId, variables);
7
+ const callTool = async (
8
+ toolId: string,
9
+ variables: Record<string, unknown> | undefined = {}
10
+ ) => await window.openai?.callTool(toolId, variables);
6
11
 
7
12
  return callTool;
8
13
  };
@@ -17,7 +17,9 @@ test("Should update value when globals are updated and event it triggered", asyn
17
17
  });
18
18
  window.dispatchEvent(
19
19
  new CustomEvent(SET_GLOBALS_EVENT_TYPE, {
20
- detail: { globals: { toolResponseMetadata: { toolName: "my-other-tool" } } },
20
+ detail: {
21
+ globals: { toolResponseMetadata: { toolName: "my-other-tool" } },
22
+ },
21
23
  })
22
24
  );
23
25
  });
@@ -1,7 +1,13 @@
1
1
  import { useSyncExternalStore } from "react";
2
- import { SET_GLOBALS_EVENT_TYPE, SetGlobalsEvent, OpenAiGlobals } from "../types/openai";
2
+ import {
3
+ SET_GLOBALS_EVENT_TYPE,
4
+ SetGlobalsEvent,
5
+ OpenAiGlobals,
6
+ } from "../types/openai";
3
7
 
4
- export function useOpenAiGlobal<K extends keyof OpenAiGlobals>(key: K): OpenAiGlobals[K] {
8
+ export function useOpenAiGlobal<K extends keyof OpenAiGlobals>(
9
+ key: K
10
+ ): OpenAiGlobals[K] {
5
11
  return useSyncExternalStore(
6
12
  (onChange) => {
7
13
  const handleSetGlobal = (event: SetGlobalsEvent) => {
@@ -9,5 +9,7 @@ test("Should set display mode when returned function is called", async () => {
9
9
  const sendFollowUpMessage = useSendFollowUpMessage();
10
10
  await sendFollowUpMessage("Do a cool thing!");
11
11
 
12
- expect(window.openai.sendFollowUpMessage).toBeCalledWith({ prompt: "Do a cool thing!" });
12
+ expect(window.openai.sendFollowUpMessage).toBeCalledWith({
13
+ prompt: "Do a cool thing!",
14
+ });
13
15
  });
@@ -7,9 +7,13 @@ test("Should trigger effect when tool name matches toolResponseMetadata", async
7
7
  toolResponseMetadata: { toolName: "my-app--my-tool" },
8
8
  });
9
9
  const navigate = vi.fn();
10
- const wrapper = ({ children }: { children: any }) => <ToolUseProvider appName="my-app">{children}</ToolUseProvider>;
10
+ const wrapper = ({ children }: { children: any }) => (
11
+ <ToolUseProvider appName="my-app">{children}</ToolUseProvider>
12
+ );
11
13
 
12
- renderHook(() => useToolEffect("my-tool", () => navigate(), [navigate]), { wrapper });
14
+ renderHook(() => useToolEffect("my-tool", () => navigate(), [navigate]), {
15
+ wrapper,
16
+ });
13
17
 
14
18
  expect(navigate).toBeCalled();
15
19
  });
@@ -19,9 +23,17 @@ test("Should trigger effect when one of multiple tool name matches toolResponseM
19
23
  toolResponseMetadata: { toolName: "my-app--my-tool" },
20
24
  });
21
25
  const navigate = vi.fn();
22
- const wrapper = ({ children }: { children: any }) => <ToolUseProvider appName="my-app">{children}</ToolUseProvider>;
26
+ const wrapper = ({ children }: { children: any }) => (
27
+ <ToolUseProvider appName="my-app">{children}</ToolUseProvider>
28
+ );
23
29
 
24
- renderHook(() => useToolEffect(["my-tool", "my-similar-tool"], () => navigate(), [navigate]), { wrapper });
30
+ renderHook(
31
+ () =>
32
+ useToolEffect(["my-tool", "my-similar-tool"], () => navigate(), [
33
+ navigate,
34
+ ]),
35
+ { wrapper }
36
+ );
25
37
 
26
38
  expect(navigate).toBeCalled();
27
39
  });
@@ -31,9 +43,13 @@ test("Should not trigger effect when tool name does not match toolResponseMetada
31
43
  toolResponseMetadata: { toolName: "my-app--my-other-tool" },
32
44
  });
33
45
  const navigate = vi.fn();
34
- const wrapper = ({ children }: { children: any }) => <ToolUseProvider appName="my-app">{children}</ToolUseProvider>;
46
+ const wrapper = ({ children }: { children: any }) => (
47
+ <ToolUseProvider appName="my-app">{children}</ToolUseProvider>
48
+ );
35
49
 
36
- renderHook(() => useToolEffect("my-tool", () => navigate(), [navigate]), { wrapper });
50
+ renderHook(() => useToolEffect("my-tool", () => navigate(), [navigate]), {
51
+ wrapper,
52
+ });
37
53
 
38
54
  expect(navigate).not.toBeCalled();
39
55
  });
@@ -44,7 +60,7 @@ test("Should throw an error when used outside of a ToolUseProvider", async () =>
44
60
  });
45
61
  const navigate = vi.fn();
46
62
 
47
- expect(() => renderHook(() => useToolEffect("my-tool", () => navigate(), [navigate]))).toThrowError(
48
- "useToolEffect must be used within ToolUseProvider"
49
- );
63
+ expect(() =>
64
+ renderHook(() => useToolEffect("my-tool", () => navigate(), [navigate]))
65
+ ).toThrowError("useToolEffect must be used within ToolUseProvider");
50
66
  });