@kaoruisaac/pedelec 0.3.3 → 0.3.5
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 +84 -3
- package/dist/deno-module-internal.d.ts +19 -0
- package/dist/deno-module-internal.js +49 -0
- package/dist/index.d.ts +34 -9
- package/dist/index.js +216 -5
- package/dist/index.type-test.js +44 -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
|
|
@@ -246,7 +306,7 @@ if (availability.available) startUi();
|
|
|
246
306
|
|
|
247
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.
|
|
248
308
|
|
|
249
|
-
### Selecting Provider and Effort
|
|
309
|
+
### Selecting Provider, Model, and Effort
|
|
250
310
|
|
|
251
311
|
```ts
|
|
252
312
|
const session = await pedelec.createSession({
|
|
@@ -258,7 +318,28 @@ const session = await pedelec.createSession({
|
|
|
258
318
|
const defaultSession = await pedelec.createSession({ effortLevel: "low" });
|
|
259
319
|
```
|
|
260
320
|
|
|
261
|
-
The SDK
|
|
321
|
+
The SDK supports two mutually exclusive session configuration modes. In Desktop profile mode, `effortLevel` selects the complete Desktop profile, including its configured model and native effort arguments. An omitted level means `default`:
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
const profileSession = await pedelec.createSession({
|
|
325
|
+
provider: "codex",
|
|
326
|
+
effortLevel: "high",
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
In explicit model mode, `model` selects the provider-native model directly and does not read or inherit any Desktop profile. The optional `effort` is provider-native and uses `low`, `medium`, `high`, `xhigh`, or `max`:
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
const modelSession = await pedelec.createSession({
|
|
334
|
+
provider: "codex",
|
|
335
|
+
model: "gpt-x",
|
|
336
|
+
effort: "max",
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Model-only requests are valid and use the provider's default effort behavior. `effort` requires `model`; `model` cannot be combined with `effortLevel`. The SDK does not expose or maintain a model catalog, and it does not translate the identifier into provider CLI flags. Model validity, effort support, and account entitlement remain provider-dependent, so an invalid or unavailable configuration may fail when the provider starts.
|
|
341
|
+
|
|
342
|
+
Explicit model requests require an `explicitModelConfigApplied` acknowledgment from Core before the SDK 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 using a different configuration.
|
|
262
343
|
|
|
263
344
|
Currently supported provider codes in the SDK:
|
|
264
345
|
|
|
@@ -271,6 +352,6 @@ Currently supported provider codes in the SDK:
|
|
|
271
352
|
| Claude Code | `claude` |
|
|
272
353
|
| Ollama | `ollama` |
|
|
273
354
|
|
|
274
|
-
Ollama sessions are executed by the bundled `pedelec-agent`.
|
|
355
|
+
Ollama sessions are executed by the bundled `pedelec-agent`. In profile mode, the selected Ollama effort profile must contain a model; otherwise Core returns `MODEL_REQUIRED`. In explicit model mode, `{ model }` supplies the model directly. Ollama currently does not accept an explicit `effort`. Low and high profiles are optional, but there is no fallback between profiles.
|
|
275
356
|
|
|
276
357
|
`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
|
@@ -3,6 +3,7 @@ export type PedelecOptions = {
|
|
|
3
3
|
};
|
|
4
4
|
export type ProviderCode = "codex" | "antigravity" | "opencode" | "cursor" | "claude" | "ollama";
|
|
5
5
|
export type EffortLevel = "default" | "low" | "high";
|
|
6
|
+
export type Effort = "low" | "medium" | "high" | "xhigh" | "max";
|
|
6
7
|
export type JsonPrimitive = string | number | boolean | null;
|
|
7
8
|
export type JsonValue = JsonPrimitive | JsonValue[] | {
|
|
8
9
|
[key: string]: JsonValue;
|
|
@@ -15,6 +16,13 @@ export type Asset = {
|
|
|
15
16
|
sizeBytes: number;
|
|
16
17
|
modifiedAt: number;
|
|
17
18
|
};
|
|
19
|
+
export type DenoModuleDefinition<TName extends string = string> = {
|
|
20
|
+
name: TName;
|
|
21
|
+
description: string;
|
|
22
|
+
entry: string;
|
|
23
|
+
usage: string;
|
|
24
|
+
preferStdinExecution?: boolean;
|
|
25
|
+
};
|
|
18
26
|
export type ToolArgsSchemaMeta<TDefault extends JsonValue = JsonValue> = {
|
|
19
27
|
description?: string;
|
|
20
28
|
default?: TDefault;
|
|
@@ -71,6 +79,7 @@ export type ToolDefinition<TArgs = unknown, TResult = unknown, TName extends str
|
|
|
71
79
|
export type SkillsInput<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
|
|
72
80
|
guidance: string;
|
|
73
81
|
tools: TTools;
|
|
82
|
+
denoModules?: readonly DenoModuleDefinition[];
|
|
74
83
|
};
|
|
75
84
|
export type ToolNameOf<TTools extends readonly ToolDefinition[]> = Extract<TTools[number]["name"], string>;
|
|
76
85
|
export type SerializableToolManifest = {
|
|
@@ -79,9 +88,16 @@ export type SerializableToolManifest = {
|
|
|
79
88
|
argsSchema: ToolArgsSchema;
|
|
80
89
|
timeoutMs?: number;
|
|
81
90
|
};
|
|
91
|
+
type SerializableDenoModuleManifest = {
|
|
92
|
+
name: string;
|
|
93
|
+
description: string;
|
|
94
|
+
usage: string;
|
|
95
|
+
preferStdinExecution?: boolean;
|
|
96
|
+
};
|
|
82
97
|
export type SerializableSkillsManifest = {
|
|
83
98
|
guidance: string;
|
|
84
99
|
tools: SerializableToolManifest[];
|
|
100
|
+
denoModules?: SerializableDenoModuleManifest[];
|
|
85
101
|
};
|
|
86
102
|
export type CreateSessionWorkspaceInput = {
|
|
87
103
|
path: string;
|
|
@@ -91,21 +107,26 @@ export interface WorkspaceFolderPickerResult {
|
|
|
91
107
|
isEmptyFolder: boolean;
|
|
92
108
|
hasWorkspaceConfig: boolean;
|
|
93
109
|
}
|
|
94
|
-
type
|
|
95
|
-
provider: ProviderCode;
|
|
96
|
-
effortLevel?: EffortLevel;
|
|
110
|
+
type CreateSessionSharedInput<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
|
|
97
111
|
skills?: SkillsInput<TTools>;
|
|
98
112
|
workspace?: CreateSessionWorkspaceInput;
|
|
99
113
|
autoEndOnDisconnect?: boolean;
|
|
100
114
|
};
|
|
101
|
-
type
|
|
102
|
-
|
|
115
|
+
type ProfileSessionConfiguration = {
|
|
116
|
+
model?: never;
|
|
117
|
+
effort?: never;
|
|
103
118
|
effortLevel?: EffortLevel;
|
|
104
|
-
skills?: SkillsInput<TTools>;
|
|
105
|
-
workspace?: CreateSessionWorkspaceInput;
|
|
106
|
-
autoEndOnDisconnect?: boolean;
|
|
107
119
|
};
|
|
108
|
-
|
|
120
|
+
type ExplicitModelSessionConfiguration = {
|
|
121
|
+
model: string;
|
|
122
|
+
effort?: Effort;
|
|
123
|
+
effortLevel?: never;
|
|
124
|
+
};
|
|
125
|
+
export type CreateSessionInput<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = (CreateSessionSharedInput<TTools> & {
|
|
126
|
+
provider?: ProviderCode;
|
|
127
|
+
} & ProfileSessionConfiguration) | (CreateSessionSharedInput<TTools> & {
|
|
128
|
+
provider?: ProviderCode;
|
|
129
|
+
} & ExplicitModelSessionConfiguration);
|
|
109
130
|
export type ProviderInfo = {
|
|
110
131
|
name: string;
|
|
111
132
|
code: ProviderCode;
|
|
@@ -287,6 +308,7 @@ type ErrorHandler = (error: PedelecError, ctx: ErrorEventContext) => void;
|
|
|
287
308
|
type StatusHandler = (status: PedelecSessionStatus, ctx: StatusEventContext) => void;
|
|
288
309
|
type EndedHandler = (ctx: EndedEventContext) => void;
|
|
289
310
|
export declare function defineTool<TArgs = unknown, TResult = unknown, const TName extends string = string>(tool: ToolDefinition<TArgs, TResult, TName>): ToolDefinition<TArgs, TResult, TName>;
|
|
311
|
+
export declare function defineDenoModule<const TName extends string>(module: DenoModuleDefinition<TName>): DenoModuleDefinition<TName>;
|
|
290
312
|
export declare class Pedelec {
|
|
291
313
|
private readonly pageWindow;
|
|
292
314
|
private readonly channelId;
|
|
@@ -301,6 +323,9 @@ export declare class Pedelec {
|
|
|
301
323
|
constructor(options?: PedelecOptions);
|
|
302
324
|
createSession(): Promise<PedelecSession<string>>;
|
|
303
325
|
createSession<const TTools extends readonly ToolDefinition[]>(input: CreateSessionInput<TTools>): Promise<PedelecSession<ToolNameOf<TTools>>>;
|
|
326
|
+
private uploadDenoModuleArtifact;
|
|
327
|
+
private abortSessionSetup;
|
|
328
|
+
private completeSessionSetup;
|
|
304
329
|
workspaceFolderPicker(): Promise<WorkspaceFolderPickerResult | null>;
|
|
305
330
|
listProviders(): Promise<ProviderInfo[]>;
|
|
306
331
|
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,8 @@ export class Pedelec {
|
|
|
144
217
|
const result = await this.request("create_session", {
|
|
145
218
|
input: {
|
|
146
219
|
provider: resolvedInput.provider,
|
|
220
|
+
model: resolvedInput.model,
|
|
221
|
+
effort: resolvedInput.effort,
|
|
147
222
|
effortLevel: resolvedInput.effortLevel,
|
|
148
223
|
skills: resolvedInput.skills,
|
|
149
224
|
workspace: resolvedInput.workspace,
|
|
@@ -153,8 +228,94 @@ export class Pedelec {
|
|
|
153
228
|
if (!result.sessionId) {
|
|
154
229
|
throw makeError("SDK_PROTOCOL_ERROR", "create_session response did not include sessionId");
|
|
155
230
|
}
|
|
231
|
+
if (resolvedInput.model !== undefined && result.explicitModelConfigApplied !== true) {
|
|
232
|
+
const error = makeError("SDK_PROTOCOL_ERROR", "The connected Pedelec Extension/Desktop does not support explicit model configuration. Update Pedelec components and try again.", { feature: "explicitModelConfig" });
|
|
233
|
+
await this.abortSessionSetup(result.sessionId);
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
for (const module of resolvedInput.denoModules) {
|
|
238
|
+
await this.uploadDenoModuleArtifact(result.sessionId, module);
|
|
239
|
+
}
|
|
240
|
+
if (resolvedInput.denoModules.length > 0) {
|
|
241
|
+
await this.completeSessionSetup(result.sessionId);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
// The thread exists at this point but is not exposed through the SDK
|
|
246
|
+
// session registry yet. Keep the setup failure as the caller-visible
|
|
247
|
+
// error even if the best-effort cleanup transport also fails.
|
|
248
|
+
await this.abortSessionSetup(result.sessionId);
|
|
249
|
+
throw normalizeError(error, "DENO_MODULE_UPLOAD_FAILED", "Deno Module setup failed");
|
|
250
|
+
}
|
|
156
251
|
return this.registerSession(result.sessionId, resolvedInput.provider, resolvedInput.effortLevel, resolvedInput.inlineToolHandlers, resolvedInput.autoEndOnDisconnect);
|
|
157
252
|
}
|
|
253
|
+
async uploadDenoModuleArtifact(sessionId, module) {
|
|
254
|
+
const envelope = JSON.stringify({
|
|
255
|
+
version: 1,
|
|
256
|
+
format: "esm",
|
|
257
|
+
runtimeSource: module.runtimeSource,
|
|
258
|
+
typesSource: module.typesSource,
|
|
259
|
+
});
|
|
260
|
+
const body = new Blob([envelope], { type: "application/json" });
|
|
261
|
+
const expectedSizeBytes = body.size;
|
|
262
|
+
if (expectedSizeBytes <= 0 || expectedSizeBytes > MAX_ASSET_UPLOAD_BYTES) {
|
|
263
|
+
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 });
|
|
264
|
+
}
|
|
265
|
+
const ticket = await this.request("create_deno_module_upload", {
|
|
266
|
+
sessionId,
|
|
267
|
+
moduleName: module.name,
|
|
268
|
+
expectedSizeBytes,
|
|
269
|
+
});
|
|
270
|
+
if (!isPlainObject(ticket) || typeof ticket.uploadUrl !== "string" || ticket.uploadUrl.length === 0 ||
|
|
271
|
+
typeof ticket.token !== "string" || ticket.token.length === 0) {
|
|
272
|
+
throw makeError("SDK_PROTOCOL_ERROR", "Deno Module upload ticket was invalid", {
|
|
273
|
+
moduleName: module.name,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
let response;
|
|
277
|
+
try {
|
|
278
|
+
response = await fetch(ticket.uploadUrl, {
|
|
279
|
+
method: "PUT",
|
|
280
|
+
headers: {
|
|
281
|
+
Authorization: `Bearer ${ticket.token}`,
|
|
282
|
+
"Content-Type": "application/json",
|
|
283
|
+
},
|
|
284
|
+
body,
|
|
285
|
+
credentials: "omit",
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
throw normalizeError(error, "DENO_MODULE_UPLOAD_FAILED", `Deno Module "${module.name}" upload failed`);
|
|
290
|
+
}
|
|
291
|
+
let payload = null;
|
|
292
|
+
try {
|
|
293
|
+
payload = await response.json();
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
// The status below remains authoritative for a successful response;
|
|
297
|
+
// failed responses without JSON receive the private fallback error.
|
|
298
|
+
}
|
|
299
|
+
if (!response.ok) {
|
|
300
|
+
const errorValue = isPlainObject(payload) ? payload.error : undefined;
|
|
301
|
+
throw normalizeError(errorValue, "DENO_MODULE_UPLOAD_FAILED", `Deno Module "${module.name}" upload failed`);
|
|
302
|
+
}
|
|
303
|
+
if (!isPlainObject(payload) || payload.ready !== true) {
|
|
304
|
+
throw makeError("DENO_MODULE_SETUP_FAILED", `Deno Module "${module.name}" was not materialized`, { moduleName: module.name });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
async abortSessionSetup(sessionId) {
|
|
308
|
+
try {
|
|
309
|
+
await this.request("abort_session_setup", { sessionId });
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
// Rollback is deliberately best effort. The original setup/upload
|
|
313
|
+
// error is the only error exposed by createSession().
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async completeSessionSetup(sessionId) {
|
|
317
|
+
await this.request("complete_session_setup", { sessionId });
|
|
318
|
+
}
|
|
158
319
|
async workspaceFolderPicker() {
|
|
159
320
|
const result = await this.requestWithOptions("pick_workspace_folder", {}, { timeoutMs: null });
|
|
160
321
|
return normalizeWorkspaceFolderPickerResponse(result);
|
|
@@ -345,8 +506,31 @@ export class Pedelec {
|
|
|
345
506
|
const raw = (input ?? {});
|
|
346
507
|
const provider = typeof raw.provider === "string" ? raw.provider.trim() : "";
|
|
347
508
|
const hasProvider = provider.length > 0;
|
|
348
|
-
|
|
349
|
-
|
|
509
|
+
const model = normalizeCreateSessionModelInput(raw.model);
|
|
510
|
+
if (model !== undefined) {
|
|
511
|
+
if (raw.effortLevel !== undefined) {
|
|
512
|
+
throw makeError("INVALID_INPUT", "effortLevel cannot be combined with model");
|
|
513
|
+
}
|
|
514
|
+
const effort = normalizeCreateSessionEffortInput(raw.effort);
|
|
515
|
+
const autoEndOnDisconnect = raw.autoEndOnDisconnect !== false;
|
|
516
|
+
const normalizedSkills = normalizeSkillsInput(raw.skills);
|
|
517
|
+
const workspace = normalizeCreateSessionWorkspaceInput(raw.workspace);
|
|
518
|
+
if (!hasProvider) {
|
|
519
|
+
return this.resolveDefaultCreateSessionInput(model, effort, undefined, normalizedSkills, workspace, autoEndOnDisconnect);
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
provider: provider,
|
|
523
|
+
model,
|
|
524
|
+
effort,
|
|
525
|
+
skills: normalizedSkills.manifest,
|
|
526
|
+
workspace,
|
|
527
|
+
inlineToolHandlers: normalizedSkills.handlers,
|
|
528
|
+
denoModules: normalizedSkills.denoModules,
|
|
529
|
+
autoEndOnDisconnect,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
if (raw.effort !== undefined) {
|
|
533
|
+
throw makeError("INVALID_INPUT", "effort requires model");
|
|
350
534
|
}
|
|
351
535
|
const effortLevel = raw.effortLevel === undefined ? "default" : raw.effortLevel;
|
|
352
536
|
if (!isEffortLevel(effortLevel)) {
|
|
@@ -356,7 +540,7 @@ export class Pedelec {
|
|
|
356
540
|
const normalizedSkills = normalizeSkillsInput(raw.skills);
|
|
357
541
|
const workspace = normalizeCreateSessionWorkspaceInput(raw.workspace);
|
|
358
542
|
if (!hasProvider) {
|
|
359
|
-
return this.resolveDefaultCreateSessionInput(effortLevel, normalizedSkills, workspace, autoEndOnDisconnect);
|
|
543
|
+
return this.resolveDefaultCreateSessionInput(undefined, undefined, effortLevel, normalizedSkills, workspace, autoEndOnDisconnect);
|
|
360
544
|
}
|
|
361
545
|
return {
|
|
362
546
|
provider: provider,
|
|
@@ -364,6 +548,7 @@ export class Pedelec {
|
|
|
364
548
|
skills: normalizedSkills.manifest,
|
|
365
549
|
workspace,
|
|
366
550
|
inlineToolHandlers: normalizedSkills.handlers,
|
|
551
|
+
denoModules: normalizedSkills.denoModules,
|
|
367
552
|
autoEndOnDisconnect,
|
|
368
553
|
};
|
|
369
554
|
}
|
|
@@ -419,7 +604,7 @@ export class Pedelec {
|
|
|
419
604
|
};
|
|
420
605
|
}
|
|
421
606
|
}
|
|
422
|
-
async resolveDefaultCreateSessionInput(effortLevel, normalizedSkills, workspace, autoEndOnDisconnect) {
|
|
607
|
+
async resolveDefaultCreateSessionInput(model, effort, effortLevel, normalizedSkills, workspace, autoEndOnDisconnect) {
|
|
423
608
|
const settings = await this.getSettings();
|
|
424
609
|
if (!settings.defaultProvider) {
|
|
425
610
|
throw makeError("DEFAULT_PROVIDER_NOT_SET", "Default provider is not set. Open the Pedelec desktop app Settings page and choose a default provider.");
|
|
@@ -427,10 +612,13 @@ export class Pedelec {
|
|
|
427
612
|
await this.assertDefaultProviderAvailable(settings.defaultProvider);
|
|
428
613
|
return {
|
|
429
614
|
provider: settings.defaultProvider,
|
|
615
|
+
model,
|
|
616
|
+
effort,
|
|
430
617
|
effortLevel,
|
|
431
618
|
skills: normalizedSkills.manifest,
|
|
432
619
|
workspace,
|
|
433
620
|
inlineToolHandlers: normalizedSkills.handlers,
|
|
621
|
+
denoModules: normalizedSkills.denoModules,
|
|
434
622
|
autoEndOnDisconnect,
|
|
435
623
|
};
|
|
436
624
|
}
|
|
@@ -593,6 +781,22 @@ function normalizeCreateSessionWorkspaceInput(value) {
|
|
|
593
781
|
}
|
|
594
782
|
return { path };
|
|
595
783
|
}
|
|
784
|
+
function normalizeCreateSessionModelInput(value) {
|
|
785
|
+
if (value === undefined)
|
|
786
|
+
return undefined;
|
|
787
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
788
|
+
throw makeError("INVALID_INPUT", "model must be a non-empty string when provided");
|
|
789
|
+
}
|
|
790
|
+
return value.trim();
|
|
791
|
+
}
|
|
792
|
+
function normalizeCreateSessionEffortInput(value) {
|
|
793
|
+
if (value === undefined)
|
|
794
|
+
return undefined;
|
|
795
|
+
if (!isEffort(value)) {
|
|
796
|
+
throw makeError("INVALID_INPUT", "effort must be one of low, medium, high, xhigh, or max");
|
|
797
|
+
}
|
|
798
|
+
return value;
|
|
799
|
+
}
|
|
596
800
|
export class PedelecSession {
|
|
597
801
|
client;
|
|
598
802
|
sessionId;
|
|
@@ -1399,6 +1603,9 @@ function isProviderCode(value) {
|
|
|
1399
1603
|
function isEffortLevel(value) {
|
|
1400
1604
|
return value === "default" || value === "low" || value === "high";
|
|
1401
1605
|
}
|
|
1606
|
+
function isEffort(value) {
|
|
1607
|
+
return value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max";
|
|
1608
|
+
}
|
|
1402
1609
|
function isPlainObject(value) {
|
|
1403
1610
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1404
1611
|
}
|
|
@@ -1504,6 +1711,10 @@ function isDeterministicResumeError(error) {
|
|
|
1504
1711
|
error.code === "THREAD_NOT_FOUND" ||
|
|
1505
1712
|
error.code === "WORKSPACE_OPEN_FAILED" ||
|
|
1506
1713
|
error.code === "PROVIDER_COMMAND_FAILED" ||
|
|
1714
|
+
error.code === "DENO_MODULE_SETUP_INCOMPLETE" ||
|
|
1715
|
+
error.code === "DENO_MODULE_SETUP_FAILED" ||
|
|
1716
|
+
error.code === "DENO_MODULE_MATERIALIZATION_FAILED" ||
|
|
1717
|
+
error.code === "DENO_IMPORT_MAP_INVALID" ||
|
|
1507
1718
|
error.code === "INVALID_INPUT";
|
|
1508
1719
|
}
|
|
1509
1720
|
function normalizeError(err, fallbackCode, fallbackMessage) {
|