@mrciphersmith/keryx 0.2.82 → 0.2.83
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 +2 -1
- package/dist/cli.js +403 -73
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -233,7 +233,8 @@ What is in it today:
|
|
|
233
233
|
`/resume`, `/sessions`, `/status`, `/flows`, `/compact`, `/new`, and
|
|
234
234
|
`keryx sessions list|fork|export` — `fork` branches a conversation into a new
|
|
235
235
|
session that keeps its ancestry, without editing a transcript by hand.
|
|
236
|
-
`/status` is the session inspector
|
|
236
|
+
`/status` is the session inspector (identity, context window and limits when
|
|
237
|
+
the provider reported them); `/session-info` and `/info` are not aliases.
|
|
237
238
|
- **Responsive busy-turn UX.** A running main turn can be interrupted with
|
|
238
239
|
`/interrupt`; additional prompts are queued and answered as read-only side workers
|
|
239
240
|
(`side-1`) in the TUI so the shell stays usable under long-running turns.
|
package/dist/cli.js
CHANGED
|
@@ -70776,7 +70776,7 @@ import { spawnSync as spawnSync2 } from "child_process";
|
|
|
70776
70776
|
// package.json
|
|
70777
70777
|
var package_default = {
|
|
70778
70778
|
name: "@mrciphersmith/keryx",
|
|
70779
|
-
version: "0.2.
|
|
70779
|
+
version: "0.2.83",
|
|
70780
70780
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
70781
70781
|
private: false,
|
|
70782
70782
|
publishConfig: {
|
|
@@ -74085,6 +74085,345 @@ function openMcpTools(otui, chrome, options) {
|
|
|
74085
74085
|
// src/tui/tui-shell.ts
|
|
74086
74086
|
init_slate();
|
|
74087
74087
|
|
|
74088
|
+
// src/commands/model-limits.ts
|
|
74089
|
+
init_guard2();
|
|
74090
|
+
init_shell_config();
|
|
74091
|
+
init_providers();
|
|
74092
|
+
var MODEL_LIMITS_TIMEOUT_MS = 8000;
|
|
74093
|
+
var DEFAULT_OLLAMA_BASE_URL2 = "http://localhost:11434";
|
|
74094
|
+
var CONTEXT_WINDOW_KEYS = [
|
|
74095
|
+
"context_length",
|
|
74096
|
+
"context_window",
|
|
74097
|
+
"contextWindow",
|
|
74098
|
+
"max_model_len",
|
|
74099
|
+
"max_input_tokens",
|
|
74100
|
+
"maxInputTokens"
|
|
74101
|
+
];
|
|
74102
|
+
var NESTED_LIMIT_KEYS = ["architecture", "top_provider", "topProvider", "meta", "limits", "capabilities"];
|
|
74103
|
+
function asPositiveInt(value) {
|
|
74104
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
74105
|
+
return Math.floor(value);
|
|
74106
|
+
}
|
|
74107
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
74108
|
+
const parsed = Number(value);
|
|
74109
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
74110
|
+
return Math.floor(parsed);
|
|
74111
|
+
}
|
|
74112
|
+
}
|
|
74113
|
+
return;
|
|
74114
|
+
}
|
|
74115
|
+
function extractContextWindow(entry) {
|
|
74116
|
+
if (typeof entry !== "object" || entry === null) {
|
|
74117
|
+
return;
|
|
74118
|
+
}
|
|
74119
|
+
const rec = entry;
|
|
74120
|
+
for (const key of CONTEXT_WINDOW_KEYS) {
|
|
74121
|
+
const n = asPositiveInt(rec[key]);
|
|
74122
|
+
if (n !== undefined) {
|
|
74123
|
+
return n;
|
|
74124
|
+
}
|
|
74125
|
+
}
|
|
74126
|
+
for (const nested of NESTED_LIMIT_KEYS) {
|
|
74127
|
+
const inner = extractContextWindow(rec[nested]);
|
|
74128
|
+
if (inner !== undefined) {
|
|
74129
|
+
return inner;
|
|
74130
|
+
}
|
|
74131
|
+
}
|
|
74132
|
+
return;
|
|
74133
|
+
}
|
|
74134
|
+
function entryId(entry) {
|
|
74135
|
+
if (typeof entry !== "object" || entry === null) {
|
|
74136
|
+
return "";
|
|
74137
|
+
}
|
|
74138
|
+
const rec = entry;
|
|
74139
|
+
if (typeof rec.id === "string" && rec.id.length > 0) {
|
|
74140
|
+
return rec.id;
|
|
74141
|
+
}
|
|
74142
|
+
if (typeof rec.name === "string" && rec.name.length > 0) {
|
|
74143
|
+
return rec.name;
|
|
74144
|
+
}
|
|
74145
|
+
return "";
|
|
74146
|
+
}
|
|
74147
|
+
function findModelEntry(data, modelId) {
|
|
74148
|
+
const wanted = modelId.trim();
|
|
74149
|
+
if (wanted.length === 0) {
|
|
74150
|
+
return;
|
|
74151
|
+
}
|
|
74152
|
+
const lower = wanted.toLowerCase();
|
|
74153
|
+
const exact = data.find((entry) => entryId(entry).toLowerCase() === lower);
|
|
74154
|
+
if (exact !== undefined) {
|
|
74155
|
+
return exact;
|
|
74156
|
+
}
|
|
74157
|
+
const short = lower.includes("/") ? lower.split("/").pop() ?? lower : lower;
|
|
74158
|
+
return data.find((entry) => {
|
|
74159
|
+
const id = entryId(entry).toLowerCase();
|
|
74160
|
+
if (id.length === 0) {
|
|
74161
|
+
return false;
|
|
74162
|
+
}
|
|
74163
|
+
return id.endsWith(`/${short}`) || id.split("/").pop() === short;
|
|
74164
|
+
});
|
|
74165
|
+
}
|
|
74166
|
+
var REQUEST_LIMIT_HEADERS = [
|
|
74167
|
+
"x-ratelimit-limit-requests",
|
|
74168
|
+
"x-ratelimit-limit",
|
|
74169
|
+
"anthropic-ratelimit-requests-limit"
|
|
74170
|
+
];
|
|
74171
|
+
var REQUEST_REMAINING_HEADERS = [
|
|
74172
|
+
"x-ratelimit-remaining-requests",
|
|
74173
|
+
"x-ratelimit-remaining",
|
|
74174
|
+
"anthropic-ratelimit-requests-remaining"
|
|
74175
|
+
];
|
|
74176
|
+
var TOKEN_LIMIT_HEADERS = ["x-ratelimit-limit-tokens", "anthropic-ratelimit-tokens-limit"];
|
|
74177
|
+
var TOKEN_REMAINING_HEADERS = [
|
|
74178
|
+
"x-ratelimit-remaining-tokens",
|
|
74179
|
+
"anthropic-ratelimit-tokens-remaining"
|
|
74180
|
+
];
|
|
74181
|
+
var RESET_HEADERS = [
|
|
74182
|
+
"x-ratelimit-reset-requests",
|
|
74183
|
+
"x-ratelimit-reset",
|
|
74184
|
+
"anthropic-ratelimit-requests-reset",
|
|
74185
|
+
"retry-after"
|
|
74186
|
+
];
|
|
74187
|
+
function firstHeaderInt(headers, names) {
|
|
74188
|
+
for (const name of names) {
|
|
74189
|
+
const n = asPositiveInt(headers.get(name));
|
|
74190
|
+
if (n !== undefined) {
|
|
74191
|
+
return n;
|
|
74192
|
+
}
|
|
74193
|
+
}
|
|
74194
|
+
return;
|
|
74195
|
+
}
|
|
74196
|
+
function firstHeaderText(headers, names) {
|
|
74197
|
+
for (const name of names) {
|
|
74198
|
+
const raw = headers.get(name)?.trim();
|
|
74199
|
+
if (raw !== undefined && raw.length > 0) {
|
|
74200
|
+
return raw;
|
|
74201
|
+
}
|
|
74202
|
+
}
|
|
74203
|
+
return;
|
|
74204
|
+
}
|
|
74205
|
+
function parseRateLimitHeaders(headers) {
|
|
74206
|
+
const snapshot = {};
|
|
74207
|
+
const requestsLimit = firstHeaderInt(headers, REQUEST_LIMIT_HEADERS);
|
|
74208
|
+
const requestsRemaining = firstHeaderInt(headers, REQUEST_REMAINING_HEADERS);
|
|
74209
|
+
const tokensLimit = firstHeaderInt(headers, TOKEN_LIMIT_HEADERS);
|
|
74210
|
+
const tokensRemaining = firstHeaderInt(headers, TOKEN_REMAINING_HEADERS);
|
|
74211
|
+
const reset = firstHeaderText(headers, RESET_HEADERS);
|
|
74212
|
+
if (requestsLimit !== undefined)
|
|
74213
|
+
snapshot.requestsLimit = requestsLimit;
|
|
74214
|
+
if (requestsRemaining !== undefined)
|
|
74215
|
+
snapshot.requestsRemaining = requestsRemaining;
|
|
74216
|
+
if (tokensLimit !== undefined)
|
|
74217
|
+
snapshot.tokensLimit = tokensLimit;
|
|
74218
|
+
if (tokensRemaining !== undefined)
|
|
74219
|
+
snapshot.tokensRemaining = tokensRemaining;
|
|
74220
|
+
if (reset !== undefined)
|
|
74221
|
+
snapshot.reset = reset;
|
|
74222
|
+
return Object.keys(snapshot).length === 0 ? undefined : snapshot;
|
|
74223
|
+
}
|
|
74224
|
+
function formatRateLimit(limit) {
|
|
74225
|
+
if (limit === undefined) {
|
|
74226
|
+
return;
|
|
74227
|
+
}
|
|
74228
|
+
const parts = [];
|
|
74229
|
+
if (limit.requestsRemaining !== undefined && limit.requestsLimit !== undefined) {
|
|
74230
|
+
parts.push(`${limit.requestsRemaining}/${limit.requestsLimit} requests`);
|
|
74231
|
+
} else if (limit.requestsRemaining !== undefined) {
|
|
74232
|
+
parts.push(`${limit.requestsRemaining} requests left`);
|
|
74233
|
+
} else if (limit.requestsLimit !== undefined) {
|
|
74234
|
+
parts.push(`${limit.requestsLimit} request cap`);
|
|
74235
|
+
}
|
|
74236
|
+
if (limit.tokensRemaining !== undefined && limit.tokensLimit !== undefined) {
|
|
74237
|
+
parts.push(`${limit.tokensRemaining}/${limit.tokensLimit} tokens`);
|
|
74238
|
+
} else if (limit.tokensRemaining !== undefined) {
|
|
74239
|
+
parts.push(`${limit.tokensRemaining} tokens left`);
|
|
74240
|
+
}
|
|
74241
|
+
if (limit.reset !== undefined) {
|
|
74242
|
+
parts.push(`reset ${limit.reset}`);
|
|
74243
|
+
}
|
|
74244
|
+
return parts.length === 0 ? undefined : parts.join(" \xB7 ");
|
|
74245
|
+
}
|
|
74246
|
+
function extractOllamaContextWindow(body) {
|
|
74247
|
+
if (typeof body !== "object" || body === null) {
|
|
74248
|
+
return;
|
|
74249
|
+
}
|
|
74250
|
+
const rec = body;
|
|
74251
|
+
if (typeof rec.parameters === "string") {
|
|
74252
|
+
const match = /\bnum_ctx\s+(\d+)/.exec(rec.parameters);
|
|
74253
|
+
if (match?.[1] !== undefined) {
|
|
74254
|
+
const n = asPositiveInt(match[1]);
|
|
74255
|
+
if (n !== undefined) {
|
|
74256
|
+
return n;
|
|
74257
|
+
}
|
|
74258
|
+
}
|
|
74259
|
+
}
|
|
74260
|
+
const info = rec.model_info;
|
|
74261
|
+
if (typeof info === "object" && info !== null) {
|
|
74262
|
+
for (const [key, value] of Object.entries(info)) {
|
|
74263
|
+
if (key === "context_length" || key.endsWith(".context_length")) {
|
|
74264
|
+
const n = asPositiveInt(value);
|
|
74265
|
+
if (n !== undefined) {
|
|
74266
|
+
return n;
|
|
74267
|
+
}
|
|
74268
|
+
}
|
|
74269
|
+
}
|
|
74270
|
+
}
|
|
74271
|
+
return extractContextWindow(body);
|
|
74272
|
+
}
|
|
74273
|
+
async function timedFetch(fetchFn, url, init, timeoutMs) {
|
|
74274
|
+
const controller = new AbortController;
|
|
74275
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
74276
|
+
try {
|
|
74277
|
+
return await fetchFn(url, { ...init, signal: controller.signal });
|
|
74278
|
+
} catch {
|
|
74279
|
+
return;
|
|
74280
|
+
} finally {
|
|
74281
|
+
clearTimeout(timer);
|
|
74282
|
+
}
|
|
74283
|
+
}
|
|
74284
|
+
function ollamaShowAllowed(baseUrl) {
|
|
74285
|
+
let host;
|
|
74286
|
+
try {
|
|
74287
|
+
host = new URL(baseUrl).hostname;
|
|
74288
|
+
} catch {
|
|
74289
|
+
return false;
|
|
74290
|
+
}
|
|
74291
|
+
if (isPrivateEgressHost(host) && !isLoopbackHost(host)) {
|
|
74292
|
+
return false;
|
|
74293
|
+
}
|
|
74294
|
+
return true;
|
|
74295
|
+
}
|
|
74296
|
+
async function fetchCompatLimits(fetchFn, providerName, model, baseUrl, env, timeoutMs) {
|
|
74297
|
+
const compat = providerByName(providerName);
|
|
74298
|
+
if (compat === undefined) {
|
|
74299
|
+
return {};
|
|
74300
|
+
}
|
|
74301
|
+
const resolved = resolveProviderBaseUrl({ ...compat, ...baseUrl !== undefined ? { baseUrl } : {} }, env);
|
|
74302
|
+
const url = `${resolved.replace(/\/+$/, "")}${compat.modelsPath ?? DEFAULT_MODELS_PATH}`;
|
|
74303
|
+
const apiKey = providerApiKey(compat, env) ?? compat.apiKey;
|
|
74304
|
+
const init = {};
|
|
74305
|
+
if (apiKey !== undefined && apiKey.length > 0) {
|
|
74306
|
+
init.headers = { authorization: `Bearer ${apiKey}` };
|
|
74307
|
+
}
|
|
74308
|
+
const res = await timedFetch(fetchFn, url, init, timeoutMs);
|
|
74309
|
+
if (res === undefined || !res.ok) {
|
|
74310
|
+
return {};
|
|
74311
|
+
}
|
|
74312
|
+
const rateLimit = parseRateLimitHeaders(res.headers);
|
|
74313
|
+
let body;
|
|
74314
|
+
try {
|
|
74315
|
+
body = await res.json();
|
|
74316
|
+
} catch {
|
|
74317
|
+
return rateLimit === undefined ? {} : { rateLimit };
|
|
74318
|
+
}
|
|
74319
|
+
const data = body?.data;
|
|
74320
|
+
const entries = Array.isArray(data) ? data : Array.isArray(body) ? body : [];
|
|
74321
|
+
const entry = findModelEntry(entries, model);
|
|
74322
|
+
const contextWindow = extractContextWindow(entry);
|
|
74323
|
+
return {
|
|
74324
|
+
...contextWindow !== undefined ? { contextWindow, contextSource: "live-models" } : {},
|
|
74325
|
+
...rateLimit !== undefined ? { rateLimit } : {}
|
|
74326
|
+
};
|
|
74327
|
+
}
|
|
74328
|
+
async function fetchOllamaLimits(fetchFn, model, baseUrl, timeoutMs) {
|
|
74329
|
+
const resolved = (baseUrl ?? DEFAULT_OLLAMA_BASE_URL2).replace(/\/+$/, "");
|
|
74330
|
+
if (!ollamaShowAllowed(resolved)) {
|
|
74331
|
+
return {};
|
|
74332
|
+
}
|
|
74333
|
+
const res = await timedFetch(fetchFn, `${resolved}/api/show`, {
|
|
74334
|
+
method: "POST",
|
|
74335
|
+
headers: { "content-type": "application/json" },
|
|
74336
|
+
body: JSON.stringify({ name: model })
|
|
74337
|
+
}, timeoutMs);
|
|
74338
|
+
if (res === undefined || !res.ok) {
|
|
74339
|
+
return {};
|
|
74340
|
+
}
|
|
74341
|
+
let body;
|
|
74342
|
+
try {
|
|
74343
|
+
body = await res.json();
|
|
74344
|
+
} catch {
|
|
74345
|
+
return {};
|
|
74346
|
+
}
|
|
74347
|
+
const contextWindow = extractOllamaContextWindow(body);
|
|
74348
|
+
return contextWindow === undefined ? {} : { contextWindow, contextSource: "ollama-show" };
|
|
74349
|
+
}
|
|
74350
|
+
async function loadSessionLimits(input) {
|
|
74351
|
+
const fetchFn = input.fetch ?? globalThis.fetch;
|
|
74352
|
+
const env = envWithSavedApiKeys(input.env ?? process.env);
|
|
74353
|
+
const timeoutMs = input.timeoutMs ?? MODEL_LIMITS_TIMEOUT_MS;
|
|
74354
|
+
const provider = input.provider.trim();
|
|
74355
|
+
const model = input.model.trim();
|
|
74356
|
+
const limitsPromise = provider === "ollama" ? fetchOllamaLimits(fetchFn, model, input.baseUrl, timeoutMs) : providerByName(provider) !== undefined ? fetchCompatLimits(fetchFn, provider, model, input.baseUrl, env, timeoutMs) : Promise.resolve({});
|
|
74357
|
+
const capable = balanceCapableProvider(provider);
|
|
74358
|
+
const balancePromise = capable === undefined ? Promise.resolve(undefined) : fetchProviderBalance(fetchFn, { ...capable, ...input.baseUrl !== undefined ? { baseUrl: input.baseUrl } : {} }, providerApiKey(capable, env) ?? capable.apiKey, { timeoutMs });
|
|
74359
|
+
const [limits, balance] = await Promise.all([limitsPromise, balancePromise]);
|
|
74360
|
+
return {
|
|
74361
|
+
...limits,
|
|
74362
|
+
...balance !== undefined ? { balance } : {}
|
|
74363
|
+
};
|
|
74364
|
+
}
|
|
74365
|
+
|
|
74366
|
+
// src/tui/balance-panel.ts
|
|
74367
|
+
init_providers();
|
|
74368
|
+
init_shell_config();
|
|
74369
|
+
function formatBalance(balance) {
|
|
74370
|
+
if (balance === undefined) {
|
|
74371
|
+
return "\u2014";
|
|
74372
|
+
}
|
|
74373
|
+
const amount = balance.remaining ?? balance.total;
|
|
74374
|
+
const symbol = balance.currency === "USD" ? "$" : balance.currency === "EUR" ? "\u20AC" : balance.currency === "GBP" ? "\xA3" : `${balance.currency} `;
|
|
74375
|
+
return `${symbol}${amount.toFixed(2)}`;
|
|
74376
|
+
}
|
|
74377
|
+
function mountBalancePanel(sidebarTop, otui, renderer, options) {
|
|
74378
|
+
const core = otui;
|
|
74379
|
+
const r = renderer;
|
|
74380
|
+
const env = envWithSavedApiKeys(options.env ?? process.env);
|
|
74381
|
+
let current;
|
|
74382
|
+
let textNode;
|
|
74383
|
+
const capable = balanceCapableProvider(options.provider) !== undefined;
|
|
74384
|
+
if (capable) {
|
|
74385
|
+
const label = new core.TextRenderable(r, {
|
|
74386
|
+
id: "sb-balance-k",
|
|
74387
|
+
content: core.t`${core.dim("Balance")}`,
|
|
74388
|
+
marginTop: 1
|
|
74389
|
+
});
|
|
74390
|
+
sidebarTop.add(label);
|
|
74391
|
+
const value = new core.TextRenderable(r, {
|
|
74392
|
+
id: "sb-balance-v",
|
|
74393
|
+
content: core.t`${core.dim("\u2026")}`,
|
|
74394
|
+
onMouseDown: () => {
|
|
74395
|
+
refresh();
|
|
74396
|
+
}
|
|
74397
|
+
});
|
|
74398
|
+
textNode = value;
|
|
74399
|
+
sidebarTop.add(value);
|
|
74400
|
+
}
|
|
74401
|
+
const paint = (balance) => {
|
|
74402
|
+
current = balance;
|
|
74403
|
+
if (textNode !== undefined) {
|
|
74404
|
+
textNode.content = core.t`${core.dim(formatBalance(balance))}`;
|
|
74405
|
+
}
|
|
74406
|
+
};
|
|
74407
|
+
const refresh = async () => {
|
|
74408
|
+
const provider = balanceCapableProvider(options.provider);
|
|
74409
|
+
if (provider === undefined) {
|
|
74410
|
+
paint(undefined);
|
|
74411
|
+
return;
|
|
74412
|
+
}
|
|
74413
|
+
const apiKey = providerApiKey(provider, env);
|
|
74414
|
+
if (apiKey === undefined) {
|
|
74415
|
+
paint(undefined);
|
|
74416
|
+
return;
|
|
74417
|
+
}
|
|
74418
|
+
const base = resolveProviderBaseUrl(provider, env);
|
|
74419
|
+
const withBase = { ...provider, baseUrl: base };
|
|
74420
|
+
const balance = await fetchProviderBalance(options.fetch ?? globalThis.fetch, withBase, apiKey, { ...options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {} });
|
|
74421
|
+
paint(balance);
|
|
74422
|
+
};
|
|
74423
|
+
refresh();
|
|
74424
|
+
return { refresh, current: () => current };
|
|
74425
|
+
}
|
|
74426
|
+
|
|
74088
74427
|
// src/tui/context-usage.ts
|
|
74089
74428
|
var CONTEXT_BAR_WIDTH = 28;
|
|
74090
74429
|
function lastTurnUsed(usage) {
|
|
@@ -74103,6 +74442,14 @@ function renderUsageBar(filled, width = CONTEXT_BAR_WIDTH) {
|
|
|
74103
74442
|
const n = Math.max(0, Math.min(width, filled > 0 ? width : 0));
|
|
74104
74443
|
return `[${"\u2588".repeat(n)}${"\u2591".repeat(width - n)}]`;
|
|
74105
74444
|
}
|
|
74445
|
+
function renderWindowBar(used, window, width = CONTEXT_BAR_WIDTH) {
|
|
74446
|
+
if (window <= 0) {
|
|
74447
|
+
return renderUsageBar(used, width);
|
|
74448
|
+
}
|
|
74449
|
+
const ratio = Math.max(0, Math.min(1, used / window));
|
|
74450
|
+
const filled = Math.round(ratio * width);
|
|
74451
|
+
return `[${"\u2588".repeat(filled)}${"\u2591".repeat(width - filled)}]`;
|
|
74452
|
+
}
|
|
74106
74453
|
function buildContextUsage(source) {
|
|
74107
74454
|
const lastIn = source.usage?.inputTokens;
|
|
74108
74455
|
const lastOut = source.usage?.outputTokens;
|
|
@@ -74110,6 +74457,7 @@ function buildContextUsage(source) {
|
|
|
74110
74457
|
const hasEstimate = source.estimateTokens !== undefined;
|
|
74111
74458
|
const total = hasEstimate ? source.estimateTokens ?? 0 : last ?? 0;
|
|
74112
74459
|
const estimated = hasEstimate || last === undefined;
|
|
74460
|
+
const window = source.contextWindow !== undefined && source.contextWindow > 0 ? source.contextWindow : undefined;
|
|
74113
74461
|
const segments = [];
|
|
74114
74462
|
if (hasEstimate) {
|
|
74115
74463
|
segments.push({ id: "history", label: "history (est.)", tokens: source.estimateTokens ?? 0 });
|
|
@@ -74120,12 +74468,16 @@ function buildContextUsage(source) {
|
|
|
74120
74468
|
if (lastOut !== undefined) {
|
|
74121
74469
|
segments.push({ id: "last-out", label: "last out", tokens: lastOut });
|
|
74122
74470
|
}
|
|
74471
|
+
if (window !== undefined) {
|
|
74472
|
+
segments.push({ id: "free", label: "free", tokens: Math.max(0, window - total) });
|
|
74473
|
+
}
|
|
74123
74474
|
return {
|
|
74124
74475
|
total,
|
|
74125
74476
|
estimated,
|
|
74477
|
+
...window !== undefined ? { window, usedPct: Math.min(100, total / window * 100), free: Math.max(0, window - total) } : {},
|
|
74126
74478
|
segments,
|
|
74127
|
-
bar: renderUsageBar(total),
|
|
74128
|
-
note: total === 0 ? "No context usage yet." : "No model context window is known. The bar is relative to used tokens, not a billed limit."
|
|
74479
|
+
bar: window === undefined ? renderUsageBar(total) : renderWindowBar(total, window),
|
|
74480
|
+
note: total === 0 ? "No context usage yet." : window === undefined ? "No model context window is known. The bar is relative to used tokens, not a billed limit." : `Context window ${window.toLocaleString()} tokens (provider-reported).`
|
|
74129
74481
|
};
|
|
74130
74482
|
}
|
|
74131
74483
|
function formatContextUsageText(view) {
|
|
@@ -74137,7 +74489,8 @@ function formatContextUsageText(view) {
|
|
|
74137
74489
|
const kind = view.estimated ? "tokens (estimate)" : "tokens";
|
|
74138
74490
|
const width = view.segments.reduce((max, segment) => Math.max(max, segment.label.length), 0);
|
|
74139
74491
|
const rows = view.segments.map((segment) => ` ${segment.label.padEnd(width)} ${segment.tokens}`);
|
|
74140
|
-
|
|
74492
|
+
const usedLine = view.window !== undefined ? ` Used ${view.total.toLocaleString()} / ${view.window.toLocaleString()} ${kind} (${(view.usedPct ?? 0).toFixed(1)}%)` : ` Used ${view.total} ${kind}`;
|
|
74493
|
+
return [`Context`, usedLine, ` ${view.bar}`, ...rows, "", ` ${view.note}`, ""].join(`
|
|
74141
74494
|
`);
|
|
74142
74495
|
}
|
|
74143
74496
|
|
|
@@ -74182,12 +74535,17 @@ function usageUsed(usage) {
|
|
|
74182
74535
|
return (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
|
|
74183
74536
|
}
|
|
74184
74537
|
function contextRow(source) {
|
|
74538
|
+
const window = source.limits?.contextWindow;
|
|
74185
74539
|
const used = usageUsed(source.usage);
|
|
74186
|
-
|
|
74187
|
-
|
|
74540
|
+
const usedLabel = used !== undefined ? `${used} tokens` : source.estimateTokens !== undefined ? `${source.estimateTokens} tokens (estimate)` : undefined;
|
|
74541
|
+
if (usedLabel !== undefined && window !== undefined) {
|
|
74542
|
+
return `${usedLabel} / ${window.toLocaleString()}`;
|
|
74543
|
+
}
|
|
74544
|
+
if (usedLabel !== undefined) {
|
|
74545
|
+
return usedLabel;
|
|
74188
74546
|
}
|
|
74189
|
-
if (
|
|
74190
|
-
return
|
|
74547
|
+
if (window !== undefined) {
|
|
74548
|
+
return `window ${window.toLocaleString()}`;
|
|
74191
74549
|
}
|
|
74192
74550
|
return MISSING;
|
|
74193
74551
|
}
|
|
@@ -74218,11 +74576,23 @@ function buildSessionInfoSnapshot(source) {
|
|
|
74218
74576
|
label: "Last turn output",
|
|
74219
74577
|
value: source.usage?.outputTokens === undefined ? MISSING : String(source.usage.outputTokens)
|
|
74220
74578
|
},
|
|
74221
|
-
{ label: "Context estimate", value: estimate }
|
|
74579
|
+
{ label: "Context estimate", value: estimate },
|
|
74580
|
+
{
|
|
74581
|
+
label: "Context window",
|
|
74582
|
+
value: source.limits?.contextWindow === undefined ? MISSING : source.limits.contextWindow.toLocaleString()
|
|
74583
|
+
}
|
|
74222
74584
|
];
|
|
74585
|
+
const rate = formatRateLimit(source.limits?.rateLimit);
|
|
74586
|
+
if (rate !== undefined) {
|
|
74587
|
+
usageRows.push({ label: "Rate limit", value: rate });
|
|
74588
|
+
}
|
|
74589
|
+
if (source.limits?.balance !== undefined) {
|
|
74590
|
+
usageRows.push({ label: "Balance", value: formatBalance(source.limits.balance) });
|
|
74591
|
+
}
|
|
74223
74592
|
const context = buildContextUsage({
|
|
74224
74593
|
estimateTokens: source.estimateTokens,
|
|
74225
|
-
usage: source.usage
|
|
74594
|
+
usage: source.usage,
|
|
74595
|
+
...source.limits?.contextWindow !== undefined ? { contextWindow: source.limits.contextWindow } : {}
|
|
74226
74596
|
});
|
|
74227
74597
|
const sessionWorkspaces = workspacesInSession(source.workspaces ?? [], {
|
|
74228
74598
|
sessionId: id,
|
|
@@ -74350,7 +74720,7 @@ function presentSessionInfo(openModal, otui, chrome, options) {
|
|
|
74350
74720
|
`), width);
|
|
74351
74721
|
return;
|
|
74352
74722
|
}
|
|
74353
|
-
paintRows(otui, renderer, body, snapshot.sessionRows, width);
|
|
74723
|
+
paintRows(otui, renderer, body, [...snapshot.sessionRows, ...snapshot.usageRows], width);
|
|
74354
74724
|
},
|
|
74355
74725
|
onClose: () => {
|
|
74356
74726
|
unsubscribeKey?.();
|
|
@@ -74437,7 +74807,7 @@ var AGENT_SLASH_COMMANDS = [
|
|
|
74437
74807
|
{ name: "/sessions", description: "Open the session list and switch to one", modes: AGENT_ONLY },
|
|
74438
74808
|
{
|
|
74439
74809
|
name: "/status",
|
|
74440
|
-
description: "Show session identity, context, workspaces, and flows",
|
|
74810
|
+
description: "Show session identity, context window, limits, workspaces, and flows",
|
|
74441
74811
|
modes: BOTH
|
|
74442
74812
|
},
|
|
74443
74813
|
{ name: "/flows", description: "Browse project flows and inspect one", modes: BOTH },
|
|
@@ -75568,67 +75938,6 @@ function presentGamesModal(openModalFn, otui, chrome, options = {}, registry = c
|
|
|
75568
75938
|
function openGamesModal(otui, chrome, options = {}) {
|
|
75569
75939
|
return presentGamesModal((hostOtui, hostChrome, input) => openModal(hostOtui, hostChrome, input), otui, chrome, options);
|
|
75570
75940
|
}
|
|
75571
|
-
// src/tui/balance-panel.ts
|
|
75572
|
-
init_providers();
|
|
75573
|
-
init_shell_config();
|
|
75574
|
-
function formatBalance(balance) {
|
|
75575
|
-
if (balance === undefined) {
|
|
75576
|
-
return "\u2014";
|
|
75577
|
-
}
|
|
75578
|
-
const amount = balance.remaining ?? balance.total;
|
|
75579
|
-
const symbol = balance.currency === "USD" ? "$" : balance.currency === "EUR" ? "\u20AC" : balance.currency === "GBP" ? "\xA3" : `${balance.currency} `;
|
|
75580
|
-
return `${symbol}${amount.toFixed(2)}`;
|
|
75581
|
-
}
|
|
75582
|
-
function mountBalancePanel(sidebarTop, otui, renderer, options) {
|
|
75583
|
-
const core = otui;
|
|
75584
|
-
const r = renderer;
|
|
75585
|
-
const env = envWithSavedApiKeys(options.env ?? process.env);
|
|
75586
|
-
let current;
|
|
75587
|
-
let textNode;
|
|
75588
|
-
const capable = balanceCapableProvider(options.provider) !== undefined;
|
|
75589
|
-
if (capable) {
|
|
75590
|
-
const label = new core.TextRenderable(r, {
|
|
75591
|
-
id: "sb-balance-k",
|
|
75592
|
-
content: core.t`${core.dim("Balance")}`,
|
|
75593
|
-
marginTop: 1
|
|
75594
|
-
});
|
|
75595
|
-
sidebarTop.add(label);
|
|
75596
|
-
const value = new core.TextRenderable(r, {
|
|
75597
|
-
id: "sb-balance-v",
|
|
75598
|
-
content: core.t`${core.dim("\u2026")}`,
|
|
75599
|
-
onMouseDown: () => {
|
|
75600
|
-
refresh();
|
|
75601
|
-
}
|
|
75602
|
-
});
|
|
75603
|
-
textNode = value;
|
|
75604
|
-
sidebarTop.add(value);
|
|
75605
|
-
}
|
|
75606
|
-
const paint = (balance) => {
|
|
75607
|
-
current = balance;
|
|
75608
|
-
if (textNode !== undefined) {
|
|
75609
|
-
textNode.content = core.t`${core.dim(formatBalance(balance))}`;
|
|
75610
|
-
}
|
|
75611
|
-
};
|
|
75612
|
-
const refresh = async () => {
|
|
75613
|
-
const provider = balanceCapableProvider(options.provider);
|
|
75614
|
-
if (provider === undefined) {
|
|
75615
|
-
paint(undefined);
|
|
75616
|
-
return;
|
|
75617
|
-
}
|
|
75618
|
-
const apiKey = providerApiKey(provider, env);
|
|
75619
|
-
if (apiKey === undefined) {
|
|
75620
|
-
paint(undefined);
|
|
75621
|
-
return;
|
|
75622
|
-
}
|
|
75623
|
-
const base = resolveProviderBaseUrl(provider, env);
|
|
75624
|
-
const withBase = { ...provider, baseUrl: base };
|
|
75625
|
-
const balance = await fetchProviderBalance(options.fetch ?? globalThis.fetch, withBase, apiKey, { ...options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {} });
|
|
75626
|
-
paint(balance);
|
|
75627
|
-
};
|
|
75628
|
-
refresh();
|
|
75629
|
-
return { refresh, current: () => current };
|
|
75630
|
-
}
|
|
75631
|
-
|
|
75632
75941
|
// src/tui/tui-shell.ts
|
|
75633
75942
|
init_providers();
|
|
75634
75943
|
init_patch_risk();
|
|
@@ -81369,12 +81678,18 @@ Staying in the current session.
|
|
|
81369
81678
|
(async () => {
|
|
81370
81679
|
const cwd = inspectorCwd();
|
|
81371
81680
|
const [workspaces, flows] = await Promise.all([loadInspectorWorkspaces(cwd), loadInspectorFlows(cwd)]);
|
|
81681
|
+
const limits = await loadSessionLimits({
|
|
81682
|
+
provider: currentSel.provider,
|
|
81683
|
+
model: currentSel.model,
|
|
81684
|
+
...currentSel.baseUrl !== undefined ? { baseUrl: currentSel.baseUrl } : {}
|
|
81685
|
+
});
|
|
81372
81686
|
const snapshot = buildSessionInfoSnapshot({
|
|
81373
81687
|
summary: liveSession.summary,
|
|
81374
81688
|
selection: currentSel,
|
|
81375
81689
|
version: package_default.version,
|
|
81376
81690
|
usage: lastUsage,
|
|
81377
81691
|
estimateTokens: estimateContextTokens(history),
|
|
81692
|
+
limits,
|
|
81378
81693
|
sessionText: history.map((message) => message.content).join(`
|
|
81379
81694
|
`),
|
|
81380
81695
|
workspaces,
|
|
@@ -82922,11 +83237,17 @@ ${formatThemeList(getThemeId())}`)}`);
|
|
|
82922
83237
|
return;
|
|
82923
83238
|
}
|
|
82924
83239
|
const summary = cwd !== undefined ? latestSession(cwd) : undefined;
|
|
83240
|
+
const limits = await loadSessionLimits({
|
|
83241
|
+
provider: selection.provider,
|
|
83242
|
+
model: selection.model,
|
|
83243
|
+
...selection.baseUrl !== undefined ? { baseUrl: selection.baseUrl } : {}
|
|
83244
|
+
});
|
|
82925
83245
|
const snapshot = buildSessionInfoSnapshot({
|
|
82926
83246
|
summary,
|
|
82927
83247
|
selection,
|
|
82928
83248
|
version: package_default.version,
|
|
82929
83249
|
estimateTokens: estimateContextTokens(seen),
|
|
83250
|
+
limits,
|
|
82930
83251
|
sessionText: seen.map((message) => message.content).join(`
|
|
82931
83252
|
`),
|
|
82932
83253
|
workspaces,
|
|
@@ -83161,6 +83482,11 @@ Starting a new session.
|
|
|
83161
83482
|
selection: { provider: providerName, model: modelName },
|
|
83162
83483
|
version: package_default.version,
|
|
83163
83484
|
estimateTokens: estimateContextTokens(history),
|
|
83485
|
+
limits: await loadSessionLimits({
|
|
83486
|
+
provider: providerName,
|
|
83487
|
+
model: modelName,
|
|
83488
|
+
...baseUrl !== undefined ? { baseUrl } : {}
|
|
83489
|
+
}),
|
|
83164
83490
|
sessionText: history.map((message) => message.content).join(`
|
|
83165
83491
|
`),
|
|
83166
83492
|
workspaces,
|
|
@@ -83990,6 +84316,10 @@ New session ${shortSessionId(live.summary.id)}.
|
|
|
83990
84316
|
version: package_default.version,
|
|
83991
84317
|
usage: lastUsage,
|
|
83992
84318
|
estimateTokens: estimateContextTokens(history),
|
|
84319
|
+
limits: await loadSessionLimits({
|
|
84320
|
+
provider: deps.providerId,
|
|
84321
|
+
model: deps.modelId
|
|
84322
|
+
}),
|
|
83993
84323
|
sessionText: history.map((message) => message.content).join(`
|
|
83994
84324
|
`),
|
|
83995
84325
|
workspaces,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.83",
|
|
4
4
|
"description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|