@pieai/swimmer-ai-provider-kit
v0.17.0
Published
Server-only AI provider authorization, model transport, media and usage primitives for PieAI products.
Readme
SwimmerAIProviderKit
AI provider authorization, model transport, media and usage primitives shared by
PieAI products. Server-only by default; the explicit ./openai-live-webrtc leaf
contains credential-free browser media controls and never imports server auth.
This package is the product-line AI socket. Product code must not call OpenRouter or model-vendor SDKs directly. The package connects providers; it does not own the application's agent, business operations or user-facing character.
Identity migration
The last independently confirmed public package is @pieai/[email protected].
The current 0.17.0 candidate adds the credential-check leaf described below;
its registry publication is a separate gate. Existing xAI subscription OAuth and
per-request TTS credentials keep their account, payer and dispatch boundaries.
The current work index retains the
publication and integration evidence. Earlier release receipts are historical,
not instructions to publish those versions again. The pre-rename package remains
available as @pieai/[email protected].
Consumer integration, real voice quality and product billing still require
their own acceptance; they are not established by the package release.
Existing published versions remain available and are not modified or unpublished.
All existing subpaths and the 63 root runtime exports are retained. New code should use named subpaths. The native tool stream is a separate opt-in subpath; the existing text adapters are still tool-free. See the active migration for preservation, validation, release and consumer status.
Manual API-key check (0.17)
import { checkProviderCredential } from '@pieai/swimmer-ai-provider-kit/credential-check';
// Server only, after host authentication and rate admission. Do not log the key.
const receipt = await checkProviderCredential({
provider: 'xai', // or 'openrouter'; unsupported providers fail before I/O
apiKey: submittedKey,
signal: request.signal,
});
// receipt: provider, keyHash (SHA-256), last4, checkedAt,
// check='authenticated-metadata', modelAccess='not-tested'.This performs one authenticated GET to the provider's official key metadata endpoint, not a public model list or a generated message/image. xAI's disabled, blocked-key and blocked-team responses are rejected. It uses the same explicit native-fetch pattern as the existing authorization transport, a 15-second abort bound, 32 KiB response bound, no redirects/retries, and sanitized typed errors. It adds no SDK/runtime dependency, token store or credential-discovery mechanism. The provider API, not local key syntax or a home-grown validation algorithm, confirms the credential. Unknown model access/billing remains unknown.
The host must bind its verified account and operation, then pass the same key and unmodified server-produced receipt to Backend's encrypted custody function. Never accept a receipt supplied by a browser or persist the key in ordinary preferences, logs or work archives. A receipt is not a signature or standalone authorization token; Backend grants its custody function only to the trusted product runtime. Encryption, durable save/revoke receipts and account ownership belong to SwimmerBackend, not this package.
Official contracts: OpenRouter key metadata and xAI API-key information.
Scope
Owns:
- Server environment readers and fallback resolution
- Product-owned model alias registries
- OpenRouter model configuration and attribution headers
- Provider-neutral chat-completion contract (
./chat) - OpenRouter chat completion transport (
./openrouter) - Gemini-direct chat completion transport (
./gemini) - Privacy-safe, provider-neutral AI execution evidence validation
- Local provider budget reservation/commit/release guards
- Typed generator registries with optional provider-budget wrapping
- Structured-output parse/generate on top of
./chat(./structured-output) - Named agent registry (
./agents): lookup plus chat / structured invoke - Explicit Codex native tool streaming (
./openai-codex-stream), without an agent loop, tool execution or automatic retry - Content safety (
./content-safety): local text rules + Sightengine visual and adult-reference adapters - Dual TTS routing (
./tts): language → western (ElevenLabs) / chinese (MiniMax) - Tripo 3D text-to-model adapter (
./tripo)
Does not own prompts, product model policy, product schemas, game logic, wallet RPCs, database access, deployment topology, or a Mastra runtime. Each product deploys its own optional Mastra service and supplies its own model aliases, budgets, generators, standing instructions, and Zod (or other) schemas.
Public API contract
Import a named subpath. The root entry @pieai/swimmer-ai-provider-kit is a
compatibility barrel for existing callers (SupaLuv). New code should not add
root imports. Runtime values on the root barrel are locked by
tests/public-exports.test.ts.
| Subpath | Stability | Source | Use it for | Do not use it for |
| ---------------------------- | --------- | --------------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
| ./env | stable | src/env.ts | Reading server env and key fallbacks | Client bundles, logging secret values |
| ./models | stable | src/models.ts | Product alias registries; Mastra OpenRouter model config | Sending HTTP; owning product model policy |
| ./chat | evolving | src/chat.ts | Typing a ChatCompletionTransport. ChatMessage.content is a string or text / image parts. | Calling a provider; parsing structured output |
| ./structured-output | evolving | src/structured-output.ts | Declaring a { parse } schema and getting a validated object from chat text | Owning product schemas; wrapping Mastra; unifying transport errors |
| ./agents | evolving | src/agents.ts | Looking up named model-call configurations and invoking them through ./chat / ./structured-output | An autonomous agent loop, tool execution, prompts or model policy |
| ./openrouter | stable | src/openrouter.ts | Text and vision chat completions through OpenRouter. Optional signal cancels or times out a request. | Gemini-direct, product prompts |
| ./openrouter-images | evolving | src/openrouter-images.ts | Bounded image requests and parsing a retained response without resubmission | Product adoption, image quality or billing truth |
| ./openrouter-authorization | evolving | src/openrouter-authorization.ts | Official HTTPS S256 authorization URL and bounded, single code exchange | User/tenant state, encrypted custody, billing or supplier licensing |
| ./openai-codex | evolving | src/openai-codex.ts | Device authorization, refresh, model inventory and a bounded tool-free text turn | Host account discovery, autonomous agents or commercial entitlement |
| ./openai-codex-stream | evolving | src/openai-codex-stream.ts | One explicit-account native Pi stream with tools and tool-result history | Tool execution, durable sessions, consent or a hard output-token budget |
| ./openai-live | evolving | src/openai-live.ts | One authenticated server-side GPT-Live session creation with client delegation | Browser API keys, subscriptions, retries, user consent or task execution |
| ./openai-live-webrtc | evolving | src/openai-live-webrtc.ts | Credential-free official SDK WebRTC and acknowledged Live media controls | Server SDK/auth, backend task cancellation or proof that the user heard output |
| ./openai-live-supervision | evolving | src/openai-live-supervision.ts | Server attachment to an existing session, original-deadline closure and trusted usage observations | Creating another session, product billing, or a provider-enforced hard cap |
| ./pi-oauth | evolving | src/pi-oauth.ts | Pi-owned device login, refresh and bounded text for Copilot, xAI, Kimi Code and Radius | CLI credential discovery, brand login, arbitrary gateways, automatic account rotation |
| ./xai-model-stream | evolving | src/xai-model-stream.ts | Explicit-account native Pi text and tool-result streaming | Another agent, tool execution or account fallback |
| ./xai-media | evolving | src/xai-media.ts | One explicitly funded image/video submission and original-ID query | Implicit credentials, retries, entitlement or final billing guarantees |
| ./gemini | evolving | src/gemini.ts | Gemini-direct ChatCompletionTransport for the same request shape | OpenRouter ids (google/…), product prompts, Mastra config |
| ./execution-evidence | stable | src/execution-evidence.ts | Parsing privacy-safe usage/cost evidence | Pricing, billing decisions, raw provider payloads |
| ./provider-budget | stable | src/provider-budget.ts | In-process cents reservation | Cross-process ledgers or wallet truth |
| ./generator-registry | stable | src/generator-registry.ts | Wrapping named generators with optional budget | Owning generator implementations |
| ./content-safety | stable | src/content-safety/ | Text/visual/adult-reference review; Sightengine signal normalize | Product visual thresholds, copy, or UI policy |
| ./tts | stable | src/tts/ | Server-side speech synthesis with static or per-request caller keys | Browser bundles, ambient user-key discovery, billing policy |
| ./tripo | stable | src/tripo.ts | Server-only Tripo text-to-model | Browser runtime; live image_to_model |
Expected consumers: server code in Pie products (Show, Collapse, SupaLuv, Sea,
Anvil, TuringPact, Break, Non-Heroes). Browser code may import only the deliberately
credential-free ./openai-live-webrtc leaf; never the root or server subpaths.
Caller-owned speech keys (0.16.0)
The ./tts entry remains server-only. Existing
createElevenLabsTtsProvider({ apiKey }), createMiniMaxTtsProvider({ apiKey })
and createDualTtsFromEnv() calls continue to work. No environment variable is
needed when the host explicitly resolves the authenticated caller's key:
import { createDualTtsRouter } from '@pieai/swimmer-ai-provider-kit/tts';
// Inside an authenticated server request; ownerId is server-verified, not a
// submitted characterId, email, arbitrary owner field or mutable global variable.
const router = createDualTtsRouter({
resolveCredentials: async (provider) => {
const apiKey = await encryptedAccountKeys.read(ownerId, provider);
return apiKey ? { apiKey } : undefined;
},
elevenlabs: { voiceMap: approvedWesternVoices },
minimax: { voiceMap: approvedChineseVoices },
});
const result = await router.synthesize({ text, language, characterId, signal });The host owns those key-store/voice-map variables, user consent, encryption,
authorization and cost limits. Keys never belong in browser synthesis bodies,
URLs, logs or evidence. The resolver gets the selected provider and a read-only
request snapshot, once per synthesis. It cannot select an endpoint. Failure,
cancellation or a missing key never uses another account or application key,
and never automatically resends the paid request. Trusted elevenlabs/minimax
options select transport, model and voices separately from credentials.
MiniMax uses Bearer authentication; no legacy GroupId is required.
durationMs and charactersBilled are optional observations, not price quotes:
MiniMax maps extra_info.audio_length (milliseconds) and usage_characters;
ElevenLabs preserves a numeric character-cost response header when supplied,
without promising its availability or manufacturing a duration for binary audio.
Missing/invalid metadata is omitted; zero is retained only when explicitly reported.
The host must reconcile billing and verify actual audio separately. An aborted or
failed request is not evidence of zero cost or permission to retry.
Both transports set redirect: 'error' so authorization cannot follow a redirect.
TtsError contains only a stable code, provider and optional HTTP status; it never
includes upstream response text, input text, keys or an unsafe error cause.
Optional voice-list enumeration is not implemented; use the existing approved
voice maps. Synthetic tests prove transport behavior, not real voices or delivery.
Provider contracts: MiniMax synchronous speech, ElevenLabs create speech.
Realtime voice (0.13.0 candidate)
./openai-live reuses [email protected], an optional peer separate from Pi's
existing provider SDK. The host supplies an explicit API key, selected voice
model, instructions, browser SDP offer, deadline and private request recorder.
createOpenAILiveSession creates one store:false, client-delegated session at
the official endpoint, bounds request/response bytes and disables retries.
Only the opaque session ID and SDP answer are returned. The host must authenticate
the user, authorize cost, validate origin, retain session identity and enforce its
own lifetime before returning that answer. Origin checks alone are not login.
This API voice connection is distinct from an existing Codex subscription. An unacknowledged request remains unknown; retrying can create another billable session. Cancellation that races HTTP headers is not proof of a cancelled creation. The browser event allow-list does not admit developer-instruction changes or Responses tool calls. Browser context/receipts still cannot authorize backend work.
The optional onReturned hook retains the bounded HTTP response privately before
SDK parsing. parseOpenAILiveSessionResponse can recover a retained successful
response without calling the provider. The host must verify its original
request/session/owner association. A lost retention acknowledgement is still
unknown even if the response was successful; it does not permit another create.
./openai-live-webrtc exposes the official OpenAILiveWebRTC and LiveDataChannel
rather than a copied SDP/state-machine implementation. createOpenAILiveControls
binds before connecting, normalizes transcript fragments/delegation metadata and
waits for session.started with the exact server-returned ID. Media remains muted
until the owning application deliberately enables it after readiness.
connectOpenAILiveVoice assembles a dedicated single-audio-track lease and an
output audio element around that SDK. It does not acquire media before the host's
explicit start action. It releases even a permission request that resolves after
cancellation. Its abort signal controls setup only; subsequent closure is explicit
and never a backend-task cancellation. Existing unrelated tracks are untouched.
The controls take an application-owned, dedicated media lease. Closing releases
that lease immediately, requests session.close once, waits a bounded time for
session.closed, then closes the native connection. Unknown finalization remains
unknown, not zero usage. Usage notifications are cumulative seconds, never sums
across notifications. Muting/closing media never cancels a backend task.
Appended facts use a deliberately small 450-UTF-8-byte update budget; the provider remains authoritative for its 500-token limit. Oversized updates are refused, not truncated or routed through another model. A matching acknowledgement means accepted context, not exact wording, completed playback or user comprehension. Task revision/authorization and durable request deduplication belong to Nerve's host application, not this per-connection adapter.
For quiet application awareness, append({ kind: 'context', delegationId: null,
... }) sends the maintained SDK's session-wide thinking-context event. It is
usable after readiness without creating a delegation or model task. The same
exact acknowledgement and 450-byte limits apply. Null is not permitted for
commentary: task-result speech still needs its real delegation identity. The
host chooses and discloses any page or selection facts; ProviderKit neither
reads the DOM nor creates developer instructions from application content.
The server-only superviseOpenAILiveSession uses the optional ws peer and the
SDK's maintained sideband client to attach to an already created session. The
product supplies its original absolute close deadline, explicit key, durable
observation sink and a separately owned cancellation signal. The adapter never
creates/forks a session, reconnects blindly, or schedules product tasks. Socket,
observation and acknowledgement failures remain explicit uncertain outcomes.
Final usage is provider-observed cumulative seconds, not client assertions.
The product runs this bounded operation in its existing durable worker; a browser
timer alone is not a spending control. Neither supervisor nor connection claims
an absolute provider billing cap when the worker/network fails.
See the official WebRTC guide and client delegation. Current tests use the actual SDK with synthetic HTTP, a synthetic sideband socket and native browser WebRTC peers. The full media assembly also tests a late input lease after cancellation. These do not prove real microphones, Chinese speech quality, provider entitlement, latency, final charges or deployment readiness.
Additional Pi OAuth accounts
./pi-oauth reuses the pinned Pi provider implementations, not copies of their
OAuth protocols. piOAuthCapabilities() is an offline inventory of the six
requested families: the existing Codex and OpenRouter adapters plus GitHub
Copilot, xAI, Kimi Code and Radius. Anthropic account login is excluded. Inventory
presence is neither a real login nor permission to sell hosted subscription use.
Radius billing is gateway-defined (subscription: null), not inferred as free.
import {
authorizePiDevice,
createPiOAuthChatTransport,
} from '@pieai/swimmer-ai-provider-kit/pi-oauth';
// Run in a durable, admitted, owner-bound server operation. The host provides
// the deadline, encrypted challenge/credential storage, consent and cancellation.
const account = await authorizePiDevice({
provider: 'xai',
signal: operationSignal,
onDeviceCode: (challenge) => retainChallengeForThisOwner(challenge),
});
await retainCredentialForThisConnection(account);
const transport = await createPiOAuthChatTransport({
account,
model: selectedModelId,
signal: taskSignal,
onAttempt: (request) => retainPrivateTaskRequest(request),
});
// transport.complete() accepts one system message and one user message.
// The existing product task/runtime owns parsing, candidate review and adoption.Each caller supplies a credential explicitly; no global account singleton,
home-directory file, environment key fallback or framework login store is used.
The host holds a cross-process claim before refreshPiOAuthCredential, saves the
result before inference, and treats a lost outcome as unresolved. Kimi's maintained
SDK may itself retry refresh; this adapter adds no retry. Copilot's maintained login
can enable unconfigured model policies, so allowCopilotModelPolicyUpdates: true
requires an informed product consent step. Enterprise/custom gateways are not
silently admitted as arbitrary token destinations.
piOAuthTextModels() returns account-filtered model IDs; Radius reads its official
gateway catalog. Catalogs remain connection-local. Model requests use Pi's maintained
stream, with a fixed provider destination, caller deadline, one outbound attempt,
no tools, bounded request/response bytes, and exact body evidence retained before
dispatch. Errors do not expose provider response text or tokens. A displayed device
code/URL is also private short-lived authorization material, not diagnostic metadata.
Default tests use synthetic HTTP and standalone Node bundles, not real accounts or
paid model calls. Directing UI, encrypted custody and live acceptance remain product
integration work. See Pi providers and the
installed SDK source for provider-specific behavior.
./openrouter-authorization is a provider-protocol adapter, not an Auth service.
The host must pin its HTTPS callback to a user-bound, expiring, single-use attempt,
persist exchanging before dispatch, and encrypt the returned key outside work/task records.
An unknown exchange must not be retried: the provider may already have issued a key.
The key is permanent/user-controlled, not a promise of refresh, subscription media
allowances, or free model usage. Disconnect and provider-side revocation are separate actions.
The adapter never reads environment credentials or starts a localhost server.
Reuse review (2026-09-14): this follows the official PKCE API contract with standard Node crypto and Fetch. Pi 0.85.1's existing OpenRouter flow is CLI-only and owns a loopback listener/manual-code race, so it cannot be directly hosted as the public callback. No Pi or third-party implementation source was copied. The host must separately satisfy the OpenRouter terms, model terms and regional restrictions; the protocol helper is not commercial permission or real-account evidence.
Internal assembly that happens to be exported (prefer the façade):
./content-safetyvisual-asset helpers (assetLooksImage,visualAssetBlob, …)./content-safetyFetchLike/ individual Sightengine factories./content-safetyparseSightengineResponse/maxSightengineScore/sightenginePathMatches(not on the root barrel)./content-safetycreateSightengineVisualTransport/readSightengineCredentials(not on the root barrel)./ttsindividual ElevenLabs / MiniMax factories whencreateDualTtsFromEnvis enough./tripoparse/map helpers whencreateTripoAdapteris enough
createOpenRouterChatTransport lives on ./openrouter. createGeminiChatTransport
lives on ./gemini. Both implement ChatCompletionTransport. Neither is
re-exported from the root barrel. parseStructuredOutput and
createStructuredOutputClient live on ./structured-output only.
createAgentRegistry lives on ./agents only.
Frozen seams
Re-measured 2026-08-13 (round 3). These look movable and are not. Do not re-open them in a later structural pass unless a real second responsibility or a dedicated capability plan appears.
| Seam | Leave it | Why |
| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| src/tripo.ts as one file | Yes. ~1243 lines is a review trigger, not a split rule. | One provider pack (credentials, credit budget, transport, adapter, parse). Splitting would make the next Tripo change touch more files for the same job. |
| ./models OpenRouter helpers | Yes. Keep createOpenRouterModel / createOpenRouterHeaders on ./models. | Collapse, TuringPact, and Show import ./models. Moving the path is a contract change. |
| FetchLike / OpenRouterFetch / GeminiFetch / TripoFetch | Yes. Four test seams, not one shared fetch type. | Unifying them changes published types. |
| OpenRouter's six redacted error strings | Yes. Do not rewrite them to match Gemini or a shared class. | Sea parses failed with (\d+). Collapse, Anvil, and SupaLuv catch the façade. A typed helper is a new capability and needs its own plan. |
| Shared HTTP/JSON helpers extracted from OpenRouter + Gemini | No extract. | The overlap is isUnknownRecord / isAbortLike / maxTokens checks — not a missing layer. A utils file would be a grab bag. |
| requestGeminiChatCompletion façade | Do not add. | The abstraction is already complete(). |
| Per-directory Boundary README / knip / madge as gates | Do not add. | 25 files; rg is enough. Extra docs become parallel truth. |
| Root barrel growth | Do not add ./chat, ./gemini, ./structured-output, ./agents, or new ./content-safety runtime values to .. | The barrel is locked at 63 runtime keys for existing root callers (SupaLuv). |
| Default Sightengine visual decide | Yes. Keep 0.72 / 0.65 / 0.75, exclude nudity.context, and do not map OCR/PII. | Tightening it would silently change SupaLuv visual review. Product policy reads parseSightengineResponse. |
| Whitespace-only Sightengine keys | Yes. trim() then missing → undefined. | Empty credentials are not credentials. Creating a provider that will 401 is worse than falling back to local visual-unavailable. |
ChatCompletionRequest.signal is optional, matching TtsSynthesizeRequest.
The kit does not pick a default timeout; products that need one pass
AbortSignal.timeout(ms). Without a signal the request JSON and fetch init
are unchanged.
ChatMessage.content is either a string (the original call shape) or an
array of { type: 'text', text } and { type: 'image', url } parts.
url is a data: URL or an https: URL. OpenRouter serializes image
parts to the OpenAI image_url wire shape. String-only callers do not
change.
Show camera-vision migration
Replace server/openRouterHttp.ts / summarizeImageSeed with
createOpenRouterChatTransport. Do not pass OpenRouter's nested
image_url object — that is the wire format, not the kit contract.
import { createOpenRouterChatTransport } from '@pieai/swimmer-ai-provider-kit/openrouter';
const result = await createOpenRouterChatTransport({
apiKey,
appName: 'Show camera seed',
appUrl: 'https://show.pieaistudio.com',
fetchImpl,
}).complete({
maxTokens: 90,
messages: [
{
role: 'user',
content: [
{ type: 'text', text: visionPrompt },
{ type: 'image', url: seed.dataUrl },
],
},
],
model: 'google/gemini-2.5-flash',
signal: AbortSignal.timeout(15_000),
temperature: 0.2,
});
const summary = result.content;The same part array works for Show's OpenRouter visual-moderation path.
Kit throws on HTTP / provider failure; Show currently inspects response.ok
itself, so the catch / fail-closed mapping stays in Show. Do not change
Show in this package.
When SHOW_MODEL_PROVIDER !== openrouter, use createGeminiChatTransport
instead of stuffing Gemini into ./openrouter. Pass a Gemini model id
(gemini-2.5-flash), not an OpenRouter id (google/gemini-2.5-flash).
Gemini image parts must be data: URLs — the transport does not fetch
https: images.
Non-Heroes character-AI migration
Replace new Mastra({ agents: { …: new Agent(…) } }). Keep product schemas,
standing instructions, and the Colyseus-safe timeout wrapper in Non-Heroes.
import { createAgentRegistry } from '@pieai/swimmer-ai-provider-kit/agents';
import { createGeminiChatTransport } from '@pieai/swimmer-ai-provider-kit/gemini';
import { createOpenRouterChatTransport } from '@pieai/swimmer-ai-provider-kit/openrouter';
const transport =
modelProvider === 'gemini'
? createGeminiChatTransport({ apiKey: geminiApiKey })
: createOpenRouterChatTransport({
apiKey: openRouterApiKey,
appName: 'Non-Heroes character AI',
appUrl: 'https://non-heroes.pieaistudio.com',
});
const agents = createAgentRegistry({
agents: {
'non-heroes-boss-ai': {
instructions: productOwnedBossInstructions,
model: modelProvider === 'gemini' ? 'gemini-2.5-flash' : 'google/gemini-2.5-flash',
name: 'Non-Heroes Boss AI',
},
},
transport,
});
const result = await agents.generateStructured({
agentId: 'non-heroes-boss-ai',
instructions: perRequestInstructions,
maxTokens: 320,
prompt,
schema: bossAiOutputSchema,
signal: AbortSignal.timeout(timeoutMs),
temperature: 0.35,
});
return result.object;Pass a transport-native model id. Do not pass Mastra's openrouter/google/…
wrapper to ./openrouter, and do not pass google/gemini-2.5-flash to
./gemini. Do not set process.env.GOOGLE_API_KEY just to satisfy Mastra.
Zod schemas stay in the product — kit only calls schema.parse.
Usage
Do not upgrade consumers to the renamed package until its exact version is visible on the official registry. For an isolated evaluation of this source, verify and create a local package:
pnpm install --frozen-lockfile
pnpm verify
pnpm pack --out artifacts/swimmer-ai-provider-kit-0.12.0.tgzThe following imports describe the renamed API. Use the packed artifact in an isolated consumer, or the verified npm release after publication, and import only the narrow module needed by the service:
import {
createContentModerationProvider,
ADULT_COMEDY_MODERATION_POLICY,
} from '@pieai/swimmer-ai-provider-kit/content-safety';
import { createDualTtsRouter, resolveTtsRoute } from '@pieai/swimmer-ai-provider-kit/tts';
import { firstDefinedEnv } from '@pieai/swimmer-ai-provider-kit/env';
import { parseAiExecutionEvidence } from '@pieai/swimmer-ai-provider-kit/execution-evidence';
import { createModelRegistry, createOpenRouterModel } from '@pieai/swimmer-ai-provider-kit/models';
import { requestOpenRouterChatCompletion } from '@pieai/swimmer-ai-provider-kit/openrouter';
import { createGeminiChatTransport } from '@pieai/swimmer-ai-provider-kit/gemini';
import type { ChatCompletionTransport } from '@pieai/swimmer-ai-provider-kit/chat';
import { createStructuredOutputClient } from '@pieai/swimmer-ai-provider-kit/structured-output';
import { createAgentRegistry } from '@pieai/swimmer-ai-provider-kit/agents';Execution evidence accepts bounded identifiers, exact integer-string usage and cost fields, and explicit completeness states. Unknown fields are discarded so raw prompts, completions, provider payloads, and accidental credential fields cannot cross through the parsed result. Products still decide pricing and whether a failed attempt is billable.
Assess a real-person reference separately from harmful-content moderation:
const moderation = createContentModerationProvider({
sightengineApiUser: process.env.SIGHTENGINE_API_USER,
sightengineApiSecret: process.env.SIGHTENGINE_API_SECRET,
});
const decision = await moderation.reviewAdultReference?.({
kind: 'image',
dataUrl: uploadedImageDataUrl,
});
if (decision?.status !== 'adult') {
// Product decides how to explain minor, uncertain, or no_real_face.
}The assessment uses Sightengine face-age, requires exactly one real face, and
fails closed when credentials, the classifier, or a reliable score are
unavailable. A normal photo of a minor is not labeled child exploitation: the
result is minor_reference_detected, a feature eligibility decision.
Sightengine signals vs default decide
parseSightengineDecision is the published default decide. It does not change
when a product wants a stricter (or looser) visual rule.
parseSightengineResponse is the normalize step that default decide used to
throw away:
signals: every finite number in the JSON tree, with lowercased path keys. This includestext.email,nudity.context.indoor_other,nudity.bikini, and non-risk numbers such asrequest.operations.raw: the classifier payload as returned. OCR match arrays andtext.detected_categoriesare often strings, not numbers — they live here.
maxSightengineScore and sightenginePathMatches let a product write its own
predicates. Example — block OCR email/phone the way Show's latch test does,
without changing kit defaults:
import {
maxSightengineScore,
parseSightengineResponse,
sightenginePathMatches,
} from '@pieai/swimmer-ai-provider-kit/content-safety';
const parsed = parseSightengineResponse(raw);
if (parsed.ok) {
const pii = maxSightengineScore(parsed.signals, (path) =>
sightenginePathMatches(path, [/email/, /phone/, /ssn/, /pii/]),
);
if (pii !== undefined && pii >= 0.7) {
// Product decision. Kit default still allows this payload.
}
}nudity.context.indoor_other is a scene-location class (indoor vs beach/pool),
not an adult-content score. Sightengine documents it for combining with
swimwear ("allow bikini at a pool, not indoors"). A product that wants that
rule should join nudity.bikini with nudity.context.*. Treating
indoor_other alone as visual_adult is expressible and is probably wrong.
Whitespace-only apiUser / apiSecret stay missing credentials.
readSightengineCredentials, createSightengineVisualTransport, and
createSightengineVisualProvider all return undefined so callers fall back
to local visual_review_unavailable. That is input validation, not a policy
threshold.
Show Sightengine migration
Do not wrap kit default decide and then re-run Show's parser. Use the
transport, then apply Show's predicates to signals / raw.
import {
createSightengineVisualTransport,
maxSightengineScore,
moderationFallback,
sightenginePathMatches,
} from '@pieai/swimmer-ai-provider-kit/content-safety';
const transport = createSightengineVisualTransport({
apiSecret: process.env.SIGHTENGINE_API_SECRET,
apiUser: process.env.SIGHTENGINE_API_USER,
fetchImpl,
});
const checked = await transport?.check(asset);
if (!checked) {
// keys missing — same as today's kit factory
}
if (!checked?.ok) {
return moderationFallback(checked?.reasonCode ?? 'visual_review_unavailable', 'sightengine');
}
const pii = maxSightengineScore(checked.signals, (path) =>
sightenginePathMatches(path, [/email/, /phone/, /ssn/, /pii/]),
);
if (pii !== undefined && pii >= 0.7) {
return {
allowed: false,
category: 'private_info',
provider: 'sightengine',
reasonCode: 'visual_private_info',
};
}Kit still does not own Show's local text rules, reviewOutput asset
extraction, or OpenRouter vision fallback. FormData filenames stay
asset-image.png / asset-video.mp4; Sightengine does not use the name
as a signal. Do not change Show in this package.
Secrets are inputs only. This package never stores, logs, or bundles secret values.
Images API
createOpenRouterImageTransport and OpenRouterImageError live on
@pieai/swimmer-ai-provider-kit/openrouter-images, not the root barrel. The transport
uses the dedicated Images API
reviewed on 2026-09-13, not a chat-output workaround. Existing brand headers
and usage normalization are reused; normalizeOpenRouterUsage is now also
exported from ./openrouter without changing its behavior.
Each request requires a model, provider tag, prompt and caller-owned abort
signal. It pins one image, allow_fallbacks: false, buffered PNG output and
up to four bounded inline PNG/JPEG/WebP reference data URLs. It does not
fetch reference URLs, retry, load host auth, discover models, choose prices,
reserve funds, write files or adopt media. Products must verify the selected
endpoint's current capabilities and price before obtaining consent.
The request hook runs after serialization and before sending; failure prevents dispatch. The returned-body hook receives bounded raw data before parsing for product-owned recovery. Hooks contain no headers but may contain private work or reference bytes, so products must apply their own authorized, redacted retention boundary. Error causes distinguish cancellation, rejection, storage, transport and invalid responses while financial outcome remains unknown after dispatch. Never infer a refund or retry from cancellation alone.
Provider-reported MIME and canonical base64 are transport evidence, not proof of valid media or artistic quality. Products still inspect bytes and candidates before adopting them. Video generation is not implemented by this subpath; existing TTS and vision contracts remain separate. Only synthetic HTTP tests have run; no real image, provider account or model bill has been tested.
The transport first shipped under the old package name in 0.7.0. Version 0.9.0 added
parseOpenRouterImageResponse(model, retainedBody) on this same subpath, so a
product can recover a previously retained response without calling generation
again. It checks the bounded response shape/base64/MIME and normalizes usage;
the product still verifies actual image bytes, owner/task binding, adoption and
settlement. Parsing is not proof of image quality or authority to charge a user.
User-owned Codex connection (server only)
@pieai/swimmer-ai-provider-kit/openai-codex consumes the public APIs of the optional
peer @earendil-works/[email protected]. Install that peer only in servers using this
subpath; existing transports and root exports are unchanged. Node 24 is required.
Use authorizeCodexDevice({ signal, onDeviceCode }) in an isolated, bounded job.
The callback receives only the official verification URL, user-visible code and
expiry, not the private device identifier. Store the returned credential through
the host's encrypted, owner-bound custody service. No credential is discovered
from the machine, environment, browser or global Pi session. The host must obtain
appropriate account consent and verify the provider's supported deployment use;
this library does not grant commercial permission.
refreshCodexCredential(credential, signal) performs one refresh. The host holds
an exclusive cross-process connection claim, saves the rotated credential before
using it and treats a lost acknowledgement as uncertain. Never retry an uncertain
refresh or infer provider-side revocation from a local disconnect.
createCodexChatTransport({ credential, model, onAttempt, fetchImpl? }) implements
the existing chat contract for one system message and one text user message.
Choose an explicit ID from codexTextModels(); catalog presence is not proof of
account entitlement. A required abort signal, one SSE attempt, no tools, no hidden
retry and no account/API-key fallback bound the execution. The credential and
model are pinned when the transport is created.
onAttempt runs at the SDK's final fetch boundary. It receives decompressed JSON,
its SHA-256 and the actual wire-body SHA-256 (identity or zstd encoding), without
headers or credentials. This body may contain private work: retain it only through
the product's authorized evidence store, with its redaction/retention rules. A
callback failure prevents dispatch; the observer cannot modify the transmitted
request. Observation is an attempted submission, not proof the provider read it.
The transport bounds request and response bytes and requires a completed stream.
It does not claim a hard output-token cap: the Codex subscription protocol does
not send max_output_tokens, even when the generic contract has maxTokens.
Products must expose bounded calls/deadlines and the user's actual account quota.
Reported usage has no invented API cost or free-credit promise. No ChatGPT web
memory, image/video entitlement, account sharing or automatic quota rotation is
provided. Current evidence is real installed SDK plus synthetic HTTP, not a real
user login or paid model test.
Native Codex tool stream (server only)
Account usage observations
readCodexAccountStatus({ credential, signal }) on ./openai-codex reads the
native Codex account usage endpoint. The credential must come from the host's
existing explicit login and private custody; no local auth file, browser session,
environment key or second account is discovered. It makes no inference request.
The result separates account fingerprint, optional plan, observation timestamp, account windows and additional feature-specific windows. Missing percentages stay missing; an unavailable reading is not zero or unlimited usage. The stable fingerprint is a display discriminator, not the account email or authentication. Display and routing policy belong to the product. A current quota observation never proves that a previous request did not execute and never authorizes replay.
Protocol reference: the native Codex backend client. No vendor source or third-party credential importer is incorporated.
Native tool transport
createCodexModelStream lives on ./openai-codex-stream. It accepts an explicit
credential, a model ID, private request-retention callback and optional test
fetch. Its stream(context, { signal }) accepts Pi's native context and returns
Pi's native event stream; a successful final toolUse message may contain
structured tool calls. A subsequent invocation can include the associated
toolResult history. It does not convert text or arbitrary JSON into commands.
Version 0.13.1 adds optional reasoningEffort at factory creation:
low, medium, high, or xhigh. Omission preserves low for existing callers.
The value is validated and frozen with the model; changing the input object later
cannot change an in-flight request. The host owns the choice and its usage budget.
The SDK's exact retained request includes the actual reasoning setting; this is
not a second model, a promised token limit, or automatic model/effort fallback.
The host supplies registered tools, deadlines, scope authorization, budget and durable execution. Incremental tool events are previews, not permission to execute. Failed streams finish with an empty, sanitized error message: never execute or replay a partial tool result. Numeric usage placeholders on failed responses mean usage is unknown, not that the attempt was free.
The source freezes credentials, model, context and cancellation signal per
request; limits context to 128 messages, 32 uniquely named tools and 120,000 UTF-8
bytes; and shares the recorded, one-attempt, fixed-destination request boundary
with the legacy text adapter. The existing ./openai-codex API does not gain
tools implicitly. Other providers have not acquired tool-stream support through
this addition. Synthetic tests and standalone Node bundles are not proof of live
account entitlement, hosted-use permission or completed product integration.
Grok native tools and media are separate connections
./xai-model-stream exports createXaiModelStream and xaiTextModels. Supply
the existing server-owned PiOAuthCredential, a catalogued model and an awaited
onAttempt callback. A stream accepts Pi's native Context, including tools
and prior tool results; it never executes those tools. Only a successful final
native message may reach the host's capability checks. The host must retain its
original request/dispatch fence and must not resend an unknown result. This path
uses the Grok model's native reasoning default, without labelling it Codex high.
./xai-media exports createXaiMediaTransport, XAI_IMAGE_MODEL,
XAI_VIDEO_MODEL and XaiMediaError. Its factory requires an explicit media
API key or the explicit { account: PiOAuthCredential } alternative described
below. generateImage submits one URL-output image;
submitVideo returns only after retainSubmission has saved the original request
ID; queryVideo performs one GET of that ID and never generates or retries a job.
The same host must retain each POST through onAttempt before dispatch. A failed
retention after dispatch is unknown; do not submit again. Invalid input and
pre-dispatch refusal are distinguished from an unknown submit or failed GET.
Callers must pass reviewed image resolution/quality and video duration/resolution/ audio explicitly. A small engineering sample can use one 1k/low image and a 3-second 480p silent video; these parameters do not authorize payment or promise a fixed price. Image references must already be owned and approved by the host. Returned URLs are temporary observations, not verified files: use the host's existing guarded-download, byte/hash/MIME, storage and explicit-adoption paths. No media bytes, user credentials, arbitrary local files or public relay are discovered by either module. Tests use the real SDK with synthetic HTTP only.
Protocol references: subscription text integration, images and asynchronous videos. Authorization for text does not establish access or billing for these media APIs.
Explicit xAI media authorization
createXaiMediaTransport on ./xai-media accepts exactly one of { apiKey }
or { account }, where account is a server-owned PiOAuthCredential with
provider: 'xai'. Pi still owns OAuth login; the host stores and refreshes that
same account before constructing the transport. No browser token, CLI auth-file
lookup, agent loop, credential refresh loop or alternate paid key is introduced.
Both authorization modes use the existing image/video wire protocol and mandatory request/result retention. The account and expiry are frozen at construction; expiry or rejection does not authorize trying another account or API key. For an existing video, construct a transport from the refreshed original account and query the retained request ID; never submit another video to recover one.
Protocol evidence is xAI's Grok Build source at
f0e3be1100ef5252488e3be8bb0e91cf68d8c305, especially
api_key_provider.rs
and the corresponding implementations/grok_build/media_bearer.rs. This is
protocol research, not copied donor implementation. Its native path explicitly
accepts xAI API keys or xAI OAuth tokens and refuses foreign-session fallback.
The xAI usage explanation describes a shared subscription allowance and optional extra usage credits. OAuth does not prove remaining included usage or disable already-enabled provider-side billing. Products must show the actual account/funding source and keep live generation closed until its entitlement and spending boundary are verified. Neither a zero platform-points quote nor a synthetic HTTP test proves zero supplier cost.
Verification commands
pnpm typecheck, pnpm test, pnpm build, or pnpm verify.
Origins
The first extraction came from behavior already present in TuringPact and Show. The product-specific migration examples above retain that integration context; they are not a fresh portfolio inventory or proof those consumers have upgraded.
doc/LearningFrom0/ is a historical beginner tutorial. It is not the public API
SSOT.
