Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,6 @@ cython_debug/
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore

# OpenCode / ace-tool index
.ace-tool/
23 changes: 18 additions & 5 deletions src/grok/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ function encodeAssetPath(raw: string): string {
}
}

function normalizeGeneratedAssetUrls(input: unknown): string[] {
if (!Array.isArray(input)) return [];
const out: string[] = [];
for (const u of input) {
if (typeof u !== "string") continue;
const trimmed = u.trim();
if (!trimmed) continue;
out.push(trimmed);
}
return out;
}
Comment on lines +74 to +84
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The normalizeGeneratedAssetUrls function filters empty strings but doesn't filter the "/" placeholder value mentioned in the PR description. According to the description, both "" and "/" are possible placeholder values from Grok, but this function only filters empty/whitespace strings. A URL of "/" would pass through as valid and still get encoded as p_Lw, which is the exact issue this PR aims to fix.

Consider adding a check to filter out "/" (and possibly other path-only placeholders like "/images"):

if (!trimmed || trimmed === "/") continue;

Copilot uses AI. Check for mistakes.

export function createOpenAiStreamFromGrokNdjson(
grokResp: Response,
opts: {
Expand Down Expand Up @@ -241,11 +253,10 @@ export function createOpenAiStreamFromGrokNdjson(
if (isImage) {
const modelResp = grok.modelResponse;
if (modelResp) {
const urls = Array.isArray(modelResp.generatedImageUrls) ? modelResp.generatedImageUrls : [];
const urls = normalizeGeneratedAssetUrls((modelResp as any).generatedImageUrls);
if (urls.length) {
const linesOut: string[] = [];
for (const u of urls) {
if (typeof u !== "string") continue;
const imgPath = encodeAssetPath(u);
const imgUrl = toImgProxyUrl(global, origin, imgPath);
linesOut.push(`![Generated Image](${imgUrl})`);
Expand Down Expand Up @@ -379,14 +390,16 @@ export async function parseOpenAiFromGrokNdjson(
if (typeof modelResp.model === "string" && modelResp.model) model = modelResp.model;
if (typeof modelResp.message === "string") content = modelResp.message;

const urls = Array.isArray(modelResp.generatedImageUrls) ? modelResp.generatedImageUrls : [];
const urls = normalizeGeneratedAssetUrls((modelResp as any).generatedImageUrls);
for (const u of urls) {
if (typeof u !== "string") continue;
const imgPath = encodeAssetPath(u);
const imgUrl = toImgProxyUrl(global, origin, imgPath);
content += `\n![Generated Image](${imgUrl})`;
}
break;

// For image generation responses, Grok may emit intermediate modelResponse frames with empty/placeholder URLs.
// Keep scanning until we see at least one usable URL; otherwise the caller gets a broken `/images/p_Lw` link.
if (urls.length) break;
}

return {
Expand Down