mycode-sdk 0.9.0__tar.gz → 0.9.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.
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/PKG-INFO +6 -6
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/pyproject.toml +6 -6
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/__init__.py +1 -8
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/agent.py +79 -87
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/attachments.py +3 -2
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/models_catalog.json +56 -112
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/providers/anthropic_like.py +47 -49
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/providers/base.py +6 -0
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/providers/gemini.py +2 -3
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/providers/openai_chat.py +15 -5
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/providers/openai_responses.py +4 -3
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/session.py +11 -10
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/tools.py +10 -12
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/.gitignore +0 -0
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/LICENSE +0 -0
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/README.md +0 -0
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/compact.py +0 -0
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/hooks.py +0 -0
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/messages.py +0 -0
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/models.py +0 -0
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/providers/__init__.py +0 -0
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/py.typed +0 -0
- {mycode_sdk-0.9.0 → mycode_sdk-0.9.2}/src/mycode/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mycode-sdk
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.2
|
|
4
4
|
Summary: Lightweight Python SDK for building AI agents.
|
|
5
5
|
Project-URL: Homepage, https://github.com/legibet/mycode
|
|
6
6
|
Project-URL: Repository, https://github.com/legibet/mycode
|
|
@@ -18,11 +18,11 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
19
|
Classifier: Topic :: Software Development
|
|
20
20
|
Requires-Python: >=3.12
|
|
21
|
-
Requires-Dist: anthropic>=0.
|
|
22
|
-
Requires-Dist: google-genai>=2.
|
|
23
|
-
Requires-Dist: griffelib>=2.0.
|
|
24
|
-
Requires-Dist: openai>=2.
|
|
25
|
-
Requires-Dist: pydantic>=2.13.
|
|
21
|
+
Requires-Dist: anthropic>=0.106.0
|
|
22
|
+
Requires-Dist: google-genai>=2.8.0
|
|
23
|
+
Requires-Dist: griffelib>=2.0.0
|
|
24
|
+
Requires-Dist: openai>=2.41.0
|
|
25
|
+
Requires-Dist: pydantic>=2.13.0
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
|
|
28
28
|
# mycode-sdk
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mycode-sdk"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.2"
|
|
8
8
|
description = "Lightweight Python SDK for building AI agents."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -23,11 +23,11 @@ classifiers = [
|
|
|
23
23
|
]
|
|
24
24
|
keywords = ["agent", "llm", "anthropic", "openai", "gemini", "sdk"]
|
|
25
25
|
dependencies = [
|
|
26
|
-
"anthropic>=0.
|
|
27
|
-
"google-genai>=2.
|
|
28
|
-
"griffelib>=2.0.
|
|
29
|
-
"openai>=2.
|
|
30
|
-
"pydantic>=2.13.
|
|
26
|
+
"anthropic>=0.106.0",
|
|
27
|
+
"google-genai>=2.8.0",
|
|
28
|
+
"griffelib>=2.0.0",
|
|
29
|
+
"openai>=2.41.0",
|
|
30
|
+
"pydantic>=2.13.0",
|
|
31
31
|
]
|
|
32
32
|
|
|
33
33
|
[project.urls]
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
"""mycode — multi-turn tool-calling agent runtime.
|
|
2
|
-
|
|
3
|
-
Public API for embedding the agent loop in other Python applications. The
|
|
4
|
-
runtime ships four built-in coding tools (``read``, ``write``, ``edit``,
|
|
5
|
-
``bash``) exposed as :data:`read_tool`, :data:`write_tool`, :data:`edit_tool`,
|
|
6
|
-
:data:`bash_tool` — pick the ones you want via ``tools=[...]`` rather than
|
|
7
|
-
silently exposing file system and shell access.
|
|
8
|
-
"""
|
|
1
|
+
"""mycode — multi-turn tool-calling agent runtime."""
|
|
9
2
|
|
|
10
3
|
from importlib import metadata
|
|
11
4
|
|
|
@@ -13,6 +13,7 @@ import os
|
|
|
13
13
|
import tempfile
|
|
14
14
|
import time
|
|
15
15
|
from collections.abc import AsyncIterator, Awaitable, Callable, Sequence
|
|
16
|
+
from contextlib import suppress
|
|
16
17
|
from dataclasses import dataclass, field
|
|
17
18
|
from pathlib import Path
|
|
18
19
|
from typing import Any, cast
|
|
@@ -232,18 +233,12 @@ class Agent:
|
|
|
232
233
|
yield Event("tool_start", {"tool_call": {"id": tool_id, "name": name, "input": args}})
|
|
233
234
|
|
|
234
235
|
if self._cancel_event.is_set():
|
|
235
|
-
yield self.
|
|
236
|
-
tool_id,
|
|
237
|
-
ToolExecutionResult(output="error: cancelled", is_error=True),
|
|
238
|
-
)
|
|
236
|
+
yield self._error_done(tool_id, "error: cancelled")
|
|
239
237
|
return
|
|
240
238
|
|
|
241
239
|
spec = self.tools.get(name)
|
|
242
240
|
if spec is None:
|
|
243
|
-
yield self.
|
|
244
|
-
tool_id,
|
|
245
|
-
ToolExecutionResult(output=f"error: unknown tool: {name}", is_error=True),
|
|
246
|
-
)
|
|
241
|
+
yield self._error_done(tool_id, f"error: unknown tool: {name}")
|
|
247
242
|
return
|
|
248
243
|
|
|
249
244
|
hook_ctx = ToolHookContext(
|
|
@@ -259,10 +254,7 @@ class Agent:
|
|
|
259
254
|
try:
|
|
260
255
|
result = await self.hooks.run_before_tool(hook_ctx)
|
|
261
256
|
except Exception as exc:
|
|
262
|
-
yield self.
|
|
263
|
-
tool_id,
|
|
264
|
-
ToolExecutionResult(output=f"error: tool hook failed: {exc}", is_error=True),
|
|
265
|
-
)
|
|
257
|
+
yield self._error_done(tool_id, f"error: tool hook failed: {exc}")
|
|
266
258
|
return
|
|
267
259
|
|
|
268
260
|
if result is not None:
|
|
@@ -270,10 +262,7 @@ class Agent:
|
|
|
270
262
|
return
|
|
271
263
|
|
|
272
264
|
if self._cancel_event.is_set():
|
|
273
|
-
yield self.
|
|
274
|
-
tool_id,
|
|
275
|
-
ToolExecutionResult(output="error: cancelled", is_error=True),
|
|
276
|
-
)
|
|
265
|
+
yield self._error_done(tool_id, "error: cancelled")
|
|
277
266
|
return
|
|
278
267
|
|
|
279
268
|
if spec.streams_output:
|
|
@@ -336,13 +325,10 @@ class Agent:
|
|
|
336
325
|
yield Event("tool_output", {"tool_use_id": tool_id, "output": output})
|
|
337
326
|
|
|
338
327
|
if was_cancelled:
|
|
339
|
-
|
|
328
|
+
with suppress(Exception):
|
|
340
329
|
await task
|
|
341
|
-
except Exception:
|
|
342
|
-
pass
|
|
343
330
|
output = "\n".join([*output_parts, "error: cancelled"]) if output_parts else "error: cancelled"
|
|
344
|
-
|
|
345
|
-
yield self._tool_done_event(tool_id, result)
|
|
331
|
+
yield self._error_done(tool_id, output)
|
|
346
332
|
return
|
|
347
333
|
else:
|
|
348
334
|
try:
|
|
@@ -398,6 +384,11 @@ class Agent:
|
|
|
398
384
|
data["content"] = result.content
|
|
399
385
|
return Event("tool_done", data)
|
|
400
386
|
|
|
387
|
+
def _error_done(self, tool_id: str, message: str) -> Event:
|
|
388
|
+
"""Build a tool_done event carrying an error result."""
|
|
389
|
+
|
|
390
|
+
return self._tool_done_event(tool_id, ToolExecutionResult(output=message, is_error=True))
|
|
391
|
+
|
|
401
392
|
# ------------------------------------------------------------------
|
|
402
393
|
# Provider streaming
|
|
403
394
|
# ------------------------------------------------------------------
|
|
@@ -429,10 +420,52 @@ class Agent:
|
|
|
429
420
|
finally:
|
|
430
421
|
close = cast(Callable[[], Awaitable[None]] | None, getattr(provider_stream, "aclose", None))
|
|
431
422
|
if close is not None:
|
|
432
|
-
|
|
423
|
+
with suppress(Exception):
|
|
433
424
|
await close()
|
|
434
|
-
|
|
435
|
-
|
|
425
|
+
|
|
426
|
+
def _build_request(
|
|
427
|
+
self,
|
|
428
|
+
*,
|
|
429
|
+
tools: list[dict[str, Any]] | None = None,
|
|
430
|
+
max_tokens: int | None = None,
|
|
431
|
+
reasoning_effort: str | None = None,
|
|
432
|
+
append_messages: Sequence[ConversationMessage] = (),
|
|
433
|
+
) -> ProviderRequest:
|
|
434
|
+
"""Build a ProviderRequest from the agent's current state."""
|
|
435
|
+
|
|
436
|
+
return ProviderRequest(
|
|
437
|
+
provider=self.provider,
|
|
438
|
+
model=self.model,
|
|
439
|
+
session_id=self.session_id,
|
|
440
|
+
messages=self.messages,
|
|
441
|
+
system=self.system,
|
|
442
|
+
tools=self.tools.definitions if tools is None else tools,
|
|
443
|
+
max_tokens=self.max_tokens if max_tokens is None else max_tokens,
|
|
444
|
+
temperature=self.temperature,
|
|
445
|
+
api_key=self.api_key,
|
|
446
|
+
api_base=self.api_base,
|
|
447
|
+
reasoning_effort=reasoning_effort,
|
|
448
|
+
supports_image_input=self.supports_image_input,
|
|
449
|
+
supports_pdf_input=self.supports_pdf_input,
|
|
450
|
+
transcript_path=self.transcript_path,
|
|
451
|
+
append_messages=list(append_messages),
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
@staticmethod
|
|
455
|
+
def _elapsed_ms(start: float) -> int:
|
|
456
|
+
return max(0, int((time.monotonic() - start) * 1000))
|
|
457
|
+
|
|
458
|
+
@staticmethod
|
|
459
|
+
def _stamp_thinking_duration(content: list[Any], duration_ms: int) -> None:
|
|
460
|
+
"""Merge duration_ms onto the last thinking block in content, if any."""
|
|
461
|
+
|
|
462
|
+
for block in reversed(content):
|
|
463
|
+
if not isinstance(block, dict) or block.get("type") != "thinking":
|
|
464
|
+
continue
|
|
465
|
+
raw_meta = block.get("meta")
|
|
466
|
+
meta = raw_meta if isinstance(raw_meta, dict) else {}
|
|
467
|
+
block["meta"] = {**meta, "duration_ms": duration_ms}
|
|
468
|
+
return
|
|
436
469
|
|
|
437
470
|
# ------------------------------------------------------------------
|
|
438
471
|
# Public entry points
|
|
@@ -460,8 +493,9 @@ class Agent:
|
|
|
460
493
|
|
|
461
494
|
self._cancel_event.clear()
|
|
462
495
|
|
|
496
|
+
user_message: ConversationMessage
|
|
463
497
|
if isinstance(user_input, str):
|
|
464
|
-
user_message
|
|
498
|
+
user_message = user_text_message(user_input)
|
|
465
499
|
else:
|
|
466
500
|
if (user_input.get("role") or "user") != "user":
|
|
467
501
|
yield Event("error", {"message": "user input must be a user message"})
|
|
@@ -479,16 +513,15 @@ class Agent:
|
|
|
479
513
|
user_message["content"] = list(user_message.get("content") or []) + blocks
|
|
480
514
|
|
|
481
515
|
content_blocks = user_message.get("content") or []
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
yield Event("error", {"message": "current model does not support image input"})
|
|
486
|
-
return
|
|
487
|
-
if not self.supports_pdf_input and any(
|
|
488
|
-
isinstance(block, dict) and block.get("type") == "document" for block in content_blocks
|
|
516
|
+
for block_type, supported, label in (
|
|
517
|
+
("image", self.supports_image_input, "image input"),
|
|
518
|
+
("document", self.supports_pdf_input, "PDF input"),
|
|
489
519
|
):
|
|
490
|
-
|
|
491
|
-
|
|
520
|
+
if not supported and any(
|
|
521
|
+
isinstance(block, dict) and block.get("type") == block_type for block in content_blocks
|
|
522
|
+
):
|
|
523
|
+
yield Event("error", {"message": f"current model does not support {label}"})
|
|
524
|
+
return
|
|
492
525
|
|
|
493
526
|
self.messages.append(user_message)
|
|
494
527
|
await persist(user_message)
|
|
@@ -507,22 +540,7 @@ class Agent:
|
|
|
507
540
|
thinking_started_at: float | None = None
|
|
508
541
|
thinking_duration_ms: int | None = None
|
|
509
542
|
provider_cancelled = False
|
|
510
|
-
request =
|
|
511
|
-
provider=self.provider,
|
|
512
|
-
model=self.model,
|
|
513
|
-
session_id=self.session_id,
|
|
514
|
-
messages=self.messages,
|
|
515
|
-
system=self.system,
|
|
516
|
-
tools=self.tools.definitions,
|
|
517
|
-
max_tokens=self.max_tokens,
|
|
518
|
-
temperature=self.temperature,
|
|
519
|
-
api_key=self.api_key,
|
|
520
|
-
api_base=self.api_base,
|
|
521
|
-
reasoning_effort=self.reasoning_effort,
|
|
522
|
-
supports_image_input=self.supports_image_input,
|
|
523
|
-
supports_pdf_input=self.supports_pdf_input,
|
|
524
|
-
transcript_path=self.transcript_path,
|
|
525
|
-
)
|
|
543
|
+
request = self._build_request(reasoning_effort=self.reasoning_effort)
|
|
526
544
|
|
|
527
545
|
try:
|
|
528
546
|
async for provider_event in self._stream_provider_turn(adapter, request):
|
|
@@ -546,7 +564,7 @@ class Agent:
|
|
|
546
564
|
delta_text = str(provider_event.data.get("text") or "")
|
|
547
565
|
if delta_text:
|
|
548
566
|
if thinking_started_at is not None and thinking_duration_ms is None:
|
|
549
|
-
thinking_duration_ms =
|
|
567
|
+
thinking_duration_ms = self._elapsed_ms(thinking_started_at)
|
|
550
568
|
yield Event("reasoning_done", {"duration_ms": thinking_duration_ms})
|
|
551
569
|
if partial_content and partial_content[-1].get("type") == "text":
|
|
552
570
|
partial_content[-1]["text"] = f"{partial_content[-1].get('text') or ''}{delta_text}"
|
|
@@ -562,7 +580,7 @@ class Agent:
|
|
|
562
580
|
continue
|
|
563
581
|
|
|
564
582
|
if thinking_started_at is not None and thinking_duration_ms is None:
|
|
565
|
-
thinking_duration_ms =
|
|
583
|
+
thinking_duration_ms = self._elapsed_ms(thinking_started_at)
|
|
566
584
|
yield Event("reasoning_done", {"duration_ms": thinking_duration_ms})
|
|
567
585
|
|
|
568
586
|
message = provider_event.data.get("message")
|
|
@@ -579,15 +597,9 @@ class Agent:
|
|
|
579
597
|
if provider_cancelled:
|
|
580
598
|
if partial_content:
|
|
581
599
|
if thinking_started_at is not None and thinking_duration_ms is None:
|
|
582
|
-
thinking_duration_ms =
|
|
600
|
+
thinking_duration_ms = self._elapsed_ms(thinking_started_at)
|
|
583
601
|
if thinking_duration_ms is not None:
|
|
584
|
-
|
|
585
|
-
if block.get("type") != "thinking":
|
|
586
|
-
continue
|
|
587
|
-
raw_meta = block.get("meta")
|
|
588
|
-
meta = cast(dict[str, Any], raw_meta) if isinstance(raw_meta, dict) else {}
|
|
589
|
-
block["meta"] = {**meta, "duration_ms": thinking_duration_ms}
|
|
590
|
-
break
|
|
602
|
+
self._stamp_thinking_duration(partial_content, thinking_duration_ms)
|
|
591
603
|
cancelled_message = build_message(
|
|
592
604
|
"assistant",
|
|
593
605
|
[dict(block) for block in partial_content],
|
|
@@ -607,13 +619,7 @@ class Agent:
|
|
|
607
619
|
return
|
|
608
620
|
|
|
609
621
|
if thinking_duration_ms is not None:
|
|
610
|
-
|
|
611
|
-
if not isinstance(block, dict) or block.get("type") != "thinking":
|
|
612
|
-
continue
|
|
613
|
-
raw_meta = block.get("meta")
|
|
614
|
-
meta = cast(dict[str, Any], raw_meta) if isinstance(raw_meta, dict) else {}
|
|
615
|
-
block["meta"] = {**meta, "duration_ms": thinking_duration_ms}
|
|
616
|
-
break
|
|
622
|
+
self._stamp_thinking_duration(assistant_message.get("content") or [], thinking_duration_ms)
|
|
617
623
|
|
|
618
624
|
# Stamp context_window onto the persisted assistant message so
|
|
619
625
|
# rewinds and refreshed clients can render token-usage % without
|
|
@@ -649,16 +655,13 @@ class Agent:
|
|
|
649
655
|
continue
|
|
650
656
|
|
|
651
657
|
d = event.data
|
|
652
|
-
output = str(d.get("output") or "")
|
|
653
|
-
metadata = d.get("metadata") if isinstance(d.get("metadata"), dict) else None
|
|
654
|
-
content = d.get("content")
|
|
655
658
|
tool_results.append(
|
|
656
659
|
tool_result_block(
|
|
657
|
-
tool_use_id=
|
|
658
|
-
output=output,
|
|
659
|
-
metadata=metadata,
|
|
660
|
-
is_error=
|
|
661
|
-
content=
|
|
660
|
+
tool_use_id=d["tool_use_id"],
|
|
661
|
+
output=d["output"],
|
|
662
|
+
metadata=d.get("metadata"),
|
|
663
|
+
is_error=d["is_error"],
|
|
664
|
+
content=d.get("content"),
|
|
662
665
|
)
|
|
663
666
|
)
|
|
664
667
|
|
|
@@ -735,20 +738,9 @@ class Agent:
|
|
|
735
738
|
) -> None:
|
|
736
739
|
"""Ask the provider for a summary, persist the compact event, append it."""
|
|
737
740
|
|
|
738
|
-
request =
|
|
739
|
-
provider=self.provider,
|
|
740
|
-
model=self.model,
|
|
741
|
-
session_id=self.session_id,
|
|
742
|
-
messages=self.messages,
|
|
743
|
-
system=self.system,
|
|
741
|
+
request = self._build_request(
|
|
744
742
|
tools=[],
|
|
745
743
|
max_tokens=min(self.max_tokens, 8192),
|
|
746
|
-
temperature=self.temperature,
|
|
747
|
-
api_key=self.api_key,
|
|
748
|
-
api_base=self.api_base,
|
|
749
|
-
supports_image_input=self.supports_image_input,
|
|
750
|
-
supports_pdf_input=self.supports_pdf_input,
|
|
751
|
-
transcript_path=self.transcript_path,
|
|
752
744
|
append_messages=[user_text_message(COMPACT_SUMMARY_PROMPT)],
|
|
753
745
|
)
|
|
754
746
|
|
|
@@ -6,6 +6,7 @@ same content blocks for the same input.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import builtins
|
|
9
10
|
import html
|
|
10
11
|
from base64 import b64encode
|
|
11
12
|
from collections.abc import Sequence
|
|
@@ -29,7 +30,7 @@ class Attachment:
|
|
|
29
30
|
bare ``Attachment("notes.txt")`` from silently becoming text instead of a path.
|
|
30
31
|
"""
|
|
31
32
|
|
|
32
|
-
source: Path | bytes | str
|
|
33
|
+
source: Path | builtins.bytes | str
|
|
33
34
|
media_type: str | None = None
|
|
34
35
|
name: str | None = None
|
|
35
36
|
|
|
@@ -40,7 +41,7 @@ class Attachment:
|
|
|
40
41
|
return cls(source=Path(path), name=name)
|
|
41
42
|
|
|
42
43
|
@classmethod
|
|
43
|
-
def bytes(cls, data: bytes, *, media_type: str, name: str | None = None) -> Self:
|
|
44
|
+
def bytes(cls, data: builtins.bytes, *, media_type: str, name: str | None = None) -> Self:
|
|
44
45
|
"""``media_type`` must be a supported image type or ``application/pdf``."""
|
|
45
46
|
|
|
46
47
|
if not media_type:
|
|
@@ -56,6 +56,13 @@
|
|
|
56
56
|
"supports_pdf_input": true,
|
|
57
57
|
"supports_reasoning": false
|
|
58
58
|
},
|
|
59
|
+
"claude-fable-5": {
|
|
60
|
+
"context_window": 1000000,
|
|
61
|
+
"max_output_tokens": 128000,
|
|
62
|
+
"supports_image_input": true,
|
|
63
|
+
"supports_pdf_input": true,
|
|
64
|
+
"supports_reasoning": true
|
|
65
|
+
},
|
|
59
66
|
"claude-haiku-4-5": {
|
|
60
67
|
"context_window": 200000,
|
|
61
68
|
"max_output_tokens": 64000,
|
|
@@ -263,6 +270,13 @@
|
|
|
263
270
|
"supports_pdf_input": true,
|
|
264
271
|
"supports_reasoning": true
|
|
265
272
|
},
|
|
273
|
+
"gemini-3-pro-image-preview": {
|
|
274
|
+
"context_window": 131072,
|
|
275
|
+
"max_output_tokens": 32768,
|
|
276
|
+
"supports_image_input": true,
|
|
277
|
+
"supports_pdf_input": false,
|
|
278
|
+
"supports_reasoning": true
|
|
279
|
+
},
|
|
266
280
|
"gemini-3-pro-preview": {
|
|
267
281
|
"context_window": 1048576,
|
|
268
282
|
"max_output_tokens": 65536,
|
|
@@ -448,6 +462,13 @@
|
|
|
448
462
|
"supports_image_input": true,
|
|
449
463
|
"supports_pdf_input": false,
|
|
450
464
|
"supports_reasoning": true
|
|
465
|
+
},
|
|
466
|
+
"kimi-k2.7-code": {
|
|
467
|
+
"context_window": 262144,
|
|
468
|
+
"max_output_tokens": 262144,
|
|
469
|
+
"supports_image_input": true,
|
|
470
|
+
"supports_pdf_input": false,
|
|
471
|
+
"supports_reasoning": true
|
|
451
472
|
}
|
|
452
473
|
},
|
|
453
474
|
"openai": {
|
|
@@ -731,20 +752,6 @@
|
|
|
731
752
|
"supports_pdf_input": true,
|
|
732
753
|
"supports_reasoning": true
|
|
733
754
|
},
|
|
734
|
-
"o1-mini": {
|
|
735
|
-
"context_window": 128000,
|
|
736
|
-
"max_output_tokens": 65536,
|
|
737
|
-
"supports_image_input": false,
|
|
738
|
-
"supports_pdf_input": false,
|
|
739
|
-
"supports_reasoning": true
|
|
740
|
-
},
|
|
741
|
-
"o1-preview": {
|
|
742
|
-
"context_window": 128000,
|
|
743
|
-
"max_output_tokens": 32768,
|
|
744
|
-
"supports_image_input": false,
|
|
745
|
-
"supports_pdf_input": false,
|
|
746
|
-
"supports_reasoning": true
|
|
747
|
-
},
|
|
748
755
|
"o1-pro": {
|
|
749
756
|
"context_window": 200000,
|
|
750
757
|
"max_output_tokens": 100000,
|
|
@@ -915,6 +922,13 @@
|
|
|
915
922
|
"supports_pdf_input": false,
|
|
916
923
|
"supports_reasoning": false
|
|
917
924
|
},
|
|
925
|
+
"anthropic/claude-fable-5": {
|
|
926
|
+
"context_window": 1000000,
|
|
927
|
+
"max_output_tokens": 128000,
|
|
928
|
+
"supports_image_input": true,
|
|
929
|
+
"supports_pdf_input": true,
|
|
930
|
+
"supports_reasoning": true
|
|
931
|
+
},
|
|
918
932
|
"anthropic/claude-haiku-4.5": {
|
|
919
933
|
"context_window": 200000,
|
|
920
934
|
"max_output_tokens": 64000,
|
|
@@ -1013,20 +1027,6 @@
|
|
|
1013
1027
|
"supports_pdf_input": false,
|
|
1014
1028
|
"supports_reasoning": false
|
|
1015
1029
|
},
|
|
1016
|
-
"arcee-ai/maestro-reasoning": {
|
|
1017
|
-
"context_window": 131072,
|
|
1018
|
-
"max_output_tokens": 32000,
|
|
1019
|
-
"supports_image_input": false,
|
|
1020
|
-
"supports_pdf_input": false,
|
|
1021
|
-
"supports_reasoning": false
|
|
1022
|
-
},
|
|
1023
|
-
"arcee-ai/spotlight": {
|
|
1024
|
-
"context_window": 131072,
|
|
1025
|
-
"max_output_tokens": 65537,
|
|
1026
|
-
"supports_image_input": true,
|
|
1027
|
-
"supports_pdf_input": false,
|
|
1028
|
-
"supports_reasoning": false
|
|
1029
|
-
},
|
|
1030
1030
|
"arcee-ai/trinity-large-thinking": {
|
|
1031
1031
|
"context_window": 262144,
|
|
1032
1032
|
"max_output_tokens": 262144,
|
|
@@ -1048,13 +1048,6 @@
|
|
|
1048
1048
|
"supports_pdf_input": false,
|
|
1049
1049
|
"supports_reasoning": false
|
|
1050
1050
|
},
|
|
1051
|
-
"baidu/ernie-4.5-vl-28b-a3b": {
|
|
1052
|
-
"context_window": 30000,
|
|
1053
|
-
"max_output_tokens": 8000,
|
|
1054
|
-
"supports_image_input": true,
|
|
1055
|
-
"supports_pdf_input": false,
|
|
1056
|
-
"supports_reasoning": true
|
|
1057
|
-
},
|
|
1058
1051
|
"baidu/ernie-4.5-vl-424b-a47b": {
|
|
1059
1052
|
"context_window": 123000,
|
|
1060
1053
|
"max_output_tokens": 16000,
|
|
@@ -1175,8 +1168,8 @@
|
|
|
1175
1168
|
"supports_reasoning": true
|
|
1176
1169
|
},
|
|
1177
1170
|
"deepseek/deepseek-r1-distill-llama-70b": {
|
|
1178
|
-
"context_window":
|
|
1179
|
-
"max_output_tokens":
|
|
1171
|
+
"context_window": 8192,
|
|
1172
|
+
"max_output_tokens": 8192,
|
|
1180
1173
|
"supports_image_input": false,
|
|
1181
1174
|
"supports_pdf_input": false,
|
|
1182
1175
|
"supports_reasoning": true
|
|
@@ -1210,7 +1203,7 @@
|
|
|
1210
1203
|
"supports_reasoning": true
|
|
1211
1204
|
},
|
|
1212
1205
|
"deepseek/deepseek-v4-flash": {
|
|
1213
|
-
"context_window":
|
|
1206
|
+
"context_window": 1048575,
|
|
1214
1207
|
"max_output_tokens": 131072,
|
|
1215
1208
|
"supports_image_input": false,
|
|
1216
1209
|
"supports_pdf_input": false,
|
|
@@ -1386,7 +1379,7 @@
|
|
|
1386
1379
|
},
|
|
1387
1380
|
"google/gemma-4-31b-it": {
|
|
1388
1381
|
"context_window": 262144,
|
|
1389
|
-
"max_output_tokens":
|
|
1382
|
+
"max_output_tokens": 262144,
|
|
1390
1383
|
"supports_image_input": true,
|
|
1391
1384
|
"supports_pdf_input": false,
|
|
1392
1385
|
"supports_reasoning": true
|
|
@@ -1532,7 +1525,7 @@
|
|
|
1532
1525
|
"supports_reasoning": false
|
|
1533
1526
|
},
|
|
1534
1527
|
"meta-llama/llama-3.1-8b-instruct": {
|
|
1535
|
-
"context_window":
|
|
1528
|
+
"context_window": 131072,
|
|
1536
1529
|
"max_output_tokens": 16384,
|
|
1537
1530
|
"supports_image_input": false,
|
|
1538
1531
|
"supports_pdf_input": false,
|
|
@@ -1594,13 +1587,6 @@
|
|
|
1594
1587
|
"supports_pdf_input": false,
|
|
1595
1588
|
"supports_reasoning": false
|
|
1596
1589
|
},
|
|
1597
|
-
"meta-llama/llama-guard-3-8b": {
|
|
1598
|
-
"context_window": 131072,
|
|
1599
|
-
"max_output_tokens": 131072,
|
|
1600
|
-
"supports_image_input": false,
|
|
1601
|
-
"supports_pdf_input": false,
|
|
1602
|
-
"supports_reasoning": false
|
|
1603
|
-
},
|
|
1604
1590
|
"meta-llama/llama-guard-4-12b": {
|
|
1605
1591
|
"context_window": 163840,
|
|
1606
1592
|
"max_output_tokens": 16384,
|
|
@@ -1840,20 +1826,20 @@
|
|
|
1840
1826
|
"supports_reasoning": true
|
|
1841
1827
|
},
|
|
1842
1828
|
"moonshotai/kimi-k2.5": {
|
|
1843
|
-
"context_window":
|
|
1829
|
+
"context_window": 256000,
|
|
1844
1830
|
"max_output_tokens": 262144,
|
|
1845
1831
|
"supports_image_input": true,
|
|
1846
1832
|
"supports_pdf_input": false,
|
|
1847
1833
|
"supports_reasoning": true
|
|
1848
1834
|
},
|
|
1849
1835
|
"moonshotai/kimi-k2.6": {
|
|
1850
|
-
"context_window":
|
|
1851
|
-
"max_output_tokens":
|
|
1836
|
+
"context_window": 262142,
|
|
1837
|
+
"max_output_tokens": 262142,
|
|
1852
1838
|
"supports_image_input": true,
|
|
1853
1839
|
"supports_pdf_input": false,
|
|
1854
1840
|
"supports_reasoning": true
|
|
1855
1841
|
},
|
|
1856
|
-
"moonshotai/kimi-k2.
|
|
1842
|
+
"moonshotai/kimi-k2.7-code": {
|
|
1857
1843
|
"context_window": 262144,
|
|
1858
1844
|
"max_output_tokens": 262144,
|
|
1859
1845
|
"supports_image_input": true,
|
|
@@ -1874,19 +1860,12 @@
|
|
|
1874
1860
|
"supports_pdf_input": false,
|
|
1875
1861
|
"supports_reasoning": false
|
|
1876
1862
|
},
|
|
1877
|
-
"nex-agi/
|
|
1878
|
-
"context_window":
|
|
1879
|
-
"max_output_tokens":
|
|
1880
|
-
"supports_image_input":
|
|
1881
|
-
"supports_pdf_input": false,
|
|
1882
|
-
"supports_reasoning": false
|
|
1883
|
-
},
|
|
1884
|
-
"nousresearch/hermes-2-pro-llama-3-8b": {
|
|
1885
|
-
"context_window": 8192,
|
|
1886
|
-
"max_output_tokens": 8192,
|
|
1887
|
-
"supports_image_input": false,
|
|
1863
|
+
"nex-agi/nex-n2-pro:free": {
|
|
1864
|
+
"context_window": 262144,
|
|
1865
|
+
"max_output_tokens": 262144,
|
|
1866
|
+
"supports_image_input": true,
|
|
1888
1867
|
"supports_pdf_input": false,
|
|
1889
|
-
"supports_reasoning":
|
|
1868
|
+
"supports_reasoning": true
|
|
1890
1869
|
},
|
|
1891
1870
|
"nousresearch/hermes-3-llama-3.1-405b": {
|
|
1892
1871
|
"context_window": 131072,
|
|
@@ -1993,13 +1972,6 @@
|
|
|
1993
1972
|
"supports_pdf_input": false,
|
|
1994
1973
|
"supports_reasoning": true
|
|
1995
1974
|
},
|
|
1996
|
-
"nvidia/nemotron-nano-9b-v2": {
|
|
1997
|
-
"context_window": 131072,
|
|
1998
|
-
"max_output_tokens": 16384,
|
|
1999
|
-
"supports_image_input": false,
|
|
2000
|
-
"supports_pdf_input": false,
|
|
2001
|
-
"supports_reasoning": true
|
|
2002
|
-
},
|
|
2003
1975
|
"nvidia/nemotron-nano-9b-v2:free": {
|
|
2004
1976
|
"context_window": 128000,
|
|
2005
1977
|
"max_output_tokens": 128000,
|
|
@@ -2042,13 +2014,6 @@
|
|
|
2042
2014
|
"supports_pdf_input": false,
|
|
2043
2015
|
"supports_reasoning": false
|
|
2044
2016
|
},
|
|
2045
|
-
"openai/gpt-4-1106-preview": {
|
|
2046
|
-
"context_window": 128000,
|
|
2047
|
-
"max_output_tokens": 4096,
|
|
2048
|
-
"supports_image_input": false,
|
|
2049
|
-
"supports_pdf_input": false,
|
|
2050
|
-
"supports_reasoning": false
|
|
2051
|
-
},
|
|
2052
2017
|
"openai/gpt-4-turbo": {
|
|
2053
2018
|
"context_window": 128000,
|
|
2054
2019
|
"max_output_tokens": 4096,
|
|
@@ -2596,8 +2561,8 @@
|
|
|
2596
2561
|
"supports_reasoning": true
|
|
2597
2562
|
},
|
|
2598
2563
|
"qwen/qwen2.5-vl-72b-instruct": {
|
|
2599
|
-
"context_window":
|
|
2600
|
-
"max_output_tokens":
|
|
2564
|
+
"context_window": 128000,
|
|
2565
|
+
"max_output_tokens": 128000,
|
|
2601
2566
|
"supports_image_input": true,
|
|
2602
2567
|
"supports_pdf_input": false,
|
|
2603
2568
|
"supports_reasoning": false
|
|
@@ -2632,7 +2597,7 @@
|
|
|
2632
2597
|
},
|
|
2633
2598
|
"qwen/qwen3-30b-a3b": {
|
|
2634
2599
|
"context_window": 40960,
|
|
2635
|
-
"max_output_tokens":
|
|
2600
|
+
"max_output_tokens": 16384,
|
|
2636
2601
|
"supports_image_input": false,
|
|
2637
2602
|
"supports_pdf_input": false,
|
|
2638
2603
|
"supports_reasoning": true
|
|
@@ -2821,7 +2786,7 @@
|
|
|
2821
2786
|
},
|
|
2822
2787
|
"qwen/qwen3.5-9b": {
|
|
2823
2788
|
"context_window": 262144,
|
|
2824
|
-
"max_output_tokens":
|
|
2789
|
+
"max_output_tokens": 262144,
|
|
2825
2790
|
"supports_image_input": true,
|
|
2826
2791
|
"supports_pdf_input": false,
|
|
2827
2792
|
"supports_reasoning": true
|
|
@@ -2855,8 +2820,8 @@
|
|
|
2855
2820
|
"supports_reasoning": true
|
|
2856
2821
|
},
|
|
2857
2822
|
"qwen/qwen3.6-35b-a3b": {
|
|
2858
|
-
"context_window":
|
|
2859
|
-
"max_output_tokens":
|
|
2823
|
+
"context_window": 262144,
|
|
2824
|
+
"max_output_tokens": 262144,
|
|
2860
2825
|
"supports_image_input": true,
|
|
2861
2826
|
"supports_pdf_input": false,
|
|
2862
2827
|
"supports_reasoning": true
|
|
@@ -2924,13 +2889,6 @@
|
|
|
2924
2889
|
"supports_pdf_input": false,
|
|
2925
2890
|
"supports_reasoning": false
|
|
2926
2891
|
},
|
|
2927
|
-
"sao10k/l3-euryale-70b": {
|
|
2928
|
-
"context_window": 8192,
|
|
2929
|
-
"max_output_tokens": 8192,
|
|
2930
|
-
"supports_image_input": false,
|
|
2931
|
-
"supports_pdf_input": false,
|
|
2932
|
-
"supports_reasoning": false
|
|
2933
|
-
},
|
|
2934
2892
|
"sao10k/l3-lunaris-8b": {
|
|
2935
2893
|
"context_window": 8192,
|
|
2936
2894
|
"max_output_tokens": 16384,
|
|
@@ -3092,13 +3050,6 @@
|
|
|
3092
3050
|
"supports_pdf_input": false,
|
|
3093
3051
|
"supports_reasoning": true
|
|
3094
3052
|
},
|
|
3095
|
-
"z-ai/glm-4-32b": {
|
|
3096
|
-
"context_window": 128000,
|
|
3097
|
-
"max_output_tokens": 128000,
|
|
3098
|
-
"supports_image_input": false,
|
|
3099
|
-
"supports_pdf_input": false,
|
|
3100
|
-
"supports_reasoning": false
|
|
3101
|
-
},
|
|
3102
3053
|
"z-ai/glm-4.5": {
|
|
3103
3054
|
"context_window": 131072,
|
|
3104
3055
|
"max_output_tokens": 98304,
|
|
@@ -3113,13 +3064,6 @@
|
|
|
3113
3064
|
"supports_pdf_input": false,
|
|
3114
3065
|
"supports_reasoning": true
|
|
3115
3066
|
},
|
|
3116
|
-
"z-ai/glm-4.5-air:free": {
|
|
3117
|
-
"context_window": 131072,
|
|
3118
|
-
"max_output_tokens": 96000,
|
|
3119
|
-
"supports_image_input": false,
|
|
3120
|
-
"supports_pdf_input": false,
|
|
3121
|
-
"supports_reasoning": true
|
|
3122
|
-
},
|
|
3123
3067
|
"z-ai/glm-4.5v": {
|
|
3124
3068
|
"context_window": 65536,
|
|
3125
3069
|
"max_output_tokens": 16384,
|
|
@@ -3136,7 +3080,7 @@
|
|
|
3136
3080
|
},
|
|
3137
3081
|
"z-ai/glm-4.6v": {
|
|
3138
3082
|
"context_window": 131072,
|
|
3139
|
-
"max_output_tokens":
|
|
3083
|
+
"max_output_tokens": 32768,
|
|
3140
3084
|
"supports_image_input": true,
|
|
3141
3085
|
"supports_pdf_input": false,
|
|
3142
3086
|
"supports_reasoning": true
|
|
@@ -3163,7 +3107,7 @@
|
|
|
3163
3107
|
"supports_reasoning": true
|
|
3164
3108
|
},
|
|
3165
3109
|
"z-ai/glm-5-turbo": {
|
|
3166
|
-
"context_window":
|
|
3110
|
+
"context_window": 262144,
|
|
3167
3111
|
"max_output_tokens": 131072,
|
|
3168
3112
|
"supports_image_input": false,
|
|
3169
3113
|
"supports_pdf_input": false,
|
|
@@ -3176,11 +3120,11 @@
|
|
|
3176
3120
|
"supports_pdf_input": false,
|
|
3177
3121
|
"supports_reasoning": true
|
|
3178
3122
|
},
|
|
3179
|
-
"
|
|
3180
|
-
"context_window":
|
|
3181
|
-
"max_output_tokens":
|
|
3123
|
+
"~anthropic/claude-fable-latest": {
|
|
3124
|
+
"context_window": 1000000,
|
|
3125
|
+
"max_output_tokens": 128000,
|
|
3182
3126
|
"supports_image_input": true,
|
|
3183
|
-
"supports_pdf_input":
|
|
3127
|
+
"supports_pdf_input": true,
|
|
3184
3128
|
"supports_reasoning": true
|
|
3185
3129
|
},
|
|
3186
3130
|
"~anthropic/claude-haiku-latest": {
|
|
@@ -3219,8 +3163,8 @@
|
|
|
3219
3163
|
"supports_reasoning": true
|
|
3220
3164
|
},
|
|
3221
3165
|
"~moonshotai/kimi-latest": {
|
|
3222
|
-
"context_window":
|
|
3223
|
-
"max_output_tokens":
|
|
3166
|
+
"context_window": 262142,
|
|
3167
|
+
"max_output_tokens": 262142,
|
|
3224
3168
|
"supports_image_input": true,
|
|
3225
3169
|
"supports_pdf_input": false,
|
|
3226
3170
|
"supports_reasoning": true
|
|
@@ -24,12 +24,10 @@ from mycode.providers.base import (
|
|
|
24
24
|
get_native_meta,
|
|
25
25
|
load_document_block_payload,
|
|
26
26
|
load_image_block_payload,
|
|
27
|
+
native_block_meta,
|
|
27
28
|
tool_result_content_blocks,
|
|
28
29
|
)
|
|
29
30
|
|
|
30
|
-
# Maps reasoning_effort values to extended thinking budget_tokens.
|
|
31
|
-
_THINKING_BUDGETS: dict[str, int] = {"low": 2048, "medium": 8192, "high": 24576, "xhigh": 32768}
|
|
32
|
-
|
|
33
31
|
|
|
34
32
|
class AnthropicLikeAdapter(ProviderAdapter):
|
|
35
33
|
"""Shared Messages adapter for Anthropic-compatible providers."""
|
|
@@ -42,14 +40,6 @@ class AnthropicLikeAdapter(ProviderAdapter):
|
|
|
42
40
|
del request
|
|
43
41
|
return None
|
|
44
42
|
|
|
45
|
-
def manual_thinking_config(self, effort: str | None) -> dict[str, Any] | None:
|
|
46
|
-
if not effort:
|
|
47
|
-
return None
|
|
48
|
-
if effort == "none":
|
|
49
|
-
return {"type": "disabled"}
|
|
50
|
-
budget = _THINKING_BUDGETS.get(effort)
|
|
51
|
-
return {"type": "enabled", "budget_tokens": budget} if budget else None
|
|
52
|
-
|
|
53
43
|
def _build_request_payload(self, request: ProviderRequest) -> dict[str, Any]:
|
|
54
44
|
messages = [self._serialize_message(message) for message in self.prepare_messages(request)]
|
|
55
45
|
self._apply_cache_control(messages)
|
|
@@ -58,9 +48,9 @@ class AnthropicLikeAdapter(ProviderAdapter):
|
|
|
58
48
|
payload: dict[str, Any] = {
|
|
59
49
|
"model": request.model,
|
|
60
50
|
"max_tokens": request.max_tokens,
|
|
61
|
-
"temperature": request.temperature,
|
|
62
51
|
"messages": messages,
|
|
63
52
|
}
|
|
53
|
+
# Anthropic-compatible providers use provider-default sampling; temperature is intentionally omitted.
|
|
64
54
|
if request.system:
|
|
65
55
|
payload["system"] = [
|
|
66
56
|
{
|
|
@@ -133,25 +123,27 @@ class AnthropicLikeAdapter(ProviderAdapter):
|
|
|
133
123
|
api_key = self.require_api_key(request.api_key)
|
|
134
124
|
|
|
135
125
|
try:
|
|
136
|
-
async with
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
126
|
+
async with (
|
|
127
|
+
AsyncAnthropic(
|
|
128
|
+
api_key=api_key,
|
|
129
|
+
base_url=self.resolve_base_url(request.api_base),
|
|
130
|
+
timeout=DEFAULT_REQUEST_TIMEOUT,
|
|
131
|
+
) as client,
|
|
132
|
+
client.messages.stream(**self._build_request_payload(request)) as stream,
|
|
133
|
+
):
|
|
134
|
+
async for event in stream:
|
|
135
|
+
event_type = getattr(event, "type", None)
|
|
136
|
+
if event_type == "thinking":
|
|
137
|
+
thinking = cast(str | None, getattr(event, "thinking", None))
|
|
138
|
+
if thinking:
|
|
139
|
+
yield ProviderStreamEvent("thinking_delta", {"text": thinking})
|
|
140
|
+
continue
|
|
141
|
+
if event_type == "text":
|
|
142
|
+
text = cast(str | None, getattr(event, "text", None))
|
|
143
|
+
if text:
|
|
144
|
+
yield ProviderStreamEvent("text_delta", {"text": text})
|
|
145
|
+
|
|
146
|
+
final_message = await stream.get_final_message()
|
|
155
147
|
except APIError as exc:
|
|
156
148
|
raise ValueError(str(exc)) from exc
|
|
157
149
|
|
|
@@ -175,7 +167,7 @@ class AnthropicLikeAdapter(ProviderAdapter):
|
|
|
175
167
|
blocks.append(
|
|
176
168
|
thinking_block(
|
|
177
169
|
getattr(block, "thinking", ""),
|
|
178
|
-
meta=
|
|
170
|
+
meta=native_block_meta(native_meta),
|
|
179
171
|
)
|
|
180
172
|
)
|
|
181
173
|
continue
|
|
@@ -185,9 +177,7 @@ class AnthropicLikeAdapter(ProviderAdapter):
|
|
|
185
177
|
citations = getattr(block, "citations", None)
|
|
186
178
|
if citations:
|
|
187
179
|
native_meta["citations"] = dump_model(citations)
|
|
188
|
-
blocks.append(
|
|
189
|
-
text_block(getattr(block, "text", ""), meta={"native": native_meta} if native_meta else None)
|
|
190
|
-
)
|
|
180
|
+
blocks.append(text_block(getattr(block, "text", ""), meta=native_block_meta(native_meta)))
|
|
191
181
|
continue
|
|
192
182
|
|
|
193
183
|
if block_type == "tool_use":
|
|
@@ -200,7 +190,7 @@ class AnthropicLikeAdapter(ProviderAdapter):
|
|
|
200
190
|
tool_id=getattr(block, "id", ""),
|
|
201
191
|
name=getattr(block, "name", ""),
|
|
202
192
|
input=getattr(block, "input", None),
|
|
203
|
-
meta=
|
|
193
|
+
meta=native_block_meta(native_meta),
|
|
204
194
|
)
|
|
205
195
|
)
|
|
206
196
|
continue
|
|
@@ -314,7 +304,7 @@ class AnthropicAdapter(AnthropicLikeAdapter):
|
|
|
314
304
|
label = "Anthropic"
|
|
315
305
|
default_base_url = "https://api.anthropic.com"
|
|
316
306
|
env_api_key_names = ("ANTHROPIC_API_KEY",)
|
|
317
|
-
default_models = ("claude-sonnet-4-6", "claude-opus-4-
|
|
307
|
+
default_models = ("claude-sonnet-4-6", "claude-opus-4-8")
|
|
318
308
|
supports_reasoning_effort = True
|
|
319
309
|
|
|
320
310
|
@override
|
|
@@ -325,12 +315,10 @@ class AnthropicAdapter(AnthropicLikeAdapter):
|
|
|
325
315
|
if effort == "none":
|
|
326
316
|
return {"type": "disabled"}
|
|
327
317
|
normalized = request.model.lower()
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
return thinking
|
|
333
|
-
return self.manual_thinking_config(effort)
|
|
318
|
+
thinking: dict[str, Any] = {"type": "adaptive"}
|
|
319
|
+
if normalized.startswith(("claude-opus-4-7", "claude-opus-4-8")):
|
|
320
|
+
thinking["display"] = "summarized"
|
|
321
|
+
return thinking
|
|
334
322
|
|
|
335
323
|
@override
|
|
336
324
|
def output_config(self, request: ProviderRequest) -> dict[str, Any] | None:
|
|
@@ -339,7 +327,7 @@ class AnthropicAdapter(AnthropicLikeAdapter):
|
|
|
339
327
|
return None
|
|
340
328
|
|
|
341
329
|
normalized = request.model.lower()
|
|
342
|
-
if normalized.startswith("claude-opus-4-7"):
|
|
330
|
+
if normalized.startswith(("claude-opus-4-7", "claude-opus-4-8")):
|
|
343
331
|
return {"effort": effort}
|
|
344
332
|
|
|
345
333
|
if normalized.startswith("claude-sonnet-4-6"):
|
|
@@ -364,12 +352,22 @@ class MoonshotAIAdapter(AnthropicLikeAdapter):
|
|
|
364
352
|
label = "Moonshot"
|
|
365
353
|
default_base_url = "https://api.moonshot.ai/anthropic"
|
|
366
354
|
env_api_key_names = ("MOONSHOT_API_KEY",)
|
|
367
|
-
default_models = ("kimi-k2.6"
|
|
355
|
+
default_models = ("kimi-k2.7-code", "kimi-k2.6")
|
|
368
356
|
supports_reasoning_effort = True
|
|
369
357
|
|
|
370
358
|
@override
|
|
371
359
|
def thinking_config(self, request: ProviderRequest) -> dict[str, Any] | None:
|
|
372
|
-
|
|
360
|
+
if not request.reasoning_effort:
|
|
361
|
+
return None
|
|
362
|
+
if request.reasoning_effort == "none" and request.model.lower() != "kimi-k2.7-code":
|
|
363
|
+
return {"type": "disabled"}
|
|
364
|
+
return {"type": "adaptive"}
|
|
365
|
+
|
|
366
|
+
@override
|
|
367
|
+
def output_config(self, request: ProviderRequest) -> dict[str, Any] | None:
|
|
368
|
+
if not request.reasoning_effort or request.reasoning_effort == "none":
|
|
369
|
+
return None
|
|
370
|
+
return {"effort": request.reasoning_effort}
|
|
373
371
|
|
|
374
372
|
|
|
375
373
|
class MiniMaxAdapter(AnthropicLikeAdapter):
|
|
@@ -384,9 +382,9 @@ class MiniMaxAdapter(AnthropicLikeAdapter):
|
|
|
384
382
|
label = "MiniMax"
|
|
385
383
|
default_base_url = "https://api.minimax.io/anthropic"
|
|
386
384
|
env_api_key_names = ("MINIMAX_API_KEY",)
|
|
387
|
-
default_models = ("MiniMax-
|
|
388
|
-
supports_reasoning_effort =
|
|
385
|
+
default_models = ("MiniMax-M3",)
|
|
386
|
+
supports_reasoning_effort = False
|
|
389
387
|
|
|
390
388
|
@override
|
|
391
389
|
def thinking_config(self, request: ProviderRequest) -> dict[str, Any] | None:
|
|
392
|
-
return
|
|
390
|
+
return {"type": "adaptive"}
|
|
@@ -68,6 +68,12 @@ def get_native_meta(block: dict[str, Any]) -> dict[str, Any]:
|
|
|
68
68
|
return {}
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
def native_block_meta(native: dict[str, Any]) -> dict[str, Any] | None:
|
|
72
|
+
"""Wrap accumulated native values as block meta, or None when empty."""
|
|
73
|
+
|
|
74
|
+
return {"native": native} if native else None
|
|
75
|
+
|
|
76
|
+
|
|
71
77
|
class ProviderAdapter(ABC):
|
|
72
78
|
"""Base class for provider adapters.
|
|
73
79
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from collections.abc import AsyncIterator
|
|
6
|
+
from contextlib import suppress
|
|
6
7
|
from typing import Any, override
|
|
7
8
|
from urllib.parse import urlparse
|
|
8
9
|
|
|
@@ -88,10 +89,8 @@ class GoogleGeminiAdapter(ProviderAdapter):
|
|
|
88
89
|
except APIError as exc:
|
|
89
90
|
raise ValueError(str(exc)) from exc
|
|
90
91
|
finally:
|
|
91
|
-
|
|
92
|
+
with suppress(Exception):
|
|
92
93
|
await client.aio.aclose()
|
|
93
|
-
except Exception:
|
|
94
|
-
pass
|
|
95
94
|
|
|
96
95
|
raw_usage = usage or {}
|
|
97
96
|
total_tokens = raw_usage.get("total_token_count") or None
|
|
@@ -19,6 +19,7 @@ from mycode.providers.base import (
|
|
|
19
19
|
get_native_meta,
|
|
20
20
|
load_document_block_payload,
|
|
21
21
|
load_image_block_payload,
|
|
22
|
+
native_block_meta,
|
|
22
23
|
)
|
|
23
24
|
from mycode.utils import omit_none, parse_tool_arguments
|
|
24
25
|
|
|
@@ -109,7 +110,7 @@ class OpenAIChatAdapter(ProviderAdapter):
|
|
|
109
110
|
blocks.append(
|
|
110
111
|
thinking_block(
|
|
111
112
|
"".join(thinking_parts),
|
|
112
|
-
meta=
|
|
113
|
+
meta=native_block_meta(thinking_native_meta),
|
|
113
114
|
)
|
|
114
115
|
)
|
|
115
116
|
if text_parts:
|
|
@@ -248,13 +249,15 @@ class OpenAIChatAdapter(ProviderAdapter):
|
|
|
248
249
|
if role != "assistant":
|
|
249
250
|
return []
|
|
250
251
|
|
|
251
|
-
text_parts = [
|
|
252
|
+
text_parts = [
|
|
253
|
+
str(block.get("text") or "") for block in blocks if block.get("type") == "text" and block.get("text")
|
|
254
|
+
]
|
|
252
255
|
thinking_blocks = [block for block in blocks if block.get("type") == "thinking"]
|
|
253
256
|
tool_use_blocks = [block for block in blocks if block.get("type") == "tool_use"]
|
|
254
257
|
|
|
255
258
|
payload: dict[str, Any] = {
|
|
256
259
|
"role": "assistant",
|
|
257
|
-
"content": "\n".join(
|
|
260
|
+
"content": "\n".join(text_parts),
|
|
258
261
|
}
|
|
259
262
|
|
|
260
263
|
if tool_use_blocks:
|
|
@@ -363,12 +366,19 @@ class ZAIAdapter(OpenAIChatAdapter):
|
|
|
363
366
|
label = "Z.AI"
|
|
364
367
|
default_base_url = "https://api.z.ai/api/paas/v4/"
|
|
365
368
|
env_api_key_names = ("ZAI_API_KEY",)
|
|
366
|
-
default_models = ("glm-5.
|
|
369
|
+
default_models = ("glm-5.2",)
|
|
367
370
|
auto_discoverable = True
|
|
371
|
+
supports_reasoning_effort = True
|
|
368
372
|
|
|
369
373
|
@override
|
|
370
374
|
def _build_provider_payload_overrides(self, request: ProviderRequest) -> dict[str, Any]:
|
|
371
|
-
|
|
375
|
+
payload: dict[str, Any] = {"extra_body": {"thinking": {"type": "enabled", "clear_thinking": False}}}
|
|
376
|
+
if request.model.lower().startswith("glm-5.2"):
|
|
377
|
+
if request.reasoning_effort in {"low", "medium", "high"}:
|
|
378
|
+
payload["reasoning_effort"] = "high"
|
|
379
|
+
elif request.reasoning_effort == "xhigh":
|
|
380
|
+
payload["reasoning_effort"] = "max"
|
|
381
|
+
return payload
|
|
372
382
|
|
|
373
383
|
|
|
374
384
|
class OpenRouterAdapter(OpenAIChatAdapter):
|
|
@@ -18,6 +18,7 @@ from mycode.providers.base import (
|
|
|
18
18
|
dump_model,
|
|
19
19
|
load_document_block_payload,
|
|
20
20
|
load_image_block_payload,
|
|
21
|
+
native_block_meta,
|
|
21
22
|
tool_result_content_blocks,
|
|
22
23
|
)
|
|
23
24
|
from mycode.utils import omit_none, parse_tool_arguments
|
|
@@ -285,7 +286,7 @@ class OpenAIResponsesAdapter(ProviderAdapter):
|
|
|
285
286
|
blocks.append(
|
|
286
287
|
thinking_block(
|
|
287
288
|
"".join(text_parts),
|
|
288
|
-
meta=
|
|
289
|
+
meta=native_block_meta(item_meta),
|
|
289
290
|
)
|
|
290
291
|
)
|
|
291
292
|
continue
|
|
@@ -301,7 +302,7 @@ class OpenAIResponsesAdapter(ProviderAdapter):
|
|
|
301
302
|
blocks.append(
|
|
302
303
|
text_block(
|
|
303
304
|
getattr(part, "text", ""),
|
|
304
|
-
meta=
|
|
305
|
+
meta=native_block_meta(native_meta),
|
|
305
306
|
)
|
|
306
307
|
)
|
|
307
308
|
continue
|
|
@@ -327,7 +328,7 @@ class OpenAIResponsesAdapter(ProviderAdapter):
|
|
|
327
328
|
tool_id=getattr(item, "call_id", ""),
|
|
328
329
|
name=getattr(item, "name", ""),
|
|
329
330
|
input=tool_input,
|
|
330
|
-
meta=
|
|
331
|
+
meta=native_block_meta(item_meta),
|
|
331
332
|
)
|
|
332
333
|
)
|
|
333
334
|
|
|
@@ -85,6 +85,12 @@ SessionMetaDict = dict[str, object]
|
|
|
85
85
|
SessionIndex = dict[str, SessionMetaDict]
|
|
86
86
|
|
|
87
87
|
|
|
88
|
+
def _project_meta(raw: dict[str, object]) -> SessionMetaDict:
|
|
89
|
+
"""Keep only the known session-meta keys."""
|
|
90
|
+
|
|
91
|
+
return {key: raw[key] for key in META_KEYS if key in raw}
|
|
92
|
+
|
|
93
|
+
|
|
88
94
|
class SessionData(TypedDict):
|
|
89
95
|
session: SessionMetaDict
|
|
90
96
|
messages: list[ConversationMessage]
|
|
@@ -138,10 +144,10 @@ class SessionStore:
|
|
|
138
144
|
return None
|
|
139
145
|
if not isinstance(raw, dict):
|
|
140
146
|
return None
|
|
141
|
-
return
|
|
147
|
+
return _project_meta(raw)
|
|
142
148
|
|
|
143
149
|
def _write_meta(self, session_id: str, meta: SessionMetaDict) -> None:
|
|
144
|
-
meta =
|
|
150
|
+
meta = _project_meta(meta)
|
|
145
151
|
self.meta_path(session_id).write_text(json.dumps(meta, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
146
152
|
index = self._read_index()
|
|
147
153
|
index[session_id] = dict(meta)
|
|
@@ -154,11 +160,7 @@ class SessionStore:
|
|
|
154
160
|
return self._rebuild_index()
|
|
155
161
|
if not isinstance(raw, dict):
|
|
156
162
|
return self._rebuild_index()
|
|
157
|
-
return {
|
|
158
|
-
str(session_id): {key: meta[key] for key in META_KEYS if key in meta}
|
|
159
|
-
for session_id, meta in raw.items()
|
|
160
|
-
if isinstance(meta, dict)
|
|
161
|
-
}
|
|
163
|
+
return {str(session_id): _project_meta(meta) for session_id, meta in raw.items() if isinstance(meta, dict)}
|
|
162
164
|
|
|
163
165
|
def _write_index(self, index: SessionIndex) -> None:
|
|
164
166
|
self.index_path().write_text(json.dumps(index, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
@@ -228,7 +230,6 @@ class SessionStore:
|
|
|
228
230
|
if meta is None:
|
|
229
231
|
return None
|
|
230
232
|
|
|
231
|
-
# Read the raw append-only log first. Replay happens after that.
|
|
232
233
|
raw_messages: list[ConversationMessage] = []
|
|
233
234
|
try:
|
|
234
235
|
with self.messages_path(session_id).open("r", encoding="utf-8") as handle:
|
|
@@ -238,10 +239,10 @@ class SessionStore:
|
|
|
238
239
|
continue
|
|
239
240
|
try:
|
|
240
241
|
msg = json.loads(line)
|
|
241
|
-
if isinstance(msg, dict):
|
|
242
|
-
raw_messages.append(cast(ConversationMessage, msg))
|
|
243
242
|
except ValueError:
|
|
244
243
|
continue
|
|
244
|
+
if isinstance(msg, dict):
|
|
245
|
+
raw_messages.append(cast(ConversationMessage, msg))
|
|
245
246
|
except FileNotFoundError:
|
|
246
247
|
pass
|
|
247
248
|
|
|
@@ -26,9 +26,11 @@ import typing
|
|
|
26
26
|
from base64 import b64encode
|
|
27
27
|
from collections import deque
|
|
28
28
|
from collections.abc import Callable, Mapping, Sequence
|
|
29
|
+
from contextlib import suppress
|
|
29
30
|
from dataclasses import dataclass
|
|
30
31
|
from difflib import SequenceMatcher, unified_diff
|
|
31
32
|
from pathlib import Path
|
|
33
|
+
from types import FunctionType
|
|
32
34
|
from typing import Any, TextIO, cast, overload
|
|
33
35
|
|
|
34
36
|
from griffe import Docstring, DocstringSectionKind, Parser
|
|
@@ -222,10 +224,8 @@ def _kill_proc_tree(proc: subprocess.Popen[str]) -> None:
|
|
|
222
224
|
else:
|
|
223
225
|
proc.kill()
|
|
224
226
|
except Exception:
|
|
225
|
-
|
|
227
|
+
with suppress(Exception):
|
|
226
228
|
proc.kill()
|
|
227
|
-
except Exception:
|
|
228
|
-
pass
|
|
229
229
|
|
|
230
230
|
|
|
231
231
|
# ---------------------------------------------------------------------------
|
|
@@ -235,7 +235,7 @@ def _kill_proc_tree(proc: subprocess.Popen[str]) -> None:
|
|
|
235
235
|
|
|
236
236
|
@overload
|
|
237
237
|
def tool(
|
|
238
|
-
function:
|
|
238
|
+
function: FunctionType,
|
|
239
239
|
*,
|
|
240
240
|
name: str | None = None,
|
|
241
241
|
description: str | None = None,
|
|
@@ -252,17 +252,17 @@ def tool(
|
|
|
252
252
|
description: str | None = None,
|
|
253
253
|
parameters: Mapping[str, str] | None = None,
|
|
254
254
|
streams_output: bool = False,
|
|
255
|
-
) -> Callable[[
|
|
255
|
+
) -> Callable[[FunctionType], ToolSpec]: ...
|
|
256
256
|
|
|
257
257
|
|
|
258
258
|
def tool(
|
|
259
|
-
function:
|
|
259
|
+
function: FunctionType | None = None,
|
|
260
260
|
*,
|
|
261
261
|
name: str | None = None,
|
|
262
262
|
description: str | None = None,
|
|
263
263
|
parameters: Mapping[str, str] | None = None,
|
|
264
264
|
streams_output: bool = False,
|
|
265
|
-
) -> ToolSpec | Callable[[
|
|
265
|
+
) -> ToolSpec | Callable[[FunctionType], ToolSpec]:
|
|
266
266
|
"""Wrap a sync or async Python function as a :class:`ToolSpec`."""
|
|
267
267
|
|
|
268
268
|
current_frame = inspect.currentframe()
|
|
@@ -271,7 +271,7 @@ def tool(
|
|
|
271
271
|
del current_frame
|
|
272
272
|
del caller_frame
|
|
273
273
|
|
|
274
|
-
def wrap(fn:
|
|
274
|
+
def wrap(fn: FunctionType) -> ToolSpec:
|
|
275
275
|
tool_name = name or fn.__name__
|
|
276
276
|
signature = inspect.signature(fn)
|
|
277
277
|
signature_parameters = list(signature.parameters.values())
|
|
@@ -421,7 +421,7 @@ def _coerce_tool_result(value: Any) -> ToolExecutionResult:
|
|
|
421
421
|
if isinstance(value, str):
|
|
422
422
|
return ToolExecutionResult(output=value)
|
|
423
423
|
try:
|
|
424
|
-
text = json.dumps(value, ensure_ascii=False)
|
|
424
|
+
text = json.dumps(value, ensure_ascii=False, default=str)
|
|
425
425
|
except TypeError:
|
|
426
426
|
text = str(value)
|
|
427
427
|
return ToolExecutionResult(output=text)
|
|
@@ -901,10 +901,8 @@ def bash_tool(ctx: ToolContext, command: str, timeout: int | None = None) -> Too
|
|
|
901
901
|
return ToolExecutionResult(output=f"error: {exc}", is_error=True)
|
|
902
902
|
finally:
|
|
903
903
|
if log_file is not None:
|
|
904
|
-
|
|
904
|
+
with suppress(Exception):
|
|
905
905
|
log_file.close()
|
|
906
|
-
except Exception:
|
|
907
|
-
pass
|
|
908
906
|
if proc is not None:
|
|
909
907
|
ctx.untrack_proc(proc)
|
|
910
908
|
if proc.poll() is None:
|
|
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
|