# Lumyren Node.js 客户端与 CLI

将 `lumyren-client.mjs`、`lumyren-cli.mjs` 保存在同一目录。使用 Node.js 24，在服务端环境中配置 `LUMYREN_API_KEY` 和 `LUMYREN_ACCOUNT_ID`。Key 由工作空间签发，不放在网页代码、URL、Git 仓库或聊天记录中。

不需要安装 npm 依赖。`node lumyren-cli.mjs --help` 查看命令。当前工具连接 Lumyren 的报价与任务接口，支持标准对话参数和已声明的 Gemini 原生参数；官方 OpenAI Chat 和 Gemini SDK 的直连接口、请求意图与参数范围见 `standard-guide.md`。配套 stdio MCP 的配置与工具见 `mcp-guide.md`，运行文件为 `lumyren-mcp.mjs`。

## 一次生成

创建 `input.json`，内容使用模型详情的真实参数：

```json
{"model":"z-image/turbo","prompt":"A blue ceramic cup on a wooden table","size":"1024*1024"}
```

```sh
node lumyren-cli.mjs prepare input.json intent.json
```

`prepare` 只计算报价，输出计费方式、金额和有效期。固定价格显示 `billing.kind: fixed` 与 `saleUsd`；语言任务显示 `token_usage`、`reservationUsd` 与 `actual_usage`。私有的 `intent.json` 保存当前空间、报价 ID、计费说明和本次操作的 UUID，不保存 API Key。文件已存在时不会覆盖。报价有效期 5 分钟；普通媒体估算价不能当作固定价提交。新版保存 version 2 意图，仍可提交原 version 1 意图。

确认显示的费用后执行：

```sh
node lumyren-cli.mjs submit intent.json
```

保存返回的任务 ID。如果请求超时、电脑中断或响应丢失，仍使用**同一个 intent.json** 再次执行 `submit`。服务器返回原任务，不另建付费意图。不要修改 UUID、删除旧意图文件或自动循环创建新报价。公共生成未开放或空间无余额时，接口会返回相应状态。

```sh
node lumyren-cli.mjs job JOB_ID
node lumyren-cli.mjs watch JOB_ID
node lumyren-cli.mjs assets JOB_ID
node lumyren-cli.mjs download JOB_ID ASSET_ID result.png
```

把 `JOB_ID`、`ASSET_ID` 换成接口返回的实际 UUID，并按素材的 `extension` 选择文件名。下载只在长度和 SHA-256 都匹配后发布完整文件，使用私有文件权限；已有目标不会被覆盖，失败清理临时文件。

`watch` 输出逐行 JSON，支持 UTF-8 分包、多行 data 和心跳。处理一条带 id 的事件后保存该 id，断线后运行 `node lumyren-cli.mjs watch JOB_ID LAST_EVENT_ID` 续读。终态发送 `job.complete`；未收到终态就断线会返回 `stream_interrupted`。它只连接已有任务，不触发生成。

## 在应用中使用

```js
import {LumyrenClient} from './lumyren-client.mjs'
const client = new LumyrenClient({
  apiKey: process.env.LUMYREN_API_KEY,
  accountId: process.env.LUMYREN_ACCOUNT_ID,
})
const balance = await client.balance()
const task = await client.job(existingJobId)
const {items} = await client.assets(task.id)
await client.downloadToFile(items[0], './result.png')
```

可用方法：`models`、`model`、`searchDocumentation`、`quote`、`quoteById`、`createJob`、`job`、`jobs`、`cancelJob`、`assets`、`balance`、`ledger`、`modelUsage`、`modelCosts`、`upload`、`uploadFile`、`downloadToFile`、`events`、`languageEvents`。`createJob` 需要调用方事先持久保存的 `quoteId` 和 `idempotencyKey`，不会为你生成新意图或自动重试付费请求。

上传使用 `client.upload(originalBytes, uploadUuid)`、`client.uploadFile(filePath, uploadUuid)`，或 `node lumyren-cli.mjs upload input.png UPLOAD_UUID`。调用方保存并复用同一 UUID 和同一文件；上传返回的 `referenceUrl` 用于模型输入。单文件最大256 MiB；本地路径方式先检查普通文件与大小，拒绝目录、符号链接和读取期间发生变化的文件，再发送原始字节。上传权限、媒体类型和当前空间开放状态仍由服务器核对。

## 搜索接入文档

`node lumyren-cli.mjs docs "账单"`，或 `client.searchDocumentation('账单', {offset:0,limit:10})` 可以查询本站已发布的说明，返回摘要、网页入口与 Markdown 原文链接。每页最多20条，使用 `nextOffset` 继续查询。文档请求不发送 API Key，不创建任务；内容不代表当前账户已开放所有模型。完整基础接口说明见 `api-guide.md`。

## 语言对话与文本流

标准对话的 `input.json` 示例：

```json
{"model":"deepseek-ai/deepseek-v3.2","messages":[{"role":"user","content":"用一句话介绍这款产品"}],"max_tokens":64,"stream":true}
```

读取模型详情；只有返回 `nativeParameterSchema` 的型号支持以下显式 Gemini 原生输入：

```json
{"model":"google/gemini-3.1-flash-lite","protocol":"gemini.generate","contents":[{"role":"user","parts":[{"text":"描述这件商品"}]}],"generationConfig":{"maxOutputTokens":64},"stream":true}
```

图片或视频先上传到同一个空间，再把返回的 `referenceUrl` 放入 `parts` 中的 `fileData.fileUri`，并填入实际 `mimeType`。不要把本地路径或另一个空间的素材地址直接作为输入。各型号支持的媒体、大小和参数以详情契约为准。系统指令、多轮 `contents`、工具参数和签名由原生 JSON 保留；客户端不执行模型返回的函数。

这两种输入都先运行 `prepare`，再显式 `submit`。语言报价中的金额是**预留上限，不是最终价格**：目前按型号公布的最大上下文和请求的最大输出计算上限；最终按有效费率与实际用量结算，释放未消费预留。报价和费率与任务一起冻结。账户未配置有效费率或未开放该模型时，服务端会拒绝报价；客户端不会改用媒体报价或自行定价。

```sh
node lumyren-cli.mjs stream JOB_ID
node lumyren-cli.mjs stream JOB_ID LAST_LANGUAGE_EVENT_ID
node lumyren-cli.mjs watch JOB_ID
node lumyren-cli.mjs job JOB_ID
```

`stream` / `client.languageEvents(jobId, {after})` 读取一条文本 SSE 连接，`language.delta` 的 `data.choices` 提供候选序号与增量文本。只公开经过验证的文本；完整结构结果在任务私有 JSON 文件中读取。文本流需要 `assets:read`；查询任务状态和最终扣费还需要 `jobs:read`。处理事件后保存该通道的 id；文本流与 `watch` 状态流的游标互不通用。

`language.end` 表示本次文本输出结束，任务可能还在保存结果或核对费用；它不等于生成成功或结算完成。用 `job` 或 `watch` 确认最终状态、实际扣费与作品文件。遇到连接中断，使用同一任务和原游标续读，不创建新任务。私有文本和原生 JSON 的保留期同样为 24 小时。

## 错误与保留期

`LumyrenError` 包含 `code`、`status`、`requestId` 和适用的 `retryAfter`。429 遵循等待时间；401/403 先核对 Key 与权限。`submission_unknown` / `billing_review` 保留原任务，由运营核对。客户端不会因这些状态另行生成。

作品私有保存 24 小时，到期后不可下载。工具不延长保存期。HTTP 仅用于明确的本地回环测试；正式地址使用 HTTPS，请求不跟随重定向，不把 Key 发送到另一个地址。

## 模型用量和实际扣费

以下只读接口需要 `billing:read`，仅访问 Key 所属空间：

- `GET /api/v1/accounts/{accountId}/model-usage`
- `GET /api/v1/accounts/{accountId}/model-costs`

```js
const query = {
  start_date: '2026-09-01',
  end_date: '2026-09-14', // 不包含这一天
  group_by: ['model', 'api_key'],
  limit: 100,
};
const usage = await client.modelUsage(query);
let costs = await client.modelCosts(query);
while (costs.has_more) {
  costs = await client.modelCosts({ ...query, page: costs.next_page });
}
```

日期按 UTC 统计，每次最多180天。费用来自实际扣账分录，保留美元六位小数精度；不把报价、预留、充值、退款或供应商成本列为客户生成支出。用量按任务完成日期统计，只包含已结束任务；词元仅包含已结算语言任务。图片/视频的产出数不从请求数量推测。

CLI 使用相同查询结构：将日期和筛选保存为 JSON 文件后运行 `node lumyren-cli.mjs costs query.json` 或 `node lumyren-cli.mjs usage query.json`。这些命令只读取统计，不创建任务或扣费。

## 保存与执行多节点工作流

`client.workflowTemplates()` 读取计划，`client.createWorkflow(definition, savedUuid)` 保存经过验证的节点图，`client.workflows(before)` 与 `client.workflow(runId)` 查询记录。创建计划免费；先保存创建 UUID，断线复用相同计划和 UUID。

CLI 提供完整的持久意图流程：

```sh
node lumyren-cli.mjs workflow-templates
node lumyren-cli.mjs workflow-create plan.json creation.json
node lumyren-cli.mjs workflow-prepare RUN_ID image image-intent.json
# 查看 image-intent.json 中的固定价格，再明确执行
node lumyren-cli.mjs workflow-submit image-intent.json
node lumyren-cli.mjs workflow RUN_ID
```

`plan.json` 使用 API 参考中的 `definition` 结构。`workflow-create` 在请求前以私有权限保存计划和 UUID；重复相同命令返回原计划。`workflow-prepare` 只报价，将节点尝试、价格及提交 UUID 保存到新私有文件。现有意图文件不会覆盖，也不会因此重新报价。`workflow-submit` 先核对当前尝试和原报价金额，再执行该节点；断线始终复用同一文件。

SDK 对应方法为 `quoteWorkflowNode(runId,nodeKey,input?)` 和 `confirmWorkflowNode(runId,nodeKey,attemptId,savedUuid)`。报价响应中的 `attemptId` 与提交 UUID 必须持久保存；确认方法是付费操作。成功、执行中或提交未知的节点不能重新报价。失败或取消节点可用 `workflow-prepare RUN_ID NODE new-intent.json adjusted-input.json` 调整参数、取得新价格，再单独确认。前序成功节点不重做，素材到期时间不延长；下游节点仍需另外报价和确认。

`group_by` 可选模型、类型、密钥或模型与密钥。HTTP 数组参数重复使用 `group_by[]`、`model_ids[]`、`model_types[]`、`api_key_ids[]`；每页最多1000个分组，`has_more` 为 true 时用原 `next_page` 和相同筛选继续。金额、次数和词元数返回字符串，金额使用 `BigInt(amount_micros)` 精确累加。
