@oh-my-pi/pi-coding-agent 3.33.0 → 3.35.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 +57 -8
- package/docs/custom-tools.md +1 -1
- package/docs/extensions.md +4 -4
- package/docs/hooks.md +2 -2
- package/docs/sdk.md +4 -8
- package/examples/custom-tools/README.md +2 -2
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/todo.ts +1 -1
- package/examples/hooks/custom-compaction.ts +4 -2
- package/examples/hooks/handoff.ts +1 -1
- package/examples/hooks/qna.ts +1 -1
- package/examples/sdk/02-custom-model.ts +1 -1
- package/examples/sdk/README.md +1 -1
- package/package.json +5 -5
- package/src/capability/ssh.ts +42 -0
- package/src/cli/file-processor.ts +1 -1
- package/src/cli/list-models.ts +1 -1
- package/src/core/agent-session.ts +214 -31
- package/src/core/auth-storage.ts +1 -1
- package/src/core/compaction/branch-summarization.ts +2 -2
- package/src/core/compaction/compaction.ts +2 -2
- package/src/core/compaction/utils.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -1
- package/src/core/extensions/runner.ts +1 -1
- package/src/core/extensions/types.ts +1 -1
- package/src/core/extensions/wrapper.ts +1 -1
- package/src/core/hooks/runner.ts +2 -2
- package/src/core/hooks/types.ts +1 -1
- package/src/core/index.ts +11 -0
- package/src/core/messages.ts +1 -1
- package/src/core/model-registry.ts +1 -1
- package/src/core/model-resolver.ts +7 -6
- package/src/core/sdk.ts +33 -4
- package/src/core/session-manager.ts +16 -1
- package/src/core/settings-manager.ts +20 -6
- package/src/core/ssh/connection-manager.ts +466 -0
- package/src/core/ssh/ssh-executor.ts +190 -0
- package/src/core/ssh/sshfs-mount.ts +162 -0
- package/src/core/ssh-executor.ts +5 -0
- package/src/core/system-prompt.ts +424 -1
- package/src/core/title-generator.ts +2 -2
- package/src/core/tools/edit.ts +1 -0
- package/src/core/tools/grep.ts +1 -1
- package/src/core/tools/index.test.ts +1 -0
- package/src/core/tools/index.ts +5 -0
- package/src/core/tools/output.ts +1 -1
- package/src/core/tools/read.ts +24 -11
- package/src/core/tools/renderers.ts +3 -0
- package/src/core/tools/ssh.ts +302 -0
- package/src/core/tools/task/index.ts +11 -2
- package/src/core/tools/task/model-resolver.ts +5 -4
- package/src/core/tools/task/types.ts +1 -1
- package/src/core/tools/task/worker.ts +1 -1
- package/src/core/voice.ts +1 -1
- package/src/discovery/index.ts +3 -0
- package/src/discovery/ssh.ts +162 -0
- package/src/main.ts +4 -1
- package/src/modes/interactive/components/assistant-message.ts +1 -1
- package/src/modes/interactive/components/custom-message.ts +1 -1
- package/src/modes/interactive/components/footer.ts +1 -1
- package/src/modes/interactive/components/hook-message.ts +1 -1
- package/src/modes/interactive/components/model-selector.ts +1 -1
- package/src/modes/interactive/components/oauth-selector.ts +1 -1
- package/src/modes/interactive/components/status-line.ts +1 -1
- package/src/modes/interactive/components/tool-execution.ts +15 -12
- package/src/modes/interactive/interactive-mode.ts +43 -9
- package/src/modes/print-mode.ts +1 -1
- package/src/modes/rpc/rpc-client.ts +1 -1
- package/src/modes/rpc/rpc-types.ts +1 -1
- package/src/prompts/system-prompt.md +4 -0
- package/src/prompts/tools/ssh.md +74 -0
- package/src/utils/image-resize.ts +1 -1
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
* Modes use this class and add their own I/O layer on top.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import type { AssistantMessage, ImageContent, Message, Model, TextContent, Usage } from "@mariozechner/pi-ai";
|
|
17
|
-
import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@mariozechner/pi-ai";
|
|
18
16
|
import type { Agent, AgentEvent, AgentMessage, AgentState, AgentTool, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
17
|
+
import type { AssistantMessage, ImageContent, Message, Model, TextContent, Usage } from "@oh-my-pi/pi-ai";
|
|
18
|
+
import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@oh-my-pi/pi-ai";
|
|
19
19
|
import type { Rule } from "../capability/rule";
|
|
20
20
|
import { getAuthPath } from "../config";
|
|
21
21
|
import { theme } from "../modes/interactive/theme/theme";
|
|
@@ -45,6 +45,7 @@ import type {
|
|
|
45
45
|
} from "./extensions";
|
|
46
46
|
import { extractFileMentions, generateFileMentionMessages } from "./file-mentions";
|
|
47
47
|
import type { HookCommandContext } from "./hooks/types";
|
|
48
|
+
import { logger } from "./logger";
|
|
48
49
|
import type { BashExecutionMessage, CustomMessage } from "./messages";
|
|
49
50
|
import type { ModelRegistry } from "./model-registry";
|
|
50
51
|
import { parseModelString } from "./model-resolver";
|
|
@@ -52,6 +53,8 @@ import { expandPromptTemplate, type PromptTemplate, parseCommandArgs } from "./p
|
|
|
52
53
|
import type { BranchSummaryEntry, CompactionEntry, NewSessionOptions, SessionManager } from "./session-manager";
|
|
53
54
|
import type { SettingsManager, SkillsSettings } from "./settings-manager";
|
|
54
55
|
import { expandSlashCommand, type FileSlashCommand } from "./slash-commands";
|
|
56
|
+
import { closeAllConnections } from "./ssh/connection-manager";
|
|
57
|
+
import { unmountAll } from "./ssh/sshfs-mount";
|
|
55
58
|
import type { TtsrManager } from "./ttsr";
|
|
56
59
|
|
|
57
60
|
/** Session-specific events that extend the core AgentEvent */
|
|
@@ -167,6 +170,15 @@ const noOpUIContext: ExtensionUIContext = {
|
|
|
167
170
|
},
|
|
168
171
|
};
|
|
169
172
|
|
|
173
|
+
async function cleanupSshResources(): Promise<void> {
|
|
174
|
+
const results = await Promise.allSettled([closeAllConnections(), unmountAll()]);
|
|
175
|
+
for (const result of results) {
|
|
176
|
+
if (result.status === "rejected") {
|
|
177
|
+
logger.warn("SSH cleanup failed", { error: String(result.reason) });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
170
182
|
// ============================================================================
|
|
171
183
|
// AgentSession Class
|
|
172
184
|
// ============================================================================
|
|
@@ -447,7 +459,10 @@ export class AgentSession {
|
|
|
447
459
|
const content = message.content;
|
|
448
460
|
if (typeof content === "string") return content;
|
|
449
461
|
const textBlocks = content.filter((c) => c.type === "text");
|
|
450
|
-
|
|
462
|
+
const text = textBlocks.map((c) => (c as TextContent).text).join("");
|
|
463
|
+
if (text.length > 0) return text;
|
|
464
|
+
const hasImages = content.some((c) => c.type === "image");
|
|
465
|
+
return hasImages ? "[Image]" : "";
|
|
451
466
|
}
|
|
452
467
|
|
|
453
468
|
/** Find the last assistant message in agent state (including aborted ones) */
|
|
@@ -534,6 +549,7 @@ export class AgentSession {
|
|
|
534
549
|
*/
|
|
535
550
|
async dispose(): Promise<void> {
|
|
536
551
|
await this.sessionManager.flush();
|
|
552
|
+
await cleanupSshResources();
|
|
537
553
|
this._disconnectFromAgent();
|
|
538
554
|
this._eventListeners = [];
|
|
539
555
|
}
|
|
@@ -709,9 +725,9 @@ export class AgentSession {
|
|
|
709
725
|
);
|
|
710
726
|
}
|
|
711
727
|
if (options.streamingBehavior === "followUp") {
|
|
712
|
-
await this._queueFollowUp(expandedText);
|
|
728
|
+
await this._queueFollowUp(expandedText, options?.images);
|
|
713
729
|
} else {
|
|
714
|
-
await this._queueSteer(expandedText);
|
|
730
|
+
await this._queueSteer(expandedText, options?.images);
|
|
715
731
|
}
|
|
716
732
|
return;
|
|
717
733
|
}
|
|
@@ -940,11 +956,16 @@ export class AgentSession {
|
|
|
940
956
|
/**
|
|
941
957
|
* Internal: Queue a steering message (already expanded, no extension command check).
|
|
942
958
|
*/
|
|
943
|
-
private async _queueSteer(text: string): Promise<void> {
|
|
944
|
-
|
|
959
|
+
private async _queueSteer(text: string, images?: ImageContent[]): Promise<void> {
|
|
960
|
+
const displayText = text || (images && images.length > 0 ? "[Image]" : "");
|
|
961
|
+
this._steeringMessages.push(displayText);
|
|
962
|
+
const content: (TextContent | ImageContent)[] = [{ type: "text", text }];
|
|
963
|
+
if (images && images.length > 0) {
|
|
964
|
+
content.push(...images);
|
|
965
|
+
}
|
|
945
966
|
this.agent.steer({
|
|
946
967
|
role: "user",
|
|
947
|
-
content
|
|
968
|
+
content,
|
|
948
969
|
timestamp: Date.now(),
|
|
949
970
|
});
|
|
950
971
|
}
|
|
@@ -952,11 +973,16 @@ export class AgentSession {
|
|
|
952
973
|
/**
|
|
953
974
|
* Internal: Queue a follow-up message (already expanded, no extension command check).
|
|
954
975
|
*/
|
|
955
|
-
private async _queueFollowUp(text: string): Promise<void> {
|
|
956
|
-
|
|
976
|
+
private async _queueFollowUp(text: string, images?: ImageContent[]): Promise<void> {
|
|
977
|
+
const displayText = text || (images && images.length > 0 ? "[Image]" : "");
|
|
978
|
+
this._followUpMessages.push(displayText);
|
|
979
|
+
const content: (TextContent | ImageContent)[] = [{ type: "text", text }];
|
|
980
|
+
if (images && images.length > 0) {
|
|
981
|
+
content.push(...images);
|
|
982
|
+
}
|
|
957
983
|
this.agent.followUp({
|
|
958
984
|
role: "user",
|
|
959
|
-
content
|
|
985
|
+
content,
|
|
960
986
|
timestamp: Date.now(),
|
|
961
987
|
});
|
|
962
988
|
}
|
|
@@ -1162,7 +1188,7 @@ export class AgentSession {
|
|
|
1162
1188
|
|
|
1163
1189
|
/**
|
|
1164
1190
|
* Cycle through configured role models in a fixed order.
|
|
1165
|
-
* Skips missing roles
|
|
1191
|
+
* Skips missing roles.
|
|
1166
1192
|
* @param roleOrder - Order of roles to cycle through (e.g., ["slow", "default", "smol"])
|
|
1167
1193
|
* @param options - Optional settings: `temporary` to not persist to settings
|
|
1168
1194
|
*/
|
|
@@ -1176,7 +1202,6 @@ export class AgentSession {
|
|
|
1176
1202
|
const currentModel = this.model;
|
|
1177
1203
|
if (!currentModel) return undefined;
|
|
1178
1204
|
const roleModels: Array<{ role: string; model: Model<any> }> = [];
|
|
1179
|
-
const seen = new Set<string>();
|
|
1180
1205
|
|
|
1181
1206
|
for (const role of roleOrder) {
|
|
1182
1207
|
const roleModelStr =
|
|
@@ -1195,15 +1220,15 @@ export class AgentSession {
|
|
|
1195
1220
|
}
|
|
1196
1221
|
if (!match) continue;
|
|
1197
1222
|
|
|
1198
|
-
const key = `${match.provider}/${match.id}`;
|
|
1199
|
-
if (seen.has(key)) continue;
|
|
1200
|
-
seen.add(key);
|
|
1201
1223
|
roleModels.push({ role, model: match });
|
|
1202
1224
|
}
|
|
1203
1225
|
|
|
1204
1226
|
if (roleModels.length <= 1) return undefined;
|
|
1205
1227
|
|
|
1206
|
-
|
|
1228
|
+
const lastRole = this.sessionManager.getLastModelChangeRole();
|
|
1229
|
+
let currentIndex = lastRole
|
|
1230
|
+
? roleModels.findIndex((entry) => entry.role === lastRole)
|
|
1231
|
+
: roleModels.findIndex((entry) => modelsAreEqual(entry.model, currentModel));
|
|
1207
1232
|
if (currentIndex === -1) currentIndex = 0;
|
|
1208
1233
|
|
|
1209
1234
|
const nextIndex = (currentIndex + 1) % roleModels.length;
|
|
@@ -1545,6 +1570,60 @@ export class AgentSession {
|
|
|
1545
1570
|
}
|
|
1546
1571
|
}
|
|
1547
1572
|
|
|
1573
|
+
private _getModelKey(model: Model<any>): string {
|
|
1574
|
+
return `${model.provider}/${model.id}`;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
private _resolveRoleModel(
|
|
1578
|
+
role: string,
|
|
1579
|
+
availableModels: Model<any>[],
|
|
1580
|
+
currentModel: Model<any> | undefined,
|
|
1581
|
+
): Model<any> | undefined {
|
|
1582
|
+
const roleModelStr =
|
|
1583
|
+
role === "default"
|
|
1584
|
+
? (this.settingsManager.getModelRole("default") ??
|
|
1585
|
+
(currentModel ? `${currentModel.provider}/${currentModel.id}` : undefined))
|
|
1586
|
+
: this.settingsManager.getModelRole(role);
|
|
1587
|
+
|
|
1588
|
+
if (!roleModelStr) return undefined;
|
|
1589
|
+
|
|
1590
|
+
const parsed = parseModelString(roleModelStr);
|
|
1591
|
+
if (parsed) {
|
|
1592
|
+
return availableModels.find((m) => m.provider === parsed.provider && m.id === parsed.id);
|
|
1593
|
+
}
|
|
1594
|
+
const roleLower = roleModelStr.toLowerCase();
|
|
1595
|
+
return availableModels.find((m) => m.id.toLowerCase() === roleLower);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
private _getCompactionModelCandidates(availableModels: Model<any>[]): Model<any>[] {
|
|
1599
|
+
const candidates: Model<any>[] = [];
|
|
1600
|
+
const seen = new Set<string>();
|
|
1601
|
+
|
|
1602
|
+
const addCandidate = (model: Model<any> | undefined): void => {
|
|
1603
|
+
if (!model) return;
|
|
1604
|
+
const key = this._getModelKey(model);
|
|
1605
|
+
if (seen.has(key)) return;
|
|
1606
|
+
seen.add(key);
|
|
1607
|
+
candidates.push(model);
|
|
1608
|
+
};
|
|
1609
|
+
|
|
1610
|
+
const currentModel = this.model;
|
|
1611
|
+
addCandidate(this._resolveRoleModel("default", availableModels, currentModel));
|
|
1612
|
+
addCandidate(this._resolveRoleModel("slow", availableModels, currentModel));
|
|
1613
|
+
addCandidate(this._resolveRoleModel("small", availableModels, currentModel));
|
|
1614
|
+
addCandidate(this._resolveRoleModel("smol", availableModels, currentModel));
|
|
1615
|
+
|
|
1616
|
+
const sortedByContext = [...availableModels].sort((a, b) => b.contextWindow - a.contextWindow);
|
|
1617
|
+
for (const model of sortedByContext) {
|
|
1618
|
+
if (!seen.has(this._getModelKey(model))) {
|
|
1619
|
+
addCandidate(model);
|
|
1620
|
+
break;
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
return candidates;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1548
1627
|
/**
|
|
1549
1628
|
* Internal: Run auto-compaction with events.
|
|
1550
1629
|
*/
|
|
@@ -1564,8 +1643,8 @@ export class AgentSession {
|
|
|
1564
1643
|
return;
|
|
1565
1644
|
}
|
|
1566
1645
|
|
|
1567
|
-
const
|
|
1568
|
-
if (
|
|
1646
|
+
const availableModels = this._modelRegistry.getAvailable();
|
|
1647
|
+
if (availableModels.length === 0) {
|
|
1569
1648
|
this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
|
|
1570
1649
|
return;
|
|
1571
1650
|
}
|
|
@@ -1613,14 +1692,68 @@ export class AgentSession {
|
|
|
1613
1692
|
tokensBefore = hookCompaction.tokensBefore;
|
|
1614
1693
|
details = hookCompaction.details;
|
|
1615
1694
|
} else {
|
|
1616
|
-
|
|
1617
|
-
const
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
this.
|
|
1623
|
-
|
|
1695
|
+
const candidates = this._getCompactionModelCandidates(availableModels);
|
|
1696
|
+
const retrySettings = this.settingsManager.getRetrySettings();
|
|
1697
|
+
let compactResult: CompactionResult | undefined;
|
|
1698
|
+
let lastError: unknown;
|
|
1699
|
+
|
|
1700
|
+
for (const candidate of candidates) {
|
|
1701
|
+
const apiKey = await this._modelRegistry.getApiKey(candidate);
|
|
1702
|
+
if (!apiKey) continue;
|
|
1703
|
+
|
|
1704
|
+
let attempt = 0;
|
|
1705
|
+
while (true) {
|
|
1706
|
+
try {
|
|
1707
|
+
compactResult = await compact(
|
|
1708
|
+
preparation,
|
|
1709
|
+
candidate,
|
|
1710
|
+
apiKey,
|
|
1711
|
+
undefined,
|
|
1712
|
+
this._autoCompactionAbortController.signal,
|
|
1713
|
+
);
|
|
1714
|
+
break;
|
|
1715
|
+
} catch (error) {
|
|
1716
|
+
if (this._autoCompactionAbortController.signal.aborted) {
|
|
1717
|
+
throw error;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1721
|
+
const retryAfterMs = this._parseRetryAfterMsFromError(message);
|
|
1722
|
+
const shouldRetry =
|
|
1723
|
+
retrySettings.enabled &&
|
|
1724
|
+
attempt < retrySettings.maxRetries &&
|
|
1725
|
+
(retryAfterMs !== undefined || this._isRetryableErrorMessage(message));
|
|
1726
|
+
if (!shouldRetry) {
|
|
1727
|
+
lastError = error;
|
|
1728
|
+
break;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
const baseDelayMs = retrySettings.baseDelayMs * 2 ** attempt;
|
|
1732
|
+
const delayMs = retryAfterMs !== undefined ? Math.max(baseDelayMs, retryAfterMs) : baseDelayMs;
|
|
1733
|
+
attempt++;
|
|
1734
|
+
logger.warn("Auto-compaction failed, retrying", {
|
|
1735
|
+
attempt,
|
|
1736
|
+
maxRetries: retrySettings.maxRetries,
|
|
1737
|
+
delayMs,
|
|
1738
|
+
retryAfterMs,
|
|
1739
|
+
error: message,
|
|
1740
|
+
});
|
|
1741
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
if (compactResult) {
|
|
1746
|
+
break;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
if (!compactResult) {
|
|
1751
|
+
if (lastError) {
|
|
1752
|
+
throw lastError;
|
|
1753
|
+
}
|
|
1754
|
+
throw new Error("Compaction failed: no available model");
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1624
1757
|
summary = compactResult.summary;
|
|
1625
1758
|
firstKeptEntryId = compactResult.firstKeptEntryId;
|
|
1626
1759
|
tokensBefore = compactResult.tokensBefore;
|
|
@@ -1712,12 +1845,61 @@ export class AgentSession {
|
|
|
1712
1845
|
if (isContextOverflow(message, contextWindow)) return false;
|
|
1713
1846
|
|
|
1714
1847
|
const err = message.errorMessage;
|
|
1848
|
+
return this._isRetryableErrorMessage(err);
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
private _isRetryableErrorMessage(errorMessage: string): boolean {
|
|
1715
1852
|
// Match: overloaded_error, rate limit, 429, 500, 502, 503, 504, service unavailable, connection error
|
|
1716
1853
|
return /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server error|internal error|connection.?error/i.test(
|
|
1717
|
-
|
|
1854
|
+
errorMessage,
|
|
1718
1855
|
);
|
|
1719
1856
|
}
|
|
1720
1857
|
|
|
1858
|
+
private _parseRetryAfterMsFromError(errorMessage: string): number | undefined {
|
|
1859
|
+
const now = Date.now();
|
|
1860
|
+
const retryAfterMsMatch = /retry-after-ms\s*[:=]\s*(\d+)/i.exec(errorMessage);
|
|
1861
|
+
if (retryAfterMsMatch) {
|
|
1862
|
+
return Math.max(0, Number(retryAfterMsMatch[1]));
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
const retryAfterMatch = /retry-after\s*[:=]\s*([^\s,;]+)/i.exec(errorMessage);
|
|
1866
|
+
if (retryAfterMatch) {
|
|
1867
|
+
const value = retryAfterMatch[1];
|
|
1868
|
+
const seconds = Number(value);
|
|
1869
|
+
if (!Number.isNaN(seconds)) {
|
|
1870
|
+
return Math.max(0, seconds * 1000);
|
|
1871
|
+
}
|
|
1872
|
+
const dateMs = Date.parse(value);
|
|
1873
|
+
if (!Number.isNaN(dateMs)) {
|
|
1874
|
+
return Math.max(0, dateMs - now);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
const resetMsMatch = /x-ratelimit-reset-ms\s*[:=]\s*(\d+)/i.exec(errorMessage);
|
|
1879
|
+
if (resetMsMatch) {
|
|
1880
|
+
const resetMs = Number(resetMsMatch[1]);
|
|
1881
|
+
if (!Number.isNaN(resetMs)) {
|
|
1882
|
+
if (resetMs > 1_000_000_000_000) {
|
|
1883
|
+
return Math.max(0, resetMs - now);
|
|
1884
|
+
}
|
|
1885
|
+
return Math.max(0, resetMs);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
const resetMatch = /x-ratelimit-reset\s*[:=]\s*(\d+)/i.exec(errorMessage);
|
|
1890
|
+
if (resetMatch) {
|
|
1891
|
+
const resetSeconds = Number(resetMatch[1]);
|
|
1892
|
+
if (!Number.isNaN(resetSeconds)) {
|
|
1893
|
+
if (resetSeconds > 1_000_000_000) {
|
|
1894
|
+
return Math.max(0, resetSeconds * 1000 - now);
|
|
1895
|
+
}
|
|
1896
|
+
return Math.max(0, resetSeconds * 1000);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
return undefined;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1721
1903
|
/**
|
|
1722
1904
|
* Handle retryable errors with exponential backoff.
|
|
1723
1905
|
* @returns true if retry was initiated, false if max retries exceeded or disabled
|
|
@@ -2472,9 +2654,10 @@ export class AgentSession {
|
|
|
2472
2654
|
* Emit a custom tool session event (backwards compatibility for older callers).
|
|
2473
2655
|
*/
|
|
2474
2656
|
async emitCustomToolSessionEvent(reason: "start" | "switch" | "branch" | "tree" | "shutdown"): Promise<void> {
|
|
2475
|
-
if (!this._extensionRunner) return;
|
|
2476
2657
|
if (reason !== "shutdown") return;
|
|
2477
|
-
if (
|
|
2478
|
-
|
|
2658
|
+
if (this._extensionRunner?.hasHandlers("session_shutdown")) {
|
|
2659
|
+
await this._extensionRunner.emit({ type: "session_shutdown" });
|
|
2660
|
+
}
|
|
2661
|
+
await cleanupSshResources();
|
|
2479
2662
|
}
|
|
2480
2663
|
}
|
package/src/core/auth-storage.ts
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* a summary of the branch being left so context isn't lost.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { Model } from "@mariozechner/pi-ai";
|
|
9
|
-
import { completeSimple } from "@mariozechner/pi-ai";
|
|
10
8
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
9
|
+
import type { Model } from "@oh-my-pi/pi-ai";
|
|
10
|
+
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
11
11
|
import branchSummaryPrompt from "../../prompts/branch-summary.md" with { type: "text" };
|
|
12
12
|
import branchSummaryPreamble from "../../prompts/branch-summary-preamble.md" with { type: "text" };
|
|
13
13
|
import {
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* and after compaction the session is reloaded.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { AssistantMessage, Model, Usage } from "@mariozechner/pi-ai";
|
|
9
|
-
import { complete, completeSimple } from "@mariozechner/pi-ai";
|
|
10
8
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
9
|
+
import type { AssistantMessage, Model, Usage } from "@oh-my-pi/pi-ai";
|
|
10
|
+
import { complete, completeSimple } from "@oh-my-pi/pi-ai";
|
|
11
11
|
import compactionSummaryPrompt from "../../prompts/compaction-summary.md" with { type: "text" };
|
|
12
12
|
import compactionTurnPrefixPrompt from "../../prompts/compaction-turn-prefix.md" with { type: "text" };
|
|
13
13
|
import compactionUpdateSummaryPrompt from "../../prompts/compaction-update-summary.md" with { type: "text" };
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Shared utilities for compaction and branch summarization.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { Message } from "@mariozechner/pi-ai";
|
|
6
5
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
6
|
+
import type { Message } from "@oh-my-pi/pi-ai";
|
|
7
7
|
import summarizationSystemPrompt from "../../prompts/summarization-system.md" with { type: "text" };
|
|
8
8
|
|
|
9
9
|
// ============================================================================
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* They can provide custom rendering for tool calls and results in the TUI.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { Model } from "@mariozechner/pi-ai";
|
|
9
8
|
import type { AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
9
|
+
import type { Model } from "@oh-my-pi/pi-ai";
|
|
10
10
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
11
11
|
import type { Static, TSchema } from "@sinclair/typebox";
|
|
12
12
|
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Extension runner - executes extensions and manages their lifecycle.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { ImageContent, Model } from "@mariozechner/pi-ai";
|
|
6
5
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
6
|
+
import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
|
|
7
7
|
import type { KeyId } from "@oh-my-pi/pi-tui";
|
|
8
8
|
import { theme } from "../../modes/interactive/theme/theme";
|
|
9
9
|
import type { ModelRegistry } from "../model-registry";
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* - Interact with the user via UI primitives
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { ImageContent, Model, TextContent, ToolResultMessage } from "@mariozechner/pi-ai";
|
|
12
11
|
import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
12
|
+
import type { ImageContent, Model, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
|
|
13
13
|
import type { Component, KeyId, TUI } from "@oh-my-pi/pi-tui";
|
|
14
14
|
import type { Static, TSchema } from "@sinclair/typebox";
|
|
15
15
|
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Tool wrappers for extensions.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { ImageContent, TextContent } from "@mariozechner/pi-ai";
|
|
6
5
|
import type { AgentTool, AgentToolContext, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
6
|
+
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
|
7
7
|
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
8
8
|
import type { ExtensionRunner } from "./runner";
|
|
9
9
|
import type { ExtensionContext, RegisteredTool, ToolCallEventResult, ToolResultEventResult } from "./types";
|
package/src/core/hooks/runner.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Hook runner - executes hooks and manages their lifecycle.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { Model } from "@mariozechner/pi-ai";
|
|
6
5
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
6
|
+
import type { Model } from "@oh-my-pi/pi-ai";
|
|
7
7
|
import { theme } from "../../modes/interactive/theme/theme";
|
|
8
8
|
import type { ModelRegistry } from "../model-registry";
|
|
9
9
|
import type { SessionManager } from "../session-manager";
|
|
@@ -400,7 +400,7 @@ export class HookRunner {
|
|
|
400
400
|
*/
|
|
401
401
|
async emitBeforeAgentStart(
|
|
402
402
|
prompt: string,
|
|
403
|
-
images?: import("@
|
|
403
|
+
images?: import("@oh-my-pi/pi-ai").ImageContent[],
|
|
404
404
|
): Promise<BeforeAgentStartEventResult | undefined> {
|
|
405
405
|
const ctx = this.createContext();
|
|
406
406
|
let result: BeforeAgentStartEventResult | undefined;
|
package/src/core/hooks/types.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* and interact with the user via UI primitives.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ImageContent, Message, Model, TextContent, ToolResultMessage } from "@mariozechner/pi-ai";
|
|
9
8
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
9
|
+
import type { ImageContent, Message, Model, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
|
|
10
10
|
import type { Component, TUI } from "@oh-my-pi/pi-tui";
|
|
11
11
|
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
12
12
|
import type { CompactionPreparation, CompactionResult } from "../compaction/index";
|
package/src/core/index.ts
CHANGED
|
@@ -38,5 +38,16 @@ export {
|
|
|
38
38
|
type MCPToolsLoadResult,
|
|
39
39
|
type MCPTransport,
|
|
40
40
|
} from "./mcp/index";
|
|
41
|
+
export {
|
|
42
|
+
buildRemoteCommand,
|
|
43
|
+
closeAllConnections,
|
|
44
|
+
closeConnection,
|
|
45
|
+
ensureConnection,
|
|
46
|
+
getControlDir,
|
|
47
|
+
getControlPathTemplate,
|
|
48
|
+
type SSHConnectionTarget,
|
|
49
|
+
} from "./ssh/connection-manager";
|
|
50
|
+
export { executeSSH, type SSHExecutorOptions, type SSHResult } from "./ssh/ssh-executor";
|
|
51
|
+
export { hasSshfs, isMounted, mountRemote, unmountAll, unmountRemote } from "./ssh/sshfs-mount";
|
|
41
52
|
|
|
42
53
|
export * as utils from "./utils";
|
package/src/core/messages.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* and provides a transformer to convert them to LLM-compatible messages.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ImageContent, Message, TextContent } from "@mariozechner/pi-ai";
|
|
9
8
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
9
|
+
import type { ImageContent, Message, TextContent } from "@oh-my-pi/pi-ai";
|
|
10
10
|
|
|
11
11
|
export const COMPACTION_SUMMARY_PREFIX = `The conversation history before this point was compacted into the following summary:
|
|
12
12
|
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type KnownProvider,
|
|
12
12
|
type Model,
|
|
13
13
|
normalizeDomain,
|
|
14
|
-
} from "@
|
|
14
|
+
} from "@oh-my-pi/pi-ai";
|
|
15
15
|
import { type Static, Type } from "@sinclair/typebox";
|
|
16
16
|
import AjvModule from "ajv";
|
|
17
17
|
import type { AuthStorage } from "./auth-storage";
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Model resolution, scoping, and initial selection
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { type Api, type KnownProvider, type Model, modelsAreEqual } from "@mariozechner/pi-ai";
|
|
6
5
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
6
|
+
import { type Api, type KnownProvider, type Model, modelsAreEqual } from "@oh-my-pi/pi-ai";
|
|
7
7
|
import chalk from "chalk";
|
|
8
8
|
import { minimatch } from "minimatch";
|
|
9
9
|
import { isValidThinkingLevel } from "../cli/args";
|
|
@@ -25,6 +25,7 @@ export const defaultModelPerProvider: Record<KnownProvider, string> = {
|
|
|
25
25
|
cerebras: "zai-glm-4.6",
|
|
26
26
|
zai: "glm-4.6",
|
|
27
27
|
mistral: "devstral-medium-latest",
|
|
28
|
+
opencode: "claude-sonnet-4-5",
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
export interface ScopedModel {
|
|
@@ -79,7 +80,7 @@ function tryMatchModel(modelPattern: string, availableModels: Model<Api>[]): Mod
|
|
|
79
80
|
const provider = modelPattern.substring(0, slashIndex);
|
|
80
81
|
const modelId = modelPattern.substring(slashIndex + 1);
|
|
81
82
|
const providerMatch = availableModels.find(
|
|
82
|
-
(m) => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase()
|
|
83
|
+
(m) => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase(),
|
|
83
84
|
);
|
|
84
85
|
if (providerMatch) {
|
|
85
86
|
return providerMatch;
|
|
@@ -97,7 +98,7 @@ function tryMatchModel(modelPattern: string, availableModels: Model<Api>[]): Mod
|
|
|
97
98
|
const matches = availableModels.filter(
|
|
98
99
|
(m) =>
|
|
99
100
|
m.id.toLowerCase().includes(modelPattern.toLowerCase()) ||
|
|
100
|
-
m.name?.toLowerCase().includes(modelPattern.toLowerCase())
|
|
101
|
+
m.name?.toLowerCase().includes(modelPattern.toLowerCase()),
|
|
101
102
|
);
|
|
102
103
|
|
|
103
104
|
if (matches.length === 0) {
|
|
@@ -351,7 +352,7 @@ export async function restoreModelFromSession(
|
|
|
351
352
|
savedModelId: string,
|
|
352
353
|
currentModel: Model<Api> | undefined,
|
|
353
354
|
shouldPrintMessages: boolean,
|
|
354
|
-
modelRegistry: ModelRegistry
|
|
355
|
+
modelRegistry: ModelRegistry,
|
|
355
356
|
): Promise<{ model: Model<Api> | undefined; fallbackMessage: string | undefined }> {
|
|
356
357
|
const restoredModel = modelRegistry.find(savedProvider, savedModelId);
|
|
357
358
|
|
|
@@ -427,7 +428,7 @@ export async function restoreModelFromSession(
|
|
|
427
428
|
*/
|
|
428
429
|
export async function findSmolModel(
|
|
429
430
|
modelRegistry: ModelRegistry,
|
|
430
|
-
savedModel?: string
|
|
431
|
+
savedModel?: string,
|
|
431
432
|
): Promise<Model<Api> | undefined> {
|
|
432
433
|
const availableModels = modelRegistry.getAvailable();
|
|
433
434
|
if (availableModels.length === 0) return undefined;
|
|
@@ -470,7 +471,7 @@ export async function findSmolModel(
|
|
|
470
471
|
*/
|
|
471
472
|
export async function findSlowModel(
|
|
472
473
|
modelRegistry: ModelRegistry,
|
|
473
|
-
savedModel?: string
|
|
474
|
+
savedModel?: string,
|
|
474
475
|
): Promise<Model<Api> | undefined> {
|
|
475
476
|
const availableModels = modelRegistry.getAvailable();
|
|
476
477
|
if (availableModels.length === 0) return undefined;
|