Skip to content

Commit 6de3ed0

Browse files
juliusmarmingecodex
andcommitted
Add Claude integration coverage across orchestration flows
Co-authored-by: codex <[email protected]>
1 parent 8f0f813 commit 6de3ed0

File tree

4 files changed

+524
-43
lines changed

4 files changed

+524
-43
lines changed

apps/server/integration/OrchestrationEngineHarness.integration.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,19 @@ export interface OrchestrationIntegrationHarness {
182182
readonly dispose: Effect.Effect<void, never>;
183183
}
184184

185-
export const makeOrchestrationIntegrationHarness = Effect.gen(function* () {
185+
interface MakeOrchestrationIntegrationHarnessOptions {
186+
readonly provider?: "codex" | "claudeCode";
187+
}
188+
189+
export const makeOrchestrationIntegrationHarness = (
190+
options?: MakeOrchestrationIntegrationHarnessOptions,
191+
) =>
192+
Effect.gen(function* () {
186193
const sleep = (ms: number) => Effect.sleep(ms);
187-
const adapterHarness = yield* makeTestProviderAdapterHarness;
194+
const provider = options?.provider ?? "codex";
195+
const adapterHarness = yield* makeTestProviderAdapterHarness({
196+
provider,
197+
});
188198

189199
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-orchestration-integration-"));
190200
const workspaceDir = path.join(rootDir, "workspace");
@@ -196,10 +206,10 @@ export const makeOrchestrationIntegrationHarness = Effect.gen(function* () {
196206

197207
const registry: typeof ProviderAdapterRegistry.Service = {
198208
getByProvider: (provider) =>
199-
provider === "codex"
209+
provider === adapterHarness.provider
200210
? Effect.succeed(adapterHarness.adapter)
201211
: Effect.fail(new ProviderUnsupportedError({ provider })),
202-
listProviders: () => Effect.succeed(["codex"]),
212+
listProviders: () => Effect.succeed([adapterHarness.provider]),
203213
};
204214

205215
const persistenceLayer = makeSqlitePersistenceLive(dbPath);

apps/server/integration/TestProviderAdapter.integration.ts

Lines changed: 74 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -42,61 +42,76 @@ interface SessionState {
4242

4343
export interface TestProviderAdapterHarness {
4444
readonly adapter: ProviderAdapterShape<ProviderAdapterError>;
45+
readonly provider: "codex" | "claudeCode";
4546
readonly queueTurnResponse: (
4647
sessionId: string,
4748
response: TestTurnResponse,
4849
) => Effect.Effect<void, ProviderAdapterSessionNotFoundError>;
4950
readonly queueTurnResponseForNextSession: (
5051
response: TestTurnResponse,
5152
) => Effect.Effect<void, never>;
53+
readonly getStartCount: () => number;
5254
readonly getRollbackCalls: (sessionId: string) => ReadonlyArray<number>;
55+
readonly getInterruptCalls: (sessionId: string) => ReadonlyArray<ProviderTurnId | undefined>;
56+
readonly listActiveSessionIds: () => ReadonlyArray<ProviderSessionId>;
5357
readonly getApprovalResponses: (sessionId: string) => ReadonlyArray<{
5458
readonly sessionId: ProviderSessionId;
5559
readonly requestId: ApprovalRequestId;
5660
readonly decision: ProviderApprovalDecision;
5761
}>;
5862
}
5963

60-
const PROVIDER = "codex" as const;
64+
interface MakeTestProviderAdapterHarnessOptions {
65+
readonly provider?: "codex" | "claudeCode";
66+
}
6167

6268
function nowIso(): string {
6369
return new Date().toISOString();
6470
}
6571

66-
function sessionNotFound(sessionId: string): ProviderAdapterSessionNotFoundError {
72+
function sessionNotFound(
73+
provider: "codex" | "claudeCode",
74+
sessionId: string,
75+
): ProviderAdapterSessionNotFoundError {
6776
return new ProviderAdapterSessionNotFoundError({
68-
provider: PROVIDER,
77+
provider,
6978
sessionId,
7079
});
7180
}
7281

73-
function missingSessionEffect(sessionId: string): Effect.Effect<never, ProviderAdapterError> {
74-
return Effect.fail(sessionNotFound(sessionId));
82+
function missingSessionEffect(
83+
provider: "codex" | "claudeCode",
84+
sessionId: string,
85+
): Effect.Effect<never, ProviderAdapterError> {
86+
return Effect.fail(sessionNotFound(provider, sessionId));
7587
}
7688

77-
export const makeTestProviderAdapterHarness = Effect.gen(function* () {
78-
const runtimeEvents = yield* Queue.unbounded<ProviderRuntimeEvent>();
79-
let sessionCount = 0;
80-
const sessions = new Map<string, SessionState>();
81-
const queuedResponsesForNextSession: TestTurnResponse[] = [];
82-
const approvalResponsesBySession = new Map<
83-
string,
84-
Array<{
85-
readonly sessionId: ProviderSessionId;
86-
readonly requestId: ApprovalRequestId;
87-
readonly decision: ProviderApprovalDecision;
88-
}>
89-
>();
89+
export const makeTestProviderAdapterHarness = (options?: MakeTestProviderAdapterHarnessOptions) =>
90+
Effect.gen(function* () {
91+
const provider = options?.provider ?? "codex";
92+
const runtimeEvents = yield* Queue.unbounded<ProviderRuntimeEvent>();
93+
let sessionCount = 0;
94+
const sessions = new Map<string, SessionState>();
95+
const queuedResponsesForNextSession: TestTurnResponse[] = [];
96+
const interruptCallsBySession = new Map<string, Array<ProviderTurnId | undefined>>();
97+
const approvalResponsesBySession = new Map<
98+
string,
99+
Array<{
100+
readonly sessionId: ProviderSessionId;
101+
readonly requestId: ApprovalRequestId;
102+
readonly decision: ProviderApprovalDecision;
103+
}>
104+
>();
90105

91106
const emit = (event: ProviderRuntimeEvent) => Queue.offer(runtimeEvents, event);
92107

93108
const startSession: ProviderAdapterShape<ProviderAdapterError>["startSession"] = (input) =>
94109
Effect.gen(function* () {
95-
if (input.provider !== undefined && input.provider !== PROVIDER) {
110+
if (input.provider !== undefined && input.provider !== provider) {
96111
return yield* new ProviderAdapterValidationError({
97-
provider: PROVIDER,
112+
provider,
98113
operation: "startSession",
99-
issue: `Expected provider '${PROVIDER}' but received '${input.provider}'.`,
114+
issue: `Expected provider '${provider}' but received '${input.provider}'.`,
100115
});
101116
}
102117

@@ -107,10 +122,11 @@ export const makeTestProviderAdapterHarness = Effect.gen(function* () {
107122

108123
const session: ProviderSession = {
109124
sessionId,
110-
provider: PROVIDER,
125+
provider,
111126
status: "ready",
112127
threadId,
113128
cwd: input.cwd,
129+
resumeCursor: input.resumeCursor ?? { sessionId },
114130
createdAt,
115131
updatedAt: createdAt,
116132
};
@@ -133,7 +149,7 @@ export const makeTestProviderAdapterHarness = Effect.gen(function* () {
133149
Effect.gen(function* () {
134150
const state = sessions.get(input.sessionId);
135151
if (!state) {
136-
return yield* missingSessionEffect(input.sessionId);
152+
return yield* missingSessionEffect(provider, input.sessionId);
137153
}
138154

139155
state.turnCount += 1;
@@ -143,7 +159,7 @@ export const makeTestProviderAdapterHarness = Effect.gen(function* () {
143159
const response = state.queuedResponses.shift();
144160
if (!response) {
145161
return yield* new ProviderAdapterValidationError({
146-
provider: PROVIDER,
162+
provider,
147163
operation: "sendTurn",
148164
issue: `No queued turn response for session ${input.sessionId}.`,
149165
});
@@ -155,7 +171,7 @@ export const makeTestProviderAdapterHarness = Effect.gen(function* () {
155171
const rawEvent: Record<string, unknown> = {
156172
...(fixtureEvent as Record<string, unknown>),
157173
eventId: randomUUID(),
158-
provider: PROVIDER,
174+
provider,
159175
sessionId: input.sessionId,
160176
createdAt: nowIso(),
161177
};
@@ -206,7 +222,7 @@ export const makeTestProviderAdapterHarness = Effect.gen(function* () {
206222
yield* emit({
207223
type: "turn.completed",
208224
eventId: EventId.makeUnsafe(randomUUID()),
209-
provider: PROVIDER,
225+
provider,
210226
sessionId: input.sessionId,
211227
createdAt: nowIso(),
212228
threadId: state.snapshot.threadId,
@@ -227,8 +243,15 @@ export const makeTestProviderAdapterHarness = Effect.gen(function* () {
227243

228244
const interruptTurn: ProviderAdapterShape<ProviderAdapterError>["interruptTurn"] = (
229245
sessionId,
230-
_turnId,
231-
) => (sessions.has(sessionId) ? Effect.void : missingSessionEffect(sessionId));
246+
turnId,
247+
) =>
248+
sessions.has(sessionId)
249+
? Effect.sync(() => {
250+
const existing = interruptCallsBySession.get(sessionId) ?? [];
251+
existing.push(turnId);
252+
interruptCallsBySession.set(sessionId, existing);
253+
})
254+
: missingSessionEffect(provider, sessionId);
232255

233256
const respondToRequest: ProviderAdapterShape<ProviderAdapterError>["respondToRequest"] = (
234257
sessionId,
@@ -245,7 +268,7 @@ export const makeTestProviderAdapterHarness = Effect.gen(function* () {
245268
});
246269
approvalResponsesBySession.set(sessionId, existing);
247270
})
248-
: missingSessionEffect(sessionId);
271+
: missingSessionEffect(provider, sessionId);
249272

250273
const stopSession: ProviderAdapterShape<ProviderAdapterError>["stopSession"] = (sessionId) =>
251274
Effect.sync(() => {
@@ -261,7 +284,7 @@ export const makeTestProviderAdapterHarness = Effect.gen(function* () {
261284
const readThread: ProviderAdapterShape<ProviderAdapterError>["readThread"] = (sessionId) => {
262285
const state = sessions.get(sessionId);
263286
if (!state) {
264-
return missingSessionEffect(sessionId);
287+
return missingSessionEffect(provider, sessionId);
265288
}
266289
return Effect.succeed(state.snapshot);
267290
};
@@ -272,12 +295,12 @@ export const makeTestProviderAdapterHarness = Effect.gen(function* () {
272295
) => {
273296
const state = sessions.get(sessionId);
274297
if (!state) {
275-
return missingSessionEffect(sessionId);
298+
return missingSessionEffect(provider, sessionId);
276299
}
277300
if (!Number.isInteger(numTurns) || numTurns < 0 || numTurns > state.snapshot.turns.length) {
278301
return Effect.fail(
279302
new ProviderAdapterValidationError({
280-
provider: PROVIDER,
303+
provider,
281304
operation: "rollbackThread",
282305
issue: "numTurns must be an integer between 0 and current turn count.",
283306
}),
@@ -301,7 +324,7 @@ export const makeTestProviderAdapterHarness = Effect.gen(function* () {
301324
});
302325

303326
const adapter: ProviderAdapterShape<ProviderAdapterError> = {
304-
provider: PROVIDER,
327+
provider,
305328
startSession,
306329
sendTurn,
307330
interruptTurn,
@@ -325,7 +348,7 @@ export const makeTestProviderAdapterHarness = Effect.gen(function* () {
325348
? Effect.sync(() => {
326349
state.queuedResponses.push(response);
327350
})
328-
: Effect.fail(sessionNotFound(sessionId)),
351+
: Effect.fail(sessionNotFound(provider, sessionId)),
329352
),
330353
);
331354

@@ -344,6 +367,19 @@ export const makeTestProviderAdapterHarness = Effect.gen(function* () {
344367
return [...state.rollbackCalls];
345368
};
346369

370+
const getStartCount = (): number => sessionCount;
371+
372+
const getInterruptCalls = (sessionId: string): ReadonlyArray<ProviderTurnId | undefined> => {
373+
const calls = interruptCallsBySession.get(sessionId);
374+
if (!calls) {
375+
return [];
376+
}
377+
return [...calls];
378+
};
379+
380+
const listActiveSessionIds = (): ReadonlyArray<ProviderSessionId> =>
381+
Array.from(sessions.values(), (state) => state.session.sessionId);
382+
347383
const getApprovalResponses = (
348384
sessionId: string,
349385
): ReadonlyArray<{
@@ -360,9 +396,13 @@ export const makeTestProviderAdapterHarness = Effect.gen(function* () {
360396

361397
return {
362398
adapter,
399+
provider,
363400
queueTurnResponse,
364401
queueTurnResponseForNextSession,
402+
getStartCount,
365403
getRollbackCalls,
404+
getInterruptCalls,
405+
listActiveSessionIds,
366406
getApprovalResponses,
367407
} satisfies TestProviderAdapterHarness;
368408
});

0 commit comments

Comments
 (0)