Skip to content

Commit e8e0742

Browse files
committed
fix: proxy JSON token parsing, wallet cache TTL, grep glob compat, clock skew guard, HTML svg/form strip, version fallback (v2.2.6)
1 parent f173225 commit e8e0742

File tree

16 files changed

+79
-37
lines changed

16 files changed

+79
-37
lines changed

dist/agent/llm.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export declare class ModelClient {
3434
private walletAddress;
3535
private cachedBaseWallet;
3636
private cachedSolanaWallet;
37+
private walletCacheTime;
38+
private static WALLET_CACHE_TTL;
3739
constructor(opts: LLMClientOptions);
3840
/**
3941
* Stream a completion from the BlockRun API.

dist/agent/llm.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export class ModelClient {
1313
walletAddress = '';
1414
cachedBaseWallet = null;
1515
cachedSolanaWallet = null;
16+
walletCacheTime = 0;
17+
static WALLET_CACHE_TTL = 30 * 60 * 1000; // 30 min TTL
1618
constructor(opts) {
1719
this.apiUrl = opts.apiUrl;
1820
this.chain = opts.chain;
@@ -219,8 +221,10 @@ export class ModelClient {
219221
}
220222
}
221223
async signBasePayment(response) {
222-
if (!this.cachedBaseWallet) {
224+
// Refresh wallet cache after TTL to pick up balance/key changes
225+
if (!this.cachedBaseWallet || (Date.now() - this.walletCacheTime > ModelClient.WALLET_CACHE_TTL)) {
223226
const w = getOrCreateWallet();
227+
this.walletCacheTime = Date.now();
224228
this.cachedBaseWallet = { privateKey: w.privateKey, address: w.address };
225229
}
226230
const wallet = this.cachedBaseWallet;
@@ -240,8 +244,9 @@ export class ModelClient {
240244
return { 'PAYMENT-SIGNATURE': payload };
241245
}
242246
async signSolanaPayment(response) {
243-
if (!this.cachedSolanaWallet) {
247+
if (!this.cachedSolanaWallet || (Date.now() - this.walletCacheTime > ModelClient.WALLET_CACHE_TTL)) {
244248
const w = await getOrCreateSolanaWallet();
249+
this.walletCacheTime = Date.now();
245250
this.cachedSolanaWallet = { privateKey: w.privateKey, address: w.address };
246251
}
247252
const wallet = this.cachedSolanaWallet;

dist/agent/optimize.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,10 @@ export function timeBasedCleanup(history, lastActivityTimestamp) {
129129
if (!lastActivityTimestamp) {
130130
return { history, cleaned: false };
131131
}
132-
const gapMinutes = (Date.now() - lastActivityTimestamp) / 60_000;
132+
const gapMs = Date.now() - lastActivityTimestamp;
133+
if (gapMs < 0)
134+
return { history, cleaned: false }; // Clock skew protection
135+
const gapMinutes = gapMs / 60_000;
133136
if (gapMinutes < IDLE_GAP_THRESHOLD_MINUTES) {
134137
return { history, cleaned: false };
135138
}

dist/config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import os from 'node:os';
33
import fs from 'node:fs';
44
import { fileURLToPath } from 'node:url';
55
const __dirname = path.dirname(fileURLToPath(import.meta.url));
6-
let _version = '1.1.0';
6+
let _version = '2.0.0';
77
try {
88
const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8'));
99
_version = pkg.version || _version;

dist/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ if (firstArg === 'solana' || firstArg === 'base') {
109109
}
110110
await startCommand(startOpts);
111111
}
112-
else if (!firstArg || (firstArg.startsWith('-') && firstArg !== '-h' && firstArg !== '--help' && firstArg !== '-V' && firstArg !== '--version')) {
112+
else if (!firstArg || (firstArg.startsWith('-') && !['-h', '--help', '-V', '--version'].includes(firstArg))) {
113113
// No subcommand or only flags — treat as 'start' with flags
114114
const startOpts = { version };
115115
for (let i = 0; i < args.length; i++) {

dist/proxy/server.js

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -394,16 +394,27 @@ export function createProxy(options) {
394394
if (done) {
395395
// Record stats from streaming response
396396
if (isStreaming && fullResponse) {
397-
// Search full response for the last output_tokens value
398-
const allOutputMatches = [...fullResponse.matchAll(/"output_tokens"\s*:\s*(\d+)/g)];
399-
const lastOutputMatch = allOutputMatches[allOutputMatches.length - 1];
400-
const inputMatch = fullResponse.match(/"input_tokens"\s*:\s*(\d+)/);
401-
if (lastOutputMatch) {
402-
const outputTokens = parseInt(lastOutputMatch[1], 10);
397+
// Extract token usage from SSE stream by parsing message_delta events
398+
let outputTokens = 0;
399+
let inputTokens = 0;
400+
// Find all data: lines and parse JSON to extract usage
401+
for (const line of fullResponse.split('\n')) {
402+
if (!line.startsWith('data: '))
403+
continue;
404+
const json = line.slice(6).trim();
405+
if (json === '[DONE]')
406+
continue;
407+
try {
408+
const parsed = JSON.parse(json);
409+
if (parsed.usage?.output_tokens)
410+
outputTokens = parsed.usage.output_tokens;
411+
if (parsed.usage?.input_tokens)
412+
inputTokens = parsed.usage.input_tokens;
413+
}
414+
catch { /* skip malformed */ }
415+
}
416+
if (outputTokens > 0) {
403417
trackOutputTokens(finalModel, outputTokens);
404-
const inputTokens = inputMatch
405-
? parseInt(inputMatch[1], 10)
406-
: 0;
407418
const latencyMs = Date.now() - requestStartTime;
408419
const cost = estimateCost(finalModel, inputTokens, outputTokens);
409420
recordUsage(finalModel, inputTokens, outputTokens, cost, latencyMs, usedFallback);

dist/tools/grep.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,12 @@ function runNativeGrep(opts, searchPath, mode, limit) {
105105
break;
106106
}
107107
if (opts.glob) {
108-
// Native grep doesn't support recursive globs like **/*.ts — strip leading **/
109-
const nativeGlob = opts.glob.replace(/^\*\*\//, '');
108+
// Native grep --include doesn't support ** or path separators
109+
// Extract file extension pattern for best compatibility
110+
const nativeGlob = opts.glob
111+
.replace(/^\*\*\//, '') // Strip leading **/
112+
.replace(/^.*\//, '') // Strip path prefix (src/ etc.)
113+
.replace(/\*\*/, '*'); // Convert ** to * for flat matching
110114
args.push(`--include=${nativeGlob}`);
111115
}
112116
args.push('--exclude-dir=node_modules', '--exclude-dir=.git', '--exclude-dir=dist');

dist/tools/webfetch.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ function stripHtml(html) {
9797
.replace(/<footer[^>]*>[\s\S]*?<\/footer>/gi, '')
9898
.replace(/<aside[^>]*>[\s\S]*?<\/aside>/gi, '')
9999
.replace(/<noscript[^>]*>[\s\S]*?<\/noscript>/gi, '')
100+
.replace(/<svg[^>]*>[\s\S]*?<\/svg>/gi, '')
101+
.replace(/<form[^>]*>[\s\S]*?<\/form>/gi, '')
100102
// Convert block elements to newlines for readability
101103
.replace(/<\/?(p|div|h[1-6]|li|br|tr)[^>]*>/gi, '\n')
102104
// Strip remaining tags

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@blockrun/runcode",
3-
"version": "2.2.5",
3+
"version": "2.2.6",
44
"description": "RunCode — AI coding agent powered by 41+ models. Pay per use with USDC.",
55
"type": "module",
66
"bin": {

src/agent/llm.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ export class ModelClient {
6262
private walletAddress = '';
6363
private cachedBaseWallet: { privateKey: string; address: string } | null = null;
6464
private cachedSolanaWallet: { privateKey: string; address: string } | null = null;
65+
private walletCacheTime = 0;
66+
private static WALLET_CACHE_TTL = 30 * 60 * 1000; // 30 min TTL
6567

6668
constructor(opts: LLMClientOptions) {
6769
this.apiUrl = opts.apiUrl;
@@ -283,8 +285,10 @@ export class ModelClient {
283285
private async signBasePayment(
284286
response: Response
285287
): Promise<Record<string, string>> {
286-
if (!this.cachedBaseWallet) {
288+
// Refresh wallet cache after TTL to pick up balance/key changes
289+
if (!this.cachedBaseWallet || (Date.now() - this.walletCacheTime > ModelClient.WALLET_CACHE_TTL)) {
287290
const w = getOrCreateWallet();
291+
this.walletCacheTime = Date.now();
288292
this.cachedBaseWallet = { privateKey: w.privateKey, address: w.address };
289293
}
290294
const wallet = this.cachedBaseWallet;
@@ -317,8 +321,9 @@ export class ModelClient {
317321
private async signSolanaPayment(
318322
response: Response
319323
): Promise<Record<string, string>> {
320-
if (!this.cachedSolanaWallet) {
324+
if (!this.cachedSolanaWallet || (Date.now() - this.walletCacheTime > ModelClient.WALLET_CACHE_TTL)) {
321325
const w = await getOrCreateSolanaWallet();
326+
this.walletCacheTime = Date.now();
322327
this.cachedSolanaWallet = { privateKey: w.privateKey, address: w.address };
323328
}
324329
const wallet = this.cachedSolanaWallet;

0 commit comments

Comments
 (0)