handleOrphanedPermission function

Stream<Map<String, dynamic>> handleOrphanedPermission({
  1. required Map<String, dynamic> permissionResult,
  2. required Map<String, dynamic> assistantMessage,
  3. required List<Map<String, dynamic>> tools,
  4. required List<Map<String, dynamic>> mutableMessages,
  5. required String sessionId,
  6. required bool persistSession,
  7. Future<void> recordTranscript(
    1. List<Map<String, dynamic>>
    )?,
  8. Stream<Map<String, dynamic>> runToolUse(
    1. Map<String, dynamic>
    )?,
})

Handle an orphaned permission by executing the tool.

Implementation

Stream<Map<String, dynamic>> handleOrphanedPermission({
  required Map<String, dynamic> permissionResult,
  required Map<String, dynamic> assistantMessage,
  required List<Map<String, dynamic>> tools,
  required List<Map<String, dynamic>> mutableMessages,
  required String sessionId,
  required bool persistSession,
  Future<void> Function(List<Map<String, dynamic>>)? recordTranscript,
  Stream<Map<String, dynamic>> Function(Map<String, dynamic>)? runToolUse,
}) async* {
  final toolUseId = permissionResult['toolUseID'] as String?;
  if (toolUseId == null) return;

  final content = assistantMessage['message']?['content'];
  Map<String, dynamic>? toolUseBlock;
  if (content is List) {
    for (final block in content) {
      if (block is Map<String, dynamic> &&
          block['type'] == 'tool_use' &&
          block['id'] == toolUseId) {
        toolUseBlock = block;
        break;
      }
    }
  }

  if (toolUseBlock == null) return;

  final toolName = toolUseBlock['name'] as String?;
  if (toolName == null) return;

  // Check if tool exists.
  final toolDefinition = tools.firstWhere(
    (t) => t['name'] == toolName,
    orElse: () => <String, dynamic>{},
  );
  if (toolDefinition.isEmpty) return;

  // Get final input.
  var finalInput = toolUseBlock['input'];
  if (permissionResult['behavior'] == 'allow' &&
      permissionResult['updatedInput'] != null) {
    finalInput = permissionResult['updatedInput'];
  }

  final finalToolUseBlock = {...toolUseBlock, 'input': finalInput};

  // Add assistant message if not already present.
  final alreadyPresent = mutableMessages.any((m) {
    if (m['type'] != 'assistant') return false;
    final c = m['message']?['content'];
    if (c is! List) return false;
    return c.any(
      (b) =>
          b is Map<String, dynamic> &&
          b['type'] == 'tool_use' &&
          b['id'] == toolUseId,
    );
  });

  if (!alreadyPresent) {
    mutableMessages.add(assistantMessage);
    if (persistSession && recordTranscript != null) {
      await recordTranscript(mutableMessages);
    }
  }

  yield {
    ...assistantMessage,
    'session_id': sessionId,
    'parent_tool_use_id': null,
  };

  // Execute the tool.
  if (runToolUse != null) {
    await for (final update in runToolUse(finalToolUseBlock)) {
      if (update.containsKey('message')) {
        mutableMessages.add(update['message'] as Map<String, dynamic>);
        if (persistSession && recordTranscript != null) {
          await recordTranscript(mutableMessages);
        }

        yield {
          ...(update['message'] as Map<String, dynamic>),
          'session_id': sessionId,
          'parent_tool_use_id': null,
        };
      }
    }
  }
}