@blockrun/clawrouter 0.9.13 → 0.9.14
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 +15 -224
- package/dist/cli.js.map +1 -1
- package/dist/index.js +15 -224
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/models.ts
|
|
2
2
|
var MODEL_ALIASES = {
|
|
3
|
-
// Claude
|
|
3
|
+
// Claude - short names
|
|
4
4
|
claude: "anthropic/claude-sonnet-4",
|
|
5
5
|
sonnet: "anthropic/claude-sonnet-4",
|
|
6
6
|
opus: "anthropic/claude-opus-4.6",
|
|
@@ -8,6 +8,11 @@ var MODEL_ALIASES = {
|
|
|
8
8
|
"opus-46": "anthropic/claude-opus-4.6",
|
|
9
9
|
"opus-45": "anthropic/claude-opus-4.5",
|
|
10
10
|
haiku: "anthropic/claude-haiku-4.5",
|
|
11
|
+
// Claude - provider/shortname patterns (common in agent frameworks)
|
|
12
|
+
"anthropic/sonnet": "anthropic/claude-sonnet-4",
|
|
13
|
+
"anthropic/opus": "anthropic/claude-opus-4.6",
|
|
14
|
+
"anthropic/haiku": "anthropic/claude-haiku-4.5",
|
|
15
|
+
"anthropic/claude": "anthropic/claude-sonnet-4",
|
|
11
16
|
// OpenAI
|
|
12
17
|
gpt: "openai/gpt-4o",
|
|
13
18
|
gpt4: "openai/gpt-4o",
|
|
@@ -3474,174 +3479,6 @@ var PROXY_PORT = (() => {
|
|
|
3474
3479
|
return DEFAULT_PORT;
|
|
3475
3480
|
})();
|
|
3476
3481
|
|
|
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
|
-
|
|
3645
3482
|
// src/proxy.ts
|
|
3646
3483
|
var BLOCKRUN_API = "https://blockrun.ai/api";
|
|
3647
3484
|
var AUTO_MODEL = "blockrun/auto";
|
|
@@ -4051,7 +3888,6 @@ async function startProxy(options) {
|
|
|
4051
3888
|
const deduplicator = new RequestDeduplicator();
|
|
4052
3889
|
const responseCache = new ResponseCache(options.cacheConfig);
|
|
4053
3890
|
const sessionStore = new SessionStore(options.sessionConfig);
|
|
4054
|
-
const sessionJournal = new SessionJournal();
|
|
4055
3891
|
const connections = /* @__PURE__ */ new Set();
|
|
4056
3892
|
const server = createServer(async (req, res) => {
|
|
4057
3893
|
req.on("error", (err) => {
|
|
@@ -4147,8 +3983,7 @@ async function startProxy(options) {
|
|
|
4147
3983
|
deduplicator,
|
|
4148
3984
|
balanceMonitor,
|
|
4149
3985
|
sessionStore,
|
|
4150
|
-
responseCache
|
|
4151
|
-
sessionJournal
|
|
3986
|
+
responseCache
|
|
4152
3987
|
);
|
|
4153
3988
|
} catch (err) {
|
|
4154
3989
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -4349,7 +4184,7 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
4349
4184
|
};
|
|
4350
4185
|
}
|
|
4351
4186
|
}
|
|
4352
|
-
async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, deduplicator, balanceMonitor, sessionStore, responseCache
|
|
4187
|
+
async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, deduplicator, balanceMonitor, sessionStore, responseCache) {
|
|
4353
4188
|
const startTime = Date.now();
|
|
4354
4189
|
const upstreamUrl = `${apiBase}${req.url}`;
|
|
4355
4190
|
const bodyChunks = [];
|
|
@@ -4362,38 +4197,13 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
4362
4197
|
let modelId = "";
|
|
4363
4198
|
let maxTokens = 4096;
|
|
4364
4199
|
let routingProfile = null;
|
|
4365
|
-
let accumulatedContent = "";
|
|
4366
4200
|
const isChatCompletion = req.url?.includes("/chat/completions");
|
|
4367
|
-
const sessionId = getSessionId(req.headers);
|
|
4368
4201
|
if (isChatCompletion && body.length > 0) {
|
|
4369
4202
|
try {
|
|
4370
4203
|
const parsed = JSON.parse(body.toString());
|
|
4371
4204
|
isStreaming = parsed.stream === true;
|
|
4372
4205
|
modelId = parsed.model || "";
|
|
4373
4206
|
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
|
-
}
|
|
4397
4207
|
let bodyModified = false;
|
|
4398
4208
|
if (parsed.stream === true) {
|
|
4399
4209
|
parsed.stream = false;
|
|
@@ -4433,18 +4243,18 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
4433
4243
|
latencyMs: 0
|
|
4434
4244
|
});
|
|
4435
4245
|
} else {
|
|
4436
|
-
const
|
|
4246
|
+
const sessionId = getSessionId(
|
|
4437
4247
|
req.headers
|
|
4438
4248
|
);
|
|
4439
|
-
const existingSession =
|
|
4249
|
+
const existingSession = sessionId ? sessionStore.getSession(sessionId) : void 0;
|
|
4440
4250
|
if (existingSession) {
|
|
4441
4251
|
console.log(
|
|
4442
|
-
`[ClawRouter] Session ${
|
|
4252
|
+
`[ClawRouter] Session ${sessionId?.slice(0, 8)}... using pinned model: ${existingSession.model}`
|
|
4443
4253
|
);
|
|
4444
4254
|
parsed.model = existingSession.model;
|
|
4445
4255
|
modelId = existingSession.model;
|
|
4446
4256
|
bodyModified = true;
|
|
4447
|
-
sessionStore.touchSession(
|
|
4257
|
+
sessionStore.touchSession(sessionId);
|
|
4448
4258
|
} else {
|
|
4449
4259
|
const messages = parsed.messages;
|
|
4450
4260
|
let lastUserMsg;
|
|
@@ -4473,10 +4283,10 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
4473
4283
|
parsed.model = routingDecision.model;
|
|
4474
4284
|
modelId = routingDecision.model;
|
|
4475
4285
|
bodyModified = true;
|
|
4476
|
-
if (
|
|
4477
|
-
sessionStore.setSession(
|
|
4286
|
+
if (sessionId) {
|
|
4287
|
+
sessionStore.setSession(sessionId, routingDecision.model, routingDecision.tier);
|
|
4478
4288
|
console.log(
|
|
4479
|
-
`[ClawRouter] Session ${
|
|
4289
|
+
`[ClawRouter] Session ${sessionId.slice(0, 8)}... pinned to model: ${routingDecision.model}`
|
|
4480
4290
|
);
|
|
4481
4291
|
}
|
|
4482
4292
|
options.onRouted?.(routingDecision);
|
|
@@ -4811,9 +4621,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
4811
4621
|
const content = stripThinkingTokens(rawContent);
|
|
4812
4622
|
const role = choice.message?.role ?? choice.delta?.role ?? "assistant";
|
|
4813
4623
|
const index = choice.index ?? 0;
|
|
4814
|
-
if (content) {
|
|
4815
|
-
accumulatedContent += content;
|
|
4816
|
-
}
|
|
4817
4624
|
const roleChunk = {
|
|
4818
4625
|
...baseChunk,
|
|
4819
4626
|
choices: [{ index, delta: { role }, logprobs: null, finish_reason: null }]
|
|
@@ -4927,22 +4734,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
4927
4734
|
});
|
|
4928
4735
|
console.log(`[ClawRouter] Cached response for ${modelId} (${responseBody.length} bytes)`);
|
|
4929
4736
|
}
|
|
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
|
-
}
|
|
4946
4737
|
}
|
|
4947
4738
|
if (estimatedCostMicros !== void 0) {
|
|
4948
4739
|
balanceMonitor.deductEstimated(estimatedCostMicros);
|