@oh-my-pi/pi-coding-agent 4.0.1 → 4.2.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/CHANGELOG.md +49 -0
- package/README.md +2 -1
- package/docs/sdk.md +0 -3
- package/package.json +6 -5
- package/src/config.ts +9 -0
- package/src/core/agent-storage.ts +450 -0
- package/src/core/auth-storage.ts +111 -184
- package/src/core/compaction/branch-summarization.ts +5 -4
- package/src/core/compaction/compaction.ts +7 -6
- package/src/core/compaction/utils.ts +6 -11
- package/src/core/custom-commands/bundled/review/index.ts +22 -94
- package/src/core/custom-share.ts +66 -0
- package/src/core/history-storage.ts +174 -0
- package/src/core/index.ts +1 -0
- package/src/core/keybindings.ts +3 -0
- package/src/core/prompt-templates.ts +271 -1
- package/src/core/sdk.ts +14 -3
- package/src/core/settings-manager.ts +100 -34
- package/src/core/slash-commands.ts +4 -1
- package/src/core/storage-migration.ts +215 -0
- package/src/core/system-prompt.ts +87 -289
- package/src/core/title-generator.ts +3 -2
- package/src/core/tools/ask.ts +2 -2
- package/src/core/tools/bash.ts +2 -1
- package/src/core/tools/calculator.ts +2 -1
- package/src/core/tools/edit.ts +2 -1
- package/src/core/tools/find.ts +2 -1
- package/src/core/tools/gemini-image.ts +2 -1
- package/src/core/tools/git.ts +2 -2
- package/src/core/tools/grep.ts +2 -1
- package/src/core/tools/index.test.ts +0 -28
- package/src/core/tools/index.ts +0 -6
- package/src/core/tools/lsp/index.ts +2 -1
- package/src/core/tools/output.ts +2 -1
- package/src/core/tools/read.ts +4 -1
- package/src/core/tools/ssh.ts +4 -2
- package/src/core/tools/task/agents.ts +56 -30
- package/src/core/tools/task/commands.ts +9 -8
- package/src/core/tools/task/index.ts +7 -15
- package/src/core/tools/web-fetch.ts +2 -1
- package/src/core/tools/web-search/auth.ts +106 -16
- package/src/core/tools/web-search/index.ts +3 -2
- package/src/core/tools/web-search/providers/anthropic.ts +44 -6
- package/src/core/tools/write.ts +2 -1
- package/src/core/voice.ts +3 -1
- package/src/main.ts +1 -1
- package/src/migrations.ts +20 -20
- package/src/modes/interactive/components/custom-editor.ts +7 -0
- package/src/modes/interactive/components/history-search.ts +158 -0
- package/src/modes/interactive/controllers/command-controller.ts +527 -0
- package/src/modes/interactive/controllers/event-controller.ts +340 -0
- package/src/modes/interactive/controllers/extension-ui-controller.ts +600 -0
- package/src/modes/interactive/controllers/input-controller.ts +585 -0
- package/src/modes/interactive/controllers/selector-controller.ts +585 -0
- package/src/modes/interactive/interactive-mode.ts +370 -3115
- package/src/modes/interactive/theme/theme.ts +5 -5
- package/src/modes/interactive/types.ts +189 -0
- package/src/modes/interactive/utils/ui-helpers.ts +449 -0
- package/src/modes/interactive/utils/voice-manager.ts +96 -0
- package/src/prompts/{explore.md → agents/explore.md} +7 -5
- package/src/prompts/agents/frontmatter.md +7 -0
- package/src/prompts/{plan.md → agents/plan.md} +3 -3
- package/src/prompts/{task.md → agents/task.md} +1 -1
- package/src/prompts/review-request.md +44 -8
- package/src/prompts/system/custom-system-prompt.md +80 -0
- package/src/prompts/system/file-operations.md +12 -0
- package/src/prompts/system/system-prompt.md +232 -0
- package/src/prompts/system/title-system.md +2 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/task.md +9 -3
- package/src/core/tools/rulebook.ts +0 -132
- package/src/prompts/system-prompt.md +0 -43
- package/src/prompts/title-system.md +0 -8
- /package/src/prompts/{architect-plan.md → agents/architect-plan.md} +0 -0
- /package/src/prompts/{implement-with-critic.md → agents/implement-with-critic.md} +0 -0
- /package/src/prompts/{implement.md → agents/implement.md} +0 -0
- /package/src/prompts/{init.md → agents/init.md} +0 -0
- /package/src/prompts/{reviewer.md → agents/reviewer.md} +0 -0
- /package/src/prompts/{branch-summary-preamble.md → compaction/branch-summary-preamble.md} +0 -0
- /package/src/prompts/{branch-summary.md → compaction/branch-summary.md} +0 -0
- /package/src/prompts/{compaction-summary.md → compaction/compaction-summary.md} +0 -0
- /package/src/prompts/{compaction-turn-prefix.md → compaction/compaction-turn-prefix.md} +0 -0
- /package/src/prompts/{compaction-update-summary.md → compaction/compaction-update-summary.md} +0 -0
- /package/src/prompts/{summarization-system.md → system/summarization-system.md} +0 -0
package/src/core/auth-storage.ts
CHANGED
|
@@ -1,23 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Credential storage for API keys and OAuth tokens.
|
|
3
|
-
* Handles loading, saving, and refreshing credentials from
|
|
4
|
-
*
|
|
5
|
-
* Uses file locking to prevent race conditions when multiple pi instances
|
|
6
|
-
* try to refresh tokens simultaneously.
|
|
3
|
+
* Handles loading, saving, and refreshing credentials from agent.db.
|
|
7
4
|
*/
|
|
8
5
|
|
|
9
|
-
import {
|
|
10
|
-
chmodSync,
|
|
11
|
-
closeSync,
|
|
12
|
-
existsSync,
|
|
13
|
-
openSync,
|
|
14
|
-
readFileSync,
|
|
15
|
-
renameSync,
|
|
16
|
-
statSync,
|
|
17
|
-
unlinkSync,
|
|
18
|
-
writeFileSync,
|
|
19
|
-
} from "node:fs";
|
|
20
|
-
import { dirname } from "node:path";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
21
7
|
import {
|
|
22
8
|
getEnvApiKey,
|
|
23
9
|
getOAuthApiKey,
|
|
@@ -29,7 +15,10 @@ import {
|
|
|
29
15
|
type OAuthCredentials,
|
|
30
16
|
type OAuthProvider,
|
|
31
17
|
} from "@oh-my-pi/pi-ai";
|
|
18
|
+
import { getAgentDbPath } from "../config";
|
|
19
|
+
import { AgentStorage } from "./agent-storage";
|
|
32
20
|
import { logger } from "./logger";
|
|
21
|
+
import { migrateJsonStorage } from "./storage-migration";
|
|
33
22
|
|
|
34
23
|
export type ApiKeyCredential = {
|
|
35
24
|
type: "api_key";
|
|
@@ -46,6 +35,12 @@ export type AuthCredentialEntry = AuthCredential | AuthCredential[];
|
|
|
46
35
|
|
|
47
36
|
export type AuthStorageData = Record<string, AuthCredentialEntry>;
|
|
48
37
|
|
|
38
|
+
/**
|
|
39
|
+
* In-memory representation pairing DB row ID with credential.
|
|
40
|
+
* The ID is required for update/delete operations against agent.db.
|
|
41
|
+
*/
|
|
42
|
+
type StoredCredential = { id: number; credential: AuthCredential };
|
|
43
|
+
|
|
49
44
|
/** Rate limit window from Codex usage API (primary or secondary quota). */
|
|
50
45
|
type CodexUsageWindow = {
|
|
51
46
|
usedPercent?: number;
|
|
@@ -86,18 +81,18 @@ function toBoolean(value: unknown): boolean | undefined {
|
|
|
86
81
|
}
|
|
87
82
|
|
|
88
83
|
/**
|
|
89
|
-
* Credential storage backed by
|
|
90
|
-
* Reads from
|
|
84
|
+
* Credential storage backed by agent.db.
|
|
85
|
+
* Reads from SQLite and migrates legacy auth.json paths.
|
|
91
86
|
*/
|
|
92
87
|
export class AuthStorage {
|
|
93
|
-
// File locking configuration for concurrent access protection
|
|
94
|
-
private static readonly lockRetryDelayMs = 50; // Polling interval when waiting for lock
|
|
95
|
-
private static readonly lockTimeoutMs = 5000; // Max wait time before failing
|
|
96
|
-
private static readonly lockStaleMs = 30000; // Age threshold for auto-removing orphaned locks
|
|
97
88
|
private static readonly codexUsageCacheTtlMs = 60_000; // Cache usage data for 1 minute
|
|
98
89
|
private static readonly defaultBackoffMs = 60_000; // Default backoff when no reset time available
|
|
99
90
|
|
|
100
|
-
|
|
91
|
+
/** Provider -> credentials cache, populated from agent.db on reload(). */
|
|
92
|
+
private data: Map<string, StoredCredential[]> = new Map();
|
|
93
|
+
private storage: AgentStorage;
|
|
94
|
+
/** Resolved path to agent.db (derived from authPath or used directly if .db). */
|
|
95
|
+
private dbPath: string;
|
|
101
96
|
private runtimeOverrides: Map<string, string> = new Map();
|
|
102
97
|
/** Tracks next credential index per provider:type key for round-robin distribution (non-session use). */
|
|
103
98
|
private providerRoundRobinIndex: Map<string, number> = new Map();
|
|
@@ -110,13 +105,28 @@ export class AuthStorage {
|
|
|
110
105
|
private fallbackResolver?: (provider: string) => string | undefined;
|
|
111
106
|
|
|
112
107
|
/**
|
|
113
|
-
* @param authPath -
|
|
114
|
-
* @param fallbackPaths - Additional paths to
|
|
108
|
+
* @param authPath - Legacy auth.json path used for migration and locating agent.db
|
|
109
|
+
* @param fallbackPaths - Additional auth.json paths to migrate (legacy support)
|
|
115
110
|
*/
|
|
116
111
|
constructor(
|
|
117
112
|
private authPath: string,
|
|
118
113
|
private fallbackPaths: string[] = [],
|
|
119
|
-
) {
|
|
114
|
+
) {
|
|
115
|
+
this.dbPath = AuthStorage.resolveDbPath(authPath);
|
|
116
|
+
this.storage = AgentStorage.open(this.dbPath);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Converts legacy auth.json path to agent.db path, or returns .db path as-is.
|
|
121
|
+
* @param authPath - Path to auth.json or agent.db
|
|
122
|
+
* @returns Resolved path to agent.db
|
|
123
|
+
*/
|
|
124
|
+
private static resolveDbPath(authPath: string): string {
|
|
125
|
+
if (authPath.endsWith(".db")) {
|
|
126
|
+
return authPath;
|
|
127
|
+
}
|
|
128
|
+
return getAgentDbPath(dirname(authPath));
|
|
129
|
+
}
|
|
120
130
|
|
|
121
131
|
/**
|
|
122
132
|
* Set a runtime API key override (not persisted to disk).
|
|
@@ -134,7 +144,7 @@ export class AuthStorage {
|
|
|
134
144
|
}
|
|
135
145
|
|
|
136
146
|
/**
|
|
137
|
-
* Set a fallback resolver for API keys not found in
|
|
147
|
+
* Set a fallback resolver for API keys not found in agent.db or env vars.
|
|
138
148
|
* Used for custom provider keys from models.json.
|
|
139
149
|
*/
|
|
140
150
|
setFallbackResolver(resolver: (provider: string) => string | undefined): void {
|
|
@@ -142,138 +152,53 @@ export class AuthStorage {
|
|
|
142
152
|
}
|
|
143
153
|
|
|
144
154
|
/**
|
|
145
|
-
* Reload credentials from
|
|
146
|
-
*
|
|
155
|
+
* Reload credentials from agent.db.
|
|
156
|
+
* Migrates legacy auth.json/settings.json on first load.
|
|
147
157
|
*/
|
|
148
158
|
async reload(): Promise<void> {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
logger.debug("AuthStorage.reload path check", { path: authPath, exists });
|
|
156
|
-
|
|
157
|
-
if (exists) {
|
|
158
|
-
try {
|
|
159
|
-
this.data = JSON.parse(readFileSync(authPath, "utf-8"));
|
|
160
|
-
logger.debug("AuthStorage.reload loaded", { path: authPath, providers: Object.keys(this.data) });
|
|
161
|
-
return;
|
|
162
|
-
} catch (e) {
|
|
163
|
-
logger.error("AuthStorage failed to parse auth file", { path: authPath, error: String(e) });
|
|
164
|
-
// Continue to next path on parse error
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
159
|
+
const agentDir = dirname(this.dbPath);
|
|
160
|
+
await migrateJsonStorage({
|
|
161
|
+
agentDir,
|
|
162
|
+
settingsPath: join(agentDir, "settings.json"),
|
|
163
|
+
authPaths: [this.authPath, ...this.fallbackPaths],
|
|
164
|
+
});
|
|
168
165
|
|
|
169
|
-
|
|
170
|
-
|
|
166
|
+
const records = this.storage.listAuthCredentials();
|
|
167
|
+
const grouped = new Map<string, StoredCredential[]>();
|
|
168
|
+
for (const record of records) {
|
|
169
|
+
const list = grouped.get(record.provider) ?? [];
|
|
170
|
+
list.push({ id: record.id, credential: record.credential });
|
|
171
|
+
grouped.set(record.provider, list);
|
|
172
|
+
}
|
|
173
|
+
this.data = grouped;
|
|
171
174
|
}
|
|
172
175
|
|
|
173
176
|
/**
|
|
174
|
-
*
|
|
177
|
+
* Gets cached credentials for a provider.
|
|
178
|
+
* @param provider - Provider name (e.g., "anthropic", "openai")
|
|
179
|
+
* @returns Array of stored credentials, empty if none exist
|
|
175
180
|
*/
|
|
176
|
-
private
|
|
177
|
-
|
|
178
|
-
const tempPath = this.getTempPath();
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
writeFileSync(tempPath, JSON.stringify(this.data, null, 2), { mode: 0o600 });
|
|
182
|
-
renameSync(tempPath, this.authPath);
|
|
183
|
-
chmodSync(this.authPath, 0o600);
|
|
184
|
-
const dir = dirname(this.authPath);
|
|
185
|
-
chmodSync(dir, 0o700);
|
|
186
|
-
} finally {
|
|
187
|
-
this.safeUnlink(tempPath);
|
|
188
|
-
this.releaseLock(lockFd);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/** Returns the lock file path (auth.json.lock) */
|
|
193
|
-
private getLockPath(): string {
|
|
194
|
-
return `${this.authPath}.lock`;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/** Returns a unique temp file path using pid and timestamp to avoid collisions */
|
|
198
|
-
private getTempPath(): string {
|
|
199
|
-
return `${this.authPath}.tmp-${process.pid}-${Date.now()}`;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/** Checks if lock file is older than lockStaleMs (orphaned by crashed process) */
|
|
203
|
-
private isLockStale(lockPath: string): boolean {
|
|
204
|
-
try {
|
|
205
|
-
const stats = statSync(lockPath);
|
|
206
|
-
return Date.now() - stats.mtimeMs > AuthStorage.lockStaleMs;
|
|
207
|
-
} catch {
|
|
208
|
-
return false;
|
|
209
|
-
}
|
|
181
|
+
private getStoredCredentials(provider: string): StoredCredential[] {
|
|
182
|
+
return this.data.get(provider) ?? [];
|
|
210
183
|
}
|
|
211
184
|
|
|
212
185
|
/**
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* @
|
|
186
|
+
* Updates in-memory credential cache for a provider.
|
|
187
|
+
* Removes the provider entry entirely if credentials array is empty.
|
|
188
|
+
* @param provider - Provider name (e.g., "anthropic", "openai")
|
|
189
|
+
* @param credentials - Array of stored credentials to cache
|
|
216
190
|
*/
|
|
217
|
-
private
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
while (true) {
|
|
224
|
-
try {
|
|
225
|
-
// O_EXCL fails if file exists, providing atomic lock acquisition
|
|
226
|
-
return openSync(lockPath, "wx", 0o600);
|
|
227
|
-
} catch (error) {
|
|
228
|
-
const err = error as NodeJS.ErrnoException;
|
|
229
|
-
if (err.code !== "EEXIST") {
|
|
230
|
-
throw err;
|
|
231
|
-
}
|
|
232
|
-
if (this.isLockStale(lockPath)) {
|
|
233
|
-
this.safeUnlink(lockPath);
|
|
234
|
-
logger.warn("AuthStorage lock was stale, removing", { path: lockPath });
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
if (Date.now() - start > timeoutMs) {
|
|
238
|
-
throw new Error(`Timed out waiting for auth lock: ${lockPath}`);
|
|
239
|
-
}
|
|
240
|
-
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/** Releases file lock by closing fd and removing lock file */
|
|
246
|
-
private releaseLock(lockFd: number): void {
|
|
247
|
-
const lockPath = this.getLockPath();
|
|
248
|
-
try {
|
|
249
|
-
closeSync(lockFd);
|
|
250
|
-
} catch (error) {
|
|
251
|
-
logger.warn("AuthStorage failed to close lock file", { error: String(error) });
|
|
252
|
-
}
|
|
253
|
-
this.safeUnlink(lockPath);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/** Removes file if it exists, ignoring ENOENT errors */
|
|
257
|
-
private safeUnlink(path: string): void {
|
|
258
|
-
try {
|
|
259
|
-
unlinkSync(path);
|
|
260
|
-
} catch (error) {
|
|
261
|
-
const err = error as NodeJS.ErrnoException;
|
|
262
|
-
if (err.code !== "ENOENT") {
|
|
263
|
-
logger.warn("AuthStorage failed to remove file", { path, error: String(error) });
|
|
264
|
-
}
|
|
191
|
+
private setStoredCredentials(provider: string, credentials: StoredCredential[]): void {
|
|
192
|
+
if (credentials.length === 0) {
|
|
193
|
+
this.data.delete(provider);
|
|
194
|
+
} else {
|
|
195
|
+
this.data.set(provider, credentials);
|
|
265
196
|
}
|
|
266
197
|
}
|
|
267
198
|
|
|
268
|
-
/** Normalizes credential storage format: single credential becomes array of one */
|
|
269
|
-
private normalizeCredentialEntry(entry: AuthCredentialEntry | undefined): AuthCredential[] {
|
|
270
|
-
if (!entry) return [];
|
|
271
|
-
return Array.isArray(entry) ? entry : [entry];
|
|
272
|
-
}
|
|
273
|
-
|
|
274
199
|
/** Returns all credentials for a provider as an array */
|
|
275
200
|
private getCredentialsForProvider(provider: string): AuthCredential[] {
|
|
276
|
-
return this.
|
|
201
|
+
return this.getStoredCredentials(provider).map((entry) => entry.credential);
|
|
277
202
|
}
|
|
278
203
|
|
|
279
204
|
/** Composite key for round-robin tracking: "anthropic:oauth" or "openai:api_key" */
|
|
@@ -423,21 +348,13 @@ export class AuthStorage {
|
|
|
423
348
|
|
|
424
349
|
/** Updates credential at index in-place (used for OAuth token refresh) */
|
|
425
350
|
private replaceCredentialAt(provider: string, index: number, credential: AuthCredential): void {
|
|
426
|
-
const
|
|
427
|
-
if (
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
this.data[provider] = updated;
|
|
434
|
-
}
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (index === 0) {
|
|
439
|
-
this.data[provider] = credential;
|
|
440
|
-
}
|
|
351
|
+
const entries = this.getStoredCredentials(provider);
|
|
352
|
+
if (index < 0 || index >= entries.length) return;
|
|
353
|
+
const target = entries[index];
|
|
354
|
+
this.storage.updateAuthCredential(target.id, credential);
|
|
355
|
+
const updated = [...entries];
|
|
356
|
+
updated[index] = { id: target.id, credential };
|
|
357
|
+
this.setStoredCredentials(provider, updated);
|
|
441
358
|
}
|
|
442
359
|
|
|
443
360
|
/**
|
|
@@ -445,20 +362,11 @@ export class AuthStorage {
|
|
|
445
362
|
* Cleans up provider entry if last credential removed.
|
|
446
363
|
*/
|
|
447
364
|
private removeCredentialAt(provider: string, index: number): void {
|
|
448
|
-
const
|
|
449
|
-
if (
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
if (updated.length > 0) {
|
|
454
|
-
this.data[provider] = updated;
|
|
455
|
-
} else {
|
|
456
|
-
delete this.data[provider];
|
|
457
|
-
}
|
|
458
|
-
} else {
|
|
459
|
-
delete this.data[provider];
|
|
460
|
-
}
|
|
461
|
-
|
|
365
|
+
const entries = this.getStoredCredentials(provider);
|
|
366
|
+
if (index < 0 || index >= entries.length) return;
|
|
367
|
+
this.storage.deleteAuthCredential(entries[index].id);
|
|
368
|
+
const updated = entries.filter((_value, idx) => idx !== index);
|
|
369
|
+
this.setStoredCredentials(provider, updated);
|
|
462
370
|
this.resetProviderAssignments(provider);
|
|
463
371
|
}
|
|
464
372
|
|
|
@@ -473,29 +381,33 @@ export class AuthStorage {
|
|
|
473
381
|
* Set credential for a provider.
|
|
474
382
|
*/
|
|
475
383
|
async set(provider: string, credential: AuthCredentialEntry): Promise<void> {
|
|
476
|
-
|
|
384
|
+
const normalized = Array.isArray(credential) ? credential : [credential];
|
|
385
|
+
const stored = this.storage.replaceAuthCredentialsForProvider(provider, normalized);
|
|
386
|
+
this.setStoredCredentials(
|
|
387
|
+
provider,
|
|
388
|
+
stored.map((record) => ({ id: record.id, credential: record.credential })),
|
|
389
|
+
);
|
|
477
390
|
this.resetProviderAssignments(provider);
|
|
478
|
-
await this.save();
|
|
479
391
|
}
|
|
480
392
|
|
|
481
393
|
/**
|
|
482
394
|
* Remove credential for a provider.
|
|
483
395
|
*/
|
|
484
396
|
async remove(provider: string): Promise<void> {
|
|
485
|
-
|
|
397
|
+
this.storage.deleteAuthCredentialsForProvider(provider);
|
|
398
|
+
this.data.delete(provider);
|
|
486
399
|
this.resetProviderAssignments(provider);
|
|
487
|
-
await this.save();
|
|
488
400
|
}
|
|
489
401
|
|
|
490
402
|
/**
|
|
491
403
|
* List all providers with credentials.
|
|
492
404
|
*/
|
|
493
405
|
list(): string[] {
|
|
494
|
-
return
|
|
406
|
+
return [...this.data.keys()];
|
|
495
407
|
}
|
|
496
408
|
|
|
497
409
|
/**
|
|
498
|
-
* Check if credentials exist for a provider in
|
|
410
|
+
* Check if credentials exist for a provider in agent.db.
|
|
499
411
|
*/
|
|
500
412
|
has(provider: string): boolean {
|
|
501
413
|
return this.getCredentialsForProvider(provider).length > 0;
|
|
@@ -533,7 +445,16 @@ export class AuthStorage {
|
|
|
533
445
|
* Get all credentials.
|
|
534
446
|
*/
|
|
535
447
|
getAll(): AuthStorageData {
|
|
536
|
-
|
|
448
|
+
const result: AuthStorageData = {};
|
|
449
|
+
for (const [provider, entries] of this.data.entries()) {
|
|
450
|
+
const credentials = entries.map((entry) => entry.credential);
|
|
451
|
+
if (credentials.length === 1) {
|
|
452
|
+
result[provider] = credentials[0];
|
|
453
|
+
} else if (credentials.length > 1) {
|
|
454
|
+
result[provider] = credentials;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return result;
|
|
537
458
|
}
|
|
538
459
|
|
|
539
460
|
/**
|
|
@@ -797,7 +718,15 @@ export class AuthStorage {
|
|
|
797
718
|
}
|
|
798
719
|
|
|
799
720
|
this.markCredentialBlocked(providerKey, sessionCredential.index, blockedUntil);
|
|
800
|
-
|
|
721
|
+
|
|
722
|
+
const remainingCredentials = this.getCredentialsForProvider(provider)
|
|
723
|
+
.map((credential, index) => ({ credential, index }))
|
|
724
|
+
.filter(
|
|
725
|
+
(entry): entry is { credential: AuthCredential; index: number } =>
|
|
726
|
+
entry.credential.type === sessionCredential.type && entry.index !== sessionCredential.index,
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
return remainingCredentials.some((candidate) => !this.isCredentialBlocked(providerKey, candidate.index));
|
|
801
730
|
}
|
|
802
731
|
|
|
803
732
|
/**
|
|
@@ -879,7 +808,6 @@ export class AuthStorage {
|
|
|
879
808
|
|
|
880
809
|
const updated: OAuthCredential = { type: "oauth", ...result.newCredentials };
|
|
881
810
|
this.replaceCredentialAt(provider, selection.index, updated);
|
|
882
|
-
await this.save();
|
|
883
811
|
|
|
884
812
|
if (checkUsage) {
|
|
885
813
|
const usage = await this.getCodexUsage(updated, options?.baseUrl);
|
|
@@ -898,7 +826,6 @@ export class AuthStorage {
|
|
|
898
826
|
return result.apiKey;
|
|
899
827
|
} catch {
|
|
900
828
|
this.removeCredentialAt(provider, selection.index);
|
|
901
|
-
await this.save();
|
|
902
829
|
if (this.getCredentialsForProvider(provider).some((credential) => credential.type === "oauth")) {
|
|
903
830
|
return this.getApiKey(provider, sessionId, options);
|
|
904
831
|
}
|
|
@@ -911,8 +838,8 @@ export class AuthStorage {
|
|
|
911
838
|
* Get API key for a provider.
|
|
912
839
|
* Priority:
|
|
913
840
|
* 1. Runtime override (CLI --api-key)
|
|
914
|
-
* 2. API key from
|
|
915
|
-
* 3. OAuth token from
|
|
841
|
+
* 2. API key from agent.db
|
|
842
|
+
* 3. OAuth token from agent.db (auto-refreshed)
|
|
916
843
|
* 4. Environment variable
|
|
917
844
|
* 5. Fallback resolver (models.json custom providers)
|
|
918
845
|
*/
|
|
@@ -8,14 +8,15 @@
|
|
|
8
8
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
9
9
|
import type { Model } from "@oh-my-pi/pi-ai";
|
|
10
10
|
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
11
|
-
import branchSummaryPrompt from "../../prompts/branch-summary.md" with { type: "text" };
|
|
12
|
-
import branchSummaryPreamble from "../../prompts/branch-summary-preamble.md" with { type: "text" };
|
|
11
|
+
import branchSummaryPrompt from "../../prompts/compaction/branch-summary.md" with { type: "text" };
|
|
12
|
+
import branchSummaryPreamble from "../../prompts/compaction/branch-summary-preamble.md" with { type: "text" };
|
|
13
13
|
import {
|
|
14
14
|
convertToLlm,
|
|
15
15
|
createBranchSummaryMessage,
|
|
16
16
|
createCompactionSummaryMessage,
|
|
17
17
|
createCustomMessage,
|
|
18
18
|
} from "../messages";
|
|
19
|
+
import { renderPromptTemplate } from "../prompt-templates";
|
|
19
20
|
import type { ReadonlySessionManager, SessionEntry } from "../session-manager";
|
|
20
21
|
import { estimateTokens } from "./compaction";
|
|
21
22
|
import {
|
|
@@ -237,9 +238,9 @@ export function prepareBranchEntries(entries: SessionEntry[], tokenBudget: numbe
|
|
|
237
238
|
// Summary Generation
|
|
238
239
|
// ============================================================================
|
|
239
240
|
|
|
240
|
-
const BRANCH_SUMMARY_PREAMBLE = branchSummaryPreamble;
|
|
241
|
+
const BRANCH_SUMMARY_PREAMBLE = renderPromptTemplate(branchSummaryPreamble);
|
|
241
242
|
|
|
242
|
-
const BRANCH_SUMMARY_PROMPT = branchSummaryPrompt;
|
|
243
|
+
const BRANCH_SUMMARY_PROMPT = renderPromptTemplate(branchSummaryPrompt);
|
|
243
244
|
|
|
244
245
|
/**
|
|
245
246
|
* Generate a summary of abandoned branch entries.
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
9
9
|
import type { AssistantMessage, Model, Usage } from "@oh-my-pi/pi-ai";
|
|
10
10
|
import { complete, completeSimple } from "@oh-my-pi/pi-ai";
|
|
11
|
-
import compactionSummaryPrompt from "../../prompts/compaction-summary.md" with { type: "text" };
|
|
12
|
-
import compactionTurnPrefixPrompt from "../../prompts/compaction-turn-prefix.md" with { type: "text" };
|
|
13
|
-
import compactionUpdateSummaryPrompt from "../../prompts/compaction-update-summary.md" with { type: "text" };
|
|
11
|
+
import compactionSummaryPrompt from "../../prompts/compaction/compaction-summary.md" with { type: "text" };
|
|
12
|
+
import compactionTurnPrefixPrompt from "../../prompts/compaction/compaction-turn-prefix.md" with { type: "text" };
|
|
13
|
+
import compactionUpdateSummaryPrompt from "../../prompts/compaction/compaction-update-summary.md" with { type: "text" };
|
|
14
14
|
import { convertToLlm, createBranchSummaryMessage, createCustomMessage } from "../messages";
|
|
15
|
+
import { renderPromptTemplate } from "../prompt-templates";
|
|
15
16
|
import type { CompactionEntry, SessionEntry } from "../session-manager";
|
|
16
17
|
import {
|
|
17
18
|
computeFileLists,
|
|
@@ -386,9 +387,9 @@ export function findCutPoint(
|
|
|
386
387
|
// Summarization
|
|
387
388
|
// ============================================================================
|
|
388
389
|
|
|
389
|
-
const SUMMARIZATION_PROMPT = compactionSummaryPrompt;
|
|
390
|
+
const SUMMARIZATION_PROMPT = renderPromptTemplate(compactionSummaryPrompt);
|
|
390
391
|
|
|
391
|
-
const UPDATE_SUMMARIZATION_PROMPT = compactionUpdateSummaryPrompt;
|
|
392
|
+
const UPDATE_SUMMARIZATION_PROMPT = renderPromptTemplate(compactionUpdateSummaryPrompt);
|
|
392
393
|
|
|
393
394
|
/**
|
|
394
395
|
* Generate a summary of the conversation using the LLM.
|
|
@@ -552,7 +553,7 @@ export function prepareCompaction(
|
|
|
552
553
|
// Main compaction function
|
|
553
554
|
// ============================================================================
|
|
554
555
|
|
|
555
|
-
const TURN_PREFIX_SUMMARIZATION_PROMPT = compactionTurnPrefixPrompt;
|
|
556
|
+
const TURN_PREFIX_SUMMARIZATION_PROMPT = renderPromptTemplate(compactionTurnPrefixPrompt);
|
|
556
557
|
|
|
557
558
|
/**
|
|
558
559
|
* Generate summaries for compaction using prepared data.
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
6
6
|
import type { Message } from "@oh-my-pi/pi-ai";
|
|
7
|
-
import
|
|
7
|
+
import fileOperationsTemplate from "../../prompts/system/file-operations.md" with { type: "text" };
|
|
8
|
+
import summarizationSystemPrompt from "../../prompts/system/summarization-system.md" with { type: "text" };
|
|
9
|
+
import { renderPromptTemplate } from "../prompt-templates";
|
|
8
10
|
|
|
9
11
|
// ============================================================================
|
|
10
12
|
// File Operation Tracking
|
|
@@ -71,15 +73,8 @@ export function computeFileLists(fileOps: FileOperations): { readFiles: string[]
|
|
|
71
73
|
* Format file operations as XML tags for summary.
|
|
72
74
|
*/
|
|
73
75
|
export function formatFileOperations(readFiles: string[], modifiedFiles: string[]): string {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
sections.push(`<read-files>\n${readFiles.join("\n")}\n</read-files>`);
|
|
77
|
-
}
|
|
78
|
-
if (modifiedFiles.length > 0) {
|
|
79
|
-
sections.push(`<modified-files>\n${modifiedFiles.join("\n")}\n</modified-files>`);
|
|
80
|
-
}
|
|
81
|
-
if (sections.length === 0) return "";
|
|
82
|
-
return `\n\n${sections.join("\n\n")}`;
|
|
76
|
+
if (readFiles.length === 0 && modifiedFiles.length === 0) return "";
|
|
77
|
+
return renderPromptTemplate(fileOperationsTemplate, { readFiles, modifiedFiles });
|
|
83
78
|
}
|
|
84
79
|
|
|
85
80
|
// ============================================================================
|
|
@@ -150,4 +145,4 @@ export function serializeConversation(messages: Message[]): string {
|
|
|
150
145
|
// Summarization System Prompt
|
|
151
146
|
// ============================================================================
|
|
152
147
|
|
|
153
|
-
export const SUMMARIZATION_SYSTEM_PROMPT = summarizationSystemPrompt;
|
|
148
|
+
export const SUMMARIZATION_SYSTEM_PROMPT = renderPromptTemplate(summarizationSystemPrompt);
|