From 3fa6394775d63cc55f8ef4910c1acf321bdd18de Mon Sep 17 00:00:00 2001 From: Joey Kudish Date: Fri, 25 Sep 2026 20:44:27 +0000 Subject: [PATCH 1/5] chore: upgrade @ai-sdk providers to the ai@7 spec (v4) Amp-Thread-ID: https://ampcode.com/threads/T-01a0da47-464b-713e-bf0f-d5d15af3352d --- package-lock.json | 176 ++++++++++++--------------------------------- package.json | 10 +-- src/navigate.ts | 4 +- test/unit.test.mjs | 17 +++++ 4 files changed, 71 insertions(+), 136 deletions(-) diff --git a/package-lock.json b/package-lock.json index 635b442..461c6d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,14 +10,14 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@ai-sdk/anthropic": "^2.0.0", - "@ai-sdk/google": "2.0.9", - "@ai-sdk/openai": "^2.0.0", - "@ai-sdk/openai-compatible": "^1.0.0", + "@ai-sdk/anthropic": "^4.0.63", + "@ai-sdk/google": "^4.0.80", + "@ai-sdk/openai": "^4.0.75", + "@ai-sdk/openai-compatible": "^3.0.55", "@jkudish/jev-agent-tools": "^0.1.0", "@modelcontextprotocol/sdk": "^1.17.0", "@typesafe-ai/sdk": "^0.6.0", - "ai": "^7.0.105", + "ai": "^7.0.114", "playwright": "^1.55.0", "turndown": "^7.2.0", "turndown-plugin-gfm": "^1.0.2", @@ -36,16 +36,16 @@ } }, "node_modules/@ai-sdk/anthropic": { - "version": "2.0.105", - "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-2.0.105.tgz", - "integrity": "sha512-yz+RATWLmsvxKuswRxuvasUlrhxgwCY9RrafM2q2ZBax7XFDuiMR7fv/syMTNRtLo4Y3Oclz8x89V28TiAvARA==", + "version": "4.0.65", + "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-4.0.65.tgz", + "integrity": "sha512-3fmxxn5xWGOPU1wGrUCI30YFW5r6h2WrY+sz3rokkVlBHrDr889hPYP1yNoSmOVvtAwCFWlm79P2lRngIPOObg==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "2.0.5", - "@ai-sdk/provider-utils": "3.0.39" + "@ai-sdk/provider": "4.0.18", + "@ai-sdk/provider-utils": "5.0.49" }, "engines": { - "node": ">=18" + "node": ">=22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" @@ -68,18 +68,6 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.18.tgz", - "integrity": "sha512-+GZJIgz1jk86pwEbb3f1BD2bdoSKyWE4Jg4YUc7NMnMozbWemSKYZcw2F4nMaO5qwsL5A8RmioAKW81YktRpIQ==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=22" - } - }, "node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider-utils": { "version": "5.0.47", "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.47.tgz", @@ -99,131 +87,85 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@ai-sdk/gateway/node_modules/undici": { - "version": "7.30.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz", - "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==", - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, "node_modules/@ai-sdk/google": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-2.0.9.tgz", - "integrity": "sha512-sWhuiRzsVEBS3HOyvqgRiMahLxK2OEBs+x6RZMnibHt4nwHOl8AjJ7UVstJtJHo4U2YX8iaMSJMU+qhX1wRPTA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.6" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4" - } - }, - "node_modules/@ai-sdk/google/node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", + "version": "4.0.82", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-4.0.82.tgz", + "integrity": "sha512-2zPYyBSyIcywLtOEJRxlrtXjBjz4KxEU19Qqg/Dk8I9BrsB08+ghG2jv155dtuDZj8qZMrh6LO8J+Wt5O4xVhg==", "license": "Apache-2.0", "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/google/node_modules/@ai-sdk/provider-utils": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.6.tgz", - "integrity": "sha512-s1+9okDSqbxKvwf1mqyyqtOY27/RV9O+XTzaRKEamVKbmVBM7BiCSfui7vH7A/1EETECtTJkS2MUL5D3Pw5GFw==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.3" + "@ai-sdk/provider": "4.0.18", + "@ai-sdk/provider-utils": "5.0.49" }, "engines": { - "node": ">=18" + "node": ">=22" }, "peerDependencies": { - "zod": "^3.25.76 || ^4" + "zod": "^3.25.76 || ^4.1.8" } }, "node_modules/@ai-sdk/openai": { - "version": "2.0.130", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-2.0.130.tgz", - "integrity": "sha512-Amwroc0eHCr03Kcc6BEHz1z7SDEDloj9+9ORFT/Hf8FKpKZZauEhohglt3TPFBuD3lqUTQbVrzKyF5fvG8s16Q==", + "version": "4.0.77", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-4.0.77.tgz", + "integrity": "sha512-KUCeRneOIww8az/mH3dy68csmOTqa7s0F6fiHEkdXtr1u0xUoQTIU4y7k0YrjjBuyybpX2yN94rf9elhPu4zFg==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "2.0.5", - "@ai-sdk/provider-utils": "3.0.39" + "@ai-sdk/provider": "4.0.18", + "@ai-sdk/provider-utils": "5.0.49" }, "engines": { - "node": ">=18" + "node": ">=22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "node_modules/@ai-sdk/openai-compatible": { - "version": "1.0.56", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-1.0.56.tgz", - "integrity": "sha512-S84f0SC506XBLEKnMq/Ldinm240z6hOHbl+vHqkdNQ965JqJLWV5D+WBWQ1EriDkbvfoXeJpzckc9ktSOt6nqQ==", + "version": "3.0.57", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-3.0.57.tgz", + "integrity": "sha512-TEY5Ng/bTJXrYy9wpdxK70gABdYejYedaKbmPCZLhBkPe21rmLzkGxAGkfPYgAfCKPaoAO9THrEhmwUZ4Xhg2Q==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "2.0.5", - "@ai-sdk/provider-utils": "3.0.39" + "@ai-sdk/provider": "4.0.18", + "@ai-sdk/provider-utils": "5.0.49" }, "engines": { - "node": ">=18" + "node": ">=22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "node_modules/@ai-sdk/provider": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.5.tgz", - "integrity": "sha512-cmG40u10QFEUrYXWYom6Lo6DLR+pND3wWABTTT9Nf7zg9F7BD46KNj+STekndp9Kg5ZAIPA0WxmTI0IwRvKvvQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.18.tgz", + "integrity": "sha512-+GZJIgz1jk86pwEbb3f1BD2bdoSKyWE4Jg4YUc7NMnMozbWemSKYZcw2F4nMaO5qwsL5A8RmioAKW81YktRpIQ==", "license": "Apache-2.0", "dependencies": { "json-schema": "^0.4.0" }, "engines": { - "node": ">=18" + "node": ">=22" } }, "node_modules/@ai-sdk/provider-utils": { - "version": "3.0.39", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.39.tgz", - "integrity": "sha512-Cb1GE1UKcZxouWhV7weIQoJTgyz4WMswQfapPClN4QQP1pO01ZNSbG9EVN5C0gCsikA8vM0wrJZIT7HiZmH3wA==", + "version": "5.0.49", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.49.tgz", + "integrity": "sha512-T+/H8DCvqJoCqLhltVatS7ffA779Iyk/ZZrgNXkrgv51E0n2Cd6eIdCmtwhCGIKqtbTtwCvTmQdAMv5Z8rOGHQ==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "2.0.5", - "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.6", - "undici": "^5.29.0" + "@ai-sdk/provider": "4.0.18", + "@standard-schema/spec": "^1.1.0", + "@workflow/serde": "4.1.0", + "eventsource-parser": "^3.0.8", + "undici": "^7.29.0" }, "engines": { - "node": ">=18" + "node": ">=22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/@hono/node-server": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.1.tgz", @@ -368,18 +310,6 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/ai/node_modules/@ai-sdk/provider": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.18.tgz", - "integrity": "sha512-+GZJIgz1jk86pwEbb3f1BD2bdoSKyWE4Jg4YUc7NMnMozbWemSKYZcw2F4nMaO5qwsL5A8RmioAKW81YktRpIQ==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=22" - } - }, "node_modules/ai/node_modules/@ai-sdk/provider-utils": { "version": "5.0.47", "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.47.tgz", @@ -399,15 +329,6 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/ai/node_modules/undici": { - "version": "7.30.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz", - "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==", - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, "node_modules/ajv": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", @@ -1540,15 +1461,12 @@ } }, "node_modules/undici": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", - "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz", + "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==", "license": "MIT", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, "engines": { - "node": ">=14.0" + "node": ">=20.18.1" } }, "node_modules/undici-types": { diff --git a/package.json b/package.json index 32d06e8..1abe319 100644 --- a/package.json +++ b/package.json @@ -54,14 +54,14 @@ "automation" ], "dependencies": { - "@ai-sdk/anthropic": "^2.0.0", - "@ai-sdk/google": "2.0.9", - "@ai-sdk/openai": "^2.0.0", - "@ai-sdk/openai-compatible": "^1.0.0", + "@ai-sdk/anthropic": "^4.0.63", + "@ai-sdk/google": "^4.0.80", + "@ai-sdk/openai": "^4.0.75", + "@ai-sdk/openai-compatible": "^3.0.55", "@jkudish/jev-agent-tools": "^0.1.0", "@modelcontextprotocol/sdk": "^1.17.0", "@typesafe-ai/sdk": "^0.6.0", - "ai": "^7.0.105", + "ai": "^7.0.114", "playwright": "^1.55.0", "turndown": "^7.2.0", "turndown-plugin-gfm": "^1.0.2", diff --git a/src/navigate.ts b/src/navigate.ts index 3744642..4a55cf0 100644 --- a/src/navigate.ts +++ b/src/navigate.ts @@ -176,8 +176,8 @@ export function createTypingGenerator(env: NodeJS.ProcessEnv = process.env): Typ return { provider: "google", modelId: selection.modelId, - // @ai-sdk/google@2 speaks model spec v2, matching the ai@7 core; no - // compatibility cast is needed or wanted here. + // Every @ai-sdk provider here implements model spec v4, the native + // spec of the ai@7 core, so no compatibility cast is needed or wanted. model: createGoogleGenerativeAI({ apiKey: (env.GOOGLE_GENERATIVE_AI_API_KEY ?? env.GEMINI_API_KEY)!, ...(selection.baseUrl ? { baseURL: selection.baseUrl } : {}), diff --git a/test/unit.test.mjs b/test/unit.test.mjs index 631d369..190a1b0 100644 --- a/test/unit.test.mjs +++ b/test/unit.test.mjs @@ -996,6 +996,23 @@ test("generateTextToType: the google provider generates without a compatibility } }); +test("createTypingGenerator: every provider builds a native spec-v4 model (no ai@7 compatibility mode)", () => { + const cases = [ + { OPENAI_API_KEY: "sk-openai-key-0123456789" }, + { OPENROUTER_API_KEY: "sk-or-v1-openrouter-key-0123456789" }, + { JEV_BROWSER_TYPE_PROVIDER: "anthropic", ANTHROPIC_API_KEY: "sk-ant-anthropic-key-0123456789" }, + { GEMINI_API_KEY: "AIza-google-key-0123456789" }, + { JEV_BROWSER_TYPE_BASE_URL: "http://typing.test/v1", JEV_BROWSER_TYPE_MODEL: "local-model" }, + ]; + const seen = new Set(); + for (const env of cases) { + const generator = createTypingGenerator(env); + seen.add(generator.provider); + assert.equal(generator.model.specificationVersion, "v4", `${generator.provider} model is not spec v4`); + } + assert.equal(seen.size, cases.length); // each case reached a distinct provider branch +}); + // ── detectBotProtection ───────────────────────────────────────────────────── // The scoring rules: a cf-mitigated header or a known challenge title is // decisive on its own; body markers need three to stand alone, so an article From d010ed602c9a693346d34a308f5a650fdedf4b7e Mon Sep 17 00:00:00 2001 From: Joey Kudish Date: Fri, 25 Sep 2026 20:47:39 +0000 Subject: [PATCH 2/5] chore: upgrade zod to 4 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 461c6d3..1750c47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "playwright": "^1.55.0", "turndown": "^7.2.0", "turndown-plugin-gfm": "^1.0.2", - "zod": "^3.25.0" + "zod": "^4.6.5" }, "bin": { "jev-browser": "dist/index.js" @@ -1516,9 +1516,9 @@ "license": "ISC" }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 1abe319..10544b8 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "playwright": "^1.55.0", "turndown": "^7.2.0", "turndown-plugin-gfm": "^1.0.2", - "zod": "^3.25.0" + "zod": "^4.6.5" }, "devDependencies": { "@types/node": "^22.0.0", From cd234f259fff368ade6913c3755c37f506994446 Mon Sep 17 00:00:00 2001 From: Joey Kudish Date: Fri, 25 Sep 2026 20:47:53 +0000 Subject: [PATCH 3/5] chore: upgrade TypeScript to 7 Amp-Thread-ID: https://ampcode.com/threads/T-01a0da47-464b-713e-bf0f-d5d15af3352d --- package-lock.json | 375 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 2 +- tsconfig.json | 1 + 3 files changed, 370 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1750c47..559e012 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "devDependencies": { "@types/node": "^22.0.0", "@types/turndown": "^5.0.5", - "typescript": "^5.6.0" + "typescript": "^7.0.2" }, "engines": { "node": ">=22" @@ -265,6 +265,346 @@ "node": ">=20" } }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/@vercel/oidc": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.2.0.tgz", @@ -1447,17 +1787,38 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", "dev": true, "license": "Apache-2.0", "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "tsc": "bin/tsc" }, "engines": { - "node": ">=14.17" + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" } }, "node_modules/undici": { diff --git a/package.json b/package.json index 10544b8..efbd07b 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "devDependencies": { "@types/node": "^22.0.0", "@types/turndown": "^5.0.5", - "typescript": "^5.6.0" + "typescript": "^7.0.2" }, "publishConfig": { "access": "public" diff --git a/tsconfig.json b/tsconfig.json index 34f8c08..9681abd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "outDir": "dist", "rootDir": "src", "strict": true, + "types": ["node"], "declaration": true, "sourceMap": true, "esModuleInterop": true, From 2450388a9b8cea845cf825357adb22b0d5adecbd Mon Sep 17 00:00:00 2001 From: Joey Kudish Date: Fri, 25 Sep 2026 21:02:02 +0000 Subject: [PATCH 4/5] fix: disable Gemini thinking for typing so short caps return text Amp-Thread-ID: https://ampcode.com/threads/T-01a0da47-464b-713e-bf0f-d5d15af3352d --- README.md | 2 +- src/navigate.ts | 19 ++++++++++++------- test/unit.test.mjs | 18 +++++++++++++----- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 22cff7b..5da3c45 100644 --- a/README.md +++ b/README.md @@ -392,7 +392,7 @@ Statuses: `done` (agent chose to stop), `goal_achieved` (the goal watcher fired) ## The typing model -Jev never generates text. It returns typed decisions only: which option, with what probabilities. So when a task needs a string, typing a search query or filling a field, that string comes from a small model you choose. This is the only place a second model is involved, and it runs once per typed field, so a multi-field form makes one call per field. Each call is capped at 256 output tokens on OpenRouter (reasoning is disabled there, since reasoning models can burn the whole budget on hidden tokens and return empty text) and 48 tokens on every other provider. +Jev never generates text. It returns typed decisions only: which option, with what probabilities. So when a task needs a string, typing a search query or filling a field, that string comes from a small model you choose. This is the only place a second model is involved, and it runs once per typed field, so a multi-field form makes one call per field. Each call is capped at 256 output tokens on OpenRouter and Google, with reasoning disabled there (reasoning models can burn the whole budget on hidden tokens and return empty text; Gemini 3 models keep their minimum thinking level), and 48 tokens on every other provider. `JEV_PROVIDER` is not involved here at all: it selects the transport for the Jev judgments and has nothing to do with typing. Typing configuration is a separate set of variables. diff --git a/src/navigate.ts b/src/navigate.ts index 4a55cf0..89bfc54 100644 --- a/src/navigate.ts +++ b/src/navigate.ts @@ -211,17 +211,22 @@ export async function generateTextToType( elementDescription: string, url: string, ): Promise { - // OpenRouter reasoning models can burn the whole budget on hidden reasoning - // tokens and return an empty message, so reasoning is disabled there and - // the output budget raised; other providers keep the tight cap. - const openrouter = generator.provider === "openrouter"; + // Reasoning models (OpenRouter's, and Gemini, which thinks by default) can + // burn the whole budget on hidden reasoning tokens and return an empty + // message, so reasoning is disabled there and the output budget raised; + // other providers keep the tight cap. Gemini 3 cannot fully disable + // thinking ("none" maps to its minimum level), hence the headroom. + const generationLimits = + generator.provider === "openrouter" + ? { maxOutputTokens: 256, providerOptions: { openrouter: { reasoning: { enabled: false } } } } + : generator.provider === "google" + ? { maxOutputTokens: 256, reasoning: "none" as const } + : { maxOutputTokens: 48 }; try { const { text, finishReason } = await generateText({ model: generator.model, prompt: `A browser agent is performing this task: "${task}". It must type into the ${elementDescription} on ${url}. Reply with ONLY the exact text to type (for a search box: a short search query; no quotes, no explanation).`, - ...(openrouter - ? { maxOutputTokens: 256, providerOptions: { openrouter: { reasoning: { enabled: false } } } } - : { maxOutputTokens: 48 }), + ...generationLimits, abortSignal: signal, }); const cleaned = text.trim().replace(/^["']|["']$/g, ""); diff --git a/test/unit.test.mjs b/test/unit.test.mjs index 190a1b0..d5d1b03 100644 --- a/test/unit.test.mjs +++ b/test/unit.test.mjs @@ -970,7 +970,7 @@ test("generateTextToType: BASE_URL routes every named provider to the configured } }); -test("generateTextToType: the google provider generates without a compatibility cast", async () => { +test("generateTextToType: google switches thinking off and raises the cap", async () => { const realFetch = globalThis.fetch; let seen; globalThis.fetch = async (url, init) => { @@ -986,11 +986,19 @@ test("generateTextToType: the google provider generates without a compatibility const out = await generateTextToType(new AbortController().signal, generator, "task", "the search box", "https://x.test/"); assert.deepEqual(out, { ok: true, text: "ristretto", via: "google" }); assert.match(seen.url, /generativelanguage\.googleapis\.com/); - assert.equal(seen.body.generationConfig.maxOutputTokens, 48); // tight cap, no openrouter options - // google models reasoning controls via generationConfig.thinkingConfig; the - // openrouter reasoning namespace must not leak into any google request - assert.equal(seen.body.generationConfig.thinkingConfig, undefined); + // Gemini thinks by default and can spend a 48-token cap on hidden thoughts, + // returning nothing: thinking is switched off (budget 0 on 2.5) and the + // cap raised, via generationConfig.thinkingConfig. The openrouter + // reasoning namespace must not leak into any google request. + assert.equal(seen.body.generationConfig.maxOutputTokens, 256); + assert.deepEqual(seen.body.generationConfig.thinkingConfig, { thinkingBudget: 0 }); assert.equal(seen.body.generationConfig.reasoning, undefined); + assert.equal(seen.body.reasoning, undefined); + + // Gemini 3 cannot disable thinking; "none" maps to its minimum level + const gemini3 = createTypingGenerator({ GEMINI_API_KEY: "AIza-google-key-0123456789", JEV_BROWSER_TYPE_MODEL: "gemini-3-flash" }); + await generateTextToType(new AbortController().signal, gemini3, "task", "the search box", "https://x.test/"); + assert.deepEqual(seen.body.generationConfig.thinkingConfig, { thinkingLevel: "minimal" }); } finally { globalThis.fetch = realFetch; } From 2df1b5267e93446f74ea85e3fe131b6505c465ff Mon Sep 17 00:00:00 2001 From: Joey Kudish Date: Fri, 25 Sep 2026 22:27:04 +0000 Subject: [PATCH 5/5] fix: only switch Gemini thinking off on Flash models Amp-Thread-ID: https://ampcode.com/threads/T-01a0da47-464b-713e-bf0f-d5d15af3352d --- README.md | 2 +- src/navigate.ts | 13 ++++++++++--- test/unit.test.mjs | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5da3c45..8bdefdf 100644 --- a/README.md +++ b/README.md @@ -392,7 +392,7 @@ Statuses: `done` (agent chose to stop), `goal_achieved` (the goal watcher fired) ## The typing model -Jev never generates text. It returns typed decisions only: which option, with what probabilities. So when a task needs a string, typing a search query or filling a field, that string comes from a small model you choose. This is the only place a second model is involved, and it runs once per typed field, so a multi-field form makes one call per field. Each call is capped at 256 output tokens on OpenRouter and Google, with reasoning disabled there (reasoning models can burn the whole budget on hidden tokens and return empty text; Gemini 3 models keep their minimum thinking level), and 48 tokens on every other provider. +Jev never generates text. It returns typed decisions only: which option, with what probabilities. So when a task needs a string, typing a search query or filling a field, that string comes from a small model you choose. This is the only place a second model is involved, and it runs once per typed field, so a multi-field form makes one call per field. Each call is capped at 256 output tokens on OpenRouter and Google and 48 tokens on every other provider. Reasoning is disabled on OpenRouter and on Gemini Flash models (reasoning models can burn the whole budget on hidden tokens and return empty text; Gemini 3 Flash keeps its minimum thinking level). Gemini Pro models cannot turn thinking that low, so they keep their default thinking. `JEV_PROVIDER` is not involved here at all: it selects the transport for the Jev judgments and has nothing to do with typing. Typing configuration is a separate set of variables. diff --git a/src/navigate.ts b/src/navigate.ts index 89bfc54..7c78e0a 100644 --- a/src/navigate.ts +++ b/src/navigate.ts @@ -194,6 +194,11 @@ export function createTypingGenerator(env: NodeJS.ProcessEnv = process.env): Typ } } +// Gemini Flash families whose thinking can be switched off (2.5) or dropped to +// a minimum level (3+) through the AI SDK's reasoning: "none". Pro models +// cannot go that low and pre-2.5 models have no thinking configuration. +const GEMINI_FLASH_THINKING_OFF = /^(models\/)?gemini-(2\.5|[3-9](\.\d+)?)-flash|^(models\/)?gemini-flash(-lite)?-latest$/; + export type TypingTextResult = | { ok: true; text: string; via: string } | { ok: false; code: TypingWarningCode; message: string; finishReason?: string }; @@ -214,13 +219,15 @@ export async function generateTextToType( // Reasoning models (OpenRouter's, and Gemini, which thinks by default) can // burn the whole budget on hidden reasoning tokens and return an empty // message, so reasoning is disabled there and the output budget raised; - // other providers keep the tight cap. Gemini 3 cannot fully disable - // thinking ("none" maps to its minimum level), hence the headroom. + // other providers keep the tight cap. On Google only Gemini Flash models + // accept the "none" mapping (budget 0 on 2.5, minimum level on 3+, which + // still thinks a little, hence the headroom); Pro and older models reject + // it, so they keep their default thinking and just get the larger cap. const generationLimits = generator.provider === "openrouter" ? { maxOutputTokens: 256, providerOptions: { openrouter: { reasoning: { enabled: false } } } } : generator.provider === "google" - ? { maxOutputTokens: 256, reasoning: "none" as const } + ? { maxOutputTokens: 256, ...(GEMINI_FLASH_THINKING_OFF.test(generator.modelId) ? { reasoning: "none" as const } : {}) } : { maxOutputTokens: 48 }; try { const { text, finishReason } = await generateText({ diff --git a/test/unit.test.mjs b/test/unit.test.mjs index d5d1b03..651a6f6 100644 --- a/test/unit.test.mjs +++ b/test/unit.test.mjs @@ -999,6 +999,22 @@ test("generateTextToType: google switches thinking off and raises the cap", asyn const gemini3 = createTypingGenerator({ GEMINI_API_KEY: "AIza-google-key-0123456789", JEV_BROWSER_TYPE_MODEL: "gemini-3-flash" }); await generateTextToType(new AbortController().signal, gemini3, "task", "the search box", "https://x.test/"); assert.deepEqual(seen.body.generationConfig.thinkingConfig, { thinkingLevel: "minimal" }); + + // Only Flash families get the override: Pro models reject budget 0 (2.5) + // and "minimal" (3.x), and pre-2.5 models have no thinking config at all. + // They keep their default thinking and only take the raised cap. + const cases = [ + ["gemini-2.5-flash-lite", { thinkingBudget: 0 }], + ["gemini-2.5-pro", undefined], + ["gemini-3.1-pro-preview", undefined], + ["gemini-2.0-flash", undefined], + ]; + for (const [model, thinkingConfig] of cases) { + const g = createTypingGenerator({ GEMINI_API_KEY: "AIza-google-key-0123456789", JEV_BROWSER_TYPE_MODEL: model }); + await generateTextToType(new AbortController().signal, g, "task", "the search box", "https://x.test/"); + assert.deepEqual(seen.body.generationConfig.thinkingConfig, thinkingConfig, model); + assert.equal(seen.body.generationConfig.maxOutputTokens, 256, model); + } } finally { globalThis.fetch = realFetch; }