Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/input-param-parity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@funkai/agents": minor
---

Add input parameter parity with the Vercel AI SDK. Surface CallSettings (temperature, maxOutputTokens, topP, topK, presencePenalty, frequencyPenalty, stopSequences, seed, maxRetries), telemetry, granular timeout objects, custom stop conditions (stopWhen), and stream-only callbacks (onChunk, onStreamError, onAbort) on both AgentConfig and per-call overrides.
7 changes: 3 additions & 4 deletions packages/agents/src/core/agents/base/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ import type {
AIStepResult,
AgentChainEntry,
Model,
StepFinishEvent,
StepStartEvent,
StreamPart,
} from "@/core/types.js";
import { stepFinishEventFromAIStep } from "@/core/types.js";
import { fireHooks, wrapHook } from "@/lib/hooks.js";
import { withModelMiddleware } from "@/lib/middleware.js";
import { AGENT_CONFIG, RUNNABLE_META } from "@/lib/runnable.js";
Expand Down Expand Up @@ -267,12 +267,11 @@ export function agent<
const stepCounter = { value: 0 };
const onStepFinish = async (step: AIStepResult) => {
const stepId = `${config.name}:${stepCounter.value++}`;
const event: StepFinishEvent = {
...step,
const event = stepFinishEventFromAIStep(step, {
stepId,
stepOperation: "agent",
agentChain: currentChain,
};
});
await fireHooks(
log,
wrapHook(config.onStepFinish, event),
Expand Down
17 changes: 7 additions & 10 deletions packages/agents/src/core/agents/flow/steps/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type { WhileConfig } from "@/core/agents/flow/steps/while.js";
/* oxlint-disable import/max-dependencies -- step factory requires many internal modules */
import type { BaseGenerateResult } from "@/core/agents/types.js";
import type { AgentChainEntry, StepFinishEvent, StepStartEvent, StreamPart } from "@/core/types.js";
import { stepFinishEventFromFlow } from "@/core/types.js";
import type { Context } from "@/lib/context.js";
import { fireHooks } from "@/lib/hooks.js";
import type { TraceEntry, OperationType } from "@/lib/trace.js";
Expand Down Expand Up @@ -233,14 +234,10 @@ function createStepBuilderInternal(options: StepBuilderOptions, indexRef: IndexR
const extras = match(buildFinishEventExtras)
.with(P.not(P.nullish), (fn) => fn(value))
.otherwise(() => ({}));
const finishEvent: StepFinishEvent = {
stepId: id,
stepOperation: type,
output: value,
duration,
agentChain,
...extras,
};
const finishEvent = stepFinishEventFromFlow(
{ stepId: id, stepOperation: type, output: value, duration, agentChain },
extras,
);
const parentOnStepFinishHook = buildParentHookCallback(parentHooks, "onStepFinish", (fn) =>
fn(finishEvent),
);
Expand Down Expand Up @@ -301,13 +298,13 @@ function createStepBuilderInternal(options: StepBuilderOptions, indexRef: IndexR
}

const onErrorHook = buildHookCallback(onError, (fn) => fn({ id, error }));
const errorFinishEvent: StepFinishEvent = {
const errorFinishEvent = stepFinishEventFromFlow({
stepId: id,
stepOperation: type,
output: undefined,
duration,
agentChain,
};
});
const parentOnStepFinishHook = buildParentHookCallback(parentHooks, "onStepFinish", (fn) =>
fn(errorFinishEvent),
);
Expand Down
52 changes: 52 additions & 0 deletions packages/agents/src/core/types.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { StepResult, ToolSet } from "ai";
import { assertType, describe, expectTypeOf, it } from "vitest";

import type { AIStepResult, StepFinishEvent } from "@/core/types.js";
import { stepFinishEventFromAIStep, stepFinishEventFromFlow } from "@/core/types.js";

describe("StepFinishEvent", () => {

Check warning on line 7 in packages/agents/src/core/types.test-d.ts

View workflow job for this annotation

GitHub Actions / ci

eslint-plugin-vitest(prefer-describe-function-title)

Title description can not have the same content as a imported function name.

Check warning on line 7 in packages/agents/src/core/types.test-d.ts

View workflow job for this annotation

GitHub Actions / ci

eslint-plugin-jest(prefer-lowercase-title)

Enforce lowercase test names
it("has required toolCalls matching AIStepResult", () => {
expectTypeOf<StepFinishEvent["toolCalls"]>().toEqualTypeOf<AIStepResult["toolCalls"]>();
});

it("has required toolResults matching AIStepResult", () => {
expectTypeOf<StepFinishEvent["toolResults"]>().toEqualTypeOf<AIStepResult["toolResults"]>();
});

it("toolCalls matches AI SDK StepResult toolCalls", () => {
expectTypeOf<StepFinishEvent["toolCalls"]>().toEqualTypeOf<StepResult<ToolSet>["toolCalls"]>();
});

it("toolResults matches AI SDK StepResult toolResults", () => {
expectTypeOf<StepFinishEvent["toolResults"]>().toEqualTypeOf<
StepResult<ToolSet>["toolResults"]
>();
});

it("has required stepId and stepOperation", () => {
expectTypeOf<StepFinishEvent["stepId"]>().toBeString();
expectTypeOf<StepFinishEvent["stepOperation"]>().toBeString();
});
});

describe("stepFinishEventFromAIStep", () => {

Check warning on line 32 in packages/agents/src/core/types.test-d.ts

View workflow job for this annotation

GitHub Actions / ci

eslint-plugin-vitest(prefer-describe-function-title)

Title description can not have the same content as a imported function name.
it("returns StepFinishEvent", () => {
expectTypeOf(stepFinishEventFromAIStep).returns.toExtend<StepFinishEvent>();
});

it("result has non-optional toolCalls", () => {
const event = {} as ReturnType<typeof stepFinishEventFromAIStep>;
assertType<AIStepResult["toolCalls"]>(event.toolCalls);
});
});

describe("stepFinishEventFromFlow", () => {

Check warning on line 43 in packages/agents/src/core/types.test-d.ts

View workflow job for this annotation

GitHub Actions / ci

eslint-plugin-vitest(prefer-describe-function-title)

Title description can not have the same content as a imported function name.
it("returns StepFinishEvent", () => {
expectTypeOf(stepFinishEventFromFlow).returns.toExtend<StepFinishEvent>();
});

it("result has non-optional toolCalls", () => {
const event = {} as ReturnType<typeof stepFinishEventFromFlow>;
assertType<AIStepResult["toolCalls"]>(event.toolCalls);
});
});
78 changes: 78 additions & 0 deletions packages/agents/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,40 @@ export type StepFinishEvent = Partial<AIStepResult> & {
*/
readonly stepOperation: OperationType;

/**
* The tool calls made during this step.
*
* Populated from the AI SDK's `StepResult.toolCalls` on agent
* tool-loop steps. Empty array on flow orchestration steps.
*
* @example
* ```typescript
* onStepFinish(event) {
* for (const call of event.toolCalls) {
* console.log(call.toolName, call.args);
* }
* }
* ```
*/
readonly toolCalls: AIStepResult["toolCalls"];

/**
* The results of tool calls made during this step.
*
* Populated from the AI SDK's `StepResult.toolResults` on agent
* tool-loop steps. Empty array on flow orchestration steps.
*
* @example
* ```typescript
* onStepFinish(event) {
* for (const result of event.toolResults) {
* console.log(result.toolName, result.result);
* }
* }
* ```
*/
readonly toolResults: AIStepResult["toolResults"];

/**
* Flow step output value.
*
Expand Down Expand Up @@ -172,6 +206,50 @@ export type StepFinishEvent = Partial<AIStepResult> & {
readonly agentChain?: readonly AgentChainEntry[] | undefined;
};

/**
* Build a `StepFinishEvent` from an AI SDK step result.
*
* Used by the agent tool-loop — spreads the full `StepResult` and
* adds funkai-specific fields.
*
* @param step - The AI SDK step result.
* @param extras - funkai-specific fields (`stepId`, `stepOperation`, `agentChain`).
* @returns A fully populated `StepFinishEvent`.
*/
export function stepFinishEventFromAIStep(
step: AIStepResult,
extras: Pick<StepFinishEvent, "stepId" | "stepOperation" | "agentChain">,
): StepFinishEvent {
return {
...step,
...extras,
};
}

/**
* Build a `StepFinishEvent` for a flow orchestration step.
*
* Stubs `toolCalls` and `toolResults` as empty arrays since flow
* steps don't interact with the AI SDK tool loop.
*
* @param fields - Flow-specific fields and optional AI SDK overrides from `extras`.
* @returns A `StepFinishEvent` with empty tool arrays.
*/
export function stepFinishEventFromFlow(
fields: Pick<StepFinishEvent, "stepId" | "stepOperation" | "agentChain"> & {
readonly output?: unknown;
readonly duration?: number;
},
extras?: Partial<AIStepResult>,
): StepFinishEvent {
return {
toolCalls: [],
toolResults: [],
...fields,
...extras,
};
}

/**
* A value that can be generated against — the shared contract
* between Agent and FlowAgent.
Expand Down
Loading