import * as Effect from "effect/Effect";import * as Schema from "effect/Schema"; import { NonNegativeInt, TextGenerationError, type ChatAttachment, type ModelSelection, type OpenCodeSettings,} from "@t3tools/contracts";import { sanitizeBranchFragment, sanitizeFeatureBranchName } from "@t3tools/shared/git";import { getModelSelectionStringOptionValue } from "@t3tools/shared/model";import { extractJsonObject } from "@t3tools/shared/schemaJson"; import * as ServerConfig from "../config.ts";import { resolveAttachmentPath } from "../attachmentStore.ts";import { buildBranchNamePrompt, buildCommitMessagePrompt, buildPrContentPrompt, buildThreadTitlePrompt,} from "./TextGenerationPrompts.ts";import * as TextGeneration from "./TextGeneration.ts";import { sanitizeCommitSubject, sanitizePrTitle, sanitizeThreadTitle,} from "./TextGenerationUtils.ts";import * as OpenCodeRuntime from "../provider/opencodeRuntime.ts";import * as OpenCodeServerOwner from "../provider/OpenCodeServerOwner.ts"; const OpenCodeTextGenerationOperation = Schema.Literals([ "generateCommitMessage", "generatePrContent", "generateBranchName", "generateThreadTitle",]); type OpenCodeTextGenerationOperation = typeof OpenCodeTextGenerationOperation.Type; const openCodeTextGenerationErrorContext = { operation: OpenCodeTextGenerationOperation, cwd: Schema.String,}; export class OpenCodeTextGenerationSessionRequestError extends Schema.TaggedErrorClass<OpenCodeTextGenerationSessionRequestError>()( "OpenCodeTextGenerationSessionRequestError", { ...openCodeTextGenerationErrorContext, cause: Schema.Defect(), },) { override get message(): string { return `OpenCode session creation request failed for ${this.operation} in ${this.cwd}.`; }} export class OpenCodeTextGenerationSessionPayloadError extends Schema.TaggedErrorClass<OpenCodeTextGenerationSessionPayloadError>()( "OpenCodeTextGenerationSessionPayloadError", openCodeTextGenerationErrorContext,) { override get message(): string { return `OpenCode session.create returned no session payload for ${this.operation} in ${this.cwd}.`; }} const openCodePromptErrorContext = { ...openCodeTextGenerationErrorContext, sessionId: Schema.String, providerId: Schema.String, modelId: Schema.String,}; export class OpenCodeTextGenerationPromptRequestError extends Schema.TaggedErrorClass<OpenCodeTextGenerationPromptRequestError>()( "OpenCodeTextGenerationPromptRequestError", { ...openCodePromptErrorContext, cause: Schema.Defect(), },) { override get message(): string { return `OpenCode prompt request failed for ${this.operation} in ${this.cwd} using ${this.providerId}/${this.modelId} (session ${this.sessionId}).`; }} export class OpenCodeTextGenerationPromptResponseError extends Schema.TaggedErrorClass<OpenCodeTextGenerationPromptResponseError>()( "OpenCodeTextGenerationPromptResponseError", { ...openCodePromptErrorContext, providerErrorName: Schema.optional(Schema.String), providerMessage: Schema.String, },) { override get message(): string { const providerError = this.providerErrorName ? ` ${this.providerErrorName}` : ""; return `OpenCode prompt${providerError} failed for ${this.operation} in ${this.cwd} using ${this.providerId}/${this.modelId} (session ${this.sessionId}): ${this.providerMessage}`; }} export class OpenCodeTextGenerationEmptyOutputError extends Schema.TaggedErrorClass<OpenCodeTextGenerationEmptyOutputError>()( "OpenCodeTextGenerationEmptyOutputError", { ...openCodePromptErrorContext, responsePartCount: NonNegativeInt, textPartCount: NonNegativeInt, },) { override get message(): string { return `OpenCode returned empty output for ${this.operation} in ${this.cwd} using ${this.providerId}/${this.modelId} (session ${this.sessionId}, ${this.responsePartCount} response parts, ${this.textPartCount} text parts).`; }} interface OpenCodePromptFailure { readonly name?: string; readonly message: string;} interface OpenCodeTextPart { readonly type: "text"; readonly text: string;} function getOpenCodePromptFailure(error: unknown): OpenCodePromptFailure | null { if (!error || typeof error !== "object") { return null; } const name = "name" in error && typeof error.name === "string" && error.name.trim().length > 0 ? error.name.trim() : undefined; const message = "data" in error && error.data && typeof error.data === "object" && "message" in error.data && typeof error.data.message === "string" ? error.data.message.trim() : ""; if (message.length > 0) { return { ...(name ? { name } : {}), message, }; } if (name) { return { name, message: name }; } return null;} function isOpenCodeTextPart(part: unknown): part is OpenCodeTextPart { return ( part !== null && typeof part === "object" && "type" in part && part.type === "text" && "text" in part && typeof part.text === "string" );} function getOpenCodeTextResponse(parts: ReadonlyArray<unknown> | undefined): string { return (parts ?? []) .filter(isOpenCodeTextPart) .map((part) => part.text) .join("") .trim();} export const makeOpenCodeTextGeneration = Effect.fn("makeOpenCodeTextGeneration")(function* ( openCodeSettings: OpenCodeSettings,) { const serverConfig = yield* ServerConfig.ServerConfig; const openCodeRuntime = yield* OpenCodeRuntime.OpenCodeRuntime; const serverOwner = yield* OpenCodeServerOwner.OpenCodeServerOwner; const runOpenCodeJson = Effect.fn("runOpenCodeJson")(function* <S extends Schema.Top>(input: { readonly operation: OpenCodeTextGenerationOperation; readonly cwd: string; readonly prompt: string; readonly outputSchemaJson: S; readonly modelSelection: ModelSelection; readonly attachments?: ReadonlyArray<ChatAttachment> | undefined; }) { const parsedModel = OpenCodeRuntime.parseOpenCodeModelSlug(input.modelSelection.model); if (!parsedModel) { return yield* new TextGenerationError({ operation: input.operation, detail: "OpenCode model selection must use the 'provider/model' format.", }); } const fileParts = OpenCodeRuntime.toOpenCodeFileParts({ attachments: input.attachments?.filter((attachment) => attachment.type === "image"), resolveAttachmentPath: (attachment) => resolveAttachmentPath({ attachmentsDir: serverConfig.attachmentsDir, attachment }), }); const runAgainstServer = Effect.fn("runOpenCodeJson.runAgainstServer")( function* ( server: Pick< OpenCodeRuntime.OpenCodeServerConnection, "url" | "serverPassword" | "version" >, ) { const client = openCodeRuntime.createOpenCodeSdkClient({ baseUrl: server.url, directory: input.cwd, ...(server.serverPassword !== undefined ? { serverPassword: server.serverPassword } : {}), }); const session = yield* Effect.tryPromise({ try: () => client.session.create({ title: `T3 Code ${input.operation}`, permission: [{ permission: "*", pattern: "*", action: "deny" }], }), catch: (cause) => new OpenCodeTextGenerationSessionRequestError({ operation: input.operation, cwd: input.cwd, cause, }), }); if (!session.data) { return yield* new OpenCodeTextGenerationSessionPayloadError({ operation: input.operation, cwd: input.cwd, }); } const selectedAgent = getModelSelectionStringOptionValue(input.modelSelection, "agent"); const selectedVariant = getModelSelectionStringOptionValue(input.modelSelection, "variant"); const promptContext = { operation: input.operation, cwd: input.cwd, sessionId: session.data.id, providerId: parsedModel.providerID, modelId: parsedModel.modelID, }; const result = yield* Effect.tryPromise({ try: () => client.session.prompt({ sessionID: session.data.id, model: parsedModel, ...(selectedAgent ? { agent: selectedAgent } : {}), ...(selectedVariant ? { variant: selectedVariant } : {}), parts: [{ type: "text", text: input.prompt }, ...fileParts], }), catch: (cause) => new OpenCodeTextGenerationPromptRequestError({ ...promptContext, cause, }), }); const promptFailure = getOpenCodePromptFailure(result.data?.info?.error); if (promptFailure) { return yield* new OpenCodeTextGenerationPromptResponseError({ ...promptContext, ...(promptFailure.name ? { providerErrorName: promptFailure.name } : {}), providerMessage: promptFailure.message, }); } const responseParts = result.data?.parts ?? []; const rawText = getOpenCodeTextResponse(responseParts); if (rawText.length === 0) { return yield* new OpenCodeTextGenerationEmptyOutputError({ ...promptContext, responsePartCount: responseParts.length, textPartCount: responseParts.filter(isOpenCodeTextPart).length, }); } return rawText; }, Effect.catchTags({ OpenCodeTextGenerationSessionRequestError: (cause) => Effect.fail( new TextGenerationError({ operation: cause.operation, detail: "OpenCode session.create request failed.", cause, }), ), OpenCodeTextGenerationSessionPayloadError: (cause) => Effect.fail( new TextGenerationError({ operation: cause.operation, detail: "OpenCode session.create returned no session payload.", cause, }), ), OpenCodeTextGenerationPromptRequestError: (cause) => Effect.fail( new TextGenerationError({ operation: cause.operation, detail: "OpenCode session.prompt request failed.", cause, }), ), OpenCodeTextGenerationPromptResponseError: (cause) => Effect.fail( new TextGenerationError({ operation: cause.operation, detail: cause.providerMessage, cause, }), ), OpenCodeTextGenerationEmptyOutputError: (cause) => Effect.fail( new TextGenerationError({ operation: cause.operation, detail: "OpenCode returned empty output.", cause, }), ), }), ); const serverOutput = openCodeSettings.serverUrl.length > 0 ? openCodeRuntime .connectToOpenCodeServer({ binaryPath: openCodeSettings.binaryPath, directory: input.cwd, serverUrl: openCodeSettings.serverUrl, ...(openCodeSettings.serverPassword ? { serverPassword: openCodeSettings.serverPassword } : {}), }) .pipe(Effect.flatMap(runAgainstServer), Effect.scoped) : serverOwner.withServer(runAgainstServer); const rawOutput = yield* serverOutput.pipe( Effect.catchTags({ OpenCodeRuntimeError: (cause) => Effect.fail( new TextGenerationError({ operation: input.operation, detail: OpenCodeRuntime.openCodeRuntimeErrorDetail(cause), cause, }), ), }), ); const decodeOutput = Schema.decodeEffect(Schema.fromJsonString(input.outputSchemaJson)); return yield* decodeOutput(extractJsonObject(rawOutput)).pipe( Effect.catchTags({ SchemaError: (cause) => Effect.fail( new TextGenerationError({ operation: input.operation, detail: "OpenCode returned invalid structured output.", cause, }), ), }), ); }); const generateCommitMessage: TextGeneration.TextGeneration["Service"]["generateCommitMessage"] = Effect.fn("OpenCodeTextGeneration.generateCommitMessage")(function* (input) { const { prompt, outputSchema } = buildCommitMessagePrompt({ branch: input.branch, stagedSummary: input.stagedSummary, stagedPatch: input.stagedPatch, includeBranch: input.includeBranch === true, policy: input.policy, }); const generated = yield* runOpenCodeJson({ operation: "generateCommitMessage", cwd: input.cwd, prompt, outputSchemaJson: outputSchema, modelSelection: input.modelSelection, }); return { subject: sanitizeCommitSubject(generated.subject), body: generated.body.trim(), ...("branch" in generated && typeof generated.branch === "string" ? { branch: sanitizeFeatureBranchName(generated.branch) } : {}), }; }); const generatePrContent: TextGeneration.TextGeneration["Service"]["generatePrContent"] = Effect.fn("OpenCodeTextGeneration.generatePrContent")(function* (input) { const { prompt, outputSchema } = buildPrContentPrompt({ baseBranch: input.baseBranch, headBranch: input.headBranch, commitSummary: input.commitSummary, diffSummary: input.diffSummary, diffPatch: input.diffPatch, policy: input.policy, changeRequestTemplate: input.changeRequestTemplate, }); const generated = yield* runOpenCodeJson({ operation: "generatePrContent", cwd: input.cwd, prompt, outputSchemaJson: outputSchema, modelSelection: input.modelSelection, }); return { title: sanitizePrTitle(generated.title), body: generated.body.trim(), }; }); const generateBranchName: TextGeneration.TextGeneration["Service"]["generateBranchName"] = Effect.fn("OpenCodeTextGeneration.generateBranchName")(function* (input) { const { prompt, outputSchema } = buildBranchNamePrompt({ message: input.message, attachments: input.attachments, }); const generated = yield* runOpenCodeJson({ operation: "generateBranchName", cwd: input.cwd, prompt, outputSchemaJson: outputSchema, modelSelection: input.modelSelection, attachments: input.attachments, }); return { branch: sanitizeBranchFragment(generated.branch), }; }); const generateThreadTitle: TextGeneration.TextGeneration["Service"]["generateThreadTitle"] = Effect.fn("OpenCodeTextGeneration.generateThreadTitle")(function* (input) { const { prompt, outputSchema } = buildThreadTitlePrompt({ message: input.message, previousTitle: input.previousTitle, attachments: input.attachments, }); const generated = yield* runOpenCodeJson({ operation: "generateThreadTitle", cwd: input.cwd, prompt, outputSchemaJson: outputSchema, modelSelection: input.modelSelection, attachments: input.attachments, }); return { title: sanitizeThreadTitle(generated.title), }; }); return { generateCommitMessage, generatePrContent, generateBranchName, generateThreadTitle, } satisfies TextGeneration.TextGeneration["Service"];});
import * as Effect from "effect/Effect";
import * as Schema from "effect/Schema";
import {
NonNegativeInt,
TextGenerationError,
type ChatAttachment,
type ModelSelection,
type OpenCodeSettings,
} from "@t3tools/contracts";
import { sanitizeBranchFragment, sanitizeFeatureBranchName } from "@t3tools/shared/git";
import { getModelSelectionStringOptionValue } from "@t3tools/shared/model";
import { extractJsonObject } from "@t3tools/shared/schemaJson";
import * as ServerConfig from "../config.ts";
import { resolveAttachmentPath } from "../attachmentStore.ts";
import {
buildBranchNamePrompt,
buildCommitMessagePrompt,
buildPrContentPrompt,
buildThreadTitlePrompt,
} from "./TextGenerationPrompts.ts";
import * as TextGeneration from "./TextGeneration.ts";
import {
sanitizeCommitSubject,
sanitizePrTitle,
sanitizeThreadTitle,
} from "./TextGenerationUtils.ts";
import * as OpenCodeRuntime from "../provider/opencodeRuntime.ts";
import * as OpenCodeServerOwner from "../provider/OpenCodeServerOwner.ts";
const OpenCodeTextGenerationOperation = Schema.Literals([
"generateCommitMessage",
"generatePrContent",
"generateBranchName",
"generateThreadTitle",
]);
type OpenCodeTextGenerationOperation = typeof OpenCodeTextGenerationOperation.Type;
const openCodeTextGenerationErrorContext = {
operation: OpenCodeTextGenerationOperation,
cwd: Schema.String,
};
export class OpenCodeTextGenerationSessionRequestError extends Schema.TaggedErrorClass<OpenCodeTextGenerationSessionRequestError>()(
"OpenCodeTextGenerationSessionRequestError",
{
...openCodeTextGenerationErrorContext,
cause: Schema.Defect(),
},
) {
override get message(): string {
return `OpenCode session creation request failed for ${this.operation} in ${this.cwd}.`;
}
}
export class OpenCodeTextGenerationSessionPayloadError extends Schema.TaggedErrorClass<OpenCodeTextGenerationSessionPayloadError>()(
"OpenCodeTextGenerationSessionPayloadError",
openCodeTextGenerationErrorContext,
) {
override get message(): string {
return `OpenCode session.create returned no session payload for ${this.operation} in ${this.cwd}.`;
}
}
const openCodePromptErrorContext = {
...openCodeTextGenerationErrorContext,
sessionId: Schema.String,
providerId: Schema.String,
modelId: Schema.String,
};
export class OpenCodeTextGenerationPromptRequestError extends Schema.TaggedErrorClass<OpenCodeTextGenerationPromptRequestError>()(
"OpenCodeTextGenerationPromptRequestError",
{
...openCodePromptErrorContext,
cause: Schema.Defect(),
},
) {
override get message(): string {
return `OpenCode prompt request failed for ${this.operation} in ${this.cwd} using ${this.providerId}/${this.modelId} (session ${this.sessionId}).`;
}
}
export class OpenCodeTextGenerationPromptResponseError extends Schema.TaggedErrorClass<OpenCodeTextGenerationPromptResponseError>()(
"OpenCodeTextGenerationPromptResponseError",
{
...openCodePromptErrorContext,
providerErrorName: Schema.optional(Schema.String),
providerMessage: Schema.String,
},
) {
override get message(): string {
const providerError = this.providerErrorName ? ` ${this.providerErrorName}` : "";
return `OpenCode prompt${providerError} failed for ${this.operation} in ${this.cwd} using ${this.providerId}/${this.modelId} (session ${this.sessionId}): ${this.providerMessage}`;
}
}
export class OpenCodeTextGenerationEmptyOutputError extends Schema.TaggedErrorClass<OpenCodeTextGenerationEmptyOutputError>()(
"OpenCodeTextGenerationEmptyOutputError",
{
...openCodePromptErrorContext,
responsePartCount: NonNegativeInt,
textPartCount: NonNegativeInt,
},
) {
override get message(): string {
return `OpenCode returned empty output for ${this.operation} in ${this.cwd} using ${this.providerId}/${this.modelId} (session ${this.sessionId}, ${this.responsePartCount} response parts, ${this.textPartCount} text parts).`;
}
}
interface OpenCodePromptFailure {
readonly name?: string;
readonly message: string;
}
interface OpenCodeTextPart {
readonly type: "text";
readonly text: string;
}
function getOpenCodePromptFailure(error: unknown): OpenCodePromptFailure | null {
if (!error || typeof error !== "object") {
return null;
}
const name =
"name" in error && typeof error.name === "string" && error.name.trim().length > 0
? error.name.trim()
: undefined;
const message =
"data" in error &&
error.data &&
typeof error.data === "object" &&
"message" in error.data &&
typeof error.data.message === "string"
? error.data.message.trim()
: "";
if (message.length > 0) {
return {
...(name ? { name } : {}),
message,
};
}
if (name) {
return { name, message: name };
}
return null;
}
function isOpenCodeTextPart(part: unknown): part is OpenCodeTextPart {
return (
part !== null &&
typeof part === "object" &&
"type" in part &&
part.type === "text" &&
"text" in part &&
typeof part.text === "string"
);
}
function getOpenCodeTextResponse(parts: ReadonlyArray<unknown> | undefined): string {
return (parts ?? [])
.filter(isOpenCodeTextPart)
.map((part) => part.text)
.join("")
.trim();
}
export const makeOpenCodeTextGeneration = Effect.fn("makeOpenCodeTextGeneration")(function* (
openCodeSettings: OpenCodeSettings,
) {
const serverConfig = yield* ServerConfig.ServerConfig;
const openCodeRuntime = yield* OpenCodeRuntime.OpenCodeRuntime;
const serverOwner = yield* OpenCodeServerOwner.OpenCodeServerOwner;
const runOpenCodeJson = Effect.fn("runOpenCodeJson")(function* <S extends Schema.Top>(input: {
readonly operation: OpenCodeTextGenerationOperation;
readonly cwd: string;
readonly prompt: string;
readonly outputSchemaJson: S;
readonly modelSelection: ModelSelection;
readonly attachments?: ReadonlyArray<ChatAttachment> | undefined;
}) {
const parsedModel = OpenCodeRuntime.parseOpenCodeModelSlug(input.modelSelection.model);
if (!parsedModel) {
return yield* new TextGenerationError({
operation: input.operation,
detail: "OpenCode model selection must use the 'provider/model' format.",
});
}
const fileParts = OpenCodeRuntime.toOpenCodeFileParts({
attachments: input.attachments?.filter((attachment) => attachment.type === "image"),
resolveAttachmentPath: (attachment) =>
resolveAttachmentPath({ attachmentsDir: serverConfig.attachmentsDir, attachment }),
});
const runAgainstServer = Effect.fn("runOpenCodeJson.runAgainstServer")(
function* (
server: Pick<
OpenCodeRuntime.OpenCodeServerConnection,
"url" | "serverPassword" | "version"
>,
) {
const client = openCodeRuntime.createOpenCodeSdkClient({
baseUrl: server.url,
directory: input.cwd,
...(server.serverPassword !== undefined ? { serverPassword: server.serverPassword } : {}),
});
const session = yield* Effect.tryPromise({
try: () =>
client.session.create({
title: `T3 Code ${input.operation}`,
permission: [{ permission: "*", pattern: "*", action: "deny" }],
}),
catch: (cause) =>
new OpenCodeTextGenerationSessionRequestError({
operation: input.operation,
cwd: input.cwd,
cause,
}),
});
if (!session.data) {
return yield* new OpenCodeTextGenerationSessionPayloadError({
operation: input.operation,
cwd: input.cwd,
});
}
const selectedAgent = getModelSelectionStringOptionValue(input.modelSelection, "agent");
const selectedVariant = getModelSelectionStringOptionValue(input.modelSelection, "variant");
const promptContext = {
operation: input.operation,
cwd: input.cwd,
sessionId: session.data.id,
providerId: parsedModel.providerID,
modelId: parsedModel.modelID,
};
const result = yield* Effect.tryPromise({
try: () =>
client.session.prompt({
sessionID: session.data.id,
model: parsedModel,
...(selectedAgent ? { agent: selectedAgent } : {}),
...(selectedVariant ? { variant: selectedVariant } : {}),
parts: [{ type: "text", text: input.prompt }, ...fileParts],
}),
catch: (cause) =>
new OpenCodeTextGenerationPromptRequestError({
...promptContext,
cause,
}),
});
const promptFailure = getOpenCodePromptFailure(result.data?.info?.error);
if (promptFailure) {
return yield* new OpenCodeTextGenerationPromptResponseError({
...promptContext,
...(promptFailure.name ? { providerErrorName: promptFailure.name } : {}),
providerMessage: promptFailure.message,
});
}
const responseParts = result.data?.parts ?? [];
const rawText = getOpenCodeTextResponse(responseParts);
if (rawText.length === 0) {
return yield* new OpenCodeTextGenerationEmptyOutputError({
...promptContext,
responsePartCount: responseParts.length,
textPartCount: responseParts.filter(isOpenCodeTextPart).length,
});
}
return rawText;
},
Effect.catchTags({
OpenCodeTextGenerationSessionRequestError: (cause) =>
Effect.fail(
new TextGenerationError({
operation: cause.operation,
detail: "OpenCode session.create request failed.",
cause,
}),
),
OpenCodeTextGenerationSessionPayloadError: (cause) =>
Effect.fail(
new TextGenerationError({
operation: cause.operation,
detail: "OpenCode session.create returned no session payload.",
cause,
}),
),
OpenCodeTextGenerationPromptRequestError: (cause) =>
Effect.fail(
new TextGenerationError({
operation: cause.operation,
detail: "OpenCode session.prompt request failed.",
cause,
}),
),
OpenCodeTextGenerationPromptResponseError: (cause) =>
Effect.fail(
new TextGenerationError({
operation: cause.operation,
detail: cause.providerMessage,
cause,
}),
),
OpenCodeTextGenerationEmptyOutputError: (cause) =>
Effect.fail(
new TextGenerationError({
operation: cause.operation,
detail: "OpenCode returned empty output.",
cause,
}),
),
}),
);
const serverOutput =
openCodeSettings.serverUrl.length > 0
? openCodeRuntime
.connectToOpenCodeServer({
binaryPath: openCodeSettings.binaryPath,
directory: input.cwd,
serverUrl: openCodeSettings.serverUrl,
...(openCodeSettings.serverPassword
? { serverPassword: openCodeSettings.serverPassword }
: {}),
})
.pipe(Effect.flatMap(runAgainstServer), Effect.scoped)
: serverOwner.withServer(runAgainstServer);
const rawOutput = yield* serverOutput.pipe(
Effect.catchTags({
OpenCodeRuntimeError: (cause) =>
Effect.fail(
new TextGenerationError({
operation: input.operation,
detail: OpenCodeRuntime.openCodeRuntimeErrorDetail(cause),
cause,
}),
),
}),
);
const decodeOutput = Schema.decodeEffect(Schema.fromJsonString(input.outputSchemaJson));
return yield* decodeOutput(extractJsonObject(rawOutput)).pipe(
Effect.catchTags({
SchemaError: (cause) =>
Effect.fail(
new TextGenerationError({
operation: input.operation,
detail: "OpenCode returned invalid structured output.",
cause,
}),
),
}),
);
});
const generateCommitMessage: TextGeneration.TextGeneration["Service"]["generateCommitMessage"] =
Effect.fn("OpenCodeTextGeneration.generateCommitMessage")(function* (input) {
const { prompt, outputSchema } = buildCommitMessagePrompt({
branch: input.branch,
stagedSummary: input.stagedSummary,
stagedPatch: input.stagedPatch,
includeBranch: input.includeBranch === true,
policy: input.policy,
});
const generated = yield* runOpenCodeJson({
operation: "generateCommitMessage",
cwd: input.cwd,
prompt,
outputSchemaJson: outputSchema,
modelSelection: input.modelSelection,
});
return {
subject: sanitizeCommitSubject(generated.subject),
body: generated.body.trim(),
...("branch" in generated && typeof generated.branch === "string"
? { branch: sanitizeFeatureBranchName(generated.branch) }
: {}),
};
});
const generatePrContent: TextGeneration.TextGeneration["Service"]["generatePrContent"] =
Effect.fn("OpenCodeTextGeneration.generatePrContent")(function* (input) {
const { prompt, outputSchema } = buildPrContentPrompt({
baseBranch: input.baseBranch,
headBranch: input.headBranch,
commitSummary: input.commitSummary,
diffSummary: input.diffSummary,
diffPatch: input.diffPatch,
policy: input.policy,
changeRequestTemplate: input.changeRequestTemplate,
});
const generated = yield* runOpenCodeJson({
operation: "generatePrContent",
cwd: input.cwd,
prompt,
outputSchemaJson: outputSchema,
modelSelection: input.modelSelection,
});
return {
title: sanitizePrTitle(generated.title),
body: generated.body.trim(),
};
});
const generateBranchName: TextGeneration.TextGeneration["Service"]["generateBranchName"] =
Effect.fn("OpenCodeTextGeneration.generateBranchName")(function* (input) {
const { prompt, outputSchema } = buildBranchNamePrompt({
message: input.message,
attachments: input.attachments,
});
const generated = yield* runOpenCodeJson({
operation: "generateBranchName",
cwd: input.cwd,
prompt,
outputSchemaJson: outputSchema,
modelSelection: input.modelSelection,
attachments: input.attachments,
});
return {
branch: sanitizeBranchFragment(generated.branch),
};
});
const generateThreadTitle: TextGeneration.TextGeneration["Service"]["generateThreadTitle"] =
Effect.fn("OpenCodeTextGeneration.generateThreadTitle")(function* (input) {
const { prompt, outputSchema } = buildThreadTitlePrompt({
message: input.message,
previousTitle: input.previousTitle,
attachments: input.attachments,
});
const generated = yield* runOpenCodeJson({
operation: "generateThreadTitle",
cwd: input.cwd,
prompt,
outputSchemaJson: outputSchema,
modelSelection: input.modelSelection,
attachments: input.attachments,
});
return {
title: sanitizeThreadTitle(generated.title),
};
});
return {
generateCommitMessage,
generatePrContent,
generateBranchName,
generateThreadTitle,
} satisfies TextGeneration.TextGeneration["Service"];
});