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:
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added retry policy support and lifecycle events for compaction and branch-summary operations in `AgentHarness` ([#6901](https://github.com/earendil-works/pi/pull/6901) by [@davidbrai](https://github.com/davidbrai)).
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Restored the `Agent` `streamFn` option and host-configurable fallback for omitted agent-loop stream functions without reintroducing a `pi-ai/compat` dependency ([#6915](https://github.com/earendil-works/pi/issues/6915)).
|
||||||
|
|
||||||
## [0.81.0] - 2026-07-21
|
## [0.81.0] - 2026-07-21
|
||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const agent = new Agent({
|
|||||||
systemPrompt: "You are a helpful assistant.",
|
systemPrompt: "You are a helpful assistant.",
|
||||||
model,
|
model,
|
||||||
},
|
},
|
||||||
streamFunction: models.streamSimple.bind(models),
|
streamFn: models.streamSimple.bind(models),
|
||||||
});
|
});
|
||||||
|
|
||||||
agent.subscribe((event) => {
|
agent.subscribe((event) => {
|
||||||
@@ -199,7 +199,7 @@ const agent = new Agent({
|
|||||||
followUpMode: "one-at-a-time",
|
followUpMode: "one-at-a-time",
|
||||||
|
|
||||||
// Required stream function
|
// Required stream function
|
||||||
streamFunction: models.streamSimple.bind(models),
|
streamFn: models.streamSimple.bind(models),
|
||||||
|
|
||||||
// Session ID for provider caching
|
// Session ID for provider caching
|
||||||
sessionId: "session-123",
|
sessionId: "session-123",
|
||||||
@@ -386,7 +386,7 @@ Handle custom types in `convertToLlm`:
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: models.streamSimple.bind(models),
|
streamFn: models.streamSimple.bind(models),
|
||||||
convertToLlm: (messages) => messages.flatMap(m => {
|
convertToLlm: (messages) => messages.flatMap(m => {
|
||||||
if (m.role === "notification") return []; // Filter out
|
if (m.role === "notification") return []; // Filter out
|
||||||
return [m];
|
return [m];
|
||||||
@@ -457,7 +457,7 @@ For browser apps that proxy through a backend:
|
|||||||
import { Agent, streamProxy } from "@earendil-works/pi-agent-core";
|
import { Agent, streamProxy } from "@earendil-works/pi-agent-core";
|
||||||
|
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: (model, context, options) =>
|
streamFn: (model, context, options) =>
|
||||||
streamProxy(model, context, {
|
streamProxy(model, context, {
|
||||||
...options,
|
...options,
|
||||||
authToken: "...",
|
authToken: "...",
|
||||||
@@ -489,13 +489,13 @@ const config: AgentLoopConfig = {
|
|||||||
|
|
||||||
const userMessage = { role: "user", content: "Hello", timestamp: Date.now() };
|
const userMessage = { role: "user", content: "Hello", timestamp: Date.now() };
|
||||||
|
|
||||||
const streamFunction = models.streamSimple.bind(models);
|
const streamFn = models.streamSimple.bind(models);
|
||||||
for await (const event of agentLoop([userMessage], context, config, undefined, streamFunction)) {
|
for await (const event of agentLoop([userMessage], context, config, undefined, streamFn)) {
|
||||||
console.log(event.type);
|
console.log(event.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue from existing context
|
// Continue from existing context
|
||||||
for await (const event of agentLoopContinue(context, config, undefined, streamFunction)) {
|
for await (const event of agentLoopContinue(context, config, undefined, streamFn)) {
|
||||||
console.log(event.type);
|
console.log(event.type);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
type ToolResultMessage,
|
type ToolResultMessage,
|
||||||
validateToolArguments,
|
validateToolArguments,
|
||||||
} from "@earendil-works/pi-ai";
|
} from "@earendil-works/pi-ai";
|
||||||
|
import { getDefaultStreamFn } from "./stream-fn.ts";
|
||||||
import type {
|
import type {
|
||||||
AgentContext,
|
AgentContext,
|
||||||
AgentEvent,
|
AgentEvent,
|
||||||
@@ -32,7 +33,7 @@ export function agentLoop(
|
|||||||
context: AgentContext,
|
context: AgentContext,
|
||||||
config: AgentLoopConfig,
|
config: AgentLoopConfig,
|
||||||
signal: AbortSignal | undefined,
|
signal: AbortSignal | undefined,
|
||||||
streamFunction: StreamFn,
|
streamFn: StreamFn,
|
||||||
): EventStream<AgentEvent, AgentMessage[]> {
|
): EventStream<AgentEvent, AgentMessage[]> {
|
||||||
const stream = createAgentStream();
|
const stream = createAgentStream();
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ export function agentLoop(
|
|||||||
stream.push(event);
|
stream.push(event);
|
||||||
},
|
},
|
||||||
signal,
|
signal,
|
||||||
streamFunction,
|
streamFn,
|
||||||
).then((messages) => {
|
).then((messages) => {
|
||||||
stream.end(messages);
|
stream.end(messages);
|
||||||
});
|
});
|
||||||
@@ -64,7 +65,7 @@ export function agentLoopContinue(
|
|||||||
context: AgentContext,
|
context: AgentContext,
|
||||||
config: AgentLoopConfig,
|
config: AgentLoopConfig,
|
||||||
signal: AbortSignal | undefined,
|
signal: AbortSignal | undefined,
|
||||||
streamFunction: StreamFn,
|
streamFn: StreamFn,
|
||||||
): EventStream<AgentEvent, AgentMessage[]> {
|
): EventStream<AgentEvent, AgentMessage[]> {
|
||||||
if (context.messages.length === 0) {
|
if (context.messages.length === 0) {
|
||||||
throw new Error("Cannot continue: no messages in context");
|
throw new Error("Cannot continue: no messages in context");
|
||||||
@@ -83,7 +84,7 @@ export function agentLoopContinue(
|
|||||||
stream.push(event);
|
stream.push(event);
|
||||||
},
|
},
|
||||||
signal,
|
signal,
|
||||||
streamFunction,
|
streamFn,
|
||||||
).then((messages) => {
|
).then((messages) => {
|
||||||
stream.end(messages);
|
stream.end(messages);
|
||||||
});
|
});
|
||||||
@@ -97,7 +98,7 @@ export async function runAgentLoop(
|
|||||||
config: AgentLoopConfig,
|
config: AgentLoopConfig,
|
||||||
emit: AgentEventSink,
|
emit: AgentEventSink,
|
||||||
signal: AbortSignal | undefined,
|
signal: AbortSignal | undefined,
|
||||||
streamFunction: StreamFn,
|
streamFn: StreamFn,
|
||||||
): Promise<AgentMessage[]> {
|
): Promise<AgentMessage[]> {
|
||||||
const newMessages: AgentMessage[] = [...prompts];
|
const newMessages: AgentMessage[] = [...prompts];
|
||||||
const currentContext: AgentContext = {
|
const currentContext: AgentContext = {
|
||||||
@@ -112,7 +113,7 @@ export async function runAgentLoop(
|
|||||||
await emit({ type: "message_end", message: prompt });
|
await emit({ type: "message_end", message: prompt });
|
||||||
}
|
}
|
||||||
|
|
||||||
await runLoop(currentContext, newMessages, config, signal, emit, streamFunction);
|
await runLoop(currentContext, newMessages, config, signal, emit, streamFn ?? getDefaultStreamFn());
|
||||||
return newMessages;
|
return newMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +122,7 @@ export async function runAgentLoopContinue(
|
|||||||
config: AgentLoopConfig,
|
config: AgentLoopConfig,
|
||||||
emit: AgentEventSink,
|
emit: AgentEventSink,
|
||||||
signal: AbortSignal | undefined,
|
signal: AbortSignal | undefined,
|
||||||
streamFunction: StreamFn,
|
streamFn: StreamFn,
|
||||||
): Promise<AgentMessage[]> {
|
): Promise<AgentMessage[]> {
|
||||||
if (context.messages.length === 0) {
|
if (context.messages.length === 0) {
|
||||||
throw new Error("Cannot continue: no messages in context");
|
throw new Error("Cannot continue: no messages in context");
|
||||||
@@ -137,7 +138,7 @@ export async function runAgentLoopContinue(
|
|||||||
await emit({ type: "agent_start" });
|
await emit({ type: "agent_start" });
|
||||||
await emit({ type: "turn_start" });
|
await emit({ type: "turn_start" });
|
||||||
|
|
||||||
await runLoop(currentContext, newMessages, config, signal, emit, streamFunction);
|
await runLoop(currentContext, newMessages, config, signal, emit, streamFn ?? getDefaultStreamFn());
|
||||||
return newMessages;
|
return newMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+22
-19
@@ -8,6 +8,7 @@ import type {
|
|||||||
Transport,
|
Transport,
|
||||||
} from "@earendil-works/pi-ai";
|
} from "@earendil-works/pi-ai";
|
||||||
import { runAgentLoop, runAgentLoopContinue } from "./agent-loop.ts";
|
import { runAgentLoop, runAgentLoopContinue } from "./agent-loop.ts";
|
||||||
|
import { getDefaultStreamFn } from "./stream-fn.ts";
|
||||||
import type {
|
import type {
|
||||||
AfterToolCallContext,
|
AfterToolCallContext,
|
||||||
AfterToolCallResult,
|
AfterToolCallResult,
|
||||||
@@ -97,7 +98,7 @@ export interface AgentOptions {
|
|||||||
initialState?: Partial<Omit<AgentState, "pendingToolCalls" | "isStreaming" | "streamingMessage" | "errorMessage">>;
|
initialState?: Partial<Omit<AgentState, "pendingToolCalls" | "isStreaming" | "streamingMessage" | "errorMessage">>;
|
||||||
convertToLlm?: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
|
convertToLlm?: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
|
||||||
transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;
|
transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;
|
||||||
streamFunction: StreamFn;
|
streamFn: StreamFn;
|
||||||
getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
|
getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
|
||||||
onPayload?: SimpleStreamOptions["onPayload"];
|
onPayload?: SimpleStreamOptions["onPayload"];
|
||||||
onResponse?: SimpleStreamOptions["onResponse"];
|
onResponse?: SimpleStreamOptions["onResponse"];
|
||||||
@@ -207,24 +208,26 @@ export class Agent {
|
|||||||
public toolExecution: ToolExecutionMode;
|
public toolExecution: ToolExecutionMode;
|
||||||
|
|
||||||
constructor(options: AgentOptions) {
|
constructor(options: AgentOptions) {
|
||||||
this._state = createMutableAgentState(options.initialState);
|
// Older compiled consumers may omit options or streamFn even though the current API requires them.
|
||||||
this.convertToLlm = options.convertToLlm ?? defaultConvertToLlm;
|
const runtimeOptions: Partial<AgentOptions> = options ?? {};
|
||||||
this.transformContext = options.transformContext;
|
this._state = createMutableAgentState(runtimeOptions.initialState);
|
||||||
this.streamFunction = options.streamFunction;
|
this.convertToLlm = runtimeOptions.convertToLlm ?? defaultConvertToLlm;
|
||||||
this.getApiKey = options.getApiKey;
|
this.transformContext = runtimeOptions.transformContext;
|
||||||
this.onPayload = options.onPayload;
|
this.streamFunction = runtimeOptions.streamFn ?? getDefaultStreamFn();
|
||||||
this.onResponse = options.onResponse;
|
this.getApiKey = runtimeOptions.getApiKey;
|
||||||
this.beforeToolCall = options.beforeToolCall;
|
this.onPayload = runtimeOptions.onPayload;
|
||||||
this.afterToolCall = options.afterToolCall;
|
this.onResponse = runtimeOptions.onResponse;
|
||||||
this.prepareNextTurn = options.prepareNextTurn;
|
this.beforeToolCall = runtimeOptions.beforeToolCall;
|
||||||
this.prepareNextTurnWithContext = options.prepareNextTurnWithContext;
|
this.afterToolCall = runtimeOptions.afterToolCall;
|
||||||
this.steeringQueue = new PendingMessageQueue(options.steeringMode ?? "one-at-a-time");
|
this.prepareNextTurn = runtimeOptions.prepareNextTurn;
|
||||||
this.followUpQueue = new PendingMessageQueue(options.followUpMode ?? "one-at-a-time");
|
this.prepareNextTurnWithContext = runtimeOptions.prepareNextTurnWithContext;
|
||||||
this.sessionId = options.sessionId;
|
this.steeringQueue = new PendingMessageQueue(runtimeOptions.steeringMode ?? "one-at-a-time");
|
||||||
this.thinkingBudgets = options.thinkingBudgets;
|
this.followUpQueue = new PendingMessageQueue(runtimeOptions.followUpMode ?? "one-at-a-time");
|
||||||
this.transport = options.transport ?? "auto";
|
this.sessionId = runtimeOptions.sessionId;
|
||||||
this.maxRetryDelayMs = options.maxRetryDelayMs;
|
this.thinkingBudgets = runtimeOptions.thinkingBudgets;
|
||||||
this.toolExecution = options.toolExecution ?? "parallel";
|
this.transport = runtimeOptions.transport ?? "auto";
|
||||||
|
this.maxRetryDelayMs = runtimeOptions.maxRetryDelayMs;
|
||||||
|
this.toolExecution = runtimeOptions.toolExecution ?? "parallel";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -43,5 +43,7 @@ export * from "./harness/utils/shell-output.ts";
|
|||||||
export * from "./harness/utils/truncate.ts";
|
export * from "./harness/utils/truncate.ts";
|
||||||
// Proxy utilities
|
// Proxy utilities
|
||||||
export * from "./proxy.ts";
|
export * from "./proxy.ts";
|
||||||
|
// Stream defaults
|
||||||
|
export { setDefaultStreamFn } from "./stream-fn.ts";
|
||||||
// Types
|
// Types
|
||||||
export * from "./types.ts";
|
export * from "./types.ts";
|
||||||
|
|||||||
@@ -84,12 +84,12 @@ export interface ProxyStreamOptions extends ProxySerializableStreamOptions {
|
|||||||
* The server strips the partial field from delta events to reduce bandwidth.
|
* The server strips the partial field from delta events to reduce bandwidth.
|
||||||
* We reconstruct the partial message client-side.
|
* We reconstruct the partial message client-side.
|
||||||
*
|
*
|
||||||
* Use this as the `streamFunction` option when creating an Agent that needs to go through a proxy.
|
* Use this as the `streamFn` option when creating an Agent that needs to go through a proxy.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* const agent = new Agent({
|
* const agent = new Agent({
|
||||||
* streamFunction: (model, context, options) =>
|
* streamFn: (model, context, options) =>
|
||||||
* streamProxy(model, context, {
|
* streamProxy(model, context, {
|
||||||
* ...options,
|
* ...options,
|
||||||
* authToken: await getAuthToken(),
|
* authToken: await getAuthToken(),
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import type { StreamFn } from "./types.ts";
|
||||||
|
|
||||||
|
let defaultStreamFn: StreamFn | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the fallback used by Agent and low-level loops when callers omit streamFn.
|
||||||
|
*
|
||||||
|
* Hosts that provide a default model runtime can install its stream function here
|
||||||
|
* without making pi-agent-core depend on a provider catalog or compatibility layer.
|
||||||
|
*/
|
||||||
|
export function setDefaultStreamFn(streamFn: StreamFn | undefined): void {
|
||||||
|
defaultStreamFn = streamFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDefaultStreamFn(): StreamFn {
|
||||||
|
if (!defaultStreamFn) {
|
||||||
|
throw new Error("No default stream function configured. Pass streamFn explicitly or call setDefaultStreamFn().");
|
||||||
|
}
|
||||||
|
return defaultStreamFn;
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
import { Type } from "typebox";
|
import { Type } from "typebox";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { agentLoop, agentLoopContinue } from "../src/agent-loop.ts";
|
import { agentLoop, agentLoopContinue } from "../src/agent-loop.ts";
|
||||||
|
import { setDefaultStreamFn } from "../src/index.ts";
|
||||||
import type { AgentContext, AgentEvent, AgentLoopConfig, AgentMessage, AgentTool } from "../src/types.ts";
|
import type { AgentContext, AgentEvent, AgentLoopConfig, AgentMessage, AgentTool } from "../src/types.ts";
|
||||||
|
|
||||||
// Mock stream for testing - mimics MockAssistantStream
|
// Mock stream for testing - mimics MockAssistantStream
|
||||||
@@ -80,6 +81,40 @@ function identityConverter(messages: AgentMessage[]): Message[] {
|
|||||||
return messages.filter((m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult") as Message[];
|
return messages.filter((m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult") as Message[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe("default stream function compatibility", () => {
|
||||||
|
it("uses the configured default when a legacy caller omits streamFn", async () => {
|
||||||
|
let calls = 0;
|
||||||
|
setDefaultStreamFn(() => {
|
||||||
|
calls++;
|
||||||
|
const stream = new MockAssistantStream();
|
||||||
|
queueMicrotask(() => {
|
||||||
|
stream.push({
|
||||||
|
type: "done",
|
||||||
|
reason: "stop",
|
||||||
|
message: createAssistantMessage([{ type: "text", text: "fallback" }]),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return stream;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const context: AgentContext = { systemPrompt: "", messages: [], tools: [] };
|
||||||
|
const config: AgentLoopConfig = { model: createModel(), convertToLlm: identityConverter };
|
||||||
|
const stream = Reflect.apply(agentLoop, undefined, [
|
||||||
|
[createUserMessage("Hello")],
|
||||||
|
context,
|
||||||
|
config,
|
||||||
|
undefined,
|
||||||
|
]) as ReturnType<typeof agentLoop>;
|
||||||
|
|
||||||
|
await stream.result();
|
||||||
|
expect(calls).toBe(1);
|
||||||
|
} finally {
|
||||||
|
setDefaultStreamFn(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("agentLoop with AgentMessage", () => {
|
describe("agentLoop with AgentMessage", () => {
|
||||||
it("should emit events with AgentMessage types", async () => {
|
it("should emit events with AgentMessage types", async () => {
|
||||||
const context: AgentContext = {
|
const context: AgentContext = {
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { type AssistantMessage, type AssistantMessageEvent, EventStream, getModel } from "@earendil-works/pi-ai/compat";
|
import { type AssistantMessage, type AssistantMessageEvent, EventStream, getModel } from "@earendil-works/pi-ai/compat";
|
||||||
import { Type } from "typebox";
|
import { Type } from "typebox";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { Agent, type AgentEvent, type AgentTool, type AgentToolUpdateCallback, type StreamFn } from "../src/index.ts";
|
import {
|
||||||
|
Agent,
|
||||||
|
type AgentEvent,
|
||||||
|
type AgentTool,
|
||||||
|
type AgentToolUpdateCallback,
|
||||||
|
type StreamFn,
|
||||||
|
setDefaultStreamFn,
|
||||||
|
} from "../src/index.ts";
|
||||||
|
|
||||||
// Mock stream that mimics AssistantMessageEventStream
|
// Mock stream that mimics AssistantMessageEventStream
|
||||||
class MockAssistantStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
|
class MockAssistantStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
|
||||||
@@ -75,8 +82,29 @@ function createDeferred(): {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("Agent", () => {
|
describe("Agent", () => {
|
||||||
|
it("uses the configured default when a legacy caller omits streamFn", async () => {
|
||||||
|
let calls = 0;
|
||||||
|
setDefaultStreamFn(() => {
|
||||||
|
calls++;
|
||||||
|
const stream = new MockAssistantStream();
|
||||||
|
queueMicrotask(() => {
|
||||||
|
const message = createAssistantMessage("fallback");
|
||||||
|
stream.push({ type: "done", reason: "stop", message });
|
||||||
|
});
|
||||||
|
return stream;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const agent = Reflect.construct(Agent, [{}]) as Agent;
|
||||||
|
await agent.prompt("Hello");
|
||||||
|
expect(calls).toBe(1);
|
||||||
|
} finally {
|
||||||
|
setDefaultStreamFn(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("should create an agent instance with default state", () => {
|
it("should create an agent instance with default state", () => {
|
||||||
const agent = new Agent({ streamFunction: unusedStreamFunction });
|
const agent = new Agent({ streamFn: unusedStreamFunction });
|
||||||
|
|
||||||
expect(agent.state).toBeDefined();
|
expect(agent.state).toBeDefined();
|
||||||
expect(agent.state.systemPrompt).toBe("");
|
expect(agent.state.systemPrompt).toBe("");
|
||||||
@@ -93,7 +121,7 @@ describe("Agent", () => {
|
|||||||
it("should create an agent instance with custom initial state", () => {
|
it("should create an agent instance with custom initial state", () => {
|
||||||
const customModel = getModel("openai", "gpt-4o-mini");
|
const customModel = getModel("openai", "gpt-4o-mini");
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: unusedStreamFunction,
|
streamFn: unusedStreamFunction,
|
||||||
initialState: {
|
initialState: {
|
||||||
systemPrompt: "You are a helpful assistant.",
|
systemPrompt: "You are a helpful assistant.",
|
||||||
model: customModel,
|
model: customModel,
|
||||||
@@ -107,7 +135,7 @@ describe("Agent", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should subscribe to events", () => {
|
it("should subscribe to events", () => {
|
||||||
const agent = new Agent({ streamFunction: unusedStreamFunction });
|
const agent = new Agent({ streamFn: unusedStreamFunction });
|
||||||
|
|
||||||
let eventCount = 0;
|
let eventCount = 0;
|
||||||
const unsubscribe = agent.subscribe((_event) => {
|
const unsubscribe = agent.subscribe((_event) => {
|
||||||
@@ -130,7 +158,7 @@ describe("Agent", () => {
|
|||||||
|
|
||||||
it("emits full lifecycle events for thrown run failures", async () => {
|
it("emits full lifecycle events for thrown run failures", async () => {
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: () => {
|
streamFn: () => {
|
||||||
throw new Error("provider exploded");
|
throw new Error("provider exploded");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -162,7 +190,7 @@ describe("Agent", () => {
|
|||||||
it("should await async subscribers before prompt resolves", async () => {
|
it("should await async subscribers before prompt resolves", async () => {
|
||||||
const barrier = createDeferred();
|
const barrier = createDeferred();
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: () => {
|
streamFn: () => {
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
stream.push({ type: "done", reason: "stop", message: createAssistantMessage("ok") });
|
stream.push({ type: "done", reason: "stop", message: createAssistantMessage("ok") });
|
||||||
@@ -200,7 +228,7 @@ describe("Agent", () => {
|
|||||||
it("waitForIdle should wait for async subscribers", async () => {
|
it("waitForIdle should wait for async subscribers", async () => {
|
||||||
const barrier = createDeferred();
|
const barrier = createDeferred();
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: () => {
|
streamFn: () => {
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
stream.push({ type: "done", reason: "stop", message: createAssistantMessage("ok") });
|
stream.push({ type: "done", reason: "stop", message: createAssistantMessage("ok") });
|
||||||
@@ -235,7 +263,7 @@ describe("Agent", () => {
|
|||||||
it("should pass the active abort signal to subscribers", async () => {
|
it("should pass the active abort signal to subscribers", async () => {
|
||||||
let receivedSignal: AbortSignal | undefined;
|
let receivedSignal: AbortSignal | undefined;
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: (_model, _context, options) => {
|
streamFn: (_model, _context, options) => {
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
stream.push({ type: "start", partial: createAssistantMessage("") });
|
stream.push({ type: "start", partial: createAssistantMessage("") });
|
||||||
@@ -298,7 +326,7 @@ describe("Agent", () => {
|
|||||||
};
|
};
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
initialState: { tools: [tool] },
|
initialState: { tools: [tool] },
|
||||||
streamFunction: () => {
|
streamFn: () => {
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
stream.push({
|
stream.push({
|
||||||
@@ -373,7 +401,7 @@ describe("Agent", () => {
|
|||||||
};
|
};
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
initialState: { tools: [settledTool, slowTool] },
|
initialState: { tools: [settledTool, slowTool] },
|
||||||
streamFunction: () => {
|
streamFn: () => {
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
stream.push({
|
stream.push({
|
||||||
@@ -412,7 +440,7 @@ describe("Agent", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should update state with mutators", () => {
|
it("should update state with mutators", () => {
|
||||||
const agent = new Agent({ streamFunction: unusedStreamFunction });
|
const agent = new Agent({ streamFn: unusedStreamFunction });
|
||||||
|
|
||||||
// Test setSystemPrompt
|
// Test setSystemPrompt
|
||||||
agent.state.systemPrompt = "Custom prompt";
|
agent.state.systemPrompt = "Custom prompt";
|
||||||
@@ -451,7 +479,7 @@ describe("Agent", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should support steering message queue", async () => {
|
it("should support steering message queue", async () => {
|
||||||
const agent = new Agent({ streamFunction: unusedStreamFunction });
|
const agent = new Agent({ streamFn: unusedStreamFunction });
|
||||||
|
|
||||||
const message = { role: "user" as const, content: "Steering message", timestamp: Date.now() };
|
const message = { role: "user" as const, content: "Steering message", timestamp: Date.now() };
|
||||||
agent.steer(message);
|
agent.steer(message);
|
||||||
@@ -461,7 +489,7 @@ describe("Agent", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should support follow-up message queue", async () => {
|
it("should support follow-up message queue", async () => {
|
||||||
const agent = new Agent({ streamFunction: unusedStreamFunction });
|
const agent = new Agent({ streamFn: unusedStreamFunction });
|
||||||
|
|
||||||
const message = { role: "user" as const, content: "Follow-up message", timestamp: Date.now() };
|
const message = { role: "user" as const, content: "Follow-up message", timestamp: Date.now() };
|
||||||
agent.followUp(message);
|
agent.followUp(message);
|
||||||
@@ -471,7 +499,7 @@ describe("Agent", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should handle abort controller", () => {
|
it("should handle abort controller", () => {
|
||||||
const agent = new Agent({ streamFunction: unusedStreamFunction });
|
const agent = new Agent({ streamFn: unusedStreamFunction });
|
||||||
|
|
||||||
// Should not throw even if nothing is running
|
// Should not throw even if nothing is running
|
||||||
expect(() => agent.abort()).not.toThrow();
|
expect(() => agent.abort()).not.toThrow();
|
||||||
@@ -481,7 +509,7 @@ describe("Agent", () => {
|
|||||||
let abortSignal: AbortSignal | undefined;
|
let abortSignal: AbortSignal | undefined;
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
// Use a stream function that responds to abort
|
// Use a stream function that responds to abort
|
||||||
streamFunction: (_model, _context, options) => {
|
streamFn: (_model, _context, options) => {
|
||||||
abortSignal = options?.signal;
|
abortSignal = options?.signal;
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
@@ -520,7 +548,7 @@ describe("Agent", () => {
|
|||||||
it("should throw when continue() called while streaming", async () => {
|
it("should throw when continue() called while streaming", async () => {
|
||||||
let abortSignal: AbortSignal | undefined;
|
let abortSignal: AbortSignal | undefined;
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: (_model, _context, options) => {
|
streamFn: (_model, _context, options) => {
|
||||||
abortSignal = options?.signal;
|
abortSignal = options?.signal;
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
@@ -555,7 +583,7 @@ describe("Agent", () => {
|
|||||||
|
|
||||||
it("continue() should process queued follow-up messages after an assistant turn", async () => {
|
it("continue() should process queued follow-up messages after an assistant turn", async () => {
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: () => {
|
streamFn: () => {
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
stream.push({ type: "done", reason: "stop", message: createAssistantMessage("Processed") });
|
stream.push({ type: "done", reason: "stop", message: createAssistantMessage("Processed") });
|
||||||
@@ -594,7 +622,7 @@ describe("Agent", () => {
|
|||||||
it("continue() should keep one-at-a-time steering semantics from assistant tail", async () => {
|
it("continue() should keep one-at-a-time steering semantics from assistant tail", async () => {
|
||||||
let responseCount = 0;
|
let responseCount = 0;
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: () => {
|
streamFn: () => {
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
responseCount++;
|
responseCount++;
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
@@ -652,7 +680,7 @@ describe("Agent", () => {
|
|||||||
sawAbortSignal = signal instanceof AbortSignal;
|
sawAbortSignal = signal instanceof AbortSignal;
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
streamFunction: () => {
|
streamFn: () => {
|
||||||
requestCount++;
|
requestCount++;
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
@@ -680,7 +708,7 @@ describe("Agent", () => {
|
|||||||
let receivedSessionId: string | undefined;
|
let receivedSessionId: string | undefined;
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
sessionId: "session-abc",
|
sessionId: "session-abc",
|
||||||
streamFunction: (_model, _context, options) => {
|
streamFn: (_model, _context, options) => {
|
||||||
receivedSessionId = options?.sessionId;
|
receivedSessionId = options?.sessionId;
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ afterEach(() => {
|
|||||||
|
|
||||||
async function basicPrompt(model: Model<string>) {
|
async function basicPrompt(model: Model<string>) {
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
initialState: {
|
initialState: {
|
||||||
systemPrompt: "You are a helpful assistant. Keep your responses concise.",
|
systemPrompt: "You are a helpful assistant. Keep your responses concise.",
|
||||||
model,
|
model,
|
||||||
@@ -61,7 +61,7 @@ async function basicPrompt(model: Model<string>) {
|
|||||||
|
|
||||||
async function toolExecution(model: Model<string>) {
|
async function toolExecution(model: Model<string>) {
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
initialState: {
|
initialState: {
|
||||||
systemPrompt: "You are a helpful assistant. Always use the calculator tool for math.",
|
systemPrompt: "You are a helpful assistant. Always use the calculator tool for math.",
|
||||||
model,
|
model,
|
||||||
@@ -101,7 +101,7 @@ async function toolExecution(model: Model<string>) {
|
|||||||
|
|
||||||
async function abortExecution(model: Model<string>) {
|
async function abortExecution(model: Model<string>) {
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
initialState: {
|
initialState: {
|
||||||
systemPrompt: "You are a helpful assistant.",
|
systemPrompt: "You are a helpful assistant.",
|
||||||
model,
|
model,
|
||||||
@@ -129,7 +129,7 @@ async function abortExecution(model: Model<string>) {
|
|||||||
|
|
||||||
async function stateUpdates(model: Model<string>) {
|
async function stateUpdates(model: Model<string>) {
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
initialState: {
|
initialState: {
|
||||||
systemPrompt: "You are a helpful assistant.",
|
systemPrompt: "You are a helpful assistant.",
|
||||||
model,
|
model,
|
||||||
@@ -162,7 +162,7 @@ async function stateUpdates(model: Model<string>) {
|
|||||||
|
|
||||||
async function multiTurnConversation(model: Model<string>) {
|
async function multiTurnConversation(model: Model<string>) {
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
initialState: {
|
initialState: {
|
||||||
systemPrompt: "You are a helpful assistant.",
|
systemPrompt: "You are a helpful assistant.",
|
||||||
model,
|
model,
|
||||||
@@ -244,7 +244,7 @@ describe("Agent integration with faux provider", () => {
|
|||||||
faux.setResponses([fauxAssistantMessage([fauxThinking("step by step"), fauxText("4")])]);
|
faux.setResponses([fauxAssistantMessage([fauxThinking("step by step"), fauxText("4")])]);
|
||||||
|
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
initialState: {
|
initialState: {
|
||||||
systemPrompt: "You are a helpful assistant.",
|
systemPrompt: "You are a helpful assistant.",
|
||||||
model: faux.getModel(),
|
model: faux.getModel(),
|
||||||
@@ -269,7 +269,7 @@ describe("Agent.continue() with faux provider", () => {
|
|||||||
it("throws when no messages in context", async () => {
|
it("throws when no messages in context", async () => {
|
||||||
const faux = createFauxRegistration();
|
const faux = createFauxRegistration();
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
initialState: {
|
initialState: {
|
||||||
systemPrompt: "Test",
|
systemPrompt: "Test",
|
||||||
model: faux.getModel(),
|
model: faux.getModel(),
|
||||||
@@ -283,7 +283,7 @@ describe("Agent.continue() with faux provider", () => {
|
|||||||
const faux = createFauxRegistration();
|
const faux = createFauxRegistration();
|
||||||
const model = faux.getModel();
|
const model = faux.getModel();
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
initialState: {
|
initialState: {
|
||||||
systemPrompt: "Test",
|
systemPrompt: "Test",
|
||||||
model,
|
model,
|
||||||
@@ -318,7 +318,7 @@ describe("Agent.continue() with faux provider", () => {
|
|||||||
const faux = createFauxRegistration();
|
const faux = createFauxRegistration();
|
||||||
faux.setResponses([fauxAssistantMessage("HELLO WORLD")]);
|
faux.setResponses([fauxAssistantMessage("HELLO WORLD")]);
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
initialState: {
|
initialState: {
|
||||||
systemPrompt: "You are a helpful assistant. Follow instructions exactly.",
|
systemPrompt: "You are a helpful assistant. Follow instructions exactly.",
|
||||||
model: faux.getModel(),
|
model: faux.getModel(),
|
||||||
@@ -353,7 +353,7 @@ describe("Agent.continue() with faux provider", () => {
|
|||||||
const model = faux.getModel();
|
const model = faux.getModel();
|
||||||
faux.setResponses([fauxAssistantMessage("The answer is 8.")]);
|
faux.setResponses([fauxAssistantMessage("The answer is 8.")]);
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
initialState: {
|
initialState: {
|
||||||
systemPrompt:
|
systemPrompt:
|
||||||
"You are a helpful assistant. After getting a calculation result, state the answer clearly.",
|
"You are a helpful assistant. After getting a calculation result, state the answer clearly.",
|
||||||
|
|||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `retryAssistantCall()` for bounded retries of transient assistant failures with lifecycle callbacks and abort handling ([#6901](https://github.com/earendil-works/pi/pull/6901) by [@davidbrai](https://github.com/davidbrai)).
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed 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
|
## [0.81.0] - 2026-07-21
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -2,9 +2,21 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
||||||
|
|
||||||
|
- 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.
|
- 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
|
## [0.81.0] - 2026-07-21
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { Agent, type AgentMessage, type ThinkingLevel } from "@earendil-works/pi-agent-core";
|
import { Agent, type AgentMessage, setDefaultStreamFn, type ThinkingLevel } from "@earendil-works/pi-agent-core";
|
||||||
import { clampThinkingLevel, type Message, type Model } from "@earendil-works/pi-ai/compat";
|
import { clampThinkingLevel, type Message, type Model, streamSimple } from "@earendil-works/pi-ai/compat";
|
||||||
import { getAgentDir } from "../config.ts";
|
import { getAgentDir } from "../config.ts";
|
||||||
import { resolvePath } from "../utils/paths.ts";
|
import { resolvePath } from "../utils/paths.ts";
|
||||||
import { AgentSession } from "./agent-session.ts";
|
import { AgentSession } from "./agent-session.ts";
|
||||||
@@ -30,6 +30,11 @@ import {
|
|||||||
withFileMutationQueue,
|
withFileMutationQueue,
|
||||||
} from "./tools/index.ts";
|
} 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 {
|
export interface CreateAgentSessionOptions {
|
||||||
/** Working directory for project-local discovery. Default: process.cwd() */
|
/** Working directory for project-local discovery. Default: process.cwd() */
|
||||||
cwd?: string;
|
cwd?: string;
|
||||||
@@ -294,7 +299,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||||||
tools: [],
|
tools: [],
|
||||||
},
|
},
|
||||||
convertToLlm: convertToLlmWithBlockImages,
|
convertToLlm: convertToLlmWithBlockImages,
|
||||||
streamFunction: async (model, context, options) => {
|
streamFn: async (model, context, options) => {
|
||||||
const providerRetrySettings = settingsManager.getProviderRetrySettings();
|
const providerRetrySettings = settingsManager.getProviderRetrySettings();
|
||||||
const httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();
|
const httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();
|
||||||
// SDKs treat timeout=0 as 0ms (immediate timeout), not "no timeout".
|
// 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 model = getModel("anthropic", "claude-sonnet-4-5")!;
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
initialState: {
|
initialState: {
|
||||||
model,
|
model,
|
||||||
systemPrompt: "Test",
|
systemPrompt: "Test",
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ describe.skipIf(!API_KEY)("AgentSession compaction e2e", () => {
|
|||||||
const model = getModel("anthropic", "claude-sonnet-4-5")!;
|
const model = getModel("anthropic", "claude-sonnet-4-5")!;
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
getApiKey: () => API_KEY,
|
getApiKey: () => API_KEY,
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
initialState: {
|
initialState: {
|
||||||
model,
|
model,
|
||||||
systemPrompt: "You are a helpful assistant. Be concise.",
|
systemPrompt: "You are a helpful assistant. Be concise.",
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ describe("AgentSession concurrent prompt guard", () => {
|
|||||||
systemPrompt: "Test",
|
systemPrompt: "Test",
|
||||||
tools: [],
|
tools: [],
|
||||||
},
|
},
|
||||||
streamFunction: (_model, _context, options) => {
|
streamFn: (_model, _context, options) => {
|
||||||
abortSignal = options?.signal;
|
abortSignal = options?.signal;
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
@@ -195,7 +195,7 @@ describe("AgentSession concurrent prompt guard", () => {
|
|||||||
systemPrompt: "Test",
|
systemPrompt: "Test",
|
||||||
tools: [],
|
tools: [],
|
||||||
},
|
},
|
||||||
streamFunction: (_model, context, options) => {
|
streamFn: (_model, context, options) => {
|
||||||
abortSignal = options?.signal;
|
abortSignal = options?.signal;
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
@@ -301,7 +301,7 @@ describe("AgentSession concurrent prompt guard", () => {
|
|||||||
systemPrompt: "Test",
|
systemPrompt: "Test",
|
||||||
tools: [],
|
tools: [],
|
||||||
},
|
},
|
||||||
streamFunction: () => {
|
streamFn: () => {
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
stream.push({ type: "start", partial: createAssistantMessage("") });
|
stream.push({ type: "start", partial: createAssistantMessage("") });
|
||||||
@@ -362,7 +362,7 @@ describe("AgentSession concurrent prompt guard", () => {
|
|||||||
systemPrompt: "Test",
|
systemPrompt: "Test",
|
||||||
tools: [tool],
|
tools: [tool],
|
||||||
},
|
},
|
||||||
streamFunction: async (_model, context) => {
|
streamFn: async (_model, context) => {
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
const toolResultCount = context.messages.filter((message) => message.role === "toolResult").length;
|
const toolResultCount = context.messages.filter((message) => message.role === "toolResult").length;
|
||||||
@@ -508,7 +508,7 @@ describe("AgentSession concurrent prompt guard", () => {
|
|||||||
systemPrompt: "Test",
|
systemPrompt: "Test",
|
||||||
tools: [tool],
|
tools: [tool],
|
||||||
},
|
},
|
||||||
streamFunction: async (_model, context) => {
|
streamFn: async (_model, context) => {
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
const hasToolResult = context.messages.some((message) => message.role === "toolResult");
|
const hasToolResult = context.messages.some((message) => message.role === "toolResult");
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ describe("AgentSession retry", () => {
|
|||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
getApiKey: () => "test-key",
|
getApiKey: () => "test-key",
|
||||||
initialState: { model, systemPrompt: "Test", tools: [] },
|
initialState: { model, systemPrompt: "Test", tools: [] },
|
||||||
streamFunction: () => {
|
streamFn: () => {
|
||||||
callCount++;
|
callCount++;
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
@@ -203,7 +203,7 @@ describe("AgentSession retry", () => {
|
|||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
getApiKey: () => "test-key",
|
getApiKey: () => "test-key",
|
||||||
initialState: { model, systemPrompt: "Test", tools: [] },
|
initialState: { model, systemPrompt: "Test", tools: [] },
|
||||||
streamFunction: streamFn,
|
streamFn: streamFn,
|
||||||
});
|
});
|
||||||
const sessionManager = SessionManager.inMemory();
|
const sessionManager = SessionManager.inMemory();
|
||||||
const settingsManager = SettingsManager.create(tempDir, tempDir);
|
const settingsManager = SettingsManager.create(tempDir, tempDir);
|
||||||
@@ -255,7 +255,7 @@ describe("AgentSession retry", () => {
|
|||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
getApiKey: () => "test-key",
|
getApiKey: () => "test-key",
|
||||||
initialState: { model, systemPrompt: "Test", tools: [] },
|
initialState: { model, systemPrompt: "Test", tools: [] },
|
||||||
streamFunction: () => {
|
streamFn: () => {
|
||||||
callCount++;
|
callCount++;
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ async function createSession() {
|
|||||||
const session = new AgentSession({
|
const session = new AgentSession({
|
||||||
agent: new Agent({
|
agent: new Agent({
|
||||||
getApiKey: () => "test-key",
|
getApiKey: () => "test-key",
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
initialState: {
|
initialState: {
|
||||||
model,
|
model,
|
||||||
systemPrompt: "You are a helpful assistant.",
|
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 model = getModel("anthropic", "claude-sonnet-4-5")!;
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
getApiKey: () => API_KEY,
|
getApiKey: () => API_KEY,
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
initialState: {
|
initialState: {
|
||||||
model,
|
model,
|
||||||
systemPrompt: "You are a helpful assistant. Be concise.",
|
systemPrompt: "You are a helpful assistant. Be concise.",
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ async function createRuntimeHost(options: { withAuth: boolean; responseDelayMs:
|
|||||||
systemPrompt: "Test",
|
systemPrompt: "Test",
|
||||||
tools: [],
|
tools: [],
|
||||||
},
|
},
|
||||||
streamFunction: (_model, _context, _options) => {
|
streamFn: (_model, _context, _options) => {
|
||||||
const stream = new MockAssistantStream();
|
const stream = new MockAssistantStream();
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
stream.push({ type: "start", partial: createAssistantMessage("") });
|
stream.push({ type: "start", partial: createAssistantMessage("") });
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ export async function createHarness(options: HarnessOptions = {}): Promise<Harne
|
|||||||
|
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
getApiKey: () => (withConfiguredAuth ? "faux-key" : undefined),
|
getApiKey: () => (withConfiguredAuth ? "faux-key" : undefined),
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
initialState: {
|
initialState: {
|
||||||
model,
|
model,
|
||||||
systemPrompt: options.systemPrompt ?? "You are a test assistant.",
|
systemPrompt: options.systemPrompt ?? "You are a test assistant.",
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ describe("regression #5596: missing configured theme export", () => {
|
|||||||
tools: [],
|
tools: [],
|
||||||
},
|
},
|
||||||
convertToLlm,
|
convertToLlm,
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
});
|
});
|
||||||
const session = new AgentSession({
|
const session = new AgentSession({
|
||||||
agent,
|
agent,
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ async function createHarnessWithResourceLoader(
|
|||||||
systemPrompt: options.systemPrompt ?? "You are a test assistant.",
|
systemPrompt: options.systemPrompt ?? "You are a test assistant.",
|
||||||
tools: options.tools ?? [],
|
tools: options.tools ?? [],
|
||||||
},
|
},
|
||||||
streamFunction: streamFn,
|
streamFn: streamFn,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sessionManager = SessionManager.inMemory();
|
const sessionManager = SessionManager.inMemory();
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ export async function createTestSession(options: TestSessionOptions = {}): Promi
|
|||||||
systemPrompt: options.systemPrompt ?? "You are a helpful assistant. Be extremely concise.",
|
systemPrompt: options.systemPrompt ?? "You are a helpful assistant. Be extremely concise.",
|
||||||
tools: createCodingTools(process.cwd()),
|
tools: createCodingTools(process.cwd()),
|
||||||
},
|
},
|
||||||
streamFunction: streamSimple,
|
streamFn: streamSimple,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sessionManager = options.inMemory ? SessionManager.inMemory() : SessionManager.create(tempDir);
|
const sessionManager = options.inMemory ? SessionManager.inMemory() : SessionManager.create(tempDir);
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ if (!model) throw new Error("Anthropic smoke-test model not found");
|
|||||||
|
|
||||||
export const agent = new Agent({
|
export const agent = new Agent({
|
||||||
initialState: { model },
|
initialState: { model },
|
||||||
streamFunction: models.streamSimple.bind(models),
|
streamFn: models.streamSimple.bind(models),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const model = getModel("google", "gemini-2.5-flash");
|
|||||||
const schema = Type.Object({ prompt: Type.String() });
|
const schema = Type.Object({ prompt: Type.String() });
|
||||||
const stream = createAssistantMessageEventStream();
|
const stream = createAssistantMessageEventStream();
|
||||||
|
|
||||||
const agent = new Agent({ initialState: { model }, streamFunction: streamSimple });
|
const agent = new Agent({ initialState: { model }, streamFn: streamSimple });
|
||||||
agent.steer({ role: "user", content: [{ type: "text", text: "queued" }], timestamp: 0 });
|
agent.steer({ role: "user", content: [{ type: "text", text: "queued" }], timestamp: 0 });
|
||||||
const repo = new InMemorySessionRepo();
|
const repo = new InMemorySessionRepo();
|
||||||
const result = getOrThrow(ok({ value: 1 }));
|
const result = getOrThrow(ok({ value: 1 }));
|
||||||
|
|||||||
Reference in New Issue
Block a user