This commit is contained in:
Sami Jawhar 2026-11-04 00:24:56 +00:00 • committed by GitHub
commit df7cea55dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 294 additions and 261 deletions

View file

@ -18,6 +18,11 @@ spent producing the tool request, so it is billed and settled where it stops
rather than polled until the timeout, which would both lose that usage and
hold its budget reservation open for the whole timeout window.
``requires_continuation`` is terminal for the same reason. A caller resumes a
long decode by creating a new interaction that carries the returned
``continuation_token``, and the stopped interaction keeps the tokens it already
spent decoding.
Deleting an interaction makes every subsequent poll fail, which would let a
caller retrieve the completed output themselves and then delete it before the
poll task settles, leaving the work unbilled and the budget reservation
@ -70,14 +75,14 @@ from litellm.types.utils import CustomPricingLiteLLMParams
if TYPE_CHECKING:
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
_TERMINAL_STATUSES: Final = frozenset(
{"completed", "failed", "cancelled", "incomplete", "budget_exceeded", "requires_action"}
_STATUSES_THAT_PRODUCED_OUTPUT: Final = frozenset({"completed", "requires_action", "requires_continuation"})
_TERMINAL_STATUSES: Final = _STATUSES_THAT_PRODUCED_OUTPUT | frozenset(
{"failed", "cancelled", "incomplete", "budget_exceeded"}
)
_POLLABLE_STATUSES: Final = frozenset({"in_progress", "queued"})
_STATUSES_THAT_PRODUCED_OUTPUT: Final = frozenset({"completed", "requires_action"})
SettlementOutcome: TypeAlias = Literal["billed", "released", "unsettled"]
@ -629,13 +634,12 @@ def missing_usage_is_expected(response: InteractionsAPIResponse) -> bool:
"""
Whether a response arriving with no usage block is a normal outcome rather
than lost billing data. An interaction that is still running, or that
stopped at ``failed``, ``cancelled``, ``incomplete`` or ``budget_exceeded``,
has nothing to charge for and should not raise a cost-tracking alarm.
stopped without producing output, has nothing to charge for and should not
raise a cost-tracking alarm.
``completed`` and ``requires_action`` both mean the model produced output,
so a usage block is always expected with them. If one arrives without it
the charge for real work has been lost, which is precisely what the
proxy's cost-tracking alert exists to surface.
A response in ``_STATUSES_THAT_PRODUCED_OUTPUT`` that arrives without a
usage block means the charge for real work has been lost, which is
precisely what the proxy's cost-tracking alert exists to surface.
"""
return response.status not in _STATUSES_THAT_PRODUCED_OUTPUT

View file

@ -16,8 +16,9 @@ files the issue "Interactions spec canary is red" on a red run and closes it onc
means the spec moved in a way these types no longer cover
When the red run names one new status, the small fix is a hand edit adding that member to the enum the failing
schema's model carries, the way `queued` was added in #34135. When the spec moved further than that, refresh the
snapshot and regenerate the types from it:
schema's model carries, the way `queued` was added in #34135. Refresh the snapshot in the same change, so the
compliance tests check the new member on every PR and not only in the canary. When the spec moved further than
that, refresh the snapshot and regenerate the types from it:
```bash
curl -fsSL https://ai.google.dev/static/api/interactions.openapi.json \

View file

@ -175,6 +175,7 @@ class Status1(Enum):
cancelled = "cancelled"
incomplete = "incomplete"
budget_exceeded = "budget_exceeded"
requires_continuation = "requires_continuation"
queued = "queued"
@ -342,6 +343,7 @@ class Status3(Enum):
CANCELLED = "cancelled"
INCOMPLETE = "incomplete"
BUDGET_EXCEEDED = "budget_exceeded"
REQUIRES_CONTINUATION = "requires_continuation"
QUEUED = "queued"

View file

@ -756,7 +756,7 @@
{
"label": "list",
"lang": "go",
"source": "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"google.golang.org/genai/interactions/models/operations\"\n)\n\nfunc main() {\n ctx := context.Background()\n client := interactions.New()\n\n res, err := client.Environments.ListEnvironments(ctx, operations.ListEnvironmentsRequest{})\n if err != nil {\n log.Fatal(err)\n }\n\n if res.ListEnvironmentsResponse != nil {\n for _, env := range res.ListEnvironmentsResponse.Environments {\n if env.ID != \"\" {\n fmt.Println(env.ID)\n }\n }\n }\n}\n"
"source": "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"google.golang.org/genai\"\n \"google.golang.org/genai/interactions/models/operations\"\n)\n\nfunc main() {\n ctx := context.Background()\n client, err := genai.NewClient(ctx, nil)\n if err != nil {\n log.Fatal(err)\n }\n\n res, err := client.Environments.ListEnvironments(ctx, operations.ListEnvironmentsRequest{})\n if err != nil {\n log.Fatal(err)\n }\n\n if res.ListEnvironmentsResponse != nil {\n for _, env := range res.ListEnvironmentsResponse.Environments {\n if env.ID != \"\" {\n fmt.Println(env.ID)\n }\n }\n }\n}\n"
}
]
},
@ -834,12 +834,12 @@
{
"label": "create",
"lang": "go",
"source": "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"google.golang.org/genai\"\n gaos_environments \"google.golang.org/genai/interactions/models/environments\"\n gaos_interactions \"google.golang.org/genai/interactions/models/interactions\"\n \"google.golang.org/genai/interactions/models/operations\"\n)\n\nfunc main() {\n ctx := context.Background()\n client := interactions.New()\n\n req := operations.CreateEnvironmentRequest{\n Body: gaos_environments.CreateEnvironmentRequest{\n Sources: []gaos_interactions.Source{\n {\n Type: genai.Ptr(gaos_interactions.SourceTypeInline),\n Target: genai.Ptr(\"main.py\"),\n Content: genai.Ptr(\"print('Hello, World!')\"),\n },\n },\n },\n }\n\n res, err := client.Environments.CreateEnvironment(ctx, req)\n if err != nil {\n log.Fatal(err)\n }\n\n if res.Environment != nil && res.Environment.ID != \"\" {\n fmt.Println(res.Environment.ID)\n\n }\n}\n"
"source": "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"google.golang.org/genai\"\n gaos_environments \"google.golang.org/genai/interactions/models/environments\"\n gaos_interactions \"google.golang.org/genai/interactions/models/interactions\"\n \"google.golang.org/genai/interactions/models/operations\"\n)\n\nfunc main() {\n ctx := context.Background()\n client, err := genai.NewClient(ctx, nil)\n if err != nil {\n log.Fatal(err)\n }\n\n req := operations.CreateEnvironmentRequest{\n Body: gaos_environments.CreateEnvironmentRequest{\n Sources: []gaos_interactions.Source{\n {\n Type: genai.Ptr(gaos_interactions.SourceTypeInline),\n Target: genai.Ptr(\"main.py\"),\n Content: genai.Ptr(\"print('Hello, World!')\"),\n },\n },\n },\n }\n\n res, err := client.Environments.CreateEnvironment(ctx, req)\n if err != nil {\n log.Fatal(err)\n }\n\n if res.Environment != nil && res.Environment.ID != \"\" {\n fmt.Println(res.Environment.ID)\n\n }\n}\n"
},
{
"label": "copy",
"lang": "go",
"source": "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"google.golang.org/genai\"\n gaos_environments \"google.golang.org/genai/interactions/models/environments\"\n \"google.golang.org/genai/interactions/models/operations\"\n)\n\nfunc main() {\n ctx := context.Background()\n client := interactions.New()\n\n\n res, err := client.Environments.CreateEnvironment(ctx, operations.CreateEnvironmentRequest{\n Body: gaos_environments.CreateEnvironmentRequest{\n FromEnvironment: genai.Ptr(sourceEnvID),\n },\n })\n if err != nil {\n log.Fatal(err)\n }\n\n if res.Environment != nil && res.Environment.ID != \"\" {\n fmt.Println(res.Environment.ID)\n\n }\n}\n"
"source": "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"google.golang.org/genai\"\n gaos_environments \"google.golang.org/genai/interactions/models/environments\"\n \"google.golang.org/genai/interactions/models/operations\"\n)\n\nfunc main() {\n ctx := context.Background()\n client, err := genai.NewClient(ctx, nil)\n if err != nil {\n log.Fatal(err)\n }\n\n\n res, err := client.Environments.CreateEnvironment(ctx, operations.CreateEnvironmentRequest{\n Body: gaos_environments.CreateEnvironmentRequest{\n FromEnvironment: genai.Ptr(sourceEnvID),\n },\n })\n if err != nil {\n log.Fatal(err)\n }\n\n if res.Environment != nil && res.Environment.ID != \"\" {\n fmt.Println(res.Environment.ID)\n\n }\n}\n"
}
]
}
@ -967,12 +967,12 @@
{
"label": "list_files",
"lang": "go",
"source": "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"google.golang.org/genai/interactions/models/operations\"\n)\n\nfunc main() {\n ctx := context.Background()\n client := interactions.New()\n\n\n res, err := client.Environments.Files.List(ctx, operations.GetEnvironmentFilesRequest{\n Environment: envID,\n Path: \"src\",\n })\n if err != nil {\n log.Fatal(err)\n }\n\n if res.GetEnvironmentFilesResponse != nil {\n for _, file := range res.GetEnvironmentFilesResponse.Files {\n name := \"\"\n if file.Name != nil {\n name = *file.Name\n }\n fileType := \"\"\n if file.Type != nil {\n fileType = string(*file.Type)\n }\n var size int64\n if file.SizeBytes != nil {\n size = *file.SizeBytes\n }\n fmt.Println(name, fileType, size)\n }\n }\n}\n"
"source": "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"google.golang.org/genai\"\n \"google.golang.org/genai/interactions/models/operations\"\n)\n\nfunc main() {\n ctx := context.Background()\n client, err := genai.NewClient(ctx, nil)\n if err != nil {\n log.Fatal(err)\n }\n\n\n res, err := client.Environments.Files.List(ctx, operations.GetEnvironmentFilesRequest{\n Environment: envID,\n Path: \"src\",\n })\n if err != nil {\n log.Fatal(err)\n }\n\n if res.GetEnvironmentFilesResponse != nil {\n for _, file := range res.GetEnvironmentFilesResponse.Files {\n name := \"\"\n if file.Name != nil {\n name = *file.Name\n }\n fileType := \"\"\n if file.Type != nil {\n fileType = string(*file.Type)\n }\n var size int64\n if file.SizeBytes != nil {\n size = *file.SizeBytes\n }\n fmt.Println(name, fileType, size)\n }\n }\n}\n"
},
{
"label": "get_file",
"lang": "go",
"source": "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"google.golang.org/genai/interactions/models/operations\"\n)\n\nfunc main() {\n ctx := context.Background()\n client := interactions.New()\n\n\n res, err := client.Environments.Files.List(ctx, operations.GetEnvironmentFilesRequest{\n Environment: envID,\n Path: \"main.py\",\n })\n if err != nil {\n log.Fatal(err)\n }\n\n if res.GetEnvironmentFilesResponse != nil {\n for _, file := range res.GetEnvironmentFilesResponse.Files {\n name := \"\"\n if file.Name != nil {\n name = *file.Name\n }\n var size int64\n if file.SizeBytes != nil {\n size = *file.SizeBytes\n }\n fmt.Println(name, size)\n }\n }\n}\n"
"source": "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"google.golang.org/genai\"\n \"google.golang.org/genai/interactions/models/operations\"\n)\n\nfunc main() {\n ctx := context.Background()\n client, err := genai.NewClient(ctx, nil)\n if err != nil {\n log.Fatal(err)\n }\n\n\n res, err := client.Environments.Files.List(ctx, operations.GetEnvironmentFilesRequest{\n Environment: envID,\n Path: \"main.py\",\n })\n if err != nil {\n log.Fatal(err)\n }\n\n if res.GetEnvironmentFilesResponse != nil {\n for _, file := range res.GetEnvironmentFilesResponse.Files {\n name := \"\"\n if file.Name != nil {\n name = *file.Name\n }\n var size int64\n if file.SizeBytes != nil {\n size = *file.SizeBytes\n }\n fmt.Println(name, size)\n }\n }\n}\n"
}
]
}
@ -1036,7 +1036,7 @@
{
"label": "delete",
"lang": "go",
"source": "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"google.golang.org/genai/interactions/models/operations\"\n)\n\nfunc main() {\n ctx := context.Background()\n client := interactions.New()\n\n\n _, err = client.Environments.DeleteEnvironment(ctx, operations.DeleteEnvironmentRequest{\n ID: envID,\n })\n if err != nil {\n log.Fatal(err)\n }\n fmt.Println(\"Environment deleted successfully.\")\n}\n"
"source": "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"google.golang.org/genai\"\n \"google.golang.org/genai/interactions/models/operations\"\n)\n\nfunc main() {\n ctx := context.Background()\n client, err := genai.NewClient(ctx, nil)\n if err != nil {\n log.Fatal(err)\n }\n\n\n _, err = client.Environments.DeleteEnvironment(ctx, operations.DeleteEnvironmentRequest{\n ID: envID,\n })\n if err != nil {\n log.Fatal(err)\n }\n fmt.Println(\"Environment deleted successfully.\")\n}\n"
}
]
},
@ -1093,7 +1093,7 @@
{
"label": "get",
"lang": "go",
"source": "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"google.golang.org/genai/interactions/models/operations\"\n)\n\nfunc main() {\n ctx := context.Background()\n client := interactions.New()\n\n\n res, err := client.Environments.GetEnvironment(ctx, operations.GetEnvironmentRequest{\n ID: envID,\n })\n if err != nil {\n log.Fatal(err)\n }\n\n if res.Environment != nil && res.Environment.ID != \"\" {\n fmt.Println(res.Environment.ID)\n }\n}\n"
"source": "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"google.golang.org/genai\"\n \"google.golang.org/genai/interactions/models/operations\"\n)\n\nfunc main() {\n ctx := context.Background()\n client, err := genai.NewClient(ctx, nil)\n if err != nil {\n log.Fatal(err)\n }\n\n\n res, err := client.Environments.GetEnvironment(ctx, operations.GetEnvironmentRequest{\n ID: envID,\n })\n if err != nil {\n log.Fatal(err)\n }\n\n if res.Environment != nil && res.Environment.ID != \"\" {\n fmt.Println(res.Environment.ID)\n }\n}\n"
}
]
}
@ -1171,7 +1171,7 @@
"deep_research": {
"summary": "Deep Research",
"value": {
"agent": "deep-research-pro-preview-12-2025",
"agent": "deep-research-preview-04-2026",
"created": "2025-11-26T12:22:47Z",
"id": "v1_ChdPU0F4YWFtNkFwS2kxZThQZ05lbXdROBIXT1NBeGFhbTZBcEtpMWU4UGdOZW13UTg",
"object": "interaction",
@ -1472,7 +1472,7 @@
{
"label": "deep_research",
"lang": "sh",
"source": "curl -X POST https://generativelanguage.googleapis.com/v1beta/interactions \\\n -H \"x-goog-api-key: $GEMINI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"agent\": \"deep-research-pro-preview-12-2025\",\n \"input\": \"Find a cure to cancer\",\n \"background\": true\n }'\n"
"source": "curl -X POST https://generativelanguage.googleapis.com/v1beta/interactions \\\n -H \"x-goog-api-key: $GEMINI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"agent\": \"deep-research-preview-04-2026\",\n \"input\": \"Find a cure to cancer\",\n \"background\": true\n }'\n"
},
{
"label": "antigravity",
@ -1517,7 +1517,7 @@
{
"label": "deep_research",
"lang": "python",
"source": "from google import genai\n\nclient = genai.Client()\ninteraction = client.interactions.create(\n agent=\"deep-research-pro-preview-12-2025\",\n input=\"find a cure to cancer\",\n background=True,\n)\nprint(interaction.status)\n"
"source": "from google import genai\n\nclient = genai.Client()\ninteraction = client.interactions.create(\n agent=\"deep-research-preview-04-2026\",\n input=\"find a cure to cancer\",\n background=True,\n)\nprint(interaction.status)\n"
},
{
"label": "antigravity",
@ -1562,7 +1562,7 @@
{
"label": "deep_research",
"lang": "javascript",
"source": "import {GoogleGenAI} from '@google/genai';\n\nconst ai = new GoogleGenAI({});\nconst interaction = await ai.interactions.create({\n agent: 'deep-research-pro-preview-12-2025',\n input: 'find a cure to cancer',\n background: true,\n});\nconsole.log(interaction.status);\n"
"source": "import {GoogleGenAI} from '@google/genai';\n\nconst ai = new GoogleGenAI({});\nconst interaction = await ai.interactions.create({\n agent: 'deep-research-preview-04-2026',\n input: 'find a cure to cancer',\n background: true,\n});\nconsole.log(interaction.status);\n"
},
{
"label": "antigravity",
@ -1607,7 +1607,7 @@
{
"label": "deep_research",
"lang": "java",
"source": "import com.google.genai.gaos.models.interactions.AgentOption;\nimport com.google.genai.Client;\nimport com.google.genai.gaos.models.interactions.CreateAgentInteraction;\nimport com.google.genai.gaos.models.operations.CreateInteractionRequestBody;\nimport com.google.genai.gaos.models.operations.CreateInteractionResponse;\nimport com.google.genai.gaos.models.interactions.Interaction;\nimport com.google.genai.gaos.models.interactions.InteractionStatus;\nimport com.google.genai.gaos.models.interactions.InteractionsInput;\n\nClient client = new Client();\nCreateAgentInteraction params =\n CreateAgentInteraction.builder()\n .agent(AgentOption.of(\"deep-research-pro-preview-12-2025\"))\n .input(InteractionsInput.of(\"find a cure to cancer\"))\n .background(true)\n .build();\nCreateInteractionResponse response =\n client.interactions.create(CreateInteractionRequestBody.of(params));\nInteraction interaction =\n response.interaction().orElseThrow(() -> new RuntimeException(\"No interaction returned\"));\nSystem.out.println(interaction.status().map(InteractionStatus::value).orElse(\"\"));\n"
"source": "import com.google.genai.gaos.models.interactions.AgentOption;\nimport com.google.genai.Client;\nimport com.google.genai.gaos.models.interactions.CreateAgentInteraction;\nimport com.google.genai.gaos.models.operations.CreateInteractionRequestBody;\nimport com.google.genai.gaos.models.operations.CreateInteractionResponse;\nimport com.google.genai.gaos.models.interactions.Interaction;\nimport com.google.genai.gaos.models.interactions.InteractionStatus;\nimport com.google.genai.gaos.models.interactions.InteractionsInput;\n\nClient client = new Client();\nCreateAgentInteraction params =\n CreateAgentInteraction.builder()\n .agent(AgentOption.of(\"deep-research-preview-04-2026\"))\n .input(InteractionsInput.of(\"find a cure to cancer\"))\n .background(true)\n .build();\nCreateInteractionResponse response =\n client.interactions.create(CreateInteractionRequestBody.of(params));\nInteraction interaction =\n response.interaction().orElseThrow(() -> new RuntimeException(\"No interaction returned\"));\nSystem.out.println(interaction.status().map(InteractionStatus::value).orElse(\"\"));\n"
},
{
"label": "antigravity",
@ -1652,7 +1652,7 @@
{
"label": "deep_research",
"lang": "go",
"source": "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"google.golang.org/genai\"\n gaos_interactions \"google.golang.org/genai/interactions/models/interactions\"\n \"google.golang.org/genai/interactions/models/operations\"\n)\n\nfunc main() {\n ctx := context.Background()\n client, err := genai.NewClient(ctx, nil)\n if err != nil {\n log.Fatal(err)\n }\n\n body := operations.NewCreateInteractionRequestBody(gaos_interactions.CreateAgentInteraction{\n Agent: gaos_interactions.AgentOption(\"deep-research-pro-preview-12-2025\"),\n Input: genai.Ptr(gaos_interactions.NewInteractionsInput(\"find a cure to cancer\")),\n Background: genai.Ptr(true),\n })\n\n req := operations.CreateInteractionRequest{\n Body: body,\n }\n\n res, err := client.Interactions.Create(ctx, req)\n if err != nil {\n log.Fatal(err)\n }\n\n if res.Interaction != nil {\n fmt.Println(res.Interaction.Status)\n }\n}\n"
"source": "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"google.golang.org/genai\"\n gaos_interactions \"google.golang.org/genai/interactions/models/interactions\"\n \"google.golang.org/genai/interactions/models/operations\"\n)\n\nfunc main() {\n ctx := context.Background()\n client, err := genai.NewClient(ctx, nil)\n if err != nil {\n log.Fatal(err)\n }\n\n body := operations.NewCreateInteractionRequestBody(gaos_interactions.CreateAgentInteraction{\n Agent: gaos_interactions.AgentOption(\"deep-research-preview-04-2026\"),\n Input: genai.Ptr(gaos_interactions.NewInteractionsInput(\"find a cure to cancer\")),\n Background: genai.Ptr(true),\n })\n\n req := operations.CreateInteractionRequest{\n Body: body,\n }\n\n res, err := client.Interactions.Create(ctx, req)\n if err != nil {\n log.Fatal(err)\n }\n\n if res.Interaction != nil {\n fmt.Println(res.Interaction.Status)\n }\n}\n"
},
{
"label": "antigravity",
@ -3041,6 +3041,22 @@
"description": "An agent definition for the CreateAgent API.\nThis message is the target for annotation-parser-based JSON parsing.\nNew format:\n {\n \"id\": \"customer-sentinel\",\n \"base_agent\": \"\",\n \"system_instruction\": \"...\",\n \"base_environment\": { \"type\": \"remote\", \"sources\": [...] },\n \"tools\": [ {\"type\": \"code_execution\"} ]\n }",
"type": "object",
"properties": {
"id": {
"description": "The unique identifier for the agent.",
"type": "string"
},
"base_agent": {
"description": "The base agent to extend.",
"type": "string"
},
"system_instruction": {
"description": "System instruction for the agent.",
"type": "string"
},
"description": {
"description": "Agent description for developers to quickly read and understand.",
"type": "string"
},
"agent_config": {
"description": "Configuration parameters for the agent.",
"oneOf": [
@ -3060,22 +3076,6 @@
}
]
},
"id": {
"description": "The unique identifier for the agent.",
"type": "string"
},
"base_agent": {
"description": "The base agent to extend.",
"type": "string"
},
"system_instruction": {
"description": "System instruction for the agent.",
"type": "string"
},
"description": {
"description": "Agent description for developers to quickly read and understand.",
"type": "string"
},
"tools": {
"description": "The tools available to the agent.",
"type": "array",
@ -3091,13 +3091,11 @@
"description": "The agent to interact with.",
"type": "string",
"enum": [
"deep-research-pro-preview-12-2025",
"deep-research-preview-04-2026",
"deep-research-max-preview-04-2026",
"antigravity-preview-05-2026"
],
"x-speakeasy-enum-descriptions": [
"Gemini Deep Research Agent",
"Gemini Deep Research Agent",
"Gemini Deep Research Max Agent",
"Use the Antigravity managed agent to perform multi-step tasks that require reasoning, file operations, and tool use."
@ -3330,14 +3328,6 @@
"type": {
"const": "audio"
},
"data": {
"type": "string",
"format": "byte",
"x-speakeasy-base64-input-mode": "file"
},
"uri": {
"type": "string"
},
"mime_type": {
"type": "string",
"enum": [
@ -3371,6 +3361,14 @@
"WEBM audio format"
]
},
"data": {
"type": "string",
"format": "byte",
"x-speakeasy-base64-input-mode": "file"
},
"uri": {
"type": "string"
},
"rate": {
"description": "Deprecated. Use sample_rate instead. The value is ignored.",
"type": "integer",
@ -3817,32 +3815,6 @@
}
]
},
"response_format": {
"description": "Enforces that the generated response is a JSON object that complies with\nthe JSON schema specified in this field.",
"oneOf": [
{
"$ref": "#/components/schemas/ResponseFormat"
},
{
"title": "ResponseFormatList",
"type": "array",
"items": {
"$ref": "#/components/schemas/ResponseFormat"
}
}
]
},
"environment": {
"description": "The environment configuration for the interaction. Can be an object\nspecifying remote environment sources or a string referencing an existing\nenvironment ID.",
"oneOf": [
{
"$ref": "#/components/schemas/EnvironmentConfig"
},
{
"type": "string"
}
]
},
"id": {
"description": "Required. Output only. A unique identifier for the interaction completion.",
"type": "string",
@ -3858,7 +3830,8 @@
"failed",
"cancelled",
"incomplete",
"budget_exceeded"
"budget_exceeded",
"requires_continuation"
],
"readOnly": true,
"x-google-enum-deprecated": [
@ -3868,7 +3841,8 @@
false,
false,
false,
true
true,
false
],
"x-google-enum-descriptions": [
"The interaction is in progress.",
@ -3877,7 +3851,8 @@
"The interaction failed.",
"The interaction was cancelled.",
"The interaction is completed, but contains incomplete results (e.g.\nhitting max_tokens).",
"Deprecated: Token and execution budget exhaustion returns INCOMPLETE\n(11)."
"Deprecated: Token and execution budget exhaustion returns INCOMPLETE\n(11).",
"The interaction requires at least one more request for it to be\ncompleted. The next request should set the continuation_token to the one\nreturned by this request."
]
},
"created": {
@ -3909,6 +3884,21 @@
"$ref": "#/components/schemas/ResponseModality"
}
},
"response_format": {
"description": "Enforces that the generated response is a JSON object that complies with\nthe JSON schema specified in this field.",
"oneOf": [
{
"$ref": "#/components/schemas/ResponseFormat"
},
{
"title": "ResponseFormatList",
"type": "array",
"items": {
"$ref": "#/components/schemas/ResponseFormat"
}
}
]
},
"response_mime_type": {
"description": "The mime type of the response. This is required if response_format is set.",
"type": "string",
@ -3918,6 +3908,17 @@
"description": "The ID of the previous interaction, if any.",
"type": "string"
},
"environment": {
"description": "The environment configuration for the interaction. Can be an object\nspecifying remote environment sources or a string referencing an existing\nenvironment ID.",
"oneOf": [
{
"$ref": "#/components/schemas/EnvironmentConfig"
},
{
"type": "string"
}
]
},
"environment_id": {
"description": "Output only. The environment ID for the interaction. Only populated if environment\nconfig is set in the request.",
"type": "string",
@ -3945,6 +3946,12 @@
"type": "string"
}
},
"continuation_token": {
"description": "Opaque token to resume a long decode. Output: set when status is\nREQUIRES_CONTINUATION and decoding can be resumed. Input: pass the latest\ntoken back unchanged in CreateInteraction to continue decoding.",
"type": "string",
"format": "byte",
"x-speakeasy-base64-input-mode": "file"
},
"stream": {
"description": "Input only. Whether the interaction will be streamed.",
"type": "boolean",
@ -4018,32 +4025,6 @@
"$ref": "#/components/schemas/GenerationConfig",
"writeOnly": true
},
"response_format": {
"description": "Enforces that the generated response is a JSON object that complies with\nthe JSON schema specified in this field.",
"oneOf": [
{
"$ref": "#/components/schemas/ResponseFormat"
},
{
"title": "ResponseFormatList",
"type": "array",
"items": {
"$ref": "#/components/schemas/ResponseFormat"
}
}
]
},
"environment": {
"description": "The environment configuration for the interaction. Can be an object\nspecifying remote environment sources or a string referencing an existing\nenvironment ID.",
"oneOf": [
{
"$ref": "#/components/schemas/EnvironmentConfig"
},
{
"type": "string"
}
]
},
"id": {
"description": "Required. Output only. A unique identifier for the interaction completion.",
"type": "string",
@ -4059,7 +4040,8 @@
"failed",
"cancelled",
"incomplete",
"budget_exceeded"
"budget_exceeded",
"requires_continuation"
],
"readOnly": true,
"x-google-enum-deprecated": [
@ -4069,7 +4051,8 @@
false,
false,
false,
true
true,
false
],
"x-google-enum-descriptions": [
"The interaction is in progress.",
@ -4078,7 +4061,8 @@
"The interaction failed.",
"The interaction was cancelled.",
"The interaction is completed, but contains incomplete results (e.g.\nhitting max_tokens).",
"Deprecated: Token and execution budget exhaustion returns INCOMPLETE\n(11)."
"Deprecated: Token and execution budget exhaustion returns INCOMPLETE\n(11).",
"The interaction requires at least one more request for it to be\ncompleted. The next request should set the continuation_token to the one\nreturned by this request."
]
},
"created": {
@ -4110,6 +4094,21 @@
"$ref": "#/components/schemas/ResponseModality"
}
},
"response_format": {
"description": "Enforces that the generated response is a JSON object that complies with\nthe JSON schema specified in this field.",
"oneOf": [
{
"$ref": "#/components/schemas/ResponseFormat"
},
{
"title": "ResponseFormatList",
"type": "array",
"items": {
"$ref": "#/components/schemas/ResponseFormat"
}
}
]
},
"response_mime_type": {
"description": "The mime type of the response. This is required if response_format is set.",
"type": "string",
@ -4119,6 +4118,17 @@
"description": "The ID of the previous interaction, if any.",
"type": "string"
},
"environment": {
"description": "The environment configuration for the interaction. Can be an object\nspecifying remote environment sources or a string referencing an existing\nenvironment ID.",
"oneOf": [
{
"$ref": "#/components/schemas/EnvironmentConfig"
},
{
"type": "string"
}
]
},
"environment_id": {
"description": "Output only. The environment ID for the interaction. Only populated if environment\nconfig is set in the request.",
"type": "string",
@ -4146,6 +4156,12 @@
"type": "string"
}
},
"continuation_token": {
"description": "Opaque token to resume a long decode. Output: set when status is\nREQUIRES_CONTINUATION and decoding can be resumed. Input: pass the latest\ntoken back unchanged in CreateInteraction to continue decoding.",
"type": "string",
"format": "byte",
"x-speakeasy-base64-input-mode": "file"
},
"stream": {
"description": "Input only. Whether the interaction will be streamed.",
"type": "boolean",
@ -4379,14 +4395,6 @@
"type": {
"const": "document"
},
"data": {
"type": "string",
"format": "byte",
"x-speakeasy-base64-input-mode": "file"
},
"uri": {
"type": "string"
},
"mime_type": {
"type": "string",
"enum": [
@ -4397,6 +4405,14 @@
"PDF document format",
"CSV document format"
]
},
"data": {
"type": "string",
"format": "byte",
"x-speakeasy-base64-input-mode": "file"
},
"uri": {
"type": "string"
}
},
"required": [
@ -4474,23 +4490,6 @@
"type": "string",
"readOnly": true
},
"network": {
"description": "Network configuration for the environment.",
"oneOf": [
{
"$ref": "#/components/schemas/EnvironmentNetworkEgressAllowlist"
},
{
"type": "string",
"enum": [
"disabled"
],
"x-google-enum-descriptions": [
"All network egress is blocked."
]
}
]
},
"sources": {
"description": "Sources to be mounted into the environment.",
"type": "array",
@ -4526,6 +4525,23 @@
""
]
},
"network": {
"description": "Network configuration for the environment.",
"oneOf": [
{
"$ref": "#/components/schemas/EnvironmentNetworkEgressAllowlist"
},
{
"type": "string",
"enum": [
"disabled"
],
"x-google-enum-descriptions": [
"All network egress is blocked."
]
}
]
},
"file_count": {
"description": "Output only. The number of files in the environment, output only.",
"type": "string",
@ -4551,6 +4567,12 @@
"type": {
"const": "remote"
},
"sources": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Source"
}
},
"network": {
"description": "Network configuration for the environment.",
"oneOf": [
@ -4568,12 +4590,6 @@
}
]
},
"sources": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Source"
}
},
"environment_id": {
"description": "Optional. The environment ID for the interaction. If specified, the request will\nupdate the existing environment instead of creating a new one.",
"type": "string"
@ -5347,30 +5363,6 @@
"description": "Configuration parameters for model interactions.",
"type": "object",
"properties": {
"tool_choice": {
"description": "The tool choice configuration.",
"oneOf": [
{
"$ref": "#/components/schemas/ToolChoiceConfig"
},
{
"type": "string",
"enum": [
"auto",
"any",
"none",
"validated"
],
"x-google-enum-descriptions": [
"Auto tool choice.",
"Any tool choice.",
"No tool choice.",
"Validated tool choice."
],
"x-speakeasy-model-namespace": "interactions"
}
]
},
"temperature": {
"description": "Controls the randomness of the output.",
"type": "number",
@ -5395,6 +5387,30 @@
"type": "string"
}
},
"tool_choice": {
"description": "The tool choice configuration.",
"oneOf": [
{
"$ref": "#/components/schemas/ToolChoiceConfig"
},
{
"type": "string",
"enum": [
"auto",
"any",
"none",
"validated"
],
"x-google-enum-descriptions": [
"Auto tool choice.",
"Any tool choice.",
"No tool choice.",
"Validated tool choice."
],
"x-speakeasy-model-namespace": "interactions"
}
]
},
"thinking_level": {
"description": "The level of thought tokens that the model should generate.",
"$ref": "#/components/schemas/ThinkingLevel"
@ -6211,14 +6227,6 @@
"type": {
"const": "image"
},
"data": {
"type": "string",
"format": "byte",
"x-speakeasy-base64-input-mode": "file"
},
"uri": {
"type": "string"
},
"mime_type": {
"type": "string",
"enum": [
@ -6242,6 +6250,14 @@
"TIFF image format"
]
},
"data": {
"type": "string",
"format": "byte",
"x-speakeasy-base64-input-mode": "file"
},
"uri": {
"type": "string"
},
"resolution": {
"description": "The resolution of the media.",
"$ref": "#/components/schemas/MediaResolution"
@ -6385,7 +6401,8 @@
"failed",
"cancelled",
"incomplete",
"budget_exceeded"
"budget_exceeded",
"requires_continuation"
],
"readOnly": true,
"x-google-enum-deprecated": [
@ -6395,7 +6412,8 @@
false,
false,
false,
true
true,
false
],
"x-google-enum-descriptions": [
"The interaction is in progress.",
@ -6404,7 +6422,8 @@
"The interaction failed.",
"The interaction was cancelled.",
"The interaction is completed, but contains incomplete results (e.g.\nhitting max_tokens).",
"Deprecated: Token and execution budget exhaustion returns INCOMPLETE\n(11)."
"Deprecated: Token and execution budget exhaustion returns INCOMPLETE\n(11).",
"The interaction requires at least one more request for it to be\ncompleted. The next request should set the continuation_token to the one\nreturned by this request."
]
},
"created": {
@ -6434,9 +6453,33 @@
"$ref": "#/components/schemas/GenerationConfig",
"writeOnly": true
},
"system_instruction": {
"description": "System instruction for the interaction.",
"type": "string"
},
"tools": {
"description": "A list of tool declarations the model may call during interaction.",
"type": "array",
"items": {
"$ref": "#/components/schemas/Tool"
}
},
"input": {
"$ref": "#/components/schemas/InteractionsInput"
},
"usage": {
"description": "Output only. Statistics on the interaction request's token usage.",
"$ref": "#/components/schemas/Usage",
"readOnly": true
},
"response_modalities": {
"description": "The requested modalities of the response (TEXT, IMAGE, AUDIO).",
"type": "array",
"deprecated": true,
"items": {
"$ref": "#/components/schemas/ResponseModality"
}
},
"response_format": {
"description": "Enforces that the generated response is a JSON object that complies with\nthe JSON schema specified in this field.",
"oneOf": [
@ -6452,6 +6495,15 @@
}
]
},
"response_mime_type": {
"description": "The mime type of the response. This is required if response_format is set.",
"type": "string",
"deprecated": true
},
"previous_interaction_id": {
"description": "The ID of the previous interaction, if any.",
"type": "string"
},
"environment": {
"description": "The environment configuration for the interaction. Can be an object\nspecifying remote environment sources or a string referencing an existing\nenvironment ID.",
"oneOf": [
@ -6463,39 +6515,6 @@
}
]
},
"system_instruction": {
"description": "System instruction for the interaction.",
"type": "string"
},
"tools": {
"description": "A list of tool declarations the model may call during interaction.",
"type": "array",
"items": {
"$ref": "#/components/schemas/Tool"
}
},
"usage": {
"description": "Output only. Statistics on the interaction request's token usage.",
"$ref": "#/components/schemas/Usage",
"readOnly": true
},
"response_modalities": {
"description": "The requested modalities of the response (TEXT, IMAGE, AUDIO).",
"type": "array",
"deprecated": true,
"items": {
"$ref": "#/components/schemas/ResponseModality"
}
},
"response_mime_type": {
"description": "The mime type of the response. This is required if response_format is set.",
"type": "string",
"deprecated": true
},
"previous_interaction_id": {
"description": "The ID of the previous interaction, if any.",
"type": "string"
},
"environment_id": {
"description": "Output only. The environment ID for the interaction. Only populated if environment\nconfig is set in the request.",
"type": "string",
@ -6531,6 +6550,12 @@
},
"readOnly": true
},
"continuation_token": {
"description": "Opaque token to resume a long decode. Output: set when status is\nREQUIRES_CONTINUATION and decoding can be resumed. Input: pass the latest\ntoken back unchanged in CreateInteraction to continue decoding.",
"type": "string",
"format": "byte",
"x-speakeasy-base64-input-mode": "file"
},
"stream": {
"description": "Input only. Whether the interaction will be streamed.",
"type": "boolean",
@ -6813,16 +6838,16 @@
},
"readOnly": true
},
"agent": {
"description": "The agent to interact with.",
"type": "string"
},
"continuation_token": {
"description": "Opaque token to resume a long decode.\nOutput: set when status is INCOMPLETE and decoding can be resumed.\nInput: pass the latest token back unchanged in CreateInteraction to\ncontinue decoding.",
"type": "string",
"format": "byte",
"x-speakeasy-base64-input-mode": "file"
},
"agent": {
"description": "The agent to interact with.",
"type": "string"
},
"object": {
"description": "Output only. The resource type.",
"type": "string",
@ -6863,7 +6888,8 @@
"failed",
"cancelled",
"incomplete",
"budget_exceeded"
"budget_exceeded",
"requires_continuation"
],
"x-google-enum-deprecated": [
false,
@ -6872,7 +6898,8 @@
false,
false,
false,
true
true,
false
],
"x-google-enum-descriptions": [
"The interaction is in progress.",
@ -6881,7 +6908,8 @@
"The interaction failed.",
"The interaction was cancelled.",
"The interaction is completed, but contains incomplete results (e.g.\nhitting max_tokens).",
"Deprecated: Token and execution budget exhaustion returns INCOMPLETE\n(11)."
"Deprecated: Token and execution budget exhaustion returns INCOMPLETE\n(11).",
"The interaction requires at least one more request for it to be\ncompleted. The next request should set the continuation_token to the one\nreturned by this request."
]
},
"event_type": {
@ -7076,14 +7104,14 @@
"type": {
"const": "mcp_server"
},
"url": {
"description": "The full URL for the MCPServer endpoint.\nExample: \"https://api.example.com/mcp\"",
"type": "string"
},
"name": {
"description": "The name of the MCPServer.",
"type": "string"
},
"url": {
"description": "The full URL for the MCPServer endpoint.\nExample: \"https://api.example.com/mcp\"",
"type": "string"
},
"headers": {
"description": "Optional: Fields for authentication headers, timeouts, etc., if needed.",
"type": "object",
@ -7362,7 +7390,7 @@
},
"ModelOption": {
"title": "Model",
"description": "The model that will complete your prompt.\\n\\nSee [models](https://ai.google.dev/gemini-api/docs/models) for additional details.",
"description": "The model that will complete your prompt.\\n\\nSee [models](https://aistudio.google.com/docs/models) for additional details.",
"type": "string",
"enum": [
"gemma-4-26b-a4b-it",
@ -8695,6 +8723,20 @@
"description": "Configuration for speech recognition (transcription).",
"type": "object",
"properties": {
"language_codes": {
"description": "Optional. BCP-47 language codes providing hints about the languages present in the\naudio. If omitted or empty, defaults to automatic language detection.",
"type": "array",
"items": {
"type": "string"
}
},
"custom_vocabulary": {
"description": "Optional. A list of custom vocabulary phrases to bias the speech recognition model\ntoward recognizing specific terms.",
"type": "array",
"items": {
"type": "string"
}
},
"mode": {
"description": "Discriminated transcription mode options or enum.",
"oneOf": [
@ -8714,20 +8756,6 @@
}
]
},
"language_codes": {
"description": "Optional. BCP-47 language codes providing hints about the languages present in the\naudio. If omitted or empty, defaults to automatic language detection.",
"type": "array",
"items": {
"type": "string"
}
},
"custom_vocabulary": {
"description": "Optional. A list of custom vocabulary phrases to bias the speech recognition model\ntoward recognizing specific terms.",
"type": "array",
"items": {
"type": "string"
}
},
"timestamp_granularities": {
"description": "Optional. The granularity of timestamps to include in the transcription output.\nSupported values: \"word\". If empty, no timestamps are generated.",
"type": "array",
@ -9332,6 +9360,11 @@
"type": "integer",
"format": "int32"
},
"initial_input_tokens": {
"description": "Number of input tokens used in the first turn of this interaction.",
"type": "integer",
"format": "int32"
},
"input_tokens_by_modality": {
"description": "A breakdown of input token usage by modality.",
"type": "array",
@ -9497,6 +9530,10 @@
"description": "The URI of the video.",
"type": "string"
},
"resolution": {
"description": "The resolution of the media.",
"$ref": "#/components/schemas/MediaResolution"
},
"processing": {
"description": "How the model processes this video for understanding.",
"oneOf": [
@ -9516,10 +9553,6 @@
}
]
},
"resolution": {
"description": "The resolution of the media.",
"$ref": "#/components/schemas/MediaResolution"
},
"name": {
"description": "A user-defined name for this content block. Can be referenced by the model\nin the final response.",
"type": "string"
@ -9573,14 +9606,6 @@
"type": {
"const": "video"
},
"data": {
"type": "string",
"format": "byte",
"x-speakeasy-base64-input-mode": "file"
},
"uri": {
"type": "string"
},
"mime_type": {
"type": "string",
"enum": [
@ -9608,6 +9633,14 @@
"JPEG 2000 video format"
]
},
"data": {
"type": "string",
"format": "byte",
"x-speakeasy-base64-input-mode": "file"
},
"uri": {
"type": "string"
},
"resolution": {
"description": "The resolution of the media.",
"$ref": "#/components/schemas/MediaResolution"

View file

@ -144,15 +144,17 @@ async def test_poller_bills_once_when_interaction_completes():
@pytest.mark.asyncio
async def test_poller_bills_an_interaction_paused_for_a_tool_result():
@pytest.mark.parametrize("status", ["requires_action", "requires_continuation"])
async def test_poller_bills_an_interaction_that_stopped_for_the_caller(status):
logging_obj = _logging_obj()
fetch, calls = _fetch_sequence(
_response("in_progress", with_usage=False),
_response("requires_action", with_usage=True),
_response(status, with_usage=True),
)
await poll_and_log_background_interaction_cost(_context(logging_obj), fetch_interaction=fetch)
outcome = await poll_and_log_background_interaction_cost(_context(logging_obj), fetch_interaction=fetch)
assert outcome == "billed"
assert len(calls) == 2
assert logging_obj.model_call_details["response_cost"] > 0
assert logging_obj.model_call_details["standard_logging_object"]["total_tokens"] == 175

View file

@ -1800,18 +1800,8 @@ async def test_track_cost_callback_releases_reservation_for_unpollable_interacti
@pytest.mark.asyncio
@pytest.mark.parametrize("status", ["completed", "requires_action"])
@pytest.mark.parametrize("status", ["completed", "requires_action", "requires_continuation"])
async def test_track_cost_callback_alerts_when_an_interaction_that_produced_output_has_no_usage(status):
"""
``completed`` and ``requires_action`` both mean the model produced output,
so a usage block is always expected with them. One arriving without it
means the charge for real work was lost, which is exactly what the
cost-tracking alert is for: silencing it here would let an operator's
interactions bill nothing with no signal that anything went wrong.
The reservation still has to be released, since suppressing the alert was
never what freed it.
"""
from litellm.types.interactions import InteractionsAPIResponse
logger = ProxyDBLogger()
@ -1836,7 +1826,7 @@ async def test_track_cost_callback_alerts_when_an_interaction_that_produced_outp
end_time=datetime.now(),
)
assert reservation["finalized"] is True
assert reservation["finalized"] is True, "raising the alert must not skip the reservation release"
mock_proxy_logging.failed_tracking_alert.assert_called_once()
@ -1928,6 +1918,7 @@ async def test_callback_handles_every_status_the_interactions_api_can_return():
"cancelled",
"incomplete",
"budget_exceeded",
"requires_continuation",
}