Merge branch 'main' into fix/claude-defautl-model-legacty

This commit is contained in:
musi 2026-07-08 19:26:40 +08:00 committed by GitHub
commit fcd386c80c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 48 additions and 3 deletions

View file

@ -1,3 +1,4 @@
import { execFileSync } from "node:child_process";
import os from "node:os";
import path from "node:path";
import type {
@ -9,6 +10,7 @@ import type {
import {
bearerAuthPlugin,
findOauthTokenSet,
isRecord,
missingCandidate,
providerInternalNamePlaceholder,
providerPayload,
@ -19,6 +21,7 @@ import {
} from "@ccr/core/agents/local-providers/shared";
const claudeDefaultModels = ["claude-sonnet-5"];
const claudeCodeKeychainService = "Claude Code-credentials";
const percentLimitMapping = (id: string, label: string, path: string, window: string) => ({
id,
@ -148,6 +151,19 @@ function readClaudeCodeOauth(): OAuthTokenSet | undefined {
sourceFile
};
}
const keychainRecord = readClaudeCodeKeychainRecord();
if (keychainRecord) {
const credential = findOauthTokenSet(keychainRecord);
if (credential) {
return {
accessToken: credential.accessToken,
refreshToken: credential.refreshToken,
sourceFile: `keychain:${claudeCodeKeychainService}`
};
}
}
return undefined;
}
@ -158,3 +174,24 @@ function claudeCredentialFiles(): string[] {
path.join(os.homedir(), ".config", "claude", "credentials.json")
]);
}
// Newer macOS builds of the Claude Code CLI store credentials in the
// Keychain instead of ~/.claude/.credentials.json. Reading it triggers the
// standard macOS keychain access prompt (Allow / Always Allow); the user
// declining or the item not existing both surface as a non-zero exit here.
function readClaudeCodeKeychainRecord(): Record<string, unknown> | undefined {
if (process.platform !== "darwin") {
return undefined;
}
try {
const output = execFileSync(
"security",
["find-generic-password", "-s", claudeCodeKeychainService, "-w"],
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
);
const parsed = JSON.parse(output.trim()) as unknown;
return isRecord(parsed) ? parsed : undefined;
} catch {
return undefined;
}
}

View file

@ -60,12 +60,17 @@ const localAgentProviderApiKey = "ccr-local-agent-login";
function materializeProviderPluginTemplates(
templates: unknown[],
providerName: string,
protocol: GatewayProviderConfig["type"]
protocol: GatewayProviderConfig["type"],
providerId: string
): unknown[] {
if (templates.length === 0) {
return [];
}
const internalName = protocol ? `${providerName}::${protocol}` : providerName;
// The core gateway matches provider plugins against the provider's runtime
// identifier (provider.id, or its slug), not the human-readable display name
// — the internal name here must mirror providerCapabilityInternalName() in
// gateway/service.ts or the plugin's auth-header override silently never applies.
const internalName = protocol ? `${providerId}::${protocol}` : providerId;
const replacements: Record<string, string> = {
[providerInternalNamePlaceholder]: internalName,
[providerNamePlaceholder]: providerName,
@ -1431,6 +1436,8 @@ function App() {
return false;
}
const existingProvider = providerEditIndex !== undefined ? draftConfig.Providers[providerEditIndex] : undefined;
const providerId = existingProvider?.id ?? providerNameSlug(providerName);
const provider: GatewayProviderConfig = {
api_base_url: normalizeProviderBaseUrl(baseUrl, protocol),
api_key: providerDraft.apiKey.trim(),
@ -1438,13 +1445,14 @@ function App() {
account: accountConfig,
credentials: credentials.length > 0 ? credentials : undefined,
icon: providerDraft.icon.trim() || undefined,
id: providerId,
modelDescriptions,
modelDisplayNames,
models,
name: providerName,
type: protocol
};
const importedProviderPlugins = materializeProviderPluginTemplates(providerDraft.providerPlugins, providerName, protocol);
const importedProviderPlugins = materializeProviderPluginTemplates(providerDraft.providerPlugins, providerName, protocol, providerId);
const wasImport = providerImportOpen;
const next = buildConfigUpdate((config) => {