HTTP クライアント(インターネット通信用のプログラム部品)の誤った使い方を Output SDK ステップで修正します。 次のような場合に使用: - トレース(処理の追跡記録)されていないリクエストが見られる - エラーの詳細情報が不足している - axios(HTTP通信ライブラリ)に関連するエラーが発生している - HTTP通信が適切にログ記録・再試行されていない - 有料 API の利用コストが料金レポートに反映されていない
Fix HTTP client misuse in Output SDK steps. Use when seeing untraced requests, missing error details, axios-related errors, HTTP calls aren't being properly logged and retried, or paid API costs are missing from cost reports.
このスキルは、Output SDKの@outputai/httpから提供されるcreateKyClientではなく、axios、fetch、または他のHTTPクライアントを直接使用することで生じる問題の診断と修正を支援します。Output SDKのクライアントは、リクエストのトレース(追跡)、自動再試行、優れたエラー処理を提供します。
以下の現象が見られている場合:
axios、fetch、または他のHTTPクライアントを直接使用すると、Output SDKの以下の機能がバイパス(スキップ)されます:
// 間違い: axiosを使用
import axios from 'axios';
export const fetchData = step( {
name: 'fetchData',
fn: async input => {
const response = await axios.get( 'https://api.example.com/data' );
return response.data;
}
} );
// 間違い: fetchを使用
export const fetchData = step( {
name: 'fetchData',
fn: async input => {
const response = await fetch( 'https://api.example.com/data' );
return response.json();
}
} );
@outputai/httpからcreateKyClientを使用します:
import { z, step } from '@outputai/core';
import { createKyClient } from '@outputai/http';
export const fetchData = step( {
name: 'fetchData',
inputSchema: z.object( {
endpoint: z.string()
} ),
outputSchema: z.object( {
data: z.unknown()
} ),
fn: async input => {
const client = createKyClient( {
prefix: 'https://api.example.com'
} );
const data = await client.get( input.endpoint ).json();
return { data };
}
} );
import { createKyClient } from '@outputai/http';
const client = createKyClient( {
prefix: 'https://api.example.com',
timeout: 30000, // 30秒のタイムアウト
retry: {
limit: 3, // 最大3回まで再試行
methods: [ 'GET', 'POST' ], // 再試行を行うHTTPメソッド
statusCodes: [ 408, 500, 502, 503, 504 ] // 再試行をトリガーするステータスコード
},
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
} );
const data = await client.get( 'users/123' ).json();
const result = await client.post( 'users', {
json: {
name: 'John',
email: 'john@example.com'
}
} ).json();
const updated = await client.put( 'users/123', {
json: {
name: 'John Updated'
}
} ).json();
await client.delete( 'users/123' );
const data = await client.get( 'search', {
searchParams: {
q: 'query',
limit: 10
}
} ).json();
非HEADリクエストの応答からメタデータ(response.url、response.status、ヘッダーなど)のみを読み取る場合は、使用していないボディをキャンセルしてください。.json()、.text()などでボディを読み取ると、すでに消費されます。
const response = await client.get( url );
try {
return response.url;
} finally {
await response.body?.cancel();
}
import axios from 'axios';
import { step } from '@outputai/core';
export const createUser = step( {
name: 'createUser',
fn: async input => {
try {
const response = await axios.post(
'https://api.example.com/users',
{ name: input.name, email: input.email },
{
headers: { 'Authorization': `Bearer ${process.env.API_KEY}` },
timeout: 30000
}
);
return response.data;
} catch ( error ) {
if ( axios.isAxiosError( error ) ) {
throw new Error( `API Error: ${error.response?.data?.message}` );
}
throw error;
}
}
} );
import { z, step } from '@outputai/core';
import { createKyClient } from '@outputai/http';
import { credentials } from '@outputai/credentials';
export const createUser = step( {
name: 'createUser',
inputSchema: z.object( {
name: z.string(),
email: z.string().email()
} ),
outputSchema: z.object( {
id: z.string(),
name: z.string(),
email: z.string()
} ),
fn: async input => {
const client = createKyClient( {
prefix: 'https://api.example.com',
timeout: 30000,
retry: { limit: 3 },
headers: {
'Authorization': `Bearer ${credentials.require( 'service.api_key' )}`
}
} );
const user = await client.post( 'users', {
json: {
name: input.name,
email: input.email
}
} ).json();
return user;
}
} );
Kyクライアントは構造化されたエラーハンドリングを提供します:
import { createKyClient, ky } from '@outputai/http';
export const fetchData = step( {
name: 'fetchData',
fn: async input => {
const client = createKyClient( { prefix: 'https://api.example.com' } );
try {
return await client.get( 'data' ).json();
} catch ( error ) {
if ( error instanceof ky.HTTPError ) {
// レスポンスの詳細にアクセス
const status = error.response.status;
const body = await error.response.json();
throw new Error( `API returned ${status}: ${body.message}` );
}
throw error;
}
}
} );
クライアントが有料の外部APIをラップしているが、npx output workflow costのAPI Costsセクションに呼び出しが表示されない。トレースは機能しているものの、コストは常に$0または報告書から欠落している。
npx output workflow costは、@outputai/httpのaddRequestCostで明示的に付加された(関連付けられた)HTTPコストのみを認識します。リクエストのトレースは自動ですが、コストの計上は自動ではありません。addRequestCostを呼び出さないクライアントはトレースが正しく機能してコストが0になりますが、これは無料/内部サービスでは期待通りですが、有料サービスではバグです。
各呼び出しサイトではなく、クライアントのafterResponseフック(処理後コールバック)内にコストを付加して、すべての呼び出しが自動的にコスト計上されるようにします。メーター化されたレスポンスおよび定額料金のパターンについてはoutput-dev-http-client-create(「リクエストコストの付加(有料API)」セクション)を、外部観測システムにコストを転送する方法についてはoutput-dev-cost-hooksを参照してください。
// 間違い: 有料API、コスト追跡なし
const client = createKyClient( { prefix: 'https://api.service.com' } );
// 正しい: すべての呼び出しに対してコストが自動的に付加される
import { addRequestCost, createKyClient } from '@outputai/http';
const client = createKyClient( {
prefix: 'https://api.service.com',
hooks: {
afterResponse: [
( _request, _options, response ) => {
if ( !response.ok ) return;
addRequestCost( response, 0.005 );
}
]
}
} );
コードベースを検索します:
# axiosのインポートを検索
grep -rn "from 'axios'\|from \"axios\"" src/
# fetchの呼び出しを検索
grep -rn "await fetch(" src/
# 他のHTTPライブラリを検索
grep -rn "got\|node-fetch\|request\|superagent" src/
| オプション | 説明 | デフォルト |
|---|---|---|
prefix |
すべてのリクエストのベースURL | (必須) |
timeout |
リクエストタイムアウト(ミリ秒) | 10000 |
retry.limit |
最大再試行回数 | 2 |
retry.methods |
再試行を行うHTTPメソッド | ['GET', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'TRACE'] |
retry.statusCodes |
再試行をトリガーするステータスコード | [408, 413, 429, 500, 502, 503, 504] |
headers |
デフォルトヘッダー | {} |
createKyClientへの移行後:
npx output workflow run <name> --input '<input>'npx output workflow debug <id> --jsonoutput-error-direct-ioを参照output-services-checkを参照output-dev-credentialsを参照output-dev-http-client-createおよびoutput-dev-cost-hooksを参照This skill helps diagnose and fix issues caused by using axios, fetch, or other HTTP clients directly instead of Output SDK's createKyClient from @outputai/http. The Output SDK client provides tracing, automatic retries, and better error handling.
You're seeing:
Using axios, fetch, or other HTTP clients directly bypasses Output SDK's:
// WRONG: Using axios
import axios from 'axios';
export const fetchData = step( {
name: 'fetchData',
fn: async input => {
const response = await axios.get( 'https://api.example.com/data' );
return response.data;
}
} );
// WRONG: Using fetch
export const fetchData = step( {
name: 'fetchData',
fn: async input => {
const response = await fetch( 'https://api.example.com/data' );
return response.json();
}
} );
Use createKyClient from @outputai/http:
import { z, step } from '@outputai/core';
import { createKyClient } from '@outputai/http';
export const fetchData = step( {
name: 'fetchData',
inputSchema: z.object( {
endpoint: z.string()
} ),
outputSchema: z.object( {
data: z.unknown()
} ),
fn: async input => {
const client = createKyClient( {
prefix: 'https://api.example.com'
} );
const data = await client.get( input.endpoint ).json();
return { data };
}
} );
import { createKyClient } from '@outputai/http';
const client = createKyClient( {
prefix: 'https://api.example.com',
timeout: 30000, // 30 second timeout
retry: {
limit: 3, // Retry up to 3 times
methods: [ 'GET', 'POST' ], // Which methods to retry
statusCodes: [ 408, 500, 502, 503, 504 ] // Which status codes trigger retry
},
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
} );
const data = await client.get( 'users/123' ).json();
const result = await client.post( 'users', {
json: {
name: 'John',
email: 'john@example.com'
}
} ).json();
const updated = await client.put( 'users/123', {
json: {
name: 'John Updated'
}
} ).json();
await client.delete( 'users/123' );
const data = await client.get( 'search', {
searchParams: {
q: 'query',
limit: 10
}
} ).json();
When code only reads metadata from a non-HEAD response, such as response.url, response.status, or headers, cancel the
unused body. Reading a body with .json(), .text(), etc. already consumes it.
const response = await client.get( url );
try {
return response.url;
} finally {
await response.body?.cancel();
}
import axios from 'axios';
import { step } from '@outputai/core';
export const createUser = step( {
name: 'createUser',
fn: async input => {
try {
const response = await axios.post(
'https://api.example.com/users',
{ name: input.name, email: input.email },
{
headers: { 'Authorization': `Bearer ${process.env.API_KEY}` },
timeout: 30000
}
);
return response.data;
} catch ( error ) {
if ( axios.isAxiosError( error ) ) {
throw new Error( `API Error: ${error.response?.data?.message}` );
}
throw error;
}
}
} );
import { z, step } from '@outputai/core';
import { createKyClient } from '@outputai/http';
import { credentials } from '@outputai/credentials';
export const createUser = step( {
name: 'createUser',
inputSchema: z.object( {
name: z.string(),
email: z.string().email()
} ),
outputSchema: z.object( {
id: z.string(),
name: z.string(),
email: z.string()
} ),
fn: async input => {
const client = createKyClient( {
prefix: 'https://api.example.com',
timeout: 30000,
retry: { limit: 3 },
headers: {
'Authorization': `Bearer ${credentials.require( 'service.api_key' )}`
}
} );
const user = await client.post( 'users', {
json: {
name: input.name,
email: input.email
}
} ).json();
return user;
}
} );
The Ky client provides structured error handling:
import { createKyClient, ky } from '@outputai/http';
export const fetchData = step( {
name: 'fetchData',
fn: async input => {
const client = createKyClient( { prefix: 'https://api.example.com' } );
try {
return await client.get( 'data' ).json();
} catch ( error ) {
if ( error instanceof ky.HTTPError ) {
// Access response details
const status = error.response.status;
const body = await error.response.json();
throw new Error( `API returned ${status}: ${body.message}` );
}
throw error;
}
}
} );
A client wraps a paid third-party API, but its calls never show up in the API Costs section of npx output workflow cost — they're traced (so tracing works), but their cost is always $0 or absent from the report.
npx output workflow cost only knows about HTTP spend that was explicitly attached with addRequestCost from @outputai/http. Tracing a request is automatic; costing it is not — a client that never calls addRequestCost will trace correctly and cost nothing, which is expected for free/internal services but a bug for paid ones.
Attach cost inside an afterResponse hook on the client so every call is costed automatically, rather than at each call site. See output-dev-http-client-create (section "Attaching Request Cost (Paid APIs)") for the metered-response and flat-rate patterns, and output-dev-cost-hooks for forwarding those costs to an external observability system.
// WRONG: paid API, no cost tracking
const client = createKyClient( { prefix: 'https://api.service.com' } );
// CORRECT: cost attached automatically for every call
import { addRequestCost, createKyClient } from '@outputai/http';
const client = createKyClient( {
prefix: 'https://api.service.com',
hooks: {
afterResponse: [
( _request, _options, response ) => {
if ( !response.ok ) return;
addRequestCost( response, 0.005 );
}
]
}
} );
Search your codebase:
# Find axios imports
grep -rn "from 'axios'\|from \"axios\"" src/
# Find fetch calls
grep -rn "await fetch(" src/
# Find other HTTP libraries
grep -rn "got\|node-fetch\|request\|superagent" src/
| Option | Description | Default |
|---|---|---|
prefix |
Base URL for all requests | (required) |
timeout |
Request timeout in ms | 10000 |
retry.limit |
Max retry attempts | 2 |
retry.methods |
HTTP methods to retry | ['GET', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'TRACE'] |
retry.statusCodes |
Status codes to retry | [408, 413, 429, 500, 502, 503, 504] |
headers |
Default headers | {} |
After migrating to createKyClient:
npx output workflow run <name> --input '<input>'npx output workflow debug <id> --jsonoutput-error-direct-iooutput-services-checkoutput-dev-credentialsoutput-dev-http-client-create and output-dev-cost-hooks原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。