clauderipple 0.9.5 → 0.10.0
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/CHANGELOG.md +67 -0
- package/README.md +31 -0
- package/dist/app/package.json +1 -1
- package/dist/cli/src/hooks/agent-title.js +7 -4
- package/dist/cli/src/index.js +43 -0
- package/dist/cli/src/remote.js +263 -0
- package/dist/cli/src/schtasks.js +48 -40
- package/dist/cli/src/settings.js +2 -1
- package/dist/mod/hooks/i18n.ts +252 -6
- package/dist/mod/hooks/judge.ts +35 -17
- package/dist/mod/hooks/modes.ts +56 -1
- package/dist/mod/hooks/register.tsx +238 -32
- package/dist/mod/hooks/session.ts +98 -1
- package/dist/mod/hooks/settings.js +7 -3
- package/dist/mod/hooks/settings.ts +9 -4
- package/dist/mod/hooks/stats.ts +25 -1
- package/dist/mod/hooks/ui/agents.tsx +858 -79
- package/dist/mod/hooks/ui/alerts.tsx +20 -17
- package/dist/mod/hooks/ui/art.ts +33 -7
- package/dist/mod/hooks/ui/band.tsx +262 -81
- package/dist/mod/hooks/ui/flat.ts +265 -0
- package/dist/mod/hooks/ui/kit.tsx +70 -55
- package/dist/mod/hooks/ui/look.ts +129 -0
- package/dist/mod/hooks/ui/settings.tsx +71 -46
- package/dist/mod/hooks/ui/setup.tsx +15 -13
- package/dist/mod/hooks/ui/theme.ts +3 -0
- package/dist/mod/hooks/ui/tools.ts +124 -0
- package/dist/mod/hooks/ui/view.ts +116 -3
- package/dist/mod/hooks/workers.ts +33 -6
- package/dist/mod/tests/register.test.tsx +155 -11
- package/dist/mod/tests/ui.test.tsx +375 -27
- package/dist/mod/types/index.d.ts +68 -2
- package/dist/remote/package.json +28 -0
- package/dist/remote/src/away.js +85 -0
- package/dist/remote/src/bridge-client.js +295 -0
- package/dist/remote/src/composer-menu.js +11 -0
- package/dist/remote/src/config.js +102 -0
- package/dist/remote/src/desktop.js +1018 -0
- package/dist/remote/src/e2ee.js +126 -0
- package/dist/remote/src/e2ee.ts +126 -0
- package/dist/remote/src/eventlog.js +298 -0
- package/dist/remote/src/expo-push.js +127 -0
- package/dist/remote/src/folders.js +79 -0
- package/dist/remote/src/fsutil.js +39 -0
- package/dist/remote/src/handoff.js +38 -0
- package/dist/remote/src/host-service.js +168 -0
- package/dist/remote/src/images.js +215 -0
- package/dist/remote/src/local-control.js +138 -0
- package/dist/remote/src/main.js +264 -0
- package/dist/remote/src/permissions.js +124 -0
- package/dist/remote/src/power.js +76 -0
- package/dist/remote/src/processes.js +24 -0
- package/dist/remote/src/protocol.js +32 -0
- package/dist/remote/src/protocol.ts +284 -0
- package/dist/remote/src/push-content.js +32 -0
- package/dist/remote/src/push.js +134 -0
- package/dist/remote/src/receipts.js +111 -0
- package/dist/remote/src/relay.js +438 -0
- package/dist/remote/src/router.js +208 -0
- package/dist/remote/src/sdk-launcher.js +18 -0
- package/dist/remote/src/server.js +706 -0
- package/dist/remote/src/service.js +157 -0
- package/dist/remote/src/sessions.js +1800 -0
- package/dist/remote/src/sidebar-storage.js +211 -0
- package/dist/remote/src/sidebar.js +173 -0
- package/dist/remote/src/tool-details.js +205 -0
- package/dist/remote/src/tool-details.ts +153 -0
- package/dist/remote/src/worktree.js +99 -0
- package/dist/remote/web/app.ts +746 -0
- package/dist/remote/web/conn.ts +193 -0
- package/dist/remote/web/demo.ts +134 -0
- package/dist/remote/web/dom.ts +124 -0
- package/dist/remote/web/esc.ts +4 -0
- package/dist/remote/web/i18n.ts +374 -0
- package/dist/remote/web/icon.svg +1 -0
- package/dist/remote/web/index.html +24 -0
- package/dist/remote/web/manifest.webmanifest +10 -0
- package/dist/remote/web/mascot.ts +60 -0
- package/dist/remote/web/md.ts +78 -0
- package/dist/remote/web/notify.ts +55 -0
- package/dist/remote/web/pushstate.ts +27 -0
- package/dist/remote/web/secure.ts +109 -0
- package/dist/remote/web/sidebar.ts +106 -0
- package/dist/remote/web/style.css +311 -0
- package/dist/remote/web/sw.ts +48 -0
- package/dist/remote/web/timeline.ts +224 -0
- package/dist/remote/web/views.ts +323 -0
- package/dist/router/src/admin.js +227 -4
- package/dist/router/src/config.js +30 -0
- package/dist/router/src/index.js +39 -0
- package/dist/router/src/ingress/translate.js +6 -2
- package/dist/router/src/phone.js +82 -0
- package/dist/router/src/providers/anthropic-usage.js +33 -1
- package/dist/router/src/providers/google/index.js +26 -0
- package/dist/router/src/providers/google/prompt-refusal.js +29 -0
- package/dist/router/src/providers/google/usage.js +257 -0
- package/dist/router/src/providers/grok/index.js +18 -0
- package/dist/router/src/providers/grok/usage.js +77 -0
- package/dist/router/src/providers/openai/translate.js +13 -2
- package/dist/router/src/providers/opencode-usage.js +116 -0
- package/dist/router/src/proxy.js +339 -258
- package/dist/router/src/remote-host.js +320 -0
- package/dist/router/src/remote-tunnel.js +320 -0
- package/dist/router/src/requestlog.js +48 -39
- package/dist/router/src/usage-history.js +227 -0
- package/dist/router/src/version.js +1 -1
- package/dist/ui/app.js +436 -59
- package/dist/ui/i18n.js +184 -0
- package/dist/ui/index.html +24 -5
- package/dist/ui/style.css +39 -0
- package/docs/ARCHITECTURE.md +108 -8
- package/package.json +7 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SessionContextUsage, SessionRateLimit } from 'claude-code'
|
|
2
|
-
import type { CacheMemo, Hot } from '../types'
|
|
2
|
+
import type { CacheMemo, Hot, LimitItem } from '../types'
|
|
3
3
|
|
|
4
4
|
export function hottest(rateLimits: readonly SessionRateLimit[]): Hot | null {
|
|
5
5
|
const eligible = rateLimits.filter(limit => ['five_hour', 'seven_day'].includes(limit.kind) && limit.percentUsed >= 80)
|
|
@@ -33,6 +33,88 @@ export function claudeLimitsFrom(status: unknown, now: number): SessionRateLimit
|
|
|
33
33
|
})
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/** One per-model weekly window the router reports beside `rate_limits` (see `scopedFromLimits`). */
|
|
37
|
+
export type ScopedLimit = { name: string; percent: number; resetsAt?: string }
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The per-model weekly windows of the Claude login, from the router's GET /api/status
|
|
41
|
+
* (`claude.accounts[id=current].quota.scoped`: `{name, used_percent, window_minutes, reset_at}`,
|
|
42
|
+
* reset in epoch seconds or ms). A stale or unknown snapshot gives nothing, like claudeLimitsFrom.
|
|
43
|
+
*/
|
|
44
|
+
export function claudeScopedFrom(status: unknown, now: number): ScopedLimit[] {
|
|
45
|
+
const object = (v: unknown): Record<string, unknown> => v && typeof v === 'object' && !Array.isArray(v) ? v as Record<string, unknown> : {}
|
|
46
|
+
const accounts = object(object(status).claude).accounts
|
|
47
|
+
const current = Array.isArray(accounts) ? accounts.map(object).find(a => a.id === 'current') : undefined
|
|
48
|
+
const quota = object(current?.quota)
|
|
49
|
+
if (typeof quota.at !== 'number' || now - quota.at > CLAUDE_USAGE_STALE_MS) return []
|
|
50
|
+
const scoped = quota.scoped
|
|
51
|
+
if (!Array.isArray(scoped)) return []
|
|
52
|
+
return scoped.map(object).flatMap(entry => {
|
|
53
|
+
if (typeof entry.name !== 'string' || !entry.name || typeof entry.used_percent !== 'number' || !Number.isFinite(entry.used_percent)) return []
|
|
54
|
+
const reset = typeof entry.reset_at === 'number' ? entry.reset_at * (entry.reset_at < 1e12 ? 1000 : 1) : NaN
|
|
55
|
+
return [{ name: entry.name, percent: Math.round(entry.used_percent * 10) / 10, ...(Number.isFinite(reset) ? { resetsAt: new Date(reset).toISOString() } : {}) }]
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const KINDS = ['five_hour', 'seven_day', 'scoped', 'window'] as const
|
|
60
|
+
type LimitKind = (typeof KINDS)[number]
|
|
61
|
+
|
|
62
|
+
/** One window of `GET /api/limits` as a limits-row item, or null when it carries nothing to show. */
|
|
63
|
+
function limitItemFromWindow(w: unknown, provider: string, type: string | undefined, title: string | undefined, account: number, now: number): LimitItem | null {
|
|
64
|
+
const o = w && typeof w === 'object' && !Array.isArray(w) ? w as Record<string, unknown> : {}
|
|
65
|
+
const kind = o.kind
|
|
66
|
+
if (typeof kind !== 'string' || !(KINDS as readonly string[]).includes(kind)) return null
|
|
67
|
+
const used = o.used_percent
|
|
68
|
+
if (typeof used !== 'number' || !Number.isFinite(used)) return null
|
|
69
|
+
const minutes = typeof o.window_minutes === 'number' && o.window_minutes > 0 ? o.window_minutes : undefined
|
|
70
|
+
const reset = typeof o.reset_at === 'number' && Number.isFinite(o.reset_at) ? o.reset_at * (o.reset_at < 1e12 ? 1000 : 1) : NaN
|
|
71
|
+
// A window whose reset has passed no longer stands (as the ChatGPT reader has always said).
|
|
72
|
+
if (Number.isFinite(reset) && reset <= now) return null
|
|
73
|
+
return {
|
|
74
|
+
provider,
|
|
75
|
+
...(type ? { type } : {}),
|
|
76
|
+
...(title ? { title } : {}),
|
|
77
|
+
account,
|
|
78
|
+
kind: kind as LimitKind,
|
|
79
|
+
label: typeof o.label === 'string' ? o.label : '',
|
|
80
|
+
...(minutes !== undefined ? { windowMinutes: minutes } : {}),
|
|
81
|
+
percent: Math.round(used * 10) / 10,
|
|
82
|
+
...(Number.isFinite(reset) ? { resetsAt: new Date(reset).toISOString() } : {}),
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Every provider's windows from the router's `GET /api/limits`: one shape for Claude, each ChatGPT
|
|
88
|
+
* account and each Antigravity account (see `buildLimits` in packages/router/src/admin.ts). The
|
|
89
|
+
* providers keep the router's order (Claude first), and each account's windows keep theirs.
|
|
90
|
+
*/
|
|
91
|
+
export function routerLimitItems(body: unknown, now: number): LimitItem[] {
|
|
92
|
+
const object = (v: unknown): Record<string, unknown> => v && typeof v === 'object' && !Array.isArray(v) ? v as Record<string, unknown> : {}
|
|
93
|
+
const providers = object(body).providers
|
|
94
|
+
if (!Array.isArray(providers)) return []
|
|
95
|
+
const out: LimitItem[] = []
|
|
96
|
+
for (const p of providers) {
|
|
97
|
+
const entry = object(p)
|
|
98
|
+
const provider = typeof entry.provider === 'string' ? entry.provider : ''
|
|
99
|
+
if (!provider) continue
|
|
100
|
+
const type = typeof entry.type === 'string' ? entry.type : undefined
|
|
101
|
+
const title = typeof entry.title === 'string' ? entry.title : undefined
|
|
102
|
+
const accounts = entry.accounts
|
|
103
|
+
if (!Array.isArray(accounts)) continue
|
|
104
|
+
for (const a of accounts) {
|
|
105
|
+
const account = object(a)
|
|
106
|
+
const index = typeof account.account === 'number' ? account.account : 0
|
|
107
|
+
const windows = account.windows
|
|
108
|
+
if (!Array.isArray(windows)) continue
|
|
109
|
+
for (const w of windows) {
|
|
110
|
+
const item = limitItemFromWindow(w, provider, type, title, index, now)
|
|
111
|
+
if (item) out.push(item)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return out
|
|
116
|
+
}
|
|
117
|
+
|
|
36
118
|
/**
|
|
37
119
|
* The reset is read in the person's own clock: `offsetMinutes` is `Date#getTimezoneOffset` (minutes
|
|
38
120
|
* behind UTC), passed in so tests do not depend on the machine's zone.
|
|
@@ -71,6 +153,21 @@ export function keptAlert(kept: { pings: number; tokens: number } | null, memo:
|
|
|
71
153
|
return kept !== null && kept.pings > 0 && memo !== null && memo.expiresAt > now
|
|
72
154
|
}
|
|
73
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Whether a put-off hot card should be forgotten: only when the window itself has moved on — another
|
|
158
|
+
* window became the hottest, or its reset moved forward by more than an hour. A reading that is
|
|
159
|
+
* missing for a while (a routed model's session carries no plan windows) or that comes from the
|
|
160
|
+
* other source with its reset spelled to the second rather than the minute is the same window, and
|
|
161
|
+
* forgetting on either brought the card back on every turn (2026-10-14, weekly at 87%).
|
|
162
|
+
*/
|
|
163
|
+
export function hotWindowMoved(previous: Hot | null, next: Hot | null): boolean {
|
|
164
|
+
if (!previous || !next) return false
|
|
165
|
+
if (previous.kind !== next.kind) return true
|
|
166
|
+
const before = previous.resetsAt ? Date.parse(previous.resetsAt) : NaN
|
|
167
|
+
const after = next.resetsAt ? Date.parse(next.resetsAt) : NaN
|
|
168
|
+
return Number.isFinite(before) && Number.isFinite(after) && after - before > 3_600_000
|
|
169
|
+
}
|
|
170
|
+
|
|
74
171
|
export function hotAlert(hot: Hot | null, hidden: number | null): boolean {
|
|
75
172
|
return hot !== null && hot.percent >= 80 && (hidden === null || hot.percent >= hidden + 10)
|
|
76
173
|
}
|
|
@@ -26,13 +26,17 @@ export const DEFAULTS = {
|
|
|
26
26
|
fallback: { agent: 'general-purpose', model: 'sonnet' },
|
|
27
27
|
keepWarm: false, keepWarmHours: 2, keepWarmMinTokens: 30000,
|
|
28
28
|
compactWith: 'haiku', handoffSkill: '', handoffAfter: 'continue', handoffButton: 'advised',
|
|
29
|
-
swampAt: 50, swampAlert: false, layout: 'dashboard', appearance: 'auto', language: 'auto', hide: ['reason'], extras: [], setupDone: false,
|
|
29
|
+
swampAt: 50, swampAlert: false, layout: 'dashboard', appearance: 'auto', skin: 'deep', language: 'auto', hide: ['reason'], extras: [], setupDone: false,
|
|
30
30
|
};
|
|
31
|
+
/** The desktop's skins, in the order the panel offers them. */
|
|
32
|
+
export const SKINS = ['deep', 'flat'];
|
|
31
33
|
/** Band chips that show only when switched on in Look. */
|
|
32
34
|
export const EXTRAS = ['context', 'limits'];
|
|
33
35
|
/**
|
|
34
36
|
* Claude's own models as workers: the built-in general-purpose agent run on the model, `model` its
|
|
35
|
-
* current id. The key is what the allow-list stores.
|
|
37
|
+
* current id. The key is what the allow-list stores. Mythos is not here: the Agent tool's `model`
|
|
38
|
+
* takes only sonnet, opus, haiku and fable (Claude Code 2.1.295, read 2026-10-14), so a
|
|
39
|
+
* general-purpose worker on it would be refused.
|
|
36
40
|
*/
|
|
37
41
|
export const CLAUDE_WORKERS = [
|
|
38
42
|
{ name: 'general-purpose:haiku', model: 'claude-haiku-5-5', provider: 'anthropic', target: { agent: 'general-purpose', model: 'haiku' } },
|
|
@@ -281,7 +285,7 @@ export function normalizeSettings(raw, workers) {
|
|
|
281
285
|
judge: ['haiku', 'jev', 'model'], jevSource: ['typesafe', 'zen'], bias: [-2, -1, 0, 1, 2], floor: EFFORTS,
|
|
282
286
|
ceiling: EFFORTS, agentAuto: ['off', 'auto', 'all'], keepWarmHours: [1, 2, 4, 8], compactWith: ['haiku', 'session'],
|
|
283
287
|
handoffAfter: ['continue', 'confirm', 'copy'], handoffButton: ['advised', 'always'], layout: ['dashboard', 'minimal'],
|
|
284
|
-
appearance: ['auto', 'dark', 'light'], language: ['auto', 'ko', 'en', 'zh', 'ja', 'es', 'pt'],
|
|
288
|
+
appearance: ['auto', 'dark', 'light'], skin: SKINS, language: ['auto', 'ko', 'en', 'zh', 'ja', 'es', 'pt'],
|
|
285
289
|
};
|
|
286
290
|
for (const key of Object.keys(choices))
|
|
287
291
|
if (choices[key].includes(value[key]))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Calibration, Domain, Effort, Level, Mode, Rule, Rung, Settings, SettingsChange, Step, TierTarget } from '../types/index.d.ts'
|
|
1
|
+
import type { Calibration, Domain, Effort, Level, Mode, Rule, Rung, Settings, SettingsChange, Skin, Step, TierTarget } from '../types/index.d.ts'
|
|
2
2
|
|
|
3
3
|
/** Bump whenever a writer's normalization can lose another format's fields. */
|
|
4
4
|
export const MOD_SETTINGS_SCHEMA_VERSION = 1
|
|
@@ -31,15 +31,20 @@ export const DEFAULTS: Settings = {
|
|
|
31
31
|
fallback: { agent: 'general-purpose', model: 'sonnet' },
|
|
32
32
|
keepWarm: false, keepWarmHours: 2, keepWarmMinTokens: 30000,
|
|
33
33
|
compactWith: 'haiku', handoffSkill: '', handoffAfter: 'continue', handoffButton: 'advised',
|
|
34
|
-
swampAt: 50, swampAlert: false, layout: 'dashboard', appearance: 'auto', language: 'auto', hide: ['reason'], extras: [], setupDone: false,
|
|
34
|
+
swampAt: 50, swampAlert: false, layout: 'dashboard', appearance: 'auto', skin: 'deep', language: 'auto', hide: ['reason'], extras: [], setupDone: false,
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/** The desktop's skins, in the order the panel offers them. */
|
|
38
|
+
export const SKINS: readonly Skin[] = ['deep', 'flat']
|
|
39
|
+
|
|
37
40
|
/** Band chips that show only when switched on in Look. */
|
|
38
41
|
export const EXTRAS: readonly string[] = ['context', 'limits']
|
|
39
42
|
|
|
40
43
|
/**
|
|
41
44
|
* Claude's own models as workers: the built-in general-purpose agent run on the model, `model` its
|
|
42
|
-
* current id. The key is what the allow-list stores.
|
|
45
|
+
* current id. The key is what the allow-list stores. Mythos is not here: the Agent tool's `model`
|
|
46
|
+
* takes only sonnet, opus, haiku and fable (Claude Code 2.1.295, read 2026-10-14), so a
|
|
47
|
+
* general-purpose worker on it would be refused.
|
|
43
48
|
*/
|
|
44
49
|
export const CLAUDE_WORKERS: readonly { name: string; model: string; provider: string; target: TierTarget }[] = [
|
|
45
50
|
{ name: 'general-purpose:haiku', model: 'claude-haiku-5-5', provider: 'anthropic', target: { agent: 'general-purpose', model: 'haiku' } },
|
|
@@ -272,7 +277,7 @@ export function normalizeSettings(raw: unknown, workers?: readonly Worker[]): Se
|
|
|
272
277
|
judge: ['haiku', 'jev', 'model'], jevSource: ['typesafe', 'zen'], bias: [-2, -1, 0, 1, 2], floor: EFFORTS,
|
|
273
278
|
ceiling: EFFORTS, agentAuto: ['off', 'auto', 'all'], keepWarmHours: [1, 2, 4, 8], compactWith: ['haiku', 'session'],
|
|
274
279
|
handoffAfter: ['continue', 'confirm', 'copy'], handoffButton: ['advised', 'always'], layout: ['dashboard', 'minimal'],
|
|
275
|
-
appearance: ['auto', 'dark', 'light'], language: ['auto', 'ko', 'en', 'zh', 'ja', 'es', 'pt'],
|
|
280
|
+
appearance: ['auto', 'dark', 'light'], skin: SKINS, language: ['auto', 'ko', 'en', 'zh', 'ja', 'es', 'pt'],
|
|
276
281
|
} as const
|
|
277
282
|
for (const key of Object.keys(choices) as (keyof typeof choices)[]) if ((choices[key] as readonly unknown[]).includes(value[key])) Object.assign(result, { [key]: value[key] })
|
|
278
283
|
if (typeof value.judgeModel === 'string' && value.judgeModel.trim()) result.judgeModel = value.judgeModel.trim()
|
package/dist/mod/hooks/stats.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Effort, JudgeKind, Stats, Tier, Usage } from '../types'
|
|
1
|
+
import type { AgentRec, Effort, JudgeKind, Stats, Tier, Usage } from '../types'
|
|
2
2
|
import { t } from './i18n.ts'
|
|
3
3
|
import type { Lang } from './i18n.ts'
|
|
4
4
|
|
|
@@ -13,6 +13,30 @@ export function weighted(usage: Usage): number {
|
|
|
13
13
|
(usage.cache_read_input_tokens ?? 0) * 0.1 + (usage.output_tokens ?? 0) * 5
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* The effort a step was sent at: a routed id's `@effort` wins, since the router forces it over the
|
|
18
|
+
* request's own (packages/router/src/routing.ts: "gpt-5.6-sol@medium → effort forced to medium").
|
|
19
|
+
*/
|
|
20
|
+
export function sentEffort(model: string, effort: string | number | undefined): string | number | undefined {
|
|
21
|
+
return /@([a-z]+)$/.exec(model)?.[1] ?? effort
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* An agent after one of its steps: the raw counts the API reported added to its own (what the
|
|
26
|
+
* Agents pane shows, so it agrees with the host's), the weighted cost as before, the effort the step
|
|
27
|
+
* was sent at and the model that answered.
|
|
28
|
+
*/
|
|
29
|
+
export function addAgentStep(agent: AgentRec, usage: Usage & { model?: string }, effort: string | number | undefined): AgentRec {
|
|
30
|
+
const was = agent.usage ?? { input: 0, cached: 0, cacheWrite: 0, output: 0 }
|
|
31
|
+
return {
|
|
32
|
+
...agent,
|
|
33
|
+
cost: agent.cost + weighted(usage),
|
|
34
|
+
usage: { input: was.input + (usage.input_tokens ?? 0), cached: was.cached + (usage.cache_read_input_tokens ?? 0), cacheWrite: was.cacheWrite + (usage.cache_creation_input_tokens ?? 0), output: was.output + (usage.output_tokens ?? 0) },
|
|
35
|
+
...(effort !== undefined ? { effort } : {}),
|
|
36
|
+
...(usage.model ? { answeredBy: usage.model } : {}),
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
16
40
|
export function addRequest(stats: Stats, usage: Usage, effort: Effort): Stats {
|
|
17
41
|
const bucket = stats.byEffort[effort] ?? { prompts: 0, cost: 0 }
|
|
18
42
|
return { ...stats, requests: stats.requests + 1, input: stats.input + (usage.input_tokens ?? 0),
|