@coloop-ai/respondent-sdk

中文文档
0.1.0发布于 2026/09/22MITTypeScriptESM + CJS近 30 天下载 1.9万

Type-safe SDK for the Respondent.io Partner API, generated from the provider's OpenAPI spec.

原始使用文档
文档目录18 节
  1. Installation
  2. Quick start
  3. Environments
  4. Authentication
  5. Error handling
  6. Request timeouts
  7. Cancelling a request
  8. Observing requests
  9. Pagination
  10. High-level methods
  11. Webhooks
  12. What the provider documents, and what it does not
  13. Generated Zod schemas
  14. Generated client access
  15. Known quirks in the provider's spec
  16. Scripts
  17. Node versions
  18. License

@coloop-ai/respondent-sdk

License: MIT TypeScript

Type-safe client bindings for the Respondent.io Partner API. Operations are generated from the provider's OpenAPI document and wrapped in an ergonomic SDK.

⚠️ This project is maintained by CoLoop and is not affiliated with or endorsed by Respondent.io — review their API terms before use.

Installation

pnpm add @coloop-ai/respondent-sdk

zod is a runtime dependency (^3.25.0 || ^4.0.0); the webhook schemas and the generated Zod schemas import from zod/v4.

Quick start

import { RespondentSdk } from '@coloop-ai/respondent-sdk'

const respondent = new RespondentSdk({
  apiKey: process.env.RESPONDENT_API_KEY!, // sent as x-api-key
  apiSecret: process.env.RESPONDENT_API_SECRET!, // sent as x-api-secret
  timeoutMs: 10_000, // abort requests that hang for 10s
})

// Lookup IDs differ between staging and production, so resolve them at
// runtime rather than hardcoding them.
const topics = await respondent.lookups.topics({ pageSize: 1 })
const topicId = topics.results[0]?.id
if (!topicId) throw new Error('No project topics on this environment')

const project = await respondent.projects.create({
  publicTitle: 'Coffee habits study',
  publicInternalName: 'coffee-habits-q1',
  targetMarketType: 'b2c',
  typeOfResearch: 'remote',
  targetResearchMethodology: 'oneOnOne',
  participantTimeRequiredMinutes: 30,
  incentiveAmount: 50,
  targetNumberOfParticipants: 5,
  externalResearcher: {
    researcherId: 'researcher-1',
    researcherName: 'Ada Lovelace',
    bookingUrl: 'https://example.com/book',
  },
  targetProjectTopics: [topicId],
})

await respondent.screenerQuestions.create(project.id, {
  text: 'How often do you drink coffee?',
  questionType: 'radio',
  answers: [
    { text: 'Daily', answerValue: 1 }, // 1 qualifies
    { text: 'Never', answerValue: 2 }, // 2 disqualifies
  ],
})

// `publicDescription` is required at publish time, so set it first.
await respondent.projects.update(project.id, {
  publicDescription: 'A 30 minute chat about how you drink coffee.',
})

await respondent.projects.publish(project.id)

Then walk each applicant through the recruitment lifecycle:

const responses = await respondent.screenerResponses.list(project.id, {
  status: 'PENDING',
})

for (const response of responses.results ?? []) {
  await respondent.screenerResponses.qualify(project.id, response.id, {
    qualifyStatus: true,
    qualifiedOverriden: false,
    disqualifyReasons: [],
    message: '',
  })

  await respondent.screenerResponses.invite(project.id, response.id, {
    meetingLink: 'https://example.com/session',
    bookingLink: 'https://example.com/book',
  })
}

// Then, in a later request — only once the session has actually happened,
// because this is what starts the incentive payment:
//
//   await respondent.screenerResponses.markAttended(projectId, responseId)

A runnable version lives in examples/recruit-participants-demo.ts.

Environments

EnvironmentBase URL
Productionhttps://api.respondent.io
Staginghttps://api-staging.respondent.io

Production is the default. Target staging by passing the exported constant:

import {
  RESPONDENT_STAGING_BASE_URL,
  RespondentSdk,
} from '@coloop-ai/respondent-sdk'

const respondent = new RespondentSdk({
  apiKey: process.env.RESPONDENT_STAGING_API_KEY!,
  apiSecret: process.env.RESPONDENT_STAGING_API_SECRET!,
  baseUrl: RESPONDENT_STAGING_BASE_URL,
})

Staging and production are separate worlds: separate credentials, separate webhooks, and different lookup IDs for industries, skills, topics and job titles. Resolve lookup IDs at runtime with respondent.lookups.* instead of hardcoding them.

The provider's OpenAPI document only declares the staging server. The production URL comes from their docs, and the generated client's default is pinned to production in openapi-ts.config.ts.

Authentication

Every request carries two headers, x-api-key (Client ID) and x-api-secret (Client Secret). Credentials are issued by the Respondent Partner team; they are not self-serve, and production access requires a reviewed staging demo and a signed MSA. There is no OAuth flow and no documented scope model.

The credentials and the configured client are held in #private fields, and no accessor exposes them, so logging or serialising a RespondentSdk instance cannot print them, and errors carry a redacted request summary rather than the Request object. Nothing exported from the package reaches the configured client either: the generated operations are plain functions that take a client as an argument, so there is no class holding a registry of the clients ever constructed.

Server-controlled text is scrubbed as well. Response headers are reduced to a short allow-list of names — a header NAME is server-controlled too, so one that is not on the list is dropped along with its value — and the surviving values, along with statusText and the payload, have any occurrence of the key or the secret replaced with [redacted]. The message, code and detail are read back out of the scrubbed payload, so they are covered too. A gateway that reflects x-api-key back in X-Request-Id, or quotes it in an error message, or answers with a header named after it, cannot put it on an error. cause is the underlying error, attached unchanged.

The SDK also sets redirect: 'error'. Node forwards custom headers across an origin-changing redirect — unlike Authorization — so following a redirect would hand x-api-key and x-api-secret to whatever host the redirect names. A redirected request fails with a RespondentSdkTransportError instead.

Error handling

Every SDK-originated failure raises a subclass of RespondentSdkError, so the failure modes are distinguishable:

ErrorRaised whencause
RespondentSdkApiErrorthe API answered with a non-2xx status (status, statusText, payload, responseHeaders, code, detail)none — read payload
RespondentSdkTransportErrorno response arrived — DNS, connection reset, or a refused redirectthe underlying fetch error
RespondentSdkResponseErrora success status whose body could not be decoded (status, statusText, responseHeaders)the SyntaxError
RespondentSdkTimeoutErrorthe configured timeoutMs elapsed (timeoutMs)none — read timeoutMs

One failure is deliberately not an SDK error: when you abort a call through your own AbortSignal, it rejects with your signal.reason exactly as you set it.

Each SDK error carries a redacted request summary on request (url, method, and headers with x-api-key / x-api-secret replaced by [redacted]), and carries cause only where an underlying error exists. No error carries the Response: RespondentSdkApiError and RespondentSdkResponseError expose status, statusText and a responseHeaders map instead. That map holds only the allow-listed headers — retry-after, the rate-limit headers, content-type, date and the request-id headers — with their values scrubbed; every other header is dropped, name included. isRespondentSdkError matches any of them; isRespondentSdkApiError, isRespondentSdkTransportError, isRespondentSdkResponseError and isRespondentSdkTimeoutError narrow.

import {
  RespondentSdk,
  isRespondentSdkApiError,
} from '@coloop-ai/respondent-sdk'

try {
  await respondent.projects.close(projectId, {})
} catch (error) {
  if (!isRespondentSdkApiError(error)) throw error

  if (error.status === 429) {
    console.warn('Respondent throttled request', error.payload)
    return
  }

  throw new Error(
    `Failed to close project (${String(error.status)}): ${error.message}`,
  )
}

The provider does not document an error response schema, and their OpenAPI document declares no error bodies. RespondentSdkApiError therefore probes the error, message and detail fields for a human-readable message and falls back to the HTTP status line. error.payload always holds the raw body.

Rate limits are documented as 250 requests/second (burst 500, 1,000,000/day) in production and 50 requests/second (burst 100, 200,000/day) in staging. The 429 response body and any Retry-After header are undocumented.

Request timeouts

Set timeoutMs to abort requests that exceed a duration. The deadline covers reading the response body, not just receiving the response headers, so a server that answers and then stalls still trips it. The SDK rejects with a RespondentSdkTimeoutError, which you can treat as retryable.

It must be a positive number of at most 2_147_483_647 (about 24.8 days), and the constructor throws otherwise. setTimeout wraps a longer delay round to 1ms, so a very long deadline would abort every request almost immediately.

import {
  RespondentSdk,
  RespondentSdkTimeoutError,
} from '@coloop-ai/respondent-sdk'

const respondent = new RespondentSdk({
  apiKey: process.env.RESPONDENT_API_KEY!,
  apiSecret: process.env.RESPONDENT_API_SECRET!,
  timeoutMs: 5_000,
})

try {
  await respondent.projects.list()
} catch (error) {
  if (error instanceof RespondentSdkTimeoutError) {
    console.warn('Respondent request timed out, retrying shortly')
  }
}

Cancelling a request

Every operation takes an optional last argument carrying an AbortSignal:

const controller = new AbortController()
setTimeout(() => controller.abort(new Error('user navigated away')), 1_000)

await respondent.projects.list(
  { status: 'DRAFT' },
  {
    signal: controller.signal,
  },
)

The call rejects with the signal's reason exactly as you set it. A configured timeoutMs still applies alongside your signal; whichever fires first wins.

Observing requests

Pass onRequestSettled to hear about every request once it has settled, whether it succeeded or failed. The event carries the redacted request summary (url, method, and headers with the credentials replaced by [redacted]), the response status when one arrived, durationMs, and error when the call rejects: the SDK error for a non-2xx status, a transport failure or a timeout, or your own abort reason. It is meant for logging and metrics, and must not throw.

const respondent = new RespondentSdk({
  apiKey: process.env.RESPONDENT_API_KEY!,
  apiSecret: process.env.RESPONDENT_API_SECRET!,
  onRequestSettled: (event) => {
    const { method, url } = event.request
    if (event.error === undefined) {
      console.info(`respondent ${method} ${url} ${String(event.status)}`)
    } else {
      console.error(`respondent ${method} ${url} failed`, event.error)
    }
  },
})

One edge: a 2xx whose body cannot be decoded is reported with its status and no error, because the response itself was fine. The call still rejects with a RespondentSdkResponseError.

Pagination

List endpoints take page (default 1), pageSize (default 50) and includeCount (default false), and return { totalResults?, page, pageSize, results }. totalResults is only present when you pass includeCount: true.

High-level methods

RespondentSdk groups operations into modules:

  • respondent.projects — list, create, retrieve, update, delete, copy, publish, pause, close, audienceSizeEstimate, replaceExternalScreenerQuestions, uploadFile, pitchSuggestions.
  • respondent.screenerQuestions — list, create, replaceAll, retrieve, update, delete, reorder.
  • respondent.screenerResponses — list, retrieve, payoutSummary, qualify, invite, schedule, markAttended, markNoShow, reject, report, favorite, hide, cancelInvite, cancelBooking, cancelBookingAndReinvite, participantCancelBooking, ingestExternalScreenerAnswers, payout.
  • respondent.quota — create, retrieve, update, delete.
  • respondent.webhooks — list, create, retrieve, deactivate, listEventTypes, simulate.
  • respondent.pricing — balanceSummary.
  • respondent.profiles — retrieve, createTestParticipant (staging only).
  • respondent.teamRespondents — list, retrieve, batchInvite.
  • respondent.messaging — conversations (list, create, retrieve, update, markAsRead, addParticipant, removeParticipant) and messages (list, retrieve, inbox, create). Convenience delegates (listConversations, createConversation, listMessages, createMessage, inbox) sit on the module itself.
  • respondent.lookups — values, industries, jobTitles, skills, topics.

Endpoints the provider marks deprecated (GET /v1/teams/{teamId}/projects, POST /v1/messaging/messages, the team-scoped inbox, and the deprecated add-participant route) are not wrapped. They remain reachable through the generated client.

Webhooks

Webhook payloads and the signature scheme are not in the provider's OpenAPI document, so @coloop-ai/respondent-sdk/webhooks hand-models them with Zod, and adds signature verification.

The order matters: verify the bytes, store the event and claim its id in one atomic step, answer 2xx, and only then do the work — reading it back from the stored record.

The invariant that makes this durable: a stored row stays pending until the work succeeds, and the only thing that starts the work is a poll over pending rows, so nothing outside the claiming transaction can strand a delivery.

import type { UnknownWebhookEvent } from '@coloop-ai/respondent-sdk/webhooks'
import {
  WebhookEvent,
  getWebhookSignatureHeader,
  verifyAndDedupeWebhook,
} from '@coloop-ai/respondent-sdk/webhooks'

/**
 * Write the whole event down and claim its `uuid`, in ONE atomic step — a
 * unique index on `uuid` plus an "insert, ignore conflict" statement that
 * stores the payload alongside it.
 *
 * Inserting the `uuid` on its own is NOT "processed". It records only that the
 * event is safely on disk. Two concurrent copies of a replay both look new if
 * the check and the claim are separate steps.
 */
async function claimAndStore(
  event: UnknownWebhookEvent,
): Promise<'stored' | 'duplicate'> {
  const inserted = await db
    .insertInto('respondent_webhook_inbox')
    .values({ uuid: event.uuid, payload: JSON.stringify(event) })
    .onConflict((c) => c.column('uuid').doNothing())
    .executeTakeFirst()
  return inserted.numInsertedOrUpdatedRows === 1n ? 'stored' : 'duplicate'
}

export async function handleWebhook(request: Request) {
  // Read the RAW BYTES, before any JSON parsing. Not `request.text()`: that
  // decodes leniently, so it has already replaced invalid UTF-8 with U+FFFD
  // before the SDK can reject the body.
  const rawBody = new Uint8Array(await request.arrayBuffer())

  const outcome = await verifyAndDedupeWebhook({
    rawBody,
    signatureHeader: getWebhookSignatureHeader(request.headers),
    privateKey: process.env.RESPONDENT_WEBHOOK_PRIVATE_KEY!,
    claimAndStore,
  })

  if (outcome.status === 'invalid_signature') {
    return new Response('Forbidden', { status: 403 })
  }
  if (outcome.status === 'stored') {
    // A nudge, not the trigger. The row is already pending on disk and the
    // poll below picks it up regardless, so a failed wake-up strands nothing —
    // which is why its failure is swallowed rather than answered non-2xx.
    await wakeWebhookWorker().catch(() => {})
  }
  // A duplicate or an unparseable body is 2xx too: the provider retries
  // anything else, and a replay must not do the work twice.
  return new Response('OK')
}

The work is started by a poll over the rows that are still pending, so it never depends on the wake-up call above having succeeded:

// Run this on a schedule, and on each wake-up.
async function drainPendingDeliveries() {
  const pending = await db
    .selectFrom('respondent_webhook_inbox')
    .select(['uuid'])
    .where('processedAt', 'is', null)
    .orderBy('receivedAt')
    .limit(100)
    .execute()

  for (const row of pending) {
    await processStoredDelivery(row.uuid)
  }
}

async function processStoredDelivery(uuid: string) {
  const row = await db
    .selectFrom('respondent_webhook_inbox')
    .select(['payload'])
    .where('uuid', '=', uuid)
    .where('processedAt', 'is', null)
    .executeTakeFirst()
  if (!row) return // already processed, or never stored

  const parsed = WebhookEvent.safeParse(JSON.parse(row.payload))
  if (parsed.success) {
    switch (parsed.data.event) {
      case 'SCREENER_RESPONSES.CREATED': {
        const { id, projectId } = parsed.data.payload.resource
        // Payloads carry IDs only — fetch the response to read it.
        const response = await respondent.screenerResponses.retrieve(
          projectId,
          id,
        )
        break
      }
      case 'PROJECTS.UPDATED': {
        console.log(parsed.data.payload.resource.updatedFields)
        break
      }
    }
  } else {
    console.warn('Unhandled webhook payload', parsed.error)
  }

  // Only now is the delivery processed.
  await db
    .updateTable('respondent_webhook_inbox')
    .set({ processedAt: new Date() })
    .where('uuid', '=', uuid)
    .execute()
}

Doing the business work inside the request handler, after the claim, is how a delivery gets lost: the uuid is already claimed, so when the work throws, the provider's retry comes back as a duplicate and the work never happens. Storing the event first makes that retry unnecessary — the record is on disk, and a throw leaves it pending for the next attempt.

Enqueueing after claimAndStore returns fails the same way when the enqueue is the only trigger: the row commits, the enqueue throws, the provider's retry reads as a duplicate, and nothing ever runs the work. Either write the job inside the same transaction as the inbox row, or — as above — poll the pending rows and treat the enqueue as an optimisation.

A Uint8Array body is decoded with fatal UTF-8 validation once the signature has verified, so a body that is not valid UTF-8 comes back as malformed_body rather than being repaired. The lenient decoder substitutes U+FFFD for an invalid sequence, JSON.parse accepts the repaired text, and the event you would then store is one the provider never sent. That check only works on bytes: pass new Uint8Array(await request.arrayBuffer()), because await request.text() has already done the lenient decode for you and a string rawBody is taken as it arrives.

Verify by hand instead if you do not want the replay check: verifyWebhookSignatureFromRawBody hashes the wire bytes, and verifyWebhookSignatureFromParsedBody hashes JSON.stringify(parsedBody). Read the next section before choosing.

The five event types are PROJECTS.UPDATED, SCREENER_RESPONSES.CREATED, SCREENER_RESPONSES.UPDATED, MESSAGES.CREATED and CONVERSATIONS.CREATED. WebhookEvent doubles as the discriminated-union type (import type { WebhookEvent }) and the union validator (WebhookEvent.safeParse). Every event schema is a loose object, so fields the provider adds later are preserved rather than stripped. UnknownWebhookEvent accepts event types this SDK does not model yet.

What the provider documents, and what it does not

The privateKey used for signing is returned by POST /v1/webhooks, and also by GET /v1/webhooks and GET /v1/webhooks/{webhookId} — all three answer with the same WebhookDto — so a lost key can be read back rather than re-created. The provider's only signing sample shows an HMAC over the body, base64-encoded, delivered in the Respondent-Webhook-Signature header as <algorithm>=<signature>.

Three gaps matter:

  1. The hash algorithm is not documented. Their sample parses the algorithm out of the header value itself and publishes no example header. Because the header is attacker-controlled, verification only accepts algorithms on an allow list (sha256, sha512 by default). Widen it with allowedAlgorithms once the provider confirms which one they send.
  2. Which bytes they sign is not stated anywhere. Their sample hashes JSON.stringify(request.body) — a parsed and re-serialised object — which is not guaranteed to reproduce the bytes that arrived on the wire. This SDK ships both readings and claims neither is proven: verifyWebhookSignatureFromRawBody (the default, and what verifyAndDedupeWebhook uses) hashes the delivered bytes, and verifyWebhookSignatureFromParsedBody hashes JSON.stringify(parsedBody). Only a real delivery settles this. Point a webhook at an endpoint that logs the raw body and the signature header, send one with respondent.webhooks.simulate(webhookId, { event }), and see which helper returns true. Until you have done that, treat the choice as unverified.
  3. There is no timestamp header and no replay protection. HMAC proves authenticity, not freshness, and the provider retries a delivery up to five times. Deduplicate on the event uuid — verifyAndDedupeWebhook does it for you, given an atomic claimAndStore callback that writes the event down as it claims the id.

Two smaller hardening notes: a delivery carrying more than one signature header is treated as unsigned, and a digest is decoded only if it is canonical padded base64. Buffer.from(value, 'base64') on its own silently accepts trailing junk, and accepts several spellings of the same digest.

Other operational facts: each team has exactly one active webhook, events fire only for projects created via the API, you must return 2xx within 3 seconds, and failures are retried up to five times at ten-minute intervals. Webhooks configured in staging do not carry over to production.

Generated Zod schemas

Every OpenAPI component schema is also emitted as a Zod schema under @coloop-ai/respondent-sdk/zod, named z<SchemaName>. Use them to compose the provider's enums into your own schemas:

import * as z from 'zod/v4'
import { zGender, zScreenerResponseStatus } from '@coloop-ai/respondent-sdk/zod'

const Participant = z.object({
  gender: zGender,
  status: zScreenerResponseStatus,
})

The generated file imports from zod/v4, which resolves on both [email protected]+ and zod@4.

Generated client access

If you need full control over request options, call a generated operation directly. Each one is a plain function that takes the client you configured; there is no generated class, and so nothing that keeps a registry of configured clients:

import { getV1Projects, sdk } from '@coloop-ai/respondent-sdk'

const client = sdk.createClient({
  baseUrl: 'https://api.respondent.io',
  headers: {
    'x-api-key': process.env.RESPONDENT_API_KEY!,
    'x-api-secret': process.env.RESPONDENT_API_SECRET!,
  },
  responseStyle: 'data',
})

const response = await getV1Projects({
  client,
  headers: {
    'x-api-key': process.env.RESPONDENT_API_KEY!,
    'x-api-secret': process.env.RESPONDENT_API_SECRET!,
  },
  query: { status: 'DRAFT' },
})

The provider declares x-api-key and x-api-secret as header parameters rather than as an OpenAPI security scheme, so the generated types require them per call even when the client already sets them. Three operations — GET /v1/team-respondents, GET /v1/team-respondents/profiles/{profileId} and PUT /v1/team-respondents/batch-invite — declare no header parameters at all, which is almost certainly an omission in the spec rather than an unauthenticated route; the SDK sends the credentials on every request regardless.

responseStyle accepts 'data' (the response body only) or 'fields' ({ data, request, response }). RespondentSdk uses 'fields' internally and returns the body.

All request/response types are exported from @coloop-ai/respondent-sdk.

Known quirks in the provider's spec

Recorded here so they are not mistaken for SDK bugs:

  • CreateB2cProjectDto.targetGenders / CreateB2bProjectDto.targetGenders is a single Gender, while every other demographic target (targetEthnicities, targetAgeGroups, targetEducation, targetHouseholdIncome) is an array. The plural name is misleading.
  • QualifyScreenerResponseDto marks all four fields required, including the misspelled qualifiedOverriden and the disqualifyReasons / message fields that only apply when disqualifying.
  • ProfileLocation.zipcode and ScreenerResponseInvitation.message declare "type": "string" with "default": null. Vendoring strips those defaults — without that the Zod generator emits code that does not compile.
  • ScreenerResponsesPaginatedDto does not mark results required, while the other paginated DTOs do, so response.results is optional there alone.
  • PUT /v1/projects/{projectId}/files/form-data declares Content-Type and Content-Length as required header parameters, and leaves the uploadFile body property optional. Vendoring removes both header parameters and makes the file required: Fetch sets the content type — with the multipart boundary, which no caller can know in advance — and the length itself, and an upload with no file is never a useful call.
  • Three team-respondent operations declare no x-api-key / x-api-secret header parameters while every other operation does.
  • The document declares no security scheme, no error schemas and no 429 responses, and its servers block lists staging only.

scripts/normalize-openapi.ts holds all three normalisations, reports each one when the schema is refreshed, and pnpm run schema:validate fails if the vendored file ever drifts out of that normal form.

Scripts

  • pnpm run schema:update – the only script that touches the network: fetch the provider's OpenAPI document, normalise it, validate it in memory, then replace schemas/openapi.json atomically.
  • pnpm run generate – validate the vendored spec and regenerate src/generated/** from it. Offline and reproducible: the same commit always generates the same output.
  • pnpm run generate:check – regenerate into a scratch directory and fail if it differs from the committed src/generated.
  • pnpm run schema:validate – validate the vendored spec and check it is still normalised.
  • pnpm run tc – type-check without emitting.
  • pnpm run lint – ESLint across the repository.
  • pnpm run test – Vitest.
  • pnpm run test:dist – smoke-test the built bundles with node --test. No dev toolchain, so it also runs on Node 18. Build first.
  • pnpm run format – Prettier check.
  • pnpm run build – dual ESM/CJS bundles in dist/ via tsdown.
  • pnpm run prepare – runs build, so the package also installs straight from git (github:Genei-Ltd/respondent-sdk#<sha>).
  • pnpm run check – schema validation, generate:check, type-check, lint, format check, build, tests, then the dist smoke tests. This is what runs before publishing, and what CI runs.

Node versions

The published package targets Node 18 and up (engines.node). Working on the SDK needs a newer runtime — devEngines.runtime asks for Node 22.18+, which is what @hey-api/openapi-ts, ESLint and Vitest require.

That gap is covered rather than assumed: .github/workflows/check.yml runs pnpm run check on Node 22, then loads the built bundles and exercises them — ESM and CJS entry points, every subpath, a request, a refused redirect, the timeout and a multipart upload — on Node 18, 20, 22 and 24. Those smoke tests (tests/dist-smoke.mjs, run by pnpm run test:dist) use node --test and the package's own runtime dependency, nothing else.

License

Released under the MIT License. See LICENSE for details.

版本与发布记录

@coloop-ai/respondent-sdk 共发布 1 个版本,首次发布于 2026/09/22;过去 12 个月发布了 1 个版本。

版本发布日期距今
0.1.0latest2026/09/221 个月前

关于 @coloop-ai/respondent-sdk 的常见问题

@coloop-ai/respondent-sdk 是什么?

Type-safe SDK for the Respondent.io Partner API, generated from the provider's OpenAPI spec.

如何安装 @coloop-ai/respondent-sdk?

在项目目录运行 npm install @coloop-ai/respondent-sdk,或使用 pnpm add / yarn add / bun add。

@coloop-ai/respondent-sdk 支持 TypeScript 吗?

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

@coloop-ai/respondent-sdk 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@coloop-ai/respondent-sdk 需要什么版本的 Node.js?

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

@coloop-ai/respondent-sdk 可以商用吗?

@coloop-ai/respondent-sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@coloop-ai/respondent-sdk 的下载量有多少?

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

@coloop-ai/respondent-sdk 还在维护吗?

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