@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,5 +1,5 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
-
import { spyOnConsole } from "../../testing/internal/index.js";
|
|
2
|
+
import { spyOnConsole, wait } from "../../testing/internal/index.js";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import { gql, type DocumentNode } from "@apollo/client";
|
|
5
5
|
import { getMainDefinition, print } from "@apollo/client/utilities";
|
|
@@ -676,11 +676,11 @@ describe("@prefetch", () => {
|
|
|
676
676
|
});
|
|
677
677
|
|
|
678
678
|
describe("@tool validation", () => {
|
|
679
|
-
test("errors when tool name is not provided", async () => {
|
|
679
|
+
test("errors when tool name is not provided on anonymous operation", async () => {
|
|
680
680
|
vol.fromJSON({
|
|
681
681
|
"package.json": mockPackageJson(),
|
|
682
682
|
"src/my-component.tsx": declareOperation(gql`
|
|
683
|
-
query
|
|
683
|
+
query @tool {
|
|
684
684
|
helloWorld
|
|
685
685
|
}
|
|
686
686
|
`),
|
|
@@ -694,11 +694,11 @@ describe("@tool validation", () => {
|
|
|
694
694
|
});
|
|
695
695
|
await server.listen();
|
|
696
696
|
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
697
|
-
`[Error:
|
|
697
|
+
`[Error: Anonymous operations cannot use @tool without providing a 'name' argument]`
|
|
698
698
|
);
|
|
699
699
|
});
|
|
700
700
|
|
|
701
|
-
test("errors when tool description is not provided", async () => {
|
|
701
|
+
test("errors when tool description is not provided and operation has no description", async () => {
|
|
702
702
|
vol.fromJSON({
|
|
703
703
|
"package.json": mockPackageJson(),
|
|
704
704
|
"src/my-component.tsx": declareOperation(gql`
|
|
@@ -716,10 +716,110 @@ describe("@tool validation", () => {
|
|
|
716
716
|
});
|
|
717
717
|
await server.listen();
|
|
718
718
|
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
719
|
-
`[Error: 'description' argument must
|
|
719
|
+
`[Error: Operations using @tool without a 'description' argument must have a description on the operation definition]`
|
|
720
720
|
);
|
|
721
721
|
});
|
|
722
722
|
|
|
723
|
+
test("uses operation name as tool name when name is omitted from @tool", async () => {
|
|
724
|
+
vol.fromJSON({
|
|
725
|
+
"package.json": mockPackageJson(),
|
|
726
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
727
|
+
query HelloWorldQuery @tool(description: "A greeting tool") {
|
|
728
|
+
helloWorld
|
|
729
|
+
}
|
|
730
|
+
`),
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
await using server = await setupServer({
|
|
734
|
+
plugins: [
|
|
735
|
+
apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
|
|
736
|
+
],
|
|
737
|
+
});
|
|
738
|
+
await server.listen();
|
|
739
|
+
|
|
740
|
+
const manifest = readManifestFile();
|
|
741
|
+
expect(manifest.operations[0].tools).toMatchInlineSnapshot(`
|
|
742
|
+
[
|
|
743
|
+
{
|
|
744
|
+
"description": "A greeting tool",
|
|
745
|
+
"name": "HelloWorldQuery",
|
|
746
|
+
},
|
|
747
|
+
]
|
|
748
|
+
`);
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
test("uses operation description as tool description when description is omitted from @tool", async () => {
|
|
752
|
+
vol.fromJSON({
|
|
753
|
+
"package.json": mockPackageJson(),
|
|
754
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
755
|
+
"""
|
|
756
|
+
A greeting tool
|
|
757
|
+
"""
|
|
758
|
+
query HelloWorldQuery @tool(name: "hello-world") {
|
|
759
|
+
helloWorld
|
|
760
|
+
}
|
|
761
|
+
`),
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
await using server = await setupServer({
|
|
765
|
+
plugins: [
|
|
766
|
+
apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
|
|
767
|
+
],
|
|
768
|
+
});
|
|
769
|
+
await server.listen();
|
|
770
|
+
|
|
771
|
+
const manifest = readManifestFile();
|
|
772
|
+
expect(manifest.operations[0].tools).toMatchInlineSnapshot(`
|
|
773
|
+
[
|
|
774
|
+
{
|
|
775
|
+
"description": "A greeting tool",
|
|
776
|
+
"name": "hello-world",
|
|
777
|
+
},
|
|
778
|
+
]
|
|
779
|
+
`);
|
|
780
|
+
expect(manifest.operations[0].body).toMatchInlineSnapshot(`
|
|
781
|
+
"query HelloWorldQuery {
|
|
782
|
+
helloWorld
|
|
783
|
+
}"
|
|
784
|
+
`);
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
test("uses operation name and description when both are omitted from @tool", async () => {
|
|
788
|
+
vol.fromJSON({
|
|
789
|
+
"package.json": mockPackageJson(),
|
|
790
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
791
|
+
"""
|
|
792
|
+
A greeting tool
|
|
793
|
+
"""
|
|
794
|
+
query HelloWorldQuery @tool {
|
|
795
|
+
helloWorld
|
|
796
|
+
}
|
|
797
|
+
`),
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
await using server = await setupServer({
|
|
801
|
+
plugins: [
|
|
802
|
+
apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
|
|
803
|
+
],
|
|
804
|
+
});
|
|
805
|
+
await server.listen();
|
|
806
|
+
|
|
807
|
+
const manifest = readManifestFile();
|
|
808
|
+
expect(manifest.operations[0].tools).toMatchInlineSnapshot(`
|
|
809
|
+
[
|
|
810
|
+
{
|
|
811
|
+
"description": "A greeting tool",
|
|
812
|
+
"name": "HelloWorldQuery",
|
|
813
|
+
},
|
|
814
|
+
]
|
|
815
|
+
`);
|
|
816
|
+
expect(manifest.operations[0].body).toMatchInlineSnapshot(`
|
|
817
|
+
"query HelloWorldQuery {
|
|
818
|
+
helloWorld
|
|
819
|
+
}"
|
|
820
|
+
`);
|
|
821
|
+
});
|
|
822
|
+
|
|
723
823
|
test("errors when tool name contains spaces", async () => {
|
|
724
824
|
vol.fromJSON({
|
|
725
825
|
"package.json": mockPackageJson(),
|
|
@@ -839,6 +939,54 @@ describe("@tool validation", () => {
|
|
|
839
939
|
`[Error: Error when parsing directive values: unexpected type 'FloatValue']`
|
|
840
940
|
);
|
|
841
941
|
});
|
|
942
|
+
|
|
943
|
+
test("errors when multiple @tool directives are used and one is missing a name", async () => {
|
|
944
|
+
vol.fromJSON({
|
|
945
|
+
"package.json": mockPackageJson(),
|
|
946
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
947
|
+
query HelloWorldQuery
|
|
948
|
+
@tool(name: "tool-a", description: "Tool A")
|
|
949
|
+
@tool(description: "Tool B") {
|
|
950
|
+
helloWorld
|
|
951
|
+
}
|
|
952
|
+
`),
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
await expect(async () => {
|
|
956
|
+
await using server = await setupServer({
|
|
957
|
+
plugins: [
|
|
958
|
+
apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
|
|
959
|
+
],
|
|
960
|
+
});
|
|
961
|
+
await server.listen();
|
|
962
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
963
|
+
`[Error: Operations with multiple @tool directives must provide a 'name' argument on each @tool]`
|
|
964
|
+
);
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
test("errors when multiple @tool directives are used and one is missing a description", async () => {
|
|
968
|
+
vol.fromJSON({
|
|
969
|
+
"package.json": mockPackageJson(),
|
|
970
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
971
|
+
query HelloWorldQuery
|
|
972
|
+
@tool(name: "tool-a", description: "Tool A")
|
|
973
|
+
@tool(name: "tool-b") {
|
|
974
|
+
helloWorld
|
|
975
|
+
}
|
|
976
|
+
`),
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
await expect(async () => {
|
|
980
|
+
await using server = await setupServer({
|
|
981
|
+
plugins: [
|
|
982
|
+
apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
|
|
983
|
+
],
|
|
984
|
+
});
|
|
985
|
+
await server.listen();
|
|
986
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
987
|
+
`[Error: Operations with multiple @tool directives must provide a 'description' argument on each @tool]`
|
|
988
|
+
);
|
|
989
|
+
});
|
|
842
990
|
});
|
|
843
991
|
|
|
844
992
|
describe("config validation", () => {
|
|
@@ -1473,6 +1621,71 @@ describe("entry points", () => {
|
|
|
1473
1621
|
true
|
|
1474
1622
|
);
|
|
1475
1623
|
});
|
|
1624
|
+
|
|
1625
|
+
test("generates .application-manifest.d.json.ts at root in build mode", async () => {
|
|
1626
|
+
vol.fromJSON({
|
|
1627
|
+
"package.json": mockPackageJson(),
|
|
1628
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
1629
|
+
query HelloWorldQuery
|
|
1630
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
1631
|
+
helloWorld
|
|
1632
|
+
}
|
|
1633
|
+
`),
|
|
1634
|
+
});
|
|
1635
|
+
|
|
1636
|
+
await buildApp({
|
|
1637
|
+
mode: "production",
|
|
1638
|
+
plugins: [
|
|
1639
|
+
apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
|
|
1640
|
+
],
|
|
1641
|
+
});
|
|
1642
|
+
|
|
1643
|
+
expect(vol.existsSync(".application-manifest.d.json.ts")).toBe(true);
|
|
1644
|
+
});
|
|
1645
|
+
|
|
1646
|
+
test("generates .application-manifest.d.json.ts at root in serve mode", async () => {
|
|
1647
|
+
vol.fromJSON({
|
|
1648
|
+
"package.json": mockPackageJson(),
|
|
1649
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
1650
|
+
query HelloWorldQuery
|
|
1651
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
1652
|
+
helloWorld
|
|
1653
|
+
}
|
|
1654
|
+
`),
|
|
1655
|
+
});
|
|
1656
|
+
|
|
1657
|
+
await using server = await setupServer({
|
|
1658
|
+
plugins: [
|
|
1659
|
+
apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
|
|
1660
|
+
],
|
|
1661
|
+
});
|
|
1662
|
+
await server.listen();
|
|
1663
|
+
|
|
1664
|
+
expect(vol.existsSync(".application-manifest.d.json.ts")).toBe(true);
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1667
|
+
test("does not write .application-manifest.json.d.ts to appsOutDir", async () => {
|
|
1668
|
+
vol.fromJSON({
|
|
1669
|
+
"package.json": mockPackageJson(),
|
|
1670
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
1671
|
+
query HelloWorldQuery
|
|
1672
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
1673
|
+
helloWorld
|
|
1674
|
+
}
|
|
1675
|
+
`),
|
|
1676
|
+
});
|
|
1677
|
+
|
|
1678
|
+
await buildApp({
|
|
1679
|
+
mode: "production",
|
|
1680
|
+
plugins: [
|
|
1681
|
+
apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
|
|
1682
|
+
],
|
|
1683
|
+
});
|
|
1684
|
+
|
|
1685
|
+
expect(
|
|
1686
|
+
vol.existsSync("dist/apps/my-app/.application-manifest.json.d.ts")
|
|
1687
|
+
).toBe(false);
|
|
1688
|
+
});
|
|
1476
1689
|
});
|
|
1477
1690
|
|
|
1478
1691
|
describe("appsOutDir", () => {
|
|
@@ -1695,6 +1908,541 @@ describe("file watching", () => {
|
|
|
1695
1908
|
});
|
|
1696
1909
|
});
|
|
1697
1910
|
|
|
1911
|
+
describe("tool input types", () => {
|
|
1912
|
+
const schema = `
|
|
1913
|
+
type Query {
|
|
1914
|
+
todo(id: ID!): Todo
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
type Mutation {
|
|
1918
|
+
createTodo(title: String!, description: String): Todo
|
|
1919
|
+
deleteTodo(id: ID!): Boolean
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
type Todo {
|
|
1923
|
+
id: ID!
|
|
1924
|
+
title: String!
|
|
1925
|
+
description: String
|
|
1926
|
+
}
|
|
1927
|
+
`;
|
|
1928
|
+
|
|
1929
|
+
test("generates operation-types.d.ts with variable types when schema is provided", async () => {
|
|
1930
|
+
vol.fromJSON({
|
|
1931
|
+
"package.json": mockPackageJson(),
|
|
1932
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
1933
|
+
mutation CreateTodo($title: String!, $description: String)
|
|
1934
|
+
@tool(name: "CreateTodo", description: "Creates a todo") {
|
|
1935
|
+
createTodo(title: $title, description: $description) {
|
|
1936
|
+
id
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
`),
|
|
1940
|
+
});
|
|
1941
|
+
|
|
1942
|
+
await using server = await setupServer({
|
|
1943
|
+
plugins: [
|
|
1944
|
+
apolloClientAiApps({
|
|
1945
|
+
targets: ["mcp"],
|
|
1946
|
+
appsOutDir: "dist/apps",
|
|
1947
|
+
schema,
|
|
1948
|
+
}),
|
|
1949
|
+
],
|
|
1950
|
+
});
|
|
1951
|
+
await server.listen();
|
|
1952
|
+
|
|
1953
|
+
const content = fs.readFileSync(
|
|
1954
|
+
".apollo-client-ai-apps/types/operation-types.d.ts",
|
|
1955
|
+
"utf-8"
|
|
1956
|
+
);
|
|
1957
|
+
expect(content).toMatchInlineSnapshot(`
|
|
1958
|
+
"// Auto-generated by @apollo/client-ai-apps. Do not edit manually.
|
|
1959
|
+
export type Maybe<T> = T | null;
|
|
1960
|
+
|
|
1961
|
+
export type InputMaybe<T> = Maybe<T>;
|
|
1962
|
+
|
|
1963
|
+
export type Exact<T extends {
|
|
1964
|
+
[key: string]: unknown;
|
|
1965
|
+
}> = {
|
|
1966
|
+
[K in keyof T]: T[K];
|
|
1967
|
+
};
|
|
1968
|
+
|
|
1969
|
+
/** All built-in and custom scalars, mapped to their actual values */
|
|
1970
|
+
export type Scalars = {
|
|
1971
|
+
ID: {
|
|
1972
|
+
input: string;
|
|
1973
|
+
output: string;
|
|
1974
|
+
};
|
|
1975
|
+
String: {
|
|
1976
|
+
input: string;
|
|
1977
|
+
output: string;
|
|
1978
|
+
};
|
|
1979
|
+
Boolean: {
|
|
1980
|
+
input: boolean;
|
|
1981
|
+
output: boolean;
|
|
1982
|
+
};
|
|
1983
|
+
Int: {
|
|
1984
|
+
input: number;
|
|
1985
|
+
output: number;
|
|
1986
|
+
};
|
|
1987
|
+
Float: {
|
|
1988
|
+
input: number;
|
|
1989
|
+
output: number;
|
|
1990
|
+
};
|
|
1991
|
+
};
|
|
1992
|
+
|
|
1993
|
+
export type CreateTodoMutationVariables = Exact<{
|
|
1994
|
+
title: Scalars["String"]["input"];
|
|
1995
|
+
description?: InputMaybe<Scalars["String"]["input"]>;
|
|
1996
|
+
}>;"
|
|
1997
|
+
`);
|
|
1998
|
+
});
|
|
1999
|
+
|
|
2000
|
+
test("generates register.d.ts with toolInputs when schema is provided", async () => {
|
|
2001
|
+
vol.fromJSON({
|
|
2002
|
+
"package.json": mockPackageJson(),
|
|
2003
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
2004
|
+
mutation CreateTodo($title: String!, $description: String)
|
|
2005
|
+
@tool(name: "CreateTodo", description: "Creates a todo") {
|
|
2006
|
+
createTodo(title: $title, description: $description) {
|
|
2007
|
+
id
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
`),
|
|
2011
|
+
});
|
|
2012
|
+
|
|
2013
|
+
await using server = await setupServer({
|
|
2014
|
+
plugins: [
|
|
2015
|
+
apolloClientAiApps({
|
|
2016
|
+
targets: ["mcp"],
|
|
2017
|
+
appsOutDir: "dist/apps",
|
|
2018
|
+
schema,
|
|
2019
|
+
}),
|
|
2020
|
+
],
|
|
2021
|
+
});
|
|
2022
|
+
await server.listen();
|
|
2023
|
+
|
|
2024
|
+
const content = fs.readFileSync(
|
|
2025
|
+
".apollo-client-ai-apps/types/register.d.ts",
|
|
2026
|
+
"utf-8"
|
|
2027
|
+
);
|
|
2028
|
+
expect(content).toMatchInlineSnapshot(`
|
|
2029
|
+
"// This file is auto-generated by @apollo/client-ai-apps. Do not edit manually.
|
|
2030
|
+
import "@apollo/client-ai-apps";
|
|
2031
|
+
|
|
2032
|
+
import type { CreateTodoMutationVariables } from "./operation-types.js";
|
|
2033
|
+
|
|
2034
|
+
declare module "@apollo/client-ai-apps" {
|
|
2035
|
+
interface Register {
|
|
2036
|
+
toolName: "CreateTodo";
|
|
2037
|
+
toolInputs: {
|
|
2038
|
+
CreateTodo: CreateTodoMutationVariables;
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
}"
|
|
2042
|
+
`);
|
|
2043
|
+
});
|
|
2044
|
+
|
|
2045
|
+
test("generates register.d.ts with operations that contain fragments", async () => {
|
|
2046
|
+
vol.fromJSON({
|
|
2047
|
+
"package.json": mockPackageJson(),
|
|
2048
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
2049
|
+
mutation CreateTodo($title: String!, $description: String)
|
|
2050
|
+
@tool(name: "CreateTodo", description: "Creates a todo") {
|
|
2051
|
+
createTodo(title: $title, description: $description) {
|
|
2052
|
+
id
|
|
2053
|
+
...TodoFragment
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
`),
|
|
2057
|
+
"src/todo-fragment.tsx": declareFragment(gql`
|
|
2058
|
+
fragment TodoFragment on Todo {
|
|
2059
|
+
title
|
|
2060
|
+
}
|
|
2061
|
+
`),
|
|
2062
|
+
});
|
|
2063
|
+
|
|
2064
|
+
await using server = await setupServer({
|
|
2065
|
+
plugins: [
|
|
2066
|
+
apolloClientAiApps({
|
|
2067
|
+
targets: ["mcp"],
|
|
2068
|
+
appsOutDir: "dist/apps",
|
|
2069
|
+
schema,
|
|
2070
|
+
}),
|
|
2071
|
+
],
|
|
2072
|
+
});
|
|
2073
|
+
await server.listen();
|
|
2074
|
+
|
|
2075
|
+
expect(
|
|
2076
|
+
fs.readFileSync(".apollo-client-ai-apps/types/register.d.ts", "utf-8")
|
|
2077
|
+
).toMatchInlineSnapshot(`
|
|
2078
|
+
"// This file is auto-generated by @apollo/client-ai-apps. Do not edit manually.
|
|
2079
|
+
import "@apollo/client-ai-apps";
|
|
2080
|
+
|
|
2081
|
+
import type { CreateTodoMutationVariables } from "./operation-types.js";
|
|
2082
|
+
|
|
2083
|
+
declare module "@apollo/client-ai-apps" {
|
|
2084
|
+
interface Register {
|
|
2085
|
+
toolName: "CreateTodo";
|
|
2086
|
+
toolInputs: {
|
|
2087
|
+
CreateTodo: CreateTodoMutationVariables;
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
}"
|
|
2091
|
+
`);
|
|
2092
|
+
expect(
|
|
2093
|
+
fs.readFileSync(
|
|
2094
|
+
".apollo-client-ai-apps/types/operation-types.d.ts",
|
|
2095
|
+
"utf-8"
|
|
2096
|
+
)
|
|
2097
|
+
).toMatchInlineSnapshot(`
|
|
2098
|
+
"// Auto-generated by @apollo/client-ai-apps. Do not edit manually.
|
|
2099
|
+
export type Maybe<T> = T | null;
|
|
2100
|
+
|
|
2101
|
+
export type InputMaybe<T> = Maybe<T>;
|
|
2102
|
+
|
|
2103
|
+
export type Exact<T extends {
|
|
2104
|
+
[key: string]: unknown;
|
|
2105
|
+
}> = {
|
|
2106
|
+
[K in keyof T]: T[K];
|
|
2107
|
+
};
|
|
2108
|
+
|
|
2109
|
+
/** All built-in and custom scalars, mapped to their actual values */
|
|
2110
|
+
export type Scalars = {
|
|
2111
|
+
ID: {
|
|
2112
|
+
input: string;
|
|
2113
|
+
output: string;
|
|
2114
|
+
};
|
|
2115
|
+
String: {
|
|
2116
|
+
input: string;
|
|
2117
|
+
output: string;
|
|
2118
|
+
};
|
|
2119
|
+
Boolean: {
|
|
2120
|
+
input: boolean;
|
|
2121
|
+
output: boolean;
|
|
2122
|
+
};
|
|
2123
|
+
Int: {
|
|
2124
|
+
input: number;
|
|
2125
|
+
output: number;
|
|
2126
|
+
};
|
|
2127
|
+
Float: {
|
|
2128
|
+
input: number;
|
|
2129
|
+
output: number;
|
|
2130
|
+
};
|
|
2131
|
+
};
|
|
2132
|
+
|
|
2133
|
+
export type CreateTodoMutationVariables = Exact<{
|
|
2134
|
+
title: Scalars["String"]["input"];
|
|
2135
|
+
description?: InputMaybe<Scalars["String"]["input"]>;
|
|
2136
|
+
}>;"
|
|
2137
|
+
`);
|
|
2138
|
+
});
|
|
2139
|
+
|
|
2140
|
+
test("tool with no extraInputs uses only the Variables type", async () => {
|
|
2141
|
+
vol.fromJSON({
|
|
2142
|
+
"package.json": mockPackageJson(),
|
|
2143
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
2144
|
+
mutation DeleteTodo($id: ID!)
|
|
2145
|
+
@tool(name: "DeleteTodo", description: "Deletes a todo") {
|
|
2146
|
+
deleteTodo(id: $id)
|
|
2147
|
+
}
|
|
2148
|
+
`),
|
|
2149
|
+
});
|
|
2150
|
+
|
|
2151
|
+
await using server = await setupServer({
|
|
2152
|
+
plugins: [
|
|
2153
|
+
apolloClientAiApps({
|
|
2154
|
+
targets: ["mcp"],
|
|
2155
|
+
appsOutDir: "dist/apps",
|
|
2156
|
+
schema,
|
|
2157
|
+
}),
|
|
2158
|
+
],
|
|
2159
|
+
});
|
|
2160
|
+
await server.listen();
|
|
2161
|
+
|
|
2162
|
+
const content = fs.readFileSync(
|
|
2163
|
+
".apollo-client-ai-apps/types/register.d.ts",
|
|
2164
|
+
"utf-8"
|
|
2165
|
+
);
|
|
2166
|
+
expect(content).toMatchInlineSnapshot(`
|
|
2167
|
+
"// This file is auto-generated by @apollo/client-ai-apps. Do not edit manually.
|
|
2168
|
+
import "@apollo/client-ai-apps";
|
|
2169
|
+
|
|
2170
|
+
import type { DeleteTodoMutationVariables } from "./operation-types.js";
|
|
2171
|
+
|
|
2172
|
+
declare module "@apollo/client-ai-apps" {
|
|
2173
|
+
interface Register {
|
|
2174
|
+
toolName: "DeleteTodo";
|
|
2175
|
+
toolInputs: {
|
|
2176
|
+
DeleteTodo: DeleteTodoMutationVariables;
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2179
|
+
}"
|
|
2180
|
+
`);
|
|
2181
|
+
});
|
|
2182
|
+
|
|
2183
|
+
test("tool with extraInputs adds properties to tool input type", async () => {
|
|
2184
|
+
vol.fromJSON({
|
|
2185
|
+
"package.json": mockPackageJson(),
|
|
2186
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
2187
|
+
mutation CreateTodo($title: String!)
|
|
2188
|
+
@tool(
|
|
2189
|
+
name: "CreateTodo"
|
|
2190
|
+
description: "Creates a todo"
|
|
2191
|
+
extraInputs: [
|
|
2192
|
+
{ name: "priority", type: "string", description: "Priority" }
|
|
2193
|
+
{ name: "urgent", type: "boolean", description: "Is urgent?" }
|
|
2194
|
+
{
|
|
2195
|
+
name: "odd-name"
|
|
2196
|
+
type: "boolean"
|
|
2197
|
+
description: "Test of odd name"
|
|
2198
|
+
}
|
|
2199
|
+
]
|
|
2200
|
+
) {
|
|
2201
|
+
createTodo(title: $title) {
|
|
2202
|
+
id
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
`),
|
|
2206
|
+
});
|
|
2207
|
+
|
|
2208
|
+
await using server = await setupServer({
|
|
2209
|
+
plugins: [
|
|
2210
|
+
apolloClientAiApps({
|
|
2211
|
+
targets: ["mcp"],
|
|
2212
|
+
appsOutDir: "dist/apps",
|
|
2213
|
+
schema,
|
|
2214
|
+
}),
|
|
2215
|
+
],
|
|
2216
|
+
});
|
|
2217
|
+
await server.listen();
|
|
2218
|
+
|
|
2219
|
+
const content = fs.readFileSync(
|
|
2220
|
+
".apollo-client-ai-apps/types/register.d.ts",
|
|
2221
|
+
"utf-8"
|
|
2222
|
+
);
|
|
2223
|
+
expect(content).toMatchInlineSnapshot(`
|
|
2224
|
+
"// This file is auto-generated by @apollo/client-ai-apps. Do not edit manually.
|
|
2225
|
+
import "@apollo/client-ai-apps";
|
|
2226
|
+
|
|
2227
|
+
import type { CreateTodoMutationVariables } from "./operation-types.js";
|
|
2228
|
+
|
|
2229
|
+
declare module "@apollo/client-ai-apps" {
|
|
2230
|
+
interface Register {
|
|
2231
|
+
toolName: "CreateTodo";
|
|
2232
|
+
toolInputs: {
|
|
2233
|
+
CreateTodo: CreateTodoMutationVariables & {
|
|
2234
|
+
priority?: string;
|
|
2235
|
+
urgent?: boolean;
|
|
2236
|
+
"odd-name"?: boolean;
|
|
2237
|
+
};
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
}"
|
|
2241
|
+
`);
|
|
2242
|
+
});
|
|
2243
|
+
|
|
2244
|
+
test("multiple @tool directives on an operation each get their own toolInputs entry", async () => {
|
|
2245
|
+
vol.fromJSON({
|
|
2246
|
+
"package.json": mockPackageJson(),
|
|
2247
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
2248
|
+
mutation CreateTodo($title: String!)
|
|
2249
|
+
@tool(name: "CreateTodo", description: "Creates a todo")
|
|
2250
|
+
@tool(name: "AddTask", description: "Adds a task") {
|
|
2251
|
+
createTodo(title: $title) {
|
|
2252
|
+
id
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
`),
|
|
2256
|
+
});
|
|
2257
|
+
|
|
2258
|
+
await using server = await setupServer({
|
|
2259
|
+
plugins: [
|
|
2260
|
+
apolloClientAiApps({
|
|
2261
|
+
targets: ["mcp"],
|
|
2262
|
+
appsOutDir: "dist/apps",
|
|
2263
|
+
schema,
|
|
2264
|
+
}),
|
|
2265
|
+
],
|
|
2266
|
+
});
|
|
2267
|
+
await server.listen();
|
|
2268
|
+
|
|
2269
|
+
const content = fs.readFileSync(
|
|
2270
|
+
".apollo-client-ai-apps/types/register.d.ts",
|
|
2271
|
+
"utf-8"
|
|
2272
|
+
);
|
|
2273
|
+
expect(content).toMatchInlineSnapshot(`
|
|
2274
|
+
"// This file is auto-generated by @apollo/client-ai-apps. Do not edit manually.
|
|
2275
|
+
import "@apollo/client-ai-apps";
|
|
2276
|
+
|
|
2277
|
+
import type { CreateTodoMutationVariables } from "./operation-types.js";
|
|
2278
|
+
|
|
2279
|
+
declare module "@apollo/client-ai-apps" {
|
|
2280
|
+
interface Register {
|
|
2281
|
+
toolName: "CreateTodo" | "AddTask";
|
|
2282
|
+
toolInputs: {
|
|
2283
|
+
CreateTodo: CreateTodoMutationVariables;
|
|
2284
|
+
AddTask: CreateTodoMutationVariables;
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
}"
|
|
2288
|
+
`);
|
|
2289
|
+
});
|
|
2290
|
+
|
|
2291
|
+
test("operation-types.d.ts is not rewritten when operation content has not changed", async () => {
|
|
2292
|
+
vol.fromJSON({
|
|
2293
|
+
"package.json": mockPackageJson(),
|
|
2294
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
2295
|
+
mutation CreateTodo($title: String!)
|
|
2296
|
+
@tool(name: "CreateTodo", description: "Creates a todo") {
|
|
2297
|
+
createTodo(title: $title) {
|
|
2298
|
+
id
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
`),
|
|
2302
|
+
});
|
|
2303
|
+
|
|
2304
|
+
await using server = await setupServer({
|
|
2305
|
+
plugins: [
|
|
2306
|
+
apolloClientAiApps({
|
|
2307
|
+
targets: ["mcp"],
|
|
2308
|
+
appsOutDir: "dist/apps",
|
|
2309
|
+
schema,
|
|
2310
|
+
}),
|
|
2311
|
+
],
|
|
2312
|
+
});
|
|
2313
|
+
await server.listen();
|
|
2314
|
+
|
|
2315
|
+
const content = fs.readFileSync(
|
|
2316
|
+
".apollo-client-ai-apps/types/operation-types.d.ts",
|
|
2317
|
+
"utf-8"
|
|
2318
|
+
);
|
|
2319
|
+
const mtime = fs.statSync(
|
|
2320
|
+
".apollo-client-ai-apps/types/operation-types.d.ts"
|
|
2321
|
+
).mtimeMs;
|
|
2322
|
+
|
|
2323
|
+
// Trigger another manifest generation (simulating a file change to an unrelated file)
|
|
2324
|
+
server.watcher.emit("change", "package.json");
|
|
2325
|
+
await wait(100);
|
|
2326
|
+
|
|
2327
|
+
expect(
|
|
2328
|
+
fs.readFileSync(
|
|
2329
|
+
".apollo-client-ai-apps/types/operation-types.d.ts",
|
|
2330
|
+
"utf-8"
|
|
2331
|
+
)
|
|
2332
|
+
).toBe(content);
|
|
2333
|
+
|
|
2334
|
+
expect(
|
|
2335
|
+
fs.statSync(".apollo-client-ai-apps/types/operation-types.d.ts").mtimeMs
|
|
2336
|
+
).toBe(mtime);
|
|
2337
|
+
});
|
|
2338
|
+
|
|
2339
|
+
test("does not generate operation-types.d.ts when schema is not provided", async () => {
|
|
2340
|
+
vol.fromJSON({
|
|
2341
|
+
"package.json": mockPackageJson(),
|
|
2342
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
2343
|
+
mutation CreateTodo($title: String!)
|
|
2344
|
+
@tool(name: "CreateTodo", description: "Creates a todo") {
|
|
2345
|
+
createTodo(title: $title) {
|
|
2346
|
+
id
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
`),
|
|
2350
|
+
});
|
|
2351
|
+
|
|
2352
|
+
await using server = await setupServer({
|
|
2353
|
+
plugins: [
|
|
2354
|
+
apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
|
|
2355
|
+
],
|
|
2356
|
+
});
|
|
2357
|
+
await server.listen();
|
|
2358
|
+
|
|
2359
|
+
expect(
|
|
2360
|
+
fs.existsSync(".apollo-client-ai-apps/types/operation-types.d.ts")
|
|
2361
|
+
).toBe(false);
|
|
2362
|
+
});
|
|
2363
|
+
|
|
2364
|
+
test("generates register.d.ts with error message when schema build fails in dev mode", async () => {
|
|
2365
|
+
using _ = spyOnConsole("error");
|
|
2366
|
+
|
|
2367
|
+
vol.fromJSON({
|
|
2368
|
+
"package.json": mockPackageJson(),
|
|
2369
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
2370
|
+
query GetNonExistent
|
|
2371
|
+
@tool(name: "GetNonExistent", description: "A tool") {
|
|
2372
|
+
nonExistentField
|
|
2373
|
+
}
|
|
2374
|
+
`),
|
|
2375
|
+
});
|
|
2376
|
+
|
|
2377
|
+
await using server = await setupServer({
|
|
2378
|
+
plugins: [
|
|
2379
|
+
apolloClientAiApps({
|
|
2380
|
+
targets: ["mcp"],
|
|
2381
|
+
appsOutDir: "dist/apps",
|
|
2382
|
+
schema,
|
|
2383
|
+
}),
|
|
2384
|
+
],
|
|
2385
|
+
});
|
|
2386
|
+
await server.listen();
|
|
2387
|
+
|
|
2388
|
+
expect(console.error).toHaveBeenCalledOnce();
|
|
2389
|
+
|
|
2390
|
+
const content = fs.readFileSync(
|
|
2391
|
+
".apollo-client-ai-apps/types/register.d.ts",
|
|
2392
|
+
"utf-8"
|
|
2393
|
+
);
|
|
2394
|
+
expect(content).toMatchInlineSnapshot(`
|
|
2395
|
+
"// This file is auto-generated by @apollo/client-ai-apps. Do not edit manually.
|
|
2396
|
+
import "@apollo/client-ai-apps";
|
|
2397
|
+
|
|
2398
|
+
declare module "@apollo/client-ai-apps" {
|
|
2399
|
+
interface Register {
|
|
2400
|
+
toolName: "[@apollo/client-ai-apps/vite]: There was an error building generated types. See the vite build output for more details.\\n\\nGraphQL Document Validation failed with 1 errors;\\n Error 0: Cannot query field \\"nonExistentField\\" on type \\"Query\\".\\n at 116185304.graphql:2:3";
|
|
2401
|
+
toolInputs: {
|
|
2402
|
+
"[@apollo/client-ai-apps/vite]: There was an error building generated types. See the vite build output for more details.\\n\\nGraphQL Document Validation failed with 1 errors;\\n Error 0: Cannot query field \\"nonExistentField\\" on type \\"Query\\".\\n at 116185304.graphql:2:3": "[@apollo/client-ai-apps/vite]: There was an error building generated types. See the vite build output for more details.\\n\\nGraphQL Document Validation failed with 1 errors;\\n Error 0: Cannot query field \\"nonExistentField\\" on type \\"Query\\".\\n at 116185304.graphql:2:3";
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
2405
|
+
}"
|
|
2406
|
+
`);
|
|
2407
|
+
});
|
|
2408
|
+
|
|
2409
|
+
test("generates register.d.ts without toolInputs when schema is not provided", async () => {
|
|
2410
|
+
vol.fromJSON({
|
|
2411
|
+
"package.json": mockPackageJson(),
|
|
2412
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
2413
|
+
mutation CreateTodo($title: String!)
|
|
2414
|
+
@tool(name: "CreateTodo", description: "Creates a todo") {
|
|
2415
|
+
createTodo(title: $title) {
|
|
2416
|
+
id
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
`),
|
|
2420
|
+
});
|
|
2421
|
+
|
|
2422
|
+
await using server = await setupServer({
|
|
2423
|
+
plugins: [
|
|
2424
|
+
apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
|
|
2425
|
+
],
|
|
2426
|
+
});
|
|
2427
|
+
await server.listen();
|
|
2428
|
+
|
|
2429
|
+
const content = fs.readFileSync(
|
|
2430
|
+
".apollo-client-ai-apps/types/register.d.ts",
|
|
2431
|
+
"utf-8"
|
|
2432
|
+
);
|
|
2433
|
+
expect(content).toMatchInlineSnapshot(`
|
|
2434
|
+
"// This file is auto-generated by @apollo/client-ai-apps. Do not edit manually.
|
|
2435
|
+
import "@apollo/client-ai-apps";
|
|
2436
|
+
|
|
2437
|
+
declare module "@apollo/client-ai-apps" {
|
|
2438
|
+
interface Register {
|
|
2439
|
+
toolName: "CreateTodo";
|
|
2440
|
+
}
|
|
2441
|
+
}"
|
|
2442
|
+
`);
|
|
2443
|
+
});
|
|
2444
|
+
});
|
|
2445
|
+
|
|
1698
2446
|
describe("html transforms", () => {
|
|
1699
2447
|
test("replaces root relative scripts with full url when origin is provided", async () => {
|
|
1700
2448
|
vol.fromJSON({
|