-
Notifications
You must be signed in to change notification settings - Fork 16
feat: Multi-turn conversation state management with type safety #124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mattapperson
wants to merge
7
commits into
main
Choose a base branch
from
multi-turn
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
…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
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
StateAccessorpattern for pluggable persistencerequireApproval,approveToolCalls,rejectToolCalls)executeToolsIfNeededfrom ~400 lines to ~110 lines by extracting 14 focused helper methodsKey Changes
New State Management Features
StateAccessorinterface for pluggable state persistence (in-memory, database, etc.)ConversationStatetype for tracking conversation status, messages, pending approvalscreateInitialState(),updateState()utilities for state managementApproval Workflow
requireApproval: trueto pause executionrequireApprovalfunction for dynamic approval logicapproveToolCallsandrejectToolCallsarrays to resume with decisionsgetPendingToolCalls()andrequiresApproval()methods onModelResultType Safety Improvements
isValidUnsentToolResult,isValidParsedToolCall) instead of type castsresolveAsyncFunctionsgeneric to eliminate double-castingisEventStreamtype guard handles both streaming and non-streaming responsesCode Organization
Extracted 14 helper methods from
executeToolsIfNeeded:getInitialResponse(),saveResponseToState(),markStateComplete()checkForInterruption(),shouldStopExecution(),hasExecutableToolCalls()executeAutoApproveTools(),handleApprovalCheck(),executeToolRound()resolveAsyncFunctionsForTurn(),applyNextTurnParams(),makeFollowupRequest()saveToolResultsToState(),validateFinalResponse()Test plan