@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 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; that mapping is not exposed through the SDK.
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 in Desktop Settings; otherwise Core returns `MODEL_REQUIRED`. Low and high profiles are optional, but there is no fallback between profiles.
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
- return normalizeProviderInfoList(await this.request("list_providers"));
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
- if (raw.model !== undefined) {
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 { name: item.name, code: item.code, available: item.available, error: item.error };
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) {
@@ -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
- // @ts-expect-error model is no longer a createSession option
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;