@flop-labs/client

中文文档
0.2.2发布于 2026/11/08MIT OR Apache-2.0TypeScriptES Module近 30 天下载 3008

Fetch client for FLOP public model discovery and gateway chat completions

原始使用文档
文档目录4 节
  1. Streaming chat completions
  2. Caller-owned replay identity
  3. Recovering an ambiguous request
  4. Task correlation

@flop-labs/client

A small ESM Fetch client for FLOP's public gateway, for browsers and Node.js 22 or later. Its only runtime dependency is @flop-labs/contracts.

pnpm add @flop-labs/client
import { FlopClient, FlopHTTPError } from "@flop-labs/client";

const client = new FlopClient({ apiKey: "your-flop-api-key" });
const models = await client.listModels();
const model = await client.getModel(models.data[0].id);

try {
  const completion = await client.createChatCompletion({
    model: model.data.id,
    messages: [{ role: "user", content: "Hello" }],
    stream: false,
  }, { signal: AbortSignal.timeout(30_000) });
  console.log(completion.choices);
} catch (error) {
  if (error instanceof FlopHTTPError) {
    console.log(error.status, error.retryAfter);
    // error.body retains the gateway's structured error, or non-JSON text.
  }
  throw error;
}

The default baseURL is https://api.flop.network/v1, the public alias documented in services/rest-gateway/README.md. An origin-only URL gets /v1 appended. An explicit API path must end in /v1 (including /api/v1); trailing slashes are removed. HTTP(S) URLs with credentials, queries, or fragments are rejected. Redirects are never followed; browsers may expose them as an opaque HTTP error with status 0.

Model discovery needs no API key and sends no bearer credential. Chat completions and receipt lookup require apiKey and send it only to the configured gateway. Keep privileged credentials on a server. fetch can be injected for tests or a compatible transport. Each method accepts an optional caller signal. Requests use credentials: "omit" and redirect: "manual".

Requests are validated before network access; successful JSON responses are checked structurally using the shared contracts. Provider extensions and receipt fields are returned unchanged. These are gateway-reported data: the client does not verify receipts, attestations, payments, chain inclusion, or finality.

The client supports listModels(), getModel(id), createChatCompletion(request) (JSON), streamChatCompletion(request) (SSE), and getCredentialReceipt(requestId). Wallet operations, x402 funding, and grant minting are outside this surface. Paid requests are sent once: there are no automatic retries, including on 429, cancellation, or transport failure. A timeout does not establish that the gateway did no work; resolve that outcome before manually repeating a paid call.

listModels also accepts offset (a non-negative integer), limit (1–1000), output_modalities and supported_parameters (arrays of names), alongside signal. These serialize to the public model feed's query parameters; model detail ids are encoded as one path segment.

FlopHTTPError retains status, body, and the raw retryAfter header. Its message excludes request credentials and gateway body content. FlopValidationError reports malformed configuration, input, or successful responses. FlopTransportError preserves the underlying transport failure as cause; caller cancellation propagates the signal's reason. Avoid logging error bodies or causes without applying your application's data policy.

Streaming chat completions

streamChatCompletion returns an async generator. It dispatches one POST when first iterated, forces stream: true, and rejects an explicit stream: false. It yields structurally validated ChatCompletionChunk objects and returns a ChatCompletionStreamResult on successful termination. Use next() to retain that terminal return value (for await consumes chunks but discards the return value):

const stream = client.streamChatCompletion({
  model: "flop/model",
  messages: [{ role: "user", content: "Hello" }],
}, { signal: AbortSignal.timeout(30_000) });

while (true) {
  const next = await stream.next();
  if (next.done) {
    console.log(next.value.finishReason, next.value.usage, next.value.receipt);
    console.log(next.value.toolCalls); // Complete calls assembled by index.
    break;
  }
  console.log(next.value.choices[0]?.delta.content ?? "");
}

The iterator enforces the role/content/tools/finish/usage/[DONE] lifecycle. Only a valid success finish, usage/receipt frame, [DONE], and a closed body produce a terminal result. No result is returned on early iterator closure; closing it cancels the body reader. The client reads through EOF to reject data frames after [DONE].

Before a stream opens, non-success HTTP statuses throw FlopHTTPError, retaining the body and Retry-After. HTTP 200 requires text/event-stream; a JSON replay response to a streaming request is rejected. In-band failures throw FlopStreamError with body, usageSource ("estimated" when supplied), retryAfter (seconds), and outputStreamed. Receipt usage and estimated error usage remain distinct. Malformed frames or ordering throw FlopValidationError. Missing [DONE] or a transport failure throws FlopStreamInterruptionError, with the cause when known.

Aborting rejects with the signal's exact reason and cancels the reader. Cancellation stops reading; it does not undo accepted work or payment. No error or interruption triggers a retry. Reconciliation and any replay are the caller's decision.

Caller-owned replay identity

Both paid methods accept { idempotencyKey } alongside signal. The key is sent as Idempotency-Key, must contain 1–128 printable ASCII characters without whitespace, and is rejected on discovery GETs. The client never creates a key or retries for you. For durable recovery use createChatCompletion with stream: false: the gateway stores a completed JSON response for replay, rather than a replayable SSE body.

Recovering an ambiguous request

Persist the request ID, original correlation ID, exact request body and gateway URL before the first paid POST. Idempotency-Key is the request ID unless the caller also supplies X-Request-ID, which takes precedence. After a lost response, use getCredentialReceipt with that same ID before considering replay:

import { FlopClient, FlopHTTPError } from "@flop-labs/client";

// Loaded from the application's durable job record; no new ID is generated here.
async function observeLostResponse(client: FlopClient, originalRequestId: string) {
  try {
    const receipt = await client.getCredentialReceipt(originalRequestId, {
      signal: AbortSignal.timeout(10_000),
    });
    if (receipt.state === "reserved" || receipt.state === "estimated") {
      return { action: "wait-or-reconcile" as const, receipt };
    }
    return { action: "review-terminal-observation" as const, receipt };
  } catch (error) {
    if (error instanceof FlopHTTPError && error.status === 404) {
      return { action: "investigate-missing-record" as const };
    }
    // 401: obtain authorized parent access; timeout/transport failure: outcome unknown.
    throw error;
  }
}

This example submits no inference and logs no credentials, prompts or outputs. Neither a missing row nor a timeout proves that paid work did not occur. They must not trigger automatic replacement paid work. If an operator subsequently chooses JSON replay, use the original credential, ID, correlation ID, body and route; an active parent may inspect its child's ledger but cannot replay as that child.

The lookup uses GET /keys/me/receipts/{request_id} and reads an exact, case-sensitive ID independently of the latest-20 list. IDs follow the client's 1–128 printable ASCII token rule; . and .. path segments are invalid. The client URL-encodes the ID and parses the response as CredentialReceipt. Malformed IDs fail locally with FlopValidationError (HTTP callers receive 400 invalid_request_id); 401 denies missing/invalid/expired/revoked authority; 404 receipt_not_found covers both absent and out-of-scope records without disclosing another credential's requests.

An active parent sees its own and all child requests. A child sees only its own for its existing introspection/session lifetime, even after its uses are exhausted or presentation window closes. Lookup neither presents a new session nor extends one. Child expiry or revocation denies recovery with that child; use its authorized, active parent instead. Parent expiry or revocation denies both. Expired child grant metadata can be pruned after unresolved requests finish, while retained ledger rows remain readable by the active parent.

reserved holds quota without a completion checkpoint; estimated retains the reservation after completion with actual spend unresolved. settled records reconciled spend and released records returned reservation quota. A released row does not invent zero settlement evidence. response_status describes the cached HTTP checkpoint independently of settlement. A fresh lookup can become settled while the original cached JSON completion still reports pending; replay returns that unchanged checkpoint. No prompt, output or cached completion body is returned by lookup.

reserved_spend and settled_spend are legacy floating-point FLOP quota values. settled_spend_raw, when available, is the exact allocation as a decimal integer string in raw FLOP base units: 10^18 raw = 1 FLOP. Unknown amounts, channel IDs and terminal events remain null. terminal_event identifies available terminal channel evidence; pooled requests share a channel event and allocation, not separate per-request miner-payment or burn transactions. These observations are gateway data, not independent cryptographic verification or a promise of immediate settlement.

Recovery covers only the configured gateway's retained SQLite ledger. There is no automatic ledger TTL today, but no archival or cross-gateway guarantee: database loss, operator deletion or switching gateways can make records unavailable. Preserve your own job identity and evidence under your application's retention policy.

Task correlation

The gateway groups an agent task's turns by X-Correlation-ID. Call client.task() once per task and make that task's paid calls through the returned handle: each call sends the same random id, and the next task() call starts a new one.

const task = client.task(); // or client.task("job-42") to supply your own id
await task.createChatCompletion({ model: "qwen/qwen3-8b", messages });
await task.createChatCompletion({ model: "qwen/qwen3-8b", messages: [...messages, reply, toolResult] });

Either method also accepts { correlationId } directly. The value follows the idempotency-key token rules and is rejected on discovery GETs. Do not reuse one id across tasks, because the gateway would merge them into one session. A replay under an idempotency key must send its original correlation id.

版本与发布记录

@flop-labs/client 共发布 3 个版本,首次发布于 2026/10/29;过去 12 个月发布了 3 个版本。

版本发布日期距今
0.2.2latest2026/11/087 天前
0.2.12026/10/2917 天前
0.0.0-stage2026/10/2917 天前

关于 @flop-labs/client 的常见问题

@flop-labs/client 是什么?

Fetch client for FLOP public model discovery and gateway chat completions

如何安装 @flop-labs/client?

在项目目录运行 npm install @flop-labs/client,或使用 pnpm add / yarn add / bun add。

@flop-labs/client 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@flop-labs/client 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@flop-labs/client 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@flop-labs/client 可以商用吗?

@flop-labs/client 使用 MIT OR Apache-2.0 许可。可在 MIT、Apache-2.0 中任选其一遵守。选择 MIT 时,宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@flop-labs/client 的下载量有多少?

根据 npm 官方下载统计,@flop-labs/client 在 2026/10/15 – 2026/11/13 这 30 天内被下载 3,008 次,日均约 100 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@flop-labs/client 还在维护吗?

最新版本 0.2.2 发布于 2026/11/08(7 天前);至今共发布 3 个版本,其中过去 12 个月发布了 3 个;首次发布于 2026/10/29。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。