feat(catalog): added supportsHashlineEdits helper and exclude step-3.7-flash models
- Added supportsHashlineEdits catalog function and Step 3.7 Flash SKU recognition. - Updated coding agent edit resolution to utilize the centralized catalog function. - Updated write snapshot generation to record empty seen-line provenance.
This commit is contained in:
@@ -119,6 +119,11 @@ export const isMimoModelIdOrName = memo((value: string): boolean => {
|
||||
return value.toLowerCase().includes("mimo");
|
||||
});
|
||||
|
||||
/** StepFun Step 3.7 Flash SKU in any namespace form (`kilo/stepfun/step-3.7-flash:free`). */
|
||||
export const isStep37FlashModelId = memo((modelId: string): boolean => {
|
||||
return modelId.toLowerCase().includes("step-3.7-flash");
|
||||
});
|
||||
|
||||
/** Gemini family ids in any namespace form (`gemini-*`, `google/gemini-*`, `openrouter/google/gemini-…`). */
|
||||
export const isGeminiModelId = memo((modelId: string): boolean => {
|
||||
return /(^|\/)gemini[-.]?/i.test(modelId);
|
||||
@@ -399,6 +404,22 @@ export const supportsMidConversationSystemMessages = memo((modelId: string): boo
|
||||
return parsed !== null && isAnthropicAdaptiveGenAtLeast(parsed, "4.8");
|
||||
});
|
||||
|
||||
/**
|
||||
* Models that reliably follow the hashline line-anchored edit dialect
|
||||
* (`[path#TAG]` headers plus 1-indexed anchors). Kimi, MiMo, DeepSeek V4
|
||||
* Flash, and Step 3.7 Flash miscount anchors or drop the tag header often
|
||||
* enough that hosts fall back to a literal search-replace edit format for
|
||||
* them.
|
||||
*/
|
||||
export const supportsHashlineEdits = memo((modelId: string): boolean => {
|
||||
return !(
|
||||
isKimiModelId(modelId) ||
|
||||
isMimoModelIdOrName(modelId) ||
|
||||
isDeepseekV4FlashModelId(modelId) ||
|
||||
isStep37FlashModelId(modelId)
|
||||
);
|
||||
});
|
||||
|
||||
export const isAnthropicFableOrMythosModel = memo((modelId: string): boolean => {
|
||||
const parsed = parseAnthropicModel(bareModelId(modelId));
|
||||
return parsed !== null && isFableOrMythos(parsed.kind);
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
modelFamilyToken,
|
||||
parseAnthropicModel,
|
||||
supportsAdaptiveThinkingDisplay,
|
||||
supportsHashlineEdits,
|
||||
supportsMidConversationSystemMessages,
|
||||
} from "@oh-my-pi/pi-catalog/identity";
|
||||
|
||||
@@ -31,6 +32,20 @@ describe("isKimiModelId", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("supportsHashlineEdits", () => {
|
||||
test("declines the families that miscount line anchors", () => {
|
||||
expect(supportsHashlineEdits("openrouter/moonshotai/Kimi-K2-Instruct")).toBe(false);
|
||||
expect(supportsHashlineEdits("xiaomi/MiMo-V2.5-Pro")).toBe(false);
|
||||
expect(supportsHashlineEdits("tensormesh/deepseek-ai/DeepSeek-V4-Flash")).toBe(false);
|
||||
expect(supportsHashlineEdits("kilo/stepfun/step-3.7-flash:free")).toBe(false);
|
||||
});
|
||||
test("vouches for structured-edit-capable models", () => {
|
||||
expect(supportsHashlineEdits("google/gemini-3.5-flash")).toBe(true);
|
||||
expect(supportsHashlineEdits("claude-fable-5")).toBe(true);
|
||||
expect(supportsHashlineEdits("moonshot/moonshot-v1-128k")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isQwen38PlusTemplateEffortModelId", () => {
|
||||
test("matches Qwen 3.8+ open-weight ids across id shapes and versions", () => {
|
||||
expect(isQwen38PlusTemplateEffortModelId("qwen3.8-27b")).toBe(true);
|
||||
|
||||
@@ -360,12 +360,16 @@ function stripWriteContent(session: ToolSession, content: string): { text: strin
|
||||
* when the session is not in hashline mode so callers can no-op cheaply.
|
||||
*
|
||||
* Mirrors the post-commit snapshot recording the hashline patcher performs
|
||||
* after a successful edit: the model gets a tag without an extra `read`.
|
||||
* after a successful edit — the model gets a tag without an extra `read` —
|
||||
* but with EMPTY seen-line provenance: a write displays no numbered lines,
|
||||
* so anchored edits against this tag must first see the anchor content (the
|
||||
* patcher rejects them with an inline reveal). Authoring content is not
|
||||
* knowing its line numbers.
|
||||
*/
|
||||
function maybeWriteSnapshotHeader(session: ToolSession, absolutePath: string, content: string): string | undefined {
|
||||
if (!resolveFileDisplayMode(session).hashLines) return undefined;
|
||||
const normalized = normalizeToLF(content);
|
||||
const tag = getFileSnapshotStore(session).record(canonicalSnapshotKey(absolutePath), normalized);
|
||||
const tag = getFileSnapshotStore(session).record(canonicalSnapshotKey(absolutePath), normalized, []);
|
||||
return formatHashlineHeader(formatPathRelativeToCwd(absolutePath, session.cwd), tag);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { supportsHashlineEdits } from "@oh-my-pi/pi-catalog/identity";
|
||||
import { $env, $flag } from "@oh-my-pi/pi-utils";
|
||||
|
||||
export type EditMode = "replace" | "patch" | "hashline" | "apply_patch";
|
||||
@@ -13,22 +14,6 @@ const EDIT_MODE_IDS = {
|
||||
|
||||
export const EDIT_MODES = Object.keys(EDIT_MODE_IDS) as EditMode[];
|
||||
|
||||
const HASHLINE_EXCLUDED_MODEL_MODES: Array<{ pattern: string; mode: EditMode }> = [
|
||||
{ pattern: "kimi", mode: "replace" },
|
||||
{ pattern: "mimo", mode: "replace" },
|
||||
{ pattern: "deepseek-v4-flash", mode: "replace" },
|
||||
{ pattern: "step-3.7-flash", mode: "replace" },
|
||||
];
|
||||
|
||||
function resolveHashlineExcludedModelMode(model: string | undefined): EditMode | null {
|
||||
if (!model) return null;
|
||||
const modelLower = model.toLowerCase();
|
||||
for (const entry of HASHLINE_EXCLUDED_MODEL_MODES) {
|
||||
if (modelLower.includes(entry.pattern)) return entry.mode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function normalizeEditMode(mode?: string | null): EditMode | undefined {
|
||||
if (!mode) return undefined;
|
||||
return EDIT_MODE_IDS[mode as keyof typeof EDIT_MODE_IDS];
|
||||
@@ -54,8 +39,8 @@ export function resolveEditMode(session: EditModeSessionLike): EditMode {
|
||||
|
||||
const settingsMode = normalizeEditMode(String(session.settings.get("edit.mode") ?? ""));
|
||||
const mode = settingsMode ?? DEFAULT_EDIT_MODE;
|
||||
if (mode === "hashline" && !$flag("PI_STRICT_EDIT_MODE")) {
|
||||
return resolveHashlineExcludedModelMode(activeModel) ?? mode;
|
||||
if (mode === "hashline" && !$flag("PI_STRICT_EDIT_MODE") && activeModel && !supportsHashlineEdits(activeModel)) {
|
||||
return "replace";
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user