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
23 changes: 17 additions & 6 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 @@ -144,10 +153,12 @@ export function callModel<TTools extends readonly Tool[]>(
client,
request: finalRequest,
options: options ?? {},
// Preserve the exact TTools type instead of widening to Tool[]
tools: tools as TTools | undefined,
...(stopWhen !== undefined && {
stopWhen,
}),
tools,
...(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,14 +110,27 @@ 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';
// Real-time tool event broadcasting
export { ToolEventBroadcaster } from './lib/tool-event-broadcaster.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