@duckmind/dm-darwin-x64 0.34.0 → 0.34.1
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/dm
CHANGED
|
Binary file
|
|
@@ -19,6 +19,14 @@ export type ContextUsage = {
|
|
|
19
19
|
percent?: number;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
export type DuckmindCreditsSnapshot = {
|
|
23
|
+
usage: number | null;
|
|
24
|
+
remaining: number | null;
|
|
25
|
+
limit: number | null;
|
|
26
|
+
isFreeTier?: boolean | null;
|
|
27
|
+
fetchedAt?: number | null;
|
|
28
|
+
};
|
|
29
|
+
|
|
22
30
|
export type BottomInputStatusState = {
|
|
23
31
|
ctx: any;
|
|
24
32
|
footerData?: any;
|
|
@@ -51,6 +59,7 @@ export type BottomInputStatusRender = {
|
|
|
51
59
|
};
|
|
52
60
|
|
|
53
61
|
export const CONTEXT_BAR_WIDTH = 10;
|
|
62
|
+
const CREDIT_BAR_WIDTH = 10;
|
|
54
63
|
const CONTEXT_COLORS = {
|
|
55
64
|
normal: "#00afaf",
|
|
56
65
|
warning: "#febc38",
|
|
@@ -73,19 +82,26 @@ export function renderBottomInputStatus(input: BottomInputStatusState): BottomIn
|
|
|
73
82
|
const safeWidth = Math.max(1, Math.floor(input.width));
|
|
74
83
|
const enabled = input.beautifiedInputEnabled;
|
|
75
84
|
const extensionStatuses = getVisibleExtensionStatuses(input.footerData);
|
|
85
|
+
const usage = readContextUsageSnapshot(input.ctx, input.isStreaming, input.liveUsage, input.latestAssistantUsage);
|
|
86
|
+
const lastPromptStatusLines = renderLastPromptStatusLines(input, usage, safeWidth);
|
|
76
87
|
if (!enabled) {
|
|
77
88
|
return {
|
|
78
89
|
topLines: [],
|
|
79
90
|
secondaryLines: renderExtensionStatusLines(extensionStatuses, safeWidth, input.theme),
|
|
80
|
-
lastPromptLines: renderLastPromptLines(input.lastPrompt, safeWidth, input.theme),
|
|
91
|
+
lastPromptLines: [...lastPromptStatusLines, ...renderLastPromptLines(input.lastPrompt, safeWidth, input.theme)],
|
|
81
92
|
frameStatus: emptyFrameStatus(),
|
|
82
|
-
cacheKey: JSON.stringify({
|
|
93
|
+
cacheKey: JSON.stringify({
|
|
94
|
+
width: safeWidth,
|
|
95
|
+
enabled: false,
|
|
96
|
+
lastPrompt: input.lastPrompt,
|
|
97
|
+
extensionStatuses,
|
|
98
|
+
lastPromptStatusLines: lastPromptStatusLines.map(stripAnsi),
|
|
99
|
+
}),
|
|
83
100
|
};
|
|
84
101
|
}
|
|
85
102
|
|
|
86
103
|
const icons = input.icons ?? getBottomInputIcons();
|
|
87
104
|
const elapsedSeconds = Math.floor(Math.max(0, input.now - input.sessionStartTime) / 1000);
|
|
88
|
-
const usage = readContextUsageSnapshot(input.ctx, input.isStreaming, input.liveUsage, input.latestAssistantUsage);
|
|
89
105
|
const modelName = readModelName(input.ctx);
|
|
90
106
|
const thinking = readThinkingLevel(input.ctx) ?? input.currentThinkingLevel ?? readThinkingLevelFromSession(input.ctx);
|
|
91
107
|
const cacheKey = JSON.stringify({
|
|
@@ -97,13 +113,14 @@ export function renderBottomInputStatus(input: BottomInputStatusState): BottomIn
|
|
|
97
113
|
elapsedSeconds,
|
|
98
114
|
lastPrompt: input.lastPrompt,
|
|
99
115
|
extensionStatuses,
|
|
116
|
+
lastPromptStatusLines: lastPromptStatusLines.map(stripAnsi),
|
|
100
117
|
icons,
|
|
101
118
|
});
|
|
102
119
|
|
|
103
120
|
return {
|
|
104
121
|
topLines: [],
|
|
105
122
|
secondaryLines: renderExtensionStatusLines(extensionStatuses, safeWidth, input.theme),
|
|
106
|
-
lastPromptLines: renderLastPromptLines(input.lastPrompt, safeWidth, input.theme),
|
|
123
|
+
lastPromptLines: [...lastPromptStatusLines, ...renderLastPromptLines(input.lastPrompt, safeWidth, input.theme)],
|
|
107
124
|
frameStatus: renderFrameStatus({ ...input, width: safeWidth, icons }, modelName, thinking, usage),
|
|
108
125
|
cacheKey,
|
|
109
126
|
};
|
|
@@ -139,6 +156,15 @@ export function renderLastPromptLines(prompt: string, width: number, theme: Them
|
|
|
139
156
|
return [truncateToWidth(`${prefix}${safeFg(theme, "muted", value)}`, safeWidth, "…", false)];
|
|
140
157
|
}
|
|
141
158
|
|
|
159
|
+
export function renderLastPromptStatusLines(input: BottomInputStatusState, usage = readContextUsageSnapshot(input.ctx, input.isStreaming, input.liveUsage, input.latestAssistantUsage), width = input.width): string[] {
|
|
160
|
+
const safeWidth = Math.max(1, Math.floor(width));
|
|
161
|
+
if (safeWidth < 10) return [];
|
|
162
|
+
|
|
163
|
+
const statusLine = renderContextCostLine(input, usage, safeWidth);
|
|
164
|
+
const creditsLine = renderDuckmindCreditsLine(input, safeWidth);
|
|
165
|
+
return [statusLine, creditsLine].filter((line): line is string => typeof line === "string" && visibleWidth(stripAnsi(line)) > 0);
|
|
166
|
+
}
|
|
167
|
+
|
|
142
168
|
export function getVisibleExtensionStatuses(footerData: any): string[] {
|
|
143
169
|
let statuses: unknown;
|
|
144
170
|
try {
|
|
@@ -236,6 +262,66 @@ function renderContextSegment(usage: ContextUsage | null): string | null {
|
|
|
236
262
|
return applyHexColor(CONTEXT_COLORS.normal, formatTokens(usage.tokens));
|
|
237
263
|
}
|
|
238
264
|
|
|
265
|
+
function renderContextCostLine(input: BottomInputStatusState, usage: ContextUsage | null, width: number): string {
|
|
266
|
+
const contextText = formatContextStatus(input.ctx, usage);
|
|
267
|
+
const costInfo = readModelCostInfo(input.ctx);
|
|
268
|
+
const costText = formatModelCostInline(costInfo);
|
|
269
|
+
const left = safeFg(input.theme, "muted", contextText);
|
|
270
|
+
const right = safeFg(input.theme, costInfo.color, costText, costInfo.color === "success" ? "accent" : "text");
|
|
271
|
+
return renderEdgeAlignedLine(left, right, width);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function renderDuckmindCreditsLine(input: BottomInputStatusState, width: number): string | null {
|
|
275
|
+
if (!isDuckmindProvider(input.ctx)) return null;
|
|
276
|
+
const credits = readDuckmindCredits(input.footerData);
|
|
277
|
+
if (!credits) return null;
|
|
278
|
+
|
|
279
|
+
const left = [
|
|
280
|
+
safeFg(input.theme, "muted", "Credits Remaining"),
|
|
281
|
+
renderCreditProgressBar(credits, input.theme),
|
|
282
|
+
safeFg(input.theme, "accent", formatCreditNumber(credits.remaining)),
|
|
283
|
+
].join(" ");
|
|
284
|
+
const right = `${safeFg(input.theme, "muted", "Credits Used")} ${safeFg(input.theme, "accent", formatCreditNumber(credits.usage))} ${safeFg(input.theme, "muted", "• Credit")}`;
|
|
285
|
+
return renderEdgeAlignedLine(left, right, width);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function formatContextStatus(ctx: any, usage: ContextUsage | null): string {
|
|
289
|
+
const tokens = usage?.tokens ?? 0;
|
|
290
|
+
const contextWindow = usage?.contextWindow ?? readModelContextWindow(ctx);
|
|
291
|
+
const mode = `(${readContextManagementMode(ctx)})`;
|
|
292
|
+
if (contextWindow > 0) {
|
|
293
|
+
const usagePercent = usage?.percent;
|
|
294
|
+
const percent = typeof usagePercent === "number" && Number.isFinite(usagePercent)
|
|
295
|
+
? usagePercent
|
|
296
|
+
: (tokens / contextWindow) * 100;
|
|
297
|
+
return `${Math.max(0, percent).toFixed(1)}%/${formatTokens(contextWindow)} ${mode}`;
|
|
298
|
+
}
|
|
299
|
+
return `${formatTokens(tokens)} ${mode}`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function readContextManagementMode(ctx: any): string {
|
|
303
|
+
const candidates: unknown[] = [];
|
|
304
|
+
try {
|
|
305
|
+
candidates.push(ctx?.getAutoCompactionEnabled?.());
|
|
306
|
+
} catch {
|
|
307
|
+
// Ignore optional API probes; older runtimes do not expose this value.
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
candidates.push(ctx?.autoCompactionEnabled, ctx?.autoCompactEnabled, ctx?.session?.autoCompactionEnabled, ctx?.session?.autoCompactEnabled);
|
|
311
|
+
} catch {
|
|
312
|
+
// Keep the display stable if the host shape changes.
|
|
313
|
+
}
|
|
314
|
+
for (const candidate of candidates) {
|
|
315
|
+
if (candidate === true) return "auto";
|
|
316
|
+
if (candidate === false) return "manual";
|
|
317
|
+
if (typeof candidate === "string") {
|
|
318
|
+
const normalized = candidate.trim().toLowerCase();
|
|
319
|
+
if (normalized === "auto" || normalized === "manual" || normalized === "off") return normalized;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return "auto";
|
|
323
|
+
}
|
|
324
|
+
|
|
239
325
|
function renderElapsedSegment(theme: ThemeLike, startedAt: number, now: number, icons: BottomInputIconSet): string | null {
|
|
240
326
|
const elapsed = Math.max(0, now - startedAt);
|
|
241
327
|
if (elapsed < 1000) return null;
|
|
@@ -349,6 +435,135 @@ function readModelName(ctx: any): string | null {
|
|
|
349
435
|
}
|
|
350
436
|
}
|
|
351
437
|
|
|
438
|
+
type ModelCostInfo = {
|
|
439
|
+
label: string;
|
|
440
|
+
color: "success" | "muted" | "warning" | "error";
|
|
441
|
+
inputPerMillion: number | null;
|
|
442
|
+
outputPerMillion: number | null;
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
function readModelCostInfo(ctx: any): ModelCostInfo {
|
|
446
|
+
let model: any;
|
|
447
|
+
try {
|
|
448
|
+
model = ctx?.model;
|
|
449
|
+
} catch {
|
|
450
|
+
model = null;
|
|
451
|
+
}
|
|
452
|
+
if (model?.id === "@preset/free") return classifyModelCost(0, 0);
|
|
453
|
+
const input = toFiniteNumber(model?.cost?.input);
|
|
454
|
+
const output = toFiniteNumber(model?.cost?.output);
|
|
455
|
+
return classifyModelCost(input, output);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function classifyModelCost(inputPerMillion: number | null, outputPerMillion: number | null): ModelCostInfo {
|
|
459
|
+
if (inputPerMillion === null || outputPerMillion === null) {
|
|
460
|
+
return { label: "Unknown Cost", color: "muted", inputPerMillion: null, outputPerMillion: null };
|
|
461
|
+
}
|
|
462
|
+
if (inputPerMillion === 0 && outputPerMillion === 0) {
|
|
463
|
+
return { label: "Free", color: "success", inputPerMillion, outputPerMillion };
|
|
464
|
+
}
|
|
465
|
+
if (inputPerMillion > 20 || outputPerMillion > 100) {
|
|
466
|
+
return { label: "Critical Cost", color: "error", inputPerMillion, outputPerMillion };
|
|
467
|
+
}
|
|
468
|
+
if (inputPerMillion > 10 || outputPerMillion > 30) {
|
|
469
|
+
return { label: "High Cost", color: "error", inputPerMillion, outputPerMillion };
|
|
470
|
+
}
|
|
471
|
+
if ((inputPerMillion >= 1 && inputPerMillion <= 10) || (outputPerMillion >= 5 && outputPerMillion <= 30)) {
|
|
472
|
+
return { label: "Moderate Cost", color: "warning", inputPerMillion, outputPerMillion };
|
|
473
|
+
}
|
|
474
|
+
if (inputPerMillion < 1 && outputPerMillion < 5) {
|
|
475
|
+
return { label: "Low Cost", color: "success", inputPerMillion, outputPerMillion };
|
|
476
|
+
}
|
|
477
|
+
return { label: "Unknown Cost", color: "muted", inputPerMillion, outputPerMillion };
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function formatModelCostInline(info: ModelCostInfo): string {
|
|
481
|
+
return `${info.label} ${formatUsd(info.inputPerMillion)}/M in · ${formatUsd(info.outputPerMillion)}/M out`;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function formatUsd(value: number | null): string {
|
|
485
|
+
if (value === null) return "n/a";
|
|
486
|
+
if (value === 0) return "$0";
|
|
487
|
+
if (value >= 100) return `$${value.toFixed(0)}`;
|
|
488
|
+
if (value >= 10) return `$${value.toFixed(1).replace(/\.0$/, "")}`;
|
|
489
|
+
if (value >= 1) return `$${value.toFixed(2).replace(/0$/, "").replace(/\.$/, "")}`;
|
|
490
|
+
return `$${value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "")}`;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function readDuckmindCredits(footerData: any): DuckmindCreditsSnapshot | null {
|
|
494
|
+
let raw: unknown;
|
|
495
|
+
try {
|
|
496
|
+
raw = footerData?.getDuckmindCredits?.();
|
|
497
|
+
} catch {
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
if (!isRecord(raw)) return null;
|
|
501
|
+
const usage = toFiniteNumber(raw.usage);
|
|
502
|
+
const remaining = toFiniteNumber(raw.remaining);
|
|
503
|
+
const limit = toFiniteNumber(raw.limit);
|
|
504
|
+
if (usage === null && remaining === null && limit === null) return null;
|
|
505
|
+
return {
|
|
506
|
+
usage,
|
|
507
|
+
remaining,
|
|
508
|
+
limit,
|
|
509
|
+
isFreeTier: typeof raw.isFreeTier === "boolean" ? raw.isFreeTier : null,
|
|
510
|
+
fetchedAt: toFiniteNumber(raw.fetchedAt),
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function renderCreditProgressBar(credits: DuckmindCreditsSnapshot, theme: ThemeLike): string {
|
|
515
|
+
const remaining = credits.remaining ?? 0;
|
|
516
|
+
const limit = credits.limit && credits.limit > 0 ? credits.limit : (remaining > 0 ? remaining : 0);
|
|
517
|
+
const percent = limit > 0 ? Math.max(0, Math.min(100, (remaining / limit) * 100)) : 0;
|
|
518
|
+
const filledCells = Math.round((percent / 100) * CREDIT_BAR_WIDTH);
|
|
519
|
+
const filled = "█".repeat(filledCells);
|
|
520
|
+
const empty = "░".repeat(Math.max(0, CREDIT_BAR_WIDTH - filledCells));
|
|
521
|
+
const color = percent <= 15 ? "error" : percent <= 35 ? "warning" : "success";
|
|
522
|
+
return `${safeFg(theme, "muted", "[")}${safeFg(theme, color, filled, "accent")}${safeFg(theme, "borderMuted", empty)}${safeFg(theme, "muted", "]")}`;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function isDuckmindProvider(ctx: any): boolean {
|
|
526
|
+
let values: unknown[] = [];
|
|
527
|
+
try {
|
|
528
|
+
const model = ctx?.model;
|
|
529
|
+
values = [
|
|
530
|
+
model?.provider,
|
|
531
|
+
model?.provider?.id,
|
|
532
|
+
model?.provider?.name,
|
|
533
|
+
model?.providerName,
|
|
534
|
+
model?.baseUrl,
|
|
535
|
+
model?.id,
|
|
536
|
+
ctx?.provider,
|
|
537
|
+
ctx?.provider?.id,
|
|
538
|
+
ctx?.provider?.name,
|
|
539
|
+
ctx?.provider?.baseUrl,
|
|
540
|
+
];
|
|
541
|
+
} catch {
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
return values.some((value) => typeof value === "string" && /duckmind|openrouter|@preset\//i.test(value));
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function renderEdgeAlignedLine(left: string, right: string, width: number): string {
|
|
548
|
+
const safeWidth = Math.max(1, Math.floor(width));
|
|
549
|
+
if (safeWidth <= 1) return truncateToWidth(left || right, safeWidth, "…", false);
|
|
550
|
+
const minGap = 2;
|
|
551
|
+
let rightPart = right;
|
|
552
|
+
let leftPart = left;
|
|
553
|
+
const originalLeftWidth = visibleWidth(leftPart);
|
|
554
|
+
const originalRightWidth = visibleWidth(rightPart);
|
|
555
|
+
if (originalLeftWidth + minGap + originalRightWidth > safeWidth) {
|
|
556
|
+
const rightBudget = Math.max(0, Math.min(originalRightWidth, Math.floor(safeWidth * 0.55)));
|
|
557
|
+
rightPart = rightBudget > 0 ? truncateToWidth(rightPart, rightBudget, "…", false) : "";
|
|
558
|
+
const leftBudget = Math.max(0, safeWidth - visibleWidth(rightPart) - (rightPart ? minGap : 0));
|
|
559
|
+
leftPart = leftBudget > 0 ? truncateToWidth(leftPart, leftBudget, "…", false) : "";
|
|
560
|
+
}
|
|
561
|
+
if (!leftPart) return truncateToWidth(rightPart, safeWidth, "…", false);
|
|
562
|
+
if (!rightPart) return truncateToWidth(leftPart, safeWidth, "…", false);
|
|
563
|
+
const gap = Math.max(1, safeWidth - visibleWidth(leftPart) - visibleWidth(rightPart));
|
|
564
|
+
return truncateToWidth(`${leftPart}${" ".repeat(gap)}${rightPart}`, safeWidth, "…", false);
|
|
565
|
+
}
|
|
566
|
+
|
|
352
567
|
export function renderContextBar(percent: number, color = CONTEXT_COLORS.normal): string {
|
|
353
568
|
const clamped = Math.max(0, Math.min(100, percent));
|
|
354
569
|
const filledCells = Math.floor((clamped / 100) * CONTEXT_BAR_WIDTH);
|
|
@@ -382,6 +597,23 @@ function formatDuration(ms: number): string {
|
|
|
382
597
|
return `${seconds}s`;
|
|
383
598
|
}
|
|
384
599
|
|
|
600
|
+
function formatCreditNumber(value: number | null): string {
|
|
601
|
+
if (value === null || !Number.isFinite(value)) return "n/a";
|
|
602
|
+
const abs = Math.abs(value);
|
|
603
|
+
if (abs >= 1000) return value.toFixed(0);
|
|
604
|
+
if (abs >= 100) return value.toFixed(1).replace(/\.0$/, "");
|
|
605
|
+
return value.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function toFiniteNumber(value: unknown): number | null {
|
|
609
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
610
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
611
|
+
const parsed = Number(value);
|
|
612
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
613
|
+
}
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
|
|
385
617
|
function safeFg(theme: ThemeLike, token: string, text: string, fallback = "text"): string {
|
|
386
618
|
try {
|
|
387
619
|
return theme.fg(token, text);
|