git-copilot-commit 0.6.1__py3-none-any.whl → 0.7.0__py3-none-any.whl
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_copilot_commit/cli.py +72 -6
- git_copilot_commit/llms/copilot.py +15 -1
- git_copilot_commit/llms/core.py +194 -16
- git_copilot_commit/llms/openai_api.py +67 -7
- git_copilot_commit/llms/providers.py +10 -9
- {git_copilot_commit-0.6.1.dist-info → git_copilot_commit-0.7.0.dist-info}/METADATA +85 -50
- {git_copilot_commit-0.6.1.dist-info → git_copilot_commit-0.7.0.dist-info}/RECORD +10 -10
- {git_copilot_commit-0.6.1.dist-info → git_copilot_commit-0.7.0.dist-info}/WHEEL +0 -0
- {git_copilot_commit-0.6.1.dist-info → git_copilot_commit-0.7.0.dist-info}/entry_points.txt +0 -0
- {git_copilot_commit-0.6.1.dist-info → git_copilot_commit-0.7.0.dist-info}/licenses/LICENSE +0 -0
git_copilot_commit/cli.py
CHANGED
|
@@ -81,8 +81,8 @@ BaseUrlOption = Annotated[
|
|
|
81
81
|
cyclopts.Parameter(
|
|
82
82
|
name="--base-url",
|
|
83
83
|
help=(
|
|
84
|
-
"
|
|
85
|
-
"http://127.0.0.1:11434/v1."
|
|
84
|
+
"Endpoint URL for an OpenAI-compatible provider, for example "
|
|
85
|
+
"http://127.0.0.1:11434/v1/chat/completions."
|
|
86
86
|
),
|
|
87
87
|
),
|
|
88
88
|
]
|
|
@@ -371,6 +371,8 @@ def ask_llm_with_system_prompt(
|
|
|
371
371
|
model: str | None = None,
|
|
372
372
|
provider_config: providers.ProviderConfig | None = None,
|
|
373
373
|
http_client_config: llm.HttpClientConfig | None = None,
|
|
374
|
+
disable_thinking: bool = True,
|
|
375
|
+
max_tokens: int = 1024,
|
|
374
376
|
) -> str:
|
|
375
377
|
"""Send a prepared prompt to the selected LLM provider."""
|
|
376
378
|
return providers.ask(
|
|
@@ -386,6 +388,8 @@ def ask_llm_with_system_prompt(
|
|
|
386
388
|
provider_config=provider_config,
|
|
387
389
|
model=normalize_model_name(model),
|
|
388
390
|
http_client_config=http_client_config,
|
|
391
|
+
disable_thinking=disable_thinking,
|
|
392
|
+
max_tokens=max_tokens,
|
|
389
393
|
)
|
|
390
394
|
|
|
391
395
|
|
|
@@ -394,6 +398,8 @@ def generate_commit_message_for_prompt(
|
|
|
394
398
|
model: str | None = None,
|
|
395
399
|
provider_config: providers.ProviderConfig | None = None,
|
|
396
400
|
http_client_config: llm.HttpClientConfig | None = None,
|
|
401
|
+
disable_thinking: bool = True,
|
|
402
|
+
max_tokens: int = 1024,
|
|
397
403
|
) -> str:
|
|
398
404
|
"""Generate a conventional commit message from a prepared prompt."""
|
|
399
405
|
return ask_llm_with_system_prompt(
|
|
@@ -402,6 +408,8 @@ def generate_commit_message_for_prompt(
|
|
|
402
408
|
model=model,
|
|
403
409
|
provider_config=provider_config,
|
|
404
410
|
http_client_config=http_client_config,
|
|
411
|
+
disable_thinking=disable_thinking,
|
|
412
|
+
max_tokens=max_tokens,
|
|
405
413
|
)
|
|
406
414
|
|
|
407
415
|
|
|
@@ -435,6 +443,8 @@ def generate_commit_message_for_status(
|
|
|
435
443
|
context: str = "",
|
|
436
444
|
provider_config: providers.ProviderConfig | None = None,
|
|
437
445
|
http_client_config: llm.HttpClientConfig | None = None,
|
|
446
|
+
disable_thinking: bool = True,
|
|
447
|
+
max_tokens: int = 1024,
|
|
438
448
|
) -> str:
|
|
439
449
|
"""Generate a commit message for a staged status snapshot."""
|
|
440
450
|
full_prompt = build_commit_message_prompt(status, context=context)
|
|
@@ -444,6 +454,8 @@ def generate_commit_message_for_status(
|
|
|
444
454
|
model=model,
|
|
445
455
|
provider_config=provider_config,
|
|
446
456
|
http_client_config=http_client_config,
|
|
457
|
+
disable_thinking=disable_thinking,
|
|
458
|
+
max_tokens=max_tokens,
|
|
447
459
|
)
|
|
448
460
|
except llm.LLMError as exc:
|
|
449
461
|
if not should_retry_with_compact_prompt(exc):
|
|
@@ -462,6 +474,8 @@ def generate_commit_message_for_status(
|
|
|
462
474
|
model=model,
|
|
463
475
|
provider_config=provider_config,
|
|
464
476
|
http_client_config=http_client_config,
|
|
477
|
+
disable_thinking=disable_thinking,
|
|
478
|
+
max_tokens=max_tokens,
|
|
465
479
|
)
|
|
466
480
|
|
|
467
481
|
|
|
@@ -549,6 +563,8 @@ def request_commit_message(
|
|
|
549
563
|
context: str = "",
|
|
550
564
|
provider_config: providers.ProviderConfig | None = None,
|
|
551
565
|
http_client_config: llm.HttpClientConfig | None = None,
|
|
566
|
+
disable_thinking: bool = True,
|
|
567
|
+
max_tokens: int = 1024,
|
|
552
568
|
) -> str:
|
|
553
569
|
"""Request a commit message for the provided staged state."""
|
|
554
570
|
try:
|
|
@@ -561,6 +577,8 @@ def request_commit_message(
|
|
|
561
577
|
context=context,
|
|
562
578
|
provider_config=provider_config,
|
|
563
579
|
http_client_config=http_client_config,
|
|
580
|
+
disable_thinking=disable_thinking,
|
|
581
|
+
max_tokens=max_tokens,
|
|
564
582
|
)
|
|
565
583
|
except llm.LLMError as exc:
|
|
566
584
|
print_llm_error("Could not generate a commit message", exc)
|
|
@@ -576,6 +594,8 @@ def request_split_commit_plan(
|
|
|
576
594
|
context: str = "",
|
|
577
595
|
provider_config: providers.ProviderConfig | None = None,
|
|
578
596
|
http_client_config: llm.HttpClientConfig | None = None,
|
|
597
|
+
disable_thinking: bool = True,
|
|
598
|
+
max_tokens: int = 1024,
|
|
579
599
|
) -> SplitCommitPlan:
|
|
580
600
|
"""Request and validate a split-commit plan for the staged patch units."""
|
|
581
601
|
planner_system_prompt = load_named_prompt(SPLIT_COMMIT_PLANNER_PROMPT_FILENAME)
|
|
@@ -596,6 +616,8 @@ def request_split_commit_plan(
|
|
|
596
616
|
model=model,
|
|
597
617
|
provider_config=provider_config,
|
|
598
618
|
http_client_config=http_client_config,
|
|
619
|
+
disable_thinking=disable_thinking,
|
|
620
|
+
max_tokens=max_tokens,
|
|
599
621
|
)
|
|
600
622
|
except llm.LLMError as exc:
|
|
601
623
|
if not should_retry_with_compact_prompt(exc):
|
|
@@ -629,6 +651,8 @@ def request_split_commit_plan(
|
|
|
629
651
|
model=model,
|
|
630
652
|
provider_config=provider_config,
|
|
631
653
|
http_client_config=http_client_config,
|
|
654
|
+
disable_thinking=disable_thinking,
|
|
655
|
+
max_tokens=max_tokens,
|
|
632
656
|
)
|
|
633
657
|
except llm.LLMError as exc:
|
|
634
658
|
print_llm_error("Could not generate a split commit plan", exc)
|
|
@@ -648,6 +672,8 @@ def request_split_commit_messages(
|
|
|
648
672
|
context: str = "",
|
|
649
673
|
provider_config: providers.ProviderConfig | None = None,
|
|
650
674
|
http_client_config: llm.HttpClientConfig | None = None,
|
|
675
|
+
disable_thinking: bool = True,
|
|
676
|
+
max_tokens: int = 1024,
|
|
651
677
|
) -> list[PreparedSplitCommit]:
|
|
652
678
|
"""Generate commit messages for each planned split-commit group."""
|
|
653
679
|
try:
|
|
@@ -665,6 +691,8 @@ def request_split_commit_messages(
|
|
|
665
691
|
context=context,
|
|
666
692
|
provider_config=provider_config,
|
|
667
693
|
http_client_config=http_client_config,
|
|
694
|
+
disable_thinking=disable_thinking,
|
|
695
|
+
max_tokens=max_tokens,
|
|
668
696
|
)
|
|
669
697
|
|
|
670
698
|
prepared_commits.append(
|
|
@@ -858,6 +886,8 @@ def handle_single_commit_flow(
|
|
|
858
886
|
context: str = "",
|
|
859
887
|
provider_config: providers.ProviderConfig | None = None,
|
|
860
888
|
http_client_config: llm.HttpClientConfig | None = None,
|
|
889
|
+
disable_thinking: bool = True,
|
|
890
|
+
max_tokens: int = 1024,
|
|
861
891
|
) -> None:
|
|
862
892
|
"""Generate, display, and execute the single-commit flow."""
|
|
863
893
|
commit_message = request_commit_message(
|
|
@@ -866,6 +896,8 @@ def handle_single_commit_flow(
|
|
|
866
896
|
context=context,
|
|
867
897
|
provider_config=provider_config,
|
|
868
898
|
http_client_config=http_client_config,
|
|
899
|
+
disable_thinking=disable_thinking,
|
|
900
|
+
max_tokens=max_tokens,
|
|
869
901
|
)
|
|
870
902
|
display_commit_message(commit_message)
|
|
871
903
|
|
|
@@ -883,6 +915,8 @@ def handle_split_commit_flow(
|
|
|
883
915
|
context: str = "",
|
|
884
916
|
provider_config: providers.ProviderConfig | None = None,
|
|
885
917
|
http_client_config: llm.HttpClientConfig | None = None,
|
|
918
|
+
disable_thinking: bool = True,
|
|
919
|
+
max_tokens: int = 1024,
|
|
886
920
|
) -> None:
|
|
887
921
|
"""Generate, display, and execute the split-commit flow."""
|
|
888
922
|
patch_units = tuple(
|
|
@@ -901,6 +935,8 @@ def handle_split_commit_flow(
|
|
|
901
935
|
context=context,
|
|
902
936
|
provider_config=provider_config,
|
|
903
937
|
http_client_config=http_client_config,
|
|
938
|
+
disable_thinking=disable_thinking,
|
|
939
|
+
max_tokens=max_tokens,
|
|
904
940
|
)
|
|
905
941
|
return
|
|
906
942
|
|
|
@@ -916,6 +952,8 @@ def handle_split_commit_flow(
|
|
|
916
952
|
context=context,
|
|
917
953
|
provider_config=provider_config,
|
|
918
954
|
http_client_config=http_client_config,
|
|
955
|
+
disable_thinking=disable_thinking,
|
|
956
|
+
max_tokens=max_tokens,
|
|
919
957
|
)
|
|
920
958
|
return
|
|
921
959
|
|
|
@@ -938,6 +976,8 @@ def handle_split_commit_flow(
|
|
|
938
976
|
context=context,
|
|
939
977
|
provider_config=provider_config,
|
|
940
978
|
http_client_config=http_client_config,
|
|
979
|
+
disable_thinking=disable_thinking,
|
|
980
|
+
max_tokens=max_tokens,
|
|
941
981
|
)
|
|
942
982
|
except SplitPlanningError as exc:
|
|
943
983
|
console.print(
|
|
@@ -952,6 +992,8 @@ def handle_split_commit_flow(
|
|
|
952
992
|
context=context,
|
|
953
993
|
provider_config=provider_config,
|
|
954
994
|
http_client_config=http_client_config,
|
|
995
|
+
disable_thinking=disable_thinking,
|
|
996
|
+
max_tokens=max_tokens,
|
|
955
997
|
)
|
|
956
998
|
return
|
|
957
999
|
|
|
@@ -969,6 +1011,8 @@ def handle_split_commit_flow(
|
|
|
969
1011
|
context=context,
|
|
970
1012
|
provider_config=provider_config,
|
|
971
1013
|
http_client_config=http_client_config,
|
|
1014
|
+
disable_thinking=disable_thinking,
|
|
1015
|
+
max_tokens=max_tokens,
|
|
972
1016
|
)
|
|
973
1017
|
prepared_commits = order_prepared_split_commits(prepared_commits)
|
|
974
1018
|
|
|
@@ -1008,7 +1052,7 @@ def authenticate(
|
|
|
1008
1052
|
] = False,
|
|
1009
1053
|
ca_bundle: CaBundleOption = None,
|
|
1010
1054
|
insecure: InsecureOption = False,
|
|
1011
|
-
native_tls: NativeTlsOption =
|
|
1055
|
+
native_tls: NativeTlsOption = True,
|
|
1012
1056
|
):
|
|
1013
1057
|
"""Authenticate with GitHub Copilot and cache credentials locally."""
|
|
1014
1058
|
print_cli_banner()
|
|
@@ -1035,7 +1079,7 @@ def summary(
|
|
|
1035
1079
|
api_key: ApiKeyOption = None,
|
|
1036
1080
|
ca_bundle: CaBundleOption = None,
|
|
1037
1081
|
insecure: InsecureOption = False,
|
|
1038
|
-
native_tls: NativeTlsOption =
|
|
1082
|
+
native_tls: NativeTlsOption = True,
|
|
1039
1083
|
):
|
|
1040
1084
|
"""Show the configured LLM provider summary."""
|
|
1041
1085
|
print_cli_banner()
|
|
@@ -1073,7 +1117,7 @@ def models_command(
|
|
|
1073
1117
|
] = None,
|
|
1074
1118
|
ca_bundle: CaBundleOption = None,
|
|
1075
1119
|
insecure: InsecureOption = False,
|
|
1076
|
-
native_tls: NativeTlsOption =
|
|
1120
|
+
native_tls: NativeTlsOption = True,
|
|
1077
1121
|
):
|
|
1078
1122
|
"""List available models for the configured LLM provider."""
|
|
1079
1123
|
print_cli_banner()
|
|
@@ -1139,12 +1183,30 @@ def commit(
|
|
|
1139
1183
|
help="Optional user-provided context to guide commit message",
|
|
1140
1184
|
),
|
|
1141
1185
|
] = "",
|
|
1186
|
+
disable_thinking: Annotated[
|
|
1187
|
+
bool,
|
|
1188
|
+
cyclopts.Parameter(
|
|
1189
|
+
name="--disable-thinking",
|
|
1190
|
+
negative="--enable-thinking",
|
|
1191
|
+
help=(
|
|
1192
|
+
"Disable or minimize reasoning/thinking tokens for commit-message requests."
|
|
1193
|
+
),
|
|
1194
|
+
),
|
|
1195
|
+
] = True,
|
|
1196
|
+
max_tokens: Annotated[
|
|
1197
|
+
int,
|
|
1198
|
+
cyclopts.Parameter(
|
|
1199
|
+
name="--max-tokens",
|
|
1200
|
+
help=("Maximum output tokens for LLM generation."),
|
|
1201
|
+
validator=cyclopts.validators.Number(gte=1),
|
|
1202
|
+
),
|
|
1203
|
+
] = 1024,
|
|
1142
1204
|
provider: ProviderOption = None,
|
|
1143
1205
|
base_url: BaseUrlOption = None,
|
|
1144
1206
|
api_key: ApiKeyOption = None,
|
|
1145
1207
|
ca_bundle: CaBundleOption = None,
|
|
1146
1208
|
insecure: InsecureOption = False,
|
|
1147
|
-
native_tls: NativeTlsOption =
|
|
1209
|
+
native_tls: NativeTlsOption = True,
|
|
1148
1210
|
):
|
|
1149
1211
|
"""
|
|
1150
1212
|
Generate commit message based on changes in the current git repository and commit them.
|
|
@@ -1212,6 +1274,8 @@ def commit(
|
|
|
1212
1274
|
context=context,
|
|
1213
1275
|
provider_config=provider_config,
|
|
1214
1276
|
http_client_config=http_client_config,
|
|
1277
|
+
disable_thinking=disable_thinking,
|
|
1278
|
+
max_tokens=max_tokens,
|
|
1215
1279
|
)
|
|
1216
1280
|
return
|
|
1217
1281
|
|
|
@@ -1223,6 +1287,8 @@ def commit(
|
|
|
1223
1287
|
context=context,
|
|
1224
1288
|
provider_config=provider_config,
|
|
1225
1289
|
http_client_config=http_client_config,
|
|
1290
|
+
disable_thinking=disable_thinking,
|
|
1291
|
+
max_tokens=max_tokens,
|
|
1226
1292
|
)
|
|
1227
1293
|
|
|
1228
1294
|
|
|
@@ -500,7 +500,13 @@ def list_models(client, credentials: CopilotCredentials) -> list[Model]:
|
|
|
500
500
|
|
|
501
501
|
|
|
502
502
|
def complete_text_prompt(
|
|
503
|
-
client,
|
|
503
|
+
client,
|
|
504
|
+
credentials: CopilotCredentials,
|
|
505
|
+
*,
|
|
506
|
+
model: Model,
|
|
507
|
+
prompt: str,
|
|
508
|
+
disable_thinking: bool = False,
|
|
509
|
+
max_tokens: int | None = None,
|
|
504
510
|
) -> str:
|
|
505
511
|
api_surface = llm.infer_api_surface(model)
|
|
506
512
|
if api_surface == "chat_completions":
|
|
@@ -512,6 +518,8 @@ def complete_text_prompt(
|
|
|
512
518
|
),
|
|
513
519
|
model_id=model.id,
|
|
514
520
|
prompt=prompt,
|
|
521
|
+
disable_thinking=disable_thinking,
|
|
522
|
+
max_tokens=max_tokens,
|
|
515
523
|
)
|
|
516
524
|
if api_surface == "responses":
|
|
517
525
|
return llm.responses_completion_request(
|
|
@@ -524,6 +532,8 @@ def complete_text_prompt(
|
|
|
524
532
|
),
|
|
525
533
|
model_id=model.id,
|
|
526
534
|
prompt=prompt,
|
|
535
|
+
disable_thinking=disable_thinking,
|
|
536
|
+
max_tokens=max_tokens,
|
|
527
537
|
)
|
|
528
538
|
|
|
529
539
|
raise LLMError(
|
|
@@ -778,6 +788,8 @@ def ask(
|
|
|
778
788
|
default_model: str | None = None,
|
|
779
789
|
configured_default_model_path: Path | None = None,
|
|
780
790
|
http_client_config: HttpClientConfig | None = None,
|
|
791
|
+
disable_thinking: bool = False,
|
|
792
|
+
max_tokens: int | None = None,
|
|
781
793
|
) -> str:
|
|
782
794
|
def run(client) -> str:
|
|
783
795
|
credentials = ensure_fresh_credentials(client)
|
|
@@ -794,6 +806,8 @@ def ask(
|
|
|
794
806
|
credentials,
|
|
795
807
|
model=selected_model,
|
|
796
808
|
prompt=prompt,
|
|
809
|
+
disable_thinking=disable_thinking,
|
|
810
|
+
max_tokens=max_tokens,
|
|
797
811
|
)
|
|
798
812
|
|
|
799
813
|
return _with_reauthentication(run, http_client_config=http_client_config)
|
git_copilot_commit/llms/core.py
CHANGED
|
@@ -142,7 +142,7 @@ class Model:
|
|
|
142
142
|
|
|
143
143
|
@dataclass(frozen=True, slots=True)
|
|
144
144
|
class HttpClientConfig:
|
|
145
|
-
native_tls: bool =
|
|
145
|
+
native_tls: bool = True
|
|
146
146
|
insecure: bool = False
|
|
147
147
|
ca_bundle: str | None = None
|
|
148
148
|
|
|
@@ -441,6 +441,80 @@ def filter_models_by_vendor(
|
|
|
441
441
|
return filtered
|
|
442
442
|
|
|
443
443
|
|
|
444
|
+
def _model_id_matches(model_id: str, keywords: tuple[str, ...]) -> bool:
|
|
445
|
+
normalized = model_id.lower()
|
|
446
|
+
return any(keyword in normalized for keyword in keywords)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def _is_openai_reasoning_model(model_id: str) -> bool:
|
|
450
|
+
normalized = model_id.lower()
|
|
451
|
+
return normalized.startswith(("o1", "o3", "o4")) or "/o" in normalized
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _uses_chat_template_thinking_controls(model_id: str) -> bool:
|
|
455
|
+
return _model_id_matches(
|
|
456
|
+
model_id,
|
|
457
|
+
(
|
|
458
|
+
"qwen",
|
|
459
|
+
"deepseek",
|
|
460
|
+
"granite",
|
|
461
|
+
"glm",
|
|
462
|
+
"hunyuan",
|
|
463
|
+
"magistral",
|
|
464
|
+
"mistral",
|
|
465
|
+
"nemotron",
|
|
466
|
+
"seed",
|
|
467
|
+
"step",
|
|
468
|
+
),
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def disable_thinking_options(
|
|
473
|
+
*,
|
|
474
|
+
model_id: str,
|
|
475
|
+
api_surface: str,
|
|
476
|
+
) -> dict[str, Any]:
|
|
477
|
+
normalized = model_id.lower()
|
|
478
|
+
|
|
479
|
+
if api_surface == "responses":
|
|
480
|
+
if "codex" in normalized:
|
|
481
|
+
return {"reasoning": {"effort": "none"}}
|
|
482
|
+
if "gpt-5" in normalized:
|
|
483
|
+
return {"reasoning": {"effort": "minimal"}}
|
|
484
|
+
if "gpt-oss" in normalized or _is_openai_reasoning_model(model_id):
|
|
485
|
+
return {"reasoning": {"effort": "low"}}
|
|
486
|
+
if _uses_chat_template_thinking_controls(model_id):
|
|
487
|
+
return {
|
|
488
|
+
"reasoning_effort": "none",
|
|
489
|
+
"chat_template_kwargs": {
|
|
490
|
+
"enable_thinking": False,
|
|
491
|
+
"thinking": False,
|
|
492
|
+
},
|
|
493
|
+
}
|
|
494
|
+
return {}
|
|
495
|
+
|
|
496
|
+
if "gemini" in normalized:
|
|
497
|
+
return {"reasoning_effort": "none"}
|
|
498
|
+
if "codex" in normalized:
|
|
499
|
+
return {"reasoning_effort": "none"}
|
|
500
|
+
if "gpt-5" in normalized:
|
|
501
|
+
return {"reasoning_effort": "minimal"}
|
|
502
|
+
if "gpt-oss" in normalized or _is_openai_reasoning_model(model_id):
|
|
503
|
+
return {"reasoning_effort": "low"}
|
|
504
|
+
if "claude" in normalized or "anthropic" in normalized:
|
|
505
|
+
return {"thinking": {"type": "disabled"}}
|
|
506
|
+
if _uses_chat_template_thinking_controls(model_id):
|
|
507
|
+
return {
|
|
508
|
+
"reasoning_effort": "none",
|
|
509
|
+
"chat_template_kwargs": {
|
|
510
|
+
"enable_thinking": False,
|
|
511
|
+
"thinking": False,
|
|
512
|
+
},
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return {}
|
|
516
|
+
|
|
517
|
+
|
|
444
518
|
def extract_completion_text(payload: Any) -> str:
|
|
445
519
|
if not isinstance(payload, dict):
|
|
446
520
|
raise LLMError("Chat completion returned an invalid payload.")
|
|
@@ -459,7 +533,9 @@ def extract_completion_text(payload: Any) -> str:
|
|
|
459
533
|
|
|
460
534
|
content = message.get("content")
|
|
461
535
|
if isinstance(content, str):
|
|
462
|
-
|
|
536
|
+
stripped = content.strip()
|
|
537
|
+
if stripped:
|
|
538
|
+
return stripped
|
|
463
539
|
|
|
464
540
|
if isinstance(content, list):
|
|
465
541
|
text_parts: list[str] = []
|
|
@@ -477,6 +553,41 @@ def extract_completion_text(payload: Any) -> str:
|
|
|
477
553
|
if joined:
|
|
478
554
|
return joined
|
|
479
555
|
|
|
556
|
+
finish_reason = choice.get("finish_reason")
|
|
557
|
+
reasoning = message.get("reasoning")
|
|
558
|
+
has_reasoning = isinstance(reasoning, str) and reasoning.strip()
|
|
559
|
+
finish_reason_detail: str | None = None
|
|
560
|
+
if finish_reason is not None:
|
|
561
|
+
try:
|
|
562
|
+
finish_reason_detail = json.dumps(finish_reason)
|
|
563
|
+
except TypeError:
|
|
564
|
+
finish_reason_detail = repr(finish_reason)
|
|
565
|
+
finish_reason_detail = truncate_response_detail(finish_reason_detail)
|
|
566
|
+
|
|
567
|
+
if finish_reason == "length":
|
|
568
|
+
detail = (
|
|
569
|
+
" The response contained reasoning text but no final assistant content."
|
|
570
|
+
if has_reasoning
|
|
571
|
+
else ""
|
|
572
|
+
)
|
|
573
|
+
raise LLMError(
|
|
574
|
+
"Chat completion reached the completion token limit before returning "
|
|
575
|
+
'message content (finish_reason="length").'
|
|
576
|
+
f"{detail} Increase `max_tokens` or reduce the prompt."
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
if finish_reason_detail is not None:
|
|
580
|
+
raise LLMError(
|
|
581
|
+
"Chat completion message content was empty "
|
|
582
|
+
f"(finish_reason={finish_reason_detail})."
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
if has_reasoning:
|
|
586
|
+
raise LLMError(
|
|
587
|
+
"Chat completion message content was empty. The response contained "
|
|
588
|
+
"reasoning text but no final assistant content."
|
|
589
|
+
)
|
|
590
|
+
|
|
480
591
|
raise LLMError("Chat completion message content was empty.")
|
|
481
592
|
|
|
482
593
|
|
|
@@ -487,24 +598,35 @@ def chat_completion_request(
|
|
|
487
598
|
*,
|
|
488
599
|
model_id: str,
|
|
489
600
|
prompt: str,
|
|
601
|
+
disable_thinking: bool = False,
|
|
602
|
+
max_tokens: int | None = None,
|
|
490
603
|
) -> str:
|
|
604
|
+
request_body: dict[str, Any] = {
|
|
605
|
+
"model": model_id,
|
|
606
|
+
"messages": [
|
|
607
|
+
{
|
|
608
|
+
"role": "user",
|
|
609
|
+
"content": prompt,
|
|
610
|
+
}
|
|
611
|
+
],
|
|
612
|
+
"temperature": 0,
|
|
613
|
+
"max_tokens": max_tokens if max_tokens is not None else 1024,
|
|
614
|
+
"stream": False,
|
|
615
|
+
}
|
|
616
|
+
if disable_thinking:
|
|
617
|
+
request_body.update(
|
|
618
|
+
disable_thinking_options(
|
|
619
|
+
model_id=model_id,
|
|
620
|
+
api_surface="chat_completions",
|
|
621
|
+
)
|
|
622
|
+
)
|
|
623
|
+
|
|
491
624
|
payload = request_json(
|
|
492
625
|
client,
|
|
493
626
|
"POST",
|
|
494
627
|
url,
|
|
495
628
|
headers=headers,
|
|
496
|
-
json_body=
|
|
497
|
-
"model": model_id,
|
|
498
|
-
"messages": [
|
|
499
|
-
{
|
|
500
|
-
"role": "user",
|
|
501
|
-
"content": prompt,
|
|
502
|
-
}
|
|
503
|
-
],
|
|
504
|
-
"temperature": 0,
|
|
505
|
-
"max_tokens": 1024,
|
|
506
|
-
"stream": False,
|
|
507
|
-
},
|
|
629
|
+
json_body=request_body,
|
|
508
630
|
)
|
|
509
631
|
return extract_completion_text(payload)
|
|
510
632
|
|
|
@@ -551,6 +673,46 @@ def extract_response_text(payload: Any) -> str:
|
|
|
551
673
|
raise LLMError("Responses API output did not contain text.")
|
|
552
674
|
|
|
553
675
|
|
|
676
|
+
def response_output_contains_reasoning(payload: dict[str, Any]) -> bool:
|
|
677
|
+
output = payload.get("output")
|
|
678
|
+
if not isinstance(output, list):
|
|
679
|
+
return False
|
|
680
|
+
|
|
681
|
+
for item in output:
|
|
682
|
+
if not isinstance(item, dict):
|
|
683
|
+
continue
|
|
684
|
+
if item.get("type") == "reasoning":
|
|
685
|
+
return True
|
|
686
|
+
content = item.get("content")
|
|
687
|
+
if not isinstance(content, list):
|
|
688
|
+
continue
|
|
689
|
+
for block in content:
|
|
690
|
+
if isinstance(block, dict) and block.get("type") in {
|
|
691
|
+
"reasoning_text",
|
|
692
|
+
"reasoning_summary",
|
|
693
|
+
}:
|
|
694
|
+
return True
|
|
695
|
+
|
|
696
|
+
return False
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def format_incomplete_response_error(
|
|
700
|
+
*,
|
|
701
|
+
reason: str,
|
|
702
|
+
final_response: dict[str, Any],
|
|
703
|
+
) -> str:
|
|
704
|
+
message = f"Responses API response was incomplete: {reason}."
|
|
705
|
+
if reason == "max_output_tokens":
|
|
706
|
+
message += " Increase `--max-tokens` or reduce the prompt."
|
|
707
|
+
if response_output_contains_reasoning(final_response):
|
|
708
|
+
message += (
|
|
709
|
+
" The response contained reasoning output before final text; if this "
|
|
710
|
+
"provider cannot disable reasoning on `/responses`, use its "
|
|
711
|
+
"`/chat/completions` endpoint instead."
|
|
712
|
+
)
|
|
713
|
+
return message
|
|
714
|
+
|
|
715
|
+
|
|
554
716
|
def responses_completion_request(
|
|
555
717
|
client: httpx.Client,
|
|
556
718
|
url: str,
|
|
@@ -558,8 +720,10 @@ def responses_completion_request(
|
|
|
558
720
|
*,
|
|
559
721
|
model_id: str,
|
|
560
722
|
prompt: str,
|
|
723
|
+
disable_thinking: bool = False,
|
|
724
|
+
max_tokens: int | None = None,
|
|
561
725
|
) -> str:
|
|
562
|
-
request_body = {
|
|
726
|
+
request_body: dict[str, Any] = {
|
|
563
727
|
"model": model_id,
|
|
564
728
|
"input": [
|
|
565
729
|
{
|
|
@@ -575,6 +739,15 @@ def responses_completion_request(
|
|
|
575
739
|
"stream": True,
|
|
576
740
|
"store": False,
|
|
577
741
|
}
|
|
742
|
+
if max_tokens is not None:
|
|
743
|
+
request_body["max_output_tokens"] = max_tokens
|
|
744
|
+
if disable_thinking:
|
|
745
|
+
request_body.update(
|
|
746
|
+
disable_thinking_options(
|
|
747
|
+
model_id=model_id,
|
|
748
|
+
api_surface="responses",
|
|
749
|
+
)
|
|
750
|
+
)
|
|
578
751
|
|
|
579
752
|
for attempt in range(HTTP_RETRY_ATTEMPTS):
|
|
580
753
|
text_parts: list[str] = []
|
|
@@ -697,7 +870,12 @@ def responses_completion_request(
|
|
|
697
870
|
reason = raw_reason.strip()
|
|
698
871
|
if text:
|
|
699
872
|
return f"{text}\n\n[Response incomplete: {reason}]"
|
|
700
|
-
raise LLMError(
|
|
873
|
+
raise LLMError(
|
|
874
|
+
format_incomplete_response_error(
|
|
875
|
+
reason=reason,
|
|
876
|
+
final_response=final_response,
|
|
877
|
+
)
|
|
878
|
+
)
|
|
701
879
|
|
|
702
880
|
if text:
|
|
703
881
|
return text
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
from urllib.parse import urlparse
|
|
4
5
|
|
|
5
6
|
from rich.console import Console
|
|
6
7
|
from rich.panel import Panel
|
|
@@ -9,6 +10,9 @@ from rich.table import Table
|
|
|
9
10
|
from . import core as llm
|
|
10
11
|
|
|
11
12
|
DEFAULT_SUPPORTED_ENDPOINTS = ("/chat/completions",)
|
|
13
|
+
CHAT_COMPLETIONS_ENDPOINT = "/chat/completions"
|
|
14
|
+
RESPONSES_ENDPOINT = "/responses"
|
|
15
|
+
MODELS_ENDPOINT = "/models"
|
|
12
16
|
|
|
13
17
|
console = Console()
|
|
14
18
|
|
|
@@ -32,16 +36,51 @@ def request_headers(
|
|
|
32
36
|
return headers
|
|
33
37
|
|
|
34
38
|
|
|
39
|
+
def endpoint_kind(url: str) -> str | None:
|
|
40
|
+
path = urlparse(url).path.rstrip("/")
|
|
41
|
+
if path.endswith(CHAT_COMPLETIONS_ENDPOINT):
|
|
42
|
+
return "chat_completions"
|
|
43
|
+
if path.endswith(RESPONSES_ENDPOINT):
|
|
44
|
+
return "responses"
|
|
45
|
+
if path.endswith(MODELS_ENDPOINT):
|
|
46
|
+
return "models"
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def supported_endpoints_for_url(url: str) -> tuple[str, ...]:
|
|
51
|
+
kind = endpoint_kind(url)
|
|
52
|
+
if kind == "chat_completions":
|
|
53
|
+
return (CHAT_COMPLETIONS_ENDPOINT,)
|
|
54
|
+
if kind == "responses":
|
|
55
|
+
return (RESPONSES_ENDPOINT,)
|
|
56
|
+
return DEFAULT_SUPPORTED_ENDPOINTS
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def completion_api_surface_from_url(url: str) -> str:
|
|
60
|
+
kind = endpoint_kind(url)
|
|
61
|
+
if kind in {"chat_completions", "responses"}:
|
|
62
|
+
return kind
|
|
63
|
+
raise LLMError(
|
|
64
|
+
"OpenAI-compatible generation URL must end with "
|
|
65
|
+
f"`{CHAT_COMPLETIONS_ENDPOINT}` or `{RESPONSES_ENDPOINT}`."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
35
69
|
def list_models(
|
|
36
70
|
client,
|
|
37
71
|
*,
|
|
38
72
|
base_url: str,
|
|
39
73
|
api_key: str | None = None,
|
|
40
74
|
) -> list[Model]:
|
|
75
|
+
if endpoint_kind(base_url) != "models":
|
|
76
|
+
raise LLMError(
|
|
77
|
+
f"OpenAI-compatible models URL must end with `{MODELS_ENDPOINT}`."
|
|
78
|
+
)
|
|
79
|
+
|
|
41
80
|
payload = llm.request_json(
|
|
42
81
|
client,
|
|
43
82
|
"GET",
|
|
44
|
-
|
|
83
|
+
base_url,
|
|
45
84
|
headers=request_headers(api_key),
|
|
46
85
|
)
|
|
47
86
|
|
|
@@ -95,10 +134,22 @@ def ensure_model_ready(
|
|
|
95
134
|
http_client_config: HttpClientConfig | None = None,
|
|
96
135
|
) -> Model:
|
|
97
136
|
if model is not None:
|
|
98
|
-
return default_model(
|
|
137
|
+
return default_model(
|
|
138
|
+
model,
|
|
139
|
+
supported_endpoints=supported_endpoints_for_url(base_url),
|
|
140
|
+
)
|
|
99
141
|
|
|
100
142
|
if default_model_id is not None:
|
|
101
|
-
return default_model(
|
|
143
|
+
return default_model(
|
|
144
|
+
default_model_id,
|
|
145
|
+
supported_endpoints=supported_endpoints_for_url(base_url),
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if endpoint_kind(base_url) != "models":
|
|
149
|
+
raise LLMError(
|
|
150
|
+
"OpenAI-compatible provider cannot choose a model automatically from "
|
|
151
|
+
"a generation URL. Pass `--model` or configure a default model."
|
|
152
|
+
)
|
|
102
153
|
|
|
103
154
|
with llm.make_http_client(http_client_config) as client:
|
|
104
155
|
models = list_models(client, base_url=base_url, api_key=api_key)
|
|
@@ -120,7 +171,10 @@ def ask(
|
|
|
120
171
|
configured_default_model_path: Path | None = None,
|
|
121
172
|
provider_label: str = "OpenAI-compatible provider",
|
|
122
173
|
http_client_config: HttpClientConfig | None = None,
|
|
174
|
+
disable_thinking: bool = False,
|
|
175
|
+
max_tokens: int | None = None,
|
|
123
176
|
) -> str:
|
|
177
|
+
api_surface = completion_api_surface_from_url(base_url)
|
|
124
178
|
selected_model = ensure_model_ready(
|
|
125
179
|
base_url=base_url,
|
|
126
180
|
api_key=api_key,
|
|
@@ -131,23 +185,26 @@ def ask(
|
|
|
131
185
|
http_client_config=http_client_config,
|
|
132
186
|
)
|
|
133
187
|
|
|
134
|
-
api_surface = llm.infer_api_surface(selected_model)
|
|
135
188
|
with llm.make_http_client(http_client_config) as client:
|
|
136
189
|
if api_surface == "responses":
|
|
137
190
|
return llm.responses_completion_request(
|
|
138
191
|
client,
|
|
139
|
-
|
|
192
|
+
base_url,
|
|
140
193
|
request_headers(api_key, accept="text/event-stream"),
|
|
141
194
|
model_id=selected_model.id,
|
|
142
195
|
prompt=prompt,
|
|
196
|
+
disable_thinking=disable_thinking,
|
|
197
|
+
max_tokens=max_tokens,
|
|
143
198
|
)
|
|
144
199
|
|
|
145
200
|
return llm.chat_completion_request(
|
|
146
201
|
client,
|
|
147
|
-
|
|
202
|
+
base_url,
|
|
148
203
|
request_headers(api_key),
|
|
149
204
|
model_id=selected_model.id,
|
|
150
205
|
prompt=prompt,
|
|
206
|
+
disable_thinking=disable_thinking,
|
|
207
|
+
max_tokens=max_tokens,
|
|
151
208
|
)
|
|
152
209
|
|
|
153
210
|
|
|
@@ -212,7 +269,10 @@ def show_summary(
|
|
|
212
269
|
f"{selected_model.id} ({llm.infer_api_surface(selected_model)})",
|
|
213
270
|
)
|
|
214
271
|
elif default_model_id is not None:
|
|
215
|
-
table.add_row(
|
|
272
|
+
table.add_row(
|
|
273
|
+
"Default model",
|
|
274
|
+
f"{default_model_id} ({endpoint_kind(base_url) or 'unknown endpoint'})",
|
|
275
|
+
)
|
|
216
276
|
|
|
217
277
|
console.print(Panel.fit(table, title="LLM Summary"))
|
|
218
278
|
if warning is not None:
|
|
@@ -79,16 +79,11 @@ def normalize_openai_base_url(value: str | None) -> str | None:
|
|
|
79
79
|
return None
|
|
80
80
|
|
|
81
81
|
normalized = normalized.rstrip("/")
|
|
82
|
-
for suffix in ("/chat/completions", "/responses", "/models"):
|
|
83
|
-
if normalized.endswith(suffix):
|
|
84
|
-
normalized = normalized[: -len(suffix)]
|
|
85
|
-
break
|
|
86
|
-
|
|
87
82
|
parsed = urlparse(normalized)
|
|
88
83
|
if not parsed.scheme or not parsed.netloc:
|
|
89
84
|
raise llm.LLMError(
|
|
90
|
-
"OpenAI-compatible
|
|
91
|
-
"`http://127.0.0.1:11434/v1`."
|
|
85
|
+
"OpenAI-compatible URL must include a scheme and host, for example "
|
|
86
|
+
"`http://127.0.0.1:11434/v1/chat/completions`."
|
|
92
87
|
)
|
|
93
88
|
|
|
94
89
|
return normalized.rstrip("/")
|
|
@@ -227,8 +222,8 @@ def resolve_provider_config(
|
|
|
227
222
|
resolved_base_url = normalize_openai_base_url(os.getenv("OPENAI_BASE_URL"))
|
|
228
223
|
if resolved_base_url is None:
|
|
229
224
|
raise llm.LLMError(
|
|
230
|
-
"OpenAI-compatible provider requires
|
|
231
|
-
"`--base-url http://127.0.0.1:11434/v1` or set "
|
|
225
|
+
"OpenAI-compatible provider requires an endpoint URL. Pass "
|
|
226
|
+
"`--base-url http://127.0.0.1:11434/v1/chat/completions` or set "
|
|
232
227
|
"`GIT_COPILOT_COMMIT_BASE_URL`."
|
|
233
228
|
)
|
|
234
229
|
|
|
@@ -316,6 +311,8 @@ def ask(
|
|
|
316
311
|
provider_config: ProviderConfig | None = None,
|
|
317
312
|
model: str | None = None,
|
|
318
313
|
http_client_config: llm.HttpClientConfig | None = None,
|
|
314
|
+
disable_thinking: bool = False,
|
|
315
|
+
max_tokens: int | None = None,
|
|
319
316
|
) -> str:
|
|
320
317
|
resolved_provider = provider_config or resolve_provider_config()
|
|
321
318
|
default_model, config_file = load_default_model()
|
|
@@ -327,6 +324,8 @@ def ask(
|
|
|
327
324
|
default_model=default_model,
|
|
328
325
|
configured_default_model_path=config_file,
|
|
329
326
|
http_client_config=http_client_config,
|
|
327
|
+
disable_thinking=disable_thinking,
|
|
328
|
+
max_tokens=max_tokens,
|
|
330
329
|
)
|
|
331
330
|
|
|
332
331
|
if resolved_provider.base_url is None:
|
|
@@ -341,6 +340,8 @@ def ask(
|
|
|
341
340
|
configured_default_model_path=config_file,
|
|
342
341
|
provider_label=resolved_provider.display_name,
|
|
343
342
|
http_client_config=http_client_config,
|
|
343
|
+
disable_thinking=disable_thinking,
|
|
344
|
+
max_tokens=max_tokens,
|
|
344
345
|
)
|
|
345
346
|
|
|
346
347
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-copilot-commit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Automatically generate and commit changes using GitHub Copilot or OpenAI-compatible LLMs
|
|
5
5
|
Author-email: Dheepak Krishnamurthy <1813121+kdheepak@users.noreply.github.com>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -18,14 +18,16 @@ Description-Content-Type: text/markdown
|
|
|
18
18
|
[](https://pypi.org/project/git-copilot-commit/)
|
|
19
19
|
[](https://github.com/kdheepak/git-copilot-commit/blob/main/LICENSE)
|
|
20
20
|
|
|
21
|
-
AI-powered Git commit assistant that generates conventional commit messages using GitHub Copilot or
|
|
21
|
+
AI-powered Git commit assistant that generates conventional commit messages using GitHub Copilot or
|
|
22
|
+
any OpenAI-compatible LLM.
|
|
22
23
|
|
|
23
24
|

|
|
24
25
|
|
|
25
26
|
## Features
|
|
26
27
|
|
|
27
28
|
- Generates commit messages based on your staged changes
|
|
28
|
-
- Supports GitHub Copilot and OpenAI-compatible `/v1/
|
|
29
|
+
- Supports GitHub Copilot and OpenAI-compatible `/v1/chat/completions`, `/v1/responses`,
|
|
30
|
+
and `/v1/models` endpoints
|
|
29
31
|
- Supports multiple LLM models: GPT, Claude, Gemini, local models, and more
|
|
30
32
|
- Allows editing of generated messages before committing
|
|
31
33
|
- Follows the [Conventional Commits](https://www.conventionalcommits.org/) standard
|
|
@@ -51,8 +53,8 @@ You can run the latest version of tool directly every time by invoking this one
|
|
|
51
53
|
uvx git-copilot-commit --help
|
|
52
54
|
```
|
|
53
55
|
|
|
54
|
-
Alternatively, you can install the tool once into a global isolated environment
|
|
55
|
-
|
|
56
|
+
Alternatively, you can install the tool once into a global isolated environment and run
|
|
57
|
+
`git-copilot-commit` to invoke it:
|
|
56
58
|
|
|
57
59
|
```bash
|
|
58
60
|
# Install into global isolated environment
|
|
@@ -96,39 +98,27 @@ git-copilot-commit --help
|
|
|
96
98
|
|
|
97
99
|
### OpenAI-compatible provider
|
|
98
100
|
|
|
99
|
-
1.
|
|
100
|
-
|
|
101
|
-
The base URL can be either the provider root such as `http://127.0.0.1:11434/v1`
|
|
102
|
-
or the full chat completions endpoint such as
|
|
103
|
-
`http://127.0.0.1:11434/v1/chat/completions`.
|
|
101
|
+
1. List models by pointing the CLI at your server's `/models` endpoint.
|
|
104
102
|
|
|
105
103
|
```bash
|
|
106
104
|
uvx git-copilot-commit models \
|
|
107
105
|
--provider openai \
|
|
108
|
-
--base-url http://127.0.0.1:11434/v1
|
|
106
|
+
--base-url http://127.0.0.1:11434/v1/models
|
|
109
107
|
```
|
|
110
108
|
|
|
111
|
-
2. Generate and commit.
|
|
109
|
+
2. Generate and commit by pointing the CLI at the generation endpoint you want to use.
|
|
112
110
|
|
|
113
111
|
```bash
|
|
114
112
|
uvx git-copilot-commit commit \
|
|
115
113
|
--provider openai \
|
|
116
|
-
--base-url http://127.0.0.1:11434/v1 \
|
|
114
|
+
--base-url http://127.0.0.1:11434/v1/chat/completions \
|
|
117
115
|
--model your-model-id
|
|
118
116
|
```
|
|
119
117
|
|
|
120
118
|
If your server requires an API key, also pass `--api-key ...` or set `OPENAI_API_KEY`.
|
|
121
119
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
uvx git-copilot-commit commit \
|
|
126
|
-
--provider openai \
|
|
127
|
-
--base-url http://example.com:8001/v1/chat/completions \
|
|
128
|
-
--model openai/gpt-oss-120b
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
Model ids with slashes such as `openai/gpt-oss-120b` are supported.
|
|
120
|
+
OpenAI-compatible generation URLs must end with `/chat/completions` or `/responses`.
|
|
121
|
+
Model listing URLs must end with `/models`.
|
|
132
122
|
|
|
133
123
|
## Usage
|
|
134
124
|
|
|
@@ -137,28 +127,39 @@ git-copilot-commit --help
|
|
|
137
127
|
```bash
|
|
138
128
|
$ uvx git-copilot-commit commit --help
|
|
139
129
|
|
|
140
|
-
Usage: git-copilot-commit commit [
|
|
130
|
+
Usage: git-copilot-commit commit [ARGS]
|
|
141
131
|
|
|
142
132
|
Generate commit message based on changes in the current git repository and commit them.
|
|
143
133
|
|
|
144
|
-
╭─
|
|
145
|
-
│ --all
|
|
146
|
-
│ --split
|
|
147
|
-
│
|
|
148
|
-
│
|
|
149
|
-
│ --
|
|
150
|
-
│ --
|
|
151
|
-
│
|
|
152
|
-
│ --
|
|
153
|
-
│
|
|
154
|
-
│
|
|
155
|
-
│
|
|
156
|
-
│
|
|
157
|
-
│
|
|
158
|
-
│
|
|
159
|
-
│
|
|
160
|
-
│ --
|
|
161
|
-
|
|
134
|
+
╭─ Parameters ─────────────────────────────────────────────────────────────────╮
|
|
135
|
+
│ ALL --all -a --no-all Stage all files before committing [default: False] │
|
|
136
|
+
│ SPLIT --split --no-split Split staged hunks into multiple commits │
|
|
137
|
+
│ automatically. Pass --split=N to express a │
|
|
138
|
+
│ preference for N commits. [default: False] │
|
|
139
|
+
│ MODEL --model -m Model to use for generating commit message │
|
|
140
|
+
│ YES --yes -y --no-yes Automatically accept the generated commit message │
|
|
141
|
+
│ [default: False] │
|
|
142
|
+
│ CONTEXT --context -c Optional user-provided context to guide commit │
|
|
143
|
+
│ message [default: ""] │
|
|
144
|
+
│ DISABLE-THINKING Disable or minimize reasoning/thinking tokens for │
|
|
145
|
+
│ --disable-thinking commit-message requests. [default: True] │
|
|
146
|
+
│ --enable-thinking │
|
|
147
|
+
│ MAX-TOKENS --max-tokens Maximum output tokens for LLM generation. │
|
|
148
|
+
│ [default: 1024] │
|
|
149
|
+
│ PROVIDER --provider LLM provider to use: copilot or openai. │
|
|
150
|
+
│ BASE-URL --base-url Endpoint URL for an OpenAI-compatible provider, │
|
|
151
|
+
│ for example │
|
|
152
|
+
│ http://127.0.0.1:11434/v1/chat/completions. │
|
|
153
|
+
│ API-KEY --api-key API key for an OpenAI-compatible provider. Omit │
|
|
154
|
+
│ when the server does not require one. │
|
|
155
|
+
│ CA-BUNDLE --ca-bundle Path to a custom CA bundle (PEM) │
|
|
156
|
+
│ INSECURE --insecure Disable SSL certificate verification. [default: │
|
|
157
|
+
│ --no-insecure False] │
|
|
158
|
+
│ NATIVE-TLS --native-tls Use the OS's native certificate store via │
|
|
159
|
+
│ --no-native-tls 'truststore' for httpx instead of the Python │
|
|
160
|
+
│ bundle. Ignored if --ca-bundle or --insecure is │
|
|
161
|
+
│ used. [default: True] │
|
|
162
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
162
163
|
```
|
|
163
164
|
|
|
164
165
|
## Examples
|
|
@@ -186,17 +187,51 @@ Use a local OpenAI-compatible server:
|
|
|
186
187
|
```bash
|
|
187
188
|
uvx git-copilot-commit commit \
|
|
188
189
|
--provider openai \
|
|
189
|
-
--base-url http://127.0.0.1:11434/v1 \
|
|
190
|
+
--base-url http://127.0.0.1:11434/v1/chat/completions \
|
|
190
191
|
--model your-model-id
|
|
191
192
|
```
|
|
192
193
|
|
|
193
|
-
|
|
194
|
+
Example with `openai/gpt-oss-120b` and `Qwen/Qwen3.6-35B-A3B`:
|
|
194
195
|
|
|
195
196
|
```bash
|
|
196
197
|
uvx git-copilot-commit commit \
|
|
197
198
|
--provider openai \
|
|
198
199
|
--base-url http://example.com:8001/v1/chat/completions \
|
|
199
200
|
--model openai/gpt-oss-120b
|
|
201
|
+
|
|
202
|
+
uvx git-copilot-commit commit \
|
|
203
|
+
--provider openai \
|
|
204
|
+
--base-url http://example.com:8002/v1/chat/completions \
|
|
205
|
+
--model Qwen/Qwen3.6-35B-A3B
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Use the Responses API endpoint:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
uvx git-copilot-commit commit \
|
|
212
|
+
--provider openai \
|
|
213
|
+
--base-url http://example.com:8002/v1/responses \
|
|
214
|
+
--model your-model-id
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Increase the output token budget:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
uvx git-copilot-commit commit --max-tokens 4096
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Thinking/reasoning is disabled or minimized by default for commit-message requests. To let the
|
|
224
|
+
selected model use its default thinking behavior, pass:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
uvx git-copilot-commit commit --enable-thinking
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
TLS uses the operating system's native certificate store by default. To use Python's default
|
|
231
|
+
certificate bundle instead, pass:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
uvx git-copilot-commit commit --no-native-tls
|
|
200
235
|
```
|
|
201
236
|
|
|
202
237
|
Split staged hunks into separate commits:
|
|
@@ -246,8 +281,8 @@ Now you can run to review the message before committing:
|
|
|
246
281
|
git ai-commit
|
|
247
282
|
```
|
|
248
283
|
|
|
249
|
-
Alternatively, you can stage all files and auto accept the commit message and
|
|
250
|
-
|
|
284
|
+
Alternatively, you can stage all files and auto accept the commit message and specify which model
|
|
285
|
+
should be used to generate the commit in one CLI invocation.
|
|
251
286
|
|
|
252
287
|
```bash
|
|
253
288
|
git ai-commit --all --yes --model claude-3.5-sonnet
|
|
@@ -257,7 +292,7 @@ You can also set provider defaults with environment variables:
|
|
|
257
292
|
|
|
258
293
|
```bash
|
|
259
294
|
export GIT_COPILOT_COMMIT_PROVIDER=openai
|
|
260
|
-
export GIT_COPILOT_COMMIT_BASE_URL=http://127.0.0.1:11434/v1
|
|
295
|
+
export GIT_COPILOT_COMMIT_BASE_URL=http://127.0.0.1:11434/v1/chat/completions
|
|
261
296
|
export GIT_COPILOT_COMMIT_API_KEY=...
|
|
262
297
|
export OPENAI_API_KEY=...
|
|
263
298
|
git ai-commit --provider openai --model your-model-id
|
|
@@ -267,7 +302,7 @@ For example:
|
|
|
267
302
|
|
|
268
303
|
```bash
|
|
269
304
|
export GIT_COPILOT_COMMIT_PROVIDER=openai
|
|
270
|
-
export GIT_COPILOT_COMMIT_BASE_URL=http://example.com:8001/v1
|
|
305
|
+
export GIT_COPILOT_COMMIT_BASE_URL=http://example.com:8001/v1/chat/completions
|
|
271
306
|
git ai-commit --model openai/gpt-oss-120b
|
|
272
307
|
```
|
|
273
308
|
|
|
@@ -279,5 +314,5 @@ git ai-commit --model openai/gpt-oss-120b
|
|
|
279
314
|
> git config --global diff.context 3
|
|
280
315
|
> ```
|
|
281
316
|
>
|
|
282
|
-
> This may be useful because this tool sends the diffs with surrounding context
|
|
283
|
-
>
|
|
317
|
+
> This may be useful because this tool sends the diffs with surrounding context to the LLM for
|
|
318
|
+
> generating a commit message
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
git_copilot_commit/__init__.py,sha256=v3x5oBkxwKJEZLv62QqSmP3iqNKLtZgrWZfH8eFzlQg,60
|
|
2
|
-
git_copilot_commit/cli.py,sha256=
|
|
2
|
+
git_copilot_commit/cli.py,sha256=ZlrXY6c4JUxMqLEkykhnNTgoegOfxLq9Pp7Yqy1PuOk,40391
|
|
3
3
|
git_copilot_commit/git.py,sha256=EbXiicWygSlMM-F6rY4LCkchwCvsFTziJcdUZM-1vnw,21059
|
|
4
4
|
git_copilot_commit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
git_copilot_commit/settings.py,sha256=WrM10_J3F7QBfOVmPDWpNZrNHhmZSeN-9FqQZxgdWvQ,3730
|
|
6
6
|
git_copilot_commit/split_commits.py,sha256=rHyuVJggjmYjbva7BVqsM3aZRxUgOKkuZtxxvFRcu6Q,15060
|
|
7
7
|
git_copilot_commit/version.py,sha256=AieHOUX52g6N67HL0iLWtDKrgOYyulxwHWViu26Jrd4,105
|
|
8
8
|
git_copilot_commit/llms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
git_copilot_commit/llms/copilot.py,sha256=
|
|
10
|
-
git_copilot_commit/llms/core.py,sha256=
|
|
11
|
-
git_copilot_commit/llms/openai_api.py,sha256
|
|
12
|
-
git_copilot_commit/llms/providers.py,sha256=
|
|
9
|
+
git_copilot_commit/llms/copilot.py,sha256=_RK4jpjziXeTrpcSA-Kaj1AT-18y4Nztu1Kqdkh95Bs,25415
|
|
10
|
+
git_copilot_commit/llms/core.py,sha256=4JseguAA17RIPi59nXcCYn1DP3duw2CZpjKrZKsLMsk,31182
|
|
11
|
+
git_copilot_commit/llms/openai_api.py,sha256=--6RVwxV2aGsJT2paJzv86CihoPczpfEVFA4P5_kSSg,8325
|
|
12
|
+
git_copilot_commit/llms/providers.py,sha256=N0DLkfRP-Z85kcYYoXWWHXr5nIwXUXDV88skS-NXLpU,11649
|
|
13
13
|
git_copilot_commit/prompts/commit-message-generator-prompt.md,sha256=3Dz8GCdumFNAtXOdTlpRtgBnmX0WyrPL6tdfMgNyYiE,2411
|
|
14
14
|
git_copilot_commit/prompts/split-commit-planner-prompt.md,sha256=tDI0v1udOhkRQM31M892FMzcPMYHExnU0fjTGia1V2k,1510
|
|
15
|
-
git_copilot_commit-0.
|
|
16
|
-
git_copilot_commit-0.
|
|
17
|
-
git_copilot_commit-0.
|
|
18
|
-
git_copilot_commit-0.
|
|
19
|
-
git_copilot_commit-0.
|
|
15
|
+
git_copilot_commit-0.7.0.dist-info/METADATA,sha256=lI1E3-S5S1HEsTFOaqPuXwPE0WCF2kNx_mrWZFNReyg,10006
|
|
16
|
+
git_copilot_commit-0.7.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
17
|
+
git_copilot_commit-0.7.0.dist-info/entry_points.txt,sha256=-D4bQqiuSPwQJG2zx--vJbZD1iqB5coUfoJ_gmC3rSg,66
|
|
18
|
+
git_copilot_commit-0.7.0.dist-info/licenses/LICENSE,sha256=14lNZAoKJPI1U7eGpletjN_PFm1JwP1vT_0jFKY6eWg,1065
|
|
19
|
+
git_copilot_commit-0.7.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|