@ulmeanua/kie-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +159 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/dist/client.d.ts +14 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +115 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +42 -0
- package/dist/config.js.map +1 -0
- package/dist/downloader.d.ts +15 -0
- package/dist/downloader.d.ts.map +1 -0
- package/dist/downloader.js +87 -0
- package/dist/downloader.js.map +1 -0
- package/dist/endpoints.d.ts +8 -0
- package/dist/endpoints.d.ts.map +1 -0
- package/dist/endpoints.js +183 -0
- package/dist/endpoints.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/poller.d.ts +10 -0
- package/dist/poller.d.ts.map +1 -0
- package/dist/poller.js +44 -0
- package/dist/poller.js.map +1 -0
- package/dist/registry.d.ts +5 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +271 -0
- package/dist/registry.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +57 -0
- package/dist/server.js.map +1 -0
- package/dist/store.d.ts +36 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +108 -0
- package/dist/store.js.map +1 -0
- package/dist/tools.d.ts +14 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +446 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +54 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +24 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/package.json +63 -0
package/INSTALL.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Install @ulmeanua/kie-mcp — step-by-step guide
|
|
2
|
+
|
|
3
|
+
This MCP server gives you access to **30+ AI models** through Kie.ai (Veo, Suno, Runway, Flux, Nano Banana, ElevenLabs, Midjourney, etc.) directly inside Claude Desktop / Claude Code / Cursor / Windsurf. Lower cost than the official APIs, automatic polling, asset downloaded locally.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Get a Kie.ai API key
|
|
8
|
+
|
|
9
|
+
Go to [kie.ai/api-key](https://kie.ai/api-key), create an account and copy the key. Format: 32 hex characters (a–f + digits).
|
|
10
|
+
|
|
11
|
+
Tip: Kie.ai gives free welcome credits. Your first test call (nano-banana-2 at 1K) costs ~$0.04 — well within the free tier.
|
|
12
|
+
|
|
13
|
+
## 2. Check Node.js
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
node --version
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
You need **22.0 or newer**. If older:
|
|
20
|
+
- Windows: [nodejs.org](https://nodejs.org) → download LTS
|
|
21
|
+
- macOS: `brew install node@22`
|
|
22
|
+
- Linux: [nodejs.org/en/download](https://nodejs.org/en/download)
|
|
23
|
+
|
|
24
|
+
## 3. Configure in your MCP client
|
|
25
|
+
|
|
26
|
+
### Claude Desktop
|
|
27
|
+
|
|
28
|
+
Edit the config file:
|
|
29
|
+
- **Mac**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
30
|
+
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
31
|
+
|
|
32
|
+
Add (or merge into `mcpServers`):
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"mcpServers": {
|
|
37
|
+
"kie": {
|
|
38
|
+
"command": "npx",
|
|
39
|
+
"args": ["-y", "@ulmeanua/kie-mcp"],
|
|
40
|
+
"env": {
|
|
41
|
+
"KIE_API_KEY": "your-kie-key-here"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Restart Claude Desktop. On your next conversation, the `kie_*` tools should be available.
|
|
49
|
+
|
|
50
|
+
### Claude Code
|
|
51
|
+
|
|
52
|
+
In Claude Code's MCP settings, add the same snippet as above.
|
|
53
|
+
|
|
54
|
+
### Cursor
|
|
55
|
+
|
|
56
|
+
Edit `~/.cursor/mcp.json` (Mac/Linux) or `%USERPROFILE%\.cursor\mcp.json` (Windows):
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"kie": {
|
|
62
|
+
"command": "npx",
|
|
63
|
+
"args": ["-y", "@ulmeanua/kie-mcp"],
|
|
64
|
+
"env": {
|
|
65
|
+
"KIE_API_KEY": "your-key-here"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Restart Cursor.
|
|
73
|
+
|
|
74
|
+
### Windsurf
|
|
75
|
+
|
|
76
|
+
Edit `~/.codeium/windsurf/mcp_config.json` with the same snippet.
|
|
77
|
+
|
|
78
|
+
## 4. Quick test
|
|
79
|
+
|
|
80
|
+
In any MCP client (after restart), tell the AI:
|
|
81
|
+
|
|
82
|
+
> "Use kie_health to show me the server is running."
|
|
83
|
+
|
|
84
|
+
If it replies with a JSON containing `package: @ulmeanua/kie-mcp` and `api_key_set: true`, you're set.
|
|
85
|
+
|
|
86
|
+
Then:
|
|
87
|
+
|
|
88
|
+
> "Generate an image with nano-banana-2: a red apple on a wooden table."
|
|
89
|
+
|
|
90
|
+
The AI will call `kie_image` with `wait:true` (default), wait ~30–60s, download the asset and give you back the local path.
|
|
91
|
+
|
|
92
|
+
## 5. Optional environment variables
|
|
93
|
+
|
|
94
|
+
| Env var | Default | What it does |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `KIE_OUTPUT_DIR` | `$HOME/.kie-mcp/assets` | Where downloaded assets are written |
|
|
97
|
+
| `KIE_COST_BUDGET_USD` | not set | If set (e.g. `5.00`), MCP blocks calls past the cap |
|
|
98
|
+
| `KIE_POLL_INTERVAL_MS` | `3000` | Polling cadence for video/music (3s default) |
|
|
99
|
+
| `KIE_POLL_MAX_MS` | `600000` | Polling timeout (10 min default — large videos may need more) |
|
|
100
|
+
|
|
101
|
+
Add them in the MCP config's `env` block:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
"env": {
|
|
105
|
+
"KIE_API_KEY": "...",
|
|
106
|
+
"KIE_COST_BUDGET_USD": "5.00",
|
|
107
|
+
"KIE_OUTPUT_DIR": "/Users/me/Desktop/kie-output"
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 6. Cost so far
|
|
112
|
+
|
|
113
|
+
At any time, ask the AI:
|
|
114
|
+
|
|
115
|
+
> "Run kie_cost_report."
|
|
116
|
+
|
|
117
|
+
You get total spend + per-model breakdown + remaining budget (if `KIE_COST_BUDGET_USD` is set).
|
|
118
|
+
|
|
119
|
+
## 7. Compare models
|
|
120
|
+
|
|
121
|
+
> "Use kie_compare with prompt='X' and models=['nano-banana-2','flux-kontext-pro','seedream-v5-lite']."
|
|
122
|
+
|
|
123
|
+
Runs all 3 in parallel and downloads the grid. Useful when picking the best model for a given prompt.
|
|
124
|
+
|
|
125
|
+
## 8. Available models
|
|
126
|
+
|
|
127
|
+
Ask: `kie_models` (or filtered by kind):
|
|
128
|
+
|
|
129
|
+
| Category | Registered models |
|
|
130
|
+
|---|---|
|
|
131
|
+
| **Image** | nano-banana-2 ✅, flux-kontext-pro, flux-kontext-max, gpt-image-2, seedream-v5-lite, qwen-image* |
|
|
132
|
+
| **Video** | veo3, veo3_fast, runway-aleph, seedance-2 |
|
|
133
|
+
| **Music** | suno-v5, suno-v4-5 |
|
|
134
|
+
| **Speech** | elevenlabs-tts, elevenlabs-sfx |
|
|
135
|
+
|
|
136
|
+
✅ = verified live against the real API. The rest come from docs.kie.ai — if you get `422: model name not supported`, the current ID in kie.ai's catalog is different; please open an issue.
|
|
137
|
+
|
|
138
|
+
\* `qwen-image` was not accepted in May 2026 — kie.ai uses a different ID; open an issue if you know the real name.
|
|
139
|
+
|
|
140
|
+
## 9. Common issues
|
|
141
|
+
|
|
142
|
+
**"Cannot find module '@ulmeanua/kie-mcp'"**
|
|
143
|
+
→ Your Node version is <22. Update Node.
|
|
144
|
+
|
|
145
|
+
**"KIE_API_KEY is required"**
|
|
146
|
+
→ The key isn't in the MCP config's `env`. Double-check.
|
|
147
|
+
|
|
148
|
+
**"kie.ai error (code=401): Invalid API key"**
|
|
149
|
+
→ The key is wrong or expired. Regenerate at kie.ai/api-key.
|
|
150
|
+
|
|
151
|
+
**"polling timeout after 600000ms"**
|
|
152
|
+
→ The video model took >10 min. Increase `KIE_POLL_MAX_MS` or use `wait:false` + `kie_wait` with a larger `timeout_ms`.
|
|
153
|
+
|
|
154
|
+
**"cost budget exceeded"**
|
|
155
|
+
→ The feature is working. Remove `KIE_COST_BUDGET_USD` from config or raise the cap.
|
|
156
|
+
|
|
157
|
+
## 10. Issues / contributions
|
|
158
|
+
|
|
159
|
+
Public repo at **https://github.com/ulmeanuadrian/kie-mcp**. Issues welcome. PRs require green evals (47/47 mocked + 2 live).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Adrian Ulmeanu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# @ulmeanua/kie-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for [kie.ai](https://kie.ai) — an aggregator for generative media models (Veo, Suno, Runway, Flux, Nano Banana, Midjourney, ElevenLabs, etc.) with prices well below the official APIs.
|
|
4
|
+
|
|
5
|
+
**What sets this server apart:**
|
|
6
|
+
|
|
7
|
+
1. **Sync-wait built-in.** `kie_video(...)` polls the task to completion and downloads the asset locally. No manual polling loop.
|
|
8
|
+
2. **Auto-download.** Every successful task writes the file to a predictable local path; the tool returns the absolute path, not a URL that expires.
|
|
9
|
+
3. **Five umbrella tools.** `kie_image`, `kie_video`, `kie_music`, `kie_speech`, `kie_compare` — dispatch by `model` param. Not 24 separate tools bloating your MCP context.
|
|
10
|
+
4. **Cost telemetry.** `kie_cost_report` shows session + per-model + total spend. Optional `KIE_COST_BUDGET_USD` blocks calls past the cap.
|
|
11
|
+
5. **Batch & compare.** `kie_compare(prompt, models=[...])` runs the same prompt on N models in parallel and returns a grid of results.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g @ulmeanua/kie-mcp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or run via `npx` from your MCP client config:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"kie": {
|
|
25
|
+
"command": "npx",
|
|
26
|
+
"args": ["-y", "@ulmeanua/kie-mcp"],
|
|
27
|
+
"env": {
|
|
28
|
+
"KIE_API_KEY": "sk-..."
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Detailed step-by-step install instructions for Claude Desktop, Claude Code, Cursor, and Windsurf are in [INSTALL.md](./INSTALL.md).
|
|
36
|
+
|
|
37
|
+
## Tools (11 total)
|
|
38
|
+
|
|
39
|
+
| Tool | Purpose |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `kie_image` | Generate / edit an image. Defaults to `wait:true, download:true`. |
|
|
42
|
+
| `kie_video` | Generate a video. Defaults to `wait:true, download:true`. |
|
|
43
|
+
| `kie_music` | Generate music (Suno). |
|
|
44
|
+
| `kie_speech` | TTS and sound effects (ElevenLabs). |
|
|
45
|
+
| `kie_compare` | Run the same prompt across N models in parallel (cap 4). Same-kind only. |
|
|
46
|
+
| `kie_wait` | Wait for an existing task_id until it terminates and download. |
|
|
47
|
+
| `kie_status` | Get current state of a task without polling. |
|
|
48
|
+
| `kie_assets` | List tasks from the local DB filtered by `model` / `state`. |
|
|
49
|
+
| `kie_cost_report` | Cumulative cost (all-time or hours window) + budget remaining. |
|
|
50
|
+
| `kie_models` | List registered models (id, kind, family, description). |
|
|
51
|
+
| `kie_health` | Health probe + echoed config (api_key set / output dir / db path). |
|
|
52
|
+
|
|
53
|
+
## Registered models (verification status)
|
|
54
|
+
|
|
55
|
+
| Model | Kind | Endpoint family | Verified live |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| `nano-banana-2` | image | unified | ✅ 2026-05-27 (live smoke) |
|
|
58
|
+
| `flux-kontext-pro` | image | unified | docs only |
|
|
59
|
+
| `flux-kontext-max` | image | unified | docs only |
|
|
60
|
+
| `gpt-image-2` | image | gpt4o legacy | docs only |
|
|
61
|
+
| `seedream-v5-lite` | image | unified | docs only |
|
|
62
|
+
| `qwen-image` | image | unified | ⚠️ this ID was not accepted on the API (May 2026); check the current kie.ai catalog |
|
|
63
|
+
| `veo3`, `veo3_fast` | video | veo legacy | docs only |
|
|
64
|
+
| `runway-aleph` | video | runway legacy | docs only |
|
|
65
|
+
| `seedance-2` | video | unified | docs only |
|
|
66
|
+
| `suno-v5`, `suno-v4-5` | music | suno legacy | docs only |
|
|
67
|
+
| `elevenlabs-tts`, `elevenlabs-sfx` | speech | unified | docs only |
|
|
68
|
+
|
|
69
|
+
When an ID returns `422: model name not supported`, query `kie_models` to see the registered catalog and update `src/registry.ts` with the exact name from kie.ai's market.
|
|
70
|
+
|
|
71
|
+
## Configuration
|
|
72
|
+
|
|
73
|
+
| Env var | Default | Role |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `KIE_API_KEY` | — | Required. Your kie.ai key |
|
|
76
|
+
| `KIE_API_BASE` | `https://api.kie.ai/api/v1` | Override base URL |
|
|
77
|
+
| `KIE_TIMEOUT_MS` | `120000` | HTTP request timeout |
|
|
78
|
+
| `KIE_OUTPUT_DIR` | `$HOME/.kie-mcp/assets` | Where downloaded assets are written |
|
|
79
|
+
| `KIE_DB_PATH` | `$HOME/.kie-mcp/state.db` | SQLite path for task tracking + cost |
|
|
80
|
+
| `KIE_POLL_INTERVAL_MS` | `3000` | Polling cadence |
|
|
81
|
+
| `KIE_POLL_MAX_MS` | `600000` | Polling cap (10 min) |
|
|
82
|
+
| `KIE_COST_BUDGET_USD` | — | When set, blocks calls after exceeding the cap |
|
|
83
|
+
|
|
84
|
+
## Development
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm install
|
|
88
|
+
npm run build
|
|
89
|
+
npm run typecheck
|
|
90
|
+
npm run eval # 47 evals across 5 phases (mocked) — should be 47/47 green
|
|
91
|
+
npm run eval:live # adds 2 live calls to kie.ai (needs KIE_API_KEY, costs ~$0.04)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Eval phases
|
|
95
|
+
|
|
96
|
+
| Phase | Eval file | Tests | Verifies |
|
|
97
|
+
|---|---|---|---|
|
|
98
|
+
| 0 | `phase0_scaffold.eval.ts` | 6 | Server boot, config validation, MCP round-trip |
|
|
99
|
+
| 1 | `phase1_client.eval.ts` | 11 | KieClient serialize / retry / parse across all 5 endpoint families |
|
|
100
|
+
| 2 | `phase2_tools.eval.ts` | 12 | Tool discovery, dispatch routing, cross-kind guard, Zod validation |
|
|
101
|
+
| 3 | `phase3_wait_download.eval.ts` | 12 | Poller, downloader, store round-trip, idempotency, budget enforcement |
|
|
102
|
+
| 4 | `phase4_telemetry_compare.eval.ts` | 5 | kie_compare parallel + graceful degrade, cost report aggregation |
|
|
103
|
+
| 5 | `phase5_live_smoke.eval.ts` | 2 (gated) | Real kie.ai call → asset downloaded + cost logged |
|
|
104
|
+
|
|
105
|
+
**Evals are the source of truth for the contract — any code change must keep 47/47 mocked evals green (or update the evals with an explicit reason in the commit message).**
|
|
106
|
+
|
|
107
|
+
## Architecture
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
MCP client
|
|
111
|
+
↓ stdio
|
|
112
|
+
src/index.ts ── boot, wire dependencies
|
|
113
|
+
src/server.ts ── MCP request routing (tools/list, tools/call)
|
|
114
|
+
src/tools.ts ── 11 tool handlers (umbrella + utility + telemetry)
|
|
115
|
+
↓
|
|
116
|
+
src/registry.ts ── MODEL_REGISTRY (id → endpoint family + Zod schema + cost estimator)
|
|
117
|
+
↓
|
|
118
|
+
src/client.ts ── KieClient (fetch + retry + timeout + idempotency-key)
|
|
119
|
+
src/endpoints.ts ── 5 endpoint families (unified, veo, runway, suno, gpt4o)
|
|
120
|
+
↓
|
|
121
|
+
src/poller.ts ── pollUntilTerminal (waiting → success/fail)
|
|
122
|
+
src/downloader.ts ── AssetDownloader (URL → KIE_OUTPUT_DIR/<task_id>.<ext>)
|
|
123
|
+
src/store.ts ── TaskStore (node:sqlite — tasks + idempotency + cost)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT. See [LICENSE](./LICENSE).
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Config } from './config.js';
|
|
2
|
+
import { CreateTaskResult, EndpointSpec, KieRequestOptions, NormalizedTaskStatus, RawKieResponse } from './types.js';
|
|
3
|
+
export interface FetchLike {
|
|
4
|
+
(input: string, init?: RequestInit): Promise<Response>;
|
|
5
|
+
}
|
|
6
|
+
export declare class KieClient {
|
|
7
|
+
private readonly config;
|
|
8
|
+
private readonly fetchImpl;
|
|
9
|
+
constructor(config: Config, fetchImpl?: FetchLike);
|
|
10
|
+
createTask(endpoint: EndpointSpec, model: string, input: Record<string, unknown>, callBackUrl?: string, options?: KieRequestOptions): Promise<CreateTaskResult>;
|
|
11
|
+
getTaskStatus(endpoint: EndpointSpec, taskId: string, options?: KieRequestOptions): Promise<NormalizedTaskStatus>;
|
|
12
|
+
request<T>(method: 'GET' | 'POST', path: string, body: unknown, options?: KieRequestOptions): Promise<RawKieResponse<T>>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EACL,gBAAgB,EAChB,YAAY,EAEZ,iBAAiB,EAEjB,oBAAoB,EACpB,cAAc,EACf,MAAM,YAAY,CAAC;AAMpB,MAAM,WAAW,SAAS;IACxB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACxD;AAED,qBAAa,SAAS;IAIlB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;gBAGnB,MAAM,EAAE,MAAM,EAC/B,SAAS,GAAE,SAA6C;IAKpD,UAAU,CACd,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC;IAoBtB,aAAa,CACjB,QAAQ,EAAE,YAAY,EACtB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;IAU1B,OAAO,CAAC,CAAC,EACb,MAAM,EAAE,KAAK,GAAG,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;CA2F9B"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { KieApiError, KieTransientError, } from './types.js';
|
|
2
|
+
const RETRY_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);
|
|
3
|
+
const MAX_RETRIES = 3;
|
|
4
|
+
const BASE_DELAY_MS = 500;
|
|
5
|
+
export class KieClient {
|
|
6
|
+
config;
|
|
7
|
+
fetchImpl;
|
|
8
|
+
constructor(config, fetchImpl = globalThis.fetch.bind(globalThis)) {
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.fetchImpl = fetchImpl;
|
|
11
|
+
}
|
|
12
|
+
async createTask(endpoint, model, input, callBackUrl, options = {}) {
|
|
13
|
+
const body = endpoint.buildCreateBody(model, input, callBackUrl);
|
|
14
|
+
const raw = await this.request('POST', endpoint.createPath, body, options);
|
|
15
|
+
const taskId = String((raw.data ?? {}).taskId ?? '');
|
|
16
|
+
if (!taskId) {
|
|
17
|
+
throw new KieApiError(200, raw.code, `kie.ai response missing taskId: ${raw.msg ?? 'no message'}`, raw);
|
|
18
|
+
}
|
|
19
|
+
return { taskId };
|
|
20
|
+
}
|
|
21
|
+
async getTaskStatus(endpoint, taskId, options = {}) {
|
|
22
|
+
const raw = await this.request('GET', endpoint.statusPath(taskId), undefined, options);
|
|
23
|
+
return endpoint.parseStatus(raw);
|
|
24
|
+
}
|
|
25
|
+
async request(method, path, body, options = {}) {
|
|
26
|
+
const url = `${this.config.apiBase}${path}`;
|
|
27
|
+
const headers = {
|
|
28
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
29
|
+
Accept: 'application/json',
|
|
30
|
+
};
|
|
31
|
+
if (method === 'POST') {
|
|
32
|
+
headers['Content-Type'] = 'application/json';
|
|
33
|
+
}
|
|
34
|
+
if (options.idempotencyKey) {
|
|
35
|
+
headers['Idempotency-Key'] = options.idempotencyKey;
|
|
36
|
+
}
|
|
37
|
+
let lastError = null;
|
|
38
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
39
|
+
const controller = new AbortController();
|
|
40
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
41
|
+
const signal = options.signal
|
|
42
|
+
? mergeSignals(options.signal, controller.signal)
|
|
43
|
+
: controller.signal;
|
|
44
|
+
try {
|
|
45
|
+
const response = await this.fetchImpl(url, {
|
|
46
|
+
method,
|
|
47
|
+
headers,
|
|
48
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
49
|
+
signal,
|
|
50
|
+
});
|
|
51
|
+
clearTimeout(timeoutId);
|
|
52
|
+
const text = await response.text();
|
|
53
|
+
let parsed;
|
|
54
|
+
try {
|
|
55
|
+
parsed = text ? JSON.parse(text) : { code: response.status, msg: '', data: null };
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
throw new KieApiError(response.status, null, `kie.ai returned non-JSON body (${response.status}): ${text.slice(0, 200)}`);
|
|
59
|
+
}
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
if (RETRY_STATUS.has(response.status) && attempt < MAX_RETRIES - 1) {
|
|
62
|
+
lastError = new KieTransientError(`HTTP ${response.status}: ${parsed.msg ?? text}`);
|
|
63
|
+
await sleep(backoffMs(attempt));
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
throw new KieApiError(response.status, parsed.code ?? null, `kie.ai error (${response.status}): ${parsed.msg ?? 'unknown'}`, parsed);
|
|
67
|
+
}
|
|
68
|
+
if (parsed.code && parsed.code !== 200) {
|
|
69
|
+
throw new KieApiError(response.status, parsed.code, `kie.ai business error (code=${parsed.code}): ${parsed.msg ?? 'unknown'}`, parsed);
|
|
70
|
+
}
|
|
71
|
+
return parsed;
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
clearTimeout(timeoutId);
|
|
75
|
+
if (err instanceof KieApiError)
|
|
76
|
+
throw err;
|
|
77
|
+
if (err instanceof KieTransientError) {
|
|
78
|
+
lastError = err;
|
|
79
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
80
|
+
await sleep(backoffMs(attempt));
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
throw err;
|
|
84
|
+
}
|
|
85
|
+
// network errors, timeouts, abort
|
|
86
|
+
lastError = err;
|
|
87
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
88
|
+
await sleep(backoffMs(attempt));
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
92
|
+
throw new KieTransientError(`request failed after ${MAX_RETRIES} attempts: ${msg}`, err);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
throw lastError instanceof Error
|
|
96
|
+
? lastError
|
|
97
|
+
: new Error('unreachable: retry loop exited without resolution');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function backoffMs(attempt) {
|
|
101
|
+
return BASE_DELAY_MS * 2 ** attempt + Math.floor(Math.random() * 200);
|
|
102
|
+
}
|
|
103
|
+
function sleep(ms) {
|
|
104
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
105
|
+
}
|
|
106
|
+
function mergeSignals(a, b) {
|
|
107
|
+
const controller = new AbortController();
|
|
108
|
+
const onAbort = () => controller.abort();
|
|
109
|
+
if (a.aborted || b.aborted)
|
|
110
|
+
controller.abort();
|
|
111
|
+
a.addEventListener('abort', onAbort);
|
|
112
|
+
b.addEventListener('abort', onAbort);
|
|
113
|
+
return controller.signal;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,WAAW,EAEX,iBAAiB,GAGlB,MAAM,YAAY,CAAC;AAEpB,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAClE,MAAM,WAAW,GAAG,CAAC,CAAC;AACtB,MAAM,aAAa,GAAG,GAAG,CAAC;AAM1B,MAAM,OAAO,SAAS;IAID;IAHF,SAAS,CAAY;IAEtC,YACmB,MAAc,EAC/B,YAAuB,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC;QADvC,WAAM,GAAN,MAAM,CAAQ;QAG/B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,UAAU,CACd,QAAsB,EACtB,KAAa,EACb,KAA8B,EAC9B,WAAoB,EACpB,UAA6B,EAAE;QAE/B,MAAM,IAAI,GAAG,QAAQ,CAAC,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;QACjE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAC5B,MAAM,EACN,QAAQ,CAAC,UAAU,EACnB,IAAI,EACJ,OAAO,CACR,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;QACrD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,WAAW,CACnB,GAAG,EACH,GAAG,CAAC,IAAI,EACR,mCAAmC,GAAG,CAAC,GAAG,IAAI,YAAY,EAAE,EAC5D,GAAG,CACJ,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,CAAC;IACpB,CAAC;IAED,KAAK,CAAC,aAAa,CACjB,QAAsB,EACtB,MAAc,EACd,UAA6B,EAAE;QAE/B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAC5B,KAAK,EACL,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,EAC3B,SAAS,EACT,OAAO,CACR,CAAC;QACF,OAAO,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,OAAO,CACX,MAAsB,EACtB,IAAY,EACZ,IAAa,EACb,UAA6B,EAAE;QAE/B,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,IAAI,EAAE,CAAC;QAC5C,MAAM,OAAO,GAA2B;YACtC,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;YAC7C,MAAM,EAAE,kBAAkB;SAC3B,CAAC;QACF,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACtB,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;QAC/C,CAAC;QACD,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;YAC3B,OAAO,CAAC,iBAAiB,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC;QACtD,CAAC;QAED,IAAI,SAAS,GAAY,IAAI,CAAC;QAC9B,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;YACvD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC9E,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM;gBAC3B,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC;gBACjD,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;YAEtB,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE;oBACzC,MAAM;oBACN,OAAO;oBACP,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;oBAC3D,MAAM;iBACP,CAAC,CAAC;gBACH,YAAY,CAAC,SAAS,CAAC,CAAC;gBAExB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACnC,IAAI,MAAyB,CAAC;gBAC9B,IAAI,CAAC;oBACH,MAAM,GAAG,IAAI,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAuB,CAAC,CAAC,CAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAwB,CAAC;gBAClI,CAAC;gBAAC,MAAM,CAAC;oBACP,MAAM,IAAI,WAAW,CACnB,QAAQ,CAAC,MAAM,EACf,IAAI,EACJ,kCAAkC,QAAQ,CAAC,MAAM,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAC5E,CAAC;gBACJ,CAAC;gBAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;oBACjB,IAAI,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,OAAO,GAAG,WAAW,GAAG,CAAC,EAAE,CAAC;wBACnE,SAAS,GAAG,IAAI,iBAAiB,CAAC,QAAQ,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC,CAAC;wBACpF,MAAM,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;wBAChC,SAAS;oBACX,CAAC;oBACD,MAAM,IAAI,WAAW,CACnB,QAAQ,CAAC,MAAM,EACf,MAAM,CAAC,IAAI,IAAI,IAAI,EACnB,iBAAiB,QAAQ,CAAC,MAAM,MAAM,MAAM,CAAC,GAAG,IAAI,SAAS,EAAE,EAC/D,MAAM,CACP,CAAC;gBACJ,CAAC;gBAED,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,KAAK,GAAG,EAAE,CAAC;oBACvC,MAAM,IAAI,WAAW,CACnB,QAAQ,CAAC,MAAM,EACf,MAAM,CAAC,IAAI,EACX,+BAA+B,MAAM,CAAC,IAAI,MAAM,MAAM,CAAC,GAAG,IAAI,SAAS,EAAE,EACzE,MAAM,CACP,CAAC;gBACJ,CAAC;gBAED,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,YAAY,CAAC,SAAS,CAAC,CAAC;gBACxB,IAAI,GAAG,YAAY,WAAW;oBAAE,MAAM,GAAG,CAAC;gBAC1C,IAAI,GAAG,YAAY,iBAAiB,EAAE,CAAC;oBACrC,SAAS,GAAG,GAAG,CAAC;oBAChB,IAAI,OAAO,GAAG,WAAW,GAAG,CAAC,EAAE,CAAC;wBAC9B,MAAM,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;wBAChC,SAAS;oBACX,CAAC;oBACD,MAAM,GAAG,CAAC;gBACZ,CAAC;gBACD,kCAAkC;gBAClC,SAAS,GAAG,GAAG,CAAC;gBAChB,IAAI,OAAO,GAAG,WAAW,GAAG,CAAC,EAAE,CAAC;oBAC9B,MAAM,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;oBAChC,SAAS;gBACX,CAAC;gBACD,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC7D,MAAM,IAAI,iBAAiB,CAAC,wBAAwB,WAAW,cAAc,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC;YAC3F,CAAC;QACH,CAAC;QACD,MAAM,SAAS,YAAY,KAAK;YAC9B,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACrE,CAAC;CACF;AAED,SAAS,SAAS,CAAC,OAAe;IAChC,OAAO,aAAa,GAAG,CAAC,IAAI,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;AACxE,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,YAAY,CAAC,CAAc,EAAE,CAAc;IAClD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IACzC,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO;QAAE,UAAU,CAAC,KAAK,EAAE,CAAC;IAC/C,CAAC,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACrC,CAAC,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACrC,OAAO,UAAU,CAAC,MAAM,CAAC;AAC3B,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface Config {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
apiBase: string;
|
|
4
|
+
timeoutMs: number;
|
|
5
|
+
outputDir: string;
|
|
6
|
+
dbPath: string;
|
|
7
|
+
pollIntervalMs: number;
|
|
8
|
+
pollMaxMs: number;
|
|
9
|
+
costBudgetUsd: number | null;
|
|
10
|
+
}
|
|
11
|
+
export declare function loadConfig(env: NodeJS.ProcessEnv): Config;
|
|
12
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AASD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CA4BzD"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
apiBase: 'https://api.kie.ai/api/v1',
|
|
5
|
+
timeoutMs: 120_000,
|
|
6
|
+
pollIntervalMs: 3_000,
|
|
7
|
+
pollMaxMs: 600_000,
|
|
8
|
+
};
|
|
9
|
+
export function loadConfig(env) {
|
|
10
|
+
const apiKey = env.KIE_API_KEY?.trim();
|
|
11
|
+
if (!apiKey) {
|
|
12
|
+
throw new Error('[kie-mcp] KIE_API_KEY is required. Set it in MCP client env or .env');
|
|
13
|
+
}
|
|
14
|
+
const home = homedir();
|
|
15
|
+
const outputDir = env.KIE_OUTPUT_DIR?.trim() || join(home, '.kie-mcp', 'assets');
|
|
16
|
+
const dbPath = env.KIE_DB_PATH?.trim() || join(home, '.kie-mcp', 'state.db');
|
|
17
|
+
const budget = env.KIE_COST_BUDGET_USD?.trim();
|
|
18
|
+
const costBudgetUsd = budget ? Number(budget) : null;
|
|
19
|
+
if (budget && Number.isNaN(costBudgetUsd)) {
|
|
20
|
+
throw new Error(`[kie-mcp] KIE_COST_BUDGET_USD must be a number, got: ${budget}`);
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
apiKey,
|
|
24
|
+
apiBase: env.KIE_API_BASE?.trim() || DEFAULTS.apiBase,
|
|
25
|
+
timeoutMs: parseIntOr(env.KIE_TIMEOUT_MS, DEFAULTS.timeoutMs),
|
|
26
|
+
outputDir,
|
|
27
|
+
dbPath,
|
|
28
|
+
pollIntervalMs: parseIntOr(env.KIE_POLL_INTERVAL_MS, DEFAULTS.pollIntervalMs),
|
|
29
|
+
pollMaxMs: parseIntOr(env.KIE_POLL_MAX_MS, DEFAULTS.pollMaxMs),
|
|
30
|
+
costBudgetUsd,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function parseIntOr(raw, fallback) {
|
|
34
|
+
if (!raw)
|
|
35
|
+
return fallback;
|
|
36
|
+
const n = Number(raw);
|
|
37
|
+
if (Number.isNaN(n) || n <= 0) {
|
|
38
|
+
throw new Error(`[kie-mcp] invalid integer: ${raw}`);
|
|
39
|
+
}
|
|
40
|
+
return n;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAajC,MAAM,QAAQ,GAAG;IACf,OAAO,EAAE,2BAA2B;IACpC,SAAS,EAAE,OAAO;IAClB,cAAc,EAAE,KAAK;IACrB,SAAS,EAAE,OAAO;CACnB,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,GAAsB;IAC/C,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;IACvC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,qEAAqE,CACtE,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,MAAM,SAAS,GAAG,GAAG,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;IACjF,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;IAE7E,MAAM,MAAM,GAAG,GAAG,CAAC,mBAAmB,EAAE,IAAI,EAAE,CAAC;IAC/C,MAAM,aAAa,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACrD,IAAI,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,wDAAwD,MAAM,EAAE,CAAC,CAAC;IACpF,CAAC;IAED,OAAO;QACL,MAAM;QACN,OAAO,EAAE,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,QAAQ,CAAC,OAAO;QACrD,SAAS,EAAE,UAAU,CAAC,GAAG,CAAC,cAAc,EAAE,QAAQ,CAAC,SAAS,CAAC;QAC7D,SAAS;QACT,MAAM;QACN,cAAc,EAAE,UAAU,CAAC,GAAG,CAAC,oBAAoB,EAAE,QAAQ,CAAC,cAAc,CAAC;QAC7E,SAAS,EAAE,UAAU,CAAC,GAAG,CAAC,eAAe,EAAE,QAAQ,CAAC,SAAS,CAAC;QAC9D,aAAa;KACd,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,GAAuB,EAAE,QAAgB;IAC3D,IAAI,CAAC,GAAG;QAAE,OAAO,QAAQ,CAAC;IAC1B,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface DownloadResult {
|
|
2
|
+
url: string;
|
|
3
|
+
path: string;
|
|
4
|
+
bytes: number;
|
|
5
|
+
contentType: string | null;
|
|
6
|
+
fromCache: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare class AssetDownloader {
|
|
9
|
+
private readonly outputDir;
|
|
10
|
+
private readonly fetchImpl;
|
|
11
|
+
constructor(outputDir: string, fetchImpl?: typeof fetch);
|
|
12
|
+
download(url: string, taskId: string, indexInTask: number): Promise<DownloadResult>;
|
|
13
|
+
downloadAll(urls: string[], taskId: string): Promise<DownloadResult[]>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=downloader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"downloader.d.ts","sourceRoot":"","sources":["../src/downloader.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS;gBADT,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,OAAO,KAAyC;IAKxE,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAgDnF,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;CAO7E"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createWriteStream, mkdirSync, existsSync } from 'node:fs';
|
|
2
|
+
import { Readable } from 'node:stream';
|
|
3
|
+
import { pipeline } from 'node:stream/promises';
|
|
4
|
+
import { extname, join } from 'node:path';
|
|
5
|
+
import { URL } from 'node:url';
|
|
6
|
+
const EXT_FROM_CONTENT_TYPE = {
|
|
7
|
+
'image/png': '.png',
|
|
8
|
+
'image/jpeg': '.jpg',
|
|
9
|
+
'image/webp': '.webp',
|
|
10
|
+
'image/gif': '.gif',
|
|
11
|
+
'video/mp4': '.mp4',
|
|
12
|
+
'video/quicktime': '.mov',
|
|
13
|
+
'video/webm': '.webm',
|
|
14
|
+
'audio/mpeg': '.mp3',
|
|
15
|
+
'audio/wav': '.wav',
|
|
16
|
+
'audio/ogg': '.ogg',
|
|
17
|
+
'audio/aac': '.aac',
|
|
18
|
+
'audio/x-m4a': '.m4a',
|
|
19
|
+
};
|
|
20
|
+
export class AssetDownloader {
|
|
21
|
+
outputDir;
|
|
22
|
+
fetchImpl;
|
|
23
|
+
constructor(outputDir, fetchImpl = globalThis.fetch.bind(globalThis)) {
|
|
24
|
+
this.outputDir = outputDir;
|
|
25
|
+
this.fetchImpl = fetchImpl;
|
|
26
|
+
mkdirSync(this.outputDir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
async download(url, taskId, indexInTask) {
|
|
29
|
+
const probableExt = guessExtFromUrl(url);
|
|
30
|
+
const suffix = indexInTask === 0 ? '' : `-${indexInTask}`;
|
|
31
|
+
let targetPath = join(this.outputDir, `${taskId}${suffix}${probableExt}`);
|
|
32
|
+
if (existsSync(targetPath)) {
|
|
33
|
+
return {
|
|
34
|
+
url,
|
|
35
|
+
path: targetPath,
|
|
36
|
+
bytes: 0,
|
|
37
|
+
contentType: null,
|
|
38
|
+
fromCache: true,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const response = await this.fetchImpl(url);
|
|
42
|
+
if (!response.ok || !response.body) {
|
|
43
|
+
throw new Error(`download failed (${response.status}) for ${url}: ${response.statusText}`);
|
|
44
|
+
}
|
|
45
|
+
const contentType = response.headers.get('content-type');
|
|
46
|
+
const ctExt = contentType ? EXT_FROM_CONTENT_TYPE[contentType.split(';')[0].trim()] : null;
|
|
47
|
+
if (ctExt && !probableExt) {
|
|
48
|
+
targetPath = join(this.outputDir, `${taskId}${suffix}${ctExt}`);
|
|
49
|
+
}
|
|
50
|
+
const tmpPath = `${targetPath}.partial`;
|
|
51
|
+
const fileStream = createWriteStream(tmpPath);
|
|
52
|
+
// Node fetch body is a WHATWG ReadableStream; convert.
|
|
53
|
+
const nodeStream = Readable.fromWeb(response.body);
|
|
54
|
+
await pipeline(nodeStream, fileStream);
|
|
55
|
+
// atomic rename
|
|
56
|
+
const { rename, stat } = await import('node:fs/promises');
|
|
57
|
+
await rename(tmpPath, targetPath);
|
|
58
|
+
const st = await stat(targetPath);
|
|
59
|
+
return {
|
|
60
|
+
url,
|
|
61
|
+
path: targetPath,
|
|
62
|
+
bytes: st.size,
|
|
63
|
+
contentType,
|
|
64
|
+
fromCache: false,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async downloadAll(urls, taskId) {
|
|
68
|
+
const out = [];
|
|
69
|
+
for (let i = 0; i < urls.length; i++) {
|
|
70
|
+
out.push(await this.download(urls[i], taskId, i));
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function guessExtFromUrl(url) {
|
|
76
|
+
try {
|
|
77
|
+
const u = new URL(url);
|
|
78
|
+
const ext = extname(u.pathname);
|
|
79
|
+
if (ext && ext.length <= 5)
|
|
80
|
+
return ext.toLowerCase();
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// fall through
|
|
84
|
+
}
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=downloader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"downloader.js","sourceRoot":"","sources":["../src/downloader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { EndpointSpec } from './types.js';
|
|
2
|
+
export declare const UNIFIED_ENDPOINT: EndpointSpec;
|
|
3
|
+
export declare const VEO_ENDPOINT: EndpointSpec;
|
|
4
|
+
export declare const RUNWAY_ENDPOINT: EndpointSpec;
|
|
5
|
+
export declare const SUNO_ENDPOINT: EndpointSpec;
|
|
6
|
+
export declare const GPT4O_ENDPOINT: EndpointSpec;
|
|
7
|
+
export declare const ENDPOINTS: Record<EndpointSpec['family'], EndpointSpec>;
|
|
8
|
+
//# sourceMappingURL=endpoints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoints.d.ts","sourceRoot":"","sources":["../src/endpoints.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAGb,MAAM,YAAY,CAAC;AAsBpB,eAAO,MAAM,gBAAgB,EAAE,YA6B9B,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,YA8B1B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,YAiC7B,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,YAwC3B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,YA0B5B,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,YAAY,CAOlE,CAAC"}
|