aweshare 0.5.5 → 0.5.7
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/README.ai.md +7 -7
- package/README.md +26 -11
- package/apps/agent/dist/config.d.ts +1 -1
- package/apps/agent/dist/config.js +2 -0
- package/apps/agent/dist/config.js.map +1 -1
- package/apps/agent/dist/connection.js +9 -4
- package/apps/agent/dist/connection.js.map +1 -1
- package/apps/agent/dist/doctor.js +55 -19
- package/apps/agent/dist/doctor.js.map +1 -1
- package/apps/agent/dist/table.d.ts +19 -2
- package/apps/agent/dist/table.js +28 -3
- package/apps/agent/dist/table.js.map +1 -1
- package/apps/consumer/dist/cli.js +1 -1
- package/apps/consumer/dist/cli.js.map +1 -1
- package/apps/consumer/dist/table.d.ts +17 -2
- package/apps/consumer/dist/table.js +18 -3
- package/apps/consumer/dist/table.js.map +1 -1
- package/apps/hub/dist/admin.d.ts +2 -0
- package/apps/hub/dist/admin.js +33 -1
- package/apps/hub/dist/admin.js.map +1 -1
- package/apps/hub/dist/cli.js +170 -124
- package/apps/hub/dist/cli.js.map +1 -1
- package/apps/hub/dist/config.d.ts +6 -1
- package/apps/hub/dist/config.js +42 -1
- package/apps/hub/dist/config.js.map +1 -1
- package/apps/hub/dist/consumer.d.ts +34 -3
- package/apps/hub/dist/consumer.js +100 -8
- package/apps/hub/dist/consumer.js.map +1 -1
- package/apps/hub/dist/db.d.ts +38 -2
- package/apps/hub/dist/db.js +80 -8
- package/apps/hub/dist/db.js.map +1 -1
- package/apps/hub/dist/local.d.ts +12 -2
- package/apps/hub/dist/local.js +26 -7
- package/apps/hub/dist/local.js.map +1 -1
- package/apps/hub/dist/meter.d.ts +6 -1
- package/apps/hub/dist/meter.js +14 -0
- package/apps/hub/dist/meter.js.map +1 -1
- package/apps/hub/dist/server.js +4 -2
- package/apps/hub/dist/server.js.map +1 -1
- package/apps/hub/dist/table.d.ts +20 -3
- package/apps/hub/dist/table.js +35 -10
- package/apps/hub/dist/table.js.map +1 -1
- package/package.json +2 -1
- package/packages/producer-core/dist/adapters.d.ts +3 -6
- package/packages/producer-core/dist/adapters.js +8 -7
- package/packages/producer-core/dist/adapters.js.map +1 -1
- package/packages/producer-core/dist/health.d.ts +25 -2
- package/packages/producer-core/dist/health.js +77 -4
- package/packages/producer-core/dist/health.js.map +1 -1
- package/packages/producer-core/dist/index.d.ts +1 -0
- package/packages/producer-core/dist/index.js +1 -0
- package/packages/producer-core/dist/index.js.map +1 -1
- package/packages/producer-core/dist/job.js +9 -8
- package/packages/producer-core/dist/job.js.map +1 -1
- package/packages/producer-core/dist/proxy.d.ts +21 -0
- package/packages/producer-core/dist/proxy.js +77 -0
- package/packages/producer-core/dist/proxy.js.map +1 -0
- package/packages/protocol/dist/index.d.ts +1 -0
- package/packages/protocol/dist/index.js +1 -0
- package/packages/protocol/dist/index.js.map +1 -1
- package/packages/protocol/dist/models.d.ts +23 -0
- package/packages/protocol/dist/models.js +72 -0
- package/packages/protocol/dist/models.js.map +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapters.js","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAqBvD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAwB,EACxB,GAA4D;IAE5D,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAChD,2FAA2F;IAC3F,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;IAC5D,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,GAAG,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,CAAA;IAC7C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;QAC3D,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IAClC,CAAC;IAED,MAAM,OAAO,GAA2B,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,CAAA;IAC1D,gBAAgB,CAAC,OAAO,CAAC,CAAA;IACzB,IAAI,OAAO,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;QAC9B,0EAA0E;QAC1E,uEAAuE;QACvE,0DAA0D;QAC1D,OAAO,OAAO,CAAC,aAAa,CAAA;QAC5B,OAAO,OAAO,CAAC,WAAW,CAAC,CAAA;QAC3B,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,oBAAoB,EAAE,CAAA;QACzD,OAAO,CAAC,aAAa,GAAG,UAAU,WAAW,EAAE,CAAA;QAC/C,OAAO,CAAC,oBAAoB,CAAC,GAAG,SAAS,CAAA;QACzC,OAAO,CAAC,aAAa,CAAC,GAAG,wBAAwB,CAAA;QACjD,OAAO,CAAC,UAAU,GAAG,cAAc,CAAA;IACrC,CAAC;SAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;QAC5C,IAAI,OAAO,CAAC,MAAM;YAAE,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,MAAM,CAAA;QACzD,OAAO,CAAC,mBAAmB,CAAC,GAAG,OAAO,CAAC,mBAAmB,CAAC,IAAI,YAAY,CAAA;IAC7E,CAAC;SAAM,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QAC1B,OAAO,CAAC,aAAa,GAAG,UAAU,OAAO,CAAC,MAAM,EAAE,CAAA;IACpD,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAA;AACzC,CAAC;AAED,mFAAmF;AACnF,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,gBAAgB,EAAE,WAAW,CAAC,CAAC,CAAA;AAE1E;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,OAA+B;IACvD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAA;QAC/B,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;YAC5F,OAAO,OAAO,CAAC,GAAG,CAAC,CAAA;QACrB,CAAC;IACH,CAAC;AACH,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"adapters.js","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,15 +1,38 @@
|
|
|
1
1
|
import type { BackendStatus } from 'aweshare-protocol';
|
|
2
|
+
import type { Logger } from 'pino';
|
|
2
3
|
export type FailureReason = 'AUTH' | 'QUOTA' | 'HTTP' | 'NETWORK' | 'CANCELLED';
|
|
4
|
+
/** Ladder transition a record() caused, for logging: entering degraded,
|
|
5
|
+
* clearing it, or nothing. */
|
|
6
|
+
export type HealthTransition = 'degraded' | 'recovered' | null;
|
|
3
7
|
/**
|
|
4
8
|
* Per-backend health state. Degraded backends are reported in heartbeats and
|
|
5
|
-
* the hub stops dispatching to their offerings until they recover.
|
|
9
|
+
* the hub stops dispatching to their offerings until they recover. The
|
|
10
|
+
* ladder is two rungs — healthy -> degraded, latched until a success — which
|
|
11
|
+
* for a relay is what awewarm's deeper ladder collapses to: its intermediate
|
|
12
|
+
* rungs trade probe expense against recovery speed, and a relay's probes are
|
|
13
|
+
* cheap pings, not quota-consuming activations. What does carry over from
|
|
14
|
+
* awewarm is the escalating probe schedule above: the longer a backend has
|
|
15
|
+
* been continuously degraded, the quieter its recovery probes become, down
|
|
16
|
+
* to a daily last gasp that never fully gives up.
|
|
6
17
|
*/
|
|
7
18
|
export declare class HealthGate {
|
|
19
|
+
private readonly now;
|
|
8
20
|
private consecutive;
|
|
9
21
|
private degraded;
|
|
10
|
-
|
|
22
|
+
private degradedSince;
|
|
23
|
+
private probeDelayMs;
|
|
24
|
+
private probeNotBefore;
|
|
25
|
+
/** The clock is injected so tests can walk the cap ladder without waiting. */
|
|
26
|
+
constructor(now?: () => number);
|
|
27
|
+
record(backendId: string, ok: boolean, reason?: FailureReason): HealthTransition;
|
|
28
|
+
/** Epoch ms before which a recovery probe for this backend should be
|
|
29
|
+
* skipped (backoff); 0 when no failed probe is being held back. */
|
|
30
|
+
probeNotBeforeMs(backendId: string): number;
|
|
11
31
|
isDegraded(backendId: string): boolean;
|
|
12
32
|
statusMap(): Record<string, BackendStatus>;
|
|
13
33
|
}
|
|
34
|
+
/** Log a ladder transition where it happened — the request path or a probe
|
|
35
|
+
* loop — so "why is it still degraded" is answerable from the logs. */
|
|
36
|
+
export declare function logHealthTransition(log: Logger, backendId: string, transition: HealthTransition, detail?: string): void;
|
|
14
37
|
/** Map an upstream HTTP status to a failure class (ok responses pass null). */
|
|
15
38
|
export declare function classifyStatus(status: number): FailureReason | null;
|
|
@@ -1,26 +1,89 @@
|
|
|
1
1
|
/** AUTH/QUOTA consecutive failures >= threshold -> degraded until a success. */
|
|
2
2
|
const DEGRADE_THRESHOLD = 2;
|
|
3
|
+
// Recovery-probe backoff, awewarm-style: while degraded, each failed probe
|
|
4
|
+
// doubles the wait before the next one, so an exhausted quota isn't pinged at
|
|
5
|
+
// the base cadence for hours on end. Any success clears the degraded state
|
|
6
|
+
// and the backoff with it. Unlike awewarm — whose probes are quota-consuming
|
|
7
|
+
// activations that must back off a full window — a relay's probes are free
|
|
8
|
+
// pings, so the cap escalates with how long the backend has stayed degraded
|
|
9
|
+
// rather than jumping straight to window-length silence:
|
|
10
|
+
// < 15h cap 5min covers ~3 GLM 5h-window rolls: refresh picked up fast
|
|
11
|
+
// < 45h cap 1h not a window roll anymore — likely needs a human
|
|
12
|
+
// < 105h cap 6h long dead; quiet but still self-healing
|
|
13
|
+
// else cap 24h daily last gasp, keeps auto-recovery alive forever
|
|
14
|
+
const PROBE_BACKOFF_BASE_MS = 30_000;
|
|
15
|
+
const PROBE_FALLBACK_CAP_MS = 24 * 3_600_000;
|
|
16
|
+
const PROBE_CAP_SCHEDULE = [
|
|
17
|
+
{ afterMs: 15 * 3_600_000, capMs: 5 * 60_000 },
|
|
18
|
+
{ afterMs: 45 * 3_600_000, capMs: 3_600_000 },
|
|
19
|
+
{ afterMs: 105 * 3_600_000, capMs: 6 * 3_600_000 },
|
|
20
|
+
{ afterMs: Number.POSITIVE_INFINITY, capMs: PROBE_FALLBACK_CAP_MS },
|
|
21
|
+
];
|
|
3
22
|
/**
|
|
4
23
|
* Per-backend health state. Degraded backends are reported in heartbeats and
|
|
5
|
-
* the hub stops dispatching to their offerings until they recover.
|
|
24
|
+
* the hub stops dispatching to their offerings until they recover. The
|
|
25
|
+
* ladder is two rungs — healthy -> degraded, latched until a success — which
|
|
26
|
+
* for a relay is what awewarm's deeper ladder collapses to: its intermediate
|
|
27
|
+
* rungs trade probe expense against recovery speed, and a relay's probes are
|
|
28
|
+
* cheap pings, not quota-consuming activations. What does carry over from
|
|
29
|
+
* awewarm is the escalating probe schedule above: the longer a backend has
|
|
30
|
+
* been continuously degraded, the quieter its recovery probes become, down
|
|
31
|
+
* to a daily last gasp that never fully gives up.
|
|
6
32
|
*/
|
|
7
33
|
export class HealthGate {
|
|
34
|
+
now;
|
|
8
35
|
consecutive = new Map();
|
|
9
36
|
degraded = new Map();
|
|
37
|
+
degradedSince = new Map();
|
|
38
|
+
probeDelayMs = new Map();
|
|
39
|
+
probeNotBefore = new Map();
|
|
40
|
+
/** The clock is injected so tests can walk the cap ladder without waiting. */
|
|
41
|
+
constructor(now = Date.now) {
|
|
42
|
+
this.now = now;
|
|
43
|
+
}
|
|
10
44
|
record(backendId, ok, reason) {
|
|
11
45
|
if (ok) {
|
|
12
46
|
this.consecutive.delete(backendId);
|
|
13
|
-
this.degraded.delete(backendId);
|
|
14
|
-
|
|
47
|
+
const wasDegraded = this.degraded.delete(backendId);
|
|
48
|
+
this.degradedSince.delete(backendId);
|
|
49
|
+
this.probeDelayMs.delete(backendId);
|
|
50
|
+
this.probeNotBefore.delete(backendId);
|
|
51
|
+
return wasDegraded ? 'recovered' : null;
|
|
15
52
|
}
|
|
16
53
|
if (reason === undefined || reason === 'CANCELLED')
|
|
17
|
-
return;
|
|
54
|
+
return null;
|
|
55
|
+
// failures recorded while already degraded are probe outcomes (admission
|
|
56
|
+
// 503s before dialing, so real traffic can't add any) — back the next
|
|
57
|
+
// probe off. The failure that causes the degradation does not: the first
|
|
58
|
+
// probe should stay immediate so a just-refreshed quota or a fresh login
|
|
59
|
+
// is picked up on the next tick.
|
|
60
|
+
const wasDegraded = this.degraded.has(backendId);
|
|
18
61
|
const current = this.consecutive.get(backendId);
|
|
19
62
|
const count = current?.reason === reason ? current.count + 1 : 1;
|
|
20
63
|
this.consecutive.set(backendId, { reason, count });
|
|
64
|
+
let transition = null;
|
|
21
65
|
if ((reason === 'AUTH' || reason === 'QUOTA') && count >= DEGRADE_THRESHOLD) {
|
|
66
|
+
if (!wasDegraded) {
|
|
67
|
+
transition = 'degraded';
|
|
68
|
+
this.degradedSince.set(backendId, this.now());
|
|
69
|
+
}
|
|
22
70
|
this.degraded.set(backendId, { state: 'degraded', reason });
|
|
23
71
|
}
|
|
72
|
+
if (wasDegraded) {
|
|
73
|
+
const since = this.degradedSince.get(backendId) ?? this.now();
|
|
74
|
+
// The final tier is Infinity, so the fallback is only a type-safe guard.
|
|
75
|
+
const cap = PROBE_CAP_SCHEDULE.find((tier) => this.now() - since < tier.afterMs)?.capMs ??
|
|
76
|
+
PROBE_FALLBACK_CAP_MS;
|
|
77
|
+
const delay = Math.min((this.probeDelayMs.get(backendId) ?? PROBE_BACKOFF_BASE_MS / 2) * 2, cap);
|
|
78
|
+
this.probeDelayMs.set(backendId, delay);
|
|
79
|
+
this.probeNotBefore.set(backendId, this.now() + delay);
|
|
80
|
+
}
|
|
81
|
+
return transition;
|
|
82
|
+
}
|
|
83
|
+
/** Epoch ms before which a recovery probe for this backend should be
|
|
84
|
+
* skipped (backoff); 0 when no failed probe is being held back. */
|
|
85
|
+
probeNotBeforeMs(backendId) {
|
|
86
|
+
return this.probeNotBefore.get(backendId) ?? 0;
|
|
24
87
|
}
|
|
25
88
|
isDegraded(backendId) {
|
|
26
89
|
return this.degraded.has(backendId);
|
|
@@ -32,6 +95,16 @@ export class HealthGate {
|
|
|
32
95
|
return out;
|
|
33
96
|
}
|
|
34
97
|
}
|
|
98
|
+
/** Log a ladder transition where it happened — the request path or a probe
|
|
99
|
+
* loop — so "why is it still degraded" is answerable from the logs. */
|
|
100
|
+
export function logHealthTransition(log, backendId, transition, detail) {
|
|
101
|
+
if (transition === 'degraded') {
|
|
102
|
+
log.warn(`backend ${backendId} degraded${detail ? ` (${detail})` : ''}`);
|
|
103
|
+
}
|
|
104
|
+
else if (transition === 'recovered') {
|
|
105
|
+
log.info(`backend ${backendId} recovered${detail ? ` — ${detail}` : ''}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
35
108
|
/** Map an upstream HTTP status to a failure class (ok responses pass null). */
|
|
36
109
|
export function classifyStatus(status) {
|
|
37
110
|
if (status < 400)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"health.js","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"AASA,gFAAgF;AAChF,MAAM,iBAAiB,GAAG,CAAC,CAAA;AAE3B,2EAA2E;AAC3E,8EAA8E;AAC9E,2EAA2E;AAC3E,6EAA6E;AAC7E,2EAA2E;AAC3E,4EAA4E;AAC5E,yDAAyD;AACzD,4EAA4E;AAC5E,uEAAuE;AACvE,8DAA8D;AAC9D,yEAAyE;AACzE,MAAM,qBAAqB,GAAG,MAAM,CAAA;AACpC,MAAM,qBAAqB,GAAG,EAAE,GAAG,SAAS,CAAA;AAC5C,MAAM,kBAAkB,GAAsD;IAC5E,EAAE,OAAO,EAAE,EAAE,GAAG,SAAS,EAAE,KAAK,EAAE,CAAC,GAAG,MAAM,EAAE;IAC9C,EAAE,OAAO,EAAE,EAAE,GAAG,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;IAC7C,EAAE,OAAO,EAAE,GAAG,GAAG,SAAS,EAAE,KAAK,EAAE,CAAC,GAAG,SAAS,EAAE;IAClD,EAAE,OAAO,EAAE,MAAM,CAAC,iBAAiB,EAAE,KAAK,EAAE,qBAAqB,EAAE;CACpE,CAAA;AAED;;;;;;;;;;GAUG;AACH,MAAM,OAAO,UAAU;IAQQ;IAPrB,WAAW,GAAG,IAAI,GAAG,EAAoD,CAAA;IACzE,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAA;IAC3C,aAAa,GAAG,IAAI,GAAG,EAAkB,CAAA;IACzC,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAA;IACxC,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAA;IAElD,8EAA8E;IAC9E,YAA6B,MAAoB,IAAI,CAAC,GAAG;QAA5B,QAAG,GAAH,GAAG,CAAyB;IAAG,CAAC;IAE7D,MAAM,CAAC,SAAiB,EAAE,EAAW,EAAE,MAAsB;QAC3D,IAAI,EAAE,EAAE,CAAC;YACP,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;YAClC,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;YACnD,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;YACpC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;YACnC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;YACrC,OAAO,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAA;QACzC,CAAC;QACD,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,WAAW;YAAE,OAAO,IAAI,CAAA;QAC/D,yEAAyE;QACzE,sEAAsE;QACtE,yEAAyE;QACzE,yEAAyE;QACzE,iCAAiC;QACjC,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QAChD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QAC/C,MAAM,KAAK,GAAG,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAChE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;QAClD,IAAI,UAAU,GAAqB,IAAI,CAAA;QACvC,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,OAAO,CAAC,IAAI,KAAK,IAAI,iBAAiB,EAAE,CAAC;YAC5E,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,UAAU,GAAG,UAAU,CAAA;gBACvB,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;YAC/C,CAAC;YACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAA;QAC7D,CAAC;QACD,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,CAAA;YAC7D,yEAAyE;YACzE,MAAM,GAAG,GACP,kBAAkB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK;gBAC3E,qBAAqB,CAAA;YACvB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,qBAAqB,GAAG,CAAC,CAAC,GAAG,CAAC,EACnE,GAAG,CACJ,CAAA;YACD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;YACvC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAA;QACxD,CAAC;QACD,OAAO,UAAU,CAAA;IACnB,CAAC;IAED;uEACmE;IACnE,gBAAgB,CAAC,SAAiB;QAChC,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IAChD,CAAC;IAED,UAAU,CAAC,SAAiB;QAC1B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACrC,CAAC;IAED,SAAS;QACP,MAAM,GAAG,GAAkC,EAAE,CAAA;QAC7C,KAAK,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ;YAAE,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAA;QAC1D,OAAO,GAAG,CAAA;IACZ,CAAC;CACF;AAED;uEACuE;AACvE,MAAM,UAAU,mBAAmB,CACjC,GAAW,EACX,SAAiB,EACjB,UAA4B,EAC5B,MAAe;IAEf,IAAI,UAAU,KAAK,UAAU,EAAE,CAAC;QAC9B,GAAG,CAAC,IAAI,CAAC,WAAW,SAAS,YAAY,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IAC1E,CAAC;SAAM,IAAI,UAAU,KAAK,WAAW,EAAE,CAAC;QACtC,GAAG,CAAC,IAAI,CAAC,WAAW,SAAS,aAAa,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IAC3E,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,IAAI,MAAM,GAAG,GAAG;QAAE,OAAO,IAAI,CAAA;IAC7B,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,MAAM,CAAA;IACnD,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,OAAO,CAAA;IACpD,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { buildUpstreamRequest,
|
|
1
|
+
import { buildUpstreamRequest, prepareCodexBody, } from './adapters.js';
|
|
2
2
|
import { CredentialError, invalidateCodexCredentials } from './credentials.js';
|
|
3
|
-
import { classifyStatus } from './health.js';
|
|
3
|
+
import { classifyStatus, logHealthTransition } from './health.js';
|
|
4
|
+
import { codexNetworkHint, fetchUpstream } from './proxy.js';
|
|
4
5
|
/** Thrown by a sink whose transport died (e.g. a closed tunnel socket): the
|
|
5
6
|
* job stops silently — no health record, no error frame nobody would read. */
|
|
6
7
|
export class SinkClosedError extends Error {
|
|
@@ -40,8 +41,8 @@ export class UpstreamJob {
|
|
|
40
41
|
// inside the try: a malformed URL must flow to sink.error like any other
|
|
41
42
|
// failure — execute() must never reject
|
|
42
43
|
const { url, headers } = buildUpstreamRequest(backend, init);
|
|
43
|
-
const body = backend.login !== undefined ?
|
|
44
|
-
const resp = await
|
|
44
|
+
const body = backend.login !== undefined ? prepareCodexBody(init.body) : init.body;
|
|
45
|
+
const resp = await fetchUpstream(backend, url, {
|
|
45
46
|
method: init.method,
|
|
46
47
|
headers,
|
|
47
48
|
// GET/HEAD must carry no body, even an empty one
|
|
@@ -52,7 +53,7 @@ export class UpstreamJob {
|
|
|
52
53
|
clearTimeout(connectTimer);
|
|
53
54
|
log.debug(`request ${id} upstream ${backend.id} -> ${resp.status}`);
|
|
54
55
|
const reason = classifyStatus(resp.status);
|
|
55
|
-
health.record(backend.id, reason === null, reason ?? undefined);
|
|
56
|
+
logHealthTransition(log, backend.id, health.record(backend.id, reason === null, reason ?? undefined), `upstream ${resp.status}`);
|
|
56
57
|
// a 401 on a login backend means the token rotated or expired; drop the
|
|
57
58
|
// cached login so the next request re-reads auth.json
|
|
58
59
|
if (resp.status === 401 && backend.login !== undefined)
|
|
@@ -80,14 +81,14 @@ export class UpstreamJob {
|
|
|
80
81
|
// an unreadable login is an auth failure, not a network one: two in a
|
|
81
82
|
// row degrade the backend like any other AUTH loss
|
|
82
83
|
log.warn(`request ${id} upstream ${backend.id}: ${err.message}`);
|
|
83
|
-
health.record(backend.id, false, 'AUTH');
|
|
84
|
+
logHealthTransition(log, backend.id, health.record(backend.id, false, 'AUTH'));
|
|
84
85
|
sink.error('BACKEND_LOGIN', err.message);
|
|
85
86
|
return;
|
|
86
87
|
}
|
|
87
88
|
const timedOut = err instanceof ConnectTimeoutError;
|
|
88
89
|
log.warn(`request ${id} upstream ${backend.id} failed: ${err instanceof Error ? err.message : err}`);
|
|
89
|
-
health.record(backend.id, false, 'NETWORK');
|
|
90
|
-
sink.error(timedOut ? 'BACKEND_TIMEOUT' : 'BACKEND_UNREACHABLE', `upstream ${backend.id} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
90
|
+
logHealthTransition(log, backend.id, health.record(backend.id, false, 'NETWORK'), err instanceof Error ? err.message : undefined);
|
|
91
|
+
sink.error(timedOut ? 'BACKEND_TIMEOUT' : 'BACKEND_UNREACHABLE', `upstream ${backend.id} failed: ${err instanceof Error ? err.message : String(err)}${codexNetworkHint(backend)}`);
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"job.js","sourceRoot":"","sources":["../src/job.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,
|
|
1
|
+
{"version":3,"file":"job.js","sourceRoot":"","sources":["../src/job.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ResolvedBackend } from './adapters.js';
|
|
2
|
+
export interface CodexProxyOptions {
|
|
3
|
+
httpProxy?: string;
|
|
4
|
+
httpsProxy?: string;
|
|
5
|
+
noProxy?: string;
|
|
6
|
+
source: 'HTTP_PROXY' | 'HTTPS_PROXY' | 'ALL_PROXY';
|
|
7
|
+
}
|
|
8
|
+
/** Proxy settings for the official Codex account upstream only.
|
|
9
|
+
*
|
|
10
|
+
* EnvHttpProxyAgent implements HTTP_PROXY/HTTPS_PROXY/NO_PROXY matching. We
|
|
11
|
+
* also accept ALL_PROXY because coding-agent proxy setups commonly provide it.
|
|
12
|
+
* SOCKS URLs are deliberately ignored: the producer has no SOCKS dependency,
|
|
13
|
+
* and silently treating one as HTTP would send account traffic incorrectly.
|
|
14
|
+
*/
|
|
15
|
+
export declare function codexProxyOptions(backend: Pick<ResolvedBackend, 'login'>, env?: NodeJS.ProcessEnv): CodexProxyOptions | null;
|
|
16
|
+
/** Fetch an upstream request, honoring proxy environment variables only for a
|
|
17
|
+
* producer machine's Codex subscription login. */
|
|
18
|
+
export declare function fetchUpstream(backend: Pick<ResolvedBackend, 'login'>, url: string, init: RequestInit): Promise<Response>;
|
|
19
|
+
/** A safe, actionable suffix for network errors. Never include proxy URLs:
|
|
20
|
+
* they can contain credentials. */
|
|
21
|
+
export declare function codexNetworkHint(backend: Pick<ResolvedBackend, 'login'>): string;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { EnvHttpProxyAgent } from 'undici';
|
|
2
|
+
let cachedRoute;
|
|
3
|
+
let cachedSignature;
|
|
4
|
+
function envValue(env, lower, upper) {
|
|
5
|
+
// Match the conventional lower-case precedence used by proxy-aware clients.
|
|
6
|
+
return env[lower] ?? env[upper];
|
|
7
|
+
}
|
|
8
|
+
function httpProxy(value) {
|
|
9
|
+
if (!value)
|
|
10
|
+
return undefined;
|
|
11
|
+
try {
|
|
12
|
+
const protocol = new URL(value).protocol;
|
|
13
|
+
return protocol === 'http:' || protocol === 'https:' ? value : undefined;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Proxy settings for the official Codex account upstream only.
|
|
20
|
+
*
|
|
21
|
+
* EnvHttpProxyAgent implements HTTP_PROXY/HTTPS_PROXY/NO_PROXY matching. We
|
|
22
|
+
* also accept ALL_PROXY because coding-agent proxy setups commonly provide it.
|
|
23
|
+
* SOCKS URLs are deliberately ignored: the producer has no SOCKS dependency,
|
|
24
|
+
* and silently treating one as HTTP would send account traffic incorrectly.
|
|
25
|
+
*/
|
|
26
|
+
export function codexProxyOptions(backend, env = process.env) {
|
|
27
|
+
if (backend.login !== 'codex')
|
|
28
|
+
return null;
|
|
29
|
+
const all = httpProxy(envValue(env, 'all_proxy', 'ALL_PROXY'));
|
|
30
|
+
const configuredHttp = httpProxy(envValue(env, 'http_proxy', 'HTTP_PROXY'));
|
|
31
|
+
const configuredHttps = httpProxy(envValue(env, 'https_proxy', 'HTTPS_PROXY'));
|
|
32
|
+
const http = configuredHttp ?? all;
|
|
33
|
+
const https = configuredHttps ?? all;
|
|
34
|
+
if (!http && !https)
|
|
35
|
+
return null;
|
|
36
|
+
const source = configuredHttps ? 'HTTPS_PROXY' : configuredHttp ? 'HTTP_PROXY' : 'ALL_PROXY';
|
|
37
|
+
return {
|
|
38
|
+
httpProxy: http,
|
|
39
|
+
httpsProxy: https,
|
|
40
|
+
noProxy: envValue(env, 'no_proxy', 'NO_PROXY'),
|
|
41
|
+
source,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function codexProxyRoute(backend) {
|
|
45
|
+
const options = codexProxyOptions(backend);
|
|
46
|
+
const signature = options ? JSON.stringify(options) : '';
|
|
47
|
+
if (cachedSignature === signature && cachedRoute !== undefined)
|
|
48
|
+
return cachedRoute;
|
|
49
|
+
cachedSignature = signature;
|
|
50
|
+
cachedRoute = options
|
|
51
|
+
? { dispatcher: new EnvHttpProxyAgent(options), source: options.source }
|
|
52
|
+
: null;
|
|
53
|
+
return cachedRoute;
|
|
54
|
+
}
|
|
55
|
+
/** Fetch an upstream request, honoring proxy environment variables only for a
|
|
56
|
+
* producer machine's Codex subscription login. */
|
|
57
|
+
export async function fetchUpstream(backend, url, init) {
|
|
58
|
+
const route = codexProxyRoute(backend);
|
|
59
|
+
if (!route)
|
|
60
|
+
return fetch(url, init);
|
|
61
|
+
// Node's built-in fetch honors undici dispatchers at runtime, but its DOM
|
|
62
|
+
// RequestInit declaration does not expose the undici extension yet. Keeping
|
|
63
|
+
// global fetch preserves the existing test seam and all non-proxy behavior.
|
|
64
|
+
const options = { ...init, dispatcher: route.dispatcher };
|
|
65
|
+
return fetch(url, options);
|
|
66
|
+
}
|
|
67
|
+
/** A safe, actionable suffix for network errors. Never include proxy URLs:
|
|
68
|
+
* they can contain credentials. */
|
|
69
|
+
export function codexNetworkHint(backend) {
|
|
70
|
+
if (backend.login !== 'codex')
|
|
71
|
+
return '';
|
|
72
|
+
const route = codexProxyRoute(backend);
|
|
73
|
+
return route
|
|
74
|
+
? ` (Codex account request used ${route.source}; check that its HTTP(S) proxy is reachable)`
|
|
75
|
+
: ' (Codex account request went direct; set HTTPS_PROXY, HTTP_PROXY, or ALL_PROXY if ChatGPT requires a proxy)';
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model honesty: an offering's declared `upstreamModel` is only the producer's
|
|
3
|
+
* claim. A response-reported model id is useful consistency evidence, not
|
|
4
|
+
* proof of the underlying weights. selfReportedModel extracts that bounded
|
|
5
|
+
* id and compareModels classifies it against the declaration.
|
|
6
|
+
*/
|
|
7
|
+
export type ModelComparison = 'exact' | 'compatible' | 'mismatch' | 'insufficient';
|
|
8
|
+
/**
|
|
9
|
+
* Compare response metadata with the declaration. Exact normalized ids and
|
|
10
|
+
* explicit revision suffixes are affirmative evidence; a less-specific
|
|
11
|
+
* response is insufficient, while adjacent named variants are mismatches.
|
|
12
|
+
*/
|
|
13
|
+
export declare function compareModels(declared: string, observed: string): ModelComparison;
|
|
14
|
+
/** Backward-compatible boolean used by existing renderers. Only affirmative
|
|
15
|
+
* evidence is a match; insufficient evidence is never shown as verified. */
|
|
16
|
+
export declare function modelsMatch(declared: string, observed: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* The model id a response names about itself in one payload,
|
|
19
|
+
* whichever wire shape carries it: OpenAI top-level `model`, Anthropic
|
|
20
|
+
* `message.model` (message_start), Responses `response.model`. Null when the
|
|
21
|
+
* payload names no model.
|
|
22
|
+
*/
|
|
23
|
+
export declare function selfReportedModel(payload: Record<string, unknown>): string | null;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model honesty: an offering's declared `upstreamModel` is only the producer's
|
|
3
|
+
* claim. A response-reported model id is useful consistency evidence, not
|
|
4
|
+
* proof of the underlying weights. selfReportedModel extracts that bounded
|
|
5
|
+
* id and compareModels classifies it against the declaration.
|
|
6
|
+
*/
|
|
7
|
+
/** Response-reported ids are persisted and rendered in operator terminals.
|
|
8
|
+
* Keep that untrusted value bounded and free of control characters. */
|
|
9
|
+
const safeModelId = (id) => id.length > 0 && id.length <= 256 && /^[a-z0-9][a-z0-9._:/-]*$/i.test(id);
|
|
10
|
+
/** Casing and separators vary across otherwise identical ids. Preserve token
|
|
11
|
+
* boundaries, though: deleting them would make gpt-4 and gpt-4o look alike. */
|
|
12
|
+
const canonicalModelId = (id) => id
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.replace(/[._:]+/g, '-')
|
|
15
|
+
.replace(/-+/g, '-')
|
|
16
|
+
.replace(/^-|-$/g, '');
|
|
17
|
+
/** A response may prefix the id with its vendor (`openai/gpt-4o`). Compare
|
|
18
|
+
* both the full id and the last path component; no arbitrary substring match. */
|
|
19
|
+
const candidates = (id) => {
|
|
20
|
+
if (!safeModelId(id))
|
|
21
|
+
return [];
|
|
22
|
+
const full = canonicalModelId(id);
|
|
23
|
+
const leaf = canonicalModelId(id.split('/').at(-1) ?? '');
|
|
24
|
+
return [...new Set([full, leaf].filter(Boolean))];
|
|
25
|
+
};
|
|
26
|
+
/** Explicit revision suffixes that preserve the model family: YYYY, MMDD,
|
|
27
|
+
* YYYYMMDD or a separated date. Names such as mini/o/sonnet are variants. */
|
|
28
|
+
const isRevisionOf = (family, versioned) => {
|
|
29
|
+
if (!versioned.startsWith(`${family}-`))
|
|
30
|
+
return false;
|
|
31
|
+
const suffix = versioned.slice(family.length + 1);
|
|
32
|
+
return /^\d{4,8}$/.test(suffix) || /^\d{4}-\d{2}(?:-\d{2})?$/.test(suffix);
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Compare response metadata with the declaration. Exact normalized ids and
|
|
36
|
+
* explicit revision suffixes are affirmative evidence; a less-specific
|
|
37
|
+
* response is insufficient, while adjacent named variants are mismatches.
|
|
38
|
+
*/
|
|
39
|
+
export function compareModels(declared, observed) {
|
|
40
|
+
const declaredIds = candidates(declared);
|
|
41
|
+
const observedIds = candidates(observed);
|
|
42
|
+
if (declaredIds.length === 0 || observedIds.length === 0)
|
|
43
|
+
return 'insufficient';
|
|
44
|
+
if (declaredIds.some((a) => observedIds.includes(a)))
|
|
45
|
+
return 'exact';
|
|
46
|
+
if (declaredIds.some((a) => observedIds.some((b) => isRevisionOf(a, b)))) {
|
|
47
|
+
return 'compatible';
|
|
48
|
+
}
|
|
49
|
+
if (observedIds.some((b) => declaredIds.some((a) => isRevisionOf(b, a)))) {
|
|
50
|
+
return 'insufficient';
|
|
51
|
+
}
|
|
52
|
+
return 'mismatch';
|
|
53
|
+
}
|
|
54
|
+
/** Backward-compatible boolean used by existing renderers. Only affirmative
|
|
55
|
+
* evidence is a match; insufficient evidence is never shown as verified. */
|
|
56
|
+
export function modelsMatch(declared, observed) {
|
|
57
|
+
const result = compareModels(declared, observed);
|
|
58
|
+
return result === 'exact' || result === 'compatible';
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The model id a response names about itself in one payload,
|
|
62
|
+
* whichever wire shape carries it: OpenAI top-level `model`, Anthropic
|
|
63
|
+
* `message.model` (message_start), Responses `response.model`. Null when the
|
|
64
|
+
* payload names no model.
|
|
65
|
+
*/
|
|
66
|
+
export function selfReportedModel(payload) {
|
|
67
|
+
const message = payload.message;
|
|
68
|
+
const response = payload.response;
|
|
69
|
+
const candidate = payload.model ?? message?.model ?? response?.model;
|
|
70
|
+
return typeof candidate === 'string' && safeModelId(candidate) ? candidate : null;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"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"}
|