Compare commits

..
6 changed files with 704 additions and 0 deletions
@@ -0,0 +1,317 @@
import { describe, expect, expectTypeOf, it } from 'vitest';
import { HARNESS_CAPABILITIES, HARNESS_ERROR_CODES } from './index.js';
import type {
AttachConversation,
ConversationSnapshot,
CreateHarnessSession,
HarnessAdapter,
HarnessCapability,
HarnessConversationService,
HarnessDescriptor,
HarnessError,
HarnessErrorCode,
HarnessEvent,
HarnessEventEnvelope,
HarnessInteractionState,
HarnessPrompt,
HarnessPromptReceipt,
HarnessSelection,
HarnessSessionHandle,
HarnessSessionSnapshot,
ResumeHarnessSession,
SendHarnessTurn,
TurnReceipt,
} from '../index.js';
const EXPECTED_CAPABILITIES = [
'modelSelection',
'thinkingLevels',
'images',
'toolEvents',
'extensionUi',
'steering',
'followUp',
'compaction',
'persistentResume',
] as const satisfies readonly HarnessCapability[];
const EXPECTED_ERROR_CODES = [
'auth_required',
'selection_invalid',
'catalog_unavailable',
'catalog_stale',
'model_unavailable',
'no_viable_provider',
'session_create_failed',
'session_not_found',
'resume_conflict',
'session_busy',
'auth_bundle_concurrency_unverified',
'adapter_unavailable',
'sandbox_unavailable',
'rpc_version_unsupported',
'rpc_protocol_error',
'process_exited',
'outcome_unknown',
'interaction_unsupported',
'aborted',
] as const satisfies readonly HarnessErrorCode[];
function assertNever(value: never): never {
throw new Error(`Unexpected contract variant: ${JSON.stringify(value)}`);
}
function describeEvent(event: HarnessEvent): string {
switch (event.type) {
case 'session.started':
case 'session.state':
case 'session.identity_changed':
case 'turn.started':
case 'text.delta':
case 'thinking.delta':
case 'tool.started':
case 'tool.updated':
case 'tool.finished':
case 'interaction.required':
case 'usage.updated':
case 'turn.completed':
case 'error':
return event.type;
default:
return assertNever(event);
}
}
function describeError(error: HarnessError): HarnessErrorCode {
switch (error.code) {
case 'auth_required':
case 'selection_invalid':
case 'catalog_unavailable':
case 'catalog_stale':
case 'model_unavailable':
case 'no_viable_provider':
case 'session_create_failed':
case 'session_not_found':
case 'resume_conflict':
case 'session_busy':
case 'auth_bundle_concurrency_unverified':
case 'adapter_unavailable':
case 'sandbox_unavailable':
case 'rpc_version_unsupported':
case 'rpc_protocol_error':
case 'process_exited':
case 'outcome_unknown':
case 'interaction_unsupported':
case 'aborted':
return error.code;
default:
return assertNever(error);
}
}
describe('generic harness contracts', (): void => {
it('keeps harness, provider, model, conversation, native session, process, and seat separate', (): void => {
const selection = {
harnessId: 'pi',
providerId: 'openai-codex',
modelId: 'gpt-5-codex',
} satisfies HarnessSelection;
const snapshot = {
conversationId: 'conversation-1',
nativeSessionId: 'native-session-1',
processId: 'process-1',
seatId: 'seat-1',
selection,
state: 'idle',
attachedClientIds: ['browser-1'],
} satisfies HarnessSessionSnapshot;
const identifiers = [
snapshot.selection.harnessId,
snapshot.selection.providerId,
snapshot.selection.modelId,
snapshot.conversationId,
snapshot.nativeSessionId,
snapshot.processId,
snapshot.seatId,
];
expect(new Set(identifiers).size).toBe(7);
expect(snapshot).toMatchObject({
conversationId: 'conversation-1',
nativeSessionId: 'native-session-1',
processId: 'process-1',
seatId: 'seat-1',
selection,
});
});
it('advertises the complete capability set as checked literals', (): void => {
const descriptor = {
id: 'pi',
displayName: 'Pi',
capabilities: HARNESS_CAPABILITIES,
} satisfies HarnessDescriptor;
expect(HARNESS_CAPABILITIES).toEqual(EXPECTED_CAPABILITIES);
expect(descriptor.capabilities).toEqual(EXPECTED_CAPABILITIES);
});
it('exposes every stable error code as an exhaustive discriminated union', (): void => {
const selection: HarnessSelection = {
harnessId: 'pi',
providerId: 'openai-codex',
modelId: 'gpt-5-codex',
};
const error: HarnessError = {
code: 'model_unavailable',
message: 'The selected model is unavailable.',
retryable: true,
correlationId: 'correlation-1',
selection,
};
expect(HARNESS_ERROR_CODES).toEqual(EXPECTED_ERROR_CODES);
expect(describeError(error)).toBe('model_unavailable');
const receipt = {
conversationId: 'conversation-1',
turnId: 'turn-1',
correlationId: 'correlation-1',
state: 'accepted',
selection,
} satisfies HarnessPromptReceipt;
expect(error.selection).toEqual(selection);
expect(receipt.selection).toEqual(selection);
expect('effectiveSelection' in error).toBe(false);
expect('effectiveSelection' in receipt).toBe(false);
});
it('wraps every normalized event variant in the persisted envelope', (): void => {
const selection: HarnessSelection = {
harnessId: 'pi',
providerId: 'openai-codex',
modelId: 'gpt-5-codex',
};
const toolStarted: HarnessEvent = {
type: 'tool.started',
toolCallId: 'tool-call-1',
toolName: 'read',
};
const events: readonly HarnessEvent[] = [
{ type: 'session.started', state: 'idle' },
{ type: 'session.state', state: 'busy' },
{
type: 'session.identity_changed',
identityGeneration: 2,
label: 'Re-enrolled account',
},
{ type: 'turn.started' },
{ type: 'text.delta', text: 'Hello' },
{ type: 'thinking.delta', text: 'Reasoning' },
toolStarted,
{
type: 'tool.updated',
toolCallId: 'tool-call-1',
toolName: 'read',
message: 'Reading',
},
{
type: 'tool.finished',
toolCallId: 'tool-call-1',
toolName: 'read',
isError: false,
},
{
type: 'interaction.required',
requestId: 'interaction-1',
interactionType: 'confirm',
state: 'pending',
prompt: 'Continue?',
},
{
type: 'usage.updated',
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
},
{ type: 'turn.completed', outcome: 'settled' },
{
type: 'error',
error: {
code: 'rpc_protocol_error',
message: 'The harness protocol failed.',
retryable: false,
correlationId: 'correlation-1',
selection,
},
},
];
const envelope: HarnessEventEnvelope = {
conversationId: 'conversation-1',
nativeSessionId: 'native-session-1',
turnId: 'turn-1',
correlationId: 'correlation-1',
sequence: 42,
nativeEntryCursor: 'native-entry-7',
occurredAt: '2026-08-11T12:00:00.000Z',
harnessId: 'pi',
selection,
event: toolStarted,
};
expect(events.map(describeEvent)).toEqual([
'session.started',
'session.state',
'session.identity_changed',
'turn.started',
'text.delta',
'thinking.delta',
'tool.started',
'tool.updated',
'tool.finished',
'interaction.required',
'usage.updated',
'turn.completed',
'error',
]);
expect(envelope).toMatchObject({
conversationId: 'conversation-1',
nativeSessionId: 'native-session-1',
turnId: 'turn-1',
correlationId: 'correlation-1',
sequence: 42,
nativeEntryCursor: 'native-entry-7',
harnessId: 'pi',
selection,
event: toolStarted,
});
});
it('models the complete one-response interaction lifecycle', (): void => {
const states = [
'pending',
'responded',
'cancelled',
'expired',
] as const satisfies readonly HarnessInteractionState[];
expect(states).toEqual(['pending', 'responded', 'cancelled', 'expired']);
});
it('preserves the approved adapter and conversation method signatures', (): void => {
expectTypeOf<HarnessAdapter['create']>().toEqualTypeOf<
(input: CreateHarnessSession) => Promise<HarnessSessionHandle>
>();
expectTypeOf<HarnessAdapter['resume']>().toEqualTypeOf<
(input: ResumeHarnessSession) => Promise<HarnessSessionHandle>
>();
expectTypeOf<HarnessSessionHandle['prompt']>().toEqualTypeOf<
(input: HarnessPrompt & { idempotencyKey: string }) => Promise<HarnessPromptReceipt>
>();
expectTypeOf<HarnessConversationService['attach']>().toEqualTypeOf<
(input: AttachConversation & { afterSequence?: number }) => Promise<ConversationSnapshot>
>();
expectTypeOf<HarnessConversationService['send']>().toEqualTypeOf<
(input: SendHarnessTurn & { idempotencyKey: string }) => Promise<TurnReceipt>
>();
});
});
+204
View File
@@ -0,0 +1,204 @@
import type { HarnessCapability, HarnessEvent, HarnessEventEnvelope } from './events.js';
/** Server-derived actor and seat authority. Browser input must not supply these values. */
export interface HarnessActorContext {
readonly actorId: string;
readonly tenantId: string;
readonly seatId: string;
readonly correlationId: string;
}
/** Exact harness/provider/model tuple. These concepts must never be merged into one identifier. */
export interface HarnessSelection {
readonly harnessId: string;
readonly providerId: string;
readonly modelId: string;
}
export interface HarnessDescriptor {
readonly id: string;
readonly displayName: string;
readonly capabilities: readonly HarnessCapability[];
}
export type HarnessInputType = 'text' | 'image';
export type HarnessAuthState = 'ready' | 'auth_required' | 'unavailable';
export type HarnessModelAvailability = 'available' | 'unavailable';
export interface HarnessCatalogEntry extends HarnessSelection {
readonly displayName: string;
readonly reasoningCapability: boolean;
readonly thinkingLevels?: readonly string[];
readonly inputTypes: readonly HarnessInputType[];
readonly contextWindow?: number;
readonly authState: HarnessAuthState;
readonly availability: HarnessModelAvailability;
}
export interface HarnessCatalog {
readonly harnessId: string;
readonly version: string;
readonly fingerprint: string;
readonly models: readonly HarnessCatalogEntry[];
}
export interface CreateHarnessSession {
readonly context: HarnessActorContext;
readonly conversationId: string;
readonly selection: HarnessSelection;
}
export interface ResumeHarnessSession {
readonly context: HarnessActorContext;
readonly conversationId: string;
readonly nativeSessionId: string;
readonly selection: HarnessSelection;
}
export type HarnessSessionState = 'starting' | 'idle' | 'busy' | 'evicted' | 'ended' | 'failed';
export interface HarnessSessionSnapshot {
readonly conversationId: string;
readonly nativeSessionId: string;
/** Absent when the resumable native session has no active process. */
readonly processId?: string;
readonly seatId: string;
readonly selection: HarnessSelection;
readonly state: HarnessSessionState;
readonly attachedClientIds: readonly string[];
}
export interface AttachClient {
readonly clientId: string;
}
export interface HarnessPrompt {
readonly turnId: string;
readonly correlationId: string;
readonly content: string;
}
export type HarnessTurnState =
| 'prepared'
| 'dispatching'
| 'accepted'
| 'streaming'
| 'settled'
| 'failed'
| 'aborted'
| 'interrupted'
| 'outcome_unknown';
/** A successful receipt reports only the selected tuple; no substitute tuple is representable. */
export interface HarnessPromptReceipt {
readonly conversationId: string;
readonly turnId: string;
readonly correlationId: string;
readonly state: HarnessTurnState;
readonly selection: HarnessSelection;
}
export interface HarnessConfirmInteractionResponse {
readonly requestId: string;
readonly type: 'confirm';
readonly accepted: boolean;
}
export interface HarnessSelectInteractionResponse {
readonly requestId: string;
readonly type: 'select';
readonly value: string;
}
export interface HarnessInputInteractionResponse {
readonly requestId: string;
readonly type: 'input';
readonly value: string;
}
export interface HarnessEditorInteractionResponse {
readonly requestId: string;
readonly type: 'editor';
readonly value: string;
}
export interface HarnessCancelInteractionResponse {
readonly requestId: string;
readonly type: 'cancel';
}
export type HarnessInteractionResponse =
| HarnessConfirmInteractionResponse
| HarnessSelectInteractionResponse
| HarnessInputInteractionResponse
| HarnessEditorInteractionResponse
| HarnessCancelInteractionResponse;
export type HarnessCloseReason =
| 'client_request'
| 'idle_timeout'
| 'gateway_shutdown'
| 'process_crash'
| 'composition_changed'
| 'session_ended';
export interface AttachConversation {
readonly context: HarnessActorContext;
readonly conversationId: string;
readonly clientId: string;
readonly selection: HarnessSelection;
}
export interface ConversationSnapshot {
readonly session: HarnessSessionSnapshot;
readonly lastSequence: number;
/** Journal rows replayed after the caller's sequence, never best-effort socket history. */
readonly replay: readonly HarnessEventEnvelope[];
}
export interface DetachConversation {
readonly context: HarnessActorContext;
readonly conversationId: string;
readonly clientId: string;
}
export interface SendHarnessTurn extends HarnessPrompt {
readonly context: HarnessActorContext;
readonly conversationId: string;
readonly selection: HarnessSelection;
}
export interface TurnReceipt extends HarnessPromptReceipt {}
export interface HarnessAdapter {
readonly id: string;
describe(context: HarnessActorContext): Promise<HarnessDescriptor>;
catalog(context: HarnessActorContext): Promise<HarnessCatalog>;
create(input: CreateHarnessSession): Promise<HarnessSessionHandle>;
resume(input: ResumeHarnessSession): Promise<HarnessSessionHandle>;
}
export interface HarnessSessionHandle {
snapshot(): Promise<HarnessSessionSnapshot>;
attach(input: AttachClient): Promise<void>;
/** Removes a browser attachment; it does not terminate the process or native session. */
detach(clientId: string): Promise<void>;
prompt(input: HarnessPrompt & { idempotencyKey: string }): Promise<HarnessPromptReceipt>;
setModel(selection: HarnessSelection): Promise<HarnessSelection>;
abort(turnId: string): Promise<void>;
respondInteraction(input: HarnessInteractionResponse): Promise<void>;
events(listener: (event: HarnessEvent) => void): () => void;
/** Stops the active process while retaining the resumable native session. */
evictProcess(reason: HarnessCloseReason): Promise<void>;
/** Explicitly and destructively ends the native session. */
endSession(reason: HarnessCloseReason): Promise<void>;
}
export interface HarnessConversationService {
attach(input: AttachConversation & { afterSequence?: number }): Promise<ConversationSnapshot>;
/** Removes only the browser attachment represented by the input. */
detach(input: DetachConversation): Promise<void>;
send(input: SendHarnessTurn & { idempotencyKey: string }): Promise<TurnReceipt>;
/** Replays persisted Gateway journal rows after the supplied monotonic sequence. */
subscribeFrom(conversationId: string, afterSequence: number): AsyncIterable<HarnessEventEnvelope>;
}
+40
View File
@@ -0,0 +1,40 @@
import type { HarnessSelection } from './contracts.js';
export const HARNESS_ERROR_CODES = [
'auth_required',
'selection_invalid',
'catalog_unavailable',
'catalog_stale',
'model_unavailable',
'no_viable_provider',
'session_create_failed',
'session_not_found',
'resume_conflict',
'session_busy',
'auth_bundle_concurrency_unverified',
'adapter_unavailable',
'sandbox_unavailable',
'rpc_version_unsupported',
'rpc_protocol_error',
'process_exited',
'outcome_unknown',
'interaction_unsupported',
'aborted',
] as const satisfies readonly string[];
export type HarnessErrorCode = (typeof HARNESS_ERROR_CODES)[number];
export interface HarnessErrorDto<Code extends HarnessErrorCode = HarnessErrorCode> {
readonly code: Code;
/** Safe for browser and operator-facing surfaces. */
readonly message: string;
readonly retryable: boolean;
readonly correlationId: string;
/** The requested selection; errors never report a substituted effective selection. */
readonly selection: HarnessSelection;
}
/** Closed discriminated union over every stable harness error code. */
export type HarnessError = {
readonly [Code in HarnessErrorCode]: HarnessErrorDto<Code>;
}[HarnessErrorCode];
+139
View File
@@ -0,0 +1,139 @@
import type { HarnessError } from './errors.js';
import type { HarnessSelection, HarnessSessionState } from './contracts.js';
export const HARNESS_CAPABILITIES = [
'modelSelection',
'thinkingLevels',
'images',
'toolEvents',
'extensionUi',
'steering',
'followUp',
'compaction',
'persistentResume',
] as const satisfies readonly string[];
export type HarnessCapability = (typeof HARNESS_CAPABILITIES)[number];
/** Durable lifecycle states; later persistence enforces one terminal response per request. */
export type HarnessInteractionState = 'pending' | 'responded' | 'cancelled' | 'expired';
export type HarnessInteractionType = 'confirm' | 'select' | 'input' | 'editor';
export interface HarnessUsage {
readonly inputTokens: number;
readonly outputTokens: number;
readonly totalTokens: number;
}
export type HarnessTurnOutcome =
| 'settled'
| 'failed'
| 'aborted'
| 'interrupted'
| 'outcome_unknown';
export interface HarnessSessionStartedEvent {
readonly type: 'session.started';
readonly state: HarnessSessionState;
}
export interface HarnessSessionStateEvent {
readonly type: 'session.state';
readonly state: HarnessSessionState;
}
export interface HarnessSessionIdentityChangedEvent {
readonly type: 'session.identity_changed';
readonly identityGeneration: number;
readonly label: string;
}
export interface HarnessTurnStartedEvent {
readonly type: 'turn.started';
}
export interface HarnessTextDeltaEvent {
readonly type: 'text.delta';
readonly text: string;
}
export interface HarnessThinkingDeltaEvent {
readonly type: 'thinking.delta';
readonly text: string;
}
export interface HarnessToolStartedEvent {
readonly type: 'tool.started';
readonly toolCallId: string;
readonly toolName: string;
}
export interface HarnessToolUpdatedEvent {
readonly type: 'tool.updated';
readonly toolCallId: string;
readonly toolName: string;
readonly message: string;
}
export interface HarnessToolFinishedEvent {
readonly type: 'tool.finished';
readonly toolCallId: string;
readonly toolName: string;
readonly isError: boolean;
}
export interface HarnessInteractionRequiredEvent {
readonly type: 'interaction.required';
readonly requestId: string;
readonly interactionType: HarnessInteractionType;
readonly state: HarnessInteractionState;
readonly prompt: string;
readonly options?: readonly string[];
}
export interface HarnessUsageUpdatedEvent {
readonly type: 'usage.updated';
readonly usage: HarnessUsage;
}
export interface HarnessTurnCompletedEvent {
readonly type: 'turn.completed';
readonly outcome: HarnessTurnOutcome;
}
export interface HarnessErrorEvent {
readonly type: 'error';
readonly error: HarnessError;
}
export type HarnessEvent =
| HarnessSessionStartedEvent
| HarnessSessionStateEvent
| HarnessSessionIdentityChangedEvent
| HarnessTurnStartedEvent
| HarnessTextDeltaEvent
| HarnessThinkingDeltaEvent
| HarnessToolStartedEvent
| HarnessToolUpdatedEvent
| HarnessToolFinishedEvent
| HarnessInteractionRequiredEvent
| HarnessUsageUpdatedEvent
| HarnessTurnCompletedEvent
| HarnessErrorEvent;
/** Persisted normalized event plus Gateway-owned ordering and native reconciliation metadata. */
export interface HarnessEventEnvelope {
readonly conversationId: string;
readonly nativeSessionId: string;
readonly turnId?: string;
readonly correlationId: string;
/** Monotonic Gateway journal sequence within the conversation. */
readonly sequence: number;
/** Native session-entry cursor when the harness provides one. */
readonly nativeEntryCursor?: string;
readonly occurredAt: string;
readonly harnessId: string;
/** Exact effective selected provider/model tuple; no alternate success selection is exposed. */
readonly selection: HarnessSelection;
readonly event: HarnessEvent;
}
+3
View File
@@ -0,0 +1,3 @@
export * from './contracts.js';
export * from './events.js';
export * from './errors.js';
+1
View File
@@ -8,3 +8,4 @@ export * from './routing/index.js';
export * from './commands/index.js'; export * from './commands/index.js';
export * from './federation/index.js'; export * from './federation/index.js';
export * from './reflection/index.js'; export * from './reflection/index.js';
export * from './harness/index.js';