polyllmkit 0.1.0__tar.gz → 0.1.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.
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/PKG-INFO +6 -1
- polyllmkit-0.1.2/pyproject.toml +37 -0
- polyllmkit-0.1.0/pyproject.toml → polyllmkit-0.1.2/pyproject.toml.orig +9 -1
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/bridge/adapters/anthropic.py +8 -8
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/bridge/adapters/claude_code.py +32 -20
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/bridge/adapters/google.py +4 -7
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/bridge/bridge.py +9 -9
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/bridge/cli.py +6 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/bridge/client.py +4 -7
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/__init__.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/bridge/__init__.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/bridge/__main__.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/bridge/adapters/__init__.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/bridge/adapters/openai.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/bridge/config.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/bridge/sinks.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/bridge/stream.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/md/__init__.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/md/render/__init__.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/md/render/__main__.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/md/render/cli.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/md/render/stream.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/md/view/__init__.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/md/view/__main__.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/md/view/app.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/md/view/cli.py +0 -0
- {polyllmkit-0.1.0 → polyllmkit-0.1.2}/src/llmkit/py.typed +0 -0
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: polyllmkit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Streaming, multi-provider LLM bridge + markdown rendering toolkit for terminal apps.
|
|
5
|
+
Requires-Dist: openai>=1.0 ; extra == 'all'
|
|
6
|
+
Requires-Dist: claude-agent-sdk>=0.2 ; extra == 'all'
|
|
7
|
+
Requires-Dist: anthropic>=0.40 ; extra == 'all'
|
|
8
|
+
Requires-Dist: google-genai>=1.0 ; extra == 'all'
|
|
5
9
|
Requires-Dist: anthropic>=0.40 ; extra == 'anthropic'
|
|
6
10
|
Requires-Dist: openai>=1.0 ; extra == 'bridge'
|
|
7
11
|
Requires-Dist: claude-agent-sdk>=0.2 ; extra == 'claude'
|
|
@@ -10,6 +14,7 @@ Requires-Dist: rich>=13.0 ; extra == 'md'
|
|
|
10
14
|
Requires-Dist: textual>=0.50 ; extra == 'md'
|
|
11
15
|
Requires-Dist: markdown-it-py>=3.0 ; extra == 'md'
|
|
12
16
|
Requires-Python: >=3.11
|
|
17
|
+
Provides-Extra: all
|
|
13
18
|
Provides-Extra: anthropic
|
|
14
19
|
Provides-Extra: bridge
|
|
15
20
|
Provides-Extra: claude
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "polyllmkit"
|
|
3
|
+
version = "0.1.2"
|
|
4
|
+
description = "Streaming, multi-provider LLM bridge + markdown rendering toolkit for terminal apps."
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
dependencies = []
|
|
7
|
+
|
|
8
|
+
[project.optional-dependencies]
|
|
9
|
+
bridge = ["openai>=1.0"]
|
|
10
|
+
claude = ["claude-agent-sdk>=0.2"]
|
|
11
|
+
anthropic = ["anthropic>=0.40"]
|
|
12
|
+
google = ["google-genai>=1.0"]
|
|
13
|
+
all = [
|
|
14
|
+
"openai>=1.0",
|
|
15
|
+
"claude-agent-sdk>=0.2",
|
|
16
|
+
"anthropic>=0.40",
|
|
17
|
+
"google-genai>=1.0",
|
|
18
|
+
]
|
|
19
|
+
md = [
|
|
20
|
+
"rich>=13.0",
|
|
21
|
+
"textual>=0.50",
|
|
22
|
+
"markdown-it-py>=3.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["uv_build>=0.5,<0.12"]
|
|
27
|
+
build-backend = "uv_build"
|
|
28
|
+
|
|
29
|
+
[tool.uv.build-backend]
|
|
30
|
+
module-root = "src"
|
|
31
|
+
module-name = "llmkit"
|
|
32
|
+
|
|
33
|
+
[dependency-groups]
|
|
34
|
+
dev = [
|
|
35
|
+
"ruff>=0.6",
|
|
36
|
+
"mypy>=1.10",
|
|
37
|
+
]
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# module-name below): consumers keep `import llmkit`, `pip install polyllmkit`.
|
|
5
5
|
[project]
|
|
6
6
|
name = "polyllmkit"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "Streaming, multi-provider LLM bridge + markdown rendering toolkit for terminal apps."
|
|
9
9
|
requires-python = ">=3.11" # tomllib (bridge.config) is stdlib only from 3.11
|
|
10
10
|
# Base install pulls nothing — pick the subpackage(s) you need via extras.
|
|
@@ -21,6 +21,14 @@ bridge = ["openai>=1.0"]
|
|
|
21
21
|
claude = ["claude-agent-sdk>=0.2"] # claude_code adapter (Claude Agent SDK)
|
|
22
22
|
anthropic = ["anthropic>=0.40"] # anthropic adapter (native Messages API)
|
|
23
23
|
google = ["google-genai>=1.0"] # google adapter (native Gemini API)
|
|
24
|
+
# Every backend at once — for hosts that resolve the adapter at runtime and
|
|
25
|
+
# must have all of them present (e.g. cribsheet's daemon).
|
|
26
|
+
all = [
|
|
27
|
+
"openai>=1.0",
|
|
28
|
+
"claude-agent-sdk>=0.2",
|
|
29
|
+
"anthropic>=0.40",
|
|
30
|
+
"google-genai>=1.0",
|
|
31
|
+
]
|
|
24
32
|
# llmkit.md — markdown rendering (one-shot + live stream + scrollable modal).
|
|
25
33
|
# markdown-it-py is a direct import in md.render.stream (textual also pulls
|
|
26
34
|
# it, but we depend on it explicitly since we use it ourselves).
|
|
@@ -29,7 +29,6 @@ openai-compatible path never requires it.
|
|
|
29
29
|
from __future__ import annotations
|
|
30
30
|
|
|
31
31
|
import os
|
|
32
|
-
import sys
|
|
33
32
|
from typing import Any, Optional
|
|
34
33
|
from urllib.parse import urlsplit, urlunsplit
|
|
35
34
|
|
|
@@ -57,17 +56,18 @@ def _base_url(endpoint: str) -> str:
|
|
|
57
56
|
|
|
58
57
|
|
|
59
58
|
def _require_sdk() -> Any:
|
|
60
|
-
"""Import the SDK or
|
|
59
|
+
"""Import the SDK, or raise ImportError with an actionable install hint.
|
|
60
|
+
|
|
61
|
+
Catchable exception, never ``SystemExit`` — see the note in
|
|
62
|
+
``claude_code._require_sdk``."""
|
|
61
63
|
try:
|
|
62
64
|
import anthropic # type: ignore[import-not-found]
|
|
63
|
-
except ImportError:
|
|
64
|
-
|
|
65
|
+
except ImportError as e:
|
|
66
|
+
raise ImportError(
|
|
65
67
|
"llmkit: the anthropic adapter needs the Anthropic SDK. "
|
|
66
68
|
"Install it with `pip install llmkit[anthropic]` (or `pip install "
|
|
67
|
-
"anthropic`)."
|
|
68
|
-
|
|
69
|
-
)
|
|
70
|
-
raise SystemExit(2)
|
|
69
|
+
"anthropic`)."
|
|
70
|
+
) from e
|
|
71
71
|
return anthropic
|
|
72
72
|
|
|
73
73
|
|
|
@@ -18,7 +18,6 @@ CLI governs those itself. Only ``model``, the request's ``system``, and
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
import os
|
|
21
|
-
import sys
|
|
22
21
|
from typing import Any
|
|
23
22
|
|
|
24
23
|
from ..bridge import ChatRequest, _Emitter
|
|
@@ -26,17 +25,21 @@ from ..config import Provider
|
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
def _require_sdk() -> Any:
|
|
29
|
-
"""Import the SDK or
|
|
28
|
+
"""Import the SDK, or raise ImportError with an actionable install hint.
|
|
29
|
+
|
|
30
|
+
A missing optional dependency must surface as a *catchable* exception, never
|
|
31
|
+
``SystemExit``: llmkit is a library, and hosts embed it inside long-lived
|
|
32
|
+
processes (e.g. an MCP daemon's lifespan startup) where a stray ``SystemExit``
|
|
33
|
+
— a ``BaseException`` — escapes ``except Exception`` and tears the whole
|
|
34
|
+
process down. The CLI translates this back into a clean exit(2)."""
|
|
30
35
|
try:
|
|
31
36
|
import claude_agent_sdk as sdk # type: ignore[import-not-found]
|
|
32
|
-
except ImportError:
|
|
33
|
-
|
|
37
|
+
except ImportError as e:
|
|
38
|
+
raise ImportError(
|
|
34
39
|
"llmkit: the claude_code adapter needs the Claude Agent SDK. "
|
|
35
40
|
"Install it with `pip install llmkit[claude]` (or `pip install "
|
|
36
|
-
"claude-agent-sdk`), and make sure the `claude` CLI is on PATH."
|
|
37
|
-
|
|
38
|
-
)
|
|
39
|
-
raise SystemExit(2)
|
|
41
|
+
"claude-agent-sdk`), and make sure the `claude` CLI is on PATH."
|
|
42
|
+
) from e
|
|
40
43
|
return sdk
|
|
41
44
|
|
|
42
45
|
|
|
@@ -55,15 +58,15 @@ def _thinking_budget() -> int:
|
|
|
55
58
|
|
|
56
59
|
def _build_options(sdk: Any, provider: Provider, request: ChatRequest) -> Any:
|
|
57
60
|
"""Assemble ClaudeAgentOptions for a tool-free single-turn chat."""
|
|
58
|
-
opts: dict[str, Any] =
|
|
59
|
-
tools
|
|
60
|
-
allowed_tools
|
|
61
|
-
disallowed_tools
|
|
62
|
-
max_turns
|
|
63
|
-
permission_mode
|
|
64
|
-
include_partial_messages
|
|
65
|
-
setting_sources
|
|
66
|
-
|
|
61
|
+
opts: dict[str, Any] = {
|
|
62
|
+
"tools": [], # no built-in tools — plain chat, no file/shell access
|
|
63
|
+
"allowed_tools": [],
|
|
64
|
+
"disallowed_tools": [],
|
|
65
|
+
"max_turns": 1,
|
|
66
|
+
"permission_mode": "bypassPermissions",
|
|
67
|
+
"include_partial_messages": True, # token-level streaming deltas
|
|
68
|
+
"setting_sources": [], # ignore project/user CLAUDE.md, settings, etc.
|
|
69
|
+
}
|
|
67
70
|
if provider.model:
|
|
68
71
|
opts["model"] = provider.model
|
|
69
72
|
if request.system:
|
|
@@ -84,13 +87,22 @@ def _build_options(sdk: Any, provider: Provider, request: ChatRequest) -> Any:
|
|
|
84
87
|
opts["thinking"] = {"type": "disabled"}
|
|
85
88
|
return sdk.ClaudeAgentOptions(**opts)
|
|
86
89
|
|
|
90
|
+
# ``display: "summarized"`` is required on current models: they default it
|
|
91
|
+
# to ``omitted``, which silences thinking entirely — no deltas stream and
|
|
92
|
+
# the final ThinkingBlock is empty (verified: enabled+budget without display
|
|
93
|
+
# streams 0 chars of thinking; with display, ~tokens arrive). Mirrors the
|
|
94
|
+
# anthropic adapter's same-named field.
|
|
87
95
|
flag = provider.enable_thinking or "auto"
|
|
88
96
|
if flag == "false":
|
|
89
97
|
opts["thinking"] = {"type": "disabled"}
|
|
90
98
|
elif flag == "true":
|
|
91
|
-
opts["thinking"] = {
|
|
99
|
+
opts["thinking"] = {
|
|
100
|
+
"type": "enabled",
|
|
101
|
+
"budget_tokens": _thinking_budget(),
|
|
102
|
+
"display": "summarized",
|
|
103
|
+
}
|
|
92
104
|
else:
|
|
93
|
-
opts["thinking"] = {"type": "adaptive"}
|
|
105
|
+
opts["thinking"] = {"type": "adaptive", "display": "summarized"}
|
|
94
106
|
|
|
95
107
|
return sdk.ClaudeAgentOptions(**opts)
|
|
96
108
|
|
|
@@ -143,7 +155,7 @@ async def _run(
|
|
|
143
155
|
|
|
144
156
|
def structured_claude_code(
|
|
145
157
|
provider: Provider, request: ChatRequest
|
|
146
|
-
) ->
|
|
158
|
+
) -> tuple[int, Any]:
|
|
147
159
|
"""Structured output via the SDK's native ``output_format`` (set in
|
|
148
160
|
``_build_options`` when the request carries a schema). Runs the single-turn
|
|
149
161
|
query and returns ``(exit_code, structured_output)`` from the ResultMessage —
|
|
@@ -20,7 +20,6 @@ openai-compatible path never requires it.
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
22
|
import os
|
|
23
|
-
import sys
|
|
24
23
|
from typing import Any, Optional
|
|
25
24
|
|
|
26
25
|
from ..bridge import DEFAULT_ENDPOINT, ChatRequest, _Emitter
|
|
@@ -33,14 +32,12 @@ def _require_sdk() -> Any:
|
|
|
33
32
|
try:
|
|
34
33
|
from google import genai # type: ignore[import-untyped,import-not-found]
|
|
35
34
|
from google.genai import types # type: ignore[import-untyped,import-not-found]
|
|
36
|
-
except ImportError:
|
|
37
|
-
|
|
35
|
+
except ImportError as e:
|
|
36
|
+
raise ImportError(
|
|
38
37
|
"llmkit: the google adapter needs the Google GenAI SDK. "
|
|
39
38
|
"Install it with `pip install llmkit[google]` (or `pip install "
|
|
40
|
-
"google-genai`)."
|
|
41
|
-
|
|
42
|
-
)
|
|
43
|
-
raise SystemExit(2)
|
|
39
|
+
"google-genai`)."
|
|
40
|
+
) from e
|
|
44
41
|
return genai, types
|
|
45
42
|
|
|
46
43
|
|
|
@@ -336,7 +336,7 @@ def complete(
|
|
|
336
336
|
) -> int:
|
|
337
337
|
"""Run a text-completion (FIM) request, streaming text to ``out``
|
|
338
338
|
(default stdout). ``openai-compatible`` only."""
|
|
339
|
-
|
|
339
|
+
sink = out if out is not None else sys.stdout
|
|
340
340
|
provider = with_defaults(provider)
|
|
341
341
|
if not provider_supports_complete(provider):
|
|
342
342
|
print(
|
|
@@ -349,12 +349,12 @@ def complete(
|
|
|
349
349
|
return 2
|
|
350
350
|
|
|
351
351
|
client = build_client(provider)
|
|
352
|
-
kwargs: dict[str, Any] =
|
|
353
|
-
model
|
|
354
|
-
prompt
|
|
355
|
-
max_tokens
|
|
356
|
-
temperature
|
|
357
|
-
|
|
352
|
+
kwargs: dict[str, Any] = {
|
|
353
|
+
"model": provider.model,
|
|
354
|
+
"prompt": request.prompt,
|
|
355
|
+
"max_tokens": provider.max_tokens,
|
|
356
|
+
"temperature": provider.temperature,
|
|
357
|
+
}
|
|
358
358
|
if request.suffix:
|
|
359
359
|
kwargs["suffix"] = request.suffix
|
|
360
360
|
if request.stop:
|
|
@@ -364,8 +364,8 @@ def complete(
|
|
|
364
364
|
for chunk in client.completions.create(**kwargs):
|
|
365
365
|
t = chunk.choices[0].text if chunk.choices else ""
|
|
366
366
|
if t:
|
|
367
|
-
|
|
368
|
-
|
|
367
|
+
sink.write(t)
|
|
368
|
+
sink.flush()
|
|
369
369
|
except KeyboardInterrupt:
|
|
370
370
|
return 130
|
|
371
371
|
except Exception as e:
|
|
@@ -172,3 +172,9 @@ if __name__ == "__main__":
|
|
|
172
172
|
sys.exit(main())
|
|
173
173
|
except BrokenPipeError:
|
|
174
174
|
sys.exit(0)
|
|
175
|
+
except ImportError as e:
|
|
176
|
+
# A missing optional-adapter dependency. The adapters raise a catchable
|
|
177
|
+
# ImportError (never SystemExit, so library hosts survive); the CLI turns
|
|
178
|
+
# it back into the actionable message + exit(2).
|
|
179
|
+
print(e, file=sys.stderr)
|
|
180
|
+
sys.exit(2)
|
|
@@ -10,7 +10,6 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import os
|
|
12
12
|
import re
|
|
13
|
-
import sys
|
|
14
13
|
import time
|
|
15
14
|
import uuid
|
|
16
15
|
from typing import Any, Callable, Dict, Optional
|
|
@@ -55,14 +54,12 @@ def build_client(provider: Provider) -> Any:
|
|
|
55
54
|
hard import of ``openai.OpenAI`` at module load."""
|
|
56
55
|
try:
|
|
57
56
|
from openai import OpenAI
|
|
58
|
-
except ImportError:
|
|
59
|
-
|
|
57
|
+
except ImportError as e:
|
|
58
|
+
raise ImportError(
|
|
60
59
|
"llmkit: the openai package is not installed. "
|
|
61
60
|
"Install it with `pip install llmkit[bridge]` (or `pip install "
|
|
62
|
-
"openai`)."
|
|
63
|
-
|
|
64
|
-
)
|
|
65
|
-
raise SystemExit(2)
|
|
61
|
+
"openai`)."
|
|
62
|
+
) from e
|
|
66
63
|
return OpenAI(
|
|
67
64
|
base_url=provider.endpoint,
|
|
68
65
|
api_key=_resolve_api_key(provider),
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|