@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/index.js
CHANGED
|
@@ -2581,12 +2581,13 @@ var DEFAULT_COMPRESSION_CONFIG = {
|
|
|
2581
2581
|
// src/compression/layers/deduplication.ts
|
|
2582
2582
|
import crypto2 from "crypto";
|
|
2583
2583
|
function hashMessage(message) {
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
message.content
|
|
2587
|
-
|
|
2588
|
-
message.
|
|
2589
|
-
|
|
2584
|
+
let contentStr = "";
|
|
2585
|
+
if (typeof message.content === "string") {
|
|
2586
|
+
contentStr = message.content;
|
|
2587
|
+
} else if (Array.isArray(message.content)) {
|
|
2588
|
+
contentStr = JSON.stringify(message.content);
|
|
2589
|
+
}
|
|
2590
|
+
const parts = [message.role, contentStr, message.tool_call_id || "", message.name || ""];
|
|
2590
2591
|
if (message.tool_calls) {
|
|
2591
2592
|
parts.push(
|
|
2592
2593
|
JSON.stringify(
|
|
@@ -2649,13 +2650,13 @@ function deduplicateMessages(messages) {
|
|
|
2649
2650
|
|
|
2650
2651
|
// src/compression/layers/whitespace.ts
|
|
2651
2652
|
function normalizeWhitespace(content) {
|
|
2652
|
-
if (!content) return content;
|
|
2653
|
+
if (!content || typeof content !== "string") return content;
|
|
2653
2654
|
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();
|
|
2654
2655
|
}
|
|
2655
2656
|
function normalizeMessagesWhitespace(messages) {
|
|
2656
2657
|
let charsSaved = 0;
|
|
2657
2658
|
const result = messages.map((message) => {
|
|
2658
|
-
if (!message.content) return message;
|
|
2659
|
+
if (!message.content || typeof message.content !== "string") return message;
|
|
2659
2660
|
const originalLength = message.content.length;
|
|
2660
2661
|
const normalizedContent = normalizeWhitespace(message.content);
|
|
2661
2662
|
charsSaved += originalLength - normalizedContent.length;
|
|
@@ -2759,6 +2760,9 @@ function generateCodebookHeader(usedCodes, pathMap = {}) {
|
|
|
2759
2760
|
|
|
2760
2761
|
// src/compression/layers/dictionary.ts
|
|
2761
2762
|
function encodeContent(content, inverseCodebook) {
|
|
2763
|
+
if (!content || typeof content !== "string") {
|
|
2764
|
+
return { encoded: content, substitutions: 0, codes: /* @__PURE__ */ new Set(), charsSaved: 0 };
|
|
2765
|
+
}
|
|
2762
2766
|
let encoded = content;
|
|
2763
2767
|
let substitutions = 0;
|
|
2764
2768
|
let charsSaved = 0;
|
|
@@ -2786,7 +2790,7 @@ function encodeMessages(messages) {
|
|
|
2786
2790
|
let totalCharsSaved = 0;
|
|
2787
2791
|
const allUsedCodes = /* @__PURE__ */ new Set();
|
|
2788
2792
|
const result = messages.map((message) => {
|
|
2789
|
-
if (!message.content) return message;
|
|
2793
|
+
if (!message.content || typeof message.content !== "string") return message;
|
|
2790
2794
|
const { encoded, substitutions, codes, charsSaved } = encodeContent(
|
|
2791
2795
|
message.content,
|
|
2792
2796
|
inverseCodebook
|
|
@@ -2812,7 +2816,7 @@ var PATH_REGEX = /(?:\/[\w.-]+){3,}/g;
|
|
|
2812
2816
|
function extractPaths(messages) {
|
|
2813
2817
|
const paths = [];
|
|
2814
2818
|
for (const message of messages) {
|
|
2815
|
-
if (!message.content) continue;
|
|
2819
|
+
if (!message.content || typeof message.content !== "string") continue;
|
|
2816
2820
|
const matches = message.content.match(PATH_REGEX);
|
|
2817
2821
|
if (matches) {
|
|
2818
2822
|
paths.push(...matches);
|
|
@@ -2854,7 +2858,7 @@ function shortenPaths(messages) {
|
|
|
2854
2858
|
});
|
|
2855
2859
|
let charsSaved = 0;
|
|
2856
2860
|
const result = messages.map((message) => {
|
|
2857
|
-
if (!message.content) return message;
|
|
2861
|
+
if (!message.content || typeof message.content !== "string") return message;
|
|
2858
2862
|
let content = message.content;
|
|
2859
2863
|
const originalLength = content.length;
|
|
2860
2864
|
for (const [code, prefix] of Object.entries(pathMap)) {
|
|
@@ -2905,7 +2909,7 @@ function compactMessagesJson(messages) {
|
|
|
2905
2909
|
const newLength = JSON.stringify(newMessage.tool_calls).length;
|
|
2906
2910
|
charsSaved += originalLength - newLength;
|
|
2907
2911
|
}
|
|
2908
|
-
if (message.role === "tool" && message.content && looksLikeJson(message.content)) {
|
|
2912
|
+
if (message.role === "tool" && message.content && typeof message.content === "string" && looksLikeJson(message.content)) {
|
|
2909
2913
|
const originalLength = message.content.length;
|
|
2910
2914
|
const compacted = compactJson(message.content);
|
|
2911
2915
|
charsSaved += originalLength - compacted.length;
|
|
@@ -2970,7 +2974,7 @@ function deduplicateLargeBlocks(messages) {
|
|
|
2970
2974
|
const blockHashes = /* @__PURE__ */ new Map();
|
|
2971
2975
|
let charsSaved = 0;
|
|
2972
2976
|
const result = messages.map((msg, idx) => {
|
|
2973
|
-
if (!msg.content || msg.content.length < 500) {
|
|
2977
|
+
if (!msg.content || typeof msg.content !== "string" || msg.content.length < 500) {
|
|
2974
2978
|
return msg;
|
|
2975
2979
|
}
|
|
2976
2980
|
const blockKey = msg.content.slice(0, 200);
|
|
@@ -2990,7 +2994,7 @@ function compressObservations(messages) {
|
|
|
2990
2994
|
let charsSaved = 0;
|
|
2991
2995
|
let observationsCompressed = 0;
|
|
2992
2996
|
let result = messages.map((msg) => {
|
|
2993
|
-
if (msg.role !== "tool" || !msg.content) {
|
|
2997
|
+
if (msg.role !== "tool" || !msg.content || typeof msg.content !== "string") {
|
|
2994
2998
|
return msg;
|
|
2995
2999
|
}
|
|
2996
3000
|
const original = msg.content;
|
|
@@ -3043,7 +3047,7 @@ function findRepeatedPhrases(allContent) {
|
|
|
3043
3047
|
function buildDynamicCodebook(messages) {
|
|
3044
3048
|
let allContent = "";
|
|
3045
3049
|
for (const msg of messages) {
|
|
3046
|
-
if (msg.content) {
|
|
3050
|
+
if (msg.content && typeof msg.content === "string") {
|
|
3047
3051
|
allContent += msg.content + "\n";
|
|
3048
3052
|
}
|
|
3049
3053
|
}
|
|
@@ -3068,6 +3072,7 @@ function buildDynamicCodebook(messages) {
|
|
|
3068
3072
|
return codebook;
|
|
3069
3073
|
}
|
|
3070
3074
|
function escapeRegex2(str) {
|
|
3075
|
+
if (!str || typeof str !== "string") return "";
|
|
3071
3076
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3072
3077
|
}
|
|
3073
3078
|
function applyDynamicCodebook(messages) {
|
|
@@ -3088,7 +3093,7 @@ function applyDynamicCodebook(messages) {
|
|
|
3088
3093
|
let charsSaved = 0;
|
|
3089
3094
|
let substitutions = 0;
|
|
3090
3095
|
const result = messages.map((msg) => {
|
|
3091
|
-
if (!msg.content) return msg;
|
|
3096
|
+
if (!msg.content || typeof msg.content !== "string") return msg;
|
|
3092
3097
|
let content = msg.content;
|
|
3093
3098
|
for (const phrase of sortedPhrases) {
|
|
3094
3099
|
const code = phraseToCode[phrase];
|
|
@@ -3121,7 +3126,12 @@ function generateDynamicCodebookHeader(codebook) {
|
|
|
3121
3126
|
// src/compression/index.ts
|
|
3122
3127
|
function calculateTotalChars(messages) {
|
|
3123
3128
|
return messages.reduce((total, msg) => {
|
|
3124
|
-
let chars =
|
|
3129
|
+
let chars = 0;
|
|
3130
|
+
if (typeof msg.content === "string") {
|
|
3131
|
+
chars = msg.content.length;
|
|
3132
|
+
} else if (Array.isArray(msg.content)) {
|
|
3133
|
+
chars = JSON.stringify(msg.content).length;
|
|
3134
|
+
}
|
|
3125
3135
|
if (msg.tool_calls) {
|
|
3126
3136
|
chars += JSON.stringify(msg.tool_calls).length;
|
|
3127
3137
|
}
|
|
@@ -3140,12 +3150,14 @@ function prependCodebookHeader(messages, usedCodes, pathMap) {
|
|
|
3140
3150
|
}
|
|
3141
3151
|
return messages.map((msg, i) => {
|
|
3142
3152
|
if (i === userIndex) {
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3153
|
+
if (typeof msg.content === "string") {
|
|
3154
|
+
return {
|
|
3155
|
+
...msg,
|
|
3156
|
+
content: `${header}
|
|
3146
3157
|
|
|
3147
|
-
${msg.content
|
|
3148
|
-
|
|
3158
|
+
${msg.content}`
|
|
3159
|
+
};
|
|
3160
|
+
}
|
|
3149
3161
|
}
|
|
3150
3162
|
return msg;
|
|
3151
3163
|
});
|
|
@@ -3250,11 +3262,11 @@ async function compressContext(messages, config = {}) {
|
|
|
3250
3262
|
const dynHeader = generateDynamicCodebookHeader(dynamicCodes);
|
|
3251
3263
|
if (dynHeader) {
|
|
3252
3264
|
const systemIndex = result.findIndex((m) => m.role === "system");
|
|
3253
|
-
if (systemIndex >= 0) {
|
|
3265
|
+
if (systemIndex >= 0 && typeof result[systemIndex].content === "string") {
|
|
3254
3266
|
result[systemIndex] = {
|
|
3255
3267
|
...result[systemIndex],
|
|
3256
3268
|
content: `${dynHeader}
|
|
3257
|
-
${result[systemIndex].content
|
|
3269
|
+
${result[systemIndex].content}`
|
|
3258
3270
|
};
|
|
3259
3271
|
}
|
|
3260
3272
|
}
|
|
@@ -3462,6 +3474,174 @@ var PROXY_PORT = (() => {
|
|
|
3462
3474
|
return DEFAULT_PORT;
|
|
3463
3475
|
})();
|
|
3464
3476
|
|
|
3477
|
+
// src/journal.ts
|
|
3478
|
+
var DEFAULT_CONFIG2 = {
|
|
3479
|
+
maxEntries: 100,
|
|
3480
|
+
maxAgeMs: 24 * 60 * 60 * 1e3,
|
|
3481
|
+
// 24 hours
|
|
3482
|
+
maxEventsPerResponse: 5
|
|
3483
|
+
};
|
|
3484
|
+
var SessionJournal = class {
|
|
3485
|
+
journals = /* @__PURE__ */ new Map();
|
|
3486
|
+
config;
|
|
3487
|
+
constructor(config) {
|
|
3488
|
+
this.config = { ...DEFAULT_CONFIG2, ...config };
|
|
3489
|
+
}
|
|
3490
|
+
/**
|
|
3491
|
+
* Extract key events from assistant response content.
|
|
3492
|
+
* Looks for patterns like "I created...", "I fixed...", "Successfully..."
|
|
3493
|
+
*/
|
|
3494
|
+
extractEvents(content) {
|
|
3495
|
+
if (!content || typeof content !== "string") {
|
|
3496
|
+
return [];
|
|
3497
|
+
}
|
|
3498
|
+
const events = [];
|
|
3499
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3500
|
+
const patterns = [
|
|
3501
|
+
// Creation patterns
|
|
3502
|
+
/I (?:also |then |have |)?(?:created|implemented|added|wrote|built|generated|set up|initialized) ([^.!?\n]{10,150})/gi,
|
|
3503
|
+
// Fix patterns
|
|
3504
|
+
/I (?:also |then |have |)?(?:fixed|resolved|solved|patched|corrected|addressed|debugged) ([^.!?\n]{10,150})/gi,
|
|
3505
|
+
// Completion patterns
|
|
3506
|
+
/I (?:also |then |have |)?(?:completed|finished|done with|wrapped up) ([^.!?\n]{10,150})/gi,
|
|
3507
|
+
// Update patterns
|
|
3508
|
+
/I (?:also |then |have |)?(?:updated|modified|changed|refactored|improved|enhanced|optimized) ([^.!?\n]{10,150})/gi,
|
|
3509
|
+
// Success patterns
|
|
3510
|
+
/Successfully ([^.!?\n]{10,150})/gi,
|
|
3511
|
+
// Tool usage patterns (when agent uses tools)
|
|
3512
|
+
/I (?:also |then |have |)?(?:ran|executed|called|invoked) ([^.!?\n]{10,100})/gi
|
|
3513
|
+
];
|
|
3514
|
+
for (const pattern of patterns) {
|
|
3515
|
+
pattern.lastIndex = 0;
|
|
3516
|
+
let match;
|
|
3517
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
3518
|
+
const action = match[0].trim();
|
|
3519
|
+
const normalized = action.toLowerCase();
|
|
3520
|
+
if (seen.has(normalized)) {
|
|
3521
|
+
continue;
|
|
3522
|
+
}
|
|
3523
|
+
if (action.length >= 15 && action.length <= 200) {
|
|
3524
|
+
events.push(action);
|
|
3525
|
+
seen.add(normalized);
|
|
3526
|
+
}
|
|
3527
|
+
if (events.length >= this.config.maxEventsPerResponse) {
|
|
3528
|
+
break;
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
if (events.length >= this.config.maxEventsPerResponse) {
|
|
3532
|
+
break;
|
|
3533
|
+
}
|
|
3534
|
+
}
|
|
3535
|
+
return events;
|
|
3536
|
+
}
|
|
3537
|
+
/**
|
|
3538
|
+
* Record events to the session journal.
|
|
3539
|
+
*/
|
|
3540
|
+
record(sessionId, events, model) {
|
|
3541
|
+
if (!sessionId || !events.length) {
|
|
3542
|
+
return;
|
|
3543
|
+
}
|
|
3544
|
+
const journal = this.journals.get(sessionId) || [];
|
|
3545
|
+
const now = Date.now();
|
|
3546
|
+
for (const action of events) {
|
|
3547
|
+
journal.push({
|
|
3548
|
+
timestamp: now,
|
|
3549
|
+
action,
|
|
3550
|
+
model
|
|
3551
|
+
});
|
|
3552
|
+
}
|
|
3553
|
+
const cutoff = now - this.config.maxAgeMs;
|
|
3554
|
+
const trimmed = journal.filter((e) => e.timestamp > cutoff).slice(-this.config.maxEntries);
|
|
3555
|
+
this.journals.set(sessionId, trimmed);
|
|
3556
|
+
}
|
|
3557
|
+
/**
|
|
3558
|
+
* Check if the user message indicates a need for historical context.
|
|
3559
|
+
*/
|
|
3560
|
+
needsContext(lastUserMessage) {
|
|
3561
|
+
if (!lastUserMessage || typeof lastUserMessage !== "string") {
|
|
3562
|
+
return false;
|
|
3563
|
+
}
|
|
3564
|
+
const lower = lastUserMessage.toLowerCase();
|
|
3565
|
+
const triggers = [
|
|
3566
|
+
// Direct questions about past work
|
|
3567
|
+
"what did you do",
|
|
3568
|
+
"what have you done",
|
|
3569
|
+
"what did we do",
|
|
3570
|
+
"what have we done",
|
|
3571
|
+
// Temporal references
|
|
3572
|
+
"earlier",
|
|
3573
|
+
"before",
|
|
3574
|
+
"previously",
|
|
3575
|
+
"this session",
|
|
3576
|
+
"today",
|
|
3577
|
+
"so far",
|
|
3578
|
+
// Summary requests
|
|
3579
|
+
"remind me",
|
|
3580
|
+
"summarize",
|
|
3581
|
+
"summary of",
|
|
3582
|
+
"recap",
|
|
3583
|
+
// Progress inquiries
|
|
3584
|
+
"your work",
|
|
3585
|
+
"your progress",
|
|
3586
|
+
"accomplished",
|
|
3587
|
+
"achievements",
|
|
3588
|
+
"completed tasks"
|
|
3589
|
+
];
|
|
3590
|
+
return triggers.some((t) => lower.includes(t));
|
|
3591
|
+
}
|
|
3592
|
+
/**
|
|
3593
|
+
* Format the journal for injection into system message.
|
|
3594
|
+
* Returns null if journal is empty.
|
|
3595
|
+
*/
|
|
3596
|
+
format(sessionId) {
|
|
3597
|
+
const journal = this.journals.get(sessionId);
|
|
3598
|
+
if (!journal?.length) {
|
|
3599
|
+
return null;
|
|
3600
|
+
}
|
|
3601
|
+
const lines = journal.map((e) => {
|
|
3602
|
+
const time = new Date(e.timestamp).toLocaleTimeString("en-US", {
|
|
3603
|
+
hour: "2-digit",
|
|
3604
|
+
minute: "2-digit",
|
|
3605
|
+
hour12: true
|
|
3606
|
+
});
|
|
3607
|
+
return `- ${time}: ${e.action}`;
|
|
3608
|
+
});
|
|
3609
|
+
return `[Session Memory - Key Actions]
|
|
3610
|
+
${lines.join("\n")}`;
|
|
3611
|
+
}
|
|
3612
|
+
/**
|
|
3613
|
+
* Get the raw journal entries for a session (for debugging/testing).
|
|
3614
|
+
*/
|
|
3615
|
+
getEntries(sessionId) {
|
|
3616
|
+
return this.journals.get(sessionId) || [];
|
|
3617
|
+
}
|
|
3618
|
+
/**
|
|
3619
|
+
* Clear journal for a specific session.
|
|
3620
|
+
*/
|
|
3621
|
+
clear(sessionId) {
|
|
3622
|
+
this.journals.delete(sessionId);
|
|
3623
|
+
}
|
|
3624
|
+
/**
|
|
3625
|
+
* Clear all journals.
|
|
3626
|
+
*/
|
|
3627
|
+
clearAll() {
|
|
3628
|
+
this.journals.clear();
|
|
3629
|
+
}
|
|
3630
|
+
/**
|
|
3631
|
+
* Get stats about the journal.
|
|
3632
|
+
*/
|
|
3633
|
+
getStats() {
|
|
3634
|
+
let totalEntries = 0;
|
|
3635
|
+
for (const entries of this.journals.values()) {
|
|
3636
|
+
totalEntries += entries.length;
|
|
3637
|
+
}
|
|
3638
|
+
return {
|
|
3639
|
+
sessions: this.journals.size,
|
|
3640
|
+
totalEntries
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3643
|
+
};
|
|
3644
|
+
|
|
3465
3645
|
// src/proxy.ts
|
|
3466
3646
|
var BLOCKRUN_API = "https://blockrun.ai/api";
|
|
3467
3647
|
var AUTO_MODEL = "blockrun/auto";
|
|
@@ -3871,6 +4051,7 @@ async function startProxy(options) {
|
|
|
3871
4051
|
const deduplicator = new RequestDeduplicator();
|
|
3872
4052
|
const responseCache = new ResponseCache(options.cacheConfig);
|
|
3873
4053
|
const sessionStore = new SessionStore(options.sessionConfig);
|
|
4054
|
+
const sessionJournal = new SessionJournal();
|
|
3874
4055
|
const connections = /* @__PURE__ */ new Set();
|
|
3875
4056
|
const server = createServer(async (req, res) => {
|
|
3876
4057
|
req.on("error", (err) => {
|
|
@@ -3966,7 +4147,8 @@ async function startProxy(options) {
|
|
|
3966
4147
|
deduplicator,
|
|
3967
4148
|
balanceMonitor,
|
|
3968
4149
|
sessionStore,
|
|
3969
|
-
responseCache
|
|
4150
|
+
responseCache,
|
|
4151
|
+
sessionJournal
|
|
3970
4152
|
);
|
|
3971
4153
|
} catch (err) {
|
|
3972
4154
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -4167,7 +4349,7 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
4167
4349
|
};
|
|
4168
4350
|
}
|
|
4169
4351
|
}
|
|
4170
|
-
async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, deduplicator, balanceMonitor, sessionStore, responseCache) {
|
|
4352
|
+
async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, deduplicator, balanceMonitor, sessionStore, responseCache, sessionJournal) {
|
|
4171
4353
|
const startTime = Date.now();
|
|
4172
4354
|
const upstreamUrl = `${apiBase}${req.url}`;
|
|
4173
4355
|
const bodyChunks = [];
|
|
@@ -4180,13 +4362,38 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
4180
4362
|
let modelId = "";
|
|
4181
4363
|
let maxTokens = 4096;
|
|
4182
4364
|
let routingProfile = null;
|
|
4365
|
+
let accumulatedContent = "";
|
|
4183
4366
|
const isChatCompletion = req.url?.includes("/chat/completions");
|
|
4367
|
+
const sessionId = getSessionId(req.headers);
|
|
4184
4368
|
if (isChatCompletion && body.length > 0) {
|
|
4185
4369
|
try {
|
|
4186
4370
|
const parsed = JSON.parse(body.toString());
|
|
4187
4371
|
isStreaming = parsed.stream === true;
|
|
4188
4372
|
modelId = parsed.model || "";
|
|
4189
4373
|
maxTokens = parsed.max_tokens || 4096;
|
|
4374
|
+
if (sessionId && Array.isArray(parsed.messages)) {
|
|
4375
|
+
const messages = parsed.messages;
|
|
4376
|
+
const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
|
|
4377
|
+
const lastContent = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
|
|
4378
|
+
if (sessionJournal.needsContext(lastContent)) {
|
|
4379
|
+
const journalText = sessionJournal.format(sessionId);
|
|
4380
|
+
if (journalText) {
|
|
4381
|
+
const sysIdx = messages.findIndex((m) => m.role === "system");
|
|
4382
|
+
if (sysIdx >= 0 && typeof messages[sysIdx].content === "string") {
|
|
4383
|
+
messages[sysIdx] = {
|
|
4384
|
+
...messages[sysIdx],
|
|
4385
|
+
content: journalText + "\n\n" + messages[sysIdx].content
|
|
4386
|
+
};
|
|
4387
|
+
} else {
|
|
4388
|
+
messages.unshift({ role: "system", content: journalText });
|
|
4389
|
+
}
|
|
4390
|
+
parsed.messages = messages;
|
|
4391
|
+
console.log(
|
|
4392
|
+
`[ClawRouter] Injected session journal (${journalText.length} chars) for session ${sessionId.slice(0, 8)}...`
|
|
4393
|
+
);
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
}
|
|
4190
4397
|
let bodyModified = false;
|
|
4191
4398
|
if (parsed.stream === true) {
|
|
4192
4399
|
parsed.stream = false;
|
|
@@ -4226,18 +4433,18 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
4226
4433
|
latencyMs: 0
|
|
4227
4434
|
});
|
|
4228
4435
|
} else {
|
|
4229
|
-
const
|
|
4436
|
+
const sessionId2 = getSessionId(
|
|
4230
4437
|
req.headers
|
|
4231
4438
|
);
|
|
4232
|
-
const existingSession =
|
|
4439
|
+
const existingSession = sessionId2 ? sessionStore.getSession(sessionId2) : void 0;
|
|
4233
4440
|
if (existingSession) {
|
|
4234
4441
|
console.log(
|
|
4235
|
-
`[ClawRouter] Session ${
|
|
4442
|
+
`[ClawRouter] Session ${sessionId2?.slice(0, 8)}... using pinned model: ${existingSession.model}`
|
|
4236
4443
|
);
|
|
4237
4444
|
parsed.model = existingSession.model;
|
|
4238
4445
|
modelId = existingSession.model;
|
|
4239
4446
|
bodyModified = true;
|
|
4240
|
-
sessionStore.touchSession(
|
|
4447
|
+
sessionStore.touchSession(sessionId2);
|
|
4241
4448
|
} else {
|
|
4242
4449
|
const messages = parsed.messages;
|
|
4243
4450
|
let lastUserMsg;
|
|
@@ -4266,10 +4473,10 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
4266
4473
|
parsed.model = routingDecision.model;
|
|
4267
4474
|
modelId = routingDecision.model;
|
|
4268
4475
|
bodyModified = true;
|
|
4269
|
-
if (
|
|
4270
|
-
sessionStore.setSession(
|
|
4476
|
+
if (sessionId2) {
|
|
4477
|
+
sessionStore.setSession(sessionId2, routingDecision.model, routingDecision.tier);
|
|
4271
4478
|
console.log(
|
|
4272
|
-
`[ClawRouter] Session ${
|
|
4479
|
+
`[ClawRouter] Session ${sessionId2.slice(0, 8)}... pinned to model: ${routingDecision.model}`
|
|
4273
4480
|
);
|
|
4274
4481
|
}
|
|
4275
4482
|
options.onRouted?.(routingDecision);
|
|
@@ -4604,6 +4811,9 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
4604
4811
|
const content = stripThinkingTokens(rawContent);
|
|
4605
4812
|
const role = choice.message?.role ?? choice.delta?.role ?? "assistant";
|
|
4606
4813
|
const index = choice.index ?? 0;
|
|
4814
|
+
if (content) {
|
|
4815
|
+
accumulatedContent += content;
|
|
4816
|
+
}
|
|
4607
4817
|
const roleChunk = {
|
|
4608
4818
|
...baseChunk,
|
|
4609
4819
|
choices: [{ index, delta: { role }, logprobs: null, finish_reason: null }]
|
|
@@ -4717,6 +4927,22 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
4717
4927
|
});
|
|
4718
4928
|
console.log(`[ClawRouter] Cached response for ${modelId} (${responseBody.length} bytes)`);
|
|
4719
4929
|
}
|
|
4930
|
+
try {
|
|
4931
|
+
const rspJson = JSON.parse(responseBody.toString());
|
|
4932
|
+
if (rspJson.choices?.[0]?.message?.content) {
|
|
4933
|
+
accumulatedContent = rspJson.choices[0].message.content;
|
|
4934
|
+
}
|
|
4935
|
+
} catch {
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
if (sessionId && accumulatedContent) {
|
|
4939
|
+
const events = sessionJournal.extractEvents(accumulatedContent);
|
|
4940
|
+
if (events.length > 0) {
|
|
4941
|
+
sessionJournal.record(sessionId, events, actualModelUsed);
|
|
4942
|
+
console.log(
|
|
4943
|
+
`[ClawRouter] Recorded ${events.length} events to session journal for session ${sessionId.slice(0, 8)}...`
|
|
4944
|
+
);
|
|
4945
|
+
}
|
|
4720
4946
|
}
|
|
4721
4947
|
if (estimatedCostMicros !== void 0) {
|
|
4722
4948
|
balanceMonitor.deductEstimated(estimatedCostMicros);
|