@apollo/client-ai-apps 0.2.4 → 0.3.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/.git-blame-ignore-revs +2 -0
- package/.github/workflows/compare-build-output.yml +28 -0
- package/.github/workflows/pr.yaml +23 -15
- package/.github/workflows/release.yaml +46 -46
- package/.prettierrc +9 -0
- package/config/compare-build-output-to.sh +90 -0
- package/dist/core/ApolloClient.d.ts +14 -0
- package/dist/index.d.ts +17 -10
- package/dist/index.js +164 -62
- package/dist/link/ToolCallLink.d.ts +26 -0
- package/dist/react/ApolloProvider.d.ts +9 -0
- package/dist/react/context/ToolUseContext.d.ts +15 -0
- package/dist/{hooks → react/hooks}/useOpenAiGlobal.d.ts +1 -1
- package/dist/react/hooks/useOpenExternal.d.ts +3 -0
- package/dist/{hooks → react/hooks}/useRequestDisplayMode.d.ts +1 -1
- package/dist/{hooks → react/hooks}/useToolEffect.d.ts +0 -4
- package/dist/react/hooks/useToolOutput.d.ts +1 -0
- package/dist/react/hooks/useToolResponseMetadata.d.ts +1 -0
- package/dist/react/hooks/useWidgetState.d.ts +4 -0
- package/dist/types/openai.d.ts +1 -2
- package/dist/vite/index.js +74 -21
- package/package.json +9 -2
- package/scripts/dev.mjs +3 -1
- package/src/core/ApolloClient.ts +108 -0
- package/src/{apollo_client/client.test.ts → core/__tests__/ApolloClient.test.ts} +232 -20
- package/src/index.ts +36 -10
- package/src/link/ToolCallLink.ts +49 -0
- package/src/{apollo_client/provider.tsx → react/ApolloProvider.tsx} +19 -9
- package/src/{apollo_client/provider.test.tsx → react/__tests__/ApolloProvider.test.tsx} +9 -9
- package/src/react/context/ToolUseContext.tsx +30 -0
- package/src/{hooks → react/hooks/__tests__}/useCallTool.test.ts +1 -1
- package/src/{hooks → react/hooks/__tests__}/useOpenAiGlobal.test.ts +5 -3
- package/src/react/hooks/__tests__/useOpenExternal.test.tsx +24 -0
- package/src/{hooks → react/hooks/__tests__}/useRequestDisplayMode.test.ts +2 -2
- package/src/{hooks → react/hooks/__tests__}/useSendFollowUpMessage.test.ts +4 -2
- package/src/{hooks → react/hooks/__tests__}/useToolEffect.test.tsx +27 -10
- package/src/{hooks → react/hooks/__tests__}/useToolInput.test.ts +1 -1
- package/src/{hooks → react/hooks/__tests__}/useToolName.test.ts +1 -1
- package/src/react/hooks/__tests__/useToolOutput.test.tsx +49 -0
- package/src/react/hooks/__tests__/useToolResponseMetadata.test.tsx +49 -0
- package/src/react/hooks/__tests__/useWidgetState.test.tsx +158 -0
- package/src/react/hooks/useCallTool.ts +13 -0
- package/src/{hooks → react/hooks}/useOpenAiGlobal.ts +11 -5
- package/src/react/hooks/useOpenExternal.ts +11 -0
- package/src/{hooks → react/hooks}/useRequestDisplayMode.ts +1 -1
- package/src/react/hooks/useToolEffect.tsx +37 -0
- package/src/{hooks → react/hooks}/useToolName.ts +1 -1
- package/src/react/hooks/useToolOutput.ts +5 -0
- package/src/react/hooks/useToolResponseMetadata.ts +5 -0
- package/src/react/hooks/useWidgetState.ts +48 -0
- package/src/testing/internal/index.ts +2 -0
- package/src/testing/internal/matchers/index.d.ts +9 -0
- package/src/testing/internal/matchers/index.ts +1 -0
- package/src/testing/internal/matchers/toRerender.ts +49 -0
- package/src/testing/internal/openai/dispatchStateChange.ts +9 -0
- package/src/testing/internal/openai/stubOpenAiGlobals.ts +13 -0
- package/src/types/openai.ts +6 -3
- package/src/vite/{absolute_asset_imports_plugin.test.ts → __tests__/absolute_asset_imports_plugin.test.ts} +4 -2
- package/src/vite/{application_manifest_plugin.test.ts → __tests__/application_manifest_plugin.test.ts} +176 -53
- package/src/vite/absolute_asset_imports_plugin.ts +3 -1
- package/src/vite/application_manifest_plugin.ts +84 -24
- package/vitest-setup.ts +1 -0
- package/dist/apollo_client/client.d.ts +0 -14
- package/dist/apollo_client/provider.d.ts +0 -5
- package/src/apollo_client/client.ts +0 -90
- package/src/hooks/useCallTool.ts +0 -8
- package/src/hooks/useToolEffect.tsx +0 -41
- /package/dist/{hooks → react/hooks}/useSendFollowUpMessage.d.ts +0 -0
- /package/dist/{hooks → react/hooks}/useToolInput.d.ts +0 -0
- /package/dist/{hooks → react/hooks}/useToolName.d.ts +0 -0
- /package/src/{hooks → react/hooks}/useSendFollowUpMessage.ts +0 -0
- /package/src/{hooks → react/hooks}/useToolInput.ts +0 -0
|
@@ -35,7 +35,9 @@ const getRawValue = (node: ValueNode): any => {
|
|
|
35
35
|
return acc;
|
|
36
36
|
}, {});
|
|
37
37
|
default:
|
|
38
|
-
throw new Error(
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Error when parsing directive values: unexpected type '${node.kind}'`
|
|
40
|
+
);
|
|
39
41
|
}
|
|
40
42
|
};
|
|
41
43
|
|
|
@@ -48,7 +50,9 @@ const getTypedDirectiveArgument = (
|
|
|
48
50
|
return undefined;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
let argument = directiveArguments.find(
|
|
53
|
+
let argument = directiveArguments.find(
|
|
54
|
+
(directiveArgument) => directiveArgument.name.value === argumentName
|
|
55
|
+
);
|
|
52
56
|
|
|
53
57
|
if (!argument) {
|
|
54
58
|
return undefined;
|
|
@@ -80,39 +84,66 @@ export const ApplicationManifestPlugin = () => {
|
|
|
80
84
|
const client = new ApolloClient({
|
|
81
85
|
cache: clientCache,
|
|
82
86
|
link: new ApolloLink((operation) => {
|
|
83
|
-
const body = print(
|
|
87
|
+
const body = print(
|
|
88
|
+
removeClientDirective(sortTopLevelDefinitions(operation.query))
|
|
89
|
+
);
|
|
84
90
|
const name = operation.operationName;
|
|
85
91
|
const variables = (
|
|
86
|
-
operation.query.definitions.find(
|
|
92
|
+
operation.query.definitions.find(
|
|
93
|
+
(d) => d.kind === "OperationDefinition"
|
|
94
|
+
) as OperationDefinitionNode
|
|
87
95
|
).variableDefinitions?.reduce(
|
|
88
|
-
(obj, varDef) => ({
|
|
96
|
+
(obj, varDef) => ({
|
|
97
|
+
...obj,
|
|
98
|
+
[varDef.variable.name.value]: getTypeName(varDef.type),
|
|
99
|
+
}),
|
|
89
100
|
{}
|
|
90
101
|
);
|
|
91
102
|
const type = (
|
|
92
|
-
operation.query.definitions.find(
|
|
103
|
+
operation.query.definitions.find(
|
|
104
|
+
(d) => d.kind === "OperationDefinition"
|
|
105
|
+
) as OperationDefinitionNode
|
|
93
106
|
).operation;
|
|
94
107
|
const prefetch = (
|
|
95
|
-
operation.query.definitions.find(
|
|
108
|
+
operation.query.definitions.find(
|
|
109
|
+
(d) => d.kind === "OperationDefinition"
|
|
110
|
+
) as OperationDefinitionNode
|
|
96
111
|
).directives?.some((d) => d.name.value === "prefetch");
|
|
97
112
|
const id = createHash("sha256").update(body).digest("hex");
|
|
98
113
|
// 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
|
|
99
114
|
const prefetchID = prefetch ? "__anonymous" : undefined;
|
|
100
115
|
|
|
101
116
|
const tools = (
|
|
102
|
-
operation.query.definitions.find(
|
|
117
|
+
operation.query.definitions.find(
|
|
118
|
+
(d) => d.kind === "OperationDefinition"
|
|
119
|
+
) as OperationDefinitionNode
|
|
103
120
|
).directives
|
|
104
121
|
?.filter((d) => d.name.value === "tool")
|
|
105
122
|
.map((directive) => {
|
|
106
|
-
const name = getTypedDirectiveArgument(
|
|
107
|
-
|
|
108
|
-
|
|
123
|
+
const name = getTypedDirectiveArgument(
|
|
124
|
+
"name",
|
|
125
|
+
Kind.STRING,
|
|
126
|
+
directive.arguments
|
|
127
|
+
);
|
|
128
|
+
const description = getTypedDirectiveArgument(
|
|
129
|
+
"description",
|
|
130
|
+
Kind.STRING,
|
|
131
|
+
directive.arguments
|
|
132
|
+
);
|
|
133
|
+
const extraInputs = getTypedDirectiveArgument(
|
|
134
|
+
"extraInputs",
|
|
135
|
+
Kind.LIST,
|
|
136
|
+
directive.arguments
|
|
137
|
+
);
|
|
109
138
|
|
|
110
139
|
if (!name) {
|
|
111
140
|
throw new Error("'name' argument must be supplied for @tool");
|
|
112
141
|
}
|
|
113
142
|
|
|
114
143
|
if (!description) {
|
|
115
|
-
throw new Error(
|
|
144
|
+
throw new Error(
|
|
145
|
+
"'description' argument must be supplied for @tool"
|
|
146
|
+
);
|
|
116
147
|
}
|
|
117
148
|
|
|
118
149
|
return {
|
|
@@ -122,7 +153,9 @@ export const ApplicationManifestPlugin = () => {
|
|
|
122
153
|
};
|
|
123
154
|
});
|
|
124
155
|
|
|
125
|
-
return Observable.of({
|
|
156
|
+
return Observable.of({
|
|
157
|
+
data: { id, name, type, body, variables, prefetch, prefetchID, tools },
|
|
158
|
+
});
|
|
126
159
|
}),
|
|
127
160
|
});
|
|
128
161
|
|
|
@@ -146,16 +179,27 @@ export const ApplicationManifestPlugin = () => {
|
|
|
146
179
|
|
|
147
180
|
const operations = [];
|
|
148
181
|
for (const source of sources) {
|
|
149
|
-
const type = (
|
|
150
|
-
.
|
|
182
|
+
const type = (
|
|
183
|
+
source.node.definitions.find(
|
|
184
|
+
(d) => d.kind === "OperationDefinition"
|
|
185
|
+
) as OperationDefinitionNode
|
|
186
|
+
).operation;
|
|
151
187
|
|
|
152
188
|
let result;
|
|
153
189
|
if (type === "query") {
|
|
154
|
-
result = await client.query({
|
|
190
|
+
result = await client.query({
|
|
191
|
+
query: source.node,
|
|
192
|
+
fetchPolicy: "no-cache",
|
|
193
|
+
});
|
|
155
194
|
} else if (type === "mutation") {
|
|
156
|
-
result = await client.mutate({
|
|
195
|
+
result = await client.mutate({
|
|
196
|
+
mutation: source.node,
|
|
197
|
+
fetchPolicy: "no-cache",
|
|
198
|
+
});
|
|
157
199
|
} else {
|
|
158
|
-
throw new Error(
|
|
200
|
+
throw new Error(
|
|
201
|
+
"Found an unsupported operation type. Only Query and Mutation are supported."
|
|
202
|
+
);
|
|
159
203
|
}
|
|
160
204
|
operations.push(result.data);
|
|
161
205
|
}
|
|
@@ -168,7 +212,9 @@ export const ApplicationManifestPlugin = () => {
|
|
|
168
212
|
};
|
|
169
213
|
|
|
170
214
|
const generateManifest = async () => {
|
|
171
|
-
const operations = Array.from(cache.values()).flatMap(
|
|
215
|
+
const operations = Array.from(cache.values()).flatMap(
|
|
216
|
+
(entry) => entry.operations
|
|
217
|
+
);
|
|
172
218
|
if (operations.filter((o) => o.prefetch).length > 1) {
|
|
173
219
|
throw new Error(
|
|
174
220
|
"Found multiple operations marked as `@prefetch`. You can only mark 1 operation with `@prefetch`."
|
|
@@ -199,7 +245,9 @@ export const ApplicationManifestPlugin = () => {
|
|
|
199
245
|
name: packageJson.name,
|
|
200
246
|
description: packageJson.description,
|
|
201
247
|
hash: createHash("sha256").update(Date.now().toString()).digest("hex"),
|
|
202
|
-
operations: Array.from(cache.values()).flatMap(
|
|
248
|
+
operations: Array.from(cache.values()).flatMap(
|
|
249
|
+
(entry) => entry.operations
|
|
250
|
+
),
|
|
203
251
|
resource,
|
|
204
252
|
csp: {
|
|
205
253
|
connectDomains: packageJson.csp?.connectDomains ?? [],
|
|
@@ -208,7 +256,11 @@ export const ApplicationManifestPlugin = () => {
|
|
|
208
256
|
};
|
|
209
257
|
|
|
210
258
|
// Always write to build directory so the MCP server picks it up
|
|
211
|
-
const dest = path.resolve(
|
|
259
|
+
const dest = path.resolve(
|
|
260
|
+
root,
|
|
261
|
+
config.build.outDir,
|
|
262
|
+
".application-manifest.json"
|
|
263
|
+
);
|
|
212
264
|
mkdirSync(path.dirname(dest), { recursive: true });
|
|
213
265
|
writeFileSync(dest, JSON.stringify(manifest));
|
|
214
266
|
|
|
@@ -279,8 +331,14 @@ export function sortTopLevelDefinitions(query: DocumentNode): DocumentNode {
|
|
|
279
331
|
// non-executable definitions don't have to have names (even though any
|
|
280
332
|
// DocumentNode actually passed here should only have executable
|
|
281
333
|
// definitions).
|
|
282
|
-
const aName =
|
|
283
|
-
|
|
334
|
+
const aName =
|
|
335
|
+
a.kind === "OperationDefinition" || a.kind === "FragmentDefinition" ?
|
|
336
|
+
(a.name?.value ?? "")
|
|
337
|
+
: "";
|
|
338
|
+
const bName =
|
|
339
|
+
b.kind === "OperationDefinition" || b.kind === "FragmentDefinition" ?
|
|
340
|
+
(b.name?.value ?? "")
|
|
341
|
+
: "";
|
|
284
342
|
|
|
285
343
|
// Sort by name ascending.
|
|
286
344
|
if (aName < bName) {
|
|
@@ -306,7 +364,9 @@ function removeClientDirective(doc: DocumentNode) {
|
|
|
306
364
|
OperationDefinition(node) {
|
|
307
365
|
return {
|
|
308
366
|
...node,
|
|
309
|
-
directives: node.directives?.filter(
|
|
367
|
+
directives: node.directives?.filter(
|
|
368
|
+
(d) => d.name.value !== "prefetch" && d.name.value !== "tool"
|
|
369
|
+
),
|
|
310
370
|
};
|
|
311
371
|
},
|
|
312
372
|
});
|
package/vitest-setup.ts
CHANGED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { ApolloClient } from "@apollo/client";
|
|
2
|
-
import "../types/openai";
|
|
3
|
-
import { ApplicationManifest } from "../types/application-manifest";
|
|
4
|
-
type ExtendedApolloClientOptions = Omit<ApolloClient.Options, "link" | "cache"> & {
|
|
5
|
-
link?: ApolloClient.Options["link"];
|
|
6
|
-
cache?: ApolloClient.Options["cache"];
|
|
7
|
-
manifest: ApplicationManifest;
|
|
8
|
-
};
|
|
9
|
-
export declare class ExtendedApolloClient extends ApolloClient {
|
|
10
|
-
manifest: ApplicationManifest;
|
|
11
|
-
constructor(options: ExtendedApolloClientOptions);
|
|
12
|
-
prefetchData(): Promise<void>;
|
|
13
|
-
}
|
|
14
|
-
export {};
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { ApolloClient, ApolloLink, InMemoryCache } from "@apollo/client";
|
|
2
|
-
import * as Observable from "rxjs";
|
|
3
|
-
import { selectHttpOptionsAndBody } from "@apollo/client/link/http";
|
|
4
|
-
import { fallbackHttpConfig } from "@apollo/client/link/http";
|
|
5
|
-
import { DocumentTransform } from "@apollo/client";
|
|
6
|
-
import { removeDirectivesFromDocument } from "@apollo/client/utilities/internal";
|
|
7
|
-
import { parse } from "graphql";
|
|
8
|
-
import "../types/openai";
|
|
9
|
-
import { ApplicationManifest } from "../types/application-manifest";
|
|
10
|
-
|
|
11
|
-
// TODO: In the future if/when we support PQs again, do pqLink.concat(toolCallLink)
|
|
12
|
-
// Commenting this out for now.
|
|
13
|
-
// import { sha256 } from "crypto-hash";
|
|
14
|
-
// import { PersistedQueryLink } from "@apollo/client/link/persisted-queries";
|
|
15
|
-
// const pqLink = new PersistedQueryLink({
|
|
16
|
-
// sha256: (queryString) => sha256(queryString),
|
|
17
|
-
// });
|
|
18
|
-
|
|
19
|
-
// Normally, ApolloClient uses an HttpLink and sends the graphql request over HTTP
|
|
20
|
-
// In our case, we're are sending the graphql request over the "execute" tool call
|
|
21
|
-
const toolCallLink = new ApolloLink((operation) => {
|
|
22
|
-
const context = operation.getContext();
|
|
23
|
-
const contextConfig = {
|
|
24
|
-
http: context.http,
|
|
25
|
-
options: context.fetchOptions,
|
|
26
|
-
credentials: context.credentials,
|
|
27
|
-
headers: context.headers,
|
|
28
|
-
};
|
|
29
|
-
const { query, variables } = selectHttpOptionsAndBody(operation, fallbackHttpConfig, contextConfig).body;
|
|
30
|
-
|
|
31
|
-
return Observable.from(window.openai.callTool("execute", { query, variables })).pipe(
|
|
32
|
-
Observable.map((result) => ({ data: result.structuredContent.data }))
|
|
33
|
-
);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// This allows us to extend the options with the "manifest" option AND make link/cache optional (they are normally required)
|
|
37
|
-
type ExtendedApolloClientOptions = Omit<ApolloClient.Options, "link" | "cache"> & {
|
|
38
|
-
link?: ApolloClient.Options["link"];
|
|
39
|
-
cache?: ApolloClient.Options["cache"];
|
|
40
|
-
manifest: ApplicationManifest;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export class ExtendedApolloClient extends ApolloClient {
|
|
44
|
-
manifest: ApplicationManifest;
|
|
45
|
-
|
|
46
|
-
constructor(options: ExtendedApolloClientOptions) {
|
|
47
|
-
super({
|
|
48
|
-
link: toolCallLink,
|
|
49
|
-
cache: options.cache ?? new InMemoryCache(),
|
|
50
|
-
// Strip out the prefetch/tool directives so they don't get sent with the operation to the server
|
|
51
|
-
documentTransform: new DocumentTransform((document) => {
|
|
52
|
-
return removeDirectivesFromDocument([{ name: "prefetch" }, { name: "tool" }], document)!;
|
|
53
|
-
}),
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
this.manifest = options.manifest;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async prefetchData() {
|
|
60
|
-
// Write prefetched data to the cache
|
|
61
|
-
this.manifest.operations.forEach((operation) => {
|
|
62
|
-
if (operation.prefetch && operation.prefetchID && window.openai.toolOutput.prefetch?.[operation.prefetchID]) {
|
|
63
|
-
this.writeQuery({
|
|
64
|
-
query: parse(operation.body),
|
|
65
|
-
data: window.openai.toolOutput.prefetch[operation.prefetchID].data,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// If this operation has the tool that matches up with the tool that was executed, write the tool result to the cache
|
|
70
|
-
if (
|
|
71
|
-
operation.tools?.find(
|
|
72
|
-
(tool) => `${this.manifest.name}--${tool.name}` === window.openai.toolResponseMetadata.toolName
|
|
73
|
-
)
|
|
74
|
-
) {
|
|
75
|
-
// We need to include the variables that were used as part of the tool call so that we get a proper cache entry
|
|
76
|
-
// However, we only want to include toolInput's that were graphql operation (ignore extraInputs)
|
|
77
|
-
const variables = Object.keys(window.openai.toolInput).reduce(
|
|
78
|
-
(obj, key) => (operation.variables[key] ? { ...obj, [key]: window.openai.toolInput[key] } : obj),
|
|
79
|
-
{}
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
this.writeQuery({
|
|
83
|
-
query: parse(operation.body),
|
|
84
|
-
data: window.openai.toolOutput.result.data,
|
|
85
|
-
variables,
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
package/src/hooks/useCallTool.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
type UseCallToolResult = <K>(toolId: string, variables?: Record<string, unknown> | undefined) => Promise<K>;
|
|
2
|
-
|
|
3
|
-
export const useCallTool = (): UseCallToolResult => {
|
|
4
|
-
const callTool = async (toolId: string, variables: Record<string, unknown> | undefined = {}) =>
|
|
5
|
-
await window.openai?.callTool(toolId, variables);
|
|
6
|
-
|
|
7
|
-
return callTool;
|
|
8
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState } from "react";
|
|
2
|
-
import { useToolName } from "./useToolName";
|
|
3
|
-
import { useToolInput } from "./useToolInput";
|
|
4
|
-
|
|
5
|
-
type ToolUseState = {
|
|
6
|
-
appName: string;
|
|
7
|
-
hasNavigated: boolean;
|
|
8
|
-
setHasNavigated: (v: boolean) => void;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const ToolUseContext = React.createContext<ToolUseState | null>(null);
|
|
12
|
-
|
|
13
|
-
export function ToolUseProvider({ children, appName }: { children: any; appName: string }) {
|
|
14
|
-
const [hasNavigated, setHasNavigated] = useState(false);
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<ToolUseContext.Provider value={{ hasNavigated, setHasNavigated, appName }}>{children}</ToolUseContext.Provider>
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const useToolEffect = (
|
|
22
|
-
toolName: string | string[],
|
|
23
|
-
effect: (toolInput: any) => void,
|
|
24
|
-
deps: React.DependencyList = []
|
|
25
|
-
) => {
|
|
26
|
-
const ctx = React.useContext(ToolUseContext);
|
|
27
|
-
const fullToolName = useToolName();
|
|
28
|
-
const toolInput = useToolInput();
|
|
29
|
-
if (!ctx) throw new Error("useToolEffect must be used within ToolUseProvider");
|
|
30
|
-
|
|
31
|
-
const toolNames = Array.isArray(toolName) ? toolName : [toolName];
|
|
32
|
-
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
const matches = toolNames.some((name) => fullToolName === `${ctx.appName}--${name}`);
|
|
35
|
-
|
|
36
|
-
if (!ctx.hasNavigated && matches) {
|
|
37
|
-
effect(toolInput);
|
|
38
|
-
ctx.setHasNavigated(true);
|
|
39
|
-
}
|
|
40
|
-
}, [ctx.hasNavigated, ctx.setHasNavigated, ctx.appName, toolNames, fullToolName, toolInput, ...deps]);
|
|
41
|
-
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|