customize-sdk-hooks について
Speakeasy 生成 SDK に独自のライフサイクルフックを追加するためのガイドです。リクエストとレスポンスの介入、フック登録、複数の対応言語で SDK 再生成後も独自ファイルを保持する方法を説明します。
主な機能
- 01初期化とリクエストライフサイクルのフック
- 02ヘッダー追加、テレメトリー、認証ロジック
- 03レスポンスとエラーの変換
- 04再生成後も保持されるフック登録
- 05複数の SDK 言語への実装案内
活用シーン
- 01独自の User-Agent や相関 ID ヘッダーの追加
- 02HMAC 認証によるリクエスト署名
- 03SDK レベルのテレメトリーやログの記録
- 04成功レスポンスや API エラーの変換
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ speakeasy-api/ sdk-generation-skills --skill customize-sdk-hooksインストール後、Claude Code で/customize-sdk-hooksを入力して読み込まれたか確認します。
詳細
SDK フックのカスタマイズ
使用するタイミング
生成された SDK にカスタムコードのロジックを追加する必要がある場合に、このスキルを使用します。
- コードを使用して、すべての SDK リクエストにカスタムヘッダー(User-Agent、相関 ID)を追加する
- SDK レベルでテレメトリ、ロギング、可観測性を実装する
- SDK コードで実行されるカスタム認証ロジック(HMAC 署名、トークンの更新)を追加する
- 呼び出し元に渡される前に、レスポンスやエラーを変換する
- カスタムのリクエスト/レスポンスミドルウェアを実装する
- ユーザーが「SDK フック」「カスタムロジックを追加」「コードでリクエストをインターセプト」「HMAC 署名フック」「SDK のテレメトリ」と言っている場合
対象外:
- OpenAPI 仕様の変更(
manage-openapi-overlaysを参照) - SDK クライアントのランタイム設定(
configure-sdk-optionsを参照)
入力
- フックの種類:インターセプトするライフサイクルイベント(初期化、リクエスト前、成功後、エラー後)
- SDK の言語:生成された SDK の対象言語(TypeScript、Go、Python、Java、C#、Ruby、PHP など)
- カスタムロジック:フックポイントに挿入する動作
出力
src/hooks/(または言語に対応するディレクトリ)内のフック実装ファイル- 新しいフックを登録するための、更新済み
src/hooks/registration.ts - SDK の再生成後も保持されるフック
前提条件
- Speakeasy で生成された SDK(サポートされている任意の言語)
- SDK のリクエスト/レスポンスのライフサイクルに関する理解
- 生成された SDK に
src/hooks/ディレクトリが存在すること(デフォルトで作成されます)
フックの種類
| フック | 呼び出されるタイミング | よくある用途 |
|---|---|---|
SDKInitHook | SDK クライアントの初期化時 | デフォルトの設定、設定の検証、ベース URL の設定 |
BeforeCreateRequestHook | HTTP リクエストオブジェクトの作成前 | 入力パラメーターの変更、デフォルト値の挿入 |
BeforeRequestHook | HTTP リクエストの送信前 | ヘッダーの追加、ロギング、テレメトリ、リクエストへの署名 |
AfterSuccessHook | HTTP レスポンスの成功後 | レスポンスの変換、警告の出力、メトリクスの記録 |
AfterErrorHook | HTTP エラーレスポンスの後 | エラーの変換、リトライロジック、エラーロギング |
フックインターフェース(TypeScript)
// SDKInitHook
interface SDKInitHook {
sdkInit(opts: SDKInitOptions): SDKInitOptions;
}
// BeforeCreateRequestHook
interface BeforeCreateRequestHook {
beforeCreateRequest(hookCtx: BeforeCreateRequestHookContext, input: any): any;
}
// BeforeRequestHook
interface BeforeRequestHook {
beforeRequest(
hookCtx: BeforeRequestHookContext,
request: Request
): Request;
}
// AfterSuccessHook
interface AfterSuccessHook {
afterSuccess(
hookCtx: AfterSuccessHookContext,
response: Response
): Response;
}
// AfterErrorHook
interface AfterErrorHook {
afterError(
hookCtx: AfterErrorHookContext,
response: Response | null,
error: unknown
): { response: Response | null; error: unknown };
}
ディレクトリ構成
src/
hooks/
types.ts # Generated - DO NOT EDIT (hook interfaces/types)
registration.ts # Custom - YOUR registrations (preserved on regen)
custom_useragent.ts # Custom - your hook implementations
telemetry.ts # Custom - your hook implementations
重要なルール:registration.ts と作成したカスタムフックファイルは、SDK の再生成後も保持されます。types.ts ファイルは再生成されるため、変更しないでください。
登録パターン
すべてのフックは src/hooks/registration.ts に登録します。このファイルはジェネレーターによって一度作成され、その後上書きされることはありません。ここにフックを追加します。
// src/hooks/registration.ts
import { Hooks } from "./types.js";
import { CustomUserAgentHook } from "./custom_useragent.js";
import { TelemetryHook } from "./telemetry.js";
/*
* This file is only ever generated once on the first generation and then is free
* to be modified. Any hooks you wish to add should be registered in the
* initHooks function. Feel free to define them in this file or in separate files
* in the hooks folder.
*/
export function initHooks(hooks: Hooks) {
hooks.registerBeforeRequestHook(new CustomUserAgentHook());
hooks.registerAfterSuccessHook(new TelemetryHook());
}
コマンド
Speakeasy で生成された SDK にフックを追加するには、次の手順に従います。
src/hooks/にフックの実装ファイルを作成しますsrc/hooks/types.tsの適切なインターフェースを実装しますsrc/hooks/registration.tsに登録します- SDK を再生成します。フックは保持されます
# After adding hooks, regenerate safely
speakeasy generate sdk -s openapi.yaml -o . -l typescript
# Your registration.ts and custom hook files are untouched
例
例 1:カスタム User-Agent フック
すべての送信リクエストにカスタムの User-Agent ヘッダーを追加します。
// src/hooks/custom_useragent.ts
import {
BeforeRequestHook,
BeforeRequestHookContext,
} from "./types.js";
export class CustomUserAgentHook implements BeforeRequestHook {
private userAgent: string;
constructor(appName: string, appVersion: string) {
this.userAgent = `${appName}/${appVersion}`;
}
beforeRequest(
hookCtx: BeforeRequestHookContext,
request: Request
): Request {
// Clone the request to add the custom header
const newRequest = new Request(request, {
headers: new Headers(request.headers),
});
newRequest.headers.set("User-Agent", this.userAgent);
// Optionally append the existing User-Agent
const existing = request.headers.get("User-Agent");
if (existing) {
newRequest.headers.set(
"User-Agent",
`${this.userAgent} ${existing}`
);
}
return newRequest;
}
}
登録します。
// src/hooks/registration.ts
import { Hooks } from "./types.js";
import { CustomUserAgentHook } from "./custom_useragent.js";
export function initHooks(hooks: Hooks) {
hooks.registerBeforeRequestHook(
new CustomUserAgentHook("my-app", "1.0.0")
);
}
例 2:カスタムセキュリティフック(HMAC 署名)
OpenAPI 仕様では表現できない HMAC 署名やカスタム認証が必要な API の場合は、オーバーレイと BeforeRequestHook を組み合わせます。
手順 1:オーバーレイを使用してセキュリティスキームに印を付け、Speakeasy がフックポイントを生成するようにします。
# overlay.yaml
overlay: 1.0.0
info:
title: Add HMAC security
actions:
- target: "$.components.securitySchemes"
update:
hmac_auth:
type: http
scheme: custom
x-speakeasy-custom-security: true
手順 2:署名フックを実装します。
// src/hooks/hmac_signing.ts
import {
BeforeRequestHook,
BeforeRequestHookContext,
} from "./types.js";
import { createHmac } from "crypto";
export class HmacSigningHook implements BeforeRequestHook {
beforeRequest(
hookCtx: BeforeRequestHookContext,
request: Request
): Request {
const timestamp = Date.now().toString();
const secret = hookCtx.securitySource?.apiSecret;
if (!secret) {
throw new Error("API secret is required for HMAC signing");
}
const signature = createHmac("sha256", secret)
.update(`${request.method}:${request.url}:${timestamp}`)
.digest("hex");
const newRequest = new Request(request, {
headers: new Headers(request.headers),
});
newRequest.headers.set("X-Timestamp", timestamp);
newRequest.headers.set("X-Signature", signature);
return newRequest;
}
}
登録します。
// src/hooks/registration.ts
import { Hooks } from "./types.js";
import { HmacSigningHook } from "./hmac_signing.js";
export function initHooks(hooks: Hooks) {
hooks.registerBeforeRequestHook(new HmacSigningHook());
}
ベストプラクティス
-
フックの役割を絞る:各フックは単一の目的に対応させてください。1つの巨大なフックにまとめるのではなく、ユーザーエージェント、テレメトリ、認証には個別のフックを使用します。
-
レスポンスの本文を読み取る前に複製する:
Response.bodyストリームは一度しか消費できません。読み取る前に必ず複製します:afterSuccess(hookCtx: AfterSuccessHookContext, response: Response): Response { // CORRECT: clone before reading const cloned = response.clone(); cloned.json().then((data) => console.log("Response:", data)); return response; // return the original, unconsumed } -
テレメトリにはファイア・アンド・フォーゲットを使用する:ログ記録やメトリクスなど、重要度の低い処理によってリクエストのパイプラインをブロックしないでください:
beforeRequest(hookCtx: BeforeRequestHookContext, request: Request): Request { // Fire-and-forget: do not await void fetch("https://telemetry.example.com/events", { method: "POST", body: JSON.stringify({ operation: hookCtx.operationID }), }); return request; } -
フックを個別にテストする:モックの
Request/Responseオブジェクトとフックコンテキストを作成し、フックを単独でテストするユニットテストを記述します。 -
hookCtx.operationIDを使用する:フックコンテキストには現在のオペレーション ID が含まれており、オペレーションごとの動作、ログ記録、メトリクスに役立ちます。
してはいけないこと
types.tsを編集しないでください:このファイルは再生成されます。変更内容は失われます。- 複製せずにレスポンス本文を消費しないでください:本文のストリームが使い切られ、後続の処理で失敗します。
- フックでブロッキング I/O を実行しないでください:フック内の長時間実行される処理(ネットワーク呼び出し、ファイル I/O)は、SDK のパフォーマンスを低下させます。重要度の低い処理には、ファイア・アンド・フォーゲットのパターンを使用します。
- 成功を失敗に変換する意図がない限り、
AfterSuccessHookでエラーをスローしないでください:フックでエラーをスローすると、通常の処理フローが妨げられます。 - 同期せずに、フックに変更可能な共有状態を保存しないでください:マルチスレッド環境では、フックが同時に呼び出されることがあります。
- OpenAPI オーバーレイに属するロジックを重複させないでください:API 仕様(セキュリティスキームの追加、パラメーターの変更など)を変更する必要がある場合は、フックではなくオーバーレイを使用します。
トラブルシューティング
フックが呼び出されない
- フックが
src/hooks/registration.tsに登録されていることを確認します - 正しいフックタイプを登録していることを確認します(例:
registerBeforeRequestHookとregisterAfterSuccessHook) initHooksがエクスポートされ、期待されるシグネチャに従っていることを確認します
レスポンス本文が空、またはすでに消費されている
- 複製せずに
response.bodyを読み取るか、response.json()を呼び出しています - 本文を消費する前に必ず
response.clone()を使用して複製し、その後、元のレスポンスを返します
再生成後にフックが失われた
src/hooks/のカスタムフックファイルは保持されますが、個別のファイルとして存在する場合に限りますregistration.tsは初回の生成後に上書きされることはありませんtypes.tsは上書きされます。カスタムコードを記述しないでください
再生成後に TypeScript のコンパイルエラーが発生する
types.tsのインターフェースが更新されている可能性があります。フックのシグネチャに破壊的変更がないか確認します- 新しいインターフェース定義に合わせて、フックの実装を更新します
フックによってリクエストが失敗する
- 有効な
RequestまたはResponseオブジェクトを返していることを確認します - 複製したリクエストで元の本文とヘッダーが保持されていることを確認します
- 挿入するヘッダーの値が有効であることを確認します(undefined や null でないこと)
その他の言語
上記の例は TypeScript ですが、Speakeasy SDK のフックは、サポートされているすべての言語で利用できます:
- Go:フックは
hooks/hooks.goのインターフェースを実装し、hooks/registration.goで登録します - Python:フックは
src/hooks/に配置するクラスで、src/hooks/types.pyのプロトコルを実装します - Java:フックは
hooks/SDKHooks.javaのインターフェースを実装します - C#:フックは
Hooks/SDKHooks.csのインターフェースを実装します - Ruby:フックには、Faraday ミドルウェアのパターンを用いた Sorbet 型付きクラスを使用します
- PHP:フックには、Guzzle ミドルウェアとともに PSR-7 のリクエスト/レスポンスインターフェースを使用します
フックの種類、ライフサイクル、登録パターンは、すべての言語で共通です。言語固有のインターフェース定義については、SDK 内の生成された types ファイルを参照してください。