claudish 7.21.0 → 7.22.0
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/index.js +222 -7
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -651,7 +651,7 @@ var init_onepassword_config = __esm(() => {
|
|
|
651
651
|
});
|
|
652
652
|
|
|
653
653
|
// src/version.ts
|
|
654
|
-
var VERSION = "7.
|
|
654
|
+
var VERSION = "7.22.0";
|
|
655
655
|
|
|
656
656
|
// src/logger.ts
|
|
657
657
|
var exports_logger = {};
|
|
@@ -37465,6 +37465,140 @@ var init_connection_error = __esm(() => {
|
|
|
37465
37465
|
BUN_CONNECT_MESSAGE = /unable to connect\. is the computer able to access the url\?/i;
|
|
37466
37466
|
});
|
|
37467
37467
|
|
|
37468
|
+
// src/handlers/shared/stream-head-sniffer.ts
|
|
37469
|
+
function isRetryableStreamError(code, type, message) {
|
|
37470
|
+
if (RETRYABLE_ERROR_CODES.has(code))
|
|
37471
|
+
return true;
|
|
37472
|
+
if (RETRYABLE_ERROR_TYPES.has(type))
|
|
37473
|
+
return true;
|
|
37474
|
+
return /overloaded|try again later|temporarily unavailable|please retry/i.test(message);
|
|
37475
|
+
}
|
|
37476
|
+
async function sniffResponsesStreamHead(response, opts = {}) {
|
|
37477
|
+
const budgetMs = opts.budgetMs ?? DEFAULT_SNIFF_BUDGET_MS;
|
|
37478
|
+
const logMsg = opts.log ?? (() => {});
|
|
37479
|
+
if (!response.body)
|
|
37480
|
+
return { kind: "clean", response };
|
|
37481
|
+
const reader = response.body.getReader();
|
|
37482
|
+
const decoder = new TextDecoder;
|
|
37483
|
+
const consumed = [];
|
|
37484
|
+
let pending = "";
|
|
37485
|
+
const deadline = Date.now() + budgetMs;
|
|
37486
|
+
const replayResponse = () => {
|
|
37487
|
+
const buffered = consumed.slice();
|
|
37488
|
+
const body = new ReadableStream({
|
|
37489
|
+
start: async (controller) => {
|
|
37490
|
+
try {
|
|
37491
|
+
for (const chunk of buffered)
|
|
37492
|
+
controller.enqueue(chunk);
|
|
37493
|
+
while (true) {
|
|
37494
|
+
const { done, value } = await reader.read();
|
|
37495
|
+
if (done)
|
|
37496
|
+
break;
|
|
37497
|
+
if (value)
|
|
37498
|
+
controller.enqueue(value);
|
|
37499
|
+
}
|
|
37500
|
+
controller.close();
|
|
37501
|
+
} catch (error46) {
|
|
37502
|
+
try {
|
|
37503
|
+
controller.error(error46);
|
|
37504
|
+
} catch {}
|
|
37505
|
+
}
|
|
37506
|
+
},
|
|
37507
|
+
cancel: () => {
|
|
37508
|
+
reader.cancel().catch(() => {});
|
|
37509
|
+
}
|
|
37510
|
+
});
|
|
37511
|
+
return new Response(body, {
|
|
37512
|
+
status: response.status,
|
|
37513
|
+
statusText: response.statusText,
|
|
37514
|
+
headers: response.headers
|
|
37515
|
+
});
|
|
37516
|
+
};
|
|
37517
|
+
try {
|
|
37518
|
+
while (true) {
|
|
37519
|
+
const remaining = deadline - Date.now();
|
|
37520
|
+
if (remaining <= 0) {
|
|
37521
|
+
logMsg(`[StreamSniff] budget ${budgetMs}ms elapsed with no verdict \u2014 streaming through`);
|
|
37522
|
+
return { kind: "clean", response: replayResponse() };
|
|
37523
|
+
}
|
|
37524
|
+
let timer;
|
|
37525
|
+
const timeout = new Promise((resolve) => {
|
|
37526
|
+
timer = setTimeout(() => resolve("timeout"), remaining);
|
|
37527
|
+
});
|
|
37528
|
+
let result;
|
|
37529
|
+
try {
|
|
37530
|
+
result = await Promise.race([reader.read(), timeout]);
|
|
37531
|
+
} finally {
|
|
37532
|
+
if (timer)
|
|
37533
|
+
clearTimeout(timer);
|
|
37534
|
+
}
|
|
37535
|
+
if (result === "timeout") {
|
|
37536
|
+
logMsg(`[StreamSniff] budget ${budgetMs}ms elapsed mid-read \u2014 streaming through`);
|
|
37537
|
+
return { kind: "clean", response: replayResponse() };
|
|
37538
|
+
}
|
|
37539
|
+
if (result.done)
|
|
37540
|
+
return { kind: "clean", response: replayResponse() };
|
|
37541
|
+
if (!result.value)
|
|
37542
|
+
continue;
|
|
37543
|
+
consumed.push(result.value);
|
|
37544
|
+
pending += decoder.decode(result.value, { stream: true });
|
|
37545
|
+
const lines = pending.split(`
|
|
37546
|
+
`);
|
|
37547
|
+
pending = lines.pop() ?? "";
|
|
37548
|
+
for (const line of lines) {
|
|
37549
|
+
const trimmed = line.trim();
|
|
37550
|
+
if (!trimmed.startsWith("data:"))
|
|
37551
|
+
continue;
|
|
37552
|
+
const payload = trimmed.slice(5).trim();
|
|
37553
|
+
if (!payload || payload === "[DONE]")
|
|
37554
|
+
continue;
|
|
37555
|
+
let event;
|
|
37556
|
+
try {
|
|
37557
|
+
event = JSON.parse(payload);
|
|
37558
|
+
} catch {
|
|
37559
|
+
continue;
|
|
37560
|
+
}
|
|
37561
|
+
const type = String(event?.type ?? "");
|
|
37562
|
+
if (PREAMBLE_EVENTS.has(type))
|
|
37563
|
+
continue;
|
|
37564
|
+
if (type === "error" || type === "response.failed") {
|
|
37565
|
+
const err = event.error ?? event.response?.error ?? {};
|
|
37566
|
+
const code = String(err.code ?? event.code ?? "");
|
|
37567
|
+
const errType = String(err.type ?? event.error_type ?? "");
|
|
37568
|
+
const message = String(err.message ?? event.message ?? "Unknown API error");
|
|
37569
|
+
if (isRetryableStreamError(code, errType, message)) {
|
|
37570
|
+
reader.cancel().catch(() => {});
|
|
37571
|
+
return { kind: "retryable", code: code || errType || "unknown", message };
|
|
37572
|
+
}
|
|
37573
|
+
return { kind: "clean", response: replayResponse() };
|
|
37574
|
+
}
|
|
37575
|
+
return { kind: "clean", response: replayResponse() };
|
|
37576
|
+
}
|
|
37577
|
+
}
|
|
37578
|
+
} catch (error46) {
|
|
37579
|
+
logMsg(`[StreamSniff] read failed (${error46}) \u2014 handing stream to parser`);
|
|
37580
|
+
return { kind: "clean", response: replayResponse() };
|
|
37581
|
+
}
|
|
37582
|
+
}
|
|
37583
|
+
var DEFAULT_SNIFF_BUDGET_MS = 12000, PREAMBLE_EVENTS, RETRYABLE_ERROR_CODES, RETRYABLE_ERROR_TYPES;
|
|
37584
|
+
var init_stream_head_sniffer = __esm(() => {
|
|
37585
|
+
PREAMBLE_EVENTS = new Set(["response.created", "response.in_progress"]);
|
|
37586
|
+
RETRYABLE_ERROR_CODES = new Set([
|
|
37587
|
+
"server_is_overloaded",
|
|
37588
|
+
"server_error",
|
|
37589
|
+
"internal_error",
|
|
37590
|
+
"internal_server_error",
|
|
37591
|
+
"rate_limit_exceeded",
|
|
37592
|
+
"overloaded",
|
|
37593
|
+
"slow_down"
|
|
37594
|
+
]);
|
|
37595
|
+
RETRYABLE_ERROR_TYPES = new Set([
|
|
37596
|
+
"service_unavailable_error",
|
|
37597
|
+
"overloaded_error",
|
|
37598
|
+
"api_error"
|
|
37599
|
+
]);
|
|
37600
|
+
});
|
|
37601
|
+
|
|
37468
37602
|
// src/handlers/shared/stream-parsers/anthropic-sse.ts
|
|
37469
37603
|
function createAnthropicPassthroughStream(c, response, opts) {
|
|
37470
37604
|
const encoder = new TextEncoder;
|
|
@@ -38130,6 +38264,11 @@ data: ${JSON.stringify(data)}
|
|
|
38130
38264
|
`));
|
|
38131
38265
|
}
|
|
38132
38266
|
};
|
|
38267
|
+
const safeClose = () => {
|
|
38268
|
+
try {
|
|
38269
|
+
controller.close();
|
|
38270
|
+
} catch {}
|
|
38271
|
+
};
|
|
38133
38272
|
const closeReasoning = () => {
|
|
38134
38273
|
if (reasoningIdx >= 0) {
|
|
38135
38274
|
send("content_block_stop", { type: "content_block_stop", index: reasoningIdx });
|
|
@@ -38352,7 +38491,7 @@ data: ${JSON.stringify(data)}
|
|
|
38352
38491
|
}
|
|
38353
38492
|
if (opts.onTokenUpdate)
|
|
38354
38493
|
opts.onTokenUpdate(inputTokens, outputTokens);
|
|
38355
|
-
|
|
38494
|
+
safeClose();
|
|
38356
38495
|
return;
|
|
38357
38496
|
}
|
|
38358
38497
|
} catch (parseError) {
|
|
@@ -38380,7 +38519,7 @@ data: ${JSON.stringify(data)}
|
|
|
38380
38519
|
isClosed = true;
|
|
38381
38520
|
if (opts.onTokenUpdate)
|
|
38382
38521
|
opts.onTokenUpdate(inputTokens, outputTokens);
|
|
38383
|
-
|
|
38522
|
+
safeClose();
|
|
38384
38523
|
} catch (error46) {
|
|
38385
38524
|
if (pingInterval) {
|
|
38386
38525
|
clearInterval(pingInterval);
|
|
@@ -38416,9 +38555,7 @@ data: ${JSON.stringify(data)}
|
|
|
38416
38555
|
isClosed = true;
|
|
38417
38556
|
if (opts.onTokenUpdate)
|
|
38418
38557
|
opts.onTokenUpdate(inputTokens, outputTokens);
|
|
38419
|
-
|
|
38420
|
-
controller.close();
|
|
38421
|
-
} catch {}
|
|
38558
|
+
safeClose();
|
|
38422
38559
|
}
|
|
38423
38560
|
}
|
|
38424
38561
|
},
|
|
@@ -39031,6 +39168,35 @@ class ComposedHandler {
|
|
|
39031
39168
|
if (droppedParams.length > 0) {
|
|
39032
39169
|
c.header("X-Dropped-Params", droppedParams.join(", "));
|
|
39033
39170
|
}
|
|
39171
|
+
if (this.resolveStreamFormat() === "openai-responses-sse") {
|
|
39172
|
+
const settled = await this.settleResponsesStreamHead(response, () => this.provider.enqueueRequest ? this.provider.enqueueRequest(doFetch) : doFetch());
|
|
39173
|
+
if (settled.kind === "exhausted") {
|
|
39174
|
+
const waited = STREAM_RETRY_DELAYS_MS.slice(0, settled.attempts).reduce((sum, ms) => sum + ms, 0);
|
|
39175
|
+
const surfaced = `${this.provider.displayName} is overloaded upstream (${settled.code}): ${settled.message} ` + `claudish retried ${settled.attempts}\xD7 over ${Math.round(waited / 1000)}s without success.`;
|
|
39176
|
+
logStderr(`Error: ${surfaced}`);
|
|
39177
|
+
try {
|
|
39178
|
+
recordStats({
|
|
39179
|
+
model_id: this.targetModel,
|
|
39180
|
+
provider_name: this.provider.name,
|
|
39181
|
+
stream_format: this.provider.streamFormat,
|
|
39182
|
+
latency_ms: Math.round(performance.now() - startTime),
|
|
39183
|
+
success: false,
|
|
39184
|
+
http_status: 503,
|
|
39185
|
+
error_class: "server_error",
|
|
39186
|
+
error_code: settled.code,
|
|
39187
|
+
token_strategy: this.options.tokenStrategy ?? "standard",
|
|
39188
|
+
adapter_name: this.getActiveAdapterName(),
|
|
39189
|
+
middleware_names: this.middlewareManager.getActiveNames(this.bareModelName),
|
|
39190
|
+
fallback_used: fallbackMeta !== undefined,
|
|
39191
|
+
fallback_chain: fallbackMeta?.chain,
|
|
39192
|
+
fallback_attempts: fallbackMeta?.attempts,
|
|
39193
|
+
invocation_mode: this.options.invocationMode ?? "auto-route"
|
|
39194
|
+
});
|
|
39195
|
+
} catch {}
|
|
39196
|
+
return c.json(wrapAnthropicError(503, surfaced, "overloaded_error"), 503);
|
|
39197
|
+
}
|
|
39198
|
+
response = settled.response;
|
|
39199
|
+
}
|
|
39034
39200
|
latencyMs = Math.round(performance.now() - startTime);
|
|
39035
39201
|
const httpStatus = response.status;
|
|
39036
39202
|
let streamApiError = null;
|
|
@@ -39062,6 +39228,52 @@ class ComposedHandler {
|
|
|
39062
39228
|
streamApiError = { code, message };
|
|
39063
39229
|
});
|
|
39064
39230
|
}
|
|
39231
|
+
async settleResponsesStreamHead(initial, reissue) {
|
|
39232
|
+
let response = initial;
|
|
39233
|
+
for (let attempt = 0;; attempt++) {
|
|
39234
|
+
const verdict = await sniffResponsesStreamHead(response, { log });
|
|
39235
|
+
if (verdict.kind === "clean")
|
|
39236
|
+
return { kind: "ok", response: verdict.response };
|
|
39237
|
+
const delayMs = STREAM_RETRY_DELAYS_MS[attempt];
|
|
39238
|
+
if (delayMs === undefined) {
|
|
39239
|
+
log(`[${this.provider.displayName}] in-stream ${verdict.code} persisted after ` + `${attempt} retries \u2014 surfacing 503 so the client can retry`);
|
|
39240
|
+
return {
|
|
39241
|
+
kind: "exhausted",
|
|
39242
|
+
code: verdict.code,
|
|
39243
|
+
message: verdict.message,
|
|
39244
|
+
attempts: attempt
|
|
39245
|
+
};
|
|
39246
|
+
}
|
|
39247
|
+
log(`[${this.provider.displayName}] in-stream ${verdict.code} before any output \u2014 ` + `retry ${attempt + 1}/${STREAM_RETRY_DELAYS_MS.length} in ${delayMs / 1000}s`);
|
|
39248
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
39249
|
+
let next;
|
|
39250
|
+
try {
|
|
39251
|
+
next = await reissue();
|
|
39252
|
+
} catch (error46) {
|
|
39253
|
+
log(`[${this.provider.displayName}] retry fetch failed: ${error46}`);
|
|
39254
|
+
return {
|
|
39255
|
+
kind: "exhausted",
|
|
39256
|
+
code: verdict.code,
|
|
39257
|
+
message: `${verdict.message} (retry could not reach the provider: ${error46})`,
|
|
39258
|
+
attempts: attempt + 1
|
|
39259
|
+
};
|
|
39260
|
+
}
|
|
39261
|
+
if (!next.ok) {
|
|
39262
|
+
const body = await next.text().catch(() => "");
|
|
39263
|
+
log(`[${this.provider.displayName}] retry returned HTTP ${next.status}`);
|
|
39264
|
+
return {
|
|
39265
|
+
kind: "exhausted",
|
|
39266
|
+
code: `http_${next.status}`,
|
|
39267
|
+
message: body.slice(0, 500) || `HTTP ${next.status}`,
|
|
39268
|
+
attempts: attempt + 1
|
|
39269
|
+
};
|
|
39270
|
+
}
|
|
39271
|
+
response = next;
|
|
39272
|
+
}
|
|
39273
|
+
}
|
|
39274
|
+
resolveStreamFormat() {
|
|
39275
|
+
return this.provider.overrideStreamFormat?.() ?? this.explicitAdapter?.getStreamFormat() ?? this.modelAdapter?.getStreamFormat() ?? this.getAdapter().getStreamFormat();
|
|
39276
|
+
}
|
|
39065
39277
|
handleStream(c, response, adapter, claudeRequest, toolNameMap, onComplete, onApiError) {
|
|
39066
39278
|
let pendingOnComplete = onComplete;
|
|
39067
39279
|
const onTokenUpdate = (input, output) => {
|
|
@@ -39087,7 +39299,7 @@ class ComposedHandler {
|
|
|
39087
39299
|
pendingOnComplete = undefined;
|
|
39088
39300
|
}
|
|
39089
39301
|
};
|
|
39090
|
-
const streamFormat = this.
|
|
39302
|
+
const streamFormat = this.resolveStreamFormat();
|
|
39091
39303
|
const priorInputTokens = this.tokenTracker.getLastInputTokens();
|
|
39092
39304
|
switch (streamFormat) {
|
|
39093
39305
|
case "openai-sse":
|
|
@@ -39191,6 +39403,7 @@ function getRecoveryHint(status, errorText, providerName) {
|
|
|
39191
39403
|
}
|
|
39192
39404
|
return `Unexpected HTTP ${status} from ${providerName}.`;
|
|
39193
39405
|
}
|
|
39406
|
+
var STREAM_RETRY_DELAYS_MS;
|
|
39194
39407
|
var init_composed_handler = __esm(() => {
|
|
39195
39408
|
init_dialect_manager();
|
|
39196
39409
|
init_logger();
|
|
@@ -39203,6 +39416,7 @@ var init_composed_handler = __esm(() => {
|
|
|
39203
39416
|
init_anthropic_error();
|
|
39204
39417
|
init_connection_error();
|
|
39205
39418
|
init_openai_compat();
|
|
39419
|
+
init_stream_head_sniffer();
|
|
39206
39420
|
init_anthropic_sse();
|
|
39207
39421
|
init_gemini_sse();
|
|
39208
39422
|
init_ollama_jsonl();
|
|
@@ -39210,6 +39424,7 @@ var init_composed_handler = __esm(() => {
|
|
|
39210
39424
|
init_openai_responses_sse();
|
|
39211
39425
|
init_openai_sse();
|
|
39212
39426
|
init_token_tracker();
|
|
39427
|
+
STREAM_RETRY_DELAYS_MS = [3000, 15000, 30000];
|
|
39213
39428
|
});
|
|
39214
39429
|
|
|
39215
39430
|
// src/handlers/fallback-handler.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudish",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.22.0",
|
|
4
4
|
"description": "Run Claude Code with any model - OpenRouter, Ollama, LM Studio & local models",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,10 +60,10 @@
|
|
|
60
60
|
"ai"
|
|
61
61
|
],
|
|
62
62
|
"optionalDependencies": {
|
|
63
|
-
"@claudish/magmux-darwin-arm64": "7.
|
|
64
|
-
"@claudish/magmux-darwin-x64": "7.
|
|
65
|
-
"@claudish/magmux-linux-arm64": "7.
|
|
66
|
-
"@claudish/magmux-linux-x64": "7.
|
|
63
|
+
"@claudish/magmux-darwin-arm64": "7.22.0",
|
|
64
|
+
"@claudish/magmux-darwin-x64": "7.22.0",
|
|
65
|
+
"@claudish/magmux-linux-arm64": "7.22.0",
|
|
66
|
+
"@claudish/magmux-linux-x64": "7.22.0"
|
|
67
67
|
},
|
|
68
68
|
"author": "Jack Rudenko <i@madappgang.com>",
|
|
69
69
|
"license": "MIT",
|