@bitkyc08/opencodex 2.10.2 → 2.11.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/README.md +31 -0
- package/bin/ocx.mjs +10 -0
- package/gui/dist/assets/index-Bk-PN-70.css +1 -0
- package/gui/dist/assets/index-BynIEIV-.js +70 -0
- package/gui/dist/index.html +2 -2
- package/package.json +4 -2
- package/src/adapters/cursor/effort-map.ts +11 -0
- package/src/adapters/cursor/live-transport.ts +11 -0
- package/src/adapters/cursor/native-exec-fs.ts +9 -6
- package/src/adapters/cursor/native-exec.ts +4 -2
- package/src/adapters/cursor/protobuf-events.ts +176 -4
- package/src/adapters/cursor/request-builder.ts +15 -4
- package/src/adapters/cursor/tool-definitions.ts +118 -2
- package/src/adapters/google.ts +15 -5
- package/src/adapters/openai-chat.ts +24 -2
- package/src/adapters/openai-responses.ts +2 -1
- package/src/bridge.ts +9 -5
- package/src/chat/outbound.ts +4 -3
- package/src/claude/desktop-3p.ts +222 -2
- package/src/claude/outbound.ts +15 -6
- package/src/cli/account-api.ts +4 -0
- package/src/cli/account-extended.ts +112 -0
- package/src/cli/account.ts +23 -6
- package/src/cli/claude-desktop.ts +26 -3
- package/src/cli/config-command.ts +9 -0
- package/src/cli/help.ts +18 -2
- package/src/cli/index.ts +277 -55
- package/src/cli/models.ts +5 -1
- package/src/cli/provider.ts +8 -2
- package/src/cli/ready.ts +301 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/clients/config-export.ts +12 -19
- package/src/codex/account-lifecycle.ts +3 -0
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/auth-api.ts +83 -0
- package/src/codex/auth-context.ts +5 -2
- package/src/codex/catalog/provider-fetch.ts +11 -0
- package/src/codex/catalog/sync.ts +23 -1
- package/src/codex/codex-write-lock.ts +16 -4
- package/src/codex/desired-state.ts +37 -4
- package/src/codex/history-job.ts +15 -5
- package/src/codex/history-provider.ts +31 -14
- package/src/codex/history-worker.ts +28 -4
- package/src/codex/inject-coordination.ts +13 -1
- package/src/codex/inject.ts +360 -66
- package/src/codex/internal/history-writer.ts +1 -1
- package/src/codex/native-main-lock-file.ts +5 -1
- package/src/codex/native-main-owner.ts +17 -3
- package/src/codex/native-profile-manager.ts +19 -0
- package/src/codex/native-profile-startup.ts +8 -0
- package/src/codex/native-residue.ts +140 -27
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/refresh.ts +7 -0
- package/src/codex/routing.ts +177 -36
- package/src/codex/subagent-model-fallback.ts +34 -4
- package/src/codex/sync.ts +61 -0
- package/src/codex/upstream-host-health.ts +329 -31
- package/src/combos/request.ts +2 -0
- package/src/config.ts +221 -2
- package/src/images/loop.ts +1 -1
- package/src/integrations/native/ownership-preflight.ts +39 -2
- package/src/lib/bun-stream-caps.ts +3 -3
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/windows-secret-acl.ts +141 -39
- package/src/lib/windows-user-principal.ts +283 -0
- package/src/lib/winsw.ts +18 -2
- package/src/oauth/key-providers.ts +12 -0
- package/src/providers/derive.ts +54 -2
- package/src/providers/free-directory.ts +6 -5
- package/src/providers/model-discovery.ts +9 -3
- package/src/providers/quota.ts +592 -0
- package/src/providers/registry.ts +316 -13
- package/src/responses/parser.ts +26 -10
- package/src/responses/reasoning-replay-cache.ts +1 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +2 -1
- package/src/server/auth-cors.ts +44 -13
- package/src/server/chat-completions.ts +0 -4
- package/src/server/claude-messages.ts +73 -15
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/index.ts +328 -111
- package/src/server/lifecycle.ts +36 -0
- package/src/server/management/agent-settings-routes.ts +147 -56
- package/src/server/management/config-routes.ts +7 -2
- package/src/server/management/context.ts +4 -0
- package/src/server/management/native-integration-routes.ts +199 -20
- package/src/server/management/provider-routes.ts +41 -0
- package/src/server/management/routing-profile-routes.ts +234 -5
- package/src/server/management/system-restart.ts +12 -10
- package/src/server/management/system-routes.ts +20 -0
- package/src/server/management-auth.ts +51 -3
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +129 -4
- package/src/server/readiness.ts +99 -0
- package/src/server/relay.ts +113 -97
- package/src/server/request-log.ts +10 -4
- package/src/server/responses/compact.ts +107 -12
- package/src/server/responses/core.ts +220 -39
- package/src/server/responses-item-id-repair.ts +22 -3
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +25 -14
- package/src/server/ws-bridge.ts +27 -22
- package/src/service-manager-probe.ts +520 -10
- package/src/service.ts +134 -2
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +74 -9
- package/src/types.ts +68 -2
- package/src/update/index.ts +12 -0
- package/src/update/job.ts +392 -18
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/usage/log.ts +1 -1
- package/src/vision/index.ts +77 -2
- package/src/web-search/loop.ts +1 -1
- package/src/web-search/parse.ts +4 -1
- package/gui/dist/assets/index-BKVqyYqT.js +0 -70
- package/gui/dist/assets/index-Ca_3269W.css +0 -1
|
@@ -1,67 +1,365 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* keyed by (provider, host). Records ONLY — no circuit breaker, no admission
|
|
4
|
-
* change (issue #914 scope). Rotation decisions stay with account health;
|
|
5
|
-
* this ledger exists so a host-wide outage is visible as host-wide.
|
|
2
|
+
* Provider-origin reachability health and the optional logical-request circuit.
|
|
6
3
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* Issue #914 owns the observational ledger. The circuit is deliberately opt-in:
|
|
5
|
+
* a threshold of 0 preserves the ledger-only behavior. Only proven
|
|
6
|
+
* `connect_neutral` failures may settle a lease as host failure; timeout, reset,
|
|
7
|
+
* HTTP, redirect, authentication, and local failures remain outside this module.
|
|
11
8
|
*/
|
|
12
9
|
|
|
13
10
|
export const UPSTREAM_HOST_HEALTH_MAX_ENTRIES = 128;
|
|
14
11
|
export const UPSTREAM_HOST_FAILURE_WINDOW_MS = 10 * 60_000;
|
|
12
|
+
export const UPSTREAM_HOST_CIRCUIT_COOLDOWN_MS = 30_000;
|
|
13
|
+
export const UPSTREAM_HOST_CIRCUIT_MAX_THRESHOLD = 20;
|
|
15
14
|
|
|
16
15
|
export type UpstreamHostHealthEntry = {
|
|
17
16
|
consecutiveFailures: number;
|
|
18
17
|
lastFailureAt: number;
|
|
19
18
|
lastFailureCode?: string;
|
|
19
|
+
cooldownUntil?: number;
|
|
20
20
|
/** Recency marker for stalest-first pruning (not health semantics). */
|
|
21
21
|
lastTouch: number;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
/** Opaque ownership token for one admitted logical request. */
|
|
25
|
+
export type UpstreamHostAdmissionLease = Readonly<{
|
|
26
|
+
key: string;
|
|
27
|
+
leaseId: symbol;
|
|
28
|
+
generation: number;
|
|
29
|
+
halfOpen: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
|
|
32
|
+
export type UpstreamHostAdmission =
|
|
33
|
+
| { kind: "admitted"; lease: UpstreamHostAdmissionLease | null }
|
|
34
|
+
| { kind: "blocked"; retryAfterSeconds: number };
|
|
35
|
+
|
|
36
|
+
type InternalUpstreamHostHealth = UpstreamHostHealthEntry & {
|
|
37
|
+
generation: number;
|
|
38
|
+
activeLeaseIds: Set<symbol>;
|
|
39
|
+
halfOpenLeaseId?: symbol;
|
|
40
|
+
/**
|
|
41
|
+
* Leases admitted in the generation that immediately preceded the current
|
|
42
|
+
* cooldown. They are stale for failure settlement, but a real HTTP response
|
|
43
|
+
* from one still proves the origin is reachable and may close this cooldown.
|
|
44
|
+
*/
|
|
45
|
+
cooldownSuccessLeaseIds?: Set<symbol>;
|
|
46
|
+
/** True only when the entry is owned by opt-in circuit admissions. */
|
|
47
|
+
circuitManaged: boolean;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const hostHealth = new Map<string, InternalUpstreamHostHealth>();
|
|
51
|
+
let nextGenerationValue = 0;
|
|
25
52
|
|
|
26
53
|
export function upstreamHostHealthKey(provider: string, host: string): string {
|
|
27
54
|
return `${provider}|${host.toLowerCase()}`;
|
|
28
55
|
}
|
|
29
56
|
|
|
30
|
-
function
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
57
|
+
export function normalizeUpstreamHostCircuitThreshold(value: unknown): number {
|
|
58
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) return 0;
|
|
59
|
+
return Math.min(value, UPSTREAM_HOST_CIRCUIT_MAX_THRESHOLD);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function nextGeneration(): number {
|
|
63
|
+
nextGenerationValue = nextGenerationValue >= Number.MAX_SAFE_INTEGER ? 1 : nextGenerationValue + 1;
|
|
64
|
+
return nextGenerationValue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function snapshot(entry: InternalUpstreamHostHealth): UpstreamHostHealthEntry {
|
|
68
|
+
return {
|
|
69
|
+
consecutiveFailures: entry.consecutiveFailures,
|
|
70
|
+
lastFailureAt: entry.lastFailureAt,
|
|
71
|
+
lastTouch: entry.lastTouch,
|
|
72
|
+
...(entry.lastFailureCode !== undefined ? { lastFailureCode: entry.lastFailureCode } : {}),
|
|
73
|
+
...(entry.cooldownUntil !== undefined ? { cooldownUntil: entry.cooldownUntil } : {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function newEntry(now: number, circuitManaged: boolean): InternalUpstreamHostHealth {
|
|
78
|
+
return {
|
|
79
|
+
consecutiveFailures: 0,
|
|
80
|
+
lastFailureAt: 0,
|
|
81
|
+
lastTouch: now,
|
|
82
|
+
generation: nextGeneration(),
|
|
83
|
+
activeLeaseIds: new Set(),
|
|
84
|
+
circuitManaged,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function advanceGeneration(entry: InternalUpstreamHostHealth): void {
|
|
89
|
+
entry.generation = nextGeneration();
|
|
90
|
+
entry.activeLeaseIds.clear();
|
|
91
|
+
delete entry.halfOpenLeaseId;
|
|
92
|
+
delete entry.cooldownSuccessLeaseIds;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function removeExpiredUnleased(now: number): void {
|
|
96
|
+
for (const [key, entry] of hostHealth) {
|
|
97
|
+
if (entry.activeLeaseIds.size > 0) continue;
|
|
98
|
+
if (
|
|
99
|
+
entry.consecutiveFailures === 0
|
|
100
|
+
|| (entry.cooldownUntil === undefined
|
|
101
|
+
&& now - entry.lastFailureAt > UPSTREAM_HOST_FAILURE_WINDOW_MS)
|
|
102
|
+
) {
|
|
103
|
+
hostHealth.delete(key);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function oldestUnleasedKey(now: number): string | undefined {
|
|
109
|
+
let preferred: [string, number] | undefined;
|
|
110
|
+
let cooling: [string, number] | undefined;
|
|
111
|
+
for (const [key, entry] of hostHealth) {
|
|
112
|
+
if (entry.activeLeaseIds.size > 0) continue;
|
|
113
|
+
const candidate: [string, number] = [key, entry.lastTouch];
|
|
114
|
+
if (entry.cooldownUntil !== undefined && entry.cooldownUntil > now) {
|
|
115
|
+
if (!cooling || candidate[1] < cooling[1]) cooling = candidate;
|
|
116
|
+
} else if (!preferred || candidate[1] < preferred[1]) {
|
|
117
|
+
preferred = candidate;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return preferred?.[0] ?? cooling?.[0];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function pruneTo(limit: number, now: number): void {
|
|
124
|
+
removeExpiredUnleased(now);
|
|
125
|
+
while (hostHealth.size > limit) {
|
|
126
|
+
const key = oldestUnleasedKey(now);
|
|
127
|
+
if (!key) return; // Active leases may temporarily exceed the retention cap.
|
|
35
128
|
hostHealth.delete(key);
|
|
36
129
|
}
|
|
37
130
|
}
|
|
38
131
|
|
|
132
|
+
function makeRoom(now: number): void {
|
|
133
|
+
pruneTo(UPSTREAM_HOST_HEALTH_MAX_ENTRIES - 1, now);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function issueLease(
|
|
137
|
+
key: string,
|
|
138
|
+
entry: InternalUpstreamHostHealth,
|
|
139
|
+
halfOpen: boolean,
|
|
140
|
+
now: number,
|
|
141
|
+
): UpstreamHostAdmissionLease {
|
|
142
|
+
const leaseId = Symbol(halfOpen ? "upstream-host-half-open" : "upstream-host-admission");
|
|
143
|
+
entry.activeLeaseIds.add(leaseId);
|
|
144
|
+
entry.lastTouch = now;
|
|
145
|
+
if (halfOpen) entry.halfOpenLeaseId = leaseId;
|
|
146
|
+
return { key, leaseId, generation: entry.generation, halfOpen };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function matchingEntry(lease: UpstreamHostAdmissionLease): InternalUpstreamHostHealth | null {
|
|
150
|
+
const entry = hostHealth.get(lease.key);
|
|
151
|
+
if (!entry || entry.generation !== lease.generation || !entry.activeLeaseIds.has(lease.leaseId)) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
if (lease.halfOpen && entry.halfOpenLeaseId !== lease.leaseId) return null;
|
|
155
|
+
return entry;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function settleLease(entry: InternalUpstreamHostHealth, lease: UpstreamHostAdmissionLease): void {
|
|
159
|
+
entry.activeLeaseIds.delete(lease.leaseId);
|
|
160
|
+
if (entry.halfOpenLeaseId === lease.leaseId) delete entry.halfOpenLeaseId;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Admit one logical request. A disabled threshold returns a null lease and has
|
|
165
|
+
* byte-for-byte compatible call-site behavior with the observational ledger.
|
|
166
|
+
*/
|
|
167
|
+
export function acquireUpstreamHostAdmission(
|
|
168
|
+
key: string,
|
|
169
|
+
thresholdValue: unknown,
|
|
170
|
+
now = Date.now(),
|
|
171
|
+
): UpstreamHostAdmission {
|
|
172
|
+
const threshold = normalizeUpstreamHostCircuitThreshold(thresholdValue);
|
|
173
|
+
if (threshold === 0) return { kind: "admitted", lease: null };
|
|
174
|
+
|
|
175
|
+
pruneTo(UPSTREAM_HOST_HEALTH_MAX_ENTRIES, now);
|
|
176
|
+
let entry = hostHealth.get(key);
|
|
177
|
+
if (
|
|
178
|
+
entry?.activeLeaseIds.size === 0
|
|
179
|
+
&& entry.cooldownUntil === undefined
|
|
180
|
+
&& entry.consecutiveFailures > 0
|
|
181
|
+
&& now - entry.lastFailureAt > UPSTREAM_HOST_FAILURE_WINDOW_MS
|
|
182
|
+
) {
|
|
183
|
+
hostHealth.delete(key);
|
|
184
|
+
entry = undefined;
|
|
185
|
+
}
|
|
186
|
+
if (!entry) {
|
|
187
|
+
makeRoom(now);
|
|
188
|
+
entry = newEntry(now, true);
|
|
189
|
+
hostHealth.set(key, entry);
|
|
190
|
+
} else if (!entry.circuitManaged) {
|
|
191
|
+
// Observational history predating opt-in admission must not count toward
|
|
192
|
+
// opening the circuit. Start a fresh fenced generation.
|
|
193
|
+
entry.consecutiveFailures = 0;
|
|
194
|
+
entry.lastFailureAt = 0;
|
|
195
|
+
entry.circuitManaged = true;
|
|
196
|
+
delete entry.lastFailureCode;
|
|
197
|
+
delete entry.cooldownUntil;
|
|
198
|
+
advanceGeneration(entry);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (entry.cooldownUntil !== undefined) {
|
|
202
|
+
if (entry.cooldownUntil > now) {
|
|
203
|
+
return {
|
|
204
|
+
kind: "blocked",
|
|
205
|
+
retryAfterSeconds: Math.max(1, Math.ceil((entry.cooldownUntil - now) / 1_000)),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
if (entry.halfOpenLeaseId !== undefined) {
|
|
209
|
+
return { kind: "blocked", retryAfterSeconds: 1 };
|
|
210
|
+
}
|
|
211
|
+
advanceGeneration(entry);
|
|
212
|
+
return { kind: "admitted", lease: issueLease(key, entry, true, now) };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return { kind: "admitted", lease: issueLease(key, entry, false, now) };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Release an admitted request without recording transport evidence. */
|
|
219
|
+
export function releaseUpstreamHostAdmission(
|
|
220
|
+
lease: UpstreamHostAdmissionLease | null | undefined,
|
|
221
|
+
now = Date.now(),
|
|
222
|
+
): boolean {
|
|
223
|
+
if (!lease) return false;
|
|
224
|
+
const entry = matchingEntry(lease);
|
|
225
|
+
if (!entry) return false;
|
|
226
|
+
settleLease(entry, lease);
|
|
227
|
+
entry.lastTouch = now;
|
|
228
|
+
if (entry.activeLeaseIds.size === 0 && entry.consecutiveFailures === 0) {
|
|
229
|
+
hostHealth.delete(lease.key);
|
|
230
|
+
}
|
|
231
|
+
pruneTo(UPSTREAM_HOST_HEALTH_MAX_ENTRIES, now);
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Downgrade circuit-owned state when the operator disables the circuit.
|
|
237
|
+
* Failure history remains observational, but cooldown and all in-flight lease
|
|
238
|
+
* authority are revoked so disabled-mode traffic can update the ledger normally.
|
|
239
|
+
*/
|
|
240
|
+
export function disableUpstreamHostCircuitForKey(key: string, now = Date.now()): boolean {
|
|
241
|
+
const entry = hostHealth.get(key);
|
|
242
|
+
if (!entry?.circuitManaged) return false;
|
|
243
|
+
entry.circuitManaged = false;
|
|
244
|
+
delete entry.cooldownUntil;
|
|
245
|
+
advanceGeneration(entry);
|
|
246
|
+
entry.lastTouch = now;
|
|
247
|
+
if (entry.consecutiveFailures === 0) hostHealth.delete(key);
|
|
248
|
+
else pruneTo(UPSTREAM_HOST_HEALTH_MAX_ENTRIES, now);
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Record one terminal logical `connect_neutral` failure. */
|
|
39
253
|
export function recordUpstreamHostFailure(
|
|
40
254
|
key: string,
|
|
41
|
-
opts: {
|
|
255
|
+
opts: {
|
|
256
|
+
code?: string;
|
|
257
|
+
now?: number;
|
|
258
|
+
threshold?: unknown;
|
|
259
|
+
lease?: UpstreamHostAdmissionLease | null;
|
|
260
|
+
} = {},
|
|
42
261
|
): void {
|
|
43
262
|
const now = opts.now ?? Date.now();
|
|
44
|
-
const
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
263
|
+
const hasCircuitSettlement = Object.hasOwn(opts, "lease");
|
|
264
|
+
// An integrated logical request may already have settled its lease on an
|
|
265
|
+
// earlier physical response. Later retry completions carry explicit null and
|
|
266
|
+
// must not mutate a newer generation opened by another request.
|
|
267
|
+
if (hasCircuitSettlement && !opts.lease) return;
|
|
268
|
+
// Legacy callers without an admission lease continue to populate only the
|
|
269
|
+
// observational ledger. Circuit mutation is fenced to explicitly admitted
|
|
270
|
+
// logical requests so an unwired side path cannot open it accidentally.
|
|
271
|
+
const threshold = opts.lease
|
|
272
|
+
? normalizeUpstreamHostCircuitThreshold(opts.threshold)
|
|
273
|
+
: 0;
|
|
274
|
+
let entry: InternalUpstreamHostHealth | undefined;
|
|
275
|
+
if (opts.lease) {
|
|
276
|
+
entry = matchingEntry(opts.lease) ?? undefined;
|
|
277
|
+
if (!entry || opts.lease.key !== key) return; // stale completion cannot mutate a newer generation
|
|
278
|
+
settleLease(entry, opts.lease);
|
|
279
|
+
} else {
|
|
280
|
+
entry = hostHealth.get(key);
|
|
281
|
+
if (entry?.circuitManaged) {
|
|
282
|
+
const stale = entry.activeLeaseIds.size === 0
|
|
283
|
+
&& entry.cooldownUntil === undefined
|
|
284
|
+
&& now - entry.lastFailureAt > UPSTREAM_HOST_FAILURE_WINDOW_MS;
|
|
285
|
+
if (!stale) return;
|
|
286
|
+
hostHealth.delete(key);
|
|
287
|
+
entry = undefined;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (!entry) {
|
|
291
|
+
makeRoom(now);
|
|
292
|
+
entry = newEntry(now, opts.lease !== undefined);
|
|
293
|
+
hostHealth.set(key, entry);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const reopens = opts.lease?.halfOpen === true || entry.cooldownUntil !== undefined;
|
|
297
|
+
const stale = entry.consecutiveFailures === 0
|
|
298
|
+
|| (!reopens && now - entry.lastFailureAt > UPSTREAM_HOST_FAILURE_WINDOW_MS);
|
|
299
|
+
entry.consecutiveFailures = reopens && threshold > 0
|
|
300
|
+
? Math.max(threshold, entry.consecutiveFailures + 1)
|
|
301
|
+
: stale ? 1 : entry.consecutiveFailures + 1;
|
|
302
|
+
entry.lastFailureAt = now;
|
|
303
|
+
entry.lastTouch = now;
|
|
304
|
+
const code = typeof opts.code === "string" && opts.code !== ""
|
|
305
|
+
? opts.code
|
|
306
|
+
: entry.lastFailureCode;
|
|
307
|
+
if (code !== undefined) entry.lastFailureCode = code;
|
|
308
|
+
|
|
309
|
+
if (threshold > 0 && (reopens || entry.consecutiveFailures >= threshold)) {
|
|
310
|
+
entry.cooldownUntil = now + UPSTREAM_HOST_CIRCUIT_COOLDOWN_MS;
|
|
311
|
+
// The failing lease was settled above. Preserve only its still-in-flight
|
|
312
|
+
// same-generation peers as one-shot reachability proofs. advanceGeneration
|
|
313
|
+
// invalidates them for every other mutation and for any later half-open generation.
|
|
314
|
+
const concurrentSuccessLeaseIds = new Set(entry.activeLeaseIds);
|
|
315
|
+
advanceGeneration(entry);
|
|
316
|
+
if (concurrentSuccessLeaseIds.size > 0) {
|
|
317
|
+
entry.cooldownSuccessLeaseIds = concurrentSuccessLeaseIds;
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
delete entry.cooldownUntil;
|
|
321
|
+
}
|
|
322
|
+
pruneTo(UPSTREAM_HOST_HEALTH_MAX_ENTRIES, now);
|
|
56
323
|
}
|
|
57
324
|
|
|
58
|
-
/** Any real HTTP response from the
|
|
59
|
-
export function resetUpstreamHostHealth(
|
|
60
|
-
|
|
325
|
+
/** Any real HTTP response from the admitted logical request proves reachability. */
|
|
326
|
+
export function resetUpstreamHostHealth(
|
|
327
|
+
key: string,
|
|
328
|
+
lease?: UpstreamHostAdmissionLease | null,
|
|
329
|
+
now = Date.now(),
|
|
330
|
+
): boolean {
|
|
331
|
+
if (lease === null) return false;
|
|
332
|
+
if (lease === undefined) {
|
|
333
|
+
const entry = hostHealth.get(key);
|
|
334
|
+
if (entry?.circuitManaged) return false;
|
|
335
|
+
return hostHealth.delete(key);
|
|
336
|
+
}
|
|
337
|
+
if (lease.key !== key) return false;
|
|
338
|
+
let entry = matchingEntry(lease);
|
|
339
|
+
if (!entry) {
|
|
340
|
+
const coolingEntry = hostHealth.get(key);
|
|
341
|
+
if (
|
|
342
|
+
coolingEntry?.cooldownUntil === undefined
|
|
343
|
+
|| !coolingEntry.cooldownSuccessLeaseIds?.delete(lease.leaseId)
|
|
344
|
+
) return false;
|
|
345
|
+
entry = coolingEntry;
|
|
346
|
+
} else {
|
|
347
|
+
settleLease(entry, lease);
|
|
348
|
+
}
|
|
349
|
+
entry.consecutiveFailures = 0;
|
|
350
|
+
entry.lastFailureAt = 0;
|
|
351
|
+
entry.lastTouch = now;
|
|
352
|
+
delete entry.lastFailureCode;
|
|
353
|
+
delete entry.cooldownUntil;
|
|
354
|
+
if (entry.activeLeaseIds.size === 0) hostHealth.delete(key);
|
|
355
|
+
pruneTo(UPSTREAM_HOST_HEALTH_MAX_ENTRIES, now);
|
|
356
|
+
return true;
|
|
61
357
|
}
|
|
62
358
|
|
|
63
359
|
export function getUpstreamHostHealth(key: string): UpstreamHostHealthEntry | null {
|
|
64
|
-
|
|
360
|
+
const entry = hostHealth.get(key);
|
|
361
|
+
if (!entry || entry.consecutiveFailures === 0) return null;
|
|
362
|
+
return snapshot(entry);
|
|
65
363
|
}
|
|
66
364
|
|
|
67
365
|
/** Test hook: clear the whole ledger. */
|
package/src/combos/request.ts
CHANGED
|
@@ -40,6 +40,8 @@ export function concreteComboRequestBody(
|
|
|
40
40
|
&& !Object.prototype.hasOwnProperty.call(reasoning, "effort")
|
|
41
41
|
);
|
|
42
42
|
if (!needsDefault) return clone;
|
|
43
|
+
// Picker availability treats an unknown ladder as a wildcard, but runtime
|
|
44
|
+
// injection stays fail-closed until this concrete target advertises support.
|
|
43
45
|
if (!targetReasoningEfforts?.includes(defaultEffort)) {
|
|
44
46
|
const key = `${target.provider}/${target.model}:${defaultEffort}`;
|
|
45
47
|
if (!warnedUnsupportedDefaults.has(key)) {
|