@kaoruisaac/pedelec 0.3.5 → 0.4.1

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
@@ -24,7 +24,7 @@ Pedelec is a browser SDK and local bridge for applications that want to work wit
24
24
  A web application can use Pedelec to:
25
25
 
26
26
  - create an agent session on the user's machine;
27
- - choose an application-managed workspace with the native directory picker;
27
+ - open an application-managed workspace with the native directory picker or an explicit path;
28
28
  - send user instructions and receive streamed assistant text;
29
29
  - expose narrowly scoped browser-side tools to the agent;
30
30
  - resume or end sessions; and
@@ -223,7 +223,7 @@ const path = await session.uploadAsset(file);
223
223
  const assets = await session.listAssets();
224
224
  ```
225
225
 
226
- The physical shared App/Agent directory is `.pedelec-runtime/assets/`. In the SDK contract, `assets/` is its implicit root: `listAssets()` recursively lists regular files at every level as a flat array, ordered by filesystem modification time (newest first) and then by name; nested paths such as `/results/report.json` are returned in full. Directory entries and symlinks are excluded, as are `.pedelec-*` entries at every level; other dotfiles are included. It may run while the agent runs. One session can only upload one file at a time, but uploads can run alongside prepare or agent execution.
226
+ Assets are stored in the Workspace shared by the Session. The physical shared App/Agent directory is `.pedelec-runtime/assets/`. In the SDK contract, `assets/` is its implicit root: `listAssets()` recursively lists regular files at every level as a flat array, ordered by filesystem modification time (newest first) and then by name; nested paths such as `/results/report.json` are returned in full. Directory entries and symlinks are excluded, as are `.pedelec-*` entries at every level; other dotfiles are included. It may run while the agent runs. One session can only upload one file at a time, but uploads can run alongside prepare or agent execution. When several sessions share a Workspace, they share this `.pedelec-runtime/assets/` area.
227
227
 
228
228
  ```ts
229
229
  const text = await session.readAsset("/report.txt", "text");
@@ -235,37 +235,80 @@ Public asset paths use `/...` with `assets/` as their implicit root; nested path
235
235
 
236
236
  ## Workspace
237
237
 
238
- Workspace is the filesystem root in which the Agent works. Pedelec stores its private runtime data under `<workspace>/.pedelec-runtime/`.
238
+ Workspace is the filesystem root in which the Agent works. Pedelec stores its private runtime data under `<workspace>/.pedelec-runtime/`. There are two supported flows:
239
239
 
240
- By default, each session receives a temporary Desktop-managed workspace. To use an application-owned workspace that can persist across sessions, pass an absolute path:
240
+ ### Explicit Workspace
241
+
242
+ ```ts
243
+ const workspace = await pedelec.openWorkspace();
244
+ if (!workspace) return;
245
+
246
+ const session = await workspace.createSession({
247
+ model: "gpt-5.6-sol",
248
+ effort: "high",
249
+ });
250
+ ```
251
+
252
+ `openWorkspace()` without a path opens the native folder picker. User cancellation resolves to `null`; no Workspace or Session is created. The returned `PedelecWorkspace` handle is the object to retain and reuse. An explicit path bypasses the picker:
253
+
254
+ ```ts
255
+ const workspace = await pedelec.openWorkspace("C:\\workspace\\project-a");
256
+ ```
257
+
258
+ Pedelec initializes `.pedelec-runtime` for Workspace-owned runtime data without clearing project files or existing private data. Assets, logs, and temporary data are Workspace-level, while generated Session skills and Deno Module state are isolated per Thread. Applications should not depend on the private `.pedelec-runtime` subdirectory layout. Explicit Workspaces are application-managed, are never deleted by Pedelec, and can own multiple Sessions. An explicit path must not overlap Pedelec's managed workspace root.
259
+
260
+ ### Managed convenience
241
261
 
242
262
  ```ts
243
263
  const session = await pedelec.createSession({
244
- provider: "codex",
245
- workspace: { path: "C:\\workspace\\project-a" },
264
+ effortLevel: "high",
246
265
  });
266
+ console.log(session.workspace.path); // null for managed Workspace
247
267
  ```
248
268
 
249
- Pedelec creates `.pedelec-runtime/` with missing `assets/`, `logs/`, `skills/`, and `tmp/` subdirectories, preserving existing project files and existing private data. It never deletes an explicit workspace, and multiple active sessions may share it. Filesystem write conflicts are the application's responsibility. An explicit path must not overlap Pedelec's managed workspace root.
269
+ `Pedelec.createSession()` creates a temporary Desktop-managed Workspace automatically. It no longer accepts Workspace configuration; use `openWorkspace()` followed by `workspace.createSession()` for an explicit Workspace. Every created or resumed Session exposes its Workspace as `session.workspace`.
270
+
271
+ ### Workspace methods
250
272
 
251
- ## Selecting an Application Workspace
273
+ `workspace.listFiles(path?)` and `workspace.listFolders(path?)` recursively return Workspace-relative paths. An omitted path lists from the Workspace root; `/` is the separator and results are lexicographically sorted. The `.pedelec-runtime` directory is included. Symlinks and junctions are neither followed nor returned. Results are not silently truncated: an oversized response fails with `WORKSPACE_LIST_TOO_LARGE`, so retry with a narrower path.
252
274
 
253
- Use `workspaceFolderPicker()` when the application wants the user to choose a local workspace before creating a session:
275
+ `workspace.run(script, options?)` executes application-supplied Deno code in the Workspace:
254
276
 
255
277
  ```ts
256
- const folder = await pedelec.workspaceFolderPicker();
257
- if (!folder) return; // The user cancelled the native picker.
278
+ const result = await workspace.run(
279
+ "console.log(JSON.stringify({ ok: true }))",
280
+ { timeoutMs: 60_000 },
281
+ );
258
282
 
259
- if (!folder.isEmptyFolder && !folder.hasWorkspaceConfig) {
260
- // The application decides whether to warn the user.
283
+ if (result.stdoutTruncated) {
284
+ throw new Error("Workspace script output was truncated");
261
285
  }
286
+ const value = JSON.parse(result.stdout);
287
+ ```
262
288
 
263
- const session = await pedelec.createSession({
264
- workspace: { path: folder.path },
289
+ The default timeout is 60 seconds; `timeoutMs` must be a positive integer. The return value is the raw `DenoRunOutput` shape: `exitCode`, `stdout`, `stderr`, `stdoutTruncated`, and `stderrTruncated`. A non-zero `exitCode` still resolves normally. The application parses `stdout` when it wants a function-like return value. Check truncation flags before relying on output; an oversized browser response fails with `WORKSPACE_RUN_OUTPUT_TOO_LARGE`.
290
+
291
+ `workspace.run()` has read and write access to the Workspace, but no network, host environment, subprocess execution, FFI, or `sys` permission. The existing no-remote, cached-only, and no-npm runtime restrictions still apply. This is a direct deterministic capability for an approved Web application to read and modify the opened Workspace; it is not unrestricted host code execution and is not mediated by an Agent.
292
+
293
+ Several `workspace.run()` calls in one Workspace may execute concurrently. A Workspace run cannot start while any Session in that Workspace has an active Agent/provider operation. Conversely, a Session in that Workspace cannot begin `sendText()` or provider preparation while one or more Workspace runs are active. This rule is Workspace-wide, including operations started through another `PedelecSession` handle; conflicts reject with `WORKSPACE_BUSY`.
294
+
295
+ Session `skills.denoModules` belong to the Session/Thread that declares them and are available only to Agent-side `pedelec-deno` execution for that Thread. Workspace runs accept the same Vite-prepared declarations explicitly:
296
+
297
+ ```ts
298
+ const sceneTools = defineDenoModule({
299
+ name: "scene-tools",
300
+ entry: "./deno/scene-tools.ts",
265
301
  });
302
+
303
+ const result = await workspace.run(
304
+ `import { buildScene } from "scene-tools";\nawait buildScene();`,
305
+ { denoModules: [sceneTools] },
306
+ );
266
307
  ```
267
308
 
268
- `workspaceFolderPicker()` returns `Promise<WorkspaceFolderPickerResult | null>`. It is a read-only snapshot: it does not validate or initialize the selected folder, create workspace directories, or create the workspace marker. `isEmptyFolder` only reports whether the selected folder root has any filesystem entry. `hasWorkspaceConfig` is true only when `.pedelec-workspace.json` exists as a regular file; its JSON is not parsed. The picker requires the same origin approval as other sensitive Desktop APIs, and cancellation returns `null`. `createSession()` still applies the full workspace rules and creates the marker after successful custom workspace initialization. The marker currently records the SDK version and normalized caller origin but does not restrict reuse by origin or SDK version.
309
+ `workspace.run()` does not inherit any Session Deno Modules, and Sessions do not inherit modules used by Workspace runs. The Vite-prepared package is cached privately per Workspace and authorized SDK origin; each run receives an import map containing exactly its requested names. A ready module name is immutable for the lifetime of the Core process, and Workspace-run modules never appear in Agent Host Context. The private Workspace-run cache is reset when Core restarts; project files, assets, and Thread module snapshots are preserved.
310
+
311
+ The Workspace handle outlives an individual Session conceptually. Ending a Session does not close or delete a custom Workspace, and Pedelec does not delete it during app cleanup. Managed Workspaces remain under Desktop cleanup ownership and are not promised to disappear immediately when a Thread ends. The browser capability is runtime-scoped; reopen the custom Workspace after a Desktop/Core restart when necessary. `pedelec.resumeSession(sessionId)` returns a Session that already includes `session.workspace`; the application does not need to call `openWorkspace()` separately. Same-handle `session.resume()` preserves the existing Workspace handle, subject to the existing Core/thread and transport caveats.
269
312
 
270
313
  ---
271
314
 
package/dist/index.d.ts CHANGED
@@ -18,11 +18,15 @@ export type Asset = {
18
18
  };
19
19
  export type DenoModuleDefinition<TName extends string = string> = {
20
20
  name: TName;
21
- description: string;
21
+ description?: string;
22
22
  entry: string;
23
- usage: string;
23
+ usage?: string;
24
24
  preferStdinExecution?: boolean;
25
25
  };
26
+ type AgentDenoModuleDefinition<TName extends string = string> = DenoModuleDefinition<TName> & {
27
+ description: string;
28
+ usage: string;
29
+ };
26
30
  export type ToolArgsSchemaMeta<TDefault extends JsonValue = JsonValue> = {
27
31
  description?: string;
28
32
  default?: TDefault;
@@ -79,7 +83,7 @@ export type ToolDefinition<TArgs = unknown, TResult = unknown, TName extends str
79
83
  export type SkillsInput<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
80
84
  guidance: string;
81
85
  tools: TTools;
82
- denoModules?: readonly DenoModuleDefinition[];
86
+ denoModules?: readonly AgentDenoModuleDefinition[];
83
87
  };
84
88
  export type ToolNameOf<TTools extends readonly ToolDefinition[]> = Extract<TTools[number]["name"], string>;
85
89
  export type SerializableToolManifest = {
@@ -99,17 +103,19 @@ export type SerializableSkillsManifest = {
99
103
  tools: SerializableToolManifest[];
100
104
  denoModules?: SerializableDenoModuleManifest[];
101
105
  };
102
- export type CreateSessionWorkspaceInput = {
103
- path: string;
106
+ export type WorkspaceRunOptions = {
107
+ timeoutMs?: number;
108
+ denoModules?: readonly DenoModuleDefinition[];
109
+ };
110
+ export type WorkspaceRunResult = {
111
+ exitCode: number;
112
+ stdout: string;
113
+ stderr: string;
114
+ stdoutTruncated: boolean;
115
+ stderrTruncated: boolean;
104
116
  };
105
- export interface WorkspaceFolderPickerResult {
106
- path: string;
107
- isEmptyFolder: boolean;
108
- hasWorkspaceConfig: boolean;
109
- }
110
117
  type CreateSessionSharedInput<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
111
118
  skills?: SkillsInput<TTools>;
112
- workspace?: CreateSessionWorkspaceInput;
113
119
  autoEndOnDisconnect?: boolean;
114
120
  };
115
121
  type ProfileSessionConfiguration = {
@@ -308,7 +314,29 @@ type ErrorHandler = (error: PedelecError, ctx: ErrorEventContext) => void;
308
314
  type StatusHandler = (status: PedelecSessionStatus, ctx: StatusEventContext) => void;
309
315
  type EndedHandler = (ctx: EndedEventContext) => void;
310
316
  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>;
317
+ export declare function defineDenoModule<const TModule extends DenoModuleDefinition>(module: TModule): TModule;
318
+ type PreparedWorkspaceDenoModule = {
319
+ name: string;
320
+ runtimeSource: string;
321
+ typesSource: string;
322
+ };
323
+ export declare class PedelecWorkspace {
324
+ private readonly client;
325
+ private workspaceId;
326
+ private currentPath;
327
+ constructor(client: Pedelec, workspaceId: string, path: string | null);
328
+ get path(): string | null;
329
+ createSession(): Promise<PedelecSession<string>>;
330
+ createSession<const TTools extends readonly ToolDefinition[]>(input: CreateSessionInput<TTools>): Promise<PedelecSession<ToolNameOf<TTools>>>;
331
+ listFiles(path?: string): Promise<string[]>;
332
+ listFolders(path?: string): Promise<string[]>;
333
+ run(script: string, options?: WorkspaceRunOptions): Promise<WorkspaceRunResult>;
334
+ /** @internal */
335
+ get workspaceIdInternal(): string;
336
+ /** @internal */
337
+ hydrate(workspaceId: string, path: string | null): void;
338
+ private list;
339
+ }
312
340
  export declare class Pedelec {
313
341
  private readonly pageWindow;
314
342
  private readonly channelId;
@@ -323,16 +351,25 @@ export declare class Pedelec {
323
351
  constructor(options?: PedelecOptions);
324
352
  createSession(): Promise<PedelecSession<string>>;
325
353
  createSession<const TTools extends readonly ToolDefinition[]>(input: CreateSessionInput<TTools>): Promise<PedelecSession<ToolNameOf<TTools>>>;
354
+ /** @internal */
355
+ createSessionInternal<const TTools extends readonly ToolDefinition[]>(input?: CreateSessionInput<TTools>, workspace?: PedelecWorkspace): Promise<PedelecSession<ToolNameOf<TTools>>>;
326
356
  private uploadDenoModuleArtifact;
357
+ /** @internal */
358
+ uploadWorkspaceDenoModuleArtifact(workspaceId: string, module: PreparedWorkspaceDenoModule): Promise<void>;
359
+ private uploadDenoModuleArtifactWithTicket;
327
360
  private abortSessionSetup;
328
361
  private completeSessionSetup;
329
- workspaceFolderPicker(): Promise<WorkspaceFolderPickerResult | null>;
362
+ openWorkspace(): Promise<PedelecWorkspace | null>;
363
+ openWorkspace(path: string): Promise<PedelecWorkspace>;
330
364
  listProviders(): Promise<ProviderInfo[]>;
331
365
  getSettings(): Promise<PedelecSettings>;
332
366
  getApprovalStatus(): Promise<ApprovalStatus>;
333
367
  resumeSession(sessionId: string): Promise<PedelecSession<string>>;
334
368
  request<T>(type: string, payload?: Record<string, unknown>): Promise<T>;
335
- private requestWithOptions;
369
+ /** @internal */
370
+ requestWithOptions<T>(type: string, payload?: Record<string, unknown>, options?: {
371
+ timeoutMs?: number | null;
372
+ }): Promise<T>;
336
373
  unregisterSession(sessionId: string): void;
337
374
  /** @internal */
338
375
  registerExistingSession(session: PedelecSession<any>): void;
@@ -363,6 +400,7 @@ export declare class PedelecSession<TToolName extends string = string> {
363
400
  readonly sessionId: string;
364
401
  readonly provider: string;
365
402
  readonly effortLevel?: EffortLevel;
403
+ readonly workspace: PedelecWorkspace;
366
404
  readonly sessionCreatedAt: number;
367
405
  readonly usage: PedelecSessionUsage;
368
406
  private status;
@@ -386,7 +424,7 @@ export declare class PedelecSession<TToolName extends string = string> {
386
424
  private readonly recoveredToolCalls;
387
425
  private readonly handledToolRequestIds;
388
426
  private authoritativePendingToolRequest;
389
- constructor(client: Pedelec, sessionId: string, provider: string, effortLevel?: EffortLevel, inlineToolHandlers?: Map<string, ToolSpecificHandler>, autoEndOnDisconnect?: boolean);
427
+ constructor(client: Pedelec, sessionId: string, provider: string, workspace: PedelecWorkspace, effortLevel?: EffortLevel, inlineToolHandlers?: Map<string, ToolSpecificHandler>, autoEndOnDisconnect?: boolean);
390
428
  prepare(): Promise<void>;
391
429
  sendText(text: string): Promise<void>;
392
430
  onChat(handler: ChatHandler): () => void;
package/dist/index.js CHANGED
@@ -39,6 +39,46 @@ export function defineTool(tool) {
39
39
  export function defineDenoModule(module) {
40
40
  return module;
41
41
  }
42
+ function normalizeWorkspaceDenoModules(value) {
43
+ if (value === undefined)
44
+ return [];
45
+ if (!Array.isArray(value)) {
46
+ throw makeError("INVALID_INPUT", "options.denoModules must be an array");
47
+ }
48
+ const seen = new Set();
49
+ return value.map((module, index) => {
50
+ if (!module || typeof module !== "object" || Array.isArray(module)) {
51
+ throw makeError("INVALID_INPUT", "options.denoModules entries must be objects", { index });
52
+ }
53
+ const rawModule = module;
54
+ if (!isValidDenoModuleName(rawModule.name)) {
55
+ throw makeError("INVALID_INPUT", "Deno Module name is invalid", {
56
+ index,
57
+ moduleName: rawModule.name,
58
+ });
59
+ }
60
+ if (seen.has(rawModule.name)) {
61
+ throw makeError("INVALID_INPUT", "duplicate Deno Module name", {
62
+ moduleName: rawModule.name,
63
+ });
64
+ }
65
+ seen.add(rawModule.name);
66
+ if (rawModule.preferStdinExecution !== undefined && typeof rawModule.preferStdinExecution !== "boolean") {
67
+ throw makeError("INVALID_INPUT", "Deno Module preferStdinExecution must be a boolean", {
68
+ moduleName: rawModule.name,
69
+ });
70
+ }
71
+ const artifact = getPreparedDenoModuleArtifact(module);
72
+ if (!artifact) {
73
+ throw makeError("INVALID_INPUT", `Deno module "${rawModule.name}" was not prepared. Configure pedelecVitePlugin() in the Vite project.`, { moduleName: rawModule.name });
74
+ }
75
+ return {
76
+ name: rawModule.name,
77
+ runtimeSource: artifact.runtimeSource,
78
+ typesSource: artifact.typesSource,
79
+ };
80
+ });
81
+ }
42
82
  function normalizeSkillsInput(value) {
43
83
  const handlers = new Map();
44
84
  if (value === undefined)
@@ -190,6 +230,92 @@ function normalizeToolArgsSchema(argsSchema, toolName) {
190
230
  });
191
231
  }
192
232
  }
233
+ export class PedelecWorkspace {
234
+ client;
235
+ workspaceId;
236
+ currentPath;
237
+ constructor(client, workspaceId, path) {
238
+ this.client = client;
239
+ this.workspaceId = workspaceId;
240
+ this.currentPath = path;
241
+ }
242
+ get path() {
243
+ return this.currentPath;
244
+ }
245
+ async createSession(input = {}) {
246
+ return this.client.createSessionInternal(input, this);
247
+ }
248
+ listFiles(path) {
249
+ return this.list("workspace_list_files", path);
250
+ }
251
+ listFolders(path) {
252
+ return this.list("workspace_list_folders", path);
253
+ }
254
+ run(script, options = {}) {
255
+ if (typeof script !== "string") {
256
+ return Promise.reject(makeError("INVALID_INPUT", "script must be a string"));
257
+ }
258
+ if (!isPlainObject(options)) {
259
+ return Promise.reject(makeError("INVALID_INPUT", "options must be an object"));
260
+ }
261
+ const timeoutMs = options.timeoutMs;
262
+ if (timeoutMs !== undefined && (!Number.isInteger(timeoutMs) || timeoutMs <= 0)) {
263
+ return Promise.reject(makeError("INVALID_INPUT", "timeoutMs must be a positive integer"));
264
+ }
265
+ let modules;
266
+ try {
267
+ modules = normalizeWorkspaceDenoModules(options.denoModules);
268
+ }
269
+ catch (error) {
270
+ return Promise.reject(error);
271
+ }
272
+ const run = async () => {
273
+ if (modules.length > 0) {
274
+ const prepared = await this.client.request("prepare_workspace_deno_modules", {
275
+ workspaceId: this.workspaceId,
276
+ moduleNames: modules.map(({ name }) => name),
277
+ });
278
+ const missingModuleNames = normalizeWorkspaceDenoModulePrepareResponse(prepared, modules.map(({ name }) => name));
279
+ const modulesByName = new Map(modules.map((module) => [module.name, module]));
280
+ for (const name of missingModuleNames) {
281
+ const module = modulesByName.get(name);
282
+ if (!module) {
283
+ throw makeError("SDK_PROTOCOL_ERROR", "prepare_workspace_deno_modules response was inconsistent");
284
+ }
285
+ await this.client.uploadWorkspaceDenoModuleArtifact(this.workspaceId, module);
286
+ }
287
+ }
288
+ return this.client.requestWithOptions("workspace_run", {
289
+ workspaceId: this.workspaceId,
290
+ script,
291
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
292
+ ...(modules.length === 0 ? {} : { denoModules: modules.map(({ name }) => name) }),
293
+ }, { timeoutMs: null }).then(normalizeWorkspaceRunResponse);
294
+ };
295
+ return run();
296
+ }
297
+ /** @internal */
298
+ get workspaceIdInternal() {
299
+ return this.workspaceId;
300
+ }
301
+ /** @internal */
302
+ hydrate(workspaceId, path) {
303
+ this.workspaceId = workspaceId;
304
+ this.currentPath = path;
305
+ }
306
+ list(type, path) {
307
+ if (path !== undefined && typeof path !== "string") {
308
+ return Promise.reject(makeError("INVALID_INPUT", "path must be a string when provided"));
309
+ }
310
+ if (path !== undefined && path.trim().length === 0) {
311
+ return Promise.reject(makeError("INVALID_INPUT", "path must not be empty when provided"));
312
+ }
313
+ return this.client.request(type, {
314
+ workspaceId: this.workspaceId,
315
+ ...(path === undefined ? {} : { path }),
316
+ }).then(normalizeWorkspaceListResponse);
317
+ }
318
+ }
193
319
  export class Pedelec {
194
320
  pageWindow;
195
321
  channelId;
@@ -212,6 +338,10 @@ export class Pedelec {
212
338
  this.connectExtension();
213
339
  }
214
340
  async createSession(input = {}) {
341
+ return this.createSessionInternal(input);
342
+ }
343
+ /** @internal */
344
+ async createSessionInternal(input = {}, workspace) {
215
345
  const resolvedOrPromise = this.resolveCreateSessionInput(input);
216
346
  const resolvedInput = resolvedOrPromise instanceof Promise ? await resolvedOrPromise : resolvedOrPromise;
217
347
  const result = await this.request("create_session", {
@@ -221,36 +351,48 @@ export class Pedelec {
221
351
  effort: resolvedInput.effort,
222
352
  effortLevel: resolvedInput.effortLevel,
223
353
  skills: resolvedInput.skills,
224
- workspace: resolvedInput.workspace,
354
+ ...(workspace ? { workspaceId: workspace.workspaceIdInternal } : {}),
225
355
  autoEndOnDisconnect: resolvedInput.autoEndOnDisconnect,
226
356
  },
227
357
  });
228
- if (!result.sessionId) {
229
- throw makeError("SDK_PROTOCOL_ERROR", "create_session response did not include sessionId");
358
+ const normalizedResult = normalizeCreateSessionResponse(result);
359
+ if (workspace && normalizedResult.workspace.workspaceId !== workspace.workspaceIdInternal) {
360
+ throw makeError("SDK_PROTOCOL_ERROR", "create_session response returned a different Workspace", {
361
+ expectedWorkspaceId: workspace.workspaceIdInternal,
362
+ actualWorkspaceId: normalizedResult.workspace.workspaceId,
363
+ });
230
364
  }
231
- if (resolvedInput.model !== undefined && result.explicitModelConfigApplied !== true) {
365
+ if (resolvedInput.model !== undefined && normalizedResult.explicitModelConfigApplied !== true) {
232
366
  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);
367
+ await this.abortSessionSetup(normalizedResult.sessionId);
234
368
  throw error;
235
369
  }
236
370
  try {
237
371
  for (const module of resolvedInput.denoModules) {
238
- await this.uploadDenoModuleArtifact(result.sessionId, module);
372
+ await this.uploadDenoModuleArtifact(normalizedResult.sessionId, module);
239
373
  }
240
374
  if (resolvedInput.denoModules.length > 0) {
241
- await this.completeSessionSetup(result.sessionId);
375
+ await this.completeSessionSetup(normalizedResult.sessionId);
242
376
  }
243
377
  }
244
378
  catch (error) {
245
379
  // The thread exists at this point but is not exposed through the SDK
246
380
  // session registry yet. Keep the setup failure as the caller-visible
247
381
  // error even if the best-effort cleanup transport also fails.
248
- await this.abortSessionSetup(result.sessionId);
382
+ await this.abortSessionSetup(normalizedResult.sessionId);
249
383
  throw normalizeError(error, "DENO_MODULE_UPLOAD_FAILED", "Deno Module setup failed");
250
384
  }
251
- return this.registerSession(result.sessionId, resolvedInput.provider, resolvedInput.effortLevel, resolvedInput.inlineToolHandlers, resolvedInput.autoEndOnDisconnect);
385
+ const sessionWorkspace = workspace ?? new PedelecWorkspace(this, normalizedResult.workspace.workspaceId, normalizedResult.workspace.path);
386
+ return this.registerSession(normalizedResult.sessionId, resolvedInput.provider, resolvedInput.effortLevel, resolvedInput.inlineToolHandlers, resolvedInput.autoEndOnDisconnect, sessionWorkspace);
252
387
  }
253
388
  async uploadDenoModuleArtifact(sessionId, module) {
389
+ await this.uploadDenoModuleArtifactWithTicket(module, "create_deno_module_upload", { sessionId, moduleName: module.name });
390
+ }
391
+ /** @internal */
392
+ async uploadWorkspaceDenoModuleArtifact(workspaceId, module) {
393
+ await this.uploadDenoModuleArtifactWithTicket(module, "create_workspace_deno_module_upload", { workspaceId, moduleName: module.name });
394
+ }
395
+ async uploadDenoModuleArtifactWithTicket(module, ticketType, ticketPayload) {
254
396
  const envelope = JSON.stringify({
255
397
  version: 1,
256
398
  format: "esm",
@@ -262,9 +404,8 @@ export class Pedelec {
262
404
  if (expectedSizeBytes <= 0 || expectedSizeBytes > MAX_ASSET_UPLOAD_BYTES) {
263
405
  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
406
  }
265
- const ticket = await this.request("create_deno_module_upload", {
266
- sessionId,
267
- moduleName: module.name,
407
+ const ticket = await this.request(ticketType, {
408
+ ...ticketPayload,
268
409
  expectedSizeBytes,
269
410
  });
270
411
  if (!isPlainObject(ticket) || typeof ticket.uploadUrl !== "string" || ticket.uploadUrl.length === 0 ||
@@ -316,9 +457,13 @@ export class Pedelec {
316
457
  async completeSessionSetup(sessionId) {
317
458
  await this.request("complete_session_setup", { sessionId });
318
459
  }
319
- async workspaceFolderPicker() {
320
- const result = await this.requestWithOptions("pick_workspace_folder", {}, { timeoutMs: null });
321
- return normalizeWorkspaceFolderPickerResponse(result);
460
+ async openWorkspace(path) {
461
+ if (path !== undefined && (typeof path !== "string" || path.trim().length === 0)) {
462
+ throw makeError("INVALID_INPUT", "path must be a non-empty string when provided");
463
+ }
464
+ const result = await this.requestWithOptions("open_workspace", path === undefined ? {} : { path }, path === undefined ? { timeoutMs: null } : {});
465
+ const workspace = normalizeOpenWorkspaceResponse(result);
466
+ return workspace === null ? null : new PedelecWorkspace(this, workspace.workspaceId, workspace.path);
322
467
  }
323
468
  async listProviders() {
324
469
  const providers = normalizeProviderInfoList(await this.request("list_providers"));
@@ -358,16 +503,23 @@ export class Pedelec {
358
503
  const existing = this.sessions.get(sessionId);
359
504
  const session = existing && !existing.isTransportDetached()
360
505
  ? existing
361
- : this.registerSession(sessionId, "", undefined, new Map(), false);
506
+ : this.registerSession(sessionId, "", undefined, new Map(), false, new PedelecWorkspace(this, "", null));
362
507
  try {
363
508
  const result = await this.request("resume_session", {
364
509
  sessionId,
365
510
  });
366
- if (!result.sessionId) {
367
- throw makeError("SDK_PROTOCOL_ERROR", "resume_session response did not include sessionId");
511
+ const normalizedResult = normalizeResumeSessionResponse(result);
512
+ if (normalizedResult.sessionId !== sessionId) {
513
+ throw makeError("SDK_PROTOCOL_ERROR", "resume_session response did not match sessionId");
514
+ }
515
+ if (existing && existing.workspace.workspaceIdInternal !== normalizedResult.workspace.workspaceId) {
516
+ throw makeError("SDK_PROTOCOL_ERROR", "resume_session response returned a different Workspace");
517
+ }
518
+ if (session !== existing) {
519
+ session.workspace.hydrate(normalizedResult.workspace.workspaceId, normalizedResult.workspace.path);
368
520
  }
369
- if (typeof result.autoEndOnDisconnect === "boolean") {
370
- session.setAutoEndOnDisconnect(result.autoEndOnDisconnect);
521
+ if (typeof normalizedResult.autoEndOnDisconnect === "boolean") {
522
+ session.setAutoEndOnDisconnect(normalizedResult.autoEndOnDisconnect);
371
523
  }
372
524
  return session;
373
525
  }
@@ -380,6 +532,7 @@ export class Pedelec {
380
532
  request(type, payload = {}) {
381
533
  return this.requestWithOptions(type, payload);
382
534
  }
535
+ /** @internal */
383
536
  requestWithOptions(type, payload = {}, options = {}) {
384
537
  let port;
385
538
  try {
@@ -394,7 +547,9 @@ export class Pedelec {
394
547
  type,
395
548
  requestId,
396
549
  ...payload,
397
- ...(type === "create_session" ? { callerSdkVersion: SDK_VERSION } : {}),
550
+ ...(type === "create_session" || type === "open_workspace"
551
+ ? { callerSdkVersion: SDK_VERSION }
552
+ : {}),
398
553
  };
399
554
  return new Promise((resolve, reject) => {
400
555
  const timeoutMs = options.timeoutMs === undefined ? this.bridgeTimeoutMs : options.timeoutMs;
@@ -514,16 +669,14 @@ export class Pedelec {
514
669
  const effort = normalizeCreateSessionEffortInput(raw.effort);
515
670
  const autoEndOnDisconnect = raw.autoEndOnDisconnect !== false;
516
671
  const normalizedSkills = normalizeSkillsInput(raw.skills);
517
- const workspace = normalizeCreateSessionWorkspaceInput(raw.workspace);
518
672
  if (!hasProvider) {
519
- return this.resolveDefaultCreateSessionInput(model, effort, undefined, normalizedSkills, workspace, autoEndOnDisconnect);
673
+ return this.resolveDefaultCreateSessionInput(model, effort, undefined, normalizedSkills, autoEndOnDisconnect);
520
674
  }
521
675
  return {
522
676
  provider: provider,
523
677
  model,
524
678
  effort,
525
679
  skills: normalizedSkills.manifest,
526
- workspace,
527
680
  inlineToolHandlers: normalizedSkills.handlers,
528
681
  denoModules: normalizedSkills.denoModules,
529
682
  autoEndOnDisconnect,
@@ -538,15 +691,13 @@ export class Pedelec {
538
691
  }
539
692
  const autoEndOnDisconnect = raw.autoEndOnDisconnect !== false;
540
693
  const normalizedSkills = normalizeSkillsInput(raw.skills);
541
- const workspace = normalizeCreateSessionWorkspaceInput(raw.workspace);
542
694
  if (!hasProvider) {
543
- return this.resolveDefaultCreateSessionInput(undefined, undefined, effortLevel, normalizedSkills, workspace, autoEndOnDisconnect);
695
+ return this.resolveDefaultCreateSessionInput(undefined, undefined, effortLevel, normalizedSkills, autoEndOnDisconnect);
544
696
  }
545
697
  return {
546
698
  provider: provider,
547
699
  effortLevel,
548
700
  skills: normalizedSkills.manifest,
549
- workspace,
550
701
  inlineToolHandlers: normalizedSkills.handlers,
551
702
  denoModules: normalizedSkills.denoModules,
552
703
  autoEndOnDisconnect,
@@ -604,7 +755,7 @@ export class Pedelec {
604
755
  };
605
756
  }
606
757
  }
607
- async resolveDefaultCreateSessionInput(model, effort, effortLevel, normalizedSkills, workspace, autoEndOnDisconnect) {
758
+ async resolveDefaultCreateSessionInput(model, effort, effortLevel, normalizedSkills, autoEndOnDisconnect) {
608
759
  const settings = await this.getSettings();
609
760
  if (!settings.defaultProvider) {
610
761
  throw makeError("DEFAULT_PROVIDER_NOT_SET", "Default provider is not set. Open the Pedelec desktop app Settings page and choose a default provider.");
@@ -616,7 +767,6 @@ export class Pedelec {
616
767
  effort,
617
768
  effortLevel,
618
769
  skills: normalizedSkills.manifest,
619
- workspace,
620
770
  inlineToolHandlers: normalizedSkills.handlers,
621
771
  denoModules: normalizedSkills.denoModules,
622
772
  autoEndOnDisconnect,
@@ -629,13 +779,13 @@ export class Pedelec {
629
779
  throw makeError("DEFAULT_PROVIDER_UNAVAILABLE", "Default provider is not currently available. Open the Pedelec desktop app Settings page and choose an available provider.", { provider });
630
780
  }
631
781
  }
632
- registerSession(sessionId, provider, effortLevel, inlineToolHandlers = new Map(), autoEndOnDisconnect = false) {
782
+ registerSession(sessionId, provider, effortLevel, inlineToolHandlers = new Map(), autoEndOnDisconnect = false, workspace) {
633
783
  const existing = this.sessions.get(sessionId);
634
784
  if (existing && !existing.isTransportDetached()) {
635
785
  existing.replaceInlineToolHandlers(inlineToolHandlers);
636
786
  return existing;
637
787
  }
638
- const session = new PedelecSession(this, sessionId, provider, effortLevel, inlineToolHandlers, autoEndOnDisconnect);
788
+ const session = new PedelecSession(this, sessionId, provider, workspace, effortLevel, inlineToolHandlers, autoEndOnDisconnect);
639
789
  this.sessions.set(sessionId, session);
640
790
  return session;
641
791
  }
@@ -766,21 +916,6 @@ export class Pedelec {
766
916
  }
767
917
  }
768
918
  }
769
- function normalizeCreateSessionWorkspaceInput(value) {
770
- if (value === undefined)
771
- return undefined;
772
- if (!value || typeof value !== "object" || Array.isArray(value)) {
773
- throw makeError("INVALID_INPUT", "workspace must be an object");
774
- }
775
- const path = value.path;
776
- if (typeof path !== "string") {
777
- throw makeError("INVALID_INPUT", "workspace.path must be a string");
778
- }
779
- if (path.trim().length === 0) {
780
- throw makeError("INVALID_INPUT", "workspace.path must not be empty");
781
- }
782
- return { path };
783
- }
784
919
  function normalizeCreateSessionModelInput(value) {
785
920
  if (value === undefined)
786
921
  return undefined;
@@ -802,6 +937,7 @@ export class PedelecSession {
802
937
  sessionId;
803
938
  provider;
804
939
  effortLevel;
940
+ workspace;
805
941
  sessionCreatedAt = Date.now();
806
942
  usage = { totalTokens: undefined };
807
943
  status = "idle";
@@ -825,11 +961,12 @@ export class PedelecSession {
825
961
  recoveredToolCalls = new Map();
826
962
  handledToolRequestIds = new Set();
827
963
  authoritativePendingToolRequest = undefined;
828
- constructor(client, sessionId, provider, effortLevel, inlineToolHandlers = new Map(), autoEndOnDisconnect = false) {
964
+ constructor(client, sessionId, provider, workspace, effortLevel, inlineToolHandlers = new Map(), autoEndOnDisconnect = false) {
829
965
  this.client = client;
830
966
  this.sessionId = sessionId;
831
967
  this.provider = provider;
832
968
  this.effortLevel = effortLevel;
969
+ this.workspace = workspace;
833
970
  this.inlineToolHandlers = new Map(inlineToolHandlers);
834
971
  this.autoEndOnDisconnect = autoEndOnDisconnect;
835
972
  }
@@ -1615,21 +1752,86 @@ function normalizePedelecSettings(value) {
1615
1752
  }
1616
1753
  return { defaultProvider: value.defaultProvider };
1617
1754
  }
1618
- function normalizeWorkspaceFolderPickerResponse(value) {
1619
- if (!isPlainObject(value) || !("path" in value)) {
1620
- throw makeError("SDK_PROTOCOL_ERROR", "pick_workspace_folder response had an invalid shape");
1755
+ function normalizeWorkspaceCapability(value, context) {
1756
+ if (!isPlainObject(value) || typeof value.workspaceId !== "string" || value.workspaceId.trim().length === 0 ||
1757
+ (value.path !== null && (typeof value.path !== "string" || value.path.length === 0))) {
1758
+ throw makeError("SDK_PROTOCOL_ERROR", `${context} response had an invalid Workspace shape`);
1621
1759
  }
1622
- const path = value.path;
1623
- if (path === null)
1760
+ return {
1761
+ workspaceId: value.workspaceId,
1762
+ path: value.path,
1763
+ };
1764
+ }
1765
+ function normalizeOpenWorkspaceResponse(value) {
1766
+ if (value === null)
1624
1767
  return null;
1625
- if (typeof path !== "string" || path.length === 0 ||
1626
- typeof value.isEmptyFolder !== "boolean" || typeof value.hasWorkspaceConfig !== "boolean") {
1627
- throw makeError("SDK_PROTOCOL_ERROR", "pick_workspace_folder response had an invalid shape");
1768
+ if (!isPlainObject(value) || !("workspace" in value)) {
1769
+ throw makeError("SDK_PROTOCOL_ERROR", "open_workspace response had an invalid shape");
1770
+ }
1771
+ if (value.workspace === null)
1772
+ return null;
1773
+ return normalizeWorkspaceCapability(value.workspace, "open_workspace");
1774
+ }
1775
+ function normalizeCreateSessionResponse(value) {
1776
+ if (!isPlainObject(value) || typeof value.sessionId !== "string" || value.sessionId.trim().length === 0 ||
1777
+ !("workspace" in value)) {
1778
+ throw makeError("SDK_PROTOCOL_ERROR", "create_session response had an invalid shape");
1779
+ }
1780
+ if ("explicitModelConfigApplied" in value && typeof value.explicitModelConfigApplied !== "boolean") {
1781
+ throw makeError("SDK_PROTOCOL_ERROR", "create_session response had an invalid model configuration flag");
1782
+ }
1783
+ return {
1784
+ sessionId: value.sessionId,
1785
+ workspace: normalizeWorkspaceCapability(value.workspace, "create_session"),
1786
+ ...(typeof value.explicitModelConfigApplied === "boolean"
1787
+ ? { explicitModelConfigApplied: value.explicitModelConfigApplied }
1788
+ : {}),
1789
+ };
1790
+ }
1791
+ function normalizeResumeSessionResponse(value) {
1792
+ if (!isPlainObject(value) || typeof value.sessionId !== "string" || value.sessionId.trim().length === 0 ||
1793
+ typeof value.autoEndOnDisconnect !== "boolean" || !("workspace" in value)) {
1794
+ throw makeError("SDK_PROTOCOL_ERROR", "resume_session response had an invalid shape");
1795
+ }
1796
+ return {
1797
+ sessionId: value.sessionId,
1798
+ autoEndOnDisconnect: value.autoEndOnDisconnect,
1799
+ workspace: normalizeWorkspaceCapability(value.workspace, "resume_session"),
1800
+ };
1801
+ }
1802
+ function normalizeWorkspaceListResponse(value) {
1803
+ const paths = Array.isArray(value) ? value : isPlainObject(value) ? value.paths : undefined;
1804
+ if (!Array.isArray(paths) || paths.some((path) => typeof path !== "string")) {
1805
+ throw makeError("SDK_PROTOCOL_ERROR", "workspace list response had an invalid shape");
1806
+ }
1807
+ return paths;
1808
+ }
1809
+ function normalizeWorkspaceDenoModulePrepareResponse(value, requestedNames) {
1810
+ if (!isPlainObject(value) || !Array.isArray(value.missingModuleNames)) {
1811
+ throw makeError("SDK_PROTOCOL_ERROR", "prepare_workspace_deno_modules response had an invalid shape");
1812
+ }
1813
+ const requested = new Set(requestedNames);
1814
+ const seen = new Set();
1815
+ for (const name of value.missingModuleNames) {
1816
+ if (typeof name !== "string" || !requested.has(name) || seen.has(name)) {
1817
+ throw makeError("SDK_PROTOCOL_ERROR", "prepare_workspace_deno_modules response had unknown or duplicate modules");
1818
+ }
1819
+ seen.add(name);
1820
+ }
1821
+ return value.missingModuleNames;
1822
+ }
1823
+ function normalizeWorkspaceRunResponse(value) {
1824
+ if (!isPlainObject(value) || !Number.isInteger(value.exitCode) || typeof value.stdout !== "string" ||
1825
+ typeof value.stderr !== "string" || typeof value.stdoutTruncated !== "boolean" ||
1826
+ typeof value.stderrTruncated !== "boolean") {
1827
+ throw makeError("SDK_PROTOCOL_ERROR", "workspace_run response had an invalid shape");
1628
1828
  }
1629
1829
  return {
1630
- path,
1631
- isEmptyFolder: value.isEmptyFolder,
1632
- hasWorkspaceConfig: value.hasWorkspaceConfig,
1830
+ exitCode: value.exitCode,
1831
+ stdout: value.stdout,
1832
+ stderr: value.stderr,
1833
+ stdoutTruncated: value.stdoutTruncated,
1834
+ stderrTruncated: value.stderrTruncated,
1633
1835
  };
1634
1836
  }
1635
1837
  function normalizeProviderInfoList(value) {
@@ -6,16 +6,16 @@ const spriteTools = defineDenoModule({
6
6
  usage: `import { previewActorSource } from "sprite-tools";`,
7
7
  preferStdinExecution: true,
8
8
  });
9
+ const workspaceTools = defineDenoModule({
10
+ name: "workspace-tools",
11
+ entry: "./workspace/workspace-tools.ts",
12
+ });
9
13
  const spriteName = spriteTools.name;
10
14
  const spriteDefinition = spriteTools;
11
15
  void spriteName;
12
16
  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
- });
17
+ const workspaceName = workspaceTools.name;
18
+ void workspaceName;
19
19
  // @ts-expect-error Deno Module entry remains required at authoring time
20
20
  defineDenoModule({
21
21
  name: "missing-entry",
@@ -29,9 +29,6 @@ defineDenoModule({
29
29
  entry: 42,
30
30
  usage: 'import "non-string-entry";',
31
31
  });
32
- function workspaceInputHasPublicType() {
33
- return { path: "C:\\workspace\\project" };
34
- }
35
32
  async function typedOnToolNameFromCreateSession() {
36
33
  const pedelec = new Pedelec();
37
34
  const session = await pedelec.createSession({
@@ -126,10 +123,37 @@ async function noSkillsFallsBackToString() {
126
123
  const pedelec = new Pedelec();
127
124
  const session = await pedelec.createSession({
128
125
  provider: "codex",
129
- workspace: { path: "C:\\workspace\\project" },
130
126
  });
131
- const typedWorkspace = workspaceInputHasPublicType();
132
- void typedWorkspace;
127
+ session.workspace.run("console.log('workspace')", { denoModules: [workspaceTools] });
128
+ session.workspace.run("console.log('workspace')", { denoModules: [spriteTools] });
129
+ await pedelec.createSession({
130
+ provider: "codex",
131
+ skills: { guidance: "Use modules.", tools: [],
132
+ // @ts-expect-error Workspace-only declarations do not satisfy Session metadata requirements
133
+ denoModules: [workspaceTools] },
134
+ });
135
+ const descriptionOnly = defineDenoModule({
136
+ name: "description-only",
137
+ description: "Missing usage.",
138
+ entry: "./agent/description-only.ts",
139
+ });
140
+ await pedelec.createSession({
141
+ provider: "codex",
142
+ skills: { guidance: "Use modules.", tools: [],
143
+ // @ts-expect-error Session metadata requires usage as well as description
144
+ denoModules: [descriptionOnly] },
145
+ });
146
+ const usageOnly = defineDenoModule({
147
+ name: "usage-only",
148
+ entry: "./agent/usage-only.ts",
149
+ usage: 'import "usage-only";',
150
+ });
151
+ await pedelec.createSession({
152
+ provider: "codex",
153
+ skills: { guidance: "Use modules.", tools: [],
154
+ // @ts-expect-error Session metadata requires description as well as usage
155
+ denoModules: [usageOnly] },
156
+ });
133
157
  session.onTool((name) => {
134
158
  const anyString = name;
135
159
  return anyString;
@@ -192,14 +216,32 @@ async function availabilityHasPublicType() {
192
216
  void availability;
193
217
  void promise;
194
218
  }
195
- async function workspaceFolderPickerHasPublicType() {
219
+ async function workspacePublicTypeContract() {
196
220
  const pedelec = new Pedelec();
197
- const result = pedelec.workspaceFolderPicker();
198
- void result;
221
+ const maybeWorkspace = await pedelec.openWorkspace();
222
+ const workspace = await pedelec.openWorkspace("C:\\workspace\\project");
223
+ const files = workspace.listFiles();
224
+ const folders = workspace.listFolders("src");
225
+ const run = workspace.run("console.log('hello')", { timeoutMs: 1000 });
226
+ const session = await workspace.createSession({
227
+ skills: {
228
+ guidance: "Use tools.",
229
+ tools: [defineTool({ name: "workspace_tool", description: "Tool", argsSchema: { type: "object" } })],
230
+ },
231
+ });
232
+ session.workspace;
233
+ maybeWorkspace?.listFiles();
234
+ void files;
235
+ void folders;
236
+ void run;
237
+ // @ts-expect-error workspace is now selected through openWorkspace
238
+ pedelec.createSession({ workspace: { path: "C:\\workspace\\project" } });
239
+ // @ts-expect-error the removed picker API must not be public
240
+ pedelec.workspaceFolderPicker();
199
241
  }
200
242
  function directoryPickerIsRemoved() {
201
243
  const pedelec = new Pedelec();
202
- // @ts-expect-error directoryPicker was removed in favor of workspaceFolderPicker
244
+ // @ts-expect-error directoryPicker was removed in favor of openWorkspace
203
245
  pedelec.directoryPicker();
204
246
  }
205
247
  function publicSecurityTypesAreRestricted() {
@@ -222,7 +264,7 @@ void noSkillsFallsBackToString;
222
264
  void effortLevelPublicTypeContract;
223
265
  void listAssetsHasPublicTypes;
224
266
  void availabilityHasPublicType;
225
- void workspaceFolderPickerHasPublicType;
267
+ void workspacePublicTypeContract;
226
268
  void directoryPickerIsRemoved;
227
269
  void publicSecurityTypesAreRestricted;
228
270
  const baseContext = {
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "0.3.5";
1
+ export declare const SDK_VERSION = "0.4.1";
@@ -1,2 +1,2 @@
1
1
  // Generated from sdk/package.json. Do not edit manually.
2
- export const SDK_VERSION = "0.3.5";
2
+ export const SDK_VERSION = "0.4.1";
package/dist/vite.js CHANGED
@@ -980,7 +980,7 @@ function readDeclaration(node, sourceFile) {
980
980
  values.set(name, property.initializer);
981
981
  }
982
982
  const name = readStringField(values, "name", sourceFile, node);
983
- const description = readStringField(values, "description", sourceFile, node);
983
+ const description = readOptionalStringField(values, "description", sourceFile, node);
984
984
  if (values.has("code")) {
985
985
  throw declarationError(sourceFile, values.get("code"), "defineDenoModule() does not support inline code; use a static entry");
986
986
  }
@@ -993,7 +993,7 @@ function readDeclaration(node, sourceFile) {
993
993
  const entry = readStaticString(entryExpression);
994
994
  if (entry === null)
995
995
  throw declarationError(sourceFile, entryExpression, "defineDenoModule() entry must be a string literal");
996
- const usage = readStringField(values, "usage", sourceFile, node);
996
+ const usage = readOptionalStringField(values, "usage", sourceFile, node);
997
997
  return {
998
998
  name,
999
999
  description,
@@ -1007,6 +1007,16 @@ function readDeclaration(node, sourceFile) {
1007
1007
  entryEnd: entryExpression.end,
1008
1008
  };
1009
1009
  }
1010
+ function readOptionalStringField(values, field, sourceFile, node) {
1011
+ if (!values.has(field))
1012
+ return undefined;
1013
+ const expression = values.get(field);
1014
+ const value = readStaticString(expression);
1015
+ if (value === null) {
1016
+ throw declarationError(sourceFile, expression ?? node, `defineDenoModule() field must be a static string when provided: ${field}`);
1017
+ }
1018
+ return value;
1019
+ }
1010
1020
  function replaceSourceRange(source, start, end, replacement) {
1011
1021
  return source.slice(0, start) + replacement + source.slice(end);
1012
1022
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaoruisaac/pedelec",
3
- "version": "0.3.5",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -37,7 +37,7 @@
37
37
  "prepublishOnly": "npm run test && npm run type-test && npm run build"
38
38
  },
39
39
  "dependencies": {
40
- "typescript": "^5.8.3"
40
+ "typescript": "5.9.3"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "vite": "^8.0.0"
@@ -48,8 +48,8 @@
48
48
  }
49
49
  },
50
50
  "devDependencies": {
51
- "@types/node": "^24.0.0",
52
- "vite": "^8.0.0",
53
- "vitest": "^4.1.8"
51
+ "@types/node": "24.13.6",
52
+ "vite": "8.3.0",
53
+ "vitest": "4.1.11"
54
54
  }
55
55
  }