@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
|
@@ -1,14 +1,16 @@
|
|
|
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";
|
|
@@ -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;
|
|
@@ -38,6 +50,8 @@ export declare namespace apolloClientAiApps {
|
|
|
38
50
|
export interface Options {
|
|
39
51
|
targets: Target[];
|
|
40
52
|
devTarget?: Target | undefined;
|
|
53
|
+
appsOutDir: string;
|
|
54
|
+
schema?: string | undefined;
|
|
41
55
|
}
|
|
42
56
|
}
|
|
43
57
|
|
|
@@ -65,19 +79,331 @@ export function devTarget(target: string | undefined) {
|
|
|
65
79
|
}
|
|
66
80
|
|
|
67
81
|
interface FileCache {
|
|
68
|
-
file: string;
|
|
69
82
|
hash: string;
|
|
70
83
|
sources: DocumentNode[];
|
|
71
84
|
}
|
|
72
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
|
+
|
|
73
398
|
export function apolloClientAiApps(
|
|
74
399
|
options: apolloClientAiApps.Options
|
|
75
400
|
): Plugin {
|
|
76
401
|
const targets = Array.from(new Set(options.targets));
|
|
77
|
-
const {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
402
|
+
const {
|
|
403
|
+
devTarget = targets.length === 1 ? targets[0] : undefined,
|
|
404
|
+
appsOutDir,
|
|
405
|
+
schema,
|
|
406
|
+
} = options;
|
|
81
407
|
let config!: ResolvedConfig;
|
|
82
408
|
|
|
83
409
|
const fragments = createFragmentRegistry();
|
|
@@ -92,41 +418,28 @@ export function apolloClientAiApps(
|
|
|
92
418
|
`All targets must be one of: ${VALID_TARGETS.join(", ")}`
|
|
93
419
|
);
|
|
94
420
|
|
|
421
|
+
invariant(
|
|
422
|
+
path.basename(path.normalize(appsOutDir)) === "apps",
|
|
423
|
+
"`appsOutDir` must end with `apps` as the final path segment (e.g. `path/to/apps`)."
|
|
424
|
+
);
|
|
425
|
+
|
|
95
426
|
const client = new ApolloClient({
|
|
96
427
|
cache: new InMemoryCache({ fragments }),
|
|
97
428
|
link: processQueryLink,
|
|
98
429
|
});
|
|
99
430
|
|
|
100
|
-
|
|
101
|
-
const code = fs.readFileSync(file, "utf-8");
|
|
102
|
-
|
|
103
|
-
if (!code.includes("gql")) return;
|
|
431
|
+
let sources: DocumentNode[] = [];
|
|
104
432
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const sources = gqlPluckFromCodeStringSync(file, code, {
|
|
108
|
-
modules: [
|
|
109
|
-
{ name: "graphql-tag", identifier: "gql" },
|
|
110
|
-
{ name: "@apollo/client", identifier: "gql" },
|
|
111
|
-
],
|
|
112
|
-
}).map((source) => parse(source.body));
|
|
113
|
-
|
|
114
|
-
fragments.register(...sources);
|
|
115
|
-
|
|
116
|
-
cache.set(file, {
|
|
117
|
-
file: file,
|
|
118
|
-
hash: fileHash,
|
|
119
|
-
sources,
|
|
120
|
-
});
|
|
433
|
+
function recomputeSources(cache: Map<string, FileCache>) {
|
|
434
|
+
sources = Array.from(cache.values()).flatMap((entry) => entry.sources);
|
|
121
435
|
}
|
|
122
436
|
|
|
123
|
-
async function
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
);
|
|
437
|
+
async function getManifestOperations() {
|
|
438
|
+
if (sources === getManifestOperations.cache.sources) {
|
|
439
|
+
return getManifestOperations.cache.manifestOperations;
|
|
440
|
+
}
|
|
128
441
|
|
|
129
|
-
const
|
|
442
|
+
const manifestOperations = [];
|
|
130
443
|
for (const source of sources) {
|
|
131
444
|
const operationDef = source.definitions.find(
|
|
132
445
|
(d) => d.kind === Kind.OPERATION_DEFINITION
|
|
@@ -141,7 +454,7 @@ export function apolloClientAiApps(
|
|
|
141
454
|
fetchPolicy: "no-cache",
|
|
142
455
|
});
|
|
143
456
|
|
|
144
|
-
|
|
457
|
+
manifestOperations.push(result.data!);
|
|
145
458
|
break;
|
|
146
459
|
}
|
|
147
460
|
case OperationTypeNode.MUTATION: {
|
|
@@ -150,7 +463,7 @@ export function apolloClientAiApps(
|
|
|
150
463
|
fetchPolicy: "no-cache",
|
|
151
464
|
});
|
|
152
465
|
|
|
153
|
-
|
|
466
|
+
manifestOperations.push(result.data!);
|
|
154
467
|
break;
|
|
155
468
|
}
|
|
156
469
|
default:
|
|
@@ -160,6 +473,50 @@ export function apolloClientAiApps(
|
|
|
160
473
|
}
|
|
161
474
|
}
|
|
162
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
|
+
|
|
163
520
|
invariant(
|
|
164
521
|
operations.filter((o) => o.prefetch).length <= 1,
|
|
165
522
|
"Found multiple operations marked as `@prefetch`. You can only mark 1 operation with `@prefetch`."
|
|
@@ -193,6 +550,14 @@ export function apolloClientAiApps(
|
|
|
193
550
|
) as { mcp?: string; openai?: string };
|
|
194
551
|
}
|
|
195
552
|
|
|
553
|
+
const packageJson = readPackageJson();
|
|
554
|
+
const appName = appsConfig.name ?? packageJson.name;
|
|
555
|
+
|
|
556
|
+
invariant(
|
|
557
|
+
appName,
|
|
558
|
+
"Error generating application manifest. Could not determine app name. Set `name` in your apollo-client-ai-apps config or `package.json`."
|
|
559
|
+
);
|
|
560
|
+
|
|
196
561
|
const manifest: ApplicationManifest = {
|
|
197
562
|
format: "apollo-ai-app-manifest",
|
|
198
563
|
version: "1",
|
|
@@ -203,6 +568,7 @@ export function apolloClientAiApps(
|
|
|
203
568
|
operations,
|
|
204
569
|
resource,
|
|
205
570
|
csp: {
|
|
571
|
+
baseUriDomains: appsConfig.csp?.baseUriDomains ?? [],
|
|
206
572
|
connectDomains: appsConfig.csp?.connectDomains ?? [],
|
|
207
573
|
frameDomains: appsConfig.csp?.frameDomains ?? [],
|
|
208
574
|
redirectDomains: appsConfig.csp?.redirectDomains ?? [],
|
|
@@ -218,30 +584,104 @@ export function apolloClientAiApps(
|
|
|
218
584
|
manifest.labels = appsConfig.labels;
|
|
219
585
|
}
|
|
220
586
|
|
|
221
|
-
|
|
222
|
-
// subdirectories, but we want the manifest to be in the root outDir. If we
|
|
223
|
-
// are running in a different environment, we'll put it in the configured
|
|
224
|
-
// outDir directly instead.
|
|
225
|
-
const outDir =
|
|
226
|
-
environment?.name === "mcp" || environment?.name === "openai" ?
|
|
227
|
-
path.resolve(config.build.outDir, "../")
|
|
228
|
-
: config.build.outDir;
|
|
587
|
+
const manifestContents = JSON.stringify(manifest);
|
|
229
588
|
|
|
230
589
|
// Always write to build directory so the MCP server picks it up
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
590
|
+
writeFileSync(
|
|
591
|
+
path.resolve(root, appsOutDir, appName, ".application-manifest.json"),
|
|
592
|
+
manifestContents
|
|
593
|
+
);
|
|
234
594
|
|
|
235
595
|
// Always write to the dev location so that the app can bundle the manifest content
|
|
236
|
-
|
|
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
|
+
}
|
|
624
|
+
}
|
|
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
|
+
);
|
|
237
678
|
}
|
|
238
679
|
|
|
680
|
+
generateTypesFiles.cache = [] as ManifestOperation[] | undefined;
|
|
681
|
+
|
|
239
682
|
return {
|
|
240
683
|
name: "@apollo/client-ai-apps/vite",
|
|
241
684
|
async buildStart() {
|
|
242
|
-
// Read package.json on start
|
|
243
|
-
packageJson = JSON.parse(fs.readFileSync("package.json", "utf-8"));
|
|
244
|
-
|
|
245
685
|
// Scan all files on startup
|
|
246
686
|
const files = await glob("./src/**/*.{ts,tsx,js,jsx}", { fs });
|
|
247
687
|
|
|
@@ -252,37 +692,52 @@ export function apolloClientAiApps(
|
|
|
252
692
|
|
|
253
693
|
// We don't want to do this here on builds cause it just gets overwritten anyways. We'll call it on writeBundle instead.
|
|
254
694
|
if (config.command === "serve") {
|
|
255
|
-
await generateManifest(
|
|
695
|
+
await Promise.all([generateManifest(), generateTypesFiles()]);
|
|
256
696
|
}
|
|
257
697
|
},
|
|
258
698
|
configResolved(resolvedConfig) {
|
|
259
699
|
config = resolvedConfig;
|
|
260
700
|
},
|
|
261
|
-
configEnvironment(name
|
|
701
|
+
async configEnvironment(name) {
|
|
262
702
|
if (!targets.includes(name as any)) return;
|
|
263
703
|
|
|
704
|
+
const appsConfig = await getAppsConfig();
|
|
705
|
+
const appName = appsConfig.name ?? readPackageJson().name;
|
|
706
|
+
|
|
707
|
+
invariant(
|
|
708
|
+
appName,
|
|
709
|
+
"Could not determine app name. Set `name` in your apollo-client-ai-apps config or `package.json`."
|
|
710
|
+
);
|
|
711
|
+
|
|
264
712
|
return {
|
|
265
713
|
build: {
|
|
266
|
-
outDir: path.join(
|
|
714
|
+
outDir: path.join(appsOutDir, appName, name),
|
|
267
715
|
},
|
|
268
716
|
};
|
|
269
717
|
},
|
|
270
718
|
configureServer(server) {
|
|
271
719
|
server.watcher.on("change", async (file) => {
|
|
272
720
|
if (file.endsWith("package.json")) {
|
|
273
|
-
|
|
721
|
+
readPackageJson.resetCache();
|
|
274
722
|
await generateManifest();
|
|
275
723
|
} else if (file.match(/\.?apollo-client-ai-apps\.config\.\w+$/)) {
|
|
276
724
|
explorer.clearCaches();
|
|
277
725
|
await generateManifest();
|
|
278
726
|
} else if (file.match(/\.(jsx?|tsx?)$/)) {
|
|
279
727
|
await processFile(file);
|
|
280
|
-
await generateManifest();
|
|
728
|
+
await Promise.all([generateManifest(), generateTypesFiles()]);
|
|
281
729
|
}
|
|
282
730
|
});
|
|
283
731
|
},
|
|
284
732
|
|
|
285
|
-
config(
|
|
733
|
+
config(userConfig, { command }) {
|
|
734
|
+
if (userConfig.build?.outDir) {
|
|
735
|
+
console.warn(
|
|
736
|
+
"[@apollo/client-ai-apps/vite] `build.outDir` is set in your Vite config but will be " +
|
|
737
|
+
"ignored. Use `appsOutDir` in the plugin options to control the output location."
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
|
|
286
741
|
if (command === "serve") {
|
|
287
742
|
invariant(
|
|
288
743
|
isValidTarget(devTarget) || targets.length === 1,
|
|
@@ -343,7 +798,7 @@ export function apolloClientAiApps(
|
|
|
343
798
|
);
|
|
344
799
|
},
|
|
345
800
|
async writeBundle() {
|
|
346
|
-
await generateManifest(
|
|
801
|
+
await Promise.all([generateManifest(), generateTypesFiles()]);
|
|
347
802
|
},
|
|
348
803
|
} satisfies Plugin;
|
|
349
804
|
}
|
|
@@ -379,35 +834,63 @@ const processQueryLink = new ApolloLink((operation) => {
|
|
|
379
834
|
// 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
|
|
380
835
|
const prefetchID = prefetch ? "__anonymous" : undefined;
|
|
381
836
|
|
|
382
|
-
const
|
|
383
|
-
?.filter((d) => d.name.value === "tool")
|
|
384
|
-
.map((directive) => {
|
|
385
|
-
const result = ToolDirectiveSchema.safeParse({
|
|
386
|
-
name: getArgumentValue(
|
|
387
|
-
getDirectiveArgument("name", directive, { required: true }),
|
|
388
|
-
Kind.STRING
|
|
389
|
-
),
|
|
390
|
-
description: getArgumentValue(
|
|
391
|
-
getDirectiveArgument("description", directive, { required: true }),
|
|
392
|
-
Kind.STRING
|
|
393
|
-
),
|
|
394
|
-
extraInputs: maybeGetArgumentValue(
|
|
395
|
-
getDirectiveArgument("extraInputs", directive),
|
|
396
|
-
Kind.LIST
|
|
397
|
-
),
|
|
398
|
-
labels: maybeGetArgumentValue(
|
|
399
|
-
getDirectiveArgument("labels", directive),
|
|
400
|
-
Kind.OBJECT
|
|
401
|
-
),
|
|
402
|
-
});
|
|
837
|
+
const toolDirectives =
|
|
838
|
+
directives?.filter((d) => d.name.value === "tool") ?? [];
|
|
403
839
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
840
|
+
const tools = toolDirectives.map((directive) => {
|
|
841
|
+
const nameArg = getDirectiveArgument("name", directive);
|
|
842
|
+
const descriptionArg = getDirectiveArgument("description", directive);
|
|
843
|
+
|
|
844
|
+
let name: string;
|
|
845
|
+
if (nameArg) {
|
|
846
|
+
name = getArgumentValue(nameArg, Kind.STRING);
|
|
847
|
+
} else {
|
|
848
|
+
invariant(
|
|
849
|
+
toolDirectives.length === 1,
|
|
850
|
+
`Operations with multiple @tool directives must provide a 'name' argument on each @tool`
|
|
851
|
+
);
|
|
852
|
+
invariant(
|
|
853
|
+
definition.name?.value,
|
|
854
|
+
`Anonymous operations cannot use @tool without providing a 'name' argument`
|
|
855
|
+
);
|
|
856
|
+
name = definition.name.value;
|
|
857
|
+
}
|
|
407
858
|
|
|
408
|
-
|
|
859
|
+
let description: string;
|
|
860
|
+
if (descriptionArg) {
|
|
861
|
+
description = getArgumentValue(descriptionArg, Kind.STRING);
|
|
862
|
+
} else {
|
|
863
|
+
invariant(
|
|
864
|
+
toolDirectives.length === 1,
|
|
865
|
+
`Operations with multiple @tool directives must provide a 'description' argument on each @tool`
|
|
866
|
+
);
|
|
867
|
+
invariant(
|
|
868
|
+
definition.description?.value,
|
|
869
|
+
`Operations using @tool without a 'description' argument must have a description on the operation definition`
|
|
870
|
+
);
|
|
871
|
+
description = definition.description.value;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const result = ToolDirectiveSchema.safeParse({
|
|
875
|
+
name,
|
|
876
|
+
description,
|
|
877
|
+
extraInputs: maybeGetArgumentValue(
|
|
878
|
+
getDirectiveArgument("extraInputs", directive),
|
|
879
|
+
Kind.LIST
|
|
880
|
+
),
|
|
881
|
+
labels: maybeGetArgumentValue(
|
|
882
|
+
getDirectiveArgument("labels", directive),
|
|
883
|
+
Kind.OBJECT
|
|
884
|
+
),
|
|
409
885
|
});
|
|
410
886
|
|
|
887
|
+
if (result.error) {
|
|
888
|
+
throw z.prettifyError(result.error);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
return result.data;
|
|
892
|
+
});
|
|
893
|
+
|
|
411
894
|
// TODO: Make this object satisfy the `ManifestOperation` type. Currently
|
|
412
895
|
// it errors because we need more validation on a few of these fields
|
|
413
896
|
return of({
|
|
@@ -503,6 +986,46 @@ function getResourceFromConfig(
|
|
|
503
986
|
return typeof config === "string" ? config : config[target];
|
|
504
987
|
}
|
|
505
988
|
|
|
989
|
+
function readPackageJson(): Record<string, any> {
|
|
990
|
+
if (readPackageJson.cache) {
|
|
991
|
+
return readPackageJson.cache;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
return (readPackageJson.cache = JSON.parse(
|
|
995
|
+
fs.readFileSync("package.json", "utf-8")
|
|
996
|
+
));
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
readPackageJson.cache = undefined as Record<string, any> | undefined;
|
|
1000
|
+
readPackageJson.resetCache = () => {
|
|
1001
|
+
readPackageJson.cache = undefined;
|
|
1002
|
+
};
|
|
1003
|
+
|
|
1004
|
+
function writeFileSync(
|
|
1005
|
+
filepath: string,
|
|
1006
|
+
content: string,
|
|
1007
|
+
options: { cache?: boolean } = {}
|
|
1008
|
+
) {
|
|
1009
|
+
function writeFile() {
|
|
1010
|
+
fs.mkdirSync(path.dirname(filepath), { recursive: true });
|
|
1011
|
+
fs.writeFileSync(filepath, content, "utf-8");
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
if (!options.cache) {
|
|
1015
|
+
return writeFile();
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
const hash = md5(content);
|
|
1019
|
+
const cachedHash = writeFileSync.cache.get(filepath);
|
|
1020
|
+
|
|
1021
|
+
if (hash !== cachedHash || !fs.existsSync(filepath)) {
|
|
1022
|
+
writeFileSync.cache.set(filepath, hash);
|
|
1023
|
+
writeFile();
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
writeFileSync.cache = new Map<string, string>();
|
|
1028
|
+
|
|
506
1029
|
const ToolDirectiveSchema = z.strictObject({
|
|
507
1030
|
name: z.stringFormat("toolName", (value) => value.indexOf(" ") === -1, {
|
|
508
1031
|
error: (iss) => `Tool with name "${iss.input}" must not contain spaces`,
|