devframe 0.6.0-beta.2 → 0.6.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/dist/adapters/build.d.mts +1 -1
- package/dist/adapters/build.mjs +2 -2
- package/dist/adapters/cli.d.mts +1 -1
- package/dist/adapters/cli.mjs +9 -6
- package/dist/adapters/dev.d.mts +9 -2
- package/dist/adapters/dev.mjs +1 -1
- package/dist/adapters/embedded.d.mts +1 -1
- package/dist/adapters/mcp.d.mts +1 -1
- package/dist/adapters/mcp.mjs +1 -337
- package/dist/build-server-0jWgMrz-.mjs +338 -0
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.mjs +56 -22
- package/dist/constants.d.mts +31 -1
- package/dist/constants.mjs +33 -1
- package/dist/{context-3x_bgBgZ.mjs → context-CKJheEtf.mjs} +2 -2
- package/dist/{context-BEVByy_Z.d.mts → context-UX9qUDK7.d.mts} +1 -1
- package/dist/{dev-BaX8fLH2.mjs → dev-zC5542OV.mjs} +49 -6
- package/dist/{devframe-D-gr9sBx.d.mts → devframe-BtVHhmwM.d.mts} +76 -4
- package/dist/{dump-DoXkj4ku.mjs → dump-D09PZb7T.mjs} +14 -4
- package/dist/{hash-DFc5WwhO.mjs → hash-PAfnyu2k.mjs} +1 -1
- package/dist/helpers/vite.d.mts +1 -1
- package/dist/helpers/vite.mjs +2 -2
- package/dist/{host-h3-C5SZlk03.mjs → host-h3-Dsf-Tgwx.mjs} +37 -11
- package/dist/http-COX960WK.mjs +114 -0
- package/dist/index-ByaMT1Xp.d.mts +107 -0
- package/dist/{index-CxaPKDXX.d.mts → index-Dy6GFDRf.d.mts} +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/{launch-editor-DPfltGA4.mjs → launch-editor-DIzaS5EG.mjs} +58 -43
- package/dist/node/auth.d.mts +2 -64
- package/dist/node/auth.mjs +2 -100
- package/dist/node/hub-internals.d.mts +2 -2
- package/dist/node/hub-internals.mjs +1 -1
- package/dist/node/index.d.mts +3 -3
- package/dist/node/index.mjs +3 -3
- package/dist/recipes/interactive-auth.d.mts +53 -0
- package/dist/recipes/interactive-auth.mjs +132 -0
- package/dist/recipes/open-helpers.mjs +2 -2
- package/dist/{revoke-ENfxWkFK.mjs → revoke-BtQDKTp7.mjs} +1 -1
- package/dist/rpc/dump.mjs +1 -1
- package/dist/rpc/index.d.mts +2 -2
- package/dist/rpc/index.mjs +4 -1
- package/dist/rpc/transports/ws-client.mjs +21 -8
- package/dist/{server-Dl6TU5LE.mjs → server-CAsvfJNK.mjs} +51 -15
- package/dist/{server-rX781kz-.d.mts → server-Cfr1NrGQ.d.mts} +52 -10
- package/dist/{shared-state-D4PPieA0.mjs → shared-state-QtMas0NG.mjs} +34 -4
- package/dist/state-WX3HT5a3.mjs +101 -0
- package/dist/{storage-l2ezeend.mjs → storage-yrUZaiib.mjs} +55 -6
- package/dist/types/index.d.mts +2 -2
- package/dist/utils/crypto-token.mjs +1 -1
- package/dist/utils/events.d.mts +1 -1
- package/dist/utils/hash.mjs +1 -1
- package/dist/utils/launch-editor.mjs +1 -1
- package/dist/utils/open.mjs +1 -1
- package/dist/utils/serve-static.mjs +2 -1
- package/dist/utils/shared-state.d.mts +1 -1
- package/dist/utils/shared-state.mjs +1 -1
- package/dist/utils/streaming-channel.d.mts +1 -1
- package/dist/utils/when.d.mts +1 -1
- package/package.json +10 -9
- package/dist/{open-Dusa2Zzd.mjs → open-Deb5xmIT.mjs} +1 -1
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { a as diagnostics } from "./storage-yrUZaiib.mjs";
|
|
2
|
+
import { n as createHostContext } from "./host-h3-Dsf-Tgwx.mjs";
|
|
3
|
+
import { join } from "pathe";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
|
+
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
import { toJsonSchema } from "@valibot/to-json-schema";
|
|
9
|
+
//#region src/adapters/mcp/stringify.ts
|
|
10
|
+
/**
|
|
11
|
+
* JSON-coercing serializer for MCP text payloads.
|
|
12
|
+
*
|
|
13
|
+
* MCP carries tool results and resource reads as plain text over a
|
|
14
|
+
* JSON-RPC transport, so we cannot use the `s:`-prefixed structured-clone
|
|
15
|
+
* format the WS RPC transport falls back to for non-JSON values. Instead,
|
|
16
|
+
* we coerce common non-JSON types into JSON-friendly forms so the LLM
|
|
17
|
+
* client sees something useful instead of `[object Object]`.
|
|
18
|
+
*
|
|
19
|
+
* Coercions:
|
|
20
|
+
* - `BigInt` → `"123n"`
|
|
21
|
+
* - `Date` → ISO string (via the native `toJSON`)
|
|
22
|
+
* - `Map` → `{ __type: 'Map', entries: [[k, v], …] }`
|
|
23
|
+
* - `Set` → `{ __type: 'Set', entries: [v, …] }`
|
|
24
|
+
* - `Error` → `{ name, message, stack, cause? }` (cause recurses)
|
|
25
|
+
* - `Function` → `"[Function: name]"`
|
|
26
|
+
* - `Symbol` → `value.toString()`
|
|
27
|
+
* - cycles → `"[Circular]"`
|
|
28
|
+
*/
|
|
29
|
+
function stringifyForMcp(value) {
|
|
30
|
+
if (value === void 0) return "undefined";
|
|
31
|
+
if (typeof value === "string") return value;
|
|
32
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
33
|
+
return JSON.stringify(value, (_key, val) => {
|
|
34
|
+
if (typeof val === "bigint") return `${val}n`;
|
|
35
|
+
if (val instanceof Error) {
|
|
36
|
+
const out = {
|
|
37
|
+
name: val.name,
|
|
38
|
+
message: val.message,
|
|
39
|
+
stack: val.stack
|
|
40
|
+
};
|
|
41
|
+
if (val.cause !== void 0) out.cause = val.cause;
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
if (val instanceof Map) return {
|
|
45
|
+
__type: "Map",
|
|
46
|
+
entries: [...val.entries()]
|
|
47
|
+
};
|
|
48
|
+
if (val instanceof Set) return {
|
|
49
|
+
__type: "Set",
|
|
50
|
+
entries: [...val]
|
|
51
|
+
};
|
|
52
|
+
if (typeof val === "function") return `[Function: ${val.name || "anonymous"}]`;
|
|
53
|
+
if (typeof val === "symbol") return val.toString();
|
|
54
|
+
if (val !== null && typeof val === "object") {
|
|
55
|
+
if (seen.has(val)) return "[Circular]";
|
|
56
|
+
seen.add(val);
|
|
57
|
+
}
|
|
58
|
+
return val;
|
|
59
|
+
}, 2);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Format a thrown value for an MCP `isError` text payload. Surfaces the
|
|
63
|
+
* `Error.name`/`message`, and one level of `cause.message` so context
|
|
64
|
+
* isn't dropped silently.
|
|
65
|
+
*/
|
|
66
|
+
function formatMcpError(error) {
|
|
67
|
+
if (!(error instanceof Error)) return String(error);
|
|
68
|
+
const cause = error.cause;
|
|
69
|
+
const causeText = cause instanceof Error ? ` (cause: ${cause.message})` : cause !== void 0 ? ` (cause: ${String(cause)})` : "";
|
|
70
|
+
return `${error.name}: ${error.message}${causeText}`;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/adapters/mcp/to-json-schema.ts
|
|
74
|
+
const FALLBACK_OBJECT_SCHEMA = Object.freeze({
|
|
75
|
+
type: "object",
|
|
76
|
+
additionalProperties: true
|
|
77
|
+
});
|
|
78
|
+
/**
|
|
79
|
+
* Convert a valibot return schema to JSON Schema.
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
function valibotReturnToJsonSchema(schema) {
|
|
83
|
+
if (!schema) return void 0;
|
|
84
|
+
try {
|
|
85
|
+
return toJsonSchema(schema);
|
|
86
|
+
} catch {
|
|
87
|
+
return FALLBACK_OBJECT_SCHEMA;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Convert positional RPC args schemas to a single MCP-friendly object
|
|
92
|
+
* schema. When the RPC declares `args: [v.object(...)]`, unwrap the
|
|
93
|
+
* single-object schema directly (nicer agent UX than `{ arg0: {...} }`).
|
|
94
|
+
*
|
|
95
|
+
* Returns `undefined` when there are no args (the MCP SDK treats this
|
|
96
|
+
* as `{ type: 'object', properties: {} }`).
|
|
97
|
+
* @internal
|
|
98
|
+
*/
|
|
99
|
+
function valibotArgsToJsonSchema(args) {
|
|
100
|
+
if (!args || args.length === 0) return {
|
|
101
|
+
schema: {
|
|
102
|
+
type: "object",
|
|
103
|
+
properties: {}
|
|
104
|
+
},
|
|
105
|
+
unwrapped: false
|
|
106
|
+
};
|
|
107
|
+
if (args.length === 1) {
|
|
108
|
+
const inner = safeToJsonSchema(args[0]);
|
|
109
|
+
if (isObjectJsonSchema(inner)) return {
|
|
110
|
+
schema: inner,
|
|
111
|
+
unwrapped: true
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const properties = {};
|
|
115
|
+
const required = [];
|
|
116
|
+
for (let i = 0; i < args.length; i++) {
|
|
117
|
+
const key = `arg${i}`;
|
|
118
|
+
properties[key] = safeToJsonSchema(args[i]);
|
|
119
|
+
required.push(key);
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
schema: {
|
|
123
|
+
type: "object",
|
|
124
|
+
properties,
|
|
125
|
+
required,
|
|
126
|
+
additionalProperties: false
|
|
127
|
+
},
|
|
128
|
+
unwrapped: false
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function safeToJsonSchema(schema) {
|
|
132
|
+
try {
|
|
133
|
+
return toJsonSchema(schema);
|
|
134
|
+
} catch {
|
|
135
|
+
return FALLBACK_OBJECT_SCHEMA;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function isObjectJsonSchema(value) {
|
|
139
|
+
return !!value && typeof value === "object" && value.type === "object";
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/adapters/mcp/build-server.ts
|
|
143
|
+
/**
|
|
144
|
+
* Wire an MCP {@link Server} to a devframe context. Returns the server
|
|
145
|
+
* plus a disposal function for the subscriptions it sets up. The
|
|
146
|
+
* transport is the caller's responsibility — `createMcpServer` connects
|
|
147
|
+
* stdio; tests can connect an {@link InMemoryTransport} instead.
|
|
148
|
+
*
|
|
149
|
+
* @internal
|
|
150
|
+
*/
|
|
151
|
+
function buildMcpServerFromContext(ctx, options) {
|
|
152
|
+
const server = new Server({
|
|
153
|
+
name: options.serverName,
|
|
154
|
+
version: options.serverVersion
|
|
155
|
+
}, { capabilities: {
|
|
156
|
+
tools: { listChanged: true },
|
|
157
|
+
resources: { listChanged: true }
|
|
158
|
+
} });
|
|
159
|
+
registerToolHandlers(server, ctx);
|
|
160
|
+
registerResourceHandlers(server, ctx, options.exposeSharedState);
|
|
161
|
+
const notify = (method) => {
|
|
162
|
+
server.notification({ method }).catch(() => {});
|
|
163
|
+
};
|
|
164
|
+
const offManifest = ctx.agent.events.on("agent:manifest:changed", () => {
|
|
165
|
+
notify("notifications/tools/list_changed");
|
|
166
|
+
notify("notifications/resources/list_changed");
|
|
167
|
+
});
|
|
168
|
+
const offKeyAdded = ctx.rpc.sharedState.onKeyAdded(() => {
|
|
169
|
+
notify("notifications/resources/list_changed");
|
|
170
|
+
});
|
|
171
|
+
return {
|
|
172
|
+
server,
|
|
173
|
+
dispose: () => {
|
|
174
|
+
offManifest();
|
|
175
|
+
offKeyAdded();
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Build an MCP server over the agent surface of a devframe definition.
|
|
181
|
+
* Currently supports `stdio` transport only.
|
|
182
|
+
*
|
|
183
|
+
* @experimental The agent-native surface is experimental and may change
|
|
184
|
+
* without a major version bump until it stabilizes.
|
|
185
|
+
*/
|
|
186
|
+
async function createMcpServer(definition, options = {}) {
|
|
187
|
+
const transport = options.transport ?? "stdio";
|
|
188
|
+
if (transport !== "stdio") throw diagnostics.DF0017({
|
|
189
|
+
transport,
|
|
190
|
+
reason: "Only stdio transport is supported in this release."
|
|
191
|
+
});
|
|
192
|
+
const ctx = await createHostContext({
|
|
193
|
+
cwd: process.cwd(),
|
|
194
|
+
mode: "dev",
|
|
195
|
+
host: {
|
|
196
|
+
mountStatic: () => {},
|
|
197
|
+
resolveOrigin: () => "mcp://devframe",
|
|
198
|
+
getStorageDir: (scope) => scope === "workspace" ? join(process.cwd(), `node_modules/.${definition.id}/devframe`) : join(homedir(), `.${definition.id}/devframe`)
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
await definition.setup(ctx);
|
|
202
|
+
const { server, dispose } = buildMcpServerFromContext(ctx, {
|
|
203
|
+
serverName: options.serverName ?? `${definition.id} (devframe)`,
|
|
204
|
+
serverVersion: options.serverVersion ?? definition.version ?? "0.0.0",
|
|
205
|
+
exposeSharedState: options.exposeSharedState ?? true
|
|
206
|
+
});
|
|
207
|
+
const { startStdioTransport } = await import("./transports-BmDVn4SF.mjs");
|
|
208
|
+
let stop;
|
|
209
|
+
try {
|
|
210
|
+
stop = await startStdioTransport(server);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
213
|
+
throw diagnostics.DF0017({
|
|
214
|
+
transport,
|
|
215
|
+
reason,
|
|
216
|
+
cause: error
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
options.onReady?.({ transport: "stdio" });
|
|
220
|
+
return { async stop() {
|
|
221
|
+
dispose();
|
|
222
|
+
await stop();
|
|
223
|
+
} };
|
|
224
|
+
}
|
|
225
|
+
function registerToolHandlers(server, ctx) {
|
|
226
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
227
|
+
return { tools: ctx.agent.list().tools.map((tool) => projectTool(tool, ctx)) };
|
|
228
|
+
});
|
|
229
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
230
|
+
const { name, arguments: args } = request.params;
|
|
231
|
+
try {
|
|
232
|
+
return { content: [{
|
|
233
|
+
type: "text",
|
|
234
|
+
text: stringifyForMcp(await ctx.agent.invoke(name, args ?? {}))
|
|
235
|
+
}] };
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return {
|
|
238
|
+
isError: true,
|
|
239
|
+
content: [{
|
|
240
|
+
type: "text",
|
|
241
|
+
text: `Error invoking "${name}": ${formatMcpError(error)}`
|
|
242
|
+
}]
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function registerResourceHandlers(server, ctx, exposeSharedState) {
|
|
248
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
249
|
+
const resources = ctx.agent.list().resources.map((resource) => ({
|
|
250
|
+
uri: resource.uri,
|
|
251
|
+
name: resource.name,
|
|
252
|
+
description: resource.description,
|
|
253
|
+
mimeType: resource.mimeType
|
|
254
|
+
}));
|
|
255
|
+
if (exposeSharedState !== false) {
|
|
256
|
+
const filter = typeof exposeSharedState === "function" ? exposeSharedState : () => true;
|
|
257
|
+
for (const key of ctx.rpc.sharedState.keys()) {
|
|
258
|
+
if (!filter(key)) continue;
|
|
259
|
+
resources.push({
|
|
260
|
+
uri: `devframe://state/${encodeURIComponent(key)}`,
|
|
261
|
+
name: key,
|
|
262
|
+
description: `Shared state: ${key}`,
|
|
263
|
+
mimeType: "application/json"
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return { resources };
|
|
268
|
+
});
|
|
269
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
270
|
+
const { uri } = request.params;
|
|
271
|
+
const parsed = parseResourceUri(uri);
|
|
272
|
+
if (parsed.kind === "resource") {
|
|
273
|
+
const content = await ctx.agent.read(parsed.id);
|
|
274
|
+
return { contents: [{
|
|
275
|
+
uri,
|
|
276
|
+
mimeType: content.mimeType ?? "application/json",
|
|
277
|
+
text: content.text ?? stringifyForMcp(content.json)
|
|
278
|
+
}] };
|
|
279
|
+
}
|
|
280
|
+
if (parsed.kind === "state") return { contents: [{
|
|
281
|
+
uri,
|
|
282
|
+
mimeType: "application/json",
|
|
283
|
+
text: stringifyForMcp((await ctx.rpc.sharedState.get(parsed.key)).value())
|
|
284
|
+
}] };
|
|
285
|
+
throw new Error(`[devframe/mcp] unknown resource URI "${uri}"`);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
function projectTool(tool, ctx) {
|
|
289
|
+
const inputSchema = tool.inputSchema ?? computeInputSchema(tool, ctx);
|
|
290
|
+
const outputSchema = tool.outputSchema ?? computeOutputSchema(tool, ctx);
|
|
291
|
+
return {
|
|
292
|
+
name: tool.id,
|
|
293
|
+
title: tool.title,
|
|
294
|
+
description: tool.description,
|
|
295
|
+
inputSchema,
|
|
296
|
+
...outputSchema ? { outputSchema } : {},
|
|
297
|
+
annotations: {
|
|
298
|
+
title: tool.title,
|
|
299
|
+
readOnlyHint: tool.safety === "read",
|
|
300
|
+
destructiveHint: tool.safety === "destructive"
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function computeInputSchema(tool, ctx) {
|
|
305
|
+
if (tool.kind !== "rpc" || !tool.rpcName) return {
|
|
306
|
+
type: "object",
|
|
307
|
+
properties: {}
|
|
308
|
+
};
|
|
309
|
+
const def = ctx.rpc.definitions.get(tool.rpcName);
|
|
310
|
+
if (!def) return {
|
|
311
|
+
type: "object",
|
|
312
|
+
properties: {}
|
|
313
|
+
};
|
|
314
|
+
const args = def.args;
|
|
315
|
+
return valibotArgsToJsonSchema(args).schema;
|
|
316
|
+
}
|
|
317
|
+
function computeOutputSchema(tool, ctx) {
|
|
318
|
+
if (tool.kind !== "rpc" || !tool.rpcName) return void 0;
|
|
319
|
+
const def = ctx.rpc.definitions.get(tool.rpcName);
|
|
320
|
+
if (!def) return void 0;
|
|
321
|
+
return valibotReturnToJsonSchema(def.returns);
|
|
322
|
+
}
|
|
323
|
+
function parseResourceUri(uri) {
|
|
324
|
+
const match = uri.match(/^devframe:\/\/(resource|state)\/(.+)$/);
|
|
325
|
+
if (!match) return { kind: "unknown" };
|
|
326
|
+
const [, kind, rest] = match;
|
|
327
|
+
const decoded = decodeURIComponent(rest);
|
|
328
|
+
if (kind === "resource") return {
|
|
329
|
+
kind: "resource",
|
|
330
|
+
id: decoded
|
|
331
|
+
};
|
|
332
|
+
return {
|
|
333
|
+
kind: "state",
|
|
334
|
+
key: decoded
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
//#endregion
|
|
338
|
+
export { createMcpServer as n, buildMcpServerFromContext as t };
|
package/dist/client/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { O as StreamReader, R as SharedState, Y as DevframeSettings, at as DevframeRpcServerFunctions, b as RpcSharedStateHost, et as ScopedServerFunctions, f as ConnectionMeta, it as DevframeRpcClientFunctions, k as StreamSink, nt as SettingsForNamespace, tt as ScopedSharedStates, xt as EventEmitter, y as RpcSharedStateGetOptions } from "../devframe-BtVHhmwM.mjs";
|
|
2
2
|
import { _ as RpcFunctionDefinition, w as RpcFunctionsCollector } from "../types-DZEx4ffs.mjs";
|
|
3
|
-
import { C as RpcCacheManager, w as RpcCacheOptions } from "../index-
|
|
3
|
+
import { C as RpcCacheManager, w as RpcCacheOptions } from "../index-Dy6GFDRf.mjs";
|
|
4
4
|
import { t as WsRpcChannelOptions } from "../ws-client-D0z0pOhn.mjs";
|
|
5
5
|
import { BirpcOptions, BirpcReturn } from "birpc";
|
|
6
6
|
|
package/dist/client/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as hash } from "../hash-
|
|
1
|
+
import { t as hash } from "../hash-PAfnyu2k.mjs";
|
|
2
2
|
import { colors } from "../utils/colors.mjs";
|
|
3
3
|
import { createEventEmitter } from "../utils/events.mjs";
|
|
4
|
-
import { t as createSharedState } from "../shared-state-
|
|
4
|
+
import { t as createSharedState } from "../shared-state-QtMas0NG.mjs";
|
|
5
5
|
import { i as structuredCloneStringify, n as structuredCloneParse, t as structuredCloneDeserialize } from "../structured-clone-CeZOHvkd.mjs";
|
|
6
6
|
import { createStreamReader, createStreamSink } from "../utils/streaming-channel.mjs";
|
|
7
7
|
import { promiseWithResolver } from "../utils/promise.mjs";
|
|
@@ -21,6 +21,14 @@ const DEVFRAME_RPC_DUMP_MANIFEST_FILENAME = "__rpc-dump/index.json";
|
|
|
21
21
|
* `consumeOtpFromUrl` client utilities (or `connectDevframe`'s `otpParam`).
|
|
22
22
|
*/
|
|
23
23
|
const DEVFRAME_OTP_URL_PARAM = "devframe_otp";
|
|
24
|
+
/**
|
|
25
|
+
* WS upgrade-URL query parameter carrying a previously-issued bearer token.
|
|
26
|
+
* Set by `createWsRpcChannel` (browser transport) whenever `authToken` is
|
|
27
|
+
* passed; read at connect time by a host's connect-time trust hook (see
|
|
28
|
+
* `recipes/interactive-auth`'s `onConnect`) so a returning client can be
|
|
29
|
+
* trusted before its own `anonymous:devframe:auth` handshake call arrives.
|
|
30
|
+
*/
|
|
31
|
+
const DEVFRAME_AUTH_TOKEN_QUERY_PARAM = "devframe_auth_token";
|
|
24
32
|
//#endregion
|
|
25
33
|
//#region src/utils/diagnostics-reporter.ts
|
|
26
34
|
const formatAnsi = ansiFormatter(colors);
|
|
@@ -83,14 +91,24 @@ async function getRpcResolvedSetupResult(definition, context) {
|
|
|
83
91
|
if (!definition.setup) return {};
|
|
84
92
|
if (typeof context === "object" && context !== null) {
|
|
85
93
|
definition.__cache ??= /* @__PURE__ */ new WeakMap();
|
|
86
|
-
|
|
94
|
+
const cache = definition.__cache;
|
|
95
|
+
let promise = cache.get(context);
|
|
87
96
|
if (!promise) {
|
|
88
97
|
promise = Promise.resolve(definition.setup(context));
|
|
89
|
-
|
|
98
|
+
promise.catch(() => {
|
|
99
|
+
if (cache.get(context) === promise) cache.delete(context);
|
|
100
|
+
});
|
|
101
|
+
cache.set(context, promise);
|
|
90
102
|
}
|
|
91
103
|
return await promise;
|
|
92
104
|
}
|
|
93
|
-
|
|
105
|
+
if (!definition.__promise) {
|
|
106
|
+
const promise = Promise.resolve(definition.setup(context));
|
|
107
|
+
promise.catch(() => {
|
|
108
|
+
if (definition.__promise === promise) definition.__promise = void 0;
|
|
109
|
+
});
|
|
110
|
+
definition.__promise = promise;
|
|
111
|
+
}
|
|
94
112
|
return await definition.__promise;
|
|
95
113
|
}
|
|
96
114
|
async function getRpcHandler(definition, context) {
|
|
@@ -122,6 +140,9 @@ var RpcCacheManager = class {
|
|
|
122
140
|
const methodCache = this.cacheMap.get(m);
|
|
123
141
|
if (methodCache) return methodCache.get(this.keySerializer(a));
|
|
124
142
|
}
|
|
143
|
+
has(m, a) {
|
|
144
|
+
return this.cacheMap.get(m)?.has(this.keySerializer(a)) ?? false;
|
|
145
|
+
}
|
|
125
146
|
apply(req, res) {
|
|
126
147
|
const methodCache = this.cacheMap.get(req.m) || /* @__PURE__ */ new Map();
|
|
127
148
|
methodCache.set(this.keySerializer(req.a), res);
|
|
@@ -398,8 +419,12 @@ function createRpcSharedStateClientHost(rpc) {
|
|
|
398
419
|
return new Promise((resolve) => {
|
|
399
420
|
if (!rpc.isTrusted) {
|
|
400
421
|
resolve(state);
|
|
422
|
+
let initialized = false;
|
|
401
423
|
rpc.events.on("rpc:is-trusted:updated", (isTrusted) => {
|
|
402
|
-
if (isTrusted)
|
|
424
|
+
if (isTrusted && !initialized) {
|
|
425
|
+
initialized = true;
|
|
426
|
+
initSharedState();
|
|
427
|
+
}
|
|
403
428
|
});
|
|
404
429
|
} else initSharedState().then(resolve);
|
|
405
430
|
});
|
|
@@ -611,14 +636,15 @@ const EMPTY_DEFS = /* @__PURE__ */ new Map();
|
|
|
611
636
|
*/
|
|
612
637
|
function createWsRpcChannel(options) {
|
|
613
638
|
let url = options.url;
|
|
614
|
-
if (options.authToken) url = `${url}
|
|
639
|
+
if (options.authToken) url = `${url}?${DEVFRAME_AUTH_TOKEN_QUERY_PARAM}=${encodeURIComponent(options.authToken)}`;
|
|
615
640
|
const ws = new WebSocket(url);
|
|
616
641
|
const { onConnected = NOOP, onError = NOOP, onDisconnected = NOOP, definitions = EMPTY_DEFS } = options;
|
|
617
642
|
ws.addEventListener("open", (e) => {
|
|
618
643
|
onConnected(e);
|
|
619
644
|
});
|
|
620
645
|
ws.addEventListener("error", (e) => {
|
|
621
|
-
|
|
646
|
+
const _e = e instanceof Error ? e : new Error(e.type);
|
|
647
|
+
onError(_e);
|
|
622
648
|
});
|
|
623
649
|
ws.addEventListener("close", (e) => {
|
|
624
650
|
onDisconnected(e);
|
|
@@ -631,14 +657,25 @@ function createWsRpcChannel(options) {
|
|
|
631
657
|
});
|
|
632
658
|
},
|
|
633
659
|
post: (data) => {
|
|
634
|
-
if (ws.readyState === WebSocket.OPEN)
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
660
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
661
|
+
ws.send(data);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
if (ws.readyState === WebSocket.CONNECTING) {
|
|
665
|
+
const onOpen = () => {
|
|
666
|
+
cleanup();
|
|
667
|
+
if (ws.readyState === WebSocket.OPEN) ws.send(data);
|
|
668
|
+
};
|
|
669
|
+
const onClose = () => cleanup();
|
|
670
|
+
function cleanup() {
|
|
671
|
+
ws.removeEventListener("open", onOpen);
|
|
672
|
+
ws.removeEventListener("close", onClose);
|
|
639
673
|
}
|
|
640
|
-
ws.addEventListener("open",
|
|
674
|
+
ws.addEventListener("open", onOpen);
|
|
675
|
+
ws.addEventListener("close", onClose);
|
|
676
|
+
return;
|
|
641
677
|
}
|
|
678
|
+
onError(/* @__PURE__ */ new Error("Devframe WebSocket is not open; message dropped"));
|
|
642
679
|
},
|
|
643
680
|
serialize: (msg) => {
|
|
644
681
|
let method;
|
|
@@ -1899,7 +1936,7 @@ function createWsRpcClientMode(options) {
|
|
|
1899
1936
|
}
|
|
1900
1937
|
async function requestTrustWithToken(token) {
|
|
1901
1938
|
currentAuthToken = token;
|
|
1902
|
-
const result = await serverRpc.$call("devframe:
|
|
1939
|
+
const result = await serverRpc.$call("anonymous:devframe:auth", {
|
|
1903
1940
|
authToken: token,
|
|
1904
1941
|
ua: describeUA(),
|
|
1905
1942
|
origin: location.origin
|
|
@@ -1910,7 +1947,7 @@ function createWsRpcClientMode(options) {
|
|
|
1910
1947
|
return result.isTrusted;
|
|
1911
1948
|
}
|
|
1912
1949
|
async function requestTrustWithCode(code) {
|
|
1913
|
-
const token = (await serverRpc.$call("devframe:auth:exchange", {
|
|
1950
|
+
const token = (await serverRpc.$call("anonymous:devframe:auth:exchange", {
|
|
1914
1951
|
code,
|
|
1915
1952
|
ua: describeUA(),
|
|
1916
1953
|
origin: location.origin
|
|
@@ -2124,12 +2161,9 @@ async function getDevframeRpcClient(options = {}) {
|
|
|
2124
2161
|
async onRequest(req, next, resolve) {
|
|
2125
2162
|
await rpcOptions.onRequest?.call(this, req, next, resolve);
|
|
2126
2163
|
if (cacheOptions && cacheManager?.validate(req.m)) {
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
const res = await next(req);
|
|
2131
|
-
cacheManager?.apply(req, res);
|
|
2132
|
-
}
|
|
2164
|
+
if (cacheManager.has(req.m, req.a)) return resolve(cacheManager.cached(req.m, req.a));
|
|
2165
|
+
const res = await next(req);
|
|
2166
|
+
cacheManager.apply(req, res);
|
|
2133
2167
|
} else await next(req);
|
|
2134
2168
|
}
|
|
2135
2169
|
},
|
package/dist/constants.d.mts
CHANGED
|
@@ -11,6 +11,13 @@ declare const DEVFRAME_CONNECTION_META_FILENAME = "__connection.json";
|
|
|
11
11
|
* handler here without colliding with its own routes (HMR, asset serving).
|
|
12
12
|
*/
|
|
13
13
|
declare const DEVFRAME_WS_ROUTE = "__devframe_ws";
|
|
14
|
+
/**
|
|
15
|
+
* Route the Streamable-HTTP MCP endpoint is bound to, relative to a
|
|
16
|
+
* devframe's base path. Sits next to `__connection.json` and the WS route
|
|
17
|
+
* so an MCP client reaches it on the same origin the SPA loaded from — the
|
|
18
|
+
* dev server shares one port for HTTP, WS, and MCP. Opt-in via `cli.mcp`.
|
|
19
|
+
*/
|
|
20
|
+
declare const DEVFRAME_MCP_ROUTE = "__mcp";
|
|
14
21
|
declare const DEVFRAME_RPC_DUMP_MANIFEST_FILENAME = "__rpc-dump/index.json";
|
|
15
22
|
declare const DEVFRAME_DOCK_IMPORTS_FILENAME = "__client-imports.js";
|
|
16
23
|
declare const DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID = "/__devframe-client-imports.js";
|
|
@@ -29,5 +36,28 @@ declare const REMOTE_CONNECTION_KEY = "devframe-remote-connection";
|
|
|
29
36
|
* `consumeOtpFromUrl` client utilities (or `connectDevframe`'s `otpParam`).
|
|
30
37
|
*/
|
|
31
38
|
declare const DEVFRAME_OTP_URL_PARAM = "devframe_otp";
|
|
39
|
+
/**
|
|
40
|
+
* WS upgrade-URL query parameter carrying a previously-issued bearer token.
|
|
41
|
+
* Set by `createWsRpcChannel` (browser transport) whenever `authToken` is
|
|
42
|
+
* passed; read at connect time by a host's connect-time trust hook (see
|
|
43
|
+
* `recipes/interactive-auth`'s `onConnect`) so a returning client can be
|
|
44
|
+
* trusted before its own `anonymous:devframe:auth` handshake call arrives.
|
|
45
|
+
*/
|
|
46
|
+
declare const DEVFRAME_AUTH_TOKEN_QUERY_PARAM = "devframe_auth_token";
|
|
47
|
+
/**
|
|
48
|
+
* Prefix that marks an RPC method as callable before a connection is
|
|
49
|
+
* trusted. This is the *only* rule the pre-trust gate applies — there is no
|
|
50
|
+
* per-method allowlist. Any handshake method a host adapter needs to reach
|
|
51
|
+
* before authentication must be named `anonymous:<rest>` (e.g.
|
|
52
|
+
* `anonymous:devframe:auth`).
|
|
53
|
+
*/
|
|
54
|
+
declare const ANONYMOUS_RPC_PREFIX = "anonymous:";
|
|
55
|
+
/**
|
|
56
|
+
* Whether `name` is callable before a connection is trusted, i.e. it starts
|
|
57
|
+
* with {@link ANONYMOUS_RPC_PREFIX}. Used by the resolver gate in
|
|
58
|
+
* `startHttpAndWs` (via an `authorize` function) and by host adapters that
|
|
59
|
+
* implement their own transport.
|
|
60
|
+
*/
|
|
61
|
+
declare function isAnonymousRpcMethod(name: string): boolean;
|
|
32
62
|
//#endregion
|
|
33
|
-
export { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DIRNAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID, DEVFRAME_MOUNT_PATH, DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH, DEVFRAME_OTP_URL_PARAM, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY };
|
|
63
|
+
export { ANONYMOUS_RPC_PREFIX, DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DIRNAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID, DEVFRAME_MCP_ROUTE, DEVFRAME_MOUNT_PATH, DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH, DEVFRAME_OTP_URL_PARAM, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY, isAnonymousRpcMethod };
|
package/dist/constants.mjs
CHANGED
|
@@ -11,6 +11,13 @@ const DEVFRAME_CONNECTION_META_FILENAME = "__connection.json";
|
|
|
11
11
|
* handler here without colliding with its own routes (HMR, asset serving).
|
|
12
12
|
*/
|
|
13
13
|
const DEVFRAME_WS_ROUTE = "__devframe_ws";
|
|
14
|
+
/**
|
|
15
|
+
* Route the Streamable-HTTP MCP endpoint is bound to, relative to a
|
|
16
|
+
* devframe's base path. Sits next to `__connection.json` and the WS route
|
|
17
|
+
* so an MCP client reaches it on the same origin the SPA loaded from — the
|
|
18
|
+
* dev server shares one port for HTTP, WS, and MCP. Opt-in via `cli.mcp`.
|
|
19
|
+
*/
|
|
20
|
+
const DEVFRAME_MCP_ROUTE = "__mcp";
|
|
14
21
|
const DEVFRAME_RPC_DUMP_MANIFEST_FILENAME = "__rpc-dump/index.json";
|
|
15
22
|
const DEVFRAME_DOCK_IMPORTS_FILENAME = "__client-imports.js";
|
|
16
23
|
const DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID = "/__devframe-client-imports.js";
|
|
@@ -29,5 +36,30 @@ const REMOTE_CONNECTION_KEY = "devframe-remote-connection";
|
|
|
29
36
|
* `consumeOtpFromUrl` client utilities (or `connectDevframe`'s `otpParam`).
|
|
30
37
|
*/
|
|
31
38
|
const DEVFRAME_OTP_URL_PARAM = "devframe_otp";
|
|
39
|
+
/**
|
|
40
|
+
* WS upgrade-URL query parameter carrying a previously-issued bearer token.
|
|
41
|
+
* Set by `createWsRpcChannel` (browser transport) whenever `authToken` is
|
|
42
|
+
* passed; read at connect time by a host's connect-time trust hook (see
|
|
43
|
+
* `recipes/interactive-auth`'s `onConnect`) so a returning client can be
|
|
44
|
+
* trusted before its own `anonymous:devframe:auth` handshake call arrives.
|
|
45
|
+
*/
|
|
46
|
+
const DEVFRAME_AUTH_TOKEN_QUERY_PARAM = "devframe_auth_token";
|
|
47
|
+
/**
|
|
48
|
+
* Prefix that marks an RPC method as callable before a connection is
|
|
49
|
+
* trusted. This is the *only* rule the pre-trust gate applies — there is no
|
|
50
|
+
* per-method allowlist. Any handshake method a host adapter needs to reach
|
|
51
|
+
* before authentication must be named `anonymous:<rest>` (e.g.
|
|
52
|
+
* `anonymous:devframe:auth`).
|
|
53
|
+
*/
|
|
54
|
+
const ANONYMOUS_RPC_PREFIX = "anonymous:";
|
|
55
|
+
/**
|
|
56
|
+
* Whether `name` is callable before a connection is trusted, i.e. it starts
|
|
57
|
+
* with {@link ANONYMOUS_RPC_PREFIX}. Used by the resolver gate in
|
|
58
|
+
* `startHttpAndWs` (via an `authorize` function) and by host adapters that
|
|
59
|
+
* implement their own transport.
|
|
60
|
+
*/
|
|
61
|
+
function isAnonymousRpcMethod(name) {
|
|
62
|
+
return name.startsWith(ANONYMOUS_RPC_PREFIX);
|
|
63
|
+
}
|
|
32
64
|
//#endregion
|
|
33
|
-
export { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DIRNAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID, DEVFRAME_MOUNT_PATH, DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH, DEVFRAME_OTP_URL_PARAM, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY };
|
|
65
|
+
export { ANONYMOUS_RPC_PREFIX, DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DIRNAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID, DEVFRAME_MCP_ROUTE, DEVFRAME_MOUNT_PATH, DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH, DEVFRAME_OTP_URL_PARAM, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY, isAnonymousRpcMethod };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as createStorage } from "./storage-
|
|
2
|
-
import { i as randomToken, n as revokeAuthToken, t as revokeActiveConnectionsForToken } from "./revoke-
|
|
1
|
+
import { t as createStorage } from "./storage-yrUZaiib.mjs";
|
|
2
|
+
import { i as randomToken, n as revokeAuthToken, t as revokeActiveConnectionsForToken } from "./revoke-BtQDKTp7.mjs";
|
|
3
3
|
import { join } from "pathe";
|
|
4
4
|
//#region src/node/hub-internals/context.ts
|
|
5
5
|
const internalContextMap = /* @__PURE__ */ new WeakMap();
|