@apollo/client-ai-apps 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/dist/config/defineConfig.d.ts +1 -0
  3. package/dist/config/defineConfig.d.ts.map +1 -1
  4. package/dist/config/schema.d.ts +1 -0
  5. package/dist/config/schema.d.ts.map +1 -1
  6. package/dist/config/schema.js +1 -0
  7. package/dist/config/schema.js.map +1 -1
  8. package/dist/mcp/core/McpAppManager.d.ts +2 -1
  9. package/dist/mcp/core/McpAppManager.d.ts.map +1 -1
  10. package/dist/mcp/core/McpAppManager.js +11 -1
  11. package/dist/mcp/core/McpAppManager.js.map +1 -1
  12. package/dist/mcp/react/hooks/useHostContext.d.ts +2 -0
  13. package/dist/mcp/react/hooks/useHostContext.d.ts.map +1 -0
  14. package/dist/mcp/react/hooks/useHostContext.js +7 -0
  15. package/dist/mcp/react/hooks/useHostContext.js.map +1 -0
  16. package/dist/mcp/react/index.d.ts +1 -0
  17. package/dist/mcp/react/index.d.ts.map +1 -1
  18. package/dist/mcp/react/index.js +1 -0
  19. package/dist/mcp/react/index.js.map +1 -1
  20. package/dist/openai/core/McpAppManager.d.ts +2 -1
  21. package/dist/openai/core/McpAppManager.d.ts.map +1 -1
  22. package/dist/openai/core/McpAppManager.js +11 -1
  23. package/dist/openai/core/McpAppManager.js.map +1 -1
  24. package/dist/openai/react/hooks/useHostContext.d.ts +2 -0
  25. package/dist/openai/react/hooks/useHostContext.d.ts.map +1 -0
  26. package/dist/openai/react/hooks/useHostContext.js +7 -0
  27. package/dist/openai/react/hooks/useHostContext.js.map +1 -0
  28. package/dist/openai/react/index.d.ts +1 -0
  29. package/dist/openai/react/index.d.ts.map +1 -1
  30. package/dist/openai/react/index.js +1 -0
  31. package/dist/openai/react/index.js.map +1 -1
  32. package/dist/react/index.d.ts +1 -0
  33. package/dist/react/index.d.ts.map +1 -1
  34. package/dist/react/index.js +1 -0
  35. package/dist/react/index.js.map +1 -1
  36. package/dist/react/index.mcp.d.ts +1 -1
  37. package/dist/react/index.mcp.d.ts.map +1 -1
  38. package/dist/react/index.mcp.js +1 -1
  39. package/dist/react/index.mcp.js.map +1 -1
  40. package/dist/react/index.openai.d.ts +1 -1
  41. package/dist/react/index.openai.d.ts.map +1 -1
  42. package/dist/react/index.openai.js +1 -1
  43. package/dist/react/index.openai.js.map +1 -1
  44. package/dist/types/application-manifest.d.ts +1 -0
  45. package/dist/types/application-manifest.d.ts.map +1 -1
  46. package/dist/types/application-manifest.js.map +1 -1
  47. package/dist/vite/__tests__/utilities/build.d.ts.map +1 -1
  48. package/dist/vite/__tests__/utilities/build.js +0 -1
  49. package/dist/vite/__tests__/utilities/build.js.map +1 -1
  50. package/dist/vite/apolloClientAiApps.d.ts +1 -0
  51. package/dist/vite/apolloClientAiApps.d.ts.map +1 -1
  52. package/dist/vite/apolloClientAiApps.js +63 -46
  53. package/dist/vite/apolloClientAiApps.js.map +1 -1
  54. package/package.json +1 -4
  55. package/src/config/schema.ts +1 -0
  56. package/src/mcp/core/McpAppManager.ts +23 -1
  57. package/src/mcp/react/hooks/__tests__/useHostContext.test.tsx +95 -0
  58. package/src/mcp/react/hooks/useHostContext.ts +14 -0
  59. package/src/mcp/react/index.ts +1 -0
  60. package/src/openai/core/McpAppManager.ts +22 -1
  61. package/src/openai/react/hooks/useHostContext.ts +14 -0
  62. package/src/openai/react/index.ts +1 -0
  63. package/src/react/index.mcp.ts +1 -0
  64. package/src/react/index.openai.ts +1 -0
  65. package/src/react/index.ts +3 -0
  66. package/src/testing/internal/mcp/mockMcpHost.ts +12 -0
  67. package/src/testing/internal/utilities/mockApplicationManifest.ts +1 -0
  68. package/src/types/application-manifest.ts +1 -0
  69. package/src/vite/__tests__/apolloClientAiApps.test.ts +460 -61
  70. package/src/vite/__tests__/utilities/build.ts +0 -1
  71. package/src/vite/apolloClientAiApps.ts +100 -57
@@ -33,7 +33,6 @@ export async function buildApp(
33
33
  ...config,
34
34
  build: {
35
35
  emptyOutDir: false,
36
- outDir: "dist",
37
36
  ...config.build,
38
37
  rollupOptions: {
39
38
  input: config.build?.rollupOptions?.input ?? "virtual:entry",
@@ -14,7 +14,7 @@ import { glob } from "glob";
14
14
  import { print } from "@apollo/client/utilities";
15
15
  import { removeDirectivesFromDocument } from "@apollo/client/utilities/internal";
16
16
  import { of } from "rxjs";
17
- import { Kind, parse, type OperationDefinitionNode } from "graphql";
17
+ import { Kind, OperationTypeNode, parse } from "graphql";
18
18
  import {
19
19
  getArgumentValue,
20
20
  getDirectiveArgument,
@@ -30,6 +30,7 @@ import { explorer } from "./utilities/config.js";
30
30
  import type { ApolloClientAiAppsConfig } from "../config/index.js";
31
31
  import { ApolloClientAiAppsConfigSchema } from "../config/schema.js";
32
32
  import { z } from "zod";
33
+ import { createFragmentRegistry } from "@apollo/client/cache";
33
34
 
34
35
  export declare namespace apolloClientAiApps {
35
36
  export type Target = ApolloClientAiAppsConfig.AppTarget;
@@ -37,6 +38,7 @@ export declare namespace apolloClientAiApps {
37
38
  export interface Options {
38
39
  targets: Target[];
39
40
  devTarget?: Target | undefined;
41
+ appsOutDir: string;
40
42
  }
41
43
  }
42
44
 
@@ -66,19 +68,23 @@ export function devTarget(target: string | undefined) {
66
68
  interface FileCache {
67
69
  file: string;
68
70
  hash: string;
69
- operations: ManifestOperation[];
71
+ sources: DocumentNode[];
70
72
  }
71
73
 
72
74
  export function apolloClientAiApps(
73
75
  options: apolloClientAiApps.Options
74
76
  ): Plugin {
75
77
  const targets = Array.from(new Set(options.targets));
76
- const { devTarget = targets.length === 1 ? targets[0] : undefined } = options;
78
+ const {
79
+ devTarget = targets.length === 1 ? targets[0] : undefined,
80
+ appsOutDir,
81
+ } = options;
77
82
  const cache = new Map<string, FileCache>();
78
83
 
79
- let packageJson!: Record<string, any>;
80
84
  let config!: ResolvedConfig;
81
85
 
86
+ const fragments = createFragmentRegistry();
87
+
82
88
  invariant(
83
89
  Array.isArray(targets) && targets.length > 0,
84
90
  "The `targets` option must be a non-empty array"
@@ -89,8 +95,13 @@ export function apolloClientAiApps(
89
95
  `All targets must be one of: ${VALID_TARGETS.join(", ")}`
90
96
  );
91
97
 
98
+ invariant(
99
+ path.basename(path.normalize(appsOutDir)) === "apps",
100
+ "`appsOutDir` must end with `apps` as the final path segment (e.g. `path/to/apps`)."
101
+ );
102
+
92
103
  const client = new ApolloClient({
93
- cache: new InMemoryCache(),
104
+ cache: new InMemoryCache({ fragments }),
94
105
  link: processQueryLink,
95
106
  });
96
107
 
@@ -106,52 +117,57 @@ export function apolloClientAiApps(
106
117
  { name: "graphql-tag", identifier: "gql" },
107
118
  { name: "@apollo/client", identifier: "gql" },
108
119
  ],
109
- }).map((source) => ({
110
- node: parse(source.body),
111
- file,
112
- location: source.locationOffset,
113
- }));
120
+ }).map((source) => parse(source.body));
114
121
 
115
- const operations: ManifestOperation[] = [];
116
- for (const source of sources) {
117
- const type = (
118
- source.node.definitions.find(
119
- (d) => d.kind === "OperationDefinition"
120
- ) as OperationDefinitionNode
121
- ).operation;
122
-
123
- let result;
124
- if (type === "query") {
125
- result = await client.query({
126
- query: source.node,
127
- fetchPolicy: "no-cache",
128
- });
129
- } else if (type === "mutation") {
130
- result = await client.mutate({
131
- mutation: source.node,
132
- fetchPolicy: "no-cache",
133
- });
134
- } else {
135
- throw new Error(
136
- "Found an unsupported operation type. Only Query and Mutation are supported."
137
- );
138
- }
139
- operations.push(result.data as ManifestOperation);
140
- }
122
+ fragments.register(...sources);
141
123
 
142
124
  cache.set(file, {
143
125
  file: file,
144
126
  hash: fileHash,
145
- operations,
127
+ sources,
146
128
  });
147
129
  }
148
130
 
149
131
  async function generateManifest(environment?: Environment) {
150
132
  const appsConfig = await getAppsConfig();
151
- const operations = Array.from(cache.values()).flatMap(
152
- (entry) => entry.operations
133
+ const sources = Array.from(cache.values()).flatMap(
134
+ (entry) => entry.sources
153
135
  );
154
136
 
137
+ const operations: ManifestOperation[] = [];
138
+ for (const source of sources) {
139
+ const operationDef = source.definitions.find(
140
+ (d) => d.kind === Kind.OPERATION_DEFINITION
141
+ );
142
+
143
+ if (!operationDef) continue;
144
+
145
+ switch (operationDef.operation) {
146
+ case OperationTypeNode.QUERY: {
147
+ const result = await client.query<ManifestOperation>({
148
+ query: source,
149
+ fetchPolicy: "no-cache",
150
+ });
151
+
152
+ operations.push(result.data!);
153
+ break;
154
+ }
155
+ case OperationTypeNode.MUTATION: {
156
+ const result = await client.mutate<ManifestOperation>({
157
+ mutation: source,
158
+ fetchPolicy: "no-cache",
159
+ });
160
+
161
+ operations.push(result.data!);
162
+ break;
163
+ }
164
+ default:
165
+ throw new Error(
166
+ `Found unsupported operation type '${operationDef.operation}'. Only queries and mutations are supported.`
167
+ );
168
+ }
169
+ }
170
+
155
171
  invariant(
156
172
  operations.filter((o) => o.prefetch).length <= 1,
157
173
  "Found multiple operations marked as `@prefetch`. You can only mark 1 operation with `@prefetch`."
@@ -185,6 +201,14 @@ export function apolloClientAiApps(
185
201
  ) as { mcp?: string; openai?: string };
186
202
  }
187
203
 
204
+ const packageJson = readPackageJson();
205
+ const appName = appsConfig.name ?? packageJson.name;
206
+
207
+ invariant(
208
+ appName,
209
+ "Error generating application manifest. Could not determine app name. Set `name` in your apollo-client-ai-apps config or `package.json`."
210
+ );
211
+
188
212
  const manifest: ApplicationManifest = {
189
213
  format: "apollo-ai-app-manifest",
190
214
  version: "1",
@@ -192,11 +216,10 @@ export function apolloClientAiApps(
192
216
  name: appsConfig.name ?? packageJson.name,
193
217
  description: appsConfig.description ?? packageJson.description,
194
218
  hash: createHash("sha256").update(Date.now().toString()).digest("hex"),
195
- operations: Array.from(cache.values()).flatMap(
196
- (entry) => entry.operations
197
- ),
219
+ operations,
198
220
  resource,
199
221
  csp: {
222
+ baseUriDomains: appsConfig.csp?.baseUriDomains ?? [],
200
223
  connectDomains: appsConfig.csp?.connectDomains ?? [],
201
224
  frameDomains: appsConfig.csp?.frameDomains ?? [],
202
225
  redirectDomains: appsConfig.csp?.redirectDomains ?? [],
@@ -212,14 +235,7 @@ export function apolloClientAiApps(
212
235
  manifest.labels = appsConfig.labels;
213
236
  }
214
237
 
215
- // We create mcp and openai environments in order to write to
216
- // subdirectories, but we want the manifest to be in the root outDir. If we
217
- // are running in a different environment, we'll put it in the configured
218
- // outDir directly instead.
219
- const outDir =
220
- environment?.name === "mcp" || environment?.name === "openai" ?
221
- path.resolve(config.build.outDir, "../")
222
- : config.build.outDir;
238
+ const outDir = path.join(appsOutDir, appName);
223
239
 
224
240
  // Always write to build directory so the MCP server picks it up
225
241
  const dest = path.resolve(root, outDir, ".application-manifest.json");
@@ -233,9 +249,6 @@ export function apolloClientAiApps(
233
249
  return {
234
250
  name: "@apollo/client-ai-apps/vite",
235
251
  async buildStart() {
236
- // Read package.json on start
237
- packageJson = JSON.parse(fs.readFileSync("package.json", "utf-8"));
238
-
239
252
  // Scan all files on startup
240
253
  const files = await glob("./src/**/*.{ts,tsx,js,jsx}", { fs });
241
254
 
@@ -252,19 +265,27 @@ export function apolloClientAiApps(
252
265
  configResolved(resolvedConfig) {
253
266
  config = resolvedConfig;
254
267
  },
255
- configEnvironment(name, { build }) {
268
+ async configEnvironment(name, { build }) {
256
269
  if (!targets.includes(name as any)) return;
257
270
 
271
+ const appsConfig = await getAppsConfig();
272
+ const appName = appsConfig.name ?? readPackageJson().name;
273
+
274
+ invariant(
275
+ appName,
276
+ "Could not determine app name. Set `name` in your apollo-client-ai-apps config or `package.json`."
277
+ );
278
+
258
279
  return {
259
280
  build: {
260
- outDir: path.join(build?.outDir ?? "dist", name),
281
+ outDir: path.join(appsOutDir, appName, name),
261
282
  },
262
283
  };
263
284
  },
264
285
  configureServer(server) {
265
286
  server.watcher.on("change", async (file) => {
266
287
  if (file.endsWith("package.json")) {
267
- packageJson = JSON.parse(fs.readFileSync("package.json", "utf-8"));
288
+ readPackageJson.resetCache();
268
289
  await generateManifest();
269
290
  } else if (file.match(/\.?apollo-client-ai-apps\.config\.\w+$/)) {
270
291
  explorer.clearCaches();
@@ -276,7 +297,14 @@ export function apolloClientAiApps(
276
297
  });
277
298
  },
278
299
 
279
- config(_, { command }) {
300
+ config(userConfig, { command }) {
301
+ if (userConfig.build?.outDir) {
302
+ console.warn(
303
+ "[@apollo/client-ai-apps/vite] `build.outDir` is set in your Vite config but will be " +
304
+ "ignored. Use `appsOutDir` in the plugin options to control the output location."
305
+ );
306
+ }
307
+
280
308
  if (command === "serve") {
281
309
  invariant(
282
310
  isValidTarget(devTarget) || targets.length === 1,
@@ -497,6 +525,21 @@ function getResourceFromConfig(
497
525
  return typeof config === "string" ? config : config[target];
498
526
  }
499
527
 
528
+ function readPackageJson(): Record<string, any> {
529
+ if (readPackageJson.cache) {
530
+ return readPackageJson.cache;
531
+ }
532
+
533
+ return (readPackageJson.cache = JSON.parse(
534
+ fs.readFileSync("package.json", "utf-8")
535
+ ));
536
+ }
537
+
538
+ readPackageJson.cache = undefined as Record<string, any> | undefined;
539
+ readPackageJson.resetCache = () => {
540
+ readPackageJson.cache = undefined;
541
+ };
542
+
500
543
  const ToolDirectiveSchema = z.strictObject({
501
544
  name: z.stringFormat("toolName", (value) => value.indexOf(" ") === -1, {
502
545
  error: (iss) => `Tool with name "${iss.input}" must not contain spaces`,