@1presence/bridge 0.51.0 → 0.54.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/claude.js +2 -2
- package/dist/index.js +100 -3
- package/package.json +1 -1
package/dist/claude.js
CHANGED
|
@@ -234,7 +234,7 @@ async function* promptStream(messages) {
|
|
|
234
234
|
}
|
|
235
235
|
// ─── Spawn (drive one turn through the SDK) ──────────────────────────────────────
|
|
236
236
|
export function spawnClaude(params) {
|
|
237
|
-
const { conversationId, presenceSessionId, text, uid, history, vaultFileOpen, clientCapabilities, syncedFolders, onEvent, onDone, onError, onNotice } = params;
|
|
237
|
+
const { conversationId, presenceSessionId, text, uid, history, vaultFileOpen, clientCapabilities, syncedFolders, model: perTurnModel, onEvent, onDone, onError, onNotice } = params;
|
|
238
238
|
const systemPromptPath = join(tmpdir(), `agent-${uid}.md`);
|
|
239
239
|
const mcpConfigPath = join(tmpdir(), `mcp-${uid}.json`);
|
|
240
240
|
if (verbose) {
|
|
@@ -337,7 +337,7 @@ export function spawnClaude(params) {
|
|
|
337
337
|
if (!safeEnv['MAX_MCP_OUTPUT_TOKENS']) {
|
|
338
338
|
safeEnv['MAX_MCP_OUTPUT_TOKENS'] = '200000';
|
|
339
339
|
}
|
|
340
|
-
const pinnedModel = getBridgeModel();
|
|
340
|
+
const pinnedModel = perTurnModel ?? getBridgeModel();
|
|
341
341
|
// Process one translated raw stream-json event: bookkeeping + forward. Mirrors
|
|
342
342
|
// the old CLI stdout parser so the gateway/accumulator see identical shapes.
|
|
343
343
|
// Returns false when the event must be suppressed (errors) or the turn was
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { tmpdir } from 'os';
|
|
|
5
5
|
import { join, dirname } from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
|
+
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
8
9
|
import { getValidAuth, ensureFreshToken, forceRefreshToken, isTokenValid, AuthCancelledError } from './auth.js';
|
|
9
10
|
import { spawnClaude, killAll, cancelConversation, setVerbose, setDebug, paint, SECTION_COLORS } from './claude.js';
|
|
10
11
|
import { ensureModelChoice } from './config.js';
|
|
@@ -218,8 +219,91 @@ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
|
|
|
218
219
|
function isUuid(value) {
|
|
219
220
|
return UUID_RE.test(value);
|
|
220
221
|
}
|
|
222
|
+
// ─── Bridge vision reconstruction (no MCP, no chat turn) ──────────────────────
|
|
223
|
+
//
|
|
224
|
+
// A lightweight path for doc reproduction: the gateway sends a signed GCS URL +
|
|
225
|
+
// system prompt. We download the document and call query() with NO MCP tools —
|
|
226
|
+
// just a direct vision call using the user's claude.ai subscription. The whole
|
|
227
|
+
// reply is collected into one string and sent back as doc_recon_response.
|
|
228
|
+
async function handleDocReconRequest(req) {
|
|
229
|
+
const send = (payload) => {
|
|
230
|
+
if (currentWs?.readyState === WebSocket.OPEN)
|
|
231
|
+
currentWs.send(JSON.stringify(payload));
|
|
232
|
+
};
|
|
233
|
+
let base64;
|
|
234
|
+
let mediaType;
|
|
235
|
+
try {
|
|
236
|
+
const res = await fetch(req.sourceUrl);
|
|
237
|
+
if (!res.ok)
|
|
238
|
+
throw new Error(`download failed: ${res.status}`);
|
|
239
|
+
const buf = await res.arrayBuffer();
|
|
240
|
+
base64 = Buffer.from(buf).toString('base64');
|
|
241
|
+
mediaType = req.sourceContentType || 'application/pdf';
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
console.error(`[bridge] doc_recon: download failed (reqId: ${req.reqId}): ${err.message}`);
|
|
245
|
+
send({ type: 'doc_recon_response', reqId: req.reqId, error: `download failed: ${err.message}` });
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const isImage = mediaType.startsWith('image/');
|
|
249
|
+
const sourceBlock = isImage
|
|
250
|
+
? { type: 'image', source: { type: 'base64', media_type: mediaType, data: base64 } }
|
|
251
|
+
: { type: 'document', source: { type: 'base64', media_type: 'application/pdf', data: base64 } };
|
|
252
|
+
// Strip API key so this uses the user's claude.ai subscription (same as every
|
|
253
|
+
// bridge turn). Spread the rest of the env through — options.env REPLACES.
|
|
254
|
+
const { ANTHROPIC_API_KEY: _stripped, ...safeEnv } = process.env;
|
|
255
|
+
if (!safeEnv['MAX_MCP_OUTPUT_TOKENS'])
|
|
256
|
+
safeEnv['MAX_MCP_OUTPUT_TOKENS'] = '200000';
|
|
257
|
+
const abort = new AbortController();
|
|
258
|
+
let assistantText = '';
|
|
259
|
+
const options = {
|
|
260
|
+
systemPrompt: req.systemPrompt,
|
|
261
|
+
mcpServers: {},
|
|
262
|
+
strictMcpConfig: true,
|
|
263
|
+
settingSources: [],
|
|
264
|
+
allowedTools: [],
|
|
265
|
+
tools: [],
|
|
266
|
+
cwd: join(tmpdir(), '1presence-bridge'),
|
|
267
|
+
abortController: abort,
|
|
268
|
+
includePartialMessages: false,
|
|
269
|
+
permissionMode: 'default',
|
|
270
|
+
env: safeEnv,
|
|
271
|
+
};
|
|
272
|
+
// Cast through `unknown[]` like buildPromptMessages in claude.ts — the SDK's
|
|
273
|
+
// discriminated content-block union won't accept the inferred-`string` `type`
|
|
274
|
+
// discriminants on these literals, but the shape is correct at runtime.
|
|
275
|
+
const promptMessages = [{
|
|
276
|
+
type: 'user',
|
|
277
|
+
message: { role: 'user', content: [sourceBlock, { type: 'text', text: req.userText }] },
|
|
278
|
+
parent_tool_use_id: null,
|
|
279
|
+
}];
|
|
280
|
+
async function* promptGen() { for (const m of promptMessages)
|
|
281
|
+
yield m; }
|
|
282
|
+
try {
|
|
283
|
+
console.log(paint('90', `[bridge] doc_recon: reqId ${req.reqId} — reconstructing`));
|
|
284
|
+
for await (const m of query({ prompt: promptGen(), options })) {
|
|
285
|
+
if (m.isReplay)
|
|
286
|
+
continue;
|
|
287
|
+
if (m.type === 'assistant') {
|
|
288
|
+
const am = m;
|
|
289
|
+
for (const block of (am.message?.content ?? [])) {
|
|
290
|
+
if (block.type === 'text' && block.text)
|
|
291
|
+
assistantText += block.text;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
console.log(paint('90', `[bridge] doc_recon: reqId ${req.reqId} — done (${assistantText.length} chars)`));
|
|
296
|
+
send({ type: 'doc_recon_response', reqId: req.reqId, text: assistantText });
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
if (!abort.signal.aborted) {
|
|
300
|
+
console.error(`[bridge] doc_recon: query failed (reqId: ${req.reqId}): ${err.message}`);
|
|
301
|
+
send({ type: 'doc_recon_response', reqId: req.reqId, error: err.message });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
221
305
|
// ─── Handle a single incoming message (token refresh + spawn) ─────────────────
|
|
222
|
-
async function handleMessage(conversationId, text, sessionId, history, auth, vaultFileOpen, clientCapabilities, syncedFolders, agentSlug) {
|
|
306
|
+
async function handleMessage(conversationId, text, sessionId, history, auth, vaultFileOpen, clientCapabilities, syncedFolders, agentSlug, model) {
|
|
223
307
|
// Refresh JWT if <10 min remaining before spawning Claude
|
|
224
308
|
let activeAuth = auth;
|
|
225
309
|
try {
|
|
@@ -322,6 +406,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
322
406
|
vaultFileOpen,
|
|
323
407
|
clientCapabilities,
|
|
324
408
|
syncedFolders,
|
|
409
|
+
model,
|
|
325
410
|
onEvent: (event) => {
|
|
326
411
|
accumulator.consume(event);
|
|
327
412
|
if (!responding && event['type'] === 'assistant') {
|
|
@@ -498,14 +583,26 @@ function connect(auth, retryDelay = 1000) {
|
|
|
498
583
|
console.log(`[bridge] ✕ stopped conversation ${msg.conversationId}`);
|
|
499
584
|
return;
|
|
500
585
|
}
|
|
586
|
+
if (msg.type === 'doc_recon_request') {
|
|
587
|
+
const req = msg;
|
|
588
|
+
if (req.reqId && req.systemPrompt && req.sourceUrl) {
|
|
589
|
+
handleDocReconRequest(req).catch((err) => {
|
|
590
|
+
console.error(`[bridge] doc_recon: unhandled error (reqId: ${req.reqId}): ${err.message}`);
|
|
591
|
+
if (currentWs?.readyState === WebSocket.OPEN) {
|
|
592
|
+
currentWs.send(JSON.stringify({ type: 'doc_recon_response', reqId: req.reqId, error: err.message }));
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
501
598
|
if (msg.type !== 'message' || !msg.conversationId || !msg.text)
|
|
502
599
|
return;
|
|
503
|
-
const { conversationId, text, sessionId, history, vaultFileOpen, clientCapabilities, syncedFolders, agentSlug } = msg;
|
|
600
|
+
const { conversationId, text, sessionId, history, vaultFileOpen, clientCapabilities, syncedFolders, agentSlug, model } = msg;
|
|
504
601
|
const ts = new Date().toLocaleTimeString();
|
|
505
602
|
const hist = Array.isArray(history) ? history : [];
|
|
506
603
|
console.log(`[${ts}] ▶ ${text}${hist.length ? ` (history: ${hist.length} turn${hist.length === 1 ? '' : 's'})` : ''}`);
|
|
507
604
|
startTurnTimer();
|
|
508
|
-
handleMessage(conversationId, text, sessionId ?? null, hist, auth, vaultFileOpen, clientCapabilities, syncedFolders, agentSlug).catch((err) => {
|
|
605
|
+
handleMessage(conversationId, text, sessionId ?? null, hist, auth, vaultFileOpen, clientCapabilities, syncedFolders, agentSlug, model).catch((err) => {
|
|
509
606
|
stopTurnTimer();
|
|
510
607
|
console.error(`[bridge] handleMessage error: ${err.message}`);
|
|
511
608
|
});
|