Skip to content

Commit 947a32a

Browse files
committed
feat: refactotr
1 parent ef7218e commit 947a32a

13 files changed

Lines changed: 885 additions & 116 deletions

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@
3232
"publint": "^0.3.8",
3333
"rimraf": "^5.0.5",
3434
"syncpack": "^13.0.2",
35+
"ts-node": "^10.9.2",
3536
"tslib": "^2.3.0",
3637
"tsup": "^8.5.0",
37-
"typescript": "^5.8.2",
38+
"typescript": "^5.9.2",
3839
"vite": "^7.2.7",
3940
"vitest": "^3.2.4"
4041
},

packages/core/src/traffic/traffic-circuit-breaker.ts

Lines changed: 94 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Logger } from "../logger";
12
import {
23
CIRCUIT_COOLDOWN_MS,
34
CIRCUIT_FAILURE_THRESHOLD,
@@ -28,22 +29,40 @@ export class TrafficCircuitBreaker {
2829
this.fallbackChains = new Map(Object.entries(chains));
2930
}
3031

31-
resolve(next: QueuedRequest): DispatchDecision | null {
32+
resolve(next: QueuedRequest, logger?: Logger): DispatchDecision | null {
33+
const circuitLogger = logger?.child({ module: "circuit-breaker" });
3234
const visited = new Set<string>();
3335

3436
while (true) {
3537
const key = this.buildRateLimitKey(next.request.metadata);
3638
next.circuitKey = key;
39+
circuitLogger?.trace?.("Circuit resolve step", {
40+
circuitKey: key,
41+
provider: next.request.metadata?.provider,
42+
model: next.request.metadata?.model,
43+
});
3744

3845
const model = next.request.metadata?.model;
3946
if (model) visited.add(model);
4047

41-
const evaluation = this.evaluateCircuitState(key);
48+
const evaluation = this.evaluateCircuitState(key, circuitLogger);
4249
next.circuitStatus = evaluation.state;
50+
circuitLogger?.debug?.("Circuit evaluated", {
51+
circuitKey: key,
52+
state: evaluation.state,
53+
allowRequest: evaluation.allowRequest,
54+
retryAfterMs: evaluation.retryAfterMs,
55+
});
4356

4457
if (evaluation.allowRequest) return null;
4558

46-
const fallback = this.findFallbackModel(next.request.metadata, visited);
59+
const fallback = this.findFallbackModel(next.request.metadata, visited, circuitLogger);
60+
circuitLogger?.debug?.("Circuit open; attempting fallback", {
61+
circuitKey: key,
62+
currentModel: next.request.metadata?.model,
63+
fallback,
64+
visitedModels: Array.from(visited),
65+
});
4766
if (!fallback || !next.request.createFallbackRequest) {
4867
next.reject(
4968
new CircuitBreakerOpenError(
@@ -52,39 +71,76 @@ export class TrafficCircuitBreaker {
5271
evaluation.retryAfterMs,
5372
),
5473
);
74+
circuitLogger?.warn?.("No fallback available; rejecting request", {
75+
circuitKey: key,
76+
retryAfterMs: evaluation.retryAfterMs,
77+
});
5578
return { kind: "skip" };
5679
}
5780

5881
const fallbackRequest = next.request.createFallbackRequest(fallback);
59-
if (!fallbackRequest) return { kind: "skip" };
82+
if (!fallbackRequest) {
83+
circuitLogger?.warn?.("createFallbackRequest returned undefined; skipping", {
84+
circuitKey: key,
85+
fallback,
86+
});
87+
return { kind: "skip" };
88+
}
6089

6190
next.request = fallbackRequest;
6291
next.attempt = 1;
6392
next.rateLimitKey = undefined;
6493
next.etaMs = undefined;
6594
next.circuitKey = undefined;
6695
next.circuitStatus = undefined;
96+
circuitLogger?.debug?.("Switched to fallback request", {
97+
previousCircuitKey: key,
98+
fallbackModel: fallback,
99+
});
67100
}
68101
}
69102

70-
markTrial(item: QueuedRequest): void {
103+
markTrial(item: QueuedRequest, logger?: Logger): void {
104+
const circuitLogger = logger?.child({ module: "circuit-breaker" });
71105
const key = item.circuitKey;
72106
if (!key) return;
73107
const state = this.circuitBreakers.get(key);
74108
if (state && state.status === "half-open" && !state.trialInFlight) {
75109
state.trialInFlight = true;
110+
circuitLogger?.debug?.("Marked half-open trial in flight", { circuitKey: key });
76111
}
77112
}
78113

79-
recordSuccess(metadata?: TrafficRequestMetadata): void {
114+
recordSuccess(metadata?: TrafficRequestMetadata, logger?: Logger): void {
115+
const circuitLogger = logger?.child({ module: "circuit-breaker" });
80116
const key = this.buildRateLimitKey(metadata);
81117
this.circuitBreakers.delete(key);
118+
circuitLogger?.debug?.("Circuit success; cleared circuit state", {
119+
circuitKey: key,
120+
provider: metadata?.provider,
121+
model: metadata?.model,
122+
});
82123
}
83124

84-
recordFailure(metadata: TrafficRequestMetadata | undefined, error: unknown): void {
85-
const status = extractStatusCode(error);
125+
recordFailure(
126+
metadata: TrafficRequestMetadata | undefined,
127+
error: unknown,
128+
logger?: Logger,
129+
): void {
130+
const circuitLogger = logger?.child({ module: "circuit-breaker" });
131+
const status = extractStatusCode(error, logger);
132+
circuitLogger?.debug?.("Circuit failure observed", {
133+
circuitKey: this.buildRateLimitKey(metadata),
134+
status,
135+
provider: metadata?.provider,
136+
model: metadata?.model,
137+
});
86138
if (!this.isCircuitBreakerStatus(status)) {
87139
this.circuitBreakers.delete(this.buildRateLimitKey(metadata));
140+
circuitLogger?.debug?.("Failure not eligible for circuit breaker; cleared circuit state", {
141+
circuitKey: this.buildRateLimitKey(metadata),
142+
status,
143+
});
88144
return;
89145
}
90146

@@ -106,18 +162,36 @@ export class TrafficCircuitBreaker {
106162
state.status = "open";
107163
state.openedAt = now;
108164
state.trialInFlight = false;
165+
circuitLogger?.warn?.("Circuit opened", {
166+
circuitKey: key,
167+
failureCount: state.failureTimestamps.length,
168+
threshold: CIRCUIT_FAILURE_THRESHOLD,
169+
openedAt: state.openedAt,
170+
});
109171
}
110172

111173
this.circuitBreakers.set(key, state);
174+
circuitLogger?.trace?.("Circuit state updated", {
175+
circuitKey: key,
176+
status: state.status,
177+
failureCount: state.failureTimestamps.length,
178+
windowMs: CIRCUIT_FAILURE_WINDOW_MS,
179+
});
112180
}
113181

114-
private evaluateCircuitState(key: string): {
182+
private evaluateCircuitState(
183+
key: string,
184+
logger?: Logger,
185+
): {
115186
allowRequest: boolean;
116187
state: CircuitStateStatus;
117188
retryAfterMs?: number;
118189
} {
119190
const state = this.circuitBreakers.get(key);
120-
if (!state) return { allowRequest: true, state: "closed" };
191+
if (!state) {
192+
logger?.trace?.("Circuit state missing; allow request", { circuitKey: key });
193+
return { allowRequest: true, state: "closed" };
194+
}
121195

122196
const now = Date.now();
123197

@@ -127,6 +201,7 @@ export class TrafficCircuitBreaker {
127201
state.status = "half-open";
128202
state.trialInFlight = false;
129203
state.failureTimestamps = [];
204+
logger?.debug?.("Circuit transitioned to half-open", { circuitKey: key });
130205
return { allowRequest: true, state: "half-open" };
131206
}
132207
return {
@@ -146,14 +221,17 @@ export class TrafficCircuitBreaker {
146221
private findFallbackModel(
147222
metadata: TrafficRequestMetadata | undefined,
148223
visitedModels: Set<string>,
224+
logger?: Logger,
149225
): string | undefined {
150226
const currentModel = metadata?.model;
151227
if (!currentModel) {
228+
logger?.trace?.("No current model; no fallback", {});
152229
return undefined;
153230
}
154231

155232
const chain = this.fallbackChains.get(currentModel);
156233
if (!chain) {
234+
logger?.trace?.("No fallback chain for model", { currentModel });
157235
return undefined;
158236
}
159237

@@ -168,9 +246,14 @@ export class TrafficCircuitBreaker {
168246
model: candidate,
169247
});
170248

171-
const evaluation = this.evaluateCircuitState(candidateKey);
249+
const evaluation = this.evaluateCircuitState(candidateKey, logger);
172250
if (evaluation.allowRequest) {
173251
visitedModels.add(candidate);
252+
logger?.debug?.("Selected fallback model", {
253+
currentModel,
254+
fallbackModel: candidate,
255+
fallbackCircuitKey: candidateKey,
256+
});
174257
return candidate;
175258
}
176259
}

packages/core/src/traffic/traffic-controller-internal.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
import type {
2-
TrafficPriority,
3-
TrafficRequest,
4-
TrafficRequestMetadata,
5-
TrafficRequestType,
6-
} from "./traffic-types";
1+
import type { TrafficPriority, TrafficRequest, TrafficRequestType } from "./traffic-types";
72

83
export type Scheduler = (callback: () => void) => void;
94

0 commit comments

Comments
 (0)