nexus-agents 10.2.0 → 11.0.1
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 +4 -1
- package/dist/{ast-rule-runner-RI7ZJ4ZI.js → ast-rule-runner-RL6XHXJB.js} +5 -5
- package/dist/{child-mcp-config-73BX4OTR.js → child-mcp-config-ADRNVJJB.js} +5 -5
- package/dist/{chunk-TDUXUVUU.js → chunk-3LE73MVE.js} +2 -2
- package/dist/{chunk-FNHVTDHZ.js → chunk-63YC42C2.js} +3 -3
- package/dist/{chunk-U37AITDA.js → chunk-6JMULY2R.js} +2 -2
- package/dist/{chunk-LP5ISXKM.js → chunk-6Y55UUWK.js} +5 -5
- package/dist/{chunk-QM5W3T3O.js → chunk-A7XALVN3.js} +2 -2
- package/dist/{chunk-JGY5HMWI.js → chunk-AX5DBV3M.js} +2 -2
- package/dist/{chunk-VLOPXV47.js → chunk-BHH3PKI6.js} +7 -7
- package/dist/{chunk-TW6PJYTP.js → chunk-C73VZSC5.js} +1 -1
- package/dist/chunk-C73VZSC5.js.map +1 -0
- package/dist/{chunk-LWOX65CP.js → chunk-CH6IBWXH.js} +31 -31
- package/dist/chunk-CH6IBWXH.js.map +1 -0
- package/dist/{chunk-RIYFVDYN.js → chunk-CSDGNGAG.js} +2 -2
- package/dist/{chunk-WD4DENPC.js → chunk-CXRZQ4NH.js} +3 -3
- package/dist/{chunk-SS53PZO6.js → chunk-DYV7XS26.js} +5 -5
- package/dist/{chunk-WKFPKPEM.js → chunk-EBOAFTOG.js} +458 -134
- package/dist/chunk-EBOAFTOG.js.map +1 -0
- package/dist/chunk-FMIALWCD.js +1046 -0
- package/dist/chunk-FMIALWCD.js.map +1 -0
- package/dist/{chunk-GAX75XCV.js → chunk-G2OI3KA4.js} +2 -2
- package/dist/{chunk-QG3VDSWD.js → chunk-GU2HV6KI.js} +3 -3
- package/dist/{chunk-2TA6KTRF.js → chunk-HBEZ2J4F.js} +16 -18
- package/dist/{chunk-2TA6KTRF.js.map → chunk-HBEZ2J4F.js.map} +1 -1
- package/dist/{chunk-QRC3QLUQ.js → chunk-HL3EHBKJ.js} +1086 -14
- package/dist/chunk-HL3EHBKJ.js.map +1 -0
- package/dist/{chunk-3UWT434E.js → chunk-I2THTVA4.js} +17 -10
- package/dist/chunk-I2THTVA4.js.map +1 -0
- package/dist/{chunk-OMSKBBL7.js → chunk-JKLXFENK.js} +18 -19
- package/dist/chunk-JKLXFENK.js.map +1 -0
- package/dist/{chunk-2DYCHHWW.js → chunk-KSSPY5KX.js} +4 -4
- package/dist/{chunk-23QA6GQD.js → chunk-LAUBKK25.js} +7 -7
- package/dist/{chunk-7EV3QAKO.js → chunk-MTB5BXYJ.js} +2 -2
- package/dist/{chunk-XSAUVC6D.js → chunk-NLFCAEEK.js} +2 -2
- package/dist/{chunk-PXYSAG5B.js → chunk-NVE262E3.js} +2 -2
- package/dist/{chunk-V26YQQK4.js → chunk-NY7IMAG5.js} +2 -2
- package/dist/{chunk-KLSNDPU6.js → chunk-NZA7EIZO.js} +17 -32
- package/dist/chunk-NZA7EIZO.js.map +1 -0
- package/dist/{chunk-XDUTAUK3.js → chunk-OKMSQJQJ.js} +75 -61
- package/dist/chunk-OKMSQJQJ.js.map +1 -0
- package/dist/{chunk-KASBBKAQ.js → chunk-OYAHRYFI.js} +54 -54
- package/dist/chunk-OYAHRYFI.js.map +1 -0
- package/dist/{chunk-GX5ZHZR2.js → chunk-PYAR3CVN.js} +7 -7
- package/dist/{chunk-EJ5EBTAV.js → chunk-QKWZZM2D.js} +2 -2
- package/dist/{chunk-FWQSOPBA.js → chunk-RR2Y2HVZ.js} +6 -6
- package/dist/{chunk-UE7A2XHM.js → chunk-RUHKJC3X.js} +2 -2
- package/dist/{chunk-D6E3LJGP.js → chunk-S4QYN2AV.js} +4 -4
- package/dist/{chunk-J44RKRGQ.js → chunk-TW4R2Q55.js} +4 -4
- package/dist/{chunk-QS5HGPCN.js → chunk-TWQEUAUG.js} +2 -2
- package/dist/{chunk-JPFRRNRR.js → chunk-U7HL7FGS.js} +2 -2
- package/dist/{chunk-JUUGLXSN.js → chunk-WAPGSEIM.js} +5 -5
- package/dist/{chunk-LXYT2P2Y.js → chunk-WILZPMEM.js} +3 -3
- package/dist/{chunk-4JJI34RV.js → chunk-WTMLF7QR.js} +3 -3
- package/dist/{chunk-SX2XWCGA.js → chunk-YIZ25Y4C.js} +22 -7
- package/dist/chunk-YIZ25Y4C.js.map +1 -0
- package/dist/{cli-auth-probe-A7ANEHJ7.js → cli-auth-probe-GUK3NXW3.js} +2 -2
- package/dist/{cli-circuit-breaker-62YOQQT3.js → cli-circuit-breaker-CDJKHR7R.js} +5 -5
- package/dist/{cli-readiness-O6EOQV4L.js → cli-readiness-7I5BYWLA.js} +9 -9
- package/dist/cli-server-gateway-626TKNQJ.js +34 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +81 -1216
- package/dist/cli.js.map +1 -1
- package/dist/{codebase-search-VMKI7FW4.js → codebase-search-YVO7BC33.js} +2 -2
- package/dist/{composite-router-YPZ3D5Y6.js → composite-router-YWSL4Y62.js} +4 -4
- package/dist/{consensus-vote-RNQQUBXO.js → consensus-vote-IRD5B3SC.js} +21 -22
- package/dist/{consensus-vote-types-DexdhOdB.d.ts → consensus-vote-types-C8RWaoxp.d.ts} +9 -8
- package/dist/{context-retriever-HVPLM6DH.js → context-retriever-CBOMGQAP.js} +11 -11
- package/dist/{doctor-deep-UZQTD65D.js → doctor-deep-2N3BHH6I.js} +5 -5
- package/dist/doctor-gateway-report-MRPZ7S34.js +20 -0
- package/dist/{doctor-live-QFLT5SDB.js → doctor-live-JBXIFI7A.js} +18 -16
- package/dist/doctor-live-JBXIFI7A.js.map +1 -0
- package/dist/{expert-bridge-O3UZXLGB.js → expert-bridge-TOMOJTMC.js} +6 -6
- package/dist/factory-H6IIGGFG.js +31 -0
- package/dist/factory-OLT24RLU.js +17 -0
- package/dist/{improvement-review-7IABFAU3.js → improvement-review-GFIINOQR.js} +8 -8
- package/dist/index.d.ts +69 -77
- package/dist/index.js +66 -57
- package/dist/index.js.map +1 -1
- package/dist/{init-opencode-RYPBCM4P.js → init-opencode-HXXKEIQF.js} +7 -7
- package/dist/issue-triage-ENNKQYKQ.js +20 -0
- package/dist/{learning-persistence-FB4U5AX2.js → learning-persistence-X24OOBYY.js} +3 -3
- package/dist/{model-config-helpers-QNV3KD2W.js → model-config-helpers-QFWX2QAO.js} +4 -4
- package/dist/{openai-compat-adapter-GPZDIR3D.js → openai-compat-adapter-N5WWPBGN.js} +7 -7
- package/dist/{outcome-store-adapter-WRONSTUD.js → outcome-store-adapter-Y6MS4JJK.js} +4 -4
- package/dist/{pr-reviewer-helpers-7SMNNDFJ.js → pr-reviewer-helpers-I6XFNU74.js} +6 -6
- package/dist/{registry-command-U7K6HIBK.js → registry-command-W7UHEQ6D.js} +4 -4
- package/dist/{repo-security-plan-LFWLVEL2.js → repo-security-plan-MENT6MS6.js} +5 -5
- package/dist/research-helpers-synthesize-BX4W3OA7.js +16 -0
- package/dist/{routing-memory-OCIVXKOU.js → routing-memory-7VHOAFNA.js} +4 -4
- package/dist/{session-memory-4LMEWO3X.js → session-memory-3EYP3GNQ.js} +5 -5
- package/dist/setup-config-VN2IOJ7S.js +13 -0
- package/dist/{setup-custom-api-QEPUCHOX.js → setup-custom-api-U3IGIJI7.js} +6 -6
- package/dist/setup-data-dir-UWOXXYFS.js +34 -0
- package/dist/{tool-memory-EFGFXF65.js → tool-memory-YYFE4EXT.js} +8 -8
- package/dist/{tool-memory-registry-adapters-FO6JVQSX.js → tool-memory-registry-adapters-IIORYBCC.js} +3 -3
- package/dist/unified-registry-IEPJHJHO.js +35 -0
- package/dist/{weather-report-PMYQ3WBW.js → weather-report-PJZFHSB7.js} +4 -4
- package/dist/weather-report-PJZFHSB7.js.map +1 -0
- package/node_modules/express-rate-limit/dist/index.cjs +9 -7
- package/node_modules/express-rate-limit/dist/index.mjs +9 -7
- package/node_modules/express-rate-limit/package.json +9 -9
- package/package.json +1 -1
- package/dist/chunk-3UWT434E.js.map +0 -1
- package/dist/chunk-KASBBKAQ.js.map +0 -1
- package/dist/chunk-KLSNDPU6.js.map +0 -1
- package/dist/chunk-LWOX65CP.js.map +0 -1
- package/dist/chunk-OMSKBBL7.js.map +0 -1
- package/dist/chunk-QRC3QLUQ.js.map +0 -1
- package/dist/chunk-SX2XWCGA.js.map +0 -1
- package/dist/chunk-TW6PJYTP.js.map +0 -1
- package/dist/chunk-UZJ6IPJR.js +0 -1057
- package/dist/chunk-UZJ6IPJR.js.map +0 -1
- package/dist/chunk-WKFPKPEM.js.map +0 -1
- package/dist/chunk-XDUTAUK3.js.map +0 -1
- package/dist/doctor-gateway-report-LRBPPJD4.js +0 -20
- package/dist/doctor-live-QFLT5SDB.js.map +0 -1
- package/dist/factory-KCKQQAEH.js +0 -31
- package/dist/factory-LUKUIB6C.js +0 -17
- package/dist/issue-triage-YBVJVAUU.js +0 -20
- package/dist/research-helpers-synthesize-X6MVAAKT.js +0 -16
- package/dist/setup-config-YNNNR4AE.js +0 -13
- package/dist/setup-data-dir-XM5DWH7R.js +0 -35
- package/dist/unified-registry-KLNC24HA.js +0 -36
- /package/dist/{ast-rule-runner-RI7ZJ4ZI.js.map → ast-rule-runner-RL6XHXJB.js.map} +0 -0
- /package/dist/{child-mcp-config-73BX4OTR.js.map → child-mcp-config-ADRNVJJB.js.map} +0 -0
- /package/dist/{chunk-TDUXUVUU.js.map → chunk-3LE73MVE.js.map} +0 -0
- /package/dist/{chunk-FNHVTDHZ.js.map → chunk-63YC42C2.js.map} +0 -0
- /package/dist/{chunk-U37AITDA.js.map → chunk-6JMULY2R.js.map} +0 -0
- /package/dist/{chunk-LP5ISXKM.js.map → chunk-6Y55UUWK.js.map} +0 -0
- /package/dist/{chunk-QM5W3T3O.js.map → chunk-A7XALVN3.js.map} +0 -0
- /package/dist/{chunk-JGY5HMWI.js.map → chunk-AX5DBV3M.js.map} +0 -0
- /package/dist/{chunk-VLOPXV47.js.map → chunk-BHH3PKI6.js.map} +0 -0
- /package/dist/{chunk-RIYFVDYN.js.map → chunk-CSDGNGAG.js.map} +0 -0
- /package/dist/{chunk-WD4DENPC.js.map → chunk-CXRZQ4NH.js.map} +0 -0
- /package/dist/{chunk-SS53PZO6.js.map → chunk-DYV7XS26.js.map} +0 -0
- /package/dist/{chunk-GAX75XCV.js.map → chunk-G2OI3KA4.js.map} +0 -0
- /package/dist/{chunk-QG3VDSWD.js.map → chunk-GU2HV6KI.js.map} +0 -0
- /package/dist/{chunk-2DYCHHWW.js.map → chunk-KSSPY5KX.js.map} +0 -0
- /package/dist/{chunk-23QA6GQD.js.map → chunk-LAUBKK25.js.map} +0 -0
- /package/dist/{chunk-7EV3QAKO.js.map → chunk-MTB5BXYJ.js.map} +0 -0
- /package/dist/{chunk-XSAUVC6D.js.map → chunk-NLFCAEEK.js.map} +0 -0
- /package/dist/{chunk-PXYSAG5B.js.map → chunk-NVE262E3.js.map} +0 -0
- /package/dist/{chunk-V26YQQK4.js.map → chunk-NY7IMAG5.js.map} +0 -0
- /package/dist/{chunk-GX5ZHZR2.js.map → chunk-PYAR3CVN.js.map} +0 -0
- /package/dist/{chunk-EJ5EBTAV.js.map → chunk-QKWZZM2D.js.map} +0 -0
- /package/dist/{chunk-FWQSOPBA.js.map → chunk-RR2Y2HVZ.js.map} +0 -0
- /package/dist/{chunk-UE7A2XHM.js.map → chunk-RUHKJC3X.js.map} +0 -0
- /package/dist/{chunk-D6E3LJGP.js.map → chunk-S4QYN2AV.js.map} +0 -0
- /package/dist/{chunk-J44RKRGQ.js.map → chunk-TW4R2Q55.js.map} +0 -0
- /package/dist/{chunk-QS5HGPCN.js.map → chunk-TWQEUAUG.js.map} +0 -0
- /package/dist/{chunk-JPFRRNRR.js.map → chunk-U7HL7FGS.js.map} +0 -0
- /package/dist/{chunk-JUUGLXSN.js.map → chunk-WAPGSEIM.js.map} +0 -0
- /package/dist/{chunk-LXYT2P2Y.js.map → chunk-WILZPMEM.js.map} +0 -0
- /package/dist/{chunk-4JJI34RV.js.map → chunk-WTMLF7QR.js.map} +0 -0
- /package/dist/{cli-auth-probe-A7ANEHJ7.js.map → cli-auth-probe-GUK3NXW3.js.map} +0 -0
- /package/dist/{cli-circuit-breaker-62YOQQT3.js.map → cli-circuit-breaker-CDJKHR7R.js.map} +0 -0
- /package/dist/{cli-readiness-O6EOQV4L.js.map → cli-readiness-7I5BYWLA.js.map} +0 -0
- /package/dist/{codebase-search-VMKI7FW4.js.map → cli-server-gateway-626TKNQJ.js.map} +0 -0
- /package/dist/{composite-router-YPZ3D5Y6.js.map → codebase-search-YVO7BC33.js.map} +0 -0
- /package/dist/{consensus-vote-RNQQUBXO.js.map → composite-router-YWSL4Y62.js.map} +0 -0
- /package/dist/{context-retriever-HVPLM6DH.js.map → consensus-vote-IRD5B3SC.js.map} +0 -0
- /package/dist/{doctor-deep-UZQTD65D.js.map → context-retriever-CBOMGQAP.js.map} +0 -0
- /package/dist/{doctor-gateway-report-LRBPPJD4.js.map → doctor-deep-2N3BHH6I.js.map} +0 -0
- /package/dist/{expert-bridge-O3UZXLGB.js.map → doctor-gateway-report-MRPZ7S34.js.map} +0 -0
- /package/dist/{factory-KCKQQAEH.js.map → expert-bridge-TOMOJTMC.js.map} +0 -0
- /package/dist/{factory-LUKUIB6C.js.map → factory-H6IIGGFG.js.map} +0 -0
- /package/dist/{improvement-review-7IABFAU3.js.map → factory-OLT24RLU.js.map} +0 -0
- /package/dist/{issue-triage-YBVJVAUU.js.map → improvement-review-GFIINOQR.js.map} +0 -0
- /package/dist/{init-opencode-RYPBCM4P.js.map → init-opencode-HXXKEIQF.js.map} +0 -0
- /package/dist/{learning-persistence-FB4U5AX2.js.map → issue-triage-ENNKQYKQ.js.map} +0 -0
- /package/dist/{model-config-helpers-QNV3KD2W.js.map → learning-persistence-X24OOBYY.js.map} +0 -0
- /package/dist/{openai-compat-adapter-GPZDIR3D.js.map → model-config-helpers-QFWX2QAO.js.map} +0 -0
- /package/dist/{pr-reviewer-helpers-7SMNNDFJ.js.map → openai-compat-adapter-N5WWPBGN.js.map} +0 -0
- /package/dist/{outcome-store-adapter-WRONSTUD.js.map → outcome-store-adapter-Y6MS4JJK.js.map} +0 -0
- /package/dist/{repo-security-plan-LFWLVEL2.js.map → pr-reviewer-helpers-I6XFNU74.js.map} +0 -0
- /package/dist/{registry-command-U7K6HIBK.js.map → registry-command-W7UHEQ6D.js.map} +0 -0
- /package/dist/{research-helpers-synthesize-X6MVAAKT.js.map → repo-security-plan-MENT6MS6.js.map} +0 -0
- /package/dist/{routing-memory-OCIVXKOU.js.map → research-helpers-synthesize-BX4W3OA7.js.map} +0 -0
- /package/dist/{session-memory-4LMEWO3X.js.map → routing-memory-7VHOAFNA.js.map} +0 -0
- /package/dist/{setup-config-YNNNR4AE.js.map → session-memory-3EYP3GNQ.js.map} +0 -0
- /package/dist/{setup-data-dir-XM5DWH7R.js.map → setup-config-VN2IOJ7S.js.map} +0 -0
- /package/dist/{setup-custom-api-QEPUCHOX.js.map → setup-custom-api-U3IGIJI7.js.map} +0 -0
- /package/dist/{tool-memory-EFGFXF65.js.map → setup-data-dir-UWOXXYFS.js.map} +0 -0
- /package/dist/{tool-memory-registry-adapters-FO6JVQSX.js.map → tool-memory-YYFE4EXT.js.map} +0 -0
- /package/dist/{unified-registry-KLNC24HA.js.map → tool-memory-registry-adapters-IIORYBCC.js.map} +0 -0
- /package/dist/{weather-report-PMYQ3WBW.js.map → unified-registry-IEPJHJHO.js.map} +0 -0
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ensureGatewayDiscovered,
|
|
3
|
+
gatewayDiscoveryGeneration,
|
|
3
4
|
gatewayDiscoveryStatus,
|
|
4
5
|
getAvailabilityCache
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-WILZPMEM.js";
|
|
6
7
|
import {
|
|
7
8
|
extractSystemPrompt
|
|
8
9
|
} from "./chunk-QZUG667L.js";
|
|
9
10
|
import {
|
|
10
11
|
CUSTOM_API_DEFAULT_MODEL
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-NLFCAEEK.js";
|
|
12
13
|
import {
|
|
13
14
|
nexusMkdtempSync
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-U7HL7FGS.js";
|
|
15
16
|
import {
|
|
16
17
|
AdapterModelError,
|
|
17
18
|
BaseAdapter,
|
|
@@ -19,22 +20,23 @@ import {
|
|
|
19
20
|
createStream,
|
|
20
21
|
planOptionalParams,
|
|
21
22
|
readGatewayTransport,
|
|
23
|
+
readOpenAICompatEndpoint,
|
|
22
24
|
readOpenAICompatEnv,
|
|
23
25
|
requireApiKey,
|
|
24
26
|
validateApiKeyPresence,
|
|
25
27
|
validateOpenAICompatBaseUrl,
|
|
26
28
|
withGatewayUsageRecording
|
|
27
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-NZA7EIZO.js";
|
|
28
30
|
import {
|
|
29
31
|
hostnameOf,
|
|
30
32
|
readCustomApiSurface,
|
|
31
33
|
readDirectOpenAiSurface,
|
|
32
34
|
readGatewayEnv,
|
|
33
35
|
redactApiKey
|
|
34
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-OYAHRYFI.js";
|
|
35
37
|
import {
|
|
36
38
|
probeCli
|
|
37
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-C73VZSC5.js";
|
|
38
40
|
import {
|
|
39
41
|
BaseCliAdapter,
|
|
40
42
|
ClaudeCliAdapter,
|
|
@@ -46,15 +48,18 @@ import {
|
|
|
46
48
|
getTimeoutForTask,
|
|
47
49
|
listModelsForCli,
|
|
48
50
|
spawnCwd
|
|
49
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-LAUBKK25.js";
|
|
50
52
|
import {
|
|
51
|
-
getCliCircuitBreakerSnapshot
|
|
52
|
-
|
|
53
|
+
getCliCircuitBreakerSnapshot,
|
|
54
|
+
getDefaultCliCircuitBreakerRegistry
|
|
55
|
+
} from "./chunk-YIZ25Y4C.js";
|
|
53
56
|
import {
|
|
54
57
|
sanitizeOutput
|
|
55
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-A7XALVN3.js";
|
|
56
59
|
import {
|
|
60
|
+
CircularBuffer,
|
|
57
61
|
CliCircuitBreaker,
|
|
62
|
+
ConfigError,
|
|
58
63
|
DEFAULT_CIRCUIT_BREAKER_CONFIG,
|
|
59
64
|
ErrorCode,
|
|
60
65
|
FALLBACK_CONTEXT_WINDOW,
|
|
@@ -64,9 +69,11 @@ import {
|
|
|
64
69
|
ModelError,
|
|
65
70
|
PROVIDER_ENV_KEYS,
|
|
66
71
|
RETRY_AFTER_CONTEXT_KEY,
|
|
72
|
+
UNRESOLVED_MODEL_ID,
|
|
67
73
|
accessModeConflict,
|
|
68
74
|
apiArmId,
|
|
69
75
|
asRecord,
|
|
76
|
+
breakerKeys,
|
|
70
77
|
buildCliCapabilityProfiles,
|
|
71
78
|
buildGatewaySlotRouterArm,
|
|
72
79
|
buildModelInfo,
|
|
@@ -81,36 +88,54 @@ import {
|
|
|
81
88
|
extractNumberField,
|
|
82
89
|
findCanonicalModel,
|
|
83
90
|
findInTreeByCli,
|
|
91
|
+
gatewayCostGap,
|
|
92
|
+
gatewaySlotEndpointOf,
|
|
84
93
|
getCliModelName,
|
|
85
94
|
getDefaultModelForCli,
|
|
86
95
|
getErrorMessage,
|
|
96
|
+
getInTreeCapabilitiesMatrix,
|
|
97
|
+
getRandomProvider,
|
|
87
98
|
getTimeProvider,
|
|
88
99
|
getTokenEstimator,
|
|
89
100
|
hasGatewaySlotCatalog,
|
|
101
|
+
isCallerCancelled,
|
|
90
102
|
isCallerInputCliError,
|
|
91
103
|
isCliAdmitted,
|
|
92
104
|
isCliDisabled,
|
|
105
|
+
isCliName,
|
|
106
|
+
isDurableCapacityError,
|
|
93
107
|
isDynamicModelsEnabled,
|
|
108
|
+
isEndpointArmId,
|
|
109
|
+
isGatewayArmId,
|
|
94
110
|
isHostUnavailableCliError,
|
|
95
111
|
isRateLimitLikeError,
|
|
96
112
|
isRateLimitText,
|
|
97
113
|
isReadOnlyAnalysis,
|
|
98
114
|
isRecord,
|
|
115
|
+
mapModelErrorToCategory,
|
|
99
116
|
ok,
|
|
117
|
+
recordRateLimitEvent,
|
|
100
118
|
resolveCliAlias,
|
|
101
119
|
resolveCliModelName,
|
|
120
|
+
resolveGatewayCostDeclaration,
|
|
102
121
|
resolveGatewayDefault,
|
|
103
122
|
resolveGatewaySlot,
|
|
104
123
|
resolveRetryAfterMs,
|
|
105
124
|
toModelTokenUsage,
|
|
125
|
+
toRateLimitError,
|
|
106
126
|
unenforcedAccessModeRefusal,
|
|
107
127
|
warnIfGatewayCostUndeclared,
|
|
108
128
|
withEnforcedAccessMode
|
|
109
|
-
} from "./chunk-
|
|
129
|
+
} from "./chunk-EBOAFTOG.js";
|
|
130
|
+
import {
|
|
131
|
+
parseBoolEnv
|
|
132
|
+
} from "./chunk-CH6IBWXH.js";
|
|
110
133
|
import {
|
|
111
134
|
CLI_NAMES,
|
|
112
135
|
CLI_SUBPROCESS_TIMEOUTS,
|
|
113
136
|
CODEX_MCP_TIMEOUTS,
|
|
137
|
+
TASK_SPECIALIZATION_MATRIX,
|
|
138
|
+
detectTaskCategory,
|
|
114
139
|
resolveClassGuardMs
|
|
115
140
|
} from "./chunk-4PN3UVZV.js";
|
|
116
141
|
|
|
@@ -235,7 +260,7 @@ var CliToModelAdapter = class {
|
|
|
235
260
|
* adapter default stays the honest report.
|
|
236
261
|
*/
|
|
237
262
|
reportedForwardedModel(forwarded) {
|
|
238
|
-
if (forwarded === void 0) return void 0;
|
|
263
|
+
if (forwarded === void 0 || !isCliName(this.cliAdapter.name)) return void 0;
|
|
239
264
|
return findCanonicalModel(this.cliAdapter.name, forwarded)?.id;
|
|
240
265
|
}
|
|
241
266
|
/**
|
|
@@ -1630,6 +1655,1019 @@ async function createAutoAdapter(config = {}) {
|
|
|
1630
1655
|
}
|
|
1631
1656
|
}
|
|
1632
1657
|
|
|
1658
|
+
// src/agents/collaboration/event-bus-helpers.ts
|
|
1659
|
+
import { randomUUID } from "node:crypto";
|
|
1660
|
+
var DEFAULT_MAX_HISTORY_SIZE = 1e3;
|
|
1661
|
+
var MAX_SUBSCRIPTIONS = 500;
|
|
1662
|
+
var MAX_TOPIC_PATTERN_LENGTH = 200;
|
|
1663
|
+
function patternToRegex(pattern) {
|
|
1664
|
+
if (pattern.length > MAX_TOPIC_PATTERN_LENGTH) {
|
|
1665
|
+
throw new Error(`Topic pattern exceeds maximum length of ${String(MAX_TOPIC_PATTERN_LENGTH)}`);
|
|
1666
|
+
}
|
|
1667
|
+
if (pattern === "*") {
|
|
1668
|
+
return /^.+$/;
|
|
1669
|
+
}
|
|
1670
|
+
const WILDCARD_PLACEHOLDER = "\0WILDCARD\0";
|
|
1671
|
+
const withPlaceholder = pattern.replace(/\*/g, WILDCARD_PLACEHOLDER);
|
|
1672
|
+
const escaped = withPlaceholder.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
1673
|
+
const withWildcards = escaped.replace(new RegExp(WILDCARD_PLACEHOLDER, "g"), "[^.]+");
|
|
1674
|
+
return new RegExp(`^${withWildcards}$`);
|
|
1675
|
+
}
|
|
1676
|
+
function topicMatchesPattern(topic, regex) {
|
|
1677
|
+
return regex.test(topic);
|
|
1678
|
+
}
|
|
1679
|
+
function generateEventId() {
|
|
1680
|
+
return `evt-${String(getTimeProvider().now())}-${randomUUID().slice(0, 8)}`;
|
|
1681
|
+
}
|
|
1682
|
+
function generateSubscriptionId() {
|
|
1683
|
+
return `sub-${String(getTimeProvider().now())}-${randomUUID().slice(0, 8)}`;
|
|
1684
|
+
}
|
|
1685
|
+
function applyHistoryFilters(history, filter) {
|
|
1686
|
+
let result = history;
|
|
1687
|
+
if (filter.topic !== void 0 && filter.topic !== "") {
|
|
1688
|
+
const regex = patternToRegex(filter.topic);
|
|
1689
|
+
result = result.filter((e) => topicMatchesPattern(e.topic, regex));
|
|
1690
|
+
}
|
|
1691
|
+
if (filter.sessionId !== void 0 && filter.sessionId !== "") {
|
|
1692
|
+
result = result.filter((e) => e.sessionId === filter.sessionId);
|
|
1693
|
+
}
|
|
1694
|
+
if (filter.correlationId !== void 0 && filter.correlationId !== "") {
|
|
1695
|
+
result = result.filter((e) => e.correlationId === filter.correlationId);
|
|
1696
|
+
}
|
|
1697
|
+
return applyTimestampFilters(result, filter);
|
|
1698
|
+
}
|
|
1699
|
+
function applyTimestampFilters(result, filter) {
|
|
1700
|
+
let filtered = result;
|
|
1701
|
+
if (filter.after !== void 0 && filter.after !== "") {
|
|
1702
|
+
const afterTimestamp = filter.after;
|
|
1703
|
+
filtered = filtered.filter((e) => e.timestamp > afterTimestamp);
|
|
1704
|
+
}
|
|
1705
|
+
if (filter.before !== void 0 && filter.before !== "") {
|
|
1706
|
+
const beforeTimestamp = filter.before;
|
|
1707
|
+
filtered = filtered.filter((e) => e.timestamp < beforeTimestamp);
|
|
1708
|
+
}
|
|
1709
|
+
return filtered;
|
|
1710
|
+
}
|
|
1711
|
+
function applyHistoryLimit(result, filter) {
|
|
1712
|
+
if (filter.limit !== void 0 && filter.limit > 0) {
|
|
1713
|
+
return result.slice(-filter.limit);
|
|
1714
|
+
}
|
|
1715
|
+
return result;
|
|
1716
|
+
}
|
|
1717
|
+
function enrichEvent(event) {
|
|
1718
|
+
return {
|
|
1719
|
+
...event,
|
|
1720
|
+
eventId: event.eventId || generateEventId(),
|
|
1721
|
+
timestamp: event.timestamp || getTimeProvider().nowIso()
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
function countMatchingSubscribers(topic, subscriptions) {
|
|
1725
|
+
let count = 0;
|
|
1726
|
+
for (const record of subscriptions.values()) {
|
|
1727
|
+
if (topicMatchesPattern(topic, record.regex)) {
|
|
1728
|
+
count++;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
return count;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
// src/agents/collaboration/event-bus.ts
|
|
1735
|
+
var CollaborationEventBus = class {
|
|
1736
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
1737
|
+
history;
|
|
1738
|
+
asyncHandling;
|
|
1739
|
+
logger;
|
|
1740
|
+
stats = {
|
|
1741
|
+
eventsEmitted: 0,
|
|
1742
|
+
subscriptionsCreated: 0,
|
|
1743
|
+
activeSubscriptions: 0,
|
|
1744
|
+
historySize: 0,
|
|
1745
|
+
errorCount: 0
|
|
1746
|
+
};
|
|
1747
|
+
constructor(options = {}) {
|
|
1748
|
+
const maxHistorySize = options.maxHistorySize ?? DEFAULT_MAX_HISTORY_SIZE;
|
|
1749
|
+
this.history = new CircularBuffer(maxHistorySize);
|
|
1750
|
+
this.asyncHandling = options.asyncHandling ?? false;
|
|
1751
|
+
this.logger = options.logger;
|
|
1752
|
+
}
|
|
1753
|
+
/**
|
|
1754
|
+
* Emit an event synchronously to all matching subscribers.
|
|
1755
|
+
*/
|
|
1756
|
+
emit(event) {
|
|
1757
|
+
const enrichedEvent = enrichEvent(event);
|
|
1758
|
+
this.addToHistory(enrichedEvent);
|
|
1759
|
+
this.stats.eventsEmitted++;
|
|
1760
|
+
this.logger?.debug("Event emitted", {
|
|
1761
|
+
topic: enrichedEvent.topic,
|
|
1762
|
+
eventId: enrichedEvent.eventId,
|
|
1763
|
+
subscriberCount: countMatchingSubscribers(enrichedEvent.topic, this.subscriptions)
|
|
1764
|
+
});
|
|
1765
|
+
for (const record of this.subscriptions.values()) {
|
|
1766
|
+
if (topicMatchesPattern(enrichedEvent.topic, record.regex)) {
|
|
1767
|
+
this.invokeListener(record, enrichedEvent);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
/**
|
|
1772
|
+
* Emit an event and wait for all async handlers to complete.
|
|
1773
|
+
*/
|
|
1774
|
+
async emitAsync(event) {
|
|
1775
|
+
const enrichedEvent = enrichEvent(event);
|
|
1776
|
+
this.addToHistory(enrichedEvent);
|
|
1777
|
+
this.stats.eventsEmitted++;
|
|
1778
|
+
this.logger?.debug("Async event emitted", {
|
|
1779
|
+
topic: enrichedEvent.topic,
|
|
1780
|
+
eventId: enrichedEvent.eventId
|
|
1781
|
+
});
|
|
1782
|
+
const promises = [];
|
|
1783
|
+
for (const record of this.subscriptions.values()) {
|
|
1784
|
+
if (topicMatchesPattern(enrichedEvent.topic, record.regex)) {
|
|
1785
|
+
promises.push(this.invokeListenerAsync(record, enrichedEvent));
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
await Promise.all(promises);
|
|
1789
|
+
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Subscribe to events matching a topic pattern.
|
|
1792
|
+
*/
|
|
1793
|
+
subscribe(pattern, listener) {
|
|
1794
|
+
if (this.subscriptions.size >= MAX_SUBSCRIPTIONS) {
|
|
1795
|
+
throw new Error(
|
|
1796
|
+
`Maximum subscription limit reached (${String(MAX_SUBSCRIPTIONS)}). Unsubscribe from unused patterns before adding new ones.`
|
|
1797
|
+
);
|
|
1798
|
+
}
|
|
1799
|
+
const id = generateSubscriptionId();
|
|
1800
|
+
const regex = patternToRegex(pattern);
|
|
1801
|
+
const record = {
|
|
1802
|
+
id,
|
|
1803
|
+
pattern,
|
|
1804
|
+
regex,
|
|
1805
|
+
listener
|
|
1806
|
+
};
|
|
1807
|
+
this.subscriptions.set(id, record);
|
|
1808
|
+
this.stats.subscriptionsCreated++;
|
|
1809
|
+
this.stats.activeSubscriptions = this.subscriptions.size;
|
|
1810
|
+
this.logger?.debug("Subscription created", { id, pattern });
|
|
1811
|
+
return {
|
|
1812
|
+
id,
|
|
1813
|
+
pattern,
|
|
1814
|
+
unsubscribe: () => {
|
|
1815
|
+
this.unsubscribe(id);
|
|
1816
|
+
}
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Unsubscribe from events.
|
|
1821
|
+
*/
|
|
1822
|
+
unsubscribe(subscriptionId) {
|
|
1823
|
+
const existed = this.subscriptions.delete(subscriptionId);
|
|
1824
|
+
if (existed) {
|
|
1825
|
+
this.stats.activeSubscriptions = this.subscriptions.size;
|
|
1826
|
+
this.logger?.debug("Subscription removed", { subscriptionId });
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
/**
|
|
1830
|
+
* Get event history matching filter criteria.
|
|
1831
|
+
*/
|
|
1832
|
+
getHistory(filter) {
|
|
1833
|
+
const historyArray = this.history.toArray();
|
|
1834
|
+
if (filter === void 0) {
|
|
1835
|
+
return historyArray;
|
|
1836
|
+
}
|
|
1837
|
+
const filtered = applyHistoryFilters(historyArray, filter);
|
|
1838
|
+
return applyHistoryLimit(filtered, filter);
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Clear event history.
|
|
1842
|
+
*/
|
|
1843
|
+
clearHistory() {
|
|
1844
|
+
this.history.clear();
|
|
1845
|
+
this.stats.historySize = 0;
|
|
1846
|
+
this.logger?.debug("History cleared");
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Get event bus statistics.
|
|
1850
|
+
*/
|
|
1851
|
+
getStats() {
|
|
1852
|
+
return { ...this.stats };
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Check if a topic pattern has any subscribers.
|
|
1856
|
+
*/
|
|
1857
|
+
hasSubscribers(pattern) {
|
|
1858
|
+
const regex = patternToRegex(pattern);
|
|
1859
|
+
for (const record of this.subscriptions.values()) {
|
|
1860
|
+
if (topicMatchesPattern(pattern, record.regex) || topicMatchesPattern(record.pattern, regex)) {
|
|
1861
|
+
return true;
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
return false;
|
|
1865
|
+
}
|
|
1866
|
+
/**
|
|
1867
|
+
* Add event to history, maintaining size limit via O(1) circular buffer.
|
|
1868
|
+
* @see Issue #407 - Performance optimization
|
|
1869
|
+
*/
|
|
1870
|
+
addToHistory(event) {
|
|
1871
|
+
this.history.push(event);
|
|
1872
|
+
this.stats.historySize = this.history.size;
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
* Invoke a listener synchronously with error isolation.
|
|
1876
|
+
*/
|
|
1877
|
+
invokeListener(record, event) {
|
|
1878
|
+
try {
|
|
1879
|
+
const result = record.listener(event);
|
|
1880
|
+
if (this.asyncHandling && result instanceof Promise) {
|
|
1881
|
+
result.catch((error) => {
|
|
1882
|
+
this.handleListenerError(record, event, error);
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
} catch (error) {
|
|
1886
|
+
this.handleListenerError(record, event, error);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
/**
|
|
1890
|
+
* Invoke a listener asynchronously.
|
|
1891
|
+
*/
|
|
1892
|
+
async invokeListenerAsync(record, event) {
|
|
1893
|
+
try {
|
|
1894
|
+
await record.listener(event);
|
|
1895
|
+
} catch (error) {
|
|
1896
|
+
this.handleListenerError(record, event, error);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
/**
|
|
1900
|
+
* Handle listener errors without disrupting other listeners.
|
|
1901
|
+
*/
|
|
1902
|
+
handleListenerError(record, event, error) {
|
|
1903
|
+
this.stats.errorCount++;
|
|
1904
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
1905
|
+
this.logger?.error("Event listener error", errorObj, {
|
|
1906
|
+
subscriptionId: record.id,
|
|
1907
|
+
pattern: record.pattern,
|
|
1908
|
+
eventTopic: event.topic,
|
|
1909
|
+
eventId: event.eventId
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1913
|
+
var globalBus = null;
|
|
1914
|
+
function getGlobalEventBus(options) {
|
|
1915
|
+
globalBus ??= new CollaborationEventBus(options);
|
|
1916
|
+
return globalBus;
|
|
1917
|
+
}
|
|
1918
|
+
function createEvent(topic, payload, options) {
|
|
1919
|
+
return {
|
|
1920
|
+
eventId: generateEventId(),
|
|
1921
|
+
timestamp: getTimeProvider().nowIso(),
|
|
1922
|
+
topic,
|
|
1923
|
+
payload,
|
|
1924
|
+
sessionId: options?.sessionId,
|
|
1925
|
+
correlationId: options?.correlationId
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
// src/agents/collaboration/event-bus-topics.ts
|
|
1930
|
+
var EventTopics = {
|
|
1931
|
+
// Session events
|
|
1932
|
+
SESSION_CREATED: "session.created",
|
|
1933
|
+
SESSION_STATUS_CHANGED: "session.status_changed",
|
|
1934
|
+
SESSION_PARTICIPANT_JOINED: "session.participant_joined",
|
|
1935
|
+
SESSION_RESULT_SUBMITTED: "session.result_submitted",
|
|
1936
|
+
SESSION_FINALIZED: "session.finalized",
|
|
1937
|
+
SESSION_ALL: "session.*",
|
|
1938
|
+
// Message events
|
|
1939
|
+
MESSAGE_SENT: "message.sent",
|
|
1940
|
+
MESSAGE_RECEIVED: "message.received",
|
|
1941
|
+
MESSAGE_ALL: "message.*",
|
|
1942
|
+
// Agent events
|
|
1943
|
+
AGENT_TASK_DELEGATED: "agent.task_delegated",
|
|
1944
|
+
AGENT_RESULT_BROADCAST: "agent.result_broadcast",
|
|
1945
|
+
AGENT_ALL: "agent.*",
|
|
1946
|
+
// Consensus events
|
|
1947
|
+
CONSENSUS_VOTE_REQUESTED: "consensus.vote_requested",
|
|
1948
|
+
CONSENSUS_VOTE_CAST: "consensus.vote_cast",
|
|
1949
|
+
CONSENSUS_REACHED: "consensus.reached",
|
|
1950
|
+
CONSENSUS_ALL: "consensus.*",
|
|
1951
|
+
// Protocol events
|
|
1952
|
+
PROTOCOL_STARTED: "protocol.started",
|
|
1953
|
+
PROTOCOL_ITERATION: "protocol.iteration",
|
|
1954
|
+
PROTOCOL_COMPLETED: "protocol.completed",
|
|
1955
|
+
PROTOCOL_ALL: "protocol.*",
|
|
1956
|
+
// Aegean phase events (Issue #216)
|
|
1957
|
+
AEGEAN_ROUND_STARTED: "protocol.aegean.round_started",
|
|
1958
|
+
AEGEAN_VOTE_COLLECTED: "protocol.aegean.vote_collected",
|
|
1959
|
+
AEGEAN_QUORUM_DETECTED: "protocol.aegean.quorum_detected",
|
|
1960
|
+
AEGEAN_ALL: "protocol.aegean.*",
|
|
1961
|
+
// Reflexion phase events (Issue #216)
|
|
1962
|
+
REFLEXION_CRITIQUE_STARTED: "protocol.reflexion.critique_started",
|
|
1963
|
+
REFLEXION_CRITIQUE_COMPLETED: "protocol.reflexion.critique_completed",
|
|
1964
|
+
REFLEXION_SYNTHESIS: "protocol.reflexion.synthesis",
|
|
1965
|
+
REFLEXION_ALL: "protocol.reflexion.*",
|
|
1966
|
+
// Trinity phase events (Issue #216)
|
|
1967
|
+
TRINITY_PHASE_STARTED: "protocol.trinity.phase_started",
|
|
1968
|
+
TRINITY_PHASE_COMPLETED: "protocol.trinity.phase_completed",
|
|
1969
|
+
TRINITY_ALL: "protocol.trinity.*",
|
|
1970
|
+
// Byzantine detection events (Issue #218)
|
|
1971
|
+
BYZANTINE_WEIGHT_UPDATED: "byzantine.weight_updated",
|
|
1972
|
+
BYZANTINE_PATTERN_DETECTED: "byzantine.pattern_detected",
|
|
1973
|
+
BYZANTINE_AGENT_FLAGGED: "byzantine.agent_flagged",
|
|
1974
|
+
BYZANTINE_COLLUSION_SUSPECTED: "byzantine.collusion_suspected",
|
|
1975
|
+
BYZANTINE_ALL: "byzantine.*",
|
|
1976
|
+
// Wildcard
|
|
1977
|
+
ALL: "*"
|
|
1978
|
+
};
|
|
1979
|
+
|
|
1980
|
+
// src/adapters/resilient-adapter.ts
|
|
1981
|
+
function createResilientAdapter(config) {
|
|
1982
|
+
return new ResilientAdapter(config);
|
|
1983
|
+
}
|
|
1984
|
+
var ResilientAdapter = class {
|
|
1985
|
+
logger;
|
|
1986
|
+
currentAdapter;
|
|
1987
|
+
currentSelection;
|
|
1988
|
+
hasEverDetected = false;
|
|
1989
|
+
health;
|
|
1990
|
+
preferredCli;
|
|
1991
|
+
failoverCallbacks = /* @__PURE__ */ new Set();
|
|
1992
|
+
circuitBreakerRegistry;
|
|
1993
|
+
circuitListener;
|
|
1994
|
+
disposed = false;
|
|
1995
|
+
defaultCliTimeoutMs;
|
|
1996
|
+
/** Inflight detection promise for coalescing concurrent calls (Issue #1423). */
|
|
1997
|
+
detectionPromise;
|
|
1998
|
+
/** The {@link gatewayDiscoveryGeneration} the current selection was made under (#6659). */
|
|
1999
|
+
detectedAtGeneration = 0;
|
|
2000
|
+
constructor(config) {
|
|
2001
|
+
this.logger = config?.logger ?? createLogger({ component: "resilient-adapter" });
|
|
2002
|
+
this.preferredCli = config?.preferredCli;
|
|
2003
|
+
this.defaultCliTimeoutMs = config?.defaultCliTimeoutMs;
|
|
2004
|
+
if (config?.circuitBreakerRegistry !== void 0) {
|
|
2005
|
+
this.attachCircuitBreakerRegistry(config.circuitBreakerRegistry);
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
// --- IModelAdapter properties (forwarded) ---
|
|
2009
|
+
/**
|
|
2010
|
+
* Before detection a CLI-pinned proxy identifies as the CLI it was asked
|
|
2011
|
+
* for (`cli-<name>`, the id its detected `CliToModelAdapter` will carry);
|
|
2012
|
+
* an unpinned proxy stays `resilient-proxy`.
|
|
2013
|
+
*
|
|
2014
|
+
* #6119: the voter fallover (#3587) keys seats by this id and skips a seat
|
|
2015
|
+
* whose key equals the fallback's. When codex never detected, the pinned
|
|
2016
|
+
* seat and the unpinned default were BOTH `resilient-proxy`, so the seat was
|
|
2017
|
+
* read as "already on the fallback" and errored instead of falling over.
|
|
2018
|
+
*/
|
|
2019
|
+
get providerId() {
|
|
2020
|
+
if (this.currentAdapter !== void 0) return this.currentAdapter.providerId;
|
|
2021
|
+
return this.preferredCli === void 0 ? "resilient-proxy" : `cli-${this.preferredCli}`;
|
|
2022
|
+
}
|
|
2023
|
+
get modelId() {
|
|
2024
|
+
return this.currentAdapter?.modelId ?? UNRESOLVED_MODEL_ID;
|
|
2025
|
+
}
|
|
2026
|
+
get capabilities() {
|
|
2027
|
+
return this.currentAdapter?.capabilities ?? [];
|
|
2028
|
+
}
|
|
2029
|
+
/**
|
|
2030
|
+
* The gateway arm of the adapter currently serving this proxy, when a
|
|
2031
|
+
* gateway model serves it (#6604 family slots); otherwise undefined. A
|
|
2032
|
+
* telemetry writer handed this proxy reads it (`isGatewayModelAdapter`) to
|
|
2033
|
+
* price the call by the gateway's `NEXUS_GATEWAY_COST` declaration instead
|
|
2034
|
+
* of a list price.
|
|
2035
|
+
*/
|
|
2036
|
+
get gatewayArm() {
|
|
2037
|
+
const arm = this.currentAdapter?.gatewayArm;
|
|
2038
|
+
return typeof arm === "string" && isEndpointArmId(arm) ? arm : void 0;
|
|
2039
|
+
}
|
|
2040
|
+
// --- IModelAdapter methods ---
|
|
2041
|
+
async complete(request) {
|
|
2042
|
+
const adapter = await this.ensureAdapter();
|
|
2043
|
+
if (adapter === void 0) {
|
|
2044
|
+
return err(new ModelError("No model adapter available"));
|
|
2045
|
+
}
|
|
2046
|
+
const result = await adapter.complete(request);
|
|
2047
|
+
if (!result.ok) {
|
|
2048
|
+
if (isRateLimitLikeError(result.error)) {
|
|
2049
|
+
const rlError = toRateLimitError(result.error, adapter.providerId);
|
|
2050
|
+
recordRateLimitEvent({
|
|
2051
|
+
provider: adapter.providerId,
|
|
2052
|
+
timestamp: getTimeProvider().now(),
|
|
2053
|
+
retryAfterMs: rlError.retryAfterMs
|
|
2054
|
+
});
|
|
2055
|
+
this.logger.warn("Rate limit detected", {
|
|
2056
|
+
provider: adapter.providerId,
|
|
2057
|
+
retryAfterMs: rlError.retryAfterMs
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
this.recordBreakerFailure(adapter, result.error);
|
|
2061
|
+
}
|
|
2062
|
+
return result;
|
|
2063
|
+
}
|
|
2064
|
+
async *stream(request) {
|
|
2065
|
+
const adapter = await this.ensureAdapter();
|
|
2066
|
+
if (adapter === void 0) {
|
|
2067
|
+
throw new ModelError("No model adapter available");
|
|
2068
|
+
}
|
|
2069
|
+
yield* adapter.stream(request);
|
|
2070
|
+
}
|
|
2071
|
+
async countTokens(text) {
|
|
2072
|
+
const adapter = await this.ensureAdapter();
|
|
2073
|
+
if (adapter === void 0) {
|
|
2074
|
+
return 0;
|
|
2075
|
+
}
|
|
2076
|
+
return adapter.countTokens(text);
|
|
2077
|
+
}
|
|
2078
|
+
validateConfig() {
|
|
2079
|
+
if (this.currentAdapter !== void 0) {
|
|
2080
|
+
return this.currentAdapter.validateConfig();
|
|
2081
|
+
}
|
|
2082
|
+
return ok(void 0);
|
|
2083
|
+
}
|
|
2084
|
+
// --- IResilientAdapter methods ---
|
|
2085
|
+
getHealth() {
|
|
2086
|
+
return this.health;
|
|
2087
|
+
}
|
|
2088
|
+
async refresh() {
|
|
2089
|
+
this.currentAdapter = void 0;
|
|
2090
|
+
this.currentSelection = void 0;
|
|
2091
|
+
this.detectionPromise = void 0;
|
|
2092
|
+
await this.ensureAdapter();
|
|
2093
|
+
}
|
|
2094
|
+
setPreferredCli(cli) {
|
|
2095
|
+
this.preferredCli = cli;
|
|
2096
|
+
this.currentAdapter = void 0;
|
|
2097
|
+
this.currentSelection = void 0;
|
|
2098
|
+
}
|
|
2099
|
+
onFailover(cb) {
|
|
2100
|
+
this.failoverCallbacks.add(cb);
|
|
2101
|
+
return () => {
|
|
2102
|
+
this.failoverCallbacks.delete(cb);
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
/**
|
|
2106
|
+
* Attach a circuit breaker registry for automatic failover.
|
|
2107
|
+
* When the current adapter's circuit opens, the cached adapter
|
|
2108
|
+
* is cleared so the next call triggers re-detection.
|
|
2109
|
+
*/
|
|
2110
|
+
/** The registry arming failover, or undefined if this adapter is unarmed (#4659). */
|
|
2111
|
+
getCircuitBreakerRegistry() {
|
|
2112
|
+
return this.circuitBreakerRegistry;
|
|
2113
|
+
}
|
|
2114
|
+
attachCircuitBreakerRegistry(registry) {
|
|
2115
|
+
this.detachCircuitBreakerRegistry();
|
|
2116
|
+
this.circuitBreakerRegistry = registry;
|
|
2117
|
+
this.circuitListener = (event) => {
|
|
2118
|
+
this.handleCircuitStateChange(event);
|
|
2119
|
+
};
|
|
2120
|
+
registry.addGlobalStateChangeListener(this.circuitListener);
|
|
2121
|
+
}
|
|
2122
|
+
dispose() {
|
|
2123
|
+
if (this.disposed) return;
|
|
2124
|
+
this.disposed = true;
|
|
2125
|
+
this.detachCircuitBreakerRegistry();
|
|
2126
|
+
this.failoverCallbacks.clear();
|
|
2127
|
+
}
|
|
2128
|
+
// --- Private methods ---
|
|
2129
|
+
async ensureAdapter() {
|
|
2130
|
+
if (this.currentAdapter === void 0) {
|
|
2131
|
+
await ensureGatewayDiscovered();
|
|
2132
|
+
} else {
|
|
2133
|
+
void ensureGatewayDiscovered();
|
|
2134
|
+
}
|
|
2135
|
+
if (this.currentAdapter !== void 0 && this.detectedAtGeneration !== gatewayDiscoveryGeneration()) {
|
|
2136
|
+
this.currentAdapter = void 0;
|
|
2137
|
+
this.currentSelection = void 0;
|
|
2138
|
+
this.hasEverDetected = false;
|
|
2139
|
+
}
|
|
2140
|
+
if (this.currentAdapter !== void 0) {
|
|
2141
|
+
return this.currentAdapter;
|
|
2142
|
+
}
|
|
2143
|
+
this.detectionPromise ??= this.detectAdapter().finally(() => {
|
|
2144
|
+
this.detectionPromise = void 0;
|
|
2145
|
+
});
|
|
2146
|
+
return this.detectionPromise;
|
|
2147
|
+
}
|
|
2148
|
+
async detectAdapter() {
|
|
2149
|
+
const generation = gatewayDiscoveryGeneration();
|
|
2150
|
+
try {
|
|
2151
|
+
this.logger.info("Detecting model adapter (lazy)");
|
|
2152
|
+
const config = {
|
|
2153
|
+
logger: this.logger,
|
|
2154
|
+
...this.preferredCli !== void 0 && { preferredCli: this.preferredCli },
|
|
2155
|
+
...this.defaultCliTimeoutMs !== void 0 && {
|
|
2156
|
+
defaultCliTimeoutMs: this.defaultCliTimeoutMs
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2159
|
+
const selection = await createAutoAdapter(config);
|
|
2160
|
+
this.applySelection(selection);
|
|
2161
|
+
this.detectedAtGeneration = generation;
|
|
2162
|
+
return this.currentAdapter;
|
|
2163
|
+
} catch (error) {
|
|
2164
|
+
const message = getErrorMessage(error);
|
|
2165
|
+
this.logger.warn("No model adapter available", { error: message });
|
|
2166
|
+
this.health = {
|
|
2167
|
+
source: "api",
|
|
2168
|
+
state: "unavailable",
|
|
2169
|
+
selectedAt: /* @__PURE__ */ new Date(),
|
|
2170
|
+
failoverCount: this.health?.failoverCount ?? 0,
|
|
2171
|
+
lastError: message
|
|
2172
|
+
};
|
|
2173
|
+
return void 0;
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
applySelection(selection) {
|
|
2177
|
+
const isFailover = this.hasEverDetected;
|
|
2178
|
+
const outgoingHealth = this.health;
|
|
2179
|
+
this.currentAdapter = selection.adapter;
|
|
2180
|
+
this.currentSelection = selection;
|
|
2181
|
+
this.hasEverDetected = true;
|
|
2182
|
+
const source = mapSelectionSource(selection);
|
|
2183
|
+
const failoverCount = (this.health?.failoverCount ?? 0) + (isFailover ? 1 : 0);
|
|
2184
|
+
this.health = {
|
|
2185
|
+
source,
|
|
2186
|
+
state: "healthy",
|
|
2187
|
+
selectedAt: /* @__PURE__ */ new Date(),
|
|
2188
|
+
failoverCount,
|
|
2189
|
+
...selection.modelVerified !== void 0 && { modelVerified: selection.modelVerified }
|
|
2190
|
+
};
|
|
2191
|
+
this.logger.info("Adapter selected", {
|
|
2192
|
+
source: selection.source,
|
|
2193
|
+
name: selection.name,
|
|
2194
|
+
model: selection.adapter.modelId,
|
|
2195
|
+
provider: selection.adapter.providerId,
|
|
2196
|
+
failover: isFailover
|
|
2197
|
+
});
|
|
2198
|
+
if (isFailover) {
|
|
2199
|
+
this.emitFailover(
|
|
2200
|
+
outgoingHealth === void 0 ? void 0 : { ...outgoingHealth, failoverCount }
|
|
2201
|
+
);
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
/**
|
|
2205
|
+
* Record a direct-API failure to the circuit breaker so API adapters get the
|
|
2206
|
+
* same degradation/failover learning that CLI subprocess failures get (#3423).
|
|
2207
|
+
*
|
|
2208
|
+
* The shared internal key policy preserves selected CLI slots, prefixes API
|
|
2209
|
+
* vendor names, and isolates gateway endpoints by slot (#7070).
|
|
2210
|
+
*
|
|
2211
|
+
* A CLI selection is NOT recorded here (#6712). Its CLI adapter's retry loop
|
|
2212
|
+
* (`executeCliRetryLoop`) has already recorded the failure in the same
|
|
2213
|
+
* registry under its route key, with the category taken from
|
|
2214
|
+
* the CliError code. Recording it again counted every CLI failure twice (the
|
|
2215
|
+
* second time usually as `unknown`), so the breaker opened at about half its
|
|
2216
|
+
* configured threshold. Only a direct-API selection, which has no inner
|
|
2217
|
+
* recorder, is recorded by this layer.
|
|
2218
|
+
*
|
|
2219
|
+
* Rate-limit failures are deliberately skipped here: they are already recorded
|
|
2220
|
+
* by the `recordRateLimitEvent` telemetry branch in `complete()`, and the
|
|
2221
|
+
* breaker counts rate limits as failures by default
|
|
2222
|
+
* (`countRateLimitsAsFailures`), so recording them again would double-count.
|
|
2223
|
+
*
|
|
2224
|
+
* Security (#3422 constraint): the logged payload carries only `provider` and
|
|
2225
|
+
* `category` — never `error.message`, the request, or the ModelError object,
|
|
2226
|
+
* any of which could embed credentials.
|
|
2227
|
+
*/
|
|
2228
|
+
recordBreakerFailure(adapter, error) {
|
|
2229
|
+
if (this.circuitBreakerRegistry === void 0 || this.currentSelection === void 0) {
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
if (this.currentSelection.source === "cli") return;
|
|
2233
|
+
if (isCallerCancelled(error)) return;
|
|
2234
|
+
const category = mapModelErrorToCategory(error);
|
|
2235
|
+
const breaker = this.circuitBreakerRegistry.getArmBreaker(
|
|
2236
|
+
breakerKeys.forArm({
|
|
2237
|
+
name: this.currentSelection.name,
|
|
2238
|
+
model: adapter.modelId,
|
|
2239
|
+
gatewayArm: this.gatewayArm
|
|
2240
|
+
})
|
|
2241
|
+
);
|
|
2242
|
+
if (isDurableCapacityError(error)) {
|
|
2243
|
+
breaker.recordFailure(category);
|
|
2244
|
+
this.logger.warn("Durable capacity cap recorded to circuit breaker", {
|
|
2245
|
+
provider: adapter.providerId,
|
|
2246
|
+
category
|
|
2247
|
+
});
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
if (category === "rate_limit" || isRateLimitLikeError(error)) {
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
breaker.recordFailure(category);
|
|
2254
|
+
this.logger.warn("API failure recorded to circuit breaker", {
|
|
2255
|
+
provider: adapter.providerId,
|
|
2256
|
+
category
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
handleCircuitStateChange(event) {
|
|
2260
|
+
if (event.newState !== "open") return;
|
|
2261
|
+
const selection = this.currentSelection;
|
|
2262
|
+
if (selection === void 0 || breakerKeys.forArm({
|
|
2263
|
+
name: selection.name,
|
|
2264
|
+
model: this.currentAdapter?.modelId,
|
|
2265
|
+
gatewayArm: this.gatewayArm
|
|
2266
|
+
}) !== event.armId) {
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
this.logger.warn("Current adapter circuit opened, clearing cache for re-detection", {
|
|
2270
|
+
cli: event.cliName,
|
|
2271
|
+
reason: event.reason
|
|
2272
|
+
});
|
|
2273
|
+
this.currentAdapter = void 0;
|
|
2274
|
+
this.currentSelection = void 0;
|
|
2275
|
+
if (this.health !== void 0) {
|
|
2276
|
+
this.health = {
|
|
2277
|
+
...this.health,
|
|
2278
|
+
state: "degraded",
|
|
2279
|
+
lastError: event.reason
|
|
2280
|
+
};
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
/**
|
|
2284
|
+
* Emit the failover signal describing the adapter we failed AWAY FROM (#4670).
|
|
2285
|
+
*
|
|
2286
|
+
* This used to read `this.health`, which `applySelection` had just replaced
|
|
2287
|
+
* with the incoming adapter's `state: 'healthy'`. So every `adapter.failover`
|
|
2288
|
+
* payload said healthy, `unhealthyCliFrom` (`observability/failover-signals.ts`)
|
|
2289
|
+
* returned undefined for all of them, and `signal.swarm_unhealthy` was never
|
|
2290
|
+
* produced by this — the more reliable of its two producers.
|
|
2291
|
+
*
|
|
2292
|
+
* An undefined outgoing health emits NOTHING rather than falling back to the
|
|
2293
|
+
* current one. Falling back would silently restore the old behaviour, and a
|
|
2294
|
+
* failover whose prior state we cannot describe is not evidence that the
|
|
2295
|
+
* prior adapter was healthy.
|
|
2296
|
+
*/
|
|
2297
|
+
emitFailover(outgoingHealth) {
|
|
2298
|
+
const info = outgoingHealth;
|
|
2299
|
+
if (info === void 0) return;
|
|
2300
|
+
for (const cb of this.failoverCallbacks) {
|
|
2301
|
+
try {
|
|
2302
|
+
cb(info);
|
|
2303
|
+
} catch (error) {
|
|
2304
|
+
this.logger.debug("Failover callback error", { error: getErrorMessage(error) });
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
try {
|
|
2308
|
+
const eventBus = getGlobalEventBus();
|
|
2309
|
+
eventBus.emit({
|
|
2310
|
+
eventId: `failover-${getTimeProvider().now().toString(36)}-${getRandomProvider().random().toString(36).slice(2, 8)}`,
|
|
2311
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2312
|
+
topic: "adapter.failover",
|
|
2313
|
+
payload: info
|
|
2314
|
+
});
|
|
2315
|
+
} catch (error) {
|
|
2316
|
+
this.logger.debug("CollaborationEventBus emit failed during failover", {
|
|
2317
|
+
error: getErrorMessage(error)
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
detachCircuitBreakerRegistry() {
|
|
2322
|
+
if (this.circuitBreakerRegistry !== void 0 && this.circuitListener !== void 0) {
|
|
2323
|
+
this.circuitBreakerRegistry.removeGlobalStateChangeListener(this.circuitListener);
|
|
2324
|
+
}
|
|
2325
|
+
this.circuitBreakerRegistry = void 0;
|
|
2326
|
+
this.circuitListener = void 0;
|
|
2327
|
+
}
|
|
2328
|
+
};
|
|
2329
|
+
function mapSelectionSource(selection) {
|
|
2330
|
+
if (selection.source === "cli") {
|
|
2331
|
+
return selection.name;
|
|
2332
|
+
}
|
|
2333
|
+
return "api";
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
// src/adapters/model-bound-adapter.ts
|
|
2337
|
+
var ModelBoundAdapter = class {
|
|
2338
|
+
constructor(slot, boundModel) {
|
|
2339
|
+
this.slot = slot;
|
|
2340
|
+
this.boundModel = boundModel;
|
|
2341
|
+
}
|
|
2342
|
+
slot;
|
|
2343
|
+
boundModel;
|
|
2344
|
+
get providerId() {
|
|
2345
|
+
return this.slot.providerId;
|
|
2346
|
+
}
|
|
2347
|
+
get modelId() {
|
|
2348
|
+
return this.slot.modelId;
|
|
2349
|
+
}
|
|
2350
|
+
get capabilities() {
|
|
2351
|
+
return this.slot.capabilities;
|
|
2352
|
+
}
|
|
2353
|
+
bind(request) {
|
|
2354
|
+
return request.model !== void 0 ? request : { ...request, model: this.boundModel };
|
|
2355
|
+
}
|
|
2356
|
+
complete(request) {
|
|
2357
|
+
return this.slot.complete(this.bind(request));
|
|
2358
|
+
}
|
|
2359
|
+
stream(request) {
|
|
2360
|
+
return this.slot.stream(this.bind(request));
|
|
2361
|
+
}
|
|
2362
|
+
countTokens(text) {
|
|
2363
|
+
return this.slot.countTokens(text);
|
|
2364
|
+
}
|
|
2365
|
+
validateConfig() {
|
|
2366
|
+
return this.slot.validateConfig();
|
|
2367
|
+
}
|
|
2368
|
+
getHealth() {
|
|
2369
|
+
return this.slot.getHealth();
|
|
2370
|
+
}
|
|
2371
|
+
refresh() {
|
|
2372
|
+
return this.slot.refresh();
|
|
2373
|
+
}
|
|
2374
|
+
setPreferredCli(cli) {
|
|
2375
|
+
this.slot.setPreferredCli(cli);
|
|
2376
|
+
}
|
|
2377
|
+
onFailover(cb) {
|
|
2378
|
+
return this.slot.onFailover(cb);
|
|
2379
|
+
}
|
|
2380
|
+
getCircuitBreakerRegistry() {
|
|
2381
|
+
return this.slot.getCircuitBreakerRegistry?.();
|
|
2382
|
+
}
|
|
2383
|
+
/** No-op: the shared slot adapter belongs to the registry, which disposes it. */
|
|
2384
|
+
dispose() {
|
|
2385
|
+
}
|
|
2386
|
+
};
|
|
2387
|
+
|
|
2388
|
+
// src/adapters/unified-registry.ts
|
|
2389
|
+
var UnifiedAdapterRegistry = class {
|
|
2390
|
+
logger;
|
|
2391
|
+
defaultCliTimeoutMs;
|
|
2392
|
+
/**
|
|
2393
|
+
* Per-arm adapter cache. CLI slots are created lazily by
|
|
2394
|
+
* {@link getAdapterForCli}; `api:*` arms enter only through
|
|
2395
|
+
* {@link registerApiArm} and are never synthesised (#4392).
|
|
2396
|
+
*/
|
|
2397
|
+
cliAdapters = /* @__PURE__ */ new Map();
|
|
2398
|
+
/** Default adapter for unscoped requests. */
|
|
2399
|
+
defaultAdapter;
|
|
2400
|
+
constructor(config) {
|
|
2401
|
+
this.logger = config?.logger ?? createLogger({ component: "unified-registry" });
|
|
2402
|
+
this.defaultCliTimeoutMs = config?.defaultCliTimeoutMs;
|
|
2403
|
+
this.logger.info("UnifiedAdapterRegistry initialized", {
|
|
2404
|
+
categories: TASK_SPECIALIZATION_MATRIX.length,
|
|
2405
|
+
models: getInTreeCapabilitiesMatrix().models.length
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2408
|
+
// --------------------------------------------------------------------------
|
|
2409
|
+
// Public API
|
|
2410
|
+
// --------------------------------------------------------------------------
|
|
2411
|
+
/** Logger used by this registry. Exposed so singleton helpers can warn. */
|
|
2412
|
+
getLogger() {
|
|
2413
|
+
return this.logger;
|
|
2414
|
+
}
|
|
2415
|
+
/**
|
|
2416
|
+
* Get adapter for a task category. Routing is re-resolved on every read
|
|
2417
|
+
* (#3185) so a post-startup overlay/registry update propagates without a
|
|
2418
|
+
* restart. Falls back to default adapter if category unknown.
|
|
2419
|
+
*/
|
|
2420
|
+
getAdapter(category) {
|
|
2421
|
+
const routing = this.getRouting(category);
|
|
2422
|
+
if (routing === void 0) {
|
|
2423
|
+
this.logger.warn("Unknown task category, using default", { category });
|
|
2424
|
+
return this.getDefault();
|
|
2425
|
+
}
|
|
2426
|
+
return this.getAdapterForCli(routing.primaryCli);
|
|
2427
|
+
}
|
|
2428
|
+
/**
|
|
2429
|
+
* Get adapter for a free-text task description.
|
|
2430
|
+
* Detects category from keywords, falls back to default.
|
|
2431
|
+
*/
|
|
2432
|
+
getAdapterForTask(taskDescription) {
|
|
2433
|
+
const match = detectTaskCategory(taskDescription);
|
|
2434
|
+
if (match !== null) {
|
|
2435
|
+
this.logger.debug("Task category detected", {
|
|
2436
|
+
category: match.category,
|
|
2437
|
+
cli: match.primaryCli
|
|
2438
|
+
});
|
|
2439
|
+
return this.getAdapterForCli(match.primaryCli);
|
|
2440
|
+
}
|
|
2441
|
+
return this.getDefault();
|
|
2442
|
+
}
|
|
2443
|
+
/**
|
|
2444
|
+
* Get adapter pinned to a specific CLI.
|
|
2445
|
+
* Creates and caches one IResilientAdapter per CLI.
|
|
2446
|
+
*/
|
|
2447
|
+
getAdapterForCli(cli) {
|
|
2448
|
+
const cached = this.cliAdapters.get(cli);
|
|
2449
|
+
if (cached !== void 0) return cached;
|
|
2450
|
+
const raw = createResilientAdapter({
|
|
2451
|
+
logger: this.logger,
|
|
2452
|
+
preferredCli: cli,
|
|
2453
|
+
// #4659: the SHARED registry — the same one that already gates
|
|
2454
|
+
// voter-panel availability (#4330). Per-adapter registries would let one
|
|
2455
|
+
// adapter keep routing to a CLI another has already seen fail.
|
|
2456
|
+
circuitBreakerRegistry: getDefaultCliCircuitBreakerRegistry(),
|
|
2457
|
+
...this.defaultCliTimeoutMs !== void 0 && {
|
|
2458
|
+
defaultCliTimeoutMs: this.defaultCliTimeoutMs
|
|
2459
|
+
}
|
|
2460
|
+
});
|
|
2461
|
+
const adapter = raw;
|
|
2462
|
+
this.cliAdapters.set(cli, adapter);
|
|
2463
|
+
this.logger.info("Created CLI-specific adapter", {
|
|
2464
|
+
cli
|
|
2465
|
+
});
|
|
2466
|
+
return adapter;
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* Get the adapter for a routing arm (#4392). A CLI slot resolves exactly as
|
|
2470
|
+
* {@link getAdapterForCli} (created lazily, cached, armed with the shared
|
|
2471
|
+
* breaker registry). An `api:*` arm resolves to what {@link registerApiArm}
|
|
2472
|
+
* supplied, or `undefined` — never to a CLI slot, and never by creating one.
|
|
2473
|
+
*/
|
|
2474
|
+
getAdapterForArm(arm) {
|
|
2475
|
+
if (isCliName(arm)) {
|
|
2476
|
+
return this.getAdapterForCli(arm);
|
|
2477
|
+
}
|
|
2478
|
+
return this.cliAdapters.get(arm);
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Register an `api:*` arm's adapter under its endpoint identity (#4392).
|
|
2482
|
+
* Refuses built-in vendor arms (`api:anthropic`, `api:openai`, `api:google`, `api:gemini`)
|
|
2483
|
+
* to prevent hijacking or redirecting vendor traffic without authorization.
|
|
2484
|
+
* Accepts a dynamic `EndpointArmId`; the id is re-validated at runtime
|
|
2485
|
+
* because the `EndpointArmId` type admits any `api:` string, so a cast from an
|
|
2486
|
+
* unvalidated name is exactly what this refuses.
|
|
2487
|
+
* Registering an id twice replaces (and disposes) the earlier adapter.
|
|
2488
|
+
* CLI-slot behaviour is untouched. Endpoint arms are observable here and in
|
|
2489
|
+
* the shared breaker registry. The router factory admits them only with
|
|
2490
|
+
* NEXUS_ROUTE_GATEWAY_ARMS opt-in and a cost declaration (#7151). An arm
|
|
2491
|
+
* registered without NEXUS_GATEWAY_COST is warned about once here, even
|
|
2492
|
+
* when routing is disabled (#4392 increment 2).
|
|
2493
|
+
*/
|
|
2494
|
+
registerApiArm(arm, adapter) {
|
|
2495
|
+
if (!isEndpointArmId(arm)) {
|
|
2496
|
+
throw new Error(`Invalid api arm id: ${JSON.stringify(arm)} (expected api:<endpoint>)`);
|
|
2497
|
+
}
|
|
2498
|
+
if (!isGatewayArmId(arm) || arm === "api:gemini") {
|
|
2499
|
+
throw new Error(`Cannot overwrite built-in vendor arm: ${arm}`);
|
|
2500
|
+
}
|
|
2501
|
+
this.cliAdapters.get(arm)?.dispose();
|
|
2502
|
+
this.cliAdapters.set(arm, adapter);
|
|
2503
|
+
this.logger.info("Registered api arm adapter", { arm });
|
|
2504
|
+
warnIfGatewayCostUndeclared(arm, this.logger);
|
|
2505
|
+
}
|
|
2506
|
+
/**
|
|
2507
|
+
* Get adapter for a model preference string (e.g., "claude-opus-4-6").
|
|
2508
|
+
* Resolves the model to its CLI via the canonical registry.
|
|
2509
|
+
* Falls back to default adapter if model not recognized.
|
|
2510
|
+
*
|
|
2511
|
+
* An exact match returns the CLI slot adapter bound to the matched registry
|
|
2512
|
+
* id, so the model reaches the CLI instead of its default (#6599). A prefix
|
|
2513
|
+
* match routes to the CLI only, as before: the name was not an entry.
|
|
2514
|
+
*/
|
|
2515
|
+
getAdapterForModel(modelPreference) {
|
|
2516
|
+
const allModels = getInTreeCapabilitiesMatrix().models;
|
|
2517
|
+
const exact = allModels.find(
|
|
2518
|
+
(m) => m.id === modelPreference || m.cliAlias === modelPreference || m.cliModelName === modelPreference
|
|
2519
|
+
);
|
|
2520
|
+
const prefix = exact ?? [...allModels].filter((m) => modelPreference.startsWith(m.id)).sort((a, b) => b.id.length - a.id.length)[0];
|
|
2521
|
+
const model = prefix;
|
|
2522
|
+
if (model !== void 0) {
|
|
2523
|
+
this.logger.debug("Model resolved to CLI", {
|
|
2524
|
+
model: modelPreference,
|
|
2525
|
+
cli: model.cliName
|
|
2526
|
+
});
|
|
2527
|
+
const slot = this.getAdapterForCli(model.cliName);
|
|
2528
|
+
return exact !== void 0 ? new ModelBoundAdapter(slot, exact.id) : slot;
|
|
2529
|
+
}
|
|
2530
|
+
this.logger.debug("Model not in registry, using default", {
|
|
2531
|
+
model: modelPreference
|
|
2532
|
+
});
|
|
2533
|
+
return this.getDefault();
|
|
2534
|
+
}
|
|
2535
|
+
/**
|
|
2536
|
+
* Get adapter for an expert role (e.g., "code_expert").
|
|
2537
|
+
* Uses ROLE_TO_TASK_CATEGORY mapping → task specialization → CLI.
|
|
2538
|
+
*/
|
|
2539
|
+
getAdapterForRole(role) {
|
|
2540
|
+
const category = ROLE_TO_CATEGORY[role];
|
|
2541
|
+
if (category !== void 0) return this.getAdapter(category);
|
|
2542
|
+
return this.getDefault();
|
|
2543
|
+
}
|
|
2544
|
+
/**
|
|
2545
|
+
* Get the default adapter (no CLI preference — auto-detection priority).
|
|
2546
|
+
*/
|
|
2547
|
+
getDefault() {
|
|
2548
|
+
if (this.defaultAdapter !== void 0) return this.defaultAdapter;
|
|
2549
|
+
const raw = createResilientAdapter({
|
|
2550
|
+
logger: this.logger,
|
|
2551
|
+
circuitBreakerRegistry: getDefaultCliCircuitBreakerRegistry(),
|
|
2552
|
+
...this.defaultCliTimeoutMs !== void 0 && {
|
|
2553
|
+
defaultCliTimeoutMs: this.defaultCliTimeoutMs
|
|
2554
|
+
}
|
|
2555
|
+
});
|
|
2556
|
+
this.defaultAdapter = raw;
|
|
2557
|
+
return this.defaultAdapter;
|
|
2558
|
+
}
|
|
2559
|
+
/**
|
|
2560
|
+
* Get snapshot of registry state for observability/debugging. Routing is
|
|
2561
|
+
* re-resolved on read (#3185) so the snapshot reflects the live registry.
|
|
2562
|
+
*/
|
|
2563
|
+
getSnapshot() {
|
|
2564
|
+
return {
|
|
2565
|
+
taskRouting: TASK_SPECIALIZATION_MATRIX.map((spec) => this.resolveRouting(spec)),
|
|
2566
|
+
cachedArms: [...this.cliAdapters.keys()],
|
|
2567
|
+
availableModels: getInTreeCapabilitiesMatrix().models.length
|
|
2568
|
+
};
|
|
2569
|
+
}
|
|
2570
|
+
/**
|
|
2571
|
+
* Resolve the routing for a specific category.
|
|
2572
|
+
*
|
|
2573
|
+
* Computed on every read (#3185) rather than cached at construction, so a
|
|
2574
|
+
* post-startup model-registry / overlay update (e.g. a default-model change
|
|
2575
|
+
* surfaced via `getDefaultModelForCli`) propagates to routing decisions
|
|
2576
|
+
* without a process restart. The matrix is ~10 categories — the per-read
|
|
2577
|
+
* resolution cost is negligible.
|
|
2578
|
+
*/
|
|
2579
|
+
getRouting(category) {
|
|
2580
|
+
const spec = TASK_SPECIALIZATION_MATRIX.find((s) => s.category === category);
|
|
2581
|
+
if (spec === void 0) return void 0;
|
|
2582
|
+
return this.resolveRouting(spec);
|
|
2583
|
+
}
|
|
2584
|
+
/**
|
|
2585
|
+
* Dispose all cached adapters.
|
|
2586
|
+
*/
|
|
2587
|
+
dispose() {
|
|
2588
|
+
for (const adapter of this.cliAdapters.values()) {
|
|
2589
|
+
adapter.dispose();
|
|
2590
|
+
}
|
|
2591
|
+
this.cliAdapters.clear();
|
|
2592
|
+
this.defaultAdapter?.dispose();
|
|
2593
|
+
this.defaultAdapter = void 0;
|
|
2594
|
+
this.logger.info("UnifiedAdapterRegistry disposed");
|
|
2595
|
+
}
|
|
2596
|
+
// --------------------------------------------------------------------------
|
|
2597
|
+
// Private
|
|
2598
|
+
// --------------------------------------------------------------------------
|
|
2599
|
+
/**
|
|
2600
|
+
* Resolve one specialization-matrix row into a routing entry, re-reading the
|
|
2601
|
+
* primary model from the (overlay-aware) model registry each call (#3185).
|
|
2602
|
+
*/
|
|
2603
|
+
resolveRouting(spec) {
|
|
2604
|
+
return {
|
|
2605
|
+
category: spec.category,
|
|
2606
|
+
primaryCli: spec.primaryCli,
|
|
2607
|
+
secondaryCli: spec.secondaryCli,
|
|
2608
|
+
primaryModel: resolveDefaultModel(spec.primaryCli)
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
};
|
|
2612
|
+
var ROLE_TO_CATEGORY = {
|
|
2613
|
+
code_expert: "code_generation",
|
|
2614
|
+
architecture_expert: "architecture",
|
|
2615
|
+
security_expert: "security_review",
|
|
2616
|
+
documentation_expert: "documentation",
|
|
2617
|
+
testing_expert: "testing",
|
|
2618
|
+
devops_expert: "devops",
|
|
2619
|
+
research_expert: "research",
|
|
2620
|
+
pm_expert: "planning",
|
|
2621
|
+
ux_expert: "planning",
|
|
2622
|
+
infrastructure_expert: "devops"
|
|
2623
|
+
};
|
|
2624
|
+
function resolveDefaultModel(cli) {
|
|
2625
|
+
if (isCliName(cli)) {
|
|
2626
|
+
return getDefaultModelForCli(cli);
|
|
2627
|
+
}
|
|
2628
|
+
return cli;
|
|
2629
|
+
}
|
|
2630
|
+
var globalRegistry;
|
|
2631
|
+
function createUnifiedRegistry(config) {
|
|
2632
|
+
return new UnifiedAdapterRegistry(config);
|
|
2633
|
+
}
|
|
2634
|
+
function claimGlobalRegistry(logger5, config) {
|
|
2635
|
+
if (globalRegistry === void 0) {
|
|
2636
|
+
globalRegistry = new UnifiedAdapterRegistry({ ...config, logger: logger5 });
|
|
2637
|
+
} else if (config !== void 0 && Object.keys(config).length > 0) {
|
|
2638
|
+
logger5.warn(
|
|
2639
|
+
"UnifiedAdapterRegistry claim ignored configuration: singleton already initialized",
|
|
2640
|
+
{
|
|
2641
|
+
providedKeys: Object.keys(config)
|
|
2642
|
+
}
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2645
|
+
return globalRegistry;
|
|
2646
|
+
}
|
|
2647
|
+
var RegistryAlreadyInitializedError = class extends ConfigError {
|
|
2648
|
+
constructor(providedKeys) {
|
|
2649
|
+
super(
|
|
2650
|
+
`UnifiedAdapterRegistry singleton is already initialized, so getGlobalRegistry() cannot apply the supplied config (keys: ${providedKeys.join(", ")}). Call resetGlobalRegistry() first if reconfiguration is intentional, or getGlobalRegistry() with no config to use the existing instance; claimGlobalRegistry(logger) is the idempotent way to name the logger.`,
|
|
2651
|
+
{ context: { providedKeys: [...providedKeys] } }
|
|
2652
|
+
);
|
|
2653
|
+
this.name = "RegistryAlreadyInitializedError";
|
|
2654
|
+
}
|
|
2655
|
+
};
|
|
2656
|
+
function getGlobalRegistry(config) {
|
|
2657
|
+
if (globalRegistry === void 0) {
|
|
2658
|
+
globalRegistry = new UnifiedAdapterRegistry(config);
|
|
2659
|
+
return globalRegistry;
|
|
2660
|
+
}
|
|
2661
|
+
if (config !== void 0 && Object.keys(config).length > 0) {
|
|
2662
|
+
throw new RegistryAlreadyInitializedError(Object.keys(config));
|
|
2663
|
+
}
|
|
2664
|
+
return globalRegistry;
|
|
2665
|
+
}
|
|
2666
|
+
function resetGlobalRegistry() {
|
|
2667
|
+
globalRegistry?.dispose();
|
|
2668
|
+
globalRegistry = void 0;
|
|
2669
|
+
}
|
|
2670
|
+
|
|
1633
2671
|
// src/cli-adapters/parsers/agy-parser.ts
|
|
1634
2672
|
import { z as z2 } from "zod";
|
|
1635
2673
|
var AgyUsageSchema = z2.object({
|
|
@@ -4012,8 +5050,33 @@ function createAllAdapters(logger5, codexTransport) {
|
|
|
4012
5050
|
adapters.set(armId, adapter);
|
|
4013
5051
|
}
|
|
4014
5052
|
}
|
|
5053
|
+
appendRegisteredEndpointArms(adapters, logger5 ?? factoryLogger);
|
|
4015
5054
|
return adapters;
|
|
4016
5055
|
}
|
|
5056
|
+
function appendRegisteredEndpointArms(adapters, logger5) {
|
|
5057
|
+
if (!parseBoolEnv("NEXUS_ROUTE_GATEWAY_ARMS", false)) return;
|
|
5058
|
+
const registry = getGlobalRegistry();
|
|
5059
|
+
const configuredArm = `api:${readOpenAICompatEndpoint(process.env, logger5)}`;
|
|
5060
|
+
const slotEndpoints = new Set([...adapters.values()].map(gatewaySlotEndpointOf));
|
|
5061
|
+
for (const arm of registry.getSnapshot().cachedArms) {
|
|
5062
|
+
if (!isEndpointArmId(arm)) continue;
|
|
5063
|
+
if (slotEndpoints.has(arm)) {
|
|
5064
|
+
logger5.debug("Gateway routing arm omitted: family slot already represents endpoint", { arm });
|
|
5065
|
+
continue;
|
|
5066
|
+
}
|
|
5067
|
+
if (resolveGatewayCostDeclaration(arm) === void 0) {
|
|
5068
|
+
logger5.warn("Gateway routing arm excluded: NEXUS_GATEWAY_COST declaration required", {
|
|
5069
|
+
arm,
|
|
5070
|
+
reason: gatewayCostGap(arm)
|
|
5071
|
+
});
|
|
5072
|
+
continue;
|
|
5073
|
+
}
|
|
5074
|
+
const endpoint = registry.getAdapterForArm(arm);
|
|
5075
|
+
if (endpoint === void 0) continue;
|
|
5076
|
+
if (arm === configuredArm) adapters.delete("api:custom-openai");
|
|
5077
|
+
adapters.set(arm, createModelToCliAdapter(endpoint, { name: arm }));
|
|
5078
|
+
}
|
|
5079
|
+
}
|
|
4017
5080
|
function sharedSlotAvailability() {
|
|
4018
5081
|
let cache;
|
|
4019
5082
|
return (cli, fresh) => {
|
|
@@ -4122,6 +5185,15 @@ export {
|
|
|
4122
5185
|
ClaudeAdapter,
|
|
4123
5186
|
createClaudeAdapter,
|
|
4124
5187
|
SdkAdapter,
|
|
4125
|
-
|
|
5188
|
+
getGlobalEventBus,
|
|
5189
|
+
createEvent,
|
|
5190
|
+
EventTopics,
|
|
5191
|
+
ModelBoundAdapter,
|
|
5192
|
+
UnifiedAdapterRegistry,
|
|
5193
|
+
createUnifiedRegistry,
|
|
5194
|
+
claimGlobalRegistry,
|
|
5195
|
+
RegistryAlreadyInitializedError,
|
|
5196
|
+
getGlobalRegistry,
|
|
5197
|
+
resetGlobalRegistry
|
|
4126
5198
|
};
|
|
4127
|
-
//# sourceMappingURL=chunk-
|
|
5199
|
+
//# sourceMappingURL=chunk-HL3EHBKJ.js.map
|