Skip to content

Conversation

@mattapperson
Copy link
Collaborator

Summary

  • Add multi-turn conversation state management with StateAccessor pattern for pluggable persistence
  • Add approval workflow for tool execution (requireApproval, approveToolCalls, rejectToolCalls)
  • Improve type safety by replacing type casts with type guards
  • Refactor executeToolsIfNeeded from ~400 lines to ~110 lines by extracting 14 focused helper methods

Key Changes

New State Management Features

  • StateAccessor interface for pluggable state persistence (in-memory, database, etc.)
  • ConversationState type for tracking conversation status, messages, pending approvals
  • createInitialState(), updateState() utilities for state management
  • Support for interruption handling and resumption

Approval Workflow

  • Tools can specify requireApproval: true to pause execution
  • Call-level requireApproval function for dynamic approval logic
  • approveToolCalls and rejectToolCalls arrays to resume with decisions
  • getPendingToolCalls() and requiresApproval() methods on ModelResult

Type Safety Improvements

  • Type guards (isValidUnsentToolResult, isValidParsedToolCall) instead of type casts
  • Made resolveAsyncFunctions generic to eliminate double-casting
  • isEventStream type guard handles both streaming and non-streaming responses

Code Organization

Extracted 14 helper methods from executeToolsIfNeeded:

  • getInitialResponse(), saveResponseToState(), markStateComplete()
  • checkForInterruption(), shouldStopExecution(), hasExecutableToolCalls()
  • executeAutoApproveTools(), handleApprovalCheck(), executeToolRound()
  • resolveAsyncFunctionsForTurn(), applyNextTurnParams(), makeFollowupRequest()
  • saveToolResultsToState(), validateFinalResponse()

Test plan

  • All 320 existing tests pass
  • New unit tests for conversation state utilities (21 tests)
  • New E2E tests for state management integration (5 tests)
  • Build passes with no TypeScript errors
  • Lint passes with no warnings

…mprovements

- Add StateAccessor interface for pluggable state persistence
- Add ConversationState type with status tracking (complete, interrupted, awaiting_approval, in_progress)
- Add approval workflow support with requireApproval tool option and partitionToolCalls helper
- Add type guards (isValidUnsentToolResult, isValidParsedToolCall) to replace unsafe type assertions
- Make resolveAsyncFunctions generic over TTools to eliminate double-casting
- Fix isEventStream to check constructor name on prototype chain
- Handle both streaming and non-streaming API responses gracefully
- Add conversation-state.ts with helper functions for state management
- Add unit tests for conversation state utilities (21 tests)
- Add e2e tests for state management integration (5 tests)
Extract 14 helper methods from the monolithic executeToolsIfNeeded method
to improve maintainability and reduce complexity:

- getInitialResponse(): Get initial response from stream or cache
- saveResponseToState(): Save response output to state
- markStateComplete(): Mark conversation complete
- saveToolResultsToState(): Save tool results to state
- checkForInterruption(): Handle interruption signals
- shouldStopExecution(): Evaluate stop conditions
- hasExecutableToolCalls(): Check for executable tools
- executeAutoApproveTools(): Execute non-approval tools
- handleApprovalCheck(): Deduplicated approval workflow
- executeToolRound(): Execute one round of tools
- resolveAsyncFunctionsForTurn(): Resolve async params per turn
- applyNextTurnParams(): Apply nextTurnParams
- makeFollowupRequest(): Build and send follow-up requests
- validateFinalResponse(): Validate final response

The main method is reduced from ~400 lines to ~110 lines, acting as a
clean coordinator that delegates to these focused helpers.

Also replaces 'name as string' casts with String() for safer conversion.
Update the call-level requireApproval function signature to:
- Accept TurnContext as a second parameter
- Support async functions (returning Promise<boolean>)

This enables approval decisions based on conversation state:
- Number of turns completed
- Dynamic approval logic based on context

Updated files:
- src/lib/async-params.ts - Update type definition
- src/lib/model-result.ts - Update type and field
- src/lib/conversation-state.ts - Make toolRequiresApproval and
  partitionToolCalls async, add context parameter
- tests/unit/conversation-state.test.ts - Update tests, add async test
- tests/e2e/call-model-state.test.ts - Update test signature
The tool-level requireApproval now supports both boolean values and async
functions that receive the tool's input params and turn context, matching
the call-level requireApproval signature.

This enables dynamic approval decisions based on:
- Tool arguments (e.g., require approval only for dangerous parameter values)
- Turn context (e.g., require approval after a certain number of turns)

Changes:
- Add ToolApprovalCheck type for function-based approval
- Update BaseToolFunction and all tool config types
- Update toolRequiresApproval to handle function-based approval
- Add unit tests for function-based tool-level approval
- Export ToolApprovalCheck type from index
Add type-level and runtime enforcement that approval-related parameters
require a state accessor:

Type-level enforcement:
- CallModelInput now uses conditional types so `approveToolCalls` and
  `rejectToolCalls` are typed as `never` when `state` is not provided
- Added helper types: ToolHasApproval, HasApprovalTools

Runtime enforcement:
- Throws error in ModelResult constructor if approveToolCalls/rejectToolCalls
  provided without state accessor
- Throws error in handleApprovalCheck if tools require approval but no
  state accessor is configured

New exports:
- Type guards: toolHasApprovalConfigured, hasApprovalRequiredTools
- Helper types: ToolHasApproval, HasApprovalTools, CallModelInputWithApprovalTools
- Added missing default stop condition in shouldStopExecution() that was
  documented but not implemented, preventing infinite tool execution loops
- Changed test model from llama-3.1-8b-instruct to claude-sonnet-4.5 to
  fix timeout issues in chat-style tools test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants