Nodaro ドキュメント
ドキュメントノードリファレンスモデルAI エージェント(MCP)開発者向けセルフホスティングリサーチ
TypeScript SDK

キャラクター

client.characters を使って、TypeScript からキャラクターを作成し、ポートレート候補を生成して 1 つを承認し、表情、ポーズ、モーションクリップを追加します。

client.characters は、キャラクタースタジオが行うことをすべてスクリプトで再現します。キャラクターの作成と編集、ポートレート候補の生成、候補の 1 つをキャラクターの顔として承認すること、そして表情、ポーズ、ライティング、アングル、モーションクリップの追加です。キャラクターは、ポートレート、アセットのコレクション、リファレンス写真、そして人物を説明するキャプションを保持するため、以降のすべての画像と動画で同じ人物を表示できます。これらのメソッドは、キャラクター REST API を呼び出します。エディターでの操作については、キャラクタースタジオを参照してください。

メソッド

メソッド内容
list(params?)自分のキャラクターを、ページ単位で一覧表示します
get(id)1 人のキャラクターを、進行中のジョブとともに読み取ります
create(input)、update(id, input)、upsert(input)キャラクターを作成または変更します
delete(id)キャラクターをアーカイブします
restore(id)アーカイブされたキャラクターを元に戻します
duplicate(id, input?)キャラクターを複製します
usage(id)キャラクターを使うワークフローを数えます
generate(input)ポートレート候補を生成します
generateAsset(input)表情、ポーズ、ライティング、アングルのバリエーションを生成します
generateMotion(input)ポートレートをアニメーション化してモーションクリップにします
approvePortrait(id, candidateJobId)候補をキャラクターのポートレートにします
recaption(id)キャラクターの説明を書き直します

キャラクターを最初から最後まで作成する

通常は、キャラクターを作成し、ポートレート候補を生成し、その 1 つを承認してから、その上にバリエーションを追加していきます。

import { createClient, StaticTokenAuth } from "@nodaro/sdk"

const client = createClient({
  baseUrl: "https://app.nodaro.ai",
  auth: new StaticTokenAuth(process.env.NODARO_TOKEN!),
})

async function waitForJob(jobId: string) {
  for (;;) {
    const { data } = await client.jobs.getStatus(jobId)
    if (data.status === "completed" || data.status === "failed" || data.status === "cancelled") return data
    await new Promise((resolve) => setTimeout(resolve, 3_000))
  }
}

// 1. Create the character
const { id: characterId } = await client.characters.create({
  nodeId: "scripted",
  name: "Kira",
  description: "Young protagonist with auburn hair",
  style: "realistic",
  seedPrompt: "Kira portrait, warm natural lighting",
})

// 2. Generate 4 portrait candidates
const { jobIds } = await client.characters.generate({
  name: "Kira",
  seedPrompt: "Kira portrait, warm natural lighting",
  count: 4,
  attachToCharacterId: characterId,
})
const results = await Promise.all(jobIds.map(waitForJob))
const firstDone = jobIds[results.findIndex((r) => r.status === "completed")]

// 3. Approve one candidate as the portrait
const { portraitUrl, canonicalDescription } = await client.characters.approvePortrait(characterId, firstDone)

// 4. Add a smile
await client.characters.generateAsset({
  name: "Kira",
  assetType: "expressions",
  variant: "smile",
  attachToCharacterId: characterId,
  attachToColumn: "expressions",
  attachName: "smile",
})

// 5. Animate the portrait
await client.characters.generateMotion({
  name: "Kira",
  motionPrompt: "Slow head turn to the left, soft smile",
  provider: "kling",
  attachToCharacterId: characterId,
  attachName: "head turn",
})

ジョブが完了したら、get() でキャラクターをもう一度読み取ります。ポートレート、smile の表情、モーションクリップが揃っています。生成先にはキャラクターアセット(Character Asset)ノードを接続するか、プロンプトで @ を使ってメンションします。キャラクターの一貫性を参照してください。

client.characters

list(params?)

自分のキャラクターを、新しい順に一覧表示します。デフォルトでは、有効なキャラクターだけを返します。

list(params?: { projectId?: string; archived?: boolean; limit?: number; cursor?: string }): Promise<{
  characters: Character[]
  nextCursor: string | null
}>

Prop

Type

1 回の呼び出しで返るのは最大 limit 件のキャラクターまでなので、1 回の呼び出しで、すべてのキャラクターが返るとは限りません。nextCursor が null になるまでページを取得してください。

import type { Character } from "@nodaro/sdk"

const all: Character[] = []
let cursor: string | undefined
do {
  const page = await client.characters.list({ projectId, cursor })
  all.push(...page.characters)
  cursor = page.nextCursor ?? undefined
} while (cursor)

nextCursor は、そのままの形で渡してください。保存したり解析したりしないでください。不正な形式のカーソルは、最初のページからやり直すのではなく、validation_error で失敗します。

get(id)

1 人のキャラクターを、スタジオが再読み込み後に使う 3 つの追加の一覧とともに読み取ります。pendingJobs(まだ生成中のバリエーション)、portraitCandidates(現在のポートレート生成の候補と、その進行状況)、previousCandidates(それより前の候補)です。

get(id: string): Promise<CharacterDetail>

Prop

Type

const character = await client.characters.get(characterId)
console.log(character.sourceImageUrl, character.expressions, character.motions)

アーカイブされたキャラクターも、ID を指定すれば引き続き返されるため、それを参照するワークフローのノードは読み込み続けられます。Character は、sourceImageUrl にポートレートを持ち、6 つのアセットコレクション(expressions、poses、motions、angles、bodyAngles、lightingVariations。いずれも { name, url } のリストです)、boards、voice、personality、canonicalDescription、identityLock を持ちます。

upsert(input)、create(input)、update(id, input)

upsert() は、input.id がない場合はキャラクターを作成し、id が設定されている場合は更新します。create() と update() は、id を自動的に設定するショートカットです。更新では、送信したフィールドだけが書き込まれます。name を含む、それ以外のフィールドはそのまま残ります。

upsert(input: UpsertCharacterInput): Promise<{ id: string; name?: string }>
create(input: Omit<UpsertCharacterInput, "id"> & { name: string }): Promise<{ id: string; name?: string }>
update(id: string, input: Omit<UpsertCharacterInput, "id">): Promise<{ id: string; name?: string }>

Prop

Type

const { id } = await client.characters.create({
  nodeId: "scripted",
  name: "Kira",
  description: "Young protagonist with auburn hair",
  style: "realistic",
  identityLock: "strict",
})

await client.characters.update(id, { baseOutfit: "Green raincoat and boots" })

すでに使われている名前は、409 name_taken で失敗します。

delete(id)

キャラクターをアーカイブします。list() には表示されなくなりますが、get(id) では引き続き読み込めます。元に戻すには restore() を使います。

delete(id: string): Promise<{ success: true; archived: true }>

Prop

Type

await client.characters.delete(characterId)

restore(id)

アーカイブされたキャラクターを元に戻します。その名前がすでにほかの有効なキャラクターに使われている場合、サーバーは名前に (restored) を追加し、実際に使った名前を返します。

restore(id: string): Promise<{ id: string; name: string }>

Prop

Type

const { name } = await client.characters.restore(characterId)

duplicate(id, input?)

キャラクターを、名前が (copy) で終わる新しいキャラクターとしてコピーします。コピーは、新しいものを生成するまで、元のアセットの URL を共有します。

duplicate(id: string, input?: { nodeId?: string; projectId?: string }): Promise<{ id: string; name: string }>

Prop

Type

const { id: copyId, name } = await client.characters.duplicate(characterId)

usage(id)

キャラクターを使っているワークフローの数と、その内訳を返します。エディターは、アーカイブする前にこれを表示します。

usage(id: string): Promise<{ workflowCount: number; workflows: Array<{ id: string; name: string }> }>

Prop

Type

const { workflowCount } = await client.characters.usage(characterId)

generate(input)

ポートレート候補を生成します(POST /v1/generate-character)。count が 1 を超える場合、どのジョブを開始するよりも前に、すべてのジョブの分が確保されるため、途中で失敗すると、バッチ全体がロールバックされます。

generate(input: GenerateCharacterInput): Promise<{ jobId: string; jobIds: string[] }>

Prop

Type

const { jobIds } = await client.characters.generate({
  name: "Kira",
  seedPrompt: "Kira portrait, warm natural lighting",
  count: 4,
  attachToCharacterId: characterId,
  provider: "gpt-image",
  quality: "high", // priced as gpt-image:high
})

attachToCharacterId を指定し、候補が 1 つの場合、ジョブが完了すると、その結果がそのままポートレートになります。候補が複数の場合は、approvePortrait() で 1 つを選びます。quality と resolution は、画像生成(Generate Image)とまったく同じ基準で価格が決まるため、4K や高品質での実行では、より多くのクレジットが確保されます。モデルが対応していない値は、拒否されるのではなく無視されます。

generateAsset(input)

キャラクターのポートレートから、表情、ポーズ、ライティング、アングルのいずれか 1 つのバリエーションを生成します。attachToCharacterId、attachToColumn、attachName を指定すると、ジョブが完了したときに、結果がキャラクターの該当するコレクションに追加されます。

generateAsset(input: GenerateAssetInput): Promise<{ jobId: string }>

Prop

Type

await client.characters.generateAsset({
  name: "Kira",
  assetType: "expressions",
  variant: "smile",
  attachToCharacterId: characterId,
  attachToColumn: "expressions",
  attachName: "smile",
})

generateMotion(input)

動画生成(Generate Video)を画像から動画のモードで使い、キャラクターのポートレートをモーションクリップにアニメーション化します。attachToCharacterId を指定すると、クリップはキャラクターの motions に追加されます。sourceImageUrl を指定しない場合は、キャラクターのポートレートが使われます。

generateMotion(input: GenerateMotionInput): Promise<{ jobId: string }>

Prop

Type

await client.characters.generateMotion({
  name: "Kira",
  motionPrompt: "Slow head turn to the left, soft smile",
  provider: "kling",
  attachToCharacterId: characterId,
  attachName: "head turn",
})

approvePortrait(id, candidateJobId)

generate() で完了した候補を、キャラクターのポートレートにします。その後、ビジョンモデルがキャラクターの説明を書き、このメソッドは両方を返します。

approvePortrait(id: string, candidateJobId: string): Promise<{ portraitUrl: string; canonicalDescription: string | null }>

Prop

Type

const { portraitUrl, canonicalDescription } = await client.characters.approvePortrait(characterId, jobIds[0])

説明を書けなかった場合、canonicalDescription は null になります。それでもポートレートは設定されます。もう一度試すには、recaption() を呼び出してください。

recaption(id)

現在のポートレートから、キャラクターの説明を書き直します。

recaption(id: string): Promise<{ canonicalDescription: string }>

Prop

Type

const { canonicalDescription } = await client.characters.recaption(characterId)

キャラクターにポートレートがない場合は 400 no_portrait で、ビジョンモデルが失敗した場合は 502 で失敗します。

よくある質問

最終更新

目次