Skip to content

Commit 197e417

Browse files
committed
stuff
1 parent ce42536 commit 197e417

7 files changed

Lines changed: 73 additions & 4 deletions

File tree

bindings/typescript/src/generated/AssistantContentPart.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ export type AssistantContentPart = { "type": "text" } & TextContentPart | { "typ
1313
* Providers will occasionally return encrypted content for reasoning parts which can
1414
* be useful when you send a follow up message.
1515
*/
16-
encrypted_content?: string, } | { "type": "tool_call", tool_call_id: string, tool_name: string, arguments: ToolCallArguments, status?: string, caller?: ToolCaller, encrypted_content?: string, provider_options?: ProviderOptions, provider_executed?: boolean, } | { "type": "builtin_tool_call", tool_call_id?: string, tool_name?: string, builtin_tool: BuiltinToolIdentity, arguments?: ToolCallArguments, status?: string, provider_options?: ProviderOptions, provider_executed?: boolean, } | { "type": "program", call_id: string, code: string, fingerprint?: string, id?: string, } | { "type": "program_output", call_id: string, result: string, status: string, id?: string, } | { "type": "tool_discovery_call", tool_call_id: string, discovery_tool_name: string, query?: string, arguments?: unknown, status?: string, execution?: string, provider_options?: ProviderOptions, } | { "type": "tool_result", tool_call_id: string, tool_name: string, output: unknown, caller?: ToolCaller, provider_options?: ProviderOptions, };
16+
encrypted_content?: string, } | { "type": "tool_call", tool_call_id: string, tool_name: string, arguments: ToolCallArguments, status?: string, caller?: ToolCaller, encrypted_content?: string, provider_options?: ProviderOptions, provider_executed?: boolean, } | { "type": "builtin_tool_call", tool_call_id?: string, tool_name?: string, builtin_tool: BuiltinToolIdentity, arguments?: ToolCallArguments, status?: string, encrypted_content?: string, provider_options?: ProviderOptions, provider_executed?: boolean, } | { "type": "program", call_id: string, code: string, fingerprint?: string, id?: string, } | { "type": "program_output", call_id: string, result: string, status: string, id?: string, } | { "type": "tool_discovery_call", tool_call_id: string, discovery_tool_name: string, query?: string, arguments?: unknown, status?: string, execution?: string, provider_options?: ProviderOptions, } | { "type": "tool_result", tool_call_id: string, tool_name: string, output: unknown, caller?: ToolCaller, provider_options?: ProviderOptions, };

crates/lingua/src/processing/dedup.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,13 +191,15 @@ fn hash_assistant_content(content: &AssistantContent, hasher: &mut DefaultHasher
191191
builtin_tool,
192192
arguments,
193193
status,
194+
encrypted_content,
194195
..
195196
} => {
196197
"builtin_tool_call".hash(hasher);
197198
tool_call_id.hash(hasher);
198199
tool_name.hash(hasher);
199200
builtin_tool.hash(hasher);
200201
status.hash(hasher);
202+
encrypted_content.hash(hasher);
201203
match arguments {
202204
Some(crate::universal::ToolCallArguments::Valid(map)) => {
203205
"valid".hash(hasher);

crates/lingua/src/processing/transform.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,7 @@ fn assistant_content_to_stream_delta(content: &AssistantContent) -> UniversalStr
777777
tool_name,
778778
builtin_tool,
779779
arguments,
780+
encrypted_content,
780781
..
781782
} => {
782783
let tool_call_index = tool_calls.len() as u32;
@@ -794,6 +795,9 @@ fn assistant_content_to_stream_delta(content: &AssistantContent) -> UniversalStr
794795
arguments: arguments.as_ref().map(ToString::to_string),
795796
}),
796797
});
798+
if reasoning_signature.is_none() {
799+
reasoning_signature = encrypted_content.clone();
800+
}
797801
}
798802
AssistantContentPart::File { .. }
799803
| AssistantContentPart::ToolResult { .. }

crates/lingua/src/providers/google/adapter.rs

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -645,9 +645,13 @@ impl ProviderAdapter for GoogleAdapter {
645645
..
646646
} = m
647647
{
648-
parts
649-
.iter()
650-
.any(|p| matches!(p, AssistantContentPart::ToolCall { .. }))
648+
parts.iter().any(|p| {
649+
matches!(
650+
p,
651+
AssistantContentPart::ToolCall { .. }
652+
| AssistantContentPart::BuiltinToolCall { .. }
653+
)
654+
})
651655
} else {
652656
false
653657
}
@@ -1781,6 +1785,48 @@ mod tests {
17811785
assert_eq!(back_typed.model_version.as_deref(), Some("gemini-1.5"));
17821786
}
17831787

1788+
#[test]
1789+
fn test_google_response_builtin_tool_call_sets_tool_calls_finish_reason() {
1790+
let adapter = GoogleAdapter;
1791+
let payload = json!({
1792+
"candidates": [{
1793+
"content": {
1794+
"role": "model",
1795+
"parts": [{
1796+
"thoughtSignature": "google_builtin_signature",
1797+
"toolCall": {
1798+
"toolType": "GOOGLE_SEARCH_WEB",
1799+
"args": {"query": "Lingua"}
1800+
}
1801+
}]
1802+
},
1803+
"finishReason": "STOP"
1804+
}]
1805+
});
1806+
1807+
let universal = adapter.response_to_universal(payload).unwrap();
1808+
1809+
assert_eq!(universal.finish_reason, Some(FinishReason::ToolCalls));
1810+
assert_eq!(universal.finish_reasons, vec![FinishReason::Stop]);
1811+
let Message::Assistant {
1812+
content: AssistantContent::Array(parts),
1813+
..
1814+
} = &universal.messages[0]
1815+
else {
1816+
panic!("expected assistant content parts");
1817+
};
1818+
let AssistantContentPart::BuiltinToolCall {
1819+
encrypted_content, ..
1820+
} = &parts[0]
1821+
else {
1822+
panic!("expected built-in tool call");
1823+
};
1824+
assert_eq!(
1825+
encrypted_content.as_deref(),
1826+
Some("google_builtin_signature")
1827+
);
1828+
}
1829+
17841830
#[test]
17851831
fn test_google_stream_tool_call_sets_tool_calls_finish_reason() {
17861832
let adapter = GoogleAdapter;

crates/lingua/src/providers/google/convert.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ impl TryFromLLM<GoogleContent> for Message {
287287
builtin_tool: builtin_identity_from_google_tool_type(tool_type),
288288
arguments: tool_call.args.clone().map(ToolCallArguments::Valid),
289289
status: None,
290+
encrypted_content: part.thought_signature.clone(),
290291
provider_options: None,
291292
provider_executed: Some(true),
292293
});
@@ -664,6 +665,7 @@ impl TryFromLLM<Message> for GoogleContent {
664665
tool_name,
665666
builtin_tool,
666667
arguments,
668+
encrypted_content,
667669
provider_executed,
668670
..
669671
} => {
@@ -696,6 +698,7 @@ impl TryFromLLM<Message> for GoogleContent {
696698
)?,
697699
),
698700
}),
701+
thought_signature: encrypted_content,
699702
..Default::default()
700703
});
701704
}
@@ -2028,6 +2031,7 @@ mod tests {
20282031
tool_name: None,
20292032
tool_type: Some(GoogleToolType::GoogleSearchWeb),
20302033
}),
2034+
thought_signature: Some("google_builtin_signature".to_string()),
20312035
..Default::default()
20322036
}]),
20332037
};
@@ -2045,6 +2049,7 @@ mod tests {
20452049
tool_call_id,
20462050
tool_name,
20472051
builtin_tool,
2052+
encrypted_content,
20482053
provider_executed,
20492054
..
20502055
} = &parts[0]
@@ -2055,6 +2060,10 @@ mod tests {
20552060
assert_eq!(tool_name, &None);
20562061
assert_eq!(builtin_tool.provider, BuiltinToolProvider::Google);
20572062
assert_eq!(builtin_tool.builtin_type, "GOOGLE_SEARCH_WEB");
2063+
assert_eq!(
2064+
encrypted_content.as_deref(),
2065+
Some("google_builtin_signature")
2066+
);
20582067
assert_eq!(*provider_executed, Some(true));
20592068

20602069
let roundtrip = <GoogleContent as TryFromLLM<Message>>::try_from(universal)

crates/lingua/src/universal/message.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ pub enum AssistantContentPart {
128128
#[ts(optional)]
129129
status: Option<String>,
130130
#[ts(optional)]
131+
encrypted_content: Option<String>,
132+
#[ts(optional)]
131133
provider_options: Option<ProviderOptions>,
132134
#[ts(optional)]
133135
provider_executed: Option<bool>,

plan.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
- The Google adapter lifts a small canonical subset of `generationConfig` and discards every other typed field, including `audioTranscriptionConfig` and request-level `mediaResolution`.
88
- Google streaming conversion only inspects `Part.functionCall`, so native `Part.toolCall` chunks are silently reduced to empty assistant deltas and may receive the wrong finish reason.
99
- Google declares built-in `ToolCall.id` and `ToolResponse.id` optional, but the universal built-in parts currently require a string and the converter rejects valid provider payloads with no ID.
10+
- Non-streaming Google built-in tool calls discard `Part.thoughtSignature`, so replaying provider-executed call history can fail Gemini signature validation.
11+
- Non-streaming response finish-reason detection recognizes only ordinary function calls, so a built-in call paired with Google `STOP` is incorrectly classified as a completed turn.
1012

1113
## Target files
1214

@@ -27,6 +29,8 @@
2729
- Dedicated universal builtin-tool call and result parts carry an optional free-form name plus a typed identity (`provider` and `builtin_type`). Google server-side tool calls/results round-trip with `provider_executed: true` without fabricating a function name, while ordinary function-tool parts remain source-compatible.
2830
- Built-in call/result correlation IDs are optional so a missing Google ID round-trips as absent rather than being rejected or synthesized. Real IDs remain unchanged.
2931
- Native Google streaming `toolCall` parts become typed universal built-in tool-call deltas, set the `tool_calls` finish reason, and round-trip back to Google. Streaming targets that cannot represent the built-in identity fail explicitly instead of treating it as a function call.
32+
- Built-in calls carry optional opaque `encrypted_content`, allowing Google `thoughtSignature` values to survive non-streaming and full-response streaming roundtrips.
33+
- Non-streaming responses containing either ordinary or built-in tool calls use the canonical `ToolCalls` finish reason.
3034
- Providers that cannot represent a provider-executed builtin return an explicit unsupported-mapping error instead of silently dropping it.
3135
- Google-to-universal preserves only the unmapped, typed remainder of `generationConfig` in Google-scoped extras. Universal-to-Google starts from that typed remainder and lets canonical fields override it, avoiding duplicate sources of truth.
3236
- The accepted REST `audioTranscriptionConfig` subtree and request-level `mediaResolution` survive Google round trips byte-for-byte at the semantic JSON level.
@@ -37,6 +41,8 @@
3741
- Add Google converter tests for named and unnamed provider-executed builtin calls and responses.
3842
- Add Google converter tests for built-in calls and responses with absent IDs.
3943
- Add Google streaming tests for typed built-in call conversion, absent-ID preservation, Google roundtrip, finish-reason handling, and explicit rejection by non-Google targets.
44+
- Add a Google built-in call test with `thoughtSignature` and assert exact non-streaming roundtrip preservation.
45+
- Add a Google response test proving a built-in call overrides provider `STOP` with canonical `ToolCalls`.
4046
- Add Google params/adapter tests proving unmapped `generationConfig` fields survive while canonical temperature/reasoning/response-format values take precedence.
4147
- Keep payload cases `googleProviderExecutedToolRoundtrip` and `audioTranscriptionConfigParam`; recapture after the logic fix.
4248
- Update existing universal/provider tests for optional tool names and builtin identities.

0 commit comments

Comments
 (0)