@blockrun/clawrouter 0.9.12 → 0.9.13
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 +259 -33
- package/dist/cli.js.map +1 -1
- package/dist/index.js +259 -33
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/scripts/reinstall.sh +17 -0
package/dist/cli.js
CHANGED
|
@@ -2441,12 +2441,13 @@ var DEFAULT_COMPRESSION_CONFIG = {
|
|
|
2441
2441
|
// src/compression/layers/deduplication.ts
|
|
2442
2442
|
import crypto2 from "crypto";
|
|
2443
2443
|
function hashMessage(message) {
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
message.content
|
|
2447
|
-
|
|
2448
|
-
message.
|
|
2449
|
-
|
|
2444
|
+
let contentStr = "";
|
|
2445
|
+
if (typeof message.content === "string") {
|
|
2446
|
+
contentStr = message.content;
|
|
2447
|
+
} else if (Array.isArray(message.content)) {
|
|
2448
|
+
contentStr = JSON.stringify(message.content);
|
|
2449
|
+
}
|
|
2450
|
+
const parts = [message.role, contentStr, message.tool_call_id || "", message.name || ""];
|
|
2450
2451
|
if (message.tool_calls) {
|
|
2451
2452
|
parts.push(
|
|
2452
2453
|
JSON.stringify(
|
|
@@ -2509,13 +2510,13 @@ function deduplicateMessages(messages) {
|
|
|
2509
2510
|
|
|
2510
2511
|
// src/compression/layers/whitespace.ts
|
|
2511
2512
|
function normalizeWhitespace(content) {
|
|
2512
|
-
if (!content) return content;
|
|
2513
|
+
if (!content || typeof content !== "string") return content;
|
|
2513
2514
|
return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n{3,}/g, "\n\n").replace(/[ \t]+$/gm, "").replace(/([^\n]) {2,}/g, "$1 ").replace(/^[ ]{8,}/gm, (match) => " ".repeat(Math.ceil(match.length / 4))).replace(/\t/g, " ").trim();
|
|
2514
2515
|
}
|
|
2515
2516
|
function normalizeMessagesWhitespace(messages) {
|
|
2516
2517
|
let charsSaved = 0;
|
|
2517
2518
|
const result = messages.map((message) => {
|
|
2518
|
-
if (!message.content) return message;
|
|
2519
|
+
if (!message.content || typeof message.content !== "string") return message;
|
|
2519
2520
|
const originalLength = message.content.length;
|
|
2520
2521
|
const normalizedContent = normalizeWhitespace(message.content);
|
|
2521
2522
|
charsSaved += originalLength - normalizedContent.length;
|
|
@@ -2619,6 +2620,9 @@ function generateCodebookHeader(usedCodes, pathMap = {}) {
|
|
|
2619
2620
|
|
|
2620
2621
|
// src/compression/layers/dictionary.ts
|
|
2621
2622
|
function encodeContent(content, inverseCodebook) {
|
|
2623
|
+
if (!content || typeof content !== "string") {
|
|
2624
|
+
return { encoded: content, substitutions: 0, codes: /* @__PURE__ */ new Set(), charsSaved: 0 };
|
|
2625
|
+
}
|
|
2622
2626
|
let encoded = content;
|
|
2623
2627
|
let substitutions = 0;
|
|
2624
2628
|
let charsSaved = 0;
|
|
@@ -2646,7 +2650,7 @@ function encodeMessages(messages) {
|
|
|
2646
2650
|
let totalCharsSaved = 0;
|
|
2647
2651
|
const allUsedCodes = /* @__PURE__ */ new Set();
|
|
2648
2652
|
const result = messages.map((message) => {
|
|
2649
|
-
if (!message.content) return message;
|
|
2653
|
+
if (!message.content || typeof message.content !== "string") return message;
|
|
2650
2654
|
const { encoded, substitutions, codes, charsSaved } = encodeContent(
|
|
2651
2655
|
message.content,
|
|
2652
2656
|
inverseCodebook
|
|
@@ -2672,7 +2676,7 @@ var PATH_REGEX = /(?:\/[\w.-]+){3,}/g;
|
|
|
2672
2676
|
function extractPaths(messages) {
|
|
2673
2677
|
const paths = [];
|
|
2674
2678
|
for (const message of messages) {
|
|
2675
|
-
if (!message.content) continue;
|
|
2679
|
+
if (!message.content || typeof message.content !== "string") continue;
|
|
2676
2680
|
const matches = message.content.match(PATH_REGEX);
|
|
2677
2681
|
if (matches) {
|
|
2678
2682
|
paths.push(...matches);
|
|
@@ -2714,7 +2718,7 @@ function shortenPaths(messages) {
|
|
|
2714
2718
|
});
|
|
2715
2719
|
let charsSaved = 0;
|
|
2716
2720
|
const result = messages.map((message) => {
|
|
2717
|
-
if (!message.content) return message;
|
|
2721
|
+
if (!message.content || typeof message.content !== "string") return message;
|
|
2718
2722
|
let content = message.content;
|
|
2719
2723
|
const originalLength = content.length;
|
|
2720
2724
|
for (const [code, prefix] of Object.entries(pathMap)) {
|
|
@@ -2765,7 +2769,7 @@ function compactMessagesJson(messages) {
|
|
|
2765
2769
|
const newLength = JSON.stringify(newMessage.tool_calls).length;
|
|
2766
2770
|
charsSaved += originalLength - newLength;
|
|
2767
2771
|
}
|
|
2768
|
-
if (message.role === "tool" && message.content && looksLikeJson(message.content)) {
|
|
2772
|
+
if (message.role === "tool" && message.content && typeof message.content === "string" && looksLikeJson(message.content)) {
|
|
2769
2773
|
const originalLength = message.content.length;
|
|
2770
2774
|
const compacted = compactJson(message.content);
|
|
2771
2775
|
charsSaved += originalLength - compacted.length;
|
|
@@ -2830,7 +2834,7 @@ function deduplicateLargeBlocks(messages) {
|
|
|
2830
2834
|
const blockHashes = /* @__PURE__ */ new Map();
|
|
2831
2835
|
let charsSaved = 0;
|
|
2832
2836
|
const result = messages.map((msg, idx) => {
|
|
2833
|
-
if (!msg.content || msg.content.length < 500) {
|
|
2837
|
+
if (!msg.content || typeof msg.content !== "string" || msg.content.length < 500) {
|
|
2834
2838
|
return msg;
|
|
2835
2839
|
}
|
|
2836
2840
|
const blockKey = msg.content.slice(0, 200);
|
|
@@ -2850,7 +2854,7 @@ function compressObservations(messages) {
|
|
|
2850
2854
|
let charsSaved = 0;
|
|
2851
2855
|
let observationsCompressed = 0;
|
|
2852
2856
|
let result = messages.map((msg) => {
|
|
2853
|
-
if (msg.role !== "tool" || !msg.content) {
|
|
2857
|
+
if (msg.role !== "tool" || !msg.content || typeof msg.content !== "string") {
|
|
2854
2858
|
return msg;
|
|
2855
2859
|
}
|
|
2856
2860
|
const original = msg.content;
|
|
@@ -2903,7 +2907,7 @@ function findRepeatedPhrases(allContent) {
|
|
|
2903
2907
|
function buildDynamicCodebook(messages) {
|
|
2904
2908
|
let allContent = "";
|
|
2905
2909
|
for (const msg of messages) {
|
|
2906
|
-
if (msg.content) {
|
|
2910
|
+
if (msg.content && typeof msg.content === "string") {
|
|
2907
2911
|
allContent += msg.content + "\n";
|
|
2908
2912
|
}
|
|
2909
2913
|
}
|
|
@@ -2928,6 +2932,7 @@ function buildDynamicCodebook(messages) {
|
|
|
2928
2932
|
return codebook;
|
|
2929
2933
|
}
|
|
2930
2934
|
function escapeRegex2(str) {
|
|
2935
|
+
if (!str || typeof str !== "string") return "";
|
|
2931
2936
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2932
2937
|
}
|
|
2933
2938
|
function applyDynamicCodebook(messages) {
|
|
@@ -2948,7 +2953,7 @@ function applyDynamicCodebook(messages) {
|
|
|
2948
2953
|
let charsSaved = 0;
|
|
2949
2954
|
let substitutions = 0;
|
|
2950
2955
|
const result = messages.map((msg) => {
|
|
2951
|
-
if (!msg.content) return msg;
|
|
2956
|
+
if (!msg.content || typeof msg.content !== "string") return msg;
|
|
2952
2957
|
let content = msg.content;
|
|
2953
2958
|
for (const phrase of sortedPhrases) {
|
|
2954
2959
|
const code = phraseToCode[phrase];
|
|
@@ -2981,7 +2986,12 @@ function generateDynamicCodebookHeader(codebook) {
|
|
|
2981
2986
|
// src/compression/index.ts
|
|
2982
2987
|
function calculateTotalChars(messages) {
|
|
2983
2988
|
return messages.reduce((total, msg) => {
|
|
2984
|
-
let chars =
|
|
2989
|
+
let chars = 0;
|
|
2990
|
+
if (typeof msg.content === "string") {
|
|
2991
|
+
chars = msg.content.length;
|
|
2992
|
+
} else if (Array.isArray(msg.content)) {
|
|
2993
|
+
chars = JSON.stringify(msg.content).length;
|
|
2994
|
+
}
|
|
2985
2995
|
if (msg.tool_calls) {
|
|
2986
2996
|
chars += JSON.stringify(msg.tool_calls).length;
|
|
2987
2997
|
}
|
|
@@ -3000,12 +3010,14 @@ function prependCodebookHeader(messages, usedCodes, pathMap) {
|
|
|
3000
3010
|
}
|
|
3001
3011
|
return messages.map((msg, i) => {
|
|
3002
3012
|
if (i === userIndex) {
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3013
|
+
if (typeof msg.content === "string") {
|
|
3014
|
+
return {
|
|
3015
|
+
...msg,
|
|
3016
|
+
content: `${header}
|
|
3006
3017
|
|
|
3007
|
-
${msg.content
|
|
3008
|
-
|
|
3018
|
+
${msg.content}`
|
|
3019
|
+
};
|
|
3020
|
+
}
|
|
3009
3021
|
}
|
|
3010
3022
|
return msg;
|
|
3011
3023
|
});
|
|
@@ -3110,11 +3122,11 @@ async function compressContext(messages, config = {}) {
|
|
|
3110
3122
|
const dynHeader = generateDynamicCodebookHeader(dynamicCodes);
|
|
3111
3123
|
if (dynHeader) {
|
|
3112
3124
|
const systemIndex = result.findIndex((m) => m.role === "system");
|
|
3113
|
-
if (systemIndex >= 0) {
|
|
3125
|
+
if (systemIndex >= 0 && typeof result[systemIndex].content === "string") {
|
|
3114
3126
|
result[systemIndex] = {
|
|
3115
3127
|
...result[systemIndex],
|
|
3116
3128
|
content: `${dynHeader}
|
|
3117
|
-
${result[systemIndex].content
|
|
3129
|
+
${result[systemIndex].content}`
|
|
3118
3130
|
};
|
|
3119
3131
|
}
|
|
3120
3132
|
}
|
|
@@ -3322,6 +3334,174 @@ var PROXY_PORT = (() => {
|
|
|
3322
3334
|
return DEFAULT_PORT;
|
|
3323
3335
|
})();
|
|
3324
3336
|
|
|
3337
|
+
// src/journal.ts
|
|
3338
|
+
var DEFAULT_CONFIG2 = {
|
|
3339
|
+
maxEntries: 100,
|
|
3340
|
+
maxAgeMs: 24 * 60 * 60 * 1e3,
|
|
3341
|
+
// 24 hours
|
|
3342
|
+
maxEventsPerResponse: 5
|
|
3343
|
+
};
|
|
3344
|
+
var SessionJournal = class {
|
|
3345
|
+
journals = /* @__PURE__ */ new Map();
|
|
3346
|
+
config;
|
|
3347
|
+
constructor(config) {
|
|
3348
|
+
this.config = { ...DEFAULT_CONFIG2, ...config };
|
|
3349
|
+
}
|
|
3350
|
+
/**
|
|
3351
|
+
* Extract key events from assistant response content.
|
|
3352
|
+
* Looks for patterns like "I created...", "I fixed...", "Successfully..."
|
|
3353
|
+
*/
|
|
3354
|
+
extractEvents(content) {
|
|
3355
|
+
if (!content || typeof content !== "string") {
|
|
3356
|
+
return [];
|
|
3357
|
+
}
|
|
3358
|
+
const events = [];
|
|
3359
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3360
|
+
const patterns = [
|
|
3361
|
+
// Creation patterns
|
|
3362
|
+
/I (?:also |then |have |)?(?:created|implemented|added|wrote|built|generated|set up|initialized) ([^.!?\n]{10,150})/gi,
|
|
3363
|
+
// Fix patterns
|
|
3364
|
+
/I (?:also |then |have |)?(?:fixed|resolved|solved|patched|corrected|addressed|debugged) ([^.!?\n]{10,150})/gi,
|
|
3365
|
+
// Completion patterns
|
|
3366
|
+
/I (?:also |then |have |)?(?:completed|finished|done with|wrapped up) ([^.!?\n]{10,150})/gi,
|
|
3367
|
+
// Update patterns
|
|
3368
|
+
/I (?:also |then |have |)?(?:updated|modified|changed|refactored|improved|enhanced|optimized) ([^.!?\n]{10,150})/gi,
|
|
3369
|
+
// Success patterns
|
|
3370
|
+
/Successfully ([^.!?\n]{10,150})/gi,
|
|
3371
|
+
// Tool usage patterns (when agent uses tools)
|
|
3372
|
+
/I (?:also |then |have |)?(?:ran|executed|called|invoked) ([^.!?\n]{10,100})/gi
|
|
3373
|
+
];
|
|
3374
|
+
for (const pattern of patterns) {
|
|
3375
|
+
pattern.lastIndex = 0;
|
|
3376
|
+
let match;
|
|
3377
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
3378
|
+
const action = match[0].trim();
|
|
3379
|
+
const normalized = action.toLowerCase();
|
|
3380
|
+
if (seen.has(normalized)) {
|
|
3381
|
+
continue;
|
|
3382
|
+
}
|
|
3383
|
+
if (action.length >= 15 && action.length <= 200) {
|
|
3384
|
+
events.push(action);
|
|
3385
|
+
seen.add(normalized);
|
|
3386
|
+
}
|
|
3387
|
+
if (events.length >= this.config.maxEventsPerResponse) {
|
|
3388
|
+
break;
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
if (events.length >= this.config.maxEventsPerResponse) {
|
|
3392
|
+
break;
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
return events;
|
|
3396
|
+
}
|
|
3397
|
+
/**
|
|
3398
|
+
* Record events to the session journal.
|
|
3399
|
+
*/
|
|
3400
|
+
record(sessionId, events, model) {
|
|
3401
|
+
if (!sessionId || !events.length) {
|
|
3402
|
+
return;
|
|
3403
|
+
}
|
|
3404
|
+
const journal = this.journals.get(sessionId) || [];
|
|
3405
|
+
const now = Date.now();
|
|
3406
|
+
for (const action of events) {
|
|
3407
|
+
journal.push({
|
|
3408
|
+
timestamp: now,
|
|
3409
|
+
action,
|
|
3410
|
+
model
|
|
3411
|
+
});
|
|
3412
|
+
}
|
|
3413
|
+
const cutoff = now - this.config.maxAgeMs;
|
|
3414
|
+
const trimmed = journal.filter((e) => e.timestamp > cutoff).slice(-this.config.maxEntries);
|
|
3415
|
+
this.journals.set(sessionId, trimmed);
|
|
3416
|
+
}
|
|
3417
|
+
/**
|
|
3418
|
+
* Check if the user message indicates a need for historical context.
|
|
3419
|
+
*/
|
|
3420
|
+
needsContext(lastUserMessage) {
|
|
3421
|
+
if (!lastUserMessage || typeof lastUserMessage !== "string") {
|
|
3422
|
+
return false;
|
|
3423
|
+
}
|
|
3424
|
+
const lower = lastUserMessage.toLowerCase();
|
|
3425
|
+
const triggers = [
|
|
3426
|
+
// Direct questions about past work
|
|
3427
|
+
"what did you do",
|
|
3428
|
+
"what have you done",
|
|
3429
|
+
"what did we do",
|
|
3430
|
+
"what have we done",
|
|
3431
|
+
// Temporal references
|
|
3432
|
+
"earlier",
|
|
3433
|
+
"before",
|
|
3434
|
+
"previously",
|
|
3435
|
+
"this session",
|
|
3436
|
+
"today",
|
|
3437
|
+
"so far",
|
|
3438
|
+
// Summary requests
|
|
3439
|
+
"remind me",
|
|
3440
|
+
"summarize",
|
|
3441
|
+
"summary of",
|
|
3442
|
+
"recap",
|
|
3443
|
+
// Progress inquiries
|
|
3444
|
+
"your work",
|
|
3445
|
+
"your progress",
|
|
3446
|
+
"accomplished",
|
|
3447
|
+
"achievements",
|
|
3448
|
+
"completed tasks"
|
|
3449
|
+
];
|
|
3450
|
+
return triggers.some((t) => lower.includes(t));
|
|
3451
|
+
}
|
|
3452
|
+
/**
|
|
3453
|
+
* Format the journal for injection into system message.
|
|
3454
|
+
* Returns null if journal is empty.
|
|
3455
|
+
*/
|
|
3456
|
+
format(sessionId) {
|
|
3457
|
+
const journal = this.journals.get(sessionId);
|
|
3458
|
+
if (!journal?.length) {
|
|
3459
|
+
return null;
|
|
3460
|
+
}
|
|
3461
|
+
const lines = journal.map((e) => {
|
|
3462
|
+
const time = new Date(e.timestamp).toLocaleTimeString("en-US", {
|
|
3463
|
+
hour: "2-digit",
|
|
3464
|
+
minute: "2-digit",
|
|
3465
|
+
hour12: true
|
|
3466
|
+
});
|
|
3467
|
+
return `- ${time}: ${e.action}`;
|
|
3468
|
+
});
|
|
3469
|
+
return `[Session Memory - Key Actions]
|
|
3470
|
+
${lines.join("\n")}`;
|
|
3471
|
+
}
|
|
3472
|
+
/**
|
|
3473
|
+
* Get the raw journal entries for a session (for debugging/testing).
|
|
3474
|
+
*/
|
|
3475
|
+
getEntries(sessionId) {
|
|
3476
|
+
return this.journals.get(sessionId) || [];
|
|
3477
|
+
}
|
|
3478
|
+
/**
|
|
3479
|
+
* Clear journal for a specific session.
|
|
3480
|
+
*/
|
|
3481
|
+
clear(sessionId) {
|
|
3482
|
+
this.journals.delete(sessionId);
|
|
3483
|
+
}
|
|
3484
|
+
/**
|
|
3485
|
+
* Clear all journals.
|
|
3486
|
+
*/
|
|
3487
|
+
clearAll() {
|
|
3488
|
+
this.journals.clear();
|
|
3489
|
+
}
|
|
3490
|
+
/**
|
|
3491
|
+
* Get stats about the journal.
|
|
3492
|
+
*/
|
|
3493
|
+
getStats() {
|
|
3494
|
+
let totalEntries = 0;
|
|
3495
|
+
for (const entries of this.journals.values()) {
|
|
3496
|
+
totalEntries += entries.length;
|
|
3497
|
+
}
|
|
3498
|
+
return {
|
|
3499
|
+
sessions: this.journals.size,
|
|
3500
|
+
totalEntries
|
|
3501
|
+
};
|
|
3502
|
+
}
|
|
3503
|
+
};
|
|
3504
|
+
|
|
3325
3505
|
// src/proxy.ts
|
|
3326
3506
|
var BLOCKRUN_API = "https://blockrun.ai/api";
|
|
3327
3507
|
var AUTO_MODEL = "blockrun/auto";
|
|
@@ -3731,6 +3911,7 @@ async function startProxy(options) {
|
|
|
3731
3911
|
const deduplicator = new RequestDeduplicator();
|
|
3732
3912
|
const responseCache = new ResponseCache(options.cacheConfig);
|
|
3733
3913
|
const sessionStore = new SessionStore(options.sessionConfig);
|
|
3914
|
+
const sessionJournal = new SessionJournal();
|
|
3734
3915
|
const connections = /* @__PURE__ */ new Set();
|
|
3735
3916
|
const server = createServer(async (req, res) => {
|
|
3736
3917
|
req.on("error", (err) => {
|
|
@@ -3826,7 +4007,8 @@ async function startProxy(options) {
|
|
|
3826
4007
|
deduplicator,
|
|
3827
4008
|
balanceMonitor,
|
|
3828
4009
|
sessionStore,
|
|
3829
|
-
responseCache
|
|
4010
|
+
responseCache,
|
|
4011
|
+
sessionJournal
|
|
3830
4012
|
);
|
|
3831
4013
|
} catch (err) {
|
|
3832
4014
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -4027,7 +4209,7 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
4027
4209
|
};
|
|
4028
4210
|
}
|
|
4029
4211
|
}
|
|
4030
|
-
async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, deduplicator, balanceMonitor, sessionStore, responseCache) {
|
|
4212
|
+
async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, deduplicator, balanceMonitor, sessionStore, responseCache, sessionJournal) {
|
|
4031
4213
|
const startTime = Date.now();
|
|
4032
4214
|
const upstreamUrl = `${apiBase}${req.url}`;
|
|
4033
4215
|
const bodyChunks = [];
|
|
@@ -4040,13 +4222,38 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
4040
4222
|
let modelId = "";
|
|
4041
4223
|
let maxTokens = 4096;
|
|
4042
4224
|
let routingProfile = null;
|
|
4225
|
+
let accumulatedContent = "";
|
|
4043
4226
|
const isChatCompletion = req.url?.includes("/chat/completions");
|
|
4227
|
+
const sessionId = getSessionId(req.headers);
|
|
4044
4228
|
if (isChatCompletion && body.length > 0) {
|
|
4045
4229
|
try {
|
|
4046
4230
|
const parsed = JSON.parse(body.toString());
|
|
4047
4231
|
isStreaming = parsed.stream === true;
|
|
4048
4232
|
modelId = parsed.model || "";
|
|
4049
4233
|
maxTokens = parsed.max_tokens || 4096;
|
|
4234
|
+
if (sessionId && Array.isArray(parsed.messages)) {
|
|
4235
|
+
const messages = parsed.messages;
|
|
4236
|
+
const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
|
|
4237
|
+
const lastContent = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
|
|
4238
|
+
if (sessionJournal.needsContext(lastContent)) {
|
|
4239
|
+
const journalText = sessionJournal.format(sessionId);
|
|
4240
|
+
if (journalText) {
|
|
4241
|
+
const sysIdx = messages.findIndex((m) => m.role === "system");
|
|
4242
|
+
if (sysIdx >= 0 && typeof messages[sysIdx].content === "string") {
|
|
4243
|
+
messages[sysIdx] = {
|
|
4244
|
+
...messages[sysIdx],
|
|
4245
|
+
content: journalText + "\n\n" + messages[sysIdx].content
|
|
4246
|
+
};
|
|
4247
|
+
} else {
|
|
4248
|
+
messages.unshift({ role: "system", content: journalText });
|
|
4249
|
+
}
|
|
4250
|
+
parsed.messages = messages;
|
|
4251
|
+
console.log(
|
|
4252
|
+
`[ClawRouter] Injected session journal (${journalText.length} chars) for session ${sessionId.slice(0, 8)}...`
|
|
4253
|
+
);
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4050
4257
|
let bodyModified = false;
|
|
4051
4258
|
if (parsed.stream === true) {
|
|
4052
4259
|
parsed.stream = false;
|
|
@@ -4086,18 +4293,18 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
4086
4293
|
latencyMs: 0
|
|
4087
4294
|
});
|
|
4088
4295
|
} else {
|
|
4089
|
-
const
|
|
4296
|
+
const sessionId2 = getSessionId(
|
|
4090
4297
|
req.headers
|
|
4091
4298
|
);
|
|
4092
|
-
const existingSession =
|
|
4299
|
+
const existingSession = sessionId2 ? sessionStore.getSession(sessionId2) : void 0;
|
|
4093
4300
|
if (existingSession) {
|
|
4094
4301
|
console.log(
|
|
4095
|
-
`[ClawRouter] Session ${
|
|
4302
|
+
`[ClawRouter] Session ${sessionId2?.slice(0, 8)}... using pinned model: ${existingSession.model}`
|
|
4096
4303
|
);
|
|
4097
4304
|
parsed.model = existingSession.model;
|
|
4098
4305
|
modelId = existingSession.model;
|
|
4099
4306
|
bodyModified = true;
|
|
4100
|
-
sessionStore.touchSession(
|
|
4307
|
+
sessionStore.touchSession(sessionId2);
|
|
4101
4308
|
} else {
|
|
4102
4309
|
const messages = parsed.messages;
|
|
4103
4310
|
let lastUserMsg;
|
|
@@ -4126,10 +4333,10 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
4126
4333
|
parsed.model = routingDecision.model;
|
|
4127
4334
|
modelId = routingDecision.model;
|
|
4128
4335
|
bodyModified = true;
|
|
4129
|
-
if (
|
|
4130
|
-
sessionStore.setSession(
|
|
4336
|
+
if (sessionId2) {
|
|
4337
|
+
sessionStore.setSession(sessionId2, routingDecision.model, routingDecision.tier);
|
|
4131
4338
|
console.log(
|
|
4132
|
-
`[ClawRouter] Session ${
|
|
4339
|
+
`[ClawRouter] Session ${sessionId2.slice(0, 8)}... pinned to model: ${routingDecision.model}`
|
|
4133
4340
|
);
|
|
4134
4341
|
}
|
|
4135
4342
|
options.onRouted?.(routingDecision);
|
|
@@ -4464,6 +4671,9 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
4464
4671
|
const content = stripThinkingTokens(rawContent);
|
|
4465
4672
|
const role = choice.message?.role ?? choice.delta?.role ?? "assistant";
|
|
4466
4673
|
const index = choice.index ?? 0;
|
|
4674
|
+
if (content) {
|
|
4675
|
+
accumulatedContent += content;
|
|
4676
|
+
}
|
|
4467
4677
|
const roleChunk = {
|
|
4468
4678
|
...baseChunk,
|
|
4469
4679
|
choices: [{ index, delta: { role }, logprobs: null, finish_reason: null }]
|
|
@@ -4577,6 +4787,22 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
4577
4787
|
});
|
|
4578
4788
|
console.log(`[ClawRouter] Cached response for ${modelId} (${responseBody.length} bytes)`);
|
|
4579
4789
|
}
|
|
4790
|
+
try {
|
|
4791
|
+
const rspJson = JSON.parse(responseBody.toString());
|
|
4792
|
+
if (rspJson.choices?.[0]?.message?.content) {
|
|
4793
|
+
accumulatedContent = rspJson.choices[0].message.content;
|
|
4794
|
+
}
|
|
4795
|
+
} catch {
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
if (sessionId && accumulatedContent) {
|
|
4799
|
+
const events = sessionJournal.extractEvents(accumulatedContent);
|
|
4800
|
+
if (events.length > 0) {
|
|
4801
|
+
sessionJournal.record(sessionId, events, actualModelUsed);
|
|
4802
|
+
console.log(
|
|
4803
|
+
`[ClawRouter] Recorded ${events.length} events to session journal for session ${sessionId.slice(0, 8)}...`
|
|
4804
|
+
);
|
|
4805
|
+
}
|
|
4580
4806
|
}
|
|
4581
4807
|
if (estimatedCostMicros !== void 0) {
|
|
4582
4808
|
balanceMonitor.deductEstimated(estimatedCostMicros);
|