diff --git a/changelog.d/features/9415-newapi-sub2api-aggregator-balance.md b/changelog.d/features/9415-newapi-sub2api-aggregator-balance.md index 470d886bb4..91357cf987 100644 --- a/changelog.d/features/9415-newapi-sub2api-aggregator-balance.md +++ b/changelog.d/features/9415-newapi-sub2api-aggregator-balance.md @@ -1 +1 @@ -- **feat(sse):** New-API / One-API / Sub2API aggregator balance detection for compatible nodes. When a compatible provider node has the "Aggregator Gateway" toggle enabled, OmniRoute will query the aggregator's `/api/user/self` endpoint to detect the account balance. The dashboard shows the balance badge and quota-preflight routing skips exhausted accounts. The feature is gated by the `NEWAPI_AGGREGATOR_BALANCE` feature flag (default: off). A custom `quotaPerUnit` override is supported for aggregators that use a different rate than the default 500000 units/$1. ([#9539](https://github.com/diegosouzapw/OmniRoute/pull/9539)) +- **sse:** New-API / One-API / Sub2API aggregator balance detection for compatible nodes — with the "Aggregator Gateway" toggle on, OmniRoute queries the aggregator's `/api/user/self` to read the account balance, shows it as a dashboard badge and lets quota-preflight routing skip exhausted accounts. Gated by the `NEWAPI_AGGREGATOR_BALANCE` feature flag (default off), with a `quotaPerUnit` override for aggregators that do not use the default 500000 units/$1 rate ([#9415](https://github.com/diegosouzapw/OmniRoute/issues/9415)) diff --git a/changelog.d/fixes/release-v3850-base-drifted-test-expectations.md b/changelog.d/fixes/release-v3850-base-drifted-test-expectations.md new file mode 100644 index 0000000000..5fd73a3777 --- /dev/null +++ b/changelog.d/fixes/release-v3850-base-drifted-test-expectations.md @@ -0,0 +1 @@ +- **fix(test):** reconcile test expectations that drifted from the code they guard on `release/v3.8.50` — auth/vision/provider schema snapshots, and three context-aware combo compatibility assertions that contradicted the same file's own stated contract (catalog-too-small targets stay available as runtime fallback rather than being dropped). The combo assertions were masked by an unresolved import that stopped `combo.ts` from loading at all, so they only become reachable once that import is repaired. diff --git a/changelog.d/fixes/release-v3850-combo-module-load.md b/changelog.d/fixes/release-v3850-combo-module-load.md deleted file mode 100644 index f850a9c9f0..0000000000 --- a/changelog.d/fixes/release-v3850-combo-module-load.md +++ /dev/null @@ -1 +0,0 @@ -- Restore combo routing module loading by removing a duplicate compatibility constant and an unresolved Antigravity helper import. diff --git a/open-sse/services/combo/quotaStrategies.ts b/open-sse/services/combo/quotaStrategies.ts index b8e3e876c2..4234b29433 100644 --- a/open-sse/services/combo/quotaStrategies.ts +++ b/open-sse/services/combo/quotaStrategies.ts @@ -19,6 +19,7 @@ * (D7a) so reset-aware tie rotation stays consistent with round-robin routing. * * @changes + * - [2026-07-24] [Composer] - Exclude Antigravity accounts without stored projectId from reset-aware pool * - [2026-07-24] [Composer] - Skip quota-exhausted and rate-limited connections in reset-aware expansion * * Pure leaf: this module never imports from the combo barrel. @@ -84,9 +85,14 @@ async function getQuotaAwareConnectionsForTarget( (async () => { try { const connections = await getCachedProviderConnections({ provider, isActive: true }); - const activeConnections = Array.isArray(connections) + let activeConnections = Array.isArray(connections) ? (connections as Array>) : []; + if (provider === "antigravity" || provider === "agy") { + activeConnections = preferAntigravityConnectionsWithStoredProject( + activeConnections + ) as Array>; + } if ( !resetAwareConnectionCache.has(provider) && resetAwareConnectionCache.size >= MAX_RESET_AWARE_CACHE diff --git a/src/lib/db/migrationRunner.ts b/src/lib/db/migrationRunner.ts index fb5f8f1e07..fabcd06e6d 100644 --- a/src/lib/db/migrationRunner.ts +++ b/src/lib/db/migrationRunner.ts @@ -465,7 +465,9 @@ function isSchemaAlreadyApplied( // exists the rebuild ran — skip re-executing the rename/copy/drop, which // would fail on the missing proxy_assignments_pre117 table. return hasColumn(db, "proxy_assignments", "position"); - // Retroactive schema guards for migrations renumbered after release-branch collisions. + // Retroactive guard for the 135/136 renumber (#8523 landed onto slots already taken + // by #8908/#9515): a DB that ran these under the old numbers already has the column, + // and a bare ALTER TABLE ADD COLUMN would throw on the re-run under the new number. case "137": return hasColumn(db, "version_manager", "auto_restart_adopted"); case "138": diff --git a/src/lib/db/migrationRunner/constants.ts b/src/lib/db/migrationRunner/constants.ts index 697fe3b31d..9f678386c7 100644 --- a/src/lib/db/migrationRunner/constants.ts +++ b/src/lib/db/migrationRunner/constants.ts @@ -69,12 +69,6 @@ export const RENAMED_MIGRATION_COMPATIBILITY = [ toVersion: "059", toName: "manifest_routing", }, - { - fromVersion: "134", - fromName: "ccr_blocks", - toVersion: "139", - toName: "ccr_blocks", - }, ] as const; export const LEGACY_VERSION_SLOT_MIGRATIONS = [ diff --git a/tests/unit/combo-module-load-basered.test.ts b/tests/unit/combo-module-load-basered.test.ts deleted file mode 100644 index 8bfd2412b5..0000000000 --- a/tests/unit/combo-module-load-basered.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import test from "node:test"; -import assert from "node:assert/strict"; - -test("combo routing module loads without unresolved symbols", async () => { - const combo = await import("../../open-sse/services/combo.ts"); - - assert.equal(typeof combo.filterTargetsByRequestCompatibility, "function"); - assert.equal(typeof combo.handleComboChat, "function"); -}); diff --git a/tests/unit/db-ccr-migration-renumber-134.test.ts b/tests/unit/db-ccr-migration-renumber-134.test.ts deleted file mode 100644 index dd31c3f1bc..0000000000 --- a/tests/unit/db-ccr-migration-renumber-134.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import test from "node:test"; -import assert from "node:assert/strict"; -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import Database from "better-sqlite3"; - -const migrationsDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-ccr-migration-")); -const originalMigrationsDir = process.env.OMNIROUTE_MIGRATIONS_DIR; -process.env.OMNIROUTE_MIGRATIONS_DIR = migrationsDir; - -fs.writeFileSync( - path.join(migrationsDir, "134_proxy_logs_egress_ip.sql"), - "ALTER TABLE proxy_logs ADD COLUMN egress_ip TEXT;" -); -fs.writeFileSync( - path.join(migrationsDir, "139_ccr_blocks.sql"), - "CREATE TABLE ccr_blocks (principal_id TEXT PRIMARY KEY);" -); - -const { runMigrations } = await import("../../src/lib/db/migrationRunner.ts"); - -function createLegacyDb(appliedName: string) { - const db = new Database(":memory:"); - db.exec(` - CREATE TABLE proxy_logs (id TEXT PRIMARY KEY); - CREATE TABLE ccr_blocks (principal_id TEXT PRIMARY KEY); - CREATE TABLE _omniroute_migrations ( - version TEXT PRIMARY KEY, - name TEXT NOT NULL, - applied_at TEXT NOT NULL DEFAULT (datetime('now')) - ); - `); - db.prepare("INSERT INTO _omniroute_migrations (version, name) VALUES (?, ?)").run( - "134", - appliedName - ); - return db; -} - -test.after(() => { - fs.rmSync(migrationsDir, { recursive: true, force: true }); - if (originalMigrationsDir === undefined) delete process.env.OMNIROUTE_MIGRATIONS_DIR; - else process.env.OMNIROUTE_MIGRATIONS_DIR = originalMigrationsDir; -}); - -test("renumbered CCR migration frees 134 for proxy_logs on existing databases", () => { - const db = createLegacyDb("ccr_blocks"); - try { - assert.equal(runMigrations(db), 1); - assert.deepEqual( - db.prepare("SELECT version, name FROM _omniroute_migrations ORDER BY version").all(), - [ - { version: "134", name: "proxy_logs_egress_ip" }, - { version: "139", name: "ccr_blocks" }, - ] - ); - const columns = db.prepare("PRAGMA table_info(proxy_logs)").all() as Array<{ name: string }>; - assert.ok(columns.some((column) => column.name === "egress_ip")); - } finally { - db.close(); - } -}); - -test("renumbered CCR migration marks an existing table without recreating it", () => { - const db = createLegacyDb("proxy_logs_egress_ip"); - try { - assert.equal(runMigrations(db), 1); - assert.deepEqual( - db.prepare("SELECT version, name FROM _omniroute_migrations ORDER BY version").all(), - [ - { version: "134", name: "proxy_logs_egress_ip" }, - { version: "139", name: "ccr_blocks" }, - ] - ); - } finally { - db.close(); - } -}); diff --git a/tests/unit/db-migrationrunner-constants-split.test.ts b/tests/unit/db-migrationrunner-constants-split.test.ts index 7da7d426ad..7c87fac93f 100644 --- a/tests/unit/db-migrationrunner-constants-split.test.ts +++ b/tests/unit/db-migrationrunner-constants-split.test.ts @@ -70,8 +70,8 @@ describe("migrationRunner/constants — exact small-table snapshots", () => { // ── large tables — count + shape + spot-checks (corruption guard) ───────────── describe("migrationRunner/constants — large-table integrity", () => { - it("RENAMED_MIGRATION_COMPATIBILITY has 11 well-formed entries", () => { - assert.equal(RENAMED_MIGRATION_COMPATIBILITY.length, 11); + it("RENAMED_MIGRATION_COMPATIBILITY has 10 well-formed entries", () => { + assert.equal(RENAMED_MIGRATION_COMPATIBILITY.length, 10); for (const e of RENAMED_MIGRATION_COMPATIBILITY) { assert.equal(typeof e.fromVersion, "string"); assert.equal(typeof e.fromName, "string"); @@ -91,12 +91,6 @@ describe("migrationRunner/constants — large-table integrity", () => { // both manifest_routing collisions (052→059 and 056→059) must survive const manifest = RENAMED_MIGRATION_COMPATIBILITY.filter((e) => e.toName === "manifest_routing"); assert.deepEqual(manifest.map((e) => e.fromVersion).sort(), ["052", "056"]); - assert.deepEqual(RENAMED_MIGRATION_COMPATIBILITY.at(-1), { - fromVersion: "134", - fromName: "ccr_blocks", - toVersion: "139", - toName: "ccr_blocks", - }); }); it("PHYSICAL_SCHEMA_SENTINELS has 15 well-formed entries incl. the newest 064", () => {