@apollo/client-ai-apps 0.1.0
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/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.github/workflows/pr.yaml +24 -0
- package/.github/workflows/release.yaml +36 -0
- package/LICENSE +21 -0
- package/dist/apollo_client/client.d.ts +14 -0
- package/dist/apollo_client/provider.d.ts +5 -0
- package/dist/hooks/useOpenAiGlobal.d.ts +2 -0
- package/dist/hooks/useRequestDisplayMode.d.ts +4 -0
- package/dist/hooks/useSendFollowUpMessage.d.ts +1 -0
- package/dist/hooks/useToolEffect.d.ts +6 -0
- package/dist/hooks/useToolInput.d.ts +1 -0
- package/dist/hooks/useToolName.d.ts +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +177 -0
- package/dist/types/application-manifest.d.ts +29 -0
- package/dist/types/openai.d.ts +73 -0
- package/dist/vite/index.d.ts +1 -0
- package/dist/vite/index.js +210 -0
- package/dist/vite/operation_manifest_plugin.d.ts +10 -0
- package/package.json +53 -0
- package/scripts/build-vite.mjs +18 -0
- package/scripts/build.mjs +7 -0
- package/scripts/dev.mjs +21 -0
- package/scripts/shared.mjs +9 -0
- package/src/apollo_client/client.test.ts +411 -0
- package/src/apollo_client/client.ts +90 -0
- package/src/apollo_client/provider.test.tsx +41 -0
- package/src/apollo_client/provider.tsx +32 -0
- package/src/hooks/useCallTool.test.ts +46 -0
- package/src/hooks/useCallTool.ts +8 -0
- package/src/hooks/useOpenAiGlobal.test.ts +54 -0
- package/src/hooks/useOpenAiGlobal.ts +26 -0
- package/src/hooks/useRequestDisplayMode.ts +7 -0
- package/src/hooks/useSendFollowUpMessage.ts +7 -0
- package/src/hooks/useToolEffect.tsx +41 -0
- package/src/hooks/useToolInput.ts +7 -0
- package/src/hooks/useToolName.ts +7 -0
- package/src/index.ts +12 -0
- package/src/types/application-manifest.ts +32 -0
- package/src/types/openai.ts +90 -0
- package/src/vite/index.ts +1 -0
- package/src/vite/operation_manifest_plugin.ts +274 -0
- package/vitest-setup.ts +1 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Changesets
|
|
2
|
+
|
|
3
|
+
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
|
4
|
+
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
|
5
|
+
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
|
6
|
+
|
|
7
|
+
We have a quick list of common questions to get you started engaging with this project in
|
|
8
|
+
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
|
|
3
|
+
"changelog": "@changesets/cli/changelog",
|
|
4
|
+
"commit": false,
|
|
5
|
+
"fixed": [],
|
|
6
|
+
"linked": [],
|
|
7
|
+
"access": "restricted",
|
|
8
|
+
"baseBranch": "main",
|
|
9
|
+
"updateInternalDependencies": "patch",
|
|
10
|
+
"ignore": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Build on PR
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize, reopened]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
|
|
11
|
+
steps:
|
|
12
|
+
- name: Checkout code
|
|
13
|
+
uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Set up Node.js
|
|
16
|
+
uses: actions/setup-node@v4
|
|
17
|
+
with:
|
|
18
|
+
node-version: 20.x
|
|
19
|
+
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: npm ci
|
|
22
|
+
|
|
23
|
+
- name: Run build
|
|
24
|
+
run: npm run build
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: Release new Version
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
release:
|
|
8
|
+
name: Release
|
|
9
|
+
if: github.repository == 'apollographql/apollo-ai-apps-client'
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write # to create release (changesets/action)
|
|
13
|
+
issues: write # to post issue comments (changesets/action)
|
|
14
|
+
pull-requests: write # to create pull request (changesets/action)
|
|
15
|
+
id-token: write # to use OpenID Connect token for provenance (changesets/action)
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Setup Node.js 20.x
|
|
20
|
+
uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: 20.x
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
shell: bash
|
|
26
|
+
run: npm ci
|
|
27
|
+
|
|
28
|
+
- name: Create Release Pull Request or Publish to npm
|
|
29
|
+
uses: changesets/action@v1
|
|
30
|
+
with:
|
|
31
|
+
publish: npm run build
|
|
32
|
+
version: changeset:version
|
|
33
|
+
env:
|
|
34
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
35
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
36
|
+
NPM_CONFIG_PROVENANCE: true
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Apollo GraphQL
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,14 @@
|
|
|
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 {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useSendFollowUpMessage: () => (prompt: string) => Promise<void>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export declare function ToolUseProvider({ children, appName }: {
|
|
3
|
+
children: any;
|
|
4
|
+
appName: string;
|
|
5
|
+
}): React.JSX.Element;
|
|
6
|
+
export declare const useToolEffect: (toolName: string | string[], effect: (toolInput: any) => void, deps?: React.DependencyList) => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useToolInput: () => any;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useToolName: () => any;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./types/openai";
|
|
2
|
+
export * from "./types/application-manifest";
|
|
3
|
+
export * from "./hooks/useOpenAiGlobal";
|
|
4
|
+
export * from "./hooks/useToolName";
|
|
5
|
+
export * from "./hooks/useToolInput";
|
|
6
|
+
export * from "./hooks/useSendFollowUpMessage";
|
|
7
|
+
export * from "./hooks/useRequestDisplayMode";
|
|
8
|
+
export * from "./hooks/useToolEffect";
|
|
9
|
+
export * from "@apollo/client";
|
|
10
|
+
export { ExtendedApolloClient as ApolloClient } from "./apollo_client/client";
|
|
11
|
+
export { ExtendedApolloProvider as ApolloProvider } from "./apollo_client/provider";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// src/types/openai.ts
|
|
2
|
+
var SET_GLOBALS_EVENT_TYPE = "openai:set_globals";
|
|
3
|
+
var SetGlobalsEvent = class extends CustomEvent {
|
|
4
|
+
type = SET_GLOBALS_EVENT_TYPE;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/hooks/useOpenAiGlobal.ts
|
|
8
|
+
import { useSyncExternalStore } from "react";
|
|
9
|
+
function useOpenAiGlobal(key) {
|
|
10
|
+
return useSyncExternalStore(
|
|
11
|
+
(onChange) => {
|
|
12
|
+
const handleSetGlobal = (event) => {
|
|
13
|
+
const value = event.detail.globals[key];
|
|
14
|
+
if (value === void 0) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
onChange();
|
|
18
|
+
};
|
|
19
|
+
window.addEventListener(SET_GLOBALS_EVENT_TYPE, handleSetGlobal, {
|
|
20
|
+
passive: true
|
|
21
|
+
});
|
|
22
|
+
return () => {
|
|
23
|
+
window.removeEventListener(SET_GLOBALS_EVENT_TYPE, handleSetGlobal);
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
() => window.openai[key]
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/hooks/useToolName.ts
|
|
31
|
+
var useToolName = () => {
|
|
32
|
+
const toolResponseMetadata = useOpenAiGlobal("toolResponseMetadata");
|
|
33
|
+
return toolResponseMetadata?.toolName;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// src/hooks/useToolInput.ts
|
|
37
|
+
var useToolInput = () => {
|
|
38
|
+
const toolInput = useOpenAiGlobal("toolInput");
|
|
39
|
+
return toolInput;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// src/hooks/useSendFollowUpMessage.ts
|
|
43
|
+
var useSendFollowUpMessage = () => {
|
|
44
|
+
return async (prompt) => {
|
|
45
|
+
await window.openai?.sendFollowUpMessage({
|
|
46
|
+
prompt
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// src/hooks/useRequestDisplayMode.ts
|
|
52
|
+
var useRequestDisplayMode = () => {
|
|
53
|
+
return async (args) => {
|
|
54
|
+
await window.openai?.requestDisplayMode(args);
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/hooks/useToolEffect.tsx
|
|
59
|
+
import React, { useEffect, useState } from "react";
|
|
60
|
+
var ToolUseContext = React.createContext(null);
|
|
61
|
+
function ToolUseProvider({ children, appName }) {
|
|
62
|
+
const [hasNavigated, setHasNavigated] = useState(false);
|
|
63
|
+
return /* @__PURE__ */ React.createElement(ToolUseContext.Provider, { value: { hasNavigated, setHasNavigated, appName } }, children);
|
|
64
|
+
}
|
|
65
|
+
var useToolEffect = (toolName, effect, deps = []) => {
|
|
66
|
+
const ctx = React.useContext(ToolUseContext);
|
|
67
|
+
const fullToolName = useToolName();
|
|
68
|
+
const toolInput = useToolInput();
|
|
69
|
+
if (!ctx) throw new Error("useToolEffect must be used within ToolUseProvider");
|
|
70
|
+
const toolNames = Array.isArray(toolName) ? toolName : [toolName];
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const matches = toolNames.some((name) => fullToolName === `${ctx.appName}--${name}`);
|
|
73
|
+
if (!ctx.hasNavigated && matches) {
|
|
74
|
+
effect(toolInput);
|
|
75
|
+
ctx.setHasNavigated(true);
|
|
76
|
+
}
|
|
77
|
+
}, [ctx.hasNavigated, ctx.setHasNavigated, ctx.appName, toolNames, fullToolName, toolInput, ...deps]);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// src/index.ts
|
|
81
|
+
export * from "@apollo/client";
|
|
82
|
+
|
|
83
|
+
// src/apollo_client/client.ts
|
|
84
|
+
import { ApolloClient, ApolloLink, InMemoryCache } from "@apollo/client";
|
|
85
|
+
import * as Observable from "rxjs";
|
|
86
|
+
import { selectHttpOptionsAndBody } from "@apollo/client/link/http";
|
|
87
|
+
import { fallbackHttpConfig } from "@apollo/client/link/http";
|
|
88
|
+
import { DocumentTransform } from "@apollo/client";
|
|
89
|
+
import { removeDirectivesFromDocument } from "@apollo/client/utilities/internal";
|
|
90
|
+
import { parse } from "graphql";
|
|
91
|
+
var toolCallLink = new ApolloLink((operation) => {
|
|
92
|
+
const context = operation.getContext();
|
|
93
|
+
const contextConfig = {
|
|
94
|
+
http: context.http,
|
|
95
|
+
options: context.fetchOptions,
|
|
96
|
+
credentials: context.credentials,
|
|
97
|
+
headers: context.headers
|
|
98
|
+
};
|
|
99
|
+
const { query, variables } = selectHttpOptionsAndBody(operation, fallbackHttpConfig, contextConfig).body;
|
|
100
|
+
return Observable.from(window.openai.callTool("execute", { query, variables })).pipe(
|
|
101
|
+
Observable.map((result) => ({ data: result.structuredContent.data }))
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
var ExtendedApolloClient = class extends ApolloClient {
|
|
105
|
+
manifest;
|
|
106
|
+
constructor(options) {
|
|
107
|
+
super({
|
|
108
|
+
link: toolCallLink,
|
|
109
|
+
cache: options.cache ?? new InMemoryCache(),
|
|
110
|
+
// Strip out the prefetch/tool directives so they don't get sent with the operation to the server
|
|
111
|
+
documentTransform: new DocumentTransform((document) => {
|
|
112
|
+
return removeDirectivesFromDocument([{ name: "prefetch" }, { name: "tool" }], document);
|
|
113
|
+
})
|
|
114
|
+
});
|
|
115
|
+
this.manifest = options.manifest;
|
|
116
|
+
}
|
|
117
|
+
async prefetchData() {
|
|
118
|
+
this.manifest.operations.forEach((operation) => {
|
|
119
|
+
if (operation.prefetch && operation.prefetchID && window.openai.toolOutput.prefetch[operation.prefetchID]) {
|
|
120
|
+
this.writeQuery({
|
|
121
|
+
query: parse(operation.body),
|
|
122
|
+
data: window.openai.toolOutput.prefetch[operation.prefetchID].data
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (operation.tools?.find(
|
|
126
|
+
(tool) => `${this.manifest.name}--${tool.name}` === window.openai.toolResponseMetadata.toolName
|
|
127
|
+
)) {
|
|
128
|
+
const variables = Object.keys(window.openai.toolInput).reduce(
|
|
129
|
+
(obj, key) => operation.variables[key] ? { ...obj, [key]: window.openai.toolInput[key] } : obj,
|
|
130
|
+
{}
|
|
131
|
+
);
|
|
132
|
+
this.writeQuery({
|
|
133
|
+
query: parse(operation.body),
|
|
134
|
+
data: window.openai.toolOutput.result.data,
|
|
135
|
+
variables
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// src/apollo_client/provider.tsx
|
|
143
|
+
import React2, { useEffect as useEffect2, useState as useState2 } from "react";
|
|
144
|
+
import { ApolloProvider } from "@apollo/client/react";
|
|
145
|
+
var ExtendedApolloProvider = ({
|
|
146
|
+
children,
|
|
147
|
+
client
|
|
148
|
+
}) => {
|
|
149
|
+
const [hasPreloaded, setHasPreloaded] = useState2(false);
|
|
150
|
+
useEffect2(() => {
|
|
151
|
+
const prefetchData = async () => {
|
|
152
|
+
await client.prefetchData();
|
|
153
|
+
setHasPreloaded(true);
|
|
154
|
+
window.removeEventListener(SET_GLOBALS_EVENT_TYPE, prefetchData);
|
|
155
|
+
};
|
|
156
|
+
window.addEventListener(SET_GLOBALS_EVENT_TYPE, prefetchData, {
|
|
157
|
+
passive: true
|
|
158
|
+
});
|
|
159
|
+
if (window.openai?.toolOutput) {
|
|
160
|
+
window.dispatchEvent(new CustomEvent(SET_GLOBALS_EVENT_TYPE));
|
|
161
|
+
}
|
|
162
|
+
}, [setHasPreloaded]);
|
|
163
|
+
return hasPreloaded ? /* @__PURE__ */ React2.createElement(ApolloProvider, { client }, children) : null;
|
|
164
|
+
};
|
|
165
|
+
export {
|
|
166
|
+
ExtendedApolloClient as ApolloClient,
|
|
167
|
+
ExtendedApolloProvider as ApolloProvider,
|
|
168
|
+
SET_GLOBALS_EVENT_TYPE,
|
|
169
|
+
SetGlobalsEvent,
|
|
170
|
+
ToolUseProvider,
|
|
171
|
+
useOpenAiGlobal,
|
|
172
|
+
useRequestDisplayMode,
|
|
173
|
+
useSendFollowUpMessage,
|
|
174
|
+
useToolEffect,
|
|
175
|
+
useToolInput,
|
|
176
|
+
useToolName
|
|
177
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type ApplicationManifest = {
|
|
2
|
+
format: "apollo-ai-app-manifest";
|
|
3
|
+
version: "1";
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
hash: string;
|
|
7
|
+
resource: string;
|
|
8
|
+
operations: ManifestOperation[];
|
|
9
|
+
};
|
|
10
|
+
export type ManifestOperation = {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
type: "query" | "mutation";
|
|
14
|
+
body: string;
|
|
15
|
+
variables: Record<string, string>;
|
|
16
|
+
prefetch: boolean;
|
|
17
|
+
prefetchID?: string;
|
|
18
|
+
tools: ManifestTool[];
|
|
19
|
+
};
|
|
20
|
+
export type ManifestTool = {
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
extraInputs?: ManifestExtraInput[];
|
|
24
|
+
};
|
|
25
|
+
export type ManifestExtraInput = {
|
|
26
|
+
name: string;
|
|
27
|
+
description: string;
|
|
28
|
+
type: "string" | "boolean" | "number";
|
|
29
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
type UnknownObject = any;
|
|
2
|
+
declare global {
|
|
3
|
+
interface Window {
|
|
4
|
+
openai: API<any> & OpenAiGlobals;
|
|
5
|
+
}
|
|
6
|
+
interface WindowEventMap {
|
|
7
|
+
[SET_GLOBALS_EVENT_TYPE]: SetGlobalsEvent;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export type OpenAiGlobals<ToolInput extends UnknownObject = UnknownObject, ToolOutput extends UnknownObject = UnknownObject, ToolResponseMetadata extends UnknownObject = UnknownObject, WidgetState extends UnknownObject = UnknownObject> = {
|
|
11
|
+
theme: Theme;
|
|
12
|
+
userAgent: UserAgent;
|
|
13
|
+
locale: string;
|
|
14
|
+
maxHeight: number;
|
|
15
|
+
displayMode: DisplayMode;
|
|
16
|
+
safeArea: SafeArea;
|
|
17
|
+
toolInput: ToolInput;
|
|
18
|
+
toolOutput: ToolOutput | null;
|
|
19
|
+
toolResponseMetadata: ToolResponseMetadata | null;
|
|
20
|
+
widgetState: WidgetState | null;
|
|
21
|
+
};
|
|
22
|
+
export type API<WidgetState extends UnknownObject> = {
|
|
23
|
+
/** Calls a tool on your MCP. Returns the full response. */
|
|
24
|
+
callTool: (name: string, args: Record<string, unknown>) => Promise<any>;
|
|
25
|
+
/** Triggers a followup turn in the ChatGPT conversation */
|
|
26
|
+
sendFollowUpMessage: (args: {
|
|
27
|
+
prompt: string;
|
|
28
|
+
}) => Promise<void>;
|
|
29
|
+
/** Opens an external link, redirects web page or mobile app */
|
|
30
|
+
openExternal(payload: {
|
|
31
|
+
href: string;
|
|
32
|
+
}): void;
|
|
33
|
+
/** For transitioning an app from inline to fullscreen or pip */
|
|
34
|
+
requestDisplayMode: (args: {
|
|
35
|
+
mode: DisplayMode;
|
|
36
|
+
}) => Promise<{
|
|
37
|
+
/**
|
|
38
|
+
* The granted display mode. The host may reject the request.
|
|
39
|
+
* For mobile, PiP is always coerced to fullscreen.
|
|
40
|
+
*/
|
|
41
|
+
mode: DisplayMode;
|
|
42
|
+
}>;
|
|
43
|
+
setWidgetState: (state: WidgetState) => Promise<void>;
|
|
44
|
+
};
|
|
45
|
+
export declare const SET_GLOBALS_EVENT_TYPE = "openai:set_globals";
|
|
46
|
+
export declare class SetGlobalsEvent extends CustomEvent<{
|
|
47
|
+
globals: Partial<OpenAiGlobals>;
|
|
48
|
+
}> {
|
|
49
|
+
readonly type = "openai:set_globals";
|
|
50
|
+
}
|
|
51
|
+
export type CallTool = (name: string, args: Record<string, unknown>) => Promise<any>;
|
|
52
|
+
export type DisplayMode = "pip" | "inline" | "fullscreen";
|
|
53
|
+
export type Theme = "light" | "dark";
|
|
54
|
+
export type SafeAreaInsets = {
|
|
55
|
+
top: number;
|
|
56
|
+
bottom: number;
|
|
57
|
+
left: number;
|
|
58
|
+
right: number;
|
|
59
|
+
};
|
|
60
|
+
export type SafeArea = {
|
|
61
|
+
insets: SafeAreaInsets;
|
|
62
|
+
};
|
|
63
|
+
export type DeviceType = "mobile" | "tablet" | "desktop" | "unknown";
|
|
64
|
+
export type UserAgent = {
|
|
65
|
+
device: {
|
|
66
|
+
type: DeviceType;
|
|
67
|
+
};
|
|
68
|
+
capabilities: {
|
|
69
|
+
hover: boolean;
|
|
70
|
+
touch: boolean;
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./operation_manifest_plugin";
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// src/vite/operation_manifest_plugin.ts
|
|
2
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { glob } from "glob";
|
|
4
|
+
import { gqlPluckFromCodeStringSync } from "@graphql-tools/graphql-tag-pluck";
|
|
5
|
+
import { createHash } from "crypto";
|
|
6
|
+
import {
|
|
7
|
+
Kind,
|
|
8
|
+
parse,
|
|
9
|
+
print,
|
|
10
|
+
visit
|
|
11
|
+
} from "graphql";
|
|
12
|
+
import { ApolloClient, ApolloLink, InMemoryCache } from "@apollo/client";
|
|
13
|
+
import Observable from "rxjs";
|
|
14
|
+
import path from "path";
|
|
15
|
+
import fs from "fs";
|
|
16
|
+
var root = process.cwd();
|
|
17
|
+
var getRawValue = (node) => {
|
|
18
|
+
switch (node.kind) {
|
|
19
|
+
case Kind.STRING:
|
|
20
|
+
case Kind.BOOLEAN:
|
|
21
|
+
return node.value;
|
|
22
|
+
case Kind.LIST:
|
|
23
|
+
return node.values.map(getRawValue);
|
|
24
|
+
case Kind.OBJECT:
|
|
25
|
+
return node.fields.reduce((acc, field) => {
|
|
26
|
+
acc[field.name.value] = getRawValue(field.value);
|
|
27
|
+
return acc;
|
|
28
|
+
}, {});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
function getTypeName(type) {
|
|
32
|
+
let t = type;
|
|
33
|
+
while (t.kind === "NonNullType" || t.kind === "ListType") {
|
|
34
|
+
t = t.type;
|
|
35
|
+
}
|
|
36
|
+
return t.name.value;
|
|
37
|
+
}
|
|
38
|
+
var OperationManifestPlugin = () => {
|
|
39
|
+
const cache = /* @__PURE__ */ new Map();
|
|
40
|
+
let packageJson = null;
|
|
41
|
+
let config = null;
|
|
42
|
+
const clientCache = new InMemoryCache();
|
|
43
|
+
const client = new ApolloClient({
|
|
44
|
+
cache: clientCache,
|
|
45
|
+
link: new ApolloLink((operation) => {
|
|
46
|
+
const body = print(removeClientDirective(sortTopLevelDefinitions(operation.query)));
|
|
47
|
+
const name = operation.operationName;
|
|
48
|
+
const variables = operation.query.definitions.find((d) => d.kind === "OperationDefinition").variableDefinitions?.reduce(
|
|
49
|
+
(obj, varDef) => ({ ...obj, [varDef.variable.name.value]: getTypeName(varDef.type) }),
|
|
50
|
+
{}
|
|
51
|
+
);
|
|
52
|
+
const type = operation.query.definitions.find((d) => d.kind === "OperationDefinition").operation;
|
|
53
|
+
const prefetch = operation.query.definitions.find((d) => d.kind === "OperationDefinition").directives?.some((d) => d.name.value === "prefetch");
|
|
54
|
+
const id = createHash("sha256").update(body).digest("hex");
|
|
55
|
+
const prefetchID = prefetch ? "__anonymous" : void 0;
|
|
56
|
+
const tools = operation.query.definitions.find((d) => d.kind === "OperationDefinition").directives?.filter((d) => d.name.value === "tool").map((directive) => {
|
|
57
|
+
const directiveArguments = directive.arguments?.reduce((obj, arg) => ({ ...obj, [arg.name.value]: getRawValue(arg.value) }), {}) ?? {};
|
|
58
|
+
return {
|
|
59
|
+
name: directiveArguments["name"],
|
|
60
|
+
description: directiveArguments["description"],
|
|
61
|
+
extraInputs: directiveArguments["extraInputs"]
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
return Observable.of({ data: { id, name, type, body, variables, prefetch, prefetchID, tools } });
|
|
65
|
+
})
|
|
66
|
+
});
|
|
67
|
+
const processFile = async (file) => {
|
|
68
|
+
const code = readFileSync(file, "utf-8");
|
|
69
|
+
if (!code.includes("gql")) return;
|
|
70
|
+
const fileHash = createHash("md5").update(code).digest("hex");
|
|
71
|
+
if (cache.get("file")?.hash === fileHash) return;
|
|
72
|
+
const sources = await gqlPluckFromCodeStringSync(file, code, {
|
|
73
|
+
modules: [
|
|
74
|
+
{ name: "graphql-tag", identifier: "gql" },
|
|
75
|
+
{ name: "@apollo/client", identifier: "gql" }
|
|
76
|
+
]
|
|
77
|
+
}).map((source) => ({
|
|
78
|
+
node: parse(source.body),
|
|
79
|
+
file,
|
|
80
|
+
location: source.locationOffset
|
|
81
|
+
}));
|
|
82
|
+
const operations = [];
|
|
83
|
+
for (const source of sources) {
|
|
84
|
+
const type = source.node.definitions.find((d) => d.kind === "OperationDefinition").operation;
|
|
85
|
+
let result;
|
|
86
|
+
if (type === "query") {
|
|
87
|
+
result = await client.query({ query: source.node, fetchPolicy: "no-cache" });
|
|
88
|
+
} else if (type === "mutation") {
|
|
89
|
+
result = await client.mutate({ mutation: source.node, fetchPolicy: "no-cache" });
|
|
90
|
+
} else {
|
|
91
|
+
throw new Error("Found an unsupported operation type. Only Query and Mutation are supported.");
|
|
92
|
+
}
|
|
93
|
+
operations.push(result.data);
|
|
94
|
+
}
|
|
95
|
+
cache.set(file, {
|
|
96
|
+
file,
|
|
97
|
+
hash: fileHash,
|
|
98
|
+
operations
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
const generateManifest = async () => {
|
|
102
|
+
const operations = Array.from(cache.values()).flatMap((entry) => entry.operations);
|
|
103
|
+
if (operations.filter((o) => o.prefetch).length > 1) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
"Found multiple operations marked as `@prefetch`. You can only mark 1 operation with `@prefetch`."
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
let resource = "";
|
|
109
|
+
if (config.command === "serve") {
|
|
110
|
+
resource = packageJson.entry?.[config.mode] ?? `http${config.server.https ? "s" : ""}://${config.server.host ?? "localhost"}:${config.server.port}`;
|
|
111
|
+
} else {
|
|
112
|
+
let entryPoint = packageJson.entry?.[config.mode];
|
|
113
|
+
if (entryPoint) {
|
|
114
|
+
resource = entryPoint;
|
|
115
|
+
} else if (config.mode === "production") {
|
|
116
|
+
resource = "index.html";
|
|
117
|
+
} else {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`No entry point found for mode "${config.mode}". Entry points other than "development" and "production" must be defined in package.json file.`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const manifest = {
|
|
124
|
+
format: "apollo-ai-app-manifest",
|
|
125
|
+
version: "1",
|
|
126
|
+
name: packageJson.name,
|
|
127
|
+
description: packageJson.description,
|
|
128
|
+
hash: createHash("sha256").update(Date.now().toString()).digest("hex"),
|
|
129
|
+
operations: Array.from(cache.values()).flatMap((entry) => entry.operations),
|
|
130
|
+
resource
|
|
131
|
+
};
|
|
132
|
+
if (config.command === "build") {
|
|
133
|
+
const dest = path.resolve(root, config.build.outDir, ".application-manifest.json");
|
|
134
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
135
|
+
writeFileSync(dest, JSON.stringify(manifest));
|
|
136
|
+
}
|
|
137
|
+
writeFileSync(".application-manifest.json", JSON.stringify(manifest));
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
name: "OperationManifest",
|
|
141
|
+
async configResolved(resolvedConfig) {
|
|
142
|
+
config = resolvedConfig;
|
|
143
|
+
},
|
|
144
|
+
async buildStart() {
|
|
145
|
+
packageJson = JSON.parse(readFileSync("package.json", "utf-8"));
|
|
146
|
+
const files = await glob("src/**/*.{ts,tsx,js,jsx}");
|
|
147
|
+
for (const file of files) {
|
|
148
|
+
const fullPath = path.resolve(root, file);
|
|
149
|
+
await processFile(fullPath);
|
|
150
|
+
}
|
|
151
|
+
if (config.command === "serve") {
|
|
152
|
+
await generateManifest();
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
156
|
+
configureServer(server) {
|
|
157
|
+
server.watcher.on("change", async (file) => {
|
|
158
|
+
if (file.endsWith("package.json")) {
|
|
159
|
+
packageJson = JSON.parse(readFileSync("package.json", "utf-8"));
|
|
160
|
+
await generateManifest();
|
|
161
|
+
} else if (file.match(/\.(jsx?|tsx?)$/)) {
|
|
162
|
+
await processFile(file);
|
|
163
|
+
await generateManifest();
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
},
|
|
167
|
+
async writeBundle() {
|
|
168
|
+
await generateManifest();
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
function sortTopLevelDefinitions(query) {
|
|
173
|
+
const definitions = [...query.definitions];
|
|
174
|
+
definitions.sort((a, b) => {
|
|
175
|
+
if (a.kind > b.kind) {
|
|
176
|
+
return -1;
|
|
177
|
+
}
|
|
178
|
+
if (a.kind < b.kind) {
|
|
179
|
+
return 1;
|
|
180
|
+
}
|
|
181
|
+
const aName = a.kind === "OperationDefinition" || a.kind === "FragmentDefinition" ? a.name?.value ?? "" : "";
|
|
182
|
+
const bName = b.kind === "OperationDefinition" || b.kind === "FragmentDefinition" ? b.name?.value ?? "" : "";
|
|
183
|
+
if (aName < bName) {
|
|
184
|
+
return -1;
|
|
185
|
+
}
|
|
186
|
+
if (aName > bName) {
|
|
187
|
+
return 1;
|
|
188
|
+
}
|
|
189
|
+
return 0;
|
|
190
|
+
});
|
|
191
|
+
return {
|
|
192
|
+
...query,
|
|
193
|
+
definitions
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function removeClientDirective(doc) {
|
|
197
|
+
return visit(doc, {
|
|
198
|
+
OperationDefinition(node) {
|
|
199
|
+
return {
|
|
200
|
+
...node,
|
|
201
|
+
directives: node.directives?.filter((d) => d.name.value !== "prefetch" && d.name.value !== "tool")
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
export {
|
|
207
|
+
OperationManifestPlugin,
|
|
208
|
+
getTypeName,
|
|
209
|
+
sortTopLevelDefinitions
|
|
210
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TypeNode, type DocumentNode } from "graphql";
|
|
2
|
+
export declare function getTypeName(type: TypeNode): string;
|
|
3
|
+
export declare const OperationManifestPlugin: () => {
|
|
4
|
+
name: string;
|
|
5
|
+
configResolved(resolvedConfig: any): Promise<void>;
|
|
6
|
+
buildStart(): Promise<void>;
|
|
7
|
+
configureServer(server: any): void;
|
|
8
|
+
writeBundle(): Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
export declare function sortTopLevelDefinitions(query: DocumentNode): DocumentNode;
|