@amir83nasr/10router 0.1.14 → 0.1.15
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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +6 -6
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +3 -3
- package/.next/server/app/(dashboard)/dashboard/cli-tools/[toolId]/page.js +2 -2
- package/.next/server/app/(dashboard)/dashboard/cli-tools/[toolId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/cli-tools/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/combos/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/console-log/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/endpoint/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/media-providers/systemone/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/media-providers/systemone/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/profile/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/providers/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/providers/new/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/providers/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/proxy-pools/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/pxpipe/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/quota/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/token-saver/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/usage/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +5 -5
- package/.next/server/app/_not-found.segments/_full.segment.rsc +5 -5
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -5
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/cli-tools/all-statuses/route.js +1 -1
- package/.next/server/app/api/cli-tools/claude-settings/route.js +2 -2
- package/.next/server/app/api/cli-tools/codex-settings/route.js +1 -1
- package/.next/server/app/api/cli-tools/cowork-mcp-registry/route.js +1 -1
- package/.next/server/app/api/cli-tools/cowork-mcp-tools/route.js +1 -1
- package/.next/server/app/api/cli-tools/cowork-settings/route.js +1 -1
- package/.next/server/app/api/cli-tools/dsh-settings/route.js +1 -1
- package/.next/server/app/api/cli-tools/opencode-settings/route.js +1 -1
- package/.next/server/app/api/oauth/[provider]/[action]/route.js +1 -1
- package/.next/server/app/api/oauth/codex/bulk-import/route.js +1 -1
- package/.next/server/app/api/oauth/codex/import-token/route.js +1 -1
- package/.next/server/app/api/providers/client/route.js +1 -1
- package/.next/server/app/api/translator/send/route.js +1 -1
- package/.next/server/app/api/translator/translate/route.js +1 -1
- package/.next/server/app/api/tunnel/tailscale-install/route.js +2 -2
- package/.next/server/app/api/usage/[connectionId]/codex-reset-credits/route.js +1 -1
- package/.next/server/app/api/usage/[connectionId]/route.js +1 -1
- package/.next/server/app/api/version/route.js +1 -1
- package/.next/server/app/callback/page_client-reference-manifest.js +1 -1
- package/.next/server/app/callback.html +1 -1
- package/.next/server/app/callback.rsc +6 -6
- package/.next/server/app/callback.segments/_full.segment.rsc +6 -6
- package/.next/server/app/callback.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/callback.segments/callback/__PAGE__.segment.rsc +6 -6
- package/.next/server/app/dashboard/cli-tools.html +1 -1
- package/.next/server/app/dashboard/cli-tools.rsc +8 -8
- package/.next/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk/dashboard/cli-tools/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/cli-tools.segments/_full.segment.rsc +8 -8
- package/.next/server/app/dashboard/cli-tools.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/combos.html +1 -1
- package/.next/server/app/dashboard/combos.rsc +7 -7
- package/.next/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk/dashboard/combos/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/combos.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/combos.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/endpoint.html +1 -1
- package/.next/server/app/dashboard/endpoint.rsc +8 -8
- package/.next/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk/dashboard/endpoint/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/endpoint.segments/_full.segment.rsc +8 -8
- package/.next/server/app/dashboard/endpoint.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/media-providers/systemone.html +1 -1
- package/.next/server/app/dashboard/media-providers/systemone.rsc +7 -7
- package/.next/server/app/dashboard/media-providers/systemone.segments/!KGRhc2hib2FyZCk/dashboard/media-providers/systemone/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/media-providers/systemone.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/media-providers/systemone.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/profile.html +1 -1
- package/.next/server/app/dashboard/profile.rsc +7 -7
- package/.next/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk/dashboard/profile/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/profile.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/profile.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/providers/new.html +1 -1
- package/.next/server/app/dashboard/providers/new.rsc +7 -7
- package/.next/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard/providers/new/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/providers/new.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/providers/new.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/providers.html +1 -1
- package/.next/server/app/dashboard/providers.rsc +7 -7
- package/.next/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk/dashboard/providers/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/providers.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/providers.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/proxy-pools.html +1 -1
- package/.next/server/app/dashboard/proxy-pools.rsc +7 -7
- package/.next/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk/dashboard/proxy-pools/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/proxy-pools.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/proxy-pools.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/pxpipe.html +1 -1
- package/.next/server/app/dashboard/pxpipe.rsc +6 -6
- package/.next/server/app/dashboard/pxpipe.segments/!KGRhc2hib2FyZCk/dashboard/pxpipe/__PAGE__.segment.rsc +6 -6
- package/.next/server/app/dashboard/pxpipe.segments/_full.segment.rsc +6 -6
- package/.next/server/app/dashboard/pxpipe.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/quota.html +2 -2
- package/.next/server/app/dashboard/quota.rsc +8 -8
- package/.next/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk/dashboard/quota/__PAGE__.segment.rsc +8 -8
- package/.next/server/app/dashboard/quota.segments/_full.segment.rsc +8 -8
- package/.next/server/app/dashboard/quota.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/settings/pricing/page_client-reference-manifest.js +1 -1
- package/.next/server/app/dashboard/settings/pricing.html +1 -1
- package/.next/server/app/dashboard/settings/pricing.rsc +5 -5
- package/.next/server/app/dashboard/settings/pricing.segments/_full.segment.rsc +5 -5
- package/.next/server/app/dashboard/settings/pricing.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/settings/pricing.segments/dashboard/settings/pricing/__PAGE__.segment.rsc +5 -5
- package/.next/server/app/dashboard/token-saver.html +1 -1
- package/.next/server/app/dashboard/token-saver.rsc +6 -6
- package/.next/server/app/dashboard/token-saver.segments/!KGRhc2hib2FyZCk/dashboard/token-saver/__PAGE__.segment.rsc +6 -6
- package/.next/server/app/dashboard/token-saver.segments/_full.segment.rsc +6 -6
- package/.next/server/app/dashboard/token-saver.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/usage.html +2 -2
- package/.next/server/app/dashboard/usage.rsc +7 -7
- package/.next/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk/dashboard/usage/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/usage.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/usage.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard.html +1 -1
- package/.next/server/app/dashboard.rsc +8 -8
- package/.next/server/app/dashboard.segments/!KGRhc2hib2FyZCk/dashboard/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard.segments/_full.segment.rsc +8 -8
- package/.next/server/app/dashboard.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +5 -5
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +5 -5
- package/.next/server/app/index.segments/_full.segment.rsc +5 -5
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/landing/page_client-reference-manifest.js +1 -1
- package/.next/server/app/landing.html +1 -1
- package/.next/server/app/landing.rsc +6 -6
- package/.next/server/app/landing.segments/_full.segment.rsc +6 -6
- package/.next/server/app/landing.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/landing.segments/landing/__PAGE__.segment.rsc +6 -6
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +5 -5
- package/.next/server/app/login.segments/_full.segment.rsc +5 -5
- package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +5 -5
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +6 -6
- package/.next/server/chunks/1011.js +1 -1
- package/.next/server/chunks/1194.js +1 -1
- package/.next/server/chunks/1289.js +1 -1
- package/.next/server/chunks/1633.js +1 -1
- package/.next/server/chunks/2282.js +7 -7
- package/.next/server/chunks/2339.js +1 -1
- package/.next/server/chunks/2801.js +1 -1
- package/.next/server/chunks/3425.js +1 -1
- package/.next/server/chunks/6056.js +1 -1
- package/.next/server/chunks/6343.js +6 -6
- package/.next/server/chunks/9429.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/{1278-1bef1ffb32ea7f41.js → 1278-5800a984ad8ad005.js} +1 -1
- package/.next/static/chunks/{1660-25564899e7912146.js → 1660-7f2e79bde5eff607.js} +1 -1
- package/.next/static/chunks/{1699-49be310981e2759e.js → 1699-13ee6864a8a31a96.js} +2 -2
- package/.next/static/chunks/{3577-1f02993330d7fba7.js → 3577-cd98d40e3099e1b0.js} +1 -1
- package/.next/static/chunks/{5862-6f3a3a6d4c00100d.js → 5862-f98c2f3d480b80d2.js} +1 -1
- package/.next/static/chunks/{9150-32b4247b1af5e206.js → 9150-8316559e1687b4a8.js} +1 -1
- package/.next/static/chunks/{9ce8c186-6cd204e9c27ecee1.js → 9ce8c186-3c7d7dbc5d428052.js} +1 -1
- package/.next/static/chunks/app/(dashboard)/dashboard/cli-tools/[toolId]/{page-c913b130003c1672.js → page-dd70f2cee9c910a9.js} +3 -3
- package/CHANGELOG.md +11 -0
- package/open-sse/handlers/chatCore/claudeMessageBody.js +357 -0
- package/open-sse/handlers/chatCore/nonStreamingHandler.js +36 -110
- package/open-sse/handlers/chatCore/sseToJsonHandler.js +147 -4
- package/open-sse/handlers/chatCore.js +48 -12
- package/open-sse/translator/concerns/classifier.js +70 -0
- package/open-sse/translator/concerns/thinkingUnified.js +18 -6
- package/open-sse/translator/request/claude-to-openai.js +20 -0
- package/open-sse/translator/request/openai-to-claude.js +11 -0
- package/package.json +1 -1
- package/src/app/(dashboard)/dashboard/cli-tools/components/cards/ClaudeToolCard.tsx +12 -0
- package/src/app/api/cli-tools/claude-settings/route.js +10 -0
- package/src/shared/constants/cliTools.js +8 -0
- /package/.next/static/{wBARQPScPhD6SLn7WQvI9 → 8QVpvFFKxoB53L449Kgan}/_buildManifest.js +0 -0
- /package/.next/static/{wBARQPScPhD6SLn7WQvI9 → 8QVpvFFKxoB53L449Kgan}/_ssgManifest.js +0 -0
|
@@ -10,7 +10,12 @@ import {
|
|
|
10
10
|
saveUsageStats,
|
|
11
11
|
formatDoneLine,
|
|
12
12
|
} from "./requestDetail.js";
|
|
13
|
-
import { ROLE, RESPONSES_ITEM } from "../../translator/schema/index.js";
|
|
13
|
+
import { ROLE, RESPONSES_ITEM, CLAUDE_BLOCK } from "../../translator/schema/index.js";
|
|
14
|
+
import {
|
|
15
|
+
chatCompletionToClaudeMessage,
|
|
16
|
+
claudeMessageToChatCompletion,
|
|
17
|
+
parseClaudeSSEToMessage,
|
|
18
|
+
} from "./claudeMessageBody.js";
|
|
14
19
|
|
|
15
20
|
// Responses-API providers (e.g. codex) may emit SSE without content-type + use Responses output shape
|
|
16
21
|
const isResponsesProvider = (p) => PROVIDERS[p]?.format === FORMATS.OPENAI_RESPONSES;
|
|
@@ -227,10 +232,15 @@ export async function handleForcedSSEToJson({
|
|
|
227
232
|
log,
|
|
228
233
|
}) {
|
|
229
234
|
const contentType = providerResponse.headers.get("content-type") || "";
|
|
235
|
+
// Responses-API providers (codex) are known to stream without a content type;
|
|
236
|
+
// for every other provider an absent header means the body is not a stream we
|
|
237
|
+
// may consume here — returning null leaves it intact for the JSON handler.
|
|
230
238
|
const isSSE =
|
|
231
239
|
contentType.includes("text/event-stream") ||
|
|
232
240
|
(contentType === "" && isResponsesProvider(provider));
|
|
233
|
-
|
|
241
|
+
// Not an aggregated stream — the caller falls back to the JSON handler. The
|
|
242
|
+
// pending counter must stay untouched here: this handler did not serve it.
|
|
243
|
+
if (!isSSE) return null;
|
|
234
244
|
|
|
235
245
|
trackDone();
|
|
236
246
|
|
|
@@ -251,6 +261,15 @@ export async function handleForcedSSEToJson({
|
|
|
251
261
|
if (isCodexResponsesApi) {
|
|
252
262
|
try {
|
|
253
263
|
const jsonResponse = await convertResponsesStreamToJson(providerResponse.body);
|
|
264
|
+
// A `response.failed` event is the upstream's own failure signal; an empty
|
|
265
|
+
// "successful" body would hide it from the client.
|
|
266
|
+
if (jsonResponse.status === "failed") {
|
|
267
|
+
appendLog({ status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}` });
|
|
268
|
+
return createErrorResult(
|
|
269
|
+
HTTP_STATUS.BAD_GATEWAY,
|
|
270
|
+
"Upstream Responses stream reported failure",
|
|
271
|
+
);
|
|
272
|
+
}
|
|
254
273
|
if (onRequestSuccess) await onRequestSuccess();
|
|
255
274
|
|
|
256
275
|
const usage = jsonResponse.usage || {};
|
|
@@ -345,7 +364,36 @@ export async function handleForcedSSEToJson({
|
|
|
345
364
|
}));
|
|
346
365
|
const hasToolCalls = toolCalls.length > 0;
|
|
347
366
|
|
|
348
|
-
if (sourceFormat === FORMATS.
|
|
367
|
+
if (sourceFormat === FORMATS.CLAUDE) {
|
|
368
|
+
// The client speaks Anthropic Messages: reuse the same conversion a
|
|
369
|
+
// chat-native upstream gets, so both forced-stream upstream dialects
|
|
370
|
+
// produce one identical Claude body.
|
|
371
|
+
finalResp = chatCompletionToClaudeMessage({
|
|
372
|
+
id: jsonResponse.id,
|
|
373
|
+
created: jsonResponse.created_at,
|
|
374
|
+
model: jsonResponse.model || model,
|
|
375
|
+
choices: [
|
|
376
|
+
{
|
|
377
|
+
index: 0,
|
|
378
|
+
message: {
|
|
379
|
+
role: ROLE.ASSISTANT,
|
|
380
|
+
content: textContent,
|
|
381
|
+
...(hasToolCalls ? { tool_calls: toolCalls } : {}),
|
|
382
|
+
},
|
|
383
|
+
finish_reason: hasToolCalls
|
|
384
|
+
? "tool_calls"
|
|
385
|
+
: jsonResponse.status === "completed" || jsonResponse.status === "done"
|
|
386
|
+
? "stop"
|
|
387
|
+
: jsonResponse.status || "stop",
|
|
388
|
+
},
|
|
389
|
+
],
|
|
390
|
+
usage: {
|
|
391
|
+
prompt_tokens: inTokens,
|
|
392
|
+
completion_tokens: outTokens,
|
|
393
|
+
...cacheDetails,
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
} else if (sourceFormat === FORMATS.ANTIGRAVITY || sourceFormat === FORMATS.GEMINI) {
|
|
349
397
|
finalResp = {
|
|
350
398
|
response: {
|
|
351
399
|
candidates: [
|
|
@@ -406,6 +454,94 @@ export async function handleForcedSSEToJson({
|
|
|
406
454
|
// Standard Chat Completions SSE path
|
|
407
455
|
try {
|
|
408
456
|
const sseText = await providerResponse.text();
|
|
457
|
+
|
|
458
|
+
// A provider that speaks Claude on the wire (opencode's `union-alpha`, any
|
|
459
|
+
// anthropic-compatible endpoint) streams Anthropic events, which the chat
|
|
460
|
+
// aggregator cannot fold — it would produce an empty message. Aggregate in
|
|
461
|
+
// the format the upstream actually used.
|
|
462
|
+
//
|
|
463
|
+
// `targetFormat` is what we asked for, not proof of what came back: some
|
|
464
|
+
// providers answer their Claude transport in OpenAI dialect. The aggregator
|
|
465
|
+
// returns null when no Anthropic event was seen, and the standard chat path
|
|
466
|
+
// below then handles the body it really is.
|
|
467
|
+
const isClaudeUpstream =
|
|
468
|
+
targetFormat === FORMATS.CLAUDE || /"type"\s*:\s*"message_start"/.test(sseText);
|
|
469
|
+
const claudeMessage = isClaudeUpstream ? parseClaudeSSEToMessage(sseText, model) : null;
|
|
470
|
+
if (claudeMessage) {
|
|
471
|
+
const message = claudeMessage;
|
|
472
|
+
if (message.error) {
|
|
473
|
+
const upstreamCode = String(message.error.code || "");
|
|
474
|
+
const status =
|
|
475
|
+
message.error.type === "invalid_request_error" ||
|
|
476
|
+
upstreamCode === "context_length_exceeded" ||
|
|
477
|
+
upstreamCode === "bad_request"
|
|
478
|
+
? HTTP_STATUS.BAD_REQUEST
|
|
479
|
+
: HTTP_STATUS.BAD_GATEWAY;
|
|
480
|
+
return createErrorResult(status, message.error.message || "Upstream SSE stream failed");
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (onRequestSuccess) await onRequestSuccess();
|
|
484
|
+
|
|
485
|
+
const usage = message.usage || {};
|
|
486
|
+
appendLog({ tokens: usage, status: "200 OK" });
|
|
487
|
+
saveUsageStats({
|
|
488
|
+
provider,
|
|
489
|
+
model,
|
|
490
|
+
tokens: usage,
|
|
491
|
+
connectionId,
|
|
492
|
+
apiKey,
|
|
493
|
+
endpoint: clientRawRequest?.endpoint,
|
|
494
|
+
silent: true,
|
|
495
|
+
});
|
|
496
|
+
if (log?.line)
|
|
497
|
+
log.line(
|
|
498
|
+
reqTag,
|
|
499
|
+
"DONE",
|
|
500
|
+
formatDoneLine({ usage, latency: { total: Date.now() - requestStartTime } }),
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
const totalLatency = Date.now() - requestStartTime;
|
|
504
|
+
const textBlocks = (message.content || []).filter((b) => b.type === CLAUDE_BLOCK.TEXT);
|
|
505
|
+
const thinkingBlocks = (message.content || []).filter(
|
|
506
|
+
(b) => b.type === CLAUDE_BLOCK.THINKING,
|
|
507
|
+
);
|
|
508
|
+
saveRequestDetail(
|
|
509
|
+
buildRequestDetail(
|
|
510
|
+
{
|
|
511
|
+
...ctx,
|
|
512
|
+
latency: { ttft: totalLatency, total: totalLatency },
|
|
513
|
+
tokens: {
|
|
514
|
+
prompt_tokens: usage.input_tokens || 0,
|
|
515
|
+
completion_tokens: usage.output_tokens || 0,
|
|
516
|
+
},
|
|
517
|
+
response: {
|
|
518
|
+
content: textBlocks.map((b) => b.text || "").join("") || null,
|
|
519
|
+
thinking: thinkingBlocks.map((b) => b.thinking || "").join("") || null,
|
|
520
|
+
finish_reason: message.stop_reason || "unknown",
|
|
521
|
+
},
|
|
522
|
+
status: "success",
|
|
523
|
+
},
|
|
524
|
+
{ endpoint: clientRawRequest?.endpoint || null },
|
|
525
|
+
),
|
|
526
|
+
).catch(() => {});
|
|
527
|
+
|
|
528
|
+
// Claude → chat conversion only when the client asked for chat; a Claude
|
|
529
|
+
// client (or any other) receives the aggregated message unchanged.
|
|
530
|
+
const finalBody =
|
|
531
|
+
sourceFormat === FORMATS.CLAUDE
|
|
532
|
+
? message
|
|
533
|
+
: sourceFormat === FORMATS.OPENAI
|
|
534
|
+
? claudeMessageToChatCompletion(message)
|
|
535
|
+
: message;
|
|
536
|
+
|
|
537
|
+
return {
|
|
538
|
+
success: true,
|
|
539
|
+
response: new Response(JSON.stringify(restoreToolNames(finalBody, toolNameMap)), {
|
|
540
|
+
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
|
|
541
|
+
}),
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
409
545
|
const parsed = parseSSEToOpenAIResponse(sseText, model);
|
|
410
546
|
if (!parsed)
|
|
411
547
|
return createErrorResult(
|
|
@@ -491,10 +627,17 @@ export async function handleForcedSSEToJson({
|
|
|
491
627
|
// lost on the non-streaming return path. Inlined (not imported from
|
|
492
628
|
// nonStreamingHandler.js) to avoid a circular import: nonStreamingHandler
|
|
493
629
|
// already imports parseSSEToOpenAIResponse from this module.
|
|
630
|
+
// A Claude-format client gets an Anthropic `message`, never a raw
|
|
631
|
+
// `chat.completion`. Every non-streaming /v1/messages request lands here
|
|
632
|
+
// when the provider forces streaming (opencode, codex, zed, openai), and
|
|
633
|
+
// Claude Code reads `message.content[]` — an OpenAI body throws a TypeError
|
|
634
|
+
// inside the client, which auto mode reports as "classifier unavailable".
|
|
494
635
|
const finalBody =
|
|
495
636
|
sourceFormat === FORMATS.OPENAI_RESPONSES
|
|
496
637
|
? chatCompletionToResponses(parsed, customToolNames)
|
|
497
|
-
:
|
|
638
|
+
: sourceFormat === FORMATS.CLAUDE
|
|
639
|
+
? chatCompletionToClaudeMessage(parsed)
|
|
640
|
+
: parsed;
|
|
498
641
|
|
|
499
642
|
return {
|
|
500
643
|
success: true,
|
|
@@ -177,10 +177,24 @@ export async function handleChatCore({
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
// Client preference (what the response must look like) is independent of the
|
|
181
|
+
// transport a provider needs. `stream` below is the upstream flag; the
|
|
182
|
+
// client-facing decision is `clientWantsJson` further down.
|
|
183
|
+
const acceptHeader = clientRawRequest?.headers?.accept || "";
|
|
184
|
+
const clientPrefersJson = acceptHeader.includes("application/json");
|
|
185
|
+
const clientPrefersSSE = acceptHeader.includes("text/event-stream");
|
|
180
186
|
const clientRequestedStreaming =
|
|
181
187
|
body.stream === true || sourceFormat === FORMATS.ANTIGRAVITY || sourceFormat === FORMATS.GEMINI;
|
|
182
188
|
const providerRequiresStreaming = PROVIDERS[provider]?.forceStream === true;
|
|
183
|
-
|
|
189
|
+
// Anthropic Messages defaults to a JSON body when `stream` is omitted; OpenAI
|
|
190
|
+
// chat callers have historically been served SSE in that case here. An
|
|
191
|
+
// explicit Accept: text/event-stream still opts a Claude client into SSE.
|
|
192
|
+
const sourceDefaultsToJson = sourceFormat === FORMATS.CLAUDE && !clientPrefersSSE;
|
|
193
|
+
let stream = providerRequiresStreaming
|
|
194
|
+
? true
|
|
195
|
+
: body.stream === undefined
|
|
196
|
+
? !sourceDefaultsToJson
|
|
197
|
+
: body.stream !== false;
|
|
184
198
|
|
|
185
199
|
// Image generation models require non-streaming (Google v1internal:generateContent)
|
|
186
200
|
const modelType = getModelType(alias, model);
|
|
@@ -197,9 +211,8 @@ export async function handleChatCore({
|
|
|
197
211
|
|
|
198
212
|
// Check client Accept header preference for non-streaming requests
|
|
199
213
|
// This fixes AI SDK compatibility where clients send Accept: application/json
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const clientPrefersSSE = acceptHeader.includes("text/event-stream");
|
|
214
|
+
// (`acceptHeader`, `clientPrefersJson` and `clientPrefersSSE` are resolved
|
|
215
|
+
// with the stream negotiation above so both decisions share one reading.)
|
|
203
216
|
if (
|
|
204
217
|
clientPrefersJson &&
|
|
205
218
|
!clientPrefersSSE &&
|
|
@@ -293,6 +306,21 @@ export async function handleChatCore({
|
|
|
293
306
|
stripContinuityFields(translatedBody);
|
|
294
307
|
}
|
|
295
308
|
|
|
309
|
+
// Reasoning depth is a model capability, so a model that cannot reason silently
|
|
310
|
+
// loses the client's effort selection inside applyThinking. Report that drop
|
|
311
|
+
// once per request: without it an ignored effort looks like a provider quirk.
|
|
312
|
+
const requestedThinking = extractThinking(body);
|
|
313
|
+
if (requestedThinking && requestedThinking.mode !== "none") {
|
|
314
|
+
const thinkingCaps = getCapabilitiesForModel(provider, stripThinkingSuffix(upstreamModel));
|
|
315
|
+
if (!thinkingCaps.reasoning) {
|
|
316
|
+
const requested = log?.fmtThink?.(requestedThinking) || requestedThinking.mode;
|
|
317
|
+
log?.warn?.(
|
|
318
|
+
"THINKING",
|
|
319
|
+
`${provider}/${model} cannot reason — requested ${requested} omitted`,
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
296
324
|
// Dedupe duplicate built-in tools when equivalent MCP tools are present (Claude clients only).
|
|
297
325
|
if (clientTool === "claude" && Array.isArray(translatedBody.tools)) {
|
|
298
326
|
const { tools: deduped, stripped } = dedupeTools(translatedBody.tools);
|
|
@@ -687,9 +715,20 @@ export async function handleChatCore({
|
|
|
687
715
|
appendRequestLog({ model, provider, connectionId, ...extra }).catch(() => {});
|
|
688
716
|
const trackDone = () => trackPendingRequest(model, provider, connectionId, false);
|
|
689
717
|
|
|
690
|
-
//
|
|
691
|
-
|
|
692
|
-
|
|
718
|
+
// The client did not ask for SSE: either the provider only streams, or the
|
|
719
|
+
// request was negotiated as JSON. Aggregating the upstream stream is tried
|
|
720
|
+
// first; it declines a non-SSE body so a provider that ignored `stream` — or
|
|
721
|
+
// one that answered JSON on a forced-stream endpoint — still reaches the
|
|
722
|
+
// ordinary non-streaming handler instead of being written as an SSE response.
|
|
723
|
+
const clientWantsJson =
|
|
724
|
+
!stream ||
|
|
725
|
+
(providerRequiresStreaming && !clientRequestedStreaming && !clientPrefersSSE) ||
|
|
726
|
+
// Source-format contract: an Anthropic Messages client that did not ask to
|
|
727
|
+
// stream keeps a JSON body even if its provider forces streaming upstream.
|
|
728
|
+
(sourceFormat === FORMATS.CLAUDE && body.stream !== true && !clientPrefersSSE);
|
|
729
|
+
|
|
730
|
+
if (clientWantsJson) {
|
|
731
|
+
const forcedResult = await handleForcedSSEToJson({
|
|
693
732
|
...sharedCtx,
|
|
694
733
|
providerResponse,
|
|
695
734
|
sourceFormat,
|
|
@@ -699,14 +738,11 @@ export async function handleChatCore({
|
|
|
699
738
|
trackDone,
|
|
700
739
|
appendLog,
|
|
701
740
|
});
|
|
702
|
-
if (
|
|
741
|
+
if (forcedResult) {
|
|
703
742
|
streamController.handleComplete();
|
|
704
|
-
return
|
|
743
|
+
return forcedResult;
|
|
705
744
|
}
|
|
706
|
-
}
|
|
707
745
|
|
|
708
|
-
// True non-streaming response
|
|
709
|
-
if (!stream) {
|
|
710
746
|
const result = await handleNonStreamingResponse({
|
|
711
747
|
...sharedCtx,
|
|
712
748
|
providerResponse,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Claude Code auto-mode safety classifier detection.
|
|
2
|
+
//
|
|
3
|
+
// The classifier is an ordinary non-streaming /v1/messages call, but it sends
|
|
4
|
+
// `stop_sequences: ["</severity>"]` (current schema) or `["</block>"]` (legacy)
|
|
5
|
+
// to end its verdict early. Providers that implement `stop` by trimming the
|
|
6
|
+
// matched text from the completion return `<severity>0` instead of
|
|
7
|
+
// `<severity>0</severity>`, and the client cannot parse that verdict — auto
|
|
8
|
+
// mode then fails closed for every action.
|
|
9
|
+
//
|
|
10
|
+
// Detection is structural, never model-based: the classifier is the only
|
|
11
|
+
// utility call that carries a `<transcript>` block, asks for the verdict tag in
|
|
12
|
+
// its system prompt, and declares no tools. User content alone can never opt a
|
|
13
|
+
// request into this policy.
|
|
14
|
+
|
|
15
|
+
import { ROLE } from "../schema/index.js";
|
|
16
|
+
|
|
17
|
+
const TRANSCRIPT_MARKER = "<transcript>";
|
|
18
|
+
const SEVERITY_INSTRUCTION = "Output <severity>N</severity>";
|
|
19
|
+
const LEGACY_YES = "yes</block>";
|
|
20
|
+
const LEGACY_NO = "no</block>";
|
|
21
|
+
|
|
22
|
+
const SEVERITY_STOP = "</severity>";
|
|
23
|
+
const LEGACY_STOP = "</block>";
|
|
24
|
+
|
|
25
|
+
/** Concatenate the text of a Claude content value (string or block array). */
|
|
26
|
+
function textOf(content) {
|
|
27
|
+
if (typeof content === "string") return content;
|
|
28
|
+
if (!Array.isArray(content)) return "";
|
|
29
|
+
return content
|
|
30
|
+
.filter((block) => typeof block?.text === "string")
|
|
31
|
+
.map((block) => block.text)
|
|
32
|
+
.join("");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Return the stop sequence a known auto-mode classifier owns, or null.
|
|
37
|
+
*
|
|
38
|
+
* @param {object} body - Claude Messages request body.
|
|
39
|
+
* @returns {string|null} `"</severity>"`, `"</block>"`, or null.
|
|
40
|
+
*/
|
|
41
|
+
export function detectClassifierStopSequence(body) {
|
|
42
|
+
if (!body || typeof body !== "object") return null;
|
|
43
|
+
if (Array.isArray(body.tools) && body.tools.length > 0) return null;
|
|
44
|
+
|
|
45
|
+
const messages = Array.isArray(body.messages) ? body.messages : [];
|
|
46
|
+
const transcript = messages
|
|
47
|
+
.filter((message) => message?.role !== ROLE.SYSTEM)
|
|
48
|
+
.map((message) => textOf(message?.content))
|
|
49
|
+
.join("\n");
|
|
50
|
+
if (!transcript.includes(TRANSCRIPT_MARKER)) return null;
|
|
51
|
+
|
|
52
|
+
const systemText = [
|
|
53
|
+
textOf(body.system),
|
|
54
|
+
...messages.filter((message) => message?.role === ROLE.SYSTEM).map((m) => textOf(m.content)),
|
|
55
|
+
].join("\n");
|
|
56
|
+
|
|
57
|
+
// Current schema wins when both known instructions appear.
|
|
58
|
+
if (systemText.includes(SEVERITY_INSTRUCTION)) return SEVERITY_STOP;
|
|
59
|
+
if (systemText.includes(LEGACY_YES) || systemText.includes(LEGACY_NO)) return LEGACY_STOP;
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Drop the classifier-owned stop hint from a `stop_sequences` array.
|
|
65
|
+
* Every other sequence passes through untouched.
|
|
66
|
+
*/
|
|
67
|
+
export function withoutClassifierStop(stopSequences, classifierStop) {
|
|
68
|
+
if (!Array.isArray(stopSequences) || !classifierStop) return stopSequences;
|
|
69
|
+
return stopSequences.filter((sequence) => sequence !== classifierStop);
|
|
70
|
+
}
|
|
@@ -276,7 +276,12 @@ function applyFormat(fmt, body, cfg, caps, supportedLevels, display) {
|
|
|
276
276
|
break;
|
|
277
277
|
}
|
|
278
278
|
const level = toLevel(eff);
|
|
279
|
-
|
|
279
|
+
// "auto" (adaptive thinking, no explicit effort) is not a member of the
|
|
280
|
+
// OpenAI reasoning_effort enum. Omitting the field is the only way to say
|
|
281
|
+
// "provider default" — every declared enum value names a specific depth.
|
|
282
|
+
if (level && level !== "auto") {
|
|
283
|
+
body.reasoning_effort = normalizeOpenAILevel(level, supportedLevels);
|
|
284
|
+
}
|
|
280
285
|
break;
|
|
281
286
|
}
|
|
282
287
|
case "claude-adaptive": {
|
|
@@ -335,13 +340,17 @@ function applyFormat(fmt, body, cfg, caps, supportedLevels, display) {
|
|
|
335
340
|
const zaiLvl = toLevel(eff);
|
|
336
341
|
// GLM-5.3 only accepts exactly low|high|max (anything else errors); GLM-5.2 accepts
|
|
337
342
|
// a wider set but z.ai maps low/medium->high and xhigh->max server-side anyway, so
|
|
338
|
-
// this 3-value mapping matches both.
|
|
339
|
-
|
|
343
|
+
// this 3-value mapping matches both. No explicit effort ("auto") omits the field:
|
|
344
|
+
// the fallback below would otherwise turn "let the model decide" into max.
|
|
345
|
+
const mapped =
|
|
340
346
|
zaiLvl === "low" || zaiLvl === "minimal"
|
|
341
347
|
? "low"
|
|
342
348
|
: zaiLvl === "high" || zaiLvl === "medium"
|
|
343
349
|
? "high"
|
|
344
|
-
: "max"
|
|
350
|
+
: zaiLvl === "xhigh" || zaiLvl === "max"
|
|
351
|
+
? "max"
|
|
352
|
+
: null;
|
|
353
|
+
if (mapped) body.reasoning_effort = mapped;
|
|
345
354
|
}
|
|
346
355
|
break;
|
|
347
356
|
}
|
|
@@ -394,7 +403,9 @@ function applyFormat(fmt, body, cfg, caps, supportedLevels, display) {
|
|
|
394
403
|
case "step": {
|
|
395
404
|
if (none && canDisable) break;
|
|
396
405
|
const level = toLevel(eff);
|
|
397
|
-
|
|
406
|
+
// "auto" is not in Step's low|medium|high enum — omit and take the default.
|
|
407
|
+
if (level && level !== "auto")
|
|
408
|
+
body.reasoning_effort = level === "xhigh" || level === "max" ? "high" : level;
|
|
398
409
|
break;
|
|
399
410
|
}
|
|
400
411
|
case "tokenrouter": {
|
|
@@ -416,7 +427,8 @@ function applyFormat(fmt, body, cfg, caps, supportedLevels, display) {
|
|
|
416
427
|
break;
|
|
417
428
|
}
|
|
418
429
|
const level = toLevel(eff);
|
|
419
|
-
|
|
430
|
+
// Native CLI accepts named levels only; "auto" means "no preference" → omit.
|
|
431
|
+
if (level && level !== "auto") body.params.reasoning_effort = level;
|
|
420
432
|
break;
|
|
421
433
|
}
|
|
422
434
|
default:
|
|
@@ -4,6 +4,7 @@ import { adjustMaxTokens } from "../formats/maxTokens.js";
|
|
|
4
4
|
import { encodeDataUri } from "../concerns/image.js";
|
|
5
5
|
import { ROLE, OPENAI_BLOCK, CLAUDE_BLOCK, EMPTY_TOOL_RESULT } from "../schema/index.js";
|
|
6
6
|
import { collapseTextParts } from "../concerns/message.js";
|
|
7
|
+
import { detectClassifierStopSequence, withoutClassifierStop } from "../concerns/classifier.js";
|
|
7
8
|
|
|
8
9
|
function stripAnthropicBillingHeader(text) {
|
|
9
10
|
if (typeof text !== "string") return "";
|
|
@@ -28,6 +29,25 @@ export function claudeToOpenAIRequest(model, body, stream) {
|
|
|
28
29
|
result.temperature = body.temperature;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
// Sampling. Only top_p: `top_k` has no OpenAI equivalent, and strict
|
|
33
|
+
// OpenAI-dialect upstreams reject unknown body fields with a 400.
|
|
34
|
+
if (body.top_p !== undefined) result.top_p = body.top_p;
|
|
35
|
+
|
|
36
|
+
// stop_sequences → stop. The wires name the same thing, so an ordinary
|
|
37
|
+
// caller's terminators carry over unchanged.
|
|
38
|
+
//
|
|
39
|
+
// One exception: Claude Code's auto-mode classifier asks for a structural
|
|
40
|
+
// verdict (<severity>N</severity> / <block>yes</block>) and also sends the
|
|
41
|
+
// closing tag as its own stop hint. OpenAI-dialect `stop` implementations
|
|
42
|
+
// trim the matched text, leaving "<severity>0" — a verdict the client cannot
|
|
43
|
+
// parse, so auto mode fails closed. Drop only that owned hint; the model then
|
|
44
|
+
// finishes the tag it was asked for.
|
|
45
|
+
const classifierStop = detectClassifierStopSequence(body);
|
|
46
|
+
const forwardedStop = withoutClassifierStop(body.stop_sequences, classifierStop);
|
|
47
|
+
if (Array.isArray(forwardedStop) && forwardedStop.length > 0) {
|
|
48
|
+
result.stop = forwardedStop;
|
|
49
|
+
}
|
|
50
|
+
|
|
31
51
|
// System message
|
|
32
52
|
if (body.system) {
|
|
33
53
|
const systemContent = Array.isArray(body.system)
|
|
@@ -31,6 +31,17 @@ export function openaiToClaudeRequest(model, body, stream) {
|
|
|
31
31
|
result.temperature = body.temperature;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
if (body.top_p !== undefined) result.top_p = body.top_p;
|
|
35
|
+
|
|
36
|
+
// stop → stop_sequences. OpenAI accepts a single string or an array; Claude
|
|
37
|
+
// only takes an array. Round-tripping this keeps a stop-terminated answer
|
|
38
|
+
// identical in both directions.
|
|
39
|
+
if (typeof body.stop === "string" && body.stop) {
|
|
40
|
+
result.stop_sequences = [body.stop];
|
|
41
|
+
} else if (Array.isArray(body.stop) && body.stop.length > 0) {
|
|
42
|
+
result.stop_sequences = body.stop;
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
// Messages
|
|
35
46
|
result.messages = [];
|
|
36
47
|
const systemParts = [];
|
package/package.json
CHANGED
|
@@ -250,6 +250,12 @@ export default function ClaudeToolCard({
|
|
|
250
250
|
// model ids never match, so the escape-hatch flag must ride along.
|
|
251
251
|
env[tool.envVars.todoTools] = "1";
|
|
252
252
|
}
|
|
253
|
+
if (tool.envVars?.autoModeServer) {
|
|
254
|
+
// Auto mode asks the Anthropic API to review actions in-band. A gateway
|
|
255
|
+
// cannot provide that review, so declaring it skips the dead round-trip
|
|
256
|
+
// and the classifier billing notice; the local classifier still runs.
|
|
257
|
+
env[tool.envVars.autoModeServer] = "0";
|
|
258
|
+
}
|
|
253
259
|
const res = await fetch("/api/cli-tools/claude-settings", {
|
|
254
260
|
method: "POST",
|
|
255
261
|
headers: { "Content-Type": "application/json" },
|
|
@@ -331,6 +337,12 @@ export default function ClaudeToolCard({
|
|
|
331
337
|
// model ids never match, so the escape-hatch flag must ride along.
|
|
332
338
|
env[tool.envVars.todoTools] = "1";
|
|
333
339
|
}
|
|
340
|
+
if (tool.envVars?.autoModeServer) {
|
|
341
|
+
// Auto mode asks the Anthropic API to review actions in-band. A gateway
|
|
342
|
+
// cannot provide that review, so declaring it skips the dead round-trip
|
|
343
|
+
// and the classifier billing notice; the local classifier still runs.
|
|
344
|
+
env[tool.envVars.autoModeServer] = "0";
|
|
345
|
+
}
|
|
334
346
|
|
|
335
347
|
return [
|
|
336
348
|
{
|
|
@@ -191,6 +191,15 @@ export async function POST(request) {
|
|
|
191
191
|
const todoToolsKey = CLI_TOOLS.claude.envVars.todoTools;
|
|
192
192
|
if (todoToolsKey) newSettings.env[todoToolsKey] = "1";
|
|
193
193
|
|
|
194
|
+
// CLAUDE_CODE_AUTO_MODE_SERVER=0 — auto mode asks the Anthropic API to
|
|
195
|
+
// review actions in-band (`safeguards` + the dangerous-tool-use beta). Behind
|
|
196
|
+
// this gateway that request never gets reviewed, so Claude Code spends one
|
|
197
|
+
// dead round-trip per session, shows the classifier billing notice, and then
|
|
198
|
+
// uses its own classifier regardless. Saying so up front keeps auto mode the
|
|
199
|
+
// same on every launch and keeps the notice out of the session.
|
|
200
|
+
const autoModeServerKey = CLI_TOOLS.claude.envVars.autoModeServer;
|
|
201
|
+
if (autoModeServerKey) newSettings.env[autoModeServerKey] = "0";
|
|
202
|
+
|
|
194
203
|
// Write new settings
|
|
195
204
|
await fs.writeFile(settingsPath, JSON.stringify(newSettings, null, 2));
|
|
196
205
|
|
|
@@ -219,6 +228,7 @@ const RESET_ENV_KEYS = [
|
|
|
219
228
|
"API_TIMEOUT_MS",
|
|
220
229
|
"CLAUDE_CODE_AUTO_COMPACT_WINDOW",
|
|
221
230
|
"CLAUDE_CODE_ENABLE_TODO_TOOLS",
|
|
231
|
+
"CLAUDE_CODE_AUTO_MODE_SERVER",
|
|
222
232
|
];
|
|
223
233
|
|
|
224
234
|
// DELETE - Reset settings (remove env fields)
|
|
@@ -19,6 +19,14 @@ export const CLI_TOOLS = {
|
|
|
19
19
|
// as `oc/...` or a combo name never match, so the task list disappears.
|
|
20
20
|
// This flag is the CLI's documented escape hatch for that gate.
|
|
21
21
|
todoTools: "CLAUDE_CODE_ENABLE_TODO_TOOLS",
|
|
22
|
+
// Auto mode's server-side classifier review asks the Anthropic API to
|
|
23
|
+
// review actions as part of the session's own requests (`safeguards` body
|
|
24
|
+
// field + `dangerous-tool-use` beta). A gateway cannot provide that review,
|
|
25
|
+
// so Claude Code prints the "this session isn't eligible" billing notice on
|
|
26
|
+
// the first checked action and falls back to its own classifier anyway.
|
|
27
|
+
// Declaring the gateway up front skips the notice: auto mode uses the local
|
|
28
|
+
// classifier for every session, with no dead round-trip first.
|
|
29
|
+
autoModeServer: "CLAUDE_CODE_AUTO_MODE_SERVER",
|
|
22
30
|
},
|
|
23
31
|
modelAliases: ["default", "sonnet", "opus", "fable", "haiku", "opusplan"],
|
|
24
32
|
settingsFile: "~/.claude/settings.json",
|
|
File without changes
|
|
File without changes
|