From 0ac66bca939c8f20a9c6a68f93a9b690e908971f Mon Sep 17 00:00:00 2001 From: mikkel Date: Wed, 21 Oct 2026 18:17:47 -0600 Subject: [PATCH] fix: replace retired default model with deepseek/deepseek-v4.1-flash-1017 (#11) * fix: replace retired default model with xiaomi/mimo-v2.6-pro * fix: default model deepseek/deepseek-v4.1-flash-1017 (per Mikkel) --------- Co-authored-by: mikkel <7003+mikkel@users.noreply.github.com> (cherry picked from commit 4cd73c4664bd21b3ee9e9f63e6b185f9bb814053) --- CHANGELOG.md | 3 ++- README.md | 4 ++-- landing/lib/env.js | 2 +- src/env.d.ts | 2 +- src/env.js | 2 +- test/default_model.test.js | 30 ++++++++++++++++++++++++ test/fixtures/demo-models.json | 43 ++++++++++++++++++++++++++++++++-- test/live.test.js | 2 +- 8 files changed, 79 insertions(+), 9 deletions(-) create mode 100644 test/default_model.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index d961dac..f51545b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,13 @@ ## [0.3.1] — Unreleased -Stricter UTF-8 tail handling and a guard against Python-style option names in `filterRelevant`. No change to `generateDiff`, `smartapply`, or the apply path. +Stricter UTF-8 tail handling, a guard against Python-style option names in `filterRelevant`, and a new default model to replace a retired one. `generateDiff` now has a working fallback model: with no `model` and no `GPTDIFF_MODEL` it uses `DEFAULT_MODEL` (`deepseek/deepseek-v4.1-flash-1017`) instead of the retired id, which failed with HTTP 410. What `smartapply` writes is unchanged. ### Behavior changes - **`filterRelevant` now throws on snake_case options.** Passing any patchling-py keyword spelling (`on_binary`, `on_oversize`, `max_bytes`, `max_tokens`, `max_output_tokens`, `api_key`, `base_url`, `call_llm`, `on_decision`, `reasoning_effort`, `retry_base_ms`, `skip_patterns`, `max_attempts`, `retry_backoff`, `sleep`) throws a `TypeError` before any model call, naming the camelCase option to use (`skip_patterns` → `ignore`, `max_attempts: n` → `retries: n - 1`, `retry_backoff: s` → `retryBaseMs: s * 1000`; `sleep` is not supported). In 0.3.0 these keys were silently ignored, so `{ on_binary: 'include' }` quietly fell back to the default `exclude`. Code that passed such keys and relied on the defaults will now fail loudly. The guard is not configurable (the `SNAKE_CASE_OPTIONS` map lives in `src/filter.js` and is not re-exported from the package root). - **A truncated UTF-8 tail keeps its valid prefix at any file length.** In 0.3.0 an incomplete trailing sequence was tolerated only when the buffer was exactly 8192 bytes; any other length took the binary path. Now a file that ends in a legal partial character is text with the partial character dropped (`0x61 0xC3` and `0x61 0xE0 0xA0` are `"a"`). Tails that can never become a legal character stay binary (`0x61 0xE0 0x80`, `0x61 0xED 0xA0`, `0x61 0xF0 0x80`, `0x61 0xF4 0x90`; leads C0, C1, F5-FF). Some short files that 0.3.0 skipped as binary are now sent to the decision model. +- **Default model is now `deepseek/deepseek-v4.1-flash-1017`.** The 0.3.0 default, `xiaomi/mimo-v2.5-pro-ultraspeed`, has been retired by NanoGPT and returns HTTP 410. So whenever `GPTDIFF_MODEL` was unset and no `model` was passed, `generateDiff` failed, and so did `smartapply` on its model-backed path: files whose patch could not be applied deterministically from the `+`/`-` lines. Patches that applied deterministically, and file deletions, never called the model and were unaffected. `DEFAULT_MODEL` (and its `.d.ts` literal type) is now `deepseek/deepseek-v4.1-flash-1017`, the October 17, 2026 snapshot of DeepSeek V4.1 Flash as listed on NanoGPT ($0.10 / $0.60 per 1M input / output tokens, 1M context). NanoGPT lists no non-Flash V4.1 model. patchling-py uses the same default. Calls that pass `model` or set `GPTDIFF_MODEL` are unchanged. The `filterRelevant` default (`xiaomi/mimo-v2.6-flash`) is unchanged. The verified catalog snapshot (`test/fixtures/demo-models.json`) adds the new id and was re-checked on 2026-10-21. ### Docs and tests diff --git a/README.md b/README.md index 26d17df..287fb8b 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ The browser demos explicitly use `xiaomi/mimo-v2.6-flash` for diff generation and repair. The landing-page picker also pins `xiaomi/mimo-v2.6-pro` and `z-ai/glm-5.3-flash`. These IDs, capabilities, and prices were checked against [NanoGPT's public catalog](https://nano-gpt.com/api/v1/models?detailed=true) -on 2026-10-13; the offline snapshot is in `test/fixtures/demo-models.json`. +on 2026-10-13 (re-checked 2026-10-21, when the default `deepseek/deepseek-v4.1-flash-1017` was added); the offline snapshot is in `test/fixtures/demo-models.json`. The decision demo requests MiMo's supported `none` reasoning effort. Library environment defaults below are independent of the demo choices. @@ -71,7 +71,7 @@ Configuration comes from environment variables (Node) or `setEnv(...)` overrides | --- | --- | --- | | `GPTDIFF_LLM_API_KEY` | NanoGPT API key (`sk-nano-…`) | — (required for live calls) | | `GPTDIFF_LLM_BASE_URL` | OpenAI-compatible base URL | `https://nano-gpt.com/api/v1/` | -| `GPTDIFF_MODEL` | Model id | `xiaomi/mimo-v2.5-pro-ultraspeed` | +| `GPTDIFF_MODEL` | Model id | `deepseek/deepseek-v4.1-flash-1017` | | `GPTDIFF_FILTER_MODEL` | Cheap model for `filterRelevant` | `xiaomi/mimo-v2.6-flash` | ```js diff --git a/landing/lib/env.js b/landing/lib/env.js index 2728268..b556f77 100644 --- a/landing/lib/env.js +++ b/landing/lib/env.js @@ -36,5 +36,5 @@ export function getEnv(name, fallback = undefined) { return fallback; } -export const DEFAULT_MODEL = 'xiaomi/mimo-v2.5-pro-ultraspeed'; +export const DEFAULT_MODEL = 'deepseek/deepseek-v4.1-flash-1017'; export const DEFAULT_BASE_URL = 'https://nano-gpt.com/api/v1/'; diff --git a/src/env.d.ts b/src/env.d.ts index a0b3528..e8605fb 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -12,5 +12,5 @@ export function setEnv(name: string, value: string | undefined): void; * @returns {string | undefined} */ export function getEnv(name: string, fallback?: string): string | undefined; -export const DEFAULT_MODEL: "xiaomi/mimo-v2.5-pro-ultraspeed"; +export const DEFAULT_MODEL: "deepseek/deepseek-v4.1-flash-1017"; export const DEFAULT_BASE_URL: "https://nano-gpt.com/api/v1/"; diff --git a/src/env.js b/src/env.js index 2728268..b556f77 100644 --- a/src/env.js +++ b/src/env.js @@ -36,5 +36,5 @@ export function getEnv(name, fallback = undefined) { return fallback; } -export const DEFAULT_MODEL = 'xiaomi/mimo-v2.5-pro-ultraspeed'; +export const DEFAULT_MODEL = 'deepseek/deepseek-v4.1-flash-1017'; export const DEFAULT_BASE_URL = 'https://nano-gpt.com/api/v1/'; diff --git a/test/default_model.test.js b/test/default_model.test.js new file mode 100644 index 0000000..a5e23ca --- /dev/null +++ b/test/default_model.test.js @@ -0,0 +1,30 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { DEFAULT_MODEL } from '../src/index.js'; +import { DEFAULT_MODEL as LANDING_DEFAULT_MODEL } from '../landing/lib/env.js'; + +const RETIRED = 'xiaomi/mimo-v2.5-pro-ultraspeed'; // NanoGPT returns HTTP 410 +const catalog = JSON.parse(readFileSync(new URL('./fixtures/demo-models.json', import.meta.url), 'utf8')); + +test('DEFAULT_MODEL is deepseek/deepseek-v4.1-flash-1017', () => { + assert.equal(DEFAULT_MODEL, 'deepseek/deepseek-v4.1-flash-1017'); + assert.equal(LANDING_DEFAULT_MODEL, DEFAULT_MODEL); +}); + +test('retired xiaomi/mimo-v2.5-pro-ultraspeed is not the default', () => { + assert.notEqual(DEFAULT_MODEL, RETIRED); + assert.notEqual(LANDING_DEFAULT_MODEL, RETIRED); +}); + +test('DEFAULT_MODEL is in the verified NanoGPT catalog fixture and cheap', () => { + const m = catalog.text[DEFAULT_MODEL]; + assert.ok(m, `${DEFAULT_MODEL} missing from test/fixtures/demo-models.json`); + assert.ok(m.pricing.prompt <= 0.5 && m.pricing.completion <= 1, 'default should stay cheap'); +}); + +test('src/env.d.ts declares the new default literal', () => { + const dts = readFileSync(new URL('../src/env.d.ts', import.meta.url), 'utf8'); + assert.match(dts, /DEFAULT_MODEL: "deepseek\/deepseek-v4\.1-flash-1017";/); + assert.ok(!dts.includes(RETIRED)); +}); diff --git a/test/fixtures/demo-models.json b/test/fixtures/demo-models.json index 46cb215..1d3d756 100644 --- a/test/fixtures/demo-models.json +++ b/test/fixtures/demo-models.json @@ -1,10 +1,10 @@ { - "verifiedOn": "2026-10-13", + "verifiedOn": "2026-10-21", "sources": { "text": "https://nano-gpt.com/api/v1/models?detailed=true" }, "catalogSha256": { - "nanogpt-models.json": "0ff41c8e54be2645c386bd00a712dca92e8b9103468ea28a0fd0a7e12c2aa89e" + "nanogpt-models.json": "e4a5241a0498b8d0db13d8bb667aaafa1d3d2b162a49d917de2412f66b634f7a" }, "text": { "z-ai/glm-5.3-flash": { @@ -125,6 +125,45 @@ "currency": "USD", "unit": "per_million_tokens" } + }, + "deepseek/deepseek-v4.1-flash-1017": { + "created": 1792195200, + "context_length": 1048576, + "max_output_tokens": 393216, + "architecture": { + "modality": "text+image->text", + "input_modalities": [ + "text", + "image" + ], + "output_modalities": [ + "text" + ] + }, + "capabilities": { + "vision": true, + "video_input": false, + "audio_input": false, + "reasoning": true, + "tool_calling": true, + "parallel_tool_calls": false, + "structured_output": false, + "pdf_upload": false, + "batch_api": true + }, + "reasoning_efforts": [ + "none", + "low", + "high", + "max" + ], + "pricing": { + "prompt": 0.09999999999999999, + "completion": 0.6, + "cacheReadInputPer1kTokens": 3e-06, + "currency": "USD", + "unit": "per_million_tokens" + } } } } diff --git a/test/live.test.js b/test/live.test.js index 4182720..75e48f5 100644 --- a/test/live.test.js +++ b/test/live.test.js @@ -5,7 +5,7 @@ * GPTDIFF_LIVE=1 * GPTDIFF_LLM_API_KEY= (e.g. sk-nano-... from OAuth sign-in) * - * Model defaults to xiaomi/mimo-v2.5-pro-ultraspeed (override with GPTDIFF_MODEL). + * Model defaults to deepseek/deepseek-v4.1-flash-1017 (override with GPTDIFF_MODEL). * Base URL defaults to https://nano-gpt.com/api/v1/ (override GPTDIFF_LLM_BASE_URL). * * Run with: npm run test:live