feat: sync deployed v1.0.0-rc.38-custom.42
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
This commit is contained in:
1 parent
aa6c3f50f4
commit
ceddfd3d6b
828 files changed
+122253
-13702
No files matched your search
+247
-4
@@ -28,9 +28,9 @@ export const meta = {
|
||||
|
||||
Each `protocols` entry claims a host protocol. A protocol that defines modes must be claimed in object form with an explicit `supports`; the bare-string form is valid only for protocols without modes (`openai_video`). `supports` declares which client request forms the plugin accepts on `openai_responses`: `"stream"` (`stream: true`), `"sync"` (neither flag; the call blocks until the terminal Response), `"background"` (`background: true`; the create returns a pending Response immediately). An unsupported request form is rejected with a 400 at channel selection, before any plugin hook runs and before billing. Retrieval (`GET /v1/responses/:response_id`) is not a mode: every created response is always retrievable.
|
||||
|
||||
`{name, models}` still narrows that protocol's endpoint bindings to a subset of `meta.models` and composes with `supports`. Unlisted models never enter the plugin on that protocol path — they fall through to the built-in Go relay. Cross-plugin endpoint conflicts are judged on the narrowed set, so two plugins may share one protocol path by claiming disjoint model sets.
|
||||
`{name, models}` still narrows that protocol's endpoint bindings to a subset of `meta.models` and composes with `supports`. Unlisted models never enter the plugin on that protocol path — they fall through to the built-in Go relay. Multiple plugins may claim the same model on the same host protocol path, including type-61 plugins and plugins with legacy `channelTypes`. Candidates are ordered by ascending plugin key. The selected channel determines the executing plugin: its channel type for legacy channels, or `setting.task_plugin_key` for type-61 channels.
|
||||
|
||||
Enabled uploads pre-flight the candidate against the live routing generation and reject the first channel-type, native-route, or protocol-model conflict (the error names the counterpart plugin). Set `force: true` or `enabled: false` to store the plugin anyway.
|
||||
Enabled uploads pre-flight the candidate against the live routing generation and reject channel-type, native-route, or incompatible protocol conflicts (the error names the counterpart plugin). Sharing a model on the same host protocol is allowed. Set `force: true` or `enabled: false` to store the plugin anyway.
|
||||
|
||||
`endpoints`, `routes[].renderer`, global `resolveRequest`, global `renderError`, and global `renderers` are rejected. `parseSubmitResponse` returns only `{taskId, taskData, immediate?, state?}`; `clientResponse` is rejected.
|
||||
|
||||
@@ -57,13 +57,97 @@ A plugin whose vendor has no LobeHub icon ships its logo as a sidecar file, `ico
|
||||
|
||||
`channelTypes` lists the legacy channel types this plugin's driver can drive (for example, sora declares `[55, 1]` because the same OpenAI-type base URL and bearer key serve both chat and video). Every entry equally participates in channel selection, historical `Task.Platform` matching, and the `byChannelType` routing index; the same type value may not appear on two plugins. Third-party plugins normally omit `channelTypes` and live on type-61 "Task Plugin" channels bound by `task_plugin_key`. The previous split identity/compatibility field names are rejected.
|
||||
|
||||
Before channel distribution, the host calls every mode-compatible candidate's
|
||||
`decodeRequest`. Only candidates returning a valid submit intent for the pinned
|
||||
model remain eligible. If all reject the request, the host returns the candidate
|
||||
failures, grouping identical messages with their plugin keys. A single candidate
|
||||
retains its original failure message. After channel selection, the executing plugin decodes again to build
|
||||
its own normalized request. Decoders must therefore tolerate repeated calls.
|
||||
Channel-type ownership remains unique; sharing models does not relax this rule.
|
||||
|
||||
Shared models may use different usage schemas and prices for each plugin.
|
||||
Administrators can save a provider expression in
|
||||
`billing_setting.plugin_billing_expr`, keyed by `<pluginKey>::<model>`.
|
||||
Without an override, the provider inherits the model-level expression. If the
|
||||
effective expression references a literal `u()` key absent from that provider's
|
||||
selected schema, the shared model is unconfigured for that provider; submission
|
||||
returns 400 `model_price_error` until compatible pricing is saved. Pricing pages
|
||||
show the provider variants and their configuration status. Model-level usage
|
||||
labels continue to come from the first plugin in ascending key order. Saving a
|
||||
model expression checks every candidate without its own override in the same
|
||||
draft. Provider overrides are not included in model-price synchronization.
|
||||
|
||||
Saved overrides remain visible and effective when only one provider is left.
|
||||
An override whose plugin is unavailable or no longer declares the model is
|
||||
marked `stale` in the administration snapshot. Its unchanged value can be kept
|
||||
while saving other prices, or explicitly removed; adding or changing an override
|
||||
still requires a current plugin/model binding. Public variants distinguish
|
||||
`ratio` fallback pricing from `tiered_expr`, so a provider using a configured
|
||||
per-call price is not marked unconfigured merely because it has no expression.
|
||||
|
||||
Numeric `usageSchema` fields declare a host-owned unit of `second`, `count`, `token`, or `credit`. Boolean fields declare `{type: "boolean"}`.
|
||||
|
||||
Number fields with `unit: "count"` may additionally declare `unitLabel`, an
|
||||
optional LocalizedText display unit. This distinguishes counted objects without
|
||||
changing the canonical unit, usage facts, expression keys, prices, or settlement:
|
||||
|
||||
```js
|
||||
image_count: {
|
||||
type: "number",
|
||||
unit: "count",
|
||||
unitLabel: {en: "image", zh: "张", "zh-TW": "張"},
|
||||
description: {en: "Image generation unit price", zh: "图片生成单价"},
|
||||
}
|
||||
```
|
||||
|
||||
Labels are short unit names or counting words, such as `image` / `张` or
|
||||
`song` / `首`, without prices, quantities, slashes, or trailing punctuation.
|
||||
They follow the same locale normalization, required English fallback, and
|
||||
256-rune-per-locale limit as field descriptions. A bare string is normalized to
|
||||
an English map. The UI uses the current language, its primary language, then
|
||||
English; omitted labels retain the host's existing count-unit display.
|
||||
Labels are plugin data, not frontend translation keys. They apply equally to
|
||||
`usageSchema` and `usageProfiles[].schema`, including pricing, calculators,
|
||||
plugin details, and usage-log pricing. Other units, booleans, and enums cannot
|
||||
declare `unitLabel`; token prices retain their per-million display scale.
|
||||
|
||||
This is an additive extension of plugin API version 1. Updated hosts accept
|
||||
existing plugins unchanged; older hosts reject the unknown `unitLabel` property.
|
||||
Upgrade the host before installing a plugin that declares it.
|
||||
|
||||
`meta.usageExamples` is an optional display-only list of pricing examples. Each `label` is a human-readable spec name. Each `facts` object must be a complete vector over `usageSchema`: every declared key present, no undeclared keys, and values that pass the same validation as usage facts. Plugins whose schema contains a `unit: "token"` number field must declare at least one example. Examples never affect billing. Labels are spec names such as `std · 10s`, not storefront prices.
|
||||
|
||||
### Usage profiles by model
|
||||
|
||||
`meta.usageProfiles` optionally contains `{models, schema, examples?}` entries.
|
||||
Each profile provides the complete schema and display examples for its models,
|
||||
replacing `usageSchema` and `usageExamples` without merging or inheriting them.
|
||||
Unmatched models use the plugin defaults. Models must use the declared spelling
|
||||
from `meta.models` and belong to at most one profile. Empty model lists and
|
||||
missing/null schemas are rejected; an empty schema object is valid. Token-unit
|
||||
profiles require their own examples. The same field, description, and example
|
||||
validation applies inside a profile as at the plugin level.
|
||||
|
||||
Define constants by use case (for example `IMAGE_USAGE_SCHEMA` and
|
||||
`VIDEO_USAGE_SCHEMA`) and reference them from the profiles. These are ordinary
|
||||
JavaScript constants: the host executes the module before reading `meta`.
|
||||
Static consumers can use the compiled display metadata in the marketplace index.
|
||||
|
||||
Model-level pricing and save validation select the declared target of an alias;
|
||||
ambiguous aliases retain the plugin defaults. Runtime validation selects the
|
||||
executing plugin's final upstream model, including each persisted task during
|
||||
batch polling. Existing saved expressions are not migrated. Single-plugin models retain their
|
||||
existing submission checks; shared models additionally enforce the per-provider
|
||||
field-reference compatibility rule above. Normal evaluation and quota safety
|
||||
checks continue to apply. Saving an expression uses its model's selected schema.
|
||||
|
||||
This remains plugin API version 1. New hosts accept older plugins unchanged;
|
||||
older hosts reject the new `usageProfiles` field. Upgrade the host to a build
|
||||
supporting profiles before installing a plugin that declares them.
|
||||
|
||||
## LocalizedText
|
||||
|
||||
`meta.description` and each `usageSchema` field `description` accept LocalizedText. A bare string is equivalent to `{en: <string>}`. A map must include a non-empty `en` value. The host normalizes both forms to a map; API responses always emit an object.
|
||||
`meta.description` and each `usageSchema` field `description` and `unitLabel` accept LocalizedText. A bare string is equivalent to `{en: <string>}`. A map must include a non-empty `en` value. The host normalizes both forms to a map; API responses always emit an object.
|
||||
|
||||
Enum fields may additionally declare `enumLabels`, a map from enum values to LocalizedText display names:
|
||||
|
||||
@@ -116,6 +200,37 @@ Descriptions are short, user-facing phrases without trailing punctuation in any
|
||||
- For example, replace `Requested video duration in seconds.` / `请求的视频时长,单位为秒。` with `Video generation unit price` / `视频生成单价`; replace `Whether audio is generated. Default true.` / `是否生成音频。默认为 true。` with `Whether audio is generated` / `是否生成音频`.
|
||||
- These are authoring conventions for plugin metadata, not additional runtime validation rules. Existing `LocalizedText` wire types, usage quantities, enum values and billing semantics are unchanged; a numeric field still contains usage, not a price.
|
||||
|
||||
## Host utilities and capabilities
|
||||
|
||||
`utils` exposes synchronous host tools. Existing utilities are `unixNow`, `uuid`,
|
||||
`hmacSHA256`, `jwtSignHS256`, `base64`, `base64URL`, `base64URLDecode`, and
|
||||
`volcSignV4`; their signatures are declared in `v1.d.ts`.
|
||||
|
||||
Plugins using new APIs declare exact versioned capabilities:
|
||||
|
||||
```js
|
||||
requiredCapabilities: ["json-clone@1", "submit-sse-delta@1"]
|
||||
```
|
||||
|
||||
The host rejects unknown, duplicate, or unsupported capability versions at load.
|
||||
Omitting the field preserves existing behavior. `utils.hasCapability(name)`
|
||||
supports optional feature detection on hosts that provide this utility; it
|
||||
does not grant a declared capability. These are additive API v1 extensions;
|
||||
older strict hosts reject `requiredCapabilities`, so upgrade the host first.
|
||||
|
||||
`json-clone@1` provides `utils.json.clone(value)`: an independent JSON snapshot
|
||||
with native mutable JavaScript objects and arrays. It accepts plain objects,
|
||||
arrays, null, booleans, finite numbers and strings. Undefined, functions,
|
||||
BigInt, dates, sparse arrays and cyclic structures are rejected. Object keys
|
||||
such as `__proto__` remain ordinary own data properties. The tool uses the
|
||||
host's JSON number and string normalization and preserves explicit zero and
|
||||
false values. Limits are 1 MiB of encoded JSON, 32 levels below the root and
|
||||
32,768 total nodes, including containers and scalar values. It performs no I/O.
|
||||
|
||||
Host functions are synchronous and must bound their own work: the JavaScript
|
||||
execution interrupt cannot preempt a running Go function. Request-scoped data
|
||||
must remain owned by the host request, not by pooled runtime globals.
|
||||
|
||||
## Request body
|
||||
|
||||
Every decoder receives one host-parsed body:
|
||||
@@ -180,7 +295,7 @@ The host registry owns these bindings:
|
||||
|
||||
`background: true` on create returns the pending Response immediately (host-synthesized, no plugin hook). Retrieval renders via `renderFinal` only at terminal status. Plugins declaring `"background"` need no create-time hook; the deliverable is rendered at retrieval via `renderFinal`. Plugins cannot observe the `background` field. Unlike the upstream OpenAI API, a plugin that does not declare `"stream"` rejects `stream: true` with a 400; the error names the supported forms.
|
||||
|
||||
Protocol media uses host-injected `ctx.artifacts[key].url`. Provider URLs from `task.data` are not protocol output. OpenAI Video projections are DTO-whitelisted and the host overwrites identity, lifecycle, timestamps, and removes URL-like metadata.
|
||||
Responses protocol media uses host-injected `ctx.artifacts[key].url`. OpenAI Video renderers may return provider fields and URLs from `task.data`; the host preserves those extensions and metadata, but overwrites `id`, `object`, `model`, `status`, `progress`, `created_at`, and `completed_at` and removes legacy `task_id`. Renderers must return a JSON object. Returning `task.data` exposes the latest persisted upstream snapshot, subject to the host's task-ID replacement and inline-video redaction; it does not perform a fresh upstream query or forward HTTP headers/status codes.
|
||||
|
||||
## Persisted data and driver hooks
|
||||
|
||||
@@ -224,3 +339,131 @@ The host classifies the HTTP status before trusting a non-terminal parse:
|
||||
A valid 2xx non-terminal parse resets `PollFailures` to 0. After `TASK_POLL_MAX_FAILURES` (default 20) consecutive failures the task becomes `FAILURE` and follows the existing refund chain. The 24h `TASK_TIMEOUT_MINUTES` sweep remains the outer deadline.
|
||||
|
||||
Marketplace index v1 plugin entries also accept optional `sortPriority` and `website` fields. Each source is sorted independently by descending priority and ascending key. Missing or invalid priorities fall back to `0`; invalid websites are hidden. Installed plugins and channel binding options use plugin metadata. Website links open only on user interaction; the host does not fetch them.
|
||||
|
||||
## Immediate completion and submission streams
|
||||
|
||||
A driver may return `immediate: {status: "SUCCESS" | "FAILURE", ...}` from
|
||||
`parseSubmitResponse`. For immediate success the host calls the existing
|
||||
`extractUsageOnComplete(ctx, result, taskData)` with a `TaskQueryContext`, using
|
||||
both model identities, the submitted task ID, parsed data and returned plugin
|
||||
state. The same model-specific usage validation as polling applies. Plugins
|
||||
return usage quantities, never host quota or a charge.
|
||||
|
||||
For task expressions the host overlays valid completion facts onto the frozen
|
||||
submission facts, evaluates the frozen expression and reserves any increase
|
||||
before persistence. The final quota, usage and tier are recorded before the
|
||||
single BillingSession settlement. Zero usage is retained. Invalid facts or an
|
||||
expression evaluation failure retain the reservation and produce a warning.
|
||||
Immediate failure settles to zero. Terminal tasks are not polled. Legacy
|
||||
per-call pricing keeps its existing meaning and submission-ratio hook.
|
||||
|
||||
### Optional SSE response reader
|
||||
|
||||
`meta.submitResponseTypes` defaults to `["json"]`. A plugin that needs SSE
|
||||
includes `"sse"` and exports `parseSubmitEvent`. `buildSubmitRequest` selects
|
||||
`responseType: "sse"` on its request descriptor (default `"json"`); the selected
|
||||
type must be declared. This optional v1 manifest field requires a supporting
|
||||
host: older strict hosts reject it instead of silently misreading the stream.
|
||||
|
||||
```ts
|
||||
parseSubmitEvent(ctx: DriverContext,
|
||||
event: {event: string; id: string; data: string},
|
||||
previousState: JSONValue | null
|
||||
): {state: JSONValue; done: boolean}
|
||||
```
|
||||
|
||||
The host accepts `text/event-stream`, joins multiple `data:` lines with newlines,
|
||||
handles CRLF, comment heartbeats, event names (default `message`) and event IDs.
|
||||
It does not parse provider JSON, interpret provider completion markers or sum
|
||||
usage. The plugin does those operations and returns explicit JSON state on every
|
||||
call; pooled runtimes do not guarantee module globals survive between events.
|
||||
Treat `previousState` as read-only JSON input. Construct owned arrays/objects
|
||||
when accumulating, rather than mutating host-backed arrays in place.
|
||||
|
||||
Idle runtimes survive garbage collection in a pool bounded by the plugin's
|
||||
configured concurrency. The host creates additional instances on demand and
|
||||
discards interrupted instances. Cancellation callbacks finish before an instance
|
||||
is returned to the pool. Hook bindings remain live; the host does not freeze
|
||||
exported functions or object members. No JavaScript execution slot is held while
|
||||
waiting for the next upstream event.
|
||||
|
||||
The host may copy plain JSON containers without re-encoding their string values;
|
||||
hook inputs and successive states remain separate snapshots. State size is still
|
||||
checked against its actual encoded JSON bytes after every event, including
|
||||
intermediate state that a later event might replace with a smaller value.
|
||||
|
||||
A complete, blank-line-delimited event whose hook returns `done: true` terminates
|
||||
the reader and closes the upstream response. The last state is passed as
|
||||
`response.body` to the existing `parseSubmitResponse`, retaining the upstream
|
||||
HTTP status and headers. The normal immediate-completion path then persists,
|
||||
settles and presents the result. SSE state is request-local; durable plugin
|
||||
state is still only the `state` returned by `parseSubmitResponse`.
|
||||
|
||||
EOF without `done`, invalid framing/state, read failures, hook failures and size
|
||||
limits fail submission. Events, accumulated JSON state and persisted task data
|
||||
are each limited to 1 MiB; data is never truncated. The existing streaming idle
|
||||
timeout applies (30 seconds if unset), as do the submission context deadline
|
||||
and per-hook execution timeout. Once a successful SSE HTTP response is accepted,
|
||||
failures are non-retryable, including 5xx-shaped local errors: starting the
|
||||
provider operation again could duplicate billable work.
|
||||
|
||||
No upstream events are forwarded to clients. Native presenters still return
|
||||
JSON, and Responses uses its existing presentation flow after the durable
|
||||
submission barrier. Native cancellation closes the upstream reader; Responses
|
||||
retains its existing independently bounded submission context. This interface
|
||||
does not add task creation before upstream completion or stream resumption.
|
||||
|
||||
### Optional host-managed SSE accumulation
|
||||
|
||||
Declaring `submit-sse-delta@1` requires `submitResponseTypes` to include `sse`
|
||||
and selects `parseSubmitEventDelta` instead of `parseSubmitEvent`. The descriptor
|
||||
still uses `responseType: "sse"`. Existing plugins without this capability keep
|
||||
the snapshot hook and its original contract.
|
||||
|
||||
```ts
|
||||
parseSubmitEventDelta(ctx: DriverContext,
|
||||
event: {event: string; id: string; data: string},
|
||||
previousState: JSONValue | null
|
||||
): {changes: JSONChange[]; state: JSONValue; done: boolean}
|
||||
```
|
||||
|
||||
`state` contains control data needed to interpret the next event, limited to
|
||||
64 KiB encoded JSON. Only this snapshot is passed back to JavaScript. The host
|
||||
maintains a separate result, initially null, and applies `changes` in order:
|
||||
|
||||
```js
|
||||
{op: "set", path: [], value: {text: "", images: []}}
|
||||
{op: "appendText", path: ["text"], value: "A new text fragment"}
|
||||
{op: "append", path: ["images"], value: {url: "https://cdn.example/image.png"}}
|
||||
```
|
||||
|
||||
- `set` replaces the root at an empty path, replaces an existing array element,
|
||||
or adds/replaces an object member. Parent containers must already exist.
|
||||
- `append` appends one JSON value to an existing array.
|
||||
- `appendText` appends a string to an existing string. The host retains text in
|
||||
Go and does not send accumulated text back to the plugin.
|
||||
- Paths are arrays of string object keys or non-negative integer array indices.
|
||||
Indices must address existing elements; sparse arrays and implicit parent
|
||||
creation are unsupported. Keys are data, never property-access expressions.
|
||||
|
||||
Each result contains exactly `changes`, `state`, and boolean `done`. Each change
|
||||
contains exactly `op`, `path`, and `value`. There are at most 256 changes per
|
||||
event, 32 path segments, 32 levels below the result root, 32,768 result nodes,
|
||||
and 1 MiB of encoded result JSON. Size accounting includes escaped keys and
|
||||
strings, punctuation and replacements, and is checked after every operation.
|
||||
An oversized intermediate operation fails even if a later operation would
|
||||
shrink the result. Invalid operations invalidate the submission; partial
|
||||
results are never published.
|
||||
|
||||
The host counts the encoded sizes of changed values rather than encoding the
|
||||
complete accumulated result on every event. At `done: true`, it materializes the
|
||||
ordinary JSON result and verifies its actual encoding against the recorded
|
||||
size before passing it as `response.body` to `parseSubmitResponse`. The result
|
||||
then follows the same persistence, usage validation and settlement path as the
|
||||
snapshot mode. State and builders belong to one request and are released on
|
||||
completion, cancellation or error; no JS slot is held during upstream I/O.
|
||||
|
||||
The plugin still interprets vendor payloads, decides which fields to update,
|
||||
distinguishes cumulative usage from increments, and decides completion. Alibaba
|
||||
uses this mode for interleaved image generation, keeping only content indices,
|
||||
text adjacency and finish reasons in its control state.
|
||||
Reference in new issue
Block a user