refactor(workflow): remove dead extract retry export

Drop unused llmExtractWithRetry implementation and public exports.

Add solve-issue template coverage for tool_calls extraction path.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Scott Wei
2026-05-08 17:10:31 +08:00
parent a11cc62a81
commit 884ff85205
4 changed files with 80 additions and 53 deletions
@@ -51,6 +51,49 @@ function installMockChatCompletions(sequence: ReadonlyArray<Record<string, unkno
};
}
function buildToolCallResponse(args: Record<string, unknown>): Response {
return jsonResponse({
choices: [
{
message: {
tool_calls: [
{
id: "tc_extract_1",
type: "function",
function: {
name: "extract",
arguments: JSON.stringify(args),
},
},
],
},
},
],
});
}
function installMockToolCallCompletions(sequence: ReadonlyArray<Record<string, unknown>>): () => void {
const origFetch = globalThis.fetch;
let i = 0;
const mockFetch = async (
_input: Parameters<typeof fetch>[0],
_init?: RequestInit,
): Promise<Response> => {
const args = sequence[i] ?? sequence[sequence.length - 1];
if (args === undefined) {
throw new Error("installMockToolCallCompletions: empty sequence");
}
i += 1;
return buildToolCallResponse(args);
};
globalThis.fetch = Object.assign(mockFetch, {
preconnect: origFetch.preconnect.bind(origFetch),
}) as typeof fetch;
return () => {
globalThis.fetch = origFetch;
};
}
function makeStart(maxRounds: number): ModeratorContext<SolveIssueMeta>["start"] {
return {
role: START,
@@ -233,6 +276,43 @@ describe("solveIssueWorkflowDefinition + createWorkflow", () => {
expect(first.value.meta).toEqual(EXPECT_PREPARER_META);
});
test("structured extraction also accepts tool_calls extraction path", async () => {
const EXPECT_PREPARER_META: PreparerMeta = {
repoPath: "/home/user/repos/tool-call",
defaultBranch: "main",
conventions: null,
toolchain: {
packageManager: "bun",
testCommand: "bun test",
lintCommand: null,
buildCommand: "bun run build",
},
};
restoreFetch = installMockToolCallCompletions([EXPECT_PREPARER_META]);
casDir = await mkdtemp(join(tmpdir(), "solve-issue-cas-"));
const cas = createCasStore(casDir);
const run = createWorkflow(solveIssueWorkflowDefinition, {
agent: async () => "",
overrides: { developer: async () => "stub-root-hash" },
});
const gen = run(
makeThread("task"),
{
cas,
extract: stubExtract,
},
);
const first = await gen.next();
expect(first.done).toBe(false);
if (first.done) {
throw new Error("expected yield");
}
expect(first.value.role).toBe("preparer");
expect(first.value.meta).toEqual(EXPECT_PREPARER_META);
});
test("per-role agent overrides default", async () => {
const PREPARER_META: PreparerMeta = {
repoPath: "/tmp/r",
-1
View File
@@ -6,7 +6,6 @@ export {
extractFunctionToolFromZodSchema,
llmErrorToCause,
llmExtract,
llmExtractWithRetry,
} from "./llm-extract.js";
export { reactExtract } from "./react-extract.js";
export type {
@@ -92,20 +92,6 @@ function readToolArgumentsJson(parsed: unknown, previewSource: string): Result<s
return ok(argsRaw);
}
function isRetryableExtractError(error: LlmError): boolean {
return error.kind === "schema_validation_failed" || error.kind === "tool_arguments_invalid_json";
}
function describeRetryHint(error: LlmError): string {
if (error.kind === "schema_validation_failed") {
return `Schema validation failed: ${error.message}`;
}
if (error.kind === "tool_arguments_invalid_json") {
return `Tool arguments were not valid JSON: ${error.message}`;
}
return JSON.stringify(error);
}
export function llmErrorToCause(error: LlmError): Error {
switch (error.kind) {
case "http_error":
@@ -206,40 +192,3 @@ async function performLlmExtract<T>(
export async function llmExtract<T>(options: LlmExtractArgs<T>): Promise<Result<T, LlmError>> {
return performLlmExtract({ ...options, userContent: options.text });
}
/**
* Runs extract up to two times: on the first schema/tool-args parse failure, resends the agent
* output plus the error so the model can correct the tool call.
*/
export async function llmExtractWithRetry<T>(
options: LlmExtractArgs<T>,
): Promise<Result<T, LlmError>> {
const first = await performLlmExtract({
...options,
userContent: options.text,
});
if (first.ok) {
return first;
}
if (!isRetryableExtractError(first.error)) {
return first;
}
const hint = describeRetryHint(first.error);
const correction = `The previous extraction attempt failed.
${hint}
Respond again with a single tool call whose \`arguments\` JSON strictly matches the schema.`;
const secondContent = `${options.text}
---
${correction}`;
return performLlmExtract({
...options,
userContent: secondContent,
});
}
-1
View File
@@ -59,7 +59,6 @@ export {
type LlmError,
llmErrorToCause,
llmExtract,
llmExtractWithRetry,
type ReactExtractArgs,
reactExtract,
} from "./extract/index.js";