# Lumyren 标准客户端接入

OpenAI Chat、Responses、Anthropic Messages 与 Gemini 原生客户端可以连接 Lumyren。使用 Lumyren Key、目录中的完整模型标识和当前空间余额。直接生成会预留余额并创建任务；仅查询模型不会扣费。模型出现在目录中不代表已经开放生成，开放状态和参数以模型详情及实际接口返回为准。

## 接口与权限

| 接口 | 用途 |
| --- | --- |
| `GET /v1/models` | 当前 Key 可查看的语言模型，OpenAI 列表格式 |
| `POST /v1/chat/completions` | OpenAI Chat，JSON 或 SSE |
| `POST /v1/responses` | Responses 的文本、函数与结构化结果，JSON 或 SSE |
| `POST /v1/messages` | Messages 的文本、函数与结构化结果，JSON 或 SSE |
| `POST /v1/models/{model}:generateContent` | Gemini 原生 JSON |
| `POST /v1/models/{model}:streamGenerateContent?alt=sse` | Gemini 原生 SSE |

Gemini 的 `/v1beta/` 是同一接口的兼容地址；同一 UUID 和相同参数在两个版本地址间恢复仍对应原任务。模型标识可包含 `/`，例如 `google/gemini-3.1-flash-lite`。

模型列表需要 `models:read`。直接生成并读取结果需要 `jobs:write`、`jobs:read` 和 `assets:read`。Key 的空间、模型限制、每日限额和有效期照常生效；不能在请求中指定其他空间。使用 `Authorization: Bearer ...`；Gemini 也支持 `x-goog-api-key`，二者只选一个。不要把 Key 放在 URL、客户端网页或代码仓库中。

Messages 也支持 `x-api-key`，与 Bearer 二选一，并需要 `anthropic-version: 2023-06-01`；官方 Anthropic SDK 自动发送这两个请求头。

每次新操作**先持久保存 UUID 和完整请求参数**，再通过 `Idempotency-Key` 请求头发送。服务中断或 SDK 重试时必须保持二者相同。改变参数却复用 UUID 返回 `409 idempotency_conflict`。请求 UUID 与 MCP 消息 id、对话 id、任务 id 是不同概念。

## OpenAI SDK 示例

在服务端使用 `openai`。Base URL 为 `https://lumyren.com/v1`。先创建一次私有意图文件，文件已存在时不要覆盖：

```js
import {randomUUID} from 'node:crypto'
import {writeFile} from 'node:fs/promises'

await writeFile('chat-intent.json', JSON.stringify({
  idempotencyKey: randomUUID(),
  input: {
    model: 'deepseek-ai/deepseek-v3.2',
    messages: [{role: 'user', content: '用一句话介绍海洋'}],
    max_tokens: 64,
    stream: false,
  },
}), {flag: 'wx', mode: 0o600})
```

另一个脚本读取这个文件并发送。需要恢复时运行同一发送脚本：

```js
import OpenAI from 'openai'
import {readFile} from 'node:fs/promises'

const intent = JSON.parse(await readFile('chat-intent.json', 'utf8'))
const client = new OpenAI({
  apiKey: process.env.LUMYREN_API_KEY,
  baseURL: 'https://lumyren.com/v1',
  maxRetries: 0,
})
const result = await client.chat.completions.create(intent.input, {
  headers: {'Idempotency-Key': intent.idempotencyKey},
})
console.log(result.choices[0].message.content)
```

新操作可在自己的新意图文件中设置 `stream: true`，随后用 `for await` 读取 SDK 流。已发送意图的 `stream` 参数不能在恢复时改变。完整结果保留工具调用等原生结构；Lumyren 不执行生成内容中的函数。

Chat 支持模型已声明的工具、工具结果回传、JSON 格式与思考选项。`max_completion_tokens` 可代替 `max_tokens`，同时提供时数值必须一致。工具调用的完整参数、角色、调用 ID 和工具返回内容都应加入下一轮消息；每一轮新的生成使用一个新的持久意图。

## Responses SDK 示例

仍使用上面的 OpenAI 客户端。先把本次 UUID 和以下参数保存到私有的 `responses-intent.json`，发送脚本读取原文件：

```js
const intent = JSON.parse(await readFile('responses-intent.json', 'utf8'))
// intent.input 示例：
// {model: 'deepseek-ai/deepseek-v3.2', input: '用一句话介绍海洋',
//  max_output_tokens: 64, store: false, stream: true}
const stream = await client.responses.create(intent.input, {
  headers: {'Idempotency-Key': intent.idempotencyKey},
})
for await (const event of stream) {
  if (event.type === 'response.output_text.delta') process.stdout.write(event.delta)
  if (event.type === 'response.failed' || event.type === 'error') {
    throw new Error('请查询原任务状态')
  }
}
```

非流式请求返回 `output` 与实际 token 用量；官方 SDK 提供 `output_text`。支持完整文本历史、`instructions`、函数调用和结果回传、`text.format`、模型已声明的 `reasoning.effort`。工具定义采用 Responses 的平铺格式，例如 `{type: 'function', name: 'weather', parameters: {...}}`。工具结果用 `{type: 'function_call_output', call_id: '原调用 ID', output: '结果文本'}` 加入下一轮完整历史。

此接口使用完整历史，不保存服务端对话状态。`store`、`background` 请省略或设为 `false`；不使用 `previous_response_id`、`conversation` 或 retrieve 接口续聊。托管工具、加密思考、自动截断、图片和音频输入尚未接入此协议；不支持的参数会在报价前拒绝。模型公开的原始思考文字保留为 reasoning content，不伪装成思考摘要。

## Anthropic SDK 示例

在服务端安装 `@anthropic-ai/sdk`，Base URL 是 `https://lumyren.com`。把 UUID 和请求参数先保存到私有意图文件：

```js
import Anthropic from '@anthropic-ai/sdk'
import {readFile} from 'node:fs/promises'

const intent = JSON.parse(await readFile('messages-intent.json', 'utf8'))
// intent.input 示例：
// {model: 'deepseek-ai/deepseek-v3.2', max_tokens: 64,
//  messages: [{role: 'user', content: '用一句话介绍海洋'}]}
const client = new Anthropic({
  apiKey: process.env.LUMYREN_API_KEY,
  baseURL: 'https://lumyren.com',
  maxRetries: 0,
})
const stream = client.messages.stream(intent.input, {
  headers: {'Idempotency-Key': intent.idempotencyKey},
})
stream.on('text', text => process.stdout.write(text))
const result = await stream.finalMessage()
```

支持文本、`system`、函数工具、`tool_use` 与 `tool_result`、模型思考选项和 `output_config.format`。使用当前目录中的模型标识；这是 Messages 格式兼容，不代表任意 Anthropic 官方型号都已上架。`max_tokens` 必填，完整历史由你的应用保存并在下一轮传回。

`tool_choice.any` 对应必须调用工具；停止序列取前四项。`cache_control` 在兼容转换中忽略，不能借此购买或保证缓存，费用仍按供应商实际返回用量核算。工具错误标志会连同原内容传给模型。公开思考文字可返回无签名 thinking 块；图片、签名或加密思考、托管工具、beta 特性与 `count_tokens` 尚不支持。

## Gemini SDK 示例

在服务端使用 `@google/genai`。先将本次操作的 `idempotencyKey` 与 `request` 保存在私有的 `gemini-intent.json` 文件中，例如：

```json
{
  "idempotencyKey": "替换为本次操作已保存的 UUID",
  "request": {
    "model": "google/gemini-3.1-flash-lite",
    "contents": "用一句话介绍海洋",
    "config": {"maxOutputTokens": 64}
  }
}
```

```js
import {GoogleGenAI} from '@google/genai'
import {readFile} from 'node:fs/promises'

const intent = JSON.parse(await readFile('gemini-intent.json', 'utf8'))
const client = new GoogleGenAI({
  apiKey: process.env.LUMYREN_API_KEY,
  httpOptions: {baseUrl: 'https://lumyren.com', apiVersion: 'v1beta'},
})
const chunks = await client.models.generateContentStream({
  ...intent.request,
  config: {
    ...intent.request.config,
    httpOptions: {headers: {'Idempotency-Key': intent.idempotencyKey}},
  },
})
for await (const chunk of chunks) process.stdout.write(chunk.text ?? '')
```

非流式新操作使用 `generateContent`。原生输入与输出支持已声明的文本、图片、视频、函数数据和结构化结果；需要继续函数对话时，保留原生响应中的签名及完整结构。不会自动购买缓存或执行供应商托管工具。

## 费用与恢复

余额预留使用已确认有效费率以及模型公布的上下文和输出上限，可能明显高于短请求的最终费用。最终按实际用量计算有效 Atlas 成本，再乘 1.5 并向上取整至微美元，多余预留释放。要先查看预留上限，可使用 `/api/v1/accounts/{accountId}/quotes` 或配套 CLI 的 `prepare`；之后用该报价和原意图创建任务。

成功创建任务后，响应头返回 `x-lumyren-job-id` 与 `x-lumyren-quote-id`。用任务接口查询最终状态和扣费；JSON 成功响应或流式正常结束只在结果保存、费用结算后返回。Chat 的 `[DONE]`、Responses 的 `response.completed`、Messages 的 `message_stop` 表示正常终态。输出触及上限时 Responses 返回 `response.incomplete`，Messages 返回 `stop_reason: max_tokens`，实际生成仍按用量结算。Gemini 正常关闭流表示结束；异常时连接会中断，SDK 会抛出连接错误。应沿原任务查询状态，不能把已收到的文字当作费用已结算。

HTTP 等待最多 180 秒。超时、断线、客户端取消只停止本次等待，已经创建的任务可能继续执行。保留原 UUID 与完整参数，再次发送可找回原任务。Chat 与 Gemini 的 SSE 手动恢复可发送已处理的 `Last-Event-ID`，只返回之后的帧。Responses 与 Messages 需要完整事件重放，请省略 `Last-Event-ID`；恢复时仍使用原 UUID 和完整参数，不会重新生成。整段重放时由你的应用更新原任务显示，避免重复追加文字。

`submission_unknown`、`billing_review` 或 `request_in_progress` 不代表没有产生费用。不要更换 UUID 重做生成，应沿原任务核对。原报价过期但任务已创建时仍返回原任务；报价过期且没有创建任务时返回 `quote_expired`，重新评估后才创建新的操作意图。

文本与文件保存 24 小时，过期返回 `410`。每个用户最多同时等待 8 条标准客户端连接。JSON 请求最大 16 KiB；大文件应先使用 Lumyren 私有上传接口，再按模型参数引用。不能把文件内容塞进超限 JSON。

当前直连接口覆盖 OpenAI Chat、Responses 与 Messages 文本兼容，以及已声明的 Gemini 原生型号；其他协议按模型实际支持情况逐步接入。音频生成使用普通异步任务 API，不提供 `/v1/audio/speech` 或 `/v1/audio/transcriptions`。媒体生成仍按各模型实际开放状态执行。
