Merge PR #8985: fix(sdk): thread response model into after_provider_response context (@roboomp)
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
- Fixed revived subagents (warm lifecycle reviver and cold persisted reviver) rebuilding the session without initializing the extension runtime, leaving every runtime action throwing `ExtensionRuntimeNotInitializedError`. An extension with a `tool_call` handler that touched a runtime action (e.g. `appendEntry`) then tripped the fail-closed gate in `emitToolCall` and blocked every tool — including the hidden `yield` — so the revived agent could neither finish nor exit and looped until killed. Both revivers now call the shared `initializeExtensions` helper, restoring runtime actions, `onError`, and the `session_start` event ([#8824](https://github.com/can1357/oh-my-pi/issues/8824)).
|
||||
- Fixed `omp commit` split-commit crashing with a misleading `No diff found for <path>` when a staged binary (or any payload) pushed `git diff --cached --binary` past the 8 MiB subprocess output cap. The capture is truncated silently, so files sorting after the binary vanished from the parsed diff; the split flow now requests a complete diff and fails fast naming the real cause instead ([#8897](https://github.com/can1357/oh-my-pi/issues/8897)).
|
||||
- Fixed a mid-run compaction being misread as a phantom overflow: after a compaction rebased the in-flight context snapshot, `getContextBreakdown` used message position (`anchorIndex >= cutoffCount`) as a freshness proxy, so an in-flight provider response whose request predated the compaction out-ranked the rebased estimate and reported the pre-compaction token count (~2.6x the real one). This tripped the "Compaction freed too little context to make progress" guard and drove the frame-rescue path on a byte-identical `tokensBefore`. Assistant context snapshots now carry a monotonic compaction epoch, and a post-cutoff anchor whose epoch predates the last compaction is no longer trusted over the rebased estimate ([#8887](https://github.com/can1357/oh-my-pi/issues/8887)).
|
||||
- Fixed `after_provider_response` extension handlers receiving the primary session model in `ctx.model` and `ctx.models.current()` for cross-provider side requests. `ExtensionRunner.emitAfterProviderResponse` accepted the response model but discarded it, so a handler revoking a credential on an HTTP 402 could target the wrong provider. It now threads the response model into the context, matching `emitBeforeProviderRequest` ([#8955](https://github.com/can1357/oh-my-pi/issues/8955)).
|
||||
- Fixed task and eval subagents discovering newly added agent definitions while resolving their role aliases from stale startup settings. Subagent preflight now atomically reloads persisted settings before agent discovery while preserving live runtime overrides.
|
||||
- Fixed images returned by tools mounted under `xd://` rendering only as file links instead of inline terminal graphics.
|
||||
- Resume Cursor idle-stall turns after completed MCP/todo tool results. The watchdog already closes the Connect stream, so unmarked blocks no longer need the `exec-resolved` marker to continue.
|
||||
|
||||
@@ -1665,8 +1665,9 @@ export class ExtensionRunner {
|
||||
return currentPayload;
|
||||
}
|
||||
|
||||
async emitAfterProviderResponse(response: ProviderResponseMetadata, _model?: Model): Promise<void> {
|
||||
const ctx = this.createContext();
|
||||
/** Runs response hooks with the model that produced that provider response. */
|
||||
async emitAfterProviderResponse(response: ProviderResponseMetadata, model?: Model): Promise<void> {
|
||||
const ctx = this.createContext(model);
|
||||
|
||||
for (const ext of this.extensions) {
|
||||
const handlers = ext.handlers.get("after_provider_response");
|
||||
|
||||
@@ -761,6 +761,80 @@ describe("ExtensionRunner", () => {
|
||||
expect(errors[0]?.event).toBe("after_provider_response");
|
||||
expect(errors[0]?.error).toContain("response failed");
|
||||
});
|
||||
|
||||
it("exposes the response model instead of the primary session model", async () => {
|
||||
const primaryModel = getBundledModel("openai-codex", "gpt-5.6-sol");
|
||||
const requestModel = getBundledModel("anthropic", "claude-sonnet-4-5");
|
||||
if (!primaryModel || !requestModel) throw new Error("Expected bundled cross-provider models to exist");
|
||||
|
||||
const eventsPath = path.join(tempDir.path(), "after-provider-response-model.jsonl");
|
||||
const extCode = `
|
||||
import * as fs from "node:fs";
|
||||
|
||||
export default function(pi) {
|
||||
pi.on("after_provider_response", async (_event, ctx) => {
|
||||
const current = ctx.models.current();
|
||||
fs.appendFileSync(
|
||||
${JSON.stringify(eventsPath)},
|
||||
JSON.stringify({
|
||||
model: ctx.model && { provider: ctx.model.provider, id: ctx.model.id },
|
||||
current: current && { provider: current.provider, id: current.id },
|
||||
}) + "\\n",
|
||||
);
|
||||
});
|
||||
}
|
||||
`;
|
||||
fs.writeFileSync(path.join(extensionsDir, "after-response-model.ts"), extCode);
|
||||
|
||||
const result = await loadTestExtensions();
|
||||
const runner = new ExtensionRunner(
|
||||
result.extensions,
|
||||
result.runtime,
|
||||
tempDir.path(),
|
||||
sessionManager,
|
||||
modelRegistry,
|
||||
);
|
||||
runner.initialize(
|
||||
{
|
||||
sendMessage: () => {},
|
||||
sendUserMessage: () => {},
|
||||
appendEntry: () => {},
|
||||
setLabel: () => {},
|
||||
getActiveTools: () => [],
|
||||
getAllTools: () => [],
|
||||
setActiveTools: async () => {},
|
||||
getCommands: () => [],
|
||||
setModel: async () => false,
|
||||
getThinkingLevel: () => undefined,
|
||||
setThinkingLevel: () => {},
|
||||
getSessionName: () => undefined,
|
||||
setSessionName: async () => {},
|
||||
},
|
||||
{
|
||||
getModel: () => primaryModel,
|
||||
isIdle: () => true,
|
||||
abort: () => {},
|
||||
hasPendingMessages: () => false,
|
||||
shutdown: () => {},
|
||||
getContextUsage: () => undefined,
|
||||
compact: async () => {},
|
||||
getSystemPrompt: () => [],
|
||||
},
|
||||
);
|
||||
|
||||
await runner.emitAfterProviderResponse(
|
||||
{ status: 402, headers: {}, requestId: "req_402", metadata: { provider: requestModel.provider } },
|
||||
requestModel,
|
||||
);
|
||||
|
||||
const expected = { provider: requestModel.provider, id: requestModel.id };
|
||||
const events = fs
|
||||
.readFileSync(eventsPath, "utf8")
|
||||
.trim()
|
||||
.split("\n")
|
||||
.map(line => JSON.parse(line));
|
||||
expect(events).toEqual([{ model: expected, current: expected }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("session_stop", () => {
|
||||
|
||||
Reference in New Issue
Block a user