Commit Graph
4 Commits
Author SHA1 Message Date
open-design-crew[bot]GitHubbone3deep1962-collabCheemsCheemszhouzhengWilliamopen-design-crew[bot] <4197780+open-design-crew[bot]@users.noreply.github.com>NJUHua
3dfd41c6b3 [OPEND-2800][OPEND-4117] feat(od-next): integrate Discovery delivery and validated prototype fixes (#8804)
* fix(od-next): continue plans with keyed markers and honor explicit deliverables

* fix(od-next): retire output contracts and hand off continued tasks

* fix(od-next): record round settlement and recover production cold

* fix(od-next): resume marker sessions with context deltas

* fix(od-next): separate completion facts and honor plan-only intent

* fix(od-next): preserve physical failures and legacy chat compatibility

* test(web): align run recovery fixtures and await campaign mount

* fix(od-next): tolerate terminal production marker formatting

* fix(od-next): unblock follow-ups after a stop or unreadable history (#8423)

Treat a previous task this build cannot read as absent so the follow-up starts a new task, and stop counting a Run with a pending cancel as holding its conversation. Add end-to-end coverage for the marker continuation flow and restore the remote Workspace directory gate cases.

* feat(od-next): add deliverable discovery and ten-type routing

* fix(od-next): refine discovery and live delivery evidence

* feat(observability): expose actual task delivery receipts for evaluation

* fix(observability): settle successful headless task telemetry

* docs: clarify focused local validation

Keep local daemon and web validation scoped to affected files and integration boundaries. Reserve full suites for PR CI unless the user explicitly requests them.

* feat: add Motion Design skill and OD Next task route

* fix(web): rename motion design entry and place it second

* fix(web): reconcile motion design catalog consumers

* feat(web): add motion design templates to Home and Community

Five motion design example templates (3D procedural island, explainer,
product launch, showreel, data story), chosen by community reach, under the
Home motion-design chip and a new Community "Motion design" type.

Each template follows the bundled example layout: an OD-generated
example.html with its runtime libraries under assets/, an HTML preview baked
by the existing preview pipeline, and a SKILL.md that carries example.html
as the reference when the card is picked on the OD Next motion-design route.
Home cards ship curated copy for every UI locale. Motion templates stay off
the Video and HyperFrames chips.

* fix(skills): keep motion-design procedural layers visible in video export

The MP4 exporter seeks timelines with callbacks suppressed
(renderSeek(t, { suppressEvents: true })), so content drawn only from a GSAP
onUpdate callback is missing from exported video. Recommend a per-frame draw
loop that reads the timeline clock, and state that visible content must not
depend on timeline callbacks. The OD Next task asset is re-synced with
scripts/sync-motion-design-skill.ts.

* fix(motion): preserve reference assets and exported animation frames

* feat(skills): integrate curated motion helpers with complete runtime files

* feat(skills): add Huashu, Oil Motion and Motion.Lab adapters

* test: classify animate design guidance in narration guard

* feat(motion): refine five community templates and portable previews

* fix(motion): refine launch transitions and center gallery previews

* fix: allow free-form briefs on automatic motion task route

* feat(motion): add template soundtracks and audio export

* fix(motion): require rendered MP4 alongside editable video source

* test(motion): align strategy activation expectation with package version

* fix: scope motion delivery and make editable sources playable

* fix(motion): bundle source dependencies and valid closing protocol

* feat(motion): rebuild the five templates on the most-viewed community posts

Re-select topics by single-post views in the latest awesome-opus-5.5-video
snapshot and anchor each template to the top eligible post of its genre:
camera-focus explainer, showreel, product launch, procedural island and a
new wordless animated short that replaces the data story.

All five example.html files are hand-authored canvas/Three.js films drawn by
one render(t) from the GSAP master timeline, so preview, scrubbing and MP4
export produce identical frames. Each ships an original procedural
soundtrack, updated SKILL.md and 19-locale copy; Home order follows reach.

* fix(plugins): load bundled fonts in plugin previews

Plugin previews blocked every bundled font: the preview CSP had no font-src,
and relative url() references inside inline <style> blocks kept pointing at
the preview URL instead of the plugin folder. Allow same-origin fonts, map
inline-style url() references through the existing asset route (escapes,
absolute and data: URLs stay untouched), and serve font files with font
MIME types.

* feat(community): make motion template Use match the Home example pick

Community 「做同款」 on a motion design template now binds it as that type's
own example card, carrying the type's project metadata, so the run takes the
same OD Next motion-design route as a pick from Home's motion design list and
delivers the MP4 together with the editable source. Other task types keep
the existing plugin-pinning hand-off.

Community use clicks now record use_source (card | detail_modal) and
use_action (use | use_with_query); the details modal Use is tracked for the
first time. The Home hand-off no longer records a second community_gallery
click for uses Community already tracked.

* fix(plugins): count the five motion templates in the official registry metadata

* fix(media): keep HyperFrames audio filters compatible with FFmpeg 4.1

CI's bundled Linux FFmpeg 4.1 rejects adelay all=1 and amix normalize=0.
Make each track stereo and delay both channels explicitly, skip amix for a
single track, and undo amix averaging with a gain for several tracks.

* fix(motion): keep template previews bakeable on slow CI renderers

CI's software-GL baker skipped the 3D island (preview readiness timeout):
under slow frames GSAP lag smoothing slowed the timeline, so the 8 s
poster frame never arrived within the baker's 15 s wait. Play every
template on the wall clock and move the island poster to 6 s. With
simulated 300 ms frames all five now report ready within 10 s.

* feat(motion): add the kinetic typography template

Template selection now follows the motion design spec's four content types
(brand and product films, kinetic typography, explainer animation, procedural
art). Kinetic typography takes the fifth slot, replacing the animated short.
The new template is a 15-second, 120 BPM type film with an original
soundtrack and 19-locale copy.

* fix(motion): let the project motion player fit template stages

Inside a project the motion source player wraps the composition, fits it and
adds its own controls. The templates also scaled their stage, so editable
sources from Remix and Community use rendered at a fraction of the preview.
Templates now keep their own fit and controls only outside that player.

* feat(motion): restage the 3D island as a midday lagoon

The island moves from golden hour to a bright tropical midday closer to its
anchor post: a shader lagoon coloured by a baked depth map with ripples,
shallow-water shimmer and shore foam, a wide white-sand beach, palms and
instanced bushes, a village with a pier, soft sun shadows and a lower
camera path. The CPU wave mesh is gone, so frames cost no more than before.
Copy is updated in all 19 locales.

* fix(motion): keep each soundtrack inside the composition root

A Community use regenerated the island source tail and dropped the root
<audio> element, so its MP4 had no sound. Templates now place the element
inside the composition root next to the stage.

* docs(motion): state the template selection rule as complete works

* fix(motion): enforce video clocks and bound content verification

* feat(motion): replace the motion templates with remakes of viral posts

The five Motion design templates are now frame-faithful remakes of viral
motion posts, so each preview shows the look users came for: the 15-second
motion designer showreel, a high-energy motion reel (copy set in English), a
one-shape UI motion piece, a pixel-art wizard and a 20-second electric-blue
tech reel. Each ships open-licensed fonts, an original soundtrack, the
creator's published prompt where one exists, and credits its source post.

The explainer, product launch, 3D island and kinetic type templates are
removed. Tests follow the new set; a backward-seek check now covers hidden
WebGL layers.

* feat(motion): feature The Future Is Already Here first and start preview sound with the film

* chore: ensure PR CI

* fix(motion): match The Future Is Already Here controls and reorder the motion templates

* fix(motion): make the one-shape UI preview controls legible

* fix(motion): hide The Future Is Already Here controls inside the project player

* fix(od-next): maintain discovery prompt assets in English

* fix(motion): plan suitable sound and synchronize source audio playback

* feat(motion): keep remake soundtracks by default and fade exported audio where it stops

* test(motion): cover audible source controls in Chromium

* fix(test): use explicit ESM imports in audio player regression

* fix(od-next): preserve planning intent and make agent CLI probes inert

* fix(motion): align preview track discovery with video export

* feat(motion): autoplay looping HTML without a player bar

* fix(test): consume the built motion playback contract

* docs(motion): state explicit soundtrack rules for make-same and the music line in replies

* chore(motion): version the audible looping delivery profile

* docs(motion): let template sound rules lead the general policy and freeze the audio contract

* fix(motion): scope autoplay to declared video motion sources

* fix(motion): reject audible MP4s under an explicit silent contract

* docs(motion): drop the routine music note from make-same replies

* test(motion): align delivery regressions with looping source playback

* fix(od-next): route refreshable sources and preserve live templates

* fix(od-next): distinguish source refresh from preview reload

* fix(motion): isolate preview and audio export from legacy HyperFrames

* fix(od-next): retain live list containers for empty source data

* test(motion): retain the existing desktop legacy audio limitation

* fix(motion): suspend audio when previews leave the active workspace

* feat(motion): replace two motion templates with a product promo and a visual lesson; sign the showreel as Open Design

* fix(discovery): distinguish reference recreation from new prototypes

* fix(discovery): require skill reads before copying reference artifacts

* feat(motion): give the showreel the Open Design palette and a full-size stacked wordmark

* feat(motion): give the product promo a livelier mascot girl

* refactor(motion): rebuild The Future Is Already Here as readable, editable scene code

* fix(motion): pick gallery cover frames that survive the baker's capture delay

* feat(motion): sharpen the showreel opener, depth, truchet and end card

* fix(discovery): avoid implementation-only skills and false video delivery

* feat(motion): make the product promo mascot talk, add a speaking score and the flow pop

* fix(discovery): distinguish video rendering from editable animation delivery

* feat(motion): give the visual lesson the original's paper-collage texture

* fix(motion): bundle gallery covers for fresh branch checkouts

* feat(motion): feature and refine Bring Ideas to Life

Adapt the paper story around OpenDesign capabilities, with short rounded hands, clearer timing, continuous paper actions, and a larger audience invitation. Feature it first in Motion and synchronize localized names, metadata, cover, guidance, and regressions.

Keep product promo audio music-only with sparse percussion.

* fix(motion): stabilize artifact cards and capture declared covers

* fix(discovery): retain WebGL intent and isolate reference-only skills

* fix(discovery): complete authorized plans and verify audible delivery

* fix(discovery): gate skill reads on current production authorization

* fix(web): share creation type order with community

* fix(discovery): stop required-model attempts on authentication failure

* fix(discovery): retain required model provider and billing channel

* fix(discovery): stop opaque video failures without diagnostic generations

* fix(discovery): distinguish local state and tighten output adherence

* fix(preview): preserve local forms and hash routes in strict sandbox

* fix(prototype): keep validation feedback selectors consistent

* fix(prototype): preserve native hidden feedback states

* fix(discovery): align local routes and live filter states

* fix(prototype): preserve dynamic controls and dialog visibility

* fix(prototype): keep review and confirmation state coherent

* fix(web): stabilize preview activation and retain scoped local progress

* test(web): verify persistent previews through activation messages

* feat(web): refine task type selection and keyboard navigation

* fix(discovery): distinguish execution from requested delivery and expose recovery

* fix(web): use final run delivery state in folded task history

* revert(web): restore todo-based continuation controls

* revert(od-next): remove experimental continuation recovery changes

* fix(telemetry): bind Task traces to authenticated Vela identity

* test(telemetry): establish accepted Task roots in bridge fixtures

* test(discovery): align required CI fixtures with integrated behavior

* fix(web): preserve succeeded messages when run probe is unavailable

* test(discovery): verify selection clearing and production turn recovery

---------

Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com>
Co-authored-by: Cheems <hihicheems@gmail.com>
Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com>
Co-authored-by: zhouzheng <zhouzheng@refly.ai>
Co-authored-by: William <zhouhaoran@refly.ai>
Co-authored-by: open-design-crew[bot] <4197780+open-design-crew[bot]@users.noreply.github.com>
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
2026-10-21 09:05:01 +00:00
CaprikaandGitHub a723012526 refactor(media): remove Codex image generation (#6742)
* refactor(media): remove Codex image generation

* fix(media): preserve HyperFrames video default

* test(media): keep Vela coverage image-only

* fix(media): persist Home image model selection
2026-08-11 09:48:11 +00:00
chenxueandGitHub b1fd529eea Feat/aihubmix byok provider (#3583)
* feat(media): integrate AIHubMix as full-stack BYOK provider

Add AIHubMix (https://aihubmix.com) as a BYOK provider at the same layer as
SenseAudio — both a chat protocol and a media-generation provider — so users
can paste one key and get chat + image + speech, with the same key auto-mirrored
into media-config for the `od media` CLI path.

AIHubMix is an OpenAI-wire-compatible aggregator. Its distinguishing trait is the
fixed APP-Code attribution header (discount), funnelled through a single
aihubmixHeaders() helper so every outbound call point (chat proxy, connection
test, model discovery, media renderers, memory extractor) injects it in one place.

Chat protocol layer:
- Extend ApiProtocol / ConnectionTestProtocol / MemoryExtractionProvider unions.
- New web provider client + dispatch; mirror across apiProtocols, config,
  utils, Settings/ChatComposer/HomeHero UI surfaces.
- Refactor the SenseAudio tool-loop chat proxy into a shared
  registerByokToolChatProxy() factory; register senseaudio + aihubmix through it
  (senseaudio behaviour byte-preserved). connectionTest + providerModels treat
  aihubmix as OpenAI-shaped with APP-Code headers.

Media layer:
- Register provider + image (aihubmix-gpt-image-1 / -dall-e-3) and speech
  (aihubmix-tts-1) models; prefixed ids stay unique and map to wire names.
- OpenAI-compatible renderers (/v1/images/generations, /v1/audio/speech) and
  chat tool executors; ENV_KEYS + media-picker allowlists updated.

Note: AIHUBMIX_APP_CODE in apps/daemon/src/aihubmix.ts is an empty placeholder —
fill it with the code from https://aihubmix.com/appstore to enable the discount
header (the integration works without it).

Tests: media-aihubmix.test.ts (wire-name mapping, APP-Code invariant, image +
speech renderers) and an AIHubMix proxy-route case; verify:media-models, guard,
and typecheck all green.

* feat(media): source AIHubMix model lists from live catalog endpoints

Chat LLM models and media image models now come from AIHubMix's live catalogue
(GET /api/v1/models?type=llm | image_generation, public, returns model_id /
model_name) instead of only the hand-curated static lists.

Chat: the existing /api/provider/models discovery, when protocol=aihubmix, now
hits the type=llm catalogue and parses the AIHubMix envelope (342 models live).
The static SUGGESTED_MODELS remain as the pre-fetch default.

Media image: new GET /api/media/providers/aihubmix/models endpoint proxies the
type=image_generation catalogue (53 models), prefixes ids `aihubmix-` for
uniqueness, and caches for 5 min with stale-on-error fallback. generateMedia
synthesizes a def for any unregistered `aihubmix-*` id (like the fal-ai/* path)
so every catalogue model renders through the OpenAI-compatible image endpoint.

Helpers (aihubmixCatalogUrl / parseAIHubMixCatalog) + tests for url building,
envelope parsing, and rendering a dynamic catalogue id. The web media pickers
still read the static seed list; wiring them to the live endpoint is a
follow-up UI change.

* feat(web): show live AIHubMix image catalog in all media pickers

Wire the AIHubMix image-generation catalogue (53 models, fetched from the new
daemon endpoint) into every image-model picker. A shared hook
(useAIHubMixImageModels) fetches once per session and caches; mergeAihubmixImageModels
replaces the two static aihubmix seeds with the live list (and falls back to the
seeds when the fetch hasn't resolved or fails, so the picker is never empty).

Surfaces:
- NewProjectPanel image picker — merged list passed to supportedModels('image').
- Home media composer — buildHomeMediaComposer / fieldsForSurface /
  normalizeHomeMediaInputs now accept an injected imageModels list; HomeView
  fetches and threads it through all three composer call sites.

Saved/template image-model ids starting with `aihubmix-` are accepted even
though they're absent from the static IMAGE_MODELS, since they render
dynamically daemon-side.

* feat(byok): load AIHubMix chat model list without an API key

The BYOK chat model dropdown already merges live-fetched models with the static
suggestions, but the fetch was gated on a non-empty API key (every provider hits
/v1/models behind its key). AIHubMix's catalogue is public, so its 342-model
list now loads as soon as the AIHubMix tab is selected — no key required.

- Daemon /api/provider/models accepts an empty apiKey when protocol=aihubmix
  (baseUrl still required); providerModelsHeaders sends no Bearer when the key
  is blank.
- Web: missingByokModelFetchFields treats apiKey as optional for aihubmix, and
  the auto-fetch effect skips the commit-on-blur gate for aihubmix (base URL is
  prefilled and there's no key to debounce), so the list populates on tab select.

* feat(byok): default AIHubMix chat model to claude-opus-4-8

* feat(byok): memory model picker uses live searchable model list

The Memory model picker was a plain <select> hard-wired to the 8 static
SUGGESTED_MODELS. Swap it to the same SearchableModelSelect the chat picker
above uses, fed by the parent's merged fetched+suggested model options
(apiModelOptions). For AIHubMix that means the full live 342-model catalogue
shows as a searchable dropdown (keyless), matching the chat picker; other
protocols get their live /v1/models list once the chat key is set. 'Same as
chat' leads the list and 'Custom…' is pinned last.

* feat(byok): BYOK chat image-model picker lists live AIHubMix catalog

The Settings BYOK 'image generation model' dropdown (byokImageModel, used by
the chat-injected generate_image tool) listed only the 2 static aihubmix seeds.
Feed it the live image catalogue (useAIHubMixImageModels) for aihubmix so it
shows all ~53 models; SenseAudio keeps its static registry list. Relax the
daemon's isAIHubMixImageModel to accept any aihubmix-* id (they all render via
the OpenAI-compatible endpoint with prefix-strip), so a dynamically-chosen
default passes validation instead of being dropped back to the seed default.

* refactor(byok): share one hook for BYOK image-model pickers; wire composer

The chat composer's inline 'image generation model' picker still listed only
the static aihubmix seeds. Rather than re-derive the merge+filter a third time,
extract useByokImageModelOptions(provider) into the shared aihubmix-image-models
module (live AIHubMix catalogue for aihubmix, static registry otherwise) and use
it from both the Settings image picker and the composer picker. One cached fetch
feeds all sites.

* feat(byok): route AIHubMix chat by model name to native protocol wire

Per the AIHubMix integration guide §4.3, route the BYOK chat by model name to
each family's native endpoint on the AIHubMix origin instead of forcing every
model through /v1/chat/completions:
  claude*           → Anthropic /v1/messages (x-api-key + anthropic-version)
  gemini*/imagen*   → Gemini :streamGenerateContent (x-goog-api-key)
  everything else   → OpenAI /v1/chat/completions (keeps the tool loop)
All three carry the APP-Code attribution header.

The claude/gemini routes run base chat only (no in-chat generate_image yet) —
each protocol has a pluggable tool slot for a later Anthropic tool_use / Gemini
functionCall adapter. The OpenAI family keeps in-chat image/speech generation.

Reuse: extracted runAnthropicChatStream / runGeminiChatStream (+ payload
builders) from the existing /api/proxy/{anthropic,google}/stream handlers; those
routes now delegate to the same functions (behaviour preserved, 79 proxy tests
green). New helpers classifyAIHubMixModel / aihubmixOriginFromBase /
aihubmixAppCodeHeader in aihubmix.ts. No SDK added.

Default AIHubMix chat model is now gpt-5.5 (an OpenAI-family model) so the
default keeps in-chat generate_image working after routing.

Tests: claude→/v1/messages and gemini→/gemini/.../streamGenerateContent routing
assertions in proxy-routes.test.ts.

* feat(byok): AIHubMix in-chat image/video/audio model selection + media-adapters

Bring the BYOK chat media surface to parity across image, video and audio:

- Composer + Settings dropdowns for image/video/speech models and TTS voice,
  threaded as byokVideoModel / byokSpeechModel / byokSpeechVoice. The user's
  explicit pick is authoritative over the LLM's tool `model` arg (regression
  the LLM kept overriding the composer choice). "Default" resolves to the first
  live-catalogue model (honest label) with the daemon const as final fallback.
- New apps/daemon/src/media-adapters: injection-style capability registry +
  family-aware video request builder (seedance content[] array vs generic
  input_reference), capability shape aligned with OpenRouter /videos/models.
  The chat video executor now builds requests through it.
- Per-family request shaping:
  * video: duration snapped to each family's allowed set (veo 4/6/8, sora
    4/8/12, wan 5/10); i2v reference resolved to a data URL.
  * image: gemini/nano-banana models routed to gemini-native generateContent
    (the /v1/images/generations shape 400s for them); others stay OpenAI-shaped.
  * speech: gemini TTS via generateContent with PCM->WAV wrapping and Gemini
    voice fallback; others use /v1/audio/speech (mp3).
  * download: same-origin AIHubMix asset URLs re-send Bearer + APP-Code.
- Clear, actionable errors for unsupported i2v / "params dropped" models, plus
  per-call submit logging (model + endpoint) for multi-model verification.
- i18n keys across 19 locales for the new pickers and the neutral default label.

Tests: byok-tools + media-adapters specs (85 passing); guard + daemon/web
typecheck green.

* feat(byok): media tool loops on claude/gemini chat + wan i2v wire + seedance resolution fix

Make AIHubMix in-chat media generation work regardless of which chat model is
selected, and fix two image-to-video wire-shape bugs surfaced by live testing.

chat-routes: the aihubmix routeByModel divert sent claude -> Anthropic
/v1/messages and gemini -> Gemini generateContent BEFORE the OpenAI tool loop,
so those chat models had no generate_image/video/speech tools ("没有音频工具").
Add per-protocol tool loops inside registerByokToolChatProxy (reusing the shared
executeOneTool): runAnthropicToolTurn/Chat parse tool_use content blocks +
input_json_delta; runGeminiToolTurn/Chat parse functionCall parts. The shared
single-pass runAnthropicChatStream/runGeminiChatStream and the direct
/api/proxy/{anthropic,google}/stream routes are untouched. Factor the per-kind
tool-result hint into buildToolResultContent (shared by all three protocols) and
add openaiToolsToAnthropic / openaiToolsToGemini schema converters (Gemini schema
sanitized to the OpenAPI subset). Gemini 3.x attaches a thoughtSignature to each
functionCall part that must be echoed back verbatim or the follow-up 400s, so
replay the raw functionCall part instead of reconstructing {name,args}.

media-adapters: seedance accepts resolution only as a token (480p/720p/1080p),
never a WxH pixel string — passing the aspect-derived 1280x720 made it 400
("resolution ... not valid ... in i2v"). Add snapResolutionToken to normalise.
Add a third request family `wan` for wan*/happyhorse*: verified against a working
happyhorse-1.0-i2v call, these are DashScope/wanx-backed and use
{ input:{ prompt, media:[{type:first_frame,url}] }, parameters:{ resolution, duration, prompt_extend, watermark } },
NOT the flat input_reference shape we were sending. Update the i2v failure-advice
message accordingly (the remaining risk is wanx needing a public image URL while
we send an inline data URL; seedance accepts the inline image).

Tests: anthropic+gemini in-chat tool loops (incl. thoughtSignature round-trip),
snapResolutionToken, seedance token resolution, and the wan input.media shape.

* feat(byok): add veo video family + default to seedance-2.0-fast

veo* models go through the gateway's OpenAI→Gemini predictLongRunning shim,
which wants `seconds` as a NUMBER and only honours `size` (no aspect_ratio) —
distinct enough from the generic flat shape to warrant its own `veo` family in
the media-adapters builder. Verified against a working veo-3.1-lite call.

Also default the BYOK chat video model to doubao-seedance-2-0-fast-260128 (the
broadly-available fast i2v/t2v path) and extend the media-adapters tests to
cover the veo family routing + duration snapping (4/6/8).

* feat(byok): hide account-source model labels for AIHubMix

AIHubMix's live model fetch returns its full account catalogue, so every
picker option carried an identical "from your account" badge that added
noise without distinguishing anything. Introduce a reusable
ACCOUNT_MODEL_SOURCE_LABEL_HIDDEN set + hidesAccountModelSourceLabel()
predicate: for listed protocols the per-option source label is dropped and
the loaded-models hint shows a plain count (new settings.modelsLoadedCount
key) instead of the account wording. Other providers are unaffected; add a
protocol to the set to opt it in.

* fix(byok): close AIHubMix catalogue SSRF + share gemini image branch

Two review fixes on the AIHubMix BYOK provider:

- SSRF: the public `/api/media/providers/aihubmix/models` GET took a
  caller-controlled `baseUrl` and fetched it server-side, so
  `?baseUrl=http://169.254.169.254/` could reach a private/metadata host.
  The catalogue lives at one fixed origin, so the route now ignores any
  `baseUrl` query and hard-codes the official AIHubMix origin — no
  attacker-chooseable fetch target remains. A focused route test asserts a
  malicious `baseUrl` is ignored and only aihubmix.com is contacted.

- Gemini image parity: the media renderer always sent AIHubMix image models
  to the OpenAI `/images/generations` endpoint, which 400s for
  gemini/imagen-family models. Extract the chat tool's Gemini-native branch
  into a shared `aihubmixGeminiImageBytes` helper and route those models
  through it from both the chat tool and the Home/New Project/CLI media path,
  so the catalogue the picker exposes is fully renderable. Added a media-path
  test that asserts a gemini-family id hits the generateContent wire.

* feat(byok): hide redundant gateway preset for single-origin AIHubMix

The "gateway preset" dropdown only ever offered AIHubMix itself (plus the
generic custom-endpoint option) because AIHubMix is a fixed-origin gateway
with exactly one preset — redundant UI. Gate it behind a reusable
GATEWAY_PRESET_HIDDEN set + hidesGatewayPreset() predicate (mirroring the
existing ACCOUNT_MODEL_SOURCE_LABEL_HIDDEN pattern) so another single-origin
gateway can opt in by adding its protocol, rather than hardcoding a check.
The Base URL field still shows the resolved origin; multi-preset and
custom-endpoint-reliant protocols (azure/google/senseaudio) are unaffected.

* feat(byok): hide Base URL field too for fixed-origin AIHubMix gateway

AIHubMix resolves its Base URL automatically, so the editable field was as
redundant as the gateway preset. Generalize the just-added GATEWAY_PRESET_HIDDEN
into one semantic concept — FIXED_ORIGIN_GATEWAYS + isFixedOriginGateway() — and
drive BOTH the preset dropdown and the Base URL field off that single predicate.
Adding a fixed-origin gateway protocol now hides both surfaces in one place; the
user only supplies an API key. azure/google/senseaudio are unaffected.

* fix(byok): close AIHubMix asset-download SSRF redirect bypass

The AIHubMix asset downloads validated the literal URL via
assertExternalAssetUrl but then fetched with the default redirect:'follow',
so a validated public URL could 302 the daemon into 169.254.169.254 / RFC1918
space (and leak Bearer/APP-Code to the hop target). Add a shared
assertAndFetchExternalAsset(url, init) helper that validates and pins
redirect:'error', and route every external asset download through it:
chat video/image (byok-tools.ts) and media image/video + SenseAudio image
(media.ts). Also add the missing isLocalSameOrigin guard to the AIHubMix
catalogue route so it matches its sibling media routes.

Regression tests lock the invariant: helper-level (blocks literal metadata
host, forces redirect:'error') plus chat video/image executors asserting the
download fetch carries redirect:'error', and a cross-origin rejection test on
the catalogue route.

Also point the AIHubMix API-key console link at the referral URL.

* feat(byok): carry New Project media-model pick into BYOK conversation

The model chosen in New Project → Media was stored on project.metadata but
dropped for BYOK providers (AIHubMix): media is produced by the generate_*
chat tools whose default comes from the per-request byok*Model field, which
ProjectView only seeded from Settings. Seed those overrides from the project's
creation-time pick instead, gated to the active BYOK provider so a cross-provider
model (e.g. a SenseAudio run whose image project defaulted to gpt-image-2) can't
clobber the user's Settings default. mediaModelProviderId resolves AIHubMix's
`aihubmix-` live ids by prefix so the seed survives before the async catalogue
loads.

* refactor(byok): remove in-chat media model selector pending unified picker

Drop the in-conversation BYOK image/video/speech model dropdowns from the chat
composer. Media model selection now comes from the project's creation-time pick
(seeded into the byok*Model overrides), and a unified selection surface will
replace this later. Also removes the now-dead option hooks/imports so the
composer no longer fires AIHubMix catalogue fetches it doesn't render. The
byok* props/handlers are retained as plumbing for the future unified picker.

* fix(byok): resolve AIHubMix fixed-origin base URL so model lists load

Hiding the Base URL field for the fixed-origin AIHubMix gateway left
config.baseUrl empty, which blocked the live model-list fetch (it gates on a
valid base URL) so the Settings dropdown and the top-bar switcher showed only
the static suggested models. Centralise the fixed-origin concept in
state/apiProtocols (FIXED_ORIGIN_GATEWAYS / isFixedOriginGateway /
resolveFixedOriginBaseUrl) and resolve the canonical origin in loadConfig so
every consumer sees it — including configs already persisted with an empty
baseUrl. SettingsDialog now imports the shared helpers instead of redefining
them.
2026-06-04 09:00:20 +00:00
41b1cd763e fix(media): hide OpenAI OAuth-only image credentials (#3308)
* fix(media): ignore OpenAI OAuth tokens

Agent-Model: gpt-5

Agent-Family: openai

Agent-Session: 019e6ceb-c33d-7cd3-bff0-cbc20c642197

Agent-Step: 0.0.1

* fix(media): hide unavailable model providers

Agent-Model: gpt-5

Agent-Family: openai

Agent-Session: 019e6ceb-c33d-7cd3-bff0-cbc20c642197

Agent-Step: 0.0.2

* fix(media): clear unavailable picker models

Agent-Model: gpt-5
Agent-Family: openai
Agent-Session: 019e6ceb-c33d-7cd3-bff0-cbc20c642197
Agent-Step: 0.0.3

* fix(media): keep missing-model projects executable

Agent-Model: gpt-5
Agent-Family: openai
Agent-Session: 019e6ceb-c33d-7cd3-bff0-cbc20c642197
Agent-Step: 0.0.8

---------

Co-authored-by: Codex <gpt-5@openai.com>
2026-05-30 04:12:10 +00:00