Files
legal-ai/scripts/adapter_profiles.py
Chaim 471934cc2c
Some checks failed
G12 Leak-Guard / leak-guard (push) Has been cancelled
Build & Deploy / build-and-deploy (push) Has been cancelled
Lint — undefined names / undefined-names (push) Has been cancelled
feat(operations): הוספת codex_local לסרגל-חירום + סקריפט A/B-benchmark
- adapter_profiles.py: codex_local.default_model = gpt-5.5 (עדכון מ-gpt-5.3-codex)
- agent-adapters-panel.tsx: כפתור "העבר הכל ל-Codex " בסרגל-החירום (ב-G/Codex fallback)
- operations.ts: הוספת codex_local לדוקומנטציה של useAdapterMigrate
- ab_halacha_codex.py: סקריפט A/B חדש — חילוץ הלכות דרך codex exec/gpt-5.5 (non-destructive benchmark)
- SCRIPTS.md: תיעוד ab_halacha_codex.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 10:45:14 +00:00

110 lines
4.4 KiB
Python

#!/usr/bin/env python3
"""adapter_profiles.py — single source of truth for Paperclip adapter capabilities.
Why this exists: switching a Paperclip agent's adapter is NOT a free dropdown
flip. Each adapter family differs on three axes that, if not reconciled, crash
the agent immediately or degrade it silently:
1. model id ↔ provider — each adapter expects a model from its own provider
(claude-* / gemini-* / deepseek-* / OpenAI Codex-capable ids). Wrong or
foreign ids fail or silently fall back to the adapter default.
2. instructions transport —
• file_path adapters pass the instructions file as a *path*
(`claude --append-system-prompt-file`); the CLI parses it,
so a leading `---` YAML frontmatter is fine.
• content_arg adapters read the file and splice its *content* into the
CLI argument (`gemini --prompt "<content>"`,
`hermes chat -q "<content>"`). yargs/arg-parsers read a
value starting with `--` as a flag → the run dies within
~2s with "Not enough arguments following: prompt". These
adapters therefore require a frontmatter-FREE instructions
file (must not start with `--`).
3. tool availability — claude uses the frontmatter `tools:`/`--allowedTools`;
gemini reads a GLOBAL `~/.gemini/settings.json` `excludeTools` list (one
list for ALL gemini agents); deepseek uses `~/.hermes`.
Keeping these facts in one declarative registry makes "migrate any agent to any
adapter" data-driven: adding a future adapter (codex_local, grok_local, ...) is
one new entry here. Consumed by scripts/migrate_agent_adapter.py.
Verified 2026-06-17 against the installed adapter packages
(`@paperclipai/adapter-{claude,gemini,codex}-local`, in-repo
`adapters/deepseek-paperclip-adapter`) and the live Paperclip agents table.
"""
from __future__ import annotations
# tool_config values:
# "frontmatter" — tools come from the agent .md frontmatter / --allowedTools
# "gemini_global" — tools gated by the global ~/.gemini/settings.json excludeTools
# "hermes" — tools configured under ~/.hermes (deepseek runtime)
ADAPTER_PROFILES: dict[str, dict] = {
"claude_local": {
"provider": "claude",
"default_model": "claude-opus-4-8",
"instructions_mode": "file_path",
"frontmatter_safe": True,
"tool_config": "frontmatter",
},
"gemini_local": {
"provider": "gemini",
"default_model": "gemini-3.1-pro-preview",
"instructions_mode": "content_arg",
"frontmatter_safe": False,
"tool_config": "gemini_global",
},
"deepseek_local": {
"provider": "deepseek",
"default_model": "deepseek-v4-pro",
"instructions_mode": "content_arg",
"frontmatter_safe": False,
"tool_config": "hermes",
},
"codex_local": {
"provider": "codex",
"default_model": "gpt-5.5",
"instructions_mode": "content_arg",
"frontmatter_safe": False,
"tool_config": "codex_home",
"model_prefixes": (
"gpt-",
"o3",
"o4",
"codex-",
),
},
}
class UnknownAdapter(ValueError):
"""Raised when an adapter type has no registered profile."""
def get_profile(adapter_type: str) -> dict:
"""Return the capability profile for an adapter type, or raise UnknownAdapter.
Fail loud: a typo'd or unregistered adapter must not silently pass through
migration (that is exactly how the original crash happened)."""
profile = ADAPTER_PROFILES.get(adapter_type)
if profile is None:
known = ", ".join(sorted(ADAPTER_PROFILES))
raise UnknownAdapter(
f"אדפטר לא מוכר: {adapter_type!r}. רשומים: {known}. "
f"הוסף פרופיל ל-adapter_profiles.py לפני מעבר אליו."
)
return profile
def model_matches_provider(model: str, adapter_type: str) -> bool:
"""True iff `model` belongs to the adapter's provider family.
Empty model is treated as matching — the adapter falls back to its own
default model, which is by definition in-family."""
if not model:
return True
profile = get_profile(adapter_type)
prefixes = profile.get("model_prefixes")
if prefixes:
return model.startswith(tuple(prefixes))
return model.startswith(profile["provider"])