@gakim-digital/dexter-bridge 0.5.1 → 0.5.3
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 +19 -3
- package/package.json +1 -1
- package/src/agent.js +127 -10
- package/src/agentOutput.js +122 -16
- package/src/config.js +3 -1
package/README.md
CHANGED
|
@@ -75,14 +75,30 @@ The CLI stores the device token in `~/.dexter-bridge/config.json` with mode
|
|
|
75
75
|
credits are not charged for companion runs; the backend records reported usage
|
|
76
76
|
for visibility.
|
|
77
77
|
|
|
78
|
-
Claude Code
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
Claude Code is launched as a model-only engine: local tools, slash commands,
|
|
79
|
+
MCP integrations, browser access, and project-agent context are disabled. The
|
|
80
|
+
bridge supplies a strict JSON schema and a system prompt that requires Dexter
|
|
81
|
+
tool requests to be returned as structured output instead of being executed
|
|
82
|
+
inside Claude Code.
|
|
83
|
+
|
|
84
|
+
Claude Code runs use streaming JSON and Codex runs use JSONL so the bridge can
|
|
85
|
+
report input, output, cache, and reasoning tokens even when a turn is
|
|
86
|
+
interrupted. Claude's reported `total_cost_usd` is displayed as an estimated
|
|
81
87
|
API-equivalent cost, not an amount Dexter charged. If an older or customized
|
|
82
88
|
CLI does not support structured output, set `DEXTER_BRIDGE_STRUCTURED_USAGE=false`;
|
|
83
89
|
the run will continue, but its token usage will be marked unavailable rather
|
|
84
90
|
than estimated from text.
|
|
85
91
|
|
|
92
|
+
`DEXTER_BRIDGE_AGENT_TIMEOUT_MS` is the inactivity limit and resets whenever
|
|
93
|
+
the local model produces output. `DEXTER_BRIDGE_AGENT_MAX_DURATION_MS` is the
|
|
94
|
+
hard wall-clock ceiling; the API may supply a smaller per-turn ceiling from the
|
|
95
|
+
remaining run budget. Claude defaults to low effort for the intent-planner lane
|
|
96
|
+
and follow-up/recovery turns, and medium effort for the opening design turn.
|
|
97
|
+
Override these with `DEXTER_BRIDGE_CLAUDE_PLANNER_EFFORT`,
|
|
98
|
+
`DEXTER_BRIDGE_CLAUDE_MAIN_EFFORT`,
|
|
99
|
+
`DEXTER_BRIDGE_CLAUDE_FOLLOWUP_EFFORT`, or
|
|
100
|
+
`DEXTER_BRIDGE_CLAUDE_EFFORT`.
|
|
101
|
+
|
|
86
102
|
## Debugging
|
|
87
103
|
|
|
88
104
|
The Dexter Bridge terminal is the shell where `dexter-bridge start` is running.
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -178,6 +178,16 @@ const CLAUDE_CLI_MODEL_IDS = {
|
|
|
178
178
|
haiku: 'claude-haiku-4-5-20251001',
|
|
179
179
|
};
|
|
180
180
|
|
|
181
|
+
const CLAUDE_MODEL_ENGINE_SYSTEM_PROMPT = [
|
|
182
|
+
'You are a model-only completion engine embedded inside Dexter.',
|
|
183
|
+
'The user prompt contains Dexter messages and a catalog of remote tools as data.',
|
|
184
|
+
'Never execute, simulate, or emit native Claude Code tool calls for those tool names.',
|
|
185
|
+
'The only allowed tool is StructuredOutput, supplied by the JSON schema.',
|
|
186
|
+
'Encode requested Dexter actions only inside StructuredOutput.toolCalls.',
|
|
187
|
+
'Do not inspect the filesystem, project, shell, plugins, skills, MCP servers, or browser.',
|
|
188
|
+
'Be concise: reason only as much as needed to choose the next remote Dexter action.',
|
|
189
|
+
].join(' ');
|
|
190
|
+
|
|
181
191
|
export function mapClaudeCliModelId(value) {
|
|
182
192
|
const raw = String(value || '').trim();
|
|
183
193
|
return CLAUDE_CLI_MODEL_IDS[raw.toLowerCase()] || raw;
|
|
@@ -195,7 +205,16 @@ function argsWithSelectedModel(args, modelDefinition, definition) {
|
|
|
195
205
|
function argsWithStructuredOutput(args, definition, env = process.env) {
|
|
196
206
|
if (!structuredUsageEnabled(env)) return args;
|
|
197
207
|
if (definition.id === 'claude-code') {
|
|
198
|
-
|
|
208
|
+
const streamed = [
|
|
209
|
+
...argsWithoutFlagValue(args, '--output-format'),
|
|
210
|
+
'--output-format',
|
|
211
|
+
'stream-json',
|
|
212
|
+
];
|
|
213
|
+
if (!argsIncludeFlag(streamed, '--include-partial-messages')) {
|
|
214
|
+
streamed.push('--include-partial-messages');
|
|
215
|
+
}
|
|
216
|
+
if (!argsIncludeFlag(streamed, '--verbose')) streamed.push('--verbose');
|
|
217
|
+
return streamed;
|
|
199
218
|
}
|
|
200
219
|
if (definition.id === 'codex') {
|
|
201
220
|
return argsIncludeFlag(args, '--json') ? args : [...args, '--json'];
|
|
@@ -214,6 +233,41 @@ function argsWithClaudeIsolation(args, definition) {
|
|
|
214
233
|
return isolated;
|
|
215
234
|
}
|
|
216
235
|
|
|
236
|
+
function normalizeClaudeEffort(value, fallback = 'medium') {
|
|
237
|
+
const effort = String(value || '').trim().toLowerCase();
|
|
238
|
+
return ['low', 'medium', 'high', 'xhigh', 'max'].includes(effort)
|
|
239
|
+
? effort
|
|
240
|
+
: fallback;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function claudeEffortForStep(step, env = process.env) {
|
|
244
|
+
if (env.DEXTER_BRIDGE_CLAUDE_EFFORT) {
|
|
245
|
+
return normalizeClaudeEffort(env.DEXTER_BRIDGE_CLAUDE_EFFORT);
|
|
246
|
+
}
|
|
247
|
+
if (Number(step) < 0) {
|
|
248
|
+
return normalizeClaudeEffort(env.DEXTER_BRIDGE_CLAUDE_PLANNER_EFFORT, 'low');
|
|
249
|
+
}
|
|
250
|
+
if (Number(step) > 0) {
|
|
251
|
+
return normalizeClaudeEffort(env.DEXTER_BRIDGE_CLAUDE_FOLLOWUP_EFFORT, 'low');
|
|
252
|
+
}
|
|
253
|
+
return normalizeClaudeEffort(env.DEXTER_BRIDGE_CLAUDE_MAIN_EFFORT, 'medium');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function argsWithClaudeModelEngine(args, definition, options = {}, env = process.env) {
|
|
257
|
+
if (definition.id !== 'claude-code') return args;
|
|
258
|
+
let isolated = argsWithoutFlagValue(args, '--system-prompt');
|
|
259
|
+
isolated = argsWithoutFlagValue(isolated, '--append-system-prompt');
|
|
260
|
+
isolated = argsWithoutFlagValue(isolated, '--agent');
|
|
261
|
+
isolated = argsWithoutFlagValue(isolated, '--effort');
|
|
262
|
+
return [
|
|
263
|
+
...isolated,
|
|
264
|
+
'--system-prompt',
|
|
265
|
+
CLAUDE_MODEL_ENGINE_SYSTEM_PROMPT,
|
|
266
|
+
'--effort',
|
|
267
|
+
claudeEffortForStep(options.step, env),
|
|
268
|
+
];
|
|
269
|
+
}
|
|
270
|
+
|
|
217
271
|
function argsWithoutFlagValue(args, flag) {
|
|
218
272
|
const filtered = [];
|
|
219
273
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -316,7 +370,8 @@ export function buildAgentArgs(definition, modelDefinition, env = process.env, o
|
|
|
316
370
|
const modelArgs = argsWithSelectedModel(requiredArgs, modelDefinition, definition);
|
|
317
371
|
const structuredArgs = argsWithStructuredOutput(modelArgs, definition, env);
|
|
318
372
|
const isolatedArgs = argsWithClaudeIsolation(structuredArgs, definition);
|
|
319
|
-
const
|
|
373
|
+
const modelEngineArgs = argsWithClaudeModelEngine(isolatedArgs, definition, options, env);
|
|
374
|
+
const schemaArgs = argsWithOutputSchema(modelEngineArgs, definition, options.outputSchema);
|
|
320
375
|
const resumedArgs = argsWithResumedSession(schemaArgs, definition, options.resumeSessionId);
|
|
321
376
|
return argsWithPromptInput(resumedArgs, definition);
|
|
322
377
|
}
|
|
@@ -593,12 +648,17 @@ export function agentFailureMessage(command, code, stdout = '', stderr = '') {
|
|
|
593
648
|
|
|
594
649
|
function runProcess(command, args, stdin, {
|
|
595
650
|
timeoutMs = 120000,
|
|
651
|
+
maxDurationMs,
|
|
596
652
|
trace,
|
|
597
653
|
childEnv: providedChildEnv,
|
|
598
654
|
platform = process.platform,
|
|
599
655
|
} = {}) {
|
|
600
656
|
return new Promise((resolve, reject) => {
|
|
601
657
|
const started = Date.now();
|
|
658
|
+
// timeoutMs is an inactivity timeout (reset whenever the child produces
|
|
659
|
+
// output) so slow-but-streaming model turns are not killed mid-generation;
|
|
660
|
+
// hardDeadlineMs bounds total wall-clock time regardless of activity.
|
|
661
|
+
const hardDeadlineMs = Math.max(maxDurationMs || timeoutMs * 5, 10);
|
|
602
662
|
const childEnv = providedChildEnv || processEnvWithCliPath(platform);
|
|
603
663
|
const cwd = resolveAgentCwd(childEnv, process.cwd(), platform);
|
|
604
664
|
const invocation = processInvocation(command, args, childEnv, platform);
|
|
@@ -610,6 +670,7 @@ function runProcess(command, args, stdin, {
|
|
|
610
670
|
env: processEnvSummary(childEnv),
|
|
611
671
|
stdinChars: stdin.length,
|
|
612
672
|
timeoutMs,
|
|
673
|
+
maxDurationMs: hardDeadlineMs,
|
|
613
674
|
});
|
|
614
675
|
const child = spawn(invocation.command, invocation.args, {
|
|
615
676
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -620,33 +681,54 @@ function runProcess(command, args, stdin, {
|
|
|
620
681
|
let stdout = '';
|
|
621
682
|
let stderr = '';
|
|
622
683
|
let settled = false;
|
|
623
|
-
|
|
684
|
+
let inactivityTimer = null;
|
|
685
|
+
const timeOut = (message) => {
|
|
624
686
|
if (settled) return;
|
|
625
687
|
settled = true;
|
|
688
|
+
clearTimeout(inactivityTimer);
|
|
689
|
+
clearTimeout(deadlineTimer);
|
|
626
690
|
child.kill('SIGTERM');
|
|
627
|
-
const error = new Error(
|
|
691
|
+
const error = new Error(message);
|
|
692
|
+
error.code = 'AGENT_TIMEOUT';
|
|
693
|
+
error.stdout = stdout;
|
|
694
|
+
error.stderr = stderr;
|
|
695
|
+
error.timedOut = true;
|
|
628
696
|
trace?.error('agent_process_timeout', {
|
|
629
697
|
command,
|
|
630
698
|
durationMs: Date.now() - started,
|
|
631
699
|
timeoutMs,
|
|
700
|
+
maxDurationMs: hardDeadlineMs,
|
|
632
701
|
stdoutChars: stdout.length,
|
|
633
702
|
stderrChars: stderr.length,
|
|
634
703
|
stdoutExcerpt: clip(stdout, 1000),
|
|
635
704
|
stderrExcerpt: clip(stderr, 1000),
|
|
636
705
|
});
|
|
637
706
|
reject(error);
|
|
638
|
-
}
|
|
707
|
+
};
|
|
708
|
+
const armInactivityTimer = () => {
|
|
709
|
+
clearTimeout(inactivityTimer);
|
|
710
|
+
inactivityTimer = setTimeout(() => {
|
|
711
|
+
timeOut(`${command} produced no output for ${timeoutMs}ms.`);
|
|
712
|
+
}, timeoutMs);
|
|
713
|
+
};
|
|
714
|
+
const deadlineTimer = setTimeout(() => {
|
|
715
|
+
timeOut(`${command} timed out after ${hardDeadlineMs}ms.`);
|
|
716
|
+
}, hardDeadlineMs);
|
|
717
|
+
armInactivityTimer();
|
|
639
718
|
|
|
640
719
|
child.stdout.on('data', (chunk) => {
|
|
641
720
|
stdout += chunk.toString('utf8');
|
|
721
|
+
armInactivityTimer();
|
|
642
722
|
});
|
|
643
723
|
child.stderr.on('data', (chunk) => {
|
|
644
724
|
stderr += chunk.toString('utf8');
|
|
725
|
+
armInactivityTimer();
|
|
645
726
|
});
|
|
646
727
|
child.on('error', (error) => {
|
|
647
728
|
if (settled) return;
|
|
648
729
|
settled = true;
|
|
649
|
-
clearTimeout(
|
|
730
|
+
clearTimeout(inactivityTimer);
|
|
731
|
+
clearTimeout(deadlineTimer);
|
|
650
732
|
trace?.error('agent_process_error', {
|
|
651
733
|
command,
|
|
652
734
|
durationMs: Date.now() - started,
|
|
@@ -657,7 +739,8 @@ function runProcess(command, args, stdin, {
|
|
|
657
739
|
child.on('close', (code) => {
|
|
658
740
|
if (settled) return;
|
|
659
741
|
settled = true;
|
|
660
|
-
clearTimeout(
|
|
742
|
+
clearTimeout(inactivityTimer);
|
|
743
|
+
clearTimeout(deadlineTimer);
|
|
661
744
|
const meta = {
|
|
662
745
|
command,
|
|
663
746
|
code,
|
|
@@ -834,8 +917,14 @@ async function callLocalJsonAgent(agent, prompt, options = {}) {
|
|
|
834
917
|
const args = buildAgentArgs(definition, modelDefinition, process.env, {
|
|
835
918
|
resumeSessionId: options.resumeSessionId,
|
|
836
919
|
outputSchema: options.outputSchema,
|
|
920
|
+
step: options.step,
|
|
837
921
|
});
|
|
838
922
|
const timeoutMs = options.timeoutMs || Number(process.env.DEXTER_BRIDGE_AGENT_TIMEOUT_MS || 120000);
|
|
923
|
+
const maxDurationMs = boundedDurationMs(
|
|
924
|
+
options.maxDurationMs ?? process.env.DEXTER_BRIDGE_AGENT_MAX_DURATION_MS,
|
|
925
|
+
Math.max(timeoutMs * 5, 600000),
|
|
926
|
+
10,
|
|
927
|
+
);
|
|
839
928
|
options.trace?.info('agent_step_invoke', {
|
|
840
929
|
agent: definition.id,
|
|
841
930
|
command,
|
|
@@ -847,13 +936,28 @@ async function callLocalJsonAgent(agent, prompt, options = {}) {
|
|
|
847
936
|
promptChars: prompt.length,
|
|
848
937
|
resumeSessionId: options.resumeSessionId,
|
|
849
938
|
timeoutMs,
|
|
939
|
+
maxDurationMs,
|
|
850
940
|
});
|
|
851
941
|
return runProcess(command, args, prompt, {
|
|
852
942
|
timeoutMs,
|
|
943
|
+
maxDurationMs,
|
|
853
944
|
trace: options.trace,
|
|
854
945
|
});
|
|
855
946
|
}
|
|
856
947
|
|
|
948
|
+
function agentErrorWithOutputUsage(agent, error) {
|
|
949
|
+
const failure = error instanceof Error
|
|
950
|
+
? error
|
|
951
|
+
: new Error(String(error || 'The companion model call failed.'));
|
|
952
|
+
const parsed = parseAgentOutput(agent, failure.stdout || '');
|
|
953
|
+
if (!parsed.usageAvailable) return failure;
|
|
954
|
+
const accumulator = createCompanionUsageAccumulator(agent);
|
|
955
|
+
accumulator.add(failure.companionUsage || {});
|
|
956
|
+
accumulator.add(parsed);
|
|
957
|
+
failure.companionUsage = accumulator.snapshot();
|
|
958
|
+
return failure;
|
|
959
|
+
}
|
|
960
|
+
|
|
857
961
|
async function executeModelTurnRun(run, send, agent, options = {}) {
|
|
858
962
|
if (normalizeAgentName(agent) === 'dry-run') {
|
|
859
963
|
await send('done', {
|
|
@@ -952,6 +1056,13 @@ async function executeModelTurnRun(run, send, agent, options = {}) {
|
|
|
952
1056
|
120000,
|
|
953
1057
|
1000,
|
|
954
1058
|
),
|
|
1059
|
+
maxDurationMs: boundedDurationMs(
|
|
1060
|
+
run?.modelTurn?.maxDurationMs
|
|
1061
|
+
?? options.maxDurationMs
|
|
1062
|
+
?? options.env?.DEXTER_BRIDGE_AGENT_MAX_DURATION_MS,
|
|
1063
|
+
600000,
|
|
1064
|
+
1000,
|
|
1065
|
+
),
|
|
955
1066
|
};
|
|
956
1067
|
const adapterOptions = {
|
|
957
1068
|
send,
|
|
@@ -1006,15 +1117,19 @@ async function executeModelTurnRun(run, send, agent, options = {}) {
|
|
|
1006
1117
|
runtime,
|
|
1007
1118
|
resumeSessionId,
|
|
1008
1119
|
outputSchema: modelTurnOutputSchema(run.modelTurn),
|
|
1120
|
+
step: run?.modelTurn?.step,
|
|
1121
|
+
maxDurationMs: run?.modelTurn?.maxDurationMs ?? options.maxDurationMs,
|
|
1009
1122
|
});
|
|
1010
1123
|
} catch (error) {
|
|
1011
|
-
|
|
1124
|
+
const failure = agentErrorWithOutputUsage(definition.id, error);
|
|
1125
|
+
if (callContextMode !== 'delta' || !resumeFailure(failure)) throw failedModelCall(failure);
|
|
1126
|
+
usageAccumulator.add(failure.companionUsage || {});
|
|
1012
1127
|
fallbackAfterResumeFailure = true;
|
|
1013
1128
|
callContextMode = 'full';
|
|
1014
1129
|
prompt = buildModelTurnFallbackPrompt(run.modelTurn);
|
|
1015
1130
|
options.trace?.warn('agent_cli_resume_fallback', {
|
|
1016
1131
|
resumeSessionId,
|
|
1017
|
-
error: errorMeta(
|
|
1132
|
+
error: errorMeta(failure),
|
|
1018
1133
|
fallbackPromptChars: prompt.length,
|
|
1019
1134
|
});
|
|
1020
1135
|
result = await callLocalJsonAgent(definition.id, prompt, {
|
|
@@ -1023,8 +1138,10 @@ async function executeModelTurnRun(run, send, agent, options = {}) {
|
|
|
1023
1138
|
runtime,
|
|
1024
1139
|
resumeSessionId: undefined,
|
|
1025
1140
|
outputSchema: modelTurnOutputSchema(run.modelTurn),
|
|
1141
|
+
step: run?.modelTurn?.step,
|
|
1142
|
+
maxDurationMs: run?.modelTurn?.maxDurationMs ?? options.maxDurationMs,
|
|
1026
1143
|
}).catch((error) => {
|
|
1027
|
-
throw failedModelCall(error);
|
|
1144
|
+
throw failedModelCall(agentErrorWithOutputUsage(definition.id, error));
|
|
1028
1145
|
});
|
|
1029
1146
|
}
|
|
1030
1147
|
const parsed = parseAgentOutput(definition.id, result.stdout);
|
package/src/agentOutput.js
CHANGED
|
@@ -79,11 +79,25 @@ function parseWholeJson(stdout) {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
function parseJsonLines(stdout) {
|
|
83
|
+
return String(stdout || '')
|
|
84
|
+
.split(/\r?\n/)
|
|
85
|
+
.map((line) => line.trim())
|
|
86
|
+
.filter(Boolean)
|
|
87
|
+
.flatMap((line) => {
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(line);
|
|
90
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? [parsed] : [];
|
|
91
|
+
} catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function parseClaudeEnvelope(parsed, fallbackText = '') {
|
|
84
98
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
85
99
|
return {
|
|
86
|
-
resultText: String(
|
|
100
|
+
resultText: String(fallbackText || '').trim(),
|
|
87
101
|
tokenUsage: normalizeCompanionTokenUsage(),
|
|
88
102
|
modelUsage: [],
|
|
89
103
|
usageAvailable: false,
|
|
@@ -101,7 +115,7 @@ export function parseClaudeOutput(stdout) {
|
|
|
101
115
|
|| parsed.modelUsage;
|
|
102
116
|
if (!hasEnvelope) {
|
|
103
117
|
return {
|
|
104
|
-
resultText: String(
|
|
118
|
+
resultText: String(fallbackText || '').trim(),
|
|
105
119
|
tokenUsage: normalizeCompanionTokenUsage(),
|
|
106
120
|
modelUsage: [],
|
|
107
121
|
usageAvailable: false,
|
|
@@ -136,19 +150,111 @@ export function parseClaudeOutput(stdout) {
|
|
|
136
150
|
};
|
|
137
151
|
}
|
|
138
152
|
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
function mergeMaximumUsage(target, rawUsage) {
|
|
154
|
+
const usage = normalizeCompanionTokenUsage(rawUsage || {});
|
|
155
|
+
for (const field of TOKEN_FIELDS) target[field] = Math.max(target[field], usage[field]);
|
|
156
|
+
target.totalTokens = target.inputTokens + target.outputTokens + target.reasoningOutputTokens;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function parseClaudeStreamUsage(events) {
|
|
160
|
+
const turns = new Map();
|
|
161
|
+
let activeTurnKey;
|
|
162
|
+
let anonymousSequence = 0;
|
|
163
|
+
let sessionId;
|
|
164
|
+
let partialText = '';
|
|
165
|
+
|
|
166
|
+
const ensureTurn = (key, model) => {
|
|
167
|
+
const resolvedKey = key || activeTurnKey || `turn-${++anonymousSequence}`;
|
|
168
|
+
if (!turns.has(resolvedKey)) {
|
|
169
|
+
turns.set(resolvedKey, {
|
|
170
|
+
model: typeof model === 'string' && model.trim() ? model.trim() : undefined,
|
|
171
|
+
usage: normalizeCompanionTokenUsage(),
|
|
172
|
+
});
|
|
173
|
+
} else if (model && !turns.get(resolvedKey).model) {
|
|
174
|
+
turns.get(resolvedKey).model = model;
|
|
175
|
+
}
|
|
176
|
+
activeTurnKey = resolvedKey;
|
|
177
|
+
return turns.get(resolvedKey);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
for (const row of events) {
|
|
181
|
+
if (
|
|
182
|
+
(row.type === 'system' || row.type === 'init')
|
|
183
|
+
&& typeof (row.session_id || row.sessionId) === 'string'
|
|
184
|
+
) {
|
|
185
|
+
sessionId = row.session_id || row.sessionId;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (row.type === 'assistant' && row.message && typeof row.message === 'object') {
|
|
189
|
+
const message = row.message;
|
|
190
|
+
const turn = ensureTurn(message.id, message.model);
|
|
191
|
+
mergeMaximumUsage(turn.usage, message.usage);
|
|
192
|
+
for (const part of Array.isArray(message.content) ? message.content : []) {
|
|
193
|
+
if (part?.type === 'text' && typeof part.text === 'string') partialText += part.text;
|
|
150
194
|
}
|
|
151
|
-
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (row.type !== 'stream_event' || !row.event || typeof row.event !== 'object') continue;
|
|
198
|
+
const event = row.event;
|
|
199
|
+
if (event.type === 'message_start' && event.message && typeof event.message === 'object') {
|
|
200
|
+
const turn = ensureTurn(event.message.id, event.message.model);
|
|
201
|
+
mergeMaximumUsage(turn.usage, event.message.usage);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (event.type === 'message_delta') {
|
|
205
|
+
const turn = ensureTurn(activeTurnKey);
|
|
206
|
+
mergeMaximumUsage(turn.usage, event.usage);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (
|
|
210
|
+
event.type === 'content_block_delta'
|
|
211
|
+
&& event.delta?.type === 'text_delta'
|
|
212
|
+
&& typeof event.delta.text === 'string'
|
|
213
|
+
) {
|
|
214
|
+
partialText += event.delta.text;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const tokenUsage = normalizeCompanionTokenUsage();
|
|
219
|
+
const modelTotals = new Map();
|
|
220
|
+
for (const turn of turns.values()) {
|
|
221
|
+
for (const field of TOKEN_FIELDS) tokenUsage[field] += turn.usage[field];
|
|
222
|
+
tokenUsage.totalTokens += turn.usage.totalTokens;
|
|
223
|
+
if (!turn.model) continue;
|
|
224
|
+
const current = modelTotals.get(turn.model) || {
|
|
225
|
+
model: turn.model,
|
|
226
|
+
...normalizeCompanionTokenUsage(),
|
|
227
|
+
};
|
|
228
|
+
for (const field of TOKEN_FIELDS) current[field] += turn.usage[field];
|
|
229
|
+
current.totalTokens += turn.usage.totalTokens;
|
|
230
|
+
modelTotals.set(turn.model, current);
|
|
231
|
+
}
|
|
232
|
+
const usageAvailable = usageHasReportedTokens(tokenUsage);
|
|
233
|
+
return {
|
|
234
|
+
resultText: partialText.trim(),
|
|
235
|
+
tokenUsage,
|
|
236
|
+
modelUsage: Array.from(modelTotals.values()),
|
|
237
|
+
sessionId,
|
|
238
|
+
usageAvailable,
|
|
239
|
+
usageSource: 'claude-code',
|
|
240
|
+
usageAccuracy: usageAvailable ? 'reported' : 'unavailable',
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function parseClaudeOutput(stdout) {
|
|
245
|
+
const whole = parseWholeJson(stdout);
|
|
246
|
+
if (whole && typeof whole === 'object' && !Array.isArray(whole)) {
|
|
247
|
+
return parseClaudeEnvelope(whole, stdout);
|
|
248
|
+
}
|
|
249
|
+
const events = parseJsonLines(stdout);
|
|
250
|
+
const resultEnvelope = [...events].reverse().find((event) =>
|
|
251
|
+
event.type === 'result'
|
|
252
|
+
|| event.structured_output !== undefined
|
|
253
|
+
|| event.structuredOutput !== undefined
|
|
254
|
+
|| typeof event.result === 'string');
|
|
255
|
+
if (resultEnvelope) return parseClaudeEnvelope(resultEnvelope, stdout);
|
|
256
|
+
if (events.length) return parseClaudeStreamUsage(events);
|
|
257
|
+
return parseClaudeEnvelope(null, stdout);
|
|
152
258
|
}
|
|
153
259
|
|
|
154
260
|
export function parseCodexOutput(stdout) {
|
package/src/config.js
CHANGED
|
@@ -3,7 +3,9 @@ import os from 'node:os';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
5
|
export const DEFAULT_API_BASE_URL = 'http://localhost:3800/iwm-api/0.0.1';
|
|
6
|
-
export const BRIDGE_VERSION =
|
|
6
|
+
export const BRIDGE_VERSION = JSON.parse(
|
|
7
|
+
fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
|
|
8
|
+
).version;
|
|
7
9
|
export const BRIDGE_CAPABILITIES = [
|
|
8
10
|
'model-turn-v1',
|
|
9
11
|
];
|