Skip to content

feat(voice): audio library and provider credential commands (Voice Automation beta) - #25

Merged
purpshell merged 17 commits into
devfrom
t3code/voice-audio-library
Sep 23, 2026
Merged

purpshell merged 17 commits into
devfrom
t3code/voice-audio-library

Conversation

@purpshell

@purpshell purpshell commented Sep 19, 2026 •

Copy link
Copy Markdown
Member

The Voice Automation beta needs CLI commands for its audio library and provider credentials. This PR adds polymorfa voice audio upload, TTS, list, view, update, preview and delete commands, plus polymorfa voice credential add, list, view, verify and delete. It targets dev; Calls remains unreleased, and enrolled teams need the deployed API and their existing credentials/scopes before these commands can work.

Upload reads and validates the local file before creating an asset, then uses the typed SDK Voice resource to transfer bytes to the signed URL without an API credential or redirect. Preview download is bounded to 32 MiB and refuses redirects; network errors and storage refusals omit the signed link, untrusted response text and nested causes. Provider keys come from POLYMORFA_VOICE_PROVIDER_KEY, an explicitly named environment variable, or piped stdin; no flag accepts a key value. The CLI redacts keys from errors and prints only their fingerprint. --wait distinguishes ready, failed and timed-out assets. Help, command research and README examples describe beta access and exit codes.

The branch normally merges CLI dev at 4a730e81586663adb05f832f15b8d9f1d1b1e5ac and pins published @polymorfa/sdk@0.1.0-dev.20260923144429 with integrity sha512-U/JQY4f5hE9LZFPQaOHSH8bSXXTNKFmd5ZtU6mpS7HmMcQH89eJlFfquTI3bgLtHJiyt4j/jt1wp8ULVv6IY7Q==, from SDK dev merge 75ac1bc478b9fd8da8b2cd393bd0d5a07fb79197 (Voice #279 and upload redaction #292). The API source is monorepo dev merge 087d0e34b53eec82ebc5d04c5b4c75eaaa556b4f from #227.

Verification

A clean Node 22 npm ci and npm run verify pass: formatting, lint, typecheck, 764 tests in 82 files, build, package dry run and production audit with zero findings. Tests cover upload/credential success and refusal paths, no credential on signed transfer, validation before requests, wait results, preview bounds and timeouts, and full signed-link redaction from inspected errors. No live API, provider call, deployment or production rollout was exercised.

Contract and release assessment

Part Status
API Unaffected here. #227 owns the Voice API routes, scopes, status codes and OpenAPI contract; this CLI uses that merged contract.
SDKs Exact published TypeScript SDK Voice resource is pinned; upload error redaction is provided by #292. No SDK source changes in this PR.
CLI Updated commands, flags, credential handling, output, errors, help, tests, README and command research.
Docs CLI README and command research updated. The monorepo Voice API/reference and release draft were handled in #227; this PR changes no wire shape.
Feature releases No flag, entitlement, enrollment, deployment or release action. Calls stays unreleased and Voice Automation stays a gated beta.
Admin Unaffected; no staff capability or read path changes.

… exit codes

voice_not_enabled exits 6 and gate_limit_reached exits 7 whatever their HTTP
status, so scripts can tell a missing Voice Automation beta enrollment or a
reached voice limit from other authorization and payment failures. Add the
voice_asset_failed condition (exit 13) for audio that fails processing while
a command waits for it.
Add voice audio upload|tts|list|view|update|delete|preview and voice
credential add|list|view|verify|delete for the Voice Automation beta
(calls.voice-automation), contract revision voice-audio-v1.

- upload detects the content type from the extension and refuses other
  types, empty files and files over 16 MB before any request. The bytes go
  to the returned upload URL with only its headers and no credential; the
  asset is then completed. --wait polls until ready or failed.
- credential add reads the key from the variable named by --key-env
  (default POLYMORFA_VOICE_PROVIDER_KEY) or piped stdin, never an argument,
  refuses a terminal, and registers the key for redaction.
- delete commands confirm or take --yes.

The pinned SDK (94bfd09) has no client.voice and git pins cannot install
newer SDK commits since the SDK became a workspace, so src/sdk/voice.ts
sends the documented requests through the organization client's raw
transport, mirroring client.voice in polymorfa/sdks f0f3c07.
README section with the audio library and provider credential commands, key
input rules and voice exit codes (6 voice_not_enabled, 7 gate_limit_reached,
13 voice_asset_failed); command research for contract voice-audio-v1 and the
SDK pin situation; a skill note on beta errors and provider key handling.
@coderabbitai

coderabbitai Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

  • Ask an admin to enable usage-based reviews

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Next included review available in 25 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 73 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: e8273570-edba-4e4a-9bf2-e70089d2b964

📥 Commits

Reviewing files that changed from the base of the PR and between 4a730e8 and 3ff9e04.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (24)
  • README.md
  • docs/command-research/voice.md
  • package.json
  • skills/polymorfa/SKILL.md
  • src/commands/voice/audio/delete.ts
  • src/commands/voice/audio/list.ts
  • src/commands/voice/audio/preview.ts
  • src/commands/voice/audio/tts.ts
  • src/commands/voice/audio/update.ts
  • src/commands/voice/audio/upload.ts
  • src/commands/voice/audio/view.ts
  • src/commands/voice/credential/add.ts
  • src/commands/voice/credential/delete.ts
  • src/commands/voice/credential/list.ts
  • src/commands/voice/credential/verify.ts
  • src/commands/voice/credential/view.ts
  • src/errors/conditions.ts
  • src/errors/present.ts
  • src/sdk/voice.ts
  • src/services/voice.ts
  • test/commands/voice-wait-timeout.test.ts
  • test/commands/voice.test.ts
  • test/errors/present.test.ts
  • test/services/voice.test.ts

Comment @coderabbitai help to get the list of available commands.

@purpshell

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-23T15:04:33.223827Z 3ff9e04 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e03082495a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/sdk/voice.ts
{cause: error, code: 'connection_error'},
)
} finally {
clearTimeout(timer)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the timeout active while reading transfer bodies

When a preview or failed upload endpoint sends response headers and then stalls, fetch() resolves as soon as the headers arrive and this finally clears the abort timer. The subsequent response.arrayBuffer() or safeBody(response) read can therefore hang indefinitely, ignoring both --timeout and the 120-second default. Keep the controller and timer alive until the response body has been fully consumed or cancelled.

Useful? React with 👍 / 👎.

Comment thread src/sdk/voice.ts Outdated
Comment on lines +347 to +348
const bytes = new Uint8Array(await response.arrayBuffer())
if (bytes.byteLength > maxBytes) throw tooLargePreview(maxBytes)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce the preview limit while streaming

When the preview response omits Content-Length, understates it, or is transparently decompressed to a larger size, this buffers the entire response before checking maxBytes. Thus the documented 32 MiB limit does not actually bound memory use, and a sufficiently large capability response can exhaust the CLI process before tooLargePreview() runs. Read the body incrementally and cancel it as soon as the accumulated bytes exceed the limit.

Useful? React with 👍 / 👎.

@purpshell

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 776e50266f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/services/voice.ts Outdated
let current = asset
// Like the SDK's waitUntilReady: read first, then sleep between reads.
while (current.data.status !== 'ready' && current.data.status !== 'failed') {
const response = await client.retrieveAudio(current.data.id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound each status poll by the wait deadline

When the asset is still processing and retrieveAudio() stalls, this request begins before the deadline is checked, so --wait-timeout does not stop the command at the promised time; for example, --wait-timeout 1 can remain blocked until the request's separate network timeout expires. Check the deadline before every poll and ensure the request itself is bounded by the remaining wait budget.

Useful? React with 👍 / 👎.

@purpshell
purpshell marked this pull request as ready for review September 23, 2026 14:50
@purpshell

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1750c11c59

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

apiKey,
label: flags.label,
provider: flags.provider,
...(projectId === undefined || projectId.length === 0 ? {} : {projectId}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject an explicitly empty credential project

When --project "$PROJECT" expands to an empty or whitespace-only value, this condition omits projectId, so an add intended to be project-scoped instead creates a team-wide credential that every project can use. Because omitting --project deliberately selects team-wide scope, an explicitly supplied blank value should fail validation rather than silently broaden the credential's scope.

Useful? React with 👍 / 👎.

@purpshell

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ff9e042ea

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/services/voice.ts
try {
const info = await handle.stat()
if (info.size > MAX_TEXT_FILE_BYTES) throw textTooLarge()
return (await handle.readFile('utf8')).replace(/\r?\n$/, '')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce the text-file limit while reading

When --text-file names a streaming or non-regular path such as /dev/stdin or a FIFO, stat() commonly reports a size of zero, after which readFile() buffers until EOF without enforcing MAX_TEXT_FILE_BYTES. A large or unending source therefore bypasses the advertised 64 KiB bound and can exhaust memory or hang the command before validateTtsText() runs. Read the handle incrementally and stop once the byte limit is exceeded, as the - stdin path already does.

Useful? React with 👍 / 👎.

@purpshell
purpshell merged commit 1fd51d6 into dev Sep 23, 2026
3 checks passed
@purpshell
purpshell deleted the t3code/voice-audio-library branch October 26, 2026 15:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant