@ccpocket/bridge 1.6.1 → 1.8.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/dist/cli.js +14 -2
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts +49 -0
- package/dist/doctor.js +517 -0
- package/dist/doctor.js.map +1 -0
- package/dist/parser.d.ts +34 -0
- package/dist/parser.js +8 -0
- package/dist/parser.js.map +1 -1
- package/dist/sdk-process.js +6 -1
- package/dist/sdk-process.js.map +1 -1
- package/dist/session.js +3 -1
- package/dist/session.js.map +1 -1
- package/dist/websocket.d.ts +21 -0
- package/dist/websocket.js +297 -65
- package/dist/websocket.js.map +1 -1
- package/dist/worktree.d.ts +2 -0
- package/dist/worktree.js +13 -0
- package/dist/worktree.js.map +1 -1
- package/package.json +3 -2
package/dist/websocket.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { execFile, execFileSync } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile, unlink } from "node:fs/promises";
|
|
4
|
+
import { resolve, extname } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
3
6
|
import { WebSocketServer, WebSocket } from "ws";
|
|
4
7
|
import { SessionManager } from "./session.js";
|
|
5
8
|
import { SdkProcess } from "./sdk-process.js";
|
|
@@ -7,7 +10,7 @@ import { parseClientMessage } from "./parser.js";
|
|
|
7
10
|
import { getAllRecentSessions, getCodexSessionHistory, getSessionHistory, findSessionsByClaudeIds, extractMessageImages, getClaudeSessionName, loadCodexSessionNames, renameClaudeSession, renameCodexSession } from "./sessions-index.js";
|
|
8
11
|
import { ArchiveStore } from "./archive-store.js";
|
|
9
12
|
import { WorktreeStore } from "./worktree-store.js";
|
|
10
|
-
import { listWorktrees, removeWorktree, worktreeExists } from "./worktree.js";
|
|
13
|
+
import { listWorktrees, removeWorktree, worktreeExists, getMainBranch } from "./worktree.js";
|
|
11
14
|
import { listWindows, takeScreenshot } from "./screenshot.js";
|
|
12
15
|
import { DebugTraceStore } from "./debug-trace-store.js";
|
|
13
16
|
import { RecordingStore } from "./recording-store.js";
|
|
@@ -151,70 +154,76 @@ export class BridgeWebSocketServer {
|
|
|
151
154
|
}
|
|
152
155
|
switch (msg.type) {
|
|
153
156
|
case "start": {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const cached = provider === "claude" ? this.sessionManager.getCachedCommands(msg.projectPath) : undefined;
|
|
159
|
-
const sessionId = this.sessionManager.create(msg.projectPath, {
|
|
160
|
-
sessionId: msg.sessionId,
|
|
161
|
-
continueMode: msg.continue,
|
|
162
|
-
permissionMode: msg.permissionMode,
|
|
163
|
-
model: msg.model,
|
|
164
|
-
effort: msg.effort,
|
|
165
|
-
maxTurns: msg.maxTurns,
|
|
166
|
-
maxBudgetUsd: msg.maxBudgetUsd,
|
|
167
|
-
fallbackModel: msg.fallbackModel,
|
|
168
|
-
forkSession: msg.forkSession,
|
|
169
|
-
persistSession: msg.persistSession,
|
|
170
|
-
}, undefined, {
|
|
171
|
-
useWorktree: msg.useWorktree,
|
|
172
|
-
worktreeBranch: msg.worktreeBranch,
|
|
173
|
-
existingWorktreePath: msg.existingWorktreePath,
|
|
174
|
-
}, provider, provider === "codex"
|
|
175
|
-
? {
|
|
176
|
-
approvalPolicy: permissionModeToApprovalPolicy(msg.permissionMode),
|
|
177
|
-
sandboxMode: sandboxModeToInternal(msg.sandboxMode),
|
|
178
|
-
model: msg.model,
|
|
179
|
-
modelReasoningEffort: msg.modelReasoningEffort ?? undefined,
|
|
180
|
-
networkAccessEnabled: msg.networkAccessEnabled,
|
|
181
|
-
webSearchMode: msg.webSearchMode ?? undefined,
|
|
182
|
-
threadId: msg.sessionId,
|
|
183
|
-
collaborationMode: msg.permissionMode === "plan" ? "plan" : "default",
|
|
157
|
+
try {
|
|
158
|
+
const provider = msg.provider ?? "claude";
|
|
159
|
+
if (provider === "codex") {
|
|
160
|
+
console.log(`[ws] start(codex): permissionMode=${msg.permissionMode} → collaboration=${msg.permissionMode === "plan" ? "plan" : "default"}`);
|
|
184
161
|
}
|
|
185
|
-
: undefined
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
162
|
+
const cached = provider === "claude" ? this.sessionManager.getCachedCommands(msg.projectPath) : undefined;
|
|
163
|
+
const sessionId = this.sessionManager.create(msg.projectPath, {
|
|
164
|
+
sessionId: msg.sessionId,
|
|
165
|
+
continueMode: msg.continue,
|
|
166
|
+
permissionMode: msg.permissionMode,
|
|
167
|
+
model: msg.model,
|
|
168
|
+
effort: msg.effort,
|
|
169
|
+
maxTurns: msg.maxTurns,
|
|
170
|
+
maxBudgetUsd: msg.maxBudgetUsd,
|
|
171
|
+
fallbackModel: msg.fallbackModel,
|
|
172
|
+
forkSession: msg.forkSession,
|
|
173
|
+
persistSession: msg.persistSession,
|
|
174
|
+
}, undefined, {
|
|
175
|
+
useWorktree: msg.useWorktree,
|
|
176
|
+
worktreeBranch: msg.worktreeBranch,
|
|
177
|
+
existingWorktreePath: msg.existingWorktreePath,
|
|
178
|
+
}, provider, provider === "codex"
|
|
179
|
+
? {
|
|
180
|
+
approvalPolicy: permissionModeToApprovalPolicy(msg.permissionMode),
|
|
181
|
+
sandboxMode: sandboxModeToInternal(msg.sandboxMode),
|
|
182
|
+
model: msg.model,
|
|
183
|
+
modelReasoningEffort: msg.modelReasoningEffort ?? undefined,
|
|
184
|
+
networkAccessEnabled: msg.networkAccessEnabled,
|
|
185
|
+
webSearchMode: msg.webSearchMode ?? undefined,
|
|
186
|
+
threadId: msg.sessionId,
|
|
187
|
+
collaborationMode: msg.permissionMode === "plan" ? "plan" : "default",
|
|
188
|
+
}
|
|
189
|
+
: undefined);
|
|
190
|
+
const createdSession = this.sessionManager.get(sessionId);
|
|
191
|
+
// Load saved session name from CLI storage (for resumed sessions)
|
|
192
|
+
void this.loadAndSetSessionName(createdSession, provider, msg.projectPath, msg.sessionId).then(() => {
|
|
193
|
+
this.send(ws, {
|
|
194
|
+
type: "system",
|
|
195
|
+
subtype: "session_created",
|
|
196
|
+
sessionId,
|
|
197
|
+
provider,
|
|
198
|
+
projectPath: msg.projectPath,
|
|
199
|
+
...(provider === "claude" && msg.permissionMode ? { permissionMode: msg.permissionMode } : {}),
|
|
200
|
+
...(provider === "codex" && msg.sandboxMode ? { sandboxMode: msg.sandboxMode } : {}),
|
|
201
|
+
...(cached ? { slashCommands: cached.slashCommands, skills: cached.skills } : {}),
|
|
202
|
+
...(createdSession?.worktreePath ? {
|
|
203
|
+
worktreePath: createdSession.worktreePath,
|
|
204
|
+
worktreeBranch: createdSession.worktreeBranch,
|
|
205
|
+
} : {}),
|
|
206
|
+
});
|
|
207
|
+
this.broadcastSessionList();
|
|
208
|
+
});
|
|
209
|
+
this.debugEvents.set(sessionId, []);
|
|
210
|
+
this.recordDebugEvent(sessionId, {
|
|
211
|
+
direction: "internal",
|
|
212
|
+
channel: "bridge",
|
|
213
|
+
type: "session_created",
|
|
214
|
+
detail: `provider=${provider} projectPath=${msg.projectPath}`,
|
|
215
|
+
});
|
|
216
|
+
this.recordingStore.saveMeta(sessionId, {
|
|
217
|
+
bridgeSessionId: sessionId,
|
|
194
218
|
projectPath: msg.projectPath,
|
|
195
|
-
|
|
196
|
-
...(provider === "codex" && msg.sandboxMode ? { sandboxMode: msg.sandboxMode } : {}),
|
|
197
|
-
...(cached ? { slashCommands: cached.slashCommands, skills: cached.skills } : {}),
|
|
198
|
-
...(createdSession?.worktreePath ? {
|
|
199
|
-
worktreePath: createdSession.worktreePath,
|
|
200
|
-
worktreeBranch: createdSession.worktreeBranch,
|
|
201
|
-
} : {}),
|
|
219
|
+
createdAt: new Date().toISOString(),
|
|
202
220
|
});
|
|
203
|
-
this.
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
type: "session_created",
|
|
210
|
-
detail: `provider=${provider} projectPath=${msg.projectPath}`,
|
|
211
|
-
});
|
|
212
|
-
this.recordingStore.saveMeta(sessionId, {
|
|
213
|
-
bridgeSessionId: sessionId,
|
|
214
|
-
projectPath: msg.projectPath,
|
|
215
|
-
createdAt: new Date().toISOString(),
|
|
216
|
-
});
|
|
217
|
-
this.projectHistory?.addProject(msg.projectPath);
|
|
221
|
+
this.projectHistory?.addProject(msg.projectPath);
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
console.error(`[ws] Failed to start session:`, err);
|
|
225
|
+
this.send(ws, { type: "error", message: `Failed to start session: ${err.message}` });
|
|
226
|
+
}
|
|
218
227
|
break;
|
|
219
228
|
}
|
|
220
229
|
case "input": {
|
|
@@ -959,14 +968,60 @@ export class BridgeWebSocketServer {
|
|
|
959
968
|
this.send(ws, { type: "diff_result", diff: "", error: `Failed to get diff: ${error}` });
|
|
960
969
|
return;
|
|
961
970
|
}
|
|
962
|
-
this.
|
|
971
|
+
void this.collectImageChanges(msg.projectPath, diff).then((imageChanges) => {
|
|
972
|
+
if (imageChanges.length > 0) {
|
|
973
|
+
this.send(ws, { type: "diff_result", diff, imageChanges });
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
this.send(ws, { type: "diff_result", diff });
|
|
977
|
+
}
|
|
978
|
+
});
|
|
963
979
|
});
|
|
964
980
|
break;
|
|
965
981
|
}
|
|
982
|
+
case "get_diff_image": {
|
|
983
|
+
if (msg.version === "both") {
|
|
984
|
+
void (async () => {
|
|
985
|
+
try {
|
|
986
|
+
const [oldResult, newResult] = await Promise.all([
|
|
987
|
+
this.loadDiffImageAsync(msg.projectPath, msg.filePath, "old"),
|
|
988
|
+
this.loadDiffImageAsync(msg.projectPath, msg.filePath, "new"),
|
|
989
|
+
]);
|
|
990
|
+
const errors = [oldResult.error, newResult.error].filter(Boolean);
|
|
991
|
+
this.send(ws, {
|
|
992
|
+
type: "diff_image_result",
|
|
993
|
+
filePath: msg.filePath,
|
|
994
|
+
version: "both",
|
|
995
|
+
oldBase64: oldResult.base64,
|
|
996
|
+
newBase64: newResult.base64,
|
|
997
|
+
mimeType: oldResult.mimeType ?? newResult.mimeType,
|
|
998
|
+
...(errors.length > 0 ? { error: errors.join("; ") } : {}),
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
catch {
|
|
1002
|
+
// WebSocket may have closed; ignore send errors.
|
|
1003
|
+
}
|
|
1004
|
+
})();
|
|
1005
|
+
}
|
|
1006
|
+
else {
|
|
1007
|
+
const version = msg.version;
|
|
1008
|
+
void (async () => {
|
|
1009
|
+
try {
|
|
1010
|
+
const result = await this.loadDiffImageAsync(msg.projectPath, msg.filePath, version);
|
|
1011
|
+
this.send(ws, { type: "diff_image_result", filePath: msg.filePath, version, ...result });
|
|
1012
|
+
}
|
|
1013
|
+
catch {
|
|
1014
|
+
// WebSocket may have closed; ignore send errors.
|
|
1015
|
+
}
|
|
1016
|
+
})();
|
|
1017
|
+
}
|
|
1018
|
+
break;
|
|
1019
|
+
}
|
|
966
1020
|
case "list_worktrees": {
|
|
967
1021
|
try {
|
|
968
1022
|
const worktrees = listWorktrees(msg.projectPath);
|
|
969
|
-
|
|
1023
|
+
const mainBranch = getMainBranch(msg.projectPath);
|
|
1024
|
+
this.send(ws, { type: "worktree_list", worktrees, mainBranch });
|
|
970
1025
|
}
|
|
971
1026
|
catch (err) {
|
|
972
1027
|
this.send(ws, { type: "error", message: `Failed to list worktrees: ${err}` });
|
|
@@ -1542,6 +1597,183 @@ export class BridgeWebSocketServer {
|
|
|
1542
1597
|
callback({ diff: stdout });
|
|
1543
1598
|
});
|
|
1544
1599
|
}
|
|
1600
|
+
// ---------------------------------------------------------------------------
|
|
1601
|
+
// Image diff helpers
|
|
1602
|
+
// ---------------------------------------------------------------------------
|
|
1603
|
+
static IMAGE_EXTENSIONS = new Set([
|
|
1604
|
+
".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico", ".bmp", ".svg",
|
|
1605
|
+
]);
|
|
1606
|
+
// Image diff thresholds (configurable via environment variables)
|
|
1607
|
+
// - Auto-display: images ≤ threshold are sent inline as base64
|
|
1608
|
+
// - Max size: images ≤ max are available for on-demand loading
|
|
1609
|
+
// - Images > max size show text info only
|
|
1610
|
+
static AUTO_DISPLAY_THRESHOLD = (() => {
|
|
1611
|
+
const kb = parseInt(process.env.DIFF_IMAGE_AUTO_DISPLAY_KB ?? "", 10);
|
|
1612
|
+
return Number.isFinite(kb) && kb > 0 ? kb * 1024 : 1024 * 1024; // default 1 MB
|
|
1613
|
+
})();
|
|
1614
|
+
static MAX_IMAGE_SIZE = (() => {
|
|
1615
|
+
const mb = parseInt(process.env.DIFF_IMAGE_MAX_SIZE_MB ?? "", 10);
|
|
1616
|
+
return Number.isFinite(mb) && mb > 0 ? mb * 1024 * 1024 : 5 * 1024 * 1024; // default 5 MB
|
|
1617
|
+
})();
|
|
1618
|
+
static mimeTypeForExt(ext) {
|
|
1619
|
+
const map = {
|
|
1620
|
+
".png": "image/png",
|
|
1621
|
+
".jpg": "image/jpeg",
|
|
1622
|
+
".jpeg": "image/jpeg",
|
|
1623
|
+
".gif": "image/gif",
|
|
1624
|
+
".webp": "image/webp",
|
|
1625
|
+
".ico": "image/x-icon",
|
|
1626
|
+
".bmp": "image/bmp",
|
|
1627
|
+
".svg": "image/svg+xml",
|
|
1628
|
+
};
|
|
1629
|
+
return map[ext.toLowerCase()] ?? "application/octet-stream";
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1632
|
+
* Scan diff text for image file changes and extract base64 data where appropriate.
|
|
1633
|
+
*
|
|
1634
|
+
* Detection strategy:
|
|
1635
|
+
* 1. Binary markers: "Binary files a/<path> and b/<path> differ"
|
|
1636
|
+
* 2. diff --git headers where the file extension is an image type
|
|
1637
|
+
*
|
|
1638
|
+
* For each detected image file:
|
|
1639
|
+
* - Old version: `git show HEAD:<path>` (committed version)
|
|
1640
|
+
* - New version: read from working tree
|
|
1641
|
+
* - Apply size thresholds for auto-display / on-demand / text-only
|
|
1642
|
+
*/
|
|
1643
|
+
async collectImageChanges(cwd, diffText) {
|
|
1644
|
+
const entries = [];
|
|
1645
|
+
const processedPaths = new Set();
|
|
1646
|
+
const lines = diffText.split("\n");
|
|
1647
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1648
|
+
const line = lines[i];
|
|
1649
|
+
const gitMatch = line.match(/^diff --git a\/(.+?) b\/(.+)$/);
|
|
1650
|
+
if (!gitMatch)
|
|
1651
|
+
continue;
|
|
1652
|
+
const filePath = gitMatch[2];
|
|
1653
|
+
const ext = extname(filePath).toLowerCase();
|
|
1654
|
+
if (!BridgeWebSocketServer.IMAGE_EXTENSIONS.has(ext))
|
|
1655
|
+
continue;
|
|
1656
|
+
if (processedPaths.has(filePath))
|
|
1657
|
+
continue;
|
|
1658
|
+
processedPaths.add(filePath);
|
|
1659
|
+
let isNew = false;
|
|
1660
|
+
let isDeleted = false;
|
|
1661
|
+
for (let j = i + 1; j < Math.min(i + 6, lines.length); j++) {
|
|
1662
|
+
if (lines[j].startsWith("diff --git "))
|
|
1663
|
+
break;
|
|
1664
|
+
if (lines[j].startsWith("new file mode"))
|
|
1665
|
+
isNew = true;
|
|
1666
|
+
if (lines[j].startsWith("deleted file mode"))
|
|
1667
|
+
isDeleted = true;
|
|
1668
|
+
}
|
|
1669
|
+
entries.push({
|
|
1670
|
+
filePath,
|
|
1671
|
+
isNew,
|
|
1672
|
+
isDeleted,
|
|
1673
|
+
isSvg: ext === ".svg",
|
|
1674
|
+
mimeType: BridgeWebSocketServer.mimeTypeForExt(ext),
|
|
1675
|
+
ext,
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
if (entries.length === 0)
|
|
1679
|
+
return [];
|
|
1680
|
+
// Phase 2: Read image data asynchronously
|
|
1681
|
+
const execFileAsync = promisify(execFile);
|
|
1682
|
+
const changes = [];
|
|
1683
|
+
for (const entry of entries) {
|
|
1684
|
+
let oldBuf;
|
|
1685
|
+
let newBuf;
|
|
1686
|
+
// Read old image (committed version)
|
|
1687
|
+
if (!entry.isNew) {
|
|
1688
|
+
try {
|
|
1689
|
+
const result = await execFileAsync("git", ["show", `HEAD:${entry.filePath}`], {
|
|
1690
|
+
cwd,
|
|
1691
|
+
maxBuffer: BridgeWebSocketServer.MAX_IMAGE_SIZE + 1024,
|
|
1692
|
+
encoding: "buffer",
|
|
1693
|
+
});
|
|
1694
|
+
oldBuf = result.stdout;
|
|
1695
|
+
}
|
|
1696
|
+
catch {
|
|
1697
|
+
// File may not exist in HEAD (e.g. untracked)
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
// Read new image (working tree)
|
|
1701
|
+
if (!entry.isDeleted) {
|
|
1702
|
+
try {
|
|
1703
|
+
const absPath = resolve(cwd, entry.filePath);
|
|
1704
|
+
if (existsSync(absPath)) {
|
|
1705
|
+
newBuf = await readFile(absPath);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
catch {
|
|
1709
|
+
// Ignore read errors
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
const oldSize = oldBuf?.length;
|
|
1713
|
+
const newSize = newBuf?.length;
|
|
1714
|
+
const maxSize = Math.max(oldSize ?? 0, newSize ?? 0);
|
|
1715
|
+
const autoDisplay = maxSize <= BridgeWebSocketServer.AUTO_DISPLAY_THRESHOLD;
|
|
1716
|
+
const loadable = autoDisplay || maxSize <= BridgeWebSocketServer.MAX_IMAGE_SIZE;
|
|
1717
|
+
const change = {
|
|
1718
|
+
filePath: entry.filePath,
|
|
1719
|
+
isNew: entry.isNew,
|
|
1720
|
+
isDeleted: entry.isDeleted,
|
|
1721
|
+
isSvg: entry.isSvg,
|
|
1722
|
+
mimeType: entry.mimeType,
|
|
1723
|
+
loadable,
|
|
1724
|
+
autoDisplay: autoDisplay || undefined,
|
|
1725
|
+
};
|
|
1726
|
+
if (oldSize !== undefined)
|
|
1727
|
+
change.oldSize = oldSize;
|
|
1728
|
+
if (newSize !== undefined)
|
|
1729
|
+
change.newSize = newSize;
|
|
1730
|
+
// Auto-display images are no longer embedded in the initial response.
|
|
1731
|
+
// They are loaded on-demand when the Flutter widget becomes visible.
|
|
1732
|
+
changes.push(change);
|
|
1733
|
+
}
|
|
1734
|
+
return changes;
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Load a single diff image on demand (async I/O for better throughput).
|
|
1738
|
+
*/
|
|
1739
|
+
async loadDiffImageAsync(cwd, filePath, version) {
|
|
1740
|
+
// Path traversal guard: reject paths containing '..' or absolute paths
|
|
1741
|
+
if (filePath.includes("..") || filePath.startsWith("/")) {
|
|
1742
|
+
return { error: "Invalid file path" };
|
|
1743
|
+
}
|
|
1744
|
+
const ext = extname(filePath).toLowerCase();
|
|
1745
|
+
if (!BridgeWebSocketServer.IMAGE_EXTENSIONS.has(ext)) {
|
|
1746
|
+
return { error: "Not an image file" };
|
|
1747
|
+
}
|
|
1748
|
+
const mimeType = BridgeWebSocketServer.mimeTypeForExt(ext);
|
|
1749
|
+
try {
|
|
1750
|
+
const execFileAsync = promisify(execFile);
|
|
1751
|
+
let buf;
|
|
1752
|
+
if (version === "old") {
|
|
1753
|
+
const result = await execFileAsync("git", ["show", `HEAD:${filePath}`], {
|
|
1754
|
+
cwd,
|
|
1755
|
+
maxBuffer: BridgeWebSocketServer.MAX_IMAGE_SIZE + 1024,
|
|
1756
|
+
encoding: "buffer",
|
|
1757
|
+
});
|
|
1758
|
+
buf = result.stdout;
|
|
1759
|
+
}
|
|
1760
|
+
else {
|
|
1761
|
+
const absPath = resolve(cwd, filePath);
|
|
1762
|
+
// Verify resolved path stays within cwd
|
|
1763
|
+
if (!absPath.startsWith(resolve(cwd) + "/")) {
|
|
1764
|
+
return { error: "Invalid file path" };
|
|
1765
|
+
}
|
|
1766
|
+
buf = await readFile(absPath);
|
|
1767
|
+
}
|
|
1768
|
+
if (buf.length > BridgeWebSocketServer.MAX_IMAGE_SIZE) {
|
|
1769
|
+
return { error: "Image too large" };
|
|
1770
|
+
}
|
|
1771
|
+
return { base64: buf.toString("base64"), mimeType };
|
|
1772
|
+
}
|
|
1773
|
+
catch (err) {
|
|
1774
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1545
1777
|
extractSessionIdFromClientMessage(msg) {
|
|
1546
1778
|
return "sessionId" in msg && typeof msg.sessionId === "string" ? msg.sessionId : undefined;
|
|
1547
1779
|
}
|