Polyweave

@polyweave/openai (1.0.10)

Published 2026-07-11 15:28:37 +00:00 by Dvorak

Installation

@polyweave:registry=https://hub.kl1.tenere.ai/api/packages/Polyweave/npm/
npm install @polyweave/openai@1.0.10
"@polyweave/openai": "1.0.10"

About this package

@polyweave/openai

OpenAI adapter for Polyweave — wraps the official OpenAI SDK to provide a Frame-duplex stream interface. Supports chat completions (text, vision, audio I/O, tools, streaming, reasoning), TTS, transcription, translation, image generation/editing, embeddings, and realtime API.

Synopsis

import { createOpenAIAdapter } from '@polyweave/openai';
import { createTextFrame, createParamFrame } from '@polyweave/core';

const adapter = createOpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY });

// Chat with streaming and tools
adapter.sink.write(createParamFrame('model', 'gpt-4o'));
adapter.sink.write(createParamFrame('tools', [{ type: 'function', function: { name: 'get_weather', parameters: { type: 'object', properties: { city: { type: 'string' } } } } }]));
adapter.sink.write(createTextFrame("What's the weather in Tokyo?"));

// Image generation
adapter.sink.write(createParamFrame('task', 'generation'));
adapter.sink.write({ type: 'IMAGE', content: { prompt: 'A sunset over a cyberpunk city', size: '1024x1024' } });

// TTS
adapter.sink.write(createParamFrame('task', 'tts'));
adapter.sink.write(createParamFrame('voice', 'alloy'));
adapter.sink.write({ type: 'AUDIO', content: { text: 'Hello, world!' } });

// Speech transcription
adapter.sink.write(createParamFrame('task', 'transcription'));
adapter.sink.write({ type: 'AUDIO', content: { data: audioBuffer, mimeType: 'audio/mp3' } });

// Embeddings
adapter.sink.write(createParamFrame('task', 'embeddings'));
adapter.sink.write({ type: 'EMBEDDING', content: { text: 'Text to embed' } });

// Cost estimation (no API call)
adapter.sink.write({ type: 'COST_CHECK', content: { maxBudget: 0.05 } });
adapter.sink.write(createTextFrame('Estimate this'));

// Realtime API (model is auto-detected)
adapter.sink.write(createParamFrame('model', 'gpt-realtime'));
adapter.sink.write({ type: 'TEXT', content: { text: 'Hello' } });

// Runtime configuration via PARAM
adapter.sink.write(createParamFrame('apiKey', 'new-key'));
adapter.sink.write(createParamFrame('organization', 'org-xxx'));
adapter.sink.write(createParamFrame('baseURL', 'https://custom-proxy.com/v1'));

Install

npm install @polyweave/openai

Requires openai (>= 6.15.0) package.

Why

OpenAI's ecosystem spans chat, images, audio (TTS + transcription + translation), embeddings, and realtime — each with distinct SDK client patterns. Polyweave harnesses need a unified Frame-duplex interface that wraps the official SDK so all capabilities work through the same PARAM-accumulation, COST_CHECK, topology-scan, and test-injection protocol. This adapter maps OpenAI SDK errors to structured Polyweave error codes, extracts rate-limit headers into normalized metadata, and supports progressive receipts during SSE streaming for real-time budget tracking.

API

createOpenAIAdapter(config)

Creates an IFrameDuplex stream adapter wrapping the OpenAI SDK.

Parameters:

Name Type Required Default Description
config.apiKey string No process.env.OPENAI_API_KEY OpenAI API key
config.organization string No OpenAI organization ID (sent as OpenAI-Organization header)
config.project string No OpenAI project ID (sent as OpenAI-Project header)
config.baseURL string No 'https://api.openai.com' Custom base URL for proxies or compatible APIs
config.timeout number No 60000 Request timeout in milliseconds
config.maxRetries number No 2 Max retry count for SDK
config.httpAgent object No Custom HTTP agent
config.fetch function No Custom fetch implementation
config.dangerouslyAllowBrowser boolean No false Allow SDK usage in browser (should stay false for Node.js)

Return value: IFrameDuplex with .source, .sink, .setReady, .resetConversation, ._conversation, .name.

Default Parameters

The adapter initializes with these defaults (all overridable via PARAM frames):

Parameter Default Category
model 'gpt-3.5-turbo' Chat
stream true Chat
voice 'alloy' Audio
n 1 Images
receipt_mode 'final' Cost
receipt_interval 50 Cost

Frame Protocol

Input frames:

Frame Type Handler Purpose
TEXT handleChatCompletion Chat prompt (also vision, audio I/O, tools, reasoning)
TEXT (task=embeddings) handleEmbeddings Embedding request
AUDIO handleAudio TTS, transcription, translation, diarization, custom voice
IMAGE handleImages Image generation, editing, variations
EMBEDDING handleEmbeddings Direct embedding request
TOOL_RESULT Tool call result (accumulated into conversation)
PARAM Configuration accumulation
COST_CHECK Enable dry-run cost estimation mode
ESTIMATED_TEXT Virtual text for cost estimation
ESTIMATED_IMAGE Virtual image for cost estimation ({ prompt, width, height, quality })
ESTIMATED_AUDIO Virtual audio for cost estimation
ESTIMATED_VIDEO Virtual video for cost estimation ({ duration, size })
ESTIMATED_OUTPUT Estimated output metadata buffer (not a trigger)
START / END Request scope markers (pass-through)
TOPOLOGY_SCAN Triggers topology report
TEST_INJECT Re-injects child frames
TEST_SNAPSHOT Returns snapshot

Output frames:

Frame Type Emitted when
TEXT Chat response chunks/full, embedding results (with embedding field)
TOOL_CALL Function calling responses
AUDIO TTS audio buffer
IMAGE Generated/edited image URLs
EMBEDDING Embedding vectors
RECEIPT Cost receipt (final or progressive)
COST Cost check estimate
ERROR Error conditions
END End of response
META Unknown frame type pass-through

PARAM Keys (complete)

Chat: model, temperature, max_tokens, stream, messages, system, tools, tool_choice, response_format, logprobs, top_logprobs

Audio: voice (default 'alloy'), speed, language

Images: size, quality, style, n

Embeddings: dimensions, encoding_format

Task routing: task'tts'/'speech', 'transcription', 'translation', 'image'/'generation'/'edit'/'variation', 'embeddings'/'embedding', default: chat

Cost/receipt: receipt_mode ('none' | 'final' | 'progressive'), receipt_interval (tokens)

Auth: apiKey, organization, baseURL — special-cased to update client config

Task Routing

task Frame Type Handler
(default) TEXT handleChatCompletion
embeddings / embedding TEXT handleEmbeddings
tts / speech AUDIO handleAudio (TTS branch)
transcription AUDIO handleAudio (transcription branch)
translation AUDIO handleAudio (translation branch)
(default) AUDIO handleAudio (auto-detect)
generation / edit / variation IMAGE handleImages
(default) IMAGE handleImages (auto-detect)
EMBEDDING handleEmbeddings

Realtime API

When params.model is a realtime model (detected by isRealtimeModel()), TEXT and AUDIO frames are routed through a WebSocket session via createRealtimeSession. Sessions are per-streamId and maintained in duplex._realtimeSessions Map. This is transparent to the caller — just set model to a realtime model and send frames as normal.

Costs (inline)

The adapter maintains pricing data in src/costs.js for token-based (mtoks), per-image, per-minute (whisper), per-million (TTS), and per-second (Sora) pricing. Exported functions:

  • getModelCost(model) → cost object or null
  • calculateTokenCost(model, inputTokens, outputTokens){ inputCost, outputCost, totalCost }

Cost configs use:

  • mtoksInput / mtoksOutput — USD per million tokens
  • perImage — USD per image
  • perMinute — USD per minute (whisper)
  • perMillion — USD per million characters (TTS)
  • perSecond — USD per second (Sora)

Cost Estimation Protocol

  1. Send COST_CHECK frame → enters dry-run mode, clears buffer
  2. Send content frames (including ESTIMATED_* variants)
  3. ESTIMATED_OUTPUT buffers metadata but doesn't trigger estimation
  4. Any other content frame triggers cost estimation via @polyweave/costs
  5. If maxBudget is set and exceeded → emits ERROR (BUDGET_EXCEEDED) + END
  6. Otherwise → emits COST frame with estimate details + END
  7. Exits cost check mode

Request type is determined by determineRequestType():

  • EMBEDDING frame → 'embedding'
  • TEXT with task=embeddings'embedding'
  • IMAGE/ESTIMATED_IMAGE'image'
  • AUDIO/ESTIMATED_AUDIO with task=tts'tts', with task=transcription'transcription', default → 'audio'
  • ESTIMATED_VIDEO'video'
  • Default → 'chat'

Estimation frames (ESTIMATED_TEXT, ESTIMATED_IMAGE, ESTIMATED_AUDIO, ESTIMATED_VIDEO, ESTIMATED_OUTPUT) are ignored when not in cost check mode.

Error Conditions

All errors are emitted as ERROR frames followed by END with metadata: { error: true, errorCode }. Errors from handleChatCompletion and other handlers pass through a done callback; uncaught exceptions in duplex.sink.write are caught in a try/catch.

Condition Error Code When
OpenAI.AuthenticationError AUTHENTICATION_ERROR Invalid API key
OpenAI.PermissionDeniedError AUTHORIZATION_ERROR Insufficient permissions
OpenAI.RateLimitError RATE_LIMIT_EXCEEDED Rate limit hit
OpenAI.APIConnectionError PROTOCOL_ERROR Network connection failure
OpenAI.APIConnectionTimeoutError QUEUE_TIMEOUT Request timeout
OpenAI.NotFoundError JOB_NOT_FOUND Resource not found
OpenAI.UnprocessableEntityError INVALID_INPUT Invalid request parameters
OpenAI.InternalServerError PROVIDER_ERROR OpenAI internal error
OpenAI.APIError (status 400) INVALID_INPUT Bad request
OpenAI.APIError (status 401) AUTHENTICATION_ERROR Unauthorized
OpenAI.APIError (status 403) AUTHORIZATION_ERROR Forbidden
OpenAI.APIError (status 404) JOB_NOT_FOUND Not found
OpenAI.APIError (status 408) QUEUE_TIMEOUT Request timeout
OpenAI.APIError (status 429) RATE_LIMIT_EXCEEDED Rate limited
OpenAI.APIError (status 500) PROVIDER_ERROR Server error
OpenAI.APIError (status 503) SERVICE_UNAVAILABLE Service unavailable
OpenAI.APIError (other) PROVIDER_ERROR Other API error
Unknown error type ADAPTER_ERROR Non-OpenAI error
Budget exceeded (COST_CHECK) BUDGET_EXCEEDED Estimate > maxBudget

Error Mapper Details (src/utils/errorMapper.js)

mapOpenAIErrorCode(error) // OpenAI.Error → ErrorCode string
mapOpenAIError(error, originalFrame) // OpenAI.Error → ERROR frame
isRetryableError(error) // boolean — true for 5xx, 429, 408, connection errors

The mapOpenAIError function extracts:

  • Rate limit headers from error.headers: x-ratelimit-limit-requests, x-ratelimit-limit-tokens, x-ratelimit-remaining-requests, x-ratelimit-remaining-tokens, x-ratelimit-reset-requests, x-ratelimit-reset-tokens
  • These are normalized via normalizeRateLimit from @polyweave/errors
  • Provider error details stored in metadata as providerError

Retryable errors: InternalServerError, RateLimitError, APIConnectionError, APIConnectionTimeoutError, and any APIError with status >= 500, 429, or 408.

Conversation Management

  • Incoming TEXT frames push { role: 'user', content: frame.content.text } to internal conversation array
  • TOOL_RESULT frames push { role: 'tool', tool_call_id, content } (serialized to string if not string)
  • duplex.resetConversation() clears the array
  • duplex._conversation() returns a snapshot

Re-exports

The adapter re-exports getModelCost and calculateTokenCost from src/costs.js (not from @polyweave/costs — OpenAI maintains its own pricing data).

Tests

npm test

Runs node --test test/openai-error-mapper.test.js test/openai.test.js test/rigor.test.js. Covers:

  • createOpenAIAdapter construction and default configuration
  • Cost check protocol (COST_CHECK entry/exit, budget exceeded, estimate emission)
  • PARAM accumulation (model, temperature, max_tokens, stream)
  • isEstimationFrame classification
  • determineRequestType routing for all frame types
  • mapOpenAIErrorCode error mapping for all OpenAI error types
  • isRetryableError classification
  • getModelCost pricing table correctness
  • calculateTokenCost monotonicity and arithmetic
  • Adapter topology report structure
  • Budget exceeded in COST_CHECK mode with all ESTIMATED_* frame types

Requirements

  • Node.js >= 22.0.0
  • ESM only ("type": "module")
  • openai >= 6.15.0 (runtime dependency)
  • Peer dependencies: @polyweave/core, @polyweave/errors, @polyweave/costs, @polyweave/push-websocket

Dependencies

Dependencies

ID Version
openai ^6.15.0

Development Dependencies

ID Version
@rigor/core *

Peer Dependencies

ID Version
@polyweave/core *
@polyweave/costs *
@polyweave/errors *
@polyweave/push-websocket *

Keywords

polyweave openai gpt ai adapter
Details
npm
2026-07-11 15:28:37 +00:00
454
John Dvorak
SEE LICENSE IN LICENSE
latest
24 KiB
Assets (1)
Versions (3) View all
1.0.10 2026-07-11
1.0.7 2026-07-11
1.0.6 2026-07-10