@mrclrchtr/supi-lsp 1.16.1 → 2.0.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/README.md +44 -56
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +6 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/abort-utils.ts +31 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/api.ts +8 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +10 -4
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config.ts +1 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/index.ts +6 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/model-selection.ts +134 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +9 -2
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +109 -4
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +5 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/spinner-frames.ts +11 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/status-spinner.ts +68 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +3 -2
- package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +7 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +56 -2
- package/node_modules/@mrclrchtr/supi-code-runtime/src/evidence-badge.ts +40 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/index.ts +5 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +76 -3
- package/node_modules/@mrclrchtr/supi-code-runtime/src/workspace/runtime.ts +37 -2
- package/node_modules/@mrclrchtr/supi-core/package.json +6 -1
- package/node_modules/@mrclrchtr/supi-core/src/abort-utils.ts +31 -0
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +8 -0
- package/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +10 -4
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +1 -0
- package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +6 -0
- package/node_modules/@mrclrchtr/supi-core/src/model-selection.ts +134 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +9 -2
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +109 -4
- package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +5 -1
- package/node_modules/@mrclrchtr/supi-core/src/spinner-frames.ts +11 -0
- package/node_modules/@mrclrchtr/supi-core/src/status-spinner.ts +68 -0
- package/package.json +4 -31
- package/src/api.ts +27 -0
- package/src/client/client-refresh.ts +5 -0
- package/src/client/client.ts +246 -1
- package/src/client/transport.ts +4 -0
- package/src/config/capabilities.ts +3 -0
- package/src/config/config.ts +4 -2
- package/src/config/lsp-settings.ts +102 -0
- package/src/config/server-actions.ts +59 -0
- package/src/config/server-config.ts +4 -0
- package/src/index.ts +11 -0
- package/src/manager/capability-index.ts +1 -1
- package/src/manager/manager-project-info.ts +2 -1
- package/src/manager/manager.ts +115 -26
- package/src/provider/lsp-semantic-provider.ts +177 -134
- package/src/provider/refactor-planning.ts +214 -0
- package/src/session/runtime-controller.ts +320 -0
- package/src/session/runtime-registration.ts +26 -5
- package/src/session/service-registry.ts +81 -4
- package/src/summary.ts +2 -0
- package/src/extension.ts +0 -1
- package/src/format.ts +0 -373
- package/src/handlers/diagnostic-injection.ts +0 -182
- package/src/handlers/session-lifecycle.ts +0 -149
- package/src/handlers/status-command.ts +0 -38
- package/src/handlers/workspace-recovery.ts +0 -92
- package/src/lsp.ts +0 -37
- package/src/session/lsp-state.ts +0 -93
- package/src/session/settings-registration.ts +0 -296
- package/src/session/tree-persist.ts +0 -75
- package/src/tool/guidance.ts +0 -69
- package/src/tool/names.ts +0 -27
- package/src/tool/overrides.ts +0 -204
- package/src/tool/register-tools.ts +0 -69
- package/src/tool/service-actions.ts +0 -399
- package/src/tool/tool-specs.ts +0 -248
- package/src/ui/renderer.ts +0 -120
- package/src/ui/ui.ts +0 -360
- package/src/workspace-change.ts +0 -40
package/README.md
CHANGED
|
@@ -8,68 +8,59 @@
|
|
|
8
8
|
|
|
9
9
|
# @mrclrchtr/supi-lsp
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Language Server Protocol runtime library for the [pi coding agent](https://github.com/earendil-works/pi).
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
pi install npm:@mrclrchtr/supi-lsp
|
|
17
|
-
```
|
|
13
|
+
This is a **library-only** package — it has no pi extension surface and does not register public tools by itself. Public semantic code-intelligence behavior is exposed through `@mrclrchtr/supi-code-intelligence`'s `code_*` tools.
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
## Install
|
|
20
16
|
|
|
21
17
|
```bash
|
|
22
|
-
|
|
18
|
+
npm install @mrclrchtr/supi-lsp
|
|
23
19
|
```
|
|
24
20
|
|
|
25
|
-
## What
|
|
26
|
-
|
|
27
|
-
After install, pi gets **10 focused expert tools** for semantic language-server analysis:
|
|
21
|
+
## What this package provides
|
|
28
22
|
|
|
29
|
-
-
|
|
30
|
-
- `lsp_definition` — go to definition at a known position
|
|
31
|
-
- `lsp_references` — find all references to a symbol
|
|
32
|
-
- `lsp_implementation` — find implementations of an interface or method
|
|
33
|
-
- `lsp_document_symbols` — semantic declarations for one supported file
|
|
34
|
-
- `lsp_workspace_symbols` — semantic symbol-name lookup across the project
|
|
35
|
-
- `lsp_diagnostics` — current diagnostics for one file or a workspace summary
|
|
36
|
-
- `lsp_rename` — semantic rename planning at a known position
|
|
37
|
-
- `lsp_code_actions` — semantic fixes or refactors at a known position
|
|
38
|
-
- `lsp_recover` — refresh stale diagnostics after workspace changes
|
|
39
|
-
- `/lsp-status` — inspect detected servers, roots, open files, and diagnostics
|
|
23
|
+
`@mrclrchtr/supi-lsp` provides the semantic substrate used by SuPi's code-intelligence stack:
|
|
40
24
|
|
|
41
|
-
|
|
25
|
+
- a session-scoped `SessionLspService`
|
|
26
|
+
- LSP client lifecycle, transport, and workspace routing
|
|
27
|
+
- diagnostics collection, stale-diagnostic recovery, and summaries
|
|
28
|
+
- a shared `SemanticProvider` adapter for the workspace runtime
|
|
29
|
+
- operation-aware semantic refactor planning for first-wave precise text-edit operations
|
|
42
30
|
|
|
43
|
-
|
|
31
|
+
First-wave refactor mapping in the semantic provider:
|
|
44
32
|
|
|
45
|
-
|
|
33
|
+
- `rename_symbol` and legacy `rename` alias → `textDocument/rename`
|
|
34
|
+
- `update_imports` → precise organize-imports/source actions only
|
|
35
|
+
- `delete_dead_code` → precise quickfix/refactor-rewrite actions only
|
|
36
|
+
- `rename_file` / `move_file` → explicit unavailable results for now
|
|
46
37
|
|
|
47
|
-
|
|
38
|
+
The historical LSP status overlay is no longer part of this package. Substrate-owned status UX now lives in `@mrclrchtr/supi-code-intelligence` as the `/supi-ci-status` command.
|
|
48
39
|
|
|
49
|
-
|
|
40
|
+
## Startup performance
|
|
50
41
|
|
|
51
|
-
|
|
52
|
-
- rebuilds project-specific prompt guidance based on active servers
|
|
53
|
-
- injects outstanding diagnostics into context before agent turns when issues exist
|
|
54
|
-
- adds inline diagnostics after `write` and `edit` results
|
|
55
|
-
- watches for workspace changes such as `package.json`, lockfile, `tsconfig`, generated types, and source-file edits so recovery can happen when diagnostics go stale
|
|
56
|
-
- warns when configured language-server commands are missing
|
|
42
|
+
Language servers start automatically when a PI session opens. By default, every server with matching source files in the project is started **concurrently** — in polyglot repos or monorepos with multiple language footprints, this parallel startup can cause a significant CPU spike.
|
|
57
43
|
|
|
58
|
-
|
|
44
|
+
**To reduce startup overhead:**
|
|
59
45
|
|
|
60
|
-
|
|
46
|
+
- **Disable specific language servers** that you don't need. Only servers whose source files are detected in the project will be started. To explicitly exclude a language:
|
|
61
47
|
|
|
62
|
-
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"lsp": {
|
|
51
|
+
"servers": {
|
|
52
|
+
"python": { "enabled": false },
|
|
53
|
+
"rust": { "enabled": false }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
63
58
|
|
|
64
|
-
|
|
65
|
-
- `severity` — inline diagnostic threshold: `1` errors, `2` warnings, `3` info, `4` hints
|
|
66
|
-
- `active` — choose which configured language servers are active; empty means all
|
|
67
|
-
- `exclude` — gitignore-style patterns that suppress diagnostics for matching files
|
|
59
|
+
Add this to `.pi/supi/config.json` (project) or `~/.pi/agent/supi/config.json` (global). Only the listed language servers are disabled; all others remain active.
|
|
68
60
|
|
|
69
|
-
|
|
61
|
+
> **Note:** The global `lsp.enabled` switch and `lsp.active` allowlist were removed in v0.7.0. LSP is always-on by default. Per-language `lsp.servers.<language>.enabled: false` is the only supported way to opt out. If your config still has `lsp.enabled` or `lsp.active` keys, they are ignored and a deprecation warning will appear at session start.
|
|
70
62
|
|
|
71
|
-
|
|
72
|
-
- project: `.pi/supi/config.json`
|
|
63
|
+
Server discovery walks the project tree at depth 3 (skipping `node_modules`, `.git`, `.pnpm`). Every detected language server starts in parallel via `Promise.all`.
|
|
73
64
|
|
|
74
65
|
## Architecture
|
|
75
66
|
|
|
@@ -82,13 +73,14 @@ diagnostic capabilities into the shared workspace runtime.
|
|
|
82
73
|
supi-code-runtime ← shared contracts + workspace runtime
|
|
83
74
|
↑
|
|
84
75
|
supi-lsp ← LSP client + session-scoped service + runtime capabilities
|
|
76
|
+
↑
|
|
77
|
+
supi-code-intelligence ← public code_* tools over the semantic substrate
|
|
85
78
|
```
|
|
86
79
|
|
|
87
80
|
## Package surfaces
|
|
88
81
|
|
|
89
82
|
- `@mrclrchtr/supi-lsp/api` — reusable session-scoped LSP service and related types
|
|
90
|
-
- `@mrclrchtr/supi-lsp/
|
|
91
|
-
- `@mrclrchtr/supi-lsp/provider/lsp-semantic-provider` — shared SemanticProvider adapter
|
|
83
|
+
- `@mrclrchtr/supi-lsp/provider/lsp-semantic-provider` — shared `SemanticProvider` adapter
|
|
92
84
|
|
|
93
85
|
Example:
|
|
94
86
|
|
|
@@ -101,19 +93,15 @@ if (state.kind === "ready") {
|
|
|
101
93
|
}
|
|
102
94
|
```
|
|
103
95
|
|
|
104
|
-
`SessionLspService` methods use raw **0-based LSP positions**.
|
|
96
|
+
`SessionLspService` methods use raw **0-based LSP positions**. Public `code_*` tools keep the user-facing **1-based** coordinate UX.
|
|
105
97
|
|
|
106
98
|
## Source
|
|
107
99
|
|
|
108
|
-
- `src/
|
|
100
|
+
- `src/client/` — LSP client, transport, refresh, and request handling
|
|
109
101
|
- `src/config/` — server config, defaults, capabilities, and exported LSP protocol types
|
|
110
|
-
- `src/
|
|
111
|
-
- `src/
|
|
112
|
-
- `src/
|
|
113
|
-
- `src/
|
|
114
|
-
- `src/tool/service-actions.ts` — service-backed tool execution and formatting
|
|
115
|
-
- `src/tool/names.ts` — stable tool name constants
|
|
116
|
-
- `src/tool/overrides.ts` — read/write/edit overrides for inline diagnostics
|
|
117
|
-
- `src/ui/` — custom diagnostic message rendering and the status overlay
|
|
118
|
-
- `src/client/`, `src/manager/`, `src/diagnostics/` — runtime engine subsystems
|
|
102
|
+
- `src/diagnostics/` — stale diagnostics, suppression diagnostics, and workspace sentinels
|
|
103
|
+
- `src/manager/` — manager lifecycle, routing, diagnostics, and recovery helpers
|
|
104
|
+
- `src/provider/lsp-semantic-provider.ts` — shared `SemanticProvider` adapter
|
|
105
|
+
- `src/session/` — service registry, runtime registration, scanner, and controller logic
|
|
119
106
|
- `src/api.ts` — reusable developer-facing surface
|
|
107
|
+
- `src/index.ts` — package-root re-export surface
|
package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "SuPi core — shared infrastructure for SuPi extensions (XML context tags, config system)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"settings",
|
|
24
24
|
"configuration"
|
|
25
25
|
],
|
|
26
|
+
"type": "module",
|
|
26
27
|
"files": [
|
|
27
28
|
"src/**/*.ts",
|
|
28
29
|
"!__tests__"
|
|
@@ -49,12 +50,14 @@
|
|
|
49
50
|
},
|
|
50
51
|
"main": "src/api.ts",
|
|
51
52
|
"exports": {
|
|
53
|
+
"./abort-utils": "./src/abort-utils.ts",
|
|
52
54
|
"./api": "./src/api.ts",
|
|
53
55
|
"./config": "./src/config.ts",
|
|
54
56
|
"./context": "./src/context.ts",
|
|
55
57
|
"./debug": "./src/debug-registry.ts",
|
|
56
58
|
"./footer-registry": "./src/footer-registry.ts",
|
|
57
59
|
"./llm": "./src/llm.ts",
|
|
60
|
+
"./model-selection": "./src/model-selection.ts",
|
|
58
61
|
"./package.json": "./package.json",
|
|
59
62
|
"./path": "./src/path.ts",
|
|
60
63
|
"./report": "./src/report.ts",
|
|
@@ -63,6 +66,8 @@
|
|
|
63
66
|
"./session": "./src/session.ts",
|
|
64
67
|
"./settings": "./src/settings.ts",
|
|
65
68
|
"./settings-ui": "./src/settings-ui.ts",
|
|
69
|
+
"./spinner-frames": "./src/spinner-frames.ts",
|
|
70
|
+
"./status-spinner": "./src/status-spinner.ts",
|
|
66
71
|
"./terminal": "./src/terminal.ts",
|
|
67
72
|
"./tool-framework": "./src/tool-framework.ts",
|
|
68
73
|
"./types": "./src/types.ts"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abort controller/signal utility helpers.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Combine a caller-provided abort controller with a generation timeout signal.
|
|
9
|
+
*
|
|
10
|
+
* Returns the combined signal and a cleanup function that removes both
|
|
11
|
+
* event listeners. Callers must invoke cleanup in a `finally` block
|
|
12
|
+
* to avoid listener leaks.
|
|
13
|
+
*/
|
|
14
|
+
export function combineAbortSignals(
|
|
15
|
+
abort: AbortController,
|
|
16
|
+
timeoutMs: number,
|
|
17
|
+
): { signal: AbortSignal; cleanup: () => void } {
|
|
18
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
19
|
+
const combinedController = new AbortController();
|
|
20
|
+
const onAbort = () => combinedController.abort();
|
|
21
|
+
abort.signal.addEventListener("abort", onAbort, { once: true });
|
|
22
|
+
timeoutSignal.addEventListener("abort", onAbort, { once: true });
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
signal: combinedController.signal,
|
|
26
|
+
cleanup: () => {
|
|
27
|
+
abort.signal.removeEventListener("abort", onAbort);
|
|
28
|
+
timeoutSignal.removeEventListener("abort", onAbort);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/api.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// For lighter imports, use one of the domain subpaths directly
|
|
7
7
|
// (e.g. @mrclrchtr/supi-core/config, @mrclrchtr/supi-core/context).
|
|
8
8
|
|
|
9
|
+
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
10
|
+
export * from "./abort-utils.ts";
|
|
9
11
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
10
12
|
export * from "./config.ts";
|
|
11
13
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
@@ -17,6 +19,8 @@ export * from "./footer-registry.ts";
|
|
|
17
19
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
18
20
|
export * from "./llm.ts";
|
|
19
21
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
22
|
+
export * from "./model-selection.ts";
|
|
23
|
+
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
20
24
|
export * from "./path.ts";
|
|
21
25
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
22
26
|
export * from "./project.ts";
|
|
@@ -29,6 +33,10 @@ export * from "./settings.ts";
|
|
|
29
33
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
30
34
|
export * from "./settings-ui.ts";
|
|
31
35
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
36
|
+
export * from "./spinner-frames.ts";
|
|
37
|
+
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
38
|
+
export * from "./status-spinner.ts";
|
|
39
|
+
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
32
40
|
export * from "./terminal.ts";
|
|
33
41
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
34
42
|
export * from "./tool-framework.ts";
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// to enable auto-generated persistChange. When all items have a configType,
|
|
6
6
|
// the persistChange callback can be omitted.
|
|
7
7
|
|
|
8
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
9
|
import type { SettingItem } from "@earendil-works/pi-tui";
|
|
9
10
|
import type { SettingsScope } from "../settings/settings-registry.ts";
|
|
10
11
|
import { registerSettings } from "../settings/settings-registry.ts";
|
|
@@ -60,7 +61,12 @@ export interface ConfigSettingsOptions<T> {
|
|
|
60
61
|
* persistChange handling. When ALL items declare a configType,
|
|
61
62
|
* the `persistChange` callback can be omitted.
|
|
62
63
|
*/
|
|
63
|
-
buildItems: (
|
|
64
|
+
buildItems: (
|
|
65
|
+
settings: T,
|
|
66
|
+
scope: SettingsScope,
|
|
67
|
+
cwd: string,
|
|
68
|
+
ctx?: ExtensionContext,
|
|
69
|
+
) => ConfigSettingItem[];
|
|
64
70
|
/**
|
|
65
71
|
* Handle a settings change with scoped persistence helpers.
|
|
66
72
|
*
|
|
@@ -141,12 +147,12 @@ export function registerConfigSettings<T>(options: ConfigSettingsOptions<T>): vo
|
|
|
141
147
|
registerSettings({
|
|
142
148
|
id: options.id,
|
|
143
149
|
label: options.label,
|
|
144
|
-
loadValues: (scope, cwd) => {
|
|
150
|
+
loadValues: (scope, cwd, ctx) => {
|
|
145
151
|
const settings = loadSupiConfigForScope(options.section, cwd, options.defaults, {
|
|
146
152
|
scope,
|
|
147
153
|
homeDir: options.homeDir,
|
|
148
154
|
});
|
|
149
|
-
const items = options.buildItems(settings, scope, cwd);
|
|
155
|
+
const items = options.buildItems(settings, scope, cwd, ctx);
|
|
150
156
|
cachedItems = items;
|
|
151
157
|
return items;
|
|
152
158
|
},
|
|
@@ -173,7 +179,7 @@ export function registerConfigSettings<T>(options: ConfigSettingsOptions<T>): vo
|
|
|
173
179
|
}
|
|
174
180
|
|
|
175
181
|
// Auto-generate when all items are declarative
|
|
176
|
-
const items = cachedItems ?? options.buildItems(options.defaults, scope, cwd);
|
|
182
|
+
const items = cachedItems ?? options.buildItems(options.defaults, scope, cwd, undefined);
|
|
177
183
|
if (areAllItemsDeclarative(items)) {
|
|
178
184
|
autoPersistChange(settingId, value, helpers, items);
|
|
179
185
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { createRegistry } from "./registry-utils.ts";
|
|
9
9
|
|
|
10
10
|
/** Where the contribution should appear in the footer. */
|
|
11
|
-
export type FooterPlacement = "stats" | "status";
|
|
11
|
+
export type FooterPlacement = "stats" | "stats-end" | "status";
|
|
12
12
|
|
|
13
13
|
/** A single footer contribution registered by an extension. */
|
|
14
14
|
export interface FooterContribution {
|
package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/index.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// For lighter imports, use one of the domain subpaths directly
|
|
7
7
|
// (e.g. @mrclrchtr/supi-core/config, @mrclrchtr/supi-core/context).
|
|
8
8
|
|
|
9
|
+
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
10
|
+
export * from "./abort-utils.ts";
|
|
9
11
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
10
12
|
export * from "./config.ts";
|
|
11
13
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
@@ -15,6 +17,8 @@ export * from "./debug-registry.ts";
|
|
|
15
17
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
16
18
|
export * from "./footer-registry.ts";
|
|
17
19
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
20
|
+
export * from "./model-selection.ts";
|
|
21
|
+
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
18
22
|
export * from "./path.ts";
|
|
19
23
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
20
24
|
export * from "./project.ts";
|
|
@@ -27,6 +31,8 @@ export * from "./settings.ts";
|
|
|
27
31
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
28
32
|
export * from "./settings-ui.ts";
|
|
29
33
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
34
|
+
export * from "./status-spinner.ts";
|
|
35
|
+
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
30
36
|
export * from "./terminal.ts";
|
|
31
37
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
32
38
|
export * from "./tool-framework.ts";
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared model-selection helpers for SuPi extensions.
|
|
3
|
+
*
|
|
4
|
+
* Provides scoped-model listing using PI's `enabledModels` configuration,
|
|
5
|
+
* matching the same semantics as the `@mrclrchtr/supi-review` model picker.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Model } from "@earendil-works/pi-ai";
|
|
11
|
+
import { type ExtensionContext, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
|
|
13
|
+
// ── Types ──────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
/** A selectable model entry with display metadata. */
|
|
16
|
+
export interface ModelSelection {
|
|
17
|
+
/** Canonical `provider/model-id` string. */
|
|
18
|
+
canonicalId: string;
|
|
19
|
+
/** Provider name, e.g. `"anthropic"`. */
|
|
20
|
+
provider: string;
|
|
21
|
+
/** Model id, e.g. `"claude-sonnet-4-5"`. */
|
|
22
|
+
id: string;
|
|
23
|
+
// biome-ignore lint/suspicious/noExplicitAny: Model<any> is pi's canonical type
|
|
24
|
+
model: Model<any>;
|
|
25
|
+
/** Human-readable label (model name or canonicalId). */
|
|
26
|
+
label: string;
|
|
27
|
+
/** Optional description (canonicalId when different from label). */
|
|
28
|
+
description?: string;
|
|
29
|
+
/** Whether this model is the current session model. */
|
|
30
|
+
isCurrent: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
/** Build the canonical `provider/model-id` string. */
|
|
36
|
+
export function toCanonicalModelId(
|
|
37
|
+
model: Pick<NonNullable<ExtensionContext["model"]>, "provider" | "id">,
|
|
38
|
+
): string {
|
|
39
|
+
return `${model.provider}/${model.id}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* List selectable models from PI's scoped model configuration.
|
|
44
|
+
*
|
|
45
|
+
* Only models that match the configured `enabledModels` patterns are offered.
|
|
46
|
+
* The current session model is included only when it is inside that scoped set.
|
|
47
|
+
* Returns an empty array when no scoped model patterns are configured.
|
|
48
|
+
*/
|
|
49
|
+
export function getSelectableModels(
|
|
50
|
+
ctx: Pick<ExtensionContext, "cwd" | "modelRegistry" | "model">,
|
|
51
|
+
enabledModelPatterns = SettingsManager.create(ctx.cwd).getEnabledModels(),
|
|
52
|
+
): ModelSelection[] {
|
|
53
|
+
if (!enabledModelPatterns || enabledModelPatterns.length === 0) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const byCanonicalId = new Map<string, ModelSelection>();
|
|
58
|
+
const availableModels = filterByEnabledModels(
|
|
59
|
+
enabledModelPatterns,
|
|
60
|
+
ctx.modelRegistry.getAvailable(),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const addModel = (
|
|
64
|
+
// biome-ignore lint/suspicious/noExplicitAny: Model<any> is pi's canonical type
|
|
65
|
+
model: Model<any>,
|
|
66
|
+
isCurrent: boolean,
|
|
67
|
+
) => {
|
|
68
|
+
const canonicalId = toCanonicalModelId(model);
|
|
69
|
+
const existing = byCanonicalId.get(canonicalId);
|
|
70
|
+
if (existing) {
|
|
71
|
+
if (isCurrent) existing.isCurrent = true;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
byCanonicalId.set(canonicalId, {
|
|
76
|
+
canonicalId,
|
|
77
|
+
provider: model.provider,
|
|
78
|
+
id: model.id,
|
|
79
|
+
model,
|
|
80
|
+
label: model.name ?? canonicalId,
|
|
81
|
+
description: canonicalId,
|
|
82
|
+
isCurrent,
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
if (ctx.model && matchModelPatterns(ctx.model, enabledModelPatterns)) {
|
|
87
|
+
addModel(ctx.model, true);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const model of availableModels) {
|
|
91
|
+
addModel(
|
|
92
|
+
model,
|
|
93
|
+
ctx.model ? toCanonicalModelId(model) === toCanonicalModelId(ctx.model) : false,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Array.from(byCanonicalId.values()).sort((a, b) => {
|
|
98
|
+
if (a.isCurrent !== b.isCurrent) return a.isCurrent ? -1 : 1;
|
|
99
|
+
return a.canonicalId.localeCompare(b.canonicalId);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── Private helpers ────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
function filterByEnabledModels<T extends { provider: string; id: string }>(
|
|
106
|
+
patterns: string[],
|
|
107
|
+
models: T[],
|
|
108
|
+
): T[] {
|
|
109
|
+
return models.filter((model) => matchModelPatterns(model, patterns));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function matchModelPatterns(model: { provider: string; id: string }, patterns: string[]): boolean {
|
|
113
|
+
return patterns.some((pattern) => matchModelPattern(model, pattern));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function matchModelPattern(model: { provider: string; id: string }, pattern: string): boolean {
|
|
117
|
+
const canonicalId = `${model.provider}/${model.id}`;
|
|
118
|
+
if (pattern.includes("/")) {
|
|
119
|
+
return simpleGlobMatch(canonicalId, pattern);
|
|
120
|
+
}
|
|
121
|
+
return simpleGlobMatch(model.id, pattern) || simpleGlobMatch(canonicalId, pattern);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function simpleGlobMatch(text: string, pattern: string): boolean {
|
|
125
|
+
if (!pattern.includes("*") && !pattern.includes("?")) {
|
|
126
|
+
return text.toLowerCase() === pattern.toLowerCase();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const regex = pattern
|
|
130
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
131
|
+
.replace(/\*/g, ".*")
|
|
132
|
+
.replace(/\?/g, ".");
|
|
133
|
+
return new RegExp(`^${regex}$`, "i").test(text);
|
|
134
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Extensions declare their settings via `registerSettings()` during their
|
|
4
4
|
// factory function. The generic settings UI reads them via `getRegisteredSettings()`.
|
|
5
5
|
|
|
6
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
7
|
import type { SettingItem } from "@earendil-works/pi-tui";
|
|
7
8
|
import { createRegistry } from "../registry-utils.ts";
|
|
8
9
|
|
|
@@ -14,9 +15,15 @@ export interface SettingsSection {
|
|
|
14
15
|
/** Human-readable label shown in the UI */
|
|
15
16
|
label: string;
|
|
16
17
|
/** Load current SettingItem[] for the given scope */
|
|
17
|
-
loadValues: (scope: SettingsScope, cwd: string) => SettingItem[];
|
|
18
|
+
loadValues: (scope: SettingsScope, cwd: string, ctx?: ExtensionContext) => SettingItem[];
|
|
18
19
|
/** Persist a change back to config */
|
|
19
|
-
persistChange: (
|
|
20
|
+
persistChange: (
|
|
21
|
+
scope: SettingsScope,
|
|
22
|
+
cwd: string,
|
|
23
|
+
settingId: string,
|
|
24
|
+
value: string,
|
|
25
|
+
ctx?: ExtensionContext,
|
|
26
|
+
) => void;
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
const registry = createRegistry<SettingsSection>("settings-registry");
|
|
@@ -10,10 +10,14 @@ import {
|
|
|
10
10
|
Input,
|
|
11
11
|
Key,
|
|
12
12
|
matchesKey,
|
|
13
|
+
type SelectItem,
|
|
14
|
+
SelectList,
|
|
15
|
+
type SelectListTheme,
|
|
13
16
|
type SettingItem,
|
|
14
17
|
SettingsList,
|
|
15
18
|
Text,
|
|
16
19
|
} from "@earendil-works/pi-tui";
|
|
20
|
+
import { getSelectableModels } from "../model-selection.ts";
|
|
17
21
|
import {
|
|
18
22
|
getRegisteredSettings,
|
|
19
23
|
type SettingsScope,
|
|
@@ -84,10 +88,11 @@ function buildFlatItems(
|
|
|
84
88
|
sections: SettingsSection[],
|
|
85
89
|
scope: SettingsScope,
|
|
86
90
|
cwd: string,
|
|
91
|
+
ctx?: ExtensionContext,
|
|
87
92
|
): SettingItem[] {
|
|
88
93
|
const items: SettingItem[] = [];
|
|
89
94
|
for (const section of sections) {
|
|
90
|
-
const sectionItems = section.loadValues(scope, cwd);
|
|
95
|
+
const sectionItems = section.loadValues(scope, cwd, ctx);
|
|
91
96
|
for (const item of sectionItems) {
|
|
92
97
|
items.push({
|
|
93
98
|
...item,
|
|
@@ -115,6 +120,7 @@ function findSectionAndId(
|
|
|
115
120
|
// ── Component ────────────────────────────────────────────────
|
|
116
121
|
|
|
117
122
|
interface SettingsOverlayDeps {
|
|
123
|
+
ctx: ExtensionContext;
|
|
118
124
|
state: OverlayState;
|
|
119
125
|
container: Container;
|
|
120
126
|
settingsList: SettingsList | null;
|
|
@@ -125,15 +131,21 @@ interface SettingsOverlayDeps {
|
|
|
125
131
|
|
|
126
132
|
function createSettingsList(deps: SettingsOverlayDeps): SettingsList {
|
|
127
133
|
const sections = getRegisteredSettings();
|
|
128
|
-
const items = buildFlatItems(sections, deps.state.scope, deps.state.cwd);
|
|
134
|
+
const items = buildFlatItems(sections, deps.state.scope, deps.state.cwd, deps.ctx);
|
|
129
135
|
const onChange = (flatId: string, newValue: string) => {
|
|
130
136
|
const found = findSectionAndId(sections, flatId);
|
|
131
137
|
if (found) {
|
|
132
|
-
found.section.persistChange(
|
|
138
|
+
found.section.persistChange(
|
|
139
|
+
deps.state.scope,
|
|
140
|
+
deps.state.cwd,
|
|
141
|
+
found.itemId,
|
|
142
|
+
newValue,
|
|
143
|
+
deps.ctx,
|
|
144
|
+
);
|
|
133
145
|
}
|
|
134
146
|
// Re-read all values to reflect persisted changes, but keep the list
|
|
135
147
|
// instance (and its selectedIndex) intact.
|
|
136
|
-
const updatedItems = buildFlatItems(sections, deps.state.scope, deps.state.cwd);
|
|
148
|
+
const updatedItems = buildFlatItems(sections, deps.state.scope, deps.state.cwd, deps.ctx);
|
|
137
149
|
for (const updated of updatedItems) {
|
|
138
150
|
const existing = items.find((i) => i.id === updated.id);
|
|
139
151
|
if (existing && existing.currentValue !== updated.currentValue) {
|
|
@@ -182,6 +194,98 @@ function handleScopeToggle(deps: SettingsOverlayDeps): void {
|
|
|
182
194
|
deps.tui.requestRender();
|
|
183
195
|
}
|
|
184
196
|
|
|
197
|
+
/** Minimal SelectList theme — uses identity so the parent SettingsList provides styling context. */
|
|
198
|
+
const PASSTHROUGH_THEME: SelectListTheme = {
|
|
199
|
+
selectedPrefix: (text) => `› ${text}`,
|
|
200
|
+
selectedText: (text) => text,
|
|
201
|
+
description: (text) => text,
|
|
202
|
+
scrollInfo: (text) => text,
|
|
203
|
+
noMatch: (text) => text,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Create a model picker submenu for settings.
|
|
208
|
+
*
|
|
209
|
+
* Shows a scrollable list of selectable models from the scoped model set,
|
|
210
|
+
* with the current session model annotated `[current]`. The first entry is
|
|
211
|
+
* always `"disabled"`.
|
|
212
|
+
*
|
|
213
|
+
* @param currentValue - Currently configured canonical model id or `"disabled"`.
|
|
214
|
+
* @param done - Callback invoked with the selected value, or undefined on cancel.
|
|
215
|
+
* @param ctx - Extension context for model listing. When undefined, only
|
|
216
|
+
* `"disabled"` is offered.
|
|
217
|
+
*/
|
|
218
|
+
export function createModelPickerSubmenu(
|
|
219
|
+
currentValue: string,
|
|
220
|
+
done: (selectedValue?: string) => void,
|
|
221
|
+
ctx?: ExtensionContext,
|
|
222
|
+
): {
|
|
223
|
+
render: (width: number) => string[];
|
|
224
|
+
invalidate: () => void;
|
|
225
|
+
handleInput: (data: string) => boolean;
|
|
226
|
+
} {
|
|
227
|
+
const items = buildModelItems(ctx);
|
|
228
|
+
|
|
229
|
+
const initialIndex =
|
|
230
|
+
currentValue === "disabled"
|
|
231
|
+
? 0
|
|
232
|
+
: Math.max(
|
|
233
|
+
0,
|
|
234
|
+
items.findIndex((item) => item.value === currentValue),
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const container = new Container();
|
|
238
|
+
container.addChild(new Text(" Select suggestion model", 1, 0));
|
|
239
|
+
container.addChild(new Text("", 1, 0));
|
|
240
|
+
|
|
241
|
+
const selectList = new SelectList(items, Math.min(items.length, 15), PASSTHROUGH_THEME);
|
|
242
|
+
|
|
243
|
+
if (initialIndex >= 0) {
|
|
244
|
+
selectList.setSelectedIndex(initialIndex);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
selectList.onSelect = (item) => done(item.value);
|
|
248
|
+
selectList.onCancel = () => done();
|
|
249
|
+
|
|
250
|
+
container.addChild(selectList);
|
|
251
|
+
container.addChild(new Text(" ↑↓ navigate • enter select • esc cancel", 1, 0));
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
render: (width: number) => container.render(width),
|
|
255
|
+
invalidate: () => container.invalidate(),
|
|
256
|
+
handleInput: (data: string) => {
|
|
257
|
+
selectList.handleInput(data);
|
|
258
|
+
return true;
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Build selectable model items with "disabled" first. */
|
|
264
|
+
function buildModelItems(ctx?: ExtensionContext): SelectItem[] {
|
|
265
|
+
const items: SelectItem[] = [
|
|
266
|
+
{
|
|
267
|
+
value: "disabled",
|
|
268
|
+
label: "disabled",
|
|
269
|
+
description: "No prompt suggestions",
|
|
270
|
+
},
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
if (!ctx) return items;
|
|
274
|
+
|
|
275
|
+
const models = getSelectableModels(ctx);
|
|
276
|
+
|
|
277
|
+
for (const model of models) {
|
|
278
|
+
const suffix = model.isCurrent ? " [current]" : "";
|
|
279
|
+
items.push({
|
|
280
|
+
value: model.canonicalId,
|
|
281
|
+
label: `${model.canonicalId}${suffix}`,
|
|
282
|
+
description: model.label !== model.canonicalId ? model.label : undefined,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return items;
|
|
287
|
+
}
|
|
288
|
+
|
|
185
289
|
// ── Entry point ──────────────────────────────────────────────
|
|
186
290
|
|
|
187
291
|
export function openSettingsOverlay(ctx: ExtensionContext): void {
|
|
@@ -196,6 +300,7 @@ export function openSettingsOverlay(ctx: ExtensionContext): void {
|
|
|
196
300
|
const container = new Container();
|
|
197
301
|
|
|
198
302
|
const deps: SettingsOverlayDeps = {
|
|
303
|
+
ctx,
|
|
199
304
|
state,
|
|
200
305
|
container,
|
|
201
306
|
settingsList: null,
|