can1357/oh-my-pi · error · LegacyDestinationError
the upload response did not include a direct image URL
Error message
the upload response did not include a direct image URL
What it means
This LegacyDestinationError is thrown by directJsonUrl() when the parsed JSON response object (including a nested `response` object) contains none of the recognized direct-URL keys: `direct_url`, `directUrl`, `url`, or `URL`. The upload 'succeeded' but the library cannot locate the link to the stored file.
Source
Thrown at packages/coding-agent/src/blob-broker/uploaders-legacy.ts:141
response: Response,
): Promise<Readonly<Record<string, unknown>>> {
let value: unknown;
try {
value = await response.json();
} catch (error) {
throw new LegacyDestinationError(destination, "the upload endpoint returned invalid JSON", error);
}
const record = objectValue(value);
for (const _ in record) return record;
throw new LegacyDestinationError(destination, "the upload endpoint returned an invalid JSON object");
}
function directJsonUrl(destination: BlobDestinationId, record: Readonly<Record<string, unknown>>, base: URL): string {
const nested = objectValue(record.response);
const raw =
firstString(record, ["direct_url", "directUrl", "url", "URL"]) ??
firstString(nested, ["direct_url", "directUrl", "url", "URL"]);
if (!raw) throw new LegacyDestinationError(destination, "the upload response did not include a direct image URL");
return httpUrl(destination, raw, base);
}
function basicAuthorization(username: string, password: string): string {
return `Basic ${Buffer.from(`${username}:${password}`, "utf8").toString("base64")}`;
}
function optionalBasicHeaders(
destination: BlobDestinationId,
config: DestinationRuntimeConfig,
usernameKey: string,
passwordKey: string,
): Headers | undefined {
const username = credentialString(config, usernameKey);
const password = credentialString(config, passwordKey);
if (!username && !password) return undefined;
if (!username || !password) {
throw new LegacyDestinationError(View on GitHub (pinned to 9690622007)
Solutions
- Configure or patch the endpoint to include a recognized key (`url` or `direct_url`) in its JSON response
- Add a server-side response shim that maps the service's actual key (e.g. `download_url`) to `url`
- If the API returns only an ID, proxy it and construct the absolute URL server-side
- Compare the actual response keys with the accepted aliases: direct_url, directUrl, url, URL (top-level or under `response`)
Example fix
// before (server response)
{"status":"ok","download_url":"https://cdn.example.com/f.png"}
// after
{"status":"ok","url":"https://cdn.example.com/f.png"} Defensive patterns
Strategy: validation
Validate before calling
const record = await response.json();
const nested = record?.response ?? {};
const hasUrl = ["direct_url","directUrl","url","URL"]
.some(k => typeof record?.[k] === "string" || typeof nested?.[k] === "string");
if (!hasUrl) throw new Error("endpoint response lacks a direct URL field"); Type guard
function hasDirectUrl(record: unknown): record is Record<string, unknown> & { url: string } {
const keys = ["direct_url", "directUrl", "url", "URL"];
const r = record as Record<string, unknown>;
const nested = (r?.response ?? {}) as Record<string, unknown>;
return keys.some(k => typeof r?.[k] === "string" || typeof nested?.[k] === "string");
} Try / catch
try {
const result = await uploader.upload(request);
} catch (err) {
if (err instanceof Error && err.message.includes("did not include a direct image URL")) {
// map the endpoint's actual field name to `url`/`direct_url`
} else throw err;
} Prevention
- Include a `url` or `direct_url` key in custom endpoint responses
- Map vendor-specific keys (download_url, link) to the recognized aliases server-side
- Keep an integration test asserting the response carries a direct URL key
- Document the four accepted key names for endpoint implementers
When it happens
Trigger: The endpoint replies with valid JSON lacking any URL field — e.g. `{"status":"ok","id":"abc"}`, or uses a key name outside the four recognized aliases; thrown before httpUrl() validation, called from url() and upload().
Common situations: Custom replacement endpoints with their own response schema (e.g. `download_url` or `link` keys); APIs that return only an identifier requiring a second request to fetch the URL; version drift where the service renamed its response fields.
Related errors
- the upload endpoint returned invalid JSON
- the upload endpoint returned an invalid JSON object
- the upload response did not contain a valid direct URL
- the upload response URL must use HTTP or HTTPS
- Gemini Files API ${context} response is not valid JSON
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/40ecd7d0240c074c.
Report an issue: GitHub.