lionagi 0.17.11__py3-none-any.whl → 0.18.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.
- lionagi/libs/schema/minimal_yaml.py +98 -0
- lionagi/ln/types.py +32 -5
- lionagi/models/field_model.py +9 -0
- lionagi/operations/ReAct/ReAct.py +474 -237
- lionagi/operations/ReAct/utils.py +3 -0
- lionagi/operations/act/act.py +206 -0
- lionagi/operations/chat/chat.py +130 -114
- lionagi/operations/communicate/communicate.py +101 -42
- lionagi/operations/flow.py +4 -4
- lionagi/operations/interpret/interpret.py +65 -20
- lionagi/operations/operate/operate.py +212 -106
- lionagi/operations/parse/parse.py +170 -142
- lionagi/operations/select/select.py +78 -17
- lionagi/operations/select/utils.py +1 -1
- lionagi/operations/types.py +119 -23
- lionagi/protocols/generic/log.py +3 -2
- lionagi/protocols/messages/__init__.py +27 -0
- lionagi/protocols/messages/action_request.py +86 -184
- lionagi/protocols/messages/action_response.py +73 -131
- lionagi/protocols/messages/assistant_response.py +130 -159
- lionagi/protocols/messages/base.py +26 -18
- lionagi/protocols/messages/instruction.py +281 -625
- lionagi/protocols/messages/manager.py +112 -62
- lionagi/protocols/messages/message.py +87 -197
- lionagi/protocols/messages/system.py +52 -123
- lionagi/protocols/types.py +0 -2
- lionagi/service/connections/endpoint.py +0 -8
- lionagi/service/connections/providers/oai_.py +29 -94
- lionagi/service/connections/providers/ollama_.py +3 -2
- lionagi/service/hooks/hooked_event.py +2 -2
- lionagi/service/third_party/claude_code.py +3 -2
- lionagi/service/third_party/openai_models.py +433 -0
- lionagi/session/branch.py +170 -178
- lionagi/session/session.py +3 -9
- lionagi/tools/file/reader.py +2 -2
- lionagi/version.py +1 -1
- {lionagi-0.17.11.dist-info → lionagi-0.18.0.dist-info}/METADATA +1 -2
- {lionagi-0.17.11.dist-info → lionagi-0.18.0.dist-info}/RECORD +41 -49
- lionagi/operations/_act/act.py +0 -86
- lionagi/protocols/messages/templates/README.md +0 -28
- lionagi/protocols/messages/templates/action_request.jinja2 +0 -5
- lionagi/protocols/messages/templates/action_response.jinja2 +0 -9
- lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -6
- lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -61
- lionagi/protocols/messages/templates/system_message.jinja2 +0 -11
- lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -7
- lionagi/service/connections/providers/types.py +0 -28
- lionagi/service/third_party/openai_model_names.py +0 -198
- lionagi/service/types.py +0 -58
- /lionagi/operations/{_act → act}/__init__.py +0 -0
- {lionagi-0.17.11.dist-info → lionagi-0.18.0.dist-info}/WHEEL +0 -0
- {lionagi-0.17.11.dist-info → lionagi-0.18.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,144 +1,73 @@
|
|
1
1
|
# Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
3
3
|
|
4
|
+
from dataclasses import dataclass
|
4
5
|
from datetime import datetime
|
5
|
-
from typing import Any
|
6
|
+
from typing import Any
|
6
7
|
|
7
|
-
from pydantic import
|
8
|
-
from typing_extensions import Self, override
|
8
|
+
from pydantic import Field, field_validator
|
9
9
|
|
10
10
|
from .base import SenderRecipient
|
11
|
-
from .message import MessageRole, RoledMessage
|
11
|
+
from .message import MessageContent, MessageRole, RoledMessage
|
12
12
|
|
13
|
-
__all__ = ("System",)
|
14
13
|
|
14
|
+
@dataclass(slots=True)
|
15
|
+
class SystemContent(MessageContent):
|
16
|
+
"""Content for system messages.
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
) -> dict:
|
18
|
+
Fields:
|
19
|
+
system_message: Main system instruction text
|
20
|
+
system_datetime: Optional datetime string
|
20
21
|
"""
|
21
|
-
Insert optional datetime string into the system message content.
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
If True, embed current time. If str, use as time. If None, omit.
|
26
|
-
system_message (str):
|
27
|
-
The main system message text.
|
28
|
-
|
29
|
-
Returns:
|
30
|
-
dict: The combined system content.
|
31
|
-
"""
|
32
|
-
content: dict = {"system_message": system_message}
|
33
|
-
if system_datetime:
|
34
|
-
if isinstance(system_datetime, str):
|
35
|
-
content["system_datetime"] = system_datetime
|
36
|
-
else:
|
37
|
-
content["system_datetime"] = datetime.now().isoformat(
|
38
|
-
timespec="minutes"
|
39
|
-
)
|
40
|
-
return content
|
41
|
-
|
42
|
-
|
43
|
-
class System(RoledMessage):
|
44
|
-
"""
|
45
|
-
A specialized message that sets a *system-level* context or policy.
|
46
|
-
Usually the first in a conversation, instructing the AI about general
|
47
|
-
constraints or identity.
|
48
|
-
"""
|
49
|
-
|
50
|
-
template: str | Template | None = jinja_env.get_template(
|
51
|
-
"system_message.jinja2"
|
23
|
+
system_message: str = (
|
24
|
+
"You are a helpful AI assistant. Let's think step by step."
|
52
25
|
)
|
26
|
+
system_datetime: str | None = None
|
53
27
|
|
54
|
-
@
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
template=None,
|
63
|
-
system: Any = None,
|
64
|
-
**kwargs,
|
65
|
-
) -> Self:
|
66
|
-
"""
|
67
|
-
Construct a system message with optional datetime annotation.
|
68
|
-
|
69
|
-
Args:
|
70
|
-
system_message (str):
|
71
|
-
The main text instructing the AI about behavior/identity.
|
72
|
-
system_datetime (bool|str, optional):
|
73
|
-
If True or str, embed a time reference. If str, it is used directly.
|
74
|
-
sender (SenderRecipient, optional):
|
75
|
-
Typically `MessageRole.SYSTEM`.
|
76
|
-
recipient (SenderRecipient, optional):
|
77
|
-
Typically `MessageRole.ASSISTANT`.
|
78
|
-
template (Template|str|None):
|
79
|
-
An optional custom template for rendering.
|
80
|
-
system (Any):
|
81
|
-
Alias for `system_message` (deprecated).
|
82
|
-
**kwargs:
|
83
|
-
Additional content merged into the final dict.
|
28
|
+
@property
|
29
|
+
def rendered(self) -> str:
|
30
|
+
"""Render system message with optional datetime."""
|
31
|
+
parts = []
|
32
|
+
if self.system_datetime:
|
33
|
+
parts.append(f"System Time: {self.system_datetime}")
|
34
|
+
parts.append(self.system_message)
|
35
|
+
return "\n\n".join(parts)
|
84
36
|
|
85
|
-
|
86
|
-
|
87
|
-
"""
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
"as they are alias, and `system` is deprecated"
|
92
|
-
)
|
93
|
-
system_message = system_message or system
|
94
|
-
|
95
|
-
content = format_system_content(
|
96
|
-
system_datetime=system_datetime, system_message=system_message
|
37
|
+
@classmethod
|
38
|
+
def from_dict(cls, data: dict[str, Any]) -> "SystemContent":
|
39
|
+
"""Construct SystemContent from dictionary."""
|
40
|
+
system_message = data.get(
|
41
|
+
"system_message",
|
42
|
+
cls.__dataclass_fields__["system_message"].default,
|
97
43
|
)
|
98
|
-
|
99
|
-
params = {
|
100
|
-
"role": MessageRole.SYSTEM,
|
101
|
-
"content": content,
|
102
|
-
"sender": sender or MessageRole.SYSTEM,
|
103
|
-
"recipient": recipient or MessageRole.ASSISTANT,
|
104
|
-
}
|
105
|
-
if template:
|
106
|
-
params["template"] = template
|
107
|
-
return cls(**params)
|
44
|
+
system_datetime = data.get("system_datetime")
|
108
45
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
system_datetime: bool | str = None,
|
115
|
-
template: Template | str | None = None,
|
116
|
-
**kwargs,
|
117
|
-
) -> NoReturn:
|
118
|
-
"""
|
119
|
-
Adjust fields of this system message.
|
46
|
+
# Handle datetime generation
|
47
|
+
if system_datetime is True:
|
48
|
+
system_datetime = datetime.now().isoformat(timespec="minutes")
|
49
|
+
elif system_datetime is False or system_datetime is None:
|
50
|
+
system_datetime = None
|
120
51
|
|
121
|
-
|
122
|
-
system_message
|
123
|
-
New system message text.
|
124
|
-
sender (SenderRecipient):
|
125
|
-
Updated sender or role.
|
126
|
-
recipient (SenderRecipient):
|
127
|
-
Updated recipient or role.
|
128
|
-
system_datetime (bool|str):
|
129
|
-
If set, embed new datetime info.
|
130
|
-
template (Template|str|None):
|
131
|
-
New template override.
|
132
|
-
**kwargs:
|
133
|
-
Additional fields for self.content.
|
134
|
-
"""
|
135
|
-
if any([system_message, system_message]):
|
136
|
-
self.content = format_system_content(
|
137
|
-
system_datetime=system_datetime, system_message=system_message
|
138
|
-
)
|
139
|
-
super().update(
|
140
|
-
sender=sender, recipient=recipient, template=template, **kwargs
|
52
|
+
return cls(
|
53
|
+
system_message=system_message, system_datetime=system_datetime
|
141
54
|
)
|
142
55
|
|
143
56
|
|
144
|
-
|
57
|
+
class System(RoledMessage):
|
58
|
+
"""System-level message setting context or policy for the conversation."""
|
59
|
+
|
60
|
+
role: MessageRole = MessageRole.SYSTEM
|
61
|
+
content: SystemContent = Field(default_factory=SystemContent)
|
62
|
+
sender: SenderRecipient | None = MessageRole.SYSTEM
|
63
|
+
recipient: SenderRecipient | None = MessageRole.ASSISTANT
|
64
|
+
|
65
|
+
@field_validator("content", mode="before")
|
66
|
+
def _validate_content(cls, v):
|
67
|
+
if v is None:
|
68
|
+
return SystemContent()
|
69
|
+
if isinstance(v, dict):
|
70
|
+
return SystemContent.from_dict(v)
|
71
|
+
if isinstance(v, SystemContent):
|
72
|
+
return v
|
73
|
+
raise TypeError("content must be dict or SystemContent instance")
|
lionagi/protocols/types.py
CHANGED
@@ -25,7 +25,6 @@ from .mail.manager import MailManager
|
|
25
25
|
from .messages.base import (
|
26
26
|
MESSAGE_FIELDS,
|
27
27
|
MessageField,
|
28
|
-
MessageFlag,
|
29
28
|
MessageRole,
|
30
29
|
validate_sender_recipient,
|
31
30
|
)
|
@@ -82,7 +81,6 @@ __all__ = (
|
|
82
81
|
"PackageCategory",
|
83
82
|
"MESSAGE_FIELDS",
|
84
83
|
"MessageField",
|
85
|
-
"MessageFlag",
|
86
84
|
"MessageRole",
|
87
85
|
"validate_sender_recipient",
|
88
86
|
"ActionRequest",
|
@@ -122,10 +122,7 @@ class Endpoint:
|
|
122
122
|
# Validate the filtered payload
|
123
123
|
payload = self.config.validate_payload(filtered_payload)
|
124
124
|
else:
|
125
|
-
# If no request_options, we still need to remove obvious non-API params
|
126
|
-
# These are parameters that are never part of any API payload
|
127
125
|
non_api_params = {
|
128
|
-
"task",
|
129
126
|
"provider",
|
130
127
|
"base_url",
|
131
128
|
"endpoint",
|
@@ -133,13 +130,9 @@ class Endpoint:
|
|
133
130
|
"api_key",
|
134
131
|
"queue_capacity",
|
135
132
|
"capacity_refresh_time",
|
136
|
-
"interval",
|
137
|
-
"limit_requests",
|
138
|
-
"limit_tokens",
|
139
133
|
"invoke_with_endpoint",
|
140
134
|
"extra_headers",
|
141
135
|
"headers",
|
142
|
-
"cache_control",
|
143
136
|
"include_token_usage_to_model",
|
144
137
|
"chat_model",
|
145
138
|
"imodel",
|
@@ -148,7 +141,6 @@ class Endpoint:
|
|
148
141
|
"aggregation_count",
|
149
142
|
"action_strategy",
|
150
143
|
"parse_model",
|
151
|
-
"reason",
|
152
144
|
"actions",
|
153
145
|
"return_operative",
|
154
146
|
"operative_model",
|
@@ -18,45 +18,21 @@ from pydantic import BaseModel
|
|
18
18
|
from lionagi.config import settings
|
19
19
|
from lionagi.service.connections.endpoint import Endpoint
|
20
20
|
from lionagi.service.connections.endpoint_config import EndpointConfig
|
21
|
-
from lionagi.service.third_party.
|
22
|
-
|
23
|
-
is_reasoning_model,
|
24
|
-
)
|
25
|
-
|
26
|
-
__all__ = (
|
27
|
-
"OpenaiChatEndpoint",
|
28
|
-
"OpenaiResponseEndpoint",
|
29
|
-
"OpenaiEmbedEndpoint",
|
30
|
-
"OpenrouterChatEndpoint",
|
31
|
-
"GroqChatEndpoint",
|
32
|
-
"OPENAI_CHAT_ENDPOINT_CONFIG",
|
33
|
-
"OPENAI_RESPONSE_ENDPOINT_CONFIG",
|
34
|
-
"OPENAI_EMBEDDING_ENDPOINT_CONFIG",
|
35
|
-
"OPENROUTER_CHAT_ENDPOINT_CONFIG",
|
36
|
-
"OPENROUTER_GEMINI_ENDPOINT_CONFIG",
|
37
|
-
"GROQ_CHAT_ENDPOINT_CONFIG",
|
38
|
-
"REASONING_MODELS",
|
39
|
-
"REASONING_NOT_SUPPORT_PARAMS",
|
21
|
+
from lionagi.service.third_party.openai_models import (
|
22
|
+
OpenAIChatCompletionsRequest,
|
40
23
|
)
|
41
24
|
|
42
25
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
content_type="application/json",
|
54
|
-
method="POST",
|
55
|
-
requires_tokens=True,
|
56
|
-
# NOTE: OpenAI models have incorrect role literals, only use for param validation
|
57
|
-
# request_options=CreateChatCompletionRequest,
|
58
|
-
)
|
59
|
-
config.update(kwargs)
|
26
|
+
def _get_oai_config(**kw):
|
27
|
+
config = {
|
28
|
+
"provider": "openai",
|
29
|
+
"base_url": "https://api.openai.com/v1",
|
30
|
+
"auth_type": "bearer",
|
31
|
+
"content_type": "application/json",
|
32
|
+
"method": "POST",
|
33
|
+
"api_key": settings.OPENAI_API_KEY or "dummy-key-for-testing",
|
34
|
+
}
|
35
|
+
config.update(kw)
|
60
36
|
return EndpointConfig(**config)
|
61
37
|
|
62
38
|
|
@@ -72,8 +48,7 @@ def _get_openrouter_config(**kwargs):
|
|
72
48
|
auth_type="bearer",
|
73
49
|
content_type="application/json",
|
74
50
|
method="POST",
|
75
|
-
|
76
|
-
# request_options=CreateChatCompletionRequest,
|
51
|
+
request_options=OpenAIChatCompletionsRequest,
|
77
52
|
)
|
78
53
|
config.update(kwargs)
|
79
54
|
return EndpointConfig(**config)
|
@@ -91,48 +66,21 @@ def _get_groq_config(**kwargs):
|
|
91
66
|
auth_type="bearer",
|
92
67
|
content_type="application/json",
|
93
68
|
method="POST",
|
69
|
+
request_options=OpenAIChatCompletionsRequest,
|
94
70
|
)
|
95
71
|
config.update(kwargs)
|
96
72
|
return EndpointConfig(**config)
|
97
73
|
|
98
74
|
|
99
|
-
# OpenAI endpoints
|
100
|
-
OPENAI_CHAT_ENDPOINT_CONFIG = _get_openai_config()
|
101
|
-
|
102
|
-
OPENAI_RESPONSE_ENDPOINT_CONFIG = _get_openai_config(
|
103
|
-
name="openai_response",
|
104
|
-
endpoint="responses",
|
105
|
-
)
|
106
|
-
|
107
|
-
OPENAI_EMBEDDING_ENDPOINT_CONFIG = _get_openai_config(
|
108
|
-
name="openai_embed",
|
109
|
-
endpoint="embeddings",
|
110
|
-
kwargs={"model": "text-embedding-3-small"},
|
111
|
-
)
|
112
|
-
|
113
|
-
# OpenRouter endpoints
|
114
|
-
OPENROUTER_CHAT_ENDPOINT_CONFIG = _get_openrouter_config()
|
115
|
-
|
116
|
-
OPENROUTER_GEMINI_ENDPOINT_CONFIG = _get_openrouter_config(
|
117
|
-
name="openrouter_gemini",
|
118
|
-
kwargs={"model": "google/gemini-2.5-flash"},
|
119
|
-
)
|
120
|
-
|
121
|
-
# Groq endpoints
|
122
|
-
GROQ_CHAT_ENDPOINT_CONFIG = _get_groq_config()
|
123
|
-
|
124
|
-
REASONING_NOT_SUPPORT_PARAMS = (
|
125
|
-
"temperature",
|
126
|
-
"top_p",
|
127
|
-
"logit_bias",
|
128
|
-
"logprobs",
|
129
|
-
"top_logprobs",
|
130
|
-
)
|
131
|
-
|
132
|
-
|
133
75
|
class OpenaiChatEndpoint(Endpoint):
|
134
76
|
def __init__(self, config=None, **kwargs):
|
135
|
-
config = config or
|
77
|
+
config = config or _get_oai_config(
|
78
|
+
name="oai_chat",
|
79
|
+
endpoint="chat/completions",
|
80
|
+
request_options=OpenAIChatCompletionsRequest,
|
81
|
+
kwargs={"model": settings.OPENAI_DEFAULT_MODEL},
|
82
|
+
requires_tokens=True,
|
83
|
+
)
|
136
84
|
super().__init__(config, **kwargs)
|
137
85
|
|
138
86
|
def create_payload(
|
@@ -145,34 +93,20 @@ class OpenaiChatEndpoint(Endpoint):
|
|
145
93
|
payload, headers = super().create_payload(
|
146
94
|
request, extra_headers, **kwargs
|
147
95
|
)
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
model
|
153
|
-
and is_reasoning_model(model)
|
154
|
-
and not model.startswith("gpt-5")
|
155
|
-
):
|
156
|
-
# Remove unsupported parameters for reasoning models
|
157
|
-
for param in REASONING_NOT_SUPPORT_PARAMS:
|
158
|
-
payload.pop(param, None)
|
159
|
-
|
160
|
-
# Convert system role to developer role for reasoning models
|
161
|
-
if "messages" in payload and payload["messages"]:
|
162
|
-
if payload["messages"][0].get("role") == "system":
|
163
|
-
payload["messages"][0]["role"] = "developer"
|
164
|
-
else:
|
165
|
-
# Remove reasoning_effort for non-reasoning models
|
166
|
-
payload.pop("reasoning_effort", None)
|
96
|
+
# Convert system role to developer role for reasoning models
|
97
|
+
if "messages" in payload and payload["messages"]:
|
98
|
+
if payload["messages"][0].get("role") == "system":
|
99
|
+
payload["messages"][0]["role"] = "developer"
|
167
100
|
|
168
101
|
return (payload, headers)
|
169
102
|
|
170
103
|
|
171
104
|
class OpenaiResponseEndpoint(Endpoint):
|
172
105
|
def __init__(self, config=None, **kwargs):
|
173
|
-
config = config or
|
106
|
+
config = config or _get_oai_config(
|
174
107
|
name="openai_response",
|
175
108
|
endpoint="responses",
|
109
|
+
requires_tokens=True,
|
176
110
|
)
|
177
111
|
super().__init__(config, **kwargs)
|
178
112
|
|
@@ -191,9 +125,10 @@ class GroqChatEndpoint(Endpoint):
|
|
191
125
|
|
192
126
|
class OpenaiEmbedEndpoint(Endpoint):
|
193
127
|
def __init__(self, config=None, **kwargs):
|
194
|
-
config = config or
|
128
|
+
config = config or _get_oai_config(
|
195
129
|
name="openai_embed",
|
196
130
|
endpoint="embeddings",
|
197
131
|
kwargs={"model": "text-embedding-3-small"},
|
132
|
+
requires_tokens=True,
|
198
133
|
)
|
199
134
|
super().__init__(config, **kwargs)
|
@@ -24,6 +24,8 @@ _HAS_OLLAMA = is_import_installed("ollama")
|
|
24
24
|
|
25
25
|
def _get_ollama_config(**kwargs):
|
26
26
|
"""Create Ollama endpoint configuration with defaults."""
|
27
|
+
from ...third_party.openai_models import OpenAIChatCompletionsRequest
|
28
|
+
|
27
29
|
config = dict(
|
28
30
|
name="ollama_chat",
|
29
31
|
provider="ollama",
|
@@ -36,8 +38,7 @@ def _get_ollama_config(**kwargs):
|
|
36
38
|
content_type="application/json",
|
37
39
|
auth_type="none", # No authentication
|
38
40
|
default_headers={}, # No auth headers needed
|
39
|
-
|
40
|
-
# request_options=CreateChatCompletionRequest,
|
41
|
+
request_options=OpenAIChatCompletionsRequest,
|
41
42
|
)
|
42
43
|
config.update(kwargs)
|
43
44
|
return EndpointConfig(**config)
|
@@ -47,7 +47,7 @@ class HookedEvent(Event):
|
|
47
47
|
raise h_ev._exit_cause or RuntimeError(
|
48
48
|
"Pre-invocation hook requested exit without a cause"
|
49
49
|
)
|
50
|
-
await global_hook_logger.alog(
|
50
|
+
await global_hook_logger.alog(h_ev)
|
51
51
|
|
52
52
|
response = await self._invoke()
|
53
53
|
|
@@ -57,7 +57,7 @@ class HookedEvent(Event):
|
|
57
57
|
raise h_ev._exit_cause or RuntimeError(
|
58
58
|
"Post-invocation hook requested exit without a cause"
|
59
59
|
)
|
60
|
-
await global_hook_logger.alog(
|
60
|
+
await global_hook_logger.alog(h_ev)
|
61
61
|
|
62
62
|
self.execution.response = response
|
63
63
|
self.execution.status = EventStatus.COMPLETED
|
@@ -18,12 +18,11 @@ from pathlib import Path
|
|
18
18
|
from textwrap import shorten
|
19
19
|
from typing import Any, Literal
|
20
20
|
|
21
|
-
from json_repair import repair_json
|
22
21
|
from pydantic import BaseModel, Field, field_validator, model_validator
|
23
22
|
|
24
23
|
from lionagi import ln
|
25
24
|
from lionagi.libs.schema.as_readable import as_readable
|
26
|
-
from lionagi.utils import is_coro_func
|
25
|
+
from lionagi.utils import is_coro_func
|
27
26
|
|
28
27
|
HAS_CLAUDE_CODE_CLI = False
|
29
28
|
CLAUDE_CLI = None
|
@@ -415,6 +414,8 @@ async def _ndjson_from_cli(request: ClaudeCodeRequest):
|
|
415
414
|
• Robust against braces inside strings (uses json.JSONDecoder.raw_decode)
|
416
415
|
• Falls back to `json_repair.repair_json` when necessary.
|
417
416
|
"""
|
417
|
+
from json_repair import repair_json
|
418
|
+
|
418
419
|
workspace = request.cwd()
|
419
420
|
workspace.mkdir(parents=True, exist_ok=True)
|
420
421
|
|