@apollo/client-ai-apps 0.5.2 → 0.5.3
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/CHANGELOG.md +31 -0
- package/dist/core/ApolloClient.d.ts +1 -1
- package/dist/core/ApolloClient.d.ts.map +1 -1
- package/dist/core/ApolloClient.js +1 -1
- package/dist/core/ApolloClient.js.map +1 -1
- package/dist/mcp/core/ApolloClient.d.ts +6 -1
- package/dist/mcp/core/ApolloClient.d.ts.map +1 -1
- package/dist/mcp/core/ApolloClient.js +36 -3
- package/dist/mcp/core/ApolloClient.js.map +1 -1
- package/dist/mcp/core/McpAppManager.d.ts +2 -2
- package/dist/mcp/core/McpAppManager.d.ts.map +1 -1
- package/dist/mcp/core/McpAppManager.js +3 -3
- package/dist/mcp/core/McpAppManager.js.map +1 -1
- package/dist/mcp/react/hooks/createHydrationUtils.d.ts +15 -0
- package/dist/mcp/react/hooks/createHydrationUtils.d.ts.map +1 -0
- package/dist/mcp/react/hooks/createHydrationUtils.js +113 -0
- package/dist/mcp/react/hooks/createHydrationUtils.js.map +1 -0
- package/dist/mcp/react/index.d.ts +1 -0
- package/dist/mcp/react/index.d.ts.map +1 -1
- package/dist/mcp/react/index.js +1 -0
- package/dist/mcp/react/index.js.map +1 -1
- package/dist/openai/core/ApolloClient.d.ts +6 -1
- package/dist/openai/core/ApolloClient.d.ts.map +1 -1
- package/dist/openai/core/ApolloClient.js +37 -3
- package/dist/openai/core/ApolloClient.js.map +1 -1
- package/dist/openai/core/McpAppManager.d.ts +2 -2
- package/dist/openai/core/McpAppManager.d.ts.map +1 -1
- package/dist/openai/core/McpAppManager.js +5 -5
- package/dist/openai/core/McpAppManager.js.map +1 -1
- package/dist/openai/react/hooks/createHydrationUtils.d.ts +15 -0
- package/dist/openai/react/hooks/createHydrationUtils.d.ts.map +1 -0
- package/dist/openai/react/hooks/createHydrationUtils.js +113 -0
- package/dist/openai/react/hooks/createHydrationUtils.js.map +1 -0
- package/dist/openai/react/index.d.ts +1 -0
- package/dist/openai/react/index.d.ts.map +1 -1
- package/dist/openai/react/index.js +1 -0
- package/dist/openai/react/index.js.map +1 -1
- package/dist/react/ApolloProvider.js +1 -1
- package/dist/react/ApolloProvider.js.map +1 -1
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +3 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mcp.d.ts +1 -1
- package/dist/react/index.mcp.d.ts.map +1 -1
- package/dist/react/index.mcp.js +1 -1
- package/dist/react/index.mcp.js.map +1 -1
- package/dist/react/index.openai.d.ts +1 -1
- package/dist/react/index.openai.d.ts.map +1 -1
- package/dist/react/index.openai.js +1 -1
- package/dist/react/index.openai.js.map +1 -1
- package/dist/react/reactive.d.ts +9 -0
- package/dist/react/reactive.d.ts.map +1 -0
- package/dist/react/reactive.js +11 -0
- package/dist/react/reactive.js.map +1 -0
- package/dist/utilities/getToolNamesFromDocument.d.ts +3 -0
- package/dist/utilities/getToolNamesFromDocument.d.ts.map +1 -0
- package/dist/utilities/getToolNamesFromDocument.js +12 -0
- package/dist/utilities/getToolNamesFromDocument.js.map +1 -0
- package/dist/utilities/getVariableNamesFromDocument.d.ts +3 -0
- package/dist/utilities/getVariableNamesFromDocument.d.ts.map +1 -0
- package/dist/utilities/getVariableNamesFromDocument.js +6 -0
- package/dist/utilities/getVariableNamesFromDocument.js.map +1 -0
- package/dist/utilities/index.d.ts +3 -0
- package/dist/utilities/index.d.ts.map +1 -1
- package/dist/utilities/index.js +3 -0
- package/dist/utilities/index.js.map +1 -1
- package/dist/utilities/warnOnVariableMismatch.d.ts +3 -0
- package/dist/utilities/warnOnVariableMismatch.d.ts.map +1 -0
- package/dist/utilities/warnOnVariableMismatch.js +10 -0
- package/dist/utilities/warnOnVariableMismatch.js.map +1 -0
- package/package.json +2 -1
- package/src/core/ApolloClient.ts +1 -1
- package/src/mcp/core/ApolloClient.ts +67 -2
- package/src/mcp/core/McpAppManager.ts +3 -3
- package/src/mcp/core/__tests__/ApolloClient.test.ts +109 -6
- package/src/mcp/link/__tests__/ToolCallLink.test.ts +1 -1
- package/src/mcp/react/hooks/__tests__/createHydrationUtils.test.tsx +1228 -0
- package/src/mcp/react/hooks/createHydrationUtils.ts +182 -0
- package/src/mcp/react/index.ts +1 -0
- package/src/openai/core/ApolloClient.ts +68 -2
- package/src/openai/core/McpAppManager.ts +5 -5
- package/src/openai/core/__tests__/ApolloClient.test.ts +113 -7
- package/src/openai/react/hooks/__tests__/createHydrationUtils.test.tsx +1333 -0
- package/src/openai/react/hooks/createHydrationUtils.ts +182 -0
- package/src/openai/react/index.ts +1 -0
- package/src/react/ApolloProvider.tsx +1 -1
- package/src/react/index.mcp.ts +1 -0
- package/src/react/index.openai.ts +1 -0
- package/src/react/index.ts +7 -0
- package/src/react/reactive.ts +19 -0
- package/src/testing/internal/mcp/graphqlToolResult.ts +5 -5
- package/src/utilities/getToolNamesFromDocument.ts +15 -0
- package/src/utilities/getVariableNamesFromDocument.ts +9 -0
- package/src/utilities/index.ts +3 -0
- package/src/utilities/warnOnVariableMismatch.ts +20 -0
|
@@ -3,15 +3,24 @@ import {
|
|
|
3
3
|
ApolloClient as BaseApolloClient,
|
|
4
4
|
DocumentTransform,
|
|
5
5
|
} from "@apollo/client";
|
|
6
|
+
import type {
|
|
7
|
+
WatchQueryOptions,
|
|
8
|
+
ObservableQuery,
|
|
9
|
+
OperationVariables,
|
|
10
|
+
} from "@apollo/client";
|
|
6
11
|
import { removeDirectivesFromDocument } from "@apollo/client/utilities/internal";
|
|
7
12
|
import { parse } from "graphql";
|
|
13
|
+
import { equal } from "@wry/equality";
|
|
8
14
|
import { __DEV__ } from "@apollo/client/utilities/environment";
|
|
9
15
|
import type { ApplicationManifest } from "../../types/application-manifest.js";
|
|
10
16
|
import { ToolCallLink } from "../link/ToolCallLink.js";
|
|
11
17
|
import {
|
|
12
18
|
aiClientSymbol,
|
|
13
19
|
cacheAsync,
|
|
20
|
+
getToolNamesFromDocument,
|
|
21
|
+
getVariableNamesFromDocument,
|
|
14
22
|
getVariablesForOperationFromToolInput,
|
|
23
|
+
warnOnVariableMismatch,
|
|
15
24
|
} from "../../utilities/index.js";
|
|
16
25
|
import { McpAppManager } from "./McpAppManager.js";
|
|
17
26
|
|
|
@@ -29,6 +38,8 @@ export class ApolloClient extends BaseApolloClient {
|
|
|
29
38
|
/** @internal */
|
|
30
39
|
readonly [aiClientSymbol] = true;
|
|
31
40
|
|
|
41
|
+
#toolInput: Record<string, unknown> | undefined;
|
|
42
|
+
|
|
32
43
|
constructor(options: ApolloClient.Options) {
|
|
33
44
|
const link = options.link ?? new ToolCallLink();
|
|
34
45
|
|
|
@@ -57,9 +68,63 @@ export class ApolloClient extends BaseApolloClient {
|
|
|
57
68
|
this.appManager.close().catch(() => {});
|
|
58
69
|
}
|
|
59
70
|
|
|
60
|
-
|
|
71
|
+
get toolInput() {
|
|
72
|
+
return this.#toolInput;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
clearToolInput() {
|
|
76
|
+
this.#toolInput = undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
watchQuery<
|
|
80
|
+
T = any,
|
|
81
|
+
TVariables extends OperationVariables = OperationVariables,
|
|
82
|
+
>(options: WatchQueryOptions<TVariables, T>): ObservableQuery<T, TVariables> {
|
|
83
|
+
if (__DEV__) {
|
|
84
|
+
const toolInput = this.#toolInput;
|
|
85
|
+
|
|
86
|
+
if (toolInput) {
|
|
87
|
+
const toolName = this.appManager.toolName;
|
|
88
|
+
const hasMatchingTool =
|
|
89
|
+
!!toolName && getToolNamesFromDocument(options.query).has(toolName);
|
|
90
|
+
|
|
91
|
+
if (hasMatchingTool) {
|
|
92
|
+
// Clear after first matching comparison so this only fires once and
|
|
93
|
+
// remounting doesn't produce spurious warnings.
|
|
94
|
+
this.#toolInput = undefined;
|
|
95
|
+
|
|
96
|
+
const variableNames = getVariableNamesFromDocument(options.query);
|
|
97
|
+
|
|
98
|
+
if (variableNames.size > 0) {
|
|
99
|
+
const { variables } = options;
|
|
100
|
+
const toolInputVariables = Object.entries(toolInput).filter(
|
|
101
|
+
([key]) => variableNames.has(key)
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const hasToolInputMismatch = toolInputVariables.some(
|
|
105
|
+
([key, value]) => !equal(value, variables?.[key])
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (hasToolInputMismatch) {
|
|
109
|
+
warnOnVariableMismatch(
|
|
110
|
+
options.query,
|
|
111
|
+
Object.fromEntries(toolInputVariables),
|
|
112
|
+
variables
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return super.watchQuery(options);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
connect = cacheAsync(async () => {
|
|
61
124
|
const { prefetch, result, toolName, args } =
|
|
62
|
-
await this.appManager.
|
|
125
|
+
await this.appManager.connect();
|
|
126
|
+
|
|
127
|
+
this.#toolInput = args;
|
|
63
128
|
|
|
64
129
|
this.manifest.operations.forEach((operation) => {
|
|
65
130
|
if (operation.prefetchID && prefetch?.[operation.prefetchID]) {
|
|
@@ -35,7 +35,7 @@ export class McpAppManager {
|
|
|
35
35
|
return this.#toolInput;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
connect = cacheAsync(async () => {
|
|
39
39
|
let toolResult = promiseWithResolvers<ApolloMcpServerApps.CallToolResult>();
|
|
40
40
|
let toolInput = promiseWithResolvers<Parameters<App["ontoolinput"]>[0]>();
|
|
41
41
|
|
|
@@ -49,7 +49,7 @@ export class McpAppManager {
|
|
|
49
49
|
toolInput.resolve(params);
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
-
await this.
|
|
52
|
+
await this.connectToHost();
|
|
53
53
|
|
|
54
54
|
const { structuredContent, _meta } = await toolResult.promise;
|
|
55
55
|
const { arguments: args } = await toolInput.promise;
|
|
@@ -93,7 +93,7 @@ export class McpAppManager {
|
|
|
93
93
|
return result.structuredContent;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
private async
|
|
96
|
+
private async connectToHost() {
|
|
97
97
|
try {
|
|
98
98
|
return await this.app.connect(
|
|
99
99
|
new PostMessageTransport(window.parent, window.parent)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { expect, test, vi } from "vitest";
|
|
1
|
+
import { expect, test, vi, describe } from "vitest";
|
|
2
2
|
import { ApolloClient } from "../ApolloClient.js";
|
|
3
3
|
import { ApolloLink, HttpLink, InMemoryCache, gql } from "@apollo/client";
|
|
4
4
|
import { print } from "@apollo/client/utilities";
|
|
5
5
|
import { ToolCallLink } from "../../link/ToolCallLink.js";
|
|
6
6
|
import {
|
|
7
|
+
graphqlToolResult,
|
|
7
8
|
minimalHostContextWithToolName,
|
|
8
9
|
mockApplicationManifest,
|
|
9
10
|
mockMcpHost,
|
|
@@ -57,7 +58,7 @@ test("writes tool result data to cache", async () => {
|
|
|
57
58
|
});
|
|
58
59
|
host.sendToolInput({ arguments: { id: "1" } });
|
|
59
60
|
|
|
60
|
-
await client.
|
|
61
|
+
await client.connect();
|
|
61
62
|
|
|
62
63
|
expect(client.extract()).toEqual({
|
|
63
64
|
"Product:1": {
|
|
@@ -124,7 +125,7 @@ test("writes prefetch data to cache", async () => {
|
|
|
124
125
|
});
|
|
125
126
|
host.sendToolInput({ arguments: {} });
|
|
126
127
|
|
|
127
|
-
await client.
|
|
128
|
+
await client.connect();
|
|
128
129
|
|
|
129
130
|
expect(client.extract()).toEqual({
|
|
130
131
|
"Product:1": {
|
|
@@ -213,7 +214,7 @@ test("writes prefetch and tool response data to cache when both are provided", a
|
|
|
213
214
|
});
|
|
214
215
|
host.sendToolInput({ arguments: { id: "2" } });
|
|
215
216
|
|
|
216
|
-
await client.
|
|
217
|
+
await client.connect();
|
|
217
218
|
|
|
218
219
|
expect(client.extract()).toEqual({
|
|
219
220
|
"Product:1": {
|
|
@@ -281,7 +282,7 @@ test("excludes extra tool input variables not defined in the operation", async (
|
|
|
281
282
|
});
|
|
282
283
|
host.sendToolInput({ arguments: { id: "1", extraParam: "ignored" } });
|
|
283
284
|
|
|
284
|
-
await client.
|
|
285
|
+
await client.connect();
|
|
285
286
|
|
|
286
287
|
expect(client.extract()).toEqual({
|
|
287
288
|
"Product:1": {
|
|
@@ -345,7 +346,7 @@ test("allows for custom links provided to the constructor", async () => {
|
|
|
345
346
|
},
|
|
346
347
|
}));
|
|
347
348
|
|
|
348
|
-
await client.
|
|
349
|
+
await client.connect();
|
|
349
350
|
|
|
350
351
|
const variables = { id: "1" };
|
|
351
352
|
const query = gql(manifest.operations[0].body);
|
|
@@ -462,3 +463,105 @@ test("creates a default ToolCallLink when no link is provided", () => {
|
|
|
462
463
|
});
|
|
463
464
|
}).not.toThrow();
|
|
464
465
|
});
|
|
466
|
+
|
|
467
|
+
describe("watchQuery dev warnings", () => {
|
|
468
|
+
const query = gql`
|
|
469
|
+
query Products($category: String!, $page: Int!, $sortBy: String!)
|
|
470
|
+
@tool(name: "GetProductsByCategory") {
|
|
471
|
+
products(category: $category, page: $page, sortBy: $sortBy) {
|
|
472
|
+
id
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
`;
|
|
476
|
+
|
|
477
|
+
async function setupClient({
|
|
478
|
+
toolInput,
|
|
479
|
+
}: {
|
|
480
|
+
toolInput: Record<string, unknown>;
|
|
481
|
+
}) {
|
|
482
|
+
const client = new ApolloClient({
|
|
483
|
+
cache: new InMemoryCache(),
|
|
484
|
+
manifest: mockApplicationManifest(),
|
|
485
|
+
});
|
|
486
|
+
using host = await mockMcpHost({
|
|
487
|
+
hostContext: minimalHostContextWithToolName("GetProductsByCategory"),
|
|
488
|
+
});
|
|
489
|
+
host.onCleanup(() => client.stop());
|
|
490
|
+
host.sendToolResult(graphqlToolResult({ data: { products: [] } }));
|
|
491
|
+
host.sendToolInput({ arguments: toolInput });
|
|
492
|
+
await client.connect();
|
|
493
|
+
return client;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
test("warns when variables don't match tool input", async () => {
|
|
497
|
+
using _ = spyOnConsole("debug", "warn");
|
|
498
|
+
const client = await setupClient({
|
|
499
|
+
toolInput: { category: "electronics", page: 1, sortBy: "title" },
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
client.watchQuery({
|
|
503
|
+
query,
|
|
504
|
+
variables: { category: "music", page: 1, sortBy: "name" },
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
expect(console.warn).toHaveBeenCalledWith(
|
|
508
|
+
expect.stringContaining("useHydratedVariables"),
|
|
509
|
+
{ category: "electronics", page: 1, sortBy: "title" },
|
|
510
|
+
{ category: "music", page: 1, sortBy: "name" }
|
|
511
|
+
);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
test("does not warn when variables match tool input", async () => {
|
|
515
|
+
using _ = spyOnConsole("debug", "warn");
|
|
516
|
+
const client = await setupClient({
|
|
517
|
+
toolInput: { category: "electronics", page: 1, sortBy: "title" },
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
client.watchQuery({
|
|
521
|
+
query,
|
|
522
|
+
variables: { category: "electronics", page: 1, sortBy: "title" },
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
expect(console.warn).not.toHaveBeenCalled();
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
test("does not warn when query has no matching @tool directive", async () => {
|
|
529
|
+
using _ = spyOnConsole("debug", "warn");
|
|
530
|
+
const client = await setupClient({
|
|
531
|
+
toolInput: { category: "electronics", page: 1, sortBy: "title" },
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
const queryWithoutTool = gql`
|
|
535
|
+
query Products($category: String!) {
|
|
536
|
+
products(category: $category) {
|
|
537
|
+
id
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
`;
|
|
541
|
+
|
|
542
|
+
client.watchQuery({
|
|
543
|
+
query: queryWithoutTool,
|
|
544
|
+
variables: { category: "music" },
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
expect(console.warn).not.toHaveBeenCalled();
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
test("warning fires at most once (subsequent calls don't re-warn)", async () => {
|
|
551
|
+
using _ = spyOnConsole("debug", "warn");
|
|
552
|
+
const client = await setupClient({
|
|
553
|
+
toolInput: { category: "electronics", page: 1, sortBy: "title" },
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
client.watchQuery({
|
|
557
|
+
query,
|
|
558
|
+
variables: { category: "music", page: 1, sortBy: "name" },
|
|
559
|
+
});
|
|
560
|
+
client.watchQuery({
|
|
561
|
+
query,
|
|
562
|
+
variables: { category: "music", page: 1, sortBy: "name" },
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
expect(console.warn).toHaveBeenCalledTimes(1);
|
|
566
|
+
});
|
|
567
|
+
});
|
|
@@ -49,7 +49,7 @@ test("delegates query execution to MCP host", async () => {
|
|
|
49
49
|
},
|
|
50
50
|
}));
|
|
51
51
|
|
|
52
|
-
await client.
|
|
52
|
+
await client.connect();
|
|
53
53
|
|
|
54
54
|
const observable = execute(new ToolCallLink(), { query }, { client });
|
|
55
55
|
const stream = new ObservableStream(observable);
|