@cocorograph/hub-agent 0.7.52 → 0.7.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/claude-stream-bridge.mjs +14 -0
- package/src/main.mjs +40 -3
- package/src/model-capability-report.mjs +118 -0
package/package.json
CHANGED
|
@@ -184,6 +184,20 @@ export function getModelCapability(model) {
|
|
|
184
184
|
return _modelCapabilities.get(model) ?? null
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
/** レジストリを Hub 報告用 [{model_name, supports_effort, supports_xhigh_effort}] へ変換する。
|
|
188
|
+
* 第2段 (2026-07-11): backend の apply_capability_report が期待する形。 */
|
|
189
|
+
export function getReportModels() {
|
|
190
|
+
const out = []
|
|
191
|
+
for (const [name, cap] of _modelCapabilities) {
|
|
192
|
+
out.push({
|
|
193
|
+
model_name: name,
|
|
194
|
+
supports_effort: cap.supportsEffort === true,
|
|
195
|
+
supports_xhigh_effort: cap.supportsXhighEffort === true,
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
return out
|
|
199
|
+
}
|
|
200
|
+
|
|
187
201
|
/** テスト用: レジストリをクリアする。 */
|
|
188
202
|
export function _resetModelCapabilities() {
|
|
189
203
|
_modelCapabilities.clear()
|
package/src/main.mjs
CHANGED
|
@@ -24,7 +24,12 @@ import { extractLastAssistantText, pickContextText } from "./extract-paragraph.m
|
|
|
24
24
|
import { loadPlugins, runHookBroadcast, runHookChain } from "./plugin-loader.mjs"
|
|
25
25
|
import { WsClient } from "./ws-client.mjs"
|
|
26
26
|
import { PtyBridge } from "./pty-bridge.mjs"
|
|
27
|
-
import { ClaudeStreamBridge } from "./claude-stream-bridge.mjs"
|
|
27
|
+
import { ClaudeStreamBridge, getReportModels } from "./claude-stream-bridge.mjs"
|
|
28
|
+
import {
|
|
29
|
+
buildCapabilityReport,
|
|
30
|
+
readCodexReport,
|
|
31
|
+
readSdkVersion,
|
|
32
|
+
} from "./model-capability-report.mjs"
|
|
28
33
|
import { CodexStreamBridge, classifyCodexInputError } from "./codex-stream-bridge.mjs"
|
|
29
34
|
import { UploadManager } from "./claude-upload.mjs"
|
|
30
35
|
import { requestSelfUninstall } from "./service-install.mjs"
|
|
@@ -1059,12 +1064,44 @@ export async function startDaemon({ version, ptyModule, claudeSdk } = {}) {
|
|
|
1059
1064
|
if (claudeBridge) {
|
|
1060
1065
|
;(async () => {
|
|
1061
1066
|
const delaysMs = [0, 3000, 15000]
|
|
1067
|
+
let claudeOk = false
|
|
1062
1068
|
for (const d of delaysMs) {
|
|
1063
1069
|
if (d) await new Promise((r) => setTimeout(r, d))
|
|
1064
1070
|
const n = await claudeBridge.refreshModelCapabilities()
|
|
1065
|
-
if (n)
|
|
1071
|
+
if (n) {
|
|
1072
|
+
claudeOk = true
|
|
1073
|
+
break // 1 件でも載れば成功
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
if (!claudeOk) {
|
|
1077
|
+
logger.warn({}, "model capability を起動フェーズで取得できず (browser effort へフォールバック)")
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// 第2段 (2026-07-11): 取得した能力を Hub へ報告する。backend が is_staff ゲート+
|
|
1081
|
+
// バージョン単調ラチェットでモデルマスタの能力列へ反映する(詳細は
|
|
1082
|
+
// apps/ai_models/capability_sync.py)。claude はレジストリ由来、codex は codex CLI が
|
|
1083
|
+
// model/list をキャッシュした ~/.codex/models_cache.json を読む(案A、ゼロ spawn)。
|
|
1084
|
+
// 送信は fire-and-forget。backend が古い版/非staffなら黙って無視するので害はない。
|
|
1085
|
+
try {
|
|
1086
|
+
const codex = await readCodexReport().catch(() => null)
|
|
1087
|
+
const report = buildCapabilityReport({
|
|
1088
|
+
claudeModels: claudeOk ? getReportModels() : [],
|
|
1089
|
+
sdkVersion: readSdkVersion(),
|
|
1090
|
+
codex,
|
|
1091
|
+
})
|
|
1092
|
+
if (report) {
|
|
1093
|
+
client.send(report)
|
|
1094
|
+
logger.info(
|
|
1095
|
+
{
|
|
1096
|
+
claude: report.claude?.models?.length ?? 0,
|
|
1097
|
+
codex: report.codex?.models?.length ?? 0,
|
|
1098
|
+
},
|
|
1099
|
+
"model capability report を Hub へ送信",
|
|
1100
|
+
)
|
|
1101
|
+
}
|
|
1102
|
+
} catch (err) {
|
|
1103
|
+
logger.warn({ err: err?.message }, "model capability report 送信に失敗")
|
|
1066
1104
|
}
|
|
1067
|
-
logger.warn({}, "model capability を起動フェーズで取得できず (browser effort へフォールバック)")
|
|
1068
1105
|
})().catch((err) =>
|
|
1069
1106
|
logger.warn({ err: err?.message }, "model capability 取得ループが例外終了"),
|
|
1070
1107
|
)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* モデル能力報告の組み立て(第2段, 2026-07-11)。
|
|
3
|
+
*
|
|
4
|
+
* hub-agent が上流(Claude Agent SDK / codex)から取得したモデル能力を、Hub backend の
|
|
5
|
+
* `model.capabilities.report` メッセージ形に整形する。backend は
|
|
6
|
+
* apply_capability_report() でバージョン単調ラチェットにより AIModelConfig の能力列へ反映する。
|
|
7
|
+
*
|
|
8
|
+
* - Claude: SDK `supportedModels()` 由来の能力レジストリ(getReportModels)+ SDK バージョン。
|
|
9
|
+
* - Codex: codex CLI 自身が `model/list` 応答をキャッシュした `~/.codex/models_cache.json` を
|
|
10
|
+
* 読む(案A)。起動ごとに codex app-server を spawn せずに済み、ラチェットに必要な
|
|
11
|
+
* client_version / fetched_at / etag / supported_reasoning_levels を全て持つ。
|
|
12
|
+
* ※ runtime の判定に cache を使うわけではない(それは第1段で直した context% の轍)。
|
|
13
|
+
* ここは「一度きりの報告」の一次データとして codex 自身の materialized view を読むだけ。
|
|
14
|
+
*/
|
|
15
|
+
import { promises as fs, readFileSync } from "node:fs"
|
|
16
|
+
import os from "node:os"
|
|
17
|
+
import path from "node:path"
|
|
18
|
+
import { createRequire } from "node:module"
|
|
19
|
+
|
|
20
|
+
/** インストール済み @anthropic-ai/claude-agent-sdk の version を読む。失敗時 null。
|
|
21
|
+
*
|
|
22
|
+
* SDK は `exports` フィールドで `./package.json` サブパスをブロックするため、
|
|
23
|
+
* `require("@anthropic-ai/claude-agent-sdk/package.json")` は
|
|
24
|
+
* ERR_PACKAGE_PATH_NOT_EXPORTED で失敗する。代わりにメインエントリを解決して
|
|
25
|
+
* そのパッケージルート(= dirname)の package.json を fs で直読みする。 */
|
|
26
|
+
export function readSdkVersion() {
|
|
27
|
+
try {
|
|
28
|
+
const require = createRequire(import.meta.url)
|
|
29
|
+
// "." export → 例: .../@anthropic-ai/claude-agent-sdk/sdk.mjs(パッケージルート直下)
|
|
30
|
+
const entry = require.resolve("@anthropic-ai/claude-agent-sdk")
|
|
31
|
+
// sdk.mjs はパッケージルートにあるため dirname がパッケージルート。
|
|
32
|
+
const pkgPath = path.join(path.dirname(entry), "package.json")
|
|
33
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"))
|
|
34
|
+
return typeof pkg?.version === "string" && pkg.version ? pkg.version : null
|
|
35
|
+
} catch {
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function codexModelsCachePath() {
|
|
41
|
+
const dir = process.env.CODEX_DIR || path.join(os.homedir(), ".codex")
|
|
42
|
+
return process.env.HUB_CODEX_MODELS_CACHE || path.join(dir, "models_cache.json")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* codex の models_cache.json を読み、報告ブロックへ整形する。失敗/未存在は null。
|
|
47
|
+
*
|
|
48
|
+
* `supported_reasoning_levels`([{effort, description}, ...])から effort 対応を導出:
|
|
49
|
+
* - supports_effort = low/medium/high 等が 1 つでもあれば true(reasoning あり)
|
|
50
|
+
* - supports_xhigh_effort = effort に "xhigh" を含むか
|
|
51
|
+
* `visibility === "hide"` のモデルはピッカー非表示だが、能力自体は正しいので報告に含める
|
|
52
|
+
* (backend は「マスタに存在する行」しか触らないため、hide モデルが紛れても実害なし)。
|
|
53
|
+
*/
|
|
54
|
+
export async function readCodexReport(cachePath = codexModelsCachePath()) {
|
|
55
|
+
let text
|
|
56
|
+
try {
|
|
57
|
+
text = await fs.readFile(cachePath, "utf8")
|
|
58
|
+
} catch {
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
let j
|
|
62
|
+
try {
|
|
63
|
+
j = JSON.parse(text)
|
|
64
|
+
} catch {
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
const version = j?.client_version
|
|
68
|
+
if (typeof version !== "string" || !version) return null
|
|
69
|
+
const rawModels = Array.isArray(j?.models) ? j.models : []
|
|
70
|
+
const models = []
|
|
71
|
+
for (const m of rawModels) {
|
|
72
|
+
const name = m?.slug
|
|
73
|
+
if (typeof name !== "string" || !name) continue
|
|
74
|
+
const levels = Array.isArray(m.supported_reasoning_levels)
|
|
75
|
+
? m.supported_reasoning_levels
|
|
76
|
+
: []
|
|
77
|
+
const efforts = levels
|
|
78
|
+
.map((x) => (x && typeof x.effort === "string" ? x.effort : null))
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
models.push({
|
|
81
|
+
model_name: name,
|
|
82
|
+
supports_effort: efforts.length > 0,
|
|
83
|
+
supports_xhigh_effort: efforts.includes("xhigh") || efforts.includes("max"),
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
version,
|
|
88
|
+
fetched_at: typeof j?.fetched_at === "string" ? j.fetched_at : undefined,
|
|
89
|
+
etag: typeof j?.etag === "string" ? j.etag : undefined,
|
|
90
|
+
models,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 報告メッセージを組み立てる。claude / codex は取得できたものだけ含める。
|
|
96
|
+
* どちらも空なら null を返す(送る意味がない)。
|
|
97
|
+
*
|
|
98
|
+
* @param {object} args
|
|
99
|
+
* @param {Array} args.claudeModels getReportModels() の戻り値(能力レジストリ由来)
|
|
100
|
+
* @param {string|null} args.sdkVersion readSdkVersion()
|
|
101
|
+
* @param {object|null} args.codex readCodexReport() の戻り値
|
|
102
|
+
*/
|
|
103
|
+
export function buildCapabilityReport({ claudeModels, sdkVersion, codex }) {
|
|
104
|
+
const msg = { type: "model.capabilities.report" }
|
|
105
|
+
if (sdkVersion && Array.isArray(claudeModels) && claudeModels.length > 0) {
|
|
106
|
+
msg.claude = { version: sdkVersion, models: claudeModels }
|
|
107
|
+
}
|
|
108
|
+
if (codex && codex.version && Array.isArray(codex.models) && codex.models.length > 0) {
|
|
109
|
+
msg.codex = {
|
|
110
|
+
version: codex.version,
|
|
111
|
+
models: codex.models,
|
|
112
|
+
}
|
|
113
|
+
if (codex.fetched_at) msg.codex.fetched_at = codex.fetched_at
|
|
114
|
+
if (codex.etag) msg.codex.etag = codex.etag
|
|
115
|
+
}
|
|
116
|
+
if (!msg.claude && !msg.codex) return null
|
|
117
|
+
return msg
|
|
118
|
+
}
|