Skip to main content

gproxy_protocol/transform/generate/gemini_responses/
identity.rs

1use crate::{
2    transform::{
3        TransformError,
4        identity::{IdentityFlow, IdentityRole, IdentityTarget, SourceIdentity, TargetIdPolicy},
5    },
6    wire::{gemini as g, openai::responses::input as r},
7};
8
9pub(crate) fn id(
10    flow: &mut IdentityFlow,
11    policy: &TargetIdPolicy,
12    role: IdentityRole,
13    to: IdentityRole,
14    source: Option<String>,
15    index: u64,
16) -> Result<String, TransformError> {
17    flow.resolve_as(
18        role,
19        to,
20        SourceIdentity::new(crate::Dialect::Gemini, source, index),
21        policy,
22    )
23    .map(|v| v.emitted_id)
24    .map_err(|e| TransformError::shape("identity", e.to_string()))
25}
26
27/// The Gemini upstream a Responses request is prepared for, when the caller
28/// knows it. Nothing native is restored from it: a signature comes back in the
29/// client's own reasoning items (see `signature`).
30#[derive(Debug, Default, Clone)]
31pub struct GeminiReplayContext {
32    pub target: Option<IdentityTarget>,
33}
34
35/// The Gemini part a Responses function call stands for, built from what the
36/// client sent. A signature is added by the caller from its carrier.
37pub(crate) fn function(call: r::FunctionCall) -> Result<g::Part, TransformError> {
38    if call.namespace.is_some()
39        || call
40            .caller
41            .flatten()
42            .is_some_and(|v| matches!(v, r::Caller::Program(_)))
43    {
44        return Err(TransformError::unsupported(
45            "function.scope",
46            "Gemini native tool caller requires adapter",
47        ));
48    }
49    let args: serde_json::Map<String, serde_json::Value> = serde_json::from_str(&call.arguments)
50        .map_err(|e| TransformError::shape("function.arguments", e.to_string()))?;
51    Ok(g::Part::builder()
52        .function_call(
53            g::FunctionCall::builder(call.name)
54                .id(call.call_id)
55                .args(args)
56                .build(),
57        )
58        .build())
59}
60
61/// The text a reasoning item shows: its content, or its summary without one.
62pub(crate) fn reasoning_text(value: &r::ReasoningItem) -> String {
63    match &value.content {
64        Some(content) => content.iter().map(|v| v.text.as_str()).collect(),
65        None => value.summary.iter().map(|v| v.text.as_str()).collect(),
66    }
67}