Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bindings/typescript/src/generated/google/Level.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

/**
* The media resolution level.
* The tokenization quality used for given media. for Gemini API support .
*/
export type Level = "MEDIA_RESOLUTION_HIGH" | "MEDIA_RESOLUTION_LOW" | "MEDIA_RESOLUTION_MEDIUM" | "MEDIA_RESOLUTION_ULTRA_HIGH" | "MEDIA_RESOLUTION_UNSPECIFIED";
4 changes: 2 additions & 2 deletions bindings/typescript/src/generated/google/Part.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import type { ExecutableCode } from "./ExecutableCode";
import type { FileData } from "./FileData";
import type { FunctionCall } from "./FunctionCall";
import type { FunctionResponse } from "./FunctionResponse";
import type { MediaResolution } from "./MediaResolution";
import type { ToolCall } from "./ToolCall";
import type { ToolResponse } from "./ToolResponse";
import type { V1MainMediaResolution } from "./V1MainMediaResolution";
import type { VideoMetadata } from "./VideoMetadata";

/**
Expand Down Expand Up @@ -47,7 +47,7 @@ inlineData: Blob | null,
/**
* Optional. Media resolution for the input media.
*/
mediaResolution: MediaResolution | null,
mediaResolution: V1MainMediaResolution | null,
/**
* Custom metadata associated with the Part. Agents using genai.Part as content
* representation may need to keep track of the additional information. For example it can
Expand Down
13 changes: 13 additions & 0 deletions bindings/typescript/src/generated/google/V1MainMediaResolution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Level } from "./Level";

/**
* Optional. Media resolution for the input media.
*
* Media resolution for tokenization.
*/
export type V1MainMediaResolution = {
/**
* The tokenization quality used for given media. for Gemini API support .
*/
level: Level | null, };
100 changes: 62 additions & 38 deletions crates/generate-types/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1747,11 +1747,7 @@ fn add_google_schema_with_dependencies(

processed.insert(type_name.to_string());

let Some(schema) = all_schemas
.get(type_name)
.cloned()
.or_else(|| google_missing_discovery_schema(type_name))
else {
let Some(schema) = all_schemas.get(type_name).cloned() else {
return;
};

Expand Down Expand Up @@ -1779,39 +1775,6 @@ fn add_google_schema_with_dependencies(
}
}

fn google_missing_discovery_schema(type_name: &str) -> Option<serde_json::Value> {
match type_name {
// The live Google Discovery spec references MediaResolution from Part but does not
// currently include a MediaResolution entry in schemas. Preserve the schema shape
// from prior Discovery specs so generation can remain fully typed.
"MediaResolution" => Some(serde_json::json!({
"description": "Media resolution for the input media.",
"type": "object",
"properties": {
"level": {
"description": "The media resolution level.",
"type": "string",
"enum": [
"MEDIA_RESOLUTION_UNSPECIFIED",
"MEDIA_RESOLUTION_LOW",
"MEDIA_RESOLUTION_MEDIUM",
"MEDIA_RESOLUTION_HIGH",
"MEDIA_RESOLUTION_ULTRA_HIGH"
],
"enumDescriptions": [
"Media resolution has not been set.",
"Media resolution set to low.",
"Media resolution set to medium.",
"Media resolution set to high.",
"Media resolution set to ultra high."
]
}
}
})),
_ => None,
}
}

fn extract_discovery_refs(value: &serde_json::Value, refs: &mut std::collections::HashSet<String>) {
match value {
serde_json::Value::Object(obj) => {
Expand Down Expand Up @@ -2335,3 +2298,64 @@ mod google_post_process_tests {
assert!(!output.contains("ModeNone"));
}
}

#[cfg(test)]
mod google_schema_walk_tests {
use super::add_google_schema_with_dependencies;
use big_serde_json as serde_json;

fn discovery_schemas() -> serde_json::Map<String, serde_json::Value> {
let spec_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../specs/google/discovery.json");
let spec: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(&spec_path)
.expect("vendored Discovery spec should be readable"),
)
.expect("vendored Discovery spec should be valid JSON");

spec.get("schemas")
.and_then(|schemas| schemas.as_object())
.expect("Discovery spec should define schemas")
.clone()
}

fn walk(root: &str) -> serde_json::Map<String, serde_json::Value> {
let all_schemas = discovery_schemas();
let mut essential = serde_json::Map::new();
let mut processed = std::collections::HashSet::new();
add_google_schema_with_dependencies(root, &all_schemas, &mut essential, &mut processed);
essential
}

#[test]
fn google_part_media_resolution_resolves_from_spec_without_fallback() {
let essential = walk("Part");

// Part's media resolution now points at a schema the spec actually defines, so no
// hand-written substitute is needed to keep generation fully typed.
let media_resolution_ref = essential["Part"]["properties"]["mediaResolution"]["$ref"]
.as_str()
.expect("Part.mediaResolution should be a $ref");
assert_eq!(media_resolution_ref, "V1mainMediaResolution");
assert!(essential.contains_key("V1mainMediaResolution"));

// The vacated name must not reappear: it now belongs to GenerationConfig's inline enum,
// so re-injecting an object schema under it would collide.
assert!(!essential.contains_key("MediaResolution"));

let levels = essential["V1mainMediaResolution"]["properties"]["level"]["enum"]
.as_array()
.expect("V1mainMediaResolution.level should be an enum");
assert!(levels
.iter()
.any(|level| level == "MEDIA_RESOLUTION_ULTRA_HIGH"));
}

#[test]
fn google_schema_walk_skips_names_the_spec_does_not_define() {
// A $ref the spec never defines resolves to nothing instead of a substituted schema,
// so generation fails visibly rather than silently generating stale types.
let essential = walk("MediaResolution");
assert!(essential.is_empty());
}
}
35 changes: 35 additions & 0 deletions crates/lingua/src/providers/google/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,41 @@ mod tests {
assert_eq!(tool_choice.mode, Some(ToolChoiceMode::Auto));
}

#[test]
fn test_google_tool_choice_none_is_distinct_from_absent_tool_config() {
let adapter = GoogleAdapter;
let banned = json!({
"contents": [{
"role": "user",
"parts": [{"text": "Hello"}]
}],
"toolConfig": {
"functionCallingConfig": {
"mode": "NONE"
}
}
});

let universal = adapter.request_to_universal(banned).unwrap();
let tool_choice = universal
.params
.tool_choice
.expect("explicit NONE must survive as a tool choice");
assert_eq!(tool_choice.mode, Some(ToolChoiceMode::None));

// An omitted toolConfig means "no preference", which is not the same request as an
// explicit ban on calling tools.
let unspecified = json!({
"contents": [{
"role": "user",
"parts": [{"text": "Hello"}]
}]
});

let universal = adapter.request_to_universal(unspecified).unwrap();
assert!(universal.params.tool_choice.is_none());
}

#[test]
fn test_google_tool_choice_from_universal() {
let adapter = GoogleAdapter;
Expand Down
Loading
Loading