From b4287445404f5f662cf083eac0e9caaeb510cf7c Mon Sep 17 00:00:00 2001 From: watashi-00 Date: Sat, 22 Aug 2026 15:51:48 -0300 Subject: [PATCH 1/4] fix(reasoning): adjust token boundary defaults, validate positive integers, and align diff chunking --- out/cli.cjs | 684 +++++++++++++----------- src/commands/config.ts | 69 ++- src/engine/Engine.ts | 2 + src/engine/openAi.ts | 20 +- src/generateCommitMessageFromGitDiff.ts | 32 +- src/utils/engine.ts | 2 + test/unit/config.test.ts | 44 +- test/unit/openAi.test.ts | 94 +++- 8 files changed, 592 insertions(+), 355 deletions(-) diff --git a/out/cli.cjs b/out/cli.cjs index 3a6c7d07..35088a20 100755 --- a/out/cli.cjs +++ b/out/cli.cjs @@ -2086,7 +2086,7 @@ var require_main = __commonJS({ return { parsed: parsedAll }; } } - function config8(options) { + function config7(options) { if (_dotenvKey(options).length === 0) { return DotenvModule.configDotenv(options); } @@ -2153,7 +2153,7 @@ var require_main = __commonJS({ configDotenv, _configVault, _parseVault, - config: config8, + config: config7, decrypt, parse, populate @@ -67306,42 +67306,6 @@ function getI18nLocal(value) { return false; } -// src/utils/provider.ts -var OCO_AI_PROVIDER_ENUM = /* @__PURE__ */ ((OCO_AI_PROVIDER_ENUM2) => { - OCO_AI_PROVIDER_ENUM2["OLLAMA"] = "ollama"; - OCO_AI_PROVIDER_ENUM2["LLAMACPP"] = "llamacpp"; - OCO_AI_PROVIDER_ENUM2["OPENAI"] = "openai"; - OCO_AI_PROVIDER_ENUM2["ANTHROPIC"] = "anthropic"; - OCO_AI_PROVIDER_ENUM2["GEMINI"] = "gemini"; - OCO_AI_PROVIDER_ENUM2["AZURE"] = "azure"; - OCO_AI_PROVIDER_ENUM2["TEST"] = "test"; - OCO_AI_PROVIDER_ENUM2["FLOWISE"] = "flowise"; - OCO_AI_PROVIDER_ENUM2["GROQ"] = "groq"; - OCO_AI_PROVIDER_ENUM2["MISTRAL"] = "mistral"; - OCO_AI_PROVIDER_ENUM2["MLX"] = "mlx"; - OCO_AI_PROVIDER_ENUM2["DEEPSEEK"] = "deepseek"; - OCO_AI_PROVIDER_ENUM2["AIMLAPI"] = "aimlapi"; - OCO_AI_PROVIDER_ENUM2["OPENROUTER"] = "openrouter"; - return OCO_AI_PROVIDER_ENUM2; -})(OCO_AI_PROVIDER_ENUM || {}); -var PROVIDER_CONFIG_REQUIREMENTS = { - ["openai" /* OPENAI */]: "apiKey", - ["anthropic" /* ANTHROPIC */]: "apiKey", - ["ollama" /* OLLAMA */]: "model", - ["llamacpp" /* LLAMACPP */]: "model", - ["gemini" /* GEMINI */]: "apiKey", - ["groq" /* GROQ */]: "apiKey", - ["mistral" /* MISTRAL */]: "apiKey", - ["deepseek" /* DEEPSEEK */]: "apiKey", - ["openrouter" /* OPENROUTER */]: "apiKey", - ["aimlapi" /* AIMLAPI */]: "apiKey", - ["azure" /* AZURE */]: "apiKey", - ["mlx" /* MLX */]: "model", - ["flowise" /* FLOWISE */]: "apiKey", - ["test" /* TEST */]: "none" -}; -var getProviderConfigRequirement = (provider = "openai" /* OPENAI */) => PROVIDER_CONFIG_REQUIREMENTS[provider] || "apiKey"; - // src/commands/config.ts var CONFIG_KEYS = /* @__PURE__ */ ((CONFIG_KEYS3) => { CONFIG_KEYS3["OCO_API_KEY"] = "OCO_API_KEY"; @@ -67363,6 +67327,8 @@ var CONFIG_KEYS = /* @__PURE__ */ ((CONFIG_KEYS3) => { CONFIG_KEYS3["OCO_OMIT_SCOPE"] = "OCO_OMIT_SCOPE"; CONFIG_KEYS3["OCO_GITPUSH"] = "OCO_GITPUSH"; CONFIG_KEYS3["OCO_HOOK_AUTO_UNCOMMENT"] = "OCO_HOOK_AUTO_UNCOMMENT"; + CONFIG_KEYS3["OCO_REASONING_MAX_TOKENS"] = "OCO_REASONING_MAX_TOKENS"; + CONFIG_KEYS3["OCO_REASONING"] = "OCO_REASONING"; CONFIG_KEYS3["OCO_OLLAMA_THINK"] = "OCO_OLLAMA_THINK"; return CONFIG_KEYS3; })(CONFIG_KEYS || {}); @@ -67465,7 +67431,7 @@ var MODEL_LIST = { "mistral-moderation-2411", "mistral-moderation-latest" ], - deepseek: ["deepseek-v4-flash", "deepseek-v4-pro"], + deepseek: ["deepseek-chat", "deepseek-reasoner"], // AI/ML API available chat-completion models // https://api.aimlapi.com/v1/models aimlapi: [ @@ -67937,9 +67903,10 @@ var validateConfig = (key, condition, validationMessage) => { process.exit(1); } }; +var isPositiveInteger = (value) => typeof value !== "boolean" && Number.isInteger(Number(value)) && Number(value) > 0; var configValidators = { - ["OCO_API_KEY" /* OCO_API_KEY */](value, config8 = {}) { - if (config8.OCO_AI_PROVIDER !== "openai") return value; + ["OCO_API_KEY" /* OCO_API_KEY */](value, config7 = {}) { + if (config7.OCO_AI_PROVIDER !== "openai") return value; validateConfig( "OCO_API_KEY", typeof value === "string" && value.length > 0, @@ -68033,7 +68000,7 @@ var configValidators = { ); return value; }, - ["OCO_MODEL" /* OCO_MODEL */](value, config8 = {}) { + ["OCO_MODEL" /* OCO_MODEL */](value, config7 = {}) { validateConfig( "OCO_MODEL" /* OCO_MODEL */, typeof value === "string", @@ -68125,8 +68092,23 @@ var configValidators = { typeof value === "boolean", "Must be true or false" ); + }, + ["OCO_REASONING" /* OCO_REASONING */](value) { + validateConfig( + "OCO_REASONING" /* OCO_REASONING */, + typeof value === "boolean", + "Must be true or false" + ); return value; }, + ["OCO_REASONING_MAX_TOKENS" /* OCO_REASONING_MAX_TOKENS */](value) { + validateConfig( + "OCO_REASONING_MAX_TOKENS" /* OCO_REASONING_MAX_TOKENS */, + isPositiveInteger(value), + "Must be a positive integer" + ); + return typeof value === "number" ? value : parseInt(value, 10); + }, ["OCO_OLLAMA_THINK" /* OCO_OLLAMA_THINK */](value) { validateConfig( "OCO_OLLAMA_THINK" /* OCO_OLLAMA_THINK */, @@ -68135,6 +68117,23 @@ var configValidators = { ); } }; +var OCO_AI_PROVIDER_ENUM = /* @__PURE__ */ ((OCO_AI_PROVIDER_ENUM2) => { + OCO_AI_PROVIDER_ENUM2["OLLAMA"] = "ollama"; + OCO_AI_PROVIDER_ENUM2["LLAMACPP"] = "llamacpp"; + OCO_AI_PROVIDER_ENUM2["OPENAI"] = "openai"; + OCO_AI_PROVIDER_ENUM2["ANTHROPIC"] = "anthropic"; + OCO_AI_PROVIDER_ENUM2["GEMINI"] = "gemini"; + OCO_AI_PROVIDER_ENUM2["AZURE"] = "azure"; + OCO_AI_PROVIDER_ENUM2["TEST"] = "test"; + OCO_AI_PROVIDER_ENUM2["FLOWISE"] = "flowise"; + OCO_AI_PROVIDER_ENUM2["GROQ"] = "groq"; + OCO_AI_PROVIDER_ENUM2["MISTRAL"] = "mistral"; + OCO_AI_PROVIDER_ENUM2["MLX"] = "mlx"; + OCO_AI_PROVIDER_ENUM2["DEEPSEEK"] = "deepseek"; + OCO_AI_PROVIDER_ENUM2["AIMLAPI"] = "aimlapi"; + OCO_AI_PROVIDER_ENUM2["OPENROUTER"] = "openrouter"; + return OCO_AI_PROVIDER_ENUM2; +})(OCO_AI_PROVIDER_ENUM || {}); var PROVIDER_API_KEY_URLS = { ["openai" /* OPENAI */]: "https://platform.openai.com/api-keys", ["anthropic" /* ANTHROPIC */]: "https://console.anthropic.com/settings/keys", @@ -68157,7 +68156,7 @@ var RECOMMENDED_MODELS = { ["gemini" /* GEMINI */]: "gemini-1.5-flash", ["groq" /* GROQ */]: "llama3-70b-8192", ["mistral" /* MISTRAL */]: "mistral-small-latest", - ["deepseek" /* DEEPSEEK */]: "deepseek-v4-flash", + ["deepseek" /* DEEPSEEK */]: "deepseek-chat", ["openrouter" /* OPENROUTER */]: "openai/gpt-4o-mini", ["aimlapi" /* AIMLAPI */]: "gpt-4o-mini" }; @@ -68171,6 +68170,7 @@ var OCO_PROMPT_MODULE_ENUM = /* @__PURE__ */ ((OCO_PROMPT_MODULE_ENUM2) => { var DEFAULT_CONFIG = { OCO_TOKENS_MAX_INPUT: 4096 /* DEFAULT_MAX_TOKENS_INPUT */, OCO_TOKENS_MAX_OUTPUT: 500 /* DEFAULT_MAX_TOKENS_OUTPUT */, + OCO_REASONING_MAX_TOKENS: 1e3 /* DEFAULT_MAX_REASONING */, OCO_DESCRIPTION: false, OCO_EMOJI: false, OCO_MODEL: getDefaultModel("openai"), @@ -68185,6 +68185,7 @@ var DEFAULT_CONFIG = { OCO_GITPUSH: true, // todo: deprecate OCO_HOOK_AUTO_UNCOMMENT: false + // OCO_REASONING: is intentionally omitted to default to 'undefined' and preserve auto-detection. }; var parseConfigVarValue = (value) => { try { @@ -68214,12 +68215,16 @@ var getEnvConfig = (envPath) => { OCO_ONE_LINE_COMMIT: parseConfigVarValue(process.env.OCO_ONE_LINE_COMMIT), OCO_TEST_MOCK_TYPE: process.env.OCO_TEST_MOCK_TYPE, OCO_OMIT_SCOPE: parseConfigVarValue(process.env.OCO_OMIT_SCOPE), + OCO_REASONING_MAX_TOKENS: parseConfigVarValue( + process.env.OCO_REASONING_MAX_TOKENS + ), + OCO_REASONING: parseConfigVarValue(process.env.OCO_REASONING), OCO_GITPUSH: parseConfigVarValue(process.env.OCO_GITPUSH) // todo: deprecate }; }; -var setGlobalConfig = (config8, configPath = defaultConfigPath) => { - (0, import_fs.writeFileSync)(configPath, (0, import_ini.stringify)(config8), "utf8"); +var setGlobalConfig = (config7, configPath = defaultConfigPath) => { + (0, import_fs.writeFileSync)(configPath, (0, import_ini.stringify)(config7), "utf8"); }; var getIsGlobalConfigFileExist = (configPath = defaultConfigPath) => { return (0, import_fs.existsSync)(configPath); @@ -68242,9 +68247,9 @@ var mergeConfigs = (main, fallback) => { return acc; }, {}); }; -var cleanUndefinedValues = (config8) => { +var cleanUndefinedValues = (config7) => { return Object.fromEntries( - Object.entries(config8).map(([_7, v5]) => { + Object.entries(config7).map(([_7, v5]) => { try { if (typeof v5 === "string") { if (v5 === "undefined") return [_7, void 0]; @@ -68265,12 +68270,12 @@ var getConfig = ({ } = {}) => { const envConfig = getEnvConfig(envPath); const globalConfig = getGlobalConfig(globalPath); - const config8 = mergeConfigs(envConfig, globalConfig); - const cleanConfig = cleanUndefinedValues(config8); + const config7 = mergeConfigs(envConfig, globalConfig); + const cleanConfig = cleanUndefinedValues(config7); return cleanConfig; }; var setConfig = (keyValues, globalConfigPath = defaultConfigPath) => { - const config8 = getConfig({ + const config7 = getConfig({ globalPath: globalConfigPath }); const configToSet = {}; @@ -68294,11 +68299,11 @@ For more help refer to our docs: https://github.com/di-sukharev/opencommit` } const validValue = configValidators[key]( parsedConfigValue, - config8 + config7 ); configToSet[key] = validValue; } - setGlobalConfig(mergeConfigs(configToSet, config8), globalConfigPath); + setGlobalConfig(mergeConfigs(configToSet, config7), globalConfigPath); ce(`${source_default.green("\u2714")} config successfully set`); }; function getConfigKeyDetails(key) { @@ -68396,6 +68401,16 @@ function getConfigKeyDetails(key) { description: "Automatically uncomment the commit message in the hook", values: ["true", "false"] }; + case "OCO_REASONING" /* OCO_REASONING */: + return { + description: "Specify if the selected model is a reasoning model (bypasses max_tokens for reasoning output)", + values: ["true", "false"] + }; + case "OCO_REASONING_MAX_TOKENS" /* OCO_REASONING_MAX_TOKENS */: + return { + description: "Max token limit allocated specifically for the reasoning/thinking output", + values: ["Any positive integer"] + }; default: return { description: "String value", @@ -68495,9 +68510,9 @@ var configCommand = G3( if (!keyValues || keyValues.length === 0) { throw new Error("No config keys specified for get mode"); } - const config8 = getConfig() || {}; + const config7 = getConfig() || {}; for (const key of keyValues) { - ce(`${key}=${config8[key]}`); + ce(`${key}=${config7[key]}`); } } else if (mode === "set" /* set */) { if (!keyValues || keyValues.length === 0) { @@ -71417,7 +71432,7 @@ var utils_default = { }; // node_modules/axios/lib/core/AxiosError.js -function AxiosError(message, code, config8, request3, response) { +function AxiosError(message, code, config7, request3, response) { Error.call(this); if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); @@ -71427,7 +71442,7 @@ function AxiosError(message, code, config8, request3, response) { this.message = message; this.name = "AxiosError"; code && (this.code = code); - config8 && (this.config = config8); + config7 && (this.config = config7); request3 && (this.request = request3); if (response) { this.response = response; @@ -71476,14 +71491,14 @@ var descriptors2 = {}; }); Object.defineProperties(AxiosError, descriptors2); Object.defineProperty(prototype, "isAxiosError", { value: true }); -AxiosError.from = (error, code, config8, request3, response, customProps) => { +AxiosError.from = (error, code, config7, request3, response, customProps) => { const axiosError = Object.create(prototype); utils_default.toFlatObject(error, axiosError, function filter2(obj) { return obj !== Error.prototype; }, (prop) => { return prop !== "isAxiosError"; }); - AxiosError.call(axiosError, error.message, code, config8, request3, response); + AxiosError.call(axiosError, error.message, code, config7, request3, response); axiosError.cause = error; axiosError.name = error.name; customProps && Object.assign(axiosError, customProps); @@ -72254,12 +72269,12 @@ var AxiosHeaders_default = AxiosHeaders; // node_modules/axios/lib/core/transformData.js function transformData(fns, response) { - const config8 = this || defaults_default; - const context = response || config8; + const config7 = this || defaults_default; + const context = response || config7; const headers = AxiosHeaders_default.from(context.headers); let data = context.data; utils_default.forEach(fns, function transform(fn) { - data = fn.call(config8, data, headers.normalize(), response ? response.status : void 0); + data = fn.call(config7, data, headers.normalize(), response ? response.status : void 0); }); headers.normalize(); return data; @@ -72271,8 +72286,8 @@ function isCancel(value) { } // node_modules/axios/lib/cancel/CanceledError.js -function CanceledError(message, config8, request3) { - AxiosError_default.call(this, message == null ? "canceled" : message, AxiosError_default.ERR_CANCELED, config8, request3); +function CanceledError(message, config7, request3) { + AxiosError_default.call(this, message == null ? "canceled" : message, AxiosError_default.ERR_CANCELED, config7, request3); this.name = "CanceledError"; } utils_default.inherits(CanceledError, AxiosError_default, { @@ -72814,11 +72829,11 @@ var resolveFamily = ({ address, family }) => { }; }; var buildAddressEntry = (address, family) => resolveFamily(utils_default.isObject(address) ? address : { address, family }); -var http_default = isHttpAdapterSupported && function httpAdapter(config8) { +var http_default = isHttpAdapterSupported && function httpAdapter(config7) { return wrapAsync(async function dispatchHttpRequest(resolve, reject, onDone) { - let { data, lookup, family } = config8; - const { responseType, responseEncoding } = config8; - const method = config8.method.toUpperCase(); + let { data, lookup, family } = config7; + const { responseType, responseEncoding } = config7; + const method = config7.method.toUpperCase(); let isDone; let rejected = false; let req; @@ -72836,11 +72851,11 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { } const emitter = new import_events.EventEmitter(); const onFinished = () => { - if (config8.cancelToken) { - config8.cancelToken.unsubscribe(abort); + if (config7.cancelToken) { + config7.cancelToken.unsubscribe(abort); } - if (config8.signal) { - config8.signal.removeEventListener("abort", abort); + if (config7.signal) { + config7.signal.removeEventListener("abort", abort); } emitter.removeAllListeners(); }; @@ -72852,16 +72867,16 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { } }); function abort(reason) { - emitter.emit("abort", !reason || reason.type ? new CanceledError_default(null, config8, req) : reason); + emitter.emit("abort", !reason || reason.type ? new CanceledError_default(null, config7, req) : reason); } emitter.once("abort", reject); - if (config8.cancelToken || config8.signal) { - config8.cancelToken && config8.cancelToken.subscribe(abort); - if (config8.signal) { - config8.signal.aborted ? abort() : config8.signal.addEventListener("abort", abort); + if (config7.cancelToken || config7.signal) { + config7.cancelToken && config7.cancelToken.subscribe(abort); + if (config7.signal) { + config7.signal.aborted ? abort() : config7.signal.addEventListener("abort", abort); } } - const fullPath = buildFullPath(config8.baseURL, config8.url, config8.allowAbsoluteUrls); + const fullPath = buildFullPath(config7.baseURL, config7.url, config7.allowAbsoluteUrls); const parsed = new URL(fullPath, platform_default.hasBrowserEnv ? platform_default.origin : void 0); const protocol = parsed.protocol || supportedProtocols[0]; if (protocol === "data:") { @@ -72871,15 +72886,15 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { status: 405, statusText: "method not allowed", headers: {}, - config: config8 + config: config7 }); } try { - convertedData = fromDataURI(config8.url, responseType === "blob", { - Blob: config8.env && config8.env.Blob + convertedData = fromDataURI(config7.url, responseType === "blob", { + Blob: config7.env && config7.env.Blob }); } catch (err) { - throw AxiosError_default.from(err, AxiosError_default.ERR_BAD_REQUEST, config8); + throw AxiosError_default.from(err, AxiosError_default.ERR_BAD_REQUEST, config7); } if (responseType === "text") { convertedData = convertedData.toString(responseEncoding); @@ -72894,20 +72909,20 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { status: 200, statusText: "OK", headers: new AxiosHeaders_default(), - config: config8 + config: config7 }); } if (supportedProtocols.indexOf(protocol) === -1) { return reject(new AxiosError_default( "Unsupported protocol " + protocol, AxiosError_default.ERR_BAD_REQUEST, - config8 + config7 )); } - const headers = AxiosHeaders_default.from(config8.headers).normalize(); + const headers = AxiosHeaders_default.from(config7.headers).normalize(); headers.set("User-Agent", "axios/" + VERSION2, false); - const { onUploadProgress, onDownloadProgress } = config8; - const maxRate = config8.maxRate; + const { onUploadProgress, onDownloadProgress } = config7; + const maxRate = config7.maxRate; let maxUploadRate = void 0; let maxDownloadRate = void 0; if (utils_default.isSpecCompliantForm(data)) { @@ -72941,15 +72956,15 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { return reject(new AxiosError_default( "Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream", AxiosError_default.ERR_BAD_REQUEST, - config8 + config7 )); } headers.setContentLength(data.length, false); - if (config8.maxBodyLength > -1 && data.length > config8.maxBodyLength) { + if (config7.maxBodyLength > -1 && data.length > config7.maxBodyLength) { return reject(new AxiosError_default( "Request body larger than maxBodyLength limit", AxiosError_default.ERR_BAD_REQUEST, - config8 + config7 )); } } @@ -72976,9 +72991,9 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { )); } let auth = void 0; - if (config8.auth) { - const username = config8.auth.username || ""; - const password = config8.auth.password || ""; + if (config7.auth) { + const username = config7.auth.username || ""; + const password = config7.auth.password || ""; auth = username + ":" + password; } if (!auth && parsed.username) { @@ -72991,13 +73006,13 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { try { path5 = buildURL( parsed.pathname + parsed.search, - config8.params, - config8.paramsSerializer + config7.params, + config7.paramsSerializer ).replace(/^\?/, ""); } catch (err) { const customErr = new Error(err.message); - customErr.config = config8; - customErr.url = config8.url; + customErr.config = config7; + customErr.url = config7.url; customErr.exists = true; return reject(customErr); } @@ -73010,7 +73025,7 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { path: path5, method, headers: headers.toJSON(), - agents: { http: config8.httpAgent, https: config8.httpsAgent }, + agents: { http: config7.httpAgent, https: config7.httpsAgent }, auth, protocol, family, @@ -73018,36 +73033,36 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { beforeRedirects: {} }; !utils_default.isUndefined(lookup) && (options.lookup = lookup); - if (config8.socketPath) { - options.socketPath = config8.socketPath; + if (config7.socketPath) { + options.socketPath = config7.socketPath; } else { options.hostname = parsed.hostname.startsWith("[") ? parsed.hostname.slice(1, -1) : parsed.hostname; options.port = parsed.port; - setProxy(options, config8.proxy, protocol + "//" + parsed.hostname + (parsed.port ? ":" + parsed.port : "") + options.path); + setProxy(options, config7.proxy, protocol + "//" + parsed.hostname + (parsed.port ? ":" + parsed.port : "") + options.path); } let transport; const isHttpsRequest = isHttps.test(options.protocol); - options.agent = isHttpsRequest ? config8.httpsAgent : config8.httpAgent; - if (config8.transport) { - transport = config8.transport; - } else if (config8.maxRedirects === 0) { + options.agent = isHttpsRequest ? config7.httpsAgent : config7.httpAgent; + if (config7.transport) { + transport = config7.transport; + } else if (config7.maxRedirects === 0) { transport = isHttpsRequest ? import_https2.default : import_http.default; } else { - if (config8.maxRedirects) { - options.maxRedirects = config8.maxRedirects; + if (config7.maxRedirects) { + options.maxRedirects = config7.maxRedirects; } - if (config8.beforeRedirect) { - options.beforeRedirects.config = config8.beforeRedirect; + if (config7.beforeRedirect) { + options.beforeRedirects.config = config7.beforeRedirect; } transport = isHttpsRequest ? httpsFollow : httpFollow; } - if (config8.maxBodyLength > -1) { - options.maxBodyLength = config8.maxBodyLength; + if (config7.maxBodyLength > -1) { + options.maxBodyLength = config7.maxBodyLength; } else { options.maxBodyLength = Infinity; } - if (config8.insecureHTTPParser) { - options.insecureHTTPParser = config8.insecureHTTPParser; + if (config7.insecureHTTPParser) { + options.insecureHTTPParser = config7.insecureHTTPParser; } req = transport.request(options, function handleResponse(res) { if (req.destroyed) return; @@ -73068,7 +73083,7 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { } let responseStream = res; const lastRequest = res.req || req; - if (config8.decompress !== false && res.headers["content-encoding"]) { + if (config7.decompress !== false && res.headers["content-encoding"]) { if (method === "HEAD" || res.statusCode === 204) { delete res.headers["content-encoding"]; } @@ -73102,7 +73117,7 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { status: res.statusCode, statusText: res.statusMessage, headers: new AxiosHeaders_default(res.headers), - config: config8, + config: config7, request: lastRequest }; if (responseType === "stream") { @@ -73114,13 +73129,13 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { responseStream.on("data", function handleStreamData(chunk) { responseBuffer.push(chunk); totalResponseBytes += chunk.length; - if (config8.maxContentLength > -1 && totalResponseBytes > config8.maxContentLength) { + if (config7.maxContentLength > -1 && totalResponseBytes > config7.maxContentLength) { rejected = true; responseStream.destroy(); reject(new AxiosError_default( - "maxContentLength size of " + config8.maxContentLength + " exceeded", + "maxContentLength size of " + config7.maxContentLength + " exceeded", AxiosError_default.ERR_BAD_RESPONSE, - config8, + config7, lastRequest )); } @@ -73132,7 +73147,7 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { const err = new AxiosError_default( "stream has been aborted", AxiosError_default.ERR_BAD_RESPONSE, - config8, + config7, lastRequest ); responseStream.destroy(err); @@ -73140,7 +73155,7 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { }); responseStream.on("error", function handleStreamError(err) { if (req.destroyed) return; - reject(AxiosError_default.from(err, null, config8, lastRequest)); + reject(AxiosError_default.from(err, null, config7, lastRequest)); }); responseStream.on("end", function handleStreamEnd() { try { @@ -73153,7 +73168,7 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { } response.data = responseData; } catch (err) { - return reject(AxiosError_default.from(err, null, config8, response.request, response)); + return reject(AxiosError_default.from(err, null, config7, response.request, response)); } settle(resolve, reject, response); }); @@ -73170,33 +73185,33 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { req.destroy(err); }); req.on("error", function handleRequestError(err) { - reject(AxiosError_default.from(err, null, config8, req)); + reject(AxiosError_default.from(err, null, config7, req)); }); req.on("socket", function handleRequestSocket(socket) { socket.setKeepAlive(true, 1e3 * 60); }); - if (config8.timeout) { - const timeout = parseInt(config8.timeout, 10); + if (config7.timeout) { + const timeout = parseInt(config7.timeout, 10); if (Number.isNaN(timeout)) { reject(new AxiosError_default( "error trying to parse `config.timeout` to int", AxiosError_default.ERR_BAD_OPTION_VALUE, - config8, + config7, req )); return; } req.setTimeout(timeout, function handleRequestTimeout() { if (isDone) return; - let timeoutErrorMessage = config8.timeout ? "timeout of " + config8.timeout + "ms exceeded" : "timeout exceeded"; - const transitional2 = config8.transitional || transitional_default; - if (config8.timeoutErrorMessage) { - timeoutErrorMessage = config8.timeoutErrorMessage; + let timeoutErrorMessage = config7.timeout ? "timeout of " + config7.timeout + "ms exceeded" : "timeout exceeded"; + const transitional2 = config7.transitional || transitional_default; + if (config7.timeoutErrorMessage) { + timeoutErrorMessage = config7.timeoutErrorMessage; } reject(new AxiosError_default( timeoutErrorMessage, transitional2.clarifyTimeoutError ? AxiosError_default.ETIMEDOUT : AxiosError_default.ECONNABORTED, - config8, + config7, req )); abort(); @@ -73214,7 +73229,7 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config8) { }); data.on("close", () => { if (!ended && !errored) { - abort(new CanceledError_default("Request stream has been aborted", config8, req)); + abort(new CanceledError_default("Request stream has been aborted", config7, req)); } }); data.pipe(req); @@ -73270,7 +73285,7 @@ var cookies_default = platform_default.hasStandardBrowserEnv ? ( var headersToObject = (thing) => thing instanceof AxiosHeaders_default ? { ...thing } : thing; function mergeConfig(config1, config22) { config22 = config22 || {}; - const config8 = {}; + const config7 = {}; function getMergedValue(target, source, prop, caseless) { if (utils_default.isPlainObject(target) && utils_default.isPlainObject(source)) { return utils_default.merge.call({ caseless }, target, source); @@ -73341,17 +73356,17 @@ function mergeConfig(config1, config22) { utils_default.forEach(Object.keys(Object.assign({}, config1, config22)), function computeConfigValue(prop) { const merge2 = mergeMap[prop] || mergeDeepProperties; const configValue = merge2(config1[prop], config22[prop], prop); - utils_default.isUndefined(configValue) && merge2 !== mergeDirectKeys || (config8[prop] = configValue); + utils_default.isUndefined(configValue) && merge2 !== mergeDirectKeys || (config7[prop] = configValue); }); - return config8; + return config7; } // node_modules/axios/lib/helpers/resolveConfig.js -var resolveConfig_default = (config8) => { - const newConfig = mergeConfig({}, config8); +var resolveConfig_default = (config7) => { + const newConfig = mergeConfig({}, config7); let { data, withXSRFToken, xsrfHeaderName, xsrfCookieName, headers, auth } = newConfig; newConfig.headers = headers = AxiosHeaders_default.from(headers); - newConfig.url = buildURL(buildFullPath(newConfig.baseURL, newConfig.url, newConfig.allowAbsoluteUrls), config8.params, config8.paramsSerializer); + newConfig.url = buildURL(buildFullPath(newConfig.baseURL, newConfig.url, newConfig.allowAbsoluteUrls), config7.params, config7.paramsSerializer); if (auth) { headers.set( "Authorization", @@ -73381,9 +73396,9 @@ var resolveConfig_default = (config8) => { // node_modules/axios/lib/adapters/xhr.js var isXHRAdapterSupported = typeof XMLHttpRequest !== "undefined"; -var xhr_default = isXHRAdapterSupported && function(config8) { +var xhr_default = isXHRAdapterSupported && function(config7) { return new Promise(function dispatchXhrRequest(resolve, reject) { - const _config = resolveConfig_default(config8); + const _config = resolveConfig_default(config7); let requestData = _config.data; const requestHeaders = AxiosHeaders_default.from(_config.headers).normalize(); let { responseType, onUploadProgress, onDownloadProgress } = _config; @@ -73412,7 +73427,7 @@ var xhr_default = isXHRAdapterSupported && function(config8) { status: request3.status, statusText: request3.statusText, headers: responseHeaders, - config: config8, + config: config7, request: request3 }; settle(function _resolve(value) { @@ -73441,11 +73456,11 @@ var xhr_default = isXHRAdapterSupported && function(config8) { if (!request3) { return; } - reject(new AxiosError_default("Request aborted", AxiosError_default.ECONNABORTED, config8, request3)); + reject(new AxiosError_default("Request aborted", AxiosError_default.ECONNABORTED, config7, request3)); request3 = null; }; request3.onerror = function handleError() { - reject(new AxiosError_default("Network Error", AxiosError_default.ERR_NETWORK, config8, request3)); + reject(new AxiosError_default("Network Error", AxiosError_default.ERR_NETWORK, config7, request3)); request3 = null; }; request3.ontimeout = function handleTimeout() { @@ -73457,7 +73472,7 @@ var xhr_default = isXHRAdapterSupported && function(config8) { reject(new AxiosError_default( timeoutErrorMessage, transitional2.clarifyTimeoutError ? AxiosError_default.ETIMEDOUT : AxiosError_default.ECONNABORTED, - config8, + config7, request3 )); request3 = null; @@ -73488,7 +73503,7 @@ var xhr_default = isXHRAdapterSupported && function(config8) { if (!request3) { return; } - reject(!cancel || cancel.type ? new CanceledError_default(null, config8, request3) : cancel); + reject(!cancel || cancel.type ? new CanceledError_default(null, config7, request3) : cancel); request3.abort(); request3 = null; }; @@ -73499,7 +73514,7 @@ var xhr_default = isXHRAdapterSupported && function(config8) { } const protocol = parseProtocol(_config.url); if (protocol && platform_default.protocols.indexOf(protocol) === -1) { - reject(new AxiosError_default("Unsupported protocol " + protocol + ":", AxiosError_default.ERR_BAD_REQUEST, config8)); + reject(new AxiosError_default("Unsupported protocol " + protocol + ":", AxiosError_default.ERR_BAD_REQUEST, config7)); return; } request3.send(requestData || null); @@ -73649,8 +73664,8 @@ var resolvers = { }; isFetchSupported && ((res) => { ["text", "arrayBuffer", "blob", "formData", "stream"].forEach((type2) => { - !resolvers[type2] && (resolvers[type2] = utils_default.isFunction(res[type2]) ? (res2) => res2[type2]() : (_7, config8) => { - throw new AxiosError_default(`Response type '${type2}' is not supported`, AxiosError_default.ERR_NOT_SUPPORT, config8); + !resolvers[type2] && (resolvers[type2] = utils_default.isFunction(res[type2]) ? (res2) => res2[type2]() : (_7, config7) => { + throw new AxiosError_default(`Response type '${type2}' is not supported`, AxiosError_default.ERR_NOT_SUPPORT, config7); }); }); })(new Response()); @@ -73682,7 +73697,7 @@ var resolveBodyLength = async (headers, body) => { const length = utils_default.toFiniteNumber(headers.getContentLength()); return length == null ? getBodyLength(body) : length; }; -var fetch_default = isFetchSupported && (async (config8) => { +var fetch_default = isFetchSupported && (async (config7) => { let { url: url2, method, @@ -73696,7 +73711,7 @@ var fetch_default = isFetchSupported && (async (config8) => { headers, withCredentials = "same-origin", fetchOptions - } = resolveConfig_default(config8); + } = resolveConfig_default(config7); responseType = responseType ? (responseType + "").toLowerCase() : "text"; let composedSignal = composeSignals_default([signal, cancelToken && cancelToken.toAbortSignal()], timeout); let request3; @@ -73757,7 +73772,7 @@ var fetch_default = isFetchSupported && (async (config8) => { ); } responseType = responseType || "text"; - let responseData = await resolvers[utils_default.findKey(resolvers, responseType) || "text"](response, config8); + let responseData = await resolvers[utils_default.findKey(resolvers, responseType) || "text"](response, config7); !isStreamResponse && unsubscribe && unsubscribe(); return await new Promise((resolve, reject) => { settle(resolve, reject, { @@ -73765,7 +73780,7 @@ var fetch_default = isFetchSupported && (async (config8) => { headers: AxiosHeaders_default.from(response.headers), status: response.status, statusText: response.statusText, - config: config8, + config: config7, request: request3 }); }); @@ -73773,13 +73788,13 @@ var fetch_default = isFetchSupported && (async (config8) => { unsubscribe && unsubscribe(); if (err && err.name === "TypeError" && /Load failed|fetch/i.test(err.message)) { throw Object.assign( - new AxiosError_default("Network Error", AxiosError_default.ERR_NETWORK, config8, request3), + new AxiosError_default("Network Error", AxiosError_default.ERR_NETWORK, config7, request3), { cause: err.cause || err } ); } - throw AxiosError_default.from(err, err && err.code, config8, request3); + throw AxiosError_default.from(err, err && err.code, config7, request3); } }); @@ -73838,41 +73853,41 @@ var adapters_default = { }; // node_modules/axios/lib/core/dispatchRequest.js -function throwIfCancellationRequested(config8) { - if (config8.cancelToken) { - config8.cancelToken.throwIfRequested(); +function throwIfCancellationRequested(config7) { + if (config7.cancelToken) { + config7.cancelToken.throwIfRequested(); } - if (config8.signal && config8.signal.aborted) { - throw new CanceledError_default(null, config8); + if (config7.signal && config7.signal.aborted) { + throw new CanceledError_default(null, config7); } } -function dispatchRequest(config8) { - throwIfCancellationRequested(config8); - config8.headers = AxiosHeaders_default.from(config8.headers); - config8.data = transformData.call( - config8, - config8.transformRequest +function dispatchRequest(config7) { + throwIfCancellationRequested(config7); + config7.headers = AxiosHeaders_default.from(config7.headers); + config7.data = transformData.call( + config7, + config7.transformRequest ); - if (["post", "put", "patch"].indexOf(config8.method) !== -1) { - config8.headers.setContentType("application/x-www-form-urlencoded", false); + if (["post", "put", "patch"].indexOf(config7.method) !== -1) { + config7.headers.setContentType("application/x-www-form-urlencoded", false); } - const adapter = adapters_default.getAdapter(config8.adapter || defaults_default.adapter); - return adapter(config8).then(function onAdapterResolution(response) { - throwIfCancellationRequested(config8); + const adapter = adapters_default.getAdapter(config7.adapter || defaults_default.adapter); + return adapter(config7).then(function onAdapterResolution(response) { + throwIfCancellationRequested(config7); response.data = transformData.call( - config8, - config8.transformResponse, + config7, + config7.transformResponse, response ); response.headers = AxiosHeaders_default.from(response.headers); return response; }, function onAdapterRejection(reason) { if (!isCancel(reason)) { - throwIfCancellationRequested(config8); + throwIfCancellationRequested(config7); if (reason && reason.response) { reason.response.data = transformData.call( - config8, - config8.transformResponse, + config7, + config7.transformResponse, reason.response ); reason.response.headers = AxiosHeaders_default.from(reason.response.headers); @@ -73964,9 +73979,9 @@ var Axios = class { * * @returns {Promise} The Promise to be fulfilled */ - async request(configOrUrl, config8) { + async request(configOrUrl, config7) { try { - return await this._request(configOrUrl, config8); + return await this._request(configOrUrl, config7); } catch (err) { if (err instanceof Error) { let dummy = {}; @@ -73984,15 +73999,15 @@ var Axios = class { throw err; } } - _request(configOrUrl, config8) { + _request(configOrUrl, config7) { if (typeof configOrUrl === "string") { - config8 = config8 || {}; - config8.url = configOrUrl; + config7 = config7 || {}; + config7.url = configOrUrl; } else { - config8 = configOrUrl || {}; + config7 = configOrUrl || {}; } - config8 = mergeConfig(this.defaults, config8); - const { transitional: transitional2, paramsSerializer, headers } = config8; + config7 = mergeConfig(this.defaults, config7); + const { transitional: transitional2, paramsSerializer, headers } = config7; if (transitional2 !== void 0) { validator_default.assertOptions(transitional2, { silentJSONParsing: validators2.transitional(validators2.boolean), @@ -74002,7 +74017,7 @@ var Axios = class { } if (paramsSerializer != null) { if (utils_default.isFunction(paramsSerializer)) { - config8.paramsSerializer = { + config7.paramsSerializer = { serialize: paramsSerializer }; } else { @@ -74012,20 +74027,20 @@ var Axios = class { }, true); } } - if (config8.allowAbsoluteUrls !== void 0) { + if (config7.allowAbsoluteUrls !== void 0) { } else if (this.defaults.allowAbsoluteUrls !== void 0) { - config8.allowAbsoluteUrls = this.defaults.allowAbsoluteUrls; + config7.allowAbsoluteUrls = this.defaults.allowAbsoluteUrls; } else { - config8.allowAbsoluteUrls = true; + config7.allowAbsoluteUrls = true; } - validator_default.assertOptions(config8, { + validator_default.assertOptions(config7, { baseUrl: validators2.spelling("baseURL"), withXsrfToken: validators2.spelling("withXSRFToken") }, true); - config8.method = (config8.method || this.defaults.method || "get").toLowerCase(); + config7.method = (config7.method || this.defaults.method || "get").toLowerCase(); let contextHeaders = headers && utils_default.merge( headers.common, - headers[config8.method] + headers[config7.method] ); headers && utils_default.forEach( ["delete", "get", "head", "post", "put", "patch", "common"], @@ -74033,11 +74048,11 @@ var Axios = class { delete headers[method]; } ); - config8.headers = AxiosHeaders_default.concat(contextHeaders, headers); + config7.headers = AxiosHeaders_default.concat(contextHeaders, headers); const requestInterceptorChain = []; let synchronousRequestInterceptors = true; this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { - if (typeof interceptor.runWhen === "function" && interceptor.runWhen(config8) === false) { + if (typeof interceptor.runWhen === "function" && interceptor.runWhen(config7) === false) { return; } synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous; @@ -74055,14 +74070,14 @@ var Axios = class { chain.unshift.apply(chain, requestInterceptorChain); chain.push.apply(chain, responseInterceptorChain); len = chain.length; - promise = Promise.resolve(config8); + promise = Promise.resolve(config7); while (i3 < len) { promise = promise.then(chain[i3++], chain[i3++]); } return promise; } len = requestInterceptorChain.length; - let newConfig = config8; + let newConfig = config7; i3 = 0; while (i3 < len) { const onFulfilled = requestInterceptorChain[i3++]; @@ -74086,25 +74101,25 @@ var Axios = class { } return promise; } - getUri(config8) { - config8 = mergeConfig(this.defaults, config8); - const fullPath = buildFullPath(config8.baseURL, config8.url, config8.allowAbsoluteUrls); - return buildURL(fullPath, config8.params, config8.paramsSerializer); + getUri(config7) { + config7 = mergeConfig(this.defaults, config7); + const fullPath = buildFullPath(config7.baseURL, config7.url, config7.allowAbsoluteUrls); + return buildURL(fullPath, config7.params, config7.paramsSerializer); } }; utils_default.forEach(["delete", "get", "head", "options"], function forEachMethodNoData(method) { - Axios.prototype[method] = function(url2, config8) { - return this.request(mergeConfig(config8 || {}, { + Axios.prototype[method] = function(url2, config7) { + return this.request(mergeConfig(config7 || {}, { method, url: url2, - data: (config8 || {}).data + data: (config7 || {}).data })); }; }); utils_default.forEach(["post", "put", "patch"], function forEachMethodWithData(method) { function generateHTTPMethod(isForm) { - return function httpMethod(url2, data, config8) { - return this.request(mergeConfig(config8 || {}, { + return function httpMethod(url2, data, config7) { + return this.request(mergeConfig(config7 || {}, { method, headers: isForm ? { "Content-Type": "multipart/form-data" @@ -74149,11 +74164,11 @@ var CancelToken = class _CancelToken { }; return promise; }; - executor(function cancel(message, config8, request3) { + executor(function cancel(message, config7, request3) { if (token.reason) { return; } - token.reason = new CanceledError_default(message, config8, request3); + token.reason = new CanceledError_default(message, config7, request3); resolvePromise(token.reason); }); } @@ -74882,7 +74897,7 @@ async function splitByTokenLimit(content, maxTokens) { // src/engine/anthropic.ts var AnthropicEngine = class { - constructor(config8) { + constructor(config7) { this.generateCommitMessage = async (messages) => { const systemMessage = messages.find((msg) => msg.role === "system")?.content; const restMessages = messages.filter( @@ -74912,12 +74927,12 @@ var AnthropicEngine = class { throw normalizeEngineError(error, "anthropic", this.config.model); } }; - this.config = config8; + this.config = config7; const clientOptions = { apiKey: this.config.apiKey }; if (this.config.baseURL) { clientOptions.baseURL = this.config.baseURL; } - const proxy = config8.proxy; + const proxy = config7.proxy; if (proxy) { clientOptions.httpAgent = new HttpsProxyAgent(proxy); } @@ -78734,7 +78749,7 @@ var OpenAIClient = class { // src/engine/azure.ts var AzureEngine = class { - constructor(config8) { + constructor(config7) { this.generateCommitMessage = async (messages) => { try { const REQUEST_TOKENS = messages.map((msg) => tokenCount(msg.content) + 4).reduce((a4, b7) => a4 + b7, 0); @@ -78755,7 +78770,7 @@ var AzureEngine = class { throw normalizeEngineError(error, "azure", this.config.model); } }; - this.config = config8; + this.config = config7; this.client = new OpenAIClient( this.config.baseURL, new AzureKeyCredential(this.config.apiKey) @@ -78765,10 +78780,10 @@ var AzureEngine = class { // src/engine/flowise.ts var FlowiseEngine = class { - constructor(config8) { - this.config = config8; + constructor(config7) { + this.config = config7; this.client = axios_default.create({ - url: `${config8.baseURL}/${config8.apiKey}`, + url: `${config7.baseURL}/${config7.apiKey}`, headers: { "Content-Type": "application/json" } }); } @@ -79833,9 +79848,9 @@ var extractGeminiText = (response) => { return ""; }; var GeminiEngine = class { - constructor(config8) { - this.client = new GoogleGenerativeAI(config8.apiKey); - this.config = config8; + constructor(config7) { + this.client = new GoogleGenerativeAI(config7.apiKey); + this.config = config7; } async generateCommitMessage(messages) { const systemInstruction = messages.filter((m5) => m5.role === "system").map((m5) => m5.content).join("\n"); @@ -79892,13 +79907,13 @@ var GeminiEngine = class { var DEFAULT_LLAMACPP_URL = "http://localhost:8080"; var LLAMACPP_CHAT_PATH = "/v1/chat/completions"; var LlamaCppEngine = class { - constructor(config8) { - this.config = config8; - const baseUrl = config8.baseURL || DEFAULT_LLAMACPP_URL; + constructor(config7) { + this.config = config7; + const baseUrl = config7.baseURL || DEFAULT_LLAMACPP_URL; this.chatUrl = `${baseUrl}${LLAMACPP_CHAT_PATH}`; const headers = { "Content-Type": "application/json", - ...config8.customHeaders + ...config7.customHeaders }; this.client = axios_default.create({ headers }); } @@ -79926,13 +79941,13 @@ var LlamaCppEngine = class { var DEFAULT_OLLAMA_URL = "http://localhost:11434"; var OLLAMA_CHAT_PATH = "/api/chat"; var OllamaEngine = class { - constructor(config8) { - this.config = config8; - const baseUrl = config8.baseURL || DEFAULT_OLLAMA_URL; + constructor(config7) { + this.config = config7; + const baseUrl = config7.baseURL || DEFAULT_OLLAMA_URL; this.chatUrl = `${baseUrl}${OLLAMA_CHAT_PATH}`; const headers = { "Content-Type": "application/json", - ...config8.customHeaders + ...config7.customHeaders }; this.client = axios_default.create({ headers }); } @@ -84683,13 +84698,14 @@ function parseCustomHeaders(headers) { // src/engine/openAi.ts var OpenAiEngine = class { - constructor(config8) { + constructor(config7) { this.generateCommitMessage = async (messages) => { - const isReasoningModel = /^(o[1-9]|gpt-5)/.test(this.config.model); + const isReasoningModel = typeof this.config.isReasoning === "boolean" ? this.config.isReasoning : /^(o[1-9]|gpt-5)/.test(this.config.model); + const reasoningTokens = this.config.tokensMaxReasoning || 1e3; const params = { model: this.config.model, messages, - ...isReasoningModel ? { max_completion_tokens: this.config.maxTokensOutput } : { + ...isReasoningModel ? { max_completion_tokens: reasoningTokens } : { temperature: 0, top_p: 0.1, max_tokens: this.config.maxTokensOutput @@ -84697,7 +84713,9 @@ var OpenAiEngine = class { }; try { const REQUEST_TOKENS = messages.map((msg) => tokenCount(msg.content) + 4).reduce((a4, b7) => a4 + b7, 0); - if (REQUEST_TOKENS > this.config.maxTokensInput - this.config.maxTokensOutput) + const maxInputLimit = this.config.maxTokensInput; + const maxOutPutLimit = isReasoningModel ? reasoningTokens : this.config.maxTokensOutput; + if (REQUEST_TOKENS > maxInputLimit - maxOutPutLimit) throw new Error("TOO_MUCH_TOKENS" /* tooMuchTokens */); const completion = await this.client.chat.completions.create( params @@ -84709,19 +84727,19 @@ var OpenAiEngine = class { throw normalizeEngineError(error, "openai", this.config.model); } }; - this.config = config8; + this.config = config7; const clientOptions = { - apiKey: config8.apiKey + apiKey: config7.apiKey }; - if (config8.baseURL) { - clientOptions.baseURL = config8.baseURL; + if (config7.baseURL) { + clientOptions.baseURL = config7.baseURL; } - const proxy = config8.proxy; + const proxy = config7.proxy; if (proxy) { clientOptions.httpAgent = new HttpsProxyAgent(proxy); } - if (config8.customHeaders) { - const headers = parseCustomHeaders(config8.customHeaders); + if (config7.customHeaders) { + const headers = parseCustomHeaders(config7.customHeaders); if (Object.keys(headers).length > 0) { clientOptions.defaultHeaders = headers; } @@ -84737,7 +84755,7 @@ var OpenAiEngine = class { var import_mistralai = __toESM(require_mistralai(), 1); var MistralAiEngine = class { // Using any type for Mistral client to avoid TS errors - constructor(config8) { + constructor(config7) { this.generateCommitMessage = async (messages) => { const params = { model: this.config.model, @@ -84760,13 +84778,13 @@ var MistralAiEngine = class { throw normalizeEngineError(error, "mistral", this.config.model); } }; - this.config = config8; - if (!config8.baseURL) { - this.client = new import_mistralai.Mistral({ apiKey: config8.apiKey }); + this.config = config7; + if (!config7.baseURL) { + this.client = new import_mistralai.Mistral({ apiKey: config7.apiKey }); } else { this.client = new import_mistralai.Mistral({ - apiKey: config8.apiKey, - serverURL: config8.baseURL + apiKey: config7.apiKey, + serverURL: config7.baseURL }); } } @@ -84774,9 +84792,9 @@ var MistralAiEngine = class { // src/engine/groq.ts var GroqEngine = class extends OpenAiEngine { - constructor(config8) { - config8.baseURL = "https://api.groq.com/openai/v1"; - super(config8); + constructor(config7) { + config7.baseURL = "https://api.groq.com/openai/v1"; + super(config7); } }; @@ -84784,9 +84802,9 @@ var GroqEngine = class extends OpenAiEngine { var DEFAULT_MLX_URL = "http://localhost:8080"; var MLX_CHAT_PATH = "/v1/chat/completions"; var MLXEngine = class { - constructor(config8) { - this.config = config8; - const baseUrl = config8.baseURL || DEFAULT_MLX_URL; + constructor(config7) { + this.config = config7; + const baseUrl = config7.baseURL || DEFAULT_MLX_URL; this.chatUrl = `${baseUrl}${MLX_CHAT_PATH}`; this.client = axios_default.create({ headers: { "Content-Type": "application/json" } @@ -84814,10 +84832,10 @@ var MLXEngine = class { // src/engine/deepseek.ts var DeepseekEngine = class extends OpenAiEngine { - constructor(config8) { + constructor(config7) { super({ baseURL: "https://api.deepseek.com/v1", - ...config8 + ...config7 }); // Identical method from OpenAiEngine, re-implemented here this.generateCommitMessage = async (messages) => { @@ -84850,8 +84868,8 @@ var DeepseekEngine = class extends OpenAiEngine { // src/engine/aimlapi.ts var AimlApiEngine = class { - constructor(config8) { - this.config = config8; + constructor(config7) { + this.config = config7; this.generateCommitMessage = async (messages) => { try { const response = await this.client.post("", { @@ -84865,13 +84883,13 @@ var AimlApiEngine = class { } }; this.client = axios_default.create({ - baseURL: config8.baseURL || "https://api.aimlapi.com/v1/chat/completions", + baseURL: config7.baseURL || "https://api.aimlapi.com/v1/chat/completions", headers: { - Authorization: `Bearer ${config8.apiKey}`, + Authorization: `Bearer ${config7.apiKey}`, "HTTP-Referer": "https://github.com/di-sukharev/opencommit", "X-Title": "opencommit", "Content-Type": "application/json", - ...config8.customHeaders + ...config7.customHeaders } }); } @@ -84879,8 +84897,8 @@ var AimlApiEngine = class { // src/engine/openrouter.ts var OpenRouterEngine = class { - constructor(config8) { - this.config = config8; + constructor(config7) { + this.config = config7; this.generateCommitMessage = async (messages) => { try { const response = await this.client.post("", { @@ -84897,7 +84915,7 @@ var OpenRouterEngine = class { this.client = axios_default.create({ baseURL: "https://openrouter.ai/api/v1/chat/completions", headers: { - Authorization: `Bearer ${config8.apiKey}`, + Authorization: `Bearer ${config7.apiKey}`, "HTTP-Referer": "https://github.com/di-sukharev/opencommit", "X-Title": "OpenCommit", "Content-Type": "application/json" @@ -84947,31 +84965,33 @@ function setupProxy(proxySetting) { // src/utils/engine.ts function getEngine() { - const config8 = getConfig(); - const provider = config8.OCO_AI_PROVIDER; - const customHeaders = parseCustomHeaders(config8.OCO_API_CUSTOM_HEADERS); - const resolvedProxy = resolveProxy(config8.OCO_PROXY); + const config7 = getConfig(); + const provider = config7.OCO_AI_PROVIDER; + const customHeaders = parseCustomHeaders(config7.OCO_API_CUSTOM_HEADERS); + const resolvedProxy = resolveProxy(config7.OCO_PROXY); const DEFAULT_CONFIG2 = { - model: config8.OCO_MODEL, - maxTokensOutput: config8.OCO_TOKENS_MAX_OUTPUT, - maxTokensInput: config8.OCO_TOKENS_MAX_INPUT, - baseURL: config8.OCO_API_URL, + model: config7.OCO_MODEL, + maxTokensOutput: config7.OCO_TOKENS_MAX_OUTPUT, + maxTokensInput: config7.OCO_TOKENS_MAX_INPUT, + baseURL: config7.OCO_API_URL, proxy: resolvedProxy, - apiKey: config8.OCO_API_KEY, + apiKey: config7.OCO_API_KEY, + isReasoning: config7.OCO_REASONING, + tokensMaxReasoning: config7.OCO_REASONING_MAX_TOKENS, customHeaders }; switch (provider) { case "ollama" /* OLLAMA */: return new OllamaEngine({ ...DEFAULT_CONFIG2, - ollamaThink: config8.OCO_OLLAMA_THINK + ollamaThink: config7.OCO_OLLAMA_THINK }); case "llamacpp" /* LLAMACPP */: return new LlamaCppEngine(DEFAULT_CONFIG2); case "anthropic" /* ANTHROPIC */: return new AnthropicEngine(DEFAULT_CONFIG2); case "test" /* TEST */: - return new TestAi(config8.OCO_TEST_MOCK_TYPE); + return new TestAi(config7.OCO_TEST_MOCK_TYPE); case "gemini" /* GEMINI */: return new GeminiEngine(DEFAULT_CONFIG2); case "azure" /* AZURE */: @@ -85080,8 +85100,8 @@ var getPrompt = (ruleName, ruleConfig, prompt) => { ce(`${source_default.red("\u2716")} No prompt handler for rule "${ruleName}".`); return `Please manualy set the prompt for rule "${ruleName}".`; }; -var inferPromptsFromCommitlintConfig = (config8) => { - const { rules, prompt } = config8; +var inferPromptsFromCommitlintConfig = (config7) => { + const { rules, prompt } = config7; if (!rules) return []; return Object.keys(rules).map( (ruleName) => getPrompt(ruleName, rules[ruleName], prompt) @@ -85547,9 +85567,6 @@ async function runTasksWithConcurrency(tasks, concurrency) { } // src/generateCommitMessageFromGitDiff.ts -var config5 = getConfig(); -var MAX_TOKENS_INPUT = config5.OCO_TOKENS_MAX_INPUT; -var MAX_TOKENS_OUTPUT = config5.OCO_TOKENS_MAX_OUTPUT; var generateCommitMessageChatCompletionPrompt = async (diff, fullGitMojiSpec, context) => { const INIT_MESSAGES_PROMPT = await getMainCommitPrompt( fullGitMojiSpec, @@ -85640,7 +85657,10 @@ var generateCommitMessageByDiff = async (diff, fullGitMojiSpec = false, context const INIT_MESSAGES_PROMPT_LENGTH = INIT_MESSAGES_PROMPT.map( (msg) => tokenCount(msg.content) + 4 ).reduce((a4, b7) => a4 + b7, 0); - const MAX_REQUEST_TOKENS = MAX_TOKENS_INPUT - ADJUSTMENT_FACTOR - INIT_MESSAGES_PROMPT_LENGTH - MAX_TOKENS_OUTPUT; + const isReasoningModel = typeof currentConfig.OCO_REASONING === "boolean" ? currentConfig.OCO_REASONING : /^(o[1-9]|gpt-5)/.test(currentModel); + const maxInputTokens = currentConfig.OCO_TOKENS_MAX_INPUT ?? 4096 /* DEFAULT_MAX_TOKENS_INPUT */; + const maxOutputTokens = isReasoningModel ? currentConfig.OCO_REASONING_MAX_TOKENS ?? 1e3 /* DEFAULT_MAX_REASONING */ : currentConfig.OCO_TOKENS_MAX_OUTPUT ?? 500 /* DEFAULT_MAX_TOKENS_OUTPUT */; + const MAX_REQUEST_TOKENS = maxInputTokens - ADJUSTMENT_FACTOR - INIT_MESSAGES_PROMPT_LENGTH - maxOutputTokens; if (await tokenCountAsync(diff) >= MAX_REQUEST_TOKENS) { const commitMessageTasks = await getCommitMessageTasksFromFileDiffs( diff, @@ -85652,9 +85672,6 @@ var generateCommitMessageByDiff = async (diff, fullGitMojiSpec = false, context commitMessageTasks, MAX_CONCURRENT_GENERATIONS ); - if (config5.OCO_ONE_LINE_COMMIT) { - return commitMessages.filter(Boolean).map((msg) => msg.split("\n")[0].trim()).join("; "); - } return commitMessages.join("\n\n"); } const messages = await generateCommitMessageChatCompletionPrompt( @@ -85910,7 +85927,7 @@ var trytm = async (promise) => { }; // src/commands/commit.ts -var config6 = getConfig(); +var config5 = getConfig(); var getGitRemotes = async () => { const { stdout } = await execa("git", ["remote"]); return stdout.split("\n").filter((remote) => Boolean(remote.trim())); @@ -85944,7 +85961,7 @@ var displayPushUrl = (stderr2) => { }; var checkMessageTemplate = (extraArgs2) => { for (const key in extraArgs2) { - if (extraArgs2[key].includes(config6.OCO_MESSAGE_TEMPLATE_PLACEHOLDER)) + if (extraArgs2[key].includes(config5.OCO_MESSAGE_TEMPLATE_PLACEHOLDER)) return extraArgs2[key]; } return false; @@ -85966,11 +85983,11 @@ var generateCommitMessageFromGitDiff = async ({ context ); const messageTemplate = checkMessageTemplate(extraArgs2); - if (config6.OCO_MESSAGE_TEMPLATE_PLACEHOLDER && typeof messageTemplate === "string") { + if (config5.OCO_MESSAGE_TEMPLATE_PLACEHOLDER && typeof messageTemplate === "string") { const messageTemplateIndex = extraArgs2.indexOf(messageTemplate); extraArgs2.splice(messageTemplateIndex, 1); commitMessage = messageTemplate.replace( - config6.OCO_MESSAGE_TEMPLATE_PLACEHOLDER, + config5.OCO_MESSAGE_TEMPLATE_PLACEHOLDER, commitMessage ); } @@ -86012,7 +86029,7 @@ ${source_default.grey("\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2 ); ce(stdout); const remotes = await getGitRemotes(); - if (config6.OCO_GITPUSH === false) return; + if (config5.OCO_GITPUSH === false) return; if (!remotes.length) { const { stdout: stdout2, stderr: stderr2 } = await runGitPush({ mode: "default", @@ -86282,6 +86299,27 @@ var hookCommand = G3( // src/commands/prepare-commit-msg-hook.ts var import_promises4 = __toESM(require("fs/promises"), 1); init_dist2(); + +// src/utils/provider.ts +var PROVIDER_CONFIG_REQUIREMENTS = { + ["openai" /* OPENAI */]: "apiKey", + ["anthropic" /* ANTHROPIC */]: "apiKey", + ["ollama" /* OLLAMA */]: "model", + ["llamacpp" /* LLAMACPP */]: "model", + ["gemini" /* GEMINI */]: "apiKey", + ["groq" /* GROQ */]: "apiKey", + ["mistral" /* MISTRAL */]: "apiKey", + ["deepseek" /* DEEPSEEK */]: "apiKey", + ["openrouter" /* OPENROUTER */]: "apiKey", + ["aimlapi" /* AIMLAPI */]: "apiKey", + ["azure" /* AZURE */]: "apiKey", + ["mlx" /* MLX */]: "model", + ["flowise" /* FLOWISE */]: "apiKey", + ["test" /* TEST */]: "none" +}; +var getProviderConfigRequirement = (provider = "openai" /* OPENAI */) => PROVIDER_CONFIG_REQUIREMENTS[provider] || "apiKey"; + +// src/commands/prepare-commit-msg-hook.ts var [messageFilePath, commitSource] = process.argv.slice(2); var prepareCommitMessageHook = async (isStageAllFlag = false) => { try { @@ -86302,8 +86340,8 @@ var prepareCommitMessageHook = async (isStageAllFlag = false) => { const staged = await getStagedFiles(); if (!staged) return; ae("opencommit"); - const config8 = getConfig(); - if (getProviderConfigRequirement(config8.OCO_AI_PROVIDER) === "apiKey" && !config8.OCO_API_KEY) { + const config7 = getConfig(); + if (getProviderConfigRequirement(config7.OCO_AI_PROVIDER) === "apiKey" && !config7.OCO_API_KEY) { ce( "No OCO_API_KEY is set. Set your key via `oco config set OCO_API_KEY=. For more info see https://github.com/di-sukharev/opencommit" ); @@ -86332,7 +86370,7 @@ ${fileContent.toString()}`; const messageWithoutComment = `${commitMessage} ${fileContent.toString()}`; - const message = config8.OCO_HOOK_AUTO_UNCOMMENT ? messageWithoutComment : messageWithComment; + const message = config7.OCO_HOOK_AUTO_UNCOMMENT ? messageWithoutComment : messageWithComment; await import_promises4.default.writeFile(messageFilePath, message); } catch (error) { try { @@ -86889,14 +86927,14 @@ async function runSetup() { ce("Setup cancelled"); return false; } - let config8 = {}; + let config7 = {}; if (provider === "ollama" /* OLLAMA */) { const ollamaConfig = await setupOllama(); if (!ollamaConfig) { ce("Setup cancelled"); return false; } - config8 = { + config7 = { OCO_AI_PROVIDER: ollamaConfig.provider, OCO_MODEL: ollamaConfig.model, OCO_API_URL: ollamaConfig.apiUrl, @@ -86915,7 +86953,7 @@ async function runSetup() { ce("Setup cancelled"); return false; } - config8 = { + config7 = { OCO_AI_PROVIDER: "mlx" /* MLX */, OCO_MODEL: model, OCO_API_KEY: "mlx" @@ -86927,7 +86965,7 @@ async function runSetup() { ce("Setup cancelled"); return false; } - config8 = { + config7 = { OCO_AI_PROVIDER: llamacppConfig.provider, OCO_MODEL: llamacppConfig.model, OCO_API_URL: llamacppConfig.apiUrl, @@ -86945,7 +86983,7 @@ async function runSetup() { ce("Setup cancelled"); return false; } - config8 = { + config7 = { OCO_AI_PROVIDER: provider, OCO_API_KEY: apiKey, OCO_MODEL: model @@ -86954,7 +86992,7 @@ async function runSetup() { const existingConfig = getIsGlobalConfigFileExist() ? getGlobalConfig() : DEFAULT_CONFIG; const newConfig = { ...existingConfig, - ...config8 + ...config7 }; setGlobalConfig(newConfig); ce( @@ -86970,19 +87008,19 @@ async function runSetup() { } function isFirstRun() { const hasGlobalConfig = getIsGlobalConfigFileExist(); - const config8 = getConfig(); - const provider = config8.OCO_AI_PROVIDER || "openai" /* OPENAI */; + const config7 = getConfig(); + const provider = config7.OCO_AI_PROVIDER || "openai" /* OPENAI */; const requirement = getProviderConfigRequirement(provider); - const hasRequiredConfig = requirement === "model" ? Boolean(config8.OCO_MODEL) : requirement === "apiKey" ? Boolean(config8.OCO_API_KEY) : true; + const hasRequiredConfig = requirement === "model" ? Boolean(config7.OCO_MODEL) : requirement === "apiKey" ? Boolean(config7.OCO_API_KEY) : true; return !hasGlobalConfig && !hasRequiredConfig; } async function promptForMissingApiKey() { - const config8 = getConfig(); - const provider = config8.OCO_AI_PROVIDER || "openai" /* OPENAI */; + const config7 = getConfig(); + const provider = config7.OCO_AI_PROVIDER || "openai" /* OPENAI */; if (getProviderConfigRequirement(provider) !== "apiKey") { return true; } - if (config8.OCO_API_KEY) { + if (config7.OCO_API_KEY) { return true; } console.log( @@ -87032,9 +87070,9 @@ function formatCacheAge2(timestamp) { return "just now"; } async function listModels(provider, useCache = true) { - const config8 = getConfig(); - const apiKey = config8.OCO_API_KEY; - const currentModel = config8.OCO_MODEL; + const config7 = getConfig(); + const apiKey = config7.OCO_API_KEY; + const currentModel = config7.OCO_MODEL; let models = []; if (useCache) { const cached = getCachedModels(provider); @@ -87064,8 +87102,8 @@ ${source_default.bold("Available models for")} ${source_default.cyan(provider)}: console.log(""); } async function refreshModels(provider) { - const config8 = getConfig(); - const apiKey = config8.OCO_API_KEY; + const config7 = getConfig(); + const apiKey = config7.OCO_API_KEY; const loadingSpinner = le(); loadingSpinner.start(`Fetching models from ${provider}...`); clearModelCache(); @@ -87108,8 +87146,8 @@ var modelsCommand = G3( } }, async ({ flags }) => { - const config8 = getConfig(); - const provider = flags.provider || config8.OCO_AI_PROVIDER || "openai" /* OPENAI */; + const config7 = getConfig(); + const provider = flags.provider || config7.OCO_AI_PROVIDER || "openai" /* OPENAI */; ae(source_default.bgCyan(" OpenCommit Models ")); const cacheInfo = getCacheInfo(); if (cacheInfo.timestamp) { @@ -87183,28 +87221,28 @@ var import_path7 = require("path"); // src/migrations/00_use_single_api_key_and_url.ts function use_single_api_key_and_url_default() { - const config8 = getConfig({ setDefaultValues: false }); - const aiProvider = config8.OCO_AI_PROVIDER; + const config7 = getConfig({ setDefaultValues: false }); + const aiProvider = config7.OCO_AI_PROVIDER; let apiKey; let apiUrl; if (aiProvider === "ollama" /* OLLAMA */) { - apiKey = config8["OCO_OLLAMA_API_KEY"]; - apiUrl = config8["OCO_OLLAMA_API_URL"]; + apiKey = config7["OCO_OLLAMA_API_KEY"]; + apiUrl = config7["OCO_OLLAMA_API_URL"]; } else if (aiProvider === "anthropic" /* ANTHROPIC */) { - apiKey = config8["OCO_ANTHROPIC_API_KEY"]; - apiUrl = config8["OCO_ANTHROPIC_BASE_PATH"]; + apiKey = config7["OCO_ANTHROPIC_API_KEY"]; + apiUrl = config7["OCO_ANTHROPIC_BASE_PATH"]; } else if (aiProvider === "openai" /* OPENAI */) { - apiKey = config8["OCO_OPENAI_API_KEY"]; - apiUrl = config8["OCO_OPENAI_BASE_PATH"]; + apiKey = config7["OCO_OPENAI_API_KEY"]; + apiUrl = config7["OCO_OPENAI_BASE_PATH"]; } else if (aiProvider === "azure" /* AZURE */) { - apiKey = config8["OCO_AZURE_API_KEY"]; - apiUrl = config8["OCO_AZURE_ENDPOINT"]; + apiKey = config7["OCO_AZURE_API_KEY"]; + apiUrl = config7["OCO_AZURE_ENDPOINT"]; } else if (aiProvider === "gemini" /* GEMINI */) { - apiKey = config8["OCO_GEMINI_API_KEY"]; - apiUrl = config8["OCO_GEMINI_BASE_PATH"]; + apiKey = config7["OCO_GEMINI_API_KEY"]; + apiUrl = config7["OCO_GEMINI_BASE_PATH"]; } else if (aiProvider === "flowise" /* FLOWISE */) { - apiKey = config8["OCO_FLOWISE_API_KEY"]; - apiUrl = config8["OCO_FLOWISE_ENDPOINT"]; + apiKey = config7["OCO_FLOWISE_API_KEY"]; + apiUrl = config7["OCO_FLOWISE_ENDPOINT"]; } else { throw new Error( `Migration failed, set AI provider first. Run "oco config set OCO_AI_PROVIDER=", where is one of: ${Object.values( @@ -87240,11 +87278,11 @@ function remove_obsolete_config_keys_from_global_file_default() { // src/migrations/02_set_missing_default_values.ts function set_missing_default_values_default() { - const setDefaultConfigValues = (config8) => { + const setDefaultConfigValues = (config7) => { const entriesToSet = []; for (const entry of Object.entries(DEFAULT_CONFIG)) { const [key, _value] = entry; - if (config8[key] === "undefined" || config8[key] === void 0) + if (config7[key] === "undefined" || config7[key] === void 0) entriesToSet.push(entry); } if (entriesToSet.length > 0) setConfig(entriesToSet); @@ -87289,8 +87327,8 @@ var saveCompletedMigration = (migrationName) => { }; var runMigrations = async () => { if (!getIsGlobalConfigFileExist()) return; - const config8 = getConfig(); - if (config8.OCO_AI_PROVIDER === "test" /* TEST */) return; + const config7 = getConfig(); + if (config7.OCO_AI_PROVIDER === "test" /* TEST */) return; if ([ "deepseek" /* DEEPSEEK */, "groq" /* GROQ */, @@ -87299,7 +87337,7 @@ var runMigrations = async () => { "openrouter" /* OPENROUTER */, "llamacpp" /* LLAMACPP */, "aimlapi" /* AIMLAPI */ - ].includes(config8.OCO_AI_PROVIDER)) { + ].includes(config7.OCO_AI_PROVIDER)) { return; } const completedMigrations = getCompletedMigrations(); @@ -87362,8 +87400,8 @@ function stripOcoFlags(argv) { } // src/cli.ts -var config7 = getConfig(); -setupProxy(resolveProxy(config7.OCO_PROXY)); +var config6 = getConfig(); +setupProxy(resolveProxy(config6.OCO_PROXY)); var rawArgv = process.argv.slice(2); var extraArgs = stripOcoFlags(rawArgv); Z2( diff --git a/src/commands/config.ts b/src/commands/config.ts index a8d6d2bd..f2ce05bd 100644 --- a/src/commands/config.ts +++ b/src/commands/config.ts @@ -9,9 +9,6 @@ import { join as pathJoin, resolve as pathResolve } from 'path'; import { COMMANDS } from './ENUMS'; import { TEST_MOCK_TYPES } from '../engine/testAi'; import { getI18nLocal, i18n } from '../i18n'; -import { OCO_AI_PROVIDER_ENUM } from '../utils/provider'; - -export { OCO_AI_PROVIDER_ENUM } from '../utils/provider'; export enum CONFIG_KEYS { OCO_API_KEY = 'OCO_API_KEY', @@ -33,6 +30,8 @@ export enum CONFIG_KEYS { OCO_OMIT_SCOPE = 'OCO_OMIT_SCOPE', OCO_GITPUSH = 'OCO_GITPUSH', // todo: deprecate OCO_HOOK_AUTO_UNCOMMENT = 'OCO_HOOK_AUTO_UNCOMMENT', + OCO_REASONING_MAX_TOKENS = 'OCO_REASONING_MAX_TOKENS', + OCO_REASONING = 'OCO_REASONING', OCO_OLLAMA_THINK = 'OCO_OLLAMA_THINK' } @@ -138,7 +137,7 @@ export const MODEL_LIST = { 'mistral-moderation-2411', 'mistral-moderation-latest' ], - deepseek: ['deepseek-v4-flash', 'deepseek-v4-pro'], + deepseek: ['deepseek-chat', 'deepseek-reasoner'], // AI/ML API available chat-completion models // https://api.aimlapi.com/v1/models @@ -606,7 +605,8 @@ const getDefaultModel = (provider: string | undefined): string => { export enum DEFAULT_TOKEN_LIMITS { DEFAULT_MAX_TOKENS_INPUT = 4096, - DEFAULT_MAX_TOKENS_OUTPUT = 500 + DEFAULT_MAX_TOKENS_OUTPUT = 500, + DEFAULT_MAX_REASONING = 1000 } const validateConfig = ( @@ -625,6 +625,11 @@ const validateConfig = ( } }; +const isPositiveInteger = (value: any) => + typeof value !== 'boolean' && + Number.isInteger(Number(value)) && + Number(value) > 0; + export const configValidators = { [CONFIG_KEYS.OCO_API_KEY](value: any, config: any = {}) { if (config.OCO_AI_PROVIDER !== 'openai') return value; @@ -846,8 +851,23 @@ export const configValidators = { typeof value === 'boolean', 'Must be true or false' ); + }, + [CONFIG_KEYS.OCO_REASONING](value: any) { + validateConfig( + CONFIG_KEYS.OCO_REASONING, + typeof value === 'boolean', + 'Must be true or false' + ); return value; }, + [CONFIG_KEYS.OCO_REASONING_MAX_TOKENS](value: any) { + validateConfig( + CONFIG_KEYS.OCO_REASONING_MAX_TOKENS, + isPositiveInteger(value), + 'Must be a positive integer' + ); + return typeof value === 'number' ? value : parseInt(value, 10); + }, [CONFIG_KEYS.OCO_OLLAMA_THINK](value: any) { validateConfig( @@ -858,6 +878,23 @@ export const configValidators = { } }; +export enum OCO_AI_PROVIDER_ENUM { + OLLAMA = 'ollama', + LLAMACPP = 'llamacpp', + OPENAI = 'openai', + ANTHROPIC = 'anthropic', + GEMINI = 'gemini', + AZURE = 'azure', + TEST = 'test', + FLOWISE = 'flowise', + GROQ = 'groq', + MISTRAL = 'mistral', + MLX = 'mlx', + DEEPSEEK = 'deepseek', + AIMLAPI = 'aimlapi', + OPENROUTER = 'openrouter' +} + export const PROVIDER_API_KEY_URLS: Record = { [OCO_AI_PROVIDER_ENUM.OPENAI]: 'https://platform.openai.com/api-keys', [OCO_AI_PROVIDER_ENUM.ANTHROPIC]: @@ -882,7 +919,7 @@ export const RECOMMENDED_MODELS: Record = { [OCO_AI_PROVIDER_ENUM.GEMINI]: 'gemini-1.5-flash', [OCO_AI_PROVIDER_ENUM.GROQ]: 'llama3-70b-8192', [OCO_AI_PROVIDER_ENUM.MISTRAL]: 'mistral-small-latest', - [OCO_AI_PROVIDER_ENUM.DEEPSEEK]: 'deepseek-v4-flash', + [OCO_AI_PROVIDER_ENUM.DEEPSEEK]: 'deepseek-chat', [OCO_AI_PROVIDER_ENUM.OPENROUTER]: 'openai/gpt-4o-mini', [OCO_AI_PROVIDER_ENUM.AIMLAPI]: 'gpt-4o-mini' }; @@ -907,6 +944,8 @@ export type ConfigType = { [CONFIG_KEYS.OCO_OMIT_SCOPE]: boolean; [CONFIG_KEYS.OCO_TEST_MOCK_TYPE]: string; [CONFIG_KEYS.OCO_HOOK_AUTO_UNCOMMENT]: boolean; + [CONFIG_KEYS.OCO_REASONING]?: boolean; + [CONFIG_KEYS.OCO_REASONING_MAX_TOKENS]?: number; [CONFIG_KEYS.OCO_OLLAMA_THINK]?: boolean; }; @@ -944,6 +983,7 @@ enum OCO_PROMPT_MODULE_ENUM { export const DEFAULT_CONFIG = { OCO_TOKENS_MAX_INPUT: DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_INPUT, OCO_TOKENS_MAX_OUTPUT: DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_OUTPUT, + OCO_REASONING_MAX_TOKENS: DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_REASONING, OCO_DESCRIPTION: false, OCO_EMOJI: false, OCO_MODEL: getDefaultModel('openai'), @@ -957,6 +997,7 @@ export const DEFAULT_CONFIG = { OCO_OMIT_SCOPE: false, OCO_GITPUSH: true, // todo: deprecate OCO_HOOK_AUTO_UNCOMMENT: false + // OCO_REASONING: is intentionally omitted to default to 'undefined' and preserve auto-detection. }; const initGlobalConfig = (configPath: string = defaultConfigPath) => { @@ -997,6 +1038,10 @@ const getEnvConfig = (envPath: string) => { OCO_ONE_LINE_COMMIT: parseConfigVarValue(process.env.OCO_ONE_LINE_COMMIT), OCO_TEST_MOCK_TYPE: process.env.OCO_TEST_MOCK_TYPE, OCO_OMIT_SCOPE: parseConfigVarValue(process.env.OCO_OMIT_SCOPE), + OCO_REASONING_MAX_TOKENS: parseConfigVarValue( + process.env.OCO_REASONING_MAX_TOKENS + ), + OCO_REASONING: parseConfigVarValue(process.env.OCO_REASONING), OCO_GITPUSH: parseConfigVarValue(process.env.OCO_GITPUSH) // todo: deprecate }; @@ -1222,6 +1267,18 @@ function getConfigKeyDetails(key) { description: 'Automatically uncomment the commit message in the hook', values: ['true', 'false'] }; + case CONFIG_KEYS.OCO_REASONING: + return { + description: + 'Specify if the selected model is a reasoning model (bypasses max_tokens for reasoning output)', + values: ['true', 'false'] + }; + case CONFIG_KEYS.OCO_REASONING_MAX_TOKENS: + return { + description: + 'Max token limit allocated specifically for the reasoning/thinking output', + values: ['Any positive integer'] + }; default: return { description: 'String value', diff --git a/src/engine/Engine.ts b/src/engine/Engine.ts index 95fbc5d9..7204437d 100644 --- a/src/engine/Engine.ts +++ b/src/engine/Engine.ts @@ -13,6 +13,8 @@ export interface AiEngineConfig { baseURL?: string; proxy?: string | null; customHeaders?: Record; + tokensMaxReasoning?: number; + isReasoning?: boolean; ollamaThink?: boolean; } diff --git a/src/engine/openAi.ts b/src/engine/openAi.ts index 2280046f..c4ac5568 100644 --- a/src/engine/openAi.ts +++ b/src/engine/openAi.ts @@ -51,13 +51,18 @@ export class OpenAiEngine implements AiEngine { public generateCommitMessage = async ( messages: Array ): Promise => { - const isReasoningModel = /^(o[1-9]|gpt-5)/.test(this.config.model); + const isReasoningModel = + typeof this.config.isReasoning === 'boolean' + ? this.config.isReasoning + : /^(o[1-9]|gpt-5)/.test(this.config.model); + + const reasoningTokens = this.config.tokensMaxReasoning || 1000; const params = { model: this.config.model, messages, ...(isReasoningModel - ? { max_completion_tokens: this.config.maxTokensOutput } + ? { max_completion_tokens: reasoningTokens } : { temperature: 0, top_p: 0.1, @@ -70,10 +75,13 @@ export class OpenAiEngine implements AiEngine { .map((msg) => tokenCount(msg.content as string) + 4) .reduce((a, b) => a + b, 0); - if ( - REQUEST_TOKENS > - this.config.maxTokensInput - this.config.maxTokensOutput - ) + const maxInputLimit = this.config.maxTokensInput; + + const maxOutPutLimit = isReasoningModel + ? reasoningTokens + : this.config.maxTokensOutput; + + if (REQUEST_TOKENS > maxInputLimit - maxOutPutLimit) throw new Error(GenerateCommitMessageErrorEnum.tooMuchTokens); const completion = await this.client.chat.completions.create( diff --git a/src/generateCommitMessageFromGitDiff.ts b/src/generateCommitMessageFromGitDiff.ts index 9f24ee24..dec29428 100644 --- a/src/generateCommitMessageFromGitDiff.ts +++ b/src/generateCommitMessageFromGitDiff.ts @@ -29,10 +29,6 @@ import { tokenCountAsync } from './utils/tokenCount'; -const config = getConfig(); -const MAX_TOKENS_INPUT = config.OCO_TOKENS_MAX_INPUT; -const MAX_TOKENS_OUTPUT = config.OCO_TOKENS_MAX_OUTPUT; - const generateCommitMessageChatCompletionPrompt = async ( diff: string, fullGitMojiSpec: boolean, @@ -163,11 +159,26 @@ export const generateCommitMessageByDiff = async ( (msg) => tokenCount(msg.content as string) + 4 ).reduce((a, b) => a + b, 0); + const isReasoningModel = + typeof currentConfig.OCO_REASONING === 'boolean' + ? currentConfig.OCO_REASONING + : /^(o[1-9]|gpt-5)/.test(currentModel); + + const maxInputTokens = + currentConfig.OCO_TOKENS_MAX_INPUT ?? + DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_INPUT; + + const maxOutputTokens = isReasoningModel + ? currentConfig.OCO_REASONING_MAX_TOKENS ?? + DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_REASONING + : currentConfig.OCO_TOKENS_MAX_OUTPUT ?? + DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_OUTPUT; + const MAX_REQUEST_TOKENS = - MAX_TOKENS_INPUT - + maxInputTokens - ADJUSTMENT_FACTOR - INIT_MESSAGES_PROMPT_LENGTH - - MAX_TOKENS_OUTPUT; + maxOutputTokens; if ((await tokenCountAsync(diff)) >= MAX_REQUEST_TOKENS) { const commitMessageTasks = await getCommitMessageTasksFromFileDiffs( @@ -182,15 +193,6 @@ export const generateCommitMessageByDiff = async ( MAX_CONCURRENT_GENERATIONS ); - // When OCO_ONE_LINE_COMMIT is enabled, combine the first line of each - // split-diff message into a single line instead of joining with '\n\n'. - if (config.OCO_ONE_LINE_COMMIT) { - return commitMessages - .filter(Boolean) - .map((msg) => msg!.split('\n')[0].trim()) - .join('; '); - } - return commitMessages.join('\n\n'); } diff --git a/src/utils/engine.ts b/src/utils/engine.ts index af358edb..3b40bb64 100644 --- a/src/utils/engine.ts +++ b/src/utils/engine.ts @@ -31,6 +31,8 @@ export function getEngine(): AiEngine { baseURL: config.OCO_API_URL!, proxy: resolvedProxy, apiKey: config.OCO_API_KEY!, + isReasoning: config.OCO_REASONING, + tokensMaxReasoning: config.OCO_REASONING_MAX_TOKENS, customHeaders }; diff --git a/test/unit/config.test.ts b/test/unit/config.test.ts index 6accb45a..96db3c73 100644 --- a/test/unit/config.test.ts +++ b/test/unit/config.test.ts @@ -101,13 +101,16 @@ describe('config', () => { globalConfigFile = await generateConfig('.opencommit', { OCO_TOKENS_MAX_INPUT: '4096', OCO_TOKENS_MAX_OUTPUT: '500', - OCO_GITPUSH: 'true' + OCO_GITPUSH: 'true', + OCO_REASONING: 'false' }); envConfigFile = await generateConfig('.env', { OCO_TOKENS_MAX_INPUT: '8192', OCO_ONE_LINE_COMMIT: 'false', - OCO_OMIT_SCOPE: 'true' + OCO_OMIT_SCOPE: 'true', + OCO_REASONING: 'true', + OCO_REASONING_MAX_TOKENS: '2048' }); const config = getConfig({ @@ -121,6 +124,8 @@ describe('config', () => { expect(config.OCO_GITPUSH).toEqual(true); expect(config.OCO_ONE_LINE_COMMIT).toEqual(false); expect(config.OCO_OMIT_SCOPE).toEqual(true); + expect(config.OCO_REASONING).toEqual(true); + expect(config.OCO_REASONING_MAX_TOKENS).toEqual(2048); }); it('should handle custom HTTP headers correctly', async () => { @@ -202,6 +207,8 @@ describe('config', () => { expect(config).not.toEqual(null); expect(config.OCO_API_KEY).toEqual(undefined); + // Ensure OCO_REASONING is undefined by default (auto-detect mode) + expect(config.OCO_REASONING).toEqual(undefined); }); it('should not create a global config file when only reading defaults', async () => { @@ -308,7 +315,9 @@ describe('config', () => { [ [CONFIG_KEYS.OCO_TOKENS_MAX_INPUT, '8192'], [CONFIG_KEYS.OCO_DESCRIPTION, 'true'], - [CONFIG_KEYS.OCO_ONE_LINE_COMMIT, 'false'] + [CONFIG_KEYS.OCO_ONE_LINE_COMMIT, 'false'], + [CONFIG_KEYS.OCO_REASONING, 'true'], + [CONFIG_KEYS.OCO_REASONING_MAX_TOKENS, '1024'] ], globalConfigFile.filePath ); @@ -319,6 +328,8 @@ describe('config', () => { expect(config.OCO_TOKENS_MAX_INPUT).toEqual(8192); expect(config.OCO_DESCRIPTION).toEqual(true); expect(config.OCO_ONE_LINE_COMMIT).toEqual(false); + expect(config.OCO_REASONING).toEqual(true); + expect(config.OCO_REASONING_MAX_TOKENS).toEqual(1024); }); it('should throw an error for unsupported config keys', async () => { @@ -386,5 +397,32 @@ describe('config', () => { expect(config.OCO_PROXY).toEqual(null); expect(fileContent).toContain('OCO_PROXY=null'); }); + + it('should validate OCO_REASONING_MAX_TOKENS as a positive integer', async () => { + globalConfigFile = await generateConfig('.opencommit', {}); + + await setConfig( + [[CONFIG_KEYS.OCO_REASONING_MAX_TOKENS, '1024']], + globalConfigFile.filePath + ); + let config = getConfig({ globalPath: globalConfigFile.filePath }); + expect(config.OCO_REASONING_MAX_TOKENS).toEqual(1024); + + const invalidValues = [ + '0', + 0, + '-10', + -10, + '10.5', + 10.5, + '100abc', + 'invalid' + ]; + for (const val of invalidValues) { + expect(() => + configValidators[CONFIG_KEYS.OCO_REASONING_MAX_TOKENS](val) + ).toThrow(); + } + }); }); }); diff --git a/test/unit/openAi.test.ts b/test/unit/openAi.test.ts index 2ebeeb39..e6c71caa 100644 --- a/test/unit/openAi.test.ts +++ b/test/unit/openAi.test.ts @@ -5,7 +5,8 @@ describe('OpenAiEngine', () => { const baseConfig = { apiKey: 'test-openai-key', maxTokensInput: 4096, - maxTokensOutput: 256 + maxTokensOutput: 256, + tokensMaxReasoning: 1024 }; const messages: Array = [ @@ -30,7 +31,7 @@ describe('OpenAiEngine', () => { expect(create).toHaveBeenCalledWith( expect.objectContaining({ model: 'o3-mini', - max_completion_tokens: 256 + max_completion_tokens: 1024 }) ); expect(create).toHaveBeenCalledWith( @@ -68,4 +69,93 @@ describe('OpenAiEngine', () => { }) ); }); + + it('forces standard params when isReasoning is explicitly false', async () => { + const engine = new OpenAiEngine({ + ...baseConfig, + model: 'o3-mini', + isReasoning: false + }); + + const create = jest + .spyOn(engine.client.chat.completions, 'create') + .mockResolvedValue({ + choices: [{ message: { content: 'feat(openai): forced standard' } }] + } as any); + + await engine.generateCommitMessage(messages); + + expect(create).toHaveBeenCalledWith( + expect.objectContaining({ + model: 'o3-mini', + max_tokens: 256, + temperature: 0, + top_p: 0.1 + }) + ); + }); + + it('throws TOO_MUCH_TOKENS error when input exceeds token limit boundary', async () => { + const engine = new OpenAiEngine({ + ...baseConfig, + model: 'o3-mini', + maxTokensInput: 1024, + tokensMaxReasoning: 1024 + // 1024 (input) - 1024 (reasoning limit) leaves 0 tokens for the prompt. + // This guarantees the request will exceed the allowed boundary. + }); + + await expect(engine.generateCommitMessage(messages)).rejects.toThrow( + /TOO_MUCH_TOKENS/ + ); + }); + + it('forces reasoning params when isReasoning is explicitly true', async () => { + const engine = new OpenAiEngine({ + ...baseConfig, + model: 'gpt-4', + isReasoning: true + }); + + const create = jest + .spyOn(engine.client.chat.completions, 'create') + .mockResolvedValue({ + choices: [{ message: { content: 'feat(openai): forced reasoning' } }] + } as any); + + await engine.generateCommitMessage(messages); + + expect(create).toHaveBeenCalledWith( + expect.objectContaining({ + model: 'gpt-4', + max_completion_tokens: 1024 + }) + ); + }); + + it('successfully reaches mocked client with real default config for auto-detected reasoning model', async () => { + const engine = new OpenAiEngine({ + apiKey: 'test-key', + model: 'o3-mini', + maxTokensInput: 4096, + maxTokensOutput: 500, + tokensMaxReasoning: 1000 + }); + + const create = jest + .spyOn(engine.client.chat.completions, 'create') + .mockResolvedValue({ + choices: [{ message: { content: 'feat(default): success' } }] + } as any); + + const result = await engine.generateCommitMessage(messages); + + expect(result).toBe('feat(default): success'); + expect(create).toHaveBeenCalledWith( + expect.objectContaining({ + model: 'o3-mini', + max_completion_tokens: 1000 + }) + ); + }); }); From 42619942b28b1356128f8e0df1456dd3d7d353a2 Mon Sep 17 00:00:00 2001 From: watashi-00 Date: Sat, 22 Aug 2026 15:57:42 -0300 Subject: [PATCH 2/4] chore: sync deepseek model defaults with upstream master --- out/cli.cjs | 4 ++-- src/commands/config.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/out/cli.cjs b/out/cli.cjs index 35088a20..4bbd3cb8 100755 --- a/out/cli.cjs +++ b/out/cli.cjs @@ -67431,7 +67431,7 @@ var MODEL_LIST = { "mistral-moderation-2411", "mistral-moderation-latest" ], - deepseek: ["deepseek-chat", "deepseek-reasoner"], + deepseek: ["deepseek-v4-flash", "deepseek-v4-pro"], // AI/ML API available chat-completion models // https://api.aimlapi.com/v1/models aimlapi: [ @@ -68156,7 +68156,7 @@ var RECOMMENDED_MODELS = { ["gemini" /* GEMINI */]: "gemini-1.5-flash", ["groq" /* GROQ */]: "llama3-70b-8192", ["mistral" /* MISTRAL */]: "mistral-small-latest", - ["deepseek" /* DEEPSEEK */]: "deepseek-chat", + ["deepseek" /* DEEPSEEK */]: "deepseek-v4-flash", ["openrouter" /* OPENROUTER */]: "openai/gpt-4o-mini", ["aimlapi" /* AIMLAPI */]: "gpt-4o-mini" }; diff --git a/src/commands/config.ts b/src/commands/config.ts index f2ce05bd..5867780f 100644 --- a/src/commands/config.ts +++ b/src/commands/config.ts @@ -137,7 +137,7 @@ export const MODEL_LIST = { 'mistral-moderation-2411', 'mistral-moderation-latest' ], - deepseek: ['deepseek-chat', 'deepseek-reasoner'], + deepseek: ['deepseek-v4-flash', 'deepseek-v4-pro'], // AI/ML API available chat-completion models // https://api.aimlapi.com/v1/models @@ -919,7 +919,7 @@ export const RECOMMENDED_MODELS: Record = { [OCO_AI_PROVIDER_ENUM.GEMINI]: 'gemini-1.5-flash', [OCO_AI_PROVIDER_ENUM.GROQ]: 'llama3-70b-8192', [OCO_AI_PROVIDER_ENUM.MISTRAL]: 'mistral-small-latest', - [OCO_AI_PROVIDER_ENUM.DEEPSEEK]: 'deepseek-chat', + [OCO_AI_PROVIDER_ENUM.DEEPSEEK]: 'deepseek-v4-flash', [OCO_AI_PROVIDER_ENUM.OPENROUTER]: 'openai/gpt-4o-mini', [OCO_AI_PROVIDER_ENUM.AIMLAPI]: 'gpt-4o-mini' }; From bee2d2b7dbbfb61d66b05adc3050ca862662563b Mon Sep 17 00:00:00 2001 From: di-sukharev Date: Sat, 22 Aug 2026 22:05:11 +0300 Subject: [PATCH 3/4] fix(reasoning): address maintainer review feedback --- out/cli.cjs | 96 ++++++++++++++----------- src/commands/config.ts | 41 +++++------ src/generateCommitMessageFromGitDiff.ts | 9 +++ src/utils/engine.ts | 3 +- test/unit/config.test.ts | 7 +- test/unit/openAi.test.ts | 60 +++++++++------- 6 files changed, 123 insertions(+), 93 deletions(-) diff --git a/out/cli.cjs b/out/cli.cjs index 4bbd3cb8..79290259 100755 --- a/out/cli.cjs +++ b/out/cli.cjs @@ -67306,6 +67306,42 @@ function getI18nLocal(value) { return false; } +// src/utils/provider.ts +var OCO_AI_PROVIDER_ENUM = /* @__PURE__ */ ((OCO_AI_PROVIDER_ENUM2) => { + OCO_AI_PROVIDER_ENUM2["OLLAMA"] = "ollama"; + OCO_AI_PROVIDER_ENUM2["LLAMACPP"] = "llamacpp"; + OCO_AI_PROVIDER_ENUM2["OPENAI"] = "openai"; + OCO_AI_PROVIDER_ENUM2["ANTHROPIC"] = "anthropic"; + OCO_AI_PROVIDER_ENUM2["GEMINI"] = "gemini"; + OCO_AI_PROVIDER_ENUM2["AZURE"] = "azure"; + OCO_AI_PROVIDER_ENUM2["TEST"] = "test"; + OCO_AI_PROVIDER_ENUM2["FLOWISE"] = "flowise"; + OCO_AI_PROVIDER_ENUM2["GROQ"] = "groq"; + OCO_AI_PROVIDER_ENUM2["MISTRAL"] = "mistral"; + OCO_AI_PROVIDER_ENUM2["MLX"] = "mlx"; + OCO_AI_PROVIDER_ENUM2["DEEPSEEK"] = "deepseek"; + OCO_AI_PROVIDER_ENUM2["AIMLAPI"] = "aimlapi"; + OCO_AI_PROVIDER_ENUM2["OPENROUTER"] = "openrouter"; + return OCO_AI_PROVIDER_ENUM2; +})(OCO_AI_PROVIDER_ENUM || {}); +var PROVIDER_CONFIG_REQUIREMENTS = { + ["openai" /* OPENAI */]: "apiKey", + ["anthropic" /* ANTHROPIC */]: "apiKey", + ["ollama" /* OLLAMA */]: "model", + ["llamacpp" /* LLAMACPP */]: "model", + ["gemini" /* GEMINI */]: "apiKey", + ["groq" /* GROQ */]: "apiKey", + ["mistral" /* MISTRAL */]: "apiKey", + ["deepseek" /* DEEPSEEK */]: "apiKey", + ["openrouter" /* OPENROUTER */]: "apiKey", + ["aimlapi" /* AIMLAPI */]: "apiKey", + ["azure" /* AZURE */]: "apiKey", + ["mlx" /* MLX */]: "model", + ["flowise" /* FLOWISE */]: "apiKey", + ["test" /* TEST */]: "none" +}; +var getProviderConfigRequirement = (provider = "openai" /* OPENAI */) => PROVIDER_CONFIG_REQUIREMENTS[provider] || "apiKey"; + // src/commands/config.ts var CONFIG_KEYS = /* @__PURE__ */ ((CONFIG_KEYS3) => { CONFIG_KEYS3["OCO_API_KEY"] = "OCO_API_KEY"; @@ -67903,7 +67939,16 @@ var validateConfig = (key, condition, validationMessage) => { process.exit(1); } }; -var isPositiveInteger = (value) => typeof value !== "boolean" && Number.isInteger(Number(value)) && Number(value) > 0; +var parsePositiveInteger = (value) => { + if (typeof value === "number") { + return Number.isSafeInteger(value) && value > 0 ? value : void 0; + } + if (typeof value !== "string" || !/^[1-9]\d*$/.test(value)) { + return void 0; + } + const parsedValue = Number(value); + return Number.isSafeInteger(parsedValue) ? parsedValue : void 0; +}; var configValidators = { ["OCO_API_KEY" /* OCO_API_KEY */](value, config7 = {}) { if (config7.OCO_AI_PROVIDER !== "openai") return value; @@ -68102,12 +68147,13 @@ var configValidators = { return value; }, ["OCO_REASONING_MAX_TOKENS" /* OCO_REASONING_MAX_TOKENS */](value) { + const parsedValue = parsePositiveInteger(value); validateConfig( "OCO_REASONING_MAX_TOKENS" /* OCO_REASONING_MAX_TOKENS */, - isPositiveInteger(value), + parsedValue !== void 0, "Must be a positive integer" ); - return typeof value === "number" ? value : parseInt(value, 10); + return parsedValue; }, ["OCO_OLLAMA_THINK" /* OCO_OLLAMA_THINK */](value) { validateConfig( @@ -68117,23 +68163,6 @@ var configValidators = { ); } }; -var OCO_AI_PROVIDER_ENUM = /* @__PURE__ */ ((OCO_AI_PROVIDER_ENUM2) => { - OCO_AI_PROVIDER_ENUM2["OLLAMA"] = "ollama"; - OCO_AI_PROVIDER_ENUM2["LLAMACPP"] = "llamacpp"; - OCO_AI_PROVIDER_ENUM2["OPENAI"] = "openai"; - OCO_AI_PROVIDER_ENUM2["ANTHROPIC"] = "anthropic"; - OCO_AI_PROVIDER_ENUM2["GEMINI"] = "gemini"; - OCO_AI_PROVIDER_ENUM2["AZURE"] = "azure"; - OCO_AI_PROVIDER_ENUM2["TEST"] = "test"; - OCO_AI_PROVIDER_ENUM2["FLOWISE"] = "flowise"; - OCO_AI_PROVIDER_ENUM2["GROQ"] = "groq"; - OCO_AI_PROVIDER_ENUM2["MISTRAL"] = "mistral"; - OCO_AI_PROVIDER_ENUM2["MLX"] = "mlx"; - OCO_AI_PROVIDER_ENUM2["DEEPSEEK"] = "deepseek"; - OCO_AI_PROVIDER_ENUM2["AIMLAPI"] = "aimlapi"; - OCO_AI_PROVIDER_ENUM2["OPENROUTER"] = "openrouter"; - return OCO_AI_PROVIDER_ENUM2; -})(OCO_AI_PROVIDER_ENUM || {}); var PROVIDER_API_KEY_URLS = { ["openai" /* OPENAI */]: "https://platform.openai.com/api-keys", ["anthropic" /* ANTHROPIC */]: "https://console.anthropic.com/settings/keys", @@ -84964,8 +84993,7 @@ function setupProxy(proxySetting) { } // src/utils/engine.ts -function getEngine() { - const config7 = getConfig(); +function getEngine(config7 = getConfig()) { const provider = config7.OCO_AI_PROVIDER; const customHeaders = parseCustomHeaders(config7.OCO_API_CUSTOM_HEADERS); const resolvedProxy = resolveProxy(config7.OCO_PROXY); @@ -85672,6 +85700,9 @@ var generateCommitMessageByDiff = async (diff, fullGitMojiSpec = false, context commitMessageTasks, MAX_CONCURRENT_GENERATIONS ); + if (currentConfig.OCO_ONE_LINE_COMMIT) { + return commitMessages.filter(Boolean).map((message) => message.split("\n")[0].trim()).join("; "); + } return commitMessages.join("\n\n"); } const messages = await generateCommitMessageChatCompletionPrompt( @@ -86299,27 +86330,6 @@ var hookCommand = G3( // src/commands/prepare-commit-msg-hook.ts var import_promises4 = __toESM(require("fs/promises"), 1); init_dist2(); - -// src/utils/provider.ts -var PROVIDER_CONFIG_REQUIREMENTS = { - ["openai" /* OPENAI */]: "apiKey", - ["anthropic" /* ANTHROPIC */]: "apiKey", - ["ollama" /* OLLAMA */]: "model", - ["llamacpp" /* LLAMACPP */]: "model", - ["gemini" /* GEMINI */]: "apiKey", - ["groq" /* GROQ */]: "apiKey", - ["mistral" /* MISTRAL */]: "apiKey", - ["deepseek" /* DEEPSEEK */]: "apiKey", - ["openrouter" /* OPENROUTER */]: "apiKey", - ["aimlapi" /* AIMLAPI */]: "apiKey", - ["azure" /* AZURE */]: "apiKey", - ["mlx" /* MLX */]: "model", - ["flowise" /* FLOWISE */]: "apiKey", - ["test" /* TEST */]: "none" -}; -var getProviderConfigRequirement = (provider = "openai" /* OPENAI */) => PROVIDER_CONFIG_REQUIREMENTS[provider] || "apiKey"; - -// src/commands/prepare-commit-msg-hook.ts var [messageFilePath, commitSource] = process.argv.slice(2); var prepareCommitMessageHook = async (isStageAllFlag = false) => { try { diff --git a/src/commands/config.ts b/src/commands/config.ts index 5867780f..812fffc0 100644 --- a/src/commands/config.ts +++ b/src/commands/config.ts @@ -9,6 +9,9 @@ import { join as pathJoin, resolve as pathResolve } from 'path'; import { COMMANDS } from './ENUMS'; import { TEST_MOCK_TYPES } from '../engine/testAi'; import { getI18nLocal, i18n } from '../i18n'; +import { OCO_AI_PROVIDER_ENUM } from '../utils/provider'; + +export { OCO_AI_PROVIDER_ENUM } from '../utils/provider'; export enum CONFIG_KEYS { OCO_API_KEY = 'OCO_API_KEY', @@ -625,10 +628,18 @@ const validateConfig = ( } }; -const isPositiveInteger = (value: any) => - typeof value !== 'boolean' && - Number.isInteger(Number(value)) && - Number(value) > 0; +const parsePositiveInteger = (value: unknown): number | undefined => { + if (typeof value === 'number') { + return Number.isSafeInteger(value) && value > 0 ? value : undefined; + } + + if (typeof value !== 'string' || !/^[1-9]\d*$/.test(value)) { + return undefined; + } + + const parsedValue = Number(value); + return Number.isSafeInteger(parsedValue) ? parsedValue : undefined; +}; export const configValidators = { [CONFIG_KEYS.OCO_API_KEY](value: any, config: any = {}) { @@ -861,12 +872,13 @@ export const configValidators = { return value; }, [CONFIG_KEYS.OCO_REASONING_MAX_TOKENS](value: any) { + const parsedValue = parsePositiveInteger(value); validateConfig( CONFIG_KEYS.OCO_REASONING_MAX_TOKENS, - isPositiveInteger(value), + parsedValue !== undefined, 'Must be a positive integer' ); - return typeof value === 'number' ? value : parseInt(value, 10); + return parsedValue!; }, [CONFIG_KEYS.OCO_OLLAMA_THINK](value: any) { @@ -878,23 +890,6 @@ export const configValidators = { } }; -export enum OCO_AI_PROVIDER_ENUM { - OLLAMA = 'ollama', - LLAMACPP = 'llamacpp', - OPENAI = 'openai', - ANTHROPIC = 'anthropic', - GEMINI = 'gemini', - AZURE = 'azure', - TEST = 'test', - FLOWISE = 'flowise', - GROQ = 'groq', - MISTRAL = 'mistral', - MLX = 'mlx', - DEEPSEEK = 'deepseek', - AIMLAPI = 'aimlapi', - OPENROUTER = 'openrouter' -} - export const PROVIDER_API_KEY_URLS: Record = { [OCO_AI_PROVIDER_ENUM.OPENAI]: 'https://platform.openai.com/api-keys', [OCO_AI_PROVIDER_ENUM.ANTHROPIC]: diff --git a/src/generateCommitMessageFromGitDiff.ts b/src/generateCommitMessageFromGitDiff.ts index dec29428..ab9975cf 100644 --- a/src/generateCommitMessageFromGitDiff.ts +++ b/src/generateCommitMessageFromGitDiff.ts @@ -193,6 +193,15 @@ export const generateCommitMessageByDiff = async ( MAX_CONCURRENT_GENERATIONS ); + // Keep one-line mode intact when a large diff is split into multiple + // requests by combining the subject from each generated message. + if (currentConfig.OCO_ONE_LINE_COMMIT) { + return commitMessages + .filter(Boolean) + .map((message) => message!.split('\n')[0].trim()) + .join('; '); + } + return commitMessages.join('\n\n'); } diff --git a/src/utils/engine.ts b/src/utils/engine.ts index 3b40bb64..b6f6fb27 100644 --- a/src/utils/engine.ts +++ b/src/utils/engine.ts @@ -17,8 +17,7 @@ import { OpenRouterEngine } from '../engine/openrouter'; import { parseCustomHeaders } from './customHeaders'; import { resolveProxy } from './proxy'; -export function getEngine(): AiEngine { - const config = getConfig(); +export function getEngine(config = getConfig()): AiEngine { const provider = config.OCO_AI_PROVIDER; const customHeaders = parseCustomHeaders(config.OCO_API_CUSTOM_HEADERS); diff --git a/test/unit/config.test.ts b/test/unit/config.test.ts index 96db3c73..3b13b240 100644 --- a/test/unit/config.test.ts +++ b/test/unit/config.test.ts @@ -416,7 +416,12 @@ describe('config', () => { '10.5', 10.5, '100abc', - 'invalid' + 'invalid', + '0x10', + '1e3', + ' 1000 ', + true, + Number.MAX_SAFE_INTEGER + 1 ]; for (const val of invalidValues) { expect(() => diff --git a/test/unit/openAi.test.ts b/test/unit/openAi.test.ts index e6c71caa..f2780492 100644 --- a/test/unit/openAi.test.ts +++ b/test/unit/openAi.test.ts @@ -1,5 +1,8 @@ import { OpenAI } from 'openai'; +import { getConfig } from '../../src/commands/config'; import { OpenAiEngine } from '../../src/engine/openAi'; +import { getEngine } from '../../src/utils/engine'; +import { prepareFile } from './utils'; describe('OpenAiEngine', () => { const baseConfig = { @@ -133,29 +136,38 @@ describe('OpenAiEngine', () => { ); }); - it('successfully reaches mocked client with real default config for auto-detected reasoning model', async () => { - const engine = new OpenAiEngine({ - apiKey: 'test-key', - model: 'o3-mini', - maxTokensInput: 4096, - maxTokensOutput: 500, - tokensMaxReasoning: 1000 - }); - - const create = jest - .spyOn(engine.client.chat.completions, 'create') - .mockResolvedValue({ - choices: [{ message: { content: 'feat(default): success' } }] - } as any); - - const result = await engine.generateCommitMessage(messages); - - expect(result).toBe('feat(default): success'); - expect(create).toHaveBeenCalledWith( - expect.objectContaining({ - model: 'o3-mini', - max_completion_tokens: 1000 - }) - ); + it('auto-detects a reasoning model through the real default config and engine path', async () => { + const envFile = await prepareFile('.env', ''); + + try { + const config = getConfig({ + envPath: envFile.filePath, + globalPath: `${envFile.filePath}.missing` + }); + config.OCO_MODEL = 'o3-mini'; + config.OCO_API_KEY = 'test-key'; + + expect(config.OCO_REASONING).toBeUndefined(); + expect(config.OCO_REASONING_MAX_TOKENS).toBe(1000); + + const engine = getEngine(config) as OpenAiEngine; + const create = jest + .spyOn(engine.client.chat.completions, 'create') + .mockResolvedValue({ + choices: [{ message: { content: 'feat(default): success' } }] + } as any); + + const result = await engine.generateCommitMessage(messages); + + expect(result).toBe('feat(default): success'); + expect(create).toHaveBeenCalledWith( + expect.objectContaining({ + model: 'o3-mini', + max_completion_tokens: 1000 + }) + ); + } finally { + await envFile.cleanup(); + } }); }); From 606e6e32f02d9d9270331bac94ac28c59a4b3d7d Mon Sep 17 00:00:00 2001 From: di-sukharev Date: Sat, 22 Aug 2026 22:06:40 +0300 Subject: [PATCH 4/4] docs(reasoning): document model token controls --- README.md | 2 ++ out/cli.cjs | 4 ++-- src/commands/config.ts | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 50d5ad71..d13c8873 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,8 @@ OCO_API_URL= OCO_API_CUSTOM_HEADERS= OCO_TOKENS_MAX_INPUT= OCO_TOKENS_MAX_OUTPUT= +OCO_REASONING= +OCO_REASONING_MAX_TOKENS=