terok-executor 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.
- terok_executor/__init__.py +179 -0
- terok_executor/_tree.py +39 -0
- terok_executor/_util/__init__.py +18 -0
- terok_executor/_util/_timezone.py +49 -0
- terok_executor/_util/_yaml.py +24 -0
- terok_executor/acp/__init__.py +69 -0
- terok_executor/acp/cache.py +81 -0
- terok_executor/acp/daemon.py +250 -0
- terok_executor/acp/endpoint.py +35 -0
- terok_executor/acp/model_options.py +159 -0
- terok_executor/acp/probe.py +156 -0
- terok_executor/acp/proxy.py +640 -0
- terok_executor/acp/roster.py +271 -0
- terok_executor/cli.py +91 -0
- terok_executor/commands.py +955 -0
- terok_executor/config.py +31 -0
- terok_executor/config_schema.py +150 -0
- terok_executor/container/__init__.py +9 -0
- terok_executor/container/build.py +1133 -0
- terok_executor/container/cache.py +127 -0
- terok_executor/container/env.py +599 -0
- terok_executor/container/inject.py +41 -0
- terok_executor/container/runner.py +1230 -0
- terok_executor/container/sidecar.py +166 -0
- terok_executor/credentials/__init__.py +10 -0
- terok_executor/credentials/auth.py +1027 -0
- terok_executor/credentials/extractors.py +217 -0
- terok_executor/credentials/vault_commands.py +202 -0
- terok_executor/credentials/vault_config.py +612 -0
- terok_executor/credentials/vendor_files.py +257 -0
- terok_executor/doctor.py +325 -0
- terok_executor/integrations/__init__.py +17 -0
- terok_executor/integrations/sandbox.py +125 -0
- terok_executor/krun.py +369 -0
- terok_executor/paths.py +40 -0
- terok_executor/preflight.py +447 -0
- terok_executor/provider/__init__.py +12 -0
- terok_executor/provider/agents.py +562 -0
- terok_executor/provider/instructions.py +126 -0
- terok_executor/provider/providers.py +496 -0
- terok_executor/provider/wrappers.py +541 -0
- terok_executor/py.typed +0 -0
- terok_executor/resources/__init__.py +4 -0
- terok_executor/resources/agents/__init__.py +4 -0
- terok_executor/resources/agents/blablador.yaml +68 -0
- terok_executor/resources/agents/caddy.yaml +29 -0
- terok_executor/resources/agents/claude.yaml +115 -0
- terok_executor/resources/agents/coderabbit.yaml +33 -0
- terok_executor/resources/agents/codex.yaml +89 -0
- terok_executor/resources/agents/copilot.yaml +39 -0
- terok_executor/resources/agents/gh.yaml +58 -0
- terok_executor/resources/agents/glab.yaml +65 -0
- terok_executor/resources/agents/kisski.yaml +62 -0
- terok_executor/resources/agents/opencode.yaml +65 -0
- terok_executor/resources/agents/openrouter.yaml +68 -0
- terok_executor/resources/agents/pi.yaml +63 -0
- terok_executor/resources/agents/sonar.yaml +55 -0
- terok_executor/resources/agents/toad.yaml +40 -0
- terok_executor/resources/agents/vibe.yaml +82 -0
- terok_executor/resources/instructions/__init__.py +4 -0
- terok_executor/resources/instructions/default.md +54 -0
- terok_executor/resources/scripts/Caddyfile +39 -0
- terok_executor/resources/scripts/__init__.py +4 -0
- terok_executor/resources/scripts/allthethings.sh +40 -0
- terok_executor/resources/scripts/hilfe +72 -0
- terok_executor/resources/scripts/init-ssh-and-repo.sh +445 -0
- terok_executor/resources/scripts/mistral-model-sync.py +292 -0
- terok_executor/resources/scripts/opencode-provider +390 -0
- terok_executor/resources/scripts/opencode-provider-acp +34 -0
- terok_executor/resources/scripts/opencode-session-plugin.mjs +26 -0
- terok_executor/resources/scripts/opencode-toad +63 -0
- terok_executor/resources/scripts/pi-env.sh +34 -0
- terok_executor/resources/scripts/pi-vault-routes.mjs +46 -0
- terok_executor/resources/scripts/setup-codex-auth.sh +54 -0
- terok_executor/resources/scripts/terok-acp-env.sh +23 -0
- terok_executor/resources/scripts/terok-bash-banner.sh +14 -0
- terok_executor/resources/scripts/terok-claude-acp +30 -0
- terok_executor/resources/scripts/terok-codex-acp +34 -0
- terok_executor/resources/scripts/terok-copilot-acp +37 -0
- terok_executor/resources/scripts/terok-env-git-identity.sh +61 -0
- terok_executor/resources/scripts/terok-env.sh +96 -0
- terok_executor/resources/scripts/terok-opencode-acp +24 -0
- terok_executor/resources/scripts/terok-toad-entry +79 -0
- terok_executor/resources/scripts/terok-trust-workspace.py +104 -0
- terok_executor/resources/scripts/terok-vibe-acp +91 -0
- terok_executor/resources/scripts/toad +128 -0
- terok_executor/resources/scripts/update-all-the-things +94 -0
- terok_executor/resources/scripts/vibe-model-sync.sh +37 -0
- terok_executor/resources/templates/__init__.py +4 -0
- terok_executor/resources/templates/l0.dev.Dockerfile.template +142 -0
- terok_executor/resources/templates/l1.agent-cli.Dockerfile.template +129 -0
- terok_executor/resources/templates/l1.sidecar.Dockerfile.template +38 -0
- terok_executor/resources/tmux/__init__.py +4 -0
- terok_executor/resources/tmux/container-tmux.conf +21 -0
- terok_executor/resources/toad-agents/__init__.py +4 -0
- terok_executor/resources/toad-agents/blablador.helmholtz.de.toml +31 -0
- terok_executor/resources/toad-agents/kisski.academiccloud.de.toml +31 -0
- terok_executor/roster/__init__.py +18 -0
- terok_executor/roster/loader.py +643 -0
- terok_executor/roster/schema.py +490 -0
- terok_executor/roster/types.py +181 -0
- terok_executor/sandbox.py +53 -0
- terok_executor/storage.py +110 -0
- terok_executor/vault_addr.py +41 -0
- terok_executor-0.1.0.dist-info/METADATA +160 -0
- terok_executor-0.1.0.dist-info/RECORD +110 -0
- terok_executor-0.1.0.dist-info/WHEEL +4 -0
- terok_executor-0.1.0.dist-info/entry_points.txt +3 -0
- terok_executor-0.1.0.dist-info/licenses/LICENSE +177 -0
- terok_executor-0.1.0.dist-info/licenses/LICENSES/Apache-2.0.txt +202 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Jiri Vyskocil
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""terok-executor: single-agent task runner for hardened Podman containers.
|
|
5
|
+
|
|
6
|
+
Builds agent images, launches instrumented containers, and manages the
|
|
7
|
+
lifecycle of one AI coding agent at a time. Designed for standalone use
|
|
8
|
+
(``terok-executor run claude .``) and as a library for terok orchestration.
|
|
9
|
+
|
|
10
|
+
The public surface is ``__all__`` below. Key entry points:
|
|
11
|
+
|
|
12
|
+
- [`AgentRunner`][terok_executor.AgentRunner] — launch agents in containers
|
|
13
|
+
- [`Authenticator`][terok_executor.Authenticator] — credential flow
|
|
14
|
+
- [`ImageBuilder`][terok_executor.ImageBuilder] — image construction
|
|
15
|
+
- [`AgentRoster.shared`][terok_executor.AgentRoster.shared] — YAML agent registry (process-wide cache)
|
|
16
|
+
|
|
17
|
+
Implementation-detail types (raw config schema fragments, ACP error
|
|
18
|
+
classes, internal result types, sidecar image / inject helpers) stay
|
|
19
|
+
in their submodules; reach into ``terok_executor.<sub>`` when you
|
|
20
|
+
need them.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
__version__: str = "0.1.0" # placeholder; replaced at build time
|
|
24
|
+
|
|
25
|
+
from importlib.metadata import PackageNotFoundError, version as _meta_version
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
__version__ = _meta_version("terok-executor")
|
|
29
|
+
except PackageNotFoundError:
|
|
30
|
+
pass # editable install or running from source without metadata
|
|
31
|
+
|
|
32
|
+
# -- terok-util shared types (re-exported for convenience) --------------------
|
|
33
|
+
from terok_util import ConfigStack
|
|
34
|
+
|
|
35
|
+
# -- terok-sandbox protocol types (re-exported for convenience) ----------------
|
|
36
|
+
from terok_executor.integrations.sandbox import ConfigScope
|
|
37
|
+
|
|
38
|
+
# -- Commands + CLI surface ----------------------------------------------------
|
|
39
|
+
from ._tree import COMMANDS
|
|
40
|
+
|
|
41
|
+
# -- ACP host-proxy (per-task multi-agent aggregator) -------------------------
|
|
42
|
+
from .acp import ACPEndpointStatus, acp_socket_is_live, list_authenticated_agents
|
|
43
|
+
from .commands import COMMANDS as AGENT_COMMANDS
|
|
44
|
+
|
|
45
|
+
# -- Config schema + read/write accessors for the executor-owned image: section --
|
|
46
|
+
from .config_schema import ExecutorConfigView, RawImageSection
|
|
47
|
+
|
|
48
|
+
# -- Container (build, env assembly, runner) -----------------------------------
|
|
49
|
+
from .container.build import (
|
|
50
|
+
AGENTS_LABEL,
|
|
51
|
+
DEFAULT_BASE_IMAGE,
|
|
52
|
+
BuildError,
|
|
53
|
+
ImageBuilder,
|
|
54
|
+
ImageSet,
|
|
55
|
+
build_project_image,
|
|
56
|
+
)
|
|
57
|
+
from .container.cache import seed_workspace_from_clone_cache
|
|
58
|
+
from .container.env import ContainerEnvSpec, assemble_container_env
|
|
59
|
+
from .container.inject import inject_prompt
|
|
60
|
+
from .container.runner import AgentRunner
|
|
61
|
+
|
|
62
|
+
# -- Credentials (auth flows, extractors, vault commands) ----------------------
|
|
63
|
+
from .credentials.auth import (
|
|
64
|
+
AUTH_PROVIDERS,
|
|
65
|
+
Authenticator,
|
|
66
|
+
AuthSession,
|
|
67
|
+
prepare_oauth_session,
|
|
68
|
+
store_api_key,
|
|
69
|
+
)
|
|
70
|
+
from .credentials.vault_commands import VAULT_COMMANDS, scan_leaked_credentials
|
|
71
|
+
|
|
72
|
+
# -- Krun (KVM-microVM) provisioning + runtime factory -----------------------
|
|
73
|
+
from .krun import KrunHost, KrunHostKeypair, ensure_krun_host_keypair
|
|
74
|
+
|
|
75
|
+
# -- Provider (descriptor + headless behaviour, instructions, agent config) ----
|
|
76
|
+
from .provider.agents import AgentConfigSpec, parse_md_agent, prepare_agent_config_dir
|
|
77
|
+
from .provider.instructions import bundled_default_instructions, resolve_instructions
|
|
78
|
+
from .provider.providers import (
|
|
79
|
+
AGENT_PROVIDERS,
|
|
80
|
+
PROVIDER_NAMES,
|
|
81
|
+
AgentProvider,
|
|
82
|
+
CLIOverrides,
|
|
83
|
+
get_provider,
|
|
84
|
+
resolve_provider_value,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# -- Roster (agent catalog + config resolution) --------------------------------
|
|
88
|
+
from .roster import AgentRoster
|
|
89
|
+
|
|
90
|
+
# -- Sandbox bootstrap composition ---------------------------------------------
|
|
91
|
+
from .sandbox import ensure_sandbox_ready
|
|
92
|
+
|
|
93
|
+
# -- Storage queries (filesystem footprint measurement) -------------------------
|
|
94
|
+
from .storage import SharedMountStorageInfo, TaskStorageInfo
|
|
95
|
+
|
|
96
|
+
# -- Bootstrap YAML roster into module-level dicts ---------------------------
|
|
97
|
+
# AGENT_PROVIDERS and AUTH_PROVIDERS are empty dicts populated here to avoid
|
|
98
|
+
# circular imports (roster → auth/providers → roster).
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _bootstrap_roster() -> None:
|
|
102
|
+
"""Populate module-level provider dicts from the YAML roster."""
|
|
103
|
+
global PROVIDER_NAMES # noqa: PLW0603 — tuple requires rebind
|
|
104
|
+
|
|
105
|
+
import terok_executor.provider.providers as _reg
|
|
106
|
+
|
|
107
|
+
roster = AgentRoster.shared()
|
|
108
|
+
AGENT_PROVIDERS.update(roster.providers)
|
|
109
|
+
AUTH_PROVIDERS.update(roster.auth_providers)
|
|
110
|
+
PROVIDER_NAMES = _reg.PROVIDER_NAMES = roster.agent_names
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
_bootstrap_roster()
|
|
114
|
+
|
|
115
|
+
__all__ = [
|
|
116
|
+
"__version__",
|
|
117
|
+
# ACP host-proxy
|
|
118
|
+
"ACPEndpointStatus",
|
|
119
|
+
"acp_socket_is_live",
|
|
120
|
+
"list_authenticated_agents",
|
|
121
|
+
# Provider registry + behaviour
|
|
122
|
+
"AGENT_PROVIDERS",
|
|
123
|
+
"AgentProvider",
|
|
124
|
+
"CLIOverrides",
|
|
125
|
+
"PROVIDER_NAMES",
|
|
126
|
+
"get_provider",
|
|
127
|
+
"resolve_provider_value",
|
|
128
|
+
# Agent config preparation
|
|
129
|
+
"AgentConfigSpec",
|
|
130
|
+
"parse_md_agent",
|
|
131
|
+
"prepare_agent_config_dir",
|
|
132
|
+
# Auth
|
|
133
|
+
"AUTH_PROVIDERS",
|
|
134
|
+
"Authenticator",
|
|
135
|
+
"AuthSession",
|
|
136
|
+
"prepare_oauth_session",
|
|
137
|
+
"store_api_key",
|
|
138
|
+
# Instructions
|
|
139
|
+
"bundled_default_instructions",
|
|
140
|
+
"resolve_instructions",
|
|
141
|
+
# Config stack
|
|
142
|
+
"ConfigScope",
|
|
143
|
+
"ConfigStack",
|
|
144
|
+
# Config schema (executor-owned slice of the shared config.yml)
|
|
145
|
+
"ExecutorConfigView",
|
|
146
|
+
"RawImageSection",
|
|
147
|
+
# Build: image construction + resource staging
|
|
148
|
+
"AGENTS_LABEL",
|
|
149
|
+
"DEFAULT_BASE_IMAGE",
|
|
150
|
+
"BuildError",
|
|
151
|
+
"ImageBuilder",
|
|
152
|
+
"ImageSet",
|
|
153
|
+
"build_project_image",
|
|
154
|
+
# Vault credential scanning
|
|
155
|
+
"scan_leaked_credentials",
|
|
156
|
+
# Roster
|
|
157
|
+
"AgentRoster",
|
|
158
|
+
# Command registry
|
|
159
|
+
"AGENT_COMMANDS",
|
|
160
|
+
"COMMANDS",
|
|
161
|
+
"VAULT_COMMANDS",
|
|
162
|
+
# Storage queries
|
|
163
|
+
"SharedMountStorageInfo",
|
|
164
|
+
"TaskStorageInfo",
|
|
165
|
+
# Runner facade
|
|
166
|
+
"AgentRunner",
|
|
167
|
+
# Container environment assembly
|
|
168
|
+
"ContainerEnvSpec",
|
|
169
|
+
"assemble_container_env",
|
|
170
|
+
# Clone cache + injection helpers
|
|
171
|
+
"inject_prompt",
|
|
172
|
+
"seed_workspace_from_clone_cache",
|
|
173
|
+
# Sandbox bootstrap composition
|
|
174
|
+
"ensure_sandbox_ready",
|
|
175
|
+
# Krun (KVM-microVM) provisioning + runtime factory
|
|
176
|
+
"KrunHost",
|
|
177
|
+
"KrunHostKeypair",
|
|
178
|
+
"ensure_krun_host_keypair",
|
|
179
|
+
]
|
terok_executor/_tree.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Jiri Vyskocil
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Composes executor's full [`CommandTree`][terok_util.cli_types.CommandTree].
|
|
5
|
+
|
|
6
|
+
Lives below the CLI surface so the package init can re-export the
|
|
7
|
+
composed tree as ``terok_executor.COMMANDS`` (the cli module is at
|
|
8
|
+
the top of the dependency graph; nothing below it may import it).
|
|
9
|
+
|
|
10
|
+
Three views over one underlying ``SANDBOX_TREE`` instance:
|
|
11
|
+
|
|
12
|
+
- ``terok-executor <own-verb>`` — executor's verbs (run, auth, …)
|
|
13
|
+
- ``terok-executor sandbox <verb>`` — full sandbox tree, deep path
|
|
14
|
+
- ``terok-executor vault <verb>`` — shortcut sharing identity with
|
|
15
|
+
the corresponding subtree under ``sandbox``
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from terok_util import CommandDef, CommandTree
|
|
21
|
+
|
|
22
|
+
from .commands import COMMANDS as OWN_COMMANDS
|
|
23
|
+
from .credentials.vault_commands import SANDBOX_TREE, VAULT_COMMANDS
|
|
24
|
+
|
|
25
|
+
#: Executor's top-level command tree. See module docstring.
|
|
26
|
+
COMMANDS: CommandTree = CommandTree(
|
|
27
|
+
OWN_COMMANDS
|
|
28
|
+
+ (
|
|
29
|
+
CommandDef(
|
|
30
|
+
name="sandbox",
|
|
31
|
+
help="Sandbox subsystem (full deep tree — same verbs as terok-sandbox)",
|
|
32
|
+
children=SANDBOX_TREE.roots,
|
|
33
|
+
),
|
|
34
|
+
)
|
|
35
|
+
+ VAULT_COMMANDS
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = ["COMMANDS"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Jiri Vyskocil
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Re-exports executor-only utilities (timezone, YAML loader) for internal use.
|
|
5
|
+
|
|
6
|
+
Standalone — no terok-executor domain imports, safe to use from any layer.
|
|
7
|
+
Cross-package helpers (``ensure_dir``, ``podman_userns_args``, ...) live in
|
|
8
|
+
the shared [`terok_util`][terok_util] package and are imported from there
|
|
9
|
+
directly at every call site.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from ._timezone import detect_host_timezone
|
|
13
|
+
from ._yaml import load as yaml_load
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"detect_host_timezone",
|
|
17
|
+
"yaml_load",
|
|
18
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Jiri Vyskocil
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Detects the host's IANA timezone for propagation into containers.
|
|
5
|
+
|
|
6
|
+
Returned as a plain string (``"Europe/Prague"``, ``"UTC"``, …) suitable
|
|
7
|
+
for use as a ``TZ`` env var inside the container — glibc resolves it
|
|
8
|
+
against ``/usr/share/zoneinfo`` without needing the host's filesystem.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
_ZONEINFO_MARKER = "/zoneinfo/"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def detect_host_timezone() -> str | None:
|
|
20
|
+
"""Return the host's IANA timezone name, or ``None`` if it can't be detected.
|
|
21
|
+
|
|
22
|
+
Tried in order:
|
|
23
|
+
|
|
24
|
+
1. ``$TZ`` — the user's explicit override.
|
|
25
|
+
2. ``/etc/timezone`` — Debian/Ubuntu convention, single-line zone name.
|
|
26
|
+
3. ``/etc/localtime`` symlink — systemd-family hosts (and macOS) symlink
|
|
27
|
+
this into the zoneinfo database; the zone name is the path suffix
|
|
28
|
+
after the ``zoneinfo/`` component.
|
|
29
|
+
|
|
30
|
+
Returns ``None`` on hosts that expose none of the above (containers with
|
|
31
|
+
only a copied-in ``/etc/localtime`` file, for instance), letting the
|
|
32
|
+
caller fall back to the image default rather than guessing.
|
|
33
|
+
"""
|
|
34
|
+
if tz := os.environ.get("TZ"):
|
|
35
|
+
return tz
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
if zone := Path("/etc/timezone").read_text(encoding="utf-8").strip():
|
|
39
|
+
return zone
|
|
40
|
+
except OSError:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
target = Path("/etc/localtime").resolve().as_posix()
|
|
45
|
+
except OSError:
|
|
46
|
+
return None
|
|
47
|
+
if _ZONEINFO_MARKER in target:
|
|
48
|
+
return target.split(_ZONEINFO_MARKER, 1)[1]
|
|
49
|
+
return None
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Jiri Vyskocil
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Loads YAML strings with round-trip fidelity (comments, order, quotes preserved).
|
|
5
|
+
|
|
6
|
+
Used for frontmatter parsing and config stack loading. Vendored from
|
|
7
|
+
terok.lib.util.yaml — only the load path is needed here.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from ruamel.yaml import YAML
|
|
15
|
+
|
|
16
|
+
__all__ = ["load"]
|
|
17
|
+
|
|
18
|
+
_yaml = YAML(typ="rt")
|
|
19
|
+
_yaml.preserve_quotes = True
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def load(text: str) -> Any:
|
|
23
|
+
"""Round-trip load from a YAML string, preserving comments and order."""
|
|
24
|
+
return _yaml.load(text)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Jiri Vyskocil
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Per-task host-side ACP (Agent Client Protocol) aggregator.
|
|
5
|
+
|
|
6
|
+
Bridges a single ACP client (Zed, Toad, …) to one of several
|
|
7
|
+
in-container agents (claude, codex, copilot, …) by namespacing models
|
|
8
|
+
as ``agent:model`` (e.g. ``claude:opus-4.6``) under ACP's standard
|
|
9
|
+
``category: "model"`` configOption.
|
|
10
|
+
|
|
11
|
+
Module map:
|
|
12
|
+
|
|
13
|
+
- [`daemon`][terok_executor.acp.daemon] — Unix-socket server, container
|
|
14
|
+
lifecycle supervision, and the standalone ``terok-executor acp``
|
|
15
|
+
entry point. Owns [`serve_acp`][terok_executor.acp.daemon.serve_acp]
|
|
16
|
+
and the [`acp_socket_is_live`][terok_executor.acp.daemon.acp_socket_is_live]
|
|
17
|
+
probe used to distinguish live daemons from stale socket files.
|
|
18
|
+
- [`roster`][terok_executor.acp.roster] — per-task aggregation: walks
|
|
19
|
+
the image's ``ai.terok.agents`` label, probes each agent, and answers
|
|
20
|
+
"what models does this container offer?" Owns
|
|
21
|
+
[`ACPRoster`][terok_executor.acp.roster.ACPRoster] and the
|
|
22
|
+
vault-side [`list_authenticated_agents`][terok_executor.acp.roster.list_authenticated_agents].
|
|
23
|
+
- [`proxy`][terok_executor.acp.proxy] — the typed bidirectional ACP
|
|
24
|
+
mediator: implements both `acp.Agent` (toward the connected
|
|
25
|
+
client) and `acp.Client` (toward the bound backend wrapper)
|
|
26
|
+
on one object. Drives the bind handshake on first model pick.
|
|
27
|
+
- [`probe`][terok_executor.acp.probe] — the minimal ``initialize +
|
|
28
|
+
session/new`` handshake that extracts an agent's model roster.
|
|
29
|
+
- [`cache`][terok_executor.acp.cache] — thread-safe per-agent model
|
|
30
|
+
cache; survives reconnects, invalidated on credential rotation.
|
|
31
|
+
- [`endpoint`][terok_executor.acp.endpoint] — the
|
|
32
|
+
[`ACPEndpointStatus`][terok_executor.acp.endpoint.ACPEndpointStatus]
|
|
33
|
+
enum the host CLI uses to classify endpoints in ``terok acp list``.
|
|
34
|
+
- [`model_options`][terok_executor.acp.model_options] — the
|
|
35
|
+
``agent:model`` namespace vocabulary and the typed builders +
|
|
36
|
+
rewriter that keep the proxy's frames schema-valid.
|
|
37
|
+
|
|
38
|
+
Bind-trigger surfaces: explicit ``session/set_model`` /
|
|
39
|
+
``session/set_config_option(configId="model")``, or — for clients
|
|
40
|
+
that trust the advertised ``currentModelId`` — lazily on the first
|
|
41
|
+
backend-needing method (e.g. ``session/prompt``). Cross-agent
|
|
42
|
+
switching mid-session is out of scope for v1; subsequent picks against
|
|
43
|
+
a different agent are rejected at the protocol level.
|
|
44
|
+
|
|
45
|
+
The exports below are re-exported from ``terok_executor`` so the
|
|
46
|
+
host-side caller (terok) doesn't have to reach into the submodules.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
from __future__ import annotations
|
|
50
|
+
|
|
51
|
+
from .cache import AgentRosterCache, CacheKey
|
|
52
|
+
from .daemon import acp_socket_is_live, serve_acp
|
|
53
|
+
from .endpoint import ACPEndpointStatus
|
|
54
|
+
from .probe import ProbeError, probe_agent_models
|
|
55
|
+
from .proxy import AgentBindError
|
|
56
|
+
from .roster import ACPRoster, list_authenticated_agents
|
|
57
|
+
|
|
58
|
+
__all__ = [
|
|
59
|
+
"ACPEndpointStatus",
|
|
60
|
+
"ACPRoster",
|
|
61
|
+
"AgentBindError",
|
|
62
|
+
"AgentRosterCache",
|
|
63
|
+
"CacheKey",
|
|
64
|
+
"ProbeError",
|
|
65
|
+
"acp_socket_is_live",
|
|
66
|
+
"list_authenticated_agents",
|
|
67
|
+
"probe_agent_models",
|
|
68
|
+
"serve_acp",
|
|
69
|
+
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Jiri Vyskocil
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Per-agent model roster cache for the ACP host-proxy.
|
|
5
|
+
|
|
6
|
+
Probing an agent (initialize + session/new + read configOptions) is
|
|
7
|
+
expensive and the result is stable for the lifetime of an authenticated
|
|
8
|
+
session. The cache is keyed ``(image_id, auth_identity, agent_id)``:
|
|
9
|
+
same image, same auth, same agent ⇒ same model list.
|
|
10
|
+
|
|
11
|
+
The cache is populated lazily on the first ``session/new`` after a new
|
|
12
|
+
auth, and never re-probed mid-session. ``invalidate_auth`` lets workflows
|
|
13
|
+
flush an entire identity's worth of entries when credentials change (today
|
|
14
|
+
auth is global so this is rarely useful; the hook exists for future
|
|
15
|
+
per-project auth).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import threading
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class CacheKey:
|
|
26
|
+
"""Composite key for one agent's roster within one auth scope.
|
|
27
|
+
|
|
28
|
+
``auth_identity`` is the constant ``"global"`` today (terok auth is
|
|
29
|
+
process-wide); the field exists from day one so per-project auth can
|
|
30
|
+
slot in without a key-schema migration.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
image_id: str
|
|
34
|
+
auth_identity: str
|
|
35
|
+
agent_id: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AgentRosterCache:
|
|
39
|
+
"""Thread-safe map from [`CacheKey`][terok_executor.acp.cache.CacheKey] to a tuple of model ids.
|
|
40
|
+
|
|
41
|
+
Models are stored as a tuple so cache entries are immutable once
|
|
42
|
+
inserted — callers can return them directly without defensive copying.
|
|
43
|
+
Empty tuples are valid and signal "probe ran but yielded nothing"
|
|
44
|
+
(saved to avoid hammering a misconfigured agent on every session).
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self) -> None:
|
|
48
|
+
self._models: dict[CacheKey, tuple[str, ...]] = {}
|
|
49
|
+
self._lock = threading.Lock()
|
|
50
|
+
|
|
51
|
+
def get(self, key: CacheKey) -> tuple[str, ...] | None:
|
|
52
|
+
"""Return cached models for *key*, or ``None`` if not yet probed."""
|
|
53
|
+
with self._lock:
|
|
54
|
+
return self._models.get(key)
|
|
55
|
+
|
|
56
|
+
def put(self, key: CacheKey, models: tuple[str, ...]) -> None:
|
|
57
|
+
"""Store *models* under *key*, replacing any existing entry."""
|
|
58
|
+
with self._lock:
|
|
59
|
+
self._models[key] = models
|
|
60
|
+
|
|
61
|
+
def invalidate_auth(self, auth_identity: str) -> None:
|
|
62
|
+
"""Drop every entry tied to *auth_identity*.
|
|
63
|
+
|
|
64
|
+
Used when credentials for an identity rotate — the next
|
|
65
|
+
``session/new`` re-probes affected agents.
|
|
66
|
+
"""
|
|
67
|
+
with self._lock:
|
|
68
|
+
self._models = {
|
|
69
|
+
k: v for k, v in self._models.items() if k.auth_identity != auth_identity
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
def __len__(self) -> int:
|
|
73
|
+
"""Return the number of cached entries (for tests / introspection)."""
|
|
74
|
+
with self._lock:
|
|
75
|
+
return len(self._models)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# Module-level singleton: most callers get this implicitly via
|
|
79
|
+
# [`ACPRoster`][terok_executor.acp.roster.ACPRoster]'s default. Tests inject a fresh
|
|
80
|
+
# [`AgentRosterCache`][terok_executor.acp.cache.AgentRosterCache] via the constructor.
|
|
81
|
+
GLOBAL_CACHE = AgentRosterCache()
|