# @seacloudai/sdk

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

TypeScript SDK for SeaCloud AI generation APIs.

SeaCloud SDK is a multimodal task execution SDK designed specifically for agents and developers. With one SeaCloud API Key, it provides LLM chat through `chat.send` and image, video, audio, 3D, and other queue generation through `run` / `runSync`; supports model search, contract queries, task execution, and result tracking; and helps discover and manage professional skills for creative workflows through SkillHub.

Chinese operation manual: [`docs/SDK_OPERATION_MANUAL.zh-CN.md`](docs/SDK_OPERATION_MANUAL.zh-CN.md).

## Install

Requires Node.js 18.17 or later.

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

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

`@seacloudai/sdk` publishes both ESM and CommonJS entrypoints:

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

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

## Server-Side Usage

Use this SDK from trusted server-side JavaScript or TypeScript code. Do not put the API key in browser code. For browser apps, call your own backend route and let that route use `@seacloudai/sdk`.

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

const docs = getSeaCloudDocs();
console.log(docs.quickStart.content);

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

try {
  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);
} catch (error) {
  if (isSeaCloudError(error)) {
    console.error(error.type, error.message, error.hint);
  }
}
```

## Overview

`@seacloudai/sdk` is a pure code SDK. It exposes typed JavaScript/TypeScript methods, returns data objects, and never reads `apiKey` from environment variables by itself. Callers must pass `apiKey` explicitly.

## Service Endpoints and Environment Overrides

The SDK ships with production service endpoints, so application code can create a client with only an explicit `apiKey`. Runtime service endpoints are generated from env files into `src/core/default-base-urls.ts`; API keys are never read from env by the SDK.

In this source repository, public build, test, CI, release, and npm package checks also load the committed `.env.prod` file so the checked defaults stay explicit and testable.

Maintainers who need local-only endpoints can create an ignored `.env.local` file with the same keys and run the explicit local commands:

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

`.env.local` is intentionally not tracked and must not be pushed.

## Quick Start

```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: "Hello" },
]);

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 Quick Start

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

async function main() {
  const docs = getSeaCloudDocs();
  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: "a photorealistic orange tabby cat by a sunny window",
      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 Overview

| Module | Method | Purpose |
| --- | --- | --- |
| Docs | `getSeaCloudDocs()` | Read the offline SDK operation manual and agent / skill usage guide |
| Client | `new SeaCloud(options)` | Create a client with an explicit `apiKey` |
| Chat | `client.chat.send(model, messages, options?)` | Send a text chat request |
| Generation | `client.run(modelId, params, options?)` | Create a queue task and return a task handle immediately |
| Generation | `client.runSync(modelId, params, options?)` | Create a queue task and wait for the final response |
| Models | `client.models.list(options?)` | List available models |
| Models | `client.models.getSpec(modelId)` | Read the model contract used for generation planning |
| Tasks | `client.tasks.get(taskId, { endpoint })` | Read queue task status |
| Tasks | `client.tasks.getResponse(taskId, { responseUrl })` | Read the final queue task response |
| Skills | `client.skills.find(query, options?)` | Search SkillHub skills |
| Skills | `client.skills.list(options?)` | List SkillHub skills |
| Version | `client.version()` | Read the SDK version |

## Client Options

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

`apiKey` is required and has no default value. `timeout` can be set at the client level or overridden for a single method call. `fetch` is optional and is useful for frontend proxies, tests, or custom runtimes.

## Offline Docs

`getSeaCloudDocs()` does not initialize a client, does not require `apiKey`, and does not make network requests. It is suitable for agents, LLM tool calls, and test pages that need to inspect public SDK usage.

```ts
const docs = getSeaCloudDocs();
const zhDocs = getSeaCloudDocs({ locale: "zh-CN" });

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

## Generation Parameters

Generation methods use fixed syntax:

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

- `modelId` is the first positional argument. By default, the SDK reads the model contract, resolves the queue submit endpoint, and falls back to `/model/v1/queue/{modelId}` when the contract is unavailable in auto mode.
- `params` is the second positional argument and must be an object. The SDK sends JavaScript objects as queue JSON; string flag syntax is not accepted.
- `options.timeout` overrides the timeout for this request or synchronous wait.
- `options.dryRun` plans and previews the request without submitting a task, polling, or reading the final response.
- `options.contract` is optional and defaults to `"auto"`. `"auto"` tries to read the contract and can fall back to raw queue passthrough. `"strict"` requires the contract to be readable and plannable. `"off"` explicitly disables contract reads and submits `params` as raw queue JSON.
- There is no `onProgress`. The current backend lifecycle APIs do not provide trustworthy progress, so the SDK does not invent progress events.

By default, `run` and `runSync` read the model contract to resolve protocol, body mode, queue submit endpoint, and contract headers. If `input_schema.required` is a non-empty array, the SDK only checks that those top-level required fields are present before submitting. It does not validate types, formats, ranges, defaults, or mutual-exclusion rules; those model-specific rules are handled by the API. If the contract cannot be read in `"auto"` mode, the SDK falls back to raw JSON submission at `/model/v1/queue/{modelId}`. The SDK accepts typed JavaScript objects, does not accept shell-style parameter strings, and does not auto-upload local files.

Contract-aware generation flow:

| Step | SDK behavior |
| --- | --- |
| 1 | `client.models.getSpec(modelId)`, `client.run()`, and `client.runSync()` read `GET https://sea-cloud-admin-web.real-cloud.seaart.ai/api/v1/skill/model-contracts/{modelId}` when contract mode is enabled. |
| 2 | If `input_schema.required` is non-empty, the SDK checks those top-level fields are present; otherwise it does not block. |
| 3 | For `protocol=queue` and `body_mode=raw_json`, the SDK resolves `spec.endpoints.submit.path` against the queue base URL and submits the caller-provided JSON body. |
| 4 | `client.runSync()` polls `statusUrl` and reads `responseUrl`; `client.run()` returns the task handle for manual `tasks.get()` / `tasks.getResponse()`. |

```mermaid
flowchart TD
  User["User calls run/runSync"] --> ValidateInput["Validate modelId and params object"]
  ValidateInput --> ContractMode{"Use contract mode?"}
  ContractMode -- default auto/strict --> ReadContract["Read model contract"]
  ContractMode -- off --> PlanRaw["Use raw queue request"]
  ReadContract --> PlanContract["Plan protocol, bodyMode, queue endpoint, headers"]
  ReadContract -- auto unavailable --> PlanRaw
  PlanRaw --> DryRun{"dryRun?"}
  PlanContract --> DryRun
  DryRun -- yes --> Preview["Return planned request preview"]
  DryRun -- no --> Submit["POST queue submit request"]
  Submit --> Task["Return task handle"]
  Task --> Sync{"runSync?"}
  Sync -- no --> Done["Caller polls manually with tasks.get/getResponse"]
  Sync -- yes --> Poll["Poll statusUrl"]
  Poll --> Response["GET responseUrl"]
  Response --> Result["Return normalized RunSyncResult"]
```

## run: Create an Async Task

`run` reads the model contract in default contract mode, then submits the planned queue request:

```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",
});
```

Task handle:

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

Async mode does not return `output`, because the final generation result does not exist when the task is created.

## runSync: Wait for Final Result

`runSync` creates a task, polls `statusUrl`, requests `responseUrl` after completion, and wraps the real response in a stable structure.

```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",
});
```

Return type:

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

Mapping rules:

- `id`: uses `request_id` first, then `id`, then falls back to the created task ID.
- `status`: successful states normalize to `completed`; failed states normalize to `failed`.
- `output.urls`: recursively extracts every `url` field from the real response.
- `output.raw`: preserves the real response to avoid losing model-specific fields.
- `error`: maps error information from the real response when a task fails. Failed results do not invent `output`.

## Dry Run

Both `run` and `runSync` support `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);
```

Dry run returns modelId, protocol, bodyMode, endpoint, method, redacted headers, the planned request body, and validation results. It does not submit a generation task.

## Manual Lookup After run

Read task status:

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

When `status.status === "completed"`, use `responseUrl` to fetch the final result:

```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` returns the same structure as `runSync`: successful results contain `output.urls` and `output.raw`; failed results contain `error` and do not invent `output`.

You can also request lifecycle URLs directly:

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

Fetch the final 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"
```

If returned URLs are unavailable, construct them with:

```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
```

## Service Endpoints

The production endpoints below match the SDK built-in defaults and the current `.env.prod` values:

| Capability | Endpoint |
| --- | --- |
| Chat | `POST https://cloud.seaart.ai/llm/chat/completions` |
| Model list | `GET https://sea-cloud-admin-web.real-cloud.seaart.ai/api/v1/skill/models` |
| Queue submit | `POST https://cloud.seaart.ai/model/v1/queue/{modelId}` |
| Queue status | `GET https://cloud.seaart.ai/model/v1/queue/{modelId}/requests/{request_id}/status` |
| Queue response | `GET https://cloud.seaart.ai/model/v1/queue/{modelId}/requests/{request_id}/response` |
| Model contract | `GET https://sea-cloud-admin-web.real-cloud.seaart.ai/api/v1/skill/model-contracts/{modelId}` |
| SkillHub search | `GET https://skill-hub.vtrix.ai/api/v1/search` |
| SkillHub list | `GET https://skill-hub.vtrix.ai/api/v1/skills` |

## Architecture

```text
src/
  client.ts              SeaCloud facade and resource assembly
  index.ts               Public exports
  core/                  Runtime config, HTTP client, errors, version
  domain/                Business rules: model aliases, contract planning, response mappers, result normalization
  resources/             One aggregate resource class per SDK capability area
  types/                 Public types grouped by capability
  utils/                 Small shared utilities: object helpers, URL builders, LRU cache
skills/                  Project-local agent skills for SDK usage
```

```mermaid
flowchart TD
  User["Application / agent code"] --> Facade["SeaCloud facade<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["stream parser<br/>SSE chunks"]

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

## Agent Skills

Root `AGENTS.md` is the project agent entrypoint.

This repository provides project-local skills under `skills/` so AI agents can quickly load method-specific usage guides:

- `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`

## Development

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

`npm run build` cleans `dist` before compiling to avoid stale generated files.
