chore: import upstream snapshot with attribution
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:10:34 +08:00
commit a789495a98
1551 changed files with 718128 additions and 0 deletions
+748
View File
@@ -0,0 +1,748 @@
use anyhow::Result;
use jcode_message_types::{ContentBlock, Message, Role, ToolCall, ToolDefinition};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use std::collections::HashSet;
pub const DEFAULT_MODEL: &str = "gemini-2.5-pro";
pub const AVAILABLE_MODELS: &[&str] = &[
"gemini-3.1-pro-preview",
"gemini-3-pro-preview",
"gemini-3-flash-preview",
"gemini-2.5-pro",
"gemini-2.5-flash",
"gemini-2.0-flash",
"gemini-1.5-pro",
"gemini-1.5-flash",
];
pub const FALLBACK_MODELS: &[&str] = &[
"gemini-3.1-pro-preview",
"gemini-3-pro-preview",
"gemini-2.5-pro",
"gemini-3-flash-preview",
"gemini-2.5-flash",
"gemini-2.0-flash",
];
pub const CODE_ASSIST_ENDPOINT: &str = "https://cloudcode-pa.googleapis.com";
pub const CODE_ASSIST_API_VERSION: &str = "v1internal";
/// Official Gemini Developer API (Google AI Studio) endpoint. Used when an API
/// key is configured instead of OAuth Code Assist credentials.
pub const GEMINI_API_ENDPOINT: &str = "https://generativelanguage.googleapis.com";
pub const GEMINI_API_VERSION: &str = "v1beta";
pub const USER_TIER_FREE: &str = "free-tier";
pub const USER_TIER_LEGACY: &str = "legacy-tier";
#[derive(Debug, Clone)]
pub struct GeminiRuntimeState {
pub project_id: String,
pub session_id: String,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientMetadata {
pub ide_type: &'static str,
pub platform: &'static str,
pub plugin_type: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub duet_project: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LoadCodeAssistRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub cloudaicompanion_project: Option<String>,
pub metadata: ClientMetadata,
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<&'static str>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LoadCodeAssistResponse {
#[serde(default)]
pub current_tier: Option<GeminiUserTier>,
#[serde(default)]
pub allowed_tiers: Option<Vec<GeminiUserTier>>,
#[serde(default)]
pub ineligible_tiers: Option<Vec<IneligibleTier>>,
#[serde(default)]
pub cloudaicompanion_project: Option<String>,
#[serde(default)]
pub paid_tier: Option<GeminiUserTier>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GeminiUserTier {
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub is_default: Option<bool>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IneligibleTier {
#[serde(default)]
pub reason_code: Option<String>,
#[serde(default)]
pub reason_message: Option<String>,
#[serde(default)]
pub validation_url: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OnboardUserRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub tier_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cloudaicompanion_project: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<ClientMetadata>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LongRunningOperationResponse {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub done: Option<bool>,
#[serde(default)]
pub response: Option<OnboardUserResponse>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OnboardUserResponse {
#[serde(default)]
pub cloudaicompanion_project: Option<ProjectRef>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ProjectRef {
#[serde(default)]
pub id: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CodeAssistGenerateRequest {
pub model: String,
pub project: String,
pub user_prompt_id: String,
pub request: VertexGenerateContentRequest,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct VertexGenerateContentRequest {
pub contents: Vec<GeminiContent>,
#[serde(rename = "systemInstruction", skip_serializing_if = "Option::is_none")]
pub system_instruction: Option<GeminiContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<GeminiTool>>,
#[serde(rename = "toolConfig", skip_serializing_if = "Option::is_none")]
pub tool_config: Option<GeminiToolConfig>,
#[serde(rename = "session_id", skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeminiContent {
// Requests always set `role` (see `build_contents`), but `generateContent`
// responses occasionally omit it on a candidate's `content` (observed on
// Antigravity/Cloud Code Gemini-3 turns). The response-side value is never
// read, so default it rather than failing the whole decode with
// "missing field `role`".
#[serde(default)]
pub role: String,
#[serde(default)]
pub parts: Vec<GeminiPart>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct GeminiPart {
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub inline_data: Option<InlineData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub function_call: Option<GeminiFunctionCall>,
#[serde(skip_serializing_if = "Option::is_none")]
pub function_response: Option<GeminiFunctionResponse>,
/// Gemini 3 thought signature for this part. Must be replayed verbatim on
/// the `functionCall` part in later turns or the Cloud Code backend rejects
/// the request.
#[serde(skip_serializing_if = "Option::is_none")]
pub thought_signature: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InlineData {
pub mime_type: String,
pub data: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeminiFunctionCall {
pub name: String,
#[serde(default)]
pub args: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeminiFunctionResponse {
pub name: String,
pub response: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
}
pub fn build_system_instruction(system: &str) -> Option<GeminiContent> {
let trimmed = system.trim();
if trimmed.is_empty() {
None
} else {
Some(GeminiContent {
role: "user".to_string(),
parts: vec![GeminiPart {
text: Some(trimmed.to_string()),
..Default::default()
}],
})
}
}
/// Prevention guidance appended to the Gemini system prompt when tools are
/// advertised. Gemini-3 "thinking" models intermittently emit Python-style
/// pseudo-code (e.g. `print(default_api.read(...))`) instead of a clean
/// `functionCall`, which the backend rejects with `MALFORMED_FUNCTION_CALL` and
/// empty content. Explicitly forbidding code/namespaces measurably reduces that
/// failure mode at no latency cost (see the Gemini function-calling guidance and
/// field reports of this exact behavior).
const GEMINI_FUNCTION_CALL_GUARD: &str = "\n\n## Function calling\n\
- When you call a tool, emit a native function call, not code. Never write \
Python (or any language) that calls the tool, and never wrap a call in \
print(...) or a code block.\n\
- Use the function name exactly as defined. Do not prepend `default_api.` \
or any other namespace to the function name.";
/// Build the Gemini `system_instruction`, appending [`GEMINI_FUNCTION_CALL_GUARD`]
/// when tools are advertised so the model is steered away from the
/// `MALFORMED_FUNCTION_CALL` pseudo-code failure mode.
pub fn build_system_instruction_with_tool_guard(
system: &str,
has_tools: bool,
) -> Option<GeminiContent> {
if !has_tools {
return build_system_instruction(system);
}
let mut combined = system.trim().to_string();
combined.push_str(GEMINI_FUNCTION_CALL_GUARD);
build_system_instruction(&combined)
}
pub fn build_contents(messages: &[Message]) -> Vec<GeminiContent> {
// Gemini-3 attaches an opaque `thoughtSignature` to function-call parts, and
// the Cloud Code / Antigravity backend rejects an assistant turn whose
// function calls are ALL unsigned with `Function call is missing a
// thought_signature in functionCall parts` (HTTP 400, issue #339). This
// happens because:
// * a parallel multi-call turn only signs its FIRST call (siblings persist
// unsigned), and
// * locally synthesized tool calls (batch sub-calls, manual tool use,
// auto-poke continuations, recovery) and pre-signature/imported sessions
// carry no signature at all.
//
// Live-verified backend rule: a turn is accepted as long as *at least one*
// of its function calls carries a (valid) signature; a fully-unsigned turn
// 400s. All calls in a session share the same opaque reasoning channel and
// the backend accepts a previously-emitted signature replayed on later
// calls, so we carry the most recent real signature forward across the whole
// conversation onto any function call that lacks one. This keeps multi-call
// turns and synthesized/imported histories replayable instead of hard-failing.
let mut last_signature: Option<String> = None;
messages
.iter()
.filter_map(|message| {
let role = match message.role {
Role::User => "user",
Role::Assistant => "model",
};
let mut parts = Vec::new();
for block in &message.content {
match block {
ContentBlock::Text { text, .. } => {
parts.push(GeminiPart {
text: Some(text.clone()),
..Default::default()
});
}
ContentBlock::Reasoning { .. }
| ContentBlock::ReasoningTrace { .. }
| ContentBlock::AnthropicThinking { .. }
| ContentBlock::OpenAIReasoning { .. } => {}
ContentBlock::ToolUse {
id,
name,
input,
thought_signature,
} => {
let own_signature = thought_signature
.as_ref()
.filter(|sig| !sig.is_empty())
.cloned();
if own_signature.is_some() {
last_signature = own_signature.clone();
}
let signature = own_signature.or_else(|| last_signature.clone());
parts.push(GeminiPart {
function_call: Some(GeminiFunctionCall {
name: name.clone(),
args: ToolCall::input_as_object(input),
id: Some(id.clone()),
}),
thought_signature: signature,
..Default::default()
});
}
ContentBlock::ToolResult {
tool_use_id,
content,
is_error,
} => {
parts.push(GeminiPart {
function_response: Some(GeminiFunctionResponse {
name: tool_name_from_tool_result(tool_use_id, messages),
response: if is_error.unwrap_or(false) {
json!({ "error": content })
} else {
json!({ "content": content })
},
id: Some(tool_use_id.clone()),
}),
..Default::default()
});
}
ContentBlock::Image { media_type, data } => {
parts.push(GeminiPart {
inline_data: Some(InlineData {
mime_type: media_type.clone(),
data: data.clone(),
}),
..Default::default()
});
}
ContentBlock::OpenAICompaction { .. } => {}
}
}
if parts.is_empty() {
None
} else {
Some(GeminiContent {
role: role.to_string(),
parts,
})
}
})
.collect()
}
fn tool_name_from_tool_result(tool_use_id: &str, messages: &[Message]) -> String {
for message in messages.iter().rev() {
for block in &message.content {
if let ContentBlock::ToolUse { id, name, .. } = block
&& id == tool_use_id
{
return name.clone();
}
}
}
"tool".to_string()
}
pub fn build_tools(tools: &[ToolDefinition]) -> Option<Vec<GeminiTool>> {
if tools.is_empty() {
return None;
}
Some(vec![GeminiTool {
function_declarations: tools
.iter()
.map(|tool| GeminiFunctionDeclaration {
name: tool.name.clone(),
// Prompt-visible. Approximate token cost for this field:
// tool.description_token_estimate().
description: tool.description.clone(),
parameters: gemini_compatible_schema(&tool.input_schema),
})
.collect(),
}])
}
/// JSON Schema keywords the Gemini Code Assist `generateContent` endpoint
/// rejects outright (HTTP 400 "Unknown name ... Cannot find field"). Gemini
/// accepts only an OpenAPI 3.0 subset for `function_declarations.parameters`,
/// so these draft-style keywords must be stripped before sending.
const GEMINI_UNSUPPORTED_SCHEMA_KEYS: &[&str] = &[
"additionalProperties",
"$schema",
"$id",
"$ref",
"$defs",
"definitions",
"$comment",
];
fn gemini_compatible_schema(schema: &Value) -> Value {
match schema {
Value::Object(map) => {
let mut out = serde_json::Map::new();
for (key, value) in map {
// Drop draft-JSON-Schema keywords the Gemini API does not model;
// leaving them in fails the whole request with HTTP 400.
if GEMINI_UNSUPPORTED_SCHEMA_KEYS.contains(&key.as_str()) {
continue;
}
if key == "const" {
out.insert(
"enum".to_string(),
Value::Array(vec![gemini_compatible_schema(value)]),
);
} else {
out.insert(key.clone(), gemini_compatible_schema(value));
}
}
Value::Object(out)
}
Value::Array(items) => Value::Array(items.iter().map(gemini_compatible_schema).collect()),
_ => schema.clone(),
}
}
#[derive(Debug, Clone, Serialize)]
pub struct GeminiTool {
#[serde(rename = "functionDeclarations")]
pub function_declarations: Vec<GeminiFunctionDeclaration>,
}
#[derive(Debug, Clone, Serialize)]
pub struct GeminiFunctionDeclaration {
pub name: String,
pub description: String,
pub parameters: Value,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GeminiToolConfig {
pub function_calling_config: GeminiFunctionCallingConfig,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GeminiFunctionCallingConfig {
pub mode: &'static str,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeAssistGenerateResponse {
#[serde(default)]
pub trace_id: Option<String>,
#[serde(default)]
pub response: Option<VertexGenerateContentResponse>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VertexGenerateContentResponse {
#[serde(default)]
pub candidates: Option<Vec<GeminiCandidate>>,
#[serde(default)]
pub prompt_feedback: Option<GeminiPromptFeedback>,
#[serde(default)]
pub usage_metadata: Option<GeminiUsageMetadata>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GeminiCandidate {
#[serde(default)]
pub content: Option<GeminiContent>,
#[serde(default)]
pub finish_reason: Option<String>,
#[serde(default)]
pub finish_message: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GeminiPromptFeedback {
#[serde(default)]
pub block_reason: Option<String>,
#[serde(default)]
pub block_reason_message: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GeminiUsageMetadata {
#[serde(default)]
pub prompt_token_count: Option<u64>,
#[serde(default)]
pub candidates_token_count: Option<u64>,
#[serde(default)]
pub cached_content_token_count: Option<u64>,
}
pub fn gemini_fallback_models(current_model: &str) -> Vec<&'static str> {
FALLBACK_MODELS
.iter()
.copied()
.filter(|candidate| !candidate.eq_ignore_ascii_case(current_model))
.collect()
}
pub fn google_cloud_project_from_env() -> Option<String> {
std::env::var("GOOGLE_CLOUD_PROJECT")
.ok()
.or_else(|| std::env::var("GOOGLE_CLOUD_PROJECT_ID").ok())
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
}
pub fn load_code_assist_request(
project_id: Option<String>,
metadata: ClientMetadata,
) -> LoadCodeAssistRequest {
LoadCodeAssistRequest {
cloudaicompanion_project: project_id,
metadata,
mode: None,
}
}
pub fn merge_gemini_model_lists(models: Vec<String>) -> Vec<String> {
let mut seen = HashSet::new();
let mut preferred = Vec::new();
for known in AVAILABLE_MODELS {
if models.iter().any(|model| model == known) && seen.insert((*known).to_string()) {
preferred.push((*known).to_string());
}
}
let mut extras: Vec<String> = models
.into_iter()
.map(|model| model.trim().to_string())
.filter(|model| is_gemini_model_id(model) && seen.insert(model.clone()))
.collect();
extras.sort();
preferred.extend(extras);
preferred
}
pub fn extract_gemini_model_ids(value: &Value) -> Vec<String> {
let mut found = HashSet::new();
collect_gemini_model_ids(value, &mut found);
merge_gemini_model_lists(found.into_iter().collect())
}
fn collect_gemini_model_ids(value: &Value, found: &mut HashSet<String>) {
match value {
Value::String(raw) => {
let trimmed = raw.trim();
if is_gemini_model_id(trimmed) {
found.insert(trimmed.to_string());
}
}
Value::Array(items) => {
for item in items {
collect_gemini_model_ids(item, found);
}
}
Value::Object(map) => {
for item in map.values() {
collect_gemini_model_ids(item, found);
}
}
Value::Null | Value::Bool(_) | Value::Number(_) => {}
}
}
pub fn is_gemini_model_id(value: &str) -> bool {
let trimmed = value.trim();
!trimmed.is_empty()
&& trimmed.starts_with("gemini-")
&& trimmed
.bytes()
.all(|byte| matches!(byte, b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_'))
}
pub fn client_metadata(project_id: Option<String>) -> ClientMetadata {
ClientMetadata {
ide_type: "IDE_UNSPECIFIED",
platform: "PLATFORM_UNSPECIFIED",
plugin_type: "GEMINI",
duet_project: project_id,
}
}
pub fn validate_load_code_assist_response(res: &LoadCodeAssistResponse) -> Result<()> {
if res.current_tier.is_none()
&& let Some(validation) = res.ineligible_tiers.as_ref().and_then(|tiers| {
tiers.iter().find(|tier| {
tier.reason_code.as_deref() == Some("VALIDATION_REQUIRED")
&& tier.validation_url.is_some()
})
})
{
let description = validation
.reason_message
.clone()
.unwrap_or_else(|| "Account validation required".to_string());
let url = validation.validation_url.clone().unwrap_or_default();
anyhow::bail!("{description}. Complete account validation: {url}");
}
Ok(())
}
pub fn ineligible_or_project_error(res: &LoadCodeAssistResponse) -> anyhow::Error {
if let Some(reasons) = res
.ineligible_tiers
.as_ref()
.filter(|tiers| !tiers.is_empty())
{
let joined = reasons
.iter()
.filter_map(|tier| tier.reason_message.as_deref())
.collect::<Vec<_>>()
.join(", ");
return anyhow::anyhow!(joined);
}
anyhow::anyhow!(
"This Google account requires setting GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID. See Gemini Code Assist Workspace auth docs."
)
}
pub fn choose_onboard_tier(res: &LoadCodeAssistResponse) -> GeminiUserTier {
if let Some(default_tier) = res.allowed_tiers.as_ref().and_then(|tiers| {
tiers
.iter()
.find(|tier| tier.is_default.unwrap_or(false))
.cloned()
}) {
return default_tier;
}
GeminiUserTier {
id: Some(USER_TIER_LEGACY.to_string()),
name: Some(String::new()),
is_default: None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn fallback_models_skip_current_model() {
assert_eq!(
gemini_fallback_models("gemini-2.5-flash"),
vec![
"gemini-3.1-pro-preview",
"gemini-3-pro-preview",
"gemini-2.5-pro",
"gemini-3-flash-preview",
"gemini-2.0-flash",
]
);
}
#[test]
fn extract_gemini_model_ids_discovers_nested_models() {
let response = json!({
"routing": {
"manual": {
"models": [
{"id": "gemini-3-pro-preview"},
{"name": "gemini-3.1-pro-preview"}
]
},
"auto": ["gemini-3-flash-preview", "not-a-model"]
}
});
assert_eq!(
extract_gemini_model_ids(&response),
vec![
"gemini-3.1-pro-preview".to_string(),
"gemini-3-pro-preview".to_string(),
"gemini-3-flash-preview".to_string(),
]
);
}
#[test]
fn candidate_content_decodes_without_role() {
// Antigravity/Cloud Code Gemini-3 responses occasionally omit `role` on
// a candidate's `content` (and sometimes `parts` entirely). The whole
// generateContent decode used to fail with "missing field `role`",
// which aborted the turn; assert the response now decodes and the
// function call survives.
let raw = json!({
"response": {
"candidates": [{
"content": {
"parts": [{
"functionCall": {"name": "read", "args": {"file_path": "/tmp/x"}},
"thoughtSignature": "SIG_XYZ"
}]
},
"finishReason": "STOP"
}]
}
})
.to_string();
let decoded: CodeAssistGenerateResponse =
serde_json::from_str(&raw).expect("decode response with role-less content");
let candidates = decoded.response.unwrap().candidates.unwrap();
let part = &candidates[0].content.as_ref().unwrap().parts[0];
assert_eq!(part.function_call.as_ref().unwrap().name, "read");
assert_eq!(part.thought_signature.as_deref(), Some("SIG_XYZ"));
}
#[test]
fn candidate_content_decodes_without_parts() {
// A bare `content: {}` (no `role`, no `parts`) must not abort the decode.
let raw = json!({
"response": {
"candidates": [{ "content": {}, "finishReason": "STOP" }]
}
})
.to_string();
let decoded: CodeAssistGenerateResponse =
serde_json::from_str(&raw).expect("decode response with empty content");
let content = decoded.response.unwrap().candidates.unwrap()[0]
.content
.clone()
.unwrap();
assert!(content.role.is_empty());
assert!(content.parts.is_empty());
}
}