@polyweave/zai (1.0.10)
Installation
@polyweave:registry=https://hub.kl1.tenere.ai/api/packages/Polyweave/npm/npm install @polyweave/zai@1.0.10"@polyweave/zai": "1.0.10"About this package
@polyweave/zai
Z.AI (ZhipuAI/GLM) adapter for Polyweave — multi-capability adapter supporting chat, image generation, video generation, audio transcription, and voice sessions through a single frame duplex.
Synopsis
import { createZaiAdapter } from '@polyweave/zai';
import { createTextFrame, createImageFrame, createAudioFrame, createParamFrame, createFrameSink, FrameType } from '@polyweave/core';
var adapter = createZaiAdapter({ apiKey: process.env.ZAI_API_KEY });
adapter.source.pipe(createFrameSink(function(frame) {
switch (frame.type) {
case FrameType.TEXT: console.log('Text:', frame.content.text); break;
case FrameType.IMAGE: console.log('Image:', frame.content.url); break;
case FrameType.VIDEO: console.log('Video:', frame.content.url); break;
case FrameType.RECEIPT: console.log('Cost:', frame.content.usage); break;
}
}));
adapter.sink.write(createParamFrame('model', 'glm-4.7'));
adapter.sink.write(createTextFrame('Explain quantum computing'));
// Image generation
adapter.sink.write(createParamFrame('task', 'image'));
adapter.sink.write(createParamFrame('model', 'cogview-4'));
adapter.sink.write(createTextFrame('A serene mountain lake at dawn'));
// Audio transcription (send base64 audio data)
adapter.sink.write(createParamFrame('model', 'glm-asr-2512'));
adapter.sink.write(createAudioFrame(audioBase64Data, { mimeType: 'audio/mpeg' }));
Install
npm install @polyweave/zai
Why
Z.AI provides GLM-4.7 with thinking mode, CogView-4 image generation, CogVideoX-3/Vidu video generation, GLM-ASR audio transcription, and GLM-4-Voice realtime sessions — all through a single provider. The adapter unifies all five capabilities under one Polyweave frame duplex, routing frames to the correct API endpoint based on model name, task PARAM, or frame type. Agents can switch between text, image, video, audio, and voice workloads without changing adapters.
API
createZaiAdapter(config)
Creates a frame duplex adapter for Z.AI. Returns an IFrameDuplex with source, sink, setReady, name, ready, and apiKey.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
config.apiKey |
string |
No | process.env.ZAI_API_KEY or process.env.ZHIPU_API_KEY |
Z.AI API bearer token |
config.baseURL |
string |
No | https://api.z.ai/api/paas/v4 |
API base URL override |
config.timeout |
number |
No | 60000 |
HTTP request timeout in milliseconds |
config.useCodingEndpoint |
boolean |
No | false |
When true, uses https://api.z.ai/api/coding/paas/v4 as the default base URL for GLM Coding Plan |
config.name |
string |
No | null |
Logical name for the node, exposed on the duplex |
Return value: IFrameDuplex — a bidirectional frame stream with additional properties.
Properties on the returned duplex:
.source— readable frame source (output frames from the adapter).sink— writable frame sink (input frames into the adapter).setReady(key)— provide the API key after construction; flushes buffered frames.name— logical name (config.name).ready— (getter) whether the adapter has an API key and is accepting frames.apiKey— (getter) current API key
isVoiceModel(model)
Returns true if the model string contains 'voice' or 'realtime', indicating it should use the GLM-4-Voice WebSocket realtime API.
Frame Protocol
Frames are routed to one of six handlers based on model name, task PARAM, and frame type. The routing priority is:
- Voice: If
isVoiceModel(model)→ WebSocket GLM-4-Voice session - Image: If model starts with
cogview→ image generation handler - Video: If model starts with
cogvideoorvidu→ video generation handler - Audio: If model starts with
glm-asr→ audio transcription handler - Chat: All other models default to chat completion handler
Within the chat path, the task PARAM can override routing:
task = 'image'or'generation'→ image handlertask = 'video'→ video handlertask = 'transcription'or'audio'→ audio handler
| Input frame | Action |
|---|---|
TEXT |
Routed per above rules. Default: sends POST /chat/completions |
IMAGE |
Vision mode (sent to chat handler with image_url) unless task=video or vidu model routes to video handler |
AUDIO |
Audio transcription via POST /audio/transcriptions (multipart form-data) |
TOOL_RESULT |
Accumulated into conversation as {role:'tool', tool_call_id, content} |
TOOL_CALL |
Accumulated into conversation as {role:'assistant', tool_calls:[...]} |
PARAM |
Accumulates into adapter params. key=apiKey triggers setReady |
COST_CHECK |
Enters dry-run mode, estimates cost from buffered frames, emits COST + END. Budget check emits ERROR(BUDGET_EXCEEDED) if exceeded |
ESTIMATED_TEXT/IMAGE/AUDIO/VIDEO/OUTPUT |
Buffered in cost check mode; ignored outside cost check mode |
START |
Ignored |
END |
Ignored |
TOPOLOGY_SCAN |
Emits TOPOLOGY_REPORT declaring all receives/generates/types, then forwards the scan |
| Output frame | Trigger |
|---|---|
TEXT |
Chat content + reasoning content (thinking mode deltas tagged metadata.reasoning: true) |
IMAGE |
One per generated image from CogView-4 |
VIDEO |
One per generated video from CogVideoX-3 or Vidu (after async polling completes) |
AUDIO |
Voice session output audio (binary PCM buffers) |
TOOL_CALL |
Tool call invocations from chat response |
RECEIPT |
Cost receipt. Supports receipt_mode: 'none' | 'final' | 'progressive' |
PROGRESS |
Video generation polling progress (0–1) |
COST |
Estimated cost from COST_CHECK dry-run |
ERROR |
Errors (see Error Conditions) followed by END |
END |
Always emitted after each request completes (including errors) |
META |
Passthrough for unknown frame types (with passthrough: true) |
PARAM Keys
All PARAM keys accumulate into the adapter's internal params object.
Chat parameters:
| Key | Type | Default | Description |
|---|---|---|---|
model |
string |
glm-4.7 |
Model ID (also drives handler routing) |
max_tokens |
number |
undefined | Max completion tokens |
temperature |
number |
undefined | Sampling temperature |
top_p |
number |
undefined | Nucleus sampling |
stream |
boolean |
false |
Enable SSE streaming (defaults to false, unlike most adapters) |
system |
string |
undefined | System prompt |
tools |
object[] |
undefined | Tool definitions |
tool_choice |
string |
undefined | Tool choice constraint |
response_format |
object |
undefined | Response format |
logprobs |
boolean |
undefined | Return log probabilities |
top_logprobs |
number |
undefined | Number of top log probabilities |
do_sample |
boolean |
undefined | Enable sampling |
stop |
string[] |
undefined | Stop sequences |
thinking |
object |
undefined | Thinking mode: {type: 'enabled'|'disabled', clear_thinking: boolean} |
messages |
object[] |
[] |
Pre-built message history |
Routing parameters:
| Key | Type | Default | Description |
|---|---|---|---|
task |
string |
undefined | Routing hint: 'image'/'generation' → image, 'video' → video, 'transcription'/'audio' → audio |
Image generation (CogView-4):
| Key | Type | Default | Description |
|---|---|---|---|
size |
string |
undefined | Image size (e.g. '1024x1024') |
quality |
string |
undefined | Quality setting |
n |
number |
1 |
Number of images to generate |
Video generation (CogVideoX-3, Vidu):
| Key | Type | Default | Description |
|---|---|---|---|
duration |
number |
undefined | Video duration |
fps |
number |
undefined | Frames per second |
Audio transcription (GLM-ASR):
| Key | Type | Default | Description |
|---|---|---|---|
language |
string |
undefined | Language code (e.g. 'zh', 'en') |
Receipt settings:
| Key | Type | Default | Description |
|---|---|---|---|
receipt_mode |
string |
'final' |
'none' disables receipts, 'final' emits one receipt at end, 'progressive' emits receipts every receipt_interval tokens |
receipt_interval |
number |
50 |
Token count between progressive receipts |
Auth:
| Key | Type | Default | Description |
|---|---|---|---|
apiKey |
string |
undefined | Set or override API key at runtime (triggers setReady) |
Voice session (GLM-4-Voice):
| Key | Type | Default | Description |
|---|---|---|---|
voice |
string |
undefined | Voice preset |
instructions |
string |
undefined | Session instructions (alias for system) |
modalities |
string[] |
undefined | Response modalities (e.g. ['text', 'audio']) |
Chat Handler
Endpoint: POST /chat/completions
Streaming is disabled by default (stream: false). Set params.stream = true to enable SSE streaming.
Vision support: When an IMAGE frame is routed to chat, it is sent as image_url content blocks with optional text prompt. Vision support is available on GLM-4.6V and GLM-4.5V models.
Thinking mode: GLM-4.5+ supports thinking mode via the thinking PARAM. Set params.thinking = { type: 'enabled' } to enable. Reasoning content is emitted as TEXT frames with metadata.reasoning: true.
Tool calling: TOOL_CALL frames from the API are emitted. TOOL_RESULT/TOOL_CALL input frames accumulate into the message history for multi-turn conversations. Progressive receipts track estimated output tokens every receipt_interval tokens.
Cached tokens: Z.AI reports cached prompt tokens in usage.prompt_tokens_details.cached_tokens. Cached tokens are priced at a reduced rate (e.g. $0.11/MTok vs $0.60/MTok for regular input on GLM-4.7).
Image Handler
Endpoint: POST /images/generations
Model: CogView-4 (default cogview-4). Supports any resolution within range.
Input: TEXT frame with frame.content.text as the prompt. An IMAGE frame routed to the image handler (via model name match) also uses its text content as prompt.
Output: One IMAGE frame per generated image with url and revisedPrompt in content. RECEIPT uses calculateMediaCost with imageCount. END frame includes imageCount in metadata.
Cost: CogView-4 is $0.01 per image.
Video Handler
Endpoint: POST /videos/generations (async — returns a task ID, polled via GET /async-result/{id})
Models: CogVideoX-3 (default cogvideox-3), ViduQ1-Text, ViduQ1-Image, ViduQ1-Start-End, Vidu2-Image, Vidu2-Start-End, Vidu2-Reference.
Input: TEXT frame (text-to-video) or IMAGE frame (image-to-video) with optional prompt, image_url, optional duration, fps, and size.
Output: Async flow — emits PROGRESS frames (0 at start, then progress updates every 5s poll) followed by a VIDEO frame when complete. RECEIPT uses calculateMediaCost with videoCount: 1. END frame terminates the stream.
Polling: Up to 120 attempts at 5-second intervals (~10 minutes total). On timeout, emits ERROR('Video generation timed out', QUEUE_TIMEOUT).
Costs:
- CogVideoX-3: $0.20/video
- ViduQ1 series: $0.40/video
- Vidu2-Image, Vidu2-Start-End: $0.20/video
- Vidu2-Reference: $0.40/video
Audio Handler
Endpoint: POST /audio/transcriptions (multipart/form-data)
Model: GLM-ASR-2512 (default glm-asr-2512). Pricing: $0.03/MTok (~$0.0024/minute).
Input: AUDIO frame with base64 data content and optional mimeType (defaults to audio/mpeg). Optional language and filename PARAMs. URL-based input is NOT supported — emits ERROR(INVALID_INPUT).
Output: TEXT frame containing the transcription text. RECEIPT uses calculateMediaCost with audioDuration (from API response or estimated from byte length: data.length / 16000 seconds). END frame includes duration in metadata.
Estimated cost: Duration is estimated as Buffer.from(data, 'base64').length / 16000 bytes-per-second (rough MP3 estimate) if not provided by the API response.
Voice Handler
Protocol: GLM-4-Voice realtime via WebSocket (wss://open.bigmodel.cn/api/paas/v4/realtime)
Model: Any model containing 'voice' or 'realtime' (e.g. glm-4-voice). Routed automatically via isVoiceModel().
Session lifecycle: Created on first frame for a given streamId, cached in duplex._voiceSessions. Reused for subsequent frames on the same stream.
Input frames:
AUDIO→input_audio_buffer.appendwith base64-encoded PCM audioTEXT→conversation.item.create+response.createPARAMwithkey=voice/instructions/modalities→ updates session configEND→ closes WebSocket session
Output frames:
AUDIO—response.audio.delta/output_audio.deltabinary audioTEXT—response.text.delta/output_text.delta(spoken text) andresponse.audio_transcript.delta(transcript)TOOL_CALL—response.function_call_arguments.done/function_calleventsRECEIPT— fromresponse.doneusage data, withaudioBytesIn/audioBytesOutmetadataEND— on session close orresponse.done
Cost Check (COST_CHECK)
When a COST_CHECK frame is received, the adapter enters dry-run mode:
- Subsequent content frames are buffered in
costCheckBuffer ESTIMATED_OUTPUTframes are buffered but do not trigger estimation- On the next non-estimation content frame,
costEstimator.estimate(params, costCheckBuffer, requestType)is called - If
costCheckOptions.maxBudgetis set and the estimate exceeds it,ERROR(BUDGET_EXCEEDED)+ENDis emitted with the estimate in error metadata - Otherwise, a
COSTframe is emitted containingbounds,accuracy,estimateSource,forwardEstimate,inputBounds, andmaxOutputTokens, followed byEND - Estimation frames (ESTIMATED_TEXT/IMAGE/AUDIO/VIDEO) outside cost check mode are silently ignored
Error Conditions
Every error emits an ERROR frame immediately followed by an END frame with metadata.error: true. Errors from handlers go through mapZaiError() which maps HTTP status codes and error types to standardized error codes.
Chat handler:
| Condition | Error Message | Error Code |
|---|---|---|
| Network request error | 'Chat request failed: ' + err.message |
PROVIDER_ERROR |
| HTTP ≥ 400 | API error message from response body, or fallback message | PROVIDER_ERROR |
| Stream error | 'Stream error: ' + err.message |
PROVIDER_ERROR |
| JSON parse failure | 'Failed to parse chat response' |
PROTOCOL_ERROR |
Image handler:
| Condition | Error Message | Error Code |
|---|---|---|
| Missing prompt | 'Image generation requires a text prompt' |
INVALID_INPUT |
| Network request error | 'Image generation request failed: ' + err.message |
PROVIDER_ERROR |
| HTTP ≥ 400 | API error message | PROVIDER_ERROR |
| JSON parse failure | 'Failed to parse image generation response' |
PROTOCOL_ERROR |
Video handler:
| Condition | Error Message | Error Code |
|---|---|---|
| Network request error | 'Video generation request failed: ' + err.message |
PROVIDER_ERROR |
| HTTP ≥ 400 | API error message | PROVIDER_ERROR |
| JSON parse failure | 'Failed to parse video generation response' |
PROTOCOL_ERROR |
| Poll timeout (10 min) | 'Video generation timed out' |
QUEUE_TIMEOUT |
| Async task FAILED | Response message | PROVIDER_ERROR |
Audio handler:
| Condition | Error Message | Error Code |
|---|---|---|
| URL input (not supported) | 'Audio URL not supported - please provide audio data directly' |
INVALID_INPUT |
| Missing audio data | 'Audio transcription requires audio data' |
INVALID_INPUT |
| Network request error | 'Audio transcription request failed: ' + err.message |
PROVIDER_ERROR |
| HTTP ≥ 400 | API error message | PROVIDER_ERROR |
| JSON parse failure | 'Failed to parse transcription response' |
PROTOCOL_ERROR |
Voice handler:
| Condition | Error Message | Error Code |
|---|---|---|
| WebSocket close (non-1000) | 'GLM-4-Voice session closed: ' + code + ' ' + reason |
PROVIDER_ERROR |
| WebSocket error | 'GLM-4-Voice WebSocket error: ' + err.message |
PROVIDER_ERROR |
| Server error event | event.error.message or 'GLM-4-Voice error' |
provider error code or PROVIDER_ERROR |
COST_CHECK:
| Condition | Error Message | Error Code |
|---|---|---|
| Estimate exceeds maxBudget | 'Estimated cost $X.XXXXXX exceeds budget $Y' |
BUDGET_EXCEEDED |
Adapter-level:
| Condition | Error Message | Error Code |
|---|---|---|
| Sink paused or ended | None (frame dropped) | — |
| Missing API key | Frames buffered in frameBuffer |
— |
| Unknown frame type | META passthrough frame emitted | — |
| Unexpected exception in write | mapZaiError(error, frame) based on status/type |
Varies by error |
Error Mapper (mapZaiError)
The mapZaiError(error, frame) function in src/utils/errorMapper.js maps API errors to standardized error frames:
status === 401 || 403→'Z.AI authentication failed. Check your API key.'status === 429→'Z.AI rate limit exceeded. Please wait and try again.'status === 400 || 422→ error message or'Invalid request parameters'status === 404→ error message or'Resource not found'status >= 500→'Z.AI server error. Please try again later.'
Error type strings are also mapped: authentication_error → AUTHENTICATION_ERROR, invalid_request_error → INVALID_INPUT, rate_limit_error → RATE_LIMIT_EXCEEDED, server_error → PROVIDER_ERROR.
Cost Functions
Re-exported from @polyweave/costs:
getModelCost(provider, model)— returns cost config object withmtoksInput,mtoksOutput,mtoksCached,perImage,perVideo,perMinutecalculateTokenCost(inputTokens, outputTokens, costConfig)— returns{ inputCost, outputCost, totalCost }calculateMediaCost(costConfig, { imageCount, videoCount, audioDuration })— returns{ totalCost }
Tests
npm test
Runs rigor.test.js (property-based cost verification: non-negative costs, zero-cost at zero tokens, monotonicity, pricing table oracle) and zai.test.js (80+ unit tests covering: cost configs for all model families, token/media cost calculations, RECEIPT frame structure for all modalities, error mapper status code mappings, retryable error detection, receipt settings defaults, request type detection by model prefix, thinking mode parameters, vision model identification, cached token savings, and progressive receipt interval calculation).
Requirements
- Node.js 22 or later (as per
enginesfield inpackage.json) - ESM only (
"type": "module") - Peer dependencies:
@polyweave/core,@polyweave/errors,@polyweave/costs,@polyweave/push-websocket
Dependencies
Development Dependencies
| ID | Version |
|---|---|
| @polyweave/core | * |
| @polyweave/costs | * |
| @polyweave/errors | * |
| @polyweave/push-websocket | * |
| @rigor/core | * |
Peer Dependencies
| ID | Version |
|---|---|
| @polyweave/core | * |
| @polyweave/costs | * |
| @polyweave/errors | * |
| @polyweave/push-websocket | * |