
Fetcher Openai Client
- 1 installs
- 15 repo stars
- Updated August 4, 2026
- ahoo-wang/fetcher
fetcher-openai-client is a Claude Code skill for building a type-safe OpenAI Chat Completions client on the @ahoo-wang/fetcher-openai package, including streaming and interceptors.
About
fetcher-openai-client is a Claude Code skill for calling OpenAI Chat Completions through the @ahoo-wang/fetcher-openai TypeScript package. It covers configuring the OpenAI and ChatClient entry points, choosing streaming or non-streaming result extraction, adding interceptors for auth or tracing, and handling client errors. A developer uses it when building an OpenAI chat client on Fetcher rather than for general OpenAI platform questions.
- Guides using the @ahoo-wang/fetcher-openai package to call OpenAI Chat Completions
- Covers streaming vs non-streaming result extraction and request interceptors
- Documents the OpenAI and ChatClient classes with type-safe conditional returns
Fetcher Openai Client by the numbers
- 1 all-time installs (skills.sh)
- Ranked #14,102 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
fetcher-openai-client capabilities & compatibility
Requires an OpenAI API key (OPENAI_API_KEY) for chat completions.
- Capabilities
- openai client · streaming completions · request interceptors · llm integration
- Works with
- openai
- Use cases
- api development
- Pricing
- Bring your own API key
What fetcher-openai-client says it does
Use when calling OpenAI Chat Completions through Fetcher, configuring OpenAI or ChatClient, sending streaming or non-streaming chat completion requests, adding interceptors
Type-safe OpenAI chat client built on the Fetcher decorator ecosystem.
npx skills add https://github.com/ahoo-wang/fetcher --skill fetcher-openai-clientAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 15 |
| Last updated | August 4, 2026 |
| Repository | ahoo-wang/fetcher ↗ |
What it does
Call OpenAI Chat Completions in TypeScript through the Fetcher-based @ahoo-wang/fetcher-openai package.
Who is it for?
Developers using @ahoo-wang/fetcher-openai to build a typed OpenAI chat client with streaming support.
Skip if: General OpenAI platform questions unrelated to the Fetcher package, which the skill routes to official docs.
When should I use this skill?
Calling OpenAI Chat Completions through Fetcher, configuring OpenAI or ChatClient, sending streaming or non-streaming requests, adding interceptors, or handling client errors.
What you get
- a configured Fetcher-based OpenAI chat client
By the numbers
- 5-step workflow
- references/api.md is 9,185 bytes
Files
fetcher-openai-client
Use This Skill When
- The task needs an OpenAI chat completion client built on Fetcher.
- The task mentions
OpenAI,ChatClient, chat completions, streaming completions, or completion result extractors. - The task needs OpenAI request interceptors, base URL configuration, or error handling.
- The task is about this repository's
@ahoo-wang/fetcher-openaipackage rather than general OpenAI platform usage.
Workflow
1. Configure the Fetcher-backed OpenAI entry point before using ChatClient directly. 2. Choose streaming or non-streaming result extraction based on the caller contract. 3. Use interceptors for auth, tracing, or request customization rather than scattering request changes. 4. For current OpenAI platform behavior, verify against official docs before changing package semantics. 5. Load references/api.md for class APIs, type shapes, streaming examples, and error handling patterns.
Key Practices
- Keep this skill scoped to Fetcher integration code; route generic OpenAI API questions to official docs workflows.
- Do not duplicate SSE parsing logic here when
fetcher-llm-streamingcovers the stream mechanics. - Make streaming consumers handle partial data and errors explicitly.
References
references/api.md: Detailed package API, examples, and edge-case guidance. Load it only when the task needs OpenAI and ChatClient APIs, chat completion types, streaming and non-streaming examples, interceptors, and error handling snippets.
Related Skills
- $fetcher-llm-streaming: Use for lower-level SSE and token stream handling.
- $fetcher-integration: Use for core Fetcher interceptors and request lifecycle behavior.
- $fetcher-react-hooks: Use when OpenAI calls are exposed through React state hooks.
interface:
display_name: 'Fetcher OpenAI Client'
short_description: 'OpenAI chat clients with Fetcher'
default_prompt: 'Use $fetcher-openai-client to call OpenAI chat completions through Fetcher.'
Fetcher OpenAI Client API Reference
Contents
- Installation
- Key Imports
- Architecture
- OpenAI Class
- ChatClient Class
- DoneDetector and CompletionStreamResultExtractor
- Types
- Usage Examples
- Basic OpenAI Client
- Non-Streaming Completion
- Streaming Completion
- Using ChatClient Directly
- Adding Interceptors
- Error Handling
- Streaming Error Handling
- Further Reading
Type-safe OpenAI chat client built on the Fetcher decorator ecosystem. Uses @api decorator, ExecuteLifeCycle hooks, and conditional return types for streaming/non-streaming completions.
Installation
pnpm add @ahoo-wang/fetcher-openai @ahoo-wang/fetcher @ahoo-wang/fetcher-decorator @ahoo-wang/fetcher-eventstream reflect-metadataPeer dependencies: @ahoo-wang/fetcher, @ahoo-wang/fetcher-eventstream, @ahoo-wang/fetcher-decorator. reflect-metadata must be imported before using decorator-based features.
Key Imports
import {
OpenAI,
OpenAIOptions,
ChatClient,
ChatRequest,
ChatResponse,
Message,
Choice,
Usage,
CompletionStreamResultExtractor,
DoneDetector,
} from '@ahoo-wang/fetcher-openai';Architecture
OpenAI Class
Top-level entry point. Creates a Fetcher with auth headers and a ChatClient:
export class OpenAI {
public readonly fetcher: Fetcher;
public readonly chat: ChatClient;
constructor(options: OpenAIOptions) {
this.fetcher = new Fetcher({
baseURL: options.baseURL,
headers: { Authorization: `Bearer ${options.apiKey}` },
});
this.chat = new ChatClient({ fetcher: this.fetcher });
}
}`fetcher` is `readonly` -- do NOT reassign it. Use interceptors on the existing instance instead.
ChatClient Class
Decorated with @api('chat'), implements ApiMetadataCapable and ExecuteLifeCycle:
@api('chat')
export class ChatClient implements ApiMetadataCapable, ExecuteLifeCycle {
constructor(public readonly apiMetadata?: ApiMetadata) {}
beforeExecute(exchange: FetchExchange): void {
const chatRequest = exchange.request.body as ChatRequest;
if (chatRequest.stream) {
exchange.resultExtractor = CompletionStreamResultExtractor;
}
}
@post('/completions')
completions<T extends ChatRequest = ChatRequest>(
@body() chatRequest: T,
): Promise<
T['stream'] extends true
? JsonServerSentEventStream<ChatResponse>
: ChatResponse
> {
throw autoGeneratedError(chatRequest);
}
}Key behaviors:
beforeExecuteautomatically assignsCompletionStreamResultExtractorfor streaming requests- Conditional return type:
stream: trueyieldsJsonServerSentEventStream<ChatResponse>, otherwiseChatResponse - Stream terminates when
DoneDetectordetectsevent.data === '[DONE]'
DoneDetector and CompletionStreamResultExtractor
// Terminates stream on [DONE] signal
export const DoneDetector: TerminateDetector = (event: ServerSentEvent) => {
return event.data === '[DONE]';
};
// Extracts streaming response as JsonServerSentEventStream
export const CompletionStreamResultExtractor: ResultExtractor<
JsonServerSentEventStream<ChatResponse>
> = (exchange: FetchExchange) => {
return exchange.requiredResponse.requiredJsonEventStream(DoneDetector);
};Types
interface ChatRequest {
messages: Message[];
model?: string;
stream?: boolean;
temperature?: number;
max_tokens?: number;
top_p?: number;
frequency_penalty?: number;
presence_penalty?: number;
n?: number;
stop?: string;
tools?: string[]; // simple string array, NOT complex objects
tool_choice?: { [key: string]: any };
response_format?: { [key: string]: any };
user?: string;
}
interface Message {
content?: string;
role?: string;
[property: string]: any; // extensible: add name, tool_call_id, etc.
}
interface ChatResponse {
choices: Choice[];
created: number;
id: string;
object: string;
usage: Usage;
[property: string]: any;
}
interface Choice {
finish_reason?: string;
index?: number;
message?: Message;
// delta is accessible via index signature in streaming chunks
[property: string]: any;
}
interface Usage {
completion_tokens: number;
prompt_tokens: number;
total_tokens: number;
[property: string]: any;
}Usage Examples
Basic OpenAI Client
import 'reflect-metadata';
import { OpenAI } from '@ahoo-wang/fetcher-openai';
const openai = new OpenAI({
baseURL: 'https://api.openai.com/v1',
apiKey: process.env.OPENAI_API_KEY!,
});Custom endpoints (Azure, proxy, local):
const azure = new OpenAI({
baseURL:
'https://your-resource.openai.azure.com/openai/deployments/your-deployment',
apiKey: 'your-azure-key',
});
const local = new OpenAI({
baseURL: 'http://localhost:8000/v1',
apiKey: 'not-needed',
});Non-Streaming Completion
const response: ChatResponse = await openai.chat.completions({
model: 'gpt-3.5-turbo',
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'Hello!' },
],
temperature: 0.7,
max_tokens: 150,
});
console.log(response.choices[0].message?.content);Streaming Completion
Streaming returns JsonServerSentEventStream<ChatResponse>. Each chunk is a JsonServerSentEvent<ChatResponse> -- access the data via .data:
const stream = await openai.chat.completions({
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: 'Tell me a story' }],
stream: true,
});
for await (const event of stream) {
const content = event.data.choices[0]?.delta?.content;
if (content) {
process.stdout.write(content);
}
}
// Stream auto-terminates when DoneDetector receives '[DONE]'Using ChatClient Directly
import 'reflect-metadata';
import { ChatClient } from '@ahoo-wang/fetcher-openai';
import { Fetcher } from '@ahoo-wang/fetcher';
const fetcher = new Fetcher({
baseURL: 'https://api.openai.com/v1',
headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
});
const chatClient = new ChatClient({ fetcher });
const response = await chatClient.completions({
model: 'gpt-4',
messages: [{ role: 'user', content: 'Explain TypeScript generics' }],
});Adding Interceptors
openai.fetcher is readonly -- add interceptors to the existing fetcher instance:
import type { FetchExchange } from '@ahoo-wang/fetcher';
// Request interceptor
openai.fetcher.interceptors.request.use({
name: 'log-request',
intercept(exchange: FetchExchange): void {
console.log('Request:', exchange.request.url);
},
});
// Response interceptor
openai.fetcher.interceptors.response.use({
name: 'log-response',
intercept(exchange: FetchExchange): void {
console.log('Response status:', exchange.response?.status);
},
});Error Handling
Use ExchangeError from @ahoo-wang/fetcher, not Axios-style patterns:
import { ExchangeError } from '@ahoo-wang/fetcher';
try {
const response = await openai.chat.completions({
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: 'Hello!' }],
});
} catch (error) {
if (error instanceof ExchangeError) {
const status = error.exchange.response?.status;
switch (status) {
case 401:
console.error('Authentication failed - check API key');
break;
case 429:
console.error('Rate limit exceeded');
break;
case 400:
console.error('Bad request');
break;
}
} else {
throw error;
}
}Streaming Error Handling
try {
const stream = await openai.chat.completions({
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: 'Hello!' }],
stream: true,
});
for await (const event of stream) {
process.stdout.write(event.data.choices[0]?.delta?.content || '');
}
} catch (error) {
if (error instanceof ExchangeError) {
console.error('Exchange failed:', error.exchange.response?.status);
}
}Further Reading
- OpenAI Class - OpenAI class source
- ChatClient - Decorator-based chat client
- Chat Types - ChatRequest, ChatResponse, Message types
- CompletionStreamResultExtractor - DoneDetector and streaming extractor
- Tests - Usage examples