@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.
- package/CHANGELOG.md +131 -0
- package/dist/core/typeRegistration.d.ts +33 -0
- package/dist/core/typeRegistration.d.ts.map +1 -0
- package/dist/core/typeRegistration.js +2 -0
- package/dist/core/typeRegistration.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/react/hooks/useToolInfo.d.ts +3 -0
- package/dist/mcp/react/hooks/useToolInfo.d.ts.map +1 -0
- package/dist/mcp/react/hooks/useToolInfo.js +10 -0
- package/dist/mcp/react/hooks/useToolInfo.js.map +1 -0
- package/dist/mcp/react/hooks/useToolInput.d.ts +6 -1
- package/dist/mcp/react/hooks/useToolInput.d.ts.map +1 -1
- package/dist/mcp/react/hooks/useToolInput.js +4 -0
- package/dist/mcp/react/hooks/useToolInput.js.map +1 -1
- package/dist/mcp/react/hooks/useToolName.d.ts +6 -1
- package/dist/mcp/react/hooks/useToolName.d.ts.map +1 -1
- package/dist/mcp/react/hooks/useToolName.js +4 -0
- package/dist/mcp/react/hooks/useToolName.js.map +1 -1
- 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/react/hooks/useToolInfo.d.ts +3 -0
- package/dist/openai/react/hooks/useToolInfo.d.ts.map +1 -0
- package/dist/openai/react/hooks/useToolInfo.js +10 -0
- package/dist/openai/react/hooks/useToolInfo.js.map +1 -0
- package/dist/openai/react/hooks/useToolInput.d.ts +6 -1
- package/dist/openai/react/hooks/useToolInput.d.ts.map +1 -1
- package/dist/openai/react/hooks/useToolInput.js +4 -0
- package/dist/openai/react/hooks/useToolInput.js.map +1 -1
- package/dist/openai/react/hooks/useToolName.d.ts +6 -1
- package/dist/openai/react/hooks/useToolName.d.ts.map +1 -1
- package/dist/openai/react/hooks/useToolName.js +4 -0
- package/dist/openai/react/hooks/useToolName.js.map +1 -1
- 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/index.d.ts +9 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +9 -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/tsconfig/core/tsconfig.json +2 -0
- package/dist/tsconfig/mcp/tsconfig.json +2 -0
- package/dist/tsconfig/openai/tsconfig.json +2 -0
- package/dist/vite/apolloClientAiApps.d.ts +1 -0
- package/dist/vite/apolloClientAiApps.d.ts.map +1 -1
- package/dist/vite/apolloClientAiApps.js +346 -43
- package/dist/vite/apolloClientAiApps.js.map +1 -1
- package/dist/vite/utilities/recast.d.ts +54 -0
- package/dist/vite/utilities/recast.d.ts.map +1 -0
- package/dist/vite/utilities/recast.js +71 -0
- package/dist/vite/utilities/recast.js.map +1 -0
- package/package.json +7 -3
- package/src/core/typeRegistration.ts +32 -0
- package/src/index.ts +7 -0
- package/src/mcp/react/hooks/__tests__/useToolInfo.test.tsx +53 -0
- package/src/mcp/react/hooks/useToolInfo.ts +13 -0
- package/src/mcp/react/hooks/useToolInput.ts +6 -1
- package/src/mcp/react/hooks/useToolName.ts +6 -1
- package/src/mcp/react/index.ts +1 -0
- package/src/openai/react/hooks/__tests__/useToolInfo.test.tsx +92 -0
- package/src/openai/react/hooks/useToolInfo.ts +13 -0
- package/src/openai/react/hooks/useToolInput.ts +6 -1
- package/src/openai/react/hooks/useToolName.ts +6 -1
- package/src/openai/react/index.ts +1 -0
- package/src/react/index.mcp.ts +1 -0
- package/src/react/index.openai.ts +1 -0
- package/src/react/index.ts +11 -0
- package/src/tsconfig/core/tsconfig.json +2 -0
- package/src/tsconfig/mcp/tsconfig.json +2 -0
- package/src/tsconfig/openai/tsconfig.json +2 -0
- package/src/vite/__tests__/apolloClientAiApps.test.ts +754 -6
- package/src/vite/apolloClientAiApps.ts +564 -68
- package/src/vite/utilities/recast.ts +100 -0
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defaultClientConditions,
|
|
3
|
-
type Environment,
|
|
4
3
|
type Plugin,
|
|
5
4
|
type ResolvedConfig,
|
|
6
5
|
} from "vite";
|
|
7
6
|
import { createHash } from "node:crypto";
|
|
8
7
|
import path from "node:path";
|
|
9
8
|
import fs from "node:fs";
|
|
9
|
+
import * as recast from "recast";
|
|
10
|
+
import typescriptParser from "recast/parsers/typescript.js";
|
|
10
11
|
import { ApolloClient, ApolloLink, type DocumentNode } from "@apollo/client";
|
|
11
12
|
import { InMemoryCache } from "@apollo/client";
|
|
13
|
+
import { equal } from "@wry/equality";
|
|
12
14
|
import { gqlPluckFromCodeStringSync } from "@graphql-tools/graphql-tag-pluck";
|
|
13
15
|
import { glob } from "glob";
|
|
14
16
|
import { print } from "@apollo/client/utilities";
|
|
15
17
|
import { removeDirectivesFromDocument } from "@apollo/client/utilities/internal";
|
|
16
18
|
import { of } from "rxjs";
|
|
17
|
-
import { Kind, OperationTypeNode, parse } from "graphql";
|
|
19
|
+
import { Kind, OperationTypeNode, parse, visit } from "graphql";
|
|
18
20
|
import {
|
|
19
21
|
getArgumentValue,
|
|
20
22
|
getDirectiveArgument,
|
|
@@ -31,6 +33,16 @@ import type { ApolloClientAiAppsConfig } from "../config/index.js";
|
|
|
31
33
|
import { ApolloClientAiAppsConfigSchema } from "../config/schema.js";
|
|
32
34
|
import { z } from "zod";
|
|
33
35
|
import { createFragmentRegistry } from "@apollo/client/cache";
|
|
36
|
+
import {
|
|
37
|
+
buildImportStatement,
|
|
38
|
+
buildPropertySignature,
|
|
39
|
+
buildKeywordLiteral,
|
|
40
|
+
printRecast,
|
|
41
|
+
type TSInterfaceBody,
|
|
42
|
+
} from "./utilities/recast.js";
|
|
43
|
+
import type { TypeScriptDocumentsPluginConfig } from "@graphql-codegen/typescript-operations";
|
|
44
|
+
|
|
45
|
+
const b = recast.types.builders;
|
|
34
46
|
|
|
35
47
|
export declare namespace apolloClientAiApps {
|
|
36
48
|
export type Target = ApolloClientAiAppsConfig.AppTarget;
|
|
@@ -39,6 +51,7 @@ export declare namespace apolloClientAiApps {
|
|
|
39
51
|
targets: Target[];
|
|
40
52
|
devTarget?: Target | undefined;
|
|
41
53
|
appsOutDir: string;
|
|
54
|
+
schema?: string | undefined;
|
|
42
55
|
}
|
|
43
56
|
}
|
|
44
57
|
|
|
@@ -66,11 +79,322 @@ export function devTarget(target: string | undefined) {
|
|
|
66
79
|
}
|
|
67
80
|
|
|
68
81
|
interface FileCache {
|
|
69
|
-
file: string;
|
|
70
82
|
hash: string;
|
|
71
83
|
sources: DocumentNode[];
|
|
72
84
|
}
|
|
73
85
|
|
|
86
|
+
function md5(contents: string) {
|
|
87
|
+
return createHash("md5").update(contents).digest("hex");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Builds the AST for the comment used at the top of a generated file from this
|
|
92
|
+
* plugin.
|
|
93
|
+
*
|
|
94
|
+
* @remarks
|
|
95
|
+
* This comment informs users that generated files from this plugin should not
|
|
96
|
+
* be edited since it is autogenerated by this plugin.
|
|
97
|
+
*/
|
|
98
|
+
function buildHeaderComment() {
|
|
99
|
+
return b.commentLine(
|
|
100
|
+
" This file is auto-generated by @apollo/client-ai-apps. Do not edit manually.",
|
|
101
|
+
true,
|
|
102
|
+
false
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Builds the `@apollo/client-ai-apps` ambient module declaration for the
|
|
108
|
+
* `register.d.ts` file.
|
|
109
|
+
*
|
|
110
|
+
* @param registerInterfaceBody - The interface body for the `Register`
|
|
111
|
+
* interface
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* buildAmbientModuleDeclaration(interfaceBody);
|
|
116
|
+
* // =>
|
|
117
|
+
* // declare namespace "@apollo/client-ai-apps" {
|
|
118
|
+
* // interface Register {
|
|
119
|
+
* // // ...interfaceBody
|
|
120
|
+
* // }
|
|
121
|
+
* // }
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
function buildAmbientModuleDeclaration(registerInterfaceBody: TSInterfaceBody) {
|
|
125
|
+
const interfaceDeclaration = b.tsInterfaceDeclaration(
|
|
126
|
+
b.identifier("Register"),
|
|
127
|
+
b.tsInterfaceBody(registerInterfaceBody)
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const moduleDeclaration = b.tsModuleDeclaration(
|
|
131
|
+
b.stringLiteral("@apollo/client-ai-apps"),
|
|
132
|
+
b.tsModuleBlock([interfaceDeclaration])
|
|
133
|
+
);
|
|
134
|
+
moduleDeclaration.declare = true;
|
|
135
|
+
|
|
136
|
+
return moduleDeclaration;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Gets the variables type name for an operation. GraphQL Codegen creates
|
|
141
|
+
* variables types with the combination of operationName + operationType +
|
|
142
|
+
* "Variables"
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```ts
|
|
146
|
+
* getVariablesTypeName({ name: "GetProduct", type: "query" });
|
|
147
|
+
* // => "GetProductQueryVariables"
|
|
148
|
+
* ````
|
|
149
|
+
*/
|
|
150
|
+
function getVariablesTypeName(operation: ManifestOperation) {
|
|
151
|
+
const { name, type } = operation;
|
|
152
|
+
|
|
153
|
+
return `${name}${type.charAt(0).toUpperCase()}${type.slice(1)}Variables`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Returns the code string written to the `.apollo-client-ai-apps/types/register.d.ts` files
|
|
158
|
+
* for a given set of operations.
|
|
159
|
+
*/
|
|
160
|
+
function getRegisteredTypeContents({
|
|
161
|
+
operations,
|
|
162
|
+
schema,
|
|
163
|
+
flagSchemaBuildError = false,
|
|
164
|
+
}: {
|
|
165
|
+
operations: ManifestOperation[];
|
|
166
|
+
schema: string | undefined;
|
|
167
|
+
/**
|
|
168
|
+
* Used during dev to avoid failing the build and instead output the error
|
|
169
|
+
* to the console. When providing an error, the error message is generated as
|
|
170
|
+
* a literal type value in the `register.d.ts` file so that users can see the
|
|
171
|
+
* error message when hovering over the type.
|
|
172
|
+
*/
|
|
173
|
+
flagSchemaBuildError?: false | Error;
|
|
174
|
+
}) {
|
|
175
|
+
if (flagSchemaBuildError) {
|
|
176
|
+
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}`;
|
|
177
|
+
|
|
178
|
+
const importBaseStatement = buildImportStatement(
|
|
179
|
+
[],
|
|
180
|
+
"@apollo/client-ai-apps"
|
|
181
|
+
);
|
|
182
|
+
importBaseStatement.comments = [buildHeaderComment()];
|
|
183
|
+
|
|
184
|
+
const typeAnnotation = b.tsLiteralType(b.stringLiteral(message));
|
|
185
|
+
|
|
186
|
+
return printRecast(
|
|
187
|
+
b.program([
|
|
188
|
+
importBaseStatement,
|
|
189
|
+
buildAmbientModuleDeclaration([
|
|
190
|
+
buildPropertySignature("toolName", typeAnnotation),
|
|
191
|
+
buildPropertySignature(
|
|
192
|
+
"toolInputs",
|
|
193
|
+
b.tsTypeLiteral([buildPropertySignature(message, typeAnnotation)])
|
|
194
|
+
),
|
|
195
|
+
]),
|
|
196
|
+
])
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const toolNames = operations.flatMap((op) => op.tools.map((t) => t.name));
|
|
201
|
+
|
|
202
|
+
if (toolNames.length === 0) {
|
|
203
|
+
const emptyExport = b.exportNamedDeclaration(null);
|
|
204
|
+
emptyExport.comments = [buildHeaderComment()];
|
|
205
|
+
return printRecast(b.program([emptyExport]));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const importBaseStatement = buildImportStatement(
|
|
209
|
+
[],
|
|
210
|
+
"@apollo/client-ai-apps"
|
|
211
|
+
);
|
|
212
|
+
importBaseStatement.comments = [buildHeaderComment()];
|
|
213
|
+
|
|
214
|
+
const toolNameProp = buildPropertySignature(
|
|
215
|
+
"toolName",
|
|
216
|
+
b.tsUnionType(toolNames.map((n) => b.tsLiteralType(b.stringLiteral(n))))
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
if (!schema) {
|
|
220
|
+
return printRecast(
|
|
221
|
+
b.program([
|
|
222
|
+
importBaseStatement,
|
|
223
|
+
buildAmbientModuleDeclaration([toolNameProp]),
|
|
224
|
+
])
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const importedVariableTypes = new Set<string>();
|
|
229
|
+
const toolInputsValue: recast.types.namedTypes.TSPropertySignature[] = [];
|
|
230
|
+
|
|
231
|
+
for (const operation of operations) {
|
|
232
|
+
const variablesTypeName = getVariablesTypeName(operation);
|
|
233
|
+
|
|
234
|
+
if (operation.tools.length) {
|
|
235
|
+
importedVariableTypes.add(variablesTypeName);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
for (const tool of operation.tools) {
|
|
239
|
+
const variablesTypeRef = b.tsTypeReference(
|
|
240
|
+
b.identifier(variablesTypeName)
|
|
241
|
+
);
|
|
242
|
+
let typeExpression:
|
|
243
|
+
| recast.types.namedTypes.TSTypeReference
|
|
244
|
+
| recast.types.namedTypes.TSIntersectionType = variablesTypeRef;
|
|
245
|
+
|
|
246
|
+
if (tool.extraInputs?.length) {
|
|
247
|
+
const extraInputsType = tool.extraInputs.map((ei) => {
|
|
248
|
+
return buildPropertySignature(
|
|
249
|
+
ei.name,
|
|
250
|
+
buildKeywordLiteral(ei.type),
|
|
251
|
+
true
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
typeExpression = b.tsIntersectionType([
|
|
256
|
+
variablesTypeRef,
|
|
257
|
+
b.tsTypeLiteral(extraInputsType),
|
|
258
|
+
]);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
toolInputsValue.push(buildPropertySignature(tool.name, typeExpression));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return printRecast(
|
|
266
|
+
b.program([
|
|
267
|
+
importBaseStatement,
|
|
268
|
+
buildImportStatement(
|
|
269
|
+
Array.from(importedVariableTypes),
|
|
270
|
+
"./operation-types.js",
|
|
271
|
+
"type"
|
|
272
|
+
),
|
|
273
|
+
buildAmbientModuleDeclaration([
|
|
274
|
+
toolNameProp,
|
|
275
|
+
buildPropertySignature("toolInputs", b.tsTypeLiteral(toolInputsValue)),
|
|
276
|
+
]),
|
|
277
|
+
])
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Generates and returns the code string written to the
|
|
283
|
+
* `.apollo-client-ai-apps/types/operation-types.d.ts` file. Uses GraphQL
|
|
284
|
+
* Codegen to introspect the given schema and extract variable types.
|
|
285
|
+
*/
|
|
286
|
+
async function generateOperationTypes(
|
|
287
|
+
schema: string,
|
|
288
|
+
documents: string[]
|
|
289
|
+
): Promise<string> {
|
|
290
|
+
const { generate } = await import("@graphql-codegen/cli");
|
|
291
|
+
|
|
292
|
+
if (documents.length === 0) {
|
|
293
|
+
return `// Auto-generated by @apollo/client-ai-apps. Do not edit manually.\nexport {};\n`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const output = await generate(
|
|
297
|
+
{
|
|
298
|
+
schema,
|
|
299
|
+
documents,
|
|
300
|
+
generates: {
|
|
301
|
+
"operation-types.d.ts": {
|
|
302
|
+
plugins: ["typescript", "typescript-operations"],
|
|
303
|
+
config: {
|
|
304
|
+
nonOptionalTypename: true,
|
|
305
|
+
skipTypeNameForRoot: true,
|
|
306
|
+
} satisfies TypeScriptDocumentsPluginConfig,
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
silent: true,
|
|
310
|
+
} as Parameters<typeof generate>[0],
|
|
311
|
+
false
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const content = (output as { filename: string; content: string }[])[0]
|
|
315
|
+
.content;
|
|
316
|
+
return `// Auto-generated by @apollo/client-ai-apps. Do not edit manually.\n${content}`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Gets the name of an exported type (e.g. `export type x`) from a TypeScript
|
|
321
|
+
* AST statement.
|
|
322
|
+
*/
|
|
323
|
+
function getExportedTypeAliasName(
|
|
324
|
+
statement: recast.types.namedTypes.Statement
|
|
325
|
+
): string | undefined {
|
|
326
|
+
if (statement.type !== "ExportNamedDeclaration") return;
|
|
327
|
+
|
|
328
|
+
const declaration = (
|
|
329
|
+
statement as recast.types.namedTypes.ExportNamedDeclaration
|
|
330
|
+
).declaration;
|
|
331
|
+
|
|
332
|
+
if (!declaration || declaration.type !== "TSTypeAliasDeclaration") return;
|
|
333
|
+
|
|
334
|
+
const alias = declaration as recast.types.namedTypes.TSTypeAliasDeclaration;
|
|
335
|
+
return typeof alias.id.name === "string" ? alias.id.name : undefined;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Removes all non-`*Variables` types from the `operation-types.d.ts` file.
|
|
340
|
+
* The `typescript` and `typescript-operations` codegen plugins add full schema
|
|
341
|
+
* types and operation types. This function ensures those unused types are
|
|
342
|
+
* removed.
|
|
343
|
+
*/
|
|
344
|
+
function filterOperationTypes(
|
|
345
|
+
content: string,
|
|
346
|
+
rootTypeNames: Set<string>
|
|
347
|
+
): string {
|
|
348
|
+
const ast = recast.parse(content, { parser: typescriptParser });
|
|
349
|
+
const statements: recast.types.namedTypes.Statement[] = ast.program.body;
|
|
350
|
+
const typeMap = new Map<
|
|
351
|
+
string,
|
|
352
|
+
recast.types.namedTypes.TSTypeAliasDeclaration
|
|
353
|
+
>();
|
|
354
|
+
|
|
355
|
+
for (const statement of statements) {
|
|
356
|
+
const name = getExportedTypeAliasName(statement);
|
|
357
|
+
|
|
358
|
+
if (name) {
|
|
359
|
+
typeMap.set(
|
|
360
|
+
name,
|
|
361
|
+
(statement as recast.types.namedTypes.ExportNamedDeclaration)
|
|
362
|
+
.declaration as recast.types.namedTypes.TSTypeAliasDeclaration
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const reachable = new Set<string>(rootTypeNames);
|
|
368
|
+
const queue = Array.from(rootTypeNames);
|
|
369
|
+
|
|
370
|
+
while (queue.length > 0) {
|
|
371
|
+
const name = queue.shift()!;
|
|
372
|
+
const node = typeMap.get(name);
|
|
373
|
+
|
|
374
|
+
if (!node) continue;
|
|
375
|
+
|
|
376
|
+
recast.visit(node, {
|
|
377
|
+
visitTSTypeReference(path) {
|
|
378
|
+
const typeName = path.value.typeName?.name as string | undefined;
|
|
379
|
+
|
|
380
|
+
if (typeName && !reachable.has(typeName)) {
|
|
381
|
+
reachable.add(typeName);
|
|
382
|
+
queue.push(typeName);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
this.traverse(path);
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
ast.program.body = statements.filter((statement) => {
|
|
391
|
+
const name = getExportedTypeAliasName(statement);
|
|
392
|
+
return name === undefined || reachable.has(name);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
return printRecast(ast);
|
|
396
|
+
}
|
|
397
|
+
|
|
74
398
|
export function apolloClientAiApps(
|
|
75
399
|
options: apolloClientAiApps.Options
|
|
76
400
|
): Plugin {
|
|
@@ -78,9 +402,8 @@ export function apolloClientAiApps(
|
|
|
78
402
|
const {
|
|
79
403
|
devTarget = targets.length === 1 ? targets[0] : undefined,
|
|
80
404
|
appsOutDir,
|
|
405
|
+
schema,
|
|
81
406
|
} = options;
|
|
82
|
-
const cache = new Map<string, FileCache>();
|
|
83
|
-
|
|
84
407
|
let config!: ResolvedConfig;
|
|
85
408
|
|
|
86
409
|
const fragments = createFragmentRegistry();
|
|
@@ -105,36 +428,18 @@ export function apolloClientAiApps(
|
|
|
105
428
|
link: processQueryLink,
|
|
106
429
|
});
|
|
107
430
|
|
|
108
|
-
|
|
109
|
-
const code = fs.readFileSync(file, "utf-8");
|
|
110
|
-
|
|
111
|
-
if (!code.includes("gql")) return;
|
|
112
|
-
|
|
113
|
-
const fileHash = createHash("md5").update(code).digest("hex");
|
|
114
|
-
if (cache.get(file)?.hash === fileHash) return;
|
|
115
|
-
const sources = gqlPluckFromCodeStringSync(file, code, {
|
|
116
|
-
modules: [
|
|
117
|
-
{ name: "graphql-tag", identifier: "gql" },
|
|
118
|
-
{ name: "@apollo/client", identifier: "gql" },
|
|
119
|
-
],
|
|
120
|
-
}).map((source) => parse(source.body));
|
|
121
|
-
|
|
122
|
-
fragments.register(...sources);
|
|
431
|
+
let sources: DocumentNode[] = [];
|
|
123
432
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
hash: fileHash,
|
|
127
|
-
sources,
|
|
128
|
-
});
|
|
433
|
+
function recomputeSources(cache: Map<string, FileCache>) {
|
|
434
|
+
sources = Array.from(cache.values()).flatMap((entry) => entry.sources);
|
|
129
435
|
}
|
|
130
436
|
|
|
131
|
-
async function
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
);
|
|
437
|
+
async function getManifestOperations() {
|
|
438
|
+
if (sources === getManifestOperations.cache.sources) {
|
|
439
|
+
return getManifestOperations.cache.manifestOperations;
|
|
440
|
+
}
|
|
136
441
|
|
|
137
|
-
const
|
|
442
|
+
const manifestOperations = [];
|
|
138
443
|
for (const source of sources) {
|
|
139
444
|
const operationDef = source.definitions.find(
|
|
140
445
|
(d) => d.kind === Kind.OPERATION_DEFINITION
|
|
@@ -149,7 +454,7 @@ export function apolloClientAiApps(
|
|
|
149
454
|
fetchPolicy: "no-cache",
|
|
150
455
|
});
|
|
151
456
|
|
|
152
|
-
|
|
457
|
+
manifestOperations.push(result.data!);
|
|
153
458
|
break;
|
|
154
459
|
}
|
|
155
460
|
case OperationTypeNode.MUTATION: {
|
|
@@ -158,7 +463,7 @@ export function apolloClientAiApps(
|
|
|
158
463
|
fetchPolicy: "no-cache",
|
|
159
464
|
});
|
|
160
465
|
|
|
161
|
-
|
|
466
|
+
manifestOperations.push(result.data!);
|
|
162
467
|
break;
|
|
163
468
|
}
|
|
164
469
|
default:
|
|
@@ -168,6 +473,50 @@ export function apolloClientAiApps(
|
|
|
168
473
|
}
|
|
169
474
|
}
|
|
170
475
|
|
|
476
|
+
getManifestOperations.cache = { sources, manifestOperations };
|
|
477
|
+
|
|
478
|
+
return manifestOperations;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
getManifestOperations.cache = {
|
|
482
|
+
sources,
|
|
483
|
+
manifestOperations: [] as ManifestOperation[],
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
async function processFile(file: string) {
|
|
487
|
+
const code = fs.readFileSync(file, "utf-8");
|
|
488
|
+
|
|
489
|
+
if (!code.includes("gql")) return;
|
|
490
|
+
|
|
491
|
+
const fileHash = md5(code);
|
|
492
|
+
if (processFile.cache.get(file)?.hash === fileHash) return;
|
|
493
|
+
|
|
494
|
+
const sources = gqlPluckFromCodeStringSync(file, code, {
|
|
495
|
+
modules: [
|
|
496
|
+
{ name: "graphql-tag", identifier: "gql" },
|
|
497
|
+
{ name: "@apollo/client", identifier: "gql" },
|
|
498
|
+
],
|
|
499
|
+
}).map((source) => parse(source.body));
|
|
500
|
+
|
|
501
|
+
const previousSources = processFile.cache.get(file)?.sources;
|
|
502
|
+
|
|
503
|
+
if (previousSources && equal(sources, previousSources)) {
|
|
504
|
+
processFile.cache.set(file, { hash: fileHash, sources: previousSources });
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
fragments.register(...sources);
|
|
509
|
+
|
|
510
|
+
processFile.cache.set(file, { hash: fileHash, sources });
|
|
511
|
+
recomputeSources(processFile.cache);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
processFile.cache = new Map<string, FileCache>();
|
|
515
|
+
|
|
516
|
+
async function generateManifest() {
|
|
517
|
+
const appsConfig = await getAppsConfig();
|
|
518
|
+
const operations = await getManifestOperations();
|
|
519
|
+
|
|
171
520
|
invariant(
|
|
172
521
|
operations.filter((o) => o.prefetch).length <= 1,
|
|
173
522
|
"Found multiple operations marked as `@prefetch`. You can only mark 1 operation with `@prefetch`."
|
|
@@ -235,17 +584,101 @@ export function apolloClientAiApps(
|
|
|
235
584
|
manifest.labels = appsConfig.labels;
|
|
236
585
|
}
|
|
237
586
|
|
|
238
|
-
const
|
|
587
|
+
const manifestContents = JSON.stringify(manifest);
|
|
239
588
|
|
|
240
589
|
// Always write to build directory so the MCP server picks it up
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
590
|
+
writeFileSync(
|
|
591
|
+
path.resolve(root, appsOutDir, appName, ".application-manifest.json"),
|
|
592
|
+
manifestContents
|
|
593
|
+
);
|
|
244
594
|
|
|
245
595
|
// Always write to the dev location so that the app can bundle the manifest content
|
|
246
|
-
|
|
596
|
+
writeFileSync(".application-manifest.json", manifestContents);
|
|
597
|
+
|
|
598
|
+
const manifestTypesFilepath = ".application-manifest.d.json.ts";
|
|
599
|
+
if (!fs.existsSync(manifestTypesFilepath)) {
|
|
600
|
+
const manifestImport = b.importDeclaration(
|
|
601
|
+
[b.importSpecifier(b.identifier("ApplicationManifest"))],
|
|
602
|
+
b.stringLiteral("@apollo/client-ai-apps"),
|
|
603
|
+
"type"
|
|
604
|
+
);
|
|
605
|
+
const manifestId = b.identifier("manifest");
|
|
606
|
+
manifestId.typeAnnotation = b.tsTypeAnnotation(
|
|
607
|
+
b.tsTypeReference(b.identifier("ApplicationManifest"), null)
|
|
608
|
+
);
|
|
609
|
+
const manifestDeclaration = b.variableDeclaration("const", [
|
|
610
|
+
b.variableDeclarator(manifestId, null),
|
|
611
|
+
]) as recast.types.namedTypes.VariableDeclaration & { declare: boolean };
|
|
612
|
+
manifestDeclaration.declare = true;
|
|
613
|
+
|
|
614
|
+
const exportDefault = b.exportDefaultDeclaration(
|
|
615
|
+
b.identifier("manifest")
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
const content = printRecast(
|
|
619
|
+
b.program([manifestImport, manifestDeclaration, exportDefault])
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
writeFileSync(manifestTypesFilepath, content);
|
|
623
|
+
}
|
|
247
624
|
}
|
|
248
625
|
|
|
626
|
+
async function generateTypesFiles() {
|
|
627
|
+
let flagSchemaBuildError: false | Error = false;
|
|
628
|
+
const operations = await getManifestOperations();
|
|
629
|
+
|
|
630
|
+
if (operations === generateTypesFiles.cache) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
generateTypesFiles.cache = operations;
|
|
635
|
+
|
|
636
|
+
if (schema) {
|
|
637
|
+
try {
|
|
638
|
+
const opTypesContent = await generateOperationTypes(
|
|
639
|
+
schema,
|
|
640
|
+
operations.map((op) => op.body)
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
const rootTypeNames = new Set(
|
|
644
|
+
operations.flatMap((op) =>
|
|
645
|
+
op.tools.length > 0 ? [getVariablesTypeName(op)] : []
|
|
646
|
+
)
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
writeFileSync(
|
|
650
|
+
path.resolve(
|
|
651
|
+
root,
|
|
652
|
+
".apollo-client-ai-apps/types/operation-types.d.ts"
|
|
653
|
+
),
|
|
654
|
+
filterOperationTypes(opTypesContent, rootTypeNames),
|
|
655
|
+
{ cache: true }
|
|
656
|
+
);
|
|
657
|
+
} catch (e) {
|
|
658
|
+
if (config.command === "build") {
|
|
659
|
+
throw e;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
flagSchemaBuildError = e as Error;
|
|
663
|
+
console.error("[@apollo/client-ai-apps/vite]:", e);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const typesFileContents = getRegisteredTypeContents({
|
|
668
|
+
operations,
|
|
669
|
+
schema,
|
|
670
|
+
flagSchemaBuildError,
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
writeFileSync(
|
|
674
|
+
path.resolve(root, ".apollo-client-ai-apps/types/register.d.ts"),
|
|
675
|
+
typesFileContents,
|
|
676
|
+
{ cache: true }
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
generateTypesFiles.cache = [] as ManifestOperation[] | undefined;
|
|
681
|
+
|
|
249
682
|
return {
|
|
250
683
|
name: "@apollo/client-ai-apps/vite",
|
|
251
684
|
async buildStart() {
|
|
@@ -259,13 +692,13 @@ export function apolloClientAiApps(
|
|
|
259
692
|
|
|
260
693
|
// We don't want to do this here on builds cause it just gets overwritten anyways. We'll call it on writeBundle instead.
|
|
261
694
|
if (config.command === "serve") {
|
|
262
|
-
await generateManifest(
|
|
695
|
+
await Promise.all([generateManifest(), generateTypesFiles()]);
|
|
263
696
|
}
|
|
264
697
|
},
|
|
265
698
|
configResolved(resolvedConfig) {
|
|
266
699
|
config = resolvedConfig;
|
|
267
700
|
},
|
|
268
|
-
async configEnvironment(name
|
|
701
|
+
async configEnvironment(name) {
|
|
269
702
|
if (!targets.includes(name as any)) return;
|
|
270
703
|
|
|
271
704
|
const appsConfig = await getAppsConfig();
|
|
@@ -292,7 +725,7 @@ export function apolloClientAiApps(
|
|
|
292
725
|
await generateManifest();
|
|
293
726
|
} else if (file.match(/\.(jsx?|tsx?)$/)) {
|
|
294
727
|
await processFile(file);
|
|
295
|
-
await generateManifest();
|
|
728
|
+
await Promise.all([generateManifest(), generateTypesFiles()]);
|
|
296
729
|
}
|
|
297
730
|
});
|
|
298
731
|
},
|
|
@@ -365,14 +798,16 @@ export function apolloClientAiApps(
|
|
|
365
798
|
);
|
|
366
799
|
},
|
|
367
800
|
async writeBundle() {
|
|
368
|
-
await generateManifest(
|
|
801
|
+
await Promise.all([generateManifest(), generateTypesFiles()]);
|
|
369
802
|
},
|
|
370
803
|
} satisfies Plugin;
|
|
371
804
|
}
|
|
372
805
|
|
|
373
806
|
const processQueryLink = new ApolloLink((operation) => {
|
|
374
807
|
const body = print(
|
|
375
|
-
|
|
808
|
+
removeOperationDescription(
|
|
809
|
+
removeManifestDirectives(sortTopLevelDefinitions(operation.query))
|
|
810
|
+
)
|
|
376
811
|
);
|
|
377
812
|
const name = operation.operationName;
|
|
378
813
|
const definition = operation.query.definitions.find(
|
|
@@ -401,35 +836,63 @@ const processQueryLink = new ApolloLink((operation) => {
|
|
|
401
836
|
// 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
|
|
402
837
|
const prefetchID = prefetch ? "__anonymous" : undefined;
|
|
403
838
|
|
|
404
|
-
const
|
|
405
|
-
?.filter((d) => d.name.value === "tool")
|
|
406
|
-
.map((directive) => {
|
|
407
|
-
const result = ToolDirectiveSchema.safeParse({
|
|
408
|
-
name: getArgumentValue(
|
|
409
|
-
getDirectiveArgument("name", directive, { required: true }),
|
|
410
|
-
Kind.STRING
|
|
411
|
-
),
|
|
412
|
-
description: getArgumentValue(
|
|
413
|
-
getDirectiveArgument("description", directive, { required: true }),
|
|
414
|
-
Kind.STRING
|
|
415
|
-
),
|
|
416
|
-
extraInputs: maybeGetArgumentValue(
|
|
417
|
-
getDirectiveArgument("extraInputs", directive),
|
|
418
|
-
Kind.LIST
|
|
419
|
-
),
|
|
420
|
-
labels: maybeGetArgumentValue(
|
|
421
|
-
getDirectiveArgument("labels", directive),
|
|
422
|
-
Kind.OBJECT
|
|
423
|
-
),
|
|
424
|
-
});
|
|
839
|
+
const toolDirectives =
|
|
840
|
+
directives?.filter((d) => d.name.value === "tool") ?? [];
|
|
425
841
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
842
|
+
const tools = toolDirectives.map((directive) => {
|
|
843
|
+
const nameArg = getDirectiveArgument("name", directive);
|
|
844
|
+
const descriptionArg = getDirectiveArgument("description", directive);
|
|
845
|
+
|
|
846
|
+
let name: string;
|
|
847
|
+
if (nameArg) {
|
|
848
|
+
name = getArgumentValue(nameArg, Kind.STRING);
|
|
849
|
+
} else {
|
|
850
|
+
invariant(
|
|
851
|
+
toolDirectives.length === 1,
|
|
852
|
+
`Operations with multiple @tool directives must provide a 'name' argument on each @tool`
|
|
853
|
+
);
|
|
854
|
+
invariant(
|
|
855
|
+
definition.name?.value,
|
|
856
|
+
`Anonymous operations cannot use @tool without providing a 'name' argument`
|
|
857
|
+
);
|
|
858
|
+
name = definition.name.value;
|
|
859
|
+
}
|
|
429
860
|
|
|
430
|
-
|
|
861
|
+
let description: string;
|
|
862
|
+
if (descriptionArg) {
|
|
863
|
+
description = getArgumentValue(descriptionArg, Kind.STRING);
|
|
864
|
+
} else {
|
|
865
|
+
invariant(
|
|
866
|
+
toolDirectives.length === 1,
|
|
867
|
+
`Operations with multiple @tool directives must provide a 'description' argument on each @tool`
|
|
868
|
+
);
|
|
869
|
+
invariant(
|
|
870
|
+
definition.description?.value,
|
|
871
|
+
`Operations using @tool without a 'description' argument must have a description on the operation definition`
|
|
872
|
+
);
|
|
873
|
+
description = definition.description.value;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const result = ToolDirectiveSchema.safeParse({
|
|
877
|
+
name,
|
|
878
|
+
description,
|
|
879
|
+
extraInputs: maybeGetArgumentValue(
|
|
880
|
+
getDirectiveArgument("extraInputs", directive),
|
|
881
|
+
Kind.LIST
|
|
882
|
+
),
|
|
883
|
+
labels: maybeGetArgumentValue(
|
|
884
|
+
getDirectiveArgument("labels", directive),
|
|
885
|
+
Kind.OBJECT
|
|
886
|
+
),
|
|
431
887
|
});
|
|
432
888
|
|
|
889
|
+
if (result.error) {
|
|
890
|
+
throw z.prettifyError(result.error);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
return result.data;
|
|
894
|
+
});
|
|
895
|
+
|
|
433
896
|
// TODO: Make this object satisfy the `ManifestOperation` type. Currently
|
|
434
897
|
// it errors because we need more validation on a few of these fields
|
|
435
898
|
return of({
|
|
@@ -437,6 +900,14 @@ const processQueryLink = new ApolloLink((operation) => {
|
|
|
437
900
|
});
|
|
438
901
|
});
|
|
439
902
|
|
|
903
|
+
function removeOperationDescription(doc: DocumentNode): DocumentNode {
|
|
904
|
+
return visit(doc, {
|
|
905
|
+
OperationDefinition(node) {
|
|
906
|
+
return { ...node, description: undefined };
|
|
907
|
+
},
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
|
|
440
911
|
function removeManifestDirectives(doc: DocumentNode) {
|
|
441
912
|
return removeDirectivesFromDocument(
|
|
442
913
|
[{ name: "prefetch" }, { name: "tool" }],
|
|
@@ -540,6 +1011,31 @@ readPackageJson.resetCache = () => {
|
|
|
540
1011
|
readPackageJson.cache = undefined;
|
|
541
1012
|
};
|
|
542
1013
|
|
|
1014
|
+
function writeFileSync(
|
|
1015
|
+
filepath: string,
|
|
1016
|
+
content: string,
|
|
1017
|
+
options: { cache?: boolean } = {}
|
|
1018
|
+
) {
|
|
1019
|
+
function writeFile() {
|
|
1020
|
+
fs.mkdirSync(path.dirname(filepath), { recursive: true });
|
|
1021
|
+
fs.writeFileSync(filepath, content, "utf-8");
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
if (!options.cache) {
|
|
1025
|
+
return writeFile();
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
const hash = md5(content);
|
|
1029
|
+
const cachedHash = writeFileSync.cache.get(filepath);
|
|
1030
|
+
|
|
1031
|
+
if (hash !== cachedHash || !fs.existsSync(filepath)) {
|
|
1032
|
+
writeFileSync.cache.set(filepath, hash);
|
|
1033
|
+
writeFile();
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
writeFileSync.cache = new Map<string, string>();
|
|
1038
|
+
|
|
543
1039
|
const ToolDirectiveSchema = z.strictObject({
|
|
544
1040
|
name: z.stringFormat("toolName", (value) => value.indexOf(" ") === -1, {
|
|
545
1041
|
error: (iss) => `Tool with name "${iss.input}" must not contain spaces`,
|