@kaoruisaac/pedelec 0.1.25 → 0.2.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
@@ -88,7 +88,7 @@ const pedelec = new Pedelec();
88
88
 
89
89
  const session = await pedelec.createSession({
90
90
  provider: "codex",
91
- model: "gpt-5",
91
+ effortLevel: "high",
92
92
  skills: {
93
93
  guidance: "Use get_current_page when you need browser page context.",
94
94
  tools: [
@@ -145,6 +145,19 @@ const result = await session.readAsset("/model.glb", "file");
145
145
 
146
146
  Public asset paths use `/...` with `assets/` as their implicit root; nested paths such as `/results/model.glb` are supported up to 100 MiB.
147
147
 
148
+ ## Sandbox workspace
149
+
150
+ By default, each session receives a temporary Desktop-managed sandbox. To use an application-owned workspace that can persist across sessions, pass an absolute path:
151
+
152
+ ```ts
153
+ const session = await pedelec.createSession({
154
+ provider: "codex",
155
+ sandbox: { path: "C:\\workspace\\project-a" },
156
+ });
157
+ ```
158
+
159
+ Pedelec creates missing `skills/`, `assets/`, `logs/`, and `tmp/` directories and preserves existing files. 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 sandbox root.
160
+
148
161
  ---
149
162
 
150
163
  ## Creating a Session
@@ -174,31 +187,31 @@ if (availability.available) startUi();
174
187
 
175
188
  An unavailable extension may be disconnected rather than absent. `desktop.launchAttempted` means the settings probe was sent, not that Desktop was confirmed to have launched. Invalid settings responses also count as Desktop unavailable in this probe.
176
189
 
177
- ### Specifying Provider and Model
190
+ ### Selecting Provider and Effort
178
191
 
179
192
  ```ts
180
193
  const session = await pedelec.createSession({
181
- provider: "opencode",
182
- model: "ollama/qwen2.5-coder:14b",
194
+ provider: "codex",
195
+ effortLevel: "high",
183
196
  });
197
+
198
+ // Or let Desktop Settings choose the provider.
199
+ const defaultSession = await pedelec.createSession({ effortLevel: "low" });
184
200
  ```
185
201
 
202
+ The SDK uses the provider-independent effort levels `default`, `low`, and `high`. An omitted level means `default`. Desktop users map each provider's profiles to concrete model and native effort arguments; that mapping is not exposed through the SDK.
203
+
186
204
  Currently supported provider codes in the SDK:
187
205
 
188
- | Provider | Code | Example model |
189
- | --- | --- | --- |
190
- | Codex | `codex` | `gpt-5` |
191
- | Antigravity | `antigravity` | Any model ID supported by the provider |
192
- | OpenCode | `opencode` | `ollama/qwen2.5-coder:14b` |
193
- | Cursor | `cursor` | `gpt-5` |
194
- | Claude Code | `claude` | `sonnet` |
195
- | Ollama | `ollama` | `qwen3-14b-32k:latest` |
206
+ | Provider | Code |
207
+ | --- | --- |
208
+ | Codex | `codex` |
209
+ | Antigravity | `antigravity` |
210
+ | OpenCode | `opencode` |
211
+ | Cursor | `cursor` |
212
+ | Claude Code | `claude` |
213
+ | Ollama | `ollama` |
196
214
 
197
- Ollama sessions are executed by the `pedelec-agent` binary bundled with the Desktop App, not by the `ollama` CLI. You still need to start the local Ollama server yourself and specify a model explicitly or configure `defaultModels.ollama` in Settings:
215
+ Ollama sessions are executed by the bundled `pedelec-agent`. The selected Ollama effort profile must contain a model in Desktop Settings; otherwise Core returns `MODEL_REQUIRED`. Low and high profiles are optional, but there is no fallback between profiles.
198
216
 
199
- ```ts
200
- const session = await pedelec.createSession({
201
- provider: "ollama",
202
- model: "qwen3-14b-32k:latest",
203
- });
204
- ```
217
+ `getSettings()` returns only `{ defaultProvider }`. It never exposes provider settings, effort arguments, credentials, or model names.
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export type PedelecOptions = {
2
2
  bridgeTimeoutMs?: number;
3
3
  };
4
4
  export type ProviderCode = "codex" | "antigravity" | "opencode" | "cursor" | "claude" | "ollama";
5
+ export type EffortLevel = "default" | "low" | "high";
5
6
  export type JsonPrimitive = string | number | boolean | null;
6
7
  export type JsonValue = JsonPrimitive | JsonValue[] | {
7
8
  [key: string]: JsonValue;
@@ -82,16 +83,21 @@ export type SerializableSkillsManifest = {
82
83
  guidance: string;
83
84
  tools: SerializableToolManifest[];
84
85
  };
86
+ export type CreateSessionSandboxInput = {
87
+ path: string;
88
+ };
85
89
  type CreateSessionInputWithProvider<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
86
90
  provider: ProviderCode;
87
- model?: string;
91
+ effortLevel?: EffortLevel;
88
92
  skills?: SkillsInput<TTools>;
93
+ sandbox?: CreateSessionSandboxInput;
89
94
  autoEndOnDisconnect?: boolean;
90
95
  };
91
96
  type CreateSessionInputWithDefaults<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
92
97
  provider?: undefined;
93
- model?: never;
98
+ effortLevel?: EffortLevel;
94
99
  skills?: SkillsInput<TTools>;
100
+ sandbox?: CreateSessionSandboxInput;
95
101
  autoEndOnDisconnect?: boolean;
96
102
  };
97
103
  export type CreateSessionInput<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = CreateSessionInputWithProvider<TTools> | CreateSessionInputWithDefaults<TTools>;
@@ -103,7 +109,6 @@ export type ProviderInfo = {
103
109
  };
104
110
  export type PedelecSettings = {
105
111
  defaultProvider: ProviderCode | null;
106
- defaultModels: Partial<Record<ProviderCode, string>>;
107
112
  };
108
113
  export type ApprovalStatus = {
109
114
  installed: boolean;
@@ -135,7 +140,7 @@ export type PedelecSessionStatus = "idle" | "running" | "waiting_tool_result" |
135
140
  export type PedelecEventContext = {
136
141
  sessionId: string;
137
142
  provider: string;
138
- model?: string;
143
+ effortLevel?: EffortLevel;
139
144
  sessionCreatedAt: number;
140
145
  eventReceivedAt?: number;
141
146
  eventEmittedAt: number;
@@ -224,7 +229,6 @@ export declare class Pedelec {
224
229
  private readonly sessions;
225
230
  private readonly lastSeqBySession;
226
231
  private nextRequestNumber;
227
- private disconnectedError;
228
232
  private port;
229
233
  constructor(options?: PedelecOptions);
230
234
  createSession(): Promise<PedelecSession<string>>;
@@ -238,21 +242,22 @@ export declare class Pedelec {
238
242
  private resolveCreateSessionInput;
239
243
  checkAvailability(): Promise<PedelecAvailability>;
240
244
  private resolveDefaultCreateSessionInput;
241
- private resolveProviderOnlyCreateSessionInput;
242
245
  private assertDefaultProviderAvailable;
243
246
  private registerSession;
247
+ private ensureExtensionPort;
244
248
  private connectExtension;
245
249
  private handlePortMessage;
246
250
  private handleResponse;
247
251
  private isNewEvent;
248
252
  private handleDisconnect;
249
253
  private broadcastError;
254
+ private invalidateSessions;
250
255
  }
251
256
  export declare class PedelecSession<TToolName extends string = string> {
252
257
  private readonly client;
253
258
  readonly sessionId: string;
254
259
  readonly provider: string;
255
- readonly model?: string;
260
+ readonly effortLevel?: EffortLevel;
256
261
  readonly sessionCreatedAt: number;
257
262
  private status;
258
263
  private pendingSend;
@@ -262,6 +267,7 @@ export declare class PedelecSession<TToolName extends string = string> {
262
267
  private ending;
263
268
  private prepared;
264
269
  private activeTurn;
270
+ private transportDetached;
265
271
  private genericToolHandler;
266
272
  private inlineToolHandlers;
267
273
  private readonly namedToolHandlers;
@@ -269,7 +275,7 @@ export declare class PedelecSession<TToolName extends string = string> {
269
275
  private readonly errorHandlers;
270
276
  private readonly statusHandlers;
271
277
  private readonly endedHandlers;
272
- constructor(client: Pedelec, sessionId: string, provider: string, model?: string, inlineToolHandlers?: Map<string, ToolSpecificHandler>);
278
+ constructor(client: Pedelec, sessionId: string, provider: string, effortLevel?: EffortLevel, inlineToolHandlers?: Map<string, ToolSpecificHandler>);
273
279
  prepare(): Promise<void>;
274
280
  sendText(text: string): Promise<void>;
275
281
  onChat(handler: ChatHandler): () => void;
@@ -288,6 +294,10 @@ export declare class PedelecSession<TToolName extends string = string> {
288
294
  end(): Promise<void>;
289
295
  handleEvent(event: SessionEvent, meta?: EventDispatchMeta): void;
290
296
  replaceInlineToolHandlers(handlers: Map<string, ToolSpecificHandler>): void;
297
+ /** @internal */
298
+ isTransportDetached(): boolean;
299
+ /** @internal */
300
+ handleTransportDisconnect(error: PedelecError): void;
291
301
  private handleToolCall;
292
302
  private resolvePendingSend;
293
303
  private resolvePendingPrepare;
package/dist/index.js CHANGED
@@ -122,14 +122,12 @@ export class Pedelec {
122
122
  sessions = new Map();
123
123
  lastSeqBySession = new Map();
124
124
  nextRequestNumber = 1;
125
- disconnectedError = null;
126
125
  port = null;
127
126
  constructor(options = {}) {
128
127
  this.pageWindow = typeof window === "undefined" ? null : window;
129
128
  this.channelId = createChannelId();
130
129
  this.bridgeTimeoutMs = Math.max(1, options.bridgeTimeoutMs ?? DEFAULT_BRIDGE_TIMEOUT_MS);
131
130
  if (!this.pageWindow) {
132
- this.disconnectedError = makeError("EXTENSION_UNAVAILABLE", "Pedelec SDK must run in a browser page context.");
133
131
  return;
134
132
  }
135
133
  this.connectExtension();
@@ -140,15 +138,16 @@ export class Pedelec {
140
138
  const result = await this.request("create_session", {
141
139
  input: {
142
140
  provider: resolvedInput.provider,
143
- model: resolvedInput.model,
141
+ effortLevel: resolvedInput.effortLevel,
144
142
  skills: resolvedInput.skills,
143
+ sandbox: resolvedInput.sandbox,
145
144
  autoEndOnDisconnect: resolvedInput.autoEndOnDisconnect,
146
145
  },
147
146
  });
148
147
  if (!result.sessionId) {
149
148
  throw makeError("SDK_PROTOCOL_ERROR", "create_session response did not include sessionId");
150
149
  }
151
- return this.registerSession(result.sessionId, resolvedInput.provider, resolvedInput.model, resolvedInput.inlineToolHandlers);
150
+ return this.registerSession(result.sessionId, resolvedInput.provider, resolvedInput.effortLevel, resolvedInput.inlineToolHandlers);
152
151
  }
153
152
  async listProviders() {
154
153
  return normalizeProviderInfoList(await this.request("list_providers"));
@@ -157,14 +156,6 @@ export class Pedelec {
157
156
  return normalizePedelecSettings(await this.request("get_settings"));
158
157
  }
159
158
  async getApprovalStatus() {
160
- if (this.disconnectedError) {
161
- return {
162
- installed: false,
163
- approved: false,
164
- origin: getCurrentOrigin(this.pageWindow),
165
- appConnected: false,
166
- };
167
- }
168
159
  try {
169
160
  const result = await this.request("get_approval_status");
170
161
  return result;
@@ -195,11 +186,12 @@ export class Pedelec {
195
186
  return this.registerSession(result.sessionId, "", undefined);
196
187
  }
197
188
  request(type, payload = {}) {
198
- if (this.disconnectedError) {
199
- return Promise.reject(this.disconnectedError);
189
+ let port;
190
+ try {
191
+ port = this.ensureExtensionPort();
200
192
  }
201
- if (!this.port) {
202
- return Promise.reject(makeError("EXTENSION_UNAVAILABLE", "Pedelec extension is unavailable."));
193
+ catch (err) {
194
+ return Promise.reject(normalizeError(err, "EXTENSION_UNAVAILABLE", "Pedelec extension is unavailable."));
203
195
  }
204
196
  const requestId = `sdk_${Date.now()}_${this.nextRequestNumber++}`;
205
197
  const message = { channelId: this.channelId, type, requestId, ...payload };
@@ -212,17 +204,22 @@ export class Pedelec {
212
204
  }));
213
205
  }, this.bridgeTimeoutMs);
214
206
  this.pendingRequests.set(requestId, {
207
+ port,
215
208
  resolve: (value) => resolve(value),
216
209
  reject,
217
210
  timeoutId,
218
211
  });
219
212
  try {
220
- this.port?.postMessage(message);
213
+ port.postMessage(message);
221
214
  }
222
215
  catch (err) {
223
216
  this.pendingRequests.delete(requestId);
224
217
  clearTimeout(timeoutId);
225
- reject(normalizeError(err, "EXTENSION_DISCONNECTED", "Pedelec extension disconnected."));
218
+ const error = normalizeError(err, "EXTENSION_DISCONNECTED", "Pedelec extension disconnected.");
219
+ if (this.port === port) {
220
+ this.handleDisconnect(port, error);
221
+ }
222
+ reject(error);
226
223
  }
227
224
  });
228
225
  }
@@ -234,24 +231,24 @@ export class Pedelec {
234
231
  const raw = (input ?? {});
235
232
  const provider = typeof raw.provider === "string" ? raw.provider.trim() : "";
236
233
  const hasProvider = provider.length > 0;
237
- const hasModel = raw.model !== undefined;
234
+ if (raw.model !== undefined) {
235
+ throw makeError("INVALID_INPUT", "model is no longer supported; configure provider effort profiles in Desktop Settings");
236
+ }
237
+ const effortLevel = raw.effortLevel === undefined ? "default" : raw.effortLevel;
238
+ if (!isEffortLevel(effortLevel)) {
239
+ throw makeError("INVALID_INPUT", "effortLevel must be one of default, low, or high");
240
+ }
238
241
  const autoEndOnDisconnect = raw.autoEndOnDisconnect !== false;
239
242
  const normalizedSkills = normalizeSkillsInput(raw.skills);
240
- if (!hasProvider && hasModel) {
241
- throw makeError("INVALID_INPUT", "model cannot be provided without provider");
242
- }
243
- const userModel = typeof raw.model === "string" ? raw.model : undefined;
243
+ const sandbox = normalizeCreateSessionSandboxInput(raw.sandbox);
244
244
  if (!hasProvider) {
245
- return this.resolveDefaultCreateSessionInput(normalizedSkills, autoEndOnDisconnect);
246
- }
247
- let model = userModel;
248
- if (model === undefined) {
249
- return this.resolveProviderOnlyCreateSessionInput(provider, normalizedSkills, autoEndOnDisconnect);
245
+ return this.resolveDefaultCreateSessionInput(effortLevel, normalizedSkills, sandbox, autoEndOnDisconnect);
250
246
  }
251
247
  return {
252
248
  provider: provider,
253
- model,
249
+ effortLevel,
254
250
  skills: normalizedSkills.manifest,
251
+ sandbox,
255
252
  inlineToolHandlers: normalizedSkills.handlers,
256
253
  autoEndOnDisconnect,
257
254
  };
@@ -308,7 +305,7 @@ export class Pedelec {
308
305
  };
309
306
  }
310
307
  }
311
- async resolveDefaultCreateSessionInput(normalizedSkills, autoEndOnDisconnect) {
308
+ async resolveDefaultCreateSessionInput(effortLevel, normalizedSkills, sandbox, autoEndOnDisconnect) {
312
309
  const settings = await this.getSettings();
313
310
  if (!settings.defaultProvider) {
314
311
  throw makeError("DEFAULT_PROVIDER_NOT_SET", "Default provider is not set. Open the Pedelec desktop app Settings page and choose a default provider.");
@@ -316,18 +313,9 @@ export class Pedelec {
316
313
  await this.assertDefaultProviderAvailable(settings.defaultProvider);
317
314
  return {
318
315
  provider: settings.defaultProvider,
319
- model: settings.defaultModels[settings.defaultProvider] ?? undefined,
320
- skills: normalizedSkills.manifest,
321
- inlineToolHandlers: normalizedSkills.handlers,
322
- autoEndOnDisconnect,
323
- };
324
- }
325
- async resolveProviderOnlyCreateSessionInput(provider, normalizedSkills, autoEndOnDisconnect) {
326
- const settings = await this.getSettings();
327
- return {
328
- provider,
329
- model: settings.defaultModels[provider] ?? undefined,
316
+ effortLevel,
330
317
  skills: normalizedSkills.manifest,
318
+ sandbox,
331
319
  inlineToolHandlers: normalizedSkills.handlers,
332
320
  autoEndOnDisconnect,
333
321
  };
@@ -339,33 +327,50 @@ export class Pedelec {
339
327
  throw makeError("DEFAULT_PROVIDER_UNAVAILABLE", "Default provider is not currently available. Open the Pedelec desktop app Settings page and choose an available provider.", { provider });
340
328
  }
341
329
  }
342
- registerSession(sessionId, provider, model, inlineToolHandlers = new Map()) {
330
+ registerSession(sessionId, provider, effortLevel, inlineToolHandlers = new Map()) {
343
331
  const existing = this.sessions.get(sessionId);
344
- if (existing) {
332
+ if (existing && !existing.isTransportDetached()) {
345
333
  existing.replaceInlineToolHandlers(inlineToolHandlers);
346
334
  return existing;
347
335
  }
348
- const session = new PedelecSession(this, sessionId, provider, model, inlineToolHandlers);
336
+ const session = new PedelecSession(this, sessionId, provider, effortLevel, inlineToolHandlers);
349
337
  this.sessions.set(sessionId, session);
350
338
  return session;
351
339
  }
352
- connectExtension() {
340
+ ensureExtensionPort() {
341
+ if (this.port)
342
+ return this.port;
343
+ if (!this.pageWindow) {
344
+ throw makeError("EXTENSION_UNAVAILABLE", "Pedelec SDK must run in a browser page context.");
345
+ }
353
346
  const runtime = globalThis.chrome?.runtime;
354
347
  if (!runtime?.connect) {
355
- this.disconnectedError = makeError("EXTENSION_UNAVAILABLE", "Pedelec extension is unavailable.");
356
- return;
348
+ throw makeError("EXTENSION_UNAVAILABLE", "Pedelec extension is unavailable.");
357
349
  }
358
350
  try {
359
- this.port = runtime.connect(PEDELEC_EXTENSION_ID, { name: SDK_EXTERNAL_PORT_NAME });
360
- this.port.onMessage.addListener((message) => this.handlePortMessage(message));
361
- this.port.onDisconnect.addListener(() => this.handleDisconnect());
351
+ const port = runtime.connect(PEDELEC_EXTENSION_ID, { name: SDK_EXTERNAL_PORT_NAME });
352
+ port.onMessage.addListener((message) => this.handlePortMessage(port, message));
353
+ port.onDisconnect.addListener(() => this.handleDisconnect(port));
354
+ this.port = port;
355
+ return port;
362
356
  }
363
357
  catch (err) {
364
- this.disconnectedError = normalizeError(err, "EXTENSION_UNAVAILABLE", "Pedelec extension is unavailable.");
365
358
  this.port = null;
359
+ throw normalizeError(err, "EXTENSION_UNAVAILABLE", "Pedelec extension is unavailable.");
360
+ }
361
+ }
362
+ connectExtension() {
363
+ try {
364
+ this.ensureExtensionPort();
365
+ }
366
+ catch (_err) {
367
+ // The first connection is eager for normal browser startup, but a failed
368
+ // attempt remains retryable through the next request.
366
369
  }
367
370
  }
368
- handlePortMessage(raw) {
371
+ handlePortMessage(port, raw) {
372
+ if (port !== this.port)
373
+ return;
369
374
  const message = raw;
370
375
  if (!message || typeof message !== "object")
371
376
  return;
@@ -411,29 +416,52 @@ export class Pedelec {
411
416
  this.lastSeqBySession.set(event.sessionId, event.seq);
412
417
  return true;
413
418
  }
414
- handleDisconnect() {
419
+ handleDisconnect(port, disconnectError) {
420
+ if (port !== this.port)
421
+ return;
415
422
  const runtime = globalThis.chrome?.runtime;
416
- const error = normalizeError(runtime?.lastError, "EXTENSION_DISCONNECTED", "Pedelec extension disconnected.");
417
- this.disconnectedError = error;
423
+ const error = normalizeError(disconnectError ?? runtime?.lastError, "EXTENSION_DISCONNECTED", "Pedelec extension disconnected.");
418
424
  this.port = null;
419
- for (const pending of this.pendingRequests.values()) {
425
+ this.invalidateSessions(error);
426
+ for (const [requestId, pending] of this.pendingRequests) {
427
+ if (pending.port !== port)
428
+ continue;
429
+ this.pendingRequests.delete(requestId);
420
430
  clearTimeout(pending.timeoutId);
421
431
  pending.reject(error);
422
432
  }
423
- this.pendingRequests.clear();
424
- this.broadcastError(error);
425
433
  }
426
434
  broadcastError(error) {
427
435
  for (const session of this.sessions.values()) {
428
436
  session.handleEvent({ type: "error", sessionId: session.sessionId, error }, { source: "sdk" });
429
437
  }
430
438
  }
439
+ invalidateSessions(error) {
440
+ for (const session of this.sessions.values()) {
441
+ session.handleTransportDisconnect(error);
442
+ }
443
+ }
444
+ }
445
+ function normalizeCreateSessionSandboxInput(value) {
446
+ if (value === undefined)
447
+ return undefined;
448
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
449
+ throw makeError("INVALID_INPUT", "sandbox must be an object");
450
+ }
451
+ const path = value.path;
452
+ if (typeof path !== "string") {
453
+ throw makeError("INVALID_INPUT", "sandbox.path must be a string");
454
+ }
455
+ if (path.trim().length === 0) {
456
+ throw makeError("INVALID_INPUT", "sandbox.path must not be empty");
457
+ }
458
+ return { path };
431
459
  }
432
460
  export class PedelecSession {
433
461
  client;
434
462
  sessionId;
435
463
  provider;
436
- model;
464
+ effortLevel;
437
465
  sessionCreatedAt = Date.now();
438
466
  status = "idle";
439
467
  pendingSend = null;
@@ -443,6 +471,7 @@ export class PedelecSession {
443
471
  ending = false;
444
472
  prepared = false;
445
473
  activeTurn = null;
474
+ transportDetached = false;
446
475
  genericToolHandler = null;
447
476
  inlineToolHandlers = new Map();
448
477
  namedToolHandlers = new Map();
@@ -450,15 +479,15 @@ export class PedelecSession {
450
479
  errorHandlers = new Set();
451
480
  statusHandlers = new Set();
452
481
  endedHandlers = new Set();
453
- constructor(client, sessionId, provider, model, inlineToolHandlers = new Map()) {
482
+ constructor(client, sessionId, provider, effortLevel, inlineToolHandlers = new Map()) {
454
483
  this.client = client;
455
484
  this.sessionId = sessionId;
456
485
  this.provider = provider;
457
- this.model = model;
486
+ this.effortLevel = effortLevel;
458
487
  this.inlineToolHandlers = new Map(inlineToolHandlers);
459
488
  }
460
489
  prepare() {
461
- if (this.status === "ended") {
490
+ if (this.transportDetached || this.status === "ended") {
462
491
  return Promise.reject(makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId }));
463
492
  }
464
493
  if (this.pendingSend) {
@@ -482,7 +511,8 @@ export class PedelecSession {
482
511
  })
483
512
  .catch((err) => {
484
513
  const error = normalizeError(err, "PREPARE_FAILED", "prepare failed");
485
- this.emitError(error, { source: "sdk" });
514
+ if (!this.transportDetached)
515
+ this.emitError(error, { source: "sdk" });
486
516
  this.clearPendingPrepare();
487
517
  this.finishActiveTurn(turn);
488
518
  throw error;
@@ -505,7 +535,7 @@ export class PedelecSession {
505
535
  // prepare is an optimization; sendText falls back to the original first-run path.
506
536
  }
507
537
  }
508
- if (this.status === "ended") {
538
+ if (this.transportDetached || this.status === "ended") {
509
539
  return Promise.reject(makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId }));
510
540
  }
511
541
  if (this.pendingSend || this.pendingPrepare) {
@@ -524,7 +554,8 @@ export class PedelecSession {
524
554
  })
525
555
  .catch((err) => {
526
556
  const error = normalizeError(err, "SEND_TEXT_FAILED", "sendText failed");
527
- this.emitError(error, { source: "sdk" });
557
+ if (!this.transportDetached)
558
+ this.emitError(error, { source: "sdk" });
528
559
  this.clearPendingSend();
529
560
  this.finishActiveTurn(turn);
530
561
  throw error;
@@ -560,7 +591,7 @@ export class PedelecSession {
560
591
  };
561
592
  }
562
593
  uploadAsset(file, targetPath) {
563
- if (this.ending || this.status === "ended")
594
+ if (this.transportDetached || this.ending || this.status === "ended")
564
595
  return Promise.reject(makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId }));
565
596
  if (this.uploadPromise) {
566
597
  return Promise.reject(makeError("SESSION_BUSY", "session is busy", { sessionId: this.sessionId }));
@@ -601,13 +632,13 @@ export class PedelecSession {
601
632
  return this.uploadPromise;
602
633
  }
603
634
  listAssets() {
604
- if (this.ending || this.status === "ended") {
635
+ if (this.transportDetached || this.ending || this.status === "ended") {
605
636
  return Promise.reject(makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId }));
606
637
  }
607
638
  return this.client.request("list_assets", { sessionId: this.sessionId }).then(normalizeListAssetsResponse);
608
639
  }
609
640
  readAsset(path, type) {
610
- if (this.ending || this.status === "ended")
641
+ if (this.transportDetached || this.ending || this.status === "ended")
611
642
  return Promise.reject(makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId }));
612
643
  if (!isValidAssetPath(path) || !["text", "json", "file"].includes(type)) {
613
644
  return Promise.reject(makeError("ASSET_PATH_INVALID", "asset path or read type is invalid"));
@@ -671,8 +702,12 @@ export class PedelecSession {
671
702
  return this.status;
672
703
  }
673
704
  async end() {
674
- if (this.status === "ended" || this.ending)
705
+ if (this.transportDetached || this.status === "ended" || this.ending) {
706
+ if (this.transportDetached) {
707
+ throw makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId });
708
+ }
675
709
  return;
710
+ }
676
711
  this.ending = true;
677
712
  try {
678
713
  await this.client.request("end_session", {
@@ -742,6 +777,19 @@ export class PedelecSession {
742
777
  replaceInlineToolHandlers(handlers) {
743
778
  this.inlineToolHandlers = new Map(handlers);
744
779
  }
780
+ /** @internal */
781
+ isTransportDetached() {
782
+ return this.transportDetached;
783
+ }
784
+ /** @internal */
785
+ handleTransportDisconnect(error) {
786
+ if (this.transportDetached)
787
+ return;
788
+ this.transportDetached = true;
789
+ this.setStatus("error", { source: "sdk" });
790
+ this.emitError(error, { source: "sdk" });
791
+ this.rejectActivePending(error);
792
+ }
745
793
  async handleToolCall(event, meta, turn) {
746
794
  let result;
747
795
  const namedHandler = this.namedToolHandlers.get(event.tool);
@@ -783,6 +831,8 @@ export class PedelecSession {
783
831
  }),
784
832
  };
785
833
  }
834
+ if (this.transportDetached)
835
+ return;
786
836
  try {
787
837
  await this.client.request("submit_tool_result", {
788
838
  sessionId: this.sessionId,
@@ -887,7 +937,7 @@ export class PedelecSession {
887
937
  return {
888
938
  sessionId: this.sessionId,
889
939
  provider: this.provider,
890
- model: this.model,
940
+ ...(this.effortLevel ? { effortLevel: this.effortLevel } : {}),
891
941
  sessionCreatedAt: this.sessionCreatedAt,
892
942
  ...(meta.eventReceivedAt === undefined ? {} : { eventReceivedAt: meta.eventReceivedAt }),
893
943
  eventEmittedAt: Date.now(),
@@ -950,22 +1000,18 @@ function isProviderCode(value) {
950
1000
  return value === "codex" || value === "antigravity" || value === "opencode" ||
951
1001
  value === "cursor" || value === "claude" || value === "ollama";
952
1002
  }
1003
+ function isEffortLevel(value) {
1004
+ return value === "default" || value === "low" || value === "high";
1005
+ }
953
1006
  function isPlainObject(value) {
954
1007
  return !!value && typeof value === "object" && !Array.isArray(value);
955
1008
  }
956
1009
  function normalizePedelecSettings(value) {
957
1010
  if (!isPlainObject(value) || (value.defaultProvider !== null && !isProviderCode(value.defaultProvider)) ||
958
- !isPlainObject(value.defaultModels)) {
1011
+ "defaultModels" in value || "providerSettings" in value) {
959
1012
  throw makeError("SDK_PROTOCOL_ERROR", "get_settings response had invalid shape");
960
1013
  }
961
- const defaultModels = {};
962
- for (const [code, model] of Object.entries(value.defaultModels)) {
963
- if (!isProviderCode(code) || typeof model !== "string") {
964
- throw makeError("SDK_PROTOCOL_ERROR", "get_settings response had invalid shape");
965
- }
966
- defaultModels[code] = model;
967
- }
968
- return { defaultProvider: value.defaultProvider, defaultModels };
1014
+ return { defaultProvider: value.defaultProvider };
969
1015
  }
970
1016
  function normalizeProviderInfoList(value) {
971
1017
  if (!Array.isArray(value))
@@ -1,4 +1,7 @@
1
1
  import { Pedelec, defineTool, } from "./index";
2
+ function sandboxInputHasPublicType() {
3
+ return { path: "C:\\workspace\\project" };
4
+ }
2
5
  async function typedOnToolNameFromCreateSession() {
3
6
  const pedelec = new Pedelec();
4
7
  const session = await pedelec.createSession({
@@ -79,7 +82,12 @@ async function resumedSessionFallsBackToString() {
79
82
  }
80
83
  async function noSkillsFallsBackToString() {
81
84
  const pedelec = new Pedelec();
82
- const session = await pedelec.createSession({ provider: "codex" });
85
+ const session = await pedelec.createSession({
86
+ provider: "codex",
87
+ sandbox: { path: "C:\\workspace\\project" },
88
+ });
89
+ const typedSandbox = sandboxInputHasPublicType();
90
+ void typedSandbox;
83
91
  session.onTool((name) => {
84
92
  const anyString = name;
85
93
  return anyString;
@@ -88,6 +96,17 @@ async function noSkillsFallsBackToString() {
88
96
  return { ok: true };
89
97
  });
90
98
  }
99
+ async function effortLevelPublicTypeContract() {
100
+ const pedelec = new Pedelec();
101
+ await pedelec.createSession({ effortLevel: "high" });
102
+ const session = await pedelec.createSession({ provider: "codex", effortLevel: "low" });
103
+ // @ts-expect-error model is no longer a createSession option
104
+ pedelec.createSession({ provider: "codex", model: "gpt-5" });
105
+ // @ts-expect-error session no longer exposes provider model
106
+ session.model;
107
+ // @ts-expect-error unsupported effort level
108
+ pedelec.createSession({ effortLevel: "medium" });
109
+ }
91
110
  async function listAssetsHasPublicTypes() {
92
111
  const pedelec = new Pedelec();
93
112
  const session = await pedelec.resumeSession("thread_1");
@@ -132,6 +151,7 @@ function publicSecurityTypesAreRestricted() {
132
151
  void typedOnToolNameFromCreateSession;
133
152
  void resumedSessionFallsBackToString;
134
153
  void noSkillsFallsBackToString;
154
+ void effortLevelPublicTypeContract;
135
155
  void listAssetsHasPublicTypes;
136
156
  void availabilityHasPublicType;
137
157
  void publicSecurityTypesAreRestricted;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaoruisaac/pedelec",
3
- "version": "0.1.25",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",