mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-15 14:22:43 +00:00
fix(caching): include Kimi/Moonshot in OpenRouter prompt cache policy (#25970)
This commit is contained in:
parent
43e566f77e
commit
3b857a35e7
7 changed files with 794 additions and 1 deletions
BIN
.dev-workflow/code-graph.db
Normal file
BIN
.dev-workflow/code-graph.db
Normal file
Binary file not shown.
74
.dev-workflow/phase-tracker.json
Normal file
74
.dev-workflow/phase-tracker.json
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
{
|
||||||
|
"template": "github-issue",
|
||||||
|
"current_phase": 13,
|
||||||
|
"completed_phases": [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
7,
|
||||||
|
8,
|
||||||
|
9,
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
12,
|
||||||
|
13
|
||||||
|
],
|
||||||
|
"issue_number": 27653,
|
||||||
|
"started_at": "2026-05-18T07:02:16.341710",
|
||||||
|
"last_advanced_at": "2026-05-18T07:15:27.942283",
|
||||||
|
"state": "completed",
|
||||||
|
"phase_evidence": {
|
||||||
|
"1": {
|
||||||
|
"evidence": "Issue #27653 scored 6/7, no competing PRs found across 5 keyword searches (history, history search, prompt reuse, slash command gateway), no duplicate markers, 0 comments. Feature request: add /history cmd with search & reuse for gateway platforms. Author: zccyman. Labels: type/feature, comp/gateway.",
|
||||||
|
"timestamp": "2026-05-18T07:02:41.657629"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"evidence": "branch feat/history-gateway-27653 created from origin/main (4c46c35ed), git email set to 16263913+zccyman@users.noreply.github.com for CI attribution",
|
||||||
|
"timestamp": "2026-05-18T07:03:04.882165"
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"evidence": "Code understanding complete: 6 key files analyzed. /history is cli_only=True in commands.py L74. Gateway dispatch at run.py L6505-6760 uses canonical command chain. Session store has load_transcript() and search_messages() (FTS5). Feishu cards at feishu.py L1854 support interactive buttons with value callbacks. /retry at L9690 shows transcript reading pattern.",
|
||||||
|
"timestamp": "2026-05-18T07:04:41.647803"
|
||||||
|
},
|
||||||
|
"4": {
|
||||||
|
"evidence": "dwf_security_scan quick: all findings pre-existing in CI configs (no github, docker) and test fixtures. Feature adds read-only /history slash command that reads session store — no new code execution, no file writes, no credentials exposed. Zero risk.",
|
||||||
|
"timestamp": "2026-05-18T07:04:58.163837"
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
"evidence": "Modified 2 files: hermes_cli/commands.py (removed cli_only=True, added args_hint=[keyword]) and gateway/run.py (added dispatch line for history + _handle_history_command handler (96 lines). Handler supports /history, /history <page>, /history <keyword>, paginated 5/page. Syntax check passed.",
|
||||||
|
"timestamp": "2026-05-18T07:11:15.389855"
|
||||||
|
},
|
||||||
|
"6": {
|
||||||
|
"evidence": "10 new tests in tests/gateway/test_history_command.py all PASSED (test_no_history, test_shows_recent_prompts_page_1, test_pagination_page_2, test_search_by_keyword, test_search_no_match, test_page_out_of_range_clamps_to_last_page, test_session_store_unavailable, test_filters_tool_and_assistant, test_empty_args_from_event, test_history_next_prev_navigation). Existing gateway tests (14) also PASSED — zero regressions.",
|
||||||
|
"timestamp": "2026-05-18T07:13:38.921925"
|
||||||
|
},
|
||||||
|
"7": {
|
||||||
|
"evidence": "dwf_review_run: 0 ERROR, 1 WARNING (pre-existing http://127.0.0.1 in commands.py L1107, NOT our change), 89 INFO (all pre-existing style/assert issues). Zero new findings across 3 files reviewed.",
|
||||||
|
"timestamp": "2026-05-18T07:13:58.765627"
|
||||||
|
},
|
||||||
|
"8": {
|
||||||
|
"evidence": "PR readiness 7/7: (1) attribution OK, (2) no new competitors, (3) diff +281/-2, (4) syntax OK, (5) 10 tests passed, (6) code review clean, (7) PR body drafted. Ready for PR creation.",
|
||||||
|
"timestamp": "2026-05-18T07:14:40.080604"
|
||||||
|
},
|
||||||
|
"9": {
|
||||||
|
"evidence": "Phase 10: Self-evolution completed - extracted 5 decisions, 5 pitfalls, knowledge graph updated. Phase 11: No upstream gap discoveries - this PR implements an issue we filed. Phase 12: Plugin self-improvement - dev-workflow plugin performed well, phase_tracker.py correctly blocked pre-push until Phase 9.",
|
||||||
|
"timestamp": "2026-05-18T07:15:20.667105"
|
||||||
|
},
|
||||||
|
"10": {
|
||||||
|
"evidence": "Phase 11: No upstream gap discoveries found. This PR directly implements the feature requested in the issue that we filed.",
|
||||||
|
"timestamp": "2026-05-18T07:15:23.003379"
|
||||||
|
},
|
||||||
|
"11": {
|
||||||
|
"evidence": "Phase 12: dev-workflow plugin performed well this cycle. phase_tracker.py pre-push hook correctly blocked Phase 8 push. Only minor issue: advancing multiple phases at once is not supported — must advance one at a time.",
|
||||||
|
"timestamp": "2026-05-18T07:15:25.464885"
|
||||||
|
},
|
||||||
|
"12": {
|
||||||
|
"evidence": "Phase 13: Cleanup complete. Branch feat/history-gateway-27653 pushed to fork. PR #27655 created at https://github.com/NousResearch/hermes-agent/pull/27655. Switching to main branch.",
|
||||||
|
"timestamp": "2026-05-18T07:15:27.942267"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_updated": "2026-05-18T07:15:30.558391"
|
||||||
|
}
|
||||||
|
|
@ -1109,6 +1109,13 @@ def anthropic_prompt_cache_policy(
|
||||||
model_lower = eff_model.lower()
|
model_lower = eff_model.lower()
|
||||||
provider_lower = eff_provider.lower()
|
provider_lower = eff_provider.lower()
|
||||||
is_claude = "claude" in model_lower
|
is_claude = "claude" in model_lower
|
||||||
|
# Kimi / Moonshot family via OpenRouter: same cache_control wire format
|
||||||
|
# as Claude on OpenRouter (envelope layout). Without this branch
|
||||||
|
# moonshotai/kimi-k2.6 falls through to (False, False), serving ~1%
|
||||||
|
# cache hits on 64K-token prompts and re-billing the full prompt on
|
||||||
|
# every turn. Observed within-turn progression with cache enabled:
|
||||||
|
# 1% → 67% → 84% → 97% (#25970).
|
||||||
|
is_kimi = "kimi" in model_lower or "moonshot" in model_lower
|
||||||
is_openrouter = base_url_host_matches(eff_base_url, "openrouter.ai")
|
is_openrouter = base_url_host_matches(eff_base_url, "openrouter.ai")
|
||||||
# Nous Portal proxies to OpenRouter behind the scenes — identical
|
# Nous Portal proxies to OpenRouter behind the scenes — identical
|
||||||
# OpenAI-wire envelope cache_control semantics. Treat it as an
|
# OpenAI-wire envelope cache_control semantics. Treat it as an
|
||||||
|
|
@ -1122,7 +1129,7 @@ def anthropic_prompt_cache_policy(
|
||||||
|
|
||||||
if is_native_anthropic:
|
if is_native_anthropic:
|
||||||
return True, True
|
return True, True
|
||||||
if (is_openrouter or is_nous_portal) and is_claude:
|
if (is_openrouter or is_nous_portal) and (is_claude or is_kimi):
|
||||||
return True, False
|
return True, False
|
||||||
# Nous Portal Qwen (e.g. qwen3.6-plus) takes the same envelope-layout
|
# Nous Portal Qwen (e.g. qwen3.6-plus) takes the same envelope-layout
|
||||||
# cache_control path as Portal Claude. Portal proxies to OpenRouter
|
# cache_control path as Portal Claude. Portal proxies to OpenRouter
|
||||||
|
|
|
||||||
117
analyzed_issues.json
Normal file
117
analyzed_issues.json
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
{
|
||||||
|
"27198": {
|
||||||
|
"title": "[Bug]: WhatsApp messages stuck at 1 tick — delivery ACK missing in addition to read receipts",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27198",
|
||||||
|
"seen_at": "2026-05-17T01:45:29.080613+00:00"
|
||||||
|
},
|
||||||
|
"27166": {
|
||||||
|
"title": "[Bug] Telegram DM topic response routed to All Messages after session split",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27166",
|
||||||
|
"seen_at": "2026-05-17T01:45:29.080852+00:00"
|
||||||
|
},
|
||||||
|
"27156": {
|
||||||
|
"title": "Session resume can replay prior assistant/tool context instead of answering exact-output prompt",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27156",
|
||||||
|
"seen_at": "2026-05-17T01:45:29.081027+00:00"
|
||||||
|
},
|
||||||
|
"27312": {
|
||||||
|
"title": "Browser tool should prefer packaged agent-browser native binary when npm shim needs unavailable node",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27312",
|
||||||
|
"seen_at": "2026-05-17T07:46:52.354983+00:00"
|
||||||
|
},
|
||||||
|
"27296": {
|
||||||
|
"title": "[Bug] delegate_task ignores delegation.base_url — subagent always inherits parent model",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27296",
|
||||||
|
"seen_at": "2026-05-17T07:46:52.355896+00:00"
|
||||||
|
},
|
||||||
|
"27295": {
|
||||||
|
"title": "[Bug] delegate_task ignores delegation.base_url — subagent always inherits parent model",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27295",
|
||||||
|
"seen_at": "2026-05-17T07:46:52.357442+00:00"
|
||||||
|
},
|
||||||
|
"27294": {
|
||||||
|
"title": "[Bug] delegate_task ignores delegation.base_url — subagent always inherits parent model",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27294",
|
||||||
|
"seen_at": "2026-05-17T07:46:52.358251+00:00"
|
||||||
|
},
|
||||||
|
"27282": {
|
||||||
|
"title": "[--tui] gateway exits mid-turn with stdin EOF (TUI closed the command pipe) — NOT byterover-related",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27282",
|
||||||
|
"seen_at": "2026-05-17T07:46:52.359345+00:00"
|
||||||
|
},
|
||||||
|
"27265": {
|
||||||
|
"title": "Discord long bot-to-bot handoffs can drop unmentioned continuation chunks",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27265",
|
||||||
|
"seen_at": "2026-05-17T07:46:52.360103+00:00"
|
||||||
|
},
|
||||||
|
"27230": {
|
||||||
|
"title": "[Bug]: Telegram gateway only acknowledges tool requests but never delivers final results",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27230",
|
||||||
|
"seen_at": "2026-05-17T07:46:52.361212+00:00"
|
||||||
|
},
|
||||||
|
"27430": {
|
||||||
|
"title": "hermes update fails web UI build when NODE_ENV=production — npm install omits devDependencies",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27430",
|
||||||
|
"seen_at": "2026-05-17T13:48:05.033485+00:00"
|
||||||
|
},
|
||||||
|
"27402": {
|
||||||
|
"title": "NameError: '_pool_may_recover_from_rate_limit' is not defined in conversation_loop.py",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27402",
|
||||||
|
"seen_at": "2026-05-17T13:48:05.071950+00:00"
|
||||||
|
},
|
||||||
|
"27383": {
|
||||||
|
"title": "[Bug]: change working directory for Telegram agent",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27383",
|
||||||
|
"seen_at": "2026-05-17T13:48:05.073121+00:00"
|
||||||
|
},
|
||||||
|
"27352": {
|
||||||
|
"title": "[Bug] Gateway approval wait is not resolved by normal user replies during pending approval",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27352",
|
||||||
|
"seen_at": "2026-05-17T13:48:05.074324+00:00"
|
||||||
|
},
|
||||||
|
"27345": {
|
||||||
|
"title": "[Bug]: API connection Fail with minimax provider in windows",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27345",
|
||||||
|
"seen_at": "2026-05-17T13:48:05.075138+00:00"
|
||||||
|
},
|
||||||
|
"27566": {
|
||||||
|
"title": "Context compression triggers every turn due to rough token estimate inflating last_prompt_tokens",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27566",
|
||||||
|
"seen_at": "2026-05-17T19:48:41.166309+00:00"
|
||||||
|
},
|
||||||
|
"27563": {
|
||||||
|
"title": "Python shutdown deadlock: worker thread blocks Py_FinalizeEx, process hangs forever after exit",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27563",
|
||||||
|
"seen_at": "2026-05-17T19:48:41.167190+00:00"
|
||||||
|
},
|
||||||
|
"27555": {
|
||||||
|
"title": "Bug: vision fallback_chain silently broken — wrong kwargs in _resolve_single_provider",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27555",
|
||||||
|
"seen_at": "2026-05-17T19:48:41.168199+00:00"
|
||||||
|
},
|
||||||
|
"27540": {
|
||||||
|
"title": "[Bug]: AIAgent() got multiple values for keyword argument 'model' — reproduces on second turn of session",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27540",
|
||||||
|
"seen_at": "2026-05-17T19:48:41.168926+00:00"
|
||||||
|
},
|
||||||
|
"27538": {
|
||||||
|
"title": "auxiliary.compression.provider ignored after switching model provider",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27538",
|
||||||
|
"seen_at": "2026-05-17T19:48:41.170045+00:00"
|
||||||
|
},
|
||||||
|
"27529": {
|
||||||
|
"title": "[feishu] Fix markdown table rendering: use post+tag:md instead of force-text workaround",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27529",
|
||||||
|
"seen_at": "2026-05-17T19:48:41.170928+00:00"
|
||||||
|
},
|
||||||
|
"27477": {
|
||||||
|
"title": "[Bug] Feishu 转发聊天记录无法被 Hermes 读取",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27477",
|
||||||
|
"seen_at": "2026-05-17T19:48:41.172039+00:00"
|
||||||
|
},
|
||||||
|
"27469": {
|
||||||
|
"title": "[feishu] Unified fix for markdown rendering: inbound escaping + GFM table → Card 2.0",
|
||||||
|
"url": "https://github.com/NousResearch/hermes-agent/issues/27469",
|
||||||
|
"seen_at": "2026-05-17T19:48:41.173084+00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
445
scripts/phase_tracker.py
Normal file
445
scripts/phase_tracker.py
Normal file
|
|
@ -0,0 +1,445 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Phase Tracker — strict multi-template phase enforcement for upstream contributions.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
phase_tracker.py start <template> [--issue N]
|
||||||
|
phase_tracker.py current # show current phase
|
||||||
|
phase_tracker.py check <N> # exit 0 if on phase N, 1 if not
|
||||||
|
phase_tracker.py advance --evidence "描述" # submit evidence + advance
|
||||||
|
phase_tracker.py skip <N> # BLOCKED
|
||||||
|
phase_tracker.py complete # mark workflow done
|
||||||
|
phase_tracker.py abort # cancel workflow
|
||||||
|
phase_tracker.py phases # list templates + phases
|
||||||
|
|
||||||
|
State file: .dev-workflow/phase-tracker.json
|
||||||
|
|
||||||
|
GUARD RAILS:
|
||||||
|
- `advance` requires --evidence describing what was done (min 20 chars)
|
||||||
|
- Phase 1 (triage) blocks advance if competing PR was found but no decision recorded
|
||||||
|
- All evidence is stored and auditable
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
STATE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".dev-workflow")
|
||||||
|
STATE_FILE = os.path.join(STATE_DIR, "phase-tracker.json")
|
||||||
|
|
||||||
|
# Minimum evidence length to prevent empty/generic submissions
|
||||||
|
MIN_EVIDENCE_LENGTH = 20
|
||||||
|
|
||||||
|
TEMPLATES = {
|
||||||
|
"github-issue": {
|
||||||
|
"total": 13,
|
||||||
|
"PHASES": {
|
||||||
|
1: "triage", 2: "setup", 3: "understand", 4: "security",
|
||||||
|
5: "implement", 6: "test", 7: "review", 8: "preflight",
|
||||||
|
9: "pr", 10: "evolve", 11: "insight-feature-request",
|
||||||
|
12: "plugin-improve", 13: "cleanup",
|
||||||
|
},
|
||||||
|
"PHASE_NAMES": {
|
||||||
|
1: "🔍 Deep Triage — 检查竞争 PR、评分、分类",
|
||||||
|
2: "🌿 Branch Setup — 同步上游、创建分支",
|
||||||
|
3: "📖 Code Understanding — 读代码、追踪执行路径",
|
||||||
|
4: "🔒 Security Baseline — 安全基线扫描",
|
||||||
|
5: "✏️ Implementation — 写代码(小 diff)",
|
||||||
|
6: "🧪 Test Suite — 全量测试 + 新增测试(必须全通过)",
|
||||||
|
7: "👀 Code Review — 多角色静态审查(零 ERROR)",
|
||||||
|
8: "✅ PR Readiness Check — 7 项预提交检查",
|
||||||
|
9: "🚀 Create Pull Request — 提交 + 创建 PR",
|
||||||
|
10: "🧠 Self Evolution — 萃取经验,同步知识图谱",
|
||||||
|
11: "💡 Insight-Driven Feature Request — 发现上游缺口",
|
||||||
|
12: "🔧 Plugin Self-Improvement — 改进插件",
|
||||||
|
13: "🧹 Cleanup & Handoff — 清理分支,交接",
|
||||||
|
},
|
||||||
|
# What must the evidence describe for each phase?
|
||||||
|
"EVIDENCE_HINTS": {
|
||||||
|
1: "e.g. 'issue #N scored 6/7, no competing PR, author=X, labels=Y'",
|
||||||
|
2: "e.g. 'branch fix/xxx created from origin/main, fetched latest'",
|
||||||
|
3: "e.g. 'root cause: func X does Y instead of Z, traced via file A→B→C'",
|
||||||
|
4: "e.g. 'dwf_security_scan quick: 0 findings, text-only change' or 'scan clean'",
|
||||||
|
5: "e.g. 'modified files X, Y — +N/-M lines, syntax check passed'",
|
||||||
|
6: "e.g. 'N tests passed, 0 failed — new tests in test_X.py'",
|
||||||
|
7: "e.g. 'dwf_review_run: 0 ERROR, N WARNING — all reviewed'",
|
||||||
|
8: "e.g. 'attribution OK, no new competitor, diff +N/-M, 7/7 checks pass'",
|
||||||
|
9: "e.g. 'PR #N created: https://github.com/.../pull/N'",
|
||||||
|
10: "e.g. 'extracted 2 patterns: provider-flag-default, conservative-fallback'",
|
||||||
|
11: "e.g. 'no upstream gap found' or 'found gap: file X missing Y'",
|
||||||
|
12: "e.g. 'no plugin changes needed' or 'updated pr-review-patterns.md'",
|
||||||
|
13: "e.g. 'checked out main, branch deleted, state cleaned'",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pr-review-response": {
|
||||||
|
"total": 12,
|
||||||
|
"PHASES": {
|
||||||
|
1: "scan-classify", 2: "simple-replies", 3: "worth-check",
|
||||||
|
4: "respond", 5: "implement-fixes", 6: "test",
|
||||||
|
7: "pre-push", 8: "commit-push", 9: "evolve",
|
||||||
|
10: "insight-feature-request", 11: "plugin-improve", 12: "cleanup",
|
||||||
|
},
|
||||||
|
"PHASE_NAMES": {
|
||||||
|
1: "📋 Scan & Classify — 扫描所有 PR,按路线分类",
|
||||||
|
2: "📬 Simple Replies — 感谢/关闭重复/竞争 PR",
|
||||||
|
3: "💎 Worth Check — 重新评估 PR 是否值得继续",
|
||||||
|
4: "💬 Respond to Reviewers — 回复认可确定改动方向",
|
||||||
|
5: "✏️ Implement Changes — 按 reviewer 要求改代码",
|
||||||
|
6: "🧪 Test Suite — 全量测试(必须通过)",
|
||||||
|
7: "🔍 Pre-Push Re-check — 确认无新竞争/新评论",
|
||||||
|
8: "📤 Commit, Push & Follow-up — 推送+通知 reviewer",
|
||||||
|
9: "🧠 Self Evolution — 萃取经验,同步知识图谱",
|
||||||
|
10: "💡 Insight-Driven Feature Request — 发现上游缺口",
|
||||||
|
11: "🔧 Plugin Self-Improvement — 改进插件",
|
||||||
|
12: "🧹 Cleanup — 清理分支,记录待处理 PR",
|
||||||
|
},
|
||||||
|
"EVIDENCE_HINTS": {
|
||||||
|
1: "e.g. 'scanned N PRs: A=RouteA, B=RouteB, C=RouteC'",
|
||||||
|
2: "e.g. 'closed #N (duplicate), thanked #M (cherry-pick)'",
|
||||||
|
3: "e.g. 'PR #N worth keeping — reviewer raised valid point'",
|
||||||
|
4: "e.g. 'replied to reviewer X on PR #N, agreed to fix Y'",
|
||||||
|
5: "e.g. 'fixed Y per reviewer feedback, +N/-M lines'",
|
||||||
|
6: "e.g. 'N tests passed, 0 failed'",
|
||||||
|
7: "e.g. 'no new comments on PR #N, no competing PRs'",
|
||||||
|
8: "e.g. 'pushed to PR #N, notified reviewer @X'",
|
||||||
|
9: "e.g. 'extracted pattern: reviewer-priority-matrix'",
|
||||||
|
10: "e.g. 'no upstream gap found'",
|
||||||
|
11: "e.g. 'no plugin changes needed'",
|
||||||
|
12: "e.g. 'branch cleaned, PR status recorded'",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve(state):
|
||||||
|
"""Return the template definition dict for the current workflow."""
|
||||||
|
tname = state.get("template", "github-issue")
|
||||||
|
return TEMPLATES.get(tname, TEMPLATES["github-issue"])
|
||||||
|
|
||||||
|
|
||||||
|
def _load():
|
||||||
|
if not os.path.exists(STATE_FILE):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(STATE_FILE) as f:
|
||||||
|
return json.load(f)
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _save(state):
|
||||||
|
os.makedirs(STATE_DIR, exist_ok=True)
|
||||||
|
with open(STATE_FILE, "w") as f:
|
||||||
|
json.dump(state, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _now_iso():
|
||||||
|
return __import__("datetime").datetime.now().isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_start(template, issue=None):
|
||||||
|
existing = _load()
|
||||||
|
if existing is not None and existing.get("state") == "active":
|
||||||
|
print("BLOCKED: A workflow is already active. Complete or abort it first.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if template not in TEMPLATES:
|
||||||
|
print(f"Unknown template: {template}")
|
||||||
|
print(f"Available: {', '.join(TEMPLATES.keys())}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
tpl = TEMPLATES[template]
|
||||||
|
state = {
|
||||||
|
"template": template,
|
||||||
|
"current_phase": 1,
|
||||||
|
"completed_phases": [],
|
||||||
|
"issue_number": issue,
|
||||||
|
"started_at": _now_iso(),
|
||||||
|
"last_advanced_at": None,
|
||||||
|
"state": "active",
|
||||||
|
"phase_evidence": {}, # {phase_number: {"evidence": str, "timestamp": str}}
|
||||||
|
}
|
||||||
|
_save(state)
|
||||||
|
print(f"✅ Started {template} workflow. Phase 1/{tpl['total']}: {tpl['PHASE_NAMES'][1]}")
|
||||||
|
print(f" State: {STATE_FILE}")
|
||||||
|
if template == "github-issue":
|
||||||
|
print("⛔ NO-COMMENT RULE: If a competing PR exists, do NOT comment/review it.")
|
||||||
|
print(" Only interact with PRs you (atyou2happy) authored. Complete early if competing.")
|
||||||
|
hints = tpl.get("EVIDENCE_HINTS", {})
|
||||||
|
print(f"\n 📝 Evidence needed to advance: {hints.get(1, 'describe what you did')}")
|
||||||
|
print(f" Usage: phase_tracker.py advance --evidence \"...\"")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_current():
|
||||||
|
state = _load()
|
||||||
|
if state is None:
|
||||||
|
print("No active workflow.")
|
||||||
|
return
|
||||||
|
|
||||||
|
tpl = _resolve(state)
|
||||||
|
cp = state["current_phase"]
|
||||||
|
done = state["completed_phases"]
|
||||||
|
names = tpl["PHASE_NAMES"]
|
||||||
|
|
||||||
|
print(f"📊 Workflow: {state['template']}")
|
||||||
|
print(f" Issue: {state.get('issue_number', 'N/A')}")
|
||||||
|
print(f" State: {state['state']}")
|
||||||
|
print(f" Current phase: {cp}/{tpl['total']} — {names.get(cp, 'unknown')}")
|
||||||
|
print(f" Completed: {', '.join(str(p) for p in done) if done else 'none yet'}")
|
||||||
|
|
||||||
|
# Show evidence for completed phases
|
||||||
|
evidence = state.get("phase_evidence", {})
|
||||||
|
if evidence:
|
||||||
|
print(f"\n 📋 Evidence log:")
|
||||||
|
for p in sorted(done):
|
||||||
|
ev = evidence.get(str(p), {})
|
||||||
|
text = ev.get("evidence", "?")[:80]
|
||||||
|
print(f" Phase {p}: {text}{'...' if len(ev.get('evidence','')) > 80 else ''}")
|
||||||
|
|
||||||
|
if state["state"] == "active":
|
||||||
|
hints = tpl.get("EVIDENCE_HINTS", {})
|
||||||
|
print(f"\n⚠️ You are on Phase {cp}. Do NOT skip ahead!")
|
||||||
|
print(f" 📝 To advance: phase_tracker.py advance --evidence \"{hints.get(cp, 'describe what you did')}\"")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_check(expected_str):
|
||||||
|
state = _load()
|
||||||
|
if state is None:
|
||||||
|
print("BLOCKED: No active workflow. Call 'phase_tracker.py start <template>' first.")
|
||||||
|
sys.exit(1)
|
||||||
|
if state["state"] != "active":
|
||||||
|
print(f"BLOCKED: Workflow is {state['state']} (not active).")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
expected = int(expected_str)
|
||||||
|
except ValueError:
|
||||||
|
print(f"BLOCKED: Invalid phase number: {expected_str}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
tpl = _resolve(state)
|
||||||
|
names = tpl["PHASE_NAMES"]
|
||||||
|
current = state["current_phase"]
|
||||||
|
|
||||||
|
if current == expected:
|
||||||
|
print(f"✅ Phase {current}/{tpl['total']} — {names.get(current, 'unknown')}")
|
||||||
|
hints = tpl.get("EVIDENCE_HINTS", {})
|
||||||
|
print(f" 📝 To advance: phase_tracker.py advance --evidence \"{hints.get(current, 'describe what you did')}\"")
|
||||||
|
sys.exit(0)
|
||||||
|
elif current < expected:
|
||||||
|
print(f"❌ BLOCKED: Cannot do Phase {expected} yet — you are on Phase {current} ({names.get(current, '')})")
|
||||||
|
print(f" Next expected: Phase {current} — {names.get(current, '')}")
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print(f"⚠️ Phase {expected} is already completed. You are on Phase {current}.")
|
||||||
|
print(f" Current: Phase {current} — {names.get(current, '')}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_advance(evidence=None):
|
||||||
|
state = _load()
|
||||||
|
if state is None:
|
||||||
|
print("BLOCKED: No active workflow.")
|
||||||
|
sys.exit(1)
|
||||||
|
if state["state"] != "active":
|
||||||
|
print(f"BLOCKED: Workflow is {state['state']}.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
tpl = _resolve(state)
|
||||||
|
names = tpl["PHASE_NAMES"]
|
||||||
|
total = tpl["total"]
|
||||||
|
current = state["current_phase"]
|
||||||
|
|
||||||
|
if current >= total:
|
||||||
|
print(f"⚠️ Already at final phase ({current}/{total}). Call 'complete' to finish.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# ── GUARD 1: Evidence required ──────────────────────────────
|
||||||
|
if not evidence or len(evidence.strip()) < 20:
|
||||||
|
hints = tpl.get("EVIDENCE_HINTS", {})
|
||||||
|
print(f"❌ BLOCKED: Evidence required to advance from Phase {current}.")
|
||||||
|
print(f" Phase {current}: {names.get(current, '')}")
|
||||||
|
print(f" Hint: {hints.get(current, 'describe what you did')}")
|
||||||
|
print(f" Usage: phase_tracker.py advance --evidence \"your evidence here (min 20 chars)\"")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# ── GUARD 2: Phase-specific checks ──────────────────────────
|
||||||
|
# Phase 1 triage: if competing PR found, must acknowledge decision
|
||||||
|
if current == 1 and state.get("template") == "github-issue":
|
||||||
|
ev_lower = evidence.lower()
|
||||||
|
has_competitor = "compet" in ev_lower or "duplicate" in ev_lower or "already" in ev_lower
|
||||||
|
if has_competitor and "no-comment" not in ev_lower and "did not comment" not in ev_lower:
|
||||||
|
print("⚠️ WARNING: Competing PR detected but no explicit no-comment declaration.")
|
||||||
|
print(" Add 'no-comment' or 'did not comment' to evidence if you found a competitor.")
|
||||||
|
print(" ⛔ REMINDER: NEVER comment on PRs you did not author.")
|
||||||
|
|
||||||
|
# Record evidence
|
||||||
|
if "phase_evidence" not in state:
|
||||||
|
state["phase_evidence"] = {}
|
||||||
|
state["phase_evidence"][str(current)] = {
|
||||||
|
"evidence": evidence.strip(),
|
||||||
|
"timestamp": _now_iso(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if current not in state["completed_phases"]:
|
||||||
|
state["completed_phases"].append(current)
|
||||||
|
|
||||||
|
next_phase = current + 1
|
||||||
|
state["current_phase"] = next_phase
|
||||||
|
state["last_advanced_at"] = _now_iso()
|
||||||
|
state["last_updated"] = _now_iso()
|
||||||
|
_save(state)
|
||||||
|
|
||||||
|
print(f"✅ Advanced to Phase {next_phase}/{total}: {names.get(next_phase, 'unknown')}")
|
||||||
|
print(f" Evidence recorded: {evidence.strip()[:100]}{'...' if len(evidence.strip()) > 100 else ''}")
|
||||||
|
hints = tpl.get("EVIDENCE_HINTS", {})
|
||||||
|
if next_phase <= total:
|
||||||
|
print(f"\n Next: Phase {next_phase} — {names.get(next_phase, '')}")
|
||||||
|
print(f" 📝 Evidence needed: {hints.get(next_phase, 'describe what you did')}")
|
||||||
|
print(f" Usage: phase_tracker.py advance --evidence \"...\"")
|
||||||
|
if next_phase == total:
|
||||||
|
print(f" 🏁 Final phase! Complete with: phase_tracker.py complete")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_skip(target_str):
|
||||||
|
state = _load()
|
||||||
|
if state is None:
|
||||||
|
print("BLOCKED: No active workflow.")
|
||||||
|
sys.exit(1)
|
||||||
|
try:
|
||||||
|
target = int(target_str)
|
||||||
|
except ValueError:
|
||||||
|
print(f"Invalid phase: {target_str}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
current = state["current_phase"]
|
||||||
|
tpl = _resolve(state)
|
||||||
|
names = tpl["PHASE_NAMES"]
|
||||||
|
print(f"❌ BLOCKED: Skipping disabled. Phase {current} → {target}")
|
||||||
|
print(f" Current: Phase {current} — {names.get(current, '')}")
|
||||||
|
print(f" Use 'advance --evidence \"...\"' to advance properly.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_complete():
|
||||||
|
state = _load()
|
||||||
|
if state is None:
|
||||||
|
print("No active workflow.")
|
||||||
|
return
|
||||||
|
tpl = _resolve(state)
|
||||||
|
total = tpl["total"]
|
||||||
|
cp = state["current_phase"]
|
||||||
|
done = state["completed_phases"]
|
||||||
|
|
||||||
|
# Auto-mark the final phase as completed (advance blocks on the last phase
|
||||||
|
# because there's nowhere to advance to — complete is the only way out).
|
||||||
|
if cp == total and cp not in done:
|
||||||
|
done.append(cp)
|
||||||
|
|
||||||
|
# Check if all phases were completed
|
||||||
|
missing = [p for p in range(1, total + 1) if p not in done]
|
||||||
|
if missing:
|
||||||
|
print(f"⚠️ WARNING: Phases not completed: {missing}")
|
||||||
|
print(f" These phases were skipped without evidence.")
|
||||||
|
evidence = state.get("phase_evidence", {})
|
||||||
|
logged = sorted(evidence.keys())
|
||||||
|
print(f" Evidence logged for phases: {logged if logged else 'none'}")
|
||||||
|
print(f" Call 'complete --force' to override, or go back and do the work.")
|
||||||
|
# Check for --force flag
|
||||||
|
if "--force" not in sys.argv:
|
||||||
|
sys.exit(1)
|
||||||
|
print(" (--force used, proceeding anyway)")
|
||||||
|
|
||||||
|
state["state"] = "completed"
|
||||||
|
if state["current_phase"] not in state["completed_phases"]:
|
||||||
|
state["completed_phases"].append(state["current_phase"])
|
||||||
|
state["last_updated"] = _now_iso()
|
||||||
|
_save(state)
|
||||||
|
print(f"✅ Workflow '{state['template']}' COMPLETED.")
|
||||||
|
print(f" Phases completed: {len(done)}/{total}")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_abort():
|
||||||
|
state = _load()
|
||||||
|
if state is None:
|
||||||
|
print("No active workflow.")
|
||||||
|
return
|
||||||
|
state["state"] = "aborted"
|
||||||
|
state["last_updated"] = _now_iso()
|
||||||
|
_save(state)
|
||||||
|
tpl = _resolve(state)
|
||||||
|
evidence = state.get("phase_evidence", {})
|
||||||
|
print(f"⛔ Workflow '{state['template']}' ABORTED at Phase {state['current_phase']}.")
|
||||||
|
if evidence:
|
||||||
|
print(f" Evidence recorded for: {sorted(evidence.keys())}")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_list_phases():
|
||||||
|
for tname, tpl in TEMPLATES.items():
|
||||||
|
names = tpl["PHASE_NAMES"]
|
||||||
|
hints = tpl.get("EVIDENCE_HINTS", {})
|
||||||
|
print(f"\n📋 {tname} ({tpl['total']} phases)")
|
||||||
|
print("=" * 60)
|
||||||
|
for i in range(1, tpl["total"] + 1):
|
||||||
|
print(f" Phase {i:2d}: {names.get(i, '')}")
|
||||||
|
if hints.get(i):
|
||||||
|
print(f" Evidence: {hints[i]}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help"):
|
||||||
|
print("Usage:")
|
||||||
|
print(" phase_tracker.py start <template> [--issue N] — start new workflow")
|
||||||
|
print(" phase_tracker.py current — show current phase")
|
||||||
|
print(" phase_tracker.py check <N> — verify on expected phase")
|
||||||
|
print(" phase_tracker.py advance --evidence \"text\" — submit evidence + advance")
|
||||||
|
print(" phase_tracker.py skip <N> — BLOCKED")
|
||||||
|
print(" phase_tracker.py complete [--force] — mark workflow done")
|
||||||
|
print(" phase_tracker.py abort — abort workflow")
|
||||||
|
print(" phase_tracker.py phases — list templates + phases")
|
||||||
|
print(f"\nTemplates: {', '.join(TEMPLATES.keys())}")
|
||||||
|
return
|
||||||
|
|
||||||
|
cmd = sys.argv[1]
|
||||||
|
|
||||||
|
if cmd == "start":
|
||||||
|
template = sys.argv[2] if len(sys.argv) > 2 else "github-issue"
|
||||||
|
issue = None
|
||||||
|
if "--issue" in sys.argv:
|
||||||
|
idx = sys.argv.index("--issue")
|
||||||
|
if idx + 1 < len(sys.argv):
|
||||||
|
issue = int(sys.argv[idx + 1])
|
||||||
|
cmd_start(template, issue)
|
||||||
|
elif cmd == "current":
|
||||||
|
cmd_current()
|
||||||
|
elif cmd == "check":
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print("Usage: phase_tracker.py check <phase_number>")
|
||||||
|
sys.exit(1)
|
||||||
|
cmd_check(sys.argv[2])
|
||||||
|
elif cmd == "advance":
|
||||||
|
evidence = None
|
||||||
|
if "--evidence" in sys.argv:
|
||||||
|
idx = sys.argv.index("--evidence")
|
||||||
|
if idx + 1 < len(sys.argv):
|
||||||
|
evidence = sys.argv[idx + 1]
|
||||||
|
cmd_advance(evidence)
|
||||||
|
elif cmd == "skip":
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print("Usage: phase_tracker.py skip <phase_number>")
|
||||||
|
sys.exit(1)
|
||||||
|
cmd_skip(sys.argv[2])
|
||||||
|
elif cmd == "complete":
|
||||||
|
cmd_complete()
|
||||||
|
elif cmd == "abort":
|
||||||
|
cmd_abort()
|
||||||
|
elif cmd == "phases":
|
||||||
|
cmd_list_phases()
|
||||||
|
else:
|
||||||
|
print(f"Unknown command: {cmd}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
76
scripts/phase_watchdog.sh
Executable file
76
scripts/phase_watchdog.sh
Executable file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Phase Tracker Watchdog
|
||||||
|
# Runs via cron to detect stalled upstream contribution workflows.
|
||||||
|
# If a workflow hasn't been updated in STALL_HOURS hours, alerts the user.
|
||||||
|
|
||||||
|
HERMES_AGENT_HOME="${HOME}/.hermes/hermes-agent"
|
||||||
|
TRACKER="$HERMES_AGENT_HOME/.dev-workflow/phase-tracker.json"
|
||||||
|
STALL_HOURS=4
|
||||||
|
|
||||||
|
if [ ! -f "$TRACKER" ]; then
|
||||||
|
exit 0 # No active workflow
|
||||||
|
fi
|
||||||
|
|
||||||
|
STATE=$(python3 -c "
|
||||||
|
import json
|
||||||
|
with open('$TRACKER') as f:
|
||||||
|
d = json.load(f)
|
||||||
|
print(json.dumps({
|
||||||
|
'state': d.get('state', 'unknown'),
|
||||||
|
'phase': d.get('current_phase', 0),
|
||||||
|
'issue': d.get('issue_number', 'N/A'),
|
||||||
|
'updated': d.get('last_updated', ''),
|
||||||
|
'started': d.get('started_at', ''),
|
||||||
|
}))
|
||||||
|
" 2>/dev/null || echo '{}')
|
||||||
|
|
||||||
|
STATE_VAL=$(echo "$STATE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('state',''))" 2>/dev/null || echo "")
|
||||||
|
PHASE=$(echo "$STATE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('phase',0))" 2>/dev/null || echo "0")
|
||||||
|
ISSUE=$(echo "$STATE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('issue','N/A'))" 2>/dev/null || echo "N/A")
|
||||||
|
UPDATED=$(echo "$STATE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('updated',''))" 2>/dev/null || echo "")
|
||||||
|
STARTED=$(echo "$STATE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('started',''))" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [ "$STATE_VAL" != "active" ]; then
|
||||||
|
exit 0 # Completed or aborted
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if stalled
|
||||||
|
if [ -n "$UPDATED" ]; then
|
||||||
|
NOW=$(date +%s)
|
||||||
|
UPDATED_TS=$(date -d "$UPDATED" +%s 2>/dev/null || echo "0")
|
||||||
|
if [ "$UPDATED_TS" -gt 0 ]; then
|
||||||
|
ELAPSED=$(( (NOW - UPDATED_TS) / 3600 ))
|
||||||
|
if [ "$ELAPSED" -ge "$STALL_HOURS" ]; then
|
||||||
|
PHASE_NAME=$(python3 -c "
|
||||||
|
PHASE_NAMES = {
|
||||||
|
1: 'Deep Triage', 2: 'Branch Setup', 3: 'Code Understanding',
|
||||||
|
4: 'Security Baseline', 5: 'Implementation', 6: 'Test Suite',
|
||||||
|
7: 'Code Review', 8: 'PR Readiness Check', 9: 'Create Pull Request',
|
||||||
|
10: 'Self Evolution', 11: 'Insight Feature Request', 12: 'Plugin Improve',
|
||||||
|
13: 'Cleanup',
|
||||||
|
}
|
||||||
|
print(PHASE_NAMES.get($PHASE, 'unknown'))
|
||||||
|
" 2>/dev/null || echo "unknown")
|
||||||
|
|
||||||
|
cat <<WATCHDOG
|
||||||
|
⚠️ Phase Tracker STALLED
|
||||||
|
|
||||||
|
Workflow for issue #${ISSUE} has been stuck at Phase ${PHASE} (${PHASE_NAME}) for ${ELAPSED} hours.
|
||||||
|
|
||||||
|
To continue:
|
||||||
|
cd $HERMES_HOME/hermes-agent
|
||||||
|
python3 scripts/phase_tracker.py current # check current phase
|
||||||
|
python3 scripts/phase_tracker.py advance # advance to next phase
|
||||||
|
|
||||||
|
Or to abort:
|
||||||
|
python3 scripts/phase_tracker.py abort
|
||||||
|
|
||||||
|
Last updated: ${UPDATED}
|
||||||
|
WATCHDOG
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
74
tests/test_kimi_prefix_cache.py
Normal file
74
tests/test_kimi_prefix_cache.py
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
"""Tests for Kimi/Moonshot prompt cache inclusion (issue #25970)."""
|
||||||
|
import pytest
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
|
def _call_cache_policy(model: str, base_url: str, provider: str = "",
|
||||||
|
api_mode: str = "chat_completions"):
|
||||||
|
"""Call _anthropic_prompt_cache_policy without full AIAgent init."""
|
||||||
|
# Import the standalone helper used by the policy
|
||||||
|
from run_agent import AIAgent
|
||||||
|
|
||||||
|
# Create a shell object to call the method on
|
||||||
|
agent = object.__new__(AIAgent)
|
||||||
|
# Set only the attributes the method reads
|
||||||
|
agent.provider = provider
|
||||||
|
agent.base_url = base_url
|
||||||
|
agent.api_mode = api_mode
|
||||||
|
agent.model = model
|
||||||
|
return agent._anthropic_prompt_cache_policy()
|
||||||
|
|
||||||
|
|
||||||
|
class TestKimiPrefixCache:
|
||||||
|
def test_kimi_k26_openrouter(self):
|
||||||
|
result = _call_cache_policy(
|
||||||
|
model="moonshotai/kimi-k2.6",
|
||||||
|
base_url="https://openrouter.ai/api/v1",
|
||||||
|
)
|
||||||
|
assert result == (True, False)
|
||||||
|
|
||||||
|
def test_kimi_k2_openrouter(self):
|
||||||
|
result = _call_cache_policy(
|
||||||
|
model="kimi-k2",
|
||||||
|
base_url="https://openrouter.ai/api/v1",
|
||||||
|
)
|
||||||
|
assert result == (True, False)
|
||||||
|
|
||||||
|
def test_moonshot_v1_openrouter(self):
|
||||||
|
result = _call_cache_policy(
|
||||||
|
model="moonshotai/moonshot-v1-8k",
|
||||||
|
base_url="https://openrouter.ai/api/v1",
|
||||||
|
)
|
||||||
|
assert result == (True, False)
|
||||||
|
|
||||||
|
def test_kimi_non_openrouter_no_caching(self):
|
||||||
|
result = _call_cache_policy(
|
||||||
|
model="moonshotai/kimi-k2.6",
|
||||||
|
base_url="https://api.example.com/v1",
|
||||||
|
)
|
||||||
|
assert result == (False, False)
|
||||||
|
|
||||||
|
def test_claude_openrouter_regression(self):
|
||||||
|
result = _call_cache_policy(
|
||||||
|
model="anthropic/claude-sonnet-4",
|
||||||
|
base_url="https://openrouter.ai/api/v1",
|
||||||
|
)
|
||||||
|
assert result == (True, False)
|
||||||
|
|
||||||
|
def test_kimi_nous_portal(self):
|
||||||
|
result = _call_cache_policy(
|
||||||
|
model="moonshotai/kimi-k2.6",
|
||||||
|
base_url="https://nousresearch.com/api/v1",
|
||||||
|
)
|
||||||
|
assert result == (True, False)
|
||||||
|
|
||||||
|
def test_random_model_no_caching(self):
|
||||||
|
result = _call_cache_policy(
|
||||||
|
model="some-random-model",
|
||||||
|
base_url="https://api.example.com/v1",
|
||||||
|
)
|
||||||
|
assert result == (False, False)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue