@nmzpy/pi-ember-stack 0.1.5 → 0.2.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 +4 -4
- package/package.json +12 -2
- package/plugins/devin-auth/extensions/index.ts +45 -7
- package/plugins/devin-auth/src/models.ts +42 -196
- package/plugins/index.ts +30 -7
- package/plugins/pi-compact-tools/index.ts +69 -48
- package/plugins/pi-compact-tools/renderer.ts +419 -0
- package/plugins/pi-custom-agents/index.ts +489 -343
- package/plugins/pi-custom-agents/questionnaire-tool.ts +1 -0
- package/plugins/pi-custom-agents/subagent/agents/coder.md +1 -0
- package/plugins/pi-custom-agents/subagent/agents/scout.md +16 -37
- package/plugins/pi-custom-agents/subagent/extensions/index.ts +6 -3
- package/plugins/pi-custom-agents/subagent/extensions/runner.ts +31 -5
- package/plugins/pi-ember-fff/index.ts +717 -0
- package/plugins/pi-ember-fff/query.ts +87 -0
- package/plugins/pi-ember-fff/test/query.test.ts +66 -0
- package/plugins/pi-ember-fff/test/renderer.test.ts +376 -0
- package/plugins/pi-ember-tps/index.ts +122 -0
- package/plugins/pi-ember-ui/ember.json +96 -0
- package/plugins/pi-ember-ui/index.ts +618 -0
- package/plugins/pi-ember-ui/mode-colors.ts +168 -0
- package/tsconfig.json +2 -1
- package/plugins/pi-custom-agents/subagent/agents/architect.md +0 -56
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ the TUI. Restart pi after changing the list. The available plugins are:
|
|
|
39
39
|
The Ember repository contains a project-local `.pi/settings.json` entry for:
|
|
40
40
|
|
|
41
41
|
```json
|
|
42
|
-
"npm:@nmzpy/pi-ember-stack@0.1.
|
|
42
|
+
"npm:@nmzpy/pi-ember-stack@0.1.6"
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
On a new clone, start pi from the project directory. Pi will ask for a
|
|
@@ -69,9 +69,9 @@ of this repository.
|
|
|
69
69
|
## Development
|
|
70
70
|
|
|
71
71
|
The package entrypoint is `plugins/index.ts`. Compact tools are under
|
|
72
|
-
`plugins/pi-compact-tools/`, while primary modes, plans,
|
|
73
|
-
agents are under `plugins/pi-custom-agents/`. Devin auth
|
|
74
|
-
`plugins/devin-auth/`.
|
|
72
|
+
`plugins/pi-compact-tools/`, while questionnaire, primary modes, plans,
|
|
73
|
+
subagents, and bundled agents are under `plugins/pi-custom-agents/`. Devin auth
|
|
74
|
+
is under `plugins/devin-auth/`.
|
|
75
75
|
|
|
76
76
|
Run the package typecheck with:
|
|
77
77
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nmzpy/pi-ember-stack",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "Ember's configurable pi plugin stack with modes, questionnaire, compact edits, subagents, and Devin auth",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Ember's configurable pi plugin stack with modes, questionnaire, compact edits, subagents, and Devin auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -25,11 +25,19 @@
|
|
|
25
25
|
"tsconfig.json",
|
|
26
26
|
"plugins"
|
|
27
27
|
],
|
|
28
|
+
"exports": {
|
|
29
|
+
".": "./plugins/index.ts",
|
|
30
|
+
"./renderer": "./plugins/pi-compact-tools/renderer.ts"
|
|
31
|
+
},
|
|
28
32
|
"pi": {
|
|
29
33
|
"extensions": [
|
|
30
34
|
"./plugins/index.ts"
|
|
31
35
|
]
|
|
32
36
|
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@ff-labs/fff-node": "0.9.6",
|
|
39
|
+
"@sinclair/typebox": "^0.34.0"
|
|
40
|
+
},
|
|
33
41
|
"peerDependencies": {
|
|
34
42
|
"@earendil-works/pi-ai": "*",
|
|
35
43
|
"@earendil-works/pi-agent-core": "*",
|
|
@@ -42,6 +50,8 @@
|
|
|
42
50
|
"@earendil-works/pi-agent-core": "^0.80.6",
|
|
43
51
|
"@earendil-works/pi-coding-agent": "^0.80.6",
|
|
44
52
|
"@earendil-works/pi-tui": "^0.80.6",
|
|
53
|
+
"@ff-labs/fff-node": "0.9.6",
|
|
54
|
+
"@sinclair/typebox": "^0.34.0",
|
|
45
55
|
"@types/node": "^20.19.43",
|
|
46
56
|
"typebox": "^1.3.1",
|
|
47
57
|
"typescript": "^5.9.3"
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* - OAuth login via Windsurf's browser sign-in flow (`loginDevin`)
|
|
6
6
|
* - A no-op token refresh (Windsurf api_keys are long-lived)
|
|
7
7
|
* - Live model catalog fetch from Cognition's GetCascadeModelConfigs
|
|
8
|
-
* after login,
|
|
8
|
+
* after login, surfacing every model the account has access to
|
|
9
9
|
* - `/devin-refresh` command to manually re-fetch the catalog
|
|
10
10
|
* - `/devin-status` command to check auth state
|
|
11
11
|
* - `session_start` auto-fetch when already logged in
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
* and auth are handled internally via the OAuth-issued api_key.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
18
|
+
import type { ExtensionAPI, ProviderModelConfig } from '@earendil-works/pi-coding-agent';
|
|
19
|
+
import { AuthStorage } from '@earendil-works/pi-coding-agent';
|
|
19
20
|
import type {
|
|
20
21
|
Api,
|
|
21
22
|
Model,
|
|
@@ -24,7 +25,7 @@ import type {
|
|
|
24
25
|
} from '@earendil-works/pi-ai';
|
|
25
26
|
import { streamDevin } from '../src/stream.js';
|
|
26
27
|
import { loginDevin } from '../src/oauth/login.js';
|
|
27
|
-
import { buildLiveModels,
|
|
28
|
+
import { buildLiveModels, DEFAULT_HOST } from '../src/models.js';
|
|
28
29
|
import { getCachedCatalog, clearCachedCatalog } from '../src/cloud-direct/catalog.js';
|
|
29
30
|
import { DEFAULT_REGION } from '../src/oauth/types.js';
|
|
30
31
|
|
|
@@ -38,7 +39,34 @@ const PLACEHOLDER_BASE_URL = DEFAULT_HOST;
|
|
|
38
39
|
|
|
39
40
|
let _pi: ExtensionAPI | null = null;
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Fetch the live catalog using credentials already in auth.json and register
|
|
44
|
+
* the resulting models. Called during the awaited factory load so devin
|
|
45
|
+
* models exist before pi flushes pending provider registrations and restores
|
|
46
|
+
* the session model.
|
|
47
|
+
*
|
|
48
|
+
* Reads auth.json directly via AuthStorage (the model registry is not bound
|
|
49
|
+
* yet during factory load). Silently no-ops when not signed in or the fetch
|
|
50
|
+
* fails — the session_start handler re-attempts once the registry is live.
|
|
51
|
+
*/
|
|
52
|
+
async function primeCatalogFromStoredAuth(): Promise<void> {
|
|
53
|
+
if (!_pi) return;
|
|
54
|
+
try {
|
|
55
|
+
const authStorage = AuthStorage.create();
|
|
56
|
+
const apiKey = await authStorage.getApiKey(PROVIDER_ID, { includeFallback: false });
|
|
57
|
+
if (!apiKey) return;
|
|
58
|
+
const catalog = await getCachedCatalog(apiKey, DEFAULT_HOST);
|
|
59
|
+
const liveModels = buildLiveModels(catalog);
|
|
60
|
+
if (liveModels.length > 0) {
|
|
61
|
+
registerDevinProvider(_pi, liveModels);
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
// Not signed in, network failure, or schema drift — keep the empty
|
|
65
|
+
// model list and let session_start retry once the registry is bound.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function registerDevinProvider(pi: ExtensionAPI, models: ProviderModelConfig[]): void {
|
|
42
70
|
pi.registerProvider(PROVIDER_ID, {
|
|
43
71
|
name: PROVIDER_NAME,
|
|
44
72
|
api: API_IDENTIFIER,
|
|
@@ -58,7 +86,7 @@ function registerDevinProvider(pi: ExtensionAPI, models: typeof FALLBACK_MODELS)
|
|
|
58
86
|
const liveModels = buildLiveModels(catalog);
|
|
59
87
|
registerDevinProvider(_pi, liveModels);
|
|
60
88
|
} catch {
|
|
61
|
-
// keep
|
|
89
|
+
// keep current models if catalog fetch fails
|
|
62
90
|
}
|
|
63
91
|
}
|
|
64
92
|
return credentials;
|
|
@@ -80,9 +108,19 @@ function registerDevinProvider(pi: ExtensionAPI, models: typeof FALLBACK_MODELS)
|
|
|
80
108
|
export default async function (pi: ExtensionAPI): Promise<void> {
|
|
81
109
|
_pi = pi;
|
|
82
110
|
|
|
83
|
-
|
|
111
|
+
// Register with an empty model list first so the provider (and OAuth
|
|
112
|
+
// login support) is known even before the catalog arrives. Then, if we
|
|
113
|
+
// already have credentials in auth.json, fetch the live catalog now —
|
|
114
|
+
// during the awaited factory load, before pi flushes pending provider
|
|
115
|
+
// registrations and restores the session model. This is what makes
|
|
116
|
+
// `devin/glm-5-2` resolvable on resume instead of falling back to the
|
|
117
|
+
// default provider with a "Could not restore model" warning.
|
|
118
|
+
registerDevinProvider(pi, []);
|
|
119
|
+
await primeCatalogFromStoredAuth();
|
|
84
120
|
|
|
85
121
|
pi.on('session_start', async (_event, ctx) => {
|
|
122
|
+
// Re-prime in case credentials were added via /login since load, or
|
|
123
|
+
// the catalog TTL expired during a long-lived session.
|
|
86
124
|
try {
|
|
87
125
|
const apiKey = await ctx.modelRegistry.getApiKeyForProvider(PROVIDER_ID);
|
|
88
126
|
if (apiKey && _pi) {
|
|
@@ -91,7 +129,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
91
129
|
registerDevinProvider(_pi, liveModels);
|
|
92
130
|
}
|
|
93
131
|
} catch {
|
|
94
|
-
// keep
|
|
132
|
+
// keep current models
|
|
95
133
|
}
|
|
96
134
|
});
|
|
97
135
|
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Model list for the Devin (Cognition) provider.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* catalog to the 11 model families we care about, stamping each entry
|
|
9
|
-
* with pricing/metadata from {@link MODEL_OVERRIDES}.
|
|
4
|
+
* Fully live-catalog driven. {@link buildLiveModels} pulls every model from
|
|
5
|
+
* Cognition's `GetCascadeModelConfigs` catalog and surfaces it directly —
|
|
6
|
+
* no hardcoded family list, no hardcoded pricing/metadata. The catalog's
|
|
7
|
+
* `label` is used as the display name (with optional overrides below).
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* pricing. We supply all of those from {@link MODEL_OVERRIDES}, keyed by
|
|
14
|
-
* prefix so variants (e.g. `claude-opus-4-8-medium`, `gpt-5-6-sol-low`)
|
|
15
|
-
* inherit the correct metadata from their parent family.
|
|
9
|
+
* Before login (or if the catalog fetch fails) we return an empty list so
|
|
10
|
+
* pi shows no models until the live catalog arrives.
|
|
16
11
|
*/
|
|
17
12
|
|
|
18
13
|
import type { ProviderModelConfig } from '@earendil-works/pi-coding-agent';
|
|
@@ -22,212 +17,63 @@ import type { CacheEntry } from './cloud-direct/index.js';
|
|
|
22
17
|
const DEFAULT_HOST = 'https://server.codeium.com';
|
|
23
18
|
|
|
24
19
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*/
|
|
29
|
-
export const WANTED_PREFIXES: readonly string[] = [
|
|
30
|
-
'swe-1-7',
|
|
31
|
-
'gpt-5-6-sol',
|
|
32
|
-
'gpt-5-6-luna',
|
|
33
|
-
'gpt-5-6-terra',
|
|
34
|
-
'claude-opus-4-8',
|
|
35
|
-
'claude-fable-5',
|
|
36
|
-
'claude-sonnet-5',
|
|
37
|
-
'glm-5-2',
|
|
38
|
-
'kimi-k2-7',
|
|
39
|
-
'grok-4-5',
|
|
40
|
-
];
|
|
41
|
-
|
|
42
|
-
function matchesWantedPrefix(uid: string): boolean {
|
|
43
|
-
for (const prefix of WANTED_PREFIXES) {
|
|
44
|
-
if (uid === prefix || uid.startsWith(prefix + '-') || uid.startsWith(prefix + '_')) {
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Per-model metadata + pricing, keyed by prefix.
|
|
20
|
+
* Per-family variant allow-list. If a family prefix is present, only UIDs
|
|
21
|
+
* whose suffix (the part after `prefix-`) is in the set are kept. A UID that
|
|
22
|
+
* exactly equals the prefix is always allowed (suffix = empty string).
|
|
53
23
|
*
|
|
54
|
-
*
|
|
55
|
-
* {@link FALLBACK_MODELS} and {@link buildLiveModels} so there is a single
|
|
56
|
-
* source of truth for pricing.
|
|
24
|
+
* Families not listed here keep all variants.
|
|
57
25
|
*/
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
maxTokens: number;
|
|
61
|
-
reasoning: boolean;
|
|
62
|
-
input: ('text' | 'image')[];
|
|
63
|
-
cost: {
|
|
64
|
-
input: number;
|
|
65
|
-
output: number;
|
|
66
|
-
cacheRead: number;
|
|
67
|
-
cacheWrite: number;
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const MODEL_META: Map<string, ModelMeta> = new Map([
|
|
72
|
-
['swe-1-7', {
|
|
73
|
-
contextWindow: 256_000,
|
|
74
|
-
maxTokens: 128_000,
|
|
75
|
-
reasoning: true,
|
|
76
|
-
input: ['text', 'image'],
|
|
77
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
78
|
-
}],
|
|
79
|
-
['swe-1-7-lightning', {
|
|
80
|
-
contextWindow: 256_000,
|
|
81
|
-
maxTokens: 128_000,
|
|
82
|
-
reasoning: true,
|
|
83
|
-
input: ['text', 'image'],
|
|
84
|
-
cost: { input: 2.50, output: 12.50, cacheRead: 0.25, cacheWrite: 3.13 },
|
|
85
|
-
}],
|
|
86
|
-
['gpt-5-6-sol', {
|
|
87
|
-
contextWindow: 1_050_000,
|
|
88
|
-
maxTokens: 128_000,
|
|
89
|
-
reasoning: true,
|
|
90
|
-
input: ['text', 'image'],
|
|
91
|
-
cost: { input: 5.00, output: 30.00, cacheRead: 0.50, cacheWrite: 6.25 },
|
|
92
|
-
}],
|
|
93
|
-
['gpt-5-6-luna', {
|
|
94
|
-
contextWindow: 1_050_000,
|
|
95
|
-
maxTokens: 128_000,
|
|
96
|
-
reasoning: true,
|
|
97
|
-
input: ['text', 'image'],
|
|
98
|
-
cost: { input: 1.00, output: 6.00, cacheRead: 0.10, cacheWrite: 1.25 },
|
|
99
|
-
}],
|
|
100
|
-
['gpt-5-6-terra', {
|
|
101
|
-
contextWindow: 1_050_000,
|
|
102
|
-
maxTokens: 128_000,
|
|
103
|
-
reasoning: true,
|
|
104
|
-
input: ['text', 'image'],
|
|
105
|
-
cost: { input: 2.50, output: 15.00, cacheRead: 0.25, cacheWrite: 3.13 },
|
|
106
|
-
}],
|
|
107
|
-
['claude-opus-4-8', {
|
|
108
|
-
contextWindow: 200_000,
|
|
109
|
-
maxTokens: 128_000,
|
|
110
|
-
reasoning: true,
|
|
111
|
-
input: ['text', 'image'],
|
|
112
|
-
cost: { input: 5.00, output: 25.00, cacheRead: 0.50, cacheWrite: 6.25 },
|
|
113
|
-
}],
|
|
114
|
-
['claude-fable-5', {
|
|
115
|
-
contextWindow: 1_000_000,
|
|
116
|
-
maxTokens: 128_000,
|
|
117
|
-
reasoning: true,
|
|
118
|
-
input: ['text', 'image'],
|
|
119
|
-
cost: { input: 10.00, output: 50.00, cacheRead: 1.00, cacheWrite: 12.50 },
|
|
120
|
-
}],
|
|
121
|
-
['claude-sonnet-5', {
|
|
122
|
-
contextWindow: 200_000,
|
|
123
|
-
maxTokens: 64_000,
|
|
124
|
-
reasoning: true,
|
|
125
|
-
input: ['text', 'image'],
|
|
126
|
-
cost: { input: 3.00, output: 15.00, cacheRead: 0.30, cacheWrite: 3.75 },
|
|
127
|
-
}],
|
|
128
|
-
['glm-5-2', {
|
|
129
|
-
contextWindow: 1_000_000,
|
|
130
|
-
maxTokens: 131_000,
|
|
131
|
-
reasoning: true,
|
|
132
|
-
input: ['text', 'image'],
|
|
133
|
-
cost: { input: 0.70, output: 2.20, cacheRead: 0.26, cacheWrite: 0.88 },
|
|
134
|
-
}],
|
|
135
|
-
['kimi-k2-7', {
|
|
136
|
-
contextWindow: 256_000,
|
|
137
|
-
maxTokens: 256_000,
|
|
138
|
-
reasoning: true,
|
|
139
|
-
input: ['text', 'image'],
|
|
140
|
-
cost: { input: 0.95, output: 4.00, cacheRead: 0.19, cacheWrite: 1.19 },
|
|
141
|
-
}],
|
|
142
|
-
['grok-4-5', {
|
|
143
|
-
contextWindow: 500_000,
|
|
144
|
-
maxTokens: 128_000,
|
|
145
|
-
reasoning: true,
|
|
146
|
-
input: ['text', 'image'],
|
|
147
|
-
cost: { input: 2.00, output: 6.00, cacheRead: 0.50, cacheWrite: 2.50 },
|
|
148
|
-
}],
|
|
26
|
+
const VARIANT_ALLOW: Map<string, Set<string>> = new Map([
|
|
27
|
+
['glm-5-2', new Set(['high', 'max'])],
|
|
149
28
|
]);
|
|
150
29
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
function findMeta(uid: string): ModelMeta | undefined {
|
|
158
|
-
let best: { key: string; meta: ModelMeta } | null = null;
|
|
159
|
-
for (const [key, meta] of MODEL_META) {
|
|
160
|
-
if (uid === key || uid.startsWith(key + '-') || uid.startsWith(key + '_')) {
|
|
161
|
-
if (!best || key.length > best.key.length) {
|
|
162
|
-
best = { key, meta };
|
|
30
|
+
function matchesVariantFilter(uid: string): boolean {
|
|
31
|
+
let bestPrefix: string | null = null;
|
|
32
|
+
for (const prefix of VARIANT_ALLOW.keys()) {
|
|
33
|
+
if (uid === prefix || uid.startsWith(prefix + '-') || uid.startsWith(prefix + '_')) {
|
|
34
|
+
if (bestPrefix === null || prefix.length > bestPrefix.length) {
|
|
35
|
+
bestPrefix = prefix;
|
|
163
36
|
}
|
|
164
37
|
}
|
|
165
38
|
}
|
|
166
|
-
return
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const DEFAULT_META: ModelMeta = {
|
|
171
|
-
contextWindow: 256_000,
|
|
172
|
-
maxTokens: 128_000,
|
|
173
|
-
reasoning: true,
|
|
174
|
-
input: ['text', 'image'],
|
|
175
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
function makeModel(id: string, name: string, meta: ModelMeta): ProviderModelConfig {
|
|
179
|
-
return {
|
|
180
|
-
id,
|
|
181
|
-
name,
|
|
182
|
-
reasoning: meta.reasoning,
|
|
183
|
-
input: meta.input,
|
|
184
|
-
cost: meta.cost,
|
|
185
|
-
contextWindow: meta.contextWindow,
|
|
186
|
-
maxTokens: meta.maxTokens,
|
|
187
|
-
};
|
|
39
|
+
if (bestPrefix === null) return true;
|
|
40
|
+
if (uid === bestPrefix) return true;
|
|
41
|
+
const suffix = uid.slice(bestPrefix.length + 1);
|
|
42
|
+
return VARIANT_ALLOW.get(bestPrefix)!.has(suffix);
|
|
188
43
|
}
|
|
189
44
|
|
|
190
|
-
/**
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
export const FALLBACK_MODELS: ProviderModelConfig[] = [
|
|
195
|
-
makeModel('swe-1-7', 'SWE-1.7', MODEL_META.get('swe-1-7')!),
|
|
196
|
-
makeModel('swe-1-7-lightning', 'SWE-1.7 Lightning', MODEL_META.get('swe-1-7-lightning')!),
|
|
197
|
-
makeModel('gpt-5-6-sol', 'GPT-5.6 Sol', MODEL_META.get('gpt-5-6-sol')!),
|
|
198
|
-
makeModel('gpt-5-6-luna', 'GPT-5.6 Luna', MODEL_META.get('gpt-5-6-luna')!),
|
|
199
|
-
makeModel('gpt-5-6-terra', 'GPT-5.6 Terra', MODEL_META.get('gpt-5-6-terra')!),
|
|
200
|
-
makeModel('claude-opus-4-8', 'Claude Opus 4.8', MODEL_META.get('claude-opus-4-8')!),
|
|
201
|
-
makeModel('claude-fable-5', 'Claude Fable 5', MODEL_META.get('claude-fable-5')!),
|
|
202
|
-
makeModel('claude-sonnet-5', 'Claude Sonnet 5', MODEL_META.get('claude-sonnet-5')!),
|
|
203
|
-
makeModel('glm-5-2', 'GLM-5.2', MODEL_META.get('glm-5-2')!),
|
|
204
|
-
makeModel('kimi-k2-7', 'Kimi K2.7', MODEL_META.get('kimi-k2-7')!),
|
|
205
|
-
makeModel('grok-4-5', 'Grok 4.5', MODEL_META.get('grok-4-5')!),
|
|
206
|
-
];
|
|
45
|
+
/** Display-name overrides for catalog UIDs whose live label is undesired. */
|
|
46
|
+
const NAME_OVERRIDES = new Map<string, string>([
|
|
47
|
+
['glm-5-2', 'GLM-5.2'],
|
|
48
|
+
]);
|
|
207
49
|
|
|
208
50
|
/**
|
|
209
51
|
* Build the model list from a live catalog response.
|
|
210
52
|
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
53
|
+
* Surfaces every non-disabled model the catalog returns, applying the variant
|
|
54
|
+
* filter and name overrides above. Returns an empty array before login or
|
|
55
|
+
* when the catalog fetch fails — pi will show no models until the live list
|
|
56
|
+
* arrives.
|
|
215
57
|
*/
|
|
216
58
|
export function buildLiveModels(catalog: CacheEntry | null): ProviderModelConfig[] {
|
|
217
59
|
if (!catalog || catalog.byUid.size === 0) {
|
|
218
|
-
return
|
|
60
|
+
return [];
|
|
219
61
|
}
|
|
220
62
|
|
|
221
63
|
const models: ProviderModelConfig[] = [];
|
|
222
64
|
for (const entry of catalog.byUid.values()) {
|
|
223
65
|
if (entry.disabled) continue;
|
|
224
|
-
if (!
|
|
225
|
-
const
|
|
226
|
-
models.push(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
66
|
+
if (!matchesVariantFilter(entry.modelUid)) continue;
|
|
67
|
+
const name = NAME_OVERRIDES.get(entry.modelUid) ?? entry.label ?? entry.modelUid;
|
|
68
|
+
models.push({
|
|
69
|
+
id: entry.modelUid,
|
|
70
|
+
name,
|
|
71
|
+
reasoning: true,
|
|
72
|
+
input: ['text', 'image'],
|
|
73
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
74
|
+
contextWindow: 256_000,
|
|
75
|
+
maxTokens: 128_000,
|
|
76
|
+
});
|
|
231
77
|
}
|
|
232
78
|
|
|
233
79
|
return models;
|
package/plugins/index.ts
CHANGED
|
@@ -3,10 +3,15 @@ import * as path from "node:path";
|
|
|
3
3
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
|
|
5
5
|
import devinAuthPlugin from "./devin-auth/extensions/index.ts";
|
|
6
|
-
import piCompactToolsPlugin from "./pi-compact-tools/index.ts";
|
|
6
|
+
import piCompactToolsPlugin, { getSharedRenderer } from "./pi-compact-tools/index.ts";
|
|
7
7
|
import piCustomAgentsPlugin from "./pi-custom-agents/index.ts";
|
|
8
|
+
import piEmberFffPlugin from "./pi-ember-fff/index.ts";
|
|
9
|
+
import piEmberTpsPlugin from "./pi-ember-tps/index.ts";
|
|
10
|
+
import piEmberUiPlugin from "./pi-ember-ui/index.ts";
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
export { getSharedRenderer };
|
|
13
|
+
|
|
14
|
+
type PluginId = "pi-compact-tools" | "pi-custom-agents" | "devin-auth" | "pi-ember-fff" | "pi-ember-ui" | "pi-ember-tps";
|
|
10
15
|
type StackPlugin = {
|
|
11
16
|
id: PluginId;
|
|
12
17
|
description: string;
|
|
@@ -20,8 +25,11 @@ type StackPluginConfig = {
|
|
|
20
25
|
const CONFIG_RELATIVE_PATH = path.join(".pi", "ember-stack.json");
|
|
21
26
|
const DEFAULT_PLUGIN_IDS: readonly PluginId[] = [
|
|
22
27
|
"pi-compact-tools",
|
|
23
|
-
"pi-custom-agents",
|
|
24
28
|
"devin-auth",
|
|
29
|
+
"pi-custom-agents",
|
|
30
|
+
"pi-ember-fff",
|
|
31
|
+
"pi-ember-ui",
|
|
32
|
+
"pi-ember-tps",
|
|
25
33
|
];
|
|
26
34
|
|
|
27
35
|
const PLUGINS: readonly StackPlugin[] = [
|
|
@@ -30,15 +38,30 @@ const PLUGINS: readonly StackPlugin[] = [
|
|
|
30
38
|
description: "Collapsed native edit rendering",
|
|
31
39
|
extension: piCompactToolsPlugin,
|
|
32
40
|
},
|
|
41
|
+
{
|
|
42
|
+
id: "devin-auth",
|
|
43
|
+
description: "Devin OAuth provider, model catalog, and streaming transport",
|
|
44
|
+
extension: devinAuthPlugin,
|
|
45
|
+
},
|
|
33
46
|
{
|
|
34
47
|
id: "pi-custom-agents",
|
|
35
|
-
description: "
|
|
48
|
+
description: "Questionnaire, primary modes, plans, subagents, and bundled agent definitions",
|
|
36
49
|
extension: piCustomAgentsPlugin,
|
|
37
50
|
},
|
|
38
51
|
{
|
|
39
|
-
id: "
|
|
40
|
-
description: "
|
|
41
|
-
extension:
|
|
52
|
+
id: "pi-ember-fff",
|
|
53
|
+
description: "FFF-powered grep and find with compact rendering",
|
|
54
|
+
extension: piEmberFffPlugin,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "pi-ember-ui",
|
|
58
|
+
description: "Ember accent theme — orange reasoning colors, accent borders",
|
|
59
|
+
extension: piEmberUiPlugin,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "pi-ember-tps",
|
|
63
|
+
description: "Tokens-per-second meter with sparkline trend and live gauge",
|
|
64
|
+
extension: piEmberTpsPlugin,
|
|
42
65
|
},
|
|
43
66
|
];
|
|
44
67
|
|
|
@@ -1,17 +1,49 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
createBashTool,
|
|
5
|
+
createEditTool,
|
|
6
|
+
createFindTool,
|
|
7
|
+
createGrepTool,
|
|
8
|
+
createLsTool,
|
|
9
|
+
createReadTool,
|
|
10
|
+
createWriteTool,
|
|
11
|
+
type ExtensionAPI,
|
|
12
|
+
} from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { type Component } from "@earendil-works/pi-tui";
|
|
14
|
+
import {
|
|
15
|
+
CompactRenderer,
|
|
16
|
+
GROUPABLE_TOOLS,
|
|
17
|
+
type ToolRenderContext,
|
|
18
|
+
type ToolRenderResultOptions,
|
|
19
|
+
} from "./renderer.ts";
|
|
5
20
|
|
|
6
21
|
const SOURCE_ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
7
22
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
23
|
+
type ToolFactory = (cwd: string) => any;
|
|
24
|
+
|
|
25
|
+
const TOOL_FACTORIES: Record<string, ToolFactory> = {
|
|
26
|
+
bash: createBashTool,
|
|
27
|
+
edit: createEditTool,
|
|
28
|
+
find: createFindTool,
|
|
29
|
+
grep: createGrepTool,
|
|
30
|
+
ls: createLsTool,
|
|
31
|
+
read: createReadTool,
|
|
32
|
+
write: createWriteTool,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function registerCompactTool(
|
|
36
|
+
pi: ExtensionAPI,
|
|
37
|
+
name: string,
|
|
38
|
+
factory: ToolFactory,
|
|
39
|
+
renderer: CompactRenderer,
|
|
40
|
+
): void {
|
|
41
|
+
const definition = factory(SOURCE_ROOT);
|
|
42
|
+
pi.registerTool({
|
|
43
|
+
name,
|
|
44
|
+
label: name,
|
|
45
|
+
description: definition.description,
|
|
46
|
+
parameters: definition.parameters,
|
|
15
47
|
renderShell: "self",
|
|
16
48
|
|
|
17
49
|
async execute(
|
|
@@ -21,51 +53,40 @@ function registerCollapsedEditTool(extensionApi: any): void {
|
|
|
21
53
|
onUpdate: any,
|
|
22
54
|
ctx: any,
|
|
23
55
|
) {
|
|
24
|
-
return
|
|
25
|
-
toolCallId,
|
|
26
|
-
params,
|
|
27
|
-
signal,
|
|
28
|
-
onUpdate,
|
|
29
|
-
);
|
|
56
|
+
return factory(ctx.cwd).execute(toolCallId, params, signal, onUpdate);
|
|
30
57
|
},
|
|
31
58
|
|
|
32
|
-
renderCall(args: any, theme: any):
|
|
33
|
-
|
|
34
|
-
return new Text(
|
|
35
|
-
theme.fg("toolTitle", theme.bold("edit ")) +
|
|
36
|
-
theme.fg("accent", filePath),
|
|
37
|
-
0,
|
|
38
|
-
0,
|
|
39
|
-
);
|
|
59
|
+
renderCall(args: any, theme: any, context: ToolRenderContext): Component {
|
|
60
|
+
return renderer.renderCall(name, args, theme, context);
|
|
40
61
|
},
|
|
41
62
|
|
|
42
|
-
renderResult(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const diff = typeof result.details?.diff === "string" ? result.details.diff : "";
|
|
51
|
-
let additions = 0;
|
|
52
|
-
let removals = 0;
|
|
53
|
-
for (const line of diff.split("\n")) {
|
|
54
|
-
if (line.startsWith("+") && !line.startsWith("+++")) additions++;
|
|
55
|
-
if (line.startsWith("-") && !line.startsWith("---")) removals++;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return new Text(
|
|
59
|
-
theme.fg("success", `+${additions}`) +
|
|
60
|
-
theme.fg("dim", " / ") +
|
|
61
|
-
theme.fg("error", `-${removals}`),
|
|
62
|
-
0,
|
|
63
|
-
0,
|
|
64
|
-
);
|
|
63
|
+
renderResult(
|
|
64
|
+
result: any,
|
|
65
|
+
options: ToolRenderResultOptions,
|
|
66
|
+
theme: any,
|
|
67
|
+
context: ToolRenderContext & { isError: boolean },
|
|
68
|
+
): Component {
|
|
69
|
+
return renderer.renderResult(name, context.args, result, options, theme, context);
|
|
65
70
|
},
|
|
66
71
|
});
|
|
67
72
|
}
|
|
68
73
|
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
let sharedRenderer: CompactRenderer | null = null;
|
|
75
|
+
|
|
76
|
+
export function getSharedRenderer(): CompactRenderer {
|
|
77
|
+
if (!sharedRenderer) sharedRenderer = new CompactRenderer();
|
|
78
|
+
return sharedRenderer;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default function piCompactToolsPlugin(pi: ExtensionAPI): void {
|
|
82
|
+
const renderer = getSharedRenderer();
|
|
83
|
+
pi.on("turn_start", () => renderer.beginTurn());
|
|
84
|
+
pi.on("tool_call", (event: any) => {
|
|
85
|
+
if (GROUPABLE_TOOLS.has(event.toolName) || TOOL_FACTORIES[event.toolName]) {
|
|
86
|
+
renderer.observeCall(event.toolName, event.toolCallId, event.input);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
for (const [name, factory] of Object.entries(TOOL_FACTORIES)) {
|
|
90
|
+
registerCompactTool(pi, name, factory, renderer);
|
|
91
|
+
}
|
|
71
92
|
}
|