@oh-my-pi/pi-coding-agent 13.15.2 → 13.15.3
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 +10 -0
- package/package.json +7 -7
- package/src/config/keybindings.ts +6 -0
- package/src/modes/components/custom-editor.ts +6 -4
- package/src/modes/controllers/input-controller.ts +19 -6
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/session/agent-session.ts +326 -69
- package/src/session/messages.ts +23 -0
- package/src/session/session-manager.ts +65 -0
- package/src/utils/image-input.ts +11 -1
- package/src/web/search/providers/codex.ts +10 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.15.3] - 2026-03-26
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added configurable `app.model.selectTemporary` keybinding for temporary model selection.
|
|
10
|
+
|
|
5
11
|
## [13.15.0] - 2026-03-23
|
|
6
12
|
### Breaking Changes
|
|
7
13
|
|
|
@@ -142,6 +148,10 @@
|
|
|
142
148
|
- Fixed autoresearch logging to require durable ASI metadata (hypothesis, rollback_reason, next_action_hint) for every run including rollback context for discarded, crashed, and checks-failed experiments
|
|
143
149
|
- Fixed autoresearch logging to require durable ASI metadata for every run, including rollback context for discarded, crashed, and checks-failed experiments
|
|
144
150
|
|
|
151
|
+
|
|
152
|
+
### Fixed
|
|
153
|
+
|
|
154
|
+
- Fixed resumed and session-switched GitHub Copilot/OpenAI Responses conversations replaying stale assistant native history from older saved sessions by sanitizing persisted assistant replay metadata on rehydration and resetting provider session state across live session boundaries ([#505](https://github.com/can1357/oh-my-pi/issues/505))
|
|
145
155
|
## [13.14.0] - 2026-03-20
|
|
146
156
|
|
|
147
157
|
### Added
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "13.15.
|
|
4
|
+
"version": "13.15.3",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
44
44
|
"@mozilla/readability": "^0.6",
|
|
45
|
-
"@oh-my-pi/omp-stats": "13.15.
|
|
46
|
-
"@oh-my-pi/pi-agent-core": "13.15.
|
|
47
|
-
"@oh-my-pi/pi-ai": "13.15.
|
|
48
|
-
"@oh-my-pi/pi-natives": "13.15.
|
|
49
|
-
"@oh-my-pi/pi-tui": "13.15.
|
|
50
|
-
"@oh-my-pi/pi-utils": "13.15.
|
|
45
|
+
"@oh-my-pi/omp-stats": "13.15.3",
|
|
46
|
+
"@oh-my-pi/pi-agent-core": "13.15.3",
|
|
47
|
+
"@oh-my-pi/pi-ai": "13.15.3",
|
|
48
|
+
"@oh-my-pi/pi-natives": "13.15.3",
|
|
49
|
+
"@oh-my-pi/pi-tui": "13.15.3",
|
|
50
|
+
"@oh-my-pi/pi-utils": "13.15.3",
|
|
51
51
|
"@sinclair/typebox": "^0.34",
|
|
52
52
|
"@xterm/headless": "^6.0",
|
|
53
53
|
"ajv": "^8.18",
|
|
@@ -25,6 +25,7 @@ interface AppKeybindings {
|
|
|
25
25
|
"app.model.cycleForward": true;
|
|
26
26
|
"app.model.cycleBackward": true;
|
|
27
27
|
"app.model.select": true;
|
|
28
|
+
"app.model.selectTemporary": true;
|
|
28
29
|
"app.tools.expand": true;
|
|
29
30
|
"app.editor.external": true;
|
|
30
31
|
"app.message.followUp": true;
|
|
@@ -95,6 +96,10 @@ export const KEYBINDINGS = {
|
|
|
95
96
|
defaultKeys: "ctrl+l",
|
|
96
97
|
description: "Select model",
|
|
97
98
|
},
|
|
99
|
+
"app.model.selectTemporary": {
|
|
100
|
+
defaultKeys: "alt+p",
|
|
101
|
+
description: "Select temporary model for current session",
|
|
102
|
+
},
|
|
98
103
|
"app.tools.expand": {
|
|
99
104
|
defaultKeys: "ctrl+o",
|
|
100
105
|
description: "Expand tools",
|
|
@@ -194,6 +199,7 @@ const KEYBINDING_NAME_MIGRATIONS = {
|
|
|
194
199
|
cycleModelForward: "app.model.cycleForward",
|
|
195
200
|
cycleModelBackward: "app.model.cycleBackward",
|
|
196
201
|
selectModel: "app.model.select",
|
|
202
|
+
selectModelTemporary: "app.model.selectTemporary",
|
|
197
203
|
togglePlanMode: "app.plan.toggle",
|
|
198
204
|
historySearch: "app.history.search",
|
|
199
205
|
expandTools: "app.tools.expand",
|
|
@@ -11,6 +11,7 @@ type ConfigurableEditorAction = Extract<
|
|
|
11
11
|
| "app.model.cycleForward"
|
|
12
12
|
| "app.model.cycleBackward"
|
|
13
13
|
| "app.model.select"
|
|
14
|
+
| "app.model.selectTemporary"
|
|
14
15
|
| "app.tools.expand"
|
|
15
16
|
| "app.thinking.toggle"
|
|
16
17
|
| "app.editor.external"
|
|
@@ -29,6 +30,7 @@ const DEFAULT_ACTION_KEYS: Record<ConfigurableEditorAction, KeyId[]> = {
|
|
|
29
30
|
"app.model.cycleForward": ["ctrl+p"],
|
|
30
31
|
"app.model.cycleBackward": ["shift+ctrl+p"],
|
|
31
32
|
"app.model.select": ["ctrl+l"],
|
|
33
|
+
"app.model.selectTemporary": ["alt+p"],
|
|
32
34
|
"app.tools.expand": ["ctrl+o"],
|
|
33
35
|
"app.thinking.toggle": ["ctrl+t"],
|
|
34
36
|
"app.editor.external": ["ctrl+g"],
|
|
@@ -56,7 +58,7 @@ export class CustomEditor extends Editor {
|
|
|
56
58
|
onHistorySearch?: () => void;
|
|
57
59
|
onSuspend?: () => void;
|
|
58
60
|
onShowHotkeys?: () => void;
|
|
59
|
-
|
|
61
|
+
onSelectModelTemporary?: () => void;
|
|
60
62
|
/** Called when the configured copy-prompt shortcut is pressed. */
|
|
61
63
|
onCopyPrompt?: () => void;
|
|
62
64
|
/** Called when the configured image-paste shortcut is pressed. */
|
|
@@ -126,9 +128,9 @@ export class CustomEditor extends Editor {
|
|
|
126
128
|
return;
|
|
127
129
|
}
|
|
128
130
|
|
|
129
|
-
// Intercept
|
|
130
|
-
if (
|
|
131
|
-
this.
|
|
131
|
+
// Intercept configured temporary model selector shortcut
|
|
132
|
+
if (this.#matchesAction(data, "app.model.selectTemporary") && this.onSelectModelTemporary) {
|
|
133
|
+
this.onSelectModelTemporary();
|
|
132
134
|
return;
|
|
133
135
|
}
|
|
134
136
|
|
|
@@ -11,6 +11,7 @@ import type { AgentSessionEvent } from "../../session/agent-session";
|
|
|
11
11
|
import { SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails } from "../../session/messages";
|
|
12
12
|
import { executeBuiltinSlashCommand } from "../../slash-commands/builtin-registry";
|
|
13
13
|
import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
14
|
+
import { ensureSupportedImageInput } from "../../utils/image-input";
|
|
14
15
|
import { resizeImage } from "../../utils/image-resize";
|
|
15
16
|
import { generateSessionTitle, setSessionTerminalTitle } from "../../utils/title-generator";
|
|
16
17
|
|
|
@@ -95,7 +96,11 @@ export class InputController {
|
|
|
95
96
|
this.ctx.editor.onCycleModelForward = () => this.cycleRoleModel();
|
|
96
97
|
this.ctx.editor.setActionKeys("app.model.cycleBackward", this.ctx.keybindings.getKeys("app.model.cycleBackward"));
|
|
97
98
|
this.ctx.editor.onCycleModelBackward = () => this.cycleRoleModel({ temporary: true });
|
|
98
|
-
this.ctx.editor.
|
|
99
|
+
this.ctx.editor.setActionKeys(
|
|
100
|
+
"app.model.selectTemporary",
|
|
101
|
+
this.ctx.keybindings.getKeys("app.model.selectTemporary"),
|
|
102
|
+
);
|
|
103
|
+
this.ctx.editor.onSelectModelTemporary = () => this.ctx.showModelSelector({ temporaryOnly: true });
|
|
99
104
|
|
|
100
105
|
// Global debug handler on TUI (works regardless of focus)
|
|
101
106
|
this.ctx.ui.onDebug = () => this.ctx.showDebugSelector();
|
|
@@ -498,17 +503,25 @@ export class InputController {
|
|
|
498
503
|
const image = await readImageFromClipboard();
|
|
499
504
|
if (image) {
|
|
500
505
|
const base64Data = image.data.toBase64();
|
|
501
|
-
let imageData = {
|
|
506
|
+
let imageData = await ensureSupportedImageInput({
|
|
507
|
+
type: "image",
|
|
508
|
+
data: base64Data,
|
|
509
|
+
mimeType: image.mimeType,
|
|
510
|
+
});
|
|
511
|
+
if (!imageData) {
|
|
512
|
+
this.ctx.showStatus(`Unsupported clipboard image format: ${image.mimeType}`);
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
502
515
|
if (settings.get("images.autoResize")) {
|
|
503
516
|
try {
|
|
504
517
|
const resized = await resizeImage({
|
|
505
518
|
type: "image",
|
|
506
|
-
data:
|
|
507
|
-
mimeType:
|
|
519
|
+
data: imageData.data,
|
|
520
|
+
mimeType: imageData.mimeType,
|
|
508
521
|
});
|
|
509
|
-
imageData = { data: resized.data, mimeType: resized.mimeType };
|
|
522
|
+
imageData = { type: "image", data: resized.data, mimeType: resized.mimeType };
|
|
510
523
|
} catch {
|
|
511
|
-
|
|
524
|
+
// Keep the normalized image when resize fails.
|
|
512
525
|
}
|
|
513
526
|
}
|
|
514
527
|
|
|
@@ -40,7 +40,7 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
|
|
|
40
40
|
`| \`${appKey(bindings, "app.thinking.cycle")}\` | Cycle thinking level |`,
|
|
41
41
|
`| \`${appKey(bindings, "app.model.cycleForward")}\` | Cycle role models (slow/default/smol) |`,
|
|
42
42
|
`| \`${appKey(bindings, "app.model.cycleBackward")}\` | Cycle role models (temporary) |`,
|
|
43
|
-
"
|
|
43
|
+
`| \`${appKey(bindings, "app.model.selectTemporary")}\` | Select model (temporary) |`,
|
|
44
44
|
`| \`${appKey(bindings, "app.model.select")}\` | Select model (set roles) |`,
|
|
45
45
|
`| \`${appKey(bindings, "app.plan.toggle")}\` | Toggle plan mode |`,
|
|
46
46
|
`| \`${appKey(bindings, "app.history.search")}\` | Search prompt history |`,
|
|
@@ -1600,16 +1600,29 @@ export class AgentSession {
|
|
|
1600
1600
|
logger.warn("Async job completion deliveries still pending during dispose", { ...deliveryState });
|
|
1601
1601
|
}
|
|
1602
1602
|
await this.sessionManager.close();
|
|
1603
|
-
|
|
1604
|
-
state.close();
|
|
1605
|
-
}
|
|
1606
|
-
this.#providerSessionState.clear();
|
|
1603
|
+
this.#closeAllProviderSessions("dispose");
|
|
1607
1604
|
this.#unsubscribePendingActionPush?.();
|
|
1608
1605
|
this.#unsubscribePendingActionPush = undefined;
|
|
1609
1606
|
this.#disconnectFromAgent();
|
|
1610
1607
|
this.#eventListeners = [];
|
|
1611
1608
|
}
|
|
1612
1609
|
|
|
1610
|
+
#closeAllProviderSessions(reason: string): void {
|
|
1611
|
+
for (const [providerKey, state] of this.#providerSessionState) {
|
|
1612
|
+
try {
|
|
1613
|
+
state.close();
|
|
1614
|
+
} catch (error) {
|
|
1615
|
+
logger.warn("Failed to close provider session state", {
|
|
1616
|
+
providerKey,
|
|
1617
|
+
reason,
|
|
1618
|
+
error: String(error),
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
this.#providerSessionState.clear();
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1613
1626
|
// =========================================================================
|
|
1614
1627
|
// Read-only State Access
|
|
1615
1628
|
// =========================================================================
|
|
@@ -2962,6 +2975,7 @@ export class AgentSession {
|
|
|
2962
2975
|
this.#disconnectFromAgent();
|
|
2963
2976
|
await this.abort();
|
|
2964
2977
|
this.#asyncJobManager?.cancelAll();
|
|
2978
|
+
this.#closeAllProviderSessions("new session");
|
|
2965
2979
|
this.agent.reset();
|
|
2966
2980
|
await this.sessionManager.flush();
|
|
2967
2981
|
await this.sessionManager.newSession(options);
|
|
@@ -4085,22 +4099,174 @@ export class AgentSession {
|
|
|
4085
4099
|
}
|
|
4086
4100
|
|
|
4087
4101
|
#closeProviderSessionsForModelSwitch(currentModel: Model, nextModel: Model): void {
|
|
4088
|
-
|
|
4102
|
+
const providerKeys = new Set<string>();
|
|
4103
|
+
if (currentModel.api === "openai-codex-responses" || nextModel.api === "openai-codex-responses") {
|
|
4104
|
+
providerKeys.add("openai-codex-responses");
|
|
4105
|
+
}
|
|
4106
|
+
if (currentModel.api === "openai-responses") {
|
|
4107
|
+
providerKeys.add(`openai-responses:${currentModel.provider}`);
|
|
4108
|
+
}
|
|
4109
|
+
if (nextModel.api === "openai-responses") {
|
|
4110
|
+
providerKeys.add(`openai-responses:${nextModel.provider}`);
|
|
4111
|
+
}
|
|
4089
4112
|
|
|
4090
|
-
const providerKey
|
|
4091
|
-
|
|
4092
|
-
|
|
4113
|
+
for (const providerKey of providerKeys) {
|
|
4114
|
+
const state = this.#providerSessionState.get(providerKey);
|
|
4115
|
+
if (!state) continue;
|
|
4093
4116
|
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4117
|
+
try {
|
|
4118
|
+
state.close();
|
|
4119
|
+
} catch (error) {
|
|
4120
|
+
logger.warn("Failed to close provider session state during model switch", {
|
|
4121
|
+
providerKey,
|
|
4122
|
+
error: String(error),
|
|
4123
|
+
});
|
|
4124
|
+
}
|
|
4125
|
+
|
|
4126
|
+
this.#providerSessionState.delete(providerKey);
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4130
|
+
#normalizeProviderReplayValue(value: unknown): unknown {
|
|
4131
|
+
if (Array.isArray(value)) {
|
|
4132
|
+
return value.map(item => this.#normalizeProviderReplayValue(item));
|
|
4133
|
+
}
|
|
4134
|
+
if (value && typeof value === "object") {
|
|
4135
|
+
return Object.fromEntries(
|
|
4136
|
+
Object.entries(value).map(([key, entryValue]) => [key, this.#normalizeProviderReplayValue(entryValue)]),
|
|
4137
|
+
);
|
|
4138
|
+
}
|
|
4139
|
+
return value;
|
|
4140
|
+
}
|
|
4141
|
+
|
|
4142
|
+
#normalizeSessionMessageForProviderReplay(message: AgentMessage): unknown {
|
|
4143
|
+
switch (message.role) {
|
|
4144
|
+
case "user":
|
|
4145
|
+
case "developer":
|
|
4146
|
+
return {
|
|
4147
|
+
role: message.role,
|
|
4148
|
+
content: this.#normalizeProviderReplayValue(message.content),
|
|
4149
|
+
providerPayload: message.providerPayload,
|
|
4150
|
+
};
|
|
4151
|
+
case "assistant": {
|
|
4152
|
+
const isResponsesFamilyMessage =
|
|
4153
|
+
message.api === "openai-responses" || message.api === "openai-codex-responses";
|
|
4154
|
+
return {
|
|
4155
|
+
role: message.role,
|
|
4156
|
+
content:
|
|
4157
|
+
isResponsesFamilyMessage && Array.isArray(message.content)
|
|
4158
|
+
? message.content.flatMap(block => {
|
|
4159
|
+
if (block.type === "thinking") {
|
|
4160
|
+
return [];
|
|
4161
|
+
}
|
|
4162
|
+
if (block.type === "toolCall") {
|
|
4163
|
+
return [
|
|
4164
|
+
{
|
|
4165
|
+
type: block.type,
|
|
4166
|
+
id: block.id,
|
|
4167
|
+
name: block.name,
|
|
4168
|
+
arguments: block.arguments,
|
|
4169
|
+
},
|
|
4170
|
+
];
|
|
4171
|
+
}
|
|
4172
|
+
if (block.type === "text") {
|
|
4173
|
+
return [{ type: block.type, text: block.text, textSignature: block.textSignature }];
|
|
4174
|
+
}
|
|
4175
|
+
return [this.#normalizeProviderReplayValue(block)];
|
|
4176
|
+
})
|
|
4177
|
+
: this.#normalizeProviderReplayValue(message.content),
|
|
4178
|
+
api: message.api,
|
|
4179
|
+
provider: message.provider,
|
|
4180
|
+
model: message.model,
|
|
4181
|
+
stopReason: message.stopReason,
|
|
4182
|
+
errorMessage: message.errorMessage,
|
|
4183
|
+
providerPayload: isResponsesFamilyMessage ? undefined : message.providerPayload,
|
|
4184
|
+
};
|
|
4185
|
+
}
|
|
4186
|
+
case "toolResult":
|
|
4187
|
+
return {
|
|
4188
|
+
role: message.role,
|
|
4189
|
+
toolName: message.toolName,
|
|
4190
|
+
toolCallId: message.toolCallId,
|
|
4191
|
+
isError: message.isError,
|
|
4192
|
+
content: this.#normalizeProviderReplayValue(message.content),
|
|
4193
|
+
};
|
|
4194
|
+
case "bashExecution":
|
|
4195
|
+
return {
|
|
4196
|
+
role: message.role,
|
|
4197
|
+
command: message.command,
|
|
4198
|
+
output: message.output,
|
|
4199
|
+
exitCode: message.exitCode,
|
|
4200
|
+
cancelled: message.cancelled,
|
|
4201
|
+
meta: message.meta
|
|
4202
|
+
? {
|
|
4203
|
+
truncation: this.#normalizeProviderReplayValue(message.meta.truncation),
|
|
4204
|
+
limits: this.#normalizeProviderReplayValue(message.meta.limits),
|
|
4205
|
+
diagnostics: message.meta.diagnostics
|
|
4206
|
+
? this.#normalizeProviderReplayValue({
|
|
4207
|
+
summary: message.meta.diagnostics.summary,
|
|
4208
|
+
messages: message.meta.diagnostics.messages,
|
|
4209
|
+
})
|
|
4210
|
+
: undefined,
|
|
4211
|
+
}
|
|
4212
|
+
: undefined,
|
|
4213
|
+
excludeFromContext: message.excludeFromContext,
|
|
4214
|
+
};
|
|
4215
|
+
case "pythonExecution":
|
|
4216
|
+
return {
|
|
4217
|
+
role: message.role,
|
|
4218
|
+
code: message.code,
|
|
4219
|
+
output: message.output,
|
|
4220
|
+
exitCode: message.exitCode,
|
|
4221
|
+
cancelled: message.cancelled,
|
|
4222
|
+
meta: message.meta
|
|
4223
|
+
? {
|
|
4224
|
+
truncation: this.#normalizeProviderReplayValue(message.meta.truncation),
|
|
4225
|
+
limits: this.#normalizeProviderReplayValue(message.meta.limits),
|
|
4226
|
+
diagnostics: message.meta.diagnostics
|
|
4227
|
+
? this.#normalizeProviderReplayValue({
|
|
4228
|
+
summary: message.meta.diagnostics.summary,
|
|
4229
|
+
messages: message.meta.diagnostics.messages,
|
|
4230
|
+
})
|
|
4231
|
+
: undefined,
|
|
4232
|
+
}
|
|
4233
|
+
: undefined,
|
|
4234
|
+
excludeFromContext: message.excludeFromContext,
|
|
4235
|
+
};
|
|
4236
|
+
case "custom":
|
|
4237
|
+
case "hookMessage":
|
|
4238
|
+
return {
|
|
4239
|
+
role: message.role,
|
|
4240
|
+
customType: message.customType,
|
|
4241
|
+
content: this.#normalizeProviderReplayValue(message.content),
|
|
4242
|
+
};
|
|
4243
|
+
case "branchSummary":
|
|
4244
|
+
return { role: message.role, summary: message.summary };
|
|
4245
|
+
case "compactionSummary":
|
|
4246
|
+
return {
|
|
4247
|
+
role: message.role,
|
|
4248
|
+
summary: message.summary,
|
|
4249
|
+
providerPayload: message.providerPayload,
|
|
4250
|
+
};
|
|
4251
|
+
case "fileMention":
|
|
4252
|
+
return {
|
|
4253
|
+
role: message.role,
|
|
4254
|
+
files: message.files.map(file => ({
|
|
4255
|
+
path: file.path,
|
|
4256
|
+
content: file.content,
|
|
4257
|
+
image: file.image,
|
|
4258
|
+
})),
|
|
4259
|
+
};
|
|
4260
|
+
default:
|
|
4261
|
+
return this.#normalizeProviderReplayValue(message);
|
|
4101
4262
|
}
|
|
4263
|
+
}
|
|
4102
4264
|
|
|
4103
|
-
|
|
4265
|
+
#didSessionMessagesChange(previousMessages: AgentMessage[], nextMessages: AgentMessage[]): boolean {
|
|
4266
|
+
return (
|
|
4267
|
+
JSON.stringify(previousMessages.map(message => this.#normalizeSessionMessageForProviderReplay(message))) !==
|
|
4268
|
+
JSON.stringify(nextMessages.map(message => this.#normalizeSessionMessageForProviderReplay(message)))
|
|
4269
|
+
);
|
|
4104
4270
|
}
|
|
4105
4271
|
|
|
4106
4272
|
#getModelKey(model: Model): string {
|
|
@@ -5035,7 +5201,9 @@ export class AgentSession {
|
|
|
5035
5201
|
*/
|
|
5036
5202
|
async switchSession(sessionPath: string): Promise<boolean> {
|
|
5037
5203
|
const previousSessionFile = this.sessionManager.getSessionFile();
|
|
5038
|
-
|
|
5204
|
+
const switchingToDifferentSession = previousSessionFile
|
|
5205
|
+
? path.resolve(previousSessionFile) !== path.resolve(sessionPath)
|
|
5206
|
+
: true;
|
|
5039
5207
|
// Emit session_before_switch event (can be cancelled)
|
|
5040
5208
|
if (this.#extensionRunner?.hasHandlers("session_before_switch")) {
|
|
5041
5209
|
const result = (await this.#extensionRunner.emit({
|
|
@@ -5051,68 +5219,149 @@ export class AgentSession {
|
|
|
5051
5219
|
|
|
5052
5220
|
this.#disconnectFromAgent();
|
|
5053
5221
|
await this.abort();
|
|
5222
|
+
|
|
5223
|
+
// Flush pending writes before switching so restore snapshots reflect committed state.
|
|
5224
|
+
await this.sessionManager.flush();
|
|
5225
|
+
const previousSessionState = this.sessionManager.captureState();
|
|
5226
|
+
const previousSessionContext = this.sessionManager.buildSessionContext();
|
|
5227
|
+
// switchSession replaces these arrays wholesale during load/rollback, so retaining
|
|
5228
|
+
// the existing message objects is sufficient and avoids structured-clone failures for
|
|
5229
|
+
// extension/custom metadata that is valid to persist but not cloneable.
|
|
5230
|
+
const previousAgentMessages = [...this.agent.state.messages];
|
|
5231
|
+
const previousSteeringMessages = [...this.#steeringMessages];
|
|
5232
|
+
const previousFollowUpMessages = [...this.#followUpMessages];
|
|
5233
|
+
const previousPendingNextTurnMessages = [...this.#pendingNextTurnMessages];
|
|
5234
|
+
const previousScheduledHiddenNextTurnGeneration = this.#scheduledHiddenNextTurnGeneration;
|
|
5235
|
+
const previousModel = this.model;
|
|
5236
|
+
const previousThinkingLevel = this.#thinkingLevel;
|
|
5237
|
+
const previousServiceTier = this.agent.serviceTier;
|
|
5238
|
+
const previousSelectedMCPToolNames = new Set(this.#selectedMCPToolNames);
|
|
5239
|
+
const previousTools = [...this.agent.state.tools];
|
|
5240
|
+
const previousBaseSystemPrompt = this.#baseSystemPrompt;
|
|
5241
|
+
const previousSystemPrompt = this.agent.state.systemPrompt;
|
|
5242
|
+
const previousFallbackSelectedMCPToolNames = previousSessionFile
|
|
5243
|
+
? this.#getSessionDefaultSelectedMCPToolNames(previousSessionFile)
|
|
5244
|
+
: undefined;
|
|
5245
|
+
|
|
5054
5246
|
this.#steeringMessages = [];
|
|
5055
5247
|
this.#followUpMessages = [];
|
|
5056
5248
|
this.#pendingNextTurnMessages = [];
|
|
5057
5249
|
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
5058
5250
|
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
// Set new session
|
|
5063
|
-
await this.sessionManager.setSessionFile(sessionPath);
|
|
5064
|
-
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
5065
|
-
|
|
5066
|
-
// Reload messages
|
|
5067
|
-
const sessionContext = this.sessionManager.buildSessionContext();
|
|
5068
|
-
const fallbackSelectedMCPToolNames = this.#getSessionDefaultSelectedMCPToolNames(sessionPath);
|
|
5069
|
-
await this.#restoreMCPSelectionsForSessionContext(sessionContext, { fallbackSelectedMCPToolNames });
|
|
5251
|
+
try {
|
|
5252
|
+
await this.sessionManager.setSessionFile(sessionPath);
|
|
5253
|
+
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
5070
5254
|
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
});
|
|
5078
|
-
}
|
|
5255
|
+
const sessionContext = this.sessionManager.buildSessionContext();
|
|
5256
|
+
const didReloadConversationChange =
|
|
5257
|
+
!switchingToDifferentSession &&
|
|
5258
|
+
this.#didSessionMessagesChange(previousSessionContext.messages, sessionContext.messages);
|
|
5259
|
+
const fallbackSelectedMCPToolNames = this.#getSessionDefaultSelectedMCPToolNames(sessionPath);
|
|
5260
|
+
await this.#restoreMCPSelectionsForSessionContext(sessionContext, { fallbackSelectedMCPToolNames });
|
|
5079
5261
|
|
|
5080
|
-
|
|
5081
|
-
|
|
5262
|
+
// Emit session_switch event to hooks
|
|
5263
|
+
if (this.#extensionRunner) {
|
|
5264
|
+
await this.#extensionRunner.emit({
|
|
5265
|
+
type: "session_switch",
|
|
5266
|
+
reason: "resume",
|
|
5267
|
+
previousSessionFile,
|
|
5268
|
+
});
|
|
5269
|
+
}
|
|
5082
5270
|
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
if (
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5271
|
+
this.agent.replaceMessages(sessionContext.messages);
|
|
5272
|
+
this.#syncTodoPhasesFromBranch();
|
|
5273
|
+
if (switchingToDifferentSession) {
|
|
5274
|
+
this.#closeAllProviderSessions("session switch");
|
|
5275
|
+
} else if (didReloadConversationChange) {
|
|
5276
|
+
this.#closeAllProviderSessions("session reload");
|
|
5277
|
+
}
|
|
5278
|
+
|
|
5279
|
+
// Restore model if saved
|
|
5280
|
+
const defaultModelStr = sessionContext.models.default;
|
|
5281
|
+
if (defaultModelStr) {
|
|
5282
|
+
const slashIdx = defaultModelStr.indexOf("/");
|
|
5283
|
+
if (slashIdx > 0) {
|
|
5284
|
+
const provider = defaultModelStr.slice(0, slashIdx);
|
|
5285
|
+
const modelId = defaultModelStr.slice(slashIdx + 1);
|
|
5286
|
+
const availableModels = this.#modelRegistry.getAvailable();
|
|
5287
|
+
const match = availableModels.find(m => m.provider === provider && m.id === modelId);
|
|
5288
|
+
if (match) {
|
|
5289
|
+
const currentModel = this.model;
|
|
5290
|
+
const shouldResetProviderState =
|
|
5291
|
+
switchingToDifferentSession ||
|
|
5292
|
+
(currentModel !== undefined &&
|
|
5293
|
+
(currentModel.provider !== match.provider ||
|
|
5294
|
+
currentModel.id !== match.id ||
|
|
5295
|
+
currentModel.api !== match.api));
|
|
5296
|
+
if (shouldResetProviderState) {
|
|
5297
|
+
this.#setModelWithProviderSessionReset(match);
|
|
5298
|
+
} else {
|
|
5299
|
+
this.agent.setModel(match);
|
|
5300
|
+
}
|
|
5301
|
+
}
|
|
5094
5302
|
}
|
|
5095
5303
|
}
|
|
5096
|
-
}
|
|
5097
5304
|
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
this.
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
?
|
|
5112
|
-
: configuredServiceTier
|
|
5305
|
+
const hasThinkingEntry = this.sessionManager.getBranch().some(entry => entry.type === "thinking_level_change");
|
|
5306
|
+
const hasServiceTierEntry = this.sessionManager
|
|
5307
|
+
.getBranch()
|
|
5308
|
+
.some(entry => entry.type === "service_tier_change");
|
|
5309
|
+
const defaultThinkingLevel = this.settings.get("defaultThinkingLevel");
|
|
5310
|
+
const configuredServiceTier = this.settings.get("serviceTier");
|
|
5311
|
+
const nextThinkingLevel = resolveThinkingLevelForModel(
|
|
5312
|
+
this.model,
|
|
5313
|
+
hasThinkingEntry ? (sessionContext.thinkingLevel as ThinkingLevel | undefined) : defaultThinkingLevel,
|
|
5314
|
+
);
|
|
5315
|
+
this.#thinkingLevel = nextThinkingLevel;
|
|
5316
|
+
this.agent.setThinkingLevel(toReasoningEffort(nextThinkingLevel));
|
|
5317
|
+
this.agent.serviceTier = hasServiceTierEntry
|
|
5318
|
+
? sessionContext.serviceTier
|
|
5319
|
+
: configuredServiceTier === "none"
|
|
5320
|
+
? undefined
|
|
5321
|
+
: configuredServiceTier;
|
|
5113
5322
|
|
|
5114
|
-
|
|
5115
|
-
|
|
5323
|
+
this.#reconnectToAgent();
|
|
5324
|
+
return true;
|
|
5325
|
+
} catch (error) {
|
|
5326
|
+
this.sessionManager.restoreState(previousSessionState);
|
|
5327
|
+
this.agent.sessionId = previousSessionState.sessionId;
|
|
5328
|
+
let restoreMcpError: unknown;
|
|
5329
|
+
try {
|
|
5330
|
+
await this.#restoreMCPSelectionsForSessionContext(previousSessionContext, {
|
|
5331
|
+
fallbackSelectedMCPToolNames: previousFallbackSelectedMCPToolNames,
|
|
5332
|
+
});
|
|
5333
|
+
} catch (mcpError) {
|
|
5334
|
+
restoreMcpError = mcpError;
|
|
5335
|
+
logger.warn("Failed to restore MCP selections after switch error", {
|
|
5336
|
+
previousSessionFile,
|
|
5337
|
+
targetSessionFile: sessionPath,
|
|
5338
|
+
error: String(mcpError),
|
|
5339
|
+
});
|
|
5340
|
+
this.#selectedMCPToolNames = new Set(previousSelectedMCPToolNames);
|
|
5341
|
+
this.agent.setTools(previousTools);
|
|
5342
|
+
this.#baseSystemPrompt = previousBaseSystemPrompt;
|
|
5343
|
+
this.agent.setSystemPrompt(previousSystemPrompt);
|
|
5344
|
+
}
|
|
5345
|
+
this.#baseSystemPrompt = previousBaseSystemPrompt;
|
|
5346
|
+
this.agent.setSystemPrompt(previousSystemPrompt);
|
|
5347
|
+
this.agent.replaceMessages(previousAgentMessages);
|
|
5348
|
+
this.#steeringMessages = previousSteeringMessages;
|
|
5349
|
+
this.#followUpMessages = previousFollowUpMessages;
|
|
5350
|
+
this.#pendingNextTurnMessages = previousPendingNextTurnMessages;
|
|
5351
|
+
this.#scheduledHiddenNextTurnGeneration = previousScheduledHiddenNextTurnGeneration;
|
|
5352
|
+
if (previousModel) {
|
|
5353
|
+
this.agent.setModel(previousModel);
|
|
5354
|
+
}
|
|
5355
|
+
this.#thinkingLevel = previousThinkingLevel;
|
|
5356
|
+
this.agent.setThinkingLevel(toReasoningEffort(previousThinkingLevel));
|
|
5357
|
+
this.agent.serviceTier = previousServiceTier;
|
|
5358
|
+
this.#syncTodoPhasesFromBranch();
|
|
5359
|
+
this.#reconnectToAgent();
|
|
5360
|
+
if (restoreMcpError) {
|
|
5361
|
+
throw restoreMcpError;
|
|
5362
|
+
}
|
|
5363
|
+
throw error;
|
|
5364
|
+
}
|
|
5116
5365
|
}
|
|
5117
5366
|
|
|
5118
5367
|
/**
|
|
@@ -5124,7 +5373,10 @@ export class AgentSession {
|
|
|
5124
5373
|
* - selectedText: The text of the selected user message (for editor pre-fill)
|
|
5125
5374
|
* - cancelled: True if a hook cancelled the branch
|
|
5126
5375
|
*/
|
|
5127
|
-
async branch(entryId: string): Promise<{
|
|
5376
|
+
async branch(entryId: string): Promise<{
|
|
5377
|
+
selectedText: string;
|
|
5378
|
+
cancelled: boolean;
|
|
5379
|
+
}> {
|
|
5128
5380
|
const previousSessionFile = this.sessionFile;
|
|
5129
5381
|
const selectedEntry = this.sessionManager.getEntry(entryId);
|
|
5130
5382
|
|
|
@@ -5202,7 +5454,12 @@ export class AgentSession {
|
|
|
5202
5454
|
async navigateTree(
|
|
5203
5455
|
targetId: string,
|
|
5204
5456
|
options: { summarize?: boolean; customInstructions?: string } = {},
|
|
5205
|
-
): Promise<{
|
|
5457
|
+
): Promise<{
|
|
5458
|
+
editorText?: string;
|
|
5459
|
+
cancelled: boolean;
|
|
5460
|
+
aborted?: boolean;
|
|
5461
|
+
summaryEntry?: BranchSummaryEntry;
|
|
5462
|
+
}> {
|
|
5206
5463
|
const oldLeafId = this.sessionManager.getLeafId();
|
|
5207
5464
|
|
|
5208
5465
|
// No-op if already at target
|
package/src/session/messages.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
8
8
|
import type {
|
|
9
|
+
AssistantMessage,
|
|
9
10
|
ImageContent,
|
|
10
11
|
Message,
|
|
11
12
|
MessageAttribution,
|
|
@@ -213,6 +214,28 @@ export function createCompactionSummaryMessage(
|
|
|
213
214
|
};
|
|
214
215
|
}
|
|
215
216
|
|
|
217
|
+
export function sanitizeRehydratedOpenAIResponsesAssistantMessage(message: AssistantMessage): AssistantMessage {
|
|
218
|
+
if (message.providerPayload?.type !== "openaiResponsesHistory") {
|
|
219
|
+
return message;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let didSanitize = false;
|
|
223
|
+
const sanitizedContent = message.content.map(block => {
|
|
224
|
+
if (block.type !== "thinking" || block.thinkingSignature === undefined) {
|
|
225
|
+
return block;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
didSanitize = true;
|
|
229
|
+
return { ...block, thinkingSignature: undefined };
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (!didSanitize) {
|
|
233
|
+
return message;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return { ...message, content: sanitizedContent };
|
|
237
|
+
}
|
|
238
|
+
|
|
216
239
|
/** Convert CustomMessageEntry to AgentMessage format */
|
|
217
240
|
export function createCustomMessage(
|
|
218
241
|
customType: string,
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
type FileMentionMessage,
|
|
47
47
|
type HookMessage,
|
|
48
48
|
type PythonExecutionMessage,
|
|
49
|
+
sanitizeRehydratedOpenAIResponsesAssistantMessage,
|
|
49
50
|
} from "./messages";
|
|
50
51
|
import type { SessionStorage, SessionStorageWriter } from "./session-storage";
|
|
51
52
|
import { FileSessionStorage, MemorySessionStorage } from "./session-storage";
|
|
@@ -1374,6 +1375,15 @@ export async function resolveResumableSession(
|
|
|
1374
1375
|
|
|
1375
1376
|
return { session: globalMatch, scope: "global" };
|
|
1376
1377
|
}
|
|
1378
|
+
interface SessionManagerStateSnapshot {
|
|
1379
|
+
sessionId: string;
|
|
1380
|
+
sessionName: string | undefined;
|
|
1381
|
+
sessionFile: string | undefined;
|
|
1382
|
+
flushed: boolean;
|
|
1383
|
+
needsFullRewriteOnNextPersist: boolean;
|
|
1384
|
+
fileEntries: FileEntry[];
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1377
1387
|
export class SessionManager {
|
|
1378
1388
|
#sessionId: string = "";
|
|
1379
1389
|
#sessionName: string | undefined;
|
|
@@ -1419,6 +1429,39 @@ export class SessionManager {
|
|
|
1419
1429
|
return this.#blobStore.put(data);
|
|
1420
1430
|
}
|
|
1421
1431
|
|
|
1432
|
+
captureState(): SessionManagerStateSnapshot {
|
|
1433
|
+
return {
|
|
1434
|
+
sessionId: this.#sessionId,
|
|
1435
|
+
sessionName: this.#sessionName,
|
|
1436
|
+
sessionFile: this.#sessionFile,
|
|
1437
|
+
flushed: this.#flushed,
|
|
1438
|
+
needsFullRewriteOnNextPersist: this.#needsFullRewriteOnNextPersist,
|
|
1439
|
+
// Snapshot entry objects by reference: switch/reload replaces the active entry array,
|
|
1440
|
+
// so rollback does not need structured cloning of extension/custom details.
|
|
1441
|
+
fileEntries: [...this.#fileEntries],
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
restoreState(snapshot: SessionManagerStateSnapshot): void {
|
|
1446
|
+
this.#sessionId = snapshot.sessionId;
|
|
1447
|
+
this.#sessionName = snapshot.sessionName;
|
|
1448
|
+
this.#sessionFile = snapshot.sessionFile;
|
|
1449
|
+
this.#flushed = snapshot.flushed;
|
|
1450
|
+
this.#needsFullRewriteOnNextPersist = snapshot.needsFullRewriteOnNextPersist;
|
|
1451
|
+
this.#fileEntries = [...snapshot.fileEntries];
|
|
1452
|
+
this.#persistWriter = undefined;
|
|
1453
|
+
this.#persistWriterPath = undefined;
|
|
1454
|
+
this.#persistChain = Promise.resolve();
|
|
1455
|
+
this.#persistError = undefined;
|
|
1456
|
+
this.#persistErrorReported = false;
|
|
1457
|
+
this.#artifactManager = null;
|
|
1458
|
+
this.#artifactManagerSessionFile = null;
|
|
1459
|
+
this.#buildIndex();
|
|
1460
|
+
if (this.#sessionFile) {
|
|
1461
|
+
writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1422
1465
|
/** Initialize with a specific session file (used by factory methods) */
|
|
1423
1466
|
async #initSessionFile(sessionFile: string): Promise<void> {
|
|
1424
1467
|
await this.setSessionFile(sessionFile);
|
|
@@ -1445,6 +1488,7 @@ export class SessionManager {
|
|
|
1445
1488
|
this.#needsFullRewriteOnNextPersist = migrateToCurrentVersion(this.#fileEntries);
|
|
1446
1489
|
|
|
1447
1490
|
await resolveBlobRefsInEntries(this.#fileEntries, this.#blobStore);
|
|
1491
|
+
this.sanitizeLoadedOpenAIResponsesReplayMetadata();
|
|
1448
1492
|
|
|
1449
1493
|
this.#buildIndex();
|
|
1450
1494
|
this.#flushed = true;
|
|
@@ -2300,6 +2344,26 @@ export class SessionManager {
|
|
|
2300
2344
|
return buildSessionContext(this.getEntries(), this.#leafId, this.#byId);
|
|
2301
2345
|
}
|
|
2302
2346
|
|
|
2347
|
+
/** Strip stale OpenAI Responses assistant replay metadata from loaded in-memory entries. */
|
|
2348
|
+
sanitizeLoadedOpenAIResponsesReplayMetadata(): boolean {
|
|
2349
|
+
let didSanitize = false;
|
|
2350
|
+
for (const entry of this.#fileEntries) {
|
|
2351
|
+
if (entry.type !== "message" || entry.message.role !== "assistant") {
|
|
2352
|
+
continue;
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
const sanitizedMessage = sanitizeRehydratedOpenAIResponsesAssistantMessage(entry.message);
|
|
2356
|
+
if (sanitizedMessage === entry.message) {
|
|
2357
|
+
continue;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
entry.message = sanitizedMessage;
|
|
2361
|
+
didSanitize = true;
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
return didSanitize;
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2303
2367
|
/**
|
|
2304
2368
|
* Get session header.
|
|
2305
2369
|
*/
|
|
@@ -2548,6 +2612,7 @@ export class SessionManager {
|
|
|
2548
2612
|
newHeader.title = sourceHeader?.title;
|
|
2549
2613
|
manager.#fileEntries = [newHeader, ...historyEntries];
|
|
2550
2614
|
manager.#sessionName = newHeader.title;
|
|
2615
|
+
manager.sanitizeLoadedOpenAIResponsesReplayMetadata();
|
|
2551
2616
|
manager.#buildIndex();
|
|
2552
2617
|
await manager.#rewriteFile();
|
|
2553
2618
|
return manager;
|
package/src/utils/image-input.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
+
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import { formatBytes } from "@oh-my-pi/pi-utils";
|
|
3
4
|
import { resolveReadPath } from "../tools/path-utils";
|
|
5
|
+
import { convertToPng } from "./image-convert";
|
|
4
6
|
import { formatDimensionNote, resizeImage } from "./image-resize";
|
|
5
7
|
import { detectSupportedImageMimeTypeFromFile } from "./mime";
|
|
6
8
|
|
|
7
9
|
export const MAX_IMAGE_INPUT_BYTES = 20 * 1024 * 1024;
|
|
8
10
|
const MAX_IMAGE_METADATA_HEADER_BYTES = 256 * 1024;
|
|
9
|
-
|
|
11
|
+
export const SUPPORTED_INPUT_IMAGE_MIME_TYPES = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
|
|
10
12
|
export interface ImageMetadata {
|
|
11
13
|
mimeType: string;
|
|
12
14
|
bytes: number;
|
|
@@ -25,6 +27,14 @@ export interface LoadedImageInput {
|
|
|
25
27
|
bytes: number;
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
export async function ensureSupportedImageInput(image: ImageContent): Promise<ImageContent | null> {
|
|
31
|
+
if (SUPPORTED_INPUT_IMAGE_MIME_TYPES.has(image.mimeType)) {
|
|
32
|
+
return image;
|
|
33
|
+
}
|
|
34
|
+
const converted = await convertToPng(image.data, image.mimeType);
|
|
35
|
+
return converted ? { type: "image", data: converted.data, mimeType: converted.mimeType } : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
export interface ReadImageMetadataOptions {
|
|
29
39
|
path: string;
|
|
30
40
|
cwd: string;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Returns synthesized answers with web search sources.
|
|
7
7
|
*/
|
|
8
8
|
import * as os from "node:os";
|
|
9
|
-
import { getAgentDbPath, readSseJson } from "@oh-my-pi/pi-utils";
|
|
9
|
+
import { $env, getAgentDbPath, readSseJson } from "@oh-my-pi/pi-utils";
|
|
10
10
|
import packageJson from "../../../../package.json" with { type: "json" };
|
|
11
11
|
import { AgentStorage } from "../../../session/agent-storage";
|
|
12
12
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
@@ -21,6 +21,11 @@ const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
|
21
21
|
const DEFAULT_INSTRUCTIONS =
|
|
22
22
|
"You are a helpful assistant with web search capabilities. Search the web to answer the user's question accurately and cite your sources.";
|
|
23
23
|
|
|
24
|
+
function getModel(): string {
|
|
25
|
+
const configuredModel = $env.PI_CODEX_WEB_SEARCH_MODEL?.trim();
|
|
26
|
+
return configuredModel ? configuredModel : DEFAULT_MODEL;
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
export interface CodexSearchParams {
|
|
25
30
|
signal?: AbortSignal;
|
|
26
31
|
query: string;
|
|
@@ -188,8 +193,10 @@ async function callCodexSearch(
|
|
|
188
193
|
const url = `${CODEX_BASE_URL}${CODEX_RESPONSES_PATH}`;
|
|
189
194
|
const headers = buildCodexHeaders(auth.accessToken, auth.accountId);
|
|
190
195
|
|
|
196
|
+
const requestedModel = getModel();
|
|
197
|
+
|
|
191
198
|
const body: Record<string, unknown> = {
|
|
192
|
-
model:
|
|
199
|
+
model: requestedModel,
|
|
193
200
|
stream: true,
|
|
194
201
|
store: false,
|
|
195
202
|
input: [
|
|
@@ -226,7 +233,7 @@ async function callCodexSearch(
|
|
|
226
233
|
// Parse SSE stream
|
|
227
234
|
const answerParts: string[] = [];
|
|
228
235
|
const sources: SearchSource[] = [];
|
|
229
|
-
let model =
|
|
236
|
+
let model = requestedModel;
|
|
230
237
|
let requestId = "";
|
|
231
238
|
let usage: { inputTokens: number; outputTokens: number; totalTokens: number } | undefined;
|
|
232
239
|
|