act-cli 0.7.1__tar.gz → 0.7.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {act_cli-0.7.1 → act_cli-0.7.2}/Cargo.lock +2 -2
- {act_cli-0.7.1 → act_cli-0.7.2}/Cargo.toml +1 -1
- {act_cli-0.7.1 → act_cli-0.7.2}/PKG-INFO +1 -1
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/main.rs +146 -44
- {act_cli-0.7.1 → act_cli-0.7.2}/README.md +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/Cargo.toml +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/README.md +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/build.rs +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/config.rs +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/format.rs +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/http.rs +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/resolve.rs +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/rmcp_bridge.rs +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/bindings/mod.rs +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/effective.rs +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/fs_matcher.rs +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/fs_policy.rs +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/http_client.rs +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/http_policy.rs +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/mod.rs +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/network.rs +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/sessions.rs +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/wit/deps/act-core/act-core.wit +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/wit/deps/act-tools/act-tools.wit +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/wit/deps.lock +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/wit/deps.toml +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/wit/world.wit +0 -0
- {act_cli-0.7.1 → act_cli-0.7.2}/pyproject.toml +0 -0
|
@@ -4,7 +4,7 @@ version = 4
|
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
6
6
|
name = "act-build"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.2"
|
|
8
8
|
dependencies = [
|
|
9
9
|
"act-types",
|
|
10
10
|
"anyhow",
|
|
@@ -25,7 +25,7 @@ dependencies = [
|
|
|
25
25
|
|
|
26
26
|
[[package]]
|
|
27
27
|
name = "act-cli"
|
|
28
|
-
version = "0.7.
|
|
28
|
+
version = "0.7.2"
|
|
29
29
|
dependencies = [
|
|
30
30
|
"act-types",
|
|
31
31
|
"anyhow",
|
|
@@ -104,6 +104,16 @@ enum Command {
|
|
|
104
104
|
#[arg(long, default_value = "{}")]
|
|
105
105
|
args: String,
|
|
106
106
|
|
|
107
|
+
/// Session args as a JSON object. When set, the host opens a
|
|
108
|
+
/// session before the call (`open-session(args, metadata)`),
|
|
109
|
+
/// injects the returned id as `std:session-id` metadata for
|
|
110
|
+
/// the tool call, and closes the session before exit. Use
|
|
111
|
+
/// this when the component requires a session — bridges,
|
|
112
|
+
/// stateful components — and you want the whole open/call/
|
|
113
|
+
/// close cycle in one process.
|
|
114
|
+
#[arg(long)]
|
|
115
|
+
session_args: Option<String>,
|
|
116
|
+
|
|
107
117
|
#[command(flatten)]
|
|
108
118
|
opts: CommonOpts,
|
|
109
119
|
},
|
|
@@ -217,8 +227,9 @@ async fn main() -> Result<()> {
|
|
|
217
227
|
component,
|
|
218
228
|
tool,
|
|
219
229
|
args,
|
|
230
|
+
session_args,
|
|
220
231
|
opts,
|
|
221
|
-
} => cmd_call(component, tool, args, opts).await,
|
|
232
|
+
} => cmd_call(component, tool, args, session_args, opts).await,
|
|
222
233
|
Command::Info {
|
|
223
234
|
component,
|
|
224
235
|
tools,
|
|
@@ -419,6 +430,7 @@ async fn cmd_call(
|
|
|
419
430
|
component: ComponentRef,
|
|
420
431
|
tool: String,
|
|
421
432
|
args: String,
|
|
433
|
+
session_args: Option<String>,
|
|
422
434
|
opts: CommonOpts,
|
|
423
435
|
) -> Result<()> {
|
|
424
436
|
let pc = prepare_component(&component, &opts).await?;
|
|
@@ -427,64 +439,154 @@ async fn cmd_call(
|
|
|
427
439
|
serde_json::from_str(&args).context("invalid --args JSON")?;
|
|
428
440
|
let cbor_args = cbor::json_to_cbor(&arguments).context("encoding args as CBOR")?;
|
|
429
441
|
|
|
442
|
+
// If --session-args is set, open a session before the call and
|
|
443
|
+
// close it on the way out. session-id is injected into the call's
|
|
444
|
+
// metadata under `std:session-id`.
|
|
445
|
+
let session_id = match session_args {
|
|
446
|
+
Some(json) => {
|
|
447
|
+
if !pc.has_sessions {
|
|
448
|
+
anyhow::bail!(
|
|
449
|
+
"--session-args was set, but the component does not export \
|
|
450
|
+
act:sessions/session-provider"
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
Some(open_session_for_call(&pc, &json).await?)
|
|
454
|
+
}
|
|
455
|
+
None => None,
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
let mut metadata = pc.metadata.clone();
|
|
459
|
+
if let Some(ref id) = session_id {
|
|
460
|
+
metadata.insert(
|
|
461
|
+
act_types::constants::META_SESSION_ID,
|
|
462
|
+
serde_json::Value::String(id.clone()),
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
430
466
|
let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
|
|
431
467
|
let request = runtime::ComponentRequest::CallTool {
|
|
432
468
|
name: tool,
|
|
433
469
|
arguments: cbor_args,
|
|
434
|
-
metadata:
|
|
470
|
+
metadata: metadata.into(),
|
|
435
471
|
reply: reply_tx,
|
|
436
472
|
};
|
|
437
473
|
|
|
438
|
-
pc.handle
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
474
|
+
let send_result = pc.handle.send(request).await;
|
|
475
|
+
let call_result = match send_result {
|
|
476
|
+
Err(_) => Err(anyhow::anyhow!("component actor unavailable")),
|
|
477
|
+
Ok(()) => match reply_rx.await {
|
|
478
|
+
Err(_) => Err(anyhow::anyhow!("component actor dropped reply")),
|
|
479
|
+
Ok(r) => Ok(r),
|
|
480
|
+
},
|
|
481
|
+
};
|
|
442
482
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
483
|
+
// Best-effort close before returning the call result, so the
|
|
484
|
+
// session is closed even if the call errored.
|
|
485
|
+
if let Some(id) = session_id {
|
|
486
|
+
close_session_best_effort(&pc, id).await;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
let result = call_result?.map_err(|e| match e {
|
|
490
|
+
runtime::ComponentError::Tool(te) => {
|
|
491
|
+
let ls = act_types::types::LocalizedString::from(&te.message);
|
|
492
|
+
anyhow::anyhow!("{}: {}", te.kind, ls.any_text())
|
|
493
|
+
}
|
|
494
|
+
runtime::ComponentError::Internal(e) => e,
|
|
495
|
+
})?;
|
|
496
|
+
|
|
497
|
+
for event in &result.events {
|
|
498
|
+
match event {
|
|
499
|
+
runtime::exports::act::tools::tool_provider::ToolEvent::Content(part) => {
|
|
500
|
+
let mime = part.mime_type.as_deref().unwrap_or("application/cbor");
|
|
501
|
+
if mime.starts_with("text/")
|
|
502
|
+
|| mime == "application/json"
|
|
503
|
+
|| mime == "application/xml"
|
|
504
|
+
{
|
|
505
|
+
let text = String::from_utf8_lossy(&part.data);
|
|
506
|
+
println!("{text}");
|
|
507
|
+
} else if mime == "application/cbor" {
|
|
508
|
+
let json_val = act_types::cbor::cbor_to_json(&part.data).unwrap_or_else(|_| {
|
|
509
|
+
serde_json::Value::String(format!(
|
|
510
|
+
"[binary: {}, {} bytes]",
|
|
511
|
+
mime,
|
|
512
|
+
part.data.len()
|
|
513
|
+
))
|
|
514
|
+
});
|
|
515
|
+
match json_val {
|
|
516
|
+
serde_json::Value::String(s) => println!("{s}"),
|
|
517
|
+
other => println!("{}", serde_json::to_string_pretty(&other)?),
|
|
478
518
|
}
|
|
519
|
+
} else if std::io::IsTerminal::is_terminal(&std::io::stdout()) {
|
|
520
|
+
println!("[binary: {}, {} bytes]", mime, part.data.len());
|
|
521
|
+
} else {
|
|
522
|
+
use std::io::Write;
|
|
523
|
+
std::io::stdout().write_all(&part.data)?;
|
|
479
524
|
}
|
|
480
525
|
}
|
|
481
|
-
|
|
526
|
+
runtime::exports::act::tools::tool_provider::ToolEvent::Error(err) => {
|
|
527
|
+
let ls = act_types::types::LocalizedString::from(&err.message);
|
|
528
|
+
anyhow::bail!("{}: {}", err.kind, ls.any_text());
|
|
529
|
+
}
|
|
482
530
|
}
|
|
531
|
+
}
|
|
532
|
+
Ok(())
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/// Marshal a JSON object of session args into the WIT shape and call
|
|
536
|
+
/// `open-session` against the prepared component. Returns the
|
|
537
|
+
/// allocated session-id.
|
|
538
|
+
async fn open_session_for_call(pc: &PreparedComponent, json: &str) -> Result<String> {
|
|
539
|
+
let value: serde_json::Value =
|
|
540
|
+
serde_json::from_str(json).context("invalid --session-args JSON")?;
|
|
541
|
+
let serde_json::Value::Object(args_obj) = value else {
|
|
542
|
+
anyhow::bail!("--session-args must be a JSON object");
|
|
543
|
+
};
|
|
544
|
+
let mut wit_args: Vec<(String, Vec<u8>)> = Vec::with_capacity(args_obj.len());
|
|
545
|
+
for (key, value) in args_obj {
|
|
546
|
+
let bytes =
|
|
547
|
+
act_types::cbor::json_to_cbor(&value).context("encoding session arg as CBOR")?;
|
|
548
|
+
wit_args.push((key, bytes));
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
|
|
552
|
+
pc.handle
|
|
553
|
+
.send(runtime::ComponentRequest::OpenSession {
|
|
554
|
+
args: wit_args,
|
|
555
|
+
metadata: pc.metadata.clone().into(),
|
|
556
|
+
reply: reply_tx,
|
|
557
|
+
})
|
|
558
|
+
.await
|
|
559
|
+
.map_err(|_| anyhow::anyhow!("component actor unavailable"))?;
|
|
560
|
+
|
|
561
|
+
match reply_rx.await? {
|
|
562
|
+
Ok(session) => Ok(session.id),
|
|
483
563
|
Err(runtime::ComponentError::Tool(te)) => {
|
|
484
564
|
let ls = act_types::types::LocalizedString::from(&te.message);
|
|
485
|
-
anyhow::bail!("{}: {}", te.kind, ls.any_text());
|
|
565
|
+
anyhow::bail!("open-session failed: {}: {}", te.kind, ls.any_text());
|
|
486
566
|
}
|
|
487
|
-
Err(runtime::ComponentError::Internal(e)) => Err(e),
|
|
567
|
+
Err(runtime::ComponentError::Internal(e)) => Err(e.context("open-session failed")),
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/// Best-effort close. Logs failures at debug; never propagates errors,
|
|
572
|
+
/// because the call result is what the user asked for and a failed
|
|
573
|
+
/// close should not surface as the command's exit code.
|
|
574
|
+
async fn close_session_best_effort(pc: &PreparedComponent, session_id: String) {
|
|
575
|
+
let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
|
|
576
|
+
if pc
|
|
577
|
+
.handle
|
|
578
|
+
.send(runtime::ComponentRequest::CloseSession {
|
|
579
|
+
session_id: session_id.clone(),
|
|
580
|
+
reply: reply_tx,
|
|
581
|
+
})
|
|
582
|
+
.await
|
|
583
|
+
.is_err()
|
|
584
|
+
{
|
|
585
|
+
tracing::debug!(%session_id, "actor unavailable for close-session");
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if let Err(e) = reply_rx.await {
|
|
589
|
+
tracing::debug!(%session_id, error = %e, "close-session reply dropped");
|
|
488
590
|
}
|
|
489
591
|
}
|
|
490
592
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|