Skip to content
20 changes: 16 additions & 4 deletions src/funcs/call-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,16 @@ export function callModel<TTools extends readonly Tool[]>(
request: CallModelInput<TTools>,
options?: RequestOptions,
): ModelResult<TTools> {
const { tools, stopWhen, ...apiRequest } = request;
// Destructure state management options along with tools and stopWhen
const {
tools,
stopWhen,
state,
requireApproval,
approveToolCalls,
rejectToolCalls,
...apiRequest
} = request;

// Convert tools to API format - no cast needed now that convertToolsToAPIFormat accepts readonly
const apiTools = tools ? convertToolsToAPIFormat(tools) : undefined;
Expand All @@ -146,8 +155,11 @@ export function callModel<TTools extends readonly Tool[]>(
options: options ?? {},
// Preserve the exact TTools type instead of widening to Tool[]
tools: tools as TTools | undefined,
...(stopWhen !== undefined && {
stopWhen,
}),
...(stopWhen !== undefined && { stopWhen }),
// Pass state management options
...(state !== undefined && { state }),
...(requireApproval !== undefined && { requireApproval }),
...(approveToolCalls !== undefined && { approveToolCalls }),
...(rejectToolCalls !== undefined && { rejectToolCalls }),
} as GetResponseOptions<TTools>);
}
22 changes: 22 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
// Async params support
export type {
CallModelInput,
CallModelInputWithState,
FieldOrAsyncFunction,
ResolvedCallModelInput,
} from './lib/async-params.js';
export type { Fetcher, HTTPClientOptions } from './lib/http.js';
// Tool types
export type {
ChatStreamEvent,
ConversationState,
ConversationStatus,
ResponseStreamEvent as EnhancedResponseStreamEvent,
HasApprovalTools,
InferToolEvent,
InferToolEventsUnion,
InferToolInput,
Expand All @@ -21,19 +25,24 @@ export type {
NextTurnParamsContext,
NextTurnParamsFunctions,
ParsedToolCall,
PartialResponse,
StateAccessor,
StepResult,
StopCondition,
StopWhen,
Tool,
ToolApprovalCheck,
ToolExecutionResult,
ToolExecutionResultUnion,
ToolHasApproval,
ToolPreliminaryResultEvent,
ToolStreamEvent,
ToolWithExecute,
ToolWithGenerator,
TurnContext,
TypedToolCall,
TypedToolCallUnion,
UnsentToolResult,
Warning,
} from './lib/tool-types.js';
export type { BuildTurnContextOptions } from './lib/turn-context.js';
Expand Down Expand Up @@ -101,12 +110,25 @@ export {
// Tool creation helpers
export { tool } from './lib/tool.js';
export {
hasApprovalRequiredTools,
hasExecuteFunction,
isGeneratorTool,
isRegularExecuteTool,
isToolPreliminaryResultEvent,
toolHasApprovalConfigured,
ToolType,
} from './lib/tool-types.js';
// Turn context helpers
export { buildTurnContext, normalizeInputToArray } from './lib/turn-context.js';
// Conversation state helpers
export {
appendToMessages,
createInitialState,
createRejectedResult,
createUnsentResult,
generateConversationId,
partitionToolCalls,
toolRequiresApproval,
updateState,
} from './lib/conversation-state.js';
export * from './sdk/sdk.js';
65 changes: 58 additions & 7 deletions src/lib/async-params.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type * as models from '../models/index.js';
import type { StopWhen, Tool, TurnContext } from './tool-types.js';
import type { ParsedToolCall, StateAccessor, StopWhen, Tool, TurnContext } from './tool-types.js';

// Re-export Tool type for convenience
export type { Tool } from './tool-types.js';

/**
* Type guard to check if a value is a parameter function
Expand Down Expand Up @@ -29,19 +32,67 @@ function buildResolvedRequest(
export type FieldOrAsyncFunction<T> = T | ((context: TurnContext) => T | Promise<T>);

/**
* Input type for callModel function
* Each field can independently be a static value or a function that computes the value
* Generic over TTools to enable proper type inference for stopWhen conditions
* Base input type for callModel without approval-related fields
*/
export type CallModelInput<TTools extends readonly Tool[] = readonly Tool[]> = {
type BaseCallModelInput<TTools extends readonly Tool[] = readonly Tool[]> = {
[K in keyof Omit<models.OpenResponsesRequest, 'stream' | 'tools'>]?: FieldOrAsyncFunction<
models.OpenResponsesRequest[K]
>;
} & {
tools?: TTools;
stopWhen?: StopWhen<TTools>;
/**
* Call-level approval check - overrides tool-level requireApproval setting
* Receives the tool call and turn context, can be sync or async
*/
requireApproval?: (
toolCall: ParsedToolCall<TTools[number]>,
context: TurnContext
) => boolean | Promise<boolean>;
};

/**
* Approval params when state is provided (allows approve/reject)
*/
type ApprovalParamsWithState<TTools extends readonly Tool[] = readonly Tool[]> = {
/** State accessor for multi-turn persistence and approval gates */
state: StateAccessor<TTools>;
/** Tool call IDs to approve (for resuming from awaiting_approval status) */
approveToolCalls?: string[];
/** Tool call IDs to reject (for resuming from awaiting_approval status) */
rejectToolCalls?: string[];
};

/**
* Approval params when state is NOT provided (forbids approve/reject)
*/
type ApprovalParamsWithoutState = {
/** State accessor for multi-turn persistence and approval gates */
state?: undefined;
/** Not allowed without state - will cause type error */
approveToolCalls?: never;
/** Not allowed without state - will cause type error */
rejectToolCalls?: never;
};

/**
* Input type for callModel function
* Each field can independently be a static value or a function that computes the value
* Generic over TTools to enable proper type inference for stopWhen conditions
*
* Type enforcement:
* - `approveToolCalls` and `rejectToolCalls` are only valid when `state` is provided
* - Using these without `state` will cause a TypeScript error
*/
export type CallModelInput<TTools extends readonly Tool[] = readonly Tool[]> =
BaseCallModelInput<TTools> & (ApprovalParamsWithState<TTools> | ApprovalParamsWithoutState);

/**
* CallModelInput variant that requires state - use when approval workflows are needed
*/
export type CallModelInputWithState<TTools extends readonly Tool[] = readonly Tool[]> =
BaseCallModelInput<TTools> & ApprovalParamsWithState<TTools>;

/**
* Resolved CallModelInput (all functions evaluated to values)
* This is the type after all async functions have been resolved to their values
Expand Down Expand Up @@ -70,8 +121,8 @@ export type ResolvedCallModelInput = Omit<models.OpenResponsesRequest, 'stream'
* // resolved.temperature === 0.2
* ```
*/
export async function resolveAsyncFunctions(
input: CallModelInput,
export async function resolveAsyncFunctions<TTools extends readonly Tool[] = readonly Tool[]>(
input: CallModelInput<TTools>,
context: TurnContext,
): Promise<ResolvedCallModelInput> {
// Build array of resolved entries
Expand Down
Loading