pi-ui-extend 0.1.62 → 0.1.64
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/dist/app/commands/command-model-actions.js +8 -8
- package/dist/app/commands/command-session-actions.js +2 -2
- package/dist/app/constants.d.ts +1 -1
- package/dist/app/constants.js +1 -1
- package/dist/app/input/autocomplete-controller.js +5 -11
- package/dist/app/input/prompt-enhancer-controller.js +4 -5
- package/dist/app/model/model-usage-status.js +16 -6
- package/dist/app/popup/menu-items-controller.js +5 -7
- package/dist/app/runtime.js +3 -3
- package/dist/bundled-extensions/session-title/index.d.ts +2 -1
- package/dist/bundled-extensions/session-title/index.js +4 -2
- package/dist/bundled-extensions/session-title/title-generation-compat.d.ts +17 -0
- package/dist/bundled-extensions/session-title/title-generation-compat.js +40 -0
- package/dist/bundled-extensions/session-title/title-generation.d.ts +16 -13
- package/dist/bundled-extensions/session-title/title-generation.js +10 -17
- package/external/pi-tools-suite/package.json +5 -4
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +10 -6
- package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +9 -5
- package/external/pi-tools-suite/src/coding-discipline/index.ts +19 -4
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +14 -5
- package/external/pi-tools-suite/src/dcp/commands.ts +4 -3
- package/external/pi-tools-suite/src/model-completion.ts +37 -0
- package/package.json +4 -4
|
@@ -63,8 +63,8 @@ export class ModelCommandActions {
|
|
|
63
63
|
const parsed = parseScopedModelRef(modelRef);
|
|
64
64
|
if (!parsed)
|
|
65
65
|
throw new Error("Model must use provider/model[:thinking] format");
|
|
66
|
-
runtime.services.
|
|
67
|
-
const model = runtime.services.
|
|
66
|
+
await runtime.services.modelRuntime.reloadConfig();
|
|
67
|
+
const model = runtime.services.modelRuntime.getModel(parsed.provider, parsed.modelId);
|
|
68
68
|
if (!model)
|
|
69
69
|
throw new Error(`Model not found: ${parsed.provider}/${parsed.modelId}`);
|
|
70
70
|
await this.runModelCommand(model);
|
|
@@ -97,8 +97,8 @@ export class ModelCommandActions {
|
|
|
97
97
|
const parsed = parseScopedModelRef(modelRef);
|
|
98
98
|
if (!parsed)
|
|
99
99
|
throw new Error("Model must use provider/model[:thinking] format");
|
|
100
|
-
runtime.services.
|
|
101
|
-
const model = runtime.services.
|
|
100
|
+
await runtime.services.modelRuntime.reloadConfig();
|
|
101
|
+
const model = runtime.services.modelRuntime.getModel(parsed.provider, parsed.modelId);
|
|
102
102
|
if (!model)
|
|
103
103
|
throw new Error(`Model not found: ${parsed.provider}/${parsed.modelId}`);
|
|
104
104
|
this.saveDefaultModel(modelRef);
|
|
@@ -118,8 +118,8 @@ export class ModelCommandActions {
|
|
|
118
118
|
const parsed = parseScopedModelRef(modelRef);
|
|
119
119
|
if (!parsed)
|
|
120
120
|
throw new Error("Model must use provider/model[:thinking] format, or run /autocomplete with no arguments to disable");
|
|
121
|
-
runtime.services.
|
|
122
|
-
const model = runtime.services.
|
|
121
|
+
await runtime.services.modelRuntime.reloadConfig();
|
|
122
|
+
const model = runtime.services.modelRuntime.getModel(parsed.provider, parsed.modelId);
|
|
123
123
|
if (!model)
|
|
124
124
|
throw new Error(`Model not found: ${parsed.provider}/${parsed.modelId}`);
|
|
125
125
|
const saved = savePixAutocompleteModel(modelRef);
|
|
@@ -199,10 +199,10 @@ export class ModelCommandActions {
|
|
|
199
199
|
const refs = value.split(/[,\s]+/).map((ref) => ref.trim()).filter(Boolean);
|
|
200
200
|
const scopedModels = [];
|
|
201
201
|
const invalidRefs = [];
|
|
202
|
-
runtime.services.
|
|
202
|
+
await runtime.services.modelRuntime.reloadConfig();
|
|
203
203
|
for (const ref of refs) {
|
|
204
204
|
const parsed = parseScopedModelRef(ref);
|
|
205
|
-
const model = parsed ? runtime.services.
|
|
205
|
+
const model = parsed ? runtime.services.modelRuntime.getModel(parsed.provider, parsed.modelId) : undefined;
|
|
206
206
|
if (!parsed || !model) {
|
|
207
207
|
invalidRefs.push(ref);
|
|
208
208
|
continue;
|
|
@@ -12,7 +12,7 @@ import { checkPixUpdate, formatPixUpdateCheck, parsePixUpdateArgs, pixUpdateUsag
|
|
|
12
12
|
import { createStartupInfoMessage } from "../cli/startup-info.js";
|
|
13
13
|
import { getCompleteSessionStats } from "../session/session-stats.js";
|
|
14
14
|
import { loadSessionTitleConfig } from "../../bundled-extensions/session-title/config.js";
|
|
15
|
-
import { fallbackSessionTitleFromInput, firstUserMessageText,
|
|
15
|
+
import { fallbackSessionTitleFromInput, firstUserMessageText, generateSessionTitleWithRuntime, sessionTitleModelRefs, } from "../../bundled-extensions/session-title/title-generation.js";
|
|
16
16
|
export class SessionCommandActions {
|
|
17
17
|
host;
|
|
18
18
|
constructor(host) {
|
|
@@ -116,7 +116,7 @@ export class SessionCommandActions {
|
|
|
116
116
|
if (config.enabled) {
|
|
117
117
|
for (const modelRef of modelRefs) {
|
|
118
118
|
for (let attempt = 0; attempt < config.generationAttempts; attempt++) {
|
|
119
|
-
generatedName = await
|
|
119
|
+
generatedName = await generateSessionTitleWithRuntime(firstPrompt.slice(0, config.maxInputChars).trim(), runtime.services.modelRuntime, config, modelRef, AbortSignal.timeout(config.timeoutMs));
|
|
120
120
|
if (generatedName)
|
|
121
121
|
break;
|
|
122
122
|
}
|
package/dist/app/constants.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ResolvedToolRule } from "../config.js";
|
|
2
2
|
export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
3
3
|
export declare const THINKING_MENU_MAX_ROWS: number;
|
|
4
|
-
export declare const PI_FAVORITE_MODEL_REFS: readonly ["amazon-bedrock/us.anthropic.claude-opus-4-6-v1", "anthropic/claude-opus-4-8", "openai/gpt-5.4", "azure-openai-responses/gpt-5.4", "openai-codex/gpt-5.6-sol", "deepseek/deepseek-v4-pro", "google/gemini-3.1-pro-preview", "google-vertex/gemini-3.1-pro-preview", "github-copilot/gpt-5.4", "openrouter/moonshotai/kimi-k2.6", "vercel-ai-gateway/zai/glm-5.2", "xai/grok-4.
|
|
4
|
+
export declare const PI_FAVORITE_MODEL_REFS: readonly ["amazon-bedrock/us.anthropic.claude-opus-4-6-v1", "anthropic/claude-opus-4-8", "openai/gpt-5.4", "azure-openai-responses/gpt-5.4", "openai-codex/gpt-5.6-sol", "deepseek/deepseek-v4-pro", "google/gemini-3.1-pro-preview", "google-vertex/gemini-3.1-pro-preview", "github-copilot/gpt-5.4", "openrouter/moonshotai/kimi-k2.6", "vercel-ai-gateway/zai/glm-5.2", "xai/grok-4.5", "groq/openai/gpt-oss-120b", "cerebras/zai-glm-4.7", "zai/glm-5.2", "mistral/devstral-medium-latest", "minimax/MiniMax-M2.7", "minimax-cn/MiniMax-M2.7", "moonshotai/kimi-k2.6", "moonshotai-cn/kimi-k2.6", "huggingface/moonshotai/Kimi-K2.6", "fireworks/accounts/fireworks/models/kimi-k2p6", "together/moonshotai/Kimi-K2.6", "opencode/kimi-k2.6", "opencode-go/kimi-k2.6", "kimi-coding/kimi-for-coding", "cloudflare-workers-ai/@cf/moonshotai/kimi-k2.6", "cloudflare-ai-gateway/workers-ai/@cf/moonshotai/kimi-k2.6", "xiaomi/mimo-v2.5-pro", "xiaomi-token-plan-cn/mimo-v2.5-pro", "xiaomi-token-plan-ams/mimo-v2.5-pro", "xiaomi-token-plan-sgp/mimo-v2.5-pro"];
|
|
5
5
|
export declare const SLASH_COMMAND_MENU_MAX_ROWS = 6;
|
|
6
6
|
export declare const RESUME_MENU_MAX_ROWS = 20;
|
|
7
7
|
export declare const RESUME_MENU_INITIAL_SESSION_ROWS = 30;
|
package/dist/app/constants.js
CHANGED
|
@@ -13,7 +13,7 @@ export const PI_FAVORITE_MODEL_REFS = [
|
|
|
13
13
|
"github-copilot/gpt-5.4",
|
|
14
14
|
"openrouter/moonshotai/kimi-k2.6",
|
|
15
15
|
"vercel-ai-gateway/zai/glm-5.2",
|
|
16
|
-
"xai/grok-4.
|
|
16
|
+
"xai/grok-4.5",
|
|
17
17
|
"groq/openai/gpt-oss-120b",
|
|
18
18
|
"cerebras/zai-glm-4.7",
|
|
19
19
|
"zai/glm-5.2",
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { streamSimple } from "@earendil-works/pi-ai/compat";
|
|
2
1
|
import { isRecord } from "../guards.js";
|
|
3
2
|
import { parseModelRef } from "../model/model-ref.js";
|
|
4
3
|
const AUTOCOMPLETE_DEBOUNCE_MS = 350;
|
|
@@ -140,17 +139,14 @@ export class AppAutocompleteController {
|
|
|
140
139
|
}
|
|
141
140
|
export async function completeInputWithPi(runtime, draft, config, signal) {
|
|
142
141
|
const parsedModel = parseModelRef(config.modelRef);
|
|
143
|
-
const
|
|
144
|
-
let model =
|
|
142
|
+
const modelRuntime = runtime.services.modelRuntime;
|
|
143
|
+
let model = modelRuntime.getModel(parsedModel.provider, parsedModel.modelId);
|
|
145
144
|
if (!model) {
|
|
146
|
-
|
|
147
|
-
model =
|
|
145
|
+
await modelRuntime.reloadConfig();
|
|
146
|
+
model = modelRuntime.getModel(parsedModel.provider, parsedModel.modelId);
|
|
148
147
|
}
|
|
149
148
|
if (!model)
|
|
150
149
|
throw new Error(`Model not found: ${parsedModel.provider}/${parsedModel.modelId}`);
|
|
151
|
-
const auth = await registry.getApiKeyAndHeaders(model);
|
|
152
|
-
if (!auth.ok)
|
|
153
|
-
throw new Error(auth.error);
|
|
154
150
|
const timeoutMs = numberInRange(config.timeoutMs, AUTOCOMPLETE_TIMEOUT_MS, 250, 10_000);
|
|
155
151
|
const maxTokens = numberInRange(config.maxTokens, AUTOCOMPLETE_MAX_TOKENS, 8, 256);
|
|
156
152
|
const maxPromptTokens = numberInRange(config.maxPromptTokens, AUTOCOMPLETE_MAX_PROMPT_TOKENS, 256, 16_000);
|
|
@@ -165,12 +161,10 @@ export async function completeInputWithPi(runtime, draft, config, signal) {
|
|
|
165
161
|
let output = "";
|
|
166
162
|
let streamError;
|
|
167
163
|
try {
|
|
168
|
-
const stream = streamSimple(requestModel, {
|
|
164
|
+
const stream = modelRuntime.streamSimple(requestModel, {
|
|
169
165
|
systemPrompt: AUTOCOMPLETE_SYSTEM_PROMPT,
|
|
170
166
|
messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
|
|
171
167
|
}, {
|
|
172
|
-
...(auth.apiKey === undefined ? {} : { apiKey: auth.apiKey }),
|
|
173
|
-
...(auth.headers === undefined ? {} : { headers: auth.headers }),
|
|
174
168
|
cacheRetention: "none",
|
|
175
169
|
maxRetryDelayMs: 0,
|
|
176
170
|
maxRetries: 0,
|
|
@@ -127,9 +127,8 @@ async function enhancePromptWithPi(runtime, draft, config) {
|
|
|
127
127
|
const services = await promptEnhancerPiDeps.createAgentSessionServices({
|
|
128
128
|
cwd: runtime.cwd,
|
|
129
129
|
agentDir: runtime.services.agentDir,
|
|
130
|
-
authStorage: runtime.services.authStorage,
|
|
131
130
|
settingsManager: runtime.services.settingsManager,
|
|
132
|
-
|
|
131
|
+
modelRuntime: runtime.services.modelRuntime,
|
|
133
132
|
resourceLoaderOptions: {
|
|
134
133
|
noExtensions: true,
|
|
135
134
|
noSkills: true,
|
|
@@ -139,10 +138,10 @@ async function enhancePromptWithPi(runtime, draft, config) {
|
|
|
139
138
|
systemPrompt: PROMPT_ENHANCER_SYSTEM_PROMPT,
|
|
140
139
|
},
|
|
141
140
|
});
|
|
142
|
-
services.
|
|
143
|
-
const model = services.
|
|
141
|
+
await services.modelRuntime.reloadConfig();
|
|
142
|
+
const model = services.modelRuntime.getModel(parsedModel.provider, parsedModel.modelId);
|
|
144
143
|
if (!model) {
|
|
145
|
-
throw new Error(modelNotFoundMessage(parsedModel.provider, parsedModel.modelId, services.
|
|
144
|
+
throw new Error(modelNotFoundMessage(parsedModel.provider, parsedModel.modelId, services.modelRuntime.getModels()));
|
|
146
145
|
}
|
|
147
146
|
const { session } = await promptEnhancerPiDeps.createAgentSessionFromServices({
|
|
148
147
|
services,
|
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { ModelRuntime, readStoredCredential } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { formatCompactProgressBar } from "../../context-progress-bar.js";
|
|
7
7
|
import { APP_ICONS } from "../icons.js";
|
|
8
8
|
const OPENAI_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
@@ -222,17 +222,27 @@ async function queryOpenAIAccountUsage(now) {
|
|
|
222
222
|
async function refreshOpenAICodexAuth() {
|
|
223
223
|
// Delegate to pi core so refresh-token rotation is persisted under the same
|
|
224
224
|
// cross-process auth.json lock used by model requests.
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
const modelRuntime = await ModelRuntime.create({
|
|
226
|
+
authPath: getPiAuthPath(),
|
|
227
|
+
allowModelNetwork: false,
|
|
228
|
+
});
|
|
229
|
+
let resolvedAuth;
|
|
230
|
+
try {
|
|
231
|
+
resolvedAuth = await modelRuntime.getAuth("openai-codex");
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
throw new Error("OpenAI Codex OAuth token refresh failed", { cause: error });
|
|
235
|
+
}
|
|
236
|
+
const credential = readStoredCredential("openai-codex", getPiAuthPath());
|
|
237
|
+
const access = resolvedAuth?.auth.apiKey ?? credential?.access;
|
|
228
238
|
if (!access || credential?.type !== "oauth" || !credential.access || isExpired(credential)) {
|
|
229
239
|
throw new Error("OpenAI Codex OAuth token refresh failed");
|
|
230
240
|
}
|
|
231
241
|
return {
|
|
232
242
|
type: "oauth",
|
|
233
243
|
access: credential.access,
|
|
234
|
-
refresh: credential.refresh,
|
|
235
|
-
expires: credential.expires,
|
|
244
|
+
...(credential.refresh === undefined ? {} : { refresh: credential.refresh }),
|
|
245
|
+
...(credential.expires === undefined ? {} : { expires: credential.expires }),
|
|
236
246
|
};
|
|
237
247
|
}
|
|
238
248
|
async function readOpenAIAuth() {
|
|
@@ -173,16 +173,15 @@ export class AppMenuItemsController {
|
|
|
173
173
|
return current?.provider === model.provider && current.id === model.id;
|
|
174
174
|
}
|
|
175
175
|
resolveScopedModelRefs(modelRefs) {
|
|
176
|
-
const
|
|
177
|
-
if (!
|
|
176
|
+
const modelRuntime = this.host.runtime()?.services.modelRuntime;
|
|
177
|
+
if (!modelRuntime)
|
|
178
178
|
return [];
|
|
179
|
-
registry.refresh();
|
|
180
179
|
const scopedModels = [];
|
|
181
180
|
for (const modelRef of modelRefs) {
|
|
182
181
|
const parsed = parseScopedModelRef(modelRef);
|
|
183
182
|
if (!parsed)
|
|
184
183
|
continue;
|
|
185
|
-
const model =
|
|
184
|
+
const model = modelRuntime.getModel(parsed.provider, parsed.modelId);
|
|
186
185
|
if (!model)
|
|
187
186
|
continue;
|
|
188
187
|
scopedModels.push({
|
|
@@ -197,10 +196,9 @@ export class AppMenuItemsController {
|
|
|
197
196
|
const scopedModels = session?.scopedModels.length ? session.scopedModels : this.getFavoriteScopedModels();
|
|
198
197
|
if (!scopedModels.length)
|
|
199
198
|
return [];
|
|
200
|
-
const
|
|
201
|
-
registry?.refresh();
|
|
199
|
+
const modelRuntime = this.host.runtime()?.services.modelRuntime;
|
|
202
200
|
return scopedModels.map((scoped) => {
|
|
203
|
-
const refreshed =
|
|
201
|
+
const refreshed = modelRuntime?.getModel(scoped.model.provider, scoped.model.id);
|
|
204
202
|
return (refreshed ?? scoped.model);
|
|
205
203
|
});
|
|
206
204
|
}
|
package/dist/app/runtime.js
CHANGED
|
@@ -239,8 +239,8 @@ export async function createPixRuntime(options, runtimeOptions = {}) {
|
|
|
239
239
|
config,
|
|
240
240
|
...(runtimeOptions.eventBus === undefined ? {} : { eventBus: runtimeOptions.eventBus }),
|
|
241
241
|
});
|
|
242
|
-
services.
|
|
243
|
-
const model = parsedModel ? services.
|
|
242
|
+
await services.modelRuntime.reloadConfig();
|
|
243
|
+
const model = parsedModel ? services.modelRuntime.getModel(parsedModel.provider, parsedModel.modelId) : undefined;
|
|
244
244
|
if (parsedModel && !model) {
|
|
245
245
|
throw new Error(`Model not found: ${parsedModel.provider}/${parsedModel.modelId}`);
|
|
246
246
|
}
|
|
@@ -250,7 +250,7 @@ export async function createPixRuntime(options, runtimeOptions = {}) {
|
|
|
250
250
|
const scoped = parseScopedModelRef(modelRef);
|
|
251
251
|
if (!scoped)
|
|
252
252
|
return [];
|
|
253
|
-
const scopedModel = services.
|
|
253
|
+
const scopedModel = services.modelRuntime.getModel(scoped.provider, scoped.modelId);
|
|
254
254
|
if (!scopedModel)
|
|
255
255
|
return [];
|
|
256
256
|
return [
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
export {
|
|
2
|
+
export { generateSessionTitle } from "./title-generation-compat.js";
|
|
3
|
+
export { fallbackSessionTitleFromInput, generateSessionTitleWithRuntime, sessionTitleModelRefs, sanitizeSessionTitle } from "./title-generation.js";
|
|
3
4
|
export declare function firstUserMessageText(ctx: ExtensionContext): string | undefined;
|
|
4
5
|
export declare function buildForkTitleInput(parentTitle: string | undefined, forkPrompt: string): string;
|
|
5
6
|
export default function sessionTitle(pi: ExtensionAPI): void;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { loadSessionTitleConfig } from "./config.js";
|
|
4
|
-
import { fallbackSessionTitleFromInput, firstUserMessageText as firstUserMessageTextFromEntries,
|
|
5
|
-
|
|
4
|
+
import { fallbackSessionTitleFromInput, firstUserMessageText as firstUserMessageTextFromEntries, sessionTitleModelRefs, } from "./title-generation.js";
|
|
5
|
+
import { generateSessionTitle } from "./title-generation-compat.js";
|
|
6
|
+
export { generateSessionTitle } from "./title-generation-compat.js";
|
|
7
|
+
export { fallbackSessionTitleFromInput, generateSessionTitleWithRuntime, sessionTitleModelRefs, sanitizeSessionTitle } from "./title-generation.js";
|
|
6
8
|
const DEFAULT_TERMINAL_TITLE = "pi";
|
|
7
9
|
function isStaleExtensionContextError(error) {
|
|
8
10
|
if (!(error instanceof Error))
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { SessionTitleConfig } from "./config.js";
|
|
3
|
+
type TitleModelRegistry = {
|
|
4
|
+
find(provider: string, modelId: string): Model<Api> | undefined;
|
|
5
|
+
getApiKeyAndHeaders(model: Model<Api>): Promise<{
|
|
6
|
+
ok: true;
|
|
7
|
+
apiKey?: string;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
env?: Record<string, string>;
|
|
10
|
+
} | {
|
|
11
|
+
ok: false;
|
|
12
|
+
error: string;
|
|
13
|
+
}>;
|
|
14
|
+
};
|
|
15
|
+
/** Extension-side title generation through Pi's public ModelRegistry facade. */
|
|
16
|
+
export declare function generateSessionTitle(input: string, modelRegistry: TitleModelRegistry, config: SessionTitleConfig, modelRef: string, signal: AbortSignal, onWarning?: (message: string) => void): Promise<string | undefined>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { complete } from "@earendil-works/pi-ai/compat";
|
|
2
|
+
import { buildTitlePrompt, parseTitleModelRef, sanitizeSessionTitle, TITLE_SYSTEM_PROMPT, titleResponseText, } from "./title-generation.js";
|
|
3
|
+
/** Extension-side title generation through Pi's public ModelRegistry facade. */
|
|
4
|
+
export async function generateSessionTitle(input, modelRegistry, config, modelRef, signal, onWarning) {
|
|
5
|
+
const parsedModel = parseTitleModelRef(modelRef);
|
|
6
|
+
if (!parsedModel) {
|
|
7
|
+
onWarning?.(`Invalid session-title model: ${modelRef}`);
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
const model = modelRegistry.find(parsedModel.provider, parsedModel.modelId);
|
|
11
|
+
if (!model) {
|
|
12
|
+
onWarning?.(`Session-title model not found: ${modelRef}`);
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const auth = await modelRegistry.getApiKeyAndHeaders(model);
|
|
16
|
+
if (auth.ok === false) {
|
|
17
|
+
onWarning?.(auth.error);
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const response = await complete(model, {
|
|
21
|
+
systemPrompt: TITLE_SYSTEM_PROMPT,
|
|
22
|
+
messages: [
|
|
23
|
+
{
|
|
24
|
+
role: "user",
|
|
25
|
+
content: [{ type: "text", text: buildTitlePrompt(input, config.maxTitleChars) }],
|
|
26
|
+
timestamp: Date.now(),
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
}, {
|
|
30
|
+
...(auth.apiKey === undefined ? {} : { apiKey: auth.apiKey }),
|
|
31
|
+
...(auth.headers === undefined ? {} : { headers: auth.headers }),
|
|
32
|
+
...(auth.env === undefined ? {} : { env: auth.env }),
|
|
33
|
+
cacheRetention: "none",
|
|
34
|
+
maxRetries: config.maxRetries,
|
|
35
|
+
maxTokens: config.maxTokens,
|
|
36
|
+
signal,
|
|
37
|
+
timeoutMs: config.timeoutMs,
|
|
38
|
+
});
|
|
39
|
+
return sanitizeSessionTitle(titleResponseText(response), config.maxTitleChars);
|
|
40
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { SessionTitleConfig } from "./config.js";
|
|
3
3
|
type SessionEntryLike = {
|
|
4
4
|
type?: string;
|
|
@@ -7,20 +7,23 @@ type SessionEntryLike = {
|
|
|
7
7
|
content?: unknown;
|
|
8
8
|
};
|
|
9
9
|
};
|
|
10
|
-
type
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} | {
|
|
17
|
-
ok: false;
|
|
18
|
-
error: string;
|
|
19
|
-
}>;
|
|
20
|
-
};
|
|
10
|
+
type TitleModelRuntime = Pick<ModelRuntime, "getModel" | "completeSimple">;
|
|
11
|
+
export declare const TITLE_SYSTEM_PROMPT: string;
|
|
12
|
+
export declare function parseTitleModelRef(modelRef: string): {
|
|
13
|
+
provider: string;
|
|
14
|
+
modelId: string;
|
|
15
|
+
} | undefined;
|
|
21
16
|
export declare function firstUserMessageText(entries: readonly SessionEntryLike[]): string | undefined;
|
|
22
17
|
export declare function fallbackSessionTitleFromInput(input: string, maxTitleChars: number): string | undefined;
|
|
18
|
+
export declare function buildTitlePrompt(input: string, maxTitleChars: number): string;
|
|
19
|
+
export declare function titleResponseText(response: {
|
|
20
|
+
content: Array<{
|
|
21
|
+
type: string;
|
|
22
|
+
text?: string;
|
|
23
|
+
}>;
|
|
24
|
+
}): string;
|
|
23
25
|
export declare function sanitizeSessionTitle(raw: string, maxTitleChars: number): string | undefined;
|
|
24
26
|
export declare function sessionTitleModelRefs(config: SessionTitleConfig): string[];
|
|
25
|
-
|
|
27
|
+
/** Host-side title generation through the canonical SDK ModelRuntime. */
|
|
28
|
+
export declare function generateSessionTitleWithRuntime(input: string, modelRuntime: TitleModelRuntime, config: SessionTitleConfig, modelRef: string, signal: AbortSignal, onWarning?: (message: string) => void): Promise<string | undefined>;
|
|
26
29
|
export {};
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
const TITLE_SYSTEM_PROMPT = [
|
|
1
|
+
export const TITLE_SYSTEM_PROMPT = [
|
|
3
2
|
"You name Pi coding-agent sessions from the user's first request.",
|
|
4
3
|
"Return only one concise title, without quotes, markdown, emoji, or explanations.",
|
|
5
4
|
"Use the same language as the task when possible.",
|
|
6
5
|
"Keep it specific, 3-7 words, and under the requested character limit.",
|
|
7
6
|
].join("\n");
|
|
8
|
-
function
|
|
7
|
+
export function parseTitleModelRef(modelRef) {
|
|
9
8
|
const trimmed = modelRef.trim();
|
|
10
9
|
const slash = trimmed.indexOf("/");
|
|
11
10
|
if (slash <= 0 || slash === trimmed.length - 1)
|
|
@@ -58,7 +57,7 @@ export function fallbackSessionTitleFromInput(input, maxTitleChars) {
|
|
|
58
57
|
const candidate = selected.join(" ");
|
|
59
58
|
return sanitizeSessionTitle(candidate || normalized, maxTitleChars);
|
|
60
59
|
}
|
|
61
|
-
function buildTitlePrompt(input, maxTitleChars) {
|
|
60
|
+
export function buildTitlePrompt(input, maxTitleChars) {
|
|
62
61
|
return [
|
|
63
62
|
`Generate a session title under ${maxTitleChars} characters for this session.`,
|
|
64
63
|
"If a parent session title is provided, use it only as context and focus on the new request.",
|
|
@@ -69,7 +68,7 @@ function buildTitlePrompt(input, maxTitleChars) {
|
|
|
69
68
|
"</session_context>",
|
|
70
69
|
].join("\n");
|
|
71
70
|
}
|
|
72
|
-
function
|
|
71
|
+
export function titleResponseText(response) {
|
|
73
72
|
return response.content
|
|
74
73
|
.filter((block) => block.type === "text" && typeof block.text === "string")
|
|
75
74
|
.map((block) => block.text)
|
|
@@ -103,23 +102,19 @@ export function sessionTitleModelRefs(config) {
|
|
|
103
102
|
.filter(Boolean)
|
|
104
103
|
.filter((modelRef, index, refs) => refs.indexOf(modelRef) === index);
|
|
105
104
|
}
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
/** Host-side title generation through the canonical SDK ModelRuntime. */
|
|
106
|
+
export async function generateSessionTitleWithRuntime(input, modelRuntime, config, modelRef, signal, onWarning) {
|
|
107
|
+
const parsedModel = parseTitleModelRef(modelRef);
|
|
108
108
|
if (!parsedModel) {
|
|
109
109
|
onWarning?.(`Invalid session-title model: ${modelRef}`);
|
|
110
110
|
return undefined;
|
|
111
111
|
}
|
|
112
|
-
const model =
|
|
112
|
+
const model = modelRuntime.getModel(parsedModel.provider, parsedModel.modelId);
|
|
113
113
|
if (!model) {
|
|
114
114
|
onWarning?.(`Session-title model not found: ${modelRef}`);
|
|
115
115
|
return undefined;
|
|
116
116
|
}
|
|
117
|
-
const
|
|
118
|
-
if (auth.ok === false) {
|
|
119
|
-
onWarning?.(auth.error);
|
|
120
|
-
return undefined;
|
|
121
|
-
}
|
|
122
|
-
const response = await complete(model, {
|
|
117
|
+
const response = await modelRuntime.completeSimple(model, {
|
|
123
118
|
systemPrompt: TITLE_SYSTEM_PROMPT,
|
|
124
119
|
messages: [
|
|
125
120
|
{
|
|
@@ -129,13 +124,11 @@ export async function generateSessionTitle(input, modelRegistry, config, modelRe
|
|
|
129
124
|
},
|
|
130
125
|
],
|
|
131
126
|
}, {
|
|
132
|
-
...(auth.apiKey === undefined ? {} : { apiKey: auth.apiKey }),
|
|
133
|
-
...(auth.headers === undefined ? {} : { headers: auth.headers }),
|
|
134
127
|
cacheRetention: "none",
|
|
135
128
|
maxRetries: config.maxRetries,
|
|
136
129
|
maxTokens: config.maxTokens,
|
|
137
130
|
signal,
|
|
138
131
|
timeoutMs: config.timeoutMs,
|
|
139
132
|
});
|
|
140
|
-
return sanitizeSessionTitle(
|
|
133
|
+
return sanitizeSessionTitle(titleResponseText(response), config.maxTitleChars);
|
|
141
134
|
}
|
|
@@ -33,8 +33,9 @@
|
|
|
33
33
|
"test:locate-benchmark-e2e": "PI_LOCATE_BENCH_E2E=1 PI_LOCATE_BENCH_MODEL=zai/glm-5-turbo bun test test/locate-benchmark-e2e.test.ts",
|
|
34
34
|
"test:tool-selection-e2e": "TOOL_SELECTION_E2E=1 TOOL_SELECTION_E2E_MODEL=zai/glm-5-turbo bun test --concurrent --max-concurrency=30 test/tool-selection-e2e.test.ts",
|
|
35
35
|
"test:e2e": "TOOL_SELECTION_E2E=1 TOOL_SELECTION_E2E_MODEL=zai/glm-5-turbo ASYNC_SUBAGENTS_E2E=1 ASYNC_SUBAGENTS_DEBUG_LOGS=1 ASYNC_SUBAGENTS_MODEL=zai/glm-5-turbo bun test --concurrent --max-concurrency=5 test/tool-selection-e2e.test.ts test/async-subagents/e2e.test.ts test/async-subagents/selection-e2e.test.ts test/todo-persistence-e2e.test.ts",
|
|
36
|
+
"typecheck": "tsc -p tsconfig.source.json",
|
|
36
37
|
"typecheck:async-subagents": "bash scripts/typecheck-source.sh",
|
|
37
|
-
"check": "npm run smoke"
|
|
38
|
+
"check": "npm run typecheck && npm test && npm run smoke"
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
41
|
"jsonc-parser": "^3.3.1",
|
|
@@ -42,9 +43,9 @@
|
|
|
42
43
|
"vscode-languageserver-protocol": "^3.17.5"
|
|
43
44
|
},
|
|
44
45
|
"peerDependencies": {
|
|
45
|
-
"@earendil-works/pi-ai": "0.80.
|
|
46
|
-
"@earendil-works/pi-coding-agent": "0.80.
|
|
47
|
-
"@earendil-works/pi-tui": "0.80.
|
|
46
|
+
"@earendil-works/pi-ai": "0.80.10",
|
|
47
|
+
"@earendil-works/pi-coding-agent": "0.80.10",
|
|
48
|
+
"@earendil-works/pi-tui": "0.80.10",
|
|
48
49
|
"typebox": "*"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { complete } from "@earendil-works/pi-ai/compat";
|
|
2
1
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
|
+
import { completeWithModelRegistry, type ModelCompletionRegistry } from "../../model-completion.js";
|
|
3
3
|
import type { AgentTask } from "./types.js";
|
|
4
4
|
import {
|
|
5
5
|
currentModelRef,
|
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
|
|
12
12
|
export interface SubagentRoutingContext {
|
|
13
13
|
model?: unknown;
|
|
14
|
-
modelRegistry?: {
|
|
14
|
+
modelRegistry?: ModelCompletionRegistry & {
|
|
15
15
|
find(provider: string, modelId: string): Model<Api> | undefined;
|
|
16
16
|
getApiKeyAndHeaders(model: Model<Api>): Promise<
|
|
17
|
-
| { ok?: true; apiKey?: string; headers?: Record<string, string> }
|
|
17
|
+
| { ok?: true; apiKey?: string; headers?: Record<string, string>; env?: Record<string, string> }
|
|
18
18
|
| { ok: false; error: string }
|
|
19
19
|
>;
|
|
20
20
|
};
|
|
@@ -67,7 +67,8 @@ export async function routeSubagentTasks(
|
|
|
67
67
|
for (const candidate of candidates) {
|
|
68
68
|
if (signal?.aborted) throw new Error("Aborted");
|
|
69
69
|
try {
|
|
70
|
-
response = await
|
|
70
|
+
response = await completeWithModelRegistry(
|
|
71
|
+
ctx.modelRegistry,
|
|
71
72
|
candidate.model,
|
|
72
73
|
{
|
|
73
74
|
systemPrompt: ROUTER_SYSTEM_PROMPT,
|
|
@@ -82,6 +83,7 @@ export async function routeSubagentTasks(
|
|
|
82
83
|
{
|
|
83
84
|
apiKey: candidate.apiKey,
|
|
84
85
|
headers: candidate.headers,
|
|
86
|
+
env: candidate.env,
|
|
85
87
|
cacheRetention: "none",
|
|
86
88
|
maxRetries: routing.maxRetries,
|
|
87
89
|
maxTokens: routing.maxTokens,
|
|
@@ -179,14 +181,16 @@ interface RoutingCandidate {
|
|
|
179
181
|
model: Model<Api>;
|
|
180
182
|
apiKey?: string;
|
|
181
183
|
headers?: Record<string, string>;
|
|
184
|
+
env?: Record<string, string>;
|
|
182
185
|
}
|
|
183
186
|
|
|
184
|
-
type RoutingResponse = Awaited<ReturnType<typeof
|
|
187
|
+
type RoutingResponse = Awaited<ReturnType<typeof completeWithModelRegistry>>;
|
|
185
188
|
|
|
186
189
|
async function resolveModelRef(ctx: SubagentRoutingContext, modelRef: string): Promise<{
|
|
187
190
|
model: Model<Api>;
|
|
188
191
|
apiKey?: string;
|
|
189
192
|
headers?: Record<string, string>;
|
|
193
|
+
env?: Record<string, string>;
|
|
190
194
|
} | undefined> {
|
|
191
195
|
const parsed = parseModelRef(modelRef);
|
|
192
196
|
if (!parsed || !ctx.modelRegistry) return undefined;
|
|
@@ -194,7 +198,7 @@ async function resolveModelRef(ctx: SubagentRoutingContext, modelRef: string): P
|
|
|
194
198
|
if (!model) return undefined;
|
|
195
199
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
196
200
|
if (auth.ok === false) return undefined;
|
|
197
|
-
return { model, apiKey: auth.apiKey, headers: auth.headers };
|
|
201
|
+
return { model, apiKey: auth.apiKey, headers: auth.headers, env: auth.env };
|
|
198
202
|
}
|
|
199
203
|
|
|
200
204
|
function parseModelRef(modelRef: string): { provider: string; modelId: string } | undefined {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { complete } from "@earendil-works/pi-ai/compat";
|
|
2
1
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
|
+
import { completeWithModelRegistry, type ModelCompletionRegistry } from "../../model-completion.js";
|
|
3
3
|
import { currentModelRef, resolveSubagentRoutingConfig, type SubagentConfig } from "./config.js";
|
|
4
4
|
|
|
5
5
|
export type UltraworkAutoDecision = "ultrawork" | "hint" | "none";
|
|
6
6
|
|
|
7
7
|
export interface UltraworkAutoContext {
|
|
8
8
|
model?: unknown;
|
|
9
|
-
modelRegistry?: {
|
|
9
|
+
modelRegistry?: ModelCompletionRegistry & {
|
|
10
10
|
find(provider: string, modelId: string): Model<Api> | undefined;
|
|
11
11
|
getApiKeyAndHeaders(model: Model<Api>): Promise<
|
|
12
|
-
| { ok?: true; apiKey?: string; headers?: Record<string, string> }
|
|
12
|
+
| { ok?: true; apiKey?: string; headers?: Record<string, string>; env?: Record<string, string> }
|
|
13
13
|
| { ok: false; error: string }
|
|
14
14
|
>;
|
|
15
15
|
};
|
|
@@ -63,7 +63,8 @@ export async function decideUltraworkAuto(
|
|
|
63
63
|
const resolved = await resolveClassifierModel(ctx, routing.model);
|
|
64
64
|
if (!resolved) return "none";
|
|
65
65
|
|
|
66
|
-
const response = await
|
|
66
|
+
const response = await completeWithModelRegistry(
|
|
67
|
+
ctx.modelRegistry,
|
|
67
68
|
resolved.model,
|
|
68
69
|
{
|
|
69
70
|
systemPrompt: CLASSIFIER_SYSTEM_PROMPT,
|
|
@@ -78,6 +79,7 @@ export async function decideUltraworkAuto(
|
|
|
78
79
|
{
|
|
79
80
|
apiKey: resolved.apiKey,
|
|
80
81
|
headers: resolved.headers,
|
|
82
|
+
env: resolved.env,
|
|
81
83
|
cacheRetention: "none",
|
|
82
84
|
maxRetries: routing.maxRetries,
|
|
83
85
|
maxTokens: Math.min(routing.maxTokens, 32),
|
|
@@ -130,6 +132,7 @@ async function resolveClassifierModel(ctx: UltraworkAutoContext, modelRef: strin
|
|
|
130
132
|
model: Model<Api>;
|
|
131
133
|
apiKey?: string;
|
|
132
134
|
headers?: Record<string, string>;
|
|
135
|
+
env?: Record<string, string>;
|
|
133
136
|
} | undefined> {
|
|
134
137
|
const configured = await resolveModelRef(ctx, modelRef);
|
|
135
138
|
if (configured) return configured;
|
|
@@ -141,6 +144,7 @@ async function resolveModelRef(ctx: UltraworkAutoContext, modelRef: string): Pro
|
|
|
141
144
|
model: Model<Api>;
|
|
142
145
|
apiKey?: string;
|
|
143
146
|
headers?: Record<string, string>;
|
|
147
|
+
env?: Record<string, string>;
|
|
144
148
|
} | undefined> {
|
|
145
149
|
const parsed = parseModelRef(modelRef);
|
|
146
150
|
if (!parsed || !ctx.modelRegistry) return undefined;
|
|
@@ -148,7 +152,7 @@ async function resolveModelRef(ctx: UltraworkAutoContext, modelRef: string): Pro
|
|
|
148
152
|
if (!model) return undefined;
|
|
149
153
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
150
154
|
if (auth.ok === false) return undefined;
|
|
151
|
-
return { model, apiKey: auth.apiKey, headers: auth.headers };
|
|
155
|
+
return { model, apiKey: auth.apiKey, headers: auth.headers, env: auth.env };
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
function parseModelRef(modelRef: string): { provider: string; modelId: string } | undefined {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { complete } from "@earendil-works/pi-ai/compat";
|
|
4
3
|
import type { Api, AssistantMessage, ImageContent, Model, TextContent } from "@earendil-works/pi-ai";
|
|
5
4
|
import { Type } from "typebox";
|
|
6
5
|
|
|
7
6
|
import { loadPiToolsSuiteConfig } from "../config.js";
|
|
8
7
|
import { ignoreStaleExtensionContextError } from "../context-usage.js";
|
|
8
|
+
import { completeWithModelRegistry, type ModelCompletionRegistry } from "../model-completion.js";
|
|
9
9
|
|
|
10
10
|
type ExtensionAPI = any;
|
|
11
11
|
|
|
@@ -25,9 +25,11 @@ type LookupDetails = {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
type ResolvedLookupModel = {
|
|
28
|
+
modelRegistry: ModelCompletionRegistry;
|
|
28
29
|
model: Model<Api>;
|
|
29
30
|
apiKey?: string;
|
|
30
31
|
headers?: Record<string, string>;
|
|
32
|
+
env?: Record<string, string>;
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
const SILENT_PROMPT_MARKER_START = "<glm_silent_mode>";
|
|
@@ -504,7 +506,8 @@ function createLookupTool() {
|
|
|
504
506
|
const promptText = buildLookupPrompt(params, recentContext, images.length, pathImages.warnings);
|
|
505
507
|
|
|
506
508
|
try {
|
|
507
|
-
const response = await
|
|
509
|
+
const response = await completeWithModelRegistry(
|
|
510
|
+
resolved.modelRegistry,
|
|
508
511
|
resolved.model,
|
|
509
512
|
{
|
|
510
513
|
systemPrompt: LOOKUP_SYSTEM_PROMPT,
|
|
@@ -519,6 +522,7 @@ function createLookupTool() {
|
|
|
519
522
|
{
|
|
520
523
|
apiKey: resolved.apiKey,
|
|
521
524
|
headers: resolved.headers,
|
|
525
|
+
env: resolved.env,
|
|
522
526
|
cacheRetention: "none",
|
|
523
527
|
maxRetries: 1,
|
|
524
528
|
maxTokens: DEFAULT_LOOKUP_MAX_TOKENS,
|
|
@@ -765,9 +769,20 @@ async function resolveLookupModel(ctx: unknown, modelRef: string): Promise<Resol
|
|
|
765
769
|
if (!isRecord(registry) || typeof registry.find !== "function" || typeof registry.getApiKeyAndHeaders !== "function") return undefined;
|
|
766
770
|
const model = registry.find(parsed.provider, parsed.modelId) as Model<Api> | undefined;
|
|
767
771
|
if (!model) return undefined;
|
|
768
|
-
const auth = await registry.getApiKeyAndHeaders(model) as {
|
|
772
|
+
const auth = await registry.getApiKeyAndHeaders(model) as {
|
|
773
|
+
ok?: true;
|
|
774
|
+
apiKey?: string;
|
|
775
|
+
headers?: Record<string, string>;
|
|
776
|
+
env?: Record<string, string>;
|
|
777
|
+
} | { ok: false; error: string };
|
|
769
778
|
if (auth.ok === false) return undefined;
|
|
770
|
-
return {
|
|
779
|
+
return {
|
|
780
|
+
modelRegistry: registry as ModelCompletionRegistry,
|
|
781
|
+
model,
|
|
782
|
+
apiKey: auth.apiKey,
|
|
783
|
+
headers: auth.headers,
|
|
784
|
+
env: auth.env,
|
|
785
|
+
};
|
|
771
786
|
}
|
|
772
787
|
|
|
773
788
|
function parseModelRef(modelRef: string): { provider: string; modelId: string } | undefined {
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
// automatic fallback to the programmatic digest on any failure/timeout.
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
|
|
16
|
-
import { complete } from "@earendil-works/pi-ai/compat"
|
|
17
16
|
import type { Model, Api } from "@earendil-works/pi-ai"
|
|
17
|
+
import { completeWithModelRegistry, type ModelCompletionRegistry } from "../model-completion.js"
|
|
18
18
|
import type { DcpState } from "./state.js"
|
|
19
19
|
import type { DcpConfig } from "./config.js"
|
|
20
20
|
import type { CompressionCandidate } from "./pruner-types.js"
|
|
@@ -134,6 +134,14 @@ export interface ModelSummaryResult {
|
|
|
134
134
|
attempts: ModelSummaryAttempt[]
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
type ModelSummaryRegistry = ModelCompletionRegistry & {
|
|
138
|
+
find(provider: string, modelId: string): Model<Api> | undefined
|
|
139
|
+
getApiKeyAndHeaders(model: Model<Api>): Promise<
|
|
140
|
+
| { ok: true; apiKey?: string; headers?: Record<string, string>; env?: Record<string, string> }
|
|
141
|
+
| { ok: false; error: string }
|
|
142
|
+
>
|
|
143
|
+
}
|
|
144
|
+
|
|
137
145
|
/**
|
|
138
146
|
* Try to produce a model-generated summary by calling each model in
|
|
139
147
|
* `modelRefs` in order. On success returns `{ text, usedModelRef, attempts }`;
|
|
@@ -146,7 +154,7 @@ export interface ModelSummaryResult {
|
|
|
146
154
|
*/
|
|
147
155
|
export async function generateModelSummary(
|
|
148
156
|
modelRefs: string[],
|
|
149
|
-
modelRegistry:
|
|
157
|
+
modelRegistry: ModelSummaryRegistry | undefined,
|
|
150
158
|
signal: AbortSignal | undefined,
|
|
151
159
|
topic: string,
|
|
152
160
|
messagesInRange: any[],
|
|
@@ -178,7 +186,7 @@ export async function generateModelSummary(
|
|
|
178
186
|
continue
|
|
179
187
|
}
|
|
180
188
|
|
|
181
|
-
let auth
|
|
189
|
+
let auth: Awaited<ReturnType<ModelSummaryRegistry["getApiKeyAndHeaders"]>>
|
|
182
190
|
try {
|
|
183
191
|
auth = await modelRegistry.getApiKeyAndHeaders(model)
|
|
184
192
|
} catch (error) {
|
|
@@ -186,7 +194,7 @@ export async function generateModelSummary(
|
|
|
186
194
|
attempts.push({ ref, outcome: "no-auth", error: error instanceof Error ? error.message : String(error) })
|
|
187
195
|
continue
|
|
188
196
|
}
|
|
189
|
-
if (
|
|
197
|
+
if (auth.ok === false) {
|
|
190
198
|
attempts.push({ ref, outcome: "no-auth" })
|
|
191
199
|
continue
|
|
192
200
|
}
|
|
@@ -202,7 +210,8 @@ export async function generateModelSummary(
|
|
|
202
210
|
}
|
|
203
211
|
|
|
204
212
|
try {
|
|
205
|
-
const result = await
|
|
213
|
+
const result = await completeWithModelRegistry(
|
|
214
|
+
modelRegistry,
|
|
206
215
|
model,
|
|
207
216
|
{ systemPrompt: SUMMARIZER_SYSTEM_PROMPT, messages: [{ role: "user", content: userPrompt, timestamp: Date.now() }] },
|
|
208
217
|
{
|
|
@@ -109,9 +109,10 @@ function collectNudgeStats(ctx: ExtensionCommandContext, state: DcpState): DcpNu
|
|
|
109
109
|
else stats.upgraded++
|
|
110
110
|
stats.byType[data.type]++
|
|
111
111
|
const createdAt = typeof data.createdAt === "number" ? data.createdAt : undefined
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
const rawContextPercent = data.contextPercent
|
|
113
|
+
let contextPercent: number | null | undefined
|
|
114
|
+
if (typeof rawContextPercent === "number") contextPercent = rawContextPercent
|
|
115
|
+
else if (rawContextPercent === null) contextPercent = null
|
|
115
116
|
if (!stats.last || (createdAt ?? 0) >= (stats.last.createdAt ?? 0)) {
|
|
116
117
|
stats.last = { type: data.type, event, createdAt, contextPercent }
|
|
117
118
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Api,
|
|
3
|
+
AssistantMessage,
|
|
4
|
+
AssistantMessageEventStream,
|
|
5
|
+
Context,
|
|
6
|
+
Model,
|
|
7
|
+
SimpleStreamOptions,
|
|
8
|
+
} from "@earendil-works/pi-ai";
|
|
9
|
+
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
|
10
|
+
|
|
11
|
+
type RegisteredProviderConfig = {
|
|
12
|
+
streamSimple?: (
|
|
13
|
+
model: Model<Api>,
|
|
14
|
+
context: Context,
|
|
15
|
+
options?: SimpleStreamOptions,
|
|
16
|
+
) => AssistantMessageEventStream;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type ModelCompletionRegistry = {
|
|
20
|
+
getRegisteredProviderConfig?(providerId: string): RegisteredProviderConfig | undefined;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Complete through an extension provider's registered stream when available.
|
|
25
|
+
* Pi 0.80.8+ no longer copies extension streams into pi-ai's global compat
|
|
26
|
+
* registry, so falling back to compat is valid only for built-in APIs.
|
|
27
|
+
*/
|
|
28
|
+
export async function completeWithModelRegistry(
|
|
29
|
+
modelRegistry: ModelCompletionRegistry | undefined,
|
|
30
|
+
model: Model<Api>,
|
|
31
|
+
context: Context,
|
|
32
|
+
options?: SimpleStreamOptions,
|
|
33
|
+
): Promise<AssistantMessage> {
|
|
34
|
+
const providerConfig = modelRegistry?.getRegisteredProviderConfig?.(model.provider);
|
|
35
|
+
if (providerConfig?.streamSimple) return providerConfig.streamSimple(model, context, options).result();
|
|
36
|
+
return completeSimple(model, context, options);
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-ui-extend",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.64",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
"prepublishOnly": "npm run check && npm run build:pix && npm run generate-schemas"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@earendil-works/pi-ai": "0.80.
|
|
69
|
-
"@earendil-works/pi-coding-agent": "0.80.
|
|
70
|
-
"@earendil-works/pi-tui": "0.80.
|
|
68
|
+
"@earendil-works/pi-ai": "0.80.10",
|
|
69
|
+
"@earendil-works/pi-coding-agent": "0.80.10",
|
|
70
|
+
"@earendil-works/pi-tui": "0.80.10",
|
|
71
71
|
"@mariozechner/clipboard": "^0.3.9",
|
|
72
72
|
"jsonc-parser": "3.3.1",
|
|
73
73
|
"typebox": "1.1.38",
|