@apollo/client-ai-apps 0.2.4 → 0.3.1

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 (72) hide show
  1. package/.git-blame-ignore-revs +2 -0
  2. package/.github/workflows/compare-build-output.yml +28 -0
  3. package/.github/workflows/pr.yaml +23 -15
  4. package/.github/workflows/release.yaml +46 -46
  5. package/.prettierrc +9 -0
  6. package/config/compare-build-output-to.sh +90 -0
  7. package/dist/core/ApolloClient.d.ts +14 -0
  8. package/dist/index.d.ts +17 -10
  9. package/dist/index.js +164 -62
  10. package/dist/link/ToolCallLink.d.ts +26 -0
  11. package/dist/react/ApolloProvider.d.ts +9 -0
  12. package/dist/react/context/ToolUseContext.d.ts +15 -0
  13. package/dist/{hooks → react/hooks}/useOpenAiGlobal.d.ts +1 -1
  14. package/dist/react/hooks/useOpenExternal.d.ts +3 -0
  15. package/dist/{hooks → react/hooks}/useRequestDisplayMode.d.ts +1 -1
  16. package/dist/{hooks → react/hooks}/useToolEffect.d.ts +0 -4
  17. package/dist/react/hooks/useToolOutput.d.ts +1 -0
  18. package/dist/react/hooks/useToolResponseMetadata.d.ts +1 -0
  19. package/dist/react/hooks/useWidgetState.d.ts +4 -0
  20. package/dist/types/openai.d.ts +1 -2
  21. package/dist/vite/index.js +74 -21
  22. package/package.json +9 -2
  23. package/scripts/dev.mjs +3 -1
  24. package/src/core/ApolloClient.ts +108 -0
  25. package/src/{apollo_client/client.test.ts → core/__tests__/ApolloClient.test.ts} +232 -20
  26. package/src/index.ts +36 -10
  27. package/src/link/ToolCallLink.ts +49 -0
  28. package/src/{apollo_client/provider.tsx → react/ApolloProvider.tsx} +19 -9
  29. package/src/{apollo_client/provider.test.tsx → react/__tests__/ApolloProvider.test.tsx} +9 -9
  30. package/src/react/context/ToolUseContext.tsx +30 -0
  31. package/src/{hooks → react/hooks/__tests__}/useCallTool.test.ts +1 -1
  32. package/src/{hooks → react/hooks/__tests__}/useOpenAiGlobal.test.ts +5 -3
  33. package/src/react/hooks/__tests__/useOpenExternal.test.tsx +24 -0
  34. package/src/{hooks → react/hooks/__tests__}/useRequestDisplayMode.test.ts +2 -2
  35. package/src/{hooks → react/hooks/__tests__}/useSendFollowUpMessage.test.ts +4 -2
  36. package/src/{hooks → react/hooks/__tests__}/useToolEffect.test.tsx +27 -10
  37. package/src/{hooks → react/hooks/__tests__}/useToolInput.test.ts +1 -1
  38. package/src/{hooks → react/hooks/__tests__}/useToolName.test.ts +1 -1
  39. package/src/react/hooks/__tests__/useToolOutput.test.tsx +49 -0
  40. package/src/react/hooks/__tests__/useToolResponseMetadata.test.tsx +49 -0
  41. package/src/react/hooks/__tests__/useWidgetState.test.tsx +158 -0
  42. package/src/react/hooks/useCallTool.ts +13 -0
  43. package/src/{hooks → react/hooks}/useOpenAiGlobal.ts +11 -5
  44. package/src/react/hooks/useOpenExternal.ts +11 -0
  45. package/src/{hooks → react/hooks}/useRequestDisplayMode.ts +1 -1
  46. package/src/react/hooks/useToolEffect.tsx +37 -0
  47. package/src/{hooks → react/hooks}/useToolName.ts +1 -1
  48. package/src/react/hooks/useToolOutput.ts +5 -0
  49. package/src/react/hooks/useToolResponseMetadata.ts +5 -0
  50. package/src/react/hooks/useWidgetState.ts +48 -0
  51. package/src/testing/internal/index.ts +2 -0
  52. package/src/testing/internal/matchers/index.d.ts +9 -0
  53. package/src/testing/internal/matchers/index.ts +1 -0
  54. package/src/testing/internal/matchers/toRerender.ts +49 -0
  55. package/src/testing/internal/openai/dispatchStateChange.ts +9 -0
  56. package/src/testing/internal/openai/stubOpenAiGlobals.ts +13 -0
  57. package/src/types/openai.ts +6 -3
  58. package/src/vite/{absolute_asset_imports_plugin.test.ts → __tests__/absolute_asset_imports_plugin.test.ts} +4 -2
  59. package/src/vite/{application_manifest_plugin.test.ts → __tests__/application_manifest_plugin.test.ts} +176 -53
  60. package/src/vite/absolute_asset_imports_plugin.ts +3 -1
  61. package/src/vite/application_manifest_plugin.ts +84 -24
  62. package/vitest-setup.ts +1 -0
  63. package/dist/apollo_client/client.d.ts +0 -14
  64. package/dist/apollo_client/provider.d.ts +0 -5
  65. package/src/apollo_client/client.ts +0 -90
  66. package/src/hooks/useCallTool.ts +0 -8
  67. package/src/hooks/useToolEffect.tsx +0 -41
  68. /package/dist/{hooks → react/hooks}/useSendFollowUpMessage.d.ts +0 -0
  69. /package/dist/{hooks → react/hooks}/useToolInput.d.ts +0 -0
  70. /package/dist/{hooks → react/hooks}/useToolName.d.ts +0 -0
  71. /package/src/{hooks → react/hooks}/useSendFollowUpMessage.ts +0 -0
  72. /package/src/{hooks → react/hooks}/useToolInput.ts +0 -0
@@ -1,7 +1,9 @@
1
1
  import { expect, test, describe, vi } from "vitest";
2
- import { ExtendedApolloClient } from "./client";
3
- import { ApplicationManifest } from "../types/application-manifest";
2
+ import { ApolloClient } from "../ApolloClient";
3
+ import { ApplicationManifest } from "../../types/application-manifest";
4
4
  import { parse } from "graphql";
5
+ import { ApolloLink, HttpLink, InMemoryCache } from "@apollo/client";
6
+ import { ToolCallLink } from "../../link/ToolCallLink";
5
7
 
6
8
  describe("Client Basics", () => {
7
9
  test("Should execute tool call when client.query is called", async () => {
@@ -34,7 +36,8 @@ describe("Client Basics", () => {
34
36
  format: "apollo-ai-app-manifest",
35
37
  version: "1",
36
38
  name: "the-store",
37
- description: "An online store selling a variety of high quality products across many different categories.",
39
+ description:
40
+ "An online store selling a variety of high quality products across many different categories.",
38
41
  hash: "f6a24922f6ad6ed8c2aa57baf3b8242ae5f38a09a6df3f2693077732434c4256",
39
42
  operations: [
40
43
  {
@@ -44,20 +47,32 @@ describe("Client Basics", () => {
44
47
  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
48
  variables: { id: "ID" },
46
49
  prefetch: false,
47
- tools: [{ name: "Get Product", description: "Shows the details page for a specific product." }],
50
+ tools: [
51
+ {
52
+ name: "Get Product",
53
+ description: "Shows the details page for a specific product.",
54
+ },
55
+ ],
48
56
  },
49
57
  ],
50
58
  resource: "index.html",
51
59
  };
52
60
 
53
- const client = new ExtendedApolloClient({
61
+ const client = new ApolloClient({
62
+ cache: new InMemoryCache(),
54
63
  manifest: manifest as ApplicationManifest,
55
64
  });
56
65
 
57
66
  const variables = { id: "1" };
58
- await client.query({ query: parse(manifest.operations[0].body), variables });
67
+ await client.query({
68
+ query: parse(manifest.operations[0].body),
69
+ variables,
70
+ });
59
71
 
60
- expect(window.openai.callTool).toBeCalledWith("execute", { query: manifest.operations[0].body, variables });
72
+ expect(window.openai.callTool).toBeCalledWith("execute", {
73
+ query: manifest.operations[0].body,
74
+ variables,
75
+ });
61
76
  expect(client.extract()).toMatchInlineSnapshot(`
62
77
  {
63
78
  "Product:1": {
@@ -110,7 +125,8 @@ describe("prefetchData", () => {
110
125
  format: "apollo-ai-app-manifest",
111
126
  version: "1",
112
127
  name: "the-store",
113
- description: "An online store selling a variety of high quality products across many different categories.",
128
+ description:
129
+ "An online store selling a variety of high quality products across many different categories.",
114
130
  hash: "f6a24922f6ad6ed8c2aa57baf3b8242ae5f38a09a6df3f2693077732434c4256",
115
131
  operations: [
116
132
  {
@@ -120,13 +136,19 @@ describe("prefetchData", () => {
120
136
  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
137
  variables: { id: "ID" },
122
138
  prefetch: false,
123
- tools: [{ name: "Get Product", description: "Shows the details page for a specific product." }],
139
+ tools: [
140
+ {
141
+ name: "Get Product",
142
+ description: "Shows the details page for a specific product.",
143
+ },
144
+ ],
124
145
  },
125
146
  ],
126
147
  resource: "index.html",
127
148
  };
128
149
 
129
- const client = new ExtendedApolloClient({
150
+ const client = new ApolloClient({
151
+ cache: new InMemoryCache(),
130
152
  manifest: manifest as ApplicationManifest,
131
153
  });
132
154
  await client.prefetchData();
@@ -184,7 +206,8 @@ describe("prefetchData", () => {
184
206
  format: "apollo-ai-app-manifest",
185
207
  version: "1",
186
208
  name: "the-store",
187
- description: "An online store selling a variety of high quality products across many different categories.",
209
+ description:
210
+ "An online store selling a variety of high quality products across many different categories.",
188
211
  hash: "f6a24922f6ad6ed8c2aa57baf3b8242ae5f38a09a6df3f2693077732434c4256",
189
212
  operations: [
190
213
  {
@@ -195,13 +218,19 @@ describe("prefetchData", () => {
195
218
  variables: {},
196
219
  prefetch: true,
197
220
  prefetchID: "__anonymous",
198
- tools: [{ name: "Top Products", description: "Shows the currently highest rated products." }],
221
+ tools: [
222
+ {
223
+ name: "Top Products",
224
+ description: "Shows the currently highest rated products.",
225
+ },
226
+ ],
199
227
  },
200
228
  ],
201
229
  resource: "index.html",
202
230
  };
203
231
 
204
- const client = new ExtendedApolloClient({
232
+ const client = new ApolloClient({
233
+ cache: new InMemoryCache(),
205
234
  manifest: manifest as ApplicationManifest,
206
235
  });
207
236
  await client.prefetchData();
@@ -273,7 +302,8 @@ describe("prefetchData", () => {
273
302
  format: "apollo-ai-app-manifest",
274
303
  version: "1",
275
304
  name: "the-store",
276
- description: "An online store selling a variety of high quality products across many different categories.",
305
+ description:
306
+ "An online store selling a variety of high quality products across many different categories.",
277
307
  hash: "f6a24922f6ad6ed8c2aa57baf3b8242ae5f38a09a6df3f2693077732434c4256",
278
308
  operations: [
279
309
  {
@@ -283,7 +313,12 @@ describe("prefetchData", () => {
283
313
  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
314
  variables: { id: "ID" },
285
315
  prefetch: false,
286
- tools: [{ name: "Get Product", description: "Shows the details page for a specific product." }],
316
+ tools: [
317
+ {
318
+ name: "Get Product",
319
+ description: "Shows the details page for a specific product.",
320
+ },
321
+ ],
287
322
  },
288
323
  {
289
324
  id: "cd0d52159b9003e791de97c6a76efa03d34fe00cee278d1a3f4bfcec5fb3e1e6",
@@ -293,13 +328,19 @@ describe("prefetchData", () => {
293
328
  variables: {},
294
329
  prefetch: true,
295
330
  prefetchID: "__anonymous",
296
- tools: [{ name: "Top Products", description: "Shows the currently highest rated products." }],
331
+ tools: [
332
+ {
333
+ name: "Top Products",
334
+ description: "Shows the currently highest rated products.",
335
+ },
336
+ ],
297
337
  },
298
338
  ],
299
339
  resource: "index.html",
300
340
  };
301
341
 
302
- const client = new ExtendedApolloClient({
342
+ const client = new ApolloClient({
343
+ cache: new InMemoryCache(),
303
344
  manifest: manifest as ApplicationManifest,
304
345
  });
305
346
  await client.prefetchData();
@@ -367,7 +408,8 @@ describe("prefetchData", () => {
367
408
  format: "apollo-ai-app-manifest",
368
409
  version: "1",
369
410
  name: "the-store",
370
- description: "An online store selling a variety of high quality products across many different categories.",
411
+ description:
412
+ "An online store selling a variety of high quality products across many different categories.",
371
413
  hash: "f6a24922f6ad6ed8c2aa57baf3b8242ae5f38a09a6df3f2693077732434c4256",
372
414
  operations: [
373
415
  {
@@ -377,13 +419,19 @@ describe("prefetchData", () => {
377
419
  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
420
  variables: { id: "ID" },
379
421
  prefetch: false,
380
- tools: [{ name: "Get Product", description: "Shows the details page for a specific product." }],
422
+ tools: [
423
+ {
424
+ name: "Get Product",
425
+ description: "Shows the details page for a specific product.",
426
+ },
427
+ ],
381
428
  },
382
429
  ],
383
430
  resource: "index.html",
384
431
  };
385
432
 
386
- const client = new ExtendedApolloClient({
433
+ const client = new ApolloClient({
434
+ cache: new InMemoryCache(),
387
435
  manifest: manifest as ApplicationManifest,
388
436
  });
389
437
  await client.prefetchData();
@@ -409,3 +457,167 @@ describe("prefetchData", () => {
409
457
  `);
410
458
  });
411
459
  });
460
+
461
+ describe("custom links", () => {
462
+ test("allows for custom links provided to the constructor", async () => {
463
+ vi.stubGlobal("openai", {
464
+ toolInput: {},
465
+ toolOutput: {},
466
+ toolResponseMetadata: {
467
+ toolName: "the-store--Get Product",
468
+ },
469
+ callTool: vi.fn(async (name: string, args: Record<string, unknown>) => {
470
+ return {
471
+ structuredContent: {
472
+ data: {
473
+ product: {
474
+ id: "1",
475
+ title: "Pen",
476
+ rating: 5,
477
+ price: 1.0,
478
+ description: "Awesome pen",
479
+ images: [],
480
+ __typename: "Product",
481
+ },
482
+ },
483
+ },
484
+ };
485
+ }),
486
+ });
487
+
488
+ const manifest = createManifest();
489
+ const linkHandler = vi.fn<ApolloLink.RequestHandler>((operation, forward) =>
490
+ forward(operation)
491
+ );
492
+
493
+ const client = new ApolloClient({
494
+ manifest,
495
+ cache: new InMemoryCache(),
496
+ link: ApolloLink.from([new ApolloLink(linkHandler), new ToolCallLink()]),
497
+ });
498
+
499
+ const variables = { id: "1" };
500
+ const query = parse(manifest.operations[0].body);
501
+
502
+ await expect(client.query({ query, variables })).resolves.toStrictEqual({
503
+ data: {
504
+ product: {
505
+ id: "1",
506
+ title: "Pen",
507
+ rating: 5,
508
+ price: 1.0,
509
+ description: "Awesome pen",
510
+ images: [],
511
+ __typename: "Product",
512
+ },
513
+ },
514
+ });
515
+
516
+ expect(linkHandler).toHaveBeenCalledOnce();
517
+ expect(linkHandler).toHaveBeenCalledWith(
518
+ expect.objectContaining({ query, variables, operationType: "query" }),
519
+ expect.any(Function)
520
+ );
521
+ });
522
+
523
+ test("enforces ToolCallLink as terminating link", async () => {
524
+ const manifest = createManifest();
525
+ const expectedError = new Error(
526
+ "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."
527
+ );
528
+
529
+ expect(() => {
530
+ new ApolloClient({
531
+ manifest,
532
+ cache: new InMemoryCache(),
533
+ link: new HttpLink(),
534
+ });
535
+ }).toThrow(expectedError);
536
+
537
+ expect(() => {
538
+ new ApolloClient({
539
+ manifest,
540
+ cache: new InMemoryCache(),
541
+ link: new ApolloLink(),
542
+ });
543
+ }).toThrow(expectedError);
544
+
545
+ expect(() => {
546
+ new ApolloClient({
547
+ manifest,
548
+ cache: new InMemoryCache(),
549
+ link: ApolloLink.from([new ApolloLink(), new HttpLink()]),
550
+ });
551
+ }).toThrow(expectedError);
552
+
553
+ expect(() => {
554
+ new ApolloClient({
555
+ manifest,
556
+ cache: new InMemoryCache(),
557
+ link: ApolloLink.split(() => true, new ApolloLink(), new HttpLink()),
558
+ });
559
+ }).toThrow(expectedError);
560
+
561
+ expect(() => {
562
+ new ApolloClient({
563
+ manifest,
564
+ cache: new InMemoryCache(),
565
+ link: ApolloLink.split(() => true, new ToolCallLink(), new HttpLink()),
566
+ });
567
+ }).toThrow(expectedError);
568
+
569
+ // Allow you to use a custom terminating link for `split` links if the
570
+ // custom link is the `left` branch link.
571
+ expect(() => {
572
+ new ApolloClient({
573
+ manifest,
574
+ cache: new InMemoryCache(),
575
+ link: ApolloLink.split(() => true, new HttpLink(), new ToolCallLink()),
576
+ });
577
+ }).not.toThrow(expectedError);
578
+
579
+ expect(() => {
580
+ new ApolloClient({
581
+ manifest,
582
+ cache: new InMemoryCache(),
583
+ link: ApolloLink.split(
584
+ () => true,
585
+ new ToolCallLink(),
586
+ new ToolCallLink()
587
+ ),
588
+ });
589
+ }).not.toThrow();
590
+ });
591
+ });
592
+
593
+ function createManifest(
594
+ overrides?: Partial<ApplicationManifest>
595
+ ): ApplicationManifest {
596
+ return {
597
+ format: "apollo-ai-app-manifest",
598
+ version: "1",
599
+ name: "the-store",
600
+ description:
601
+ "An online store selling a variety of high quality products across many different categories.",
602
+ hash: "f6a24922f6ad6ed8c2aa57baf3b8242ae5f38a09a6df3f2693077732434c4256",
603
+ operations: [
604
+ {
605
+ id: "c43af26552874026c3fb346148c5795896aa2f3a872410a0a2621cffee25291c",
606
+ name: "Product",
607
+ type: "query",
608
+ 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}",
609
+ variables: { id: "ID" },
610
+ prefetch: false,
611
+ tools: [
612
+ {
613
+ name: "Get Product",
614
+ description: "Shows the details page for a specific product.",
615
+ },
616
+ ],
617
+ },
618
+ ],
619
+ resource: "index.html",
620
+ csp: { resourceDomains: [], connectDomains: [] },
621
+ ...overrides,
622
+ };
623
+ }
package/src/index.ts CHANGED
@@ -1,12 +1,38 @@
1
- export * from "./types/openai";
2
- export * from "./types/application-manifest";
3
- export * from "./hooks/useOpenAiGlobal";
4
- export * from "./hooks/useToolName";
5
- export * from "./hooks/useToolInput";
6
- export * from "./hooks/useSendFollowUpMessage";
7
- export * from "./hooks/useRequestDisplayMode";
8
- export * from "./hooks/useToolEffect";
1
+ export type {
2
+ API,
3
+ CallTool,
4
+ DeviceType,
5
+ DisplayMode,
6
+ OpenAiGlobals,
7
+ SafeArea,
8
+ SafeAreaInsets,
9
+ Theme,
10
+ UserAgent,
11
+ UnknownObject,
12
+ } from "./types/openai";
13
+ export { SET_GLOBALS_EVENT_TYPE, SetGlobalsEvent } from "./types/openai";
14
+
15
+ export type {
16
+ ApplicationManifest,
17
+ ManifestOperation,
18
+ ManifestTool,
19
+ ManifestExtraInput,
20
+ ManifestCsp,
21
+ } from "./types/application-manifest";
22
+
23
+ export { ToolUseProvider } from "./react/context/ToolUseContext";
24
+ export { useOpenAiGlobal } from "./react/hooks/useOpenAiGlobal";
25
+ export { useToolName } from "./react/hooks/useToolName";
26
+ export { useToolInput } from "./react/hooks/useToolInput";
27
+ export { useSendFollowUpMessage } from "./react/hooks/useSendFollowUpMessage";
28
+ export { useRequestDisplayMode } from "./react/hooks/useRequestDisplayMode";
29
+ export { useToolEffect } from "./react/hooks/useToolEffect";
30
+ export { useOpenExternal } from "./react/hooks/useOpenExternal";
31
+ export { useToolOutput } from "./react/hooks/useToolOutput";
32
+ export { useToolResponseMetadata } from "./react/hooks/useToolResponseMetadata";
33
+ export { useWidgetState } from "./react/hooks/useWidgetState";
9
34
 
10
35
  export * from "@apollo/client";
11
- export { ExtendedApolloClient as ApolloClient } from "./apollo_client/client";
12
- export { ExtendedApolloProvider as ApolloProvider } from "./apollo_client/provider";
36
+ export { ApolloClient } from "./core/ApolloClient";
37
+ export { ApolloProvider } from "./react/ApolloProvider";
38
+ export { ToolCallLink } from "./link/ToolCallLink";
@@ -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
+ }
@@ -1,12 +1,16 @@
1
- import React, { useEffect, useState } from "react";
2
- import { ApolloProvider } from "@apollo/client/react";
3
- import { ExtendedApolloClient } from "./client";
1
+ import React, { ReactNode, useEffect, useState } from "react";
2
+ import { ApolloProvider as BaseApolloProvider } from "@apollo/client/react";
3
+ import { ApolloClient } from "../core/ApolloClient";
4
4
  import { SET_GLOBALS_EVENT_TYPE } from "../types/openai";
5
5
 
6
- export const ExtendedApolloProvider = ({
7
- children,
8
- client,
9
- }: React.PropsWithChildren<{ client: ExtendedApolloClient }>) => {
6
+ export declare namespace ApolloProvider {
7
+ export interface Props {
8
+ children?: ReactNode;
9
+ client: ApolloClient;
10
+ }
11
+ }
12
+
13
+ export const ApolloProvider = ({ children, client }: ApolloProvider.Props) => {
10
14
  const [hasPreloaded, setHasPreloaded] = useState(false);
11
15
 
12
16
  // This is to prevent against a race condition. We don't know if window.openai will be available when this loads or if it will become available shortly after.
@@ -26,7 +30,13 @@ export const ExtendedApolloProvider = ({
26
30
  if (window.openai?.toolOutput) {
27
31
  window.dispatchEvent(new CustomEvent(SET_GLOBALS_EVENT_TYPE));
28
32
  }
29
- }, [setHasPreloaded]);
30
33
 
31
- return hasPreloaded ? <ApolloProvider client={client}>{children}</ApolloProvider> : null;
34
+ return () => {
35
+ window.removeEventListener(SET_GLOBALS_EVENT_TYPE, prefetchData);
36
+ };
37
+ }, []);
38
+
39
+ return hasPreloaded ?
40
+ <BaseApolloProvider client={client}>{children}</BaseApolloProvider>
41
+ : null;
32
42
  };
@@ -1,8 +1,8 @@
1
1
  import { expect, test, vi } from "vitest";
2
- import { ExtendedApolloProvider } from "./provider";
2
+ import { ApolloProvider } from "../ApolloProvider";
3
3
  import { render } from "@testing-library/react";
4
- import { ExtendedApolloClient } from "./client";
5
- import { SET_GLOBALS_EVENT_TYPE } from "../types/openai";
4
+ import { ApolloClient } from "../../core/ApolloClient";
5
+ import { SET_GLOBALS_EVENT_TYPE } from "../../types/openai";
6
6
 
7
7
  test("Should call prefetch data when window.open is immediately available", () => {
8
8
  vi.stubGlobal("openai", {
@@ -11,9 +11,9 @@ test("Should call prefetch data when window.open is immediately available", () =
11
11
 
12
12
  const client = {
13
13
  prefetchData: vi.fn(async () => {}),
14
- } as unknown as ExtendedApolloClient;
14
+ } as unknown as ApolloClient;
15
15
 
16
- render(<ExtendedApolloProvider client={client} />);
16
+ render(<ApolloProvider client={client} />);
17
17
 
18
18
  expect(client.prefetchData).toBeCalled();
19
19
  });
@@ -21,9 +21,9 @@ test("Should call prefetch data when window.open is immediately available", () =
21
21
  test("Should NOT call prefetch data when window.open is not immediately available", () => {
22
22
  const client = {
23
23
  prefetchData: vi.fn(async () => {}),
24
- } as unknown as ExtendedApolloClient;
24
+ } as unknown as ApolloClient;
25
25
 
26
- render(<ExtendedApolloProvider client={client} />);
26
+ render(<ApolloProvider client={client} />);
27
27
 
28
28
  expect(client.prefetchData).not.toBeCalled();
29
29
  });
@@ -31,9 +31,9 @@ test("Should NOT call prefetch data when window.open is not immediately availabl
31
31
  test("Should call prefetch data when window.open is not immediately available and event is sent", () => {
32
32
  const client = {
33
33
  prefetchData: vi.fn(async () => {}),
34
- } as unknown as ExtendedApolloClient;
34
+ } as unknown as ApolloClient;
35
35
 
36
- render(<ExtendedApolloProvider client={client} />);
36
+ render(<ApolloProvider client={client} />);
37
37
 
38
38
  window.dispatchEvent(new CustomEvent(SET_GLOBALS_EVENT_TYPE));
39
39
 
@@ -0,0 +1,30 @@
1
+ import React, { createContext, ReactNode, useContext, useState } from "react";
2
+
3
+ interface ToolUseState {
4
+ appName: string;
5
+ hasNavigated: boolean;
6
+ setHasNavigated: (v: boolean) => void;
7
+ }
8
+
9
+ const ToolUseContext = createContext<ToolUseState | null>(null);
10
+
11
+ export declare namespace ToolUseProvider {
12
+ export interface Props {
13
+ children?: ReactNode;
14
+ appName: string;
15
+ }
16
+ }
17
+
18
+ export function ToolUseProvider({ children, appName }: ToolUseProvider.Props) {
19
+ const [hasNavigated, setHasNavigated] = useState(false);
20
+
21
+ return (
22
+ <ToolUseContext.Provider value={{ hasNavigated, setHasNavigated, appName }}>
23
+ {children}
24
+ </ToolUseContext.Provider>
25
+ );
26
+ }
27
+
28
+ export function useToolUseState() {
29
+ return useContext(ToolUseContext);
30
+ }
@@ -1,5 +1,5 @@
1
1
  import { expect, test, vi } from "vitest";
2
- import { useCallTool } from "./useCallTool";
2
+ import { useCallTool } from "../useCallTool";
3
3
 
4
4
  test("Should execute tool when returned function is called", async () => {
5
5
  vi.stubGlobal("openai", {
@@ -1,7 +1,7 @@
1
1
  import { expect, test, vi } from "vitest";
2
- import { useOpenAiGlobal } from "./useOpenAiGlobal";
2
+ import { useOpenAiGlobal } from "../useOpenAiGlobal";
3
3
  import { renderHook, act } from "@testing-library/react";
4
- import { SET_GLOBALS_EVENT_TYPE } from "../types/openai";
4
+ import { SET_GLOBALS_EVENT_TYPE } from "../../../types/openai";
5
5
 
6
6
  test("Should update value when globals are updated and event it triggered", async () => {
7
7
  vi.stubGlobal("openai", {
@@ -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
  });
@@ -0,0 +1,24 @@
1
+ import { expect, test, vi } from "vitest";
2
+ import { renderHookToSnapshotStream } from "@testing-library/react-render-stream";
3
+ import { useOpenExternal } from "../useOpenExternal";
4
+ import { stubOpenAiGlobals } from "../../../testing/internal";
5
+
6
+ test("calls the global openExternal function", async () => {
7
+ const openExternalMock = vi.fn();
8
+
9
+ stubOpenAiGlobals({ openExternal: openExternalMock });
10
+
11
+ const { takeSnapshot } = await renderHookToSnapshotStream(() =>
12
+ useOpenExternal()
13
+ );
14
+
15
+ const openExternal = await takeSnapshot();
16
+ openExternal({ href: "https://example.com" });
17
+
18
+ expect(openExternalMock).toHaveBeenCalledTimes(1);
19
+ expect(openExternalMock).toHaveBeenCalledWith({
20
+ href: "https://example.com",
21
+ });
22
+
23
+ await expect(takeSnapshot).not.toRerender();
24
+ });
@@ -1,6 +1,6 @@
1
1
  import { expect, test, vi } from "vitest";
2
- import { useRequestDisplayMode } from "./useRequestDisplayMode";
3
- import { DisplayMode } from "../types/openai";
2
+ import { useRequestDisplayMode } from "../useRequestDisplayMode";
3
+ import { DisplayMode } from "../../../types/openai";
4
4
 
5
5
  test("Should set display mode when returned function is called", async () => {
6
6
  vi.stubGlobal("openai", {
@@ -1,5 +1,5 @@
1
1
  import { expect, test, vi } from "vitest";
2
- import { useSendFollowUpMessage } from "./useSendFollowUpMessage";
2
+ import { useSendFollowUpMessage } from "../useSendFollowUpMessage";
3
3
 
4
4
  test("Should set display mode when returned function is called", async () => {
5
5
  vi.stubGlobal("openai", {
@@ -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
  });