Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources. Confidence: high Scope-risk: narrow Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history. Tested: Marketplace validation passed.
This commit is contained in:
1 parent
049ed5ace3
commit
cdebff3acd
23 files changed
+570
-60
No files matched your search
@@ -49,46 +49,46 @@
|
||||
"id": "generate-agents-md",
|
||||
"pluginName": "generate-agents-md",
|
||||
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
|
||||
"ref": "v1.0.0",
|
||||
"ref": "v1.1.0",
|
||||
"adapter": "codex-plugin",
|
||||
"commit": "e869eb169eeb5a7cd9e58c0e514fa2dbbf50987d",
|
||||
"syncedAt": "2026-10-06T07:26:41Z"
|
||||
"commit": "4e572a4377f3164a2d4b7bd62876a63a86494551",
|
||||
"syncedAt": "2026-10-15T03:03:03Z"
|
||||
},
|
||||
{
|
||||
"id": "design-md",
|
||||
"pluginName": "design-md",
|
||||
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
|
||||
"ref": "v1.0.0",
|
||||
"ref": "v1.1.0",
|
||||
"adapter": "codex-plugin",
|
||||
"commit": "e869eb169eeb5a7cd9e58c0e514fa2dbbf50987d",
|
||||
"syncedAt": "2026-10-06T07:26:41Z"
|
||||
"commit": "4e572a4377f3164a2d4b7bd62876a63a86494551",
|
||||
"syncedAt": "2026-10-15T03:03:03Z"
|
||||
},
|
||||
{
|
||||
"id": "eapil-llmapi-structured",
|
||||
"pluginName": "eapil-llmapi-structured",
|
||||
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
|
||||
"ref": "v1.0.0",
|
||||
"ref": "v1.1.0",
|
||||
"adapter": "codex-plugin",
|
||||
"commit": "e869eb169eeb5a7cd9e58c0e514fa2dbbf50987d",
|
||||
"syncedAt": "2026-10-06T07:26:41Z"
|
||||
"commit": "4e572a4377f3164a2d4b7bd62876a63a86494551",
|
||||
"syncedAt": "2026-10-15T03:03:03Z"
|
||||
},
|
||||
{
|
||||
"id": "eapil-gpt2-image",
|
||||
"pluginName": "eapil-gpt2-image",
|
||||
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
|
||||
"ref": "v1.0.0",
|
||||
"ref": "v1.1.0",
|
||||
"adapter": "codex-plugin",
|
||||
"commit": "e869eb169eeb5a7cd9e58c0e514fa2dbbf50987d",
|
||||
"syncedAt": "2026-10-06T07:26:41Z"
|
||||
"commit": "4e572a4377f3164a2d4b7bd62876a63a86494551",
|
||||
"syncedAt": "2026-10-15T03:03:03Z"
|
||||
},
|
||||
{
|
||||
"id": "eapil-voice",
|
||||
"pluginName": "eapil-voice",
|
||||
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
|
||||
"ref": "v1.0.0",
|
||||
"ref": "v1.1.0",
|
||||
"adapter": "codex-plugin",
|
||||
"commit": "e869eb169eeb5a7cd9e58c0e514fa2dbbf50987d",
|
||||
"syncedAt": "2026-10-06T07:26:41Z"
|
||||
"commit": "4e572a4377f3164a2d4b7bd62876a63a86494551",
|
||||
"syncedAt": "2026-10-15T03:03:03Z"
|
||||
},
|
||||
{
|
||||
"id": "dingtalk-workspace",
|
||||
@@ -105,8 +105,8 @@
|
||||
"repo": "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill.git",
|
||||
"ref": "main",
|
||||
"adapter": "claude-skill",
|
||||
"commit": "7246805992a42b7bba37d4cac83b01416f0c6c44",
|
||||
"syncedAt": "2026-10-12T15:59:59Z"
|
||||
"commit": "56ea9cf174ed86786187daa9de0e6247cb0747e4",
|
||||
"syncedAt": "2026-10-15T03:03:03Z"
|
||||
},
|
||||
{
|
||||
"id": "caveman",
|
||||
@@ -258,8 +258,8 @@
|
||||
"repo": "https://github.com/vercel/next.js.git",
|
||||
"ref": "canary",
|
||||
"adapter": "skill-collection",
|
||||
"commit": "2df613dbe00b82a6a684e570bc77e242825182ab",
|
||||
"syncedAt": "2026-10-14T16:00:00Z"
|
||||
"commit": "0b3693ddd5fca101fe6d286849a980ddc1bf0650",
|
||||
"syncedAt": "2026-10-15T03:03:03Z"
|
||||
},
|
||||
{
|
||||
"id": "shuorenhua",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "design-md",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "创建或完善 DESIGN.md,维护项目设计读取入口,并应用、核对界面设计规则。",
|
||||
"author": {
|
||||
"name": "EAPIL",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"sourceId": "design-md",
|
||||
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
|
||||
"ref": "v1.0.0",
|
||||
"commit": "e869eb169eeb5a7cd9e58c0e514fa2dbbf50987d",
|
||||
"ref": "v1.1.0",
|
||||
"commit": "4e572a4377f3164a2d4b7bd62876a63a86494551",
|
||||
"adapter": "codex-plugin",
|
||||
"sourcePath": "plugins/design-md",
|
||||
"syncedAt": "2026-10-06T07:26:41Z"
|
||||
"syncedAt": "2026-10-15T03:03:03Z"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eapil-gpt2-image",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "通过 EAPIL 图像 API 生成、编辑图像或完善提示词,保留精确文字、参考图角色和用户设计意图。",
|
||||
"author": {
|
||||
"name": "EAPIL",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"sourceId": "eapil-gpt2-image",
|
||||
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
|
||||
"ref": "v1.0.0",
|
||||
"commit": "e869eb169eeb5a7cd9e58c0e514fa2dbbf50987d",
|
||||
"ref": "v1.1.0",
|
||||
"commit": "4e572a4377f3164a2d4b7bd62876a63a86494551",
|
||||
"adapter": "codex-plugin",
|
||||
"sourcePath": "plugins/eapil-gpt2-image",
|
||||
"syncedAt": "2026-10-06T07:26:41Z"
|
||||
"syncedAt": "2026-10-15T03:03:03Z"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eapil-llmapi-structured",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "易瞳 LLM API 通用调用指南:选择、构建和实测 Chat、Responses、结构化输出、工具、视觉、流式、Embedding、Rerank 与 Decisions 请求,核对模型和中转渠道的实际兼容性。",
|
||||
"author": {
|
||||
"name": "EAPIL",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"sourceId": "eapil-llmapi-structured",
|
||||
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
|
||||
"ref": "v1.0.0",
|
||||
"commit": "e869eb169eeb5a7cd9e58c0e514fa2dbbf50987d",
|
||||
"ref": "v1.1.0",
|
||||
"commit": "4e572a4377f3164a2d4b7bd62876a63a86494551",
|
||||
"adapter": "codex-plugin",
|
||||
"sourcePath": "plugins/eapil-llmapi-structured",
|
||||
"syncedAt": "2026-10-06T07:26:41Z"
|
||||
"syncedAt": "2026-10-15T03:03:03Z"
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eapil-voice",
|
||||
"version": "1.0.0",
|
||||
"description": "易瞳音频服务调用指南:语音设计、朗读、克隆、ASR、MossFormer2/RE-USE 降噪、Stable Audio 音乐与音效、Yue2 歌曲及批量导出。包含请求校验、错误处理和实测记录。",
|
||||
"version": "1.1.0",
|
||||
"description": "易瞳音频服务调用指南:语音设计、朗读、克隆、ASR、MossFormer2/RE-USE 降噪、Stable Audio 音乐与音效、ACE-Step 1.5 Turbo 纯音乐/歌曲、Yue2 歌曲及批量导出。包含请求校验、错误处理和验证记录。",
|
||||
"author": {
|
||||
"name": "EAPIL",
|
||||
"url": "https://git.playones.com/arechen/EapilSkillMarket"
|
||||
@@ -18,7 +18,7 @@
|
||||
"interface": {
|
||||
"displayName": "易瞳音频:语音 / 转录 / 降噪 / 音乐 / 音效",
|
||||
"shortDescription": "语音、ASR、降噪、音乐、音效与歌曲的调用及验证指南。",
|
||||
"longDescription": "按需选择 Qwen/VoxCPM2 语音、转录、两类降噪、Stable Audio 和 Yue2 接口。配套单请求客户端与顺序批处理,校验 WAV/JSON 响应、使用准确参考转写,并明确超时不可盲目重发及部署限制。保留 eapil-voice 插件 ID。",
|
||||
"longDescription": "按需选择 Qwen/VoxCPM2 语音、转录、两类降噪、Stable Audio、ACE-Step 1.5 Turbo 和 Yue2 接口。ACE-Step 支持显式纯音乐/歌词模式、语义 LM、调性拍号及实际种子读取。配套单请求客户端与顺序语音批处理,校验 WAV/JSON 响应,并明确超时不可盲目重发及部署限制。保留 eapil-voice 插件 ID。",
|
||||
"developerName": "EAPIL",
|
||||
"category": "Creativity",
|
||||
"capabilities": [
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"sourceId": "eapil-voice",
|
||||
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
|
||||
"ref": "v1.0.0",
|
||||
"commit": "e869eb169eeb5a7cd9e58c0e514fa2dbbf50987d",
|
||||
"ref": "v1.1.0",
|
||||
"commit": "4e572a4377f3164a2d4b7bd62876a63a86494551",
|
||||
"adapter": "codex-plugin",
|
||||
"sourcePath": "plugins/eapil-voice",
|
||||
"syncedAt": "2026-10-06T07:26:41Z"
|
||||
"syncedAt": "2026-10-15T03:03:03Z"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: eapil-voice
|
||||
description: Build, test or use EAPIL audio API calls for speech design and cloning, transcription, denoising, music, sound effects and lyric-conditioned songs; includes sequential batch speech export.
|
||||
description: Build, test or use EAPIL audio API calls for speech, transcription, denoising, music, sound effects and ACE-Step/Yue2 songs; includes sequential batch speech export.
|
||||
---
|
||||
|
||||
# EAPIL audio service
|
||||
@@ -16,6 +16,7 @@ Use the hosted service at `https://ades.playones.com`, or the deployment chosen
|
||||
| MossFormer2 enhancement / alias | `/v1/audio/enhance`, `/v1/audio/denoise` | [ASR and enhancement](references/recognition-enhancement.md) |
|
||||
| RE-USE denoising | `/v1/audio/reuse` | [ASR and enhancement](references/recognition-enhancement.md) |
|
||||
| Instrumental music or sound effects | `/v1/audio/music`, `/v1/audio/sfx` | [Stable Audio](references/music.md) |
|
||||
| ACE-Step 1.5 Turbo instrumentals or lyric songs | `/v1/audio/music/acestep` | [ACE-Step](references/acestep.md) |
|
||||
| Songs from lyrics or ABC arrangement | `/v1/audio/music/yue2` | [Yue2](references/yue2.md) |
|
||||
| Directory-scale ASR/clone/export | Sequential calls | [Batch audio](references/batch-audio.md) |
|
||||
|
||||
@@ -25,9 +26,9 @@ Read [transport and errors](references/api.md) for client integration or trouble
|
||||
|
||||
Use `AUDIO_API_BASE` and `AUDIO_API_TOKEN`; old `QWEN3_VOICE_API_BASE_URL` and `OPENAI_API_KEY` remain fallback aliases. Explicit flags take precedence. An available legacy key is not proof of authorization on this deployment. Inference requires Bearer authentication; health/models are public. Obtain missing credentials from the provider without putting them in chat or tracked files.
|
||||
|
||||
Speech design/synthesis, music/SFX and Yue2 take JSON. Clone, ASR and enhancement use multipart with file field `file`; let the client create the boundary. Audio success is complete WAV bytes, not JSON/Base64/a URL or inference streaming. ASR returns JSON. Preserve explicit false/0; keep provider and endpoint controls separate.
|
||||
Speech design/synthesis, music/SFX, ACE-Step and Yue2 take JSON. Clone, ASR and enhancement use multipart with file field `file`; let the client create the boundary. Audio success is complete WAV bytes, not JSON/Base64/a URL or inference streaming. ASR returns JSON. Preserve explicit false/0; keep provider and endpoint controls separate. ACE-Step uses this service's bounded protocol, not the upstream API; choose `acestep` explicitly without rerouting existing `music` or `yue2` calls.
|
||||
|
||||
[call_voice_api.py](scripts/call_voice_api.py) makes one validated request. Existing command names remain; new commands are reuse, music, sfx, yue2. Dry run prints the payload without a key/network call, so share only synthetic inputs. Resolve the actual skill directory:
|
||||
[call_voice_api.py](scripts/call_voice_api.py) makes one validated request; `acestep` adds Turbo music/song generation to the existing commands. Dry run prints the payload without a key/network call, so share only synthetic inputs. Resolve the actual skill directory:
|
||||
|
||||
~~~powershell
|
||||
python '<skill-dir>/scripts/call_voice_api.py' design --text '你好。' --instruct '温暖自然、清晰的女声。' --language Chinese --max-tokens 512 --out voice.wav
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# ACE-Step 1.5 Turbo music and songs
|
||||
|
||||
Source: maintainer's **2026-10-15** unified API guide; public [models](https://ades.playones.com/models) and [OpenAPI](https://ades.playones.com/openapi.json) inspected the same day. This is the hosted service's protocol, **not upstream ACE-Step API**.
|
||||
|
||||
`POST /v1/audio/music/acestep`, JSON. Model `acestep-v15-turbo`, optional semantic LM `acestep-5Hz-lm-0.6B`. Reuse the existing service Bearer token or an enabled KeyInfo member key via `AUDIO_API_TOKEN`; no separate ACE-Step key. Never embed a real key in the skill or a public frontend.
|
||||
|
||||
Choose ACE-Step explicitly for Turbo instrumentals or supplied-lyric songs. Existing `music`/`sfx` still call Stable Audio; `yue2` still supports its own lyric/ABC workflow. This ACE-Step route has no reference audio, covers, inpainting, continuation, stems or multi-output batch mode.
|
||||
|
||||
## Fields and mode checks
|
||||
|
||||
Only `prompt` is required. Unknown fields return 422. Character limits count Unicode characters, not UTF-8 bytes.
|
||||
|
||||
| Field | Default | Allowed / meaning |
|
||||
|---|---|---|
|
||||
| prompt | required | Nonblank 1–512 characters; describe 2–5 core traits such as genre, instruments, mood and arrangement. Put sung words in lyrics |
|
||||
| lyrics | `""` | String <=4096 characters; omitted/empty is valid for instrumentals, **null is invalid** |
|
||||
| instrumental | true | Boolean; set **false explicitly** for a lyric song |
|
||||
| seconds | 30 | Number **10–600**; deployment may lower the upper bound, see `models.acestep.max_seconds` |
|
||||
| steps | 8 | Integer **1–20**; start at 8, try 4 for drafts. Do not copy non-Turbo 30/50-step settings |
|
||||
| seed | null/random | Integer **0–4294967295** or null; actual value in response `X-Audio-Seed` |
|
||||
| use_lm | true | Boolean; optional 0.6B planning. False usually lowers latency but changes results; the text encoder still runs |
|
||||
| vocal_language | unknown | Exact code below; zh Mandarin, yue Cantonese, en English. Not auto/zh-CN; does not translate lyrics |
|
||||
| bpm | null | Integer **30–300** or null; rhythmic condition, not a guarantee of measured beat accuracy |
|
||||
| keyscale | `""` | <=16 characters; empty or A–G + optional #/b/♯/♭ + space + Major/Minor, e.g. `C Major`, `F# minor`, `Bb Major`; no `Am` shorthand |
|
||||
| timesignature | `""` | String `""`, `"2"`, `"3"`, `"4"`, `"6"`; correspond to unspecified, 2/4, 3/4, 4/4, 6/8. Not `"4/4"` |
|
||||
|
||||
`instrumental=true` allows only omitted/empty/whitespace lyrics or `[Instrumental]`. `instrumental=false` requires nonblank lyrics other than that marker. Conflicts return 422 instead of silently discarding lyrics. Instrumental conditioning does not guarantee the absence of voice-like sounds.
|
||||
|
||||
Language codes:
|
||||
|
||||
```text
|
||||
ar az bg bn ca cs da de el en es fa fi fr he hi hr ht hu id
|
||||
is it ja ko la lt ms ne nl no pa pl pt ro ru sa sk sr sv sw
|
||||
ta te th tl tr uk ur vi yue zh unknown
|
||||
```
|
||||
|
||||
Turbo guidance is fixed at 1.0: **do not send cfg, cfg_scale or guidance_scale**. Internal shift=3.0, batch_size=1 and ODE are not request controls. LM does not write/rewrite lyrics or prompt, and does not fill unspecified BPM/key/meter via CoT. More steps do not guarantee better quality.
|
||||
|
||||
## CLI and payload examples
|
||||
|
||||
Use existing environment credentials. In this repository prefix external commands with `rtk proxy`; replace `<skill-dir>` with the actual skill path.
|
||||
|
||||
~~~powershell
|
||||
python '<skill-dir>/scripts/call_voice_api.py' acestep --prompt 'Warm piano, bright acoustic guitar, steady drums, upbeat indie pop. Instrumental, no vocals.' --instrumental true --seconds 30 --steps 8 --seed 7 --out music.wav
|
||||
python '<skill-dir>/scripts/call_voice_api.py' acestep --prompt 'Mandarin acoustic pop, clear female vocal, warm piano, gentle drums.' --instrumental false --lyrics-file lyrics.txt --vocal-language zh --seconds 30 --steps 8 --use-lm true --bpm 120 --keyscale 'C Major' --timesignature 4 --seed 7 --out song.wav
|
||||
~~~
|
||||
|
||||
`--lyrics` and UTF-8/BOM `--lyrics-file` are alternatives; add `--dry-run` to inspect a request without credentials or inference. The helper defaults to a 900-second timeout and prints the actual response seed when a valid `X-Audio-Seed` header is available; it does not substitute the requested seed for a missing header.
|
||||
|
||||
Equivalent song JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"prompt": "Mandarin acoustic pop, clear female vocal, warm piano, gentle drums.",
|
||||
"instrumental": false,
|
||||
"lyrics": "[Verse]\n风吹过安静的街道\n你的笑还留在转角\n[Chorus]\n我们向着晨光奔跑\n让每个明天都闪耀",
|
||||
"vocal_language": "zh",
|
||||
"seconds": 30,
|
||||
"steps": 8,
|
||||
"use_lm": true,
|
||||
"seed": 7
|
||||
}
|
||||
```
|
||||
|
||||
Use a JSON serializer for real multiline strings; do not double-escape `\n`. `[Verse]`/`[Chorus]` guide structure but are not strict score instructions. 4096 input characters do not fit automatically in 30 seconds: start with a short verse/chorus, match duration to lyrics, then listen for omissions, truncation, repetition and word accuracy. Neither lyrics nor seed ensure exact performance across hardware/versions.
|
||||
|
||||
## Output, availability and failures
|
||||
|
||||
Success is complete **48 kHz stereo PCM16 WAV** with `Content-Disposition: attachment; filename=acestep.wav` and `X-Audio-Seed`. Use bytes/arrayBuffer/Blob after checking HTTP status/media; no JSON/Base64/download URL. The generated OpenAPI 200 entry currently says application/json; follow the supplied binary contract and check the actual response. Approximate PCM data: 30 s = 5.76 MB, 600 s = 115.2 MB; account for client/proxy response size and memory. CORS/proxies must expose `X-Audio-Seed` for browser access.
|
||||
|
||||
One active ACE-Step task per API process, including first load; busy rejects immediately with **503 + Retry-After: 2**, without queueing. Missing installation, disabled autostart and worker connection failures can also be 503; retry only explicit busy rejection with a finite bound. The helper never retries.
|
||||
|
||||
This is synchronous: output starts after generation, with no streaming playback, job ID, progress or cancellation. First use includes weight verification/loading/compilation; first LM use loads the LM too. Client disconnect/timeout or proxy HTML 504 may leave inference running. The guide distinguishes internal JSON 504, which terminates the worker, from proxy timeout; diagnose before manually resubmitting either.
|
||||
|
||||
401 needs credential correction; 422 needs schema/mode/deployment-limit correction; 500 indicates failed/invalid generation and worker cleanup. Startup and inference have separate timeouts (currently 120 + 600 seconds); a client timeout of 900 does not extend a gateway timeout.
|
||||
|
||||
Public `/models` reports `models.acestep` installed/state/loaded/lm_loaded/max_seconds; `/health` and `/models` do not load or prewarm models. `installed=true` is not proof that weight validation or inference succeeds; stopped/loaded=false can be normal lazy startup. Hosted callers need no local runtime/extra port. Service deployment/worker changes belong to the operator, not this skill's request workflow.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Transport and deployed contract
|
||||
|
||||
Source: maintainer's API usage guide dated 2026-10-06 and live [OpenAPI](https://ades.playones.com/openapi.json). Schema limits, deployment caps and model recommendations differ; deployment settings can change.
|
||||
Source: maintainer's API usage guide dated 2026-10-06, ACE-Step addition dated 2026-10-15 and live [OpenAPI](https://ades.playones.com/openapi.json). Schema limits, deployment caps and model recommendations differ; deployment settings can change.
|
||||
|
||||
## Requests and credentials
|
||||
|
||||
@@ -14,18 +14,20 @@ Size limit: 26,214,400 bytes (25 MiB). Qwen clone, ASR, MossFormer2 and RE-USE e
|
||||
|
||||
Audio success is complete binary audio/wav. The helper checks media type and complete RIFF format/data chunks before saving. It handles PCM/IEEE float WAV; RF64/compressed WAV needs another validated decoder. ASR must have string text; an empty transcription can represent silence, but cannot drive a batch clone.
|
||||
|
||||
Read actual rate/channels. Music/SFX currently use 44.1 kHz stereo PCM16; Yue2 48 kHz stereo PCM16. Provider speech rates vary. RE-USE encodes the input rate; enhancement/RE-USE mix channels to mono and are not stereo mastering tools.
|
||||
Read actual rate/channels. Stable Audio music/SFX currently use 44.1 kHz stereo PCM16; Yue2 and ACE-Step 48 kHz stereo PCM16. ACE-Step returns the actual seed in X-Audio-Seed; the CLI includes it in success JSON when available. Provider speech rates vary. RE-USE encodes the input rate; enhancement/RE-USE mix channels to mono and are not stereo mastering tools.
|
||||
|
||||
Server-side JavaScript reads response.arrayBuffer() for audio and response.json() for ASR, once per response body. Current deployment lacks general browser CORS; use a backend proxy or configured CORS. Keep service tokens on that backend.
|
||||
|
||||
## Timeouts and concurrency
|
||||
|
||||
Speech/ASR/enhancement timeout starts at 600 seconds; music/SFX/Yue2 at 1800. Client timeouts cannot extend a gateway timeout. HTTP chunked transport is not progressive generation.
|
||||
Speech/ASR/enhancement client timeout starts at 600 seconds; music/SFX/Yue2 at 1800; ACE-Step at 900 to cover its separate startup/generation budgets plus transport. Client timeouts cannot extend a gateway timeout. HTTP chunked transport is not progressive generation.
|
||||
|
||||
There is no async job, task query, cancellation endpoint or idempotency key. Timeout/504 does not establish that inference stopped; investigate before repeating. Do not invent a status endpoint.
|
||||
|
||||
Qwen design can reject busy with 503 and Retry-After. Stable Audio music/SFX share a queue. Qwen synthesis/clone share serialized execution; ASR, VoxCPM2 and Yue2 are separately serialized; MossFormer2/RE-USE share execution. Loading/connection failures can also be 503: status alone is insufficient.
|
||||
|
||||
ACE-Step allows one active task per API process, including first load; busy rejects immediately with 503/Retry-After: 2 instead of queueing. Read [ACE-Step](acestep.md) for its mode constraints, response-size budget and JSON-versus-HTML 504 distinction.
|
||||
|
||||
The single-call helper never retries. Batch --retries defaults to 1 total attempt, capped at 5. Additional attempts only follow explicit busy/running 503 detail without load/error/timeout indicators. It honors numeric Retry-After up to 60 seconds; longer backoff is left to the application, and fallback delays are bounded. Upload bodies are rebuilt in full. Ambiguous errors are failures, not retry signals.
|
||||
|
||||
## Errors and state
|
||||
@@ -34,4 +36,4 @@ Failure detail can be a string/array or gateway HTML. Check status/media before
|
||||
|
||||
Library callers can inspect APIError.status/detail/busy. CLI logs omit raw provider detail because it can echo private inputs; inspect locally and redact before sharing.
|
||||
|
||||
Health ok means API responsiveness. Models keys include voice_design_tts, base_tts, asr, speech_enhancement, reuse, stable_audio_3, yue2, voxcpm2. Loaded=false or Yue2 stopped can be normal lazy loading. Hosted API use does not require local model startup.
|
||||
Health ok means API responsiveness. Models keys include voice_design_tts, base_tts, asr, speech_enhancement, reuse, stable_audio_3, yue2, voxcpm2, acestep. Loaded=false or a stopped lazy worker can be normal. Hosted API use does not require local model startup.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Stable Audio music and sound effects
|
||||
|
||||
JSON music uses Medium + SAME-L; sfx uses Small-SFX + SAME-S. Text-to-audio only: no dedicated lyrics, reference audio, inpainting or continuation. Use Yue2 for explicit lyrics. MiniMax was removed.
|
||||
JSON music uses Medium + SAME-L; sfx uses Small-SFX + SAME-S. Text-to-audio only: no dedicated lyrics, reference audio, inpainting or continuation. Use [ACE-Step](acestep.md) or [Yue2](yue2.md) for explicit lyrics. MiniMax was removed. This route remains Stable Audio; ACE-Step has a separate command and schema.
|
||||
|
||||
| Field | Default | Schema / effective meaning |
|
||||
|---|---|---|
|
||||
|
||||
@@ -48,3 +48,15 @@ Skill frontmatter/UI metadata, local documentation links, root AGENTS.md and all
|
||||
- Denoise alias was checked through the client/offline routing; live enhancement used the primary enhance endpoint.
|
||||
- Deployment caps, maximum-duration behavior, denied denoise=true, invalid-token behavior, real busy/503 handling, raw-binary uploads and browser CORS were not load-tested. Their guidance comes from the supplied contract/public schema; retry/error behavior was simulated offline.
|
||||
- No broad concurrency tests, timeout resubmission, API/service reconfiguration, commit, push, tag or market publication occurred.
|
||||
|
||||
## ACE-Step addition — 2026-10-15
|
||||
|
||||
Basis: maintainer's **ACE-Step 1.5 Turbo 调用指南**, dated 2026-10-15, plus public GET inspection of hosted `/models` and `/openapi.json`. These reads did not authenticate, load models or submit inference.
|
||||
|
||||
- Hosted OpenAPI lists `POST /v1/audio/music/acestep` and `AceStepGenerationRequest`: 11 allowed fields, required prompt, 512/4096-character prompt/lyrics limits, 10–600 seconds, 1–20 steps, unsigned 32-bit seed, language/meter enums and no additional properties. Lyric-mode/keyscale checks also follow the supplied guide; they are not fully expressed in OpenAPI.
|
||||
- `/models` reported `acestep-v15-turbo`, optional `acestep-5Hz-lm-0.6B`, installed=true, stopped, loaded/lm_loaded=false, autostart=true, max_seconds=600, max_steps=20, startup timeout=120 and generation timeout=600. Stopped is consistent with lazy startup, not a successful-inference result.
|
||||
- The generated OpenAPI 200 response lists application/json; the supplied guide specifies PCM16 stereo 48 kHz WAV and X-Audio-Seed. The client checks actual WAV media/structure and reads the actual seed header rather than interpreting successful audio as JSON or echoing the requested seed.
|
||||
|
||||
**30 offline regression tests passed** (22 existing + 8 ACE-Step tests). Coverage added: independent endpoint/defaults without changing Stable Audio/Yue2, instrumental/lyric conflicts, Unicode character bounds, numeric types/ranges/nullability, unknown-field rejection, musical controls, UTF-8/BOM multiline lyric files, false/zero wire values, full uint32 seeds, response seed handling, valid WAV saving, and 401/422/503/500/JSON-or-HTML-504 failures without retry or overwriting existing output. HTTP was mocked; no real service token is in the fixtures. Frontmatter, changed-script Ruff correctness/import checks, local reference links and all five plugin manifests/resources/versions were also checked.
|
||||
|
||||
The documented instrumental request was executed through the actual CLI in dry-run mode; the song/lyric-file path was exercised by the regression suite. **No ACE-Step inference or listening test was run in this update.** The guide's four local HTTP benchmarks on Apple M3 Ultra are maintainer-reported evidence, not a reproduced hosted-service test, latency SLA or quality guarantee. No deployment configuration, installed plugin cache or 1.0.0 release/tag was changed.
|
||||
@@ -23,4 +23,4 @@ python '<skill-dir>/scripts/call_voice_api.py' yue2 --style 'Mandopop ballad, fe
|
||||
python '<skill-dir>/scripts/call_voice_api.py' yue2 --style 'Acoustic folk, intimate vocal' --lyrics-file lyrics.txt --mode melody --abc-file melody.abc --max-duration-seconds 30 --out arrangement.wav
|
||||
~~~
|
||||
|
||||
Lyrics must not be blank/absent. [Instrumental] is experimental, not a guarantee; prefer Stable Audio for pure instrumentals. Current output is 48 kHz stereo PCM16. Lazy startup is handled by the service. Verify lyrics/music by listening; one direct probe does not establish melody/full/ABC.
|
||||
Lyrics must not be blank/absent. [Instrumental] is experimental, not a guarantee; use Stable Audio or [ACE-Step's explicit instrumental mode](acestep.md) for pure instrumentals. ACE-Step also supports lyric songs, with a separate bounded schema and no ABC input. Current Yue2 output is 48 kHz stereo PCM16. Lazy startup is handled by the service. Verify lyrics/music by listening; one direct probe does not establish melody/full/ABC.
|
||||
@@ -2,11 +2,13 @@
|
||||
"""Dependency-free EAPIL audio client. One request, no inference retries."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
import uuid
|
||||
@@ -31,9 +33,15 @@ PATHS = {
|
||||
"music": "/v1/audio/music",
|
||||
"sfx": "/v1/audio/sfx",
|
||||
"yue2": "/v1/audio/music/yue2",
|
||||
"acestep": "/v1/audio/music/acestep",
|
||||
}
|
||||
QWEN = ("language", "temperature", "max_tokens", "top_k", "top_p", "repetition_penalty")
|
||||
VOX = ("cfg_value", "inference_timesteps", "normalize", "denoise", "retry_badcase")
|
||||
ACESTEP_LANGUAGES = (
|
||||
"ar az bg bn ca cs da de el en es fa fi fr he hi hr ht hu id "
|
||||
"is it ja ko la lt ms ne nl no pa pl pt ro ru sa sk sr sv sw "
|
||||
"ta te th tl tr uk ur vi yue zh unknown"
|
||||
).split()
|
||||
|
||||
|
||||
class APIError(RuntimeError):
|
||||
@@ -210,6 +218,7 @@ def http_request(
|
||||
headers=None,
|
||||
timeout=600,
|
||||
expected="json",
|
||||
response_metadata=None,
|
||||
):
|
||||
if not math.isfinite(timeout) or timeout <= 0:
|
||||
raise ValueError("timeout must be positive and finite")
|
||||
@@ -227,6 +236,15 @@ def http_request(
|
||||
.strip()
|
||||
)
|
||||
data = response.read()
|
||||
if response_metadata is not None and path == PATHS["acestep"]:
|
||||
seed = response.headers.get("X-Audio-Seed", "").strip()
|
||||
if (
|
||||
seed.isascii()
|
||||
and seed.isdecimal()
|
||||
and len(seed) <= 10
|
||||
and int(seed) <= 4294967295
|
||||
):
|
||||
response_metadata["seed"] = int(seed)
|
||||
except HTTPError as exc:
|
||||
raw = exc.read().decode("utf-8", "replace")
|
||||
try:
|
||||
@@ -258,7 +276,7 @@ def http_request(
|
||||
return data
|
||||
|
||||
|
||||
def post_json(args, path, payload):
|
||||
def post_json(args, path, payload, *, response_metadata=None):
|
||||
return http_request(
|
||||
method="POST",
|
||||
base_url=args.base_url,
|
||||
@@ -268,6 +286,7 @@ def post_json(args, path, payload):
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=args.timeout,
|
||||
expected="wav",
|
||||
response_metadata=response_metadata,
|
||||
)
|
||||
|
||||
|
||||
@@ -294,7 +313,74 @@ def write_output(path, data):
|
||||
target.write_bytes(data)
|
||||
|
||||
|
||||
def validate_acestep_payload(payload):
|
||||
allowed = {
|
||||
"prompt",
|
||||
"lyrics",
|
||||
"instrumental",
|
||||
"seconds",
|
||||
"steps",
|
||||
"seed",
|
||||
"use_lm",
|
||||
"vocal_language",
|
||||
"bpm",
|
||||
"keyscale",
|
||||
"timesignature",
|
||||
}
|
||||
if payload.keys() - allowed:
|
||||
raise ValueError("unknown ACE-Step fields; use its dedicated request schema")
|
||||
if not isinstance(payload.get("prompt"), str) or not payload["prompt"].strip():
|
||||
raise ValueError("prompt must be nonblank text")
|
||||
for key, limit in (("prompt", 512), ("lyrics", 4096), ("keyscale", 16)):
|
||||
value = payload.get(key, "")
|
||||
if not isinstance(value, str) or len(value) > limit:
|
||||
raise ValueError(f"{key} must be text of at most {limit} characters")
|
||||
for key in ("instrumental", "use_lm"):
|
||||
if not isinstance(payload.get(key, True), bool):
|
||||
raise ValueError(f"{key} must be a boolean")
|
||||
for key, low, high, integer, nullable in (
|
||||
("seconds", 10, 600, False, False),
|
||||
("steps", 1, 20, True, False),
|
||||
("seed", 0, 4294967295, True, True),
|
||||
("bpm", 30, 300, True, True),
|
||||
):
|
||||
if key not in payload or payload[key] is None and nullable:
|
||||
continue
|
||||
value = payload[key]
|
||||
if (
|
||||
isinstance(value, bool)
|
||||
or not isinstance(value, int if integer else (int, float))
|
||||
or not low <= value <= high
|
||||
or not math.isfinite(value)
|
||||
):
|
||||
raise ValueError(
|
||||
f"{key} must be {'an integer' if integer else 'a number'} between {low} and {high}"
|
||||
)
|
||||
language = payload.get("vocal_language", "unknown")
|
||||
if not isinstance(language, str) or language not in ACESTEP_LANGUAGES:
|
||||
raise ValueError("vocal_language must be an ACE-Step language code")
|
||||
signature = payload.get("timesignature", "")
|
||||
if not isinstance(signature, str) or signature not in {"", "2", "3", "4", "6"}:
|
||||
raise ValueError('timesignature must be "", "2", "3", "4" or "6"')
|
||||
keyscale = payload.get("keyscale", "")
|
||||
if keyscale and not re.fullmatch(r"[A-G][#b♯♭]? (?i:major|minor)", keyscale):
|
||||
raise ValueError(
|
||||
'keyscale must be empty or e.g. "C Major", "F# minor", "Bb Major"'
|
||||
)
|
||||
lyrics = payload.get("lyrics", "").strip()
|
||||
if payload.get("instrumental", True):
|
||||
if lyrics and lyrics != "[Instrumental]":
|
||||
raise ValueError(
|
||||
"instrumental=true allows only empty lyrics or [Instrumental]"
|
||||
)
|
||||
elif not lyrics or lyrics == "[Instrumental]":
|
||||
raise ValueError("instrumental=false requires nonblank song lyrics")
|
||||
|
||||
|
||||
def validate_payload(command, payload):
|
||||
if command == "acestep":
|
||||
validate_acestep_payload(payload)
|
||||
return
|
||||
required = {
|
||||
"design": ("text", "instruct"),
|
||||
"synthesize": ("text",),
|
||||
@@ -388,7 +474,11 @@ def build_parser():
|
||||
sub.add_argument(
|
||||
"--timeout",
|
||||
type=float,
|
||||
default=1800 if command in {"music", "sfx", "yue2"} else 600,
|
||||
default=900
|
||||
if command == "acestep"
|
||||
else 1800
|
||||
if command in {"music", "sfx", "yue2"}
|
||||
else 600,
|
||||
)
|
||||
sub.add_argument("--dry-run", action="store_true")
|
||||
if command in {"health", "models"}:
|
||||
@@ -460,6 +550,24 @@ def build_parser():
|
||||
sub.add_argument("--cfg-scale", type=float, default=1.0)
|
||||
sub.add_argument("--min-duration-seconds", type=int, default=8)
|
||||
sub.add_argument("--max-duration-seconds", type=int, default=120)
|
||||
if command == "acestep":
|
||||
sub.add_argument("--prompt", required=True)
|
||||
lyrics = sub.add_mutually_exclusive_group()
|
||||
lyrics.add_argument("--lyrics")
|
||||
lyrics.add_argument("--lyrics-file")
|
||||
sub.add_argument("--instrumental", type=bool_value, default=True)
|
||||
sub.add_argument("--seconds", type=float, default=30)
|
||||
sub.add_argument("--steps", type=int, default=8)
|
||||
sub.add_argument("--seed", type=int)
|
||||
sub.add_argument("--use-lm", type=bool_value, default=True)
|
||||
sub.add_argument(
|
||||
"--vocal-language", choices=ACESTEP_LANGUAGES, default="unknown"
|
||||
)
|
||||
sub.add_argument("--bpm", type=int)
|
||||
sub.add_argument("--keyscale", default="")
|
||||
sub.add_argument(
|
||||
"--timesignature", choices=["", "2", "3", "4", "6"], default=""
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
@@ -484,6 +592,8 @@ def payload_from_args(args):
|
||||
if key in payload:
|
||||
raise ValueError(f"supply {key} text or file, not both")
|
||||
payload[key] = Path(path).read_text(encoding="utf-8-sig")
|
||||
if args.command == "acestep":
|
||||
payload.setdefault("lyrics", "")
|
||||
validate_payload(args.command, payload)
|
||||
return payload
|
||||
|
||||
@@ -519,6 +629,7 @@ def main(argv=None):
|
||||
args.token = args.token or (
|
||||
os.getenv(args.api_key_env) if args.api_key_env else env_token()
|
||||
)
|
||||
response_metadata = {}
|
||||
if args.command in {"health", "models"}:
|
||||
data = http_request(
|
||||
method="GET",
|
||||
@@ -529,7 +640,9 @@ def main(argv=None):
|
||||
elif file:
|
||||
data = post_multipart(args, PATHS[args.command], payload, file)
|
||||
else:
|
||||
data = post_json(args, PATHS[args.command], payload)
|
||||
data = post_json(
|
||||
args, PATHS[args.command], payload, response_metadata=response_metadata
|
||||
)
|
||||
if args.command in {"asr", "health", "models"}:
|
||||
print(json.dumps(json.loads(data), ensure_ascii=False, indent=2))
|
||||
else:
|
||||
@@ -540,6 +653,7 @@ def main(argv=None):
|
||||
"ok": True,
|
||||
"output": str(Path(args.out).resolve()),
|
||||
**wav_info(data),
|
||||
**response_metadata,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
import wave
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
from urllib.error import HTTPError
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
|
||||
import call_voice_api as api
|
||||
|
||||
|
||||
def stereo_wav():
|
||||
data = io.BytesIO()
|
||||
with wave.open(data, "wb") as stream:
|
||||
stream.setnchannels(2)
|
||||
stream.setsampwidth(2)
|
||||
stream.setframerate(48000)
|
||||
stream.writeframes(b"\x01\x00\x02\x00" * 480)
|
||||
return data.getvalue()
|
||||
|
||||
|
||||
class AceStepTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
patch = mock.patch.object(
|
||||
api, "urlopen", side_effect=AssertionError("unexpected network")
|
||||
)
|
||||
self.net = patch.start()
|
||||
self.addCleanup(patch.stop)
|
||||
env = mock.patch.dict(os.environ, {"AUDIO_API_TOKEN": "test-key"}, clear=True)
|
||||
env.start()
|
||||
self.addCleanup(env.stop)
|
||||
|
||||
def test_defaults_and_existing_routes_stay_separate(self):
|
||||
args = api.build_parser().parse_args(["acestep", "--prompt", "piano"])
|
||||
payload = api.payload_from_args(args)
|
||||
self.assertEqual(args.timeout, 900)
|
||||
self.assertEqual(payload["seconds"], 30)
|
||||
self.assertEqual(payload["steps"], 8)
|
||||
self.assertIs(payload["instrumental"], True)
|
||||
self.assertIs(payload["use_lm"], True)
|
||||
self.assertEqual(payload["lyrics"], "")
|
||||
self.assertNotIn("seed", payload)
|
||||
self.assertNotIn("bpm", payload)
|
||||
self.assertEqual(api.PATHS["acestep"], "/v1/audio/music/acestep")
|
||||
music = api.build_parser().parse_args(["music", "--prompt", "piano"])
|
||||
self.assertEqual(api.PATHS[music.command], "/v1/audio/music")
|
||||
self.assertEqual(music.timeout, 1800)
|
||||
self.assertEqual(api.payload_from_args(music)["cfg"], 1)
|
||||
self.assertNotIn("instrumental", api.payload_from_args(music))
|
||||
api.validate_payload("music", {"prompt": "p" * 513, "seconds": 5})
|
||||
api.validate_payload(
|
||||
"yue2", {"style": "pop", "lyrics": "字" * 4097, "seed": 2**63 - 1}
|
||||
)
|
||||
with self.assertRaises(ValueError):
|
||||
api.validate_payload("music", {"prompt": "piano", "seed": 2**32 - 1})
|
||||
|
||||
def test_instrumental_and_lyric_mode_conflicts(self):
|
||||
for lyrics in (None, "", " \n", "[Instrumental]"):
|
||||
payload = {"prompt": "piano"}
|
||||
if lyrics is not None:
|
||||
payload["lyrics"] = lyrics
|
||||
api.validate_payload("acestep", payload)
|
||||
for lyrics, instrumental in (
|
||||
(None, True),
|
||||
("歌声", True),
|
||||
("", False),
|
||||
(" \n", False),
|
||||
("[Instrumental]", False),
|
||||
):
|
||||
with (
|
||||
self.subTest(lyrics=lyrics, instrumental=instrumental),
|
||||
self.assertRaises(ValueError),
|
||||
):
|
||||
api.validate_payload(
|
||||
"acestep",
|
||||
{"prompt": "pop", "lyrics": lyrics, "instrumental": instrumental},
|
||||
)
|
||||
api.validate_payload(
|
||||
"acestep",
|
||||
{"prompt": "pop", "lyrics": "[Verse]\n你好", "instrumental": False},
|
||||
)
|
||||
|
||||
def test_string_and_numeric_boundaries(self):
|
||||
api.validate_payload(
|
||||
"acestep",
|
||||
{
|
||||
"prompt": "音" * 512,
|
||||
"lyrics": "词" * 4096,
|
||||
"instrumental": False,
|
||||
"seconds": 600,
|
||||
"steps": 20,
|
||||
"seed": 2**32 - 1,
|
||||
"bpm": 300,
|
||||
},
|
||||
)
|
||||
api.validate_payload(
|
||||
"acestep",
|
||||
{"prompt": "piano", "seconds": 10, "steps": 1, "seed": 0, "bpm": 30},
|
||||
)
|
||||
api.validate_payload(
|
||||
"acestep", {"prompt": "piano", "seconds": 10.5, "seed": None, "bpm": None}
|
||||
)
|
||||
invalid = {
|
||||
"prompt": ("", " \n", "音" * 513, None),
|
||||
"lyrics": (None, "词" * 4097),
|
||||
"seconds": (9.99, 600.01, float("nan"), float("inf"), True, "30", None),
|
||||
"steps": (0, 21, 8.5, True, "8", None),
|
||||
"seed": (-1, 2**32, 10**1000, 7.5, True, "7"),
|
||||
"bpm": (29, 301, 120.5, False, "120"),
|
||||
"instrumental": (None, "false", 0),
|
||||
"use_lm": (None, "false", 0),
|
||||
}
|
||||
for field, values in invalid.items():
|
||||
for value in values:
|
||||
with (
|
||||
self.subTest(field=field, value=value),
|
||||
self.assertRaises(ValueError),
|
||||
):
|
||||
api.validate_payload("acestep", {"prompt": "pop", field: value})
|
||||
|
||||
def test_music_controls_and_unknown_fields(self):
|
||||
for key in ("", "C Major", "F# minor", "Bb Major", "G♯ Minor", "D♭ major"):
|
||||
api.validate_payload("acestep", {"prompt": "pop", "keyscale": key})
|
||||
for signature in ("", "2", "3", "4", "6"):
|
||||
api.validate_payload(
|
||||
"acestep", {"prompt": "pop", "timesignature": signature}
|
||||
)
|
||||
for lang in ("unknown", "zh", "yue", "en"):
|
||||
api.validate_payload("acestep", {"prompt": "pop", "vocal_language": lang})
|
||||
for field, value in (
|
||||
("keyscale", "Am"),
|
||||
("keyscale", "H Major"),
|
||||
("keyscale", None),
|
||||
("timesignature", "4/4"),
|
||||
("timesignature", 4),
|
||||
("vocal_language", "auto"),
|
||||
("vocal_language", "zh-CN"),
|
||||
("cfg", 1),
|
||||
("cfg_scale", 1),
|
||||
("guidance_scale", 1),
|
||||
("negative_prompt", "noise"),
|
||||
("abc", "X:1"),
|
||||
("model", "other"),
|
||||
("batch_size", 2),
|
||||
):
|
||||
with self.subTest(field=field), self.assertRaises(ValueError):
|
||||
api.validate_payload("acestep", {"prompt": "pop", field: value})
|
||||
|
||||
def test_lyric_file_wire_preserves_unicode_false_and_seed_zero(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
lyrics = Path(directory) / "歌词.txt"
|
||||
lyrics.write_text("[Verse]\n清晨的风\n[Chorus]\n你好", encoding="utf-8-sig")
|
||||
args = api.build_parser().parse_args(
|
||||
[
|
||||
"acestep",
|
||||
"--prompt",
|
||||
"中文流行",
|
||||
"--lyrics-file",
|
||||
str(lyrics),
|
||||
"--instrumental",
|
||||
"false",
|
||||
"--use-lm",
|
||||
"false",
|
||||
"--seed",
|
||||
"0",
|
||||
"--vocal-language",
|
||||
"zh",
|
||||
"--bpm",
|
||||
"120",
|
||||
"--keyscale",
|
||||
"C Major",
|
||||
"--timesignature",
|
||||
"4",
|
||||
]
|
||||
)
|
||||
args.token = "test-key"
|
||||
payload = api.payload_from_args(args)
|
||||
result = io.BytesIO(stereo_wav())
|
||||
result.headers = {"Content-Type": "audio/wav", "X-Audio-Seed": "0"}
|
||||
self.net.side_effect = None
|
||||
self.net.return_value = result
|
||||
metadata = {}
|
||||
api.post_json(
|
||||
args, api.PATHS[args.command], payload, response_metadata=metadata
|
||||
)
|
||||
request = self.net.call_args.args[0]
|
||||
self.assertEqual(
|
||||
request.full_url, "https://ades.playones.com/v1/audio/music/acestep"
|
||||
)
|
||||
self.assertEqual(request.get_header("Authorization"), "Bearer test-key")
|
||||
self.assertEqual(request.get_header("Accept"), "audio/wav")
|
||||
self.assertEqual(json.loads(request.data), payload)
|
||||
self.assertEqual(payload["lyrics"], "[Verse]\n清晨的风\n[Chorus]\n你好")
|
||||
self.assertIs(payload["instrumental"], False)
|
||||
self.assertIs(payload["use_lm"], False)
|
||||
self.assertEqual(payload["seed"], 0)
|
||||
self.assertEqual(payload["timesignature"], "4")
|
||||
self.assertEqual(metadata, {"seed": 0})
|
||||
self.assertEqual(self.net.call_args.kwargs["timeout"], 900)
|
||||
|
||||
def test_dry_run_and_conflict_never_call_network(self):
|
||||
output = io.StringIO()
|
||||
with (
|
||||
mock.patch.dict(os.environ, {}, clear=True),
|
||||
contextlib.redirect_stdout(output),
|
||||
):
|
||||
self.assertEqual(api.main(["acestep", "--prompt", "piano", "--dry-run"]), 0)
|
||||
data = json.loads(output.getvalue())
|
||||
self.assertNotIn("token", data)
|
||||
self.assertEqual(data["payload"]["lyrics"], "")
|
||||
with contextlib.redirect_stderr(io.StringIO()):
|
||||
self.assertEqual(
|
||||
api.main(["acestep", "--prompt", "pop", "--lyrics", "歌词"]), 1
|
||||
)
|
||||
self.net.assert_not_called()
|
||||
|
||||
def test_cli_saves_binary_wav_and_reports_actual_response_seed(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
for seed in ("4294967295", None, "not-a-seed", "4294967296"):
|
||||
with self.subTest(seed=seed):
|
||||
result = io.BytesIO(stereo_wav())
|
||||
result.headers = {"Content-Type": "audio/wav"}
|
||||
if seed is not None:
|
||||
result.headers["X-Audio-Seed"] = seed
|
||||
self.net.side_effect = None
|
||||
self.net.return_value = result
|
||||
output = io.StringIO()
|
||||
target = Path(directory) / "music.wav"
|
||||
with contextlib.redirect_stdout(output):
|
||||
self.assertEqual(
|
||||
api.main(
|
||||
[
|
||||
"acestep",
|
||||
"--prompt",
|
||||
"piano",
|
||||
"--seed",
|
||||
"7",
|
||||
"--out",
|
||||
str(target),
|
||||
]
|
||||
),
|
||||
0,
|
||||
)
|
||||
report = json.loads(output.getvalue())
|
||||
self.assertEqual(target.read_bytes(), stereo_wav())
|
||||
self.assertEqual(
|
||||
(report["sample_rate"], report["channels"], report["bits"]),
|
||||
(48000, 2, 16),
|
||||
)
|
||||
if seed == "4294967295":
|
||||
self.assertEqual(report["seed"], 2**32 - 1)
|
||||
else:
|
||||
self.assertNotIn("seed", report)
|
||||
self.assertNotIn("test-key", output.getvalue())
|
||||
|
||||
def test_failed_inference_never_retries_or_overwrites_output(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
target = Path(directory) / "music.wav"
|
||||
target.write_bytes(b"existing user output")
|
||||
for status, raw in (
|
||||
(401, b'{"detail":"invalid key"}'),
|
||||
(422, b'{"detail":"deployment duration limit"}'),
|
||||
(503, b'{"detail":"ACE-Step busy"}'),
|
||||
(503, b'{"detail":"ACE-Step not installed"}'),
|
||||
(500, b'{"detail":"invalid output"}'),
|
||||
(504, b'{"detail":"worker timeout"}'),
|
||||
(504, b"<html>Gateway Time-out</html>"),
|
||||
):
|
||||
with self.subTest(status=status, raw=raw):
|
||||
self.net.reset_mock()
|
||||
self.net.side_effect = HTTPError(
|
||||
"https://example.test",
|
||||
status,
|
||||
"error",
|
||||
{"Retry-After": "2"},
|
||||
io.BytesIO(raw),
|
||||
)
|
||||
error = io.StringIO()
|
||||
with contextlib.redirect_stderr(error):
|
||||
self.assertEqual(
|
||||
api.main(
|
||||
["acestep", "--prompt", "piano", "--out", str(target)]
|
||||
),
|
||||
1,
|
||||
)
|
||||
self.net.assert_called_once()
|
||||
self.assertEqual(target.read_bytes(), b"existing user output")
|
||||
report = json.loads(error.getvalue())
|
||||
self.assertEqual(report["http_status"], status)
|
||||
self.assertEqual(
|
||||
report["confirmed_busy"], raw == b'{"detail":"ACE-Step busy"}'
|
||||
)
|
||||
self.assertNotIn("test-key", error.getvalue())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "generate-agents-md",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "创建、更新或审查 AGENTS.md;基于仓库事实生成精炼、准确的 Agent 指南。",
|
||||
"author": {
|
||||
"name": "EAPIL",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"sourceId": "generate-agents-md",
|
||||
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
|
||||
"ref": "v1.0.0",
|
||||
"commit": "e869eb169eeb5a7cd9e58c0e514fa2dbbf50987d",
|
||||
"ref": "v1.1.0",
|
||||
"commit": "4e572a4377f3164a2d4b7bd62876a63a86494551",
|
||||
"adapter": "codex-plugin",
|
||||
"sourcePath": "plugins/generate-agents-md",
|
||||
"syncedAt": "2026-10-06T07:26:41Z"
|
||||
"syncedAt": "2026-10-15T03:03:03Z"
|
||||
}
|
||||
@@ -3,5 +3,5 @@
|
||||
"name": "playwright浏览器自动化操作",
|
||||
"version": "20260605",
|
||||
"keySource": "none",
|
||||
"syncedAt": "2026-10-14T16:02:40Z"
|
||||
"syncedAt": "2026-10-15T03:05:45Z"
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
"sourceId": "next-skills",
|
||||
"repo": "https://github.com/vercel/next.js.git",
|
||||
"ref": "canary",
|
||||
"commit": "2df613dbe00b82a6a684e570bc77e242825182ab",
|
||||
"commit": "0b3693ddd5fca101fe6d286849a980ddc1bf0650",
|
||||
"adapter": "skill-collection",
|
||||
"sourcePath": "skills",
|
||||
"syncedAt": "2026-10-14T16:00:00Z"
|
||||
"syncedAt": "2026-10-15T03:03:03Z"
|
||||
}
|
||||
@@ -335,7 +335,7 @@ If it is missing, install it yourself from [python.org](https://www.python.org/d
|
||||
|
||||
### Skill Mode (Auto-activate)
|
||||
|
||||
**Supported:** Claude Code, Cursor, Windsurf, Antigravity, Codex CLI, Continue, Gemini CLI, OpenCode, Qoder, CodeBuddy, Droid (Factory), KiloCode, Warp, Augment, CodeWhale, ZCode, Amazon Q Developer, OpenClaw
|
||||
**Supported:** Claude Code, Cursor, Windsurf, Antigravity, Codex CLI, Continue, Gemini CLI, OpenCode, Qoder, CodeBuddy, Droid (Factory), KiloCode, Warp, Augment, CodeWhale, ZCode, Amazon Q Developer, OpenClaw, GitHub Copilot
|
||||
|
||||
The skill activates automatically when you request UI/UX work. Just chat naturally:
|
||||
|
||||
@@ -347,7 +347,7 @@ Build a landing page for my SaaS product
|
||||
|
||||
### Workflow Mode (Slash Command)
|
||||
|
||||
**Supported:** Kiro, GitHub Copilot, Roo Code, KiloCode
|
||||
**Supported:** Kiro, Roo Code, KiloCode
|
||||
|
||||
Use the slash command to invoke the skill:
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"sourceId": "ui-ux-pro-max",
|
||||
"repo": "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill.git",
|
||||
"ref": "main",
|
||||
"commit": "7246805992a42b7bba37d4cac83b01416f0c6c44",
|
||||
"commit": "56ea9cf174ed86786187daa9de0e6247cb0747e4",
|
||||
"adapter": "claude-skill",
|
||||
"sourcePath": ".claude/skills/ui-ux-pro-max",
|
||||
"syncedAt": "2026-10-12T15:59:59Z"
|
||||
"syncedAt": "2026-10-15T03:03:03Z"
|
||||
}
|
||||
Reference in new issue
Block a user