xAI公式TypeScript SDKで、何ができるようになったのか

AIエージェント

はじめに:「Grok を TypeScript から使う」に公式の道ができた

Grok(xAI の大規模言語モデル)を Node.js や Next.js のサーバーから呼びたいとき、これまでは少し遠回りが必要でした。xAI が公式に出している SDK(API を呼ぶための道具一式)は Python 版だけ で、TypeScript では OpenAI の SDK の接続先を差し替えるか、Vercel の AI SDK を使うか、fetch を自分で書くしかありませんでした。

2026年10月2日(米国時間)、xAI の GitHub 組織から公式の TypeScript SDK @xai-official/sdk が公開されました。ライセンスは Apache 2.0 で、ソースコードも読めます。ただし本人たちが「Experimental(実験版)」と明記しており、公開当日にクラス名が変わる破壊的変更も入りました。

この記事では、機能の一覧を並べるのではなく、「これまで何ができなかったのか」「公開されたことで何が新しくできるようになったのか」 に絞って整理します。

これまでの3つの代わりの道と、公式クライアント1つにまとまった今の姿を比べた図。下段では SpaceXAI クライアントから responses・images・videos・voice・files・batches・tokenizer・models の8つに分かれる

図1:上段がこれまでの3つの道、下段が公式 SDK。機能の数より「xAI 自身が API の形のまま型を付けて保守する」ことが大きな変化です

先に結論を3つにまとめます。

  • 型が守ってくれる範囲が広がった:X 検索やリモート MCP など xAI 独自の道具を、書いている途中に TypeScript が検査してくれます。OpenAI SDK の流用では、ここが型エラーになっていました。
  • xAI の API の形そのままに、依存パッケージなしで使える:文章・画像・動画・音声・ファイル・Batch・トークン数え・モデル価格の一覧までが、1つのクライアントにまとまっています。
  • 実務の細かい困りごとに専用の道具がある:読み上げタグの打ち間違い検査、生成途中の JSON の表示、出力前だけの自動再試行など、自作すると手間がかかる部分が最初から入っています。

想定読者は、TypeScript で AI を使ったアプリやエージェント(自分で道具を使いながら作業を進める AI の仕組み)を作っている方、これから作ってみたい方です。

この記事の確認範囲

この記事の内容は、次の範囲で確認しています。

項目内容
確認日2026年10月4日
SDK の版@xai-official/sdk 0.2.1(npm の latest)
比較に使ったものopenai 7.27.0、TypeScript 5.8.3(strict)
実行環境MacBook(Apple シリコン)、Node.js 22.23.2
筆者が実際に試したことインストール、型検査(tsc)、API キーを使わない関数の実行、ダミーのキーでの1回の通信
試していないことxAI の API キーを使った生成(文章・画像・動画・音声)。該当するコードは型検査の通過までを確認しています

本文中の「実測」「筆者の Mac で確認」は上の範囲での結果です。それ以外は公式リポジトリの README・CHANGELOG・ソースコードと docs.x.ai に基づく説明です。

公開されたものを整理する

まず、何がいつ公開されたのかを確認しておきます。

GitHub の xai-org/xai-sdk-ts リポジトリのトップ画面。説明文に The official TypeScript SDK for the SpaceXAI API とあり、Apache-2.0 ライセンス、最新リリース v0.2.1 が表示されている

公式リポジトリ xai-org/xai-sdk-ts(2026年10月4日 筆者撮影)。説明文では提供元を「SpaceXAI」と表記しています

npm に公開された版は次のとおりです。わずか5時間ほどの間に3つの版が出ています。

版公開日時(UTC)主な変更
0.1.0-rc.12026-09-29公開候補版。ツール呼び出しのイベントを「自分で実行するもの」と「xAI が実行するもの」に分割
0.1.02026-10-02 16:58最初の正式公開
0.2.02026-10-02 18:28破壊的変更:クラス名を xAI から SpaceXAI に変更
0.2.12026-10-02 22:18構造化出力の検証、生成途中の JSON 読み取り、読み上げタグの検査、出力前の再試行を追加

動かすための条件は、Node.js 22.13 以上 と ESM(import 文を使う形式)のプロジェクト です。実行時に必要な依存パッケージは 0 個 で、筆者の環境ではインストール後の容量が 676KB でした。

なお、筆者の Node.js 22.23.2 では CommonJS の require() からも読み込めました。これは Node.js 22.12 以降の「ESM を require できる」機能によるもので、SDK が公式にうたっている使い方ではありません。新しく作るなら ESM にしておくのが安全です。

これまで TypeScript から Grok を使う3つの道

「何が新しいか」を正しく言うには、これまでの状態を正確に押さえる必要があります。docs.x.ai のクイックスタートが案内していた JavaScript の方法は、OpenAI SDK と Vercel AI SDK の2つでした。これに fetch の自作を加えた3つが、これまでの選択肢です。

観点① OpenAI SDK の流用② Vercel AI SDK(@ai-sdk/xai)③ 公式 SDK(今回)
作っているところOpenAIVercelxAI
追加で入るパッケージopenaiai と @ai-sdk/xai などなし
X 検索などの独自ツール型エラーになる(後述)対応対応
動画・音声・BatchxAI の形式に合わないため自作対応(Batch・リアルタイム音声は実験的)対応
トークン数え・モデル価格の一覧・API キー照会自作筆者の調べでは見当たらない対応
型の作り方OpenAI の API に合わせた型AI SDK 共通の抽象化を通すxAI の API 定義から生成
安定度安定安定(5.x)実験版(0.2.1)

ここで注意したいのは、② の Vercel AI SDK は、すでに多くの機能に対応していた という点です。動画や音声も使えたので、「TypeScript では動画が作れなかった」と言うのは正しくありません。

では何が変わったのかというと、次の2点です。

  1. 一番手軽だった ① の道で起きていた「型の壁」がなくなった
  2. xAI 自身が、自社 API の形をそのまま TypeScript の型にして保守するようになった

② は複数の AI 会社を同じ書き方で使えるのが長所ですが、その分だけ「AI SDK 流の書き方」に合わせる必要があります。③ は xAI 専用の代わりに、xAI の API にある項目をそのままの名前で扱えます。どちらが優れているかではなく、用途で選ぶものです。

ここからは、③ で新しくできるようになったことを7つに分けて見ていきます。

新しくできること①:xAI 独自のツールを、型に守られて書ける

いちばん分かりやすい変化から始めます。xAI の API には、モデルが X(旧 Twitter)の投稿を検索する X 検索(x_search) というツールがあります。これを、これまでの書き方と公式 SDK で比べました。

OpenAI SDK で x_search を書くと TS2769 エラーになり、as any で回避すると綴り間違いも素通りしてしまう流れと、公式 SDK の xSearch ヘルパーなら型検査を通り、送られる中身も確認できる流れを比べた図

図2:上段が OpenAI SDK を流用した場合、下段が公式 SDK。どちらも筆者の Mac で tsc を実行した結果です

OpenAI SDK(7.27.0)の型には、x_search というツールの種類が含まれていません。そのため、REST API としては受け付けられる内容でも、TypeScript の段階で TS2769(呼び出し方が合わない)エラーになります。as any などで型を外せば送れますが、その代わりに オプション名の綴り間違いも検査されなくなり、実行して API からエラーが返るまで気付けません。

公式 SDK では @xai-official/sdk/tools からヘルパー関数を読み込んで書きます。

import { SpaceXAI } from "@xai-official/sdk";
import { mcp, webSearch, xSearch } from "@xai-official/sdk/tools";

const client = new SpaceXAI();

const stream = await client.responses.create({
  model: "grok-4.7",
  input: "今週発表された TypeScript 向け AI SDK を、X と Web の両方で調べて3行でまとめて",
  tools: [
    // X の投稿検索(xAI 側で実行)。期間と対象アカウントを絞れる
    xSearch({ allowed_x_handles: ["xai"], from_date: "2026-09-28" }),
    // Web 検索(xAI 側で実行)
    webSearch(),
    // リモート MCP サーバー(xAI が接続して呼び出す)
    mcp({ server_url: "https://mcp.deepwiki.com/mcp", server_label: "deepwiki" }),
  ],
  stream: true,
});

const response = await stream
  // xAI 側で実行されたツール呼び出し(自分では何もしなくてよい)
  .on("server_tool_call", (call) => console.error(`[xAI が実行] ${call.type}`))
  .on("citation", (c) => console.error(`[出典] ${JSON.stringify(c)}`))
  .on("text", (text) => process.stdout.write(text))
  .done();

console.log("\nサーバー側ツールの使用回数:", response.usage.num_server_side_tools_used);

このコードは型検査を通ることを確認済みです(API キーがないため実行結果は未確認)。ヘルパーが作るのはただのオブジェクトで、実際に xSearch({ allowed_x_handles: ["xai"], from_date: "2026-09-28" }) の中身を表示すると {"allowed_x_handles":["xai"],"from_date":"2026-09-28","type":"x_search"} でした。魔法のような変換はしておらず、API に送る形を、型付きで組み立ててくれる道具 だと考えると分かりやすいです。

型検査がどこまで間違いを拾うのかも試しました。次の画面は、わざと間違えたコードに tsc をかけた実際の出力です。

ターミナルで npx tsc を実行した結果。読み上げタグの [laff] に対して did you mean [laugh] と提案するエラー、whisper タグの閉じ忘れのエラー、OpenAI SDK で x_search が型に合わないエラー、MCP の require_approval が存在しないというエラー、モデル名 grok-4.77 に対して grok-4.7 を提案するエラーが並んでいる

筆者の Mac で tsc を実行した出力(見やすさのため、同じ理由のエラーの一部を省略して整形)

拾えたもの・拾えなかったものをまとめると、次のとおりです。

わざと入れた間違い型検査の結果
モデル名を grok-4.77 と打ち間違える(KnownModelId 型を使った場合)エラー。grok-4.7 を提案
MCP に未対応の require_approval を書くエラー
読み上げタグ [laugh] を [laff] と打ち間違えるエラー。[laugh] を提案(後述)
X 検索で「許可リスト」と「除外リスト」を同時に指定するエラーにならない(README には同時に使えないと記載)

すべてを型で防げるわけではありません。特に最後の項目は実行してから API に断られるはずなので、README の注意書きもあわせて読む必要があります。

新しくできること②:「誰がツールを動かすか」を分けて受け取れる

エージェントを作るときに混乱しやすいのが、ツールを 誰が実行するのか です。公式 SDK は、ここをイベント名ではっきり分けています。

左に自分のアプリで実行する function と shell、右に xAI のサーバーで実行する web_search・x_search・code_interpreter・file_search・image_generation・mcp・tool_search を並べ、中央で server_tool_call と client_tool_call のイベントが行き来する図

図3:tools に並べたツールは、xAI 側で動くもの(右)と自分のアプリで動くもの(左)に分かれます

xAI 側で動くツール(Web 検索、X 検索、Python のコード実行、コレクション検索、画像生成、リモート MCP、ツール検索)は、結果が回答に組み込まれて返ってきます。こちらは何もしなくてよく、経過を見たければ server_tool_call を聞けば十分です。

一方、自分で書いた関数(function)と、モデルが書いたコマンドを自分の環境で実行する shell は、こちらで実行して結果を返す 必要があります。これらは client_tool_call として届きます。0.1.0 の直前(9月29日)にイベントがこの2つに分けられたことが、コミット履歴から分かります。

在庫を調べる関数を1つ持たせた例です。

import { type InputItem, type Tool, SpaceXAI } from "@xai-official/sdk";

const client = new SpaceXAI();

// 自分のアプリで実行する関数(在庫を調べる例)
const checkStock: Tool = {
  type: "function",
  name: "check_stock",
  description: "商品コードから在庫数を返す",
  parameters: {
    type: "object",
    properties: { sku: { type: "string" } },
    required: ["sku"],
    additionalProperties: false,
  },
};

const input: Array<InputItem> = [{ role: "user", content: "A-100 と B-200 の在庫はある?" }];
const outputs: Array<InputItem> = [];

const stream = await client.responses.create({ model: "grok-4.7", input, tools: [checkStock], stream: true });
const first = await stream
  // 自分で実行する必要がある呼び出しだけが届く
  .on("client_tool_call", (call) => {
    if (call.type !== "function_call" || call.name !== "check_stock") return;
    const { sku } = JSON.parse(call.arguments) as { sku: string };
    const stock = sku === "A-100" ? 12 : 0; // 本来はデータベースを引く
    outputs.push({ type: "function_call_output", call_id: call.call_id, output: JSON.stringify({ sku, stock }) });
  })
  .done();

// モデルの出力と関数の結果を足して、もう一度呼ぶ
const answer = await client.responses.create({
  model: "grok-4.7",
  input: [...input, ...first.toInput(), ...outputs],
});
console.log(answer.toText());

first.toInput() は、モデルの出力(推論の暗号化された中身を含む)を次の入力にそのまま足せる形に変えるメソッドです。この SDK は 既定で会話を xAI 側に保存しない(store: false)ので、続きの会話はこの方法でつなぎます。

実際のアプリでは、モデルがツールを呼ばなくなるまでこれを繰り返す「ループ」にします。README には、回数の上限を付けたループの完成例が載っています。関数名や引数はモデルが作るものなので、許可した関数だけを呼び、引数を検査してから実行する ことが README でも強調されています。

新しくできること③:リモート MCP を、MCP クライアントなしで使える

MCP(Model Context Protocol)は、AI が外部のツールやデータにつながるための共通の約束ごとです。今回の SDK では、MCP がほかの組み込みツールと同じ並びで、最初から入っています。

自分のアプリが mcp ヘルパーで依頼を送り、xAI の Responses API がリモート MCP サーバーに接続して呼び出し、結果を受け取って回答を返す4段階の流れ。下に使える設定と、まだ使えない設定が並んでいる

図4:MCP サーバーへの接続と呼び出しは xAI が代わりに行います

ポイントは、自分のアプリに MCP クライアントを組み込まなくてよい ことです。mcp({ server_url, server_label }) を tools に1行足すと、xAI が MCP サーバーへ接続し、ツールの一覧を取り、モデルが選んだツールを呼んで、結果を回答に反映します。対象は Streaming HTTP か SSE で公開されている「リモート」の MCP サーバーです。手元の PC だけで動く MCP サーバーには、xAI からは届きません。

ツールが多い MCP サーバーでは、全ツールの説明文をプロンプトに入れると、それだけで入力が膨らみます。その対策として、defer_loading: true と toolSearch() を組み合わせると、必要になったツールの定義だけを後から読み込む ことができます。

まだ使えない設定もあります。実行前に人が承認する require_approval と、connector_id は未対応で、公式 SDK ではこれらを書くと型エラーになりました(先ほどの tsc 出力を参照)。データを書き換えるような MCP サーバーをつなぐときは、allowed_tools で使ってよいツールを絞っておくのが現実的です。

新しくできること④:文章・画像・動画・音声・Batch を1つのクライアントで

1つの SpaceXAI クライアントから、文章以外の機能も呼べます。ただし、すべてが同じ「呼んだら答えが返る」形ではありません。

画像はその場で URL が返り、動画は request_id を受け取ってから wait で待ち、Batch は入れ物を作って依頼を追加し結果を順不同で読む、という3種類の待ち方を並べた図

図5:画像は即時、動画と Batch は受付番号を待つ形です

画像生成(images.generate())は、1回の呼び出しで最大10枚の結果が返ります。動画(videos.generate())は裏で生成される仕事なので、まず受付番号(request_id)だけが返り、videos.wait() で終わるまで待ちます。待ち合わせを SDK が用意しているので、自分で確認の繰り返し処理を書く必要がありません。

import { writeFile } from "node:fs/promises";
import { SpaceXAI } from "@xai-official/sdk";

const client = new SpaceXAI();

// 1. 生成を依頼する(すぐに受付番号だけが返る)
const { request_id } = await client.videos.generate({
  model: "grok-imagine-video-1.5",
  prompt: "雨上がりの路地を、紙の船がゆっくり流れていく",
  duration: 8,
  aspect_ratio: "16:9",
  resolution: "720p",
});

// 2. 終わるまで待つ(既定は5秒ごとに確認、最長10分)
const result = await client.videos.wait(request_id);

if (result.status === "done" && result.video?.url) {
  // URL は一時的なので、すぐ保存する
  const mp4 = await fetch(result.video.url);
  await writeFile("boat.mp4", Buffer.from(await mp4.arrayBuffer()));
  console.log("費用(USD):", result.usage?.cost_usd);
} else {
  console.error(result.status, result.error?.code, result.error?.message);
}

ここで気を付けたいのは、wait() をやめても動画の生成は止まらない ことです。README によると、待ち時間の上限に達して TimeoutError になっても生成は続き、終われば課金されます。動画を取り消す API はまだありません。時間切れになったら、同じ request_id でもう一度 wait() を呼べば続きから待てます。

画像や動画の元素材には、URL・base64・Files API の file_id・Blob を渡せます。ローカルのファイルは openAsBlob() で読み込めば、SDK が送れる形に変換してくれます。大きな動画は、先に client.files.upload()(50MB まで)で上げて file_id で渡すほうが効率的です。

大量の分類や要約には Batch API が向いています。依頼ごとに ID を付けて追加し、通常より割安な料金で、多くは24時間以内に処理されます。結果は順不同で届き、終わったものから読めます。

新しくできること⑤:読み上げの「演出タグ」の間違いを、送る前に見つける

xAI の読み上げ(voice.speak())では、文章の中に [pause](間を空ける)や [laugh](笑う)、<whisper>…</whisper>(ささやく)といった 演出タグ を書けます。困るのは、API がタグの間違いを報告せず、間違ったタグをそのまま読み上げてしまう ことです。

上段は自分でコードに書いた文章の [laff] や閉じ忘れを TypeScript が指摘する流れ、下段はモデルが書いた文章を checkSpeechText で検査し stripInvalidSpeechTags で不明なタグだけ消してから voice.speak で読み上げる流れ

図6:自分で書く文章は型で、モデルが書く文章は実行時に検査します

0.2.1 では、この問題に2段構えの対策が入りました。1つ目は、コードに直接書いた文字列を TypeScript の型で検査する 仕組みです。筆者が "それは面白いですね [laff]" と書いたところ、tsc は「[laff] は不明なタグです。[laugh] のことですか?」と指摘しました。<whisper> の閉じ忘れも検出されています。

文字列の中身まで型で検査するのは、TypeScript の「テンプレートリテラル型」という仕組みを使ったものです。SDK より新しいタグを意図して使うときは、as UnsafeSpeechText を付けると検査を外せます。

2つ目は、モデルが書いた文章のように 実行時にしか中身が分からない文字列 への対策です。こちらは型では検査できないので、関数で検査します。

import { writeFile } from "node:fs/promises";
import { INLINE_SPEECH_TAGS, SpaceXAI, checkSpeechText, stripInvalidSpeechTags } from "@xai-official/sdk";

const client = new SpaceXAI();

// モデルに台本を書かせる。使ってよいタグを SDK の一覧から渡す
const tags = INLINE_SPEECH_TAGS.map((tag) => `[${tag}]`).join(", ");
const draft = await client.responses.create({
  model: "grok-4.7",
  input: `火山についての番組の最初の一言を英語で書いて。使えるタグ: ${tags}`,
});
const line = draft.toText();

// モデルが作ったタグ(例: [laff])を検査してログに残す
const problems = checkSpeechText(line);
if (problems.length > 0) console.warn(problems);

// 不明なタグだけ取り除いてから読み上げる
const speech = await client.voice.speak({ text: stripInvalidSpeechTags(line), language: "en", voice_id: "eve" });
await writeFile("opening.mp3", await speech.bytes());

検査用の関数は API キーなしで動くので、筆者の Mac で実際に試しました。ようこそ [laff] 今日は火山の話です。<whisper>実は内緒の話も</whisper> [long-pause] では始めましょう を渡すと、checkSpeechText() は Unknown speech tag [laff], did you mean [laugh]? を返しました。stripInvalidSpeechTags() を通すと、[laff] だけが消え、<whisper> と [long-pause] は残りました。

0.2.1 に同梱されていたタグは、文中に置くもの(pause・laugh・sigh など)が14種、文を囲むもの(whisper・slow・singing など)が12種でした。音声 API 自体には、文字起こし(voice.transcribe())や、120秒までの音声から声を作るカスタムボイス(Enterprise プラン向け)も含まれます。

新しくできること⑥:生成途中の JSON を、閉じる前から画面に出す

AI に JSON(決まった形のデータ)で答えさせると、後の処理は楽になります。ただ、JSON は最後の } が届くまで JSON.parse できないため、生成が終わるまで画面に何も出せない という弱点がありました。0.2.1 で追加された "json" イベントと parsePartialJson() は、この弱点を埋めるものです。

受信途中の文字列が12文字・75文字・113文字・115文字と増えていくごとに、parsePartialJson が返すオブジェクトと、画面に表示できる内容がどう変わるかを並べた図。113文字の時点では minutes がまだ出ていない

図7:筆者の Mac で parsePartialJson を実行した実際の結果。数値は書き終わるまで出てきません

parsePartialJson() は、途中で切れた JSON を受け取ると、開いたままの文字列・配列・オブジェクトを閉じて、その時点までの値を返します。筆者が台本の JSON を少しずつ渡して試したところ、次のように動きました。

  • 文字列は途中でも返る:話者名が "gu" まで届いた時点で {"speaker":"gu"} が返った
  • 数値は完成するまで返らない:"minutes":1 まで届いた時点では minutes 自体が含まれず、12 が届いてから現れた
  • 壊れた JSON には undefined を返す:{"title" "oops"} は undefined だった

数値を途中で出さないのは、12 になるはずの値を 1 と表示してしまう事故を防ぐためです。配列は「最後の要素以外は完成している」ので、1つ前の要素までを確定として表示する のがコツです。

import { z } from "zod";
import { SpaceXAI } from "@xai-official/sdk";

const client = new SpaceXAI();

// 台本の形を Zod で1回だけ定義し、JSON Schema にも変換して使う
const Script = z.object({
  title: z.string(),
  lines: z.array(z.object({ speaker: z.enum(["host", "guest"]), text: z.string() })),
});
type Line = z.infer<typeof Script>["lines"][number];
// 先頭の $schema 項目は API に不要なので外しておく
const { $schema, ...schema } = z.toJSONSchema(Script);

const stream = await client.responses.create({
  model: "grok-4.7",
  input: "空が青い理由を、2人の対話台本(4行)にして",
  reasoning: { effort: "low" }, // 表示を早く始めたいので推論は軽めに
  text: { format: { type: "json_schema", name: "script", schema } },
  stream: true,
});

let shown = 0;
const response = await stream
  .on("json", (partial) => {
    // 最後の1行は書きかけの可能性があるので、その手前まで表示する
    const lines = (partial as { lines?: Array<Line> }).lines ?? [];
    while (shown < lines.length - 1) {
      const l = lines[shown++]!;
      console.log(`${l.speaker}: ${l.text}`);
    }
  })
  .done();

// 完成したら Zod で検証し、型付きの値として受け取る
const script = response.toJson(Script);
for (const l of script.lines.slice(shown)) console.log(`${l.speaker}: ${l.text}`);

形の定義は Zod で1回だけ書き、z.toJSONSchema()(Zod 4 の機能)で API に渡す JSON Schema に変換しています。最後の response.toJson(Script) も 0.2.1 の追加機能です。Zod・Valibot・ArkType など「Standard Schema」に対応した検証ライブラリを渡すと、検証と型付けを1行で 行えます。形が合わなければ、どの項目が違うかを添えて例外を投げます。途中の値は検証されていないので、確定した値は必ずここで受け取ります。

台本の1行が完成するたびに voice.speak() へ渡せば、「モデルが次の行を書いている間に、前の行を読み上げる」こともできます。README でもこの使い方が紹介されています。

新しくできること⑦:本番運用で困る細部に、最初から答えがある

ここまでの機能に比べると地味ですが、実際にサービスで使うときに効いてくるのがこの部分です。

出力が始まる前の失敗だけ、自動でやり直す

429 が返った場合は待ってやり直し、出力が始まった後の失敗はやり直さず、出力前の失敗は retryBeforeOutput が true のときだけやり直す、という判断の流れ図

図8:文章生成は「二重に出力しない」ことを優先して再試行が決まっています

文章生成の API で失敗を自動的にやり直すと、途中まで出ていた文章と、やり直した文章が二重になるおそれがあります。そのため、この SDK は 出力が始まった後の失敗は決してやり直しません。

0.2.1 で追加された retryBeforeOutput: true を付けると、モデルがまだ何も出力していない段階の失敗(5xx エラー、接続の切断、作成直後の 503 など)だけを自動でやり直します。やり直しのたびに新しい応答が作られ、失敗した回の入力トークンが課金される場合があるため、既定では off になっています。

import { APIError, RateLimitError, SpaceXAI } from "@xai-official/sdk";

const client = new SpaceXAI({
  timeout: 60_000,
  maxRetries: 2,
  // 出力が始まる前の 5xx や切断なら、自動でやり直す(0.2.1 で追加。既定は off)
  retryBeforeOutput: true,
});

try {
  const res = await client.responses.create({ model: "grok-4.7", input: "こんにちは" });
  console.log(res.toText(), res.http.requestId);
} catch (error) {
  if (error instanceof RateLimitError) {
    console.error("回数制限。少し待ってから再実行");
  } else if (APIError.is(error)) {
    // 問い合わせ時はこの2つの ID を添える
    console.error(error.status, error.message, error.requestId, error.clientRequestId);
  } else {
    throw error;
  }
}

もう1つ知っておきたいのは、stream: true を付けなくても responses.create() は裏でストリーミングしている ことです。最初に応答のヘッダーがすぐ返るので、長く考える推論モデルでも、Node.js 標準の fetch が持つ「ヘッダーを5分待つと切れる」制限に引っかかりにくくなっています。

何を送ったかを、秘密を伏せたまま確認できる

うまく動かないとき、実際に送ったリクエストを見たくなります。環境変数 XAI_DEBUG=1 を付けて実行すると、リクエストを cURL コマンドの形で表示してくれます。ブラウザで使えないようにする安全装置とあわせて、ダミーのキーで試しました。

ターミナルの出力。XAI_DEBUG=1 で実行すると api.x.ai/v1/responses への curl コマンドが表示され、authorization ヘッダーは REDACTED と伏せ字になり、本文は省略される。その後に status 400 と Incorrect API key のメッセージ、clientRequestId が表示される。下段ではブラウザに見せかけた環境で、browser and worker use is disabled というエラーで作成が止められている

ダミーのキーで1回だけ通信した実際の出力。本物の API キーは使っていません

表示を見ると、authorization ヘッダーは [REDACTED] と伏せ字になり、プロンプトが入る本文は「機密を含む可能性があるため省略」と出力されません。ログに残しても秘密が漏れにくい作りです。

x-client-request-id は SDK が毎回付ける ID で、やり直しの間も同じ値が使われます。API に届く前に失敗した場合でもエラーから取り出せるので、問い合わせのときに役立ちます。

この実験で1つ意外だったのは、誤った API キーのエラーが 401 ではなく 400 で返ってきた ことです。そのため SDK の AuthenticationError(認証エラー)ではなく、一般的な APIStatusError として届きました。キーの誤りを AuthenticationError だけで判定していると見逃すので、status と message もあわせて確認するのが安全です。

下段は、ブラウザ環境に見せかけてクライアントを作ろうとした結果です。SDK は既定で ブラウザや Worker での利用を拒否 します。秘密の API キーが利用者の手元に渡ってしまうのを防ぐためで、dangerouslyAllowBrowser: true を付けたときだけ作成できます。ブラウザから音声のリアルタイム接続をしたい場合は、サーバー側で短い有効期限の「client secret」を発行して渡す方法が用意されています。

送る前に数え、選ぶ前に価格を見る

トークン数を数える機能や、使えるモデルと価格の一覧、API キーの権限の確認も、同じクライアントから呼べます。

import { SpaceXAI } from "@xai-official/sdk";

const client = new SpaceXAI();

// 送る前にトークン数を数える
const { token_ids } = await client.tokenizer.encode({ model: "grok-4.7", text: "吾輩は猫である。名前はまだ無い。" });
console.log("トークン数:", token_ids.length);

// 使えるモデルと価格を一覧する(価格は 1億トークンあたりの米セント)
const models = await client.models.language.list();
for (const m of models.models) {
  const perMillionUsd = (m.prompt_text_token_price ?? 0) / 10_000;
  console.log(m.id, `入力 $${perMillionUsd}/100万トークン`);
}

// 今使っている API キーの権限を確認する
const key = await client.account.apiKey();
console.log(key.name, key.acls);

価格の単位が「1億トークンあたりの米セント」という独特なものなので、README の説明どおり 10,000 で割ると「100万トークンあたりのドル」になります。料金表をコードに直接書かず、API から取って計算できるのは、料金が変わったときに助かります。

実際に始める手順

ここまで読んで試したくなった方のために、最初の1回を動かすまでの流れをまとめます。

1 Node.js 22.13 以上か確認、2 版を固定してインストール、3 API キーを環境変数に置く、4 文章だけで1回呼ぶ、5 ツールを1つずつ足す、の5段階と、版の固定・ブラウザ利用・デバッグの補足

図9:サーバー側(Node.js)で使う前提の手順です

まず Node.js の版を確認し、新しいフォルダーを作って SDK を入れます。実験版で変更が続いているので、--save-exact で版を固定しておきます。

# Node.js が 22.13 以上か確認する
node -v

# 作業フォルダーを作り、ESM の設定にする
mkdir grok-ts-hello && cd grok-ts-hello
npm init -y
npm pkg set type=module

# 版を固定してインストールする(TypeScript で書く場合は tsx も入れる)
npm install --save-exact @xai-official/sdk@0.2.1
npm install --save-dev tsx typescript @types/node

# API キーを環境変数に置く(キーは https://console.x.ai で発行)
export XAI_API_KEY="<YOUR_API_KEY>"

次に hello.ts を作ります。

import { SpaceXAI } from "@xai-official/sdk";

// XAI_API_KEY 環境変数を自動で読む
const client = new SpaceXAI();

const response = await client.responses.create({
  model: "grok-4.7",
  input: "TypeScript の型推論を、料理にたとえて1文で説明して",
});

console.log(response.toText());
console.log("合計トークン:", response.usage.total_tokens);
# 実行する
npx tsx hello.ts

文章だけで動いたら、webSearch() → xSearch() → mcp() の順に1つずつツールを足していくと、問題が起きたときにどこが原因か切り分けやすくなります。モデル名は記事執筆時点で README が使っている grok-4.7 にしています。使えるモデルは API キーや時期で変わるので、client.models.list() で確認してください。

OpenAI・Anthropic の SDK と書き比べるときの観点

公式 SDK が出たことで、「同じ課題を各社の公式 SDK で作って比べる」ことがやりやすくなりました。xAI の SDK は、OpenAI の Responses API とよく似た形(responses.create、input、tools、function_call_output など)を採っているので、OpenAI からの移行や比較は特に楽です。

比べるときは、次の観点を表にしておくと違いが見えやすいです。

観点見るところ
ツール定義組み込みツールの種類と、オプションがどこまで型で検査されるか
実行場所の区別サーバー側ツールと自前ツールが、ストリームでどう届くか
MCPリモート MCP の設定方法、承認フローの有無、ツールの絞り方
会話の続け方保存の既定値(xAI の SDK は保存しない)と、履歴の渡し方
失敗時の挙動再試行の条件、二重出力を防ぐ仕組み、エラーの型
依存と動作環境依存パッケージの数、Node.js の必要版、ブラウザでの扱い

ここで挙げた xAI 側の挙動は、この記事で確認したとおりです。他社 SDK 側は版によって変わるので、比べるときはその時点の公式ドキュメントで確認してください。

トラブルシューティング

最後に、つまずきやすい点を、表示されるメッセージから引けるようにまとめます。

動かないときに見る順番を示した表。requires Node.js 22.13、export named xAI がない、browser and worker use is disabled、400 Incorrect API key、TS2769 x_search、videos.wait の TimeoutError の6つについて、原因と対処を並べている

図10:表示されたメッセージから原因と対処を探します

表示されるもの原因対処
@xai-official/sdk requires Node.js 22.13 or laterNode.js が古い22.13 以上に上げる
does not provide an export named 'xAI'0.2.0 でクラス名が変わったimport { SpaceXAI } に直す。環境変数 XAI_API_KEY は変わっていない
browser and worker use is disabled…ブラウザや Worker で作ろうとしたサーバー側に移す。音声のリアルタイム接続は client secret を使う
400 Incorrect API key provided.API キーの誤り(筆者の環境では 401 ではなく 400 だった)キーを確認する。エラー判定は status と message も見る
TS2769 … "x_search" is not assignableOpenAI SDK の型で xAI 独自ツールを書いた公式 SDK の xSearch() に置き換える
TimeoutError(videos.wait)10分待って打ち切っただけ生成は続いている。同じ request_id で wait() を呼び直す

それでも解決しないときは、XAI_DEBUG=1 で送った内容を確認し、エラーの requestId と clientRequestId を控えてから、GitHub の Issues で報告するのが近道です。現在、外部からのプルリクエストは受け付けていません。

まとめ

xAI の公式 TypeScript SDK で新しくできるようになったことを振り返ります。

  • 独自ツールを型に守られて書ける:X 検索やリモート MCP を、OpenAI SDK の型エラーを回避せずに書け、モデル名や設定の誤りも書いている途中に分かる
  • ツールの実行場所を分けて受け取れる:server_tool_call と client_tool_call で、自分が実行すべき呼び出しだけを処理できる
  • MCP クライアントなしでリモート MCP を使える:URL を1行書けば xAI が代わりに接続する
  • 文章以外も1つのクライアントで扱える:画像・動画・音声・ファイル・Batch・トークン数え・価格一覧まで、依存パッケージなしでそろう
  • 自作すると面倒な部分が最初から入っている:読み上げタグの検査、生成途中の JSON の表示、出力前だけの再試行、秘密を伏せたデバッグ表示

一方で、まだ 実験版 です。公開当日にクラス名が変わったように、1.0 までは変更が続く前提で、版を固定し、更新のたびに CHANGELOG を読むのがよいでしょう。Vercel AI SDK のように複数社を同じ書き方で扱いたい場合は、引き続きそちらが向いています。xAI の機能を余すところなく、xAI の API の形のまま使いたいときに、この公式 SDK が新しい選択肢になります。

次の一歩としては、手元のエージェントで使っているツールを1つ選び、OpenAI の Responses API と xAI の公式 SDK で同じ処理を書き比べてみるのがおすすめです。ツール定義の書きやすさと、型がどこまで間違いを拾ってくれるかの違いが、すぐに実感できるはずです。

参考リソース