@apollo/client-ai-apps 0.6.0 → 0.6.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 (85) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/dist/core/typeRegistration.d.ts +33 -0
  3. package/dist/core/typeRegistration.d.ts.map +1 -0
  4. package/dist/core/typeRegistration.js +2 -0
  5. package/dist/core/typeRegistration.js.map +1 -0
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/mcp/react/hooks/useToolInfo.d.ts +3 -0
  10. package/dist/mcp/react/hooks/useToolInfo.d.ts.map +1 -0
  11. package/dist/mcp/react/hooks/useToolInfo.js +10 -0
  12. package/dist/mcp/react/hooks/useToolInfo.js.map +1 -0
  13. package/dist/mcp/react/hooks/useToolInput.d.ts +6 -1
  14. package/dist/mcp/react/hooks/useToolInput.d.ts.map +1 -1
  15. package/dist/mcp/react/hooks/useToolInput.js +4 -0
  16. package/dist/mcp/react/hooks/useToolInput.js.map +1 -1
  17. package/dist/mcp/react/hooks/useToolName.d.ts +6 -1
  18. package/dist/mcp/react/hooks/useToolName.d.ts.map +1 -1
  19. package/dist/mcp/react/hooks/useToolName.js +4 -0
  20. package/dist/mcp/react/hooks/useToolName.js.map +1 -1
  21. package/dist/mcp/react/index.d.ts +1 -0
  22. package/dist/mcp/react/index.d.ts.map +1 -1
  23. package/dist/mcp/react/index.js +1 -0
  24. package/dist/mcp/react/index.js.map +1 -1
  25. package/dist/openai/react/hooks/useToolInfo.d.ts +3 -0
  26. package/dist/openai/react/hooks/useToolInfo.d.ts.map +1 -0
  27. package/dist/openai/react/hooks/useToolInfo.js +10 -0
  28. package/dist/openai/react/hooks/useToolInfo.js.map +1 -0
  29. package/dist/openai/react/hooks/useToolInput.d.ts +6 -1
  30. package/dist/openai/react/hooks/useToolInput.d.ts.map +1 -1
  31. package/dist/openai/react/hooks/useToolInput.js +4 -0
  32. package/dist/openai/react/hooks/useToolInput.js.map +1 -1
  33. package/dist/openai/react/hooks/useToolName.d.ts +6 -1
  34. package/dist/openai/react/hooks/useToolName.d.ts.map +1 -1
  35. package/dist/openai/react/hooks/useToolName.js +4 -0
  36. package/dist/openai/react/hooks/useToolName.js.map +1 -1
  37. package/dist/openai/react/index.d.ts +1 -0
  38. package/dist/openai/react/index.d.ts.map +1 -1
  39. package/dist/openai/react/index.js +1 -0
  40. package/dist/openai/react/index.js.map +1 -1
  41. package/dist/react/index.d.ts +9 -0
  42. package/dist/react/index.d.ts.map +1 -1
  43. package/dist/react/index.js +9 -0
  44. package/dist/react/index.js.map +1 -1
  45. package/dist/react/index.mcp.d.ts +1 -1
  46. package/dist/react/index.mcp.d.ts.map +1 -1
  47. package/dist/react/index.mcp.js +1 -1
  48. package/dist/react/index.mcp.js.map +1 -1
  49. package/dist/react/index.openai.d.ts +1 -1
  50. package/dist/react/index.openai.d.ts.map +1 -1
  51. package/dist/react/index.openai.js +1 -1
  52. package/dist/react/index.openai.js.map +1 -1
  53. package/dist/tsconfig/core/tsconfig.json +2 -0
  54. package/dist/tsconfig/mcp/tsconfig.json +2 -0
  55. package/dist/tsconfig/openai/tsconfig.json +2 -0
  56. package/dist/vite/apolloClientAiApps.d.ts +1 -0
  57. package/dist/vite/apolloClientAiApps.d.ts.map +1 -1
  58. package/dist/vite/apolloClientAiApps.js +346 -43
  59. package/dist/vite/apolloClientAiApps.js.map +1 -1
  60. package/dist/vite/utilities/recast.d.ts +54 -0
  61. package/dist/vite/utilities/recast.d.ts.map +1 -0
  62. package/dist/vite/utilities/recast.js +71 -0
  63. package/dist/vite/utilities/recast.js.map +1 -0
  64. package/package.json +7 -3
  65. package/src/core/typeRegistration.ts +32 -0
  66. package/src/index.ts +7 -0
  67. package/src/mcp/react/hooks/__tests__/useToolInfo.test.tsx +53 -0
  68. package/src/mcp/react/hooks/useToolInfo.ts +13 -0
  69. package/src/mcp/react/hooks/useToolInput.ts +6 -1
  70. package/src/mcp/react/hooks/useToolName.ts +6 -1
  71. package/src/mcp/react/index.ts +1 -0
  72. package/src/openai/react/hooks/__tests__/useToolInfo.test.tsx +92 -0
  73. package/src/openai/react/hooks/useToolInfo.ts +13 -0
  74. package/src/openai/react/hooks/useToolInput.ts +6 -1
  75. package/src/openai/react/hooks/useToolName.ts +6 -1
  76. package/src/openai/react/index.ts +1 -0
  77. package/src/react/index.mcp.ts +1 -0
  78. package/src/react/index.openai.ts +1 -0
  79. package/src/react/index.ts +11 -0
  80. package/src/tsconfig/core/tsconfig.json +2 -0
  81. package/src/tsconfig/mcp/tsconfig.json +2 -0
  82. package/src/tsconfig/openai/tsconfig.json +2 -0
  83. package/src/vite/__tests__/apolloClientAiApps.test.ts +754 -6
  84. package/src/vite/apolloClientAiApps.ts +564 -68
  85. package/src/vite/utilities/recast.ts +100 -0
@@ -2,20 +2,25 @@ import { defaultClientConditions, } from "vite";
2
2
  import { createHash } from "node:crypto";
3
3
  import path from "node:path";
4
4
  import fs from "node:fs";
5
+ import * as recast from "recast";
6
+ import typescriptParser from "recast/parsers/typescript.js";
5
7
  import { ApolloClient, ApolloLink } from "@apollo/client";
6
8
  import { InMemoryCache } from "@apollo/client";
9
+ import { equal } from "@wry/equality";
7
10
  import { gqlPluckFromCodeStringSync } from "@graphql-tools/graphql-tag-pluck";
8
11
  import { glob } from "glob";
9
12
  import { print } from "@apollo/client/utilities";
10
13
  import { removeDirectivesFromDocument } from "@apollo/client/utilities/internal";
11
14
  import { of } from "rxjs";
12
- import { Kind, OperationTypeNode, parse } from "graphql";
15
+ import { Kind, OperationTypeNode, parse, visit } from "graphql";
13
16
  import { getArgumentValue, getDirectiveArgument, getTypeName, maybeGetArgumentValue, } from "./utilities/graphql.js";
14
17
  import { invariant } from "../utilities/invariant.js";
15
18
  import { explorer } from "./utilities/config.js";
16
19
  import { ApolloClientAiAppsConfigSchema } from "../config/schema.js";
17
20
  import { z } from "zod";
18
21
  import { createFragmentRegistry } from "@apollo/client/cache";
22
+ import { buildImportStatement, buildPropertySignature, buildKeywordLiteral, printRecast, } from "./utilities/recast.js";
23
+ const b = recast.types.builders;
19
24
  const root = process.cwd();
20
25
  const VALID_TARGETS = ["openai", "mcp"];
21
26
  function isValidTarget(target) {
@@ -28,10 +33,208 @@ export function devTarget(target) {
28
33
  invariant(target === undefined || isValidTarget(target), `devTarget '${target}' is not a valid dev target. Must be one of ${VALID_TARGETS.join(", ")}.`);
29
34
  return target;
30
35
  }
36
+ function md5(contents) {
37
+ return createHash("md5").update(contents).digest("hex");
38
+ }
39
+ /**
40
+ * Builds the AST for the comment used at the top of a generated file from this
41
+ * plugin.
42
+ *
43
+ * @remarks
44
+ * This comment informs users that generated files from this plugin should not
45
+ * be edited since it is autogenerated by this plugin.
46
+ */
47
+ function buildHeaderComment() {
48
+ return b.commentLine(" This file is auto-generated by @apollo/client-ai-apps. Do not edit manually.", true, false);
49
+ }
50
+ /**
51
+ * Builds the `@apollo/client-ai-apps` ambient module declaration for the
52
+ * `register.d.ts` file.
53
+ *
54
+ * @param registerInterfaceBody - The interface body for the `Register`
55
+ * interface
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * buildAmbientModuleDeclaration(interfaceBody);
60
+ * // =>
61
+ * // declare namespace "@apollo/client-ai-apps" {
62
+ * // interface Register {
63
+ * // // ...interfaceBody
64
+ * // }
65
+ * // }
66
+ * ```
67
+ */
68
+ function buildAmbientModuleDeclaration(registerInterfaceBody) {
69
+ const interfaceDeclaration = b.tsInterfaceDeclaration(b.identifier("Register"), b.tsInterfaceBody(registerInterfaceBody));
70
+ const moduleDeclaration = b.tsModuleDeclaration(b.stringLiteral("@apollo/client-ai-apps"), b.tsModuleBlock([interfaceDeclaration]));
71
+ moduleDeclaration.declare = true;
72
+ return moduleDeclaration;
73
+ }
74
+ /**
75
+ * Gets the variables type name for an operation. GraphQL Codegen creates
76
+ * variables types with the combination of operationName + operationType +
77
+ * "Variables"
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * getVariablesTypeName({ name: "GetProduct", type: "query" });
82
+ * // => "GetProductQueryVariables"
83
+ * ````
84
+ */
85
+ function getVariablesTypeName(operation) {
86
+ const { name, type } = operation;
87
+ return `${name}${type.charAt(0).toUpperCase()}${type.slice(1)}Variables`;
88
+ }
89
+ /**
90
+ * Returns the code string written to the `.apollo-client-ai-apps/types/register.d.ts` files
91
+ * for a given set of operations.
92
+ */
93
+ function getRegisteredTypeContents({ operations, schema, flagSchemaBuildError = false, }) {
94
+ if (flagSchemaBuildError) {
95
+ const message = `[@apollo/client-ai-apps/vite]: There was an error building generated types. See the vite build output for more details.\n\n${flagSchemaBuildError.message}`;
96
+ const importBaseStatement = buildImportStatement([], "@apollo/client-ai-apps");
97
+ importBaseStatement.comments = [buildHeaderComment()];
98
+ const typeAnnotation = b.tsLiteralType(b.stringLiteral(message));
99
+ return printRecast(b.program([
100
+ importBaseStatement,
101
+ buildAmbientModuleDeclaration([
102
+ buildPropertySignature("toolName", typeAnnotation),
103
+ buildPropertySignature("toolInputs", b.tsTypeLiteral([buildPropertySignature(message, typeAnnotation)])),
104
+ ]),
105
+ ]));
106
+ }
107
+ const toolNames = operations.flatMap((op) => op.tools.map((t) => t.name));
108
+ if (toolNames.length === 0) {
109
+ const emptyExport = b.exportNamedDeclaration(null);
110
+ emptyExport.comments = [buildHeaderComment()];
111
+ return printRecast(b.program([emptyExport]));
112
+ }
113
+ const importBaseStatement = buildImportStatement([], "@apollo/client-ai-apps");
114
+ importBaseStatement.comments = [buildHeaderComment()];
115
+ const toolNameProp = buildPropertySignature("toolName", b.tsUnionType(toolNames.map((n) => b.tsLiteralType(b.stringLiteral(n)))));
116
+ if (!schema) {
117
+ return printRecast(b.program([
118
+ importBaseStatement,
119
+ buildAmbientModuleDeclaration([toolNameProp]),
120
+ ]));
121
+ }
122
+ const importedVariableTypes = new Set();
123
+ const toolInputsValue = [];
124
+ for (const operation of operations) {
125
+ const variablesTypeName = getVariablesTypeName(operation);
126
+ if (operation.tools.length) {
127
+ importedVariableTypes.add(variablesTypeName);
128
+ }
129
+ for (const tool of operation.tools) {
130
+ const variablesTypeRef = b.tsTypeReference(b.identifier(variablesTypeName));
131
+ let typeExpression = variablesTypeRef;
132
+ if (tool.extraInputs?.length) {
133
+ const extraInputsType = tool.extraInputs.map((ei) => {
134
+ return buildPropertySignature(ei.name, buildKeywordLiteral(ei.type), true);
135
+ });
136
+ typeExpression = b.tsIntersectionType([
137
+ variablesTypeRef,
138
+ b.tsTypeLiteral(extraInputsType),
139
+ ]);
140
+ }
141
+ toolInputsValue.push(buildPropertySignature(tool.name, typeExpression));
142
+ }
143
+ }
144
+ return printRecast(b.program([
145
+ importBaseStatement,
146
+ buildImportStatement(Array.from(importedVariableTypes), "./operation-types.js", "type"),
147
+ buildAmbientModuleDeclaration([
148
+ toolNameProp,
149
+ buildPropertySignature("toolInputs", b.tsTypeLiteral(toolInputsValue)),
150
+ ]),
151
+ ]));
152
+ }
153
+ /**
154
+ * Generates and returns the code string written to the
155
+ * `.apollo-client-ai-apps/types/operation-types.d.ts` file. Uses GraphQL
156
+ * Codegen to introspect the given schema and extract variable types.
157
+ */
158
+ async function generateOperationTypes(schema, documents) {
159
+ const { generate } = await import("@graphql-codegen/cli");
160
+ if (documents.length === 0) {
161
+ return `// Auto-generated by @apollo/client-ai-apps. Do not edit manually.\nexport {};\n`;
162
+ }
163
+ const output = await generate({
164
+ schema,
165
+ documents,
166
+ generates: {
167
+ "operation-types.d.ts": {
168
+ plugins: ["typescript", "typescript-operations"],
169
+ config: {
170
+ nonOptionalTypename: true,
171
+ skipTypeNameForRoot: true,
172
+ },
173
+ },
174
+ },
175
+ silent: true,
176
+ }, false);
177
+ const content = output[0]
178
+ .content;
179
+ return `// Auto-generated by @apollo/client-ai-apps. Do not edit manually.\n${content}`;
180
+ }
181
+ /**
182
+ * Gets the name of an exported type (e.g. `export type x`) from a TypeScript
183
+ * AST statement.
184
+ */
185
+ function getExportedTypeAliasName(statement) {
186
+ if (statement.type !== "ExportNamedDeclaration")
187
+ return;
188
+ const declaration = statement.declaration;
189
+ if (!declaration || declaration.type !== "TSTypeAliasDeclaration")
190
+ return;
191
+ const alias = declaration;
192
+ return typeof alias.id.name === "string" ? alias.id.name : undefined;
193
+ }
194
+ /**
195
+ * Removes all non-`*Variables` types from the `operation-types.d.ts` file.
196
+ * The `typescript` and `typescript-operations` codegen plugins add full schema
197
+ * types and operation types. This function ensures those unused types are
198
+ * removed.
199
+ */
200
+ function filterOperationTypes(content, rootTypeNames) {
201
+ const ast = recast.parse(content, { parser: typescriptParser });
202
+ const statements = ast.program.body;
203
+ const typeMap = new Map();
204
+ for (const statement of statements) {
205
+ const name = getExportedTypeAliasName(statement);
206
+ if (name) {
207
+ typeMap.set(name, statement
208
+ .declaration);
209
+ }
210
+ }
211
+ const reachable = new Set(rootTypeNames);
212
+ const queue = Array.from(rootTypeNames);
213
+ while (queue.length > 0) {
214
+ const name = queue.shift();
215
+ const node = typeMap.get(name);
216
+ if (!node)
217
+ continue;
218
+ recast.visit(node, {
219
+ visitTSTypeReference(path) {
220
+ const typeName = path.value.typeName?.name;
221
+ if (typeName && !reachable.has(typeName)) {
222
+ reachable.add(typeName);
223
+ queue.push(typeName);
224
+ }
225
+ this.traverse(path);
226
+ },
227
+ });
228
+ }
229
+ ast.program.body = statements.filter((statement) => {
230
+ const name = getExportedTypeAliasName(statement);
231
+ return name === undefined || reachable.has(name);
232
+ });
233
+ return printRecast(ast);
234
+ }
31
235
  export function apolloClientAiApps(options) {
32
236
  const targets = Array.from(new Set(options.targets));
33
- const { devTarget = targets.length === 1 ? targets[0] : undefined, appsOutDir, } = options;
34
- const cache = new Map();
237
+ const { devTarget = targets.length === 1 ? targets[0] : undefined, appsOutDir, schema, } = options;
35
238
  let config;
36
239
  const fragments = createFragmentRegistry();
37
240
  invariant(Array.isArray(targets) && targets.length > 0, "The `targets` option must be a non-empty array");
@@ -41,30 +244,15 @@ export function apolloClientAiApps(options) {
41
244
  cache: new InMemoryCache({ fragments }),
42
245
  link: processQueryLink,
43
246
  });
44
- async function processFile(file) {
45
- const code = fs.readFileSync(file, "utf-8");
46
- if (!code.includes("gql"))
47
- return;
48
- const fileHash = createHash("md5").update(code).digest("hex");
49
- if (cache.get(file)?.hash === fileHash)
50
- return;
51
- const sources = gqlPluckFromCodeStringSync(file, code, {
52
- modules: [
53
- { name: "graphql-tag", identifier: "gql" },
54
- { name: "@apollo/client", identifier: "gql" },
55
- ],
56
- }).map((source) => parse(source.body));
57
- fragments.register(...sources);
58
- cache.set(file, {
59
- file: file,
60
- hash: fileHash,
61
- sources,
62
- });
247
+ let sources = [];
248
+ function recomputeSources(cache) {
249
+ sources = Array.from(cache.values()).flatMap((entry) => entry.sources);
63
250
  }
64
- async function generateManifest(environment) {
65
- const appsConfig = await getAppsConfig();
66
- const sources = Array.from(cache.values()).flatMap((entry) => entry.sources);
67
- const operations = [];
251
+ async function getManifestOperations() {
252
+ if (sources === getManifestOperations.cache.sources) {
253
+ return getManifestOperations.cache.manifestOperations;
254
+ }
255
+ const manifestOperations = [];
68
256
  for (const source of sources) {
69
257
  const operationDef = source.definitions.find((d) => d.kind === Kind.OPERATION_DEFINITION);
70
258
  if (!operationDef)
@@ -75,7 +263,7 @@ export function apolloClientAiApps(options) {
75
263
  query: source,
76
264
  fetchPolicy: "no-cache",
77
265
  });
78
- operations.push(result.data);
266
+ manifestOperations.push(result.data);
79
267
  break;
80
268
  }
81
269
  case OperationTypeNode.MUTATION: {
@@ -83,13 +271,46 @@ export function apolloClientAiApps(options) {
83
271
  mutation: source,
84
272
  fetchPolicy: "no-cache",
85
273
  });
86
- operations.push(result.data);
274
+ manifestOperations.push(result.data);
87
275
  break;
88
276
  }
89
277
  default:
90
278
  throw new Error(`Found unsupported operation type '${operationDef.operation}'. Only queries and mutations are supported.`);
91
279
  }
92
280
  }
281
+ getManifestOperations.cache = { sources, manifestOperations };
282
+ return manifestOperations;
283
+ }
284
+ getManifestOperations.cache = {
285
+ sources,
286
+ manifestOperations: [],
287
+ };
288
+ async function processFile(file) {
289
+ const code = fs.readFileSync(file, "utf-8");
290
+ if (!code.includes("gql"))
291
+ return;
292
+ const fileHash = md5(code);
293
+ if (processFile.cache.get(file)?.hash === fileHash)
294
+ return;
295
+ const sources = gqlPluckFromCodeStringSync(file, code, {
296
+ modules: [
297
+ { name: "graphql-tag", identifier: "gql" },
298
+ { name: "@apollo/client", identifier: "gql" },
299
+ ],
300
+ }).map((source) => parse(source.body));
301
+ const previousSources = processFile.cache.get(file)?.sources;
302
+ if (previousSources && equal(sources, previousSources)) {
303
+ processFile.cache.set(file, { hash: fileHash, sources: previousSources });
304
+ return;
305
+ }
306
+ fragments.register(...sources);
307
+ processFile.cache.set(file, { hash: fileHash, sources });
308
+ recomputeSources(processFile.cache);
309
+ }
310
+ processFile.cache = new Map();
311
+ async function generateManifest() {
312
+ const appsConfig = await getAppsConfig();
313
+ const operations = await getManifestOperations();
93
314
  invariant(operations.filter((o) => o.prefetch).length <= 1, "Found multiple operations marked as `@prefetch`. You can only mark 1 operation with `@prefetch`.");
94
315
  function getBuildResourceForTarget(target) {
95
316
  const entryPoint = getResourceFromConfig(appsConfig, config.mode, target);
@@ -137,14 +358,54 @@ export function apolloClientAiApps(options) {
137
358
  if (isNonEmptyObject(appsConfig.labels)) {
138
359
  manifest.labels = appsConfig.labels;
139
360
  }
140
- const outDir = path.join(appsOutDir, appName);
361
+ const manifestContents = JSON.stringify(manifest);
141
362
  // Always write to build directory so the MCP server picks it up
142
- const dest = path.resolve(root, outDir, ".application-manifest.json");
143
- fs.mkdirSync(path.dirname(dest), { recursive: true });
144
- fs.writeFileSync(dest, JSON.stringify(manifest));
363
+ writeFileSync(path.resolve(root, appsOutDir, appName, ".application-manifest.json"), manifestContents);
145
364
  // Always write to the dev location so that the app can bundle the manifest content
146
- fs.writeFileSync(".application-manifest.json", JSON.stringify(manifest));
365
+ writeFileSync(".application-manifest.json", manifestContents);
366
+ const manifestTypesFilepath = ".application-manifest.d.json.ts";
367
+ if (!fs.existsSync(manifestTypesFilepath)) {
368
+ const manifestImport = b.importDeclaration([b.importSpecifier(b.identifier("ApplicationManifest"))], b.stringLiteral("@apollo/client-ai-apps"), "type");
369
+ const manifestId = b.identifier("manifest");
370
+ manifestId.typeAnnotation = b.tsTypeAnnotation(b.tsTypeReference(b.identifier("ApplicationManifest"), null));
371
+ const manifestDeclaration = b.variableDeclaration("const", [
372
+ b.variableDeclarator(manifestId, null),
373
+ ]);
374
+ manifestDeclaration.declare = true;
375
+ const exportDefault = b.exportDefaultDeclaration(b.identifier("manifest"));
376
+ const content = printRecast(b.program([manifestImport, manifestDeclaration, exportDefault]));
377
+ writeFileSync(manifestTypesFilepath, content);
378
+ }
379
+ }
380
+ async function generateTypesFiles() {
381
+ let flagSchemaBuildError = false;
382
+ const operations = await getManifestOperations();
383
+ if (operations === generateTypesFiles.cache) {
384
+ return;
385
+ }
386
+ generateTypesFiles.cache = operations;
387
+ if (schema) {
388
+ try {
389
+ const opTypesContent = await generateOperationTypes(schema, operations.map((op) => op.body));
390
+ const rootTypeNames = new Set(operations.flatMap((op) => op.tools.length > 0 ? [getVariablesTypeName(op)] : []));
391
+ writeFileSync(path.resolve(root, ".apollo-client-ai-apps/types/operation-types.d.ts"), filterOperationTypes(opTypesContent, rootTypeNames), { cache: true });
392
+ }
393
+ catch (e) {
394
+ if (config.command === "build") {
395
+ throw e;
396
+ }
397
+ flagSchemaBuildError = e;
398
+ console.error("[@apollo/client-ai-apps/vite]:", e);
399
+ }
400
+ }
401
+ const typesFileContents = getRegisteredTypeContents({
402
+ operations,
403
+ schema,
404
+ flagSchemaBuildError,
405
+ });
406
+ writeFileSync(path.resolve(root, ".apollo-client-ai-apps/types/register.d.ts"), typesFileContents, { cache: true });
147
407
  }
408
+ generateTypesFiles.cache = [];
148
409
  return {
149
410
  name: "@apollo/client-ai-apps/vite",
150
411
  async buildStart() {
@@ -156,13 +417,13 @@ export function apolloClientAiApps(options) {
156
417
  }
157
418
  // We don't want to do this here on builds cause it just gets overwritten anyways. We'll call it on writeBundle instead.
158
419
  if (config.command === "serve") {
159
- await generateManifest(this.environment);
420
+ await Promise.all([generateManifest(), generateTypesFiles()]);
160
421
  }
161
422
  },
162
423
  configResolved(resolvedConfig) {
163
424
  config = resolvedConfig;
164
425
  },
165
- async configEnvironment(name, { build }) {
426
+ async configEnvironment(name) {
166
427
  if (!targets.includes(name))
167
428
  return;
168
429
  const appsConfig = await getAppsConfig();
@@ -186,7 +447,7 @@ export function apolloClientAiApps(options) {
186
447
  }
187
448
  else if (file.match(/\.(jsx?|tsx?)$/)) {
188
449
  await processFile(file);
189
- await generateManifest();
450
+ await Promise.all([generateManifest(), generateTypesFiles()]);
190
451
  }
191
452
  });
192
453
  },
@@ -238,12 +499,12 @@ export function apolloClientAiApps(options) {
238
499
  .replace(/(src=["'])\/([^"']+)/gi, `$1${baseUrl}/$2`));
239
500
  },
240
501
  async writeBundle() {
241
- await generateManifest(this.environment);
502
+ await Promise.all([generateManifest(), generateTypesFiles()]);
242
503
  },
243
504
  };
244
505
  }
245
506
  const processQueryLink = new ApolloLink((operation) => {
246
- const body = print(removeManifestDirectives(sortTopLevelDefinitions(operation.query)));
507
+ const body = print(removeOperationDescription(removeManifestDirectives(sortTopLevelDefinitions(operation.query))));
247
508
  const name = operation.operationName;
248
509
  const definition = operation.query.definitions.find((d) => d.kind === "OperationDefinition");
249
510
  // Use `operation.query` so that the error reflects the end-user defined
@@ -258,12 +519,31 @@ const processQueryLink = new ApolloLink((operation) => {
258
519
  const id = createHash("sha256").update(body).digest("hex");
259
520
  // TODO: For now, you can only have 1 operation marked as prefetch. In the future, we'll likely support more than 1, and the "prefetchId" will be defined on the `@prefetch` itself as an argument
260
521
  const prefetchID = prefetch ? "__anonymous" : undefined;
261
- const tools = directives
262
- ?.filter((d) => d.name.value === "tool")
263
- .map((directive) => {
522
+ const toolDirectives = directives?.filter((d) => d.name.value === "tool") ?? [];
523
+ const tools = toolDirectives.map((directive) => {
524
+ const nameArg = getDirectiveArgument("name", directive);
525
+ const descriptionArg = getDirectiveArgument("description", directive);
526
+ let name;
527
+ if (nameArg) {
528
+ name = getArgumentValue(nameArg, Kind.STRING);
529
+ }
530
+ else {
531
+ invariant(toolDirectives.length === 1, `Operations with multiple @tool directives must provide a 'name' argument on each @tool`);
532
+ invariant(definition.name?.value, `Anonymous operations cannot use @tool without providing a 'name' argument`);
533
+ name = definition.name.value;
534
+ }
535
+ let description;
536
+ if (descriptionArg) {
537
+ description = getArgumentValue(descriptionArg, Kind.STRING);
538
+ }
539
+ else {
540
+ invariant(toolDirectives.length === 1, `Operations with multiple @tool directives must provide a 'description' argument on each @tool`);
541
+ invariant(definition.description?.value, `Operations using @tool without a 'description' argument must have a description on the operation definition`);
542
+ description = definition.description.value;
543
+ }
264
544
  const result = ToolDirectiveSchema.safeParse({
265
- name: getArgumentValue(getDirectiveArgument("name", directive, { required: true }), Kind.STRING),
266
- description: getArgumentValue(getDirectiveArgument("description", directive, { required: true }), Kind.STRING),
545
+ name,
546
+ description,
267
547
  extraInputs: maybeGetArgumentValue(getDirectiveArgument("extraInputs", directive), Kind.LIST),
268
548
  labels: maybeGetArgumentValue(getDirectiveArgument("labels", directive), Kind.OBJECT),
269
549
  });
@@ -278,6 +558,13 @@ const processQueryLink = new ApolloLink((operation) => {
278
558
  data: { id, name, type, body, variables, prefetch, prefetchID, tools },
279
559
  });
280
560
  });
561
+ function removeOperationDescription(doc) {
562
+ return visit(doc, {
563
+ OperationDefinition(node) {
564
+ return { ...node, description: undefined };
565
+ },
566
+ });
567
+ }
281
568
  function removeManifestDirectives(doc) {
282
569
  return removeDirectivesFromDocument([{ name: "prefetch" }, { name: "tool" }], doc);
283
570
  }
@@ -352,6 +639,22 @@ readPackageJson.cache = undefined;
352
639
  readPackageJson.resetCache = () => {
353
640
  readPackageJson.cache = undefined;
354
641
  };
642
+ function writeFileSync(filepath, content, options = {}) {
643
+ function writeFile() {
644
+ fs.mkdirSync(path.dirname(filepath), { recursive: true });
645
+ fs.writeFileSync(filepath, content, "utf-8");
646
+ }
647
+ if (!options.cache) {
648
+ return writeFile();
649
+ }
650
+ const hash = md5(content);
651
+ const cachedHash = writeFileSync.cache.get(filepath);
652
+ if (hash !== cachedHash || !fs.existsSync(filepath)) {
653
+ writeFileSync.cache.set(filepath, hash);
654
+ writeFile();
655
+ }
656
+ }
657
+ writeFileSync.cache = new Map();
355
658
  const ToolDirectiveSchema = z.strictObject({
356
659
  name: z.stringFormat("toolName", (value) => value.indexOf(" ") === -1, {
357
660
  error: (iss) => `Tool with name "${iss.input}" must not contain spaces`,