dsh-llm-verifier 0.1.13 → 0.1.14
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/LICENSE +22 -22
- package/README.md +301 -301
- package/cordis.patch.yml +4 -4
- package/lib/client.js +176 -14
- package/lib/index.js +418 -43
- package/lib/types/client-i18n.d.ts +27 -0
- package/lib/types/client-i18n.js +18 -0
- package/lib/types/client.d.ts +15 -2
- package/lib/types/client.js +134 -9
- package/lib/types/config.d.ts +4 -0
- package/lib/types/config.js +5 -3
- package/lib/types/index.d.ts +2 -0
- package/lib/types/index.js +185 -4
- package/lib/types/plan-gate.d.ts +14 -0
- package/lib/types/plan-gate.js +70 -0
- package/lib/types/router.d.ts +10 -1
- package/lib/types/router.js +37 -7
- package/lib/types/session.js +25 -10
- package/lib/types/team-gate.d.ts +11 -0
- package/lib/types/team-gate.js +51 -0
- package/package.json +112 -112
- package/src/auto.test.ts +86 -86
- package/src/auto.ts +133 -133
- package/src/cache.ts +115 -115
- package/src/caller.test.ts +34 -34
- package/src/caller.ts +151 -151
- package/src/client-i18n.ts +18 -0
- package/src/client.tsx +385 -213
- package/src/config.ts +189 -183
- package/src/core.test.ts +71 -71
- package/src/core.ts +205 -205
- package/src/engine.test.ts +136 -136
- package/src/engine.ts +140 -140
- package/src/images.ts +33 -33
- package/src/index.ts +416 -247
- package/src/parity.test.ts +85 -85
- package/src/plan-gate.test.ts +56 -0
- package/src/plan-gate.ts +77 -0
- package/src/router.test.ts +107 -89
- package/src/router.ts +349 -309
- package/src/session.test.ts +53 -36
- package/src/session.ts +117 -103
- package/src/statistics.test.ts +80 -80
- package/src/statistics.ts +328 -328
- package/src/team-gate.test.ts +56 -0
- package/src/team-gate.ts +63 -0
- package/src/top-logprobs.test.ts +84 -84
- package/src/top-logprobs.ts +153 -153
- package/src/topic-storage.test.ts +31 -31
- package/src/topic-storage.ts +42 -42
package/lib/types/client.d.ts
CHANGED
|
@@ -44,7 +44,8 @@ interface VerifierSettingsProps {
|
|
|
44
44
|
remote: VerifierRemote;
|
|
45
45
|
}
|
|
46
46
|
interface StatisticsPageProps {
|
|
47
|
-
sessionId
|
|
47
|
+
sessionId?: string;
|
|
48
|
+
isGlobal?: boolean;
|
|
48
49
|
rpc: {
|
|
49
50
|
call(channel: string, endpoint: string, payload: unknown, signal?: AbortSignal): Promise<{
|
|
50
51
|
ok: boolean;
|
|
@@ -56,7 +57,19 @@ interface StatisticsPageProps {
|
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
59
|
export declare function VerifierSettings({ remote }: VerifierSettingsProps): import("react").JSX.Element;
|
|
59
|
-
export declare function StatisticsPage({ sessionId, rpc }: StatisticsPageProps): import("react").JSX.Element;
|
|
60
|
+
export declare function StatisticsPage({ sessionId, rpc, isGlobal }: StatisticsPageProps): import("react").JSX.Element;
|
|
61
|
+
export declare function VerifierSidebarIcon({ size, active }: {
|
|
62
|
+
size: number;
|
|
63
|
+
active?: boolean;
|
|
64
|
+
}): import("react").JSX.Element;
|
|
65
|
+
export declare function GlobalVerifierDashboard({ rpc }: {
|
|
66
|
+
rpc: any;
|
|
67
|
+
}): import("react").JSX.Element;
|
|
68
|
+
export declare function RightSidebarVerifierPanel({ rpc, sessionId }: {
|
|
69
|
+
rpc: any;
|
|
70
|
+
sessionId?: string;
|
|
71
|
+
}): import("react").JSX.Element;
|
|
72
|
+
export declare function RightSidebarVerifierTitle(): import("react").JSX.Element;
|
|
60
73
|
export declare const inject: string[];
|
|
61
74
|
export declare function apply(ctx: ClientContext): void;
|
|
62
75
|
//# sourceMappingURL=client.d.ts.map
|
package/lib/types/client.js
CHANGED
|
@@ -73,7 +73,7 @@ export function VerifierSettings({ remote }) {
|
|
|
73
73
|
if (!loaded || !draft)
|
|
74
74
|
return _jsxs("div", { style: shell, children: [_jsx("h2", { style: settingsHeading, children: t['settings.title'] }), _jsx("p", { style: settingsIntro, children: error ?? t['settings.loading'] }), error && _jsx("div", { children: _jsx(Button, { variant: "outline", onClick: () => void load(), children: t['settings.retry'] }) })] });
|
|
75
75
|
const numeric = (key, min = 0) => _jsx(Input, { style: { width: '100%', height: 36, borderRadius: 8 }, type: "number", min: min, value: String(draft[key]), onChange: e => patch(key, Number(e.target.value)) });
|
|
76
|
-
return _jsxs("div", { style: shell, children: [_jsx("h2", { style: settingsHeading, children: t['settings.title'] }), _jsx("p", { style: settingsIntro, children: t['settings.intro'] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.tools'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.enabled.title'], help: draft.enabled ? t['field.enabled.helpOn'] : t['field.enabled.helpOff'] }), _jsx("button", { type: "button", role: "switch", "aria-checked": draft.enabled, "aria-label": t['field.enabled.title'], onClick: () => patch('enabled', !draft.enabled), style: toggleStyle(draft.enabled), children: _jsx("span", { style: toggleThumbStyle(draft.enabled) }) })] }), !draft.enabled && _jsx("p", { style: { margin: '8px 0 0', fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-warn-label)' }, children: t['field.enabled.warnDisabled'] })] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.autoVerify'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMode.title'], help: t['field.autoVerifyMode.help'] }), _jsxs("select", { style: selectStyle, value: draft.autoVerifyMode, onChange: e => patch('autoVerifyMode', e.target.value), children: [_jsx("option", { value: "manual", children: t['field.autoVerifyMode.manual'] }), _jsx("option", { value: "smart", children: t['field.autoVerifyMode.smart'] }), _jsx("option", { value: "strict", children: t['field.autoVerifyMode.strict'] })] })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteSemantic.title'], help: t['field.autoRouteSemantic.help'] }), _jsx("button", { type: "button", role: "switch", "aria-checked": draft.autoRouteSemantic, "aria-label": t['field.autoRouteSemantic.title'], onClick: () => patch('autoRouteSemantic', !draft.autoRouteSemantic), style: toggleStyle(draft.autoRouteSemantic), children: _jsx("span", { style: toggleThumbStyle(draft.autoRouteSemantic) }) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMinConfidence.title'], help: t['field.autoRouteMinConfidence.help'] }), numeric('autoRouteMinConfidence', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxCandidates.title'], help: t['field.autoRouteMaxCandidates.help'] }), numeric('autoRouteMaxCandidates', 3)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxPerTask.title'], help: t['field.autoRouteMaxPerTask.help'] }), numeric('autoRouteMaxPerTask', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxPerSession.title'], help: t['field.autoRouteMaxPerSession.help'] }), numeric('autoRouteMaxPerSession', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoTrackCompletionThreshold.title'], help: t['field.autoTrackCompletionThreshold.help'] }), numeric('autoTrackCompletionThreshold', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxItemChars.title'], help: t['field.autoRouteMaxItemChars.help'] }), numeric('autoRouteMaxItemChars', 100)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxInputChars.title'], help: t['field.autoRouteMaxInputChars.help'] }), numeric('autoRouteMaxInputChars', 1000)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoMaxModelCallsPerTask.title'], help: t['field.autoMaxModelCallsPerTask.help'] }), numeric('autoMaxModelCallsPerTask', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoMaxModelCallsPerSession.title'], help: t['field.autoMaxModelCallsPerSession.help'] }), numeric('autoMaxModelCallsPerSession', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyThreshold.title'], help: t['field.autoVerifyThreshold.help'] }), numeric('autoVerifyThreshold', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyRepeats.title'], help: t['field.autoVerifyRepeats.help'] }), numeric('autoVerifyRepeats', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMinToolCalls.title'], help: t['field.autoVerifyMinToolCalls.help'] }), numeric('autoVerifyMinToolCalls', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMaxChars.title'], help: t['field.autoVerifyMaxChars.help'] }), numeric('autoVerifyMaxChars', 1000)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMaxPerTask.title'], help: t['field.autoVerifyMaxPerTask.help'] }), numeric('autoVerifyMaxPerTask', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMaxPerSession.title'], help: t['field.autoVerifyMaxPerSession.help'] }), numeric('autoVerifyMaxPerSession', 1)] }), draft.autoVerifyMode !== 'manual' && _jsx("p", { style: { margin: '8px 0 0', fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-warn-label)' }, children: t['field.autoVerify.warnNotice'] })] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.model'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.provider.title'], help: t['field.provider.help'] }), _jsx("select", { style: selectStyle, value: draft.provider, onChange: e => { const provider = e.target.value; const first = loaded.groups.find(g => g.id === provider)?.models[0]; setDraft({ ...draft, provider, ...(first ? { model: first.id, reasoningEffort: first.reasoning?.defaultEffort } : {}) }); }, children: loaded.groups.map(g => _jsxs("option", { value: g.id, children: [g.name, " \u00B7 ", g.id] }, g.id)) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.model.title'], help: t['field.model.help'] }), _jsx("select", { style: selectStyle, value: draft.model, onChange: e => { const model = e.target.value; const found = models.find(m => m.id === model); setDraft({ ...draft, model, ...(found?.reasoning?.defaultEffort ? { reasoningEffort: found.reasoning.defaultEffort } : { reasoningEffort: undefined }) }); }, children: models.map(m => _jsxs("option", { value: m.id, children: [m.name, " \u00B7 ", m.id] }, m.id)) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.reasoningEffort.title'], help: t['field.reasoningEffort.help'] }), _jsxs("select", { style: selectStyle, value: draft.reasoningEffort ?? '', onChange: e => patch('reasoningEffort', e.target.value || undefined), children: [_jsx("option", { value: "", children: t['field.reasoningEffort.default'] }), efforts.map(e => _jsx("option", { value: e.id, children: e.name }, e.id))] })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.maxTokens.title'], help: t['field.maxTokens.help'] }), numeric('maxTokens', 1)] })] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.execution'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.maxConcurrency.title'], help: t['field.maxConcurrency.help'] }), numeric('maxConcurrency', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.maxRetries.title'], help: t['field.maxRetries.help'] }), numeric('maxRetries', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.timeoutMs.title'], help: t['field.timeoutMs.help'] }), numeric('timeoutMs', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.cacheMaxEntries.title'], help: t['field.cacheMaxEntries.help'] }), numeric('cacheMaxEntries', 1)] })] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.cost'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.estimatedInputUsdPerMillion.title'], help: t['field.estimatedInputUsdPerMillion.help'] }), numeric('estimatedInputUsdPerMillion', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.estimatedOutputUsdPerMillion.title'], help: t['field.estimatedOutputUsdPerMillion.help'] }), numeric('estimatedOutputUsdPerMillion', 0)] })] }), loaded.failures.length > 0 && _jsxs("div", { style: { padding: '10px 12px', borderRadius: 8, background: 'var(--dsw-alias-state-warn-bg)', color: 'var(--dsw-alias-state-warn-label)', fontSize: 12, lineHeight: '18px' }, children: [_jsx("div", { style: { fontWeight: 500, marginBottom: 3 }, children: t['settings.catalogFailures'] }), loaded.failures.map(x => _jsx("div", { children: x }, x))] }), error && _jsx("p", { style: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' }, children: error }), saved && _jsx("p", { style: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-success-primary)' }, children: t['settings.saved'] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, paddingTop: 4 }, children: [_jsx(Button, { variant: "outline", disabled: busy, onClick: () => void load(), children: t['settings.reload'] }), _jsx(Button, { variant: "primary", disabled: busy || !loaded.writable, onClick: () => void save(), children: busy ? t['settings.saving'] : t['settings.save'] })] })] });
|
|
76
|
+
return _jsxs("div", { style: shell, children: [_jsx("h2", { style: settingsHeading, children: t['settings.title'] }), _jsx("p", { style: settingsIntro, children: t['settings.intro'] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.tools'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.enabled.title'], help: draft.enabled ? t['field.enabled.helpOn'] : t['field.enabled.helpOff'] }), _jsx("button", { type: "button", role: "switch", "aria-checked": draft.enabled, "aria-label": t['field.enabled.title'], onClick: () => patch('enabled', !draft.enabled), style: toggleStyle(draft.enabled), children: _jsx("span", { style: toggleThumbStyle(draft.enabled) }) })] }), !draft.enabled && _jsx("p", { style: { margin: '8px 0 0', fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-warn-label)' }, children: t['field.enabled.warnDisabled'] })] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.autoVerify'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMode.title'], help: t['field.autoVerifyMode.help'] }), _jsxs("select", { style: selectStyle, value: draft.autoVerifyMode, onChange: e => patch('autoVerifyMode', e.target.value), children: [_jsx("option", { value: "manual", children: t['field.autoVerifyMode.manual'] }), _jsx("option", { value: "smart", children: t['field.autoVerifyMode.smart'] }), _jsx("option", { value: "strict", children: t['field.autoVerifyMode.strict'] })] })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteSemantic.title'], help: t['field.autoRouteSemantic.help'] }), _jsx("button", { type: "button", role: "switch", "aria-checked": draft.autoRouteSemantic, "aria-label": t['field.autoRouteSemantic.title'], onClick: () => patch('autoRouteSemantic', !draft.autoRouteSemantic), style: toggleStyle(draft.autoRouteSemantic), children: _jsx("span", { style: toggleThumbStyle(draft.autoRouteSemantic) }) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyTeamTasks.title'], help: t['field.autoVerifyTeamTasks.help'] }), _jsx("button", { type: "button", role: "switch", "aria-checked": draft.autoVerifyTeamTasks ?? true, "aria-label": t['field.autoVerifyTeamTasks.title'], onClick: () => patch('autoVerifyTeamTasks', !(draft.autoVerifyTeamTasks ?? true)), style: toggleStyle(draft.autoVerifyTeamTasks ?? true), children: _jsx("span", { style: toggleThumbStyle(draft.autoVerifyTeamTasks ?? true) }) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyPlanMode.title'], help: t['field.autoVerifyPlanMode.help'] }), _jsx("button", { type: "button", role: "switch", "aria-checked": draft.autoVerifyPlanMode ?? true, "aria-label": t['field.autoVerifyPlanMode.title'], onClick: () => patch('autoVerifyPlanMode', !(draft.autoVerifyPlanMode ?? true)), style: toggleStyle(draft.autoVerifyPlanMode ?? true), children: _jsx("span", { style: toggleThumbStyle(draft.autoVerifyPlanMode ?? true) }) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMinConfidence.title'], help: t['field.autoRouteMinConfidence.help'] }), numeric('autoRouteMinConfidence', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxCandidates.title'], help: t['field.autoRouteMaxCandidates.help'] }), numeric('autoRouteMaxCandidates', 3)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxPerTask.title'], help: t['field.autoRouteMaxPerTask.help'] }), numeric('autoRouteMaxPerTask', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxPerSession.title'], help: t['field.autoRouteMaxPerSession.help'] }), numeric('autoRouteMaxPerSession', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoTrackCompletionThreshold.title'], help: t['field.autoTrackCompletionThreshold.help'] }), numeric('autoTrackCompletionThreshold', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxItemChars.title'], help: t['field.autoRouteMaxItemChars.help'] }), numeric('autoRouteMaxItemChars', 100)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxInputChars.title'], help: t['field.autoRouteMaxInputChars.help'] }), numeric('autoRouteMaxInputChars', 1000)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoMaxModelCallsPerTask.title'], help: t['field.autoMaxModelCallsPerTask.help'] }), numeric('autoMaxModelCallsPerTask', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoMaxModelCallsPerSession.title'], help: t['field.autoMaxModelCallsPerSession.help'] }), numeric('autoMaxModelCallsPerSession', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyThreshold.title'], help: t['field.autoVerifyThreshold.help'] }), numeric('autoVerifyThreshold', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyRepeats.title'], help: t['field.autoVerifyRepeats.help'] }), numeric('autoVerifyRepeats', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMinToolCalls.title'], help: t['field.autoVerifyMinToolCalls.help'] }), numeric('autoVerifyMinToolCalls', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMaxChars.title'], help: t['field.autoVerifyMaxChars.help'] }), numeric('autoVerifyMaxChars', 1000)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMaxPerTask.title'], help: t['field.autoVerifyMaxPerTask.help'] }), numeric('autoVerifyMaxPerTask', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMaxPerSession.title'], help: t['field.autoVerifyMaxPerSession.help'] }), numeric('autoVerifyMaxPerSession', 1)] }), draft.autoVerifyMode !== 'manual' && _jsx("p", { style: { margin: '8px 0 0', fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-warn-label)' }, children: t['field.autoVerify.warnNotice'] })] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.model'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.provider.title'], help: t['field.provider.help'] }), _jsx("select", { style: selectStyle, value: draft.provider, onChange: e => { const provider = e.target.value; const first = loaded.groups.find(g => g.id === provider)?.models[0]; setDraft({ ...draft, provider, ...(first ? { model: first.id, reasoningEffort: first.reasoning?.defaultEffort } : {}) }); }, children: loaded.groups.map(g => _jsxs("option", { value: g.id, children: [g.name, " \u00B7 ", g.id] }, g.id)) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.model.title'], help: t['field.model.help'] }), _jsx("select", { style: selectStyle, value: draft.model, onChange: e => { const model = e.target.value; const found = models.find(m => m.id === model); setDraft({ ...draft, model, ...(found?.reasoning?.defaultEffort ? { reasoningEffort: found.reasoning.defaultEffort } : { reasoningEffort: undefined }) }); }, children: models.map(m => _jsxs("option", { value: m.id, children: [m.name, " \u00B7 ", m.id] }, m.id)) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.reasoningEffort.title'], help: t['field.reasoningEffort.help'] }), _jsxs("select", { style: selectStyle, value: draft.reasoningEffort ?? '', onChange: e => patch('reasoningEffort', e.target.value || undefined), children: [_jsx("option", { value: "", children: t['field.reasoningEffort.default'] }), efforts.map(e => _jsx("option", { value: e.id, children: e.name }, e.id))] })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.maxTokens.title'], help: t['field.maxTokens.help'] }), numeric('maxTokens', 1)] })] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.execution'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.maxConcurrency.title'], help: t['field.maxConcurrency.help'] }), numeric('maxConcurrency', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.maxRetries.title'], help: t['field.maxRetries.help'] }), numeric('maxRetries', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.timeoutMs.title'], help: t['field.timeoutMs.help'] }), numeric('timeoutMs', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.cacheMaxEntries.title'], help: t['field.cacheMaxEntries.help'] }), numeric('cacheMaxEntries', 1)] })] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.cost'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.estimatedInputUsdPerMillion.title'], help: t['field.estimatedInputUsdPerMillion.help'] }), numeric('estimatedInputUsdPerMillion', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.estimatedOutputUsdPerMillion.title'], help: t['field.estimatedOutputUsdPerMillion.help'] }), numeric('estimatedOutputUsdPerMillion', 0)] })] }), loaded.failures.length > 0 && _jsxs("div", { style: { padding: '10px 12px', borderRadius: 8, background: 'var(--dsw-alias-state-warn-bg)', color: 'var(--dsw-alias-state-warn-label)', fontSize: 12, lineHeight: '18px' }, children: [_jsx("div", { style: { fontWeight: 500, marginBottom: 3 }, children: t['settings.catalogFailures'] }), loaded.failures.map(x => _jsx("div", { children: x }, x))] }), error && _jsx("p", { style: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' }, children: error }), saved && _jsx("p", { style: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-success-primary)' }, children: t['settings.saved'] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, paddingTop: 4 }, children: [_jsx(Button, { variant: "outline", disabled: busy, onClick: () => void load(), children: t['settings.reload'] }), _jsx(Button, { variant: "primary", disabled: busy || !loaded.writable, onClick: () => void save(), children: busy ? t['settings.saving'] : t['settings.save'] })] })] });
|
|
77
77
|
}
|
|
78
78
|
function Metric({ label, value, note, accent }) {
|
|
79
79
|
return _jsxs("div", { style: { ...dashboardCard, padding: '16px 18px', minWidth: 0 }, children: [_jsx("div", { style: muted, children: label }), _jsx("div", { style: { fontSize: 25, fontWeight: 750, lineHeight: 1.2, margin: '7px 0 5px', color: accent }, children: value }), _jsx("div", { style: { ...muted, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: note })] });
|
|
@@ -96,23 +96,93 @@ function TrendChart({ daily, days, lang }) {
|
|
|
96
96
|
const barWidth = Math.max(3, Math.min(18, innerW / Math.max(rows.length, 1) * .55));
|
|
97
97
|
return _jsxs("div", { style: { width: '100%', overflowX: 'auto' }, children: [_jsxs("svg", { viewBox: `0 0 ${width} ${height}`, style: { display: 'block', width: '100%', minWidth: 620, height: 'auto' }, "aria-label": t['chart.ariaLabel'], children: [[0, .25, .5, .75, 1].map(ratio => _jsxs("g", { children: [_jsx("line", { x1: pad.l, x2: width - pad.r, y1: pad.t + innerH * ratio, y2: pad.t + innerH * ratio, stroke: "rgba(148,163,184,.16)" }), _jsx("text", { x: pad.l - 8, y: pad.t + innerH * ratio + 4, textAnchor: "end", fontSize: "10", fill: "var(--dsw-text-secondary)", children: Math.round(max * (1 - ratio)) })] }, ratio)), rows.map((row, index) => _jsx("rect", { x: x(index) - barWidth / 2, y: y(row.invocations), width: barWidth, height: pad.t + innerH - y(row.invocations), rx: "2", fill: "#4f8cff", opacity: ".82" }, row.date)), _jsx("polyline", { points: points, fill: "none", stroke: "#5ed7e8", strokeWidth: "2.4", strokeLinejoin: "round", strokeLinecap: "round" }), rows.map((row, index) => index % step === 0 || index === rows.length - 1 ? _jsxs("text", { x: x(index), y: height - 14, textAnchor: "middle", fontSize: "10", fill: "var(--dsw-text-secondary)", children: [Number(row.date.slice(5, 7)), "/", Number(row.date.slice(8, 10))] }, row.date) : null)] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'center', gap: 18, ...muted }, children: [_jsxs("span", { children: [_jsx("i", { style: { display: 'inline-block', width: 8, height: 8, borderRadius: 2, background: '#4f8cff', marginRight: 6 } }), t['chart.legendToolCalls']] }), _jsxs("span", { children: [_jsx("i", { style: { display: 'inline-block', width: 14, height: 2, background: '#5ed7e8', marginRight: 6, verticalAlign: 'middle' } }), t['chart.legendModelCalls']] })] })] });
|
|
98
98
|
}
|
|
99
|
-
export function StatisticsPage({ sessionId, rpc }) {
|
|
99
|
+
export function StatisticsPage({ sessionId, rpc, isGlobal }) {
|
|
100
100
|
const lang = useLanguage();
|
|
101
101
|
const t = dictionaries[lang];
|
|
102
102
|
const labels = toolLabels[lang];
|
|
103
103
|
const [days, setDays] = useState(30);
|
|
104
|
-
const [sessionOnly, setSessionOnly] = useState(
|
|
104
|
+
const [sessionOnly, setSessionOnly] = useState(!isGlobal && Boolean(sessionId));
|
|
105
105
|
const [data, setData] = useState(null);
|
|
106
106
|
const [loading, setLoading] = useState(true);
|
|
107
107
|
const [error, setError] = useState(null);
|
|
108
108
|
const [refresh, setRefresh] = useState(0);
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
setError(
|
|
113
|
-
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
const controller = new AbortController();
|
|
111
|
+
setLoading(true);
|
|
112
|
+
setError(null);
|
|
113
|
+
const effectiveSessionId = sessionOnly && sessionId ? String(sessionId) : undefined;
|
|
114
|
+
const queryPayload = {
|
|
115
|
+
fromMs: startOfRange(days),
|
|
116
|
+
toMs: endOfToday(),
|
|
117
|
+
timezoneOffsetMinutes: new Date().getTimezoneOffset(),
|
|
118
|
+
recentLimit: 50,
|
|
119
|
+
...(effectiveSessionId ? { sessionId: effectiveSessionId } : {}),
|
|
120
|
+
};
|
|
121
|
+
const fetchOverview = async () => {
|
|
122
|
+
if (rpc && typeof rpc.call === 'function') {
|
|
123
|
+
try {
|
|
124
|
+
const result = await rpc.call('/api', 'llm-verifier/statistics', queryPayload, controller.signal);
|
|
125
|
+
if (result && result.ok) {
|
|
126
|
+
return result.value;
|
|
127
|
+
}
|
|
128
|
+
if (result && result.ok === false) {
|
|
129
|
+
throw new Error(result.error?.message ?? t['stats.requestFailed']);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (rpcError) {
|
|
133
|
+
if (controller.signal.aborted)
|
|
134
|
+
throw rpcError;
|
|
135
|
+
console.warn('[llm-verifier] rpc.call failed, trying fetch fallback:', rpcError);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const response = await fetch('/api/llm-verifier/statistics', {
|
|
139
|
+
method: 'POST',
|
|
140
|
+
headers: { 'content-type': 'application/json' },
|
|
141
|
+
body: JSON.stringify(queryPayload),
|
|
142
|
+
signal: controller.signal,
|
|
143
|
+
});
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText || t['stats.requestFailed']}`);
|
|
146
|
+
}
|
|
147
|
+
const data = await response.json();
|
|
148
|
+
if (data && data.ok === true) {
|
|
149
|
+
return data.value;
|
|
150
|
+
}
|
|
151
|
+
if (data && data.type === 'server-response' && data.result?.ok === true) {
|
|
152
|
+
return data.result.value;
|
|
153
|
+
}
|
|
154
|
+
throw new Error(data?.error?.message ?? data?.result?.error?.message ?? t['stats.requestFailed']);
|
|
155
|
+
};
|
|
156
|
+
void fetchOverview()
|
|
157
|
+
.then(overview => {
|
|
158
|
+
setData(overview);
|
|
159
|
+
})
|
|
160
|
+
.catch((cause) => {
|
|
161
|
+
if (!controller.signal.aborted)
|
|
162
|
+
setError(message(cause));
|
|
163
|
+
})
|
|
164
|
+
.finally(() => {
|
|
165
|
+
if (!controller.signal.aborted)
|
|
166
|
+
setLoading(false);
|
|
167
|
+
});
|
|
168
|
+
return () => controller.abort();
|
|
169
|
+
}, [days, sessionOnly, sessionId, refresh, rpc, t]);
|
|
114
170
|
const totals = data?.totals;
|
|
115
|
-
return _jsx("main", { style: { height: '100%', overflow: 'auto', boxSizing: 'border-box', padding: '22px clamp(16px, 3vw, 38px) 48px', color: 'var(--dsw-text-primary)', background: 'radial-gradient(circle at 10% 0%, rgba(115,77,255,.09), transparent 32%), radial-gradient(circle at 100% 8%, rgba(47,197,201,.07), transparent 28%)' }, children: _jsxs("div", { style: { maxWidth: 1180, margin: '0 auto', display: 'flex', flexDirection: 'column', gap: 16 }, children: [_jsxs("header", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 16, flexWrap: 'wrap' }, children: [_jsxs("div", { children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 10 }, children: [_jsx("span", { style: { display: 'grid', placeItems: 'center', width: 34, height: 34, borderRadius: 10, background: 'rgba(79,140,255,.14)', color: '#6da0ff' }, children: _jsx(IconDataOutline16, { size: 18 }) }), _jsx("h2", { style: { margin: 0, fontSize: 23 }, children: t['stats.pageTitle'] })] }), _jsx("p", { style: { margin: '7px 0 0 44px', ...muted }, children: tFormat(t['stats.updatedAt'], { time: data ? dateTime(data.generatedAt, lang) : '--' }) })] }), _jsxs("div", { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }, children: [_jsx("div", { style: { display: 'flex', padding: 3, borderRadius: 10, background: 'var(--dsw-surface-sunken)', border: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12))' }, children: [7, 30, 90].map(value => _jsx("button", { onClick: () => setDays(value), style: { border: 0, borderRadius: 7, padding: '6px 10px', cursor: 'pointer', color: days === value ? '#fff' : 'var(--dsw-text-secondary)', background: days === value ? '#3f68d8' : 'transparent' }, children: tFormat(t['stats.daysUnit'], { days: value }) }, value)) }), _jsx("button", { onClick: () => setSessionOnly(value => !value), style: { border: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.15))', borderRadius: 9, padding: '7px 11px', cursor: 'pointer', color: 'var(--dsw-text-primary)', background: sessionOnly ? 'rgba(79,140,255,.18)' : 'var(--dsw-surface-sunken)' }, children: sessionOnly ? t['stats.currentSession'] : t['stats.allSessions'] }), _jsx("button", { title: t['stats.refresh'], onClick: () => setRefresh(value => value + 1), style: { display: 'grid', placeItems: 'center', width: 34, height: 34, borderRadius: 9, border: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.15))', color: 'var(--dsw-text-primary)', background: 'var(--dsw-surface-sunken)', cursor: 'pointer' }, children: _jsx(IconRefreshOutline16, { size: 16 }) })] })] }), error && _jsxs("div", { style: { ...dashboardCard, padding: 18, borderColor: 'var(--dsw-danger, #e85858)', color: 'var(--dsw-danger, #e85858)' }, children: [error, _jsx("div", { style: { ...muted, marginTop: 6 }, children: t['stats.hostRestartHint'] })] }), loading && !data ? _jsx("div", { style: { ...dashboardCard, padding: 32, textAlign: 'center', ...muted }, children: t['stats.loading'] }) : _jsxs(_Fragment, { children: [_jsxs("section", { style: { ...dashboardCard, padding: '22px 24px', display: 'grid', gridTemplateColumns: 'minmax(220px,1.4fr) minmax(240px,1fr)', gap: 24, alignItems: 'center' }, children: [_jsxs("div", { children: [_jsx("div", { style: muted, children: tFormat(t['stats.costSummary'], { days }) }), _jsx("div", { style: { fontSize: 38, fontWeight: 780, letterSpacing: '-.03em', margin: '5px 0' }, children: money(totals?.estimatedCostUsd ?? 0) }), _jsx("div", { style: muted, children: tFormat(t['stats.callsSummary'], { invocations: compact(totals?.invocations ?? 0, lang), calls: compact(totals?.calls ?? 0, lang) }) })] }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr auto', gap: '9px 16px', fontSize: 13 }, children: [_jsx("span", { style: muted, children: t['stats.successCalls'] }), _jsxs("strong", { children: [compact(totals?.successes ?? 0, lang), " ", _jsxs("small", { style: { color: '#77d49b' }, children: ["\u25B2 ", ((totals?.successRate ?? 0) * 100).toFixed(1), "%"] })] }), _jsx("span", { style: muted, children: t['stats.failedCalls'] }), _jsx("strong", { children: compact(totals?.failures ?? 0, lang) }), _jsx("span", { style: muted, children: t['stats.avgDuration'] }), _jsx("strong", { children: duration(totals?.averageDurationMs ?? 0) })] })] }), _jsxs("section", { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(180px,1fr))', gap: 12 }, children: [_jsx(Metric, { label: t['metric.cacheHitRate'], value: ((totals?.cacheHitRate ?? 0) * 100).toFixed(1) + '%', note: tFormat(t['metric.cacheHitNote'], { hits: compact(totals?.cacheHits ?? 0, lang), total: compact((totals?.cacheHits ?? 0) + (totals?.cacheMisses ?? 0), lang) }), accent: "#b7dd64" }), _jsx(Metric, { label: t['metric.tokens'], value: compact(totals?.tokens ?? 0, lang), note: tFormat(t['metric.tokensNote'], { input: compact((totals?.inputTokens ?? 0) + (totals?.cachedInputTokens ?? 0), lang), output: compact(totals?.outputTokens ?? 0, lang) }) }), _jsx(Metric, { label: t['metric.avgModelCalls'], value: (totals?.invocations ?? 0) > 0 ? ((totals?.calls ?? 0) / (totals?.invocations ?? 1)).toFixed(1) : '0', note: tFormat(t['metric.avgModelCallsNote'], { attempts: compact(totals?.attempts ?? 0, lang), retries: compact(totals?.retries ?? 0, lang) }) }), _jsx(Metric, { label: t['metric.scoringMode'], value: compact(totals?.topLogprobScores ?? 0, lang), note: tFormat(t['metric.scoringModeNote'], { explicit: compact(totals?.explicitTagScores ?? 0, lang) }) })] }), _jsxs("section", { style: { ...dashboardCard, padding: '18px 20px 16px' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }, children: [_jsx("strong", { children: t['chart.title'] }), _jsx("span", { style: muted, children: sessionOnly ? t['stats.currentSession'] : t['stats.allSessions'] })] }), _jsx(TrendChart, { daily: data?.daily ?? [], days: days, lang: lang })] }), _jsxs("section", { style: { ...dashboardCard, padding: '18px 18px 8px', overflow: 'hidden' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', margin: '0 2px 12px' }, children: [_jsx("strong", { children: t['table.title'] }), _jsx("span", { style: muted, children: tFormat(t['table.toolCount'], { count: data?.tools.length ?? 0 }) })] }), _jsx("div", { style: { overflowX: 'auto' }, children: _jsxs("table", { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13, minWidth: 760 }, children: [_jsx("thead", { children: _jsx("tr", { style: { textAlign: 'left', color: 'var(--dsw-text-secondary)', background: 'var(--dsw-surface-sunken)' }, children: [t['table.colTool'], t['table.colInvocations'], t['table.colSuccessRate'], t['table.colAvgDuration'], t['table.colModelCalls'], t['table.colTokens'], t['table.colCacheHits'], t['table.colEstimatedCost']].map(value => _jsx("th", { style: { padding: '10px 12px', fontWeight: 500 }, children: value }, value)) }) }), _jsxs("tbody", { children: [(data?.tools ?? []).map(tool => _jsxs("tr", { style: { borderTop: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.1))' }, children: [_jsxs("td", { style: { padding: '13px 12px' }, children: [_jsx("span", { style: { display: 'inline-block', width: 8, height: 8, borderRadius: '50%', background: toolColors[tool.toolName] ?? '#8691a8', marginRight: 8 } }), _jsx("strong", { children: labels[tool.toolName] ?? tool.toolName }), _jsx("div", { style: { ...muted, margin: '3px 0 0 16px' }, children: tool.toolName })] }), _jsx("td", { style: { padding: '13px 12px' }, children: compact(tool.invocations, lang) }), _jsxs("td", { style: { padding: '13px 12px', color: tool.successRate >= .9 ? '#77d49b' : tool.successRate >= .7 ? '#e3bd63' : '#ed7777' }, children: [(tool.successRate * 100).toFixed(1), "%"] }), _jsx("td", { style: { padding: '13px 12px' }, children: duration(tool.averageDurationMs) }), _jsx("td", { style: { padding: '13px 12px' }, children: compact(tool.calls, lang) }), _jsx("td", { style: { padding: '13px 12px' }, children: compact(tool.tokens, lang) }), _jsxs("td", { style: { padding: '13px 12px' }, children: [tool.cacheHits, "/", tool.cacheHits + tool.cacheMisses] }), _jsx("td", { style: { padding: '13px 12px' }, children: money(tool.estimatedCostUsd) })] }, tool.toolName)), (data?.tools.length ?? 0) === 0 && _jsx("tr", { children: _jsx("td", { colSpan: 8, style: { padding: 28, textAlign: 'center', ...muted }, children: t['table.empty'] }) })] })] }) })] }), _jsxs("section", { style: { display: 'grid', gridTemplateColumns: 'minmax(0,1fr) minmax(290px,.45fr)', gap: 16, alignItems: 'start' }, children: [_jsxs("div", { style: { ...dashboardCard, padding: '18px 18px 8px', overflow: 'hidden' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', margin: '0 2px 12px' }, children: [_jsx("strong", { children: t['recent.title'] }), _jsx("span", { style: muted, children: t['recent.maxCount'] })] }), _jsxs("div", { style: { maxHeight: 360, overflow: 'auto' }, children: [(data?.recent ?? []).map(item => _jsxs("div", { style: { display: 'grid', gridTemplateColumns: 'minmax(170px,1fr) auto', gap: 12, padding: '11px 8px', borderTop: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.1))' }, children: [_jsxs("div", { children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx("span", { style: { width: 7, height: 7, borderRadius: '50%', background: item.success ? '#59c985' : '#e76565' } }), _jsx("strong", { style: { fontSize: 13 }, children: labels[item.toolName] ?? item.toolName }), _jsxs("span", { style: muted, children: [item.provider, "/", item.model] })] }), !item.success && _jsx("div", { title: item.errorMessage, style: { margin: '5px 0 0 15px', fontSize: 11, color: '#e76565', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: item.errorMessage ?? item.errorName })] }), _jsxs("div", { style: { textAlign: 'right' }, children: [_jsx("div", { style: { fontSize: 12 }, children: duration(item.durationMs) }), _jsx("div", { style: { ...muted, marginTop: 3 }, children: dateTime(item.startedAt, lang) })] })] }, item.id)), (data?.recent.length ?? 0) === 0 && _jsx("div", { style: { padding: 24, textAlign: 'center', ...muted }, children: t['recent.empty'] })] })] }), _jsxs("div", { style: { ...dashboardCard, padding: '18px' }, children: [_jsx("strong", { children: t['models.title'] }), _jsxs("div", { style: { marginTop: 10, display: 'flex', flexDirection: 'column', gap: 10 }, children: [(data?.models ?? []).map(model => _jsxs("div", { style: { padding: '11px 12px', borderRadius: 10, background: 'var(--dsw-surface-sunken)' }, children: [_jsx("div", { style: { fontWeight: 650, fontSize: 13, overflow: 'hidden', textOverflow: 'ellipsis' }, children: model.model }), _jsx("div", { style: { ...muted, marginTop: 3 }, children: model.provider }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', marginTop: 9, fontSize: 12 }, children: [_jsx("span", { children: tFormat(t['models.calls'], { calls: compact(model.calls, lang) }) }), _jsx("span", { children: tFormat(t['models.tokens'], { tokens: compact(model.tokens, lang) }) }), _jsx("strong", { children: money(model.estimatedCostUsd) })] })] }, model.provider + '\0' + model.model)), (data?.models.length ?? 0) === 0 && _jsx("div", { style: muted, children: t['models.empty'] })] })] })] })] })] }) });
|
|
171
|
+
return _jsx("main", { style: { height: '100%', overflow: 'auto', boxSizing: 'border-box', padding: '22px clamp(16px, 3vw, 38px) 48px', color: 'var(--dsw-text-primary)', background: 'radial-gradient(circle at 10% 0%, rgba(115,77,255,.09), transparent 32%), radial-gradient(circle at 100% 8%, rgba(47,197,201,.07), transparent 28%)' }, children: _jsxs("div", { style: { maxWidth: 1180, margin: '0 auto', display: 'flex', flexDirection: 'column', gap: 16 }, children: [_jsxs("header", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 16, flexWrap: 'wrap' }, children: [_jsxs("div", { children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 10 }, children: [_jsx("span", { style: { display: 'grid', placeItems: 'center', width: 34, height: 34, borderRadius: 10, background: 'rgba(79,140,255,.14)', color: '#6da0ff' }, children: _jsx(IconDataOutline16, { size: 18 }) }), _jsx("h2", { style: { margin: 0, fontSize: 23 }, children: isGlobal ? t['global.panelTitle'] : t['stats.pageTitle'] })] }), _jsx("p", { style: { margin: '7px 0 0 44px', ...muted }, children: isGlobal ? t['global.panelIntro'] : tFormat(t['stats.updatedAt'], { time: data ? dateTime(data.generatedAt, lang) : '--' }) })] }), _jsxs("div", { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }, children: [_jsx("div", { style: { display: 'flex', padding: 3, borderRadius: 10, background: 'var(--dsw-surface-sunken)', border: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12))' }, children: [7, 30, 90].map(value => _jsx("button", { onClick: () => setDays(value), style: { border: 0, borderRadius: 7, padding: '6px 10px', cursor: 'pointer', color: days === value ? '#fff' : 'var(--dsw-text-secondary)', background: days === value ? '#3f68d8' : 'transparent' }, children: tFormat(t['stats.daysUnit'], { days: value }) }, value)) }), Boolean(sessionId) && _jsx("button", { onClick: () => setSessionOnly(value => !value), style: { border: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.15))', borderRadius: 9, padding: '7px 11px', cursor: 'pointer', color: 'var(--dsw-text-primary)', background: sessionOnly ? 'rgba(79,140,255,.18)' : 'var(--dsw-surface-sunken)' }, children: sessionOnly ? t['stats.currentSession'] : t['stats.allSessions'] }), _jsx("button", { title: t['stats.refresh'], onClick: () => setRefresh(value => value + 1), style: { display: 'grid', placeItems: 'center', width: 34, height: 34, borderRadius: 9, border: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.15))', color: 'var(--dsw-text-primary)', background: 'var(--dsw-surface-sunken)', cursor: 'pointer' }, children: _jsx(IconRefreshOutline16, { size: 16 }) })] })] }), error && _jsxs("div", { style: { ...dashboardCard, padding: 18, borderColor: 'var(--dsw-danger, #e85858)', color: 'var(--dsw-danger, #e85858)' }, children: [error, _jsx("div", { style: { ...muted, marginTop: 6 }, children: t['stats.hostRestartHint'] })] }), loading && !data ? _jsx("div", { style: { ...dashboardCard, padding: 32, textAlign: 'center', ...muted }, children: t['stats.loading'] }) : _jsxs(_Fragment, { children: [_jsxs("section", { style: { ...dashboardCard, padding: '22px 24px', display: 'grid', gridTemplateColumns: 'minmax(220px,1.4fr) minmax(240px,1fr)', gap: 24, alignItems: 'center' }, children: [_jsxs("div", { children: [_jsx("div", { style: muted, children: tFormat(t['stats.costSummary'], { days }) }), _jsx("div", { style: { fontSize: 38, fontWeight: 780, letterSpacing: '-.03em', margin: '5px 0' }, children: money(totals?.estimatedCostUsd ?? 0) }), _jsx("div", { style: muted, children: tFormat(t['stats.callsSummary'], { invocations: compact(totals?.invocations ?? 0, lang), calls: compact(totals?.calls ?? 0, lang) }) })] }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr auto', gap: '9px 16px', fontSize: 13 }, children: [_jsx("span", { style: muted, children: t['stats.successCalls'] }), _jsxs("strong", { children: [compact(totals?.successes ?? 0, lang), " ", _jsxs("small", { style: { color: '#77d49b' }, children: ["\u25B2 ", ((totals?.successRate ?? 0) * 100).toFixed(1), "%"] })] }), _jsx("span", { style: muted, children: t['stats.failedCalls'] }), _jsx("strong", { children: compact(totals?.failures ?? 0, lang) }), _jsx("span", { style: muted, children: t['stats.avgDuration'] }), _jsx("strong", { children: duration(totals?.averageDurationMs ?? 0) })] })] }), _jsxs("section", { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(180px,1fr))', gap: 12 }, children: [_jsx(Metric, { label: t['metric.cacheHitRate'], value: ((totals?.cacheHitRate ?? 0) * 100).toFixed(1) + '%', note: tFormat(t['metric.cacheHitNote'], { hits: compact(totals?.cacheHits ?? 0, lang), total: compact((totals?.cacheHits ?? 0) + (totals?.cacheMisses ?? 0), lang) }), accent: "#b7dd64" }), _jsx(Metric, { label: t['metric.tokens'], value: compact(totals?.tokens ?? 0, lang), note: tFormat(t['metric.tokensNote'], { input: compact((totals?.inputTokens ?? 0) + (totals?.cachedInputTokens ?? 0), lang), output: compact(totals?.outputTokens ?? 0, lang) }) }), _jsx(Metric, { label: t['metric.avgModelCalls'], value: (totals?.invocations ?? 0) > 0 ? ((totals?.calls ?? 0) / (totals?.invocations ?? 1)).toFixed(1) : '0', note: tFormat(t['metric.avgModelCallsNote'], { attempts: compact(totals?.attempts ?? 0, lang), retries: compact(totals?.retries ?? 0, lang) }) }), _jsx(Metric, { label: t['metric.scoringMode'], value: compact(totals?.topLogprobScores ?? 0, lang), note: tFormat(t['metric.scoringModeNote'], { explicit: compact(totals?.explicitTagScores ?? 0, lang) }) })] }), _jsxs("section", { style: { ...dashboardCard, padding: '18px 20px 16px' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }, children: [_jsx("strong", { children: t['chart.title'] }), _jsx("span", { style: muted, children: sessionOnly ? t['stats.currentSession'] : t['stats.allSessions'] })] }), _jsx(TrendChart, { daily: data?.daily ?? [], days: days, lang: lang })] }), _jsxs("section", { style: { ...dashboardCard, padding: '18px 18px 8px', overflow: 'hidden' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', margin: '0 2px 12px' }, children: [_jsx("strong", { children: t['table.title'] }), _jsx("span", { style: muted, children: tFormat(t['table.toolCount'], { count: data?.tools.length ?? 0 }) })] }), _jsx("div", { style: { overflowX: 'auto' }, children: _jsxs("table", { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13, minWidth: 760 }, children: [_jsx("thead", { children: _jsx("tr", { style: { textAlign: 'left', color: 'var(--dsw-text-secondary)', background: 'var(--dsw-surface-sunken)' }, children: [t['table.colTool'], t['table.colInvocations'], t['table.colSuccessRate'], t['table.colAvgDuration'], t['table.colModelCalls'], t['table.colTokens'], t['table.colCacheHits'], t['table.colEstimatedCost']].map(value => _jsx("th", { style: { padding: '10px 12px', fontWeight: 500 }, children: value }, value)) }) }), _jsxs("tbody", { children: [(data?.tools ?? []).map(tool => _jsxs("tr", { style: { borderTop: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.1))' }, children: [_jsxs("td", { style: { padding: '13px 12px' }, children: [_jsx("span", { style: { display: 'inline-block', width: 8, height: 8, borderRadius: '50%', background: toolColors[tool.toolName] ?? '#8691a8', marginRight: 8 } }), _jsx("strong", { children: labels[tool.toolName] ?? tool.toolName }), _jsx("div", { style: { ...muted, margin: '3px 0 0 16px' }, children: tool.toolName })] }), _jsx("td", { style: { padding: '13px 12px' }, children: compact(tool.invocations, lang) }), _jsxs("td", { style: { padding: '13px 12px', color: tool.successRate >= .9 ? '#77d49b' : tool.successRate >= .7 ? '#e3bd63' : '#ed7777' }, children: [(tool.successRate * 100).toFixed(1), "%"] }), _jsx("td", { style: { padding: '13px 12px' }, children: duration(tool.averageDurationMs) }), _jsx("td", { style: { padding: '13px 12px' }, children: compact(tool.calls, lang) }), _jsx("td", { style: { padding: '13px 12px' }, children: compact(tool.tokens, lang) }), _jsxs("td", { style: { padding: '13px 12px' }, children: [tool.cacheHits, "/", tool.cacheHits + tool.cacheMisses] }), _jsx("td", { style: { padding: '13px 12px' }, children: money(tool.estimatedCostUsd) })] }, tool.toolName)), (data?.tools.length ?? 0) === 0 && _jsx("tr", { children: _jsx("td", { colSpan: 8, style: { padding: 28, textAlign: 'center', ...muted }, children: t['table.empty'] }) })] })] }) })] }), _jsxs("section", { style: { display: 'grid', gridTemplateColumns: 'minmax(0,1fr) minmax(290px,.45fr)', gap: 16, alignItems: 'start' }, children: [_jsxs("div", { style: { ...dashboardCard, padding: '18px 18px 8px', overflow: 'hidden' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', margin: '0 2px 12px' }, children: [_jsx("strong", { children: t['recent.title'] }), _jsx("span", { style: muted, children: t['recent.maxCount'] })] }), _jsxs("div", { style: { maxHeight: 360, overflow: 'auto' }, children: [(data?.recent ?? []).map(item => _jsxs("div", { style: { display: 'grid', gridTemplateColumns: 'minmax(170px,1fr) auto', gap: 12, padding: '11px 8px', borderTop: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.1))' }, children: [_jsxs("div", { children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx("span", { style: { width: 7, height: 7, borderRadius: '50%', background: item.success ? '#59c985' : '#e76565' } }), _jsx("strong", { style: { fontSize: 13 }, children: labels[item.toolName] ?? item.toolName }), _jsxs("span", { style: muted, children: [item.provider, "/", item.model] })] }), !item.success && _jsx("div", { title: item.errorMessage, style: { margin: '5px 0 0 15px', fontSize: 11, color: '#e76565', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: item.errorMessage ?? item.errorName })] }), _jsxs("div", { style: { textAlign: 'right' }, children: [_jsx("div", { style: { fontSize: 12 }, children: duration(item.durationMs) }), _jsx("div", { style: { ...muted, marginTop: 3 }, children: dateTime(item.startedAt, lang) })] })] }, item.id)), (data?.recent.length ?? 0) === 0 && _jsx("div", { style: { padding: 24, textAlign: 'center', ...muted }, children: t['recent.empty'] })] })] }), _jsxs("div", { style: { ...dashboardCard, padding: '18px' }, children: [_jsx("strong", { children: t['models.title'] }), _jsxs("div", { style: { marginTop: 10, display: 'flex', flexDirection: 'column', gap: 10 }, children: [(data?.models ?? []).map(model => _jsxs("div", { style: { padding: '11px 12px', borderRadius: 10, background: 'var(--dsw-surface-sunken)' }, children: [_jsx("div", { style: { fontWeight: 650, fontSize: 13, overflow: 'hidden', textOverflow: 'ellipsis' }, children: model.model }), _jsx("div", { style: { ...muted, marginTop: 3 }, children: model.provider }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', marginTop: 9, fontSize: 12 }, children: [_jsx("span", { children: tFormat(t['models.calls'], { calls: compact(model.calls, lang) }) }), _jsx("span", { children: tFormat(t['models.tokens'], { tokens: compact(model.tokens, lang) }) }), _jsx("strong", { children: money(model.estimatedCostUsd) })] })] }, model.provider + '\0' + model.model)), (data?.models.length ?? 0) === 0 && _jsx("div", { style: muted, children: t['models.empty'] })] })] })] })] })] }) });
|
|
172
|
+
}
|
|
173
|
+
export function VerifierSidebarIcon({ size, active }) {
|
|
174
|
+
return _jsx("span", { style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: size, height: size, color: active ? 'var(--dsw-alias-brand-primary, #4f8cff)' : 'currentColor' }, children: _jsx(IconDataOutline16, { size: Math.min(18, size) }) });
|
|
175
|
+
}
|
|
176
|
+
export function GlobalVerifierDashboard({ rpc }) {
|
|
177
|
+
return _jsx(StatisticsPage, { rpc: rpc, isGlobal: true });
|
|
178
|
+
}
|
|
179
|
+
export function RightSidebarVerifierPanel({ rpc, sessionId }) {
|
|
180
|
+
return (_jsx("div", { style: { height: '100%', overflow: 'hidden', display: 'flex', flexDirection: 'column' }, children: _jsx(StatisticsPage, { rpc: rpc, sessionId: sessionId }) }));
|
|
181
|
+
}
|
|
182
|
+
export function RightSidebarVerifierTitle() {
|
|
183
|
+
const lang = useLanguage();
|
|
184
|
+
const t = dictionaries[lang];
|
|
185
|
+
return _jsx("span", { style: { fontSize: 13, fontWeight: 500 }, children: t['slot.statistics'] });
|
|
116
186
|
}
|
|
117
187
|
export const inject = ['slots', 'connection', 'remote', 'remote.session', 'remote.settings'];
|
|
118
188
|
export function apply(ctx) {
|
|
@@ -132,5 +202,60 @@ export function apply(ctx) {
|
|
|
132
202
|
label: detectLanguage() === 'zh' ? zh['slot.statistics'] : en['slot.statistics'],
|
|
133
203
|
inject: () => ({ rpc: connection.rpc }),
|
|
134
204
|
}, StatisticsPage));
|
|
205
|
+
ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({
|
|
206
|
+
name: 'sidebar.panellist',
|
|
207
|
+
id: 'llm-verifier',
|
|
208
|
+
order: 40,
|
|
209
|
+
label: detectLanguage() === 'zh' ? zh['slot.globalDashboard'] : en['slot.globalDashboard'],
|
|
210
|
+
}, VerifierSidebarIcon));
|
|
211
|
+
ctx.slots.inject('main', () => ctx.slots.register({
|
|
212
|
+
name: 'main',
|
|
213
|
+
key: 'llm-verifier',
|
|
214
|
+
inject: () => ({ rpc: connection.rpc }),
|
|
215
|
+
}, GlobalVerifierDashboard));
|
|
216
|
+
const VERIFIER_TAB_KIND = 'llm-verifier';
|
|
217
|
+
const VERIFIER_TAB_ID = 'dsh-llm-verifier';
|
|
218
|
+
const registerRightSidebar = (tabs) => {
|
|
219
|
+
if (!tabs || typeof tabs.register !== 'function')
|
|
220
|
+
return;
|
|
221
|
+
try {
|
|
222
|
+
tabs.register({
|
|
223
|
+
id: VERIFIER_TAB_ID,
|
|
224
|
+
kind: VERIFIER_TAB_KIND,
|
|
225
|
+
priority: 'extension',
|
|
226
|
+
title: () => (detectLanguage() === 'zh' ? zh['slot.statistics'] : en['slot.statistics']),
|
|
227
|
+
guide: [{
|
|
228
|
+
order: 45,
|
|
229
|
+
title: () => (detectLanguage() === 'zh' ? zh['guide.verifier.title'] : en['guide.verifier.title']),
|
|
230
|
+
description: () => (detectLanguage() === 'zh' ? zh['guide.verifier.desc'] : en['guide.verifier.desc']),
|
|
231
|
+
icon: VerifierSidebarIcon,
|
|
232
|
+
}],
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
catch { }
|
|
236
|
+
};
|
|
237
|
+
const existingTabs = typeof ctx.get === 'function' ? ctx.get('sidebarRightTabs') : undefined;
|
|
238
|
+
if (existingTabs) {
|
|
239
|
+
registerRightSidebar(existingTabs);
|
|
240
|
+
}
|
|
241
|
+
if (typeof ctx.inject === 'function') {
|
|
242
|
+
try {
|
|
243
|
+
ctx.inject(['sidebarRightTabs'], (subCtx) => {
|
|
244
|
+
const subTabs = typeof subCtx?.get === 'function' ? subCtx.get('sidebarRightTabs') : subCtx?.sidebarRightTabs;
|
|
245
|
+
if (subTabs)
|
|
246
|
+
registerRightSidebar(subTabs);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
catch { }
|
|
250
|
+
}
|
|
251
|
+
ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({
|
|
252
|
+
name: 'sidebar.right.pane.tab',
|
|
253
|
+
key: VERIFIER_TAB_ID,
|
|
254
|
+
inject: () => ({ rpc: connection.rpc }),
|
|
255
|
+
}, RightSidebarVerifierPanel));
|
|
256
|
+
ctx.slots.inject('sidebar.right.pane.tab.title', () => ctx.slots.register({
|
|
257
|
+
name: 'sidebar.right.pane.tab.title',
|
|
258
|
+
key: VERIFIER_TAB_ID,
|
|
259
|
+
}, RightSidebarVerifierTitle));
|
|
135
260
|
}
|
|
136
261
|
//# sourceMappingURL=client.js.map
|
package/lib/types/config.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface Config {
|
|
|
21
21
|
autoRouteMaxInputChars?: number;
|
|
22
22
|
autoMaxModelCallsPerTask?: number;
|
|
23
23
|
autoMaxModelCallsPerSession?: number;
|
|
24
|
+
autoVerifyTeamTasks?: boolean;
|
|
25
|
+
autoVerifyPlanMode?: boolean;
|
|
24
26
|
provider?: string;
|
|
25
27
|
model?: string;
|
|
26
28
|
reasoningEffort?: string;
|
|
@@ -53,6 +55,8 @@ export interface ResolvedConfig {
|
|
|
53
55
|
autoRouteMaxInputChars: number;
|
|
54
56
|
autoMaxModelCallsPerTask: number;
|
|
55
57
|
autoMaxModelCallsPerSession: number;
|
|
58
|
+
autoVerifyTeamTasks: boolean;
|
|
59
|
+
autoVerifyPlanMode: boolean;
|
|
56
60
|
provider: string;
|
|
57
61
|
model: string;
|
|
58
62
|
reasoningEffort?: string;
|
package/lib/types/config.js
CHANGED
|
@@ -19,8 +19,10 @@ export const Config = z.object({
|
|
|
19
19
|
autoRouteMaxInputChars: z.number().step(1).min(1000).default(60000),
|
|
20
20
|
autoMaxModelCallsPerTask: z.number().step(1).min(1).default(48),
|
|
21
21
|
autoMaxModelCallsPerSession: z.number().step(1).min(1).default(160),
|
|
22
|
+
autoVerifyTeamTasks: z.boolean().default(true),
|
|
23
|
+
autoVerifyPlanMode: z.boolean().default(true),
|
|
22
24
|
provider: z.string().default('deepseek-official'),
|
|
23
|
-
model: z.string().default('deepseek-
|
|
25
|
+
model: z.string().default('deepseek-flash'),
|
|
24
26
|
reasoningEffort: z.string(),
|
|
25
27
|
maxTokens: z.number().step(1).min(1).default(32768),
|
|
26
28
|
timeoutMs: z.number().step(1).min(1).default(300000),
|
|
@@ -34,7 +36,7 @@ export const Config = z.object({
|
|
|
34
36
|
});
|
|
35
37
|
export function resolveConfig(config = {}) {
|
|
36
38
|
const provider = (config.provider ?? 'deepseek-official').trim();
|
|
37
|
-
const model = (config.model ?? 'deepseek-
|
|
39
|
+
const model = (config.model ?? 'deepseek-flash').trim();
|
|
38
40
|
if (!provider)
|
|
39
41
|
throw new Error('llm-verifier: provider must be non-empty');
|
|
40
42
|
if (!model)
|
|
@@ -92,7 +94,7 @@ export function resolveConfig(config = {}) {
|
|
|
92
94
|
if (![estimatedInputUsdPerMillion, estimatedOutputUsdPerMillion].every(value => Number.isFinite(value) && value >= 0))
|
|
93
95
|
throw new Error('llm-verifier: estimated token prices must be finite non-negative numbers');
|
|
94
96
|
const reasoningEffort = config.reasoningEffort?.trim();
|
|
95
|
-
return { enabled: config.enabled ?? true, autoVerifyMode, autoVerifyThreshold, autoRouteSemantic: config.autoRouteSemantic ?? true, autoRouteMinConfidence, autoTrackCompletionThreshold, provider, model, ...(reasoningEffort ? { reasoningEffort } : {}), maxRetries, cacheDir, estimatedInputUsdPerMillion, estimatedOutputUsdPerMillion, ...values };
|
|
97
|
+
return { enabled: config.enabled ?? true, autoVerifyMode, autoVerifyThreshold, autoRouteSemantic: config.autoRouteSemantic ?? true, autoRouteMinConfidence, autoTrackCompletionThreshold, autoVerifyTeamTasks: config.autoVerifyTeamTasks ?? true, autoVerifyPlanMode: config.autoVerifyPlanMode ?? true, provider, model, ...(reasoningEffort ? { reasoningEffort } : {}), maxRetries, cacheDir, estimatedInputUsdPerMillion, estimatedOutputUsdPerMillion, ...values };
|
|
96
98
|
}
|
|
97
99
|
export function installVerifierSettings(ctx, entry, onChange) {
|
|
98
100
|
let source = () => entry;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export * from './statistics.ts';
|
|
|
10
10
|
export * from './topic-storage.ts';
|
|
11
11
|
export * from './auto.ts';
|
|
12
12
|
export * from './router.ts';
|
|
13
|
+
export * from './plan-gate.ts';
|
|
14
|
+
export * from './team-gate.ts';
|
|
13
15
|
export { callVerifier, RequestLimiter, type VerifierClientConfig, type VerifierImage, type UsageStats, type VerifierCompletion } from './caller.ts';
|
|
14
16
|
export declare function apply(ctx: Context, config?: Config): void;
|
|
15
17
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/types/index.js
CHANGED
|
@@ -9,6 +9,8 @@ import { loadVerifierImages } from "./images.js";
|
|
|
9
9
|
import { extractSession } from "./session.js";
|
|
10
10
|
import { AutoVerificationBudget, analyzeAutoTask, automaticFeedback } from "./auto.js";
|
|
11
11
|
import { AutoVerifierRouter, analyzeStructuredRoute, boundDecision, buildSemanticRoutePrompt, parseSemanticRoute, semanticDecision, semanticRouteHint } from "./router.js";
|
|
12
|
+
import { detectPlanExit, buildPlanPreReviewPrompt, parsePlanReviewVerdict } from "./plan-gate.js";
|
|
13
|
+
import { inspectTeamTasks, buildTeamTaskVerificationPrompt } from "./team-gate.js";
|
|
12
14
|
import { StatisticsStore, emptyRunStats, errorDetails, mergeStatisticsOverviews, resolveStatisticsFile } from "./statistics.js";
|
|
13
15
|
import { resolveTopicDataDir } from "./topic-storage.js";
|
|
14
16
|
export const name = 'llm-verifier';
|
|
@@ -21,6 +23,8 @@ export * from "./statistics.js";
|
|
|
21
23
|
export * from "./topic-storage.js";
|
|
22
24
|
export * from "./auto.js";
|
|
23
25
|
export * from "./router.js";
|
|
26
|
+
export * from "./plan-gate.js";
|
|
27
|
+
export * from "./team-gate.js";
|
|
24
28
|
export { callVerifier, RequestLimiter } from "./caller.js";
|
|
25
29
|
const criterionSchema = { type: 'object', additionalProperties: false, properties: { id: { type: 'string', required: true }, name: { type: 'string', required: true }, description: { type: 'string', required: true } } };
|
|
26
30
|
const statsSchema = { type: 'object', additionalProperties: false, properties: { calls: { type: 'integer', required: true }, attempts: { type: 'integer', required: true }, retries: { type: 'integer', required: true }, inputTokens: { type: 'integer', required: true }, cachedInputTokens: { type: 'integer', required: true }, outputTokens: { type: 'integer', required: true }, reasoningTokens: { type: 'integer', required: true }, cacheHits: { type: 'integer', required: true }, cacheMisses: { type: 'integer', required: true }, estimatedCostUsd: { type: 'number', required: true }, topLogprobScores: { type: 'integer', required: true }, explicitTagScores: { type: 'integer', required: true } } };
|
|
@@ -116,9 +120,7 @@ export function apply(ctx, config = {}) {
|
|
|
116
120
|
const extractTask = async (agent, fromSeq, toSeq, maxChars, signal) => extractSession(agent, async (ref) => { const stored = await services.attachments.readImage(ref, signal); return { data: stored.data, mediaType: stored.ref.mediaType }; }, { fromSeq, toSeq, includeAssistantText: true, maxChars });
|
|
117
121
|
const routePolicy = (selected) => ({ mode: selected.autoVerifyMode, minConfidence: selected.autoRouteMinConfidence, maxCandidates: selected.autoRouteMaxCandidates, maxPerTask: selected.autoRouteMaxPerTask + selected.autoVerifyMaxPerTask, maxPerSession: selected.autoRouteMaxPerSession + selected.autoVerifyMaxPerSession, maxModelCallsPerTask: selected.autoMaxModelCallsPerTask, maxModelCallsPerSession: selected.autoMaxModelCallsPerSession, maxInputChars: selected.autoRouteMaxInputChars, maxItemChars: selected.autoRouteMaxItemChars });
|
|
118
122
|
const routeFeedback = (decision, detail) => createUserMessage({ content: [{ type: 'text', text: '[Automatic verifier routing: ' + decision.kind + ']\n' + detail + '\nUse this independent result to continue the actual task. Do not merely restate the ranking or progress score; implement, correct, and verify the required work.' }], source: { kind: 'plugin', plugin: 'dsh-llm-verifier', form: 'notice', summary: 'Automatic verifier routed ' + decision.kind } });
|
|
119
|
-
|
|
120
|
-
if (endpoint !== 'statistics')
|
|
121
|
-
return rpcFailure('unknown llm-verifier endpoint');
|
|
123
|
+
const handleStatisticsQuery = async (payload) => {
|
|
122
124
|
if (typeof payload !== 'object' || payload === null)
|
|
123
125
|
return rpcFailure('statistics payload must be an object');
|
|
124
126
|
const row = payload;
|
|
@@ -136,7 +138,103 @@ export function apply(ctx, config = {}) {
|
|
|
136
138
|
catch (error) {
|
|
137
139
|
return rpcFailure(error instanceof Error ? error.message : String(error));
|
|
138
140
|
}
|
|
139
|
-
}
|
|
141
|
+
};
|
|
142
|
+
// 1. 优先注册到官方 /api 共享通道 (connection.fetch.register)
|
|
143
|
+
const anyConn = services.connection;
|
|
144
|
+
if (anyConn && anyConn.fetch && typeof anyConn.fetch.register === 'function') {
|
|
145
|
+
try {
|
|
146
|
+
anyConn.fetch.register({
|
|
147
|
+
path: '/api/llm-verifier/statistics',
|
|
148
|
+
methods: ['POST'],
|
|
149
|
+
requestBody: 'buffered',
|
|
150
|
+
fetch: async (request) => {
|
|
151
|
+
let body;
|
|
152
|
+
try {
|
|
153
|
+
body = await request.json();
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return new Response('body is not JSON', { status: 400 });
|
|
157
|
+
}
|
|
158
|
+
const isRpcEnvelope = typeof body === 'object' && body !== null && body.type === 'client-request' && typeof body.rpcId === 'string';
|
|
159
|
+
const rpcId = isRpcEnvelope ? body.rpcId : 'direct';
|
|
160
|
+
const payload = isRpcEnvelope ? body.payload : body;
|
|
161
|
+
const outcome = await handleStatisticsQuery(payload);
|
|
162
|
+
if (isRpcEnvelope) {
|
|
163
|
+
return Response.json({
|
|
164
|
+
type: 'server-response',
|
|
165
|
+
rpcId,
|
|
166
|
+
result: outcome,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return Response.json(outcome);
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
ctx.logger.warn('failed to register /api/llm-verifier/statistics fetch route: ' + String(e));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// 2. 尝试向底层 webServer 挂载 /llm-verifier 前缀路由(若当前宿主提供 webServer 服务)
|
|
178
|
+
const mountWebServerRoute = (ws) => {
|
|
179
|
+
if (!ws || typeof ws.register !== 'function')
|
|
180
|
+
return;
|
|
181
|
+
try {
|
|
182
|
+
ws.register({
|
|
183
|
+
kind: 'prefix',
|
|
184
|
+
path: '/llm-verifier',
|
|
185
|
+
handler: (req, res) => {
|
|
186
|
+
if (req.method !== 'POST') {
|
|
187
|
+
res.writeHead(405);
|
|
188
|
+
res.end();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
let bodyStr = '';
|
|
192
|
+
req.on('data', (chunk) => { bodyStr += chunk.toString(); });
|
|
193
|
+
req.on('end', async () => {
|
|
194
|
+
try {
|
|
195
|
+
const body = JSON.parse(bodyStr);
|
|
196
|
+
const isRpcEnvelope = typeof body === 'object' && body !== null && body.type === 'client-request' && typeof body.rpcId === 'string';
|
|
197
|
+
const rpcId = isRpcEnvelope ? body.rpcId : 'direct';
|
|
198
|
+
const payload = isRpcEnvelope ? body.payload : body;
|
|
199
|
+
const outcome = await handleStatisticsQuery(payload);
|
|
200
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
201
|
+
if (isRpcEnvelope) {
|
|
202
|
+
res.end(JSON.stringify({ type: 'server-response', rpcId, result: outcome }));
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
res.end(JSON.stringify(outcome));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
res.writeHead(400, { 'content-type': 'application/json' });
|
|
210
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
catch { }
|
|
217
|
+
};
|
|
218
|
+
if (typeof ctx.inject === 'function') {
|
|
219
|
+
try {
|
|
220
|
+
ctx.inject(['webServer'], (webCtx) => {
|
|
221
|
+
if (webCtx?.webServer)
|
|
222
|
+
mountWebServerRoute(webCtx.webServer);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
catch { }
|
|
226
|
+
}
|
|
227
|
+
// 3. 保留原有 RPC channel 注册以保持纯单元测试和旧客户端兼容
|
|
228
|
+
try {
|
|
229
|
+
services.connection?.rpc?.handle('/llm-verifier', async (endpoint, payload) => {
|
|
230
|
+
if (endpoint !== 'statistics')
|
|
231
|
+
return rpcFailure('unknown llm-verifier endpoint');
|
|
232
|
+
return handleStatisticsQuery(payload);
|
|
233
|
+
}, { authority: 'loopback' });
|
|
234
|
+
}
|
|
235
|
+
catch (e) {
|
|
236
|
+
ctx.logger.warn('failed to register /llm-verifier rpc handler: ' + String(e));
|
|
237
|
+
}
|
|
140
238
|
ctx.on('agent/disposed', ({ agent }) => { autoBudget.release(agent); autoRouter.release(agent); topics.delete(String(agent.id)); });
|
|
141
239
|
ctx.on('agent/turn-stopping', async ({ agent, signal }) => {
|
|
142
240
|
const selected = current();
|
|
@@ -147,6 +245,89 @@ export function apply(ctx, config = {}) {
|
|
|
147
245
|
const admittedLastSeq = agent.session.events.at(-1)?.seq ?? -1;
|
|
148
246
|
const snapshot = agent.session.events.filter(event => event.seq <= admittedLastSeq);
|
|
149
247
|
const stillCurrent = () => !signal.aborted && (agent.session.events.at(-1)?.seq ?? -1) === admittedLastSeq;
|
|
248
|
+
if (selected.autoVerifyPlanMode) {
|
|
249
|
+
const planDetection = detectPlanExit(snapshot, evidence.taskStartSeq);
|
|
250
|
+
if (planDetection.hasExitPlanMode && planDetection.planText) {
|
|
251
|
+
const planFingerprint = stableHash({ phase: 'plan_review', callSeq: planDetection.callSeq, plan: planDetection.planText });
|
|
252
|
+
const planReservation = autoRouter.reserve(agent, 'plan_review', planFingerprint, 1, policy);
|
|
253
|
+
if (planReservation) {
|
|
254
|
+
try {
|
|
255
|
+
const extracted = await extractTask(agent, evidence.taskStartSeq, admittedLastSeq, selected.autoVerifyMaxChars, signal);
|
|
256
|
+
const prompt = buildPlanPreReviewPrompt(extracted.problem, planDetection.planText, selected.autoRouteMaxInputChars);
|
|
257
|
+
const classified = await classifyRoute(agent, prompt, signal);
|
|
258
|
+
if (!stillCurrent()) {
|
|
259
|
+
autoRouter.fail(agent, planReservation, false);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const verdict = parsePlanReviewVerdict(classified.text);
|
|
263
|
+
if (verdict.score >= selected.autoVerifyThreshold) {
|
|
264
|
+
autoRouter.commit(agent, planReservation, admittedLastSeq);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
autoRouter.fail(agent, planReservation, selected.autoVerifyMode === 'strict');
|
|
268
|
+
agent.steer(createUserMessage({
|
|
269
|
+
content: [{
|
|
270
|
+
type: 'text',
|
|
271
|
+
text: '[Automatic Verifier Plan Pre-review]\nPlan pre-review scored ' + (verdict.score * 100).toFixed(1) + '% (Threshold: ' + (selected.autoVerifyThreshold * 100).toFixed(0) + '%).\n' + verdict.feedback + '\nAddress the identified issues or improve the plan before proceeding.'
|
|
272
|
+
}],
|
|
273
|
+
source: { kind: 'plugin', plugin: 'dsh-llm-verifier', form: 'notice', summary: 'Plan Pre-review Feedback' }
|
|
274
|
+
}));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
autoRouter.fail(agent, planReservation, selected.autoVerifyMode === 'strict');
|
|
280
|
+
ctx.logger.warn('llm-verifier plan pre-review failed: ' + (error instanceof Error ? error.message : String(error)));
|
|
281
|
+
if (selected.autoVerifyMode === 'strict' && !signal.aborted) {
|
|
282
|
+
agent.steer(createUserMessage({ content: [{ type: 'text', text: '[Automatic Verifier Plan Pre-review]\nPlan pre-review failed: ' + (error instanceof Error ? error.message : String(error)) }], source: { kind: 'plugin', plugin: 'dsh-llm-verifier' } }));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (selected.autoVerifyTeamTasks) {
|
|
290
|
+
const teamInspection = inspectTeamTasks(snapshot, evidence.taskStartSeq);
|
|
291
|
+
if (teamInspection.hasRecentCompletedTask && teamInspection.latestCompletedTask) {
|
|
292
|
+
const task = teamInspection.latestCompletedTask;
|
|
293
|
+
const taskFingerprint = stableHash({ phase: 'team_task', taskId: task.id, status: task.status, seq: teamInspection.completedSeq });
|
|
294
|
+
const taskReservation = autoRouter.reserve(agent, 'team_task', taskFingerprint, 1, policy);
|
|
295
|
+
if (taskReservation) {
|
|
296
|
+
try {
|
|
297
|
+
const extracted = await extractTask(agent, evidence.taskStartSeq, admittedLastSeq, selected.autoVerifyMaxChars, signal);
|
|
298
|
+
const prompt = buildTeamTaskVerificationPrompt(task, extracted.trace, selected.autoRouteMaxInputChars);
|
|
299
|
+
const classified = await classifyRoute(agent, prompt, signal);
|
|
300
|
+
if (!stillCurrent()) {
|
|
301
|
+
autoRouter.fail(agent, taskReservation, false);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const verdict = parsePlanReviewVerdict(classified.text);
|
|
305
|
+
if (verdict.score >= selected.autoVerifyThreshold) {
|
|
306
|
+
autoRouter.commit(agent, taskReservation, admittedLastSeq);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
autoRouter.fail(agent, taskReservation, selected.autoVerifyMode === 'strict');
|
|
310
|
+
agent.steer(createUserMessage({
|
|
311
|
+
content: [{
|
|
312
|
+
type: 'text',
|
|
313
|
+
text: '[Automatic Verifier Team Task Gate]\nTask "' + task.subject + '" (#' + task.id + ') verification scored ' + (verdict.score * 100).toFixed(1) + '% (Threshold: ' + (selected.autoVerifyThreshold * 100).toFixed(0) + '%).\n' + verdict.feedback + '\nProvide verified execution evidence or resolve remaining issues before completing the task.'
|
|
314
|
+
}],
|
|
315
|
+
source: { kind: 'plugin', plugin: 'dsh-llm-verifier', form: 'notice', summary: 'Team Task Gate Feedback' }
|
|
316
|
+
}));
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
autoRouter.fail(agent, taskReservation, selected.autoVerifyMode === 'strict');
|
|
322
|
+
ctx.logger.warn('llm-verifier team task verification failed: ' + (error instanceof Error ? error.message : String(error)));
|
|
323
|
+
if (selected.autoVerifyMode === 'strict' && !signal.aborted) {
|
|
324
|
+
agent.steer(createUserMessage({ content: [{ type: 'text', text: '[Automatic Verifier Team Task Gate]\nTask verification failed: ' + (error instanceof Error ? error.message : String(error)) }], source: { kind: 'plugin', plugin: 'dsh-llm-verifier' } }));
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
150
331
|
let decision = boundDecision(analyzeStructuredRoute(snapshot, selected.autoRouteMaxCandidates, selected.autoRouteMaxItemChars), policy);
|
|
151
332
|
if (decision === undefined && selected.autoRouteSemantic && (selected.autoVerifyMode === 'strict' || semanticRouteHint(snapshot))) {
|
|
152
333
|
const fingerprint = stableHash({ phase: 'semantic', from: evidence.taskStartSeq, to: admittedLastSeq, model: selected.provider + '/' + selected.model });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
2
|
+
export interface PlanModeDetection {
|
|
3
|
+
hasExitPlanMode: boolean;
|
|
4
|
+
callSeq?: number;
|
|
5
|
+
planText?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function detectPlanExit(events: readonly SessionEvent[], fromSeq?: number): PlanModeDetection;
|
|
8
|
+
export declare function buildPlanPreReviewPrompt(problem: string, planText: string, maxChars?: number): string;
|
|
9
|
+
export declare function parsePlanReviewVerdict(text: string): {
|
|
10
|
+
verdict: string;
|
|
11
|
+
score: number;
|
|
12
|
+
feedback: string;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=plan-gate.d.ts.map
|