Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion packages/provider-opencode/src/server/textGeneration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,24 @@ const advanceIdleClock = Effect.gen(function* () {
});

it.layer(layerOpenCodeTextGenerationTest)("OpenCodeTextGeneration", (it) => {
it.effect("cancels an in-flight session creation before prompting", () =>
it.effect("cancels or times out an in-flight session creation before prompting", () =>
withOpenCodeTextGeneration(EXISTING_SERVER_OPENCODE_SETTINGS, (textGeneration) =>
Effect.gen(function* () {
const timedOut = yield* Deferred.make<AbortSignal | undefined>();
runtimeMock.state.onSessionCreate = (signal) => {
Deferred.doneUnsafe(timedOut, Effect.succeed(signal));
return new Promise<never>(() => {});
};
const timedOutFiber = yield* textGeneration
.generateCommitMessage(DEFAULT_COMMIT_MESSAGE_INPUT)
.pipe(Effect.forkChild);
const timedOutSignal = yield* Deferred.await(timedOut);
yield* TestClock.adjust(Duration.minutes(3));
const timeoutError = yield* Fiber.join(timedOutFiber).pipe(Effect.flip);
expect(timeoutError).toBeInstanceOf(TextGenerationError);
expect(timeoutError.detail).toBe("OpenCode did not finish generating in time.");
expect(timedOutSignal?.aborted).toBe(true);

const started = yield* Deferred.make<AbortSignal | undefined>();
runtimeMock.state.onSessionCreate = (signal) => {
Deferred.doneUnsafe(started, Effect.succeed(signal));
Expand Down Expand Up @@ -357,6 +372,34 @@ it.layer(layerOpenCodeTextGenerationTest)("OpenCodeTextGeneration", (it) => {
),
);

it.effect("gives up and aborts the session when a prompt never settles", () =>
withOpenCodeTextGeneration(DEFAULT_OPENCODE_SETTINGS, (textGeneration) =>
Effect.gen(function* () {
const started = yield* Deferred.make<AbortSignal | undefined>();
let sessionAborts = 0;
runtimeMock.state.onPrompt = (signal) => {
Deferred.doneUnsafe(started, Effect.succeed(signal));
return new Promise<never>(() => {});
};
runtimeMock.state.onAbort = () => {
sessionAborts += 1;
return Promise.reject(new Error("abort observed"));
};
const fiber = yield* textGeneration
.generateCommitMessage(DEFAULT_COMMIT_MESSAGE_INPUT)
.pipe(Effect.forkChild);
const signal = yield* Deferred.await(started);
yield* TestClock.adjust(Duration.minutes(3));

const error = yield* Fiber.join(fiber).pipe(Effect.flip);
expect(error).toBeInstanceOf(TextGenerationError);
expect(error.detail).toBe("OpenCode did not finish generating in time.");
expect(signal?.aborted).toBe(true);
expect(sessionAborts).toBe(1);
}),
),
);

it.effect("uses an environment-only password for a locally spawned server", () =>
withOpenCodeTextGeneration(
DEFAULT_OPENCODE_SETTINGS,
Expand Down
12 changes: 12 additions & 0 deletions packages/provider-opencode/src/server/textGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import * as TextGenerationOperations from "@t3tools/provider-core/server/textGen
import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
import * as OpenCodeServerOwner from "./OpenCodeServerOwner.ts";

const GENERATION_TIMEOUT = "3 minutes";

const OpenCodeTextGenerationOperation = Schema.Literals([
"generateCommitMessage",
"generatePrContent",
Expand Down Expand Up @@ -333,6 +335,16 @@ export const makeOpenCodeTextGeneration = Effect.fn("makeOpenCodeTextGeneration"
.pipe(Effect.flatMap(runAgainstServer), Effect.scoped)
: serverOwner.withServer(runAgainstServer);
const rawOutput = yield* serverOutput.pipe(
Effect.timeoutOrElse({
duration: GENERATION_TIMEOUT,
orElse: () =>
Effect.fail(
new TextGenerationError({
operation: input.operation,
detail: "OpenCode did not finish generating in time.",
}),
),
}),
Effect.catchTags({
OpenCodeRuntimeError: (cause) =>
Effect.fail(
Expand Down
Loading