diff --git a/bun.lock b/bun.lock index 04fe83859..45eefb6da 100644 --- a/bun.lock +++ b/bun.lock @@ -187,59 +187,59 @@ "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], - "@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1002.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.17", "@aws-sdk/credential-provider-node": "^3.972.16", "@aws-sdk/eventstream-handler-node": "^3.972.9", "@aws-sdk/middleware-eventstream": "^3.972.6", "@aws-sdk/middleware-host-header": "^3.972.6", "@aws-sdk/middleware-logger": "^3.972.6", "@aws-sdk/middleware-recursion-detection": "^3.972.6", "@aws-sdk/middleware-user-agent": "^3.972.17", "@aws-sdk/middleware-websocket": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.6", "@aws-sdk/token-providers": "3.1002.0", "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-endpoints": "^3.996.3", "@aws-sdk/util-user-agent-browser": "^3.972.6", "@aws-sdk/util-user-agent-node": "^3.973.2", "@smithy/config-resolver": "^4.4.9", "@smithy/core": "^3.23.7", "@smithy/eventstream-serde-browser": "^4.2.10", "@smithy/eventstream-serde-config-resolver": "^4.3.10", "@smithy/eventstream-serde-node": "^4.2.10", "@smithy/fetch-http-handler": "^5.3.12", "@smithy/hash-node": "^4.2.10", "@smithy/invalid-dependency": "^4.2.10", "@smithy/middleware-content-length": "^4.2.10", "@smithy/middleware-endpoint": "^4.4.21", "@smithy/middleware-retry": "^4.4.38", "@smithy/middleware-serde": "^4.2.11", "@smithy/middleware-stack": "^4.2.10", "@smithy/node-config-provider": "^4.3.10", "@smithy/node-http-handler": "^4.4.13", "@smithy/protocol-http": "^5.3.10", "@smithy/smithy-client": "^4.12.1", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.10", "@smithy/util-base64": "^4.3.1", "@smithy/util-body-length-browser": "^4.2.1", "@smithy/util-body-length-node": "^4.2.2", "@smithy/util-defaults-mode-browser": "^4.3.37", "@smithy/util-defaults-mode-node": "^4.2.40", "@smithy/util-endpoints": "^3.3.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-retry": "^4.2.10", "@smithy/util-stream": "^4.5.16", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-xUmzgTvTeQFVxBqla8U4nXpZNXLcZ0xszfZ4yxdTUNyChQQb7JLaH4E8pAbl7ulg0RoJ4ChNWtOqMJC/N3+qcQ=="], + "@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1003.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.18", "@aws-sdk/credential-provider-node": "^3.972.17", "@aws-sdk/eventstream-handler-node": "^3.972.10", "@aws-sdk/middleware-eventstream": "^3.972.7", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.18", "@aws-sdk/middleware-websocket": "^3.972.12", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/token-providers": "3.1003.0", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.3", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.8", "@smithy/eventstream-serde-browser": "^4.2.11", "@smithy/eventstream-serde-config-resolver": "^4.3.11", "@smithy/eventstream-serde-node": "^4.2.11", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.22", "@smithy/middleware-retry": "^4.4.39", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.38", "@smithy/util-defaults-mode-node": "^4.2.41", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-stream": "^4.5.17", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-b39kYrFC3dGFQ7S5UiHKD8aGCFr0/k+QXDzqnT8N2zi8JILEvdxBhMWNqCIpZAbCCK2Jp9S8jK5/Vh0TfLUIPQ=="], - "@aws-sdk/core": ["@aws-sdk/core@3.973.17", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@aws-sdk/xml-builder": "^3.972.9", "@smithy/core": "^3.23.7", "@smithy/node-config-provider": "^4.3.10", "@smithy/property-provider": "^4.2.10", "@smithy/protocol-http": "^5.3.10", "@smithy/signature-v4": "^5.3.10", "@smithy/smithy-client": "^4.12.1", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-VtgGP0TjbCeyp6DQpiBqJKbemTSIaN2bZc3UbeTDCani3lBCyxn75ouJYD6koSSp0bh7rKLEbUpiFsNCI7tr0w=="], + "@aws-sdk/core": ["@aws-sdk/core@3.973.18", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.8", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-GUIlegfcK2LO1J2Y98sCJy63rQSiLiDOgVw7HiHPRqfI2vb3XozTVqemwO0VSGXp54ngCnAQz0Lf0YPCBINNxA=="], - "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.15", "", { "dependencies": { "@aws-sdk/core": "^3.973.17", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-RhHQG1lhkWHL4tK1C/KDjaOeis+9U0tAMnWDiwiSVQZMC7CsST9Xin+sK89XywJ5g/tyABtb7TvFePJ4Te5XSQ=="], + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.16", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-HrdtnadvTGAQUr18sPzGlE5El3ICphnH6SU7UQOMOWFgRKbTRNN8msTxM4emzguUso9CzaHU2xy5ctSrmK5YNA=="], - "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.17", "", { "dependencies": { "@aws-sdk/core": "^3.973.17", "@aws-sdk/types": "^3.973.4", "@smithy/fetch-http-handler": "^5.3.12", "@smithy/node-http-handler": "^4.4.13", "@smithy/property-provider": "^4.2.10", "@smithy/protocol-http": "^5.3.10", "@smithy/smithy-client": "^4.12.1", "@smithy/types": "^4.13.0", "@smithy/util-stream": "^4.5.16", "tslib": "^2.6.2" } }, "sha512-b/bDL76p51+yQ+0O9ZDH5nw/ioE0sRYkjwjOwFWAWZXo6it2kQZUOXhVpjohx3ldKyUxt/SwAivjUu1Nr/PWlQ=="], + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.18", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/types": "^3.973.5", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/node-http-handler": "^4.4.14", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/util-stream": "^4.5.17", "tslib": "^2.6.2" } }, "sha512-NyB6smuZAixND5jZumkpkunQ0voc4Mwgkd+SZ6cvAzIB7gK8HV8Zd4rS8Kn5MmoGgusyNfVGG+RLoYc4yFiw+A=="], - "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.15", "", { "dependencies": { "@aws-sdk/core": "^3.973.17", "@aws-sdk/credential-provider-env": "^3.972.15", "@aws-sdk/credential-provider-http": "^3.972.17", "@aws-sdk/credential-provider-login": "^3.972.15", "@aws-sdk/credential-provider-process": "^3.972.15", "@aws-sdk/credential-provider-sso": "^3.972.15", "@aws-sdk/credential-provider-web-identity": "^3.972.15", "@aws-sdk/nested-clients": "^3.996.5", "@aws-sdk/types": "^3.973.4", "@smithy/credential-provider-imds": "^4.2.10", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-qWnM+wB8MmU2kKY7f4KowKjOjkwRosaFxrtseEEIefwoXn1SjN+CbHzXBVdTAQxxkbBiqhPgJ/WHiPtES4grRQ=="], + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.16", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/credential-provider-env": "^3.972.16", "@aws-sdk/credential-provider-http": "^3.972.18", "@aws-sdk/credential-provider-login": "^3.972.16", "@aws-sdk/credential-provider-process": "^3.972.16", "@aws-sdk/credential-provider-sso": "^3.972.16", "@aws-sdk/credential-provider-web-identity": "^3.972.16", "@aws-sdk/nested-clients": "^3.996.6", "@aws-sdk/types": "^3.973.5", "@smithy/credential-provider-imds": "^4.2.11", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hzAnzNXKV0A4knFRWGu2NCt72P4WWxpEGnOc6H3DptUjC4oX3hGw846oN76M1rTHAOwDdbhjU0GAOWR4OUfTZg=="], - "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.15", "", { "dependencies": { "@aws-sdk/core": "^3.973.17", "@aws-sdk/nested-clients": "^3.996.5", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/protocol-http": "^5.3.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-x92FJy34/95wgu+qOGD8SHcgh1hZ9Qx2uFtQEGn4m9Ljou8ICIv3Ybq5yxdB7A60S8ZGCQB0mIopmjJwiLbh5g=="], + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.16", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/nested-clients": "^3.996.6", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-VI0kXTlr0o1FTay+Jvx6AKqx5ECBgp7X4VevGBEbuXdCXnNp7SPU0KvjsOLVhIz3OoPK4/lTXphk43t0IVk65w=="], - "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.16", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.15", "@aws-sdk/credential-provider-http": "^3.972.17", "@aws-sdk/credential-provider-ini": "^3.972.15", "@aws-sdk/credential-provider-process": "^3.972.15", "@aws-sdk/credential-provider-sso": "^3.972.15", "@aws-sdk/credential-provider-web-identity": "^3.972.15", "@aws-sdk/types": "^3.973.4", "@smithy/credential-provider-imds": "^4.2.10", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-7mlt14Ee4rPFAFUVgpWE7+0CBhetJJyzVFqfIsMp7sgyOSm9Y/+qHZOWAuK5I4JNc+Y5PltvJ9kssTzRo92iXQ=="], + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.17", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.16", "@aws-sdk/credential-provider-http": "^3.972.18", "@aws-sdk/credential-provider-ini": "^3.972.16", "@aws-sdk/credential-provider-process": "^3.972.16", "@aws-sdk/credential-provider-sso": "^3.972.16", "@aws-sdk/credential-provider-web-identity": "^3.972.16", "@aws-sdk/types": "^3.973.5", "@smithy/credential-provider-imds": "^4.2.11", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-98MAcQ2Dk7zkvgwZ5f6fLX2lTyptC3gTSDx4EpvTdJWET8qs9lBPYggoYx7GmKp/5uk0OwVl0hxIDZsDNS/Y9g=="], - "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.15", "", { "dependencies": { "@aws-sdk/core": "^3.973.17", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-PrH3iTeD18y/8uJvQD2s/T87BTGhsdS/1KZU7ReWHXsplBwvCqi7AbnnNbML1pFlQwRWCE2RdSZFWDVId3CvkA=="], + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.16", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-n89ibATwnLEg0ZdZmUds5bq8AfBAdoYEDpqP3uzPLaRuGelsKlIvCYSNNvfgGLi8NaHPNNhs1HjJZYbqkW9b+g=="], - "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.15", "", { "dependencies": { "@aws-sdk/core": "^3.973.17", "@aws-sdk/nested-clients": "^3.996.5", "@aws-sdk/token-providers": "3.1002.0", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-M/+LBHTPKZxxXckM6m4dnJeR+jlm9NynH9b2YDswN4Zj2St05SK/crdL3Wy3WfJTZootnnhm3oTh87Usl7PS7w=="], + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.16", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/nested-clients": "^3.996.6", "@aws-sdk/token-providers": "3.1003.0", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-b9of7tQgERxgcEcwAFWvRe84ivw+Kw6b3jVuz/6LQzonkomiY5UoWfprkbjc8FSCQ2VjDqKTvIRA9F0KSQ025w=="], - "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.15", "", { "dependencies": { "@aws-sdk/core": "^3.973.17", "@aws-sdk/nested-clients": "^3.996.5", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-QTH6k93v+UOfFam/ado8zc71tH+enTVyuvLy9uEWXX1x894dN5ovtf/MdBDgFwq3g6c9mbtgVJ4B+yBqDtXvdA=="], + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.16", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/nested-clients": "^3.996.6", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-PaOH5jFoPQX4WkqpKzKh9cM7rieKtbgEGqrZ+ybGmotJhcvhI/xl69yCwMbHGnpQJJmHZIX9q2zaPB7HTBn/4w=="], - "@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.9", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/eventstream-codec": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-mKPiiVssgFDWkAXdEDh8+wpr2pFSX/fBn2onXXnrfIAYbdZhYb4WilKbZ3SJMUnQi+Y48jZMam5J0RrgARluaA=="], + "@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/eventstream-codec": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-g2Z9s6Y4iNh0wICaEqutgYgt/Pmhv5Ev9G3eKGFe2w9VuZDhc76vYdop6I5OocmpHV79d4TuLG+JWg5rQIVDVA=="], - "@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-mB2+3G/oxRC+y9WRk0KCdradE2rSfxxJpcOSmAm+vDh3ex3WQHVLZ1catNIe1j5NQ+3FLBsNMRPVGkZ43PRpjw=="], + "@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-VWndapHYCfwLgPpCb/xwlMKG4imhFzKJzZcKOEioGn7OHY+6gdr0K7oqy1HZgbLa3ACznZ9fku+DzmAi8fUC0g=="], - "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-5XHwjPH1lHB+1q4bfC7T8Z5zZrZXfaLcjSMwTd1HPSPrCmPFMbg3UQ5vgNWcVj0xoX4HWqTGkSf2byrjlnRg5w=="], + "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ=="], - "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-iFnaMFMQdljAPrvsCVKYltPt2j40LQqukAbXvW7v0aL5I+1GO7bZ/W8m12WxW3gwyK5p5u1WlHg8TSAizC5cZw=="], + "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w=="], - "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-dY4v3of5EEMvik6+UDwQ96KfUFDk8m1oZDdkSc5lwi4o7rFrjnv0A+yTV+gu230iybQZnKgDLg/rt2P3H+Vscw=="], + "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ=="], - "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.17", "", { "dependencies": { "@aws-sdk/core": "^3.973.17", "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-endpoints": "^3.996.3", "@smithy/core": "^3.23.7", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-HHArkgWzomuwufXwheQqkddu763PWCpoNTq1dGjqXzJT/lojX3VlOqjNSR2Xvb6/T9ISfwYcMOcbFgUp4EWxXA=="], + "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.18", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@smithy/core": "^3.23.8", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-KcqQDs/7WtoEnp52+879f8/i1XAJkgka5i4arOtOCPR10o4wWo3VRecDI9Gxoh6oghmLCnIiOSKyRcXI/50E+w=="], - "@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-format-url": "^3.972.6", "@smithy/eventstream-codec": "^4.2.10", "@smithy/eventstream-serde-browser": "^4.2.10", "@smithy/fetch-http-handler": "^5.3.12", "@smithy/protocol-http": "^5.3.10", "@smithy/signature-v4": "^5.3.10", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.1", "@smithy/util-hex-encoding": "^4.2.1", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-cWf+8iUUnitgFuUu/ryK2uVfx7f5ezdhGwsjLLEEC1Nk716Ld2Hw4LA8iipyVcQI3EarvK6ExY2dSBET/0PYng=="], + "@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.12", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-format-url": "^3.972.7", "@smithy/eventstream-codec": "^4.2.11", "@smithy/eventstream-serde-browser": "^4.2.11", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-iyPP6FVDKe/5wy5ojC0akpDFG1vX3FeCUU47JuwN8xfvT66xlEI8qUJZPtN55TJVFzzWZJpWL78eqUE31md08Q=="], - "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.5", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.17", "@aws-sdk/middleware-host-header": "^3.972.6", "@aws-sdk/middleware-logger": "^3.972.6", "@aws-sdk/middleware-recursion-detection": "^3.972.6", "@aws-sdk/middleware-user-agent": "^3.972.17", "@aws-sdk/region-config-resolver": "^3.972.6", "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-endpoints": "^3.996.3", "@aws-sdk/util-user-agent-browser": "^3.972.6", "@aws-sdk/util-user-agent-node": "^3.973.2", "@smithy/config-resolver": "^4.4.9", "@smithy/core": "^3.23.7", "@smithy/fetch-http-handler": "^5.3.12", "@smithy/hash-node": "^4.2.10", "@smithy/invalid-dependency": "^4.2.10", "@smithy/middleware-content-length": "^4.2.10", "@smithy/middleware-endpoint": "^4.4.21", "@smithy/middleware-retry": "^4.4.38", "@smithy/middleware-serde": "^4.2.11", "@smithy/middleware-stack": "^4.2.10", "@smithy/node-config-provider": "^4.3.10", "@smithy/node-http-handler": "^4.4.13", "@smithy/protocol-http": "^5.3.10", "@smithy/smithy-client": "^4.12.1", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.10", "@smithy/util-base64": "^4.3.1", "@smithy/util-body-length-browser": "^4.2.1", "@smithy/util-body-length-node": "^4.2.2", "@smithy/util-defaults-mode-browser": "^4.3.37", "@smithy/util-defaults-mode-node": "^4.2.40", "@smithy/util-endpoints": "^3.3.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-retry": "^4.2.10", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-zn0WApcULn7Rtl6T+KP2CQTZo/7wOa2YV1yHQnbijTQoi4YXQHM8s21JcJzt33/mqPh8AdvWX1f+83KvKuxlZw=="], + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.6", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.18", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.18", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.3", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.8", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.22", "@smithy/middleware-retry": "^4.4.39", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.38", "@smithy/util-defaults-mode-node": "^4.2.41", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-blNJ3ugn4gCQ9ZSZi/firzKCvVl5LvPFVxv24LprENeWI4R8UApG006UQkF4SkmLygKq2BQXRad2/anQ13Te4Q=="], - "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/config-resolver": "^4.4.9", "@smithy/node-config-provider": "^4.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-Aa5PusHLXAqLTX1UKDvI3pHQJtIsF7Q+3turCHqfz/1F61/zDMWfbTC8evjhrrYVAtz9Vsv3SJ/waSUeu7B6gw=="], + "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA=="], - "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1002.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.17", "@aws-sdk/nested-clients": "^3.996.5", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-x972uKOydFn4Rb0PZJzLdNW59rH0KWC78Q2JbQzZpGlGt0DxjYdDRwBG6F42B1MyaEwHGqO/tkGc4r3/PRFfMw=="], + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1003.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/nested-clients": "^3.996.6", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-SOyyWNdT7njKRwtZ1JhwHlH1csv6Pkgf305X96/OIfnhq1pU/EjmT6W6por57rVrjrKuHBuEIXgpWv8OgoMHpg=="], - "@aws-sdk/types": ["@aws-sdk/types@3.973.4", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q=="], + "@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], - "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.10", "@smithy/util-endpoints": "^3.3.1", "tslib": "^2.6.2" } }, "sha512-yWIQSNiCjykLL+ezN5A+DfBb1gfXTytBxm57e64lYmwxDHNmInYHRJYYRAGWG1o77vKEiWaw4ui28e3yb1k5aQ=="], + "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.4", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-endpoints": "^3.3.2", "tslib": "^2.6.2" } }, "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA=="], - "@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/querystring-builder": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-0YNVNgFyziCejXJx0rzxPiD2rkxTWco4c9wiMF6n37Tb9aQvIF8+t7GyEyIFCwQHZ0VMQaAl+nCZHOYz5I5EKw=="], + "@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/querystring-builder": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-V+PbnWfUl93GuFwsOHsAq7hY/fnm9kElRqR8IexIJr5Rvif9e614X5sGSyz3mVSf1YAZ+VTy63W1/pGdA55zyA=="], - "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.4", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog=="], + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.5", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ=="], - "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-Fwr/llD6GOrFgQnKaI2glhohdGuBDfHfora6iG9qsBBBR8xv1SdCSwbtf5CWlUdCw5X7g76G/9Hf0Inh0EmoxA=="], + "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw=="], - "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.2", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.17", "@aws-sdk/types": "^3.973.4", "@smithy/node-config-provider": "^4.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-lpaIuekdkpw7VRiik0IZmd6TyvEUcuLgKZ5fKRGpCA3I4PjrD/XH15sSwW+OptxQjNU4DEzSxag70spC9SluvA=="], + "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.3", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.18", "@aws-sdk/types": "^3.973.5", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-8s2cQmTUOwcBlIJyI9PAZNnnnF+cGtdhHc1yzMMsSD/GR/Hxj7m0IGUE92CslXXb8/p5Q76iqOCjN1GFwyf+1A=="], - "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.9", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-ItnlMgSqkPrUfJs7EsvU/01zw5UeIb2tNPhD09LBLHbg+g+HDiKibSLwpkuz/ZIlz4F2IMn+5XgE4AK/pfPuog=="], + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="], @@ -263,23 +263,23 @@ "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], - "@biomejs/biome": ["@biomejs/biome@2.4.5", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.5", "@biomejs/cli-darwin-x64": "2.4.5", "@biomejs/cli-linux-arm64": "2.4.5", "@biomejs/cli-linux-arm64-musl": "2.4.5", "@biomejs/cli-linux-x64": "2.4.5", "@biomejs/cli-linux-x64-musl": "2.4.5", "@biomejs/cli-win32-arm64": "2.4.5", "@biomejs/cli-win32-x64": "2.4.5" }, "bin": { "biome": "bin/biome" } }, "sha512-OWNCyMS0Q011R6YifXNOg6qsOg64IVc7XX6SqGsrGszPbkVCoaO7Sr/lISFnXZ9hjQhDewwZ40789QmrG0GYgQ=="], + "@biomejs/biome": ["@biomejs/biome@2.4.6", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.6", "@biomejs/cli-darwin-x64": "2.4.6", "@biomejs/cli-linux-arm64": "2.4.6", "@biomejs/cli-linux-arm64-musl": "2.4.6", "@biomejs/cli-linux-x64": "2.4.6", "@biomejs/cli-linux-x64-musl": "2.4.6", "@biomejs/cli-win32-arm64": "2.4.6", "@biomejs/cli-win32-x64": "2.4.6" }, "bin": { "biome": "bin/biome" } }, "sha512-QnHe81PMslpy3mnpL8DnO2M4S4ZnYPkjlGCLWBZT/3R9M6b5daArWMMtEfP52/n174RKnwRIf3oT8+wc9ihSfQ=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lGS4Nd5O3KQJ6TeWv10mElnx1phERhBxqGP/IKq0SvZl78kcWDFMaTtVK+w3v3lusRFxJY78n07PbKplirsU5g=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-NW18GSyxr+8sJIqgoGwVp5Zqm4SALH4b4gftIA0n62PTuBs6G2tHlwNAOj0Vq0KKSs7Sf88VjjmHh0O36EnzrQ=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-6MoH4tyISIBNkZ2Q5T1R7dLd5BsITb2yhhhrU9jHZxnNSNMWl+s2Mxu7NBF8Y3a7JJcqq9nsk8i637z4gqkJxQ=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-4uiE/9tuI7cnjtY9b07RgS7gGyYOAfIAGeVJWEfeCnAarOAS7qVmuRyX6d7JTKw28/mt+rUzMasYeZ+0R/U1Mw=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-U1GAG6FTjhAO04MyH4xn23wRNBkT6H7NentHh+8UxD6ShXKBm5SY4RedKJzkUThANxb9rUKIPc7B8ew9Xo/cWg=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-kMLaI7OF5GN1Q8Doymjro1P8rVEoy7BKQALNz6fiR8IC1WKduoNyteBtJlHT7ASIL0Cx2jR6VUOBIbcB1B8pew=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-iqLDgpzobG7gpBF0fwEVS/LT8kmN7+S0E2YKFDtqliJfzNLnAiV2Nnyb+ehCDCJgAZBASkYHR2o60VQWikpqIg=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-F/JdB7eN22txiTqHM5KhIVt0jVkzZwVYrdTR1O3Y4auBOQcXxHK4dxULf4z43QyZI5tsnQJrRBHZy7wwtL+B3A=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.5", "", { "os": "linux", "cpu": "x64" }, "sha512-NdODlSugMzTlENPTa4z0xB82dTUlCpsrOxc43///aNkTLblIYH4XpYflBbf5ySlQuP8AA4AZd1qXhV07IdrHdQ=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.6", "", { "os": "linux", "cpu": "x64" }, "sha512-oHXmUFEoH8Lql1xfc3QkFLiC1hGR7qedv5eKNlC185or+o4/4HiaU7vYODAH3peRCfsuLr1g6v2fK9dFFOYdyw=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.5", "", { "os": "linux", "cpu": "x64" }, "sha512-NlKa7GpbQmNhZf9kakQeddqZyT7itN7jjWdakELeXyTU3pg/83fTysRRDPJD0akTfKDl6vZYNT9Zqn4MYZVBOA=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.6", "", { "os": "linux", "cpu": "x64" }, "sha512-C9s98IPDu7DYarjlZNuzJKTjVHN03RUnmHV5htvqsx6vEUXCDSJ59DNwjKVD5XYoSS4N+BYhq3RTBAL8X6svEg=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-EBfrTqRIWOFSd7CQb/0ttjHMR88zm3hGravnDwUA9wHAaCAYsULKDebWcN5RmrEo1KBtl/gDVJMrFjNR0pdGUw=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-xzThn87Pf3YrOGTEODFGONmqXpTwUNxovQb72iaUOdcw8sBSY3+3WD8Hm9IhMYLnPi0n32s3L3NWU6+eSjfqFg=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.5", "", { "os": "win32", "cpu": "x64" }, "sha512-Pmhv9zT95YzECfjEHNl3mN9Vhusw9VA5KHY0ZvlGsxsjwS5cb7vpRnHzJIv0vG7jB0JI7xEaMH9ddfZm/RozBw=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.6", "", { "os": "win32", "cpu": "x64" }, "sha512-7++XhnsPlr1HDbor5amovPjOH6vsrFOCdp93iKXhFn6bcMUI6soodj3WWKfgEO6JosKU1W5n3uky3WW9RlRjTg=="], "@bufbuild/protobuf": ["@bufbuild/protobuf@2.11.0", "", {}, "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ=="], @@ -455,7 +455,7 @@ "@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="], - "@types/node": ["@types/node@25.3.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ=="], + "@types/node": ["@types/node@25.3.5", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA=="], "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], @@ -467,21 +467,21 @@ "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], - "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260304.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260304.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260304.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260304.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260304.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260304.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260304.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260304.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-Xj0ZeHEy+yJ/bIg6psPwl0POvBf1j5u7IZAXsUqgvgWbMIvdM9JOGmhpifcj6j28LcXM6GTvXUoXwlatxJ73Qg=="], + "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260306.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260306.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260306.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260306.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260306.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260306.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260306.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260306.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-4m7cOjtKu+iLazWW5MuJuI2ZZMkQkS42+GxN6FVdja1nL0t47l1wpaTnzUa1Ny9Xa0opIJ7psPAMBKYAPKbCKA=="], - "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260304.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TnTUxYt+dShRSoeOldx7VlKoEG+bvPHnyPEBImlNc7c3WP0AHYyNHrNg6EbLbzkOorARtd06J3Vk+XYzkrRzZg=="], + "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260306.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4vuh4VlPydMS/nymDzjJIKDk3dntnEEB5UzyJV9mM4kxF5+geFgJih1DTtZS3qVafhHLB3e4l8omtvGftMnb8g=="], - "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260304.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1nwXX1zbyYI3sDKdaR8NsBdM7LmE0J6OzVtlWgEJ/8YR7oC2/HY6/SfShF3DHHcEOHOFxRLbkJ9zVTJJspWLCw=="], + "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260306.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-qxYfv0aM4KCZPEe584KIjT5sO4uR+xdyuQXX5tXbnH1UoksIz7bvJ9KUgRloS/q/ww0f8UjPS2+27LnRA4y7ig=="], - "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260304.1", "", { "os": "linux", "cpu": "arm" }, "sha512-TXZClCJVteK2f9gcI+I7o1Sxgq3qdMtraXOP9GZF8o0sKCLdDWENN8uORfZSeQv2qOJohcKvrrEz6LLSSngvEg=="], + "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260306.1", "", { "os": "linux", "cpu": "arm" }, "sha512-8gRAFx0ExDWHOmphl8mzBrSoGWnLWDU4VpxkPRsWqaJpHVbjr9Yk2QkuJNIaDmF6q44eJmW/huSiObmHTbZ1UQ=="], - "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260304.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-cw+xqroXtsk/yVTKbelcPWMd6oZdET9kNWmigyc189KWwzOu2eq2EPXPQsrhEigq8O3j0xW0z3q2oqG+smOiXg=="], + "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260306.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-8G0BKvTkE+eKX1tSnyKeDaf3bWPWY7OI77SMipagCAyYi06v4gxx+IVE3Px7W7kLX2Wqp1MjWDXu2N76wfJtXQ=="], - "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260304.1", "", { "os": "linux", "cpu": "x64" }, "sha512-EXufnN4PG0HYBHYbHXQXXRXtaQKuKBT3e6nxPhKnwpBBgy2MgWDIxzroTLvI9+SllhbJQzHNZOWiB+SU+KdCNw=="], + "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260306.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rsJV3Z9J/zYCEtcqvm+WfLAml3i1OAyMEUn0hja7i8C0kzE+tXKXzsJ0+I1TrSU5O7hHvqlLTvueBoCoM4aL4g=="], - "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260304.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-Be9yyDDbT/PEdNlhG+NXT47fwuiIeN0+/9BkeRKkiLgzY8DqQIC9w5FRWmwAJ+9PVa2sKr5cjD1SpJDHGrPIrA=="], + "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260306.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-US1WsIu9IukaFzM+w8wt0fIAkmk2WtxeVuk8nkbrnH9S3ax39r0J4ikMNZSXEJE0VMxhXJoymzfWxhj3s9yW/Q=="], - "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260304.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lg/w+rZ9NIUoqSsk2TbtDsqyD9nW0/rhTMYd14RFP7vuNijLrTbl7GPiMhFtMxaqCSOFapwbql7/3lU4BKHB6g=="], + "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260306.1", "", { "os": "win32", "cpu": "x64" }, "sha512-MlneT0RWS9Zdb8XoWvHsUgmnMJu6K3S0BXRu5ZgUYjcbQKlkz+Z87aUB8eX8qnDFd9csJcMp3+ZrgQ/LKVGP1g=="], "@typescript/vfs": ["@typescript/vfs@1.6.4", "", { "dependencies": { "debug": "^4.4.3" }, "peerDependencies": { "typescript": "*" } }, "sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ=="], @@ -821,7 +821,7 @@ "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], - "openai": ["openai@6.25.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-mEh6VZ2ds2AGGokWARo18aPISI1OhlgdEIC1ewhkZr8pSIT31dec0ecr9Nhxx0JlybyOgoAT1sWeKtwPZzJyww=="], + "openai": ["openai@6.27.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-osTKySlrdYrLYTt0zjhY8yp0JUBmWDCN+Q+QxsV4xMQnnoVFpylgKGgxwN8sSdTNw0G4y+WUXs4eCMWpyDNWZQ=="], "p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="], diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index 5b88d6374..1d202cbe8 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -1,10 +1,18 @@ # Changelog ## [Unreleased] + ### Added +- Exported `ThinkingLevel` selector constants and types for configuring agent reasoning behavior +- Added `inherit` thinking level option to defer reasoning configuration to higher-level selectors - Added `serviceTier` option to configure service tier for agent requests +### Changed + +- Changed `thinkingLevel` from required string to optional `Effort` type, allowing undefined state +- Updated `setThinkingLevel()` method to accept `Effort | undefined` instead of `ThinkingLevel` string + ## [13.4.0] - 2026-03-01 ### Added diff --git a/packages/agent/src/agent.ts b/packages/agent/src/agent.ts index 745c5e998..c3ad101ad 100644 --- a/packages/agent/src/agent.ts +++ b/packages/agent/src/agent.ts @@ -5,6 +5,7 @@ import { type AssistantMessage, type CursorExecHandlers, type CursorToolResultHandler, + type Effort, getBundledModel, type ImageContent, type Message, @@ -14,7 +15,6 @@ import { streamSimple, type TextContent, type ThinkingBudgets, - type ThinkingLevel, type ToolChoice, type ToolResultMessage, } from "@oh-my-pi/pi-ai"; @@ -175,7 +175,7 @@ export class Agent { #state: AgentState = { systemPrompt: "", model: getBundledModel("google", "gemini-2.5-flash-lite-preview-06-17"), - thinkingLevel: "off", + thinkingLevel: undefined, tools: [], messages: [], isStreaming: false, @@ -416,7 +416,7 @@ export class Agent { this.#state.model = m; } - setThinkingLevel(l: ThinkingLevel) { + setThinkingLevel(l: Effort | undefined) { this.#state.thinkingLevel = l; } @@ -669,7 +669,7 @@ export class Agent { // Clear Cursor tool result buffer at start of each run this.#cursorToolResultBuffer = []; - const reasoning = this.#state.thinkingLevel === "off" ? undefined : this.#state.thinkingLevel; + const reasoning = this.#state.thinkingLevel; const context: AgentContext = { systemPrompt: this.#state.systemPrompt, diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index 96692861d..51b371865 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -4,5 +4,7 @@ export * from "./agent"; export * from "./agent-loop"; // Proxy utilities export * from "./proxy"; +// Thinking selectors +export * from "./thinking"; // Types export * from "./types"; diff --git a/packages/agent/src/thinking.ts b/packages/agent/src/thinking.ts new file mode 100644 index 000000000..e89c1e834 --- /dev/null +++ b/packages/agent/src/thinking.ts @@ -0,0 +1,19 @@ +import { Effort } from "@oh-my-pi/pi-ai"; + +/** + * Agent-local thinking selector. + * + * `off` disables reasoning, while `inherit` defers to a higher-level selector. + */ +export const ThinkingLevel = { + Inherit: "inherit", + Off: "off", + Minimal: Effort.Minimal, + Low: Effort.Low, + Medium: Effort.Medium, + High: Effort.High, + XHigh: Effort.XHigh, +} as const; + +export type ThinkingLevel = (typeof ThinkingLevel)[keyof typeof ThinkingLevel]; +export type ResolvedThinkingLevel = Exclude; diff --git a/packages/agent/src/types.ts b/packages/agent/src/types.ts index ec4615ef8..78706e0e6 100644 --- a/packages/agent/src/types.ts +++ b/packages/agent/src/types.ts @@ -1,13 +1,13 @@ import type { AssistantMessageEvent, AssistantMessageEventStream, + Effort, ImageContent, Message, Model, SimpleStreamOptions, streamSimple, TextContent, - ThinkingLevel, Tool, ToolChoice, ToolResultMessage, @@ -171,7 +171,7 @@ export type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessag export interface AgentState { systemPrompt: string; model: Model; - thinkingLevel: ThinkingLevel; + thinkingLevel?: Effort; tools: AgentTool[]; messages: AgentMessage[]; // Can include attachments + custom message types isStreaming: boolean; diff --git a/packages/agent/test/agent.test.ts b/packages/agent/test/agent.test.ts index e72f08be4..3ad28ca7f 100644 --- a/packages/agent/test/agent.test.ts +++ b/packages/agent/test/agent.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "bun:test"; -import { Agent } from "@oh-my-pi/pi-agent-core"; +import { Agent, ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import { type AssistantMessage, getBundledModel, type ThinkingBudgets, type Usage } from "@oh-my-pi/pi-ai"; import { AssistantMessageEventStream } from "@oh-my-pi/pi-ai/utils/event-stream"; @@ -39,7 +39,7 @@ describe("Agent", () => { expect(agent.state).toBeDefined(); expect(agent.state.systemPrompt).toBe(""); expect(agent.state.model).toBeDefined(); - expect(agent.state.thinkingLevel).toBe("off"); + expect(agent.state.thinkingLevel).toBeUndefined(); expect(agent.state.tools).toEqual([]); expect(agent.state.messages).toEqual([]); expect(agent.state.isStreaming).toBe(false); @@ -54,13 +54,13 @@ describe("Agent", () => { initialState: { systemPrompt: "You are a helpful assistant.", model: customModel, - thinkingLevel: "low", + thinkingLevel: ThinkingLevel.Low, }, }); expect(agent.state.systemPrompt).toBe("You are a helpful assistant."); expect(agent.state.model).toBe(customModel); - expect(agent.state.thinkingLevel).toBe("low"); + expect(agent.state.thinkingLevel).toBe(ThinkingLevel.Low); }); it("should subscribe to events", () => { @@ -98,8 +98,8 @@ describe("Agent", () => { expect(agent.state.model).toBe(newModel); // Test setThinkingLevel - agent.setThinkingLevel("high"); - expect(agent.state.thinkingLevel).toBe("high"); + agent.setThinkingLevel(ThinkingLevel.High); + expect(agent.state.thinkingLevel).toBe(ThinkingLevel.High); // Test setTools const tools = [{ name: "test", description: "test tool" } as any]; diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 0651f578a..d58dcfa28 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -1,8 +1,26 @@ # Changelog ## [Unreleased] +### Breaking Changes + +- Changed `reasoning` parameter from `ThinkingLevel | undefined` to `Effort | undefined` in `SimpleStreamOptions`; 'off' is no longer valid (omit the field instead) +- Removed `supportsXhigh()` function; check `model.thinking?.maxLevel` instead +- Removed `ThinkingLevel` and `ThinkingEffort` types; use `Effort` enum +- Removed `getAvailableThinkingLevels()` and `getAvailableThinkingEfforts()` functions +- Changed `transformRequestBody()` signature to require `Model` parameter as second argument for effort validation +- Removed `thinking.ts` module export; import from `model-thinking.ts` instead + ### Added +- Added `ThinkingConfig` interface to models for canonical thinking transport metadata with min/max effort levels and provider-specific mode +- Added `thinking` field to `Model` type containing per-model thinking capabilities used to clamp and map user-facing effort levels +- Added `Effort` enum (minimal, low, medium, high, xhigh) as canonical user-facing thinking levels replacing `ThinkingLevel` +- Added `enrichModelThinking()` function to automatically populate thinking metadata on models based on their capabilities +- Added `mapEffortToAnthropicAdaptiveEffort()` function to map user effort levels to Anthropic adaptive thinking effort +- Added `mapEffortToGoogleThinkingLevel()` function to map user effort levels to Google thinking levels +- Added `requireSupportedEffort()` function to validate and clamp effort levels per model, throwing errors for unsupported combinations +- Added `clampThinkingLevelForModel()` function to clamp thinking levels to model-supported range +- Added `applyGeneratedModelPolicies()` and `linkSparkPromotionTargets()` exports from model-thinking module - Added `serviceTier` option to control OpenAI processing priority and cost (auto, default, flex, scale, priority) - Added `providerPayload` field to messages and responses for reconstructing transport-native history - Added Gemini usage provider for tracking quota and tier information @@ -11,6 +29,14 @@ ### Changed +- Changed `reasoning` parameter type from `ThinkingLevel` to `Effort` in `SimpleStreamOptions`, removing 'off' value (callers should omit the field instead) +- Changed thinking configuration to use model-specific metadata instead of hardcoded provider logic for effort mapping +- Changed OpenAI Codex request transformer to accept `Model` parameter for effort validation instead of string model ID +- Changed Anthropic provider to use model thinking metadata for determining adaptive thinking support instead of model ID pattern matching +- Changed Google Vertex and Google providers to use shorter variable names for thinking config construction +- Moved thinking-related utilities from `thinking.ts` to new `model-thinking.ts` module with expanded functionality +- Moved model policy functions from `provider-models/model-policies.ts` to `model-thinking.ts` +- Moved `googleGeminiCliUsageProvider` from `providers/google-gemini-cli-usage.ts` to `usage/gemini.ts` - Changed default OpenAI model from gpt-5.1-codex to gpt-5.4 across all providers - Changed `UsageFetchContext` to remove cache and now() dependencies—usage fetchers now use Date.now() directly - Removed `resetInMs` field from usage windows; consumers should calculate from `resetsAt` timestamp @@ -19,6 +45,13 @@ ### Removed +- Removed `thinking.ts` module; use `model-thinking.ts` instead +- Removed `provider-models/model-policies.ts` module; functionality moved to `model-thinking.ts` +- Removed `supportsXhigh()` function from models.ts; use model.thinking metadata instead +- Removed `ThinkingLevel` and `ThinkingEffort` types; use `Effort` enum instead +- Removed `getAvailableThinkingLevels()` and `getAvailableThinkingEfforts()` functions +- Removed `model-policies` export from `provider-models/index.ts` +- Removed hardcoded thinking level clamping logic from OpenAI Codex request transformer; now uses model metadata - Removed `UsageCache` and `UsageCacheEntry` interfaces—caching is now handled internally by AuthStorage - Removed `google-gemini-cli-usage` export; use new `gemini` usage provider instead - Removed `resetInMs` computation from all usage providers @@ -26,6 +59,9 @@ ### Fixed +- Fixed OpenAI Codex to reject unsupported effort levels instead of silently clamping them, providing clear error messages about supported efforts +- Fixed model cache normalization to properly apply thinking enrichment when loading cached models +- Fixed dynamic model merging to apply thinking enrichment to merged model results - Fixed OpenAI Codex streaming to properly include service_tier in SSE payloads - Fixed type safety in OpenAI responses by removing unsafe type casts on image content blocks - Fixed credential purging to respect disabled credentials when deduplicating by email @@ -47,31 +83,33 @@ - Fixed Unicode normalization to consistently apply `toWellFormed()` to all text content, including thinking blocks, ensuring proper handling of malformed UTF-16 sequences ## [13.9.1] - 2026-03-05 + ### Breaking Changes - Removed `THINKING_LEVELS`, `ALL_THINKING_LEVELS`, `ALL_THINKING_MODES`, `THINKING_MODE_DESCRIPTIONS`, and `THINKING_MODE_LABELS` exports - Renamed `formatThinking()` to `getThinkingMetadata()` with changed return type from string to `ThinkingMetadata` object - Renamed `getAvailableThinkingLevel()` to `getAvailableThinkingLevels()` and added default parameter -- Renamed `getAvailableThinkingEffort()` to `getAvailableThinkingEfforts()` and added default parameter +- Renamed `getAvailableEffort()` to `getAvailableEfforts()` and added default parameter ### Added - Added `ThinkingMetadata` type to provide structured access to thinking mode information (value, label, description) ## [13.9.0] - 2026-03-05 + ### Added -- Exported new thinking module with `ThinkingEffort`, `ThinkingLevel`, and `ThinkingMode` types for managing reasoning effort levels -- Added `getAvailableThinkingEffort()` function to determine supported thinking effort levels based on model capabilities -- Added `parseThinkingEffort()`, `parseThinkingLevel()`, and `parseThinkingMode()` functions for parsing thinking configuration strings +- Exported new thinking module with `Effort`, `ThinkingLevel`, and `ThinkingMode` types for managing reasoning effort levels +- Added `getAvailableEffort()` function to determine supported thinking effort levels based on model capabilities +- Added `parseEffort()`, `parseThinkingLevel()`, and `parseThinkingMode()` functions for parsing thinking configuration strings - Added `THINKING_LEVELS`, `ALL_THINKING_LEVELS`, and `ALL_THINKING_MODES` constants for iterating over available thinking options - Added `THINKING_MODE_DESCRIPTIONS` and `THINKING_MODE_LABELS` for displaying thinking modes in user interfaces - Added `formatThinking()` function to format thinking modes as compact display labels ### Changed -- Refactored thinking level handling to distinguish between `ThinkingEffort` (provider-level, no "off") and `ThinkingLevel` (user-facing, includes "off") -- Updated `ThinkingBudgets` type to use `ThinkingEffort` instead of `ThinkingLevel` for more precise token budget configuration +- Refactored thinking level handling to distinguish between `Effort` (provider-level, no "off") and `ThinkingLevel` (user-facing, includes "off") +- Updated `ThinkingBudgets` type to use `Effort` instead of `ThinkingLevel` for more precise token budget configuration - Improved reasoning option handling to explicitly support "off" value for disabling reasoning across all providers - Simplified thinking effort mapping logic by centralizing provider-specific clamping behavior diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index 80ff853c0..896652cea 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -12,6 +12,11 @@ import * as path from "node:path"; import { $env } from "@oh-my-pi/pi-utils"; import { AuthCredentialStore } from "../src/auth-storage"; import { createModelManager } from "../src/model-manager"; +import { + applyGeneratedModelPolicies, + CLOUDFLARE_FALLBACK_MODEL, + linkSparkPromotionTargets, +} from "../src/model-thinking"; import prevModelsJson from "../src/models.json" with { type: "json" }; import { allowsUnauthenticatedCatalogDiscovery, @@ -20,11 +25,6 @@ import { isCatalogDescriptor, PROVIDER_DESCRIPTORS, } from "../src/provider-models/descriptors"; -import { - applyGeneratedModelPolicies, - CLOUDFLARE_FALLBACK_MODEL, - linkSparkPromotionTargets, -} from "../src/provider-models/model-policies"; import { MODELS_DEV_PROVIDER_DESCRIPTORS, mapModelsDevToModels } from "../src/provider-models/openai-compat"; import { getGitLabDuoModels } from "../src/providers/gitlab-duo"; import { JWT_CLAIM_PATH } from "../src/providers/openai-codex/constants"; diff --git a/packages/ai/src/auth-storage.ts b/packages/ai/src/auth-storage.ts index 5b181f34c..faf2c5f43 100644 --- a/packages/ai/src/auth-storage.ts +++ b/packages/ai/src/auth-storage.ts @@ -11,7 +11,6 @@ import { Database, type Statement } from "bun:sqlite"; import * as fs from "node:fs/promises"; import * as path from "node:path"; import { getAgentDir, logger } from "@oh-my-pi/pi-utils"; -import { googleGeminiCliUsageProvider } from "./providers/google-gemini-cli-usage"; import { getEnvApiKey } from "./stream"; import type { Provider } from "./types"; import type { @@ -23,6 +22,7 @@ import type { UsageReport, } from "./usage"; import { claudeRankingStrategy, claudeUsageProvider } from "./usage/claude"; +import { googleGeminiCliUsageProvider } from "./usage/gemini"; import { githubCopilotUsageProvider } from "./usage/github-copilot"; import { antigravityUsageProvider } from "./usage/google-antigravity"; import { kimiUsageProvider } from "./usage/kimi"; diff --git a/packages/ai/src/index.ts b/packages/ai/src/index.ts index b9d94214a..df719797a 100644 --- a/packages/ai/src/index.ts +++ b/packages/ai/src/index.ts @@ -4,6 +4,7 @@ export * from "./api-registry"; export * from "./auth-storage"; export * from "./model-cache"; export * from "./model-manager"; +export * from "./model-thinking"; export * from "./models"; export * from "./provider-details"; export * from "./provider-models"; @@ -20,7 +21,6 @@ export * from "./providers/openai-responses"; export * from "./providers/synthetic"; export * from "./rate-limit-utils"; export * from "./stream"; -export * from "./thinking"; export * from "./types"; export * from "./usage"; export * from "./usage/claude"; diff --git a/packages/ai/src/model-manager.ts b/packages/ai/src/model-manager.ts index 4f21dea7c..522d3afd5 100644 --- a/packages/ai/src/model-manager.ts +++ b/packages/ai/src/model-manager.ts @@ -1,4 +1,5 @@ import { readModelCache, writeModelCache } from "./model-cache"; +import { enrichModelThinking } from "./model-thinking"; import { type GeneratedProvider, getBundledModels } from "./models"; import type { Api, Model, Provider } from "./types"; import { isRecord } from "./utils"; @@ -108,7 +109,7 @@ export async function resolveProviderModels(cache?.models ?? []); const dynamicModels = fetchedDynamicModels ?? []; const mergedWithoutDynamic = mergeModelSources(staticModels, modelsDevModels, cacheModels); const models = mergeDynamicModels(mergedWithoutDynamic, dynamicModels); @@ -223,7 +224,7 @@ function mergeDynamicModels( function mergeDynamicModel(existingModel: Model, dynamicModel: Model): Model { const supportsImage = existingModel.input.includes("image") || dynamicModel.input.includes("image"); - return { + return enrichModelThinking({ ...existingModel, ...dynamicModel, name: preferDiscoveryName(dynamicModel.name, existingModel.name, dynamicModel.id), @@ -240,7 +241,7 @@ function mergeDynamicModel(existingModel: Model, dynamic headers: dynamicModel.headers ? { ...existingModel.headers, ...dynamicModel.headers } : existingModel.headers, compat: dynamicModel.compat ?? existingModel.compat, contextPromotionTarget: dynamicModel.contextPromotionTarget ?? existingModel.contextPromotionTarget, - }; + }); } function preferDiscoveryCost(discoveryCost: number, fallbackCost: number): number { @@ -278,7 +279,7 @@ function normalizeModelList(value: unknown): Model[] { const models: Model[] = []; for (const item of value) { if (isModelLike(item)) { - models.push(item as Model); + models.push(enrichModelThinking(item as Model)); } } return models; diff --git a/packages/ai/src/model-thinking.ts b/packages/ai/src/model-thinking.ts new file mode 100644 index 000000000..91bd21dc7 --- /dev/null +++ b/packages/ai/src/model-thinking.ts @@ -0,0 +1,526 @@ +import type { Api, Model as ApiModel, ThinkingConfig } from "./types"; + +/** User-facing thinking levels, ordered least to most intensive. */ +export const enum Effort { + Minimal = "minimal", + Low = "low", + Medium = "medium", + High = "high", + XHigh = "xhigh", +} + +export const THINKING_EFFORTS: readonly Effort[] = [ + Effort.Minimal, + Effort.Low, + Effort.Medium, + Effort.High, + Effort.XHigh, +]; + +const DEFAULT_REASONING_EFFORTS: readonly Effort[] = [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High]; +const DEFAULT_REASONING_EFFORTS_WITH_XHIGH: readonly Effort[] = [ + Effort.Minimal, + Effort.Low, + Effort.Medium, + Effort.High, + Effort.XHigh, +]; +const GEMINI_3_PRO_EFFORTS: readonly Effort[] = [Effort.Low, Effort.High]; +const GEMINI_3_FLASH_EFFORTS: readonly Effort[] = [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High]; +const GPT_5_2_PLUS_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh]; +const GPT_5_1_CODEX_MINI_EFFORTS: readonly Effort[] = [Effort.Medium, Effort.High]; +const CLOUDFLARE_AI_GATEWAY_BASE_URL = "https://gateway.ai.cloudflare.com/v1///anthropic"; + +type SemVer = { + major: number; + minor: number; + patch: number; +}; + +type GeminiKind = "pro" | "flash"; +type AnthropicKind = "opus" | "sonnet"; +type OpenAIVariant = "base" | "codex" | "codex-max" | "codex-mini" | "codex-spark" | "max" | "nano"; + +interface GeminiModel { + family: "gemini"; + kind: GeminiKind; + version: SemVer; +} + +interface AnthropicModel { + family: "anthropic"; + kind: AnthropicKind; + version: SemVer; +} + +interface OpenAIModel { + family: "openai"; + variant: OpenAIVariant; + version: SemVer; +} + +interface UnknownModel { + family: "unknown"; + id: string; +} + +type ParsedModel = GeminiModel | AnthropicModel | OpenAIModel | UnknownModel; + +/** + * Static fallback model injected when Cloudflare AI Gateway discovery + * returns no results. Ensures the provider always has at least one usable + * model entry in the catalog. + */ +export const CLOUDFLARE_FALLBACK_MODEL: ApiModel<"anthropic-messages"> = { + id: "claude-sonnet-4-5", + name: "Claude Sonnet 4.5", + api: "anthropic-messages", + provider: "cloudflare-ai-gateway", + baseUrl: CLOUDFLARE_AI_GATEWAY_BASE_URL, + reasoning: true, + input: ["text", "image"], + cost: { + input: 3, + output: 15, + cacheRead: 0.3, + cacheWrite: 3.75, + }, + contextWindow: 200000, + maxTokens: 64000, +}; + +/** + * Returns a copy of the model with canonical thinking metadata attached. + * + * This helper belongs to catalog enrichment only. Runtime consumers should + * trust `model.thinking` and avoid inferring capabilities on demand. + */ +export function enrichModelThinking(model: ApiModel): ApiModel { + const normalizedThinking = normalizeThinkingConfig(model.thinking); + if (!model.reasoning) { + return normalizedThinking === undefined && model.thinking === undefined + ? model + : { ...model, thinking: undefined }; + } + + const thinking = normalizedThinking ?? inferModelThinking(model); + if (thinkingsEqual(normalizedThinking, thinking)) { + return model; + } + return { ...model, thinking }; +} + +/** + * Returns a copy of the model with thinking metadata recomputed from the + * canonical rules, replacing any existing `thinking`. + */ +export function refreshModelThinking(model: ApiModel): ApiModel { + if (!model.reasoning) { + const normalizedThinking = normalizeThinkingConfig(model.thinking); + return normalizedThinking === undefined && model.thinking === undefined + ? model + : { ...model, thinking: undefined }; + } + return { ...model, thinking: inferModelThinking(model) }; +} + +/** + * Apply upstream metadata corrections to a mutable array of models. + * + * Each model is first normalized through `refreshModelThinking()` so generated + * catalogs keep canonical thinking metadata and policy fixes in one pass. + */ +export function applyGeneratedModelPolicies(models: ApiModel[]): void { + for (let index = 0; index < models.length; index++) { + const model = refreshModelThinking(models[index]!); + applyGeneratedModelPolicy(model); + models[index] = model; + } +} + +/** + * Link `-spark` model variants to their base models for context promotion. + * + * When a spark model's context is exhausted, the agent can promote to the + * corresponding full model. This sets `contextPromotionTarget` on each + * spark variant that has a matching base model. + */ +export function linkSparkPromotionTargets(models: ApiModel[]): void { + for (const candidate of models) { + const parsedCandidate = parseKnownModel(candidate.id); + if (parsedCandidate.family !== "openai" || parsedCandidate.variant !== "codex-spark") continue; + const baseId = candidate.id.slice(0, -"-spark".length); + const fallback = models.find( + model => model.provider === candidate.provider && model.api === candidate.api && model.id === baseId, + ); + if (!fallback) continue; + candidate.contextPromotionTarget = `${fallback.provider}/${fallback.id}`; + } +} + +/** + * Returns supported thinking efforts from canonical model rules constrained by + * explicit model metadata. + * + * @throws Error when a reasoning-capable model is missing thinking metadata + */ +export function getSupportedEfforts(model: ApiModel): readonly Effort[] { + if (!model.reasoning) { + return []; + } + if (!model.thinking) { + throw new Error(`Model ${model.provider}/${model.id} is missing thinking metadata`); + } + const configuredEfforts = expandEffortRange(model.thinking); + const parsedModel = parseKnownModel(model.id); + if (parsedModel.family === "unknown") { + return configuredEfforts; + } + return intersectEfforts(configuredEfforts, inferSupportedEfforts(parsedModel, model)); +} + +/** + * Clamps a requested thinking level against explicit model metadata. + * + * Non-reasoning models always resolve to `undefined`. + */ +export function clampThinkingLevelForModel( + model: ApiModel | undefined, + requested: Effort | undefined, +): Effort | undefined { + if (!model) { + return requested; + } + if (!model.reasoning || requested === undefined) { + return undefined; + } + + const levels = getSupportedEfforts(model); + if (levels.includes(requested)) { + return requested; + } + + const requestedIndex = THINKING_EFFORTS.indexOf(requested); + if (requestedIndex === -1) { + return undefined; + } + + let clamped: Effort | undefined; + for (const effort of levels) { + if (THINKING_EFFORTS.indexOf(effort) > requestedIndex) { + break; + } + clamped = effort; + } + + return clamped ?? levels[0]; +} + +export function requireSupportedEffort(model: ApiModel, effort: Effort): Effort { + if (!model.reasoning) { + throw new Error(`Model ${model.provider}/${model.id} does not support thinking`); + } + const levels = getSupportedEfforts(model); + if (!levels.includes(effort)) { + throw new Error( + `Thinking effort ${effort} is not supported by ${model.provider}/${model.id}. Supported efforts: ${levels.join(", ")}`, + ); + } + return effort; +} + +/** Maps a normalized thinking effort to Google's `thinkingLevel` enum values. */ +export function mapEffortToGoogleThinkingLevel( + model: ApiModel, + effort: Effort, +): "MINIMAL" | "LOW" | "MEDIUM" | "HIGH" { + switch (requireSupportedEffort(model, effort)) { + case Effort.Minimal: + return "MINIMAL"; + case Effort.Low: + return "LOW"; + case Effort.Medium: + return "MEDIUM"; + case Effort.High: + case Effort.XHigh: + return "HIGH"; + } +} + +/** Maps a normalized thinking effort to Anthropic adaptive effort values. */ +export function mapEffortToAnthropicAdaptiveEffort( + model: ApiModel, + effort: Effort, +): "low" | "medium" | "high" | "max" { + switch (requireSupportedEffort(model, effort)) { + case Effort.Minimal: + case Effort.Low: + return "low"; + case Effort.Medium: + return "medium"; + case Effort.High: + return "high"; + case Effort.XHigh: + return "max"; + } +} + +function applyGeneratedModelPolicy(model: ApiModel): void { + const parsedModel = parseKnownModel(model.id); + if (parsedModel.family === "anthropic") { + applyAnthropicCatalogPolicy(model, parsedModel); + } + if (parsedModel.family === "openai") { + applyOpenAICatalogPolicy(model, parsedModel); + } +} + +function applyAnthropicCatalogPolicy(model: ApiModel, parsedModel: AnthropicModel): void { + // Claude Opus 4.5: models.dev reports 3x the correct cache pricing. + if (model.provider === "anthropic" && parsedModel.kind === "opus" && semverEqual(parsedModel.version, "4.5")) { + model.cost.cacheRead = 0.5; + model.cost.cacheWrite = 6.25; + } + + // Bedrock Opus 4.6: upstream cache pricing is incorrect. + if (model.provider === "amazon-bedrock" && parsedModel.kind === "opus" && semverEqual(parsedModel.version, "4.6")) { + model.cost.cacheRead = 0.5; + model.cost.cacheWrite = 6.25; + } + + // Opus 4.6 / Sonnet 4.6: 1M context is beta; clamp to 200K. + if (semverEqual(parsedModel.version, "4.6")) { + model.contextWindow = 200000; + } + + // OpenCode variants: Claude Sonnet 4/4.5 listed with 1M context, actual limit is 200K. + if ( + (model.provider === "opencode-zen" || model.provider === "opencode-go") && + parsedModel.kind === "sonnet" && + (semverEqual(parsedModel.version, "4.0") || semverEqual(parsedModel.version, "4.5")) + ) { + model.contextWindow = 200000; + } +} + +function applyOpenAICatalogPolicy(model: ApiModel, parsedModel: OpenAIModel): void { + // Codex models: 400K figure includes output budget; input window is 272K. + if (parsedModel.variant.startsWith("codex") && parsedModel.variant !== "codex-spark") { + model.contextWindow = 272000; + } +} + +function inferModelThinking(model: ApiModel): ThinkingConfig { + const parsedModel = parseKnownModel(model.id); + const efforts = inferSupportedEfforts(parsedModel, model); + const minLevel = efforts[0]; + const maxLevel = efforts.at(-1); + if (!minLevel || !maxLevel) { + throw new Error(`Model ${model.provider}/${model.id} resolved to an empty thinking range`); + } + return { + mode: inferThinkingControlMode(model, parsedModel), + minLevel, + maxLevel, + }; +} + +function normalizeThinkingConfig(thinking: ThinkingConfig | undefined): ThinkingConfig | undefined { + if (!thinking || expandEffortRange(thinking).length === 0) { + return undefined; + } + return thinking; +} + +function thinkingsEqual(left: ThinkingConfig | undefined, right: ThinkingConfig | undefined): boolean { + if (left === right) return true; + if (!left || !right) return false; + return left.mode === right.mode && left.minLevel === right.minLevel && left.maxLevel === right.maxLevel; +} + +function expandEffortRange(thinking: ThinkingConfig): readonly Effort[] { + const minIndex = THINKING_EFFORTS.indexOf(thinking.minLevel); + const maxIndex = THINKING_EFFORTS.indexOf(thinking.maxLevel); + if (minIndex === -1 || maxIndex === -1 || minIndex > maxIndex) { + return []; + } + return THINKING_EFFORTS.slice(minIndex, maxIndex + 1); +} + +function intersectEfforts(left: readonly Effort[], right: readonly Effort[]): readonly Effort[] { + return left.filter(effort => right.includes(effort)); +} + +function inferSupportedEfforts(parsedModel: ParsedModel, model: ApiModel): readonly Effort[] { + switch (parsedModel.family) { + case "openai": + return inferOpenAISupportedEfforts(parsedModel); + case "gemini": + return inferGeminiSupportedEfforts(parsedModel); + case "anthropic": + return inferAnthropicSupportedEfforts(parsedModel, model); + case "unknown": + return inferFallbackEfforts(model); + } +} + +function inferOpenAISupportedEfforts(model: OpenAIModel): readonly Effort[] { + if (model.variant === "codex-mini" && semverEqual(model.version, "5.1")) { + return GPT_5_1_CODEX_MINI_EFFORTS; + } + if (semverGte(model.version, "5.2")) { + return GPT_5_2_PLUS_EFFORTS; + } + return DEFAULT_REASONING_EFFORTS; +} + +function inferGeminiSupportedEfforts(model: GeminiModel): readonly Effort[] { + if (!semverGte(model.version, "3.0")) { + return DEFAULT_REASONING_EFFORTS; + } + return model.kind === "pro" ? GEMINI_3_PRO_EFFORTS : GEMINI_3_FLASH_EFFORTS; +} + +function inferAnthropicSupportedEfforts( + parsedModel: AnthropicModel, + model: ApiModel, +): readonly Effort[] { + if (model.api === "anthropic-messages" && semverGte(parsedModel.version, "4.6")) { + return parsedModel.kind === "opus" ? DEFAULT_REASONING_EFFORTS_WITH_XHIGH : DEFAULT_REASONING_EFFORTS; + } + return inferFallbackEfforts(model); +} + +function inferFallbackEfforts(model: ApiModel): readonly Effort[] { + if (model.api === "anthropic-messages") { + return DEFAULT_REASONING_EFFORTS_WITH_XHIGH; + } + if (model.api === "bedrock-converse-stream") { + return DEFAULT_REASONING_EFFORTS; + } + return DEFAULT_REASONING_EFFORTS; +} + +function inferThinkingControlMode( + model: ApiModel, + parsedModel: ParsedModel, +): ThinkingConfig["mode"] { + switch (model.api) { + case "google-generative-ai": + case "google-gemini-cli": + case "google-vertex": + return parsedModel.family === "gemini" && + semverGte(parsedModel.version, "3.0") && + parsedModel.version.major === 3 + ? "google-level" + : "budget"; + + case "anthropic-messages": + if (parsedModel.family === "anthropic") { + if (semverGte(parsedModel.version, "4.6")) { + return "anthropic-adaptive"; + } + if (semverGte(parsedModel.version, "4.5")) { + return "anthropic-budget-effort"; + } + } + return "budget"; + + case "bedrock-converse-stream": + return "budget"; + + default: + return "effort"; + } +} + +function parseKnownModel(modelId: string): ParsedModel { + const canonicalId = getCanonicalModelId(modelId); + return ( + parseGeminiModel(canonicalId) ?? + parseAnthropicModel(canonicalId) ?? + parseOpenAIModel(canonicalId) ?? { family: "unknown", id: canonicalId } + ); +} + +function parseGeminiModel(modelId: string): GeminiModel | null { + const match = /gemini-(\d+(?:\.\d+){0,2})-(pro|flash)\b/.exec(modelId); + if (!match) { + return null; + } + const version = parseSemVer(match[1]); + if (!version) { + return null; + } + return { family: "gemini", kind: match[2] as GeminiKind, version }; +} + +function parseAnthropicModel(modelId: string): AnthropicModel | null { + const match = /claude-(opus|sonnet)-(\d+(?:[.-]\d+){0,2})\b/.exec(modelId); + if (!match) { + return null; + } + const version = parseSemVer(match[2]); + if (!version) { + return null; + } + return { family: "anthropic", kind: match[1] as AnthropicKind, version }; +} + +function parseOpenAIModel(modelId: string): OpenAIModel | null { + const match = /gpt-(\d+(?:\.\d+){0,2})(?:-(codex-spark|codex-mini|codex-max|codex|max|nano))?\b/.exec(modelId); + if (!match) { + return null; + } + const version = parseSemVer(match[1]); + if (!version) { + return null; + } + return { family: "openai", variant: (match[2] as OpenAIVariant | undefined) ?? "base", version }; +} + +function createSemVer(major: number, minor: number, patch = 0): SemVer { + return { major, minor, patch }; +} + +// extend this table if we need anything more than 9.10 +const precomputeTable: Record = {}; +for (let major = 0; major <= 9; major++) { + for (let minor = 0; minor <= 10; minor++) { + const version = createSemVer(major, minor, 0); + precomputeTable[`${major}.${minor}`] = version; + precomputeTable[`${major}-${minor}`] = version; + } + precomputeTable[`${major}`] = createSemVer(major, 0, 0); +} + +function parseSemVer(version: string): SemVer | null { + return precomputeTable[version] ?? null; +} + +function semverGte(left: SemVer | string, right: SemVer | string): boolean { + return compareSemVer(left, right) >= 0; +} + +function semverEqual(left: SemVer | string, right: SemVer | string): boolean { + return compareSemVer(left, right) === 0; +} + +function compareSemVer(left: SemVer | string | null, right: SemVer | string | null): number { + left = typeof left === "string" ? parseSemVer(left) : left; + right = typeof right === "string" ? parseSemVer(right) : right; + if (!left || !right) return (left ? 1 : 0) - (right ? 1 : 0); + + if (left.major !== right.major) { + return left.major - right.major; + } + if (left.minor !== right.minor) { + return left.minor - right.minor; + } + return left.patch - right.patch; +} + +function getCanonicalModelId(modelId: string): string { + const p = modelId.lastIndexOf("/"); + return p !== -1 ? modelId.slice(p + 1) : modelId; +} diff --git a/packages/ai/src/models.json b/packages/ai/src/models.json index e25045846..4ead4729b 100644 --- a/packages/ai/src/models.json +++ b/packages/ai/src/models.json @@ -138,7 +138,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "cohere.command-r-plus-v1:0": { "id": "cohere.command-r-plus-v1:0", @@ -195,7 +200,12 @@ "cacheWrite": 0 }, "contextWindow": 163840, - "maxTokens": 81920 + "maxTokens": 81920, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek.v3.2-v1:0": { "id": "deepseek.v3.2-v1:0", @@ -214,7 +224,12 @@ "cacheWrite": 0 }, "contextWindow": 163840, - "maxTokens": 81920 + "maxTokens": 81920, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "eu.anthropic.claude-3-5-haiku-20241022-v1:0": { "id": "eu.anthropic.claude-3-5-haiku-20241022-v1:0", @@ -374,7 +389,12 @@ "cacheWrite": 1.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "eu.anthropic.claude-opus-4-1-20250805-v1:0": { "id": "eu.anthropic.claude-opus-4-1-20250805-v1:0", @@ -394,7 +414,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "eu.anthropic.claude-opus-4-20250514-v1:0": { "id": "eu.anthropic.claude-opus-4-20250514-v1:0", @@ -414,7 +439,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "eu.anthropic.claude-opus-4-5-20251101-v1:0": { "id": "eu.anthropic.claude-opus-4-5-20251101-v1:0", @@ -434,7 +464,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "eu.anthropic.claude-opus-4-6-v1": { "id": "eu.anthropic.claude-opus-4-6-v1", @@ -454,7 +489,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "eu.anthropic.claude-sonnet-4-20250514-v1:0": { "id": "eu.anthropic.claude-sonnet-4-20250514-v1:0", @@ -474,7 +514,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "eu.anthropic.claude-sonnet-4-5-20250929-v1:0": { "id": "eu.anthropic.claude-sonnet-4-5-20250929-v1:0", @@ -494,7 +539,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "eu.anthropic.claude-sonnet-4-6": { "id": "eu.anthropic.claude-sonnet-4-6", @@ -514,7 +564,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "global.amazon.nova-2-lite-v1:0": { "id": "global.amazon.nova-2-lite-v1:0", @@ -554,7 +609,12 @@ "cacheWrite": 1.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "global.anthropic.claude-opus-4-5-20251101-v1:0": { "id": "global.anthropic.claude-opus-4-5-20251101-v1:0", @@ -574,7 +634,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "global.anthropic.claude-opus-4-6-v1": { "id": "global.anthropic.claude-opus-4-6-v1", @@ -594,7 +659,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "global.anthropic.claude-sonnet-4-20250514-v1:0": { "id": "global.anthropic.claude-sonnet-4-20250514-v1:0", @@ -614,7 +684,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "global.anthropic.claude-sonnet-4-5-20250929-v1:0": { "id": "global.anthropic.claude-sonnet-4-5-20250929-v1:0", @@ -634,7 +709,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "global.anthropic.claude-sonnet-4-6": { "id": "global.anthropic.claude-sonnet-4-6", @@ -654,7 +734,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google.gemma-3-27b-it": { "id": "google.gemma-3-27b-it", @@ -751,7 +836,12 @@ "cacheWrite": 0 }, "contextWindow": 204608, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "minimax.minimax-m2.1": { "id": "minimax.minimax-m2.1", @@ -770,7 +860,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "mistral.ministral-3-14b-instruct": { "id": "mistral.ministral-3-14b-instruct", @@ -884,7 +979,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 256000 + "maxTokens": 256000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "moonshotai.kimi-k2.5": { "id": "moonshotai.kimi-k2.5", @@ -904,7 +1004,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 256000 + "maxTokens": 256000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "nvidia.nemotron-nano-12b-v2": { "id": "nvidia.nemotron-nano-12b-v2", @@ -1057,7 +1162,12 @@ "cacheWrite": 0 }, "contextWindow": 16384, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen.qwen3-coder-30b-a3b-v1:0": { "id": "qwen.qwen3-coder-30b-a3b-v1:0", @@ -1193,7 +1303,12 @@ "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "us.amazon.nova-pro-v1:0": { "id": "us.amazon.nova-pro-v1:0", @@ -1253,7 +1368,12 @@ "cacheWrite": 1.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "us.anthropic.claude-opus-4-1-20250805-v1:0": { "id": "us.anthropic.claude-opus-4-1-20250805-v1:0", @@ -1273,7 +1393,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "us.anthropic.claude-opus-4-20250514-v1:0": { "id": "us.anthropic.claude-opus-4-20250514-v1:0", @@ -1293,7 +1418,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "us.anthropic.claude-opus-4-5-20251101-v1:0": { "id": "us.anthropic.claude-opus-4-5-20251101-v1:0", @@ -1313,7 +1443,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "us.anthropic.claude-opus-4-6-v1": { "id": "us.anthropic.claude-opus-4-6-v1", @@ -1333,7 +1468,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "us.anthropic.claude-sonnet-4-20250514-v1:0": { "id": "us.anthropic.claude-sonnet-4-20250514-v1:0", @@ -1353,7 +1493,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "us.anthropic.claude-sonnet-4-5-20250929-v1:0": { "id": "us.anthropic.claude-sonnet-4-5-20250929-v1:0", @@ -1373,7 +1518,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "us.anthropic.claude-sonnet-4-6": { "id": "us.anthropic.claude-sonnet-4-6", @@ -1393,7 +1543,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "us.deepseek.r1-v1:0": { "id": "us.deepseek.r1-v1:0", @@ -1412,7 +1567,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 32768 + "maxTokens": 32768, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "us.meta.llama3-2-11b-instruct-v1:0": { "id": "us.meta.llama3-2-11b-instruct-v1:0", @@ -1568,7 +1728,12 @@ "cacheWrite": 0 }, "contextWindow": 122880, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "writer.palmyra-x5-v1:0": { "id": "writer.palmyra-x5-v1:0", @@ -1587,7 +1752,12 @@ "cacheWrite": 0 }, "contextWindow": 1040000, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "zai.glm-4.7": { "id": "zai.glm-4.7", @@ -1606,7 +1776,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "zai.glm-4.7-flash": { "id": "zai.glm-4.7-flash", @@ -1625,7 +1800,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } } }, "anthropic": { @@ -1707,7 +1887,12 @@ "cacheWrite": 1.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-haiku-4-5-20251001": { "id": "claude-haiku-4-5-20251001", @@ -1727,7 +1912,12 @@ "cacheWrite": 1.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-opus-4-0": { "id": "claude-opus-4-0", @@ -1747,7 +1937,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-opus-4-1": { "id": "claude-opus-4-1", @@ -1767,7 +1962,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-opus-4-1-20250805": { "id": "claude-opus-4-1-20250805", @@ -1787,7 +1987,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-opus-4-20250514": { "id": "claude-opus-4-20250514", @@ -1807,7 +2012,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "anthropic-adaptive", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-opus-4-5": { "id": "claude-opus-4-5", @@ -1827,7 +2037,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-budget-effort", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-opus-4-5-20251101": { "id": "claude-opus-4-5-20251101", @@ -1847,7 +2062,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-budget-effort", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-opus-4-6": { "id": "claude-opus-4-6", @@ -1867,7 +2087,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "anthropic-adaptive", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-sonnet-4-0": { "id": "claude-sonnet-4-0", @@ -1887,7 +2112,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-sonnet-4-20250514": { "id": "claude-sonnet-4-20250514", @@ -1907,7 +2137,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-adaptive", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-sonnet-4-5": { "id": "claude-sonnet-4-5", @@ -1927,7 +2162,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-budget-effort", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-sonnet-4-5-20250929": { "id": "claude-sonnet-4-5-20250929", @@ -1947,7 +2187,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-budget-effort", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-sonnet-4-6": { "id": "claude-sonnet-4-6", @@ -1967,7 +2212,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-adaptive", + "minLevel": "minimal", + "maxLevel": "high" + } } }, "cerebras": { @@ -1988,7 +2238,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 32768 + "maxTokens": 32768, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "llama3.1-8b": { "id": "llama3.1-8b", @@ -2225,7 +2480,12 @@ "cacheWrite": 1.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-opus-4": { "id": "anthropic/claude-opus-4", @@ -2245,7 +2505,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-opus-4-1": { "id": "anthropic/claude-opus-4-1", @@ -2265,7 +2530,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-opus-4-5": { "id": "anthropic/claude-opus-4-5", @@ -2285,7 +2555,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-budget-effort", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-opus-4-6": { "id": "anthropic/claude-opus-4-6", @@ -2305,7 +2580,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "anthropic-adaptive", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-sonnet-4": { "id": "anthropic/claude-sonnet-4", @@ -2325,7 +2605,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-sonnet-4-5": { "id": "anthropic/claude-sonnet-4-5", @@ -2345,7 +2630,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-budget-effort", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-sonnet-4-6": { "id": "anthropic/claude-sonnet-4-6", @@ -2365,11 +2655,16 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-adaptive", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-sonnet-4-5": { "id": "claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (latest)", + "name": "Claude Sonnet 4.5", "api": "anthropic-messages", "provider": "cloudflare-ai-gateway", "baseUrl": "https://gateway.ai.cloudflare.com/v1///anthropic", @@ -2384,8 +2679,18 @@ "cacheRead": 0.3, "cacheWrite": 3.75 }, - "contextWindow": 200000, - "maxTokens": 64000 + "contextWindow": 1000000, + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "openai/gpt-4": { "id": "openai/gpt-4", @@ -2484,7 +2789,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1-codex": { "id": "openai/gpt-5.1-codex", @@ -2504,7 +2814,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.2": { "id": "openai/gpt-5.2", @@ -2524,7 +2839,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.2-codex": { "id": "openai/gpt-5.2-codex", @@ -2544,7 +2864,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.3-codex": { "id": "openai/gpt-5.3-codex", @@ -2564,7 +2889,37 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "low", + "maxLevel": "xhigh" + } + }, + "openai/gpt-5.4": { + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "api": "anthropic-messages", + "provider": "cloudflare-ai-gateway", + "baseUrl": "https://gateway.ai.cloudflare.com/v1///anthropic", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 2.5, + "output": 15, + "cacheRead": 0.25, + "cacheWrite": 0 + }, + "contextWindow": 1050000, + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/o1": { "id": "openai/o1", @@ -2584,7 +2939,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "openai/o3": { "id": "openai/o3", @@ -2604,7 +2964,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "openai/o3-mini": { "id": "openai/o3-mini", @@ -2623,7 +2988,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "openai/o3-pro": { "id": "openai/o3-pro", @@ -2643,7 +3013,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "openai/o4-mini": { "id": "openai/o4-mini", @@ -2663,7 +3038,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } } }, "cursor": { @@ -2705,7 +3085,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-4.5-sonnet": { "id": "claude-4.5-sonnet", @@ -2745,7 +3130,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-4.6-opus-high": { "id": "claude-4.6-opus-high", @@ -2899,7 +3289,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-3-pro": { "id": "gemini-3-pro", @@ -2919,7 +3314,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "high" + } }, "gemini-3.1-pro": { "id": "gemini-3.1-pro", @@ -2939,7 +3339,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "high" + } }, "gpt-5.1-codex-max": { "id": "gpt-5.1-codex-max", @@ -2958,8 +3363,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1-codex-max-high": { "id": "gpt-5.1-codex-max-high", @@ -2979,7 +3389,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1-codex-mini": { "id": "gpt-5.1-codex-mini", @@ -2998,8 +3413,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "medium", + "maxLevel": "high" + } }, "gpt-5.1-high": { "id": "gpt-5.1-high", @@ -3038,7 +3458,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.2-codex": { "id": "gpt-5.2-codex", @@ -3057,8 +3482,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.2-codex-fast": { "id": "gpt-5.2-codex-fast", @@ -3211,7 +3641,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.3-codex": { "id": "gpt-5.3-codex", @@ -3230,8 +3665,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.3-codex-fast": { "id": "gpt-5.3-codex-fast", @@ -3402,7 +3842,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 10000 + "maxTokens": 10000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "kimi-k2.5": { "id": "kimi-k2.5", @@ -3422,7 +3867,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } } }, "github-copilot": { @@ -3451,6 +3901,11 @@ "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" }, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + }, "premiumMultiplier": 0.33 }, "claude-opus-4.5": { @@ -3477,6 +3932,11 @@ "Editor-Version": "vscode/1.107.0", "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" + }, + "thinking": { + "mode": "anthropic-budget-effort", + "minLevel": "minimal", + "maxLevel": "xhigh" } }, "claude-opus-4.6": { @@ -3504,6 +3964,11 @@ "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" }, + "thinking": { + "mode": "anthropic-adaptive", + "minLevel": "minimal", + "maxLevel": "xhigh" + }, "premiumMultiplier": 3 }, "claude-sonnet-4": { @@ -3530,6 +3995,11 @@ "Editor-Version": "vscode/1.107.0", "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" + }, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" } }, "claude-sonnet-4.5": { @@ -3556,6 +4026,11 @@ "Editor-Version": "vscode/1.107.0", "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" + }, + "thinking": { + "mode": "anthropic-budget-effort", + "minLevel": "minimal", + "maxLevel": "xhigh" } }, "claude-sonnet-4.6": { @@ -3582,6 +4057,11 @@ "Editor-Version": "vscode/1.107.0", "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" + }, + "thinking": { + "mode": "anthropic-adaptive", + "minLevel": "minimal", + "maxLevel": "high" } }, "gemini-2.5-pro": { @@ -3644,6 +4124,11 @@ "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "gemini-3-pro-preview": { @@ -3675,6 +4160,11 @@ "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false + }, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "high" } }, "gemini-3.1-pro-preview": { @@ -3706,6 +4196,11 @@ "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false + }, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "high" } }, "gpt-4.1": { @@ -3795,6 +4290,11 @@ "Editor-Version": "vscode/1.107.0", "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "gpt-5-mini": { @@ -3821,6 +4321,11 @@ "Editor-Version": "vscode/1.107.0", "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "gpt-5.1": { @@ -3847,6 +4352,11 @@ "Editor-Version": "vscode/1.107.0", "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "gpt-5.1-codex": { @@ -3873,6 +4383,11 @@ "Editor-Version": "vscode/1.107.0", "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "gpt-5.1-codex-max": { @@ -3899,6 +4414,11 @@ "Editor-Version": "vscode/1.107.0", "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "gpt-5.1-codex-mini": { @@ -3925,6 +4445,11 @@ "Editor-Version": "vscode/1.107.0", "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" + }, + "thinking": { + "mode": "effort", + "minLevel": "medium", + "maxLevel": "high" } }, "gpt-5.2": { @@ -3951,6 +4476,11 @@ "Editor-Version": "vscode/1.107.0", "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" + }, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" } }, "gpt-5.2-codex": { @@ -3977,14 +4507,18 @@ "Editor-Version": "vscode/1.107.0", "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" + }, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" } }, "gpt-5.3-codex": { "id": "gpt-5.3-codex", - "name": "GPT-5.3 Codex", + "name": "GPT-5.3-Codex", "api": "openai-responses", "provider": "github-copilot", - "premiumMultiplier": 1, "baseUrl": "https://api.individual.githubcopilot.com", "reasoning": true, "input": [ @@ -4004,6 +4538,42 @@ "Editor-Version": "vscode/1.107.0", "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" + }, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } + }, + "gpt-5.4": { + "id": "gpt-5.4", + "name": "GPT-5.4", + "api": "openai-responses", + "provider": "github-copilot", + "baseUrl": "https://api.individual.githubcopilot.com", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 0, + "output": 0, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 400000, + "maxTokens": 128000, + "headers": { + "User-Agent": "GitHubCopilotChat/0.35.0", + "Editor-Version": "vscode/1.107.0", + "Editor-Plugin-Version": "copilot-chat/0.35.0", + "Copilot-Integration-Id": "vscode-chat" + }, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" } }, "grok-code-fast-1": { @@ -4035,6 +4605,11 @@ "supportsDeveloperRole": false, "supportsReasoningEffort": false }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + }, "premiumMultiplier": 0.25 } }, @@ -4057,7 +4632,17 @@ }, "contextWindow": 200000, "maxTokens": 64000, - "provider": "gitlab-duo" + "provider": "gitlab-duo", + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "claude-opus-4-5-20251101": { "id": "claude-opus-4-5-20251101", @@ -4077,7 +4662,17 @@ }, "contextWindow": 200000, "maxTokens": 64000, - "provider": "gitlab-duo" + "provider": "gitlab-duo", + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "claude-sonnet-4-5-20250929": { "id": "claude-sonnet-4-5-20250929", @@ -4097,7 +4692,17 @@ }, "contextWindow": 200000, "maxTokens": 64000, - "provider": "gitlab-duo" + "provider": "gitlab-duo", + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "duo-chat-gpt-5-1": { "id": "duo-chat-gpt-5-1", @@ -4117,7 +4722,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "duo-chat-gpt-5-2": { "id": "duo-chat-gpt-5-2", @@ -4137,7 +4747,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "duo-chat-gpt-5-2-codex": { "id": "duo-chat-gpt-5-2-codex", @@ -4157,7 +4772,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "duo-chat-gpt-5-codex": { "id": "duo-chat-gpt-5-codex", @@ -4177,7 +4797,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "duo-chat-gpt-5-mini": { "id": "duo-chat-gpt-5-mini", @@ -4197,7 +4822,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "duo-chat-haiku-4-5": { "id": "duo-chat-haiku-4-5", @@ -4217,7 +4847,12 @@ "cacheWrite": 1.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "duo-chat-opus-4-5": { "id": "duo-chat-opus-4-5", @@ -4237,7 +4872,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "duo-chat-opus-4-6": { "id": "duo-chat-opus-4-6", @@ -4257,7 +4897,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "duo-chat-sonnet-4-5": { "id": "duo-chat-sonnet-4-5", @@ -4277,7 +4922,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "duo-chat-sonnet-4-6": { "id": "duo-chat-sonnet-4-6", @@ -4297,7 +4947,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "gpt-5-codex": { "id": "gpt-5-codex", @@ -4315,9 +4970,18 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, + "contextWindow": 400000, "maxTokens": 128000, - "provider": "gitlab-duo" + "provider": "gitlab-duo", + "thinking": { + "mode": "effort", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "gpt-5-mini-2025-08-07": { "id": "gpt-5-mini-2025-08-07", @@ -4337,7 +5001,16 @@ }, "contextWindow": 128000, "maxTokens": 16384, - "provider": "gitlab-duo" + "provider": "gitlab-duo", + "thinking": { + "mode": "effort", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "gpt-5.1-2025-11-13": { "id": "gpt-5.1-2025-11-13", @@ -4357,7 +5030,16 @@ }, "contextWindow": 128000, "maxTokens": 16384, - "provider": "gitlab-duo" + "provider": "gitlab-duo", + "thinking": { + "mode": "effort", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } } }, "google": { @@ -4479,7 +5161,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-flash-lite": { "id": "gemini-2.5-flash-lite", @@ -4499,7 +5186,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-flash-lite-preview-06-17": { "id": "gemini-2.5-flash-lite-preview-06-17", @@ -4519,7 +5211,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-flash-lite-preview-09-2025": { "id": "gemini-2.5-flash-lite-preview-09-2025", @@ -4539,7 +5236,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-flash-preview-04-17": { "id": "gemini-2.5-flash-preview-04-17", @@ -4559,7 +5261,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-flash-preview-05-20": { "id": "gemini-2.5-flash-preview-05-20", @@ -4579,7 +5286,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-flash-preview-09-2025": { "id": "gemini-2.5-flash-preview-09-2025", @@ -4599,7 +5311,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-pro": { "id": "gemini-2.5-pro", @@ -4619,7 +5336,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-pro-preview-05-06": { "id": "gemini-2.5-pro-preview-05-06", @@ -4639,7 +5361,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-pro-preview-06-05": { "id": "gemini-2.5-pro-preview-06-05", @@ -4659,7 +5386,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-3-flash-preview": { "id": "gemini-3-flash-preview", @@ -4679,7 +5411,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "google-level", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-3-pro-preview": { "id": "gemini-3-pro-preview", @@ -4699,7 +5436,12 @@ "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "google-level", + "minLevel": "low", + "maxLevel": "high" + } }, "gemini-3.1-pro-preview": { "id": "gemini-3.1-pro-preview", @@ -4719,7 +5461,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "google-level", + "minLevel": "low", + "maxLevel": "high" + } }, "gemini-3.1-pro-preview-customtools": { "id": "gemini-3.1-pro-preview-customtools", @@ -4739,7 +5486,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "google-level", + "minLevel": "low", + "maxLevel": "high" + } }, "gemini-flash-latest": { "id": "gemini-flash-latest", @@ -4759,7 +5511,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-flash-lite-latest": { "id": "gemini-flash-lite-latest", @@ -4779,7 +5536,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-live-2.5-flash": { "id": "gemini-live-2.5-flash", @@ -4799,7 +5561,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 8000 + "maxTokens": 8000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-live-2.5-flash-preview-native-audio": { "id": "gemini-live-2.5-flash-preview-native-audio", @@ -4818,7 +5585,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } } }, "google-antigravity": { @@ -4840,7 +5612,16 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "claude-opus-4-6-thinking": { "id": "claude-opus-4-6-thinking", @@ -4860,11 +5641,21 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "claude-sonnet-4-5": { "id": "claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (latest)", + "name": "Claude Sonnet 4.5", "api": "google-gemini-cli", "provider": "google-antigravity", "baseUrl": "https://daily-cloudcode-pa.sandbox.googleapis.com", @@ -4879,8 +5670,17 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 200000, - "maxTokens": 64000 + "contextWindow": 1000000, + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "claude-sonnet-4-5-thinking": { "id": "claude-sonnet-4-5-thinking", @@ -4900,7 +5700,16 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "claude-sonnet-4-6": { "id": "claude-sonnet-4-6", @@ -4919,8 +5728,17 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 200000, - "maxTokens": 64000 + "contextWindow": 1000000, + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "claude-sonnet-4-6-thinking": { "id": "claude-sonnet-4-6-thinking", @@ -4940,7 +5758,16 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "gemini-2.5-flash": { "id": "gemini-2.5-flash", @@ -4960,7 +5787,16 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "gemini-2.5-flash-thinking": { "id": "gemini-2.5-flash-thinking", @@ -4980,7 +5816,16 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65535 + "maxTokens": 65535, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "gemini-2.5-pro": { "id": "gemini-2.5-pro", @@ -5000,7 +5845,16 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "gemini-3-flash": { "id": "gemini-3-flash", @@ -5020,7 +5874,16 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "google-level", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "gemini-3-pro-high": { "id": "gemini-3-pro-high", @@ -5040,7 +5903,14 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65535 + "maxTokens": 65535, + "thinking": { + "mode": "google-level", + "levels": [ + "low", + "high" + ] + } }, "gemini-3-pro-low": { "id": "gemini-3-pro-low", @@ -5060,7 +5930,14 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65535 + "maxTokens": 65535, + "thinking": { + "mode": "google-level", + "levels": [ + "low", + "high" + ] + } }, "gemini-3.1-pro-high": { "id": "gemini-3.1-pro-high", @@ -5080,7 +5957,14 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65535 + "maxTokens": 65535, + "thinking": { + "mode": "google-level", + "levels": [ + "low", + "high" + ] + } }, "gemini-3.1-pro-low": { "id": "gemini-3.1-pro-low", @@ -5100,7 +5984,14 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65535 + "maxTokens": 65535, + "thinking": { + "mode": "google-level", + "levels": [ + "low", + "high" + ] + } }, "gpt-oss-120b-medium": { "id": "gpt-oss-120b-medium", @@ -5119,7 +6010,16 @@ "cacheWrite": 0 }, "contextWindow": 114000, - "maxTokens": 32768 + "maxTokens": 32768, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } } }, "google-gemini-cli": { @@ -5161,7 +6061,16 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "gemini-2.5-pro": { "id": "gemini-2.5-pro", @@ -5181,7 +6090,16 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "gemini-3-flash-preview": { "id": "gemini-3-flash-preview", @@ -5201,7 +6119,16 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "google-level", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "gemini-3-pro-preview": { "id": "gemini-3-pro-preview", @@ -5221,7 +6148,14 @@ "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "google-level", + "levels": [ + "low", + "high" + ] + } }, "gemini-3.1-pro-preview": { "id": "gemini-3.1-pro-preview", @@ -5241,7 +6175,14 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "google-level", + "levels": [ + "low", + "high" + ] + } } }, "google-vertex": { @@ -5363,7 +6304,16 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "gemini-2.5-flash-lite": { "id": "gemini-2.5-flash-lite", @@ -5383,7 +6333,16 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "gemini-2.5-flash-lite-preview-09-2025": { "id": "gemini-2.5-flash-lite-preview-09-2025", @@ -5403,7 +6362,16 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "gemini-2.5-pro": { "id": "gemini-2.5-pro", @@ -5423,7 +6391,16 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "gemini-3-flash-preview": { "id": "gemini-3-flash-preview", @@ -5443,7 +6420,16 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "google-level", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "gemini-3-pro-preview": { "id": "gemini-3-pro-preview", @@ -5463,7 +6449,14 @@ "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "google-level", + "levels": [ + "low", + "high" + ] + } } }, "groq": { @@ -5484,7 +6477,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemma2-9b-it": { "id": "gemma2-9b-it", @@ -5695,7 +6693,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-oss-20b": { "id": "openai/gpt-oss-20b", @@ -5714,7 +6717,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen-qwq-32b": { "id": "qwen-qwq-32b", @@ -5733,7 +6741,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-32b": { "id": "qwen/qwen3-32b", @@ -5752,7 +6765,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } } }, "huggingface": { @@ -5773,7 +6791,16 @@ "cacheWrite": 3 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "deepseek-ai/DeepSeek-V3.1": { "id": "deepseek-ai/DeepSeek-V3.1", @@ -5830,7 +6857,16 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } } }, "kilo": { @@ -6311,7 +7347,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "anthropic/claude-3.7-sonnet:thinking": { "id": "anthropic/claude-3.7-sonnet:thinking", @@ -6370,7 +7411,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "anthropic/claude-opus-4.1": { "id": "anthropic/claude-opus-4.1", @@ -6390,7 +7436,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "anthropic/claude-opus-4.5": { "id": "anthropic/claude-opus-4.5", @@ -6410,7 +7461,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "anthropic/claude-opus-4.6": { "id": "anthropic/claude-opus-4.6", @@ -6429,8 +7485,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 200000, - "maxTokens": 128000 + "contextWindow": 1000000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "anthropic/claude-sonnet-4": { "id": "anthropic/claude-sonnet-4", @@ -6450,7 +7511,12 @@ "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "anthropic/claude-sonnet-4.5": { "id": "anthropic/claude-sonnet-4.5", @@ -6470,7 +7536,12 @@ "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "anthropic/claude-sonnet-4.6": { "id": "anthropic/claude-sonnet-4.6", @@ -6489,8 +7560,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 200000, - "maxTokens": 64000 + "contextWindow": 1000000, + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "arcee-ai/coder-large": { "id": "arcee-ai/coder-large", @@ -7079,7 +8155,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek/deepseek-v3.2-exp": { "id": "deepseek/deepseek-v3.2-exp", @@ -7098,7 +8179,12 @@ "cacheWrite": 0 }, "contextWindow": 163000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek/deepseek-v3.2-speciale": { "id": "deepseek/deepseek-v3.2-speciale", @@ -7251,7 +8337,12 @@ "cacheWrite": 0 }, "contextWindow": 1048000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-2.5-flash-image": { "id": "google/gemini-2.5-flash-image", @@ -7329,7 +8420,12 @@ "cacheWrite": 0 }, "contextWindow": 1048000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-2.5-pro-preview": { "id": "google/gemini-2.5-pro-preview", @@ -7387,7 +8483,12 @@ "cacheWrite": 0 }, "contextWindow": 1048000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-3-pro-image-preview": { "id": "google/gemini-3-pro-image-preview", @@ -7426,7 +8527,12 @@ "cacheWrite": 0 }, "contextWindow": 1048000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "high" + } }, "google/gemini-3.1-flash-image-preview": { "id": "google/gemini-3.1-flash-image-preview", @@ -7579,7 +8685,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemma-3-4b-it": { "id": "google/gemma-3-4b-it", @@ -7677,6 +8788,25 @@ "contextWindow": 222222, "maxTokens": 8888 }, + "inception/mercury-2": { + "id": "inception/mercury-2", + "name": "Inception: Mercury 2", + "api": "openai-completions", + "provider": "kilo", + "baseUrl": "https://api.kilo.ai/api/gateway", + "reasoning": false, + "input": [ + "text" + ], + "cost": { + "input": 0, + "output": 0, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 222222, + "maxTokens": 8888 + }, "inception/mercury-coder": { "id": "inception/mercury-coder", "name": "Inception: Mercury Coder", @@ -8283,7 +9413,12 @@ "cacheWrite": 0 }, "contextWindow": 204000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "minimax/minimax-m2-her": { "id": "minimax/minimax-m2-her", @@ -8321,7 +9456,12 @@ "cacheWrite": 0 }, "contextWindow": 204000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "minimax/minimax-m2.5": { "id": "minimax/minimax-m2.5", @@ -8340,7 +9480,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "minimax/minimax-m2.5:free": { "id": "minimax/minimax-m2.5:free", @@ -8929,7 +10074,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "moonshotai/kimi-k2.5": { "id": "moonshotai/kimi-k2.5", @@ -8949,7 +10099,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "moonshotai/kimi-k2.5:free": { "id": "moonshotai/kimi-k2.5:free", @@ -9215,7 +10370,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "nvidia/nemotron-nano-12b-v2-vl": { "id": "nvidia/nemotron-nano-12b-v2-vl", @@ -9694,7 +10854,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-chat": { "id": "openai/gpt-5-chat", @@ -9732,8 +10897,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 64000 + "contextWindow": 400000, + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-image": { "id": "openai/gpt-5-image", @@ -9848,7 +11018,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1-chat": { "id": "openai/gpt-5.1-chat", @@ -9887,8 +11062,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1-codex-max": { "id": "openai/gpt-5.1-codex-max", @@ -9926,8 +11106,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 64000 + "contextWindow": 400000, + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "medium", + "maxLevel": "high" + } }, "openai/gpt-5.2": { "id": "openai/gpt-5.2", @@ -9947,7 +11132,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.2-chat": { "id": "openai/gpt-5.2-chat", @@ -9985,8 +11175,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.2-pro": { "id": "openai/gpt-5.2-pro", @@ -10043,8 +11238,57 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } + }, + "openai/gpt-5.4": { + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "api": "openai-completions", + "provider": "kilo", + "baseUrl": "https://api.kilo.ai/api/gateway", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 0, + "output": 0, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 1050000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } + }, + "openai/gpt-5.4-pro": { + "id": "openai/gpt-5.4-pro", + "name": "OpenAI: GPT-5.4 Pro", + "api": "openai-completions", + "provider": "kilo", + "baseUrl": "https://api.kilo.ai/api/gateway", + "reasoning": false, + "input": [ + "text" + ], + "cost": { + "input": 0, + "output": 0, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 222222, + "maxTokens": 8888 }, "openai/gpt-audio": { "id": "openai/gpt-audio", @@ -10101,7 +11345,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-oss-120b:exacto": { "id": "openai/gpt-oss-120b:exacto", @@ -10139,7 +11388,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-oss-safeguard-20b": { "id": "openai/gpt-oss-safeguard-20b", @@ -10178,7 +11432,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o1-pro": { "id": "openai/o1-pro", @@ -10217,7 +11476,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o3-deep-research": { "id": "openai/o3-deep-research", @@ -10255,7 +11519,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o3-mini-high": { "id": "openai/o3-mini-high", @@ -10294,7 +11563,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o4-mini": { "id": "openai/o4-mini", @@ -10314,7 +11588,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o4-mini-deep-research": { "id": "openai/o4-mini-deep-research", @@ -10846,7 +12125,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-235b-a22b-2507": { "id": "qwen/qwen3-235b-a22b-2507", @@ -10960,7 +12244,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-8b": { "id": "qwen/qwen3-8b", @@ -11112,7 +12401,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-max-thinking": { "id": "qwen/qwen3-max-thinking", @@ -11169,7 +12463,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-vl-235b-a22b-instruct": { "id": "qwen/qwen3-vl-235b-a22b-instruct", @@ -11911,7 +13210,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "x-ai/grok-4-fast": { "id": "x-ai/grok-4-fast", @@ -11931,7 +13235,12 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "x-ai/grok-4.1-fast": { "id": "x-ai/grok-4.1-fast", @@ -11951,7 +13260,12 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "x-ai/grok-code-fast-1": { "id": "x-ai/grok-code-fast-1", @@ -11970,7 +13284,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "xiaomi/mimo-v2-flash": { "id": "xiaomi/mimo-v2-flash", @@ -11989,7 +13308,12 @@ "cacheWrite": 0 }, "contextWindow": 262000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4-32b": { "id": "z-ai/glm-4-32b", @@ -12027,7 +13351,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.5-air": { "id": "z-ai/glm-4.5-air", @@ -12046,7 +13375,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.5v": { "id": "z-ai/glm-4.5v", @@ -12084,7 +13418,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.6:exacto": { "id": "z-ai/glm-4.6:exacto", @@ -12123,7 +13462,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.7": { "id": "z-ai/glm-4.7", @@ -12142,7 +13486,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.7-flash": { "id": "z-ai/glm-4.7-flash", @@ -12180,7 +13529,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } } }, "kimi-code": { @@ -12211,6 +13565,11 @@ "thinkingFormat": "zai", "reasoningContentField": "reasoning_content", "supportsDeveloperRole": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "kimi-k2": { @@ -12267,6 +13626,11 @@ "thinkingFormat": "zai", "reasoningContentField": "reasoning_content", "supportsDeveloperRole": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "kimi-k2.5": { @@ -12296,6 +13660,11 @@ "thinkingFormat": "zai", "reasoningContentField": "reasoning_content", "supportsDeveloperRole": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } } }, @@ -12395,7 +13764,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-haiku-4-5-20251001": { "id": "claude-haiku-4-5-20251001", @@ -12415,7 +13789,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-opus-4": { "id": "claude-opus-4", @@ -12454,7 +13833,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-opus-4-1-20250805": { "id": "claude-opus-4-1-20250805", @@ -12474,7 +13858,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-opus-4-20250514": { "id": "claude-opus-4-20250514", @@ -12494,7 +13883,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-opus-4-5": { "id": "claude-opus-4-5", @@ -12514,7 +13908,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-opus-4-5-20251101": { "id": "claude-opus-4-5-20251101", @@ -12534,7 +13933,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-opus-4-6": { "id": "claude-opus-4-6", @@ -12553,8 +13957,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 200000, - "maxTokens": 128000 + "contextWindow": 1000000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-sonnet-4": { "id": "claude-sonnet-4", @@ -12573,8 +13982,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 200000, - "maxTokens": 64000 + "contextWindow": 1000000, + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-sonnet-4-20250514": { "id": "claude-sonnet-4-20250514", @@ -12594,11 +14008,16 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-sonnet-4-5": { "id": "claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (latest)", + "name": "Claude Sonnet 4.5", "api": "openai-completions", "provider": "litellm", "baseUrl": "http://localhost:4000/v1", @@ -12613,8 +14032,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 200000, - "maxTokens": 64000 + "contextWindow": 1000000, + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-sonnet-4-5-20250929": { "id": "claude-sonnet-4-5-20250929", @@ -12634,7 +14058,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-sonnet-4-6": { "id": "claude-sonnet-4-6", @@ -12653,8 +14082,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 200000, - "maxTokens": 64000 + "contextWindow": 1000000, + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-flash": { "id": "gemini-2.5-flash", @@ -12674,7 +14108,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-flash-lite": { "id": "gemini-2.5-flash-lite", @@ -12694,7 +14133,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-pro": { "id": "gemini-2.5-pro", @@ -12714,7 +14158,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-3-flash": { "id": "gemini-3-flash", @@ -12734,7 +14183,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-3-flash-preview": { "id": "gemini-3-flash-preview", @@ -12754,7 +14208,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-3-pro": { "id": "gemini-3-pro", @@ -12774,7 +14233,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "high" + } }, "gemini-3-pro-preview": { "id": "gemini-3-pro-preview", @@ -12794,7 +14258,12 @@ "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "high" + } }, "glm-4.5": { "id": "glm-4.5", @@ -12813,7 +14282,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 98304 + "maxTokens": 98304, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "glm-4.5-air": { "id": "glm-4.5-air", @@ -12832,7 +14306,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 98304 + "maxTokens": 98304, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "glm-4.6": { "id": "glm-4.6", @@ -12851,7 +14330,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "glm-4.7": { "id": "glm-4.7", @@ -12870,7 +14354,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "glm-5": { "id": "glm-5", @@ -12889,7 +14378,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5": { "id": "gpt-5", @@ -12909,7 +14403,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5-codex": { "id": "gpt-5-codex", @@ -12928,8 +14427,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5-codex-mini": { "id": "gpt-5-codex-mini", @@ -12968,7 +14472,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1-codex": { "id": "gpt-5.1-codex", @@ -12987,8 +14496,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1-codex-max": { "id": "gpt-5.1-codex-max", @@ -13007,8 +14521,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1-codex-mini": { "id": "gpt-5.1-codex-mini", @@ -13027,8 +14546,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "medium", + "maxLevel": "high" + } }, "gpt-5.2": { "id": "gpt-5.2", @@ -13048,7 +14572,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.2-codex": { "id": "gpt-5.2-codex", @@ -13067,8 +14596,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.3-codex": { "id": "gpt-5.3-codex", @@ -13087,8 +14621,13 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.3-codex-spark": { "id": "gpt-5.3-codex-spark", @@ -13098,8 +14637,7 @@ "baseUrl": "http://localhost:4000/v1", "reasoning": true, "input": [ - "text", - "image" + "text" ], "cost": { "input": 0, @@ -13108,8 +14646,13 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 32000, - "contextPromotionTarget": "litellm/gpt-5.3-codex" + "maxTokens": 128000, + "contextPromotionTarget": "litellm/gpt-5.3-codex", + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "grok-4-1-fast-non-reasoning": { "id": "grok-4-1-fast-non-reasoning", @@ -13148,7 +14691,12 @@ "cacheWrite": 0 }, "contextWindow": 4096, - "maxTokens": 4096 + "maxTokens": 4096, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "grok-4-fast-non-reasoning": { "id": "grok-4-fast-non-reasoning", @@ -13187,7 +14735,12 @@ "cacheWrite": 0 }, "contextWindow": 4096, - "maxTokens": 4096 + "maxTokens": 4096, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "grok-code-fast-1": { "id": "grok-code-fast-1", @@ -13206,7 +14759,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 10000 + "maxTokens": 10000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "grok-imagine-image": { "id": "grok-imagine-image", @@ -13282,7 +14840,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "kimi-k2.5": { "id": "kimi-k2.5", @@ -13302,7 +14865,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "zai-glm-4.7": { "id": "zai-glm-4.7", @@ -13342,7 +14910,12 @@ "cacheWrite": 0 }, "contextWindow": 196608, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "MiniMax-M2.1": { "id": "MiniMax-M2.1", @@ -13361,7 +14934,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "MiniMax-M2.5": { "id": "MiniMax-M2.5", @@ -13380,7 +14958,12 @@ "cacheWrite": 0.375 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "MiniMax-M2.5-highspeed": { "id": "MiniMax-M2.5-highspeed", @@ -13399,7 +14982,12 @@ "cacheWrite": 0.375 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "MiniMax-M2.5-lightning": { "id": "MiniMax-M2.5-lightning", @@ -13418,7 +15006,17 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } } }, "minimax-cn": { @@ -13439,7 +15037,12 @@ "cacheWrite": 0 }, "contextWindow": 196608, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "MiniMax-M2.1": { "id": "MiniMax-M2.1", @@ -13458,7 +15061,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "MiniMax-M2.5": { "id": "MiniMax-M2.5", @@ -13477,7 +15085,12 @@ "cacheWrite": 0.375 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "MiniMax-M2.5-highspeed": { "id": "MiniMax-M2.5-highspeed", @@ -13496,7 +15109,12 @@ "cacheWrite": 0.375 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "MiniMax-M2.5-lightning": { "id": "MiniMax-M2.5-lightning", @@ -13515,7 +15133,17 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "budget", + "levels": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } } }, "minimax-code": { @@ -13542,6 +15170,11 @@ "supportsDeveloperRole": false, "thinkingFormat": "zai", "reasoningContentField": "reasoning_content" + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "MiniMax-M2.1": { @@ -13567,6 +15200,11 @@ "supportsDeveloperRole": false, "thinkingFormat": "zai", "reasoningContentField": "reasoning_content" + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "MiniMax-M2.1-lightning": { @@ -13591,7 +15229,16 @@ "reasoningContentField": "reasoning_content" }, "contextWindow": 1000000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "effort", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "MiniMax-M2.5": { "id": "MiniMax-M2.5", @@ -13616,6 +15263,11 @@ "supportsDeveloperRole": false, "thinkingFormat": "zai", "reasoningContentField": "reasoning_content" + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "MiniMax-M2.5-highspeed": { @@ -13641,6 +15293,11 @@ "supportsDeveloperRole": false, "thinkingFormat": "zai", "reasoningContentField": "reasoning_content" + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "MiniMax-M2.5-lightning": { @@ -13665,7 +15322,16 @@ "reasoningContentField": "reasoning_content" }, "contextWindow": 204800, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "effort", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } } }, "minimax-code-cn": { @@ -13692,6 +15358,11 @@ "supportsDeveloperRole": false, "thinkingFormat": "zai", "reasoningContentField": "reasoning_content" + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "MiniMax-M2.1": { @@ -13717,6 +15388,11 @@ "supportsDeveloperRole": false, "thinkingFormat": "zai", "reasoningContentField": "reasoning_content" + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "MiniMax-M2.1-lightning": { @@ -13741,7 +15417,16 @@ "reasoningContentField": "reasoning_content" }, "contextWindow": 1000000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "effort", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "MiniMax-M2.5": { "id": "MiniMax-M2.5", @@ -13766,6 +15451,11 @@ "supportsDeveloperRole": false, "thinkingFormat": "zai", "reasoningContentField": "reasoning_content" + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "MiniMax-M2.5-highspeed": { @@ -13791,6 +15481,11 @@ "supportsDeveloperRole": false, "thinkingFormat": "zai", "reasoningContentField": "reasoning_content" + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "MiniMax-M2.5-lightning": { @@ -13815,7 +15510,16 @@ "reasoningContentField": "reasoning_content" }, "contextWindow": 204800, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "effort", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } } }, "mistral": { @@ -13970,7 +15674,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "magistral-small": { "id": "magistral-small", @@ -13989,7 +15698,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "ministral-3b-latest": { "id": "ministral-3b-latest", @@ -14324,7 +16038,16 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } } }, "nanogpt": { @@ -14536,7 +16259,12 @@ "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 65535 + "maxTokens": 65535, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "amazon/nova-lite-v1": { "id": "amazon/nova-lite-v1", @@ -14661,8 +16389,13 @@ "cacheRead": 0.5, "cacheWrite": 6.25 }, - "contextWindow": 200000, - "maxTokens": 128000 + "contextWindow": 1000000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "anthropic/claude-sonnet-4.6": { "id": "anthropic/claude-sonnet-4.6", @@ -14681,8 +16414,13 @@ "cacheRead": 0.3, "cacheWrite": 3.75 }, - "contextWindow": 200000, - "maxTokens": 64000 + "contextWindow": 1000000, + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "arcee-ai/trinity-large": { "id": "arcee-ai/trinity-large", @@ -14720,7 +16458,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "asi1-mini": { "id": "asi1-mini", @@ -15006,7 +16749,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "baseten/Kimi-K2-Instruct-FP4": { "id": "baseten/Kimi-K2-Instruct-FP4", @@ -15313,7 +17061,12 @@ "cacheWrite": 1.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-opus-4-1-20250805": { "id": "claude-opus-4-1-20250805", @@ -15333,7 +17086,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-opus-4-1-thinking": { "id": "claude-opus-4-1-thinking", @@ -15448,7 +17206,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-opus-4-5-20251101": { "id": "claude-opus-4-5-20251101", @@ -15468,7 +17231,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-opus-4-thinking": { "id": "claude-opus-4-thinking", @@ -15583,7 +17351,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-sonnet-4-5-20250929": { "id": "claude-sonnet-4-5-20250929", @@ -15603,7 +17376,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "claude-sonnet-4-5-20250929-thinking": { "id": "claude-sonnet-4-5-20250929-thinking", @@ -15907,7 +17685,12 @@ "cacheWrite": 0.6 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek-ai/DeepSeek-V3.1-Terminus": { "id": "deepseek-ai/DeepSeek-V3.1-Terminus", @@ -15926,7 +17709,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek-ai/deepseek-v3.2-exp": { "id": "deepseek-ai/deepseek-v3.2-exp", @@ -16154,7 +17942,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek/deepseek-v3.2-speciale": { "id": "deepseek/deepseek-v3.2-speciale", @@ -17030,7 +18823,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-flash-lite": { "id": "gemini-2.5-flash-lite", @@ -17050,7 +18848,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-flash-lite-preview-06-17": { "id": "gemini-2.5-flash-lite-preview-06-17", @@ -17070,7 +18873,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-flash-lite-preview-09-2025": { "id": "gemini-2.5-flash-lite-preview-09-2025", @@ -17090,7 +18898,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-flash-lite-preview-09-2025-thinking": { "id": "gemini-2.5-flash-lite-preview-09-2025-thinking", @@ -17148,7 +18961,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-flash-preview-05-20": { "id": "gemini-2.5-flash-preview-05-20", @@ -17168,7 +18986,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-flash-preview-09-2025": { "id": "gemini-2.5-flash-preview-09-2025", @@ -17188,7 +19011,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-flash-preview-09-2025-thinking": { "id": "gemini-2.5-flash-preview-09-2025-thinking", @@ -17238,6 +19066,11 @@ "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "gemini-2.5-pro-exp-03-25": { @@ -17296,7 +19129,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-2.5-pro-preview-06-05": { "id": "gemini-2.5-pro-preview-06-05", @@ -17316,7 +19154,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-3-pro-preview": { "id": "gemini-3-pro-preview", @@ -17339,6 +19182,11 @@ "maxTokens": 64000, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "high" } }, "gemini-3-pro-preview-thinking": { @@ -17929,7 +19777,12 @@ "cacheWrite": 0.08333333333333334 }, "contextWindow": 1048000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-3-flash-preview-thinking": { "id": "google/gemini-3-flash-preview-thinking", @@ -17968,7 +19821,12 @@ "cacheWrite": 0.08333333333333334 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-3.1-pro-preview": { "id": "google/gemini-3.1-pro-preview", @@ -17988,7 +19846,12 @@ "cacheWrite": 0.375 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "high" + } }, "google/gemini-3.1-pro-preview-customtools": { "id": "google/gemini-3.1-pro-preview-customtools", @@ -18008,7 +19871,12 @@ "cacheWrite": 0.375 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "high" + } }, "google/gemini-3.1-pro-preview-high": { "id": "google/gemini-3.1-pro-preview-high", @@ -19820,6 +21688,11 @@ "supportsDeveloperRole": false, "thinkingFormat": "zai", "reasoningContentField": "reasoning_content" + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "minimax/minimax-01": { @@ -19877,7 +21750,12 @@ "cacheWrite": 0 }, "contextWindow": 204000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "minimax/minimax-m2.5": { "id": "minimax/minimax-m2.5", @@ -19896,7 +21774,12 @@ "cacheWrite": 0.375 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "MiniMaxAI/MiniMax-M1-80k": { "id": "MiniMaxAI/MiniMax-M1-80k", @@ -20010,7 +21893,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "mistralai/Devstral-Small-2505": { "id": "mistralai/Devstral-Small-2505", @@ -20435,7 +22323,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "moonshotai/kimi-k2-thinking-original": { "id": "moonshotai/kimi-k2-thinking-original", @@ -20493,7 +22386,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "NeverSleep/Llama-3-Lumimaid-70B-v0.1": { "id": "NeverSleep/Llama-3-Lumimaid-70B-v0.1", @@ -20626,7 +22524,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "nousresearch/hermes-4-70b": { "id": "nousresearch/hermes-4-70b", @@ -20645,7 +22548,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "nvidia/Llama-3_3-Nemotron-Super-49B-v1_5": { "id": "nvidia/Llama-3_3-Nemotron-Super-49B-v1_5", @@ -20740,7 +22648,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "nvidia/nvidia-nemotron-nano-9b-v2": { "id": "nvidia/nvidia-nemotron-nano-9b-v2", @@ -20759,7 +22672,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/chatgpt-4o-latest": { "id": "openai/chatgpt-4o-latest", @@ -21034,7 +22952,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-chat-latest": { "id": "openai/gpt-5-chat-latest", @@ -21072,8 +22995,13 @@ "cacheRead": 0.125, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 64000 + "contextWindow": 400000, + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-mini": { "id": "openai/gpt-5-mini", @@ -21093,7 +23021,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-nano": { "id": "openai/gpt-5-nano", @@ -21113,7 +23046,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-pro": { "id": "openai/gpt-5-pro", @@ -21133,7 +23071,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1": { "id": "openai/gpt-5.1", @@ -21153,7 +23096,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1-2025-11-13": { "id": "openai/gpt-5.1-2025-11-13", @@ -21230,8 +23178,13 @@ "cacheRead": 0.125, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1-codex-max": { "id": "openai/gpt-5.1-codex-max", @@ -21251,7 +23204,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1-codex-mini": { "id": "openai/gpt-5.1-codex-mini", @@ -21270,8 +23228,13 @@ "cacheRead": 0.024999999999999998, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 64000 + "contextWindow": 400000, + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "medium", + "maxLevel": "high" + } }, "openai/gpt-5.2": { "id": "openai/gpt-5.2", @@ -21291,7 +23254,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.2-chat": { "id": "openai/gpt-5.2-chat", @@ -21330,8 +23298,13 @@ "cacheRead": 0.175, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.2-pro": { "id": "openai/gpt-5.2-pro", @@ -21351,7 +23324,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.3-chat": { "id": "openai/gpt-5.3-chat", @@ -21389,8 +23367,63 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } + }, + "openai/gpt-5.4": { + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "api": "openai-completions", + "provider": "nanogpt", + "baseUrl": "https://nano-gpt.com/api/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 0, + "output": 0, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 1050000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } + }, + "openai/gpt-5.4-pro": { + "id": "openai/gpt-5.4-pro", + "name": "OpenAI: GPT-5.4 Pro", + "api": "openai-completions", + "provider": "nanogpt", + "baseUrl": "https://nano-gpt.com/api/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 30, + "output": 180, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 1050000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-oss-120b": { "id": "openai/gpt-oss-120b", @@ -21409,7 +23442,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-oss-20b": { "id": "openai/gpt-oss-20b", @@ -21428,7 +23466,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-oss-safeguard-20b": { "id": "openai/gpt-oss-safeguard-20b", @@ -21447,7 +23490,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o1": { "id": "openai/o1", @@ -21467,7 +23515,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o1-preview": { "id": "openai/o1-preview", @@ -21525,7 +23578,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o3-deep-research": { "id": "openai/o3-deep-research", @@ -21545,7 +23603,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o3-mini": { "id": "openai/o3-mini", @@ -21564,7 +23627,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o3-mini-high": { "id": "openai/o3-mini-high", @@ -21641,7 +23709,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o4-mini-deep-research": { "id": "openai/o4-mini-deep-research", @@ -21661,7 +23734,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o4-mini-high": { "id": "openai/o4-mini-high", @@ -21681,7 +23759,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "pamanseau/OpenReasoning-Nemotron-32B": { "id": "pamanseau/OpenReasoning-Nemotron-32B", @@ -21890,7 +23973,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "Qwen/Qwen3-235B-A22B": { "id": "Qwen/Qwen3-235B-A22B", @@ -21966,7 +24054,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-32b": { "id": "qwen/qwen3-32b", @@ -21985,7 +24078,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "Qwen/Qwen3-8B": { "id": "Qwen/Qwen3-8B", @@ -22099,7 +24197,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "Qwen/Qwen3-Next-80B-A3B-Instruct": { "id": "Qwen/Qwen3-Next-80B-A3B-Instruct", @@ -22137,7 +24240,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "Qwen/Qwen3-VL-235B-A22B-Instruct": { "id": "Qwen/Qwen3-VL-235B-A22B-Instruct", @@ -22176,7 +24284,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3.5-397b-a17b-thinking": { "id": "qwen/qwen3.5-397b-a17b-thinking", @@ -22215,7 +24328,12 @@ "cacheWrite": 0.5 }, "contextWindow": 1000000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3.5-plus-thinking": { "id": "qwen/qwen3.5-plus-thinking", @@ -22405,7 +24523,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen3.5-27b": { "id": "qwen3.5-27b", @@ -22424,7 +24547,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen3.5-35b-a3b": { "id": "qwen3.5-35b-a3b", @@ -22443,7 +24571,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen3.5-flash": { "id": "qwen3.5-flash", @@ -22462,7 +24595,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwq-32b": { "id": "qwq-32b", @@ -23051,7 +25189,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "study_gpt-chatgpt-4o-latest": { "id": "study_gpt-chatgpt-4o-latest", @@ -23773,7 +25916,12 @@ "cacheWrite": 0 }, "contextWindow": 163840, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "Tongyi-Zhiwen/QwenLong-L1-32B": { "id": "Tongyi-Zhiwen/QwenLong-L1-32B", @@ -24081,7 +26229,12 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "x-ai/grok-4.1-fast": { "id": "x-ai/grok-4.1-fast", @@ -24101,7 +26254,12 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "x-ai/grok-4.1-fast-reasoning": { "id": "x-ai/grok-4.1-fast-reasoning", @@ -24139,7 +26297,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "xiaomi/mimo-v2-flash": { "id": "xiaomi/mimo-v2-flash", @@ -24158,7 +26321,12 @@ "cacheWrite": 0 }, "contextWindow": 262000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "xiaomi/mimo-v2-flash-original": { "id": "xiaomi/mimo-v2-flash-original", @@ -24292,7 +26460,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.6": { "id": "z-ai/glm-4.6", @@ -24311,7 +26484,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "zai-org/glm-4.5": { "id": "zai-org/glm-4.5", @@ -24349,7 +26527,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "zai-org/glm-4.6-original": { "id": "zai-org/glm-4.6-original", @@ -24387,7 +26570,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "zai-org/glm-4.6v": { "id": "zai-org/glm-4.6v", @@ -24463,7 +26651,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "zai-org/glm-4.7-flash": { "id": "zai-org/glm-4.7-flash", @@ -24482,7 +26675,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "zai-org/glm-4.7-flash-original": { "id": "zai-org/glm-4.7-flash-original", @@ -24501,7 +26699,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "zai-org/glm-4.7-original": { "id": "zai-org/glm-4.7-original", @@ -24520,7 +26723,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "zai-org/glm-5": { "id": "zai-org/glm-5", @@ -24539,7 +26747,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "zai-org/glm-5-original": { "id": "zai-org/glm-5-original", @@ -24558,7 +26771,12 @@ "cacheWrite": 0 }, "contextWindow": 222222, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } } }, "nvidia": { @@ -24598,7 +26816,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 4096 + "maxTokens": 4096, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek-ai/deepseek-v3.1": { "id": "deepseek-ai/deepseek-v3.1", @@ -24617,7 +26840,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek-ai/deepseek-v3.1-terminus": { "id": "deepseek-ai/deepseek-v3.1-terminus", @@ -24636,7 +26864,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek-ai/deepseek-v3.2": { "id": "deepseek-ai/deepseek-v3.2", @@ -24655,7 +26888,12 @@ "cacheWrite": 0 }, "contextWindow": 163840, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemma-2-27b-it": { "id": "google/gemma-2-27b-it", @@ -24752,7 +26990,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemma-3n-e2b-it": { "id": "google/gemma-3n-e2b-it", @@ -25125,7 +27368,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "minimaxai/minimax-m2": { "id": "minimaxai/minimax-m2", @@ -25144,7 +27392,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "minimaxai/minimax-m2.1": { "id": "minimaxai/minimax-m2.1", @@ -25163,7 +27416,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "mistralai/codestral-22b-instruct-v0.1": { "id": "mistralai/codestral-22b-instruct-v0.1", @@ -25201,7 +27459,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "mistralai/ministral-14b-instruct-2512": { "id": "mistralai/ministral-14b-instruct-2512", @@ -25298,7 +27561,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "moonshotai/kimi-k2-instruct-0905": { "id": "moonshotai/kimi-k2-instruct-0905", @@ -25336,7 +27604,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "moonshotai/kimi-k2.5": { "id": "moonshotai/kimi-k2.5", @@ -25356,7 +27629,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "nvidia/llama-3.1-nemotron-51b-instruct": { "id": "nvidia/llama-3.1-nemotron-51b-instruct", @@ -25413,7 +27691,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "nvidia/llama3-chatqa-1.5-70b": { "id": "nvidia/llama3-chatqa-1.5-70b", @@ -25470,7 +27753,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "nvidia/nemotron-4-340b-instruct": { "id": "nvidia/nemotron-4-340b-instruct", @@ -25508,7 +27796,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen2.5-coder-32b-instruct": { "id": "qwen/qwen2.5-coder-32b-instruct", @@ -25565,7 +27858,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-coder-480b-a35b-instruct": { "id": "qwen/qwen3-coder-480b-a35b-instruct", @@ -25622,7 +27920,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm4.7": { "id": "z-ai/glm4.7", @@ -25641,7 +27944,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm5": { "id": "z-ai/glm5", @@ -25660,7 +27968,12 @@ "cacheWrite": 0 }, "contextWindow": 202752, - "maxTokens": 131000 + "maxTokens": 131000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } } }, "openai": { @@ -25681,7 +27994,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-4": { "id": "gpt-4", @@ -25900,7 +28218,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5-chat-latest": { "id": "gpt-5-chat-latest", @@ -25940,7 +28263,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5-mini": { "id": "gpt-5-mini", @@ -25960,7 +28288,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5-nano": { "id": "gpt-5-nano", @@ -25980,7 +28313,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5-pro": { "id": "gpt-5-pro", @@ -26000,7 +28338,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 272000 + "maxTokens": 272000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1": { "id": "gpt-5.1", @@ -26020,7 +28363,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1-chat-latest": { "id": "gpt-5.1-chat-latest", @@ -26040,7 +28388,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1-codex": { "id": "gpt-5.1-codex", @@ -26060,7 +28413,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1-codex-max": { "id": "gpt-5.1-codex-max", @@ -26080,7 +28438,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1-codex-mini": { "id": "gpt-5.1-codex-mini", @@ -26100,7 +28463,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "medium", + "maxLevel": "high" + } }, "gpt-5.2": { "id": "gpt-5.2", @@ -26120,7 +28488,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.2-chat-latest": { "id": "gpt-5.2-chat-latest", @@ -26140,7 +28513,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.2-codex": { "id": "gpt-5.2-codex", @@ -26160,7 +28538,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.2-pro": { "id": "gpt-5.2-pro", @@ -26180,7 +28563,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.3-codex": { "id": "gpt-5.3-codex", @@ -26200,7 +28588,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.3-codex-spark": { "id": "gpt-5.3-codex-spark", @@ -26221,8 +28614,63 @@ }, "contextWindow": 128000, "maxTokens": 32000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + }, "contextPromotionTarget": "openai/gpt-5.3-codex" }, + "gpt-5.4": { + "id": "gpt-5.4", + "name": "GPT-5.4", + "api": "openai-responses", + "provider": "openai", + "baseUrl": "https://api.openai.com/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 2.5, + "output": 15, + "cacheRead": 0.25, + "cacheWrite": 0 + }, + "contextWindow": 1050000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } + }, + "gpt-5.4-pro": { + "id": "gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "api": "openai-responses", + "provider": "openai", + "baseUrl": "https://api.openai.com/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 30, + "output": 180, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 1050000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } + }, "o1": { "id": "o1", "name": "o1", @@ -26241,7 +28689,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "o1-pro": { "id": "o1-pro", @@ -26261,7 +28714,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "o3": { "id": "o3", @@ -26281,7 +28739,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "o3-deep-research": { "id": "o3-deep-research", @@ -26301,7 +28764,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "o3-mini": { "id": "o3-mini", @@ -26320,7 +28788,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "o3-pro": { "id": "o3-pro", @@ -26340,7 +28813,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "o4-mini": { "id": "o4-mini", @@ -26360,7 +28838,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "o4-mini-deep-research": { "id": "o4-mini-deep-research", @@ -26380,7 +28863,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } } }, "openai-codex": { @@ -26404,7 +28892,12 @@ "contextWindow": 400000, "maxTokens": 128000, "preferWebsockets": true, - "priority": 11 + "priority": 11, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5-codex": { "id": "gpt-5-codex", @@ -26423,10 +28916,15 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, + "contextWindow": 400000, "maxTokens": 128000, "preferWebsockets": true, - "priority": 10 + "priority": 10, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5-codex-mini": { "id": "gpt-5-codex-mini", @@ -26448,7 +28946,12 @@ "contextWindow": 272000, "maxTokens": 128000, "preferWebsockets": true, - "priority": 13 + "priority": 13, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1": { "id": "gpt-5.1", @@ -26470,7 +28973,12 @@ "contextWindow": 400000, "maxTokens": 128000, "preferWebsockets": true, - "priority": 7 + "priority": 7, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1-codex": { "id": "gpt-5.1-codex", @@ -26489,10 +28997,15 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, + "contextWindow": 400000, "maxTokens": 128000, "preferWebsockets": true, - "priority": 5 + "priority": 5, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1-codex-max": { "id": "gpt-5.1-codex-max", @@ -26511,10 +29024,15 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, + "contextWindow": 400000, "maxTokens": 128000, "preferWebsockets": true, - "priority": 4 + "priority": 4, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1-codex-mini": { "id": "gpt-5.1-codex-mini", @@ -26533,10 +29051,15 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, + "contextWindow": 400000, "maxTokens": 128000, "preferWebsockets": true, - "priority": 12 + "priority": 12, + "thinking": { + "mode": "effort", + "minLevel": "medium", + "maxLevel": "high" + } }, "gpt-5.2": { "id": "gpt-5.2", @@ -26558,7 +29081,12 @@ "contextWindow": 400000, "maxTokens": 128000, "preferWebsockets": true, - "priority": 6 + "priority": 6, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.2-codex": { "id": "gpt-5.2-codex", @@ -26577,10 +29105,15 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, + "contextWindow": 400000, "maxTokens": 128000, "preferWebsockets": true, - "priority": 3 + "priority": 3, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.3-codex": { "id": "gpt-5.3-codex", @@ -26599,9 +29132,15 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 272000, + "contextWindow": 400000, "maxTokens": 128000, - "priority": 0 + "preferWebsockets": true, + "priority": 0, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.3-codex-spark": { "id": "gpt-5.3-codex-spark", @@ -26612,8 +29151,7 @@ "reasoning": true, "preferWebsockets": true, "input": [ - "text", - "image" + "text" ], "cost": { "input": 1.75, @@ -26622,8 +29160,265 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 32000, - "contextPromotionTarget": "openai-codex/gpt-5.3-codex" + "maxTokens": 128000, + "contextPromotionTarget": "openai-codex/gpt-5.3-codex", + "thinking": { + "mode": "effort", + "levels": [ + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "gpt-5.4": { + "id": "gpt-5.4", + "name": "GPT-5.4", + "api": "openai-codex-responses", + "provider": "openai-codex", + "baseUrl": "https://chatgpt.com/backend-api", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 0, + "output": 0, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 1050000, + "maxTokens": 128000, + "preferWebsockets": true, + "priority": 0, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } + } + }, + "opencode": { + "glm-5-free": { + "id": "glm-5-free", + "name": "GLM-5 Free", + "api": "openai-completions", + "provider": "opencode", + "baseUrl": "https://opencode.ai/zen/v1", + "reasoning": true, + "input": [ + "text" + ], + "cost": { + "input": 0, + "output": 0, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 204800, + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } + }, + "gpt-5.3-codex-spark": { + "id": "gpt-5.3-codex-spark", + "name": "GPT-5.3 Codex Spark", + "api": "openai-responses", + "provider": "opencode", + "baseUrl": "https://opencode.ai/zen/v1", + "reasoning": true, + "input": [ + "text" + ], + "cost": { + "input": 1.75, + "output": 14, + "cacheRead": 0.175, + "cacheWrite": 0 + }, + "contextWindow": 128000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "levels": [ + "low", + "medium", + "high", + "xhigh" + ] + }, + "contextPromotionTarget": "opencode/gpt-5.3-codex" + }, + "gpt-5.4": { + "id": "gpt-5.4", + "name": "GPT-5.4", + "api": "openai-responses", + "provider": "opencode", + "baseUrl": "https://opencode.ai/zen/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 2.5, + "output": 15, + "cacheRead": 0.25, + "cacheWrite": 0 + }, + "contextWindow": 1050000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "levels": [ + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "gpt-5.4-pro": { + "id": "gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "api": "openai-responses", + "provider": "opencode", + "baseUrl": "https://opencode.ai/zen/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 30, + "output": 180, + "cacheRead": 30, + "cacheWrite": 0 + }, + "contextWindow": 1050000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "levels": [ + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "kimi-k2.5-free": { + "id": "kimi-k2.5-free", + "name": "Kimi K2.5 Free", + "api": "openai-completions", + "provider": "opencode", + "baseUrl": "https://opencode.ai/zen/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 0, + "output": 0, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 262144, + "maxTokens": 262144, + "thinking": { + "mode": "effort", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } + } + }, + "opencode-go": { + "glm-5": { + "id": "glm-5", + "name": "GLM-5", + "api": "openai-completions", + "provider": "opencode-go", + "baseUrl": "https://opencode.ai/zen/go/v1", + "reasoning": true, + "input": [ + "text" + ], + "cost": { + "input": 1, + "output": 3.2, + "cacheRead": 0.2, + "cacheWrite": 0 + }, + "contextWindow": 204800, + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } + }, + "kimi-k2.5": { + "id": "kimi-k2.5", + "name": "Kimi K2.5", + "api": "openai-completions", + "provider": "opencode-go", + "baseUrl": "https://opencode.ai/zen/go/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 0.6, + "output": 3, + "cacheRead": 0.1, + "cacheWrite": 0 + }, + "contextWindow": 262144, + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } + }, + "minimax-m2.5": { + "id": "minimax-m2.5", + "name": "MiniMax M2.5", + "api": "anthropic-messages", + "provider": "opencode-go", + "baseUrl": "https://opencode.ai/zen/go", + "reasoning": true, + "input": [ + "text" + ], + "cost": { + "input": 0.3, + "output": 1.2, + "cacheRead": 0.03, + "cacheWrite": 0 + }, + "contextWindow": 204800, + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } } }, "opencode-zen": { @@ -26644,7 +29439,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-3-5-haiku": { "id": "claude-3-5-haiku", @@ -26684,7 +29484,12 @@ "cacheWrite": 1.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-opus-4-1": { "id": "claude-opus-4-1", @@ -26704,7 +29509,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-opus-4-5": { "id": "claude-opus-4-5", @@ -26724,7 +29534,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-budget-effort", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-opus-4-6": { "id": "claude-opus-4-6", @@ -26744,7 +29559,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "anthropic-adaptive", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-sonnet-4": { "id": "claude-sonnet-4", @@ -26764,7 +29584,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-sonnet-4-5": { "id": "claude-sonnet-4-5", @@ -26784,7 +29609,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-budget-effort", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "claude-sonnet-4-6": { "id": "claude-sonnet-4-6", @@ -26804,7 +29634,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-adaptive", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-3-flash": { "id": "gemini-3-flash", @@ -26824,7 +29659,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "google-level", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gemini-3-pro": { "id": "gemini-3-pro", @@ -26844,7 +29684,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "google-level", + "minLevel": "low", + "maxLevel": "high" + } }, "gemini-3.1-pro": { "id": "gemini-3.1-pro", @@ -26864,7 +29709,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "google-level", + "minLevel": "low", + "maxLevel": "high" + } }, "glm-4.6": { "id": "glm-4.6", @@ -26883,7 +29733,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "glm-4.7": { "id": "glm-4.7", @@ -26902,7 +29757,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "glm-5": { "id": "glm-5", @@ -26921,7 +29781,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5": { "id": "gpt-5", @@ -26941,7 +29806,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5-codex": { "id": "gpt-5-codex", @@ -26961,7 +29831,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5-nano": { "id": "gpt-5-nano", @@ -26981,7 +29856,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1": { "id": "gpt-5.1", @@ -27001,7 +29881,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1-codex": { "id": "gpt-5.1-codex", @@ -27021,7 +29906,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1-codex-max": { "id": "gpt-5.1-codex-max", @@ -27041,7 +29931,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "gpt-5.1-codex-mini": { "id": "gpt-5.1-codex-mini", @@ -27061,7 +29956,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "medium", + "maxLevel": "high" + } }, "gpt-5.2": { "id": "gpt-5.2", @@ -27081,7 +29981,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.2-codex": { "id": "gpt-5.2-codex", @@ -27101,7 +30006,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "gpt-5.3-codex": { "id": "gpt-5.3-codex", @@ -27121,7 +30031,87 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } + }, + "gpt-5.3-codex-spark": { + "id": "gpt-5.3-codex-spark", + "name": "GPT-5.3 Codex Spark", + "api": "openai-responses", + "provider": "opencode-zen", + "baseUrl": "https://opencode.ai/zen/v1", + "reasoning": true, + "input": [ + "text" + ], + "cost": { + "input": 1.75, + "output": 14, + "cacheRead": 0.175, + "cacheWrite": 0 + }, + "contextWindow": 128000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + }, + "contextPromotionTarget": "opencode-zen/gpt-5.3-codex" + }, + "gpt-5.4": { + "id": "gpt-5.4", + "name": "GPT-5.4", + "api": "openai-responses", + "provider": "opencode-zen", + "baseUrl": "https://opencode.ai/zen/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 2.5, + "output": 15, + "cacheRead": 0.25, + "cacheWrite": 0 + }, + "contextWindow": 1050000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } + }, + "gpt-5.4-pro": { + "id": "gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "api": "openai-responses", + "provider": "opencode-zen", + "baseUrl": "https://opencode.ai/zen/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 30, + "output": 180, + "cacheRead": 30, + "cacheWrite": 0 + }, + "contextWindow": 1050000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "kimi-k2": { "id": "kimi-k2", @@ -27159,7 +30149,16 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "thinking": { + "mode": "effort", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "kimi-k2.5": { "id": "kimi-k2.5", @@ -27179,7 +30178,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "minimax-m2.1": { "id": "minimax-m2.1", @@ -27198,7 +30202,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "minimax-m2.5": { "id": "minimax-m2.5", @@ -27217,7 +30226,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "minimax-m2.5-free": { "id": "minimax-m2.5-free", @@ -27236,7 +30250,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "trinity-large-preview-free": { "id": "trinity-large-preview-free", @@ -27258,66 +30277,6 @@ "maxTokens": 131072 } }, - "opencode-go": { - "glm-5": { - "id": "glm-5", - "name": "GLM-5", - "api": "openai-completions", - "provider": "opencode-go", - "baseUrl": "https://opencode.ai/zen/go/v1", - "reasoning": true, - "input": [ - "text" - ], - "cost": { - "input": 1, - "output": 3.2, - "cacheRead": 0.2, - "cacheWrite": 0 - }, - "contextWindow": 204800, - "maxTokens": 131072 - }, - "kimi-k2.5": { - "id": "kimi-k2.5", - "name": "Kimi K2.5", - "api": "openai-completions", - "provider": "opencode-go", - "baseUrl": "https://opencode.ai/zen/go/v1", - "reasoning": true, - "input": [ - "text", - "image" - ], - "cost": { - "input": 0.6, - "output": 3, - "cacheRead": 0.1, - "cacheWrite": 0 - }, - "contextWindow": 262144, - "maxTokens": 65536 - }, - "minimax-m2.5": { - "id": "minimax-m2.5", - "name": "MiniMax M2.5", - "api": "anthropic-messages", - "provider": "opencode-go", - "baseUrl": "https://opencode.ai/zen/go", - "reasoning": true, - "input": [ - "text" - ], - "cost": { - "input": 0.3, - "output": 1.2, - "cacheRead": 0.03, - "cacheWrite": 0 - }, - "contextWindow": 204800, - "maxTokens": 131072 - } - }, "openrouter": { "ai21/jamba-large-1.7": { "id": "ai21/jamba-large-1.7", @@ -27355,7 +30314,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "allenai/olmo-3.1-32b-instruct": { "id": "allenai/olmo-3.1-32b-instruct", @@ -27394,7 +30358,12 @@ "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 65535 + "maxTokens": 65535, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "amazon/nova-lite-v1": { "id": "amazon/nova-lite-v1", @@ -27565,7 +30534,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "anthropic/claude-3.7-sonnet:thinking": { "id": "anthropic/claude-3.7-sonnet:thinking", @@ -27585,7 +30559,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "anthropic/claude-haiku-4.5": { "id": "anthropic/claude-haiku-4.5", @@ -27625,7 +30604,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "anthropic/claude-opus-4.1": { "id": "anthropic/claude-opus-4.1", @@ -27645,7 +30629,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "anthropic/claude-opus-4.5": { "id": "anthropic/claude-opus-4.5", @@ -27665,7 +30654,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "anthropic/claude-opus-4.6": { "id": "anthropic/claude-opus-4.6", @@ -27684,8 +30678,13 @@ "cacheRead": 0.5, "cacheWrite": 6.25 }, - "contextWindow": 200000, - "maxTokens": 128000 + "contextWindow": 1000000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "anthropic/claude-sonnet-4": { "id": "anthropic/claude-sonnet-4", @@ -27705,7 +30704,12 @@ "cacheWrite": 3.75 }, "contextWindow": 1000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "anthropic/claude-sonnet-4.5": { "id": "anthropic/claude-sonnet-4.5", @@ -27725,7 +30729,12 @@ "cacheWrite": 3.75 }, "contextWindow": 1000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "anthropic/claude-sonnet-4.6": { "id": "anthropic/claude-sonnet-4.6", @@ -27744,8 +30753,13 @@ "cacheRead": 0.3, "cacheWrite": 3.75 }, - "contextWindow": 200000, - "maxTokens": 64000 + "contextWindow": 1000000, + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "arcee-ai/trinity-large-preview:free": { "id": "arcee-ai/trinity-large-preview:free", @@ -27786,7 +30800,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "arcee-ai/trinity-mini:free": { "id": "arcee-ai/trinity-mini:free", @@ -27805,7 +30824,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "arcee-ai/virtuoso-large": { "id": "arcee-ai/virtuoso-large", @@ -27844,7 +30868,12 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 30000 + "maxTokens": 30000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "baidu/ernie-4.5-21b-a3b": { "id": "baidu/ernie-4.5-21b-a3b", @@ -27883,7 +30912,12 @@ "cacheWrite": 0 }, "contextWindow": 30000, - "maxTokens": 8000 + "maxTokens": 8000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "bytedance-seed/seed-1.6": { "id": "bytedance-seed/seed-1.6", @@ -27903,7 +30937,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 32768 + "maxTokens": 32768, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "bytedance-seed/seed-1.6-flash": { "id": "bytedance-seed/seed-1.6-flash", @@ -27923,7 +30962,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 32768 + "maxTokens": 32768, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "bytedance-seed/seed-2.0-mini": { "id": "bytedance-seed/seed-2.0-mini", @@ -27943,7 +30987,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "cohere/command-r-08-2024": { "id": "cohere/command-r-08-2024", @@ -28015,11 +31064,16 @@ "cost": { "input": 0.19999999999999998, "output": 0.77, - "cacheRead": 0.135, + "cacheRead": 0.13, "cacheWrite": 0 }, "contextWindow": 163840, - "maxTokens": 8888 + "maxTokens": 163840, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek/deepseek-chat-v3.1": { "id": "deepseek/deepseek-chat-v3.1", @@ -28038,7 +31092,12 @@ "cacheWrite": 0 }, "contextWindow": 32768, - "maxTokens": 7168 + "maxTokens": 7168, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek/deepseek-r1": { "id": "deepseek/deepseek-r1", @@ -28057,7 +31116,12 @@ "cacheWrite": 0 }, "contextWindow": 64000, - "maxTokens": 16000 + "maxTokens": 16000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek/deepseek-r1-0528": { "id": "deepseek/deepseek-r1-0528", @@ -28076,7 +31140,12 @@ "cacheWrite": 0 }, "contextWindow": 163840, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek/deepseek-v3.1-terminus": { "id": "deepseek/deepseek-v3.1-terminus", @@ -28095,7 +31164,12 @@ "cacheWrite": 0 }, "contextWindow": 163840, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek/deepseek-v3.1-terminus:exacto": { "id": "deepseek/deepseek-v3.1-terminus:exacto", @@ -28114,7 +31188,12 @@ "cacheWrite": 0 }, "contextWindow": 163840, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek/deepseek-v3.2": { "id": "deepseek/deepseek-v3.2", @@ -28133,7 +31212,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek/deepseek-v3.2-exp": { "id": "deepseek/deepseek-v3.2-exp", @@ -28152,7 +31236,12 @@ "cacheWrite": 0 }, "contextWindow": 163000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "essentialai/rnj-1-instruct": { "id": "essentialai/rnj-1-instruct", @@ -28231,7 +31320,12 @@ "cacheWrite": 0.08333333333333334 }, "contextWindow": 1048000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-2.5-flash-lite": { "id": "google/gemini-2.5-flash-lite", @@ -28271,7 +31365,12 @@ "cacheWrite": 0.08333333333333334 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-2.5-flash-preview-09-2025": { "id": "google/gemini-2.5-flash-preview-09-2025", @@ -28291,7 +31390,12 @@ "cacheWrite": 0.08333333333333334 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-2.5-pro": { "id": "google/gemini-2.5-pro", @@ -28311,7 +31415,12 @@ "cacheWrite": 0.375 }, "contextWindow": 1048000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-2.5-pro-preview": { "id": "google/gemini-2.5-pro-preview", @@ -28331,7 +31440,12 @@ "cacheWrite": 0.375 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-2.5-pro-preview-05-06": { "id": "google/gemini-2.5-pro-preview-05-06", @@ -28351,7 +31465,12 @@ "cacheWrite": 0.375 }, "contextWindow": 1048576, - "maxTokens": 65535 + "maxTokens": 65535, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-3-flash-preview": { "id": "google/gemini-3-flash-preview", @@ -28371,7 +31490,12 @@ "cacheWrite": 0.08333333333333334 }, "contextWindow": 1048000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-3-pro-preview": { "id": "google/gemini-3-pro-preview", @@ -28391,7 +31515,12 @@ "cacheWrite": 0.375 }, "contextWindow": 1048000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "high" + } }, "google/gemini-3.1-flash-lite-preview": { "id": "google/gemini-3.1-flash-lite-preview", @@ -28411,7 +31540,12 @@ "cacheWrite": 0.08333333333333334 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-3.1-pro-preview": { "id": "google/gemini-3.1-pro-preview", @@ -28431,7 +31565,12 @@ "cacheWrite": 0.375 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "high" + } }, "google/gemini-3.1-pro-preview-customtools": { "id": "google/gemini-3.1-pro-preview-customtools", @@ -28451,7 +31590,12 @@ "cacheWrite": 0.375 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "high" + } }, "google/gemma-3-27b-it": { "id": "google/gemma-3-27b-it", @@ -28471,7 +31615,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemma-3-27b-it:free": { "id": "google/gemma-3-27b-it:free", @@ -28510,7 +31659,31 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 16384 + "maxTokens": 32000 + }, + "inception/mercury-2": { + "id": "inception/mercury-2", + "name": "Inception: Mercury 2", + "api": "openai-completions", + "provider": "openrouter", + "baseUrl": "https://openrouter.ai/api/v1", + "reasoning": true, + "input": [ + "text" + ], + "cost": { + "input": 0.25, + "output": 0.75, + "cacheRead": 0.024999999999999998, + "cacheWrite": 0 + }, + "contextWindow": 128000, + "maxTokens": 50000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "inception/mercury-coder": { "id": "inception/mercury-coder", @@ -28529,7 +31702,7 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 16384 + "maxTokens": 32000 }, "kwaipilot/kat-coder-pro": { "id": "kwaipilot/kat-coder-pro", @@ -28740,7 +31913,12 @@ "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 40000 + "maxTokens": 40000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "minimax/minimax-m2": { "id": "minimax/minimax-m2", @@ -28759,7 +31937,12 @@ "cacheWrite": 0 }, "contextWindow": 204000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "minimax/minimax-m2.1": { "id": "minimax/minimax-m2.1", @@ -28778,7 +31961,12 @@ "cacheWrite": 0 }, "contextWindow": 204000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "minimax/minimax-m2.5": { "id": "minimax/minimax-m2.5", @@ -28797,7 +31985,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "mistralai/codestral-2508": { "id": "mistralai/codestral-2508", @@ -29339,7 +32532,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "moonshotai/kimi-k2.5": { "id": "moonshotai/kimi-k2.5", @@ -29359,7 +32557,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "nex-agi/deepseek-v3.1-nex-n1": { "id": "nex-agi/deepseek-v3.1-nex-n1", @@ -29397,7 +32600,12 @@ "cacheWrite": 0 }, "contextWindow": 32768, - "maxTokens": 32768 + "maxTokens": 32768, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "nousresearch/hermes-4-70b": { "id": "nousresearch/hermes-4-70b", @@ -29416,7 +32624,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "nvidia/llama-3.1-nemotron-70b-instruct": { "id": "nvidia/llama-3.1-nemotron-70b-instruct", @@ -29454,7 +32667,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "nvidia/nemotron-3-nano-30b-a3b": { "id": "nvidia/nemotron-3-nano-30b-a3b", @@ -29473,7 +32691,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "nvidia/nemotron-3-nano-30b-a3b:free": { "id": "nvidia/nemotron-3-nano-30b-a3b:free", @@ -29492,7 +32715,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "nvidia/nemotron-nano-12b-v2-vl:free": { "id": "nvidia/nemotron-nano-12b-v2-vl:free", @@ -29512,7 +32740,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "nvidia/nemotron-nano-9b-v2": { "id": "nvidia/nemotron-nano-9b-v2", @@ -29531,7 +32764,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "nvidia/nemotron-nano-9b-v2:free": { "id": "nvidia/nemotron-nano-9b-v2:free", @@ -29550,7 +32788,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-3.5-turbo": { "id": "openai/gpt-3.5-turbo", @@ -29942,7 +33185,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-codex": { "id": "openai/gpt-5-codex", @@ -29961,8 +33209,13 @@ "cacheRead": 0.125, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 64000 + "contextWindow": 400000, + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-image": { "id": "openai/gpt-5-image", @@ -29982,7 +33235,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-image-mini": { "id": "openai/gpt-5-image-mini", @@ -30002,7 +33260,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-mini": { "id": "openai/gpt-5-mini", @@ -30022,7 +33285,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-nano": { "id": "openai/gpt-5-nano", @@ -30042,7 +33310,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-pro": { "id": "openai/gpt-5-pro", @@ -30062,7 +33335,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1": { "id": "openai/gpt-5.1", @@ -30082,7 +33360,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1-chat": { "id": "openai/gpt-5.1-chat", @@ -30121,8 +33404,13 @@ "cacheRead": 0.125, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1-codex-max": { "id": "openai/gpt-5.1-codex-max", @@ -30142,7 +33430,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1-codex-mini": { "id": "openai/gpt-5.1-codex-mini", @@ -30161,8 +33454,13 @@ "cacheRead": 0.024999999999999998, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 64000 + "contextWindow": 400000, + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "medium", + "maxLevel": "high" + } }, "openai/gpt-5.2": { "id": "openai/gpt-5.2", @@ -30182,7 +33480,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.2-chat": { "id": "openai/gpt-5.2-chat", @@ -30221,8 +33524,13 @@ "cacheRead": 0.175, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.2-pro": { "id": "openai/gpt-5.2-pro", @@ -30242,7 +33550,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.3-chat": { "id": "openai/gpt-5.3-chat", @@ -30281,8 +33594,63 @@ "cacheRead": 0.175, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } + }, + "openai/gpt-5.4": { + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "api": "openai-completions", + "provider": "openrouter", + "baseUrl": "https://openrouter.ai/api/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 2.5, + "output": 15, + "cacheRead": 0.25, + "cacheWrite": 0 + }, + "contextWindow": 1050000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } + }, + "openai/gpt-5.4-pro": { + "id": "openai/gpt-5.4-pro", + "name": "OpenAI: GPT-5.4 Pro", + "api": "openai-completions", + "provider": "openrouter", + "baseUrl": "https://openrouter.ai/api/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 30, + "output": 180, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 1050000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-oss-120b": { "id": "openai/gpt-oss-120b", @@ -30301,7 +33669,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-oss-120b:exacto": { "id": "openai/gpt-oss-120b:exacto", @@ -30320,7 +33693,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-oss-120b:free": { "id": "openai/gpt-oss-120b:free", @@ -30339,7 +33717,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-oss-20b": { "id": "openai/gpt-oss-20b", @@ -30358,7 +33741,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-oss-20b:free": { "id": "openai/gpt-oss-20b:free", @@ -30377,7 +33765,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-oss-safeguard-20b": { "id": "openai/gpt-oss-safeguard-20b", @@ -30396,7 +33789,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o1": { "id": "openai/o1", @@ -30416,7 +33814,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o3": { "id": "openai/o3", @@ -30436,7 +33839,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o3-deep-research": { "id": "openai/o3-deep-research", @@ -30456,7 +33864,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o3-mini": { "id": "openai/o3-mini", @@ -30475,7 +33888,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o3-mini-high": { "id": "openai/o3-mini-high", @@ -30514,7 +33932,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o4-mini": { "id": "openai/o4-mini", @@ -30534,7 +33957,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o4-mini-deep-research": { "id": "openai/o4-mini-deep-research", @@ -30554,7 +33982,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/o4-mini-high": { "id": "openai/o4-mini-high", @@ -30574,7 +34007,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openrouter/aurora-alpha": { "id": "openrouter/aurora-alpha", @@ -30593,7 +34031,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 50000 + "maxTokens": 50000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openrouter/auto": { "id": "openrouter/auto", @@ -30613,7 +34056,12 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openrouter/free": { "id": "openrouter/free", @@ -30633,7 +34081,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "prime-intellect/intellect-3": { "id": "prime-intellect/intellect-3", @@ -30652,7 +34105,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen-2.5-72b-instruct": { "id": "qwen/qwen-2.5-72b-instruct", @@ -30766,7 +34224,12 @@ "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 32768 + "maxTokens": 32768, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen-turbo": { "id": "qwen/qwen-turbo", @@ -30824,7 +34287,12 @@ "cacheWrite": 0 }, "contextWindow": 40960, - "maxTokens": 40960 + "maxTokens": 40960, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-235b-a22b": { "id": "qwen/qwen3-235b-a22b", @@ -30843,7 +34311,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-235b-a22b-2507": { "id": "qwen/qwen3-235b-a22b-2507", @@ -30862,7 +34335,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-235b-a22b-thinking-2507": { "id": "qwen/qwen3-235b-a22b-thinking-2507", @@ -30881,7 +34359,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-30b-a3b": { "id": "qwen/qwen3-30b-a3b", @@ -30900,7 +34383,12 @@ "cacheWrite": 0 }, "contextWindow": 40960, - "maxTokens": 40960 + "maxTokens": 40960, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-30b-a3b-instruct-2507": { "id": "qwen/qwen3-30b-a3b-instruct-2507", @@ -30938,7 +34426,12 @@ "cacheWrite": 0 }, "contextWindow": 32768, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-32b": { "id": "qwen/qwen3-32b", @@ -30957,7 +34450,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-4b": { "id": "qwen/qwen3-4b", @@ -30976,7 +34474,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-4b:free": { "id": "qwen/qwen3-4b:free", @@ -30995,7 +34498,12 @@ "cacheWrite": 0 }, "contextWindow": 40960, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-8b": { "id": "qwen/qwen3-8b", @@ -31014,7 +34522,12 @@ "cacheWrite": 0 }, "contextWindow": 40960, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-coder": { "id": "qwen/qwen3-coder", @@ -31166,7 +34679,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-max-thinking": { "id": "qwen/qwen3-max-thinking", @@ -31185,7 +34703,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 32768 + "maxTokens": 32768, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-next-80b-a3b-instruct": { "id": "qwen/qwen3-next-80b-a3b-instruct", @@ -31242,7 +34765,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-vl-235b-a22b-instruct": { "id": "qwen/qwen3-vl-235b-a22b-instruct", @@ -31282,7 +34810,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 32768 + "maxTokens": 32768, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-vl-30b-a3b-instruct": { "id": "qwen/qwen3-vl-30b-a3b-instruct", @@ -31322,7 +34855,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 32768 + "maxTokens": 32768, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-vl-32b-instruct": { "id": "qwen/qwen3-vl-32b-instruct", @@ -31382,7 +34920,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 32768 + "maxTokens": 32768, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3.5-122b-a10b": { "id": "qwen/qwen3.5-122b-a10b", @@ -31402,7 +34945,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3.5-27b": { "id": "qwen/qwen3.5-27b", @@ -31422,7 +34970,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3.5-35b-a3b": { "id": "qwen/qwen3.5-35b-a3b", @@ -31442,7 +34995,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3.5-397b-a17b": { "id": "qwen/qwen3.5-397b-a17b", @@ -31462,7 +35020,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3.5-flash-02-23": { "id": "qwen/qwen3.5-flash-02-23", @@ -31482,7 +35045,12 @@ "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3.5-plus-02-15": { "id": "qwen/qwen3.5-plus-02-15", @@ -31502,7 +35070,12 @@ "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwq-32b": { "id": "qwen/qwq-32b", @@ -31521,7 +35094,12 @@ "cacheWrite": 0 }, "contextWindow": 32768, - "maxTokens": 32768 + "maxTokens": 32768, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "relace/relace-search": { "id": "relace/relace-search", @@ -31622,6 +35200,11 @@ "maxTokens": 256000, "compat": { "supportsToolChoice": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "thedrummer/rocinante-12b": { @@ -31679,7 +35262,12 @@ "cacheWrite": 0 }, "contextWindow": 163840, - "maxTokens": 163840 + "maxTokens": 163840, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "tngtech/tng-r1t-chimera": { "id": "tngtech/tng-r1t-chimera", @@ -31698,7 +35286,12 @@ "cacheWrite": 0 }, "contextWindow": 163840, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "upstage/solar-pro-3": { "id": "upstage/solar-pro-3", @@ -31717,7 +35310,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "upstage/solar-pro-3:free": { "id": "upstage/solar-pro-3:free", @@ -31736,7 +35334,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "x-ai/grok-3": { "id": "x-ai/grok-3", @@ -31793,7 +35396,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "x-ai/grok-3-mini-beta": { "id": "x-ai/grok-3-mini-beta", @@ -31812,7 +35420,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "x-ai/grok-4": { "id": "x-ai/grok-4", @@ -31832,7 +35445,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "x-ai/grok-4-fast": { "id": "x-ai/grok-4-fast", @@ -31852,7 +35470,12 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "x-ai/grok-4.1-fast": { "id": "x-ai/grok-4.1-fast", @@ -31872,7 +35495,12 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "x-ai/grok-code-fast-1": { "id": "x-ai/grok-code-fast-1", @@ -31891,7 +35519,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "xiaomi/mimo-v2-flash": { "id": "xiaomi/mimo-v2-flash", @@ -31910,7 +35543,12 @@ "cacheWrite": 0 }, "contextWindow": 262000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4-32b": { "id": "z-ai/glm-4-32b", @@ -31942,13 +35580,18 @@ "text" ], "cost": { - "input": 0.55, - "output": 2, - "cacheRead": 0, + "input": 0.6, + "output": 2.2, + "cacheRead": 0.11, "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.5-air": { "id": "z-ai/glm-4.5-air", @@ -31967,7 +35610,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.5-air:free": { "id": "z-ai/glm-4.5-air:free", @@ -31986,7 +35634,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 96000 + "maxTokens": 96000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.5v": { "id": "z-ai/glm-4.5v", @@ -32006,7 +35659,12 @@ "cacheWrite": 0 }, "contextWindow": 65536, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.6": { "id": "z-ai/glm-4.6", @@ -32025,7 +35683,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.6:exacto": { "id": "z-ai/glm-4.6:exacto", @@ -32044,7 +35707,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.6v": { "id": "z-ai/glm-4.6v", @@ -32064,7 +35732,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.7": { "id": "z-ai/glm-4.7", @@ -32077,13 +35750,18 @@ "text" ], "cost": { - "input": 0.3, - "output": 1.4, - "cacheRead": 0.15, + "input": 0.38, + "output": 1.9800000000000002, + "cacheRead": 0.19, "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.7-flash": { "id": "z-ai/glm-4.7-flash", @@ -32102,7 +35780,12 @@ "cacheWrite": 0 }, "contextWindow": 202752, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-5": { "id": "z-ai/glm-5", @@ -32121,7 +35804,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } } }, "qianfan": { @@ -32529,7 +36217,16 @@ "cacheWrite": 3 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "deepseek-ai/DeepSeek-V3.1": { "id": "deepseek-ai/DeepSeek-V3.1", @@ -32646,7 +36343,16 @@ "cacheWrite": 2.8 }, "contextWindow": 262144, - "maxTokens": 32768 + "maxTokens": 32768, + "thinking": { + "mode": "effort", + "levels": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "zai-org/GLM-4.7": { "id": "zai-org/GLM-4.7", @@ -32690,6 +36396,11 @@ "maxTokens": 64000, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "claude-opus-4-6": { @@ -32709,10 +36420,15 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 200000, + "contextWindow": 1000000, "maxTokens": 128000, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "claude-opus-45": { @@ -32736,11 +36452,16 @@ "maxTokens": 8192, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "claude-sonnet-4-5": { "id": "claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (latest)", + "name": "Claude Sonnet 4.5", "api": "openai-completions", "provider": "venice", "baseUrl": "https://api.venice.ai/api/v1", @@ -32755,10 +36476,15 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 200000, + "contextWindow": 1000000, "maxTokens": 64000, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "claude-sonnet-4-6": { @@ -32778,10 +36504,15 @@ "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 200000, + "contextWindow": 1000000, "maxTokens": 64000, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "claude-sonnet-45": { @@ -32805,6 +36536,11 @@ "maxTokens": 8192, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "deepseek-v3.2": { @@ -32827,6 +36563,11 @@ "maxTokens": 8192, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "gemini-3-1-pro-preview": { @@ -32872,6 +36613,11 @@ "maxTokens": 65536, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "gemini-3-pro-preview": { @@ -32895,6 +36641,11 @@ "maxTokens": 64000, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "high" } }, "google-gemma-3-27b-it": { @@ -32941,6 +36692,11 @@ "maxTokens": 8192, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "grok-code-fast-1": { @@ -32963,6 +36719,11 @@ "maxTokens": 10000, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "hermes-3-llama-3.1-405b": { @@ -33008,6 +36769,11 @@ "maxTokens": 8192, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "kimi-k2-thinking": { @@ -33030,6 +36796,11 @@ "maxTokens": 262144, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "llama-3.2-3b": { @@ -33096,6 +36867,11 @@ "maxTokens": 8192, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "minimax-m25": { @@ -33118,6 +36894,11 @@ "maxTokens": 8192, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "mistral-31-24b": { @@ -33185,6 +36966,11 @@ "maxTokens": 8192, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "openai-gpt-4o-2024-11-20": { @@ -33251,6 +37037,11 @@ "maxTokens": 8192, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "openai-gpt-52-codex": { @@ -33274,6 +37065,11 @@ "maxTokens": 8192, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "openai-gpt-53-codex": { @@ -33298,6 +37094,28 @@ "supportsUsageInStreaming": false } }, + "openai-gpt-54": { + "id": "openai-gpt-54", + "name": "openai-gpt-54", + "api": "openai-completions", + "provider": "venice", + "baseUrl": "https://api.venice.ai/api/v1", + "reasoning": false, + "input": [ + "text" + ], + "cost": { + "input": 0, + "output": 0, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 222222, + "maxTokens": 8888, + "compat": { + "supportsUsageInStreaming": false + } + }, "openai-gpt-oss-120b": { "id": "openai-gpt-oss-120b", "name": "OpenAI GPT OSS 120B", @@ -33362,6 +37180,11 @@ "maxTokens": 8192, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "qwen3-4b": { @@ -33384,6 +37207,11 @@ "maxTokens": 8192, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "qwen3-5-35b-a3b": { @@ -33561,6 +37389,11 @@ "maxTokens": 8192, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "zai-org-glm-4.7-flash": { @@ -33583,6 +37416,11 @@ "maxTokens": 8192, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } }, "zai-org-glm-5": { @@ -33605,6 +37443,11 @@ "maxTokens": 8192, "compat": { "supportsUsageInStreaming": false + }, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" } } }, @@ -33626,7 +37469,12 @@ "cacheWrite": 0 }, "contextWindow": 40960, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "alibaba/qwen-3-235b": { "id": "alibaba/qwen-3-235b", @@ -33664,7 +37512,12 @@ "cacheWrite": 0 }, "contextWindow": 40960, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "alibaba/qwen-3-32b": { "id": "alibaba/qwen-3-32b", @@ -33683,7 +37536,12 @@ "cacheWrite": 0 }, "contextWindow": 40960, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "alibaba/qwen3-235b-a22b-thinking": { "id": "alibaba/qwen3-235b-a22b-thinking", @@ -33703,7 +37561,12 @@ "cacheWrite": 0 }, "contextWindow": 262114, - "maxTokens": 262114 + "maxTokens": 262114, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "alibaba/qwen3-coder": { "id": "alibaba/qwen3-coder", @@ -33741,7 +37604,12 @@ "cacheWrite": 0 }, "contextWindow": 160000, - "maxTokens": 32768 + "maxTokens": 32768, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "alibaba/qwen3-coder-next": { "id": "alibaba/qwen3-coder-next", @@ -33760,7 +37628,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 256000 + "maxTokens": 256000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "alibaba/qwen3-coder-plus": { "id": "alibaba/qwen3-coder-plus", @@ -33817,7 +37690,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "alibaba/qwen3-vl-thinking": { "id": "alibaba/qwen3-vl-thinking", @@ -33837,7 +37715,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 256000 + "maxTokens": 256000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "alibaba/qwen3.5-plus": { "id": "alibaba/qwen3.5-plus", @@ -33857,7 +37740,12 @@ "cacheWrite": 0.5 }, "contextWindow": 1000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-3-haiku": { "id": "anthropic/claude-3-haiku", @@ -33957,7 +37845,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-haiku-4.5": { "id": "anthropic/claude-haiku-4.5", @@ -33997,7 +37890,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-opus-4.1": { "id": "anthropic/claude-opus-4.1", @@ -34017,7 +37915,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-opus-4.5": { "id": "anthropic/claude-opus-4.5", @@ -34037,7 +37940,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-budget-effort", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-opus-4.6": { "id": "anthropic/claude-opus-4.6", @@ -34056,8 +37964,13 @@ "cacheRead": 0.5, "cacheWrite": 6.25 }, - "contextWindow": 200000, - "maxTokens": 128000 + "contextWindow": 1000000, + "maxTokens": 128000, + "thinking": { + "mode": "anthropic-adaptive", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-sonnet-4": { "id": "anthropic/claude-sonnet-4", @@ -34077,7 +37990,12 @@ "cacheWrite": 3.75 }, "contextWindow": 1000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-sonnet-4.5": { "id": "anthropic/claude-sonnet-4.5", @@ -34097,7 +38015,12 @@ "cacheWrite": 3.75 }, "contextWindow": 1000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-budget-effort", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-sonnet-4.6": { "id": "anthropic/claude-sonnet-4.6", @@ -34116,8 +38039,13 @@ "cacheRead": 0.3, "cacheWrite": 3.75 }, - "contextWindow": 200000, - "maxTokens": 64000 + "contextWindow": 1000000, + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-adaptive", + "minLevel": "minimal", + "maxLevel": "high" + } }, "arcee-ai/trinity-large-preview": { "id": "arcee-ai/trinity-large-preview", @@ -34155,7 +38083,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "cohere/command-a": { "id": "cohere/command-a", @@ -34212,7 +38145,12 @@ "cacheWrite": 0 }, "contextWindow": 163840, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "deepseek/deepseek-v3.1-terminus": { "id": "deepseek/deepseek-v3.1-terminus", @@ -34231,7 +38169,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "deepseek/deepseek-v3.2": { "id": "deepseek/deepseek-v3.2", @@ -34250,7 +38193,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "deepseek/deepseek-v3.2-thinking": { "id": "deepseek/deepseek-v3.2-thinking", @@ -34269,7 +38217,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "google/gemini-2.5-flash": { "id": "google/gemini-2.5-flash", @@ -34289,7 +38242,12 @@ "cacheWrite": 0 }, "contextWindow": 1048000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-2.5-flash-lite": { "id": "google/gemini-2.5-flash-lite", @@ -34329,7 +38287,12 @@ "cacheWrite": 0 }, "contextWindow": 1048576, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-2.5-flash-preview-09-2025": { "id": "google/gemini-2.5-flash-preview-09-2025", @@ -34349,7 +38312,12 @@ "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-2.5-pro": { "id": "google/gemini-2.5-pro", @@ -34369,7 +38337,12 @@ "cacheWrite": 0 }, "contextWindow": 1048000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-3-flash": { "id": "google/gemini-3-flash", @@ -34389,7 +38362,12 @@ "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-3-pro-preview": { "id": "google/gemini-3-pro-preview", @@ -34409,7 +38387,12 @@ "cacheWrite": 0 }, "contextWindow": 1048000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "low", + "maxLevel": "high" + } }, "inception/mercury-coder-small": { "id": "inception/mercury-coder-small", @@ -34466,7 +38449,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "meta/llama-3.1-70b": { "id": "meta/llama-3.1-70b", @@ -34622,7 +38610,12 @@ "cacheWrite": 0.375 }, "contextWindow": 204000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "minimax/minimax-m2.1": { "id": "minimax/minimax-m2.1", @@ -34641,7 +38634,12 @@ "cacheWrite": 0 }, "contextWindow": 204000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "minimax/minimax-m2.1-lightning": { "id": "minimax/minimax-m2.1-lightning", @@ -34660,7 +38658,12 @@ "cacheWrite": 0.375 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "minimax/minimax-m2.5": { "id": "minimax/minimax-m2.5", @@ -34679,7 +38682,12 @@ "cacheWrite": 0.375 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "mistral/codestral": { "id": "mistral/codestral", @@ -34911,7 +38919,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "moonshotai/kimi-k2-thinking-turbo": { "id": "moonshotai/kimi-k2-thinking-turbo", @@ -34930,7 +38943,12 @@ "cacheWrite": 0 }, "contextWindow": 262000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "moonshotai/kimi-k2-turbo": { "id": "moonshotai/kimi-k2-turbo", @@ -34969,7 +38987,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 262144 + "maxTokens": 262144, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "nvidia/nemotron-nano-12b-v2-vl": { "id": "nvidia/nemotron-nano-12b-v2-vl", @@ -34989,7 +39012,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "nvidia/nemotron-nano-9b-v2": { "id": "nvidia/nemotron-nano-9b-v2", @@ -35008,7 +39036,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "openai/codex-mini": { "id": "openai/codex-mini", @@ -35028,7 +39061,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "openai/gpt-4-turbo": { "id": "openai/gpt-4-turbo", @@ -35168,7 +39206,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-chat": { "id": "openai/gpt-5-chat", @@ -35188,7 +39231,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-codex": { "id": "openai/gpt-5-codex", @@ -35207,8 +39255,13 @@ "cacheRead": 0.13, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 64000 + "contextWindow": 400000, + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-mini": { "id": "openai/gpt-5-mini", @@ -35228,7 +39281,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-nano": { "id": "openai/gpt-5-nano", @@ -35248,7 +39306,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-pro": { "id": "openai/gpt-5-pro", @@ -35268,7 +39331,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 272000 + "maxTokens": 272000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1-codex": { "id": "openai/gpt-5.1-codex", @@ -35287,8 +39355,13 @@ "cacheRead": 0.13, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1-codex-max": { "id": "openai/gpt-5.1-codex-max", @@ -35308,7 +39381,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1-codex-mini": { "id": "openai/gpt-5.1-codex-mini", @@ -35327,8 +39405,13 @@ "cacheRead": 0.024999999999999998, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 64000 + "contextWindow": 400000, + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "medium", + "maxLevel": "high" + } }, "openai/gpt-5.1-instant": { "id": "openai/gpt-5.1-instant", @@ -35348,7 +39431,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1-thinking": { "id": "openai/gpt-5.1-thinking", @@ -35368,7 +39456,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.2": { "id": "openai/gpt-5.2", @@ -35388,7 +39481,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.2-chat": { "id": "openai/gpt-5.2-chat", @@ -35408,7 +39506,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "budget", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.2-codex": { "id": "openai/gpt-5.2-codex", @@ -35427,8 +39530,13 @@ "cacheRead": 0.175, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.2-pro": { "id": "openai/gpt-5.2-pro", @@ -35448,7 +39556,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-oss-120b": { "id": "openai/gpt-oss-120b", @@ -35467,7 +39580,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "openai/gpt-oss-20b": { "id": "openai/gpt-oss-20b", @@ -35486,7 +39604,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "openai/gpt-oss-safeguard-20b": { "id": "openai/gpt-oss-safeguard-20b", @@ -35505,7 +39628,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 65536 + "maxTokens": 65536, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "openai/o1": { "id": "openai/o1", @@ -35525,7 +39653,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "openai/o3": { "id": "openai/o3", @@ -35545,7 +39678,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "openai/o3-deep-research": { "id": "openai/o3-deep-research", @@ -35565,7 +39703,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "openai/o3-mini": { "id": "openai/o3-mini", @@ -35584,7 +39727,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "openai/o3-pro": { "id": "openai/o3-pro", @@ -35604,7 +39752,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "openai/o4-mini": { "id": "openai/o4-mini", @@ -35624,7 +39777,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "perplexity/sonar": { "id": "perplexity/sonar", @@ -35683,7 +39841,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "vercel/v0-1.0-md": { "id": "vercel/v0-1.0-md", @@ -35839,7 +40002,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 256000 + "maxTokens": 256000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "xai/grok-4-fast-non-reasoning": { "id": "xai/grok-4-fast-non-reasoning", @@ -35877,7 +40045,12 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 256000 + "maxTokens": 256000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "xai/grok-4.1-fast-non-reasoning": { "id": "xai/grok-4.1-fast-non-reasoning", @@ -35915,7 +40088,12 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 30000 + "maxTokens": 30000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "xai/grok-code-fast-1": { "id": "xai/grok-code-fast-1", @@ -35934,7 +40112,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 256000 + "maxTokens": 256000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "xiaomi/mimo-v2-flash": { "id": "xiaomi/mimo-v2-flash", @@ -35953,7 +40136,12 @@ "cacheWrite": 0 }, "contextWindow": 262000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "zai/glm-4.5": { "id": "zai/glm-4.5", @@ -35972,7 +40160,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "zai/glm-4.5-air": { "id": "zai/glm-4.5-air", @@ -35991,7 +40184,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 96000 + "maxTokens": 96000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "zai/glm-4.5v": { "id": "zai/glm-4.5v", @@ -36011,7 +40209,12 @@ "cacheWrite": 0 }, "contextWindow": 65536, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "zai/glm-4.6": { "id": "zai/glm-4.6", @@ -36030,7 +40233,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 96000 + "maxTokens": 96000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "zai/glm-4.6v": { "id": "zai/glm-4.6v", @@ -36050,7 +40258,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 24000 + "maxTokens": 24000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "zai/glm-4.6v-flash": { "id": "zai/glm-4.6v-flash", @@ -36070,7 +40283,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 24000 + "maxTokens": 24000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "zai/glm-4.7": { "id": "zai/glm-4.7", @@ -36089,7 +40307,12 @@ "cacheWrite": 0 }, "contextWindow": 202752, - "maxTokens": 120000 + "maxTokens": 120000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "zai/glm-4.7-flashx": { "id": "zai/glm-4.7-flashx", @@ -36108,7 +40331,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "zai/glm-5": { "id": "zai/glm-5", @@ -36127,7 +40355,12 @@ "cacheWrite": 0 }, "contextWindow": 202800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } } }, "xai": { @@ -36341,7 +40574,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "grok-3-mini-fast": { "id": "grok-3-mini-fast", @@ -36360,7 +40598,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "grok-3-mini-fast-latest": { "id": "grok-3-mini-fast-latest", @@ -36379,7 +40622,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "grok-3-mini-latest": { "id": "grok-3-mini-latest", @@ -36398,7 +40646,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "grok-4": { "id": "grok-4", @@ -36417,7 +40670,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "grok-4-1-fast": { "id": "grok-4-1-fast", @@ -36437,7 +40695,12 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 30000 + "maxTokens": 30000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "grok-4-1-fast-non-reasoning": { "id": "grok-4-1-fast-non-reasoning", @@ -36477,7 +40740,12 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 30000 + "maxTokens": 30000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "grok-4-fast-non-reasoning": { "id": "grok-4-fast-non-reasoning", @@ -36535,7 +40803,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 10000 + "maxTokens": 10000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "grok-vision-beta": { "id": "grok-vision-beta", @@ -36576,7 +40849,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 32000 + "maxTokens": 32000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } } }, "zai": { @@ -36597,7 +40875,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 98304 + "maxTokens": 98304, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "glm-4.5-air": { "id": "glm-4.5-air", @@ -36616,7 +40899,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 98304 + "maxTokens": 98304, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "glm-4.5-flash": { "id": "glm-4.5-flash", @@ -36635,7 +40923,12 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 98304 + "maxTokens": 98304, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "glm-4.5v": { "id": "glm-4.5v", @@ -36655,7 +40948,12 @@ "cacheWrite": 0 }, "contextWindow": 64000, - "maxTokens": 16384 + "maxTokens": 16384, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "glm-4.6": { "id": "glm-4.6", @@ -36674,7 +40972,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "glm-4.6v": { "id": "glm-4.6v", @@ -36694,7 +40997,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 32768 + "maxTokens": 32768, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "glm-4.7": { "id": "glm-4.7", @@ -36713,7 +41021,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "glm-4.7-flash": { "id": "glm-4.7-flash", @@ -36732,7 +41045,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "glm-4.7-flashx": { "id": "glm-4.7-flashx", @@ -36751,7 +41069,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "glm-5": { "id": "glm-5", @@ -36770,7 +41093,12 @@ "cacheWrite": 0 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } } }, "zenmux": { @@ -36832,7 +41160,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-haiku-4.5": { "id": "anthropic/claude-haiku-4.5", @@ -36872,7 +41205,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-opus-4.1": { "id": "anthropic/claude-opus-4.1", @@ -36892,7 +41230,12 @@ "cacheWrite": 18.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-opus-4.5": { "id": "anthropic/claude-opus-4.5", @@ -36912,7 +41255,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-budget-effort", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-opus-4.6": { "id": "anthropic/claude-opus-4.6", @@ -36932,7 +41280,12 @@ "cacheWrite": 6.25 }, "contextWindow": 200000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "anthropic-adaptive", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-sonnet-4": { "id": "anthropic/claude-sonnet-4", @@ -36952,7 +41305,12 @@ "cacheWrite": 3.75 }, "contextWindow": 1000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "budget", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-sonnet-4.5": { "id": "anthropic/claude-sonnet-4.5", @@ -36972,7 +41330,12 @@ "cacheWrite": 3.75 }, "contextWindow": 1000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-budget-effort", + "minLevel": "minimal", + "maxLevel": "xhigh" + } }, "anthropic/claude-sonnet-4.6": { "id": "anthropic/claude-sonnet-4.6", @@ -36992,7 +41355,12 @@ "cacheWrite": 3.75 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "anthropic-adaptive", + "minLevel": "minimal", + "maxLevel": "high" + } }, "baidu/ernie-5.0-thinking-preview": { "id": "baidu/ernie-5.0-thinking-preview", @@ -37012,7 +41380,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "baidu/ernie-x1.1-preview": { "id": "baidu/ernie-x1.1-preview", @@ -37031,7 +41404,12 @@ "cacheWrite": 0 }, "contextWindow": 65536, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek/deepseek-chat": { "id": "deepseek/deepseek-chat", @@ -37088,7 +41466,12 @@ "cacheWrite": 0 }, "contextWindow": 64000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek/deepseek-reasoner": { "id": "deepseek/deepseek-reasoner", @@ -37107,7 +41490,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek/deepseek-v3.2": { "id": "deepseek/deepseek-v3.2", @@ -37126,7 +41514,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "deepseek/deepseek-v3.2-exp": { "id": "deepseek/deepseek-v3.2-exp", @@ -37145,7 +41538,12 @@ "cacheWrite": 0 }, "contextWindow": 163000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-2.0-flash": { "id": "google/gemini-2.0-flash", @@ -37205,7 +41603,12 @@ "cacheWrite": 1 }, "contextWindow": 1048000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-2.5-flash-lite": { "id": "google/gemini-2.5-flash-lite", @@ -37245,7 +41648,12 @@ "cacheWrite": 4.5 }, "contextWindow": 1048000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-3-flash-preview": { "id": "google/gemini-3-flash-preview", @@ -37265,7 +41673,12 @@ "cacheWrite": 1 }, "contextWindow": 1048000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-3-pro-preview": { "id": "google/gemini-3-pro-preview", @@ -37285,7 +41698,12 @@ "cacheWrite": 4.5 }, "contextWindow": 1048000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "high" + } }, "google/gemini-3.1-flash-lite-preview": { "id": "google/gemini-3.1-flash-lite-preview", @@ -37305,7 +41723,12 @@ "cacheWrite": 1 }, "contextWindow": 1048576, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "google/gemini-3.1-pro-preview": { "id": "google/gemini-3.1-pro-preview", @@ -37325,7 +41748,12 @@ "cacheWrite": 4.5 }, "contextWindow": 1048576, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "high" + } }, "google/gemma-3-12b-it": { "id": "google/gemma-3-12b-it", @@ -37459,7 +41887,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "inclusionai/ring-flash-2.0": { "id": "inclusionai/ring-flash-2.0", @@ -37478,7 +41911,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "inclusionai/ring-mini-2.0": { "id": "inclusionai/ring-mini-2.0", @@ -37497,7 +41935,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "kuaishou/kat-coder-pro-v1": { "id": "kuaishou/kat-coder-pro-v1", @@ -37593,7 +42036,12 @@ "cacheWrite": 0.38 }, "contextWindow": 204000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "minimax/minimax-m2-her": { "id": "minimax/minimax-m2-her", @@ -37631,7 +42079,12 @@ "cacheWrite": 0.38 }, "contextWindow": 204000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "minimax/minimax-m2.5": { "id": "minimax/minimax-m2.5", @@ -37650,7 +42103,12 @@ "cacheWrite": 0.375 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "minimax/minimax-m2.5-lightning": { "id": "minimax/minimax-m2.5-lightning", @@ -37669,7 +42127,12 @@ "cacheWrite": 0.75 }, "contextWindow": 204800, - "maxTokens": 131072 + "maxTokens": 131072, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "mistralai/mistral-large-2512": { "id": "mistralai/mistral-large-2512", @@ -37746,7 +42209,12 @@ "cacheWrite": 0 }, "contextWindow": 262000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "moonshotai/kimi-k2-thinking-turbo": { "id": "moonshotai/kimi-k2-thinking-turbo", @@ -37765,7 +42233,12 @@ "cacheWrite": 0 }, "contextWindow": 262000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "moonshotai/kimi-k2.5": { "id": "moonshotai/kimi-k2.5", @@ -37785,7 +42258,12 @@ "cacheWrite": 0 }, "contextWindow": 262000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-4.1": { "id": "openai/gpt-4.1", @@ -37905,7 +42383,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-chat": { "id": "openai/gpt-5-chat", @@ -37945,7 +42428,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-mini": { "id": "openai/gpt-5-mini", @@ -37965,7 +42453,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-nano": { "id": "openai/gpt-5-nano", @@ -37985,7 +42478,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5-pro": { "id": "openai/gpt-5-pro", @@ -38005,7 +42503,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1": { "id": "openai/gpt-5.1", @@ -38025,7 +42528,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1-chat": { "id": "openai/gpt-5.1-chat", @@ -38065,7 +42573,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "openai/gpt-5.1-codex-mini": { "id": "openai/gpt-5.1-codex-mini", @@ -38085,7 +42598,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "medium", + "maxLevel": "high" + } }, "openai/gpt-5.2": { "id": "openai/gpt-5.2", @@ -38105,7 +42623,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.2-chat": { "id": "openai/gpt-5.2-chat", @@ -38125,7 +42648,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.2-codex": { "id": "openai/gpt-5.2-codex", @@ -38145,7 +42673,12 @@ "cacheWrite": 0 }, "contextWindow": 272000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.2-pro": { "id": "openai/gpt-5.2-pro", @@ -38165,7 +42698,12 @@ "cacheWrite": 0 }, "contextWindow": 400000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.3-chat": { "id": "openai/gpt-5.3-chat", @@ -38185,7 +42723,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/gpt-5.3-codex": { "id": "openai/gpt-5.3-codex", @@ -38204,8 +42747,63 @@ "cacheRead": 0.175, "cacheWrite": 0 }, - "contextWindow": 272000, - "maxTokens": 128000 + "contextWindow": 400000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } + }, + "openai/gpt-5.4": { + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "api": "openai-completions", + "provider": "zenmux", + "baseUrl": "https://zenmux.ai/api/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 2.5, + "output": 15, + "cacheRead": 0.25, + "cacheWrite": 0 + }, + "contextWindow": 1050000, + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } + }, + "openai/gpt-5.4-pro": { + "id": "openai/gpt-5.4-pro", + "name": "OpenAI: GPT-5.4 Pro", + "api": "openai-completions", + "provider": "zenmux", + "baseUrl": "https://zenmux.ai/api/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 30, + "output": 180, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 1050000, + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "low", + "maxLevel": "xhigh" + } }, "openai/o4-mini": { "id": "openai/o4-mini", @@ -38225,7 +42823,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 100000 + "maxTokens": 100000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-14b": { "id": "qwen/qwen3-14b", @@ -38244,7 +42847,12 @@ "cacheWrite": 0 }, "contextWindow": 32000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-235b-a22b-2507": { "id": "qwen/qwen3-235b-a22b-2507", @@ -38282,7 +42890,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-coder": { "id": "qwen/qwen3-coder", @@ -38339,7 +42952,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-max-preview": { "id": "qwen/qwen3-max-preview", @@ -38358,7 +42976,12 @@ "cacheWrite": 1.5 }, "contextWindow": 262144, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3-vl-plus": { "id": "qwen/qwen3-vl-plus", @@ -38378,7 +43001,12 @@ "cacheWrite": 0 }, "contextWindow": 262144, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3.5-flash": { "id": "qwen/qwen3.5-flash", @@ -38398,7 +43026,12 @@ "cacheWrite": 0.125 }, "contextWindow": 1024000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "qwen/qwen3.5-plus": { "id": "qwen/qwen3.5-plus", @@ -38418,7 +43051,12 @@ "cacheWrite": 0.5 }, "contextWindow": 1000000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "stepfun/step-3": { "id": "stepfun/step-3", @@ -38438,7 +43076,12 @@ "cacheWrite": 0 }, "contextWindow": 65536, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "stepfun/step-3.5-flash": { "id": "stepfun/step-3.5-flash", @@ -38476,7 +43119,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "tencent/hunyuan-2.0-thinking": { "id": "tencent/hunyuan-2.0-thinking", @@ -38495,7 +43143,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "volcengine/doubao-seed-1-6-vision": { "id": "volcengine/doubao-seed-1-6-vision", @@ -38515,7 +43168,12 @@ "cacheWrite": 0.0024 }, "contextWindow": 256000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "volcengine/doubao-seed-1.8": { "id": "volcengine/doubao-seed-1.8", @@ -38535,7 +43193,12 @@ "cacheWrite": 0.0024 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "volcengine/doubao-seed-2.0-code": { "id": "volcengine/doubao-seed-2.0-code", @@ -38555,7 +43218,12 @@ "cacheWrite": 0.0024 }, "contextWindow": 256000, - "maxTokens": 8888 + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "volcengine/doubao-seed-2.0-lite": { "id": "volcengine/doubao-seed-2.0-lite", @@ -38575,7 +43243,12 @@ "cacheWrite": 0.0024 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "volcengine/doubao-seed-2.0-mini": { "id": "volcengine/doubao-seed-2.0-mini", @@ -38595,7 +43268,12 @@ "cacheWrite": 0.0024 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "volcengine/doubao-seed-2.0-pro": { "id": "volcengine/doubao-seed-2.0-pro", @@ -38615,7 +43293,12 @@ "cacheWrite": 0.0024 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "volcengine/doubao-seed-code": { "id": "volcengine/doubao-seed-code", @@ -38635,7 +43318,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "x-ai/grok-4": { "id": "x-ai/grok-4", @@ -38655,7 +43343,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "x-ai/grok-4-fast": { "id": "x-ai/grok-4-fast", @@ -38675,7 +43368,12 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "x-ai/grok-4-fast-non-reasoning": { "id": "x-ai/grok-4-fast-non-reasoning", @@ -38715,7 +43413,12 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "x-ai/grok-4.1-fast-non-reasoning": { "id": "x-ai/grok-4.1-fast-non-reasoning", @@ -38737,6 +43440,51 @@ "contextWindow": 2000000, "maxTokens": 64000 }, + "x-ai/grok-4.2-fast": { + "id": "x-ai/grok-4.2-fast", + "name": "xAI: Grok 4.2 Fast", + "api": "openai-completions", + "provider": "zenmux", + "baseUrl": "https://zenmux.ai/api/v1", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 2, + "output": 6, + "cacheRead": 0.2, + "cacheWrite": 0 + }, + "contextWindow": 2000000, + "maxTokens": 8888, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } + }, + "x-ai/grok-4.2-fast-non-reasoning": { + "id": "x-ai/grok-4.2-fast-non-reasoning", + "name": "xAI: Grok 4.2 Fast Non Reasoning", + "api": "openai-completions", + "provider": "zenmux", + "baseUrl": "https://zenmux.ai/api/v1", + "reasoning": false, + "input": [ + "text", + "image" + ], + "cost": { + "input": 2, + "output": 6, + "cacheRead": 0.2, + "cacheWrite": 0 + }, + "contextWindow": 2000000, + "maxTokens": 8888 + }, "x-ai/grok-code-fast-1": { "id": "x-ai/grok-code-fast-1", "name": "Grok Code Fast 1", @@ -38754,7 +43502,12 @@ "cacheWrite": 0 }, "contextWindow": 256000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "xiaomi/mimo-v2-flash": { "id": "xiaomi/mimo-v2-flash", @@ -38773,7 +43526,12 @@ "cacheWrite": 0 }, "contextWindow": 262000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "xiaomi/mimo-v2-flash-free": { "id": "xiaomi/mimo-v2-flash-free", @@ -38792,7 +43550,12 @@ "cacheWrite": 0 }, "contextWindow": 262000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.5": { "id": "z-ai/glm-4.5", @@ -38811,7 +43574,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.5-air": { "id": "z-ai/glm-4.5-air", @@ -38830,7 +43598,12 @@ "cacheWrite": 0 }, "contextWindow": 128000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.6": { "id": "z-ai/glm-4.6", @@ -38849,7 +43622,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.6v": { "id": "z-ai/glm-4.6v", @@ -38869,7 +43647,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.6v-flash": { "id": "z-ai/glm-4.6v-flash", @@ -38889,7 +43672,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.6v-flash-free": { "id": "z-ai/glm-4.6v-flash-free", @@ -38909,7 +43697,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.7": { "id": "z-ai/glm-4.7", @@ -38928,7 +43721,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.7-flash-free": { "id": "z-ai/glm-4.7-flash-free", @@ -38947,7 +43745,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-4.7-flashx": { "id": "z-ai/glm-4.7-flashx", @@ -38966,7 +43769,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 64000 + "maxTokens": 64000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } }, "z-ai/glm-5": { "id": "z-ai/glm-5", @@ -38985,7 +43793,12 @@ "cacheWrite": 0 }, "contextWindow": 200000, - "maxTokens": 128000 + "maxTokens": 128000, + "thinking": { + "mode": "effort", + "minLevel": "minimal", + "maxLevel": "high" + } } } } \ No newline at end of file diff --git a/packages/ai/src/models.ts b/packages/ai/src/models.ts index ebf6ec3cf..55bf553e3 100644 --- a/packages/ai/src/models.ts +++ b/packages/ai/src/models.ts @@ -1,3 +1,4 @@ +import { enrichModelThinking } from "./model-thinking"; import MODELS from "./models.json" with { type: "json" }; import type { Api, KnownProvider, Model, Usage } from "./types"; @@ -13,7 +14,7 @@ const modelRegistry: Map>> = new Map(); for (const [provider, models] of Object.entries(MODELS)) { const providerModels = new Map>(); for (const [id, model] of Object.entries(models)) { - providerModels.set(id, model as Model); + providerModels.set(id, enrichModelThinking(model as Model)); } modelRegistry.set(provider, providerModels); } @@ -42,22 +43,6 @@ export function calculateCost(model: Model, usage: Usage usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite; return usage.cost; } - -/** - * Check if a model supports xhigh thinking level. - * - * Supported today: - * - GPT-5.1 Codex Max - * - GPT-5.2 / GPT-5.3 model families - * - Anthropic Messages API Opus 4.6 models (xhigh maps to adaptive effort "max"), or other models that support budget-based thinking - */ -export function supportsXhigh(model: Model): boolean { - if (model.id.includes("gpt-5.2") || model.id.includes("gpt-5.3") || model.id.includes("gpt-5.1-codex-max")) { - return true; - } - return model.api === "anthropic-messages"; -} - /** * Check if two models are equal by comparing both their id and provider. * Returns false if either model is null or undefined. diff --git a/packages/ai/src/provider-models/index.ts b/packages/ai/src/provider-models/index.ts index bca524f50..3bbaa404c 100644 --- a/packages/ai/src/provider-models/index.ts +++ b/packages/ai/src/provider-models/index.ts @@ -1,5 +1,4 @@ export * from "./descriptors"; export * from "./google"; -export * from "./model-policies"; export * from "./openai-compat"; export * from "./special"; diff --git a/packages/ai/src/provider-models/model-policies.ts b/packages/ai/src/provider-models/model-policies.ts deleted file mode 100644 index 98361ec37..000000000 --- a/packages/ai/src/provider-models/model-policies.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Post-processing policies applied to generated model catalogs. - * - * Each policy corrects known upstream metadata errors or normalizes model - * properties that differ from the canonical values. Keeping these in a - * dedicated module makes them explicit, isolated, and testable. - */ -import type { Api, Model } from "../types"; - -const CLOUDFLARE_AI_GATEWAY_BASE_URL = "https://gateway.ai.cloudflare.com/v1///anthropic"; - -/** - * Static fallback model injected when Cloudflare AI Gateway discovery - * returns no results. Ensures the provider always has at least one usable - * model entry in the catalog. - */ -export const CLOUDFLARE_FALLBACK_MODEL: Model<"anthropic-messages"> = { - id: "claude-sonnet-4-5", - name: "Claude Sonnet 4.5", - api: "anthropic-messages", - provider: "cloudflare-ai-gateway", - baseUrl: CLOUDFLARE_AI_GATEWAY_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: { - input: 3, - output: 15, - cacheRead: 0.3, - cacheWrite: 3.75, - }, - contextWindow: 200000, - maxTokens: 64000, -}; - -/** - * Apply upstream metadata corrections to a mutable array of models. - * - * Corrections include cache-pricing fixes and context-window clamps where - * provider APIs or models.dev report incorrect values. - */ -export function applyGeneratedModelPolicies(models: Model[]): void { - for (const model of models) { - // Claude Opus 4.5: models.dev reports 3x the correct cache pricing - if (model.provider === "anthropic" && model.id === "claude-opus-4-5") { - model.cost.cacheRead = 0.5; - model.cost.cacheWrite = 6.25; - } - - // Bedrock Opus 4.6: upstream cache pricing is incorrect - if (model.provider === "amazon-bedrock" && model.id.includes("anthropic.claude-opus-4-6-v1")) { - model.cost.cacheRead = 0.5; - model.cost.cacheWrite = 6.25; - } - - // Opus 4.6 / Sonnet 4.6: 1M context is beta; clamp to 200K - if ( - model.id.includes("opus-4-6") || - model.id.includes("opus-4.6") || - model.id.includes("sonnet-4-6") || - model.id.includes("sonnet-4.6") - ) { - model.contextWindow = 200000; - } - - // OpenCode variants: Claude Sonnet 4/4.5 listed with 1M context, actual limit is 200K - if ( - (model.provider === "opencode-zen" || model.provider === "opencode-go") && - (model.id === "claude-sonnet-4-5" || model.id === "claude-sonnet-4") - ) { - model.contextWindow = 200000; - } - - // Codex models: 400K figure includes output budget; input window is 272K - if (model.id.includes("codex") && !model.id.includes("codex-spark")) { - model.contextWindow = 272000; - } - } -} - -/** - * Link `-spark` model variants to their base models for context promotion. - * - * When a spark model's context is exhausted, the agent can promote to the - * corresponding full model. This sets `contextPromotionTarget` on each - * spark variant that has a matching base model. - */ -export function linkSparkPromotionTargets(models: Model[]): void { - for (const candidate of models) { - if (!candidate.id.endsWith("-spark")) continue; - const baseId = candidate.id.slice(0, -"-spark".length); - const fallback = models.find( - model => model.provider === candidate.provider && model.api === candidate.api && model.id === baseId, - ); - if (!fallback) continue; - candidate.contextPromotionTarget = `${fallback.provider}/${fallback.id}`; - } -} diff --git a/packages/ai/src/providers/amazon-bedrock.ts b/packages/ai/src/providers/amazon-bedrock.ts index 5578ceef0..c9323aef5 100644 --- a/packages/ai/src/providers/amazon-bedrock.ts +++ b/packages/ai/src/providers/amazon-bedrock.ts @@ -21,15 +21,15 @@ import { } from "@aws-sdk/client-bedrock-runtime"; import { $env } from "@oh-my-pi/pi-utils"; import { NodeHttpHandler } from "@smithy/node-http-handler"; +import type { Effort } from "../model-thinking"; +import { mapEffortToAnthropicAdaptiveEffort, requireSupportedEffort } from "../model-thinking"; import { calculateCost } from "../models"; -import type { ThinkingEffort, ThinkingLevel } from "../thinking"; import type { Api, AssistantMessage, CacheRetention, Context, Model, - SimpleStreamOptions, StopReason, StreamFunction, StreamOptions, @@ -51,7 +51,7 @@ export interface BedrockOptions extends StreamOptions { profile?: string; toolChoice?: "auto" | "any" | "none" | { type: "tool"; name: string }; /* See https://docs.aws.amazon.com/bedrock/latest/userguide/inference-reasoning.html for supported models. */ - reasoning?: ThinkingLevel; + reasoning?: Effort; /* Custom token budgets per thinking level. Overrides default budgets. */ thinkingBudgets?: ThinkingBudgets; /* Only supported by Claude 4.x models, see https://docs.aws.amazon.com/bedrock/latest/userguide/claude-messages-extended-thinking.html#claude-messages-extended-thinking-tool-use-interleaved */ @@ -591,85 +591,46 @@ function mapStopReason(reason: string | undefined): StopReason { } } -/** Check if the model supports adaptive thinking (Opus 4.6+ / Sonnet 4.6+). */ -function supportsAdaptiveThinking(modelId: string): boolean { - return ( - modelId.includes("opus-4-6") || - modelId.includes("opus-4.6") || - modelId.includes("sonnet-4-6") || - modelId.includes("sonnet-4.6") - ); -} - -/** Map a thinking level to an adaptive effort value. */ -function mapThinkingLevelToEffort(level: SimpleStreamOptions["reasoning"]): "low" | "medium" | "high" | "max" { - switch (level) { - case "minimal": - case "low": - return "low"; - case "medium": - return "medium"; - case "high": - return "high"; - case "xhigh": - return "max"; - default: - return "high"; - } -} - function buildAdditionalModelRequestFields( model: Model<"bedrock-converse-stream">, options: BedrockOptions, ): Record | undefined { const reasoning = options.reasoning; - if (!reasoning || !model.reasoning || reasoning === "off") { + if (!reasoning || !model.reasoning) { return undefined; } - if (model.id.includes("anthropic.claude")) { - // Opus 4.6+ / Sonnet 4.6+ uses adaptive thinking with effort levels - if (supportsAdaptiveThinking(model.id)) { - let effort = mapThinkingLevelToEffort(reasoning); - // "max" effort is only supported on Opus 4.6; clamp to "high" for Sonnet 4.6 - const supportsMax = model.id.includes("opus-4-6") || model.id.includes("opus-4.6"); - if (effort === "max" && !supportsMax) { - effort = "high"; - } - const result: Record = { - thinking: { type: "adaptive" }, - output_config: { effort }, - }; - return result; - } - - const defaultBudgets: Record = { - minimal: 1024, - low: 2048, - medium: 8192, - high: 16384, - xhigh: 16384, // Claude doesn't support xhigh, clamp to high + const mode = model.thinking?.mode; + if (mode === "anthropic-adaptive") { + const effort = mapEffortToAnthropicAdaptiveEffort(model, reasoning); + return { + thinking: { type: "adaptive" }, + output_config: { effort }, }; - - // Custom budgets override defaults (xhigh not in ThinkingBudgets, use high) - const level = reasoning === "xhigh" ? "high" : reasoning; - const budget = options.thinkingBudgets?.[level] ?? defaultBudgets[level]; - - const result: Record = { - thinking: { - type: "enabled", - budget_tokens: budget, - }, - }; - - if (options.interleavedThinking && !supportsAdaptiveThinking(model.id)) { - result.anthropic_beta = ["interleaved-thinking-2025-05-14"]; - } - - return result; } - return undefined; + const level = requireSupportedEffort(model, reasoning); + const defaultBudgets: Record = { + minimal: 1024, + low: 2048, + medium: 8192, + high: 16384, + xhigh: 32768, + }; + const budget = options.thinkingBudgets?.[level] ?? defaultBudgets[level]; + + const result: Record = { + thinking: { + type: "enabled", + budget_tokens: budget, + }, + }; + + if (options.interleavedThinking) { + result.anthropic_beta = ["interleaved-thinking-2025-05-14"]; + } + + return result; } function createImageBlock(mimeType: string, data: string) { diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts index ffec766b1..25765de20 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/providers/anthropic.ts @@ -8,6 +8,7 @@ import type { MessageParam, } from "@anthropic-ai/sdk/resources/messages"; import { $env, abortableSleep, isEnoent } from "@oh-my-pi/pi-utils"; +import { mapEffortToAnthropicAdaptiveEffort } from "../model-thinking"; import { calculateCost } from "../models"; import { getEnvApiKey, OUTPUT_FALLBACK_BUFFER } from "../stream"; import type { @@ -846,19 +847,6 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = ( return stream; }; -/** - * Check if a model supports adaptive thinking (Opus 4.6+) - */ -function supportsAdaptiveThinking(modelId: string): boolean { - // Opus/Sonnet 4.6 model IDs (with or without date suffix) - return ( - modelId.includes("opus-4-6") || - modelId.includes("opus-4.6") || - modelId.includes("sonnet-4-6") || - modelId.includes("sonnet-4.6") - ); -} - export type AnthropicSystemBlock = { type: "text"; text: string; @@ -914,26 +902,6 @@ export function buildAnthropicSystemBlocks( return blocks.length > 0 ? blocks : undefined; } -/** - * Map ThinkingLevel to Anthropic effort levels for adaptive thinking - */ -function mapThinkingLevelToEffort(level: SimpleStreamOptions["reasoning"]): AnthropicEffort { - switch (level) { - case "minimal": - return "low"; - case "low": - return "low"; - case "medium": - return "medium"; - case "high": - return "high"; - case "xhigh": - return "max"; - default: - return "high"; - } -} - export function normalizeExtraBetas(betas?: string[] | string): string[] { if (!betas) return []; const raw = Array.isArray(betas) ? betas : betas.split(","); @@ -1303,9 +1271,13 @@ function buildParams( } if (options?.thinkingEnabled && model.reasoning) { - if (supportsAdaptiveThinking(model.id)) { + const mode = model.thinking?.mode; + const requestedEffort = options.reasoning; + const effort = + options.effort ?? (requestedEffort ? mapEffortToAnthropicAdaptiveEffort(model, requestedEffort) : undefined); + + if (mode === "anthropic-adaptive") { params.thinking = { type: "adaptive" }; - const effort = options.effort ?? mapThinkingLevelToEffort(options.reasoning); if (effort) { params.output_config = { effort }; } @@ -1314,12 +1286,8 @@ function buildParams( type: "enabled", budget_tokens: options.thinkingBudgetTokens || 1024, }; - // Opus 4.5 supports effort alongside budget-based thinking - if (model.id.includes("opus-4-5") || model.id.includes("opus-4.5")) { - const effort = options.effort ?? mapThinkingLevelToEffort(options.reasoning); - if (effort) { - params.output_config = { effort }; - } + if (mode === "anthropic-budget-effort" && effort) { + params.output_config = { effort }; } } } diff --git a/packages/ai/src/providers/gitlab-duo.ts b/packages/ai/src/providers/gitlab-duo.ts index 8ddc8cf3b..a20225d1e 100644 --- a/packages/ai/src/providers/gitlab-duo.ts +++ b/packages/ai/src/providers/gitlab-duo.ts @@ -267,10 +267,7 @@ export function streamGitLabDuo( ...options.headers, }; - const reasoningEffort = - options.reasoning === "off" - ? undefined - : (options.reasoning as "minimal" | "low" | "medium" | "high" | "xhigh" | undefined); + const reasoningEffort = options.reasoning; const inner = mapping.provider === "anthropic" diff --git a/packages/ai/src/providers/google-vertex.ts b/packages/ai/src/providers/google-vertex.ts index 1ee23235a..7dad38f27 100644 --- a/packages/ai/src/providers/google-vertex.ts +++ b/packages/ai/src/providers/google-vertex.ts @@ -385,13 +385,13 @@ function buildParams( } if (options.thinking?.enabled && model.reasoning) { - const thinkingConfig: ThinkingConfig = { includeThoughts: true }; + const cfg: ThinkingConfig = { includeThoughts: true }; if (options.thinking.level !== undefined) { - thinkingConfig.thinkingLevel = THINKING_LEVEL_MAP[options.thinking.level]; + cfg.thinkingLevel = THINKING_LEVEL_MAP[options.thinking.level]; } else if (options.thinking.budgetTokens !== undefined) { - thinkingConfig.thinkingBudget = options.thinking.budgetTokens; + cfg.thinkingBudget = options.thinking.budgetTokens; } - config.thinkingConfig = thinkingConfig; + config.thinkingConfig = cfg; } if (options.signal) { diff --git a/packages/ai/src/providers/google.ts b/packages/ai/src/providers/google.ts index c49c02f8d..90395ea47 100644 --- a/packages/ai/src/providers/google.ts +++ b/packages/ai/src/providers/google.ts @@ -348,14 +348,14 @@ function buildParams( } if (options.thinking?.enabled && model.reasoning) { - const thinkingConfig: ThinkingConfig = { includeThoughts: true }; + const cfg: ThinkingConfig = { includeThoughts: true }; if (options.thinking.level !== undefined) { // Cast to any since our GoogleThinkingLevel mirrors Google's ThinkingLevel enum values - thinkingConfig.thinkingLevel = options.thinking.level as any; + cfg.thinkingLevel = options.thinking.level as any; } else if (options.thinking.budgetTokens !== undefined) { - thinkingConfig.thinkingBudget = options.thinking.budgetTokens; + cfg.thinkingBudget = options.thinking.budgetTokens; } - config.thinkingConfig = thinkingConfig; + config.thinkingConfig = cfg; } if (options.signal) { diff --git a/packages/ai/src/providers/kimi.ts b/packages/ai/src/providers/kimi.ts index f9729bf79..bdb91d431 100644 --- a/packages/ai/src/providers/kimi.ts +++ b/packages/ai/src/providers/kimi.ts @@ -62,7 +62,7 @@ export function streamKimi( // Calculate thinking budget from reasoning level const reasoning = options?.reasoning; - const reasoningEffort = reasoning === "off" ? undefined : reasoning; + const reasoningEffort = reasoning; const thinkingEnabled = !!reasoningEffort && model.reasoning; const thinkingBudget = reasoningEffort ? (options?.thinkingBudgets?.[reasoningEffort] ?? ANTHROPIC_THINKING[reasoningEffort]) @@ -90,7 +90,7 @@ export function streamKimi( } } else { // OpenAI format - use original model with Kimi headers - const reasoningEffort = options?.reasoning === "off" ? undefined : options?.reasoning; + const reasoningEffort = options?.reasoning; const innerStream = streamOpenAICompletions(model, context, { apiKey: options?.apiKey, temperature: options?.temperature, diff --git a/packages/ai/src/providers/openai-codex-responses.ts b/packages/ai/src/providers/openai-codex-responses.ts index b33e69ed5..441b561fe 100644 --- a/packages/ai/src/providers/openai-codex-responses.ts +++ b/packages/ai/src/providers/openai-codex-responses.ts @@ -383,7 +383,7 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses" include: options?.include, }; - const transformedBody = await transformRequestBody(params, codexOptions, systemPrompt); + const transformedBody = await transformRequestBody(params, model, codexOptions, systemPrompt); options?.onPayload?.(transformedBody); const reasoningEffort = transformedBody.reasoning?.effort ?? null; diff --git a/packages/ai/src/providers/openai-codex/request-transformer.ts b/packages/ai/src/providers/openai-codex/request-transformer.ts index 3a69e4170..fd294baff 100644 --- a/packages/ai/src/providers/openai-codex/request-transformer.ts +++ b/packages/ai/src/providers/openai-codex/request-transformer.ts @@ -1,3 +1,7 @@ +import type { Effort } from "../../model-thinking"; +import { requireSupportedEffort } from "../../model-thinking"; +import type { Api, Model } from "../../types"; + export interface ReasoningConfig { effort: "none" | "minimal" | "low" | "medium" | "high" | "xhigh"; summary: "auto" | "concise" | "detailed" | null; @@ -47,30 +51,10 @@ export interface RequestBody { [key: string]: unknown; } -function clampReasoningEffort(model: string, effort: ReasoningConfig["effort"]): ReasoningConfig["effort"] { - // Codex backend expects exact model IDs. Do not normalize model names here. - const modelId = model.includes("/") ? model.split("/").pop()! : model; - - // gpt-5.1 does not support xhigh. - if (modelId === "gpt-5.1" && effort === "xhigh") { - return "high"; - } - - if ((modelId.startsWith("gpt-5.2") || modelId.startsWith("gpt-5.3")) && effort === "minimal") { - return "low"; - } - - // gpt-5.1-codex-mini only supports medium/high. - if (modelId === "gpt-5.1-codex-mini") { - return effort === "high" || effort === "xhigh" ? "high" : "medium"; - } - - return effort; -} - -function getReasoningConfig(model: string, options: CodexRequestOptions): ReasoningConfig { +function getReasoningConfig(model: Model, options: CodexRequestOptions): ReasoningConfig { return { - effort: clampReasoningEffort(model, options.reasoningEffort as ReasoningConfig["effort"]), + effort: + options.reasoningEffort === "none" ? "none" : requireSupportedEffort(model, options.reasoningEffort as Effort), summary: options.reasoningSummary ?? "detailed", }; } @@ -91,6 +75,7 @@ function filterInput(input: InputItem[] | undefined): InputItem[] | undefined { export async function transformRequestBody( body: RequestBody, + model: Model, options: CodexRequestOptions = {}, prompt?: { instructions: string; developerMessages: string[] }, ): Promise { @@ -148,7 +133,7 @@ export async function transformRequestBody( } if (options.reasoningEffort !== undefined) { - const reasoningConfig = getReasoningConfig(body.model, options); + const reasoningConfig = getReasoningConfig(model, options); body.reasoning = { ...body.reasoning, ...reasoningConfig, diff --git a/packages/ai/src/providers/synthetic.ts b/packages/ai/src/providers/synthetic.ts index 3d6dd1d54..4baf4bc35 100644 --- a/packages/ai/src/providers/synthetic.ts +++ b/packages/ai/src/providers/synthetic.ts @@ -59,7 +59,7 @@ export function streamSynthetic( // Calculate thinking budget from reasoning level const reasoning = options?.reasoning; - const reasoningEffort = reasoning === "off" ? undefined : reasoning; + const reasoningEffort = reasoning; const thinkingEnabled = !!reasoningEffort && model.reasoning; const thinkingBudget = reasoningEffort ? (options?.thinkingBudgets?.[reasoningEffort] ?? ANTHROPIC_THINKING[reasoningEffort]) @@ -93,7 +93,7 @@ export function streamSynthetic( headers: mergedHeaders, }; - const reasoningEffort = options?.reasoning === "off" ? undefined : options?.reasoning; + const reasoningEffort = options?.reasoning; const innerStream = streamOpenAICompletions(syntheticModel, context, { apiKey: options?.apiKey, temperature: options?.temperature, diff --git a/packages/ai/src/stream.ts b/packages/ai/src/stream.ts index 476ec5463..087ef671a 100644 --- a/packages/ai/src/stream.ts +++ b/packages/ai/src/stream.ts @@ -3,25 +3,25 @@ import * as os from "node:os"; import * as path from "node:path"; import { $env, $pickenv } from "@oh-my-pi/pi-utils"; import { getCustomApi } from "./api-registry"; -import { supportsXhigh } from "./models"; +import type { Effort } from "./model-thinking"; +import { + mapEffortToAnthropicAdaptiveEffort, + mapEffortToGoogleThinkingLevel, + requireSupportedEffort, +} from "./model-thinking"; import { type BedrockOptions, streamBedrock } from "./providers/amazon-bedrock"; import { type AnthropicOptions, streamAnthropic } from "./providers/anthropic"; import { streamAzureOpenAIResponses } from "./providers/azure-openai-responses"; import { type CursorOptions, streamCursor } from "./providers/cursor"; import { isGitLabDuoModel, streamGitLabDuo } from "./providers/gitlab-duo"; import { type GoogleOptions, streamGoogle } from "./providers/google"; -import { - type GoogleGeminiCliOptions, - type GoogleThinkingLevel, - streamGoogleGeminiCli, -} from "./providers/google-gemini-cli"; +import { type GoogleGeminiCliOptions, streamGoogleGeminiCli } from "./providers/google-gemini-cli"; import { type GoogleVertexOptions, streamGoogleVertex } from "./providers/google-vertex"; import { isKimiModel, streamKimi } from "./providers/kimi"; import { streamOpenAICodexResponses } from "./providers/openai-codex-responses"; import { type OpenAICompletionsOptions, streamOpenAICompletions } from "./providers/openai-completions"; import { streamOpenAIResponses } from "./providers/openai-responses"; import { isSyntheticModel, streamSynthetic } from "./providers/synthetic"; -import type { ThinkingEffort } from "./thinking"; import type { Api, AssistantMessage, @@ -304,7 +304,7 @@ const MIN_OUTPUT_TOKENS = 1024; export const OUTPUT_FALLBACK_BUFFER = 4000; const ANTHROPIC_USE_INTERLEAVED_THINKING = Bun.env.PI_NO_INTERLEAVED_THINKING !== "1"; -export const ANTHROPIC_THINKING: Record = { +export const ANTHROPIC_THINKING: Record = { minimal: 1024, low: 4096, medium: 8192, @@ -312,7 +312,7 @@ export const ANTHROPIC_THINKING: Record = { xhigh: 32768, }; -const GOOGLE_THINKING: Record = { +const GOOGLE_THINKING: Record = { minimal: 1024, low: 4096, medium: 8192, @@ -320,7 +320,7 @@ const GOOGLE_THINKING: Record = { xhigh: 24575, }; -const BEDROCK_CLAUDE_THINKING: Record = { +const BEDROCK_CLAUDE_THINKING: Record = { minimal: 1024, low: 2048, medium: 8192, @@ -331,10 +331,9 @@ const BEDROCK_CLAUDE_THINKING: Record = { function resolveBedrockThinkingBudget( model: Model<"bedrock-converse-stream">, options?: SimpleStreamOptions, -): { budget: number; level: ThinkingEffort } | null { - if (!options?.reasoning || !model.reasoning || options.reasoning === "off") return null; - if (!model.id.includes("anthropic.claude")) return null; - const level = options.reasoning === "xhigh" ? "high" : options.reasoning; +): { budget: number; level: Effort } | null { + if (!options?.reasoning || !model.reasoning) return null; + const level = requireSupportedEffort(model, options.reasoning); const budget = options.thinkingBudgets?.[level] ?? BEDROCK_CLAUDE_THINKING[level]; return { budget, level }; } @@ -356,26 +355,6 @@ export function mapAnthropicToolChoice(choice?: ToolChoice): AnthropicOptions["t return undefined; } -/** - * Map ThinkingLevel to Anthropic effort levels for adaptive thinking (Opus 4.6+) - */ -function mapThinkingLevelToAnthropicEffort(level: ThinkingEffort, supportsXhigh: boolean): AnthropicOptions["effort"] { - switch (level) { - case "minimal": - return "low"; - case "low": - return "low"; - case "medium": - return "medium"; - case "high": - return "high"; - case "xhigh": - return supportsXhigh ? "max" : "high"; - default: - return "high"; - } -} - function mapGoogleToolChoice( choice?: ToolChoice, ): GoogleOptions["toolChoice"] | GoogleGeminiCliOptions["toolChoice"] | GoogleVertexOptions["toolChoice"] { @@ -408,11 +387,10 @@ function mapOpenAiToolChoice(choice?: ToolChoice): OpenAICompletionsOptions["too function resolveOpenAiReasoningEffort( model: Model, options?: SimpleStreamOptions, -): ThinkingEffort | undefined { +): Effort | undefined { const reasoning = options?.reasoning; - if (!reasoning || reasoning === "off") return undefined; - if (reasoning === "xhigh" && !supportsXhigh(model)) return "high"; - return reasoning; + if (!reasoning) return undefined; + return requireSupportedEffort(model, reasoning); } const castApi = (api: OptionsForApi): OptionsForApi => api as OptionsForApi; @@ -446,7 +424,7 @@ function mapOptionsForApi( case "anthropic-messages": { // Explicitly disable thinking when reasoning is not specified const reasoning = options?.reasoning; - if (!reasoning || reasoning === "off") { + if (!reasoning) { return castApi<"anthropic-messages">({ ...base, thinkingEnabled: false, @@ -465,14 +443,8 @@ function mapOptionsForApi( // For Opus 4.6+ and Sonnet 4.6+: use adaptive thinking with effort level // For older models: use budget-based thinking - if ( - model.id.includes("opus-4-6") || - model.id.includes("opus-4.6") || - model.id.includes("sonnet-4-6") || - model.id.includes("sonnet-4.6") - ) { - const supportsMaxEffort = model.id.includes("opus-4-6") || model.id.includes("opus-4.6"); - const effort = mapThinkingLevelToAnthropicEffort(reasoning, supportsMaxEffort); + if (model.thinking?.mode === "anthropic-adaptive") { + const effort = mapEffortToAnthropicAdaptiveEffort(model, reasoning); return castApi<"anthropic-messages">({ ...base, thinkingEnabled: true, @@ -577,7 +549,7 @@ function mapOptionsForApi( // Explicitly disable thinking when reasoning is not specified // This is needed because Gemini has "dynamic thinking" enabled by default const reasoning = options?.reasoning; - if (!reasoning || reasoning === "off") { + if (!reasoning) { return castApi<"google-generative-ai">({ ...base, thinking: { enabled: false }, @@ -586,16 +558,16 @@ function mapOptionsForApi( } const googleModel = model as Model<"google-generative-ai">; - const effort = reasoning === "xhigh" ? "high" : reasoning; + const effort = requireSupportedEffort(googleModel, reasoning); // Gemini 3+ models use thinkingLevel exclusively instead of thinkingBudget. // https://ai.google.dev/gemini-api/docs/thinking#set-budget - if (isGemini3ProModel(googleModel) || isGemini3FlashModel(googleModel)) { + if (googleModel.thinking?.mode === "google-level") { return castApi<"google-generative-ai">({ ...base, thinking: { enabled: true, - level: getGemini3ThinkingLevel(effort, googleModel), + level: mapEffortToGoogleThinkingLevel(googleModel, effort), }, toolChoice: mapGoogleToolChoice(options?.toolChoice), }); @@ -613,7 +585,7 @@ function mapOptionsForApi( case "google-gemini-cli": { const reasoning = options?.reasoning; - if (!reasoning || reasoning === "off") { + if (!reasoning) { return castApi<"google-gemini-cli">({ ...base, thinking: { enabled: false }, @@ -621,15 +593,15 @@ function mapOptionsForApi( }); } - const effort = reasoning === "xhigh" ? "high" : reasoning; + const effort = requireSupportedEffort(model, reasoning); // Gemini 3+ models use thinkingLevel instead of thinkingBudget - if (isGemini3ProModelId(model.id) || isGemini3FlashModelId(model.id)) { - return castApi<"google-vertex">({ + if (model.thinking?.mode === "google-level") { + return castApi<"google-gemini-cli">({ ...base, thinking: { enabled: true, - level: getGeminiCliThinkingLevel(effort, model.id), + level: mapEffortToGoogleThinkingLevel(model, effort), }, toolChoice: mapGoogleToolChoice(options?.toolChoice), }); @@ -665,7 +637,7 @@ function mapOptionsForApi( case "google-vertex": { // Explicitly disable thinking when reasoning is not specified const reasoning = options?.reasoning; - if (!reasoning || reasoning === "off") { + if (!reasoning) { return castApi<"google-vertex">({ ...base, thinking: { enabled: false }, @@ -674,15 +646,15 @@ function mapOptionsForApi( } const vertexModel = model as Model<"google-vertex">; - const effort = reasoning === "xhigh" ? "high" : reasoning; + const effort = requireSupportedEffort(vertexModel, reasoning); const geminiModel = vertexModel as unknown as Model<"google-generative-ai">; - if (isGemini3ProModel(geminiModel) || isGemini3FlashModel(geminiModel)) { + if (geminiModel.thinking?.mode === "google-level") { return castApi<"google-vertex">({ ...base, thinking: { enabled: true, - level: getGemini3ThinkingLevel(effort, geminiModel), + level: mapEffortToGoogleThinkingLevel(geminiModel, effort), }, toolChoice: mapGoogleToolChoice(options?.toolChoice), }); @@ -713,78 +685,12 @@ function mapOptionsForApi( } } -function isGemini3ProModelId(modelId: string): boolean { - return /3(?:\.\d+)?-pro/.test(modelId); -} - -function isGemini3FlashModelId(modelId: string): boolean { - return /3(?:\.\d+)?-flash/.test(modelId); -} - -function isGemini3ProModel(model: Model<"google-generative-ai">): boolean { - // Covers gemini-3-pro, gemini-3-pro-preview, gemini-3.1-pro-preview, and future 3.x variants - return isGemini3ProModelId(model.id); -} - -function isGemini3FlashModel(model: Model<"google-generative-ai">): boolean { - // Covers gemini-3-flash, gemini-3-flash-preview, gemini-3.1-flash, and future 3.x variants - return isGemini3FlashModelId(model.id); -} - -function getGemini3ThinkingLevel(effort: ThinkingEffort, model: Model<"google-generative-ai">): GoogleThinkingLevel { - if (isGemini3ProModel(model)) { - // Gemini 3 Pro only supports LOW/HIGH (for now) - switch (effort) { - case "minimal": - case "low": - return "LOW"; - default: - return "HIGH"; - } - } - // Gemini 3 Flash supports all four levels - switch (effort) { - case "minimal": - return "MINIMAL"; - case "low": - return "LOW"; - case "medium": - return "MEDIUM"; - default: - return "HIGH"; - } -} - -function getGeminiCliThinkingLevel(effort: ThinkingEffort, modelId: string): GoogleThinkingLevel { - if (isGemini3ProModelId(modelId)) { - // Gemini 3 Pro only supports LOW/HIGH (for now) - switch (effort) { - case "minimal": - case "low": - return "LOW"; - default: - return "HIGH"; - } - } - // Gemini 3 Flash supports all four levels - switch (effort) { - case "minimal": - return "MINIMAL"; - case "low": - return "LOW"; - case "medium": - return "MEDIUM"; - default: - return "HIGH"; - } -} - function getGoogleBudget( model: Model<"google-generative-ai">, - effort: ThinkingEffort, + effort: Effort, customBudgets?: ThinkingBudgets, ): number { - effort = effort === "xhigh" ? "high" : effort; + requireSupportedEffort(model, effort); // Custom budgets take precedence if provided for this level if (customBudgets?.[effort] !== undefined) { diff --git a/packages/ai/src/thinking.ts b/packages/ai/src/thinking.ts deleted file mode 100644 index ed0e7bc9b..000000000 --- a/packages/ai/src/thinking.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** Provider-level thinking levels (no "off"), ordered least to most. */ -export type ThinkingEffort = "minimal" | "low" | "medium" | "high" | "xhigh"; - -/** - * ThinkingLevel extended with "off" to disable reasoning entirely. - * Used in UI, config, session state, and CLI args. - * "off" is never sent to providers — callers strip it before streaming. - */ -export type ThinkingLevel = ThinkingEffort | "off"; - -/** - * ThinkingSelector extended with "inherit" to indicate the role should - * use the session-level default rather than an explicit choice. - * Used in per-role model assignment UI. - */ -export type ThinkingMode = ThinkingLevel | "inherit"; - -/** Metadata for a thinking mode. */ -export type ThinkingMetadata = { - /** The value of the thinking mode. */ - value: ThinkingMode; - /** The label to display for the thinking mode. */ - label: string; - /** The description to display for the thinking mode. */ - description: string; -}; - -const THINKING_META: Record = { - inherit: { value: "inherit", label: "inherit", description: "Inherit session default" }, - off: { value: "off", label: "off", description: "No reasoning" }, - minimal: { value: "minimal", label: "min", description: "Very brief reasoning (~1k tokens)" }, - low: { value: "low", label: "low", description: "Light reasoning (~2k tokens)" }, - medium: { value: "medium", label: "medium", description: "Moderate reasoning (~8k tokens)" }, - high: { value: "high", label: "high", description: "Deep reasoning (~16k tokens)" }, - xhigh: { value: "xhigh", label: "xhigh", description: "Maximum reasoning (~32k tokens)" }, -}; - -const F_LEVEL = 3; -const F_SEL = 2; -const F_MODE = 1; - -const F_THINKING: Record = { - inherit: F_MODE, - off: F_SEL, - minimal: F_LEVEL, - low: F_LEVEL, - medium: F_LEVEL, - high: F_LEVEL, - xhigh: F_LEVEL, -}; - -// Parses an unknown value and returns a ThinkingLevel if valid, otherwise undefined. -export function parseThinkingEffort(level: string | null | undefined): ThinkingEffort | undefined { - return level && (F_THINKING[level] ?? 0) >= F_LEVEL ? (level as ThinkingEffort) : undefined; -} - -// Parses an unknown value and returns a ThinkingSelector if valid, otherwise undefined. -export function parseThinkingLevel(level: string | null | undefined): ThinkingLevel | undefined { - return level && (F_THINKING[level] ?? 0) >= F_SEL ? (level as ThinkingLevel) : undefined; -} - -// Parses an unknown value and returns a ThinkingMode if valid, otherwise undefined. -export function parseThinkingMode(level: string | null | undefined): ThinkingMode | undefined { - return level && (F_THINKING[level] ?? 0) >= F_MODE ? (level as ThinkingMode) : undefined; -} - -/** Get the information for a thinking mode. */ -export function getThinkingMetadata(mode: ThinkingMode): ThinkingMetadata { - return THINKING_META[mode]; -} - -const REG_LVL: readonly ThinkingLevel[] = ["off", "minimal", "low", "medium", "high"]; -const XHI_LVL: readonly ThinkingLevel[] = ["off", "minimal", "low", "medium", "high", "xhigh"]; - -/** Returns the available thinking modes for a model based on whether it supports xhigh. */ -export function getAvailableThinkingLevels(hasXhigh: boolean = true): ReadonlyArray { - return hasXhigh ? XHI_LVL : REG_LVL; -} - -const REG_EFF: readonly ThinkingEffort[] = ["minimal", "low", "medium", "high"]; -const XHI_EFF: readonly ThinkingEffort[] = ["minimal", "low", "medium", "high", "xhigh"]; - -export function getAvailableThinkingEfforts(hasXhigh: boolean = true): ReadonlyArray { - return hasXhigh ? XHI_EFF : REG_EFF; -} diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts index e86618f5e..2f43b5aeb 100644 --- a/packages/ai/src/types.ts +++ b/packages/ai/src/types.ts @@ -66,6 +66,24 @@ export type OptionsForApi = | StreamOptions | (TApi extends keyof ApiOptionsMap ? ApiOptionsMap[TApi] : never); +/** Canonical thinking transport used by a model. */ +export type ThinkingControlMode = + | "effort" + | "budget" + | "google-level" + | "anthropic-adaptive" + | "anthropic-budget-effort"; + +/** Per-model thinking capabilities used to clamp and map user-facing effort levels. */ +export interface ThinkingConfig { + /** Least intensive supported user-facing effort level. */ + minLevel: Effort; + /** Most intensive supported user-facing effort level. */ + maxLevel: Effort; + /** Provider-specific transport used to encode the selected effort. */ + mode: ThinkingControlMode; +} + export type KnownProvider = | "amazon-bedrock" | "anthropic" @@ -110,10 +128,10 @@ export type KnownProvider = | "lm-studio"; export type Provider = KnownProvider | string; -import type { ThinkingEffort, ThinkingLevel } from "./thinking"; +import type { Effort } from "./model-thinking"; /** Token budgets for each thinking level (token-based providers only) */ -export type ThinkingBudgets = { [key in ThinkingEffort]?: number }; +export type ThinkingBudgets = { [key in Effort]?: number }; export type MessageAttribution = "user" | "agent"; @@ -192,7 +210,7 @@ export interface StreamOptions { // Unified options with reasoning passed to streamSimple() and completeSimple() export interface SimpleStreamOptions extends StreamOptions { - reasoning?: ThinkingLevel; + reasoning?: Effort; /** Custom token budgets for thinking levels (token-based providers only) */ thinkingBudgets?: ThinkingBudgets; /** Cursor exec handlers for local tool execution */ @@ -476,6 +494,8 @@ export interface Model { contextPromotionTarget?: string; /** Provider-assigned priority value (lower = higher priority). */ priority?: number; + /** Canonical thinking capability metadata for this model. */ + thinking?: ThinkingConfig; /** Compatibility overrides for openai-completions API. If not set, auto-detected from baseUrl. */ compat?: TApi extends "openai-completions" ? OpenAICompat : never; } diff --git a/packages/ai/test/google-gemini-cli-3x-thinking.test.ts b/packages/ai/test/google-gemini-cli-3x-thinking.test.ts index 5d80ee578..465608759 100644 --- a/packages/ai/test/google-gemini-cli-3x-thinking.test.ts +++ b/packages/ai/test/google-gemini-cli-3x-thinking.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect, it, vi } from "bun:test"; +import { Effort } from "@oh-my-pi/pi-ai"; import { getBundledModel } from "../src/models"; import { streamSimple } from "../src/stream"; import type { Context, Model } from "../src/types"; @@ -11,7 +12,7 @@ interface GeminiCliThinkingConfig { interface CapturedRequestBody { request?: { generationConfig?: { - thinkingConfig?: GeminiCliThinkingConfig; + thinking?: GeminiCliThinkingConfig; }; }; } @@ -35,10 +36,10 @@ const context: Context = { messages: [{ role: "user", content: "hello", timestamp: Date.now() }], }; -function extractThinkingConfig(bodyText: string | undefined): GeminiCliThinkingConfig | undefined { +function extractThinking(bodyText: string | undefined): GeminiCliThinkingConfig | undefined { if (!bodyText) return undefined; const parsed = JSON.parse(bodyText) as CapturedRequestBody; - return parsed.request?.generationConfig?.thinkingConfig; + return parsed.request?.generationConfig?.thinking; } describe("google-gemini-cli Gemini 3.x thinking mapping", () => { @@ -52,7 +53,7 @@ describe("google-gemini-cli Gemini 3.x thinking mapping", () => { it("includes gemini-3.1-pro-preview in bundled google-gemini-cli models", () => { expect(getBundledModel("google-gemini-cli", "gemini-3.1-pro-preview")?.id).toBe("gemini-3.1-pro-preview"); }); - it("uses thinkingLevel for gemini-3.1-pro-preview", async () => { + it("uses thinkingLevel for gemini-3.1-pro-preview when the effort is supported", async () => { let requestBody: string | undefined; globalThis.fetch = vi.fn(async (_input, init) => { requestBody = typeof init?.body === "string" ? init.body : undefined; @@ -61,13 +62,29 @@ describe("google-gemini-cli Gemini 3.x thinking mapping", () => { const stream = streamSimple(createModel("gemini-3.1-pro-preview"), context, { apiKey: JSON.stringify({ token: "token", projectId: "proj-123" }), - reasoning: "medium", + reasoning: Effort.High, }); await stream.result(); - const thinkingConfig = extractThinkingConfig(requestBody); - expect(thinkingConfig?.thinkingLevel).toBe("HIGH"); - expect(thinkingConfig?.thinkingBudget).toBeUndefined(); + const thinking = extractThinking(requestBody); + expect(thinking?.thinkingLevel).toBe("HIGH"); + expect(thinking?.thinkingBudget).toBeUndefined(); + }); + + it("rejects unsupported gemini-3.1-pro-preview efforts instead of promoting them", () => { + let requestBody: string | undefined; + globalThis.fetch = vi.fn(async (_input, init) => { + requestBody = typeof init?.body === "string" ? init.body : undefined; + return new Response('{"error":{"message":"bad request"}}', { status: 400 }); + }) as unknown as typeof fetch; + + expect(() => + streamSimple(createModel("gemini-3.1-pro-preview"), context, { + apiKey: JSON.stringify({ token: "token", projectId: "proj-123" }), + reasoning: Effort.Medium, + }), + ).toThrow(/Supported efforts: low, high/); + expect(requestBody).toBeUndefined(); }); it("uses thinkingLevel for gemini-3.1-flash-preview", async () => { @@ -79,13 +96,13 @@ describe("google-gemini-cli Gemini 3.x thinking mapping", () => { const stream = streamSimple(createModel("gemini-3.1-flash-preview"), context, { apiKey: JSON.stringify({ token: "token", projectId: "proj-123" }), - reasoning: "medium", + reasoning: Effort.Medium, }); await stream.result(); - const thinkingConfig = extractThinkingConfig(requestBody); - expect(thinkingConfig?.thinkingLevel).toBe("MEDIUM"); - expect(thinkingConfig?.thinkingBudget).toBeUndefined(); + const thinking = extractThinking(requestBody); + expect(thinking?.thinkingLevel).toBe("MEDIUM"); + expect(thinking?.thinkingBudget).toBeUndefined(); }); it("keeps thinkingBudget for gemini-2.5-pro", async () => { @@ -97,12 +114,12 @@ describe("google-gemini-cli Gemini 3.x thinking mapping", () => { const stream = streamSimple(createModel("gemini-2.5-pro"), context, { apiKey: JSON.stringify({ token: "token", projectId: "proj-123" }), - reasoning: "medium", + reasoning: Effort.Medium, }); await stream.result(); - const thinkingConfig = extractThinkingConfig(requestBody); - expect(thinkingConfig?.thinkingLevel).toBeUndefined(); - expect(thinkingConfig?.thinkingBudget).toBeDefined(); + const thinking = extractThinking(requestBody); + expect(thinking?.thinkingLevel).toBeUndefined(); + expect(thinking?.thinkingBudget).toBeDefined(); }); }); diff --git a/packages/ai/test/model-thinking.test.ts b/packages/ai/test/model-thinking.test.ts new file mode 100644 index 000000000..28ebbd6de --- /dev/null +++ b/packages/ai/test/model-thinking.test.ts @@ -0,0 +1,278 @@ +import { describe, expect, it } from "bun:test"; +import { + applyGeneratedModelPolicies, + clampThinkingLevelForModel, + Effort, + enrichModelThinking, + linkSparkPromotionTargets, + mapEffortToAnthropicAdaptiveEffort, + mapEffortToGoogleThinkingLevel, + requireSupportedEffort, +} from "@oh-my-pi/pi-ai/model-thinking"; +import type { Api, Model, Provider } from "@oh-my-pi/pi-ai/types"; + +function createModel(overrides: { + id: string; + api: TApi; + provider: Provider; + reasoning?: boolean; +}): Model { + return enrichModelThinking({ + id: overrides.id, + name: overrides.id, + api: overrides.api, + provider: overrides.provider, + baseUrl: "", + reasoning: overrides.reasoning ?? true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 200000, + maxTokens: 32000, + }); +} + +describe("model thinking metadata", () => { + it("stores supported efforts for Codex mini in model metadata", () => { + const model = createModel({ + id: "gpt-5.1-codex-mini", + api: "openai-codex-responses", + provider: "openai-codex", + }); + + expect(model.thinking).toEqual({ + mode: "effort", + minLevel: Effort.Medium, + maxLevel: Effort.High, + }); + expect(() => requireSupportedEffort(model, Effort.Low)).toThrow(/Supported efforts: medium, high/); + expect(() => requireSupportedEffort(model, Effort.XHigh)).toThrow(/Supported efforts: medium, high/); + }); + + it("stores xhigh support directly in metadata for GPT-5.2", () => { + const model = createModel({ + id: "gpt-5.2-codex", + api: "openai-codex-responses", + provider: "openai-codex", + }); + + expect(model.thinking).toEqual({ + mode: "effort", + minLevel: Effort.Low, + maxLevel: Effort.XHigh, + }); + expect(requireSupportedEffort(model, Effort.XHigh)).toBe(Effort.XHigh); + }); + + it("maps Gemini 3 Pro only for supported levels", () => { + const model = createModel({ + id: "gemini-3-pro-preview", + api: "google-generative-ai", + provider: "google", + }); + + expect(model.thinking).toEqual({ + mode: "google-level", + minLevel: Effort.Low, + maxLevel: Effort.High, + }); + expect(mapEffortToGoogleThinkingLevel(model, Effort.Low)).toBe("LOW"); + expect(mapEffortToGoogleThinkingLevel(model, Effort.High)).toBe("HIGH"); + expect(() => mapEffortToGoogleThinkingLevel(model, Effort.Medium)).toThrow(/not supported/); + }); + + it("encodes anthropic transport mode in metadata", () => { + const opus45 = createModel({ + id: "claude-opus-4-5", + api: "anthropic-messages", + provider: "anthropic", + }); + const opus46 = createModel({ + id: "claude-opus-4.6", + api: "anthropic-messages", + provider: "anthropic", + }); + const sonnet46 = createModel({ + id: "claude-sonnet-4.6", + api: "anthropic-messages", + provider: "anthropic", + }); + + expect(opus45.thinking?.mode).toBe("anthropic-budget-effort"); + expect(opus46.thinking?.mode).toBe("anthropic-adaptive"); + expect(sonnet46.thinking?.mode).toBe("anthropic-adaptive"); + expect(opus46.thinking).toEqual({ + mode: "anthropic-adaptive", + minLevel: Effort.Minimal, + maxLevel: Effort.XHigh, + }); + expect(sonnet46.thinking).toEqual({ + mode: "anthropic-adaptive", + minLevel: Effort.Minimal, + maxLevel: Effort.High, + }); + expect(mapEffortToAnthropicAdaptiveEffort(opus46, Effort.XHigh)).toBe("max"); + expect(() => mapEffortToAnthropicAdaptiveEffort(sonnet46, Effort.XHigh)).toThrow(/not supported/); + }); +}); + +describe("generated model policies", () => { + it("refreshes thinking metadata and applies parsed catalog corrections", () => { + const models: Model[] = [ + { + id: "claude-opus-4-5", + name: "Claude Opus 4.5", + api: "anthropic-messages", + provider: "anthropic", + baseUrl: "https://example.com", + reasoning: true, + thinking: { + mode: "budget", + minLevel: Effort.High, + maxLevel: Effort.High, + }, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 1.5, cacheWrite: 18.75 }, + contextWindow: 1000000, + maxTokens: 32000, + }, + { + id: "anthropic.claude-opus-4-6-v1:0", + name: "Claude Opus 4.6", + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://example.com", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 1.5, cacheWrite: 18.75 }, + contextWindow: 1000000, + maxTokens: 32000, + }, + { + id: "gpt-5.2-codex", + name: "GPT-5.2 Codex", + api: "openai-codex-responses", + provider: "openai-codex", + baseUrl: "https://example.com", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 400000, + maxTokens: 32000, + }, + ]; + + applyGeneratedModelPolicies(models); + + expect(models[0]?.thinking).toEqual({ + mode: "anthropic-budget-effort", + minLevel: Effort.Minimal, + maxLevel: Effort.XHigh, + }); + expect(models[0]?.cost.cacheRead).toBe(0.5); + expect(models[0]?.cost.cacheWrite).toBe(6.25); + expect(models[1]?.thinking).toEqual({ + mode: "budget", + minLevel: Effort.Minimal, + maxLevel: Effort.High, + }); + expect(models[1]?.cost.cacheRead).toBe(0.5); + expect(models[1]?.cost.cacheWrite).toBe(6.25); + expect(models[1]?.contextWindow).toBe(200000); + expect(models[2]?.contextWindow).toBe(272000); + }); + + it("links spark variants to their base models", () => { + const models = [ + createModel({ + id: "gpt-5.2-codex-spark", + api: "openai-codex-responses", + provider: "openai-codex", + }), + createModel({ + id: "gpt-5.2-codex", + api: "openai-codex-responses", + provider: "openai-codex", + }), + ]; + + linkSparkPromotionTargets(models); + + expect(models[0]?.contextPromotionTarget).toBe("openai-codex/gpt-5.2-codex"); + }); +}); + +describe("model thinking runtime helpers", () => { + it("clamps from explicit metadata instead of inferring from model id", () => { + const model: Model<"openai-codex-responses"> = { + id: "custom-reasoner", + name: "Custom Reasoner", + api: "openai-codex-responses", + provider: "custom", + baseUrl: "https://example.com", + reasoning: true, + thinking: { + mode: "effort", + minLevel: Effort.Medium, + maxLevel: Effort.High, + }, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 200000, + maxTokens: 32000, + }; + + expect(clampThinkingLevelForModel(model, Effort.Minimal)).toBe(Effort.Medium); + expect(clampThinkingLevelForModel(model, Effort.XHigh)).toBe(Effort.High); + expect(clampThinkingLevelForModel(model, Effort.High)).toBe(Effort.High); + }); + + it('forces "off" for non-reasoning models', () => { + const model = createModel({ + id: "plain-model", + api: "openai-responses", + provider: "openai", + reasoning: false, + }); + + expect(clampThinkingLevelForModel(model, Effort.High)).toBeUndefined(); + }); + + it("rejects reasoning models that are missing thinking metadata at runtime", () => { + const model = { + id: "broken-reasoner", + name: "Broken Reasoner", + api: "openai-responses", + provider: "custom", + baseUrl: "https://example.com", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 200000, + maxTokens: 32000, + } as Model<"openai-responses">; + + expect(() => requireSupportedEffort(model, Effort.High)).toThrow(/missing thinking metadata/); + }); + + it("drops empty thinking metadata so presence checks stay meaningful", () => { + const model = enrichModelThinking({ + id: "plain-model", + name: "Plain Model", + api: "openai-responses", + provider: "custom", + baseUrl: "https://example.com", + reasoning: false, + thinking: { + mode: "effort", + minLevel: Effort.High, + maxLevel: Effort.Low, + }, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 200000, + maxTokens: 32000, + } satisfies Model<"openai-responses">); + + expect(model.thinking).toBeUndefined(); + }); +}); diff --git a/packages/ai/test/openai-codex-include.test.ts b/packages/ai/test/openai-codex-include.test.ts index c7a336ece..c3cd36c68 100644 --- a/packages/ai/test/openai-codex-include.test.ts +++ b/packages/ai/test/openai-codex-include.test.ts @@ -1,5 +1,22 @@ import { describe, expect, it } from "bun:test"; +import { enrichModelThinking } from "@oh-my-pi/pi-ai/model-thinking"; import { type RequestBody, transformRequestBody } from "@oh-my-pi/pi-ai/providers/openai-codex/request-transformer"; +import type { Model } from "@oh-my-pi/pi-ai/types"; + +function createCodexModel(id: string): Model<"openai-codex-responses"> { + return enrichModelThinking({ + id, + name: id, + api: "openai-codex-responses", + provider: "openai-codex", + baseUrl: "https://api.openai.com/v1", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 272000, + maxTokens: 128000, + }); +} describe("openai-codex include handling", () => { it("always includes reasoning.encrypted_content when caller include is custom", async () => { @@ -7,7 +24,7 @@ describe("openai-codex include handling", () => { model: "gpt-5.1-codex", }; - const transformed = await transformRequestBody(body, { include: ["foo"] }); + const transformed = await transformRequestBody(body, createCodexModel(body.model), { include: ["foo"] }); expect(transformed.include).toEqual(["foo", "reasoning.encrypted_content"]); }); @@ -16,7 +33,7 @@ describe("openai-codex include handling", () => { model: "gpt-5.1-codex", }; - const transformed = await transformRequestBody(body, { + const transformed = await transformRequestBody(body, createCodexModel(body.model), { include: ["foo", "reasoning.encrypted_content"], }); expect(transformed.include).toEqual(["foo", "reasoning.encrypted_content"]); diff --git a/packages/ai/test/openai-codex-stream.test.ts b/packages/ai/test/openai-codex-stream.test.ts index 0e4dca581..46b565ae0 100644 --- a/packages/ai/test/openai-codex-stream.test.ts +++ b/packages/ai/test/openai-codex-stream.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect, it, vi } from "bun:test"; +import { enrichModelThinking } from "@oh-my-pi/pi-ai/model-thinking"; import { getOpenAICodexTransportDetails, prewarmOpenAICodexResponses, @@ -484,7 +485,7 @@ describe("openai-codex streaming", () => { await streamResult.result(); }); - it("clamps gpt-5.3-codex minimal reasoning effort to low", async () => { + it("rejects gpt-5.3-codex minimal reasoning effort instead of clamping", async () => { const tempDir = TempDir.createSync("@pi-codex-stream-"); setAgentDir(tempDir.path()); @@ -555,7 +556,7 @@ describe("openai-codex streaming", () => { global.fetch = fetchMock as unknown as typeof fetch; - const model: Model<"openai-codex-responses"> = { + const model = enrichModelThinking({ id: "gpt-5.3-codex", name: "GPT-5.3 Codex", api: "openai-codex-responses", @@ -566,7 +567,7 @@ describe("openai-codex streaming", () => { cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 400000, maxTokens: 128000, - }; + }); const context: Context = { systemPrompt: "You are a helpful assistant.", @@ -577,7 +578,9 @@ describe("openai-codex streaming", () => { apiKey: token, reasoning: "minimal", }); - await streamResult.result(); + const response = await streamResult.result(); + expect(response.stopReason).toBe("error"); + expect(response.errorMessage).toContain("Supported efforts: low, medium, high, xhigh"); }); it("does not set conversation_id/session_id headers when sessionId is not provided", async () => { diff --git a/packages/ai/test/openai-codex.test.ts b/packages/ai/test/openai-codex.test.ts index b5389bdc5..092a2b990 100644 --- a/packages/ai/test/openai-codex.test.ts +++ b/packages/ai/test/openai-codex.test.ts @@ -1,10 +1,27 @@ import { describe, expect, it } from "bun:test"; +import { enrichModelThinking } from "@oh-my-pi/pi-ai/model-thinking"; import { type RequestBody, transformRequestBody } from "@oh-my-pi/pi-ai/providers/openai-codex/request-transformer"; import { parseCodexError } from "@oh-my-pi/pi-ai/providers/openai-codex/response-handler"; +import type { Model } from "@oh-my-pi/pi-ai/types"; const DEFAULT_PROMPT_PREFIX = "You are an expert coding assistant. You help users with coding tasks by reading files, executing commands"; +function createCodexModel(id: string): Model<"openai-codex-responses"> { + return enrichModelThinking({ + id, + name: id, + api: "openai-codex-responses", + provider: "openai-codex", + baseUrl: "https://api.openai.com/v1", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 272000, + maxTokens: 128000, + }); +} + describe("openai-codex request transformer", () => { it("filters item_reference and strips ids", async () => { const body: RequestBody = { @@ -28,7 +45,7 @@ describe("openai-codex request transformer", () => { tools: [{ type: "function", name: "tool", description: "", parameters: {} }], }; - const transformed = await transformRequestBody(body, {}); + const transformed = await transformRequestBody(body, createCodexModel(body.model), {}); expect(transformed.store).toBe(false); expect(transformed.stream).toBe(true); @@ -47,21 +64,24 @@ describe("openai-codex request transformer", () => { }); }); -describe("openai-codex reasoning effort clamping", () => { - it("clamps gpt-5.1 xhigh to high", async () => { +describe("openai-codex reasoning effort validation", () => { + it("rejects gpt-5.1 xhigh when metadata does not list it", async () => { const body: RequestBody = { model: "gpt-5.1", input: [] }; - const transformed = await transformRequestBody(body, { reasoningEffort: "xhigh" }); - expect(transformed.reasoning?.effort).toBe("high"); + await expect( + transformRequestBody(body, createCodexModel(body.model), { reasoningEffort: "xhigh" }), + ).rejects.toThrow(/Supported efforts: minimal, low, medium, high/); }); - it("clamps gpt-5.1-codex-mini to medium/high only", async () => { + it("rejects unsupported Codex mini efforts instead of clamping", async () => { const body: RequestBody = { model: "gpt-5.1-codex-mini", input: [] }; - const low = await transformRequestBody({ ...body }, { reasoningEffort: "low" }); - expect(low.reasoning?.effort).toBe("medium"); + await expect( + transformRequestBody({ ...body }, createCodexModel(body.model), { reasoningEffort: "low" }), + ).rejects.toThrow(/Supported efforts: medium, high/); - const xhigh = await transformRequestBody({ ...body }, { reasoningEffort: "xhigh" }); - expect(xhigh.reasoning?.effort).toBe("high"); + await expect( + transformRequestBody({ ...body }, createCodexModel(body.model), { reasoningEffort: "xhigh" }), + ).rejects.toThrow(/Supported efforts: medium, high/); }); }); diff --git a/packages/ai/test/stream.test.ts b/packages/ai/test/stream.test.ts index fd5180ca6..d576724de 100644 --- a/packages/ai/test/stream.test.ts +++ b/packages/ai/test/stream.test.ts @@ -2,6 +2,7 @@ import { afterAll, beforeAll, describe, expect, it } from "bun:test"; import { type ChildProcess, execSync, spawn } from "node:child_process"; import * as fs from "node:fs/promises"; import * as path from "node:path"; +import { Effort } from "@oh-my-pi/pi-ai"; import { getBundledModel } from "@oh-my-pi/pi-ai/models"; import { complete, stream } from "@oh-my-pi/pi-ai/stream"; import type { Api, Context, ImageContent, Model, OptionsForApi, Tool, ToolResultMessage } from "@oh-my-pi/pi-ai/types"; @@ -534,7 +535,7 @@ describe("Generate E2E Tests", () => { it( "should handle thinking", async () => { - await handleThinking(llm, { reasoning: "high" }); + await handleThinking(llm, { reasoning: Effort.High }); }, { retry: 2 }, ); @@ -542,7 +543,7 @@ describe("Generate E2E Tests", () => { it( "should handle multi-turn with thinking and tools", async () => { - await multiTurn(llm, { reasoning: "high" }); + await multiTurn(llm, { reasoning: Effort.High }); }, { retry: 3 }, ); @@ -658,7 +659,7 @@ describe("Generate E2E Tests", () => { it( "should handle thinking mode", async () => { - await handleThinking(llm, { reasoning: "medium" }); + await handleThinking(llm, { reasoning: Effort.Medium }); }, { retry: 3 }, ); @@ -666,7 +667,7 @@ describe("Generate E2E Tests", () => { it( "should handle multi-turn with thinking and tools", async () => { - await multiTurn(llm, { reasoning: "medium" }); + await multiTurn(llm, { reasoning: Effort.Medium }); }, { retry: 3 }, ); @@ -702,7 +703,7 @@ describe("Generate E2E Tests", () => { it( "should handle thinking mode", async () => { - await handleThinking(llm, { reasoning: "medium" }); + await handleThinking(llm, { reasoning: Effort.Medium }); }, { retry: 3 }, ); @@ -710,7 +711,7 @@ describe("Generate E2E Tests", () => { it( "should handle multi-turn with thinking and tools", async () => { - await multiTurn(llm, { reasoning: "medium" }); + await multiTurn(llm, { reasoning: Effort.Medium }); }, { retry: 3 }, ); @@ -746,7 +747,7 @@ describe("Generate E2E Tests", () => { it( "should handle thinking mode", async () => { - await handleThinking(llm, { reasoning: "medium" }); + await handleThinking(llm, { reasoning: Effort.Medium }); }, { retry: 3 }, ); @@ -754,7 +755,7 @@ describe("Generate E2E Tests", () => { it( "should handle multi-turn with thinking and tools", async () => { - await multiTurn(llm, { reasoning: "medium" }); + await multiTurn(llm, { reasoning: Effort.Medium }); }, { retry: 3 }, ); @@ -790,7 +791,7 @@ describe("Generate E2E Tests", () => { it( "should handle thinking mode", async () => { - await handleThinking(llm, { reasoning: "medium" }); + await handleThinking(llm, { reasoning: Effort.Medium }); }, { retry: 3 }, ); @@ -798,7 +799,7 @@ describe("Generate E2E Tests", () => { it( "should handle multi-turn with thinking and tools", async () => { - await multiTurn(llm, { reasoning: "medium" }); + await multiTurn(llm, { reasoning: Effort.Medium }); }, { retry: 2 }, ); @@ -842,7 +843,7 @@ describe("Generate E2E Tests", () => { it.skip( "should handle thinking mode", async () => { - await handleThinking(llm, { reasoning: "medium" }); + await handleThinking(llm, { reasoning: Effort.Medium }); }, { retry: 3 }, ); @@ -850,7 +851,7 @@ describe("Generate E2E Tests", () => { it( "should handle multi-turn with thinking and tools", async () => { - await multiTurn(llm, { reasoning: "medium" }); + await multiTurn(llm, { reasoning: Effort.Medium }); }, { retry: 3 }, ); @@ -886,7 +887,7 @@ describe("Generate E2E Tests", () => { it( "should handle thinking mode", async () => { - await handleThinking(llm, { reasoning: "medium" }); + await handleThinking(llm, { reasoning: Effort.Medium }); }, { retry: 3 }, ); @@ -894,7 +895,7 @@ describe("Generate E2E Tests", () => { it( "should handle multi-turn with thinking and tools", async () => { - await multiTurn(llm, { reasoning: "medium" }); + await multiTurn(llm, { reasoning: Effort.Medium }); }, { retry: 3 }, ); @@ -950,7 +951,7 @@ describe("Generate E2E Tests", () => { it( "should handle multi-turn with thinking and tools", async () => { - await multiTurn(llm, { reasoning: "medium" }); + await multiTurn(llm, { reasoning: Effort.Medium }); }, { retry: 3 }, ); @@ -1076,7 +1077,7 @@ describe("Generate E2E Tests", () => { "should handle thinking", async () => { const thinkingModel = getBundledModel("github-copilot", "gpt-5-mini"); - await handleThinking(thinkingModel, { apiKey: githubCopilotToken, reasoning: "high" }); + await handleThinking(thinkingModel, { apiKey: githubCopilotToken, reasoning: Effort.High }); }, { retry: 2 }, ); @@ -1085,7 +1086,7 @@ describe("Generate E2E Tests", () => { "should handle multi-turn with thinking and tools", async () => { const thinkingModel = getBundledModel("github-copilot", "gpt-5-mini"); - await multiTurn(thinkingModel, { apiKey: githubCopilotToken, reasoning: "high" }); + await multiTurn(thinkingModel, { apiKey: githubCopilotToken, reasoning: Effort.High }); }, { retry: 3 }, ); @@ -1318,7 +1319,7 @@ describe("Generate E2E Tests", () => { it.skipIf(!openaiCodexToken)( "should handle thinking", async () => { - await handleThinking(llm, { apiKey: openaiCodexToken, reasoning: "high" }); + await handleThinking(llm, { apiKey: openaiCodexToken, reasoning: Effort.High }); }, { retry: 3 }, ); @@ -1361,7 +1362,7 @@ describe("Generate E2E Tests", () => { tools: [calculatorTool], }, { - reasoning: "xhigh", + reasoning: Effort.XHigh, interleavedThinking: true, onPayload: payload => { capturedPayload = payload; @@ -1490,7 +1491,7 @@ describe("Generate E2E Tests", () => { "should handle thinking mode", async () => { if (!llm) return; - await handleThinking(llm, { apiKey: "test", reasoning: "medium" }); + await handleThinking(llm, { apiKey: "test", reasoning: Effort.Medium }); }, { retry: 3 }, ); @@ -1499,7 +1500,7 @@ describe("Generate E2E Tests", () => { "should handle multi-turn with thinking and tools", async () => { if (!llm) return; - await multiTurn(llm, { apiKey: "test", reasoning: "medium" }); + await multiTurn(llm, { apiKey: "test", reasoning: Effort.Medium }); }, { retry: 3 }, ); diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index f59428944..b45ea78f4 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,8 +1,23 @@ # Changelog ## [Unreleased] + +### Breaking Changes + +- Changed `ThinkingLevel` type to be imported from `@oh-my-pi/pi-agent-core` instead of `@oh-my-pi/pi-ai` +- Changed thinking level representation from string literals to `Effort` enum values (e.g., `Effort.High` instead of `"high"`) +- Changed `getThinkingLevel()` return type to `ThinkingLevel | undefined` to support models without thinking support +- Changed model `reasoning` property to `thinking` property with `ThinkingConfig` for explicit effort level configuration +- Changed `thinkingLevel` in session context to be optional (`ThinkingLevel | undefined`) instead of always present + ### Added +- Added `thinking.ts` module with `getThinkingLevelMetadata()` and `resolveThinkingLevelForModel()` utilities for thinking level handling +- Added `ThinkingConfig` support to model definitions for specifying supported thinking effort levels per model +- Added `enrichModelThinking()` function to apply thinking configuration to models during registry initialization +- Added `clampThinkingLevelForModel()` function to constrain thinking levels to model-supported ranges +- Added `getSupportedEfforts()` function to retrieve available thinking efforts for a model +- Added `Effort` enum import from `@oh-my-pi/pi-ai` for type-safe thinking level representation - Added `/fast` slash command to toggle OpenAI service tier priority mode for faster response processing - Added `serviceTier` setting to control OpenAI processing priority (none, auto, default, flex, scale, priority) - Added `compaction.remoteEnabled` setting to control use of remote compaction endpoints @@ -13,6 +28,14 @@ ### Changed +- Changed thinking level parsing to use `parseEffort()` from local thinking module instead of `parseThinkingLevel()` from pi-ai +- Changed model list display to show supported thinking efforts (e.g., "low,medium,high") instead of yes/no reasoning indicator +- Changed footer and status line to check `model.thinking` instead of `model.reasoning` for thinking level display +- Changed thinking selector to work with `Effort` type instead of `ThinkingLevel` for available levels +- Changed model resolver to return `undefined` for thinking level instead of `"off"` when no thinking is specified +- Changed compaction reasoning parameters to use `Effort` enum values instead of string literals +- Changed RPC types to use `Effort` for cycling thinking levels and `ThinkingLevel | undefined` for session state +- Changed theme thinking border color function to accept both `ThinkingLevel` and `Effort` types - Changed context usage coloring in footer and status line to use token-aware thresholds instead of fixed percentages - Changed compaction to preserve OpenAI remote compaction state and encrypted reasoning across sessions - Changed compaction to skip emitting kept messages when using OpenAI remote compaction with preserved history @@ -22,6 +45,8 @@ ### Fixed +- Fixed thinking level display logic in main.ts to correctly check for undefined instead of "off" +- Fixed model registry to preserve explicit thinking configuration on runtime-registered models - Fixed usage limit reset time calculation to use absolute `resetsAt` timestamps instead of deprecated `resetInMs` field - Fixed compaction summary message creation to no longer be automatically added to chat during compaction (now handled by session manager) diff --git a/packages/coding-agent/examples/sdk/02-custom-model.ts b/packages/coding-agent/examples/sdk/02-custom-model.ts index 04e308b96..77f267517 100644 --- a/packages/coding-agent/examples/sdk/02-custom-model.ts +++ b/packages/coding-agent/examples/sdk/02-custom-model.ts @@ -3,6 +3,7 @@ * * Shows how to select a specific model and thinking level. */ +import { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import { getModel } from "@oh-my-pi/pi-ai"; import { createAgentSession, discoverAuthStorage, discoverModels } from "@oh-my-pi/pi-coding-agent"; @@ -32,7 +33,7 @@ console.log( if (available.length > 0) { const { session } = await createAgentSession({ model: available[0], - thinkingLevel: "medium", // off, low, medium, high + thinkingLevel: ThinkingLevel.Medium, // off, low, medium, high authStorage, modelRegistry, }); diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index 5788e6252..b146cfb4a 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -1,9 +1,10 @@ /** * CLI argument parsing and help display */ -import { getAvailableThinkingLevels, parseThinkingLevel, type ThinkingLevel } from "@oh-my-pi/pi-ai"; +import { type Effort, THINKING_EFFORTS } from "@oh-my-pi/pi-ai"; import { APP_NAME, CONFIG_DIR_NAME, logger } from "@oh-my-pi/pi-utils"; import chalk from "chalk"; +import { parseEffort } from "../thinking"; import { BUILTIN_TOOLS } from "../tools"; export type Mode = "text" | "json" | "rpc"; @@ -19,7 +20,7 @@ export interface Args { apiKey?: string; systemPrompt?: string; appendSystemPrompt?: string; - thinking?: ThinkingLevel; + thinking?: Effort; continue?: boolean; resume?: string | true; help?: boolean; @@ -122,13 +123,13 @@ export function parseArgs(args: string[], extensionFlags?: Map, override: ModelOverride): Model, override: ModelOverride): Model; + } as Model); } /** @@ -855,19 +884,21 @@ export class ModelRegistry { for (const item of models) { const id = item.model || item.name; if (!id) continue; - discovered.push({ - id, - name: item.name || id, - api: providerConfig.api, - provider: providerConfig.provider, - baseUrl: `${endpoint}/v1`, - reasoning: false, - input: ["text"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 128000, - maxTokens: 8192, - headers: providerConfig.headers, - }); + discovered.push( + enrichModelThinking({ + id, + name: item.name || id, + api: providerConfig.api, + provider: providerConfig.provider, + baseUrl: `${endpoint}/v1`, + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 8192, + headers: providerConfig.headers, + }), + ); } return this.#applyProviderModelOverrides(providerConfig.provider, discovered); } catch (error) { @@ -909,24 +940,26 @@ export class ModelRegistry { for (const item of models) { const id = item.id; if (!id) continue; - discovered.push({ - id, - name: id, - api: providerConfig.api, - provider: providerConfig.provider, - baseUrl, - reasoning: false, - input: ["text"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 128000, - maxTokens: 8192, - headers, - compat: { - supportsStore: false, - supportsDeveloperRole: false, - supportsReasoningEffort: false, - }, - }); + discovered.push( + enrichModelThinking({ + id, + name: id, + api: providerConfig.api, + provider: providerConfig.provider, + baseUrl, + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 8192, + headers, + compat: { + supportsStore: false, + supportsDeveloperRole: false, + supportsReasoningEffort: false, + }, + }), + ); } return this.#applyProviderModelOverrides(providerConfig.provider, discovered); } catch (error) { @@ -1008,7 +1041,7 @@ export class ModelRegistry { providerConfig.headers, providerConfig.apiKey, providerConfig.authHeader, - modelDef, + modelDef as CustomModelDefinitionLike, { useDefaults: true }, ); if (!model) continue; @@ -1161,7 +1194,7 @@ export class ModelRegistry { config.headers, config.apiKey, config.authHeader, - modelDef, + modelDef as CustomModelDefinitionLike, { useDefaults: false }, ); if (!model) { @@ -1218,6 +1251,7 @@ export interface ProviderConfigInput { api?: Api; baseUrl?: string; reasoning: boolean; + thinking?: ThinkingConfig; input: ("text" | "image")[]; cost: { input: number; output: number; cacheRead: number; cacheWrite: number }; contextWindow: number; diff --git a/packages/coding-agent/src/config/model-resolver.ts b/packages/coding-agent/src/config/model-resolver.ts index 8c776d4f8..a0aee56bd 100644 --- a/packages/coding-agent/src/config/model-resolver.ts +++ b/packages/coding-agent/src/config/model-resolver.ts @@ -1,17 +1,20 @@ /** * Model resolution, scoping, and initial selection */ + +import { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import { type Api, + clampThinkingLevelForModel, DEFAULT_MODEL_PER_PROVIDER, + type Effort, type KnownProvider, type Model, modelsAreEqual, - parseThinkingLevel, - type ThinkingLevel, } from "@oh-my-pi/pi-ai"; import chalk from "chalk"; import MODEL_PRIO from "../priority.json" with { type: "json" }; +import { parseThinkingLevel, resolveThinkingLevelForModel } from "../thinking"; import { fuzzyMatch } from "../utils/fuzzy"; import { MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "./model-registry"; import type { Settings } from "./settings"; @@ -377,7 +380,14 @@ export function resolveModelRoleValue( options?.matchPreferences, ); - return { model, thinkingLevel, explicitThinkingLevel, warning }; + return { + model, + thinkingLevel: explicitThinkingLevel + ? (resolveThinkingLevelForModel(model, thinkingLevel) ?? thinkingLevel) + : thinkingLevel, + explicitThinkingLevel, + warning, + }; } export function extractExplicitThinkingSelector( @@ -393,10 +403,10 @@ export function extractExplicitThinkingSelector( while (!visited.has(current)) { visited.add(current); const lastColonIndex = current.lastIndexOf(":"); - const hasThinkingSuffix = - lastColonIndex > PREFIX_MODEL_ROLE.length && parseThinkingLevel(current.slice(lastColonIndex + 1)); - if (hasThinkingSuffix) { - return current.slice(lastColonIndex + 1) as ThinkingLevel; + const thinkingSelector = + lastColonIndex > PREFIX_MODEL_ROLE.length ? parseThinkingLevel(current.slice(lastColonIndex + 1)) : undefined; + if (thinkingSelector) { + return thinkingSelector; } const expanded = expandRoleAlias(current, settings).trim(); if (!expanded || expanded === current) break; @@ -520,7 +530,13 @@ export async function resolveModelScope( for (const model of matchingModels) { if (!scopedModels.find(sm => modelsAreEqual(sm.model, model))) { - scopedModels.push({ model, thinkingLevel, explicitThinkingLevel }); + scopedModels.push({ + model, + thinkingLevel: explicitThinkingLevel + ? (resolveThinkingLevelForModel(model, thinkingLevel) ?? thinkingLevel) + : thinkingLevel, + explicitThinkingLevel, + }); } } continue; @@ -543,7 +559,13 @@ export async function resolveModelScope( // Avoid duplicates if (!scopedModels.find(sm => modelsAreEqual(sm.model, model))) { - scopedModels.push({ model, thinkingLevel, explicitThinkingLevel }); + scopedModels.push({ + model, + thinkingLevel: explicitThinkingLevel + ? (resolveThinkingLevelForModel(model, thinkingLevel) ?? thinkingLevel) + : thinkingLevel, + explicitThinkingLevel, + }); } } @@ -644,7 +666,7 @@ export function resolveCliModel(options: { export interface InitialModelResult { model: Model | undefined; - thinkingLevel: ThinkingLevel; + thinkingLevel?: ThinkingLevel; fallbackMessage: string | undefined; } @@ -663,7 +685,7 @@ export async function findInitialModel(options: { isContinuing: boolean; defaultProvider?: string; defaultModelId?: string; - defaultThinkingSelector?: ThinkingLevel; + defaultThinkingSelector?: Effort; modelRegistry: ModelRegistry; }): Promise { const { @@ -678,7 +700,7 @@ export async function findInitialModel(options: { } = options; let model: Model | undefined; - let thinkingLevel: ThinkingLevel = "off"; + let thinkingLevel: Effort | undefined; // 1. CLI args take priority if (cliProvider && cliModel) { @@ -687,16 +709,22 @@ export async function findInitialModel(options: { console.error(chalk.red(`Model ${cliProvider}/${cliModel} not found`)); process.exit(1); } - return { model: found, thinkingLevel: "off", fallbackMessage: undefined }; + return { model: found, thinkingLevel: undefined, fallbackMessage: undefined }; } // 2. Use first model from scoped models (skip if continuing/resuming) if (scopedModels.length > 0 && !isContinuing) { const scoped = scopedModels[0]; - const scopedThinkingSelector = scoped.thinkingLevel ?? defaultThinkingSelector ?? "off"; + const scopedThinkingSelector = + scoped.thinkingLevel === ThinkingLevel.Inherit + ? defaultThinkingSelector + : (scoped.thinkingLevel ?? defaultThinkingSelector); return { model: scoped.model, - thinkingLevel: scopedThinkingSelector, + thinkingLevel: + scopedThinkingSelector === ThinkingLevel.Off + ? ThinkingLevel.Off + : clampThinkingLevelForModel(scoped.model, scopedThinkingSelector), fallbackMessage: undefined, }; } @@ -706,9 +734,7 @@ export async function findInitialModel(options: { const found = modelRegistry.find(defaultProvider, defaultModelId); if (found) { model = found; - if (defaultThinkingSelector) { - thinkingLevel = defaultThinkingSelector; - } + thinkingLevel = clampThinkingLevelForModel(found, defaultThinkingSelector); return { model, thinkingLevel, fallbackMessage: undefined }; } } @@ -722,16 +748,16 @@ export async function findInitialModel(options: { const defaultId = defaultModelPerProvider[provider]; const match = availableModels.find(m => m.provider === provider && m.id === defaultId); if (match) { - return { model: match, thinkingLevel: "off", fallbackMessage: undefined }; + return { model: match, thinkingLevel: undefined, fallbackMessage: undefined }; } } // If no default found, use first available - return { model: availableModels[0], thinkingLevel: "off", fallbackMessage: undefined }; + return { model: availableModels[0], thinkingLevel: undefined, fallbackMessage: undefined }; } // 5. No model found - return { model: undefined, thinkingLevel: "off", fallbackMessage: undefined }; + return { model: undefined, thinkingLevel: undefined, fallbackMessage: undefined }; } /** diff --git a/packages/coding-agent/src/config/settings-schema.ts b/packages/coding-agent/src/config/settings-schema.ts index bfb673eb9..a8bc928d0 100644 --- a/packages/coding-agent/src/config/settings-schema.ts +++ b/packages/coding-agent/src/config/settings-schema.ts @@ -1,4 +1,4 @@ -import { getAvailableThinkingLevels } from "@oh-my-pi/pi-ai"; +import { THINKING_EFFORTS } from "@oh-my-pi/pi-ai"; /** Unified settings schema - single source of truth for all settings. * Unified settings schema - single source of truth for all settings. @@ -192,7 +192,7 @@ export const SETTINGS_SCHEMA = { }, defaultThinkingLevel: { type: "enum", - values: getAvailableThinkingLevels(), + values: THINKING_EFFORTS, default: "high", ui: { tab: "agent", diff --git a/packages/coding-agent/src/discovery/helpers.ts b/packages/coding-agent/src/discovery/helpers.ts index 46105066c..2d0fdc429 100644 --- a/packages/coding-agent/src/discovery/helpers.ts +++ b/packages/coding-agent/src/discovery/helpers.ts @@ -1,13 +1,13 @@ import * as fs from "node:fs"; import * as path from "node:path"; -import type { ThinkingLevel } from "@oh-my-pi/pi-ai"; -import { parseThinkingLevel } from "@oh-my-pi/pi-ai"; +import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import { FileType, glob } from "@oh-my-pi/pi-natives"; import { CONFIG_DIR_NAME, tryParseJson } from "@oh-my-pi/pi-utils"; import { readFile } from "../capability/fs"; import { parseRuleConditionAndScope, type Rule, type RuleFrontmatter } from "../capability/rule"; import type { Skill, SkillFrontmatter } from "../capability/skill"; import type { LoadContext, LoadResult, SourceMeta } from "../capability/types"; +import { parseThinkingLevel } from "../thinking"; import { parseFrontmatter } from "../utils/frontmatter"; /** diff --git a/packages/coding-agent/src/extensibility/extensions/loader.ts b/packages/coding-agent/src/extensibility/extensions/loader.ts index c3ba5b096..7b13ff214 100644 --- a/packages/coding-agent/src/extensibility/extensions/loader.ts +++ b/packages/coding-agent/src/extensibility/extensions/loader.ts @@ -4,7 +4,8 @@ import type * as fs1 from "node:fs"; import * as fs from "node:fs/promises"; import * as path from "node:path"; -import type { ImageContent, Model, TextContent, ThinkingLevel } from "@oh-my-pi/pi-ai"; +import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; +import type { ImageContent, Model, TextContent } from "@oh-my-pi/pi-ai"; import * as piCodingAgent from "@oh-my-pi/pi-coding-agent"; import type { KeyId } from "@oh-my-pi/pi-tui"; import { hasFsCode, isEacces, isEnoent, logger } from "@oh-my-pi/pi-utils"; @@ -214,7 +215,7 @@ class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime { return this.runtime.setModel(model); } - getThinkingLevel(): ThinkingLevel { + getThinkingLevel(): ThinkingLevel | undefined { return this.runtime.getThinkingLevel(); } diff --git a/packages/coding-agent/src/extensibility/extensions/types.ts b/packages/coding-agent/src/extensibility/extensions/types.ts index 1a6f0153e..275f21df1 100644 --- a/packages/coding-agent/src/extensibility/extensions/types.ts +++ b/packages/coding-agent/src/extensibility/extensions/types.ts @@ -7,7 +7,7 @@ * - Register commands, keyboard shortcuts, and CLI flags * - Interact with the user via UI primitives */ -import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core"; +import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback, ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import type { Api, AssistantMessageEvent, @@ -19,7 +19,6 @@ import type { OAuthLoginCallbacks, SimpleStreamOptions, TextContent, - ThinkingLevel, ToolResultMessage, } from "@oh-my-pi/pi-ai"; import type * as piCodingAgent from "@oh-my-pi/pi-coding-agent"; @@ -1058,9 +1057,9 @@ export interface ExtensionAPI { setModel(model: Model): Promise; /** Get current thinking level. */ - getThinkingLevel(): ThinkingLevel; + getThinkingLevel(): ThinkingLevel | undefined; - /** Set thinking level (clamped to model capabilities). */ + /** Set thinking level for the current session. */ setThinkingLevel(level: ThinkingLevel): void; // ========================================================================= @@ -1086,11 +1085,11 @@ export interface ExtensionAPI { * id: "claude-sonnet-4@20250514", * name: "Claude Sonnet 4 (Vertex)", * reasoning: true, + * thinking: { mode: "anthropic-adaptive", minLevel: "minimal", maxLevel: "high" }, * input: ["text", "image"], * cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }, * contextWindow: 200000, * maxTokens: 64000, - * } * ] * }); * @@ -1149,8 +1148,10 @@ export interface ProviderModelConfig { name: string; /** API type override for this model. */ api?: Api; - /** Whether the model supports extended thinking. */ + /** Whether the model supports extended thinking at all. */ reasoning: boolean; + /** Optional canonical thinking capability metadata for per-model effort support. */ + thinking?: Model["thinking"]; /** Supported input types. */ input: ("text" | "image")[]; /** Cost per million tokens. */ @@ -1218,7 +1219,7 @@ export type SetActiveToolsHandler = (toolNames: string[]) => Promise; export type SetModelHandler = (model: Model) => Promise; -export type GetThinkingLevelHandler = () => ThinkingLevel; +export type GetThinkingLevelHandler = () => ThinkingLevel | undefined; export type SetThinkingLevelHandler = (level: ThinkingLevel, persist?: boolean) => void; diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 6e1403a70..d132f5644 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -10,7 +10,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { createInterface } from "node:readline/promises"; -import { type ImageContent, supportsXhigh } from "@oh-my-pi/pi-ai"; +import type { ImageContent } from "@oh-my-pi/pi-ai"; import { $env, getProjectDir, logger, postmortem, setProjectDir, VERSION } from "@oh-my-pi/pi-utils"; import chalk from "chalk"; import type { Args } from "./cli/args"; @@ -334,11 +334,10 @@ async function buildSessionOptions( scopedModels: ScopedModel[], sessionManager: SessionManager | undefined, modelRegistry: ModelRegistry, -): Promise<{ options: CreateAgentSessionOptions; cliThinkingFromModel: boolean }> { +): Promise<{ options: CreateAgentSessionOptions }> { const options: CreateAgentSessionOptions = { cwd: parsed.cwd ?? getProjectDir(), }; - let cliThinkingFromModel = false; // Auto-discover SYSTEM.md if no CLI system prompt provided const systemPromptSource = parsed.systemPrompt ?? discoverSystemPromptFile(); @@ -380,7 +379,6 @@ async function buildSessionOptions( settings.overrideModelRoles({ default: `${resolved.model.provider}/${resolved.model.id}` }); if (!parsed.thinking && resolved.thinkingLevel) { options.thinkingLevel = resolved.thinkingLevel; - cliThinkingFromModel = true; } } } else if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) { @@ -483,7 +481,7 @@ async function buildSessionOptions( options.additionalExtensionPaths = []; } - return { options, cliThinkingFromModel }; + return { options }; } export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise { @@ -618,7 +616,7 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise + const { options: sessionOptions } = await logger.timeAsync("buildSessionOptions", () => buildSessionOptions(parsedArgs, scopedModels, sessionManager, modelRegistry), ); sessionOptions.authStorage = authStorage; @@ -692,21 +690,6 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise and --model :. - const cliThinkingOverride = parsedArgs.thinking !== undefined || cliThinkingFromModel; - if (session.model && cliThinkingOverride) { - let effectiveThinking = session.thinkingLevel; - if (!session.model.reasoning) { - effectiveThinking = "off"; - } else if (effectiveThinking === "xhigh" && !supportsXhigh(session.model)) { - effectiveThinking = "high"; - } - if (effectiveThinking !== session.thinkingLevel) { - session.setThinkingLevel(effectiveThinking); - } - } - if (mode === "rpc") { await runRpcMode(session); } else if (isInteractive) { @@ -717,7 +700,7 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise 0) { const modelList = scopedModelsForDisplay .map(scopedModel => { - const thinkingStr = scopedModel.thinkingLevel !== "off" ? `:${scopedModel.thinkingLevel}` : ""; + const thinkingStr = !scopedModel.thinkingLevel ? `:${scopedModel.thinkingLevel}` : ""; return `${scopedModel.model.id}${thinkingStr}`; }) .join(", "); diff --git a/packages/coding-agent/src/memories/index.ts b/packages/coding-agent/src/memories/index.ts index b6b95a628..ece865c06 100644 --- a/packages/coding-agent/src/memories/index.ts +++ b/packages/coding-agent/src/memories/index.ts @@ -3,7 +3,7 @@ import type * as fsNode from "node:fs"; import * as fs from "node:fs/promises"; import * as path from "node:path"; import type { AgentMessage } from "@oh-my-pi/pi-agent-core"; -import { completeSimple, type Model } from "@oh-my-pi/pi-ai"; +import { completeSimple, Effort, type Model } from "@oh-my-pi/pi-ai"; import { getAgentDbPath, logger, parseJsonlLenient } from "@oh-my-pi/pi-utils"; import type { ModelRegistry } from "../config/model-registry"; import { parseModelString } from "../config/model-resolver"; @@ -583,7 +583,11 @@ async function runStage1Job(options: { systemPrompt: stageOneSystemTemplate, messages: [{ role: "user", content: [{ type: "text", text: inputPrompt }], timestamp: Date.now() }], }, - { apiKey, maxTokens: Math.max(1024, Math.min(4096, Math.floor(modelMaxTokens * 0.2))), reasoning: "low" }, + { + apiKey, + maxTokens: Math.max(1024, Math.min(4096, Math.floor(modelMaxTokens * 0.2))), + reasoning: Effort.Low, + }, ); if (response.stopReason === "error") { @@ -709,7 +713,7 @@ async function runConsolidationModel(options: { memoryRoot: string; model: Model { messages: [{ role: "user", content: [{ type: "text", text: input }], timestamp: Date.now() }], }, - { apiKey, maxTokens: 8192, reasoning: "medium" }, + { apiKey, maxTokens: 8192, reasoning: Effort.Medium }, ); if (response.stopReason === "error") { throw new Error(response.errorMessage || "phase2 model error"); diff --git a/packages/coding-agent/src/modes/components/footer.ts b/packages/coding-agent/src/modes/components/footer.ts index 49f1651d1..85e702b63 100644 --- a/packages/coding-agent/src/modes/components/footer.ts +++ b/packages/coding-agent/src/modes/components/footer.ts @@ -1,4 +1,5 @@ import * as fs from "node:fs"; +import { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import { type Component, padding, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui"; import { formatNumber, getProjectDir } from "@oh-my-pi/pi-utils"; import { theme } from "../../modes/theme/theme"; @@ -212,11 +213,11 @@ export class FooterComponent implements Component { // Add model name on the right side, plus thinking level if model supports it const modelName = state.model?.id || "no-model"; - // Add thinking level hint if model supports reasoning and thinking is enabled + // Add thinking level hint when the current model advertises supported efforts let rightSide = modelName; - if (state.model?.reasoning) { - const thinkingLevel = state.thinkingLevel || "off"; - if (thinkingLevel !== "off") { + if (state.model?.thinking) { + const thinkingLevel = state.thinkingLevel ?? ThinkingLevel.Off; + if (thinkingLevel !== ThinkingLevel.Off) { rightSide = `${modelName} • ${thinkingLevel}`; } } diff --git a/packages/coding-agent/src/modes/components/model-selector.ts b/packages/coding-agent/src/modes/components/model-selector.ts index b04aeb29b..3d0f1e094 100644 --- a/packages/coding-agent/src/modes/components/model-selector.ts +++ b/packages/coding-agent/src/modes/components/model-selector.ts @@ -1,16 +1,11 @@ -import { - getAvailableThinkingLevels, - getThinkingMetadata, - type Model, - modelsAreEqual, - supportsXhigh, - type ThinkingMode, -} from "@oh-my-pi/pi-ai"; +import { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; +import { getSupportedEfforts, type Model, modelsAreEqual } from "@oh-my-pi/pi-ai"; import { Container, Input, matchesKey, Spacer, type Tab, TabBar, Text, type TUI, visibleWidth } from "@oh-my-pi/pi-tui"; import { MODEL_ROLE_IDS, MODEL_ROLES, type ModelRegistry, type ModelRole } from "../../config/model-registry"; import { resolveModelRoleValue } from "../../config/model-resolver"; import type { Settings } from "../../config/settings"; import { type ThemeColor, theme } from "../../modes/theme/theme"; +import { getThinkingLevelMetadata } from "../../thinking"; import { fuzzyFilter } from "../../utils/fuzzy"; import { getTabBarTheme } from "../shared"; import { DynamicBorder } from "./dynamic-border"; @@ -29,15 +24,15 @@ interface ModelItem { interface ScopedModelItem { model: Model; - thinkingLevel: string; + thinkingLevel?: string; } interface RoleAssignment { model: Model; - thinkingMode: ThinkingMode; + thinkingLevel: ThinkingLevel; } -type RoleSelectCallback = (model: Model, role: ModelRole | null, thinkingMode?: ThinkingMode) => void; +type RoleSelectCallback = (model: Model, role: ModelRole | null, thinkingLevel?: ThinkingLevel) => void; type CancelCallback = () => void; interface MenuRoleAction { label: string; @@ -97,7 +92,7 @@ export class ModelSelectorComponent extends Container { settings: Settings, modelRegistry: ModelRegistry, scopedModels: ReadonlyArray, - onSelect: (model: Model, role: ModelRole | null, thinkingMode?: ThinkingMode) => void, + onSelect: (model: Model, role: ModelRole | null, thinkingLevel?: ThinkingLevel) => void, onCancel: () => void, options?: { temporaryOnly?: boolean; initialSearchInput?: string }, ) { @@ -192,7 +187,8 @@ export class ModelSelectorComponent extends Container { if (model) { this.#roles[role] = { model, - thinkingMode: explicitThinkingLevel && thinkingLevel !== undefined ? thinkingLevel : "inherit", + thinkingLevel: + explicitThinkingLevel && thinkingLevel !== undefined ? thinkingLevel : ThinkingLevel.Inherit, }; } } @@ -409,7 +405,7 @@ export class ModelSelectorComponent extends Container { if (!tag || !assigned || !modelsAreEqual(assigned.model, item.model)) continue; const badge = makeInvertedBadge(tag, color ?? "success"); - const thinkingLabel = getThinkingMetadata(assigned.thinkingMode).label; + const thinkingLabel = getThinkingLevelMetadata(assigned.thinkingLevel).label; roleBadgeTokens.push(`${badge} ${theme.fg("dim", `(${thinkingLabel})`)}`); } const badgeText = roleBadgeTokens.length > 0 ? ` ${roleBadgeTokens.join(" ")}` : ""; @@ -456,19 +452,18 @@ export class ModelSelectorComponent extends Container { this.#listContainer.addChild(new Text(theme.fg("muted", ` Model Name: ${selected.model.name}`), 0, 0)); } } - #getThinkingModesForModel(model: Model): ReadonlyArray { - return ["inherit", ...getAvailableThinkingLevels(supportsXhigh(model))]; + #getThinkingLevelsForModel(model: Model): ReadonlyArray { + return [ThinkingLevel.Inherit, ThinkingLevel.Off, ...getSupportedEfforts(model)]; } - #getCurrentRoleThinkingMode(role: ModelRole): ThinkingMode { - return this.#roles[role]?.thinkingMode ?? "inherit"; + #getCurrentRoleThinkingLevel(role: ModelRole): ThinkingLevel { + return this.#roles[role]?.thinkingLevel ?? ThinkingLevel.Inherit; } #getThinkingPreselectIndex(role: ModelRole, model: Model): number { - const options = this.#getThinkingModesForModel(model); - const currentMode = this.#getCurrentRoleThinkingMode(role); - const preferredMode = currentMode === "xhigh" && !options.includes("xhigh") ? "high" : currentMode; - const foundIndex = options.indexOf(preferredMode); + const options = this.#getThinkingLevelsForModel(model); + const currentLevel = this.#getCurrentRoleThinkingLevel(role); + const foundIndex = options.indexOf(currentLevel); return foundIndex >= 0 ? foundIndex : 0; } @@ -496,11 +491,11 @@ export class ModelSelectorComponent extends Container { if (!selectedModel) return; const showingThinking = this.#menuStep === "thinking" && this.#menuSelectedRole !== null; - const thinkingOptions = showingThinking ? this.#getThinkingModesForModel(selectedModel.model) : []; + const thinkingOptions = showingThinking ? this.#getThinkingLevelsForModel(selectedModel.model) : []; const optionLines = showingThinking - ? thinkingOptions.map((thinkingMode, index) => { + ? thinkingOptions.map((thinkingLevel, index) => { const prefix = index === this.#menuSelectedIndex ? ` ${theme.nav.cursor} ` : " "; - const label = getThinkingMetadata(thinkingMode).label; + const label = getThinkingLevelMetadata(thinkingLevel).label; return `${prefix}${label}`; }) : MENU_ROLE_ACTIONS.map((action, index) => { @@ -607,7 +602,7 @@ export class ModelSelectorComponent extends Container { const optionCount = this.#menuStep === "thinking" && this.#menuSelectedRole !== null - ? this.#getThinkingModesForModel(selectedModel.model).length + ? this.#getThinkingLevelsForModel(selectedModel.model).length : MENU_ROLE_ACTIONS.length; if (optionCount === 0) return; @@ -635,10 +630,10 @@ export class ModelSelectorComponent extends Container { } if (!this.#menuSelectedRole) return; - const thinkingOptions = this.#getThinkingModesForModel(selectedModel.model); - const thinkingMode = thinkingOptions[this.#menuSelectedIndex]; - if (!thinkingMode) return; - this.#handleSelect(selectedModel.model, this.#menuSelectedRole, thinkingMode); + const thinkingOptions = this.#getThinkingLevelsForModel(selectedModel.model); + const thinkingLevel = thinkingOptions[this.#menuSelectedIndex]; + if (!thinkingLevel) return; + this.#handleSelect(selectedModel.model, this.#menuSelectedRole, thinkingLevel); this.#closeMenu(); return; } @@ -657,28 +652,28 @@ export class ModelSelectorComponent extends Container { } } - #formatRoleModelValue(model: Model, thinkingMode: ThinkingMode): string { + #formatRoleModelValue(model: Model, thinkingLevel: ThinkingLevel): string { const modelKey = `${model.provider}/${model.id}`; - if (thinkingMode === "inherit") return modelKey; - return `${modelKey}:${thinkingMode}`; + if (thinkingLevel === ThinkingLevel.Inherit) return modelKey; + return `${modelKey}:${thinkingLevel}`; } - #handleSelect(model: Model, role: ModelRole | null, thinkingMode?: ThinkingMode): void { + #handleSelect(model: Model, role: ModelRole | null, thinkingLevel?: ThinkingLevel): void { // For temporary role, don't save to settings - just notify caller if (role === null) { this.#onSelectCallback(model, null); return; } - const selectedThinkingMode = thinkingMode ?? this.#getCurrentRoleThinkingMode(role); + const selectedThinkingLevel = thinkingLevel ?? this.#getCurrentRoleThinkingLevel(role); // Save to settings - this.#settings.setModelRole(role, this.#formatRoleModelValue(model, selectedThinkingMode)); + this.#settings.setModelRole(role, this.#formatRoleModelValue(model, selectedThinkingLevel)); // Update local state for UI - this.#roles[role] = { model, thinkingMode: selectedThinkingMode }; + this.#roles[role] = { model, thinkingLevel: selectedThinkingLevel }; // Notify caller (for updating agent state if needed) - this.#onSelectCallback(model, role, selectedThinkingMode); + this.#onSelectCallback(model, role, selectedThinkingLevel); // Update list to show new badges this.#updateList(); diff --git a/packages/coding-agent/src/modes/components/settings-defs.ts b/packages/coding-agent/src/modes/components/settings-defs.ts index 37709b9e4..f82f3014c 100644 --- a/packages/coding-agent/src/modes/components/settings-defs.ts +++ b/packages/coding-agent/src/modes/components/settings-defs.ts @@ -7,7 +7,7 @@ * 2. That's it - it appears in the UI automatically */ -import { getAvailableThinkingLevels, getThinkingMetadata } from "@oh-my-pi/pi-ai"; +import { THINKING_EFFORTS } from "@oh-my-pi/pi-ai"; import { TERMINAL } from "@oh-my-pi/pi-tui"; import { getDefault, @@ -19,6 +19,7 @@ import { type SettingPath, type SettingTab, } from "../../config/settings-schema"; +import { getThinkingLevelMetadata } from "../../thinking"; // ═══════════════════════════════════════════════════════════════════════════ // UI Definition Types @@ -251,7 +252,7 @@ const OPTION_PROVIDERS: Partial> = { { value: "on", label: "On", description: "Force websockets for OpenAI Codex models" }, ], // Default thinking level - defaultThinkingLevel: [...getAvailableThinkingLevels().map(getThinkingMetadata)], + defaultThinkingLevel: [...THINKING_EFFORTS.map(getThinkingLevelMetadata)], // Temperature temperature: [ { value: "-1", label: "Default", description: "Use provider default" }, diff --git a/packages/coding-agent/src/modes/components/settings-selector.ts b/packages/coding-agent/src/modes/components/settings-selector.ts index eedd38321..8c540e84f 100644 --- a/packages/coding-agent/src/modes/components/settings-selector.ts +++ b/packages/coding-agent/src/modes/components/settings-selector.ts @@ -1,4 +1,5 @@ -import type { ThinkingLevel } from "@oh-my-pi/pi-ai"; +import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; +import type { Effort } from "@oh-my-pi/pi-ai"; import { Container, matchesKey, @@ -134,9 +135,9 @@ function getSettingsTabs(): Tab[] { */ export interface SettingsRuntimeContext { /** Available thinking levels (from session) */ - availableThinkingLevels: ThinkingLevel[]; + availableThinkingLevels: Effort[]; /** Current thinking level (from session) */ - thinkingLevel: ThinkingLevel; + thinkingLevel: ThinkingLevel | undefined; /** Available themes */ availableThemes: string[]; /** Working directory for plugins tab */ diff --git a/packages/coding-agent/src/modes/components/status-line/segments.ts b/packages/coding-agent/src/modes/components/status-line/segments.ts index 720abee5b..4e5d396e7 100644 --- a/packages/coding-agent/src/modes/components/status-line/segments.ts +++ b/packages/coding-agent/src/modes/components/status-line/segments.ts @@ -1,4 +1,5 @@ import * as os from "node:os"; +import { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import { TERMINAL } from "@oh-my-pi/pi-tui"; import { formatDuration, formatNumber, getProjectDir } from "@oh-my-pi/pi-utils"; import { theme } from "../../../modes/theme/theme"; @@ -50,9 +51,9 @@ const modelSegment: StatusLineSegment = { } // Add thinking level with dot separator - if (opts.showThinkingLevel !== false && state.model?.reasoning) { - const level = state.thinkingLevel || "off"; - if (level !== "off") { + if (opts.showThinkingLevel !== false && state.model?.thinking) { + const level = state.thinkingLevel ?? ThinkingLevel.Off; + if (level !== ThinkingLevel.Off) { const thinkingText = theme.thinking[level as keyof typeof theme.thinking]; if (thinkingText) { content += `${theme.sep.dot}${thinkingText}`; diff --git a/packages/coding-agent/src/modes/components/thinking-selector.ts b/packages/coding-agent/src/modes/components/thinking-selector.ts index 9cb397ebc..9c26dc94b 100644 --- a/packages/coding-agent/src/modes/components/thinking-selector.ts +++ b/packages/coding-agent/src/modes/components/thinking-selector.ts @@ -1,7 +1,7 @@ -import { getThinkingMetadata, type ThinkingLevel } from "@oh-my-pi/pi-ai"; - +import type { Effort } from "@oh-my-pi/pi-ai"; import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui"; import { getSelectListTheme } from "../../modes/theme/theme"; +import { getThinkingLevelMetadata } from "../../thinking"; import { DynamicBorder } from "./dynamic-border"; /** @@ -11,14 +11,14 @@ export class ThinkingSelectorComponent extends Container { #selectList: SelectList; constructor( - currentLevel: ThinkingLevel, - availableLevels: ThinkingLevel[], - onSelect: (level: ThinkingLevel) => void, + currentLevel: Effort, + availableLevels: Effort[], + onSelect: (level: Effort) => void, onCancel: () => void, ) { super(); - const thinkingLevels: SelectItem[] = availableLevels.map(getThinkingMetadata); + const thinkingLevels: SelectItem[] = availableLevels.map(getThinkingLevelMetadata); // Add top border this.addChild(new DynamicBorder()); @@ -33,7 +33,7 @@ export class ThinkingSelectorComponent extends Container { } this.#selectList.onSelect = item => { - onSelect(item.value as ThinkingLevel); + onSelect(item.value as Effort); }; this.#selectList.onCancel = () => { diff --git a/packages/coding-agent/src/modes/components/tree-selector.ts b/packages/coding-agent/src/modes/components/tree-selector.ts index b50805f50..7dfdd7df9 100644 --- a/packages/coding-agent/src/modes/components/tree-selector.ts +++ b/packages/coding-agent/src/modes/components/tree-selector.ts @@ -1,3 +1,4 @@ +import { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import { type Component, Container, @@ -382,7 +383,7 @@ class TreeList implements Component { parts.push("model", entry.model); break; case "thinking_level_change": - parts.push("thinking", entry.thinkingLevel); + parts.push("thinking", entry.thinkingLevel ?? ThinkingLevel.Off); break; case "custom": parts.push("custom", entry.customType); @@ -585,7 +586,7 @@ class TreeList implements Component { result = theme.fg("dim", `[model: ${entry.model}]`); break; case "thinking_level_change": - result = theme.fg("dim", `[thinking: ${entry.thinkingLevel}]`); + result = theme.fg("dim", `[thinking: ${entry.thinkingLevel ?? ThinkingLevel.Off}]`); break; case "custom": result = theme.fg("dim", `[custom: ${entry.customType}]`); diff --git a/packages/coding-agent/src/modes/controllers/input-controller.ts b/packages/coding-agent/src/modes/controllers/input-controller.ts index 8d00b2241..2f4719d60 100644 --- a/packages/coding-agent/src/modes/controllers/input-controller.ts +++ b/packages/coding-agent/src/modes/controllers/input-controller.ts @@ -1,5 +1,5 @@ import * as fs from "node:fs/promises"; -import type { AgentMessage } from "@oh-my-pi/pi-agent-core"; +import { type AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import { copyToClipboard, readImageFromClipboard, sanitizeText } from "@oh-my-pi/pi-natives"; import { $env } from "@oh-my-pi/pi-utils"; import { settings } from "../../config/settings"; @@ -544,7 +544,9 @@ export class InputController { const roleLabel = result.role === "default" ? "default" : result.role; const roleLabelStyled = theme.bold(theme.fg("accent", roleLabel)); const thinkingStr = - result.model.reasoning && result.thinkingLevel !== "off" ? ` (thinking: ${result.thinkingLevel})` : ""; + result.model.thinking && result.thinkingLevel !== ThinkingLevel.Off + ? ` (thinking: ${result.thinkingLevel})` + : ""; const tempLabel = options?.temporary ? " (temporary)" : ""; const cycleSeparator = theme.fg("dim", " > "); const cycleLabel = roleOrder diff --git a/packages/coding-agent/src/modes/controllers/selector-controller.ts b/packages/coding-agent/src/modes/controllers/selector-controller.ts index 6e7992ed1..fd0f62415 100644 --- a/packages/coding-agent/src/modes/controllers/selector-controller.ts +++ b/packages/coding-agent/src/modes/controllers/selector-controller.ts @@ -1,4 +1,5 @@ -import { getOAuthProviders, type OAuthProvider, type ThinkingLevel } from "@oh-my-pi/pi-ai"; +import { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; +import { getOAuthProviders, type OAuthProvider } from "@oh-my-pi/pi-ai"; import type { Component } from "@oh-my-pi/pi-tui"; import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui"; import { getAgentDbPath, getProjectDir } from "@oh-my-pi/pi-utils"; @@ -380,7 +381,7 @@ export class SelectorController { this.ctx.settings, this.ctx.session.modelRegistry, this.ctx.session.scopedModels, - async (model, role, thinkingMode) => { + async (model, role, thinkingLevel) => { try { if (role === null) { // Temporary: update agent state but don't persist to settings @@ -393,8 +394,8 @@ export class SelectorController { } else if (role === "default") { // Default: update agent state and persist await this.ctx.session.setModel(model, role); - if (thinkingMode && thinkingMode !== "inherit") { - this.ctx.session.setThinkingLevel(thinkingMode as ThinkingLevel); + if (thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit) { + this.ctx.session.setThinkingLevel(thinkingLevel); } this.ctx.statusLine.invalidate(); this.ctx.updateEditorBorderColor(); diff --git a/packages/coding-agent/src/modes/interactive-mode.ts b/packages/coding-agent/src/modes/interactive-mode.ts index 0d67ba62d..bd1748c99 100644 --- a/packages/coding-agent/src/modes/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive-mode.ts @@ -3,7 +3,7 @@ * Handles TUI rendering and user interaction, delegating business logic to AgentSession. */ import * as path from "node:path"; -import type { Agent, AgentMessage } from "@oh-my-pi/pi-agent-core"; +import { type Agent, type AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import type { AssistantMessage, ImageContent, Message, Model, UsageReport } from "@oh-my-pi/pi-ai"; import type { Component, Loader, SlashCommand } from "@oh-my-pi/pi-tui"; import { @@ -423,7 +423,7 @@ export class InteractiveMode implements InteractiveModeContext { } else if (this.isPythonMode) { this.editor.borderColor = theme.getPythonModeBorderColor(); } else { - const level = this.session.thinkingLevel || "off"; + const level = this.session.thinkingLevel ?? ThinkingLevel.Off; this.editor.borderColor = theme.getThinkingBorderColor(level); } this.updateEditorTopBorder(); diff --git a/packages/coding-agent/src/modes/rpc/rpc-client.ts b/packages/coding-agent/src/modes/rpc/rpc-client.ts index cf18e0406..f401d3922 100644 --- a/packages/coding-agent/src/modes/rpc/rpc-client.ts +++ b/packages/coding-agent/src/modes/rpc/rpc-client.ts @@ -3,8 +3,8 @@ * * Spawns the agent in RPC mode and provides a typed API for all operations. */ -import type { AgentEvent, AgentMessage } from "@oh-my-pi/pi-agent-core"; -import type { ImageContent, ThinkingLevel } from "@oh-my-pi/pi-ai"; +import type { AgentEvent, AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core"; +import type { Effort, ImageContent, Model } from "@oh-my-pi/pi-ai"; import { isRecord, ptree, readJsonl } from "@oh-my-pi/pi-utils"; import type { BashResult } from "../../exec/bash-executor"; import type { SessionStats } from "../../session/agent-session"; @@ -34,12 +34,7 @@ export interface RpcClientOptions { args?: string[]; } -export interface ModelInfo { - provider: string; - id: string; - contextWindow: number; - reasoning: boolean; -} +export type ModelInfo = Pick; export type RpcEventListener = (event: AgentEvent) => void; @@ -284,7 +279,7 @@ export class RpcClient { */ async cycleModel(): Promise<{ model: { provider: string; id: string }; - thinkingLevel: ThinkingLevel; + thinkingLevel: ThinkingLevel | undefined; isScoped: boolean; } | null> { const response = await this.#send({ type: "cycle_model" }); @@ -309,7 +304,7 @@ export class RpcClient { /** * Cycle thinking level. */ - async cycleThinkingLevel(): Promise<{ level: ThinkingLevel } | null> { + async cycleThinkingLevel(): Promise<{ level: Effort } | null> { const response = await this.#send({ type: "cycle_thinking_level" }); return this.#getData(response); } diff --git a/packages/coding-agent/src/modes/rpc/rpc-types.ts b/packages/coding-agent/src/modes/rpc/rpc-types.ts index bbf2dc7ae..d212d70d7 100644 --- a/packages/coding-agent/src/modes/rpc/rpc-types.ts +++ b/packages/coding-agent/src/modes/rpc/rpc-types.ts @@ -4,8 +4,8 @@ * Commands are sent as JSON lines on stdin. * Responses and events are emitted as JSON lines on stdout. */ -import type { AgentMessage } from "@oh-my-pi/pi-agent-core"; -import type { ImageContent, Model, ThinkingLevel } from "@oh-my-pi/pi-ai"; +import type { AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core"; +import type { Effort, ImageContent, Model } from "@oh-my-pi/pi-ai"; import type { BashResult } from "../../exec/bash-executor"; import type { SessionStats } from "../../session/agent-session"; import type { CompactionResult } from "../../session/compaction"; @@ -70,7 +70,7 @@ export type RpcCommand = export interface RpcSessionState { model?: Model; - thinkingLevel: ThinkingLevel; + thinkingLevel: ThinkingLevel | undefined; isStreaming: boolean; isCompacting: boolean; steeringMode: "all" | "one-at-a-time"; @@ -114,7 +114,7 @@ export type RpcResponse = type: "response"; command: "cycle_model"; success: true; - data: { model: Model; thinkingLevel: ThinkingLevel; isScoped: boolean } | null; + data: { model: Model; thinkingLevel: ThinkingLevel | undefined; isScoped: boolean } | null; } | { id?: string; @@ -131,7 +131,7 @@ export type RpcResponse = type: "response"; command: "cycle_thinking_level"; success: true; - data: { level: ThinkingLevel } | null; + data: { level: Effort } | null; } // Queue modes diff --git a/packages/coding-agent/src/modes/theme/theme.ts b/packages/coding-agent/src/modes/theme/theme.ts index cec67e45c..4437443af 100644 --- a/packages/coding-agent/src/modes/theme/theme.ts +++ b/packages/coding-agent/src/modes/theme/theme.ts @@ -1,6 +1,7 @@ import * as fs from "node:fs"; import * as path from "node:path"; -import type { ThinkingLevel } from "@oh-my-pi/pi-ai"; +import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; +import type { Effort } from "@oh-my-pi/pi-ai"; import { detectMacOSAppearance, type HighlightColors as NativeHighlightColors, @@ -1223,7 +1224,7 @@ export class Theme { return this.mode; } - getThinkingBorderColor(level: ThinkingLevel): (str: string) => string { + getThinkingBorderColor(level: ThinkingLevel | Effort): (str: string) => string { // Map thinking levels to dedicated theme colors switch (level) { case "off": diff --git a/packages/coding-agent/src/prompts/system/auto-handoff-threshold-focus.md b/packages/coding-agent/src/prompts/system/auto-handoff-threshold-focus.md index 371e1ad7c..925a126c1 100644 --- a/packages/coding-agent/src/prompts/system/auto-handoff-threshold-focus.md +++ b/packages/coding-agent/src/prompts/system/auto-handoff-threshold-focus.md @@ -1 +1 @@ -Threshold-triggered maintenance: preserve critical implementation state and immediate next actions. +Threshold-triggered maintenance: preserve critical implementation state and immediate next actions. \ No newline at end of file diff --git a/packages/coding-agent/src/sdk.ts b/packages/coding-agent/src/sdk.ts index 4120a2972..38c4539ae 100644 --- a/packages/coding-agent/src/sdk.ts +++ b/packages/coding-agent/src/sdk.ts @@ -1,5 +1,12 @@ -import { Agent, type AgentEvent, type AgentMessage, type AgentTool, INTENT_FIELD } from "@oh-my-pi/pi-agent-core"; -import { type Message, type Model, supportsXhigh, type ThinkingLevel } from "@oh-my-pi/pi-ai"; +import { + Agent, + type AgentEvent, + type AgentMessage, + type AgentTool, + INTENT_FIELD, + type ThinkingLevel, +} from "@oh-my-pi/pi-agent-core"; +import type { Message, Model } from "@oh-my-pi/pi-ai"; import { prewarmOpenAICodexResponses } from "@oh-my-pi/pi-ai/providers/openai-codex-responses"; import type { Component } from "@oh-my-pi/pi-tui"; @@ -72,6 +79,7 @@ import { loadProjectContextFiles as loadContextFilesInternal, } from "./system-prompt"; import { AgentOutputManager } from "./task/output-manager"; +import { resolveThinkingLevelForModel, toReasoningEffort } from "./thinking"; import { BashTool, BUILTIN_TOOLS, @@ -117,10 +125,10 @@ export interface CreateAgentSessionOptions { /** Raw model pattern string (e.g. from --model CLI flag) to resolve after extensions load. * Used when model lookup is deferred because extension-provided models aren't registered yet. */ modelPattern?: string; - /** Thinking level. Default: from settings, else 'off' (clamped to model capabilities) */ + /** Thinking selector. Default: from settings, else unset */ thinkingLevel?: ThinkingLevel; /** Models available for cycling (Ctrl+P in interactive mode) */ - scopedModels?: Array<{ model: Model; thinkingLevel: ThinkingLevel }>; + scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>; /** System prompt. String replaces default, function receives default and returns final. */ systemPrompt?: string | ((defaultPrompt: string) => string); @@ -697,7 +705,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} // If session has data and includes a thinking entry, restore it if (thinkingLevel === undefined && hasExistingSession && hasThinkingEntry) { - thinkingLevel = existingSession.thinkingLevel as ThinkingLevel; + thinkingLevel = existingSession.thinkingLevel as ThinkingLevel | undefined; } if (thinkingLevel === undefined && !hasExplicitModel && !hasThinkingEntry && defaultRoleSpec.explicitThinkingLevel) { @@ -706,14 +714,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} // Fall back to settings default if (thinkingLevel === undefined) { - thinkingLevel = settings.get("defaultThinkingLevel") ?? "off"; + thinkingLevel = settings.get("defaultThinkingLevel"); } - - // Clamp to model capabilities - if (!model || !model.reasoning) { - thinkingLevel = "off"; - } else if (thinkingLevel === "xhigh" && !supportsXhigh(model)) { - thinkingLevel = "high"; + if (model) { + thinkingLevel = resolveThinkingLevelForModel(model, thinkingLevel); } let skills: Skill[]; @@ -1350,7 +1354,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} initialState: { systemPrompt, model, - thinkingLevel, + thinkingLevel: toReasoningEffort(thinkingLevel), tools: initialTools, }, convertToLlm: convertToLlmFinal, @@ -1421,6 +1425,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} session = new AgentSession({ agent, + thinkingLevel, sessionManager, settings, scopedModels: options.scopedModels, diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts index 11531de01..b5e257a86 100644 --- a/packages/coding-agent/src/session/agent-session.ts +++ b/packages/coding-agent/src/session/agent-session.ts @@ -24,16 +24,17 @@ import { type AgentState, type AgentTool, INTENT_FIELD, + ThinkingLevel, } from "@oh-my-pi/pi-agent-core"; import type { AssistantMessage, + Effort, ImageContent, Message, Model, ProviderSessionState, ServiceTier, TextContent, - ThinkingLevel, ToolCall, ToolChoice, Usage, @@ -41,11 +42,10 @@ import type { } from "@oh-my-pi/pi-ai"; import { calculateRateLimitBackoffMs, - getAvailableThinkingLevels, + getSupportedEfforts, isContextOverflow, modelsAreEqual, parseRateLimitReason, - supportsXhigh, } from "@oh-my-pi/pi-ai"; import { abortableSleep, getAgentDbPath, isEnoent, logger } from "@oh-my-pi/pi-utils"; import type { AsyncJob, AsyncJobManager } from "../async"; @@ -96,6 +96,7 @@ import planModeToolDecisionReminderPrompt from "../prompts/system/plan-mode-tool }; import ttsrInterruptTemplate from "../prompts/system/ttsr-interrupt.md" with { type: "text" }; import type { SecretObfuscator } from "../secrets/obfuscator"; +import { resolveThinkingLevelForModel, toReasoningEffort } from "../thinking"; import type { CheckpointState } from "../tools/checkpoint"; import { outputMeta } from "../tools/output-meta"; import { resolveToCwd } from "../tools/path-utils"; @@ -166,7 +167,9 @@ export interface AgentSessionConfig { /** Async background jobs launched by tools */ asyncJobManager?: AsyncJobManager; /** Models to cycle through with Ctrl+P (from --models flag) */ - scopedModels?: Array<{ model: Model; thinkingLevel: ThinkingLevel }>; + scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>; + /** Initial session thinking selector. */ + thinkingLevel?: ThinkingLevel; /** Prompt templates for expansion */ promptTemplates?: PromptTemplate[]; /** File-based slash commands for expansion */ @@ -215,7 +218,7 @@ export interface PromptOptions { /** Result from cycleModel() */ export interface ModelCycleResult { model: Model; - thinkingLevel: ThinkingLevel; + thinkingLevel: ThinkingLevel | undefined; /** Whether cycling through scoped models (--models flag) or all available */ isScoped: boolean; } @@ -223,7 +226,7 @@ export interface ModelCycleResult { /** Result from cycleRoleModels() */ export interface RoleModelCycleResult { model: Model; - thinkingLevel: ThinkingLevel; + thinkingLevel: ThinkingLevel | undefined; role: ModelRole; } @@ -305,7 +308,8 @@ export class AgentSession { readonly settings: Settings; #asyncJobManager: AsyncJobManager | undefined = undefined; - #scopedModels: Array<{ model: Model; thinkingLevel: ThinkingLevel }>; + #scopedModels: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>; + #thinkingLevel: ThinkingLevel | undefined; #promptTemplates: PromptTemplate[]; #slashCommands: FileSlashCommand[]; @@ -406,6 +410,7 @@ export class AgentSession { this.settings = config.settings; this.#asyncJobManager = config.asyncJobManager; this.#scopedModels = config.scopedModels ?? []; + this.#thinkingLevel = config.thinkingLevel; this.#promptTemplates = config.promptTemplates ?? []; this.#slashCommands = config.slashCommands ?? []; this.#extensionRunner = config.extensionRunner; @@ -1544,8 +1549,8 @@ export class AgentSession { } /** Current thinking level */ - get thinkingLevel(): ThinkingLevel { - return this.agent.state.thinkingLevel; + get thinkingLevel(): ThinkingLevel | undefined { + return this.#thinkingLevel; } get serviceTier(): ServiceTier | undefined { @@ -1724,7 +1729,7 @@ export class AgentSession { } /** Scoped models for cycling (from --models flag) */ - get scopedModels(): ReadonlyArray<{ model: Model; thinkingLevel: ThinkingLevel }> { + get scopedModels(): ReadonlyArray<{ model: Model; thinkingLevel?: ThinkingLevel }> { return this.#scopedModels; } @@ -2654,7 +2659,7 @@ export class AgentSession { this.settings.setModelRole(role, this.#formatRoleModelValue(role, model)); this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`); - // Re-clamp thinking level for new model's capabilities without persisting settings + // Re-apply the current thinking level for the newly selected model this.setThinkingLevel(this.thinkingLevel); } @@ -2673,7 +2678,7 @@ export class AgentSession { this.sessionManager.appendModelChange(`${model.provider}/${model.id}`, "temporary"); this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`); - // Re-clamp thinking level for new model's capabilities without persisting settings + // Re-apply the current thinking level for the newly selected model this.setThinkingLevel(this.thinkingLevel); } @@ -2758,9 +2763,9 @@ export class AgentSession { return { model: next.model, thinkingLevel: this.thinkingLevel, role: next.role }; } - async #getScopedModelsWithApiKey(): Promise> { + async #getScopedModelsWithApiKey(): Promise> { const apiKeysByProvider = new Map(); - const result: Array<{ model: Model; thinkingLevel: ThinkingLevel }> = []; + const result: Array<{ model: Model; thinkingLevel?: ThinkingLevel }> = []; for (const scoped of this.#scopedModels) { const provider = scoped.model.provider; @@ -2798,7 +2803,7 @@ export class AgentSession { this.settings.setModelRole("default", this.#formatRoleModelValue("default", next.model)); this.settings.getStorage()?.recordModelUsage(`${next.model.provider}/${next.model.id}`); - // Apply thinking level (setThinkingLevel clamps to model capabilities) + // Apply the scoped model's configured thinking level this.setThinkingLevel(next.thinkingLevel); return { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true }; @@ -2826,7 +2831,7 @@ export class AgentSession { this.settings.setModelRole("default", this.#formatRoleModelValue("default", nextModel)); this.settings.getStorage()?.recordModelUsage(`${nextModel.provider}/${nextModel.id}`); - // Re-clamp thinking level for new model's capabilities without persisting settings + // Re-apply the current thinking level for the newly selected model this.setThinkingLevel(this.thinkingLevel); return { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false }; @@ -2845,21 +2850,18 @@ export class AgentSession { /** * Set thinking level. - * Clamps to model capabilities based on available thinking levels. - * Saves to session and settings only if the level actually changes. + * Saves the effective metadata-clamped level to session and settings only if it changes. */ - setThinkingLevel(level: ThinkingLevel, persist: boolean = false): void { - const availableLevels = this.getAvailableThinkingLevels(); - const effectiveLevel = availableLevels.includes(level) ? level : this.#clampThinkingLevel(level, availableLevels); + setThinkingLevel(level: ThinkingLevel | undefined, persist: boolean = false): void { + const effectiveLevel = resolveThinkingLevelForModel(this.model, level); + const isChanging = effectiveLevel !== this.#thinkingLevel; - // Only persist if actually changing - const isChanging = effectiveLevel !== this.agent.state.thinkingLevel; - - this.agent.setThinkingLevel(effectiveLevel); + this.#thinkingLevel = effectiveLevel; + this.agent.setThinkingLevel(toReasoningEffort(effectiveLevel)); if (isChanging) { this.sessionManager.appendThinkingLevelChange(effectiveLevel); - if (persist) { + if (persist && effectiveLevel !== undefined && effectiveLevel !== ThinkingLevel.Off) { this.settings.set("defaultThinkingLevel", effectiveLevel); } } @@ -2869,13 +2871,17 @@ export class AgentSession { * Cycle to next thinking level. * @returns New level, or undefined if model doesn't support thinking */ - cycleThinkingLevel(): ThinkingLevel | undefined { - if (!this.supportsThinking()) return undefined; + cycleThinkingLevel(): Effort | undefined { + if (!this.model?.reasoning) return undefined; const levels = this.getAvailableThinkingLevels(); - const currentIndex = levels.indexOf(this.thinkingLevel); + const currentIndex = + this.thinkingLevel && this.thinkingLevel !== ThinkingLevel.Off && this.thinkingLevel !== ThinkingLevel.Inherit + ? levels.indexOf(this.thinkingLevel) + : -1; const nextIndex = (currentIndex + 1) % levels.length; const nextLevel = levels[nextIndex]; + if (!nextLevel) return undefined; this.setThinkingLevel(nextLevel); return nextLevel; @@ -2903,43 +2909,10 @@ export class AgentSession { /** * Get available thinking levels for current model. - * The provider will clamp to what the specific model supports internally. */ - getAvailableThinkingLevels(): ReadonlyArray { - if (!this.supportsThinking()) return ["off"]; - return getAvailableThinkingLevels(this.supportsXhighThinking()); - } - - /** - * Check if current model supports xhigh thinking level. - */ - supportsXhighThinking(): boolean { - return this.model ? supportsXhigh(this.model) : false; - } - - /** - * Check if current model supports thinking/reasoning. - */ - supportsThinking(): boolean { - return !!this.model?.reasoning; - } - - #clampThinkingLevel(level: ThinkingLevel, availableLevels: ReadonlyArray): ThinkingLevel { - const ordered = getAvailableThinkingLevels(true); - const available = new Set(availableLevels); - const requestedIndex = ordered.indexOf(level); - if (requestedIndex === -1) { - return availableLevels[0] ?? "off"; - } - for (let i = requestedIndex; i < ordered.length; i++) { - const candidate = ordered[i]; - if (available.has(candidate)) return candidate; - } - for (let i = requestedIndex - 1; i >= 0; i--) { - const candidate = ordered[i]; - if (available.has(candidate)) return candidate; - } - return availableLevels[0] ?? "off"; + getAvailableThinkingLevels(): ReadonlyArray { + if (!this.model) return []; + return getSupportedEfforts(this.model); } // ========================================================================= @@ -4647,18 +4620,15 @@ Be thorough - include exact file paths, function names, error messages, and tech const hasThinkingEntry = this.sessionManager.getBranch().some(entry => entry.type === "thinking_level_change"); const hasServiceTierEntry = this.sessionManager.getBranch().some(entry => entry.type === "service_tier_change"); - const defaultThinkingLevel = (this.settings.get("defaultThinkingLevel") ?? "off") as ThinkingLevel; + const defaultThinkingLevel = this.settings.get("defaultThinkingLevel"); if (hasThinkingEntry) { - // Restore thinking level if saved (setThinkingLevel clamps to model capabilities) - this.setThinkingLevel(sessionContext.thinkingLevel as ThinkingLevel); + this.setThinkingLevel(sessionContext.thinkingLevel as ThinkingLevel | undefined); } else { - const availableLevels = this.getAvailableThinkingLevels(); - const effectiveLevel = availableLevels.includes(defaultThinkingLevel) - ? defaultThinkingLevel - : this.#clampThinkingLevel(defaultThinkingLevel, availableLevels); - this.agent.setThinkingLevel(effectiveLevel); - this.sessionManager.appendThinkingLevelChange(effectiveLevel); + const effectiveDefaultThinkingLevel = resolveThinkingLevelForModel(this.model, defaultThinkingLevel); + this.#thinkingLevel = effectiveDefaultThinkingLevel; + this.agent.setThinkingLevel(toReasoningEffort(effectiveDefaultThinkingLevel)); + this.sessionManager.appendThinkingLevelChange(effectiveDefaultThinkingLevel); } if (hasServiceTierEntry) { @@ -5181,7 +5151,7 @@ Be thorough - include exact file paths, function names, error messages, and tech // Include model and thinking level const model = this.agent.state.model; - const thinkingLevel = this.agent.state.thinkingLevel; + const thinkingLevel = this.#thinkingLevel; lines.push("## Configuration\n"); lines.push(`Model: ${model.provider}/${model.id}`); lines.push(`Thinking Level: ${thinkingLevel}`); diff --git a/packages/coding-agent/src/session/compaction/compaction.ts b/packages/coding-agent/src/session/compaction/compaction.ts index 603ea763e..21e2e6a6d 100644 --- a/packages/coding-agent/src/session/compaction/compaction.ts +++ b/packages/coding-agent/src/session/compaction/compaction.ts @@ -5,8 +5,7 @@ * and after compaction the session is reloaded. */ import type { AgentMessage } from "@oh-my-pi/pi-agent-core"; -import type { AssistantMessage, Model, Usage } from "@oh-my-pi/pi-ai"; -import { completeSimple } from "@oh-my-pi/pi-ai"; +import { type AssistantMessage, completeSimple, Effort, type Model, type Usage } from "@oh-my-pi/pi-ai"; import { CODEX_BASE_URL, getCodexAccountId, @@ -985,7 +984,7 @@ export async function generateSummary( const response = await completeSimple( model, { systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages }, - { maxTokens, signal, apiKey, reasoning: "high" }, + { maxTokens, signal, apiKey, reasoning: Effort.High }, ); if (response.stopReason === "error") { @@ -1034,7 +1033,7 @@ async function generateShortSummary( systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: [{ role: "user", content: [{ type: "text", text: promptText }], timestamp: Date.now() }], }, - { maxTokens, signal, apiKey, reasoning: "high" }, + { maxTokens, signal, apiKey, reasoning: Effort.High }, ); if (response.stopReason === "error") { @@ -1335,7 +1334,7 @@ async function generateTurnPrefixSummary( const response = await completeSimple( model, { systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages }, - { maxTokens, signal, apiKey, reasoning: "high" }, + { maxTokens, signal, apiKey, reasoning: Effort.High }, ); if (response.stopReason === "error") { diff --git a/packages/coding-agent/src/session/session-manager.ts b/packages/coding-agent/src/session/session-manager.ts index 343f199ae..535004cd0 100644 --- a/packages/coding-agent/src/session/session-manager.ts +++ b/packages/coding-agent/src/session/session-manager.ts @@ -67,7 +67,7 @@ export interface SessionMessageEntry extends SessionEntryBase { export interface ThinkingLevelChangeEntry extends SessionEntryBase { type: "thinking_level_change"; - thinkingLevel: string; + thinkingLevel?: string | null; } export interface ModelChangeEntry extends SessionEntryBase { @@ -209,7 +209,7 @@ export interface SessionTreeNode { export interface SessionContext { messages: AgentMessage[]; - thinkingLevel: string; + thinkingLevel?: string; serviceTier?: ServiceTier; /** Model roles: { default: "provider/modelId", small: "provider/modelId", ... } */ models: Record; @@ -433,7 +433,7 @@ export function buildSessionContext( // Explicitly null - return no messages (navigated to before first entry) return { messages: [], - thinkingLevel: "off", + thinkingLevel: undefined, serviceTier: undefined, models: {}, injectedTtsrRules: [], @@ -451,7 +451,7 @@ export function buildSessionContext( if (!leaf) { return { messages: [], - thinkingLevel: "off", + thinkingLevel: undefined, serviceTier: undefined, models: {}, injectedTtsrRules: [], @@ -468,7 +468,7 @@ export function buildSessionContext( } // Extract settings and find compaction - let thinkingLevel = "off"; + let thinkingLevel: string | undefined; let serviceTier: ServiceTier | undefined; const models: Record = {}; let compaction: CompactionEntry | null = null; @@ -478,7 +478,7 @@ export function buildSessionContext( for (const entry of path) { if (entry.type === "thinking_level_change") { - thinkingLevel = entry.thinkingLevel; + thinkingLevel = entry.thinkingLevel ?? undefined; } else if (entry.type === "model_change") { // New format: { model: "provider/id", role?: string } if (entry.model) { @@ -1829,13 +1829,13 @@ export class SessionManager { } /** Append a thinking level change as child of current leaf, then advance leaf. Returns entry id. */ - appendThinkingLevelChange(thinkingLevel: string): string { + appendThinkingLevelChange(thinkingLevel?: string): string { const entry: ThinkingLevelChangeEntry = { type: "thinking_level_change", id: generateId(this.#byId), parentId: this.#leafId, timestamp: new Date().toISOString(), - thinkingLevel, + thinkingLevel: thinkingLevel ?? null, }; this.#appendEntry(entry); return entry.id; diff --git a/packages/coding-agent/src/task/agents.ts b/packages/coding-agent/src/task/agents.ts index f6c33d5cd..16eec27fd 100644 --- a/packages/coding-agent/src/task/agents.ts +++ b/packages/coding-agent/src/task/agents.ts @@ -3,6 +3,7 @@ * * Agents are embedded at build time via Bun's import with { type: "text" }. */ +import { Effort } from "@oh-my-pi/pi-ai"; import { renderPromptTemplate } from "../config/prompt-templates"; import { parseAgentFields } from "../discovery/helpers"; import designerMd from "../prompts/agents/designer.md" with { type: "text" }; @@ -53,7 +54,7 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [ description: "General-purpose subagent with full capabilities for delegated multi-step tasks", spawns: "*", model: "default", - thinkingLevel: "medium", + thinkingLevel: Effort.Medium, }, template: taskMd, }, @@ -63,7 +64,7 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [ name: "quick_task", description: "Low-reasoning agent for strictly mechanical updates or data collection only", model: "pi/smol", - thinkingLevel: "minimal", + thinkingLevel: Effort.Minimal, }, template: taskMd, }, diff --git a/packages/coding-agent/src/task/executor.ts b/packages/coding-agent/src/task/executor.ts index 5e09d4b48..45454c5e0 100644 --- a/packages/coding-agent/src/task/executor.ts +++ b/packages/coding-agent/src/task/executor.ts @@ -4,8 +4,8 @@ * Runs each subagent on the main thread and forwards AgentEvents for progress tracking. */ import path from "node:path"; -import type { AgentEvent } from "@oh-my-pi/pi-agent-core"; -import type { Api, Model, ThinkingLevel, ToolChoice } from "@oh-my-pi/pi-ai"; +import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core"; +import type { Api, Model, ToolChoice } from "@oh-my-pi/pi-ai"; import { logger, untilAborted } from "@oh-my-pi/pi-utils"; import type { TSchema } from "@sinclair/typebox"; import Ajv, { type ValidateFunction } from "ajv"; diff --git a/packages/coding-agent/src/task/types.ts b/packages/coding-agent/src/task/types.ts index a04415350..d0a42a492 100644 --- a/packages/coding-agent/src/task/types.ts +++ b/packages/coding-agent/src/task/types.ts @@ -1,4 +1,5 @@ -import type { ThinkingLevel, Usage } from "@oh-my-pi/pi-ai"; +import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; +import type { Usage } from "@oh-my-pi/pi-ai"; import { $env } from "@oh-my-pi/pi-utils"; import { type Static, Type } from "@sinclair/typebox"; import type { NestedRepoPatch } from "./worktree"; diff --git a/packages/coding-agent/src/thinking.ts b/packages/coding-agent/src/thinking.ts new file mode 100644 index 000000000..f9dbe4b66 --- /dev/null +++ b/packages/coding-agent/src/thinking.ts @@ -0,0 +1,87 @@ +import { type ResolvedThinkingLevel, ThinkingLevel } from "@oh-my-pi/pi-agent-core"; +import { clampThinkingLevelForModel, type Effort, type Model, THINKING_EFFORTS } from "@oh-my-pi/pi-ai"; + +/** + * Metadata used to render thinking selector values in the coding-agent UI. + */ +export interface ThinkingLevelMetadata { + value: ThinkingLevel; + label: string; + description: string; +} + +const THINKING_LEVEL_METADATA: Record = { + [ThinkingLevel.Inherit]: { + value: ThinkingLevel.Inherit, + label: "inherit", + description: "Inherit session default", + }, + [ThinkingLevel.Off]: { value: ThinkingLevel.Off, label: "off", description: "No reasoning" }, + [ThinkingLevel.Minimal]: { + value: ThinkingLevel.Minimal, + label: "min", + description: "Very brief reasoning (~1k tokens)", + }, + [ThinkingLevel.Low]: { value: ThinkingLevel.Low, label: "low", description: "Light reasoning (~2k tokens)" }, + [ThinkingLevel.Medium]: { + value: ThinkingLevel.Medium, + label: "medium", + description: "Moderate reasoning (~8k tokens)", + }, + [ThinkingLevel.High]: { value: ThinkingLevel.High, label: "high", description: "Deep reasoning (~16k tokens)" }, + [ThinkingLevel.XHigh]: { + value: ThinkingLevel.XHigh, + label: "xhigh", + description: "Maximum reasoning (~32k tokens)", + }, +}; + +const THINKING_LEVELS = new Set([ThinkingLevel.Inherit, ThinkingLevel.Off, ...THINKING_EFFORTS]); +const EFFORT_LEVELS = new Set(THINKING_EFFORTS); + +/** + * Parses a provider-facing effort value. + */ +export function parseEffort(value: string | null | undefined): Effort | undefined { + return value !== undefined && value !== null && EFFORT_LEVELS.has(value) ? (value as Effort) : undefined; +} + +/** + * Parses an agent-local thinking selector. + */ +export function parseThinkingLevel(value: string | null | undefined): ThinkingLevel | undefined { + return value !== undefined && value !== null && THINKING_LEVELS.has(value) ? (value as ThinkingLevel) : undefined; +} + +/** + * Returns display metadata for a thinking selector. + */ +export function getThinkingLevelMetadata(level: ThinkingLevel): ThinkingLevelMetadata { + return THINKING_LEVEL_METADATA[level]; +} + +/** + * Converts an agent-local selector into the effort sent to providers. + */ +export function toReasoningEffort(level: ThinkingLevel | undefined): Effort | undefined { + if (level === undefined || level === ThinkingLevel.Off || level === ThinkingLevel.Inherit) { + return undefined; + } + return level; +} + +/** + * Resolves a selector against the current model while preserving explicit "off". + */ +export function resolveThinkingLevelForModel( + model: Model | undefined, + level: ThinkingLevel | undefined, +): ResolvedThinkingLevel | undefined { + if (level === undefined || level === ThinkingLevel.Inherit) { + return undefined; + } + if (level === ThinkingLevel.Off) { + return ThinkingLevel.Off; + } + return clampThinkingLevelForModel(model, level); +} diff --git a/packages/coding-agent/test/agent-session-role-thinking.test.ts b/packages/coding-agent/test/agent-session-role-thinking.test.ts index e0a8ce4a0..f99bcb6d2 100644 --- a/packages/coding-agent/test/agent-session-role-thinking.test.ts +++ b/packages/coding-agent/test/agent-session-role-thinking.test.ts @@ -1,13 +1,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as path from "node:path"; import { Agent } from "@oh-my-pi/pi-agent-core"; -import { - getBundledModel, - getBundledModels, - getBundledProviders, - supportsXhigh, - type ThinkingLevel, -} from "@oh-my-pi/pi-ai"; +import { Effort, getBundledModel } from "@oh-my-pi/pi-ai"; import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; @@ -37,18 +31,9 @@ describe("AgentSession role model thinking behavior", () => { return model; } - function getReasoningModelWithoutXhighOrThrow() { - for (const provider of getBundledProviders()) { - for (const model of getBundledModels(provider as Parameters[0])) { - if (model.reasoning && !supportsXhigh(model)) return model; - } - } - throw new Error("Expected at least one bundled reasoning model without xhigh support"); - } - async function createSession(options: { initialModelId: string; - initialThinkingLevel: ThinkingLevel; + initialThinkingLevel: Effort; modelRoles: Record; }) { const model = getAnthropicModelOrThrow(options.initialModelId); @@ -83,7 +68,7 @@ describe("AgentSession role model thinking behavior", () => { await createSession({ initialModelId: defaultModel.id, - initialThinkingLevel: "high", + initialThinkingLevel: Effort.High, modelRoles: { default: `${defaultModel.provider}/${defaultModel.id}`, slow: `${slowModel.provider}/${slowModel.id}:off`, @@ -96,13 +81,13 @@ describe("AgentSession role model thinking behavior", () => { expect(firstSwitch?.thinkingLevel).toBe("off"); expect(session.thinkingLevel).toBe("off"); - session.setThinkingLevel("high"); - expect(session.thinkingLevel).toBe("high"); + session.setThinkingLevel(Effort.High); + expect(session.thinkingLevel).toBe(Effort.High); const secondSwitch = await session.cycleRoleModels(["default", "slow"]); expect(secondSwitch?.role).toBe("default"); expect(secondSwitch?.model.id).toBe(defaultModel.id); - expect(session.thinkingLevel).toBe("high"); + expect(session.thinkingLevel).toBe(Effort.High); const thirdSwitch = await session.cycleRoleModels(["default", "slow"]); expect(thirdSwitch?.role).toBe("slow"); @@ -117,7 +102,7 @@ describe("AgentSession role model thinking behavior", () => { await createSession({ initialModelId: defaultModel.id, - initialThinkingLevel: "low", + initialThinkingLevel: Effort.Low, modelRoles: { default: `${defaultModel.provider}/${defaultModel.id}`, slow: `${slowModel.provider}/${slowModel.id}:high`, @@ -126,17 +111,17 @@ describe("AgentSession role model thinking behavior", () => { const toSlow = await session.cycleRoleModels(["default", "slow"]); expect(toSlow?.role).toBe("slow"); - expect(toSlow?.thinkingLevel).toBe("high"); - expect(session.thinkingLevel).toBe("high"); + expect(toSlow?.thinkingLevel).toBe(Effort.High); + expect(session.thinkingLevel).toBe(Effort.High); - session.setThinkingLevel("minimal"); - expect(session.thinkingLevel).toBe("minimal"); + session.setThinkingLevel(Effort.Minimal); + expect(session.thinkingLevel).toBe(Effort.Minimal); const toDefault = await session.cycleRoleModels(["default", "slow"]); expect(toDefault?.role).toBe("default"); expect(toDefault?.model.id).toBe(defaultModel.id); - expect(toDefault?.thinkingLevel).toBe("minimal"); - expect(session.thinkingLevel).toBe("minimal"); + expect(toDefault?.thinkingLevel).toBe(Effort.Minimal); + expect(session.thinkingLevel).toBe(Effort.Minimal); }); it("applies slow role thinking even when plan shares the same model", async () => { @@ -146,7 +131,7 @@ describe("AgentSession role model thinking behavior", () => { await createSession({ initialModelId: defaultModel.id, - initialThinkingLevel: "medium", + initialThinkingLevel: Effort.Medium, modelRoles: { default: `${defaultModel.provider}/${defaultModel.id}`, smol: `${smolModel.provider}/${smolModel.id}:low`, @@ -157,14 +142,14 @@ describe("AgentSession role model thinking behavior", () => { const toSmol = await session.cycleRoleModels(["slow", "default", "smol"]); expect(toSmol?.role).toBe("smol"); - expect(toSmol?.thinkingLevel).toBe("low"); - expect(session.thinkingLevel).toBe("low"); + expect(toSmol?.thinkingLevel).toBe(Effort.Low); + expect(session.thinkingLevel).toBe(Effort.Low); const toSlow = await session.cycleRoleModels(["slow", "default", "smol"]); expect(toSlow?.role).toBe("slow"); expect(toSlow?.model.id).toBe(slowPlanModel.id); - expect(toSlow?.thinkingLevel).toBe("high"); - expect(session.thinkingLevel).toBe("high"); + expect(toSlow?.thinkingLevel).toBe(Effort.High); + expect(session.thinkingLevel).toBe(Effort.High); }); it("preserves explicit role thinking when updating default model despite unresolved previous model", async () => { @@ -173,7 +158,7 @@ describe("AgentSession role model thinking behavior", () => { await createSession({ initialModelId: defaultModel.id, - initialThinkingLevel: "high", + initialThinkingLevel: Effort.High, modelRoles: { default: "anthropic/nonexistent-model:off", }, @@ -184,15 +169,15 @@ describe("AgentSession role model thinking behavior", () => { expect(sessionSettings.getModelRole("default")).toBe(`${slowModel.provider}/${slowModel.id}:off`); }); - it("clamps unsupported xhigh to highest supported level instead of off", async () => { - const model = getReasoningModelWithoutXhighOrThrow(); + it("clamps unsupported selections from model metadata", async () => { + const model = getAnthropicModelOrThrow("claude-sonnet-4-6"); const agent = new Agent({ initialState: { model, systemPrompt: "Test", tools: [], messages: [], - thinkingLevel: "off", + thinkingLevel: undefined, }, }); const authStorage = await AuthStorage.create(path.join(tempDir.path(), "testauth-non-xhigh.db")); @@ -207,7 +192,8 @@ describe("AgentSession role model thinking behavior", () => { modelRegistry, }); - session.setThinkingLevel("xhigh"); - expect(session.thinkingLevel).toBe("high"); + session.setThinkingLevel(Effort.XHigh); + expect(session.thinkingLevel).toBe(Effort.High); + expect(session.getAvailableThinkingLevels()).not.toContain("xhigh"); }); }); diff --git a/packages/coding-agent/test/args.test.ts b/packages/coding-agent/test/args.test.ts index ad940d993..f2ad92d8a 100644 --- a/packages/coding-agent/test/args.test.ts +++ b/packages/coding-agent/test/args.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "bun:test"; +import { Effort } from "@oh-my-pi/pi-ai"; import { parseArgs } from "@oh-my-pi/pi-coding-agent/cli/args"; describe("parseArgs", () => { @@ -133,7 +134,7 @@ describe("parseArgs", () => { test("parses --thinking", () => { const result = parseArgs(["--thinking", "high"]); - expect(result.thinking).toBe("high"); + expect(result.thinking).toBe(Effort.High); }); test("parses --models as comma-separated list", () => { @@ -247,7 +248,7 @@ describe("parseArgs", () => { expect(result.provider).toBe("anthropic"); expect(result.model).toBe("claude-sonnet"); expect(result.print).toBe(true); - expect(result.thinking).toBe("high"); + expect(result.thinking).toBe(Effort.High); expect(result.fileArgs).toEqual(["prompt.md"]); expect(result.messages).toEqual(["Do the task"]); }); diff --git a/packages/coding-agent/test/compaction-thinking-model.test.ts b/packages/coding-agent/test/compaction-thinking-model.test.ts index 4c906b6d0..19f94e65f 100644 --- a/packages/coding-agent/test/compaction-thinking-model.test.ts +++ b/packages/coding-agent/test/compaction-thinking-model.test.ts @@ -13,7 +13,7 @@ import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { Agent } from "@oh-my-pi/pi-agent-core"; -import { getBundledModel, type Model, type ThinkingLevel } from "@oh-my-pi/pi-ai"; +import { Effort, getBundledModel, type Model, type Effort as ThinkingLevelType } from "@oh-my-pi/pi-ai"; import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; @@ -47,7 +47,7 @@ describe.skipIf(!HAS_ANTIGRAVITY_AUTH)("Compaction with thinking models (Antigra async function createSession( modelId: "claude-opus-4-5-thinking" | "claude-sonnet-4-5", - thinkingLevel: ThinkingLevel = "high", + thinkingLevel: ThinkingLevelType = Effort.High, ) { const toolSession: ToolSession = { cwd: tempDir, @@ -92,7 +92,7 @@ describe.skipIf(!HAS_ANTIGRAVITY_AUTH)("Compaction with thinking models (Antigra } it("should compact successfully with claude-opus-4-5-thinking and thinking level high", async () => { - createSession("claude-opus-4-5-thinking", "high"); + createSession("claude-opus-4-5-thinking", Effort.High); // Send a simple prompt await session.prompt("Write down the first 10 prime numbers."); @@ -119,7 +119,7 @@ describe.skipIf(!HAS_ANTIGRAVITY_AUTH)("Compaction with thinking models (Antigra }, 180000); it("should compact successfully with claude-sonnet-4-5 (non-thinking) for comparison", async () => { - createSession("claude-sonnet-4-5", "off"); + createSession("claude-sonnet-4-5"); await session.prompt("Write down the first 10 prime numbers."); await session.agent.waitForIdle(); @@ -156,7 +156,7 @@ describe.skipIf(!HAS_ANTHROPIC_AUTH)("Compaction with thinking models (Anthropic } }); - async function createSession(model: Model, thinkingLevel: ThinkingLevel = "high") { + async function createSession(model: Model, thinkingLevel: ThinkingLevelType = Effort.High) { const toolSession: ToolSession = { cwd: tempDir, hasUI: false, @@ -196,7 +196,7 @@ describe.skipIf(!HAS_ANTHROPIC_AUTH)("Compaction with thinking models (Anthropic it("should compact successfully with claude-3-7-sonnet and thinking level high", async () => { const model = getBundledModel("anthropic", "claude-3-7-sonnet-latest")!; - createSession(model, "high"); + createSession(model, Effort.High); // Send a simple prompt await session.prompt("Write down the first 10 prime numbers."); diff --git a/packages/coding-agent/test/discovery/agent-fields.test.ts b/packages/coding-agent/test/discovery/agent-fields.test.ts index 446de1a93..92e61139c 100644 --- a/packages/coding-agent/test/discovery/agent-fields.test.ts +++ b/packages/coding-agent/test/discovery/agent-fields.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "bun:test"; +import { Effort } from "@oh-my-pi/pi-ai"; import { parseAgentFields } from "../../src/discovery/helpers"; describe("parseAgentFields", () => { @@ -42,7 +43,7 @@ describe("parseAgentFields", () => { }); expect(fields).toBeDefined(); - expect(fields?.thinkingLevel).toBe("medium"); + expect(fields?.thinkingLevel).toBe(Effort.Medium); }); test("prefers thinking-level over legacy thinking", () => { @@ -50,9 +51,9 @@ describe("parseAgentFields", () => { name: "reviewer", description: "desc", thinking: "minimal", - thinkingLevel: "high", + thinkingLevel: Effort.High, }); - expect(fields?.thinkingLevel).toBe("high"); + expect(fields?.thinkingLevel).toBe(Effort.High); }); }); diff --git a/packages/coding-agent/test/model-registry-runtime-provider.test.ts b/packages/coding-agent/test/model-registry-runtime-provider.test.ts index 29132100a..dd9dcf09a 100644 --- a/packages/coding-agent/test/model-registry-runtime-provider.test.ts +++ b/packages/coding-agent/test/model-registry-runtime-provider.test.ts @@ -5,6 +5,7 @@ import * as path from "node:path"; import { type AssistantMessageEventStream, clearCustomApis, + Effort, getCustomApi, getOAuthProviders, type OAuthCredentials, @@ -102,6 +103,36 @@ describe("ModelRegistry runtime provider registration", () => { expect(model?.headers?.["X-Model"]).toBe("model-header"); }); + test("registerProvider preserves explicit thinking on runtime models", () => { + const registry = new ModelRegistry(authStorage, modelsJsonPath); + const config: ProviderConfigInput = { + baseUrl: "https://runtime.example.com/v1", + apiKey: "RUNTIME_KEY", + api: "anthropic-messages", + models: [ + { + ...baseModel, + id: "runtime-thinking-model", + reasoning: true, + thinking: { + mode: "anthropic-adaptive", + minLevel: Effort.Minimal, + maxLevel: Effort.High, + }, + }, + ], + }; + + registry.registerProvider("runtime-provider", config, "ext://runtime"); + const model = registry.find("runtime-provider", "runtime-thinking-model"); + + expect(model?.thinking).toEqual({ + mode: "anthropic-adaptive", + minLevel: Effort.Minimal, + maxLevel: Effort.High, + }); + }); + test("clearSourceRegistrations and syncExtensionSources remove source-scoped API and OAuth providers", () => { const registry = new ModelRegistry(authStorage, modelsJsonPath); const oauthCredentials: OAuthCredentials = { diff --git a/packages/coding-agent/test/model-registry.test.ts b/packages/coding-agent/test/model-registry.test.ts index 41c97252a..a0eefd746 100644 --- a/packages/coding-agent/test/model-registry.test.ts +++ b/packages/coding-agent/test/model-registry.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; -import type { OpenAICompat } from "@oh-my-pi/pi-ai"; +import { Effort, type OpenAICompat, type ThinkingConfig } from "@oh-my-pi/pi-ai"; import { kNoAuth, MODEL_ROLES, ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { Snowflake } from "@oh-my-pi/pi-utils"; @@ -38,6 +38,7 @@ describe("ModelRegistry", () => { id: string; name: string; reasoning: boolean; + thinking?: ThinkingConfig; input: string[]; cost: { input: number; output: number; cacheRead: number; cacheWrite: number }; contextWindow: number; @@ -48,7 +49,7 @@ describe("ModelRegistry", () => { /** Create minimal provider config */ function providerConfig( baseUrl: string, - models: Array<{ id: string; name?: string }>, + models: Array<{ id: string; name?: string; reasoning?: boolean; thinking?: ThinkingConfig }>, api: string = "anthropic-messages", ) { return { @@ -58,7 +59,8 @@ describe("ModelRegistry", () => { models: models.map(m => ({ id: m.id, name: m.name ?? m.id, - reasoning: false, + reasoning: m.reasoning ?? false, + thinking: m.thinking, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 100000, @@ -350,6 +352,48 @@ describe("ModelRegistry", () => { }); }); + describe("thinking metadata normalization", () => { + test("custom models preserve explicit thinking", () => { + const thinking: ThinkingConfig = { + mode: "anthropic-adaptive", + minLevel: Effort.Minimal, + maxLevel: Effort.High, + }; + + writeModelsJson({ + anthropic: providerConfig("https://my-proxy.example.com/v1", [ + { id: "claude-custom", reasoning: true, thinking }, + ]), + }); + + const registry = new ModelRegistry(authStorage, modelsJsonPath); + const model = getModelsForProvider(registry, "anthropic").find(m => m.id === "claude-custom"); + + expect(model?.thinking).toEqual(thinking); + }); + + test("model overrides can replace canonical thinking metadata", () => { + writeRawModelsJson({ + openrouter: { + modelOverrides: { + "anthropic/claude-sonnet-4": { + thinking: { mode: "budget", minLevel: Effort.Low, maxLevel: Effort.Medium }, + }, + }, + }, + }); + + const registry = new ModelRegistry(authStorage, modelsJsonPath); + const model = getModelsForProvider(registry, "openrouter").find(m => m.id === "anthropic/claude-sonnet-4"); + + expect(model?.thinking).toEqual({ + mode: "budget", + minLevel: Effort.Low, + maxLevel: Effort.Medium, + }); + }); + }); + describe("modelOverrides (per-model customization)", () => { test("model override applies to a single built-in model", () => { writeRawModelsJson({ diff --git a/packages/coding-agent/test/model-resolver.test.ts b/packages/coding-agent/test/model-resolver.test.ts index d9c2485e2..2d9392d23 100644 --- a/packages/coding-agent/test/model-resolver.test.ts +++ b/packages/coding-agent/test/model-resolver.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import type { Model } from "@oh-my-pi/pi-ai"; +import { Effort, type Model } from "@oh-my-pi/pi-ai"; import { parseModelPattern, parseModelString, @@ -18,6 +18,11 @@ const mockModels: Model<"anthropic-messages">[] = [ provider: "anthropic", baseUrl: "https://api.anthropic.com", reasoning: true, + thinking: { + mode: "budget", + minLevel: Effort.Minimal, + maxLevel: Effort.High, + }, input: ["text", "image"], cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }, contextWindow: 200000, @@ -46,6 +51,11 @@ const mockOpenRouterModels: Model<"anthropic-messages">[] = [ provider: "openrouter", baseUrl: "https://openrouter.ai/api/v1", reasoning: true, + thinking: { + mode: "budget", + minLevel: Effort.Minimal, + maxLevel: Effort.High, + }, input: ["text"], cost: { input: 1, output: 2, cacheRead: 0.1, cacheWrite: 1 }, contextWindow: 128000, @@ -100,6 +110,11 @@ const mockCodexOverlapModels: Model<"anthropic-messages">[] = [ provider: "openai-codex", baseUrl: "https://api.openai.com", reasoning: true, + thinking: { + mode: "effort", + minLevel: Effort.Low, + maxLevel: Effort.XHigh, + }, input: ["text"], cost: { input: 1.5, output: 6, cacheRead: 0.15, cacheWrite: 1.5 }, contextWindow: 200000, @@ -112,6 +127,11 @@ const mockCodexOverlapModels: Model<"anthropic-messages">[] = [ provider: "openai-codex", baseUrl: "https://api.openai.com", reasoning: true, + thinking: { + mode: "effort", + minLevel: Effort.Low, + maxLevel: Effort.XHigh, + }, input: ["text"], cost: { input: 1, output: 4, cacheRead: 0.1, cacheWrite: 1 }, contextWindow: 200000, @@ -152,19 +172,19 @@ describe("parseModelPattern", () => { test("sonnet:high returns sonnet with high thinking level", () => { const result = parseModelPattern("sonnet:high", allModels); expect(result.model?.id).toBe("claude-sonnet-4-5"); - expect(result.thinkingLevel).toBe("high"); + expect(result.thinkingLevel).toBe(Effort.High); expect(result.warning).toBeUndefined(); }); test("gpt-4o:medium returns gpt-4o with medium thinking level", () => { const result = parseModelPattern("gpt-4o:medium", allModels); expect(result.model?.id).toBe("gpt-4o"); - expect(result.thinkingLevel).toBe("medium"); + expect(result.thinkingLevel).toBe(Effort.Medium); expect(result.warning).toBeUndefined(); }); test("all valid thinking levels work", () => { - const levels = ["off", "minimal", "low", "medium", "high", "xhigh"] as const; + const levels = ["off", Effort.Minimal, Effort.Low, Effort.Medium, Effort.High, Effort.XHigh] as const; for (const level of levels) { const result = parseModelPattern(`sonnet:${level}`, allModels); expect(result.model?.id).toBe("claude-sonnet-4-5"); @@ -214,7 +234,7 @@ describe("parseModelPattern", () => { test("qwen3-coder:exacto:high matches model with high thinking level", () => { const result = parseModelPattern("qwen/qwen3-coder:exacto:high", allModels); expect(result.model?.id).toBe("qwen/qwen3-coder:exacto"); - expect(result.thinkingLevel).toBe("high"); + expect(result.thinkingLevel).toBe(Effort.High); expect(result.explicitThinkingLevel).toBe(true); expect(result.warning).toBeUndefined(); }); @@ -223,7 +243,7 @@ describe("parseModelPattern", () => { const result = parseModelPattern("openrouter/qwen/qwen3-coder:exacto:high", allModels); expect(result.model?.id).toBe("qwen/qwen3-coder:exacto"); expect(result.model?.provider).toBe("openrouter"); - expect(result.thinkingLevel).toBe("high"); + expect(result.thinkingLevel).toBe(Effort.High); expect(result.explicitThinkingLevel).toBe(true); expect(result.warning).toBeUndefined(); }); @@ -308,7 +328,7 @@ describe("resolveModelRoleValue", () => { expect(result.model?.provider).toBe("openrouter"); expect(result.model?.id).toBe("qwen/qwen3-coder:exacto"); - expect(result.thinkingLevel).toBe("high"); + expect(result.thinkingLevel).toBe(Effort.High); expect(result.explicitThinkingLevel).toBe(true); }); @@ -330,15 +350,24 @@ describe("resolveModelRoleValue", () => { const providerQualified = resolveModelRoleValue("openai-codex/gpt-5.3-codex:xhigh", allModels); expect(providerQualified.model?.provider).toBe("openai-codex"); expect(providerQualified.model?.id).toBe("gpt-5.3-codex"); - expect(providerQualified.thinkingLevel).toBe("xhigh"); + expect(providerQualified.thinkingLevel).toBe(Effort.XHigh); expect(providerQualified.explicitThinkingLevel).toBe(true); const idOnly = resolveModelRoleValue("gpt-5.3-codex:xhigh", allModels); expect(idOnly.model?.provider).toBe("openai-codex"); expect(idOnly.model?.id).toBe("gpt-5.3-codex"); - expect(idOnly.thinkingLevel).toBe("xhigh"); + expect(idOnly.thinkingLevel).toBe(Effort.XHigh); expect(idOnly.explicitThinkingLevel).toBe(true); }); + + test("clamps explicit thinking selectors from model metadata", () => { + const result = resolveModelRoleValue("anthropic/claude-sonnet-4-5:xhigh", allModels); + + expect(result.model?.provider).toBe("anthropic"); + expect(result.model?.id).toBe("claude-sonnet-4-5"); + expect(result.thinkingLevel).toBe(Effort.High); + expect(result.explicitThinkingLevel).toBe(true); + }); }); describe("resolveModelFromString", () => { test("falls back to pattern parsing for provider/model:thinking when strict provider+id miss", () => { @@ -376,7 +405,7 @@ describe("resolveModelOverride", () => { expect(result.model?.provider).toBe("openrouter"); expect(result.model?.id).toBe("qwen/qwen3-coder:exacto"); - expect(result.thinkingLevel).toBe("high"); + expect(result.thinkingLevel).toBe(Effort.High); expect(result.explicitThinkingLevel).toBe(true); }); }); @@ -424,7 +453,7 @@ describe("resolveCliModel", () => { expect(result.error).toBeUndefined(); expect(result.model?.id).toBe("claude-sonnet-4-5"); - expect(result.thinkingLevel).toBe("high"); + expect(result.thinkingLevel).toBe(Effort.High); }); test("prefers exact model id match over provider inference (OpenRouter-style ids)", () => { @@ -507,11 +536,11 @@ describe("parseModelString", () => { describe("thinking level suffix extraction", () => { test("extracts valid thinking level from provider/id:level", () => { const result = parseModelString("anthropic/claude-sonnet-4-5:high"); - expect(result).toEqual({ provider: "anthropic", id: "claude-sonnet-4-5", thinkingLevel: "high" }); + expect(result).toEqual({ provider: "anthropic", id: "claude-sonnet-4-5", thinkingLevel: Effort.High }); }); test("extracts all valid thinking levels", () => { - const levels = ["off", "minimal", "low", "medium", "high", "xhigh"] as const; + const levels = ["off", Effort.Minimal, Effort.Low, Effort.Medium, Effort.High, Effort.XHigh] as const; for (const level of levels) { const result = parseModelString(`anthropic/claude-sonnet-4-5:${level}`); expect(result?.id).toBe("claude-sonnet-4-5"); @@ -527,7 +556,11 @@ describe("parseModelString", () => { test("handles model ID with colon followed by valid thinking level", () => { // e.g. "openrouter/qwen/qwen3-coder:exacto:high" — last colon is thinking level const result = parseModelString("openrouter/qwen/qwen3-coder:exacto:high"); - expect(result).toEqual({ provider: "openrouter", id: "qwen/qwen3-coder:exacto", thinkingLevel: "high" }); + expect(result).toEqual({ + provider: "openrouter", + id: "qwen/qwen3-coder:exacto", + thinkingLevel: Effort.High, + }); }); test("does not extract thinking level from model ID with invalid suffix", () => { diff --git a/packages/coding-agent/test/rpc.test.ts b/packages/coding-agent/test/rpc.test.ts index c176efc24..8ebf6b65a 100644 --- a/packages/coding-agent/test/rpc.test.ts +++ b/packages/coding-agent/test/rpc.test.ts @@ -3,7 +3,7 @@ import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import type { AgentEvent, AgentMessage } from "@oh-my-pi/pi-agent-core"; -import type { AssistantMessage, TextContent } from "@oh-my-pi/pi-ai"; +import { type AssistantMessage, Effort, type TextContent } from "@oh-my-pi/pi-ai"; import { type CompactionEntry, type FileEntry, @@ -198,11 +198,11 @@ describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("RPC mode", () => { await client.start(); // Set thinking level - await client.setThinkingLevel("high"); + await client.setThinkingLevel(Effort.High); // Verify via state const state = await client.getState(); - expect(state.thinkingLevel).toBe("high"); + expect(state.thinkingLevel).toBe(Effort.High); }, 30000); test("should cycle thinking level", async () => { diff --git a/packages/coding-agent/test/settings-manager.test.ts b/packages/coding-agent/test/settings-manager.test.ts index 1c1d2037a..3666eb294 100644 --- a/packages/coding-agent/test/settings-manager.test.ts +++ b/packages/coding-agent/test/settings-manager.test.ts @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; +import { Effort } from "@oh-my-pi/pi-ai"; import { _resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { getPathsForTab, getUi } from "@oh-my-pi/pi-coding-agent/config/settings-schema"; import { getProjectAgentDir, Snowflake } from "@oh-my-pi/pi-utils"; @@ -70,12 +71,12 @@ describe("Settings", () => { }); // Settings saves a change - should merge, not overwrite - settings.set("defaultThinkingLevel", "high"); + settings.set("defaultThinkingLevel", Effort.High); await settings.flush(); const savedSettings = await readSettings(); expect(savedSettings.enabledModels).toEqual(["claude-opus-4-5", "gpt-5.2-codex"]); - expect(savedSettings.defaultThinkingLevel).toBe("high"); + expect(savedSettings.defaultThinkingLevel).toBe(Effort.High); expect(savedSettings.theme).toEqual({ dark: "anthracite" }); expect((savedSettings.modelRoles as { default?: string } | undefined)?.default).toBe("claude-sonnet"); }); @@ -111,14 +112,14 @@ describe("Settings", () => { await writeSettings({ theme: { dark: "anthracite" }, - defaultThinkingLevel: "low", + defaultThinkingLevel: Effort.Low, }); - settings.set("defaultThinkingLevel", "high"); + settings.set("defaultThinkingLevel", Effort.High); await settings.flush(); const savedSettings = await readSettings(); - expect(savedSettings.defaultThinkingLevel).toBe("high"); + expect(savedSettings.defaultThinkingLevel).toBe(Effort.High); }); }); describe("compaction remote setting", () => { diff --git a/packages/coding-agent/test/task/executor-subagent-reminders.test.ts b/packages/coding-agent/test/task/executor-subagent-reminders.test.ts index 41ad99d0d..297477346 100644 --- a/packages/coding-agent/test/task/executor-subagent-reminders.test.ts +++ b/packages/coding-agent/test/task/executor-subagent-reminders.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it, vi } from "bun:test"; -import type { AssistantMessage } from "@oh-my-pi/pi-ai"; +import { type AssistantMessage, Effort } from "@oh-my-pi/pi-ai"; import { Settings } from "../../src/config/settings"; import type { LoadExtensionsResult } from "../../src/extensibility/extensions/types"; import * as sdkModule from "../../src/sdk"; @@ -241,7 +241,7 @@ describe("runSubprocess submit_result reminders", () => { ...baseOptions, id: "subagent-thinking-fallback", modelOverride: "openai/gpt-4o", - thinkingLevel: "high", + thinkingLevel: Effort.High, modelRegistry, }); @@ -260,7 +260,7 @@ describe("runSubprocess submit_result reminders", () => { } as unknown as import("../../src/config/model-registry").ModelRegistry; const cases = [ - { modelOverride: "openai/gpt-4o:low", expectedThinkingLevel: "low" }, + { modelOverride: "openai/gpt-4o:low", expectedThinkingLevel: Effort.Low }, { modelOverride: "openai/gpt-4o:off", expectedThinkingLevel: "off" }, ] as const; @@ -290,7 +290,7 @@ describe("runSubprocess submit_result reminders", () => { ...baseOptions, id: `subagent-thinking-override-${index}`, modelOverride: testCase.modelOverride, - thinkingLevel: "high", + thinkingLevel: Effort.High, modelRegistry, }); } diff --git a/packages/react-edit-benchmark/src/index.ts b/packages/react-edit-benchmark/src/index.ts index 3176b18de..e95991add 100644 --- a/packages/react-edit-benchmark/src/index.ts +++ b/packages/react-edit-benchmark/src/index.ts @@ -11,13 +11,22 @@ import * as fs from "node:fs"; import * as path from "node:path"; import { parseArgs } from "node:util"; -import { getAvailableThinkingLevels, parseThinkingLevel, type ThinkingLevel } from "@oh-my-pi/pi-ai"; +import { type ResolvedThinkingLevel, ThinkingLevel } from "@oh-my-pi/pi-agent-core"; +import { Effort, THINKING_EFFORTS } from "@oh-my-pi/pi-ai"; import { padding } from "@oh-my-pi/pi-tui"; import { TempDir } from "@oh-my-pi/pi-utils"; import { generateJsonReport, generateReport } from "./report"; import { type BenchmarkConfig, type ProgressEvent, runBenchmark } from "./runner"; import { type EditTask, loadTasksFromDir, validateFixturesFromDir } from "./tasks"; +function parseThinkingLevel(value: string | null | undefined): ResolvedThinkingLevel | undefined { + return value !== undefined && + value !== null && + [ThinkingLevel.Off, ...THINKING_EFFORTS].includes(value as ResolvedThinkingLevel) + ? (value as ResolvedThinkingLevel) + : undefined; +} + function generateReportFilename(config: BenchmarkConfig, format: "markdown" | "json"): string { const modelName = config.model .split("/") @@ -207,12 +216,12 @@ async function main(): Promise { process.exit(0); } - let thinkingLevel: ThinkingLevel = "low"; + let thinkingLevel: ResolvedThinkingLevel = Effort.Low; if (values.thinking) { const level = parseThinkingLevel(values.thinking); if (!level) { console.error(`Invalid thinking level: ${values.thinking}`); - console.error(`Valid levels: ${getAvailableThinkingLevels().join(", ")}`); + console.error(`Valid levels: ${[ThinkingLevel.Off, ...THINKING_EFFORTS].join(", ")}`); process.exit(1); } thinkingLevel = level; diff --git a/packages/react-edit-benchmark/src/runner.ts b/packages/react-edit-benchmark/src/runner.ts index 24855a696..d7fd303a3 100644 --- a/packages/react-edit-benchmark/src/runner.ts +++ b/packages/react-edit-benchmark/src/runner.ts @@ -7,7 +7,7 @@ /// import * as fs from "node:fs"; import * as path from "node:path"; -import type { ThinkingLevel } from "@oh-my-pi/pi-ai"; +import type { ResolvedThinkingLevel } from "@oh-my-pi/pi-agent-core"; import { computeLineHash, RpcClient, renderPromptTemplate } from "@oh-my-pi/pi-coding-agent"; import { Snowflake } from "@oh-my-pi/pi-utils"; @@ -31,7 +31,7 @@ function makeTempDir(pre?: string): string { export interface BenchmarkConfig { provider: string; model: string; - thinkingLevel?: ThinkingLevel; + thinkingLevel?: ResolvedThinkingLevel; runsPerTask: number; timeout: number; maxTurns?: number; diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/analyze_small_edits.py b/scripts/analyze_small_edits.py new file mode 100644 index 000000000..616182666 --- /dev/null +++ b/scripts/analyze_small_edits.py @@ -0,0 +1,428 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +from collections.abc import Iterable +from dataclasses import asdict, dataclass +from pathlib import Path +import argparse +import json +import re +import sys + +if __package__ in (None, ""): + sys.path.insert(0, str(Path(__file__).resolve().parent)) + from tool_io import ReservoirSample, ToolIOConfig, ToolInvocation, iter_tool_invocations, list_recent_session_files +else: + from scripts.tool_io import ReservoirSample, ToolIOConfig, ToolInvocation, iter_tool_invocations, list_recent_session_files + +TOOL_NAMES = ("edit", "ast_edit") + + +@dataclass(slots=True) +class DiffSummary: + small: bool + added_lines: int + removed_lines: int + changed_lines: int + changed_preview: list[str] + category: str | None = None + + +@dataclass(slots=True) +class CompletedEdit: + session_file: str + tool_call_id: str + tool_name: str + path: str + args: dict[str, object] + result_text: str + diff: str | None + is_error: bool + assistant_thinking: str | None + assistant_timestamp: str | None + tool_timestamp: str | None + issue: str + small: bool + small_category: str | None + added_lines: int + removed_lines: int + changed_lines: int + changed_preview: list[str] + + +@dataclass(slots=True) +class PreviousEditSummary: + tool_name: str + path: str + issue: str + is_error: bool + small: bool + same_path: bool + changed_preview: list[str] + + +@dataclass(slots=True) +class Candidate: + kind: str + edit: CompletedEdit + previous_edit: PreviousEditSummary | None = None + + +@dataclass(slots=True) +class RunStats: + files_scanned: int = 0 + total_edit_attempts: int = 0 + failed_edits: int = 0 + small_edits: int = 0 + small_edits_with_previous_edit: int = 0 + small_edits_with_previous_same_path: int = 0 + small_edits_after_failed_edit: int = 0 + small_edits_after_same_path_failed_edit: int = 0 + + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Analyze small edit/ast_edit tool usage in session logs.") + parser.add_argument("--sessions-dir", type=Path, default=Path.home() / ".omp" / "agent" / "sessions") + parser.add_argument("--sample-size", type=positive_int, default=30) + parser.add_argument("--max-files", type=positive_int, default=500) + parser.add_argument("--since-days", type=positive_int, default=30) + parser.add_argument("--max-items", type=positive_int, default=50_000) + parser.add_argument("--limit-mode", choices=("calls", "events"), default="calls") + parser.add_argument("--json", action="store_true") + return parser.parse_args() + + + +def positive_int(value: str) -> int: + parsed = int(value) + if parsed <= 0: + raise argparse.ArgumentTypeError("value must be a positive integer") + return parsed + + + +def strip_decorations(line: str) -> str: + return re.sub(r"^\s*\d+\s+", "", line).strip() + + +def is_delimiter_line(line: str) -> bool: + return bool(re.match(r"^[\]}),;]+$", line)) + + + +def is_tiny_structural_line(line: str) -> bool: + if len(line) == 0: + return True + if is_delimiter_line(line): + return True + if re.match(r"^(pub\s+mod|pub\s+use|mod|use|import|export)\b", line): + return True + if re.match(r"^(return|break|continue);$", line): + return True + if re.match(r"^[A-Za-z0-9_.$]+\([^)]*\);$", line) and len(line) <= 60: + return True + return False + + + +def classify_success_issue(summary: DiffSummary) -> str: + previews = summary.changed_preview + if previews and all(len(line) == 0 for line in previews): + return "blank-line-adjustment" + if previews and all(is_delimiter_line(line) for line in previews): + return "delimiter-adjustment" + if previews and all(re.match(r"^(pub\s+mod|pub\s+use|mod|use|import|export)\b", line) for line in previews): + return "import-or-module-tweak" + if summary.removed_lines == 1 and summary.added_lines == 0: + return "single-line-delete" + if summary.added_lines == 1 and summary.removed_lines == 0: + return "single-line-add" + if summary.added_lines == 1 and summary.removed_lines == 1: + return "single-line-replace" + return "small-structural-fix" + + + +def classify_failure_issue(result_text: str) -> str: + if re.search(r"identical content|No changes made", result_text, re.IGNORECASE): + return "no-op-identical" + if re.search(r"Failed to find context|matches for context|expected lines|tag mismatch|>>>", result_text, re.IGNORECASE): + return "context-mismatch" + if re.search(r"Unexpected line in hunk|parse error|SyntaxError", result_text, re.IGNORECASE): + return "invalid-patch-shape" + if re.search(r"File not found", result_text, re.IGNORECASE): + return "missing-file" + if re.search(r"occurrence|ambiguous", result_text, re.IGNORECASE): + return "ambiguous-target" + if re.search(r"Validation failed|required property|must have required property", result_text, re.IGNORECASE): + return "invalid-arguments" + return "other-failure" + + + +def summarize_diff(diff: str | None) -> DiffSummary: + if not diff: + return DiffSummary( + small=False, + added_lines=0, + removed_lines=0, + changed_lines=0, + changed_preview=[], + ) + + added: list[str] = [] + removed: list[str] = [] + for raw_line in diff.splitlines(): + if raw_line.startswith(("+++", "---", "@@")): + continue + if raw_line.startswith("+"): + added.append(strip_decorations(raw_line[1:])) + continue + if raw_line.startswith("-"): + removed.append(strip_decorations(raw_line[1:])) + + all_changes = [*removed, *added] + include_blank = previews_need_blank_marker(all_changes) + previews = [line for line in all_changes if line or include_blank] + changed_lines = len(added) + len(removed) + tiny_only = all(is_tiny_structural_line(line) for line in all_changes) + small = changed_lines > 0 and (changed_lines <= 2 or (changed_lines <= 4 and tiny_only)) + preview_slice = previews[:4] + category = None + if small: + category = classify_success_issue( + DiffSummary( + small=small, + added_lines=len(added), + removed_lines=len(removed), + changed_lines=changed_lines, + changed_preview=preview_slice, + ) + ) + return DiffSummary( + small=small, + added_lines=len(added), + removed_lines=len(removed), + changed_lines=changed_lines, + changed_preview=preview_slice, + category=category, + ) + + + +def previews_need_blank_marker(lines: list[str]) -> bool: + return any(len(line) == 0 for line in lines) + + + +def build_completed_edit(invocation: ToolInvocation) -> CompletedEdit | None: + if not invocation.has_result: + return None + diff_summary = summarize_diff(invocation.diff) + is_error = invocation.is_error + issue = classify_failure_issue(invocation.result_text) if is_error else (diff_summary.category or "other-success") + return CompletedEdit( + session_file=str(invocation.session_file), + tool_call_id=invocation.tool_call_id, + tool_name=invocation.tool_name, + path=invocation.path_hint, + args=invocation.arguments, + result_text=invocation.result_text, + diff=invocation.diff, + is_error=is_error, + assistant_thinking=invocation.assistant_thinking, + assistant_timestamp=invocation.assistant_timestamp, + tool_timestamp=invocation.tool_timestamp, + issue=issue, + small=(not is_error and diff_summary.small), + small_category=diff_summary.category, + added_lines=diff_summary.added_lines, + removed_lines=diff_summary.removed_lines, + changed_lines=diff_summary.changed_lines, + changed_preview=diff_summary.changed_preview, + ) + + + +def analyze_small_edits(stream: Iterable[ToolInvocation], *, sample_size: int, files_scanned: int) -> dict[str, object]: + sample: ReservoirSample[Candidate] = ReservoirSample(size=sample_size) + issue_counts: dict[str, int] = {} + stats = RunStats(files_scanned=files_scanned) + last_edit: CompletedEdit | None = None + + for invocation in stream: + completed = build_completed_edit(invocation) + if completed is None: + continue + stats.total_edit_attempts += 1 + + if completed.is_error: + stats.failed_edits += 1 + issue_counts[completed.issue] = issue_counts.get(completed.issue, 0) + 1 + sample.add(Candidate(kind="failed", edit=completed)) + + if completed.small: + stats.small_edits += 1 + issue_counts[completed.issue] = issue_counts.get(completed.issue, 0) + 1 + previous = None + if last_edit is not None: + stats.small_edits_with_previous_edit += 1 + if last_edit.is_error: + stats.small_edits_after_failed_edit += 1 + if last_edit.path and last_edit.path == completed.path: + stats.small_edits_with_previous_same_path += 1 + if last_edit.is_error: + stats.small_edits_after_same_path_failed_edit += 1 + previous = PreviousEditSummary( + tool_name=last_edit.tool_name, + path=last_edit.path, + issue=last_edit.issue, + is_error=last_edit.is_error, + small=last_edit.small, + same_path=last_edit.path == completed.path, + changed_preview=last_edit.changed_preview, + ) + sample.add(Candidate(kind="small", edit=completed, previous_edit=previous)) + + last_edit = completed + + return { + "stats": asdict(stats), + "top_issues": top_entries(issue_counts, 20), + "sample": [candidate_to_dict(candidate) for candidate in sample.items], + } + + + +def candidate_to_dict(candidate: Candidate) -> dict[str, object]: + payload = {"kind": candidate.kind, "edit": asdict(candidate.edit)} + if candidate.previous_edit is not None: + payload["previous_edit"] = asdict(candidate.previous_edit) + return payload + + + +def top_entries(counts: dict[str, int], limit: int) -> list[dict[str, object]]: + return [ + {"name": name, "count": count} + for name, count in sorted(counts.items(), key=lambda entry: (-entry[1], entry[0]))[:limit] + ] + + + +def short_path(target_path: str) -> str: + home = str(Path.home()) + return f"~{target_path[len(home):]}" if target_path.startswith(home) else target_path + + + +def truncate(text: str, limit: int) -> str: + if len(text) <= limit: + return text + return f"{text[: limit - 1]}…" + + + +def format_sample_entry(candidate: dict[str, object], index: int) -> str: + edit = candidate["edit"] + assert isinstance(edit, dict) + lines = [ + f"{index + 1}. [{candidate['kind']}] {edit['issue']}", + f" file: {Path(str(edit['session_file'])).name}", + f" target: {short_path(str(edit['path'])) if edit['path'] else '(unknown path)'}", + f" tool: {edit['tool_name']}", + ] + if candidate["kind"] == "small": + lines.append( + f" change: +{edit['added_lines']} / -{edit['removed_lines']} ({edit['changed_lines']} changed line(s))" + ) + changed_preview = edit.get("changed_preview") + if isinstance(changed_preview, list) and changed_preview: + lines.append(f" preview: {' | '.join(str(item) for item in changed_preview)}") + previous = candidate.get("previous_edit") + if isinstance(previous, dict): + path_part = f" ({short_path(str(previous['path']))})" if previous.get("path") else "" + lines.append( + " previous edit: " + f"{'same-path' if previous.get('same_path') else 'other-path'} " + f"{'failed' if previous.get('is_error') else previous.get('issue')}{path_part}" + ) + previous_preview = previous.get("changed_preview") + if isinstance(previous_preview, list) and previous_preview: + lines.append(f" previous preview: {' | '.join(str(item) for item in previous_preview)}") + else: + lines.append(" previous edit: none") + else: + lines.append(f" result: {truncate(' '.join(str(edit['result_text']).split()), 220)}") + changed_preview = edit.get("changed_preview") + if isinstance(changed_preview, list) and changed_preview: + lines.append(f" diff preview: {' | '.join(str(item) for item in changed_preview)}") + return '\n'.join(lines) + + + +def main() -> None: + options = parse_args() + config = ToolIOConfig( + sessions_dir=options.sessions_dir, + since_days=options.since_days, + max_files=options.max_files, + max_items=options.max_items, + limit_mode=options.limit_mode, + include_unresolved=False, + ) + files = list_recent_session_files(config) + stream = iter_tool_invocations(TOOL_NAMES, config) + analysis = analyze_small_edits(stream, sample_size=options.sample_size, files_scanned=len(files)) + + if options.json: + print( + json.dumps( + { + "options": { + "sessions_dir": str(options.sessions_dir), + "sample_size": options.sample_size, + "max_files": options.max_files, + "since_days": options.since_days, + "max_items": options.max_items, + "limit_mode": options.limit_mode, + "json": options.json, + }, + **analysis, + }, + indent=2, + ) + ) + return + + stats = analysis["stats"] + top_issues = analysis["top_issues"] + sample = analysis["sample"] + assert isinstance(stats, dict) + assert isinstance(top_issues, list) + assert isinstance(sample, list) + print(f"Scanned {stats['files_scanned']} session file(s) from {short_path(str(options.sessions_dir))}") + print(f"Edit attempts: {stats['total_edit_attempts']}") + print(f"Failed edits: {stats['failed_edits']}") + print(f"Small edits: {stats['small_edits']}") + print(f"Small edits with previous edit: {stats['small_edits_with_previous_edit']}") + print(f"Small edits with previous same-path edit: {stats['small_edits_with_previous_same_path']}") + print(f"Small edits after failed edit: {stats['small_edits_after_failed_edit']}") + print(f"Small edits after same-path failed edit: {stats['small_edits_after_same_path_failed_edit']}") + print() + print("Top issues:") + for entry in top_issues[:12]: + assert isinstance(entry, dict) + print(f" - {entry['name']}: {entry['count']}") + print() + print(f"Random sample ({len(sample)}):") + for index, candidate in enumerate(sample): + assert isinstance(candidate, dict) + print(format_sample_entry(candidate, index)) + print() + + +if __name__ == "__main__": + main() diff --git a/scripts/tool_io.py b/scripts/tool_io.py new file mode 100644 index 000000000..332f5beb7 --- /dev/null +++ b/scripts/tool_io.py @@ -0,0 +1,386 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +from collections.abc import Iterable, Iterator +from dataclasses import dataclass, field +from pathlib import Path +import json +import random +import time +from typing import Any, Literal + +LimitMode = Literal["calls", "events"] + +DEFAULT_MAX_ITEMS = 50_000 +DEFAULT_SINCE_DAYS = 30 +DEFAULT_MAX_FILES = 500 +DEFAULT_SESSIONS_DIR = Path.home() / ".omp" / "agent" / "sessions" + + +TOOL_GROUPS: dict[str, tuple[str, ...]] = { + "edits": ("edit", "ast_edit"), + "reads": ("read", "grep", "find", "ast_grep", "lsp"), + "writes": ("edit", "ast_edit", "write"), +} + + +@dataclass(slots=True) +class ToolIOConfig: + sessions_dir: Path = DEFAULT_SESSIONS_DIR + since_days: int = DEFAULT_SINCE_DAYS + max_files: int = DEFAULT_MAX_FILES + max_items: int = DEFAULT_MAX_ITEMS + limit_mode: LimitMode = "calls" + include_unresolved: bool = True + + +@dataclass(slots=True) +class ToolCall: + session_file: Path + tool_call_id: str + tool_name: str + arguments: dict[str, Any] + assistant_thinking: str | None = None + assistant_timestamp: str | None = None + path_hint: str = "" + + +@dataclass(slots=True) +class ToolResult: + tool_call_id: str + tool_name: str + is_error: bool + result_text: str + details: dict[str, Any] = field(default_factory=dict) + tool_timestamp: str | None = None + + +@dataclass(slots=True) +class ToolInvocation: + call: ToolCall + result: ToolResult | None = None + + @property + def session_file(self) -> Path: + return self.call.session_file + + @property + def tool_call_id(self) -> str: + return self.call.tool_call_id + + @property + def tool_name(self) -> str: + return self.call.tool_name + + @property + def arguments(self) -> dict[str, Any]: + return self.call.arguments + + @property + def assistant_thinking(self) -> str | None: + return self.call.assistant_thinking + + @property + def assistant_timestamp(self) -> str | None: + return self.call.assistant_timestamp + + @property + def tool_timestamp(self) -> str | None: + return self.result.tool_timestamp if self.result else None + + @property + def path_hint(self) -> str: + return self.call.path_hint + + @property + def has_result(self) -> bool: + return self.result is not None + + @property + def is_error(self) -> bool: + return bool(self.result and self.result.is_error) + + @property + def result_text(self) -> str: + return self.result.result_text if self.result else "" + + @property + def details(self) -> dict[str, Any]: + return self.result.details if self.result else {} + + @property + def diff(self) -> str | None: + diff = self.details.get("diff") + return diff if isinstance(diff, str) else None + + +@dataclass(slots=True) +class ReservoirSample[T]: + size: int + items: list[T] = field(default_factory=list) + seen: int = 0 + rng: random.Random = field(default_factory=random.Random) + + def add(self, item: T) -> None: + if self.size <= 0: + return + self.seen += 1 + if len(self.items) < self.size: + self.items.append(item) + return + index = self.rng.randrange(self.seen) + if index < self.size: + self.items[index] = item + + + +def list_recent_session_files(config: ToolIOConfig) -> list[Path]: + min_mtime = time.time() - config.since_days * 24 * 60 * 60 + candidates: list[tuple[float, Path]] = [] + for session_file in config.sessions_dir.rglob("*.jsonl"): + try: + stat = session_file.stat() + except FileNotFoundError: + continue + if stat.st_mtime < min_mtime: + continue + candidates.append((stat.st_mtime, session_file)) + candidates.sort(key=lambda entry: entry[0], reverse=True) + return [entry[1] for entry in candidates[: config.max_files]] + + + +def iter_tool_invocations( + tool_names: str | Iterable[str], + config: ToolIOConfig | None = None, +) -> Iterator[ToolInvocation]: + resolved = config or ToolIOConfig() + wanted = _normalize_tool_names(tool_names) + seen_items = 0 + + for session_file in list_recent_session_files(resolved): + pending: dict[str, ToolCall] = {} + for entry in _iter_session_entries(session_file): + if entry.get("type") != "message": + continue + message = _as_record(entry.get("message")) + if message is None: + continue + + role = message.get("role") + if role == "assistant": + content = message.get("content") + if not isinstance(content, list): + continue + thinking = _extract_thinking(content) + assistant_timestamp = _as_string(entry.get("timestamp")) + for item in content: + payload = _as_record(item) + if payload is None: + continue + if payload.get("type") != "toolCall": + continue + tool_name = _as_string(payload.get("name")) + tool_call_id = _as_string(payload.get("id")) + if tool_name is None or tool_call_id is None or tool_name not in wanted: + continue + arguments = _as_record(payload.get("arguments")) or {} + pending[tool_call_id] = ToolCall( + session_file=session_file, + tool_call_id=tool_call_id, + tool_name=tool_name, + arguments=arguments, + assistant_thinking=thinking, + assistant_timestamp=assistant_timestamp, + path_hint=extract_path(arguments), + ) + continue + + if role != "toolResult": + continue + tool_name = _as_string(message.get("toolName")) + tool_call_id = _as_string(message.get("toolCallId")) + if tool_name is None or tool_call_id is None or tool_name not in wanted: + continue + pending_call = pending.pop(tool_call_id, None) + if pending_call is None: + continue + result = ToolResult( + tool_call_id=tool_call_id, + tool_name=tool_name, + is_error=message.get("isError") is True, + result_text=extract_result_text(message), + details=_as_record(message.get("details")) or {}, + tool_timestamp=_as_string(entry.get("timestamp")), + ) + invocation = ToolInvocation(call=pending_call, result=result) + seen_items += _event_weight(invocation, resolved.limit_mode) + yield invocation + if seen_items >= resolved.max_items: + return + + if not resolved.include_unresolved: + continue + for pending_call in pending.values(): + invocation = ToolInvocation(call=pending_call) + seen_items += _event_weight(invocation, resolved.limit_mode) + yield invocation + if seen_items >= resolved.max_items: + return + + + +def iter_results(stream: Iterable[ToolInvocation]) -> Iterator[ToolInvocation]: + for invocation in stream: + if invocation.has_result: + yield invocation + + + +def iter_failed(stream: Iterable[ToolInvocation]) -> Iterator[ToolInvocation]: + for invocation in stream: + if invocation.is_error: + yield invocation + + + +def iter_successful(stream: Iterable[ToolInvocation]) -> Iterator[ToolInvocation]: + for invocation in stream: + if invocation.has_result and not invocation.is_error: + yield invocation + + + +def iter_with_diff(stream: Iterable[ToolInvocation]) -> Iterator[ToolInvocation]: + for invocation in stream: + if invocation.diff: + yield invocation + + + +def iter_paths(stream: Iterable[ToolInvocation], *paths: str) -> Iterator[ToolInvocation]: + wanted = set(paths) + for invocation in stream: + if invocation.path_hint in wanted: + yield invocation + + + +def take(stream: Iterable[ToolInvocation], limit: int) -> Iterator[ToolInvocation]: + if limit <= 0: + return + remaining = limit + for invocation in stream: + if remaining <= 0: + return + yield invocation + remaining -= 1 + + + +def sample_reservoir[T](stream: Iterable[T], size: int, seed: int | None = None) -> list[T]: + sample: ReservoirSample[T] = ReservoirSample(size=size, rng=random.Random(seed)) + for item in stream: + sample.add(item) + return sample.items + + + +def extract_result_text(message: dict[str, Any] | None) -> str: + if message is None: + return "" + content = message.get("content") + if not isinstance(content, list): + return "" + for item in content: + payload = _as_record(item) + if payload is None: + continue + if payload.get("type") != "text": + continue + text = _as_string(payload.get("text")) + if text is not None: + return text + return "" + + + +def extract_path(arguments: dict[str, Any]) -> str: + for key in ("path", "file", "move"): + value = arguments.get(key) + if isinstance(value, str): + return value + return "" + + + +def _iter_session_entries(session_file: Path) -> Iterator[dict[str, Any]]: + with session_file.open("r", encoding="utf-8") as handle: + for line in handle: + line = line.strip() + if not line: + continue + try: + entry = json.loads(line) + except json.JSONDecodeError: + continue + payload = _as_record(entry) + if payload is not None: + yield payload + + + +def _extract_thinking(content: list[Any]) -> str | None: + for item in content: + payload = _as_record(item) + if payload is None: + continue + if payload.get("type") != "thinking": + continue + thinking = _as_string(payload.get("thinking")) + if thinking: + return thinking + return None + + + +def resolve_tool_names(*names_or_groups: str) -> tuple[str, ...]: + ordered: list[str] = [] + seen: set[str] = set() + for name in names_or_groups: + expanded = TOOL_GROUPS.get(name, (name,)) + for tool_name in expanded: + if tool_name in seen: + continue + seen.add(tool_name) + ordered.append(tool_name) + return tuple(ordered) + + +def _normalize_tool_names(tool_names: str | Iterable[str]) -> set[str]: + if isinstance(tool_names, str): + return set(resolve_tool_names(tool_names)) + ordered: list[str] = [] + for name in tool_names: + ordered.extend(resolve_tool_names(name)) + return set(ordered) + + + +def _event_weight(invocation: ToolInvocation, limit_mode: LimitMode) -> int: + if limit_mode == "calls": + return 1 + return 2 if invocation.has_result else 1 + + + +def _as_record(value: Any) -> dict[str, Any] | None: + if not isinstance(value, dict): + return None + return value + + + +def _as_string(value: Any) -> str | None: + return value if isinstance(value, str) else None