# @seacloudai/sdk

[English](README.md) | 简体中文

SeaCloud 生成 API 的 TypeScript SDK。

SeaCloud SDK 是专为 Agent 和开发者设计的多模态任务执行 SDK。只需一个 SeaCloud API Key，即可通过 `chat.send` 发起 LLM 对话，通过 `run` / `runSync` 执行图像、视频、音频、3D 等 queue 生成任务；支持模型搜索、合约查询、任务执行和结果追踪；并可通过 SkillHub 帮助发现和管理面向创意工作流的专业技能。

中文操作手册见 [`docs/SDK_OPERATION_MANUAL.zh-CN.md`](docs/SDK_OPERATION_MANUAL.zh-CN.md)。

## 中文说明

这个 SDK 是纯代码 SDK。它只暴露类型化的 JavaScript/TypeScript 方法，返回数据对象，并且不会自行从环境变量读取 `apiKey`。调用方必须显式传入 `apiKey`。

## 仓库环境

SDK 内置生产服务端点，应用代码只需要显式传入 `apiKey` 就可以创建 client。运行时服务端点由 env 文件生成到 `src/core/default-base-urls.ts`；SDK 永远不会从环境变量读取 API key。

本源码仓库里，公开默认构建、测试、CI、发布前检查和 npm 包发布也会加载已提交的 `.env.prod`，确保入库默认端点清晰、可测试。

维护者如果需要本地私有域名，可以自行创建未入库的 `.env.local`，保持与 `.env.prod` 同一组 key，然后显式运行本地命令：

```bash
npm run build:local
npm run typecheck:local
npm run test:local
```

`.env.local` 已被 `.gitignore` 忽略，不能提交到远程仓库。

## 安装

要求 Node.js 18.17 或更高版本。

```bash
pnpm add @seacloudai/sdk
```

```bash
npm install @seacloudai/sdk
```

`@seacloudai/sdk` 同时发布 ESM 和 CommonJS 入口：

```ts
import { SeaCloud, getSeaCloudDocs } from "@seacloudai/sdk";
```

```js
const { SeaCloud, getSeaCloudDocs } = require("@seacloudai/sdk");
```

## 快速开始

```ts
import { SeaCloud, getSeaCloudDocs } from "@seacloudai/sdk";

const docs = getSeaCloudDocs();
console.log(docs.methods.map((method) => method.name));

const client = new SeaCloud({
  apiKey: "sk-...",
  timeout: 600_000,
});

const text = await client.chat.send("gpt-5.5", [
  { role: "user", content: "你好" },
]);

const task = await client.run("gpt_image_2", {
  prompt: "Generate cute cats programming",
  n: 1,
  size: "1024x1024",
  output_format: "png",
  quality: "auto",
  moderation: "auto",
});

console.log(task.id, task.statusUrl, task.responseUrl);

const result = await client.runSync("gpt_image_2", {
  prompt: "Generate cute cats programming",
  n: 1,
  size: "1024x1024",
  output_format: "png",
  quality: "auto",
  moderation: "auto",
});

console.log(result.output?.urls[0]);
```

## CommonJS 快速开始

```js
const { SeaCloud, getSeaCloudDocs, isSeaCloudError } = require("@seacloudai/sdk");

async function main() {
  const docs = getSeaCloudDocs({ locale: "zh-CN" });
  console.log(docs.methods.map((method) => method.name));

  const client = new SeaCloud({
    apiKey: process.env.SEACLOUD_API_KEY,
    timeout: 600_000,
  });

  try {
    const result = await client.runSync("gpt_image_2", {
      prompt: "一只橘猫坐在阳光窗边，写实照片风格",
      n: 1,
      size: "1024x1024",
      output_format: "png",
      quality: "auto",
      moderation: "auto",
    });

    console.log(result.output?.urls[0]);
  } catch (error) {
    if (isSeaCloudError(error)) {
      console.error(error.type, error.message, error.hint);
      return;
    }
    throw error;
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
```

## API 一览

| 模块 | 方法 | 用途 |
| --- | --- | --- |
| 文档 | `getSeaCloudDocs()` | 离线读取 SDK 操作手册和 Agent / Skill 用法 |
| 客户端 | `new SeaCloud(options)` | 使用显式 `apiKey` 创建客户端 |
| 对话 | `client.chat.send(model, messages, options?)` | 发送文本对话请求 |
| 生成 | `client.run(modelId, params, options?)` | 创建 queue 任务并立即返回任务句柄 |
| 生成 | `client.runSync(modelId, params, options?)` | 创建 queue 任务并等待最终 response |
| 模型 | `client.models.list(options?)` | 列出可用模型 |
| 模型 | `client.models.getSpec(modelId)` | 读取用于生成规划的模型契约 |
| 任务 | `client.tasks.get(taskId, { endpoint })` | 查询 queue 任务状态 |
| 任务 | `client.tasks.getResponse(taskId, { responseUrl })` | 读取 queue 任务最终 response |
| 技能 | `client.skills.find(query, options?)` | 搜索 SkillHub 技能 |
| 技能 | `client.skills.list(options?)` | 列出 SkillHub 技能 |
| 版本 | `client.version()` | 读取 SDK 版本 |

## 客户端选项

```ts
const client = new SeaCloud({
  apiKey: "sk-...",
  timeout: 600_000,
  fetch: globalThis.fetch,
});
```

`apiKey` 是必填项，没有默认值。`timeout` 可以在客户端级别设置，也可以在单个方法调用中覆盖。`fetch` 可选，适合前端代理、测试或特殊运行时注入自定义请求实现。

## 离线文档

`getSeaCloudDocs()` 不需要初始化 client，不需要 `apiKey`，也不会发起网络请求。它适合 agent、大模型工具调用和测试页直接读取 SDK 的公开用法。

```ts
const docs = getSeaCloudDocs();

console.log(docs.operationManual.content);
console.log(docs.agentSkillUsage.content);
console.table(docs.methods);
```

## 生成参数

生成方法使用固定语法：

```ts
client.run(modelId, params, options?)
client.runSync(modelId, params, options?)
```

- `modelId` 是第一个位置参数；SDK 默认会读取模型契约、解析 queue submit endpoint，`auto` 模式下契约不可用时才回退到 `/model/v1/queue/{modelId}`。
- `params` 是第二个位置参数，必须是对象；SDK 会把 JavaScript 对象作为 queue JSON 提交，不接受字符串形式的命令参数。
- `options.timeout` 可覆盖本次请求或同步等待的超时时间。
- `options.dryRun` 会预览 queue 请求，不提交任务、不轮询、不读取 response。
- `options.contract` 可选，默认是 `"auto"`；`"auto"` 会尝试读取契约并可回退到 raw queue；`"strict"` 要求契约必须可读取且能规划请求；`"off"` 明确关闭契约读取，直接提交原始 `params`。
- 没有 `onProgress`。当前后端生命周期接口不提供可信进度，SDK 不伪造进度。

SDK 默认在 `run` 和 `runSync` 内部读取模型契约，用它规划 protocol、body mode、queue submit endpoint 和契约 headers。如果 `input_schema.required` 是非空数组，SDK 只检查这些顶层必传字段是否存在；它不校验类型、format、范围、默认值或互斥规则，这些模型业务规则由接口负责。`auto` 模式下契约读取失败时会回退到 `/model/v1/queue/{modelId}` 的 raw JSON 直提；只有 `contract: "off"` 才完全关闭契约读取。SDK 接收 JavaScript 对象，不接受字符串形式的命令参数，也不会自动上传本地文件。

生成调用链如下：

| 步骤 | SDK 行为 |
| --- | --- |
| 1 | `client.models.getSpec(modelId)`、`client.run()`、`client.runSync()` 在启用契约模式时读取 `GET https://sea-cloud-admin-web.real-cloud.seaart.ai/api/v1/skill/model-contracts/{modelId}`。 |
| 2 | 如果 `input_schema.required` 非空，SDK 检查这些顶层字段是否存在；否则不拦截。 |
| 3 | `protocol=queue` 且 `body_mode=raw_json` 时，SDK 解析 `spec.endpoints.submit.path`，拼到 queue base URL 后提交调用方传入的 JSON body。 |
| 4 | `client.runSync()` 轮询 `statusUrl` 并读取 `responseUrl`；`client.run()` 只返回任务句柄，调用方可继续用 `tasks.get()` / `tasks.getResponse()`。 |

```mermaid
flowchart TD
  User["用户调用 run/runSync"] --> ValidateInput["校验 modelId 和 params 对象"]
  ValidateInput --> ContractMode{"contract auto/strict?"}
  ContractMode -- 默认 auto/strict --> ReadContract["读取模型契约"]
  ContractMode -- off --> PlanRaw["使用原始 queue 请求"]
  ReadContract --> PlanRequest["规划协议、bodyMode、queue endpoint 和 headers"]
  ReadContract -- auto 不可用 --> PlanRaw
  PlanRaw --> DryRun{"dryRun?"}
  PlanRequest --> DryRun{"dryRun?"}
  DryRun -- 是 --> Preview["返回规划后的请求预览"]
  DryRun -- 否 --> Submit["POST 提交 queue 请求"]
  Submit --> Task["返回任务句柄"]
  Task --> Sync{"runSync?"}
  Sync -- 否 --> Done["调用方用 tasks.get/getResponse 手动查询"]
  Sync -- 是 --> Poll["轮询 statusUrl"]
  Poll --> Response["GET responseUrl"]
  Response --> Result["返回标准化 RunSyncResult"]
```

## run: 创建异步任务

`run` 默认读取模型契约并提交规划后的 queue 请求：

```text
POST https://cloud.seaart.ai/model/v1/queue/{modelId}
```

```ts
const task = await client.run("gpt_image_2", {
  prompt: "Generate cute cats programming",
  n: 1,
  size: "1024x1024",
  output_format: "png",
  quality: "auto",
  moderation: "auto",
});
```

返回任务句柄：

```ts
interface RunTask {
  id: string;
  status: string;
  model: string;
  statusUrl?: string;
  responseUrl?: string;
  cancelUrl?: string;
  queuePosition?: number;
}
```

异步模式不返回 `output`，因为任务创建时还没有最终生成结果。

## runSync: 等待最终结果

`runSync` 会创建任务、轮询 `statusUrl`，完成后请求 `responseUrl`，并把真实 response 包装成稳定结构。

```ts
const result = await client.runSync("gpt_image_2", {
  prompt: "a cute orange tabby cat sitting by a sunny window, detailed fur, soft natural light, photorealistic",
  n: 1,
  size: "1024x1024",
  output_format: "png",
  quality: "auto",
  moderation: "auto",
});
```

返回：

```ts
interface RunSyncResult {
  id: string;
  status: "completed" | "failed";
  output?: {
    urls: string[];
    raw: unknown;
  };
  model: string;
  error?: {
    message: string;
    code?: string | number;
    raw?: unknown;
  };
}
```

映射规则：

- `id`：优先取 `request_id`，其次取 `id`，兜底使用创建任务返回的 ID。
- `status`：成功态统一为 `completed`，失败态统一为 `failed`。
- `output.urls`：从真实 response 中递归提取所有 `url` 字段。
- `output.raw`：保留真实 response，避免丢失模型特有字段。
- `error`：任务失败时从真实 response 的错误信息映射；失败结果不会伪造 `output`。

## 预检运行

`run` 和 `runSync` 都支持 `dryRun: true`。

```ts
const preview = await client.run("gpt_image_2", {
  prompt: "a cinematic photo of a cat astronaut",
  n: 1,
  size: "1024x1024",
  output_format: "png",
  quality: "auto",
  moderation: "auto",
}, {
  dryRun: true,
});

console.log(preview.endpoint);
console.log(preview.request);
console.log(preview.protocol);
console.log(preview.validation);
```

预检运行会返回 modelId、protocol、bodyMode、endpoint、method、脱敏后的 headers、规划后的请求体和 validation 结果，但不会提交生成任务。默认会尝试读取模型契约来规划 endpoint 和 headers；如果 `input_schema.required` 非空，只检查这些顶层必传字段是否存在，不补默认值，也不本地校验类型、format、范围或互斥规则。传 `contract: "off"` 时才只返回原始 queue 请求体。

## run 后手动查询

查询任务状态：

```ts
const status = await client.tasks.get(task.id, {
  endpoint: "gpt_image_2",
  statusUrl: task.statusUrl,
});
```

当 `status.status === "completed"` 时，使用 `responseUrl` 获取最终结果：

```ts
const result = await client.tasks.getResponse(task.id, {
  endpoint: "gpt_image_2",
  responseUrl: status.responseUrl ?? task.responseUrl,
});

console.log(result.output?.urls);
console.log(result.output?.raw);
```

`getResponse` 返回结构与 `runSync` 一致：成功时返回 `output.urls` 和 `output.raw`，失败时返回 `error` 且不伪造 `output`。

也可以直接请求生命周期 URL：

```bash
curl -sS "$STATUS_URL" \
  -H "Authorization: Bearer $SEACLOUD_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Source: sdk" \
  -H "X-SDK-Language: javascript"
```

获取最终 response：

```bash
curl -sS "$RESPONSE_URL" \
  -H "Authorization: Bearer $SEACLOUD_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Source: sdk" \
  -H "X-SDK-Language: javascript"
```

如果不用返回 URL，也可以按下面规则拼接：

```text
https://cloud.seaart.ai/model/v1/queue/{modelId}/requests/{request_id}/status
https://cloud.seaart.ai/model/v1/queue/{modelId}/requests/{request_id}/response
```

## 服务端点

下面的生产端点与 SDK 内置默认值和当前 `.env.prod` 保持一致：

| 能力 | 端点 |
| --- | --- |
| 对话 | `POST https://cloud.seaart.ai/llm/chat/completions` |
| 模型列表 | `GET https://sea-cloud-admin-web.real-cloud.seaart.ai/api/v1/skill/models` |
| queue 提交 | `POST https://cloud.seaart.ai/model/v1/queue/{modelId}` |
| queue 状态 | `GET https://cloud.seaart.ai/model/v1/queue/{modelId}/requests/{request_id}/status` |
| queue 结果 | `GET https://cloud.seaart.ai/model/v1/queue/{modelId}/requests/{request_id}/response` |
| 模型契约 | `GET https://sea-cloud-admin-web.real-cloud.seaart.ai/api/v1/skill/model-contracts/{modelId}` |
| SkillHub 搜索 | `GET https://skill-hub.vtrix.ai/api/v1/search` |
| SkillHub 列表 | `GET https://skill-hub.vtrix.ai/api/v1/skills` |

## 架构

```text
src/
  client.ts              SeaCloud 门面和资源装配
  index.ts               对外导出
  core/                  运行时配置、HTTP 客户端、错误、版本
  domain/                模型别名、响应映射、任务结果归一化
  resources/             每个 SDK 功能区一个聚合资源类
  types/                 按功能分组的公开类型
  utils/                 小型共享对象和 URL 工具
skills/                  面向 SDK 使用的项目本地智能体技能
```

```mermaid
flowchart TD
  User["应用 / 智能体代码"] --> Facade["SeaCloud 门面<br/>src/client.ts"]
  Facade --> Chat["ChatResource"]
  Facade --> Models["ModelsResource"]
  Facade --> Tasks["TasksResource"]
  Facade --> Skills["SkillsResource"]
  Facade --> Generation["GenerationResource<br/>run / runSync"]

  Chat --> Core["core/http-client.ts"]
  Models --> Core
  Tasks --> Core
  Skills --> Core
  Generation --> Core

  Generation --> Domain["domain rules<br/>validation / mappers"]
  Models --> Domain
  Tasks --> Domain
  Skills --> Domain
  Chat --> Stream["流解析器<br/>SSE chunks"]

  Core --> Fetch["fetch + timeout + headers"]
  Fetch --> SeaCloudAPI["SeaCloud HTTP API"]
```

## 智能体技能

根目录的 `AGENTS.md` 是项目智能体入口。

这个仓库在 `skills/` 下提供项目本地技能，方便 AI 智能体快速加载具体方法的使用指南：

- `skills/seacloud-sdk/SKILL.md`
- `skills/seacloud-chat-send/SKILL.md`
- `skills/seacloud-run/SKILL.md`
- `skills/seacloud-models-list/SKILL.md`
- `skills/seacloud-models-get-spec/SKILL.md`
- `skills/seacloud-tasks-get/SKILL.md`
- `skills/seacloud-skills-find/SKILL.md`
- `skills/seacloud-skills-list/SKILL.md`
- `skills/seacloud-version/SKILL.md`
- `skills/seacloud-errors/SKILL.md`

## 开发

```bash
npm install
npm run typecheck
npm test
```

`npm run build` 会先清理 `dist` 再编译，避免残留旧的生成文件。
