M4(文风): style-auditor 双轨(提取指纹/漂移第四审)+ jobs 长任务框架(zombie reaper) + 回炉 refine + GET /style read-back。 M5(生成+扩展): worldbuilder/character-gen(入库 continuity 409 gate + partition_writes 白名单 + schema→JSONB 形变); 网关多 provider 回退链/熔断/能力降级(Anthropic/Gemini 适配器);Skill registry + 表权限沙箱 + 规则; 前端 角色生成器/世界观/Codex/规则页/技能库/⌘K 命令面板。 K1(Kimi Code 订阅接入): OAuth device-flow(kimi-code)+ 静态 Console key(kimi-code-key)两路径; coding 端点 KimiCLI 伪造头(实测 UA allow-list 门禁,缺则 403)+ JSON 模式结构化(thinking ⊥ tool_choice)。 本地联调修复: CORS 中间件;assemble 注入 premise+「写第N章」指令(修空 prompt 400); GET /outline·/draft read-back + 大纲/工作台/审稿页重载;写页 client/server 常量边界 + notFound 健壮化; 字数 toLocaleString locale 水合;审稿页终稿从已存草稿 seed(修 accept 422)。 门禁: backend ruff/mypy(157)/alembic 无漂移/pytest 451 · frontend lint/tsc/vitest/build。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
83 lines
3.2 KiB
Python
83 lines
3.2 KiB
Python
"""内存替身:凭据存储 + 提供商探测(端点测试用,无 DB/无网络)。
|
||
|
||
绝对导入 `from fakes import ...`(包目录无 __init__.py,见 memory/gotchas)。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import uuid
|
||
|
||
from ww_api.services.credentials import StoredCredential, StoredRouting
|
||
from ww_llm_gateway.adapters.base import Capabilities
|
||
|
||
|
||
class FakeCredentialStore:
|
||
"""实现 `CredentialStore` Protocol 的内存版。"""
|
||
|
||
def __init__(self) -> None:
|
||
# key: (owner_id, provider) → api_key 密文
|
||
self.creds: dict[tuple[uuid.UUID, str], bytes] = {}
|
||
# key: (owner_id, provider) → oauth 密文(OAuth 凭据,auth_type="oauth")
|
||
self.oauth: dict[tuple[uuid.UUID, str], bytes] = {}
|
||
self.routing: dict[str, StoredRouting] = {}
|
||
|
||
async def list_credentials(self, owner_id: uuid.UUID) -> list[StoredCredential]:
|
||
rows = [
|
||
StoredCredential(provider=p, api_key_enc=blob)
|
||
for (o, p), blob in self.creds.items()
|
||
if o == owner_id
|
||
]
|
||
rows += [
|
||
StoredCredential(provider=p, api_key_enc=None, auth_type="oauth", oauth_enc=blob)
|
||
for (o, p), blob in self.oauth.items()
|
||
if o == owner_id
|
||
]
|
||
return rows
|
||
|
||
async def list_routing(self) -> list[StoredRouting]:
|
||
return list(self.routing.values())
|
||
|
||
async def get_credential(self, owner_id: uuid.UUID, provider: str) -> StoredCredential | None:
|
||
oauth_blob = self.oauth.get((owner_id, provider))
|
||
if oauth_blob is not None:
|
||
return StoredCredential(
|
||
provider=provider, api_key_enc=None, auth_type="oauth", oauth_enc=oauth_blob
|
||
)
|
||
blob = self.creds.get((owner_id, provider))
|
||
if blob is None:
|
||
return None
|
||
return StoredCredential(provider=provider, api_key_enc=blob)
|
||
|
||
async def upsert_credential(
|
||
self, owner_id: uuid.UUID, provider: str, api_key_enc: bytes
|
||
) -> None:
|
||
self.creds[(owner_id, provider)] = api_key_enc
|
||
self.oauth.pop((owner_id, provider), None)
|
||
|
||
async def upsert_oauth_credential(
|
||
self, owner_id: uuid.UUID, provider: str, oauth_enc: bytes
|
||
) -> None:
|
||
self.oauth[(owner_id, provider)] = oauth_enc
|
||
self.creds.pop((owner_id, provider), None)
|
||
|
||
async def delete_credential(self, owner_id: uuid.UUID, provider: str) -> bool:
|
||
had = (owner_id, provider) in self.creds or (owner_id, provider) in self.oauth
|
||
self.creds.pop((owner_id, provider), None)
|
||
self.oauth.pop((owner_id, provider), None)
|
||
return had
|
||
|
||
async def upsert_routing(self, routing: StoredRouting) -> None:
|
||
self.routing[routing.tier] = routing
|
||
|
||
|
||
class FakeProviderProbe:
|
||
"""实现 `ProviderProbe` Protocol:返回固定能力矩阵,绝不联网。"""
|
||
|
||
def __init__(self, caps: Capabilities | None = None) -> None:
|
||
self.caps = caps or Capabilities(structured_output=True, prefix_cache=True, thinking=False)
|
||
self.calls: list[tuple[uuid.UUID, str]] = []
|
||
|
||
async def probe(self, owner_id: uuid.UUID, provider: str) -> Capabilities:
|
||
self.calls.append((owner_id, provider))
|
||
return self.caps
|