@kaoruisaac/pedelec 0.3.4 → 0.4.0

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
 
@@ -318,19 +361,28 @@ const session = await pedelec.createSession({
318
361
  const defaultSession = await pedelec.createSession({ effortLevel: "low" });
319
362
  ```
320
363
 
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:
364
+ 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
365
 
323
366
  ```ts
324
- const specializedSession = await pedelec.createSession({
367
+ const profileSession = await pedelec.createSession({
325
368
  provider: "codex",
326
369
  effortLevel: "high",
370
+ });
371
+ ```
372
+
373
+ 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`:
374
+
375
+ ```ts
376
+ const modelSession = await pedelec.createSession({
377
+ provider: "codex",
327
378
  model: "gpt-x",
379
+ effort: "max",
328
380
  });
329
381
  ```
330
382
 
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.
383
+ 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.
332
384
 
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.
385
+ 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.
334
386
 
335
387
  Currently supported provider codes in the SDK:
336
388
 
@@ -343,6 +395,6 @@ Currently supported provider codes in the SDK:
343
395
  | Claude Code | `claude` |
344
396
  | Ollama | `ollama` |
345
397
 
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.
398
+ 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.
347
399
 
348
400
  `getSettings()` returns only `{ defaultProvider }`. It never exposes provider settings, effort arguments, credentials, or model names.
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;
@@ -17,11 +18,15 @@ export type Asset = {
17
18
  };
18
19
  export type DenoModuleDefinition<TName extends string = string> = {
19
20
  name: TName;
20
- description: string;
21
+ description?: string;
21
22
  entry: string;
22
- usage: string;
23
+ usage?: string;
23
24
  preferStdinExecution?: boolean;
24
25
  };
26
+ type AgentDenoModuleDefinition<TName extends string = string> = DenoModuleDefinition<TName> & {
27
+ description: string;
28
+ usage: string;
29
+ };
25
30
  export type ToolArgsSchemaMeta<TDefault extends JsonValue = JsonValue> = {
26
31
  description?: string;
27
32
  default?: TDefault;
@@ -78,7 +83,7 @@ export type ToolDefinition<TArgs = unknown, TResult = unknown, TName extends str
78
83
  export type SkillsInput<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
79
84
  guidance: string;
80
85
  tools: TTools;
81
- denoModules?: readonly DenoModuleDefinition[];
86
+ denoModules?: readonly AgentDenoModuleDefinition[];
82
87
  };
83
88
  export type ToolNameOf<TTools extends readonly ToolDefinition[]> = Extract<TTools[number]["name"], string>;
84
89
  export type SerializableToolManifest = {
@@ -98,31 +103,36 @@ export type SerializableSkillsManifest = {
98
103
  tools: SerializableToolManifest[];
99
104
  denoModules?: SerializableDenoModuleManifest[];
100
105
  };
101
- export type CreateSessionWorkspaceInput = {
102
- path: string;
106
+ export type WorkspaceRunOptions = {
107
+ timeoutMs?: number;
108
+ denoModules?: readonly DenoModuleDefinition[];
103
109
  };
104
- export interface WorkspaceFolderPickerResult {
105
- path: string;
106
- isEmptyFolder: boolean;
107
- hasWorkspaceConfig: boolean;
108
- }
109
- type CreateSessionInputWithProvider<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
110
- provider: ProviderCode;
111
- model?: string;
112
- effortLevel?: EffortLevel;
110
+ export type WorkspaceRunResult = {
111
+ exitCode: number;
112
+ stdout: string;
113
+ stderr: string;
114
+ stdoutTruncated: boolean;
115
+ stderrTruncated: boolean;
116
+ };
117
+ type CreateSessionSharedInput<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
113
118
  skills?: SkillsInput<TTools>;
114
- workspace?: CreateSessionWorkspaceInput;
115
119
  autoEndOnDisconnect?: boolean;
116
120
  };
117
- type CreateSessionInputWithDefaults<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
118
- provider?: undefined;
119
- model?: string;
121
+ type ProfileSessionConfiguration = {
122
+ model?: never;
123
+ effort?: never;
120
124
  effortLevel?: EffortLevel;
121
- skills?: SkillsInput<TTools>;
122
- workspace?: CreateSessionWorkspaceInput;
123
- autoEndOnDisconnect?: boolean;
124
125
  };
125
- export type CreateSessionInput<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = CreateSessionInputWithProvider<TTools> | CreateSessionInputWithDefaults<TTools>;
126
+ type ExplicitModelSessionConfiguration = {
127
+ model: string;
128
+ effort?: Effort;
129
+ effortLevel?: never;
130
+ };
131
+ export type CreateSessionInput<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = (CreateSessionSharedInput<TTools> & {
132
+ provider?: ProviderCode;
133
+ } & ProfileSessionConfiguration) | (CreateSessionSharedInput<TTools> & {
134
+ provider?: ProviderCode;
135
+ } & ExplicitModelSessionConfiguration);
126
136
  export type ProviderInfo = {
127
137
  name: string;
128
138
  code: ProviderCode;
@@ -304,7 +314,29 @@ type ErrorHandler = (error: PedelecError, ctx: ErrorEventContext) => void;
304
314
  type StatusHandler = (status: PedelecSessionStatus, ctx: StatusEventContext) => void;
305
315
  type EndedHandler = (ctx: EndedEventContext) => void;
306
316
  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>;
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
+ }
308
340
  export declare class Pedelec {
309
341
  private readonly pageWindow;
310
342
  private readonly channelId;
@@ -319,16 +351,25 @@ export declare class Pedelec {
319
351
  constructor(options?: PedelecOptions);
320
352
  createSession(): Promise<PedelecSession<string>>;
321
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>>>;
322
356
  private uploadDenoModuleArtifact;
357
+ /** @internal */
358
+ uploadWorkspaceDenoModuleArtifact(workspaceId: string, module: PreparedWorkspaceDenoModule): Promise<void>;
359
+ private uploadDenoModuleArtifactWithTicket;
323
360
  private abortSessionSetup;
324
361
  private completeSessionSetup;
325
- workspaceFolderPicker(): Promise<WorkspaceFolderPickerResult | null>;
362
+ openWorkspace(): Promise<PedelecWorkspace | null>;
363
+ openWorkspace(path: string): Promise<PedelecWorkspace>;
326
364
  listProviders(): Promise<ProviderInfo[]>;
327
365
  getSettings(): Promise<PedelecSettings>;
328
366
  getApprovalStatus(): Promise<ApprovalStatus>;
329
367
  resumeSession(sessionId: string): Promise<PedelecSession<string>>;
330
368
  request<T>(type: string, payload?: Record<string, unknown>): Promise<T>;
331
- private requestWithOptions;
369
+ /** @internal */
370
+ requestWithOptions<T>(type: string, payload?: Record<string, unknown>, options?: {
371
+ timeoutMs?: number | null;
372
+ }): Promise<T>;
332
373
  unregisterSession(sessionId: string): void;
333
374
  /** @internal */
334
375
  registerExistingSession(session: PedelecSession<any>): void;
@@ -359,6 +400,7 @@ export declare class PedelecSession<TToolName extends string = string> {
359
400
  readonly sessionId: string;
360
401
  readonly provider: string;
361
402
  readonly effortLevel?: EffortLevel;
403
+ readonly workspace: PedelecWorkspace;
362
404
  readonly sessionCreatedAt: number;
363
405
  readonly usage: PedelecSessionUsage;
364
406
  private status;
@@ -382,7 +424,7 @@ export declare class PedelecSession<TToolName extends string = string> {
382
424
  private readonly recoveredToolCalls;
383
425
  private readonly handledToolRequestIds;
384
426
  private authoritativePendingToolRequest;
385
- 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);
386
428
  prepare(): Promise<void>;
387
429
  sendText(text: string): Promise<void>;
388
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,44 +338,61 @@ 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", {
218
348
  input: {
219
349
  provider: resolvedInput.provider,
220
350
  model: resolvedInput.model,
351
+ effort: resolvedInput.effort,
221
352
  effortLevel: resolvedInput.effortLevel,
222
353
  skills: resolvedInput.skills,
223
- workspace: resolvedInput.workspace,
354
+ ...(workspace ? { workspaceId: workspace.workspaceIdInternal } : {}),
224
355
  autoEndOnDisconnect: resolvedInput.autoEndOnDisconnect,
225
356
  },
226
357
  });
227
- if (!result.sessionId) {
228
- 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
+ });
229
364
  }
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);
365
+ if (resolvedInput.model !== undefined && normalizedResult.explicitModelConfigApplied !== true) {
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" });
367
+ await this.abortSessionSetup(normalizedResult.sessionId);
233
368
  throw error;
234
369
  }
235
370
  try {
236
371
  for (const module of resolvedInput.denoModules) {
237
- await this.uploadDenoModuleArtifact(result.sessionId, module);
372
+ await this.uploadDenoModuleArtifact(normalizedResult.sessionId, module);
238
373
  }
239
374
  if (resolvedInput.denoModules.length > 0) {
240
- await this.completeSessionSetup(result.sessionId);
375
+ await this.completeSessionSetup(normalizedResult.sessionId);
241
376
  }
242
377
  }
243
378
  catch (error) {
244
379
  // The thread exists at this point but is not exposed through the SDK
245
380
  // session registry yet. Keep the setup failure as the caller-visible
246
381
  // error even if the best-effort cleanup transport also fails.
247
- await this.abortSessionSetup(result.sessionId);
382
+ await this.abortSessionSetup(normalizedResult.sessionId);
248
383
  throw normalizeError(error, "DENO_MODULE_UPLOAD_FAILED", "Deno Module setup failed");
249
384
  }
250
- 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);
251
387
  }
252
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) {
253
396
  const envelope = JSON.stringify({
254
397
  version: 1,
255
398
  format: "esm",
@@ -261,9 +404,8 @@ export class Pedelec {
261
404
  if (expectedSizeBytes <= 0 || expectedSizeBytes > MAX_ASSET_UPLOAD_BYTES) {
262
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 });
263
406
  }
264
- const ticket = await this.request("create_deno_module_upload", {
265
- sessionId,
266
- moduleName: module.name,
407
+ const ticket = await this.request(ticketType, {
408
+ ...ticketPayload,
267
409
  expectedSizeBytes,
268
410
  });
269
411
  if (!isPlainObject(ticket) || typeof ticket.uploadUrl !== "string" || ticket.uploadUrl.length === 0 ||
@@ -315,9 +457,13 @@ export class Pedelec {
315
457
  async completeSessionSetup(sessionId) {
316
458
  await this.request("complete_session_setup", { sessionId });
317
459
  }
318
- async workspaceFolderPicker() {
319
- const result = await this.requestWithOptions("pick_workspace_folder", {}, { timeoutMs: null });
320
- 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);
321
467
  }
322
468
  async listProviders() {
323
469
  const providers = normalizeProviderInfoList(await this.request("list_providers"));
@@ -357,16 +503,23 @@ export class Pedelec {
357
503
  const existing = this.sessions.get(sessionId);
358
504
  const session = existing && !existing.isTransportDetached()
359
505
  ? existing
360
- : this.registerSession(sessionId, "", undefined, new Map(), false);
506
+ : this.registerSession(sessionId, "", undefined, new Map(), false, new PedelecWorkspace(this, "", null));
361
507
  try {
362
508
  const result = await this.request("resume_session", {
363
509
  sessionId,
364
510
  });
365
- if (!result.sessionId) {
366
- 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");
367
514
  }
368
- if (typeof result.autoEndOnDisconnect === "boolean") {
369
- session.setAutoEndOnDisconnect(result.autoEndOnDisconnect);
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);
520
+ }
521
+ if (typeof normalizedResult.autoEndOnDisconnect === "boolean") {
522
+ session.setAutoEndOnDisconnect(normalizedResult.autoEndOnDisconnect);
370
523
  }
371
524
  return session;
372
525
  }
@@ -379,6 +532,7 @@ export class Pedelec {
379
532
  request(type, payload = {}) {
380
533
  return this.requestWithOptions(type, payload);
381
534
  }
535
+ /** @internal */
382
536
  requestWithOptions(type, payload = {}, options = {}) {
383
537
  let port;
384
538
  try {
@@ -393,7 +547,9 @@ export class Pedelec {
393
547
  type,
394
548
  requestId,
395
549
  ...payload,
396
- ...(type === "create_session" ? { callerSdkVersion: SDK_VERSION } : {}),
550
+ ...(type === "create_session" || type === "open_workspace"
551
+ ? { callerSdkVersion: SDK_VERSION }
552
+ : {}),
397
553
  };
398
554
  return new Promise((resolve, reject) => {
399
555
  const timeoutMs = options.timeoutMs === undefined ? this.bridgeTimeoutMs : options.timeoutMs;
@@ -506,22 +662,42 @@ export class Pedelec {
506
662
  const provider = typeof raw.provider === "string" ? raw.provider.trim() : "";
507
663
  const hasProvider = provider.length > 0;
508
664
  const model = normalizeCreateSessionModelInput(raw.model);
665
+ if (model !== undefined) {
666
+ if (raw.effortLevel !== undefined) {
667
+ throw makeError("INVALID_INPUT", "effortLevel cannot be combined with model");
668
+ }
669
+ const effort = normalizeCreateSessionEffortInput(raw.effort);
670
+ const autoEndOnDisconnect = raw.autoEndOnDisconnect !== false;
671
+ const normalizedSkills = normalizeSkillsInput(raw.skills);
672
+ if (!hasProvider) {
673
+ return this.resolveDefaultCreateSessionInput(model, effort, undefined, normalizedSkills, autoEndOnDisconnect);
674
+ }
675
+ return {
676
+ provider: provider,
677
+ model,
678
+ effort,
679
+ skills: normalizedSkills.manifest,
680
+ inlineToolHandlers: normalizedSkills.handlers,
681
+ denoModules: normalizedSkills.denoModules,
682
+ autoEndOnDisconnect,
683
+ };
684
+ }
685
+ if (raw.effort !== undefined) {
686
+ throw makeError("INVALID_INPUT", "effort requires model");
687
+ }
509
688
  const effortLevel = raw.effortLevel === undefined ? "default" : raw.effortLevel;
510
689
  if (!isEffortLevel(effortLevel)) {
511
690
  throw makeError("INVALID_INPUT", "effortLevel must be one of default, low, or high");
512
691
  }
513
692
  const autoEndOnDisconnect = raw.autoEndOnDisconnect !== false;
514
693
  const normalizedSkills = normalizeSkillsInput(raw.skills);
515
- const workspace = normalizeCreateSessionWorkspaceInput(raw.workspace);
516
694
  if (!hasProvider) {
517
- return this.resolveDefaultCreateSessionInput(model, effortLevel, normalizedSkills, workspace, autoEndOnDisconnect);
695
+ return this.resolveDefaultCreateSessionInput(undefined, undefined, effortLevel, normalizedSkills, autoEndOnDisconnect);
518
696
  }
519
697
  return {
520
698
  provider: provider,
521
- model,
522
699
  effortLevel,
523
700
  skills: normalizedSkills.manifest,
524
- workspace,
525
701
  inlineToolHandlers: normalizedSkills.handlers,
526
702
  denoModules: normalizedSkills.denoModules,
527
703
  autoEndOnDisconnect,
@@ -579,7 +755,7 @@ export class Pedelec {
579
755
  };
580
756
  }
581
757
  }
582
- async resolveDefaultCreateSessionInput(model, effortLevel, normalizedSkills, workspace, autoEndOnDisconnect) {
758
+ async resolveDefaultCreateSessionInput(model, effort, effortLevel, normalizedSkills, autoEndOnDisconnect) {
583
759
  const settings = await this.getSettings();
584
760
  if (!settings.defaultProvider) {
585
761
  throw makeError("DEFAULT_PROVIDER_NOT_SET", "Default provider is not set. Open the Pedelec desktop app Settings page and choose a default provider.");
@@ -588,9 +764,9 @@ export class Pedelec {
588
764
  return {
589
765
  provider: settings.defaultProvider,
590
766
  model,
767
+ effort,
591
768
  effortLevel,
592
769
  skills: normalizedSkills.manifest,
593
- workspace,
594
770
  inlineToolHandlers: normalizedSkills.handlers,
595
771
  denoModules: normalizedSkills.denoModules,
596
772
  autoEndOnDisconnect,
@@ -603,13 +779,13 @@ export class Pedelec {
603
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 });
604
780
  }
605
781
  }
606
- registerSession(sessionId, provider, effortLevel, inlineToolHandlers = new Map(), autoEndOnDisconnect = false) {
782
+ registerSession(sessionId, provider, effortLevel, inlineToolHandlers = new Map(), autoEndOnDisconnect = false, workspace) {
607
783
  const existing = this.sessions.get(sessionId);
608
784
  if (existing && !existing.isTransportDetached()) {
609
785
  existing.replaceInlineToolHandlers(inlineToolHandlers);
610
786
  return existing;
611
787
  }
612
- const session = new PedelecSession(this, sessionId, provider, effortLevel, inlineToolHandlers, autoEndOnDisconnect);
788
+ const session = new PedelecSession(this, sessionId, provider, workspace, effortLevel, inlineToolHandlers, autoEndOnDisconnect);
613
789
  this.sessions.set(sessionId, session);
614
790
  return session;
615
791
  }
@@ -740,21 +916,6 @@ export class Pedelec {
740
916
  }
741
917
  }
742
918
  }
743
- function normalizeCreateSessionWorkspaceInput(value) {
744
- if (value === undefined)
745
- return undefined;
746
- if (!value || typeof value !== "object" || Array.isArray(value)) {
747
- throw makeError("INVALID_INPUT", "workspace must be an object");
748
- }
749
- const path = value.path;
750
- if (typeof path !== "string") {
751
- throw makeError("INVALID_INPUT", "workspace.path must be a string");
752
- }
753
- if (path.trim().length === 0) {
754
- throw makeError("INVALID_INPUT", "workspace.path must not be empty");
755
- }
756
- return { path };
757
- }
758
919
  function normalizeCreateSessionModelInput(value) {
759
920
  if (value === undefined)
760
921
  return undefined;
@@ -763,11 +924,20 @@ function normalizeCreateSessionModelInput(value) {
763
924
  }
764
925
  return value.trim();
765
926
  }
927
+ function normalizeCreateSessionEffortInput(value) {
928
+ if (value === undefined)
929
+ return undefined;
930
+ if (!isEffort(value)) {
931
+ throw makeError("INVALID_INPUT", "effort must be one of low, medium, high, xhigh, or max");
932
+ }
933
+ return value;
934
+ }
766
935
  export class PedelecSession {
767
936
  client;
768
937
  sessionId;
769
938
  provider;
770
939
  effortLevel;
940
+ workspace;
771
941
  sessionCreatedAt = Date.now();
772
942
  usage = { totalTokens: undefined };
773
943
  status = "idle";
@@ -791,11 +961,12 @@ export class PedelecSession {
791
961
  recoveredToolCalls = new Map();
792
962
  handledToolRequestIds = new Set();
793
963
  authoritativePendingToolRequest = undefined;
794
- constructor(client, sessionId, provider, effortLevel, inlineToolHandlers = new Map(), autoEndOnDisconnect = false) {
964
+ constructor(client, sessionId, provider, workspace, effortLevel, inlineToolHandlers = new Map(), autoEndOnDisconnect = false) {
795
965
  this.client = client;
796
966
  this.sessionId = sessionId;
797
967
  this.provider = provider;
798
968
  this.effortLevel = effortLevel;
969
+ this.workspace = workspace;
799
970
  this.inlineToolHandlers = new Map(inlineToolHandlers);
800
971
  this.autoEndOnDisconnect = autoEndOnDisconnect;
801
972
  }
@@ -1569,6 +1740,9 @@ function isProviderCode(value) {
1569
1740
  function isEffortLevel(value) {
1570
1741
  return value === "default" || value === "low" || value === "high";
1571
1742
  }
1743
+ function isEffort(value) {
1744
+ return value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max";
1745
+ }
1572
1746
  function isPlainObject(value) {
1573
1747
  return !!value && typeof value === "object" && !Array.isArray(value);
1574
1748
  }
@@ -1578,21 +1752,86 @@ function normalizePedelecSettings(value) {
1578
1752
  }
1579
1753
  return { defaultProvider: value.defaultProvider };
1580
1754
  }
1581
- function normalizeWorkspaceFolderPickerResponse(value) {
1582
- if (!isPlainObject(value) || !("path" in value)) {
1583
- 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`);
1759
+ }
1760
+ return {
1761
+ workspaceId: value.workspaceId,
1762
+ path: value.path,
1763
+ };
1764
+ }
1765
+ function normalizeOpenWorkspaceResponse(value) {
1766
+ if (value === null)
1767
+ return null;
1768
+ if (!isPlainObject(value) || !("workspace" in value)) {
1769
+ throw makeError("SDK_PROTOCOL_ERROR", "open_workspace response had an invalid shape");
1584
1770
  }
1585
- const path = value.path;
1586
- if (path === null)
1771
+ if (value.workspace === null)
1587
1772
  return null;
1588
- if (typeof path !== "string" || path.length === 0 ||
1589
- typeof value.isEmptyFolder !== "boolean" || typeof value.hasWorkspaceConfig !== "boolean") {
1590
- throw makeError("SDK_PROTOCOL_ERROR", "pick_workspace_folder response had an invalid shape");
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");
1591
1828
  }
1592
1829
  return {
1593
- path,
1594
- isEmptyFolder: value.isEmptyFolder,
1595
- hasWorkspaceConfig: value.hasWorkspaceConfig,
1830
+ exitCode: value.exitCode,
1831
+ stdout: value.stdout,
1832
+ stderr: value.stderr,
1833
+ stdoutTruncated: value.stdoutTruncated,
1834
+ stderrTruncated: value.stderrTruncated,
1596
1835
  };
1597
1836
  }
1598
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;
@@ -147,7 +171,17 @@ async function effortLevelPublicTypeContract() {
147
171
  // @ts-expect-error the removed sandbox picker is not part of the public contract
148
172
  pedelec.sandboxFolderPicker();
149
173
  await pedelec.createSession({ model: "gpt-5" });
174
+ await pedelec.createSession({ provider: "codex", model: "gpt-5", effort: "max" });
175
+ const explicitEffort = "xhigh";
176
+ await pedelec.createSession({ provider: "codex", model: "gpt-5", effort: explicitEffort });
177
+ // @ts-expect-error explicit model mode cannot select a Desktop effort profile
150
178
  await pedelec.createSession({ provider: "codex", model: "gpt-5", effortLevel: "high" });
179
+ // @ts-expect-error explicit effort requires an explicit model
180
+ await pedelec.createSession({ provider: "codex", effort: "high" });
181
+ // @ts-expect-error explicit model, effort, and Desktop profile are mutually exclusive
182
+ await pedelec.createSession({ model: "gpt-5", effort: "max", effortLevel: "low" });
183
+ // @ts-expect-error unsupported provider-native effort value
184
+ await pedelec.createSession({ model: "gpt-5", effort: "default" });
151
185
  // @ts-expect-error session no longer exposes provider model
152
186
  session.model;
153
187
  // @ts-expect-error unsupported effort level
@@ -182,14 +216,32 @@ async function availabilityHasPublicType() {
182
216
  void availability;
183
217
  void promise;
184
218
  }
185
- async function workspaceFolderPickerHasPublicType() {
219
+ async function workspacePublicTypeContract() {
186
220
  const pedelec = new Pedelec();
187
- const result = pedelec.workspaceFolderPicker();
188
- 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();
189
241
  }
190
242
  function directoryPickerIsRemoved() {
191
243
  const pedelec = new Pedelec();
192
- // @ts-expect-error directoryPicker was removed in favor of workspaceFolderPicker
244
+ // @ts-expect-error directoryPicker was removed in favor of openWorkspace
193
245
  pedelec.directoryPicker();
194
246
  }
195
247
  function publicSecurityTypesAreRestricted() {
@@ -212,7 +264,7 @@ void noSkillsFallsBackToString;
212
264
  void effortLevelPublicTypeContract;
213
265
  void listAssetsHasPublicTypes;
214
266
  void availabilityHasPublicType;
215
- void workspaceFolderPickerHasPublicType;
267
+ void workspacePublicTypeContract;
216
268
  void directoryPickerIsRemoved;
217
269
  void publicSecurityTypesAreRestricted;
218
270
  const baseContext = {
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "0.3.4";
1
+ export declare const SDK_VERSION = "0.4.0";
@@ -1,2 +1,2 @@
1
1
  // Generated from sdk/package.json. Do not edit manually.
2
- export const SDK_VERSION = "0.3.4";
2
+ export const SDK_VERSION = "0.4.0";
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.4",
3
+ "version": "0.4.0",
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
  }