// P4: bus-only native Console operations. No upstream credential is persisted here. import { randomBytes } from 'node:crypto'; import { isDeepStrictEqual } from 'node:util'; import { sha256, refuse } from '../../model-proxy/src/common.mjs'; import { PROXY_PAGE_CODES } from './proxy-page-vocabulary.mjs'; import { ZAI_URL, MODEL_ID } from '../../plugins/model-provider/cliproxy/index.mjs'; export { PROXY_PAGE_VERBS, PROXY_PAGE_CODES } from './proxy-page-vocabulary.mjs'; const object = x => x !== null && typeof x === 'object' && !Array.isArray(x); function fields(args, names) { if (!object(args) || Object.keys(args).length !== names.length || names.some(k => !Object.hasOwn(args, k))) refuse('invalid-request'); } const priorityOk = n => Number.isInteger(n) && n >= -1000 && n <= 1000; const identifier = x => typeof x === 'string' && x.length > 0 && x.length <= 512; const stateOk = x => typeof x === 'string' && /^[a-zA-Z0-9_-]{1,512}$/.test(x); const keyId = value => sha256(value).slice(0, 12); const providerName = provider => ({ claude: 'Claude', codex: 'Codex', 'z.ai': 'z.ai', other: 'Other' })[provider]; // Credential labels/emails and group names are arbitrary upstream strings. A stable // generated display name provides identification without reflecting a misplaced token. function view(id, provider, status, priority) { return { accountId: keyId(id), provider, displayName: `${providerName(provider)} account ${keyId(id)}`, status, priority: priorityOk(priority) ? priority : 0 }; } export class ProxyPage { #client; #tag; #records; #lock; #org; #dirty = false; #now; #timeout; #onChange; #pending = new Map(); #writes = Promise.resolve(); constructor({ client, instanceTag, keyRecords = () => [], now = Date.now, loginTimeoutSeconds = 300, onChange = () => {}, lock = null, org = null }) { if (!client || !/^[a-zA-Z0-9_-]{1,64}$/.test(instanceTag) || typeof keyRecords !== 'function' || typeof now !== 'function' || typeof onChange !== 'function' || !Number.isSafeInteger(loginTimeoutSeconds) || loginTimeoutSeconds <= 0) throw new TypeError('invalid proxy page settings'); this.#lock = lock; this.#org = org; this.#client = client; this.#tag = instanceTag; this.#records = keyRecords; this.#now = now; this.#timeout = loginTimeoutSeconds * 1000; this.#onChange = onChange; } // Serializes read-modify-write provider config within the bus. P2 owns the // independent client-key list. Store's lock admits only one bus per data root. #write(operation) { const next = this.#writes.then(() => { const run = async () => { this.#dirty = false; this.#onChange('begin'); try { const result = await operation(); if (this.#dirty) this.#lock.confirm(); return result; } finally { this.#onChange('end'); } }; return this.#lock ? this.#lock.run(run) : run(); }); this.#writes = next.catch(() => {}); return next; } #beforeMutation(op) { if (this.#lock) { this.#lock.fence({ org: this.#org, op }); this.#dirty = true; } } async #accounts() { const response = await this.#client.getCredentials(); const groups = await this.#client.getUpstreamGroups(); if (!Array.isArray(response?.files) || !Array.isArray(groups)) refuse('proxy-down'); const rows = []; for (const row of response.files) { if (!identifier(row?.id) || !identifier(row?.name)) continue; const provider = ['claude', 'codex'].includes(row.provider) ? row.provider : 'other'; const status = row.disabled === true ? 'disabled' : row.unavailable === true ? 'unavailable' : ['ready', 'active', 'error', 'disabled', 'unavailable'].includes(row.status) ? row.status : 'unknown'; rows.push({ ...view(row.id, provider, status, row.priority), source: 'oauth', name: row.name }); } for (const [groupIndex, group] of groups.entries()) { if (!Array.isArray(group?.keys)) continue; for (const [keyIndex, key] of group.keys.entries()) { // auth_index is generated by the proxy. Never fall back to hashing api-key. if (!identifier(key?.auth_index)) continue; const provider = group['base-url'] === ZAI_URL ? 'z.ai' : 'other'; const status = key.disabled === true || group.disabled === true ? 'disabled' : 'active'; const configured = group.models ?? []; if (!Array.isArray(configured) || configured.length > 32 || configured.some(m => !m || typeof (m.alias || m.name) !== 'string' || !MODEL_ID.test(m.alias || m.name))) refuse('proxy-down'); rows.push({ ...view(key.auth_index, provider, status, key.priority ?? group.priority), source: 'apikey', groupIndex, keyIndex, models: configured.map(m => m.alias || m.name) }); } } return { rows, groups }; } #public(row) { const { accountId, provider, displayName, status, priority } = row; return { accountId, provider, displayName, status, priority, ...(row.source === 'apikey' && provider === 'z.ai' ? { models: row.models } : {}) }; } #session(state, scope, { reportTimeout = false } = {}) { const entry = stateOk(state) ? this.#pending.get(state) : null; if (!entry || entry.business !== scope.business || entry.human !== scope.human) refuse('login-state-unknown'); if (entry.expires <= this.#now()) { this.#pending.delete(state); if (reportTimeout) return null; refuse('login-state-unknown'); } return entry; } async request(verb, args, scope) { try { return await this.#request(verb, args, scope); } catch (error) { // A client or fixture may put request values in its errors. Only locally // enumerated refusal codes cross the broker, never a raw exception/body. try { this.#lock?.assertCertain(); } catch { refuse('proxy-uncertain'); } refuse(PROXY_PAGE_CODES.includes(error.code) ? error.code : 'proxy-down'); } } async #request(verb, args, scope) { if (verb === 'proxy.accounts') { fields(args, []); return { accounts: (await this.#accounts()).rows.map(row => this.#public(row)) }; } if (verb === 'proxy.login.start') { fields(args, ['provider']); if (!['claude', 'codex'].includes(args.provider)) refuse('provider-not-allowed'); for (const [state, value] of this.#pending) if (value.expires <= this.#now()) this.#pending.delete(state); if (this.#pending.size >= 128) refuse('proxy-down'); const answer = await (this.#lock ? this.#write(() => this.#client.oauthStart(args.provider)) : this.#client.oauthStart(args.provider)); let url; try { url = new URL(answer.url); } catch { refuse('proxy-down'); } const host = args.provider === 'claude' ? 'claude.ai' : 'auth.openai.com'; if (url.protocol !== 'https:' || url.hostname !== host || url.username || url.password || url.port || url.href.length > 8192 || !stateOk(answer.state) || url.searchParams.getAll('state').length !== 1 || url.searchParams.get('state') !== answer.state || this.#pending.has(answer.state)) refuse('proxy-down'); this.#pending.set(answer.state, { business: scope.business, human: scope.human, expires: this.#now() + this.#timeout }); return { url: url.href, state: answer.state }; } if (verb === 'proxy.login.submit') { fields(args, ['redirect_url']); let url; try { if (typeof args.redirect_url !== 'string' || args.redirect_url.length > 8192) throw Error(); url = new URL(args.redirect_url); if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.hash || url.searchParams.getAll('state').length !== 1) throw Error(); } catch { refuse('callback-url-invalid'); } const state = url.searchParams.get('state'); this.#session(state, scope); await (this.#lock ? this.#write(async () => { this.#beforeMutation('proxy.login.submit'); await this.#client.oauthCallback(args.redirect_url); const status = await this.#client.oauthStatus(state); if (!['wait', 'ok', 'error'].includes(status?.status)) refuse('proxy-down'); }) : this.#client.oauthCallback(args.redirect_url)); // Callback acceptance is explicitly not proof of credential persistence. return { status: 'wait' }; } if (verb === 'proxy.login.status') { fields(args, ['state']); if (!this.#session(args.state, scope, { reportTimeout: true })) return { status: 'error', reason: 'login-timeout' }; const answer = await (this.#lock ? this.#write(async () => { const result = await this.#client.oauthStatus(args.state); if (result?.status === 'ok') this.#beforeMutation('proxy.login.complete'); return result; }) : this.#client.oauthStatus(args.state)); if (answer?.status === 'wait') return { status: 'wait' }; if (!['ok', 'error'].includes(answer?.status)) refuse('proxy-down'); this.#pending.delete(args.state); if (answer.status === 'ok') this.#onChange('invalidate'); return answer.status === 'ok' ? { status: 'ok' } : { status: 'error', reason: 'login-failed' }; } if (verb === 'proxy.login.cancel') { fields(args, ['state']); this.#session(args.state, scope); await (this.#lock ? this.#write(() => this.#client.oauthCancel(args.state)) : this.#client.oauthCancel(args.state)); this.#pending.delete(args.state); return { status: 'cancelled' }; } if (verb === 'proxy.apikey.add') { fields(args, ['provider', 'key']); if (args.provider !== 'z.ai') refuse('provider-not-allowed'); if (typeof args.key !== 'string' || !/^[\x21-\x7e]{1,4096}$/.test(args.key)) refuse('apikey-invalid'); return this.#write(async () => { const groups = await this.#client.getUpstreamGroups(); if (!Array.isArray(groups)) refuse('proxy-down'); const group = { name: `mosaic-zai-${randomBytes(6).toString('hex')}`, 'base-url': ZAI_URL, priority: 0, keys: [{ 'api-key': args.key }] }; if (this.#lock && !isDeepStrictEqual(await this.#client.getUpstreamGroups(), groups)) refuse('proxy-down'); this.#beforeMutation('proxy.apikey.add'); await this.#client.putUpstreamGroups([...groups, group]); if (this.#lock && !(await this.#client.getUpstreamGroups()).some(g => g.name === group.name && g['base-url'] === group['base-url'] && g.keys?.some(k => k['api-key'] === args.key))) refuse('proxy-down'); return { added: true }; }); } if (verb === 'proxy.group.models.set') { fields(args, ['accountId', 'models']); if (typeof args.accountId !== 'string' || !/^[a-f0-9]{12}$/.test(args.accountId) || !Array.isArray(args.models) || args.models.length > 32 || args.models.some(m => typeof m !== 'string' || !MODEL_ID.test(m)) || new Set(args.models).size !== args.models.length) refuse('models-invalid'); return this.#write(async () => { const { rows, groups } = await this.#accounts(); const original = this.#lock ? structuredClone(groups) : null; const matching = new Set(rows.filter(r => r.source === 'apikey' && (r.accountId === args.accountId || (typeof groups[r.groupIndex]?.name === 'string' && keyId(groups[r.groupIndex].name) === args.accountId))).map(r => r.groupIndex)); if (matching.size !== 1) refuse('models-invalid'); const index = [...matching][0]; const existing = groups[index].models ?? []; const byId = new Map(); for (const entry of existing) { const display = entry.alias || entry.name; if (byId.has(display)) refuse('models-ambiguous'); byId.set(display, entry); } const desired = args.models.map(name => structuredClone(byId.get(name) ?? { name })); groups[index].models = desired; if (this.#lock && !isDeepStrictEqual(await this.#client.getUpstreamGroups(), original)) refuse('proxy-down'); this.#beforeMutation('proxy.group.models.set'); try { await this.#client.putUpstreamGroups(groups); } catch (error) { refuse(error.code === 'proxy-client-refused' || error.code === 'proxy-client-redirect' ? 'models-write-refused' : 'proxy-down'); } const after = await this.#accounts(); const row = after.rows.find(r => r.source === 'apikey' && (r.accountId === args.accountId || (typeof after.groups[r.groupIndex]?.name === 'string' && keyId(after.groups[r.groupIndex].name) === args.accountId))); const actual = row && after.groups[row.groupIndex].models; const pair = entry => ({ name: entry.name, ...(entry.alias !== undefined ? { alias: entry.alias } : {}) }); if (!row || JSON.stringify(actual?.map(pair)) !== JSON.stringify(desired.map(pair))) refuse('models-unconfirmed'); return { accountId: args.accountId, models: args.models }; }); } if (verb === 'proxy.priority.set') { fields(args, ['accountId', 'priority']); if (typeof args.accountId !== 'string' || !/^[a-f0-9]{12}$/.test(args.accountId) || !priorityOk(args.priority)) refuse('priority-invalid'); return this.#write(async () => { const { rows, groups } = await this.#accounts(); const original = this.#lock ? structuredClone(groups) : null; const matches = rows.filter(row => row.accountId === args.accountId); if (matches.length !== 1) refuse('priority-invalid'); const row = matches[0]; if (this.#lock && !isDeepStrictEqual(await this.#client.getUpstreamGroups(), original)) refuse('proxy-down'); this.#beforeMutation('proxy.priority.set'); try { if (row.source === 'oauth') await this.#client.patchCredential(row.name, args.priority); else { groups[row.groupIndex].keys[row.keyIndex].priority = args.priority; await this.#client.putUpstreamGroups(groups); } } catch (error) { refuse(error.code === 'proxy-client-refused' || error.code === 'proxy-client-redirect' ? 'priority-write-refused' : 'proxy-down'); } const after = (await this.#accounts()).rows.find(r => r.accountId === args.accountId); if (!after || after.priority !== args.priority) refuse('priority-unconfirmed'); return { accountId: row.accountId, priority: args.priority }; }); } if (verb === 'proxy.keys') { fields(args, []); const values = await this.#client.getKeys(); if (!Array.isArray(values) || values.some(k => typeof k !== 'string')) refuse('proxy-down'); const records = await this.#records(scope.business); const prefix = this.#org === null ? `msk-${this.#tag}-` : `mso-${this.#tag}-${this.#org}-`; return { keys: values.map(value => { const id = keyId(value), suffix = value.startsWith(prefix) ? value.slice(prefix.length) : null; const sentinel = suffix !== null && (this.#org === null ? suffix.startsWith('sentinel-') : /^sentinel-[A-Za-z0-9_-]{43}$/.test(suffix)); const managed = suffix !== null && (this.#org === null || /^key-[A-Za-z0-9_-]{43}$/.test(suffix)); const record = records.find(r => r.keyId === id); return { keyId: id, management: sentinel ? 'sentinel' : managed ? 'stack-managed' : 'not stack-managed', agent: record?.agent ?? null, issuedAt: record?.issuedAt ?? null, state: record?.state ?? null }; }) }; } refuse('invalid-request'); } }