@toddzheng024/dscode-bundle 0.7.5 → 0.7.7

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.
Files changed (55) hide show
  1. package/THIRD_PARTY_NOTICES.md +3 -3
  2. package/cordis.patch.yml +26 -5
  3. package/package.json +4 -4
  4. package/plugins/auto-review/index.mjs +6 -1
  5. package/plugins/code-review/index.mjs +9 -4
  6. package/plugins/compaction/tetris.mjs +65 -0
  7. package/plugins/compaction/threshold.mjs +46 -0
  8. package/plugins/credentials/index.mjs +2 -2
  9. package/plugins/dscode/index.mjs +4 -10
  10. package/plugins/exec/cli.mjs +3 -2
  11. package/plugins/exec/index.mjs +6 -1
  12. package/plugins/i18n/messages.mjs +18 -0
  13. package/plugins/memory/index.mjs +7 -3
  14. package/plugins/openrouter/adapter.mjs +157 -0
  15. package/plugins/openrouter/index.mjs +112 -0
  16. package/plugins/openrouter/models.mjs +151 -0
  17. package/plugins/openrouter/search.mjs +109 -0
  18. package/plugins/openrouter/wire.mjs +413 -0
  19. package/plugins/providers/catalog.mjs +28 -26
  20. package/plugins/providers/effort.mjs +35 -0
  21. package/plugins/providers/openrouter-account.mjs +171 -0
  22. package/plugins/session-cards/index.mjs +5 -1
  23. package/plugins/session-metrics/balance.mjs +29 -19
  24. package/plugins/session-metrics/index.mjs +19 -6
  25. package/plugins/session-metrics/pricing.mjs +45 -14
  26. package/plugins/session-metrics/view.mjs +1 -1
  27. package/plugins/tui-tools/doctor.mjs +3 -1
  28. package/plugins/tui-tools/index.mjs +1 -1
  29. package/plugins/ultra/policy.mjs +0 -16
  30. package/presets/dscode/agent.cordis.yml +1 -1
  31. package/vendor/compaction-basic/index.js +983 -0
  32. package/vendor/compaction-basic/types/config.d.ts +37 -0
  33. package/vendor/compaction-basic/types/index.d.ts +84 -0
  34. package/vendor/compaction-basic/types/region.d.ts +65 -0
  35. package/vendor/compaction-basic/types/summarizer.d.ts +64 -0
  36. package/vendor/compaction-basic/types/types.d.ts +73 -0
  37. package/vendor/deepseek/index.js +1 -1
  38. package/vendor/subagent/index.js +3 -3
  39. package/vendor/tui/dscode-providers/catalog.mjs +28 -26
  40. package/vendor/tui/dscode-providers/effort.mjs +35 -0
  41. package/vendor/tui/dscode-providers/openrouter-account.mjs +171 -0
  42. package/vendor/tui/index.mjs +395 -133
  43. package/vendor/pi-ai/index.js +0 -2702
  44. package/vendor/pi-ai/types/adapter.d.ts +0 -105
  45. package/vendor/pi-ai/types/auth.d.ts +0 -60
  46. package/vendor/pi-ai/types/catalog.d.ts +0 -355
  47. package/vendor/pi-ai/types/config.d.ts +0 -208
  48. package/vendor/pi-ai/types/context.d.ts +0 -42
  49. package/vendor/pi-ai/types/discovery.d.ts +0 -43
  50. package/vendor/pi-ai/types/index.d.ts +0 -69
  51. package/vendor/pi-ai/types/login.d.ts +0 -21
  52. package/vendor/pi-ai/types/provider.d.ts +0 -59
  53. package/vendor/pi-ai/types/replay.d.ts +0 -63
  54. package/vendor/pi-ai/types/stream.d.ts +0 -43
  55. /package/vendor/{pi-ai → compaction-basic}/LICENSE +0 -0
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Load-time validation and routed-model policy resolution for compaction-basic.
3
+ *
4
+ * @module @deepseek-ai/dsh-compaction-basic/config
5
+ */
6
+ import type { LlmCallConfig } from '@deepseek-ai/dsh-llm';
7
+ import type { BasicCompactionConfig, ResolvedCompactSpec, ResolvedConfig, ResolvedTargetPolicy } from './types.ts';
8
+ /** Target-specific pressure configuration failure eligible for warning suppression. */
9
+ export declare class TargetPressureConfigError extends Error {
10
+ readonly targetKey: string;
11
+ /**
12
+ * @param targetKey - exact provider/model route used as the warning key.
13
+ * @param message - actionable configuration failure detail.
14
+ */
15
+ constructor(targetKey: string, message: string);
16
+ }
17
+ /**
18
+ * Resolve and validate service defaults plus exact-target partial overrides.
19
+ * @param config - untrusted plugin configuration after Loader normalization.
20
+ * @returns detached immutable defaults and validated exact-target overrides.
21
+ */
22
+ export declare function resolveConfig(config?: BasicCompactionConfig): ResolvedConfig;
23
+ /**
24
+ * Merge the exact provider/model override over the validated default policy.
25
+ * @param config - validated service defaults and override table.
26
+ * @param target - exact durable provider/model route to match.
27
+ * @returns detached immutable policy before model-capacity scaling.
28
+ */
29
+ export declare function resolveTargetPolicy(config: ResolvedConfig, target: Pick<LlmCallConfig, 'provider' | 'model'>): ResolvedTargetPolicy;
30
+ /**
31
+ * Scale one routed policy into concrete token budgets for its model capacity.
32
+ * @param policy - merged policy for the exact routed target.
33
+ * @param contextWindow - positive adapter-owned capacity for that target.
34
+ * @returns detached immutable pressure and retention budgets.
35
+ */
36
+ export declare function resolveCompactSpec(policy: ResolvedTargetPolicy, contextWindow: number): ResolvedCompactSpec;
37
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Basic replay-aware compaction backend.
3
+ *
4
+ * @module @deepseek-ai/dsh-compaction-basic
5
+ */
6
+ import { Context } from '@deepseek-ai/cordis';
7
+ import z from '@deepseek-ai/schemastery';
8
+ import { CompactionEngine } from '@deepseek-ai/dsh-compaction';
9
+ import type { CompactionResult, CompactionTrigger } from '@deepseek-ai/dsh-compaction';
10
+ import type { SessionSeq } from '@deepseek-ai/dsh-session';
11
+ import type { Agent } from '@deepseek-ai/dsh-agent';
12
+ import type { CommandId } from '@deepseek-ai/dsh-commands/brand';
13
+ import type { SummarizationInput, SummaryResult } from './summarizer.ts';
14
+ import type { BasicCompactionConfig, ResolvedConfig } from './types.ts';
15
+ export type { BasicCompactionConfig, CompactionPolicyConfig, ModelCompactPolicyConfig, ResolvedCompactSpec, ResolvedConfig, ResolvedRetention, ResolvedTargetPolicy, } from './types.ts';
16
+ /**
17
+ * Dependency-light compaction backend using `ctx.tokenMeter` for pressure,
18
+ * retention, cited source events, and summary-convergence pricing.
19
+ *
20
+ * `summarize()` is the sole subclass customization hook; the replay and durable
21
+ * mutation strategy stays fixed so every pricing decision uses the singleton
22
+ * token meter.
23
+ */
24
+ export declare class BasicCompactionEngine extends CompactionEngine {
25
+ static inject: string[];
26
+ static Config: z<BasicCompactionConfig>;
27
+ /** Resolved and validated compaction configuration. */
28
+ readonly config: ResolvedConfig;
29
+ private readonly warnedPressureConfigTargets;
30
+ private readonly overflowRetries;
31
+ private readonly overflowAgents;
32
+ constructor(ctx: Context, config?: BasicCompactionConfig);
33
+ /**
34
+ * Register automatic between-step pressure and model-request overflow
35
+ * recovery. `compactIfNeeded` stays dynamically dispatched so subclass
36
+ * overrides are honored at event time.
37
+ */
38
+ private _registerAutomaticCompaction;
39
+ /**
40
+ * Summarize the replayed conversation region through a direct one-shot
41
+ * `ctx.llm.stream()` call whose prefix reuses the conversation's own system
42
+ * prompt, tools, and messages so the provider's KV cache is not invalidated.
43
+ * Override this sole hook for a template or remote summarizer.
44
+ * @param input - replayed conversation prefix (system, tools, and leading messages) to condense.
45
+ * @param agent - supplies routed-model history, fallback model, and session id.
46
+ * @param signal - optional cancellation forwarded to the adapter.
47
+ * @returns safe text summary blocks and the exact auxiliary call envelope and output.
48
+ */
49
+ protected summarize(input: SummarizationInput, agent: Agent, signal?: AbortSignal): Promise<SummaryResult>;
50
+ /**
51
+ * Compact for replayed step-boundary pressure or one provider-confirmed context
52
+ * overflow. Both triggers price the latest durable routed request envelope;
53
+ * overflow bypasses the normal threshold and retained-tail policy so it can
54
+ * force one useful balanced reduction.
55
+ * @param agent - agent whose latest durable routed request is measured.
56
+ * @param trigger - normal step-boundary pressure or context-overflow recovery.
57
+ * @param signal - live turn cancellation signal forwarded to summarization.
58
+ * @returns the latest summary compaction result, or `null` when no summary ran.
59
+ */
60
+ compactIfNeeded(agent: Agent, trigger: CompactionTrigger, signal: AbortSignal): Promise<CompactionResult | null>;
61
+ /**
62
+ * Compact one inclusive positional range from the agent-owned surface using
63
+ * the effective token meter for all retention and shrink pricing.
64
+ * @param start - inclusive first surface-node seq.
65
+ * @param end - inclusive last surface-node seq.
66
+ * @param agent - owner of the target session, used by the summarizer.
67
+ * @param signal - optional summarization cancellation signal.
68
+ * @returns the successful durable compaction result.
69
+ */
70
+ compactRegion(start: SessionSeq, end: SessionSeq, agent: Agent, signal?: AbortSignal): Promise<CompactionResult>;
71
+ /**
72
+ * Force one useful idle-session compaction below the pressure threshold, and
73
+ * resolve only after its standalone marker pair is durably checkpointed.
74
+ * @param agent - idle agent whose next-turn admission this call reserves.
75
+ * @param signal - cancellation scoped to this compaction request.
76
+ * @param sourceCommandId - initiating command identity for presentation correlation.
77
+ * @returns the committed result, or `null` when no safe useful range exists.
78
+ */
79
+ compactNow(agent: Agent, signal: AbortSignal, sourceCommandId?: CommandId): Promise<CompactionResult | null>;
80
+ /** Bind the effective token meter and dynamically dispatched summarizer hook. */
81
+ private regionDependencies;
82
+ }
83
+ export default BasicCompactionEngine;
84
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Surface retention selection and the shared log-recorded compaction
3
+ * transaction for automatic open-turn and manual idle-session compaction.
4
+ *
5
+ * @module @deepseek-ai/dsh-compaction-basic/region
6
+ */
7
+ import type { CompactionResult } from '@deepseek-ai/dsh-compaction';
8
+ import type { CommandId } from '@deepseek-ai/dsh-commands/brand';
9
+ import type { TokenMeasurement, TokenMeter } from '@deepseek-ai/dsh-token-meter';
10
+ import { SessionSeq, type Session } from '@deepseek-ai/dsh-session';
11
+ import type { Agent } from '@deepseek-ai/dsh-agent';
12
+ import type { SummarizationInput, SummaryResult } from './summarizer.ts';
13
+ interface RegionDependencies {
14
+ readonly meter: TokenMeter;
15
+ summarize(input: SummarizationInput, agent: Agent, signal?: AbortSignal): Promise<SummaryResult>;
16
+ }
17
+ interface CompactionTransactionOptions {
18
+ /** `current-turn` derives a numbered owner; `null` writes a standalone bracket. */
19
+ readonly owner: 'current-turn' | null;
20
+ /** Surface relationship that must survive asynchronous summarization. */
21
+ readonly stability: 'whole-surface' | 'selected-span';
22
+ /** Optional durability checkpoint after a successfully closed bracket. */
23
+ readonly flush?: () => Promise<void>;
24
+ /** Manual command that initiated this transaction, when present. */
25
+ readonly sourceCommandId?: CommandId;
26
+ }
27
+ /**
28
+ * Resolve the next range starting at the first non-system surface node while
29
+ * retaining a priced recent tail and never splitting an assistant
30
+ * tool-call/result pair. A `system/message` at surface node 0 is never inside
31
+ * the range; without one the range starts at node 0.
32
+ * @param session - session supplying authoritative current surface positions.
33
+ * @param measurement - unified pressure and surface measurement from the conversation meter.
34
+ * @param retainTokens - minimum recent tail budget retained verbatim.
35
+ * @returns the inclusive positional seq range to compact, or `null`.
36
+ */
37
+ export declare function selectCompactableRange(session: Session, measurement: TokenMeasurement, retainTokens: number): {
38
+ start: SessionSeq;
39
+ end: SessionSeq;
40
+ } | null;
41
+ /**
42
+ * Run the single compaction transaction over one selected positional span.
43
+ * Selection and validation are read-only. Idle/log validation and
44
+ * `compaction/start` are synchronously adjacent, so the durable opening marker is
45
+ * the compaction lock before summarization yields. Every later failure makes
46
+ * exactly one `compaction/end` attempt; a failed close deliberately leaves the
47
+ * unmatched start detectable.
48
+ * @param dependencies - conversation meter and dynamically dispatched summarizer hook.
49
+ * @param session - session whose surface is mutated.
50
+ * @param start - inclusive first surface-node seq.
51
+ * @param end - inclusive last surface-node seq.
52
+ * @param agent - agent used by the summarizer.
53
+ * @param options - bracket owner, stability rule, and optional durability checkpoint.
54
+ * @param signal - optional summarization cancellation signal.
55
+ * @returns the successful durable compaction result.
56
+ */
57
+ export declare function compactSurfaceRegion(dependencies: RegionDependencies, session: Session, start: SessionSeq, end: SessionSeq, agent: Agent, options: CompactionTransactionOptions, signal?: AbortSignal): Promise<CompactionResult>;
58
+ /**
59
+ * Recheck the durable compaction lock after an asynchronous policy decision.
60
+ * @param session - session whose latest marker state is inspected.
61
+ * @param stage - operation label included in the busy diagnostic.
62
+ */
63
+ export declare function assertNoActiveCompaction(session: Session, stage: string): void;
64
+ export {};
65
+ //# sourceMappingURL=region.d.ts.map
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Default one-shot summarization and durable checkpoint framing.
3
+ *
4
+ * @module @deepseek-ai/dsh-compaction-basic/summarizer
5
+ */
6
+ import type { Context } from '@deepseek-ai/cordis';
7
+ import type { ContentBlock, Message, TokenUsage, ToolSchema } from '@deepseek-ai/dsh-llm';
8
+ import type { Agent } from '@deepseek-ai/dsh-agent';
9
+ interface SummaryConfig {
10
+ readonly summarizationProvider: string;
11
+ readonly summarizationModel: string;
12
+ readonly maxTokens: number;
13
+ }
14
+ /**
15
+ * The replayed conversation surface the summarizer condenses. Reproducing the
16
+ * last routed request's system prompt, tools, and leading messages verbatim
17
+ * lets the auxiliary call reuse the provider's warm prefix cache; the trailing
18
+ * compaction instruction is then the only novel input.
19
+ */
20
+ export interface SummarizationInput {
21
+ /** The conversation's tool schemas, reused for prefix-cache alignment; absent when the request carried none. */
22
+ readonly tools?: readonly ToolSchema[];
23
+ /** The derived system head, when present, followed by the shadowed region in surface order. */
24
+ readonly messages: readonly Message[];
25
+ }
26
+ /** Safe summary content plus the exact auxiliary call envelope recorded with it. */
27
+ export type SummaryResult = {
28
+ summary: ContentBlock[];
29
+ provider: string;
30
+ model: string;
31
+ maxTokens?: number;
32
+ /** Provider-reported usage for this summarization request. */
33
+ usage?: TokenUsage;
34
+ } & ({
35
+ /** Complete provider output before the text-only summary projection. */
36
+ rawOutput: ContentBlock[];
37
+ /** Identifies exactly one call through this context's `ctx.llm.stream()`. */
38
+ llmStreamCall: true;
39
+ } | {
40
+ /** Optional complete output from an unmarked template, remote, or other summarizer. */
41
+ rawOutput?: ContentBlock[];
42
+ /** An unmarked result does not identify a call through this context's LLM seam. */
43
+ llmStreamCall?: never;
44
+ });
45
+ /**
46
+ * Run the default cache-reusing `ctx.llm.stream()` summarization call: replay
47
+ * the conversation prefix, then append the compaction instruction as the final
48
+ * user message so the provider's warm prefix cache is reused.
49
+ * @param ctx - context providing the LLM service.
50
+ * @param config - resolved backend configuration.
51
+ * @param input - replayed conversation prefix (system, tools, and leading messages) to condense.
52
+ * @param agent - supplies routed-model history, fallback model, and session id.
53
+ * @param signal - optional cancellation forwarded to the adapter.
54
+ * @returns safe text-only summary blocks and the exact call envelope and output.
55
+ */
56
+ export declare function summarizeWithLlm(ctx: Context, config: SummaryConfig, input: SummarizationInput, agent: Agent, signal?: AbortSignal): Promise<SummaryResult>;
57
+ /**
58
+ * Wrap raw summary blocks in the durable checkpoint framing.
59
+ * @param summary - safe text-only model output.
60
+ * @returns content for the synthesized replacement user message.
61
+ */
62
+ export declare function frameSummary(summary: readonly ContentBlock[]): ContentBlock[];
63
+ export {};
64
+ //# sourceMappingURL=summarizer.d.ts.map
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Configuration vocabulary for the replay-aware basic compaction backend.
3
+ *
4
+ * @module @deepseek-ai/dsh-compaction-basic/types
5
+ */
6
+ import type { LlmCallConfig } from '@deepseek-ai/dsh-llm';
7
+ /** Policy fields shared by the default policy and exact model overrides. */
8
+ export interface CompactionPolicyConfig {
9
+ /** Compact at this fraction of the model's context window. Defaults to `0.8`. */
10
+ thresholdRatio?: number;
11
+ /** Recent context retained as a fraction of the model's window. Defaults to `0.16`. */
12
+ retainRatio?: number;
13
+ /** Absolute recent-context budget; mutually exclusive with `retainRatio`. */
14
+ retainTokens?: number;
15
+ /** Summary provider; set together with `summarizationModel`, or inherit the conversation target. */
16
+ summarizationProvider?: string;
17
+ /** Summary model; set together with `summarizationProvider`, or inherit the conversation target. */
18
+ summarizationModel?: string;
19
+ /** Provider generation cap for summarization. Defaults to `8192`. */
20
+ maxTokens?: number;
21
+ /** Extra attempts after the first compaction when pressure remains above threshold. Defaults to `1`. */
22
+ compactionRetries?: number;
23
+ /** Maximum retries after canonical context overflow; `0` disables recovery. Defaults to `1`. */
24
+ maxOverflowRetries?: number;
25
+ }
26
+ /** Exact provider/model override merged over the default compaction policy. */
27
+ export interface ModelCompactPolicyConfig extends CompactionPolicyConfig {
28
+ /** Registered provider route to match. */
29
+ provider: string;
30
+ /** Exact routed model id to match within `provider`. */
31
+ model: string;
32
+ }
33
+ /** Basic compaction configuration with an optional exact-target policy table. */
34
+ export interface BasicCompactionConfig extends CompactionPolicyConfig {
35
+ /** Exact provider/model overrides; duplicate targets fail plugin load. */
36
+ modelPolicies?: ModelCompactPolicyConfig[];
37
+ /** Enable automatic step-boundary pressure and overflow-recovery listeners. Defaults to `true`. */
38
+ auto?: boolean;
39
+ }
40
+ /** Exactly one validated retention form. */
41
+ export type ResolvedRetention = {
42
+ readonly retainRatio: number;
43
+ readonly retainTokens?: never;
44
+ } | {
45
+ readonly retainRatio?: never;
46
+ readonly retainTokens: number;
47
+ };
48
+ /** Validated policy fields shared before and after exact-target matching. */
49
+ interface ResolvedPolicyFields {
50
+ readonly thresholdRatio: number;
51
+ readonly summarizationProvider: string;
52
+ readonly summarizationModel: string;
53
+ readonly maxTokens: number;
54
+ readonly compactionRetries: number;
55
+ readonly maxOverflowRetries: number;
56
+ }
57
+ /** Validated immutable config whose target-specific defaults remain unresolved. */
58
+ export type ResolvedConfig = ResolvedPolicyFields & ResolvedRetention & {
59
+ readonly modelPolicies: readonly Readonly<ModelCompactPolicyConfig>[];
60
+ readonly auto: boolean;
61
+ };
62
+ /** Fully merged policy for one routed conversation target, before capacity scaling. */
63
+ export type ResolvedTargetPolicy = ResolvedPolicyFields & ResolvedRetention & {
64
+ readonly target: Pick<LlmCallConfig, 'provider' | 'model'>;
65
+ };
66
+ /** One routed model's concrete pressure and retention budget. */
67
+ export type ResolvedCompactSpec = Omit<ResolvedTargetPolicy, 'retainRatio' | 'retainTokens'> & {
68
+ readonly contextWindow: number;
69
+ readonly thresholdTokens: number;
70
+ readonly retainTokens: number;
71
+ };
72
+ export {};
73
+ //# sourceMappingURL=types.d.ts.map
@@ -247,7 +247,7 @@ async function serializeMessagesWithImages(messages, images) {
247
247
  function requestWithMessages(options, messages, defaults) {
248
248
  messages = flashRequest(options, messages);
249
249
  messages = ultraRequest(options, messages);
250
- const tools = options.tools?.filter((tool) => options.reasoningEffort === "ultra" ? tool.name !== "workflow" && tool.name !== "ralph" : !["subagent", "subagent_fork", "workflow", "ralph"].includes(tool.name)).map((tool) => ({
250
+ const tools = options.tools?.filter((tool) => tool.name !== "workflow" && tool.name !== "ralph").map((tool) => ({
251
251
  type: "function",
252
252
  function: {
253
253
  name: tool.name,
@@ -1,6 +1,6 @@
1
1
  // dscode-child-name-v1
2
2
  // dscode-child-worktree-v3
3
- // dscode-child-effort-v1
3
+ // dscode-child-effort-v2
4
4
  import { createChildWorktree, discardCleanChildWorktree } from "../../plugins/worktree-subagent/worktree.mjs";
5
5
  import z from "@deepseek-ai/schemastery";
6
6
  import { scopeChainOf, scopeOf } from "@deepseek-ai/dsh-scope";
@@ -396,7 +396,7 @@ function apply(ctx, config, session) {
396
396
  assertSubagentProviderConfiguration(subagentProvider);
397
397
  const wording = providerWording(subagentProvider.inheritsParentContext);
398
398
  const providerRouteDefaults = subagentProvider.agentRouteDefaults;
399
- const choiceDescription = !modelSelectionEnabled ? (subagentProvider.capabilities.agentOptions ? " Optionally set reasoning_effort for this child without changing its provider/model. Omit to inherit. Choose low for bounded tasks, high for difficult work, and max only when needed; use an effort supported by the current model." : "") : (providerRouteDefaults !== void 0 ? " Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and this provider's route defaults. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort." : " Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.") + (subagentProvider.inheritsParentContext ? " Changing the route can prevent provider-side reuse of the inherited conversation prefix." : "");
399
+ const choiceDescription = !modelSelectionEnabled ? (subagentProvider.capabilities.agentOptions ? " Optionally set reasoning_effort for this child without changing its provider/model. Omit to inherit. Use a level the current model offers: the lowest that fits the task, raised only for difficult work or real uncertainty." : "") : (providerRouteDefaults !== void 0 ? " Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and this provider's route defaults. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort." : " Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.") + (subagentProvider.inheritsParentContext ? " Changing the route can prevent provider-side reuse of the inherited conversation prefix." : "");
400
400
  mounted = {
401
401
  subagentProvider,
402
402
  disposeTool: runtimeCtx.tools.register(defineTool({
@@ -434,7 +434,7 @@ function apply(ctx, config, session) {
434
434
  } : {},
435
435
  ...!modelSelectionEnabled && subagentProvider.capabilities.agentOptions ? { reasoning_effort: {
436
436
  type: "string",
437
- description: "Reasoning effort for this child only, validated against its model. Omit to inherit. Prefer low for bounded tasks, high for difficult work, max for exceptional uncertainty. Provider/model remain unchanged."
437
+ description: "Reasoning effort for this child only, validated against its model; use a level that model offers. Omit to inherit. Prefer the lowest level that fits the task and raise it only for difficult work or real uncertainty. Provider/model remain unchanged."
438
438
  } } : {},
439
439
  ...continuable && (config.provider === "spawn" || config.provider === "fork") ? { worktree: {
440
440
  type: "boolean",
@@ -1,37 +1,46 @@
1
1
  // Model providers `/provider` switches between. DeepSeek's official API is the
2
- // native `llm-deepseek` route; OpenRouter reaches the same DeepSeek models
3
- // through pi-ai's catalog route, which the base composition mounts dormant until
4
- // a `llm-pi-ai:` settings section declares it.
2
+ // native `llm-deepseek` route; OpenRouter is DSCODE's own adapter
3
+ // (plugins/openrouter), which serves OpenRouter's live model listing and is always
4
+ // registered. This module ships beside the TUI too, so it imports nothing.
5
5
 
6
6
  export const PROVIDERS = Object.freeze([
7
7
  { id: 'deepseek-official', name: 'DeepSeek', aliases: ['deepseek', 'deepseek-official', 'official'], credentialRef: 'DEEPSEEK_API_KEY', defaultModel: 'deepseek-flash' },
8
- { id: 'openrouter', name: 'OpenRouter', aliases: ['openrouter', 'open-router'], credentialRef: 'OPENROUTER_API_KEY', defaultModel: 'deepseek/deepseek-v4-flash' },
8
+ // The optional management key reads account data only; it cannot call models.
9
+ { id: 'openrouter', name: 'OpenRouter', aliases: ['openrouter', 'open-router'], credentialRef: 'OPENROUTER_API_KEY', managementRef: 'OPENROUTER_MANAGEMENT_KEY', defaultModel: 'deepseek/deepseek-v4-flash' },
9
10
  ]);
10
11
 
12
+ // The pi-ai adapter served OpenRouter until 0.7.6, from this settings section.
11
13
  const PI_AI_NS = 'llm-pi-ai';
12
14
 
13
15
  // OpenRouter serves DeepSeek V4 thinking as none/high/xhigh. DeepSeek itself
14
16
  // answers `low` as high and `max` as xhigh, so the route offers the official
15
17
  // low/high/max detents (and Ultra on top of max) with the wire spelling OpenRouter
16
18
  // accepts; session cards and delegated children that ask for `low` keep working.
17
- const OPENROUTER_EFFORTS = Object.freeze({ off: 'none', low: 'high', high: 'high', max: 'xhigh' });
19
+ export const OPENROUTER_EFFORTS = Object.freeze({ off: 'none', low: 'high', high: 'high', max: 'xhigh' });
18
20
 
19
- /** The DeepSeek models the OpenRouter route declares, with their official-route counterparts. */
21
+ /** The DeepSeek models the OpenRouter route serves, with their official-route counterparts. */
20
22
  export const OPENROUTER_MODELS = Object.freeze([
21
23
  { id: 'deepseek/deepseek-v4-flash', name: 'DeepSeek V4 Flash', official: ['deepseek-flash', 'deepseek-v4-flash'] },
22
24
  { id: 'deepseek/deepseek-v4-pro', name: 'DeepSeek V4 Pro', official: ['deepseek-v4-pro'] },
23
25
  { id: 'deepseek/deepseek-v4-flash-vision-exp', name: 'DeepSeek V4 Flash Vision Exp', official: ['deepseek-v4-flash-vision-exp'] },
24
26
  ]);
25
27
 
26
- /** The `llm-pi-ai` profile `/provider openrouter` writes: installed-catalog models narrowed to DeepSeek. */
27
- export function openRouterProfile() {
28
- return {
29
- displayName: 'OpenRouter',
30
- apiKeyEnv: 'OPENROUTER_API_KEY',
31
- // Like the official route, requests default to high; it also gives /effort the DSCODE detent bar.
32
- reasoning: 'high',
33
- models: OPENROUTER_MODELS.map(({ id, name }) => ({ id, name, reasoningEfforts: { ...OPENROUTER_EFFORTS } })),
34
- };
28
+ /**
29
+ * Remove the `openrouter` profile earlier builds wrote into the pi-ai section. The
30
+ * pi-ai adapter is no longer mounted, so the profile is inert, and it would claim the
31
+ * route a second time if that adapter were ever mounted again.
32
+ * Never throws: /model and /provider must open regardless.
33
+ * @returns whether the settings changed.
34
+ */
35
+ export async function migrateOpenRouterProfile(settings) {
36
+ try {
37
+ const descriptor = settings?.describe?.({ redactSecrets: true }).find(entry => entry.ns === PI_AI_NS);
38
+ if (!descriptor || settings.writable !== true || descriptor.value?.providers?.openrouter === undefined) return false;
39
+ await settings.mutate(PI_AI_NS, [{ op: 'unset', path: ['providers', 'openrouter'] }], descriptor.revision);
40
+ return true;
41
+ } catch {
42
+ return false;
43
+ }
35
44
  }
36
45
 
37
46
  export function providerSpec(id) {
@@ -106,23 +115,16 @@ export function credentialState(row) {
106
115
  }
107
116
 
108
117
  /**
109
- * Declare a provider's route before it is used. Only OpenRouter needs one; a
110
- * profile the user already has (their own models or endpoint) is left alone.
118
+ * Prepare a provider before a switch. Both routes are always registered; switching
119
+ * to OpenRouter only clears the inert pi-ai profile earlier builds wrote.
111
120
  * @param settings - the host settings service.
112
121
  * @returns whether the settings changed.
113
122
  */
114
123
  export async function ensureProviderRoute(settings, provider) {
115
- if (provider !== 'openrouter') return false;
116
- if (typeof settings?.describe !== 'function' || typeof settings.mutate !== 'function') throw new Error('settings are unavailable; OpenRouter cannot be configured in this profile');
117
- const descriptor = settings.describe({ redactSecrets: true }).find(entry => entry.ns === PI_AI_NS);
118
- if (!descriptor) throw new Error('the OpenRouter adapter (llm-pi-ai) is not mounted in this profile');
119
- if (descriptor.value?.providers?.openrouter !== undefined) return false;
120
- if (settings.writable !== true) throw new Error('settings are read-only; OpenRouter cannot be configured here');
121
- await settings.mutate(PI_AI_NS, [{ op: 'set', path: ['providers', 'openrouter'], value: openRouterProfile() }], descriptor.revision);
122
- return true;
124
+ return provider === 'openrouter' ? migrateOpenRouterProfile(settings) : false;
123
125
  }
124
126
 
125
- /** Wait for a freshly declared route to reach the model directory. */
127
+ /** Wait for a route's models to reach the model directory. */
126
128
  export async function waitForModels(loadModels, provider, { attempts = 30, delayMs = 100 } = {}) {
127
129
  let directory;
128
130
  for (let attempt = 0; attempt < attempts; attempt++) {
@@ -0,0 +1,35 @@
1
+ // Reasoning levels differ per model: DeepSeek offers low/high/max, GPT models
2
+ // minimal through high, and many OpenRouter models none at all. Auxiliary calls
3
+ // name the level they would like and send the nearest one the model offers, or no
4
+ // effort when the model offers no levels.
5
+
6
+ /** Standard reasoning levels, lowest first. */
7
+ export const EFFORT_LEVELS = Object.freeze(['minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
8
+
9
+ /**
10
+ * The level to request from a model.
11
+ * @param offered - the model's effort ids, or `undefined` when its capability is unknown.
12
+ * @param wanted - the level the caller would like.
13
+ * @returns `wanted` when offered or when the capability is unknown; otherwise the nearest
14
+ * offered level at or above it, else the highest below; `undefined` when the model offers
15
+ * no standard level.
16
+ */
17
+ export function chooseEffort(offered, wanted) {
18
+ if (wanted === undefined || offered === undefined || offered.includes(wanted)) return wanted;
19
+ const rank = EFFORT_LEVELS.indexOf(wanted);
20
+ const levels = EFFORT_LEVELS.filter(level => offered.includes(level));
21
+ if (rank < 0 || levels.length === 0) return undefined;
22
+ return levels.find(level => EFFORT_LEVELS.indexOf(level) >= rank) ?? levels.at(-1);
23
+ }
24
+
25
+ /**
26
+ * {@link chooseEffort} for a route, reading the model's levels from the LLM service.
27
+ * A service without model metadata, or a failed lookup, keeps `wanted`.
28
+ */
29
+ export async function effortFor(llm, route, wanted, signal) {
30
+ if (wanted === undefined || typeof llm?.resolveModelInfo !== 'function' || !route?.provider || !route?.model) return wanted;
31
+ let info;
32
+ try { info = await llm.resolveModelInfo(route.provider, route.model, signal); }
33
+ catch { return wanted; }
34
+ return chooseEffort(info?.reasoning?.efforts?.map(effort => effort.id) ?? [], wanted);
35
+ }