From 7e514ac3e34644fde7a4e43f1fb7de2e2d2bf95f Mon Sep 17 00:00:00 2001 From: YuTengjing Date: Mon, 18 May 2026 00:55:29 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20use=20JSON=20object=20for?= =?UTF-8?q?=20video=20image=20reference=20(#14900)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/claude-pr-assign.yml | 40 +++++++++++++++++++ .../createVideo.test.ts | 28 ++++++++++++- .../openaiCompatibleFactory/createVideo.ts | 8 +++- 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/.github/workflows/claude-pr-assign.yml b/.github/workflows/claude-pr-assign.yml index 2393910ddc..4c20bb4d67 100644 --- a/.github/workflows/claude-pr-assign.yml +++ b/.github/workflows/claude-pr-assign.yml @@ -21,6 +21,46 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 + # Remind contributors when a non-release PR targets `main`. + # Day-to-day PRs should target `canary`; `main` is reserved for releases + # (see .agents/skills/version-release/SKILL.md). Allowed exceptions: + # - PR title matches `🚀 release: v{x.y.z}` (minor release) + # - head branch matches `hotfix/*` or `release/*` (patch release) + - name: Remind contributor if base branch is not canary + if: github.event.action == 'opened' && github.event.pull_request.base.ref == 'main' + env: + HEAD_REF: ${{ github.event.pull_request.head.ref }} + PR_TITLE: ${{ github.event.pull_request.title }} + PR_NUMBER: ${{ github.event.pull_request.number }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} + run: | + if [[ "$HEAD_REF" == hotfix/* ]] || [[ "$HEAD_REF" == release/* ]]; then + echo "✅ Release/hotfix branch ($HEAD_REF) -> main is allowed" + exit 0 + fi + if [[ "$PR_TITLE" =~ ^🚀[[:space:]]+release: ]]; then + echo "✅ Release-titled PR -> main is allowed" + exit 0 + fi + echo "⚠️ Non-release PR targets main; posting reminder comment." + gh pr comment "$PR_NUMBER" --body "$(cat <<'EOF' + 👋 Thanks for your contribution! + + This PR currently targets the **`main`** branch, but `main` is reserved for release PRs only. Day-to-day development (features, fixes, refactors, docs, etc.) should target the **`canary`** branch. + + ### How to fix + + On the PR page, click **Edit** next to the title, then change the base branch from `main` to `canary`. + + ### When targeting `main` is allowed + + - PR title starts with `🚀 release: v{x.y.z}` (minor release) + - Head branch matches `hotfix/*` or `release/*` (patch release) + + If your PR fits one of these cases, please ignore this message. + EOF + )" + - name: Check if author is a team member id: check-team run: | diff --git a/packages/model-runtime/src/core/openaiCompatibleFactory/createVideo.test.ts b/packages/model-runtime/src/core/openaiCompatibleFactory/createVideo.test.ts index fb6829b561..05342cd231 100644 --- a/packages/model-runtime/src/core/openaiCompatibleFactory/createVideo.test.ts +++ b/packages/model-runtime/src/core/openaiCompatibleFactory/createVideo.test.ts @@ -19,6 +19,12 @@ const mockOptions: CreateVideoOptions = { provider: 'openai', }; +const mockVllmOptions: CreateVideoOptions = { + apiKey: 'test-api-key', + baseURL: 'http://localhost:8000/v1', + provider: 'vllm', +}; + beforeEach(() => { vi.clearAllMocks(); }); @@ -95,7 +101,7 @@ describe('createOpenAICompatibleVideo', () => { expect(body.size).toBe('1920x1080'); }); - it('should include imageUrl as input_reference', async () => { + it('should include imageUrl as JSON input_reference object', async () => { global.fetch = vi.fn().mockResolvedValueOnce({ ok: true, json: async () => ({ id: 'video-task-img' }), @@ -111,6 +117,26 @@ describe('createOpenAICompatibleVideo', () => { await createOpenAICompatibleVideo(payload, mockOptions); + const body = JSON.parse((global.fetch as any).mock.calls[0][1].body); + expect(body.input_reference).toEqual({ image_url: 'https://example.com/image.jpg' }); + }); + + it('should preserve string input_reference for non-OpenAI compatible providers', async () => { + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => ({ id: 'video-task-vllm-img' }), + }); + + const payload: CreateVideoPayload = { + model: 'vllm-omni', + params: { + prompt: 'Continue this scene', + imageUrl: 'https://example.com/image.jpg', + }, + }; + + await createOpenAICompatibleVideo(payload, mockVllmOptions); + const body = JSON.parse((global.fetch as any).mock.calls[0][1].body); expect(body.input_reference).toBe('https://example.com/image.jpg'); }); diff --git a/packages/model-runtime/src/core/openaiCompatibleFactory/createVideo.ts b/packages/model-runtime/src/core/openaiCompatibleFactory/createVideo.ts index 97b3a9159c..be9aa2aa6e 100644 --- a/packages/model-runtime/src/core/openaiCompatibleFactory/createVideo.ts +++ b/packages/model-runtime/src/core/openaiCompatibleFactory/createVideo.ts @@ -1,4 +1,5 @@ import createDebug from 'debug'; +import { ModelProvider } from 'model-bank'; import type { CreateVideoPayload, @@ -115,7 +116,7 @@ export async function pollOpenAICompatibleVideoStatus( * model: string, * prompt: string, * seconds?: string, // OpenAI Sora format (string type) - * input_reference?: string | object, // For image-to-video + * input_reference?: string | { image_url: string } | { file_id: string }, // For image-to-video * } * * Creates a video generation task and returns immediately with inferenceId. @@ -150,7 +151,10 @@ export async function createOpenAICompatibleVideo( // Image-to-video support if (imageUrl) { - body['input_reference'] = imageUrl; + // OpenAI JSON requests reject bare strings, for example: + // `input_reference: "https://example.com/image.jpg"`. + body['input_reference'] = + options.provider === ModelProvider.OpenAI ? { image_url: imageUrl } : imageUrl; } log('OpenAI-compatible video API request body: %O', body);