@kaoruisaac/pedelec 0.4.7 → 0.4.9
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/README.md +42 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +108 -17
- package/dist/index.type-test.js +45 -0
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ A web application can use Pedelec to:
|
|
|
27
27
|
- open an application-managed workspace with the native directory picker or an explicit path;
|
|
28
28
|
- send user instructions and receive streamed assistant text;
|
|
29
29
|
- expose narrowly scoped browser-side tools to the agent;
|
|
30
|
+
- register those same tools with WebMCP when the browser provides `document.modelContext`;
|
|
30
31
|
- resume or end sessions; and
|
|
31
32
|
- upload and list completed workspace assets; and
|
|
32
33
|
- show connection, approval, provider, and lifecycle state in the UI.
|
|
@@ -204,6 +205,47 @@ await session.sendText("Please help me analyze the current page state");
|
|
|
204
205
|
|
|
205
206
|
`sendText()` resolves after Core reports the matching semantic operation as completed. An `idle` status or bridge request response alone does not complete it. If the session is already handling a previous prompt, the new `sendText()` call is rejected to prevent multiple concurrent requests from running in the same session.
|
|
206
207
|
|
|
208
|
+
## Share App Tools with WebMCP
|
|
209
|
+
|
|
210
|
+
The same `defineTool()` definitions can be registered for a browser WebMCP agent. `webmcp()` does not start a Pedelec Session and does not require the Extension or Desktop. Pedelec binds the tools when `document.modelContext.registerTool` is available; otherwise `available` is `false`.
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import { Pedelec, defineTool } from "@kaoruisaac/pedelec";
|
|
214
|
+
|
|
215
|
+
const pedelec = new Pedelec();
|
|
216
|
+
|
|
217
|
+
const getCurrentPage = defineTool({
|
|
218
|
+
name: "get_current_page",
|
|
219
|
+
description: "Read the current browser page title and URL.",
|
|
220
|
+
argsSchema: {
|
|
221
|
+
type: "object",
|
|
222
|
+
properties: {},
|
|
223
|
+
required: [],
|
|
224
|
+
},
|
|
225
|
+
handler: async () => ({
|
|
226
|
+
title: document.title,
|
|
227
|
+
url: location.href,
|
|
228
|
+
}),
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const tools = [getCurrentPage] as const;
|
|
232
|
+
|
|
233
|
+
const webMcpBinding = await pedelec.webmcp({
|
|
234
|
+
skills: { tools },
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const session = await pedelec.createSession({
|
|
238
|
+
skills: {
|
|
239
|
+
guidance: "Use browser tools when useful.",
|
|
240
|
+
tools,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
webMcpBinding.dispose();
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
A handler shared by Session and WebMCP should depend only on `args` and ordinary application or browser state. WebMCP execution does not receive a Pedelec `ToolCallContext`.
|
|
248
|
+
|
|
207
249
|
## End and resume a session
|
|
208
250
|
|
|
209
251
|
```ts
|
package/dist/index.d.ts
CHANGED
|
@@ -101,6 +101,16 @@ export type SkillsInput<TTools extends readonly ToolDefinition[] = readonly Tool
|
|
|
101
101
|
tools: TTools;
|
|
102
102
|
denoModules?: readonly AgentDenoModuleDefinition[];
|
|
103
103
|
};
|
|
104
|
+
export type WebMcpSkillsInput<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
|
|
105
|
+
tools: TTools;
|
|
106
|
+
};
|
|
107
|
+
export type WebMcpInput<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
|
|
108
|
+
skills: WebMcpSkillsInput<TTools>;
|
|
109
|
+
};
|
|
110
|
+
export type WebMcpBinding = {
|
|
111
|
+
available: boolean;
|
|
112
|
+
dispose(): void;
|
|
113
|
+
};
|
|
104
114
|
export type ToolNameOf<TTools extends readonly ToolDefinition[]> = Extract<TTools[number]["name"], string>;
|
|
105
115
|
export type SerializableToolManifest = {
|
|
106
116
|
name: string;
|
|
@@ -388,6 +398,7 @@ export declare class Pedelec {
|
|
|
388
398
|
constructor(options?: PedelecOptions);
|
|
389
399
|
createSession(): Promise<PedelecSession<string>>;
|
|
390
400
|
createSession<const TTools extends readonly ToolDefinition[]>(input: CreateSessionInput<TTools>): Promise<PedelecSession<ToolNameOf<TTools>>>;
|
|
401
|
+
webmcp<const TTools extends readonly ToolDefinition[]>(input: WebMcpInput<TTools>): Promise<WebMcpBinding>;
|
|
391
402
|
/** @internal */
|
|
392
403
|
createSessionInternal<const TTools extends readonly ToolDefinition[]>(input?: CreateSessionInput<TTools>, workspace?: PedelecWorkspace): Promise<PedelecSession<ToolNameOf<TTools>>>;
|
|
393
404
|
private uploadDenoModuleArtifact;
|
package/dist/index.js
CHANGED
|
@@ -145,6 +145,7 @@ function downloadBrowserFile(client, request) {
|
|
|
145
145
|
}
|
|
146
146
|
const MAX_ASSET_UPLOAD_BYTES = 100 * 1024 * 1024;
|
|
147
147
|
const TOOL_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_.-]*$/;
|
|
148
|
+
const WEBMCP_TOOL_NAME_MAX_LENGTH = 128;
|
|
148
149
|
export function defineTool(tool) {
|
|
149
150
|
return tool;
|
|
150
151
|
}
|
|
@@ -191,26 +192,13 @@ function normalizeWorkspaceDenoModules(value) {
|
|
|
191
192
|
};
|
|
192
193
|
});
|
|
193
194
|
}
|
|
194
|
-
function
|
|
195
|
-
|
|
196
|
-
if (value === undefined)
|
|
197
|
-
return { handlers, denoModules: [] };
|
|
198
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
199
|
-
throw makeError("INVALID_INPUT", "skills must be an object");
|
|
200
|
-
}
|
|
201
|
-
const skills = value;
|
|
202
|
-
if (typeof skills.guidance !== "string") {
|
|
203
|
-
throw makeError("INVALID_INPUT", "skills.guidance must be a string");
|
|
204
|
-
}
|
|
205
|
-
if (!Array.isArray(skills.tools)) {
|
|
195
|
+
function normalizeToolDefinitions(value, options = {}) {
|
|
196
|
+
if (!Array.isArray(value)) {
|
|
206
197
|
throw makeError("INVALID_INPUT", "skills.tools must be an array");
|
|
207
198
|
}
|
|
208
|
-
|
|
209
|
-
throw makeError("INVALID_INPUT", "skills.denoModules must be an array");
|
|
210
|
-
}
|
|
199
|
+
const handlers = new Map();
|
|
211
200
|
const seen = new Set();
|
|
212
|
-
const
|
|
213
|
-
const tools = skills.tools.map((tool, index) => {
|
|
201
|
+
const tools = value.map((tool, index) => {
|
|
214
202
|
if (!tool || typeof tool !== "object" || Array.isArray(tool)) {
|
|
215
203
|
throw makeError("INVALID_INPUT", "skills.tools entries must be objects", { index });
|
|
216
204
|
}
|
|
@@ -218,6 +206,12 @@ function normalizeSkillsInput(value) {
|
|
|
218
206
|
if (typeof rawTool.name !== "string" || !TOOL_NAME_PATTERN.test(rawTool.name)) {
|
|
219
207
|
throw makeError("INVALID_INPUT", "tool name is invalid", { index, toolName: rawTool.name });
|
|
220
208
|
}
|
|
209
|
+
if (options.enforceWebMcpNameLimit && rawTool.name.length > WEBMCP_TOOL_NAME_MAX_LENGTH) {
|
|
210
|
+
throw makeError("INVALID_INPUT", "WebMCP tool name must be at most 128 characters", {
|
|
211
|
+
index,
|
|
212
|
+
toolName: rawTool.name,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
221
215
|
if (seen.has(rawTool.name)) {
|
|
222
216
|
throw makeError("INVALID_INPUT", "duplicate tool name", { toolName: rawTool.name });
|
|
223
217
|
}
|
|
@@ -236,6 +230,11 @@ function normalizeSkillsInput(value) {
|
|
|
236
230
|
if (rawTool.handler !== undefined && typeof rawTool.handler !== "function") {
|
|
237
231
|
throw makeError("INVALID_INPUT", "tool handler must be a function", { toolName: rawTool.name });
|
|
238
232
|
}
|
|
233
|
+
if (options.requireHandler && typeof rawTool.handler !== "function") {
|
|
234
|
+
throw makeError("INVALID_INPUT", "WebMCP tool handler must be a function", {
|
|
235
|
+
toolName: rawTool.name,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
239
238
|
if (rawTool.input !== undefined) {
|
|
240
239
|
throw makeError("INVALID_INPUT", "tool input is no longer supported; use argsSchema", {
|
|
241
240
|
toolName: rawTool.name,
|
|
@@ -251,6 +250,26 @@ function normalizeSkillsInput(value) {
|
|
|
251
250
|
...(rawTool.timeoutMs === undefined ? {} : { timeoutMs: rawTool.timeoutMs }),
|
|
252
251
|
};
|
|
253
252
|
});
|
|
253
|
+
return { tools, handlers };
|
|
254
|
+
}
|
|
255
|
+
function normalizeSkillsInput(value) {
|
|
256
|
+
if (value === undefined)
|
|
257
|
+
return { handlers: new Map(), denoModules: [] };
|
|
258
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
259
|
+
throw makeError("INVALID_INPUT", "skills must be an object");
|
|
260
|
+
}
|
|
261
|
+
const skills = value;
|
|
262
|
+
if (typeof skills.guidance !== "string") {
|
|
263
|
+
throw makeError("INVALID_INPUT", "skills.guidance must be a string");
|
|
264
|
+
}
|
|
265
|
+
if (!Array.isArray(skills.tools)) {
|
|
266
|
+
throw makeError("INVALID_INPUT", "skills.tools must be an array");
|
|
267
|
+
}
|
|
268
|
+
if (skills.denoModules !== undefined && !Array.isArray(skills.denoModules)) {
|
|
269
|
+
throw makeError("INVALID_INPUT", "skills.denoModules must be an array");
|
|
270
|
+
}
|
|
271
|
+
const { tools, handlers } = normalizeToolDefinitions(skills.tools);
|
|
272
|
+
const seenDenoModuleNames = new Set();
|
|
254
273
|
const denoModules = (skills.denoModules ?? []).map((module, index) => {
|
|
255
274
|
if (!module || typeof module !== "object" || Array.isArray(module)) {
|
|
256
275
|
throw makeError("INVALID_INPUT", "skills.denoModules entries must be objects", { index });
|
|
@@ -342,6 +361,43 @@ function normalizeToolArgsSchema(argsSchema, toolName) {
|
|
|
342
361
|
});
|
|
343
362
|
}
|
|
344
363
|
}
|
|
364
|
+
function normalizeWebMcpInput(value) {
|
|
365
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
366
|
+
throw makeError("INVALID_INPUT", "input must be an object");
|
|
367
|
+
}
|
|
368
|
+
const input = value;
|
|
369
|
+
if (!input.skills || typeof input.skills !== "object" || Array.isArray(input.skills)) {
|
|
370
|
+
throw makeError("INVALID_INPUT", "skills must be an object");
|
|
371
|
+
}
|
|
372
|
+
return normalizeToolDefinitions(input.skills.tools, {
|
|
373
|
+
requireHandler: true,
|
|
374
|
+
enforceWebMcpNameLimit: true,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
function webMcpModelContext(pageWindow) {
|
|
378
|
+
const document = pageWindow?.document;
|
|
379
|
+
const modelContext = document?.modelContext;
|
|
380
|
+
if (!modelContext || typeof modelContext.registerTool !== "function")
|
|
381
|
+
return null;
|
|
382
|
+
return modelContext;
|
|
383
|
+
}
|
|
384
|
+
function unavailableWebMcpBinding() {
|
|
385
|
+
return {
|
|
386
|
+
available: false,
|
|
387
|
+
dispose() { },
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function invokeWebMcpHandler(handler, args) {
|
|
391
|
+
// WebMCP has no Pedelec session. Call the shared handler with args only.
|
|
392
|
+
return handler(args);
|
|
393
|
+
}
|
|
394
|
+
function webMcpRegistrationError(error, toolName) {
|
|
395
|
+
const normalized = normalizeError(error, "WEBMCP_REGISTRATION_FAILED", "WebMCP tool registration failed");
|
|
396
|
+
if (normalized.code === "WEBMCP_REGISTRATION_FAILED" && normalized.details === undefined && toolName) {
|
|
397
|
+
return makeError(normalized.code, normalized.message, { toolName });
|
|
398
|
+
}
|
|
399
|
+
return normalized;
|
|
400
|
+
}
|
|
345
401
|
export class PedelecWorkspace {
|
|
346
402
|
client;
|
|
347
403
|
workspaceId;
|
|
@@ -498,6 +554,41 @@ export class Pedelec {
|
|
|
498
554
|
async createSession(input = {}) {
|
|
499
555
|
return this.createSessionInternal(input);
|
|
500
556
|
}
|
|
557
|
+
async webmcp(input) {
|
|
558
|
+
const normalized = normalizeWebMcpInput(input);
|
|
559
|
+
const modelContext = webMcpModelContext(this.pageWindow);
|
|
560
|
+
if (!modelContext)
|
|
561
|
+
return unavailableWebMcpBinding();
|
|
562
|
+
const controller = new AbortController();
|
|
563
|
+
let registeringToolName;
|
|
564
|
+
try {
|
|
565
|
+
for (const tool of normalized.tools) {
|
|
566
|
+
registeringToolName = tool.name;
|
|
567
|
+
const handler = normalized.handlers.get(tool.name);
|
|
568
|
+
if (!handler) {
|
|
569
|
+
throw makeError("INVALID_INPUT", "WebMCP tool handler must be a function", {
|
|
570
|
+
toolName: tool.name,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
await modelContext.registerTool({
|
|
574
|
+
name: tool.name,
|
|
575
|
+
description: tool.description,
|
|
576
|
+
inputSchema: tool.argsSchema,
|
|
577
|
+
execute: async (args) => invokeWebMcpHandler(handler, args),
|
|
578
|
+
}, { signal: controller.signal });
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
catch (error) {
|
|
582
|
+
controller.abort();
|
|
583
|
+
throw webMcpRegistrationError(error, registeringToolName);
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
available: true,
|
|
587
|
+
dispose() {
|
|
588
|
+
controller.abort();
|
|
589
|
+
},
|
|
590
|
+
};
|
|
591
|
+
}
|
|
501
592
|
/** @internal */
|
|
502
593
|
async createSessionInternal(input = {}, workspace) {
|
|
503
594
|
const resolvedOrPromise = this.resolveCreateSessionInput(input);
|
package/dist/index.type-test.js
CHANGED
|
@@ -265,6 +265,50 @@ async function workspaceFileTransferTypes() {
|
|
|
265
265
|
void parsed;
|
|
266
266
|
void downloaded;
|
|
267
267
|
}
|
|
268
|
+
async function sharedAppToolsBindToWebMcpAndSession() {
|
|
269
|
+
const pedelec = new Pedelec();
|
|
270
|
+
const sharedTool = defineTool({
|
|
271
|
+
name: "get_selection",
|
|
272
|
+
description: "Get selected text.",
|
|
273
|
+
argsSchema: {
|
|
274
|
+
type: "object",
|
|
275
|
+
properties: {},
|
|
276
|
+
required: [],
|
|
277
|
+
},
|
|
278
|
+
handler: async () => ({ text: "..." }),
|
|
279
|
+
});
|
|
280
|
+
const binding = await pedelec.webmcp({
|
|
281
|
+
skills: {
|
|
282
|
+
tools: [sharedTool],
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
binding.available;
|
|
286
|
+
binding.dispose();
|
|
287
|
+
const tools = [sharedTool];
|
|
288
|
+
await pedelec.webmcp({
|
|
289
|
+
skills: { tools },
|
|
290
|
+
});
|
|
291
|
+
await pedelec.createSession({
|
|
292
|
+
skills: {
|
|
293
|
+
guidance: "Use available browser tools.",
|
|
294
|
+
tools,
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
await pedelec.webmcp({
|
|
298
|
+
skills: {
|
|
299
|
+
tools,
|
|
300
|
+
// @ts-expect-error WebMCP skills do not include guidance
|
|
301
|
+
guidance: "not part of WebMCP",
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
await pedelec.webmcp({
|
|
305
|
+
skills: {
|
|
306
|
+
tools,
|
|
307
|
+
// @ts-expect-error WebMCP skills do not include denoModules
|
|
308
|
+
denoModules: [spriteTools],
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
}
|
|
268
312
|
function directoryPickerIsRemoved() {
|
|
269
313
|
const pedelec = new Pedelec();
|
|
270
314
|
// @ts-expect-error directoryPicker was removed in favor of openWorkspace
|
|
@@ -292,6 +336,7 @@ void listAssetsHasPublicTypes;
|
|
|
292
336
|
void availabilityHasPublicType;
|
|
293
337
|
void workspacePublicTypeContract;
|
|
294
338
|
void workspaceFileTransferTypes;
|
|
339
|
+
void sharedAppToolsBindToWebMcpAndSession;
|
|
295
340
|
void directoryPickerIsRemoved;
|
|
296
341
|
void publicSecurityTypesAreRestricted;
|
|
297
342
|
const baseContext = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "0.4.
|
|
1
|
+
export declare const SDK_VERSION = "0.4.9";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated from sdk/package.json. Do not edit manually.
|
|
2
|
-
export const SDK_VERSION = "0.4.
|
|
2
|
+
export const SDK_VERSION = "0.4.9";
|