klaude-code 1.8.0__py3-none-any.whl → 2.0.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.
- klaude_code/auth/base.py +97 -0
- klaude_code/auth/claude/__init__.py +6 -0
- klaude_code/auth/claude/exceptions.py +9 -0
- klaude_code/auth/claude/oauth.py +172 -0
- klaude_code/auth/claude/token_manager.py +26 -0
- klaude_code/auth/codex/token_manager.py +10 -50
- klaude_code/cli/auth_cmd.py +127 -46
- klaude_code/cli/config_cmd.py +4 -2
- klaude_code/cli/cost_cmd.py +14 -9
- klaude_code/cli/list_model.py +248 -200
- klaude_code/cli/main.py +1 -1
- klaude_code/cli/runtime.py +7 -5
- klaude_code/cli/self_update.py +1 -1
- klaude_code/cli/session_cmd.py +1 -1
- klaude_code/command/clear_cmd.py +6 -2
- klaude_code/command/command_abc.py +2 -2
- klaude_code/command/debug_cmd.py +4 -4
- klaude_code/command/export_cmd.py +2 -2
- klaude_code/command/export_online_cmd.py +12 -12
- klaude_code/command/fork_session_cmd.py +29 -23
- klaude_code/command/help_cmd.py +4 -4
- klaude_code/command/model_cmd.py +4 -4
- klaude_code/command/model_select.py +1 -1
- klaude_code/command/prompt-commit.md +82 -0
- klaude_code/command/prompt_command.py +3 -3
- klaude_code/command/refresh_cmd.py +2 -2
- klaude_code/command/registry.py +7 -5
- klaude_code/command/release_notes_cmd.py +4 -4
- klaude_code/command/resume_cmd.py +15 -11
- klaude_code/command/status_cmd.py +4 -4
- klaude_code/command/terminal_setup_cmd.py +8 -8
- klaude_code/command/thinking_cmd.py +4 -4
- klaude_code/config/assets/builtin_config.yaml +52 -3
- klaude_code/config/builtin_config.py +16 -5
- klaude_code/config/config.py +31 -7
- klaude_code/config/thinking.py +4 -4
- klaude_code/const.py +146 -91
- klaude_code/core/agent.py +3 -12
- klaude_code/core/executor.py +21 -13
- klaude_code/core/manager/sub_agent_manager.py +71 -7
- klaude_code/core/prompt.py +1 -1
- klaude_code/core/prompts/prompt-sub-agent-image-gen.md +1 -0
- klaude_code/core/prompts/prompt-sub-agent-web.md +27 -1
- klaude_code/core/reminders.py +88 -69
- klaude_code/core/task.py +44 -45
- klaude_code/core/tool/file/apply_patch_tool.py +9 -9
- klaude_code/core/tool/file/diff_builder.py +3 -5
- klaude_code/core/tool/file/edit_tool.py +23 -23
- klaude_code/core/tool/file/move_tool.py +43 -43
- klaude_code/core/tool/file/read_tool.py +44 -39
- klaude_code/core/tool/file/write_tool.py +14 -14
- klaude_code/core/tool/report_back_tool.py +4 -4
- klaude_code/core/tool/shell/bash_tool.py +23 -23
- klaude_code/core/tool/skill/skill_tool.py +7 -7
- klaude_code/core/tool/sub_agent_tool.py +38 -9
- klaude_code/core/tool/todo/todo_write_tool.py +8 -8
- klaude_code/core/tool/todo/update_plan_tool.py +6 -6
- klaude_code/core/tool/tool_abc.py +2 -2
- klaude_code/core/tool/tool_context.py +27 -0
- klaude_code/core/tool/tool_runner.py +88 -42
- klaude_code/core/tool/truncation.py +38 -20
- klaude_code/core/tool/web/mermaid_tool.py +6 -7
- klaude_code/core/tool/web/web_fetch_tool.py +68 -30
- klaude_code/core/tool/web/web_search_tool.py +15 -17
- klaude_code/core/turn.py +120 -73
- klaude_code/llm/anthropic/client.py +104 -44
- klaude_code/llm/anthropic/input.py +116 -108
- klaude_code/llm/bedrock/client.py +8 -5
- klaude_code/llm/claude/__init__.py +3 -0
- klaude_code/llm/claude/client.py +105 -0
- klaude_code/llm/client.py +4 -3
- klaude_code/llm/codex/client.py +16 -10
- klaude_code/llm/google/client.py +122 -60
- klaude_code/llm/google/input.py +94 -108
- klaude_code/llm/image.py +123 -0
- klaude_code/llm/input_common.py +136 -189
- klaude_code/llm/openai_compatible/client.py +17 -7
- klaude_code/llm/openai_compatible/input.py +36 -66
- klaude_code/llm/openai_compatible/stream.py +119 -67
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +23 -11
- klaude_code/llm/openrouter/client.py +34 -9
- klaude_code/llm/openrouter/input.py +63 -64
- klaude_code/llm/openrouter/reasoning.py +22 -24
- klaude_code/llm/registry.py +20 -15
- klaude_code/llm/responses/client.py +107 -45
- klaude_code/llm/responses/input.py +115 -98
- klaude_code/llm/usage.py +52 -25
- klaude_code/protocol/__init__.py +1 -0
- klaude_code/protocol/events.py +16 -12
- klaude_code/protocol/llm_param.py +22 -3
- klaude_code/protocol/message.py +250 -0
- klaude_code/protocol/model.py +94 -281
- klaude_code/protocol/op.py +2 -2
- klaude_code/protocol/sub_agent/__init__.py +2 -2
- klaude_code/protocol/sub_agent/explore.py +10 -0
- klaude_code/protocol/sub_agent/image_gen.py +119 -0
- klaude_code/protocol/sub_agent/task.py +10 -0
- klaude_code/protocol/sub_agent/web.py +10 -0
- klaude_code/session/codec.py +6 -6
- klaude_code/session/export.py +261 -62
- klaude_code/session/selector.py +7 -24
- klaude_code/session/session.py +125 -53
- klaude_code/session/store.py +5 -32
- klaude_code/session/templates/export_session.html +1 -1
- klaude_code/session/templates/mermaid_viewer.html +1 -1
- klaude_code/trace/log.py +11 -6
- klaude_code/ui/core/input.py +1 -1
- klaude_code/ui/core/stage_manager.py +1 -8
- klaude_code/ui/modes/debug/display.py +2 -2
- klaude_code/ui/modes/repl/clipboard.py +2 -2
- klaude_code/ui/modes/repl/completers.py +18 -10
- klaude_code/ui/modes/repl/event_handler.py +136 -127
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +1 -1
- klaude_code/ui/modes/repl/key_bindings.py +1 -1
- klaude_code/ui/modes/repl/renderer.py +107 -15
- klaude_code/ui/renderers/assistant.py +2 -2
- klaude_code/ui/renderers/common.py +65 -7
- klaude_code/ui/renderers/developer.py +7 -6
- klaude_code/ui/renderers/diffs.py +11 -11
- klaude_code/ui/renderers/mermaid_viewer.py +49 -2
- klaude_code/ui/renderers/metadata.py +39 -31
- klaude_code/ui/renderers/sub_agent.py +57 -16
- klaude_code/ui/renderers/thinking.py +37 -2
- klaude_code/ui/renderers/tools.py +180 -165
- klaude_code/ui/rich/live.py +3 -1
- klaude_code/ui/rich/markdown.py +39 -7
- klaude_code/ui/rich/quote.py +76 -1
- klaude_code/ui/rich/status.py +14 -8
- klaude_code/ui/rich/theme.py +13 -6
- klaude_code/ui/terminal/image.py +34 -0
- klaude_code/ui/terminal/notifier.py +2 -1
- klaude_code/ui/terminal/progress_bar.py +4 -4
- klaude_code/ui/terminal/selector.py +22 -4
- klaude_code/ui/utils/common.py +55 -0
- {klaude_code-1.8.0.dist-info → klaude_code-2.0.0.dist-info}/METADATA +28 -6
- klaude_code-2.0.0.dist-info/RECORD +229 -0
- klaude_code/command/prompt-jj-describe.md +0 -32
- klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -22
- klaude_code/protocol/sub_agent/oracle.py +0 -91
- klaude_code-1.8.0.dist-info/RECORD +0 -219
- {klaude_code-1.8.0.dist-info → klaude_code-2.0.0.dist-info}/WHEEL +0 -0
- {klaude_code-1.8.0.dist-info → klaude_code-2.0.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from pydantic import BaseModel
|
|
2
2
|
|
|
3
3
|
from klaude_code.llm.openai_compatible.stream import ReasoningDeltaResult, ReasoningHandlerABC
|
|
4
|
-
from klaude_code.protocol import
|
|
4
|
+
from klaude_code.protocol import message
|
|
5
5
|
from klaude_code.trace import log
|
|
6
6
|
|
|
7
7
|
|
|
@@ -42,7 +42,7 @@ class ReasoningStreamHandler(ReasoningHandlerABC):
|
|
|
42
42
|
if not reasoning_details:
|
|
43
43
|
return ReasoningDeltaResult(handled=False, outputs=[])
|
|
44
44
|
|
|
45
|
-
outputs: list[str |
|
|
45
|
+
outputs: list[str | message.Part] = []
|
|
46
46
|
for item in reasoning_details:
|
|
47
47
|
try:
|
|
48
48
|
reasoning_detail = ReasoningDetail.model_validate(item)
|
|
@@ -56,16 +56,16 @@ class ReasoningStreamHandler(ReasoningHandlerABC):
|
|
|
56
56
|
|
|
57
57
|
return ReasoningDeltaResult(handled=True, outputs=outputs)
|
|
58
58
|
|
|
59
|
-
def on_detail(self, detail: ReasoningDetail) -> list[
|
|
60
|
-
"""Process a single reasoning detail and return streamable
|
|
61
|
-
items: list[
|
|
59
|
+
def on_detail(self, detail: ReasoningDetail) -> list[message.Part]:
|
|
60
|
+
"""Process a single reasoning detail and return streamable parts."""
|
|
61
|
+
items: list[message.Part] = []
|
|
62
62
|
|
|
63
63
|
if detail.type == "reasoning.encrypted":
|
|
64
64
|
self._reasoning_id = detail.id
|
|
65
65
|
# Flush accumulated text before encrypted content
|
|
66
66
|
items.extend(self._flush_text())
|
|
67
|
-
if
|
|
68
|
-
items.append(
|
|
67
|
+
if signature_part := self._build_signature_part(detail.data, detail):
|
|
68
|
+
items.append(signature_part)
|
|
69
69
|
return items
|
|
70
70
|
|
|
71
71
|
if detail.type in ("reasoning.text", "reasoning.summary"):
|
|
@@ -77,42 +77,40 @@ class ReasoningStreamHandler(ReasoningHandlerABC):
|
|
|
77
77
|
# Flush on signature (encrypted content)
|
|
78
78
|
if detail.signature:
|
|
79
79
|
items.extend(self._flush_text())
|
|
80
|
-
if
|
|
81
|
-
items.append(
|
|
80
|
+
if signature_part := self._build_signature_part(detail.signature, detail):
|
|
81
|
+
items.append(signature_part)
|
|
82
82
|
|
|
83
83
|
return items
|
|
84
84
|
|
|
85
|
-
def flush(self) -> list[
|
|
85
|
+
def flush(self) -> list[message.Part]:
|
|
86
86
|
"""Flush buffered reasoning text on finalize."""
|
|
87
87
|
return self._flush_text()
|
|
88
88
|
|
|
89
|
-
def _flush_text(self) -> list[
|
|
90
|
-
"""Flush accumulated reasoning text as a single
|
|
89
|
+
def _flush_text(self) -> list[message.Part]:
|
|
90
|
+
"""Flush accumulated reasoning text as a single part."""
|
|
91
91
|
if not self._accumulated_reasoning:
|
|
92
92
|
return []
|
|
93
|
-
item = self.
|
|
93
|
+
item = self._build_text_part("".join(self._accumulated_reasoning))
|
|
94
94
|
self._accumulated_reasoning = []
|
|
95
95
|
return [item]
|
|
96
96
|
|
|
97
|
-
def
|
|
98
|
-
return
|
|
97
|
+
def _build_text_part(self, content: str) -> message.ThinkingTextPart:
|
|
98
|
+
return message.ThinkingTextPart(
|
|
99
99
|
id=self._reasoning_id,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
model=self._param_model,
|
|
100
|
+
text=content,
|
|
101
|
+
model_id=self._param_model,
|
|
103
102
|
)
|
|
104
103
|
|
|
105
|
-
def
|
|
104
|
+
def _build_signature_part(
|
|
106
105
|
self,
|
|
107
106
|
content: str | None,
|
|
108
107
|
detail: ReasoningDetail,
|
|
109
|
-
) ->
|
|
108
|
+
) -> message.ThinkingSignaturePart | None:
|
|
110
109
|
if not content:
|
|
111
110
|
return None
|
|
112
|
-
return
|
|
111
|
+
return message.ThinkingSignaturePart(
|
|
113
112
|
id=detail.id,
|
|
114
|
-
|
|
113
|
+
signature=content,
|
|
115
114
|
format=detail.format,
|
|
116
|
-
|
|
117
|
-
model=self._param_model,
|
|
115
|
+
model_id=self._param_model,
|
|
118
116
|
)
|
klaude_code/llm/registry.py
CHANGED
|
@@ -12,29 +12,34 @@ _T = TypeVar("_T", bound=type["LLMClientABC"])
|
|
|
12
12
|
# Track which protocols have been loaded
|
|
13
13
|
_loaded_protocols: set[llm_param.LLMClientProtocol] = set()
|
|
14
14
|
_REGISTRY: dict[llm_param.LLMClientProtocol, type["LLMClientABC"]] = {}
|
|
15
|
+
_PROTOCOL_MODULES: dict[llm_param.LLMClientProtocol, str] = {
|
|
16
|
+
llm_param.LLMClientProtocol.ANTHROPIC: "klaude_code.llm.anthropic",
|
|
17
|
+
llm_param.LLMClientProtocol.CLAUDE_OAUTH: "klaude_code.llm.claude",
|
|
18
|
+
llm_param.LLMClientProtocol.BEDROCK: "klaude_code.llm.bedrock",
|
|
19
|
+
llm_param.LLMClientProtocol.CODEX_OAUTH: "klaude_code.llm.codex",
|
|
20
|
+
llm_param.LLMClientProtocol.OPENAI: "klaude_code.llm.openai_compatible",
|
|
21
|
+
llm_param.LLMClientProtocol.OPENROUTER: "klaude_code.llm.openrouter",
|
|
22
|
+
llm_param.LLMClientProtocol.RESPONSES: "klaude_code.llm.responses",
|
|
23
|
+
llm_param.LLMClientProtocol.GOOGLE: "klaude_code.llm.google",
|
|
24
|
+
}
|
|
15
25
|
|
|
16
26
|
|
|
17
27
|
def _load_protocol(protocol: llm_param.LLMClientProtocol) -> None:
|
|
18
28
|
"""Load the module for a specific protocol on demand."""
|
|
19
29
|
if protocol in _loaded_protocols:
|
|
20
30
|
return
|
|
21
|
-
|
|
31
|
+
module_path = _PROTOCOL_MODULES.get(protocol)
|
|
32
|
+
if module_path is None:
|
|
33
|
+
raise ValueError(f"Unknown LLMClient protocol: {protocol}")
|
|
22
34
|
|
|
23
35
|
# Import only the needed module to trigger @register decorator
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
importlib.import_module("klaude_code.llm.openai_compatible")
|
|
32
|
-
elif protocol == llm_param.LLMClientProtocol.OPENROUTER:
|
|
33
|
-
importlib.import_module("klaude_code.llm.openrouter")
|
|
34
|
-
elif protocol == llm_param.LLMClientProtocol.RESPONSES:
|
|
35
|
-
importlib.import_module("klaude_code.llm.responses")
|
|
36
|
-
elif protocol == llm_param.LLMClientProtocol.GOOGLE:
|
|
37
|
-
importlib.import_module("klaude_code.llm.google")
|
|
36
|
+
importlib.import_module(module_path)
|
|
37
|
+
_loaded_protocols.add(protocol)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def load_protocol(protocol: llm_param.LLMClientProtocol) -> None:
|
|
41
|
+
"""Load the module for a specific protocol on demand."""
|
|
42
|
+
_load_protocol(protocol)
|
|
38
43
|
|
|
39
44
|
|
|
40
45
|
def register(name: llm_param.LLMClientProtocol) -> Callable[[_T], _T]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from collections.abc import AsyncGenerator
|
|
3
|
-
from typing import TYPE_CHECKING, override
|
|
3
|
+
from typing import TYPE_CHECKING, Literal, override
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
6
6
|
import openai
|
|
@@ -8,12 +8,13 @@ from openai import AsyncAzureOpenAI, AsyncOpenAI
|
|
|
8
8
|
from openai.types import responses
|
|
9
9
|
from openai.types.responses.response_create_params import ResponseCreateParamsStreaming
|
|
10
10
|
|
|
11
|
+
from klaude_code.const import LLM_HTTP_TIMEOUT_CONNECT, LLM_HTTP_TIMEOUT_READ, LLM_HTTP_TIMEOUT_TOTAL
|
|
11
12
|
from klaude_code.llm.client import LLMClientABC
|
|
12
13
|
from klaude_code.llm.input_common import apply_config_defaults
|
|
13
14
|
from klaude_code.llm.registry import register
|
|
14
15
|
from klaude_code.llm.responses.input import convert_history_to_input, convert_tool_schema
|
|
15
|
-
from klaude_code.llm.usage import MetadataTracker
|
|
16
|
-
from klaude_code.protocol import llm_param, model
|
|
16
|
+
from klaude_code.llm.usage import MetadataTracker, error_stream_items
|
|
17
|
+
from klaude_code.protocol import llm_param, message, model
|
|
17
18
|
from klaude_code.trace import DebugType, log_debug
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
@@ -59,9 +60,57 @@ async def parse_responses_stream(
|
|
|
59
60
|
stream: "AsyncStream[ResponseStreamEvent]",
|
|
60
61
|
param: llm_param.LLMCallParameter,
|
|
61
62
|
metadata_tracker: MetadataTracker,
|
|
62
|
-
) -> AsyncGenerator[
|
|
63
|
-
"""Parse OpenAI Responses API stream events into
|
|
63
|
+
) -> AsyncGenerator[message.LLMStreamItem]:
|
|
64
|
+
"""Parse OpenAI Responses API stream events into stream items."""
|
|
64
65
|
response_id: str | None = None
|
|
66
|
+
stage: Literal["waiting", "thinking", "assistant", "tool"] = "waiting"
|
|
67
|
+
|
|
68
|
+
accumulated_thinking: list[str] = []
|
|
69
|
+
accumulated_text: list[str] = []
|
|
70
|
+
pending_signature: str | None = None
|
|
71
|
+
assistant_parts: list[message.Part] = []
|
|
72
|
+
stop_reason: model.StopReason | None = None
|
|
73
|
+
|
|
74
|
+
def flush_thinking() -> None:
|
|
75
|
+
nonlocal pending_signature
|
|
76
|
+
if accumulated_thinking:
|
|
77
|
+
assistant_parts.append(
|
|
78
|
+
message.ThinkingTextPart(
|
|
79
|
+
text="".join(accumulated_thinking),
|
|
80
|
+
model_id=str(param.model),
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
accumulated_thinking.clear()
|
|
84
|
+
if pending_signature:
|
|
85
|
+
assistant_parts.append(
|
|
86
|
+
message.ThinkingSignaturePart(
|
|
87
|
+
signature=pending_signature,
|
|
88
|
+
model_id=str(param.model),
|
|
89
|
+
format="openai_reasoning",
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
pending_signature = None
|
|
93
|
+
|
|
94
|
+
def flush_text() -> None:
|
|
95
|
+
if not accumulated_text:
|
|
96
|
+
return
|
|
97
|
+
assistant_parts.append(message.TextPart(text="".join(accumulated_text)))
|
|
98
|
+
accumulated_text.clear()
|
|
99
|
+
|
|
100
|
+
def map_stop_reason(status: str | None, reason: str | None) -> model.StopReason | None:
|
|
101
|
+
if reason:
|
|
102
|
+
normalized = reason.strip().lower()
|
|
103
|
+
if normalized in {"max_output_tokens", "length", "max_tokens"}:
|
|
104
|
+
return "length"
|
|
105
|
+
if normalized in {"content_filter", "safety"}:
|
|
106
|
+
return "error"
|
|
107
|
+
if normalized in {"cancelled", "canceled", "aborted"}:
|
|
108
|
+
return "aborted"
|
|
109
|
+
if status == "completed":
|
|
110
|
+
return "stop"
|
|
111
|
+
if status in {"failed", "error"}:
|
|
112
|
+
return "error"
|
|
113
|
+
return None
|
|
65
114
|
|
|
66
115
|
try:
|
|
67
116
|
async for event in stream:
|
|
@@ -74,29 +123,29 @@ async def parse_responses_stream(
|
|
|
74
123
|
match event:
|
|
75
124
|
case responses.ResponseCreatedEvent() as event:
|
|
76
125
|
response_id = event.response.id
|
|
77
|
-
yield model.StartItem(response_id=response_id)
|
|
78
126
|
case responses.ResponseReasoningSummaryTextDeltaEvent() as event:
|
|
79
127
|
if event.delta:
|
|
80
128
|
metadata_tracker.record_token()
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
)
|
|
129
|
+
if stage == "assistant":
|
|
130
|
+
flush_text()
|
|
131
|
+
stage = "thinking"
|
|
132
|
+
accumulated_thinking.append(event.delta)
|
|
133
|
+
yield message.ThinkingTextDelta(content=event.delta, response_id=response_id)
|
|
85
134
|
case responses.ResponseReasoningSummaryTextDoneEvent() as event:
|
|
86
|
-
if event.text:
|
|
87
|
-
|
|
88
|
-
content=event.text,
|
|
89
|
-
response_id=response_id,
|
|
90
|
-
model=str(param.model),
|
|
91
|
-
)
|
|
135
|
+
if event.text and not accumulated_thinking:
|
|
136
|
+
accumulated_thinking.append(event.text)
|
|
92
137
|
case responses.ResponseTextDeltaEvent() as event:
|
|
93
138
|
if event.delta:
|
|
94
139
|
metadata_tracker.record_token()
|
|
95
|
-
|
|
140
|
+
if stage == "thinking":
|
|
141
|
+
flush_thinking()
|
|
142
|
+
stage = "assistant"
|
|
143
|
+
accumulated_text.append(event.delta)
|
|
144
|
+
yield message.AssistantTextDelta(content=event.delta, response_id=response_id)
|
|
96
145
|
case responses.ResponseOutputItemAddedEvent() as event:
|
|
97
146
|
if isinstance(event.item, responses.ResponseFunctionToolCall):
|
|
98
147
|
metadata_tracker.record_token()
|
|
99
|
-
yield
|
|
148
|
+
yield message.ToolCallStartItem(
|
|
100
149
|
response_id=response_id,
|
|
101
150
|
call_id=event.item.call_id,
|
|
102
151
|
name=event.item.name,
|
|
@@ -105,34 +154,30 @@ async def parse_responses_stream(
|
|
|
105
154
|
match event.item:
|
|
106
155
|
case responses.ResponseReasoningItem() as item:
|
|
107
156
|
if item.encrypted_content:
|
|
108
|
-
|
|
109
|
-
yield model.ReasoningEncryptedItem(
|
|
110
|
-
id=item.id,
|
|
111
|
-
encrypted_content=item.encrypted_content,
|
|
112
|
-
response_id=response_id,
|
|
113
|
-
model=str(param.model),
|
|
114
|
-
)
|
|
157
|
+
pending_signature = item.encrypted_content
|
|
115
158
|
case responses.ResponseOutputMessage() as item:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
content="\n".join(
|
|
159
|
+
if not accumulated_text:
|
|
160
|
+
text_content = "\n".join(
|
|
119
161
|
[
|
|
120
162
|
part.text
|
|
121
163
|
for part in item.content
|
|
122
164
|
if isinstance(part, responses.ResponseOutputText)
|
|
123
165
|
]
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
)
|
|
166
|
+
)
|
|
167
|
+
if text_content:
|
|
168
|
+
accumulated_text.append(text_content)
|
|
128
169
|
case responses.ResponseFunctionToolCall() as item:
|
|
129
170
|
metadata_tracker.record_token()
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
171
|
+
flush_thinking()
|
|
172
|
+
flush_text()
|
|
173
|
+
stage = "tool"
|
|
174
|
+
assistant_parts.append(
|
|
175
|
+
message.ToolCallPart(
|
|
176
|
+
call_id=item.call_id,
|
|
177
|
+
id=item.id,
|
|
178
|
+
tool_name=item.name,
|
|
179
|
+
arguments_json=item.arguments.strip(),
|
|
180
|
+
)
|
|
136
181
|
)
|
|
137
182
|
case _:
|
|
138
183
|
pass
|
|
@@ -154,7 +199,7 @@ async def parse_responses_stream(
|
|
|
154
199
|
)
|
|
155
200
|
metadata_tracker.set_model_name(str(param.model))
|
|
156
201
|
metadata_tracker.set_response_id(response_id)
|
|
157
|
-
|
|
202
|
+
stop_reason = map_stop_reason(event.response.status, error_reason)
|
|
158
203
|
if event.response.status != "completed":
|
|
159
204
|
error_message = f"LLM response finished with status '{event.response.status}'"
|
|
160
205
|
if error_reason:
|
|
@@ -165,7 +210,7 @@ async def parse_responses_stream(
|
|
|
165
210
|
style="red",
|
|
166
211
|
debug_type=DebugType.LLM_STREAM,
|
|
167
212
|
)
|
|
168
|
-
yield
|
|
213
|
+
yield message.StreamErrorItem(error=error_message)
|
|
169
214
|
case _:
|
|
170
215
|
log_debug(
|
|
171
216
|
"[Unhandled stream event]",
|
|
@@ -174,7 +219,18 @@ async def parse_responses_stream(
|
|
|
174
219
|
debug_type=DebugType.LLM_STREAM,
|
|
175
220
|
)
|
|
176
221
|
except (openai.OpenAIError, httpx.HTTPError) as e:
|
|
177
|
-
yield
|
|
222
|
+
yield message.StreamErrorItem(error=f"{e.__class__.__name__} {e!s}")
|
|
223
|
+
|
|
224
|
+
flush_thinking()
|
|
225
|
+
flush_text()
|
|
226
|
+
metadata_tracker.set_response_id(response_id)
|
|
227
|
+
metadata = metadata_tracker.finalize()
|
|
228
|
+
yield message.AssistantMessage(
|
|
229
|
+
parts=assistant_parts,
|
|
230
|
+
response_id=response_id,
|
|
231
|
+
usage=metadata,
|
|
232
|
+
stop_reason=stop_reason,
|
|
233
|
+
)
|
|
178
234
|
|
|
179
235
|
|
|
180
236
|
@register(llm_param.LLMClientProtocol.RESPONSES)
|
|
@@ -188,13 +244,17 @@ class ResponsesClient(LLMClientABC):
|
|
|
188
244
|
api_key=config.api_key,
|
|
189
245
|
azure_endpoint=str(config.base_url),
|
|
190
246
|
api_version=config.azure_api_version,
|
|
191
|
-
timeout=httpx.Timeout(
|
|
247
|
+
timeout=httpx.Timeout(
|
|
248
|
+
LLM_HTTP_TIMEOUT_TOTAL, connect=LLM_HTTP_TIMEOUT_CONNECT, read=LLM_HTTP_TIMEOUT_READ
|
|
249
|
+
),
|
|
192
250
|
)
|
|
193
251
|
else:
|
|
194
252
|
client = AsyncOpenAI(
|
|
195
253
|
api_key=config.api_key,
|
|
196
254
|
base_url=config.base_url,
|
|
197
|
-
timeout=httpx.Timeout(
|
|
255
|
+
timeout=httpx.Timeout(
|
|
256
|
+
LLM_HTTP_TIMEOUT_TOTAL, connect=LLM_HTTP_TIMEOUT_CONNECT, read=LLM_HTTP_TIMEOUT_READ
|
|
257
|
+
),
|
|
198
258
|
)
|
|
199
259
|
self.client: AsyncAzureOpenAI | AsyncOpenAI = client
|
|
200
260
|
|
|
@@ -204,7 +264,7 @@ class ResponsesClient(LLMClientABC):
|
|
|
204
264
|
return cls(config)
|
|
205
265
|
|
|
206
266
|
@override
|
|
207
|
-
async def call(self, param: llm_param.LLMCallParameter) -> AsyncGenerator[
|
|
267
|
+
async def call(self, param: llm_param.LLMCallParameter) -> AsyncGenerator[message.LLMStreamItem]:
|
|
208
268
|
param = apply_config_defaults(param, self.get_llm_config())
|
|
209
269
|
|
|
210
270
|
metadata_tracker = MetadataTracker(cost_config=self.get_llm_config().cost)
|
|
@@ -222,7 +282,9 @@ class ResponsesClient(LLMClientABC):
|
|
|
222
282
|
extra_headers={"extra": json.dumps({"session_id": param.session_id}, sort_keys=True)},
|
|
223
283
|
)
|
|
224
284
|
except (openai.OpenAIError, httpx.HTTPError) as e:
|
|
225
|
-
|
|
285
|
+
error_message = f"{e.__class__.__name__} {e!s}"
|
|
286
|
+
for item in error_stream_items(metadata_tracker, error=error_message):
|
|
287
|
+
yield item
|
|
226
288
|
return
|
|
227
289
|
|
|
228
290
|
async for item in parse_responses_stream(stream, param, metadata_tracker):
|