karta-runtime 0.1.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.
- karta/__init__.py +48 -0
- karta/__main__.py +4 -0
- karta/active_releases.py +129 -0
- karta/agent.py +59 -0
- karta/app.py +672 -0
- karta/billing/__init__.py +13 -0
- karta/billing/usage_emitter.py +416 -0
- karta/builder.py +223 -0
- karta/byok.py +206 -0
- karta/cache.py +257 -0
- karta/cli.py +609 -0
- karta/cli_renderer.py +173 -0
- karta/consumer/__init__.py +7 -0
- karta/consumer/challenge.py +116 -0
- karta/consumer/embed.py +310 -0
- karta/consumer/event_log.py +80 -0
- karta/consumer/events.py +34 -0
- karta/consumer/managed_agents.py +66 -0
- karta/consumer/openai_chat.py +97 -0
- karta/consumer/openai_responses.py +116 -0
- karta/consumer/project_origins.py +98 -0
- karta/consumer/session_token.py +78 -0
- karta/control_plane.py +140 -0
- karta/durable/__init__.py +22 -0
- karta/durable/local.py +34 -0
- karta/durable/port.py +19 -0
- karta/durable/s3.py +64 -0
- karta/events.py +21 -0
- karta/execution/__init__.py +22 -0
- karta/execution/fake.py +78 -0
- karta/execution/local.py +96 -0
- karta/execution/port.py +85 -0
- karta/gateway/__init__.py +15 -0
- karta/gateway/base.py +29 -0
- karta/gateway/events.py +73 -0
- karta/gateway/http.py +80 -0
- karta/gateway/local.py +151 -0
- karta/gateway/registry.py +65 -0
- karta/harness/__init__.py +4 -0
- karta/harness/base.py +142 -0
- karta/harness/claude.py +974 -0
- karta/harness/claude_message_mapper.py +418 -0
- karta/harness/factory.py +37 -0
- karta/harness/opencode.py +592 -0
- karta/harness/utils.py +45 -0
- karta/hub.py +126 -0
- karta/instance.py +729 -0
- karta/model_settings.py +134 -0
- karta/observability/__init__.py +156 -0
- karta/observability/_noop.py +75 -0
- karta/observability/_otel.py +81 -0
- karta/observability/logging_setup.py +96 -0
- karta/observability/metrics.py +74 -0
- karta/observability/secret_redaction.py +96 -0
- karta/participant.py +55 -0
- karta/persistence/__init__.py +46 -0
- karta/persistence/gc.py +164 -0
- karta/persistence/local_backend.py +128 -0
- karta/persistence/manifest.py +225 -0
- karta/persistence/profile.py +156 -0
- karta/persistence/reconcile.py +76 -0
- karta/persistence/s3_backend.py +131 -0
- karta/persistence/store.py +297 -0
- karta/persistence/text_merge.py +70 -0
- karta/policies.py +29 -0
- karta/project_runtime.py +201 -0
- karta/registry.py +137 -0
- karta/release_artifacts.py +70 -0
- karta/releases.py +478 -0
- karta/response.py +150 -0
- karta/runtime/__init__.py +3 -0
- karta/runtime/turn_accumulator.py +146 -0
- karta/secrets_bootstrap.py +88 -0
- karta/server/__init__.py +3 -0
- karta/server/agentcore.py +393 -0
- karta/server/agentcore_client.py +481 -0
- karta/server/agentcore_project_runtime.py +210 -0
- karta/server/agentcore_router.py +118 -0
- karta/server/agentcore_serve.py +116 -0
- karta/server/auth.py +625 -0
- karta/server/credential_guard.py +51 -0
- karta/server/fastapi_app.py +2454 -0
- karta/server/instance_routes.py +199 -0
- karta/server/serve_surfaces.py +231 -0
- karta/server/session_store.py +220 -0
- karta/server/sse.py +10 -0
- karta/session.py +161 -0
- karta/vault/__init__.py +21 -0
- karta/vault/agentcore.py +33 -0
- karta/vault/local.py +43 -0
- karta/vault/port.py +28 -0
- karta/workspace.py +489 -0
- karta_runtime-0.1.0.dist-info/METADATA +396 -0
- karta_runtime-0.1.0.dist-info/RECORD +96 -0
- karta_runtime-0.1.0.dist-info/WHEEL +4 -0
- karta_runtime-0.1.0.dist-info/entry_points.txt +3 -0
karta/__init__.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from karta.agent import Agent
|
|
2
|
+
from karta.app import Karta
|
|
3
|
+
from karta.gateway import (
|
|
4
|
+
Gateway,
|
|
5
|
+
GatewayDelivery,
|
|
6
|
+
GatewayEvent,
|
|
7
|
+
HttpGateway,
|
|
8
|
+
LocalGateway,
|
|
9
|
+
ParticipantRegistry,
|
|
10
|
+
)
|
|
11
|
+
from karta.hub import KartaHub
|
|
12
|
+
from karta.instance import AgentInstance, InstanceManager, InstanceSession, InstanceStore
|
|
13
|
+
from karta.participant import AIAgent, HumanAgent, Participant
|
|
14
|
+
from karta.policies import PolicyViolationError
|
|
15
|
+
from karta.registry import ProjectRegistry, PublishedProject
|
|
16
|
+
from karta.response import Message, Part, Response, StreamEvent, Usage
|
|
17
|
+
from karta.session import Session
|
|
18
|
+
from karta.workspace import UserProjectView, WorkspaceManager
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"AgentInstance",
|
|
22
|
+
"Karta",
|
|
23
|
+
"Agent",
|
|
24
|
+
"AIAgent",
|
|
25
|
+
"Gateway",
|
|
26
|
+
"GatewayDelivery",
|
|
27
|
+
"GatewayEvent",
|
|
28
|
+
"HttpGateway",
|
|
29
|
+
"HumanAgent",
|
|
30
|
+
"InstanceManager",
|
|
31
|
+
"InstanceSession",
|
|
32
|
+
"InstanceStore",
|
|
33
|
+
"KartaHub",
|
|
34
|
+
"LocalGateway",
|
|
35
|
+
"Message",
|
|
36
|
+
"Part",
|
|
37
|
+
"Participant",
|
|
38
|
+
"ParticipantRegistry",
|
|
39
|
+
"PolicyViolationError",
|
|
40
|
+
"ProjectRegistry",
|
|
41
|
+
"PublishedProject",
|
|
42
|
+
"Response",
|
|
43
|
+
"Session",
|
|
44
|
+
"StreamEvent",
|
|
45
|
+
"Usage",
|
|
46
|
+
"UserProjectView",
|
|
47
|
+
"WorkspaceManager",
|
|
48
|
+
]
|
karta/__main__.py
ADDED
karta/active_releases.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Active-release resolution for the data plane.
|
|
2
|
+
|
|
3
|
+
When a session is created for a project URL, the data plane must learn
|
|
4
|
+
WHICH materialized release to run. karta-web is authoritative: it owns
|
|
5
|
+
the ``AgentProject#current_release`` pointer that the deploy/activate
|
|
6
|
+
flow flips. This module fetches that pointer for an ``(org, project)``
|
|
7
|
+
pair and caches it briefly so the hot path doesn't round-trip the
|
|
8
|
+
control plane on every session create.
|
|
9
|
+
|
|
10
|
+
Mirrors :mod:`karta.model_settings` in shape (cached control-plane GET,
|
|
11
|
+
``OrgScopedTtlCache`` keyed by org first so push-invalidation matches).
|
|
12
|
+
The returned :class:`ActiveRelease` carries the project's numeric id and
|
|
13
|
+
version, which :mod:`karta.project_runtime` turns into a deterministic
|
|
14
|
+
on-disk template path via :func:`karta.releases.template_path_for`.
|
|
15
|
+
|
|
16
|
+
Nothing here is secret: a template path / commit sha / version is
|
|
17
|
+
operational metadata already surfaced in dashboards, so no scrubbing.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import Optional
|
|
24
|
+
from urllib.parse import quote
|
|
25
|
+
|
|
26
|
+
from karta import observability as obs
|
|
27
|
+
from karta.cache import OrgScopedTtlCache, env_number
|
|
28
|
+
from karta.control_plane import ControlPlaneUnavailable, control_plane_get
|
|
29
|
+
|
|
30
|
+
logger = obs.get_logger("karta.active_releases")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class ActiveRelease:
|
|
35
|
+
"""The project's currently-active release, as resolved by karta-web."""
|
|
36
|
+
|
|
37
|
+
project_id: int
|
|
38
|
+
version: int
|
|
39
|
+
build_kind: str
|
|
40
|
+
template_path: Optional[str]
|
|
41
|
+
commit_sha: Optional[str]
|
|
42
|
+
status: Optional[str] = None
|
|
43
|
+
image_ref: Optional[str] = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Keyed by (organization_id, project_ref) so invalidate_org matches on
|
|
47
|
+
# the first tuple element. TTL is short by default: the cached value
|
|
48
|
+
# carries the active version, so a longer window means a freshly
|
|
49
|
+
# activated release keeps serving the OLD version until expiry. A new
|
|
50
|
+
# session is cheap to mis-route only within this window; in-flight
|
|
51
|
+
# sessions are pinned to their start version regardless (see
|
|
52
|
+
# project_runtime). karta-web push-invalidates on activate when wired.
|
|
53
|
+
_CACHE_TTL_SECONDS = env_number("KARTA_ACTIVE_RELEASE_CACHE_TTL", 10.0, float)
|
|
54
|
+
_cache: OrgScopedTtlCache[ActiveRelease] = OrgScopedTtlCache(_CACHE_TTL_SECONDS)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def reset_active_release_cache() -> None:
|
|
58
|
+
"""Test/ops hook: drop all cached active-release entries."""
|
|
59
|
+
_cache.clear()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def invalidate_active_release_for_org(organization_id: int) -> int:
|
|
63
|
+
"""Drop every cached active-release entry for ``organization_id``.
|
|
64
|
+
|
|
65
|
+
Called when karta-web posts a CacheInvalidationJob after a release
|
|
66
|
+
is activated or rolled back. Returns the number of entries evicted.
|
|
67
|
+
"""
|
|
68
|
+
return _cache.invalidate_org(organization_id)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def fetch_active_release(
|
|
72
|
+
*,
|
|
73
|
+
organization_id: int,
|
|
74
|
+
project_ref: str,
|
|
75
|
+
control_plane_url: Optional[str] = None,
|
|
76
|
+
service_token: Optional[str] = None,
|
|
77
|
+
) -> Optional[ActiveRelease]:
|
|
78
|
+
"""Return the active release for ``(organization_id, project_ref)``.
|
|
79
|
+
|
|
80
|
+
``project_ref`` is the project slug from the project URL. Returns
|
|
81
|
+
``None`` when the project has no active release, is unknown, or the
|
|
82
|
+
control plane is unreachable. The two are distinguished by caching:
|
|
83
|
+
a definitive miss (404 / non-200) is negative-cached; an outage
|
|
84
|
+
(:class:`ControlPlaneUnavailable`) is not, so the next create retries.
|
|
85
|
+
"""
|
|
86
|
+
key = (organization_id, project_ref)
|
|
87
|
+
hit, cached = _cache.get(key)
|
|
88
|
+
if hit:
|
|
89
|
+
return cached
|
|
90
|
+
|
|
91
|
+
path = f"/internal/projects/{quote(project_ref, safe='')}/active_release"
|
|
92
|
+
try:
|
|
93
|
+
payload = control_plane_get(
|
|
94
|
+
path,
|
|
95
|
+
params={"organization_id": organization_id},
|
|
96
|
+
control_plane_url=control_plane_url,
|
|
97
|
+
service_token=service_token,
|
|
98
|
+
)
|
|
99
|
+
except ControlPlaneUnavailable:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
if payload is None:
|
|
103
|
+
_cache.put(key, None)
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
project_id = payload.get("project_id")
|
|
107
|
+
version = payload.get("version")
|
|
108
|
+
if not isinstance(project_id, int) or not isinstance(version, int):
|
|
109
|
+
# Malformed answer — treat as a definitive miss rather than
|
|
110
|
+
# constructing a half-formed release we can't resolve a path for.
|
|
111
|
+
logger.warning(
|
|
112
|
+
"active_release for org=%s project=%s missing project_id/version",
|
|
113
|
+
organization_id,
|
|
114
|
+
project_ref,
|
|
115
|
+
)
|
|
116
|
+
_cache.put(key, None)
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
release = ActiveRelease(
|
|
120
|
+
project_id=project_id,
|
|
121
|
+
version=version,
|
|
122
|
+
build_kind=str(payload.get("build_kind") or "file_copy"),
|
|
123
|
+
template_path=(str(payload["template_path"]) if payload.get("template_path") else None),
|
|
124
|
+
commit_sha=(str(payload["commit_sha"]) if payload.get("commit_sha") else None),
|
|
125
|
+
status=(str(payload["status"]) if payload.get("status") else None),
|
|
126
|
+
image_ref=(str(payload["image_ref"]) if payload.get("image_ref") else None),
|
|
127
|
+
)
|
|
128
|
+
_cache.put(key, release)
|
|
129
|
+
return release
|
karta/agent.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from karta.harness.base import AgentDefinition
|
|
6
|
+
from karta.response import Response, StreamEvent
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from karta.app import Karta
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Agent:
|
|
13
|
+
def __init__(self, app: "Karta", definition: AgentDefinition) -> None:
|
|
14
|
+
self._app = app
|
|
15
|
+
self.definition = definition
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def name(self) -> str:
|
|
19
|
+
return self.definition.name
|
|
20
|
+
|
|
21
|
+
async def send(
|
|
22
|
+
self,
|
|
23
|
+
text: str,
|
|
24
|
+
*,
|
|
25
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
26
|
+
thinking: bool = False,
|
|
27
|
+
) -> Response:
|
|
28
|
+
return await self._app.send(text, metadata=metadata, agent=self, thinking=thinking)
|
|
29
|
+
|
|
30
|
+
def send_sync(
|
|
31
|
+
self,
|
|
32
|
+
text: str,
|
|
33
|
+
*,
|
|
34
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
35
|
+
thinking: bool = False,
|
|
36
|
+
) -> Response:
|
|
37
|
+
return self._app.send_sync(text, metadata=metadata, agent=self, thinking=thinking)
|
|
38
|
+
|
|
39
|
+
async def stream(
|
|
40
|
+
self,
|
|
41
|
+
text: str,
|
|
42
|
+
*,
|
|
43
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
44
|
+
thinking: bool = False,
|
|
45
|
+
) -> AsyncIterator[StreamEvent]:
|
|
46
|
+
async for event in self._app.stream(text, metadata=metadata, agent=self, thinking=thinking):
|
|
47
|
+
yield event
|
|
48
|
+
|
|
49
|
+
def stream_sync(
|
|
50
|
+
self,
|
|
51
|
+
text: str,
|
|
52
|
+
*,
|
|
53
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
54
|
+
thinking: bool = False,
|
|
55
|
+
):
|
|
56
|
+
return self._app.stream_sync(text, metadata=metadata, agent=self, thinking=thinking)
|
|
57
|
+
|
|
58
|
+
def __repr__(self) -> str:
|
|
59
|
+
return f"Agent(name={self.name!r})"
|