@frod.io/bridge 0.7.4 → 0.7.5
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 +170 -21
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// @frod.io/bridge 0.7.
|
|
2
|
+
// @frod.io/bridge 0.7.5. One file; do not edit: the source is apps/bridge/src in the platform repository.
|
|
3
3
|
import { createRequire as __createRequire } from 'node:module';
|
|
4
4
|
const require = __createRequire(import.meta.url);
|
|
5
5
|
var __create = Object.create;
|
|
@@ -4090,6 +4090,25 @@ var LineBuffer = class {
|
|
|
4090
4090
|
// src/session-store.ts
|
|
4091
4091
|
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, chmodSync as chmodSync3 } from "node:fs";
|
|
4092
4092
|
import { join as join4 } from "node:path";
|
|
4093
|
+
|
|
4094
|
+
// src/adapters/usage-delta.ts
|
|
4095
|
+
var KEYS = ["inputTokens", "outputTokens", "cacheCreationTokens", "cacheReadTokens"];
|
|
4096
|
+
function usageDelta(current, previous) {
|
|
4097
|
+
if (!previous || KEYS.some((key) => current[key] < previous[key])) return { ...current };
|
|
4098
|
+
return {
|
|
4099
|
+
inputTokens: current.inputTokens - previous.inputTokens,
|
|
4100
|
+
outputTokens: current.outputTokens - previous.outputTokens,
|
|
4101
|
+
cacheCreationTokens: current.cacheCreationTokens - previous.cacheCreationTokens,
|
|
4102
|
+
cacheReadTokens: current.cacheReadTokens - previous.cacheReadTokens
|
|
4103
|
+
};
|
|
4104
|
+
}
|
|
4105
|
+
function isUsageCounters(value) {
|
|
4106
|
+
if (!value || typeof value !== "object") return false;
|
|
4107
|
+
const row = value;
|
|
4108
|
+
return KEYS.every((key) => typeof row[key] === "number" && Number.isFinite(row[key]) && row[key] >= 0);
|
|
4109
|
+
}
|
|
4110
|
+
|
|
4111
|
+
// src/session-store.ts
|
|
4093
4112
|
var PATH = join4(FROD_DIR, "sessions.json");
|
|
4094
4113
|
function read() {
|
|
4095
4114
|
if (!existsSync3(PATH)) return {};
|
|
@@ -4107,6 +4126,9 @@ function write(store) {
|
|
|
4107
4126
|
function getStored(platformSessionId) {
|
|
4108
4127
|
return read()[platformSessionId] ?? null;
|
|
4109
4128
|
}
|
|
4129
|
+
function getStoredByHostSessionId(hostSessionId) {
|
|
4130
|
+
return Object.values(read()).filter((session) => session.hostSessionId === hostSessionId).sort((a, b) => Date.parse(b.lastPromptAt ?? b.startedAt) - Date.parse(a.lastPromptAt ?? a.startedAt))[0] ?? null;
|
|
4131
|
+
}
|
|
4110
4132
|
function putStored(platformSessionId, s) {
|
|
4111
4133
|
const store = read();
|
|
4112
4134
|
store[platformSessionId] = s;
|
|
@@ -4121,6 +4143,14 @@ function touchStored(platformSessionId) {
|
|
|
4121
4143
|
write(store);
|
|
4122
4144
|
}
|
|
4123
4145
|
}
|
|
4146
|
+
function putStoredUsage(platformSessionId, adapter, counters) {
|
|
4147
|
+
const store = read();
|
|
4148
|
+
const session = store[platformSessionId];
|
|
4149
|
+
if (!session || !isUsageCounters(counters)) return;
|
|
4150
|
+
session.cumulativeUsage = { adapter, counters: { ...counters } };
|
|
4151
|
+
session.lastPromptAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4152
|
+
write(store);
|
|
4153
|
+
}
|
|
4124
4154
|
|
|
4125
4155
|
// src/log.ts
|
|
4126
4156
|
var REDACT = [
|
|
@@ -4860,6 +4890,8 @@ var CodexTranslator = class {
|
|
|
4860
4890
|
}
|
|
4861
4891
|
/** The newest `thread/tokenUsage/updated` of this turn, or null if none arrived. */
|
|
4862
4892
|
usage = null;
|
|
4893
|
+
/** The running thread counters immediately before this turn's first model call. */
|
|
4894
|
+
billingBase = null;
|
|
4863
4895
|
/** Item ids whose text was already streamed as deltas — not repeated on completion. */
|
|
4864
4896
|
streamed = /* @__PURE__ */ new Set();
|
|
4865
4897
|
/** Tool items announced with `item/started`, so a completion without a start still opens a card. */
|
|
@@ -4949,19 +4981,42 @@ var CodexTranslator = class {
|
|
|
4949
4981
|
}
|
|
4950
4982
|
onTokenUsage(p) {
|
|
4951
4983
|
const u = p.tokenUsage;
|
|
4952
|
-
if (u && typeof u === "object" && u.last && u.total)
|
|
4984
|
+
if (u && typeof u === "object" && u.last && u.total) {
|
|
4985
|
+
if (!this.billingBase) {
|
|
4986
|
+
const total = countersOf(u.total);
|
|
4987
|
+
const last = countersOf(u.last);
|
|
4988
|
+
if (total && last && countersCover(total, last)) this.billingBase = usageDelta(total, last);
|
|
4989
|
+
}
|
|
4990
|
+
this.usage = u;
|
|
4991
|
+
}
|
|
4953
4992
|
return true;
|
|
4954
4993
|
}
|
|
4955
4994
|
};
|
|
4956
|
-
function
|
|
4995
|
+
function countersOf(breakdown) {
|
|
4996
|
+
const inputTokens = num(breakdown.inputTokens);
|
|
4997
|
+
const outputTokens = num(breakdown.outputTokens);
|
|
4998
|
+
if (inputTokens === null || outputTokens === null) return null;
|
|
4999
|
+
return {
|
|
5000
|
+
inputTokens,
|
|
5001
|
+
outputTokens,
|
|
5002
|
+
cacheCreationTokens: num(breakdown.cacheWriteInputTokens) ?? 0,
|
|
5003
|
+
cacheReadTokens: num(breakdown.cachedInputTokens) ?? 0
|
|
5004
|
+
};
|
|
5005
|
+
}
|
|
5006
|
+
function countersCover(total, part) {
|
|
5007
|
+
return total.inputTokens >= part.inputTokens && total.outputTokens >= part.outputTokens && total.cacheCreationTokens >= part.cacheCreationTokens && total.cacheReadTokens >= part.cacheReadTokens;
|
|
5008
|
+
}
|
|
5009
|
+
function toUsage(usage, model, billingBase = null) {
|
|
4957
5010
|
if (!usage || !model) return null;
|
|
4958
5011
|
const { last, total } = usage;
|
|
4959
|
-
|
|
5012
|
+
const cumulative = countersOf(total);
|
|
5013
|
+
if (!cumulative) return null;
|
|
5014
|
+
const turn = usageDelta(cumulative, billingBase);
|
|
4960
5015
|
return {
|
|
4961
|
-
input_tokens: Math.max(0,
|
|
4962
|
-
output_tokens:
|
|
4963
|
-
cache_creation_tokens:
|
|
4964
|
-
cache_read_tokens:
|
|
5016
|
+
input_tokens: Math.max(0, turn.inputTokens - turn.cacheReadTokens),
|
|
5017
|
+
output_tokens: turn.outputTokens,
|
|
5018
|
+
cache_creation_tokens: turn.cacheCreationTokens,
|
|
5019
|
+
cache_read_tokens: turn.cacheReadTokens,
|
|
4965
5020
|
model,
|
|
4966
5021
|
context_tokens: num(last.inputTokens),
|
|
4967
5022
|
context_window: num(usage.modelContextWindow)
|
|
@@ -5232,7 +5287,8 @@ var CodexAdapter = class _CodexAdapter {
|
|
|
5232
5287
|
const status2 = typeof turn.status === "string" ? turn.status : "completed";
|
|
5233
5288
|
const err = turn.error;
|
|
5234
5289
|
const stopReason = status2 === "interrupted" ? "cancelled" : status2 === "failed" ? "error" : "end_turn";
|
|
5235
|
-
const
|
|
5290
|
+
const translator = live.turn?.translator;
|
|
5291
|
+
const usage = toUsage(translator?.usage ?? null, live.model, translator?.billingBase ?? null);
|
|
5236
5292
|
live.turnId = null;
|
|
5237
5293
|
this.settleTurn(live, {
|
|
5238
5294
|
stopReason,
|
|
@@ -5571,19 +5627,29 @@ function stopReasonFor(acp) {
|
|
|
5571
5627
|
}
|
|
5572
5628
|
}
|
|
5573
5629
|
var num2 = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
5574
|
-
function
|
|
5630
|
+
function cumulativeUsageOf(meta) {
|
|
5575
5631
|
const total = meta?.usage;
|
|
5632
|
+
if (!total) return null;
|
|
5633
|
+
const inputTokens = num2(total.inputTokens);
|
|
5634
|
+
const outputTokens = num2(total.outputTokens);
|
|
5635
|
+
if (inputTokens === null || outputTokens === null) return null;
|
|
5636
|
+
return {
|
|
5637
|
+
inputTokens,
|
|
5638
|
+
outputTokens,
|
|
5639
|
+
cacheCreationTokens: num2(total.cacheCreationTokens) ?? 0,
|
|
5640
|
+
cacheReadTokens: num2(total.cachedReadTokens) ?? 0
|
|
5641
|
+
};
|
|
5642
|
+
}
|
|
5643
|
+
function toUsage2(meta, fallbackModel, contextWindow, previous = null) {
|
|
5576
5644
|
const model = str2(meta?.modelId) ?? fallbackModel;
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
const
|
|
5580
|
-
if (input === null || output === null) return null;
|
|
5581
|
-
const cacheRead = num2(total.cachedReadTokens) ?? 0;
|
|
5645
|
+
const cumulative = cumulativeUsageOf(meta);
|
|
5646
|
+
if (!cumulative || !model) return null;
|
|
5647
|
+
const turn = usageDelta(cumulative, previous);
|
|
5582
5648
|
return {
|
|
5583
|
-
input_tokens: Math.max(0,
|
|
5584
|
-
output_tokens:
|
|
5585
|
-
cache_creation_tokens:
|
|
5586
|
-
cache_read_tokens:
|
|
5649
|
+
input_tokens: Math.max(0, turn.inputTokens - turn.cacheReadTokens),
|
|
5650
|
+
output_tokens: turn.outputTokens,
|
|
5651
|
+
cache_creation_tokens: turn.cacheCreationTokens,
|
|
5652
|
+
cache_read_tokens: turn.cacheReadTokens,
|
|
5587
5653
|
model,
|
|
5588
5654
|
context_tokens: num2(meta?.inputTokens),
|
|
5589
5655
|
context_window: contextWindow
|
|
@@ -5809,6 +5875,8 @@ ${res.stderr ?? ""}`;
|
|
|
5809
5875
|
turn: null,
|
|
5810
5876
|
model: spec.model?.name ?? null,
|
|
5811
5877
|
contextWindow: null,
|
|
5878
|
+
cumulativeUsage: null,
|
|
5879
|
+
needsUsageBaseline: false,
|
|
5812
5880
|
onExit: o.onExit,
|
|
5813
5881
|
askPermission: o.askPermission,
|
|
5814
5882
|
reportAutoAllowed: o.reportAutoAllowed,
|
|
@@ -5839,6 +5907,10 @@ ${res.stderr ?? ""}`;
|
|
|
5839
5907
|
const mcpServers = mcpServersFor(spec);
|
|
5840
5908
|
const params = { cwd: o.sessionDir, mcpServers, _meta: sessionMeta(spec) };
|
|
5841
5909
|
const resumeId = spec.resume_session_id ?? null;
|
|
5910
|
+
const resumedStore = resumeId ? getStoredByHostSessionId(resumeId) : null;
|
|
5911
|
+
if (resumedStore?.cumulativeUsage?.adapter === "grok" && isUsageCounters(resumedStore.cumulativeUsage.counters)) {
|
|
5912
|
+
live.cumulativeUsage = { ...resumedStore.cumulativeUsage.counters };
|
|
5913
|
+
}
|
|
5842
5914
|
let resumed = false;
|
|
5843
5915
|
let started;
|
|
5844
5916
|
if (resumeId) {
|
|
@@ -5850,12 +5922,25 @@ ${res.stderr ?? ""}`;
|
|
|
5850
5922
|
}
|
|
5851
5923
|
}
|
|
5852
5924
|
if (!started) started = await this.request(live, "session/new", params);
|
|
5925
|
+
if (!resumed) live.cumulativeUsage = null;
|
|
5926
|
+
live.needsUsageBaseline = resumed && !live.cumulativeUsage;
|
|
5853
5927
|
const sessionId = typeof started?.sessionId === "string" ? started.sessionId : null;
|
|
5854
5928
|
if (!sessionId) throw new Error("Grok Build started but did not name the session \u2014 it cannot be addressed.");
|
|
5855
5929
|
live.acpSessionId = sessionId;
|
|
5856
5930
|
const models = started?.models ?? init?._meta?.modelState ?? null;
|
|
5857
5931
|
live.model = live.model ?? (typeof models?.currentModelId === "string" ? models.currentModelId : null);
|
|
5858
5932
|
live.contextWindow = contextWindowOf(models, live.model);
|
|
5933
|
+
try {
|
|
5934
|
+
putStored(spec.session_id, {
|
|
5935
|
+
hostSessionId: sessionId,
|
|
5936
|
+
cwd: o.sessionDir,
|
|
5937
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5938
|
+
lastPromptAt: null,
|
|
5939
|
+
...resumed && live.cumulativeUsage ? { cumulativeUsage: { adapter: "grok", counters: { ...live.cumulativeUsage } } } : {}
|
|
5940
|
+
});
|
|
5941
|
+
} catch (err) {
|
|
5942
|
+
log.warn(`[${spec.session_id.slice(0, 8)}] grok: could not open the usage-counter record (${err instanceof Error ? err.message : String(err)})`);
|
|
5943
|
+
}
|
|
5859
5944
|
log.event("grok.started", {
|
|
5860
5945
|
session: spec.session_id,
|
|
5861
5946
|
acp_session: sessionId,
|
|
@@ -5880,7 +5965,21 @@ ${res.stderr ?? ""}`;
|
|
|
5880
5965
|
const meta = r._meta ?? null;
|
|
5881
5966
|
if (typeof meta?.modelId === "string" && meta.modelId) live.model = meta.modelId;
|
|
5882
5967
|
const { stopReason, error } = stopReasonFor(r.stopReason);
|
|
5883
|
-
|
|
5968
|
+
const cumulative = cumulativeUsageOf(meta);
|
|
5969
|
+
const usage = live.needsUsageBaseline ? null : toUsage2(meta, live.model, live.contextWindow, live.cumulativeUsage);
|
|
5970
|
+
if (cumulative) {
|
|
5971
|
+
if (live.needsUsageBaseline) {
|
|
5972
|
+
log.warn(`[${live.spec.session_id.slice(0, 8)}] grok: resumed without an earlier usage snapshot \u2014 this turn is unpriced and seeds the next increment`);
|
|
5973
|
+
live.needsUsageBaseline = false;
|
|
5974
|
+
}
|
|
5975
|
+
live.cumulativeUsage = cumulative;
|
|
5976
|
+
try {
|
|
5977
|
+
putStoredUsage(live.spec.session_id, "grok", cumulative);
|
|
5978
|
+
} catch (err) {
|
|
5979
|
+
log.warn(`[${live.spec.session_id.slice(0, 8)}] grok: could not remember usage counters (${err instanceof Error ? err.message : String(err)})`);
|
|
5980
|
+
}
|
|
5981
|
+
}
|
|
5982
|
+
this.settleTurn(live, { stopReason, usage, ...error ? { error } : {} });
|
|
5884
5983
|
}).catch((err) => {
|
|
5885
5984
|
this.settleTurn(live, { stopReason: "error", usage: null, error: err.message });
|
|
5886
5985
|
});
|
|
@@ -7790,6 +7889,8 @@ function askChild(input) {
|
|
|
7790
7889
|
} catch {
|
|
7791
7890
|
}
|
|
7792
7891
|
};
|
|
7892
|
+
child.stdin?.on("error", () => {
|
|
7893
|
+
});
|
|
7793
7894
|
child.on("error", (err) => finish({ problem: `could not start: ${err.message}` }));
|
|
7794
7895
|
child.on("exit", (code) => {
|
|
7795
7896
|
if (!done) finish({ problem: `ended (exit ${code ?? "signal"}) before answering` });
|
|
@@ -7849,8 +7950,56 @@ function samplesInWords(samples) {
|
|
|
7849
7950
|
return samples.map((s) => `${label[s.provider]} ${s.usedPct === null ? `(${s.words ?? "no number"})` : `${Math.round(s.usedPct)}% of ${win[s.window]}`}`).join(" \xB7 ") || "nothing to report";
|
|
7850
7951
|
}
|
|
7851
7952
|
|
|
7953
|
+
// package.json
|
|
7954
|
+
var package_default = {
|
|
7955
|
+
name: "@frod.io/bridge",
|
|
7956
|
+
version: "0.7.5",
|
|
7957
|
+
type: "module",
|
|
7958
|
+
description: "Your computer is a desk: hosts a Frod.io agent's sessions with your own Claude Code, Codex or Grok Build. Installs and updates itself.",
|
|
7959
|
+
license: "UNLICENSED",
|
|
7960
|
+
homepage: "https://frod.io",
|
|
7961
|
+
repository: {
|
|
7962
|
+
type: "git",
|
|
7963
|
+
url: "https://github.com/awafeek/agentic-engineering.git",
|
|
7964
|
+
directory: "apps/bridge"
|
|
7965
|
+
},
|
|
7966
|
+
bin: {
|
|
7967
|
+
"frod-bridge": "dist/cli.js"
|
|
7968
|
+
},
|
|
7969
|
+
files: [
|
|
7970
|
+
"dist",
|
|
7971
|
+
"README.md"
|
|
7972
|
+
],
|
|
7973
|
+
engines: {
|
|
7974
|
+
node: ">=20"
|
|
7975
|
+
},
|
|
7976
|
+
publishConfig: {
|
|
7977
|
+
access: "public"
|
|
7978
|
+
},
|
|
7979
|
+
scripts: {
|
|
7980
|
+
build: "node scripts/bundle.mjs && tsc --noEmit",
|
|
7981
|
+
bundle: "node scripts/bundle.mjs",
|
|
7982
|
+
lint: "eslint src",
|
|
7983
|
+
test: "vitest run --passWithNoTests",
|
|
7984
|
+
"type-check": "tsc --noEmit",
|
|
7985
|
+
"pack:check": "npm pack --dry-run"
|
|
7986
|
+
},
|
|
7987
|
+
devDependencies: {
|
|
7988
|
+
"@eslint/js": "^9.39.3",
|
|
7989
|
+
"@types/node": "^22.0.0",
|
|
7990
|
+
"@types/ws": "^8.18.1",
|
|
7991
|
+
esbuild: "^0.27.3",
|
|
7992
|
+
eslint: "^9.0.0",
|
|
7993
|
+
tsx: "^4.19.0",
|
|
7994
|
+
typescript: "^5.7.0",
|
|
7995
|
+
"typescript-eslint": "^8.56.1",
|
|
7996
|
+
vitest: "^4.0.18",
|
|
7997
|
+
ws: "^8.21.3"
|
|
7998
|
+
}
|
|
7999
|
+
};
|
|
8000
|
+
|
|
7852
8001
|
// src/cli.ts
|
|
7853
|
-
var VERSION =
|
|
8002
|
+
var VERSION = package_default.version;
|
|
7854
8003
|
function arg(flag, argv) {
|
|
7855
8004
|
const i = argv.indexOf(flag);
|
|
7856
8005
|
return i >= 0 ? argv[i + 1] : void 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frod.io/bridge",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Your computer is a desk: hosts a Frod.io agent's sessions with your own Claude Code, Codex or Grok Build. Installs and updates itself.",
|
|
6
6
|
"license": "UNLICENSED",
|