@oh-my-pi/pi-coding-agent 15.9.67 → 15.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +136 -0
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/cli/gallery-cli.d.ts +43 -0
- package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
- package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
- package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
- package/dist/types/cli/gallery-screenshot.d.ts +35 -0
- package/dist/types/commands/gallery.d.ts +47 -0
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/model-id-affixes.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +25 -2
- package/dist/types/config/settings-schema.d.ts +41 -6
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
- package/dist/types/lsp/types.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/main.d.ts +3 -2
- package/dist/types/memory-backend/index.d.ts +2 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +1 -1
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +5 -1
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +0 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/index.d.ts +5 -4
- package/dist/types/modes/interactive-mode.d.ts +16 -6
- package/dist/types/modes/setup-version.d.ts +11 -0
- package/dist/types/modes/setup-wizard/index.d.ts +2 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +19 -6
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +3 -1
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +14 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/telemetry-export.d.ts +1 -1
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/eval-render.d.ts +1 -8
- package/dist/types/tools/fetch.d.ts +15 -7
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +13 -9
- package/dist/types/tools/renderers.d.ts +16 -2
- package/dist/types/tools/search.d.ts +5 -1
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +5 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/scrapers/github.d.ts +22 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/dist/types/web/search/providers/perplexity.d.ts +8 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +9 -9
- package/scripts/dev-launch +42 -0
- package/scripts/dev-launch-preload.ts +19 -0
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/args.ts +2 -2
- package/src/cli/dry-balance-cli.ts +52 -17
- package/src/cli/gallery-cli.ts +226 -0
- package/src/cli/gallery-fixtures/agentic.ts +292 -0
- package/src/cli/gallery-fixtures/codeintel.ts +188 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +153 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +250 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -0
- package/src/cli/gallery-fixtures/types.ts +41 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli-commands.ts +1 -0
- package/src/commands/gallery.ts +52 -0
- package/src/commands/launch.ts +1 -1
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +33 -9
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/keybindings.ts +15 -6
- package/src/config/model-equivalence.ts +35 -12
- package/src/config/model-id-affixes.ts +39 -22
- package/src/config/model-registry.ts +41 -18
- package/src/config/settings-schema.ts +28 -5
- package/src/config/settings.ts +31 -2
- package/src/dap/client.ts +14 -16
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +40 -54
- package/src/edit/renderer.ts +111 -119
- package/src/eval/__tests__/agent-bridge.test.ts +75 -32
- package/src/eval/__tests__/llm-bridge.test.ts +90 -31
- package/src/eval/agent-bridge.ts +34 -7
- package/src/eval/llm-bridge.ts +8 -3
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/plugins/doctor.ts +0 -1
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/goals/tools/goal-tool.ts +37 -27
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/lsp/client.ts +104 -55
- package/src/lsp/types.ts +10 -0
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +53 -56
- package/src/memories/index.ts +12 -5
- package/src/memory-backend/index.ts +13 -1
- package/src/memory-backend/resolve.ts +3 -5
- package/src/memory-backend/types.ts +1 -1
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +33 -1
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tips.txt +1 -0
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +115 -90
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +1 -2
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +70 -57
- package/src/modes/controllers/event-controller.ts +41 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +135 -122
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +25 -27
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/index.ts +5 -4
- package/src/modes/interactive-mode.ts +171 -82
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +3 -2
- package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +19 -8
- package/src/modes/utils/context-usage.ts +10 -6
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/sdk.ts +32 -60
- package/src/session/agent-session.ts +89 -13
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +13 -5
- package/src/slash-commands/builtin-registry.ts +37 -10
- package/src/slash-commands/helpers/usage-report.ts +2 -0
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +25 -4
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +212 -148
- package/src/telemetry-export.ts +25 -7
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +50 -6
- package/src/tools/debug.ts +20 -8
- package/src/tools/eval-backends.ts +6 -17
- package/src/tools/eval-render.ts +21 -18
- package/src/tools/eval.ts +5 -4
- package/src/tools/fetch.ts +391 -91
- package/src/tools/find.ts +44 -30
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +8 -1
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/plan-mode-guard.ts +21 -39
- package/src/tools/read.ts +23 -16
- package/src/tools/render-utils.ts +38 -40
- package/src/tools/renderers.ts +16 -1
- package/src/tools/report-tool-issue.ts +1 -1
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +189 -95
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +138 -59
- package/src/tools/write.ts +100 -60
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +9 -1
- package/src/utils/enhanced-paste.ts +202 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/scrapers/github.ts +255 -3
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/perplexity.ts +199 -51
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
- package/src/web/search/render.ts +39 -54
- package/src/web/search/types.ts +5 -1
- package/dist/types/eval/__tests__/shared-executors.test.d.ts +0 -1
- package/src/eval/__tests__/shared-executors.test.ts +0 -609
|
@@ -21,6 +21,7 @@ interface AppKeybindings {
|
|
|
21
21
|
"app.clear": true;
|
|
22
22
|
"app.exit": true;
|
|
23
23
|
"app.suspend": true;
|
|
24
|
+
"app.display.reset": true;
|
|
24
25
|
"app.thinking.cycle": true;
|
|
25
26
|
"app.thinking.toggle": true;
|
|
26
27
|
"app.model.cycleForward": true;
|
|
@@ -86,6 +87,10 @@ export const KEYBINDINGS = {
|
|
|
86
87
|
defaultKeys: "ctrl+z",
|
|
87
88
|
description: "Suspend application",
|
|
88
89
|
},
|
|
90
|
+
"app.display.reset": {
|
|
91
|
+
defaultKeys: "ctrl+l",
|
|
92
|
+
description: "Reset terminal display",
|
|
93
|
+
},
|
|
89
94
|
"app.thinking.cycle": {
|
|
90
95
|
defaultKeys: "shift+tab",
|
|
91
96
|
description: "Cycle thinking level",
|
|
@@ -103,7 +108,7 @@ export const KEYBINDINGS = {
|
|
|
103
108
|
description: "Cycle to previous model",
|
|
104
109
|
},
|
|
105
110
|
"app.model.select": {
|
|
106
|
-
defaultKeys: "
|
|
111
|
+
defaultKeys: "alt+m",
|
|
107
112
|
description: "Select model",
|
|
108
113
|
},
|
|
109
114
|
"app.model.selectTemporary": {
|
|
@@ -216,6 +221,7 @@ const KEYBINDING_NAME_MIGRATIONS = {
|
|
|
216
221
|
clear: "app.clear",
|
|
217
222
|
exit: "app.exit",
|
|
218
223
|
suspend: "app.suspend",
|
|
224
|
+
displayReset: "app.display.reset",
|
|
219
225
|
cycleThinkingLevel: "app.thinking.cycle",
|
|
220
226
|
cycleModelForward: "app.model.cycleForward",
|
|
221
227
|
cycleModelBackward: "app.model.cycleBackward",
|
|
@@ -444,7 +450,6 @@ function migrateKeybindingsConfigFile(agentDir: string): void {
|
|
|
444
450
|
|
|
445
451
|
const FOLLOW_UP_KEYBINDING: AppKeybinding = "app.message.followUp";
|
|
446
452
|
const WINDOWS_FOLLOW_UP_FALLBACK_KEY: KeyId = "ctrl+q";
|
|
447
|
-
|
|
448
453
|
function keyListIncludes(keys: KeyId | KeyId[] | undefined, target: KeyId): boolean {
|
|
449
454
|
if (keys === undefined) return false;
|
|
450
455
|
const keyList = Array.isArray(keys) ? keys : [keys];
|
|
@@ -525,10 +530,14 @@ export class KeybindingsManager extends TuiKeybindingsManager {
|
|
|
525
530
|
|
|
526
531
|
getKeys(keybinding: Keybinding): KeyId[] {
|
|
527
532
|
const keys = super.getKeys(keybinding);
|
|
528
|
-
if (keybinding
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
533
|
+
if (keybinding === FOLLOW_UP_KEYBINDING) {
|
|
534
|
+
if (this.#userBindings[FOLLOW_UP_KEYBINDING] !== undefined) return keys;
|
|
535
|
+
if (!userBindingClaimsKey(this.#userBindings, WINDOWS_FOLLOW_UP_FALLBACK_KEY, FOLLOW_UP_KEYBINDING)) {
|
|
536
|
+
return keys;
|
|
537
|
+
}
|
|
538
|
+
return removeKey(keys, WINDOWS_FOLLOW_UP_FALLBACK_KEY);
|
|
539
|
+
}
|
|
540
|
+
return keys;
|
|
532
541
|
}
|
|
533
542
|
|
|
534
543
|
getResolvedBindings(): KeybindingsConfig {
|
|
@@ -58,7 +58,7 @@ const EMPTY_COMPILED_EQUIVALENCE: CompiledEquivalenceConfig = {
|
|
|
58
58
|
};
|
|
59
59
|
const kModelResolutionCache = Symbol("model-equivalence.resolutionCache");
|
|
60
60
|
interface CompiledEquivalenceConfigWithCache extends CompiledEquivalenceConfig {
|
|
61
|
-
[kModelResolutionCache]?:
|
|
61
|
+
[kModelResolutionCache]?: Map<string, ResolvedCanonicalModel>;
|
|
62
62
|
}
|
|
63
63
|
const FAMILY_EXTRACTION_PATTERNS = [
|
|
64
64
|
/(?:^|[/:._-])((?:claude|gemini|gpt|grok|glm|qwen|minimax|kimi|deepseek|llama|gemma|nova|mistral|ministral|pixtral|codestral|devstral|magistral|ernie|doubao|seed|aion|olmo|molmo|nemotron|palmyra|command|codex|coder|o[1345])[-a-z0-9.]+)(?::|$)/i,
|
|
@@ -128,10 +128,18 @@ function normalizeCanonicalIdKey(canonicalId: string): string {
|
|
|
128
128
|
return canonicalId.trim().toLowerCase();
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
function getCanonicalSuffixAliasKey(candidate: string): string {
|
|
132
|
+
return PENALTY_HAS_UPPERCASE.test(candidate) ? normalizeCanonicalIdKey(candidate) : candidate;
|
|
133
|
+
}
|
|
134
|
+
|
|
131
135
|
export function formatCanonicalVariantSelector(model: Model<Api>): string {
|
|
132
136
|
return `${model.provider}/${model.id}`;
|
|
133
137
|
}
|
|
134
138
|
|
|
139
|
+
function getModelResolutionCacheKey(model: Model<Api>): string {
|
|
140
|
+
return `${model.provider}\0${model.id}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
135
143
|
function buildOverrideMap(overrides: Record<string, string> | undefined): Map<string, string> {
|
|
136
144
|
const result = new Map<string, string>();
|
|
137
145
|
if (!overrides) {
|
|
@@ -159,13 +167,24 @@ function buildExclusionSet(exclusions: readonly string[] | undefined): Set<strin
|
|
|
159
167
|
return result;
|
|
160
168
|
}
|
|
161
169
|
|
|
170
|
+
const compiledEquivalenceCache = new WeakMap<ModelEquivalenceConfig, CompiledEquivalenceConfig>();
|
|
162
171
|
function compileEquivalenceConfig(config: ModelEquivalenceConfig | undefined): CompiledEquivalenceConfig {
|
|
172
|
+
if (config) {
|
|
173
|
+
const cached = compiledEquivalenceCache.get(config);
|
|
174
|
+
if (cached) {
|
|
175
|
+
return cached;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
163
178
|
const overrides = buildOverrideMap(config?.overrides);
|
|
164
179
|
const exclude = buildExclusionSet(config?.exclude);
|
|
165
180
|
if (overrides.size === 0 && exclude.size === 0) {
|
|
166
181
|
return EMPTY_COMPILED_EQUIVALENCE;
|
|
167
182
|
}
|
|
168
|
-
|
|
183
|
+
const compiled: CompiledEquivalenceConfig = { overrides, exclude };
|
|
184
|
+
if (config) {
|
|
185
|
+
compiledEquivalenceCache.set(config, compiled);
|
|
186
|
+
}
|
|
187
|
+
return compiled;
|
|
169
188
|
}
|
|
170
189
|
|
|
171
190
|
function addCanonicalCandidate(candidates: Set<string>, candidate: string): void {
|
|
@@ -277,7 +296,7 @@ function expandCompactSeriesMinorVersions(candidate: string): string[] {
|
|
|
277
296
|
// safely return the same instance. Cap keeps memory bounded under adversarial
|
|
278
297
|
// model-id churn.
|
|
279
298
|
const QUALIFIED_NAMESPACE_SUFFIX_CACHE = new Map<string, string[]>();
|
|
280
|
-
const QUALIFIED_NAMESPACE_SUFFIX_CACHE_CAP =
|
|
299
|
+
const QUALIFIED_NAMESPACE_SUFFIX_CACHE_CAP = 4096;
|
|
281
300
|
function getQualifiedNamespaceSuffixes(candidate: string): string[] {
|
|
282
301
|
const cached = QUALIFIED_NAMESPACE_SUFFIX_CACHE.get(candidate);
|
|
283
302
|
if (cached !== undefined) {
|
|
@@ -670,7 +689,7 @@ function expandHeavyCanonicalCandidates(normalized: string, queue: string[]): vo
|
|
|
670
689
|
// is unused — kept for signature stability). The returned array is consumed via
|
|
671
690
|
// `.filter` at every callsite, so sharing the cached instance is safe.
|
|
672
691
|
const HEURISTIC_CANDIDATES_CACHE = new Map<string, string[]>();
|
|
673
|
-
const HEURISTIC_CANDIDATES_CACHE_CAP =
|
|
692
|
+
const HEURISTIC_CANDIDATES_CACHE_CAP = 4096;
|
|
674
693
|
function getHeuristicCanonicalCandidates(modelId: string, _officialIds?: ReadonlySet<string>): string[] {
|
|
675
694
|
const cached = HEURISTIC_CANDIDATES_CACHE.get(modelId);
|
|
676
695
|
if (cached !== undefined) {
|
|
@@ -728,10 +747,10 @@ function getPreferredFallbackCanonicalCandidate(modelId: string, candidates: rea
|
|
|
728
747
|
|
|
729
748
|
function resolveCanonicalIdForModel(
|
|
730
749
|
model: Model<Api>,
|
|
750
|
+
selector: string,
|
|
731
751
|
equivalence: CompiledEquivalenceConfig,
|
|
732
752
|
referenceData: CanonicalReferenceData,
|
|
733
753
|
): ResolvedCanonicalModel {
|
|
734
|
-
const selector = formatCanonicalVariantSelector(model);
|
|
735
754
|
const normalizedSelector = normalizeSelectorKey(selector);
|
|
736
755
|
|
|
737
756
|
if (equivalence.overrides.has(normalizedSelector)) {
|
|
@@ -753,9 +772,12 @@ function resolveCanonicalIdForModel(
|
|
|
753
772
|
}
|
|
754
773
|
|
|
755
774
|
const heuristicCandidates = getHeuristicCanonicalCandidates(model.id, referenceData.officialIds);
|
|
756
|
-
const officialMatches = new Set(
|
|
775
|
+
const officialMatches = new Set<string>();
|
|
757
776
|
for (const candidate of heuristicCandidates) {
|
|
758
|
-
|
|
777
|
+
if (referenceData.officialIds.has(candidate)) {
|
|
778
|
+
officialMatches.add(candidate);
|
|
779
|
+
}
|
|
780
|
+
const aliased = referenceData.suffixAliases.get(getCanonicalSuffixAliasKey(candidate));
|
|
759
781
|
if (aliased) {
|
|
760
782
|
officialMatches.add(aliased);
|
|
761
783
|
}
|
|
@@ -814,17 +836,18 @@ export function buildCanonicalModelIndex(
|
|
|
814
836
|
const compiledWithCache = compiledEquivalence as CompiledEquivalenceConfigWithCache;
|
|
815
837
|
let modelCache = compiledWithCache[kModelResolutionCache];
|
|
816
838
|
if (!modelCache) {
|
|
817
|
-
modelCache = new
|
|
839
|
+
modelCache = new Map<string, ResolvedCanonicalModel>();
|
|
818
840
|
compiledWithCache[kModelResolutionCache] = modelCache;
|
|
819
841
|
}
|
|
820
842
|
|
|
821
843
|
for (const model of models) {
|
|
822
|
-
|
|
844
|
+
const selector = formatCanonicalVariantSelector(model);
|
|
845
|
+
const cacheKey = getModelResolutionCacheKey(model);
|
|
846
|
+
let canonical = modelCache.get(cacheKey);
|
|
823
847
|
if (!canonical) {
|
|
824
|
-
canonical = resolveCanonicalIdForModel(model, compiledEquivalence, referenceData);
|
|
825
|
-
modelCache.set(
|
|
848
|
+
canonical = resolveCanonicalIdForModel(model, selector, compiledEquivalence, referenceData);
|
|
849
|
+
modelCache.set(cacheKey, canonical);
|
|
826
850
|
}
|
|
827
|
-
const selector = formatCanonicalVariantSelector(model);
|
|
828
851
|
const variant: CanonicalModelVariant = {
|
|
829
852
|
canonicalId: canonical.id,
|
|
830
853
|
selector,
|
|
@@ -4,34 +4,49 @@ const MODEL_ID_SEGMENT_PATTERN = /[a-z0-9.:-]+/g;
|
|
|
4
4
|
const MODEL_FAMILY_PREFIX_PATTERN =
|
|
5
5
|
/^(claude|gemini|gpt|grok|glm|qwen|deepseek|kimi|mimo|doubao|ernie|gpt-oss|gemma|minimax|step|command|jamba|llama|o[1345])/i;
|
|
6
6
|
|
|
7
|
-
function
|
|
8
|
-
return
|
|
7
|
+
function normalizeModelIdWhitespace(value: string): string {
|
|
8
|
+
return value.trim().replace(/\s+/g, " ");
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/** Ordering for model-like segments: longest first, ties broken lexicographically. */
|
|
11
12
|
function compareSegmentPreference(left: string, right: string): number {
|
|
12
|
-
|
|
13
|
-
return right.length - left.length;
|
|
14
|
-
}
|
|
15
|
-
return left.localeCompare(right);
|
|
13
|
+
return left.length !== right.length ? right.length - left.length : left.localeCompare(right);
|
|
16
14
|
}
|
|
17
15
|
|
|
18
16
|
export function getModelLikeIdSegments(modelId: string): string[] {
|
|
19
|
-
const
|
|
20
|
-
if (!
|
|
21
|
-
const segments = (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return unique;
|
|
17
|
+
const matches = normalizeModelIdWhitespace(modelId).toLowerCase().match(MODEL_ID_SEGMENT_PATTERN);
|
|
18
|
+
if (!matches) return [];
|
|
19
|
+
const segments = new Set<string>();
|
|
20
|
+
for (const segment of matches) {
|
|
21
|
+
if (MODEL_FAMILY_PREFIX_PATTERN.test(segment) && /\d/.test(segment)) segments.add(segment);
|
|
22
|
+
}
|
|
23
|
+
return [...segments].sort(compareSegmentPreference);
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
export function getLongestModelLikeIdSegment(modelId: string): string | undefined {
|
|
30
|
-
|
|
27
|
+
const matches = normalizeModelIdWhitespace(modelId).toLowerCase().match(MODEL_ID_SEGMENT_PATTERN);
|
|
28
|
+
if (!matches) return undefined;
|
|
29
|
+
let best: string | undefined;
|
|
30
|
+
for (const segment of matches) {
|
|
31
|
+
if (
|
|
32
|
+
MODEL_FAMILY_PREFIX_PATTERN.test(segment) &&
|
|
33
|
+
/\d/.test(segment) &&
|
|
34
|
+
(best === undefined || compareSegmentPreference(segment, best) < 0)
|
|
35
|
+
) {
|
|
36
|
+
best = segment;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return best;
|
|
31
40
|
}
|
|
32
41
|
|
|
33
|
-
function
|
|
34
|
-
|
|
42
|
+
function hasBracketAffixMarker(value: string): boolean {
|
|
43
|
+
for (let index = 0; index < value.length; index++) {
|
|
44
|
+
const code = value.charCodeAt(index);
|
|
45
|
+
if (code === 91 || code === 93 || code === 0x3010 || code === 0x3011) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
/**
|
|
@@ -39,18 +54,20 @@ function normalizeModelIdWhitespace(value: string): string {
|
|
|
39
54
|
* upstream model id, e.g.
|
|
40
55
|
* "[Kiro] claude-opus-4-8" -> "claude-opus-4-8"
|
|
41
56
|
* "[gcli转] gemini-3.1-pro-preview [假流]" -> "gemini-3.1-pro-preview"
|
|
57
|
+
*
|
|
58
|
+
* Candidates are returned most-stripped first: both ends, then leading-only, then trailing-only.
|
|
42
59
|
*/
|
|
43
60
|
export function getBracketStrippedModelIdCandidates(modelId: string): string[] {
|
|
61
|
+
if (!hasBracketAffixMarker(modelId)) return [];
|
|
44
62
|
const normalized = normalizeModelIdWhitespace(modelId);
|
|
45
63
|
if (!normalized) return [];
|
|
46
64
|
|
|
47
|
-
const
|
|
48
|
-
const withoutLeading = normalizeModelIdWhitespace(
|
|
65
|
+
const strippedLeading = normalized.replace(LEADING_BRACKETED_AFFIX_PATTERN, "");
|
|
66
|
+
const withoutLeading = normalizeModelIdWhitespace(strippedLeading);
|
|
49
67
|
const withoutTrailing = normalizeModelIdWhitespace(normalized.replace(TRAILING_BRACKETED_AFFIX_PATTERN, ""));
|
|
50
|
-
const withoutBoth = normalizeModelIdWhitespace(
|
|
51
|
-
normalized.replace(LEADING_BRACKETED_AFFIX_PATTERN, "").replace(TRAILING_BRACKETED_AFFIX_PATTERN, ""),
|
|
52
|
-
);
|
|
68
|
+
const withoutBoth = normalizeModelIdWhitespace(strippedLeading.replace(TRAILING_BRACKETED_AFFIX_PATTERN, ""));
|
|
53
69
|
|
|
70
|
+
const candidates = new Set<string>();
|
|
54
71
|
for (const candidate of [withoutBoth, withoutLeading, withoutTrailing]) {
|
|
55
72
|
if (candidate && candidate !== normalized) {
|
|
56
73
|
candidates.add(candidate);
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
+
import { registerCustomApi, unregisterCustomApis } from "@oh-my-pi/pi-ai/api-registry";
|
|
3
|
+
import { readModelCache } from "@oh-my-pi/pi-ai/model-cache";
|
|
4
|
+
import { createModelManager, type ModelManagerOptions, type ModelRefreshStrategy } from "@oh-my-pi/pi-ai/model-manager";
|
|
5
|
+
import { enrichModelThinking } from "@oh-my-pi/pi-ai/model-thinking";
|
|
6
|
+
import { getBundledModels, getBundledProviders } from "@oh-my-pi/pi-ai/models";
|
|
2
7
|
import {
|
|
3
|
-
type Api,
|
|
4
|
-
type AssistantMessageEventStream,
|
|
5
|
-
type Context,
|
|
6
|
-
createModelManager,
|
|
7
|
-
enrichModelThinking,
|
|
8
|
-
getBundledModels,
|
|
9
|
-
getBundledProviders,
|
|
10
8
|
googleAntigravityModelManagerOptions,
|
|
11
9
|
googleGeminiCliModelManagerOptions,
|
|
12
|
-
type Model,
|
|
13
|
-
type ModelManagerOptions,
|
|
14
|
-
type ModelRefreshStrategy,
|
|
15
10
|
openaiCodexModelManagerOptions,
|
|
16
11
|
PROVIDER_DESCRIPTORS,
|
|
17
|
-
readModelCache,
|
|
18
|
-
registerCustomApi,
|
|
19
|
-
type SimpleStreamOptions,
|
|
20
|
-
type ThinkingConfig,
|
|
21
12
|
UNK_CONTEXT_WINDOW,
|
|
22
13
|
UNK_MAX_TOKENS,
|
|
23
|
-
|
|
24
|
-
} from "@oh-my-pi/pi-ai";
|
|
14
|
+
} from "@oh-my-pi/pi-ai/provider-models";
|
|
15
|
+
import type { Api, Context, Model, SimpleStreamOptions, ThinkingConfig } from "@oh-my-pi/pi-ai/types";
|
|
16
|
+
import type { AssistantMessageEventStream } from "@oh-my-pi/pi-ai/utils/event-stream";
|
|
25
17
|
|
|
26
18
|
// Sentinel for local-only OAuth token (LM Studio, vLLM) — declared inline to avoid loading
|
|
27
19
|
// any provider module at startup. Must match `DEFAULT_LOCAL_TOKEN` in oauth/lm-studio.ts.
|
|
@@ -103,12 +95,14 @@ const STARTUP_MODEL_CACHE_PROVIDER_IDS: readonly string[] = [
|
|
|
103
95
|
...SPECIAL_MODEL_MANAGER_PROVIDER_IDS,
|
|
104
96
|
];
|
|
105
97
|
|
|
98
|
+
import type { ApiKeyResolver } from "@oh-my-pi/pi-ai";
|
|
106
99
|
import { registerOAuthProvider, unregisterOAuthProviders } from "@oh-my-pi/pi-ai/utils/oauth";
|
|
107
100
|
import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/utils/oauth/types";
|
|
108
101
|
import { isRecord, logger } from "@oh-my-pi/pi-utils";
|
|
109
102
|
import { parseModelString, resolveProviderModelReference } from "../config/model-resolver";
|
|
110
103
|
import { isValidThemeColor, type ThemeColor } from "../modes/theme/theme";
|
|
111
104
|
import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
|
|
105
|
+
import { type ApiKeyResolverOptions, createApiKeyResolver } from "./api-key-resolver";
|
|
112
106
|
import { type ConfigError, ConfigFile } from "./config-file";
|
|
113
107
|
import {
|
|
114
108
|
buildCanonicalModelIndex,
|
|
@@ -2373,12 +2367,33 @@ export class ModelRegistry {
|
|
|
2373
2367
|
|
|
2374
2368
|
/**
|
|
2375
2369
|
* Get API key for a provider (e.g., "openai").
|
|
2370
|
+
*
|
|
2371
|
+
* `options.forceRefresh` powers step (b) of the auth-retry policy — it
|
|
2372
|
+
* re-mints the session-sticky OAuth token even when the cached copy still
|
|
2373
|
+
* looks valid. `options.signal` is threaded into any broker-bound refresh.
|
|
2376
2374
|
*/
|
|
2377
|
-
async getApiKeyForProvider(
|
|
2375
|
+
async getApiKeyForProvider(
|
|
2376
|
+
provider: string,
|
|
2377
|
+
sessionId?: string,
|
|
2378
|
+
options?: { baseUrl?: string; forceRefresh?: boolean; signal?: AbortSignal },
|
|
2379
|
+
): Promise<string | undefined> {
|
|
2378
2380
|
if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
|
|
2379
2381
|
return kNoAuth;
|
|
2380
2382
|
}
|
|
2381
|
-
return this.authStorage.getApiKey(provider, sessionId, {
|
|
2383
|
+
return this.authStorage.getApiKey(provider, sessionId, {
|
|
2384
|
+
baseUrl: options?.baseUrl,
|
|
2385
|
+
forceRefresh: options?.forceRefresh,
|
|
2386
|
+
signal: options?.signal,
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
/**
|
|
2391
|
+
* Build an {@link ApiKeyResolver} for this provider, implementing the
|
|
2392
|
+
* central a/b/c auth-retry policy. Callers that need the initial key for
|
|
2393
|
+
* a guard can call `resolveApiKeyOnce(resolver)`.
|
|
2394
|
+
*/
|
|
2395
|
+
resolver(provider: string, options?: ApiKeyResolverOptions): ApiKeyResolver {
|
|
2396
|
+
return createApiKeyResolver(this, provider, options);
|
|
2382
2397
|
}
|
|
2383
2398
|
|
|
2384
2399
|
async #peekApiKeyForProvider(provider: string): Promise<string | undefined> {
|
|
@@ -2609,6 +2624,14 @@ export class ModelRegistry {
|
|
|
2609
2624
|
}
|
|
2610
2625
|
return true;
|
|
2611
2626
|
}
|
|
2627
|
+
|
|
2628
|
+
/**
|
|
2629
|
+
* Clear all cooldown suppressions recorded via {@link suppressSelector}.
|
|
2630
|
+
* Used to reset retry-fallback cooldown state without a full {@link refresh}.
|
|
2631
|
+
*/
|
|
2632
|
+
clearSuppressedSelectors(): void {
|
|
2633
|
+
this.#suppressedSelectors.clear();
|
|
2634
|
+
}
|
|
2612
2635
|
}
|
|
2613
2636
|
|
|
2614
2637
|
/**
|
|
@@ -660,6 +660,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
660
660
|
},
|
|
661
661
|
},
|
|
662
662
|
|
|
663
|
+
"display.smoothStreaming": {
|
|
664
|
+
type: "boolean",
|
|
665
|
+
default: true,
|
|
666
|
+
ui: {
|
|
667
|
+
tab: "appearance",
|
|
668
|
+
label: "Smooth Streaming",
|
|
669
|
+
description: "Reveal assistant text smoothly while streamed chunks arrive",
|
|
670
|
+
},
|
|
671
|
+
},
|
|
672
|
+
|
|
663
673
|
"display.showTokenUsage": {
|
|
664
674
|
type: "boolean",
|
|
665
675
|
default: false,
|
|
@@ -3059,13 +3069,26 @@ export const SETTINGS_SCHEMA = {
|
|
|
3059
3069
|
],
|
|
3060
3070
|
},
|
|
3061
3071
|
},
|
|
3062
|
-
"providers.
|
|
3063
|
-
type: "
|
|
3064
|
-
|
|
3072
|
+
"providers.fetch": {
|
|
3073
|
+
type: "enum",
|
|
3074
|
+
values: ["auto", "native", "trafilatura", "lynx", "parallel", "jina"] as const,
|
|
3075
|
+
default: "auto",
|
|
3065
3076
|
ui: {
|
|
3066
3077
|
tab: "providers",
|
|
3067
|
-
label: "
|
|
3068
|
-
description: "
|
|
3078
|
+
label: "Fetch Provider",
|
|
3079
|
+
description: "Reader backend priority for the fetch/read URL tool",
|
|
3080
|
+
options: [
|
|
3081
|
+
{
|
|
3082
|
+
value: "auto",
|
|
3083
|
+
label: "Auto",
|
|
3084
|
+
description: "Priority: native > trafilatura > lynx > parallel > jina",
|
|
3085
|
+
},
|
|
3086
|
+
{ value: "native", label: "Native", description: "In-process HTML→Markdown converter (always available)" },
|
|
3087
|
+
{ value: "trafilatura", label: "Trafilatura", description: "Auto-installs via uv/pip" },
|
|
3088
|
+
{ value: "lynx", label: "Lynx", description: "Requires lynx system package" },
|
|
3089
|
+
{ value: "parallel", label: "Parallel", description: "Requires PARALLEL_API_KEY" },
|
|
3090
|
+
{ value: "jina", label: "Jina", description: "Uses r.jina.ai reader (JINA_API_KEY optional)" },
|
|
3091
|
+
],
|
|
3069
3092
|
},
|
|
3070
3093
|
},
|
|
3071
3094
|
"provider.appendOnlyContext": {
|
package/src/config/settings.ts
CHANGED
|
@@ -240,11 +240,13 @@ export class Settings {
|
|
|
240
240
|
return promise.then(
|
|
241
241
|
instance => {
|
|
242
242
|
globalInstance = instance;
|
|
243
|
+
clearBoundSettingsMethods();
|
|
243
244
|
globalInstancePromise = Promise.resolve(instance);
|
|
244
245
|
return instance;
|
|
245
246
|
},
|
|
246
247
|
error => {
|
|
247
248
|
globalInstance = null;
|
|
249
|
+
clearBoundSettingsMethods();
|
|
248
250
|
throw error;
|
|
249
251
|
},
|
|
250
252
|
);
|
|
@@ -712,6 +714,17 @@ export class Settings {
|
|
|
712
714
|
}
|
|
713
715
|
}
|
|
714
716
|
|
|
717
|
+
// providers.parallelFetch (boolean) replaced by the providers.fetch reader
|
|
718
|
+
// priority enum. The new default ("auto") supersedes both old values —
|
|
719
|
+
// Parallel is now a deep fallback in the auto chain rather than the first
|
|
720
|
+
// choice — so drop the legacy key (flat and nested) and let the enum
|
|
721
|
+
// default apply.
|
|
722
|
+
const providersObj = raw.providers as Record<string, unknown> | undefined;
|
|
723
|
+
if (providersObj && "parallelFetch" in providersObj) {
|
|
724
|
+
delete providersObj.parallelFetch;
|
|
725
|
+
}
|
|
726
|
+
delete raw["providers.parallelFetch"];
|
|
727
|
+
|
|
715
728
|
// Map legacy `memories.enabled` boolean to the explicit `memory.backend`
|
|
716
729
|
// enum if the latter hasn't been set yet. Idempotent: subsequent
|
|
717
730
|
// migrations are no-ops once memory.backend is materialised.
|
|
@@ -967,6 +980,13 @@ export function onHindsightScopeChanged(cb: () => void): () => void {
|
|
|
967
980
|
|
|
968
981
|
let globalInstance: Settings | null = null;
|
|
969
982
|
let globalInstancePromise: Promise<Settings> | null = null;
|
|
983
|
+
let boundSettingsInstance: Settings | null = null;
|
|
984
|
+
let boundSettingsMethods = new Map<PropertyKey, unknown>();
|
|
985
|
+
|
|
986
|
+
function clearBoundSettingsMethods(): void {
|
|
987
|
+
boundSettingsInstance = null;
|
|
988
|
+
boundSettingsMethods = new Map<PropertyKey, unknown>();
|
|
989
|
+
}
|
|
970
990
|
|
|
971
991
|
export function isSettingsInitialized(): boolean {
|
|
972
992
|
return globalInstance !== null;
|
|
@@ -979,6 +999,7 @@ export function isSettingsInitialized(): boolean {
|
|
|
979
999
|
export function resetSettingsForTest(): void {
|
|
980
1000
|
globalInstance = null;
|
|
981
1001
|
globalInstancePromise = null;
|
|
1002
|
+
clearBoundSettingsMethods();
|
|
982
1003
|
}
|
|
983
1004
|
|
|
984
1005
|
/**
|
|
@@ -990,9 +1011,17 @@ export const settings = new Proxy({} as Settings, {
|
|
|
990
1011
|
if (!globalInstance) {
|
|
991
1012
|
throw new Error("Settings not initialized. Call Settings.init() first.");
|
|
992
1013
|
}
|
|
993
|
-
|
|
1014
|
+
if (boundSettingsInstance !== globalInstance) {
|
|
1015
|
+
clearBoundSettingsMethods();
|
|
1016
|
+
boundSettingsInstance = globalInstance;
|
|
1017
|
+
}
|
|
1018
|
+
const value = (globalInstance as unknown as Record<PropertyKey, unknown>)[prop];
|
|
994
1019
|
if (typeof value === "function") {
|
|
995
|
-
|
|
1020
|
+
const cached = boundSettingsMethods.get(prop);
|
|
1021
|
+
if (cached) return cached;
|
|
1022
|
+
const bound = value.bind(globalInstance);
|
|
1023
|
+
boundSettingsMethods.set(prop, bound);
|
|
1024
|
+
return bound;
|
|
996
1025
|
}
|
|
997
1026
|
return value;
|
|
998
1027
|
},
|
package/src/dap/client.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import { isEnoent, logger, ptree } from "@oh-my-pi/pi-utils";
|
|
2
3
|
import { NON_INTERACTIVE_ENV } from "../exec/non-interactive-env";
|
|
3
4
|
import { ToolAbortError } from "../tools/tool-errors";
|
|
4
5
|
import type {
|
|
@@ -165,19 +166,7 @@ export class DapClient {
|
|
|
165
166
|
detached: true,
|
|
166
167
|
});
|
|
167
168
|
|
|
168
|
-
|
|
169
|
-
await waitForCondition(
|
|
170
|
-
() => {
|
|
171
|
-
try {
|
|
172
|
-
Bun.file(socketPath).size;
|
|
173
|
-
return true;
|
|
174
|
-
} catch {
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
177
|
-
},
|
|
178
|
-
10_000,
|
|
179
|
-
proc,
|
|
180
|
-
);
|
|
169
|
+
await waitForCondition(() => isUnixSocketReady(socketPath), 10_000, proc);
|
|
181
170
|
|
|
182
171
|
const { readable, writeSink, socket } = await connectSocket({ unix: socketPath });
|
|
183
172
|
const client = new DapClient(adapter, cwd, proc, { readable, writeSink, socket });
|
|
@@ -553,15 +542,24 @@ export class DapClient {
|
|
|
553
542
|
}
|
|
554
543
|
}
|
|
555
544
|
|
|
545
|
+
async function isUnixSocketReady(socketPath: string): Promise<boolean> {
|
|
546
|
+
try {
|
|
547
|
+
return (await fs.stat(socketPath)).isSocket();
|
|
548
|
+
} catch (error) {
|
|
549
|
+
if (isEnoent(error)) return false;
|
|
550
|
+
throw error;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
556
554
|
/** Poll a condition until it returns true, or timeout/process exit. */
|
|
557
555
|
async function waitForCondition(
|
|
558
|
-
check: () => boolean
|
|
556
|
+
check: () => boolean | Promise<boolean>,
|
|
559
557
|
timeoutMs: number,
|
|
560
558
|
proc: { exitCode: number | null },
|
|
561
559
|
): Promise<void> {
|
|
562
560
|
const deadline = Date.now() + timeoutMs;
|
|
563
561
|
while (Date.now() < deadline) {
|
|
564
|
-
if (check()) return;
|
|
562
|
+
if (await check()) return;
|
|
565
563
|
if (proc.exitCode !== null) {
|
|
566
564
|
throw new Error("Adapter process exited before socket was ready");
|
|
567
565
|
}
|
package/src/dap/config.ts
CHANGED
|
@@ -27,6 +27,7 @@ function normalizeAdapterConfig(config: unknown): DapAdapterConfig | null {
|
|
|
27
27
|
rootMarkers: normalizeStringArray(config.rootMarkers),
|
|
28
28
|
launchDefaults: normalizeObject(config.launchDefaults),
|
|
29
29
|
attachDefaults: normalizeObject(config.attachDefaults),
|
|
30
|
+
acceptsDirectoryProgram: config.acceptsDirectoryProgram === true,
|
|
30
31
|
...(connectMode ? { connectMode } : {}),
|
|
31
32
|
};
|
|
32
33
|
}
|
|
@@ -64,6 +65,7 @@ export function resolveAdapter(adapterName: string, cwd: string): DapResolvedAda
|
|
|
64
65
|
launchDefaults: config.launchDefaults ?? {},
|
|
65
66
|
attachDefaults: config.attachDefaults ?? {},
|
|
66
67
|
connectMode: config.connectMode ?? "stdio",
|
|
68
|
+
acceptsDirectoryProgram: config.acceptsDirectoryProgram === true,
|
|
67
69
|
};
|
|
68
70
|
}
|
|
69
71
|
|
|
@@ -124,12 +126,19 @@ function sortAdaptersForLaunch(program: string, cwd: string, adapters: DapResolv
|
|
|
124
126
|
return rootAware.map(entry => entry.adapter);
|
|
125
127
|
}
|
|
126
128
|
|
|
127
|
-
export function selectLaunchAdapter(
|
|
129
|
+
export function selectLaunchAdapter(
|
|
130
|
+
program: string,
|
|
131
|
+
cwd: string,
|
|
132
|
+
adapterName?: string,
|
|
133
|
+
programKind: LaunchProgramKind = "file",
|
|
134
|
+
): DapResolvedAdapter | null {
|
|
128
135
|
if (adapterName) {
|
|
129
136
|
return resolveAdapter(adapterName, cwd);
|
|
130
137
|
}
|
|
131
138
|
const matches = getMatchingAdapters(program, cwd);
|
|
132
|
-
const
|
|
139
|
+
const candidates =
|
|
140
|
+
programKind === "directory" ? matches.filter(adapter => adapter.acceptsDirectoryProgram) : matches;
|
|
141
|
+
const sorted = sortAdaptersForLaunch(program, cwd, candidates.length > 0 ? candidates : matches);
|
|
133
142
|
return sorted[0] ?? null;
|
|
134
143
|
}
|
|
135
144
|
|
|
@@ -148,3 +157,33 @@ export function selectAttachAdapter(cwd: string, adapterName?: string, port?: nu
|
|
|
148
157
|
}
|
|
149
158
|
return available[0] ?? null;
|
|
150
159
|
}
|
|
160
|
+
|
|
161
|
+
/** How the launch `program` resolves on disk. `"missing"` is reserved for
|
|
162
|
+
* programs the adapter creates on demand (rare); we treat them like files. */
|
|
163
|
+
export type LaunchProgramKind = "file" | "directory" | "missing";
|
|
164
|
+
|
|
165
|
+
/** Compute adapter-specific launch arguments that depend on the resolved
|
|
166
|
+
* program. Returned values are spread over `adapter.launchDefaults` so they
|
|
167
|
+
* take precedence over the static defaults but can still be overridden by
|
|
168
|
+
* the fields `DapSessionManager.launch` sets explicitly (program, cwd, args).
|
|
169
|
+
*
|
|
170
|
+
* Currently scoped to dlv, where `mode` selects how the program path is
|
|
171
|
+
* interpreted: directories and `.go` source files debug as a Go package
|
|
172
|
+
* (`mode=debug`), anything else is treated as a compiled binary (`mode=exec`).
|
|
173
|
+
*/
|
|
174
|
+
export function resolveLaunchOverrides(
|
|
175
|
+
adapter: DapResolvedAdapter,
|
|
176
|
+
program: string,
|
|
177
|
+
programKind: LaunchProgramKind,
|
|
178
|
+
): Record<string, unknown> {
|
|
179
|
+
if (adapter.name === "dlv") {
|
|
180
|
+
const extension = path.extname(program).toLowerCase();
|
|
181
|
+
if (programKind === "directory" || extension === ".go") {
|
|
182
|
+
return { mode: "debug" };
|
|
183
|
+
}
|
|
184
|
+
if (programKind === "file") {
|
|
185
|
+
return { mode: "exec" };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return {};
|
|
189
|
+
}
|
package/src/dap/defaults.json
CHANGED
package/src/dap/session.ts
CHANGED
|
@@ -259,6 +259,7 @@ export class DapSessionManager {
|
|
|
259
259
|
session.needsConfigurationDone = session.capabilities.supportsConfigurationDoneRequest === true;
|
|
260
260
|
const launchArguments: DapLaunchArguments = {
|
|
261
261
|
...options.adapter.launchDefaults,
|
|
262
|
+
...(options.extraLaunchArguments ?? {}),
|
|
262
263
|
program: options.program,
|
|
263
264
|
cwd: options.cwd,
|
|
264
265
|
args: options.args,
|
package/src/dap/types.ts
CHANGED
|
@@ -488,6 +488,10 @@ export interface DapAdapterConfig {
|
|
|
488
488
|
* On Linux, connects via a unix domain socket.
|
|
489
489
|
* On macOS, the adapter dials into a local TCP listener (--client-addr). */
|
|
490
490
|
connectMode?: "stdio" | "socket";
|
|
491
|
+
/** When true, the adapter accepts a directory as the launch `program`
|
|
492
|
+
* (e.g. dlv treats it as a Go package path). When false/undefined, the
|
|
493
|
+
* debug tool rejects directory programs upfront. */
|
|
494
|
+
acceptsDirectoryProgram?: boolean;
|
|
491
495
|
}
|
|
492
496
|
|
|
493
497
|
export interface DapResolvedAdapter {
|
|
@@ -501,6 +505,7 @@ export interface DapResolvedAdapter {
|
|
|
501
505
|
launchDefaults: Record<string, unknown>;
|
|
502
506
|
attachDefaults: Record<string, unknown>;
|
|
503
507
|
connectMode: "stdio" | "socket";
|
|
508
|
+
acceptsDirectoryProgram: boolean;
|
|
504
509
|
}
|
|
505
510
|
|
|
506
511
|
export interface DapBreakpointRecord {
|
|
@@ -589,6 +594,11 @@ export interface DapLaunchSessionOptions {
|
|
|
589
594
|
program: string;
|
|
590
595
|
args?: string[];
|
|
591
596
|
cwd: string;
|
|
597
|
+
/** Per-launch overrides merged over `adapter.launchDefaults`. Used to
|
|
598
|
+
* inject adapter-specific values that depend on the resolved program
|
|
599
|
+
* (e.g. dlv's `mode` switches between `debug` and `exec` based on
|
|
600
|
+
* whether `program` is a Go package path or a compiled binary). */
|
|
601
|
+
extraLaunchArguments?: Record<string, unknown>;
|
|
592
602
|
}
|
|
593
603
|
|
|
594
604
|
export interface DapAttachSessionOptions {
|