29 Commits
Author SHA1 Message Date
CaIon db80ac8e0c fix(task): let task plugins validate their own requests
Before billing, the gateway walked the whole decoded request of a task
plugin and rejected duration, seconds and count members that were out
of range at any depth, including vendor parameters passed through
metadata. A plugin had to declare duration-auto@1 for the gateway to let
a duration of -1 through.

The gateway no longer reads billing quantities from the request. It
still bounds every usage fact a plugin returns before quota is
calculated: second facts at most 3600, count facts at most 128, token and
credit facts saturated at the quota limit. Each plugin validates its own
request and derives its facts from the request it sends upstream.
duration-auto@1 is still accepted and has no effect.

Kling now bills the duration it sends. metadata.duration, which is the
whole body on native routes, overrides the top-level duration and must be
a whole number of seconds from 1 to 3600. Before, a metadata duration of
10 was sent upstream while 5 seconds were billed.
2026-10-22 16:23:18 +08:00
CaIon 206e0fa5c6 fix(kling): bump plugin to 1.1.1 2026-10-20 22:43:11 +08:00
Sheldon a8535fac10 fix(kling): accept single API keys (#7897) 2026-10-20 22:23:32 +08:00
CaIon f196934ce6 feat(plugins): bind Hailuo, Vidu and Veo plugins to New API channels
The hailuo, vidu, google and vertex-ai plugins now declare the new_api
upstream, so a New API (type 60) channel can bind them, and each serves its
vendor wire format on prefixed native routes that a downstream gateway
running the same plugin addresses:

- hailuo: /hailuo/v1/video_generation and /hailuo/v2/video_generation, with
  path-parameter queries. Through a gateway, v1 videos download from the
  upstream's task artifact, since only the upstream can resolve the MiniMax
  file link.
- vidu: /vidu/ent/v2/{text2video,img2video,start-end2video,reference2video}
  and the creations query; the route names the action, so one or two
  reference images stay a reference request.
- google: models/{model}:predictLongRunning and operation polling. The
  operation is renamed after the gateway task, and videos download from the
  upstream's task artifact, since the Gemini file needs the upstream key.
- vertex-ai: one dynamic route for predictLongRunning and
  fetchPredictOperation. Operation names echo the caller's project and
  location and never carry the upstream project.

Status presenters render a task that is not polled yet as pending and a
task the poller failed while its snapshot was still running as failed, so
the downstream gateway reads the upstream state and settles on the same
usage.

Versions: hailuo 1.3.0; vidu, google and vertex-ai 1.1.0.
2026-10-14 23:20:54 +08:00
CaIon bfdad293cd fix(vidu): bill the model, duration and resolution Vidu generates
buildSubmitRequest spread metadata over the request after the billed
fields were computed, so a model, duration or resolution inside metadata
reached Vidu while the charge used the top-level values: a request billed
as vidu1.5 for 4 seconds at 360p could generate viduq2 for 8 seconds at
1080p. The billed fields are now applied last; metadata still carries
every other Vidu field.
2026-10-14 23:20:54 +08:00
CaIon c8d234baa1 fix(xai): bill video edits and extensions by their output resolution
The "source" resolution tier was not an xAI price. xAI bills edits and
extensions at the output resolution, which keeps the input video's
resolution capped at 720p and is reported nowhere. xai 1.1.0 reserves 720p,
then reads the output MP4's track header with ranged utils.fetch calls to
vidgen.x.ai and settles at 480p when the shorter side is at most 480 pixels.
If the header cannot be read, the task settles at 720p.
2026-10-10 13:40:04 +08:00
CaIon ba3b2c015a refactor(jsplugin): stop requiring json-clone@1 and submit-sse-delta@1
utils.json.clone is available to every plugin, and a plugin that exports
parseSubmitEventDelta without parseSubmitEvent now gets the delta hook
without declaring anything. Both capability strings stay accepted for
plugins written for earlier hosts, and a plugin exporting both hooks keeps
the snapshot hook unless it declares submit-sse-delta@1. alibaba drops both
declarations.
2026-10-10 13:40:04 +08:00
CaIon 66c3e14282 fix(hailuo): download MiniMax v1 videos through the file retrieve link
The plugin pointed content requests at /v1/files/download, which MiniMax
does not serve, so v1 video downloads returned 410. hailuo 1.2.1 looks the
file up with /v1/files/retrieve through utils.fetch and proxies the signed
download_url without the channel key, using GET for HEAD clients because the
link is signed for GET.

Fixes #7709
2026-10-10 13:39:55 +08:00
CaIon b66674ac23 feat(task-plugin): let Seedance and wan3.0 pick the video duration with -1
A plugin that declares duration-auto@1 may receive a duration of -1 (the
vendor picks the length) anywhere in the request; other negative values and
values above the limit are still rejected, and usage facts stay
non-negative. A declaring plugin reserves the model's longest duration and
settles on the usage the vendor reports.

- doubao 1.3.0 declares it, forwards -1 to Ark and reserves each model's
  longest duration (12 s for Seedance 1.0 and 1.5 pro, 15 s for 2.0, 30 s for
  2.5). A JSON duration on /v1/videos now reaches Ark.
- alibaba 1.4.2 declares it, so wan3.0 smart duration also works on
  /v1/video/generations.

Fixes #7700
2026-10-10 13:39:44 +08:00
CaIon 0a532f7f7d feat(plugins): add an xAI Grok Imagine video plugin
xAI-type channels could only serve chat. The new built-in xai plugin
serves grok-imagine-video, grok-imagine-video-1.5, and
grok-imagine-video-1.5-lite through the native /xai/v1/videos routes
(generations, edits, extensions, and status), /v1/videos, and
/v1/responses, on xAI channels and on New API channels that bind it.

Usage bills output seconds by model and resolution, input images, and
the input video seconds of edits and extensions on grok-imagine-video.
Edits and extensions keep the input video's resolution, which xAI does
not report, so they bill under a separate "same as input video"
resolution. xAI still charges for a video its moderation filters after
generation and for a failed request that reports a cost, so those tasks
settle as charged with the moderated fact set instead of being refunded.
The native status route presents such a failure as a moderated result,
so a chained New API gateway charges it too. Other failures are refunded
as before.
2026-10-07 22:55:39 +08:00
CaIon ae73ef8e25 feat(plugins): add Seedream 5.0 flash and price each Seedream model by its own tiers
The doubao plugin now serves doubao-seedream-5-0-flash-260915 with 1K,
1.5K and 2K sizes, layer decomposition, output_format and the same
background rules as 5.0 pro.

Seedream models shared one usage schema, so the pricing matrix offered
an up-to-1.5K price and a layer decomposition price for every model.
Each model now gets a profile from its capability table: Seedream 5.0,
5.0 lite and 4.5 start at 2K and show a single image price, and layer
decomposition is listed only for 5.0 pro and flash. Seedance profiles
are built by the same helper. On completion a single-tier model counts
every delivered image at the upper tier, even when the result has no
parseable size.

Usage facts keep all four released image keys, and every Seedance model
still reports video_input, so expressions saved before the per-model
profiles charge as before.
2026-09-30 20:52:51 +08:00
CaIon 87bb71e7fc feat(plugins): serve Seedream images through the OpenAI Images protocol in doubao
Claim openai_image for the Seedream image models so /v1/images/generations and
/v1/images/edits run through the task plugin instead of the HTTP image relay,
which has no usage facts for u() expressions.

Requests map onto the same Ark body as /doubao/api/v3/images/generations, so
validation and usage facts are identical: images_up_to_1_5k, images_above_1_5k,
input_images and layer_decomposition. n only accepts 1 (group generation covers
multiple images), stream and mask are rejected, response_format is left to the
host, and multipart uploads are inlined as data:image Base64 URLs.
2026-09-30 17:24:19 +08:00
feitianbubu 474ed66fb1 fix(plugins/alibaba): keep upstream metadata in openai_image render (#7507) 2026-09-21 20:46:28 +08:00
CaIon 47713bcb17 feat: update plugins 2026-09-20 23:23:48 +08:00
CaIon 65d3a2171f fix(plugins): price only the resolutions each Seedance and Wan model offers
The doubao plugin shared one usage schema across every Seedance model, so
the pricing matrix listed 1080p and 4k rows for Seedance 2.0 mini and fast
and a 4k row for Seedance 2.5, none of which Ark offers, and priced 1.0
and 1.5 pro by a reference-video dimension they do not accept. Describe
each video model with its resolutions, reference-video and audio
capabilities from the Ark model list and derive the usage schema,
examples and profiles from that table; reject unsupported resolutions at
submission, reserve the model's highest tier when the resolution is left
to Ark and ignore an out-of-profile resolution on completion. Seedance
1.5 pro gains the generate_audio fact its audio and silent prices need.

The alibaba plugin likewise derives one Wan video profile per resolution
set from the model capability table instead of listing 480P/720P/1080P
for every model, and adds the audio fact for wan2.6-i2v-flash.
2026-09-20 22:17:20 +08:00
CaIon 03563a4a79 feat(plugins): serve OpenAI Images API through task plugins
Add the synchronous openai_image host protocol so a task plugin can claim
POST /v1/images/generations and /v1/images/edits; the host submits the
task, polls it inside the request and lets the plugin render the OpenAI
image response. Unclaimed models still fall back to the HTTP image relay.

- Migrate Ali (Bailian) image models to the alibaba plugin and remove the
  ali channel image/wan adaptors and the qwen model setting
- Add Doubao Seedream image generation to the doubao plugin
- Support retainResult on routes and indexed multipart file references
- Settle image counts from upstream usage or image payloads; document the
  rule in the billing conventions and plugin API docs
- Show billing source and task artifacts in usage logs; rename the
  DoubaoVideo channel display name to Doubao and refresh the favicon
2026-09-20 17:58:45 +08:00
CaIon 4c34f25a49 feat(channel): bind multiple task plugins to New API channels
A New API (type 60) channel proxies another gateway that may host many
task plugins, so the channel setting gains task_extend_plugin_keys; the
single task_plugin_key stays valid there as well. Channel selection,
shared-model candidate rebinding, the task_plugin.bind permission gate,
plugin usage counting and the disable cascade (which now unbinds gateway
channels instead of disabling them) all honour the union of both fields,
and plugins may no longer claim channel type 60 in channelTypes.

Plugins declare meta.upstreams and the host injects ctx.upstream into
every driver hook context. On a type-60 channel the driver addresses its
own prefixed native routes (doubao, alibaba, kling, jimeng) and the host
sends the channel key as a Bearer header for every auth type; polling
threads the channel type through ChannelMeta and tasks keep the key
that submitted them. Only plugins declaring new_api can be bound.

The channel form offers an "Upstream task plugins" selector for type-60
channels, MultiSelect options can carry an icon, and the plugin API docs,
type declarations and schema describe the additive apiVersion 1
extension.
2026-09-20 17:51:51 +08:00
CaIon a8ed7f7c5f fix(plugins/hailuo): bill only the dimensions each model family supports
Only MiniMax-H3 charges for input media and each family renders its own
set of output resolutions, so build the usage schema per model instead of
sharing one schema across the plugin.
2026-09-18 11:24:30 +08:00
CaIon 74629e29f8 feat(plugins): enhance task streaming and model pricing 2026-09-11 21:44:56 +08:00
CaIon a20574136b fix(alibaba): correct Wan model protocols and usage accounting 2026-09-09 15:05:37 +08:00
CaIon 9bf328d974 fix: preserve provider fields in Sora video queries 2026-09-08 23:46:37 +08:00
CaIon 99974a814f feat(plugins): extend plugin metadata and icon support 2026-09-08 15:15:55 +08:00
CaIon 92bc7ff73c fix(plugins): make sunoapi alias-safe and lock alias echo across built-ins
Decode on ctx.upstreamModel || ctx.model and echo ctx.model; fix the
lyrics/music render branch that read a nonexistent ctx.requestBody.model;
stop sending empty Accept/Content-Type. Bump sunoapi to 1.0.2. Add an
alias-echo table test covering every built-in.
2026-09-07 22:38:08 +08:00
Calcium-Ionandqiuliw 3b4652269a feat(ali): support wan3.0 all-in-one video models
* feat(ali): support wan3.0 all-in-one video models

Extend the Alibaba task plugin for wan3.0-video / wan3.0-video-prime
using DashScope media, resolution/ratio, duration rules, and billing ratios.

* fix(ali): make wan3.0 smart duration billable and alias-safe

Review fixes on top of #7240 (qiuliw):

- duration -1 never reached the plugin: the host rejects negative
  canonical duration/seconds facts before any hook runs, on every
  entry point. Decoders now normalize -1 into an auto_duration marker;
  convert emits -1 upstream and bills 30s up front; non-wan3.0 models
  reject the marker instead of silently defaulting to 5s.
- extractUsageOnComplete read output.duration/output.resolution, but
  wan3.0 reports usage.output_video_duration and numeric usage.SR, so
  smart-duration and resolution settlement never reconciled. Read the
  usage block first, keep the legacy output fields as fallback.
- convert keyed default resolution on the client model name, so a
  channel-mapped alias fell to 720P while the direct request got 1080P.
  Every model-shaped decision in convert now uses ctx.upstreamModel.
- Unknown wan3.0 size values were silently coerced to 1080P; reject them.
- Image-only openai_responses input was accepted for every model,
  regressing t2v models into pre-consume then upstream rejection.
  Restore the guard and allow image-only for i2v and wan3.0 only.
- Native passthrough dropped wan3.0 parameters (ratio, audio) unless
  media was present; forward them via metadata for wan3.0.
- Hoist the triplicated size-to-resolution map; add wan3.0 contract tests.

---------

Co-authored-by: qiuliw <a1807191473@qgmail.com>
2026-09-07 12:37:43 +08:00
CaIon 9df450fe54 feat(task): give polling hooks a real query context, host HTTP classification, and bounded poll failures
Plugin polling hooks previously ran against a hollow context: parseTaskResult
and parseBatchResult received {} / nil, buildQueryRequest received a
{task_id, action} map under the misleading name requestBody, and batch hooks
saw only bare task ids. The per-task poller also never looked at the upstream
HTTP status, and every built-in plugin papered over unrecognized bodies with
`|| "IN_PROGRESS"`, so a 404, a revoked key, or a shape the plugin did not
know would sit in IN_PROGRESS for the full 24h TASK_TIMEOUT_MINUTES while
holding the user's pre-charged quota.

Contract (docs/plugin-api v1.d.ts, v1.md, v1.schema.json):
- TaskQueryContext is declared separately from DriverContext and rebuilt from
  the persisted Task row: taskId, publicTaskId, action, model, upstreamModel,
  baseUrl, apiKey, authHeader, auth, data, state. Query-side requestBody is
  removed; the original request is not persisted and hooks that need a
  request-derived value must save it into state at submit time.
- parseTaskResult / parseBatchResult receive a third {status, headers}
  argument. Batch hooks receive tasks[] with one TaskQueryContext per task.
- NormalizedTaskResult accepts status "UNKNOWN" meaning "I do not recognize
  this body". Falling back to IN_PROGRESS for unknown shapes is forbidden;
  `plugin lint` warns on the literal.
- parseSubmitResponse / parseTaskResult / parseBatchResult may return `state`.
  Task.Data remains a per-round snapshot overwritten on every valid parse;
  state is plugin-owned, persisted in TaskPrivateData.PluginState, preserved
  when a hook omits it, byte-capped like taskData, and never exposed through
  presenter views.

Host (service/task_polling.go, relay/channel/task/jsplugin/adaptor.go):
- TaskPollingAdaptor / BatchTaskPollingAdaptor take *model.Task and the
  *http.Response so the adaptor can build the full context; jsplugin is the
  only implementation.
- HTTP classification before the plugin sees the body: 2xx -> plugin;
  404/410 -> FAILURE and refund; 401/403 -> poll failure plus a channel-scoped
  warning, no auto-disable; 429/5xx/transport -> poll failure; other 4xx ->
  plugin with the status visible, counted as unrecognized if the plugin still
  reports a non-terminal state.
- TaskPrivateData.PollFailures counts consecutive poll failures (transient
  HTTP, auth, transport, hook error, UNKNOWN). It is persisted through the
  existing UpdateWithStatus CAS so a concurrent terminal transition on another
  instance is never clobbered, and reset on any valid 2xx non-terminal parse.
  Reaching TASK_POLL_MAX_FAILURES (default 20, <= 0 disables) fails the task
  with the last classification and HTTP code in fail_reason and runs the
  existing settle/refund chain exactly once. sweepTimedOutTasks and its
  1440-minute default are unchanged as the outer backstop.
- Unrecognized bodies are logged at WARN with a bounded redacted copy since
  Task.Data is intentionally not overwritten on that path.

Plugins (all ten bumped one patch version):
- jimeng persists the outbound req_key in state and reads it back in
  buildQueryRequest, replacing dead reads of ctx.data / ctx.requestBody that
  never resolved.
- sunoapi batch hooks read tasks[] instead of the removed requestBody.
- hailuo treats base_resp.status_code != 0 as FAILURE before the status table.
- kling, vidu, sora, alibaba, doubao, hailuo, jimeng return UNKNOWN with the
  raw upstream status in reason on table miss.
- google and vertex-ai treat a missing `done` as in-progress: Google
  long-running operations omit proto3 default fields, so a running Veo
  operation has no `done` key at all. Only a body without an operation name is
  UNKNOWN. plugins/veo_poll_test.go locks this so the poll-failure cutoff can
  never fail a rendering Veo task.

Tests cover the classification table end to end against a real DB (404
immediate refund, 429xN refund, 401 increments without status change, 2xx
reset, UNKNOWN increments, state preserved vs replaced, PollFailures survives
the CAS write), the query-context shape, UNKNOWN on unrecognized bodies, and
the absence of PluginState/PollFailures from TaskView. Controller tests derive
the kling factory version from the embedded manifest instead of hardcoding it.
2026-09-03 14:37:47 +08:00
Calcium-Ion 73afad588c fix(plugin): account for MiniMax-H3 input media usage (#7171) 2026-09-03 11:26:35 +08:00
星云猫 aece11d2f7 feat(plugin): add MiniMax-H3 /v2 video generation to the hailuo task … (#7168)
* feat(plugin): add MiniMax-H3 /v2 video generation to the hailuo task plugin

MiniMax-H3 speaks a different contract from the other Hailuo models, so the
hailuo task plugin now branches on the upstream model instead of adding a Go
adaptor:

- submit builds /v2/video_generation with a multimodal `content` array
  (text, first/last frame images, reference video/audio, or a full
  `metadata.content` passthrough), an explicit `ratio`, and 768P/2K
  resolutions; `metadata.callback_url` and `metadata.aigc_watermark` pass
  through
- query uses /v2/query/video_generation/{task_id} and parses the
  `{"task": {...}}` envelope, falling back to the /v1 shapes for every other
  model
- the /v2 result is a public CDN URL, so its artifact is proxied
  credentialless instead of through /v1/files/download
- request bounds (duration 4-15, resolution 768P/2K, ratio whitelist, at most
  2 frame images and 9/3/3 reference images/videos/audios) are enforced while
  the request body is built, which the host runs during validation, so an
  out-of-range duration is rejected with a 400 before it can become a billing
  multiplier
- duration and resolution are reported as usage facts only. Like the rest of
  this plugin, extractUsage returns no billing ratios, so per-call pricing is
  flat and 2K/duration pricing is expressed through the model's tiered billing
  expression over those facts.

Query hooks are driver hooks and are documented to receive `ctx.model` and
`ctx.upstreamModel`, but polling has no relay info and never populated them.
The polling and realtime-fetch call sites now carry the persisted task model
properties and the plugin adaptor maps them onto the query context, with
`upstreamModel` falling back to the origin name for tasks submitted without a
channel mapping.

* fix(plugin): validate Hailuo H3 requests and errors
2026-09-03 11:04:48 +08:00
CaIon 6c22550ea3 feat(task): resolve channel-mapped aliases and case variants for plugin models
Channel model_mapping keys exposed in a channel's model list now act as
first-class aliases for task-plugin models across the whole line:

- Derived alias view (model/task_model_alias.go): built from enabled
  channels' model_mapping, chain-following with cycle detection, declared
  names always win, cross-plugin conflicts dropped. Rebuilt on channel
  cache refresh, registry generation change, and a 60s TTL.
- Request path: PinTaskPluginEndpoint resolves declared-name case folds
  and mapping aliases before endpoint lookup (never rewriting the body
  until the endpoint is claimed), pins with MappedModel, and the decode
  contract accepts alias echoes without loosening model ownership for
  normal pins. Legacy /v1/tasks submit folds case variants the same way.
  Fixes aliases on POST /v1/responses silently falling through to the
  main relay against task channels.
- Mapping order: ModelMappedHelper now runs before the plugin submit
  hook builds and caches the upstream body, so channel model_mapping
  actually reaches the upstream request. Plugins receive the mapped
  name as ctx.upstreamModel in both decode and submit contexts.
- Billing: identity stays the origin name; when the alias has no tiered
  expression, the selected channel's mapping tail expression applies.
  Pricing page and billing-expr smoke tests resolve aliases to the
  owning plugin's usage schema.
- Case folding: ASCII-only fold with exact-match priority; same-plugin
  and cross-plugin fold collisions rejected at registration.
- Plugins: model-keyed rate tables, req_key derivation, and combo
  validation in doubao/kling/jimeng/hailuo/vidu/sunoapi now key on
  ctx.upstreamModel || ctx.model; render/echo paths keep ctx.model.
2026-08-30 19:13:51 +08:00
Calcium-Ion eb48396d5f feat(task): replace built-in task adaptors with a sandboxed JS plugin system (#7076) 2026-08-29 18:51:57 +08:00