Skip to content

feat: new Message type that supports encryption#5

Merged
seun-ja merged 3 commits into
mainfrom
4-feature-prompt-encryption
Apr 7, 2026
Merged

feat: new Message type that supports encryption#5
seun-ja merged 3 commits into
mainfrom
4-feature-prompt-encryption

Conversation

@seun-ja

@seun-ja seun-ja commented Apr 7, 2026

Copy link
Copy Markdown
Owner

Fixes #4

Signed-off-by: Aminu Oluwaseun Joshua <seun.aminujoshua@gmail.com>

Copilot AI 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.

Pull request overview

Adds support for sending prompts as JWT-wrapped messages, introducing a new Message variant and JWT decoding logic to transform incoming RPC messages into provider-ready prompts.

Changes:

  • Extend Message with an Encrypted variant and convert Message -> String via TryFrom (including JWT decoding).
  • Add JWT decoding module, new error variants/status mapping, and unit tests covering JWT decode + message conversion.
  • Update agent RPC handler to use the new fallible prompt conversion and adjust error-string assertions.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/message.rs Adds Message::Encrypted and TryFrom<Message> for String to decode JWT-based prompts
src/jwt.rs Introduces JWT claims struct and decode_jwt helper using HS256 validation
src/error.rs Adds JWT-related error variants and centralizes HTTP status mapping for ApiError
src/agent.rs Switches from Message stringification to fallible prompt extraction (try_into)
src/lib.rs Documents feature/usage and adds internal jwt module
src/tests/jwt.rs Adds unit tests for JWT decoding and Message::Encrypted behavior
src/tests/mod.rs Registers the new JWT test module
src/tests/unit_error.rs Updates expected ApiError string formatting
Cargo.toml Adds jsonwebtoken dependency and serial_test dev-dependency
Cargo.lock Locks new dependency graph for JWT + serial test

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib.rs Outdated
Comment thread Cargo.toml
Comment thread src/message.rs
Comment thread src/message.rs Outdated
Comment thread src/jwt.rs Outdated
Comment thread src/tests/jwt.rs
Comment thread src/jwt.rs Outdated
Comment thread src/message.rs Outdated
Comment thread src/lib.rs Outdated
Signed-off-by: Aminu Oluwaseun Joshua <seun.aminujoshua@gmail.com>

Copilot AI 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.

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/message.rs
Comment on lines +21 to 27
impl TryFrom<Message> for String {
type Error = ApiError;

fn try_from(message: Message) -> Result<Self, Self::Error> {
match message {
Message::Text(text) => Ok(text),
Message::Struct(value) => {

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

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

This PR removes Display formatting for Message and replaces it with TryFrom<Message> for String, which is a breaking change for downstream users relying on message.to_string()/formatting. Consider keeping a Display impl for non-fallible variants (or a non-breaking as_prompt()/try_into_prompt() API) and limiting fallible conversion to an explicit method/trait for the encrypted/JWT case.

Copilot uses AI. Check for mistakes.
Comment thread src/message.rs
Comment on lines +34 to +39
Message::Encrypted(token) => {
let hmac_secret =
std::env::var("JWT_SECRET").map_err(|_| Error::NoJWTSecretFound)?;

let prompt = decode_jwt(&token, &hmac_secret).map(|c| c.prompt)?;

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

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

Reading JWT_SECRET directly from the process environment here introduces a hidden global dependency and makes prompt extraction harder to configure/test (and requires serializing tests that touch env). Prefer passing the secret via configuration (e.g., AgentServerBuilder) or including it in the request context so conversion doesn’t depend on global env state.

Copilot uses AI. Check for mistakes.
Comment thread src/jwt.rs Outdated
Comment thread src/tests/jwt.rs Outdated
Comment on lines +6 to +18
/// Guard to ensure JWT_SECRET is removed from the environment after test, even on panic.
struct JwtSecretGuard;

impl JwtSecretGuard {
fn set(secret: &str) -> Self {
unsafe { env::set_var("JWT_SECRET", secret) };
JwtSecretGuard
}
}

impl Drop for JwtSecretGuard {
fn drop(&mut self) {
unsafe { env::remove_var("JWT_SECRET") };

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

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

JwtSecretGuard always removes JWT_SECRET on drop rather than restoring any pre-existing value. If JWT_SECRET is set in the developer/CI environment, these tests will permanently unset it for the remainder of the test process. Consider capturing the previous value in the guard and restoring it in Drop.

Suggested change
/// Guard to ensure JWT_SECRET is removed from the environment after test, even on panic.
struct JwtSecretGuard;
impl JwtSecretGuard {
fn set(secret: &str) -> Self {
unsafe { env::set_var("JWT_SECRET", secret) };
JwtSecretGuard
}
}
impl Drop for JwtSecretGuard {
fn drop(&mut self) {
unsafe { env::remove_var("JWT_SECRET") };
/// Guard to ensure JWT_SECRET is restored after test, even on panic.
struct JwtSecretGuard {
previous: Option<String>,
}
impl JwtSecretGuard {
fn set(secret: &str) -> Self {
let previous = env::var("JWT_SECRET").ok();
unsafe { env::set_var("JWT_SECRET", secret) };
JwtSecretGuard { previous }
}
}
impl Drop for JwtSecretGuard {
fn drop(&mut self) {
match &self.previous {
Some(previous) => unsafe { env::set_var("JWT_SECRET", previous) },
None => unsafe { env::remove_var("JWT_SECRET") },
}

Copilot uses AI. Check for mistakes.
Comment thread src/tests/jwt.rs
Comment on lines +86 to +95
#[test]
#[serial_test::serial]
fn message_encrypted_variant_no_secret() {
unsafe { env::remove_var("JWT_SECRET") };
let msg = Message::Encrypted("sometoken".to_string());
let result: Result<String, ApiError> = msg.try_into();
assert_eq!(
result.unwrap_err().to_string(),
ApiError::from(Error::NoJWTSecretFound).to_string()
);

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

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

This test mutates global process environment (remove_var("JWT_SECRET")) without restoring the previous value afterward. Even with serial_test, this can make subsequent tests depend on ordering and can break local runs where JWT_SECRET is set. Prefer using a guard that restores the prior value at the end of the test.

Copilot uses AI. Check for mistakes.
Signed-off-by: Aminu Oluwaseun Joshua <seun.aminujoshua@gmail.com>
@seun-ja
seun-ja merged commit b041893 into main Apr 7, 2026
1 check passed
@seun-ja
seun-ja deleted the 4-feature-prompt-encryption branch April 7, 2026 11:44
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.

Feature: Prompt encryption

2 participants