chore: import upstream snapshot with attribution
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:35:30 +08:00
commit 26382a7ac6
2353 changed files with 629146 additions and 0 deletions
@@ -0,0 +1,32 @@
"""Framework adapters for lean-ctx tools (EPIC 12.6).
Thin, optional integrations that expose the lean-ctx tool surface to popular
agent frameworks. Each framework is an *optional* dependency, imported lazily —
installing the SDK pulls in none of them. The OpenAI adapter is a pure
transformation and needs no extra package.
from leanctx import LeanCtxClient
from leanctx.adapters import to_openai_tools, to_langchain_tools
client = LeanCtxClient("http://127.0.0.1:8080")
tools = to_openai_tools(client)
"""
from __future__ import annotations
from ._common import ToolSpec, coerce_arguments, normalized_tool_specs
from .crewai import to_crewai_tools
from .langchain import to_langchain_tools
from .llamaindex import to_llamaindex_tools
from .openai import run_openai_tool_call, to_openai_tools
__all__ = [
"ToolSpec",
"normalized_tool_specs",
"coerce_arguments",
"to_openai_tools",
"run_openai_tool_call",
"to_langchain_tools",
"to_llamaindex_tools",
"to_crewai_tools",
]
@@ -0,0 +1,83 @@
"""Shared helpers for framework adapters (EPIC 12.6).
All adapters normalize lean-ctx tools into a common [`ToolSpec`] and reuse the
same SDK call path (`call_tool_text`), so every framework integration behaves
identically and stays correct as the tool surface evolves.
"""
from __future__ import annotations
import json
from dataclasses import dataclass
from typing import Any, Callable, Dict, List
from ..client import LeanCtxClient
@dataclass
class ToolSpec:
"""A framework-neutral description of one lean-ctx tool."""
name: str
description: str
parameters: Dict[str, Any] # JSON Schema for the arguments object
def _default_schema() -> Dict[str, Any]:
return {"type": "object", "properties": {}}
def normalized_tool_specs(client: LeanCtxClient, *, limit: int = 500) -> List[ToolSpec]:
"""Fetch and normalize the server's tool surface into [`ToolSpec`]s."""
listing = client.list_tools(limit=limit)
tools = listing.get("tools", []) if isinstance(listing, dict) else []
specs: List[ToolSpec] = []
for entry in tools:
if not isinstance(entry, dict):
continue
name = entry.get("name")
if not isinstance(name, str) or not name:
continue
description = entry.get("description")
if not isinstance(description, str):
description = ""
schema = (
entry.get("input_schema")
or entry.get("inputSchema")
or entry.get("parameters")
or _default_schema()
)
if not isinstance(schema, dict):
schema = _default_schema()
specs.append(ToolSpec(name=name, description=description, parameters=schema))
return specs
def coerce_arguments(raw: Any) -> Dict[str, Any]:
"""Coerce tool-call arguments (JSON string or dict) into a dict."""
if raw is None:
return {}
if isinstance(raw, dict):
return raw
if isinstance(raw, str):
text = raw.strip()
if not text:
return {}
parsed = json.loads(text)
return parsed if isinstance(parsed, dict) else {}
return {}
def make_json_runner(client: LeanCtxClient, name: str) -> Callable[[str], str]:
"""A `(arguments: str) -> str` runner used by string-input frameworks.
The single JSON-string argument is the most portable shape across framework
versions and avoids brittle per-tool signature synthesis.
"""
def run(arguments: str = "") -> str:
return client.call_tool_text(name, coerce_arguments(arguments))
run.__name__ = name
run.__doc__ = f"Invoke the lean-ctx tool '{name}' with a JSON object string."
return run
+53
View File
@@ -0,0 +1,53 @@
"""CrewAI adapter (EPIC 12.6).
Exposes lean-ctx tools as CrewAI `BaseTool`s. The framework is an optional
dependency, imported lazily.
"""
from __future__ import annotations
from typing import Any, List
from ..client import LeanCtxClient
from ._common import coerce_arguments, normalized_tool_specs
def to_crewai_tools(client: LeanCtxClient) -> List[Any]:
"""Return lean-ctx tools as CrewAI `BaseTool` instances.
Each tool takes a single ``arguments`` field: a JSON object string of the
tool's arguments. This is portable across CrewAI versions and avoids
synthesizing a distinct pydantic schema per tool.
"""
try:
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
except ImportError as exc: # pragma: no cover - exercised only without dep
raise ImportError(
"CrewAI is required for this adapter: pip install crewai"
) from exc
class _ArgsSchema(BaseModel):
arguments: str = Field(
default="",
description="JSON object string of the tool's arguments.",
)
class _LeanCtxTool(BaseTool):
args_schema: type[BaseModel] = _ArgsSchema
def __init__(self, tool_name: str, tool_description: str) -> None:
super().__init__(name=tool_name, description=tool_description)
self._tool_name = tool_name
def _run(self, arguments: str = "") -> str:
return client.call_tool_text(self._tool_name, coerce_arguments(arguments))
tools: List[Any] = []
for spec in normalized_tool_specs(client):
description = (
f"{spec.description} "
"Input: a JSON object string matching this tool's argument schema."
).strip()
tools.append(_LeanCtxTool(spec.name, description))
return tools
@@ -0,0 +1,36 @@
"""LangChain adapter (EPIC 12.6).
Exposes lean-ctx tools as LangChain `Tool`s. The framework is an optional
dependency, imported lazily so the SDK has no hard coupling.
"""
from __future__ import annotations
from typing import Any, List
from ..client import LeanCtxClient
from ._common import make_json_runner, normalized_tool_specs
def to_langchain_tools(client: LeanCtxClient) -> List[Any]:
"""Return lean-ctx tools as `langchain_core.tools.Tool` instances.
Each tool accepts a JSON object string of arguments — the most portable
shape across LangChain versions.
"""
try:
from langchain_core.tools import Tool
except ImportError as exc: # pragma: no cover - exercised only without dep
raise ImportError(
"LangChain is required for this adapter: pip install langchain-core"
) from exc
tools: List[Any] = []
for spec in normalized_tool_specs(client):
runner = make_json_runner(client, spec.name)
description = (
f"{spec.description} "
"Input: a JSON object string matching this tool's argument schema."
).strip()
tools.append(Tool(name=spec.name, description=description, func=runner))
return tools
@@ -0,0 +1,36 @@
"""LlamaIndex adapter (EPIC 12.6).
Exposes lean-ctx tools as LlamaIndex `FunctionTool`s. The framework is an
optional dependency, imported lazily.
"""
from __future__ import annotations
from typing import Any, List
from ..client import LeanCtxClient
from ._common import make_json_runner, normalized_tool_specs
def to_llamaindex_tools(client: LeanCtxClient) -> List[Any]:
"""Return lean-ctx tools as `llama_index.core.tools.FunctionTool` instances."""
try:
from llama_index.core.tools import FunctionTool
except ImportError as exc: # pragma: no cover - exercised only without dep
raise ImportError(
"LlamaIndex is required for this adapter: pip install llama-index-core"
) from exc
tools: List[Any] = []
for spec in normalized_tool_specs(client):
runner = make_json_runner(client, spec.name)
description = (
f"{spec.description} "
"Input: a JSON object string matching this tool's argument schema."
).strip()
tools.append(
FunctionTool.from_defaults(
fn=runner, name=spec.name, description=description
)
)
return tools
+46
View File
@@ -0,0 +1,46 @@
"""OpenAI function-calling adapter (EPIC 12.6).
Pure transformation — no `openai` package required. Turns the lean-ctx tool
surface into OpenAI ``tools=[...]`` specs and executes the tool calls the model
returns. Works with both the Chat Completions and Responses tool-call shapes.
"""
from __future__ import annotations
from typing import Any, Dict, List
from ..client import LeanCtxClient
from ._common import coerce_arguments, normalized_tool_specs
def to_openai_tools(client: LeanCtxClient) -> List[Dict[str, Any]]:
"""Return lean-ctx tools as OpenAI function-tool specs."""
return [
{
"type": "function",
"function": {
"name": spec.name,
"description": spec.description,
"parameters": spec.parameters,
},
}
for spec in normalized_tool_specs(client)
]
def run_openai_tool_call(client: LeanCtxClient, tool_call: Any) -> str:
"""Execute one OpenAI tool call and return the tool's text result.
Accepts either the dict shape (``{"function": {"name", "arguments"}}``) or
an SDK object exposing ``.function.name`` / ``.function.arguments``.
"""
function = tool_call.get("function") if isinstance(tool_call, dict) else getattr(tool_call, "function", None)
if function is None:
raise ValueError("tool_call has no 'function'")
name = function.get("name") if isinstance(function, dict) else getattr(function, "name", None)
if not name:
raise ValueError("tool_call.function has no 'name'")
raw_args = (
function.get("arguments") if isinstance(function, dict) else getattr(function, "arguments", None)
)
return client.call_tool_text(name, coerce_arguments(raw_args))