Skip to content

feat(calls): move transcript to agent#2377

Merged
whutchinson98 merged 6 commits intomainfrom
hutch/feat-calls-move-transcript-to-agent
Apr 6, 2026
Merged

feat(calls): move transcript to agent#2377
whutchinson98 merged 6 commits intomainfrom
hutch/feat-calls-move-transcript-to-agent

Conversation

@whutchinson98
Copy link
Copy Markdown
Member

No description provided.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Agents can submit transcript segments to a new internal HTTP endpoint secured by a shared secret.
    • Transcript submissions include speaker identity, content, deterministic segment IDs, and UTC timestamps; submissions are deduplicated.
  • Chores

    • Added HTTP client dependency for agent outbound requests.
    • Added configuration and secrets for internal-call authentication (dev and prod).
    • Router and service wiring updated to expose internal-call endpoints.

Walkthrough

This PR adds agent-to-backend transcript posting using a shared secret: the transcription agent now sends transcript segments via HTTP (httpx) to an internal endpoint authenticated with an x-macro-internal-call header; backend routing and service wiring were updated to validate that secret.

Changes

Cohort / File(s) Summary
Agent HTTP & deps
agents/transcription/requirements.txt, agents/transcription/transcriber.py
Added httpx dependency. Transcriber now requires channel_id and an injected httpx.AsyncClient, builds deterministic segmentId/UTC timestamps, POSTs segments to {MACRO_API_URL}/call/{channel_id}/transcript with x-macro-internal-call, implements retry/backoff for network/5xx, logs failures, always raises StopResponse() after attempt. Entrypoint creates/shares the AsyncClient and closes it on shutdown.
Infra config & env
infra/stacks/cloud-storage-service/Pulumi.dev.yaml, infra/stacks/cloud-storage-service/Pulumi.prod.yaml, infra/stacks/cloud-storage-service/index.ts, .env-local.enc, .env-localdev.enc
Added call_internal_secret Pulumi config entries and wired retrieval of INTERNAL_CALL_SECRET from Secrets Manager into container env. Added encrypted INTERNAL_CALL_SECRET entries to local env files.
Backend domain & service
rust/cloud-storage/call/src/domain/ports.rs, rust/cloud-storage/call/src/domain/service.rs
Extended CallService trait with validate_internal_call(&self, token: &str) -> bool. CallServiceImpl gains internal_call_secret: Option<String>, a with_internal_call_secret builder, and an internal validation helper implementing exact-match secret validation.
Backend routing & extractor
rust/cloud-storage/call/src/inbound/axum_router.rs
Removed POST /{channel_id}/transcript from authenticated call_router; added internal_call_router, InternalCallRouterState, InternalCallAccessExtractor (validates x-macro-internal-call via service), and updated transcript_handler to accept internal state, the extractor, and path channel_id.
Document Storage service wiring
rust/cloud-storage/document_storage_service/src/config.rs, .../src/api/context.rs, .../src/api/mod.rs, .../src/main.rs
Declared InternalCallSecret env var type, added DssCallInternalState alias and call_internal_state to ApiContext, mounted internal_call_router under /call, and conditionally configured CallServiceImpl with the internal secret at startup (enforcing presence when agent name set).
API spec
js/app/packages/service-clients/service-storage/openapi.json
Updated POST /call/{channel_id}/transcript description to indicate agent-origin and x-macro-internal-call shared-secret auth; duplicate-segment behavior unchanged.

Possibly related PRs

🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess whether it relates to the changeset. Add a pull request description explaining the motivation, implementation approach, and any relevant context for moving transcript handling to the agent.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title follows conventional commits format with 'feat:' prefix and is 37 characters, well under the 72-character limit, and accurately describes the main change of moving transcript handling from frontend to agent.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@agents/transcription/transcriber.py`:
- Around line 58-75: Replace the ephemeral uuid4() segmentId with a stable id
derived from the provider/message identifier (e.g., use an existing
provider_message_id or compute a deterministic hash of provider_message_id +
participant + startedAt) so retries are idempotent; then wrap the
self.http_client.post call in a small bounded retry with exponential backoff
(e.g., 3 attempts with increasing delays) and only retry on transient errors
(timeouts/5xx/network), calling resp.raise_for_status() and logging the final
failure with the segmentId and error; refer to the segment construction,
segmentId, self.http_client.post, MACRO_API_URL, channel_id and
INTERNAL_CALL_SECRET to locate where to change.

In `@infra/stacks/cloud-storage-service/index.ts`:
- Around line 280-282: The transcription agent Docker setup is missing the
INTERNAL_CALL_SECRET env var used by the agent (it reads
os.environ.get("INTERNAL_CALL_SECRET", "")), causing empty x-macro-internal-call
headers; update agents/transcription/Dockerfile to accept and pass the same
secret from AWS Secrets Manager into the container (either as a build-arg and/or
an ENV) so the running container has INTERNAL_CALL_SECRET set, and ensure the
deployment that creates the agent image injects the secret value retrieved via
the same INTERNAL_CALL_SECRET/aws.secretsmanager.getSecretVersionOutput(...)
used for CloudStorageService; this will align the agent with CloudStorageService
which expects the header.

In `@rust/cloud-storage/call/src/domain/service.rs`:
- Around line 45-49: The compare in validate_internal_call currently uses ==
which short-circuits and creates a timing side channel; update
validate_internal_call to perform a constant-time byte comparison by using
subtle::ConstantTimeEq (e.g., compare secret.as_bytes() and token.as_bytes()
with ct_eq(...).into()) inside the existing as_deref().is_some_and closure, and
ensure the call crate's Cargo.toml includes the subtle dependency if not already
present.

In `@rust/cloud-storage/document_storage_service/src/config.rs`:
- Around line 75-78: Startup should fail fast if a live transcription agent is
configured but the internal secret is missing: add a validation during
initialization that checks if LivekitTranscriptionAgentName is set (non-empty)
and InternalCallSecret is missing or empty, and abort startup with a clear
error; update the code path that currently calls dispatch_transcription_agent()
to rely on this check rather than allowing dispatch when
validate_internal_call() would later reject requests, referencing the symbols
InternalCallSecret, validate_internal_call(), dispatch_transcription_agent(),
and LivekitTranscriptionAgentName so the check is added during config/bootstrap
initialization.

In `@rust/cloud-storage/document_storage_service/src/main.rs`:
- Around line 363-375: The code unconditionally mounts InternalCallRouterState
despite InternalCallSecret being optional, so processes can start without a
secret and then fail-close on /call/.../transcript; modify the wiring in main.rs
to require the secret before creating/mounting InternalCallRouterState: after
constructing internal_call_secret (config::InternalCallSecret::new()), if it is
None do not call CallServiceImpl::with_internal_call_secret and do not create
InternalCallRouterState (InternalCallRouterState::new) nor add
call_internal_state to the router; alternatively, if you prefer failing fast,
return an error and abort startup when internal_call_secret is None so
CallServiceImpl and InternalCallRouterState are only created when a valid secret
exists (use the existing symbols InternalCallSecret, CallServiceImpl,
with_internal_call_secret, and InternalCallRouterState to locate the relevant
code).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0adb9445-b431-49cf-877b-3c7b1606aea3

📥 Commits

Reviewing files that changed from the base of the PR and between af7a17f and 2a0394e.

📒 Files selected for processing (12)
  • agents/transcription/requirements.txt
  • agents/transcription/transcriber.py
  • infra/stacks/cloud-storage-service/Pulumi.dev.yaml
  • infra/stacks/cloud-storage-service/Pulumi.prod.yaml
  • infra/stacks/cloud-storage-service/index.ts
  • rust/cloud-storage/call/src/domain/ports.rs
  • rust/cloud-storage/call/src/domain/service.rs
  • rust/cloud-storage/call/src/inbound/axum_router.rs
  • rust/cloud-storage/document_storage_service/src/api/context.rs
  • rust/cloud-storage/document_storage_service/src/api/mod.rs
  • rust/cloud-storage/document_storage_service/src/config.rs
  • rust/cloud-storage/document_storage_service/src/main.rs

@whutchinson98 whutchinson98 requested a review from a team as a code owner April 6, 2026 18:30
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@js/app/packages/service-clients/service-storage/openapi.json`:
- Line 738: The OpenAPI spec documents the required x-macro-internal-call header
in prose but doesn't declare it as an auth requirement, causing client
generation/runtime mismatch; fix by adding a components.securitySchemes entry
for an apiKey in the header named "x-macro-internal-call" and reference that
scheme in the operation's security array (or global security) for the
transcript-segments endpoint that mentions "Receives transcript segments…" so
generated clients and docs treat the header as required; alternatively, add a
header parameter entry for "x-macro-internal-call" on the operation if you
prefer parameter-based requirements, but ensure the components.securitySchemes
name and the operation security reference match exactly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fde3c44f-4c58-4782-8d36-055d86e4b656

📥 Commits

Reviewing files that changed from the base of the PR and between 2a0394e and cc7f51a.

⛔ Files ignored due to path filters (1)
  • js/app/packages/service-clients/service-storage/generated/zod.ts is excluded by !**/generated/**
📒 Files selected for processing (5)
  • .env-local.enc
  • .env-localdev.enc
  • agents/transcription/transcriber.py
  • js/app/packages/service-clients/service-storage/openapi.json
  • rust/cloud-storage/document_storage_service/src/main.rs

@whutchinson98 whutchinson98 merged commit 0a2cc97 into main Apr 6, 2026
38 of 39 checks passed
@whutchinson98 whutchinson98 deleted the hutch/feat-calls-move-transcript-to-agent branch April 6, 2026 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant