# Node.js ও TypeScript (OpenAI SDK)

> Node.js ও TypeScript থেকে Tokens-এর সাথে official openai npm package ব্যবহার করুন: client setup, streaming, APIError দিয়ে error handling, আর key server-এ রাখার জন্য Next.js route handler।

Official `openai` npm package Tokens-এর সাথে চালাতে দুটো setting বদলালেই হয়: `baseURL` আর `apiKey`। এই পেজে আছে TypeScript setup, streaming, `APIError` থেকে Tokens-এর error code পড়া, আর একটা Next.js route handler, যাতে আপনার key কখনো browser-এ না পৌঁছায়।

## OpenAI Node.js SDK install করুন

```bash
npm install openai
export TOKENS_API_KEY="tok_live_your_key"
```

উদাহরণগুলোতে ES module আর top-level `await` আছে, তাই `.mjs` বা `.ts` file হিসেবে চালান (যেমন `npx tsx hello.ts`)। ধরে নেওয়া হয়েছে আপনি `openai`-এর সাম্প্রতিক major version (v5 বা তার পরের) ব্যবহার করছেন।

```ts title="lib/tokens.ts"
import OpenAI from "openai";

export const tokens = new OpenAI({
  baseURL: "https://tokens.bd/v1",
  apiKey: process.env.TOKENS_API_KEY,
  timeout: 120_000, // ms; SDK default is 10 minutes
  maxRetries: 2, // SDK default; retries connection errors, 408, 409, 429, 5xx
});
```

```ts title="hello.ts"
import { tokens } from "./lib/tokens";

const completion = await tokens.chat.completions.create({
  model: "deepseek/deepseek-v4.1-flash",
  messages: [
    { role: "system", content: "Reply in one short paragraph." },
    { role: "user", content: "What's the difference between interface and type in TypeScript?" },
  ],
  max_tokens: 400,
});

console.log(completion.choices[0]?.message.content);
console.log(completion.usage);
```

Code না ছুঁয়েই কাজ সারতে চাইলে SDK environment থেকে `OPENAI_BASE_URL` আর `OPENAI_API_KEY`-ও পড়ে নেয়। তবে খেয়াল রাখবেন, ওই environment-এ যত OpenAI-based tool আছে, এই variable সবগুলোর ওপরই প্রভাব ফেলবে।

Model ID copy করুন [/models](/models) থেকে বা `await tokens.models.list()` থেকে। আন্দাজে লিখবেন না।

## Chat completion stream করুন

```ts
const stream = await tokens.chat.completions.create({
  model: "deepseek/deepseek-v4.1-flash",
  messages: [{ role: "user", content: "List five git commands I should know, one per line." }],
  stream: true,
  stream_options: { include_usage: true },
});

for await (const chunk of stream) {
  const text = chunk.choices[0]?.delta?.content;
  if (text) process.stdout.write(text);
  if (chunk.usage) {
    console.log(`\n${chunk.usage.prompt_tokens} in, ${chunk.usage.completion_tokens} out`);
  }
}
```

`include_usage` চালু থাকলে শেষ chunk-এর `choices` array ফাঁকা থাকে, সেজন্যই code-এ optional chaining আছে। মাঝপথে থামাতে চাইলে loop থেকে `break` করুন অথবা `stream.controller.abort()` call করুন। আরও দেখুন [Streaming](/docs/streaming)।

## APIError দিয়ে error সামলান

2xx ছাড়া যেকোনো response `OpenAI.APIError`-এর একটা subclass throw করে। Tokens প্রতিটি error-এ একটা নির্দিষ্ট `code` বসিয়ে দেয়, আর HTTP status-এর চেয়ে সেটা থেকে অনেক বেশি জানা যায়।

```ts
import OpenAI from "openai";
import { tokens } from "./lib/tokens";

try {
  await tokens.chat.completions.create({
    model: "deepseek/deepseek-v4.1-flash",
    messages: [{ role: "user", content: "hi" }],
  });
} catch (err) {
  if (err instanceof OpenAI.APIConnectionError) {
    console.error("Network problem:", err.message);
  } else if (err instanceof OpenAI.APIError) {
    const requestId = err.headers?.get("x-tokens-request-id");
    console.error(err.status, err.code, err.message, requestId);

    switch (err.code) {
      case "insufficient_credits":
        // 402: top up at /dashboard/billing
        break;
      case "window_exhausted":
        // 429: plan window used up; Retry-After header = seconds until reset
        console.error("Resets in", err.headers?.get("retry-after"), "s");
        break;
      case "model_not_allowed_on_key":
      case "tier_permission_denied":
        // 403: key allow-list or plan doesn't cover this model
        break;
    }
  } else {
    throw err;
  }
}
```

দুটো কথা জেনে রাখুন:

- SDK-র সাম্প্রতিক version-এ `err.headers` একটা সাধারণ `Headers` object, তাই `.get()` দিয়ে পড়ুন। `x-tokens-request-id` সবসময় log করুন। আপনার request খুঁজে পেতে Support-এর এটা লাগে।
- SDK নিজে থেকেই `429` আর `5xx`-এ retry করে। আর `502`, `503` বা `504` ফেরত দেওয়ার আগেই gateway অন্য upstream source-এ চেষ্টা করে দেখে নিয়েছে। `window_exhausted`-এ `Retry-After` পার না হওয়া পর্যন্ত retry করে লাভ নেই, আর সেটা কয়েক ঘণ্টাও হতে পারে। তাই তাড়াতাড়ি fail করাতে চাইলে `maxRetries` কম রাখুন।

সব code-এর তালিকা [Errors](/docs/errors) পেজে, আর প্রতিটার সমাধান [Troubleshooting](/docs/troubleshooting) পেজে।

## Key server-এ রাখুন: Next.js route handler

Tokens CORS header পাঠায় না, তাই browser-এর JavaScript থেকে সরাসরি call করলে সেটা fail করবে। আর করলেও আপনার key ফাঁস হয়ে যেত। Call-টা একটা route handler-এর ভেতরে রাখুন, আর frontend থেকে সেই route-কে call করুন।

```ts title="app/api/chat/route.ts"
import OpenAI from "openai";

export const runtime = "nodejs";

const tokens = new OpenAI({
  baseURL: "https://tokens.bd/v1",
  apiKey: process.env.TOKENS_API_KEY, // server-only: no NEXT_PUBLIC_ prefix
});

type ChatMessage = { role: "user" | "assistant"; content: string };

export async function POST(req: Request) {
  const body = (await req.json()) as { messages?: ChatMessage[] };
  if (!Array.isArray(body.messages) || body.messages.length === 0) {
    return Response.json({ error: "messages required" }, { status: 400 });
  }

  try {
    const stream = await tokens.chat.completions.create(
      {
        model: "deepseek/deepseek-v4.1-flash", // pick on the server, not from the client
        messages: body.messages.slice(-20),
        max_tokens: 1024,
        stream: true,
      },
      { signal: req.signal } // stop paying for tokens if the user navigates away
    );

    const encoder = new TextEncoder();
    const text = new ReadableStream<Uint8Array>({
      async start(controller) {
        try {
          for await (const chunk of stream) {
            const delta = chunk.choices[0]?.delta?.content;
            if (delta) controller.enqueue(encoder.encode(delta));
          }
          controller.close();
        } catch (e) {
          controller.error(e);
        }
      },
    });

    return new Response(text, {
      headers: { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-store" },
    });
  } catch (err) {
    if (err instanceof OpenAI.APIError) {
      return Response.json(
        { error: err.code ?? "upstream_error", requestId: err.headers?.get("x-tokens-request-id") },
        { status: err.status ?? 502 }
      );
    }
    throw err;
  }
}
```

Client-এর দিকে লাগে শুধু সাধারণ একটা `fetch`, যা response body-কে stream হিসেবে পড়ে:

```ts
const res = await fetch("/api/chat", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ messages: [{ role: "user", content: "Hello" }] }),
});
const reader = res.body!.pipeThrough(new TextDecoderStream()).getReader();
for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  console.log(value);
}
```

Handler-এর কিছু সিদ্ধান্ত ইচ্ছে করেই নেওয়া। Model আর `max_tokens` server-এ ঠিক করা হয়েছে, যাতে কোনো visitor আপনার app-কে দামি model-এ নিয়ে যেতে না পারে। Message history ছেঁটে রাখা হয়েছে। এই route-এর সামনে নিজের authentication আর প্রতি user-এর rate limiting বসানোও উচিত, কারণ যে-ই route-টায় পৌঁছাতে পারবে, সে-ই আপনার ব্যালান্স খরচ করবে। মাসিক spend cap দেওয়া key ([API keys](/docs/api-keys)) ক্ষতির একটা শক্ত ঊর্ধ্বসীমা বেঁধে দেয়।

Vercel AI SDK ব্যবহার করলে `streamText` দিয়ে একই pattern [Vercel AI SDK](/docs/vercel-ai-sdk) পেজে দেখানো আছে।

---
Page: https://tokens.bd/bn/docs/nodejs
