@askalf/dario 3.10.1 → 3.10.3

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.
@@ -291,21 +291,23 @@ export function buildCCRequest(clientBody, billingTag, cache1h, identity, opts =
291
291
  }
292
292
  }
293
293
  }
294
- // ── Drop trailing empty/assistant turns ──
294
+ // ── Drop trailing empty turns ──
295
295
  // An assistant turn that was thinking-only before the strip above becomes
296
296
  // content: []. Forwarding that shape makes Anthropic interpret the request
297
297
  // as a prefill ("continue from this assistant text"), which Opus 4.6 under
298
298
  // adaptive thinking + the claude-code beta refuses with:
299
299
  // "This model does not support assistant message prefill. The
300
300
  // conversation must end with a user message."
301
- // Clients that preserve full thinking in history (OpenClaw, Hermes) hit
302
- // this any time a prior turn was interrupted mid-thinking. Drop trailing
303
- // assistant/empty turns so the request ends on a user message.
301
+ // Drop ONLY empty trailing turns. Do not pop trailing assistant turns that
302
+ // still carry text or tool_use content v3.10.1 popped any trailing
303
+ // assistant and that caused a runaway loop in OpenClaw (#37): the client
304
+ // appended its assistant reply locally, dario stripped it from the next
305
+ // request, the model regenerated the same reply, dario stripped that, and
306
+ // the loop never terminated (133 POSTs from a single user prompt).
304
307
  while (messages.length > 0) {
305
308
  const last = messages[messages.length - 1];
306
309
  const contentEmpty = Array.isArray(last.content) && last.content.length === 0;
307
- const isTrailingAssistant = last.role === 'assistant';
308
- if (contentEmpty || isTrailingAssistant) {
310
+ if (contentEmpty) {
309
311
  messages.pop();
310
312
  continue;
311
313
  }
package/dist/proxy.js CHANGED
@@ -1013,14 +1013,37 @@ export async function startProxy(opts = {}) {
1013
1013
  }
1014
1014
  }
1015
1015
  requestCount++;
1016
- // Log billing classification on first request or in verbose mode
1016
+ // Log billing classification on first request or in verbose mode.
1017
+ //
1018
+ // Anthropic is inconsistent about returning rate-limit headers:
1019
+ // - Non-200 responses (429, 500, early aborts) often omit them entirely.
1020
+ // - The overage-utilization header is omitted when there is no overage
1021
+ // bucket configured or when the subscription claim covers the request
1022
+ // — in that case "overage" is effectively 0%, not unknown.
1023
+ // Pre-fix we logged `overage: ?` on every five_hour request that had no
1024
+ // overage configured, which looked like a broken parser (see #37 log
1025
+ // dump). Fix: treat missing overage header as 0% when the claim is
1026
+ // five_hour / five_hour_fallback (the subscription covered it), and fall
1027
+ // back to `n/a` in the genuinely-unknown case.
1017
1028
  const billingClaim = upstream.headers.get('anthropic-ratelimit-unified-representative-claim');
1018
1029
  const overageUtil = upstream.headers.get('anthropic-ratelimit-unified-overage-utilization');
1019
1030
  if (requestCount === 1 || verbose) {
1020
1031
  if (billingClaim) {
1021
- const overagePct = overageUtil ? `${Math.round(parseFloat(overageUtil) * 100)}%` : '?';
1032
+ let overagePct;
1033
+ if (overageUtil !== null) {
1034
+ overagePct = `${Math.round(parseFloat(overageUtil) * 100)}%`;
1035
+ }
1036
+ else if (billingClaim === 'five_hour' || billingClaim === 'five_hour_fallback') {
1037
+ overagePct = '0%';
1038
+ }
1039
+ else {
1040
+ overagePct = 'n/a';
1041
+ }
1022
1042
  console.log(`[dario] #${requestCount} billing: ${billingClaim} (overage: ${overagePct})`);
1023
1043
  }
1044
+ else if (verbose) {
1045
+ console.log(`[dario] #${requestCount} billing: headers absent (status=${upstream.status})`);
1046
+ }
1024
1047
  }
1025
1048
  res.writeHead(upstream.status, responseHeaders);
1026
1049
  if (isStream && upstream.body) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "3.10.1",
3
+ "version": "3.10.3",
4
4
  "description": "A local LLM router. One endpoint, every provider — Claude subscriptions, OpenAI, OpenRouter, Groq, local LiteLLM, any OpenAI-compat endpoint — your tools don't need to change.",
5
5
  "type": "module",
6
6
  "bin": {