1.0 mvp
This commit is contained in:
@@ -65,7 +65,7 @@ pub async fn mnote_call(
|
||||
Extension(context): Extension<RequestContext>,
|
||||
Json(input): Json<ToolCallInput>,
|
||||
) -> Result<(StatusCode, HeaderMap, Json<Value>), WebError> {
|
||||
ensure_authenticated(&context)?;
|
||||
let context = authenticated_tool_context(&context, &input)?;
|
||||
let trace_id = input
|
||||
.effective_trace_id(&context.trace.trace_id)
|
||||
.to_string();
|
||||
@@ -373,6 +373,71 @@ fn ensure_authenticated(context: &RequestContext) -> Result<(), WebError> {
|
||||
.with_header(HEADER_HERMES_TOOL_OWNER, "mnote-web-hermes-tools"))
|
||||
}
|
||||
|
||||
fn authenticated_tool_context(
|
||||
context: &RequestContext,
|
||||
input: &ToolCallInput,
|
||||
) -> Result<RequestContext, WebError> {
|
||||
if ensure_authenticated(context).is_ok() {
|
||||
return Ok(context.clone());
|
||||
}
|
||||
let actor_id = input
|
||||
.actor_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty() && *value != "anonymous")
|
||||
.ok_or_else(|| {
|
||||
WebError::new(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"mnote_tool_unauthorized",
|
||||
"mnote Hermes tool 需要登录后访问",
|
||||
)
|
||||
.with_context(context)
|
||||
.with_header(HEADER_MNOTE_WEB_OWNER, "mnote-web")
|
||||
.with_header(HEADER_HERMES_TOOL_OWNER, "mnote-web-hermes-tools")
|
||||
})?;
|
||||
let has_run_identity = input
|
||||
.session_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.is_some()
|
||||
&& input
|
||||
.run_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.is_some()
|
||||
&& input
|
||||
.tool_call_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.is_some()
|
||||
&& input
|
||||
.trace_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.is_some();
|
||||
if !has_run_identity {
|
||||
return Err(WebError::new(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"mnote_tool_unauthorized",
|
||||
"mnote Hermes tool 委托调用缺少 sessionId/runId/toolCallId/traceId",
|
||||
)
|
||||
.with_context(context)
|
||||
.with_header(HEADER_MNOTE_WEB_OWNER, "mnote-web")
|
||||
.with_header(HEADER_HERMES_TOOL_OWNER, "mnote-web-hermes-tools"));
|
||||
}
|
||||
let mut next = context.clone();
|
||||
next.auth.actor_id = actor_id.to_string();
|
||||
next.auth.actor_type = "user".into();
|
||||
if next.auth.session_id.is_none() {
|
||||
next.auth.session_id = input.session_id.clone();
|
||||
}
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
fn ensure_workspace_context(
|
||||
context: &RequestContext,
|
||||
input_workspace_id: Option<&str>,
|
||||
@@ -493,6 +558,11 @@ mod tests {
|
||||
let tools = payload["manifest"]["tools"].as_array().expect("tools");
|
||||
assert!(tools.iter().any(|tool| tool["name"] == "mnote.page.get"));
|
||||
assert!(tools.iter().any(|tool| tool["name"] == "mnote.page.save"));
|
||||
let page_save = tools
|
||||
.iter()
|
||||
.find(|tool| tool["name"] == "mnote.page.save")
|
||||
.expect("page save tool");
|
||||
assert_eq!(page_save["status"], "available");
|
||||
assert_eq!(
|
||||
payload["manifest"]["schemaVersion"],
|
||||
"mnote.hermes_tool_manifest.v1"
|
||||
@@ -517,6 +587,41 @@ mod tests {
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn hermes_tools_page_get_accepts_delegated_actor_from_hermes_payload() {
|
||||
let response = app()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/hermes/tools/mnote/call")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(
|
||||
json!({
|
||||
"toolName": "mnote.page.get",
|
||||
"workspaceId": "ws_demo",
|
||||
"documentId": "doc_1",
|
||||
"actorId": "user_hermes",
|
||||
"sessionId": "sess_1",
|
||||
"runId": "run_1",
|
||||
"toolCallId": "call_1",
|
||||
"traceId": "trace_1",
|
||||
"capabilityScope": ["page.read"]
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
.expect("request"),
|
||||
)
|
||||
.await
|
||||
.expect("response");
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let body = to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.expect("body");
|
||||
let payload: Value = serde_json::from_slice(&body).expect("json");
|
||||
assert_eq!(payload["audit"]["actorId"], "user_hermes");
|
||||
assert_eq!(payload["result"]["title"], "服务端页面");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn hermes_tools_write_tools_require_auth() {
|
||||
let response = app()
|
||||
|
||||
Reference in New Issue
Block a user