@apollo/client-ai-apps 0.5.4 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +180 -0
- package/dist/config/defineConfig.d.ts +1 -0
- package/dist/config/defineConfig.d.ts.map +1 -1
- package/dist/config/schema.d.ts +1 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +1 -0
- package/dist/config/schema.js.map +1 -1
- 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/core/McpAppManager.d.ts +2 -1
- package/dist/mcp/core/McpAppManager.d.ts.map +1 -1
- package/dist/mcp/core/McpAppManager.js +11 -1
- package/dist/mcp/core/McpAppManager.js.map +1 -1
- package/dist/mcp/react/hooks/useHostContext.d.ts +2 -0
- package/dist/mcp/react/hooks/useHostContext.d.ts.map +1 -0
- package/dist/mcp/react/hooks/useHostContext.js +7 -0
- package/dist/mcp/react/hooks/useHostContext.js.map +1 -0
- 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 +2 -0
- package/dist/mcp/react/index.d.ts.map +1 -1
- package/dist/mcp/react/index.js +2 -0
- package/dist/mcp/react/index.js.map +1 -1
- package/dist/openai/core/McpAppManager.d.ts +2 -1
- package/dist/openai/core/McpAppManager.d.ts.map +1 -1
- package/dist/openai/core/McpAppManager.js +11 -1
- package/dist/openai/core/McpAppManager.js.map +1 -1
- package/dist/openai/react/hooks/useHostContext.d.ts +2 -0
- package/dist/openai/react/hooks/useHostContext.d.ts.map +1 -0
- package/dist/openai/react/hooks/useHostContext.js +7 -0
- package/dist/openai/react/hooks/useHostContext.js.map +1 -0
- 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 +2 -0
- package/dist/openai/react/index.d.ts.map +1 -1
- package/dist/openai/react/index.js +2 -0
- package/dist/openai/react/index.js.map +1 -1
- package/dist/react/index.d.ts +10 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +10 -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/types/application-manifest.d.ts +1 -0
- package/dist/types/application-manifest.d.ts.map +1 -1
- package/dist/types/application-manifest.js.map +1 -1
- package/dist/vite/__tests__/utilities/build.d.ts.map +1 -1
- package/dist/vite/__tests__/utilities/build.js +0 -1
- package/dist/vite/__tests__/utilities/build.js.map +1 -1
- package/dist/vite/apolloClientAiApps.d.ts +2 -0
- package/dist/vite/apolloClientAiApps.d.ts.map +1 -1
- package/dist/vite/apolloClientAiApps.js +362 -53
- 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 -6
- package/src/config/schema.ts +1 -0
- package/src/core/typeRegistration.ts +32 -0
- package/src/index.ts +7 -0
- package/src/mcp/core/McpAppManager.ts +23 -1
- package/src/mcp/react/hooks/__tests__/useHostContext.test.tsx +95 -0
- package/src/mcp/react/hooks/__tests__/useToolInfo.test.tsx +53 -0
- package/src/mcp/react/hooks/useHostContext.ts +14 -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 +2 -0
- package/src/openai/core/McpAppManager.ts +22 -1
- package/src/openai/react/hooks/__tests__/useToolInfo.test.tsx +92 -0
- package/src/openai/react/hooks/useHostContext.ts +14 -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 +2 -0
- package/src/react/index.mcp.ts +2 -0
- package/src/react/index.openai.ts +2 -0
- package/src/react/index.ts +14 -0
- package/src/testing/internal/mcp/mockMcpHost.ts +12 -0
- package/src/testing/internal/utilities/mockApplicationManifest.ts +1 -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/types/application-manifest.ts +1 -0
- package/src/vite/__tests__/apolloClientAiApps.test.ts +1022 -66
- package/src/vite/__tests__/utilities/build.ts +0 -1
- package/src/vite/apolloClientAiApps.ts +604 -81
- package/src/vite/utilities/recast.ts +100 -0
|
@@ -2,8 +2,11 @@ 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";
|
|
@@ -16,6 +19,8 @@ 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,43 +33,226 @@ 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 } = options;
|
|
34
|
-
const cache = new Map();
|
|
35
|
-
let packageJson;
|
|
237
|
+
const { devTarget = targets.length === 1 ? targets[0] : undefined, appsOutDir, schema, } = options;
|
|
36
238
|
let config;
|
|
37
239
|
const fragments = createFragmentRegistry();
|
|
38
240
|
invariant(Array.isArray(targets) && targets.length > 0, "The `targets` option must be a non-empty array");
|
|
39
241
|
invariant(targets.every(isValidTarget), `All targets must be one of: ${VALID_TARGETS.join(", ")}`);
|
|
242
|
+
invariant(path.basename(path.normalize(appsOutDir)) === "apps", "`appsOutDir` must end with `apps` as the final path segment (e.g. `path/to/apps`).");
|
|
40
243
|
const client = new ApolloClient({
|
|
41
244
|
cache: new InMemoryCache({ fragments }),
|
|
42
245
|
link: processQueryLink,
|
|
43
246
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
|
@@ -111,6 +332,9 @@ export function apolloClientAiApps(options) {
|
|
|
111
332
|
else {
|
|
112
333
|
resource = Object.fromEntries(targets.map((target) => [target, getBuildResourceForTarget(target)]));
|
|
113
334
|
}
|
|
335
|
+
const packageJson = readPackageJson();
|
|
336
|
+
const appName = appsConfig.name ?? packageJson.name;
|
|
337
|
+
invariant(appName, "Error generating application manifest. Could not determine app name. Set `name` in your apollo-client-ai-apps config or `package.json`.");
|
|
114
338
|
const manifest = {
|
|
115
339
|
format: "apollo-ai-app-manifest",
|
|
116
340
|
version: "1",
|
|
@@ -121,6 +345,7 @@ export function apolloClientAiApps(options) {
|
|
|
121
345
|
operations,
|
|
122
346
|
resource,
|
|
123
347
|
csp: {
|
|
348
|
+
baseUriDomains: appsConfig.csp?.baseUriDomains ?? [],
|
|
124
349
|
connectDomains: appsConfig.csp?.connectDomains ?? [],
|
|
125
350
|
frameDomains: appsConfig.csp?.frameDomains ?? [],
|
|
126
351
|
redirectDomains: appsConfig.csp?.redirectDomains ?? [],
|
|
@@ -133,25 +358,57 @@ export function apolloClientAiApps(options) {
|
|
|
133
358
|
if (isNonEmptyObject(appsConfig.labels)) {
|
|
134
359
|
manifest.labels = appsConfig.labels;
|
|
135
360
|
}
|
|
136
|
-
|
|
137
|
-
// subdirectories, but we want the manifest to be in the root outDir. If we
|
|
138
|
-
// are running in a different environment, we'll put it in the configured
|
|
139
|
-
// outDir directly instead.
|
|
140
|
-
const outDir = environment?.name === "mcp" || environment?.name === "openai" ?
|
|
141
|
-
path.resolve(config.build.outDir, "../")
|
|
142
|
-
: config.build.outDir;
|
|
361
|
+
const manifestContents = JSON.stringify(manifest);
|
|
143
362
|
// Always write to build directory so the MCP server picks it up
|
|
144
|
-
|
|
145
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
146
|
-
fs.writeFileSync(dest, JSON.stringify(manifest));
|
|
363
|
+
writeFileSync(path.resolve(root, appsOutDir, appName, ".application-manifest.json"), manifestContents);
|
|
147
364
|
// Always write to the dev location so that the app can bundle the manifest content
|
|
148
|
-
|
|
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 });
|
|
149
407
|
}
|
|
408
|
+
generateTypesFiles.cache = [];
|
|
150
409
|
return {
|
|
151
410
|
name: "@apollo/client-ai-apps/vite",
|
|
152
411
|
async buildStart() {
|
|
153
|
-
// Read package.json on start
|
|
154
|
-
packageJson = JSON.parse(fs.readFileSync("package.json", "utf-8"));
|
|
155
412
|
// Scan all files on startup
|
|
156
413
|
const files = await glob("./src/**/*.{ts,tsx,js,jsx}", { fs });
|
|
157
414
|
for (const file of files) {
|
|
@@ -160,25 +417,28 @@ export function apolloClientAiApps(options) {
|
|
|
160
417
|
}
|
|
161
418
|
// We don't want to do this here on builds cause it just gets overwritten anyways. We'll call it on writeBundle instead.
|
|
162
419
|
if (config.command === "serve") {
|
|
163
|
-
await generateManifest(
|
|
420
|
+
await Promise.all([generateManifest(), generateTypesFiles()]);
|
|
164
421
|
}
|
|
165
422
|
},
|
|
166
423
|
configResolved(resolvedConfig) {
|
|
167
424
|
config = resolvedConfig;
|
|
168
425
|
},
|
|
169
|
-
configEnvironment(name
|
|
426
|
+
async configEnvironment(name) {
|
|
170
427
|
if (!targets.includes(name))
|
|
171
428
|
return;
|
|
429
|
+
const appsConfig = await getAppsConfig();
|
|
430
|
+
const appName = appsConfig.name ?? readPackageJson().name;
|
|
431
|
+
invariant(appName, "Could not determine app name. Set `name` in your apollo-client-ai-apps config or `package.json`.");
|
|
172
432
|
return {
|
|
173
433
|
build: {
|
|
174
|
-
outDir: path.join(
|
|
434
|
+
outDir: path.join(appsOutDir, appName, name),
|
|
175
435
|
},
|
|
176
436
|
};
|
|
177
437
|
},
|
|
178
438
|
configureServer(server) {
|
|
179
439
|
server.watcher.on("change", async (file) => {
|
|
180
440
|
if (file.endsWith("package.json")) {
|
|
181
|
-
|
|
441
|
+
readPackageJson.resetCache();
|
|
182
442
|
await generateManifest();
|
|
183
443
|
}
|
|
184
444
|
else if (file.match(/\.?apollo-client-ai-apps\.config\.\w+$/)) {
|
|
@@ -187,11 +447,15 @@ export function apolloClientAiApps(options) {
|
|
|
187
447
|
}
|
|
188
448
|
else if (file.match(/\.(jsx?|tsx?)$/)) {
|
|
189
449
|
await processFile(file);
|
|
190
|
-
await generateManifest();
|
|
450
|
+
await Promise.all([generateManifest(), generateTypesFiles()]);
|
|
191
451
|
}
|
|
192
452
|
});
|
|
193
453
|
},
|
|
194
|
-
config(
|
|
454
|
+
config(userConfig, { command }) {
|
|
455
|
+
if (userConfig.build?.outDir) {
|
|
456
|
+
console.warn("[@apollo/client-ai-apps/vite] `build.outDir` is set in your Vite config but will be " +
|
|
457
|
+
"ignored. Use `appsOutDir` in the plugin options to control the output location.");
|
|
458
|
+
}
|
|
195
459
|
if (command === "serve") {
|
|
196
460
|
invariant(isValidTarget(devTarget) || targets.length === 1, "`devTarget` must be set for development when using multiple targets.");
|
|
197
461
|
const target = devTarget ?? targets[0];
|
|
@@ -235,7 +499,7 @@ export function apolloClientAiApps(options) {
|
|
|
235
499
|
.replace(/(src=["'])\/([^"']+)/gi, `$1${baseUrl}/$2`));
|
|
236
500
|
},
|
|
237
501
|
async writeBundle() {
|
|
238
|
-
await generateManifest(
|
|
502
|
+
await Promise.all([generateManifest(), generateTypesFiles()]);
|
|
239
503
|
},
|
|
240
504
|
};
|
|
241
505
|
}
|
|
@@ -255,12 +519,31 @@ const processQueryLink = new ApolloLink((operation) => {
|
|
|
255
519
|
const id = createHash("sha256").update(body).digest("hex");
|
|
256
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
|
|
257
521
|
const prefetchID = prefetch ? "__anonymous" : undefined;
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
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
|
+
}
|
|
261
544
|
const result = ToolDirectiveSchema.safeParse({
|
|
262
|
-
name
|
|
263
|
-
description
|
|
545
|
+
name,
|
|
546
|
+
description,
|
|
264
547
|
extraInputs: maybeGetArgumentValue(getDirectiveArgument("extraInputs", directive), Kind.LIST),
|
|
265
548
|
labels: maybeGetArgumentValue(getDirectiveArgument("labels", directive), Kind.OBJECT),
|
|
266
549
|
});
|
|
@@ -339,6 +622,32 @@ function getResourceFromConfig(appsConfig, mode, target) {
|
|
|
339
622
|
const config = appsConfig.entry[mode];
|
|
340
623
|
return typeof config === "string" ? config : config[target];
|
|
341
624
|
}
|
|
625
|
+
function readPackageJson() {
|
|
626
|
+
if (readPackageJson.cache) {
|
|
627
|
+
return readPackageJson.cache;
|
|
628
|
+
}
|
|
629
|
+
return (readPackageJson.cache = JSON.parse(fs.readFileSync("package.json", "utf-8")));
|
|
630
|
+
}
|
|
631
|
+
readPackageJson.cache = undefined;
|
|
632
|
+
readPackageJson.resetCache = () => {
|
|
633
|
+
readPackageJson.cache = undefined;
|
|
634
|
+
};
|
|
635
|
+
function writeFileSync(filepath, content, options = {}) {
|
|
636
|
+
function writeFile() {
|
|
637
|
+
fs.mkdirSync(path.dirname(filepath), { recursive: true });
|
|
638
|
+
fs.writeFileSync(filepath, content, "utf-8");
|
|
639
|
+
}
|
|
640
|
+
if (!options.cache) {
|
|
641
|
+
return writeFile();
|
|
642
|
+
}
|
|
643
|
+
const hash = md5(content);
|
|
644
|
+
const cachedHash = writeFileSync.cache.get(filepath);
|
|
645
|
+
if (hash !== cachedHash || !fs.existsSync(filepath)) {
|
|
646
|
+
writeFileSync.cache.set(filepath, hash);
|
|
647
|
+
writeFile();
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
writeFileSync.cache = new Map();
|
|
342
651
|
const ToolDirectiveSchema = z.strictObject({
|
|
343
652
|
name: z.stringFormat("toolName", (value) => value.indexOf(" ") === -1, {
|
|
344
653
|
error: (iss) => `Tool with name "${iss.input}" must not contain spaces`,
|