diff --git a/CHANGELOG.md b/CHANGELOG.md index c8426bc..d1e969e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- **Axon Eido 3 Flash 400K context option.** Added `axon-eido-3-flash-400k`, + a 400K context variant of the flash model that maps to the same + `axon-eido-3-flash` gateway model. Gated to Pro Plus and Ultra plans like + the other 400K options; the picker renders it under the 400K group and the + 200K fallback resolves to the bare `axon-eido-3-flash` id. + ## [6.7.4] - 2026-08-04 ### Added diff --git a/package-lock.json b/package-lock.json index 7c8e7d8..a91ea25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@matterailab/orbcode", - "version": "0.6.0", + "version": "6.7.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@matterailab/orbcode", - "version": "0.6.0", + "version": "6.7.6", "license": "MIT", "dependencies": { "@ai-sdk/anthropic": "^3.0.85", @@ -2975,36 +2975,6 @@ "setimmediate": "^1.0.5" } }, - "node_modules/jszip/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/jszip/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/jszip/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -3026,9 +2996,9 @@ } }, "node_modules/mammoth": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/mammoth/-/mammoth-1.12.0.tgz", - "integrity": "sha512-cwnK1RIcRdDMi2HRx2EXGYlxqIEh0Oo3bLhorgnsVJi2UkbX1+jKxuBNR9PC5+JaX7EkmJxFPmo6mjLpqShI2w==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/mammoth/-/mammoth-1.12.1.tgz", + "integrity": "sha512-nCH9KKjWi3jQ+i8bUKs7k1yrXtSEGpWgF8IYkzsFMcbn+5S6l4bZEBbyx2hOQErFiXPuAs9RPa6qjXVxhyx/8g==", "license": "BSD-2-Clause", "dependencies": { "@xmldom/xmldom": "^0.8.6", @@ -3482,26 +3452,33 @@ "license": "MIT" }, "node_modules/read-excel-file": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/read-excel-file/-/read-excel-file-9.2.0.tgz", - "integrity": "sha512-jwSi8Q9oCmswrYMkuiKvJ6VdHbAXGQnANSOaHBNQZEm2XvaXx8d0t/V7oepgHqT9YWxlQE3HEuCtDZq8BZBmvw==", + "version": "9.3.9", + "resolved": "https://registry.npmjs.org/read-excel-file/-/read-excel-file-9.3.9.tgz", + "integrity": "sha512-b4bB8rnZg0g+UI4xR8Ntr84UGKfMOEpT66tX2XMsMloWMCB/jVBsH7EquLG/1c+TuC8+1BuIiFJJghTCLLjZdA==", "license": "MIT", "dependencies": { - "@xmldom/xmldom": "^0.9.10", "fflate": "^0.8.3", - "unzipper-esm": "^0.13.0" + "saxen": "^11.1.0", + "unzipper-esm": "^0.13.3", + "worker-f": "^0.1.12" }, "engines": { "node": ">=18" } }, - "node_modules/read-excel-file/node_modules/@xmldom/xmldom": { - "version": "0.9.10", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz", - "integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==", + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "license": "MIT", - "engines": { - "node": ">=14.6" + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "node_modules/require-from-string": { @@ -3541,12 +3518,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/saxen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/saxen/-/saxen-11.1.1.tgz", + "integrity": "sha512-J4BkmJFaM7VgE7pgkFGsNEcqqM3h7+Mz80vfLWFhx7uNOCOXIu6LLjQHYWNejdst3pf/3JUaBIG9+pkk1umlow==", + "license": "MIT", + "engines": { + "node": ">= 20.12" + } + }, "node_modules/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", @@ -3750,6 +3742,15 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -3907,9 +3908,9 @@ } }, "node_modules/unzipper-esm": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/unzipper-esm/-/unzipper-esm-0.13.2.tgz", - "integrity": "sha512-lt8GtgDYV8YcAFZNQuLyR2QvHI8C/TstpgsdjUn9ZxiWLJgn+e5uW6DsO3e/HUJVuWD57ZLLFMZ9xk26tePuHQ==", + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/unzipper-esm/-/unzipper-esm-0.13.3.tgz", + "integrity": "sha512-LUO6VZ6fCzkDbdMev0/fOhoIeVGKaOkTIOoYxVLE0SQjfvmAHK+oywl7lfhloSZIsdGJ25mJ18Mtd9CyTASjrA==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.2", @@ -3980,6 +3981,15 @@ "node": ">= 8" } }, + "node_modules/worker-f": { + "version": "0.1.20", + "resolved": "https://registry.npmjs.org/worker-f/-/worker-f-0.1.20.tgz", + "integrity": "sha512-7z5K5z4x++FykhpDTfriT/dOu7CmSap9BBv38DVFU3CD38obopq+DoFH75yBV3butpGm+OeqR9BKdSvYJSnUfQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index f2b236f..585e7b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@matterailab/orbcode", - "version": "6.7.4", + "version": "6.7.6", "description": "OrbCode CLI — agentic coding in your terminal, powered by Axon models by MatterAI", "type": "module", "bin": { diff --git a/src/api/models.ts b/src/api/models.ts index 3d6848d..2baacbd 100644 --- a/src/api/models.ts +++ b/src/api/models.ts @@ -20,6 +20,8 @@ export interface AxonModel { /** USD per token */ outputPrice: number; free: boolean; + /** Human-readable pricing shown when the gateway chooses the billable model dynamically. */ + pricingLabel?: string; /** * Which transport serves this model. Absent (or "matterai"/"axon") routes * through the MatterAI gateway (OpenAI `/chat/completions`). Any other value @@ -141,6 +143,34 @@ export const ANTHROPIC_MODELS: Record = { * interactive picker for now. */ export const BUILTIN_AXON_MODELS: Record = { + "axon-auto-200k": { + id: "axon-auto-200k", + gatewayModelId: "axon-auto", + name: "Axon Auto (200K context)", + description: + "Starts with Code Flash, then dynamically selects Code Flash, Mini, or Pro as the task develops.", + contextWindow: 200000, + maxOutputTokens: 64000, + supportsImages: true, + inputPrice: 0.0000005, + outputPrice: 0.0000015, + free: false, + pricingLabel: "dynamic pricing", + }, + "axon-auto-400k": { + id: "axon-auto-400k", + gatewayModelId: "axon-auto", + name: "Axon Auto (400K context)", + description: + "Starts with Code Flash, then dynamically selects Code Flash, Mini, or Pro as the task develops.", + contextWindow: 400000, + maxOutputTokens: 64000, + supportsImages: true, + inputPrice: 0.0000005, + outputPrice: 0.0000015, + free: false, + pricingLabel: "dynamic pricing", + }, "axon-eido-3-flash": { id: "axon-eido-3-flash", name: "Axon Eido 3 Flash", @@ -153,6 +183,19 @@ export const BUILTIN_AXON_MODELS: Record = { outputPrice: 0.0000015, free: false, }, + "axon-eido-3-flash-400k": { + id: "axon-eido-3-flash-400k", + gatewayModelId: "axon-eido-3-flash", + name: "Axon Eido 3 Flash (400K context)", + description: + "Axon Eido is a fast and low cost general purpose model for low-effort day-to-day tasks", + contextWindow: 400000, + maxOutputTokens: 64000, + supportsImages: true, + inputPrice: 0.0000005, + outputPrice: 0.0000015, + free: false, + }, "axon-eido-3-code-pro-200k": { id: "axon-eido-3-code-pro-200k", gatewayModelId: "axon-eido-3-code-pro", @@ -271,7 +314,9 @@ export function canUseEidoProModels(plan?: string): boolean { export function is400kAxonModel(modelId: string): boolean { return ( - (modelId.startsWith("axon-eido-3-code-") || + (modelId.startsWith("axon-auto-") || + modelId.startsWith("axon-eido-3-code-") || + modelId.startsWith("axon-eido-3-flash-") || modelId.startsWith("axon-lumen-4-code-")) && modelId.endsWith("-400k") ); @@ -286,6 +331,9 @@ export function isEidoProAxonModel(modelId: string): boolean { } export function get200kAxonFallback(modelId: string): string { + // The 200K Flash option uses the bare id (no "-200k" suffix), so the + // generic -400k → -200k rewrite would point at a non-existent model. + if (modelId === "axon-eido-3-flash-400k") return "axon-eido-3-flash"; return modelId.replace(/-400k$/, "-200k"); } diff --git a/src/ui/components/ModelPicker.tsx b/src/ui/components/ModelPicker.tsx index 661bd26..b72dca6 100644 --- a/src/ui/components/ModelPicker.tsx +++ b/src/ui/components/ModelPicker.tsx @@ -7,8 +7,9 @@ import { PopoverBox } from "./PopoverBox.js" const VISIBLE_ROWS = 6 const CONTEXT_WINDOW_ORDER = [200000, 400000] -// Display order within a context-window group: Flash, Mini, Pro, Lumen. +// Display order within a context-window group: Auto, Flash, Mini, Pro, Lumen. const DISPLAY_ORDER = [ + "axon-auto", "axon-eido-3-flash", "axon-eido-3-code-mini", "axon-eido-3-code-pro", @@ -31,6 +32,7 @@ interface ModelPickerProps { } function formatPrice(model: AxonModel): string { + if (model.pricingLabel) return model.pricingLabel if (model.free) return "free" const perMillion = (price: number) => `$${(price * 1_000_000).toFixed(2)}` return `${perMillion(model.inputPrice)} in / ${perMillion(model.outputPrice)} out per 1M tokens` diff --git a/test/axon-auto-models.test.ts b/test/axon-auto-models.test.ts new file mode 100644 index 0000000..b85475c --- /dev/null +++ b/test/axon-auto-models.test.ts @@ -0,0 +1,42 @@ +import assert from "node:assert/strict" +import test from "node:test" + +import { + BUILTIN_AXON_MODELS, + get200kAxonFallback, + getGatewayModelId, + is400kAxonModel, +} from "../src/api/models.js" + +test("Axon Auto exposes 200K and 400K dynamic-pricing choices", () => { + const auto200k = BUILTIN_AXON_MODELS["axon-auto-200k"] + const auto400k = BUILTIN_AXON_MODELS["axon-auto-400k"] + + assert.equal(auto200k.contextWindow, 200_000) + assert.equal(auto400k.contextWindow, 400_000) + assert.equal(auto200k.pricingLabel, "dynamic pricing") + assert.equal(auto400k.pricingLabel, "dynamic pricing") + assert.equal(auto200k.free, false) + assert.equal(auto400k.free, false) + assert.equal(getGatewayModelId(auto200k), "axon-auto") + assert.equal(getGatewayModelId(auto400k), "axon-auto") +}) + +test("Auto 400K uses the existing extended-context gate and fallback", () => { + assert.equal(is400kAxonModel("axon-auto-400k"), true) + assert.equal(is400kAxonModel("axon-auto-200k"), false) + assert.equal(get200kAxonFallback("axon-auto-400k"), "axon-auto-200k") +}) + +test("Axon Eido 3 Flash exposes a 400K variant through the same gates", () => { + const flash400k = BUILTIN_AXON_MODELS["axon-eido-3-flash-400k"] + + assert.equal(flash400k.contextWindow, 400_000) + assert.equal(flash400k.maxOutputTokens, 64_000) + assert.equal(getGatewayModelId(flash400k), "axon-eido-3-flash") + assert.equal(is400kAxonModel("axon-eido-3-flash-400k"), true) + assert.equal(is400kAxonModel("axon-eido-3-flash"), false) + // The 200K Flash option uses the bare id (no "-200k" suffix), so the + // fallback must map to that exact id rather than the generic -200k form. + assert.equal(get200kAxonFallback("axon-eido-3-flash-400k"), "axon-eido-3-flash") +})