git-cai-cli 0.17.2__tar.gz → 0.17.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.github/ci/cai_config.ci.yml +5 -6
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.github/workflows/python-tests.yml +1 -1
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.mega-linter.yml +1 -1
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/Makefile +9 -15
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/PKG-INFO +1 -1
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/cai_config.yml +6 -6
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/docs/git-cai.txt +29 -24
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/docs/man/git-cai.1 +31 -26
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/_version.py +3 -3
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/config.py +18 -6
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/gitutils.py +39 -10
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/llm.py +64 -8
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli.egg-info/PKG-INFO +1 -1
- git_cai_cli-0.17.4/src/git_cai_cli.egg-info/scm_version.json +8 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_config.py +12 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_gitutils.py +50 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_llm.py +200 -3
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_options.py +1 -1
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/uv.lock +3 -3
- git_cai_cli-0.17.2/src/git_cai_cli.egg-info/scm_version.json +0 -8
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.caiignore +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.gitattributes +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.github/cd/.SRCINFO +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.github/cd/PKGBUILD +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.github/ci/_version.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.github/ci/tokens.ci.yml +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.github/workflows/release.yml +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.github/workflows/release_aur.yml +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.gitignore +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/.bandit.yml +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/.checkov.yml +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/.flake8 +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/.ls-lint.yml +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/.markdownlint.json +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/.proselintrc.json +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/.pylintrc +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/.yamllint.yml +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/check_git_branch_name.sh +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/lychee.toml +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/pyrightconfig.json +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.markdownlintignore +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.semgrepignore +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.trivyignore +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/CLAUDE.md +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/LICENSE +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/README.md +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/pyproject.toml +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/setup.cfg +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/__init__.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/cli/__init__.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/cli/cli.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/cli/helptext.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/cli/modes.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/__init__.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/changelog.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/completion.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/doctor.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/explain.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/generation.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/init.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/languages.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/options.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/pr.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/prompts_fallback.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/release.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/secrets.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/spinner.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/split.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/squash.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/stats.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/validate.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/main.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli.egg-info/SOURCES.txt +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli.egg-info/dependency_links.txt +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli.egg-info/entry_points.txt +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli.egg-info/requires.txt +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli.egg-info/scm_file_list.json +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli.egg-info/top_level.txt +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/conftest.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/integration/test_cli_integration.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/integration/test_config_integration.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/integration/test_gitutils_integration.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/integration/test_modes_integration.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/integration/test_options_integration.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/integration/test_pr_integration.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/integration/test_squash_integration.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_amend.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_branch_context.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_changelog.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_classification.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_cli.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_completion.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_conventional.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_custom_provider.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_doctor.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_explain.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_generation.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_helptext.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_init.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_main.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_modes.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_pr.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_print.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_prompt_loading.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_release.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_secrets.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_set_config.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_signoff.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_spinner.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_split.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_squash.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_stats.py +0 -0
- {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_validate.py +0 -0
|
@@ -6,14 +6,13 @@ load_tokens_from: /home/runner/work/.config/cai/tokens.yml
|
|
|
6
6
|
timeout: 30
|
|
7
7
|
full_files: false
|
|
8
8
|
anthropic:
|
|
9
|
-
model: claude-haiku-
|
|
10
|
-
temperature: 0
|
|
9
|
+
model: claude-haiku-5-5
|
|
11
10
|
max_tokens: 32768
|
|
12
11
|
deepseek:
|
|
13
|
-
model: deepseek-
|
|
12
|
+
model: deepseek-flash
|
|
14
13
|
temperature: 0
|
|
15
14
|
gemini:
|
|
16
|
-
model: gemini-3.
|
|
15
|
+
model: gemini-3.5-flash-lite
|
|
17
16
|
temperature: 0
|
|
18
17
|
groq:
|
|
19
18
|
model: openai/gpt-oss-20b
|
|
@@ -22,11 +21,11 @@ mistral:
|
|
|
22
21
|
model: codestral-2508
|
|
23
22
|
temperature: 0
|
|
24
23
|
ollama:
|
|
25
|
-
model:
|
|
24
|
+
model: gemma4:e2b
|
|
26
25
|
temperature: 0
|
|
27
26
|
timeout: 300
|
|
28
27
|
openai:
|
|
29
|
-
model: gpt-
|
|
28
|
+
model: gpt-6-luna
|
|
30
29
|
xai:
|
|
31
30
|
model: grok-4.3
|
|
32
31
|
temperature: 0
|
|
@@ -99,7 +99,7 @@ jobs:
|
|
|
99
99
|
|
|
100
100
|
# MegaLinter
|
|
101
101
|
- name: MegaLinter
|
|
102
|
-
uses: oxsecurity/megalinter@
|
|
102
|
+
uses: oxsecurity/megalinter@f2fe8c1cba37091848df3e524066c8c23ef353ed # v10.2.0
|
|
103
103
|
id: ml
|
|
104
104
|
env:
|
|
105
105
|
VALIDATE_ALL_CODEBASE: false
|
|
@@ -54,7 +54,7 @@ LINTER_RULES_PATH: .linters
|
|
|
54
54
|
LOG_LEVEL: INFO
|
|
55
55
|
MARKDOWN_DEFAULT_STYLE: markdownlint
|
|
56
56
|
# Pins the Docker image for local runs to the version the CI workflow pins.
|
|
57
|
-
MEGALINTER_VERSION: v10.
|
|
57
|
+
MEGALINTER_VERSION: v10.2.0
|
|
58
58
|
PARALLEL: true
|
|
59
59
|
PRINT_ALPACA: false
|
|
60
60
|
PYTHON_BANDIT_RULES_PATH: .linters
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/make -f
|
|
2
2
|
|
|
3
|
-
PIP := pipx
|
|
4
3
|
UV := uv
|
|
5
4
|
SHELL := /bin/bash
|
|
5
|
+
MEGALINTER_VERSION := $(shell sed -n 's/^MEGALINTER_VERSION: *//p' .mega-linter.yml)
|
|
6
|
+
MEGALINTER_IMAGE := ghcr.io/oxsecurity/megalinter:$(MEGALINTER_VERSION)
|
|
7
|
+
# --platform: the image is amd64 only. z: relabels the mount on SELinux hosts, ignored elsewhere.
|
|
8
|
+
MEGALINTER_RUN := docker run --rm --platform linux/amd64 -v "$(CURDIR):/tmp/lint:rw,z"
|
|
6
9
|
|
|
7
|
-
.PHONY: help lint check-docker
|
|
10
|
+
.PHONY: help lint check-docker lint-fix add-lint-hook clean test
|
|
8
11
|
|
|
9
12
|
help: ## Shows this help message
|
|
10
13
|
@echo "Available commands:"
|
|
@@ -27,25 +30,16 @@ check-docker: ## Checks if docker is installed
|
|
|
27
30
|
docker --version; \
|
|
28
31
|
fi
|
|
29
32
|
|
|
30
|
-
check-npx: ## Checks if npx is installed
|
|
31
|
-
@if ! command -v npx &> /dev/null; then \
|
|
32
|
-
echo "npx is not installed. Please install it."; \
|
|
33
|
-
exit 1; \
|
|
34
|
-
else \
|
|
35
|
-
echo "npx version:"; \
|
|
36
|
-
npx --version; \
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
33
|
clean: ## Clean cache of uv and delete virtual environment
|
|
40
34
|
@$(UV) cache clean
|
|
41
35
|
@rm -rf .venv
|
|
42
36
|
|
|
43
|
-
lint:
|
|
37
|
+
lint: ## Checks the branch name and runs MegaLinter
|
|
44
38
|
@sh ./.linters/check_git_branch_name.sh
|
|
45
|
-
|
|
39
|
+
@$(MEGALINTER_RUN) $(MEGALINTER_IMAGE)
|
|
46
40
|
|
|
47
|
-
lint-fix: ##
|
|
48
|
-
|
|
41
|
+
lint-fix: ## Runs MegaLinter and applies its fixes
|
|
42
|
+
@$(MEGALINTER_RUN) -e APPLY_FIXES=all $(MEGALINTER_IMAGE)
|
|
49
43
|
|
|
50
44
|
test: ## Runs tests
|
|
51
45
|
@$(UV) run pytest
|
|
@@ -18,15 +18,15 @@ pr_prompt_file: "/home/thorsten/.config/cai/pr_prompt.md"
|
|
|
18
18
|
stats: true
|
|
19
19
|
signoff: true
|
|
20
20
|
anthropic:
|
|
21
|
-
model: claude-haiku-
|
|
22
|
-
temperature: 0
|
|
21
|
+
model: claude-haiku-5-5
|
|
23
22
|
max_tokens: 32768
|
|
24
23
|
deepseek:
|
|
25
|
-
model: deepseek-
|
|
24
|
+
model: deepseek-flash
|
|
26
25
|
temperature: 0
|
|
27
26
|
gemini:
|
|
28
|
-
model: gemini-3.
|
|
27
|
+
model: gemini-3.5-flash-lite
|
|
29
28
|
temperature: 0
|
|
29
|
+
timeout: 60
|
|
30
30
|
groq:
|
|
31
31
|
model: openai/gpt-oss-20b
|
|
32
32
|
temperature: 0
|
|
@@ -34,11 +34,11 @@ mistral:
|
|
|
34
34
|
model: codestral-2508
|
|
35
35
|
temperature: 0
|
|
36
36
|
ollama:
|
|
37
|
-
model:
|
|
37
|
+
model: gemma4:e2b
|
|
38
38
|
temperature: 0
|
|
39
39
|
timeout: 300
|
|
40
40
|
openai:
|
|
41
|
-
model: gpt-
|
|
41
|
+
model: gpt-6-luna
|
|
42
42
|
xai:
|
|
43
43
|
model: grok-4.3
|
|
44
44
|
temperature: 0
|
|
@@ -360,7 +360,7 @@ provider keys.
|
|
|
360
360
|
git cai -H language=de
|
|
361
361
|
git cai -H default=anthropic
|
|
362
362
|
git cai -H emoji=false
|
|
363
|
-
git cai -H groq.model=
|
|
363
|
+
git cai -H groq.model=openai/gpt-oss-120b
|
|
364
364
|
----
|
|
365
365
|
|
|
366
366
|
-h, --help::
|
|
@@ -455,7 +455,7 @@ identifier for that provider.
|
|
|
455
455
|
+
|
|
456
456
|
----
|
|
457
457
|
git cai -P openai -m gpt-4o
|
|
458
|
-
git cai -P anthropic -m claude-haiku-
|
|
458
|
+
git cai -P anthropic -m claude-haiku-5-5
|
|
459
459
|
git cai -P ollama -m mistral
|
|
460
460
|
----
|
|
461
461
|
|
|
@@ -528,7 +528,7 @@ both.
|
|
|
528
528
|
+
|
|
529
529
|
----
|
|
530
530
|
git cai -P anthropic
|
|
531
|
-
git cai -P groq -m
|
|
531
|
+
git cai -P groq -m openai/gpt-oss-120b
|
|
532
532
|
git cai -P openrouter
|
|
533
533
|
----
|
|
534
534
|
|
|
@@ -662,7 +662,7 @@ become integers or floats, everything else stays a string.
|
|
|
662
662
|
----
|
|
663
663
|
git cai -S default=anthropic
|
|
664
664
|
git cai -S emoji=false
|
|
665
|
-
git cai -S groq.model=
|
|
665
|
+
git cai -S groq.model=openai/gpt-oss-120b
|
|
666
666
|
git cai -S openai.temperature=0.7
|
|
667
667
|
git cai -S conventional=true
|
|
668
668
|
----
|
|
@@ -695,9 +695,10 @@ git cai -s a1b2c3d
|
|
|
695
695
|
|
|
696
696
|
-T, --timeout SECONDS::
|
|
697
697
|
Override the HTTP timeout for the LLM call in this invocation. The default
|
|
698
|
-
is 30 seconds for remote providers
|
|
699
|
-
|
|
700
|
-
|
|
698
|
+
is 30 seconds for remote providers, 60 seconds for Gemini (Flash Lite can queue
|
|
699
|
+
for up to 30 seconds) and 300 seconds for Ollama (since local generation can be
|
|
700
|
+
significantly slower). The override applies uniformly to whichever provider is
|
|
701
|
+
selected for this run, including those with their own `<provider>.timeout`.
|
|
701
702
|
+
|
|
702
703
|
To change the default permanently, set `timeout: <seconds>` in
|
|
703
704
|
`cai_config.yml` or the home config:
|
|
@@ -707,11 +708,12 @@ git cai -H timeout=60
|
|
|
707
708
|
git cai -S timeout=45
|
|
708
709
|
----
|
|
709
710
|
+
|
|
710
|
-
Ollama
|
|
711
|
-
|
|
711
|
+
Ollama and Gemini keep their own timeouts under `ollama.timeout` (default 300)
|
|
712
|
+
and `gemini.timeout` (default 60). Override them separately:
|
|
712
713
|
+
|
|
713
714
|
----
|
|
714
715
|
git cai -H ollama.timeout=600
|
|
716
|
+
git cai -H gemini.timeout=90
|
|
715
717
|
----
|
|
716
718
|
+
|
|
717
719
|
----
|
|
@@ -938,13 +940,13 @@ Example output:
|
|
|
938
940
|
----
|
|
939
941
|
Default models:
|
|
940
942
|
|
|
941
|
-
anthropic → claude-haiku-
|
|
942
|
-
deepseek → deepseek-
|
|
943
|
-
gemini → gemini-3.
|
|
943
|
+
anthropic → claude-haiku-5-5
|
|
944
|
+
deepseek → deepseek-flash
|
|
945
|
+
gemini → gemini-3.5-flash-lite
|
|
944
946
|
groq → openai/gpt-oss-20b
|
|
945
947
|
mistral → codestral-2508
|
|
946
|
-
ollama →
|
|
947
|
-
openai → gpt-
|
|
948
|
+
ollama → gemma4:e2b
|
|
949
|
+
openai → gpt-6-luna
|
|
948
950
|
xai → grok-4.3
|
|
949
951
|
----
|
|
950
952
|
|
|
@@ -1003,13 +1005,13 @@ Example output:
|
|
|
1003
1005
|
----
|
|
1004
1006
|
Supported providers:
|
|
1005
1007
|
|
|
1006
|
-
anthropic model: claude-haiku-
|
|
1007
|
-
deepseek model: deepseek-
|
|
1008
|
-
gemini model: gemini-3.
|
|
1008
|
+
anthropic model: claude-haiku-5-5 (token required)
|
|
1009
|
+
deepseek model: deepseek-flash (token required)
|
|
1010
|
+
gemini model: gemini-3.5-flash-lite (token required)
|
|
1009
1011
|
groq model: openai/gpt-oss-20b (token required)
|
|
1010
1012
|
mistral model: codestral-2508 (token required)
|
|
1011
|
-
ollama model:
|
|
1012
|
-
openai model: gpt-
|
|
1013
|
+
ollama model: gemma4:e2b (no token required)
|
|
1014
|
+
openai model: gpt-6-luna (token required)
|
|
1013
1015
|
xai model: grok-4.3 (token required)
|
|
1014
1016
|
|
|
1015
1017
|
Custom providers (OpenAI compatible, from config):
|
|
@@ -1097,9 +1099,11 @@ Available configuration keys:
|
|
|
1097
1099
|
- `full_files` -- send full working-tree contents of staged files alongside
|
|
1098
1100
|
the diff (`true`/`false`, default `false`)
|
|
1099
1101
|
- `max_diff_bytes` -- maximum size (in UTF-8 bytes) of the diff/commit-log
|
|
1100
|
-
sent to the LLM. When the input exceeds this,
|
|
1101
|
-
|
|
1102
|
-
|
|
1102
|
+
sent to the LLM. When the input exceeds this, the largest files are shortened
|
|
1103
|
+
first so every file stays represented, and marker lines tell the model what
|
|
1104
|
+
was cut. `0` (the default) means no limit. You rarely need this for very
|
|
1105
|
+
large staged changes: when a provider rejects the input as too large, git-cai
|
|
1106
|
+
shortens it the same way, warns, and retries up to two times.
|
|
1103
1107
|
- `pr_to_file` -- when running `--PR`, write the generated PR description
|
|
1104
1108
|
to a Markdown file in the repository root instead of printing it to
|
|
1105
1109
|
stdout (`true`/`false`, default `false`)
|
|
@@ -1141,7 +1145,8 @@ Available configuration keys:
|
|
|
1141
1145
|
`anthropic` the default is `32768` (`max_tokens` is still read as a legacy
|
|
1142
1146
|
alias). For OpenAI compatible providers it is only sent when set.
|
|
1143
1147
|
- `<provider>.timeout` -- HTTP timeout in seconds for one provider,
|
|
1144
|
-
overriding the global `timeout` (`ollama` defaults to `300`)
|
|
1148
|
+
overriding the global `timeout` (`ollama` defaults to `300`, `gemini` to `60`).
|
|
1149
|
+
The `--timeout` flag overrides both.
|
|
1145
1150
|
- `stats` -- opt in to local-only usage analytics (`true`/`false`,
|
|
1146
1151
|
default `false`). When enabled, every generation appends one row to
|
|
1147
1152
|
`~/.local/share/git-cai/stats.db` capturing kind (`commit`, `amend`,
|
|
@@ -1315,7 +1320,7 @@ Change a value in the repo config:
|
|
|
1315
1320
|
|
|
1316
1321
|
----
|
|
1317
1322
|
git cai -S default=anthropic
|
|
1318
|
-
git cai -S groq.model=
|
|
1323
|
+
git cai -S groq.model=openai/gpt-oss-120b
|
|
1319
1324
|
git cai -S emoji=false
|
|
1320
1325
|
----
|
|
1321
1326
|
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
.\" Title: git-cai
|
|
3
3
|
.\" Author: Thorsten Foltz
|
|
4
4
|
.\" Generator: Asciidoctor 2.0.26
|
|
5
|
-
.\" Date: 2026-
|
|
5
|
+
.\" Date: 2026-11-06
|
|
6
6
|
.\" Manual: \ \&
|
|
7
7
|
.\" Source: \ \&
|
|
8
8
|
.\" Language: English
|
|
9
9
|
.\"
|
|
10
|
-
.TH "GIT\-CAI" "1" "2026-
|
|
10
|
+
.TH "GIT\-CAI" "1" "2026-11-06" "\ \&" "\ \&"
|
|
11
11
|
.ie \n(.g .ds Aq \(aq
|
|
12
12
|
.el .ds Aq '
|
|
13
13
|
.ss \n[.ss] 0
|
|
@@ -656,7 +656,7 @@ provider keys.
|
|
|
656
656
|
git cai \-H language=de
|
|
657
657
|
git cai \-H default=anthropic
|
|
658
658
|
git cai \-H emoji=false
|
|
659
|
-
git cai \-H groq.model=
|
|
659
|
+
git cai \-H groq.model=openai/gpt\-oss\-120b
|
|
660
660
|
.fam
|
|
661
661
|
.fi
|
|
662
662
|
.if n .RE
|
|
@@ -789,7 +789,7 @@ identifier for that provider.
|
|
|
789
789
|
.nf
|
|
790
790
|
.fam C
|
|
791
791
|
git cai \-P openai \-m gpt\-4o
|
|
792
|
-
git cai \-P anthropic \-m claude\-haiku\-
|
|
792
|
+
git cai \-P anthropic \-m claude\-haiku\-5\-5
|
|
793
793
|
git cai \-P ollama \-m mistral
|
|
794
794
|
.fam
|
|
795
795
|
.fi
|
|
@@ -890,7 +890,7 @@ both.
|
|
|
890
890
|
.nf
|
|
891
891
|
.fam C
|
|
892
892
|
git cai \-P anthropic
|
|
893
|
-
git cai \-P groq \-m
|
|
893
|
+
git cai \-P groq \-m openai/gpt\-oss\-120b
|
|
894
894
|
git cai \-P openrouter
|
|
895
895
|
.fam
|
|
896
896
|
.fi
|
|
@@ -1066,7 +1066,7 @@ become integers or floats, everything else stays a string.
|
|
|
1066
1066
|
.fam C
|
|
1067
1067
|
git cai \-S default=anthropic
|
|
1068
1068
|
git cai \-S emoji=false
|
|
1069
|
-
git cai \-S groq.model=
|
|
1069
|
+
git cai \-S groq.model=openai/gpt\-oss\-120b
|
|
1070
1070
|
git cai \-S openai.temperature=0.7
|
|
1071
1071
|
git cai \-S conventional=true
|
|
1072
1072
|
.fam
|
|
@@ -1109,9 +1109,10 @@ git cai \-s a1b2c3d
|
|
|
1109
1109
|
\-T, \-\-timeout SECONDS
|
|
1110
1110
|
.RS 4
|
|
1111
1111
|
Override the HTTP timeout for the LLM call in this invocation. The default
|
|
1112
|
-
is 30 seconds for remote providers
|
|
1113
|
-
|
|
1114
|
-
|
|
1112
|
+
is 30 seconds for remote providers, 60 seconds for Gemini (Flash Lite can queue
|
|
1113
|
+
for up to 30 seconds) and 300 seconds for Ollama (since local generation can be
|
|
1114
|
+
significantly slower). The override applies uniformly to whichever provider is
|
|
1115
|
+
selected for this run, including those with their own \f(CR<provider>.timeout\fP.
|
|
1115
1116
|
.sp
|
|
1116
1117
|
To change the default permanently, set \f(CRtimeout: <seconds>\fP in
|
|
1117
1118
|
\f(CRcai_config.yml\fP or the home config:
|
|
@@ -1125,13 +1126,14 @@ git cai \-S timeout=45
|
|
|
1125
1126
|
.fi
|
|
1126
1127
|
.if n .RE
|
|
1127
1128
|
.sp
|
|
1128
|
-
Ollama
|
|
1129
|
-
|
|
1129
|
+
Ollama and Gemini keep their own timeouts under \f(CRollama.timeout\fP (default 300)
|
|
1130
|
+
and \f(CRgemini.timeout\fP (default 60). Override them separately:
|
|
1130
1131
|
.sp
|
|
1131
1132
|
.if n .RS 4
|
|
1132
1133
|
.nf
|
|
1133
1134
|
.fam C
|
|
1134
1135
|
git cai \-H ollama.timeout=600
|
|
1136
|
+
git cai \-H gemini.timeout=90
|
|
1135
1137
|
.fam
|
|
1136
1138
|
.fi
|
|
1137
1139
|
.if n .RE
|
|
@@ -1441,13 +1443,13 @@ Example output:
|
|
|
1441
1443
|
.fam C
|
|
1442
1444
|
Default models:
|
|
1443
1445
|
|
|
1444
|
-
anthropic\& → claude\-haiku\-
|
|
1445
|
-
deepseek\& → deepseek\-
|
|
1446
|
-
gemini\& → gemini\-3.
|
|
1446
|
+
anthropic\& → claude\-haiku\-5\-5
|
|
1447
|
+
deepseek\& → deepseek\-flash
|
|
1448
|
+
gemini\& → gemini\-3.5\-flash\-lite
|
|
1447
1449
|
groq\& → openai/gpt\-oss\-20b
|
|
1448
1450
|
mistral\& → codestral\-2508
|
|
1449
|
-
ollama\& →
|
|
1450
|
-
openai\& → gpt\-
|
|
1451
|
+
ollama\& → gemma4:e2b
|
|
1452
|
+
openai\& → gpt\-6\-luna
|
|
1451
1453
|
xai\& → grok\-4.3
|
|
1452
1454
|
.fam
|
|
1453
1455
|
.fi
|
|
@@ -1530,13 +1532,13 @@ Example output:
|
|
|
1530
1532
|
.fam C
|
|
1531
1533
|
Supported providers:
|
|
1532
1534
|
|
|
1533
|
-
anthropic\& model: claude\-haiku\-
|
|
1534
|
-
deepseek\& model: deepseek\-
|
|
1535
|
-
gemini\& model: gemini\-3.
|
|
1535
|
+
anthropic\& model: claude\-haiku\-5\-5\& (token required)
|
|
1536
|
+
deepseek\& model: deepseek\-flash\& (token required)
|
|
1537
|
+
gemini\& model: gemini\-3.5\-flash\-lite\& (token required)
|
|
1536
1538
|
groq\& model: openai/gpt\-oss\-20b\& (token required)
|
|
1537
1539
|
mistral\& model: codestral\-2508\& (token required)
|
|
1538
|
-
ollama\& model:
|
|
1539
|
-
openai\& model: gpt\-
|
|
1540
|
+
ollama\& model: gemma4:e2b\& (no token required)
|
|
1541
|
+
openai\& model: gpt\-6\-luna\& (token required)
|
|
1540
1542
|
xai\& model: grok\-4.3\& (token required)
|
|
1541
1543
|
|
|
1542
1544
|
Custom providers (OpenAI compatible, from config):
|
|
@@ -1827,9 +1829,11 @@ the diff (\f(CRtrue\fP/\f(CRfalse\fP, default \f(CRfalse\fP)
|
|
|
1827
1829
|
. IP \(bu 2.3
|
|
1828
1830
|
.\}
|
|
1829
1831
|
\f(CRmax_diff_bytes\fP \(em maximum size (in UTF\-8 bytes) of the diff/commit\-log
|
|
1830
|
-
sent to the LLM. When the input exceeds this,
|
|
1831
|
-
|
|
1832
|
-
|
|
1832
|
+
sent to the LLM. When the input exceeds this, the largest files are shortened
|
|
1833
|
+
first so every file stays represented, and marker lines tell the model what
|
|
1834
|
+
was cut. \f(CR0\fP (the default) means no limit. You rarely need this for very
|
|
1835
|
+
large staged changes: when a provider rejects the input as too large, git\-cai
|
|
1836
|
+
shortens it the same way, warns, and retries up to two times.
|
|
1833
1837
|
.RE
|
|
1834
1838
|
.sp
|
|
1835
1839
|
.RS 4
|
|
@@ -2021,7 +2025,8 @@ alias). For OpenAI compatible providers it is only sent when set.
|
|
|
2021
2025
|
. IP \(bu 2.3
|
|
2022
2026
|
.\}
|
|
2023
2027
|
\f(CR<provider>.timeout\fP \(em HTTP timeout in seconds for one provider,
|
|
2024
|
-
overriding the global \f(CRtimeout\fP (\f(CRollama\fP defaults to \f(CR300\fP)
|
|
2028
|
+
overriding the global \f(CRtimeout\fP (\f(CRollama\fP defaults to \f(CR300\fP, \f(CRgemini\fP to \f(CR60\fP).
|
|
2029
|
+
The \f(CR\-\-timeout\fP flag overrides both.
|
|
2025
2030
|
.RE
|
|
2026
2031
|
.sp
|
|
2027
2032
|
.RS 4
|
|
@@ -2310,7 +2315,7 @@ Change a value in the repo config:
|
|
|
2310
2315
|
.nf
|
|
2311
2316
|
.fam C
|
|
2312
2317
|
git cai \-S default=anthropic
|
|
2313
|
-
git cai \-S groq.model=
|
|
2318
|
+
git cai \-S groq.model=openai/gpt\-oss\-120b
|
|
2314
2319
|
git cai \-S emoji=false
|
|
2315
2320
|
.fam
|
|
2316
2321
|
.fi
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.17.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 17,
|
|
21
|
+
__version__ = version = '0.17.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 17, 4)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'gf30a44c67'
|
|
@@ -35,19 +35,26 @@ TOKENS_FILE = CONFIG_DIR / "tokens.yml"
|
|
|
35
35
|
|
|
36
36
|
DEFAULT_CONFIG: dict[str, Any] = {
|
|
37
37
|
"anthropic": {
|
|
38
|
-
"model": "claude-haiku-
|
|
39
|
-
"temperature": 0,
|
|
38
|
+
"model": "claude-haiku-5-5",
|
|
40
39
|
# ``max_tokens`` is still read as a legacy alias (see llm.py)
|
|
41
40
|
"max_output_tokens": 32768,
|
|
42
41
|
},
|
|
43
|
-
"openai": {"model": "gpt-
|
|
44
|
-
"deepseek": {"model": "deepseek-
|
|
45
|
-
"gemini": {
|
|
42
|
+
"openai": {"model": "gpt-6-luna"},
|
|
43
|
+
"deepseek": {"model": "deepseek-flash", "temperature": 0},
|
|
44
|
+
"gemini": {
|
|
45
|
+
"model": "gemini-3.5-flash-lite",
|
|
46
|
+
"temperature": 0,
|
|
47
|
+
# Flash Lite was measured taking up to 30 seconds server side, even for
|
|
48
|
+
# a single word reply, so the global 30 second timeout is too tight.
|
|
49
|
+
"timeout": 60,
|
|
50
|
+
},
|
|
46
51
|
"groq": {"model": "openai/gpt-oss-20b", "temperature": 0},
|
|
47
52
|
"xai": {"model": "grok-4.3", "temperature": 0},
|
|
53
|
+
# Not the cheapest on the list: ministral-3b wrapped messages in code
|
|
54
|
+
# fences and ministral-8b added a warning line, both unfit for a commit.
|
|
48
55
|
"mistral": {"model": "codestral-2508", "temperature": 0},
|
|
49
56
|
"ollama": {
|
|
50
|
-
"model": "
|
|
57
|
+
"model": "gemma4:e2b",
|
|
51
58
|
"temperature": 0,
|
|
52
59
|
"timeout": 300,
|
|
53
60
|
"startup_timeout": 8,
|
|
@@ -733,6 +740,11 @@ def apply_cli_overrides(
|
|
|
733
740
|
config["branch_context"] = branch_context
|
|
734
741
|
if timeout_override is not None:
|
|
735
742
|
config["timeout"] = timeout_override
|
|
743
|
+
# The flag outranks ``<provider>.timeout``, which is checked first when
|
|
744
|
+
# resolving. Rebuild instead of pop: blocks may be shared with DEFAULT_CONFIG.
|
|
745
|
+
for name, block in list(config.items()):
|
|
746
|
+
if isinstance(block, dict) and "timeout" in block:
|
|
747
|
+
config[name] = {k: v for k, v in block.items() if k != "timeout"}
|
|
736
748
|
if full_files_override is not None:
|
|
737
749
|
config["full_files"] = full_files_override
|
|
738
750
|
if sql_override is not None:
|
|
@@ -349,26 +349,55 @@ def git_diff_excluding(
|
|
|
349
349
|
return result.stdout
|
|
350
350
|
|
|
351
351
|
|
|
352
|
+
# Where an independently trimmable part of an LLM payload starts: one file's
|
|
353
|
+
# diff, or a block appended after the diff. A stray match inside file content
|
|
354
|
+
# only creates one more part, which is harmless.
|
|
355
|
+
_PAYLOAD_PART_RE = re.compile(
|
|
356
|
+
r"^(?=diff --git "
|
|
357
|
+
r"|--- (?:Full file contents|Additional context from the author) ---)",
|
|
358
|
+
re.MULTILINE,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def shorten_diff(diff: str, max_bytes: int) -> str:
|
|
363
|
+
"""Cut ``diff`` to about ``max_bytes`` UTF-8 bytes, largest parts first.
|
|
364
|
+
|
|
365
|
+
Parts below their fair share stay whole and the others split what is left,
|
|
366
|
+
so every file keeps its header and the start of its changes. Text without
|
|
367
|
+
file headers (a commit log) is one part, which makes this a plain tail cut.
|
|
368
|
+
Each cut adds a short marker, so the result can slightly exceed the limit.
|
|
369
|
+
"""
|
|
370
|
+
parts = [part.encode("utf-8") for part in _PAYLOAD_PART_RE.split(diff) if part]
|
|
371
|
+
remaining = max_bytes
|
|
372
|
+
shares = [0] * len(parts)
|
|
373
|
+
by_size = sorted(range(len(parts)), key=lambda i: len(parts[i]))
|
|
374
|
+
for position, index in enumerate(by_size):
|
|
375
|
+
shares[index] = min(len(parts[index]), remaining // (len(parts) - position))
|
|
376
|
+
remaining -= shares[index]
|
|
377
|
+
|
|
378
|
+
shortened = []
|
|
379
|
+
for part, share in zip(parts, shares):
|
|
380
|
+
# errors="ignore" drops a multibyte character split by the byte slice.
|
|
381
|
+
text = part[:share].decode("utf-8", errors="ignore")
|
|
382
|
+
if share < len(part):
|
|
383
|
+
text += f"\n[... {len(part) - share} bytes omitted ...]\n"
|
|
384
|
+
shortened.append(text)
|
|
385
|
+
return "".join(shortened)
|
|
386
|
+
|
|
387
|
+
|
|
352
388
|
def truncate_diff(diff: str, max_bytes: int) -> tuple[str, bool]:
|
|
353
|
-
"""Truncate ``diff`` to
|
|
389
|
+
"""Truncate ``diff`` to about ``max_bytes`` UTF-8 bytes.
|
|
354
390
|
|
|
355
391
|
A ``max_bytes`` of 0 (or negative) means "no limit" and the diff is
|
|
356
392
|
returned unchanged — preserving the default behavior. When truncation
|
|
357
393
|
occurs, a marker line is appended so both the LLM and the user know the
|
|
358
394
|
input was cut. Returns ``(possibly_truncated_diff, was_truncated)``.
|
|
359
395
|
"""
|
|
360
|
-
if max_bytes <= 0:
|
|
361
|
-
return diff, False
|
|
362
|
-
|
|
363
|
-
encoded = diff.encode("utf-8")
|
|
364
|
-
if len(encoded) <= max_bytes:
|
|
396
|
+
if max_bytes <= 0 or len(diff.encode("utf-8")) <= max_bytes:
|
|
365
397
|
return diff, False
|
|
366
398
|
|
|
367
|
-
# Cut on a UTF-8 safe boundary: decoding with errors="ignore" drops any
|
|
368
|
-
# partial trailing multibyte sequence left by the byte-level slice.
|
|
369
|
-
truncated = encoded[:max_bytes].decode("utf-8", errors="ignore")
|
|
370
399
|
marker = f"\n\n[... diff truncated: exceeded max_diff_bytes={max_bytes} ...]"
|
|
371
|
-
return
|
|
400
|
+
return shorten_diff(diff, max_bytes) + marker, True
|
|
372
401
|
|
|
373
402
|
|
|
374
403
|
def apply_diff_limit(text: str, config: dict, *, label: str = "Input") -> str:
|
|
@@ -18,7 +18,11 @@ from urllib.parse import urlparse
|
|
|
18
18
|
|
|
19
19
|
import requests
|
|
20
20
|
from git_cai_cli.core.config import CONFIG_DIR, is_custom_provider
|
|
21
|
-
from git_cai_cli.core.gitutils import
|
|
21
|
+
from git_cai_cli.core.gitutils import (
|
|
22
|
+
classify_changed_paths,
|
|
23
|
+
paths_from_diff,
|
|
24
|
+
shorten_diff,
|
|
25
|
+
)
|
|
22
26
|
from git_cai_cli.core.languages import LANGUAGE_MAP
|
|
23
27
|
from git_cai_cli.core.prompts_fallback import (
|
|
24
28
|
HARDCODED_CHANGELOG_PROMPT,
|
|
@@ -30,6 +34,7 @@ from git_cai_cli.core.prompts_fallback import (
|
|
|
30
34
|
HARDCODED_SPLIT_PROMPT,
|
|
31
35
|
HARDCODED_SQUASH_PROMPT,
|
|
32
36
|
)
|
|
37
|
+
from git_cai_cli.core.validate import _extract_api_error_message
|
|
33
38
|
from requests.adapters import HTTPAdapter
|
|
34
39
|
from urllib3.util.retry import Retry
|
|
35
40
|
|
|
@@ -41,10 +46,11 @@ log = logging.getLogger(__name__)
|
|
|
41
46
|
# still handled — ``_http_post`` retries once without the field — so add
|
|
42
47
|
# an entry only when a warning shows up in practice.
|
|
43
48
|
_TEMPERATURE_UNSUPPORTED_PATTERNS = [
|
|
44
|
-
re.compile(r"^gpt-5", re.IGNORECASE), # OpenAI GPT-5
|
|
49
|
+
re.compile(r"^gpt-[5-9]", re.IGNORECASE), # OpenAI GPT-5+ (incl. mini/nano/luna)
|
|
45
50
|
re.compile(r"^o[134](-|$)", re.IGNORECASE), # OpenAI o1/o3/o4 reasoning series
|
|
46
51
|
re.compile(r"opus-4-[7-9]", re.IGNORECASE), # Anthropic Opus 4.7 / 4.8 / 4.9
|
|
47
52
|
re.compile(r"opus-[5-9]", re.IGNORECASE), # Anthropic Opus 5+
|
|
53
|
+
re.compile(r"haiku-[5-9]", re.IGNORECASE), # Anthropic Haiku 5+
|
|
48
54
|
]
|
|
49
55
|
|
|
50
56
|
|
|
@@ -178,6 +184,37 @@ def _http_post(*args, **kwargs):
|
|
|
178
184
|
return response
|
|
179
185
|
|
|
180
186
|
|
|
187
|
+
# Model limits change too often for a table, and the provider's rejection
|
|
188
|
+
# names them, so an oversized request is shortened and retried instead.
|
|
189
|
+
_OVERFLOW_STATUS_CODES = (400, 413, 422)
|
|
190
|
+
_OVERFLOW_RE = re.compile(
|
|
191
|
+
r"too (?:long|large)|exceed|maximum (?:context|prompt)|reduce the length",
|
|
192
|
+
re.IGNORECASE,
|
|
193
|
+
)
|
|
194
|
+
# Skips numbers glued to a name or version, like a date in a model name.
|
|
195
|
+
_OVERFLOW_SIZE_RE = re.compile(r"(?<![\w.-])\d[\d,]{2,}\b")
|
|
196
|
+
_OVERFLOW_RETRIES = 2
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _overflow_shrink_factor(exc: requests.HTTPError) -> float | None:
|
|
200
|
+
"""Return how much to shrink the input after a "too many tokens" rejection.
|
|
201
|
+
|
|
202
|
+
``None`` means ``exc`` is some other error. Providers word the rejection
|
|
203
|
+
differently, but the first two numbers are the limit and the actual size,
|
|
204
|
+
and the smaller one is the limit. Without them, halve the input.
|
|
205
|
+
"""
|
|
206
|
+
response = exc.response
|
|
207
|
+
if response is None or response.status_code not in _OVERFLOW_STATUS_CODES:
|
|
208
|
+
return None
|
|
209
|
+
message = _extract_api_error_message(response)
|
|
210
|
+
if not _OVERFLOW_RE.search(message):
|
|
211
|
+
return None
|
|
212
|
+
sizes = [int(n.replace(",", "")) for n in _OVERFLOW_SIZE_RE.findall(message)[:2]]
|
|
213
|
+
if len(sizes) < 2 or sizes[0] == sizes[1]:
|
|
214
|
+
return 0.5
|
|
215
|
+
return 0.9 * min(sizes) / max(sizes)
|
|
216
|
+
|
|
217
|
+
|
|
181
218
|
def load_prompt_file(
|
|
182
219
|
config_key: str,
|
|
183
220
|
config: Dict[str, Any],
|
|
@@ -967,7 +1004,8 @@ class CommitMessageGenerator:
|
|
|
967
1004
|
def _dispatch_generate(self, content: str, system_prompt: str) -> str:
|
|
968
1005
|
"""
|
|
969
1006
|
Route to the right provider. ``system_prompt`` comes from the
|
|
970
|
-
matching ``_build_*_prompt``; ``content`` is the git output.
|
|
1007
|
+
matching ``_build_*_prompt``; ``content`` is the git output. Input the
|
|
1008
|
+
provider rejects as too large is shortened and retried.
|
|
971
1009
|
"""
|
|
972
1010
|
self._scan_for_secrets(content)
|
|
973
1011
|
|
|
@@ -986,12 +1024,30 @@ class CommitMessageGenerator:
|
|
|
986
1024
|
|
|
987
1025
|
log.debug("Using provider '%s' for generation.", provider)
|
|
988
1026
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1027
|
+
def call_provider(text: str) -> str:
|
|
1028
|
+
if openai_compatible:
|
|
1029
|
+
return self.generate_openai_compatible(
|
|
1030
|
+
text, provider, system_prompt_override=system_prompt
|
|
1031
|
+
)
|
|
1032
|
+
return model_dispatch[provider](text, system_prompt_override=system_prompt)
|
|
993
1033
|
|
|
994
|
-
|
|
1034
|
+
sent = content
|
|
1035
|
+
for _ in range(_OVERFLOW_RETRIES):
|
|
1036
|
+
try:
|
|
1037
|
+
return call_provider(sent)
|
|
1038
|
+
except requests.HTTPError as exc:
|
|
1039
|
+
factor = _overflow_shrink_factor(exc)
|
|
1040
|
+
if factor is None:
|
|
1041
|
+
raise
|
|
1042
|
+
target = int(len(sent.encode("utf-8")) * factor)
|
|
1043
|
+
log.warning(
|
|
1044
|
+
"The input is too large for this model. Retrying with the largest "
|
|
1045
|
+
"files shortened to about %d bytes in total, so the result may "
|
|
1046
|
+
"miss details from the cut parts.",
|
|
1047
|
+
target,
|
|
1048
|
+
)
|
|
1049
|
+
sent = shorten_diff(content, target)
|
|
1050
|
+
return call_provider(sent)
|
|
995
1051
|
|
|
996
1052
|
# ---------------------------
|
|
997
1053
|
# MODEL CALLS
|