@toddzheng024/dscode-bundle 0.7.1 → 0.7.3
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/THIRD_PARTY_NOTICES.md +3 -0
- package/cordis.patch.yml +4 -0
- package/package.json +4 -2
- package/plugins/auto-review/index.mjs +21 -3
- package/plugins/auto-review/policy.mjs +25 -0
- package/plugins/code-review/git.mjs +24 -2
- package/plugins/code-review/index.mjs +13 -6
- package/plugins/credentials/index.mjs +7 -5
- package/plugins/exec/cli.mjs +63 -0
- package/plugins/exec/index.mjs +1 -1
- package/plugins/i18n/messages.mjs +6 -6
- package/plugins/memory/index.mjs +17 -12
- package/plugins/providers/catalog.mjs +134 -0
- package/plugins/session-cards/index.mjs +11 -8
- package/plugins/session-cards/manager.mjs +1 -1
- package/plugins/session-metrics/attribution.mjs +19 -0
- package/plugins/session-metrics/balance.mjs +71 -0
- package/plugins/session-metrics/index.mjs +39 -9
- package/plugins/session-metrics/pricing.mjs +39 -5
- package/plugins/session-metrics/view.mjs +36 -15
- package/plugins/tui-tools/doctor.mjs +9 -6
- package/plugins/tui-tools/index.mjs +2 -2
- package/plugins/ultra/policy.mjs +16 -0
- package/vendor/persistent/index.js +19 -2
- package/vendor/pi-ai/LICENSE +21 -0
- package/vendor/pi-ai/index.js +2702 -0
- package/vendor/pi-ai/types/adapter.d.ts +105 -0
- package/vendor/pi-ai/types/auth.d.ts +60 -0
- package/vendor/pi-ai/types/catalog.d.ts +355 -0
- package/vendor/pi-ai/types/config.d.ts +208 -0
- package/vendor/pi-ai/types/context.d.ts +42 -0
- package/vendor/pi-ai/types/discovery.d.ts +43 -0
- package/vendor/pi-ai/types/index.d.ts +69 -0
- package/vendor/pi-ai/types/login.d.ts +21 -0
- package/vendor/pi-ai/types/provider.d.ts +59 -0
- package/vendor/pi-ai/types/replay.d.ts +63 -0
- package/vendor/pi-ai/types/stream.d.ts +43 -0
- package/vendor/tui/dscode-providers/catalog.mjs +134 -0
- package/vendor/tui/index.mjs +310 -295
- package/vendor/tui/.dscode-viewport-probe-78546.mjs +0 -39239
- package/vendor/tui/dscode-email.mjs +0 -69
|
@@ -3,19 +3,22 @@ import { homedir } from 'node:os';
|
|
|
3
3
|
import { BlockAssembler, createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
4
4
|
import { SessionCards } from './manager.mjs';
|
|
5
5
|
import { TOPIC_PROMPT } from './content.mjs';
|
|
6
|
+
import { chargeTo } from '../session-metrics/attribution.mjs';
|
|
6
7
|
export const name = 'dscode-session-cards';
|
|
7
8
|
export const inject = ['sessions', 'llm'];
|
|
8
9
|
export function apply(ctx, config = {}) {
|
|
9
10
|
const home = process.env.DSH_HOME ?? process.env.DSCODE_HOME ?? join(homedir(), '.local/share/dscode-hub');
|
|
10
|
-
const cards = new SessionCards({ root: join(home, 'session-cards'), config, generate: async (input, route, signal) => {
|
|
11
|
+
const cards = new SessionCards({ root: join(home, 'session-cards'), config, generate: async (input, route, signal, sessionId) => {
|
|
11
12
|
const assembler = new BlockAssembler(); let finished = false, usage;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
await chargeTo(sessionId, 'session-card', async () => {
|
|
14
|
+
for await (const chunk of ctx.llm.stream({ ...route, reasoningEffort: 'low', system: TOPIC_PROMPT,
|
|
15
|
+
messages: [createUserMessage({ content: [{ type: 'text', text: JSON.stringify(input) }], source: { kind: 'plugin', plugin: name } })],
|
|
16
|
+
maxTokens: 2000, signal })) {
|
|
17
|
+
signal.throwIfAborted(); assembler.push(chunk);
|
|
18
|
+
if (chunk.type === 'finish') finished = true;
|
|
19
|
+
if (chunk.type === 'usage') usage = chunk.usage;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
19
22
|
if (!finished || assembler.finish.kind !== 'stop') throw Error('Incomplete topic response');
|
|
20
23
|
const blocks = assembler.blocks();
|
|
21
24
|
if (blocks.some(b => !['text', 'reasoning'].includes(b.type))) throw Error('Unexpected topic tool call');
|
|
@@ -105,7 +105,7 @@ export class SessionCards {
|
|
|
105
105
|
try {
|
|
106
106
|
const signal = AbortSignal.any([this.stop.signal, controller.signal, AbortSignal.timeout(this.config.timeoutMs)]);
|
|
107
107
|
const result = await this.generate({ messages, topicCount: this.config.topicCount },
|
|
108
|
-
{ provider: this.config.provider ?? state.route.provider, model: this.config.model ?? state.route.model }, signal);
|
|
108
|
+
{ provider: this.config.provider ?? state.route.provider, model: this.config.model ?? state.route.model }, signal, state.session.id);
|
|
109
109
|
usage = result.usage;
|
|
110
110
|
signal.throwIfAborted();
|
|
111
111
|
if (this.states.get(state.session.id) !== state || hash !== this.hash(state)) return;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Charge model calls a plugin makes for a session (review, memory, session cards,
|
|
2
|
+
// /doctor) to that session's cost ledger without putting the session on the wire:
|
|
3
|
+
// a `sessionId` request option also drives session-log delivery, provider cache
|
|
4
|
+
// affinity and agent-only prompt shaping, which these calls must not trigger. The
|
|
5
|
+
// ledger middleware runs when the caller iterates the stream, so it reads the charge
|
|
6
|
+
// from the caller's async context.
|
|
7
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
8
|
+
|
|
9
|
+
const charges = new AsyncLocalStorage();
|
|
10
|
+
|
|
11
|
+
/** Run `fn` with its model calls charged to `sessionId` under `purpose`; no session runs it uncharged. */
|
|
12
|
+
export function chargeTo(sessionId, purpose, fn) {
|
|
13
|
+
return sessionId ? charges.run({ sessionId, purpose }, fn) : fn();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** The charge of the model call being iterated, if any. */
|
|
17
|
+
export function currentCharge() {
|
|
18
|
+
return charges.getStore();
|
|
19
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Remaining provider balance, refreshed on a long cache. DeepSeek's response
|
|
2
|
+
// also carries the trusted clock: its `Date` header anchors peak/off-peak pricing
|
|
3
|
+
// without a second network call. OpenRouter has no peak window, so only its
|
|
4
|
+
// remaining credits are read.
|
|
5
|
+
const SOURCES = {
|
|
6
|
+
'deepseek-official': { url: 'https://api.deepseek.com/user/balance', env: 'DEEPSEEK_API_KEY', parse: body => parseBalance(body), clock: true },
|
|
7
|
+
openrouter: { url: 'https://openrouter.ai/api/v1/credits', env: 'OPENROUTER_API_KEY', parse: body => parseOpenRouterCredits(body), clock: false },
|
|
8
|
+
};
|
|
9
|
+
export const BALANCE_PROVIDERS = Object.freeze(Object.keys(SOURCES));
|
|
10
|
+
const CACHE_MS = 5 * 60 * 1000;
|
|
11
|
+
const RETRY_MS = 60 * 1000;
|
|
12
|
+
const snapshots = new Map();
|
|
13
|
+
let clock = { anchor: null, skewMs: 0 };
|
|
14
|
+
const snapshotOf = provider => snapshots.get(provider) ?? { balance: null, fetchedAt: 0, pending: false };
|
|
15
|
+
|
|
16
|
+
/** Best-effort positive USD balance from a `/user/balance` body. */
|
|
17
|
+
export function parseBalance(body) {
|
|
18
|
+
const infos = Array.isArray(body?.balance_infos) ? body.balance_infos : [];
|
|
19
|
+
const usd = infos.find(info => String(info?.currency ?? '').toUpperCase() === 'USD') ?? infos[0];
|
|
20
|
+
const total = Number(usd?.total_balance);
|
|
21
|
+
if (!Number.isFinite(total) || total < 0) return null;
|
|
22
|
+
return body?.is_available === false ? null : total;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Remaining USD credits from an OpenRouter `/credits` body: purchased minus used. */
|
|
26
|
+
export function parseOpenRouterCredits(body) {
|
|
27
|
+
const credits = Number(body?.data?.total_credits), used = Number(body?.data?.total_usage);
|
|
28
|
+
if (body?.data?.total_credits == null || body?.data?.total_usage == null) return null;
|
|
29
|
+
if (!Number.isFinite(credits) || !Number.isFinite(used) || credits < 0 || used < 0) return null;
|
|
30
|
+
return Math.max(0, credits - used);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function balanceNow(provider = 'deepseek-official') { return snapshotOf(provider).balance; }
|
|
34
|
+
/** Clock anchored to the last DeepSeek balance response's `Date` header, else the local one. */
|
|
35
|
+
export function trustedNow() {
|
|
36
|
+
const local = Date.now();
|
|
37
|
+
return clock.anchor === null ? local : local + clock.skewMs;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Refresh one provider at most once per cache window; never throws into the render path. */
|
|
41
|
+
export async function refreshBalance(options = {}) {
|
|
42
|
+
const provider = options.provider ?? 'deepseek-official';
|
|
43
|
+
const source = SOURCES[provider];
|
|
44
|
+
if (!source) return null;
|
|
45
|
+
const snapshot = snapshotOf(provider);
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
if (snapshot.pending || now - snapshot.fetchedAt < CACHE_MS) return snapshot.balance;
|
|
48
|
+
const retrySoon = () => Date.now() - (CACHE_MS - RETRY_MS);
|
|
49
|
+
const key = options.key ?? process.env[source.env];
|
|
50
|
+
if (!key) { snapshots.set(provider, { ...snapshot, fetchedAt: retrySoon(), pending: false }); return snapshot.balance; }
|
|
51
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
52
|
+
if (typeof fetchImpl !== 'function') return snapshot.balance;
|
|
53
|
+
snapshots.set(provider, { ...snapshot, pending: true });
|
|
54
|
+
try {
|
|
55
|
+
const response = await fetchImpl(source.url, { headers: { Authorization: `Bearer ${key}`, Accept: 'application/json' } });
|
|
56
|
+
const header = source.clock ? response.headers?.get?.('date') : null;
|
|
57
|
+
const anchor = header ? Date.parse(header) : NaN;
|
|
58
|
+
const body = await response.json();
|
|
59
|
+
const parsed = response.ok ? source.parse(body) : null;
|
|
60
|
+
if (Number.isFinite(anchor)) clock = { anchor, skewMs: anchor - Date.now() };
|
|
61
|
+
snapshots.set(provider, {
|
|
62
|
+
balance: parsed === null && response.ok ? null : parsed ?? snapshot.balance,
|
|
63
|
+
fetchedAt: parsed === null ? retrySoon() : Date.now(),
|
|
64
|
+
pending: false,
|
|
65
|
+
});
|
|
66
|
+
} catch {
|
|
67
|
+
// A transient failure keeps the last known balance and retries sooner.
|
|
68
|
+
snapshots.set(provider, { ...snapshot, fetchedAt: retrySoon(), pending: false });
|
|
69
|
+
}
|
|
70
|
+
return snapshotOf(provider).balance;
|
|
71
|
+
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { appendMetric } from './store.mjs';
|
|
3
|
-
import { estimateCost,
|
|
3
|
+
import { estimateCost, priceVersionFor } from './pricing.mjs';
|
|
4
4
|
import { setMetricSource } from './view.mjs';
|
|
5
|
+
import { BALANCE_PROVIDERS, refreshBalance } from './balance.mjs';
|
|
6
|
+
import { providerSpec } from '../providers/catalog.mjs';
|
|
5
7
|
import { createWindowRate } from './rate.mjs';
|
|
8
|
+
import { currentCharge } from './attribution.mjs';
|
|
9
|
+
|
|
10
|
+
// Chunks that carry generated output; the first one marks time to first token.
|
|
11
|
+
const OUTPUT_CHUNKS = new Set(['text-delta', 'reasoning-delta', 'tool-call-delta']);
|
|
6
12
|
export const name = 'dscode-session-metrics';
|
|
7
13
|
export const inject = ['llm', 'agents', 'tokenMeter', 'sessionProjections'];
|
|
8
14
|
export function apply(ctx) {
|
|
@@ -21,29 +27,53 @@ export function apply(ctx) {
|
|
|
21
27
|
return { ...value, currentTps: liveRate.get(session) };
|
|
22
28
|
}));
|
|
23
29
|
const home = process.env.DSH_HOME;
|
|
30
|
+
// Remaining balance is best-effort decoration: resolve the key lazily (never
|
|
31
|
+
// inject the credentials service, so a missing one cannot fail startup), keep
|
|
32
|
+
// the request on a five-minute cache, and never let it reach the render path.
|
|
33
|
+
const credentials = ctx.get?.('credentials');
|
|
34
|
+
const refresh = () => Promise.all(BALANCE_PROVIDERS.map(async provider => {
|
|
35
|
+
try {
|
|
36
|
+
const ref = providerSpec(provider).credentialRef;
|
|
37
|
+
const resolved = await credentials?.resolve?.(ref);
|
|
38
|
+
const key = typeof resolved === 'string' ? resolved : resolved?.value;
|
|
39
|
+
await refreshBalance({ provider, key: key ?? process.env[ref] });
|
|
40
|
+
} catch {
|
|
41
|
+
/* balance stays unknown */
|
|
42
|
+
}
|
|
43
|
+
}));
|
|
44
|
+
refresh();
|
|
45
|
+
const balanceTimer = setInterval(refresh, 5 * 60 * 1000);
|
|
46
|
+
if (typeof balanceTimer.unref === 'function') balanceTimer.unref();
|
|
47
|
+
ctx.effect(() => () => clearInterval(balanceTimer));
|
|
24
48
|
const record = (id, entry) => { try { appendMetric(home, id, entry); } catch { ctx.logger.warn('Session cost telemetry could not be saved.'); } };
|
|
25
49
|
ctx.on('llm/stream', async function* (options, next) {
|
|
26
|
-
|
|
50
|
+
// Plugin calls made for a session carry no sessionId on the wire; they are charged through the async context.
|
|
51
|
+
const charge = options.sessionId ? undefined : currentCharge();
|
|
52
|
+
const sessionId = options.sessionId ?? charge?.sessionId;
|
|
53
|
+
if (!sessionId || !home) { yield* next(); return; }
|
|
27
54
|
const id = randomUUID(), time = Date.now();
|
|
28
|
-
const
|
|
29
|
-
|
|
55
|
+
const purpose = options.purpose ?? charge?.purpose ?? 'agent';
|
|
56
|
+
const recipients = new Set([sessionId]);
|
|
57
|
+
let child = ctx.agents.get(sessionId);
|
|
30
58
|
while (child?.session.header.origin === 'subagent' && child.session.header.parentSession && !recipients.has(child.session.header.parentSession)) {
|
|
31
59
|
recipients.add(child.session.header.parentSession);
|
|
32
60
|
child = ctx.agents.get(child.session.header.parentSession);
|
|
33
61
|
}
|
|
34
|
-
const save = entry => { for (const recipient of recipients) record(recipient, { ...entry, sessionId
|
|
35
|
-
save(
|
|
36
|
-
let usage;
|
|
37
|
-
const liveSession =
|
|
62
|
+
const save = entry => { for (const recipient of recipients) record(recipient, { ...entry, sessionId }); };
|
|
63
|
+
save({ kind: 'start', id, time, provider: options.provider, model: options.model, purpose });
|
|
64
|
+
let usage, firstTokenTime;
|
|
65
|
+
const liveSession = purpose === 'agent' ? ctx.agents.get(sessionId)?.session : undefined;
|
|
38
66
|
try {
|
|
39
67
|
for await (const chunk of next()) {
|
|
40
68
|
if (chunk.type === 'usage') usage = chunk.usage;
|
|
69
|
+
else if (firstTokenTime === undefined && OUTPUT_CHUNKS.has(chunk.type)) firstTokenTime = Date.now();
|
|
41
70
|
if (liveSession) liveRate.add(liveSession, chunk);
|
|
42
71
|
yield chunk;
|
|
43
72
|
}
|
|
44
73
|
} finally {
|
|
45
74
|
if (liveSession) liveRate.calibrate(liveSession, usage?.outputTokens);
|
|
46
|
-
|
|
75
|
+
// `time` stays the start (it prices the call); `endTime` and `firstTokenTime` time it.
|
|
76
|
+
save({ kind: 'end', id, time, endTime: Date.now(), ...(firstTokenTime === undefined ? {} : { firstTokenTime }), usage: usage ?? null, cost: estimateCost(options.provider, options.model, usage, time), priceVersion: priceVersionFor(options.provider) });
|
|
47
77
|
}
|
|
48
78
|
});
|
|
49
79
|
}
|
|
@@ -2,17 +2,51 @@
|
|
|
2
2
|
// https://api-docs.deepseek.com/quick_start/pricing/
|
|
3
3
|
export const PRICE_SOURCE = 'https://api-docs.deepseek.com/quick_start/pricing/';
|
|
4
4
|
export const PRICE_VERSION = 'deepseek-2026-09-11';
|
|
5
|
+
// OpenRouter list prices for the DeepSeek models `/provider openrouter` declares,
|
|
6
|
+
// from the pinned pi-ai 0.85.1 catalog (its OpenRouter /models snapshot).
|
|
7
|
+
// OpenRouter bills no peak window. [cache read, input, output].
|
|
8
|
+
export const OPENROUTER_PRICE_VERSION = 'openrouter-pi-ai-0.85.1';
|
|
9
|
+
const OPENROUTER_PRICES = {
|
|
10
|
+
'deepseek/deepseek-v4-flash': [0.017052, 0.08526, 0.17052],
|
|
11
|
+
'deepseek/deepseek-v4-pro': [0.074196, 0.890358, 1.780716],
|
|
12
|
+
'deepseek/deepseek-v4-flash-vision-exp': [0.007, 0.22, 0.66],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** The price table a provider's ledger entries are estimated with. */
|
|
16
|
+
export function priceVersionFor(provider) {
|
|
17
|
+
return provider === 'openrouter' ? OPENROUTER_PRICE_VERSION : PRICE_VERSION;
|
|
18
|
+
}
|
|
19
|
+
|
|
5
20
|
export function estimateCost(provider, model, usage, time) {
|
|
6
|
-
if (
|
|
21
|
+
if (!usage || !Number.isFinite(time)) return null;
|
|
22
|
+
if (provider === 'openrouter') return OPENROUTER_PRICES[model] ? charge(usage, OPENROUTER_PRICES[model]) : null;
|
|
23
|
+
if (provider !== 'deepseek-official') return null;
|
|
7
24
|
// Earlier requests require an older price table; never back-price them at today's rate.
|
|
8
25
|
if (time < Date.UTC(2026, 8, 11)) return null;
|
|
9
26
|
const flash = ['deepseek-flash', 'deepseek-v4-flash', 'deepseek-v4-flash-vision-exp'].includes(model)
|
|
10
27
|
|| model === 'deepseek-v4-pro' && time >= Date.UTC(2026, 8, 14, 4);
|
|
11
28
|
if (!flash && model !== 'deepseek-v4-pro') return null;
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
29
|
+
const cost = charge(usage, flash ? [0.003, 0.15, 0.6] : [0.022, 0.66, 1.98]);
|
|
30
|
+
return cost === null ? null : cost * (isPeak(time) ? 2 : 1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function charge(usage, [read, input, output]) {
|
|
15
34
|
const values = [usage.inputTokens, usage.outputTokens, usage.cacheReadTokens ?? 0, usage.cacheWriteTokens ?? 0];
|
|
16
35
|
if (!values.every(n => Number.isFinite(n) && n >= 0) || values[3] !== 0) return null;
|
|
17
|
-
return (values[0] * input + values[1] * output + values[2] * read)
|
|
36
|
+
return (values[0] * input + values[1] * output + values[2] * read) / 1e6;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Peak/off-peak window in UTC: weekdays 01:00-04:00 and 06:00-10:00.
|
|
41
|
+
* @param time - Unix ms timestamp.
|
|
42
|
+
* @returns Whether that instant bills at the peak rate.
|
|
43
|
+
*/
|
|
44
|
+
export function isPeak(time) {
|
|
45
|
+
const date = new Date(time), day = date.getUTCDay(), hour = date.getUTCHours();
|
|
46
|
+
return day >= 1 && day <= 5 && (hour >= 1 && hour < 4 || hour >= 6 && hour < 10);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Footer marker for the billing window: fire for peak, snowflake for off-peak. */
|
|
50
|
+
export function peakEmoji(time) {
|
|
51
|
+
return isPeak(time) ? '🔥' : '❄️';
|
|
18
52
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { readMetrics } from './store.mjs';
|
|
2
2
|
import { t } from '../i18n/messages.mjs';
|
|
3
|
-
import { estimateCost } from './pricing.mjs';
|
|
3
|
+
import { estimateCost, peakEmoji } from './pricing.mjs';
|
|
4
|
+
import { balanceNow, trustedNow } from './balance.mjs';
|
|
4
5
|
import { sessionAverageTps } from './rate.mjs';
|
|
6
|
+
import { providerOfHeader } from '../providers/catalog.mjs';
|
|
5
7
|
let source;
|
|
6
8
|
export function setMetricSource(next) { source = next; return () => { if (source === next) source = undefined; }; }
|
|
7
9
|
export function summarize(rows, events = [], corrupt = false) {
|
|
@@ -37,31 +39,50 @@ export function summarize(rows, events = [], corrupt = false) {
|
|
|
37
39
|
}
|
|
38
40
|
return { cost, unknown, calls, pending, cache: input > 0 && !cacheUnknown ? Math.min(100, hit / input * 100) : null };
|
|
39
41
|
}
|
|
40
|
-
/** Terminal columns of a string: East Asian wide characters (
|
|
42
|
+
/** Terminal columns of a string: East Asian wide characters (such as the cache label's CJK glyphs) take two. */
|
|
41
43
|
export function displayWidth(text) {
|
|
42
44
|
let width = 0;
|
|
43
|
-
for (const char of text) width += /[\u1100-\u115f\u2e80-\ua4cf\uac00-\ud7a3\uf900-\ufaff\ufe30-\ufe4f\uff00-\uff60\uffe0-\uffe6]
|
|
45
|
+
for (const char of text) width += /[\u1100-\u115f\u2e80-\ua4cf\uac00-\ud7a3\uf900-\ufaff\ufe30-\ufe4f\uff00-\uff60\uffe0-\uffe6\u2600-\u27bf\u{1f300}-\u{1faff}]/u.test(char) ? 2 : 1;
|
|
44
46
|
return width;
|
|
45
47
|
}
|
|
46
|
-
export function formatFooter(metrics, context, columns = 80, rates, locale = 'en') {
|
|
48
|
+
export function formatFooter(metrics, context, columns = 80, rates, locale = 'en', header = '', provider = providerOfHeader(header) ?? 'deepseek-official') {
|
|
47
49
|
const label = key => t(locale, key);
|
|
48
50
|
const ctx = Number.isFinite(context) ? `${Math.round(context)}%` : '--';
|
|
49
51
|
const cache = metrics.cache === null ? '--' : `${metrics.cache.toFixed(1)}%`;
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
+
// The balance belongs to the provider the header names; only DeepSeek's official route bills by a peak window.
|
|
53
|
+
const balance = balanceNow(provider);
|
|
54
|
+
const spend = metrics.unknown && metrics.cost === 0 ? '--' : `$${metrics.cost.toFixed(2)}${metrics.unknown ? '+' : ''}${metrics.pending ? '…' : ''}`;
|
|
55
|
+
const dollars = `${spend} / ${balance === null ? '$--' : '$' + balance.toFixed(2)}${provider === 'deepseek-official' ? ' ' + peakEmoji(trustedNow()) : ''}`;
|
|
56
|
+
const base = rates ? [
|
|
52
57
|
`${label('footer.current')}: ${Number.isFinite(rates.current) ? '~' + rates.current.toFixed(1) : '--'} tps`,
|
|
53
58
|
`${label('footer.average')}: ${Number.isFinite(rates.average) ? rates.average.toFixed(1) : '--'} tps`,
|
|
54
|
-
`${label('footer.context')}: ${ctx}`, dollars, `${label('footer.cache')} ${cache}`,
|
|
55
|
-
] : [`${label('footer.context')}: ${ctx}`, dollars, `${label('footer.cache')} ${cache}`];
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
`${label('footer.context')}: ${ctx}`, dollars, `${label('footer.cache')}: ${cache}`,
|
|
60
|
+
] : [`${label('footer.context')}: ${ctx}`, dollars, `${label('footer.cache')}: ${cache}`];
|
|
61
|
+
// Narrow terminals shed the quietest figures first: average, cache hit, current. The
|
|
62
|
+
// model header then falls back to its bare `model @ effort` form, then context goes,
|
|
63
|
+
// and only then the header itself — the running cost is the last thing standing.
|
|
64
|
+
const drops = rates ? [1, 4, 0, 2] : [2, 0];
|
|
65
|
+
const offset = header === '' ? 0 : 1;
|
|
66
|
+
const parts = header === '' ? base : [header, ...base];
|
|
67
|
+
const short = header.replace(/^[^:]+: /, '');
|
|
68
|
+
const heads = header === '' ? [''] : short === header ? [header] : [header, short];
|
|
69
|
+
const render = ({ omit, head }) => parts
|
|
70
|
+
.map((part, index) => (index === 0 && header !== '' ? head : part))
|
|
71
|
+
.filter((part, index) => part !== '' && !omit.has(index))
|
|
72
|
+
.join(' | ');
|
|
73
|
+
for (let dropped = 0; dropped <= drops.length; dropped++) {
|
|
74
|
+
const omit = new Set(drops.slice(0, dropped).map(index => index + offset));
|
|
75
|
+
for (const head of heads) {
|
|
76
|
+
const value = render({ omit, head });
|
|
77
|
+
if (displayWidth(value) <= columns) return value;
|
|
78
|
+
}
|
|
59
79
|
}
|
|
80
|
+
const floor = render({ omit: new Set(drops.map(index => index + offset)), head: '' });
|
|
60
81
|
let clipped = '';
|
|
61
|
-
for (const char of
|
|
82
|
+
for (const char of floor) { if (displayWidth(clipped + char) > columns) break; clipped += char; }
|
|
62
83
|
return clipped;
|
|
63
84
|
}
|
|
64
|
-
export function footerFor(id, stats, columns, locale = 'en') {
|
|
85
|
+
export function footerFor(id, stats, columns, header = '', locale = 'en') {
|
|
65
86
|
try {
|
|
66
87
|
const data = id ? source?.(id) : undefined;
|
|
67
88
|
const ledger = id && process.env.DSH_HOME ? readMetrics(process.env.DSH_HOME, id) : { rows: [], corrupt: false };
|
|
@@ -69,6 +90,6 @@ export function footerFor(id, stats, columns, locale = 'en') {
|
|
|
69
90
|
const used = data?.used;
|
|
70
91
|
const capacity = data?.capacity ?? stats.contextWindow;
|
|
71
92
|
const average = sessionAverageTps(data?.events ?? []);
|
|
72
|
-
return formatFooter(summary, Number.isFinite(used) && capacity > 0 ? used / capacity * 100 : undefined, columns, { current: data?.currentTps, average }, locale);
|
|
73
|
-
} catch { return formatFooter({ cost: 0, unknown: true, cache: null }, undefined, columns, { current: null, average: null }, locale); }
|
|
93
|
+
return formatFooter(summary, Number.isFinite(used) && capacity > 0 ? used / capacity * 100 : undefined, columns, { current: data?.currentTps, average }, locale, header);
|
|
94
|
+
} catch { return formatFooter({ cost: 0, unknown: true, cache: null }, undefined, columns, { current: null, average: null }, locale, header); }
|
|
74
95
|
}
|
|
@@ -4,6 +4,7 @@ import { Logger } from '@deepseek-ai/cordis';
|
|
|
4
4
|
import { BlockAssembler, createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
5
5
|
import { redact } from '../auto-review/policy.mjs';
|
|
6
6
|
import { t, readLanguage } from '../i18n/messages.mjs';
|
|
7
|
+
import { chargeTo } from '../session-metrics/attribution.mjs';
|
|
7
8
|
const L = (key, params) => t(readLanguage(), key, params);
|
|
8
9
|
|
|
9
10
|
const MAX_LOG_BYTES = 1024 * 1024;
|
|
@@ -118,7 +119,7 @@ export function localDoctorReport(evidence) {
|
|
|
118
119
|
return `${L('doctor.evidence', { traces: evidence.traces.length, logs: evidence.logs.length })}${evidence.logs.length ? '' : evidence.logCoverage ?? ''}\n${local.length ? local.slice(-8).join('\n') : L('doctor.noFindings')}\n${near300.length >= 2 ? `${L('doctor.nearTimeout', { count: near300.length })}\n` : ''}${evidence.logs.slice(-5).map(l => `${time(l.time)} ${l.level} ${l.source}: ${l.detail}`).join('\n')}`;
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
export async function analyzeDoctorEvidence(ctx, evidence, route, signal, { model = true } = {}) {
|
|
122
|
+
export async function analyzeDoctorEvidence(ctx, evidence, route, signal, { model = true, sessionId } = {}) {
|
|
122
123
|
const fallback = localDoctorReport(evidence);
|
|
123
124
|
if (!model) return fallback;
|
|
124
125
|
if (!route?.provider || !route?.model) return `${fallback}\n${L('doctor.noRoute')}`;
|
|
@@ -126,11 +127,13 @@ export async function analyzeDoctorEvidence(ctx, evidence, route, signal, { mode
|
|
|
126
127
|
const deadline = AbortSignal.any([signal ?? new AbortController().signal, AbortSignal.timeout(45000)]);
|
|
127
128
|
try {
|
|
128
129
|
let finished = false;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
await chargeTo(sessionId, 'doctor', async () => {
|
|
131
|
+
for await (const chunk of ctx.llm.stream({ provider: route.provider, model: route.model, reasoningEffort: 'low', maxTokens: 4096, system: SYSTEM,
|
|
132
|
+
messages: [createUserMessage({ content: [{ type: 'text', text: JSON.stringify(evidence) }], source: { kind: 'plugin', plugin: 'dscode-doctor' } })], signal: deadline })) {
|
|
133
|
+
deadline.throwIfAborted(); assembler.push(chunk);
|
|
134
|
+
if (chunk.type === 'finish') finished = true;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
134
137
|
if (!finished || assembler.finish.kind !== 'stop') throw Error(`model response was incomplete (${safe(JSON.stringify(assembler.finish ?? { kind: 'no finish' }))})`);
|
|
135
138
|
const blocks = assembler.blocks();
|
|
136
139
|
if (blocks.some(b => !['text', 'reasoning'].includes(b.type))) throw Error('model returned unexpected tool output');
|
|
@@ -92,7 +92,7 @@ export function apply(ctx) {
|
|
|
92
92
|
`Session: ${session.id}`, `Workspace: ${session.header.cwd ?? process.cwd()}`,
|
|
93
93
|
`Agent: ${agent.status} | preset: ${session.header.agentPreset ?? 'standard'}`,
|
|
94
94
|
`Model: ${route?.provider ?? 'default'} / ${route?.model ?? 'default'}`,
|
|
95
|
-
`Effort: ${route?.reasoningEffort ?? 'model default'}${route?.reasoningEffort === 'ultra' ? '
|
|
95
|
+
`Effort: ${route?.reasoningEffort ?? 'model default'}${route?.reasoningEffort === 'ultra' ? ` (${route.provider === 'openrouter' ? 'OpenRouter wire: xhigh' : 'DeepSeek wire: max'}; collaboration enabled)` : ''}`,
|
|
96
96
|
`Permission: ${show(ctx.permissionPresets.current(session))}`,
|
|
97
97
|
`Tokens: ${show(usage ?? 'no provider usage yet')}`,
|
|
98
98
|
`Context: ${pressure?.pressureTokens ?? pressure?.surfaceTokens ?? '?'} / ${pressure?.contextWindow ?? '?'} tokens`,
|
|
@@ -120,7 +120,7 @@ export function apply(ctx) {
|
|
|
120
120
|
const evidence = await collectDoctorEvidence(ctx, { agent, signal });
|
|
121
121
|
if (action === 'preview') return ok(JSON.stringify(evidence, null, 2));
|
|
122
122
|
const route = agent.session.requestHeader()?.config ?? agent.options;
|
|
123
|
-
return ok(`${health}\n\n${await analyzeDoctorEvidence(ctx, evidence, route, signal, { model: action !== 'local' })}`);
|
|
123
|
+
return ok(`${health}\n\n${await analyzeDoctorEvidence(ctx, evidence, route, signal, { model: action !== 'local', sessionId: agent.session.id })}`);
|
|
124
124
|
});
|
|
125
125
|
register('mcp', 'MCP list, tools <id>, enable/disable/reconnect <id>', async ({ agent, rawInput }) => {
|
|
126
126
|
const [action = 'list', id, extra] = rawInput.trim().split(/\s+/).filter(Boolean);
|
package/plugins/ultra/policy.mjs
CHANGED
|
@@ -16,6 +16,22 @@ export function flashRequest(options, messages) {
|
|
|
16
16
|
return copy;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* The same shaping for pi-ai routes (OpenRouter), on harness-format options:
|
|
21
|
+
* Ultra sends max and adds the collaboration policy; below Ultra the delegation
|
|
22
|
+
* tools are not offered. Returns a copy; the logged input is never mutated.
|
|
23
|
+
*/
|
|
24
|
+
export function piAiRequest(options) {
|
|
25
|
+
const ultra = options.reasoningEffort === 'ultra';
|
|
26
|
+
const hidden = ultra ? ['workflow', 'ralph'] : ['subagent', 'subagent_fork', 'workflow', 'ralph'];
|
|
27
|
+
const next = { ...options, ...(options.tools ? { tools: options.tools.filter(tool => !hidden.includes(tool.name)) } : {}), ...(ultra ? { reasoningEffort: 'max' } : {}) };
|
|
28
|
+
if (!ultra || options.purpose || !options.tools?.some(t => t.name === 'subagent' || t.name === 'subagent_fork')) return next;
|
|
29
|
+
if (typeof next.system === 'string') return { ...next, system: next.system + '\n\n' + ULTRA_POLICY };
|
|
30
|
+
const [first, ...rest] = next.messages ?? [];
|
|
31
|
+
if (first?.role === 'system' && Array.isArray(first.content)) return { ...next, messages: [{ ...first, content: [...first.content, { type: 'text', text: '\n\n' + ULTRA_POLICY }] }, ...rest] };
|
|
32
|
+
return { ...next, messages: [{ role: 'system', content: [{ type: 'text', text: ULTRA_POLICY }] }, ...(next.messages ?? [])] };
|
|
33
|
+
}
|
|
34
|
+
|
|
19
35
|
export function ultraRequest(options, messages) {
|
|
20
36
|
if (options.reasoningEffort !== 'ultra' || options.purpose || !options.tools?.some(t => t.name === 'subagent' || t.name === 'subagent_fork')) return messages;
|
|
21
37
|
const copy = messages.map(m => ({ ...m }));
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// dscode-stdin-stall-v2
|
|
2
|
+
const STDIN_STALL_MS = 5000;
|
|
3
|
+
const STDIN_STALL_NOTE = "\n[Interrupted after 5s with no output: the command held the terminal without producing anything, and this tool cannot supply terminal input. Give it stdin (a heredoc, `< file` or `< /dev/null`) or use a non-interactive form.]";
|
|
1
4
|
// dscode-shell-reset-v1
|
|
2
5
|
import { randomUUID } from "node:crypto";
|
|
3
6
|
import z from "@deepseek-ai/schemastery";
|
|
@@ -255,6 +258,9 @@ async function executeCommand(ctx, shells, owner, command, config, upstream) {
|
|
|
255
258
|
const id = await shells.get(owner, commandDeadline.signal);
|
|
256
259
|
const marker = markers();
|
|
257
260
|
const wrapped = wrapCommand(command, marker);
|
|
261
|
+
const startedAt = Date.now();
|
|
262
|
+
let stdinWaited = false;
|
|
263
|
+
let stdinStalled = false;
|
|
258
264
|
let first = true;
|
|
259
265
|
let fallback = "";
|
|
260
266
|
let fallbackTruncated = false;
|
|
@@ -298,10 +304,21 @@ async function executeCommand(ctx, shells, owner, command, config, upstream) {
|
|
|
298
304
|
}
|
|
299
305
|
if (latest.text.includes(marker.end)) {
|
|
300
306
|
const complete = commandOutput(retainedScrollback(ctx, owner, id, latest), marker);
|
|
301
|
-
if (complete !== void 0) return renderCaptured(complete, config.maxOutputChars);
|
|
307
|
+
if (complete !== void 0) return renderCaptured(complete, config.maxOutputChars) + (stdinStalled ? STDIN_STALL_NOTE : "");
|
|
302
308
|
}
|
|
303
309
|
if (result.sessionStatus.kind === "exited") return await respondToSessionExit(ctx, shells, owner, id, result.sessionStatus, marker, fallback, fallbackTruncated, config);
|
|
304
|
-
|
|
310
|
+
const partial = renderCaptured(partialOutput(retainedScrollback(ctx, owner, id, latest), marker, fallback, fallbackTruncated), config.maxOutputChars);
|
|
311
|
+
if (result.waitReason === "stdin_read") stdinWaited = true;
|
|
312
|
+
if (result.waitReason === "stdin_read" && partial.length > 0) return partial;
|
|
313
|
+
if (stdinWaited && partial.length === 0 && !stdinStalled && Date.now() - startedAt >= STDIN_STALL_MS) {
|
|
314
|
+
stdinStalled = true;
|
|
315
|
+
try {
|
|
316
|
+
await ctx.terminals.signal(owner, id, "SIGINT");
|
|
317
|
+
} catch (_unsignallableForeground) {
|
|
318
|
+
stdinStalled = false;
|
|
319
|
+
return partial;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
305
322
|
await pause();
|
|
306
323
|
}
|
|
307
324
|
} catch (e_1) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|