@kaoruisaac/pedelec 0.1.17 → 0.1.18

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
@@ -138,12 +138,12 @@ const assets = await session.listAssets();
138
138
  `assets/` is shared by uploads and files written by the agent. `listAssets()` lists only its first level, ordered by filesystem modification time (newest first), and 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.
139
139
 
140
140
  ```ts
141
- const text = await session.readAsset("assets/report.txt", "text");
142
- const json = await session.readAsset<{ ok: boolean }>("assets/result.json", "json");
143
- const result = await session.readAsset("assets/model.glb", "file");
141
+ const text = await session.readAsset("/report.txt", "text");
142
+ const json = await session.readAsset<{ ok: boolean }>("/result.json", "json");
143
+ const result = await session.readAsset("/model.glb", "file");
144
144
  ```
145
145
 
146
- Reads support nested `assets/...` paths up to 100 MiB. `text` and `json` require valid UTF-8; binary content is transferred over a loopback download ticket rather than extension messages.
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
148
  ---
149
149
 
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export type JsonPrimitive = string | number | boolean | null;
6
6
  export type JsonValue = JsonPrimitive | JsonValue[] | {
7
7
  [key: string]: JsonValue;
8
8
  };
9
- export type SandboxAssetPath = `assets/${string}`;
9
+ export type SandboxAssetPath = `/${string}`;
10
10
  export type ReadAssetType = "text" | "json" | "file";
11
11
  export type SandboxAsset = {
12
12
  name: string;
@@ -276,6 +276,7 @@ export declare class PedelecSession<TToolName extends string = string> {
276
276
  onTool(handler: GenericToolHandler<TToolName>): () => void;
277
277
  onTool<TArgs = unknown, TResult = unknown>(toolName: TToolName, handler: ToolSpecificHandler<TArgs, TResult>): () => void;
278
278
  uploadAsset(file: File): Promise<SandboxAssetPath>;
279
+ uploadAsset(file: File, targetPath: SandboxAssetPath): Promise<SandboxAssetPath>;
279
280
  listAssets(): Promise<SandboxAsset[]>;
280
281
  readAsset(path: SandboxAssetPath, type: "text"): Promise<string>;
281
282
  readAsset<T = JsonValue>(path: SandboxAssetPath, type: "json"): Promise<T>;
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ function normalizeListAssetsResponse(response) {
12
12
  return invalidListAssetsResponse({ index, asset });
13
13
  const { name, path, sizeBytes, modifiedAt } = asset;
14
14
  const validName = typeof name === "string" && name.length > 0 && name !== "." && name !== ".." && !name.includes("/") && !name.includes("\\");
15
- const validPath = typeof path === "string" && !path.includes("\\") && path === `assets/${name}` && !path.split("/").includes("..");
15
+ const validPath = typeof path === "string" && path === `/${name}` && isValidAssetPath(path);
16
16
  const validNumber = (value) => typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= 0;
17
17
  if (!validName || !validPath || !validNumber(sizeBytes) || !validNumber(modifiedAt)) {
18
18
  return invalidListAssetsResponse({ index, asset });
@@ -24,8 +24,8 @@ function invalidListAssetsResponse(details) {
24
24
  throw makeError("SDK_PROTOCOL_ERROR", "list_assets response had an invalid shape", { response: details });
25
25
  }
26
26
  function isValidAssetPath(path) {
27
- return typeof path === "string" && path.startsWith("assets/") && path.length > "assets/".length && !path.includes("\\") &&
28
- path.split("/").every((part, index) => index === 0 || (part.length > 0 && part !== "." && part !== ".."));
27
+ return typeof path === "string" && /^\/(?:[^/\\\x00-\x1f]+)(?:\/[^/\\\x00-\x1f]+)*$/.test(path) &&
28
+ path.split("/").every((part, index) => index === 0 || (part !== "." && part !== ".."));
29
29
  }
30
30
  const MAX_ASSET_UPLOAD_BYTES = 100 * 1024 * 1024;
31
31
  const TOOL_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_.-]*$/;
@@ -559,7 +559,7 @@ export class PedelecSession {
559
559
  }
560
560
  };
561
561
  }
562
- uploadAsset(file) {
562
+ uploadAsset(file, targetPath) {
563
563
  if (this.ending || this.status === "ended")
564
564
  return Promise.reject(makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId }));
565
565
  if (this.uploadPromise) {
@@ -570,8 +570,11 @@ export class PedelecSession {
570
570
  }
571
571
  if (file.size > MAX_ASSET_UPLOAD_BYTES)
572
572
  return Promise.reject(makeError("ASSET_TOO_LARGE", "asset exceeds the 100 MiB limit"));
573
+ if (targetPath !== undefined && !isValidAssetPath(targetPath))
574
+ return Promise.reject(makeError("ASSET_PATH_INVALID", "asset target path is invalid"));
573
575
  this.uploadPromise = this.client.request("create_asset_upload", {
574
576
  sessionId: this.sessionId, filename: file.name, sizeBytes: file.size, mimeType: file.type,
577
+ ...(targetPath === undefined ? {} : { targetPath }),
575
578
  }).then(async (ticket) => {
576
579
  if (!ticket?.uploadUrl || !ticket.token)
577
580
  throw makeError("SDK_PROTOCOL_ERROR", "asset upload ticket was invalid");
@@ -591,6 +594,8 @@ export class PedelecSession {
591
594
  throw normalizeError(payload?.error, "ASSET_UPLOAD_FAILED", "asset upload failed");
592
595
  if (!isValidAssetPath(payload?.path))
593
596
  throw makeError("SDK_PROTOCOL_ERROR", "asset upload response had an invalid path");
597
+ if (targetPath !== undefined && payload.path !== targetPath)
598
+ throw makeError("SDK_PROTOCOL_ERROR", "asset upload response path did not match target path");
594
599
  return payload.path;
595
600
  }).finally(() => { this.uploadPromise = null; });
596
601
  return this.uploadPromise;
@@ -96,6 +96,20 @@ async function listAssetsHasPublicTypes() {
96
96
  const path = assets[0].path;
97
97
  path;
98
98
  }
99
+ async function assetPathsUseAssetsAsAnImplicitRoot() {
100
+ const pedelec = new Pedelec();
101
+ const session = await pedelec.resumeSession("thread_1");
102
+ const file = new File(["asset"], "original.txt", { type: "text/plain" });
103
+ const generated = await session.uploadAsset(file);
104
+ generated;
105
+ const exact = await session.uploadAsset(file, "/img/image.txt");
106
+ exact;
107
+ const namedAssetsDirectory = "/assets/image.txt";
108
+ void namedAssetsDirectory;
109
+ // @ts-expect-error asset paths must begin with a slash
110
+ const missingSlash = "assets/image.txt";
111
+ void missingSlash;
112
+ }
99
113
  async function availabilityHasPublicType() {
100
114
  const pedelec = new Pedelec();
101
115
  const availability = await pedelec.checkAvailability();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaoruisaac/pedelec",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",