@apollo/client-ai-apps 0.3.0 → 0.3.2

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 (63) hide show
  1. package/.github/workflows/compare-build-output.yml +28 -0
  2. package/config/compare-build-output-to.sh +90 -0
  3. package/dist/core/ApolloClient.d.ts +14 -0
  4. package/dist/index.d.ts +17 -11
  5. package/dist/index.js +96 -44
  6. package/dist/react/ApolloProvider.d.ts +9 -0
  7. package/dist/react/context/ToolUseContext.d.ts +15 -0
  8. package/dist/{hooks → react/hooks}/useOpenAiGlobal.d.ts +1 -1
  9. package/dist/react/hooks/useOpenExternal.d.ts +3 -0
  10. package/dist/{hooks → react/hooks}/useRequestDisplayMode.d.ts +1 -1
  11. package/dist/{hooks → react/hooks}/useToolEffect.d.ts +0 -4
  12. package/dist/react/hooks/useToolOutput.d.ts +1 -0
  13. package/dist/react/hooks/useToolResponseMetadata.d.ts +1 -0
  14. package/dist/react/hooks/useWidgetState.d.ts +4 -0
  15. package/dist/types/openai.d.ts +1 -2
  16. package/dist/vite/index.js +5 -0
  17. package/package.json +5 -1
  18. package/src/{apollo_client/client.ts → core/ApolloClient.ts} +21 -17
  19. package/src/{apollo_client/client.test.ts → core/__tests__/ApolloClient.test.ts} +8 -9
  20. package/src/index.ts +36 -11
  21. package/src/{apollo_client/provider.tsx → react/ApolloProvider.tsx} +12 -8
  22. package/src/{apollo_client/provider.test.tsx → react/__tests__/ApolloProvider.test.tsx} +9 -9
  23. package/src/react/context/ToolUseContext.tsx +30 -0
  24. package/src/{hooks → react/hooks/__tests__}/useCallTool.test.ts +1 -1
  25. package/src/{hooks → react/hooks/__tests__}/useOpenAiGlobal.test.ts +2 -2
  26. package/src/react/hooks/__tests__/useOpenExternal.test.tsx +24 -0
  27. package/src/{hooks → react/hooks/__tests__}/useRequestDisplayMode.test.ts +2 -2
  28. package/src/{hooks → react/hooks/__tests__}/useSendFollowUpMessage.test.ts +1 -1
  29. package/src/{hooks → react/hooks/__tests__}/useToolEffect.test.tsx +2 -1
  30. package/src/{hooks → react/hooks/__tests__}/useToolInput.test.ts +1 -1
  31. package/src/{hooks → react/hooks/__tests__}/useToolName.test.ts +1 -1
  32. package/src/react/hooks/__tests__/useToolOutput.test.tsx +49 -0
  33. package/src/react/hooks/__tests__/useToolResponseMetadata.test.tsx +49 -0
  34. package/src/react/hooks/__tests__/useWidgetState.test.tsx +158 -0
  35. package/src/{hooks → react/hooks}/useOpenAiGlobal.ts +4 -4
  36. package/src/react/hooks/useOpenExternal.ts +11 -0
  37. package/src/{hooks → react/hooks}/useRequestDisplayMode.ts +1 -1
  38. package/src/{hooks → react/hooks}/useToolEffect.tsx +3 -26
  39. package/src/{hooks → react/hooks}/useToolName.ts +1 -1
  40. package/src/react/hooks/useToolOutput.ts +5 -0
  41. package/src/react/hooks/useToolResponseMetadata.ts +5 -0
  42. package/src/react/hooks/useWidgetState.ts +48 -0
  43. package/src/testing/internal/index.ts +2 -0
  44. package/src/testing/internal/matchers/index.d.ts +9 -0
  45. package/src/testing/internal/matchers/index.ts +1 -0
  46. package/src/testing/internal/matchers/toRerender.ts +49 -0
  47. package/src/testing/internal/openai/dispatchStateChange.ts +9 -0
  48. package/src/testing/internal/openai/stubOpenAiGlobals.ts +13 -0
  49. package/src/types/openai.ts +1 -1
  50. package/src/vite/{absolute_asset_imports_plugin.test.ts → __tests__/absolute_asset_imports_plugin.test.ts} +1 -1
  51. package/src/vite/{application_manifest_plugin.test.ts → __tests__/application_manifest_plugin.test.ts} +33 -7
  52. package/src/vite/application_manifest_plugin.ts +6 -0
  53. package/vitest-setup.ts +1 -0
  54. package/dist/apollo_client/client.d.ts +0 -13
  55. package/dist/apollo_client/provider.d.ts +0 -5
  56. /package/dist/{apollo_client/link → link}/ToolCallLink.d.ts +0 -0
  57. /package/dist/{hooks → react/hooks}/useSendFollowUpMessage.d.ts +0 -0
  58. /package/dist/{hooks → react/hooks}/useToolInput.d.ts +0 -0
  59. /package/dist/{hooks → react/hooks}/useToolName.d.ts +0 -0
  60. /package/src/{apollo_client/link → link}/ToolCallLink.ts +0 -0
  61. /package/src/{hooks → react/hooks}/useCallTool.ts +0 -0
  62. /package/src/{hooks → react/hooks}/useSendFollowUpMessage.ts +0 -0
  63. /package/src/{hooks → react/hooks}/useToolInput.ts +0 -0
@@ -1,10 +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
5
  import { ApolloLink, HttpLink, InMemoryCache } from "@apollo/client";
6
- import { ApolloClient } from "..";
7
- import { ToolCallLink } from "./link/ToolCallLink";
6
+ import { ToolCallLink } from "../../link/ToolCallLink";
8
7
 
9
8
  describe("Client Basics", () => {
10
9
  test("Should execute tool call when client.query is called", async () => {
@@ -59,7 +58,7 @@ describe("Client Basics", () => {
59
58
  resource: "index.html",
60
59
  };
61
60
 
62
- const client = new ExtendedApolloClient({
61
+ const client = new ApolloClient({
63
62
  cache: new InMemoryCache(),
64
63
  manifest: manifest as ApplicationManifest,
65
64
  });
@@ -148,7 +147,7 @@ describe("prefetchData", () => {
148
147
  resource: "index.html",
149
148
  };
150
149
 
151
- const client = new ExtendedApolloClient({
150
+ const client = new ApolloClient({
152
151
  cache: new InMemoryCache(),
153
152
  manifest: manifest as ApplicationManifest,
154
153
  });
@@ -230,7 +229,7 @@ describe("prefetchData", () => {
230
229
  resource: "index.html",
231
230
  };
232
231
 
233
- const client = new ExtendedApolloClient({
232
+ const client = new ApolloClient({
234
233
  cache: new InMemoryCache(),
235
234
  manifest: manifest as ApplicationManifest,
236
235
  });
@@ -340,7 +339,7 @@ describe("prefetchData", () => {
340
339
  resource: "index.html",
341
340
  };
342
341
 
343
- const client = new ExtendedApolloClient({
342
+ const client = new ApolloClient({
344
343
  cache: new InMemoryCache(),
345
344
  manifest: manifest as ApplicationManifest,
346
345
  });
@@ -431,7 +430,7 @@ describe("prefetchData", () => {
431
430
  resource: "index.html",
432
431
  };
433
432
 
434
- const client = new ExtendedApolloClient({
433
+ const client = new ApolloClient({
435
434
  cache: new InMemoryCache(),
436
435
  manifest: manifest as ApplicationManifest,
437
436
  });
package/src/index.ts CHANGED
@@ -1,13 +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";
13
- export { ToolCallLink } from "./apollo_client/link/ToolCallLink";
36
+ export { ApolloClient } from "./core/ApolloClient";
37
+ export { ApolloProvider } from "./react/ApolloProvider";
38
+ export { ToolCallLink } from "./link/ToolCallLink";
@@ -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.
@@ -33,6 +37,6 @@ export const ExtendedApolloProvider = ({
33
37
  }, []);
34
38
 
35
39
  return hasPreloaded ?
36
- <ApolloProvider client={client}>{children}</ApolloProvider>
40
+ <BaseApolloProvider client={client}>{children}</BaseApolloProvider>
37
41
  : null;
38
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", {
@@ -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", {
@@ -1,6 +1,7 @@
1
1
  import { expect, test, vi } from "vitest";
2
- import { useToolEffect, ToolUseProvider } from "./useToolEffect";
2
+ import { useToolEffect } from "../useToolEffect";
3
3
  import { renderHook } from "@testing-library/react";
4
+ import { ToolUseProvider } from "../../context/ToolUseContext";
4
5
 
5
6
  test("Should trigger effect when tool name matches toolResponseMetadata", async () => {
6
7
  vi.stubGlobal("openai", {
@@ -1,5 +1,5 @@
1
1
  import { expect, test, vi } from "vitest";
2
- import { useToolInput } from "./useToolInput";
2
+ import { useToolInput } from "../useToolInput";
3
3
  import { renderHook } from "@testing-library/react";
4
4
 
5
5
  test("Should return tool input when called", async () => {
@@ -1,5 +1,5 @@
1
1
  import { expect, test, vi } from "vitest";
2
- import { useToolName } from "./useToolName";
2
+ import { useToolName } from "../useToolName";
3
3
  import { renderHook } from "@testing-library/react";
4
4
 
5
5
  test("Should return tool input when called", async () => {
@@ -0,0 +1,49 @@
1
+ import { afterEach, expect, test, vi } from "vitest";
2
+ import {
3
+ dispatchStateChange,
4
+ stubOpenAiGlobals,
5
+ } from "../../../testing/internal";
6
+ import { renderHookToSnapshotStream } from "@testing-library/react-render-stream";
7
+ import { useToolOutput } from "../useToolOutput";
8
+
9
+ afterEach(() => {
10
+ vi.unstubAllGlobals();
11
+ });
12
+
13
+ test("returns the tool output set in window", async () => {
14
+ stubOpenAiGlobals({ toolOutput: { test: true } });
15
+
16
+ const { takeSnapshot } = await renderHookToSnapshotStream(() =>
17
+ useToolOutput()
18
+ );
19
+
20
+ await expect(takeSnapshot()).resolves.toEqual({ test: true });
21
+ await expect(takeSnapshot).not.toRerender();
22
+ });
23
+
24
+ test("returns null when not set", async () => {
25
+ stubOpenAiGlobals();
26
+
27
+ const { takeSnapshot } = await renderHookToSnapshotStream(() =>
28
+ useToolOutput()
29
+ );
30
+
31
+ await expect(takeSnapshot()).resolves.toBeNull();
32
+ await expect(takeSnapshot).not.toRerender();
33
+ });
34
+
35
+ test("reacts to changes in globals", async () => {
36
+ stubOpenAiGlobals({ toolOutput: { initial: true } });
37
+
38
+ const { takeSnapshot } = await renderHookToSnapshotStream(() =>
39
+ useToolOutput()
40
+ );
41
+
42
+ await expect(takeSnapshot()).resolves.toEqual({ initial: true });
43
+
44
+ window.openai.toolOutput = { updated: true };
45
+ dispatchStateChange();
46
+
47
+ await expect(takeSnapshot()).resolves.toEqual({ updated: true });
48
+ await expect(takeSnapshot).not.toRerender();
49
+ });
@@ -0,0 +1,49 @@
1
+ import { afterEach, expect, test, vi } from "vitest";
2
+ import {
3
+ dispatchStateChange,
4
+ stubOpenAiGlobals,
5
+ } from "../../../testing/internal";
6
+ import { renderHookToSnapshotStream } from "@testing-library/react-render-stream";
7
+ import { useToolResponseMetadata } from "../useToolResponseMetadata";
8
+
9
+ afterEach(() => {
10
+ vi.unstubAllGlobals();
11
+ });
12
+
13
+ test("returns the tool output set in window", async () => {
14
+ stubOpenAiGlobals({ toolResponseMetadata: { test: true } });
15
+
16
+ const { takeSnapshot } = await renderHookToSnapshotStream(() =>
17
+ useToolResponseMetadata()
18
+ );
19
+
20
+ await expect(takeSnapshot()).resolves.toEqual({ test: true });
21
+ await expect(takeSnapshot).not.toRerender();
22
+ });
23
+
24
+ test("returns null when not set", async () => {
25
+ stubOpenAiGlobals();
26
+
27
+ const { takeSnapshot } = await renderHookToSnapshotStream(() =>
28
+ useToolResponseMetadata()
29
+ );
30
+
31
+ await expect(takeSnapshot()).resolves.toBeNull();
32
+ await expect(takeSnapshot).not.toRerender();
33
+ });
34
+
35
+ test("reacts to changes in globals", async () => {
36
+ stubOpenAiGlobals({ toolResponseMetadata: { initial: true } });
37
+
38
+ const { takeSnapshot } = await renderHookToSnapshotStream(() =>
39
+ useToolResponseMetadata()
40
+ );
41
+
42
+ await expect(takeSnapshot()).resolves.toEqual({ initial: true });
43
+
44
+ window.openai.toolResponseMetadata = { updated: true };
45
+ dispatchStateChange();
46
+
47
+ await expect(takeSnapshot()).resolves.toEqual({ updated: true });
48
+ await expect(takeSnapshot).not.toRerender();
49
+ });
@@ -0,0 +1,158 @@
1
+ import { afterEach, expect, test, vi } from "vitest";
2
+ import {
3
+ disableActEnvironment,
4
+ renderHookToSnapshotStream,
5
+ } from "@testing-library/react-render-stream";
6
+ import { useWidgetState } from "../useWidgetState";
7
+ import { stubOpenAiGlobals } from "../../../testing/internal";
8
+
9
+ afterEach(() => {
10
+ vi.unstubAllGlobals();
11
+ });
12
+
13
+ test("returns state from global", async () => {
14
+ stubOpenAiGlobals({ widgetState: { test: true } });
15
+
16
+ using _disabledAct = disableActEnvironment();
17
+ const { takeSnapshot } = await renderHookToSnapshotStream(() =>
18
+ useWidgetState()
19
+ );
20
+
21
+ const [widgetState] = await takeSnapshot();
22
+
23
+ expect(widgetState).toEqual({ test: true });
24
+ await expect(takeSnapshot).not.toRerender();
25
+ });
26
+
27
+ test("returns null when global does not exist", async () => {
28
+ stubOpenAiGlobals();
29
+
30
+ using _disabledAct = disableActEnvironment();
31
+ const { takeSnapshot } = await renderHookToSnapshotStream(() =>
32
+ useWidgetState()
33
+ );
34
+
35
+ const [widgetState] = await takeSnapshot();
36
+
37
+ expect(widgetState).toBeNull();
38
+ await expect(takeSnapshot).not.toRerender();
39
+ });
40
+
41
+ test("returns provided default state when global does not exist", async () => {
42
+ stubOpenAiGlobals();
43
+
44
+ using _disabledAct = disableActEnvironment();
45
+ const { takeSnapshot } = await renderHookToSnapshotStream(() =>
46
+ useWidgetState({ defaultValue: true })
47
+ );
48
+
49
+ const [widgetState] = await takeSnapshot();
50
+
51
+ expect(widgetState).toEqual({ defaultValue: true });
52
+ await expect(takeSnapshot).not.toRerender();
53
+ });
54
+
55
+ test("returns provided default state returned from init function when global does not exist", async () => {
56
+ stubOpenAiGlobals();
57
+
58
+ using _disabledAct = disableActEnvironment();
59
+ const { takeSnapshot } = await renderHookToSnapshotStream(() =>
60
+ useWidgetState(() => ({ defaultValueFromFunction: true }))
61
+ );
62
+
63
+ const [widgetState] = await takeSnapshot();
64
+
65
+ expect(widgetState).toEqual({ defaultValueFromFunction: true });
66
+ await expect(takeSnapshot).not.toRerender();
67
+ });
68
+
69
+ test("prefers global value over default value", async () => {
70
+ stubOpenAiGlobals({ widgetState: { globalWidgetState: true } });
71
+
72
+ using _disabledAct = disableActEnvironment();
73
+ const { takeSnapshot } = await renderHookToSnapshotStream(() =>
74
+ useWidgetState({ defaultValue: true })
75
+ );
76
+
77
+ const [widgetState] = await takeSnapshot();
78
+
79
+ expect(widgetState).toEqual({ globalWidgetState: true });
80
+ await expect(takeSnapshot).not.toRerender();
81
+ });
82
+
83
+ test("rerenders with new value after setting new value", async () => {
84
+ stubOpenAiGlobals({ widgetState: { globalWidgetState: true } });
85
+
86
+ using _disabledAct = disableActEnvironment();
87
+ const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
88
+ () => useWidgetState()
89
+ );
90
+
91
+ {
92
+ const [widgetState] = await takeSnapshot();
93
+
94
+ expect(widgetState).toEqual({ globalWidgetState: true });
95
+ }
96
+
97
+ const [, setWidgetState] = getCurrentSnapshot();
98
+ setWidgetState({ rerendered: true });
99
+
100
+ {
101
+ const [widgetState] = await takeSnapshot();
102
+
103
+ expect(widgetState).toEqual({ rerendered: true });
104
+ }
105
+
106
+ await expect(takeSnapshot).not.toRerender();
107
+ });
108
+
109
+ test("allows state setter function with previous value", async () => {
110
+ stubOpenAiGlobals({ widgetState: { globalWidgetState: true } });
111
+
112
+ using _disabledAct = disableActEnvironment();
113
+ const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
114
+ () => useWidgetState()
115
+ );
116
+
117
+ {
118
+ const [widgetState] = await takeSnapshot();
119
+
120
+ expect(widgetState).toEqual({ globalWidgetState: true });
121
+ }
122
+
123
+ const [, setWidgetState] = getCurrentSnapshot();
124
+ setWidgetState((prev) => ({ ...prev, rerendered: true }));
125
+
126
+ {
127
+ const [widgetState] = await takeSnapshot();
128
+
129
+ expect(widgetState).toEqual({ globalWidgetState: true, rerendered: true });
130
+ }
131
+
132
+ await expect(takeSnapshot).not.toRerender();
133
+ });
134
+
135
+ test("updates value from window when changed globally", async () => {
136
+ stubOpenAiGlobals({ widgetState: { globalWidgetState: true } });
137
+
138
+ using _disabledAct = disableActEnvironment();
139
+ const { takeSnapshot } = await renderHookToSnapshotStream(() =>
140
+ useWidgetState()
141
+ );
142
+
143
+ {
144
+ const [widgetState] = await takeSnapshot();
145
+
146
+ expect(widgetState).toEqual({ globalWidgetState: true });
147
+ }
148
+
149
+ window.openai.setWidgetState({ fromEvent: true });
150
+
151
+ {
152
+ const [widgetState] = await takeSnapshot();
153
+
154
+ expect(widgetState).toEqual({ fromEvent: true });
155
+ }
156
+
157
+ await expect(takeSnapshot).not.toRerender();
158
+ });
@@ -1,15 +1,15 @@
1
- import { useSyncExternalStore } from "react";
1
+ import { useSyncExternalStore, useCallback } from "react";
2
2
  import {
3
3
  SET_GLOBALS_EVENT_TYPE,
4
4
  SetGlobalsEvent,
5
5
  OpenAiGlobals,
6
- } from "../types/openai";
6
+ } from "../../types/openai";
7
7
 
8
8
  export function useOpenAiGlobal<K extends keyof OpenAiGlobals>(
9
9
  key: K
10
10
  ): OpenAiGlobals[K] {
11
11
  return useSyncExternalStore(
12
- (onChange) => {
12
+ useCallback((onChange) => {
13
13
  const handleSetGlobal = (event: SetGlobalsEvent) => {
14
14
  const value = event.detail.globals[key];
15
15
  if (value === undefined) {
@@ -26,7 +26,7 @@ export function useOpenAiGlobal<K extends keyof OpenAiGlobals>(
26
26
  return () => {
27
27
  window.removeEventListener(SET_GLOBALS_EVENT_TYPE, handleSetGlobal);
28
28
  };
29
- },
29
+ }, []),
30
30
  () => window.openai[key]
31
31
  );
32
32
  }
@@ -0,0 +1,11 @@
1
+ import { useCallback } from "react";
2
+ import { API } from "../../types/openai";
3
+
4
+ type OpenExternalFn = API<any>["openExternal"];
5
+
6
+ export function useOpenExternal() {
7
+ return useCallback<OpenExternalFn>(
8
+ (...args) => window.openai.openExternal(...args),
9
+ []
10
+ );
11
+ }
@@ -1,4 +1,4 @@
1
- import { DisplayMode } from "../types/openai";
1
+ import { DisplayMode } from "../../types/openai";
2
2
 
3
3
  export const useRequestDisplayMode = () => {
4
4
  return async (args: { mode: DisplayMode }) => {
@@ -1,37 +1,14 @@
1
- import React, { useEffect, useState } from "react";
1
+ import React, { useEffect } from "react";
2
2
  import { useToolName } from "./useToolName";
3
3
  import { useToolInput } from "./useToolInput";
4
-
5
- type ToolUseState = {
6
- appName: string;
7
- hasNavigated: boolean;
8
- setHasNavigated: (v: boolean) => void;
9
- };
10
-
11
- const ToolUseContext = React.createContext<ToolUseState | null>(null);
12
-
13
- export function ToolUseProvider({
14
- children,
15
- appName,
16
- }: {
17
- children: any;
18
- appName: string;
19
- }) {
20
- const [hasNavigated, setHasNavigated] = useState(false);
21
-
22
- return (
23
- <ToolUseContext.Provider value={{ hasNavigated, setHasNavigated, appName }}>
24
- {children}
25
- </ToolUseContext.Provider>
26
- );
27
- }
4
+ import { useToolUseState } from "../context/ToolUseContext";
28
5
 
29
6
  export const useToolEffect = (
30
7
  toolName: string | string[],
31
8
  effect: (toolInput: any) => void,
32
9
  deps: React.DependencyList = []
33
10
  ) => {
34
- const ctx = React.useContext(ToolUseContext);
11
+ const ctx = useToolUseState();
35
12
  const fullToolName = useToolName();
36
13
  const toolInput = useToolInput();
37
14
  if (!ctx)
@@ -3,5 +3,5 @@ import { useOpenAiGlobal } from "./useOpenAiGlobal";
3
3
  export const useToolName = (): string | undefined => {
4
4
  const toolResponseMetadata = useOpenAiGlobal("toolResponseMetadata");
5
5
 
6
- return toolResponseMetadata?.toolName;
6
+ return toolResponseMetadata?.toolName as string;
7
7
  };
@@ -0,0 +1,5 @@
1
+ import { useOpenAiGlobal } from "./useOpenAiGlobal";
2
+
3
+ export function useToolOutput() {
4
+ return useOpenAiGlobal("toolOutput") ?? null;
5
+ }