@pancake-apps/server 0.0.0-snapshot-20260125200133
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/dist/chunk-72LRTGM5.js +169 -0
- package/dist/chunk-72LRTGM5.js.map +1 -0
- package/dist/chunk-CVG3DAN6.js +172 -0
- package/dist/chunk-CVG3DAN6.js.map +1 -0
- package/dist/index.cjs +1519 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +703 -0
- package/dist/index.d.ts +703 -0
- package/dist/index.js +443 -0
- package/dist/index.js.map +1 -0
- package/dist/server-B32JUAYX.js +518 -0
- package/dist/server-B32JUAYX.js.map +1 -0
- package/dist/stdio-ZTUR5PV4.js +104 -0
- package/dist/stdio-ZTUR5PV4.js.map +1 -0
- package/dist/tunnel-AGGQW6IC.js +3 -0
- package/dist/tunnel-AGGQW6IC.js.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
|
|
3
|
+
// src/tunnel/index.ts
|
|
4
|
+
async function createTunnel(config) {
|
|
5
|
+
if (config.provider === "cloudflare") {
|
|
6
|
+
return createCloudflareTunnel(config.port);
|
|
7
|
+
} else if (config.provider === "ngrok") {
|
|
8
|
+
return createNgrokTunnel(config.port, config.authtoken);
|
|
9
|
+
} else {
|
|
10
|
+
throw new Error(`Unknown tunnel provider: ${config.provider}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
async function createCloudflareTunnel(port) {
|
|
14
|
+
let state = "idle";
|
|
15
|
+
let process = null;
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
state = "starting";
|
|
18
|
+
process = spawn("cloudflared", ["tunnel", "--url", `http://localhost:${port}`], {
|
|
19
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
20
|
+
});
|
|
21
|
+
let resolved = false;
|
|
22
|
+
const urlPattern = /https:\/\/[a-zA-Z0-9-]+\.trycloudflare\.com/;
|
|
23
|
+
const handleOutput = (data) => {
|
|
24
|
+
const output = data.toString();
|
|
25
|
+
const match = output.match(urlPattern);
|
|
26
|
+
if (match && !resolved) {
|
|
27
|
+
resolved = true;
|
|
28
|
+
state = "active";
|
|
29
|
+
const tunnelUrl = match[0];
|
|
30
|
+
resolve({
|
|
31
|
+
url: tunnelUrl,
|
|
32
|
+
close: async () => {
|
|
33
|
+
if (process && state === "active") {
|
|
34
|
+
state = "terminated";
|
|
35
|
+
process.kill("SIGTERM");
|
|
36
|
+
process = null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
process.stdout?.on("data", handleOutput);
|
|
43
|
+
process.stderr?.on("data", handleOutput);
|
|
44
|
+
process.on("error", (error) => {
|
|
45
|
+
state = "terminated";
|
|
46
|
+
if (!resolved) {
|
|
47
|
+
if (error.code === "ENOENT") {
|
|
48
|
+
reject(new Error(
|
|
49
|
+
"cloudflared is not installed. Install it with:\n macOS: brew install cloudflared\n Linux: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/"
|
|
50
|
+
));
|
|
51
|
+
} else {
|
|
52
|
+
reject(new Error(`Failed to start cloudflared: ${error.message}`));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
process.on("exit", (code) => {
|
|
57
|
+
state = "terminated";
|
|
58
|
+
if (!resolved) {
|
|
59
|
+
reject(new Error(`cloudflared exited with code ${code} before providing a tunnel URL`));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
if (!resolved) {
|
|
64
|
+
state = "terminated";
|
|
65
|
+
process?.kill("SIGTERM");
|
|
66
|
+
reject(new Error("Timed out waiting for cloudflared to provide a tunnel URL"));
|
|
67
|
+
}
|
|
68
|
+
}, 3e4);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async function createNgrokTunnel(port, authtoken) {
|
|
72
|
+
let state = "idle";
|
|
73
|
+
let process = null;
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
state = "starting";
|
|
76
|
+
const args = ["http", String(port)];
|
|
77
|
+
if (authtoken) {
|
|
78
|
+
args.push("--authtoken", authtoken);
|
|
79
|
+
}
|
|
80
|
+
args.push("--log", "stdout", "--log-format", "json");
|
|
81
|
+
process = spawn("ngrok", args, {
|
|
82
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
83
|
+
});
|
|
84
|
+
let resolved = false;
|
|
85
|
+
const urlPattern = /https:\/\/[a-zA-Z0-9-]+\.ngrok(-free)?\.app/;
|
|
86
|
+
const handleOutput = (data) => {
|
|
87
|
+
const output = data.toString();
|
|
88
|
+
const lines = output.split("\n").filter(Boolean);
|
|
89
|
+
for (const line of lines) {
|
|
90
|
+
try {
|
|
91
|
+
const json = JSON.parse(line);
|
|
92
|
+
if (json.url && !resolved) {
|
|
93
|
+
resolved = true;
|
|
94
|
+
state = "active";
|
|
95
|
+
resolve({
|
|
96
|
+
url: json.url,
|
|
97
|
+
close: async () => {
|
|
98
|
+
if (process && state === "active") {
|
|
99
|
+
state = "terminated";
|
|
100
|
+
process.kill("SIGTERM");
|
|
101
|
+
process = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
const match = line.match(urlPattern);
|
|
109
|
+
if (match && !resolved) {
|
|
110
|
+
resolved = true;
|
|
111
|
+
state = "active";
|
|
112
|
+
resolve({
|
|
113
|
+
url: match[0],
|
|
114
|
+
close: async () => {
|
|
115
|
+
if (process && state === "active") {
|
|
116
|
+
state = "terminated";
|
|
117
|
+
process.kill("SIGTERM");
|
|
118
|
+
process = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
process.stdout?.on("data", handleOutput);
|
|
128
|
+
process.stderr?.on("data", handleOutput);
|
|
129
|
+
process.on("error", (error) => {
|
|
130
|
+
state = "terminated";
|
|
131
|
+
if (!resolved) {
|
|
132
|
+
if (error.code === "ENOENT") {
|
|
133
|
+
reject(new Error(
|
|
134
|
+
"ngrok is not installed. Install it with:\n macOS: brew install ngrok\n Linux/Windows: https://ngrok.com/download"
|
|
135
|
+
));
|
|
136
|
+
} else {
|
|
137
|
+
reject(new Error(`Failed to start ngrok: ${error.message}`));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
process.on("exit", (code) => {
|
|
142
|
+
state = "terminated";
|
|
143
|
+
if (!resolved) {
|
|
144
|
+
reject(new Error(`ngrok exited with code ${code} before providing a tunnel URL`));
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
if (!resolved) {
|
|
149
|
+
state = "terminated";
|
|
150
|
+
process?.kill("SIGTERM");
|
|
151
|
+
reject(new Error("Timed out waiting for ngrok to provide a tunnel URL"));
|
|
152
|
+
}
|
|
153
|
+
}, 3e4);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
async function isTunnelProviderAvailable(provider) {
|
|
157
|
+
const command = provider === "cloudflare" ? "cloudflared" : "ngrok";
|
|
158
|
+
return new Promise((resolve) => {
|
|
159
|
+
const process = spawn(command, ["--version"], {
|
|
160
|
+
stdio: "ignore"
|
|
161
|
+
});
|
|
162
|
+
process.on("error", () => resolve(false));
|
|
163
|
+
process.on("exit", (code) => resolve(code === 0));
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export { createTunnel, isTunnelProviderAvailable };
|
|
168
|
+
//# sourceMappingURL=chunk-72LRTGM5.js.map
|
|
169
|
+
//# sourceMappingURL=chunk-72LRTGM5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tunnel/index.ts"],"names":[],"mappings":"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","file":"chunk-72LRTGM5.js","sourcesContent":["import { spawn, type ChildProcess } from 'node:child_process';\n\n/**\n * Tunnel provider type\n */\nexport type TunnelProvider = 'ngrok' | 'cloudflare';\n\n/**\n * Tunnel configuration\n */\nexport interface TunnelConfig {\n /** Tunnel provider to use */\n provider: TunnelProvider;\n /** Local port to tunnel */\n port: number;\n /** Optional authtoken for ngrok */\n authtoken?: string;\n}\n\n/**\n * Tunnel result\n */\nexport interface TunnelResult {\n /** Public URL of the tunnel */\n url: string;\n /** Stop the tunnel */\n close: () => Promise<void>;\n}\n\n/**\n * Tunnel state for lifecycle management\n */\ntype TunnelState = 'idle' | 'starting' | 'active' | 'terminated';\n\n/**\n * Create a tunnel using the specified provider\n */\nexport async function createTunnel(config: TunnelConfig): Promise<TunnelResult> {\n if (config.provider === 'cloudflare') {\n return createCloudflareTunnel(config.port);\n } else if (config.provider === 'ngrok') {\n return createNgrokTunnel(config.port, config.authtoken);\n } else {\n throw new Error(`Unknown tunnel provider: ${config.provider}`);\n }\n}\n\n/**\n * Create a Cloudflare Quick Tunnel\n * \n * Uses `cloudflared tunnel --url` command which:\n * - Does NOT require login\n * - Does NOT require Cloudflare credentials\n * - Does NOT require config files\n * - Automatically selects Quick Tunnel mode\n * \n * The tunnel URL is:\n * - Randomly generated\n * - Ephemeral (tied to the process lifetime)\n * - Hosted under *.trycloudflare.com\n */\nasync function createCloudflareTunnel(port: number): Promise<TunnelResult> {\n let state: TunnelState = 'idle';\n let process: ChildProcess | null = null;\n\n return new Promise((resolve, reject) => {\n state = 'starting';\n\n // Start cloudflared process\n process = spawn('cloudflared', ['tunnel', '--url', `http://localhost:${port}`], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let resolved = false;\n const urlPattern = /https:\\/\\/[a-zA-Z0-9-]+\\.trycloudflare\\.com/;\n\n // Parse stdout/stderr for the tunnel URL\n const handleOutput = (data: Buffer) => {\n const output = data.toString();\n \n // Check for URL in output\n const match = output.match(urlPattern);\n if (match && !resolved) {\n resolved = true;\n state = 'active';\n \n const tunnelUrl = match[0];\n resolve({\n url: tunnelUrl,\n close: async () => {\n if (process && state === 'active') {\n state = 'terminated';\n process.kill('SIGTERM');\n process = null;\n }\n },\n });\n }\n };\n\n process.stdout?.on('data', handleOutput);\n process.stderr?.on('data', handleOutput);\n\n process.on('error', (error) => {\n state = 'terminated';\n if (!resolved) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n reject(new Error(\n 'cloudflared is not installed. Install it with:\\n' +\n ' macOS: brew install cloudflared\\n' +\n ' Linux: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/'\n ));\n } else {\n reject(new Error(`Failed to start cloudflared: ${error.message}`));\n }\n }\n });\n\n process.on('exit', (code) => {\n state = 'terminated';\n if (!resolved) {\n reject(new Error(`cloudflared exited with code ${code} before providing a tunnel URL`));\n }\n });\n\n // Timeout after 30 seconds\n setTimeout(() => {\n if (!resolved) {\n state = 'terminated';\n process?.kill('SIGTERM');\n reject(new Error('Timed out waiting for cloudflared to provide a tunnel URL'));\n }\n }, 30000);\n });\n}\n\n/**\n * Create an ngrok tunnel\n * \n * Uses `ngrok http` command to create a tunnel\n */\nasync function createNgrokTunnel(port: number, authtoken?: string): Promise<TunnelResult> {\n let state: TunnelState = 'idle';\n let process: ChildProcess | null = null;\n\n return new Promise((resolve, reject) => {\n state = 'starting';\n\n // Build command args\n const args = ['http', String(port)];\n if (authtoken) {\n args.push('--authtoken', authtoken);\n }\n // Request JSON output for easier parsing\n args.push('--log', 'stdout', '--log-format', 'json');\n\n process = spawn('ngrok', args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let resolved = false;\n // ngrok outputs JSON with url field when started\n const urlPattern = /https:\\/\\/[a-zA-Z0-9-]+\\.ngrok(-free)?\\.app/;\n\n const handleOutput = (data: Buffer) => {\n const output = data.toString();\n \n // Try to parse JSON log lines\n const lines = output.split('\\n').filter(Boolean);\n for (const line of lines) {\n try {\n const json = JSON.parse(line);\n if (json.url && !resolved) {\n resolved = true;\n state = 'active';\n resolve({\n url: json.url,\n close: async () => {\n if (process && state === 'active') {\n state = 'terminated';\n process.kill('SIGTERM');\n process = null;\n }\n },\n });\n return;\n }\n } catch {\n // Not JSON, try regex pattern\n const match = line.match(urlPattern);\n if (match && !resolved) {\n resolved = true;\n state = 'active';\n resolve({\n url: match[0],\n close: async () => {\n if (process && state === 'active') {\n state = 'terminated';\n process.kill('SIGTERM');\n process = null;\n }\n },\n });\n return;\n }\n }\n }\n };\n\n process.stdout?.on('data', handleOutput);\n process.stderr?.on('data', handleOutput);\n\n process.on('error', (error) => {\n state = 'terminated';\n if (!resolved) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n reject(new Error(\n 'ngrok is not installed. Install it with:\\n' +\n ' macOS: brew install ngrok\\n' +\n ' Linux/Windows: https://ngrok.com/download'\n ));\n } else {\n reject(new Error(`Failed to start ngrok: ${error.message}`));\n }\n }\n });\n\n process.on('exit', (code) => {\n state = 'terminated';\n if (!resolved) {\n reject(new Error(`ngrok exited with code ${code} before providing a tunnel URL`));\n }\n });\n\n // Timeout after 30 seconds\n setTimeout(() => {\n if (!resolved) {\n state = 'terminated';\n process?.kill('SIGTERM');\n reject(new Error('Timed out waiting for ngrok to provide a tunnel URL'));\n }\n }, 30000);\n });\n}\n\n/**\n * Check if a tunnel provider is available\n */\nexport async function isTunnelProviderAvailable(provider: TunnelProvider): Promise<boolean> {\n const command = provider === 'cloudflare' ? 'cloudflared' : 'ngrok';\n \n return new Promise((resolve) => {\n const process = spawn(command, ['--version'], {\n stdio: 'ignore',\n });\n \n process.on('error', () => resolve(false));\n process.on('exit', (code) => resolve(code === 0));\n });\n}\n"]}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// src/utils/metadata.ts
|
|
2
|
+
function mapVisibilityToMcp(visibility) {
|
|
3
|
+
switch (visibility) {
|
|
4
|
+
case "model":
|
|
5
|
+
return ["model"];
|
|
6
|
+
case "app":
|
|
7
|
+
return ["app"];
|
|
8
|
+
case "both":
|
|
9
|
+
default:
|
|
10
|
+
return ["model", "app"];
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function mapVisibilityToOpenAI(visibility) {
|
|
14
|
+
switch (visibility) {
|
|
15
|
+
case "model":
|
|
16
|
+
return { "openai/visibility": "public", "openai/widgetAccessible": false };
|
|
17
|
+
case "app":
|
|
18
|
+
return { "openai/visibility": "private", "openai/widgetAccessible": true };
|
|
19
|
+
case "both":
|
|
20
|
+
default:
|
|
21
|
+
return { "openai/visibility": "public", "openai/widgetAccessible": true };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function mapVisibilityToAudience(visibility) {
|
|
25
|
+
switch (visibility) {
|
|
26
|
+
case "model":
|
|
27
|
+
return ["assistant"];
|
|
28
|
+
case "app":
|
|
29
|
+
return ["user"];
|
|
30
|
+
case "both":
|
|
31
|
+
default:
|
|
32
|
+
return ["assistant", "user"];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/utils/csp.ts
|
|
37
|
+
function generateMcpCSPMetadata(csp) {
|
|
38
|
+
const result = {};
|
|
39
|
+
if (csp.connectDomains && csp.connectDomains.length > 0) {
|
|
40
|
+
result.connectDomains = csp.connectDomains;
|
|
41
|
+
}
|
|
42
|
+
if (csp.resourceDomains && csp.resourceDomains.length > 0) {
|
|
43
|
+
result.resourceDomains = csp.resourceDomains;
|
|
44
|
+
}
|
|
45
|
+
if (csp.scriptDomains && csp.scriptDomains.length > 0) {
|
|
46
|
+
result.scriptDomains = csp.scriptDomains;
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
function generateOpenAICSPMetadata(csp) {
|
|
51
|
+
const result = {};
|
|
52
|
+
if (csp.connectDomains && csp.connectDomains.length > 0) {
|
|
53
|
+
result.connect_domains = csp.connectDomains;
|
|
54
|
+
}
|
|
55
|
+
if (csp.resourceDomains && csp.resourceDomains.length > 0) {
|
|
56
|
+
result.resource_domains = csp.resourceDomains;
|
|
57
|
+
}
|
|
58
|
+
if (csp.scriptDomains && csp.scriptDomains.length > 0) {
|
|
59
|
+
result.script_domains = csp.scriptDomains;
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/adapters/mcp.ts
|
|
65
|
+
var McpAdapter = class {
|
|
66
|
+
/**
|
|
67
|
+
* Build tool metadata for MCP protocol
|
|
68
|
+
*/
|
|
69
|
+
buildToolMeta(tool, uiUri) {
|
|
70
|
+
const visibility = mapVisibilityToMcp(tool.visibility);
|
|
71
|
+
const uiMeta = {
|
|
72
|
+
visibility,
|
|
73
|
+
...uiUri ? { resourceUri: uiUri } : {}
|
|
74
|
+
};
|
|
75
|
+
const annotations = {
|
|
76
|
+
audience: mapVisibilityToAudience(tool.visibility)
|
|
77
|
+
};
|
|
78
|
+
const meta = {
|
|
79
|
+
ui: uiMeta,
|
|
80
|
+
"ui/visibility": visibility,
|
|
81
|
+
...uiUri ? { "ui/resourceUri": uiUri } : {}
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
annotations,
|
|
85
|
+
_meta: meta
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Build UI resource metadata for MCP protocol
|
|
90
|
+
*/
|
|
91
|
+
buildUIResourceMeta(uiDef) {
|
|
92
|
+
const uiMeta = {};
|
|
93
|
+
if (uiDef.csp) {
|
|
94
|
+
const cspMetadata = generateMcpCSPMetadata(uiDef.csp);
|
|
95
|
+
if (Object.keys(cspMetadata).length > 0) {
|
|
96
|
+
uiMeta.csp = cspMetadata;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (uiDef.prefersBorder !== void 0) {
|
|
100
|
+
uiMeta.prefersBorder = uiDef.prefersBorder;
|
|
101
|
+
}
|
|
102
|
+
if (uiDef.autoResize !== void 0) {
|
|
103
|
+
uiMeta.autoResize = uiDef.autoResize;
|
|
104
|
+
}
|
|
105
|
+
if (uiDef.domain) {
|
|
106
|
+
uiMeta.domain = uiDef.domain;
|
|
107
|
+
}
|
|
108
|
+
const result = {
|
|
109
|
+
mimeType: "text/html;profile=mcp-app"
|
|
110
|
+
};
|
|
111
|
+
if (Object.keys(uiMeta).length > 0) {
|
|
112
|
+
result._meta = { ui: uiMeta };
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// src/adapters/openai.ts
|
|
119
|
+
var OpenAIAdapter = class {
|
|
120
|
+
/**
|
|
121
|
+
* Build tool metadata for OpenAI protocol
|
|
122
|
+
*/
|
|
123
|
+
buildToolMeta(tool, uiUri) {
|
|
124
|
+
const meta = {};
|
|
125
|
+
const visibilitySettings = mapVisibilityToOpenAI(tool.visibility);
|
|
126
|
+
Object.assign(meta, visibilitySettings);
|
|
127
|
+
if (uiUri) {
|
|
128
|
+
meta["openai/outputTemplate"] = uiUri;
|
|
129
|
+
}
|
|
130
|
+
const annotations = {
|
|
131
|
+
audience: mapVisibilityToAudience(tool.visibility)
|
|
132
|
+
};
|
|
133
|
+
return {
|
|
134
|
+
annotations,
|
|
135
|
+
_meta: Object.keys(meta).length > 0 ? meta : void 0
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Build UI resource metadata for OpenAI protocol
|
|
140
|
+
*/
|
|
141
|
+
buildUIResourceMeta(uiDef) {
|
|
142
|
+
const meta = {};
|
|
143
|
+
if (uiDef.csp) {
|
|
144
|
+
const cspMetadata = generateOpenAICSPMetadata(uiDef.csp);
|
|
145
|
+
if (Object.keys(cspMetadata).length > 0) {
|
|
146
|
+
meta["openai/widgetCSP"] = cspMetadata;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (uiDef.prefersBorder !== void 0) {
|
|
150
|
+
meta["openai/widgetPrefersBorder"] = uiDef.prefersBorder;
|
|
151
|
+
}
|
|
152
|
+
if (uiDef.domain) {
|
|
153
|
+
meta["openai/widgetDomain"] = uiDef.domain;
|
|
154
|
+
}
|
|
155
|
+
const result = {
|
|
156
|
+
mimeType: "text/html+skybridge"
|
|
157
|
+
};
|
|
158
|
+
if (Object.keys(meta).length > 0) {
|
|
159
|
+
result._meta = meta;
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// src/adapters/index.ts
|
|
166
|
+
function createAdapter(protocol) {
|
|
167
|
+
return protocol === "openai" ? new OpenAIAdapter() : new McpAdapter();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export { createAdapter };
|
|
171
|
+
//# sourceMappingURL=chunk-CVG3DAN6.js.map
|
|
172
|
+
//# sourceMappingURL=chunk-CVG3DAN6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/metadata.ts","../src/utils/csp.ts","../src/adapters/mcp.ts","../src/adapters/openai.ts","../src/adapters/index.ts"],"names":[],"mappings":"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","file":"chunk-CVG3DAN6.js","sourcesContent":["/**\n * Protocol metadata utilities\n *\n * Provides utilities for mapping tool definitions to protocol-specific metadata\n * formats for MCP Apps and OpenAI/ChatGPT Apps.\n */\n\nimport type { Visibility } from '../types/protocol.js';\n\n// =============================================================================\n// MCP VISIBILITY MAPPING\n// =============================================================================\n\n/**\n * MCP visibility value\n * Array of who can invoke: \"model\", \"app\", or both\n */\nexport type McpVisibilityValue = ('model' | 'app')[];\n\n/**\n * Map visibility to MCP protocol format\n *\n * Visibility mappings:\n * - \"both\" (default): [\"model\", \"app\"] - Tool accessible to both model and UI\n * - \"model\": [\"model\"] - Tool only accessible to the model\n * - \"app\": [\"app\"] - Tool only accessible to the UI\n */\nexport function mapVisibilityToMcp(visibility?: Visibility): McpVisibilityValue {\n switch (visibility) {\n case 'model':\n return ['model'];\n case 'app':\n return ['app'];\n case 'both':\n default:\n return ['model', 'app'];\n }\n}\n\n// =============================================================================\n// OPENAI VISIBILITY MAPPING\n// =============================================================================\n\n/**\n * OpenAI visibility settings with proper openai/ prefixed keys\n *\n * Settings:\n * - openai/visibility: \"public\" | \"private\" - Controls model access\n * - openai/widgetAccessible: boolean - Controls UI access\n */\nexport interface OpenAIVisibilitySettings {\n 'openai/visibility': 'public' | 'private';\n 'openai/widgetAccessible': boolean;\n}\n\n/**\n * Map visibility to OpenAI/ChatGPT protocol settings\n *\n * Visibility mappings:\n * - \"both\" (default): visibility=\"public\", widgetAccessible=true\n * - \"model\": visibility=\"public\", widgetAccessible=false\n * - \"app\": visibility=\"private\", widgetAccessible=true\n */\nexport function mapVisibilityToOpenAI(visibility?: Visibility): OpenAIVisibilitySettings {\n switch (visibility) {\n case 'model':\n return { 'openai/visibility': 'public', 'openai/widgetAccessible': false };\n case 'app':\n return { 'openai/visibility': 'private', 'openai/widgetAccessible': true };\n case 'both':\n default:\n return { 'openai/visibility': 'public', 'openai/widgetAccessible': true };\n }\n}\n\n// =============================================================================\n// MCP AUDIENCE MAPPING (for annotations)\n// =============================================================================\n\n/**\n * Map visibility to MCP audience format for annotations\n *\n * Visibility mappings:\n * - \"both\" (default): [\"assistant\", \"user\"]\n * - \"model\": [\"assistant\"]\n * - \"app\": [\"user\"]\n */\nexport function mapVisibilityToAudience(visibility?: Visibility): string[] {\n switch (visibility) {\n case 'model':\n return ['assistant'];\n case 'app':\n return ['user'];\n case 'both':\n default:\n return ['assistant', 'user'];\n }\n}\n","/**\n * CSP (Content Security Policy) utilities\n *\n * Provides utilities for mapping CSP configuration to protocol-specific formats\n * for MCP Apps and OpenAI/ChatGPT Apps.\n */\n\nimport type { ContentSecurityPolicy } from '../types/protocol.js';\n\n// =============================================================================\n// MCP CSP METADATA\n// =============================================================================\n\n/**\n * MCP CSP metadata format (camelCase)\n */\nexport interface McpCSPMetadata {\n connectDomains?: string[];\n resourceDomains?: string[];\n scriptDomains?: string[];\n}\n\n/**\n * Generate CSP metadata for MCP Apps protocol\n *\n * MCP Apps use camelCase field names.\n */\nexport function generateMcpCSPMetadata(csp: ContentSecurityPolicy): McpCSPMetadata {\n const result: McpCSPMetadata = {};\n\n if (csp.connectDomains && csp.connectDomains.length > 0) {\n result.connectDomains = csp.connectDomains;\n }\n\n if (csp.resourceDomains && csp.resourceDomains.length > 0) {\n result.resourceDomains = csp.resourceDomains;\n }\n\n if (csp.scriptDomains && csp.scriptDomains.length > 0) {\n result.scriptDomains = csp.scriptDomains;\n }\n\n return result;\n}\n\n// =============================================================================\n// OPENAI CSP METADATA\n// =============================================================================\n\n/**\n * OpenAI CSP metadata format (snake_case)\n */\nexport interface OpenAICSPMetadata {\n connect_domains?: string[];\n resource_domains?: string[];\n script_domains?: string[];\n}\n\n/**\n * Generate CSP metadata for OpenAI/ChatGPT Apps protocol\n *\n * ChatGPT Apps use snake_case field names.\n */\nexport function generateOpenAICSPMetadata(csp: ContentSecurityPolicy): OpenAICSPMetadata {\n const result: OpenAICSPMetadata = {};\n\n if (csp.connectDomains && csp.connectDomains.length > 0) {\n result.connect_domains = csp.connectDomains;\n }\n\n if (csp.resourceDomains && csp.resourceDomains.length > 0) {\n result.resource_domains = csp.resourceDomains;\n }\n\n if (csp.scriptDomains && csp.scriptDomains.length > 0) {\n result.script_domains = csp.scriptDomains;\n }\n\n return result;\n}\n","/**\n * MCP Protocol Adapter\n *\n * Handles metadata generation for MCP Apps protocol.\n * Uses camelCase naming and _meta.ui.* namespace.\n */\n\nimport type { InternalTool } from '../types/tool.js';\nimport type { UIDefinition } from '../types/protocol.js';\nimport type { ProtocolAdapter, ToolMetaResult, UIResourceMetaResult } from './types.js';\nimport { mapVisibilityToMcp, mapVisibilityToAudience } from '../utils/metadata.js';\nimport { generateMcpCSPMetadata } from '../utils/csp.js';\n\n// =============================================================================\n// MCP ADAPTER\n// =============================================================================\n\n/**\n * MCP protocol adapter implementation\n *\n * Generates metadata in MCP Apps format:\n * - Visibility as array: [\"model\"], [\"app\"], or [\"model\", \"app\"]\n * - Metadata under _meta.ui.* namespace\n * - MIME type: text/html;profile=mcp-app\n * - CSP with camelCase keys (connectDomains, resourceDomains)\n */\nexport class McpAdapter implements ProtocolAdapter {\n /**\n * Build tool metadata for MCP protocol\n */\n buildToolMeta(tool: InternalTool, uiUri?: string): ToolMetaResult {\n const visibility = mapVisibilityToMcp(tool.visibility);\n const uiMeta: Record<string, unknown> = {\n visibility,\n ...(uiUri ? { resourceUri: uiUri } : {}),\n };\n\n // Build annotations with audience\n const annotations: Record<string, unknown> = {\n audience: mapVisibilityToAudience(tool.visibility),\n };\n\n // Provide both nested and flat shapes for compatibility:\n // - ext-apps / MCP Apps uses nested `_meta.ui.resourceUri`\n // - Some inspectors use flat `_meta[\"ui/resourceUri\"]` key\n const meta: Record<string, unknown> = {\n ui: uiMeta,\n 'ui/visibility': visibility,\n ...(uiUri ? { 'ui/resourceUri': uiUri } : {}),\n };\n\n return {\n annotations,\n _meta: meta,\n };\n }\n\n /**\n * Build UI resource metadata for MCP protocol\n */\n buildUIResourceMeta(uiDef: UIDefinition): UIResourceMetaResult {\n const uiMeta: Record<string, unknown> = {};\n\n // Add CSP metadata if specified\n if (uiDef.csp) {\n const cspMetadata = generateMcpCSPMetadata(uiDef.csp);\n if (Object.keys(cspMetadata).length > 0) {\n uiMeta.csp = cspMetadata;\n }\n }\n\n // Add prefersBorder if specified\n if (uiDef.prefersBorder !== undefined) {\n uiMeta.prefersBorder = uiDef.prefersBorder;\n }\n\n // Add autoResize if specified\n if (uiDef.autoResize !== undefined) {\n uiMeta.autoResize = uiDef.autoResize;\n }\n\n // Add domain if specified\n if (uiDef.domain) {\n uiMeta.domain = uiDef.domain;\n }\n\n const result: UIResourceMetaResult = {\n mimeType: 'text/html;profile=mcp-app',\n };\n\n if (Object.keys(uiMeta).length > 0) {\n result._meta = { ui: uiMeta };\n }\n\n return result;\n }\n}\n","/**\n * OpenAI Protocol Adapter\n *\n * Handles metadata generation for OpenAI/ChatGPT Apps protocol.\n * Uses snake_case naming and openai/* prefixed keys.\n */\n\nimport type { InternalTool } from '../types/tool.js';\nimport type { UIDefinition } from '../types/protocol.js';\nimport type { ProtocolAdapter, ToolMetaResult, UIResourceMetaResult } from './types.js';\nimport { mapVisibilityToOpenAI, mapVisibilityToAudience } from '../utils/metadata.js';\nimport { generateOpenAICSPMetadata } from '../utils/csp.js';\n\n// =============================================================================\n// OPENAI ADAPTER\n// =============================================================================\n\n/**\n * OpenAI protocol adapter implementation\n *\n * Generates metadata in OpenAI/ChatGPT Apps format:\n * - Visibility as openai/visibility (\"public\"/\"private\") + openai/widgetAccessible\n * - Metadata with openai/* prefixed keys\n * - MIME type: text/html+skybridge\n * - CSP with snake_case keys (connect_domains, resource_domains, etc.)\n */\nexport class OpenAIAdapter implements ProtocolAdapter {\n /**\n * Build tool metadata for OpenAI protocol\n */\n buildToolMeta(tool: InternalTool, uiUri?: string): ToolMetaResult {\n const meta: Record<string, unknown> = {};\n\n // Add visibility settings with openai/ prefixes\n const visibilitySettings = mapVisibilityToOpenAI(tool.visibility);\n Object.assign(meta, visibilitySettings);\n\n // Add UI binding with OpenAI prefix\n if (uiUri) {\n meta['openai/outputTemplate'] = uiUri;\n }\n\n // Build annotations with audience (same as MCP)\n const annotations: Record<string, unknown> = {\n audience: mapVisibilityToAudience(tool.visibility),\n };\n\n return {\n annotations,\n _meta: Object.keys(meta).length > 0 ? meta : undefined,\n };\n }\n\n /**\n * Build UI resource metadata for OpenAI protocol\n */\n buildUIResourceMeta(uiDef: UIDefinition): UIResourceMetaResult {\n const meta: Record<string, unknown> = {};\n\n // Add CSP metadata with openai/ prefix\n if (uiDef.csp) {\n const cspMetadata = generateOpenAICSPMetadata(uiDef.csp);\n if (Object.keys(cspMetadata).length > 0) {\n meta['openai/widgetCSP'] = cspMetadata;\n }\n }\n\n // Add prefersBorder with openai/ prefix\n if (uiDef.prefersBorder !== undefined) {\n meta['openai/widgetPrefersBorder'] = uiDef.prefersBorder;\n }\n\n // Add domain with openai/ prefix\n if (uiDef.domain) {\n meta['openai/widgetDomain'] = uiDef.domain;\n }\n\n const result: UIResourceMetaResult = {\n mimeType: 'text/html+skybridge',\n };\n\n if (Object.keys(meta).length > 0) {\n result._meta = meta;\n }\n\n return result;\n }\n}\n","/**\n * Protocol adapters module\n *\n * Provides adapter factory and exports for protocol-specific metadata generation.\n */\n\nimport type { Protocol } from '../types/protocol.js';\nimport type { ProtocolAdapter } from './types.js';\nimport { McpAdapter } from './mcp.js';\nimport { OpenAIAdapter } from './openai.js';\n\n// =============================================================================\n// ADAPTER FACTORY\n// =============================================================================\n\n/**\n * Create a protocol adapter for the specified protocol\n *\n * @param protocol - Target protocol (\"mcp\" | \"openai\")\n * @returns Protocol-specific adapter instance\n *\n * @example\n * ```typescript\n * const adapter = createAdapter(\"openai\");\n * const toolMeta = adapter.buildToolMeta(tool, uiUri);\n * ```\n */\nexport function createAdapter(protocol: Protocol): ProtocolAdapter {\n return protocol === 'openai' ? new OpenAIAdapter() : new McpAdapter();\n}\n\n// =============================================================================\n// EXPORTS\n// =============================================================================\n\nexport type { ProtocolAdapter, ToolMetaResult, UIResourceMetaResult } from './types.js';\nexport { McpAdapter } from './mcp.js';\nexport { OpenAIAdapter } from './openai.js';\n"]}
|