@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 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 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 mapping is not exposed through 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`. The selected Ollama effort profile must contain a model in Desktop Settings; otherwise Core returns `MODEL_REQUIRED`. Low and high profiles are optional, but there is no fallback between profiles.
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 CreateSessionInputWithProvider<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
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 CreateSessionInputWithDefaults<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
102
- provider?: undefined;
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
- export type CreateSessionInput<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = CreateSessionInputWithProvider<TTools> | CreateSessionInputWithDefaults<TTools>;
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
- if (raw.model !== undefined) {
349
- throw makeError("INVALID_INPUT", "model is no longer supported; configure provider effort profiles in Desktop Settings");
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) {