feat(gemini): add Gemini provider integration (#202)
* feat(gemini): add Gemini provider integration - Add gemini_config.rs module for .env file parsing - Extend AppType enum to support Gemini - Implement GeminiConfigEditor and GeminiFormFields components - Add GeminiIcon with standardized 1024x1024 viewBox - Add Gemini provider presets configuration - Update i18n translations for Gemini support - Extend ProviderService and McpService for Gemini * fix(gemini): resolve TypeScript errors, add i18n support, and fix MCP logic **Critical Fixes:** - Fix TS2741 errors in tests/msw/state.ts by adding missing Gemini type definitions - Fix ProviderCard.extractApiUrl to support GOOGLE_GEMINI_BASE_URL display - Add missing apps.gemini i18n keys (zh/en) for proper app name display - Fix MCP service Gemini cross-app duplication logic to prevent self-copy **Technical Details:** - tests/msw/state.ts: Add gemini default providers, current ID, and MCP config - ProviderCard.tsx: Check both ANTHROPIC_BASE_URL and GOOGLE_GEMINI_BASE_URL - services/mcp.rs: Skip Gemini in sync_other_side logic with unreachable!() guards - Run pnpm format to auto-fix code style issues **Verification:** - ✅ pnpm typecheck passes - ✅ pnpm format completed * feat(gemini): enhance authentication and config parsing - Add strict and lenient .env parsing modes - Implement PackyCode partner authentication detection - Support Google OAuth official authentication - Auto-configure security.auth.selectedType for PackyCode - Add comprehensive test coverage for all auth types - Update i18n for OAuth hints and Gemini config --------- Co-authored-by: Jason <farion1231@gmail.com>
This commit is contained in:
@@ -165,12 +165,32 @@ export const hasCommonConfigSnippet = (
|
||||
}
|
||||
};
|
||||
|
||||
// 读取配置中的 API Key(优先 ANTHROPIC_AUTH_TOKEN,其次 ANTHROPIC_API_KEY)
|
||||
export const getApiKeyFromConfig = (jsonString: string): string => {
|
||||
// 读取配置中的 API Key(支持 Claude, Codex, Gemini)
|
||||
export const getApiKeyFromConfig = (
|
||||
jsonString: string,
|
||||
appType?: string,
|
||||
): string => {
|
||||
try {
|
||||
const config = JSON.parse(jsonString);
|
||||
const token = config?.env?.ANTHROPIC_AUTH_TOKEN;
|
||||
const apiKey = config?.env?.ANTHROPIC_API_KEY;
|
||||
const env = config?.env;
|
||||
|
||||
if (!env) return "";
|
||||
|
||||
// Gemini API Key
|
||||
if (appType === "gemini") {
|
||||
const geminiKey = env.GEMINI_API_KEY;
|
||||
return typeof geminiKey === "string" ? geminiKey : "";
|
||||
}
|
||||
|
||||
// Codex API Key
|
||||
if (appType === "codex") {
|
||||
const codexKey = env.CODEX_API_KEY;
|
||||
return typeof codexKey === "string" ? codexKey : "";
|
||||
}
|
||||
|
||||
// Claude API Key (优先 ANTHROPIC_AUTH_TOKEN,其次 ANTHROPIC_API_KEY)
|
||||
const token = env.ANTHROPIC_AUTH_TOKEN;
|
||||
const apiKey = env.ANTHROPIC_API_KEY;
|
||||
const value =
|
||||
typeof token === "string"
|
||||
? token
|
||||
@@ -229,10 +249,22 @@ export const applyTemplateValues = (
|
||||
};
|
||||
|
||||
// 判断配置中是否存在 API Key 字段
|
||||
export const hasApiKeyField = (jsonString: string): boolean => {
|
||||
export const hasApiKeyField = (
|
||||
jsonString: string,
|
||||
appType?: string,
|
||||
): boolean => {
|
||||
try {
|
||||
const config = JSON.parse(jsonString);
|
||||
const env = config?.env ?? {};
|
||||
|
||||
if (appType === "gemini") {
|
||||
return Object.prototype.hasOwnProperty.call(env, "GEMINI_API_KEY");
|
||||
}
|
||||
|
||||
if (appType === "codex") {
|
||||
return Object.prototype.hasOwnProperty.call(env, "CODEX_API_KEY");
|
||||
}
|
||||
|
||||
return (
|
||||
Object.prototype.hasOwnProperty.call(env, "ANTHROPIC_AUTH_TOKEN") ||
|
||||
Object.prototype.hasOwnProperty.call(env, "ANTHROPIC_API_KEY")
|
||||
@@ -246,9 +278,9 @@ export const hasApiKeyField = (jsonString: string): boolean => {
|
||||
export const setApiKeyInConfig = (
|
||||
jsonString: string,
|
||||
apiKey: string,
|
||||
options: { createIfMissing?: boolean } = {},
|
||||
options: { createIfMissing?: boolean; appType?: string } = {},
|
||||
): string => {
|
||||
const { createIfMissing = false } = options;
|
||||
const { createIfMissing = false, appType } = options;
|
||||
try {
|
||||
const config = JSON.parse(jsonString);
|
||||
if (!config.env) {
|
||||
@@ -256,7 +288,32 @@ export const setApiKeyInConfig = (
|
||||
config.env = {};
|
||||
}
|
||||
const env = config.env as Record<string, any>;
|
||||
// 优先写入已存在的字段;若两者均不存在且允许创建,则默认创建 AUTH_TOKEN 字段
|
||||
|
||||
// Gemini API Key
|
||||
if (appType === "gemini") {
|
||||
if ("GEMINI_API_KEY" in env) {
|
||||
env.GEMINI_API_KEY = apiKey;
|
||||
} else if (createIfMissing) {
|
||||
env.GEMINI_API_KEY = apiKey;
|
||||
} else {
|
||||
return jsonString;
|
||||
}
|
||||
return JSON.stringify(config, null, 2);
|
||||
}
|
||||
|
||||
// Codex API Key
|
||||
if (appType === "codex") {
|
||||
if ("CODEX_API_KEY" in env) {
|
||||
env.CODEX_API_KEY = apiKey;
|
||||
} else if (createIfMissing) {
|
||||
env.CODEX_API_KEY = apiKey;
|
||||
} else {
|
||||
return jsonString;
|
||||
}
|
||||
return JSON.stringify(config, null, 2);
|
||||
}
|
||||
|
||||
// Claude API Key (优先写入已存在的字段;若两者均不存在且允许创建,则默认创建 AUTH_TOKEN 字段)
|
||||
if ("ANTHROPIC_AUTH_TOKEN" in env) {
|
||||
env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
||||
} else if ("ANTHROPIC_API_KEY" in env) {
|
||||
|
||||
@@ -6,15 +6,17 @@
|
||||
*/
|
||||
export const normalizeQuotes = (text: string): string => {
|
||||
if (!text) return text;
|
||||
return text
|
||||
// 双引号族 → "
|
||||
.replace(/[“”„‟"]/g, '"')
|
||||
// 单引号族 → '
|
||||
.replace(/[‘’']/g, "'");
|
||||
return (
|
||||
text
|
||||
// 双引号族 → "
|
||||
.replace(/[“”„‟"]/g, '"')
|
||||
// 单引号族 → '
|
||||
.replace(/[‘’']/g, "'")
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 专用于 TOML 文本的归一化;目前等同于 normalizeQuotes,后续可扩展(如空白、行尾等)。
|
||||
*/
|
||||
export const normalizeTomlText = (text: string): string => normalizeQuotes(text);
|
||||
|
||||
export const normalizeTomlText = (text: string): string =>
|
||||
normalizeQuotes(text);
|
||||
|
||||
Reference in New Issue
Block a user