media-analysis-mcp 2.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sommio
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,191 @@
1
+ # Media Analysis MCP
2
+
3
+ [![CI](https://github.com/JaylanJerry/media-analysis-mcp/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/JaylanJerry/media-analysis-mcp/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/media-analysis-mcp)](https://www.npmjs.com/package/media-analysis-mcp)
5
+ [![License: MIT](https://img.shields.io/github/license/JaylanJerry/media-analysis-mcp)](LICENSE)
6
+ [![Node.js](https://img.shields.io/badge/node-24.x-339933)](https://nodejs.org)
7
+
8
+ > **Rename release candidate:** Media Analysis MCP uses the new package/CLI `media-analysis-mcp` and Host key `media_analysis_mcp`. Version 2.0.0 is prepared but not published yet. The previous `analyze-video-mcp@1.0.0` remains available; see [migration](#name-migration) and [release status](tasks/release-2.0.0.md).
9
+
10
+ MCP media gateway for local agents: submit an MP4/MOV video, MP3 audio, or public HTTPS video URL, and ask the media model a question. The Agent decides what to ask and how to present the answer.
11
+
12
+ 本地 Agent 的媒体分析入口:上传视频或音频,由 Agent 向媒体模型提问。服务端不补写分析提纲、不强制证据 JSON、不自动发起纠错分析。
13
+
14
+ ## Requirements
15
+
16
+ - **Node.js 24.x**. Other Node major versions are not formally supported.
17
+ - An Alibaba Cloud Bailian API key (`DASHSCOPE_API_KEY`). Requests can incur provider charges.
18
+ - An MCP client such as Cursor, Claude Code, Claude Desktop, Codex or VS Code.
19
+
20
+ ## Getting started
21
+
22
+ 1. Create a key in the [Bailian console](https://bailian.console.aliyun.com/).
23
+ 2. Add the config below to your MCP client. Set `MEDIA_ALLOWED_ROOTS` to an existing folder containing media you permit the Agent to upload.
24
+ 3. Restart or reload MCP servers. In Codex, start a new chat after changing the server configuration.
25
+ 4. Explicitly ask for MCP analysis of a small MP4, MOV or MP3 inside the allowed folder, and state what you want to know.
26
+
27
+ Do not commit a config containing a real key. Local media is uploaded in full to Bailian's Beijing temporary storage on a cache miss (objects expire after about 48 hours).
28
+
29
+ ### Standard config
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "media_analysis_mcp": {
35
+ "command": "npx",
36
+ "args": ["-y", "--prefer-offline", "media-analysis-mcp@2.0.0"],
37
+ "env": {
38
+ "DASHSCOPE_API_KEY": "YOUR_DASHSCOPE_API_KEY",
39
+ "QWEN_MODEL": "qwen3.8-omni-flash",
40
+ "MEDIA_ALLOWED_ROOTS": "C:\\Users\\用户名\\Videos"
41
+ }
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ Names are separate: product/MCP server name `Media Analysis MCP`; package/CLI name `media-analysis-mcp`; example Host config key `media_analysis_mcp`; the only Tool is `analyze_media`. The Host key may be renamed.
48
+
49
+ Templates: [`Cursor`](examples/mcp.cursor.json), [`Claude Code/Desktop`](examples/mcp.claude-code.json), [`Codex`](examples/mcp.codex.toml).
50
+
51
+ ### Cursor and Claude Desktop
52
+
53
+ Use the standard config in Cursor's `~/.cursor/mcp.json`, or Claude Desktop's `claude_desktop_config.json`.
54
+
55
+ On Windows, if the host cannot find `npx`, use `"command": "cmd"` and `"args": ["/c", "npx", "-y", "--prefer-offline", "media-analysis-mcp@2.0.0"]`.
56
+
57
+ [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=media_analysis_mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIi0tcHJlZmVyLW9mZmxpbmUiLCJtZWRpYS1hbmFseXNpcy1tY3BAMi4wLjAiXSwiZW52Ijp7IkRBU0hTQ09QRV9BUElfS0VZIjoiWU9VUl9EQVNIU0NPUEVfQVBJX0tFWSIsIlFXRU5fTU9ERUwiOiJxd2VuMy44LW9tbmktZmxhc2giLCJNRURJQV9BTExPV0VEX1JPT1RTIjoiQzpcXFVzZXJzXFznlKjmiLflkI1cXFZpZGVvcyJ9fQ==)
58
+
59
+ ### Claude Code
60
+
61
+ ```bash
62
+ claude mcp add --env DASHSCOPE_API_KEY=YOUR_DASHSCOPE_API_KEY --env QWEN_MODEL=qwen3.8-omni-flash --env MEDIA_ALLOWED_ROOTS="C:\Users\用户名\Videos" --transport stdio media_analysis_mcp -- npx -y --prefer-offline media-analysis-mcp@2.0.0
63
+ ```
64
+
65
+ On native Windows, add `cmd /c` before `npx` if required by the host.
66
+
67
+ ### Codex
68
+
69
+ Use [`examples/mcp.codex.toml`](examples/mcp.codex.toml). Set `startup_timeout_sec = 120` and `tool_timeout_sec = 1200`; put the Key and allowed roots in the MCP `env` table because Codex may not inherit user/system environment variables. Start a new chat after installation.
70
+
71
+ ```powershell
72
+ codex mcp list
73
+ codex mcp get media_analysis_mcp
74
+ ```
75
+
76
+ ### VS Code
77
+
78
+ Use the standard server entry under `servers` instead of `mcpServers` in `.vscode/mcp.json` or the user MCP configuration.
79
+
80
+ ## Tool
81
+
82
+ ```text
83
+ analyze_media(media, prompt)
84
+ ```
85
+
86
+ | Field | Required | Description |
87
+ | -------- | -------- | --------------------------------------------------------------------------------------- |
88
+ | `media` | yes | Absolute local MP4/MOV/MP3 path, or public HTTPS video URL without embedded credentials |
89
+ | `prompt` | yes | The Agent's question; 1–8000 characters after trimming surrounding whitespace |
90
+
91
+ The prompt's internal whitespace and wording are retained. The server adds only a fixed protocol note requesting text and honest uncertainty. It does not prescribe a timeline, shot list, critique or output structure. Empty and oversized questions are rejected.
92
+
93
+ ```text
94
+ 请用 MCP 分析 C:\Videos\clip.mov,描述画面和内嵌声音;无法确认时明确说明。
95
+ ```
96
+
97
+ Results contain one text answer plus `structuredContent.answer`, `media`, `request`, optional `usage`, and `limitations`. Media facts are populated only when established locally. A matching answer does not prove the model's internal modality path or the accuracy of its timestamps.
98
+
99
+ The Agent should read only one copy of the answer. For long results, save the tool result and read it in chunks if the host display truncates it; do not call paid analysis again merely because display output was truncated.
100
+
101
+ There is no evidence correction request. Before any answer text arrives, transient failures may receive one bounded retry (configurable). Known content-inspection rejections are not automatically retried. Only one analysis is active per process.
102
+
103
+ Internal upload URLs, credential-shaped tokens and local absolute paths are redacted. The exact local input path is hidden in the answer, while public HTTP(S) links are preserved. Generic redaction of other paths has declared limits; see [`SECURITY.md`](docs/SECURITY.md).
104
+
105
+ ## Environment
106
+
107
+ | Variable | Purpose |
108
+ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
109
+ | `DASHSCOPE_API_KEY` | Required Bailian Key. Resolved from explicit config, MCP/process env, user config, then supported Windows user env lookup. Never printed. |
110
+ | `MEDIA_ALLOWED_ROOTS` | Existing absolute allowed folders; separate with the platform delimiter (`;` on Windows). Unset means all local files are refused. |
111
+ | `MEDIA_ALLOW_ANY_LOCAL_FILE` | Default `off`. `on` permits any absolute local media path the Agent names, including files outside allowed roots. Enable only if you accept mistaken or prompt-induced uploads. |
112
+ | `MEDIA_MAX_LOCAL_MEDIA_MB` | Local size limit; default and maximum 1024 MiB. |
113
+ | `QWEN_MODEL` | Model id; default `qwen3.8-omni-flash`. Video/audio support varies by model. `qwen3.5-omni-plus` remains selectable. Not a Tool field. |
114
+ | `QWEN_CONFIG_FILE` | Optional config file path (same format as `--config`); the server does not implicitly load the cwd `.env`. |
115
+ | `QWEN_MCP_SERVER_NAME` | MCP server name; does not change Tool name. |
116
+ | `QWEN_UPLOAD_CACHE` | Default on; `off` disables temporary-upload reuse. |
117
+ | `DASHSCOPE_BASE_URL` | Default Beijing OpenAI-compatible endpoint. Key region must match. |
118
+ | `DASHSCOPE_UPLOAD_URL` | Default Beijing temporary upload endpoint. |
119
+
120
+ Use `media-analysis-mcp --doctor --json` to inspect configuration, variable sources and obsolete names without printing the Key. A missing Key permits MCP initialization but analysis returns `CONFIG_MISSING`.
121
+
122
+ ## Limits
123
+
124
+ - Local inputs: **MP4/MOV** (ISO BMFF) with H.264/H.265 video and AAC audio; **MP3** with real MPEG Layer III frames. Renaming an MP4 to `.mp3` does not convert it and is refused.
125
+ - An audio-only MP4 has been refused by the provider through the video path. Export genuine MP3 for standalone audio. The server never transcodes, extracts frames, decodes audio or installs FFmpeg.
126
+ - Known local durations greater than **3600 seconds** are refused; exactly 3600 is allowed. Unknown duration is allowed. MP3 duration is reported only from declared Xing/Info/VBRI frame counts, with no bitrate estimate.
127
+ - HTTPS inputs are fetched by the provider; the server does not download or probe their content/duration. Remote `.mp3` URLs are refused.
128
+ - Media understanding is sampled, not frame-accurate or a verified transcript. Use a 5–30 second excerpt for precise timing questions; shot counts, dialogue and timestamps still require independent checking.
129
+ - Cache entries include file identity, a size/head/tail fingerprint, model, upload endpoint and credential fingerprint, and expire after about 47 hours. Changing only the middle while preserving identity/head/tail can reuse stale media; this is an accepted limitation. Disable cache if that matters.
130
+ - Images, downloads from video sites, automatic conversion, batch analysis, cross-call conversation memory and deterministic loudness/edit checks are outside scope.
131
+
132
+ ## Migrating from 0.6.1
133
+
134
+ This is a breaking release. Existing configs do not silently gain MP3 access.
135
+
136
+ | Published 0.6.1 | 1.0.0 |
137
+ | ------------------------------------ | ------------------------------------------------ |
138
+ | `analyze_video(video, question?)` | `analyze_media(media, prompt)`; prompt required |
139
+ | Optional question and server outline | Agent supplies the question; no business outline |
140
+ | Evidence JSON and correction request | Plain answer with media facts and limitations |
141
+ | Local MP4/MOV | Local MP4/MOV/MP3 |
142
+ | `QWEN_ALLOWED_ROOTS` | `MEDIA_ALLOWED_ROOTS` |
143
+ | `QWEN_ALLOW_ANY_LOCAL_VIDEO` | `MEDIA_ALLOW_ANY_LOCAL_FILE` (off by default) |
144
+ | `QWEN_MAX_LOCAL_VIDEO_MB` | `MEDIA_MAX_LOCAL_MEDIA_MB` |
145
+ | `QWEN_AUDIO_SILENCE_CHECK` | Removed |
146
+ | Node `>=22` declaration | Formal support only for Node 24.x |
147
+
148
+ Old authorization variables grant nothing. Reload the MCP server after changing the version and variables. Full contract: [`API_CONTRACT.md`](docs/API_CONTRACT.md).
149
+
150
+ ## Development and release
151
+
152
+ ```powershell
153
+ npm install
154
+ npm run typecheck
155
+ npm run lint
156
+ npm run format:check
157
+ npm test
158
+ npm run coverage
159
+ npm run build
160
+ npm run test:pack-install
161
+ ```
162
+
163
+ Default tests are mocked and free. Live tests require explicit authorization and a real Key; they are not part of the default suite.
164
+
165
+ After remote Node 24 CI passes, pushing an authorized version tag matching `package.json` and `src/version.ts` runs [`release.yml`](.github/workflows/release.yml): package/install probes, npm Trusted Publishing (OIDC), then GitHub Release. No `NPM_TOKEN` is used. See [ADR 0014](docs/decisions/0014-npm-trusted-publishing.md).
166
+
167
+ GitHub fallback:
168
+
169
+ ```text
170
+ npx -y --allow-git=all github:JaylanJerry/media-analysis-mcp#v2.0.0
171
+ ```
172
+
173
+ npm 12 requires `--allow-git=all`. Use the `v2.0.0` fallback only after its release is confirmed.
174
+
175
+ ## Name migration
176
+
177
+ The product and MCP server are **Media Analysis MCP**. Version 2.0.0 renames the npm package and CLI to `media-analysis-mcp`, the example Host key to `media_analysis_mcp`, and the repository to `JaylanJerry/media-analysis-mcp`. `analyze_media(media, prompt)` and the media protocol remain unchanged.
178
+
179
+ Existing `analyze-video-mcp@1.0.0` installations are not automatically upgraded to a different package. Replace the command/package and Host key using the examples above, remove the duplicate old Host entry, and restart the Host. This repository does not edit your installed Host configuration or global packages automatically.
180
+
181
+ The default user config path becomes `~/.media-analysis-mcp/config.env`; the default upload cache directory becomes `media-analysis-mcp`. Move your own config file or use `--config` explicitly. Old implicit paths are not read, and the old upload cache is not automatically migrated; the first analysis after migration may upload again and incur normal provider charges. `DASHSCOPE_*`, `QWEN_MODEL`, `QWEN_UPLOAD_CACHE`, and `MEDIA_*` environment variables keep their existing meanings.
182
+
183
+ Historical tags, published versions, ADRs, and archived reports keep their original names. See [ADR 0026](docs/decisions/0026-media-analysis-mcp-rename.md).
184
+
185
+ ## Repository maintenance
186
+
187
+ Start with [DEVELOPMENT_HANDOFF.md](DEVELOPMENT_HANDOFF.md), the [current documentation index](docs/README.md) and [maintenance status](tasks/README.md). Historical specifications and completed work are indexed under [docs/archive](docs/archive/README.md) and [tasks/archive](tasks/archive/README.md). Archived plans describe their original dates and are not current implementation instructions.
188
+
189
+ ## License
190
+
191
+ MIT. Specialized fork of [`sommio/qwen-omni-mcp`](https://github.com/sommio/qwen-omni-mcp).
@@ -0,0 +1,320 @@
1
+ import { MediaError } from "./errors.js";
2
+ import { mapProviderError, safeProviderRequestId } from "./provider-error.js";
3
+ import { SseParser } from "./sse.js";
4
+ /**
5
+ * The only server-side wording added to a request. It is protocol-level — ask for
6
+ * text, and do not fabricate — and deliberately carries no analysis outline: the
7
+ * Agent owns the question, and the server must not expand or replace it.
8
+ */
9
+ export const PROTOCOL_NOTE = "只输出文本回答。只写你实际看到或听到的内容;看不清、听不清或无法判断的地方请直接说明,不要编造或补全没有观察到的内容。";
10
+ export function contentBlock(media) {
11
+ if (media.format === "mp3") {
12
+ return { type: "input_audio", input_audio: { data: media.url, format: "mp3" } };
13
+ }
14
+ return { type: "video_url", video_url: { url: media.url } };
15
+ }
16
+ const MAX_RETRY_AFTER_MS = 30_000;
17
+ function chatCompletionsUrl(cfg) {
18
+ const base = cfg.baseUrl.replace(/\/+$/, "");
19
+ return `${base}/chat/completions`;
20
+ }
21
+ export function buildMediaPayload(cfg, media, request) {
22
+ return {
23
+ model: cfg.model,
24
+ messages: [
25
+ {
26
+ role: "system",
27
+ content: [{ type: "text", text: PROTOCOL_NOTE }],
28
+ },
29
+ {
30
+ role: "user",
31
+ content: [contentBlock(media), { type: "text", text: request.prompt }],
32
+ },
33
+ ],
34
+ modalities: ["text"],
35
+ stream: true,
36
+ stream_options: { include_usage: true },
37
+ };
38
+ }
39
+ function analysisHeaders(cfg, media) {
40
+ const headers = {
41
+ "Content-Type": "application/json",
42
+ Authorization: `Bearer ${cfg.apiKey}`,
43
+ Accept: "text/event-stream",
44
+ };
45
+ if (media.requiresOssResolve || media.url.startsWith("oss://")) {
46
+ headers["X-DashScope-OssResourceResolve"] = "enable";
47
+ }
48
+ return headers;
49
+ }
50
+ function errorForStatus(status, requestId) {
51
+ if (status === 401 || status === 403) {
52
+ return new MediaError({
53
+ code: "PROVIDER_UNAUTHORIZED",
54
+ stage: "analyzing",
55
+ httpStatus: status,
56
+ ...(requestId === undefined ? {} : { requestId }),
57
+ });
58
+ }
59
+ if (status === 429) {
60
+ return new MediaError({
61
+ code: "PROVIDER_RATE_LIMITED",
62
+ stage: "analyzing",
63
+ httpStatus: status,
64
+ ...(requestId === undefined ? {} : { requestId }),
65
+ });
66
+ }
67
+ if (status === 502 || status === 503) {
68
+ return new MediaError({
69
+ code: "PROVIDER_UNAVAILABLE",
70
+ stage: "analyzing",
71
+ httpStatus: status,
72
+ ...(requestId === undefined ? {} : { requestId }),
73
+ });
74
+ }
75
+ return new MediaError({
76
+ code: "MEDIA_ANALYSIS_FAILED",
77
+ stage: "analyzing",
78
+ httpStatus: status,
79
+ retryable: false,
80
+ ...(requestId === undefined ? {} : { requestId }),
81
+ });
82
+ }
83
+ /** Error bodies can be arbitrary; read only enough to identify a provider error. */
84
+ async function readErrorBody(body) {
85
+ if (body === null)
86
+ return undefined;
87
+ const reader = body.getReader();
88
+ const chunks = [];
89
+ let bytes = 0;
90
+ try {
91
+ for (;;) {
92
+ const part = await reader.read();
93
+ if (part.done)
94
+ break;
95
+ bytes += part.value.byteLength;
96
+ if (bytes > 64 * 1024) {
97
+ await reader.cancel().catch(() => undefined);
98
+ return undefined;
99
+ }
100
+ chunks.push(Buffer.from(part.value));
101
+ }
102
+ }
103
+ catch {
104
+ return undefined;
105
+ }
106
+ finally {
107
+ reader.releaseLock();
108
+ }
109
+ try {
110
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
111
+ }
112
+ catch {
113
+ return undefined;
114
+ }
115
+ }
116
+ function canAutoRetry(err, sawText, retriesLeft) {
117
+ if (sawText || retriesLeft <= 0) {
118
+ return false;
119
+ }
120
+ return err.code === "PROVIDER_RATE_LIMITED" || err.code === "PROVIDER_UNAVAILABLE";
121
+ }
122
+ async function sleep(ms, signal) {
123
+ if (ms <= 0) {
124
+ return;
125
+ }
126
+ await new Promise((resolve, reject) => {
127
+ const timer = setTimeout(() => {
128
+ signal.removeEventListener("abort", onAbort);
129
+ resolve();
130
+ }, ms);
131
+ const onAbort = () => {
132
+ clearTimeout(timer);
133
+ reject(new MediaError({ code: "PROVIDER_TIMEOUT", stage: "analyzing" }));
134
+ };
135
+ if (signal.aborted) {
136
+ onAbort();
137
+ return;
138
+ }
139
+ signal.addEventListener("abort", onAbort, { once: true });
140
+ });
141
+ }
142
+ function retryWaitMs(res) {
143
+ if (res === undefined) {
144
+ return 250 + Math.floor(Math.random() * 250);
145
+ }
146
+ const raw = res.headers.get("Retry-After");
147
+ if (raw === null || raw.trim() === "") {
148
+ return 250 + Math.floor(Math.random() * 250);
149
+ }
150
+ const seconds = Number(raw);
151
+ if (!Number.isFinite(seconds) || seconds < 0) {
152
+ return 250 + Math.floor(Math.random() * 250);
153
+ }
154
+ return Math.min(seconds * 1000, MAX_RETRY_AFTER_MS);
155
+ }
156
+ async function* bodyChunks(body) {
157
+ const reader = body.getReader();
158
+ try {
159
+ for (;;) {
160
+ const read = await reader.read();
161
+ if (read.done) {
162
+ return;
163
+ }
164
+ yield read.value;
165
+ }
166
+ }
167
+ finally {
168
+ reader.releaseLock();
169
+ }
170
+ }
171
+ async function analyzeMediaOnce(cfg, media, request, signal) {
172
+ let res;
173
+ try {
174
+ res = await fetch(chatCompletionsUrl(cfg), {
175
+ method: "POST",
176
+ headers: analysisHeaders(cfg, media),
177
+ body: JSON.stringify(buildMediaPayload(cfg, media, request)),
178
+ signal,
179
+ });
180
+ }
181
+ catch (err) {
182
+ if (err instanceof Error && err.name === "AbortError") {
183
+ return {
184
+ ok: false,
185
+ error: new MediaError({ code: "PROVIDER_TIMEOUT", stage: "analyzing" }),
186
+ retryAfterMs: 0,
187
+ sawText: false,
188
+ };
189
+ }
190
+ return {
191
+ ok: false,
192
+ error: new MediaError({ code: "PROVIDER_UNAVAILABLE", stage: "analyzing" }),
193
+ retryAfterMs: retryWaitMs(undefined),
194
+ sawText: false,
195
+ };
196
+ }
197
+ if (!res.ok) {
198
+ const wait = retryWaitMs(res);
199
+ const requestId = safeProviderRequestId(res.headers.get("x-request-id")) ??
200
+ safeProviderRequestId(res.headers.get("x-dashscope-request-id"));
201
+ const providerError = mapProviderError(await readErrorBody(res.body), {
202
+ ...(requestId === undefined ? {} : { requestId }),
203
+ httpStatus: res.status,
204
+ });
205
+ const error = providerError?.code === "PROVIDER_CONTENT_REJECTED" ||
206
+ providerError?.code === "MEDIA_MODEL_UNSUPPORTED"
207
+ ? providerError
208
+ : errorForStatus(res.status, requestId);
209
+ return { ok: false, error, retryAfterMs: wait, sawText: false };
210
+ }
211
+ if (res.body === null) {
212
+ return {
213
+ ok: false,
214
+ error: new MediaError({ code: "PROVIDER_RESPONSE_INVALID", stage: "analyzing" }),
215
+ retryAfterMs: 0,
216
+ sawText: false,
217
+ };
218
+ }
219
+ const requestId = safeProviderRequestId(res.headers.get("x-request-id")) ??
220
+ safeProviderRequestId(res.headers.get("x-dashscope-request-id"));
221
+ const parser = new SseParser(requestId);
222
+ try {
223
+ for await (const chunk of bodyChunks(res.body)) {
224
+ parser.push(chunk);
225
+ }
226
+ const aggregated = parser.finish();
227
+ if (aggregated.finishReason === "length") {
228
+ throw new MediaError({
229
+ code: "PROVIDER_RESPONSE_INVALID",
230
+ stage: "analyzing",
231
+ ...(aggregated.requestId === undefined ? {} : { requestId: aggregated.requestId }),
232
+ diagnostic: {
233
+ parse_reason: "truncated",
234
+ received_sse_events: aggregated.receivedEvents,
235
+ ...(aggregated.usage?.prompt_tokens === undefined
236
+ ? {}
237
+ : { prompt_tokens: aggregated.usage.prompt_tokens }),
238
+ ...(aggregated.usage?.completion_tokens === undefined
239
+ ? {}
240
+ : { completion_tokens: aggregated.usage.completion_tokens }),
241
+ ...(aggregated.usage?.total_tokens === undefined
242
+ ? {}
243
+ : { total_tokens: aggregated.usage.total_tokens }),
244
+ },
245
+ });
246
+ }
247
+ return {
248
+ ok: true,
249
+ result: {
250
+ answer: aggregated.text,
251
+ requestId: aggregated.requestId,
252
+ receivedEvents: aggregated.receivedEvents,
253
+ finishReason: aggregated.finishReason,
254
+ usage: aggregated.usage,
255
+ },
256
+ };
257
+ }
258
+ catch (err) {
259
+ const error = err instanceof MediaError
260
+ ? err
261
+ : new MediaError({
262
+ code: "PROVIDER_RESPONSE_INVALID",
263
+ stage: "analyzing",
264
+ diagnostic: { received_sse_events: parser.eventCount },
265
+ });
266
+ return { ok: false, error, retryAfterMs: 0, sawText: parser.sawText };
267
+ }
268
+ }
269
+ export async function analyzeMedia(cfg, media, request, external) {
270
+ const controller = new AbortController();
271
+ const abortFromExternal = () => {
272
+ controller.abort();
273
+ };
274
+ if (external?.aborted) {
275
+ throw new MediaError({ code: "MEDIA_ANALYSIS_CANCELLED", stage: "aborted" });
276
+ }
277
+ external?.addEventListener("abort", abortFromExternal, { once: true });
278
+ const timer = setTimeout(() => {
279
+ controller.abort();
280
+ }, cfg.analysisTimeoutMs);
281
+ let retriesLeft = cfg.analysisRetries;
282
+ try {
283
+ for (;;) {
284
+ const attempt = await analyzeMediaOnce(cfg, media, request, controller.signal);
285
+ if (attempt.ok) {
286
+ return attempt.result;
287
+ }
288
+ if (external?.aborted) {
289
+ throw new MediaError({ code: "MEDIA_ANALYSIS_CANCELLED", stage: "aborted" });
290
+ }
291
+ if (!canAutoRetry(attempt.error, attempt.sawText, retriesLeft)) {
292
+ throw attempt.error;
293
+ }
294
+ retriesLeft -= 1;
295
+ try {
296
+ await sleep(attempt.retryAfterMs, controller.signal);
297
+ }
298
+ catch (err) {
299
+ // The backoff wait is a cancellation point: an abort here is the caller
300
+ // cancelling, not the provider timing out.
301
+ if (external?.aborted) {
302
+ throw new MediaError({ code: "MEDIA_ANALYSIS_CANCELLED", stage: "aborted" });
303
+ }
304
+ throw err;
305
+ }
306
+ }
307
+ }
308
+ finally {
309
+ clearTimeout(timer);
310
+ external?.removeEventListener("abort", abortFromExternal);
311
+ }
312
+ }
313
+ export function createMediaAnalyzer(cfg) {
314
+ return {
315
+ analyze(media, request, signal) {
316
+ return analyzeMedia(cfg, media, request, signal);
317
+ },
318
+ };
319
+ }
320
+ //# sourceMappingURL=bailian.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bailian.js","sourceRoot":"","sources":["../src/bailian.ts"],"names":[],"mappings":"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"}
package/dist/bytes.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Positioned read that refuses to run past the probe budget, so a malformed or
3
+ * hostile header can never turn a bounded probe into a full-file scan.
4
+ */
5
+ export async function readBounded(reader, position, length, budget, limitBytes) {
6
+ if (length <= 0 ||
7
+ !Number.isSafeInteger(position) ||
8
+ position < 0 ||
9
+ budget.bytesRead + length > limitBytes) {
10
+ return undefined;
11
+ }
12
+ const buffer = Buffer.alloc(length);
13
+ const { bytesRead } = await reader.read(buffer, 0, length, position);
14
+ budget.bytesRead += bytesRead;
15
+ if (bytesRead < length) {
16
+ return undefined;
17
+ }
18
+ return buffer;
19
+ }
20
+ //# sourceMappingURL=bytes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bytes.js","sourceRoot":"","sources":["../src/bytes.ts"],"names":[],"mappings":"AAcA;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAwB,EACxB,QAAgB,EAChB,MAAc,EACd,MAAkB,EAClB,UAAkB;IAElB,IACE,MAAM,IAAI,CAAC;QACX,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC;QAC/B,QAAQ,GAAG,CAAC;QACZ,MAAM,CAAC,SAAS,GAAG,MAAM,GAAG,UAAU,EACtC,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IACrE,MAAM,CAAC,SAAS,IAAI,SAAS,CAAC;IAC9B,IAAI,SAAS,GAAG,MAAM,EAAE,CAAC;QACvB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
package/dist/cli.js ADDED
@@ -0,0 +1,56 @@
1
+ import { resolve } from "node:path";
2
+ import { setCliConfigPath } from "./config-lookup.js";
3
+ import { ConfigError } from "./errors.js";
4
+ export function parseCliArgs(argv) {
5
+ let version = false;
6
+ let doctor = false;
7
+ let json = false;
8
+ let configPath;
9
+ for (let i = 0; i < argv.length; i += 1) {
10
+ const arg = argv[i];
11
+ if (arg === undefined) {
12
+ continue;
13
+ }
14
+ if (arg === "--version" || arg === "-v") {
15
+ version = true;
16
+ continue;
17
+ }
18
+ if (arg === "--doctor") {
19
+ doctor = true;
20
+ continue;
21
+ }
22
+ if (arg === "--json") {
23
+ json = true;
24
+ continue;
25
+ }
26
+ if (arg === "--config") {
27
+ const next = argv[i + 1];
28
+ if (next === undefined || next.startsWith("-")) {
29
+ throw new ConfigError("--config requires a file path", {
30
+ suggestion: "请提供 --config 指向的 env 文件路径",
31
+ });
32
+ }
33
+ configPath = next;
34
+ i += 1;
35
+ continue;
36
+ }
37
+ if (arg.startsWith("--config=")) {
38
+ const value = arg.slice("--config=".length);
39
+ if (value.trim() === "") {
40
+ throw new ConfigError("--config requires a file path", {
41
+ suggestion: "请提供 --config 指向的 env 文件路径",
42
+ });
43
+ }
44
+ configPath = value;
45
+ }
46
+ }
47
+ return { version, doctor, json, configPath };
48
+ }
49
+ export function applyCliArgs(argv) {
50
+ const args = parseCliArgs(argv);
51
+ if (args.configPath !== undefined) {
52
+ setCliConfigPath(resolve(args.configPath));
53
+ }
54
+ return args;
55
+ }
56
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAS1C,MAAM,UAAU,YAAY,CAAC,IAAc;IACzC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,IAAI,UAA8B,CAAC;IACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,SAAS;QACX,CAAC;QACD,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACxC,OAAO,GAAG,IAAI,CAAC;YACf,SAAS;QACX,CAAC;QACD,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;YACvB,MAAM,GAAG,IAAI,CAAC;YACd,SAAS;QACX,CAAC;QACD,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;YACrB,IAAI,GAAG,IAAI,CAAC;YACZ,SAAS;QACX,CAAC;QACD,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACzB,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC/C,MAAM,IAAI,WAAW,CAAC,+BAA+B,EAAE;oBACrD,UAAU,EAAE,2BAA2B;iBACxC,CAAC,CAAC;YACL,CAAC;YACD,UAAU,GAAG,IAAI,CAAC;YAClB,CAAC,IAAI,CAAC,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAChC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YAC5C,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBACxB,MAAM,IAAI,WAAW,CAAC,+BAA+B,EAAE;oBACrD,UAAU,EAAE,2BAA2B;iBACxC,CAAC,CAAC;YACL,CAAC;YACD,UAAU,GAAG,KAAK,CAAC;QACrB,CAAC;IACH,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAc;IACzC,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QAClC,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}