klaude-code 2.7.0__py3-none-any.whl → 2.8.1__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.
Files changed (74) hide show
  1. klaude_code/auth/AGENTS.md +325 -0
  2. klaude_code/auth/__init__.py +17 -1
  3. klaude_code/auth/antigravity/__init__.py +20 -0
  4. klaude_code/auth/antigravity/exceptions.py +17 -0
  5. klaude_code/auth/antigravity/oauth.py +320 -0
  6. klaude_code/auth/antigravity/pkce.py +25 -0
  7. klaude_code/auth/antigravity/token_manager.py +45 -0
  8. klaude_code/auth/base.py +4 -0
  9. klaude_code/auth/claude/oauth.py +29 -9
  10. klaude_code/auth/codex/exceptions.py +4 -0
  11. klaude_code/cli/auth_cmd.py +53 -3
  12. klaude_code/cli/cost_cmd.py +83 -160
  13. klaude_code/cli/list_model.py +50 -0
  14. klaude_code/cli/main.py +2 -2
  15. klaude_code/config/assets/builtin_config.yaml +108 -0
  16. klaude_code/config/builtin_config.py +5 -11
  17. klaude_code/config/config.py +24 -10
  18. klaude_code/const.py +2 -1
  19. klaude_code/core/agent.py +5 -1
  20. klaude_code/core/agent_profile.py +29 -33
  21. klaude_code/core/compaction/AGENTS.md +112 -0
  22. klaude_code/core/compaction/__init__.py +11 -0
  23. klaude_code/core/compaction/compaction.py +705 -0
  24. klaude_code/core/compaction/overflow.py +30 -0
  25. klaude_code/core/compaction/prompts.py +97 -0
  26. klaude_code/core/executor.py +121 -2
  27. klaude_code/core/manager/llm_clients.py +5 -0
  28. klaude_code/core/manager/llm_clients_builder.py +14 -2
  29. klaude_code/core/prompts/prompt-antigravity.md +80 -0
  30. klaude_code/core/prompts/prompt-codex-gpt-5-2.md +335 -0
  31. klaude_code/core/reminders.py +7 -2
  32. klaude_code/core/task.py +126 -0
  33. klaude_code/core/tool/file/edit_tool.py +1 -2
  34. klaude_code/core/tool/todo/todo_write_tool.py +1 -1
  35. klaude_code/core/turn.py +3 -1
  36. klaude_code/llm/antigravity/__init__.py +3 -0
  37. klaude_code/llm/antigravity/client.py +558 -0
  38. klaude_code/llm/antigravity/input.py +261 -0
  39. klaude_code/llm/registry.py +1 -0
  40. klaude_code/protocol/commands.py +1 -0
  41. klaude_code/protocol/events.py +18 -0
  42. klaude_code/protocol/llm_param.py +1 -0
  43. klaude_code/protocol/message.py +23 -1
  44. klaude_code/protocol/op.py +29 -1
  45. klaude_code/protocol/op_handler.py +10 -0
  46. klaude_code/session/export.py +308 -299
  47. klaude_code/session/session.py +36 -0
  48. klaude_code/session/templates/export_session.html +430 -134
  49. klaude_code/skill/assets/create-plan/SKILL.md +6 -6
  50. klaude_code/tui/command/__init__.py +6 -0
  51. klaude_code/tui/command/compact_cmd.py +32 -0
  52. klaude_code/tui/command/continue_cmd.py +34 -0
  53. klaude_code/tui/command/fork_session_cmd.py +110 -14
  54. klaude_code/tui/command/model_picker.py +5 -1
  55. klaude_code/tui/command/thinking_cmd.py +1 -1
  56. klaude_code/tui/commands.py +6 -0
  57. klaude_code/tui/components/rich/markdown.py +119 -12
  58. klaude_code/tui/components/rich/theme.py +10 -2
  59. klaude_code/tui/components/tools.py +39 -25
  60. klaude_code/tui/components/user_input.py +1 -1
  61. klaude_code/tui/input/__init__.py +5 -2
  62. klaude_code/tui/input/drag_drop.py +6 -57
  63. klaude_code/tui/input/key_bindings.py +10 -0
  64. klaude_code/tui/input/prompt_toolkit.py +19 -6
  65. klaude_code/tui/machine.py +25 -0
  66. klaude_code/tui/renderer.py +68 -4
  67. klaude_code/tui/runner.py +18 -2
  68. klaude_code/tui/terminal/image.py +72 -10
  69. klaude_code/tui/terminal/selector.py +31 -7
  70. {klaude_code-2.7.0.dist-info → klaude_code-2.8.1.dist-info}/METADATA +1 -1
  71. {klaude_code-2.7.0.dist-info → klaude_code-2.8.1.dist-info}/RECORD +73 -56
  72. klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +0 -117
  73. {klaude_code-2.7.0.dist-info → klaude_code-2.8.1.dist-info}/WHEEL +0 -0
  74. {klaude_code-2.7.0.dist-info → klaude_code-2.8.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,261 @@
1
+ """Message conversion utilities for Antigravity Cloud Code Assist API."""
2
+
3
+ import json
4
+ from base64 import b64decode
5
+ from binascii import Error as BinasciiError
6
+ from typing import Any, TypedDict
7
+
8
+ from klaude_code.const import EMPTY_TOOL_OUTPUT_MESSAGE
9
+ from klaude_code.llm.image import assistant_image_to_data_url, parse_data_url
10
+ from klaude_code.llm.input_common import (
11
+ DeveloperAttachment,
12
+ attach_developer_messages,
13
+ merge_reminder_text,
14
+ split_thinking_parts,
15
+ )
16
+ from klaude_code.protocol import llm_param, message
17
+
18
+
19
+ class InlineData(TypedDict, total=False):
20
+ mimeType: str
21
+ data: str
22
+
23
+
24
+ class FunctionCall(TypedDict, total=False):
25
+ id: str
26
+ name: str
27
+ args: dict[str, Any]
28
+
29
+
30
+ class FunctionResponse(TypedDict, total=False):
31
+ id: str
32
+ name: str
33
+ response: dict[str, Any]
34
+ parts: list[dict[str, Any]]
35
+
36
+
37
+ class Part(TypedDict, total=False):
38
+ text: str
39
+ thought: bool
40
+ thoughtSignature: str
41
+ inlineData: InlineData
42
+ functionCall: FunctionCall
43
+ functionResponse: FunctionResponse
44
+
45
+
46
+ class Content(TypedDict):
47
+ role: str
48
+ parts: list[Part]
49
+
50
+
51
+ class FunctionDeclaration(TypedDict, total=False):
52
+ name: str
53
+ description: str
54
+ parameters: dict[str, Any]
55
+
56
+
57
+ class Tool(TypedDict):
58
+ functionDeclarations: list[FunctionDeclaration]
59
+
60
+
61
+ def _data_url_to_inline_data(url: str) -> InlineData:
62
+ """Convert data URL to inline_data dict."""
63
+ media_type, _, decoded = parse_data_url(url)
64
+ import base64
65
+
66
+ return InlineData(mimeType=media_type, data=base64.b64encode(decoded).decode("ascii"))
67
+
68
+
69
+ def _image_part_to_part(image: message.ImageURLPart) -> Part:
70
+ """Convert ImageURLPart to Part dict."""
71
+ url = image.url
72
+ if url.startswith("data:"):
73
+ return Part(inlineData=_data_url_to_inline_data(url))
74
+ # For non-data URLs, best-effort using inline_data format
75
+ return Part(text=f"[Image: {url}]")
76
+
77
+
78
+ def _user_message_to_content(msg: message.UserMessage, attachment: DeveloperAttachment) -> Content:
79
+ """Convert UserMessage to Content dict."""
80
+ parts: list[Part] = []
81
+ for part in msg.parts:
82
+ if isinstance(part, message.TextPart):
83
+ parts.append(Part(text=part.text))
84
+ elif isinstance(part, message.ImageURLPart):
85
+ parts.append(_image_part_to_part(part))
86
+ if attachment.text:
87
+ parts.append(Part(text=attachment.text))
88
+ for image in attachment.images:
89
+ parts.append(_image_part_to_part(image))
90
+ if not parts:
91
+ parts.append(Part(text=""))
92
+ return Content(role="user", parts=parts)
93
+
94
+
95
+ def _tool_messages_to_contents(
96
+ msgs: list[tuple[message.ToolResultMessage, DeveloperAttachment]], model_name: str | None
97
+ ) -> list[Content]:
98
+ """Convert tool result messages to Content dicts."""
99
+ supports_multimodal_function_response = bool(model_name and "gemini-3" in model_name.lower())
100
+
101
+ response_parts: list[Part] = []
102
+ extra_image_contents: list[Content] = []
103
+
104
+ for msg, attachment in msgs:
105
+ merged_text = merge_reminder_text(
106
+ msg.output_text or EMPTY_TOOL_OUTPUT_MESSAGE,
107
+ attachment.text,
108
+ )
109
+ has_text = merged_text.strip() != ""
110
+
111
+ images = [part for part in msg.parts if isinstance(part, message.ImageURLPart)] + attachment.images
112
+ image_parts: list[Part] = []
113
+ function_response_parts: list[dict[str, Any]] = []
114
+
115
+ for image in images:
116
+ try:
117
+ image_parts.append(_image_part_to_part(image))
118
+ if image.url.startswith("data:"):
119
+ inline_data = _data_url_to_inline_data(image.url)
120
+ function_response_parts.append({"inlineData": inline_data})
121
+ except ValueError:
122
+ continue
123
+
124
+ has_images = len(image_parts) > 0
125
+ response_value = merged_text if has_text else "(see attached image)" if has_images else ""
126
+ response_payload = {"error": response_value} if msg.status != "success" else {"output": response_value}
127
+
128
+ function_response = FunctionResponse(
129
+ id=msg.call_id,
130
+ name=msg.tool_name,
131
+ response=response_payload,
132
+ )
133
+ if has_images and supports_multimodal_function_response:
134
+ function_response["parts"] = function_response_parts
135
+
136
+ response_parts.append(Part(functionResponse=function_response))
137
+
138
+ if has_images and not supports_multimodal_function_response:
139
+ extra_image_contents.append(Content(role="user", parts=[Part(text="Tool result image:"), *image_parts]))
140
+
141
+ contents: list[Content] = []
142
+ if response_parts:
143
+ contents.append(Content(role="user", parts=response_parts))
144
+ contents.extend(extra_image_contents)
145
+ return contents
146
+
147
+
148
+ def _decode_thought_signature(sig: str | None) -> str | None:
149
+ """Validate base64 thought signature."""
150
+ if not sig:
151
+ return None
152
+ try:
153
+ b64decode(sig)
154
+ return sig
155
+ except (BinasciiError, ValueError):
156
+ return None
157
+
158
+
159
+ def _assistant_message_to_content(msg: message.AssistantMessage, model_name: str | None) -> Content | None:
160
+ """Convert AssistantMessage to Content dict."""
161
+ parts: list[Part] = []
162
+ native_thinking_parts, degraded_thinking_texts = split_thinking_parts(msg, model_name)
163
+ native_thinking_ids = {id(part) for part in native_thinking_parts}
164
+
165
+ for part in msg.parts:
166
+ if isinstance(part, message.ThinkingTextPart):
167
+ if id(part) not in native_thinking_ids:
168
+ continue
169
+ parts.append(Part(text=part.text, thought=True))
170
+
171
+ elif isinstance(part, message.ThinkingSignaturePart):
172
+ if id(part) not in native_thinking_ids:
173
+ continue
174
+ if not part.signature or part.format != "google":
175
+ continue
176
+ # Attach signature to the previous part
177
+ if parts:
178
+ sig = _decode_thought_signature(part.signature)
179
+ if sig:
180
+ parts[-1]["thoughtSignature"] = sig
181
+
182
+ elif isinstance(part, message.TextPart):
183
+ # Skip empty text blocks
184
+ if not part.text or part.text.strip() == "":
185
+ continue
186
+ parts.append(Part(text=part.text))
187
+
188
+ elif isinstance(part, message.ToolCallPart):
189
+ args: dict[str, Any]
190
+ if part.arguments_json:
191
+ try:
192
+ args = json.loads(part.arguments_json)
193
+ except json.JSONDecodeError:
194
+ args = {"_raw": part.arguments_json}
195
+ else:
196
+ args = {}
197
+ parts.append(Part(functionCall=FunctionCall(id=part.call_id, name=part.tool_name, args=args)))
198
+
199
+ elif isinstance(part, message.ImageFilePart):
200
+ try:
201
+ data_url = assistant_image_to_data_url(part)
202
+ parts.append(_image_part_to_part(message.ImageURLPart(url=data_url)))
203
+ except (ValueError, FileNotFoundError):
204
+ pass
205
+
206
+ if degraded_thinking_texts:
207
+ parts.insert(0, Part(text="<thinking>\n" + "\n".join(degraded_thinking_texts) + "\n</thinking>"))
208
+
209
+ if not parts:
210
+ return None
211
+ return Content(role="model", parts=parts)
212
+
213
+
214
+ def convert_history_to_contents(
215
+ history: list[message.Message],
216
+ model_name: str | None,
217
+ ) -> list[Content]:
218
+ """Convert message history to Cloud Code Assist Content format."""
219
+ contents: list[Content] = []
220
+ pending_tool_messages: list[tuple[message.ToolResultMessage, DeveloperAttachment]] = []
221
+
222
+ def flush_tool_messages() -> None:
223
+ nonlocal pending_tool_messages
224
+ if pending_tool_messages:
225
+ contents.extend(_tool_messages_to_contents(pending_tool_messages, model_name=model_name))
226
+ pending_tool_messages = []
227
+
228
+ for msg, attachment in attach_developer_messages(history):
229
+ match msg:
230
+ case message.ToolResultMessage():
231
+ pending_tool_messages.append((msg, attachment))
232
+ case message.UserMessage():
233
+ flush_tool_messages()
234
+ contents.append(_user_message_to_content(msg, attachment))
235
+ case message.AssistantMessage():
236
+ flush_tool_messages()
237
+ content = _assistant_message_to_content(msg, model_name=model_name)
238
+ if content is not None:
239
+ contents.append(content)
240
+ case message.SystemMessage():
241
+ continue
242
+ case _:
243
+ continue
244
+
245
+ flush_tool_messages()
246
+ return contents
247
+
248
+
249
+ def convert_tool_schema(tools: list[llm_param.ToolSchema] | None) -> list[Tool] | None:
250
+ """Convert tool schemas to Cloud Code Assist Tool format."""
251
+ if tools is None or len(tools) == 0:
252
+ return None
253
+ declarations = [
254
+ FunctionDeclaration(
255
+ name=tool.name,
256
+ description=tool.description,
257
+ parameters=dict(tool.parameters) if tool.parameters else {},
258
+ )
259
+ for tool in tools
260
+ ]
261
+ return [Tool(functionDeclarations=declarations)]
@@ -21,6 +21,7 @@ _PROTOCOL_MODULES: dict[llm_param.LLMClientProtocol, str] = {
21
21
  llm_param.LLMClientProtocol.OPENROUTER: "klaude_code.llm.openrouter",
22
22
  llm_param.LLMClientProtocol.RESPONSES: "klaude_code.llm.responses",
23
23
  llm_param.LLMClientProtocol.GOOGLE: "klaude_code.llm.google",
24
+ llm_param.LLMClientProtocol.ANTIGRAVITY: "klaude_code.llm.antigravity",
24
25
  }
25
26
 
26
27
 
@@ -27,6 +27,7 @@ class CommandName(str, Enum):
27
27
  FORK_SESSION = "fork-session"
28
28
  RESUME = "resume"
29
29
  COPY = "copy"
30
+ CONTINUE = "continue"
30
31
 
31
32
  def __str__(self) -> str:
32
33
  return self.value
@@ -14,6 +14,8 @@ __all__ = [
14
14
  "AssistantTextEndEvent",
15
15
  "AssistantTextStartEvent",
16
16
  "CommandOutputEvent",
17
+ "CompactionEndEvent",
18
+ "CompactionStartEvent",
17
19
  "DeveloperMessageEvent",
18
20
  "EndEvent",
19
21
  "ErrorEvent",
@@ -83,6 +85,20 @@ class TaskStartEvent(Event):
83
85
  model_id: str | None = None
84
86
 
85
87
 
88
+ class CompactionStartEvent(Event):
89
+ reason: Literal["threshold", "overflow", "manual"]
90
+
91
+
92
+ class CompactionEndEvent(Event):
93
+ reason: Literal["threshold", "overflow", "manual"]
94
+ aborted: bool = False
95
+ will_retry: bool = False
96
+ tokens_before: int | None = None
97
+ kept_from_index: int | None = None
98
+ summary: str | None = None
99
+ kept_items_brief: list[message.KeptItemBrief] = Field(default_factory=list) # pyright: ignore[reportUnknownVariableType]
100
+
101
+
86
102
  class TaskFinishEvent(Event):
87
103
  task_result: str
88
104
  has_structured_output: bool = False
@@ -185,6 +201,8 @@ type ReplayEventUnion = (
185
201
  | InterruptEvent
186
202
  | DeveloperMessageEvent
187
203
  | ErrorEvent
204
+ | CompactionStartEvent
205
+ | CompactionEndEvent
188
206
  )
189
207
 
190
208
 
@@ -16,6 +16,7 @@ class LLMClientProtocol(Enum):
16
16
  BEDROCK = "bedrock"
17
17
  CODEX_OAUTH = "codex_oauth"
18
18
  GOOGLE = "google"
19
+ ANTIGRAVITY = "antigravity"
19
20
 
20
21
 
21
22
  class ToolSchema(BaseModel):
@@ -63,6 +63,28 @@ class StreamErrorItem(BaseModel):
63
63
  created_at: datetime = Field(default_factory=datetime.now)
64
64
 
65
65
 
66
+ class CompactionDetails(BaseModel):
67
+ read_files: list[str] = Field(default_factory=list)
68
+ modified_files: list[str] = Field(default_factory=list)
69
+
70
+
71
+ class KeptItemBrief(BaseModel):
72
+ """Brief info about a kept (non-compacted) message item."""
73
+
74
+ item_type: str # "User", "Assistant", "Read", "Edit", "Bash", etc.
75
+ count: int = 1
76
+ preview: str = "" # Short preview text
77
+
78
+
79
+ class CompactionEntry(BaseModel):
80
+ summary: str
81
+ first_kept_index: int
82
+ tokens_before: int | None = None
83
+ details: CompactionDetails | None = None
84
+ kept_items_brief: list[KeptItemBrief] = Field(default_factory=list) # pyright: ignore[reportUnknownVariableType]
85
+ created_at: datetime = Field(default_factory=datetime.now)
86
+
87
+
66
88
  # Part types
67
89
 
68
90
 
@@ -173,7 +195,7 @@ class ToolResultMessage(MessageBase):
173
195
 
174
196
  Message = SystemMessage | DeveloperMessage | UserMessage | AssistantMessage | ToolResultMessage
175
197
 
176
- HistoryEvent = Message | StreamErrorItem | TaskMetadataItem
198
+ HistoryEvent = Message | StreamErrorItem | TaskMetadataItem | CompactionEntry
177
199
 
178
200
  StreamItem = AssistantTextDelta | AssistantImageDelta | ThinkingTextDelta | ToolCallStartDelta
179
201
 
@@ -8,7 +8,7 @@ that the executor uses to handle different types of requests.
8
8
  from __future__ import annotations
9
9
 
10
10
  from enum import Enum
11
- from typing import TYPE_CHECKING
11
+ from typing import TYPE_CHECKING, Literal
12
12
  from uuid import uuid4
13
13
 
14
14
  from pydantic import BaseModel, Field
@@ -24,6 +24,8 @@ class OperationType(Enum):
24
24
  """Enumeration of supported operation types."""
25
25
 
26
26
  RUN_AGENT = "run_agent"
27
+ CONTINUE_AGENT = "continue_agent"
28
+ COMPACT_SESSION = "compact_session"
27
29
  CHANGE_MODEL = "change_model"
28
30
  CHANGE_SUB_AGENT_MODEL = "change_sub_agent_model"
29
31
  CHANGE_THINKING = "change_thinking"
@@ -57,6 +59,32 @@ class RunAgentOperation(Operation):
57
59
  await handler.handle_run_agent(self)
58
60
 
59
61
 
62
+ class ContinueAgentOperation(Operation):
63
+ """Operation for continuing an agent task without adding a new user message.
64
+
65
+ Used for recovery after interruptions (network errors, API failures, etc.).
66
+ """
67
+
68
+ type: OperationType = OperationType.CONTINUE_AGENT
69
+ session_id: str
70
+
71
+ async def execute(self, handler: OperationHandler) -> None:
72
+ await handler.handle_continue_agent(self)
73
+
74
+
75
+ class CompactSessionOperation(Operation):
76
+ """Operation for compacting a session's conversation history."""
77
+
78
+ type: OperationType = OperationType.COMPACT_SESSION
79
+ session_id: str
80
+ reason: Literal["threshold", "overflow", "manual"]
81
+ focus: str | None = None
82
+ will_retry: bool = False
83
+
84
+ async def execute(self, handler: OperationHandler) -> None:
85
+ await handler.handle_compact_session(self)
86
+
87
+
60
88
  class ChangeModelOperation(Operation):
61
89
  """Operation for changing the model used by the active agent session."""
62
90
 
@@ -14,6 +14,8 @@ if TYPE_CHECKING:
14
14
  ChangeSubAgentModelOperation,
15
15
  ChangeThinkingOperation,
16
16
  ClearSessionOperation,
17
+ CompactSessionOperation,
18
+ ContinueAgentOperation,
17
19
  ExportSessionOperation,
18
20
  InitAgentOperation,
19
21
  InterruptOperation,
@@ -29,6 +31,14 @@ class OperationHandler(Protocol):
29
31
  """Handle a run agent operation."""
30
32
  ...
31
33
 
34
+ async def handle_continue_agent(self, operation: ContinueAgentOperation) -> None:
35
+ """Handle a continue agent operation (resume without adding user message)."""
36
+ ...
37
+
38
+ async def handle_compact_session(self, operation: CompactSessionOperation) -> None:
39
+ """Handle a compact session operation."""
40
+ ...
41
+
32
42
  async def handle_change_model(self, operation: ChangeModelOperation) -> None:
33
43
  """Handle a change model operation."""
34
44
  ...