toGeminiPart function

  1. @visibleForTesting
Part toGeminiPart(
  1. Part p
)

Implementation

@visibleForTesting
gcl.Part toGeminiPart(Part p) {
  final thoughtSignature = p.metadata?['thoughtSignature'] != null
      ? p.metadata!['thoughtSignature'] as String
      : null;

  if (p.isReasoning) {
    return gcl.Part(
      text: p.reasoning,
      thought: true,
      thoughtSignature: thoughtSignature,
    );
  }
  if (p.isText) {
    return gcl.Part(text: p.text, thoughtSignature: thoughtSignature);
  }
  if (p.isToolRequest) {
    return gcl.Part(
      functionCall: gcl.FunctionCall(
        id: p.toolRequest!.ref ?? '',
        name: _toGeminiToolName(p.toolRequest!.name),
        args: p.toolRequest!.input is Map
            ? (p.toolRequest!.input as Map).cast<String, Object?>()
            : null,
      ),

      thoughtSignature: thoughtSignature,
    );
  }
  if (p.isToolResponse) {
    final tr = p.toolResponse!;
    // Multipart tool content (images, media, etc.) becomes function-response
    // parts. Gemini's FunctionResponse.parts only supports inline/file data
    // (see js-genai `FunctionResponsePart`), so we map media parts to their
    // `inlineData`/`fileData` shape and skip parts that cannot be represented
    // there (e.g. text) which would otherwise be rejected by the API. The
    // structured result still travels in `response.output`.
    final contentParts = tr.content
        ?.map((c) => Part.fromJson(c as Map<String, dynamic>))
        .where((part) => part.isMedia)
        .map((part) => toGeminiPart(part).toJson())
        .toList();
    return gcl.Part(
      functionResponse: gcl.FunctionResponse.fromJson({
        'id': tr.ref ?? '',
        'name': _toGeminiToolName(tr.name),
        'response': {'output': tr.output},
        'parts': ?(contentParts == null || contentParts.isEmpty
            ? null
            : contentParts),
      }),

      thoughtSignature: thoughtSignature,
    );
  }

  if (p.isMedia) {
    final media = p.media;
    if (media!.url.startsWith('data:')) {
      final uri = Uri.parse(media.url);
      if (uri.data != null) {
        return gcl.Part.fromJson({
          'inlineData': {
            'mimeType': media.contentType ?? uri.data!.mimeType,
            'data': base64Encode(uri.data!.contentAsBytes()),
          },
          'thoughtSignature': ?thoughtSignature,
        });
      }
    }
    return gcl.Part.fromJson({
      'fileData': {'mimeType': media.contentType ?? '', 'fileUri': media.url},
      'thoughtSignature': ?thoughtSignature,
    });
  }
  if (p.isCustom && p.custom!['codeExecutionResult'] != null) {
    p as CustomPart;
    return gcl.Part(
      codeExecutionResult: gcl.CodeExecutionResult(
        outcome:
            (p.custom['codeExecutionResult'] as Map<String, dynamic>)['outcome']
                as String?,
        output:
            (p.custom['codeExecutionResult'] as Map<String, dynamic>)['output']
                as String?,
      ),
      thoughtSignature: thoughtSignature,
    );
  }
  if (p.isCustom && p.custom!['executableCode'] != null) {
    p as CustomPart;
    return gcl.Part(
      executableCode: gcl.ExecutableCode(
        language:
            (p.custom['executableCode'] as Map<String, dynamic>)['language']
                as String?,
        code:
            (p.custom['executableCode'] as Map<String, dynamic>)['code']
                as String?,
      ),
      thoughtSignature: thoughtSignature,
    );
  }
  throw UnimplementedError('Unsupported part type: $p');
}