Skip to content
Back to skills

Fetcher Openai Client

ASecurity

Call OpenAI-compatible chat completions with `@ahoo-wang/fetcher-openai`: `OpenAI`, `ChatClient`, `chat.completions` streaming and non-streaming, `DoneDetector`, gateway base URLs, headers and interceptors, HTTP and mid-stream errors. Use whenever a Fetcher app requests or streams a chat completion from GPT or any OpenAI-compatible `/chat/completions` endpoint. For other SSE or LLM streams use fetcher-llm-streaming.

  • 3 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 2, 2026
ai-agentsgoreactazureapi

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned October 7, 2026

npx -y skills add Ahoo-Wang/skills --skill fetcher-openai-client --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Fetcher Openai Client?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Fetcher Openai Client
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/ahoo-wang-fetcher-openai-client/badge)](https://www.skillsdirectory.com/skills/ahoo-wang-fetcher-openai-client)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: fetcher-openai-client
description: >
  Call OpenAI-compatible chat completions with `@ahoo-wang/fetcher-openai`: `OpenAI`, `ChatClient`, `chat.completions` streaming and non-streaming, `DoneDetector`, gateway base URLs, headers and interceptors, HTTP and mid-stream errors. Use whenever a Fetcher app requests or streams a chat completion from GPT or any OpenAI-compatible `/chat/completions` endpoint. For other SSE or LLM streams use fetcher-llm-streaming.
---

# fetcher-openai-client

## Decisions

- **Entry point**: `new OpenAI({ baseURL, apiKey, ...fetcherOptions })` builds its own `Fetcher`. `baseURL` and `apiKey` are required and have no defaults; requests go to `${baseURL}/chat/completions`, so the base URL must include `/v1`. Every other `FetcherOptions` field (`headers`, `timeout`, `fetch`, `validateStatus`, `interceptors`) configures that Fetcher; `apiKey` becomes `Authorization: Bearer <apiKey>` and wins over an `Authorization` in `headers`.
- **Static vs per-request headers**: a fixed header goes in `headers`; a value computed per request (trace id, signed timestamp) goes in a request interceptor on `openai.fetcher.interceptors.request`.
- **Existing Fetcher**: `new ChatClient({ fetcher })` takes a `Fetcher` instance or registered name (you set the `Authorization` header yourself).
- **Streaming is chosen by the request**: `chat.completions(req, signal?)` resolves to a `JsonServerSentEventStream<ChatResponse>` (ended by `DoneDetector` at `data: [DONE]`) when `stream: true`, otherwise a `ChatResponse`. A `stream` typed as plain `boolean` yields the union.
- **Not chat**: embeddings, images, Azure `api-version` and other endpoints are not covered; declare them with `$fetcher-decorator-service` (SSE via `$fetcher-llm-streaming`).

## Gotchas a capable model gets wrong

- `completions` is a method: `openai.chat.completions({...})`, not `openai.chat.completions.create(...)`; this is not the official `openai` SDK (no `defaultHeaders`, no `client.chat.completions.create`).
- Stream items are `JsonServerSentEvent<ChatResponse>`: `event.data.choices[0]?.delta?.content`, not `chunk.choices`. Non-streaming: `response.choices[0].message?.content`.
- `openai.fetcher` is `readonly`; don't reassign it. `interceptors.request.use({ name, order, intercept })` — `name` and `order` are required. Passing your own `interceptors` manager replaces the default one, so `validateStatus` and `fetch` options are then ignored.
- A non-2xx status rejects the `completions()` promise with `HttpStatusValidationError` (an `ExchangeError`; status at `error.exchange.response?.status`, not `error.response.status` or `error.status`). Failures after the stream starts come from the `for await`: `SyntaxError`, `EventStreamIncompleteError` (ended before `[DONE]`) or network errors — none is an `ExchangeError`; rethrow what you don't handle.
- `ChatRequest`/`Message`/`ChatResponse` are a loose subset of OpenAI's schema with index signatures; don't assume every OpenAI field is typed.

## Minimal example

```ts
import { ExchangeError } from '@ahoo-wang/fetcher';
import { OpenAI } from '@ahoo-wang/fetcher-openai';

const openai = new OpenAI({
  baseURL: 'https://api.openai.com/v1',
  apiKey: process.env.OPENAI_API_KEY!,
  timeout: 60_000,
});
openai.fetcher.interceptors.request.use({
  name: 'trace-id',
  order: 0,
  intercept(exchange) {
    exchange.ensureRequestHeaders()['X-Trace-Id'] = crypto.randomUUID();
  },
});

try {
  const stream = await openai.chat.completions({
    model: 'gpt-4o-mini',
    messages: [{ role: 'user', content: 'Hi' }],
    stream: true,
  });
  for await (const event of stream) {
    process.stdout.write(event.data.choices[0]?.delta?.content ?? '');
  }
} catch (error) {
  if (!(error instanceof ExchangeError)) throw error; // mid-stream errors
  console.error('HTTP', error.exchange.response?.status);
}
```

## References

- `references/api.md`: `OpenAI`/`OpenAIOptions`, `ChatClient`, request/response types, `CompletionStreamResultExtractor` with a plain Fetcher, interceptors and error handling. Load it for exact types.

## Related Skills

- $fetcher-llm-streaming: SSE parsing and termination for any other stream.
- $fetcher-integration: interceptors and the request lifecycle of the underlying Fetcher.
- $fetcher-react-hooks: exposing completions through React state.

Files in this skill

  • SKILL.md1.6 KB
  • agents/openai.yaml203 B
  • references/api.md9.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.