nexus-agents 2.105.0 → 2.106.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/{child-mcp-config-XOIMNU6R.js → child-mcp-config-KP2XBOIB.js} +2 -2
  2. package/dist/{chunk-WDVUKSIQ.js → chunk-2DHISFDY.js} +90 -49
  3. package/dist/chunk-2DHISFDY.js.map +1 -0
  4. package/dist/{chunk-FVJ2LM2G.js → chunk-2J6HSR6X.js} +2 -2
  5. package/dist/{chunk-BMM6OX2K.js → chunk-3YWBA5GE.js} +7 -7
  6. package/dist/{chunk-46GXDOWQ.js → chunk-5UU6BQJ3.js} +2 -2
  7. package/dist/{chunk-M54LGBTY.js → chunk-75NKH2VD.js} +4 -4
  8. package/dist/{chunk-ETGNZ6H2.js → chunk-BEWKRRNZ.js} +2 -2
  9. package/dist/{chunk-I5QZ3PVT.js → chunk-BJQA6DSN.js} +2 -2
  10. package/dist/{chunk-WT46JLBD.js → chunk-CNIRLXNH.js} +3 -3
  11. package/dist/{chunk-TFRXRKU5.js → chunk-CUX775NF.js} +4 -4
  12. package/dist/{chunk-UFTQXDUA.js → chunk-DLFJQ6W6.js} +2 -2
  13. package/dist/{chunk-I7UDM43R.js → chunk-DOIOSEDU.js} +118 -4
  14. package/dist/chunk-DOIOSEDU.js.map +1 -0
  15. package/dist/{chunk-2B7332HE.js → chunk-E3JGMUGY.js} +4 -4
  16. package/dist/{chunk-IFO52GSQ.js → chunk-EBJJGYOQ.js} +3 -3
  17. package/dist/{chunk-VIHUOFIT.js → chunk-GMHCPEHS.js} +1418 -176
  18. package/dist/chunk-GMHCPEHS.js.map +1 -0
  19. package/dist/{chunk-MKSP4M55.js → chunk-GRHCGUBQ.js} +9 -1034
  20. package/dist/chunk-GRHCGUBQ.js.map +1 -0
  21. package/dist/{chunk-FTFFYAY6.js → chunk-HNBA265P.js} +39 -37
  22. package/dist/{chunk-FTFFYAY6.js.map → chunk-HNBA265P.js.map} +1 -1
  23. package/dist/{chunk-ABNHZEKO.js → chunk-KMASI7UC.js} +2 -2
  24. package/dist/{chunk-XWOLOXKN.js → chunk-KZWJ52HH.js} +2 -2
  25. package/dist/{chunk-A5UJBVCQ.js → chunk-M6JRWJZW.js} +2 -2
  26. package/dist/{chunk-RZ4A2RRN.js → chunk-MYXT7VPF.js} +2 -2
  27. package/dist/{chunk-7RE2S46N.js → chunk-OP4VPTQZ.js} +3 -3
  28. package/dist/{chunk-P5XUYJBO.js → chunk-PRPHNXUM.js} +3 -3
  29. package/dist/{chunk-75BXDWVG.js → chunk-R2VTDJO5.js} +5 -5
  30. package/dist/{chunk-OESO6AQL.js → chunk-RWAI74H6.js} +9 -9
  31. package/dist/{chunk-VCLQMXVJ.js → chunk-SFIGDPAE.js} +3 -3
  32. package/dist/{chunk-3TZUEGNB.js → chunk-U3Y6AWG5.js} +2 -2
  33. package/dist/{chunk-VGHPMNDR.js → chunk-VHIRBRZF.js} +2 -2
  34. package/dist/{chunk-PGSXRUH5.js → chunk-VIXH6P3Y.js} +3 -3
  35. package/dist/{chunk-E6FIVDNH.js → chunk-WPF7G56I.js} +2 -2
  36. package/dist/{chunk-K7OKFTCH.js → chunk-YL7SS6JZ.js} +2 -2
  37. package/dist/{cli-circuit-breaker-QMCG6JGV.js → cli-circuit-breaker-PE3AYA56.js} +4 -4
  38. package/dist/cli.js +42 -42
  39. package/dist/cli.js.map +1 -1
  40. package/dist/{composite-router-ZX64CEVG.js → composite-router-GXLSZZQU.js} +2 -2
  41. package/dist/{consensus-vote-VUNGPXXS.js → consensus-vote-ZS4IDC5T.js} +12 -13
  42. package/dist/{context-retriever-J7DIPQUO.js → context-retriever-52RLYDVE.js} +6 -6
  43. package/dist/{doctor-deep-ITSJV2RH.js → doctor-deep-3463IMLO.js} +3 -3
  44. package/dist/{expert-bridge-MQURDKJI.js → expert-bridge-OBOSFIHR.js} +4 -4
  45. package/dist/factory-SM7AIAFL.js +21 -0
  46. package/dist/{factory-Y2RF33UB.js → factory-VXD4XO7R.js} +5 -5
  47. package/dist/{improvement-review-NBVWPFXR.js → improvement-review-Y4DSR2AF.js} +4 -4
  48. package/dist/index.d.ts +36 -13
  49. package/dist/index.js +61 -63
  50. package/dist/index.js.map +1 -1
  51. package/dist/{init-opencode-QPHX4KVI.js → init-opencode-3FW2TAOR.js} +5 -5
  52. package/dist/{issue-triage-D5ADEG3V.js → issue-triage-KAVEQE5V.js} +5 -5
  53. package/dist/{pr-reviewer-helpers-R7U4EI2P.js → pr-reviewer-helpers-KORDKN6O.js} +4 -4
  54. package/dist/{registry-command-CFNNKJ5S.js → registry-command-WZK7PYVZ.js} +2 -2
  55. package/dist/{repo-security-plan-2AXFDREW.js → repo-security-plan-EO5R3S7C.js} +3 -3
  56. package/dist/{research-helpers-synthesize-YTIDNKBF.js → research-helpers-synthesize-6HFD32XJ.js} +3 -3
  57. package/dist/{routing-memory-YLX6QNY2.js → routing-memory-XPJT3Y6Z.js} +2 -2
  58. package/dist/{session-memory-Y5MUOY56.js → session-memory-TTE5YPV4.js} +3 -3
  59. package/dist/{setup-command-KTMIAEU6.js → setup-command-VVHIWCD4.js} +11 -10
  60. package/dist/{setup-config-5J3R7RET.js → setup-config-NEM4KPUA.js} +3 -3
  61. package/dist/{setup-custom-api-MJ24PJA7.js → setup-custom-api-GMMHEJJ5.js} +4 -6
  62. package/dist/{setup-custom-api-MJ24PJA7.js.map → setup-custom-api-GMMHEJJ5.js.map} +1 -1
  63. package/dist/{tool-memory-YNMRI3PZ.js → tool-memory-TTLQ7MHZ.js} +5 -5
  64. package/dist/{weather-report-GZRODMYW.js → weather-report-KGAHUQFF.js} +2 -2
  65. package/package.json +1 -1
  66. package/dist/chunk-BLMWODJG.js +0 -120
  67. package/dist/chunk-BLMWODJG.js.map +0 -1
  68. package/dist/chunk-I7UDM43R.js.map +0 -1
  69. package/dist/chunk-MKSP4M55.js.map +0 -1
  70. package/dist/chunk-VIHUOFIT.js.map +0 -1
  71. package/dist/chunk-WDVUKSIQ.js.map +0 -1
  72. package/dist/factory-UZQ3RGFQ.js +0 -19
  73. /package/dist/{child-mcp-config-XOIMNU6R.js.map → child-mcp-config-KP2XBOIB.js.map} +0 -0
  74. /package/dist/{chunk-FVJ2LM2G.js.map → chunk-2J6HSR6X.js.map} +0 -0
  75. /package/dist/{chunk-BMM6OX2K.js.map → chunk-3YWBA5GE.js.map} +0 -0
  76. /package/dist/{chunk-46GXDOWQ.js.map → chunk-5UU6BQJ3.js.map} +0 -0
  77. /package/dist/{chunk-M54LGBTY.js.map → chunk-75NKH2VD.js.map} +0 -0
  78. /package/dist/{chunk-ETGNZ6H2.js.map → chunk-BEWKRRNZ.js.map} +0 -0
  79. /package/dist/{chunk-I5QZ3PVT.js.map → chunk-BJQA6DSN.js.map} +0 -0
  80. /package/dist/{chunk-WT46JLBD.js.map → chunk-CNIRLXNH.js.map} +0 -0
  81. /package/dist/{chunk-TFRXRKU5.js.map → chunk-CUX775NF.js.map} +0 -0
  82. /package/dist/{chunk-UFTQXDUA.js.map → chunk-DLFJQ6W6.js.map} +0 -0
  83. /package/dist/{chunk-2B7332HE.js.map → chunk-E3JGMUGY.js.map} +0 -0
  84. /package/dist/{chunk-IFO52GSQ.js.map → chunk-EBJJGYOQ.js.map} +0 -0
  85. /package/dist/{chunk-ABNHZEKO.js.map → chunk-KMASI7UC.js.map} +0 -0
  86. /package/dist/{chunk-XWOLOXKN.js.map → chunk-KZWJ52HH.js.map} +0 -0
  87. /package/dist/{chunk-A5UJBVCQ.js.map → chunk-M6JRWJZW.js.map} +0 -0
  88. /package/dist/{chunk-RZ4A2RRN.js.map → chunk-MYXT7VPF.js.map} +0 -0
  89. /package/dist/{chunk-7RE2S46N.js.map → chunk-OP4VPTQZ.js.map} +0 -0
  90. /package/dist/{chunk-P5XUYJBO.js.map → chunk-PRPHNXUM.js.map} +0 -0
  91. /package/dist/{chunk-75BXDWVG.js.map → chunk-R2VTDJO5.js.map} +0 -0
  92. /package/dist/{chunk-OESO6AQL.js.map → chunk-RWAI74H6.js.map} +0 -0
  93. /package/dist/{chunk-VCLQMXVJ.js.map → chunk-SFIGDPAE.js.map} +0 -0
  94. /package/dist/{chunk-3TZUEGNB.js.map → chunk-U3Y6AWG5.js.map} +0 -0
  95. /package/dist/{chunk-VGHPMNDR.js.map → chunk-VHIRBRZF.js.map} +0 -0
  96. /package/dist/{chunk-PGSXRUH5.js.map → chunk-VIXH6P3Y.js.map} +0 -0
  97. /package/dist/{chunk-E6FIVDNH.js.map → chunk-WPF7G56I.js.map} +0 -0
  98. /package/dist/{chunk-K7OKFTCH.js.map → chunk-YL7SS6JZ.js.map} +0 -0
  99. /package/dist/{cli-circuit-breaker-QMCG6JGV.js.map → cli-circuit-breaker-PE3AYA56.js.map} +0 -0
  100. /package/dist/{composite-router-ZX64CEVG.js.map → composite-router-GXLSZZQU.js.map} +0 -0
  101. /package/dist/{consensus-vote-VUNGPXXS.js.map → consensus-vote-ZS4IDC5T.js.map} +0 -0
  102. /package/dist/{context-retriever-J7DIPQUO.js.map → context-retriever-52RLYDVE.js.map} +0 -0
  103. /package/dist/{doctor-deep-ITSJV2RH.js.map → doctor-deep-3463IMLO.js.map} +0 -0
  104. /package/dist/{expert-bridge-MQURDKJI.js.map → expert-bridge-OBOSFIHR.js.map} +0 -0
  105. /package/dist/{factory-UZQ3RGFQ.js.map → factory-SM7AIAFL.js.map} +0 -0
  106. /package/dist/{factory-Y2RF33UB.js.map → factory-VXD4XO7R.js.map} +0 -0
  107. /package/dist/{improvement-review-NBVWPFXR.js.map → improvement-review-Y4DSR2AF.js.map} +0 -0
  108. /package/dist/{init-opencode-QPHX4KVI.js.map → init-opencode-3FW2TAOR.js.map} +0 -0
  109. /package/dist/{issue-triage-D5ADEG3V.js.map → issue-triage-KAVEQE5V.js.map} +0 -0
  110. /package/dist/{pr-reviewer-helpers-R7U4EI2P.js.map → pr-reviewer-helpers-KORDKN6O.js.map} +0 -0
  111. /package/dist/{registry-command-CFNNKJ5S.js.map → registry-command-WZK7PYVZ.js.map} +0 -0
  112. /package/dist/{repo-security-plan-2AXFDREW.js.map → repo-security-plan-EO5R3S7C.js.map} +0 -0
  113. /package/dist/{research-helpers-synthesize-YTIDNKBF.js.map → research-helpers-synthesize-6HFD32XJ.js.map} +0 -0
  114. /package/dist/{routing-memory-YLX6QNY2.js.map → routing-memory-XPJT3Y6Z.js.map} +0 -0
  115. /package/dist/{session-memory-Y5MUOY56.js.map → session-memory-TTE5YPV4.js.map} +0 -0
  116. /package/dist/{setup-command-KTMIAEU6.js.map → setup-command-VVHIWCD4.js.map} +0 -0
  117. /package/dist/{setup-config-5J3R7RET.js.map → setup-config-NEM4KPUA.js.map} +0 -0
  118. /package/dist/{tool-memory-YNMRI3PZ.js.map → tool-memory-TTLQ7MHZ.js.map} +0 -0
  119. /package/dist/{weather-report-GZRODMYW.js.map → weather-report-KGAHUQFF.js.map} +0 -0
@@ -1,24 +1,43 @@
1
+ import {
2
+ AdapterModelError,
3
+ BaseAdapter,
4
+ createStream,
5
+ requireApiKey,
6
+ validateApiKeyPresence
7
+ } from "./chunk-KMASI7UC.js";
8
+ import {
9
+ CUSTOM_API_BASE_URL_ENV,
10
+ CUSTOM_API_DEFAULT_MODEL,
11
+ PROVIDER_ENV_KEYS,
12
+ validateCustomApiBaseUrl
13
+ } from "./chunk-DOIOSEDU.js";
1
14
  import {
2
15
  CliCircuitBreaker,
3
16
  DEFAULT_CIRCUIT_BREAKER_CONFIG
4
- } from "./chunk-ETGNZ6H2.js";
17
+ } from "./chunk-BEWKRRNZ.js";
5
18
  import {
6
19
  getAvailabilityCache
7
- } from "./chunk-RZ4A2RRN.js";
20
+ } from "./chunk-MYXT7VPF.js";
8
21
  import {
9
22
  generateHyphenId
10
- } from "./chunk-A5UJBVCQ.js";
23
+ } from "./chunk-M6JRWJZW.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,1385 @@ 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
- } from "./chunk-WDVUKSIQ.js";
55
+ ok,
56
+ resolveCliAlias
57
+ } from "./chunk-2DHISFDY.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
+ yield {
1018
+ type: "content_block_delta",
1019
+ index,
1020
+ delta: { type: "text_delta", text }
1021
+ };
1022
+ }
1023
+ yield { type: "content_block_stop", index };
1024
+ index++;
1025
+ yield {
1026
+ type: "message_delta",
1027
+ delta: { stop_reason: "end_turn" },
1028
+ usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
1029
+ };
1030
+ yield { type: "message_stop" };
1031
+ }
1032
+ /**
1033
+ * Converts a caught error into a Result error with categorized ErrorCode.
1034
+ */
1035
+ toErrorResult(error, code) {
1036
+ const message = getErrorMessage(error);
1037
+ const safeMessage = sanitizeOutput(message);
1038
+ const errorObj = error instanceof Error ? error : new Error(safeMessage);
1039
+ this.logger.error(`SDK adapter error (${this.sdkProviderId})`, errorObj);
1040
+ const modelError = new AdapterModelError(`${this.sdkProviderId} SDK error: ${safeMessage}`, {
1041
+ code
1042
+ });
1043
+ return { ok: false, error: modelError };
1044
+ }
1045
+ };
1046
+
1047
+ // src/cli-adapters/cli-detection-cache.ts
1048
+ import { z } from "zod";
1049
+ var CliDetectionCacheConfigSchema = z.object({
1050
+ ttlMs: z.number().min(1e3).max(36e5).default(3e5)
1051
+ });
1052
+ var DEFAULT_CACHE_CONFIG = {
1053
+ ttlMs: 3e5,
1054
+ // 5 minutes
1055
+ adaptiveTtl: true
1056
+ };
1057
+ var ADAPTIVE_MULTIPLIER_HEALTHY = 2;
1058
+ var ADAPTIVE_MULTIPLIER_UNHEALTHY = 0.25;
1059
+ var ADAPTIVE_STREAK_THRESHOLD = 2;
1060
+ var CliDetectionCache = class {
1061
+ config;
1062
+ logger;
1063
+ cache = /* @__PURE__ */ new Map();
1064
+ /** Consecutive same-health-status count per CLI (for adaptive TTL). */
1065
+ streaks = /* @__PURE__ */ new Map();
1066
+ hits = 0;
1067
+ misses = 0;
1068
+ lastReset = new Date(getTimeProvider().now());
1069
+ constructor(config) {
1070
+ const validated = CliDetectionCacheConfigSchema.parse({
1071
+ ttlMs: config?.ttlMs ?? DEFAULT_CACHE_CONFIG.ttlMs
1072
+ });
1073
+ this.config = {
1074
+ ...validated,
1075
+ adaptiveTtl: config?.adaptiveTtl ?? DEFAULT_CACHE_CONFIG.adaptiveTtl,
1076
+ logger: config?.logger
1077
+ };
1078
+ this.logger = config?.logger ?? createLogger({ component: "CliDetectionCache" });
1079
+ this.logger.debug("CliDetectionCache initialized", { ttlMs: this.config.ttlMs });
1080
+ }
1081
+ get(cli) {
1082
+ const result = this.cache.get(cli);
1083
+ if (result === void 0) {
1084
+ this.misses++;
1085
+ this.logger.debug("Cache miss", { cli });
1086
+ return void 0;
1087
+ }
1088
+ if (this.isStale(cli)) {
1089
+ this.misses++;
1090
+ this.logger.debug("Cache stale", { cli, checkedAt: result.checkedAt });
1091
+ return void 0;
1092
+ }
1093
+ this.hits++;
1094
+ this.logger.debug("Cache hit", { cli, healthy: result.healthy });
1095
+ return result;
1096
+ }
1097
+ set(cli, result) {
1098
+ this.cache.set(cli, result);
1099
+ this.updateStreak(cli, result.healthy);
1100
+ this.logger.debug("Cache updated", {
1101
+ cli,
1102
+ healthy: result.healthy,
1103
+ version: result.version
1104
+ });
1105
+ }
1106
+ isStale(cli) {
1107
+ const result = this.cache.get(cli);
1108
+ if (result === void 0) return true;
1109
+ const age = getTimeProvider().now() - result.checkedAt.getTime();
1110
+ return age > this.getEffectiveTtl(cli);
1111
+ }
1112
+ /** Returns the effective TTL for a CLI, applying adaptive multiplier if enabled. */
1113
+ getEffectiveTtl(cli) {
1114
+ if (this.config.adaptiveTtl === false) return this.config.ttlMs;
1115
+ const streak = this.streaks.get(cli);
1116
+ if (streak === void 0 || streak.count < ADAPTIVE_STREAK_THRESHOLD) {
1117
+ return this.config.ttlMs;
1118
+ }
1119
+ const multiplier = streak.healthy ? ADAPTIVE_MULTIPLIER_HEALTHY : ADAPTIVE_MULTIPLIER_UNHEALTHY;
1120
+ return this.config.ttlMs * multiplier;
1121
+ }
1122
+ updateStreak(cli, healthy) {
1123
+ const prev = this.streaks.get(cli);
1124
+ if (prev?.healthy === healthy) {
1125
+ this.streaks.set(cli, { healthy, count: prev.count + 1 });
1126
+ } else {
1127
+ this.streaks.set(cli, { healthy, count: 1 });
1128
+ }
1129
+ }
1130
+ invalidate(cli) {
1131
+ if (cli !== void 0) {
1132
+ this.cache.delete(cli);
1133
+ this.streaks.delete(cli);
1134
+ this.logger.info("Cache invalidated", { cli });
1135
+ } else {
1136
+ this.cache.clear();
1137
+ this.streaks.clear();
1138
+ this.logger.info("Cache cleared");
1139
+ }
1140
+ }
1141
+ getAll() {
1142
+ return this.cache;
1143
+ }
1144
+ getStats() {
1145
+ const total = this.hits + this.misses;
1146
+ return {
1147
+ size: this.cache.size,
1148
+ hits: this.hits,
1149
+ misses: this.misses,
1150
+ hitRate: total > 0 ? this.hits / total : 0,
1151
+ lastReset: this.lastReset
1152
+ };
1153
+ }
1154
+ /**
1155
+ * Resets cache statistics.
1156
+ */
1157
+ resetStats() {
1158
+ this.hits = 0;
1159
+ this.misses = 0;
1160
+ this.lastReset = new Date(getTimeProvider().now());
1161
+ this.logger.debug("Cache stats reset");
1162
+ }
1163
+ /**
1164
+ * Converts HealthStatus to CliHealthResult for caching.
1165
+ */
1166
+ static fromHealthStatus(status) {
1167
+ return {
1168
+ healthy: status.healthy,
1169
+ version: status.version,
1170
+ versionStatus: status.versionStatus,
1171
+ checkedAt: status.lastChecked,
1172
+ message: status.message
1173
+ };
1174
+ }
1175
+ };
1176
+ function createCliDetectionCache(config) {
1177
+ return new CliDetectionCache(config);
1178
+ }
1179
+
1180
+ // src/adapters/auto-adapter.ts
1181
+ var defaultLogger = createLogger({ component: "auto-adapter" });
1182
+ function resolveCache(config, logger6) {
1183
+ if (config.cache !== void 0) {
1184
+ return config.cache;
1185
+ }
1186
+ const enableCache = config.enableCache ?? true;
1187
+ return enableCache ? createCliDetectionCache({ logger: logger6 }) : void 0;
1188
+ }
1189
+ async function tryCliAdapter(config, logger6, cache) {
1190
+ const preferredCli = config.preferredCli;
1191
+ const bridgeConfig = config.defaultCliTimeoutMs !== void 0 ? { defaultTimeoutMs: config.defaultCliTimeoutMs } : void 0;
1192
+ if (preferredCli !== void 0 && await isCliAvailable(preferredCli, cache)) {
1193
+ logger6.info("Using preferred CLI", { cli: preferredCli });
1194
+ const cliAdapter2 = createCliAdapter({ cli: preferredCli, logger: logger6 });
1195
+ await cliAdapter2.initialize();
1196
+ return {
1197
+ adapter: createCliToModelAdapter(cliAdapter2, bridgeConfig),
1198
+ source: "cli",
1199
+ name: preferredCli,
1200
+ reason: `Preferred CLI '${preferredCli}' is available (model selection handled by CLI)`,
1201
+ cache
1202
+ };
1203
+ }
1204
+ const availableClis = await getAvailableClis(cache);
1205
+ if (availableClis.length === 0) {
1206
+ logger6.info("No CLI adapters available");
1207
+ return null;
1208
+ }
1209
+ const selectedCli = availableClis[0];
1210
+ if (selectedCli === void 0) {
1211
+ return null;
1212
+ }
1213
+ logger6.info("Auto-selected CLI", { cli: selectedCli, available: availableClis });
1214
+ const cliAdapter = createCliAdapter({ cli: selectedCli, logger: logger6 });
1215
+ await cliAdapter.initialize();
1216
+ return {
1217
+ adapter: createCliToModelAdapter(cliAdapter, bridgeConfig),
1218
+ source: "cli",
1219
+ name: selectedCli,
1220
+ reason: `Using '${selectedCli}' CLI (model selection handled by CLI)`,
1221
+ cache
1222
+ };
1223
+ }
1224
+ function resolveApiKeyFromEnv(configKey, envVar) {
1225
+ const key = configKey ?? process.env[envVar];
1226
+ return key !== void 0 && key.length > 0 ? key : void 0;
1227
+ }
1228
+ function tryApiAdapter(config, logger6) {
1229
+ const claudeModelId = getCliModelName(getDefaultModelForCli("claude"));
1230
+ const codexModelId = getCliModelName(getDefaultModelForCli("codex"));
1231
+ const geminiModelId = getCliModelName(getDefaultModelForCli("gemini"));
1232
+ const anthropicKey = resolveApiKeyFromEnv(config.anthropicApiKey, "ANTHROPIC_API_KEY");
1233
+ if (anthropicKey !== void 0) {
1234
+ logger6.info("Using Anthropic API adapter", { model: claudeModelId });
1235
+ return {
1236
+ adapter: createClaudeAdapter({ modelId: claudeModelId, apiKey: anthropicKey }),
1237
+ source: "api",
1238
+ name: "anthropic",
1239
+ reason: `Using Anthropic API (native adapter, model: ${claudeModelId})`
1240
+ };
1241
+ }
1242
+ const openaiKey = resolveApiKeyFromEnv(config.openaiApiKey, "OPENAI_API_KEY");
1243
+ if (openaiKey !== void 0) {
1244
+ logger6.info("Using OpenAI API adapter (AI SDK)", { model: codexModelId });
1245
+ return {
1246
+ adapter: new SdkAdapter({ providerId: "openai", modelId: codexModelId, apiKey: openaiKey }),
1247
+ source: "api",
1248
+ name: "openai",
1249
+ reason: `Using OpenAI API via AI SDK (model: ${codexModelId})`
1250
+ };
1251
+ }
1252
+ const googleKey = resolveApiKeyFromEnv(config.googleApiKey, "GOOGLE_AI_API_KEY");
1253
+ if (googleKey !== void 0) {
1254
+ logger6.info("Using Google AI API adapter (AI SDK)", { model: geminiModelId });
1255
+ return {
1256
+ adapter: new SdkAdapter({
1257
+ providerId: "google",
1258
+ modelId: geminiModelId,
1259
+ apiKey: googleKey
1260
+ }),
1261
+ source: "api",
1262
+ name: "google",
1263
+ reason: `Using Google AI API via AI SDK (model: ${geminiModelId})`
1264
+ };
1265
+ }
1266
+ const custom = tryCustomOpenAiAdapter(logger6);
1267
+ if (custom !== null) return custom;
1268
+ logger6.info("No API keys available for any provider");
1269
+ return null;
1270
+ }
1271
+ function tryCustomOpenAiAdapter(logger6) {
1272
+ const customKey = resolveApiKeyFromEnv(void 0, "NEXUS_CUSTOM_API_KEY");
1273
+ const customBaseUrl = process.env["NEXUS_CUSTOM_API_BASE_URL"];
1274
+ if (customKey === void 0 || customBaseUrl === void 0 || customBaseUrl === "") {
1275
+ return null;
1276
+ }
1277
+ const customModelId = process.env["NEXUS_CUSTOM_MODEL"] ?? CUSTOM_API_DEFAULT_MODEL;
1278
+ logger6.info("Using custom-openai SDK adapter", {
1279
+ model: customModelId,
1280
+ baseUrl: customBaseUrl
1281
+ });
1282
+ return {
1283
+ adapter: new SdkAdapter({
1284
+ providerId: "custom-openai",
1285
+ modelId: customModelId,
1286
+ apiKey: customKey,
1287
+ baseUrl: customBaseUrl
1288
+ }),
1289
+ source: "api",
1290
+ name: "custom-openai",
1291
+ reason: `Using custom OpenAI-compatible gateway at ${customBaseUrl} (model: ${customModelId})`
1292
+ };
1293
+ }
1294
+ function resolveApiVendor(name) {
1295
+ switch (name) {
1296
+ case "anthropic":
1297
+ return { vendor: "anthropic", slot: "claude" };
1298
+ case "openai":
1299
+ return { vendor: "openai", slot: "codex" };
1300
+ case "google":
1301
+ return { vendor: "google", slot: "gemini" };
1302
+ case "custom-openai":
1303
+ return { vendor: "custom-openai", slot: "opencode" };
1304
+ default:
1305
+ return void 0;
1306
+ }
1307
+ }
1308
+ function wrapApiSelectionForRouter(selection) {
1309
+ if (selection.source !== "api") return null;
1310
+ const resolved = resolveApiVendor(selection.name);
1311
+ if (resolved === void 0) return null;
1312
+ const adapter = createModelToCliAdapter(selection.adapter, {
1313
+ name: resolved.slot,
1314
+ capabilities: buildCliCapabilityProfiles()[resolved.slot]
1315
+ });
1316
+ return { armId: apiArmId(resolved.vendor), adapter };
1317
+ }
1318
+ function buildApiSelectionForVendor(vendor, logger6) {
1319
+ switch (vendor) {
1320
+ case "anthropic": {
1321
+ const key = resolveApiKeyFromEnv(void 0, "ANTHROPIC_API_KEY");
1322
+ if (key === void 0) return null;
1323
+ const modelId = getCliModelName(getDefaultModelForCli("claude"));
1324
+ return {
1325
+ adapter: createClaudeAdapter({ modelId, apiKey: key }),
1326
+ source: "api",
1327
+ name: "anthropic",
1328
+ reason: `Using Anthropic API (native adapter, model: ${modelId})`
1329
+ };
1330
+ }
1331
+ case "openai": {
1332
+ const key = resolveApiKeyFromEnv(void 0, "OPENAI_API_KEY");
1333
+ if (key === void 0) return null;
1334
+ const modelId = getCliModelName(getDefaultModelForCli("codex"));
1335
+ return {
1336
+ adapter: new SdkAdapter({ providerId: "openai", modelId, apiKey: key }),
1337
+ source: "api",
1338
+ name: "openai",
1339
+ reason: `Using OpenAI API via AI SDK (model: ${modelId})`
1340
+ };
1341
+ }
1342
+ case "google": {
1343
+ const key = resolveApiKeyFromEnv(void 0, "GOOGLE_AI_API_KEY");
1344
+ if (key === void 0) return null;
1345
+ const modelId = getCliModelName(getDefaultModelForCli("gemini"));
1346
+ return {
1347
+ adapter: new SdkAdapter({ providerId: "google", modelId, apiKey: key }),
1348
+ source: "api",
1349
+ name: "google",
1350
+ reason: `Using Google AI API via AI SDK (model: ${modelId})`
1351
+ };
1352
+ }
1353
+ case "custom-openai":
1354
+ return tryCustomOpenAiAdapter(logger6);
1355
+ default: {
1356
+ const exhaustive = vendor;
1357
+ throw new Error(`Unknown API vendor: ${String(exhaustive)}`);
1358
+ }
1359
+ }
1360
+ }
1361
+ var API_ROUTING_VENDORS = [
1362
+ "anthropic",
1363
+ "openai",
1364
+ "google",
1365
+ "custom-openai"
1366
+ ];
1367
+ function collectApiRoutingArms(logger6 = defaultLogger) {
1368
+ const arms = [];
1369
+ for (const vendor of API_ROUTING_VENDORS) {
1370
+ const selection = buildApiSelectionForVendor(vendor, logger6);
1371
+ if (selection === null) continue;
1372
+ const wrapped = wrapApiSelectionForRouter(selection);
1373
+ if (wrapped !== null) arms.push(wrapped);
1374
+ }
1375
+ return arms;
1376
+ }
1377
+ async function selectCliFirst(config, logger6, cache) {
1378
+ const cliResult = await tryCliAdapter(config, logger6, cache);
1379
+ if (cliResult !== null) return cliResult;
1380
+ const apiResult = tryApiAdapter(config, logger6);
1381
+ if (apiResult !== null) return apiResult;
1382
+ throw new Error(
1383
+ "No adapters available. Install a CLI (claude/gemini/codex) or set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_AI_API_KEY."
1384
+ );
1385
+ }
1386
+ async function selectApiFirst(config, logger6, cache) {
1387
+ const apiResult = tryApiAdapter(config, logger6);
1388
+ if (apiResult !== null) return apiResult;
1389
+ const cliResult = await tryCliAdapter(config, logger6, cache);
1390
+ if (cliResult !== null) return cliResult;
1391
+ throw new Error(
1392
+ "No adapters available. Set ANTHROPIC_API_KEY/OPENAI_API_KEY/GOOGLE_AI_API_KEY or install a CLI (claude/gemini/codex)."
1393
+ );
1394
+ }
1395
+ async function selectCliOnly(config, logger6, cache) {
1396
+ const cliResult = await tryCliAdapter(config, logger6, cache);
1397
+ if (cliResult !== null) return cliResult;
1398
+ throw new Error(
1399
+ "No CLI adapters available. Install and authenticate claude, gemini, or codex CLI."
1400
+ );
1401
+ }
1402
+ function selectApiOnly(config, logger6) {
1403
+ const apiResult = tryApiAdapter(config, logger6);
1404
+ if (apiResult !== null) return apiResult;
1405
+ throw new Error(
1406
+ "No API key available. Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_AI_API_KEY."
1407
+ );
1408
+ }
1409
+ async function createAutoAdapter(config = {}) {
1410
+ const logger6 = config.logger ?? defaultLogger;
1411
+ const priority = config.priority ?? "cli-first";
1412
+ const cache = resolveCache(config, logger6);
1413
+ logger6.info("Auto-selecting adapter", { priority, cacheEnabled: cache !== void 0 });
1414
+ switch (priority) {
1415
+ case "cli-first":
1416
+ return selectCliFirst(config, logger6, cache);
1417
+ case "api-first":
1418
+ return selectApiFirst(config, logger6, cache);
1419
+ case "cli-only":
1420
+ return selectCliOnly(config, logger6, cache);
1421
+ case "api-only":
1422
+ return selectApiOnly(config, logger6);
1423
+ default: {
1424
+ const exhaustive = priority;
1425
+ throw new Error(`Unknown priority: ${String(exhaustive)}`);
1426
+ }
1427
+ }
1428
+ }
36
1429
 
37
1430
  // src/cli-adapters/subprocess-adapter.ts
38
1431
  import { spawn } from "child_process";
@@ -315,7 +1708,7 @@ function calculateBackoffDelay(attempt, baseDelayMs, maxDelayMs) {
315
1708
  function isRetryableError(code) {
316
1709
  return RETRYABLE_ERROR_CODES.has(code);
317
1710
  }
318
- function categorizeError(error) {
1711
+ function categorizeError2(error) {
319
1712
  switch (error.code) {
320
1713
  case "TIMEOUT":
321
1714
  return "timeout";
@@ -339,7 +1732,7 @@ async function executeCliRetryLoop(executeFn, config) {
339
1732
  }
340
1733
  lastError = result.error;
341
1734
  if (config.circuitBreaker !== void 0 && config.circuitBreaker !== null) {
342
- config.circuitBreaker.recordFailure(categorizeError(lastError));
1735
+ config.circuitBreaker.recordFailure(categorizeError2(lastError));
343
1736
  }
344
1737
  if (!shouldRetry(lastError, attempt, maxAttempts, config.circuitBreaker)) {
345
1738
  return err(lastError);
@@ -691,38 +2084,6 @@ function buildChildEnv(cliName) {
691
2084
  return childEnv;
692
2085
  }
693
2086
 
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
2087
  // src/cli-adapters/cli-error-envelope.ts
727
2088
  function isClaudeErrorEnvelope(v) {
728
2089
  if (typeof v !== "object" || v === null) return false;
@@ -3000,139 +4361,6 @@ ${task.content}` : task.content;
3000
4361
  }
3001
4362
  };
3002
4363
 
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
4364
  // src/cli/cli-auth-probe.ts
3137
4365
  import { execFile as execFile2 } from "child_process";
3138
4366
  import { promisify as promisify2 } from "util";
@@ -3354,6 +4582,11 @@ function createAllAdapters(logger6, codexTransport = "mcp") {
3354
4582
  adapters.set("gemini", new GeminiCliAdapter(options));
3355
4583
  adapters.set("codex", createCodexAdapter(codexTransport, options ?? {}));
3356
4584
  adapters.set("opencode", new OpenCodeCliAdapter(options));
4585
+ if (process.env["NEXUS_BILLING_MODE"] === "api") {
4586
+ for (const { armId, adapter } of collectApiRoutingArms(logger6)) {
4587
+ adapters.set(armId, adapter);
4588
+ }
4589
+ }
3357
4590
  return adapters;
3358
4591
  }
3359
4592
  async function isCliAvailable(cli, cache) {
@@ -3408,9 +4641,17 @@ async function getAvailableClis(cache) {
3408
4641
  }
3409
4642
 
3410
4643
  export {
4644
+ generateCacheKey,
4645
+ messagesToAnthropicFormat,
4646
+ extractSystemPrompt,
4647
+ extractRequestSystemPrompt,
3411
4648
  sleep,
3412
4649
  delay,
3413
4650
  withTimeout,
4651
+ CLAUDE_MODELS,
4652
+ CLAUDE_MODEL_ALIASES,
4653
+ ClaudeAdapter,
4654
+ createClaudeAdapter,
3414
4655
  CLI_TIMEOUT_PROFILES,
3415
4656
  DEFAULT_TIMEOUT_PROFILE,
3416
4657
  getTimeoutForTask,
@@ -3418,10 +4659,9 @@ export {
3418
4659
  getTimeoutForTaskAuto,
3419
4660
  calculateBackoffDelay,
3420
4661
  isRetryableError,
3421
- categorizeError,
4662
+ categorizeError2 as categorizeError,
3422
4663
  executeCliRetryLoop,
3423
4664
  BaseCliAdapter,
3424
- sanitizeOutput,
3425
4665
  authRemediation,
3426
4666
  SubprocessCliAdapter,
3427
4667
  asRecord,
@@ -3443,6 +4683,8 @@ export {
3443
4683
  createCliAdapter,
3444
4684
  createAllAdapters,
3445
4685
  isCliAvailable,
3446
- getAvailableClis
4686
+ getAvailableClis,
4687
+ SdkAdapter,
4688
+ createAutoAdapter
3447
4689
  };
3448
- //# sourceMappingURL=chunk-VIHUOFIT.js.map
4690
+ //# sourceMappingURL=chunk-GMHCPEHS.js.map