@kaoruisaac/pedelec 0.3.2 → 0.3.4
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 +86 -4
- package/dist/deno-module-internal.d.ts +19 -0
- package/dist/deno-module-internal.js +49 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +201 -11
- package/dist/index.type-test.js +36 -3
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/vite.d.ts +9 -0
- package/dist/vite.js +1255 -0
- package/dist/vite.type-test.d.ts +1 -0
- package/dist/vite.type-test.js +10 -0
- package/package.json +18 -2
package/README.md
CHANGED
|
@@ -90,6 +90,66 @@ npm install ../path/to/pedelec/sdk
|
|
|
90
90
|
|
|
91
91
|
---
|
|
92
92
|
|
|
93
|
+
## Deno Modules
|
|
94
|
+
|
|
95
|
+
Vite applications can package local or installed JavaScript/TypeScript libraries for Agent-authored `pedelec-deno` scripts.
|
|
96
|
+
|
|
97
|
+
### Setup
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
// vite.config.ts
|
|
101
|
+
import { defineConfig } from "vite";
|
|
102
|
+
import { pedelecVitePlugin } from "@kaoruisaac/pedelec/vite";
|
|
103
|
+
|
|
104
|
+
export default defineConfig({
|
|
105
|
+
plugins: [pedelecVitePlugin()],
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Authoring
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { Pedelec, defineDenoModule } from "@kaoruisaac/pedelec";
|
|
113
|
+
|
|
114
|
+
const pedelec = new Pedelec();
|
|
115
|
+
const session = await pedelec.createSession({
|
|
116
|
+
skills: {
|
|
117
|
+
guidance: "Use sprite-tools for sprite authoring tasks.",
|
|
118
|
+
tools: [],
|
|
119
|
+
denoModules: [
|
|
120
|
+
defineDenoModule({
|
|
121
|
+
name: "sprite-tools",
|
|
122
|
+
description: "Sprite authoring and preview utilities.",
|
|
123
|
+
entry: "./agent/sprite-tools.ts",
|
|
124
|
+
usage: `import { previewActorSource } from "sprite-tools";`,
|
|
125
|
+
preferStdinExecution: true,
|
|
126
|
+
}),
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
For an installed package, use its package specifier as the build-time entry:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
defineDenoModule({
|
|
136
|
+
name: "gsap",
|
|
137
|
+
description: "Animation utilities.",
|
|
138
|
+
entry: "gsap",
|
|
139
|
+
usage: `import { gsap } from "gsap";`,
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`entry` is resolved by Vite during build/dev and is never a Desktop filesystem instruction. The plugin bundles normal third-party runtime dependencies locally and generates the public `index.d.ts` declarations automatically. Node built-ins may be imported with either canonical `node:` specifiers or legacy bare specifiers; the prepared artifact preserves them as canonical `node:` imports for Deno's Node compatibility layer. This does not change the sandbox: runtime npm, JSR, network fetching, and filesystem access outside the permitted workspace remain unavailable.
|
|
144
|
+
|
|
145
|
+
The generated declaration describes the module's Agent-facing API, so implementation-only dependency types are omitted. Module-owned types are supported; packages that expose dependency-owned or Node-specific external types directly in their public API are not guaranteed to produce a standalone declaration in V1. `name` is the Agent import specifier, and `usage` is required common-case guidance. `createSession()` transfers the prepared artifact automatically; a session keeps an immutable module snapshot, and `session.end()`/`session.resume()` reuse it while Core still knows the thread.
|
|
146
|
+
|
|
147
|
+
Deno Modules are imported inside JavaScript/TypeScript run with `pedelec-deno`. They are different from `skills.tools`, whose browser/App RPC capabilities use `pedelec-cli` tool-spec and tool-call commands.
|
|
148
|
+
|
|
149
|
+
Set the optional `preferStdinExecution: true` when short scripts primarily use a module and should be steered toward stdin execution. Host Context then adds that module's concrete `runCommandTemplate` (`@'\n<typescript-source>\n'@ | pedelec-deno --thread-id <thread-id> run -`), so the Agent only needs to replace the TypeScript source placeholder instead of deciding how to serialize stdin. This is a preference, not a restriction: file-backed execution remains valid, and omitting the field or setting it to `false` preserves the default behavior. It does not change bundling, artifact contents, or runtime permissions.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
93
153
|
## Minimal Example
|
|
94
154
|
|
|
95
155
|
```ts
|
|
@@ -225,7 +285,17 @@ console.log(status.installed, status.approved, status.origin);
|
|
|
225
285
|
|
|
226
286
|
`getApprovalStatus()` includes `appConnected`, a non-sensitive Core `ping` result. It does not require approval or open the approval popup, but it may use the Native Host's existing Desktop auto-launch fallback. `appConnected` does not mean the origin is approved or a provider is ready.
|
|
227
287
|
|
|
228
|
-
`getSettings()` and `listProviders()` require origin approval and may open the approval popup. Settings expose only defaults (never provider credentials); providers expose only `name`, `code`, `available`, and `error`. Use `getApprovalStatus().appConnected` rather than `listProviders()` as a connection probe.
|
|
288
|
+
`getSettings()` and `listProviders()` require origin approval and may open the approval popup. Settings expose only defaults (never provider credentials); providers expose only `name`, `code`, `available`, `isDefault`, and `error`. `isDefault` identifies the provider selected by the current Desktop settings and is independent of `available`; it remains true when that provider is unavailable. Use `getApprovalStatus().appConnected` rather than `listProviders()` as a connection probe.
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
type ProviderInfo = {
|
|
292
|
+
name: string;
|
|
293
|
+
code: ProviderCode;
|
|
294
|
+
available: boolean;
|
|
295
|
+
isDefault: boolean;
|
|
296
|
+
error: string | null;
|
|
297
|
+
};
|
|
298
|
+
```
|
|
229
299
|
|
|
230
300
|
For a complete Extension, approval, and Desktop readiness probe, use `checkAvailability()`. It never creates/resumes a session or opens approval. Its status ping may attempt Desktop launch even for an unapproved origin.
|
|
231
301
|
|
|
@@ -236,7 +306,7 @@ if (availability.available) startUi();
|
|
|
236
306
|
|
|
237
307
|
An unavailable extension may be disconnected rather than absent. `desktop.launchAttempted` means the settings probe was sent, not that Desktop was confirmed to have launched. Invalid settings responses also count as Desktop unavailable in this probe.
|
|
238
308
|
|
|
239
|
-
### Selecting Provider and Effort
|
|
309
|
+
### Selecting Provider, Model, and Effort
|
|
240
310
|
|
|
241
311
|
```ts
|
|
242
312
|
const session = await pedelec.createSession({
|
|
@@ -248,7 +318,19 @@ const session = await pedelec.createSession({
|
|
|
248
318
|
const defaultSession = await pedelec.createSession({ effortLevel: "low" });
|
|
249
319
|
```
|
|
250
320
|
|
|
251
|
-
The SDK uses the provider-independent effort levels `default`, `low`, and `high`. An omitted level means `default`. Desktop users map each provider's profiles to concrete model and native effort arguments
|
|
321
|
+
The SDK uses the provider-independent effort levels `default`, `low`, and `high`. An omitted level means `default`. Desktop users map each provider's profiles to concrete model and native effort arguments. That profile remains the default source of both values, but an application may optionally override only the model for one session:
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
const specializedSession = await pedelec.createSession({
|
|
325
|
+
provider: "codex",
|
|
326
|
+
effortLevel: "high",
|
|
327
|
+
model: "gpt-x",
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
`model` is an optional provider-native identifier. Omitting it preserves the existing Desktop-profile behavior; providing it does not disable `effortLevel`, replaces only the selected profile's model, and leaves its native effort arguments intact. The SDK does not expose or maintain a model catalog, and it does not translate the identifier into provider CLI flags. Model validity and account entitlement remain provider-dependent, so an invalid or unavailable model may fail when the provider starts.
|
|
332
|
+
|
|
333
|
+
For an explicit model, the SDK requires an acknowledgment from Core that the override was applied before it exposes the session or starts Deno Module setup. If the connected Extension/Desktop is too old to provide that acknowledgment, the SDK aborts the newly created setup and reports a compatibility error instead of silently ignoring the model.
|
|
252
334
|
|
|
253
335
|
Currently supported provider codes in the SDK:
|
|
254
336
|
|
|
@@ -261,6 +343,6 @@ Currently supported provider codes in the SDK:
|
|
|
261
343
|
| Claude Code | `claude` |
|
|
262
344
|
| Ollama | `ollama` |
|
|
263
345
|
|
|
264
|
-
Ollama sessions are executed by the bundled `pedelec-agent`. The selected Ollama effort profile must contain a model
|
|
346
|
+
Ollama sessions are executed by the bundled `pedelec-agent`. The selected Ollama effort profile must contain a model unless `createSession({ model })` supplies the explicit model override; otherwise Core returns `MODEL_REQUIRED`. Low and high profiles are optional, but there is no fallback between profiles.
|
|
265
347
|
|
|
266
348
|
`getSettings()` returns only `{ defaultProvider }`. It never exposes provider settings, effort arguments, credentials, or model names.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe private interop shared by the root SDK entry and the Vite
|
|
3
|
+
* build-time plugin. This file is intentionally not re-exported from the
|
|
4
|
+
* package root: application code authors the small DenoModuleDefinition only.
|
|
5
|
+
*/
|
|
6
|
+
export declare const PREPARED_DENO_MODULE_ARTIFACT_PROPERTY = "__pedelecArtifact";
|
|
7
|
+
export type PreparedDenoModuleArtifact = {
|
|
8
|
+
readonly format: "esm";
|
|
9
|
+
readonly runtimeSource: string;
|
|
10
|
+
readonly typesSource: string;
|
|
11
|
+
readonly contentHash?: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function getPreparedDenoModuleArtifact(value: unknown): PreparedDenoModuleArtifact | null;
|
|
14
|
+
/**
|
|
15
|
+
* Keep this deliberately narrower than arbitrary URL/path syntax. Core has
|
|
16
|
+
* the corresponding contract in Rust; both sides accept ordinary bare and
|
|
17
|
+
* scoped package-like specifiers only.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isValidDenoModuleName(value: unknown): value is string;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe private interop shared by the root SDK entry and the Vite
|
|
3
|
+
* build-time plugin. This file is intentionally not re-exported from the
|
|
4
|
+
* package root: application code authors the small DenoModuleDefinition only.
|
|
5
|
+
*/
|
|
6
|
+
export const PREPARED_DENO_MODULE_ARTIFACT_PROPERTY = "__pedelecArtifact";
|
|
7
|
+
export function getPreparedDenoModuleArtifact(value) {
|
|
8
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
9
|
+
return null;
|
|
10
|
+
const artifact = value[PREPARED_DENO_MODULE_ARTIFACT_PROPERTY];
|
|
11
|
+
if (!artifact || typeof artifact !== "object" || Array.isArray(artifact))
|
|
12
|
+
return null;
|
|
13
|
+
const candidate = artifact;
|
|
14
|
+
if (candidate.format !== "esm" ||
|
|
15
|
+
typeof candidate.runtimeSource !== "string" ||
|
|
16
|
+
candidate.runtimeSource.trim().length === 0 ||
|
|
17
|
+
typeof candidate.typesSource !== "string" ||
|
|
18
|
+
candidate.typesSource.trim().length === 0) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
format: "esm",
|
|
23
|
+
runtimeSource: candidate.runtimeSource,
|
|
24
|
+
typesSource: candidate.typesSource,
|
|
25
|
+
...(typeof candidate.contentHash === "string" ? { contentHash: candidate.contentHash } : {}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const DENO_MODULE_SEGMENT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
29
|
+
/**
|
|
30
|
+
* Keep this deliberately narrower than arbitrary URL/path syntax. Core has
|
|
31
|
+
* the corresponding contract in Rust; both sides accept ordinary bare and
|
|
32
|
+
* scoped package-like specifiers only.
|
|
33
|
+
*/
|
|
34
|
+
export function isValidDenoModuleName(value) {
|
|
35
|
+
if (typeof value !== "string" || value.length === 0 || value.trim() !== value)
|
|
36
|
+
return false;
|
|
37
|
+
if (value === "." || value === ".." || value.includes("\\") || value.includes("\0"))
|
|
38
|
+
return false;
|
|
39
|
+
if (value.includes(":") || value.startsWith("/") || value.endsWith("/"))
|
|
40
|
+
return false;
|
|
41
|
+
const parts = value.split("/");
|
|
42
|
+
if (parts.length === 1)
|
|
43
|
+
return DENO_MODULE_SEGMENT_PATTERN.test(parts[0]);
|
|
44
|
+
if (parts.length !== 2 || !parts[0].startsWith("@") || parts[0].length === 1)
|
|
45
|
+
return false;
|
|
46
|
+
const scope = parts[0].slice(1);
|
|
47
|
+
const packageName = parts[1];
|
|
48
|
+
return DENO_MODULE_SEGMENT_PATTERN.test(scope) && DENO_MODULE_SEGMENT_PATTERN.test(packageName);
|
|
49
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,13 @@ export type Asset = {
|
|
|
15
15
|
sizeBytes: number;
|
|
16
16
|
modifiedAt: number;
|
|
17
17
|
};
|
|
18
|
+
export type DenoModuleDefinition<TName extends string = string> = {
|
|
19
|
+
name: TName;
|
|
20
|
+
description: string;
|
|
21
|
+
entry: string;
|
|
22
|
+
usage: string;
|
|
23
|
+
preferStdinExecution?: boolean;
|
|
24
|
+
};
|
|
18
25
|
export type ToolArgsSchemaMeta<TDefault extends JsonValue = JsonValue> = {
|
|
19
26
|
description?: string;
|
|
20
27
|
default?: TDefault;
|
|
@@ -71,6 +78,7 @@ export type ToolDefinition<TArgs = unknown, TResult = unknown, TName extends str
|
|
|
71
78
|
export type SkillsInput<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
|
|
72
79
|
guidance: string;
|
|
73
80
|
tools: TTools;
|
|
81
|
+
denoModules?: readonly DenoModuleDefinition[];
|
|
74
82
|
};
|
|
75
83
|
export type ToolNameOf<TTools extends readonly ToolDefinition[]> = Extract<TTools[number]["name"], string>;
|
|
76
84
|
export type SerializableToolManifest = {
|
|
@@ -79,9 +87,16 @@ export type SerializableToolManifest = {
|
|
|
79
87
|
argsSchema: ToolArgsSchema;
|
|
80
88
|
timeoutMs?: number;
|
|
81
89
|
};
|
|
90
|
+
type SerializableDenoModuleManifest = {
|
|
91
|
+
name: string;
|
|
92
|
+
description: string;
|
|
93
|
+
usage: string;
|
|
94
|
+
preferStdinExecution?: boolean;
|
|
95
|
+
};
|
|
82
96
|
export type SerializableSkillsManifest = {
|
|
83
97
|
guidance: string;
|
|
84
98
|
tools: SerializableToolManifest[];
|
|
99
|
+
denoModules?: SerializableDenoModuleManifest[];
|
|
85
100
|
};
|
|
86
101
|
export type CreateSessionWorkspaceInput = {
|
|
87
102
|
path: string;
|
|
@@ -93,6 +108,7 @@ export interface WorkspaceFolderPickerResult {
|
|
|
93
108
|
}
|
|
94
109
|
type CreateSessionInputWithProvider<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
|
|
95
110
|
provider: ProviderCode;
|
|
111
|
+
model?: string;
|
|
96
112
|
effortLevel?: EffortLevel;
|
|
97
113
|
skills?: SkillsInput<TTools>;
|
|
98
114
|
workspace?: CreateSessionWorkspaceInput;
|
|
@@ -100,6 +116,7 @@ type CreateSessionInputWithProvider<TTools extends readonly ToolDefinition[] = r
|
|
|
100
116
|
};
|
|
101
117
|
type CreateSessionInputWithDefaults<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
|
|
102
118
|
provider?: undefined;
|
|
119
|
+
model?: string;
|
|
103
120
|
effortLevel?: EffortLevel;
|
|
104
121
|
skills?: SkillsInput<TTools>;
|
|
105
122
|
workspace?: CreateSessionWorkspaceInput;
|
|
@@ -110,6 +127,7 @@ export type ProviderInfo = {
|
|
|
110
127
|
name: string;
|
|
111
128
|
code: ProviderCode;
|
|
112
129
|
available: boolean;
|
|
130
|
+
isDefault: boolean;
|
|
113
131
|
error: string | null;
|
|
114
132
|
};
|
|
115
133
|
export type PedelecSettings = {
|
|
@@ -286,6 +304,7 @@ type ErrorHandler = (error: PedelecError, ctx: ErrorEventContext) => void;
|
|
|
286
304
|
type StatusHandler = (status: PedelecSessionStatus, ctx: StatusEventContext) => void;
|
|
287
305
|
type EndedHandler = (ctx: EndedEventContext) => void;
|
|
288
306
|
export declare function defineTool<TArgs = unknown, TResult = unknown, const TName extends string = string>(tool: ToolDefinition<TArgs, TResult, TName>): ToolDefinition<TArgs, TResult, TName>;
|
|
307
|
+
export declare function defineDenoModule<const TName extends string>(module: DenoModuleDefinition<TName>): DenoModuleDefinition<TName>;
|
|
289
308
|
export declare class Pedelec {
|
|
290
309
|
private readonly pageWindow;
|
|
291
310
|
private readonly channelId;
|
|
@@ -300,6 +319,9 @@ export declare class Pedelec {
|
|
|
300
319
|
constructor(options?: PedelecOptions);
|
|
301
320
|
createSession(): Promise<PedelecSession<string>>;
|
|
302
321
|
createSession<const TTools extends readonly ToolDefinition[]>(input: CreateSessionInput<TTools>): Promise<PedelecSession<ToolNameOf<TTools>>>;
|
|
322
|
+
private uploadDenoModuleArtifact;
|
|
323
|
+
private abortSessionSetup;
|
|
324
|
+
private completeSessionSetup;
|
|
303
325
|
workspaceFolderPicker(): Promise<WorkspaceFolderPickerResult | null>;
|
|
304
326
|
listProviders(): Promise<ProviderInfo[]>;
|
|
305
327
|
getSettings(): Promise<PedelecSettings>;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PEDELEC_EXTENSION_ID } from "./extension-id.js";
|
|
2
|
+
import { getPreparedDenoModuleArtifact, isValidDenoModuleName, } from "./deno-module-internal.js";
|
|
2
3
|
import { SDK_VERSION } from "./version.generated.js";
|
|
3
4
|
const SDK_EXTERNAL_PORT_NAME = "pedelec-sdk-external";
|
|
4
5
|
const PAGE_ACTIVITY_MESSAGE_TYPE = "page_activity";
|
|
@@ -35,10 +36,13 @@ const TOOL_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_.-]*$/;
|
|
|
35
36
|
export function defineTool(tool) {
|
|
36
37
|
return tool;
|
|
37
38
|
}
|
|
39
|
+
export function defineDenoModule(module) {
|
|
40
|
+
return module;
|
|
41
|
+
}
|
|
38
42
|
function normalizeSkillsInput(value) {
|
|
39
43
|
const handlers = new Map();
|
|
40
44
|
if (value === undefined)
|
|
41
|
-
return { handlers };
|
|
45
|
+
return { handlers, denoModules: [] };
|
|
42
46
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
43
47
|
throw makeError("INVALID_INPUT", "skills must be an object");
|
|
44
48
|
}
|
|
@@ -49,7 +53,11 @@ function normalizeSkillsInput(value) {
|
|
|
49
53
|
if (!Array.isArray(skills.tools)) {
|
|
50
54
|
throw makeError("INVALID_INPUT", "skills.tools must be an array");
|
|
51
55
|
}
|
|
56
|
+
if (skills.denoModules !== undefined && !Array.isArray(skills.denoModules)) {
|
|
57
|
+
throw makeError("INVALID_INPUT", "skills.denoModules must be an array");
|
|
58
|
+
}
|
|
52
59
|
const seen = new Set();
|
|
60
|
+
const seenDenoModuleNames = new Set();
|
|
53
61
|
const tools = skills.tools.map((tool, index) => {
|
|
54
62
|
if (!tool || typeof tool !== "object" || Array.isArray(tool)) {
|
|
55
63
|
throw makeError("INVALID_INPUT", "skills.tools entries must be objects", { index });
|
|
@@ -91,12 +99,77 @@ function normalizeSkillsInput(value) {
|
|
|
91
99
|
...(rawTool.timeoutMs === undefined ? {} : { timeoutMs: rawTool.timeoutMs }),
|
|
92
100
|
};
|
|
93
101
|
});
|
|
102
|
+
const denoModules = (skills.denoModules ?? []).map((module, index) => {
|
|
103
|
+
if (!module || typeof module !== "object" || Array.isArray(module)) {
|
|
104
|
+
throw makeError("INVALID_INPUT", "skills.denoModules entries must be objects", { index });
|
|
105
|
+
}
|
|
106
|
+
const rawModule = module;
|
|
107
|
+
if (!isValidDenoModuleName(rawModule.name)) {
|
|
108
|
+
throw makeError("INVALID_INPUT", "Deno Module name is invalid", {
|
|
109
|
+
index,
|
|
110
|
+
moduleName: rawModule.name,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (seenDenoModuleNames.has(rawModule.name)) {
|
|
114
|
+
throw makeError("INVALID_INPUT", "duplicate Deno Module name", {
|
|
115
|
+
moduleName: rawModule.name,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
seenDenoModuleNames.add(rawModule.name);
|
|
119
|
+
if (typeof rawModule.description !== "string" || rawModule.description.trim().length === 0) {
|
|
120
|
+
throw makeError("INVALID_INPUT", "Deno Module description must be a non-empty string", {
|
|
121
|
+
moduleName: rawModule.name,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (typeof rawModule.usage !== "string" || rawModule.usage.trim().length === 0) {
|
|
125
|
+
throw makeError("INVALID_INPUT", "Deno Module usage must be a non-empty string", {
|
|
126
|
+
moduleName: rawModule.name,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (rawModule.entry !== undefined &&
|
|
130
|
+
(typeof rawModule.entry !== "string" || rawModule.entry.trim().length === 0)) {
|
|
131
|
+
throw makeError("INVALID_INPUT", "Deno Module entry must be a non-empty string", {
|
|
132
|
+
moduleName: rawModule.name,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (rawModule.preferStdinExecution !== undefined && typeof rawModule.preferStdinExecution !== "boolean") {
|
|
136
|
+
throw makeError("INVALID_INPUT", "Deno Module preferStdinExecution must be a boolean", {
|
|
137
|
+
moduleName: rawModule.name,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const artifact = getPreparedDenoModuleArtifact(module);
|
|
141
|
+
if (!artifact) {
|
|
142
|
+
throw makeError("INVALID_INPUT", `Deno module "${rawModule.name}" was not prepared. Configure pedelecVitePlugin() in the Vite project.`, { moduleName: rawModule.name });
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
name: rawModule.name,
|
|
146
|
+
description: rawModule.description,
|
|
147
|
+
usage: rawModule.usage,
|
|
148
|
+
...(rawModule.preferStdinExecution === true ? { preferStdinExecution: true } : {}),
|
|
149
|
+
artifact,
|
|
150
|
+
};
|
|
151
|
+
});
|
|
94
152
|
return {
|
|
95
153
|
manifest: {
|
|
96
154
|
guidance: skills.guidance,
|
|
97
155
|
tools,
|
|
156
|
+
...(denoModules.length > 0
|
|
157
|
+
? {
|
|
158
|
+
denoModules: denoModules.map(({ name, description, usage, preferStdinExecution }) => ({
|
|
159
|
+
name,
|
|
160
|
+
description,
|
|
161
|
+
usage,
|
|
162
|
+
...(preferStdinExecution === true ? { preferStdinExecution: true } : {}),
|
|
163
|
+
})),
|
|
164
|
+
}
|
|
165
|
+
: {}),
|
|
98
166
|
},
|
|
99
167
|
handlers,
|
|
168
|
+
denoModules: denoModules.map(({ name, artifact }) => ({
|
|
169
|
+
name,
|
|
170
|
+
runtimeSource: artifact.runtimeSource,
|
|
171
|
+
typesSource: artifact.typesSource,
|
|
172
|
+
})),
|
|
100
173
|
};
|
|
101
174
|
}
|
|
102
175
|
function normalizeToolArgsSchema(argsSchema, toolName) {
|
|
@@ -144,6 +217,7 @@ export class Pedelec {
|
|
|
144
217
|
const result = await this.request("create_session", {
|
|
145
218
|
input: {
|
|
146
219
|
provider: resolvedInput.provider,
|
|
220
|
+
model: resolvedInput.model,
|
|
147
221
|
effortLevel: resolvedInput.effortLevel,
|
|
148
222
|
skills: resolvedInput.skills,
|
|
149
223
|
workspace: resolvedInput.workspace,
|
|
@@ -153,14 +227,107 @@ export class Pedelec {
|
|
|
153
227
|
if (!result.sessionId) {
|
|
154
228
|
throw makeError("SDK_PROTOCOL_ERROR", "create_session response did not include sessionId");
|
|
155
229
|
}
|
|
230
|
+
if (resolvedInput.model !== undefined && result.modelOverrideApplied !== true) {
|
|
231
|
+
const error = makeError("SDK_PROTOCOL_ERROR", "The connected Pedelec Extension/Desktop does not support createSession model override. Update Pedelec components and try again.", { feature: "modelOverride" });
|
|
232
|
+
await this.abortSessionSetup(result.sessionId);
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
for (const module of resolvedInput.denoModules) {
|
|
237
|
+
await this.uploadDenoModuleArtifact(result.sessionId, module);
|
|
238
|
+
}
|
|
239
|
+
if (resolvedInput.denoModules.length > 0) {
|
|
240
|
+
await this.completeSessionSetup(result.sessionId);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
// The thread exists at this point but is not exposed through the SDK
|
|
245
|
+
// session registry yet. Keep the setup failure as the caller-visible
|
|
246
|
+
// error even if the best-effort cleanup transport also fails.
|
|
247
|
+
await this.abortSessionSetup(result.sessionId);
|
|
248
|
+
throw normalizeError(error, "DENO_MODULE_UPLOAD_FAILED", "Deno Module setup failed");
|
|
249
|
+
}
|
|
156
250
|
return this.registerSession(result.sessionId, resolvedInput.provider, resolvedInput.effortLevel, resolvedInput.inlineToolHandlers, resolvedInput.autoEndOnDisconnect);
|
|
157
251
|
}
|
|
252
|
+
async uploadDenoModuleArtifact(sessionId, module) {
|
|
253
|
+
const envelope = JSON.stringify({
|
|
254
|
+
version: 1,
|
|
255
|
+
format: "esm",
|
|
256
|
+
runtimeSource: module.runtimeSource,
|
|
257
|
+
typesSource: module.typesSource,
|
|
258
|
+
});
|
|
259
|
+
const body = new Blob([envelope], { type: "application/json" });
|
|
260
|
+
const expectedSizeBytes = body.size;
|
|
261
|
+
if (expectedSizeBytes <= 0 || expectedSizeBytes > MAX_ASSET_UPLOAD_BYTES) {
|
|
262
|
+
throw makeError("DENO_MODULE_ARTIFACT_TOO_LARGE", `Deno Module "${module.name}" artifact exceeds the 100 MiB limit`, { moduleName: module.name, expectedSizeBytes, maxSizeBytes: MAX_ASSET_UPLOAD_BYTES });
|
|
263
|
+
}
|
|
264
|
+
const ticket = await this.request("create_deno_module_upload", {
|
|
265
|
+
sessionId,
|
|
266
|
+
moduleName: module.name,
|
|
267
|
+
expectedSizeBytes,
|
|
268
|
+
});
|
|
269
|
+
if (!isPlainObject(ticket) || typeof ticket.uploadUrl !== "string" || ticket.uploadUrl.length === 0 ||
|
|
270
|
+
typeof ticket.token !== "string" || ticket.token.length === 0) {
|
|
271
|
+
throw makeError("SDK_PROTOCOL_ERROR", "Deno Module upload ticket was invalid", {
|
|
272
|
+
moduleName: module.name,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
let response;
|
|
276
|
+
try {
|
|
277
|
+
response = await fetch(ticket.uploadUrl, {
|
|
278
|
+
method: "PUT",
|
|
279
|
+
headers: {
|
|
280
|
+
Authorization: `Bearer ${ticket.token}`,
|
|
281
|
+
"Content-Type": "application/json",
|
|
282
|
+
},
|
|
283
|
+
body,
|
|
284
|
+
credentials: "omit",
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
throw normalizeError(error, "DENO_MODULE_UPLOAD_FAILED", `Deno Module "${module.name}" upload failed`);
|
|
289
|
+
}
|
|
290
|
+
let payload = null;
|
|
291
|
+
try {
|
|
292
|
+
payload = await response.json();
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// The status below remains authoritative for a successful response;
|
|
296
|
+
// failed responses without JSON receive the private fallback error.
|
|
297
|
+
}
|
|
298
|
+
if (!response.ok) {
|
|
299
|
+
const errorValue = isPlainObject(payload) ? payload.error : undefined;
|
|
300
|
+
throw normalizeError(errorValue, "DENO_MODULE_UPLOAD_FAILED", `Deno Module "${module.name}" upload failed`);
|
|
301
|
+
}
|
|
302
|
+
if (!isPlainObject(payload) || payload.ready !== true) {
|
|
303
|
+
throw makeError("DENO_MODULE_SETUP_FAILED", `Deno Module "${module.name}" was not materialized`, { moduleName: module.name });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async abortSessionSetup(sessionId) {
|
|
307
|
+
try {
|
|
308
|
+
await this.request("abort_session_setup", { sessionId });
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
// Rollback is deliberately best effort. The original setup/upload
|
|
312
|
+
// error is the only error exposed by createSession().
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async completeSessionSetup(sessionId) {
|
|
316
|
+
await this.request("complete_session_setup", { sessionId });
|
|
317
|
+
}
|
|
158
318
|
async workspaceFolderPicker() {
|
|
159
319
|
const result = await this.requestWithOptions("pick_workspace_folder", {}, { timeoutMs: null });
|
|
160
320
|
return normalizeWorkspaceFolderPickerResponse(result);
|
|
161
321
|
}
|
|
162
322
|
async listProviders() {
|
|
163
|
-
|
|
323
|
+
const providers = normalizeProviderInfoList(await this.request("list_providers"));
|
|
324
|
+
if (hasProviderDefaultFlags(providers))
|
|
325
|
+
return providers;
|
|
326
|
+
const settings = await this.getSettings();
|
|
327
|
+
return providers.map((provider) => ({
|
|
328
|
+
...provider,
|
|
329
|
+
isDefault: provider.code === settings.defaultProvider,
|
|
330
|
+
}));
|
|
164
331
|
}
|
|
165
332
|
async getSettings() {
|
|
166
333
|
return normalizePedelecSettings(await this.request("get_settings"));
|
|
@@ -338,9 +505,7 @@ export class Pedelec {
|
|
|
338
505
|
const raw = (input ?? {});
|
|
339
506
|
const provider = typeof raw.provider === "string" ? raw.provider.trim() : "";
|
|
340
507
|
const hasProvider = provider.length > 0;
|
|
341
|
-
|
|
342
|
-
throw makeError("INVALID_INPUT", "model is no longer supported; configure provider effort profiles in Desktop Settings");
|
|
343
|
-
}
|
|
508
|
+
const model = normalizeCreateSessionModelInput(raw.model);
|
|
344
509
|
const effortLevel = raw.effortLevel === undefined ? "default" : raw.effortLevel;
|
|
345
510
|
if (!isEffortLevel(effortLevel)) {
|
|
346
511
|
throw makeError("INVALID_INPUT", "effortLevel must be one of default, low, or high");
|
|
@@ -349,14 +514,16 @@ export class Pedelec {
|
|
|
349
514
|
const normalizedSkills = normalizeSkillsInput(raw.skills);
|
|
350
515
|
const workspace = normalizeCreateSessionWorkspaceInput(raw.workspace);
|
|
351
516
|
if (!hasProvider) {
|
|
352
|
-
return this.resolveDefaultCreateSessionInput(effortLevel, normalizedSkills, workspace, autoEndOnDisconnect);
|
|
517
|
+
return this.resolveDefaultCreateSessionInput(model, effortLevel, normalizedSkills, workspace, autoEndOnDisconnect);
|
|
353
518
|
}
|
|
354
519
|
return {
|
|
355
520
|
provider: provider,
|
|
521
|
+
model,
|
|
356
522
|
effortLevel,
|
|
357
523
|
skills: normalizedSkills.manifest,
|
|
358
524
|
workspace,
|
|
359
525
|
inlineToolHandlers: normalizedSkills.handlers,
|
|
526
|
+
denoModules: normalizedSkills.denoModules,
|
|
360
527
|
autoEndOnDisconnect,
|
|
361
528
|
};
|
|
362
529
|
}
|
|
@@ -412,7 +579,7 @@ export class Pedelec {
|
|
|
412
579
|
};
|
|
413
580
|
}
|
|
414
581
|
}
|
|
415
|
-
async resolveDefaultCreateSessionInput(effortLevel, normalizedSkills, workspace, autoEndOnDisconnect) {
|
|
582
|
+
async resolveDefaultCreateSessionInput(model, effortLevel, normalizedSkills, workspace, autoEndOnDisconnect) {
|
|
416
583
|
const settings = await this.getSettings();
|
|
417
584
|
if (!settings.defaultProvider) {
|
|
418
585
|
throw makeError("DEFAULT_PROVIDER_NOT_SET", "Default provider is not set. Open the Pedelec desktop app Settings page and choose a default provider.");
|
|
@@ -420,10 +587,12 @@ export class Pedelec {
|
|
|
420
587
|
await this.assertDefaultProviderAvailable(settings.defaultProvider);
|
|
421
588
|
return {
|
|
422
589
|
provider: settings.defaultProvider,
|
|
590
|
+
model,
|
|
423
591
|
effortLevel,
|
|
424
592
|
skills: normalizedSkills.manifest,
|
|
425
593
|
workspace,
|
|
426
594
|
inlineToolHandlers: normalizedSkills.handlers,
|
|
595
|
+
denoModules: normalizedSkills.denoModules,
|
|
427
596
|
autoEndOnDisconnect,
|
|
428
597
|
};
|
|
429
598
|
}
|
|
@@ -586,6 +755,14 @@ function normalizeCreateSessionWorkspaceInput(value) {
|
|
|
586
755
|
}
|
|
587
756
|
return { path };
|
|
588
757
|
}
|
|
758
|
+
function normalizeCreateSessionModelInput(value) {
|
|
759
|
+
if (value === undefined)
|
|
760
|
+
return undefined;
|
|
761
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
762
|
+
throw makeError("INVALID_INPUT", "model must be a non-empty string when provided");
|
|
763
|
+
}
|
|
764
|
+
return value.trim();
|
|
765
|
+
}
|
|
589
766
|
export class PedelecSession {
|
|
590
767
|
client;
|
|
591
768
|
sessionId;
|
|
@@ -1396,8 +1573,7 @@ function isPlainObject(value) {
|
|
|
1396
1573
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1397
1574
|
}
|
|
1398
1575
|
function normalizePedelecSettings(value) {
|
|
1399
|
-
if (!isPlainObject(value) || (value.defaultProvider !== null && !isProviderCode(value.defaultProvider))
|
|
1400
|
-
"defaultModels" in value || "providerSettings" in value) {
|
|
1576
|
+
if (!isPlainObject(value) || (value.defaultProvider !== null && !isProviderCode(value.defaultProvider))) {
|
|
1401
1577
|
throw makeError("SDK_PROTOCOL_ERROR", "get_settings response had invalid shape");
|
|
1402
1578
|
}
|
|
1403
1579
|
return { defaultProvider: value.defaultProvider };
|
|
@@ -1425,12 +1601,22 @@ function normalizeProviderInfoList(value) {
|
|
|
1425
1601
|
return value.map((item) => {
|
|
1426
1602
|
if (!isPlainObject(item) || typeof item.name !== "string" || item.name.length === 0 ||
|
|
1427
1603
|
!isProviderCode(item.code) || typeof item.available !== "boolean" ||
|
|
1428
|
-
(item.error !== null && typeof item.error !== "string")
|
|
1604
|
+
(item.error !== null && typeof item.error !== "string") ||
|
|
1605
|
+
("isDefault" in item && typeof item.isDefault !== "boolean")) {
|
|
1429
1606
|
throw makeError("SDK_PROTOCOL_ERROR", "list_providers response had invalid provider data");
|
|
1430
1607
|
}
|
|
1431
|
-
return {
|
|
1608
|
+
return {
|
|
1609
|
+
name: item.name,
|
|
1610
|
+
code: item.code,
|
|
1611
|
+
available: item.available,
|
|
1612
|
+
...(typeof item.isDefault === "boolean" ? { isDefault: item.isDefault } : {}),
|
|
1613
|
+
error: item.error,
|
|
1614
|
+
};
|
|
1432
1615
|
});
|
|
1433
1616
|
}
|
|
1617
|
+
function hasProviderDefaultFlags(value) {
|
|
1618
|
+
return value.every((provider) => typeof provider.isDefault === "boolean");
|
|
1619
|
+
}
|
|
1434
1620
|
function makeError(code, message, details) {
|
|
1435
1621
|
return details === undefined ? { code, message } : { code, message, details };
|
|
1436
1622
|
}
|
|
@@ -1488,6 +1674,10 @@ function isDeterministicResumeError(error) {
|
|
|
1488
1674
|
error.code === "THREAD_NOT_FOUND" ||
|
|
1489
1675
|
error.code === "WORKSPACE_OPEN_FAILED" ||
|
|
1490
1676
|
error.code === "PROVIDER_COMMAND_FAILED" ||
|
|
1677
|
+
error.code === "DENO_MODULE_SETUP_INCOMPLETE" ||
|
|
1678
|
+
error.code === "DENO_MODULE_SETUP_FAILED" ||
|
|
1679
|
+
error.code === "DENO_MODULE_MATERIALIZATION_FAILED" ||
|
|
1680
|
+
error.code === "DENO_IMPORT_MAP_INVALID" ||
|
|
1491
1681
|
error.code === "INVALID_INPUT";
|
|
1492
1682
|
}
|
|
1493
1683
|
function normalizeError(err, fallbackCode, fallbackMessage) {
|
package/dist/index.type-test.js
CHANGED
|
@@ -1,4 +1,34 @@
|
|
|
1
|
-
import { Pedelec, defineTool, } from "./index";
|
|
1
|
+
import { Pedelec, defineTool, defineDenoModule, } from "./index";
|
|
2
|
+
const spriteTools = defineDenoModule({
|
|
3
|
+
name: "sprite-tools",
|
|
4
|
+
description: "Sprite authoring utilities.",
|
|
5
|
+
entry: "./agent/sprite-tools.ts",
|
|
6
|
+
usage: `import { previewActorSource } from "sprite-tools";`,
|
|
7
|
+
preferStdinExecution: true,
|
|
8
|
+
});
|
|
9
|
+
const spriteName = spriteTools.name;
|
|
10
|
+
const spriteDefinition = spriteTools;
|
|
11
|
+
void spriteName;
|
|
12
|
+
void spriteDefinition;
|
|
13
|
+
// @ts-expect-error Deno Module usage is required by the public authoring API
|
|
14
|
+
defineDenoModule({
|
|
15
|
+
name: "missing-usage",
|
|
16
|
+
description: "Missing usage.",
|
|
17
|
+
entry: "./agent/missing-usage.ts",
|
|
18
|
+
});
|
|
19
|
+
// @ts-expect-error Deno Module entry remains required at authoring time
|
|
20
|
+
defineDenoModule({
|
|
21
|
+
name: "missing-entry",
|
|
22
|
+
description: "Missing entry.",
|
|
23
|
+
usage: 'import "missing-entry";',
|
|
24
|
+
});
|
|
25
|
+
defineDenoModule({
|
|
26
|
+
name: "non-string-entry",
|
|
27
|
+
description: "Invalid entry.",
|
|
28
|
+
// @ts-expect-error Deno Module entry is a build-time string authoring field
|
|
29
|
+
entry: 42,
|
|
30
|
+
usage: 'import "non-string-entry";',
|
|
31
|
+
});
|
|
2
32
|
function workspaceInputHasPublicType() {
|
|
3
33
|
return { path: "C:\\workspace\\project" };
|
|
4
34
|
}
|
|
@@ -20,6 +50,7 @@ async function typedOnToolNameFromCreateSession() {
|
|
|
20
50
|
argsSchema: { type: "object", properties: {}, required: [] },
|
|
21
51
|
}),
|
|
22
52
|
],
|
|
53
|
+
denoModules: [spriteTools],
|
|
23
54
|
},
|
|
24
55
|
});
|
|
25
56
|
session.onTool((name, args, ctx) => {
|
|
@@ -115,8 +146,8 @@ async function effortLevelPublicTypeContract() {
|
|
|
115
146
|
pedelec.createSession({ provider: "codex", sandbox: { path: "C:\\workspace\\legacy" } });
|
|
116
147
|
// @ts-expect-error the removed sandbox picker is not part of the public contract
|
|
117
148
|
pedelec.sandboxFolderPicker();
|
|
118
|
-
|
|
119
|
-
pedelec.createSession({ provider: "codex", model: "gpt-5" });
|
|
149
|
+
await pedelec.createSession({ model: "gpt-5" });
|
|
150
|
+
await pedelec.createSession({ provider: "codex", model: "gpt-5", effortLevel: "high" });
|
|
120
151
|
// @ts-expect-error session no longer exposes provider model
|
|
121
152
|
session.model;
|
|
122
153
|
// @ts-expect-error unsupported effort level
|
|
@@ -169,6 +200,8 @@ function publicSecurityTypesAreRestricted() {
|
|
|
169
200
|
appConnected: true,
|
|
170
201
|
};
|
|
171
202
|
const provider = {};
|
|
203
|
+
const isDefault = provider.isDefault;
|
|
204
|
+
void isDefault;
|
|
172
205
|
// @ts-expect-error SDK provider metadata must not expose executable paths
|
|
173
206
|
provider.path;
|
|
174
207
|
return status;
|