Skip to content

feat(detection): bring text detection types up to date with the API - #62

Merged
lena-sabadina merged 3 commits into
mainfrom
feat/text-detection-sync
Nov 8, 2026
Merged

lena-sabadina merged 3 commits into
mainfrom
feat/text-detection-sync

Conversation

@safenestdev

@safenestdev safenestdev commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

What changes for SDK users

New

  • detectNickname() for POST /api/v1/safety/nickname: reads a display name before it is shown. Returns recommendation (advice for the name), not recommended_action, so it stays out of strongestAction and isActionable.
  • context can now declare sender, recipients (with relationshipToSender), conversationType, minorDisclosureCheck, and authorRole (distress signals). All are translated to the API's snake_case keys, including nested participant fields.
  • attestation and messageId on detectBullying, detectGrooming, detectUnsafe and all unified detectors; newConversation on detectUnsafe; includeRelationshipFrame on detectGrooming.
  • ConversationType and CONVERSATION_TYPES exported from constants; Detection gains DISTRESS_SIGNALS, TFGBV, SYNTHETIC_CONTENT and is now exported from the package root (the README already imported it from there); DetectionName string union for analyseMulti.
  • default_conversation_type on the detection settings types.

Result types

  • Shared interfaces: AttestationFields (signed attestation), DeclaredContextFields (setting in effect, participants applied, minor disclosure), ResultAdjustments, combined as TextDetectionFields on BullyingResult, GroomingResult, UnsafeResult and DetectionResult. Result adjustment fields are now typed.
  • BullyingResult: bullying_reported, bullying_reported_by, speaker_role, closed BullyingType union. GroomingResult: analysis_status, speaker_stance, relationship_frame, closed GroomingFlag union. UnsafeResult: third_party_concern, context_factors.
  • Per-endpoint result types, and narrowed return types, for the ten unified detectors (CoerciveControlResult with disclosure_capped, DistressSignalsResult, RadicalisationResult, AppFraudResult, VulnerabilityExploitationResult, and others). Each extends DetectionResult, so existing code keeps compiling.
  • analyseMulti: results typed as MultiDetectionResult[]; summary gains close_calls, analysis_status, recommended_action/action_detail (partial runs), failed_endpoints, skipped_endpoints, total_credits_used, processing_time_ms; top-level support.
  • BatchResultItem.error_code; MessageAnalysis.unsupported_flags.
  • The existing doc comments on escalation_capped/escalation_capped_reason (BullyingResult, UnsafeResult) now use the same generic wording as the other result adjustment fields. DetectionAttestation.detector.prompt_template_version is documented as a version identifier included in the signed attestation.

Deprecated (never returned or accepted by the API; kept, not removed)
UnsafeResult.risk_level, DetectionResult.trajectory_risk/trajectory/severity_series, SupportData.response_guides/language, SupportResponseGuide.childSpecificActions/confidential/language, AnalyseMultiResult.credits_used, AnalyseMultiInput.verdictOnly, BatchAnalyzeInput.continueOnError, GenerateReportInput.incident, ActionPlanResult.reading_level.

Type narrowing to note

  • Audience no longer includes 'educator': the API has always rejected it with a 400, and the SDK now rejects it with a ValidationError before sending.
  • AnalyseMultiInput.detections is DetectionName[] instead of string[]. Plain string literals and Detection enum values both work.

Bugs fixed (failing test first)

  1. analyzeEmotions (and batch emotions items) sent the declared age as ageGroup with a platform; the route reads only context.age_group, so the age never arrived. Now sends { age_group } only. Also forwards incident_moderation_enabled.
  2. getActionPlan accepted audience: 'educator', which the API rejects; and read reading_level while the API returns approx_reading_level. Now validates the audience and returns approx_reading_level (copied to reading_level for existing callers).
  3. generateReport spread incident into meta, where the API drops it, and could not send conversation_id or timestamp_range. Now sends conversationId and timestampRange; incident is deprecated and no longer sent.
  4. analyseMulti did not forward flagProfanity/flagRiskTerms, and sent verdict_only, which the multi route does not accept.
  5. New context keys were spread raw, so they went out in camelCase and were ignored.
  6. batch dropped the per-item error_code.

Before (on main):

 × analyzeEmotions > sends the declared age as context.age_group, the only key the route reads
 × analyzeEmotions > omits context when no age group is declared
 × analyzeEmotions > forwards incident_moderation_enabled
 × analyzeEmotions > batch emotions items send context.age_group too
 × getActionPlan > rejects an audience the API does not accept before sending
 × getActionPlan > returns approx_reading_level, and keeps reading_level populated from it
 × generateReport > sends conversation_id and timestamp_range in meta, and nothing the route drops
 × analyseMulti > forwards flagProfanity and flagRiskTerms as options
 × analyseMulti > does not send verdict_only, which the multi route does not accept
 × context > translates the declared participants and setting to snake_case on the wire
 × context > translates authorRole for distress signals
 × batch > keeps the per-item error_code the API returns
      Tests  12 failed (12)

AssertionError: expected { ageGroup: '11-13', …(1) } to deeply equal { age_group: '11-13' }
AssertionError: promise resolved "{}" instead of rejecting
AssertionError: expected undefined to be 'grade 6'
-   "conversation_id": "conv-1",   +   "notes": "n",  +   "type": "bullying",
AssertionError: expected undefined to deeply equal { flag_profanity: true, …(1) }
-   "conversation_type": "family", +   "conversationType": "family",

After:

 ✓ analyzeEmotions > sends the declared age as context.age_group, the only key the route reads
 ✓ analyzeEmotions > omits context when no age group is declared
 ✓ analyzeEmotions > forwards incident_moderation_enabled
 ✓ analyzeEmotions > batch emotions items send context.age_group too
 ✓ getActionPlan > rejects an audience the API does not accept before sending
 ✓ getActionPlan > returns approx_reading_level, and keeps reading_level populated from it
 ✓ generateReport > sends conversation_id and timestamp_range in meta, and nothing the route drops
 ✓ analyseMulti > forwards flagProfanity and flagRiskTerms as options
 ✓ analyseMulti > does not send verdict_only, which the multi route does not accept
 ✓ context > translates the declared participants and setting to snake_case on the wire
 ✓ context > translates authorRole for distress signals
 ✓ batch > keeps the per-item error_code the API returns
      Tests  12 passed (12)

Tests

  • tests/textRequestBugs.test.ts: the reproductions above.
  • tests/textDetectionSync.test.ts: wire bodies for the new request fields, detectNickname (body, validation, result), result field pass-through, Detection and CONVERSATION_TYPES.
  • Full suite: 146 before, 175 after, all passing. npm run build clean.

Closes #57

Failing tests for the declared age on emotions, the action plan audience
and reading level, the incident report meta, the multi-analysis flags,
context translation and the batch error code.
- Context can declare the sender, the recipients and how they relate,
  the conversation type, the minor disclosure check and the distress
  author role; all are translated to the wire's snake_case.
- attestation and messageId on every single-endpoint text detector,
  newConversation on detectUnsafe, includeRelationshipFrame on
  detectGrooming.
- New detectNickname method.
- Shared result fields (attestation, setting in effect, participants
  applied, minor disclosure) and typed result adjustment fields;
  per-endpoint result types for the unified detectors.
- analyseMulti: summary status fields, support, flagProfanity and
  flagRiskTerms; verdictOnly is no longer sent.
- Fixes for emotions age group, action plan audience and reading level,
  incident report meta and batch error codes.
- Fields the API never returns are marked deprecated.

Closes #57
…er result adjustments

Also documents prompt_template_version on the signed attestation.

@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 the detection type and client changes. New fields are covered by tests, CI is green and the full suite passes locally.

@lena-sabadina
lena-sabadina merged commit 2c5da87 into main Nov 8, 2026
6 checks passed
@lena-sabadina
lena-sabadina deleted the feat/text-detection-sync branch November 8, 2026 09:36
@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.

Text detection: bring request and result types up to date with the API, and fix five request bugs

2 participants