can1357/oh-my-pi · error

Gemini Files API ${context} response is not a JSON object

Error message

Gemini Files API ${context} response is not a JSON object

What it means

The Gemini Files API client assumes every JSON response body is a JSON object (`Record<string, unknown>`) — that is the documented shape for both the upload-poll (`file`) and metadata endpoints. `responseObject` guards this assumption: if the parsed body is not an object (a string, number, boolean, null, or an array), the response does not match the API contract and the client throws rather than indexing into an unexpected shape.

Source

Thrown at packages/coding-agent/src/blob-broker/provider-files-gemini.ts:18

import type { Model } from "@oh-my-pi/pi-ai";
import type { ProviderFileClient, ProviderFileHandle, ProviderFileUploadRequest } from "./provider-file-types";
import type { FetchImpl } from "./uploader-runtime";

const GEMINI_FILES_ORIGIN = "https://generativelanguage.googleapis.com";
const GEMINI_FILES_UPLOAD_URL = `${GEMINI_FILES_ORIGIN}/upload/v1beta/files`;
const GEMINI_FILES_RESOURCE_URL = `${GEMINI_FILES_ORIGIN}/v1beta`;

interface GeminiFileResource {
	name: string;
	uri: string;
	mimeType: string;
	expiresAt: number;
}

function responseObject(value: unknown, context: string): Record<string, unknown> {
	if (typeof value !== "object" || value === null || Array.isArray(value)) {
		throw new Error(`Gemini Files API ${context} response is not a JSON object`);
	}
	return value as Record<string, unknown>;
}

async function responseJson(response: Response, context: string): Promise<Record<string, unknown>> {
	try {
		return responseObject((await response.json()) as unknown, context);
	} catch (error) {
		if (error instanceof Error && error.message.startsWith("Gemini Files API")) throw error;
		throw new Error(`Gemini Files API ${context} response is not valid JSON`);
	}
}

function requireString(value: unknown, field: string): string {
	if (typeof value !== "string" || value.length === 0) {
		throw new Error(`Gemini Files API finalize response is missing ${field}`);
	}
	return value;

View on GitHub (pinned to 9690622007)

Solutions

  1. Log the raw response body (status + text) for the failing call and check whether it is an object with the expected Gemini `file` fields (`name`, `mimeType`, `expirationTime`, `state`).
  2. Verify you are using the documented Gemini Files API endpoints/versions (generativelanguage.googleapis.com, v1beta files) and a supported apiVersion — older or newer versions may wrap results differently.
  3. Remove or fix any proxy/mock that returns arrays or strings, and update recorded fixtures to real object-shaped responses.
  4. Check for an out-of-date package version if Google changed the response envelope, and update so the client unwraps the new shape.

Example fix

// before — assuming the body shape
const body = await response.json();
const state = body.state; // throws later or here if body is an array

// after — narrow before use
const body: unknown = await response.json();
if (typeof body !== "object" || body === null || Array.isArray(body)) {
  throw new Error(`Gemini Files API file response is not a JSON object: ${JSON.stringify(body)}`);
}
const state = (body as Record<string, unknown>).state;
Defensive patterns

Strategy: try-catch

Validate before calling

// check the shape of a Gemini response before consuming it
function isJsonObject(v: unknown): v is Record<string, unknown> {
  return typeof v === "object" && v !== null && !Array.isArray(v);
}
const body: unknown = await response.json();
if (!isJsonObject(body)) throw new Error(`Unexpected Gemini response shape: ${JSON.stringify(body)}`);

Type guard

const isJsonObject = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null && !Array.isArray(v);

Try / catch

try {
  const file = await geminiFiles.file(name);
} catch (err) {
  if (err instanceof Error && err.message.includes("not a JSON object")) {
    logger.error("Gemini Files API returned non-object body — check API version/baseUrl/proxy", { cause: err });
    // retry once or fall back to re-uploading
  } else throw err;
}

Prevention

When it happens

Trigger: `responseObject` is called (from `responseJson`/`file`) with a parsed body that is a JSON array, a JSON primitive/string, or null — e.g. the endpoint returned a list, an error string, or `null` while still being valid JSON.

Common situations: Gemini API version drift (endpoint now returns an array or wrapped envelope); an API error returned as a JSON string/HTML-like body that still parses; a proxy or local mock returning `[]` or `"ok"`; a custom baseUrl pointing at a non-Gemini compatible server; response.json() parsing an empty-ish body into null.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/195ad2b8fe198084. Report an issue: GitHub.