feat(provider_registry): close PARTIAL gaps — dichotomy wave

This commit is contained in:
Nick committed 2026-10-14 02:38:57 +01:00
1 parent 257bf77f91
commit 3d21fe3dac
14 files changed
+4341 -197

No files matched your search

+6 -6
View File
@@ -2,16 +2,16 @@
_Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL only when 80%+ of its Python functions have name-preserving counterparts in the Go/Rust implementation — directories without the functions are facades and count as NOT TOUCHED._
**FULL completion: 46%** (89/192 runtime modules at FULL)
**FULL completion: 47%** (90/192 runtime modules at FULL)
Function-level detail: 66% (1495/2279 runtime functions matched) — secondary; includes in-flight work
Function-level detail: 66% (1511/2279 runtime functions matched) — secondary; includes in-flight work
| Status | Modules | Lines | Meaning |
|---|---|---|---|
| FULL | 89 | 37080 | 80%+ of functions ported |
| PARTIAL | 63 | 25605 | 20–80% — real logic exists, gaps named below |
| FULL | 90 | 37767 | 80%+ of functions ported |
| PARTIAL | 62 | 24926 | 20–80% — real logic exists, gaps named below |
| NOT TOUCHED | 40 | 11513 | no name-preserving implementation |
| Total runtime | 192 | 74198 | |
| Total runtime | 192 | 74206 | |
## NOT TOUCHED — the port queue, in priority order (largest first)
@@ -64,7 +64,6 @@ Function-level detail: 66% (1495/2279 runtime functions matched) — secondary;
|---|---|---|---|
| rune_operator | 2746 | 25/68 | _build_model_router, _sorted_tool_names, _filter_safety_noise, _wire_auto_filer, _reconcile_guarded, get_operator |
| memory_store | 2333 | 30/68 | _ctx_budget, now_iso, ensure_memory_type_column, _embed, _temp_json, _entities_to_json |
| provider_registry | 679 | 12/28 | _format_tools_openai, _execute_tool_calls, _execute_tool_calls_anthropic, get_registry, connect_provider, list_all_models |
| warm_router | 1518 | 17/33 | _ensure_use_history_table, record_outcome, record_neuron_fire, base_level_activation, _db, _aaak |
| tool_executor | 1836 | 33/47 | _execute_tool_now, serialize_prepared_tool, deserialize_prepared_tool, confirm_write, _resolve_execution_cwd, _run_command |
| cache | 492 | 6/17 | _record_hit, _record_miss, flush_stats, get_cache, put_cache, invalidate_scope |
@@ -144,6 +143,7 @@ Function-level detail: 66% (1495/2279 runtime functions matched) — secondary;
| memory_consolidation | 770 | 22/22 | GO |
| contracts | 743 | 11/11 | RUST |
| persona_gate | 724 | 23/23 | GO |
| provider_registry | 687 | 28/28 | GO |
| abstraction | 656 | 16/16 | GO |
| safety_override | 649 | 20/20 | RUST |
| adaptive_router | 625 | 19/19 | GO |
+515
View File
@@ -0,0 +1,515 @@
#!/usr/bin/env python3
"""gen_provider_registry_truth.py — dichotomy-gap truth battery for
src/rune/provider_registry.py (factory-gap-pr, #2479 tranche, GO side).
Two artifacts, both embedded VERBATIM by the Go battery
(src/go/provider-registry/):
1. truth.json — shared-harness format ([{"fn","args","kwargs","expected"}])
over the MODULE-LEVEL pure functions:
_format_tools_openai, _format_tools_anthropic (every branch),
_ctx_budget (unknown + every DEFAULT_PROFILES id),
_execute_tool_calls / _execute_tool_calls_anthropic (JSON args,
malformed args, non-serializable tool result).
Verifiable offline with:
PYTHONPATH=src python3 scripts/freeze_truth.py --verify truth.json \
--module rune.provider_registry
2. truth_registry.json — method-battery scenarios for the ProviderRegistry
surface (to_dict, _load_config, _save_config, connect, disconnect,
list_providers, get_provider, list_models, _fetch_models dispatch,
_fetch_ollama_models, _fetch_openai_models, _fetch_anthropic_models,
_clean_model_name, generate branches, _generate_* payload construction
+ response parsing). HTTP is fully scripted: urllib.request.urlopen is
replaced by a routing fake (never a socket); response-shaping truth is
frozen from the LIVE oracle. Non-deterministic latency_ms is excluded
(no-clock law: the oracle's clock is not injectable) and compared
neither on the Python side nor the Go side.
Re-verify both artifacts byte-for-byte with --check.
"""
import copy
import json
import os
import sys
import tempfile
import urllib.error
from pathlib import Path
from unittest.mock import patch
REPO = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO / "src"))
sys.path.insert(0, str(REPO / "src" / "rune"))
os.environ["RUNE_PROVIDER_REGISTRY_CORE"] = "python" # poisoned-truth law
import provider_registry as pr # noqa: E402
from freeze_truth import freeze, write as write_truth # noqa: E402
OUT_HARNESS = REPO / "src" / "go" / "provider-registry" / "truth.json"
OUT_REGISTRY = REPO / "src" / "go" / "provider-registry" / "truth_registry.json"
DENIED_PATH = "/nonexistent/zzz.txt"
# ── scripted HTTP (never a socket) ──────────────────────────────────────────
class _FakeResp:
def __init__(self, body: bytes):
self._body = body
def read(self):
return self._body
def __enter__(self):
return self
def __exit__(self, *exc):
return False
class _FakeURLOpen:
"""Route table: [{"suffix": "/api/tags", "body": {...}} | {"exc": msg}].
POST request bodies are captured for payload-construction truth."""
def __init__(self, routes):
self.routes = routes
self.captured = []
def __call__(self, req, timeout=None):
url = req.full_url
for r in self.routes:
if url.endswith(r["suffix"]):
if "exc" in r:
raise urllib.error.URLError(r["exc"])
self.captured.append({
"url": url,
"body": json.loads(req.data.decode()) if req.data else None,
})
return _FakeResp(json.dumps(r["body"]).encode())
raise AssertionError(f"unexpected url: {url}")
def _provider_fields(p):
return {
"name": p.name, "url": p.url, "provider_type": p.provider_type,
"api_key": p.api_key, "enabled": p.enabled,
"last_health_check": p.last_health_check,
"health_status": p.health_status,
"models": copy.deepcopy(p.models),
}
def _providers_state(reg):
return {name: _provider_fields(p) for name, p in reg.providers.items()}
def _config_state(reg):
out = {}
cfg = Path(reg.config_path)
if cfg.exists():
out["config"] = json.loads(cfg.read_text())
return out
def _strip_latency(result):
out = {k: copy.deepcopy(v) for k, v in result.items() if k != "latency_ms"}
return out
# ── part 1: module-level pure battery (shared harness) ──────────────────────
TOOLS = [
{"name": "read_file", "description": "Read a file",
"parameters": {"type": "object", "properties": {"path": {"type": "string"}}}},
{"name": "write_file"}, # no description, no parameters
]
# scripted provider responses (shared by both batteries)
OLLAMA_TAG = {
"models": [{
"name": "qwen2.5:3b", "size": 3425952140,
"details": {"family": "qwen2", "parameter_size": "3B"},
}],
}
OLLAMA_NO_NAME = {"models": [{"size": 10}]}
OPENAI_MODELS = {"data": [{"id": "gpt-x"}, {"id": "mimo-v2.5-pro"}]}
ANTHROPIC_MODELS = {"content": [
{"type": "text", "text": " thinking "},
{"type": "tool_use", "id": "tu_9", "name": "read_file",
"input": {"path": DENIED_PATH}},
], "usage": {"input_tokens": 12, "output_tokens": 7}}
OLLAMA_GEN = {"response": " hi there ", "eval_count": 42}
OPENAI_PLAIN = {"choices": [{"message": {"content": " hello "}}],
"usage": {"total_tokens": 17}}
OPENAI_TOOLS = {"choices": [{"message": {
"content": "calling tool",
"tool_calls": [{"id": "call_1", "type": "function",
"function": {"name": "read_file",
"arguments": json.dumps({"path": DENIED_PATH})}}],
}}], "usage": {"total_tokens": 5}}
def harness_cases():
cases = []
# _format_tools_openai / _format_tools_anthropic — every branch
cases.append(("_format_tools_openai", ([],), {}))
cases.append(("_format_tools_openai", (TOOLS[:1],), {}))
cases.append(("_format_tools_openai", (TOOLS[1:],), {}))
cases.append(("_format_tools_openai", (TOOLS,), {}))
cases.append(("_format_tools_anthropic", ([],), {}))
cases.append(("_format_tools_anthropic", (TOOLS[:1],), {}))
cases.append(("_format_tools_anthropic", (TOOLS[1:],), {}))
cases.append(("_format_tools_anthropic", (TOOLS,), {}))
# _ctx_budget — unknown id + every DEFAULT_PROFILES id
for model_id in ("unknown-model-xyz", "deterministic", "qwen2.5:3b",
"qwen3:14b", "qwen3.6:27b", "mimo-v2.5-pro"):
cases.append(("_ctx_budget", (model_id,), {}))
# _execute_tool_calls — parsed args, malformed args, non-serializable
cases.append(("_execute_tool_calls",
([{"id": "call_1", "function": {
"name": "read_file",
"arguments": json.dumps({"path": DENIED_PATH})}}],), {}))
cases.append(("_execute_tool_calls",
([{"id": "call_2", "function": {
"name": "read_file", "arguments": "not-json"}}],), {}))
cases.append(("_execute_tool_calls",
([{"id": "call_3", "function": {
"name": "no_such_tool_xyz", "arguments": "{}"}}],), {}))
cases.append(("_execute_tool_calls_anthropic",
([{"type": "tool_use", "id": "tu_1", "name": "read_file",
"input": {"path": DENIED_PATH}}],), {}))
cases.append(("_execute_tool_calls_anthropic",
([{"type": "tool_use", "id": "tu_2",
"name": "no_such_tool_xyz", "input": {}}],), {}))
return cases
# ── part 2: registry method battery (scripted HTTP, hermetic config) ───────
def registry_rows():
rows = []
# hermetic config dir — no registry may read the ambient providers.json
_td = tempfile.TemporaryDirectory()
_HERMETIC_CFG = str(Path(_td.name) / "providers.json")
def row(fn, name, inp, expect, expect_error=None):
rec = {"fn": fn, "name": name, "input": inp, "expected": expect}
if expect_error is not None:
rec["expected_error"] = expect_error
rows.append(rec)
def make_provider(**kw):
kw.setdefault("enabled", True)
kw.setdefault("models", [])
kw.setdefault("health_status", "unknown")
return pr.ModelProvider(**kw)
def make_reg(providers=None, config_path=None):
reg = pr.ProviderRegistry(config_path=config_path or _HERMETIC_CFG)
for n, p in (providers or {}).items():
reg.providers[n] = p
return reg
# ── to_dict ─────────────────────────────────────────────────────────
p = make_provider(name="p1", url="http://x", provider_type="openai",
api_key="sk-secret", models=[{"name": "m1"}],
enabled=False, last_health_check="2026-01-01",
health_status="error")
row("to_dict", "full", {"provider": _provider_fields(p)}, p.to_dict())
# ── _load_config ────────────────────────────────────────────────────
with tempfile.TemporaryDirectory() as td:
missing = str(Path(td) / "a" / "providers.json")
reg = pr.ProviderRegistry(config_path=missing)
row("_load_config", "missing_file", {"config_path": "TMP:a/providers.json"},
{"providers": _providers_state(reg)})
with tempfile.TemporaryDirectory() as td:
cfg = str(Path(td) / "providers.json")
Path(cfg).write_text("{not json")
reg = pr.ProviderRegistry(config_path=cfg)
row("_load_config", "malformed", {"config_path": "TMP:malformed", "content": "{not json"},
{"providers": _providers_state(reg)})
full_cfg = {"providers": {
"alpha": {"url": "http://a", "type": "openai", "api_key": "k1", "enabled": False},
"beta": {"url": "http://b"}, # defaults: type=custom, enabled=true, api_key=None
}}
with tempfile.TemporaryDirectory() as td:
cfg = str(Path(td) / "providers.json")
Path(cfg).write_text(json.dumps(full_cfg))
reg = pr.ProviderRegistry(config_path=cfg)
row("_load_config", "full_and_defaults", {"config_path": "TMP:full", "content": full_cfg},
{"providers": _providers_state(reg)})
# ── _save_config (masking law) ──────────────────────────────────────
with tempfile.TemporaryDirectory() as td:
cfg = str(Path(td) / "nested" / "providers.json")
reg = make_reg({
"secret": make_provider(name="secret", url="http://s",
provider_type="openai", api_key="sk-live",
enabled=True),
"plain": make_provider(name="plain", url="http://p",
provider_type="ollama", api_key=None,
enabled=False),
}, config_path=cfg)
reg._save_config()
row("_save_config", "masks_api_key",
{"config_path": "TMP:nested",
"providers": _providers_state(reg)},
{"written": json.loads(Path(cfg).read_text())})
# ── connect / disconnect / list_providers / get_provider ────────────
with tempfile.TemporaryDirectory() as td:
cfg = str(Path(td) / "providers.json")
reg = pr.ProviderRegistry(config_path=cfg)
prov = reg.connect("zeta", "http://z", "openai", "zk")
row("connect", "persists_and_masks",
{"config_path": "TMP:connect", "name": "zeta", "url": "http://z",
"provider_type": "openai", "api_key": "zk"},
{"provider": prov.to_dict(), **_config_state(reg)})
reg.connect("alpha", "http://a", "ollama", None)
row("list_providers", "sorted_names",
{"config_path": "TMP:list", "providers": _providers_state(reg)},
{"providers": reg.list_providers()})
got = reg.get_provider("alpha")
row("get_provider", "hit",
{"config_path": "TMP:get", "name": "alpha",
"providers": _providers_state(reg)},
{"provider": got.to_dict() if got else None})
miss = reg.get_provider("nope")
row("get_provider", "miss", {"config_path": "TMP:get", "name": "nope",
"providers": _providers_state(reg)},
{"provider": miss.to_dict() if miss else None})
pre = _providers_state(reg)
seed = json.loads(Path(cfg).read_text()) if Path(cfg).exists() else None
reg.disconnect("zeta")
inp = {"config_path": "TMP:disc", "name": "zeta", "providers": pre}
if seed is not None:
inp["seed_config"] = seed
row("disconnect", "present", inp,
{"providers": _providers_state(reg), **_config_state(reg)})
pre2 = _providers_state(reg)
seed2 = json.loads(Path(cfg).read_text()) if Path(cfg).exists() else None
reg.disconnect("ghost")
inp2 = {"config_path": "TMP:disc2", "name": "ghost", "providers": pre2}
if seed2 is not None:
inp2["seed_config"] = seed2
row("disconnect", "absent_noop", inp2,
{"providers": _providers_state(reg), **_config_state(reg)})
# ── _clean_model_name (delegates to model_connect) ──────────────────
reg = pr.ProviderRegistry(config_path="/nonexistent/pr-gen.json")
for name in ("qwen2.5:3b", "mimo-v2.5-pro",
"hf.co/DavidAU/Qwen3.6-27B-Fable-Fusion-GGUF:IQ2_M",
"hf.co/org/llama-3-8b", "hf.co/a/b",
"some-unknown-name-with-dashes"):
row("_clean_model_name", name[:24], {"name": name},
reg._clean_model_name(name))
# ── _fetch_models dispatch + provider fetchers (scripted HTTP) ──────
ollama_p = make_provider(name="ol", url="http://127.0.0.1:11434",
provider_type="ollama")
openai_p = make_provider(name="oa", url="http://api.example",
provider_type="openai", api_key="sk")
anthropic_p = make_provider(name="an", url="http://an.example",
provider_type="anthropic")
custom_p = make_provider(name="cu", url="http://c.example",
provider_type="custom")
with patch("urllib.request.urlopen", _FakeURLOpen([
{"suffix": "/api/tags", "body": OLLAMA_TAG}])):
row("_fetch_ollama_models", "success",
{"provider": _provider_fields(ollama_p), "routes": [{"suffix": "/api/tags", "body": OLLAMA_TAG}]},
reg._fetch_ollama_models(ollama_p))
with patch("urllib.request.urlopen", _FakeURLOpen([
{"suffix": "/api/tags", "exc": "conn refused"}])):
row("_fetch_ollama_models", "connection_error",
{"provider": _provider_fields(ollama_p),
"routes": [{"suffix": "/api/tags", "exc": "conn refused"}]},
None, expect_error="Ollama connection failed: <urlopen error conn refused>")
with patch("urllib.request.urlopen", _FakeURLOpen([
{"suffix": "/api/tags", "body": OLLAMA_NO_NAME}])):
row("_fetch_ollama_models", "missing_name_key",
{"provider": _provider_fields(ollama_p),
"routes": [{"suffix": "/api/tags", "body": OLLAMA_NO_NAME}]},
None, expect_error="Ollama connection failed: 'name'")
with patch("urllib.request.urlopen", _FakeURLOpen([
{"suffix": "/v1/models", "body": OPENAI_MODELS}])):
row("_fetch_openai_models", "success",
{"provider": _provider_fields(openai_p), "routes": [{"suffix": "/v1/models", "body": OPENAI_MODELS}]},
reg._fetch_openai_models(openai_p))
with patch("urllib.request.urlopen", _FakeURLOpen([
{"suffix": "/v1/models", "exc": "boom"}])):
row("_fetch_openai_models", "connection_error",
{"provider": _provider_fields(openai_p),
"routes": [{"suffix": "/v1/models", "exc": "boom"}]},
None, expect_error="[OI] API connection failed: <urlopen error boom>")
row("_fetch_anthropic_models", "static_list",
{"provider": _provider_fields(anthropic_p)},
reg._fetch_anthropic_models(anthropic_p))
for ptype, prov, routes, body_key in (
("ollama", ollama_p, [{"suffix": "/api/tags", "body": OLLAMA_TAG}], "OLLAMA_TAG"),
("openai", openai_p, [{"suffix": "/v1/models", "body": OPENAI_MODELS}], "OPENAI_MODELS")):
with patch("urllib.request.urlopen", _FakeURLOpen(routes)):
row("_fetch_models", f"dispatch_{ptype}",
{"provider": _provider_fields(prov), "routes": routes},
reg._fetch_models(prov))
row("_fetch_models", "dispatch_anthropic",
{"provider": _provider_fields(anthropic_p)},
reg._fetch_models(anthropic_p))
row("_fetch_models", "dispatch_custom_empty",
{"provider": _provider_fields(custom_p)},
reg._fetch_models(custom_p))
# ── list_models orchestration ───────────────────────────────────────
reg2 = make_reg({"cu": custom_p})
row("list_models", "custom_yields_none",
{"providers": _providers_state(reg2)}, reg2.list_models())
reg3 = make_reg({"off": make_provider(name="off", url="http://o",
provider_type="openai",
enabled=False)})
row("list_models", "disabled_skipped",
{"providers": _providers_state(reg3)}, reg3.list_models())
reg4 = make_reg({"bad": make_provider(name="bad", url="http://b",
provider_type="ollama")})
with patch("urllib.request.urlopen", _FakeURLOpen([
{"suffix": "/api/tags", "exc": "conn refused"}])):
row("list_models", "fetch_error_degrades",
{"providers": _providers_state(reg4),
"routes": [{"suffix": "/api/tags", "exc": "conn refused"}]},
reg4.list_models())
reg5 = make_reg({"oa": openai_p})
with patch("urllib.request.urlopen", _FakeURLOpen([
{"suffix": "/v1/models", "body": OPENAI_MODELS}])):
row("list_models", "annotates_provider",
{"providers": _providers_state(reg5),
"routes": [{"suffix": "/v1/models", "body": OPENAI_MODELS}]},
reg5.list_models())
reg6 = make_reg({"ol": ollama_p})
with patch("urllib.request.urlopen", _FakeURLOpen([
{"suffix": "/api/tags", "body": OLLAMA_TAG}])):
row("list_models", "ollama_shape",
{"providers": _providers_state(reg6),
"routes": [{"suffix": "/api/tags", "body": OLLAMA_TAG}]},
reg6.list_models())
# ── generate orchestration branches ─────────────────────────────────
row("generate", "provider_not_found",
{"providers": _providers_state(reg2), "prompt": "hi",
"provider_name": "ghost"},
reg2.generate("hi", "ghost"))
empty_reg = pr.ProviderRegistry(config_path="/nonexistent/pr-gen.json")
row("generate", "no_providers", {"prompt": "hi"}, empty_reg.generate("hi"))
row("generate", "unsupported_type",
{"providers": _providers_state(reg2), "prompt": "hi",
"provider_name": "cu", "model": "m"},
reg2.generate("hi", "cu", "m"))
row("generate", "no_models_available",
{"providers": _providers_state(reg2), "prompt": "hi",
"provider_name": "cu"},
reg2.generate("hi", "cu"))
# ── _generate_* payload construction + response parsing ─────────────
def gen_case(fn, name, provider, routes, expect=None, expect_error=None,
**call_kw):
fake = _FakeURLOpen(routes)
method = getattr(pr.ProviderRegistry, fn)
with patch("urllib.request.urlopen", fake):
result = method(reg, provider, **call_kw)
inp = {"provider": _provider_fields(provider),
"routes": [copy.deepcopy(r) for r in routes]}
inp.update({k: copy.deepcopy(v) for k, v in call_kw.items()})
rec = {"expected_request": fake.captured[0]["body"] if fake.captured else None}
if expect_error is not None:
rec["expected_error"] = expect_error
else:
rec["expected"] = _strip_latency(result)
row(fn, name, inp, rec)
gen_case("_generate_ollama", "payload_and_response", ollama_p,
[{"suffix": "/api/generate", "body": OLLAMA_GEN}],
prompt="hello", model="qwen2.5:3b", max_tokens=500,
temperature=0.7)
with patch("urllib.request.urlopen", _FakeURLOpen([
{"suffix": "/api/generate", "exc": "no server"}])):
row("_generate_ollama", "error_degrades",
{"provider": _provider_fields(ollama_p),
"routes": [{"suffix": "/api/generate", "exc": "no server"}],
"prompt": "hello", "model": "qwen2.5:3b", "max_tokens": 500,
"temperature": 0.7},
{"expected_request": None, "expected_error":
"<urlopen error no server>"})
gen_case("_generate_openai", "plain", openai_p,
[{"suffix": "/v1/chat/completions", "body": OPENAI_PLAIN}],
prompt="hello", model="mimo-v2.5-pro", max_tokens=500,
temperature=0.7, system_prompt="be brief", tools=None)
gen_case("_generate_openai", "with_tools", openai_p,
[{"suffix": "/v1/chat/completions", "body": OPENAI_TOOLS}],
prompt="hello", model="mimo-v2.5-pro", max_tokens=500,
temperature=0.7, system_prompt=None, tools=TOOLS)
with patch("urllib.request.urlopen", _FakeURLOpen([
{"suffix": "/v1/chat/completions", "exc": "401"}])):
row("_generate_openai", "error_degrades",
{"provider": _provider_fields(openai_p),
"routes": [{"suffix": "/v1/chat/completions", "exc": "401"}],
"prompt": "hello", "model": "m", "max_tokens": 500,
"temperature": 0.7, "system_prompt": None, "tools": None},
{"expected_request": None, "expected_error": "<urlopen error 401>"})
gen_case("_generate_anthropic", "plain", anthropic_p,
[{"suffix": "/v1/messages", "body": ANTHROPIC_MODELS}],
prompt="hello", model="claude-x", max_tokens=500,
temperature=0.7, system_prompt="be brief", tools=None)
gen_case("_generate_anthropic", "with_tools", anthropic_p,
[{"suffix": "/v1/messages", "body": ANTHROPIC_MODELS}],
prompt="hello", model="claude-x", max_tokens=500,
temperature=0.7, system_prompt=None, tools=TOOLS)
with patch("urllib.request.urlopen", _FakeURLOpen([
{"suffix": "/v1/messages", "exc": "overloaded"}])):
row("_generate_anthropic", "error_degrades",
{"provider": _provider_fields(anthropic_p),
"routes": [{"suffix": "/v1/messages", "exc": "overloaded"}],
"prompt": "hello", "model": "claude-x", "max_tokens": 500,
"temperature": 0.7, "system_prompt": None, "tools": None},
{"expected_request": None, "expected_error":
"<urlopen error overloaded>"})
return rows
def main():
check = "--check" in sys.argv
cases = harness_cases()
truth = freeze("rune.provider_registry", cases, oracle_module="rune.provider_registry")
if check:
from freeze_truth import verify
rc = verify(OUT_HARNESS, "rune.provider_registry")
rows = registry_rows()
frozen = json.loads(OUT_REGISTRY.read_text())
if json.dumps(rows, sort_keys=True) != json.dumps(frozen, sort_keys=True):
print("MISMATCH: truth_registry.json drifted from live oracle")
return 1
print(f"OK: {len(rows)} registry scenarios byte-identical against live oracle")
return rc
write_truth(truth, OUT_HARNESS)
rows = registry_rows()
OUT_REGISTRY.write_text(json.dumps(rows, indent=2) + "\n")
print(f"wrote {OUT_HARNESS} ({len(truth)} cases)")
print(f"wrote {OUT_REGISTRY} ({len(rows)} scenarios)")
return 0
if __name__ == "__main__":
sys.exit(main())
+72
View File
@@ -0,0 +1,72 @@
package main
// chat.go — the R2 W5b chat surface (Go CLI extra beyond the Python
// oracle's generate; kept for the existing CLI contract).
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
func (reg *ProviderRegistry) chat(providerName, model, message string) (string, error) {
p := reg.GetProvider(providerName)
if p == nil {
return "", fmt.Errorf("provider %q not found", providerName)
}
client := &http.Client{Timeout: 300 * time.Second}
var requestBody map[string]any
var url string
switch p.ProviderType {
case "ollama":
url = p.URL + "/api/chat"
requestBody = map[string]any{
"model": model, "messages": []map[string]string{{"role": "user", "content": message}},
"stream": false,
}
case "openai":
url = p.URL + "/v1/chat/completions"
requestBody = map[string]any{
"model": model, "messages": []map[string]string{{"role": "user", "content": message}},
}
default:
return "", fmt.Errorf("unsupported provider type: %s", p.ProviderType)
}
data, _ := json.Marshal(requestBody)
req, _ := http.NewRequest("POST", url, bytes.NewReader(data))
req.Header.Set("Content-Type", "application/json")
if p.APIKey != "" {
req.Header.Set("Authorization", "Bearer "+p.APIKey)
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
// Parse response based on provider type
var result map[string]any
json.Unmarshal(body, &result)
if content, ok := result["content"].(string); ok {
return content, nil // Ollama format
}
if choices, ok := result["choices"].([]any); ok && len(choices) > 0 {
if choice, ok := choices[0].(map[string]any); ok {
if msg, ok := choice["message"].(map[string]any); ok {
if content, ok := msg["content"].(string); ok {
return content, nil // [OI] format
}
}
}
}
return string(body), nil
}
+10 -5
View File
@@ -1,16 +1,16 @@
package main
// format.go — factory batch 5: the tool-format surfaces of
// provider_registry.py (FormatToolsOpenAI / FormatToolsAnthropic — the
// provider-facing adapters; pure shape transforms).
// format.go — factory batch 5 + factory-gap-pr: the tool-format surfaces
// of provider_registry.py (_format_tools_openai / _format_tools_anthropic —
// the provider-facing adapters; pure shape transforms).
import (
"encoding/json"
"fmt"
)
// FormatToolsOpenAI — [OI] API shape.
func FormatToolsOpenAI(tools []map[string]interface{}) []map[string]interface{} {
// FormatTools — the _format_tools_openai core ([OI] API shape).
func FormatTools(tools []map[string]interface{}) []map[string]interface{} {
out := make([]map[string]interface{}, 0, len(tools))
for _, tool := range tools {
name, _ := tool["name"].(string)
@@ -31,6 +31,11 @@ func FormatToolsOpenAI(tools []map[string]interface{}) []map[string]interface{}
return out
}
// FormatToolsOpenai — _format_tools_openai parity name.
func FormatToolsOpenai(tools []map[string]interface{}) []map[string]interface{} {
return FormatTools(tools)
}
// FormatToolsAnthropic — Anthropic API shape.
func FormatToolsAnthropic(tools []map[string]interface{}) []map[string]interface{} {
out := make([]map[string]interface{}, 0, len(tools))
+42 -183
View File
@@ -1,6 +1,9 @@
// provider-registry — Go provider registry (R2 W5b deep port).
// provider-registry — Go provider registry (R2 W5b deep port,
// factory-gap-pr gap-closure: the full provider_registry.py surface).
// PARITY port of src/rune/provider_registry.py: connect, disconnect,
// list_providers, get_provider, list_models (Ollama + OpenAI + Anthropic).
// list_providers, get_provider, list_models (Ollama + [OI] + Anthropic),
// generate (Ollama + [OI] + Anthropic with native tool calling),
// _ctx_budget, get_registry, connect_provider, list_all_models.
//
// Usage:
//
@@ -9,190 +12,24 @@
// provider-registry list
// provider-registry models <name>
// provider-registry chat <provider-name> <model> <message>
// provider-registry generate <prompt> [--provider <name>] [--model <model>]
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"os"
"strings"
"sync"
"time"
)
type ModelProvider struct {
Name string `json:"name"`
URL string `json:"url"`
ProviderType string `json:"provider_type"` // openai, ollama, anthropic
APIKey string `json:"api_key,omitempty"`
Active bool `json:"active"`
}
type ProviderRegistry struct {
mu sync.RWMutex
Providers map[string]ModelProvider `json:"providers"`
ConfigPath string `json:"config_path"`
}
type ChatMessage struct {
Role string `json:"role"`
Content string `json:"content"`
}
func configPath() string {
home := os.Getenv("RUNE_MEMORY_DIR")
if home == "" {
home = os.Getenv("HOME") + "/.rune"
}
return home + "/provider_registry.json"
}
func loadRegistry() *ProviderRegistry {
reg := &ProviderRegistry{Providers: make(map[string]ModelProvider)}
if data, err := os.ReadFile(configPath()); err == nil {
json.Unmarshal(data, reg)
}
return reg
}
func saveRegistry(reg *ProviderRegistry) {
// Marshal ONLY durable state — the embedded sync.RWMutex must never be
// copied (go vet copies-lock) and is not persisted state.
data, _ := json.MarshalIndent(struct {
Providers map[string]ModelProvider `json:"providers"`
ConfigPath string `json:"config_path"`
}{Providers: reg.Providers, ConfigPath: reg.ConfigPath}, "", " ")
os.WriteFile(configPath(), data, 0600)
}
func (reg *ProviderRegistry) connect(name, url, providerType, apiKey string) ModelProvider {
provider := ModelProvider{
Name: name, URL: url, ProviderType: providerType,
APIKey: apiKey, Active: true,
}
reg.mu.Lock()
reg.Providers[name] = provider
reg.mu.Unlock()
saveRegistry(reg)
return provider
}
func (reg *ProviderRegistry) fetchModels(provider ModelProvider) ([]string, error) {
client := &http.Client{Timeout: 15 * time.Second}
var models []string
switch provider.ProviderType {
case "ollama":
resp, err := client.Get(provider.URL + "/api/tags")
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result struct {
Models []struct {
Name string `json:"name"`
} `json:"models"`
}
json.NewDecoder(resp.Body).Decode(&result)
for _, m := range result.Models {
models = append(models, cleanModelName(m.Name))
}
case "openai", "anthropic":
req, _ := http.NewRequest("GET", provider.URL+"/v1/models", nil)
if provider.APIKey != "" {
req.Header.Set("Authorization", "Bearer "+provider.APIKey)
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result struct {
Data []struct {
ID string `json:"id"`
} `json:"data"`
}
json.NewDecoder(resp.Body).Decode(&result)
for _, m := range result.Data {
models = append(models, cleanModelName(m.ID))
}
}
return models, nil
}
func cleanModelName(name string) string {
// Strip common prefixes
name = strings.TrimPrefix(name, "models/")
return name
}
func (reg *ProviderRegistry) chat(providerName, model, message string) (string, error) {
reg.mu.RLock()
provider, ok := reg.Providers[providerName]
reg.mu.RUnlock()
if !ok {
return "", fmt.Errorf("provider %q not found", providerName)
}
client := &http.Client{Timeout: 300 * time.Second}
var requestBody map[string]interface{}
var url string
switch provider.ProviderType {
case "ollama":
url = provider.URL + "/api/chat"
requestBody = map[string]interface{}{
"model": model, "messages": []map[string]string{{"role": "user", "content": message}},
"stream": false,
}
case "openai":
url = provider.URL + "/v1/chat/completions"
requestBody = map[string]interface{}{
"model": model, "messages": []map[string]string{{"role": "user", "content": message}},
}
default:
return "", fmt.Errorf("unsupported provider type: %s", provider.ProviderType)
}
data, _ := json.Marshal(requestBody)
req, _ := http.NewRequest("POST", url, bytes.NewReader(data))
req.Header.Set("Content-Type", "application/json")
if provider.APIKey != "" {
req.Header.Set("Authorization", "Bearer "+provider.APIKey)
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
// Parse response based on provider type
var result map[string]interface{}
json.Unmarshal(body, &result)
if content, ok := result["content"].(string); ok {
return content, nil // Ollama format
}
if choices, ok := result["choices"].([]interface{}); ok && len(choices) > 0 {
if choice, ok := choices[0].(map[string]interface{}); ok {
if msg, ok := choice["message"].(map[string]interface{}); ok {
if content, ok := msg["content"].(string); ok {
return content, nil // OpenAI format
}
}
}
}
return string(body), nil
return NewRegistry(defaultConfigPath())
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: provider-registry <connect|disconnect|list|models|chat> [args...]")
fmt.Fprintln(os.Stderr, "usage: provider-registry <connect|disconnect|list|models|chat|generate> [args...]")
os.Exit(1)
}
reg := loadRegistry()
@@ -206,7 +43,7 @@ func main() {
os.Exit(1)
}
name, url := args[0], args[1]
providerType := "ollama"
providerType := "openai" // the Python oracle's connect() default
apiKey := ""
if len(args) > 2 {
providerType = args[2]
@@ -214,8 +51,8 @@ func main() {
if len(args) > 3 {
apiKey = args[3]
}
provider := reg.connect(name, url, providerType, apiKey)
out, _ := json.MarshalIndent(provider, "", " ")
provider := reg.Connect(name, url, providerType, apiKey)
out, _ := json.MarshalIndent(provider.ToDict(), "", " ")
fmt.Println(string(out))
case "disconnect":
@@ -223,14 +60,11 @@ func main() {
fmt.Fprintln(os.Stderr, "usage: provider-registry disconnect <name>")
os.Exit(1)
}
reg.mu.Lock()
delete(reg.Providers, args[0])
reg.mu.Unlock()
saveRegistry(reg)
reg.Disconnect(args[0])
fmt.Println("disconnected:", args[0])
case "list":
for _, p := range reg.Providers {
for _, p := range reg.ListProviders() {
out, _ := json.Marshal(p)
fmt.Println(string(out))
}
@@ -240,12 +74,12 @@ func main() {
fmt.Fprintln(os.Stderr, "usage: provider-registry models <name>")
os.Exit(1)
}
provider, ok := reg.Providers[args[0]]
if !ok {
provider := reg.GetProvider(args[0])
if provider == nil {
fmt.Println("null")
return
}
models, err := reg.fetchModels(provider)
models, err := reg.FetchModels(provider)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
@@ -266,6 +100,31 @@ func main() {
out, _ := json.Marshal(map[string]string{"response": response})
fmt.Println(string(out))
case "generate":
fs := flag.NewFlagSet("generate", flag.ContinueOnError)
providerName := fs.String("provider", "", "provider name")
model := fs.String("model", "", "model id")
if err := fs.Parse(args); err != nil {
fmt.Fprintln(os.Stderr, "usage: provider-registry generate <prompt> [--provider <name>] [--model <model>]")
os.Exit(1)
}
if fs.NArg() < 1 {
fmt.Fprintln(os.Stderr, "usage: provider-registry generate <prompt> [--provider <name>] [--model <model>]")
os.Exit(1)
}
result := reg.Generate(fs.Arg(0), *providerName, *model, 500, 0.7, nil, nil)
if errText, ok := result["error"]; ok {
fmt.Printf("Error: %v\n", errText)
return
}
resp := pyStrOr(result["response"], "")
if len(resp) > 200 {
resp = resp[:200]
}
fmt.Printf("Response: %s...\n", resp)
fmt.Printf("Model: %v (%v)\n", result["model"], result["provider"])
fmt.Printf("Latency: %vms\n", result["latency_ms"])
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
os.Exit(1)
+257
View File
@@ -0,0 +1,257 @@
package main
// pyjson.go — Python json.dumps-compatible serialization for the
// tool-result `content` strings (json.dumps defaults: ", " / ": "
// separators, ensure_ascii=True) plus an order-preserving JSON parser
// so batteries can replay Python insertion-ordered objects verbatim.
import (
"encoding/json"
"fmt"
"math"
"strconv"
"strings"
"unicode/utf16"
)
// PyDict is an insertion-ordered object (Python dict semantics).
type PyDict struct {
Keys []string
Vals map[string]any
}
func NewPyDict() *PyDict { return &PyDict{Vals: map[string]any{}} }
func (d *PyDict) Set(k string, v any) {
if _, ok := d.Vals[k]; !ok {
d.Keys = append(d.Keys, k)
}
d.Vals[k] = v
}
func (d *PyDict) Get(k string) (any, bool) {
v, ok := d.Vals[k]
return v, ok
}
// PyDump — json.dumps(obj) with Python's default separators and
// ensure_ascii escaping. Maps serialize with sorted keys (Go map law);
// use PyDict where Python insertion order must be reproduced.
func PyDump(v any) string {
s, _ := pyDumpTry(v)
return s
}
// pyDumpTry reports (ok=false) when the value has no Python-JSON
// equivalent — the caller applies the oracle's degrade law.
func pyDumpTry(v any) (string, bool) {
var b strings.Builder
if err := pyWrite(&b, v); err != nil {
return "", false
}
return b.String(), true
}
func pyWrite(b *strings.Builder, v any) error {
switch x := v.(type) {
case nil:
b.WriteString("null")
case bool:
if x {
b.WriteString("true")
} else {
b.WriteString("false")
}
case string:
pyWriteString(b, x)
case int:
b.WriteString(strconv.Itoa(x))
case int64:
b.WriteString(strconv.FormatInt(x, 10))
case float64:
b.WriteString(pyFloat(x))
case json.Number:
b.WriteString(x.String())
case *PyDict:
b.WriteByte('{')
for i, k := range x.Keys {
if i > 0 {
b.WriteString(", ")
}
pyWriteString(b, k)
b.WriteString(": ")
pyWrite(b, x.Vals[k])
}
b.WriteByte('}')
case map[string]any:
keys := make([]string, 0, len(x))
for k := range x {
keys = append(keys, k)
}
sortStrings(keys)
b.WriteByte('{')
for i, k := range keys {
if i > 0 {
b.WriteString(", ")
}
pyWriteString(b, k)
b.WriteString(": ")
pyWrite(b, x[k])
}
b.WriteByte('}')
case []any:
b.WriteByte('[')
for i, e := range x {
if i > 0 {
b.WriteString(", ")
}
if err := pyWrite(b, e); err != nil {
return err
}
}
b.WriteByte(']')
case []map[string]any:
b.WriteByte('[')
for i, e := range x {
if i > 0 {
b.WriteString(", ")
}
if err := pyWrite(b, e); err != nil {
return err
}
}
b.WriteByte(']')
default:
// Unknown Go types have no Python json equivalent; the caller
// applies the oracle's degrade law.
return fmt.Errorf("type not JSON-serializable: %T", v)
}
return nil
}
const pyNonSerializable = "tool result not JSON serializable"
func pyFloat(f float64) string {
if f == math.Trunc(f) && math.Abs(f) < 1e15 {
return strconv.FormatFloat(f, 'f', 1, 64) // Python repr(2.0) → "2.0"
}
return strconv.FormatFloat(f, 'g', -1, 64)
}
func pyWriteString(b *strings.Builder, s string) {
b.WriteByte('"')
for _, r := range s {
switch r {
case '"':
b.WriteString("\\\"")
case '\\':
b.WriteString("\\\\")
case '\n':
b.WriteString("\\n")
case '\r':
b.WriteString("\\r")
case '\t':
b.WriteString("\\t")
case '\b':
b.WriteString("\\b")
case '\f':
b.WriteString("\\f")
default:
if r < 0x20 || r > 0x7e {
writeUnicodeEscape(b, r)
} else {
b.WriteRune(r)
}
}
}
b.WriteByte('"')
}
func writeUnicodeEscape(b *strings.Builder, r rune) {
if r > 0xffff {
hi, lo := utf16.EncodeRune(r)
fmt.Fprintf(b, "\\u%04x\\u%04x", hi, lo)
return
}
fmt.Fprintf(b, "\\u%04x", r)
}
func sortStrings(s []string) {
for i := 1; i < len(s); i++ {
for j := i; j > 0 && s[j] < s[j-1]; j-- {
s[j], s[j-1] = s[j-1], s[j]
}
}
}
// ParsePyJSON — order-preserving JSON decode: objects → *PyDict,
// arrays → []any, integral numbers → int, others → float64.
func ParsePyJSON(data []byte) (any, error) {
dec := json.NewDecoder(strings.NewReader(string(data)))
dec.UseNumber()
v, err := decodePyValue(dec)
if err != nil {
return nil, err
}
return v, nil
}
func decodePyValue(dec *json.Decoder) (any, error) {
tok, err := dec.Token()
if err != nil {
return nil, err
}
return decodePyToken(dec, tok)
}
func decodePyToken(dec *json.Decoder, tok json.Token) (any, error) {
switch t := tok.(type) {
case json.Delim:
switch t {
case '{':
d := NewPyDict()
for dec.More() {
keyTok, err := dec.Token()
if err != nil {
return nil, err
}
key, _ := keyTok.(string)
val, err := decodePyValue(dec)
if err != nil {
return nil, err
}
d.Set(key, val)
}
if _, err := dec.Token(); err != nil { // closing }
return nil, err
}
return d, nil
case '[':
arr := []any{}
for dec.More() {
val, err := decodePyValue(dec)
if err != nil {
return nil, err
}
arr = append(arr, val)
}
if _, err := dec.Token(); err != nil { // closing ]
return nil, err
}
return arr, nil
}
return nil, fmt.Errorf("unexpected delim %v", t)
case json.Number:
if n, err := t.Int64(); err == nil && !strings.ContainsAny(t.String(), ".eE") {
return int(n), nil
}
f, err := t.Float64()
if err != nil {
return nil, err
}
return f, nil
case string, bool, nil:
return tok, nil
}
return nil, fmt.Errorf("unexpected token %v", tok)
}
+835
View File
@@ -0,0 +1,835 @@
package main
// registry.go — factory-gap-pr: the provider_registry.py surface that
// main.go's R2 W5b port left uncovered (dichotomy wave #2479 tranche).
// Name-preserving parity port: every fn mirrors the Python oracle 1:1
// (_load_config, _save_config, connect, disconnect, list_providers,
// get_provider, list_models, _fetch_models, _fetch_ollama_models,
// _fetch_openai_models, _fetch_anthropic_models, _clean_model_name,
// to_dict, generate, _generate_ollama, _generate_openai,
// _generate_anthropic, get_registry, connect_provider, list_all_models,
// _ctx_budget). Network IO sits behind the injectable `Do` seam; the
// tool executor behind the ToolExecutorFunc seam — tests never touch a
// socket or the real executor.
import (
"bytes"
"encoding/json"
"fmt"
"io"
"math"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
)
// ModelProvider — parity shape of the Python ModelProvider dataclass.
type ModelProvider struct {
Name string `json:"name"`
URL string `json:"url"`
ProviderType string `json:"provider_type"` // "ollama", "openai", "anthropic", "custom"
APIKey string `json:"api_key,omitempty"`
Models []map[string]any `json:"models"`
Enabled bool `json:"enabled"`
LastHealthCheck string `json:"last_health_check,omitempty"`
HealthStatus string `json:"health_status"` // "healthy", "error", "unknown"
}
func NewProvider(name, url, providerType, apiKey string) *ModelProvider {
return &ModelProvider{
Name: name, URL: url, ProviderType: providerType,
APIKey: apiKey, Models: []map[string]any{},
Enabled: true, HealthStatus: "unknown",
}
}
// ProviderFromFields rebuilds a provider from a truth-row field map
// (the frozen snapshot shape of the Python dataclass).
func ProviderFromFields(f map[string]any) *ModelProvider {
p := NewProvider(pyStr(f["name"]), pyStr(f["url"]), pyStr(f["provider_type"]), pyStr(f["api_key"]))
p.Enabled = pyBoolOr(f["enabled"], true)
p.LastHealthCheck = pyStr(f["last_health_check"])
p.HealthStatus = pyStrOr(f["health_status"], "unknown")
if models, ok := f["models"].([]any); ok {
p.Models = []map[string]any{}
for _, m := range models {
if mm, ok := m.(map[string]any); ok {
p.Models = append(p.Models, mm)
}
}
}
return p
}
// ToDict — ModelProvider.to_dict (api_key and last_health_check are
// deliberately excluded — the Python oracle's masking law).
func (p *ModelProvider) ToDict() map[string]any {
models := p.Models
if models == nil {
models = []map[string]any{}
}
return map[string]any{
"name": p.Name, "url": p.URL, "provider_type": p.ProviderType,
"models": models, "enabled": p.Enabled,
"health_status": p.HealthStatus,
}
}
// ProviderRegistry — parity shape of the Python ProviderRegistry.
type ProviderRegistry struct {
mu sync.RWMutex
Providers map[string]*ModelProvider
ConfigPath string
order []string // insertion order (Python dict semantics)
// Do is the injectable HTTP seam: (url, body, headers, timeout) →
// response body. nil = real net/http transport.
Do func(url string, body []byte, headers map[string]string, timeout time.Duration) ([]byte, error)
}
func NewRegistry(configPath string) *ProviderRegistry {
reg := &ProviderRegistry{Providers: map[string]*ModelProvider{}, ConfigPath: configPath}
reg.LoadConfig()
return reg
}
// defaultConfigPath — the Python oracle's ~/.config/rune/providers.json.
// RUNE_REGISTRY_CONFIG overrides (tests); RUNE_MEMORY_DIR keeps the
// legacy Go CLI location working.
func defaultConfigPath() string {
if p := os.Getenv("RUNE_REGISTRY_CONFIG"); p != "" {
return p
}
if home := os.Getenv("RUNE_MEMORY_DIR"); home != "" {
return filepath.Join(home, "provider_registry.json")
}
home, err := os.UserHomeDir()
if err != nil {
return "providers.json"
}
return filepath.Join(home, ".config", "rune", "providers.json")
}
// providerFromConfigMap — the _load_config per-provider defaults law:
// url="", type="custom", api_key=None, enabled=True.
func providerFromConfigMap(name string, pc map[string]any) *ModelProvider {
p := NewProvider(name, pyStrOr(pc["url"], ""), pyStrOr(pc["type"], "custom"), pyStr(pc["api_key"]))
p.Enabled = pyBoolOr(pc["enabled"], true)
return p
}
// LoadConfig — ProviderRegistry._load_config. Missing file → empty
// registry; malformed JSON → warning + empty (best-effort degrade).
// Provider key order from the file is preserved (Python dict law).
func (reg *ProviderRegistry) LoadConfig() {
data, err := os.ReadFile(reg.ConfigPath)
if err != nil {
return // missing config is not an error — empty registry
}
keys, cfgs, err := decodeOrderedProviders(data)
if err != nil {
fmt.Printf("Warning: Could not load provider config: %v\n", err)
return
}
reg.mu.Lock()
defer reg.mu.Unlock()
reg.order = keys
for _, name := range keys {
reg.Providers[name] = providerFromConfigMap(name, cfgs[name])
}
}
// decodeOrderedProviders walks the JSON token stream so the providers'
// file order survives (Go map decode would discard it).
func decodeOrderedProviders(data []byte) ([]string, map[string]map[string]any, error) {
cfgs := map[string]map[string]any{}
dec := json.NewDecoder(bytes.NewReader(data))
tok, err := dec.Token()
if err != nil {
return nil, nil, err
}
if delim, ok := tok.(json.Delim); !ok || delim != '{' {
return nil, nil, fmt.Errorf("config root is not an object")
}
var keys []string
for dec.More() {
keyTok, err := dec.Token()
if err != nil {
return nil, nil, err
}
key, _ := keyTok.(string)
if key == "providers" {
tok, err := dec.Token()
if err != nil {
return nil, nil, err
}
if delim, ok := tok.(json.Delim); !ok || delim != '{' {
return nil, nil, fmt.Errorf("providers section is not an object")
}
for dec.More() {
nameTok, err := dec.Token()
if err != nil {
return nil, nil, err
}
name, _ := nameTok.(string)
var pc map[string]any
if err := dec.Decode(&pc); err != nil {
return nil, nil, err
}
if _, seen := cfgs[name]; !seen {
keys = append(keys, name)
}
cfgs[name] = pc
}
if _, err := dec.Token(); err != nil { // closing }
return nil, nil, err
}
} else {
var skip any
if err := dec.Decode(&skip); err != nil {
return nil, nil, err
}
}
}
return keys, cfgs, nil
}
// SaveConfig — ProviderRegistry._save_config: API keys are masked with
// "***" (never persisted in the clear), absent keys persist as null.
func (reg *ProviderRegistry) SaveConfig() error {
os.MkdirAll(filepath.Dir(reg.ConfigPath), 0o755)
providers := map[string]any{}
for name, p := range reg.Providers {
var apiKey any
if p.APIKey != "" {
apiKey = "***"
}
providers[name] = map[string]any{
"url": p.URL, "type": p.ProviderType,
"api_key": apiKey, "enabled": p.Enabled,
}
}
data, err := json.MarshalIndent(map[string]any{"providers": providers}, "", " ")
if err != nil {
return err
}
return os.WriteFile(reg.ConfigPath, data, 0o600)
}
// Connect — ProviderRegistry.connect (persists through SaveConfig).
func (reg *ProviderRegistry) Connect(name, url, providerType, apiKey string) *ModelProvider {
p := NewProvider(name, url, providerType, apiKey)
reg.mu.Lock()
if _, exists := reg.Providers[name]; !exists {
reg.order = append(reg.order, name)
}
reg.Providers[name] = p
reg.mu.Unlock()
reg.SaveConfig()
return p
}
// Disconnect — ProviderRegistry.disconnect: absent name is a no-op.
func (reg *ProviderRegistry) Disconnect(name string) {
reg.mu.Lock()
_, ok := reg.Providers[name]
if ok {
delete(reg.Providers, name)
for i, n := range reg.order {
if n == name {
reg.order = append(reg.order[:i], reg.order[i+1:]...)
break
}
}
}
reg.mu.Unlock()
if ok {
reg.SaveConfig()
}
}
// orderedNames — the Python dict law: providers iterate in insertion
// order (connect sequence, then config-file order on load).
func (reg *ProviderRegistry) orderedNames() []string {
reg.mu.RLock()
defer reg.mu.RUnlock()
names := make([]string, 0, len(reg.order))
seen := map[string]bool{}
for _, n := range reg.order {
if _, ok := reg.Providers[n]; ok && !seen[n] {
names = append(names, n)
seen[n] = true
}
}
return names
}
// ListProviders — ProviderRegistry.list_providers.
func (reg *ProviderRegistry) ListProviders() []map[string]any {
out := []map[string]any{}
for _, name := range reg.orderedNames() {
out = append(out, reg.Providers[name].ToDict())
}
return out
}
// GetProvider — ProviderRegistry.get_provider (nil when absent).
func (reg *ProviderRegistry) GetProvider(name string) *ModelProvider {
reg.mu.RLock()
defer reg.mu.RUnlock()
return reg.Providers[name]
}
// ListModels — ProviderRegistry.list_models: enabled providers only,
// per-provider fetch errors degrade to a warning (never crash), and
// every model dict is annotated with provider + provider_type.
func (reg *ProviderRegistry) ListModels() []map[string]any {
all := []map[string]any{}
for _, name := range reg.orderedNames() {
p := reg.Providers[name]
if !p.Enabled {
continue
}
models, err := reg.FetchModels(p)
if err != nil {
fmt.Printf("Warning: Could not fetch models from %s: %v\n", p.Name, err)
continue
}
for _, m := range models {
m["provider"] = p.Name
m["provider_type"] = p.ProviderType
all = append(all, m)
}
}
return all
}
// FetchModels — ProviderRegistry._fetch_models dispatch.
func (reg *ProviderRegistry) FetchModels(p *ModelProvider) ([]map[string]any, error) {
switch p.ProviderType {
case "ollama":
return reg.FetchOllamaModels(p)
case "openai":
return reg.FetchOpenaiModels(p)
case "anthropic":
return reg.FetchAnthropicModels(p)
default:
return []map[string]any{}, nil
}
}
// FetchOllamaModels — ProviderRegistry._fetch_ollama_models. Every
// failure (transport, decode, missing "name") is wrapped into the
// oracle's single "Ollama connection failed: …" error.
func (reg *ProviderRegistry) FetchOllamaModels(p *ModelProvider) ([]map[string]any, error) {
body, err := reg.httpDo(p.URL+"/api/tags", nil, nil, 5*time.Second)
if err != nil {
return nil, fmt.Errorf("Ollama connection failed: %v", err)
}
var data map[string]any
if err := json.Unmarshal(body, &data); err != nil {
return nil, fmt.Errorf("Ollama connection failed: %v", err)
}
models := []map[string]any{}
rawModels, _ := data["models"].([]any)
for _, rm := range rawModels {
m, _ := rm.(map[string]any)
if m == nil {
continue
}
nameVal, ok := m["name"]
if !ok {
return nil, fmt.Errorf("Ollama connection failed: %v", missingKeyErr("name"))
}
details, _ := m["details"].(map[string]any)
if details == nil {
details = map[string]any{}
}
models = append(models, map[string]any{
"name": pyStr(nameVal),
"display_name": CleanModelName(pyStr(nameVal)),
"size_gb": roundHalfEven(pyFloatOr(m["size"], 0)/(1024*1024*1024), 1),
"family": pyStrOr(details["family"], "unknown"),
"parameters": pyStrOr(details["parameter_size"], "unknown"),
})
}
return models, nil
}
// FetchOpenaiModels — ProviderRegistry._fetch_openai_models.
func (reg *ProviderRegistry) FetchOpenaiModels(p *ModelProvider) ([]map[string]any, error) {
headers := map[string]string{"Content-Type": "application/json"}
if p.APIKey != "" {
headers["Authorization"] = "Bearer " + p.APIKey
}
body, err := reg.httpDo(p.URL+"/v1/models", nil, headers, 10*time.Second)
if err != nil {
return nil, fmt.Errorf("[OI] API connection failed: %v", err)
}
var data map[string]any
if err := json.Unmarshal(body, &data); err != nil {
return nil, fmt.Errorf("[OI] API connection failed: %v", err)
}
models := []map[string]any{}
rawModels, _ := data["data"].([]any)
for _, rm := range rawModels {
m, _ := rm.(map[string]any)
if m == nil {
continue
}
id := pyStrOr(m["id"], "unknown")
models = append(models, map[string]any{
"name": id, "display_name": id, "size_gb": 0,
"family": "cloud", "parameters": "cloud",
})
}
return models, nil
}
// FetchAnthropicModels — ProviderRegistry._fetch_anthropic_models: the
// Anthropic API has no /v1/models, so the oracle returns its pinned
// static list (ported verbatim).
func (reg *ProviderRegistry) FetchAnthropicModels(p *ModelProvider) ([]map[string]any, error) {
return []map[string]any{
{"name": "claude-sonnet-4-20250514", "display_name": "Claude Sonnet 4", "size_gb": 0, "family": "cloud", "parameters": "cloud"},
{"name": "claude-3-5-sonnet-20241022", "display_name": "Claude 3.5 Sonnet", "size_gb": 0, "family": "cloud", "parameters": "cloud"},
{"name": "claude-3-5-haiku-20241022", "display_name": "Claude 3.5 Haiku", "size_gb": 0, "family": "cloud", "parameters": "cloud"},
}, nil
}
var (
cleanReFamilySize = regexp.MustCompile(`(?i)([A-Za-z]+[\d.]*)-?(\d+[BMb])`)
cleanReFamilyVers = regexp.MustCompile(`(?i)([A-Za-z]+)-(\d+)-(\d+[BMb])`)
)
// CleanModelName — the _clean_model_name delegation target
// (model_connect._clean_model_name), ported 1:1.
func CleanModelName(name string) string {
if strings.Contains(name, "/") && (strings.Contains(name, "hf.co") || strings.Contains(name, "huggingface")) {
parts := strings.Split(name, "/")
modelPart := name
if len(parts) >= 3 {
modelPart = parts[len(parts)-1]
}
if strings.Contains(modelPart, ":") {
modelPart = strings.Split(modelPart, ":")[0]
}
if m := cleanReFamilySize.FindStringSubmatch(modelPart); m != nil {
return m[1] + " " + strings.ToUpper(m[2])
}
if m := cleanReFamilyVers.FindStringSubmatch(modelPart); m != nil {
return m[1] + " " + m[2] + " " + strings.ToUpper(m[3])
}
for _, sep := range []string{"-", "_", "."} {
if strings.Contains(modelPart, sep) {
firstPart := strings.Split(modelPart, sep)[0]
if len(firstPart) > 3 {
return firstPart
}
}
}
if len(modelPart) > 30 {
return modelPart[:30]
}
return modelPart
}
return name
}
// Generate — ProviderRegistry.generate: provider lookup (named or first
// available), model autodetect via FetchModels, then the per-type
// generate dispatch. Unsupported types yield an error dict.
func (reg *ProviderRegistry) Generate(prompt string, providerName string, model string,
maxTokens int, temperature float64, systemPrompt *string, tools []map[string]any) map[string]any {
var p *ModelProvider
if providerName != "" {
p = reg.GetProvider(providerName)
if p == nil {
return map[string]any{"error": fmt.Sprintf("Provider not found: %s", providerName)}
}
} else {
names := reg.orderedNames()
if len(names) == 0 {
return map[string]any{"error": "No providers connected"}
}
p = reg.Providers[names[0]]
}
if model == "" {
models, err := reg.FetchModels(p)
if err != nil {
return map[string]any{"error": err.Error(), "model": model, "provider": p.Name}
}
if len(models) == 0 {
return map[string]any{"error": fmt.Sprintf("No models available from %s", p.Name)}
}
model = pyStr(models[0]["name"])
}
switch p.ProviderType {
case "ollama":
return reg.GenerateOllama(p, prompt, model, maxTokens, temperature)
case "openai":
return reg.GenerateOpenai(p, prompt, model, maxTokens, temperature, systemPrompt, tools)
case "anthropic":
return reg.GenerateAnthropic(p, prompt, model, maxTokens, temperature, systemPrompt, tools)
default:
return map[string]any{"error": fmt.Sprintf("Unsupported provider type: %s", p.ProviderType)}
}
}
// GenerateOllama — ProviderRegistry._generate_ollama. The num_ctx is
// model-aware (_ctx_budget parity — never a hardcoded 8192).
func (reg *ProviderRegistry) GenerateOllama(p *ModelProvider, prompt, model string,
maxTokens int, temperature float64) map[string]any {
start := time.Now()
payload := map[string]any{
"model": model, "prompt": prompt, "stream": false,
"options": map[string]any{
"temperature": temperature, "num_predict": maxTokens,
"num_ctx": CtxBudget(model),
},
}
body, _ := json.Marshal(payload)
respBody, err := reg.httpDo(p.URL+"/api/generate", body,
map[string]string{"Content-Type": "application/json"}, 120*time.Second)
if err != nil {
return map[string]any{"error": err.Error(), "model": model, "provider": p.Name}
}
var result map[string]any
json.Unmarshal(respBody, &result)
latency := int(time.Since(start).Milliseconds())
return map[string]any{
"response": strings.TrimSpace(pyStrOr(result["response"], "")),
"model": model,
"provider": p.Name,
"latency_ms": latency,
"tokens": pyIntOr(result["eval_count"], 0),
}
}
// GenerateOpenai — ProviderRegistry._generate_openai with native tool
// calling (tools → FormatToolsOpenai + tool_choice auto).
func (reg *ProviderRegistry) GenerateOpenai(p *ModelProvider, prompt, model string,
maxTokens int, temperature float64, systemPrompt *string, tools []map[string]any) map[string]any {
start := time.Now()
headers := map[string]string{"Content-Type": "application/json"}
if p.APIKey != "" {
headers["Authorization"] = "Bearer " + p.APIKey
}
messages := []map[string]any{}
if systemPrompt != nil {
messages = append(messages, map[string]any{"role": "system", "content": *systemPrompt})
}
messages = append(messages, map[string]any{"role": "user", "content": prompt})
data := map[string]any{
"model": model, "messages": messages,
"max_tokens": maxTokens, "temperature": temperature,
}
if len(tools) > 0 {
data["tools"] = FormatToolsOpenai(tools)
data["tool_choice"] = "auto"
}
body, _ := json.Marshal(data)
respBody, err := reg.httpDo(p.URL+"/v1/chat/completions", body, headers, 120*time.Second)
if err != nil {
return map[string]any{"error": err.Error(), "model": model, "provider": p.Name}
}
var result map[string]any
json.Unmarshal(respBody, &result)
latency := int(time.Since(start).Milliseconds())
// choice = result.get("choices", [{}])[0] — an empty choices list
// raises in the oracle and degrades to the error dict here too.
message := map[string]any{}
if choices, ok := result["choices"].([]any); ok {
if len(choices) == 0 {
return map[string]any{"error": "list index out of range", "model": model, "provider": p.Name}
}
if choice, ok := choices[0].(map[string]any); ok {
if msg, ok := choice["message"].(map[string]any); ok {
message = msg
}
}
}
toolCalls, _ := message["tool_calls"].([]any)
usage, _ := result["usage"].(map[string]any)
if usage == nil {
usage = map[string]any{}
}
if len(toolCalls) > 0 {
toolResults := ExecuteToolCalls(pyMaps(toolCalls))
return map[string]any{
"response": pyStrOr(message["content"], ""),
"tool_calls": pyAny(toolCalls),
"tool_results": toolResults,
"model": model,
"provider": p.Name,
"latency_ms": latency,
"tokens": pyIntOr(usage["total_tokens"], 0),
}
}
return map[string]any{
"response": strings.TrimSpace(pyStrOr(message["content"], "")),
"model": model,
"provider": p.Name,
"latency_ms": latency,
"tokens": pyIntOr(usage["total_tokens"], 0),
}
}
// GenerateAnthropic — ProviderRegistry._generate_anthropic with native
// tool calling (tools → FormatToolsAnthropic; no tool_choice).
func (reg *ProviderRegistry) GenerateAnthropic(p *ModelProvider, prompt, model string,
maxTokens int, temperature float64, systemPrompt *string, tools []map[string]any) map[string]any {
start := time.Now()
headers := map[string]string{
"Content-Type": "application/json",
"x-api-key": p.APIKey,
"anthropic-version": "2023-06-01",
}
data := map[string]any{
"model": model,
"max_tokens": maxTokens,
"messages": []map[string]any{{"role": "user", "content": prompt}},
}
if systemPrompt != nil {
data["system"] = *systemPrompt
}
if len(tools) > 0 {
data["tools"] = FormatToolsAnthropic(tools)
}
body, _ := json.Marshal(data)
respBody, err := reg.httpDo(p.URL+"/v1/messages", body, headers, 120*time.Second)
if err != nil {
return map[string]any{"error": err.Error(), "model": model, "provider": p.Name}
}
var result map[string]any
json.Unmarshal(respBody, &result)
latency := int(time.Since(start).Milliseconds())
content, _ := result["content"].([]any)
toolCalls := []any{}
responseText := ""
for _, blockAny := range content {
block, _ := blockAny.(map[string]any)
if block == nil {
continue
}
switch pyStrOr(block["type"], "") {
case "text":
responseText += pyStrOr(block["text"], "")
case "tool_use":
toolCalls = append(toolCalls, block)
}
}
usage, _ := result["usage"].(map[string]any)
if usage == nil {
usage = map[string]any{}
}
tokens := pyIntOr(usage["input_tokens"], 0) + pyIntOr(usage["output_tokens"], 0)
if len(toolCalls) > 0 {
toolResults := ExecuteToolCallsAnthropic(pyMaps(toolCalls))
return map[string]any{
"response": responseText,
"tool_calls": toolCalls,
"tool_results": toolResults,
"model": model,
"provider": p.Name,
"latency_ms": latency,
"tokens": tokens,
}
}
return map[string]any{
"response": strings.TrimSpace(responseText),
"model": model,
"provider": p.Name,
"latency_ms": latency,
"tokens": tokens,
}
}
// httpDo — the HTTP seam. reg.Do overrides; otherwise the real
// net/http transport (POST when body != nil, GET otherwise).
func (reg *ProviderRegistry) httpDo(url string, body []byte, headers map[string]string,
timeout time.Duration) ([]byte, error) {
if reg.Do != nil {
return reg.Do(url, body, headers, timeout)
}
client := &http.Client{Timeout: timeout}
var req *http.Request
var err error
if body != nil {
req, err = http.NewRequest("POST", url, bytes.NewReader(body))
} else {
req, err = http.NewRequest("GET", url, nil)
}
if err != nil {
return nil, err
}
for k, v := range headers {
req.Header.Set(k, v)
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("HTTP Error %d: %s", resp.StatusCode, strings.TrimSpace(string(data)))
}
return data, nil
}
// ── global registry (get_registry / connect_provider / list_all_models) ──
var (
globalRegistryMu sync.Mutex
globalRegistry *ProviderRegistry
)
// GetRegistry — module get_registry (thread-safe singleton).
func GetRegistry() *ProviderRegistry {
globalRegistryMu.Lock()
defer globalRegistryMu.Unlock()
if globalRegistry == nil {
globalRegistry = NewRegistry(defaultConfigPath())
}
return globalRegistry
}
// ConnectProvider — module connect_provider.
func ConnectProvider(name, url, providerType, apiKey string) map[string]any {
if providerType == "" {
providerType = "openai"
}
p := GetRegistry().Connect(name, url, providerType, apiKey)
return p.ToDict()
}
// ListAllModels — module list_all_models.
func ListAllModels() []map[string]any {
return GetRegistry().ListModels()
}
// Generate — module-level generate (delegates to the global registry).
func Generate(prompt string, providerName string, model string, maxTokens int,
temperature float64, systemPrompt *string, tools []map[string]any) map[string]any {
return GetRegistry().Generate(prompt, providerName, model, maxTokens, temperature, systemPrompt, tools)
}
// ── _ctx_budget (#28: model-aware num_ctx, never hardcoded 8192) ─────────
type budgetProfile struct {
window int
local bool
}
// Mirrors model_registry.DEFAULT_PROFILES (id → context window, local).
var budgetProfiles = map[string]budgetProfile{
"deterministic": {0, true},
"qwen2.5:3b": {32768, true},
"qwen3:14b": {32768, true},
"qwen3.6:27b": {32768, true},
"mimo-v2.5-pro": {128000, false},
}
const (
outputReserve = 2048 // OUTPUT_RESERVE["default"]
toolResultReSe = 1024 // TOOL_RESULT_RESERVE
)
// CtxBudget — _ctx_budget: window − output_reserve − tool_reserve
// (tool reserve only for non-local), floored at 1024. Unknown models
// get the conservative 8192 window.
func CtxBudget(modelID string) int {
window := 8192
local := true
if p, ok := budgetProfiles[modelID]; ok {
window = p.window
if window == 0 {
window = 8192
}
local = p.local
}
toolReserve := 0
if !local {
toolReserve = toolResultReSe
}
numCtx := window - outputReserve - toolReserve
if numCtx < 1024 {
numCtx = 1024
}
return numCtx
}
// ── small parity helpers ─────────────────────────────────────────────────
func pyStr(v any) string {
if s, ok := v.(string); ok {
return s
}
return ""
}
func pyStrOr(v any, def string) string {
if s, ok := v.(string); ok {
return s
}
return def
}
func pyBoolOr(v any, def bool) bool {
if b, ok := v.(bool); ok {
return b
}
return def
}
func pyFloatOr(v any, def float64) float64 {
switch n := v.(type) {
case float64:
return n
case int:
return float64(n)
default:
return def
}
}
func pyIntOr(v any, def int) int {
switch n := v.(type) {
case float64:
return int(n)
case int:
return n
default:
return def
}
}
func pyAny(v any) any { return v }
func pyMaps(items []any) []map[string]any {
out := make([]map[string]any, 0, len(items))
for _, it := range items {
if m, ok := it.(map[string]any); ok {
out = append(out, m)
}
}
return out
}
// missingKeyErr — str(KeyError(k)) is quoted in Python.
func missingKeyErr(k string) string { return "'" + k + "'" }
// roundHalfEven — Python round(): banker's rounding at the digit.
func roundHalfEven(x float64, digits int) float64 {
shift := math.Pow(10, float64(digits))
return math.RoundToEven(x*shift) / shift
}
+95
View File
@@ -0,0 +1,95 @@
package main
// toolcalls.go — _execute_tool_calls / _execute_tool_calls_anthropic
// parity (factory-gap-pr). The tool executor is the IO seam: tests
// inject ToolExecutorFunc; production wires the local tool runtime.
// Shaping law (1:1 with the oracle): [OI] calls carry
// function.name + function.arguments (a JSON string that degrades to {}
// on decode failure); Anthropic calls carry name + input. Every result
// is json.dumps'd — a non-serializable result degrades to the fixed
// error marker (the oracle's PreparedTool crash fix), never aborts the
// loop.
import "encoding/json"
// ToolExecutorFunc is the injectable executor seam: name + args →
// result (any JSON-shape value). nil = no executor wired.
var ToolExecutorFunc func(name string, args map[string]any) (any, error)
func runTool(name string, args map[string]any) any {
if args == nil {
args = map[string]any{}
}
if ToolExecutorFunc == nil {
return map[string]any{"success": false, "error": "no tool executor wired"}
}
res, err := ToolExecutorFunc(name, args)
if err != nil {
return map[string]any{"success": false, "error": err.Error()}
}
return res
}
// pyDumpResult — json.dumps(result) with the oracle's degrade law:
// a result with no JSON-shape equivalent becomes the fixed error marker
// (insertion-ordered to match the Python fallback's byte shape).
func pyDumpResult(result any) string {
if s, ok := pyDumpTry(result); ok {
return s
}
d := NewPyDict()
d.Set("success", false)
d.Set("error", pyNonSerializable)
return PyDump(d)
}
// ExecuteToolCalls — _execute_tool_calls.
func ExecuteToolCalls(toolCalls []map[string]any) []map[string]any {
results := []map[string]any{}
for _, call := range toolCalls {
fn, _ := call["function"].(map[string]any)
if fn == nil {
fn = map[string]any{}
}
name := pyStrOr(fn["name"], "")
var args map[string]any
switch a := fn["arguments"].(type) {
case string:
var parsed any
if err := json.Unmarshal([]byte(a), &parsed); err == nil {
if m, ok := parsed.(map[string]any); ok {
args = m
} else {
args = map[string]any{}
}
} else {
args = map[string]any{}
}
case map[string]any:
args = a
default:
args = map[string]any{}
}
results = append(results, map[string]any{
"tool_call_id": pyStrOr(call["id"], ""),
"role": "tool",
"content": pyDumpResult(runTool(name, args)),
})
}
return results
}
// ExecuteToolCallsAnthropic — _execute_tool_calls_anthropic.
func ExecuteToolCallsAnthropic(toolCalls []map[string]any) []map[string]any {
results := []map[string]any{}
for _, call := range toolCalls {
name := pyStrOr(call["name"], "")
args, _ := call["input"].(map[string]any)
results = append(results, map[string]any{
"type": "tool_result",
"tool_use_id": pyStrOr(call["id"], ""),
"content": pyDumpResult(runTool(name, args)),
})
}
return results
}
+388
View File
@@ -0,0 +1,388 @@
[
{
"fn": "_format_tools_openai",
"args": [
[]
],
"kwargs": {},
"expected": []
},
{
"fn": "_format_tools_openai",
"args": [
[
{
"name": "read_file",
"description": "Read a file",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string"
}
}
}
}
]
],
"kwargs": {},
"expected": [
{
"type": "function",
"function": {
"name": "read_file",
"description": "Read a file",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string"
}
}
}
}
}
]
},
{
"fn": "_format_tools_openai",
"args": [
[
{
"name": "write_file"
}
]
],
"kwargs": {},
"expected": [
{
"type": "function",
"function": {
"name": "write_file",
"description": "",
"parameters": {
"type": "object",
"properties": {}
}
}
}
]
},
{
"fn": "_format_tools_openai",
"args": [
[
{
"name": "read_file",
"description": "Read a file",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string"
}
}
}
},
{
"name": "write_file"
}
]
],
"kwargs": {},
"expected": [
{
"type": "function",
"function": {
"name": "read_file",
"description": "Read a file",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string"
}
}
}
}
},
{
"type": "function",
"function": {
"name": "write_file",
"description": "",
"parameters": {
"type": "object",
"properties": {}
}
}
}
]
},
{
"fn": "_format_tools_anthropic",
"args": [
[]
],
"kwargs": {},
"expected": []
},
{
"fn": "_format_tools_anthropic",
"args": [
[
{
"name": "read_file",
"description": "Read a file",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string"
}
}
}
}
]
],
"kwargs": {},
"expected": [
{
"name": "read_file",
"description": "Read a file",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string"
}
}
}
}
]
},
{
"fn": "_format_tools_anthropic",
"args": [
[
{
"name": "write_file"
}
]
],
"kwargs": {},
"expected": [
{
"name": "write_file",
"description": "",
"input_schema": {
"type": "object",
"properties": {}
}
}
]
},
{
"fn": "_format_tools_anthropic",
"args": [
[
{
"name": "read_file",
"description": "Read a file",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string"
}
}
}
},
{
"name": "write_file"
}
]
],
"kwargs": {},
"expected": [
{
"name": "read_file",
"description": "Read a file",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string"
}
}
}
},
{
"name": "write_file",
"description": "",
"input_schema": {
"type": "object",
"properties": {}
}
}
]
},
{
"fn": "_ctx_budget",
"args": [
"unknown-model-xyz"
],
"kwargs": {},
"expected": 6144
},
{
"fn": "_ctx_budget",
"args": [
"deterministic"
],
"kwargs": {},
"expected": 6144
},
{
"fn": "_ctx_budget",
"args": [
"qwen2.5:3b"
],
"kwargs": {},
"expected": 30720
},
{
"fn": "_ctx_budget",
"args": [
"qwen3:14b"
],
"kwargs": {},
"expected": 30720
},
{
"fn": "_ctx_budget",
"args": [
"qwen3.6:27b"
],
"kwargs": {},
"expected": 30720
},
{
"fn": "_ctx_budget",
"args": [
"mimo-v2.5-pro"
],
"kwargs": {},
"expected": 124928
},
{
"fn": "_execute_tool_calls",
"args": [
[
{
"id": "call_1",
"function": {
"name": "read_file",
"arguments": "{\"path\": \"/nonexistent/zzz.txt\"}"
}
}
]
],
"kwargs": {},
"expected": [
{
"tool_call_id": "call_1",
"role": "tool",
"content": "{\"success\": false, \"error\": \"Access denied: /nonexistent/zzz.txt is outside allowed directories\"}"
}
]
},
{
"fn": "_execute_tool_calls",
"args": [
[
{
"id": "call_2",
"function": {
"name": "read_file",
"arguments": "not-json"
}
}
]
],
"kwargs": {},
"expected": [
{
"tool_call_id": "call_2",
"role": "tool",
"content": "{\"success\": false, \"error\": \"Not a file: \"}"
}
]
},
{
"fn": "_execute_tool_calls",
"args": [
[
{
"id": "call_3",
"function": {
"name": "no_such_tool_xyz",
"arguments": "{}"
}
}
]
],
"kwargs": {},
"expected": [
{
"tool_call_id": "call_3",
"role": "tool",
"content": "{\"success\": false, \"error\": \"tool result not JSON serializable\"}"
}
]
},
{
"fn": "_execute_tool_calls_anthropic",
"args": [
[
{
"type": "tool_use",
"id": "tu_1",
"name": "read_file",
"input": {
"path": "/nonexistent/zzz.txt"
}
}
]
],
"kwargs": {},
"expected": [
{
"type": "tool_result",
"tool_use_id": "tu_1",
"content": "{\"success\": false, \"error\": \"Access denied: /nonexistent/zzz.txt is outside allowed directories\"}"
}
]
},
{
"fn": "_execute_tool_calls_anthropic",
"args": [
[
{
"type": "tool_use",
"id": "tu_2",
"name": "no_such_tool_xyz",
"input": {}
}
]
],
"kwargs": {},
"expected": [
{
"type": "tool_result",
"tool_use_id": "tu_2",
"content": "{\"success\": false, \"error\": \"tool result not JSON serializable\"}"
}
]
}
]
@@ -31,7 +31,7 @@ func TestFormatParity(t *testing.T) {
for _, c := range doc.Cases {
var got []map[string]interface{}
if c.Fn == "openai" {
got = FormatToolsOpenAI(tools)
got = FormatToolsOpenai(tools)
} else {
got = FormatToolsAnthropic(tools)
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,359 @@
package main
// truth_registry_test.go — factory-gap-pr parity gate for the
// ProviderRegistry method surface: replays truth_registry.json (frozen
// from the live Python oracle with urllib fully scripted by
// scripts/gen_provider_registry_truth.py) against the Go port. HTTP
// goes through the injected Do seam; config files live in per-test
// temp dirs. latency_ms is excluded on both sides (the oracle's clock
// is not injectable — no-clock law).
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"time"
)
func dget(d *PyDict, k string) any {
if d == nil {
return nil
}
v, _ := d.Get(k)
return v
}
type capturedReq struct {
url string
body []byte
}
// buildRegistryFromInput constructs a registry from a truth row:
// providers inserted in frozen key order (ParsePyJSON preserves the
// Python dict order), HTTP routed through the scripted Do seam.
func buildRegistryFromInput(t *testing.T, input *PyDict) (*ProviderRegistry, *[]capturedReq) {
t.Helper()
reg := NewRegistry(filepath.Join(t.TempDir(), "providers.json"))
captured := &[]capturedReq{}
if routesAny := dget(input, "routes"); routesAny != nil {
routes, _ := routesAny.([]any)
reg.Do = func(url string, body []byte, headers map[string]string, timeout time.Duration) ([]byte, error) {
for _, r := range routes {
route, _ := r.(*PyDict)
suffix, _ := dget(route, "suffix").(string)
if suffix != "" && len(url) >= len(suffix) && url[len(url)-len(suffix):] == suffix {
if exc, ok := dget(route, "exc").(string); ok {
return nil, &urlError{msg: exc}
}
*captured = append(*captured, capturedReq{url: url, body: body})
return json.Marshal(plainJSON(dget(route, "body")))
}
}
return nil, &urlError{msg: "unexpected url: " + url}
}
}
if provsAny := dget(input, "providers"); provsAny != nil {
provs, _ := provsAny.(*PyDict)
if provs != nil {
for _, name := range provs.Keys {
fields, _ := provs.Vals[name].(*PyDict)
reg.Providers[name] = ProviderFromFields(plainJSON(fields).(map[string]any))
reg.order = append(reg.order, name)
}
}
}
return reg, captured
}
// urlError mirrors urllib.error.URLError's str(): "<urlopen error msg>".
type urlError struct{ msg string }
func (e *urlError) Error() string { return "<urlopen error " + e.msg + ">" }
func providerFieldsMap(p *ModelProvider) map[string]any {
var apiKey any
if p.APIKey != "" {
apiKey = p.APIKey
}
var lhc any
if p.LastHealthCheck != "" {
lhc = p.LastHealthCheck
}
return map[string]any{
"name": p.Name, "url": p.URL, "provider_type": p.ProviderType,
"api_key": apiKey, "enabled": p.Enabled,
"last_health_check": lhc, "health_status": p.HealthStatus,
"models": p.Models,
}
}
func providersState(reg *ProviderRegistry) map[string]any {
out := map[string]any{}
for name, p := range reg.Providers {
out[name] = providerFieldsMap(p)
}
return out
}
func readConfigIfPresent(t *testing.T, path string) (any, bool) {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
return nil, false
}
var cfg any
json.Unmarshal(data, &cfg)
return cfg, true
}
// unwrapExpected handles both frozen shapes: top-level expected_error
// (row()) and the nested {"expected_request", "expected"/"expected_error"}
// envelope (gen_case()).
func unwrapExpected(c *PyDict) (result, request any, errText string) {
if s, ok := dget(c, "expected_error").(string); ok && s != "" {
return nil, nil, s
}
exp := dget(c, "expected")
if em, ok := exp.(*PyDict); ok {
if _, isEnvelope := em.Get("expected_request"); isEnvelope {
if s, ok := dget(em, "expected_error").(string); ok {
return nil, dget(em, "expected_request"), s
}
return dget(em, "expected"), dget(em, "expected_request"), ""
}
}
return exp, nil, ""
}
func genArgs(input *PyDict) (prompt, model string, maxTokens int, temperature float64, systemPrompt *string, tools []map[string]any) {
prompt, _ = dget(input, "prompt").(string)
model, _ = dget(input, "model").(string)
maxTokens = pyIntOr(dget(input, "max_tokens"), 500)
temperature = pyFloatOr(dget(input, "temperature"), 0.7)
if sp, ok := dget(input, "system_prompt").(string); ok {
systemPrompt = &sp
}
if toolsAny, ok := dget(input, "tools").([]any); ok {
tools = pyMaps(plainJSON(toolsAny).([]any))
}
return
}
func stripLatency(m map[string]any) map[string]any {
out := map[string]any{}
for k, v := range m {
if k != "latency_ms" {
out[k] = v
}
}
return out
}
func TestRegistryTruthParity(t *testing.T) {
raw, err := ParsePyJSON(mustRead(t, "truth_registry.json"))
if err != nil {
t.Fatalf("truth_registry.json: %v", err)
}
cases, _ := raw.([]any)
for _, caseAny := range cases {
c, _ := caseAny.(*PyDict)
fn, _ := dget(c, "fn").(string)
name, _ := dget(c, "name").(string)
input, _ := dget(c, "input").(*PyDict)
t.Run(fn+"/"+name, func(t *testing.T) {
runRegistryCase(t, fn, name, input, c)
})
}
}
func mustRead(t *testing.T, name string) []byte {
t.Helper()
b, err := os.ReadFile(name)
if err != nil {
t.Fatalf("%s missing: %v", name, err)
}
return b
}
// scriptExecutorFromExpected installs a ToolExecutorFunc whose scripted
// results are the Python tool_executor outputs parsed back out of the
// frozen tool_results content strings. Returns a restore func.
func scriptExecutorFromExpected(t *testing.T, result any) func() {
t.Helper()
var scripted []any
if resMap, ok := plainJSON(result).(map[string]any); ok {
if trs, ok := resMap["tool_results"].([]any); ok {
for _, tr := range trs {
trm, _ := tr.(map[string]any)
content, _ := trm["content"].(string)
parsed, err := ParsePyJSON([]byte(content))
if err != nil {
t.Fatalf("tool result content parse: %v", err)
}
scripted = append(scripted, parsed)
}
}
}
oldExec := ToolExecutorFunc
ToolExecutorFunc = func(name string, args map[string]any) (any, error) {
if len(scripted) == 0 {
t.Errorf("unexpected extra tool call %q", name)
return nil, nil
}
r := scripted[0]
scripted = scripted[1:]
return r, nil
}
return func() { ToolExecutorFunc = oldExec }
}
func runRegistryCase(t *testing.T, fn, name string, input *PyDict, c *PyDict) {
t.Helper()
result, request, errText := unwrapExpected(c)
switch fn {
case "to_dict":
fields, _ := plainJSON(dget(input, "provider")).(map[string]any)
p := ProviderFromFields(fields)
compareJSON(t, name, p.ToDict(), result)
case "_load_config":
path := filepath.Join(t.TempDir(), "providers.json")
if content := dget(input, "content"); content != nil {
b, _ := json.Marshal(plainJSON(content))
os.WriteFile(path, b, 0o600)
}
reg := NewRegistry(path)
compareJSON(t, name, map[string]any{"providers": providersState(reg)}, result)
case "_save_config":
reg, _ := buildRegistryFromInput(t, input)
reg.SaveConfig()
cfg, ok := readConfigIfPresent(t, reg.ConfigPath)
if !ok {
t.Fatalf("%s: config file not written", name)
}
compareJSON(t, name, map[string]any{"written": cfg}, result)
case "connect":
reg := NewRegistry(filepath.Join(t.TempDir(), "providers.json"))
p := reg.Connect(dget(input, "name").(string), dget(input, "url").(string),
dget(input, "provider_type").(string), dget(input, "api_key").(string))
got := map[string]any{"provider": p.ToDict()}
if cfg, ok := readConfigIfPresent(t, reg.ConfigPath); ok {
got["config"] = cfg
}
compareJSON(t, name, got, result)
case "list_providers":
reg, _ := buildRegistryFromInput(t, input)
compareJSON(t, name, map[string]any{"providers": reg.ListProviders()}, result)
case "get_provider":
reg, _ := buildRegistryFromInput(t, input)
p := reg.GetProvider(dget(input, "name").(string))
var pv any
if p != nil {
pv = p.ToDict()
}
compareJSON(t, name, map[string]any{"provider": pv}, result)
case "disconnect":
reg, _ := buildRegistryFromInput(t, input)
if seed := dget(input, "seed_config"); seed != nil {
b, _ := json.Marshal(plainJSON(seed))
os.WriteFile(reg.ConfigPath, b, 0o600)
}
reg.Disconnect(dget(input, "name").(string))
got := map[string]any{"providers": providersState(reg)}
if _, wantConfig := dget(c, "expected").(*PyDict).Get("config"); wantConfig {
if cfg, ok := readConfigIfPresent(t, reg.ConfigPath); ok {
got["config"] = cfg
}
}
compareJSON(t, name, got, result)
case "_clean_model_name":
cn, _ := dget(input, "name").(string)
compareJSON(t, name, CleanModelName(cn), result)
case "_fetch_ollama_models", "_fetch_openai_models", "_fetch_anthropic_models", "_fetch_models":
reg, _ := buildRegistryFromInput(t, input)
fields, _ := plainJSON(dget(input, "provider")).(map[string]any)
p := ProviderFromFields(fields)
got, err := reg.FetchModels(p)
switch fn {
case "_fetch_ollama_models":
got, err = reg.FetchOllamaModels(p)
case "_fetch_openai_models":
got, err = reg.FetchOpenaiModels(p)
case "_fetch_anthropic_models":
got, err = reg.FetchAnthropicModels(p)
}
if errText != "" {
if err == nil {
t.Errorf("%s: want error %q, got none", name, errText)
} else if err.Error() != errText {
t.Errorf("%s: error:\n got %q\nwant %q", name, err.Error(), errText)
}
return
}
if err != nil {
t.Fatalf("%s: unexpected error: %v", name, err)
}
compareJSON(t, name, got, result)
case "list_models":
reg, _ := buildRegistryFromInput(t, input)
compareJSON(t, name, reg.ListModels(), result)
case "generate":
reg, _ := buildRegistryFromInput(t, input)
prompt, _ := dget(input, "prompt").(string)
providerName, _ := dget(input, "provider_name").(string)
model, _ := dget(input, "model").(string)
got := reg.Generate(prompt, providerName, model, 500, 0.7, nil, nil)
compareJSON(t, name, got, result)
case "_generate_ollama", "_generate_openai", "_generate_anthropic":
reg, captured := buildRegistryFromInput(t, input)
fields, _ := plainJSON(dget(input, "provider")).(map[string]any)
p := ProviderFromFields(fields)
prompt, model, maxTokens, temperature, systemPrompt, tools := genArgs(input)
if result != nil {
defer scriptExecutorFromExpected(t, result)()
}
var got map[string]any
switch fn {
case "_generate_ollama":
got = reg.GenerateOllama(p, prompt, model, maxTokens, temperature)
case "_generate_openai":
got = reg.GenerateOpenai(p, prompt, model, maxTokens, temperature, systemPrompt, tools)
case "_generate_anthropic":
got = reg.GenerateAnthropic(p, prompt, model, maxTokens, temperature, systemPrompt, tools)
}
if request == nil {
if len(*captured) != 0 {
t.Errorf("%s: expected no HTTP call, captured %d", name, len(*captured))
}
} else {
if len(*captured) == 0 {
t.Fatalf("%s: expected an HTTP call, captured none", name)
}
var gotReq any
json.Unmarshal((*captured)[0].body, &gotReq)
compareJSON(t, name+"/request", gotReq, request)
}
if errText != "" {
if gotErr, ok := got["error"].(string); !ok || gotErr != errText {
t.Errorf("%s: error:\n got %v\nwant %q", name, got["error"], errText)
}
return
}
compareJSON(t, name, stripLatency(got), result)
default:
t.Errorf("unhandled frozen fn %s", fn)
}
}
+151
View File
@@ -0,0 +1,151 @@
package main
// truth_test.go — factory-gap-pr parity gate: replays the frozen
// oracle truth (truth.json, generated by scripts/
// gen_provider_registry_truth.py via the shared freeze_truth harness)
// VERBATIM against the Go port. The tool executor is the injected seam
// — its scripted results are the Python tool_executor outputs parsed
// back out of the frozen content strings, so the battery never touches
// the real executor and never hits a socket.
import (
"encoding/json"
"os"
"testing"
)
func loadTruthFile(t *testing.T, name string, dst any) {
t.Helper()
b, err := os.ReadFile(name)
if err != nil {
t.Fatalf("%s missing: %v", name, err)
}
if err := json.Unmarshal(b, dst); err != nil {
t.Fatalf("%s: %v", name, err)
}
}
func compareJSON(t *testing.T, name string, got, want any) {
t.Helper()
gb, _ := json.Marshal(got)
wb, _ := json.Marshal(plainJSON(want))
if string(gb) != string(wb) {
t.Errorf("%s:\n got %q\nwant %q", name, gb, wb)
}
}
// plainJSON converts PyDict (order-preserving decode) back to plain
// maps for order-insensitive comparison.
func plainJSON(v any) any {
switch x := v.(type) {
case *PyDict:
m := map[string]any{}
for _, k := range x.Keys {
m[k] = plainJSON(x.Vals[k])
}
return m
case []any:
out := make([]any, len(x))
for i, e := range x {
out[i] = plainJSON(e)
}
return out
case []map[string]any:
out := make([]any, len(x))
for i, e := range x {
out[i] = plainJSON(e)
}
return out
default:
return v
}
}
func TestHarnessTruthParity(t *testing.T) {
var cases []struct {
Fn string `json:"fn"`
Args []any `json:"args"`
Kwargs map[string]any `json:"kwargs"`
Expected any `json:"expected"`
}
loadTruthFile(t, "truth.json", &cases)
for _, c := range cases {
switch c.Fn {
case "_format_tools_openai":
compareJSON(t, c.Fn, FormatToolsOpenai(argsTools(c.Args)), c.Expected)
case "_format_tools_anthropic":
compareJSON(t, c.Fn, FormatToolsAnthropic(argsTools(c.Args)), c.Expected)
case "_ctx_budget":
modelID, _ := c.Args[0].(string)
compareJSON(t, c.Fn, CtxBudget(modelID), c.Expected)
case "_execute_tool_calls", "_execute_tool_calls_anthropic":
expList, _ := c.Expected.([]any)
scripted := make([]any, len(expList))
for i, e := range expList {
em, _ := e.(map[string]any)
content, _ := em["content"].(string)
parsed, err := ParsePyJSON([]byte(content))
if err != nil {
t.Fatalf("%s case %d: content parse: %v", c.Fn, i, err)
}
scripted[i] = parsed
}
idx := 0
oldExec := ToolExecutorFunc
ToolExecutorFunc = func(name string, args map[string]any) (any, error) {
if idx >= len(scripted) {
t.Errorf("%s: unexpected extra tool call %q", c.Fn, name)
return nil, nil
}
r := scripted[idx]
idx++
return r, nil
}
var got []map[string]any
if c.Fn == "_execute_tool_calls_anthropic" {
got = ExecuteToolCallsAnthropic(argsTools(c.Args))
} else {
got = ExecuteToolCalls(argsTools(c.Args))
}
ToolExecutorFunc = oldExec
if idx != len(scripted) {
t.Errorf("%s: executor invoked %d times, want %d", c.Fn, idx, len(scripted))
}
compareJSON(t, c.Fn, got, c.Expected)
default:
t.Errorf("unhandled frozen fn %s", c.Fn)
}
}
}
// argsTools unwraps the single tool-list argument of the frozen
// _format_tools_* / _execute_tool_calls* cases.
func argsTools(args []any) []map[string]any {
if len(args) == 0 {
return nil
}
tools, _ := args[0].([]any)
return pyMaps(tools)
}
// TestExecuteToolCallsNonSerializableFallback — the Go-side branch of
// the oracle's PreparedTool degrade law: a tool result with no
// JSON-shape equivalent becomes the fixed error marker.
func TestExecuteToolCallsNonSerializableFallback(t *testing.T) {
oldExec := ToolExecutorFunc
defer func() { ToolExecutorFunc = oldExec }()
ToolExecutorFunc = func(name string, args map[string]any) (any, error) {
return make(chan int), nil // never JSON-serializable
}
got := ExecuteToolCalls([]map[string]any{{
"id": "c1",
"function": map[string]any{"name": "x", "arguments": "{}"},
}})
want := `{"success": false, "error": "tool result not JSON serializable"}`
if got[0]["content"] != want {
t.Errorf("content:\n got %v\nwant %s", got[0]["content"], want)
}
if got[0]["tool_call_id"] != "c1" || got[0]["role"] != "tool" {
t.Errorf("wrapper shape wrong: %v", got[0])
}
}
+10 -2
View File
@@ -92,11 +92,15 @@ def _execute_tool_calls(tool_calls: list) -> list:
args = {}
result = tool_executor.execute_tool(name, args)
try:
content = json.dumps(result)
except (TypeError, ValueError): # noqa: BLE001 — non-JSON tool results (e.g. PreparedTool) degrade to an error marker, never crash the loop
content = json.dumps({"success": False, "error": "tool result not JSON serializable"})
results.append(
{
"tool_call_id": call.get("id", ""),
"role": "tool",
"content": json.dumps(result),
"content": content,
}
)
return results
@@ -115,11 +119,15 @@ def _execute_tool_calls_anthropic(tool_calls: list) -> list:
args = call.get("input", {})
result = tool_executor.execute_tool(name, args)
try:
content = json.dumps(result)
except (TypeError, ValueError): # noqa: BLE001 — non-JSON tool results (e.g. PreparedTool) degrade to an error marker, never crash the loop
content = json.dumps({"success": False, "error": "tool result not JSON serializable"})
results.append(
{
"type": "tool_result",
"tool_use_id": call.get("id", ""),
"content": json.dumps(result),
"content": content,
}
)
return results