nexus-agents 2.105.0 → 2.107.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/dist/{child-mcp-config-XOIMNU6R.js → child-mcp-config-VF736CNG.js} +2 -2
- package/dist/{chunk-VIHUOFIT.js → chunk-2MPLNCGG.js} +1419 -176
- package/dist/chunk-2MPLNCGG.js.map +1 -0
- package/dist/{chunk-UFTQXDUA.js → chunk-3NON6THG.js} +2 -2
- package/dist/{chunk-XWOLOXKN.js → chunk-4QH4IHCY.js} +2 -2
- package/dist/{chunk-M54LGBTY.js → chunk-5H3YYSMY.js} +4 -4
- package/dist/{chunk-WT46JLBD.js → chunk-7PW7DTAV.js} +3 -3
- package/dist/{chunk-2B7332HE.js → chunk-AESMMLSZ.js} +4 -4
- package/dist/{chunk-TFRXRKU5.js → chunk-C5OTCDF6.js} +4 -4
- package/dist/{chunk-I5QZ3PVT.js → chunk-DLWY7U6K.js} +2 -2
- package/dist/{chunk-IFO52GSQ.js → chunk-EAQLDAKX.js} +3 -3
- package/dist/{chunk-FTFFYAY6.js → chunk-EDXT2FPY.js} +39 -37
- package/dist/{chunk-FTFFYAY6.js.map → chunk-EDXT2FPY.js.map} +1 -1
- package/dist/{chunk-P5XUYJBO.js → chunk-H47QJVMR.js} +3 -3
- package/dist/{chunk-ABNHZEKO.js → chunk-H6Q3EPQG.js} +2 -2
- package/dist/{chunk-75BXDWVG.js → chunk-HSFLKLCK.js} +5 -5
- package/dist/{chunk-A5UJBVCQ.js → chunk-IJZGHD7O.js} +2 -2
- package/dist/{chunk-ETGNZ6H2.js → chunk-K3UILZ5X.js} +21 -3
- package/dist/chunk-K3UILZ5X.js.map +1 -0
- package/dist/{chunk-7RE2S46N.js → chunk-LLM3HY54.js} +3 -3
- package/dist/{chunk-3TZUEGNB.js → chunk-LQLFZEOP.js} +2 -2
- package/dist/{chunk-MKSP4M55.js → chunk-OKZTLUKU.js} +57 -1045
- package/dist/chunk-OKZTLUKU.js.map +1 -0
- package/dist/{chunk-K7OKFTCH.js → chunk-PFZNK25E.js} +2 -2
- package/dist/{chunk-BMM6OX2K.js → chunk-RIZPD2LX.js} +7 -7
- package/dist/{chunk-RZ4A2RRN.js → chunk-RMTFEDQV.js} +2 -2
- package/dist/{chunk-VCLQMXVJ.js → chunk-RROQMZW2.js} +3 -3
- package/dist/{chunk-E6FIVDNH.js → chunk-SXI5ZFBR.js} +2 -2
- package/dist/{chunk-46GXDOWQ.js → chunk-TJUGEDQI.js} +2 -2
- package/dist/{chunk-VGHPMNDR.js → chunk-UIFDDBIG.js} +2 -2
- package/dist/{chunk-FVJ2LM2G.js → chunk-V5UOHHOD.js} +2 -2
- package/dist/{chunk-WDVUKSIQ.js → chunk-VO7FEIXD.js} +93 -51
- package/dist/chunk-VO7FEIXD.js.map +1 -0
- package/dist/{chunk-OESO6AQL.js → chunk-W3QOKU42.js} +9 -9
- package/dist/{chunk-PGSXRUH5.js → chunk-YDPEK6NQ.js} +3 -3
- package/dist/{chunk-I7UDM43R.js → chunk-ZFDG2CDY.js} +118 -4
- package/dist/chunk-ZFDG2CDY.js.map +1 -0
- package/dist/{cli-circuit-breaker-QMCG6JGV.js → cli-circuit-breaker-X4DTCCLH.js} +4 -4
- package/dist/cli.js +42 -42
- package/dist/cli.js.map +1 -1
- package/dist/{composite-router-ZX64CEVG.js → composite-router-QJRU2JU5.js} +2 -2
- package/dist/{consensus-vote-VUNGPXXS.js → consensus-vote-A6DOEWEZ.js} +12 -13
- package/dist/{context-retriever-J7DIPQUO.js → context-retriever-3N7NVP57.js} +6 -6
- package/dist/{doctor-deep-ITSJV2RH.js → doctor-deep-6DKSM74K.js} +3 -3
- package/dist/{expert-bridge-MQURDKJI.js → expert-bridge-CP6XN5U7.js} +4 -4
- package/dist/factory-AT4YGH24.js +21 -0
- package/dist/{factory-Y2RF33UB.js → factory-U23DIWPS.js} +5 -5
- package/dist/{improvement-review-NBVWPFXR.js → improvement-review-5DRHKSXV.js} +4 -4
- package/dist/index.d.ts +36 -13
- package/dist/index.js +65 -64
- package/dist/index.js.map +1 -1
- package/dist/{init-opencode-QPHX4KVI.js → init-opencode-YAYDQ36A.js} +5 -5
- package/dist/{issue-triage-D5ADEG3V.js → issue-triage-L3MTN6RS.js} +5 -5
- package/dist/{pr-reviewer-helpers-R7U4EI2P.js → pr-reviewer-helpers-H4R57FMV.js} +4 -4
- package/dist/{registry-command-CFNNKJ5S.js → registry-command-7TVGPC43.js} +2 -2
- package/dist/{repo-security-plan-2AXFDREW.js → repo-security-plan-543K7VCE.js} +3 -3
- package/dist/{research-helpers-synthesize-YTIDNKBF.js → research-helpers-synthesize-4BQHGJ3O.js} +3 -3
- package/dist/{routing-memory-YLX6QNY2.js → routing-memory-QN3PS4SS.js} +2 -2
- package/dist/{session-memory-Y5MUOY56.js → session-memory-Q3MMU45M.js} +3 -3
- package/dist/{setup-command-KTMIAEU6.js → setup-command-VZ675W23.js} +11 -10
- package/dist/{setup-config-5J3R7RET.js → setup-config-D6WCNU6M.js} +3 -3
- package/dist/{setup-custom-api-MJ24PJA7.js → setup-custom-api-SMLJ27IY.js} +4 -6
- package/dist/{setup-custom-api-MJ24PJA7.js.map → setup-custom-api-SMLJ27IY.js.map} +1 -1
- package/dist/{tool-memory-YNMRI3PZ.js → tool-memory-H6W6KNR3.js} +5 -5
- package/dist/{weather-report-GZRODMYW.js → weather-report-R3KUDV4W.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-BLMWODJG.js +0 -120
- package/dist/chunk-BLMWODJG.js.map +0 -1
- package/dist/chunk-ETGNZ6H2.js.map +0 -1
- package/dist/chunk-I7UDM43R.js.map +0 -1
- package/dist/chunk-MKSP4M55.js.map +0 -1
- package/dist/chunk-VIHUOFIT.js.map +0 -1
- package/dist/chunk-WDVUKSIQ.js.map +0 -1
- package/dist/factory-UZQ3RGFQ.js +0 -19
- /package/dist/{child-mcp-config-XOIMNU6R.js.map → child-mcp-config-VF736CNG.js.map} +0 -0
- /package/dist/{chunk-UFTQXDUA.js.map → chunk-3NON6THG.js.map} +0 -0
- /package/dist/{chunk-XWOLOXKN.js.map → chunk-4QH4IHCY.js.map} +0 -0
- /package/dist/{chunk-M54LGBTY.js.map → chunk-5H3YYSMY.js.map} +0 -0
- /package/dist/{chunk-WT46JLBD.js.map → chunk-7PW7DTAV.js.map} +0 -0
- /package/dist/{chunk-2B7332HE.js.map → chunk-AESMMLSZ.js.map} +0 -0
- /package/dist/{chunk-TFRXRKU5.js.map → chunk-C5OTCDF6.js.map} +0 -0
- /package/dist/{chunk-I5QZ3PVT.js.map → chunk-DLWY7U6K.js.map} +0 -0
- /package/dist/{chunk-IFO52GSQ.js.map → chunk-EAQLDAKX.js.map} +0 -0
- /package/dist/{chunk-P5XUYJBO.js.map → chunk-H47QJVMR.js.map} +0 -0
- /package/dist/{chunk-ABNHZEKO.js.map → chunk-H6Q3EPQG.js.map} +0 -0
- /package/dist/{chunk-75BXDWVG.js.map → chunk-HSFLKLCK.js.map} +0 -0
- /package/dist/{chunk-A5UJBVCQ.js.map → chunk-IJZGHD7O.js.map} +0 -0
- /package/dist/{chunk-7RE2S46N.js.map → chunk-LLM3HY54.js.map} +0 -0
- /package/dist/{chunk-3TZUEGNB.js.map → chunk-LQLFZEOP.js.map} +0 -0
- /package/dist/{chunk-K7OKFTCH.js.map → chunk-PFZNK25E.js.map} +0 -0
- /package/dist/{chunk-BMM6OX2K.js.map → chunk-RIZPD2LX.js.map} +0 -0
- /package/dist/{chunk-RZ4A2RRN.js.map → chunk-RMTFEDQV.js.map} +0 -0
- /package/dist/{chunk-VCLQMXVJ.js.map → chunk-RROQMZW2.js.map} +0 -0
- /package/dist/{chunk-E6FIVDNH.js.map → chunk-SXI5ZFBR.js.map} +0 -0
- /package/dist/{chunk-46GXDOWQ.js.map → chunk-TJUGEDQI.js.map} +0 -0
- /package/dist/{chunk-VGHPMNDR.js.map → chunk-UIFDDBIG.js.map} +0 -0
- /package/dist/{chunk-FVJ2LM2G.js.map → chunk-V5UOHHOD.js.map} +0 -0
- /package/dist/{chunk-OESO6AQL.js.map → chunk-W3QOKU42.js.map} +0 -0
- /package/dist/{chunk-PGSXRUH5.js.map → chunk-YDPEK6NQ.js.map} +0 -0
- /package/dist/{cli-circuit-breaker-QMCG6JGV.js.map → cli-circuit-breaker-X4DTCCLH.js.map} +0 -0
- /package/dist/{composite-router-ZX64CEVG.js.map → composite-router-QJRU2JU5.js.map} +0 -0
- /package/dist/{consensus-vote-VUNGPXXS.js.map → consensus-vote-A6DOEWEZ.js.map} +0 -0
- /package/dist/{context-retriever-J7DIPQUO.js.map → context-retriever-3N7NVP57.js.map} +0 -0
- /package/dist/{doctor-deep-ITSJV2RH.js.map → doctor-deep-6DKSM74K.js.map} +0 -0
- /package/dist/{expert-bridge-MQURDKJI.js.map → expert-bridge-CP6XN5U7.js.map} +0 -0
- /package/dist/{factory-UZQ3RGFQ.js.map → factory-AT4YGH24.js.map} +0 -0
- /package/dist/{factory-Y2RF33UB.js.map → factory-U23DIWPS.js.map} +0 -0
- /package/dist/{improvement-review-NBVWPFXR.js.map → improvement-review-5DRHKSXV.js.map} +0 -0
- /package/dist/{init-opencode-QPHX4KVI.js.map → init-opencode-YAYDQ36A.js.map} +0 -0
- /package/dist/{issue-triage-D5ADEG3V.js.map → issue-triage-L3MTN6RS.js.map} +0 -0
- /package/dist/{pr-reviewer-helpers-R7U4EI2P.js.map → pr-reviewer-helpers-H4R57FMV.js.map} +0 -0
- /package/dist/{registry-command-CFNNKJ5S.js.map → registry-command-7TVGPC43.js.map} +0 -0
- /package/dist/{repo-security-plan-2AXFDREW.js.map → repo-security-plan-543K7VCE.js.map} +0 -0
- /package/dist/{research-helpers-synthesize-YTIDNKBF.js.map → research-helpers-synthesize-4BQHGJ3O.js.map} +0 -0
- /package/dist/{routing-memory-YLX6QNY2.js.map → routing-memory-QN3PS4SS.js.map} +0 -0
- /package/dist/{session-memory-Y5MUOY56.js.map → session-memory-Q3MMU45M.js.map} +0 -0
- /package/dist/{setup-command-KTMIAEU6.js.map → setup-command-VZ675W23.js.map} +0 -0
- /package/dist/{setup-config-5J3R7RET.js.map → setup-config-D6WCNU6M.js.map} +0 -0
- /package/dist/{tool-memory-YNMRI3PZ.js.map → tool-memory-H6W6KNR3.js.map} +0 -0
- /package/dist/{weather-report-GZRODMYW.js.map → weather-report-R3KUDV4W.js.map} +0 -0
|
@@ -1,24 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AdapterModelError,
|
|
3
|
+
BaseAdapter,
|
|
4
|
+
createStream,
|
|
5
|
+
requireApiKey,
|
|
6
|
+
validateApiKeyPresence
|
|
7
|
+
} from "./chunk-H6Q3EPQG.js";
|
|
8
|
+
import {
|
|
9
|
+
CUSTOM_API_BASE_URL_ENV,
|
|
10
|
+
CUSTOM_API_DEFAULT_MODEL,
|
|
11
|
+
PROVIDER_ENV_KEYS,
|
|
12
|
+
validateCustomApiBaseUrl
|
|
13
|
+
} from "./chunk-ZFDG2CDY.js";
|
|
1
14
|
import {
|
|
2
15
|
CliCircuitBreaker,
|
|
3
16
|
DEFAULT_CIRCUIT_BREAKER_CONFIG
|
|
4
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-K3UILZ5X.js";
|
|
5
18
|
import {
|
|
6
19
|
getAvailabilityCache
|
|
7
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-RMTFEDQV.js";
|
|
8
21
|
import {
|
|
9
22
|
generateHyphenId
|
|
10
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-IJZGHD7O.js";
|
|
11
24
|
import {
|
|
12
25
|
CLI_SUBPROCESS_TIMEOUTS,
|
|
13
26
|
CLI_TIMEOUTS,
|
|
14
27
|
CLI_VERSION_REQUIREMENTS,
|
|
15
28
|
CODEX_MCP_TIMEOUTS,
|
|
16
29
|
DEFAULT_CAPABILITIES,
|
|
30
|
+
ErrorCode,
|
|
31
|
+
ModelCapability,
|
|
32
|
+
ModelError,
|
|
33
|
+
apiArmId,
|
|
34
|
+
buildCliCapabilityProfiles,
|
|
17
35
|
buildModelInfo,
|
|
18
36
|
clampPercent,
|
|
19
37
|
createLogger,
|
|
20
38
|
detectTaskCategory,
|
|
21
39
|
err,
|
|
40
|
+
estimateTokens,
|
|
22
41
|
extractJsonObject,
|
|
23
42
|
findInTreeByCli,
|
|
24
43
|
getCliModelName,
|
|
@@ -28,11 +47,1386 @@ import {
|
|
|
28
47
|
getOutcomeStore,
|
|
29
48
|
getRandomProvider,
|
|
30
49
|
getTimeProvider,
|
|
50
|
+
getTokenEstimator,
|
|
31
51
|
isDynamicModelsEnabled,
|
|
52
|
+
isRateLimitLikeError,
|
|
32
53
|
isRateLimitText,
|
|
33
54
|
loadModelsDevSnapshot,
|
|
34
|
-
ok
|
|
35
|
-
|
|
55
|
+
ok,
|
|
56
|
+
resolveCliAlias
|
|
57
|
+
} from "./chunk-VO7FEIXD.js";
|
|
58
|
+
|
|
59
|
+
// src/cli-adapters/cli-to-model-adapter.ts
|
|
60
|
+
var CliToModelAdapter = class {
|
|
61
|
+
providerId;
|
|
62
|
+
modelId;
|
|
63
|
+
capabilities;
|
|
64
|
+
cliAdapter;
|
|
65
|
+
defaultTimeoutMs;
|
|
66
|
+
/**
|
|
67
|
+
* Creates a bridge from CLI adapter to model adapter.
|
|
68
|
+
*
|
|
69
|
+
* @param cliAdapter - The CLI adapter to wrap
|
|
70
|
+
* @param config - Optional configuration (e.g. timeout override)
|
|
71
|
+
*/
|
|
72
|
+
constructor(cliAdapter, config) {
|
|
73
|
+
this.cliAdapter = cliAdapter;
|
|
74
|
+
this.defaultTimeoutMs = config?.defaultTimeoutMs;
|
|
75
|
+
this.providerId = `cli-${cliAdapter.name}`;
|
|
76
|
+
this.modelId = cliAdapter.getModelInfo().id;
|
|
77
|
+
this.capabilities = this.deriveCapabilities();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Derives ModelCapability from CLI capabilities.
|
|
81
|
+
*/
|
|
82
|
+
deriveCapabilities() {
|
|
83
|
+
const caps = [ModelCapability.COMPLETION, ModelCapability.TOOL_USE];
|
|
84
|
+
if (this.cliAdapter.name === "claude") {
|
|
85
|
+
caps.push(ModelCapability.EXTENDED_THINKING);
|
|
86
|
+
}
|
|
87
|
+
return caps;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Converts CompletionRequest to CliTask.
|
|
91
|
+
*/
|
|
92
|
+
toCliTask(request) {
|
|
93
|
+
const content = request.messages.map((msg) => {
|
|
94
|
+
const text = typeof msg.content === "string" ? msg.content : msg.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
95
|
+
return `[${msg.role}]: ${text}`;
|
|
96
|
+
}).join("\n\n");
|
|
97
|
+
const task = { content };
|
|
98
|
+
if (request.systemPrompt !== void 0) {
|
|
99
|
+
task.systemPrompt = request.systemPrompt;
|
|
100
|
+
}
|
|
101
|
+
if (request.maxTokens !== void 0) {
|
|
102
|
+
task.maxTokens = request.maxTokens;
|
|
103
|
+
}
|
|
104
|
+
return task;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Converts CliResponse to CompletionResponse.
|
|
108
|
+
*/
|
|
109
|
+
toCompletionResponse(response) {
|
|
110
|
+
return {
|
|
111
|
+
content: [{ type: "text", text: response.text }],
|
|
112
|
+
usage: {
|
|
113
|
+
inputTokens: response.usage?.inputTokens ?? 0,
|
|
114
|
+
outputTokens: response.usage?.outputTokens ?? 0,
|
|
115
|
+
totalTokens: response.usage?.totalTokens ?? 0
|
|
116
|
+
},
|
|
117
|
+
stopReason: "end_turn",
|
|
118
|
+
model: response.model ?? this.modelId
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Converts CliError to ModelError.
|
|
123
|
+
*/
|
|
124
|
+
toModelError(cliError) {
|
|
125
|
+
const options = cliError.cause !== void 0 ? { cause: cliError.cause } : {};
|
|
126
|
+
return new ModelError(cliError.message, options);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Send a completion request via CLI.
|
|
130
|
+
*/
|
|
131
|
+
async complete(request) {
|
|
132
|
+
const task = this.toCliTask(request);
|
|
133
|
+
const effectiveTimeoutMs = request.timeoutMs ?? this.defaultTimeoutMs;
|
|
134
|
+
const opts = effectiveTimeoutMs !== void 0 ? { timeoutMs: effectiveTimeoutMs } : void 0;
|
|
135
|
+
const result = await this.cliAdapter.execute(task, opts);
|
|
136
|
+
if (!result.ok) {
|
|
137
|
+
return err(this.toModelError(result.error));
|
|
138
|
+
}
|
|
139
|
+
return ok(this.toCompletionResponse(result.value));
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Streaming is not supported via CLI adapters.
|
|
143
|
+
* Falls back to non-streaming and yields single chunk.
|
|
144
|
+
*/
|
|
145
|
+
async *stream(request) {
|
|
146
|
+
const result = await this.complete(request);
|
|
147
|
+
if (!result.ok) {
|
|
148
|
+
throw result.error;
|
|
149
|
+
}
|
|
150
|
+
const response = result.value;
|
|
151
|
+
yield { type: "message_start", message: { model: response.model } };
|
|
152
|
+
yield {
|
|
153
|
+
type: "content_block_start",
|
|
154
|
+
index: 0,
|
|
155
|
+
contentBlock: response.content[0] ?? { type: "text", text: "" }
|
|
156
|
+
};
|
|
157
|
+
const firstBlock = response.content[0];
|
|
158
|
+
const text = firstBlock?.type === "text" ? firstBlock.text : "";
|
|
159
|
+
yield { type: "content_block_delta", index: 0, delta: { type: "text_delta", text } };
|
|
160
|
+
yield { type: "content_block_stop", index: 0 };
|
|
161
|
+
yield {
|
|
162
|
+
type: "message_delta",
|
|
163
|
+
delta: { stop_reason: response.stopReason },
|
|
164
|
+
usage: response.usage
|
|
165
|
+
};
|
|
166
|
+
yield { type: "message_stop" };
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Token count via canonical estimator (DRY consolidation Issue #1596).
|
|
170
|
+
*/
|
|
171
|
+
countTokens(text) {
|
|
172
|
+
return Promise.resolve(estimateTokens(text));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Validate configuration by running health check.
|
|
176
|
+
*/
|
|
177
|
+
validateConfig() {
|
|
178
|
+
return ok(void 0);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Initialize the underlying CLI adapter.
|
|
182
|
+
*/
|
|
183
|
+
async initialize() {
|
|
184
|
+
await this.cliAdapter.initialize();
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Dispose the underlying CLI adapter.
|
|
188
|
+
*/
|
|
189
|
+
async dispose() {
|
|
190
|
+
await this.cliAdapter.dispose();
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
function createCliToModelAdapter(cliAdapter, config) {
|
|
194
|
+
return new CliToModelAdapter(cliAdapter, config);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/cli-adapters/model-to-cli-adapter.ts
|
|
198
|
+
var DEFAULT_CONTEXT_WINDOW = 2e5;
|
|
199
|
+
var NEUTRAL_CAPABILITIES = {
|
|
200
|
+
reasoning: 7,
|
|
201
|
+
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
202
|
+
codeGeneration: 7,
|
|
203
|
+
speed: 7,
|
|
204
|
+
cost: 5
|
|
205
|
+
};
|
|
206
|
+
var API_TRANSPORT = "subprocess";
|
|
207
|
+
var ModelToCliAdapter = class {
|
|
208
|
+
name;
|
|
209
|
+
transport = API_TRANSPORT;
|
|
210
|
+
capabilities;
|
|
211
|
+
modelAdapter;
|
|
212
|
+
constructor(modelAdapter, config) {
|
|
213
|
+
this.modelAdapter = modelAdapter;
|
|
214
|
+
this.name = config.name;
|
|
215
|
+
this.capabilities = config.capabilities ?? NEUTRAL_CAPABILITIES;
|
|
216
|
+
}
|
|
217
|
+
/** Build a single-turn CompletionRequest from a CliTask. */
|
|
218
|
+
toCompletionRequest(task, options) {
|
|
219
|
+
const request = {
|
|
220
|
+
messages: [{ role: "user", content: task.content }]
|
|
221
|
+
};
|
|
222
|
+
if (task.systemPrompt !== void 0) {
|
|
223
|
+
request.systemPrompt = task.systemPrompt;
|
|
224
|
+
}
|
|
225
|
+
if (task.maxTokens !== void 0) {
|
|
226
|
+
request.maxTokens = task.maxTokens;
|
|
227
|
+
}
|
|
228
|
+
if (options?.timeoutMs !== void 0) {
|
|
229
|
+
request.timeoutMs = options.timeoutMs;
|
|
230
|
+
}
|
|
231
|
+
return request;
|
|
232
|
+
}
|
|
233
|
+
/** Flatten a CompletionResponse's content blocks to plain text. */
|
|
234
|
+
toText(response) {
|
|
235
|
+
return response.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
236
|
+
}
|
|
237
|
+
/** Convert a CompletionResponse to a CliResponse. */
|
|
238
|
+
toCliResponse(response) {
|
|
239
|
+
return {
|
|
240
|
+
text: this.toText(response),
|
|
241
|
+
usage: {
|
|
242
|
+
inputTokens: response.usage.inputTokens,
|
|
243
|
+
outputTokens: response.usage.outputTokens,
|
|
244
|
+
totalTokens: response.usage.totalTokens
|
|
245
|
+
},
|
|
246
|
+
model: response.model
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Convert a ModelError to a CliError. Rate-limit text → RATE_LIMITED
|
|
251
|
+
* (retryable); everything else → EXECUTION_ERROR (non-retryable) so the
|
|
252
|
+
* routing/circuit layers see a real, typed failure.
|
|
253
|
+
*/
|
|
254
|
+
toCliError(error) {
|
|
255
|
+
const rateLimited = isRateLimitText(error.message);
|
|
256
|
+
const code = rateLimited ? "RATE_LIMITED" : "EXECUTION_ERROR";
|
|
257
|
+
const base = {
|
|
258
|
+
code,
|
|
259
|
+
message: error.message,
|
|
260
|
+
cli: this.name,
|
|
261
|
+
retryable: rateLimited
|
|
262
|
+
};
|
|
263
|
+
return error.cause instanceof Error ? { ...base, cause: error.cause } : base;
|
|
264
|
+
}
|
|
265
|
+
async execute(task, options) {
|
|
266
|
+
const result = await this.modelAdapter.complete(this.toCompletionRequest(task, options));
|
|
267
|
+
if (!result.ok) {
|
|
268
|
+
return err(this.toCliError(result.error));
|
|
269
|
+
}
|
|
270
|
+
return ok(this.toCliResponse(result.value));
|
|
271
|
+
}
|
|
272
|
+
/** Health is derived from the model adapter's config validation. */
|
|
273
|
+
async healthCheck() {
|
|
274
|
+
const valid = this.modelAdapter.validateConfig();
|
|
275
|
+
return Promise.resolve({
|
|
276
|
+
healthy: valid.ok,
|
|
277
|
+
version: "api",
|
|
278
|
+
versionStatus: "supported",
|
|
279
|
+
lastChecked: /* @__PURE__ */ new Date(0),
|
|
280
|
+
...valid.ok ? {} : { message: valid.error.message }
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* API adapters don't expose subprocess-style rate windows; report
|
|
285
|
+
* non-exhausted capacity. Real rate-limit signals surface via execute()'s
|
|
286
|
+
* RATE_LIMITED error, which the resilience layer acts on.
|
|
287
|
+
*/
|
|
288
|
+
getCapacity() {
|
|
289
|
+
return Promise.resolve({
|
|
290
|
+
remainingTokens: Number.POSITIVE_INFINITY,
|
|
291
|
+
remainingRequests: Number.POSITIVE_INFINITY,
|
|
292
|
+
resetTime: /* @__PURE__ */ new Date(0),
|
|
293
|
+
utilizationPercent: 0,
|
|
294
|
+
exhausted: false
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
getVersion() {
|
|
298
|
+
return Promise.resolve("api");
|
|
299
|
+
}
|
|
300
|
+
getModelInfo() {
|
|
301
|
+
return {
|
|
302
|
+
id: this.modelAdapter.modelId,
|
|
303
|
+
name: this.modelAdapter.modelId,
|
|
304
|
+
contextWindow: DEFAULT_CONTEXT_WINDOW
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/** Delegate to the model adapter's listModels surface when present (#2529). */
|
|
308
|
+
async listModels() {
|
|
309
|
+
if (this.modelAdapter.listModels === void 0) return [];
|
|
310
|
+
const models = await this.modelAdapter.listModels();
|
|
311
|
+
return models.map((m) => ({
|
|
312
|
+
id: m.id,
|
|
313
|
+
...m.ownedBy !== void 0 ? { provider: m.ownedBy } : {}
|
|
314
|
+
}));
|
|
315
|
+
}
|
|
316
|
+
initialize() {
|
|
317
|
+
return Promise.resolve();
|
|
318
|
+
}
|
|
319
|
+
dispose() {
|
|
320
|
+
return Promise.resolve();
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
function createModelToCliAdapter(modelAdapter, config) {
|
|
324
|
+
return new ModelToCliAdapter(modelAdapter, config);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/adapters/claude-adapter.ts
|
|
328
|
+
import Anthropic3 from "@anthropic-ai/sdk";
|
|
329
|
+
|
|
330
|
+
// src/adapters/claude-adapter-types.ts
|
|
331
|
+
var CLAUDE_MODELS = {
|
|
332
|
+
OPUS_4: getCliModelName("claude-opus"),
|
|
333
|
+
SONNET_4: getCliModelName("claude-sonnet"),
|
|
334
|
+
HAIKU_4: getCliModelName("claude-haiku")
|
|
335
|
+
};
|
|
336
|
+
var CLAUDE_MODEL_ALIASES = {
|
|
337
|
+
"claude-opus-4": CLAUDE_MODELS.OPUS_4,
|
|
338
|
+
"claude-sonnet-4": CLAUDE_MODELS.SONNET_4,
|
|
339
|
+
"claude-haiku-4": CLAUDE_MODELS.HAIKU_4,
|
|
340
|
+
// Legacy alias — pre-4.x users routed to the current haiku.
|
|
341
|
+
"claude-haiku-3": CLAUDE_MODELS.HAIKU_4
|
|
342
|
+
};
|
|
343
|
+
var DEFAULT_MAX_TOKENS = 4096;
|
|
344
|
+
|
|
345
|
+
// src/adapters/claude-adapter-helpers.ts
|
|
346
|
+
import "@anthropic-ai/sdk";
|
|
347
|
+
function mapStopReason(anthropicReason) {
|
|
348
|
+
switch (anthropicReason) {
|
|
349
|
+
case "end_turn":
|
|
350
|
+
return "end_turn";
|
|
351
|
+
case "max_tokens":
|
|
352
|
+
return "max_tokens";
|
|
353
|
+
case "stop_sequence":
|
|
354
|
+
return "stop_sequence";
|
|
355
|
+
case "tool_use":
|
|
356
|
+
return "tool_use";
|
|
357
|
+
default:
|
|
358
|
+
return "end_turn";
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function mapContentBlock(block) {
|
|
362
|
+
if (block.type === "text") {
|
|
363
|
+
return { type: "text", text: block.text };
|
|
364
|
+
}
|
|
365
|
+
if (block.type === "tool_use") {
|
|
366
|
+
const toolBlock = block;
|
|
367
|
+
return {
|
|
368
|
+
type: "tool_use",
|
|
369
|
+
id: toolBlock.id,
|
|
370
|
+
name: toolBlock.name,
|
|
371
|
+
input: toolBlock.input
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
return { type: "text", text: "" };
|
|
375
|
+
}
|
|
376
|
+
function mapMessage(message) {
|
|
377
|
+
const role = message.role === "user" ? "user" : "assistant";
|
|
378
|
+
if (typeof message.content === "string") {
|
|
379
|
+
return { role, content: message.content };
|
|
380
|
+
}
|
|
381
|
+
const content = message.content.map((block) => {
|
|
382
|
+
if (block.type === "text") {
|
|
383
|
+
return { type: "text", text: block.text };
|
|
384
|
+
}
|
|
385
|
+
if (block.type === "tool_use") {
|
|
386
|
+
return {
|
|
387
|
+
type: "tool_use",
|
|
388
|
+
id: block.id,
|
|
389
|
+
name: block.name,
|
|
390
|
+
input: block.input
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
if (block.type === "tool_result") {
|
|
394
|
+
const toolResult = {
|
|
395
|
+
type: "tool_result",
|
|
396
|
+
tool_use_id: block.tool_use_id,
|
|
397
|
+
content: block.content
|
|
398
|
+
};
|
|
399
|
+
if (block.is_error !== void 0) {
|
|
400
|
+
toolResult.is_error = block.is_error;
|
|
401
|
+
}
|
|
402
|
+
return toolResult;
|
|
403
|
+
}
|
|
404
|
+
return {
|
|
405
|
+
type: "image",
|
|
406
|
+
source: block.source
|
|
407
|
+
};
|
|
408
|
+
});
|
|
409
|
+
return { role, content };
|
|
410
|
+
}
|
|
411
|
+
function mapTool(tool) {
|
|
412
|
+
return {
|
|
413
|
+
name: tool.name,
|
|
414
|
+
description: tool.description,
|
|
415
|
+
input_schema: tool.inputSchema
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function resolveModelId(modelId) {
|
|
419
|
+
const registryId = resolveCliAlias(modelId);
|
|
420
|
+
if (registryId !== void 0) return getCliModelName(registryId);
|
|
421
|
+
return modelId;
|
|
422
|
+
}
|
|
423
|
+
function getModelCapabilities(modelId) {
|
|
424
|
+
const capabilities = [
|
|
425
|
+
ModelCapability.COMPLETION,
|
|
426
|
+
ModelCapability.STREAMING,
|
|
427
|
+
ModelCapability.TOOL_USE,
|
|
428
|
+
ModelCapability.VISION
|
|
429
|
+
];
|
|
430
|
+
const resolvedId = resolveModelId(modelId);
|
|
431
|
+
if (resolvedId.includes("opus") || resolvedId.includes("sonnet-4")) {
|
|
432
|
+
capabilities.push(ModelCapability.EXTENDED_THINKING);
|
|
433
|
+
}
|
|
434
|
+
return capabilities;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/context/token-counter-helpers.ts
|
|
438
|
+
import "@anthropic-ai/sdk";
|
|
439
|
+
function generateCacheKey(content, provider, model) {
|
|
440
|
+
const contentStr = typeof content === "string" ? content : JSON.stringify(content);
|
|
441
|
+
return `${provider}:${model ?? "default"}:${contentStr}`;
|
|
442
|
+
}
|
|
443
|
+
function messagesToAnthropicFormat(messages) {
|
|
444
|
+
return messages.filter((m) => m.role !== "system").map((m) => {
|
|
445
|
+
const role = m.role === "user" ? "user" : "assistant";
|
|
446
|
+
if (typeof m.content === "string") {
|
|
447
|
+
return { role, content: m.content };
|
|
448
|
+
}
|
|
449
|
+
const content = m.content.map((block) => {
|
|
450
|
+
if (block.type === "text") {
|
|
451
|
+
return { type: "text", text: block.text };
|
|
452
|
+
}
|
|
453
|
+
if (block.type === "tool_use") {
|
|
454
|
+
return {
|
|
455
|
+
type: "tool_use",
|
|
456
|
+
id: block.id,
|
|
457
|
+
name: block.name,
|
|
458
|
+
input: block.input
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
if (block.type === "tool_result") {
|
|
462
|
+
return {
|
|
463
|
+
type: "tool_result",
|
|
464
|
+
tool_use_id: block.tool_use_id,
|
|
465
|
+
content: block.content
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
type: "image",
|
|
470
|
+
source: block.source
|
|
471
|
+
};
|
|
472
|
+
});
|
|
473
|
+
return { role, content };
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
function extractSystemPrompt(messages) {
|
|
477
|
+
const systemMsg = messages.find((m) => m.role === "system");
|
|
478
|
+
if (systemMsg === void 0) {
|
|
479
|
+
return void 0;
|
|
480
|
+
}
|
|
481
|
+
if (typeof systemMsg.content === "string") {
|
|
482
|
+
return systemMsg.content;
|
|
483
|
+
}
|
|
484
|
+
return systemMsg.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/adapters/prompt-utils.ts
|
|
488
|
+
function extractRequestSystemPrompt(request) {
|
|
489
|
+
if (request.systemPrompt !== void 0 && request.systemPrompt !== "") {
|
|
490
|
+
return request.systemPrompt;
|
|
491
|
+
}
|
|
492
|
+
return extractSystemPrompt(request.messages);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/adapters/claude-adapter.ts
|
|
496
|
+
var ClaudeAdapter = class extends BaseAdapter {
|
|
497
|
+
client;
|
|
498
|
+
resolvedModelId;
|
|
499
|
+
/**
|
|
500
|
+
* Creates a new ClaudeAdapter instance.
|
|
501
|
+
*
|
|
502
|
+
* @param config - Claude adapter configuration
|
|
503
|
+
* @throws {ConfigError} If API key is missing
|
|
504
|
+
*/
|
|
505
|
+
constructor(config) {
|
|
506
|
+
const resolvedModelId = resolveModelId(config.modelId);
|
|
507
|
+
const baseConfig = {
|
|
508
|
+
providerId: "anthropic",
|
|
509
|
+
modelId: resolvedModelId,
|
|
510
|
+
capabilities: getModelCapabilities(config.modelId),
|
|
511
|
+
apiKey: config.apiKey
|
|
512
|
+
};
|
|
513
|
+
if (config.baseUrl !== void 0) {
|
|
514
|
+
baseConfig.baseUrl = config.baseUrl;
|
|
515
|
+
}
|
|
516
|
+
if (config.timeout !== void 0) {
|
|
517
|
+
baseConfig.timeout = config.timeout;
|
|
518
|
+
}
|
|
519
|
+
if (config.maxRetries !== void 0) {
|
|
520
|
+
baseConfig.maxRetries = config.maxRetries;
|
|
521
|
+
}
|
|
522
|
+
super(baseConfig);
|
|
523
|
+
this.resolvedModelId = resolvedModelId;
|
|
524
|
+
requireApiKey(config.apiKey, "Anthropic", config.modelId);
|
|
525
|
+
this.client = new Anthropic3({
|
|
526
|
+
apiKey: config.apiKey,
|
|
527
|
+
baseURL: config.baseUrl,
|
|
528
|
+
timeout: config.timeout,
|
|
529
|
+
maxRetries: config.maxRetries ?? 2
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Validates adapter configuration.
|
|
534
|
+
* Extends base validation with Claude-specific checks.
|
|
535
|
+
*/
|
|
536
|
+
validateConfig() {
|
|
537
|
+
const baseResult = super.validateConfig();
|
|
538
|
+
if (!baseResult.ok) {
|
|
539
|
+
return baseResult;
|
|
540
|
+
}
|
|
541
|
+
const keyResult = validateApiKeyPresence(this.config.apiKey, this.providerId, this.modelId);
|
|
542
|
+
if (!keyResult.ok) return keyResult;
|
|
543
|
+
return ok(void 0);
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Send a completion request to Claude.
|
|
547
|
+
*
|
|
548
|
+
* @param request - The completion request
|
|
549
|
+
* @returns Result with response or ModelError
|
|
550
|
+
*/
|
|
551
|
+
async complete(request) {
|
|
552
|
+
this.logRequest(request);
|
|
553
|
+
try {
|
|
554
|
+
const response = await this.executeCompletion(request);
|
|
555
|
+
this.logResponse(response);
|
|
556
|
+
return ok(response);
|
|
557
|
+
} catch (error) {
|
|
558
|
+
return err(this.transformError(error));
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Stream a completion request from Claude.
|
|
563
|
+
*
|
|
564
|
+
* @param request - The completion request
|
|
565
|
+
* @yields StreamChunk objects as they arrive
|
|
566
|
+
*/
|
|
567
|
+
async *stream(request) {
|
|
568
|
+
this.logRequest(request);
|
|
569
|
+
const [controller, iterable] = createStream();
|
|
570
|
+
this.executeStream(request, controller).catch((error) => {
|
|
571
|
+
const modelError = this.transformError(error);
|
|
572
|
+
controller.error(modelError);
|
|
573
|
+
});
|
|
574
|
+
yield* iterable;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Count tokens in text using Claude-specific estimation.
|
|
578
|
+
*
|
|
579
|
+
* Claude uses a custom tokenizer. This provides a more accurate estimate
|
|
580
|
+
* than the base adapter's generic calculation.
|
|
581
|
+
*
|
|
582
|
+
* @param text - Text to count tokens for
|
|
583
|
+
* @returns Approximate token count
|
|
584
|
+
*/
|
|
585
|
+
countTokens(text) {
|
|
586
|
+
return Promise.resolve(getTokenEstimator().estimateText(text, "claude"));
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Executes the completion request against the Anthropic API.
|
|
590
|
+
*/
|
|
591
|
+
async executeCompletion(request) {
|
|
592
|
+
const params = this.buildRequestParams(request);
|
|
593
|
+
const response = request.signal !== void 0 ? await this.client.messages.create(params, { signal: request.signal }) : await this.client.messages.create(params);
|
|
594
|
+
return this.mapResponse(response);
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Executes streaming completion and pushes chunks to the controller.
|
|
598
|
+
*/
|
|
599
|
+
async executeStream(request, controller) {
|
|
600
|
+
try {
|
|
601
|
+
const params = this.buildRequestParams(request);
|
|
602
|
+
const stream = this.client.messages.stream(params);
|
|
603
|
+
for await (const event of stream) {
|
|
604
|
+
const chunk = this.mapStreamEvent(event);
|
|
605
|
+
if (chunk) {
|
|
606
|
+
controller.push(chunk);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
controller.complete();
|
|
610
|
+
} catch (error) {
|
|
611
|
+
const modelError = this.transformError(error);
|
|
612
|
+
controller.error(modelError);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Builds Anthropic API request parameters from our CompletionRequest.
|
|
617
|
+
*/
|
|
618
|
+
buildRequestParams(request) {
|
|
619
|
+
const messages = request.messages.filter((m) => m.role !== "system").map(mapMessage);
|
|
620
|
+
const params = {
|
|
621
|
+
model: this.resolvedModelId,
|
|
622
|
+
messages,
|
|
623
|
+
max_tokens: request.maxTokens ?? DEFAULT_MAX_TOKENS
|
|
624
|
+
};
|
|
625
|
+
const systemPrompt = extractRequestSystemPrompt(request);
|
|
626
|
+
if (systemPrompt !== void 0) {
|
|
627
|
+
params.system = systemPrompt;
|
|
628
|
+
}
|
|
629
|
+
this.applyOptionalParams(params, request);
|
|
630
|
+
return params;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Applies optional parameters to the request params.
|
|
634
|
+
*/
|
|
635
|
+
applyOptionalParams(params, request) {
|
|
636
|
+
if (request.temperature !== void 0) {
|
|
637
|
+
params.temperature = request.temperature;
|
|
638
|
+
}
|
|
639
|
+
if (request.stop !== void 0 && request.stop.length > 0) {
|
|
640
|
+
params.stop_sequences = request.stop;
|
|
641
|
+
}
|
|
642
|
+
if (request.tools !== void 0 && request.tools.length > 0) {
|
|
643
|
+
params.tools = request.tools.map(mapTool);
|
|
644
|
+
}
|
|
645
|
+
if (request.responseFormat !== void 0 && request.responseFormat.type !== "text") {
|
|
646
|
+
this.logger.warn("responseFormat is not supported by Claude adapter", {
|
|
647
|
+
requestedFormat: request.responseFormat.type,
|
|
648
|
+
suggestion: "Use tool use or prompt engineering for structured output"
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Maps Anthropic API response to our CompletionResponse format.
|
|
654
|
+
*/
|
|
655
|
+
mapResponse(response) {
|
|
656
|
+
const content = response.content.map(mapContentBlock);
|
|
657
|
+
const usage = {
|
|
658
|
+
inputTokens: response.usage.input_tokens,
|
|
659
|
+
outputTokens: response.usage.output_tokens,
|
|
660
|
+
totalTokens: response.usage.input_tokens + response.usage.output_tokens
|
|
661
|
+
};
|
|
662
|
+
return {
|
|
663
|
+
content,
|
|
664
|
+
usage,
|
|
665
|
+
stopReason: mapStopReason(response.stop_reason),
|
|
666
|
+
model: response.model
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Maps Anthropic stream events to our StreamChunk format.
|
|
671
|
+
*/
|
|
672
|
+
mapStreamEvent(event) {
|
|
673
|
+
switch (event.type) {
|
|
674
|
+
case "message_start":
|
|
675
|
+
return {
|
|
676
|
+
type: "message_start",
|
|
677
|
+
message: { model: event.message.model }
|
|
678
|
+
};
|
|
679
|
+
case "content_block_start":
|
|
680
|
+
return {
|
|
681
|
+
type: "content_block_start",
|
|
682
|
+
index: event.index,
|
|
683
|
+
contentBlock: mapContentBlock(event.content_block)
|
|
684
|
+
};
|
|
685
|
+
case "content_block_delta":
|
|
686
|
+
if (event.delta.type === "text_delta") {
|
|
687
|
+
return {
|
|
688
|
+
type: "content_block_delta",
|
|
689
|
+
index: event.index,
|
|
690
|
+
delta: { type: "text_delta", text: event.delta.text }
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
return null;
|
|
694
|
+
case "content_block_stop":
|
|
695
|
+
return {
|
|
696
|
+
type: "content_block_stop",
|
|
697
|
+
index: event.index
|
|
698
|
+
};
|
|
699
|
+
case "message_delta":
|
|
700
|
+
return {
|
|
701
|
+
type: "message_delta",
|
|
702
|
+
delta: { stop_reason: mapStopReason(event.delta.stop_reason ?? null) },
|
|
703
|
+
usage: {
|
|
704
|
+
inputTokens: 0,
|
|
705
|
+
// Not available in delta
|
|
706
|
+
outputTokens: event.usage.output_tokens,
|
|
707
|
+
totalTokens: event.usage.output_tokens
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
case "message_stop":
|
|
711
|
+
return { type: "message_stop" };
|
|
712
|
+
default:
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* (#2540) List models the Anthropic API currently exposes.
|
|
718
|
+
* Wraps `client.models.list()`. Cached for 5 min, in-flight promise
|
|
719
|
+
* shared across concurrent callers, throws on non-2xx so the
|
|
720
|
+
* harness-side identity resolver knows to fall back.
|
|
721
|
+
*/
|
|
722
|
+
async listModels() {
|
|
723
|
+
const now = Date.now();
|
|
724
|
+
if (this.modelsCache !== null && now - this.modelsCache.fetchedAt < CLAUDE_LIST_MODELS_TTL_MS) {
|
|
725
|
+
return this.modelsCache.value;
|
|
726
|
+
}
|
|
727
|
+
if (this.modelsInFlight !== null) return this.modelsInFlight;
|
|
728
|
+
const inFlight = this.fetchModels();
|
|
729
|
+
this.modelsInFlight = inFlight;
|
|
730
|
+
try {
|
|
731
|
+
const value = await inFlight;
|
|
732
|
+
this.modelsCache = { value, fetchedAt: Date.now() };
|
|
733
|
+
return value;
|
|
734
|
+
} finally {
|
|
735
|
+
this.modelsInFlight = null;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
modelsCache = null;
|
|
739
|
+
modelsInFlight = null;
|
|
740
|
+
async fetchModels() {
|
|
741
|
+
const list = await this.client.models.list();
|
|
742
|
+
const out = [];
|
|
743
|
+
for (const m of list.data) {
|
|
744
|
+
const entry = { id: m.id };
|
|
745
|
+
if (typeof m.created_at === "string") {
|
|
746
|
+
const ts = Date.parse(m.created_at);
|
|
747
|
+
if (!Number.isNaN(ts)) {
|
|
748
|
+
out.push({ ...entry, ownedBy: "anthropic", createdAt: Math.floor(ts / 1e3) });
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
out.push({ ...entry, ownedBy: "anthropic" });
|
|
753
|
+
}
|
|
754
|
+
return out;
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
var CLAUDE_LIST_MODELS_TTL_MS = 5 * 60 * 1e3;
|
|
758
|
+
function createClaudeAdapter(config) {
|
|
759
|
+
return new ClaudeAdapter(config);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/security/output-sanitizer.ts
|
|
763
|
+
var REDACTED_KEY_PLACEHOLDER = "[REDACTED_KEY]";
|
|
764
|
+
var KEY_PATTERNS = [
|
|
765
|
+
// Anthropic: sk-ant-api03-... (at least 20 chars after prefix)
|
|
766
|
+
/sk-ant-[A-Za-z0-9_-]{20,}/g,
|
|
767
|
+
// OpenAI project: sk-proj-... (at least 20 chars after prefix)
|
|
768
|
+
/sk-proj-[A-Za-z0-9_-]{20,}/g,
|
|
769
|
+
// Generic OpenAI: sk-... (at least 20 chars after prefix)
|
|
770
|
+
/sk-[A-Za-z0-9_-]{20,}/g,
|
|
771
|
+
// Google AI / Gemini: AIzaSy... (at least 30 chars total)
|
|
772
|
+
/AIzaSy[A-Za-z0-9_-]{24,}/g,
|
|
773
|
+
// GitHub PAT: ghp_...
|
|
774
|
+
/ghp_[A-Za-z0-9]{20,}/g,
|
|
775
|
+
// GitHub OAuth: gho_...
|
|
776
|
+
/gho_[A-Za-z0-9]{20,}/g,
|
|
777
|
+
// GitLab PAT: glpat-...
|
|
778
|
+
/glpat-[A-Za-z0-9_-]{10,}/g,
|
|
779
|
+
// npm token: npm_...
|
|
780
|
+
/npm_[A-Za-z0-9]{20,}/g,
|
|
781
|
+
// PyPI token: pypi-...
|
|
782
|
+
/pypi-[A-Za-z0-9_-]{20,}/g
|
|
783
|
+
];
|
|
784
|
+
function sanitizeOutput(text) {
|
|
785
|
+
if (text === "") return text;
|
|
786
|
+
let result = text;
|
|
787
|
+
for (const pattern of KEY_PATTERNS) {
|
|
788
|
+
pattern.lastIndex = 0;
|
|
789
|
+
result = result.replace(pattern, REDACTED_KEY_PLACEHOLDER);
|
|
790
|
+
}
|
|
791
|
+
return result;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// src/adapters/sdk/sdk-adapter.ts
|
|
795
|
+
function extractProviderFactory(mod, factoryName) {
|
|
796
|
+
const factory = mod[factoryName];
|
|
797
|
+
if (typeof factory !== "function") {
|
|
798
|
+
throw new Error(`AI SDK module missing expected export: ${factoryName}`);
|
|
799
|
+
}
|
|
800
|
+
return factory;
|
|
801
|
+
}
|
|
802
|
+
function extractAiSdkFunctions(mod) {
|
|
803
|
+
const generateText = mod["generateText"];
|
|
804
|
+
const streamText = mod["streamText"];
|
|
805
|
+
if (typeof generateText !== "function") {
|
|
806
|
+
throw new Error("AI SDK module missing expected export: 'generateText'");
|
|
807
|
+
}
|
|
808
|
+
if (typeof streamText !== "function") {
|
|
809
|
+
throw new Error("AI SDK module missing expected export: 'streamText'");
|
|
810
|
+
}
|
|
811
|
+
return {
|
|
812
|
+
generateText,
|
|
813
|
+
streamText
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
function resolveApiKey(providerId, configKey) {
|
|
817
|
+
if (configKey !== void 0) return configKey;
|
|
818
|
+
const envVar = PROVIDER_ENV_KEYS[providerId];
|
|
819
|
+
return process.env[envVar];
|
|
820
|
+
}
|
|
821
|
+
function resolveAndValidateCustomBaseUrl(config) {
|
|
822
|
+
if (config.providerId !== "custom-openai") return void 0;
|
|
823
|
+
const raw = config.baseUrl ?? process.env[CUSTOM_API_BASE_URL_ENV];
|
|
824
|
+
const validated = validateCustomApiBaseUrl(raw);
|
|
825
|
+
if (!validated.ok) throw validated.error;
|
|
826
|
+
return validated.value.toString();
|
|
827
|
+
}
|
|
828
|
+
function mapFinishReason(reason) {
|
|
829
|
+
switch (reason) {
|
|
830
|
+
case "stop":
|
|
831
|
+
case "end-turn":
|
|
832
|
+
return "end_turn";
|
|
833
|
+
case "length":
|
|
834
|
+
return "max_tokens";
|
|
835
|
+
case "tool-calls":
|
|
836
|
+
return "tool_use";
|
|
837
|
+
default:
|
|
838
|
+
return "end_turn";
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
function categorizeError(error) {
|
|
842
|
+
if (isRateLimitLikeError(error)) {
|
|
843
|
+
return ErrorCode.MODEL_RATE_LIMITED;
|
|
844
|
+
}
|
|
845
|
+
const message = getErrorMessage(error).toLowerCase();
|
|
846
|
+
if (message.includes("timeout") || message.includes("timed out")) {
|
|
847
|
+
return ErrorCode.MODEL_TIMEOUT;
|
|
848
|
+
}
|
|
849
|
+
if (message.includes("401") || message.includes("unauthorized") || message.includes("api key")) {
|
|
850
|
+
return ErrorCode.CONFIG_INVALID;
|
|
851
|
+
}
|
|
852
|
+
return ErrorCode.MODEL_ERROR;
|
|
853
|
+
}
|
|
854
|
+
var SdkAdapter = class extends BaseAdapter {
|
|
855
|
+
sdkProviderId;
|
|
856
|
+
model;
|
|
857
|
+
sdkFunctions;
|
|
858
|
+
sdkConfig;
|
|
859
|
+
/** Validated base URL for custom-openai provider; undefined for built-ins. */
|
|
860
|
+
customBaseUrl;
|
|
861
|
+
/** Inflight init promise for coalescing concurrent calls (Issue #1438). */
|
|
862
|
+
initPromise;
|
|
863
|
+
constructor(config, logger6) {
|
|
864
|
+
const apiKey = resolveApiKey(config.providerId, config.apiKey);
|
|
865
|
+
super({
|
|
866
|
+
providerId: `sdk-${config.providerId}`,
|
|
867
|
+
modelId: config.modelId,
|
|
868
|
+
capabilities: [ModelCapability.COMPLETION, ModelCapability.STREAMING],
|
|
869
|
+
logger: logger6 ?? createLogger({ adapter: `sdk-${config.providerId}` }),
|
|
870
|
+
...apiKey !== void 0 ? { apiKey } : {},
|
|
871
|
+
...config.timeout !== void 0 ? { timeout: config.timeout } : {},
|
|
872
|
+
...config.maxRetries !== void 0 ? { maxRetries: config.maxRetries } : {}
|
|
873
|
+
});
|
|
874
|
+
this.sdkProviderId = config.providerId;
|
|
875
|
+
this.sdkConfig = config;
|
|
876
|
+
this.customBaseUrl = resolveAndValidateCustomBaseUrl(config);
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Lazily initialize the AI SDK model and functions.
|
|
880
|
+
* This allows the adapter to be created without the AI SDK installed,
|
|
881
|
+
* failing only when actually used.
|
|
882
|
+
*/
|
|
883
|
+
async ensureInitialized() {
|
|
884
|
+
if (this.model !== void 0) return;
|
|
885
|
+
if (this.initPromise !== void 0) {
|
|
886
|
+
await this.initPromise;
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
this.initPromise = this.doInitialize();
|
|
890
|
+
try {
|
|
891
|
+
await this.initPromise;
|
|
892
|
+
} finally {
|
|
893
|
+
this.initPromise = void 0;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
async doInitialize() {
|
|
897
|
+
const apiKey = resolveApiKey(this.sdkProviderId, this.sdkConfig.apiKey);
|
|
898
|
+
if (apiKey === void 0) {
|
|
899
|
+
throw new AdapterModelError(`No API key for ${this.sdkProviderId}`, {
|
|
900
|
+
code: ErrorCode.CONFIG_INVALID
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
const providerModule = await this.loadProvider(apiKey);
|
|
904
|
+
this.model = providerModule.model;
|
|
905
|
+
const aiModule = await import("ai");
|
|
906
|
+
this.sdkFunctions = extractAiSdkFunctions(aiModule);
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Loads the provider-specific AI SDK module.
|
|
910
|
+
*
|
|
911
|
+
* Each @ai-sdk/* package exports a factory function (e.g., createAnthropic)
|
|
912
|
+
* that returns a callable provider instance. We use extractProviderFactory()
|
|
913
|
+
* to validate the export exists at runtime, since these are optional peer deps.
|
|
914
|
+
*/
|
|
915
|
+
async loadProvider(apiKey) {
|
|
916
|
+
switch (this.sdkProviderId) {
|
|
917
|
+
case "anthropic": {
|
|
918
|
+
const mod = await import("@ai-sdk/anthropic");
|
|
919
|
+
const factory = extractProviderFactory(mod, "createAnthropic");
|
|
920
|
+
const provider = factory({ apiKey });
|
|
921
|
+
return { model: provider(this.modelId) };
|
|
922
|
+
}
|
|
923
|
+
case "openai": {
|
|
924
|
+
const mod = await import("@ai-sdk/openai");
|
|
925
|
+
const factory = extractProviderFactory(mod, "createOpenAI");
|
|
926
|
+
const provider = factory({ apiKey });
|
|
927
|
+
return { model: provider(this.modelId) };
|
|
928
|
+
}
|
|
929
|
+
case "google": {
|
|
930
|
+
const mod = await import("@ai-sdk/google");
|
|
931
|
+
const factory = extractProviderFactory(mod, "createGoogleGenerativeAI");
|
|
932
|
+
const provider = factory({ apiKey });
|
|
933
|
+
return { model: provider(this.modelId) };
|
|
934
|
+
}
|
|
935
|
+
case "custom-openai": {
|
|
936
|
+
const mod = await import("@ai-sdk/openai");
|
|
937
|
+
const factory = extractProviderFactory(mod, "createOpenAI");
|
|
938
|
+
const opts = { apiKey };
|
|
939
|
+
if (this.customBaseUrl !== void 0) opts["baseURL"] = this.customBaseUrl;
|
|
940
|
+
const provider = factory(opts);
|
|
941
|
+
return { model: provider(this.modelId) };
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Maps our CompletionRequest to AI SDK generateText options.
|
|
947
|
+
*/
|
|
948
|
+
buildSdkOptions(request) {
|
|
949
|
+
const options = {
|
|
950
|
+
model: this.model,
|
|
951
|
+
messages: request.messages.map((m) => ({
|
|
952
|
+
role: m.role === "system" ? "system" : m.role,
|
|
953
|
+
content: typeof m.content === "string" ? m.content : m.content.map((c) => {
|
|
954
|
+
if (c.type === "text") return { type: "text", text: c.text };
|
|
955
|
+
return c;
|
|
956
|
+
})
|
|
957
|
+
}))
|
|
958
|
+
};
|
|
959
|
+
if (request.systemPrompt !== void 0) {
|
|
960
|
+
options["system"] = request.systemPrompt;
|
|
961
|
+
}
|
|
962
|
+
if (request.temperature !== void 0) {
|
|
963
|
+
options["temperature"] = request.temperature;
|
|
964
|
+
}
|
|
965
|
+
if (request.maxTokens !== void 0) {
|
|
966
|
+
options["maxTokens"] = request.maxTokens;
|
|
967
|
+
}
|
|
968
|
+
if (request.stop !== void 0) {
|
|
969
|
+
options["stopSequences"] = request.stop;
|
|
970
|
+
}
|
|
971
|
+
return options;
|
|
972
|
+
}
|
|
973
|
+
async complete(request) {
|
|
974
|
+
try {
|
|
975
|
+
await this.ensureInitialized();
|
|
976
|
+
this.logRequest(request);
|
|
977
|
+
const sdk = this.sdkFunctions;
|
|
978
|
+
if (sdk === void 0) {
|
|
979
|
+
throw new Error(
|
|
980
|
+
`SDK not initialized for model '${this.sdkConfig.modelId}' (provider: ${this.sdkProviderId}). Ensure ensureInitialized() completes before calling complete().`
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
const options = this.buildSdkOptions(request);
|
|
984
|
+
const result = await sdk.generateText(options);
|
|
985
|
+
const response = {
|
|
986
|
+
content: [{ type: "text", text: result.text }],
|
|
987
|
+
usage: {
|
|
988
|
+
inputTokens: result.usage.inputTokens ?? 0,
|
|
989
|
+
outputTokens: result.usage.outputTokens ?? 0,
|
|
990
|
+
totalTokens: result.usage.totalTokens ?? 0
|
|
991
|
+
},
|
|
992
|
+
stopReason: mapFinishReason(result.finishReason),
|
|
993
|
+
model: result.response.modelId
|
|
994
|
+
};
|
|
995
|
+
this.logResponse(response);
|
|
996
|
+
return ok(response);
|
|
997
|
+
} catch (error) {
|
|
998
|
+
const code = categorizeError(error);
|
|
999
|
+
return this.toErrorResult(error, code);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
async *stream(request) {
|
|
1003
|
+
await this.ensureInitialized();
|
|
1004
|
+
this.logRequest(request);
|
|
1005
|
+
const sdk = this.sdkFunctions;
|
|
1006
|
+
if (sdk === void 0) {
|
|
1007
|
+
throw new AdapterModelError("SDK not initialized after ensureInitialized()", {
|
|
1008
|
+
code: ErrorCode.CONFIG_INVALID
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
const options = this.buildSdkOptions(request);
|
|
1012
|
+
yield { type: "message_start", message: { model: this.modelId } };
|
|
1013
|
+
const result = sdk.streamText(options);
|
|
1014
|
+
let index = 0;
|
|
1015
|
+
yield { type: "content_block_start", index, contentBlock: { type: "text", text: "" } };
|
|
1016
|
+
for await (const text of result.textStream) {
|
|
1017
|
+
if (text === "") continue;
|
|
1018
|
+
yield {
|
|
1019
|
+
type: "content_block_delta",
|
|
1020
|
+
index,
|
|
1021
|
+
delta: { type: "text_delta", text }
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
yield { type: "content_block_stop", index };
|
|
1025
|
+
index++;
|
|
1026
|
+
yield {
|
|
1027
|
+
type: "message_delta",
|
|
1028
|
+
delta: { stop_reason: "end_turn" },
|
|
1029
|
+
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
|
|
1030
|
+
};
|
|
1031
|
+
yield { type: "message_stop" };
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Converts a caught error into a Result error with categorized ErrorCode.
|
|
1035
|
+
*/
|
|
1036
|
+
toErrorResult(error, code) {
|
|
1037
|
+
const message = getErrorMessage(error);
|
|
1038
|
+
const safeMessage = sanitizeOutput(message);
|
|
1039
|
+
const errorObj = error instanceof Error ? error : new Error(safeMessage);
|
|
1040
|
+
this.logger.error(`SDK adapter error (${this.sdkProviderId})`, errorObj);
|
|
1041
|
+
const modelError = new AdapterModelError(`${this.sdkProviderId} SDK error: ${safeMessage}`, {
|
|
1042
|
+
code
|
|
1043
|
+
});
|
|
1044
|
+
return { ok: false, error: modelError };
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
|
|
1048
|
+
// src/cli-adapters/cli-detection-cache.ts
|
|
1049
|
+
import { z } from "zod";
|
|
1050
|
+
var CliDetectionCacheConfigSchema = z.object({
|
|
1051
|
+
ttlMs: z.number().min(1e3).max(36e5).default(3e5)
|
|
1052
|
+
});
|
|
1053
|
+
var DEFAULT_CACHE_CONFIG = {
|
|
1054
|
+
ttlMs: 3e5,
|
|
1055
|
+
// 5 minutes
|
|
1056
|
+
adaptiveTtl: true
|
|
1057
|
+
};
|
|
1058
|
+
var ADAPTIVE_MULTIPLIER_HEALTHY = 2;
|
|
1059
|
+
var ADAPTIVE_MULTIPLIER_UNHEALTHY = 0.25;
|
|
1060
|
+
var ADAPTIVE_STREAK_THRESHOLD = 2;
|
|
1061
|
+
var CliDetectionCache = class {
|
|
1062
|
+
config;
|
|
1063
|
+
logger;
|
|
1064
|
+
cache = /* @__PURE__ */ new Map();
|
|
1065
|
+
/** Consecutive same-health-status count per CLI (for adaptive TTL). */
|
|
1066
|
+
streaks = /* @__PURE__ */ new Map();
|
|
1067
|
+
hits = 0;
|
|
1068
|
+
misses = 0;
|
|
1069
|
+
lastReset = new Date(getTimeProvider().now());
|
|
1070
|
+
constructor(config) {
|
|
1071
|
+
const validated = CliDetectionCacheConfigSchema.parse({
|
|
1072
|
+
ttlMs: config?.ttlMs ?? DEFAULT_CACHE_CONFIG.ttlMs
|
|
1073
|
+
});
|
|
1074
|
+
this.config = {
|
|
1075
|
+
...validated,
|
|
1076
|
+
adaptiveTtl: config?.adaptiveTtl ?? DEFAULT_CACHE_CONFIG.adaptiveTtl,
|
|
1077
|
+
logger: config?.logger
|
|
1078
|
+
};
|
|
1079
|
+
this.logger = config?.logger ?? createLogger({ component: "CliDetectionCache" });
|
|
1080
|
+
this.logger.debug("CliDetectionCache initialized", { ttlMs: this.config.ttlMs });
|
|
1081
|
+
}
|
|
1082
|
+
get(cli) {
|
|
1083
|
+
const result = this.cache.get(cli);
|
|
1084
|
+
if (result === void 0) {
|
|
1085
|
+
this.misses++;
|
|
1086
|
+
this.logger.debug("Cache miss", { cli });
|
|
1087
|
+
return void 0;
|
|
1088
|
+
}
|
|
1089
|
+
if (this.isStale(cli)) {
|
|
1090
|
+
this.misses++;
|
|
1091
|
+
this.logger.debug("Cache stale", { cli, checkedAt: result.checkedAt });
|
|
1092
|
+
return void 0;
|
|
1093
|
+
}
|
|
1094
|
+
this.hits++;
|
|
1095
|
+
this.logger.debug("Cache hit", { cli, healthy: result.healthy });
|
|
1096
|
+
return result;
|
|
1097
|
+
}
|
|
1098
|
+
set(cli, result) {
|
|
1099
|
+
this.cache.set(cli, result);
|
|
1100
|
+
this.updateStreak(cli, result.healthy);
|
|
1101
|
+
this.logger.debug("Cache updated", {
|
|
1102
|
+
cli,
|
|
1103
|
+
healthy: result.healthy,
|
|
1104
|
+
version: result.version
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
isStale(cli) {
|
|
1108
|
+
const result = this.cache.get(cli);
|
|
1109
|
+
if (result === void 0) return true;
|
|
1110
|
+
const age = getTimeProvider().now() - result.checkedAt.getTime();
|
|
1111
|
+
return age > this.getEffectiveTtl(cli);
|
|
1112
|
+
}
|
|
1113
|
+
/** Returns the effective TTL for a CLI, applying adaptive multiplier if enabled. */
|
|
1114
|
+
getEffectiveTtl(cli) {
|
|
1115
|
+
if (this.config.adaptiveTtl === false) return this.config.ttlMs;
|
|
1116
|
+
const streak = this.streaks.get(cli);
|
|
1117
|
+
if (streak === void 0 || streak.count < ADAPTIVE_STREAK_THRESHOLD) {
|
|
1118
|
+
return this.config.ttlMs;
|
|
1119
|
+
}
|
|
1120
|
+
const multiplier = streak.healthy ? ADAPTIVE_MULTIPLIER_HEALTHY : ADAPTIVE_MULTIPLIER_UNHEALTHY;
|
|
1121
|
+
return this.config.ttlMs * multiplier;
|
|
1122
|
+
}
|
|
1123
|
+
updateStreak(cli, healthy) {
|
|
1124
|
+
const prev = this.streaks.get(cli);
|
|
1125
|
+
if (prev?.healthy === healthy) {
|
|
1126
|
+
this.streaks.set(cli, { healthy, count: prev.count + 1 });
|
|
1127
|
+
} else {
|
|
1128
|
+
this.streaks.set(cli, { healthy, count: 1 });
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
invalidate(cli) {
|
|
1132
|
+
if (cli !== void 0) {
|
|
1133
|
+
this.cache.delete(cli);
|
|
1134
|
+
this.streaks.delete(cli);
|
|
1135
|
+
this.logger.info("Cache invalidated", { cli });
|
|
1136
|
+
} else {
|
|
1137
|
+
this.cache.clear();
|
|
1138
|
+
this.streaks.clear();
|
|
1139
|
+
this.logger.info("Cache cleared");
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
getAll() {
|
|
1143
|
+
return this.cache;
|
|
1144
|
+
}
|
|
1145
|
+
getStats() {
|
|
1146
|
+
const total = this.hits + this.misses;
|
|
1147
|
+
return {
|
|
1148
|
+
size: this.cache.size,
|
|
1149
|
+
hits: this.hits,
|
|
1150
|
+
misses: this.misses,
|
|
1151
|
+
hitRate: total > 0 ? this.hits / total : 0,
|
|
1152
|
+
lastReset: this.lastReset
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Resets cache statistics.
|
|
1157
|
+
*/
|
|
1158
|
+
resetStats() {
|
|
1159
|
+
this.hits = 0;
|
|
1160
|
+
this.misses = 0;
|
|
1161
|
+
this.lastReset = new Date(getTimeProvider().now());
|
|
1162
|
+
this.logger.debug("Cache stats reset");
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Converts HealthStatus to CliHealthResult for caching.
|
|
1166
|
+
*/
|
|
1167
|
+
static fromHealthStatus(status) {
|
|
1168
|
+
return {
|
|
1169
|
+
healthy: status.healthy,
|
|
1170
|
+
version: status.version,
|
|
1171
|
+
versionStatus: status.versionStatus,
|
|
1172
|
+
checkedAt: status.lastChecked,
|
|
1173
|
+
message: status.message
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
function createCliDetectionCache(config) {
|
|
1178
|
+
return new CliDetectionCache(config);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// src/adapters/auto-adapter.ts
|
|
1182
|
+
var defaultLogger = createLogger({ component: "auto-adapter" });
|
|
1183
|
+
function resolveCache(config, logger6) {
|
|
1184
|
+
if (config.cache !== void 0) {
|
|
1185
|
+
return config.cache;
|
|
1186
|
+
}
|
|
1187
|
+
const enableCache = config.enableCache ?? true;
|
|
1188
|
+
return enableCache ? createCliDetectionCache({ logger: logger6 }) : void 0;
|
|
1189
|
+
}
|
|
1190
|
+
async function tryCliAdapter(config, logger6, cache) {
|
|
1191
|
+
const preferredCli = config.preferredCli;
|
|
1192
|
+
const bridgeConfig = config.defaultCliTimeoutMs !== void 0 ? { defaultTimeoutMs: config.defaultCliTimeoutMs } : void 0;
|
|
1193
|
+
if (preferredCli !== void 0 && await isCliAvailable(preferredCli, cache)) {
|
|
1194
|
+
logger6.info("Using preferred CLI", { cli: preferredCli });
|
|
1195
|
+
const cliAdapter2 = createCliAdapter({ cli: preferredCli, logger: logger6 });
|
|
1196
|
+
await cliAdapter2.initialize();
|
|
1197
|
+
return {
|
|
1198
|
+
adapter: createCliToModelAdapter(cliAdapter2, bridgeConfig),
|
|
1199
|
+
source: "cli",
|
|
1200
|
+
name: preferredCli,
|
|
1201
|
+
reason: `Preferred CLI '${preferredCli}' is available (model selection handled by CLI)`,
|
|
1202
|
+
cache
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
const availableClis = await getAvailableClis(cache);
|
|
1206
|
+
if (availableClis.length === 0) {
|
|
1207
|
+
logger6.info("No CLI adapters available");
|
|
1208
|
+
return null;
|
|
1209
|
+
}
|
|
1210
|
+
const selectedCli = availableClis[0];
|
|
1211
|
+
if (selectedCli === void 0) {
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
logger6.info("Auto-selected CLI", { cli: selectedCli, available: availableClis });
|
|
1215
|
+
const cliAdapter = createCliAdapter({ cli: selectedCli, logger: logger6 });
|
|
1216
|
+
await cliAdapter.initialize();
|
|
1217
|
+
return {
|
|
1218
|
+
adapter: createCliToModelAdapter(cliAdapter, bridgeConfig),
|
|
1219
|
+
source: "cli",
|
|
1220
|
+
name: selectedCli,
|
|
1221
|
+
reason: `Using '${selectedCli}' CLI (model selection handled by CLI)`,
|
|
1222
|
+
cache
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
function resolveApiKeyFromEnv(configKey, envVar) {
|
|
1226
|
+
const key = configKey ?? process.env[envVar];
|
|
1227
|
+
return key !== void 0 && key.length > 0 ? key : void 0;
|
|
1228
|
+
}
|
|
1229
|
+
function tryApiAdapter(config, logger6) {
|
|
1230
|
+
const claudeModelId = getCliModelName(getDefaultModelForCli("claude"));
|
|
1231
|
+
const codexModelId = getCliModelName(getDefaultModelForCli("codex"));
|
|
1232
|
+
const geminiModelId = getCliModelName(getDefaultModelForCli("gemini"));
|
|
1233
|
+
const anthropicKey = resolveApiKeyFromEnv(config.anthropicApiKey, "ANTHROPIC_API_KEY");
|
|
1234
|
+
if (anthropicKey !== void 0) {
|
|
1235
|
+
logger6.info("Using Anthropic API adapter", { model: claudeModelId });
|
|
1236
|
+
return {
|
|
1237
|
+
adapter: createClaudeAdapter({ modelId: claudeModelId, apiKey: anthropicKey }),
|
|
1238
|
+
source: "api",
|
|
1239
|
+
name: "anthropic",
|
|
1240
|
+
reason: `Using Anthropic API (native adapter, model: ${claudeModelId})`
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
const openaiKey = resolveApiKeyFromEnv(config.openaiApiKey, "OPENAI_API_KEY");
|
|
1244
|
+
if (openaiKey !== void 0) {
|
|
1245
|
+
logger6.info("Using OpenAI API adapter (AI SDK)", { model: codexModelId });
|
|
1246
|
+
return {
|
|
1247
|
+
adapter: new SdkAdapter({ providerId: "openai", modelId: codexModelId, apiKey: openaiKey }),
|
|
1248
|
+
source: "api",
|
|
1249
|
+
name: "openai",
|
|
1250
|
+
reason: `Using OpenAI API via AI SDK (model: ${codexModelId})`
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
const googleKey = resolveApiKeyFromEnv(config.googleApiKey, "GOOGLE_AI_API_KEY");
|
|
1254
|
+
if (googleKey !== void 0) {
|
|
1255
|
+
logger6.info("Using Google AI API adapter (AI SDK)", { model: geminiModelId });
|
|
1256
|
+
return {
|
|
1257
|
+
adapter: new SdkAdapter({
|
|
1258
|
+
providerId: "google",
|
|
1259
|
+
modelId: geminiModelId,
|
|
1260
|
+
apiKey: googleKey
|
|
1261
|
+
}),
|
|
1262
|
+
source: "api",
|
|
1263
|
+
name: "google",
|
|
1264
|
+
reason: `Using Google AI API via AI SDK (model: ${geminiModelId})`
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
const custom = tryCustomOpenAiAdapter(logger6);
|
|
1268
|
+
if (custom !== null) return custom;
|
|
1269
|
+
logger6.info("No API keys available for any provider");
|
|
1270
|
+
return null;
|
|
1271
|
+
}
|
|
1272
|
+
function tryCustomOpenAiAdapter(logger6) {
|
|
1273
|
+
const customKey = resolveApiKeyFromEnv(void 0, "NEXUS_CUSTOM_API_KEY");
|
|
1274
|
+
const customBaseUrl = process.env["NEXUS_CUSTOM_API_BASE_URL"];
|
|
1275
|
+
if (customKey === void 0 || customBaseUrl === void 0 || customBaseUrl === "") {
|
|
1276
|
+
return null;
|
|
1277
|
+
}
|
|
1278
|
+
const customModelId = process.env["NEXUS_CUSTOM_MODEL"] ?? CUSTOM_API_DEFAULT_MODEL;
|
|
1279
|
+
logger6.info("Using custom-openai SDK adapter", {
|
|
1280
|
+
model: customModelId,
|
|
1281
|
+
baseUrl: customBaseUrl
|
|
1282
|
+
});
|
|
1283
|
+
return {
|
|
1284
|
+
adapter: new SdkAdapter({
|
|
1285
|
+
providerId: "custom-openai",
|
|
1286
|
+
modelId: customModelId,
|
|
1287
|
+
apiKey: customKey,
|
|
1288
|
+
baseUrl: customBaseUrl
|
|
1289
|
+
}),
|
|
1290
|
+
source: "api",
|
|
1291
|
+
name: "custom-openai",
|
|
1292
|
+
reason: `Using custom OpenAI-compatible gateway at ${customBaseUrl} (model: ${customModelId})`
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
function resolveApiVendor(name) {
|
|
1296
|
+
switch (name) {
|
|
1297
|
+
case "anthropic":
|
|
1298
|
+
return { vendor: "anthropic", slot: "claude" };
|
|
1299
|
+
case "openai":
|
|
1300
|
+
return { vendor: "openai", slot: "codex" };
|
|
1301
|
+
case "google":
|
|
1302
|
+
return { vendor: "google", slot: "gemini" };
|
|
1303
|
+
case "custom-openai":
|
|
1304
|
+
return { vendor: "custom-openai", slot: "opencode" };
|
|
1305
|
+
default:
|
|
1306
|
+
return void 0;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
function wrapApiSelectionForRouter(selection) {
|
|
1310
|
+
if (selection.source !== "api") return null;
|
|
1311
|
+
const resolved = resolveApiVendor(selection.name);
|
|
1312
|
+
if (resolved === void 0) return null;
|
|
1313
|
+
const adapter = createModelToCliAdapter(selection.adapter, {
|
|
1314
|
+
name: resolved.slot,
|
|
1315
|
+
capabilities: buildCliCapabilityProfiles()[resolved.slot]
|
|
1316
|
+
});
|
|
1317
|
+
return { armId: apiArmId(resolved.vendor), adapter };
|
|
1318
|
+
}
|
|
1319
|
+
function buildApiSelectionForVendor(vendor, logger6) {
|
|
1320
|
+
switch (vendor) {
|
|
1321
|
+
case "anthropic": {
|
|
1322
|
+
const key = resolveApiKeyFromEnv(void 0, "ANTHROPIC_API_KEY");
|
|
1323
|
+
if (key === void 0) return null;
|
|
1324
|
+
const modelId = getCliModelName(getDefaultModelForCli("claude"));
|
|
1325
|
+
return {
|
|
1326
|
+
adapter: createClaudeAdapter({ modelId, apiKey: key }),
|
|
1327
|
+
source: "api",
|
|
1328
|
+
name: "anthropic",
|
|
1329
|
+
reason: `Using Anthropic API (native adapter, model: ${modelId})`
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
case "openai": {
|
|
1333
|
+
const key = resolveApiKeyFromEnv(void 0, "OPENAI_API_KEY");
|
|
1334
|
+
if (key === void 0) return null;
|
|
1335
|
+
const modelId = getCliModelName(getDefaultModelForCli("codex"));
|
|
1336
|
+
return {
|
|
1337
|
+
adapter: new SdkAdapter({ providerId: "openai", modelId, apiKey: key }),
|
|
1338
|
+
source: "api",
|
|
1339
|
+
name: "openai",
|
|
1340
|
+
reason: `Using OpenAI API via AI SDK (model: ${modelId})`
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
case "google": {
|
|
1344
|
+
const key = resolveApiKeyFromEnv(void 0, "GOOGLE_AI_API_KEY");
|
|
1345
|
+
if (key === void 0) return null;
|
|
1346
|
+
const modelId = getCliModelName(getDefaultModelForCli("gemini"));
|
|
1347
|
+
return {
|
|
1348
|
+
adapter: new SdkAdapter({ providerId: "google", modelId, apiKey: key }),
|
|
1349
|
+
source: "api",
|
|
1350
|
+
name: "google",
|
|
1351
|
+
reason: `Using Google AI API via AI SDK (model: ${modelId})`
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
case "custom-openai":
|
|
1355
|
+
return tryCustomOpenAiAdapter(logger6);
|
|
1356
|
+
default: {
|
|
1357
|
+
const exhaustive = vendor;
|
|
1358
|
+
throw new Error(`Unknown API vendor: ${String(exhaustive)}`);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
var API_ROUTING_VENDORS = [
|
|
1363
|
+
"anthropic",
|
|
1364
|
+
"openai",
|
|
1365
|
+
"google",
|
|
1366
|
+
"custom-openai"
|
|
1367
|
+
];
|
|
1368
|
+
function collectApiRoutingArms(logger6 = defaultLogger) {
|
|
1369
|
+
const arms = [];
|
|
1370
|
+
for (const vendor of API_ROUTING_VENDORS) {
|
|
1371
|
+
const selection = buildApiSelectionForVendor(vendor, logger6);
|
|
1372
|
+
if (selection === null) continue;
|
|
1373
|
+
const wrapped = wrapApiSelectionForRouter(selection);
|
|
1374
|
+
if (wrapped !== null) arms.push(wrapped);
|
|
1375
|
+
}
|
|
1376
|
+
return arms;
|
|
1377
|
+
}
|
|
1378
|
+
async function selectCliFirst(config, logger6, cache) {
|
|
1379
|
+
const cliResult = await tryCliAdapter(config, logger6, cache);
|
|
1380
|
+
if (cliResult !== null) return cliResult;
|
|
1381
|
+
const apiResult = tryApiAdapter(config, logger6);
|
|
1382
|
+
if (apiResult !== null) return apiResult;
|
|
1383
|
+
throw new Error(
|
|
1384
|
+
"No adapters available. Install a CLI (claude/gemini/codex) or set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_AI_API_KEY."
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
async function selectApiFirst(config, logger6, cache) {
|
|
1388
|
+
const apiResult = tryApiAdapter(config, logger6);
|
|
1389
|
+
if (apiResult !== null) return apiResult;
|
|
1390
|
+
const cliResult = await tryCliAdapter(config, logger6, cache);
|
|
1391
|
+
if (cliResult !== null) return cliResult;
|
|
1392
|
+
throw new Error(
|
|
1393
|
+
"No adapters available. Set ANTHROPIC_API_KEY/OPENAI_API_KEY/GOOGLE_AI_API_KEY or install a CLI (claude/gemini/codex)."
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1396
|
+
async function selectCliOnly(config, logger6, cache) {
|
|
1397
|
+
const cliResult = await tryCliAdapter(config, logger6, cache);
|
|
1398
|
+
if (cliResult !== null) return cliResult;
|
|
1399
|
+
throw new Error(
|
|
1400
|
+
"No CLI adapters available. Install and authenticate claude, gemini, or codex CLI."
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
function selectApiOnly(config, logger6) {
|
|
1404
|
+
const apiResult = tryApiAdapter(config, logger6);
|
|
1405
|
+
if (apiResult !== null) return apiResult;
|
|
1406
|
+
throw new Error(
|
|
1407
|
+
"No API key available. Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_AI_API_KEY."
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
async function createAutoAdapter(config = {}) {
|
|
1411
|
+
const logger6 = config.logger ?? defaultLogger;
|
|
1412
|
+
const priority = config.priority ?? "cli-first";
|
|
1413
|
+
const cache = resolveCache(config, logger6);
|
|
1414
|
+
logger6.info("Auto-selecting adapter", { priority, cacheEnabled: cache !== void 0 });
|
|
1415
|
+
switch (priority) {
|
|
1416
|
+
case "cli-first":
|
|
1417
|
+
return selectCliFirst(config, logger6, cache);
|
|
1418
|
+
case "api-first":
|
|
1419
|
+
return selectApiFirst(config, logger6, cache);
|
|
1420
|
+
case "cli-only":
|
|
1421
|
+
return selectCliOnly(config, logger6, cache);
|
|
1422
|
+
case "api-only":
|
|
1423
|
+
return selectApiOnly(config, logger6);
|
|
1424
|
+
default: {
|
|
1425
|
+
const exhaustive = priority;
|
|
1426
|
+
throw new Error(`Unknown priority: ${String(exhaustive)}`);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
36
1430
|
|
|
37
1431
|
// src/cli-adapters/subprocess-adapter.ts
|
|
38
1432
|
import { spawn } from "child_process";
|
|
@@ -315,7 +1709,7 @@ function calculateBackoffDelay(attempt, baseDelayMs, maxDelayMs) {
|
|
|
315
1709
|
function isRetryableError(code) {
|
|
316
1710
|
return RETRYABLE_ERROR_CODES.has(code);
|
|
317
1711
|
}
|
|
318
|
-
function
|
|
1712
|
+
function categorizeError2(error) {
|
|
319
1713
|
switch (error.code) {
|
|
320
1714
|
case "TIMEOUT":
|
|
321
1715
|
return "timeout";
|
|
@@ -339,7 +1733,7 @@ async function executeCliRetryLoop(executeFn, config) {
|
|
|
339
1733
|
}
|
|
340
1734
|
lastError = result.error;
|
|
341
1735
|
if (config.circuitBreaker !== void 0 && config.circuitBreaker !== null) {
|
|
342
|
-
config.circuitBreaker.recordFailure(
|
|
1736
|
+
config.circuitBreaker.recordFailure(categorizeError2(lastError));
|
|
343
1737
|
}
|
|
344
1738
|
if (!shouldRetry(lastError, attempt, maxAttempts, config.circuitBreaker)) {
|
|
345
1739
|
return err(lastError);
|
|
@@ -691,38 +2085,6 @@ function buildChildEnv(cliName) {
|
|
|
691
2085
|
return childEnv;
|
|
692
2086
|
}
|
|
693
2087
|
|
|
694
|
-
// src/security/output-sanitizer.ts
|
|
695
|
-
var REDACTED_KEY_PLACEHOLDER = "[REDACTED_KEY]";
|
|
696
|
-
var KEY_PATTERNS = [
|
|
697
|
-
// Anthropic: sk-ant-api03-... (at least 20 chars after prefix)
|
|
698
|
-
/sk-ant-[A-Za-z0-9_-]{20,}/g,
|
|
699
|
-
// OpenAI project: sk-proj-... (at least 20 chars after prefix)
|
|
700
|
-
/sk-proj-[A-Za-z0-9_-]{20,}/g,
|
|
701
|
-
// Generic OpenAI: sk-... (at least 20 chars after prefix)
|
|
702
|
-
/sk-[A-Za-z0-9_-]{20,}/g,
|
|
703
|
-
// Google AI / Gemini: AIzaSy... (at least 30 chars total)
|
|
704
|
-
/AIzaSy[A-Za-z0-9_-]{24,}/g,
|
|
705
|
-
// GitHub PAT: ghp_...
|
|
706
|
-
/ghp_[A-Za-z0-9]{20,}/g,
|
|
707
|
-
// GitHub OAuth: gho_...
|
|
708
|
-
/gho_[A-Za-z0-9]{20,}/g,
|
|
709
|
-
// GitLab PAT: glpat-...
|
|
710
|
-
/glpat-[A-Za-z0-9_-]{10,}/g,
|
|
711
|
-
// npm token: npm_...
|
|
712
|
-
/npm_[A-Za-z0-9]{20,}/g,
|
|
713
|
-
// PyPI token: pypi-...
|
|
714
|
-
/pypi-[A-Za-z0-9_-]{20,}/g
|
|
715
|
-
];
|
|
716
|
-
function sanitizeOutput(text) {
|
|
717
|
-
if (text === "") return text;
|
|
718
|
-
let result = text;
|
|
719
|
-
for (const pattern of KEY_PATTERNS) {
|
|
720
|
-
pattern.lastIndex = 0;
|
|
721
|
-
result = result.replace(pattern, REDACTED_KEY_PLACEHOLDER);
|
|
722
|
-
}
|
|
723
|
-
return result;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
2088
|
// src/cli-adapters/cli-error-envelope.ts
|
|
727
2089
|
function isClaudeErrorEnvelope(v) {
|
|
728
2090
|
if (typeof v !== "object" || v === null) return false;
|
|
@@ -3000,139 +4362,6 @@ ${task.content}` : task.content;
|
|
|
3000
4362
|
}
|
|
3001
4363
|
};
|
|
3002
4364
|
|
|
3003
|
-
// src/cli-adapters/cli-detection-cache.ts
|
|
3004
|
-
import { z } from "zod";
|
|
3005
|
-
var CliDetectionCacheConfigSchema = z.object({
|
|
3006
|
-
ttlMs: z.number().min(1e3).max(36e5).default(3e5)
|
|
3007
|
-
});
|
|
3008
|
-
var DEFAULT_CACHE_CONFIG = {
|
|
3009
|
-
ttlMs: 3e5,
|
|
3010
|
-
// 5 minutes
|
|
3011
|
-
adaptiveTtl: true
|
|
3012
|
-
};
|
|
3013
|
-
var ADAPTIVE_MULTIPLIER_HEALTHY = 2;
|
|
3014
|
-
var ADAPTIVE_MULTIPLIER_UNHEALTHY = 0.25;
|
|
3015
|
-
var ADAPTIVE_STREAK_THRESHOLD = 2;
|
|
3016
|
-
var CliDetectionCache = class {
|
|
3017
|
-
config;
|
|
3018
|
-
logger;
|
|
3019
|
-
cache = /* @__PURE__ */ new Map();
|
|
3020
|
-
/** Consecutive same-health-status count per CLI (for adaptive TTL). */
|
|
3021
|
-
streaks = /* @__PURE__ */ new Map();
|
|
3022
|
-
hits = 0;
|
|
3023
|
-
misses = 0;
|
|
3024
|
-
lastReset = new Date(getTimeProvider().now());
|
|
3025
|
-
constructor(config) {
|
|
3026
|
-
const validated = CliDetectionCacheConfigSchema.parse({
|
|
3027
|
-
ttlMs: config?.ttlMs ?? DEFAULT_CACHE_CONFIG.ttlMs
|
|
3028
|
-
});
|
|
3029
|
-
this.config = {
|
|
3030
|
-
...validated,
|
|
3031
|
-
adaptiveTtl: config?.adaptiveTtl ?? DEFAULT_CACHE_CONFIG.adaptiveTtl,
|
|
3032
|
-
logger: config?.logger
|
|
3033
|
-
};
|
|
3034
|
-
this.logger = config?.logger ?? createLogger({ component: "CliDetectionCache" });
|
|
3035
|
-
this.logger.debug("CliDetectionCache initialized", { ttlMs: this.config.ttlMs });
|
|
3036
|
-
}
|
|
3037
|
-
get(cli) {
|
|
3038
|
-
const result = this.cache.get(cli);
|
|
3039
|
-
if (result === void 0) {
|
|
3040
|
-
this.misses++;
|
|
3041
|
-
this.logger.debug("Cache miss", { cli });
|
|
3042
|
-
return void 0;
|
|
3043
|
-
}
|
|
3044
|
-
if (this.isStale(cli)) {
|
|
3045
|
-
this.misses++;
|
|
3046
|
-
this.logger.debug("Cache stale", { cli, checkedAt: result.checkedAt });
|
|
3047
|
-
return void 0;
|
|
3048
|
-
}
|
|
3049
|
-
this.hits++;
|
|
3050
|
-
this.logger.debug("Cache hit", { cli, healthy: result.healthy });
|
|
3051
|
-
return result;
|
|
3052
|
-
}
|
|
3053
|
-
set(cli, result) {
|
|
3054
|
-
this.cache.set(cli, result);
|
|
3055
|
-
this.updateStreak(cli, result.healthy);
|
|
3056
|
-
this.logger.debug("Cache updated", {
|
|
3057
|
-
cli,
|
|
3058
|
-
healthy: result.healthy,
|
|
3059
|
-
version: result.version
|
|
3060
|
-
});
|
|
3061
|
-
}
|
|
3062
|
-
isStale(cli) {
|
|
3063
|
-
const result = this.cache.get(cli);
|
|
3064
|
-
if (result === void 0) return true;
|
|
3065
|
-
const age = getTimeProvider().now() - result.checkedAt.getTime();
|
|
3066
|
-
return age > this.getEffectiveTtl(cli);
|
|
3067
|
-
}
|
|
3068
|
-
/** Returns the effective TTL for a CLI, applying adaptive multiplier if enabled. */
|
|
3069
|
-
getEffectiveTtl(cli) {
|
|
3070
|
-
if (this.config.adaptiveTtl === false) return this.config.ttlMs;
|
|
3071
|
-
const streak = this.streaks.get(cli);
|
|
3072
|
-
if (streak === void 0 || streak.count < ADAPTIVE_STREAK_THRESHOLD) {
|
|
3073
|
-
return this.config.ttlMs;
|
|
3074
|
-
}
|
|
3075
|
-
const multiplier = streak.healthy ? ADAPTIVE_MULTIPLIER_HEALTHY : ADAPTIVE_MULTIPLIER_UNHEALTHY;
|
|
3076
|
-
return this.config.ttlMs * multiplier;
|
|
3077
|
-
}
|
|
3078
|
-
updateStreak(cli, healthy) {
|
|
3079
|
-
const prev = this.streaks.get(cli);
|
|
3080
|
-
if (prev?.healthy === healthy) {
|
|
3081
|
-
this.streaks.set(cli, { healthy, count: prev.count + 1 });
|
|
3082
|
-
} else {
|
|
3083
|
-
this.streaks.set(cli, { healthy, count: 1 });
|
|
3084
|
-
}
|
|
3085
|
-
}
|
|
3086
|
-
invalidate(cli) {
|
|
3087
|
-
if (cli !== void 0) {
|
|
3088
|
-
this.cache.delete(cli);
|
|
3089
|
-
this.streaks.delete(cli);
|
|
3090
|
-
this.logger.info("Cache invalidated", { cli });
|
|
3091
|
-
} else {
|
|
3092
|
-
this.cache.clear();
|
|
3093
|
-
this.streaks.clear();
|
|
3094
|
-
this.logger.info("Cache cleared");
|
|
3095
|
-
}
|
|
3096
|
-
}
|
|
3097
|
-
getAll() {
|
|
3098
|
-
return this.cache;
|
|
3099
|
-
}
|
|
3100
|
-
getStats() {
|
|
3101
|
-
const total = this.hits + this.misses;
|
|
3102
|
-
return {
|
|
3103
|
-
size: this.cache.size,
|
|
3104
|
-
hits: this.hits,
|
|
3105
|
-
misses: this.misses,
|
|
3106
|
-
hitRate: total > 0 ? this.hits / total : 0,
|
|
3107
|
-
lastReset: this.lastReset
|
|
3108
|
-
};
|
|
3109
|
-
}
|
|
3110
|
-
/**
|
|
3111
|
-
* Resets cache statistics.
|
|
3112
|
-
*/
|
|
3113
|
-
resetStats() {
|
|
3114
|
-
this.hits = 0;
|
|
3115
|
-
this.misses = 0;
|
|
3116
|
-
this.lastReset = new Date(getTimeProvider().now());
|
|
3117
|
-
this.logger.debug("Cache stats reset");
|
|
3118
|
-
}
|
|
3119
|
-
/**
|
|
3120
|
-
* Converts HealthStatus to CliHealthResult for caching.
|
|
3121
|
-
*/
|
|
3122
|
-
static fromHealthStatus(status) {
|
|
3123
|
-
return {
|
|
3124
|
-
healthy: status.healthy,
|
|
3125
|
-
version: status.version,
|
|
3126
|
-
versionStatus: status.versionStatus,
|
|
3127
|
-
checkedAt: status.lastChecked,
|
|
3128
|
-
message: status.message
|
|
3129
|
-
};
|
|
3130
|
-
}
|
|
3131
|
-
};
|
|
3132
|
-
function createCliDetectionCache(config) {
|
|
3133
|
-
return new CliDetectionCache(config);
|
|
3134
|
-
}
|
|
3135
|
-
|
|
3136
4365
|
// src/cli/cli-auth-probe.ts
|
|
3137
4366
|
import { execFile as execFile2 } from "child_process";
|
|
3138
4367
|
import { promisify as promisify2 } from "util";
|
|
@@ -3354,6 +4583,11 @@ function createAllAdapters(logger6, codexTransport = "mcp") {
|
|
|
3354
4583
|
adapters.set("gemini", new GeminiCliAdapter(options));
|
|
3355
4584
|
adapters.set("codex", createCodexAdapter(codexTransport, options ?? {}));
|
|
3356
4585
|
adapters.set("opencode", new OpenCodeCliAdapter(options));
|
|
4586
|
+
if (process.env["NEXUS_BILLING_MODE"] === "api") {
|
|
4587
|
+
for (const { armId, adapter } of collectApiRoutingArms(logger6)) {
|
|
4588
|
+
adapters.set(armId, adapter);
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
3357
4591
|
return adapters;
|
|
3358
4592
|
}
|
|
3359
4593
|
async function isCliAvailable(cli, cache) {
|
|
@@ -3408,9 +4642,17 @@ async function getAvailableClis(cache) {
|
|
|
3408
4642
|
}
|
|
3409
4643
|
|
|
3410
4644
|
export {
|
|
4645
|
+
generateCacheKey,
|
|
4646
|
+
messagesToAnthropicFormat,
|
|
4647
|
+
extractSystemPrompt,
|
|
4648
|
+
extractRequestSystemPrompt,
|
|
3411
4649
|
sleep,
|
|
3412
4650
|
delay,
|
|
3413
4651
|
withTimeout,
|
|
4652
|
+
CLAUDE_MODELS,
|
|
4653
|
+
CLAUDE_MODEL_ALIASES,
|
|
4654
|
+
ClaudeAdapter,
|
|
4655
|
+
createClaudeAdapter,
|
|
3414
4656
|
CLI_TIMEOUT_PROFILES,
|
|
3415
4657
|
DEFAULT_TIMEOUT_PROFILE,
|
|
3416
4658
|
getTimeoutForTask,
|
|
@@ -3418,10 +4660,9 @@ export {
|
|
|
3418
4660
|
getTimeoutForTaskAuto,
|
|
3419
4661
|
calculateBackoffDelay,
|
|
3420
4662
|
isRetryableError,
|
|
3421
|
-
categorizeError,
|
|
4663
|
+
categorizeError2 as categorizeError,
|
|
3422
4664
|
executeCliRetryLoop,
|
|
3423
4665
|
BaseCliAdapter,
|
|
3424
|
-
sanitizeOutput,
|
|
3425
4666
|
authRemediation,
|
|
3426
4667
|
SubprocessCliAdapter,
|
|
3427
4668
|
asRecord,
|
|
@@ -3443,6 +4684,8 @@ export {
|
|
|
3443
4684
|
createCliAdapter,
|
|
3444
4685
|
createAllAdapters,
|
|
3445
4686
|
isCliAvailable,
|
|
3446
|
-
getAvailableClis
|
|
4687
|
+
getAvailableClis,
|
|
4688
|
+
SdkAdapter,
|
|
4689
|
+
createAutoAdapter
|
|
3447
4690
|
};
|
|
3448
|
-
//# sourceMappingURL=chunk-
|
|
4691
|
+
//# sourceMappingURL=chunk-2MPLNCGG.js.map
|