@askalf/dario 6.6.0 → 6.6.1
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/analytics.d.ts +34 -0
- package/dist/analytics.js +25 -0
- package/dist/cli.js +13 -0
- package/dist/midstream.d.ts +21 -0
- package/dist/midstream.js +29 -4
- package/dist/proxy.d.ts +9 -0
- package/dist/proxy.js +25 -1
- package/docs/midstream-continuation.md +26 -0
- package/package.json +1 -1
package/dist/analytics.d.ts
CHANGED
|
@@ -41,7 +41,39 @@ export interface RequestRecord {
|
|
|
41
41
|
status: number;
|
|
42
42
|
isStream: boolean;
|
|
43
43
|
isOpenAI: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Set when the stream died with content on the wire and the mid-stream
|
|
46
|
+
* guard acted (v6.1, src/midstream.ts): what came of it and, when a resume
|
|
47
|
+
* delivered, which leg served the rest. Absent on every ordinary request.
|
|
48
|
+
*/
|
|
49
|
+
continuation?: RequestContinuation;
|
|
50
|
+
}
|
|
51
|
+
export interface RequestContinuation {
|
|
52
|
+
/** See midstream.ts ContinuationOutcome. */
|
|
53
|
+
outcome: 'continued' | 'continued-unfinished' | 'resume-failed' | 'no-target';
|
|
54
|
+
/** Label of the leg that served the rest (`gpt-5.6-terra (codex)`), when one did. */
|
|
55
|
+
by?: string;
|
|
56
|
+
/** Characters the client already had when the stream died. */
|
|
57
|
+
partialChars: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* How the continuations in a window went. `attempted` is every stream that
|
|
61
|
+
* died with content on the wire and a guard in place; the other four
|
|
62
|
+
* partition it. The number that says whether --pool-fallback is set up to
|
|
63
|
+
* catch a dying stream, and how often one dies at all.
|
|
64
|
+
*/
|
|
65
|
+
export interface ContinuationStats {
|
|
66
|
+
attempted: number;
|
|
67
|
+
/** The client holds one complete message. */
|
|
68
|
+
finished: number;
|
|
69
|
+
/** A resume delivered content and then died too; the stream was left open-ended. */
|
|
70
|
+
unfinished: number;
|
|
71
|
+
/** Every choice delivered nothing; the stream ended truncated as before. */
|
|
72
|
+
failed: number;
|
|
73
|
+
/** Nothing to resume through — no --pool-fallback entry for the other provider. */
|
|
74
|
+
noTarget: number;
|
|
44
75
|
}
|
|
76
|
+
export declare function continuationStats(records: readonly RequestRecord[]): ContinuationStats;
|
|
45
77
|
/**
|
|
46
78
|
* The four billing buckets a request can land in, derived from the
|
|
47
79
|
* `anthropic-ratelimit-unified-representative-claim` response header.
|
|
@@ -316,6 +348,8 @@ interface WindowStats {
|
|
|
316
348
|
estimatedCost: number;
|
|
317
349
|
avgLatencyMs: number;
|
|
318
350
|
errorRate: number;
|
|
351
|
+
/** Mid-stream continuations in the window and how they went (v6.1 guard, counted since v6.6.1). */
|
|
352
|
+
continuations: ContinuationStats;
|
|
319
353
|
claimBreakdown: Record<string, number>;
|
|
320
354
|
/** Count of requests in each derived billing bucket. See #34. */
|
|
321
355
|
billingBucketBreakdown: Record<BillingBucket, number>;
|
package/dist/analytics.js
CHANGED
|
@@ -18,6 +18,29 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { EventEmitter } from 'node:events';
|
|
20
20
|
import { createHash } from 'node:crypto';
|
|
21
|
+
export function continuationStats(records) {
|
|
22
|
+
const out = { attempted: 0, finished: 0, unfinished: 0, failed: 0, noTarget: 0 };
|
|
23
|
+
for (const r of records) {
|
|
24
|
+
if (!r.continuation)
|
|
25
|
+
continue;
|
|
26
|
+
out.attempted++;
|
|
27
|
+
switch (r.continuation.outcome) {
|
|
28
|
+
case 'continued':
|
|
29
|
+
out.finished++;
|
|
30
|
+
break;
|
|
31
|
+
case 'continued-unfinished':
|
|
32
|
+
out.unfinished++;
|
|
33
|
+
break;
|
|
34
|
+
case 'resume-failed':
|
|
35
|
+
out.failed++;
|
|
36
|
+
break;
|
|
37
|
+
case 'no-target':
|
|
38
|
+
out.noTarget++;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
21
44
|
/**
|
|
22
45
|
* Map the raw `representative-claim` header value to a human-friendly
|
|
23
46
|
* billing bucket. Pure function; no state; safe to call from any context.
|
|
@@ -372,6 +395,7 @@ export class Analytics extends EventEmitter {
|
|
|
372
395
|
totalInputTokens: 0, totalOutputTokens: 0, totalThinkingTokens: 0,
|
|
373
396
|
totalCacheReadTokens: 0, totalCacheCreateTokens: 0, cachedPromptPercent: 0,
|
|
374
397
|
estimatedCost: 0, avgLatencyMs: 0, errorRate: 0,
|
|
398
|
+
continuations: { attempted: 0, finished: 0, unfinished: 0, failed: 0, noTarget: 0 },
|
|
375
399
|
claimBreakdown: {},
|
|
376
400
|
billingBucketBreakdown: {
|
|
377
401
|
subscription: 0,
|
|
@@ -418,6 +442,7 @@ export class Analytics extends EventEmitter {
|
|
|
418
442
|
estimatedCost: Math.round(cost * 10000) / 10000,
|
|
419
443
|
avgLatencyMs: Math.round(avgLatency),
|
|
420
444
|
errorRate: Math.round((errors / records.length) * 10000) / 10000,
|
|
445
|
+
continuations: continuationStats(records),
|
|
421
446
|
claimBreakdown: claims,
|
|
422
447
|
billingBucketBreakdown: buckets,
|
|
423
448
|
subscriptionPercent: subscriptionPct,
|
package/dist/cli.js
CHANGED
|
@@ -2450,6 +2450,19 @@ async function usage() {
|
|
|
2450
2450
|
if ((win.estimatedCost ?? 0) > 0) {
|
|
2451
2451
|
console.log(` Est. cost: $${(win.estimatedCost ?? 0).toFixed(4)} (would-be API cost)`);
|
|
2452
2452
|
}
|
|
2453
|
+
// Streams that died with content on the wire, and what the mid-stream
|
|
2454
|
+
// guard made of them. Silent when none did — the common case.
|
|
2455
|
+
const c = win.continuations;
|
|
2456
|
+
if (c && c.attempted > 0) {
|
|
2457
|
+
const parts = [`${c.finished} finished`];
|
|
2458
|
+
if (c.unfinished > 0)
|
|
2459
|
+
parts.push(`${c.unfinished} unfinished`);
|
|
2460
|
+
if (c.failed > 0)
|
|
2461
|
+
parts.push(`${c.failed} failed`);
|
|
2462
|
+
if (c.noTarget > 0)
|
|
2463
|
+
parts.push(`${c.noTarget} no target — set --pool-fallback for the other provider`);
|
|
2464
|
+
console.log(` Continuations: ${c.attempted} stream${c.attempted === 1 ? '' : 's'} died mid-answer: ${parts.join(', ')}`);
|
|
2465
|
+
}
|
|
2453
2466
|
}
|
|
2454
2467
|
if (perAccount && Object.keys(perAccount).length > 0) {
|
|
2455
2468
|
console.log('');
|
package/dist/midstream.d.ts
CHANGED
|
@@ -65,6 +65,14 @@ export declare const CONTINUATION_HEADER = "x-dario-continuation";
|
|
|
65
65
|
export declare const MAX_CONTINUATION_DEPTH = 2;
|
|
66
66
|
/** The depth a request carries, 0 for an ordinary client request. */
|
|
67
67
|
export declare function continuationDepth(headerValue: string | string[] | undefined): number;
|
|
68
|
+
/**
|
|
69
|
+
* The loopback also names the request it resumes (the guard's request
|
|
70
|
+
* number), so the resume leg's own log row can point back at the row whose
|
|
71
|
+
* stream died. Log-side only: nothing routes on it.
|
|
72
|
+
*/
|
|
73
|
+
export declare const CONTINUATION_OF_HEADER = "x-dario-continuation-of";
|
|
74
|
+
/** The request number a resume leg names, or undefined when absent / not a number. */
|
|
75
|
+
export declare function continuationOfRequest(headerValue: string | string[] | undefined): number | undefined;
|
|
68
76
|
/** Characters of the partial the model is asked to repeat verbatim (the seam anchor). */
|
|
69
77
|
export declare const ANCHOR_CHARS = 40;
|
|
70
78
|
export interface SseFrame {
|
|
@@ -304,11 +312,24 @@ export interface MidstreamGuardOptions {
|
|
|
304
312
|
log?: (line: string) => void;
|
|
305
313
|
}
|
|
306
314
|
export type FinishOutcome = 'clean' | 'continued' | 'continued-unfinished' | 'ended' | 'not-continuable' | 'no-target' | 'resume-failed';
|
|
315
|
+
/**
|
|
316
|
+
* What a continuation attempt came to, for the request's analytics row and
|
|
317
|
+
* log line: the four FinishOutcomes where the guard actually acted. A clean
|
|
318
|
+
* stream, one the client left, and one that was never continuable are not
|
|
319
|
+
* attempts and are not recorded.
|
|
320
|
+
*/
|
|
321
|
+
export type ContinuationOutcome = 'continued' | 'continued-unfinished' | 'resume-failed' | 'no-target';
|
|
307
322
|
export declare class MidstreamGuard {
|
|
308
323
|
private readonly o;
|
|
309
324
|
readonly state: ClientStreamState;
|
|
310
325
|
private readonly splitter;
|
|
311
326
|
private finished;
|
|
327
|
+
/** Set by finish(): the attempt's outcome, or null when the stream needed none. */
|
|
328
|
+
outcome: ContinuationOutcome | null;
|
|
329
|
+
/** The label of the leg that put content on the wire (`gpt-5.6-terra (codex)`, `claude-opus-5 (same model)`). */
|
|
330
|
+
continuedBy: string | null;
|
|
331
|
+
/** Characters the client had when the stream died — where the seam sits. */
|
|
332
|
+
partialChars: number;
|
|
312
333
|
constructor(o: MidstreamGuardOptions);
|
|
313
334
|
/**
|
|
314
335
|
* The site knows the upstream turn failed (a codex `response.failed`, a
|
package/dist/midstream.js
CHANGED
|
@@ -69,6 +69,20 @@ export function continuationDepth(headerValue) {
|
|
|
69
69
|
const n = Number.parseInt(v, 10);
|
|
70
70
|
return Number.isFinite(n) && n > 0 ? n : 1;
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* The loopback also names the request it resumes (the guard's request
|
|
74
|
+
* number), so the resume leg's own log row can point back at the row whose
|
|
75
|
+
* stream died. Log-side only: nothing routes on it.
|
|
76
|
+
*/
|
|
77
|
+
export const CONTINUATION_OF_HEADER = 'x-dario-continuation-of';
|
|
78
|
+
/** The request number a resume leg names, or undefined when absent / not a number. */
|
|
79
|
+
export function continuationOfRequest(headerValue) {
|
|
80
|
+
const v = Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
81
|
+
if (v === undefined)
|
|
82
|
+
return undefined;
|
|
83
|
+
const n = Number.parseInt(v, 10);
|
|
84
|
+
return Number.isFinite(n) && n >= 0 ? n : undefined;
|
|
85
|
+
}
|
|
72
86
|
/** Characters of the partial the model is asked to repeat verbatim (the seam anchor). */
|
|
73
87
|
export const ANCHOR_CHARS = 40;
|
|
74
88
|
/** Upper bound on continuation text held back while looking for the anchor. */
|
|
@@ -702,6 +716,12 @@ export class MidstreamGuard {
|
|
|
702
716
|
state;
|
|
703
717
|
splitter = new SseFrameSplitter();
|
|
704
718
|
finished = false;
|
|
719
|
+
/** Set by finish(): the attempt's outcome, or null when the stream needed none. */
|
|
720
|
+
outcome = null;
|
|
721
|
+
/** The label of the leg that put content on the wire (`gpt-5.6-terra (codex)`, `claude-opus-5 (same model)`). */
|
|
722
|
+
continuedBy = null;
|
|
723
|
+
/** Characters the client had when the stream died — where the seam sits. */
|
|
724
|
+
partialChars = 0;
|
|
705
725
|
constructor(o) {
|
|
706
726
|
this.o = o;
|
|
707
727
|
this.state = new ClientStreamState(o.shape);
|
|
@@ -762,6 +782,7 @@ export class MidstreamGuard {
|
|
|
762
782
|
// dead before its first byte) hands over to the next; the first one that
|
|
763
783
|
// puts content on the wire ends the search, finished or not.
|
|
764
784
|
const partial = s.textSoFar;
|
|
785
|
+
this.partialChars = partial.length;
|
|
765
786
|
let tried = 0;
|
|
766
787
|
for (let choice = (this.o.depth ?? 0) + 1; choice <= MAX_CONTINUATION_DEPTH; choice++) {
|
|
767
788
|
let target = null;
|
|
@@ -781,15 +802,19 @@ export class MidstreamGuard {
|
|
|
781
802
|
continue;
|
|
782
803
|
}
|
|
783
804
|
this.o.end();
|
|
784
|
-
|
|
805
|
+
this.continuedBy = target.label;
|
|
806
|
+
this.outcome = outcome === 'finished' ? 'continued' : 'continued-unfinished';
|
|
807
|
+
return this.outcome;
|
|
785
808
|
}
|
|
786
809
|
if (tried === 0) {
|
|
787
810
|
this.log(`#${this.o.requestNo} stream died after ${partial.length} chars — no continuation target (set --pool-fallback with an entry for the other provider)`);
|
|
788
811
|
cleanEnd();
|
|
789
|
-
|
|
812
|
+
this.outcome = 'no-target';
|
|
813
|
+
return this.outcome;
|
|
790
814
|
}
|
|
791
815
|
cleanEnd();
|
|
792
|
-
|
|
816
|
+
this.outcome = 'resume-failed';
|
|
817
|
+
return this.outcome;
|
|
793
818
|
}
|
|
794
819
|
/**
|
|
795
820
|
* 'failed': nothing of the resume reached the client — the site ends the
|
|
@@ -817,7 +842,7 @@ export class MidstreamGuard {
|
|
|
817
842
|
r.onBeforeResume?.();
|
|
818
843
|
const res = await fetchImpl(`${r.loopbackBase}${path}`, {
|
|
819
844
|
method: 'POST',
|
|
820
|
-
headers: { 'content-type': 'application/json', [CONTINUATION_HEADER]: String((this.o.depth ?? 0) + 1), ...r.loopbackHeaders },
|
|
845
|
+
headers: { 'content-type': 'application/json', [CONTINUATION_HEADER]: String((this.o.depth ?? 0) + 1), [CONTINUATION_OF_HEADER]: String(this.o.requestNo), ...r.loopbackHeaders },
|
|
821
846
|
body: JSON.stringify(body),
|
|
822
847
|
signal: abort.signal,
|
|
823
848
|
});
|
package/dist/proxy.d.ts
CHANGED
|
@@ -586,6 +586,15 @@ export interface ProxyLogEntry {
|
|
|
586
586
|
client?: string;
|
|
587
587
|
preserve_tools?: boolean;
|
|
588
588
|
stream?: boolean;
|
|
589
|
+
/** Mid-stream continuation outcome, when this request's guard acted (see analytics RequestContinuation). */
|
|
590
|
+
continued?: 'continued' | 'continued-unfinished' | 'resume-failed' | 'no-target';
|
|
591
|
+
continued_by?: string;
|
|
592
|
+
/** Characters the client already had when the stream died. */
|
|
593
|
+
continued_after?: number;
|
|
594
|
+
/** On a resume leg: how deep it sits (1 = resume of a client request, 2 = resume of a resume). */
|
|
595
|
+
continuation_depth?: number;
|
|
596
|
+
/** On a resume leg: the request number whose stream it resumes (the guard's number). */
|
|
597
|
+
continuation_of?: number;
|
|
589
598
|
reject?: string;
|
|
590
599
|
error?: string;
|
|
591
600
|
event?: string;
|
package/dist/proxy.js
CHANGED
|
@@ -27,7 +27,19 @@ import { createTokenBucket } from './rate-limit.js';
|
|
|
27
27
|
import { getOpenAIBackend, isOpenAIModel, forwardToOpenAI } from './openai-backend.js';
|
|
28
28
|
import { forwardToCodex, forwardResponsesToCodex, getCodexModelSlugs, peekCodexModelSlugs, isCodexModel, pickCodexFallback, pickClaudeTarget, CODEX_BACKEND_BASE_URL } from './codex-backend.js';
|
|
29
29
|
import { effortForCodex } from './effort.js';
|
|
30
|
-
import { MidstreamGuard, guardFor, loopbackBaseFor, chaosCutFetch, chaosCutState, CONTINUATION_HEADER, MAX_CONTINUATION_DEPTH, continuationDepth } from './midstream.js';
|
|
30
|
+
import { MidstreamGuard, guardFor, loopbackBaseFor, chaosCutFetch, chaosCutState, CONTINUATION_HEADER, CONTINUATION_OF_HEADER, MAX_CONTINUATION_DEPTH, continuationDepth, continuationOfRequest } from './midstream.js';
|
|
31
|
+
/**
|
|
32
|
+
* The continuation fields for a request's analytics row, from its guard.
|
|
33
|
+
* Only the client's own request (depth 0) carries them: a resume leg is a
|
|
34
|
+
* loopback request with a guard of its own, and counting its attempt too
|
|
35
|
+
* would show one dying client stream as two. The log line still carries
|
|
36
|
+
* every leg's outcome — that is where the hop-by-hop story is read.
|
|
37
|
+
*/
|
|
38
|
+
function continuationOf(guard, depth) {
|
|
39
|
+
if (!guard || !guard.outcome || depth > 0)
|
|
40
|
+
return undefined;
|
|
41
|
+
return { outcome: guard.outcome, ...(guard.continuedBy ? { by: guard.continuedBy } : {}), partialChars: guard.partialChars };
|
|
42
|
+
}
|
|
31
43
|
import { responsesRequestToAnthropic, unsupportedOnClaudeError, ResponsesRequestError, ResponsesOut, wrapResponsesClient } from './responses-inbound.js';
|
|
32
44
|
import { isClaudeServableModel } from './claude-model.js';
|
|
33
45
|
import { MODEL_UNROUTABLE } from './upstream-rejection.js';
|
|
@@ -3129,6 +3141,12 @@ export async function startProxy(opts = {}) {
|
|
|
3129
3141
|
// never continued itself (MAX_CONTINUATION_DEPTH).
|
|
3130
3142
|
const requestDepth = continuationDepth(req.headers[CONTINUATION_HEADER]);
|
|
3131
3143
|
const isContinuation = requestDepth >= MAX_CONTINUATION_DEPTH;
|
|
3144
|
+
// A resume leg's log row points back at the request whose stream died
|
|
3145
|
+
// (its guard's number) and says how deep it sits, so the hop-by-hop
|
|
3146
|
+
// story is readable from the log alone. Empty on a client request.
|
|
3147
|
+
const continuationLeg = requestDepth > 0
|
|
3148
|
+
? { continuation_depth: requestDepth, continuation_of: continuationOfRequest(req.headers[CONTINUATION_OF_HEADER]) }
|
|
3149
|
+
: {};
|
|
3132
3150
|
/**
|
|
3133
3151
|
* First hop: the SAME model again, through the front door. The pool
|
|
3134
3152
|
* picks a seat (sticky binding keeps the prompt cache warm), and if the
|
|
@@ -3650,6 +3668,7 @@ export async function startProxy(opts = {}) {
|
|
|
3650
3668
|
// overage guard (#288) leaves it alone.
|
|
3651
3669
|
claim: CODEX_CLAIM, util5h: 0, util7d: 0, overageUtil: 0,
|
|
3652
3670
|
latencyMs: o.latencyMs, status: o.status, isStream: o.stream, isOpenAI,
|
|
3671
|
+
continuation: continuationOf(codexGuard, requestDepth),
|
|
3653
3672
|
});
|
|
3654
3673
|
writeLogLine(logFileStream, {
|
|
3655
3674
|
ts: new Date().toISOString(), req: codexReq,
|
|
@@ -3657,6 +3676,8 @@ export async function startProxy(opts = {}) {
|
|
|
3657
3676
|
status: o.status, latency_ms: o.latencyMs, in_tokens: o.inputTokens, out_tokens: o.outputTokens,
|
|
3658
3677
|
cache_read: o.cacheReadTokens, cache_create: o.cacheCreateTokens,
|
|
3659
3678
|
claim: CODEX_CLAIM, bucket: 'subscription', account: o.alias, consumer, stream: o.stream,
|
|
3679
|
+
...(codexGuard?.outcome ? { continued: codexGuard.outcome, continued_by: codexGuard.continuedBy ?? undefined, continued_after: codexGuard.partialChars } : {}),
|
|
3680
|
+
...continuationLeg,
|
|
3660
3681
|
});
|
|
3661
3682
|
if (verbose)
|
|
3662
3683
|
console.log(formatUsageLogLine(codexReq, {
|
|
@@ -5182,6 +5203,7 @@ export async function startProxy(opts = {}) {
|
|
|
5182
5203
|
thinkingTokens: Math.round(streamThinkingChars / 4),
|
|
5183
5204
|
claim: rl.claim, util5h: rl.util5h, util7d: rl.util7d, overageUtil: rl.overageUtil,
|
|
5184
5205
|
latencyMs: Date.now() - startTime, status: upstream.status, isStream: true, isOpenAI,
|
|
5206
|
+
continuation: continuationOf(guard, requestDepth),
|
|
5185
5207
|
});
|
|
5186
5208
|
}
|
|
5187
5209
|
writeLogLine(logFileStream, {
|
|
@@ -5198,6 +5220,8 @@ export async function startProxy(opts = {}) {
|
|
|
5198
5220
|
client: detectedClientForLog,
|
|
5199
5221
|
preserve_tools: preserveToolsEffective,
|
|
5200
5222
|
stream: true,
|
|
5223
|
+
...(guard?.outcome ? { continued: guard.outcome, continued_by: guard.continuedBy ?? undefined, continued_after: guard.partialChars } : {}),
|
|
5224
|
+
...continuationLeg,
|
|
5201
5225
|
});
|
|
5202
5226
|
if (verbose)
|
|
5203
5227
|
console.log(formatUsageLogLine(requestCount, {
|
|
@@ -161,6 +161,32 @@ its own answer; the other subscription takes over only when that model cannot
|
|
|
161
161
|
serve the resume. dario warns loudly at startup while the tap is set; it is a
|
|
162
162
|
demo and test affordance, never a default.
|
|
163
163
|
|
|
164
|
+
## Seeing it after the fact
|
|
165
|
+
|
|
166
|
+
A continuation leaves three traces. The SSE comment on the wire
|
|
167
|
+
(`: dario continuation gpt-5.6-terra (codex live) after 1204 chars`) is the
|
|
168
|
+
one a raw capture shows; every SSE parser ignores it. The request log
|
|
169
|
+
(`--log-file`) tells the hop-by-hop story: the row of a request whose stream
|
|
170
|
+
died carries `continued` (`continued`, `continued-unfinished`,
|
|
171
|
+
`resume-failed`, `no-target`), `continued_by` (the leg that served the rest)
|
|
172
|
+
and `continued_after` (characters the client already had); every resume leg
|
|
173
|
+
has a row of its own with `continuation_depth` (1, or 2 for the resume of a
|
|
174
|
+
resume) and `continuation_of`, the number of the request it resumed — and,
|
|
175
|
+
when it died too, its own `continued_*` fields. A two-hop resume is three
|
|
176
|
+
rows that point at each other. `/analytics` tallies per window under
|
|
177
|
+
`continuations`: `attempted`, split into `finished`, `unfinished`, `failed`
|
|
178
|
+
and `noTarget`. Only the client's own request counts there — a resume leg is
|
|
179
|
+
a loopback request with a guard of its own, and counting its attempt too
|
|
180
|
+
would show one dying stream as two. `dario usage` prints the tally as one
|
|
181
|
+
line when anything died:
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
Continuations: 3 streams died mid-answer: 2 finished, 1 unfinished
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`noTarget` above zero is the line to act on: streams are dying and there is
|
|
188
|
+
no `--pool-fallback` entry for the other provider to finish them.
|
|
189
|
+
|
|
164
190
|
## How it was proven
|
|
165
191
|
|
|
166
192
|
`test/midstream-continuation-wiring.mjs` runs a real proxy against a fake
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "6.6.
|
|
3
|
+
"version": "6.6.1",
|
|
4
4
|
"description": "Use your Claude and ChatGPT subscriptions in Cursor, Cline, Aider, Claude Code and the Agent SDK — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint: either plan answers either wire shape, with automatic failover when one hits its limit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|