diff --git a/packages/ui/src/assets/provider-logos/cline.svg b/packages/ui/src/assets/provider-logos/cline.svg
new file mode 100644
index 00000000..ebda2239
--- /dev/null
+++ b/packages/ui/src/assets/provider-logos/cline.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/hooks/useProviderLogo.ts b/packages/ui/src/hooks/useProviderLogo.ts
index 80ded66d..c2342a3f 100644
--- a/packages/ui/src/hooks/useProviderLogo.ts
+++ b/packages/ui/src/hooks/useProviderLogo.ts
@@ -20,6 +20,7 @@ const LOGO_ALIAS = new Map([
['codex', 'openai'],
['chatgpt', 'openai'],
['claude', 'anthropic'],
+ ['cline-pass', 'cline'],
['gemini', 'google'],
['evroc-ai', 'evroc'],
['evrocai', 'evroc'],
diff --git a/packages/ui/src/lib/quota/providers/index.ts b/packages/ui/src/lib/quota/providers/index.ts
index 5616135d..c626256b 100644
--- a/packages/ui/src/lib/quota/providers/index.ts
+++ b/packages/ui/src/lib/quota/providers/index.ts
@@ -7,6 +7,7 @@ export interface QuotaProviderMeta {
export const QUOTA_PROVIDERS: QuotaProviderMeta[] = [
{ id: 'claude', name: 'Claude' },
+ { id: 'cline-pass', name: 'ClinePass' },
{ id: 'codex', name: 'Codex' },
{ id: 'cursor', name: 'Cursor' },
{ id: 'github-copilot', name: 'GitHub Copilot' },
diff --git a/packages/ui/src/types/quota.ts b/packages/ui/src/types/quota.ts
index aa55d52e..c05a6dd9 100644
--- a/packages/ui/src/types/quota.ts
+++ b/packages/ui/src/types/quota.ts
@@ -3,6 +3,7 @@ export type QuotaProviderId =
| 'codex'
| 'cursor'
| 'claude'
+ | 'cline-pass'
| 'github-copilot'
| 'github-copilot-addon'
| 'google'
diff --git a/packages/vscode/src/quotaProviders.test.ts b/packages/vscode/src/quotaProviders.test.ts
index 0ec87390..90dc7af6 100644
--- a/packages/vscode/src/quotaProviders.test.ts
+++ b/packages/vscode/src/quotaProviders.test.ts
@@ -15,6 +15,7 @@ const ORIGINAL_FS = { ...fs };
const AUTH = JSON.stringify({
openai: { access: 'test-token' },
crof: { key: 'test-token' },
+ 'cline-pass': { key: 'test-token' },
neuralwatt: { key: 'test-token' },
'opencode-go': { key: 'test-token' },
openrouter: { key: 'test-token' },
@@ -27,7 +28,7 @@ const AUTH = JSON.stringify({
((fs as unknown) as { existsSync: () => boolean }).existsSync = () => true;
((fs as unknown) as { readFileSync: () => string }).readFileSync = () => AUTH;
-import { fetchHyperQuota, fetchOllamaCloudQuota, fetchQuotaForProvider } from './quotaProviders';
+import { fetchClinePassQuota, fetchHyperQuota, fetchOllamaCloudQuota, fetchQuotaForProvider } from './quotaProviders';
import { validateCredential } from './quotaCredentials';
type MockResponseInit = { ok?: boolean; status?: number };
@@ -336,6 +337,121 @@ describe('Crof quota provider (VS Code parity)', () => {
});
});
+describe('ClinePass quota provider (VS Code parity)', () => {
+ // Live-verified response shape of
+ // GET https://api.cline.bot/api/v1/users/me/plan/usage-limits
+ const documentedPayload = {
+ data: {
+ limits: [
+ { type: 'five_hour', percentUsed: 43, resetsAt: '2026-09-08T17:00:44.598174595Z' },
+ { type: 'weekly', percentUsed: 17, resetsAt: '2026-09-13T17:00:44.598174595Z' },
+ { type: 'monthly', percentUsed: 8, resetsAt: '2026-10-01T00:00:00Z' },
+ ],
+ },
+ success: true,
+ };
+
+ test('maps documented limit kinds to 5h/weekly/monthly windows', async () => {
+ stubFetchReturning(() => Promise.resolve(mockResponse(documentedPayload)));
+
+ const result = await fetchQuotaForProvider('cline-pass');
+
+ assert.equal(result.ok, true);
+ assert.equal(result.providerId, 'cline-pass');
+ assert.equal(result.usage?.windows['5h']?.usedPercent, 43);
+ assert.equal(result.usage?.windows['5h']?.windowSeconds, 18_000);
+ assert.equal(result.usage?.windows['5h']?.resetAt, Date.parse('2026-09-08T17:00:44.598174595Z'));
+ assert.equal(result.usage?.windows.weekly?.usedPercent, 17);
+ assert.equal(result.usage?.windows.weekly?.windowSeconds, 604_800);
+ assert.equal(result.usage?.windows.monthly?.usedPercent, 8);
+ assert.equal(result.usage?.windows.monthly?.windowSeconds, null);
+ });
+
+ test('ignores unknown limit types and rejects responses without quota data', async () => {
+ stubFetchReturning(() => Promise.resolve(mockResponse({ data: { limits: [{ type: 'quarterly', percentUsed: 5 }] } })));
+
+ const result = await fetchQuotaForProvider('cline-pass');
+
+ assert.equal(result.ok, false);
+ assert.equal(result.configured, true);
+ assert.equal(result.usage, null);
+ assert.equal(result.error, 'No quota data in response');
+ });
+
+ test('maps 401 to session-expired with ClinePass branding', async () => {
+ stubFetchFailing(async () => ({}), { ok: false, status: 401 });
+
+ const result = await fetchQuotaForProvider('cline-pass');
+
+ assert.equal(result.ok, false);
+ assert.equal(result.configured, true);
+ assert.equal(result.error, 'Session expired — please re-authenticate with ClinePass');
+ });
+
+ test('reports invalid-response on JSON parse failure', async () => {
+ stubFetchFailing(async () => { throw new SyntaxError('Unexpected token'); }, { ok: true, status: 200 });
+
+ const result = await fetchQuotaForProvider('cline-pass');
+
+ assert.equal(result.ok, false);
+ assert.equal(result.error, 'Invalid response from provider');
+ });
+
+ const readAuth = () => ({ 'cline-pass': { key: 'test-token' } });
+
+ for (const limit of [
+ { type: 'constructor', percentUsed: 5 }, { type: 'toString', percentUsed: 5 },
+ { type: '__proto__', percentUsed: 5 }, { type: 'weekly', percentUsed: '' },
+ { type: 'weekly', percentUsed: ' ' }, { type: 'weekly', percentUsed: true },
+ { type: 'weekly', percentUsed: null }, null,
+ ]) {
+ test(`skips malformed windows independently: ${JSON.stringify(limit)}`, async () => {
+ const invalid = await fetchClinePassQuota({ readAuth, fetchImpl: async () => Response.json({ data: { limits: [limit] } }) });
+ assert.equal(invalid.ok, false);
+ assert.equal(invalid.usage, null);
+ const mixed = await fetchClinePassQuota({ readAuth, fetchImpl: async () => Response.json({ data: { limits: [limit, { type: 'monthly', percentUsed: 8 }] } }) });
+ assert.equal(mixed.ok, true);
+ assert.ok(mixed.usage);
+ assert.deepEqual(Object.keys(mixed.usage.windows), ['monthly']);
+ });
+ }
+
+ for (const percentUsed of [0, '0', '51']) {
+ test(`accepts percentage ${JSON.stringify(percentUsed)}`, async () => {
+ const result = await fetchClinePassQuota({ readAuth, fetchImpl: async () => Response.json({ data: { limits: [{ type: 'weekly', percentUsed }] } }) });
+ assert.equal(result.ok, true);
+ assert.equal(result.usage?.windows.weekly?.usedPercent, Number(percentUsed));
+ });
+ }
+
+ for (const entry of [{ key: '' }, { key: ' ' }, { key: 42 }, {}]) {
+ test(`falls back to a usable token: ${JSON.stringify(entry)}`, async () => {
+ const result = await fetchClinePassQuota({
+ readAuth: () => ({ 'cline-pass': { ...entry, token: 'test-token' } }),
+ fetchImpl: async (_url, options) => {
+ assert.equal(new Headers(options.headers).get('Authorization'), 'Bearer test-token');
+ return Response.json(documentedPayload);
+ },
+ });
+ assert.equal(result.ok, true);
+ });
+ }
+
+ test('does not request usage without usable credentials', async () => {
+ const result = await fetchClinePassQuota({ readAuth: () => ({ 'cline-pass': { key: 42 } }), fetchImpl: async () => { throw new Error('Unexpected fetch'); } });
+ assert.equal(result.configured, false);
+ assert.equal(result.error, 'Not configured');
+ });
+
+ test('recognizes the timeout exception from AbortSignal.timeout', async () => {
+ const result = await fetchClinePassQuota({ readAuth, fetchImpl: async () => { throw new DOMException('Timed out', 'TimeoutError'); } });
+ assert.equal(result.ok, false);
+ assert.equal(result.configured, true);
+ assert.equal(result.usage, null);
+ assert.equal(result.error, 'Request timed out');
+ });
+});
+
describe('Codex quota provider (VS Code parity)', () => {
test('coalesces concurrent refreshes for the same provider', async () => {
let resolveResponse: ((response: Response) => void) | undefined;
diff --git a/packages/vscode/src/quotaProviders.ts b/packages/vscode/src/quotaProviders.ts
index 90951d93..045c9b9e 100644
--- a/packages/vscode/src/quotaProviders.ts
+++ b/packages/vscode/src/quotaProviders.ts
@@ -146,6 +146,11 @@ type CrofPayload = {
credits?: number | string;
};
+type ClineWindowKind = {
+ key: string;
+ windowSeconds: number | null;
+};
+
type DeepseekPayload = {
is_available?: boolean;
balance_infos?: Array<{
@@ -844,6 +849,11 @@ export const listConfiguredQuotaProviders = () => {
configured.add('crof');
}
+ const clineAuth = normalizeAuthEntry(getAuthEntry(auth, ['cline-pass']));
+ if (clineAuth && (asNonEmptyString(clineAuth.key) || asNonEmptyString(clineAuth.token))) {
+ configured.add('cline-pass');
+ }
+
const neuralwattAuth = normalizeAuthEntry(getAuthEntry(auth, ['neuralwatt']));
if (neuralwattAuth && ((neuralwattAuth as Record).key || (neuralwattAuth as Record).token)) {
configured.add('neuralwatt');
@@ -2745,6 +2755,118 @@ const fetchCrofQuota = async (): Promise => {
}
};
+const CLINE_PASS_USAGE_URL = 'https://api.cline.bot/api/v1/users/me/plan/usage-limits';
+
+// Cline reports a rolling five-hour window, a rolling weekly window, and a
+// calendar-month limit. Each window carries its duration so consumers can rank
+// limits by how soon they run out; the calendar month has no fixed duration.
+const CLINE_WINDOW_KINDS = new Map([
+ ['five_hour', { key: '5h', windowSeconds: 5 * 60 * 60 }],
+ ['weekly', { key: 'weekly', windowSeconds: 7 * 24 * 60 * 60 }],
+ ['monthly', { key: 'monthly', windowSeconds: null }],
+]);
+
+type ClineQuotaDependencies = {
+ readAuth?: () => AuthFile;
+ fetchImpl?: (url: string, options: RequestInit) => Promise;
+};
+
+export const fetchClinePassQuota = async ({ readAuth = readAuthFile, fetchImpl = fetch }: ClineQuotaDependencies = {}): Promise => {
+ const auth = readAuth();
+ const entry = normalizeAuthEntry(getAuthEntry(auth, ['cline-pass']));
+ const apiKey = asNonEmptyString(entry?.key) ?? asNonEmptyString(entry?.token);
+
+ if (!apiKey) {
+ return buildResult({
+ providerId: 'cline-pass',
+ providerName: 'ClinePass',
+ ok: false,
+ configured: false,
+ error: 'Not configured',
+ });
+ }
+
+ const timeoutSignal = AbortSignal.timeout(15_000);
+
+ try {
+ const response = await fetchImpl(CLINE_PASS_USAGE_URL, {
+ method: 'GET',
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ 'Accept-Encoding': 'identity',
+ },
+ signal: timeoutSignal,
+ });
+
+ if (!response.ok) {
+ return buildResult({
+ providerId: 'cline-pass',
+ providerName: 'ClinePass',
+ ok: false,
+ configured: true,
+ error: response.status === 401
+ ? 'Session expired — please re-authenticate with ClinePass'
+ : `API error: ${response.status}`,
+ });
+ }
+
+ const payload = asObject(await response.json());
+ const data = asObject(payload?.data);
+ const limits = Array.isArray(data?.limits) ? data.limits : [];
+
+ const windows: Record = {};
+ for (const item of limits) {
+ const limit = asObject(item);
+ if (!limit) continue;
+ const limitType = asNonEmptyString(limit.type);
+ const kind = limitType === null ? undefined : CLINE_WINDOW_KINDS.get(limitType);
+ if (!kind) continue;
+ const usedPercent = toNumber(asNonEmptyString(limit.percentUsed)
+ ?? (Number.isFinite(limit.percentUsed) ? limit.percentUsed : null));
+ if (usedPercent === null) continue;
+ windows[kind.key] = toUsageWindow({
+ usedPercent,
+ windowSeconds: kind.windowSeconds,
+ resetAt: toTimestamp(limit.resetsAt),
+ });
+ }
+
+ if (Object.keys(windows).length === 0) {
+ return buildResult({
+ providerId: 'cline-pass',
+ providerName: 'ClinePass',
+ ok: false,
+ configured: true,
+ error: 'No quota data in response',
+ });
+ }
+
+ return buildResult({
+ providerId: 'cline-pass',
+ providerName: 'ClinePass',
+ ok: true,
+ configured: true,
+ usage: { windows },
+ });
+ } catch (error) {
+ const isTimeout = error instanceof DOMException && (
+ error.name === 'TimeoutError' || (error.name === 'AbortError' && timeoutSignal.aborted)
+ );
+ const isParseError = error instanceof SyntaxError;
+ return buildResult({
+ providerId: 'cline-pass',
+ providerName: 'ClinePass',
+ ok: false,
+ configured: true,
+ error: isTimeout
+ ? 'Request timed out'
+ : isParseError
+ ? 'Invalid response from provider'
+ : (error instanceof Error ? error.message : 'Request failed'),
+ });
+ }
+};
+
const DEEPSEEK_QUOTA_URL = 'https://api.deepseek.com/user/balance';
const fetchDeepseekQuota = async (): Promise => {
@@ -3068,6 +3190,8 @@ const fetchQuotaForProviderUncoalesced = async (providerId: string): Promise {
+ const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
+ return asNonEmptyString(entry?.key) ?? asNonEmptyString(entry?.token);
+};
+
+export const isConfigured = (auth = readAuthFile()) => Boolean(getApiKey(auth));
+
+export const fetchQuota = async ({ readAuth = readAuthFile, fetchImpl = fetch } = {}) => {
+ const apiKey = getApiKey(readAuth());
+
+ if (!apiKey) {
+ return buildResult({
+ providerId,
+ providerName,
+ ok: false,
+ configured: false,
+ error: 'Not configured'
+ });
+ }
+
+ const timeoutSignal = AbortSignal.timeout(15_000);
+
+ try {
+ const response = await fetchImpl(CLINE_USAGE_URL, {
+ method: 'GET',
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ 'Accept-Encoding': 'identity'
+ },
+ signal: timeoutSignal
+ });
+
+ if (!response.ok) {
+ return buildResult({
+ providerId,
+ providerName,
+ ok: false,
+ configured: true,
+ error: response.status === 401
+ ? 'Session expired — please re-authenticate with ClinePass'
+ : `API error: ${response.status}`
+ });
+ }
+
+ const payload = asObject(await response.json());
+ const data = asObject(payload?.data);
+ const limits = Array.isArray(data?.limits) ? data.limits : [];
+
+ const windows = {};
+ for (const item of limits) {
+ const limit = asObject(item);
+ if (!limit) continue;
+ const kind = WINDOW_KINDS.get(asNonEmptyString(limit.type));
+ if (!kind) continue;
+ const usedPercent = toNumber(asNonEmptyString(limit.percentUsed)
+ ?? (Number.isFinite(limit.percentUsed) ? limit.percentUsed : null));
+ if (usedPercent === null) continue;
+ windows[kind.key] = toUsageWindow({
+ usedPercent,
+ windowSeconds: kind.windowSeconds,
+ resetAt: toTimestamp(limit.resetsAt)
+ });
+ }
+
+ if (Object.keys(windows).length === 0) {
+ return buildResult({
+ providerId,
+ providerName,
+ ok: false,
+ configured: true,
+ error: 'No quota data in response'
+ });
+ }
+
+ return buildResult({
+ providerId,
+ providerName,
+ ok: true,
+ configured: true,
+ usage: { windows }
+ });
+ } catch (error) {
+ const isTimeout = error instanceof DOMException && (
+ error.name === 'TimeoutError' || (error.name === 'AbortError' && timeoutSignal.aborted)
+ );
+ const isParseError = error instanceof SyntaxError;
+ return buildResult({
+ providerId,
+ providerName,
+ ok: false,
+ configured: true,
+ error: isTimeout
+ ? 'Request timed out'
+ : isParseError
+ ? 'Invalid response from provider'
+ : (error instanceof Error ? error.message : 'Request failed')
+ });
+ }
+};
diff --git a/packages/web/server/lib/quota/providers/cline-pass.test.js b/packages/web/server/lib/quota/providers/cline-pass.test.js
new file mode 100644
index 00000000..d9551219
--- /dev/null
+++ b/packages/web/server/lib/quota/providers/cline-pass.test.js
@@ -0,0 +1,103 @@
+import { describe, expect, it } from 'vitest';
+import { fetchQuota, isConfigured } from './cline-pass.js';
+
+const readAuth = () => ({ 'cline-pass': { key: 'test-token' } });
+
+// Response shape verified by the contributor against the live ClinePass API.
+const documentedPayload = {
+ data: { limits: [
+ { type: 'five_hour', percentUsed: 43, resetsAt: '2026-09-08T17:00:44.598174595Z' },
+ { type: 'weekly', percentUsed: 17 },
+ { type: 'monthly', percentUsed: 8 },
+ ] },
+ success: true,
+};
+
+describe('ClinePass quota provider', () => {
+ it('maps the documented windows and sends credentials only in headers', async () => {
+ const result = await fetchQuota({ readAuth, fetchImpl: async (url, options) => {
+ expect(url).toBe('https://api.cline.bot/api/v1/users/me/plan/usage-limits');
+ expect(new Headers(options.headers).get('Authorization')).toBe('Bearer test-token');
+ expect(options.signal).toBeInstanceOf(AbortSignal);
+ return Response.json(documentedPayload);
+ } });
+ expect(result.ok).toBe(true);
+ expect(result.providerId).toBe('cline-pass');
+ expect(Object.keys(result.usage.windows)).toEqual(['5h', 'weekly', 'monthly']);
+ expect(result.usage.windows['5h'].usedPercent).toBe(43);
+ expect(result.usage.windows['5h'].remainingPercent).toBe(57);
+ expect(result.usage.windows['5h'].windowSeconds).toBe(18_000);
+ expect(result.usage.windows['5h'].resetAt).toBe(Date.parse('2026-09-08T17:00:44.598174595Z'));
+ expect(result.usage.windows.weekly.usedPercent).toBe(17);
+ expect(result.usage.windows.weekly.windowSeconds).toBe(604_800);
+ expect(result.usage.windows.monthly.usedPercent).toBe(8);
+ expect(result.usage.windows.monthly.windowSeconds).toBeNull();
+ expect(JSON.stringify(result)).not.toContain('test-token');
+ });
+
+ it.each([0, '0', '51'])('accepts finite percentage %s', async (percentUsed) => {
+ const result = await fetchQuota({ readAuth, fetchImpl: async () => Response.json({ data: { limits: [{ type: 'weekly', percentUsed }] } }) });
+ expect(result.ok).toBe(true);
+ expect(result.usage.windows.weekly.usedPercent).toBe(Number(percentUsed));
+ });
+
+ it.each([
+ { type: 'constructor', percentUsed: 5 }, { type: 'toString', percentUsed: 5 },
+ { type: '__proto__', percentUsed: 5 }, { type: 'quarterly', percentUsed: 5 },
+ { type: 'weekly', percentUsed: '' }, { type: 'weekly', percentUsed: ' ' },
+ { type: 'weekly', percentUsed: null }, { type: 'weekly', percentUsed: true },
+ { type: 'weekly', percentUsed: 'NaN' }, null,
+ ])('ignores malformed windows without discarding valid siblings: %j', async (limit) => {
+ const invalid = await fetchQuota({ readAuth, fetchImpl: async () => Response.json({ data: { limits: [limit] } }) });
+ expect(invalid.ok).toBe(false);
+ expect(invalid.configured).toBe(true);
+ expect(invalid.usage).toBeNull();
+ const mixed = await fetchQuota({ readAuth, fetchImpl: async () => Response.json({ data: { limits: [limit, { type: 'monthly', percentUsed: 8 }] } }) });
+ expect(mixed.ok).toBe(true);
+ expect(Object.keys(mixed.usage.windows)).toEqual(['monthly']);
+ });
+
+ it.each([null, [], {}, { data: null }, { data: { limits: [] } }])('rejects empty or malformed payload %j', async (payload) => {
+ const result = await fetchQuota({ readAuth, fetchImpl: async () => Response.json(payload) });
+ expect(result.ok).toBe(false);
+ expect(result.usage).toBeNull();
+ expect(result.error).toBe('No quota data in response');
+ });
+
+ it.each([{ key: '' }, { key: ' ' }, { key: 42 }, {}])('uses a usable token when the key is malformed: %j', async (entry) => {
+ const auth = { 'cline-pass': { ...entry, token: 'test-token' } };
+ expect(isConfigured(auth)).toBe(true);
+ const result = await fetchQuota({ readAuth: () => auth, fetchImpl: async (_url, options) => {
+ expect(new Headers(options.headers).get('Authorization')).toBe('Bearer test-token');
+ return Response.json(documentedPayload);
+ } });
+ expect(result.ok).toBe(true);
+ });
+
+ it.each([{}, { 'cline-pass': { key: '' } }, { 'cline-pass': { key: 42 } }])('does not fetch without usable credentials: %j', async (auth) => {
+ expect(isConfigured(auth)).toBe(false);
+ const result = await fetchQuota({ readAuth: () => auth, fetchImpl: async () => { throw new Error('Unexpected fetch'); } });
+ expect(result.configured).toBe(false);
+ expect(result.error).toBe('Not configured');
+ });
+
+ it.each([[401, 'Session expired — please re-authenticate with ClinePass'], [503, 'API error: 503']])('reports HTTP %s', async (status, error) => {
+ const result = await fetchQuota({ readAuth, fetchImpl: async () => new Response(null, { status }) });
+ expect(result.ok).toBe(false);
+ expect(result.usage).toBeNull();
+ expect(result.error).toBe(error);
+ });
+
+ it('reports invalid JSON', async () => {
+ const result = await fetchQuota({ readAuth, fetchImpl: async () => new Response('{') });
+ expect(result.error).toBe('Invalid response from provider');
+ });
+
+ it('recognizes the timeout exception from AbortSignal.timeout', async () => {
+ const result = await fetchQuota({ readAuth, fetchImpl: async () => { throw new DOMException('Timed out', 'TimeoutError'); } });
+ expect(result.ok).toBe(false);
+ expect(result.configured).toBe(true);
+ expect(result.usage).toBeNull();
+ expect(result.error).toBe('Request timed out');
+ });
+});
diff --git a/packages/web/server/lib/quota/providers/index.js b/packages/web/server/lib/quota/providers/index.js
index c9ef0050..1a330043 100644
--- a/packages/web/server/lib/quota/providers/index.js
+++ b/packages/web/server/lib/quota/providers/index.js
@@ -8,6 +8,7 @@
import { buildResult } from '../utils/index.js';
import * as claude from './claude/index.js';
+import * as clinePass from './cline-pass.js';
import * as codex from './codex.js';
import * as copilot from './copilot.js';
import * as crof from './crof.js';
@@ -37,6 +38,12 @@ const registry = {
isConfigured: claude.isConfigured,
fetchQuota: claude.fetchQuota
},
+ 'cline-pass': {
+ providerId: clinePass.providerId,
+ providerName: clinePass.providerName,
+ isConfigured: clinePass.isConfigured,
+ fetchQuota: clinePass.fetchQuota
+ },
codex: {
providerId: codex.providerId,
providerName: codex.providerName,