@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.
- package/.git-blame-ignore-revs +2 -0
- package/.github/workflows/compare-build-output.yml +28 -0
- package/.github/workflows/pr.yaml +23 -15
- package/.github/workflows/release.yaml +46 -46
- package/.prettierrc +9 -0
- package/config/compare-build-output-to.sh +90 -0
- package/dist/core/ApolloClient.d.ts +14 -0
- package/dist/index.d.ts +17 -10
- package/dist/index.js +164 -62
- package/dist/link/ToolCallLink.d.ts +26 -0
- package/dist/react/ApolloProvider.d.ts +9 -0
- package/dist/react/context/ToolUseContext.d.ts +15 -0
- package/dist/{hooks → react/hooks}/useOpenAiGlobal.d.ts +1 -1
- package/dist/react/hooks/useOpenExternal.d.ts +3 -0
- package/dist/{hooks → react/hooks}/useRequestDisplayMode.d.ts +1 -1
- package/dist/{hooks → react/hooks}/useToolEffect.d.ts +0 -4
- package/dist/react/hooks/useToolOutput.d.ts +1 -0
- package/dist/react/hooks/useToolResponseMetadata.d.ts +1 -0
- package/dist/react/hooks/useWidgetState.d.ts +4 -0
- package/dist/types/openai.d.ts +1 -2
- package/dist/vite/index.js +74 -21
- package/package.json +9 -2
- package/scripts/dev.mjs +3 -1
- package/src/core/ApolloClient.ts +108 -0
- package/src/{apollo_client/client.test.ts → core/__tests__/ApolloClient.test.ts} +232 -20
- package/src/index.ts +36 -10
- package/src/link/ToolCallLink.ts +49 -0
- package/src/{apollo_client/provider.tsx → react/ApolloProvider.tsx} +19 -9
- package/src/{apollo_client/provider.test.tsx → react/__tests__/ApolloProvider.test.tsx} +9 -9
- package/src/react/context/ToolUseContext.tsx +30 -0
- package/src/{hooks → react/hooks/__tests__}/useCallTool.test.ts +1 -1
- package/src/{hooks → react/hooks/__tests__}/useOpenAiGlobal.test.ts +5 -3
- package/src/react/hooks/__tests__/useOpenExternal.test.tsx +24 -0
- package/src/{hooks → react/hooks/__tests__}/useRequestDisplayMode.test.ts +2 -2
- package/src/{hooks → react/hooks/__tests__}/useSendFollowUpMessage.test.ts +4 -2
- package/src/{hooks → react/hooks/__tests__}/useToolEffect.test.tsx +27 -10
- package/src/{hooks → react/hooks/__tests__}/useToolInput.test.ts +1 -1
- package/src/{hooks → react/hooks/__tests__}/useToolName.test.ts +1 -1
- package/src/react/hooks/__tests__/useToolOutput.test.tsx +49 -0
- package/src/react/hooks/__tests__/useToolResponseMetadata.test.tsx +49 -0
- package/src/react/hooks/__tests__/useWidgetState.test.tsx +158 -0
- package/src/react/hooks/useCallTool.ts +13 -0
- package/src/{hooks → react/hooks}/useOpenAiGlobal.ts +11 -5
- package/src/react/hooks/useOpenExternal.ts +11 -0
- package/src/{hooks → react/hooks}/useRequestDisplayMode.ts +1 -1
- package/src/react/hooks/useToolEffect.tsx +37 -0
- package/src/{hooks → react/hooks}/useToolName.ts +1 -1
- package/src/react/hooks/useToolOutput.ts +5 -0
- package/src/react/hooks/useToolResponseMetadata.ts +5 -0
- package/src/react/hooks/useWidgetState.ts +48 -0
- package/src/testing/internal/index.ts +2 -0
- package/src/testing/internal/matchers/index.d.ts +9 -0
- package/src/testing/internal/matchers/index.ts +1 -0
- package/src/testing/internal/matchers/toRerender.ts +49 -0
- package/src/testing/internal/openai/dispatchStateChange.ts +9 -0
- package/src/testing/internal/openai/stubOpenAiGlobals.ts +13 -0
- package/src/types/openai.ts +6 -3
- package/src/vite/{absolute_asset_imports_plugin.test.ts → __tests__/absolute_asset_imports_plugin.test.ts} +4 -2
- package/src/vite/{application_manifest_plugin.test.ts → __tests__/application_manifest_plugin.test.ts} +176 -53
- package/src/vite/absolute_asset_imports_plugin.ts +3 -1
- package/src/vite/application_manifest_plugin.ts +84 -24
- package/vitest-setup.ts +1 -0
- package/dist/apollo_client/client.d.ts +0 -14
- package/dist/apollo_client/provider.d.ts +0 -5
- package/src/apollo_client/client.ts +0 -90
- package/src/hooks/useCallTool.ts +0 -8
- package/src/hooks/useToolEffect.tsx +0 -41
- /package/dist/{hooks → react/hooks}/useSendFollowUpMessage.d.ts +0 -0
- /package/dist/{hooks → react/hooks}/useToolInput.d.ts +0 -0
- /package/dist/{hooks → react/hooks}/useToolName.d.ts +0 -0
- /package/src/{hooks → react/hooks}/useSendFollowUpMessage.ts +0 -0
- /package/src/{hooks → react/hooks}/useToolInput.ts +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { expect, test, describe, vi } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
import { ApplicationManifest } from "
|
|
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:
|
|
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: [
|
|
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
|
|
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({
|
|
67
|
+
await client.query({
|
|
68
|
+
query: parse(manifest.operations[0].body),
|
|
69
|
+
variables,
|
|
70
|
+
});
|
|
59
71
|
|
|
60
|
-
expect(window.openai.callTool).toBeCalledWith("execute", {
|
|
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:
|
|
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: [
|
|
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
|
|
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:
|
|
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: [
|
|
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
|
|
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:
|
|
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: [
|
|
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: [
|
|
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
|
|
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:
|
|
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: [
|
|
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
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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 {
|
|
12
|
-
export {
|
|
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 {
|
|
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
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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 {
|
|
2
|
+
import { ApolloProvider } from "../ApolloProvider";
|
|
3
3
|
import { render } from "@testing-library/react";
|
|
4
|
-
import {
|
|
5
|
-
import { SET_GLOBALS_EVENT_TYPE } from "
|
|
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
|
|
14
|
+
} as unknown as ApolloClient;
|
|
15
15
|
|
|
16
|
-
render(<
|
|
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
|
|
24
|
+
} as unknown as ApolloClient;
|
|
25
25
|
|
|
26
|
-
render(<
|
|
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
|
|
34
|
+
} as unknown as ApolloClient;
|
|
35
35
|
|
|
36
|
-
render(<
|
|
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,7 +1,7 @@
|
|
|
1
1
|
import { expect, test, vi } from "vitest";
|
|
2
|
-
import { useOpenAiGlobal } from "
|
|
2
|
+
import { useOpenAiGlobal } from "../useOpenAiGlobal";
|
|
3
3
|
import { renderHook, act } from "@testing-library/react";
|
|
4
|
-
import { SET_GLOBALS_EVENT_TYPE } from "
|
|
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: {
|
|
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 "
|
|
3
|
-
import { DisplayMode } from "
|
|
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 "
|
|
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({
|
|
12
|
+
expect(window.openai.sendFollowUpMessage).toBeCalledWith({
|
|
13
|
+
prompt: "Do a cool thing!",
|
|
14
|
+
});
|
|
13
15
|
});
|