Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/apflu_aichatgroup.json
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
15 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "apflu/aichatgroup", "files": {".env.example": {"1ab7e46cba4893940cc7d4ff9f0e95a9edd5bca9": {"t": 1785745173.8271205, "n": 2032, "h": "6634b9e722341de9", "c": "# Anthropic API key —— 仅 --live 模式需要;replay_demo 默认用 MockGateway,无需 key\nANTHROPIC_API_KEY=sk-ant-...\n\n# ---- 模型选择:写成 provider_alias#model(别名与模型名解耦,见 gateway/router.py)----\n# 内置别名(配了对应标准 key 即可用):\n# anthropic#claude-opus-4-8 openai#gpt-4o gemini#gemini-2.0-flash\n# 不带 # 的裸模型名仍按前缀推断路由(向后兼容),但推荐显式写别名。\n\n# 其他 provider 的标准 key:\n# OPENAI_API_KEY=sk-...\n# OPENAI_BASE_URL=https://api.openai.com/v1 # 让内置 openai 别名指向兼容端点时改这里\n# GEMINI_API_KEY=... # 或 GOOGLE_API_KEY\n\n# 额外命名 provider —— 两种写法,任选:\n#\n# 写法 A(推荐,声明式):写一个 providers.json(见 examples/providers.example.json),\n# 每个 provider 一个块:{ \"alias\", \"type\", \"provider_url\", \"api_key_env\" }。\n# 放在项目根会被自动加载;或用 AICG_PROVIDERS_FILE 指定路径。密钥仍留在下面这些 *_env 里。\n# AICG_PROVIDERS_FILE=providers.json\n# DEEPSEEK_API_KEY=sk-...\n# GROQ_API_KEY=gsk-...\n#\n# 写法 B(摊平进 env,无需额外文件):\n# AICG_PROVIDERS=deepseek,groq\n# AICG_PROVIDER_DEEPSEEK_KIND=openai # openai | anthropic | gemini(默认 openai)\n# AICG_PROVIDER_DEEPSEEK_BASE_URL=https://api.deepseek.com/v1\n# AICG_PROVIDER_DEEPSEEK_API_KEY=sk-... # 或 AICG_PROVIDER_DEEPSEEK_API_KEY_ENV=DEEPSEEK_KEY\n#\n# 两种写法都让你之后在角色卡 / 调度模型里写 deepseek#deepseek-chat\n\n# 可选:覆盖各层默认模型(推荐用别名#模型 形式)\n# AICG_MODEL_OPUS=anthropic#claude-opus-4-8\n# AICG_MODEL_SONNET=anthropic#claude-sonnet-5\n# AICG_MODEL_HAIKU=anthropic#claude-haiku-4-5\n# AICG_MODEL_DIRECTOR=anthropic#claude-haiku-4-5 # 调度用便宜模型\n# AICG_MODEL_COMPACTION=anthropic#claude-haiku-4-5 # 压缩用便宜模型\n\n# 可选:主循环节奏与 compaction 阈值\n# AICG_TURN_INTERVAL_S=1.5\n# AICG_IDLE_POLL_S=2.0\n# AICG_MAX_HISTORY=60\n# AICG_KEEP_LAST=20\n# AICG_SQLITE_PATH=aichatgroup.sqlite\n# AICG_PRESET_PATH=examples/room.example.json\n\n# ---- M1 Telegram 落地(scripts/run_telegram.py 需要)----\n# 观察者 bot(务必在 BotFather 关掉 privacy mode)+ 群 chat_id\nTG_TOKEN_OBSERVER=123456:观察者bot-token\nTG_CHAT_ID=-1001234567890\n# 每个角色一个 bot token(与预设 JSON 里的 bot_token_env 对应,按槽位编号、不绑角色名)\nTG_TOKEN_CHAR_1=123456:角色1bot-token\nTG_TOKEN_CHAR_2=123456:角色2bot-token\nTG_TOKEN_CHAR_3=123456:角色3bot-token\n"}, "0bd0b180954e5efd24a0dc36cda2c9b8ba2164a6": {"t": 1785748390.632067, "n": 2032, "h": "6634b9e722341de9", "c": "# Anthropic API key —— 仅 --live 模式需要;replay_demo 默认用 MockGateway,无需 key\nANTHROPIC_API_KEY=sk-ant-...\n\n# ---- 模型选择:写成 provider_alias#model(别名与模型名解耦,见 gateway/router.py)----\n# 内置别名(配了对应标准 key 即可用):\n# anthropic#claude-opus-4-8 openai#gpt-4o gemini#gemini-2.0-flash\n# 不带 # 的裸模型名仍按前缀推断路由(向后兼容),但推荐显式写别名。\n\n# 其他 provider 的标准 key:\n# OPENAI_API_KEY=sk-...\n# OPENAI_BASE_URL=https://api.openai.com/v1 # 让内置 openai 别名指向兼容端点时改这里\n# GEMINI_API_KEY=... # 或 GOOGLE_API_KEY\n\n# 额外命名 provider —— 两种写法,任选:\n#\n# 写法 A(推荐,声明式):写一个 providers.json(见 examples/providers.example.json),\n# 每个 provider 一个块:{ \"alias\", \"type\", \"provider_url\", \"api_key_env\" }。\n# 放在项目根会被自动加载;或用 AICG_PROVIDERS_FILE 指定路径。密钥仍留在下面这些 *_env 里。\n# AICG_PROVIDERS_FILE=providers.json\n# DEEPSEEK_API_KEY=sk-...\n# GROQ_API_KEY=gsk-...\n#\n# 写法 B(摊平进 env,无需额外文件):\n# AICG_PROVIDERS=deepseek,groq\n# AICG_PROVIDER_DEEPSEEK_KIND=openai # openai | anthropic | gemini(默认 openai)\n# AICG_PROVIDER_DEEPSEEK_BASE_URL=https://api.deepseek.com/v1\n# AICG_PROVIDER_DEEPSEEK_API_KEY=sk-... # 或 AICG_PROVIDER_DEEPSEEK_API_KEY_ENV=DEEPSEEK_KEY\n#\n# 两种写法都让你之后在角色卡 / 调度模型里写 deepseek#deepseek-chat\n\n# 可选:覆盖各层默认模型(推荐用别名#模型 形式)\n# AICG_MODEL_OPUS=anthropic#claude-opus-4-8\n# AICG_MODEL_SONNET=anthropic#claude-sonnet-5\n# AICG_MODEL_HAIKU=anthropic#claude-haiku-4-5\n# AICG_MODEL_DIRECTOR=anthropic#claude-haiku-4-5 # 调度用便宜模型\n# AICG_MODEL_COMPACTION=anthropic#claude-haiku-4-5 # 压缩用便宜模型\n\n# 可选:主循环节奏与 compaction 阈值\n# AICG_TURN_INTERVAL_S=1.5\n# AICG_IDLE_POLL_S=2.0\n# AICG_MAX_HISTORY=60\n# AICG_KEEP_LAST=20\n# AICG_SQLITE_PATH=aichatgroup.sqlite\n# AICG_PRESET_PATH=examples/room.example.json\n\n# ---- M1 Telegram 落地(scripts/run_telegram.py 需要)----\n# 观察者 bot(务必在 BotFather 关掉 privacy mode)+ 群 chat_id\nTG_TOKEN_OBSERVER=123456:观察者bot-token\nTG_CHAT_ID=-1001234567890\n# 每个角色一个 bot token(与预设 JSON 里的 bot_token_env 对应,按槽位编号、不绑角色名)\nTG_TOKEN_CHAR_1=123456:角色1bot-token\nTG_TOKEN_CHAR_2=123456:角色2bot-token\nTG_TOKEN_CHAR_3=123456:角色3bot-token\n"}, "29aaeb1fa7a89e2b34163d54a482af1dff06339f": {"t": 1785748391.741618, "n": 235, "h": "edae7f03516e10f3", "c": "# Anthropic API key —— 仅 --live 模式需要;replay_demo 默认用 MockGateway,无需 key\nANTHROPIC_API_KEY=sk-ant-...\n\n# 可选:覆盖各层默认模型(见 config.py)\n# AICG_MODEL_OPUS=claude-opus-4-8\n# AICG_MODEL_SONNET=claude-sonnet-5\n# AICG_MODEL_HAIKU=claude-haiku-4-5\n"}}, "src/aichatgroup/config.py": {"1ab7e46cba4893940cc7d4ff9f0e95a9edd5bca9": {"t": 1786068521.9160078, "n": 7196, "h": "1a91315917aee76c", "c": "\"\"\"运行配置与默认模型。\n\n模型 ID 以 Anthropic 官方现役别名为准(截至 2026-07):\n- Opus 4.8 : claude-opus-4-8\n- Sonnet 5 : claude-sonnet-5\n- Haiku 4.5: claude-haiku-4-5\n\"\"\"\nfrom __future__ import annotations\n\nimport json\nimport os\nimport re\nfrom dataclasses import dataclass, field\nfrom pathlib import Path\n\n# 各层默认模型别名(可被环境变量覆盖)\nDEFAULT_MODEL_OPUS = \"claude-opus-4-8\"\nDEFAULT_MODEL_SONNET = \"claude-sonnet-5\"\nDEFAULT_MODEL_HAIKU = \"claude-haiku-4-5\"\n\n\n@dataclass\nclass ProviderSpec:\n \"\"\"一个可配置的 provider 别名 → 具体网关的定义。\n\n kind 决定用哪套适配器:openai(含所有兼容端点)/ anthropic / gemini。\n 内置别名 anthropic/openai/gemini 由标准 key 自动装配;这里是**额外**的命名端点,\n 比如多个 OpenAI 兼容服务(deepseek / groq / 本地 vLLM)各自的 base_url + key。\n \"\"\"\n\n alias: str\n kind: str = \"openai\" # openai | anthropic | gemini\n base_url: str | None = None\n api_key: str | None = None\n\n @classmethod\n def from_dict(cls, d: dict) -> \"ProviderSpec\":\n \"\"\"从声明式配置构造。字段:\n\n alias 必填,provider 别名(用 别名#模型 引用)\n type / kind openai | anthropic | gemini(默认 openai)\n provider_url / base_url / url 端点地址(openai 兼容端点必填)\n api_key 直接写密钥(不推荐,会进配置文件)\n api_key_env 密钥所在的环境变量名(推荐,密钥留在 .env 里)\n \"\"\"\n alias = d.get(\"alias\") or d.get(\"name\")\n if not alias:\n raise ValueError(f\"provider 定义缺少 alias:{d!r}\")\n api_key = d.get(\"api_key\")\n if not api_key and d.get(\"api_key_env\"):\n api_key = os.environ.get(d[\"api_key_env\"])\n return cls(\n alias=str(alias),\n kind=str(d.get(\"type\") or d.get(\"kind\") or \"openai\").lower(),\n base_url=d.get(\"provider_url\") or d.get(\"base_url\") or d.get(\"url\"),\n api_key=api_key,\n )\n\n\ndef load_provider_specs(path: str | os.PathLike[str]) -> list[ProviderSpec]:\n \"\"\"从 JSON 文件读 provider 列表。\n\n 文件可以是 {\"providers\": [ {...}, {...} ]} 或直接一个 [ {...}, {...} ] 数组。\n \"\"\"\n data = json.loads(Path(path).read_text(encoding=\"utf-8\"))\n items = data.get(\"providers\", []) if isinstance(data, dict) else data\n return [ProviderSpec.from_dict(x) for x in items]\n\n\ndef _parse_providers() -> list[ProviderSpec]:\n \"\"\"从 env 读额外 provider 定义。\n\n AICG_PROVIDERS=deepseek,groq\n AICG_PROVIDER_DEEPSEEK_KIND=openai\n AICG_PROVIDER_DEEPSEEK_BASE_URL=https://api.deepseek.com/v1\n AICG_PROVIDER_DEEPSEEK_API_KEY=sk-... # 或 *_API_KEY_ENV 间接指向另一个环境变量\n \"\"\"\n raw = os.environ.get(\"AICG_PROVIDERS\", \"\")\n specs: list[ProviderSpec] = []\n for alias in (a.strip() for a in raw.split(\",\") if a.strip()):\n up = alias.upper()\n key = os.environ.get(f\"AICG_PROVIDER_{up}_API_KEY\")\n if not key:\n key_env = os.environ.get(f\"AICG_PROVIDER_{up}_API_KEY_ENV\")\n key = os.environ.get(key_env) if key_env else None\n specs.append(ProviderSpec(\n alias=alias,\n kind=os.environ.get(f\"AICG_PROVIDER_{up}_KIND\", \"openai\").lower(),\n base_url=os.environ.get(f\"AICG_PROVIDER_{up}_BASE_URL\"),\n api_key=key,\n ))\n return specs\n\n\ndef _strip_inline_comment(value: str) -> str:\n \"\"\"剥掉行内注释:`#` 前有空白才算注释起点。\n\n 值本身可能含 `#`(如 provider_alias#model),那种 `#` 前无空白,不会被误伤。\n 整段带引号的值(\"...\" / '...')则原样保留、不剥注释。\n \"\"\"\n v = value.strip()\n if v[:1] in (\"'\", '\"'):\n return v\n m = re.search(r\"\\s#\", v)\n return v[: m.start()].rstrip() if m else v\n\n\ndef _load_dotenv(path: Path) -> None:\n \"\"\"极简 .env 加载:仅在文件存在时把 KEY=VALUE 写入 os.environ(不覆盖已存在项)。\"\"\"\n if not path.is_file():\n return\n for raw in path.read_text(encoding=\"utf-8\").splitlines():\n line = raw.strip()\n if not line or line.startswith(\"#\") or \"=\" not in line:\n continue\n key, _, value = line.partition(\"=\")\n key = key.strip()\n value = _strip_inline_comment(value).strip('\"').strip(\"'\")\n os.environ.setdefault(key, value)\n\n\n@dataclass\nclass Settings:\n anthropic_api_key: str | None = None\n model_opus: str = DEFAULT_MODEL_OPUS\n model_sonnet: str = DEFAULT_MODEL_SONNET\n model_haiku: str = DEFAULT_MODEL_HAIKU\n # 单个发言回合默认输出上限(气泡短,无需很大)\n max_tokens: int = 1024\n\n # ---- 多 provider(可混用;模型写成 provider_alias#model,见 gateway/router.py)----\n openai_api_key: str | None = None\n openai_base_url: str | None = None # 内置 openai 别名指向兼容端点时设这个\n openai_prefixes: str = \"\" # 逗号分隔的额外前缀,供裸模型名的前缀推断\n gemini_api_key: str | None = None\n # 额外的命名 provider(别名可配置;用 alias#model 引用)\n providers: list = field(default_factory=list)\n\n # ---- M1 编排相关 ----\n # 调度与 compaction 用便宜模型(Haiku)\n director_model: str = DEFAULT_MODEL_HAIKU\n compaction_model: str = DEFAULT_MODEL_HAIKU\n # 主循环节奏(秒)\n turn_interval_s: float = 1.5 # 两个发言回合之间的间隔(也顺带遵守 Telegram 限速)\n idle_poll_s: float = 2.0 # 暂停 / 无人接话时的轮询间隔\n # compaction 阈值\n max_history: int = 60 # 历史超过这么多条触发压缩\n keep_last: int = 20 # 压缩后保留的近期条数\n # 持久化与预设\n sqlite_path: str = \"aichatgroup.sqlite\"\n preset_path: str | None = None\n\n @classmethod\n def from_env(cls, dotenv_path: str | os.PathLike[str] | None = None) -> \"Settings\":\n if dotenv_path is not None:\n _load_dotenv(Path(dotenv_path))\n else:\n _load_dotenv(Path.cwd() / \".env\")\n\n def _f(name: str, default: float) -> float:\n raw = os.environ.get(name)\n return float(raw) if raw else default\n\n def _i(name: str, default: int) -> int:\n raw = os.environ.get(name)\n return int(raw) if raw else default\n\n # provider 定义:声明式 JSON 文件 + 摊平的 env 变量,二者合并(同别名后者覆盖)\n providers = _parse_providers()\n providers_file = os.environ.get(\"AICG_PROVIDERS_FILE\")\n if providers_file is None and (Path.cwd() / \"providers.json\").is_file():\n providers_file = str(Path.cwd() / \"providers.json\")\n if providers_file and Path(providers_file).is_file():\n providers = load_provider_specs(providers_file) + providers\n\n return cls(\n anthropic_api_key=os.environ.get(\"ANTHROPIC_API_KEY\"),\n openai_api_key=os.environ.get(\"OPENAI_API_KEY\"),\n openai_base_url=os.environ.get(\"OPENAI_BASE_URL\"),\n openai_prefixes=os.environ.get(\"AICG_OPENAI_PREFIXES\", \"\"),\n gemini_api_key=os.environ.get(\"GEMINI_API_KEY\") or os.environ.get(\"GOOGLE_API_KEY\"),\n model_opus=os.environ.get(\"AICG_MODEL_OPUS\", DEFAULT_MODEL_OPUS),\n model_sonnet=os.environ.get(\"AICG_MODEL_SONNET\", DEFAULT_MODEL_SONNET),\n model_haiku=os.environ.get(\"AICG_MODEL_HAIKU\", DEFAULT_MODEL_HAIKU),\n director_model=os.environ.get(\"AICG_MODEL_DIRECTOR\", DEFAULT_MODEL_HAIKU),\n compaction_model=os.environ.get(\"AICG_MODEL_COMPACTION\", DEFAULT_MODEL_HAIKU),\n turn_interval_s=_f(\"AICG_TURN_INTERVAL_S\", 1.5),\n idle_poll_s=_f(\"AICG_IDLE_POLL_S\", 2.0),\n max_history=_i(\"AICG_MAX_HISTORY\", 60),\n keep_last=_i(\"AICG_KEEP_LAST\", 20),\n sqlite_path=os.environ.get(\"AICG_SQLITE_PATH\", \"aichatgroup.sqlite\"),\n preset_path=os.environ.get(\"AICG_PRESET_PATH\"),\n providers=providers,\n )\n"}}}}