lionagi 0.12.6__py3-none-any.whl → 0.12.7__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/service/connections/api_calling.py +3 -3
- lionagi/service/connections/endpoint.py +28 -19
- lionagi/service/connections/match_endpoint.py +8 -0
- lionagi/service/connections/providers/claude_code_.py +250 -0
- lionagi/service/imodel.py +34 -5
- lionagi/version.py +1 -1
- {lionagi-0.12.6.dist-info → lionagi-0.12.7.dist-info}/METADATA +35 -2
- {lionagi-0.12.6.dist-info → lionagi-0.12.7.dist-info}/RECORD +10 -9
- {lionagi-0.12.6.dist-info → lionagi-0.12.7.dist-info}/WHEEL +0 -0
- {lionagi-0.12.6.dist-info → lionagi-0.12.7.dist-info}/licenses/LICENSE +0 -0
@@ -4,9 +4,8 @@
|
|
4
4
|
|
5
5
|
import asyncio
|
6
6
|
import logging
|
7
|
-
from typing import Any
|
8
7
|
|
9
|
-
from pydantic import
|
8
|
+
from pydantic import Field, model_validator
|
10
9
|
from typing_extensions import Self
|
11
10
|
|
12
11
|
from lionagi.protocols.generic.event import Event, EventStatus
|
@@ -164,10 +163,11 @@ class APICalling(Event):
|
|
164
163
|
try:
|
165
164
|
self.execution.status = EventStatus.PROCESSING
|
166
165
|
|
167
|
-
# Make the API call
|
166
|
+
# Make the API call with skip_payload_creation=True since payload is already prepared
|
168
167
|
response = await self.endpoint.call(
|
169
168
|
request=self.payload,
|
170
169
|
cache_control=self.cache_control,
|
170
|
+
skip_payload_creation=True,
|
171
171
|
extra_headers=self.headers if self.headers else None,
|
172
172
|
)
|
173
173
|
|
@@ -47,9 +47,7 @@ class Endpoint:
|
|
47
47
|
if isinstance(config, dict):
|
48
48
|
_config = EndpointConfig(**config, **kwargs)
|
49
49
|
elif isinstance(config, EndpointConfig):
|
50
|
-
_config = config.model_copy(
|
51
|
-
deep=True
|
52
|
-
) # Use deep copy to avoid sharing kwargs dict
|
50
|
+
_config = config.model_copy(deep=True)
|
53
51
|
_config.update(**kwargs)
|
54
52
|
else:
|
55
53
|
raise ValueError(
|
@@ -72,8 +70,6 @@ class Endpoint:
|
|
72
70
|
**self.config.client_kwargs,
|
73
71
|
)
|
74
72
|
|
75
|
-
# Removed old context manager methods - endpoint is now stateless
|
76
|
-
|
77
73
|
@property
|
78
74
|
def request_options(self):
|
79
75
|
return self.config.request_options
|
@@ -159,8 +155,17 @@ class Endpoint:
|
|
159
155
|
|
160
156
|
return (payload, headers)
|
161
157
|
|
158
|
+
async def _call(self, payload: dict, headers: dict, **kwargs):
|
159
|
+
return await self._call_aiohttp(
|
160
|
+
payload=payload, headers=headers, **kwargs
|
161
|
+
)
|
162
|
+
|
162
163
|
async def call(
|
163
|
-
self,
|
164
|
+
self,
|
165
|
+
request: dict | BaseModel,
|
166
|
+
cache_control: bool = False,
|
167
|
+
skip_payload_creation: bool = False,
|
168
|
+
**kwargs,
|
164
169
|
):
|
165
170
|
"""
|
166
171
|
Make a call to the endpoint.
|
@@ -168,6 +173,7 @@ class Endpoint:
|
|
168
173
|
Args:
|
169
174
|
request: The request parameters or model.
|
170
175
|
cache_control: Whether to use cache control.
|
176
|
+
skip_payload_creation: Whether to skip create_payload and treat request as ready payload.
|
171
177
|
**kwargs: Additional keyword arguments for the request.
|
172
178
|
|
173
179
|
Returns:
|
@@ -175,25 +181,28 @@ class Endpoint:
|
|
175
181
|
"""
|
176
182
|
# Extract extra_headers before passing to create_payload
|
177
183
|
extra_headers = kwargs.pop("extra_headers", None)
|
178
|
-
payload, headers = self.create_payload(
|
179
|
-
request, extra_headers=extra_headers, **kwargs
|
180
|
-
)
|
181
184
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
185
|
+
payload, headers = None, None
|
186
|
+
if skip_payload_creation:
|
187
|
+
# Treat request as the ready payload
|
188
|
+
payload = (
|
189
|
+
request if isinstance(request, dict) else request.model_dump()
|
190
|
+
)
|
191
|
+
headers = extra_headers or {}
|
192
|
+
else:
|
193
|
+
payload, headers = self.create_payload(
|
194
|
+
request, extra_headers=extra_headers, **kwargs
|
186
195
|
)
|
187
196
|
|
188
197
|
# Apply resilience patterns if configured
|
189
|
-
call_func = _call
|
198
|
+
call_func = self._call
|
190
199
|
|
191
200
|
# Apply retry if configured
|
192
201
|
if self.retry_config:
|
193
202
|
|
194
203
|
async def call_func(p, h, **kw):
|
195
204
|
return await retry_with_backoff(
|
196
|
-
_call, p, h, **kw, **self.retry_config.as_kwargs()
|
205
|
+
self._call, p, h, **kw, **self.retry_config.as_kwargs()
|
197
206
|
)
|
198
207
|
|
199
208
|
# Apply circuit breaker if configured
|
@@ -208,7 +217,7 @@ class Endpoint:
|
|
208
217
|
# If only circuit breaker is configured, apply it directly
|
209
218
|
if not cache_control:
|
210
219
|
return await self.circuit_breaker.execute(
|
211
|
-
_call, payload, headers, **kwargs
|
220
|
+
self._call, payload, headers, **kwargs
|
212
221
|
)
|
213
222
|
|
214
223
|
# Handle caching if requested
|
@@ -223,12 +232,12 @@ class Endpoint:
|
|
223
232
|
)
|
224
233
|
if self.circuit_breaker:
|
225
234
|
return await self.circuit_breaker.execute(
|
226
|
-
_call, payload, headers, **kwargs
|
235
|
+
self._call, payload, headers, **kwargs
|
227
236
|
)
|
228
237
|
if self.retry_config:
|
229
238
|
return await call_func(payload, headers, **kwargs)
|
230
239
|
|
231
|
-
return await _call(payload, headers, **kwargs)
|
240
|
+
return await self._call(payload, headers, **kwargs)
|
232
241
|
|
233
242
|
return await _cached_call(payload, headers, **kwargs)
|
234
243
|
|
@@ -236,7 +245,7 @@ class Endpoint:
|
|
236
245
|
if self.retry_config:
|
237
246
|
return await call_func(payload, headers, **kwargs)
|
238
247
|
|
239
|
-
return await _call(payload, headers, **kwargs)
|
248
|
+
return await self._call(payload, headers, **kwargs)
|
240
249
|
|
241
250
|
async def _call_aiohttp(self, payload: dict, headers: dict, **kwargs):
|
242
251
|
"""
|
@@ -45,5 +45,13 @@ def match_endpoint(
|
|
45
45
|
from .providers.perplexity_ import PerplexityChatEndpoint
|
46
46
|
|
47
47
|
return PerplexityChatEndpoint(**kwargs)
|
48
|
+
if provider == "claude_code" and (
|
49
|
+
"query" in endpoint or "code" in endpoint
|
50
|
+
):
|
51
|
+
from lionagi.service.connections.providers.claude_code_ import (
|
52
|
+
ClaudeCodeEndpoint,
|
53
|
+
)
|
54
|
+
|
55
|
+
return ClaudeCodeEndpoint(**kwargs)
|
48
56
|
|
49
57
|
return None
|
@@ -0,0 +1,250 @@
|
|
1
|
+
# Copyright (c) 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
|
5
|
+
import json
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import Any
|
8
|
+
|
9
|
+
from claude_code_sdk import ClaudeCodeOptions, PermissionMode
|
10
|
+
from pydantic import BaseModel, Field
|
11
|
+
|
12
|
+
from lionagi.service.connections.endpoint import Endpoint
|
13
|
+
from lionagi.service.connections.endpoint_config import EndpointConfig
|
14
|
+
from lionagi.utils import to_dict
|
15
|
+
|
16
|
+
|
17
|
+
class ClaudeCodeRequest(BaseModel):
|
18
|
+
prompt: str = Field(description="The prompt for Claude Code")
|
19
|
+
allowed_tools: list[str] = Field(
|
20
|
+
default_factory=list, description="List of allowed tools"
|
21
|
+
)
|
22
|
+
max_thinking_tokens: int = 8000
|
23
|
+
mcp_tools: list[str] = list
|
24
|
+
mcp_servers: dict[str, Any] = Field(default_factory=dict)
|
25
|
+
permission_mode: PermissionMode | None = None
|
26
|
+
continue_conversation: bool = False
|
27
|
+
resume: str | None = None
|
28
|
+
max_turns: int | None = None
|
29
|
+
disallowed_tools: list[str] = Field(default_factory=list)
|
30
|
+
model: str | None = None
|
31
|
+
permission_prompt_tool_name: str | None = None
|
32
|
+
cwd: str | Path | None = None
|
33
|
+
system_prompt: str | None = None
|
34
|
+
append_system_prompt: str | None = None
|
35
|
+
|
36
|
+
def as_claude_options(self) -> ClaudeCodeOptions:
|
37
|
+
dict_ = self.model_dump(exclude_unset=True)
|
38
|
+
dict_.pop("prompt")
|
39
|
+
return ClaudeCodeOptions(**dict_)
|
40
|
+
|
41
|
+
@classmethod
|
42
|
+
def create(
|
43
|
+
cls,
|
44
|
+
messages: list[dict],
|
45
|
+
resume: str | None = None,
|
46
|
+
continue_conversation: bool = None,
|
47
|
+
**kwargs,
|
48
|
+
):
|
49
|
+
prompt = messages[-1]["content"]
|
50
|
+
if isinstance(prompt, dict | list):
|
51
|
+
prompt = json.dumps(prompt)
|
52
|
+
|
53
|
+
# If resume is provided, set continue_conversation to True
|
54
|
+
if resume is not None and continue_conversation is None:
|
55
|
+
continue_conversation = True
|
56
|
+
|
57
|
+
dict_ = dict(
|
58
|
+
prompt=prompt,
|
59
|
+
continue_conversation=continue_conversation,
|
60
|
+
resume=resume,
|
61
|
+
)
|
62
|
+
|
63
|
+
if resume is not None or continue_conversation is not None:
|
64
|
+
if messages[0]["role"] == "system":
|
65
|
+
dict_["system_prompt"] = messages[0]["content"]
|
66
|
+
|
67
|
+
if (a := kwargs.get("system_prompt")) is not None:
|
68
|
+
dict_["append_system_prompt"] = a
|
69
|
+
|
70
|
+
if (a := kwargs.get("append_system_prompt")) is not None:
|
71
|
+
dict_.setdefault("append_system_prompt", "")
|
72
|
+
dict_["append_system_prompt"] += str(a)
|
73
|
+
|
74
|
+
dict_ = {**dict_, **kwargs}
|
75
|
+
dict_ = {k: v for k, v in dict_.items() if v is not None}
|
76
|
+
return cls(**dict_)
|
77
|
+
|
78
|
+
|
79
|
+
ENDPOINT_CONFIG = EndpointConfig(
|
80
|
+
name="claude_code",
|
81
|
+
provider="anthropic",
|
82
|
+
base_url="internal",
|
83
|
+
endpoint="query",
|
84
|
+
api_key="dummy",
|
85
|
+
request_options=ClaudeCodeRequest,
|
86
|
+
)
|
87
|
+
|
88
|
+
|
89
|
+
class ClaudeCodeEndpoint(Endpoint):
|
90
|
+
|
91
|
+
def __init__(self, config=ENDPOINT_CONFIG, **kwargs):
|
92
|
+
super().__init__(config=config, **kwargs)
|
93
|
+
|
94
|
+
def create_payload(
|
95
|
+
self,
|
96
|
+
request: dict | BaseModel,
|
97
|
+
**kwargs,
|
98
|
+
):
|
99
|
+
request_dict = to_dict(request)
|
100
|
+
# Merge stored kwargs from config, then request, then additional kwargs
|
101
|
+
request_dict = {**self.config.kwargs, **request_dict, **kwargs}
|
102
|
+
messages = request_dict.pop("messages", None)
|
103
|
+
|
104
|
+
resume = request_dict.pop("resume", None)
|
105
|
+
continue_conversation = request_dict.pop("continue_conversation", None)
|
106
|
+
|
107
|
+
request_obj = ClaudeCodeRequest.create(
|
108
|
+
messages=messages,
|
109
|
+
resume=resume,
|
110
|
+
continue_conversation=continue_conversation,
|
111
|
+
**{
|
112
|
+
k: v
|
113
|
+
for k, v in request_dict.items()
|
114
|
+
if v is not None and k in ClaudeCodeRequest.model_fields
|
115
|
+
},
|
116
|
+
)
|
117
|
+
request_options = request_obj.as_claude_options()
|
118
|
+
payload = {
|
119
|
+
"prompt": request_obj.prompt,
|
120
|
+
"options": request_options,
|
121
|
+
}
|
122
|
+
return (payload, {})
|
123
|
+
|
124
|
+
def _stream_claude_code(self, prompt: str, options: ClaudeCodeOptions):
|
125
|
+
from claude_code_sdk import query
|
126
|
+
|
127
|
+
return query(prompt=prompt, options=options)
|
128
|
+
|
129
|
+
async def stream(
|
130
|
+
self,
|
131
|
+
request: dict | BaseModel,
|
132
|
+
**kwargs,
|
133
|
+
):
|
134
|
+
async for chunk in self._stream_claude_code(**request, **kwargs):
|
135
|
+
yield chunk
|
136
|
+
|
137
|
+
def _parse_claude_code_response(self, responses: list) -> dict:
|
138
|
+
"""Parse Claude Code responses into a clean chat completions-like format.
|
139
|
+
|
140
|
+
Claude Code returns a list of messages:
|
141
|
+
- SystemMessage: initialization info
|
142
|
+
- AssistantMessage(s): actual assistant responses with content blocks
|
143
|
+
- UserMessage(s): for tool use interactions
|
144
|
+
- ResultMessage: final result with metadata
|
145
|
+
|
146
|
+
When Claude Code uses tools, the ResultMessage.result may be None.
|
147
|
+
In that case, we need to look at the tool results in UserMessages.
|
148
|
+
"""
|
149
|
+
result_message = None
|
150
|
+
model = "claude-code"
|
151
|
+
assistant_text_content = []
|
152
|
+
tool_results = []
|
153
|
+
|
154
|
+
# Process all messages
|
155
|
+
for response in responses:
|
156
|
+
class_name = response.__class__.__name__
|
157
|
+
|
158
|
+
if class_name == "SystemMessage" and hasattr(response, "data"):
|
159
|
+
model = response.data.get("model", "claude-code")
|
160
|
+
|
161
|
+
elif class_name == "AssistantMessage":
|
162
|
+
# Extract text content from assistant messages
|
163
|
+
if hasattr(response, "content") and response.content:
|
164
|
+
for block in response.content:
|
165
|
+
if hasattr(block, "text"):
|
166
|
+
assistant_text_content.append(block.text)
|
167
|
+
elif isinstance(block, dict) and "text" in block:
|
168
|
+
assistant_text_content.append(block["text"])
|
169
|
+
|
170
|
+
elif class_name == "UserMessage":
|
171
|
+
# Extract tool results from user messages
|
172
|
+
if hasattr(response, "content") and response.content:
|
173
|
+
for item in response.content:
|
174
|
+
if (
|
175
|
+
isinstance(item, dict)
|
176
|
+
and item.get("type") == "tool_result"
|
177
|
+
):
|
178
|
+
tool_results.append(item.get("content", ""))
|
179
|
+
|
180
|
+
elif class_name == "ResultMessage":
|
181
|
+
result_message = response
|
182
|
+
|
183
|
+
# Determine the final content
|
184
|
+
final_content = ""
|
185
|
+
if (
|
186
|
+
result_message
|
187
|
+
and hasattr(result_message, "result")
|
188
|
+
and result_message.result
|
189
|
+
):
|
190
|
+
# Use ResultMessage.result if available
|
191
|
+
final_content = result_message.result
|
192
|
+
elif assistant_text_content:
|
193
|
+
# Use assistant text content if available
|
194
|
+
final_content = "\n".join(assistant_text_content)
|
195
|
+
elif tool_results:
|
196
|
+
# If only tool results are available, use a generic summary
|
197
|
+
# (Claude Code typically provides its own summary after tool use)
|
198
|
+
final_content = (
|
199
|
+
"I've completed the requested task using the available tools."
|
200
|
+
)
|
201
|
+
|
202
|
+
# Build the clean chat completions response
|
203
|
+
result = {
|
204
|
+
"model": model,
|
205
|
+
"choices": [
|
206
|
+
{
|
207
|
+
"index": 0,
|
208
|
+
"message": {"role": "assistant", "content": final_content},
|
209
|
+
"finish_reason": (
|
210
|
+
"stop"
|
211
|
+
if not (
|
212
|
+
result_message
|
213
|
+
and hasattr(result_message, "is_error")
|
214
|
+
and result_message.is_error
|
215
|
+
)
|
216
|
+
else "error"
|
217
|
+
),
|
218
|
+
}
|
219
|
+
],
|
220
|
+
}
|
221
|
+
|
222
|
+
# Add usage information if available
|
223
|
+
if result_message and hasattr(result_message, "usage"):
|
224
|
+
result["usage"] = result_message.usage
|
225
|
+
|
226
|
+
# Add only essential Claude Code metadata
|
227
|
+
if result_message:
|
228
|
+
if hasattr(result_message, "cost_usd"):
|
229
|
+
result["usage"]["cost_usd"] = result_message.cost_usd
|
230
|
+
if hasattr(result_message, "session_id"):
|
231
|
+
result["session_id"] = result_message.session_id
|
232
|
+
if hasattr(result_message, "is_error"):
|
233
|
+
result["is_error"] = result_message.is_error
|
234
|
+
if hasattr(result_message, "num_turns"):
|
235
|
+
result["num_turns"] = result_message.num_turns
|
236
|
+
|
237
|
+
return result
|
238
|
+
|
239
|
+
async def _call(
|
240
|
+
self,
|
241
|
+
payload: dict,
|
242
|
+
headers: dict,
|
243
|
+
**kwargs,
|
244
|
+
):
|
245
|
+
responses = []
|
246
|
+
async for chunk in self._stream_claude_code(**payload):
|
247
|
+
responses.append(chunk)
|
248
|
+
|
249
|
+
# Parse the responses into a consistent format
|
250
|
+
return self._parse_claude_code_response(responses)
|
lionagi/service/imodel.py
CHANGED
@@ -120,6 +120,9 @@ class iModel:
|
|
120
120
|
# Use provided streaming_process_func or default to None
|
121
121
|
self.streaming_process_func = streaming_process_func
|
122
122
|
|
123
|
+
# Provider-specific metadata storage (e.g., session_id for Claude Code)
|
124
|
+
self._provider_metadata = {}
|
125
|
+
|
123
126
|
def create_api_calling(
|
124
127
|
self, include_token_usage_to_model: bool = False, **kwargs
|
125
128
|
) -> APICalling:
|
@@ -134,6 +137,15 @@ class iModel:
|
|
134
137
|
An `APICalling` instance with the constructed payload,
|
135
138
|
headers, and the selected endpoint.
|
136
139
|
"""
|
140
|
+
# For Claude Code, auto-inject session_id for resume if available and not explicitly provided
|
141
|
+
if (
|
142
|
+
self.endpoint.config.provider == "claude_code"
|
143
|
+
and "resume" not in kwargs
|
144
|
+
and "session_id" not in kwargs
|
145
|
+
and self._provider_metadata.get("session_id")
|
146
|
+
):
|
147
|
+
kwargs["resume"] = self._provider_metadata["session_id"]
|
148
|
+
|
137
149
|
# The new Endpoint.create_payload returns (payload, headers)
|
138
150
|
payload, headers = self.endpoint.create_payload(request=kwargs)
|
139
151
|
|
@@ -256,7 +268,23 @@ class iModel:
|
|
256
268
|
await self.executor.forward()
|
257
269
|
ctr += 1
|
258
270
|
await asyncio.sleep(0.1)
|
259
|
-
|
271
|
+
|
272
|
+
# Get the completed API call
|
273
|
+
completed_call = self.executor.pile.pop(api_call.id)
|
274
|
+
|
275
|
+
# Store session_id for Claude Code provider
|
276
|
+
if (
|
277
|
+
self.endpoint.config.provider == "claude_code"
|
278
|
+
and completed_call
|
279
|
+
and completed_call.response
|
280
|
+
):
|
281
|
+
response = completed_call.response
|
282
|
+
if isinstance(response, dict) and "session_id" in response:
|
283
|
+
self._provider_metadata["session_id"] = response[
|
284
|
+
"session_id"
|
285
|
+
]
|
286
|
+
|
287
|
+
return completed_call
|
260
288
|
except Exception as e:
|
261
289
|
raise ValueError(f"Failed to invoke API call: {e}")
|
262
290
|
|
@@ -300,12 +328,13 @@ class iModel:
|
|
300
328
|
def from_dict(cls, data: dict):
|
301
329
|
endpoint = Endpoint.from_dict(data.get("endpoint", {}))
|
302
330
|
|
303
|
-
e1
|
331
|
+
if e1 := match_endpoint(
|
304
332
|
provider=endpoint.config.provider,
|
305
333
|
endpoint=endpoint.config.endpoint,
|
306
|
-
)
|
307
|
-
|
308
|
-
|
334
|
+
):
|
335
|
+
e1.config = endpoint.config
|
336
|
+
else:
|
337
|
+
e1 = endpoint
|
309
338
|
|
310
339
|
return cls(
|
311
340
|
endpoint=e1,
|
lionagi/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.12.
|
1
|
+
__version__ = "0.12.7"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: lionagi
|
3
|
-
Version: 0.12.
|
3
|
+
Version: 0.12.7
|
4
4
|
Summary: An Intelligence Operating System.
|
5
5
|
Author-email: HaiyangLi <quantocean.li@gmail.com>, Liangbingyan Luo <llby_luo@outlook.com>
|
6
6
|
License: Apache License
|
@@ -225,13 +225,16 @@ Requires-Dist: backoff>=2.2.1
|
|
225
225
|
Requires-Dist: jinja2>=3.1.0
|
226
226
|
Requires-Dist: pillow>=11.0.0
|
227
227
|
Requires-Dist: pydantic-settings>=2.8.0
|
228
|
-
Requires-Dist: pydapter[pandas]>=0.3.
|
228
|
+
Requires-Dist: pydapter[pandas]>=0.3.2
|
229
229
|
Requires-Dist: python-dotenv>=1.1.0
|
230
230
|
Requires-Dist: tiktoken>=0.8.0
|
231
231
|
Requires-Dist: toml>=0.9.0
|
232
232
|
Provides-Extra: all
|
233
|
+
Requires-Dist: claude-code-sdk>=0.0.10; extra == 'all'
|
233
234
|
Requires-Dist: docling>=2.15.1; extra == 'all'
|
234
235
|
Requires-Dist: ollama>=0.5.0; extra == 'all'
|
236
|
+
Provides-Extra: claude-code
|
237
|
+
Requires-Dist: claude-code-sdk>=0.0.10; extra == 'claude-code'
|
235
238
|
Provides-Extra: docs
|
236
239
|
Requires-Dist: furo>=2024.8.6; extra == 'docs'
|
237
240
|
Requires-Dist: sphinx-autobuild>=2024.10.3; extra == 'docs'
|
@@ -389,11 +392,41 @@ analysis = await branch.communicate("Analyze these stats", imodel=sonnet)
|
|
389
392
|
|
390
393
|
Seamlessly route to different models in the same workflow.
|
391
394
|
|
395
|
+
### Claude Code Integration
|
396
|
+
|
397
|
+
LionAGI now supports Anthropic's [Claude Code SDK](https://github.com/anthropics/claude-code-sdk), enabling autonomous coding capabilities with persistent session management:
|
398
|
+
|
399
|
+
```python
|
400
|
+
from lionagi import iModel, Branch
|
401
|
+
|
402
|
+
# Create a Claude Code model
|
403
|
+
coder = iModel(
|
404
|
+
provider="claude_code",
|
405
|
+
endpoint="code",
|
406
|
+
model="claude-sonnet-4-20250514",
|
407
|
+
allowed_tools=["Write", "Read", "Edit"], # Control which tools Claude can use
|
408
|
+
)
|
409
|
+
|
410
|
+
# Start a coding session
|
411
|
+
branch = Branch(chat_model=coder)
|
412
|
+
response = await branch.chat("Create a Python function to calculate fibonacci numbers")
|
413
|
+
|
414
|
+
# Claude Code maintains session context automatically
|
415
|
+
response2 = await branch.chat("Now optimize it for performance")
|
416
|
+
```
|
417
|
+
|
418
|
+
Key features:
|
419
|
+
- **Auto-Resume Sessions**: Conversations automatically continue from where they left off
|
420
|
+
- **Tool Permissions**: Fine-grained control over which tools Claude can access
|
421
|
+
- **Streaming Support**: Real-time feedback during code generation
|
422
|
+
- **Seamless Integration**: Works with existing LionAGI workflows
|
423
|
+
|
392
424
|
### optional dependencies
|
393
425
|
|
394
426
|
```
|
395
427
|
pip install "lionagi[reader]"
|
396
428
|
pip install "lionagi[ollama]"
|
429
|
+
pip install "lionagi[claude-code]"
|
397
430
|
```
|
398
431
|
|
399
432
|
## Community & Contributing
|
@@ -6,7 +6,7 @@ lionagi/config.py,sha256=dAhDFKtaaSfn6WT9dwX9Vd4TWWs6-Su1FgYIrFgYcgc,3709
|
|
6
6
|
lionagi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
7
|
lionagi/settings.py,sha256=W52mM34E6jXF3GyqCFzVREKZrmnUqtZm_BVDsUiDI_s,1627
|
8
8
|
lionagi/utils.py,sha256=uLTJKl7aTnFXV6ehA6zwiwEB7G2nQYKsO2pZ6mqFzUk,78908
|
9
|
-
lionagi/version.py,sha256=
|
9
|
+
lionagi/version.py,sha256=WvcWmk-c-I-XXiq6oM9Y1VYwNWFe64tXpnG_RI47HhE,23
|
10
10
|
lionagi/fields/__init__.py,sha256=8oU7Vfk-fKiULFKqhM6VpJMqdZcVXPTM7twVfNDN_SQ,603
|
11
11
|
lionagi/fields/action.py,sha256=iWSApCM77jS0Oc28lb7G601Etkp-yjx5U1hfI_FQgfA,5792
|
12
12
|
lionagi/fields/base.py,sha256=5CJc7j8kTTWzXwpYzkSAFzx4BglABfx3AElIATKB7bg,3857
|
@@ -163,20 +163,21 @@ lionagi/protocols/operatives/__init__.py,sha256=5y5joOZzfFWERl75auAcNcKC3lImVJ5Z
|
|
163
163
|
lionagi/protocols/operatives/operative.py,sha256=PXEMzD6tFM5PPK9kkPaSb7DBIzy7TNC3f2evuGhWhpg,6677
|
164
164
|
lionagi/protocols/operatives/step.py,sha256=AXXRhjsbWqkoMDQ_JyqsfQItQsjBJmldugJz36mA4N0,9772
|
165
165
|
lionagi/service/__init__.py,sha256=DMGXIqPsmut9H5GT0ZeSzQIzYzzPwI-2gLXydpbwiV8,21
|
166
|
-
lionagi/service/imodel.py,sha256=
|
166
|
+
lionagi/service/imodel.py,sha256=LAlU6orAAZr7B-_3upZng8OvMVz0FadWLRawwdqfBGY,12346
|
167
167
|
lionagi/service/manager.py,sha256=9-dIE4ZftI94RLLLPXH-yB4E3zfnbTs3yppdFDPNchM,1165
|
168
168
|
lionagi/service/rate_limited_processor.py,sha256=PnO0rBf9ObKhD3vtl6pYZX3nHVDvMPdOww59zCWgslQ,5230
|
169
169
|
lionagi/service/resilience.py,sha256=uYJYZQ9M-tje8ME3vJmYabXwKHF1c3Ij4-WrdCwogcs,18742
|
170
170
|
lionagi/service/token_calculator.py,sha256=zpbk1YlFW5M_e-vs9YJyhDThMm3U3IaZ7hKm-_6XrDU,6460
|
171
171
|
lionagi/service/types.py,sha256=6zavqBxK1Fj0nB9eZgJn3JICxmdT-n0nn8YWZFzM5LU,508
|
172
172
|
lionagi/service/connections/__init__.py,sha256=yHQZ7OJpCftd6CStYR8inbxjJydYdmv9kCvbUBhJ2zU,362
|
173
|
-
lionagi/service/connections/api_calling.py,sha256=
|
174
|
-
lionagi/service/connections/endpoint.py,sha256=
|
173
|
+
lionagi/service/connections/api_calling.py,sha256=XetCrjMhOHNKGGv-NzHhBhVS7XjKPalrS_iExzU-4S4,8005
|
174
|
+
lionagi/service/connections/endpoint.py,sha256=yNIjq9wETMnytynGbq3qY_dkyaMlaHrcfiZjS-tnmLg,14756
|
175
175
|
lionagi/service/connections/endpoint_config.py,sha256=jCgMOujN5KzQ2miOrfEqKrVZW3jlZEgBp2R3_2sXynI,4380
|
176
176
|
lionagi/service/connections/header_factory.py,sha256=22sG4ian3MiNklF6SdQqkEYgtWKOZik_yDE0Lna6BiE,1754
|
177
|
-
lionagi/service/connections/match_endpoint.py,sha256=
|
177
|
+
lionagi/service/connections/match_endpoint.py,sha256=mEZPDkK1qtvjTGN9-PZsK7w_yB7642nZiJsb0l5QUx4,1827
|
178
178
|
lionagi/service/connections/providers/__init__.py,sha256=3lzOakDoBWmMaNnT2g-YwktPKa_Wme4lnPRSmOQfayY,105
|
179
179
|
lionagi/service/connections/providers/anthropic_.py,sha256=SUPnw2UqjY5wuHXLHas6snMTzhQ-UuixvPYbkVnXn34,3083
|
180
|
+
lionagi/service/connections/providers/claude_code_.py,sha256=1kCLHjc-bqjCsG6HieaJQWy6SCV6PzBZGjTneLPhCY0,8845
|
180
181
|
lionagi/service/connections/providers/exa_.py,sha256=GGWaD9jd5gKM257OfUaIBBKIqR1NrNcBE67p_7JbK7g,938
|
181
182
|
lionagi/service/connections/providers/oai_.py,sha256=rKZdGufX4vpj88rr98KqoOHzHa7HgH_pCVWjCqAq-XU,4971
|
182
183
|
lionagi/service/connections/providers/ollama_.py,sha256=jdx6dGeChwVk5TFfFRbpnrpKzj8YQZw6D5iWJ6zYmfk,4096
|
@@ -197,7 +198,7 @@ lionagi/tools/types.py,sha256=XtJLY0m-Yi_ZLWhm0KycayvqMCZd--HxfQ0x9vFUYDE,230
|
|
197
198
|
lionagi/tools/file/__init__.py,sha256=5y5joOZzfFWERl75auAcNcKC3lImVJ5ZZGvvHZUFCJM,112
|
198
199
|
lionagi/tools/file/reader.py,sha256=0TdnfVGVCKuM58MmGM-NyVjhU9BFoitkNYEepdc0z_Y,9529
|
199
200
|
lionagi/tools/memory/tools.py,sha256=zTGBenVsF8Wuh303kWntmQSGlAFKonHNdh5ePuQ26KE,15948
|
200
|
-
lionagi-0.12.
|
201
|
-
lionagi-0.12.
|
202
|
-
lionagi-0.12.
|
203
|
-
lionagi-0.12.
|
201
|
+
lionagi-0.12.7.dist-info/METADATA,sha256=6cpK8bMdHTYDG78vn9Gl09ENET3A4vYkfaot81uoAa0,20200
|
202
|
+
lionagi-0.12.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
203
|
+
lionagi-0.12.7.dist-info/licenses/LICENSE,sha256=VXFWsdoN5AAknBCgFqQNgPWYx7OPp-PFEP961zGdOjc,11288
|
204
|
+
lionagi-0.12.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|