@gakim-digital/dexter-bridge 0.5.21 → 0.11.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/README.md +116 -35
- package/package.json +19 -5
- package/src/agent.js +1351 -331
- package/src/agentOutput.js +209 -0
- package/src/api.js +48 -1
- package/src/cli.js +267 -39
- package/src/config.js +30 -7
- package/src/framerAgentTools.js +1108 -0
- package/src/harnessMcpServer.js +240 -0
- package/src/harnessTools.js +548 -0
- package/src/logger.js +1 -1
- package/src/nativeSkills.js +295 -0
- package/src/outcomeWorkspace.js +351 -0
- package/src/protocol.js +239 -0
- package/src/providers/acp.js +241 -0
- package/src/providers/codexAppServer.js +1050 -156
- package/src/providers/codexStructuredOutput.js +243 -16
- package/src/providers/directByok.js +197 -0
- package/src/providers/index.js +33 -7
- package/src/providers/openCode.js +607 -0
- package/src/runtimeProfiles.js +284 -0
- package/src/providers/claudeAgentSdk.js +0 -507
package/src/agentOutput.js
CHANGED
|
@@ -241,6 +241,215 @@ function parseClaudeStreamUsage(events) {
|
|
|
241
241
|
};
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
function progressText(value, maximum = 500) {
|
|
245
|
+
if (typeof value !== 'string') return '';
|
|
246
|
+
return value.replace(/\s+/g, ' ').trim().slice(0, maximum);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function progressPath(value, cwd) {
|
|
250
|
+
if (typeof value !== 'string') return undefined;
|
|
251
|
+
let normalized = value.trim().replaceAll('\\', '/').replace(/\/+/g, '/');
|
|
252
|
+
const normalizedCwd = typeof cwd === 'string'
|
|
253
|
+
? cwd.trim().replaceAll('\\', '/').replace(/\/+$/, '')
|
|
254
|
+
: '';
|
|
255
|
+
if (normalizedCwd && normalized.startsWith(`${normalizedCwd}/`)) {
|
|
256
|
+
normalized = normalized.slice(normalizedCwd.length + 1);
|
|
257
|
+
}
|
|
258
|
+
normalized = normalized.replace(/^\.\/+/, '');
|
|
259
|
+
if (!normalized || normalized.split('/').some((part) => part === '..')) return undefined;
|
|
260
|
+
if (normalized.startsWith('/') || /^[A-Za-z]:\//.test(normalized)) {
|
|
261
|
+
const parts = normalized.split('/').filter(Boolean);
|
|
262
|
+
normalized = parts.slice(-2).join('/');
|
|
263
|
+
}
|
|
264
|
+
return normalized.slice(0, 500) || undefined;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function claudeToolProgress(name, input = {}, cwd) {
|
|
268
|
+
const toolName = String(name || '').trim();
|
|
269
|
+
const shortName = toolName.replace(/^mcp__instawebai__/, '');
|
|
270
|
+
const filePath = progressPath(
|
|
271
|
+
input.file_path || input.path || input.notebook_path,
|
|
272
|
+
cwd,
|
|
273
|
+
);
|
|
274
|
+
const paths = filePath ? [filePath] : undefined;
|
|
275
|
+
const description = progressText(input.description, 180);
|
|
276
|
+
const messages = {
|
|
277
|
+
Read: [
|
|
278
|
+
filePath ? `Reading ${filePath}` : 'Reading a project file',
|
|
279
|
+
filePath ? `Read ${filePath}` : 'Finished reading the project file',
|
|
280
|
+
],
|
|
281
|
+
Write: [
|
|
282
|
+
filePath ? `Creating ${filePath}` : 'Creating a project file',
|
|
283
|
+
filePath ? `Created ${filePath}` : 'Created the project file',
|
|
284
|
+
],
|
|
285
|
+
Edit: [
|
|
286
|
+
filePath ? `Updating ${filePath}` : 'Updating a project file',
|
|
287
|
+
filePath ? `Updated ${filePath}` : 'Updated the project file',
|
|
288
|
+
],
|
|
289
|
+
Glob: ['Finding project files', 'Found the project files'],
|
|
290
|
+
Grep: ['Searching the code', 'Finished searching the code'],
|
|
291
|
+
Bash: [
|
|
292
|
+
description || 'Running a project command',
|
|
293
|
+
description ? `Finished: ${description}` : 'Finished the project command',
|
|
294
|
+
],
|
|
295
|
+
workspace_inspect: ['Inspecting the project', 'Finished inspecting the project'],
|
|
296
|
+
workspace_sync: ['Saving project changes', 'Saved the project changes'],
|
|
297
|
+
shell_run: [
|
|
298
|
+
description || 'Running project checks',
|
|
299
|
+
description ? `Finished: ${description}` : 'Finished the project checks',
|
|
300
|
+
],
|
|
301
|
+
preview_control: ['Starting the live preview', 'Started the live preview'],
|
|
302
|
+
browser_control: ['Checking the app in the browser', 'Finished checking the app in the browser'],
|
|
303
|
+
data_inspect: ['Checking app data', 'Finished checking app data'],
|
|
304
|
+
verification_run: ['Running final verification', 'Finished final verification'],
|
|
305
|
+
};
|
|
306
|
+
const [startedMessage, completedMessage] = messages[shortName] || [
|
|
307
|
+
`Using ${shortName || 'a project tool'}`,
|
|
308
|
+
`Finished using ${shortName || 'the project tool'}`,
|
|
309
|
+
];
|
|
310
|
+
return {
|
|
311
|
+
startedMessage,
|
|
312
|
+
completedMessage,
|
|
313
|
+
failedMessage: `Couldn’t finish: ${startedMessage}`,
|
|
314
|
+
paths,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function summedClaudeProgressUsage(turnUsage) {
|
|
319
|
+
const total = normalizeCompanionTokenUsage();
|
|
320
|
+
for (const usage of turnUsage.values()) {
|
|
321
|
+
for (const field of TOKEN_FIELDS) total[field] += usage[field];
|
|
322
|
+
total.totalTokens += usage.totalTokens;
|
|
323
|
+
}
|
|
324
|
+
return total;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function createClaudeProgressParser(onProgress, options = {}) {
|
|
328
|
+
let buffer = '';
|
|
329
|
+
let lastUsage = '';
|
|
330
|
+
const tools = new Map();
|
|
331
|
+
const emittedTools = new Set();
|
|
332
|
+
const emittedText = new Set();
|
|
333
|
+
const turnUsage = new Map();
|
|
334
|
+
|
|
335
|
+
const emit = (event) => {
|
|
336
|
+
onProgress?.({
|
|
337
|
+
provider: 'claude-code',
|
|
338
|
+
occurredAt: new Date().toISOString(),
|
|
339
|
+
...event,
|
|
340
|
+
});
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const updateUsage = (message) => {
|
|
344
|
+
if (!message?.id || !message.usage || typeof message.usage !== 'object') return;
|
|
345
|
+
const current = turnUsage.get(message.id) || normalizeCompanionTokenUsage();
|
|
346
|
+
mergeMaximumUsage(current, message.usage);
|
|
347
|
+
turnUsage.set(message.id, current);
|
|
348
|
+
const tokenUsage = summedClaudeProgressUsage(turnUsage);
|
|
349
|
+
const serialized = JSON.stringify(tokenUsage);
|
|
350
|
+
if (!usageHasReportedTokens(tokenUsage) || serialized === lastUsage) return;
|
|
351
|
+
lastUsage = serialized;
|
|
352
|
+
emit({ kind: 'token_usage', tokenUsage });
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const processAssistant = (row) => {
|
|
356
|
+
const message = row.message;
|
|
357
|
+
if (!message || typeof message !== 'object') return;
|
|
358
|
+
updateUsage(message);
|
|
359
|
+
for (const [index, part] of (Array.isArray(message.content) ? message.content : []).entries()) {
|
|
360
|
+
if (part?.type === 'text') {
|
|
361
|
+
const text = progressText(part.text);
|
|
362
|
+
const key = `${message.id || 'assistant'}:${index}:${text}`;
|
|
363
|
+
if (
|
|
364
|
+
!text
|
|
365
|
+
|| emittedText.has(key)
|
|
366
|
+
|| (text.startsWith('{') && text.endsWith('}'))
|
|
367
|
+
) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
emittedText.add(key);
|
|
371
|
+
emit({
|
|
372
|
+
kind: 'output_delta',
|
|
373
|
+
message: text,
|
|
374
|
+
activity: `assistant:${message.id || key}:${index}`,
|
|
375
|
+
});
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (part?.type !== 'tool_use' || !part.id || emittedTools.has(part.id)) continue;
|
|
379
|
+
emittedTools.add(part.id);
|
|
380
|
+
const input = part.input && typeof part.input === 'object' && !Array.isArray(part.input)
|
|
381
|
+
? part.input
|
|
382
|
+
: {};
|
|
383
|
+
if (
|
|
384
|
+
String(part.name || '').replace(/^mcp__instawebai__/, '') === 'progress_update'
|
|
385
|
+
&& progressText(input.message)
|
|
386
|
+
) {
|
|
387
|
+
tools.set(part.id, { suppressResult: true });
|
|
388
|
+
emit({
|
|
389
|
+
kind: 'output_delta',
|
|
390
|
+
message: progressText(input.message),
|
|
391
|
+
activity: `assistant-progress:${part.id}`,
|
|
392
|
+
});
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
const progress = claudeToolProgress(part.name, input, options.cwd);
|
|
396
|
+
tools.set(part.id, { name: part.name, ...progress });
|
|
397
|
+
emit({
|
|
398
|
+
kind: 'item_started',
|
|
399
|
+
name: part.name,
|
|
400
|
+
phase: 'started',
|
|
401
|
+
activity: `tool:${part.id}`,
|
|
402
|
+
paths: progress.paths,
|
|
403
|
+
message: progress.startedMessage,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const processUser = (row) => {
|
|
409
|
+
const content = row.message?.content;
|
|
410
|
+
if (!Array.isArray(content)) return;
|
|
411
|
+
for (const part of content) {
|
|
412
|
+
if (part?.type !== 'tool_result' || !part.tool_use_id) continue;
|
|
413
|
+
const tool = tools.get(part.tool_use_id);
|
|
414
|
+
if (!tool || tool.suppressResult) continue;
|
|
415
|
+
const failed = part.is_error === true || Boolean(row.toolDenialKind);
|
|
416
|
+
emit({
|
|
417
|
+
kind: failed ? 'item_failed' : 'item_completed',
|
|
418
|
+
name: tool.name,
|
|
419
|
+
phase: failed ? 'failed' : 'completed',
|
|
420
|
+
activity: `tool:${part.tool_use_id}`,
|
|
421
|
+
paths: tool.paths,
|
|
422
|
+
message: failed ? tool.failedMessage : tool.completedMessage,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
const processLine = (line) => {
|
|
428
|
+
const trimmed = line.trim();
|
|
429
|
+
if (!trimmed) return;
|
|
430
|
+
try {
|
|
431
|
+
const row = JSON.parse(trimmed);
|
|
432
|
+
if (row?.type === 'assistant') processAssistant(row);
|
|
433
|
+
if (row?.type === 'user') processUser(row);
|
|
434
|
+
} catch {
|
|
435
|
+
// Claude may split JSON records across chunks. Incomplete records remain buffered.
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
return {
|
|
440
|
+
push(chunk) {
|
|
441
|
+
buffer += String(chunk || '');
|
|
442
|
+
const lines = buffer.split(/\r?\n/);
|
|
443
|
+
buffer = lines.pop() || '';
|
|
444
|
+
for (const line of lines) processLine(line);
|
|
445
|
+
},
|
|
446
|
+
flush() {
|
|
447
|
+
if (buffer.trim()) processLine(buffer);
|
|
448
|
+
buffer = '';
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
244
453
|
export function parseClaudeOutput(stdout) {
|
|
245
454
|
const whole = parseWholeJson(stdout);
|
|
246
455
|
if (whole && typeof whole === 'object' && !Array.isArray(whole)) {
|
package/src/api.js
CHANGED
|
@@ -39,9 +39,14 @@ export async function requestJson(apiBaseUrl, path, {
|
|
|
39
39
|
body,
|
|
40
40
|
deviceToken,
|
|
41
41
|
fetchImpl = fetch,
|
|
42
|
+
signal,
|
|
43
|
+
allowInsecureHttp = false,
|
|
42
44
|
} = {}) {
|
|
43
|
-
const response = await fetchImpl(`${normalizeApiBaseUrl(apiBaseUrl
|
|
45
|
+
const response = await fetchImpl(`${normalizeApiBaseUrl(apiBaseUrl, {
|
|
46
|
+
allowInsecureHttp,
|
|
47
|
+
})}${path}`, {
|
|
44
48
|
method,
|
|
49
|
+
signal,
|
|
45
50
|
headers: {
|
|
46
51
|
Accept: 'application/json',
|
|
47
52
|
'Content-Type': 'application/json',
|
|
@@ -75,6 +80,8 @@ export function claimPairing(apiBaseUrl, {
|
|
|
75
80
|
agent,
|
|
76
81
|
model,
|
|
77
82
|
metadata,
|
|
83
|
+
runtimeProfiles,
|
|
84
|
+
runtimeSelection,
|
|
78
85
|
fetchImpl,
|
|
79
86
|
}) {
|
|
80
87
|
const normalizedAgent = normalizeAgentName(agent);
|
|
@@ -89,6 +96,8 @@ export function claimPairing(apiBaseUrl, {
|
|
|
89
96
|
platform: process.platform,
|
|
90
97
|
companionVersion: BRIDGE_VERSION,
|
|
91
98
|
metadata: bridgeMetadata(normalizedAgent, normalizedModel, metadata),
|
|
99
|
+
runtimeProfiles,
|
|
100
|
+
runtimeSelection,
|
|
92
101
|
},
|
|
93
102
|
});
|
|
94
103
|
}
|
|
@@ -99,7 +108,9 @@ export function heartbeat(apiBaseUrl, {
|
|
|
99
108
|
agent,
|
|
100
109
|
model,
|
|
101
110
|
metadata,
|
|
111
|
+
runtimeProfiles,
|
|
102
112
|
fetchImpl,
|
|
113
|
+
allowInsecureHttp,
|
|
103
114
|
}) {
|
|
104
115
|
const normalizedAgent = normalizeAgentName(agent);
|
|
105
116
|
const normalizedModel = normalizeCompanionModelName(model, normalizedAgent);
|
|
@@ -107,11 +118,13 @@ export function heartbeat(apiBaseUrl, {
|
|
|
107
118
|
method: 'POST',
|
|
108
119
|
deviceToken,
|
|
109
120
|
fetchImpl,
|
|
121
|
+
allowInsecureHttp,
|
|
110
122
|
body: {
|
|
111
123
|
status,
|
|
112
124
|
platform: process.platform,
|
|
113
125
|
companionVersion: BRIDGE_VERSION,
|
|
114
126
|
metadata: bridgeMetadata(normalizedAgent, normalizedModel, metadata),
|
|
127
|
+
runtimeProfiles,
|
|
115
128
|
},
|
|
116
129
|
});
|
|
117
130
|
}
|
|
@@ -137,7 +150,9 @@ export function pollRun(apiBaseUrl, {
|
|
|
137
150
|
agent,
|
|
138
151
|
model,
|
|
139
152
|
metadata,
|
|
153
|
+
runtimeProfiles,
|
|
140
154
|
fetchImpl,
|
|
155
|
+
allowInsecureHttp,
|
|
141
156
|
}) {
|
|
142
157
|
const normalizedAgent = normalizeAgentName(agent);
|
|
143
158
|
const normalizedModel = normalizeCompanionModelName(model, normalizedAgent);
|
|
@@ -145,26 +160,58 @@ export function pollRun(apiBaseUrl, {
|
|
|
145
160
|
method: 'POST',
|
|
146
161
|
deviceToken,
|
|
147
162
|
fetchImpl,
|
|
163
|
+
allowInsecureHttp,
|
|
148
164
|
body: {
|
|
149
165
|
waitMs,
|
|
150
166
|
platform: process.platform,
|
|
151
167
|
companionVersion: BRIDGE_VERSION,
|
|
152
168
|
status: 'polling',
|
|
153
169
|
metadata: bridgeMetadata(normalizedAgent, normalizedModel, metadata),
|
|
170
|
+
runtimeProfiles,
|
|
154
171
|
},
|
|
155
172
|
});
|
|
156
173
|
}
|
|
157
174
|
|
|
175
|
+
export function getFramerProjectAuthorization(apiBaseUrl, {
|
|
176
|
+
deviceToken,
|
|
177
|
+
runId,
|
|
178
|
+
initiate = false,
|
|
179
|
+
forceRefresh = false,
|
|
180
|
+
fetchImpl,
|
|
181
|
+
signal,
|
|
182
|
+
allowInsecureHttp,
|
|
183
|
+
}) {
|
|
184
|
+
return requestJson(
|
|
185
|
+
apiBaseUrl,
|
|
186
|
+
`/ai/framer/companion/runs/${encodeURIComponent(runId)}/framer-authorization`,
|
|
187
|
+
{
|
|
188
|
+
method: 'POST',
|
|
189
|
+
deviceToken,
|
|
190
|
+
fetchImpl,
|
|
191
|
+
signal,
|
|
192
|
+
allowInsecureHttp,
|
|
193
|
+
body: {
|
|
194
|
+
initiate,
|
|
195
|
+
forceRefresh,
|
|
196
|
+
platform: process.platform,
|
|
197
|
+
companionVersion: BRIDGE_VERSION,
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
158
203
|
export function postRunEvent(apiBaseUrl, {
|
|
159
204
|
deviceToken,
|
|
160
205
|
runId,
|
|
161
206
|
event,
|
|
162
207
|
fetchImpl,
|
|
208
|
+
allowInsecureHttp,
|
|
163
209
|
}) {
|
|
164
210
|
return requestJson(apiBaseUrl, `/ai/framer/companion/runs/${encodeURIComponent(runId)}/events`, {
|
|
165
211
|
method: 'POST',
|
|
166
212
|
deviceToken,
|
|
167
213
|
fetchImpl,
|
|
214
|
+
allowInsecureHttp,
|
|
168
215
|
body: {
|
|
169
216
|
event,
|
|
170
217
|
platform: process.platform,
|