@kaoruisaac/pedelec 0.1.26 → 0.2.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
@@ -8,7 +8,17 @@
8
8
  - [Pedelec documentation](https://kaoruisaac.github.io/pedelec/)
9
9
  - [GitHub repository](https://github.com/kaoruisaac/pedelec)
10
10
 
11
+ ## Agent Skill
11
12
 
13
+ If you use an AI coding agent that supports skills, run the following command from the project you want to integrate with Pedelec:
14
+
15
+ ```bash
16
+ npx skills add kaoruisaac/pedelec
17
+ ```
18
+
19
+ This installs the `pedelec-integration` skill, which guides the agent through SDK setup, session lifecycle, frontend tools, Provider and effort selection, connection/recovery UX, and integration verification. For exact SDK APIs and behavior, continue to use the installed SDK declarations and the official documentation as the source of truth.
20
+
21
+ ---
12
22
  Pedelec is a browser SDK and local bridge for applications that want to work with AI coding agents such as Codex, Antigravity, OpenCode, Cursor, Claude Code, or an Ollama-backed agent.
13
23
 
14
24
  A web application can use Pedelec to:
@@ -88,7 +98,7 @@ const pedelec = new Pedelec();
88
98
 
89
99
  const session = await pedelec.createSession({
90
100
  provider: "codex",
91
- model: "gpt-5",
101
+ effortLevel: "high",
92
102
  skills: {
93
103
  guidance: "Use get_current_page when you need browser page context.",
94
104
  tools: [
@@ -145,6 +155,19 @@ const result = await session.readAsset("/model.glb", "file");
145
155
 
146
156
  Public asset paths use `/...` with `assets/` as their implicit root; nested paths such as `/results/model.glb` are supported up to 100 MiB.
147
157
 
158
+ ## Sandbox workspace
159
+
160
+ 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:
161
+
162
+ ```ts
163
+ const session = await pedelec.createSession({
164
+ provider: "codex",
165
+ sandbox: { path: "C:\\workspace\\project-a" },
166
+ });
167
+ ```
168
+
169
+ 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.
170
+
148
171
  ---
149
172
 
150
173
  ## Creating a Session
@@ -174,31 +197,31 @@ if (availability.available) startUi();
174
197
 
175
198
  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
199
 
177
- ### Specifying Provider and Model
200
+ ### Selecting Provider and Effort
178
201
 
179
202
  ```ts
180
203
  const session = await pedelec.createSession({
181
- provider: "opencode",
182
- model: "ollama/qwen2.5-coder:14b",
204
+ provider: "codex",
205
+ effortLevel: "high",
183
206
  });
207
+
208
+ // Or let Desktop Settings choose the provider.
209
+ const defaultSession = await pedelec.createSession({ effortLevel: "low" });
184
210
  ```
185
211
 
212
+ 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.
213
+
186
214
  Currently supported provider codes in the SDK:
187
215
 
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` |
216
+ | Provider | Code |
217
+ | --- | --- |
218
+ | Codex | `codex` |
219
+ | Antigravity | `antigravity` |
220
+ | OpenCode | `opencode` |
221
+ | Cursor | `cursor` |
222
+ | Claude Code | `claude` |
223
+ | Ollama | `ollama` |
196
224
 
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:
225
+ 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
226
 
199
- ```ts
200
- const session = await pedelec.createSession({
201
- provider: "ollama",
202
- model: "qwen3-14b-32k:latest",
203
- });
204
- ```
227
+ `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;
@@ -237,7 +242,6 @@ export declare class Pedelec {
237
242
  private resolveCreateSessionInput;
238
243
  checkAvailability(): Promise<PedelecAvailability>;
239
244
  private resolveDefaultCreateSessionInput;
240
- private resolveProviderOnlyCreateSessionInput;
241
245
  private assertDefaultProviderAvailable;
242
246
  private registerSession;
243
247
  private ensureExtensionPort;
@@ -253,7 +257,7 @@ export declare class PedelecSession<TToolName extends string = string> {
253
257
  private readonly client;
254
258
  readonly sessionId: string;
255
259
  readonly provider: string;
256
- readonly model?: string;
260
+ readonly effortLevel?: EffortLevel;
257
261
  readonly sessionCreatedAt: number;
258
262
  private status;
259
263
  private pendingSend;
@@ -271,7 +275,7 @@ export declare class PedelecSession<TToolName extends string = string> {
271
275
  private readonly errorHandlers;
272
276
  private readonly statusHandlers;
273
277
  private readonly endedHandlers;
274
- 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>);
275
279
  prepare(): Promise<void>;
276
280
  sendText(text: string): Promise<void>;
277
281
  onChat(handler: ChatHandler): () => void;
package/dist/index.js CHANGED
@@ -138,15 +138,16 @@ export class Pedelec {
138
138
  const result = await this.request("create_session", {
139
139
  input: {
140
140
  provider: resolvedInput.provider,
141
- model: resolvedInput.model,
141
+ effortLevel: resolvedInput.effortLevel,
142
142
  skills: resolvedInput.skills,
143
+ sandbox: resolvedInput.sandbox,
143
144
  autoEndOnDisconnect: resolvedInput.autoEndOnDisconnect,
144
145
  },
145
146
  });
146
147
  if (!result.sessionId) {
147
148
  throw makeError("SDK_PROTOCOL_ERROR", "create_session response did not include sessionId");
148
149
  }
149
- return this.registerSession(result.sessionId, resolvedInput.provider, resolvedInput.model, resolvedInput.inlineToolHandlers);
150
+ return this.registerSession(result.sessionId, resolvedInput.provider, resolvedInput.effortLevel, resolvedInput.inlineToolHandlers);
150
151
  }
151
152
  async listProviders() {
152
153
  return normalizeProviderInfoList(await this.request("list_providers"));
@@ -230,24 +231,24 @@ export class Pedelec {
230
231
  const raw = (input ?? {});
231
232
  const provider = typeof raw.provider === "string" ? raw.provider.trim() : "";
232
233
  const hasProvider = provider.length > 0;
233
- 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
+ }
234
241
  const autoEndOnDisconnect = raw.autoEndOnDisconnect !== false;
235
242
  const normalizedSkills = normalizeSkillsInput(raw.skills);
236
- if (!hasProvider && hasModel) {
237
- throw makeError("INVALID_INPUT", "model cannot be provided without provider");
238
- }
239
- const userModel = typeof raw.model === "string" ? raw.model : undefined;
243
+ const sandbox = normalizeCreateSessionSandboxInput(raw.sandbox);
240
244
  if (!hasProvider) {
241
- return this.resolveDefaultCreateSessionInput(normalizedSkills, autoEndOnDisconnect);
242
- }
243
- let model = userModel;
244
- if (model === undefined) {
245
- return this.resolveProviderOnlyCreateSessionInput(provider, normalizedSkills, autoEndOnDisconnect);
245
+ return this.resolveDefaultCreateSessionInput(effortLevel, normalizedSkills, sandbox, autoEndOnDisconnect);
246
246
  }
247
247
  return {
248
248
  provider: provider,
249
- model,
249
+ effortLevel,
250
250
  skills: normalizedSkills.manifest,
251
+ sandbox,
251
252
  inlineToolHandlers: normalizedSkills.handlers,
252
253
  autoEndOnDisconnect,
253
254
  };
@@ -304,7 +305,7 @@ export class Pedelec {
304
305
  };
305
306
  }
306
307
  }
307
- async resolveDefaultCreateSessionInput(normalizedSkills, autoEndOnDisconnect) {
308
+ async resolveDefaultCreateSessionInput(effortLevel, normalizedSkills, sandbox, autoEndOnDisconnect) {
308
309
  const settings = await this.getSettings();
309
310
  if (!settings.defaultProvider) {
310
311
  throw makeError("DEFAULT_PROVIDER_NOT_SET", "Default provider is not set. Open the Pedelec desktop app Settings page and choose a default provider.");
@@ -312,18 +313,9 @@ export class Pedelec {
312
313
  await this.assertDefaultProviderAvailable(settings.defaultProvider);
313
314
  return {
314
315
  provider: settings.defaultProvider,
315
- model: settings.defaultModels[settings.defaultProvider] ?? undefined,
316
- skills: normalizedSkills.manifest,
317
- inlineToolHandlers: normalizedSkills.handlers,
318
- autoEndOnDisconnect,
319
- };
320
- }
321
- async resolveProviderOnlyCreateSessionInput(provider, normalizedSkills, autoEndOnDisconnect) {
322
- const settings = await this.getSettings();
323
- return {
324
- provider,
325
- model: settings.defaultModels[provider] ?? undefined,
316
+ effortLevel,
326
317
  skills: normalizedSkills.manifest,
318
+ sandbox,
327
319
  inlineToolHandlers: normalizedSkills.handlers,
328
320
  autoEndOnDisconnect,
329
321
  };
@@ -335,13 +327,13 @@ export class Pedelec {
335
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 });
336
328
  }
337
329
  }
338
- registerSession(sessionId, provider, model, inlineToolHandlers = new Map()) {
330
+ registerSession(sessionId, provider, effortLevel, inlineToolHandlers = new Map()) {
339
331
  const existing = this.sessions.get(sessionId);
340
332
  if (existing && !existing.isTransportDetached()) {
341
333
  existing.replaceInlineToolHandlers(inlineToolHandlers);
342
334
  return existing;
343
335
  }
344
- const session = new PedelecSession(this, sessionId, provider, model, inlineToolHandlers);
336
+ const session = new PedelecSession(this, sessionId, provider, effortLevel, inlineToolHandlers);
345
337
  this.sessions.set(sessionId, session);
346
338
  return session;
347
339
  }
@@ -450,11 +442,26 @@ export class Pedelec {
450
442
  }
451
443
  }
452
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 };
459
+ }
453
460
  export class PedelecSession {
454
461
  client;
455
462
  sessionId;
456
463
  provider;
457
- model;
464
+ effortLevel;
458
465
  sessionCreatedAt = Date.now();
459
466
  status = "idle";
460
467
  pendingSend = null;
@@ -472,11 +479,11 @@ export class PedelecSession {
472
479
  errorHandlers = new Set();
473
480
  statusHandlers = new Set();
474
481
  endedHandlers = new Set();
475
- constructor(client, sessionId, provider, model, inlineToolHandlers = new Map()) {
482
+ constructor(client, sessionId, provider, effortLevel, inlineToolHandlers = new Map()) {
476
483
  this.client = client;
477
484
  this.sessionId = sessionId;
478
485
  this.provider = provider;
479
- this.model = model;
486
+ this.effortLevel = effortLevel;
480
487
  this.inlineToolHandlers = new Map(inlineToolHandlers);
481
488
  }
482
489
  prepare() {
@@ -930,7 +937,7 @@ export class PedelecSession {
930
937
  return {
931
938
  sessionId: this.sessionId,
932
939
  provider: this.provider,
933
- model: this.model,
940
+ ...(this.effortLevel ? { effortLevel: this.effortLevel } : {}),
934
941
  sessionCreatedAt: this.sessionCreatedAt,
935
942
  ...(meta.eventReceivedAt === undefined ? {} : { eventReceivedAt: meta.eventReceivedAt }),
936
943
  eventEmittedAt: Date.now(),
@@ -993,22 +1000,18 @@ function isProviderCode(value) {
993
1000
  return value === "codex" || value === "antigravity" || value === "opencode" ||
994
1001
  value === "cursor" || value === "claude" || value === "ollama";
995
1002
  }
1003
+ function isEffortLevel(value) {
1004
+ return value === "default" || value === "low" || value === "high";
1005
+ }
996
1006
  function isPlainObject(value) {
997
1007
  return !!value && typeof value === "object" && !Array.isArray(value);
998
1008
  }
999
1009
  function normalizePedelecSettings(value) {
1000
1010
  if (!isPlainObject(value) || (value.defaultProvider !== null && !isProviderCode(value.defaultProvider)) ||
1001
- !isPlainObject(value.defaultModels)) {
1011
+ "defaultModels" in value || "providerSettings" in value) {
1002
1012
  throw makeError("SDK_PROTOCOL_ERROR", "get_settings response had invalid shape");
1003
1013
  }
1004
- const defaultModels = {};
1005
- for (const [code, model] of Object.entries(value.defaultModels)) {
1006
- if (!isProviderCode(code) || typeof model !== "string") {
1007
- throw makeError("SDK_PROTOCOL_ERROR", "get_settings response had invalid shape");
1008
- }
1009
- defaultModels[code] = model;
1010
- }
1011
- return { defaultProvider: value.defaultProvider, defaultModels };
1014
+ return { defaultProvider: value.defaultProvider };
1012
1015
  }
1013
1016
  function normalizeProviderInfoList(value) {
1014
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,12 +1,11 @@
1
1
  {
2
2
  "name": "@kaoruisaac/pedelec",
3
- "version": "0.1.26",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "files": [
8
8
  "dist",
9
- "skills",
10
9
  "README.md",
11
10
  "LICENSE"
12
11
  ],