@duckmind/dm-darwin-x64 0.34.0 → 0.34.2
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,16 @@ 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 (!normalizePromptText(input.lastPrompt)) return [];
|
|
162
|
+
if (safeWidth < 10) return [];
|
|
163
|
+
|
|
164
|
+
const statusLine = renderContextCostLine(input, usage, safeWidth);
|
|
165
|
+
const creditsLine = renderDuckmindCreditsLine(input, safeWidth);
|
|
166
|
+
return [statusLine, creditsLine].filter((line): line is string => typeof line === "string" && visibleWidth(stripAnsi(line)) > 0);
|
|
167
|
+
}
|
|
168
|
+
|
|
142
169
|
export function getVisibleExtensionStatuses(footerData: any): string[] {
|
|
143
170
|
let statuses: unknown;
|
|
144
171
|
try {
|
|
@@ -236,6 +263,66 @@ function renderContextSegment(usage: ContextUsage | null): string | null {
|
|
|
236
263
|
return applyHexColor(CONTEXT_COLORS.normal, formatTokens(usage.tokens));
|
|
237
264
|
}
|
|
238
265
|
|
|
266
|
+
function renderContextCostLine(input: BottomInputStatusState, usage: ContextUsage | null, width: number): string {
|
|
267
|
+
const contextText = formatContextStatus(input.ctx, usage);
|
|
268
|
+
const costInfo = readModelCostInfo(input.ctx);
|
|
269
|
+
const costText = formatModelCostInline(costInfo);
|
|
270
|
+
const left = safeFg(input.theme, "muted", contextText);
|
|
271
|
+
const right = safeFg(input.theme, costInfo.color, costText, costInfo.color === "success" ? "accent" : "text");
|
|
272
|
+
return renderEdgeAlignedLine(left, right, width);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function renderDuckmindCreditsLine(input: BottomInputStatusState, width: number): string | null {
|
|
276
|
+
if (!isDuckmindProvider(input.ctx)) return null;
|
|
277
|
+
const credits = readDuckmindCredits(input.footerData);
|
|
278
|
+
if (!credits) return null;
|
|
279
|
+
|
|
280
|
+
const left = [
|
|
281
|
+
safeFg(input.theme, "muted", "Credits Remaining"),
|
|
282
|
+
renderCreditProgressBar(credits, input.theme),
|
|
283
|
+
safeFg(input.theme, "accent", formatCreditNumber(credits.remaining)),
|
|
284
|
+
].join(" ");
|
|
285
|
+
const right = `${safeFg(input.theme, "muted", "Credits Used")} ${safeFg(input.theme, "accent", formatCreditNumber(credits.usage))} ${safeFg(input.theme, "muted", "• Credit")}`;
|
|
286
|
+
return renderEdgeAlignedLine(left, right, width);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function formatContextStatus(ctx: any, usage: ContextUsage | null): string {
|
|
290
|
+
const tokens = usage?.tokens ?? 0;
|
|
291
|
+
const contextWindow = usage?.contextWindow ?? readModelContextWindow(ctx);
|
|
292
|
+
const mode = `(${readContextManagementMode(ctx)})`;
|
|
293
|
+
if (contextWindow > 0) {
|
|
294
|
+
const usagePercent = usage?.percent;
|
|
295
|
+
const percent = typeof usagePercent === "number" && Number.isFinite(usagePercent)
|
|
296
|
+
? usagePercent
|
|
297
|
+
: (tokens / contextWindow) * 100;
|
|
298
|
+
return `${Math.max(0, percent).toFixed(1)}%/${formatTokens(contextWindow)} ${mode}`;
|
|
299
|
+
}
|
|
300
|
+
return `${formatTokens(tokens)} ${mode}`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function readContextManagementMode(ctx: any): string {
|
|
304
|
+
const candidates: unknown[] = [];
|
|
305
|
+
try {
|
|
306
|
+
candidates.push(ctx?.getAutoCompactionEnabled?.());
|
|
307
|
+
} catch {
|
|
308
|
+
// Ignore optional API probes; older runtimes do not expose this value.
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
candidates.push(ctx?.autoCompactionEnabled, ctx?.autoCompactEnabled, ctx?.session?.autoCompactionEnabled, ctx?.session?.autoCompactEnabled);
|
|
312
|
+
} catch {
|
|
313
|
+
// Keep the display stable if the host shape changes.
|
|
314
|
+
}
|
|
315
|
+
for (const candidate of candidates) {
|
|
316
|
+
if (candidate === true) return "auto";
|
|
317
|
+
if (candidate === false) return "manual";
|
|
318
|
+
if (typeof candidate === "string") {
|
|
319
|
+
const normalized = candidate.trim().toLowerCase();
|
|
320
|
+
if (normalized === "auto" || normalized === "manual" || normalized === "off") return normalized;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return "auto";
|
|
324
|
+
}
|
|
325
|
+
|
|
239
326
|
function renderElapsedSegment(theme: ThemeLike, startedAt: number, now: number, icons: BottomInputIconSet): string | null {
|
|
240
327
|
const elapsed = Math.max(0, now - startedAt);
|
|
241
328
|
if (elapsed < 1000) return null;
|
|
@@ -349,6 +436,135 @@ function readModelName(ctx: any): string | null {
|
|
|
349
436
|
}
|
|
350
437
|
}
|
|
351
438
|
|
|
439
|
+
type ModelCostInfo = {
|
|
440
|
+
label: string;
|
|
441
|
+
color: "success" | "muted" | "warning" | "error";
|
|
442
|
+
inputPerMillion: number | null;
|
|
443
|
+
outputPerMillion: number | null;
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
function readModelCostInfo(ctx: any): ModelCostInfo {
|
|
447
|
+
let model: any;
|
|
448
|
+
try {
|
|
449
|
+
model = ctx?.model;
|
|
450
|
+
} catch {
|
|
451
|
+
model = null;
|
|
452
|
+
}
|
|
453
|
+
if (model?.id === "@preset/free") return classifyModelCost(0, 0);
|
|
454
|
+
const input = toFiniteNumber(model?.cost?.input);
|
|
455
|
+
const output = toFiniteNumber(model?.cost?.output);
|
|
456
|
+
return classifyModelCost(input, output);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function classifyModelCost(inputPerMillion: number | null, outputPerMillion: number | null): ModelCostInfo {
|
|
460
|
+
if (inputPerMillion === null || outputPerMillion === null) {
|
|
461
|
+
return { label: "Unknown Cost", color: "muted", inputPerMillion: null, outputPerMillion: null };
|
|
462
|
+
}
|
|
463
|
+
if (inputPerMillion === 0 && outputPerMillion === 0) {
|
|
464
|
+
return { label: "Free", color: "success", inputPerMillion, outputPerMillion };
|
|
465
|
+
}
|
|
466
|
+
if (inputPerMillion > 20 || outputPerMillion > 100) {
|
|
467
|
+
return { label: "Critical Cost", color: "error", inputPerMillion, outputPerMillion };
|
|
468
|
+
}
|
|
469
|
+
if (inputPerMillion > 10 || outputPerMillion > 30) {
|
|
470
|
+
return { label: "High Cost", color: "error", inputPerMillion, outputPerMillion };
|
|
471
|
+
}
|
|
472
|
+
if ((inputPerMillion >= 1 && inputPerMillion <= 10) || (outputPerMillion >= 5 && outputPerMillion <= 30)) {
|
|
473
|
+
return { label: "Moderate Cost", color: "warning", inputPerMillion, outputPerMillion };
|
|
474
|
+
}
|
|
475
|
+
if (inputPerMillion < 1 && outputPerMillion < 5) {
|
|
476
|
+
return { label: "Low Cost", color: "success", inputPerMillion, outputPerMillion };
|
|
477
|
+
}
|
|
478
|
+
return { label: "Unknown Cost", color: "muted", inputPerMillion, outputPerMillion };
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function formatModelCostInline(info: ModelCostInfo): string {
|
|
482
|
+
return `${info.label} ${formatUsd(info.inputPerMillion)}/M in · ${formatUsd(info.outputPerMillion)}/M out`;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function formatUsd(value: number | null): string {
|
|
486
|
+
if (value === null) return "n/a";
|
|
487
|
+
if (value === 0) return "$0";
|
|
488
|
+
if (value >= 100) return `$${value.toFixed(0)}`;
|
|
489
|
+
if (value >= 10) return `$${value.toFixed(1).replace(/\.0$/, "")}`;
|
|
490
|
+
if (value >= 1) return `$${value.toFixed(2).replace(/0$/, "").replace(/\.$/, "")}`;
|
|
491
|
+
return `$${value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "")}`;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function readDuckmindCredits(footerData: any): DuckmindCreditsSnapshot | null {
|
|
495
|
+
let raw: unknown;
|
|
496
|
+
try {
|
|
497
|
+
raw = footerData?.getDuckmindCredits?.();
|
|
498
|
+
} catch {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
if (!isRecord(raw)) return null;
|
|
502
|
+
const usage = toFiniteNumber(raw.usage);
|
|
503
|
+
const remaining = toFiniteNumber(raw.remaining);
|
|
504
|
+
const limit = toFiniteNumber(raw.limit);
|
|
505
|
+
if (usage === null && remaining === null && limit === null) return null;
|
|
506
|
+
return {
|
|
507
|
+
usage,
|
|
508
|
+
remaining,
|
|
509
|
+
limit,
|
|
510
|
+
isFreeTier: typeof raw.isFreeTier === "boolean" ? raw.isFreeTier : null,
|
|
511
|
+
fetchedAt: toFiniteNumber(raw.fetchedAt),
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function renderCreditProgressBar(credits: DuckmindCreditsSnapshot, theme: ThemeLike): string {
|
|
516
|
+
const remaining = credits.remaining ?? 0;
|
|
517
|
+
const limit = credits.limit && credits.limit > 0 ? credits.limit : (remaining > 0 ? remaining : 0);
|
|
518
|
+
const percent = limit > 0 ? Math.max(0, Math.min(100, (remaining / limit) * 100)) : 0;
|
|
519
|
+
const filledCells = Math.round((percent / 100) * CREDIT_BAR_WIDTH);
|
|
520
|
+
const filled = "█".repeat(filledCells);
|
|
521
|
+
const empty = "░".repeat(Math.max(0, CREDIT_BAR_WIDTH - filledCells));
|
|
522
|
+
const color = percent <= 15 ? "error" : percent <= 35 ? "warning" : "success";
|
|
523
|
+
return `${safeFg(theme, "muted", "[")}${safeFg(theme, color, filled, "accent")}${safeFg(theme, "borderMuted", empty)}${safeFg(theme, "muted", "]")}`;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function isDuckmindProvider(ctx: any): boolean {
|
|
527
|
+
let values: unknown[] = [];
|
|
528
|
+
try {
|
|
529
|
+
const model = ctx?.model;
|
|
530
|
+
values = [
|
|
531
|
+
model?.provider,
|
|
532
|
+
model?.provider?.id,
|
|
533
|
+
model?.provider?.name,
|
|
534
|
+
model?.providerName,
|
|
535
|
+
model?.baseUrl,
|
|
536
|
+
model?.id,
|
|
537
|
+
ctx?.provider,
|
|
538
|
+
ctx?.provider?.id,
|
|
539
|
+
ctx?.provider?.name,
|
|
540
|
+
ctx?.provider?.baseUrl,
|
|
541
|
+
];
|
|
542
|
+
} catch {
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
return values.some((value) => typeof value === "string" && /duckmind|openrouter|@preset\//i.test(value));
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function renderEdgeAlignedLine(left: string, right: string, width: number): string {
|
|
549
|
+
const safeWidth = Math.max(1, Math.floor(width));
|
|
550
|
+
if (safeWidth <= 1) return truncateToWidth(left || right, safeWidth, "…", false);
|
|
551
|
+
const minGap = 2;
|
|
552
|
+
let rightPart = right;
|
|
553
|
+
let leftPart = left;
|
|
554
|
+
const originalLeftWidth = visibleWidth(leftPart);
|
|
555
|
+
const originalRightWidth = visibleWidth(rightPart);
|
|
556
|
+
if (originalLeftWidth + minGap + originalRightWidth > safeWidth) {
|
|
557
|
+
const rightBudget = Math.max(0, Math.min(originalRightWidth, Math.floor(safeWidth * 0.55)));
|
|
558
|
+
rightPart = rightBudget > 0 ? truncateToWidth(rightPart, rightBudget, "…", false) : "";
|
|
559
|
+
const leftBudget = Math.max(0, safeWidth - visibleWidth(rightPart) - (rightPart ? minGap : 0));
|
|
560
|
+
leftPart = leftBudget > 0 ? truncateToWidth(leftPart, leftBudget, "…", false) : "";
|
|
561
|
+
}
|
|
562
|
+
if (!leftPart) return truncateToWidth(rightPart, safeWidth, "…", false);
|
|
563
|
+
if (!rightPart) return truncateToWidth(leftPart, safeWidth, "…", false);
|
|
564
|
+
const gap = Math.max(1, safeWidth - visibleWidth(leftPart) - visibleWidth(rightPart));
|
|
565
|
+
return truncateToWidth(`${leftPart}${" ".repeat(gap)}${rightPart}`, safeWidth, "…", false);
|
|
566
|
+
}
|
|
567
|
+
|
|
352
568
|
export function renderContextBar(percent: number, color = CONTEXT_COLORS.normal): string {
|
|
353
569
|
const clamped = Math.max(0, Math.min(100, percent));
|
|
354
570
|
const filledCells = Math.floor((clamped / 100) * CONTEXT_BAR_WIDTH);
|
|
@@ -382,6 +598,23 @@ function formatDuration(ms: number): string {
|
|
|
382
598
|
return `${seconds}s`;
|
|
383
599
|
}
|
|
384
600
|
|
|
601
|
+
function formatCreditNumber(value: number | null): string {
|
|
602
|
+
if (value === null || !Number.isFinite(value)) return "n/a";
|
|
603
|
+
const abs = Math.abs(value);
|
|
604
|
+
if (abs >= 1000) return value.toFixed(0);
|
|
605
|
+
if (abs >= 100) return value.toFixed(1).replace(/\.0$/, "");
|
|
606
|
+
return value.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function toFiniteNumber(value: unknown): number | null {
|
|
610
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
611
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
612
|
+
const parsed = Number(value);
|
|
613
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
614
|
+
}
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
|
|
385
618
|
function safeFg(theme: ThemeLike, token: string, text: string, fallback = "text"): string {
|
|
386
619
|
try {
|
|
387
620
|
return theme.fg(token, text);
|