@albinocrabs/o-switcher 0.1.0 → 0.2.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/CHANGELOG.md +27 -0
- package/LICENSE +199 -21
- package/README.md +88 -288
- package/dist/{chunk-BTDKGS7P.js → chunk-7ITX5623.js} +122 -237
- package/dist/chunk-TJ7ZGZHD.cjs +1736 -0
- package/dist/index.cjs +567 -1976
- package/dist/index.js +281 -225
- package/dist/plugin.cjs +111 -1024
- package/dist/plugin.d.cts +1 -1
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.js +100 -35
- package/package.json +56 -14
- package/src/registry/types.ts +65 -0
- package/src/state-bridge.ts +119 -0
- package/src/tui.tsx +214 -0
- package/CONTRIBUTING.md +0 -72
- package/dist/chunk-BTDKGS7P.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/plugin.cjs.map +0 -1
- package/dist/plugin.js.map +0 -1
- package/docs/api-reference.md +0 -286
- package/docs/architecture.md +0 -511
- package/docs/examples.md +0 -190
- package/docs/getting-started.md +0 -316
- package/scripts/collect-errors.ts +0 -159
- package/scripts/corpus.jsonl +0 -5
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import pino from 'pino';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
import { EventEmitter } from 'eventemitter3';
|
|
5
|
+
import { CircuitState, ConsecutiveBreaker, CountBreaker, BrokenCircuitError, IsolatedCircuitError, circuitBreaker, handleAll } from 'cockatiel';
|
|
6
|
+
import PQueue from 'p-queue';
|
|
7
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
8
|
+
import { readFile, mkdir, writeFile, rename, watch } from 'fs/promises';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { join, dirname } from 'path';
|
|
11
|
+
|
|
1
12
|
// src/config/defaults.ts
|
|
2
13
|
var DEFAULT_RETRY_BUDGET = 3;
|
|
3
14
|
var DEFAULT_FAILOVER_BUDGET = 2;
|
|
@@ -21,9 +32,6 @@ var DEFAULT_CB_HALF_OPEN_MAX_PROBES = 1;
|
|
|
21
32
|
var DEFAULT_CB_SUCCESS_THRESHOLD = 2;
|
|
22
33
|
var DEFAULT_RETRY = 3;
|
|
23
34
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
24
|
-
|
|
25
|
-
// src/config/schema.ts
|
|
26
|
-
import { z } from "zod";
|
|
27
35
|
var BackoffConfigSchema = z.object({
|
|
28
36
|
base_ms: z.number().positive().default(DEFAULT_BACKOFF_BASE_MS),
|
|
29
37
|
multiplier: z.number().positive().default(DEFAULT_BACKOFF_MULTIPLIER),
|
|
@@ -133,13 +141,11 @@ var validateConfig = (raw) => {
|
|
|
133
141
|
}
|
|
134
142
|
return Object.freeze(data);
|
|
135
143
|
}
|
|
136
|
-
const diagnostics = result.error.issues.map(
|
|
137
|
-
(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
})
|
|
142
|
-
);
|
|
144
|
+
const diagnostics = result.error.issues.map((issue) => ({
|
|
145
|
+
path: issue.path.join("."),
|
|
146
|
+
message: issue.message,
|
|
147
|
+
received: "expected" in issue ? issue.expected : void 0
|
|
148
|
+
}));
|
|
143
149
|
throw new ConfigValidationError(diagnostics);
|
|
144
150
|
};
|
|
145
151
|
|
|
@@ -240,10 +246,7 @@ var TargetRegistry = class {
|
|
|
240
246
|
if (!target) {
|
|
241
247
|
return;
|
|
242
248
|
}
|
|
243
|
-
const newHealthScore = updateHealthScore(
|
|
244
|
-
target.health_score,
|
|
245
|
-
observation
|
|
246
|
-
);
|
|
249
|
+
const newHealthScore = updateHealthScore(target.health_score, observation);
|
|
247
250
|
this.targets.set(id, { ...target, health_score: newHealthScore });
|
|
248
251
|
}
|
|
249
252
|
/** Sets the cooldown_until timestamp for a target. */
|
|
@@ -302,10 +305,6 @@ var TARGET_STATES = [
|
|
|
302
305
|
"Draining",
|
|
303
306
|
"Disabled"
|
|
304
307
|
];
|
|
305
|
-
|
|
306
|
-
// src/audit/logger.ts
|
|
307
|
-
import pino from "pino";
|
|
308
|
-
import crypto from "crypto";
|
|
309
308
|
var REDACT_PATHS = [
|
|
310
309
|
"api_key",
|
|
311
310
|
"token",
|
|
@@ -337,57 +336,54 @@ var createAuditLogger = (options) => {
|
|
|
337
336
|
};
|
|
338
337
|
var createRequestLogger = (baseLogger, requestId) => baseLogger.child({ request_id: requestId });
|
|
339
338
|
var generateCorrelationId = () => crypto.randomUUID();
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
retryable: z2.literal(true),
|
|
346
|
-
retry_after_ms: z2.number().optional(),
|
|
347
|
-
provider_reason: z2.string().optional()
|
|
339
|
+
var RateLimitedSchema = z.object({
|
|
340
|
+
class: z.literal("RateLimited"),
|
|
341
|
+
retryable: z.literal(true),
|
|
342
|
+
retry_after_ms: z.number().optional(),
|
|
343
|
+
provider_reason: z.string().optional()
|
|
348
344
|
});
|
|
349
|
-
var QuotaExhaustedSchema =
|
|
350
|
-
class:
|
|
351
|
-
retryable:
|
|
352
|
-
provider_reason:
|
|
345
|
+
var QuotaExhaustedSchema = z.object({
|
|
346
|
+
class: z.literal("QuotaExhausted"),
|
|
347
|
+
retryable: z.literal(false),
|
|
348
|
+
provider_reason: z.string().optional()
|
|
353
349
|
});
|
|
354
|
-
var AuthFailureSchema =
|
|
355
|
-
class:
|
|
356
|
-
retryable:
|
|
357
|
-
recovery_attempted:
|
|
350
|
+
var AuthFailureSchema = z.object({
|
|
351
|
+
class: z.literal("AuthFailure"),
|
|
352
|
+
retryable: z.literal(false),
|
|
353
|
+
recovery_attempted: z.boolean().default(false)
|
|
358
354
|
});
|
|
359
|
-
var PermissionFailureSchema =
|
|
360
|
-
class:
|
|
361
|
-
retryable:
|
|
355
|
+
var PermissionFailureSchema = z.object({
|
|
356
|
+
class: z.literal("PermissionFailure"),
|
|
357
|
+
retryable: z.literal(false)
|
|
362
358
|
});
|
|
363
|
-
var PolicyFailureSchema =
|
|
364
|
-
class:
|
|
365
|
-
retryable:
|
|
359
|
+
var PolicyFailureSchema = z.object({
|
|
360
|
+
class: z.literal("PolicyFailure"),
|
|
361
|
+
retryable: z.literal(false)
|
|
366
362
|
});
|
|
367
|
-
var RegionRestrictionSchema =
|
|
368
|
-
class:
|
|
369
|
-
retryable:
|
|
363
|
+
var RegionRestrictionSchema = z.object({
|
|
364
|
+
class: z.literal("RegionRestriction"),
|
|
365
|
+
retryable: z.literal(false)
|
|
370
366
|
});
|
|
371
|
-
var ModelUnavailableSchema =
|
|
372
|
-
class:
|
|
373
|
-
retryable:
|
|
374
|
-
failover_eligible:
|
|
367
|
+
var ModelUnavailableSchema = z.object({
|
|
368
|
+
class: z.literal("ModelUnavailable"),
|
|
369
|
+
retryable: z.literal(false),
|
|
370
|
+
failover_eligible: z.literal(true)
|
|
375
371
|
});
|
|
376
|
-
var TransientServerFailureSchema =
|
|
377
|
-
class:
|
|
378
|
-
retryable:
|
|
379
|
-
http_status:
|
|
372
|
+
var TransientServerFailureSchema = z.object({
|
|
373
|
+
class: z.literal("TransientServerFailure"),
|
|
374
|
+
retryable: z.literal(true),
|
|
375
|
+
http_status: z.number().optional()
|
|
380
376
|
});
|
|
381
|
-
var TransportFailureSchema =
|
|
382
|
-
class:
|
|
383
|
-
retryable:
|
|
377
|
+
var TransportFailureSchema = z.object({
|
|
378
|
+
class: z.literal("TransportFailure"),
|
|
379
|
+
retryable: z.literal(true)
|
|
384
380
|
});
|
|
385
|
-
var InterruptedExecutionSchema =
|
|
386
|
-
class:
|
|
387
|
-
retryable:
|
|
388
|
-
partial_output_bytes:
|
|
381
|
+
var InterruptedExecutionSchema = z.object({
|
|
382
|
+
class: z.literal("InterruptedExecution"),
|
|
383
|
+
retryable: z.literal(true),
|
|
384
|
+
partial_output_bytes: z.number().optional()
|
|
389
385
|
});
|
|
390
|
-
var ErrorClassSchema =
|
|
386
|
+
var ErrorClassSchema = z.discriminatedUnion("class", [
|
|
391
387
|
RateLimitedSchema,
|
|
392
388
|
QuotaExhaustedSchema,
|
|
393
389
|
AuthFailureSchema,
|
|
@@ -433,10 +429,7 @@ var DEFAULT_BACKOFF_PARAMS = {
|
|
|
433
429
|
jitter: "full"
|
|
434
430
|
};
|
|
435
431
|
var computeBackoffMs = (attempt, params, retryAfterMs) => {
|
|
436
|
-
const rawDelay = Math.min(
|
|
437
|
-
params.base_ms * Math.pow(params.multiplier, attempt),
|
|
438
|
-
params.max_ms
|
|
439
|
-
);
|
|
432
|
+
const rawDelay = Math.min(params.base_ms * Math.pow(params.multiplier, attempt), params.max_ms);
|
|
440
433
|
const jitteredDelay = params.jitter === "full" ? Math.random() * rawDelay : params.jitter === "equal" ? rawDelay / 2 + Math.random() * (rawDelay / 2) : rawDelay;
|
|
441
434
|
return Math.max(jitteredDelay, retryAfterMs ?? 0);
|
|
442
435
|
};
|
|
@@ -480,22 +473,8 @@ var EXCLUSION_REASONS = [
|
|
|
480
473
|
"capability_mismatch",
|
|
481
474
|
"draining"
|
|
482
475
|
];
|
|
483
|
-
var ADMISSION_RESULTS = [
|
|
484
|
-
"admitted",
|
|
485
|
-
"queued",
|
|
486
|
-
"degraded",
|
|
487
|
-
"rejected"
|
|
488
|
-
];
|
|
489
|
-
|
|
490
|
-
// src/routing/events.ts
|
|
491
|
-
import { EventEmitter } from "eventemitter3";
|
|
476
|
+
var ADMISSION_RESULTS = ["admitted", "queued", "degraded", "rejected"];
|
|
492
477
|
var createRoutingEventBus = () => new EventEmitter();
|
|
493
|
-
|
|
494
|
-
// src/routing/dual-breaker.ts
|
|
495
|
-
import {
|
|
496
|
-
ConsecutiveBreaker,
|
|
497
|
-
CountBreaker
|
|
498
|
-
} from "cockatiel";
|
|
499
478
|
var DualBreaker = class {
|
|
500
479
|
consecutive;
|
|
501
480
|
count;
|
|
@@ -537,21 +516,11 @@ var DualBreaker = class {
|
|
|
537
516
|
return consecutiveTripped || countTripped;
|
|
538
517
|
}
|
|
539
518
|
};
|
|
540
|
-
|
|
541
|
-
// src/routing/circuit-breaker.ts
|
|
542
|
-
import {
|
|
543
|
-
CircuitState as CircuitState2,
|
|
544
|
-
handleAll,
|
|
545
|
-
circuitBreaker,
|
|
546
|
-
BrokenCircuitError,
|
|
547
|
-
IsolatedCircuitError
|
|
548
|
-
} from "cockatiel";
|
|
549
|
-
import "eventemitter3";
|
|
550
519
|
var COCKATIEL_TO_TARGET = /* @__PURE__ */ new Map([
|
|
551
|
-
[
|
|
552
|
-
[
|
|
553
|
-
[
|
|
554
|
-
[
|
|
520
|
+
[CircuitState.Closed, "Active"],
|
|
521
|
+
[CircuitState.Open, "CircuitOpen"],
|
|
522
|
+
[CircuitState.HalfOpen, "CircuitHalfOpen"],
|
|
523
|
+
[CircuitState.Isolated, "Disabled"]
|
|
555
524
|
]);
|
|
556
525
|
var toTargetState = (state) => COCKATIEL_TO_TARGET.get(state) ?? "Disabled";
|
|
557
526
|
var createCircuitBreaker = (targetId, config, eventBus) => {
|
|
@@ -567,11 +536,11 @@ var createCircuitBreaker = (targetId, config, eventBus) => {
|
|
|
567
536
|
return { policy, breaker };
|
|
568
537
|
};
|
|
569
538
|
let current = createPolicy();
|
|
570
|
-
let previousState =
|
|
539
|
+
let previousState = CircuitState.Closed;
|
|
571
540
|
let openedAtMs = 0;
|
|
572
541
|
const subscribeEvents = (policy) => {
|
|
573
542
|
policy.onStateChange((newState) => {
|
|
574
|
-
if (newState ===
|
|
543
|
+
if (newState === CircuitState.Open) {
|
|
575
544
|
openedAtMs = Date.now();
|
|
576
545
|
}
|
|
577
546
|
const from = toTargetState(previousState);
|
|
@@ -590,8 +559,8 @@ var createCircuitBreaker = (targetId, config, eventBus) => {
|
|
|
590
559
|
subscribeEvents(current.policy);
|
|
591
560
|
const effectiveState = () => {
|
|
592
561
|
const raw = current.policy.state;
|
|
593
|
-
if (raw ===
|
|
594
|
-
return
|
|
562
|
+
if (raw === CircuitState.Open && openedAtMs > 0 && Date.now() - openedAtMs >= config.half_open_after_ms) {
|
|
563
|
+
return CircuitState.HalfOpen;
|
|
595
564
|
}
|
|
596
565
|
return raw;
|
|
597
566
|
};
|
|
@@ -618,11 +587,11 @@ var createCircuitBreaker = (targetId, config, eventBus) => {
|
|
|
618
587
|
}
|
|
619
588
|
},
|
|
620
589
|
allowRequest() {
|
|
621
|
-
return effectiveState() !==
|
|
590
|
+
return effectiveState() !== CircuitState.Open;
|
|
622
591
|
},
|
|
623
592
|
reset() {
|
|
624
593
|
current = createPolicy();
|
|
625
|
-
previousState =
|
|
594
|
+
previousState = CircuitState.Closed;
|
|
626
595
|
openedAtMs = 0;
|
|
627
596
|
subscribeEvents(current.policy);
|
|
628
597
|
}
|
|
@@ -824,9 +793,6 @@ var createCooldownManager = (registry, eventBus) => ({
|
|
|
824
793
|
return target.cooldown_until !== null && target.cooldown_until > nowMs;
|
|
825
794
|
}
|
|
826
795
|
});
|
|
827
|
-
|
|
828
|
-
// src/routing/admission.ts
|
|
829
|
-
import PQueue from "p-queue";
|
|
830
796
|
var checkHardRejects = (ctx) => {
|
|
831
797
|
if (!ctx.hasEligibleTargets) {
|
|
832
798
|
return { result: "rejected", reason: "no eligible targets available" };
|
|
@@ -997,7 +963,11 @@ var createFailoverOrchestrator = (deps) => ({
|
|
|
997
963
|
let totalFailovers = 0;
|
|
998
964
|
let lastErrorClass;
|
|
999
965
|
log?.info(
|
|
1000
|
-
{
|
|
966
|
+
{
|
|
967
|
+
event: "failover_start",
|
|
968
|
+
retry_budget: deps.retryBudget,
|
|
969
|
+
failover_budget: deps.failoverBudget
|
|
970
|
+
},
|
|
1001
971
|
"Failover loop started"
|
|
1002
972
|
);
|
|
1003
973
|
for (let failoverNo = 0; failoverNo <= deps.failoverBudget; failoverNo++) {
|
|
@@ -1028,10 +998,7 @@ var createFailoverOrchestrator = (deps) => ({
|
|
|
1028
998
|
};
|
|
1029
999
|
}
|
|
1030
1000
|
const targetId = selection.selected.target_id;
|
|
1031
|
-
const retryPolicy = createRetryPolicy(
|
|
1032
|
-
deps.retryBudget,
|
|
1033
|
-
deps.backoffParams
|
|
1034
|
-
);
|
|
1001
|
+
const retryPolicy = createRetryPolicy(deps.retryBudget, deps.backoffParams);
|
|
1035
1002
|
for (let retry = 0; retry <= deps.retryBudget; retry++) {
|
|
1036
1003
|
const attemptNo = retry + 1;
|
|
1037
1004
|
const acquired = deps.concurrency.acquire(targetId);
|
|
@@ -1057,7 +1024,13 @@ var createFailoverOrchestrator = (deps) => ({
|
|
|
1057
1024
|
deps.registry.recordObservation(targetId, 1);
|
|
1058
1025
|
deps.registry.updateLatency(targetId, attemptResult.latency_ms);
|
|
1059
1026
|
log?.info(
|
|
1060
|
-
{
|
|
1027
|
+
{
|
|
1028
|
+
event: "attempt_success",
|
|
1029
|
+
target_id: targetId,
|
|
1030
|
+
attempt_no: attemptNo,
|
|
1031
|
+
failover_no: failoverNo,
|
|
1032
|
+
latency_ms: attemptResult.latency_ms
|
|
1033
|
+
},
|
|
1061
1034
|
"Attempt succeeded"
|
|
1062
1035
|
);
|
|
1063
1036
|
attempts.push({
|
|
@@ -1095,15 +1068,16 @@ var createFailoverOrchestrator = (deps) => ({
|
|
|
1095
1068
|
deps.backoffParams,
|
|
1096
1069
|
deps.backoffParams.max_ms
|
|
1097
1070
|
);
|
|
1098
|
-
deps.cooldownManager.setCooldown(
|
|
1099
|
-
targetId,
|
|
1100
|
-
cooldownMs,
|
|
1101
|
-
errorClass.class
|
|
1102
|
-
);
|
|
1071
|
+
deps.cooldownManager.setCooldown(targetId, cooldownMs, errorClass.class);
|
|
1103
1072
|
}
|
|
1104
1073
|
if (errorClass.class === "ModelUnavailable") {
|
|
1105
1074
|
log?.info(
|
|
1106
|
-
{
|
|
1075
|
+
{
|
|
1076
|
+
event: "early_failover",
|
|
1077
|
+
target_id: targetId,
|
|
1078
|
+
error_class: errorClass.class,
|
|
1079
|
+
failover_no: failoverNo
|
|
1080
|
+
},
|
|
1107
1081
|
"Early failover \u2014 ModelUnavailable"
|
|
1108
1082
|
);
|
|
1109
1083
|
attempts.push({
|
|
@@ -1181,17 +1155,25 @@ var createFailoverOrchestrator = (deps) => ({
|
|
|
1181
1155
|
totalRetries++;
|
|
1182
1156
|
if (decision.action === "retry") {
|
|
1183
1157
|
log?.info(
|
|
1184
|
-
{
|
|
1158
|
+
{
|
|
1159
|
+
event: "retry",
|
|
1160
|
+
target_id: targetId,
|
|
1161
|
+
attempt_no: attemptNo,
|
|
1162
|
+
error_class: errorClass.class,
|
|
1163
|
+
delay_ms: decision.delay_ms
|
|
1164
|
+
},
|
|
1185
1165
|
"Retrying after delay"
|
|
1186
1166
|
);
|
|
1187
|
-
await new Promise(
|
|
1188
|
-
(resolve) => setTimeout(resolve, decision.delay_ms)
|
|
1189
|
-
);
|
|
1167
|
+
await new Promise((resolve) => setTimeout(resolve, decision.delay_ms));
|
|
1190
1168
|
}
|
|
1191
1169
|
}
|
|
1192
1170
|
}
|
|
1193
1171
|
log?.warn(
|
|
1194
|
-
{
|
|
1172
|
+
{
|
|
1173
|
+
event: "failover_budget_exhausted",
|
|
1174
|
+
total_retries: totalRetries,
|
|
1175
|
+
total_failovers: totalFailovers
|
|
1176
|
+
},
|
|
1195
1177
|
"Failover budget exhausted"
|
|
1196
1178
|
);
|
|
1197
1179
|
return {
|
|
@@ -1231,7 +1213,8 @@ var hasConfigChanged = (oldTarget, newTarget) => {
|
|
|
1231
1213
|
if (oldTarget.enabled !== newTarget.enabled) return true;
|
|
1232
1214
|
if (oldTarget.operator_priority !== newTarget.operator_priority) return true;
|
|
1233
1215
|
if (JSON.stringify(oldTarget.policy_tags) !== JSON.stringify(newTarget.policy_tags)) return true;
|
|
1234
|
-
if (JSON.stringify(oldTarget.capabilities) !== JSON.stringify(newTarget.capabilities))
|
|
1216
|
+
if (JSON.stringify(oldTarget.capabilities) !== JSON.stringify(newTarget.capabilities))
|
|
1217
|
+
return true;
|
|
1235
1218
|
if (oldTarget.retry_budget !== newTarget.retry_budget) return true;
|
|
1236
1219
|
if (oldTarget.failover_budget !== newTarget.failover_budget) return true;
|
|
1237
1220
|
if (oldTarget.concurrency_limit !== newTarget.concurrency_limit) return true;
|
|
@@ -1372,9 +1355,6 @@ var reloadConfig = (deps, rawConfig) => {
|
|
|
1372
1355
|
throw err;
|
|
1373
1356
|
}
|
|
1374
1357
|
};
|
|
1375
|
-
|
|
1376
|
-
// src/operator/plugin-tools.ts
|
|
1377
|
-
import { tool } from "@opencode-ai/plugin/tool";
|
|
1378
1358
|
var { schema: z3 } = tool;
|
|
1379
1359
|
var createOperatorTools = (deps) => ({
|
|
1380
1360
|
listTargets: tool({
|
|
@@ -1390,10 +1370,7 @@ var createOperatorTools = (deps) => ({
|
|
|
1390
1370
|
description: "Pause a target, preventing new requests from being routed to it.",
|
|
1391
1371
|
args: { target_id: z3.string().min(1) },
|
|
1392
1372
|
async execute(args) {
|
|
1393
|
-
deps.logger.info(
|
|
1394
|
-
{ op: "pauseTarget", target_id: args.target_id },
|
|
1395
|
-
"operator: pauseTarget"
|
|
1396
|
-
);
|
|
1373
|
+
deps.logger.info({ op: "pauseTarget", target_id: args.target_id }, "operator: pauseTarget");
|
|
1397
1374
|
const result = pauseTarget(deps, args.target_id);
|
|
1398
1375
|
return JSON.stringify(result, null, 2);
|
|
1399
1376
|
}
|
|
@@ -1402,10 +1379,7 @@ var createOperatorTools = (deps) => ({
|
|
|
1402
1379
|
description: "Resume a previously paused or disabled target, allowing new requests.",
|
|
1403
1380
|
args: { target_id: z3.string().min(1) },
|
|
1404
1381
|
async execute(args) {
|
|
1405
|
-
deps.logger.info(
|
|
1406
|
-
{ op: "resumeTarget", target_id: args.target_id },
|
|
1407
|
-
"operator: resumeTarget"
|
|
1408
|
-
);
|
|
1382
|
+
deps.logger.info({ op: "resumeTarget", target_id: args.target_id }, "operator: resumeTarget");
|
|
1409
1383
|
const result = resumeTarget(deps, args.target_id);
|
|
1410
1384
|
return JSON.stringify(result, null, 2);
|
|
1411
1385
|
}
|
|
@@ -1414,10 +1388,7 @@ var createOperatorTools = (deps) => ({
|
|
|
1414
1388
|
description: "Drain a target, allowing in-flight requests to complete but preventing new ones.",
|
|
1415
1389
|
args: { target_id: z3.string().min(1) },
|
|
1416
1390
|
async execute(args) {
|
|
1417
|
-
deps.logger.info(
|
|
1418
|
-
{ op: "drainTarget", target_id: args.target_id },
|
|
1419
|
-
"operator: drainTarget"
|
|
1420
|
-
);
|
|
1391
|
+
deps.logger.info({ op: "drainTarget", target_id: args.target_id }, "operator: drainTarget");
|
|
1421
1392
|
const result = drainTarget(deps, args.target_id);
|
|
1422
1393
|
return JSON.stringify(result, null, 2);
|
|
1423
1394
|
}
|
|
@@ -1456,11 +1427,6 @@ var createOperatorTools = (deps) => ({
|
|
|
1456
1427
|
}
|
|
1457
1428
|
})
|
|
1458
1429
|
});
|
|
1459
|
-
|
|
1460
|
-
// src/profiles/store.ts
|
|
1461
|
-
import { readFile, writeFile, rename, mkdir } from "fs/promises";
|
|
1462
|
-
import { homedir } from "os";
|
|
1463
|
-
import { join, dirname } from "path";
|
|
1464
1430
|
var PROFILES_DIR = join(homedir(), ".local", "share", "o-switcher");
|
|
1465
1431
|
var PROFILES_PATH = join(PROFILES_DIR, "profiles.json");
|
|
1466
1432
|
var loadProfiles = async (path = PROFILES_PATH) => {
|
|
@@ -1527,22 +1493,11 @@ var nextProfileId = (store, provider) => {
|
|
|
1527
1493
|
const maxN = Object.keys(store).filter((key) => key.startsWith(prefix)).map((key) => Number(key.slice(prefix.length))).filter((n) => !Number.isNaN(n)).reduce((max, n) => Math.max(max, n), 0);
|
|
1528
1494
|
return `${provider}-${maxN + 1}`;
|
|
1529
1495
|
};
|
|
1530
|
-
|
|
1531
|
-
// src/profiles/watcher.ts
|
|
1532
|
-
import { readFile as readFile2, watch } from "fs/promises";
|
|
1533
|
-
import { homedir as homedir2 } from "os";
|
|
1534
|
-
import { join as join2, dirname as dirname2 } from "path";
|
|
1535
|
-
var AUTH_JSON_PATH = join2(
|
|
1536
|
-
homedir2(),
|
|
1537
|
-
".local",
|
|
1538
|
-
"share",
|
|
1539
|
-
"opencode",
|
|
1540
|
-
"auth.json"
|
|
1541
|
-
);
|
|
1496
|
+
var AUTH_JSON_PATH = join(homedir(), ".local", "share", "opencode", "auth.json");
|
|
1542
1497
|
var DEBOUNCE_MS = 100;
|
|
1543
1498
|
var readAuthJson = async (path) => {
|
|
1544
1499
|
try {
|
|
1545
|
-
const content = await
|
|
1500
|
+
const content = await readFile(path, "utf-8");
|
|
1546
1501
|
return JSON.parse(content);
|
|
1547
1502
|
} catch {
|
|
1548
1503
|
return {};
|
|
@@ -1575,7 +1530,6 @@ var createAuthWatcher = (options) => {
|
|
|
1575
1530
|
let lastKnownAuth = {};
|
|
1576
1531
|
let abortController = null;
|
|
1577
1532
|
let debounceTimer = null;
|
|
1578
|
-
let watchPromise = null;
|
|
1579
1533
|
const processChange = async () => {
|
|
1580
1534
|
const newAuth = await readAuthJson(authPath);
|
|
1581
1535
|
let store = await loadProfiles(profPath);
|
|
@@ -1584,7 +1538,10 @@ var createAuthWatcher = (options) => {
|
|
|
1584
1538
|
if (!entry) continue;
|
|
1585
1539
|
const previousEntry = lastKnownAuth[provider];
|
|
1586
1540
|
if (previousEntry !== void 0 && !entriesEqual(previousEntry, entry)) {
|
|
1587
|
-
log?.info(
|
|
1541
|
+
log?.info(
|
|
1542
|
+
{ provider, action: "credential_changed" },
|
|
1543
|
+
"Credential change detected \u2014 saving previous credential"
|
|
1544
|
+
);
|
|
1588
1545
|
const prevCredential = toCredential(previousEntry);
|
|
1589
1546
|
const newStore = addProfile(store, provider, prevCredential);
|
|
1590
1547
|
if (newStore !== store) {
|
|
@@ -1630,12 +1587,15 @@ var createAuthWatcher = (options) => {
|
|
|
1630
1587
|
store = addProfile(store, provider, credential);
|
|
1631
1588
|
}
|
|
1632
1589
|
await saveProfiles(store, profPath);
|
|
1633
|
-
log?.info(
|
|
1590
|
+
log?.info(
|
|
1591
|
+
{ profiles_initialized: Object.keys(store).length },
|
|
1592
|
+
"Initialized profiles from auth.json"
|
|
1593
|
+
);
|
|
1634
1594
|
}
|
|
1635
1595
|
lastKnownAuth = currentAuth;
|
|
1636
1596
|
abortController = new AbortController();
|
|
1637
|
-
const parentDir =
|
|
1638
|
-
|
|
1597
|
+
const parentDir = dirname(authPath);
|
|
1598
|
+
(async () => {
|
|
1639
1599
|
try {
|
|
1640
1600
|
const watcher = watch(parentDir, {
|
|
1641
1601
|
signal: abortController.signal
|
|
@@ -1646,9 +1606,7 @@ var createAuthWatcher = (options) => {
|
|
|
1646
1606
|
}
|
|
1647
1607
|
}
|
|
1648
1608
|
} catch (err) {
|
|
1649
|
-
|
|
1650
|
-
if (name !== "AbortError") {
|
|
1651
|
-
}
|
|
1609
|
+
err.name;
|
|
1652
1610
|
}
|
|
1653
1611
|
})();
|
|
1654
1612
|
};
|
|
@@ -1661,17 +1619,13 @@ var createAuthWatcher = (options) => {
|
|
|
1661
1619
|
abortController.abort();
|
|
1662
1620
|
abortController = null;
|
|
1663
1621
|
}
|
|
1664
|
-
watchPromise = null;
|
|
1665
1622
|
log?.info("Auth watcher stopped");
|
|
1666
1623
|
};
|
|
1667
1624
|
return { start, stop };
|
|
1668
1625
|
};
|
|
1669
|
-
|
|
1670
|
-
// src/profiles/tools.ts
|
|
1671
|
-
import { tool as tool2 } from "@opencode-ai/plugin/tool";
|
|
1672
|
-
var { schema: z4 } = tool2;
|
|
1626
|
+
var { schema: z4 } = tool;
|
|
1673
1627
|
var createProfileTools = (options) => ({
|
|
1674
|
-
profilesList:
|
|
1628
|
+
profilesList: tool({
|
|
1675
1629
|
description: "List all saved auth profiles with provider, type, and creation date.",
|
|
1676
1630
|
args: {},
|
|
1677
1631
|
async execute() {
|
|
@@ -1686,7 +1640,7 @@ var createProfileTools = (options) => ({
|
|
|
1686
1640
|
return JSON.stringify(result, null, 2);
|
|
1687
1641
|
}
|
|
1688
1642
|
}),
|
|
1689
|
-
profilesRemove:
|
|
1643
|
+
profilesRemove: tool({
|
|
1690
1644
|
description: "Remove a saved auth profile by ID.",
|
|
1691
1645
|
args: { id: z4.string().min(1) },
|
|
1692
1646
|
async execute(args) {
|
|
@@ -1705,73 +1659,4 @@ var createProfileTools = (options) => ({
|
|
|
1705
1659
|
})
|
|
1706
1660
|
});
|
|
1707
1661
|
|
|
1708
|
-
export {
|
|
1709
|
-
DEFAULT_RETRY_BUDGET,
|
|
1710
|
-
DEFAULT_FAILOVER_BUDGET,
|
|
1711
|
-
DEFAULT_BACKOFF_BASE_MS,
|
|
1712
|
-
DEFAULT_BACKOFF_MULTIPLIER,
|
|
1713
|
-
DEFAULT_BACKOFF_MAX_MS,
|
|
1714
|
-
DEFAULT_BACKOFF_JITTER,
|
|
1715
|
-
DEFAULT_RETRY,
|
|
1716
|
-
DEFAULT_TIMEOUT_MS,
|
|
1717
|
-
BackoffConfigSchema,
|
|
1718
|
-
TargetConfigSchema,
|
|
1719
|
-
SwitcherConfigSchema,
|
|
1720
|
-
ConfigValidationError,
|
|
1721
|
-
validateConfig,
|
|
1722
|
-
discoverTargets,
|
|
1723
|
-
discoverTargetsFromProfiles,
|
|
1724
|
-
INITIAL_HEALTH_SCORE,
|
|
1725
|
-
DEFAULT_ALPHA,
|
|
1726
|
-
updateHealthScore,
|
|
1727
|
-
updateLatencyEma,
|
|
1728
|
-
TargetRegistry,
|
|
1729
|
-
createRegistry,
|
|
1730
|
-
TARGET_STATES,
|
|
1731
|
-
REDACT_PATHS,
|
|
1732
|
-
createAuditLogger,
|
|
1733
|
-
createRequestLogger,
|
|
1734
|
-
generateCorrelationId,
|
|
1735
|
-
ErrorClassSchema,
|
|
1736
|
-
isRetryable,
|
|
1737
|
-
getTargetStateTransition,
|
|
1738
|
-
DEFAULT_BACKOFF_PARAMS,
|
|
1739
|
-
computeBackoffMs,
|
|
1740
|
-
createRetryPolicy,
|
|
1741
|
-
EXCLUSION_REASONS,
|
|
1742
|
-
ADMISSION_RESULTS,
|
|
1743
|
-
createRoutingEventBus,
|
|
1744
|
-
DualBreaker,
|
|
1745
|
-
createCircuitBreaker,
|
|
1746
|
-
createConcurrencyTracker,
|
|
1747
|
-
normalizeLatency,
|
|
1748
|
-
computeScore,
|
|
1749
|
-
getExclusionReason,
|
|
1750
|
-
selectTarget,
|
|
1751
|
-
computeCooldownMs,
|
|
1752
|
-
createCooldownManager,
|
|
1753
|
-
checkHardRejects,
|
|
1754
|
-
createAdmissionController,
|
|
1755
|
-
createLogSubscriber,
|
|
1756
|
-
createFailoverOrchestrator,
|
|
1757
|
-
computeConfigDiff,
|
|
1758
|
-
applyConfigDiff,
|
|
1759
|
-
createRequestTraceBuffer,
|
|
1760
|
-
listTargets,
|
|
1761
|
-
pauseTarget,
|
|
1762
|
-
resumeTarget,
|
|
1763
|
-
drainTarget,
|
|
1764
|
-
disableTarget,
|
|
1765
|
-
inspectRequest,
|
|
1766
|
-
reloadConfig,
|
|
1767
|
-
createOperatorTools,
|
|
1768
|
-
loadProfiles,
|
|
1769
|
-
saveProfiles,
|
|
1770
|
-
addProfile,
|
|
1771
|
-
removeProfile,
|
|
1772
|
-
listProfiles,
|
|
1773
|
-
nextProfileId,
|
|
1774
|
-
createAuthWatcher,
|
|
1775
|
-
createProfileTools
|
|
1776
|
-
};
|
|
1777
|
-
//# sourceMappingURL=chunk-BTDKGS7P.js.map
|
|
1662
|
+
export { ADMISSION_RESULTS, BackoffConfigSchema, ConfigValidationError, DEFAULT_ALPHA, DEFAULT_BACKOFF_BASE_MS, DEFAULT_BACKOFF_JITTER, DEFAULT_BACKOFF_MAX_MS, DEFAULT_BACKOFF_MULTIPLIER, DEFAULT_BACKOFF_PARAMS, DEFAULT_FAILOVER_BUDGET, DEFAULT_RETRY, DEFAULT_RETRY_BUDGET, DEFAULT_TIMEOUT_MS, DualBreaker, EXCLUSION_REASONS, ErrorClassSchema, INITIAL_HEALTH_SCORE, REDACT_PATHS, SwitcherConfigSchema, TARGET_STATES, TargetConfigSchema, TargetRegistry, addProfile, applyConfigDiff, checkHardRejects, computeBackoffMs, computeConfigDiff, computeCooldownMs, computeScore, createAdmissionController, createAuditLogger, createAuthWatcher, createCircuitBreaker, createConcurrencyTracker, createCooldownManager, createFailoverOrchestrator, createLogSubscriber, createOperatorTools, createProfileTools, createRegistry, createRequestLogger, createRequestTraceBuffer, createRetryPolicy, createRoutingEventBus, disableTarget, discoverTargets, discoverTargetsFromProfiles, drainTarget, generateCorrelationId, getExclusionReason, getTargetStateTransition, inspectRequest, isRetryable, listProfiles, listTargets, loadProfiles, nextProfileId, normalizeLatency, pauseTarget, reloadConfig, removeProfile, resumeTarget, saveProfiles, selectTarget, updateHealthScore, updateLatencyEma, validateConfig };
|