@tansr/api-client
v0.2.0
Published
Thin zero-dependency TypeScript client for the Tansr gateway: OpenAI/Anthropic/Responses-shaped data plane, models catalog & usage, SSE streaming (public, MIT).
Maintainers
Readme
@tansr/api-client
Tansr 网关薄客户端:用平台 key 直连 Tansr 数据面(OpenAI / Anthropic / OpenAI
Responses 三协议形)与账面(模型目录 / 用量)。零依赖——只用运行时全局
fetch 与 Web Streams,不引 openai/anthropic SDK。
- 运行环境:Node ≥ 20.3(内建 fetch),或任何具备
fetch/ReadableStream的 现代浏览器 / 边缘运行时; - 请求体 passthrough:网关不做协议转换,字段语义与各上游协议一致,本包类型 只钉常用字段、其余原样放行。
获取 key
登录 Tansr 门户,在 /console → API Keys 创建 PAT(tansr_sk_ 前缀;scope
可选 data 或 data control——/v1/usage 需要 control)。登录态 access JWT
同样可用,两者均走 Authorization: Bearer。
三行起步
import { Tansr } from '@tansr/api-client';
const client = new Tansr({ apiKey: process.env.TANSR_API_KEY }); // 省略 apiKey 时自动读 env TANSR_API_KEY
const r = await client.chat.completions.create({
model: 'kimi-k3',
messages: [{ role: 'user', content: '你好' }],
});
console.log(r.choices?.[0]?.message?.content);
console.log(r.meta); // { requestId, cost, balance } —— 消费 x-tansr-* 响应头自建/私有部署传 baseUrl(含 /v1):new Tansr({ apiKey, baseUrl: 'http://127.0.0.1:8787/v1' })。
流式(SSE)
const stream = client.chat.completions.stream({ model: 'kimi-k3', messages });
for await (const chunk of stream) {
process.stdout.write(chunk.choices?.[0]?.delta?.content ?? '');
}
console.log(stream.meta); // 迭代完成后可取;流式无 x-tansr-cost(成本流末才可知),cost 恒 null- OpenAI 系(chat / responses):
data: [DONE]哨兵终止; - Anthropic(messages):
event:行语义已归一到帧对象的type字段,流体自然 结束即止; - 提前
break会取消上游读,网关按断链结算,不留悬挂请求; - 流对象单次可迭代。
三协议同型 API 面
| 径 | 非流式 | 流式 |
|----|--------|------|
| OpenAI chat | client.chat.completions.create(params) | client.chat.completions.stream(params) |
| Anthropic messages | client.messages.create(params) | client.messages.stream(params) |
| OpenAI Responses | client.responses.create(params) | client.responses.stream(params) |
目录与账面:
const models = await client.models.list();
const usage = await client.usage.get({ window: '7d', groupBy: 'model' }); // 需 control scope错误处理
import { TansrApiError, TansrConnectionError } from '@tansr/api-client';
try {
await client.chat.completions.create({ model, messages });
} catch (err) {
if (err instanceof TansrApiError) {
// 网关错误信封 { error: { code, message, requestId, detail? } } 的映射
console.error(err.status, err.code, err.requestId, err.detail);
if (err.status === 402) {/* 余额不足:detail 携 balance / estimatedCost */}
if (err.status === 429) {/* 限流:err.retryAfterMs 由 Retry-After 头换算(服务端原值,毫秒) */}
} else if (err instanceof TansrConnectionError) {
// 网络故障 / 超时(cause 保真底层错误)
}
}上游 4xx/5xx 由网关原样透传,本包尽力从透传体提取 code/message,兜底
http_<status>。错误对象永不携带 api key。
取消与超时
const ac = new AbortController();
const p = client.messages.create(params, { signal: ac.signal, timeoutMs: 30_000 });
ac.abort(); // 用户取消:原样抛调用方的 AbortError,不做包装timeoutMs(构造器缺省 600_000)是一次调用的总预算:以调用起点计,覆盖整个
请求生命周期(含流式全程),也含幂等 GET 的 429 重试等待与后续尝试——重试不会
重新起算。预算耗尽抛 TansrConnectionError(timed out after <timeoutMs>ms)。
重试纪律
幂等 GET(
models.list/usage.get)的 429 自动重试:尊重Retry-After(秒、小数秒或 HTTP-date;缺失 / 不可解析时回落 1 s、2 s 退避),上限 2 次;单次等待钳 30 s 上限(
Retry-After来自对端,不让远端决定客户端悬挂时长);等待与重试都在
timeoutMs总预算之内:剩余预算盖不住(钳制后的)等待时不再等待、 不再重试,直接抛出该 429 的TansrApiError——status === 429,retryAfterMs携服务端原值(不受 30 s 钳制影响),调用方可据此自行排程:try { await client.models.list({ timeoutMs: 5_000 }); } catch (err) { if (err instanceof TansrApiError && err.status === 429 && err.retryAfterMs !== null) { // 预算内放弃重试:按服务端建议自行排程,而不是让本次调用悬挂 scheduleRetry(err.retryAfterMs); } }POST 数据面一律不自动重试:网关按请求预扣与结算(计费面),盲重试会 重复计费——失败原样抛出,由调用方决策。
meta 挂载说明
非流式返回值的 meta 是不可枚举属性:JSON.stringify(响应) 不含 meta,
持久化原始响应体不被污染;注意 { ...res } 展开会丢弃它,先取后展开。
License
MIT
