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
120 changes: 6 additions & 114 deletions apps/server/src/textGeneration/AntigravityTextGeneration.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import {
type ModelSelection,
type ProviderSetupError,
TextGenerationError,
} from "@t3tools/contracts";
import { formatGeneratedBranchName, sanitizeFeatureBranchName } from "@t3tools/shared/git";
import { extractJsonObject } from "@t3tools/shared/schemaJson";
import { type ProviderSetupError, TextGenerationError } from "@t3tools/contracts";
import * as Deferred from "effect/Deferred";
import * as Effect from "effect/Effect";
import * as Exit from "effect/Exit";
Expand All @@ -20,18 +14,7 @@ import { type AcpError, AcpRequestError } from "effect-acp/errors";
import { applyAntigravityAcpModelSelection } from "../provider/acp/AntigravityAcpSupport.ts";
import { removeAntigravitySessionFiles } from "../provider/acp/AntigravitySessionFiles.ts";
import type { AcpSessionRuntime } from "../provider/acp/AcpSessionRuntime.ts";
import type * as TextGeneration from "./TextGeneration.ts";
import {
buildBranchNamePrompt,
buildCommitMessagePrompt,
buildPrContentPrompt,
buildThreadTitlePrompt,
} from "./TextGenerationPrompts.ts";
import {
sanitizeCommitSubject,
sanitizePrTitle,
sanitizeThreadTitle,
} from "./TextGenerationUtils.ts";
import * as TextGenerationOperations from "./TextGenerationOperations.ts";

const ANTIGRAVITY_TIMEOUT_MS = 180_000;
const MAX_OUTPUT_CHARS = 128_000;
Expand Down Expand Up @@ -119,13 +102,9 @@ export const makeAntigravityTextGeneration = Effect.fn("makeAntigravityTextGener
Effect.provideService(Path.Path, path),
);

// Ignores `cwd`: the helper runs in an empty temp directory, away from the project.
const runAntigravityJson = Effect.fn("AntigravityTextGeneration.runJson")(
function* <S extends Schema.Top>(input: {
readonly operation: keyof TextGeneration.TextGeneration["Service"];
readonly prompt: string;
readonly outputSchema: S;
readonly modelSelection: ModelSelection;
}) {
function* <S extends Schema.Top>(input: TextGenerationOperations.Request<S>) {
const { operation } = input;
const scope = yield* Scope.make();
yield* Effect.addFinalizer((exit) => Scope.close(scope, exit));
Expand Down Expand Up @@ -290,17 +269,7 @@ export const makeAntigravityTextGeneration = Effect.fn("makeAntigravityTextGener
detail: "Antigravity returned empty text generation output.",
});
}
const decodeOutput = Schema.decodeEffect(Schema.fromJsonString(input.outputSchema));
return yield* decodeOutput(extractJsonObject(rawResult)).pipe(
Effect.mapError(
(cause) =>
new TextGenerationError({
operation,
detail: "Antigravity returned invalid structured output.",
cause,
}),
),
);
return yield* TextGenerationOperations.decodeJsonReply(input, "Antigravity", rawResult);
}).pipe(
Effect.scoped,
Effect.timeoutOption(ANTIGRAVITY_TIMEOUT_MS),
Expand Down Expand Up @@ -337,82 +306,5 @@ export const makeAntigravityTextGeneration = Effect.fn("makeAntigravityTextGener
),
);

const generateCommitMessage: TextGeneration.TextGeneration["Service"]["generateCommitMessage"] =
Effect.fn("AntigravityTextGeneration.generateCommitMessage")(function* (input) {
const generated = yield* runAntigravityJson({
operation: "generateCommitMessage",
...buildCommitMessagePrompt({
branch: input.branch,
stagedSummary: input.stagedSummary,
stagedPatch: input.stagedPatch,
includeBranch: input.includeBranch === true,
policy: input.policy,
}),
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("AntigravityTextGeneration.generatePrContent")(function* (input) {
const generated = yield* runAntigravityJson({
operation: "generatePrContent",
...buildPrContentPrompt({
baseBranch: input.baseBranch,
headBranch: input.headBranch,
commitSummary: input.commitSummary,
diffSummary: input.diffSummary,
diffPatch: input.diffPatch,
policy: input.policy,
changeRequestTemplate: input.changeRequestTemplate,
}),
modelSelection: input.modelSelection,
});
return { title: sanitizePrTitle(generated.title), body: generated.body.trim() };
});

const generateBranchName: TextGeneration.TextGeneration["Service"]["generateBranchName"] =
Effect.fn("AntigravityTextGeneration.generateBranchName")(function* (input) {
const generated = yield* runAntigravityJson({
operation: "generateBranchName",
...buildBranchNamePrompt({
message: input.message,
attachments: input.attachments,
naming: input.naming,
}),
modelSelection: input.modelSelection,
});
return { branch: formatGeneratedBranchName(generated.branch, input.naming) };
});

const generateThreadTitle: TextGeneration.TextGeneration["Service"]["generateThreadTitle"] =
Effect.fn("AntigravityTextGeneration.generateThreadTitle")(function* (input) {
const generated = yield* runAntigravityJson({
operation: "generateThreadTitle",
...buildThreadTitlePrompt({
message: input.message,
previousTitle: input.previousTitle,
linkedContext: input.linkedContext,
attachments: input.attachments,
}),
modelSelection: input.modelSelection,
});
return {
title: sanitizeThreadTitle(generated.title),
...(generated.needsRefinement ? { needsRefinement: true } : {}),
};
});

return {
generateCommitMessage,
generatePrContent,
generateBranchName,
generateThreadTitle,
} satisfies TextGeneration.TextGeneration["Service"];
return TextGenerationOperations.fromRunner("AntigravityTextGeneration", runAntigravityJson);
});
152 changes: 11 additions & 141 deletions apps/server/src/textGeneration/ClaudeTextGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,11 @@ import * as Schema from "effect/Schema";
import * as Stream from "effect/Stream";
import { ChildProcess, ChildProcessSpawner } from "effect/process";

import { type ClaudeSettings, type ModelSelection } from "@t3tools/contracts";
import { formatGeneratedBranchName, sanitizeFeatureBranchName } from "@t3tools/shared/git";
import { type ClaudeSettings, TextGenerationError } from "@t3tools/contracts";
import { resolveSpawnCommand } from "@t3tools/shared/shell";

import { TextGenerationError } from "@t3tools/contracts";
import * as TextGeneration from "./TextGeneration.ts";
import {
buildBranchNamePrompt,
buildCommitMessagePrompt,
buildPrContentPrompt,
buildThreadTitlePrompt,
} from "./TextGenerationPrompts.ts";
import {
normalizeCliError,
sanitizeCommitSubject,
sanitizePrTitle,
sanitizeThreadTitle,
toJsonSchemaObject,
} from "./TextGenerationUtils.ts";
import * as TextGenerationOperations from "./TextGenerationOperations.ts";
import { normalizeCliError, toJsonSchemaObject } from "./TextGenerationUtils.ts";
import {
getModelSelectionStringOptionValue,
getProviderOptionDescriptors,
Expand Down Expand Up @@ -98,11 +84,7 @@ export const makeClaudeTextGeneration = Effect.fn("makeClaudeTextGeneration")(fu
);

const encodeJsonForOperation = (
operation:
| "generateCommitMessage"
| "generatePrContent"
| "generateBranchName"
| "generateThreadTitle",
operation: TextGenerationOperations.Operation,
value: unknown,
detail: string,
): Effect.Effect<string, TextGenerationError> =>
Expand All @@ -125,19 +107,13 @@ export const makeClaudeTextGeneration = Effect.fn("makeClaudeTextGeneration")(fu
operation,
cwd,
prompt,
outputSchemaJson,
outputSchema: outputSchemaJson,
modelSelection,
}: {
operation:
| "generateCommitMessage"
| "generatePrContent"
| "generateBranchName"
| "generateThreadTitle";
cwd: string;
prompt: string;
outputSchemaJson: S;
modelSelection: ModelSelection;
}): Effect.fn.Return<S["Type"], TextGenerationError, S["DecodingServices"]> {
}: TextGenerationOperations.Request<S>): Effect.fn.Return<
S["Type"],
TextGenerationError,
S["DecodingServices"]
> {
const catalog = yield* scopedModelCatalog;
const resolvedModelSelection = {
...modelSelection,
Expand Down Expand Up @@ -310,111 +286,5 @@ export const makeClaudeTextGeneration = Effect.fn("makeClaudeTextGeneration")(fu
);
});

// ---------------------------------------------------------------------------
// TextGeneration service methods
// ---------------------------------------------------------------------------

const generateCommitMessage: TextGeneration.TextGeneration["Service"]["generateCommitMessage"] =
Effect.fn("ClaudeTextGeneration.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* runClaudeJson({
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("ClaudeTextGeneration.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* runClaudeJson({
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("ClaudeTextGeneration.generateBranchName")(function* (input) {
const { prompt, outputSchema } = buildBranchNamePrompt({
message: input.message,
attachments: input.attachments,
naming: input.naming,
});

const generated = yield* runClaudeJson({
operation: "generateBranchName",
cwd: input.cwd,
prompt,
outputSchemaJson: outputSchema,
modelSelection: input.modelSelection,
});

return {
branch: formatGeneratedBranchName(generated.branch, input.naming),
};
});

const generateThreadTitle: TextGeneration.TextGeneration["Service"]["generateThreadTitle"] =
Effect.fn("ClaudeTextGeneration.generateThreadTitle")(function* (input) {
const { prompt, outputSchema } = buildThreadTitlePrompt({
message: input.message,
previousTitle: input.previousTitle,
linkedContext: input.linkedContext,
attachments: input.attachments,
});

const generated = yield* runClaudeJson({
operation: "generateThreadTitle",
cwd: input.cwd,
prompt,
outputSchemaJson: outputSchema,
modelSelection: input.modelSelection,
});

return {
title: sanitizeThreadTitle(generated.title),
...(generated.needsRefinement ? { needsRefinement: true } : {}),
};
});

return {
generateCommitMessage,
generatePrContent,
generateBranchName,
generateThreadTitle,
} satisfies TextGeneration.TextGeneration["Service"];
return TextGenerationOperations.fromRunner("ClaudeTextGeneration", runClaudeJson);
});
Loading
Loading