@apollo/client-ai-apps 0.6.3 → 0.6.5
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 +48 -0
- package/dist/core/types.d.ts +2 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/mcp/core/ApolloClient.d.ts.map +1 -1
- package/dist/mcp/core/ApolloClient.js +11 -8
- package/dist/mcp/core/ApolloClient.js.map +1 -1
- package/dist/mcp/core/McpAppManager.d.ts +11 -3
- package/dist/mcp/core/McpAppManager.d.ts.map +1 -1
- package/dist/mcp/core/McpAppManager.js +8 -2
- package/dist/mcp/core/McpAppManager.js.map +1 -1
- package/dist/openai/core/ApolloClient.d.ts.map +1 -1
- package/dist/openai/core/ApolloClient.js +11 -8
- package/dist/openai/core/ApolloClient.js.map +1 -1
- package/dist/openai/core/McpAppManager.d.ts +12 -4
- package/dist/openai/core/McpAppManager.d.ts.map +1 -1
- package/dist/openai/core/McpAppManager.js +9 -3
- package/dist/openai/core/McpAppManager.js.map +1 -1
- 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/apolloClientAiApps.d.ts.map +1 -1
- package/dist/vite/apolloClientAiApps.js +3 -7
- package/dist/vite/apolloClientAiApps.js.map +1 -1
- package/package.json +1 -1
- package/src/core/types.ts +2 -1
- package/src/mcp/core/ApolloClient.ts +13 -8
- package/src/mcp/core/McpAppManager.ts +9 -2
- package/src/mcp/core/__tests__/ApolloClient.test.ts +242 -0
- package/src/mcp/link/__tests__/ToolCallLink.test.ts +51 -0
- package/src/openai/core/ApolloClient.ts +13 -8
- package/src/openai/core/McpAppManager.ts +12 -3
- package/src/openai/core/__tests__/ApolloClient.test.ts +303 -0
- package/src/types/application-manifest.ts +1 -0
- package/src/vite/__tests__/apolloClientAiApps.test.ts +56 -0
- package/src/vite/apolloClientAiApps.ts +6 -7
|
@@ -11,6 +11,57 @@ import {
|
|
|
11
11
|
} from "../../../testing/internal/index.js";
|
|
12
12
|
import { ToolCallLink } from "../ToolCallLink.js";
|
|
13
13
|
|
|
14
|
+
test("merges _meta.structuredContent into result for @private fields", async () => {
|
|
15
|
+
using _ = spyOnConsole("debug");
|
|
16
|
+
const query = gql`
|
|
17
|
+
query GreetingQuery {
|
|
18
|
+
greeting @private
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
const client = new ApolloClient({
|
|
23
|
+
cache: new InMemoryCache(),
|
|
24
|
+
manifest: mockApplicationManifest(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
using host = await mockMcpHost({
|
|
28
|
+
hostContext: minimalHostContextWithToolName("GetProduct"),
|
|
29
|
+
});
|
|
30
|
+
host.onCleanup(() => client.stop());
|
|
31
|
+
|
|
32
|
+
host.sendToolInput({ arguments: {} });
|
|
33
|
+
host.sendToolResult({
|
|
34
|
+
_meta: { toolName: "GetProduct" },
|
|
35
|
+
content: [],
|
|
36
|
+
structuredContent: {
|
|
37
|
+
result: {
|
|
38
|
+
data: {
|
|
39
|
+
product: null,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
host.mockToolCall("execute", () => ({
|
|
46
|
+
content: [],
|
|
47
|
+
structuredContent: {},
|
|
48
|
+
_meta: {
|
|
49
|
+
structuredContent: { data: { greeting: "Hello, private world" } },
|
|
50
|
+
},
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
await client.connect();
|
|
54
|
+
|
|
55
|
+
const observable = execute(new ToolCallLink(), { query }, { client });
|
|
56
|
+
const stream = new ObservableStream(observable);
|
|
57
|
+
|
|
58
|
+
await expect(stream).toEmitValue({
|
|
59
|
+
data: { greeting: "Hello, private world" },
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await expect(stream).toComplete();
|
|
63
|
+
});
|
|
64
|
+
|
|
14
65
|
test("delegates query execution to MCP host", async () => {
|
|
15
66
|
using _ = spyOnConsole("debug");
|
|
16
67
|
const query = gql`
|
|
@@ -126,25 +126,30 @@ export class ApolloClient extends BaseApolloClient {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
connect = cacheAsync(async () => {
|
|
129
|
-
const {
|
|
129
|
+
const { structuredContent, toolName, args } =
|
|
130
130
|
await this.appManager.connect();
|
|
131
131
|
|
|
132
132
|
this.#toolInput = args;
|
|
133
133
|
|
|
134
134
|
this.manifest.operations.forEach((operation) => {
|
|
135
|
-
if (
|
|
135
|
+
if (
|
|
136
|
+
operation.prefetchID &&
|
|
137
|
+
structuredContent.prefetch?.[operation.prefetchID]
|
|
138
|
+
) {
|
|
136
139
|
this.writeQuery({
|
|
137
140
|
query: parse(operation.body),
|
|
138
|
-
data: prefetch[operation.prefetchID].data,
|
|
141
|
+
data: structuredContent.prefetch[operation.prefetchID].data,
|
|
139
142
|
});
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
if (operation.tools.find((tool) => tool.name === toolName)) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
if (structuredContent.result?.data) {
|
|
147
|
+
this.writeQuery({
|
|
148
|
+
query: parse(operation.body),
|
|
149
|
+
data: structuredContent.result.data,
|
|
150
|
+
variables: getVariablesForOperationFromToolInput(operation, args),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
148
153
|
}
|
|
149
154
|
});
|
|
150
155
|
});
|
|
@@ -13,6 +13,7 @@ import type { ApolloMcpServerApps } from "../../core/types";
|
|
|
13
13
|
|
|
14
14
|
type ExecuteQueryCallToolResult = Omit<CallToolResult, "structuredContent"> & {
|
|
15
15
|
structuredContent: FormattedExecutionResult;
|
|
16
|
+
_meta?: { structuredContent?: FormattedExecutionResult };
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
/** @internal */
|
|
@@ -100,9 +101,14 @@ export class McpAppManager {
|
|
|
100
101
|
this.#toolMetadata = window.openai.toolResponseMetadata;
|
|
101
102
|
|
|
102
103
|
return {
|
|
103
|
-
|
|
104
|
+
structuredContent: {
|
|
105
|
+
...structuredContent,
|
|
106
|
+
...(
|
|
107
|
+
window.openai.toolResponseMetadata as ApolloMcpServerApps.Meta | null
|
|
108
|
+
)?.structuredContent,
|
|
109
|
+
},
|
|
104
110
|
toolName: this.toolName,
|
|
105
|
-
args: this
|
|
111
|
+
args: this.#toolInput,
|
|
106
112
|
};
|
|
107
113
|
});
|
|
108
114
|
|
|
@@ -122,7 +128,10 @@ export class McpAppManager {
|
|
|
122
128
|
arguments: { query: print(query), variables },
|
|
123
129
|
})) as ExecuteQueryCallToolResult;
|
|
124
130
|
|
|
125
|
-
return
|
|
131
|
+
return {
|
|
132
|
+
...result.structuredContent,
|
|
133
|
+
...result._meta?.structuredContent,
|
|
134
|
+
};
|
|
126
135
|
}
|
|
127
136
|
|
|
128
137
|
private async connectToHost() {
|
|
@@ -2,6 +2,7 @@ import { expect, test, describe, vi } from "vitest";
|
|
|
2
2
|
import { ApolloClient } from "../ApolloClient.js";
|
|
3
3
|
import { parse } from "graphql";
|
|
4
4
|
import { ApolloLink, HttpLink, InMemoryCache, gql } from "@apollo/client";
|
|
5
|
+
import { print } from "@apollo/client/utilities";
|
|
5
6
|
import { ToolCallLink } from "../../link/ToolCallLink.js";
|
|
6
7
|
import {
|
|
7
8
|
graphqlToolResult,
|
|
@@ -91,6 +92,62 @@ describe("Client Basics", () => {
|
|
|
91
92
|
});
|
|
92
93
|
});
|
|
93
94
|
|
|
95
|
+
test("merges _meta.structuredContent into result for @private fields", async () => {
|
|
96
|
+
stubOpenAiGlobals();
|
|
97
|
+
using _ = spyOnConsole("debug");
|
|
98
|
+
const manifest = mockApplicationManifest();
|
|
99
|
+
const client = new ApolloClient({
|
|
100
|
+
cache: new InMemoryCache(),
|
|
101
|
+
manifest,
|
|
102
|
+
});
|
|
103
|
+
using host = await mockMcpHost();
|
|
104
|
+
|
|
105
|
+
const query = gql`
|
|
106
|
+
query Product {
|
|
107
|
+
id
|
|
108
|
+
title @private
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
host.onCleanup(() => client.stop());
|
|
113
|
+
|
|
114
|
+
host.sendToolInput({});
|
|
115
|
+
host.sendToolResult({
|
|
116
|
+
content: [],
|
|
117
|
+
structuredContent: {},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
host.mockToolCall("execute", () => ({
|
|
121
|
+
content: [],
|
|
122
|
+
structuredContent: {},
|
|
123
|
+
_meta: {
|
|
124
|
+
structuredContent: {
|
|
125
|
+
data: {
|
|
126
|
+
product: {
|
|
127
|
+
id: "1",
|
|
128
|
+
title: "Private Pen",
|
|
129
|
+
__typename: "Product",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
}));
|
|
135
|
+
|
|
136
|
+
await client.connect();
|
|
137
|
+
|
|
138
|
+
await expect(
|
|
139
|
+
client.query({ query, variables: { id: "1" } })
|
|
140
|
+
).resolves.toStrictEqual({
|
|
141
|
+
data: {
|
|
142
|
+
product: {
|
|
143
|
+
__typename: "Product",
|
|
144
|
+
id: "1",
|
|
145
|
+
title: "Private Pen",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
94
151
|
describe("prefetchData", () => {
|
|
95
152
|
test("caches tool response when data is provided", async () => {
|
|
96
153
|
stubOpenAiGlobals({ toolInput: { id: 1 } });
|
|
@@ -400,6 +457,252 @@ describe("prefetchData", () => {
|
|
|
400
457
|
});
|
|
401
458
|
});
|
|
402
459
|
|
|
460
|
+
test("reads result data from toolResponseMetadata.structuredContent", async () => {
|
|
461
|
+
stubOpenAiGlobals({
|
|
462
|
+
toolInput: { id: "1" },
|
|
463
|
+
toolResponseMetadata: {
|
|
464
|
+
structuredContent: {
|
|
465
|
+
result: {
|
|
466
|
+
data: {
|
|
467
|
+
product: { id: "1", title: "Pen", __typename: "Product" },
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
using _ = spyOnConsole("debug");
|
|
474
|
+
|
|
475
|
+
const query = gql`
|
|
476
|
+
query Product($id: ID!) {
|
|
477
|
+
product(id: $id) @private {
|
|
478
|
+
id
|
|
479
|
+
title
|
|
480
|
+
__typename
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
`;
|
|
484
|
+
|
|
485
|
+
const client = new ApolloClient({
|
|
486
|
+
cache: new InMemoryCache(),
|
|
487
|
+
manifest: mockApplicationManifest({
|
|
488
|
+
operations: [
|
|
489
|
+
{
|
|
490
|
+
id: "c43af26552874026c3fb346148c5795896aa2f3a872410a0a2621cffee25291c",
|
|
491
|
+
name: "Product",
|
|
492
|
+
type: "query",
|
|
493
|
+
body: print(query),
|
|
494
|
+
variables: { id: "ID" },
|
|
495
|
+
prefetch: false,
|
|
496
|
+
tools: [{ name: "GetProduct", description: "Get a product" }],
|
|
497
|
+
},
|
|
498
|
+
],
|
|
499
|
+
}),
|
|
500
|
+
});
|
|
501
|
+
using host = await mockMcpHost({
|
|
502
|
+
hostContext: minimalHostContextWithToolName("GetProduct"),
|
|
503
|
+
});
|
|
504
|
+
host.onCleanup(() => client.stop());
|
|
505
|
+
|
|
506
|
+
host.sendToolInput({ arguments: { id: "1" } });
|
|
507
|
+
host.sendToolResult({
|
|
508
|
+
content: [],
|
|
509
|
+
structuredContent: {},
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
await client.connect();
|
|
513
|
+
|
|
514
|
+
expect(client.extract()).toEqual({
|
|
515
|
+
"Product:1": {
|
|
516
|
+
__typename: "Product",
|
|
517
|
+
id: "1",
|
|
518
|
+
title: "Pen",
|
|
519
|
+
},
|
|
520
|
+
ROOT_QUERY: {
|
|
521
|
+
__typename: "Query",
|
|
522
|
+
'product({"id":"1"})@private': { __ref: "Product:1" },
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
test("merges prefetch from structuredContent and result from toolResponseMetadata.structuredContent", async () => {
|
|
528
|
+
stubOpenAiGlobals({
|
|
529
|
+
toolInput: { id: "2" },
|
|
530
|
+
toolResponseMetadata: {
|
|
531
|
+
structuredContent: {
|
|
532
|
+
result: {
|
|
533
|
+
data: {
|
|
534
|
+
product: { id: "2", title: "iPad", __typename: "Product" },
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
});
|
|
540
|
+
using _ = spyOnConsole("debug");
|
|
541
|
+
|
|
542
|
+
const prefetchQuery = gql`
|
|
543
|
+
query TopProducts {
|
|
544
|
+
topProducts {
|
|
545
|
+
id
|
|
546
|
+
title
|
|
547
|
+
__typename
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
`;
|
|
551
|
+
|
|
552
|
+
const query = gql`
|
|
553
|
+
query Product($id: ID!) {
|
|
554
|
+
product(id: $id) @private {
|
|
555
|
+
id
|
|
556
|
+
title
|
|
557
|
+
__typename
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
`;
|
|
561
|
+
|
|
562
|
+
const client = new ApolloClient({
|
|
563
|
+
cache: new InMemoryCache(),
|
|
564
|
+
manifest: mockApplicationManifest({
|
|
565
|
+
operations: [
|
|
566
|
+
{
|
|
567
|
+
id: "1",
|
|
568
|
+
name: "TopProducts",
|
|
569
|
+
body: print(prefetchQuery),
|
|
570
|
+
type: "query",
|
|
571
|
+
variables: {},
|
|
572
|
+
prefetch: true,
|
|
573
|
+
prefetchID: "__anonymous",
|
|
574
|
+
tools: [
|
|
575
|
+
{
|
|
576
|
+
name: "TopProducts",
|
|
577
|
+
description: "Shows the currently highest rated products.",
|
|
578
|
+
},
|
|
579
|
+
],
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
id: "2",
|
|
583
|
+
name: "Product",
|
|
584
|
+
body: print(query),
|
|
585
|
+
type: "query",
|
|
586
|
+
variables: { id: "ID" },
|
|
587
|
+
prefetch: false,
|
|
588
|
+
tools: [{ name: "GetProduct", description: "Get a product" }],
|
|
589
|
+
},
|
|
590
|
+
],
|
|
591
|
+
}),
|
|
592
|
+
});
|
|
593
|
+
using host = await mockMcpHost({
|
|
594
|
+
hostContext: minimalHostContextWithToolName("GetProduct"),
|
|
595
|
+
});
|
|
596
|
+
host.onCleanup(() => client.stop());
|
|
597
|
+
|
|
598
|
+
host.sendToolInput({ arguments: { id: "2" } });
|
|
599
|
+
host.sendToolResult({
|
|
600
|
+
content: [],
|
|
601
|
+
structuredContent: {
|
|
602
|
+
prefetch: {
|
|
603
|
+
__anonymous: {
|
|
604
|
+
data: {
|
|
605
|
+
topProducts: [{ id: "1", title: "iPhone", __typename: "Product" }],
|
|
606
|
+
},
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
await client.connect();
|
|
613
|
+
|
|
614
|
+
expect(client.extract()).toEqual({
|
|
615
|
+
"Product:1": {
|
|
616
|
+
__typename: "Product",
|
|
617
|
+
id: "1",
|
|
618
|
+
title: "iPhone",
|
|
619
|
+
},
|
|
620
|
+
"Product:2": {
|
|
621
|
+
__typename: "Product",
|
|
622
|
+
id: "2",
|
|
623
|
+
title: "iPad",
|
|
624
|
+
},
|
|
625
|
+
ROOT_QUERY: {
|
|
626
|
+
__typename: "Query",
|
|
627
|
+
topProducts: [{ __ref: "Product:1" }],
|
|
628
|
+
'product({"id":"2"})@private': { __ref: "Product:2" },
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
test("toolResponseMetadata.structuredContent wins over structuredContent", async () => {
|
|
634
|
+
stubOpenAiGlobals({
|
|
635
|
+
toolInput: { id: "1" },
|
|
636
|
+
toolResponseMetadata: {
|
|
637
|
+
structuredContent: {
|
|
638
|
+
result: {
|
|
639
|
+
data: {
|
|
640
|
+
product: { id: "1", title: "Meta title", __typename: "Product" },
|
|
641
|
+
},
|
|
642
|
+
},
|
|
643
|
+
},
|
|
644
|
+
},
|
|
645
|
+
});
|
|
646
|
+
using _ = spyOnConsole("debug");
|
|
647
|
+
|
|
648
|
+
const query = gql`
|
|
649
|
+
query Product($id: ID!) {
|
|
650
|
+
product(id: $id) {
|
|
651
|
+
id
|
|
652
|
+
title @private
|
|
653
|
+
__typename
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
`;
|
|
657
|
+
|
|
658
|
+
const client = new ApolloClient({
|
|
659
|
+
cache: new InMemoryCache(),
|
|
660
|
+
manifest: mockApplicationManifest({
|
|
661
|
+
operations: [
|
|
662
|
+
{
|
|
663
|
+
id: "1",
|
|
664
|
+
name: "Product",
|
|
665
|
+
body: print(query),
|
|
666
|
+
type: "query",
|
|
667
|
+
variables: { id: "ID" },
|
|
668
|
+
prefetch: false,
|
|
669
|
+
tools: [{ name: "GetProduct", description: "Get a product" }],
|
|
670
|
+
},
|
|
671
|
+
],
|
|
672
|
+
}),
|
|
673
|
+
});
|
|
674
|
+
using host = await mockMcpHost({
|
|
675
|
+
hostContext: minimalHostContextWithToolName("GetProduct"),
|
|
676
|
+
});
|
|
677
|
+
host.onCleanup(() => client.stop());
|
|
678
|
+
|
|
679
|
+
host.sendToolInput({ arguments: { id: "1" } });
|
|
680
|
+
host.sendToolResult({
|
|
681
|
+
content: [],
|
|
682
|
+
structuredContent: {
|
|
683
|
+
result: {
|
|
684
|
+
data: {
|
|
685
|
+
product: { id: "1", __typename: "Product" },
|
|
686
|
+
},
|
|
687
|
+
},
|
|
688
|
+
},
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
await client.connect();
|
|
692
|
+
|
|
693
|
+
expect(client.extract()).toEqual({
|
|
694
|
+
"Product:1": {
|
|
695
|
+
__typename: "Product",
|
|
696
|
+
id: "1",
|
|
697
|
+
"title@private": "Meta title",
|
|
698
|
+
},
|
|
699
|
+
ROOT_QUERY: {
|
|
700
|
+
__typename: "Query",
|
|
701
|
+
'product({"id":"1"})': { __ref: "Product:1" },
|
|
702
|
+
},
|
|
703
|
+
});
|
|
704
|
+
});
|
|
705
|
+
|
|
403
706
|
test("connects using window.openai.toolOutput when tool-result notification is not sent", async () => {
|
|
404
707
|
stubOpenAiGlobals({
|
|
405
708
|
toolOutput: {
|
|
@@ -143,6 +143,35 @@ describe("operations", () => {
|
|
|
143
143
|
`);
|
|
144
144
|
});
|
|
145
145
|
|
|
146
|
+
test("writes extraOutputs to the manifest", async () => {
|
|
147
|
+
vol.fromJSON({
|
|
148
|
+
"package.json": mockPackageJson(),
|
|
149
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
150
|
+
query HelloWorldQuery
|
|
151
|
+
@tool(
|
|
152
|
+
name: "hello-world"
|
|
153
|
+
description: "This is an awesome tool!"
|
|
154
|
+
extraOutputs: { foo: "bar", nested: { label: "count" } }
|
|
155
|
+
) {
|
|
156
|
+
helloWorld
|
|
157
|
+
}
|
|
158
|
+
`),
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await using server = await setupServer({
|
|
162
|
+
plugins: [
|
|
163
|
+
apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
|
|
164
|
+
],
|
|
165
|
+
});
|
|
166
|
+
await server.listen();
|
|
167
|
+
|
|
168
|
+
const manifest = readManifestFile();
|
|
169
|
+
expect(manifest.operations[0].tools[0].extraOutputs).toEqual({
|
|
170
|
+
foo: "bar",
|
|
171
|
+
nested: { label: "count" },
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
146
175
|
test("handles operations with fragments in the same file", async () => {
|
|
147
176
|
vol.fromJSON({
|
|
148
177
|
"package.json": mockPackageJson(),
|
|
@@ -890,6 +919,33 @@ describe("@tool validation", () => {
|
|
|
890
919
|
);
|
|
891
920
|
});
|
|
892
921
|
|
|
922
|
+
test("errors when extraOutputs is not an object", async () => {
|
|
923
|
+
vol.fromJSON({
|
|
924
|
+
"package.json": mockPackageJson(),
|
|
925
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
926
|
+
query HelloWorldQuery
|
|
927
|
+
@tool(
|
|
928
|
+
name: "hello-world"
|
|
929
|
+
description: "hello"
|
|
930
|
+
extraOutputs: [1, 2, 3]
|
|
931
|
+
) {
|
|
932
|
+
helloWorld
|
|
933
|
+
}
|
|
934
|
+
`),
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
await expect(async () => {
|
|
938
|
+
await using server = await setupServer({
|
|
939
|
+
plugins: [
|
|
940
|
+
apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
|
|
941
|
+
],
|
|
942
|
+
});
|
|
943
|
+
await server.listen();
|
|
944
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
945
|
+
`[Error: Expected argument 'extraOutputs' to be of type 'ObjectValue' but found 'ListValue' instead.]`
|
|
946
|
+
);
|
|
947
|
+
});
|
|
948
|
+
|
|
893
949
|
test("errors when extraInputs is not an array", async () => {
|
|
894
950
|
vol.fromJSON({
|
|
895
951
|
"package.json": mockPackageJson(),
|
|
@@ -690,10 +690,7 @@ export function apolloClientAiApps(
|
|
|
690
690
|
await processFile(fullPath);
|
|
691
691
|
}
|
|
692
692
|
|
|
693
|
-
|
|
694
|
-
if (config.command === "serve") {
|
|
695
|
-
await Promise.all([generateManifest(), generateTypesFiles()]);
|
|
696
|
-
}
|
|
693
|
+
await Promise.all([generateManifest(), generateTypesFiles()]);
|
|
697
694
|
},
|
|
698
695
|
configResolved(resolvedConfig) {
|
|
699
696
|
config = resolvedConfig;
|
|
@@ -797,9 +794,6 @@ export function apolloClientAiApps(
|
|
|
797
794
|
.replace(/(src=["'])\/([^"']+)/gi, `$1${baseUrl}/$2`)
|
|
798
795
|
);
|
|
799
796
|
},
|
|
800
|
-
async writeBundle() {
|
|
801
|
-
await Promise.all([generateManifest(), generateTypesFiles()]);
|
|
802
|
-
},
|
|
803
797
|
} satisfies Plugin;
|
|
804
798
|
}
|
|
805
799
|
|
|
@@ -880,6 +874,10 @@ const processQueryLink = new ApolloLink((operation) => {
|
|
|
880
874
|
getDirectiveArgument("extraInputs", directive),
|
|
881
875
|
Kind.LIST
|
|
882
876
|
),
|
|
877
|
+
extraOutputs: maybeGetArgumentValue(
|
|
878
|
+
getDirectiveArgument("extraOutputs", directive),
|
|
879
|
+
Kind.OBJECT
|
|
880
|
+
),
|
|
883
881
|
labels: maybeGetArgumentValue(
|
|
884
882
|
getDirectiveArgument("labels", directive),
|
|
885
883
|
Kind.OBJECT
|
|
@@ -1050,5 +1048,6 @@ const ToolDirectiveSchema = z.strictObject({
|
|
|
1050
1048
|
})
|
|
1051
1049
|
)
|
|
1052
1050
|
),
|
|
1051
|
+
extraOutputs: z.optional(z.record(z.string(), z.unknown())),
|
|
1053
1052
|
labels: ApolloClientAiAppsConfigSchema.shape.labels.optional(),
|
|
1054
1053
|
});
|