Repository navigation
Sync TypeScript Server SDK docs - #830
hey-august wants to merge 21 commits into
Conversation
…256 webhook helpers
…prefabs, and Relay reference to 3.7.1
…g, and requireArgs
Devon-White
left a comment
There was a problem hiding this comment.
I checked every changed reference page against the v3.7.1 tag (875e6e2, the commit sdk-source-sync.json now records) and the published @signalwire/sdk@3.7.1. I type-checked every TypeScript block in the PR under tsc --strict and checked platform behavior in mod_openai, mod_infrastructure and prime-rails. Most of it holds up. Six things should change before this merges. The first two are coordination; the rest are wrong content.
Must change before merge
1. The pin bump and the new "Pins match the reference" rule conflict with #908. #908 unpins Server SDK installs in every guide and replaces the "Pinned versions" rule. This PR pins @3.7.1 and adds a rule that ties pins to sdk-source-sync.json, so the two can't both land. This PR also conflicts with main in .claude/rules/mdx-capability-guides.md, where main moved the Browser pin to rc.5. Even on its own terms, merging main leaves 14 guides that this PR doesn't touch on @signalwire/sdk@2.0.5: sidecar, SIP/byoc, eight TTS/* pages, both SMS pages, api-credentials and SWML deployment. That breaks "identical across every guide".
My suggestion: drop the install-line bumps and the rule change, rebase on #908 after it lands, and keep the sample fixes. The builder-gap fixes are right, and #908 doesn't have them. I ran addVerb on 3.7.1, and enter_queue without transfer_after_bridge, stream, stop_stream, and connect with codec/realtime/name all throw schema errors. #908 still chains .enter_queue({...}) in the Call queues TypeScript sample, which throws on 3.7.1.
2. The sync record says 3.7.1, but parts of the TypeScript reference still document 2.x. None of these files is in the PR, but moving synced_commit to the 3.7.1 tag says they're current. Either fix them here, or leave synced_commit where it was and list them as remaining work.
- REST pages still pass the 2.x options object. 3.7.1 takes positional arguments here, so these fail
tscand send malformed requests.fabric/tokens/create-subscriber-token.mdx:27callscreateSubscriberToken({ reference }), but the 3.7.1 signature iscreateSubscriberToken(reference: string, options?)(dist/rest/namespaces/fabric.resources.generated.d.ts:167).phone-numbers/set-relay-topic.mdx:50callssetRelayTopic(id, { topic, statusCallbackUrl }), but the signature issetRelayTopic(resourceId, topic: string, status_callback_url?, …)(relay-rest.resources.generated.d.ts:81). The guides in this PR fix exactly these two calls, so guide and reference now disagree on the same methods. The same pattern appears in:fabric/tokens/{index,create-embed-token,create-guest-token,create-invite-token,refresh-subscriber-token}phone-numbers/{set-cxml-webhook,set-call-flow}mfa/{index,sms,call,verify}video/room-tokens/{index,create},video/rooms/create-stream,video/conferences/create-stream,video/streams/updateverified-callers/submit-verification,number-groups/add-membership,fabric/resources/assign-domain-application
- Wrong argument counts (TS2554):
addresses/create,chat/{index,create-token},pubsub/{index,create-token},project/{index,create},imported-numbers/{index,create},short-codes/update,fabric/resources/assign-phone-routeandfabric/subscribers/create-sip-endpoint. - Missing required field:
fabric/swml-scripts/create.mdx:27omits the requiredcontents. SWMLService.run()no longer exists. 3.7.1 has onlyserve()andstop()(src/SWMLService.ts:1319).swml-service/run.mdxstill documents it, andswml-service/index.mdx:247,set-on-request-callback.mdx:51,as-router.mdx:19andstop.mdx:4still refer to it.data-map/global-error-keys.mdx:10-12says the keys "apply to all webhooks". The 3.7.1 JSDoc onDataMap.globalErrorKeys(src/DataMap.ts:467-476) says the platform readserror_keysonly on a webhook, "so these keys fail no request". The PR edits this page's example but not the claim, so the page still promises behavior the method doesn't have.
3. validateWebhookSignatureSha256 is wrong about cXML, and its example rejects genuine cXML webhooks. See the inline comments. The page copies the SDK JSDoc, which needs an upstream fix too.
4. The prefab factory imports don't resolve. prefabs/receptionist-agent.mdx:105 imports createReceptionistAgent and prefabs/survey-agent.mdx:150 imports createSurveyAgent, both from @signalwire/sdk. The package root exports only the classes (src/index.ts:272-276), the factories exist only in src/prefabs/index.ts, and package.json exports has no deep path to them. tsc fails with TS2724/TS2305. The PR edits both pages. The same import is on concierge-agent.mdx:88, faq-bot-agent.mdx:110 and info-gatherer-agent.mdx:116, and prefabs/index.mdx:34 lists the factories. Show new ReceptionistAgent({...}) etc. instead, or drop the sections.
5. The DataMap template fix stops partway. The correction itself is right. In mod_openai actions.c (around 1333-1346), a webhook's output, expressions and foreach expand against the response root plus input, global_data and prompt_vars, with no response key. That matches the SWML data_map reference. Four places still teach the old form:
data-map/foreach.mdx:51usesinput_key: 'response.items', which resolves to nothing. It should beinput_key: 'items'. Also, the ParamFields are orderedinput_key, output_key, max, append, but source order is…, append, max(DataMap.ts:407).data-map/index.mdx:125: theparamscard still says "Set query or form parameters for a webhook", which contradicts the rewrittenparams.mdx. Suggested: "Set the JSON request body for a webhook."guides/build-ai-agents/datamap.mdxuses${response.*}and${args.city}inside webhook output in every language tab, TypeScript included (lines 106-107, the table at 260-265, and 45${response.uses in all). After this merges, the guide contradicts the TypeScript reference.- The Python DataMap reference has 17
${response.uses.mdx-reference-pagessays to sync an edit across every language variant, so this belongs here or in a follow-up opened before merge. Separately, Python'sbody()writesbody(signalwire/core/data_map.py:273at the synced Python commit), and the platform ignores that key (actions.c:740reads onlyparams). That's an upstream SDK bug worth filing next to your TypeScript ones.
6. Version wording in pages. The semantic gates guide Note and semantic-gate/index.mdx:97 add "from version 3.7.1" and "Requires SDK 3.7.1 or later". Guides and reference shouldn't state minimum Server SDK versions, and guide prose shouldn't name per-language options. See the inline suggestions.
Should fix
Inline: relay/call/user-event.mdx (the platform rejects what the page now says is sent), the ChatGateway constructor's other throw conditions, serve.mdx's run() alias claim, the security overview's fallback wording, and a highlight range in Record calls.
Nits
configuration/index.mdx:78-83(not in the PR) still passesindex_filetonative_vector_search. The updatedskills/native-vector-search.mdxcorrectly says no index file is read (native_vector_search.ts:252-256).ai-chat-client/raw-post.mdx:23: "check the status first" works against the source. The_requestJSDoc says success is decided by the body because the keepalive commits a200early. Reading the body'serrormember is the check.agent-base/add-per-call-config.mdx:30:## Rejecting a callsits before## Parameters. Fold it into the intro or move it after Returns.agent-base/add-language.mdx:functionFillerscomes afterspeechModel.LanguageConfigorder (types.ts:108) puts it right afterspeechFillers.function-result/index.mdx:218: theholdcard still says only "with an optional timeout". It now also takes the announcement and the resume step.cli/sw-tsdocs.mdx:193-195hard-codes3.7.1as the--versionoutput. That goes stale, so use a placeholder.- Highlights already off in blocks this PR touches:
agent-base/register-swaig-function.mdx:32{15}should be{16}, andswml-service/register-swaig-function.mdx:30{12}should be{13}. record-calls.mdx:Object(event.params.result).params?.digitsreads oddly as a teaching sample. A typed cast with the comment you already have would be clearer.- 19 touched pages lack
max-toc-depth: 3(both prefab pages,skill-manager/*,skill-registry/*,skills/*). This predates the PR.
Public API still without a page
Besides WebService and createCgiHandler, which you noted:
AgentBase.runServerless()andgetTools()- The SemanticGate helpers:
gateDefinitions,applyGateFields,MAX_GATES,MAX_QUESTION_BYTES,MAX_CRITERIA_BYTES,MAX_ON_FAIL_BYTESandRESERVED_FUNCTION_NAMES - The top-level
registerSkill()andlistSkillsWithParams() paginateAll- Relay
CollectAction.pause()/resume() SkillManager.sizeandSkillRegistry.size
Not blocking, but worth a tracking issue.
What I verified as correct
- Guide samples: every complete TypeScript sample in the 10 guides type-checks against 3.7.1. The only failure is the Hono
checkJsmismatch you called out. - AgentBase:
addPerCallConfig,setDynamicConfigCallback,onCallStart/onCallEnd,mount,defineContextsoverloads,validateBasicAuth(now actually called, constant-time), credential precedence,swaigSecretand/mcpauth. - FunctionResult:
hold,changeVoice,rpcAiMessage/rpcAiGlobalData,joinConference(no 250 default),paycoercion, andtap(speak|listen|both). - ChatGateway and HandoffRouter: options and defaults; I ran
register/redeem/say/escalateand the outputs match. - Other areas: the capabilities and post-prompt helpers (run, and the output matches the comments), Bedrock voices (match the schema enum), the
swaig-testandsw-tsdocsflags, Relaystreamtrack, andSemanticGate.visualInput. - Environment variables: every public variable the source reads is documented, and nothing documented is missing from the source.
I couldn't verify the startup_hook 120-second wait and retry in mod_openai, swaig-test loading .ts agents on Node below 22, or "SignalWire address widget" as a customer-facing name (it isn't in the glossary). My mod_openai and mod_infrastructure checkouts are from April 2026, so the DataMap and user_event points should be rechecked against the deployed build if anything looks off.
| - **Pinned versions** are literal and identical across every guide: `signalwire-sdk==3.4.1`, `@signalwire/sdk@2.0.5`, `@signalwire/js@4.0.0-rc.2 rxjs@7.8.2`. Bump them in every guide in one PR, never one page at a time. | ||
| - **Header comments** on every complete sample. Python: `# Install: python -m pip install signalwire-sdk==3.4.1` then `# Save as <name>.py and run: python <name>.py`. TypeScript with no type annotations: `// Install: npm install @signalwire/sdk@3.7.1` then `// This sample also runs as JavaScript: save as <name>.mjs,` / `// then run: node <name>.mjs`. TypeScript that uses types: `// Save as <name>.mts and run: npx tsx <name>.mts`. Browser: `// Install: npm install @signalwire/js@4.0.0-rc.2 rxjs@7.8.2` plus the HTML elements the script expects, as comments. | ||
| - **Pinned versions** are literal and identical across every guide: `signalwire-sdk==3.4.1`, `@signalwire/sdk@3.7.1`, `@signalwire/js@4.0.0-rc.2 rxjs@7.8.2`. Bump them in every guide in one PR, never one page at a time. | ||
| - **Pins match the reference.** Each pinned Server SDK version must be the release whose source the reference pages are synced to, as recorded for that SDK in `fern/products/server-sdks/sdk-source-sync.json` (`synced_commit` is that release's tag commit). Bumping a pin means syncing that SDK's reference to the same release, and syncing the reference to a new release means bumping the pin: do both in the same PR, or guide samples and reference pages disagree. |
There was a problem hiding this comment.
This rule and the @3.7.1 pin go the opposite way from #908. #908 replaces "Pinned versions" with unpinned Server SDK installs and a no-minimum-version rule. Under this rule, every reference sync would also have to bump every guide. I'd drop this line and the pin changes on 105-106. The file also conflicts with main, which moved the Browser pin to @signalwire/js@4.0.0-rc.5.
| Only RELAY, SWML, and JSON webhooks are signed this way. Form-encoded cXML webhooks stay on the SHA-1 | ||
| signature, which [`validateWebhookSignature()`][validate-webhook-signature] checks. |
There was a problem hiding this comment.
cXML webhooks do carry a SHA-256 header, just in a different scheme. prime-rails app/services/laml/send_callback.rb (build_headers) sets X-SignalWire-SHA256-Signature on cXML callbacks. The value is Hmac::GenerateTwilioCompatibleSignature with SHA-256: base64 of the HMAC over the URL plus sorted params. mod_infrastructure.c does the same for laml-format requests. This function only checks hex HMAC-SHA256 over URL + raw body (src/WebhookValidator.ts, validateWebhookSignatureSha256), so it returns false for a genuine cXML webhook. The SDK JSDoc this copies says the same thing and is worth an upstream issue.
| Only RELAY, SWML, and JSON webhooks are signed this way. Form-encoded cXML webhooks stay on the SHA-1 | |
| signature, which [`validateWebhookSignature()`][validate-webhook-signature] checks. | |
| cXML webhooks also carry this header, but it's computed over the sorted form parameters in a | |
| different encoding, and this function doesn't verify it. Validate cXML webhooks with | |
| [`validateWebhookSignature()`][validate-webhook-signature]. |
There was a problem hiding this comment.
A qualifier on my suggested wording. I re-checked this against current mod_infrastructure master (c80bf95) and prime-rails master (00cf02f), and the finding holds: cXML requests carry a SHA-256 header that this function can't verify.
"In a different encoding" isn't accurate for every cXML request, though:
- Callbacks sent from
prime-rails(app/services/laml/send_callback.rb:95-96, 138-140) are base64. - Requests signed in
mod_infrastructure.c(build_hmac_base_stringandhmac_hex, around line 2015) are hex.
What differs in both cases is the signed string: the URL plus the sorted parameters, not the URL plus the raw body. The wording should be:
| Only RELAY, SWML, and JSON webhooks are signed this way. Form-encoded cXML webhooks stay on the SHA-1 | |
| signature, which [`validateWebhookSignature()`][validate-webhook-signature] checks. | |
| cXML webhooks also carry this header, but it's computed over the URL and the sorted form | |
| parameters rather than the raw body, and this function doesn't verify it. Validate cXML webhooks | |
| with [`validateWebhookSignature()`][validate-webhook-signature]. |
| const ok = sha256 | ||
| ? validateWebhookSignatureSha256(signingKey, sha256, url, rawBody) | ||
| : validateWebhookSignature(signingKey, sha1, url, rawBody); |
There was a problem hiding this comment.
This rejects genuine cXML webhooks. On a cXML callback the SHA-256 header is present but doesn't match under this scheme (see above), so the ternary never reaches the SHA-1 check and returns 403. validate() falls back on a mismatch, not just on a missing header (src/WebhookMiddleware.ts:116-124). The sample should do the same:
| const ok = sha256 | |
| ? validateWebhookSignatureSha256(signingKey, sha256, url, rawBody) | |
| : validateWebhookSignature(signingKey, sha1, url, rawBody); | |
| const ok = | |
| (sha256 !== undefined && validateWebhookSignatureSha256(signingKey, sha256, url, rawBody)) || | |
| validateWebhookSignature(signingKey, sha1, url, rawBody); |
Line 55 then becomes "Check the SHA-256 header when it is present, and fall back to the SHA-1 header when it is missing or doesn't match:".
| SignalWire sends two signature headers on signed webhooks: `X-SignalWire-Sha256-Signature` (HMAC-SHA256) | ||
| and `X-SignalWire-Signature` (SHA-1). [`validate()`][validate] and | ||
| [`webhookValidationMiddleware()`][webhook-validation-middleware] check the SHA-256 header first and fall | ||
| back to the SHA-1 header, so deployments that only receive the SHA-1 header keep validating. |
There was a problem hiding this comment.
The fallback also runs on a mismatch, and that's the case that matters for cXML. validate() and the middleware try the SHA-1 header whenever the SHA-256 check fails (src/WebhookMiddleware.ts:116-124), and cXML webhooks carry a SHA-256 header this scheme doesn't match.
| SignalWire sends two signature headers on signed webhooks: `X-SignalWire-Sha256-Signature` (HMAC-SHA256) | |
| and `X-SignalWire-Signature` (SHA-1). [`validate()`][validate] and | |
| [`webhookValidationMiddleware()`][webhook-validation-middleware] check the SHA-256 header first and fall | |
| back to the SHA-1 header, so deployments that only receive the SHA-1 header keep validating. | |
| SignalWire sends two signature headers on signed webhooks: `X-SignalWire-Sha256-Signature` (HMAC-SHA256) | |
| and `X-SignalWire-Signature` (SHA-1). [`validate()`][validate] and | |
| [`webhookValidationMiddleware()`][webhook-validation-middleware] check the SHA-256 header first and fall | |
| back to the SHA-1 header when the SHA-256 header is missing or doesn't match, so cXML webhooks and | |
| deployments that only receive the SHA-1 header keep validating. |
| The `options` type also accepts additional keys (`& Record<string, unknown>`), and | ||
| every field is sent in the RPC call. `event` is left out when it isn't set. |
There was a problem hiding this comment.
This matches the SDK (src/relay/Call.ts:1741-1751), but the platform rejects both cases it describes. The call_user_event whitelist is method,call_id,project_id,protocol,event,swml,async (mod_infrastructure relay_apis.c:1836), so any other key fails CHECK_call_user_event. relay_calling_call_user_event (relay.c:9186) returns Bad Request when event is missing. In practice event is required, and the ParamField on line 14 should get required={true}. The return type is CallingUserEventResult (protocol.types.generated.ts:1253), not Record<string, unknown>.
| The `options` type also accepts additional keys (`& Record<string, unknown>`), and | |
| every field is sent in the RPC call. `event` is left out when it isn't set. | |
| The `options` type also accepts additional keys (`& Record<string, unknown>`), and the SDK sends | |
| every field. SignalWire accepts only `event`: a request without it, or with any other key, fails | |
| with a bad request error. |
| The constructor takes one options object, `ChatGatewayOptions`. It throws an `Error` when | ||
| `configUrl` is empty. |
There was a problem hiding this comment.
It also throws when client is omitted and the environment can't build one. ChatGateway.ts:381 calls new AIChatClient(), which throws "project is required" without SIGNALWIRE_PROJECT_ID, and "No service URL" without SIGNALWIRE_SPACE or a url (AIChatClient.ts:299-314). Every sample that builds a bare new ChatGateway({ configUrl }) throws unless those are set. That covers close.mdx, mint-handle.mdx, read-handle.mdx, and the HandoffRouter index/escalate/redeem/register/say pages. A line about the environment in those samples, or on the client field, would help too.
| The constructor takes one options object, `ChatGatewayOptions`. It throws an `Error` when | |
| `configUrl` is empty. | |
| The constructor takes one options object, `ChatGatewayOptions`. It throws an `Error` when | |
| `configUrl` is empty, and, when `client` is omitted, when `SIGNALWIRE_PROJECT_ID` isn't set or no | |
| service URL resolves from `SIGNALWIRE_SPACE`. |
| [`run()`][run] is an alias for `serve()`. Both methods are equivalent in the | ||
| TypeScript SDK. |
There was a problem hiding this comment.
run() isn't an alias anymore. In 3.7.1, AgentBase.run() (src/AgentBase.ts:3976) hands off to runServerless() when it gets an event, or when AWS_LAMBDA_FUNCTION_NAME, _HANDLER, FUNCTION_TARGET, FUNCTIONS_WORKER_RUNTIME or GATEWAY_INTERFACE is set, and only otherwise calls serve(). It was already true at the previous sync commit. run.mdx needs the same correction: its opts include event, context and platform, and it returns Promise<void | ServerlessResponse>.
| [`run()`][run] is an alias for `serve()`. Both methods are equivalent in the | |
| TypeScript SDK. | |
| [`run()`][run] starts the server like `serve()`, except that when it's given a serverless `event`, | |
| or detects a serverless platform from the environment, it handles that one invocation instead. | |
| Call `serve()` when you always want a server. |
| as the gate that asks for the most. If the call has no video frame to show, the | ||
| gate fails without being asked. The gate's provider must accept images: one that | ||
| doesn't makes the check an error, and the function isn't called. Omitted by | ||
| default. Requires SDK 3.7.1 or later. |
There was a problem hiding this comment.
Drop the minimum version. Reference and guides don't state "requires X.Y or later" for the Server SDKs. Version lines like this go stale.
| default. Requires SDK 3.7.1 or later. | |
| default. |
| <Note> | ||
| The Python Server SDK accepts `visual_input` on a gate from version 3.7.1. The TypeScript Server SDK | ||
| doesn't accept it yet, so define video gates in SWML. | ||
| The Server SDKs accept `visual_input` on a gate from version 3.7.1: `visual_input` in Python and the | ||
| `visualInput` option in TypeScript. | ||
| </Note> | ||
|
|
There was a problem hiding this comment.
I'd delete this Note. It states a minimum SDK version and names per-language options in guide prose, and the guides avoid both: say "the Server SDKs" and let the code tabs carry language names. Now that both SDKs accept visual_input, nothing is left to warn about. If you want SDK coverage here, a <CodeBlocks> with Python and TypeScript tabs ahead of the YAML would follow the SDK-first rule.
| <Note> | |
| The Python Server SDK accepts `visual_input` on a gate from version 3.7.1. The TypeScript Server SDK | |
| doesn't accept it yet, so define video gates in SWML. | |
| The Server SDKs accept `visual_input` on a gate from version 3.7.1: `visual_input` in Python and the | |
| `visualInput` option in TypeScript. | |
| </Note> |
| ``` | ||
|
|
||
| ```typescript title="TypeScript — Relay client" {12-14} | ||
| ```typescript title="TypeScript — Relay client" {11-16} |
There was a problem hiding this comment.
The highlight now covers the extraction lines and a trailing blank line. The Python block highlights only the if and record lines ({14-15}). Here those are lines 13-14.
| ```typescript title="TypeScript — Relay client" {11-16} | |
| ```typescript title="TypeScript — Relay client" {13-14} |
|
A correction to item 5 of my review: Python's I re-checked the other backend-based points against current code, and they hold:
|
Description
@signalwire/sdkv3.7.1. It was synced to an unreleased commit between 2.0.5 and 3.5.0.AgentBase.addPerCallConfig,onCallStart,onCallEnd,mount;FunctionResult.changeVoice,rpcAiGlobalData;ChatGateway;HandoffRouter;AIChatClient.rawPost; the capabilities helpers; the post-prompt helpers;validateWebhookSignatureSha256; thesw-tsdocsCLI.onSummary,validateBasicAuth,enableMcpServer,defineContexts,addLanguage,hold,rpcAiMessage,tap,joinConference,pay,SemanticGate(visualInput), Bedrock, LiveWire, prefabs, skills,swaig-test, environment variables, serverless,AgentServer, Relaystream/userEvent.body()sets the webhook'sparams, which is sent as a POST body. A webhook'soutput,expressions, andforeachread response fields from the root (${temp}) and arguments as${input.args.x}, so 20 examples change.@signalwire/sdk@2.0.5to3.7.1and fixes the samples 3.x breaks:calling.dial,transfer,stream/streamStop,phoneNumbers.setRelayTopic, andfabric.tokens.createSubscriberToken;swmlScripts.createtakescontentsas a string; listdatais optional.ringbackitems; typedcall.deviceand collect results;record()'s missingmax_length.enter_queue,stream,stop_stream, andconnect's stream settings fail the SDK's schema validation, so those samples skip validation for that verb only, as the rule's builder-gap convention says.sdk-source-sync.jsonto the v3.7.1 tag commit.mdx-capability-guides.md: the TypeScript pin is now 3.7.1, and a new rule requires each pinned Server SDK version to match the release recorded insdk-source-sync.json, bumped together in one PR.visual_inputfrom 3.7.1.Notes for reviewers
@signalwire/sdk@2.0.5: Add Transcribe Calls guide #829, Add send and receive messages guide #819, Add Speak text on a call guide #789, Add AI sidecar guide to the platform guides #783. Each needs the 3.7.1 pin and the same sample fixes before or after merge.transfer_after_bridgeonenter_queue, which the platform doesn't. This repo's generatedspecs/swml/tsp-outputstill lists that field too, though the.tspsource was updated.streamandstop_streamverbs andconnect'scodec,realtime, andname.record()'s type omitsmax_length, which the platform honors.SwmlBuilder.build()returns a type thatcalling.dial'sswmloption rejects under--strict, and that option's type isn't exported. Samples passJSON.parse(builder.render()).dialmaxDurationis in minutes. The platform reads seconds, and the reference keeps seconds.joannawas replaced withtiffanyin the examples, since the valid Bedrock voices aretiffany,matthew,amy,lupe, andcarlos.WebServiceandServerlessAdapter.createCgiHandlerhave no TypeScript reference pages; that gap predates this sync. The Python reference may have the same DataMap template issue; out of scope here.Preview
Guides:
Reference (main entry points):
Type of Change
Related Issues
Follow-up to #829, which pins
@signalwire/sdk@2.0.5and notes that this PR bumps the pin and syncs the TypeScript reference.Testing
Behavior verified against the platform and the API spec.
tsc --strictagainst an installed@signalwire/sdk@3.7.1. One existing JavaScript sample (call streaming, Hono server) has acheckJs-only type mismatch under both 2.0.5 and 3.7.1; it runs as-is.yarn fern-md-check: all MDX files valid.Tests
Checklist
🤖 Generated with Claude Code