krons 0.1.1__py3-none-any.whl → 0.2.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.
- krons/__init__.py +49 -0
- krons/agent/__init__.py +144 -0
- krons/agent/mcps/__init__.py +14 -0
- krons/agent/mcps/loader.py +287 -0
- krons/agent/mcps/wrapper.py +799 -0
- krons/agent/message/__init__.py +20 -0
- krons/agent/message/action.py +69 -0
- krons/agent/message/assistant.py +52 -0
- krons/agent/message/common.py +49 -0
- krons/agent/message/instruction.py +130 -0
- krons/agent/message/prepare_msg.py +187 -0
- krons/agent/message/role.py +53 -0
- krons/agent/message/system.py +53 -0
- krons/agent/operations/__init__.py +82 -0
- krons/agent/operations/act.py +100 -0
- krons/agent/operations/generate.py +145 -0
- krons/agent/operations/llm_reparse.py +89 -0
- krons/agent/operations/operate.py +247 -0
- krons/agent/operations/parse.py +243 -0
- krons/agent/operations/react.py +286 -0
- krons/agent/operations/specs.py +235 -0
- krons/agent/operations/structure.py +151 -0
- krons/agent/operations/utils.py +79 -0
- krons/agent/providers/__init__.py +17 -0
- krons/agent/providers/anthropic_messages.py +146 -0
- krons/agent/providers/claude_code.py +276 -0
- krons/agent/providers/gemini.py +268 -0
- krons/agent/providers/match.py +75 -0
- krons/agent/providers/oai_chat.py +174 -0
- krons/agent/third_party/__init__.py +2 -0
- krons/agent/third_party/anthropic_models.py +154 -0
- krons/agent/third_party/claude_code.py +682 -0
- krons/agent/third_party/gemini_models.py +508 -0
- krons/agent/third_party/openai_models.py +295 -0
- krons/agent/tool.py +291 -0
- krons/core/__init__.py +56 -74
- krons/core/base/__init__.py +121 -0
- krons/core/{broadcaster.py → base/broadcaster.py} +7 -3
- krons/core/{element.py → base/element.py} +13 -5
- krons/core/{event.py → base/event.py} +39 -6
- krons/core/{eventbus.py → base/eventbus.py} +3 -1
- krons/core/{flow.py → base/flow.py} +11 -4
- krons/core/{graph.py → base/graph.py} +24 -8
- krons/core/{node.py → base/node.py} +44 -19
- krons/core/{pile.py → base/pile.py} +22 -8
- krons/core/{processor.py → base/processor.py} +21 -7
- krons/core/{progression.py → base/progression.py} +3 -1
- krons/{specs → core/specs}/__init__.py +0 -5
- krons/{specs → core/specs}/adapters/dataclass_field.py +16 -8
- krons/{specs → core/specs}/adapters/pydantic_adapter.py +11 -5
- krons/{specs → core/specs}/adapters/sql_ddl.py +14 -8
- krons/{specs → core/specs}/catalog/__init__.py +2 -2
- krons/{specs → core/specs}/catalog/_audit.py +2 -2
- krons/{specs → core/specs}/catalog/_common.py +2 -2
- krons/{specs → core/specs}/catalog/_content.py +4 -4
- krons/{specs → core/specs}/catalog/_enforcement.py +3 -3
- krons/{specs → core/specs}/factory.py +5 -5
- krons/{specs → core/specs}/operable.py +8 -2
- krons/{specs → core/specs}/protocol.py +4 -2
- krons/{specs → core/specs}/spec.py +23 -11
- krons/{types → core/types}/base.py +4 -2
- krons/{types → core/types}/db_types.py +2 -2
- krons/errors.py +13 -13
- krons/protocols.py +9 -4
- krons/resource/__init__.py +89 -0
- krons/{services → resource}/backend.py +48 -22
- krons/{services → resource}/endpoint.py +28 -14
- krons/{services → resource}/hook.py +20 -7
- krons/{services → resource}/imodel.py +46 -28
- krons/{services → resource}/registry.py +26 -24
- krons/{services → resource}/utilities/rate_limited_executor.py +7 -3
- krons/{services → resource}/utilities/rate_limiter.py +3 -1
- krons/{services → resource}/utilities/resilience.py +15 -5
- krons/resource/utilities/token_calculator.py +185 -0
- krons/session/__init__.py +12 -17
- krons/session/constraints.py +70 -0
- krons/session/exchange.py +11 -3
- krons/session/message.py +3 -1
- krons/session/registry.py +35 -0
- krons/session/session.py +165 -174
- krons/utils/__init__.py +45 -0
- krons/utils/_function_arg_parser.py +99 -0
- krons/utils/_pythonic_function_call.py +249 -0
- krons/utils/_to_list.py +9 -3
- krons/utils/_utils.py +6 -2
- krons/utils/concurrency/_async_call.py +4 -2
- krons/utils/concurrency/_errors.py +3 -1
- krons/utils/concurrency/_patterns.py +3 -1
- krons/utils/concurrency/_resource_tracker.py +6 -2
- krons/utils/display.py +257 -0
- krons/utils/fuzzy/__init__.py +6 -1
- krons/utils/fuzzy/_fuzzy_match.py +14 -8
- krons/utils/fuzzy/_string_similarity.py +3 -1
- krons/utils/fuzzy/_to_dict.py +3 -1
- krons/utils/schemas/__init__.py +26 -0
- krons/utils/schemas/_breakdown_pydantic_annotation.py +131 -0
- krons/utils/schemas/_formatter.py +72 -0
- krons/utils/schemas/_minimal_yaml.py +151 -0
- krons/utils/schemas/_typescript.py +153 -0
- krons/utils/validators/__init__.py +3 -0
- krons/utils/validators/_validate_image_url.py +56 -0
- krons/work/__init__.py +126 -0
- krons/work/engine.py +333 -0
- krons/work/form.py +305 -0
- krons/{operations → work/operations}/__init__.py +7 -4
- krons/{operations → work/operations}/builder.py +1 -1
- krons/{enforcement → work/operations}/context.py +36 -5
- krons/{operations → work/operations}/flow.py +13 -5
- krons/{operations → work/operations}/node.py +45 -43
- krons/work/operations/registry.py +103 -0
- krons/{specs → work}/phrase.py +130 -13
- krons/{enforcement → work}/policy.py +3 -3
- krons/work/report.py +268 -0
- krons/work/rules/__init__.py +47 -0
- krons/{enforcement → work/rules}/common/boolean.py +3 -1
- krons/{enforcement → work/rules}/common/choice.py +9 -3
- krons/{enforcement → work/rules}/common/number.py +3 -1
- krons/{enforcement → work/rules}/common/string.py +9 -3
- krons/{enforcement → work/rules}/rule.py +1 -1
- krons/{enforcement → work/rules}/validator.py +20 -5
- krons/{enforcement → work}/service.py +16 -7
- krons/work/worker.py +266 -0
- {krons-0.1.1.dist-info → krons-0.2.0.dist-info}/METADATA +15 -1
- krons-0.2.0.dist-info/RECORD +154 -0
- krons/enforcement/__init__.py +0 -57
- krons/operations/registry.py +0 -92
- krons/services/__init__.py +0 -81
- krons-0.1.1.dist-info/RECORD +0 -101
- /krons/{specs → core/specs}/adapters/__init__.py +0 -0
- /krons/{specs → core/specs}/adapters/_utils.py +0 -0
- /krons/{specs → core/specs}/adapters/factory.py +0 -0
- /krons/{types → core/types}/__init__.py +0 -0
- /krons/{types → core/types}/_sentinel.py +0 -0
- /krons/{types → core/types}/identity.py +0 -0
- /krons/{services → resource}/utilities/__init__.py +0 -0
- /krons/{services → resource}/utilities/header_factory.py +0 -0
- /krons/{enforcement → work/rules}/common/__init__.py +0 -0
- /krons/{enforcement → work/rules}/common/mapping.py +0 -0
- /krons/{enforcement → work/rules}/common/model.py +0 -0
- /krons/{enforcement → work/rules}/registry.py +0 -0
- {krons-0.1.1.dist-info → krons-0.2.0.dist-info}/WHEEL +0 -0
- {krons-0.1.1.dist-info → krons-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Copyright (c) 2025 - 2026, HaiyangLi <quantocean.li at gmail dot com>
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from types import MappingProxyType
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from krons.resource.backend import NormalizedResponseModel
|
|
10
|
+
from krons.resource.endpoint import Endpoint, EndpointConfig
|
|
11
|
+
|
|
12
|
+
__all__ = (
|
|
13
|
+
"OAIChatEndpoint",
|
|
14
|
+
"create_oai_chat",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
_PROVIDER_MAP: MappingProxyType | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _get_providers() -> MappingProxyType:
|
|
21
|
+
"""Get mapping of provider names to base URLs.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Immutable mapping of provider name to base_url for OpenAI-compatible providers:
|
|
25
|
+
- openai: OpenAI official API
|
|
26
|
+
- groq: Groq fast inference
|
|
27
|
+
- openrouter: Multi-provider gateway
|
|
28
|
+
- nvidia_nim: NVIDIA Inference Microservices
|
|
29
|
+
"""
|
|
30
|
+
global _PROVIDER_MAP
|
|
31
|
+
if _PROVIDER_MAP is not None:
|
|
32
|
+
return _PROVIDER_MAP
|
|
33
|
+
|
|
34
|
+
_PROVIDER_MAP = MappingProxyType(
|
|
35
|
+
{
|
|
36
|
+
"openai": ("https://api.openai.com/v1", "OPENAI_API_KEY"),
|
|
37
|
+
"groq": ("https://api.groq.com/openai/v1", "GROQ_API_KEY"),
|
|
38
|
+
"openrouter": ("https://openrouter.ai/api/v1", "OPENROUTER_API_KEY"),
|
|
39
|
+
"nvidia_nim": ("https://integrate.api.nvidia.com/v1", "NVIDIA_NIM_API_KEY"),
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
return _PROVIDER_MAP
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _ensure_request_options(config: dict) -> dict:
|
|
46
|
+
if config.get("request_options") is not None:
|
|
47
|
+
return config
|
|
48
|
+
|
|
49
|
+
from ..third_party.openai_models import OpenAIChatCompletionsRequest
|
|
50
|
+
|
|
51
|
+
return {**config, "request_options": OpenAIChatCompletionsRequest}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def create_oai_chat(
|
|
55
|
+
provider: str = "openai",
|
|
56
|
+
name: str | None = None,
|
|
57
|
+
base_url: str | None = None,
|
|
58
|
+
api_key: str | None = None,
|
|
59
|
+
endpoint: str = "chat/completions",
|
|
60
|
+
) -> dict:
|
|
61
|
+
"""Factory for OpenAI-compatible Chat API config.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
provider: Provider name (openai, groq, openrouter, nvidia_nim)
|
|
65
|
+
name: Config name (default: "{provider}_chat")
|
|
66
|
+
base_url: Base API URL (optional, auto-detected from provider)
|
|
67
|
+
api_key: API key or env var name (default: "{PROVIDER}_API_KEY")
|
|
68
|
+
endpoint: Endpoint path
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
# Get provider base URL
|
|
72
|
+
base_url = base_url or _get_providers().get(provider, (None,))[0]
|
|
73
|
+
if base_url is None:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
f"Unknown provider: {provider}. Available: {list(_get_providers().keys())}"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Set default name
|
|
79
|
+
if name is None:
|
|
80
|
+
name = f"{provider}_chat"
|
|
81
|
+
|
|
82
|
+
# Set default API key env var
|
|
83
|
+
if api_key is None:
|
|
84
|
+
api_key = _get_providers().get(provider, (None, None))[1]
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
"name": name,
|
|
88
|
+
"provider": provider,
|
|
89
|
+
"base_url": base_url,
|
|
90
|
+
"endpoint": endpoint,
|
|
91
|
+
"api_key": api_key,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class OAIChatEndpoint(Endpoint):
|
|
96
|
+
"""OpenAI Chat Completions API endpoint.
|
|
97
|
+
|
|
98
|
+
Supports OpenAI and OpenAI-compatible providers.
|
|
99
|
+
|
|
100
|
+
Usage:
|
|
101
|
+
endpoint = OpenAIChatEndpoint()
|
|
102
|
+
response = await endpoint.call({
|
|
103
|
+
"model": "gpt-4o-mini",
|
|
104
|
+
"messages": [{"role": "user", "content": "Hello"}]
|
|
105
|
+
})
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
config: dict | EndpointConfig | None = None,
|
|
111
|
+
circuit_breaker: Any | None = None,
|
|
112
|
+
**kwargs,
|
|
113
|
+
):
|
|
114
|
+
"""Initialize with OpenAI config."""
|
|
115
|
+
if config is None:
|
|
116
|
+
# Extract provider-specific kwargs for create_oai_chat
|
|
117
|
+
config = create_oai_chat(
|
|
118
|
+
provider=kwargs.pop("provider", "openai"),
|
|
119
|
+
name=kwargs.pop("name", None),
|
|
120
|
+
base_url=kwargs.pop("base_url", None),
|
|
121
|
+
api_key=kwargs.pop("api_key", None),
|
|
122
|
+
endpoint=kwargs.pop("endpoint", "chat/completions"),
|
|
123
|
+
)
|
|
124
|
+
elif isinstance(config, EndpointConfig):
|
|
125
|
+
config = config.model_dump()
|
|
126
|
+
if not isinstance(config, dict):
|
|
127
|
+
raise ValueError(
|
|
128
|
+
"Provided config must be a dict or EndpointConfig instance"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if kwargs.get("request_options") is None:
|
|
132
|
+
config = _ensure_request_options(config)
|
|
133
|
+
|
|
134
|
+
super().__init__(config=config, circuit_breaker=circuit_breaker, **kwargs)
|
|
135
|
+
|
|
136
|
+
def normalize_response(
|
|
137
|
+
self, raw_response: dict[str, Any]
|
|
138
|
+
) -> NormalizedResponseModel:
|
|
139
|
+
"""Normalize OpenAI response to standard format.
|
|
140
|
+
|
|
141
|
+
Extracts:
|
|
142
|
+
- Text from choices[0].message.content
|
|
143
|
+
- Usage stats
|
|
144
|
+
- Model info
|
|
145
|
+
- Finish reason
|
|
146
|
+
- Tool calls (if present)
|
|
147
|
+
"""
|
|
148
|
+
# Extract text from first choice
|
|
149
|
+
text = ""
|
|
150
|
+
choices = raw_response.get("choices")
|
|
151
|
+
if choices and len(choices) > 0:
|
|
152
|
+
choice = choices[0]
|
|
153
|
+
message = choice.get("message", {})
|
|
154
|
+
text = message.get("content") or ""
|
|
155
|
+
|
|
156
|
+
# Extract metadata
|
|
157
|
+
metadata: dict[str, Any] = {
|
|
158
|
+
k: raw_response[k] for k in ("model", "usage", "id") if k in raw_response
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if choices and len(choices) > 0:
|
|
162
|
+
choice = choices[0]
|
|
163
|
+
metadata.update({k: choice[k] for k in ("finish_reason",) if k in choice})
|
|
164
|
+
|
|
165
|
+
# Extract tool calls if present
|
|
166
|
+
message = choice.get("message", {})
|
|
167
|
+
metadata.update({k: message[k] for k in ("tool_calls",) if k in message})
|
|
168
|
+
|
|
169
|
+
return NormalizedResponseModel(
|
|
170
|
+
status="success",
|
|
171
|
+
data=text,
|
|
172
|
+
raw_response=raw_response,
|
|
173
|
+
metadata=metadata,
|
|
174
|
+
)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Copyright (c) 2025 - 2026, HaiyangLi <quantocean.li at gmail dot com>
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Literal, Union
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TextContentBlock(BaseModel):
|
|
12
|
+
type: Literal["text"] = "text"
|
|
13
|
+
text: str
|
|
14
|
+
cache_control: dict | None = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ImageSource(BaseModel):
|
|
18
|
+
type: Literal["base64"] = "base64"
|
|
19
|
+
media_type: Literal["image/jpeg", "image/png", "image/gif", "image/webp"]
|
|
20
|
+
data: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ImageContentBlock(BaseModel):
|
|
24
|
+
type: Literal["image"] = "image"
|
|
25
|
+
source: ImageSource
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
ContentBlock = Union[TextContentBlock, ImageContentBlock]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Message(BaseModel):
|
|
32
|
+
role: Literal["user", "assistant"]
|
|
33
|
+
content: str | list[str | ContentBlock]
|
|
34
|
+
|
|
35
|
+
@field_validator("content", mode="before")
|
|
36
|
+
@classmethod
|
|
37
|
+
def validate_content(cls, v):
|
|
38
|
+
"""Convert string content to proper format."""
|
|
39
|
+
if isinstance(v, str):
|
|
40
|
+
return v
|
|
41
|
+
if isinstance(v, list):
|
|
42
|
+
# Ensure all items are either strings or proper content blocks
|
|
43
|
+
result = []
|
|
44
|
+
for item in v:
|
|
45
|
+
if isinstance(item, str):
|
|
46
|
+
result.append({"type": "text", "text": item})
|
|
47
|
+
else:
|
|
48
|
+
result.append(item)
|
|
49
|
+
return result
|
|
50
|
+
return v
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ToolDefinition(BaseModel):
|
|
54
|
+
name: str = Field(..., min_length=1, max_length=64, pattern="^[a-zA-Z0-9_-]+$")
|
|
55
|
+
description: str | None = None
|
|
56
|
+
input_schema: dict
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ToolChoice(BaseModel):
|
|
60
|
+
type: Literal["auto", "any", "tool"]
|
|
61
|
+
name: str | None = None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class CreateMessageRequest(BaseModel):
|
|
65
|
+
"""Request model for Anthropic messages API."""
|
|
66
|
+
|
|
67
|
+
model: str = Field(..., min_length=1, max_length=256)
|
|
68
|
+
messages: list[Message]
|
|
69
|
+
max_tokens: int = Field(..., ge=1)
|
|
70
|
+
|
|
71
|
+
# Optional fields
|
|
72
|
+
system: str | list[ContentBlock] | None = None
|
|
73
|
+
temperature: float | None = Field(None, ge=0, le=1)
|
|
74
|
+
top_p: float | None = Field(None, ge=0, le=1)
|
|
75
|
+
top_k: int | None = Field(None, ge=0)
|
|
76
|
+
stop_sequences: list[str] | None = None
|
|
77
|
+
stream: bool | None = False
|
|
78
|
+
metadata: dict | None = None
|
|
79
|
+
tools: list[ToolDefinition] | None = None
|
|
80
|
+
tool_choice: ToolChoice | dict | None = None
|
|
81
|
+
|
|
82
|
+
model_config = ConfigDict(extra="forbid")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Usage(BaseModel):
|
|
86
|
+
"""Token usage information."""
|
|
87
|
+
|
|
88
|
+
input_tokens: int
|
|
89
|
+
output_tokens: int
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ContentBlockResponse(BaseModel):
|
|
93
|
+
"""Response content block."""
|
|
94
|
+
|
|
95
|
+
type: Literal["text"]
|
|
96
|
+
text: str
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class CreateMessageResponse(BaseModel):
|
|
100
|
+
"""Response model for Anthropic messages API."""
|
|
101
|
+
|
|
102
|
+
id: str
|
|
103
|
+
type: Literal["message"] = "message"
|
|
104
|
+
role: Literal["assistant"] = "assistant"
|
|
105
|
+
content: list[ContentBlockResponse]
|
|
106
|
+
model: str
|
|
107
|
+
stop_reason: (
|
|
108
|
+
None | (Literal["end_turn", "max_tokens", "stop_sequence", "tool_use"])
|
|
109
|
+
) = None
|
|
110
|
+
stop_sequence: str | None = None
|
|
111
|
+
usage: Usage
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# Streaming response models
|
|
115
|
+
class MessageStartEvent(BaseModel):
|
|
116
|
+
type: Literal["message_start"] = "message_start"
|
|
117
|
+
message: CreateMessageResponse
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class ContentBlockStartEvent(BaseModel):
|
|
121
|
+
type: Literal["content_block_start"] = "content_block_start"
|
|
122
|
+
index: int
|
|
123
|
+
content_block: ContentBlockResponse
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ContentBlockDeltaEvent(BaseModel):
|
|
127
|
+
type: Literal["content_block_delta"] = "content_block_delta"
|
|
128
|
+
index: int
|
|
129
|
+
delta: dict
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class ContentBlockStopEvent(BaseModel):
|
|
133
|
+
type: Literal["content_block_stop"] = "content_block_stop"
|
|
134
|
+
index: int
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class MessageDeltaEvent(BaseModel):
|
|
138
|
+
type: Literal["message_delta"] = "message_delta"
|
|
139
|
+
delta: dict
|
|
140
|
+
usage: Usage | None = None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class MessageStopEvent(BaseModel):
|
|
144
|
+
type: Literal["message_stop"] = "message_stop"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
StreamEvent = Union[
|
|
148
|
+
MessageStartEvent,
|
|
149
|
+
ContentBlockStartEvent,
|
|
150
|
+
ContentBlockDeltaEvent,
|
|
151
|
+
ContentBlockStopEvent,
|
|
152
|
+
MessageDeltaEvent,
|
|
153
|
+
MessageStopEvent,
|
|
154
|
+
]
|