@kaoruisaac/pedelec 0.2.1 → 0.2.3
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 +20 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +48 -11
- package/dist/index.type-test.js +12 -0
- package/dist/version.generated.d.ts +1 -0
- package/dist/version.generated.js +2 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ Pedelec is a browser SDK and local bridge for applications that want to work wit
|
|
|
24
24
|
A web application can use Pedelec to:
|
|
25
25
|
|
|
26
26
|
- create an agent session on the user's machine;
|
|
27
|
+
- choose an application-managed workspace with the native directory picker;
|
|
27
28
|
- send user instructions and receive streamed assistant text;
|
|
28
29
|
- expose narrowly scoped browser-side tools to the agent;
|
|
29
30
|
- resume or end sessions; and
|
|
@@ -168,6 +169,25 @@ const session = await pedelec.createSession({
|
|
|
168
169
|
|
|
169
170
|
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
171
|
|
|
172
|
+
## Selecting an Application Workspace
|
|
173
|
+
|
|
174
|
+
Use `sandboxFolderPicker()` when the application wants the user to choose a local workspace before creating a session:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
const folder = await pedelec.sandboxFolderPicker();
|
|
178
|
+
if (!folder) return; // The user cancelled the native picker.
|
|
179
|
+
|
|
180
|
+
if (!folder.isEmptyFolder && !folder.hasSandboxConfig) {
|
|
181
|
+
// The application decides whether to warn the user.
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const session = await pedelec.createSession({
|
|
185
|
+
sandbox: { path: folder.path },
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`sandboxFolderPicker()` returns `Promise<SandboxFolderPickerResult | null>`. It is a read-only snapshot: it does not validate or initialize the selected folder, create workspace directories, or create the sandbox marker. `isEmptyFolder` only reports whether the selected folder root has any filesystem entry. `hasSandboxConfig` is true only when `.pedelec-sandbox.json` exists as a regular file; its JSON is not parsed. The picker requires the same origin approval as other sensitive Desktop APIs, and cancellation returns `null`. `createSession()` still applies the full sandbox rules and creates the marker after successful custom sandbox initialization. The marker currently records the SDK version and normalized caller origin but does not restrict reuse by origin or SDK version.
|
|
190
|
+
|
|
171
191
|
---
|
|
172
192
|
|
|
173
193
|
## Creating a Session
|
package/dist/index.d.ts
CHANGED
|
@@ -86,6 +86,11 @@ export type SerializableSkillsManifest = {
|
|
|
86
86
|
export type CreateSessionSandboxInput = {
|
|
87
87
|
path: string;
|
|
88
88
|
};
|
|
89
|
+
export interface SandboxFolderPickerResult {
|
|
90
|
+
path: string;
|
|
91
|
+
isEmptyFolder: boolean;
|
|
92
|
+
hasSandboxConfig: boolean;
|
|
93
|
+
}
|
|
89
94
|
type CreateSessionInputWithProvider<TTools extends readonly ToolDefinition[] = readonly ToolDefinition[]> = {
|
|
90
95
|
provider: ProviderCode;
|
|
91
96
|
effortLevel?: EffortLevel;
|
|
@@ -233,11 +238,13 @@ export declare class Pedelec {
|
|
|
233
238
|
constructor(options?: PedelecOptions);
|
|
234
239
|
createSession(): Promise<PedelecSession<string>>;
|
|
235
240
|
createSession<const TTools extends readonly ToolDefinition[]>(input: CreateSessionInput<TTools>): Promise<PedelecSession<ToolNameOf<TTools>>>;
|
|
241
|
+
sandboxFolderPicker(): Promise<SandboxFolderPickerResult | null>;
|
|
236
242
|
listProviders(): Promise<ProviderInfo[]>;
|
|
237
243
|
getSettings(): Promise<PedelecSettings>;
|
|
238
244
|
getApprovalStatus(): Promise<ApprovalStatus>;
|
|
239
245
|
resumeSession(sessionId: string): Promise<PedelecSession<string>>;
|
|
240
246
|
request<T>(type: string, payload?: Record<string, unknown>): Promise<T>;
|
|
247
|
+
private requestWithOptions;
|
|
241
248
|
unregisterSession(sessionId: string): void;
|
|
242
249
|
private resolveCreateSessionInput;
|
|
243
250
|
checkAvailability(): Promise<PedelecAvailability>;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PEDELEC_EXTENSION_ID } from "./extension-id.js";
|
|
2
|
+
import { SDK_VERSION } from "./version.generated.js";
|
|
2
3
|
const SDK_EXTERNAL_PORT_NAME = "pedelec-sdk-external";
|
|
3
4
|
const DEFAULT_BRIDGE_TIMEOUT_MS = 30_000;
|
|
4
5
|
function normalizeListAssetsResponse(response) {
|
|
@@ -149,6 +150,10 @@ export class Pedelec {
|
|
|
149
150
|
}
|
|
150
151
|
return this.registerSession(result.sessionId, resolvedInput.provider, resolvedInput.effortLevel, resolvedInput.inlineToolHandlers);
|
|
151
152
|
}
|
|
153
|
+
async sandboxFolderPicker() {
|
|
154
|
+
const result = await this.requestWithOptions("pick_sandbox_folder", {}, { timeoutMs: null });
|
|
155
|
+
return normalizeSandboxFolderPickerResponse(result);
|
|
156
|
+
}
|
|
152
157
|
async listProviders() {
|
|
153
158
|
return normalizeProviderInfoList(await this.request("list_providers"));
|
|
154
159
|
}
|
|
@@ -186,6 +191,9 @@ export class Pedelec {
|
|
|
186
191
|
return this.registerSession(result.sessionId, "", undefined);
|
|
187
192
|
}
|
|
188
193
|
request(type, payload = {}) {
|
|
194
|
+
return this.requestWithOptions(type, payload);
|
|
195
|
+
}
|
|
196
|
+
requestWithOptions(type, payload = {}, options = {}) {
|
|
189
197
|
let port;
|
|
190
198
|
try {
|
|
191
199
|
port = this.ensureExtensionPort();
|
|
@@ -194,15 +202,24 @@ export class Pedelec {
|
|
|
194
202
|
return Promise.reject(normalizeError(err, "EXTENSION_UNAVAILABLE", "Pedelec extension is unavailable."));
|
|
195
203
|
}
|
|
196
204
|
const requestId = `sdk_${Date.now()}_${this.nextRequestNumber++}`;
|
|
197
|
-
const message = {
|
|
205
|
+
const message = {
|
|
206
|
+
channelId: this.channelId,
|
|
207
|
+
type,
|
|
208
|
+
requestId,
|
|
209
|
+
...payload,
|
|
210
|
+
...(type === "create_session" ? { callerSdkVersion: SDK_VERSION } : {}),
|
|
211
|
+
};
|
|
198
212
|
return new Promise((resolve, reject) => {
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
213
|
+
const timeoutMs = options.timeoutMs === undefined ? this.bridgeTimeoutMs : options.timeoutMs;
|
|
214
|
+
const timeoutId = timeoutMs === null
|
|
215
|
+
? null
|
|
216
|
+
: setTimeout(() => {
|
|
217
|
+
this.pendingRequests.delete(requestId);
|
|
218
|
+
reject(makeError("SDK_BRIDGE_TIMEOUT", "Pedelec extension did not respond.", {
|
|
219
|
+
requestId,
|
|
220
|
+
type,
|
|
221
|
+
}));
|
|
222
|
+
}, timeoutMs);
|
|
206
223
|
this.pendingRequests.set(requestId, {
|
|
207
224
|
port,
|
|
208
225
|
resolve: (value) => resolve(value),
|
|
@@ -214,7 +231,8 @@ export class Pedelec {
|
|
|
214
231
|
}
|
|
215
232
|
catch (err) {
|
|
216
233
|
this.pendingRequests.delete(requestId);
|
|
217
|
-
|
|
234
|
+
if (timeoutId !== null)
|
|
235
|
+
clearTimeout(timeoutId);
|
|
218
236
|
const error = normalizeError(err, "EXTENSION_DISCONNECTED", "Pedelec extension disconnected.");
|
|
219
237
|
if (this.port === port) {
|
|
220
238
|
this.handleDisconnect(port, error);
|
|
@@ -398,7 +416,8 @@ export class Pedelec {
|
|
|
398
416
|
if (!pending)
|
|
399
417
|
return;
|
|
400
418
|
this.pendingRequests.delete(message.requestId);
|
|
401
|
-
|
|
419
|
+
if (pending.timeoutId !== null)
|
|
420
|
+
clearTimeout(pending.timeoutId);
|
|
402
421
|
if (message.ok) {
|
|
403
422
|
pending.resolve(message.result);
|
|
404
423
|
}
|
|
@@ -427,7 +446,8 @@ export class Pedelec {
|
|
|
427
446
|
if (pending.port !== port)
|
|
428
447
|
continue;
|
|
429
448
|
this.pendingRequests.delete(requestId);
|
|
430
|
-
|
|
449
|
+
if (pending.timeoutId !== null)
|
|
450
|
+
clearTimeout(pending.timeoutId);
|
|
431
451
|
pending.reject(error);
|
|
432
452
|
}
|
|
433
453
|
}
|
|
@@ -1013,6 +1033,23 @@ function normalizePedelecSettings(value) {
|
|
|
1013
1033
|
}
|
|
1014
1034
|
return { defaultProvider: value.defaultProvider };
|
|
1015
1035
|
}
|
|
1036
|
+
function normalizeSandboxFolderPickerResponse(value) {
|
|
1037
|
+
if (!isPlainObject(value) || !("path" in value)) {
|
|
1038
|
+
throw makeError("SDK_PROTOCOL_ERROR", "pick_sandbox_folder response had an invalid shape");
|
|
1039
|
+
}
|
|
1040
|
+
const path = value.path;
|
|
1041
|
+
if (path === null)
|
|
1042
|
+
return null;
|
|
1043
|
+
if (typeof path !== "string" || path.length === 0 ||
|
|
1044
|
+
typeof value.isEmptyFolder !== "boolean" || typeof value.hasSandboxConfig !== "boolean") {
|
|
1045
|
+
throw makeError("SDK_PROTOCOL_ERROR", "pick_sandbox_folder response had an invalid shape");
|
|
1046
|
+
}
|
|
1047
|
+
return {
|
|
1048
|
+
path,
|
|
1049
|
+
isEmptyFolder: value.isEmptyFolder,
|
|
1050
|
+
hasSandboxConfig: value.hasSandboxConfig,
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1016
1053
|
function normalizeProviderInfoList(value) {
|
|
1017
1054
|
if (!Array.isArray(value))
|
|
1018
1055
|
throw makeError("SDK_PROTOCOL_ERROR", "list_providers response was not an array");
|
package/dist/index.type-test.js
CHANGED
|
@@ -136,6 +136,16 @@ async function availabilityHasPublicType() {
|
|
|
136
136
|
void availability;
|
|
137
137
|
void promise;
|
|
138
138
|
}
|
|
139
|
+
async function sandboxFolderPickerHasPublicType() {
|
|
140
|
+
const pedelec = new Pedelec();
|
|
141
|
+
const result = pedelec.sandboxFolderPicker();
|
|
142
|
+
void result;
|
|
143
|
+
}
|
|
144
|
+
function directoryPickerIsRemoved() {
|
|
145
|
+
const pedelec = new Pedelec();
|
|
146
|
+
// @ts-expect-error directoryPicker was removed in favor of sandboxFolderPicker
|
|
147
|
+
pedelec.directoryPicker();
|
|
148
|
+
}
|
|
139
149
|
function publicSecurityTypesAreRestricted() {
|
|
140
150
|
const status = {
|
|
141
151
|
installed: true,
|
|
@@ -154,6 +164,8 @@ void noSkillsFallsBackToString;
|
|
|
154
164
|
void effortLevelPublicTypeContract;
|
|
155
165
|
void listAssetsHasPublicTypes;
|
|
156
166
|
void availabilityHasPublicType;
|
|
167
|
+
void sandboxFolderPickerHasPublicType;
|
|
168
|
+
void directoryPickerIsRemoved;
|
|
157
169
|
void publicSecurityTypesAreRestricted;
|
|
158
170
|
const baseContext = {
|
|
159
171
|
sessionId: "thread_1",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SDK_VERSION = "0.2.3";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaoruisaac/pedelec",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -24,8 +24,11 @@
|
|
|
24
24
|
"directory": "sdk"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
|
+
"prebuild": "node scripts/generate-version.mjs",
|
|
27
28
|
"build": "tsc -p tsconfig.json",
|
|
29
|
+
"pretest": "node scripts/generate-version.mjs",
|
|
28
30
|
"test": "vitest run",
|
|
31
|
+
"pretype-test": "node scripts/generate-version.mjs",
|
|
29
32
|
"type-test": "tsc -p tsconfig.type-test.json",
|
|
30
33
|
"prepublishOnly": "npm run test && npm run type-test && npm run build"
|
|
31
34
|
},
|