| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import { describe, test } from 'node:test'; |
| import assert from 'node:assert/strict'; |
| import { deferred, waitFor, withTimeout } from '@maka/core/test-only/async-primitives'; |
| import type { RuntimeHostConnectionCatalogSnapshot as ConnectionCatalogSnapshot } from '@maka/runtime-host/client'; |
| import { |
| createRuntimeHostReconnectingConnection, |
| RuntimeHostOperationError, |
| RuntimeHostRequestInterruptedError, |
| type ClientCapabilityProvider, |
| type RuntimeHostConnection, |
| } from '@maka/runtime-host/client'; |
| import { resolveConnectionModelCatalog } from '@maka/core/model-catalog'; |
| import { |
| createRuntimeHostOnboardingSurface, |
| projectProviders, |
| projectRuntimeHostModelChoices, |
| } from '../runtime-host-onboarding.js'; |
| import type { OnboardingOAuthInput } from '../pi-tui-contracts.js'; |
| |
| type StoredConnection = Omit<ConnectionCatalogSnapshot['connections'][number], 'catalogEntries'>; |
| |
| /** |
| * Fixtures describe what the Host stores; the Host resolves the catalog before |
| * projecting it, so these tests read the entries the same resolution produces. |
| */ |
| function catalog(connections: readonly StoredConnection[]): ConnectionCatalogSnapshot { |
| return { |
| revision: 1, |
| defaultTarget: null, |
| connections: connections.map((connection) => ({ |
| ...connection, |
| catalogEntries: resolveConnectionModelCatalog({ |
| ...connection, |
| defaultModel: '', |
| enabledModelIds: [...connection.enabledModelIds], |
| models: [...connection.models], |
| }), |
| })), |
| }; |
| } |
| |
| const live = { |
| connectionId: 'live-id', |
| revision: 1, |
| slug: 'openai', |
| name: 'OpenAI', |
| providerType: 'openai', |
| enabled: true, |
| enabledModelIds: ['gpt-5-mini'], |
| models: [{ id: 'gpt-5-mini', displayName: 'GPT-5 Mini' }], |
| } as const; |
| |
| const oauthConnectionIdentity = { |
| connectionId: 'codex-id', |
| slug: 'codex-subscription', |
| providerType: 'openai-codex', |
| } as const; |
| |
| function oauthProjection( |
| attemptId: string, |
| phase: 'awaiting_authorization' | 'authenticated' | 'committing' | 'cancelled', |
| ) { |
| return { attemptId, connection: oauthConnectionIdentity, phase }; |
| } |
| |
| function interruptedOAuthRequest(operation: 'oauth.login.start' | 'oauth.login.cancel') { |
| return new RuntimeHostRequestInterruptedError( |
| operation, |
| operation === 'oauth.login.start' ? 'command' : 'control', |
| 'dispatched', |
| 'connection_lost', |
| ); |
| } |
| |
| function oauthTestSurface( |
| attemptId: string, |
| request: (operation: string, input: unknown) => unknown | Promise<unknown>, |
| options: { readonly pollIntervalMs?: number; readonly onClose?: () => void } = {}, |
| ) { |
| const requests: string[] = []; |
| const connection = { |
| replaceClientCapabilities: async () => ({ |
| registrationId: 'oauth-registration', |
| revision: 1, |
| }), |
| request: async (operation: string, input: unknown) => { |
| requests.push(operation); |
| return request(operation, input); |
| }, |
| } as unknown as RuntimeHostConnection; |
| const surface = createRuntimeHostOnboardingSurface({} as RuntimeHostConnection, { |
| connectOAuth: async () => ({ |
| connection, |
| close: async () => options.onClose?.(), |
| }), |
| ...(options.pollIntervalMs === undefined ? {} : { pollIntervalMs: options.pollIntervalMs }), |
| createAttemptId: () => attemptId, |
| }); |
| return { requests, surface }; |
| } |
| |
| function loginWithOAuth( |
| surface: ReturnType<typeof oauthTestSurface>['surface'], |
| options: { |
| readonly signal?: AbortSignal; |
| readonly target?: OnboardingOAuthInput['target']; |
| } = {}, |
| ) { |
| return surface.loginOAuth!({ |
| target: options.target ?? { kind: 'create', providerType: 'openai-codex' }, |
| signal: options.signal ?? new AbortController().signal, |
| onPresentation: () => undefined, |
| }); |
| } |
| |
| /** A physical peer: capabilities belong to this connection, not its reconnect wrapper. */ |
| function oauthPhysicalConnection( |
| connectionId: string, |
| request: (operation: string, input: { attemptId: string }) => unknown | Promise<unknown>, |
| onRegister?: () => Promise<void>, |
| ) { |
| const closed = deferred<void>(); |
| const events: string[] = []; |
| let registered = false; |
| const connection = { |
| rootId: 'oauth-test-root', |
| hostEpoch: 'oauth-test-host', |
| connectionId, |
| selectedProtocol: 0, |
| compositionId: 'maka.interactive', |
| compositionRevision: '1', |
| closed: closed.promise, |
| replaceClientCapabilities: async () => { |
| events.push('capabilities'); |
| if (onRegister) await onRegister(); |
| registered = true; |
| return { registrationId: connectionId, revision: 1 }; |
| }, |
| request: async (operation: string, input: { attemptId: string }) => { |
| events.push(operation); |
| if (operation === 'oauth.login.start' && !registered) { |
| throw new RuntimeHostOperationError(operation, 'capability_unavailable', 'Not registered'); |
| } |
| return request(operation, input); |
| }, |
| subscribeConfigurationChanges: () => () => {}, |
| subscribeConnectionCatalogChanges: () => () => {}, |
| subscribeProjectCatalogChanges: () => () => {}, |
| subscribeSessionCatalogChanges: () => () => {}, |
| subscribeScheduledTaskChanges: () => () => {}, |
| close: async () => closed.resolve(), |
| } as unknown as RuntimeHostConnection; |
| return { connection, events, disconnect: () => closed.resolve() }; |
| } |
| |
| describe('createRuntimeHostOnboardingSurface', () => { |
| for (const action of ['cancel', 'close'] as const) { |
| test(`bounds OAuth ${action} while a real reconnecting query waits for an offline Host`, async () => { |
| const queryDispatched = deferred<void>(); |
| const first = oauthPhysicalConnection('first', (operation, { attemptId }) => { |
| if (operation === 'oauth.login.start') { |
| return oauthProjection(attemptId, 'awaiting_authorization'); |
| } |
| assert.equal(operation, 'oauth.login.query'); |
| first.disconnect(); |
| queryDispatched.resolve(); |
| throw new RuntimeHostRequestInterruptedError( |
| operation, |
| 'query', |
| 'dispatched', |
| 'connection_lost', |
| ); |
| }); |
| const connection = await createRuntimeHostReconnectingConnection({ |
| initialConnection: first.connection, |
| connect: (signal) => |
| new Promise<RuntimeHostConnection>((_resolve, reject) => { |
| if (signal.aborted) reject(signal.reason); |
| else signal.addEventListener('abort', () => reject(signal.reason), { once: true }); |
| }), |
| }); |
| let closeCalls = 0; |
| const options = { |
| connectOAuth: async () => ({ |
| connection, |
| close: async () => { |
| closeCalls += 1; |
| await connection.close(); |
| }, |
| }), |
| pollIntervalMs: 0, |
| requestTimeoutMs: 60_000, |
| cancellationTimeoutMs: 20, |
| shutdownTimeoutMs: 20, |
| }; |
| const surface = createRuntimeHostOnboardingSurface(connection, options); |
| const abort = new AbortController(); |
| const login = loginWithOAuth(surface, { signal: abort.signal }); |
| try { |
| await queryDispatched.promise; |
| if (action === 'cancel') abort.abort(); |
| const closing = action === 'close' ? surface.close() : Promise.resolve(); |
| const [result] = await withTimeout( |
| Promise.all([login, closing]), |
| 500, |
| `OAuth ${action} did not settle while the Host was offline`, |
| ); |
| assert.deepEqual(result, { kind: 'unconfirmed' }); |
| assert.equal(closeCalls, 1); |
| await connection.closed; |
| } finally { |
| await connection.close(); |
| await login; |
| await surface.close(); |
| } |
| }); |
| } |
| |
| test('waits for the replacement OAuth capability acknowledgement before retrying start', async () => { |
| const starts: string[] = []; |
| const registration = deferred<void>(); |
| const queried = deferred<void>(); |
| const first = oauthPhysicalConnection('first', (operation, { attemptId }) => { |
| starts.push(attemptId); |
| first.disconnect(); |
| assert.equal(operation, 'oauth.login.start'); |
| throw interruptedOAuthRequest(operation); |
| }); |
| const replacement = oauthPhysicalConnection( |
| 'replacement', |
| (operation, { attemptId }) => { |
| if (operation === 'oauth.login.query') { |
| queried.resolve(); |
| throw new RuntimeHostOperationError(operation, 'not_found', 'Not admitted'); |
| } |
| starts.push(attemptId); |
| return oauthProjection(attemptId, 'authenticated'); |
| }, |
| () => registration.promise, |
| ); |
| const connection = await createRuntimeHostReconnectingConnection({ |
| initialConnection: first.connection, |
| connect: async () => replacement.connection, |
| }); |
| const surface = createRuntimeHostOnboardingSurface(connection, { |
| connectOAuth: async () => ({ connection, close: () => connection.close() }), |
| createAttemptId: () => 'reconnected-start', |
| }); |
| const login = loginWithOAuth(surface); |
| try { |
| await withTimeout(queried.promise, 500, 'Replacement was not queried'); |
| assert.ok(!replacement.events.includes('oauth.login.start')); |
| registration.resolve(); |
| assert.deepEqual(await login, { |
| kind: 'authenticated', |
| connection: oauthConnectionIdentity, |
| }); |
| assert.deepEqual(starts, ['reconnected-start', 'reconnected-start']); |
| assert.ok( |
| replacement.events.indexOf('capabilities') < |
| replacement.events.indexOf('oauth.login.start'), |
| ); |
| assert.equal(replacement.events.filter((event) => event === 'oauth.login.start').length, 1); |
| } finally { |
| registration.resolve(); |
| await surface.close(); |
| await connection.close(); |
| } |
| }); |
| |
| test('survives another disconnect while publishing the replacement OAuth capability', async () => { |
| const first = oauthPhysicalConnection('first', (operation) => { |
| first.disconnect(); |
| assert.equal(operation, 'oauth.login.start'); |
| throw interruptedOAuthRequest(operation); |
| }); |
| const unstable = oauthPhysicalConnection( |
| 'unstable', |
| (operation) => { |
| assert.equal(operation, 'oauth.login.query'); |
| throw new RuntimeHostOperationError(operation, 'not_found', 'Not admitted'); |
| }, |
| async () => { |
| unstable.disconnect(); |
| throw new RuntimeHostRequestInterruptedError( |
| 'client.capability.replace', |
| 'control', |
| 'dispatched', |
| 'connection_lost', |
| ); |
| }, |
| ); |
| const replacement = oauthPhysicalConnection('replacement', (operation, { attemptId }) => { |
| if (operation === 'oauth.login.query') { |
| throw new RuntimeHostOperationError(operation, 'not_found', 'Not admitted'); |
| } |
| return oauthProjection(attemptId, 'authenticated'); |
| }); |
| let reconnects = 0; |
| const connection = await createRuntimeHostReconnectingConnection({ |
| initialConnection: first.connection, |
| connect: async () => (++reconnects === 1 ? unstable.connection : replacement.connection), |
| }); |
| const surface = createRuntimeHostOnboardingSurface(connection, { |
| connectOAuth: async () => ({ connection, close: () => connection.close() }), |
| }); |
| try { |
| assert.equal((await loginWithOAuth(surface)).kind, 'authenticated'); |
| assert.ok(!unstable.events.includes('oauth.login.start')); |
| assert.ok( |
| replacement.events.indexOf('capabilities') < |
| replacement.events.indexOf('oauth.login.start'), |
| ); |
| } finally { |
| await surface.close(); |
| await connection.close(); |
| } |
| }); |
| |
| for (const blockedOperation of [ |
| 'oauth.login.start', |
| 'oauth.login.query', |
| 'oauth.login.cancel', |
| ] as const) { |
| test(`retains the attempt and exact target after ${blockedOperation} times out`, async () => { |
| const blocked = deferred<never>(); |
| const started = deferred<void>(); |
| const abort = new AbortController(); |
| const requests: Array<{ operation: string; attemptId: string }> = []; |
| const target = { |
| kind: 'create', |
| providerType: 'openai-codex', |
| name: 'Work Codex', |
| slug: 'codex-work', |
| } as const; |
| let connections = 0; |
| let ids = 0; |
| let closes = 0; |
| const surface = createRuntimeHostOnboardingSurface({} as RuntimeHostConnection, { |
| connectOAuth: async () => { |
| const ordinal = ++connections; |
| const physical = oauthPhysicalConnection(`physical-${ordinal}`, (operation, input) => { |
| requests.push({ operation, attemptId: input.attemptId }); |
| if (ordinal === 1) { |
| if (operation === 'oauth.login.start') { |
| assert.deepEqual(input, { attemptId: 'attempt-1', target }); |
| started.resolve(); |
| } |
| if (operation === blockedOperation) return blocked.promise; |
| return oauthProjection(input.attemptId, 'awaiting_authorization'); |
| } |
| assert.equal(operation, 'oauth.login.query'); |
| return { |
| ...oauthProjection(input.attemptId, 'authenticated'), |
| connection: { ...oauthConnectionIdentity, slug: 'codex-work' }, |
| }; |
| }); |
| return { |
| connection: physical.connection, |
| close: async () => { |
| closes += 1; |
| await physical.connection.close(); |
| }, |
| }; |
| }, |
| createAttemptId: () => `attempt-${++ids}`, |
| pollIntervalMs: 0, |
| requestTimeoutMs: 20, |
| cancellationTimeoutMs: 20, |
| }); |
| try { |
| const login = loginWithOAuth(surface, { signal: abort.signal, target }); |
| if (blockedOperation === 'oauth.login.cancel') { |
| await started.promise; |
| abort.abort(); |
| } |
| assert.deepEqual(await withTimeout(login, 500, 'OAuth request remained pending'), { |
| kind: 'unconfirmed', |
| }); |
| assert.deepEqual(await loginWithOAuth(surface, { target, signal: AbortSignal.abort() }), { |
| kind: 'unconfirmed', |
| }); |
| assert.deepEqual(await loginWithOAuth(surface, { target }), { |
| kind: 'authenticated', |
| connection: { ...oauthConnectionIdentity, slug: 'codex-work' }, |
| }); |
| assert.equal(ids, 1); |
| assert.equal(closes, 2); |
| assert.ok(requests.every(({ attemptId }) => attemptId === 'attempt-1')); |
| assert.equal( |
| requests.filter(({ operation }) => operation === 'oauth.login.start').length, |
| 1, |
| ); |
| } finally { |
| blocked.reject(new Error('Fixture closed')); |
| await surface.close(); |
| } |
| }); |
| } |
| |
| test('does not apply the OAuth request timeout to connection acquisition', async () => { |
| const connecting = deferred<{ connection: RuntimeHostConnection; close(): Promise<void> }>(); |
| const entered = deferred<void>(); |
| const physical = oauthPhysicalConnection('slow', (operation, { attemptId }) => { |
| assert.equal(operation, 'oauth.login.start'); |
| return oauthProjection(attemptId, 'authenticated'); |
| }); |
| let closes = 0; |
| const surface = createRuntimeHostOnboardingSurface({} as RuntimeHostConnection, { |
| connectOAuth: async () => { |
| entered.resolve(); |
| return connecting.promise; |
| }, |
| requestTimeoutMs: 20, |
| }); |
| const login = loginWithOAuth(surface); |
| try { |
| await entered.promise; |
| await assert.rejects( |
| withTimeout(login, 60, 'OAuth connection acquisition remained pending'), |
| /remained pending/, |
| ); |
| connecting.resolve({ |
| connection: physical.connection, |
| close: async () => { |
| closes += 1; |
| await physical.connection.close(); |
| }, |
| }); |
| assert.deepEqual(await login, { |
| kind: 'authenticated', |
| connection: oauthConnectionIdentity, |
| }); |
| assert.equal(closes, 1); |
| } finally { |
| connecting.resolve({ |
| connection: physical.connection, |
| close: () => physical.connection.close(), |
| }); |
| await surface.close(); |
| } |
| }); |
| |
| test('shutdown bounds a pending connection and releases it if it arrives late', async () => { |
| const connecting = deferred<{ connection: RuntimeHostConnection; close(): Promise<void> }>(); |
| const entered = deferred<void>(); |
| const physical = oauthPhysicalConnection('late', () => assert.fail('Must not start OAuth')); |
| let closes = 0; |
| const surface = createRuntimeHostOnboardingSurface({} as RuntimeHostConnection, { |
| connectOAuth: async () => { |
| entered.resolve(); |
| return connecting.promise; |
| }, |
| shutdownTimeoutMs: 20, |
| }); |
| const login = loginWithOAuth(surface); |
| await entered.promise; |
| await withTimeout(surface.close(), 500, 'Shutdown kept waiting for the connector'); |
| assert.deepEqual(await login, { kind: 'cancelled' }); |
| connecting.resolve({ |
| connection: physical.connection, |
| close: async () => { |
| closes += 1; |
| await physical.connection.close(); |
| }, |
| }); |
| await waitFor(() => closes === 1); |
| assert.deepEqual(physical.events, []); |
| }); |
| |
| test('cancelling during capability publication never dispatches OAuth start', async () => { |
| const registration = deferred<void>(); |
| const entered = deferred<void>(); |
| const abort = new AbortController(); |
| const physical = oauthPhysicalConnection( |
| 'first', |
| () => assert.fail('Must not start OAuth'), |
| async () => { |
| entered.resolve(); |
| await registration.promise; |
| }, |
| ); |
| const surface = createRuntimeHostOnboardingSurface(physical.connection, { |
| connectOAuth: async () => ({ |
| connection: physical.connection, |
| close: () => physical.connection.close(), |
| }), |
| }); |
| const login = loginWithOAuth(surface, { signal: abort.signal }); |
| try { |
| await entered.promise; |
| abort.abort(); |
| registration.resolve(); |
| assert.deepEqual(await login, { kind: 'cancelled' }); |
| assert.deepEqual(physical.events, ['capabilities']); |
| } finally { |
| registration.resolve(); |
| await surface.close(); |
| } |
| }); |
| |
| test('a slow connection cleanup cannot hide an authenticated result or block shutdown', async () => { |
| const cleanup = deferred<void>(); |
| let closes = 0; |
| const physical = oauthPhysicalConnection('first', (_operation, { attemptId }) => |
| oauthProjection(attemptId, 'authenticated'), |
| ); |
| const surface = createRuntimeHostOnboardingSurface(physical.connection, { |
| connectOAuth: async () => ({ |
| connection: physical.connection, |
| close: async () => { |
| closes += 1; |
| await cleanup.promise; |
| await physical.connection.close(); |
| }, |
| }), |
| shutdownTimeoutMs: 20, |
| }); |
| try { |
| const result = await withTimeout( |
| loginWithOAuth(surface), |
| 500, |
| 'Cleanup hid the login result', |
| ); |
| assert.deepEqual(result, { kind: 'authenticated', connection: oauthConnectionIdentity }); |
| await withTimeout(surface.close(), 500, 'Cleanup blocked shutdown'); |
| assert.equal(closes, 1); |
| } finally { |
| cleanup.resolve(); |
| await physical.connection.closed; |
| await surface.close(); |
| } |
| }); |
| |
| test('an unconfirmed attempt cannot be resumed against a different Host root', async () => { |
| const blocked = deferred<never>(); |
| const first = oauthPhysicalConnection('first', () => blocked.promise); |
| const wrongRoot = oauthPhysicalConnection('wrong-root', () => |
| assert.fail('Wrong root was queried'), |
| ); |
| let connections = 0; |
| let ids = 0; |
| const surface = createRuntimeHostOnboardingSurface(first.connection, { |
| connectOAuth: async () => { |
| const connection = |
| ++connections === 1 |
| ? first.connection |
| : { ...wrongRoot.connection, rootId: 'another-root' }; |
| return { connection, close: () => connection.close() }; |
| }, |
| createAttemptId: () => `attempt-${++ids}`, |
| requestTimeoutMs: 20, |
| }); |
| try { |
| assert.deepEqual(await loginWithOAuth(surface), { kind: 'unconfirmed' }); |
| assert.deepEqual(await loginWithOAuth(surface), { kind: 'unconfirmed' }); |
| assert.equal(ids, 1); |
| assert.deepEqual(wrongRoot.events, []); |
| } finally { |
| blocked.reject(new Error('Fixture closed')); |
| await surface.close(); |
| } |
| }); |
| |
| test('reopening setup during cancellation observes the original commit instead of starting again', async () => { |
| const started = deferred<void>(); |
| const queried = deferred<void>(); |
| const committed = deferred<ReturnType<typeof oauthProjection>>(); |
| const abort = new AbortController(); |
| let starts = 0; |
| let ids = 0; |
| const physical = oauthPhysicalConnection('first', (operation, { attemptId }) => { |
| if (operation === 'oauth.login.start') { |
| starts += 1; |
| started.resolve(); |
| return oauthProjection(attemptId, 'awaiting_authorization'); |
| } |
| if (operation === 'oauth.login.cancel') return oauthProjection(attemptId, 'committing'); |
| queried.resolve(); |
| return committed.promise; |
| }); |
| const surface = createRuntimeHostOnboardingSurface(physical.connection, { |
| connectOAuth: async () => ({ |
| connection: physical.connection, |
| close: () => physical.connection.close(), |
| }), |
| createAttemptId: () => `attempt-${++ids}`, |
| pollIntervalMs: 0, |
| }); |
| const first = loginWithOAuth(surface, { signal: abort.signal }); |
| try { |
| await started.promise; |
| abort.abort(); |
| await queried.promise; |
| const reopened = loginWithOAuth(surface); |
| committed.resolve(oauthProjection('attempt-1', 'authenticated')); |
| const expected = { kind: 'authenticated', connection: oauthConnectionIdentity }; |
| assert.deepEqual(await first, expected); |
| assert.deepEqual(await reopened, expected); |
| assert.equal(starts, 1); |
| assert.equal(ids, 1); |
| } finally { |
| committed.resolve(oauthProjection('attempt-1', 'authenticated')); |
| await surface.close(); |
| } |
| }); |
| |
| test('asks the Host enrollment gate before offering Codex OAuth', async () => { |
| const operations: string[] = []; |
| const connection = { |
| request: async (operation: string) => { |
| operations.push(operation); |
| if (operation === 'connection.catalog.query') { |
| return { |
| kind: 'page', |
| revision: 1, |
| defaultTarget: null, |
| connectionCount: 0, |
| items: [], |
| nextCursor: null, |
| }; |
| } |
| if (operation === 'oauth.enrollment.query') { |
| return { provider: 'openai-codex', enabled: true }; |
| } |
| throw new Error(`Unexpected operation ${operation}`); |
| }, |
| } as unknown as RuntimeHostConnection; |
| |
| const providers = await createRuntimeHostOnboardingSurface(connection).listProviders(); |
| |
| assert.deepEqual(operations, ['connection.catalog.query', 'oauth.enrollment.query']); |
| assert.equal( |
| providers.some( |
| ({ providerType, target }) => providerType === 'openai-codex' && target.kind === 'create', |
| ), |
| true, |
| ); |
| }); |
| |
| test('runs presentation and polling behind one OAuth login call', async () => { |
| const requests: string[] = []; |
| const presentations: Array<{ readonly url: string; readonly stateHint?: string }> = []; |
| let provider: ClientCapabilityProvider | undefined; |
| let closeCalls = 0; |
| const oauthConnection = { |
| replaceClientCapabilities: async (next: ClientCapabilityProvider) => { |
| provider = next; |
| return { registrationId: 'oauth-registration', revision: 1 }; |
| }, |
| request: async (operation: string) => { |
| requests.push(operation); |
| const connection = { |
| connectionId: 'codex-id', |
| slug: 'codex-subscription', |
| providerType: 'openai-codex' as const, |
| }; |
| if (operation === 'oauth.login.start') { |
| assert.ok(provider?.callService); |
| await provider.callService( |
| { |
| kind: 'client.capability.service_call', |
| invocationId: 'presentation-1', |
| registrationId: 'oauth-registration', |
| serviceId: 'oauth_presentation', |
| version: '1', |
| method: 'open_external', |
| input: { |
| url: 'https://auth.openai.com/codex/device', |
| stateHint: 'ABCD-EFGH', |
| }, |
| }, |
| { |
| signal: new AbortController().signal, |
| accept: async () => undefined, |
| }, |
| ); |
| return { |
| attemptId: 'setup-oauth-1', |
| connection, |
| phase: 'awaiting_authorization', |
| }; |
| } |
| if (operation === 'oauth.login.query') { |
| return { attemptId: 'setup-oauth-1', connection, phase: 'authenticated' }; |
| } |
| throw new Error(`Unexpected operation ${operation}`); |
| }, |
| } as unknown as RuntimeHostConnection; |
| const surface = createRuntimeHostOnboardingSurface({} as RuntimeHostConnection, { |
| connectOAuth: async () => ({ |
| connection: oauthConnection, |
| close: async () => { |
| closeCalls += 1; |
| }, |
| }), |
| pollIntervalMs: 0, |
| createAttemptId: () => 'setup-oauth-1', |
| }); |
| |
| const result = await surface.loginOAuth?.({ |
| target: { kind: 'create', providerType: 'openai-codex' }, |
| signal: new AbortController().signal, |
| onPresentation: (presentation) => presentations.push(presentation), |
| }); |
| |
| assert.deepEqual(result, { |
| kind: 'authenticated', |
| connection: { |
| connectionId: 'codex-id', |
| slug: 'codex-subscription', |
| providerType: 'openai-codex', |
| }, |
| }); |
| assert.deepEqual(presentations, [ |
| { url: 'https://auth.openai.com/codex/device', stateHint: 'ABCD-EFGH' }, |
| ]); |
| assert.deepEqual(requests, ['oauth.login.start', 'oauth.login.query']); |
| assert.equal(closeCalls, 1); |
| }); |
| |
| test('reconciles a dispatched OAuth start after its response is lost', async () => { |
| const attemptId = 'setup-oauth-dispatched-start'; |
| const { requests, surface } = oauthTestSurface(attemptId, (operation) => { |
| if (operation === 'oauth.login.start') throw interruptedOAuthRequest(operation); |
| if (operation === 'oauth.login.query') return oauthProjection(attemptId, 'authenticated'); |
| throw new Error(`Unexpected operation ${operation}`); |
| }); |
| |
| const result = await loginWithOAuth(surface); |
| |
| assert.deepEqual(result, { |
| kind: 'authenticated', |
| connection: oauthConnectionIdentity, |
| }); |
| assert.deepEqual(requests, ['oauth.login.start', 'oauth.login.query']); |
| }); |
| |
| test('retries the same OAuth start when reconciliation proves it was not admitted', async () => { |
| const attemptId = 'setup-oauth-retried-start'; |
| let startCalls = 0; |
| const { requests, surface } = oauthTestSurface(attemptId, (operation) => { |
| if (operation === 'oauth.login.start') { |
| startCalls += 1; |
| if (startCalls === 1) { |
| throw interruptedOAuthRequest(operation); |
| } |
| return oauthProjection(attemptId, 'authenticated'); |
| } |
| if (operation === 'oauth.login.query') { |
| throw new RuntimeHostOperationError( |
| 'oauth.login.query', |
| 'not_found', |
| 'OAuth login was not found', |
| ); |
| } |
| throw new Error(`Unexpected operation ${operation}`); |
| }); |
| |
| assert.equal((await loginWithOAuth(surface)).kind, 'authenticated'); |
| assert.deepEqual(requests, ['oauth.login.start', 'oauth.login.query', 'oauth.login.start']); |
| }); |
| |
| test('reconciles a dispatched OAuth cancellation instead of trusting the local signal', async () => { |
| const attemptId = 'setup-oauth-dispatched-cancel'; |
| const started = deferred<void>(); |
| const controller = new AbortController(); |
| const { requests, surface } = oauthTestSurface( |
| attemptId, |
| (operation) => { |
| if (operation === 'oauth.login.start') { |
| started.resolve(); |
| return oauthProjection(attemptId, 'awaiting_authorization'); |
| } |
| if (operation === 'oauth.login.cancel') throw interruptedOAuthRequest(operation); |
| if (operation === 'oauth.login.query') return oauthProjection(attemptId, 'authenticated'); |
| throw new Error(`Unexpected operation ${operation}`); |
| }, |
| { pollIntervalMs: 60_000 }, |
| ); |
| |
| const login = loginWithOAuth(surface, { signal: controller.signal }); |
| await started.promise; |
| controller.abort(); |
| |
| assert.deepEqual(await login, { |
| kind: 'authenticated', |
| connection: oauthConnectionIdentity, |
| }); |
| assert.deepEqual(requests, ['oauth.login.start', 'oauth.login.cancel', 'oauth.login.query']); |
| }); |
| |
| test('retries cancellation when reconciliation still finds an active OAuth attempt', async () => { |
| const attemptId = 'setup-oauth-retried-cancel'; |
| const started = deferred<void>(); |
| const controller = new AbortController(); |
| let cancelCalls = 0; |
| const { requests, surface } = oauthTestSurface( |
| attemptId, |
| (operation) => { |
| if (operation === 'oauth.login.start') { |
| started.resolve(); |
| return oauthProjection(attemptId, 'awaiting_authorization'); |
| } |
| if (operation === 'oauth.login.cancel') { |
| cancelCalls += 1; |
| if (cancelCalls === 1) throw interruptedOAuthRequest(operation); |
| return oauthProjection(attemptId, 'cancelled'); |
| } |
| if (operation === 'oauth.login.query') { |
| return oauthProjection(attemptId, 'awaiting_authorization'); |
| } |
| throw new Error(`Unexpected operation ${operation}`); |
| }, |
| { pollIntervalMs: 60_000 }, |
| ); |
| |
| const login = loginWithOAuth(surface, { signal: controller.signal }); |
| await started.promise; |
| controller.abort(); |
| |
| assert.deepEqual(await login, { kind: 'cancelled' }); |
| assert.deepEqual(requests, [ |
| 'oauth.login.start', |
| 'oauth.login.cancel', |
| 'oauth.login.query', |
| 'oauth.login.cancel', |
| ]); |
| }); |
| |
| test('does not mistake a Host cancellation failure for local cancellation', async () => { |
| const attemptId = 'setup-oauth-cancel-failed'; |
| const started = deferred<void>(); |
| const controller = new AbortController(); |
| const { surface } = oauthTestSurface( |
| attemptId, |
| (operation) => { |
| if (operation === 'oauth.login.start') { |
| started.resolve(); |
| return oauthProjection(attemptId, 'awaiting_authorization'); |
| } |
| throw new RuntimeHostOperationError( |
| 'oauth.login.cancel', |
| 'persistence_failed', |
| 'OAuth cancellation could not be reconciled', |
| ); |
| }, |
| { pollIntervalMs: 60_000 }, |
| ); |
| |
| const login = loginWithOAuth(surface, { signal: controller.signal }); |
| await started.promise; |
| controller.abort(); |
| |
| assert.deepEqual(await login, { kind: 'unconfirmed' }); |
| }); |
| |
| test('preserves a Host slug_taken error as an OAuth failure reason', async () => { |
| const { surface } = oauthTestSurface('setup-oauth-slug-taken', () => { |
| throw new RuntimeHostOperationError( |
| 'oauth.login.start', |
| 'slug_taken', |
| 'Connection slug is already in use', |
| ); |
| }); |
| |
| assert.deepEqual( |
| await surface.loginOAuth!({ |
| target: { |
| kind: 'create', |
| providerType: 'openai-codex', |
| slug: 'codex-work', |
| name: 'Work Codex', |
| }, |
| signal: new AbortController().signal, |
| onPresentation: () => undefined, |
| }), |
| { kind: 'failed', reason: 'slug_taken' }, |
| ); |
| }); |
| |
| test('forwards the requested Connection identity to OAuth start', async () => { |
| const attemptId = 'setup-oauth-custom'; |
| const target = { |
| kind: 'create', |
| providerType: 'openai-codex', |
| slug: 'codex-work', |
| name: 'Work Codex', |
| } as const; |
| const starts: unknown[] = []; |
| const { surface } = oauthTestSurface(attemptId, (operation, input) => { |
| assert.equal(operation, 'oauth.login.start'); |
| starts.push(input); |
| return { |
| ...oauthProjection(attemptId, 'authenticated'), |
| connection: { ...oauthConnectionIdentity, slug: 'codex-work' }, |
| }; |
| }); |
| |
| await loginWithOAuth(surface, { target }); |
| |
| assert.deepEqual(starts, [{ attemptId, target }]); |
| }); |
| |
| test('closing the onboarding surface cancels and settles its active OAuth attempt', async () => { |
| const attemptId = 'setup-oauth-close'; |
| const started = deferred<void>(); |
| let closeCalls = 0; |
| const { requests, surface } = oauthTestSurface( |
| attemptId, |
| (operation) => { |
| if (operation === 'oauth.login.start') { |
| started.resolve(); |
| return oauthProjection(attemptId, 'awaiting_authorization'); |
| } |
| if (operation === 'oauth.login.cancel') return oauthProjection(attemptId, 'cancelled'); |
| throw new Error(`Unexpected operation ${operation}`); |
| }, |
| { |
| pollIntervalMs: 60_000, |
| onClose: () => { |
| closeCalls += 1; |
| }, |
| }, |
| ); |
| const login = loginWithOAuth(surface); |
| await started.promise; |
| |
| await surface.close(); |
| |
| assert.deepEqual(await login, { kind: 'cancelled' }); |
| assert.deepEqual(requests, ['oauth.login.start', 'oauth.login.cancel']); |
| assert.equal(closeCalls, 1); |
| }); |
| |
| test('accepts authentication when cancellation loses the Host commit race', async () => { |
| const attemptId = 'setup-oauth-race'; |
| const started = deferred<void>(); |
| const abort = new AbortController(); |
| const { requests, surface } = oauthTestSurface( |
| attemptId, |
| (operation) => { |
| if (operation === 'oauth.login.start') { |
| started.resolve(); |
| return oauthProjection(attemptId, 'awaiting_authorization'); |
| } |
| if (operation === 'oauth.login.cancel') return oauthProjection(attemptId, 'committing'); |
| if (operation === 'oauth.login.query') return oauthProjection(attemptId, 'authenticated'); |
| throw new Error(`Unexpected operation ${operation}`); |
| }, |
| { pollIntervalMs: 20 }, |
| ); |
| const login = loginWithOAuth(surface, { signal: abort.signal }); |
| await started.promise; |
| |
| abort.abort(); |
| |
| await new Promise((resolve) => setTimeout(resolve, 0)); |
| assert.deepEqual(requests, ['oauth.login.start', 'oauth.login.cancel']); |
| |
| assert.deepEqual(await login, { |
| kind: 'authenticated', |
| connection: oauthConnectionIdentity, |
| }); |
| assert.deepEqual(requests, ['oauth.login.start', 'oauth.login.cancel', 'oauth.login.query']); |
| await surface.close(); |
| }); |
| |
| test('preserves Host failure codes without projecting backend text', async () => { |
| const connection = { |
| request: async (operation: string) => { |
| if (operation === 'connection.onboarding.verify') { |
| return { kind: 'rejected', reason: 'connection_not_found' }; |
| } |
| if (operation === 'connection.onboarding.save') { |
| return { kind: 'failed', errorClass: 'network' }; |
| } |
| throw new Error(`Unexpected operation ${operation}`); |
| }, |
| } as unknown as RuntimeHostConnection; |
| const surface = createRuntimeHostOnboardingSurface(connection); |
| |
| assert.deepEqual( |
| await surface.verify({ |
| target: { kind: 'existing', connectionId: 'gone-id' }, |
| apiKey: 'sk-test', |
| }), |
| { kind: 'rejected', reason: 'connection_not_found' }, |
| ); |
| assert.deepEqual( |
| await surface.save({ |
| target: { kind: 'existing', connectionId: 'live-id' }, |
| apiKey: 'sk-test', |
| enabledModelIds: ['gpt-5-mini'], |
| }), |
| { kind: 'failed', errorClass: 'network' }, |
| ); |
| }); |
| |
| test('classifies transport exceptions without exposing their message', async () => { |
| const connection = { |
| request: async () => { |
| throw new Error('Host transport leaked this English detail'); |
| }, |
| } as unknown as RuntimeHostConnection; |
| |
| assert.deepEqual( |
| await createRuntimeHostOnboardingSurface(connection).verify({ |
| target: { kind: 'create', providerType: 'openai' }, |
| apiKey: 'sk-test', |
| }), |
| { kind: 'unavailable' }, |
| ); |
| }); |
| |
| test('keeps the committed Connection when the follow-up catalog refresh fails', async () => { |
| const committed = { |
| connectionId: 'committed-openai-id', |
| revision: 3, |
| slug: 'openai-2', |
| providerType: 'openai', |
| } as const; |
| const connection = { |
| request: async (operation: string) => { |
| if (operation === 'connection.onboarding.save') { |
| return { kind: 'saved', connection: committed }; |
| } |
| if (operation === 'connection.catalog.query') { |
| throw new Error('transient catalog failure'); |
| } |
| throw new Error(`Unexpected operation ${operation}`); |
| }, |
| } as unknown as RuntimeHostConnection; |
| |
| const result = await createRuntimeHostOnboardingSurface(connection).save({ |
| target: { kind: 'create', providerType: 'openai' }, |
| apiKey: 'sk-test', |
| enabledModelIds: ['gpt-5-mini'], |
| }); |
| |
| assert.deepEqual(result, { |
| kind: 'ok', |
| connection: committed, |
| refresh: { |
| kind: 'failed', |
| reason: 'catalog_unavailable', |
| }, |
| }); |
| }); |
| }); |
| |
| describe('projectRuntimeHostModelChoices', () => { |
| test('a retained retired connection contributes no /model choices', () => { |
| // Retirement keeps the connection enabled so its credential stays visible |
| // and deletable. Filtering on `enabled` alone listed its models here and |
| // only refused them after the user picked one. |
| const choices = projectRuntimeHostModelChoices( |
| catalog([ |
| live, |
| { |
| ...live, |
| connectionId: 'retired-id', |
| slug: 'claude-subscription', |
| name: 'Claude Subscription', |
| providerType: 'claude-subscription', |
| enabledModelIds: ['claude-opus-5'], |
| models: [{ id: 'claude-opus-5' }], |
| }, |
| ]), |
| ); |
| assert.deepEqual( |
| choices.map(({ connectionSlug, model }) => ({ connectionSlug, model })), |
| [{ connectionSlug: 'openai', model: 'gpt-5-mini' }], |
| ); |
| }); |
| |
| test('a disabled connection is also excluded, so the filter is not over-broad', () => { |
| const choices = projectRuntimeHostModelChoices( |
| catalog([live, { ...live, connectionId: 'off-id', slug: 'openai-off', enabled: false }]), |
| ); |
| assert.deepEqual( |
| choices.map(({ connectionSlug }) => connectionSlug), |
| ['openai'], |
| ); |
| }); |
| |
| test('projects the catalog display name onto each model choice', () => { |
| const choices = projectRuntimeHostModelChoices(catalog([live])); |
| |
| assert.equal(choices[0]?.displayName, 'GPT-5 Mini'); |
| }); |
| |
| test('a model that exists only in the resolved catalog still carries its context window', () => { |
| // A provider with no model-list endpoint stores no rows, so its models are |
| // reachable only through the Host's resolved catalog. The TUI reads its |
| // opening context window from these choices for exactly this reason: the |
| // stored list it used to read is empty here, and the very first status |
| // line would have had no denominator. |
| const choices = projectRuntimeHostModelChoices( |
| catalog([ |
| { |
| connectionId: 'fallback-id', |
| revision: 1, |
| slug: 'codex', |
| name: 'Codex', |
| providerType: 'openai-codex', |
| enabled: true, |
| enabledModelIds: ['gpt-5.5'], |
| models: [], |
| }, |
| ]), |
| ); |
| |
| assert.ok(choices.length > 0, 'a fallback-only connection still offers models'); |
| for (const choice of choices) { |
| assert.equal( |
| typeof choice.contextWindow, |
| 'number', |
| `${choice.model} reached the picker without a context window`, |
| ); |
| } |
| }); |
| }); |
| |
| describe('projectProviders', () => { |
| const relay = { |
| connectionId: 'relay-custom-id', |
| revision: 1, |
| slug: 'my-relay', |
| name: 'My Relay', |
| providerType: 'openai-compatible', |
| baseUrl: 'https://relay.example.test/v1', |
| enabled: true, |
| enabledModelIds: ['relay/model'], |
| models: [{ id: 'relay/model' }], |
| } as const; |
| |
| test('enabled Codex OAuth projects existing accounts and one add-account row', () => { |
| const codex = { |
| connectionId: 'codex-id', |
| revision: 1, |
| slug: 'codex-subscription', |
| name: 'Work Codex', |
| providerType: 'openai-codex', |
| enabled: true, |
| enabledModelIds: ['gpt-5.5'], |
| models: [], |
| } as const; |
| |
| const entries = projectProviders(catalog([codex]), true).filter( |
| ({ providerType }) => providerType === 'openai-codex', |
| ); |
| |
| assert.deepEqual(entries, [ |
| { |
| providerType: 'openai-codex', |
| label: 'Work Codex · codex-subscription', |
| requiresBaseUrl: false, |
| setupMethod: 'oauth', |
| target: { kind: 'existing', connectionId: 'codex-id' }, |
| connectionSlug: 'codex-subscription', |
| enabledModelIds: ['gpt-5.5'], |
| }, |
| { |
| providerType: 'openai-codex', |
| label: 'OpenAI OAuth (ChatGPT / Codex)', |
| requiresBaseUrl: false, |
| setupMethod: 'oauth', |
| target: { kind: 'create', providerType: 'openai-codex' }, |
| suggestedSlug: 'codex-subscription-2', |
| enabledModelIds: ['gpt-6-astra', 'gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna'], |
| }, |
| ]); |
| }); |
| |
| test('a Desktop-created relay and add-account action are both explicit', () => { |
| const entries = projectProviders(catalog([relay])).filter( |
| ({ providerType }) => providerType === 'openai-compatible', |
| ); |
| const entry = entries.find(({ target }) => target.kind === 'existing'); |
| assert.deepEqual(entry?.target, { kind: 'existing', connectionId: 'relay-custom-id' }); |
| assert.equal(entry && 'connectionSlug' in entry ? entry.connectionSlug : undefined, 'my-relay'); |
| assert.deepEqual(entry?.enabledModelIds, ['relay/model']); |
| assert.deepEqual(entries.find(({ target }) => target.kind === 'create')?.target, { |
| kind: 'create', |
| providerType: 'openai-compatible', |
| }); |
| assert.equal( |
| entries.find(({ target }) => target.kind === 'create')?.label, |
| 'Custom relay (OpenAI Chat-compatible)', |
| ); |
| }); |
| |
| test('several non-canonical connections remain independently editable', () => { |
| const entries = projectProviders( |
| catalog([relay, { ...relay, connectionId: 'relay-2-id', slug: 'my-relay-2' }]), |
| ).filter(({ providerType }) => providerType === 'openai-compatible'); |
| assert.deepEqual( |
| entries.flatMap(({ target }) => (target.kind === 'existing' ? [target.connectionId] : [])), |
| ['relay-custom-id', 'relay-2-id'], |
| ); |
| }); |
| |
| test('the create row carries the Host-derived slug suggestion for the identity step', () => { |
| const taken = { |
| ...relay, |
| connectionId: 'openai-id', |
| slug: 'openai', |
| providerType: 'openai' as const, |
| }; |
| const entries = projectProviders(catalog([taken])).filter( |
| ({ providerType }) => providerType === 'openai', |
| ); |
| const create = entries.find(({ target }) => target.kind === 'create'); |
| assert.equal( |
| create && 'suggestedSlug' in create ? create.suggestedSlug : undefined, |
| 'openai-2', |
| ); |
| // …and with no existing connection the suggestion is the canonical base. |
| const bare = projectProviders(catalog([])).find( |
| ({ providerType }) => providerType === 'openai', |
| ); |
| assert.equal(bare && 'suggestedSlug' in bare ? bare.suggestedSlug : undefined, 'openai'); |
| }); |
| |
| test('a canonical connection does not hide another account', () => { |
| const canonical = { ...relay, connectionId: 'canonical-id', slug: 'openai-compatible' }; |
| const entries = projectProviders(catalog([relay, canonical])).filter( |
| ({ providerType, target }) => |
| providerType === 'openai-compatible' && target.kind === 'existing', |
| ); |
| assert.deepEqual( |
| entries.flatMap(({ target }) => (target.kind === 'existing' ? [target.connectionId] : [])), |
| ['relay-custom-id', 'canonical-id'], |
| ); |
| }); |
| }); |