import type { AttachClient, CreateHarnessSession, HarnessAdapter, HarnessActorContext, HarnessCapability, HarnessCatalog, HarnessCatalogEntry, HarnessCloseReason, HarnessDescriptor, HarnessEvent, HarnessInteractionResponse, HarnessPrompt, HarnessPromptReceipt, HarnessSelection, HarnessSessionHandle, HarnessSessionSnapshot, HarnessSessionState, ResumeHarnessSession, } from '@mosaicstack/types'; import { HARNESS_CAPABILITIES } from '@mosaicstack/types'; import { operationError } from '../harness.registry.js'; export interface FakeHarnessAdapterOptions { readonly id: string; readonly capabilities?: readonly HarnessCapability[]; readonly catalog?: readonly HarnessCatalogEntry[]; } const FAKE_PROVIDER = 'fake-openai'; function defaultCatalog(harnessId: string): readonly HarnessCatalogEntry[] { return [ { harnessId, providerId: FAKE_PROVIDER, modelId: 'fake-mini', displayName: 'Fake Mini', reasoningCapability: false, inputTypes: ['text'], authState: 'ready', availability: 'available', }, { harnessId, providerId: FAKE_PROVIDER, modelId: 'fake-pro', displayName: 'Fake Pro', reasoningCapability: true, inputTypes: ['text', 'image'], authState: 'ready', availability: 'available', }, { harnessId, providerId: FAKE_PROVIDER, modelId: 'fake-legacy', displayName: 'Fake Legacy', reasoningCapability: false, inputTypes: ['text'], authState: 'unavailable', availability: 'unavailable', }, ]; } function matches(entry: HarnessCatalogEntry, selection: HarnessSelection): boolean { return ( entry.harnessId === selection.harnessId && entry.providerId === selection.providerId && entry.modelId === selection.modelId ); } /** * In-memory harness session handle used by the fake adapter and by the shared * conformance suite. It enforces the two invariants the real adapters must also * honor: model selection is validated against the catalog and is NEVER * substituted, and unsupported capabilities fail with a typed error. */ export class FakeHarnessSessionHandle implements HarnessSessionHandle { private state: HarnessSessionState = 'idle'; private processId: string | undefined; private readonly attachedClientIds = new Set(); private readonly listeners = new Set<(event: HarnessEvent) => void>(); constructor( private readonly conversationId: string, private readonly nativeSessionId: string, private readonly seatId: string, private selection: HarnessSelection, private readonly correlationId: string, private readonly capabilities: readonly HarnessCapability[], private readonly catalog: readonly HarnessCatalogEntry[], ) { this.processId = `process-${nativeSessionId}`; } async snapshot(): Promise { return { conversationId: this.conversationId, nativeSessionId: this.nativeSessionId, processId: this.processId, seatId: this.seatId, selection: this.selection, state: this.state, attachedClientIds: [...this.attachedClientIds], }; } async attach(input: AttachClient): Promise { this.attachedClientIds.add(input.clientId); } async detach(clientId: string): Promise { // Removes the browser attachment only; the process and native session persist. this.attachedClientIds.delete(clientId); } async prompt(input: HarnessPrompt & { idempotencyKey: string }): Promise { return { conversationId: this.conversationId, turnId: input.turnId, correlationId: input.correlationId, state: 'accepted', selection: this.selection, }; } async setModel(selection: HarnessSelection): Promise { const entry = this.catalog.find((candidate) => matches(candidate, selection)); if (!entry) { // No fallback to the first catalog row: reject with the requested tuple, unchanged. throw operationError( 'selection_invalid', 'The requested harness/provider/model tuple is not in the catalog.', selection, this.correlationId, ); } if (entry.availability === 'unavailable') { throw operationError( 'model_unavailable', 'The requested model is currently unavailable.', selection, this.correlationId, true, ); } this.selection = selection; return this.selection; } async abort(_turnId: string): Promise { // No active turn machinery in the fake; abort is a no-op acknowledgement. } async respondInteraction(_input: HarnessInteractionResponse): Promise { if (!this.capabilities.includes('extensionUi')) { throw operationError( 'interaction_unsupported', 'This harness does not support interactive responses.', this.selection, this.correlationId, ); } } events(listener: (event: HarnessEvent) => void): () => void { this.listeners.add(listener); return () => { this.listeners.delete(listener); }; } async evictProcess(_reason: HarnessCloseReason): Promise { // Stop the process but keep the resumable native session. this.processId = undefined; this.state = 'evicted'; } async endSession(_reason: HarnessCloseReason): Promise { // Destructively end the native session. this.processId = undefined; this.state = 'ended'; } } /** * Minimal in-memory {@link HarnessAdapter} for Slice Zero. It mints a fresh * native session id on `create` and binds the supplied one on `resume`, so the * two paths are observably distinct. */ export class FakeHarnessAdapter implements HarnessAdapter { readonly id: string; private readonly capabilities: readonly HarnessCapability[]; private readonly catalogEntries: readonly HarnessCatalogEntry[]; private createdCount = 0; constructor(options: FakeHarnessAdapterOptions) { this.id = options.id; this.capabilities = options.capabilities ?? [...HARNESS_CAPABILITIES]; this.catalogEntries = options.catalog ?? defaultCatalog(options.id); } async describe(_context: HarnessActorContext): Promise { return { id: this.id, displayName: `Fake harness (${this.id})`, capabilities: this.capabilities, }; } async catalog(_context: HarnessActorContext): Promise { return { harnessId: this.id, version: '1.0.0', fingerprint: `fake-${this.id}-${this.catalogEntries.length}`, models: this.catalogEntries, }; } async create(input: CreateHarnessSession): Promise { this.createdCount += 1; const nativeSessionId = `native-${input.conversationId}-${this.createdCount}`; return new FakeHarnessSessionHandle( input.conversationId, nativeSessionId, input.context.seatId, input.selection, input.context.correlationId, this.capabilities, this.catalogEntries, ); } async resume(input: ResumeHarnessSession): Promise { return new FakeHarnessSessionHandle( input.conversationId, input.nativeSessionId, input.context.seatId, input.selection, input.context.correlationId, this.capabilities, this.catalogEntries, ); } }