acdev 1.0.6 → 1.0.7
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/package.json +1 -1
- package/public/app.js +4 -5
- package/src/agent.js +5 -0
- package/src/models.js +74 -8
- package/src/server.js +19 -3
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -3639,8 +3639,7 @@ function defaultModelsForProvider(provider = 'claude') {
|
|
|
3639
3639
|
return [
|
|
3640
3640
|
{ id: 'anthropic/claude-sonnet-4', label: 'Claude Sonnet 4 (Anthropic)' },
|
|
3641
3641
|
{ id: 'anthropic/claude-opus-4', label: 'Claude Opus 4 (Anthropic)' },
|
|
3642
|
-
{ id: '
|
|
3643
|
-
{ id: 'x-ai/grok-2', label: 'Grok 2 (xAI)' },
|
|
3642
|
+
{ id: 'anthropic/claude-3.5-sonnet', label: 'Claude 3.5 Sonnet (Anthropic)' },
|
|
3644
3643
|
];
|
|
3645
3644
|
}
|
|
3646
3645
|
return [
|
|
@@ -3665,11 +3664,11 @@ function updateModelSourceHint(source, provider = 'claude') {
|
|
|
3665
3664
|
`${name} model for agent runs. List loaded from Anthropic Models API.`;
|
|
3666
3665
|
} else if (source === 'openrouter') {
|
|
3667
3666
|
els.settingsModelHint.textContent =
|
|
3668
|
-
'OpenRouter model for agent runs. List loaded from OpenRouter Models API.';
|
|
3667
|
+
'OpenRouter model for agent runs (anthropic/* only — Claude Agent SDK uses Anthropic-compatible API). List loaded from OpenRouter Models API.';
|
|
3669
3668
|
} else if (source === 'fallback') {
|
|
3670
3669
|
if (provider === 'openrouter') {
|
|
3671
3670
|
els.settingsModelHint.textContent =
|
|
3672
|
-
'OpenRouter model for agent runs. Showing curated models (live list unavailable — set OPENROUTER_API_KEY in Authentication).';
|
|
3671
|
+
'OpenRouter model for agent runs (anthropic/* only). Showing curated Anthropic models (live list unavailable — set OPENROUTER_API_KEY in Authentication).';
|
|
3673
3672
|
} else {
|
|
3674
3673
|
els.settingsModelHint.textContent =
|
|
3675
3674
|
'Claude model for agent runs. Showing curated Claude Code models (live list unavailable — set an API key or use claude auth login).';
|
|
@@ -3722,7 +3721,7 @@ function syncLlmProviderSelects(provider) {
|
|
|
3722
3721
|
if (els.settingsLlmProviderHint) {
|
|
3723
3722
|
els.settingsLlmProviderHint.textContent =
|
|
3724
3723
|
value === 'openrouter'
|
|
3725
|
-
? 'Agent runs route through OpenRouter (requires OPENROUTER_API_KEY
|
|
3724
|
+
? 'Agent runs route through OpenRouter (requires OPENROUTER_API_KEY). Use anthropic/* models only.'
|
|
3726
3725
|
: 'Agent runs use Claude via Anthropic API, OAuth token, or claude auth login.';
|
|
3727
3726
|
}
|
|
3728
3727
|
}
|
package/src/agent.js
CHANGED
|
@@ -427,11 +427,16 @@ export async function withLlmProviderEnv(config, fn) {
|
|
|
427
427
|
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
|
|
428
428
|
HTTP_REFERER: process.env.HTTP_REFERER,
|
|
429
429
|
X_TITLE: process.env.X_TITLE,
|
|
430
|
+
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: process.env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS,
|
|
430
431
|
};
|
|
431
432
|
|
|
432
433
|
process.env.ANTHROPIC_BASE_URL = OPENROUTER_ANTHROPIC_BASE_URL;
|
|
433
434
|
process.env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
434
435
|
process.env.ANTHROPIC_API_KEY = '';
|
|
436
|
+
// OpenRouter rejects Anthropic-only beta headers on some models.
|
|
437
|
+
if (!process.env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS) {
|
|
438
|
+
process.env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = '1';
|
|
439
|
+
}
|
|
435
440
|
if (!process.env.HTTP_REFERER) {
|
|
436
441
|
process.env.HTTP_REFERER = 'https://github.com/acdev';
|
|
437
442
|
}
|
package/src/models.js
CHANGED
|
@@ -44,17 +44,19 @@ export const MODEL_OPTIONS = CLAUDE_MODEL_OPTIONS;
|
|
|
44
44
|
|
|
45
45
|
export const DEFAULT_MODEL = 'claude-sonnet-5';
|
|
46
46
|
|
|
47
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* OpenRouter slugs that work with acdev agent runs.
|
|
49
|
+
* Agent jobs use Claude Agent SDK via OpenRouter's Anthropic-compatible API;
|
|
50
|
+
* only anthropic/* models are supported on that path.
|
|
51
|
+
*/
|
|
52
|
+
export const OPENROUTER_AGENT_MODEL_PREFIX = 'anthropic/';
|
|
53
|
+
|
|
54
|
+
/** Curated OpenRouter model ids used as defaults + fallback (agent-compatible only). */
|
|
48
55
|
export const OPENROUTER_MODEL_OPTIONS = [
|
|
49
56
|
{ id: 'anthropic/claude-sonnet-4', label: 'Claude Sonnet 4 (Anthropic)' },
|
|
50
57
|
{ id: 'anthropic/claude-opus-4', label: 'Claude Opus 4 (Anthropic)' },
|
|
51
58
|
{ id: 'anthropic/claude-3.5-sonnet', label: 'Claude 3.5 Sonnet (Anthropic)' },
|
|
52
59
|
{ id: 'anthropic/claude-3.7-sonnet', label: 'Claude 3.7 Sonnet (Anthropic)' },
|
|
53
|
-
{ id: 'openai/gpt-4.1', label: 'GPT-4.1 (OpenAI)' },
|
|
54
|
-
{ id: 'openai/gpt-4o', label: 'GPT-4o (OpenAI)' },
|
|
55
|
-
{ id: 'google/gemini-2.5-pro-preview', label: 'Gemini 2.5 Pro (Google)' },
|
|
56
|
-
{ id: 'x-ai/grok-2', label: 'Grok 2 (xAI)' },
|
|
57
|
-
{ id: 'x-ai/grok-2-1212', label: 'Grok 2 1212 (xAI)' },
|
|
58
60
|
];
|
|
59
61
|
|
|
60
62
|
export const DEFAULT_OPENROUTER_MODEL = 'anthropic/claude-sonnet-4';
|
|
@@ -191,6 +193,64 @@ export function isOpenRouterCatalogId(id) {
|
|
|
191
193
|
return isValidModelId(id) && String(id).includes('/');
|
|
192
194
|
}
|
|
193
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Whether an OpenRouter slug can be used for acdev agent runs.
|
|
198
|
+
* Non-anthropic slugs may appear in OpenRouter's catalog but fail at runtime
|
|
199
|
+
* because the Claude Agent SDK speaks Anthropic Messages API semantics.
|
|
200
|
+
* @param {string} modelId
|
|
201
|
+
* @returns {boolean}
|
|
202
|
+
*/
|
|
203
|
+
export function isOpenRouterAgentCompatibleModel(modelId) {
|
|
204
|
+
if (!isOpenRouterCatalogId(modelId)) return false;
|
|
205
|
+
return String(modelId).trim().toLowerCase().startsWith(OPENROUTER_AGENT_MODEL_PREFIX);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @param {ModelOption[]} models
|
|
210
|
+
* @returns {ModelOption[]}
|
|
211
|
+
*/
|
|
212
|
+
function filterOpenRouterAgentModels(models) {
|
|
213
|
+
return models.filter((m) => m?.id && isOpenRouterAgentCompatibleModel(m.id));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Improve OpenRouter agent failure messages for the job UI.
|
|
218
|
+
* @param {string} message
|
|
219
|
+
* @param {string} [model]
|
|
220
|
+
* @returns {string}
|
|
221
|
+
*/
|
|
222
|
+
export function enhanceOpenRouterAgentError(message, model) {
|
|
223
|
+
const raw = String(message || '').trim();
|
|
224
|
+
if (!raw) return raw;
|
|
225
|
+
|
|
226
|
+
const id = String(model || '').trim();
|
|
227
|
+
const routingFailure =
|
|
228
|
+
/no allowed providers are available/i.test(raw) ||
|
|
229
|
+
/not allowed by provider/i.test(raw) ||
|
|
230
|
+
/model.*not allowed/i.test(raw);
|
|
231
|
+
|
|
232
|
+
if (id && !isOpenRouterAgentCompatibleModel(id)) {
|
|
233
|
+
return [
|
|
234
|
+
`OpenRouter model "${id}" is not supported for acdev agent runs.`,
|
|
235
|
+
'Agent jobs use the Claude Agent SDK via OpenRouter\'s Anthropic-compatible API.',
|
|
236
|
+
'Choose an anthropic/* model (e.g. anthropic/claude-sonnet-4).',
|
|
237
|
+
raw !== id ? `Provider error: ${raw}` : '',
|
|
238
|
+
]
|
|
239
|
+
.filter(Boolean)
|
|
240
|
+
.join(' ');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (routingFailure) {
|
|
244
|
+
return [
|
|
245
|
+
raw,
|
|
246
|
+
'If you use OpenRouter provider allowlists, clear Settings → Privacy → Providers or add the model\'s upstream provider.',
|
|
247
|
+
'For acdev, anthropic/* models on OpenRouter are the most reliable choice.',
|
|
248
|
+
].join(' ');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return raw;
|
|
252
|
+
}
|
|
253
|
+
|
|
194
254
|
/**
|
|
195
255
|
* Curated fallback list for a provider.
|
|
196
256
|
* @param {LlmProvider} provider
|
|
@@ -531,14 +591,20 @@ async function listModelsForProvider(provider, opts = {}) {
|
|
|
531
591
|
? await fetchOpenRouterModels()
|
|
532
592
|
: await fetchAnthropicModels();
|
|
533
593
|
const live = filterModelsForProvider(liveRaw, provider);
|
|
534
|
-
|
|
594
|
+
let models = filterModelsForProvider(mergeModelLists(curated, live), provider);
|
|
595
|
+
if (provider === 'openrouter') {
|
|
596
|
+
models = filterOpenRouterAgentModels(models);
|
|
597
|
+
}
|
|
535
598
|
const selected = reconcileModelForProvider(models, selectedRaw);
|
|
536
599
|
const source = /** @type {ModelsSource} */ (provider === 'openrouter' ? 'openrouter' : 'anthropic');
|
|
537
600
|
const result = { models, source };
|
|
538
601
|
cacheByProvider.set(provider, { expiresAt: now + CACHE_TTL_MS, result });
|
|
539
602
|
return { ...result, selected, provider };
|
|
540
603
|
} catch {
|
|
541
|
-
|
|
604
|
+
let models = curated;
|
|
605
|
+
if (provider === 'openrouter') {
|
|
606
|
+
models = filterOpenRouterAgentModels(models);
|
|
607
|
+
}
|
|
542
608
|
const selected = reconcileModelForProvider(models, selectedRaw);
|
|
543
609
|
const result = { models, source: /** @type {ModelsSource} */ ('fallback') };
|
|
544
610
|
cacheByProvider.set(provider, { expiresAt: now + 30_000, result });
|
package/src/server.js
CHANGED
|
@@ -39,7 +39,7 @@ import { usageFromLogs, withJobUsage } from './usage.js';
|
|
|
39
39
|
import { checkGhAuth } from './gh-auth.js';
|
|
40
40
|
import { checkClaudeAuth } from './claude-auth.js';
|
|
41
41
|
import { checkOpenRouterAuth } from './openrouter-auth.js';
|
|
42
|
-
import { isValidLlmProvider, isValidModelId, isNoModel, NO_MODEL } from './models.js';
|
|
42
|
+
import { isValidLlmProvider, isValidModelId, isNoModel, NO_MODEL, isOpenRouterAgentCompatibleModel, enhanceOpenRouterAgentError } from './models.js';
|
|
43
43
|
|
|
44
44
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
45
45
|
|
|
@@ -197,6 +197,14 @@ export function createServer({ repoRoot, config, store, useStubAgent = false, de
|
|
|
197
197
|
return isValidLlmProvider(config.llmProvider) ? config.llmProvider : 'claude';
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
function formatAgentJobError(err) {
|
|
201
|
+
let message = err instanceof Error ? err.message : String(err);
|
|
202
|
+
if (currentLlmProvider() === 'openrouter') {
|
|
203
|
+
message = enhanceOpenRouterAgentError(message, config.model);
|
|
204
|
+
}
|
|
205
|
+
return message;
|
|
206
|
+
}
|
|
207
|
+
|
|
200
208
|
/**
|
|
201
209
|
* Reject enqueue / agent / PR actions when required auth is missing.
|
|
202
210
|
* @param {{ needGh?: boolean, needLlm?: boolean }} [opts]
|
|
@@ -245,6 +253,14 @@ export function createServer({ repoRoot, config, store, useStubAgent = false, de
|
|
|
245
253
|
code: 'openrouter_auth_required',
|
|
246
254
|
};
|
|
247
255
|
}
|
|
256
|
+
if (!isOpenRouterAgentCompatibleModel(model)) {
|
|
257
|
+
return {
|
|
258
|
+
status: 400,
|
|
259
|
+
error:
|
|
260
|
+
`Model "${model}" is not supported for OpenRouter agent runs. acdev uses the Claude Agent SDK (Anthropic-compatible API). Choose an anthropic/* model such as anthropic/claude-sonnet-4.`,
|
|
261
|
+
code: 'openrouter_model_incompatible',
|
|
262
|
+
};
|
|
263
|
+
}
|
|
248
264
|
} else {
|
|
249
265
|
const claude = doCheckClaudeAuth();
|
|
250
266
|
if (!claude.ok) {
|
|
@@ -421,7 +437,7 @@ export function createServer({ repoRoot, config, store, useStubAgent = false, de
|
|
|
421
437
|
if (usage) patch.usage = usage;
|
|
422
438
|
setStatus(jobId, 'awaiting_review', patch);
|
|
423
439
|
} catch (err) {
|
|
424
|
-
const message =
|
|
440
|
+
const message = formatAgentJobError(err);
|
|
425
441
|
const current = store.getJob(jobId);
|
|
426
442
|
if (!current) return;
|
|
427
443
|
appendLog(current, 'error', message);
|
|
@@ -522,7 +538,7 @@ export function createServer({ repoRoot, config, store, useStubAgent = false, de
|
|
|
522
538
|
if (usage) patch.usage = usage;
|
|
523
539
|
setStatus(jobId, 'awaiting_review', patch);
|
|
524
540
|
} catch (err) {
|
|
525
|
-
const message =
|
|
541
|
+
const message = formatAgentJobError(err);
|
|
526
542
|
const current = store.getJob(jobId);
|
|
527
543
|
if (!current) return;
|
|
528
544
|
appendLog(current, 'error', message);
|