opencode-studio-server 1.8.0 → 1.9.0
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/index.js +109 -40
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -204,17 +204,59 @@ function processLogLine(line) {
|
|
|
204
204
|
|
|
205
205
|
if (isQuotaError) {
|
|
206
206
|
console.log(`[LogWatcher] Detected quota exhaustion for ${namespace}`);
|
|
207
|
-
// Mark exhausted for today
|
|
208
|
-
metadata._quota[namespace].exhausted = true;
|
|
209
|
-
metadata._quota[namespace].exhaustedDate = today;
|
|
210
207
|
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
208
|
+
// Reload metadata to ensure freshness
|
|
209
|
+
const currentMeta = loadPoolMetadata();
|
|
210
|
+
if (!currentMeta._quota) currentMeta._quota = {};
|
|
211
|
+
if (!currentMeta._quota[namespace]) currentMeta._quota[namespace] = {};
|
|
212
|
+
|
|
213
|
+
// Debounce check
|
|
214
|
+
const lastRotation = currentMeta._quota[namespace].lastRotation || 0;
|
|
215
|
+
if (Date.now() - lastRotation < 10000) {
|
|
216
|
+
console.log(`[LogWatcher] Ignoring 429 (rotation debounce active)`);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const studio = loadStudioConfig();
|
|
221
|
+
const activeAccount = studio.activeProfiles?.[provider];
|
|
222
|
+
|
|
223
|
+
let rotated = false;
|
|
224
|
+
|
|
225
|
+
if (activeAccount) {
|
|
226
|
+
console.log(`[LogWatcher] Auto-rotating due to rate limit on ${activeAccount}`);
|
|
227
|
+
|
|
228
|
+
if (!currentMeta[namespace]) currentMeta[namespace] = {};
|
|
229
|
+
if (!currentMeta[namespace][activeAccount]) currentMeta[namespace][activeAccount] = {};
|
|
230
|
+
|
|
231
|
+
// Mark cooldown (1 hour)
|
|
232
|
+
currentMeta[namespace][activeAccount].cooldownUntil = Date.now() + 3600000;
|
|
233
|
+
currentMeta[namespace][activeAccount].lastCooldownReason = 'auto_429';
|
|
234
|
+
|
|
235
|
+
savePoolMetadata(currentMeta);
|
|
236
|
+
|
|
237
|
+
// Attempt rotation
|
|
238
|
+
const result = rotateAccount(provider, 'auto_rotation_429');
|
|
239
|
+
if (result.success) {
|
|
240
|
+
console.log(`[LogWatcher] Successfully rotated to ${result.newAccount}`);
|
|
241
|
+
rotated = true;
|
|
242
|
+
} else {
|
|
243
|
+
console.log(`[LogWatcher] Auto-rotation failed: ${result.error}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (rotated) return;
|
|
248
|
+
|
|
249
|
+
// Fallback: Mark namespace exhausted
|
|
250
|
+
currentMeta._quota[namespace].exhausted = true;
|
|
251
|
+
currentMeta._quota[namespace].exhaustedDate = today;
|
|
252
|
+
|
|
253
|
+
const currentUsage = currentMeta._quota[namespace][today] || 0;
|
|
214
254
|
if (currentUsage > 5) {
|
|
215
|
-
|
|
216
|
-
metadata._quota[namespace].dailyLimit = currentUsage;
|
|
255
|
+
currentMeta._quota[namespace].dailyLimit = currentUsage;
|
|
217
256
|
}
|
|
257
|
+
|
|
258
|
+
savePoolMetadata(currentMeta);
|
|
259
|
+
return;
|
|
218
260
|
}
|
|
219
261
|
}
|
|
220
262
|
|
|
@@ -1922,67 +1964,56 @@ function getPoolQuota(provider, pool) {
|
|
|
1922
1964
|
};
|
|
1923
1965
|
}
|
|
1924
1966
|
|
|
1925
|
-
|
|
1926
|
-
app.get('/api/auth/pool', (req, res) => {
|
|
1927
|
-
const provider = req.query.provider || 'google';
|
|
1928
|
-
syncAntigravityPool();
|
|
1967
|
+
function rotateAccount(provider, reason = 'manual_rotation') {
|
|
1929
1968
|
const pool = buildAccountPool(provider);
|
|
1930
|
-
const quota = getPoolQuota(provider, pool);
|
|
1931
|
-
res.json({ pool, quota });
|
|
1932
|
-
});
|
|
1933
1969
|
|
|
1934
|
-
// POST /api/auth/pool/rotate - Rotate to next available account
|
|
1935
|
-
app.post('/api/auth/pool/rotate', (req, res) => {
|
|
1936
|
-
const provider = req.body.provider || 'google';
|
|
1937
|
-
const pool = buildAccountPool(provider);
|
|
1938
|
-
|
|
1939
1970
|
if (pool.accounts.length === 0) {
|
|
1940
|
-
return
|
|
1971
|
+
return { success: false, error: 'No accounts in pool' };
|
|
1941
1972
|
}
|
|
1942
|
-
|
|
1973
|
+
|
|
1943
1974
|
const now = Date.now();
|
|
1944
1975
|
const available = pool.accounts.filter(acc =>
|
|
1945
1976
|
acc.status === 'ready' || (acc.status === 'cooldown' && acc.cooldownUntil && acc.cooldownUntil < now)
|
|
1946
1977
|
);
|
|
1947
|
-
|
|
1978
|
+
|
|
1948
1979
|
if (available.length === 0) {
|
|
1949
|
-
return
|
|
1980
|
+
return { success: false, error: 'No available accounts (all in cooldown or expired)' };
|
|
1950
1981
|
}
|
|
1951
|
-
|
|
1982
|
+
|
|
1952
1983
|
// Pick least recently used
|
|
1953
1984
|
const next = available.sort((a, b) => a.lastUsed - b.lastUsed)[0];
|
|
1954
1985
|
const previousActive = pool.activeAccount;
|
|
1955
|
-
|
|
1986
|
+
|
|
1956
1987
|
// Activate the new account
|
|
1957
1988
|
const activePlugin = getActiveGooglePlugin();
|
|
1958
1989
|
const namespace = provider === 'google'
|
|
1959
1990
|
? (activePlugin === 'antigravity' ? 'google.antigravity' : 'google.gemini')
|
|
1960
1991
|
: provider;
|
|
1961
|
-
|
|
1992
|
+
|
|
1962
1993
|
const profilePath = path.join(AUTH_PROFILES_DIR, namespace, `${next.name}.json`);
|
|
1963
1994
|
if (!fs.existsSync(profilePath)) {
|
|
1964
|
-
return
|
|
1995
|
+
return { success: false, error: 'Profile file not found' };
|
|
1965
1996
|
}
|
|
1966
|
-
|
|
1997
|
+
|
|
1967
1998
|
const profileData = JSON.parse(fs.readFileSync(profilePath, 'utf8'));
|
|
1968
|
-
|
|
1999
|
+
|
|
1969
2000
|
// Update auth.json
|
|
1970
2001
|
const authCfg = loadAuthConfig() || {};
|
|
1971
2002
|
authCfg[provider] = profileData;
|
|
1972
2003
|
if (provider === 'google') {
|
|
1973
2004
|
authCfg[namespace] = profileData;
|
|
1974
2005
|
}
|
|
1975
|
-
|
|
2006
|
+
|
|
1976
2007
|
const cp = getConfigPath();
|
|
1977
2008
|
const ap = path.join(path.dirname(cp), 'auth.json');
|
|
1978
2009
|
atomicWriteFileSync(ap, JSON.stringify(authCfg, null, 2));
|
|
1979
|
-
|
|
2010
|
+
|
|
1980
2011
|
// Update studio config
|
|
1981
2012
|
const studio = loadStudioConfig();
|
|
1982
2013
|
if (!studio.activeProfiles) studio.activeProfiles = {};
|
|
1983
2014
|
studio.activeProfiles[provider] = next.name;
|
|
1984
2015
|
saveStudioConfig(studio);
|
|
1985
|
-
|
|
2016
|
+
|
|
1986
2017
|
// Update metadata
|
|
1987
2018
|
const metadata = loadPoolMetadata();
|
|
1988
2019
|
if (!metadata[namespace]) metadata[namespace] = {};
|
|
@@ -1992,19 +2023,46 @@ app.post('/api/auth/pool/rotate', (req, res) => {
|
|
|
1992
2023
|
usageCount: (metadata[namespace][next.name]?.usageCount || 0) + 1
|
|
1993
2024
|
};
|
|
1994
2025
|
|
|
2026
|
+
// Unmark exhaustion if we successfully rotated
|
|
2027
|
+
if (metadata._quota?.[namespace]?.exhausted) {
|
|
2028
|
+
delete metadata._quota[namespace].exhausted;
|
|
2029
|
+
}
|
|
2030
|
+
|
|
1995
2031
|
if (!metadata._quota) metadata._quota = {};
|
|
1996
2032
|
if (!metadata._quota[namespace]) metadata._quota[namespace] = {};
|
|
1997
2033
|
const today = new Date().toISOString().split('T')[0];
|
|
1998
2034
|
metadata._quota[namespace][today] = (metadata._quota[namespace][today] || 0) + 1;
|
|
2035
|
+
metadata._quota[namespace].lastRotation = now;
|
|
1999
2036
|
|
|
2000
2037
|
savePoolMetadata(metadata);
|
|
2001
|
-
|
|
2002
|
-
|
|
2038
|
+
|
|
2039
|
+
return {
|
|
2003
2040
|
success: true,
|
|
2004
2041
|
previousAccount: previousActive,
|
|
2005
2042
|
newAccount: next.name,
|
|
2006
|
-
reason:
|
|
2007
|
-
}
|
|
2043
|
+
reason: reason
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
// GET /api/auth/pool - Get account pool for Google (or specified provider)
|
|
2048
|
+
app.get('/api/auth/pool', (req, res) => {
|
|
2049
|
+
const provider = req.query.provider || 'google';
|
|
2050
|
+
syncAntigravityPool();
|
|
2051
|
+
const pool = buildAccountPool(provider);
|
|
2052
|
+
const quota = getPoolQuota(provider, pool);
|
|
2053
|
+
res.json({ pool, quota });
|
|
2054
|
+
});
|
|
2055
|
+
|
|
2056
|
+
// POST /api/auth/pool/rotate - Rotate to next available account
|
|
2057
|
+
app.post('/api/auth/pool/rotate', (req, res) => {
|
|
2058
|
+
const provider = req.body.provider || 'google';
|
|
2059
|
+
const result = rotateAccount(provider, 'manual_rotation');
|
|
2060
|
+
|
|
2061
|
+
if (!result.success) {
|
|
2062
|
+
return res.status(400).json(result);
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
res.json(result);
|
|
2008
2066
|
});
|
|
2009
2067
|
|
|
2010
2068
|
// POST /api/auth/pool/limit - Set daily quota limit
|
|
@@ -2759,7 +2817,18 @@ app.post('/api/presets/:id/apply', (req, res) => {
|
|
|
2759
2817
|
});
|
|
2760
2818
|
|
|
2761
2819
|
// Start watcher on server start
|
|
2762
|
-
|
|
2763
|
-
|
|
2820
|
+
if (require.main === module) {
|
|
2821
|
+
setupLogWatcher();
|
|
2822
|
+
importExistingAuth();
|
|
2823
|
+
app.listen(PORT, () => console.log(`Server running at http://localhost:${PORT}`));
|
|
2824
|
+
}
|
|
2764
2825
|
|
|
2765
|
-
|
|
2826
|
+
module.exports = {
|
|
2827
|
+
rotateAccount,
|
|
2828
|
+
processLogLine,
|
|
2829
|
+
loadPoolMetadata,
|
|
2830
|
+
savePoolMetadata,
|
|
2831
|
+
loadStudioConfig,
|
|
2832
|
+
saveStudioConfig,
|
|
2833
|
+
buildAccountPool
|
|
2834
|
+
};
|