@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 +73 -21
- package/dist/index.d.ts +68 -26
- package/dist/index.js +296 -57
- package/dist/index.type-test.js +69 -17
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/vite.js +12 -2
- package/package.json +5 -5
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
275
|
+
`workspace.run(script, options?)` executes application-supplied Deno code in the Workspace:
|
|
254
276
|
|
|
255
277
|
```ts
|
|
256
|
-
const
|
|
257
|
-
|
|
278
|
+
const result = await workspace.run(
|
|
279
|
+
"console.log(JSON.stringify({ ok: true }))",
|
|
280
|
+
{ timeoutMs: 60_000 },
|
|
281
|
+
);
|
|
258
282
|
|
|
259
|
-
if (
|
|
260
|
-
|
|
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
|
-
|
|
264
|
-
|
|
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
|
-
`
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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`.
|
|
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
|
|
21
|
+
description?: string;
|
|
21
22
|
entry: string;
|
|
22
|
-
usage
|
|
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
|
|
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
|
|
102
|
-
|
|
106
|
+
export type WorkspaceRunOptions = {
|
|
107
|
+
timeoutMs?: number;
|
|
108
|
+
denoModules?: readonly DenoModuleDefinition[];
|
|
103
109
|
};
|
|
104
|
-
export
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
354
|
+
...(workspace ? { workspaceId: workspace.workspaceIdInternal } : {}),
|
|
224
355
|
autoEndOnDisconnect: resolvedInput.autoEndOnDisconnect,
|
|
225
356
|
},
|
|
226
357
|
});
|
|
227
|
-
|
|
228
|
-
|
|
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 &&
|
|
231
|
-
const error = makeError("SDK_PROTOCOL_ERROR", "The connected Pedelec Extension/Desktop does not support
|
|
232
|
-
await this.abortSessionSetup(
|
|
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(
|
|
372
|
+
await this.uploadDenoModuleArtifact(normalizedResult.sessionId, module);
|
|
238
373
|
}
|
|
239
374
|
if (resolvedInput.denoModules.length > 0) {
|
|
240
|
-
await this.completeSessionSetup(
|
|
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(
|
|
382
|
+
await this.abortSessionSetup(normalizedResult.sessionId);
|
|
248
383
|
throw normalizeError(error, "DENO_MODULE_UPLOAD_FAILED", "Deno Module setup failed");
|
|
249
384
|
}
|
|
250
|
-
|
|
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(
|
|
265
|
-
|
|
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
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
366
|
-
|
|
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 (
|
|
369
|
-
|
|
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"
|
|
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(
|
|
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,
|
|
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
|
|
1582
|
-
if (!isPlainObject(value) ||
|
|
1583
|
-
|
|
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
|
-
|
|
1586
|
-
if (path === null)
|
|
1771
|
+
if (value.workspace === null)
|
|
1587
1772
|
return null;
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
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
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
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) {
|
package/dist/index.type-test.js
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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
|
|
219
|
+
async function workspacePublicTypeContract() {
|
|
186
220
|
const pedelec = new Pedelec();
|
|
187
|
-
const
|
|
188
|
-
|
|
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
|
|
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
|
|
267
|
+
void workspacePublicTypeContract;
|
|
216
268
|
void directoryPickerIsRemoved;
|
|
217
269
|
void publicSecurityTypesAreRestricted;
|
|
218
270
|
const baseContext = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "0.
|
|
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.
|
|
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 =
|
|
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 =
|
|
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
|
+
"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": "
|
|
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": "
|
|
52
|
-
"vite": "
|
|
53
|
-
"vitest": "
|
|
51
|
+
"@types/node": "24.13.6",
|
|
52
|
+
"vite": "8.3.0",
|
|
53
|
+
"vitest": "4.1.11"
|
|
54
54
|
}
|
|
55
55
|
}
|