Files
vscode-cowriting-plugin/src/liveTurn.ts
T
2026-06-10 10:35:16 -07:00

65 lines
2.5 KiB
TypeScript

/**
* LiveTurn — the minimal machine-edit ingress (spec §6.2): one @cline/sdk
* Agent turn on the built-in `claude-code` provider, which rides the
* operator's local Claude Code Pro/Max login — the extension holds NO
* credentials of any kind (INV-8). vscode-free; the editSelection command
* (extension.ts) feeds the result into the applyAgentEdit seam (INV-9).
*
* Like src/cline.ts: @cline/sdk is ESM-only, loaded via dynamic import(),
* never bundled (esbuild keeps it external).
*/
export interface EditTurnResult {
replacement: string;
model: string;
/** the SDK run id — recorded as provenance sessionId. */
sessionId: string;
}
const SYSTEM_PROMPT = [
"You are a precise text editor embedded in VS Code.",
"You will be given a piece of text and an instruction.",
"Respond with ONLY the edited replacement text — no explanation, no preamble,",
"no markdown code fences. Preserve the original's leading/trailing whitespace",
"unless the instruction says otherwise.",
].join(" ");
/**
* Strip a wrapping markdown code fence if the model added one anyway.
* If `selectedText` itself was fence-wrapped (i.e. the user's selection was a
* fenced block), the model mirroring that format is correct — return
* `outputText` unchanged so the fence is preserved.
*/
export function extractReplacement(outputText: string, selectedText: string): string {
const fencePattern = /^\s*```[^\n]*\n[\s\S]*?\n?```\s*$/;
if (fencePattern.test(selectedText)) return outputText;
const m = outputText.match(/^\s*```[^\n]*\n([\s\S]*?)\n?```\s*$/);
return m ? m[1] : outputText;
}
export async function runEditTurn(
instruction: string,
selectedText: string,
opts?: { modelId?: string },
): Promise<EditTurnResult> {
const sdk = await import("@cline/sdk");
const modelId = opts?.modelId ?? "sonnet";
const agent = new sdk.Agent({
providerId: "claude-code",
modelId,
systemPrompt: SYSTEM_PROMPT,
});
const result = await agent.run(
`<instruction>\n${instruction}\n</instruction>\n<text>\n${selectedText}\n</text>`,
);
// The SDK's AgentRunResult.status union is "completed" | "aborted" | "failed"
// (@cline/shared agent.d.ts) — "completed" is the success status.
if (result.status !== "completed") {
throw new Error(
`claude-code turn ${result.status}: ${result.error?.message ?? "unknown error"} ` +
"(is Claude Code installed and signed in?)",
);
}
return { replacement: extractReplacement(result.outputText, selectedText), model: modelId, sessionId: result.runId };
}