@chatpanel/bridge 0.10.2 → 0.10.4
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/package.json +1 -1
- package/src/engines/antigravity.js +2 -15
- package/src/engines/claude.js +3 -16
- package/src/engines/codex.js +2 -14
- package/src/engines/custom.js +2 -15
- package/src/engines/prompt.js +28 -0
- package/src/mcp-local.js +0 -0
- package/src/server.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatpanel/bridge",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local bridge that exposes the AI coding agents installed on your machine — Claude Code (CLI), Codex (CLI), and Gemini CLI — to the ChatPanel Chrome extension over a localhost SSE endpoint. Bring your own agent.",
|
|
6
6
|
"keywords": [
|
|
@@ -15,6 +15,7 @@ import { mkdirSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
|
15
15
|
import os from 'node:os';
|
|
16
16
|
import path from 'node:path';
|
|
17
17
|
import { findAgentBin } from '../env.js';
|
|
18
|
+
import { buildCliPrompt } from './prompt.js';
|
|
18
19
|
|
|
19
20
|
const IDLE_MS = Number(process.env.CHATPANEL_AGY_TIMEOUT_MS) || 180_000;
|
|
20
21
|
const SCRATCH = path.join(os.tmpdir(), 'chatpanel-agy-scratch');
|
|
@@ -77,20 +78,6 @@ function writeImages(images, dir) {
|
|
|
77
78
|
return files;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
// The bridge is stateless, so replay the conversation as a single prompt.
|
|
81
|
-
function buildPrompt(messages, system) {
|
|
82
|
-
let p = system ? `${system}\n\n` : '';
|
|
83
|
-
const history = messages.slice(0, -1);
|
|
84
|
-
const last = messages[messages.length - 1];
|
|
85
|
-
if (history.length) {
|
|
86
|
-
p += 'Conversation so far:\n';
|
|
87
|
-
for (const m of history) p += `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}\n\n`;
|
|
88
|
-
p += '---\n\n';
|
|
89
|
-
}
|
|
90
|
-
p += last ? last.content : '';
|
|
91
|
-
return p;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
81
|
export async function chat({ messages, system, options, images }, emit) {
|
|
95
82
|
try {
|
|
96
83
|
mkdirSync(SCRATCH, { recursive: true });
|
|
@@ -104,7 +91,7 @@ export async function chat({ messages, system, options, images }, emit) {
|
|
|
104
91
|
// read-tool approval is needed in headless `-p` mode. (Confirmed working.)
|
|
105
92
|
const imageFiles = writeImages(images, cwd);
|
|
106
93
|
const cleanup = () => imageFiles.forEach((f) => { try { unlinkSync(f); } catch { /* gone */ } });
|
|
107
|
-
let prompt =
|
|
94
|
+
let prompt = buildCliPrompt(messages, system);
|
|
108
95
|
if (imageFiles.length) {
|
|
109
96
|
prompt += `\n\nThe user attached image(s): ${imageFiles.map((f) => '@' + path.basename(f)).join(' ')}`;
|
|
110
97
|
}
|
package/src/engines/claude.js
CHANGED
|
@@ -18,6 +18,7 @@ import { writeFile, unlink } from 'node:fs/promises';
|
|
|
18
18
|
import os from 'node:os';
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import { resolveClaude, buildSpawnSpec, isCompiledBinary, selfMcpStdio } from '../env.js';
|
|
21
|
+
import { buildCliPrompt } from './prompt.js';
|
|
21
22
|
|
|
22
23
|
// Write base64 data-URL images to temp files. Claude Code reads them with its
|
|
23
24
|
// Read tool (which feeds images to the model as vision), so we just reference the
|
|
@@ -79,20 +80,6 @@ export async function listModels() {
|
|
|
79
80
|
return ['opus', 'sonnet', 'haiku'];
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
// The bridge is stateless, so we replay the conversation as a single prompt.
|
|
83
|
-
function buildPrompt(messages) {
|
|
84
|
-
const history = messages.slice(0, -1);
|
|
85
|
-
const last = messages[messages.length - 1];
|
|
86
|
-
let prompt = '';
|
|
87
|
-
if (history.length) {
|
|
88
|
-
prompt += 'Conversation so far:\n';
|
|
89
|
-
for (const m of history) prompt += `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}\n\n`;
|
|
90
|
-
prompt += '---\n\n';
|
|
91
|
-
}
|
|
92
|
-
prompt += last ? last.content : '';
|
|
93
|
-
return prompt;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
83
|
export function claudeMcpConfig(mcp) {
|
|
97
84
|
if (!mcp?.url || !Array.isArray(mcp.specs) || !mcp.specs.length) return null;
|
|
98
85
|
const serverName = mcp.serverName || 'chatpanel_browser';
|
|
@@ -250,7 +237,7 @@ export async function chat({ messages, system, options, images }, emit) {
|
|
|
250
237
|
imageFiles.forEach((f) => unlink(f).catch(() => {}));
|
|
251
238
|
mcpFiles.forEach((f) => unlink(f).catch(() => {}));
|
|
252
239
|
};
|
|
253
|
-
let prompt =
|
|
240
|
+
let prompt = buildCliPrompt(messages);
|
|
254
241
|
if (imageFiles.length) {
|
|
255
242
|
prompt += `\n\nThe user attached ${imageFiles.length} image file(s). Use the Read tool to view ${
|
|
256
243
|
imageFiles.length === 1 ? 'it' : 'them'
|
|
@@ -336,7 +323,7 @@ async function sdkChat({ messages, system, options }, emit) {
|
|
|
336
323
|
let streamedAny = false;
|
|
337
324
|
let resultText = '';
|
|
338
325
|
const iterator = query({
|
|
339
|
-
prompt:
|
|
326
|
+
prompt: buildCliPrompt(messages),
|
|
340
327
|
options: {
|
|
341
328
|
cwd,
|
|
342
329
|
permissionMode,
|
package/src/engines/codex.js
CHANGED
|
@@ -20,6 +20,7 @@ import { existsSync, mkdirSync, symlinkSync, readFileSync } from 'node:fs';
|
|
|
20
20
|
import os from 'node:os';
|
|
21
21
|
import path from 'node:path';
|
|
22
22
|
import { findAgentBin, selfMcpStdio } from '../env.js';
|
|
23
|
+
import { buildCliPrompt } from './prompt.js';
|
|
23
24
|
|
|
24
25
|
// Idle timeout: re-armed on every stdout/stderr chunk, so a long run that keeps
|
|
25
26
|
// streaming never trips it — only true silence does. Override with
|
|
@@ -97,19 +98,6 @@ export async function available() {
|
|
|
97
98
|
: { ok: false, reason: 'codex not found on PATH. Install it and run `codex login`.' };
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
function buildPrompt(messages, system) {
|
|
101
|
-
let p = system ? `${system}\n\n` : '';
|
|
102
|
-
const history = messages.slice(0, -1);
|
|
103
|
-
const last = messages[messages.length - 1];
|
|
104
|
-
if (history.length) {
|
|
105
|
-
p += 'Conversation so far:\n';
|
|
106
|
-
for (const m of history) p += `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}\n\n`;
|
|
107
|
-
p += '---\n\n';
|
|
108
|
-
}
|
|
109
|
-
p += last ? last.content : '';
|
|
110
|
-
return p;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
101
|
export function codexMcpConfigArgs(mcp) {
|
|
114
102
|
if (!mcp?.url) return [];
|
|
115
103
|
const name = mcp.serverName || 'chatpanel_browser';
|
|
@@ -247,7 +235,7 @@ export async function chat({ messages, system, options, images }, emit) {
|
|
|
247
235
|
}
|
|
248
236
|
});
|
|
249
237
|
|
|
250
|
-
child.stdin.write(
|
|
238
|
+
child.stdin.write(buildCliPrompt(messages, system));
|
|
251
239
|
child.stdin.end();
|
|
252
240
|
});
|
|
253
241
|
}
|
package/src/engines/custom.js
CHANGED
|
@@ -22,6 +22,7 @@ import path from 'node:path';
|
|
|
22
22
|
import { resolveCommand, buildSpawnSpec, selfMcpStdio } from '../env.js';
|
|
23
23
|
import { isProEntitled } from '../entitlement.js';
|
|
24
24
|
import { handleMessage } from './claude.js';
|
|
25
|
+
import { buildCliPrompt } from './prompt.js';
|
|
25
26
|
|
|
26
27
|
// Write base64 data-URL images to temp files so a custom CLI can take them via
|
|
27
28
|
// its configured `imageArg` template (e.g. "-i {path}", "@{path}"). Returns paths.
|
|
@@ -125,20 +126,6 @@ export async function listSpecModels(command, listModelsArgs, workingDir) {
|
|
|
125
126
|
return parseModelList(stdout);
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
// The bridge is stateless, so replay the conversation as a single prompt.
|
|
129
|
-
function buildPrompt(messages, system) {
|
|
130
|
-
let p = system ? `${system}\n\n` : '';
|
|
131
|
-
const history = messages.slice(0, -1);
|
|
132
|
-
const last = messages[messages.length - 1];
|
|
133
|
-
if (history.length) {
|
|
134
|
-
p += 'Conversation so far:\n';
|
|
135
|
-
for (const m of history) p += `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}\n\n`;
|
|
136
|
-
p += '---\n\n';
|
|
137
|
-
}
|
|
138
|
-
p += last ? last.content : '';
|
|
139
|
-
return p;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
129
|
function mcpToolSpecs(mcp) {
|
|
143
130
|
return (mcp?.specs || []).filter((s) => s?.name);
|
|
144
131
|
}
|
|
@@ -272,7 +259,7 @@ export async function runSpec(spec, { messages, system, options = {}, images },
|
|
|
272
259
|
throw new Error(`Couldn't find "${spec.command}". Enter its full path, or install it on your PATH (or in WSL).`);
|
|
273
260
|
}
|
|
274
261
|
|
|
275
|
-
const prompt =
|
|
262
|
+
const prompt = buildCliPrompt(messages, system);
|
|
276
263
|
let cwd = options.workingDir ? path.resolve(options.workingDir) : null;
|
|
277
264
|
const label = spec.label || spec.command;
|
|
278
265
|
const fmt = ['claude-stream-json', 'opencode-json'].includes(spec.format) ? spec.format : 'text';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function roleLabel(role) {
|
|
2
|
+
return role === 'assistant' ? 'Assistant' : 'User';
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function buildCliPrompt(messages = [], system = '') {
|
|
6
|
+
const cleanSystem = String(system || '').trim();
|
|
7
|
+
const list = Array.isArray(messages) ? messages : [];
|
|
8
|
+
const history = list.slice(0, -1);
|
|
9
|
+
const current = list[list.length - 1];
|
|
10
|
+
const parts = [];
|
|
11
|
+
|
|
12
|
+
if (cleanSystem) parts.push(cleanSystem);
|
|
13
|
+
|
|
14
|
+
if (history.length) {
|
|
15
|
+
const transcript = history
|
|
16
|
+
.map((m) => `${roleLabel(m.role)}: ${String(m.content || '').trim()}`)
|
|
17
|
+
.join('\n\n');
|
|
18
|
+
parts.push(`Conversation history (context only; do not answer these prior turns):\n${transcript}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (current) {
|
|
22
|
+
parts.push(
|
|
23
|
+
`Current ${roleLabel(current.role).toLowerCase()} message (answer this message now; use the history above only as context):\n${String(current.content || '').trim()}`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return parts.join('\n\n---\n\n');
|
|
28
|
+
}
|
package/src/mcp-local.js
CHANGED
|
Binary file
|
package/src/server.js
CHANGED
|
@@ -30,7 +30,7 @@ import { callLocalMcp } from './mcp-local.js';
|
|
|
30
30
|
// Hardcoded (not read from package.json) so it survives Bun's single-file
|
|
31
31
|
// --compile, where package.json isn't on a readable FS. CI fails the publish if
|
|
32
32
|
// this drifts from package.json, so the two can't silently diverge.
|
|
33
|
-
const VERSION = '0.10.
|
|
33
|
+
const VERSION = '0.10.4';
|
|
34
34
|
const HOST = process.env.CHATPANEL_BRIDGE_HOST || '127.0.0.1';
|
|
35
35
|
const PORT = Number(process.env.CHATPANEL_BRIDGE_PORT) || 4319;
|
|
36
36
|
|