Skip to content

fix(media): voice stream connects to the served URL; media types match the API - #63

Merged
lena-sabadina merged 3 commits into
mainfrom
fix/media-sync
Nov 8, 2026
Merged

lena-sabadina merged 3 commits into
mainfrom
fix/media-sync

Conversation

@safenestdev

@safenestdev safenestdev commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

What changes for users

Voice streaming works again. voiceStream() connected to a URL the API does not serve, so sessions could not connect. It now connects to /api/v1/safety/voice/stream, and:

  • sends the initial config once the session is ready (it was sent before, when the server was not yet reading messages, so it never applied);
  • sends context in the format the API reads (ageGroup and the new childAge now take effect; platform is attributed like every other call; relationship is deprecated, it was never read);
  • supports video: enableVideo and frameIntervalSeconds in the config, sendVideoFrame() on the session, a new frame_analysis event with an onFrameAnalysis handler, and alerts with category visual;
  • sendAudio() and sendVideoFrame() throw until the session is ready, instead of sending data the server would not read.

Uploads

  • analyzeVoice, analyzeImage, analyzeVideo and analyzeDocument now send incident_moderation_enabled (it was accepted but never sent).
  • analyzeImage and analyzeVideo take flagRiskTerms and return risk_terms.
  • analyzeVideo takes analysisType and maxFrames.
  • analyzeDocument accepts PNG, JPEG and WebP images as well as PDF, and takes splitBySender for messaging screenshots. file_id is always returned.
  • VoiceAnalysisResult gains language and language_status.

Synthetic content results now match what the API returns

  • Image: the analysis is in semantic_analysis (vision was never returned). Adds screenshot_detection (is_screenshot, confidence), metadata, file_id.
  • Audio: AudioStats holds the seven published measurements (rms_level, rms_peak, dc_offset, dynamic_range, silence_ratio, peak_count, flat_factor). Adds metadata, file_id. Transcription segments is optional (absent when there is no speech).
  • Video: temporal_consistency uses score, identity_consistency, landmark_stability, anomalous_pairs. Adds drift_signature, forensic_signals, language_status, metadata, file_id.
  • The forensic summary carries signal_count, sources (name, signal_count, confidence_boost) and combined_confidence_boost. These types follow the published response shape for these endpoints.
  • fileId is forwarded on synthetic image, audio and video.

New methods

  • verifyImageMatch(): face match, age estimate, explicit-content and synthetic checks in one call (POST /api/v1/verify-image-match). Bytes are base64-encoded for you.
  • getSafetyClassification(): the account's daily safety classification (GET /api/v1/safety-classification). An account with no classification yet answers { exists: false }, never a zero score.

Deprecated (kept, marked @deprecated): bypassCache (no-op, there is no verdict cache), vision on synthetic image results, rms_mean, the old temporal_consistency field names and signals on temporal and lip-sync results, action_detail on synthetic text, image, audio and video results and on document page results, detected_language on document page results, incident_moderation_enabled on synthetic image, audio and video inputs (not read there), and voice stream context.relationship. The getSyntheticProfile note now says the route was removed. The SDK no longer sends bypass_cache.

Tests

  • Voice streaming is tested against a local ws server that, like the API, reads messages only after ready. It asserts the URL and path the client connects to, Bearer auth, the config message shape, the 0x01 video frame prefix and frame_analysis dispatch. No test reaches the network.
  • Form fields and the new methods are tested with vi.spyOn(global, 'fetch').
  • Result shapes are type-level tests (tests/types/*.test-d.ts), which npx vitest run now type-checks through tsconfig.typecheck.json. A field name the API does not send passes every runtime test, so these pin the shapes.

Before the fix (tests written first)

     × posts JSON to /api/v1/verify-image-match with images base64-encoded
     × passes a base64 string through unchanged and omits unset fields
     × rejects a missing image before calling the API
     × GETs /api/v1/safety-classification and returns the stored classification
     × keeps a null score as null when data is insufficient
     × answers exists: false on 404, never a zero score
     × still throws on other errors
       × analyzeVoice: forwards an explicit false
       × analyzeVoice: forwards true
       × analyzeImage: forwards an explicit false
       × analyzeImage: forwards true
       × analyzeVideo: forwards an explicit false
       × analyzeVideo: forwards true
       × analyzeDocument: forwards an explicit false
       × analyzeDocument: forwards true
       × analyzeImage: forwards true and an explicit false
       × analyzeVideo: forwards true and an explicit false
       × forwards analysisType and maxFrames
       × forwards splitBySender for a screenshot
       × detectSyntheticImage forwards file_id
       × detectSyntheticAudio forwards file_id
       × detectSyntheticVideo forwards file_id
     × connects to the path the API serves, with Bearer auth
     × sends the initial config after ready, so the API does not drop it
     × sends context in the wire format the API reads
     × attributes the platform even when no context is given
     × updateConfig maps context the same way and leaves context out when not given
     × sends video frames as binary prefixed with 0x01, and audio untagged
     × delivers frame_analysis events to onFrameAnalysis
     × image and video take flagRiskTerms
     × video takes analysisType and maxFrames
     × document takes splitBySender
     × synthetic media inputs take fileId
     × voice reports its language
     × image and video carry risk_terms
     × document always returns file_id
     × image returns semantic_analysis, not vision
     × image returns the screenshot and deep image summaries and echoes
     × audio stats use the field names the API sends
     × audio transcription may come without segments
     × video temporal consistency and lip sync match the wire
     × video returns drift_signature, language_status, forensic and audio summaries
     × config takes video options, childAge and the all analysis type
     × frame analysis is a stream event with its own handler
     × alerts and summaries carry the visual category and frame count

⎯⎯⎯⎯⎯⎯ Failed Tests 45 ⎯⎯⎯⎯⎯⎯⎯
 Test Files  4 failed | 9 passed (13)
      Tests  45 failed | 151 passed (196)

After the fix

 ✓ tests/imageMatchAndClassification.test.ts > verifyImageMatch > posts JSON to /api/v1/verify-image-match with images base64-encoded
 ✓ tests/imageMatchAndClassification.test.ts > verifyImageMatch > passes a base64 string through unchanged and omits unset fields
 ✓ tests/imageMatchAndClassification.test.ts > verifyImageMatch > rejects a missing image before calling the API
 ✓ tests/imageMatchAndClassification.test.ts > getSafetyClassification > GETs /api/v1/safety-classification and returns the stored classification
 ✓ tests/imageMatchAndClassification.test.ts > getSafetyClassification > keeps a null score as null when data is insufficient
 ✓ tests/imageMatchAndClassification.test.ts > getSafetyClassification > answers exists: false on 404, never a zero score
 ✓ tests/imageMatchAndClassification.test.ts > getSafetyClassification > still throws on other errors
 ✓ tests/mediaUploads.test.ts > media upload form fields > incident_moderation_enabled > analyzeVoice: forwards an explicit false
 ✓ tests/mediaUploads.test.ts > media upload form fields > incident_moderation_enabled > analyzeVoice: forwards true
 ✓ tests/mediaUploads.test.ts > media upload form fields > incident_moderation_enabled > analyzeImage: forwards an explicit false
 ✓ tests/mediaUploads.test.ts > media upload form fields > incident_moderation_enabled > analyzeImage: forwards true
 ✓ tests/mediaUploads.test.ts > media upload form fields > incident_moderation_enabled > analyzeVideo: forwards an explicit false
 ✓ tests/mediaUploads.test.ts > media upload form fields > incident_moderation_enabled > analyzeVideo: forwards true
 ✓ tests/mediaUploads.test.ts > media upload form fields > incident_moderation_enabled > analyzeDocument: forwards an explicit false
 ✓ tests/mediaUploads.test.ts > media upload form fields > incident_moderation_enabled > analyzeDocument: forwards true
 ✓ tests/mediaUploads.test.ts > media upload form fields > incident_moderation_enabled > sends nothing when not set (the account setting applies)
 ✓ tests/mediaUploads.test.ts > media upload form fields > flagRiskTerms on image and video > analyzeImage: sends nothing when not set
 ✓ tests/mediaUploads.test.ts > media upload form fields > flagRiskTerms on image and video > analyzeImage: forwards true and an explicit false
 ✓ tests/mediaUploads.test.ts > media upload form fields > flagRiskTerms on image and video > analyzeVideo: forwards true and an explicit false
 ✓ tests/mediaUploads.test.ts > media upload form fields > flagRiskTerms on image and video > analyzeImage: passes the risk_terms result through
 ✓ tests/mediaUploads.test.ts > media upload form fields > analyzeVideo options > forwards analysisType and maxFrames
 ✓ tests/mediaUploads.test.ts > media upload form fields > analyzeVideo options > sends neither when not set
 ✓ tests/mediaUploads.test.ts > media upload form fields > analyzeDocument > forwards splitBySender for a screenshot
 ✓ tests/mediaUploads.test.ts > media upload form fields > analyzeDocument > sends no split_by_sender when not set
 ✓ tests/mediaUploads.test.ts > media upload form fields > fileId on synthetic media > detectSyntheticImage forwards file_id
 ✓ tests/mediaUploads.test.ts > media upload form fields > fileId on synthetic media > detectSyntheticAudio forwards file_id
 ✓ tests/mediaUploads.test.ts > media upload form fields > fileId on synthetic media > detectSyntheticVideo forwards file_id
 ✓ tests/voiceStream.test.ts > voice stream > connects to the path the API serves, with Bearer auth
 ✓ tests/voiceStream.test.ts > voice stream > sends the initial config after ready, so the API does not drop it
 ✓ tests/voiceStream.test.ts > voice stream > sends context in the wire format the API reads
 ✓ tests/voiceStream.test.ts > voice stream > attributes the platform even when no context is given
 ✓ tests/voiceStream.test.ts > voice stream > updateConfig maps context the same way and leaves context out when not given
 ✓ tests/voiceStream.test.ts > voice stream > sends video frames as binary prefixed with 0x01, and audio untagged
 ✓ tests/voiceStream.test.ts > voice stream > delivers frame_analysis events to onFrameAnalysis
 ✓ tests/types/media.test-d.ts:36 > media inputs > image and video take flagRiskTerms
 ✓ tests/types/media.test-d.ts:41 > media inputs > video takes analysisType and maxFrames
 ✓ tests/types/media.test-d.ts:46 > media inputs > document takes splitBySender
 ✓ tests/types/media.test-d.ts:50 > media inputs > synthetic media inputs take fileId
 ✓ tests/types/media.test-d.ts:58 > media results > voice reports its language
 ✓ tests/types/media.test-d.ts:63 > media results > image and video carry risk_terms
 ✓ tests/types/media.test-d.ts:69 > media results > document always returns file_id
 ✓ tests/types/media.test-d.ts:75 > synthetic results > image returns semantic_analysis, not vision
 ✓ tests/types/media.test-d.ts:80 > synthetic results > image returns the screenshot and deep image summaries and echoes
 ✓ tests/types/media.test-d.ts:87 > synthetic results > audio stats use the field names the API sends
 ✓ tests/types/media.test-d.ts:93 > synthetic results > audio transcription may come without segments
 ✓ tests/types/media.test-d.ts:100 > synthetic results > video temporal consistency and lip sync match the wire
 ✓ tests/types/media.test-d.ts:108 > synthetic results > video returns drift_signature, language_status, forensic and audio summaries
 ✓ tests/types/media.test-d.ts:118 > voice stream > config takes video options, childAge and the all analysis type
 ✓ tests/types/media.test-d.ts:125 > voice stream > frame analysis is a stream event with its own handler
 ✓ tests/types/media.test-d.ts:131 > voice stream > alerts and summaries carry the visual category and frame count

 Test Files  13 passed (13)
      Tests  196 passed (196)

Follow-up: synthetic results follow the published response shape

The synthetic image, audio and video types now list exactly the fields in the published response for those endpoints. Fields added earlier in this PR that are not part of it were removed (never released), and action_detail is deprecated on these results. Type tests pin the exact key sets.

Before:

     × image returns the public screenshot summary and echoes, nothing more
     × audio stats hold exactly the public keys, plus the deprecated rms_mean
     × audio transcription may come without segments
     × video returns drift_signature, language_status and the forensic summary
 Test Files  1 failed | 12 passed (13)
      Tests  4 failed | 193 passed (197)

After:

 ✓ tests/types/media.test-d.ts:81 > synthetic results > image returns the public screenshot summary and echoes, nothing more
 ✓ tests/types/media.test-d.ts:89 > synthetic results > audio stats hold exactly the public keys, plus the deprecated rms_mean
 ✓ tests/types/media.test-d.ts:97 > synthetic results > forensic summary holds only the public fields
 ✓ tests/types/media.test-d.ts:102 > synthetic results > audio transcription may come without segments
 ✓ tests/types/media.test-d.ts:117 > synthetic results > video returns drift_signature, language_status and the forensic summary
 Test Files  13 passed (13)
      Tests  197 passed (197)

Full suite: 146 tests before this PR, 197 after. npm run build is clean.

Closes #58

…h the API

- Voice stream connects to /api/v1/safety/voice/stream, sends its initial
  config once the session is ready, sends context in the wire format
  (age_group, child_age, resolved platform), and supports video frames with
  frame_analysis events and visual alerts.
- Voice, image, video and document uploads send incident_moderation_enabled.
- Image and video take flagRiskTerms and return risk_terms; video takes
  analysisType and maxFrames; document accepts images and splitBySender.
- Synthetic image, audio and video results use the field names the API
  returns; fileId is forwarded; bypassCache is deprecated as a no-op.
- New verifyImageMatch() and getSafetyClassification().
- Type-level tests pin the result shapes.

Closes #58
safenestdev and others added 2 commits November 8, 2026 08:49
…e shape

Synthetic image, audio and video results now list only the fields the API
publishes: screenshot_detection carries is_screenshot and confidence,
AudioStats carries the seven published measurements, and the forensic
summary carries signal_count, sources and combined_confidence_boost.
action_detail is deprecated on these results, as it is not part of their
response. Type-level tests pin the exact key sets.

@lena-sabadina lena-sabadina left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed media and voice changes. Merged main in and unified SpeakerStance so the typecheck passes; CI is green and the full suite passes locally.

@lena-sabadina
lena-sabadina merged commit 58c64c4 into main Nov 8, 2026
6 of 7 checks passed
@lena-sabadina
lena-sabadina deleted the fix/media-sync branch November 8, 2026 09:42
@safenestdev safenestdev mentioned this pull request Nov 8, 2026
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.

Media: voice streaming connects to a URL the API does not serve, and synthetic-content results misname fields

2 participants