Tokan 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.
- tokan/__init__.py +33 -0
- tokan/__main__.py +5 -0
- tokan/adapters/__init__.py +23 -0
- tokan/adapters/base.py +143 -0
- tokan/adapters/builtin.py +533 -0
- tokan/adapters/registry.py +123 -0
- tokan/cache/__init__.py +4 -0
- tokan/cache/edge_cache.py +179 -0
- tokan/cdm/__init__.py +4 -0
- tokan/cdm/normalizer.py +265 -0
- tokan/cli.py +278 -0
- tokan/client.py +166 -0
- tokan/jev/__init__.py +4 -0
- tokan/jev/router.py +201 -0
- tokan/mcp.py +284 -0
- tokan/metrics.py +78 -0
- tokan/pipeline.py +345 -0
- tokan/proxy/__init__.py +4 -0
- tokan/proxy/__main__.py +5 -0
- tokan/proxy/interceptor.py +653 -0
- tokan/repo_index.py +179 -0
- tokan-0.2.0.dist-info/METADATA +1122 -0
- tokan-0.2.0.dist-info/RECORD +27 -0
- tokan-0.2.0.dist-info/WHEEL +5 -0
- tokan-0.2.0.dist-info/entry_points.txt +2 -0
- tokan-0.2.0.dist-info/licenses/LICENSE +21 -0
- tokan-0.2.0.dist-info/top_level.txt +1 -0
tokan/__init__.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""token-optimizer: token-frugality middleware for AI coding agents.
|
|
2
|
+
|
|
3
|
+
Three integration patterns, one pipeline (CDM -> edge cache -> JeV):
|
|
4
|
+
|
|
5
|
+
1. Gateway proxy - point the agent's base URL at it (zero code changes)
|
|
6
|
+
2. SDK client - wrap the agent loop in Python
|
|
7
|
+
3. Repo mapper - see ``examples/build_repo_index.py`` for the .md mapper
|
|
8
|
+
"""
|
|
9
|
+
from .cache import CDNConfig, EdgeCache
|
|
10
|
+
from .cdm import CanonicalRecord, CDMNormalizer, estimate_tokens
|
|
11
|
+
from .client import ChatResponse, OptimizedAgentClient
|
|
12
|
+
from .jev import JevDecision, JevRouter
|
|
13
|
+
from .metrics import SessionMetrics
|
|
14
|
+
from .pipeline import LocalExecutor, OptimizationPipeline, PipelineResult
|
|
15
|
+
|
|
16
|
+
__version__ = "0.2.0"
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"CDNConfig",
|
|
20
|
+
"CanonicalRecord",
|
|
21
|
+
"CDMNormalizer",
|
|
22
|
+
"ChatResponse",
|
|
23
|
+
"EdgeCache",
|
|
24
|
+
"JevDecision",
|
|
25
|
+
"JevRouter",
|
|
26
|
+
"LocalExecutor",
|
|
27
|
+
"OptimizationPipeline",
|
|
28
|
+
"OptimizedAgentClient",
|
|
29
|
+
"PipelineResult",
|
|
30
|
+
"SessionMetrics",
|
|
31
|
+
"estimate_tokens",
|
|
32
|
+
"__version__",
|
|
33
|
+
]
|
tokan/__main__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Agent adapter plugins: built-ins + third-party entry-point discovery."""
|
|
2
|
+
from .base import AgentAdapter, ConfigSnippet, GatewayInfo
|
|
3
|
+
from .registry import (
|
|
4
|
+
ENTRY_POINT_GROUP,
|
|
5
|
+
all_adapters,
|
|
6
|
+
detect_all,
|
|
7
|
+
get,
|
|
8
|
+
register,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
# Importing the module runs its @register decorators.
|
|
12
|
+
from . import builtin as _builtin # noqa: F401 (side effect: registration)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"AgentAdapter",
|
|
16
|
+
"ConfigSnippet",
|
|
17
|
+
"GatewayInfo",
|
|
18
|
+
"ENTRY_POINT_GROUP",
|
|
19
|
+
"all_adapters",
|
|
20
|
+
"detect_all",
|
|
21
|
+
"get",
|
|
22
|
+
"register",
|
|
23
|
+
]
|
tokan/adapters/base.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Adapter plugin interface: how one coding agent plugs into the gateway.
|
|
2
|
+
|
|
3
|
+
An adapter knows three things about its agent:
|
|
4
|
+
1. which protocols it speaks (``openai`` / ``anthropic`` / ``mcp``),
|
|
5
|
+
2. whether it is installed (binary on PATH, config file present),
|
|
6
|
+
3. how to wire it to a running gateway (copy-pasteable snippets).
|
|
7
|
+
|
|
8
|
+
Third-party agents plug in via the ``tokan.adapters`` entry-point
|
|
9
|
+
group — see :mod:`tokan.adapters.registry`.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import shutil
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Dict, List, Optional, Sequence, Tuple
|
|
18
|
+
|
|
19
|
+
__all__ = ["AgentAdapter", "ConfigSnippet", "GatewayInfo"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class GatewayInfo:
|
|
24
|
+
"""Endpoints a running gateway exposes; adapters render against this."""
|
|
25
|
+
|
|
26
|
+
host: str = "127.0.0.1"
|
|
27
|
+
port: int = 4040
|
|
28
|
+
openai_base_url: str = "" # e.g. http://127.0.0.1:4040/v1
|
|
29
|
+
anthropic_base_url: str = "" # e.g. http://127.0.0.1:4040
|
|
30
|
+
mcp_command: Tuple[str, ...] = ("python3", "-m", "tokan", "mcp")
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def for_gateway(cls, host: str, port: int, mcp_command: Optional[Sequence[str]] = None) -> "GatewayInfo":
|
|
34
|
+
cmd = tuple(mcp_command) if mcp_command else ("python3", "-m", "tokan", "mcp")
|
|
35
|
+
return cls(
|
|
36
|
+
host=host,
|
|
37
|
+
port=port,
|
|
38
|
+
openai_base_url=f"http://{host}:{port}/v1",
|
|
39
|
+
anthropic_base_url=f"http://{host}:{port}",
|
|
40
|
+
mcp_command=cmd,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def origin(self) -> str:
|
|
45
|
+
"""Gateway root URL (Anthropic-style base, no trailing slash)."""
|
|
46
|
+
return f"http://{self.host}:{self.port}"
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def mcp_config_entry(self) -> Dict[str, object]:
|
|
50
|
+
"""MCP ``mcpServers`` entry (Claude Code / Cursor / Continue shape)."""
|
|
51
|
+
return {"command": self.mcp_command[0], "args": list(self.mcp_command[1:])}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class ConfigSnippet:
|
|
56
|
+
"""One copy-pasteable piece of agent configuration.
|
|
57
|
+
|
|
58
|
+
``kind``: ``env`` (shell export), ``file`` (config file content),
|
|
59
|
+
``ui`` (in-app steps), or ``command`` (one-shot shell command).
|
|
60
|
+
``target``: where it goes — a path, a settings panel, or a shell.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
kind: str
|
|
64
|
+
target: str
|
|
65
|
+
body: str
|
|
66
|
+
note: str = ""
|
|
67
|
+
|
|
68
|
+
def render(self) -> str:
|
|
69
|
+
header = f"[{self.kind}] {self.target}"
|
|
70
|
+
bar = "─" * max(len(header), 40)
|
|
71
|
+
out = f"{bar}\n{header}\n{bar}\n{self.body.rstrip()}\n"
|
|
72
|
+
if self.note:
|
|
73
|
+
out += f"\n# {self.note}\n"
|
|
74
|
+
return out
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class AgentAdapter(ABC):
|
|
78
|
+
"""Base class for built-in and third-party agent adapters."""
|
|
79
|
+
|
|
80
|
+
#: stable identifier used on the CLI (``token-optimizer setup <id>``)
|
|
81
|
+
id: str = ""
|
|
82
|
+
#: human-readable name for listings
|
|
83
|
+
display_name: str = ""
|
|
84
|
+
#: wire protocols this agent can consume: "openai", "anthropic", "mcp"
|
|
85
|
+
protocols: Tuple[str, ...] = ()
|
|
86
|
+
#: binaries that identify an installed agent (any match counts)
|
|
87
|
+
executables: Tuple[str, ...] = ()
|
|
88
|
+
#: config paths (``~`` expanded) whose presence identifies the agent
|
|
89
|
+
config_paths: Tuple[str, ...] = ()
|
|
90
|
+
#: one-line description shown in ``token-optimizer list``
|
|
91
|
+
summary: str = ""
|
|
92
|
+
|
|
93
|
+
# ------------------------------------------------------------ detection
|
|
94
|
+
def detect(self) -> Optional[str]:
|
|
95
|
+
"""Return how the agent was detected, or None if not installed."""
|
|
96
|
+
for exe in self.executables:
|
|
97
|
+
if shutil.which(exe):
|
|
98
|
+
return f"binary: {exe}"
|
|
99
|
+
for raw in self.config_paths:
|
|
100
|
+
if Path(raw).expanduser().exists():
|
|
101
|
+
return f"config: {raw}"
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def detected(self) -> bool:
|
|
106
|
+
return self.detect() is not None
|
|
107
|
+
|
|
108
|
+
# ------------------------------------------------------------- snippets
|
|
109
|
+
@abstractmethod
|
|
110
|
+
def snippets(self, gw: GatewayInfo) -> List[ConfigSnippet]:
|
|
111
|
+
"""Config pieces that point this agent at the gateway."""
|
|
112
|
+
|
|
113
|
+
# ------------------------------------------------------------------ MCP
|
|
114
|
+
@property
|
|
115
|
+
def supports_mcp(self) -> bool:
|
|
116
|
+
return "mcp" in self.protocols
|
|
117
|
+
|
|
118
|
+
def mcp_snippet(self, gw: GatewayInfo, *, target: str = "MCP config") -> ConfigSnippet:
|
|
119
|
+
"""Default MCP registration snippet (JSON ``mcpServers`` entry)."""
|
|
120
|
+
import json
|
|
121
|
+
|
|
122
|
+
entry = {"mcpServers": {"token-optimizer": gw.mcp_config_entry}}
|
|
123
|
+
return ConfigSnippet(
|
|
124
|
+
kind="file",
|
|
125
|
+
target=target,
|
|
126
|
+
body=json.dumps(entry, indent=2),
|
|
127
|
+
note="Exposes optimize_context, repo_index, cache_lookup, session_metrics tools.",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def all_snippets(self, gw: GatewayInfo, *, include_mcp: bool = True) -> List[ConfigSnippet]:
|
|
131
|
+
out = list(self.snippets(gw))
|
|
132
|
+
if include_mcp and self.supports_mcp:
|
|
133
|
+
out.append(self.mcp_snippet(gw, target=self.mcp_target()))
|
|
134
|
+
return out
|
|
135
|
+
|
|
136
|
+
def mcp_target(self) -> str:
|
|
137
|
+
"""Where this agent keeps its MCP server registry."""
|
|
138
|
+
return "MCP config"
|
|
139
|
+
|
|
140
|
+
# --------------------------------------------------------------- helper
|
|
141
|
+
@staticmethod
|
|
142
|
+
def _snippet(kind: str, target: str, body: str, note: str = "") -> ConfigSnippet:
|
|
143
|
+
return ConfigSnippet(kind=kind, target=target, body=body, note=note)
|