Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 9 additions & 0 deletions .changeset/evolve-model-generic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@funkai/agents": minor
---

Add `TModel` generic to `AgentConfig` and `Agent` for discriminated model types in `evolve()`.

Previously, `evolve(base, (config) => ...)` always typed `config.model` as the full `Resolver<TInput, Model>` union, even when the base agent was created with a static `LanguageModel`. This required unnecessary narrowing with `isFunction()` before accessing `.modelId`.

Now the 5th generic `TModel` is inferred from `agent()` and threaded through `evolve()`, so `config.model` is correctly typed as `Model` (with `.modelId`) when the base agent uses a static model.
9 changes: 5 additions & 4 deletions packages/agents/src/core/agents/base/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { createDefaultLogger } from "@/core/logger.js";
import type { Logger } from "@/core/logger.js";
import type { LanguageModel } from "@/core/provider/types.js";
import type { Tool } from "@/core/tool.js";
import type { StepFinishEvent, StreamPart } from "@/core/types.js";
import type { Model, StepFinishEvent, StreamPart } 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 @@ -86,9 +86,10 @@ export function agent<
TTools extends Record<string, Tool> = {},
// oxlint-disable-next-line typescript-eslint/ban-types
TSubAgents extends SubAgents = {},
TModel extends Resolver<TInput, Model> = Resolver<TInput, Model>,
>(
config: AgentConfig<TInput, TOutput, TTools, TSubAgents>,
): Agent<TInput, TOutput, TTools, TSubAgents> {
config: AgentConfig<TInput, TOutput, TTools, TSubAgents, TModel>,
): Agent<TInput, TOutput, TTools, TSubAgents, TModel> {
/**
* Extract the raw input from unified params.
*
Expand Down Expand Up @@ -538,7 +539,7 @@ export function agent<
}

// eslint-disable-next-line no-shadow -- Local variable is the return value constructed inside its own factory function
const agent: Agent<TInput, TOutput, TTools, TSubAgents> = {
const agent: Agent<TInput, TOutput, TTools, TSubAgents, TModel> = {
generate,
stream,
fn: () => generate,
Expand Down
6 changes: 3 additions & 3 deletions packages/agents/src/core/agents/base/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import type { RunnableMeta } from "@/lib/runnable.js";
export function buildAITools(
tools?: Record<string, Tool>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Agent generic params are contravariant; `unknown` breaks assignability
agents?: Record<string, Agent<any, any, any, any>>,
agents?: Record<string, Agent<any, any, any, any, any>>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ToolSet requires `any` values; `unknown` breaks assignability with AI SDK
): Record<string, any> | undefined {
const hasTools = isNotNil(tools) && Object.keys(tools).length > 0;
Expand Down Expand Up @@ -251,7 +251,7 @@ function resolveToolName(meta: RunnableMeta | undefined, fallback: string): stri
*/
function buildAgentTools(
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Agent generic params are contravariant; `unknown` breaks assignability
agents: Record<string, Agent<any, any, any, any>> | undefined,
agents: Record<string, Agent<any, any, any, any, any>> | undefined,
tools: Record<string, Tool> | undefined,
): Record<string, unknown> {
if (!agents) {
Expand Down Expand Up @@ -294,7 +294,7 @@ function buildAgentTools(
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ToolSet requires `any` values; `unknown` breaks assignability with AI SDK
function buildAgentTool(
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Agent generic params are contravariant; `unknown` breaks assignability
runnable: Agent<any, any, any, any>,
runnable: Agent<any, any, any, any, any>,
meta: RunnableMeta | undefined,
toolName: string,
tools: Record<string, Tool> | undefined,
Expand Down
16 changes: 9 additions & 7 deletions packages/agents/src/core/agents/evolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import type {
FlowAgentHandler,
FlowSubAgents,
} from "@/core/agents/flow/types.js";
import type { Agent, AgentConfig, SubAgents } from "@/core/agents/types.js";
import type { Agent, AgentConfig, Resolver, SubAgents } from "@/core/agents/types.js";
import type { Tool } from "@/core/tool.js";
import type { Model } from "@/core/types.js";
import { getAgentConfig, getFlowAgentConfig, isAgent, isFlowAgent } from "@/lib/runnable.js";

/**
Expand Down Expand Up @@ -79,14 +80,15 @@ export function evolve<
TOutput,
TTools extends Record<string, Tool>,
TSubAgents extends SubAgents,
TModel extends Resolver<TInput, Model>,
>(
base: Agent<TInput, TOutput, TTools, TSubAgents>,
base: Agent<TInput, TOutput, TTools, TSubAgents, TModel>,
overrides:
| Partial<AgentConfig<TInput, TOutput, TTools, TSubAgents>>
| ((
config: AgentConfig<TInput, TOutput, TTools, TSubAgents>,
config: AgentConfig<TInput, TOutput, TTools, TSubAgents, TModel>,
) => Partial<AgentConfig<TInput, TOutput, TTools, TSubAgents>>),
): Agent<TInput, TOutput, TTools, TSubAgents>;
): Agent<TInput, TOutput, TTools, TSubAgents, TModel>;

/**
* Create a new flow agent from an existing one with config overrides.
Expand Down Expand Up @@ -154,7 +156,7 @@ function evolveAgent(
overridesOrMapper: Record<string, any> | ((config: any) => Record<string, any>),
): any {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- internal: stored config type is erased
const baseConfig = getAgentConfig<AgentConfig<any, any, any, any>>(base);
const baseConfig = getAgentConfig<AgentConfig<any, any, any, any, any>>(base);
if (isNil(baseConfig)) {
throw new Error("Cannot evolve: agent does not have stored configuration.");
}
Expand Down Expand Up @@ -203,9 +205,9 @@ function evolveFlowAgent(
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- internal merge operates on erased types
function mergeAgentConfigs(
base: AgentConfig<any, any, any, any>,
base: AgentConfig<any, any, any, any, any>,
overrides: Record<string, unknown>,
): AgentConfig<any, any, any, any> {
): AgentConfig<any, any, any, any, any> {
const { tools: overrideTools, agents: overrideAgents, ...restOverrides } = overrides;
return {
...base,
Expand Down
2 changes: 1 addition & 1 deletion packages/agents/src/core/agents/flow/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export type { StepInfo } from "@/core/types.js";
* ```
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type FlowSubAgents = Record<string, Agent<any, any, any, any> | FlowAgent<any, any>>;
export type FlowSubAgents = Record<string, Agent<any, any, any, any, any> | FlowAgent<any, any>>;

/**
* Result of a completed flow agent generation.
Expand Down
6 changes: 4 additions & 2 deletions packages/agents/src/core/agents/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export type ToolName<S extends string> = S extends ""
* ```
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type SubAgents = Record<string, Agent<any, any, any, any>>;
export type SubAgents = Record<string, Agent<any, any, any, any, any>>;

/**
* Chat message type.
Expand Down Expand Up @@ -500,6 +500,7 @@ export interface AgentConfig<
TOutput,
TTools extends Record<string, Tool>,
TSubAgents extends SubAgents,
TModel extends Resolver<TInput, Model> = Resolver<TInput, Model>,
> {
/**
* Unique agent name.
Expand All @@ -518,7 +519,7 @@ export interface AgentConfig<
* @see {@link Model}
* @see {@link Resolver}
*/
model: Resolver<TInput, Model>;
model: TModel;

/**
* Zod schema for the agent's typed input.
Expand Down Expand Up @@ -676,6 +677,7 @@ export interface Agent<
TOutput = string,
TTools extends Record<string, Tool> = Record<string, Tool>,
TSubAgents extends SubAgents = Record<string, never>,
TModel extends Resolver<TInput, Model> = Resolver<TInput, Model>,
> {
/**
* Run the agent to completion.
Expand Down
2 changes: 1 addition & 1 deletion packages/agents/src/lib/runnable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export interface RunnableMeta {
* @internal
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Widest instantiation; concrete generics are unknown at runtime
export function isAgent(value: unknown): value is Agent<any, any, any, any> {
export function isAgent(value: unknown): value is Agent<any, any, any, any, any> {
return isObject(value) && has(value, AGENT_CONFIG);
}

Expand Down
Loading