@toddzheng024/dscode-bundle 0.7.2 → 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/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/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 +41 -22
- package/plugins/session-metrics/index.mjs +26 -14
- package/plugins/session-metrics/pricing.mjs +24 -4
- package/plugins/session-metrics/view.mjs +5 -3
- 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/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 +146 -59
|
@@ -1,9 +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 { refreshBalance } from './balance.mjs';
|
|
5
|
+
import { BALANCE_PROVIDERS, refreshBalance } from './balance.mjs';
|
|
6
|
+
import { providerSpec } from '../providers/catalog.mjs';
|
|
6
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']);
|
|
7
12
|
export const name = 'dscode-session-metrics';
|
|
8
13
|
export const inject = ['llm', 'agents', 'tokenMeter', 'sessionProjections'];
|
|
9
14
|
export function apply(ctx) {
|
|
@@ -26,42 +31,49 @@ export function apply(ctx) {
|
|
|
26
31
|
// inject the credentials service, so a missing one cannot fail startup), keep
|
|
27
32
|
// the request on a five-minute cache, and never let it reach the render path.
|
|
28
33
|
const credentials = ctx.get?.('credentials');
|
|
29
|
-
const refresh =
|
|
34
|
+
const refresh = () => Promise.all(BALANCE_PROVIDERS.map(async provider => {
|
|
30
35
|
try {
|
|
31
|
-
const
|
|
36
|
+
const ref = providerSpec(provider).credentialRef;
|
|
37
|
+
const resolved = await credentials?.resolve?.(ref);
|
|
32
38
|
const key = typeof resolved === 'string' ? resolved : resolved?.value;
|
|
33
|
-
await refreshBalance({ key: key ?? process.env
|
|
39
|
+
await refreshBalance({ provider, key: key ?? process.env[ref] });
|
|
34
40
|
} catch {
|
|
35
41
|
/* balance stays unknown */
|
|
36
42
|
}
|
|
37
|
-
};
|
|
43
|
+
}));
|
|
38
44
|
refresh();
|
|
39
45
|
const balanceTimer = setInterval(refresh, 5 * 60 * 1000);
|
|
40
46
|
if (typeof balanceTimer.unref === 'function') balanceTimer.unref();
|
|
41
47
|
ctx.effect(() => () => clearInterval(balanceTimer));
|
|
42
48
|
const record = (id, entry) => { try { appendMetric(home, id, entry); } catch { ctx.logger.warn('Session cost telemetry could not be saved.'); } };
|
|
43
49
|
ctx.on('llm/stream', async function* (options, next) {
|
|
44
|
-
|
|
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; }
|
|
45
54
|
const id = randomUUID(), time = Date.now();
|
|
46
|
-
const
|
|
47
|
-
|
|
55
|
+
const purpose = options.purpose ?? charge?.purpose ?? 'agent';
|
|
56
|
+
const recipients = new Set([sessionId]);
|
|
57
|
+
let child = ctx.agents.get(sessionId);
|
|
48
58
|
while (child?.session.header.origin === 'subagent' && child.session.header.parentSession && !recipients.has(child.session.header.parentSession)) {
|
|
49
59
|
recipients.add(child.session.header.parentSession);
|
|
50
60
|
child = ctx.agents.get(child.session.header.parentSession);
|
|
51
61
|
}
|
|
52
|
-
const save = entry => { for (const recipient of recipients) record(recipient, { ...entry, sessionId
|
|
53
|
-
save(
|
|
54
|
-
let usage;
|
|
55
|
-
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;
|
|
56
66
|
try {
|
|
57
67
|
for await (const chunk of next()) {
|
|
58
68
|
if (chunk.type === 'usage') usage = chunk.usage;
|
|
69
|
+
else if (firstTokenTime === undefined && OUTPUT_CHUNKS.has(chunk.type)) firstTokenTime = Date.now();
|
|
59
70
|
if (liveSession) liveRate.add(liveSession, chunk);
|
|
60
71
|
yield chunk;
|
|
61
72
|
}
|
|
62
73
|
} finally {
|
|
63
74
|
if (liveSession) liveRate.calibrate(liveSession, usage?.outputTokens);
|
|
64
|
-
|
|
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) });
|
|
65
77
|
}
|
|
66
78
|
});
|
|
67
79
|
}
|
|
@@ -2,18 +2,38 @@
|
|
|
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
|
-
|
|
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]) {
|
|
14
34
|
const values = [usage.inputTokens, usage.outputTokens, usage.cacheReadTokens ?? 0, usage.cacheWriteTokens ?? 0];
|
|
15
35
|
if (!values.every(n => Number.isFinite(n) && n >= 0) || values[3] !== 0) return null;
|
|
16
|
-
return (values[0] * input + values[1] * output + values[2] * read)
|
|
36
|
+
return (values[0] * input + values[1] * output + values[2] * read) / 1e6;
|
|
17
37
|
}
|
|
18
38
|
|
|
19
39
|
/**
|
|
@@ -3,6 +3,7 @@ import { t } from '../i18n/messages.mjs';
|
|
|
3
3
|
import { estimateCost, peakEmoji } from './pricing.mjs';
|
|
4
4
|
import { balanceNow, trustedNow } from './balance.mjs';
|
|
5
5
|
import { sessionAverageTps } from './rate.mjs';
|
|
6
|
+
import { providerOfHeader } from '../providers/catalog.mjs';
|
|
6
7
|
let source;
|
|
7
8
|
export function setMetricSource(next) { source = next; return () => { if (source === next) source = undefined; }; }
|
|
8
9
|
export function summarize(rows, events = [], corrupt = false) {
|
|
@@ -44,13 +45,14 @@ export function displayWidth(text) {
|
|
|
44
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;
|
|
45
46
|
return width;
|
|
46
47
|
}
|
|
47
|
-
export function formatFooter(metrics, context, columns = 80, rates, locale = 'en', header = '') {
|
|
48
|
+
export function formatFooter(metrics, context, columns = 80, rates, locale = 'en', header = '', provider = providerOfHeader(header) ?? 'deepseek-official') {
|
|
48
49
|
const label = key => t(locale, key);
|
|
49
50
|
const ctx = Number.isFinite(context) ? `${Math.round(context)}%` : '--';
|
|
50
51
|
const cache = metrics.cache === null ? '--' : `${metrics.cache.toFixed(1)}%`;
|
|
51
|
-
|
|
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);
|
|
52
54
|
const spend = metrics.unknown && metrics.cost === 0 ? '--' : `$${metrics.cost.toFixed(2)}${metrics.unknown ? '+' : ''}${metrics.pending ? '…' : ''}`;
|
|
53
|
-
const dollars = `${spend} / ${balance === null ? '$--' : '$' + balance.toFixed(2)}
|
|
55
|
+
const dollars = `${spend} / ${balance === null ? '$--' : '$' + balance.toFixed(2)}${provider === 'deepseek-official' ? ' ' + peakEmoji(trustedNow()) : ''}`;
|
|
54
56
|
const base = rates ? [
|
|
55
57
|
`${label('footer.current')}: ${Number.isFinite(rates.current) ? '~' + rates.current.toFixed(1) : '--'} tps`,
|
|
56
58
|
`${label('footer.average')}: ${Number.isFinite(rates.average) ? rates.average.toFixed(1) : '--'} tps`,
|
|
@@ -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 }));
|
|
@@ -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.
|