git-copilot-commit 0.4.0__tar.gz → 0.4.2__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_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/PKG-INFO +1 -1
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/src/git_copilot_commit/cli.py +12 -1
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/src/git_copilot_commit/github_copilot.py +197 -29
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/.github/workflows/ci.yml +0 -0
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/.gitignore +0 -0
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/.justfile +0 -0
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/.python-version +0 -0
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/LICENSE +0 -0
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/README.md +0 -0
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/pyproject.toml +0 -0
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/src/git_copilot_commit/__init__.py +0 -0
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/src/git_copilot_commit/git.py +0 -0
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/src/git_copilot_commit/prompts/commit-message-generator-prompt.md +0 -0
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/src/git_copilot_commit/py.typed +0 -0
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/src/git_copilot_commit/settings.py +0 -0
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/src/git_copilot_commit/version.py +0 -0
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/uv.lock +0 -0
- {git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/vhs/demo.vhs +0 -0
|
@@ -116,7 +116,18 @@ def generate_commit_message(
|
|
|
116
116
|
if model.startswith("github_copilot/"):
|
|
117
117
|
model = model.replace("github_copilot/", "")
|
|
118
118
|
|
|
119
|
-
return github_copilot.ask(
|
|
119
|
+
return github_copilot.ask(
|
|
120
|
+
f"""
|
|
121
|
+
# System Prompt
|
|
122
|
+
|
|
123
|
+
{load_system_prompt()}
|
|
124
|
+
|
|
125
|
+
# Prompt
|
|
126
|
+
|
|
127
|
+
{prompt}
|
|
128
|
+
""",
|
|
129
|
+
model=model,
|
|
130
|
+
)
|
|
120
131
|
|
|
121
132
|
|
|
122
133
|
def commit_with_retry_no_verify(
|
{git_copilot_commit-0.4.0 → git_copilot_commit-0.4.2}/src/git_copilot_commit/github_copilot.py
RENAMED
|
@@ -251,6 +251,59 @@ def request_json(
|
|
|
251
251
|
raise CopilotError(f"Invalid JSON response from {url}.") from exc
|
|
252
252
|
|
|
253
253
|
|
|
254
|
+
def iter_sse_events(response: httpx.Response, url: str):
|
|
255
|
+
event_name: str | None = None
|
|
256
|
+
data_lines: list[str] = []
|
|
257
|
+
|
|
258
|
+
def decode_event(raw_data: str, current_event: str | None) -> Any:
|
|
259
|
+
if raw_data == "[DONE]":
|
|
260
|
+
return None
|
|
261
|
+
try:
|
|
262
|
+
payload = json.loads(raw_data)
|
|
263
|
+
except json.JSONDecodeError as exc:
|
|
264
|
+
label = current_event or "message"
|
|
265
|
+
raise CopilotError(
|
|
266
|
+
f"Invalid SSE event payload from {url} ({label})."
|
|
267
|
+
) from exc
|
|
268
|
+
if isinstance(payload, dict) and current_event and "type" not in payload:
|
|
269
|
+
payload = dict(payload)
|
|
270
|
+
payload["type"] = current_event
|
|
271
|
+
return payload
|
|
272
|
+
|
|
273
|
+
for raw_line in response.iter_lines():
|
|
274
|
+
line = raw_line if isinstance(raw_line, str) else raw_line.decode("utf-8")
|
|
275
|
+
if not line:
|
|
276
|
+
if data_lines:
|
|
277
|
+
current_event = event_name
|
|
278
|
+
raw_data = "\n".join(data_lines)
|
|
279
|
+
event_name = None
|
|
280
|
+
data_lines = []
|
|
281
|
+
payload = decode_event(raw_data, current_event)
|
|
282
|
+
if payload is not None:
|
|
283
|
+
yield payload
|
|
284
|
+
else:
|
|
285
|
+
event_name = None
|
|
286
|
+
continue
|
|
287
|
+
|
|
288
|
+
if line.startswith(":"):
|
|
289
|
+
continue
|
|
290
|
+
|
|
291
|
+
field, _, value = line.partition(":")
|
|
292
|
+
if value.startswith(" "):
|
|
293
|
+
value = value[1:]
|
|
294
|
+
|
|
295
|
+
if field == "event":
|
|
296
|
+
event_name = value
|
|
297
|
+
continue
|
|
298
|
+
if field == "data":
|
|
299
|
+
data_lines.append(value)
|
|
300
|
+
|
|
301
|
+
if data_lines:
|
|
302
|
+
payload = decode_event("\n".join(data_lines), event_name)
|
|
303
|
+
if payload is not None:
|
|
304
|
+
yield payload
|
|
305
|
+
|
|
306
|
+
|
|
254
307
|
def load_credentials() -> CopilotCredentials | None:
|
|
255
308
|
path = credentials_path()
|
|
256
309
|
if not path.exists():
|
|
@@ -455,10 +508,11 @@ def copilot_request_headers(
|
|
|
455
508
|
access_token: str,
|
|
456
509
|
*,
|
|
457
510
|
intent: str = "conversation-panel",
|
|
511
|
+
accept: str = "application/json",
|
|
458
512
|
) -> dict[str, str]:
|
|
459
513
|
return {
|
|
460
514
|
"Authorization": f"Bearer {access_token}",
|
|
461
|
-
"Accept":
|
|
515
|
+
"Accept": accept,
|
|
462
516
|
"Content-Type": "application/json",
|
|
463
517
|
"User-Agent": USER_AGENT,
|
|
464
518
|
"Editor-Version": EDITOR_VERSION,
|
|
@@ -652,7 +706,7 @@ def chat_completion(
|
|
|
652
706
|
}
|
|
653
707
|
],
|
|
654
708
|
"temperature": 0,
|
|
655
|
-
"max_tokens":
|
|
709
|
+
"max_tokens": 1024,
|
|
656
710
|
"stream": False,
|
|
657
711
|
},
|
|
658
712
|
)
|
|
@@ -672,6 +726,7 @@ def extract_response_text(payload: Any) -> str:
|
|
|
672
726
|
raise CopilotError("Responses API returned no output.")
|
|
673
727
|
|
|
674
728
|
parts: list[str] = []
|
|
729
|
+
refusals: list[str] = []
|
|
675
730
|
for item in output:
|
|
676
731
|
if not isinstance(item, dict):
|
|
677
732
|
continue
|
|
@@ -684,12 +739,20 @@ def extract_response_text(payload: Any) -> str:
|
|
|
684
739
|
text = block.get("text")
|
|
685
740
|
if isinstance(text, str) and text.strip():
|
|
686
741
|
parts.append(text.strip())
|
|
742
|
+
continue
|
|
743
|
+
refusal = block.get("refusal")
|
|
744
|
+
if isinstance(refusal, str) and refusal.strip():
|
|
745
|
+
refusals.append(refusal.strip())
|
|
687
746
|
|
|
688
747
|
joined = "\n".join(parts).strip()
|
|
689
748
|
if joined:
|
|
690
749
|
return joined
|
|
691
750
|
|
|
692
|
-
|
|
751
|
+
joined_refusals = "\n".join(refusals).strip()
|
|
752
|
+
if joined_refusals:
|
|
753
|
+
return joined_refusals
|
|
754
|
+
|
|
755
|
+
raise CopilotError("Responses API output did not contain text.")
|
|
693
756
|
|
|
694
757
|
|
|
695
758
|
def responses_completion(
|
|
@@ -699,32 +762,137 @@ def responses_completion(
|
|
|
699
762
|
model: CopilotModel,
|
|
700
763
|
prompt: str,
|
|
701
764
|
) -> str:
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
"
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
765
|
+
url = f"{credentials.base_url()}/responses"
|
|
766
|
+
request_body = {
|
|
767
|
+
"model": model.id,
|
|
768
|
+
"input": [
|
|
769
|
+
{
|
|
770
|
+
"role": "user",
|
|
771
|
+
"content": [
|
|
772
|
+
{
|
|
773
|
+
"type": "input_text",
|
|
774
|
+
"text": prompt,
|
|
775
|
+
}
|
|
776
|
+
],
|
|
777
|
+
}
|
|
778
|
+
],
|
|
779
|
+
"stream": True,
|
|
780
|
+
"store": False,
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
text_parts: list[str] = []
|
|
784
|
+
final_response: dict[str, Any] | None = None
|
|
785
|
+
|
|
786
|
+
try:
|
|
787
|
+
with client.stream(
|
|
788
|
+
"POST",
|
|
789
|
+
url,
|
|
790
|
+
headers=copilot_request_headers(
|
|
791
|
+
credentials.copilot_token,
|
|
792
|
+
intent="conversation-edits",
|
|
793
|
+
accept="text/event-stream",
|
|
794
|
+
),
|
|
795
|
+
json=request_body,
|
|
796
|
+
) as response:
|
|
797
|
+
if response.is_error:
|
|
798
|
+
detail = response.read().decode("utf-8", errors="replace").strip()
|
|
799
|
+
if len(detail) > 400:
|
|
800
|
+
detail = f"{detail[:397]}..."
|
|
801
|
+
suffix = f": {detail}" if detail else ""
|
|
802
|
+
raise CopilotError(
|
|
803
|
+
f"{response.status_code} {response.reason_phrase}{suffix}"
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
content_type = response.headers.get("content-type", "")
|
|
807
|
+
if "text/event-stream" not in content_type:
|
|
808
|
+
body = response.read()
|
|
809
|
+
try:
|
|
810
|
+
payload = json.loads(body)
|
|
811
|
+
except ValueError as exc:
|
|
812
|
+
detail = body.decode("utf-8", errors="replace").strip()
|
|
813
|
+
if len(detail) > 400:
|
|
814
|
+
detail = f"{detail[:397]}..."
|
|
815
|
+
raise CopilotError(
|
|
816
|
+
f"Expected an SSE stream from {url}, got {content_type or 'unknown content type'}: {detail}"
|
|
817
|
+
) from exc
|
|
818
|
+
return extract_response_text(payload)
|
|
819
|
+
|
|
820
|
+
for event in iter_sse_events(response, url):
|
|
821
|
+
if not isinstance(event, dict):
|
|
822
|
+
continue
|
|
823
|
+
|
|
824
|
+
event_type = event.get("type")
|
|
825
|
+
if event_type == "response.output_text.delta":
|
|
826
|
+
delta = event.get("delta")
|
|
827
|
+
if isinstance(delta, str) and delta:
|
|
828
|
+
text_parts.append(delta)
|
|
829
|
+
continue
|
|
830
|
+
|
|
831
|
+
if event_type == "response.output_text.done" and not text_parts:
|
|
832
|
+
text = event.get("text")
|
|
833
|
+
if isinstance(text, str) and text.strip():
|
|
834
|
+
text_parts.append(text)
|
|
835
|
+
continue
|
|
836
|
+
|
|
837
|
+
if event_type == "error":
|
|
838
|
+
error = event.get("error")
|
|
839
|
+
if isinstance(error, dict):
|
|
840
|
+
message = error.get("message")
|
|
841
|
+
code = error.get("code")
|
|
842
|
+
if isinstance(message, str) and message.strip():
|
|
843
|
+
prefix = (
|
|
844
|
+
f"{code}: " if isinstance(code, str) and code else ""
|
|
845
|
+
)
|
|
846
|
+
raise CopilotError(
|
|
847
|
+
f"Responses stream error: {prefix}{message.strip()}"
|
|
848
|
+
)
|
|
849
|
+
raise CopilotError("Responses stream returned an error event.")
|
|
850
|
+
|
|
851
|
+
if event_type in {
|
|
852
|
+
"response.completed",
|
|
853
|
+
"response.failed",
|
|
854
|
+
"response.incomplete",
|
|
855
|
+
}:
|
|
856
|
+
response_payload = event.get("response")
|
|
857
|
+
if isinstance(response_payload, dict):
|
|
858
|
+
final_response = response_payload
|
|
859
|
+
except httpx.HTTPError as exc:
|
|
860
|
+
raise CopilotError(f"Request failed for {url}: {exc}") from exc
|
|
861
|
+
|
|
862
|
+
text = "".join(text_parts).strip()
|
|
863
|
+
if final_response is None:
|
|
864
|
+
if text:
|
|
865
|
+
return text
|
|
866
|
+
raise CopilotError("Responses stream ended without a terminal response event.")
|
|
867
|
+
|
|
868
|
+
status = final_response.get("status")
|
|
869
|
+
if status == "failed":
|
|
870
|
+
error = final_response.get("error")
|
|
871
|
+
if isinstance(error, dict):
|
|
872
|
+
message = error.get("message")
|
|
873
|
+
code = error.get("code")
|
|
874
|
+
if isinstance(message, str) and message.strip():
|
|
875
|
+
prefix = f"{code}: " if isinstance(code, str) and code else ""
|
|
876
|
+
raise CopilotError(
|
|
877
|
+
f"Responses API request failed: {prefix}{message.strip()}"
|
|
878
|
+
)
|
|
879
|
+
raise CopilotError("Responses API request failed.")
|
|
880
|
+
|
|
881
|
+
if status == "incomplete":
|
|
882
|
+
details = final_response.get("incomplete_details")
|
|
883
|
+
reason = "unknown"
|
|
884
|
+
if isinstance(details, dict):
|
|
885
|
+
raw_reason = details.get("reason")
|
|
886
|
+
if isinstance(raw_reason, str) and raw_reason.strip():
|
|
887
|
+
reason = raw_reason.strip()
|
|
888
|
+
if text:
|
|
889
|
+
return f"{text}\n\n[Response incomplete: {reason}]"
|
|
890
|
+
raise CopilotError(f"Responses API response was incomplete: {reason}.")
|
|
891
|
+
|
|
892
|
+
if text:
|
|
893
|
+
return text
|
|
894
|
+
|
|
895
|
+
return extract_response_text(final_response)
|
|
728
896
|
|
|
729
897
|
|
|
730
898
|
def complete_text_prompt(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|