@vellumai/credential-executor 0.10.7-dev.202607091111.4355283 → 0.10.7-dev.202607091507.bac9bfe

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.
@@ -16,6 +16,7 @@
16
16
  "./grants": "./src/grants.ts",
17
17
  "./rpc": "./src/rpc.ts",
18
18
  "./rendering": "./src/rendering.ts",
19
+ "./attachment-naming": "./src/attachment-naming.ts",
19
20
  "./error": "./src/error.ts"
20
21
  },
21
22
  "scripts": {
@@ -0,0 +1,104 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ inferMimeType,
5
+ resolveAttachmentFilename,
6
+ } from "../attachment-naming.js";
7
+
8
+ describe("inferMimeType", () => {
9
+ test("maps known extensions case-insensitively", () => {
10
+ expect(inferMimeType("shot.PNG")).toBe("image/png");
11
+ expect(inferMimeType("report.pdf")).toBe("application/pdf");
12
+ });
13
+
14
+ test("falls back to octet-stream for unknown or missing extensions", () => {
15
+ expect(inferMimeType("desktop")).toBe("application/octet-stream");
16
+ expect(inferMimeType("archive.xyz")).toBe("application/octet-stream");
17
+ });
18
+ });
19
+
20
+ describe("resolveAttachmentFilename", () => {
21
+ test("uses explicit filenames verbatim", () => {
22
+ expect(
23
+ resolveAttachmentFilename("custom.dat", "/tmp/shot.png", "explicit"),
24
+ ).toBe("custom.dat");
25
+ });
26
+
27
+ test("honors labels with recognized extensions", () => {
28
+ expect(
29
+ resolveAttachmentFilename("nice-name.png", "/tmp/shot.png", "label"),
30
+ ).toBe("nice-name.png");
31
+ });
32
+
33
+ test("keeps bare labels and appends the POSIX path extension", () => {
34
+ expect(resolveAttachmentFilename("desktop", "/tmp/shot.png", "label")).toBe(
35
+ "desktop.png",
36
+ );
37
+ });
38
+
39
+ test("keeps bare labels and appends the Windows path extension", () => {
40
+ expect(
41
+ resolveAttachmentFilename(
42
+ "desktop",
43
+ "C:\\Users\\user1\\Pictures\\shot.png",
44
+ "label",
45
+ ),
46
+ ).toBe("desktop.png");
47
+ });
48
+
49
+ test("bare labels to files sharing a basename resolve to unique names", () => {
50
+ expect(
51
+ resolveAttachmentFilename("first", "/workspace/a/result.png", "label"),
52
+ ).toBe("first.png");
53
+ expect(
54
+ resolveAttachmentFilename("second", "/workspace/b/result.png", "label"),
55
+ ).toBe("second.png");
56
+ });
57
+
58
+ test("does not duplicate an unknown extension the label already has", () => {
59
+ expect(
60
+ resolveAttachmentFilename(
61
+ "report.xlsx",
62
+ "/workspace/report.xlsx",
63
+ "label",
64
+ ),
65
+ ).toBe("report.xlsx");
66
+ });
67
+
68
+ test("matches the existing extension case-insensitively", () => {
69
+ expect(
70
+ resolveAttachmentFilename("data.YAML", "/workspace/data.yaml", "label"),
71
+ ).toBe("data.YAML");
72
+ });
73
+
74
+ test("still appends when the label ends differently", () => {
75
+ expect(
76
+ resolveAttachmentFilename(
77
+ "summary.v2",
78
+ "/workspace/data.parquet",
79
+ "label",
80
+ ),
81
+ ).toBe("summary.v2.parquet");
82
+ });
83
+
84
+ test("falls back to the basename when the path has no extension", () => {
85
+ expect(resolveAttachmentFilename("label", "/tmp/rawfile", "label")).toBe(
86
+ "rawfile",
87
+ );
88
+ });
89
+
90
+ test("uses the basename when no preferred name is given", () => {
91
+ expect(
92
+ resolveAttachmentFilename(undefined, "C:\\temp\\report.pdf", "label"),
93
+ ).toBe("report.pdf");
94
+ expect(resolveAttachmentFilename(undefined, "/tmp/report.pdf")).toBe(
95
+ "report.pdf",
96
+ );
97
+ });
98
+
99
+ test("ignores trailing separators", () => {
100
+ expect(resolveAttachmentFilename(undefined, "/tmp/dir/", "label")).toBe(
101
+ "dir",
102
+ );
103
+ });
104
+ });
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Attachment filename contract shared by the daemon (which materializes
3
+ * `vellum://` links and `<vellum-attachment>` directives into stored
4
+ * attachments) and the web client (which resolves clicked links back to
5
+ * those attachments). Both sides must agree on the stored filename, so the
6
+ * naming rule lives here once.
7
+ */
8
+
9
+ const EXTENSION_MIME_MAP: Record<string, string> = {
10
+ // Images
11
+ png: "image/png",
12
+ jpg: "image/jpeg",
13
+ jpeg: "image/jpeg",
14
+ gif: "image/gif",
15
+ webp: "image/webp",
16
+ svg: "image/svg+xml",
17
+ ico: "image/x-icon",
18
+ bmp: "image/bmp",
19
+ heic: "image/heic",
20
+ heif: "image/heif",
21
+
22
+ // Documents
23
+ pdf: "application/pdf",
24
+ json: "application/json",
25
+ xml: "application/xml",
26
+ csv: "text/csv",
27
+ txt: "text/plain",
28
+ md: "text/markdown",
29
+ html: "text/html",
30
+ css: "text/css",
31
+ js: "text/javascript",
32
+ ts: "text/typescript",
33
+
34
+ // Audio
35
+ mp3: "audio/mpeg",
36
+ wav: "audio/wav",
37
+ ogg: "audio/ogg",
38
+ flac: "audio/flac",
39
+ aac: "audio/aac",
40
+ m4a: "audio/x-m4a",
41
+ opus: "audio/opus",
42
+
43
+ // Video
44
+ mp4: "video/mp4",
45
+ webm: "video/webm",
46
+ mov: "video/quicktime",
47
+ mpeg: "video/mpeg",
48
+
49
+ // Archives
50
+ zip: "application/zip",
51
+ gz: "application/gzip",
52
+ tar: "application/x-tar",
53
+ };
54
+
55
+ /**
56
+ * Infer a MIME type from a filename extension.
57
+ * Returns `application/octet-stream` when the extension is unrecognised.
58
+ */
59
+ export function inferMimeType(filename: string): string {
60
+ const dot = filename.lastIndexOf(".");
61
+ if (dot === -1) {
62
+ return "application/octet-stream";
63
+ }
64
+ const ext = filename.slice(dot + 1).toLowerCase();
65
+ return EXTENSION_MIME_MAP[ext] ?? "application/octet-stream";
66
+ }
67
+
68
+ /**
69
+ * Pick the stored attachment filename for a materialized file directive.
70
+ *
71
+ * Explicit filenames (`<vellum-attachment filename="..." />` attributes)
72
+ * are an authoring contract and always used verbatim. Link display text
73
+ * (`[label](vellum://...)`) is cosmetic: it is only honored as the filename
74
+ * when it carries a recognized extension. A bare label
75
+ * (e.g. `[desktop](vellum://.../shot.png)`) would otherwise produce an
76
+ * extensionless download with an `application/octet-stream` MIME type,
77
+ * which macOS opens as raw text. In that case the label keeps the path's
78
+ * extension (`desktop.png`), staying unique per link even when several
79
+ * links share a basename; the raw basename is the last resort.
80
+ *
81
+ * The basename fallback splits on both `/` and `\\` so it handles POSIX
82
+ * sandbox paths, `vellum://` URL paths, and Windows host paths alike.
83
+ * Splitting on both separators regardless of the runtime platform also
84
+ * covers Windows host paths resolved by a non-Windows daemon, which
85
+ * platform-selected `node:path` semantics cannot.
86
+ */
87
+ export function resolveAttachmentFilename(
88
+ preferred: string | undefined,
89
+ resolvedPath: string,
90
+ filenameSource: "explicit" | "label" = "explicit",
91
+ ): string {
92
+ const fallback =
93
+ resolvedPath.split(/[\\/]/).filter(Boolean).pop() ?? resolvedPath;
94
+ if (!preferred) {
95
+ return fallback;
96
+ }
97
+ if (filenameSource === "explicit") {
98
+ return preferred;
99
+ }
100
+ if (inferMimeType(preferred) !== "application/octet-stream") {
101
+ return preferred;
102
+ }
103
+ // Bare label: keep the label as a per-link disambiguator (two links to
104
+ // different files sharing a basename must not collide, because the
105
+ // transcript resolves clicks by filename) and append the path's
106
+ // extension so the stored name carries the correct type. Skip the
107
+ // append when the label already ends with that extension (a label like
108
+ // `report.xlsx` for `report.xlsx` must not become `report.xlsx.xlsx`).
109
+ const dot = fallback.lastIndexOf(".");
110
+ if (dot > 0 && dot < fallback.length - 1) {
111
+ const ext = fallback.slice(dot + 1);
112
+ if (preferred.toLowerCase().endsWith(`.${ext.toLowerCase()}`)) {
113
+ return preferred;
114
+ }
115
+ return `${preferred}.${ext}`;
116
+ }
117
+ return fallback;
118
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/credential-executor",
3
- "version": "0.10.7-dev.202607091111.4355283",
3
+ "version": "0.10.7-dev.202607091507.bac9bfe",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
package/src/main.ts CHANGED
@@ -610,6 +610,7 @@ function serveStandaloneSocket(opts: {
610
610
  });
611
611
 
612
612
  netServer.on("connection", (socket: Socket) => {
613
+ connectionCount++;
613
614
  const readable = new Readable({ read() {} });
614
615
  const writable = new Writable({
615
616
  write(chunk, _encoding, callback) {
@@ -634,7 +635,6 @@ function serveStandaloneSocket(opts: {
634
635
  logger,
635
636
  signal,
636
637
  onHandshakeComplete: (sessionId, apiKey, assistantId) => {
637
- rpcConnected = true;
638
638
  onHandshakeComplete?.(sessionId, apiKey, assistantId);
639
639
  },
640
640
  onApiKeyUpdate: onApiKeyUpdate ?? (() => {}),
@@ -646,7 +646,7 @@ function serveStandaloneSocket(opts: {
646
646
  "CES connection ended with a transport error",
647
647
  );
648
648
  }).then(() => {
649
- rpcConnected = false;
649
+ connectionCount = Math.max(0, connectionCount - 1);
650
650
  });
651
651
  });
652
652
 
@@ -672,7 +672,7 @@ function serveStandaloneSocket(opts: {
672
672
  // Health server (managed mode only)
673
673
  // ---------------------------------------------------------------------------
674
674
 
675
- let rpcConnected = false;
675
+ let connectionCount = 0;
676
676
 
677
677
  function startHealthServer(
678
678
  port: number,
@@ -695,7 +695,7 @@ function startHealthServer(
695
695
  // scheduling during dark-launch. The sidecar can't do useful work
696
696
  // without a connection anyway, so readiness is purely about the
697
697
  // process being up and able to accept a future connection.
698
- return new Response(JSON.stringify({ status: "ok", rpcConnected }), {
698
+ return new Response(JSON.stringify({ status: "ok", connections: connectionCount, rpcConnected: connectionCount > 0 }), {
699
699
  status: 200,
700
700
  headers: { "Content-Type": "application/json" },
701
701
  });
@@ -834,11 +834,13 @@ async function main(): Promise<void> {
834
834
  log,
835
835
  onHandshakeComplete: refs
836
836
  ? (_hsSessionId, hsApiKey, hsAssistantId) => {
837
- // Overwrite the credential refs on every handshake. The handler
838
- // registry persists across reconnects, so a new session that omits
839
- // the API key / assistant ID must fail closed (falling back to the
840
- // env key, or no key) rather than reusing the previous session's
841
- // credentials.
837
+ // Update the process-scoped credential refs on every handshake.
838
+ // With multi-client CES, multiple connections from the same
839
+ // assistant instance share these refs. All connections belong to
840
+ // the same guardian, so they carry the same API key - last
841
+ // handshake wins is correct. A connection that omits the key
842
+ // fails closed (falling back to the env key, or no key) rather
843
+ // than reusing a previous session's credentials.
842
844
  applyManagedCredentialRefs(
843
845
  refs.apiKeyRef,
844
846
  refs.assistantIdRef,