feat: add lesson export and import functionality

- Implemented `LessonExport` and `LessonImport` actions in the action module.
- Added corresponding command parsing for lesson export and import.
- Created functions to handle lesson export and import in the memory module.
- Updated state management to reflect changes after lesson operations.
- Introduced deferred operations for handling asynchronous tasks in the event loop.
- Enhanced the tool execution context to include graduated checks for file operations.
- Added OAuth support with PKCE for secure authorization flows.
- Implemented a loopback server for handling OAuth redirects.
- Refactored various modules to improve code organization and maintainability.
This commit is contained in:
asepharyana
2026-07-11 18:23:01 +07:00
parent cc03bd79b6
commit c1ad206a00
49 changed files with 1088 additions and 12 deletions
View File
View File
+2
View File
@@ -1,2 +1,4 @@
#[expect(dead_code)]
pub mod tools;
#[expect(dead_code)]
pub mod turn;
+24
View File
@@ -0,0 +1,24 @@
use crate::tool::{ToolCtx, all_tools};
use serde_json::Value;
use anyhow::Result;
pub fn execute_tool_call(name: &str, args: &Value, ctx: &ToolCtx) -> Result<String> {
let tools = all_tools();
for tool in &tools {
if tool.name() == name {
return tool.run(ctx, args);
}
}
Err(anyhow::anyhow!("tool not found: {}", name))
}
#[expect(dead_code)]
pub fn execute_deferred_tool(name: &str, args: &Value, ctx: &ToolCtx) -> Result<String> {
let tools = all_tools();
for tool in &tools {
if tool.name() == name {
return tool.run(ctx, args);
}
}
Err(anyhow::anyhow!("deferred tool not found: {}", name))
}
+70
View File
@@ -0,0 +1,70 @@
use crate::app::state::rest::AppStateRest;
use crate::app::runtime::actions::{Action, apply_action};
pub fn advance_turn(state: &mut AppStateRest) {
if state.session_runtime.is_none() {
return;
}
let rt = state.session_runtime.as_mut().unwrap();
if rt.messages.is_empty() {
return;
}
let api_key = state.settings.api_key.clone();
let model = state.settings.model.clone();
let msgs = rt.messages.clone();
let pending = state.pending_api_response.clone();
if let Some(key) = api_key {
if !key.is_empty() {
std::thread::spawn(move || {
let client = crate::service::openrouter::OpenRouterClient::new(key, model);
match client.chat(&msgs) {
Ok(response) => {
if let Ok(mut guard) = pending.lock() {
*guard = Some(response);
}
}
Err(e) => {
if let Ok(mut guard) = pending.lock() {
*guard = Some(format!("Error: {}", e));
}
}
}
});
}
}
}
#[expect(dead_code)]
pub fn process_tools(state: &mut AppStateRest) {
let tool_calls: Vec<_> = {
let rt = match state.session_runtime.as_ref() {
Some(r) => r,
None => return,
};
rt.pending_tool_queue.clone()
};
if tool_calls.is_empty() {
return;
}
for tool_call in &tool_calls {
let _result = format!("processing tool: {}", tool_call.tool_name);
}
}
#[expect(dead_code)]
pub fn finish_tool_round(state: &mut AppStateRest) {
let tool_count = {
let rt = match state.session_runtime.as_ref() {
Some(r) => r,
None => return,
};
rt.tool_call_results.len()
};
if tool_count > 0 {
let note = format!("{} tool calls completed", tool_count);
apply_action(state, Action::SystemNote {
kind: "tool_round".to_string(),
message: note,
});
}
}