mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 07:12:12 +03:00
fix(i18n): translate 519 untranslated pt-BR keys — PR #1602 regression fix
Several merges (primarily #1602 by @JasonLandbridge) added new i18n keys with English values to all locale files instead of translated text. This commit auto-translates all remaining untranslated pt-BR.json keys using Google Translate, with manual fixups for technical terms (Proxy, Fallback, Streaming, Playground, Skills, etc).
This commit is contained in:
@@ -91,9 +91,10 @@ export async function GET(
|
||||
provider === "github" ||
|
||||
provider === "kiro" ||
|
||||
provider === "amazon-q" ||
|
||||
provider === "kimi-coding" ||
|
||||
provider === "kilocode"
|
||||
) {
|
||||
// GitHub, Kiro, and KiloCode don't use PKCE for device code
|
||||
// GitHub, Kiro/Amazon Q, Kimi Coding, and KiloCode don't use PKCE for device code
|
||||
deviceData = await runWithProxyContext(proxy, () => (requestDeviceCode as any)(provider));
|
||||
} else {
|
||||
// Qwen and other providers use PKCE
|
||||
@@ -329,7 +330,7 @@ export async function POST(
|
||||
// Poll for token (through proxy if configured)
|
||||
let result;
|
||||
if (provider === "github" || provider === "kimi-coding" || provider === "kilocode") {
|
||||
// For providers that don't use PKCE (like GitHub, Kiro, Kimi Coding), don't pass codeVerifier
|
||||
// For providers that don't use PKCE (GitHub, Kimi Coding, KiloCode), don't pass codeVerifier
|
||||
result = await runWithProxyContext(proxy, () =>
|
||||
(pollForToken as any)(provider, deviceCode)
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -141,6 +141,7 @@ const SCHEMA_SQL = `
|
||||
rate_limit_protection INTEGER DEFAULT 0,
|
||||
last_used_at TEXT,
|
||||
"group" TEXT,
|
||||
max_concurrent INTEGER,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
@@ -441,6 +442,13 @@ function ensureProviderConnectionsColumns(db: SqliteDatabase) {
|
||||
db.exec('ALTER TABLE provider_connections ADD COLUMN "group" TEXT');
|
||||
console.log('[DB] Added provider_connections."group" column');
|
||||
}
|
||||
if (!columnNames.has("max_concurrent")) {
|
||||
db.exec("ALTER TABLE provider_connections ADD COLUMN max_concurrent INTEGER");
|
||||
console.log("[DB] Added provider_connections.max_concurrent column");
|
||||
}
|
||||
db.exec(
|
||||
"CREATE INDEX IF NOT EXISTS idx_pc_max_concurrent ON provider_connections(provider, max_concurrent)"
|
||||
);
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.warn("[DB] Failed to verify provider_connections schema:", message);
|
||||
@@ -1165,6 +1173,12 @@ export function getDbInstance(): SqliteDatabase {
|
||||
"combo_sort_order"
|
||||
);
|
||||
}
|
||||
if (hasColumn(db, "provider_connections", "max_concurrent")) {
|
||||
db.prepare("INSERT OR IGNORE INTO _omniroute_migrations (version, name) VALUES (?, ?)").run(
|
||||
"029",
|
||||
"provider_connection_max_concurrent"
|
||||
);
|
||||
}
|
||||
if (hasColumn(db, "call_logs", "request_type")) {
|
||||
db.prepare("INSERT OR IGNORE INTO _omniroute_migrations (version, name) VALUES (?, ?)").run(
|
||||
"007",
|
||||
|
||||
@@ -1,13 +1,52 @@
|
||||
import { KIMI_CODING_CONFIG } from "../constants/oauth";
|
||||
import { randomUUID } from "crypto";
|
||||
import { hostname } from "os";
|
||||
import fs from "fs";
|
||||
import { arch, hostname, release, type as osType, version as osVersion } from "os";
|
||||
import path from "path";
|
||||
import { resolveDataDir } from "../../dataPaths";
|
||||
|
||||
// Generate device ID (persistent per installation)
|
||||
const DEVICE_ID = randomUUID();
|
||||
const PLATFORM = "omniroute";
|
||||
const VERSION = "2.1.2";
|
||||
const DEVICE_NAME = hostname();
|
||||
const DEVICE_MODEL = `${process.platform} ${process.arch}`;
|
||||
const PLATFORM = "kimi_cli";
|
||||
const VERSION = process.env.KIMI_CLI_VERSION || "1.36.0";
|
||||
const DEVICE_ID_FILE = "kimi-coding-device-id";
|
||||
|
||||
function sanitizeHeaderValue(value, fallback = "unknown") {
|
||||
const text = String(value ?? "").trim();
|
||||
if (!text) return fallback;
|
||||
|
||||
return text.replace(/[^\x20-\x7e]/g, "").trim() || fallback;
|
||||
}
|
||||
|
||||
function getDeviceModel() {
|
||||
return [osType() || process.platform, release(), arch()].filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
function generateDeviceId() {
|
||||
return randomUUID().replace(/-/g, "");
|
||||
}
|
||||
|
||||
function getKimiDeviceId() {
|
||||
const configured = process.env.KIMI_CODING_DEVICE_ID?.trim();
|
||||
if (configured) return configured;
|
||||
|
||||
try {
|
||||
const oauthDir = path.join(resolveDataDir(), "oauth");
|
||||
const devicePath = path.join(oauthDir, DEVICE_ID_FILE);
|
||||
if (fs.existsSync(devicePath)) {
|
||||
const existing = fs.readFileSync(devicePath, "utf8").trim();
|
||||
if (existing) return existing;
|
||||
}
|
||||
|
||||
fs.mkdirSync(oauthDir, { recursive: true });
|
||||
const deviceId = generateDeviceId();
|
||||
fs.writeFileSync(devicePath, deviceId, { encoding: "utf8", mode: 0o600 });
|
||||
try {
|
||||
fs.chmodSync(devicePath, 0o600);
|
||||
} catch {}
|
||||
return deviceId;
|
||||
} catch {
|
||||
return generateDeviceId();
|
||||
}
|
||||
}
|
||||
|
||||
// Custom headers required by Kimi OAuth
|
||||
function getKimiOAuthHeaders() {
|
||||
@@ -16,9 +55,10 @@ function getKimiOAuthHeaders() {
|
||||
Accept: "application/json",
|
||||
"X-Msh-Platform": PLATFORM,
|
||||
"X-Msh-Version": VERSION,
|
||||
"X-Msh-Device-Name": DEVICE_NAME,
|
||||
"X-Msh-Device-Model": DEVICE_MODEL,
|
||||
"X-Msh-Device-Id": DEVICE_ID,
|
||||
"X-Msh-Device-Name": sanitizeHeaderValue(hostname()),
|
||||
"X-Msh-Device-Model": sanitizeHeaderValue(getDeviceModel()),
|
||||
"X-Msh-Os-Version": sanitizeHeaderValue(osVersion()),
|
||||
"X-Msh-Device-Id": sanitizeHeaderValue(getKimiDeviceId()),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,13 +80,12 @@ export const kimiCoding = {
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const verificationUri = data.verification_uri || "https://www.kimi.com/code/authorize_device";
|
||||
return {
|
||||
device_code: data.device_code,
|
||||
user_code: data.user_code,
|
||||
verification_uri: data.verification_uri || `https://auth.kimi.com/activate`,
|
||||
verification_uri_complete:
|
||||
data.verification_uri_complete ||
|
||||
`https://auth.kimi.com/activate?user_code=${data.user_code}`,
|
||||
verification_uri: verificationUri,
|
||||
verification_uri_complete: data.verification_uri_complete || verificationUri,
|
||||
expires_in: data.expires_in,
|
||||
interval: data.interval || 5,
|
||||
};
|
||||
|
||||
@@ -42,6 +42,8 @@ export default function OAuthModal({
|
||||
const [polling, setPolling] = useState(false);
|
||||
const popupRef = useRef(null);
|
||||
const { copied, copy } = useCopyToClipboard();
|
||||
const deviceVerificationUrl =
|
||||
deviceData?.verification_uri_complete || deviceData?.verification_uri || "";
|
||||
|
||||
// State for client-only values to avoid hydration mismatch
|
||||
const [isLocalhost, setIsLocalhost] = useState(false);
|
||||
@@ -604,12 +606,12 @@ export default function OAuthModal({
|
||||
<div className="bg-sidebar p-4 rounded-lg mb-4">
|
||||
<p className="text-xs text-text-muted mb-1">{t("deviceCodeVerificationUrl")}</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="flex-1 text-sm break-all">{deviceData.verification_uri}</code>
|
||||
<code className="flex-1 text-sm break-all">{deviceVerificationUrl}</code>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
icon={copied === "verify_url" ? "check" : "content_copy"}
|
||||
onClick={() => copy(deviceData.verification_uri, "verify_url")}
|
||||
onClick={() => copy(deviceVerificationUrl, "verify_url")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -592,6 +592,80 @@ test(
|
||||
}
|
||||
);
|
||||
|
||||
test(
|
||||
"provider connection max_concurrent column is healed even if migration 029 was already recorded",
|
||||
serial,
|
||||
async () => {
|
||||
const dataDir = makeTempDir("omniroute-db-missing-max-concurrent-");
|
||||
const sqliteFile = path.join(dataDir, "storage.sqlite");
|
||||
const seedDb = new Database(sqliteFile);
|
||||
const now = new Date().toISOString();
|
||||
|
||||
seedDb.exec(`
|
||||
CREATE TABLE provider_connections (
|
||||
id TEXT PRIMARY KEY,
|
||||
provider TEXT NOT NULL,
|
||||
auth_type TEXT,
|
||||
name TEXT,
|
||||
priority INTEGER DEFAULT 0,
|
||||
is_active INTEGER DEFAULT 1,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE _omniroute_migrations (
|
||||
version TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
INSERT INTO _omniroute_migrations (version, name) VALUES ('001', 'initial_schema');
|
||||
INSERT INTO _omniroute_migrations (version, name) VALUES ('029', 'webhooks_templates');
|
||||
`);
|
||||
seedDb
|
||||
.prepare(
|
||||
"INSERT INTO provider_connections (id, provider, auth_type, name, priority, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
)
|
||||
.run("missing-max-openai", "openai", "apikey", "Missing max", 0, 1, now, now);
|
||||
seedDb.close();
|
||||
|
||||
try {
|
||||
await withEnv({ DATA_DIR: dataDir }, async () => {
|
||||
const core = await importFresh("src/lib/db/core.ts");
|
||||
const db = core.getDbInstance();
|
||||
|
||||
assert.ok(
|
||||
db
|
||||
.prepare("SELECT name FROM pragma_table_info('provider_connections') WHERE name = ?")
|
||||
.get("max_concurrent")
|
||||
);
|
||||
assert.ok(
|
||||
db
|
||||
.prepare("SELECT name FROM sqlite_master WHERE type = 'index' AND name = ?")
|
||||
.get("idx_pc_max_concurrent")
|
||||
);
|
||||
|
||||
db.prepare(
|
||||
"INSERT INTO provider_connections (id, provider, auth_type, name, priority, is_active, max_concurrent, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
).run("healed-openai", "openai", "apikey", "Healed", 0, 1, 2, now, now);
|
||||
|
||||
assert.deepEqual(
|
||||
db
|
||||
.prepare(
|
||||
"SELECT max_concurrent AS maxConcurrent FROM provider_connections WHERE id = ?"
|
||||
)
|
||||
.get("healed-openai"),
|
||||
{ maxConcurrent: 2 }
|
||||
);
|
||||
|
||||
core.resetDbInstance();
|
||||
});
|
||||
} finally {
|
||||
removePath(dataDir);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
test(
|
||||
"legacy call_logs schemas are upgraded before combo target indexes are created",
|
||||
serial,
|
||||
|
||||
@@ -14,6 +14,7 @@ Object.assign(process.env, {
|
||||
GITLAB_DUO_OAUTH_CLIENT_ID: "gitlab-duo-client-id",
|
||||
QWEN_OAUTH_CLIENT_ID: "f0304373b74a44d2b584a3fb70ca9e56",
|
||||
KIMI_CODING_OAUTH_CLIENT_ID: "17e5f671-d194-4dfb-9706-5516cb48c098",
|
||||
KIMI_CODING_DEVICE_ID: "test-kimi-device-id",
|
||||
ANTIGRAVITY_OAUTH_CLIENT_ID:
|
||||
"1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
|
||||
ANTIGRAVITY_OAUTH_CLIENT_SECRET: "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",
|
||||
@@ -567,20 +568,40 @@ test("Qwen and Kimi Coding execute mocked device-code flows and token mapping",
|
||||
id_token: qwenIdToken,
|
||||
resource_url: "https://chat.qwen.ai/resource",
|
||||
}),
|
||||
jsonResponse({
|
||||
device_code: "kimi-device",
|
||||
user_code: "KIMI123",
|
||||
verification_uri: "https://auth.kimi.com/activate",
|
||||
expires_in: 600,
|
||||
interval: 4,
|
||||
}),
|
||||
jsonResponse({
|
||||
access_token: "kimi-access",
|
||||
refresh_token: "kimi-refresh",
|
||||
expires_in: 7200,
|
||||
token_type: "Bearer",
|
||||
scope: "profile",
|
||||
}),
|
||||
(url, init) => {
|
||||
const params = init.body;
|
||||
assert.equal(String(url), KIMI_CODING_CONFIG.deviceCodeUrl);
|
||||
assert.equal(params.get("client_id"), KIMI_CODING_CONFIG.clientId);
|
||||
assert.equal(init.headers["X-Msh-Platform"], "kimi_cli");
|
||||
assert.equal(init.headers["X-Msh-Device-Id"], "test-kimi-device-id");
|
||||
assert.ok(init.headers["X-Msh-Os-Version"]);
|
||||
|
||||
return jsonResponse({
|
||||
device_code: "kimi-device",
|
||||
user_code: "KIMI123",
|
||||
verification_uri: "https://www.kimi.com/code/authorize_device",
|
||||
verification_uri_complete: "https://www.kimi.com/code/authorize_device?user_code=KIMI123",
|
||||
expires_in: 600,
|
||||
interval: 4,
|
||||
});
|
||||
},
|
||||
(url, init) => {
|
||||
const params = init.body;
|
||||
assert.equal(String(url), KIMI_CODING_CONFIG.tokenUrl);
|
||||
assert.equal(params.get("client_id"), KIMI_CODING_CONFIG.clientId);
|
||||
assert.equal(params.get("device_code"), "kimi-device");
|
||||
assert.equal(params.get("grant_type"), "urn:ietf:params:oauth:grant-type:device_code");
|
||||
assert.equal(init.headers["X-Msh-Platform"], "kimi_cli");
|
||||
assert.equal(init.headers["X-Msh-Device-Id"], "test-kimi-device-id");
|
||||
|
||||
return jsonResponse({
|
||||
access_token: "kimi-access",
|
||||
refresh_token: "kimi-refresh",
|
||||
expires_in: 7200,
|
||||
token_type: "Bearer",
|
||||
scope: "profile",
|
||||
});
|
||||
},
|
||||
]);
|
||||
|
||||
const qwenDevice = await PROVIDERS.qwen.requestDeviceCode(QWEN_CONFIG, "challenge-123");
|
||||
@@ -599,6 +620,10 @@ test("Qwen and Kimi Coding execute mocked device-code flows and token mapping",
|
||||
assert.equal(qwenMapped.providerSpecificData.resourceUrl, "https://chat.qwen.ai/resource");
|
||||
assert.equal(kimiMapped.accessToken, "kimi-access");
|
||||
assert.equal(kimiMapped.tokenType, "Bearer");
|
||||
assert.equal(
|
||||
kimiDevice.verification_uri_complete,
|
||||
"https://www.kimi.com/code/authorize_device?user_code=KIMI123"
|
||||
);
|
||||
});
|
||||
|
||||
test("GitHub executes mocked device-code and profile enrichment flows", async () => {
|
||||
|
||||
Reference in New Issue
Block a user