@mrclrchtr/supi-code-intelligence 1.9.1 → 1.11.0
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 +15 -7
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/README.md +7 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +7 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/api.ts +2 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +112 -6
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config.ts +20 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/llm.ts +211 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/progress-widget.ts +108 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +66 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +4 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +13 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +45 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/workspace/context.ts +5 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/src/workspace/runtime.ts +14 -3
- package/node_modules/@mrclrchtr/supi-core/README.md +7 -1
- package/node_modules/@mrclrchtr/supi-core/package.json +7 -1
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +112 -6
- package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +20 -0
- package/node_modules/@mrclrchtr/supi-core/src/llm.ts +211 -0
- package/node_modules/@mrclrchtr/supi-core/src/progress-widget.ts +108 -0
- package/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +66 -0
- package/node_modules/@mrclrchtr/supi-lsp/README.md +7 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/README.md +7 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +7 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/api.ts +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +112 -6
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config.ts +20 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/llm.ts +211 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/progress-widget.ts +108 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +66 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +4 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +13 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +45 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/workspace/context.ts +5 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/workspace/runtime.ts +14 -3
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +7 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +7 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/api.ts +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +112 -6
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/config.ts +20 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/llm.ts +211 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/progress-widget.ts +108 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +66 -0
- package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-semantic-provider.ts +139 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +7 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/README.md +7 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +7 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/api.ts +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +112 -6
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config.ts +20 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/llm.ts +211 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/progress-widget.ts +108 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +66 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +4 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +13 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +45 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/workspace/context.ts +5 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/workspace/runtime.ts +14 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +7 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +7 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/api.ts +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +112 -6
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config.ts +20 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/llm.ts +211 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/progress-widget.ts +108 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +66 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
- package/package.json +5 -5
- package/src/intent/types.ts +34 -0
- package/src/planner/planner.ts +82 -0
- package/src/presentation/markdown/refactor.ts +27 -0
- package/src/refactor/apply-workspace-edit.ts +162 -0
- package/src/refactor/safety.ts +154 -0
- package/src/tool/execute-affected.ts +22 -0
- package/src/tool/execute-brief.ts +9 -2
- package/src/tool/execute-refactor.ts +101 -0
- package/src/tool/execute-relations.ts +21 -3
- package/src/tool/guidance.ts +18 -11
- package/src/tool/tool-specs.ts +25 -0
package/README.md
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<a href="https://github.com/mrclrchtr/supi/tree/main/packages/supi-code-intelligence">
|
|
3
|
+
<picture>
|
|
4
|
+
<img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-code-intelligence/assets/logo.png" alt="SuPi" width="50%">
|
|
5
|
+
</picture>
|
|
6
|
+
</a>
|
|
7
|
+
</div>
|
|
2
8
|
|
|
3
9
|
# @mrclrchtr/supi-code-intelligence
|
|
4
10
|
|
|
@@ -27,6 +33,7 @@ After install, pi gets:
|
|
|
27
33
|
- `code_relations` — callers, callees, or implementations for a resolved target
|
|
28
34
|
- `code_affected` — blast radius, downstream impact, and risk for a target
|
|
29
35
|
- `code_pattern` — explicit literal, regex, or structured search
|
|
36
|
+
- `code_refactor` — direct-apply semantic rename with safety gates
|
|
30
37
|
- a lightweight hidden architecture overview injected near the start of a session when a project model can be built
|
|
31
38
|
- **all `lsp_*` expert tools** from `@mrclrchtr/supi-lsp` (hover, definition, references, implementation, diagnostics, rename, code actions, recover, document/workspace symbols)
|
|
32
39
|
- **all `tree_sitter_*` expert tools** from `@mrclrchtr/supi-tree-sitter` (outline, imports, exports, node-at, query, callees)
|
|
@@ -109,15 +116,16 @@ Results report confidence such as:
|
|
|
109
116
|
## Architecture
|
|
110
117
|
|
|
111
118
|
`@mrclrchtr/supi-code-intelligence` is the **orchestration layer** that consumes
|
|
112
|
-
semantic and structural providers through shared
|
|
119
|
+
semantic and structural providers through the shared workspace broker and routes
|
|
120
|
+
user intents through a planner.
|
|
113
121
|
|
|
114
122
|
```text
|
|
115
|
-
supi-code-
|
|
116
|
-
|
|
117
|
-
supi-lsp
|
|
123
|
+
supi-code-runtime ← shared broker + canonical provider/result contracts
|
|
124
|
+
↑
|
|
125
|
+
supi-lsp / supi-tree-sitter
|
|
118
126
|
(semantic) (structural)
|
|
119
|
-
|
|
120
|
-
|
|
127
|
+
↑
|
|
128
|
+
supi-code-intelligence ← planner, presentation, code_* tools, code_refactor
|
|
121
129
|
```
|
|
122
130
|
|
|
123
131
|
## Package surfaces
|
package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/README.md
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<a href="https://github.com/mrclrchtr/supi/tree/main/packages/supi-core">
|
|
3
|
+
<picture>
|
|
4
|
+
<img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-core/assets/logo.png" alt="SuPi" width="50%">
|
|
5
|
+
</picture>
|
|
6
|
+
</a>
|
|
7
|
+
</div>
|
|
2
8
|
|
|
3
9
|
# @mrclrchtr/supi-core
|
|
4
10
|
|
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": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "SuPi core — shared infrastructure for SuPi extensions (XML context tags, config system)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -19,11 +19,15 @@
|
|
|
19
19
|
"!__tests__"
|
|
20
20
|
],
|
|
21
21
|
"peerDependencies": {
|
|
22
|
+
"@earendil-works/pi-ai": "*",
|
|
22
23
|
"@earendil-works/pi-coding-agent": "*",
|
|
23
24
|
"@earendil-works/pi-tui": "*",
|
|
24
25
|
"typebox": "*"
|
|
25
26
|
},
|
|
26
27
|
"peerDependenciesMeta": {
|
|
28
|
+
"@earendil-works/pi-ai": {
|
|
29
|
+
"optional": true
|
|
30
|
+
},
|
|
27
31
|
"@earendil-works/pi-coding-agent": {
|
|
28
32
|
"optional": true
|
|
29
33
|
},
|
|
@@ -40,8 +44,10 @@
|
|
|
40
44
|
"./config": "./src/config.ts",
|
|
41
45
|
"./context": "./src/context.ts",
|
|
42
46
|
"./debug": "./src/debug-registry.ts",
|
|
47
|
+
"./llm": "./src/llm.ts",
|
|
43
48
|
"./package.json": "./package.json",
|
|
44
49
|
"./path": "./src/path.ts",
|
|
50
|
+
"./progress-widget": "./src/progress-widget.ts",
|
|
45
51
|
"./project": "./src/project.ts",
|
|
46
52
|
"./session": "./src/session.ts",
|
|
47
53
|
"./settings": "./src/settings.ts",
|
package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/api.ts
CHANGED
|
@@ -13,6 +13,8 @@ export * from "./context.ts";
|
|
|
13
13
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
14
14
|
export * from "./debug-registry.ts";
|
|
15
15
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
16
|
+
export * from "./llm.ts";
|
|
17
|
+
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
16
18
|
export * from "./path.ts";
|
|
17
19
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
18
20
|
export * from "./project.ts";
|
|
@@ -1,11 +1,42 @@
|
|
|
1
1
|
// Config-aware settings helper for SuPi config-backed settings sections.
|
|
2
2
|
// Wraps registerSettings() and centralizes selected-scope loading + scoped persistence.
|
|
3
|
+
//
|
|
4
|
+
// Setting items can declare a `configType` ("boolean" | "number" | "stringList")
|
|
5
|
+
// to enable auto-generated persistChange. When all items have a configType,
|
|
6
|
+
// the persistChange callback can be omitted.
|
|
3
7
|
|
|
4
8
|
import type { SettingItem } from "@earendil-works/pi-tui";
|
|
5
9
|
import type { SettingsScope } from "../settings/settings-registry.ts";
|
|
6
10
|
import { registerSettings } from "../settings/settings-registry.ts";
|
|
7
11
|
import { loadSupiConfigForScope, removeSupiConfigKey, writeSupiConfig } from "./config.ts";
|
|
8
12
|
|
|
13
|
+
// ── Types ──────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Supported config value types for declarative persistChange.
|
|
17
|
+
*
|
|
18
|
+
* - `"boolean"`: maps "on" → true, "off" → false
|
|
19
|
+
* - `"number"`: parses integer via Number.parseInt, falls back to unset on invalid
|
|
20
|
+
* - `"stringList"`: splits on comma, trims whitespace, unsets on empty
|
|
21
|
+
*/
|
|
22
|
+
export type ConfigSettingType = "boolean" | "number" | "stringList";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Extended setting item that can declare its config type for auto-generated
|
|
26
|
+
* persistence handling.
|
|
27
|
+
*/
|
|
28
|
+
export interface ConfigSettingItem extends SettingItem {
|
|
29
|
+
/**
|
|
30
|
+
* When set, persistChange for this item is auto-generated.
|
|
31
|
+
* All items must declare a configType for auto-generation to activate.
|
|
32
|
+
*/
|
|
33
|
+
configType?: ConfigSettingType;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Helpers provided to the persistChange callback for writing or removing
|
|
38
|
+
* scoped config values.
|
|
39
|
+
*/
|
|
9
40
|
export interface ConfigSettingsHelpers {
|
|
10
41
|
/** Write a key to the selected scope's config section. */
|
|
11
42
|
set(key: string, value: unknown): void;
|
|
@@ -22,10 +53,21 @@ export interface ConfigSettingsOptions<T> {
|
|
|
22
53
|
section: string;
|
|
23
54
|
/** Default config values */
|
|
24
55
|
defaults: T;
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Build SettingItem[] from scoped config. Called by loadValues.
|
|
58
|
+
*
|
|
59
|
+
* Items can include a `configType` property for auto-generated
|
|
60
|
+
* persistChange handling. When ALL items declare a configType,
|
|
61
|
+
* the `persistChange` callback can be omitted.
|
|
62
|
+
*/
|
|
63
|
+
buildItems: (settings: T, scope: SettingsScope, cwd: string) => ConfigSettingItem[];
|
|
64
|
+
/**
|
|
65
|
+
* Handle a settings change with scoped persistence helpers.
|
|
66
|
+
*
|
|
67
|
+
* Optional when all items returned by `buildItems` declare a `configType`.
|
|
68
|
+
* Required when any item lacks a `configType`.
|
|
69
|
+
*/
|
|
70
|
+
persistChange?: (
|
|
29
71
|
scope: SettingsScope,
|
|
30
72
|
cwd: string,
|
|
31
73
|
settingId: string,
|
|
@@ -36,6 +78,52 @@ export interface ConfigSettingsOptions<T> {
|
|
|
36
78
|
homeDir?: string;
|
|
37
79
|
}
|
|
38
80
|
|
|
81
|
+
// ── Auto-generated persistChange ───────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
function autoPersistChange(
|
|
84
|
+
settingId: string,
|
|
85
|
+
value: string,
|
|
86
|
+
helpers: ConfigSettingsHelpers,
|
|
87
|
+
items: ConfigSettingItem[],
|
|
88
|
+
): void {
|
|
89
|
+
const item = items.find((i) => i.id === settingId);
|
|
90
|
+
if (!item?.configType) return;
|
|
91
|
+
|
|
92
|
+
switch (item.configType) {
|
|
93
|
+
case "boolean": {
|
|
94
|
+
helpers.set(settingId, value === "on");
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case "number": {
|
|
98
|
+
const num = Number.parseInt(value, 10);
|
|
99
|
+
if (Number.isFinite(num) && num > 0) {
|
|
100
|
+
helpers.set(settingId, num);
|
|
101
|
+
} else {
|
|
102
|
+
helpers.unset(settingId);
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
case "stringList": {
|
|
107
|
+
const names = value
|
|
108
|
+
.split(",")
|
|
109
|
+
.map((s) => s.trim())
|
|
110
|
+
.filter((s) => s.length > 0);
|
|
111
|
+
if (names.length > 0) {
|
|
112
|
+
helpers.set(settingId, names);
|
|
113
|
+
} else {
|
|
114
|
+
helpers.unset(settingId);
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function areAllItemsDeclarative(items: ConfigSettingItem[]): boolean {
|
|
122
|
+
return items.length > 0 && items.every((i) => i.configType !== undefined);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Registration ───────────────────────────────────────────────────────────
|
|
126
|
+
|
|
39
127
|
/**
|
|
40
128
|
* Register a config-backed settings section.
|
|
41
129
|
*
|
|
@@ -43,8 +131,13 @@ export interface ConfigSettingsOptions<T> {
|
|
|
43
131
|
* instead of merged effective runtime config. Provides scoped `set` / `unset`
|
|
44
132
|
* persistence helpers so extensions don't need to wire `writeSupiConfig` /
|
|
45
133
|
* `removeSupiConfigKey` by hand.
|
|
134
|
+
*
|
|
135
|
+
* When every item returned by `buildItems` declares a `configType`, the
|
|
136
|
+
* `persistChange` callback is optional and will be auto-generated.
|
|
46
137
|
*/
|
|
47
138
|
export function registerConfigSettings<T>(options: ConfigSettingsOptions<T>): void {
|
|
139
|
+
let cachedItems: ConfigSettingItem[] | undefined;
|
|
140
|
+
|
|
48
141
|
registerSettings({
|
|
49
142
|
id: options.id,
|
|
50
143
|
label: options.label,
|
|
@@ -53,7 +146,9 @@ export function registerConfigSettings<T>(options: ConfigSettingsOptions<T>): vo
|
|
|
53
146
|
scope,
|
|
54
147
|
homeDir: options.homeDir,
|
|
55
148
|
});
|
|
56
|
-
|
|
149
|
+
const items = options.buildItems(settings, scope, cwd);
|
|
150
|
+
cachedItems = items;
|
|
151
|
+
return items;
|
|
57
152
|
},
|
|
58
153
|
persistChange: (scope, cwd, settingId, value) => {
|
|
59
154
|
const helpers: ConfigSettingsHelpers = {
|
|
@@ -70,7 +165,18 @@ export function registerConfigSettings<T>(options: ConfigSettingsOptions<T>): vo
|
|
|
70
165
|
});
|
|
71
166
|
},
|
|
72
167
|
};
|
|
73
|
-
|
|
168
|
+
|
|
169
|
+
// Use manual persistChange when provided
|
|
170
|
+
if (options.persistChange) {
|
|
171
|
+
options.persistChange(scope, cwd, settingId, value, helpers);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Auto-generate when all items are declarative
|
|
176
|
+
const items = cachedItems ?? options.buildItems(options.defaults, scope, cwd);
|
|
177
|
+
if (areAllItemsDeclarative(items)) {
|
|
178
|
+
autoPersistChange(settingId, value, helpers, items);
|
|
179
|
+
}
|
|
74
180
|
},
|
|
75
181
|
});
|
|
76
182
|
}
|
|
@@ -184,3 +184,23 @@ function extractSection(
|
|
|
184
184
|
}
|
|
185
185
|
return null;
|
|
186
186
|
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Shorthand for {@link loadSupiConfig} that infers the return type from defaults.
|
|
190
|
+
*
|
|
191
|
+
* Reduces boilerplate when a package only needs the merged runtime config.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```ts
|
|
195
|
+
* const config = loadSectionConfig("my-ext", cwd, { enabled: true, timeout: 30 });
|
|
196
|
+
* // config is typed as { enabled: boolean; timeout: number }
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
export function loadSectionConfig<T extends Record<string, unknown>>(
|
|
200
|
+
section: string,
|
|
201
|
+
cwd: string,
|
|
202
|
+
defaults: T,
|
|
203
|
+
options?: SupiConfigOptions,
|
|
204
|
+
): T {
|
|
205
|
+
return loadSupiConfig(section, cwd, defaults, options);
|
|
206
|
+
}
|
package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/llm.ts
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { complete } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { TSchema } from "typebox";
|
|
4
|
+
import { Value } from "typebox/value";
|
|
5
|
+
|
|
6
|
+
// Shared LLM utilities for SuPi extensions.
|
|
7
|
+
//
|
|
8
|
+
// Provides retry logic, structured LLM call helpers, and other
|
|
9
|
+
// common patterns for extensions that interact with AI models.
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Options for {@link withRetry}.
|
|
13
|
+
*/
|
|
14
|
+
export interface WithRetryOptions {
|
|
15
|
+
/** Maximum number of retry attempts after the initial call. Default: 2 */
|
|
16
|
+
retries?: number;
|
|
17
|
+
/** Base delay in milliseconds for exponential backoff. Default: 1000 */
|
|
18
|
+
baseDelayMs?: number;
|
|
19
|
+
/** AbortSignal to cancel retry loops. */
|
|
20
|
+
signal?: AbortSignal;
|
|
21
|
+
/** Called with each failed attempt's attempt index and error. */
|
|
22
|
+
logger?: (attempt: number, error: unknown) => void;
|
|
23
|
+
/** Called before each retry delay with attempt index and computed delay. */
|
|
24
|
+
onRetry?: (attempt: number, delayMs: number) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Attempt an async operation with retries and exponential backoff.
|
|
29
|
+
*
|
|
30
|
+
* If the signal is already aborted on entry, the operation is skipped entirely.
|
|
31
|
+
* If the signal aborts during a delay, the delay is cancelled immediately.
|
|
32
|
+
*
|
|
33
|
+
* @param fn - The async operation to retry.
|
|
34
|
+
* @param options - Optional configuration for retries, backoff, signal, and callbacks.
|
|
35
|
+
* @returns The result on success, or `null` if all attempts fail or the signal aborts.
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Create a promise that resolves after `ms` milliseconds, or rejects if
|
|
39
|
+
* the signal fires before the timeout elapses.
|
|
40
|
+
*/
|
|
41
|
+
function delay(ms: number, signal?: AbortSignal): Promise<void> {
|
|
42
|
+
return new Promise<void>((resolve, reject) => {
|
|
43
|
+
const timer = setTimeout(resolve, ms);
|
|
44
|
+
if (signal) {
|
|
45
|
+
const onAbort = () => {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
48
|
+
};
|
|
49
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Attempt an async operation with retries and exponential backoff.
|
|
56
|
+
*
|
|
57
|
+
* If the signal is already aborted on entry, the operation is skipped entirely.
|
|
58
|
+
* If the signal aborts during a delay, the delay is cancelled immediately.
|
|
59
|
+
*
|
|
60
|
+
* @param fn - The async operation to retry.
|
|
61
|
+
* @param options - Optional configuration for retries, backoff, signal, and callbacks.
|
|
62
|
+
* @returns The result on success, or `null` if all attempts fail or the signal aborts.
|
|
63
|
+
*/
|
|
64
|
+
export async function withRetry<T>(
|
|
65
|
+
fn: () => Promise<T>,
|
|
66
|
+
options?: WithRetryOptions,
|
|
67
|
+
): Promise<T | null> {
|
|
68
|
+
const { retries = 2, baseDelayMs = 1000, signal, logger, onRetry } = options ?? {};
|
|
69
|
+
|
|
70
|
+
if (signal?.aborted) return null;
|
|
71
|
+
|
|
72
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
73
|
+
try {
|
|
74
|
+
return await fn();
|
|
75
|
+
} catch (err) {
|
|
76
|
+
logger?.(attempt, err);
|
|
77
|
+
if (attempt >= retries || signal?.aborted) continue;
|
|
78
|
+
|
|
79
|
+
const delayMs = baseDelayMs * 2 ** attempt;
|
|
80
|
+
onRetry?.(attempt, delayMs);
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
await delay(delayMs, signal);
|
|
84
|
+
} catch {
|
|
85
|
+
// delay() only rejects on abort
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Extract and validate JSON from LLM response content blocks.
|
|
96
|
+
*
|
|
97
|
+
* Finds the first JSON object `{...}` in the combined text content,
|
|
98
|
+
* parses it, and validates against a TypeBox schema.
|
|
99
|
+
*
|
|
100
|
+
* @param content - The LLM response content blocks.
|
|
101
|
+
* @param schema - TypeBox schema to validate against.
|
|
102
|
+
* @returns The parsed and validated result, or `null` if extraction or validation fails.
|
|
103
|
+
*/
|
|
104
|
+
export function extractJsonFromResponse<T extends TSchema>(
|
|
105
|
+
content: ReadonlyArray<{ type: string; text?: string }>,
|
|
106
|
+
schema: T,
|
|
107
|
+
): { parsed: import("typebox").Static<T> } | null {
|
|
108
|
+
const text = content
|
|
109
|
+
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
110
|
+
.map((c) => c.text)
|
|
111
|
+
.join("");
|
|
112
|
+
|
|
113
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
114
|
+
if (!jsonMatch) return null;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
118
|
+
if (Value.Check(schema, parsed)) {
|
|
119
|
+
return { parsed } as { parsed: import("typebox").Static<T> };
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── callWithJsonResponse ───────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Options for {@link callWithJsonResponse}.
|
|
131
|
+
*/
|
|
132
|
+
export interface CallWithJsonResponseOptions {
|
|
133
|
+
/** The prompt to send to the LLM. */
|
|
134
|
+
prompt: string;
|
|
135
|
+
/** Optional data context appended to the prompt. */
|
|
136
|
+
dataContext?: string;
|
|
137
|
+
/** Maximum tokens for the response. Default: 4096 */
|
|
138
|
+
maxTokens?: number;
|
|
139
|
+
/** System prompt for the LLM call. Default: "" */
|
|
140
|
+
systemPrompt?: string;
|
|
141
|
+
/** Number of retries for the LLM call. Default: 2 */
|
|
142
|
+
retries?: number;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Call the LLM with a prompt and validate the JSON response against a TypeBox schema.
|
|
147
|
+
*
|
|
148
|
+
* Handles model resolution, auth, retry via `withRetry`, text extraction,
|
|
149
|
+
* JSON regex matching, and TypeBox validation.
|
|
150
|
+
*
|
|
151
|
+
* Returns `null` when:
|
|
152
|
+
* - No model is available
|
|
153
|
+
* - All retries fail
|
|
154
|
+
* - Response contains no valid JSON
|
|
155
|
+
* - JSON doesn't match the schema
|
|
156
|
+
* - The request is aborted
|
|
157
|
+
*
|
|
158
|
+
* @param ctx - The extension context for model resolution and auth.
|
|
159
|
+
* @param options - Call options including prompt, schema, and retry config.
|
|
160
|
+
* @param schema - TypeBox schema to validate the JSON response against.
|
|
161
|
+
* @returns The parsed and validated result, or `null`.
|
|
162
|
+
*/
|
|
163
|
+
export async function callWithJsonResponse<T extends TSchema>(
|
|
164
|
+
ctx: ExtensionContext,
|
|
165
|
+
options: CallWithJsonResponseOptions,
|
|
166
|
+
schema: T,
|
|
167
|
+
): Promise<{ parsed: import("typebox").Static<T> } | null> {
|
|
168
|
+
const { prompt, dataContext, maxTokens = 4096, systemPrompt = "", retries = 2 } = options;
|
|
169
|
+
|
|
170
|
+
const model = ctx.model ?? ctx.modelRegistry.getAvailable()[0] ?? null;
|
|
171
|
+
if (!model) return null;
|
|
172
|
+
|
|
173
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
174
|
+
if (!auth.ok || !auth.apiKey) return null;
|
|
175
|
+
|
|
176
|
+
const fullPrompt = dataContext
|
|
177
|
+
? `${prompt}
|
|
178
|
+
|
|
179
|
+
DATA:
|
|
180
|
+
${dataContext}`
|
|
181
|
+
: prompt;
|
|
182
|
+
|
|
183
|
+
const response = await withRetry(
|
|
184
|
+
async () => {
|
|
185
|
+
return complete(
|
|
186
|
+
model,
|
|
187
|
+
{
|
|
188
|
+
systemPrompt,
|
|
189
|
+
messages: [
|
|
190
|
+
{
|
|
191
|
+
role: "user",
|
|
192
|
+
content: [{ type: "text", text: fullPrompt }],
|
|
193
|
+
timestamp: Date.now(),
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
apiKey: auth.apiKey,
|
|
199
|
+
headers: auth.headers,
|
|
200
|
+
signal: ctx.signal,
|
|
201
|
+
maxTokens,
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
},
|
|
205
|
+
{ retries, baseDelayMs: 1000, signal: ctx.signal },
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
if (!response) return null;
|
|
209
|
+
|
|
210
|
+
return extractJsonFromResponse(response.content, schema);
|
|
211
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Generic progress widget for SuPi long-running operations.
|
|
2
|
+
//
|
|
3
|
+
// Provides a TUI-based progress display with animated loader, turn counts,
|
|
4
|
+
// tool usage, and activity descriptions.
|
|
5
|
+
|
|
6
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { CancellableLoader, Container, Text } from "@earendil-works/pi-tui";
|
|
8
|
+
|
|
9
|
+
// ── Types ──────────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
/** Progress state for widget display, compatible with child-session updates. */
|
|
12
|
+
export interface WidgetProgress {
|
|
13
|
+
/** Number of agent turns completed. */
|
|
14
|
+
turns: number;
|
|
15
|
+
/** Number of tool executions started. */
|
|
16
|
+
toolUses: number;
|
|
17
|
+
/** Human-readable active tool descriptions. */
|
|
18
|
+
activities: string[];
|
|
19
|
+
/** Token usage stats, if available. */
|
|
20
|
+
tokens?: { input: number; output: number; total: number };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ── Widget ─────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* TUI progress widget for long-running operations.
|
|
27
|
+
*
|
|
28
|
+
* Shows an animated loader, turn count, tool uses, token count, and any active
|
|
29
|
+
* tool descriptions while the child session or operation is running.
|
|
30
|
+
*/
|
|
31
|
+
export class ProgressWidget extends Container {
|
|
32
|
+
private message: string;
|
|
33
|
+
private progress: WidgetProgress = { turns: 0, toolUses: 0, activities: [] };
|
|
34
|
+
private loader: CancellableLoader;
|
|
35
|
+
private tui: { requestRender(): void };
|
|
36
|
+
private theme: Theme;
|
|
37
|
+
|
|
38
|
+
constructor(tui: { requestRender(): void }, theme: Theme, message: string) {
|
|
39
|
+
super();
|
|
40
|
+
this.tui = tui;
|
|
41
|
+
this.theme = theme;
|
|
42
|
+
this.message = message;
|
|
43
|
+
this.loader = new CancellableLoader(
|
|
44
|
+
tui as ConstructorParameters<typeof CancellableLoader>[0],
|
|
45
|
+
(text: string) => theme.fg("accent", text),
|
|
46
|
+
(text: string) => theme.fg("muted", text),
|
|
47
|
+
message,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
this.renderContent();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** AbortSignal that fires when the user presses Escape. */
|
|
54
|
+
get signal(): AbortSignal {
|
|
55
|
+
return this.loader.signal;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Callback invoked when the user presses Escape. */
|
|
59
|
+
set onAbort(fn: (() => void) | undefined) {
|
|
60
|
+
this.loader.onAbort = fn;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Delegate keyboard input to the loader. */
|
|
64
|
+
handleInput(data: string): void {
|
|
65
|
+
this.loader.handleInput(data);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Update progress state and request a re-render. */
|
|
69
|
+
updateProgress(progress: WidgetProgress): void {
|
|
70
|
+
this.progress = progress;
|
|
71
|
+
this.renderContent();
|
|
72
|
+
this.tui.requestRender();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Clean up the widget. */
|
|
76
|
+
dispose(): void {
|
|
77
|
+
this.loader.dispose();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private renderContent(): void {
|
|
81
|
+
this.clear();
|
|
82
|
+
|
|
83
|
+
const stats: string[] = [];
|
|
84
|
+
if (this.progress.turns > 0) stats.push(`⟳${this.progress.turns}`);
|
|
85
|
+
if (this.progress.toolUses > 0) stats.push(`${this.progress.toolUses} tool uses`);
|
|
86
|
+
if (this.progress.tokens) stats.push(`${formatTokens(this.progress.tokens.total)} tokens`);
|
|
87
|
+
|
|
88
|
+
const loaderMessage =
|
|
89
|
+
stats.length > 0 ? `${this.message} · ${stats.join(" · ")}` : this.message;
|
|
90
|
+
|
|
91
|
+
this.loader.setMessage(loaderMessage);
|
|
92
|
+
this.addChild(this.loader);
|
|
93
|
+
|
|
94
|
+
if (this.progress.activities.length > 0) {
|
|
95
|
+
this.addChild(
|
|
96
|
+
new Text(this.theme.fg("dim", ` ⎿ ${this.progress.activities.join(", ")}…`), 1, 0),
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
function formatTokens(count: number): string {
|
|
105
|
+
if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M`;
|
|
106
|
+
if (count >= 1_000) return `${(count / 1_000).toFixed(1)}k`;
|
|
107
|
+
return String(count);
|
|
108
|
+
}
|
|
@@ -8,9 +8,11 @@ import type {
|
|
|
8
8
|
AgentToolResult,
|
|
9
9
|
AgentToolUpdateCallback,
|
|
10
10
|
ExtensionAPI,
|
|
11
|
+
ExtensionCommandContext,
|
|
11
12
|
ExtensionContext,
|
|
12
13
|
} from "@earendil-works/pi-coding-agent";
|
|
13
14
|
import { type TSchema, Type } from "typebox";
|
|
15
|
+
import { ProgressWidget, type WidgetProgress } from "./progress-widget.ts";
|
|
14
16
|
|
|
15
17
|
// ---------------------------------------------------------------------------
|
|
16
18
|
// Types
|
|
@@ -114,3 +116,67 @@ export const SymbolParam = Type.String({
|
|
|
114
116
|
|
|
115
117
|
/** Maximum results to return. */
|
|
116
118
|
export const MaxResultsParam = Type.Number({ description: "Maximum results to return" });
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// Progress widget runner
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Run an async operation with a live TUI progress widget.
|
|
126
|
+
*
|
|
127
|
+
* Automatically manages:
|
|
128
|
+
* - The {@link ProgressWidget} lifecycle
|
|
129
|
+
* - `supi:working:start` / `supi:working:end` events for tab-spinner integration
|
|
130
|
+
* - Abort signal handling
|
|
131
|
+
* - Error catching (returns `null` on failure)
|
|
132
|
+
*
|
|
133
|
+
* Falls back to running without a widget when `ctx.hasUI` is false.
|
|
134
|
+
*
|
|
135
|
+
* @param pi - The extension API (for event emission).
|
|
136
|
+
* @param ctx - The command context (for UI access and hasUI check).
|
|
137
|
+
* @param title - The progress widget title.
|
|
138
|
+
* @param runner - Async function that receives (signal, onProgress).
|
|
139
|
+
* @returns The runner result, or `null` on cancel/error.
|
|
140
|
+
*/
|
|
141
|
+
export async function runWithProgressWidget<T>(
|
|
142
|
+
pi: ExtensionAPI,
|
|
143
|
+
ctx: ExtensionCommandContext,
|
|
144
|
+
title: string,
|
|
145
|
+
runner: (signal: AbortSignal, onProgress: (p: WidgetProgress) => void) => Promise<T>,
|
|
146
|
+
): Promise<T | null> {
|
|
147
|
+
if (!ctx.hasUI) {
|
|
148
|
+
// No UI — run without progress widget but still emit working events
|
|
149
|
+
pi.events.emit("supi:working:start", { source: "supi-core" });
|
|
150
|
+
try {
|
|
151
|
+
return await runner(new AbortController().signal, () => {});
|
|
152
|
+
} catch {
|
|
153
|
+
return null;
|
|
154
|
+
} finally {
|
|
155
|
+
pi.events.emit("supi:working:end", { source: "supi-core" });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return ctx.ui.custom<T | null>((tui, theme, _kb, done) => {
|
|
160
|
+
const widget = new ProgressWidget(tui, theme, title);
|
|
161
|
+
let finished = false;
|
|
162
|
+
|
|
163
|
+
const finish = (result: T | null) => {
|
|
164
|
+
if (finished) return;
|
|
165
|
+
finished = true;
|
|
166
|
+
pi.events.emit("supi:working:end", { source: "supi-core" });
|
|
167
|
+
widget.dispose();
|
|
168
|
+
done(result);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
widget.onAbort = () => {
|
|
172
|
+
// Widget handles abort signal; runner resolves with cancel/error.
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
pi.events.emit("supi:working:start", { source: "supi-core" });
|
|
176
|
+
runner(widget.signal, (progress) => widget.updateProgress(progress))
|
|
177
|
+
.then((result) => finish(result))
|
|
178
|
+
.catch(() => finish(null));
|
|
179
|
+
|
|
180
|
+
return widget;
|
|
181
|
+
});
|
|
182
|
+
}
|