@albinocrabs/o-switcher 0.1.1 → 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 +9 -0
- package/dist/{chunk-IKNWSNAS.js → chunk-7ITX5623.js} +76 -4
- package/dist/{chunk-VABBGKSR.cjs → chunk-TJ7ZGZHD.cjs} +76 -3
- package/dist/index.cjs +80 -145
- package/dist/index.js +9 -77
- package/dist/plugin.cjs +100 -17
- package/dist/plugin.d.cts +1 -1
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.js +85 -2
- package/package.json +11 -5
- package/src/registry/types.ts +65 -0
- package/src/state-bridge.ts +119 -0
- package/src/tui.tsx +214 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 57f1eb9: Add TUI sidebar panel and status dashboard for OpenCode
|
|
8
|
+
- Sidebar footer shows active profile, health %, and target count
|
|
9
|
+
- `/switcher` slash command opens full status dashboard with all targets
|
|
10
|
+
- File-based state bridge between server plugin and TUI (atomic writes, debounced)
|
|
11
|
+
|
|
3
12
|
All notable changes to this project will be documented in this file.
|
|
4
13
|
|
|
5
14
|
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
|
@@ -4,10 +4,10 @@ import crypto from 'crypto';
|
|
|
4
4
|
import { EventEmitter } from 'eventemitter3';
|
|
5
5
|
import { CircuitState, ConsecutiveBreaker, CountBreaker, BrokenCircuitError, IsolatedCircuitError, circuitBreaker, handleAll } from 'cockatiel';
|
|
6
6
|
import PQueue from 'p-queue';
|
|
7
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
7
8
|
import { readFile, mkdir, writeFile, rename, watch } from 'fs/promises';
|
|
8
9
|
import { homedir } from 'os';
|
|
9
10
|
import { join, dirname } from 'path';
|
|
10
|
-
import { tool } from '@opencode-ai/plugin/tool';
|
|
11
11
|
|
|
12
12
|
// src/config/defaults.ts
|
|
13
13
|
var DEFAULT_RETRY_BUDGET = 3;
|
|
@@ -1355,6 +1355,78 @@ var reloadConfig = (deps, rawConfig) => {
|
|
|
1355
1355
|
throw err;
|
|
1356
1356
|
}
|
|
1357
1357
|
};
|
|
1358
|
+
var { schema: z3 } = tool;
|
|
1359
|
+
var createOperatorTools = (deps) => ({
|
|
1360
|
+
listTargets: tool({
|
|
1361
|
+
description: "List all routing targets with health scores, states, and circuit breaker status.",
|
|
1362
|
+
args: {},
|
|
1363
|
+
async execute() {
|
|
1364
|
+
deps.logger.info({ op: "listTargets" }, "operator: listTargets");
|
|
1365
|
+
const result = listTargets(deps);
|
|
1366
|
+
return JSON.stringify(result, null, 2);
|
|
1367
|
+
}
|
|
1368
|
+
}),
|
|
1369
|
+
pauseTarget: tool({
|
|
1370
|
+
description: "Pause a target, preventing new requests from being routed to it.",
|
|
1371
|
+
args: { target_id: z3.string().min(1) },
|
|
1372
|
+
async execute(args) {
|
|
1373
|
+
deps.logger.info({ op: "pauseTarget", target_id: args.target_id }, "operator: pauseTarget");
|
|
1374
|
+
const result = pauseTarget(deps, args.target_id);
|
|
1375
|
+
return JSON.stringify(result, null, 2);
|
|
1376
|
+
}
|
|
1377
|
+
}),
|
|
1378
|
+
resumeTarget: tool({
|
|
1379
|
+
description: "Resume a previously paused or disabled target, allowing new requests.",
|
|
1380
|
+
args: { target_id: z3.string().min(1) },
|
|
1381
|
+
async execute(args) {
|
|
1382
|
+
deps.logger.info({ op: "resumeTarget", target_id: args.target_id }, "operator: resumeTarget");
|
|
1383
|
+
const result = resumeTarget(deps, args.target_id);
|
|
1384
|
+
return JSON.stringify(result, null, 2);
|
|
1385
|
+
}
|
|
1386
|
+
}),
|
|
1387
|
+
drainTarget: tool({
|
|
1388
|
+
description: "Drain a target, allowing in-flight requests to complete but preventing new ones.",
|
|
1389
|
+
args: { target_id: z3.string().min(1) },
|
|
1390
|
+
async execute(args) {
|
|
1391
|
+
deps.logger.info({ op: "drainTarget", target_id: args.target_id }, "operator: drainTarget");
|
|
1392
|
+
const result = drainTarget(deps, args.target_id);
|
|
1393
|
+
return JSON.stringify(result, null, 2);
|
|
1394
|
+
}
|
|
1395
|
+
}),
|
|
1396
|
+
disableTarget: tool({
|
|
1397
|
+
description: "Disable a target entirely, removing it from routing.",
|
|
1398
|
+
args: { target_id: z3.string().min(1) },
|
|
1399
|
+
async execute(args) {
|
|
1400
|
+
deps.logger.info(
|
|
1401
|
+
{ op: "disableTarget", target_id: args.target_id },
|
|
1402
|
+
"operator: disableTarget"
|
|
1403
|
+
);
|
|
1404
|
+
const result = disableTarget(deps, args.target_id);
|
|
1405
|
+
return JSON.stringify(result, null, 2);
|
|
1406
|
+
}
|
|
1407
|
+
}),
|
|
1408
|
+
inspectRequest: tool({
|
|
1409
|
+
description: "Inspect a request trace by ID, showing attempts, segments, and outcome.",
|
|
1410
|
+
args: { request_id: z3.string().min(1) },
|
|
1411
|
+
async execute(args) {
|
|
1412
|
+
deps.logger.info(
|
|
1413
|
+
{ op: "inspectRequest", request_id: args.request_id },
|
|
1414
|
+
"operator: inspectRequest"
|
|
1415
|
+
);
|
|
1416
|
+
const result = inspectRequest(deps, args.request_id);
|
|
1417
|
+
return JSON.stringify(result, null, 2);
|
|
1418
|
+
}
|
|
1419
|
+
}),
|
|
1420
|
+
reloadConfig: tool({
|
|
1421
|
+
description: "Reload routing configuration with diff-apply. Validates new config before applying.",
|
|
1422
|
+
args: { config: z3.record(z3.string(), z3.unknown()) },
|
|
1423
|
+
async execute(args) {
|
|
1424
|
+
deps.logger.info({ op: "reloadConfig" }, "operator: reloadConfig");
|
|
1425
|
+
const result = reloadConfig(deps, args.config);
|
|
1426
|
+
return JSON.stringify(result, null, 2);
|
|
1427
|
+
}
|
|
1428
|
+
})
|
|
1429
|
+
});
|
|
1358
1430
|
var PROFILES_DIR = join(homedir(), ".local", "share", "o-switcher");
|
|
1359
1431
|
var PROFILES_PATH = join(PROFILES_DIR, "profiles.json");
|
|
1360
1432
|
var loadProfiles = async (path = PROFILES_PATH) => {
|
|
@@ -1551,7 +1623,7 @@ var createAuthWatcher = (options) => {
|
|
|
1551
1623
|
};
|
|
1552
1624
|
return { start, stop };
|
|
1553
1625
|
};
|
|
1554
|
-
var { schema:
|
|
1626
|
+
var { schema: z4 } = tool;
|
|
1555
1627
|
var createProfileTools = (options) => ({
|
|
1556
1628
|
profilesList: tool({
|
|
1557
1629
|
description: "List all saved auth profiles with provider, type, and creation date.",
|
|
@@ -1570,7 +1642,7 @@ var createProfileTools = (options) => ({
|
|
|
1570
1642
|
}),
|
|
1571
1643
|
profilesRemove: tool({
|
|
1572
1644
|
description: "Remove a saved auth profile by ID.",
|
|
1573
|
-
args: { id:
|
|
1645
|
+
args: { id: z4.string().min(1) },
|
|
1574
1646
|
async execute(args) {
|
|
1575
1647
|
const store = await loadProfiles(options?.profilesPath);
|
|
1576
1648
|
const { store: newStore, removed } = removeProfile(store, args.id);
|
|
@@ -1587,4 +1659,4 @@ var createProfileTools = (options) => ({
|
|
|
1587
1659
|
})
|
|
1588
1660
|
});
|
|
1589
1661
|
|
|
1590
|
-
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, 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 };
|
|
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 };
|
|
@@ -6,10 +6,10 @@ var crypto = require('crypto');
|
|
|
6
6
|
var eventemitter3 = require('eventemitter3');
|
|
7
7
|
var cockatiel = require('cockatiel');
|
|
8
8
|
var PQueue = require('p-queue');
|
|
9
|
+
var tool = require('@opencode-ai/plugin/tool');
|
|
9
10
|
var promises = require('fs/promises');
|
|
10
11
|
var os = require('os');
|
|
11
12
|
var path = require('path');
|
|
12
|
-
var tool = require('@opencode-ai/plugin/tool');
|
|
13
13
|
|
|
14
14
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
15
|
|
|
@@ -1363,6 +1363,78 @@ var reloadConfig = (deps, rawConfig) => {
|
|
|
1363
1363
|
throw err;
|
|
1364
1364
|
}
|
|
1365
1365
|
};
|
|
1366
|
+
var { schema: z3 } = tool.tool;
|
|
1367
|
+
var createOperatorTools = (deps) => ({
|
|
1368
|
+
listTargets: tool.tool({
|
|
1369
|
+
description: "List all routing targets with health scores, states, and circuit breaker status.",
|
|
1370
|
+
args: {},
|
|
1371
|
+
async execute() {
|
|
1372
|
+
deps.logger.info({ op: "listTargets" }, "operator: listTargets");
|
|
1373
|
+
const result = listTargets(deps);
|
|
1374
|
+
return JSON.stringify(result, null, 2);
|
|
1375
|
+
}
|
|
1376
|
+
}),
|
|
1377
|
+
pauseTarget: tool.tool({
|
|
1378
|
+
description: "Pause a target, preventing new requests from being routed to it.",
|
|
1379
|
+
args: { target_id: z3.string().min(1) },
|
|
1380
|
+
async execute(args) {
|
|
1381
|
+
deps.logger.info({ op: "pauseTarget", target_id: args.target_id }, "operator: pauseTarget");
|
|
1382
|
+
const result = pauseTarget(deps, args.target_id);
|
|
1383
|
+
return JSON.stringify(result, null, 2);
|
|
1384
|
+
}
|
|
1385
|
+
}),
|
|
1386
|
+
resumeTarget: tool.tool({
|
|
1387
|
+
description: "Resume a previously paused or disabled target, allowing new requests.",
|
|
1388
|
+
args: { target_id: z3.string().min(1) },
|
|
1389
|
+
async execute(args) {
|
|
1390
|
+
deps.logger.info({ op: "resumeTarget", target_id: args.target_id }, "operator: resumeTarget");
|
|
1391
|
+
const result = resumeTarget(deps, args.target_id);
|
|
1392
|
+
return JSON.stringify(result, null, 2);
|
|
1393
|
+
}
|
|
1394
|
+
}),
|
|
1395
|
+
drainTarget: tool.tool({
|
|
1396
|
+
description: "Drain a target, allowing in-flight requests to complete but preventing new ones.",
|
|
1397
|
+
args: { target_id: z3.string().min(1) },
|
|
1398
|
+
async execute(args) {
|
|
1399
|
+
deps.logger.info({ op: "drainTarget", target_id: args.target_id }, "operator: drainTarget");
|
|
1400
|
+
const result = drainTarget(deps, args.target_id);
|
|
1401
|
+
return JSON.stringify(result, null, 2);
|
|
1402
|
+
}
|
|
1403
|
+
}),
|
|
1404
|
+
disableTarget: tool.tool({
|
|
1405
|
+
description: "Disable a target entirely, removing it from routing.",
|
|
1406
|
+
args: { target_id: z3.string().min(1) },
|
|
1407
|
+
async execute(args) {
|
|
1408
|
+
deps.logger.info(
|
|
1409
|
+
{ op: "disableTarget", target_id: args.target_id },
|
|
1410
|
+
"operator: disableTarget"
|
|
1411
|
+
);
|
|
1412
|
+
const result = disableTarget(deps, args.target_id);
|
|
1413
|
+
return JSON.stringify(result, null, 2);
|
|
1414
|
+
}
|
|
1415
|
+
}),
|
|
1416
|
+
inspectRequest: tool.tool({
|
|
1417
|
+
description: "Inspect a request trace by ID, showing attempts, segments, and outcome.",
|
|
1418
|
+
args: { request_id: z3.string().min(1) },
|
|
1419
|
+
async execute(args) {
|
|
1420
|
+
deps.logger.info(
|
|
1421
|
+
{ op: "inspectRequest", request_id: args.request_id },
|
|
1422
|
+
"operator: inspectRequest"
|
|
1423
|
+
);
|
|
1424
|
+
const result = inspectRequest(deps, args.request_id);
|
|
1425
|
+
return JSON.stringify(result, null, 2);
|
|
1426
|
+
}
|
|
1427
|
+
}),
|
|
1428
|
+
reloadConfig: tool.tool({
|
|
1429
|
+
description: "Reload routing configuration with diff-apply. Validates new config before applying.",
|
|
1430
|
+
args: { config: z3.record(z3.string(), z3.unknown()) },
|
|
1431
|
+
async execute(args) {
|
|
1432
|
+
deps.logger.info({ op: "reloadConfig" }, "operator: reloadConfig");
|
|
1433
|
+
const result = reloadConfig(deps, args.config);
|
|
1434
|
+
return JSON.stringify(result, null, 2);
|
|
1435
|
+
}
|
|
1436
|
+
})
|
|
1437
|
+
});
|
|
1366
1438
|
var PROFILES_DIR = path.join(os.homedir(), ".local", "share", "o-switcher");
|
|
1367
1439
|
var PROFILES_PATH = path.join(PROFILES_DIR, "profiles.json");
|
|
1368
1440
|
var loadProfiles = async (path = PROFILES_PATH) => {
|
|
@@ -1559,7 +1631,7 @@ var createAuthWatcher = (options) => {
|
|
|
1559
1631
|
};
|
|
1560
1632
|
return { start, stop };
|
|
1561
1633
|
};
|
|
1562
|
-
var { schema:
|
|
1634
|
+
var { schema: z4 } = tool.tool;
|
|
1563
1635
|
var createProfileTools = (options) => ({
|
|
1564
1636
|
profilesList: tool.tool({
|
|
1565
1637
|
description: "List all saved auth profiles with provider, type, and creation date.",
|
|
@@ -1578,7 +1650,7 @@ var createProfileTools = (options) => ({
|
|
|
1578
1650
|
}),
|
|
1579
1651
|
profilesRemove: tool.tool({
|
|
1580
1652
|
description: "Remove a saved auth profile by ID.",
|
|
1581
|
-
args: { id:
|
|
1653
|
+
args: { id: z4.string().min(1) },
|
|
1582
1654
|
async execute(args) {
|
|
1583
1655
|
const store = await loadProfiles(options?.profilesPath);
|
|
1584
1656
|
const { store: newStore, removed } = removeProfile(store, args.id);
|
|
@@ -1632,6 +1704,7 @@ exports.createConcurrencyTracker = createConcurrencyTracker;
|
|
|
1632
1704
|
exports.createCooldownManager = createCooldownManager;
|
|
1633
1705
|
exports.createFailoverOrchestrator = createFailoverOrchestrator;
|
|
1634
1706
|
exports.createLogSubscriber = createLogSubscriber;
|
|
1707
|
+
exports.createOperatorTools = createOperatorTools;
|
|
1635
1708
|
exports.createProfileTools = createProfileTools;
|
|
1636
1709
|
exports.createRegistry = createRegistry;
|
|
1637
1710
|
exports.createRequestLogger = createRequestLogger;
|