fix(agent): restore streamFn extension compatibility

Keep streamFn required for typed callers while preserving the legacy runtime fallback for extensions that omit it.\n\nfixes #6915
This commit is contained in:
Mario Zechner
2026-07-21 18:34:56 +02:00
parent b142504125
commit b9e5c5d941
26 changed files with 210 additions and 88 deletions
+12
View File
@@ -2,9 +2,21 @@
## [Unreleased]
### New Features
- **Verifiable release source archives** — GitHub releases now include deterministic, checksummed source archives with instructions for rebuilding standalone binaries. See [Building standalone binaries from release source](../../README.md#building-standalone-binaries-from-release-source).
- **Resilient compaction and branch summaries** — Transient provider failures now follow the configured retry policy, with retry lifecycle events available to interactive, JSON, RPC, and SDK consumers. See [Compaction & Branch Summarization](docs/compaction.md) and [RPC retry events](docs/rpc.md#summarization_retry_scheduled--summarization_retry_attempt_start--summarization_retry_finished).
### Added
- Added deterministic, checksummed source archives to GitHub releases with documented standalone binary rebuild instructions ([#6913](https://github.com/earendil-works/pi/pull/6913) by [@christianklotz](https://github.com/christianklotz)).
### Fixed
- Fixed compaction and branch summarization to retry transient provider failures using the configured retry policy, with retry lifecycle events exposed to interactive, JSON, RPC, and SDK consumers ([#6901](https://github.com/earendil-works/pi/pull/6901) by [@davidbrai](https://github.com/davidbrai)).
- Fixed interactive startup waiting for background model catalog refresh while computing the footer provider count.
- Restored the default stream fallback for extensions using the pre-0.81 agent-core API ([#6915](https://github.com/earendil-works/pi/issues/6915)).
- Fixed inherited Kimi K3 models from Moonshot AI and Moonshot AI China to use the OpenAI thinking format and expose reasoning effort support.
## [0.81.0] - 2026-07-21
+8 -3
View File
@@ -1,6 +1,6 @@
import { join } from "node:path";
import { Agent, type AgentMessage, type ThinkingLevel } from "@earendil-works/pi-agent-core";
import { clampThinkingLevel, type Message, type Model } from "@earendil-works/pi-ai/compat";
import { Agent, type AgentMessage, setDefaultStreamFn, type ThinkingLevel } from "@earendil-works/pi-agent-core";
import { clampThinkingLevel, type Message, type Model, streamSimple } from "@earendil-works/pi-ai/compat";
import { getAgentDir } from "../config.ts";
import { resolvePath } from "../utils/paths.ts";
import { AgentSession } from "./agent-session.ts";
@@ -30,6 +30,11 @@ import {
withFileMutationQueue,
} from "./tools/index.ts";
// Preserve the pre-0.81 fallback for extensions that construct Agent instances
// or invoke low-level agent loops without supplying streamFn. Agent core remains
// provider-agnostic and does not import pi-ai/compat itself.
setDefaultStreamFn(streamSimple);
export interface CreateAgentSessionOptions {
/** Working directory for project-local discovery. Default: process.cwd() */
cwd?: string;
@@ -294,7 +299,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
tools: [],
},
convertToLlm: convertToLlmWithBlockImages,
streamFunction: async (model, context, options) => {
streamFn: async (model, context, options) => {
const providerRetrySettings = settingsManager.getProviderRetrySettings();
const httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();
// SDKs treat timeout=0 as 0ms (immediate timeout), not "no timeout".
@@ -24,7 +24,7 @@ describe("AgentSession auto-compaction queue resume", () => {
const model = getModel("anthropic", "claude-sonnet-4-5")!;
const agent = new Agent({
streamFunction: streamSimple,
streamFn: streamSimple,
initialState: {
model,
systemPrompt: "Test",
@@ -49,7 +49,7 @@ describe.skipIf(!API_KEY)("AgentSession compaction e2e", () => {
const model = getModel("anthropic", "claude-sonnet-4-5")!;
const agent = new Agent({
getApiKey: () => API_KEY,
streamFunction: streamSimple,
streamFn: streamSimple,
initialState: {
model,
systemPrompt: "You are a helpful assistant. Be concise.",
@@ -90,7 +90,7 @@ describe("AgentSession concurrent prompt guard", () => {
systemPrompt: "Test",
tools: [],
},
streamFunction: (_model, _context, options) => {
streamFn: (_model, _context, options) => {
abortSignal = options?.signal;
const stream = new MockAssistantStream();
queueMicrotask(() => {
@@ -195,7 +195,7 @@ describe("AgentSession concurrent prompt guard", () => {
systemPrompt: "Test",
tools: [],
},
streamFunction: (_model, context, options) => {
streamFn: (_model, context, options) => {
abortSignal = options?.signal;
const stream = new MockAssistantStream();
queueMicrotask(() => {
@@ -301,7 +301,7 @@ describe("AgentSession concurrent prompt guard", () => {
systemPrompt: "Test",
tools: [],
},
streamFunction: () => {
streamFn: () => {
const stream = new MockAssistantStream();
queueMicrotask(() => {
stream.push({ type: "start", partial: createAssistantMessage("") });
@@ -362,7 +362,7 @@ describe("AgentSession concurrent prompt guard", () => {
systemPrompt: "Test",
tools: [tool],
},
streamFunction: async (_model, context) => {
streamFn: async (_model, context) => {
const stream = new MockAssistantStream();
queueMicrotask(() => {
const toolResultCount = context.messages.filter((message) => message.role === "toolResult").length;
@@ -508,7 +508,7 @@ describe("AgentSession concurrent prompt guard", () => {
systemPrompt: "Test",
tools: [tool],
},
streamFunction: async (_model, context) => {
streamFn: async (_model, context) => {
const stream = new MockAssistantStream();
queueMicrotask(() => {
const hasToolResult = context.messages.some((message) => message.role === "toolResult");
@@ -82,7 +82,7 @@ describe("AgentSession retry", () => {
const agent = new Agent({
getApiKey: () => "test-key",
initialState: { model, systemPrompt: "Test", tools: [] },
streamFunction: () => {
streamFn: () => {
callCount++;
const stream = new MockAssistantStream();
queueMicrotask(() => {
@@ -203,7 +203,7 @@ describe("AgentSession retry", () => {
const agent = new Agent({
getApiKey: () => "test-key",
initialState: { model, systemPrompt: "Test", tools: [] },
streamFunction: streamFn,
streamFn: streamFn,
});
const sessionManager = SessionManager.inMemory();
const settingsManager = SettingsManager.create(tempDir, tempDir);
@@ -255,7 +255,7 @@ describe("AgentSession retry", () => {
const agent = new Agent({
getApiKey: () => "test-key",
initialState: { model, systemPrompt: "Test", tools: [] },
streamFunction: () => {
streamFn: () => {
callCount++;
const stream = new MockAssistantStream();
queueMicrotask(() => {
@@ -75,7 +75,7 @@ async function createSession() {
const session = new AgentSession({
agent: new Agent({
getApiKey: () => "test-key",
streamFunction: streamSimple,
streamFn: streamSimple,
initialState: {
model,
systemPrompt: "You are a helpful assistant.",
@@ -89,7 +89,7 @@ describe.skipIf(!API_KEY)("Compaction extensions", () => {
const model = getModel("anthropic", "claude-sonnet-4-5")!;
const agent = new Agent({
getApiKey: () => API_KEY,
streamFunction: streamSimple,
streamFn: streamSimple,
initialState: {
model,
systemPrompt: "You are a helpful assistant. Be concise.",
@@ -114,7 +114,7 @@ async function createRuntimeHost(options: { withAuth: boolean; responseDelayMs:
systemPrompt: "Test",
tools: [],
},
streamFunction: (_model, _context, _options) => {
streamFn: (_model, _context, _options) => {
const stream = new MockAssistantStream();
queueMicrotask(() => {
stream.push({ type: "start", partial: createAssistantMessage("") });
+1 -1
View File
@@ -137,7 +137,7 @@ export async function createHarness(options: HarnessOptions = {}): Promise<Harne
const agent = new Agent({
getApiKey: () => (withConfiguredAuth ? "faux-key" : undefined),
streamFunction: streamSimple,
streamFn: streamSimple,
initialState: {
model,
systemPrompt: options.systemPrompt ?? "You are a test assistant.",
@@ -61,7 +61,7 @@ describe("regression #5596: missing configured theme export", () => {
tools: [],
},
convertToLlm,
streamFunction: streamSimple,
streamFn: streamSimple,
});
const session = new AgentSession({
agent,
+1 -1
View File
@@ -378,7 +378,7 @@ async function createHarnessWithResourceLoader(
systemPrompt: options.systemPrompt ?? "You are a test assistant.",
tools: options.tools ?? [],
},
streamFunction: streamFn,
streamFn: streamFn,
});
const sessionManager = SessionManager.inMemory();
+1 -1
View File
@@ -246,7 +246,7 @@ export async function createTestSession(options: TestSessionOptions = {}): Promi
systemPrompt: options.systemPrompt ?? "You are a helpful assistant. Be extremely concise.",
tools: createCodingTools(process.cwd()),
},
streamFunction: streamSimple,
streamFn: streamSimple,
});
const sessionManager = options.inMemory ? SessionManager.inMemory() : SessionManager.create(tempDir);