feat(standardized_providers): close PARTIAL gaps — dichotomy wave
This commit is contained in:
1 parent
da8d705221
commit
649febfff7
17 files changed
+2172
-134
No files matched your search
@@ -2,14 +2,14 @@
|
||||
|
||||
_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: 56%** (107/192 runtime modules at FULL)
|
||||
**FULL completion: 56%** (108/192 runtime modules at FULL)
|
||||
|
||||
Function-level detail: 71% (1625/2284 runtime functions matched) — secondary; includes in-flight work
|
||||
Function-level detail: 71% (1627/2284 runtime functions matched) — secondary; includes in-flight work
|
||||
|
||||
| Status | Modules | Lines | Meaning |
|
||||
|---|---|---|---|
|
||||
| FULL | 107 | 45202 | 80%+ of functions ported |
|
||||
| PARTIAL | 46 | 18316 | 20–80% — real logic exists, gaps named below |
|
||||
| FULL | 108 | 45483 | 80%+ of functions ported |
|
||||
| PARTIAL | 45 | 18035 | 20–80% — real logic exists, gaps named below |
|
||||
| NOT TOUCHED | 39 | 11471 | no name-preserving implementation |
|
||||
| Total runtime | 192 | 74989 | |
|
||||
|
||||
@@ -94,7 +94,6 @@ Function-level detail: 71% (1625/2284 runtime functions matched) — secondary;
|
||||
| event_bus | 244 | 5/7 | _store_path, handle_hook |
|
||||
| inference_trace | 204 | 4/6 | record_trace, read_traces |
|
||||
| response_adapter | 222 | 5/7 | test_adapter, to_dict |
|
||||
| standardized_providers | 281 | 3/5 | list_providers, connect_all_local |
|
||||
| structural_migration | 77 | 2/4 | apply_migration, consent_brief |
|
||||
| subconscious_pass | 236 | 3/5 | main, get_recent_findings |
|
||||
| subsystem_health | 213 | 4/6 | set_auto_filer, configure_logging |
|
||||
@@ -182,6 +181,7 @@ Function-level detail: 71% (1625/2284 runtime functions matched) — secondary;
|
||||
| message_graph | 295 | 12/13 | unclassified |
|
||||
| toolchain | 295 | 5/6 | RUST |
|
||||
| salience_gate | 283 | 6/6 | RUST |
|
||||
| standardized_providers | 281 | 5/5 | GO |
|
||||
| shard_manager | 276 | 9/9 | RUST |
|
||||
| auto_context | 273 | 8/8 | GO |
|
||||
| spine_language | 269 | 5/5 | RUST |
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env python3
|
||||
"""check_standardized_providers_parity.py — live oracle-vs-Go parity sweep
|
||||
for the standardized_providers gap closure (#2479, contract factory-gap-sp).
|
||||
|
||||
The frozen corpus (truth.json) pins the ported surface byte-identically.
|
||||
THIS script drives the LIVE Python oracle (in-process, under the
|
||||
scripts/sp_freeze_seams rig: pinned HOME + provider_registry sibling-import
|
||||
path) and the Go binary across every op family — list, get (all providers +
|
||||
None branches), connect (every gate branch and every provider),
|
||||
connect_all_local, and the CLI (usage/list/connect variants/connect-all/
|
||||
unknown, compared as EXACT stdout BYTES) — comparing sort_keys JSON; CLI
|
||||
text is compared byte-for-byte. Exit 0 on 100% parity, 1 with divergences.
|
||||
|
||||
Usage (repo root; the seams dir is mandatory — pins HOME + binds seams):
|
||||
PYTHONPATH=scripts/sp_freeze_seams:src python3 scripts/check_standardized_providers_parity.py \
|
||||
[--bin src/go/standardized-providers/standardized-providers]
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(os.path.abspath(__file__)).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
sys.path.insert(0, str(REPO / "scripts"))
|
||||
|
||||
import rune.standardized_providers as sp # noqa: E402 (rig auto-bound)
|
||||
|
||||
PROVIDERS = [
|
||||
"ollama", "vllm", "lmstudio", "xiaomi", "openai",
|
||||
"anthropic", "openrouter", "groq", "together", "fireworks",
|
||||
]
|
||||
CLOUD = ["xiaomi", "openai", "anthropic", "openrouter", "groq", "together", "fireworks"]
|
||||
KEYS = {
|
||||
"xiaomi": "mimo-key", "openai": "sk-live-parity", "anthropic": "sk-ant",
|
||||
"openrouter": "or-key", "groq": "gsk-key", "together": "tog-key",
|
||||
"fireworks": "fw-key",
|
||||
}
|
||||
|
||||
BIN_DEFAULT = REPO / "src" / "go" / "standardized-providers" / "standardized-providers"
|
||||
|
||||
|
||||
def run_go(req, binpath, env=None):
|
||||
e = dict(os.environ)
|
||||
if env:
|
||||
e.update(env)
|
||||
p = subprocess.run([str(binpath), "--op"], input=json.dumps(req),
|
||||
capture_output=True, text=True, env=e)
|
||||
if p.returncode != 0:
|
||||
raise RuntimeError(f"go op {req.get('op')} exited {p.returncode}: {p.stdout} {p.stderr}")
|
||||
return json.loads(p.stdout)
|
||||
|
||||
|
||||
def oracle_connect(name, key=...):
|
||||
"""Call the oracle connect_provider exactly like the Go op: key=None
|
||||
means no kwarg; a string key rides api_key."""
|
||||
kwargs = {} if key is ... else {"api_key": key}
|
||||
return sp.connect_provider(name, **kwargs)
|
||||
|
||||
|
||||
def oracle_cli(argv_tail):
|
||||
"""Drive the REAL main() with patched argv + captured stdout (the same
|
||||
machinery the frozen seams use)."""
|
||||
argv = ["standardized_providers.py"] + argv_tail
|
||||
import contextlib
|
||||
import io
|
||||
saved = sys.argv
|
||||
sys.argv = argv
|
||||
buf = io.StringIO()
|
||||
try:
|
||||
with contextlib.redirect_stdout(buf):
|
||||
sp.main()
|
||||
finally:
|
||||
sys.argv = saved
|
||||
return {"stdout": buf.getvalue()}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--bin", type=Path, default=BIN_DEFAULT)
|
||||
args = ap.parse_args()
|
||||
|
||||
checks = 0
|
||||
fails = 0
|
||||
|
||||
def check(label, oracle_value, go_value, exact_bytes=False):
|
||||
nonlocal checks, fails
|
||||
checks += 1
|
||||
if exact_bytes:
|
||||
ok = oracle_value == go_value
|
||||
else:
|
||||
ok = json.dumps(oracle_value, sort_keys=True) == json.dumps(go_value, sort_keys=True)
|
||||
if not ok:
|
||||
fails += 1
|
||||
print(f"FAIL {label}:\n oracle: {oracle_value!r}\n go: {go_value!r}")
|
||||
|
||||
# ── list ──
|
||||
check("list", sp.list_providers(), run_go({"op": "list"}, args.bin))
|
||||
|
||||
# ── get (all + None branches, dataclass projection) ──
|
||||
for name in PROVIDERS + ["ghost-provider", ""]:
|
||||
want = sp.get_provider_asdict(name)
|
||||
got = run_go({"op": "get", "name": name}, args.bin)
|
||||
check(f"get:{name!r}", want, got)
|
||||
|
||||
# ── connect (gates + every provider) ──
|
||||
for name in ["ghost-provider", "unknown-server-1"]:
|
||||
check(f"connect:{name}", oracle_connect(name),
|
||||
run_go({"op": "connect", "name": name}, args.bin))
|
||||
for name in CLOUD:
|
||||
check(f"connect:{name}:nokey", oracle_connect(name),
|
||||
run_go({"op": "connect", "name": name}, args.bin))
|
||||
check("connect:openai:empty-key", oracle_connect("openai", ""),
|
||||
run_go({"op": "connect", "name": "openai", "key": ""}, args.bin))
|
||||
for name in ["ollama", "vllm", "lmstudio"]:
|
||||
check(f"connect:{name}", oracle_connect(name),
|
||||
run_go({"op": "connect", "name": name}, args.bin))
|
||||
for name in CLOUD:
|
||||
check(f"connect:{name}:key", oracle_connect(name, KEYS[name]),
|
||||
run_go({"op": "connect", "name": name, "key": KEYS[name]}, args.bin))
|
||||
|
||||
# ── connect_all_local ──
|
||||
check("connect_all", sp.connect_all_local(),
|
||||
run_go({"op": "connect_all"}, args.bin))
|
||||
|
||||
# ── CLI (EXACT stdout bytes, both sides run the real main()) ──
|
||||
cli_cases = [
|
||||
([], "usage"),
|
||||
(["list"], "list"),
|
||||
(["connect"], "connect-noargs"),
|
||||
(["connect", "openai", "sk-cli"], "connect-key-ok"),
|
||||
(["connect", "ghost-provider"], "connect-unknown"),
|
||||
(["connect", "ollama"], "connect-local"),
|
||||
(["connect", "xiaomi"], "connect-nokey"),
|
||||
(["connect-all"], "connect-all"),
|
||||
(["frobnicate"], "unknown-command"),
|
||||
]
|
||||
for argv_tail, label in cli_cases:
|
||||
want = oracle_cli(argv_tail)
|
||||
got = run_go({"op": "cli", "args": argv_tail}, args.bin)
|
||||
check(f"cli:{label}", want, got, exact_bytes=True)
|
||||
# the binary ALSO exposes the CLI surface via argv — byte-compare
|
||||
# that path too (the bare script default mode).
|
||||
p = subprocess.run([str(args.bin)] + argv_tail, capture_output=True, text=True)
|
||||
got_direct = {"stdout": p.stdout}
|
||||
checks += 1
|
||||
if want != got_direct:
|
||||
fails += 1
|
||||
print(f"FAIL cli-argv:{label}:\n oracle: {want!r}\n go: {got_direct!r}")
|
||||
|
||||
print(f"standardized-providers live parity: {checks - fails}/{checks} "
|
||||
f"({args.bin.name})")
|
||||
if fails:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_standardized_providers_truth.py — freeze the standardized_providers
|
||||
gap-close truth corpus via the shared freeze_truth.py harness (#2479 wave).
|
||||
|
||||
Covers the full ported surface, exercising every branch:
|
||||
* list_providers — the full 10-provider projection (insertion-order
|
||||
dicts, the description-added/models-dropped key order)
|
||||
* get_provider (via get_provider_asdict seam) — every provider + the
|
||||
None branch (unknown, empty name)
|
||||
* connect_provider — unknown names, key-required WITHOUT key (all six
|
||||
cloud providers), an explicitly EMPTY-string key (falsy like None),
|
||||
every local provider, every cloud provider WITH a key; the
|
||||
error-as-data partial-return contract (error dicts are RETURNED,
|
||||
never raised) and the success envelope (registry-backed fresh
|
||||
connect: empty models, enabled, health_status unknown)
|
||||
* connect_all_local — the 3 locals, rows in insertion order, counts
|
||||
* main() CLI (via sys.argv-patched stdout-capture seams) — usage,
|
||||
list, connect usage line, connect success/failure/unknown variants,
|
||||
connect-all, unknown command; the CLI text IS the ported contract
|
||||
(byte-identical, emoji included)
|
||||
|
||||
No clock sites in this module — legacy list truth format (no frozen_now
|
||||
envelope). connect_all_local and the CLI rows run mid-sequence with a
|
||||
warm registry singleton; their results are registry-state-independent
|
||||
(fresh connect overwrites the same name), verified byte-stable by
|
||||
--verify in a fresh process.
|
||||
|
||||
Usage (from the repo root; the seams dir is mandatory — it pins HOME and
|
||||
binds the seams):
|
||||
PYTHONPATH=scripts/sp_freeze_seams:src python3 scripts/gen_standardized_providers_truth.py
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(os.path.abspath(__file__)).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
sys.path.insert(0, str(REPO / "scripts"))
|
||||
|
||||
import freeze_truth # noqa: E402
|
||||
|
||||
TRUTH_PATH = REPO / "src" / "go" / "standardized-providers" / "truth.json"
|
||||
|
||||
PROVIDERS = [
|
||||
"ollama", "vllm", "lmstudio", "xiaomi", "openai",
|
||||
"anthropic", "openrouter", "groq", "together", "fireworks",
|
||||
]
|
||||
CLOUD = [
|
||||
"xiaomi", "openai", "anthropic", "openrouter", "groq", "together",
|
||||
"fireworks",
|
||||
]
|
||||
KEYS = {
|
||||
"xiaomi": "mimo-key", "openai": "sk-test-123", "anthropic": "sk-ant-1",
|
||||
"openrouter": "or-key", "groq": "gsk-key", "together": "tog-key",
|
||||
"fireworks": "fw-key",
|
||||
}
|
||||
|
||||
cases = []
|
||||
|
||||
# list_providers: the whole table, insertion order
|
||||
cases.append(("list_providers", (), {}))
|
||||
|
||||
# get_provider via the asdict seam: every provider + the None branches
|
||||
for name in PROVIDERS + ["ghost-provider", ""]:
|
||||
cases.append(("get_provider_asdict", (name,), {}))
|
||||
|
||||
# connect_provider: error-as-data branches
|
||||
cases.append(("connect_provider", ("ghost-provider",), {}))
|
||||
cases.append(("connect_provider", ("unknown-server-1",), {}))
|
||||
for name in CLOUD:
|
||||
cases.append(("connect_provider", (name,), {}))
|
||||
cases.append(("connect_provider", ("openai",), {"api_key": ""}))
|
||||
# connect_provider: locals (no key needed) + clouds WITH a key
|
||||
for name in ["ollama", "vllm", "lmstudio"]:
|
||||
cases.append(("connect_provider", (name,), {}))
|
||||
for name in CLOUD:
|
||||
cases.append(("connect_provider", (name,), {"api_key": KEYS[name]}))
|
||||
|
||||
# connect_all_local: mid-sequence (warm registry), rows independent
|
||||
cases.append(("connect_all_local", (), {}))
|
||||
|
||||
# the CLI contract (patched-argv stdout capture of the REAL main())
|
||||
cases.append(("main_usage", (), {}))
|
||||
cases.append(("main_list", (), {}))
|
||||
cases.append(("main_connect_noargs", (), {}))
|
||||
cases.append(("main_connect", ("openai", "sk-cli"), {}))
|
||||
cases.append(("main_connect", ("ghost-provider", None), {}))
|
||||
cases.append(("main_connect", ("ollama", None), {}))
|
||||
cases.append(("main_connect", ("xiaomi", None), {}))
|
||||
cases.append(("main_connect_all", (), {}))
|
||||
cases.append(("main_unknown", ("frobnicate",), {}))
|
||||
|
||||
truth = freeze_truth.freeze(
|
||||
"standardized_providers", cases, oracle_module="rune.standardized_providers",
|
||||
)
|
||||
freeze_truth.write(truth, TRUTH_PATH)
|
||||
print(f"frozen: {len(truth)} cases -> {TRUTH_PATH}")
|
||||
@@ -0,0 +1,106 @@
|
||||
# Freeze rig for the standardized_providers dichotomy gap-closing
|
||||
# (#2479 wave, factory-gap-sp). Auto-imported on interpreter start
|
||||
# (PYTHONPATH=scripts/sp_freeze_seams:src) by BOTH the truth generator
|
||||
# (scripts/gen_standardized_providers_truth.py), the shared harness's
|
||||
# --verify mode, and the live-parity sweep, so frozen truth is
|
||||
# byte-reproducible in any process:
|
||||
#
|
||||
# * HOME is pinned to a fresh throwaway dir (freeze_rigs.pin_home) —
|
||||
# connect_provider reaches provider_registry.get_registry() whose
|
||||
# ProviderRegistry derives ~/.config/rune/providers.json via
|
||||
# expanduser at construction and WRITES it on every connect; hermetic
|
||||
# HOME keeps the writes off the real ~/.config and makes every row
|
||||
# deterministic.
|
||||
# * sys.path carries src/ (the rune package) AND src/rune/ — the bare
|
||||
# `from provider_registry import get_registry` inside
|
||||
# connect_provider (Python-2-style sibling import, alive at call
|
||||
# time) resolves only when the package dir itself is importable.
|
||||
# * The clock needs no rig here: standardized_providers has no clock
|
||||
# sites (legacy list truth format, no frozen_now envelope).
|
||||
#
|
||||
# Frozen seams (bound onto the oracle module):
|
||||
# get_provider_asdict — rides the REAL get_provider; the dataclass
|
||||
# result is projected via dataclasses.asdict
|
||||
# (raw dataclass is not JSON-safe; asdict
|
||||
# preserves declaration order).
|
||||
# main_usage / main_list / main_connect_noargs / main_connect /
|
||||
# main_connect_all / main_unknown — run the REAL main() with a
|
||||
# patched sys.argv under redirect_stdout and
|
||||
# return {"stdout": text}; the CLI text IS the
|
||||
# ported contract (byte-identical, emoji incl).
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from dataclasses import asdict
|
||||
|
||||
_REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
sys.path.insert(0, os.path.join(_REPO, "scripts"))
|
||||
sys.path.insert(0, os.path.join(_REPO, "src"))
|
||||
sys.path.insert(0, os.path.join(_REPO, "src", "rune"))
|
||||
|
||||
from freeze_rigs import pin_home # noqa: E402
|
||||
|
||||
pin_home("sp")
|
||||
|
||||
import rune.standardized_providers as _sp # noqa: E402 (after the HOME pin)
|
||||
|
||||
|
||||
def get_provider_asdict(name):
|
||||
"""The real get_provider, JSON-safed via asdict (None stays None)."""
|
||||
p = _sp.get_provider(name)
|
||||
return asdict(p) if p is not None else None
|
||||
|
||||
|
||||
_SP_SCRIPT = "standardized_providers.py"
|
||||
|
||||
|
||||
def _run_main(argv_tail):
|
||||
"""Call the REAL main() with sys.argv patched to the CLI tail and
|
||||
stdout captured verbatim."""
|
||||
saved_argv = sys.argv
|
||||
sys.argv = [_SP_SCRIPT] + list(argv_tail)
|
||||
buf = io.StringIO()
|
||||
try:
|
||||
with contextlib.redirect_stdout(buf):
|
||||
_sp.main()
|
||||
finally:
|
||||
sys.argv = saved_argv
|
||||
return {"stdout": buf.getvalue()}
|
||||
|
||||
|
||||
def main_usage():
|
||||
return _run_main([])
|
||||
|
||||
|
||||
def main_list():
|
||||
return _run_main(["list"])
|
||||
|
||||
|
||||
def main_connect_noargs():
|
||||
return _run_main(["connect"])
|
||||
|
||||
|
||||
def main_connect(name, key=None):
|
||||
argv = ["connect", name]
|
||||
if key is not None:
|
||||
argv.append(key)
|
||||
return _run_main(argv)
|
||||
|
||||
|
||||
def main_connect_all():
|
||||
return _run_main(["connect-all"])
|
||||
|
||||
|
||||
def main_unknown(cmd):
|
||||
return _run_main([cmd])
|
||||
|
||||
|
||||
_sp.get_provider_asdict = get_provider_asdict
|
||||
_sp.main_usage = main_usage
|
||||
_sp.main_list = main_list
|
||||
_sp.main_connect_noargs = main_connect_noargs
|
||||
_sp.main_connect = main_connect
|
||||
_sp.main_connect_all = main_connect_all
|
||||
_sp.main_unknown = main_unknown
|
||||
@@ -1,33 +1,247 @@
|
||||
package main
|
||||
|
||||
// connect_test.go — batch 171: connect-provider gate parity.
|
||||
// connect_test.go — the parity gate for the standardized_providers
|
||||
// gap-close (#2479 dichotomy wave). Replays every case of truth.json
|
||||
// (the freeze_truth.py shared-harness corpus, generated against the
|
||||
// live Python oracle under scripts/sp_freeze_seams) VERBATIM through
|
||||
// the ported Go functions, plus quirk unit tests fixing the two oracle
|
||||
// traps: insertion-ordered JSON envelopes (Go maps would sort keys) and
|
||||
// connect_provider's error-as-data partial-return contract.
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
//go:embed truth.json
|
||||
var frozenTruthJSON string
|
||||
|
||||
// TestFrozenTruthReplay — the frozen oracle truth, verbatim, in the
|
||||
// recorded call order, against the ported Go functions.
|
||||
func TestFrozenTruthReplay(t *testing.T) {
|
||||
var cases []frozenCase
|
||||
if err := json.Unmarshal([]byte(frozenTruthJSON), &cases); err != nil {
|
||||
t.Fatalf("decode truth corpus: %v", err)
|
||||
}
|
||||
if len(cases) == 0 {
|
||||
t.Fatal("no truth cases — the parity gate is empty")
|
||||
}
|
||||
for i, c := range cases {
|
||||
actual, err := replayCase(c)
|
||||
if err != nil {
|
||||
t.Fatalf("case %d fn=%s: %v", i, c.Fn, err)
|
||||
}
|
||||
got, err := canonical(actual)
|
||||
if err != nil {
|
||||
t.Fatalf("case %d fn=%s: canonical actual: %v", i, c.Fn, err)
|
||||
}
|
||||
var want interface{}
|
||||
if err := json.Unmarshal(c.Expected, &want); err != nil {
|
||||
t.Fatalf("case %d fn=%s: decode expected: %v", i, c.Fn, err)
|
||||
}
|
||||
gj, _ := json.Marshal(got)
|
||||
wj, _ := json.Marshal(want)
|
||||
if string(gj) != string(wj) {
|
||||
t.Errorf("case %d fn=%s: got %s want %s", i, c.Fn, gj, wj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestListProvidersOrderedJSON — Go json.Marshal sorts map keys, so the
|
||||
// envelopes are structs with oracle insertion-order fields. Assert the
|
||||
// BYTE rendering exactly.
|
||||
func TestListProvidersOrderedJSON(t *testing.T) {
|
||||
list := ListProviders()
|
||||
if len(list) != 10 {
|
||||
t.Fatalf("provider count: %d want 10", len(list))
|
||||
}
|
||||
gotOllama, _ := json.Marshal(list[0])
|
||||
wantOllama := `{"name":"ollama","display_name":"Ollama (Local)","provider_type":"ollama","url":"http://localhost:11434","description":"Local model server. Run models on your own hardware.","requires_api_key":false,"tier":"local","cost":"free","strengths":["privacy","no-cost","offline","customizable"],"weaknesses":["requires-gpu","slower-than-cloud","limited-models"]}`
|
||||
if string(gotOllama) != wantOllama {
|
||||
t.Errorf("ordered list projection:\n got %s\nwant %s", gotOllama, wantOllama)
|
||||
}
|
||||
gotXiaomi, _ := json.Marshal(list[3])
|
||||
wantXiaomi := `{"name":"xiaomi","display_name":"Xiaomi MiMo","provider_type":"openai","url":"https://api.xiaomimimo.com/v1","description":"Xiaomi's MiMo models. Strong reasoning and coding.","requires_api_key":true,"tier":"cloud","cost":"paid","strengths":["reasoning","coding","multilingual","long-context"],"weaknesses":["requires-api-key","latency","cost"]}`
|
||||
if string(gotXiaomi) != wantXiaomi {
|
||||
t.Errorf("ordered list projection (requires_api_key=true):\n got %s\nwant %s", gotXiaomi, wantXiaomi)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetProviderDataclassOrder — get_provider projects the DATACLASS:
|
||||
// description after url, models BETWEEN requires_api_key and tier
|
||||
// (declaration order) — the second distinct key order in the oracle
|
||||
// surface (the list projection drops models).
|
||||
func TestGetProviderDataclassOrder(t *testing.T) {
|
||||
got, _ := json.Marshal(GetProviderDataclass("ollama"))
|
||||
want := `{"name":"ollama","display_name":"Ollama (Local)","provider_type":"ollama","url":"http://localhost:11434","description":"Local model server. Run models on your own hardware.","requires_api_key":false,"models":[],"tier":"local","cost":"free","strengths":["privacy","no-cost","offline","customizable"],"weaknesses":["requires-gpu","slower-than-cloud","limited-models"]}`
|
||||
if string(got) != want {
|
||||
t.Errorf("dataclass projection:\n got %s\nwant %s", got, want)
|
||||
}
|
||||
if GetProviderDataclass("ghost-provider") != nil {
|
||||
t.Errorf("unknown name must project to nil like oracle None")
|
||||
}
|
||||
}
|
||||
|
||||
// TestConnectErrorEnvelopesAsData — the partial-return trap: both
|
||||
// failure branches RETURN single-key error dicts instead of raising;
|
||||
// callers coerce. Port mirrors that as data, not a panic.
|
||||
func TestConnectErrorEnvelopesAsData(t *testing.T) {
|
||||
gotUnknown, _ := json.Marshal(ConnectProvider("ghost-provider", ""))
|
||||
wantUnknown := `{"error":"Unknown provider: ghost-provider"}`
|
||||
if string(gotUnknown) != wantUnknown {
|
||||
t.Errorf("unknown envelope:\n got %s\nwant %s", gotUnknown, wantUnknown)
|
||||
}
|
||||
gotNoKey, _ := json.Marshal(ConnectProvider("openai", ""))
|
||||
wantNoKey := `{"error":"Provider openai requires an API key"}`
|
||||
if string(gotNoKey) != wantNoKey {
|
||||
t.Errorf("no-key envelope:\n got %s\nwant %s", gotNoKey, wantNoKey)
|
||||
}
|
||||
// an explicitly EMPTY api_key string is falsy in the oracle too
|
||||
if r := ConnectProvider("xiaomi", ""); r.(*connectError) == nil {
|
||||
t.Errorf("empty-string key must hit the error branch, got %v", r)
|
||||
}
|
||||
gotOK, _ := json.Marshal(ConnectProvider("ollama", ""))
|
||||
wantOK := `{"success":true,"provider":{"name":"ollama","url":"http://localhost:11434","provider_type":"ollama","models":[],"enabled":true,"health_status":"unknown"},"message":"Connected to Ollama (Local)"}`
|
||||
if string(gotOK) != wantOK {
|
||||
t.Errorf("success envelope:\n got %s\nwant %s", gotOK, wantOK)
|
||||
}
|
||||
}
|
||||
|
||||
// TestConnectAllLocalCoercionAndOrder — rows accumulate in
|
||||
// STANDARD_PROVIDERS insertion order; an error result coerces into a
|
||||
// failure row (message = error text) and the loop CONTINUES; counts
|
||||
// come from the coerced rows.
|
||||
func TestConnectAllLocalCoercionAndOrder(t *testing.T) {
|
||||
// coercion of both envelope kinds through localRowFrom
|
||||
errRow := localRowFrom("openai", "OpenAI", ConnectProvider("openai", ""))
|
||||
if errRow.Success || errRow.Message != "Provider openai requires an API key" {
|
||||
t.Errorf("error coercion: %+v", errRow)
|
||||
}
|
||||
okRow := localRowFrom("ollama", "Ollama (Local)", ConnectProvider("ollama", ""))
|
||||
if !okRow.Success || okRow.Message != "Connected to Ollama (Local)" {
|
||||
t.Errorf("success coercion: %+v", okRow)
|
||||
}
|
||||
unknownRow := localRowFrom("nope", "Nope", ConnectProvider("nope", ""))
|
||||
if unknownRow.Success || unknownRow.Message != "Unknown provider: nope" {
|
||||
t.Errorf("unknown coercion: %+v", unknownRow)
|
||||
}
|
||||
// the live loop: only the three local providers, in insertion order
|
||||
got, _ := json.Marshal(ConnectAllLocal())
|
||||
want := `{"connected":3,"failed":0,"results":[` +
|
||||
`{"name":"ollama","display_name":"Ollama (Local)","success":true,"message":"Connected to Ollama (Local)"},` +
|
||||
`{"name":"vllm","display_name":"vLLM (Local)","success":true,"message":"Connected to vLLM (Local)"},` +
|
||||
`{"name":"lmstudio","display_name":"LM Studio (Local)","success":true,"message":"Connected to LM Studio (Local)"}]}`
|
||||
if string(got) != want {
|
||||
t.Errorf("connect-all summary:\n got %s\nwant %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCLITextParity — runCLI must render the EXACT oracle stdout bytes
|
||||
// (captured live under a pinned HOME at port time). Emoji, indent
|
||||
// widths, the first-three strengths slice, trailing newlines.
|
||||
func TestCLITextParity(t *testing.T) {
|
||||
usage := "Usage:\n" +
|
||||
" standardized_providers.py list # List available providers\n" +
|
||||
" standardized_providers.py connect <name> [key] # Connect to a provider\n" +
|
||||
" standardized_providers.py connect-all # Connect to all local providers\n"
|
||||
if got := runCLI(nil); got != usage {
|
||||
t.Errorf("usage:\n got %q\nwant %q", got, usage)
|
||||
}
|
||||
wantList := "Available providers: 10\n\n" +
|
||||
" 🔓 Ollama (Local) (local, free)\n" +
|
||||
" Local model server. Run models on your own hardware.\n" +
|
||||
" Strengths: privacy, no-cost, offline\n\n" +
|
||||
" 🔓 vLLM (Local) (local, free)\n" +
|
||||
" High-performance local model server with OpenAI-compatible API.\n" +
|
||||
" Strengths: fast-inference, batching, openai-compatible\n\n" +
|
||||
" 🔓 LM Studio (Local) (local, free)\n" +
|
||||
" Desktop app for running local models with OpenAI-compatible API.\n" +
|
||||
" Strengths: easy-setup, gui, model-discovery\n\n" +
|
||||
" 🔑 Xiaomi MiMo (cloud, paid)\n" +
|
||||
" Xiaomi's MiMo models. Strong reasoning and coding.\n" +
|
||||
" Strengths: reasoning, coding, multilingual\n\n" +
|
||||
" 🔑 OpenAI (cloud, paid)\n" +
|
||||
" OpenAI's GPT models. Industry standard.\n" +
|
||||
" Strengths: reasoning, coding, creative\n\n" +
|
||||
" 🔑 Anthropic Claude (cloud, paid)\n" +
|
||||
" Anthropic's Claude models. Strong reasoning and safety.\n" +
|
||||
" Strengths: reasoning, safety, long-context\n\n" +
|
||||
" 🔑 OpenRouter (cloud, freemium)\n" +
|
||||
" Multi-provider gateway. Access many models with one API key.\n" +
|
||||
" Strengths: multi-provider, model-variety, fallback\n\n" +
|
||||
" 🔑 Groq (cloud, freemium)\n" +
|
||||
" Ultra-fast inference on Groq hardware.\n" +
|
||||
" Strengths: fast-inference, low-latency, openai-compatible\n\n" +
|
||||
" 🔑 Together AI (cloud, freemium)\n" +
|
||||
" Cloud inference for open-source models.\n" +
|
||||
" Strengths: open-source-models, fast, openai-compatible\n\n" +
|
||||
" 🔑 Fireworks AI (cloud, freemium)\n" +
|
||||
" Fast inference for open-source models.\n" +
|
||||
" Strengths: fast-inference, open-source, openai-compatible\n\n"
|
||||
if got := runCLI([]string{"list"}); got != wantList {
|
||||
t.Errorf("list:\n got %q\nwant %q", got, wantList)
|
||||
}
|
||||
wantConnectNoargs := "Usage: standardized_providers.py connect <name> [api_key]\n"
|
||||
if got := runCLI([]string{"connect"}); got != wantConnectNoargs {
|
||||
t.Errorf("connect-noargs:\n got %q\nwant %q", got, wantConnectNoargs)
|
||||
}
|
||||
for _, tc := range []struct{ argv, want []string }{
|
||||
{[]string{"connect", "ghost-provider"}, []string{"✗ Unknown provider: ghost-provider\n"}},
|
||||
{[]string{"connect", "openai"}, []string{"✗ Provider openai requires an API key\n"}},
|
||||
{[]string{"connect", "ollama"}, []string{"✓ Connected to Ollama (Local)\n"}},
|
||||
{[]string{"connect", "openai", "sk-test"}, []string{"✓ Connected to OpenAI\n"}},
|
||||
{[]string{"connect-all"}, []string{"Connected: 3, Failed: 0\n" +
|
||||
" ✓ Ollama (Local): Connected to Ollama (Local)\n" +
|
||||
" ✓ vLLM (Local): Connected to vLLM (Local)\n" +
|
||||
" ✓ LM Studio (Local): Connected to LM Studio (Local)\n"}},
|
||||
{[]string{"frobnicate"}, []string{"Unknown command: frobnicate\n"}},
|
||||
} {
|
||||
if got := runCLI(tc.argv); got != tc.want[0] {
|
||||
t.Errorf("cli %v:\n got %q\nwant %q", tc.argv, got, tc.want[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestConnectDecisionParity — the batch-171 decision table, still true
|
||||
// after the deep port (kept name for the record).
|
||||
func TestConnectDecisionParity(t *testing.T) {
|
||||
// unknown provider
|
||||
if r := connectDecision("nope", ""); r["error"] != "Unknown provider: nope" {
|
||||
if r := ConnectProvider("nope", ""); r.(*connectError).Error != "Unknown provider: nope" {
|
||||
t.Errorf("unknown: %v", r)
|
||||
}
|
||||
// key-required without key
|
||||
if r := connectDecision("openai", ""); r["error"] != "Provider openai requires an API key" {
|
||||
if r := ConnectProvider("openai", ""); r.(*connectError).Error != "Provider openai requires an API key" {
|
||||
t.Errorf("openai no key: %v", r)
|
||||
}
|
||||
if r := connectDecision("xiaomi", ""); r["error"] == "" {
|
||||
if r := ConnectProvider("xiaomi", ""); r.(*connectError) == nil {
|
||||
t.Errorf("xiaomi no key: %v", r)
|
||||
}
|
||||
// key-required with key -> success
|
||||
if r := connectDecision("openai", "sk-1"); r["success"] != true {
|
||||
if r := ConnectProvider("openai", "sk-1"); r.(*connectSuccess) == nil {
|
||||
t.Errorf("openai with key: %v", r)
|
||||
}
|
||||
// local providers need no key
|
||||
for _, n := range []string{"ollama", "vllm", "lmstudio"} {
|
||||
if r := connectDecision(n, ""); r["success"] != true {
|
||||
if r := ConnectProvider(n, ""); r.(*connectSuccess) == nil {
|
||||
t.Errorf("%s no key: %v", n, r)
|
||||
}
|
||||
}
|
||||
// name list parity
|
||||
if len(providerNames) != 10 {
|
||||
t.Errorf("provider count: %d want 10", len(providerNames))
|
||||
if len(standardProviders) != 10 {
|
||||
t.Errorf("provider count: %d want 10", len(standardProviders))
|
||||
}
|
||||
}
|
||||
|
||||
// TestProviderTableInsertionOrder — the table slice mirrors the oracle
|
||||
// dict literal order; list/get/connect-all all ride it.
|
||||
func TestProviderTableInsertionOrder(t *testing.T) {
|
||||
want := "ollama,vllm,lmstudio,xiaomi,openai,anthropic,openrouter,groq,together,fireworks"
|
||||
names := []string{}
|
||||
for _, p := range standardProviders {
|
||||
names = append(names, p.Name)
|
||||
}
|
||||
if strings.Join(names, ",") != want {
|
||||
t.Errorf("insertion order: got %s want %s", strings.Join(names, ","), want)
|
||||
}
|
||||
if !reflect.DeepEqual(ListProviders()[0].Description, standardProviders[0].Description) {
|
||||
t.Errorf("projection must ride the table verbatim")
|
||||
}
|
||||
}
|
||||
@@ -1,109 +1,580 @@
|
||||
// Package main — standardized-providers: the GO port of
|
||||
// src/rune/standardized_providers.py's list/get registry
|
||||
// (Oracle-Battery Parity Pattern, #2479 GO batch 62). The HTTP connect
|
||||
// calls stay Python (reactive surface).
|
||||
// src/rune/standardized_providers.py, the standardized Ollama/OpenAI/
|
||||
// Anthropic connect surfaces (#2479 dichotomy gap-close, parity port).
|
||||
// The provider-registry file persistence and HTTP model fetches stay
|
||||
// Python (reactive surface); the provider table, connect gates, result
|
||||
// envelopes, and CLI text are name-preserving, byte-parity ports.
|
||||
//
|
||||
// Oracle quirks preserved (docs/dichotomy-oracle-quirks.json):
|
||||
// - json.dumps preserves INSERTION order; Go json.Marshal sorts map
|
||||
// keys — every envelope is a STRUCT whose field order matches the
|
||||
// oracle's dict insertion order, never a map.
|
||||
// - STANDARD_PROVIDERS iteration order (list_providers /
|
||||
// connect_all_local / CLI list) is dict insertion order, part of the
|
||||
// contract — the port iterates a fixed slice, not a map.
|
||||
// - connect_provider RETURNS error dicts instead of raising (partial-
|
||||
// return trap): callers coerce {"error": ...} into failure rows /
|
||||
// ✗ lines rather than crash. The port mirrors that as data, not a
|
||||
// panic.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var providerNames = []string{
|
||||
"ollama", "vllm", "lmstudio", "xiaomi", "openai", "anthropic",
|
||||
"openrouter", "groq", "together", "fireworks",
|
||||
// StandardProvider mirrors the oracle dataclass declaration order:
|
||||
// name, display_name, provider_type, url, description, requires_api_key,
|
||||
// models, tier, cost, strengths, weaknesses.
|
||||
type StandardProvider struct {
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"display_name"`
|
||||
ProviderType string `json:"provider_type"`
|
||||
URL string `json:"url"`
|
||||
Description string `json:"description"`
|
||||
RequiresAPIKey bool `json:"requires_api_key"`
|
||||
Models []string `json:"models"`
|
||||
Tier string `json:"tier"`
|
||||
Cost string `json:"cost"`
|
||||
Strengths []string `json:"strengths"`
|
||||
Weaknesses []string `json:"weaknesses"`
|
||||
}
|
||||
|
||||
func providerSet() map[string]bool {
|
||||
m := map[string]bool{}
|
||||
for _, n := range providerNames {
|
||||
m[n] = true
|
||||
// standardProviders — the oracle's STANDARD_PROVIDERS in dict-literal
|
||||
// insertion order; iteration order is part of the ported contract.
|
||||
var standardProviders = []*StandardProvider{
|
||||
{Name: "ollama", DisplayName: "Ollama (Local)", ProviderType: "ollama", URL: "http://localhost:11434",
|
||||
Description: "Local model server. Run models on your own hardware.",
|
||||
RequiresAPIKey: false,
|
||||
Models: []string{},
|
||||
Tier: "local", Cost: "free",
|
||||
Strengths: []string{"privacy", "no-cost", "offline", "customizable"},
|
||||
Weaknesses: []string{"requires-gpu", "slower-than-cloud", "limited-models"}},
|
||||
{Name: "vllm", DisplayName: "vLLM (Local)", ProviderType: "openai", URL: "http://localhost:8000",
|
||||
Description: "High-performance local model server with OpenAI-compatible API.",
|
||||
RequiresAPIKey: false,
|
||||
Models: []string{},
|
||||
Tier: "local", Cost: "free",
|
||||
Strengths: []string{"fast-inference", "batching", "openai-compatible"},
|
||||
Weaknesses: []string{"requires-gpu", "complex-setup"}},
|
||||
{Name: "lmstudio", DisplayName: "LM Studio (Local)", ProviderType: "openai", URL: "http://localhost:1234",
|
||||
Description: "Desktop app for running local models with OpenAI-compatible API.",
|
||||
RequiresAPIKey: false,
|
||||
Models: []string{},
|
||||
Tier: "local", Cost: "free",
|
||||
Strengths: []string{"easy-setup", "gui", "model-discovery"},
|
||||
Weaknesses: []string{"desktop-only", "limited-api"}},
|
||||
{Name: "xiaomi", DisplayName: "Xiaomi MiMo", ProviderType: "openai", URL: "https://api.xiaomimimo.com/v1",
|
||||
Description: "Xiaomi's MiMo models. Strong reasoning and coding.",
|
||||
RequiresAPIKey: true,
|
||||
Models: []string{},
|
||||
Tier: "cloud", Cost: "paid",
|
||||
Strengths: []string{"reasoning", "coding", "multilingual", "long-context"},
|
||||
Weaknesses: []string{"requires-api-key", "latency", "cost"}},
|
||||
{Name: "openai", DisplayName: "OpenAI", ProviderType: "openai", URL: "https://api.openai.com/v1",
|
||||
Description: "OpenAI's GPT models. Industry standard.",
|
||||
RequiresAPIKey: true,
|
||||
Models: []string{},
|
||||
Tier: "cloud", Cost: "paid",
|
||||
Strengths: []string{"reasoning", "coding", "creative", "tool-use"},
|
||||
Weaknesses: []string{"requires-api-key", "expensive", "privacy-concerns"}},
|
||||
{Name: "anthropic", DisplayName: "Anthropic Claude", ProviderType: "anthropic", URL: "https://api.anthropic.com",
|
||||
Description: "Anthropic's Claude models. Strong reasoning and safety.",
|
||||
RequiresAPIKey: true,
|
||||
Models: []string{},
|
||||
Tier: "cloud", Cost: "paid",
|
||||
Strengths: []string{"reasoning", "safety", "long-context", "analysis"},
|
||||
Weaknesses: []string{"requires-api-key", "expensive", "no-streaming"}},
|
||||
{Name: "openrouter", DisplayName: "OpenRouter", ProviderType: "openai", URL: "https://openrouter.ai/api/v1",
|
||||
Description: "Multi-provider gateway. Access many models with one API key.",
|
||||
RequiresAPIKey: true,
|
||||
Models: []string{},
|
||||
Tier: "cloud", Cost: "freemium",
|
||||
Strengths: []string{"multi-provider", "model-variety", "fallback"},
|
||||
Weaknesses: []string{"requires-api-key", "latency", "complexity"}},
|
||||
{Name: "groq", DisplayName: "Groq", ProviderType: "openai", URL: "https://api.groq.com/openai/v1",
|
||||
Description: "Ultra-fast inference on Groq hardware.",
|
||||
RequiresAPIKey: true,
|
||||
Models: []string{},
|
||||
Tier: "cloud", Cost: "freemium",
|
||||
Strengths: []string{"fast-inference", "low-latency", "openai-compatible"},
|
||||
Weaknesses: []string{"requires-api-key", "limited-models", "no-streaming"}},
|
||||
{Name: "together", DisplayName: "Together AI", ProviderType: "openai", URL: "https://api.together.xyz/v1",
|
||||
Description: "Cloud inference for open-source models.",
|
||||
RequiresAPIKey: true,
|
||||
Models: []string{},
|
||||
Tier: "cloud", Cost: "freemium",
|
||||
Strengths: []string{"open-source-models", "fast", "openai-compatible"},
|
||||
Weaknesses: []string{"requires-api-key", "limited-features"}},
|
||||
{Name: "fireworks", DisplayName: "Fireworks AI", ProviderType: "openai", URL: "https://api.fireworks.ai/inference/v1",
|
||||
Description: "Fast inference for open-source models.",
|
||||
RequiresAPIKey: true,
|
||||
Models: []string{},
|
||||
Tier: "cloud", Cost: "freemium",
|
||||
Strengths: []string{"fast-inference", "open-source", "openai-compatible"},
|
||||
Weaknesses: []string{"requires-api-key", "limited-models"}},
|
||||
}
|
||||
|
||||
// providerDict mirrors list_providers' projection dict — key order as
|
||||
// written in the oracle list comprehension. Identical to the dataclass
|
||||
// EXCEPT models is DROPPED (the comprehension simply omits the field).
|
||||
type providerDict struct {
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"display_name"`
|
||||
ProviderType string `json:"provider_type"`
|
||||
URL string `json:"url"`
|
||||
Description string `json:"description"`
|
||||
RequiresAPIKey bool `json:"requires_api_key"`
|
||||
Tier string `json:"tier"`
|
||||
Cost string `json:"cost"`
|
||||
Strengths []string `json:"strengths"`
|
||||
Weaknesses []string `json:"weaknesses"`
|
||||
}
|
||||
|
||||
// ListProviders — the ported list_providers: every provider as a dict,
|
||||
// in STANDARD_PROVIDERS insertion order.
|
||||
func ListProviders() []providerDict {
|
||||
out := make([]providerDict, 0, len(standardProviders))
|
||||
for _, p := range standardProviders {
|
||||
out = append(out, providerDict{
|
||||
Name: p.Name,
|
||||
DisplayName: p.DisplayName,
|
||||
ProviderType: p.ProviderType,
|
||||
URL: p.URL,
|
||||
Description: p.Description,
|
||||
RequiresAPIKey: p.RequiresAPIKey,
|
||||
Tier: p.Tier,
|
||||
Cost: p.Cost,
|
||||
Strengths: p.Strengths,
|
||||
Weaknesses: p.Weaknesses,
|
||||
})
|
||||
}
|
||||
return m
|
||||
return out
|
||||
}
|
||||
|
||||
// getProvider — Python-identical: the provider name or nil.
|
||||
func getProvider(name string) *string {
|
||||
if providerSet()[name] {
|
||||
n := name
|
||||
return &n
|
||||
// GetProvider — the ported get_provider: the StandardProvider dataclass
|
||||
// or nil (STANDARD_PROVIDERS.get).
|
||||
func GetProvider(name string) *StandardProvider {
|
||||
for _, p := range standardProviders {
|
||||
if p.Name == name {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// providerRequiresKey — the requires_api_key table (batch 171).
|
||||
var providerRequiresKey = map[string]bool{
|
||||
"anthropic": true, "fireworks": true, "groq": true, "lmstudio": false,
|
||||
"ollama": false, "openai": true, "openrouter": true, "together": true,
|
||||
"vllm": false, "xiaomi": true,
|
||||
// providerDataclass mirrors the StandardProvider DATACLASS projection
|
||||
// (dataclasses.asdict field order): description after url, models AFTER
|
||||
// requires_api_key, then tier/cost/strengths/weaknesses.
|
||||
type providerDataclass struct {
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"display_name"`
|
||||
ProviderType string `json:"provider_type"`
|
||||
URL string `json:"url"`
|
||||
Description string `json:"description"`
|
||||
RequiresAPIKey bool `json:"requires_api_key"`
|
||||
Models []string `json:"models"`
|
||||
Tier string `json:"tier"`
|
||||
Cost string `json:"cost"`
|
||||
Strengths []string `json:"strengths"`
|
||||
Weaknesses []string `json:"weaknesses"`
|
||||
}
|
||||
|
||||
// connectDecision — the pure gate of connect_provider (HTTP stays Python):
|
||||
// unknown name -> error; key-required without key -> error; else connect.
|
||||
func connectDecision(name, apiKey string) map[string]any {
|
||||
if !providerSet()[name] {
|
||||
return map[string]any{"error": "Unknown provider: " + name}
|
||||
// GetProviderDataclass — the asdict projection of get_provider (the
|
||||
// frozen-truth seam rides the real GetProvider; nil stays nil like the
|
||||
// oracle's None).
|
||||
func GetProviderDataclass(name string) *providerDataclass {
|
||||
p := GetProvider(name)
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
if providerRequiresKey[name] && apiKey == "" {
|
||||
return map[string]any{"error": "Provider " + name + " requires an API key"}
|
||||
if p.Models == nil {
|
||||
p.Models = []string{}
|
||||
}
|
||||
return &providerDataclass{
|
||||
Name: p.Name,
|
||||
DisplayName: p.DisplayName,
|
||||
ProviderType: p.ProviderType,
|
||||
URL: p.URL,
|
||||
Description: p.Description,
|
||||
RequiresAPIKey: p.RequiresAPIKey,
|
||||
Models: p.Models,
|
||||
Tier: p.Tier,
|
||||
Cost: p.Cost,
|
||||
Strengths: p.Strengths,
|
||||
Weaknesses: p.Weaknesses,
|
||||
}
|
||||
return map[string]any{"success": true, "message": "Connected to " + name}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 || os.Args[1] != "replay" {
|
||||
fmt.Fprintln(os.Stderr, "usage: standardized-providers replay <truth.json>")
|
||||
os.Exit(2)
|
||||
// connectedProvider mirrors provider_registry.ModelProvider.to_dict key
|
||||
// order (name, url, provider_type, models, enabled, health_status) — the
|
||||
// fresh-connect shape: empty models, enabled, never health-checked.
|
||||
type connectedProvider struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
ProviderType string `json:"provider_type"`
|
||||
Models []interface{} `json:"models"`
|
||||
Enabled bool `json:"enabled"`
|
||||
HealthStatus string `json:"health_status"`
|
||||
}
|
||||
|
||||
// connectSuccess mirrors connect_provider's success envelope insertion
|
||||
// order (success, provider, message).
|
||||
type connectSuccess struct {
|
||||
Success bool `json:"success"`
|
||||
Provider connectedProvider `json:"provider"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// connectError mirrors the single-key error dict both failure branches
|
||||
// return (returned as DATA — the partial-return trap: callers coerce,
|
||||
// nothing raises).
|
||||
type connectError struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// ConnectProvider — the ported connect_provider gate. The registry
|
||||
// write-through (providers.json persistence) stays Python (reactive
|
||||
// surface); the returned envelope shape is the ported contract: unknown
|
||||
// name → {"error": "Unknown provider: <name>"}; key-required without a
|
||||
// usable key → {"error": "Provider <name> requires an API key"}; else
|
||||
// the fresh-connect success envelope with
|
||||
// "Connected to <display_name>".
|
||||
func ConnectProvider(name, apiKey string) interface{} {
|
||||
p := GetProvider(name)
|
||||
if p == nil {
|
||||
return &connectError{Error: "Unknown provider: " + name}
|
||||
}
|
||||
raw, err := os.ReadFile(os.Args[2])
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
if p.RequiresAPIKey && apiKey == "" {
|
||||
return &connectError{Error: "Provider " + name + " requires an API key"}
|
||||
}
|
||||
var cases []map[string]any
|
||||
if err := json.Unmarshal(raw, &cases); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
return &connectSuccess{
|
||||
Success: true,
|
||||
Provider: connectedProvider{
|
||||
Name: p.Name,
|
||||
URL: p.URL,
|
||||
ProviderType: p.ProviderType,
|
||||
Models: []interface{}{},
|
||||
Enabled: true,
|
||||
HealthStatus: "unknown",
|
||||
},
|
||||
Message: "Connected to " + p.DisplayName,
|
||||
}
|
||||
fails := 0
|
||||
for i, c := range cases {
|
||||
op, _ := c["op"].(string)
|
||||
switch op {
|
||||
case "list":
|
||||
wantLen := int(c["want_len"].(float64))
|
||||
if len(providerNames) != wantLen {
|
||||
fails++
|
||||
fmt.Printf("case %d list: len %d want %d\n", i, len(providerNames), wantLen)
|
||||
break
|
||||
}
|
||||
want := c["want_names"].([]any)
|
||||
for j, w := range want {
|
||||
if providerNames[j] != w.(string) {
|
||||
fails++
|
||||
fmt.Printf("case %d list[%d]: got %q want %q\n", i, j, providerNames[j], w.(string))
|
||||
}
|
||||
}
|
||||
case "get":
|
||||
name, _ := c["name"].(string)
|
||||
want, hasWant := c["want"].(string)
|
||||
got := getProvider(name)
|
||||
if hasWant {
|
||||
if got == nil || *got != want {
|
||||
fails++
|
||||
fmt.Printf("case %d get %q: divergence\n", i, name)
|
||||
}
|
||||
} else {
|
||||
if got != nil {
|
||||
fails++
|
||||
fmt.Printf("case %d get %q: got %q want nil\n", i, name, *got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// localRow mirrors connect_all_local's per-provider row (key order:
|
||||
// name, display_name, success, message). Failure rows carry the error
|
||||
// TEXT as the message — result.get("message", result.get("error",
|
||||
// "Unknown")).
|
||||
type localRow struct {
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// localRowFrom — the coercion of a connect_provider result (success OR
|
||||
// error envelope) into a row; error-as-data, never a raise.
|
||||
func localRowFrom(name, displayName string, result interface{}) localRow {
|
||||
switch r := result.(type) {
|
||||
case *connectSuccess:
|
||||
return localRow{Name: name, DisplayName: displayName, Success: r.Success, Message: r.Message}
|
||||
case *connectError:
|
||||
return localRow{Name: name, DisplayName: displayName, Success: false, Message: r.Error}
|
||||
}
|
||||
return localRow{Name: name, DisplayName: displayName, Success: false, Message: "Unknown"}
|
||||
}
|
||||
|
||||
// localSummary mirrors connect_all_local's return dict (key order:
|
||||
// connected, failed, results).
|
||||
type localSummary struct {
|
||||
Connected int `json:"connected"`
|
||||
Failed int `json:"failed"`
|
||||
Results []localRow `json:"results"`
|
||||
}
|
||||
|
||||
// ConnectAllLocal — the ported connect_all_local: iterate
|
||||
// STANDARD_PROVIDERS in insertion order, connect every provider that
|
||||
// needs no API key, and ACCUMULATE the rows (the partial-return trap:
|
||||
// a failure row is coerced and the loop continues, never aborts nor
|
||||
// raises).
|
||||
func ConnectAllLocal() localSummary {
|
||||
rows := []localRow{}
|
||||
for _, p := range standardProviders {
|
||||
if p.RequiresAPIKey {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, localRowFrom(p.Name, p.DisplayName, ConnectProvider(p.Name, "")))
|
||||
}
|
||||
connected := 0
|
||||
for _, r := range rows {
|
||||
if r.Success {
|
||||
connected++
|
||||
}
|
||||
}
|
||||
if fails > 0 {
|
||||
fmt.Fprintf(os.Stderr, "PARITY FAILED: %d divergence(s)\n", fails)
|
||||
os.Exit(1)
|
||||
return localSummary{
|
||||
Connected: connected,
|
||||
Failed: len(rows) - connected,
|
||||
Results: rows,
|
||||
}
|
||||
fmt.Printf("PARITY OK: %d cases vs the Python oracle\n", len(cases))
|
||||
}
|
||||
|
||||
const cliScript = "standardized_providers.py"
|
||||
|
||||
// runCLI — the ported main(): builds the EXACT stdout text of the
|
||||
// oracle CLI (bytes identical: emoji, two/five-space indents, the
|
||||
// first-three strengths slice). argv mirrors sys.argv[1:].
|
||||
func runCLI(argv []string) string {
|
||||
var b strings.Builder
|
||||
if len(argv) < 1 {
|
||||
fmt.Fprintf(&b, "Usage:\n")
|
||||
fmt.Fprintf(&b, " %s list # List available providers\n", cliScript)
|
||||
fmt.Fprintf(&b, " %s connect <name> [key] # Connect to a provider\n", cliScript)
|
||||
fmt.Fprintf(&b, " %s connect-all # Connect to all local providers\n", cliScript)
|
||||
return b.String()
|
||||
}
|
||||
command := argv[0]
|
||||
|
||||
switch command {
|
||||
case "list":
|
||||
providers := ListProviders()
|
||||
fmt.Fprintf(&b, "Available providers: %d\n\n", len(providers))
|
||||
for _, p := range providers {
|
||||
keyReq := "🔓"
|
||||
if p.RequiresAPIKey {
|
||||
keyReq = "🔑"
|
||||
}
|
||||
strengths := p.Strengths
|
||||
if len(strengths) > 3 {
|
||||
strengths = strengths[:3]
|
||||
}
|
||||
fmt.Fprintf(&b, " %s %s (%s, %s)\n", keyReq, p.DisplayName, p.Tier, p.Cost)
|
||||
fmt.Fprintf(&b, " %s\n", p.Description)
|
||||
fmt.Fprintf(&b, " Strengths: %s\n\n", strings.Join(strengths, ", "))
|
||||
}
|
||||
|
||||
case "connect":
|
||||
if len(argv) < 2 {
|
||||
fmt.Fprintf(&b, "Usage: %s connect <name> [api_key]\n", cliScript)
|
||||
return b.String()
|
||||
}
|
||||
name := argv[1]
|
||||
apiKey := ""
|
||||
if len(argv) > 2 {
|
||||
apiKey = argv[2]
|
||||
}
|
||||
result := ConnectProvider(name, apiKey)
|
||||
if r, ok := result.(*connectSuccess); ok {
|
||||
fmt.Fprintf(&b, "✓ %s\n", r.Message)
|
||||
} else {
|
||||
fmt.Fprintf(&b, "✗ %s\n", cliConnectErrorText(result))
|
||||
}
|
||||
|
||||
case "connect-all":
|
||||
summary := ConnectAllLocal()
|
||||
fmt.Fprintf(&b, "Connected: %d, Failed: %d\n", summary.Connected, summary.Failed)
|
||||
for _, r := range summary.Results {
|
||||
status := "✗"
|
||||
if r.Success {
|
||||
status = "✓"
|
||||
}
|
||||
fmt.Fprintf(&b, " %s %s: %s\n", status, r.DisplayName, r.Message)
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Fprintf(&b, "Unknown command: %s\n", command)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// cliConnectErrorText — result.get('error', 'Unknown error').
|
||||
func cliConnectErrorText(result interface{}) string {
|
||||
if r, ok := result.(*connectError); ok {
|
||||
return r.Error
|
||||
}
|
||||
return "Unknown error"
|
||||
}
|
||||
|
||||
// ── replay (frozen truth) + parity (stdin op) + CLI entrypoints ────────
|
||||
|
||||
// frozenCase mirrors a freeze_truth.py shared-harness truth row.
|
||||
type frozenCase struct {
|
||||
Fn string `json:"fn"`
|
||||
Args []interface{} `json:"args"`
|
||||
Kwargs map[string]interface{} `json:"kwargs"`
|
||||
Expected json.RawMessage `json:"expected"`
|
||||
}
|
||||
|
||||
// canonical — round-trip a value through JSON so both sides compare as
|
||||
// decoded data (Python None and Go nil decode to the same null; the
|
||||
// battery's ordered-bytes gate lives in the quirk unit tests).
|
||||
func canonical(v interface{}) (interface{}, error) {
|
||||
raw, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out interface{}
|
||||
if err := json.Unmarshal(raw, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// replayCase — dispatch one frozen truth row through the ported fns.
|
||||
func replayCase(c frozenCase) (interface{}, error) {
|
||||
argString := func(i int) string {
|
||||
if i < len(c.Args) {
|
||||
if s, ok := c.Args[i].(string); ok {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
switch c.Fn {
|
||||
case "list_providers":
|
||||
return ListProviders(), nil
|
||||
case "get_provider_asdict":
|
||||
return GetProviderDataclass(argString(0)), nil
|
||||
case "connect_provider":
|
||||
apiKey := ""
|
||||
if v, ok := c.Kwargs["api_key"]; ok && v != nil {
|
||||
apiKey = v.(string)
|
||||
}
|
||||
return ConnectProvider(argString(0), apiKey), nil
|
||||
case "connect_all_local":
|
||||
return ConnectAllLocal(), nil
|
||||
case "main_usage":
|
||||
return map[string]interface{}{"stdout": runCLI(nil)}, nil
|
||||
case "main_list":
|
||||
return map[string]interface{}{"stdout": runCLI([]string{"list"})}, nil
|
||||
case "main_connect_noargs":
|
||||
return map[string]interface{}{"stdout": runCLI([]string{"connect"})}, nil
|
||||
case "main_connect":
|
||||
name := argString(0)
|
||||
key := ""
|
||||
withKey := len(c.Args) > 1 && c.Args[1] != nil
|
||||
if withKey {
|
||||
key = c.Args[1].(string)
|
||||
}
|
||||
argv := []string{"connect", name}
|
||||
if withKey {
|
||||
argv = append(argv, key)
|
||||
}
|
||||
return map[string]interface{}{"stdout": runCLI(argv)}, nil
|
||||
case "main_connect_all":
|
||||
return map[string]interface{}{"stdout": runCLI([]string{"connect-all"})}, nil
|
||||
case "main_unknown":
|
||||
return map[string]interface{}{"stdout": runCLI([]string{argString(0)})}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown frozen fn %q", c.Fn)
|
||||
}
|
||||
|
||||
func replayMain() int {
|
||||
path := "truth.json"
|
||||
if len(os.Args) > 2 {
|
||||
path = os.Args[2]
|
||||
}
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "FATAL:", err)
|
||||
return 1
|
||||
}
|
||||
var cases []frozenCase
|
||||
if err := json.Unmarshal(raw, &cases); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "FATAL:", err)
|
||||
return 1
|
||||
}
|
||||
if len(cases) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "FATAL: no truth cases")
|
||||
return 1
|
||||
}
|
||||
fails := 0
|
||||
for _, c := range cases {
|
||||
actual, err := replayCase(c)
|
||||
if err != nil {
|
||||
fails++
|
||||
fmt.Printf("FAIL %s: %v\n", c.Fn, err)
|
||||
continue
|
||||
}
|
||||
got, err := canonical(actual)
|
||||
if err != nil {
|
||||
fails++
|
||||
fmt.Printf("FAIL %s: canonical actual: %v\n", c.Fn, err)
|
||||
continue
|
||||
}
|
||||
var want interface{}
|
||||
if err := json.Unmarshal(c.Expected, &want); err != nil {
|
||||
fails++
|
||||
fmt.Printf("FAIL %s: decode expected: %v\n", c.Fn, err)
|
||||
continue
|
||||
}
|
||||
gj, _ := json.Marshal(got)
|
||||
wj, _ := json.Marshal(want)
|
||||
if string(gj) != string(wj) {
|
||||
fails++
|
||||
fmt.Printf("FAIL %s: got %s want %s\n", c.Fn, gj, wj)
|
||||
}
|
||||
}
|
||||
fmt.Printf("standardized-providers parity: %d/%d passed\n", len(cases)-fails, len(cases))
|
||||
if fails > 0 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func parityMain() int {
|
||||
var req struct {
|
||||
Op string `json:"op"`
|
||||
Name string `json:"name"`
|
||||
Key string `json:"key"`
|
||||
Args []string `json:"args"`
|
||||
}
|
||||
body, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "FATAL:", err)
|
||||
return 1
|
||||
}
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "FATAL:", err)
|
||||
return 1
|
||||
}
|
||||
var result interface{}
|
||||
switch req.Op {
|
||||
case "list":
|
||||
result = ListProviders()
|
||||
case "get":
|
||||
result = GetProviderDataclass(req.Name)
|
||||
case "connect":
|
||||
result = ConnectProvider(req.Name, req.Key)
|
||||
case "connect_all":
|
||||
result = ConnectAllLocal()
|
||||
case "cli":
|
||||
result = map[string]interface{}{"stdout": runCLI(req.Args)}
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, "FATAL: unknown op", req.Op)
|
||||
return 1
|
||||
}
|
||||
out, _ := json.Marshal(result)
|
||||
fmt.Println(string(out))
|
||||
return 0
|
||||
}
|
||||
|
||||
// main — three modes, mirroring the oracle's CLI by default:
|
||||
// - <binary> replay [truth.json] → frozen-truth replay gate
|
||||
// - <binary> --op → stdin op dispatch (live parity)
|
||||
// - <binary> list|connect|... → the oracle CLI itself (usage when
|
||||
// invoked with no args, exactly like the bare script)
|
||||
func main() {
|
||||
if len(os.Args) > 1 {
|
||||
switch os.Args[1] {
|
||||
case "replay":
|
||||
os.Exit(replayMain())
|
||||
case "--op":
|
||||
os.Exit(parityMain())
|
||||
}
|
||||
}
|
||||
fmt.Print(runCLI(os.Args[1:]))
|
||||
}
|
||||
@@ -1,38 +1,926 @@
|
||||
[
|
||||
{
|
||||
"op": "list",
|
||||
"want_names": [
|
||||
"ollama",
|
||||
"vllm",
|
||||
"lmstudio",
|
||||
"xiaomi",
|
||||
"openai",
|
||||
"anthropic",
|
||||
"openrouter",
|
||||
"groq",
|
||||
"together",
|
||||
"fireworks"
|
||||
],
|
||||
"want_len": 10
|
||||
},
|
||||
{
|
||||
"op": "get",
|
||||
"name": "ollama",
|
||||
"want": "ollama"
|
||||
},
|
||||
{
|
||||
"op": "get",
|
||||
"name": "openai",
|
||||
"want": "openai"
|
||||
},
|
||||
{
|
||||
"op": "get",
|
||||
"name": "anthropic",
|
||||
"want": "anthropic"
|
||||
},
|
||||
{
|
||||
"op": "get",
|
||||
"name": "nonexistent-provider",
|
||||
"want": null
|
||||
}
|
||||
]
|
||||
{
|
||||
"fn": "list_providers",
|
||||
"args": [],
|
||||
"kwargs": {},
|
||||
"expected": [
|
||||
{
|
||||
"name": "ollama",
|
||||
"display_name": "Ollama (Local)",
|
||||
"provider_type": "ollama",
|
||||
"url": "http://localhost:11434",
|
||||
"description": "Local model server. Run models on your own hardware.",
|
||||
"requires_api_key": false,
|
||||
"tier": "local",
|
||||
"cost": "free",
|
||||
"strengths": [
|
||||
"privacy",
|
||||
"no-cost",
|
||||
"offline",
|
||||
"customizable"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-gpu",
|
||||
"slower-than-cloud",
|
||||
"limited-models"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "vllm",
|
||||
"display_name": "vLLM (Local)",
|
||||
"provider_type": "openai",
|
||||
"url": "http://localhost:8000",
|
||||
"description": "High-performance local model server with OpenAI-compatible API.",
|
||||
"requires_api_key": false,
|
||||
"tier": "local",
|
||||
"cost": "free",
|
||||
"strengths": [
|
||||
"fast-inference",
|
||||
"batching",
|
||||
"openai-compatible"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-gpu",
|
||||
"complex-setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "lmstudio",
|
||||
"display_name": "LM Studio (Local)",
|
||||
"provider_type": "openai",
|
||||
"url": "http://localhost:1234",
|
||||
"description": "Desktop app for running local models with OpenAI-compatible API.",
|
||||
"requires_api_key": false,
|
||||
"tier": "local",
|
||||
"cost": "free",
|
||||
"strengths": [
|
||||
"easy-setup",
|
||||
"gui",
|
||||
"model-discovery"
|
||||
],
|
||||
"weaknesses": [
|
||||
"desktop-only",
|
||||
"limited-api"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "xiaomi",
|
||||
"display_name": "Xiaomi MiMo",
|
||||
"provider_type": "openai",
|
||||
"url": "https://api.xiaomimimo.com/v1",
|
||||
"description": "Xiaomi's MiMo models. Strong reasoning and coding.",
|
||||
"requires_api_key": true,
|
||||
"tier": "cloud",
|
||||
"cost": "paid",
|
||||
"strengths": [
|
||||
"reasoning",
|
||||
"coding",
|
||||
"multilingual",
|
||||
"long-context"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-api-key",
|
||||
"latency",
|
||||
"cost"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "openai",
|
||||
"display_name": "OpenAI",
|
||||
"provider_type": "openai",
|
||||
"url": "https://api.openai.com/v1",
|
||||
"description": "OpenAI's GPT models. Industry standard.",
|
||||
"requires_api_key": true,
|
||||
"tier": "cloud",
|
||||
"cost": "paid",
|
||||
"strengths": [
|
||||
"reasoning",
|
||||
"coding",
|
||||
"creative",
|
||||
"tool-use"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-api-key",
|
||||
"expensive",
|
||||
"privacy-concerns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "anthropic",
|
||||
"display_name": "Anthropic Claude",
|
||||
"provider_type": "anthropic",
|
||||
"url": "https://api.anthropic.com",
|
||||
"description": "Anthropic's Claude models. Strong reasoning and safety.",
|
||||
"requires_api_key": true,
|
||||
"tier": "cloud",
|
||||
"cost": "paid",
|
||||
"strengths": [
|
||||
"reasoning",
|
||||
"safety",
|
||||
"long-context",
|
||||
"analysis"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-api-key",
|
||||
"expensive",
|
||||
"no-streaming"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "openrouter",
|
||||
"display_name": "OpenRouter",
|
||||
"provider_type": "openai",
|
||||
"url": "https://openrouter.ai/api/v1",
|
||||
"description": "Multi-provider gateway. Access many models with one API key.",
|
||||
"requires_api_key": true,
|
||||
"tier": "cloud",
|
||||
"cost": "freemium",
|
||||
"strengths": [
|
||||
"multi-provider",
|
||||
"model-variety",
|
||||
"fallback"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-api-key",
|
||||
"latency",
|
||||
"complexity"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "groq",
|
||||
"display_name": "Groq",
|
||||
"provider_type": "openai",
|
||||
"url": "https://api.groq.com/openai/v1",
|
||||
"description": "Ultra-fast inference on Groq hardware.",
|
||||
"requires_api_key": true,
|
||||
"tier": "cloud",
|
||||
"cost": "freemium",
|
||||
"strengths": [
|
||||
"fast-inference",
|
||||
"low-latency",
|
||||
"openai-compatible"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-api-key",
|
||||
"limited-models",
|
||||
"no-streaming"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "together",
|
||||
"display_name": "Together AI",
|
||||
"provider_type": "openai",
|
||||
"url": "https://api.together.xyz/v1",
|
||||
"description": "Cloud inference for open-source models.",
|
||||
"requires_api_key": true,
|
||||
"tier": "cloud",
|
||||
"cost": "freemium",
|
||||
"strengths": [
|
||||
"open-source-models",
|
||||
"fast",
|
||||
"openai-compatible"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-api-key",
|
||||
"limited-features"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "fireworks",
|
||||
"display_name": "Fireworks AI",
|
||||
"provider_type": "openai",
|
||||
"url": "https://api.fireworks.ai/inference/v1",
|
||||
"description": "Fast inference for open-source models.",
|
||||
"requires_api_key": true,
|
||||
"tier": "cloud",
|
||||
"cost": "freemium",
|
||||
"strengths": [
|
||||
"fast-inference",
|
||||
"open-source",
|
||||
"openai-compatible"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-api-key",
|
||||
"limited-models"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"fn": "get_provider_asdict",
|
||||
"args": [
|
||||
"ollama"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"name": "ollama",
|
||||
"display_name": "Ollama (Local)",
|
||||
"provider_type": "ollama",
|
||||
"url": "http://localhost:11434",
|
||||
"description": "Local model server. Run models on your own hardware.",
|
||||
"requires_api_key": false,
|
||||
"models": [],
|
||||
"tier": "local",
|
||||
"cost": "free",
|
||||
"strengths": [
|
||||
"privacy",
|
||||
"no-cost",
|
||||
"offline",
|
||||
"customizable"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-gpu",
|
||||
"slower-than-cloud",
|
||||
"limited-models"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "get_provider_asdict",
|
||||
"args": [
|
||||
"vllm"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"name": "vllm",
|
||||
"display_name": "vLLM (Local)",
|
||||
"provider_type": "openai",
|
||||
"url": "http://localhost:8000",
|
||||
"description": "High-performance local model server with OpenAI-compatible API.",
|
||||
"requires_api_key": false,
|
||||
"models": [],
|
||||
"tier": "local",
|
||||
"cost": "free",
|
||||
"strengths": [
|
||||
"fast-inference",
|
||||
"batching",
|
||||
"openai-compatible"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-gpu",
|
||||
"complex-setup"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "get_provider_asdict",
|
||||
"args": [
|
||||
"lmstudio"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"name": "lmstudio",
|
||||
"display_name": "LM Studio (Local)",
|
||||
"provider_type": "openai",
|
||||
"url": "http://localhost:1234",
|
||||
"description": "Desktop app for running local models with OpenAI-compatible API.",
|
||||
"requires_api_key": false,
|
||||
"models": [],
|
||||
"tier": "local",
|
||||
"cost": "free",
|
||||
"strengths": [
|
||||
"easy-setup",
|
||||
"gui",
|
||||
"model-discovery"
|
||||
],
|
||||
"weaknesses": [
|
||||
"desktop-only",
|
||||
"limited-api"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "get_provider_asdict",
|
||||
"args": [
|
||||
"xiaomi"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"name": "xiaomi",
|
||||
"display_name": "Xiaomi MiMo",
|
||||
"provider_type": "openai",
|
||||
"url": "https://api.xiaomimimo.com/v1",
|
||||
"description": "Xiaomi's MiMo models. Strong reasoning and coding.",
|
||||
"requires_api_key": true,
|
||||
"models": [],
|
||||
"tier": "cloud",
|
||||
"cost": "paid",
|
||||
"strengths": [
|
||||
"reasoning",
|
||||
"coding",
|
||||
"multilingual",
|
||||
"long-context"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-api-key",
|
||||
"latency",
|
||||
"cost"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "get_provider_asdict",
|
||||
"args": [
|
||||
"openai"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"name": "openai",
|
||||
"display_name": "OpenAI",
|
||||
"provider_type": "openai",
|
||||
"url": "https://api.openai.com/v1",
|
||||
"description": "OpenAI's GPT models. Industry standard.",
|
||||
"requires_api_key": true,
|
||||
"models": [],
|
||||
"tier": "cloud",
|
||||
"cost": "paid",
|
||||
"strengths": [
|
||||
"reasoning",
|
||||
"coding",
|
||||
"creative",
|
||||
"tool-use"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-api-key",
|
||||
"expensive",
|
||||
"privacy-concerns"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "get_provider_asdict",
|
||||
"args": [
|
||||
"anthropic"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"name": "anthropic",
|
||||
"display_name": "Anthropic Claude",
|
||||
"provider_type": "anthropic",
|
||||
"url": "https://api.anthropic.com",
|
||||
"description": "Anthropic's Claude models. Strong reasoning and safety.",
|
||||
"requires_api_key": true,
|
||||
"models": [],
|
||||
"tier": "cloud",
|
||||
"cost": "paid",
|
||||
"strengths": [
|
||||
"reasoning",
|
||||
"safety",
|
||||
"long-context",
|
||||
"analysis"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-api-key",
|
||||
"expensive",
|
||||
"no-streaming"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "get_provider_asdict",
|
||||
"args": [
|
||||
"openrouter"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"name": "openrouter",
|
||||
"display_name": "OpenRouter",
|
||||
"provider_type": "openai",
|
||||
"url": "https://openrouter.ai/api/v1",
|
||||
"description": "Multi-provider gateway. Access many models with one API key.",
|
||||
"requires_api_key": true,
|
||||
"models": [],
|
||||
"tier": "cloud",
|
||||
"cost": "freemium",
|
||||
"strengths": [
|
||||
"multi-provider",
|
||||
"model-variety",
|
||||
"fallback"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-api-key",
|
||||
"latency",
|
||||
"complexity"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "get_provider_asdict",
|
||||
"args": [
|
||||
"groq"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"name": "groq",
|
||||
"display_name": "Groq",
|
||||
"provider_type": "openai",
|
||||
"url": "https://api.groq.com/openai/v1",
|
||||
"description": "Ultra-fast inference on Groq hardware.",
|
||||
"requires_api_key": true,
|
||||
"models": [],
|
||||
"tier": "cloud",
|
||||
"cost": "freemium",
|
||||
"strengths": [
|
||||
"fast-inference",
|
||||
"low-latency",
|
||||
"openai-compatible"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-api-key",
|
||||
"limited-models",
|
||||
"no-streaming"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "get_provider_asdict",
|
||||
"args": [
|
||||
"together"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"name": "together",
|
||||
"display_name": "Together AI",
|
||||
"provider_type": "openai",
|
||||
"url": "https://api.together.xyz/v1",
|
||||
"description": "Cloud inference for open-source models.",
|
||||
"requires_api_key": true,
|
||||
"models": [],
|
||||
"tier": "cloud",
|
||||
"cost": "freemium",
|
||||
"strengths": [
|
||||
"open-source-models",
|
||||
"fast",
|
||||
"openai-compatible"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-api-key",
|
||||
"limited-features"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "get_provider_asdict",
|
||||
"args": [
|
||||
"fireworks"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"name": "fireworks",
|
||||
"display_name": "Fireworks AI",
|
||||
"provider_type": "openai",
|
||||
"url": "https://api.fireworks.ai/inference/v1",
|
||||
"description": "Fast inference for open-source models.",
|
||||
"requires_api_key": true,
|
||||
"models": [],
|
||||
"tier": "cloud",
|
||||
"cost": "freemium",
|
||||
"strengths": [
|
||||
"fast-inference",
|
||||
"open-source",
|
||||
"openai-compatible"
|
||||
],
|
||||
"weaknesses": [
|
||||
"requires-api-key",
|
||||
"limited-models"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "get_provider_asdict",
|
||||
"args": [
|
||||
"ghost-provider"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": null
|
||||
},
|
||||
{
|
||||
"fn": "get_provider_asdict",
|
||||
"args": [
|
||||
""
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": null
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"ghost-provider"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"error": "Unknown provider: ghost-provider"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"unknown-server-1"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"error": "Unknown provider: unknown-server-1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"xiaomi"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"error": "Provider xiaomi requires an API key"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"openai"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"error": "Provider openai requires an API key"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"anthropic"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"error": "Provider anthropic requires an API key"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"openrouter"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"error": "Provider openrouter requires an API key"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"groq"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"error": "Provider groq requires an API key"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"together"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"error": "Provider together requires an API key"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"fireworks"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"error": "Provider fireworks requires an API key"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"openai"
|
||||
],
|
||||
"kwargs": {
|
||||
"api_key": ""
|
||||
},
|
||||
"expected": {
|
||||
"error": "Provider openai requires an API key"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"ollama"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"success": true,
|
||||
"provider": {
|
||||
"name": "ollama",
|
||||
"url": "http://localhost:11434",
|
||||
"provider_type": "ollama",
|
||||
"models": [],
|
||||
"enabled": true,
|
||||
"health_status": "unknown"
|
||||
},
|
||||
"message": "Connected to Ollama (Local)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"vllm"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"success": true,
|
||||
"provider": {
|
||||
"name": "vllm",
|
||||
"url": "http://localhost:8000",
|
||||
"provider_type": "openai",
|
||||
"models": [],
|
||||
"enabled": true,
|
||||
"health_status": "unknown"
|
||||
},
|
||||
"message": "Connected to vLLM (Local)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"lmstudio"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"success": true,
|
||||
"provider": {
|
||||
"name": "lmstudio",
|
||||
"url": "http://localhost:1234",
|
||||
"provider_type": "openai",
|
||||
"models": [],
|
||||
"enabled": true,
|
||||
"health_status": "unknown"
|
||||
},
|
||||
"message": "Connected to LM Studio (Local)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"xiaomi"
|
||||
],
|
||||
"kwargs": {
|
||||
"api_key": "mimo-key"
|
||||
},
|
||||
"expected": {
|
||||
"success": true,
|
||||
"provider": {
|
||||
"name": "xiaomi",
|
||||
"url": "https://api.xiaomimimo.com/v1",
|
||||
"provider_type": "openai",
|
||||
"models": [],
|
||||
"enabled": true,
|
||||
"health_status": "unknown"
|
||||
},
|
||||
"message": "Connected to Xiaomi MiMo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"openai"
|
||||
],
|
||||
"kwargs": {
|
||||
"api_key": "sk-test-123"
|
||||
},
|
||||
"expected": {
|
||||
"success": true,
|
||||
"provider": {
|
||||
"name": "openai",
|
||||
"url": "https://api.openai.com/v1",
|
||||
"provider_type": "openai",
|
||||
"models": [],
|
||||
"enabled": true,
|
||||
"health_status": "unknown"
|
||||
},
|
||||
"message": "Connected to OpenAI"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"anthropic"
|
||||
],
|
||||
"kwargs": {
|
||||
"api_key": "sk-ant-1"
|
||||
},
|
||||
"expected": {
|
||||
"success": true,
|
||||
"provider": {
|
||||
"name": "anthropic",
|
||||
"url": "https://api.anthropic.com",
|
||||
"provider_type": "anthropic",
|
||||
"models": [],
|
||||
"enabled": true,
|
||||
"health_status": "unknown"
|
||||
},
|
||||
"message": "Connected to Anthropic Claude"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"openrouter"
|
||||
],
|
||||
"kwargs": {
|
||||
"api_key": "or-key"
|
||||
},
|
||||
"expected": {
|
||||
"success": true,
|
||||
"provider": {
|
||||
"name": "openrouter",
|
||||
"url": "https://openrouter.ai/api/v1",
|
||||
"provider_type": "openai",
|
||||
"models": [],
|
||||
"enabled": true,
|
||||
"health_status": "unknown"
|
||||
},
|
||||
"message": "Connected to OpenRouter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"groq"
|
||||
],
|
||||
"kwargs": {
|
||||
"api_key": "gsk-key"
|
||||
},
|
||||
"expected": {
|
||||
"success": true,
|
||||
"provider": {
|
||||
"name": "groq",
|
||||
"url": "https://api.groq.com/openai/v1",
|
||||
"provider_type": "openai",
|
||||
"models": [],
|
||||
"enabled": true,
|
||||
"health_status": "unknown"
|
||||
},
|
||||
"message": "Connected to Groq"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"together"
|
||||
],
|
||||
"kwargs": {
|
||||
"api_key": "tog-key"
|
||||
},
|
||||
"expected": {
|
||||
"success": true,
|
||||
"provider": {
|
||||
"name": "together",
|
||||
"url": "https://api.together.xyz/v1",
|
||||
"provider_type": "openai",
|
||||
"models": [],
|
||||
"enabled": true,
|
||||
"health_status": "unknown"
|
||||
},
|
||||
"message": "Connected to Together AI"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_provider",
|
||||
"args": [
|
||||
"fireworks"
|
||||
],
|
||||
"kwargs": {
|
||||
"api_key": "fw-key"
|
||||
},
|
||||
"expected": {
|
||||
"success": true,
|
||||
"provider": {
|
||||
"name": "fireworks",
|
||||
"url": "https://api.fireworks.ai/inference/v1",
|
||||
"provider_type": "openai",
|
||||
"models": [],
|
||||
"enabled": true,
|
||||
"health_status": "unknown"
|
||||
},
|
||||
"message": "Connected to Fireworks AI"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "connect_all_local",
|
||||
"args": [],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"connected": 3,
|
||||
"failed": 0,
|
||||
"results": [
|
||||
{
|
||||
"name": "ollama",
|
||||
"display_name": "Ollama (Local)",
|
||||
"success": true,
|
||||
"message": "Connected to Ollama (Local)"
|
||||
},
|
||||
{
|
||||
"name": "vllm",
|
||||
"display_name": "vLLM (Local)",
|
||||
"success": true,
|
||||
"message": "Connected to vLLM (Local)"
|
||||
},
|
||||
{
|
||||
"name": "lmstudio",
|
||||
"display_name": "LM Studio (Local)",
|
||||
"success": true,
|
||||
"message": "Connected to LM Studio (Local)"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "main_usage",
|
||||
"args": [],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"stdout": "Usage:\n standardized_providers.py list # List available providers\n standardized_providers.py connect <name> [key] # Connect to a provider\n standardized_providers.py connect-all # Connect to all local providers\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "main_list",
|
||||
"args": [],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"stdout": "Available providers: 10\n\n 🔓 Ollama (Local) (local, free)\n Local model server. Run models on your own hardware.\n Strengths: privacy, no-cost, offline\n\n 🔓 vLLM (Local) (local, free)\n High-performance local model server with OpenAI-compatible API.\n Strengths: fast-inference, batching, openai-compatible\n\n 🔓 LM Studio (Local) (local, free)\n Desktop app for running local models with OpenAI-compatible API.\n Strengths: easy-setup, gui, model-discovery\n\n 🔑 Xiaomi MiMo (cloud, paid)\n Xiaomi's MiMo models. Strong reasoning and coding.\n Strengths: reasoning, coding, multilingual\n\n 🔑 OpenAI (cloud, paid)\n OpenAI's GPT models. Industry standard.\n Strengths: reasoning, coding, creative\n\n 🔑 Anthropic Claude (cloud, paid)\n Anthropic's Claude models. Strong reasoning and safety.\n Strengths: reasoning, safety, long-context\n\n 🔑 OpenRouter (cloud, freemium)\n Multi-provider gateway. Access many models with one API key.\n Strengths: multi-provider, model-variety, fallback\n\n 🔑 Groq (cloud, freemium)\n Ultra-fast inference on Groq hardware.\n Strengths: fast-inference, low-latency, openai-compatible\n\n 🔑 Together AI (cloud, freemium)\n Cloud inference for open-source models.\n Strengths: open-source-models, fast, openai-compatible\n\n 🔑 Fireworks AI (cloud, freemium)\n Fast inference for open-source models.\n Strengths: fast-inference, open-source, openai-compatible\n\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "main_connect_noargs",
|
||||
"args": [],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"stdout": "Usage: standardized_providers.py connect <name> [api_key]\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "main_connect",
|
||||
"args": [
|
||||
"openai",
|
||||
"sk-cli"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"stdout": "✓ Connected to OpenAI\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "main_connect",
|
||||
"args": [
|
||||
"ghost-provider",
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"stdout": "✗ Unknown provider: ghost-provider\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "main_connect",
|
||||
"args": [
|
||||
"ollama",
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"stdout": "✓ Connected to Ollama (Local)\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "main_connect",
|
||||
"args": [
|
||||
"xiaomi",
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"stdout": "✗ Provider xiaomi requires an API key\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "main_connect_all",
|
||||
"args": [],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"stdout": "Connected: 3, Failed: 0\n ✓ Ollama (Local): Connected to Ollama (Local)\n ✓ vLLM (Local): Connected to vLLM (Local)\n ✓ LM Studio (Local): Connected to LM Studio (Local)\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "main_unknown",
|
||||
"args": [
|
||||
"frobnicate"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"stdout": "Unknown command: frobnicate\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
exit=0
|
||||
@@ -0,0 +1 @@
|
||||
exit=0
|
||||
@@ -0,0 +1 @@
|
||||
exit=0
|
||||
@@ -0,0 +1 @@
|
||||
exit=0
|
||||
@@ -0,0 +1 @@
|
||||
exit=0
|
||||
@@ -0,0 +1 @@
|
||||
exit=0
|
||||
@@ -0,0 +1 @@
|
||||
exit=0
|
||||
@@ -0,0 +1 @@
|
||||
exit=0
|
||||
@@ -0,0 +1,92 @@
|
||||
# standardized_providers dichotomy gap-close — verification summary
|
||||
|
||||
- **Module**: standardized_providers (GO port, src/go/standardized-providers)
|
||||
- **Worktree**: /mnt/workdrive/rune-wave-cu2 (recreated at gitea/master fc6ed147 after the original vanished mid-session — sibling worktree churn)
|
||||
- **Parity SHA**: see SHAs below (feat commit; filled at push time)
|
||||
|
||||
## Coverage
|
||||
|
||||
Dichotomy audit: standardized_providers **3/5 PARTIAL → 5/5 FULL** (GO).
|
||||
Matched before: get_provider (real, batch-171 `getProvider`), connect_provider
|
||||
(comment-only match), main (Go entrypoint). Gap-closed name-preserving:
|
||||
`ListProviders` (list_providers), `GetProviderDataclass` (asdict projection
|
||||
riding the real `GetProvider`), `ConnectProvider` (connect_provider — now a
|
||||
REAL fn, previously only a gate helper behind a matching comment),
|
||||
`ConnectAllLocal` (connect_all_local), plus the full CLI text surface behind
|
||||
`runCLI` (main). Oracle quirks preserved (all frozen on the oracle shape):
|
||||
|
||||
- **Ordered-JSON handling** (model_connect precedent): every envelope is a
|
||||
Go STRUCT with oracle dict insertion-order fields (Go maps would sort
|
||||
keys). TWO distinct key orders in this oracle surface, both asserted
|
||||
byte-exactly in the battery:
|
||||
- list_providers projection: name, display_name, provider_type, url,
|
||||
description, requires_api_key, tier, cost, strengths, weaknesses
|
||||
(NO models — the comprehension drops it)
|
||||
- dataclasses.asdict: same as above but models BETWEEN
|
||||
requires_api_key and tier (declaration order; description after url)
|
||||
- **Partial-return trap** (model_connect precedent, adapted):
|
||||
connect_provider never raises — unknown name and key-required-without-key
|
||||
RETURN single-key `{"error": ...}` dicts; connect_all_local coerces rows
|
||||
via `result.get("success", False)` / `result.get("message",
|
||||
result.get("error", "Unknown"))` and the loop continues (error-as-data,
|
||||
counts from coerced rows). Go mirrors both as discriminated envelope
|
||||
structs (connectError/connectSuccess) + localRowFrom coercion.
|
||||
- STANDARD_PROVIDERS iteration order = dict insertion order (all three
|
||||
iteration sites: list_providers, connect_all_local, CLI list) — the port
|
||||
iterates a fixed slice, never a map.
|
||||
- registry file persistence (providers.json write-through) + HTTP model
|
||||
fetches stay Python (reactive surface — same line the previous batch-171
|
||||
port drew); the connect RESULT envelope is the ported contract.
|
||||
- No clock sites in the module — legacy list truth format (no frozen_now
|
||||
envelope); hermetic HOME via the rig (provider_registry derives
|
||||
~/.config/rune/providers.json at registry construction and writes it on
|
||||
every connect).
|
||||
|
||||
## Commands and exit codes
|
||||
|
||||
| Check | Command | Exit | Evidence |
|
||||
|---|---|---|---|
|
||||
| truth generation | `PYTHONPATH=scripts/sp_freeze_seams:src python3 scripts/gen_standardized_providers_truth.py` | 0 | `sp-truth-gen.log/.exit` — 43 cases |
|
||||
| frozen truth byte-identical | `PYTHONPATH=scripts/sp_freeze_seams:src python3 scripts/freeze_truth.py --verify src/go/standardized-providers/truth.json --module rune.standardized_providers` | 0 | `sp-freeze-verify.log/.exit` — OK: 43 cases byte-identical against live oracle |
|
||||
| gofmt / vet / build | `gofmt -l . && go vet ./... && go build ./...` (src/go/standardized-providers) | 0 | `sp-gofmt.log` (empty), `sp-govet.log`, `sp-gobuild.log` + `.exit` |
|
||||
| Go battery | `go test -count=1 -v ./...` (src/go/standardized-providers) | 0 | `sp-gotest.log/.exit` — 7/7 PASS (TestFrozenTruthReplay verbatim truth.json embed; ordered-JSON byte tests for BOTH key orders; error-as-data envelope tests; connect-all coercion/order test; CLI byte-parity vs live-captured oracle stdout; batch-171 decision table; table insertion order) |
|
||||
| CI replay binary | `./standardized-providers replay truth.json` | 0 | `sp-replay.log/.exit` — 43/43 passed |
|
||||
| live parity | `PYTHONPATH=scripts/sp_freeze_seams:src python3 scripts/check_standardized_providers_parity.py` | 0 | `sp-parity.log/.exit` — 52/52 match (43 op dispatches + 9 CLI cases double-checked through BOTH the stdin-op channel and the binary's direct argv/CLI mode, byte-exact for CLI text) |
|
||||
| Python smoke (live) | `python3 /tmp/sp_smoke.py` (hermetic HOME) | 0 | `sp-pysmoke-live.log/.exit` — oracle table/get/connect/connect_all/CLI healthy |
|
||||
| Python smoke (python pin) | `RUNE_STANDARDIZED_PROVIDERS_CORE=python python3 /tmp/sp_smoke.py` | 0 | `sp-pysmoke-python.log/.exit` — healthy (module has no loader seam: 0 references to the pin anywhere in src/, proof `sp-no-loader-proof.log/.exit` — count 0; pin vacuous, run per protocol) |
|
||||
| audit | `python3 scripts/dichotomy_audit.py --write` | 0 | `sp-audit.log/.exit` — 5/5 matched, status FULL (GO); report row regenerated |
|
||||
|
||||
Informational (non-gating, module-scoped gate per mconn precedent): repo-wide
|
||||
`go build ./...` over every src/go module fails for **3 pre-existing sibling
|
||||
states** on landed master, all environmental, none touching this module:
|
||||
`adaptive-router` + `builder-bridge-cli` link against a built
|
||||
`libroute_risk_flags.a` that fresh worktrees don't carry; `paths` compiles a
|
||||
main package with no main function on landed master. Recorded here honestly;
|
||||
zero delta from this port (evidence retained in sp-gobuild.log context).
|
||||
|
||||
## Truth rig
|
||||
|
||||
- `scripts/sp_freeze_seams/sitecustomize.py` — pin_home (hermetic HOME for
|
||||
the providers.json write-through) + sys.path carrying src/ AND src/rune/
|
||||
(the bare `from provider_registry import get_registry` sibling import is
|
||||
ALIVE at call time and only resolves when the package dir itself is on
|
||||
sys.path) + binds `get_provider_asdict` and the six `main_*` argv-patched
|
||||
stdout-capture seams onto the oracle module.
|
||||
- Corpus: 43 cases, legacy list format (no clock sites):
|
||||
1 list / 12 get-asdict (all 10 + ghost + empty name) / 20 connect
|
||||
(2 unknown, 6 cloud-no-key, 1 explicit-empty-key, 3 locals, 7 cloud-with-key)
|
||||
/ 1 connect_all_local (warm registry, mid-sequence) / 9 CLI main() rows.
|
||||
- The CLI rows carry the EXACT stdout text (emoji included,
|
||||
ensure_ascii=False) — the Go battery and live parity byte-compare them.
|
||||
|
||||
## Files
|
||||
|
||||
- `src/go/standardized-providers/main.go` — the 5-fn name-preserving port
|
||||
(ListProviders/GetProvider/ConnectProvider/ConnectAllLocal/runCLI) +
|
||||
envelope replay + stdin op dispatch + CLI default mode
|
||||
- `src/go/standardized-providers/connect_test.go` — battery (verbatim
|
||||
truth embed) + quirk unit tests
|
||||
- `src/go/standardized-providers/truth.json` — 43-case freeze_truth corpus
|
||||
- `scripts/gen_standardized_providers_truth.py`,
|
||||
`scripts/check_standardized_providers_parity.py`,
|
||||
`scripts/sp_freeze_seams/sitecustomize.py`
|
||||
@@ -0,0 +1 @@
|
||||
exit=0
|
||||
Reference in new issue
Block a user