commit 6bbc20471a937a92517a9928f64804a7c9f45089
parent 093189b019af8628cdf5e2044d78a21245a634db
Author: William Casarin <jb55@jb55.com>
Date: Thu, 1 May 2025 18:42:45 -0700
dave: include anonymous user identifier in api call
- don't include users pubkey
This could be used to associate requests with real users,
rendering the anonymized user_id pointless
TODO: Implement a new tool call that lets dave ask for your pubkey
Fixes: #834
Fixes: #836
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
4 files changed, 40 insertions(+), 11 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -3281,6 +3281,7 @@ dependencies = [
"rand 0.9.0",
"serde",
"serde_json",
+ "sha2",
"tokio",
"tracing",
]
diff --git a/crates/notedeck/src/user_account.rs b/crates/notedeck/src/user_account.rs
@@ -1,4 +1,4 @@
-use enostr::Keypair;
+use enostr::{Keypair, KeypairUnowned};
use tokenator::{ParseError, TokenParser, TokenSerializable};
use crate::wallet::ZapWallet;
@@ -13,6 +13,13 @@ impl UserAccount {
Self { key, wallet: None }
}
+ pub fn keypair(&self) -> KeypairUnowned {
+ KeypairUnowned {
+ pubkey: &self.key.pubkey,
+ secret_key: self.key.secret_key.as_ref(),
+ }
+ }
+
pub fn with_wallet(mut self, wallet: ZapWallet) -> Self {
self.wallet = Some(wallet);
self
diff --git a/crates/notedeck_dave/Cargo.toml b/crates/notedeck_dave/Cargo.toml
@@ -6,6 +6,7 @@ version.workspace = true
[dependencies]
async-openai = "0.28.0"
egui = { workspace = true }
+sha2 = { workspace = true }
notedeck = { workspace = true }
notedeck_ui = { workspace = true }
eframe = { workspace = true }
diff --git a/crates/notedeck_dave/src/lib.rs b/crates/notedeck_dave/src/lib.rs
@@ -5,6 +5,7 @@ use async_openai::{
};
use chrono::{Duration, Local};
use egui_wgpu::RenderState;
+use enostr::KeypairUnowned;
use futures::StreamExt;
use nostrdb::Transaction;
use notedeck::{AppAction, AppContext};
@@ -37,20 +38,31 @@ pub struct Dave {
/// A 3d representation of dave.
avatar: Option<DaveAvatar>,
input: String,
- pubkey: String,
tools: Arc<HashMap<String, Tool>>,
client: async_openai::Client<OpenAIConfig>,
incoming_tokens: Option<Receiver<DaveApiResponse>>,
model_config: ModelConfig,
}
+/// Calculate an anonymous user_id from a keypair
+fn calculate_user_id(keypair: KeypairUnowned) -> String {
+ use sha2::{Digest, Sha256};
+ // pubkeys have degraded privacy, don't do that
+ let key_input = keypair
+ .secret_key
+ .map(|sk| sk.as_secret_bytes())
+ .unwrap_or(keypair.pubkey.bytes());
+ let hex_key = hex::encode(key_input);
+ let input = format!("{hex_key}notedeck_dave_user_id");
+ hex::encode(Sha256::digest(input))
+}
+
impl Dave {
pub fn avatar_mut(&mut self) -> Option<&mut DaveAvatar> {
self.avatar.as_mut()
}
fn system_prompt() -> Message {
- let pubkey = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245".to_string();
let now = Local::now();
let yesterday = now - Duration::hours(24);
let date = now.format("%Y-%m-%d %H:%M:%S");
@@ -65,8 +77,6 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
- Yesterday (-24hrs) was {yesterday_timestamp}. You can use this in combination with `since` queries for pulling notes for summarizing notes the user might have missed while they were away.
-- The current users pubkey is {pubkey}
-
# Response Guidelines
- You *MUST* call the present_notes tool with a list of comma-separated note id references when referring to notes so that the UI can display them. Do *NOT* include note id references in the text response, but you *SHOULD* use ^1, ^2, etc to reference note indices passed to present_notes.
@@ -84,19 +94,18 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
let input = "".to_string();
let avatar = render_state.map(DaveAvatar::new);
let mut tools: HashMap<String, Tool> = HashMap::new();
- let pubkey = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245".to_string();
for tool in tools::dave_tools() {
tools.insert(tool.name().to_string(), tool);
}
+
Dave {
client,
- pubkey: pubkey.clone(),
avatar,
incoming_tokens: None,
tools: Arc::new(tools),
input,
model_config,
- chat: vec![Self::system_prompt()],
+ chat: vec![],
}
}
@@ -170,7 +179,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
}
fn handle_new_chat(&mut self) {
- self.chat = vec![Self::system_prompt()];
+ self.chat = vec![];
self.input.clear();
}
@@ -190,7 +199,13 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
.collect()
};
tracing::debug!("sending messages, latest: {:?}", messages.last().unwrap());
- let pubkey = self.pubkey.clone();
+
+ let user_id = app_ctx
+ .accounts
+ .get_selected_account()
+ .map(|sa| calculate_user_id(sa.keypair()))
+ .unwrap_or_else(|| "unknown_user".to_string());
+
let ctx = ctx.clone();
let client = self.client.clone();
let tools = self.tools.clone();
@@ -207,7 +222,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
stream: Some(true),
messages,
tools: Some(tools::dave_tools().iter().map(|t| t.to_api()).collect()),
- user: Some(pubkey),
+ user: Some(user_id),
..Default::default()
})
.await
@@ -321,6 +336,11 @@ impl notedeck::App for Dave {
*/
let mut app_action: Option<AppAction> = None;
+ // always insert system prompt if we have no context
+ if self.chat.is_empty() {
+ self.chat.push(Dave::system_prompt());
+ }
+
//update_dave(self, ctx, ui.ctx());
let should_send = self.process_events(ctx);
if let Some(action) = self.ui(ctx, ui).action {