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.
Files changed (28) hide show
  1. {act_cli-0.7.1 → act_cli-0.7.2}/Cargo.lock +2 -2
  2. {act_cli-0.7.1 → act_cli-0.7.2}/Cargo.toml +1 -1
  3. {act_cli-0.7.1 → act_cli-0.7.2}/PKG-INFO +1 -1
  4. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/main.rs +146 -44
  5. {act_cli-0.7.1 → act_cli-0.7.2}/README.md +0 -0
  6. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/Cargo.toml +0 -0
  7. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/README.md +0 -0
  8. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/build.rs +0 -0
  9. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/config.rs +0 -0
  10. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/format.rs +0 -0
  11. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/http.rs +0 -0
  12. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/resolve.rs +0 -0
  13. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/rmcp_bridge.rs +0 -0
  14. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/bindings/mod.rs +0 -0
  15. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/effective.rs +0 -0
  16. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/fs_matcher.rs +0 -0
  17. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/fs_policy.rs +0 -0
  18. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/http_client.rs +0 -0
  19. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/http_policy.rs +0 -0
  20. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/mod.rs +0 -0
  21. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/network.rs +0 -0
  22. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/src/runtime/sessions.rs +0 -0
  23. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/wit/deps/act-core/act-core.wit +0 -0
  24. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/wit/deps/act-tools/act-tools.wit +0 -0
  25. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/wit/deps.lock +0 -0
  26. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/wit/deps.toml +0 -0
  27. {act_cli-0.7.1 → act_cli-0.7.2}/act-cli/wit/world.wit +0 -0
  28. {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.1"
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.1"
28
+ version = "0.7.2"
29
29
  dependencies = [
30
30
  "act-types",
31
31
  "anyhow",
@@ -3,7 +3,7 @@ members = ["act-cli"]
3
3
  resolver = "3"
4
4
 
5
5
  [workspace.package]
6
- version = "0.7.1"
6
+ version = "0.7.2"
7
7
  edition = "2024"
8
8
  license = "MIT OR Apache-2.0"
9
9
  repository = "https://github.com/actcore/act-cli"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: act-cli
3
- Version: 0.7.1
3
+ Version: 0.7.2
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -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: pc.metadata.clone().into(),
470
+ metadata: metadata.into(),
435
471
  reply: reply_tx,
436
472
  };
437
473
 
438
- pc.handle
439
- .send(request)
440
- .await
441
- .map_err(|_| anyhow::anyhow!("component actor unavailable"))?;
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
- match reply_rx.await? {
444
- Ok(result) => {
445
- for event in &result.events {
446
- match event {
447
- runtime::exports::act::tools::tool_provider::ToolEvent::Content(part) => {
448
- let mime = part.mime_type.as_deref().unwrap_or("application/cbor");
449
- if mime.starts_with("text/")
450
- || mime == "application/json"
451
- || mime == "application/xml"
452
- {
453
- let text = String::from_utf8_lossy(&part.data);
454
- println!("{text}");
455
- } else if mime == "application/cbor" {
456
- let json_val = act_types::cbor::cbor_to_json(&part.data)
457
- .unwrap_or_else(|_| {
458
- serde_json::Value::String(format!(
459
- "[binary: {}, {} bytes]",
460
- mime,
461
- part.data.len()
462
- ))
463
- });
464
- match json_val {
465
- serde_json::Value::String(s) => println!("{s}"),
466
- other => println!("{}", serde_json::to_string_pretty(&other)?),
467
- }
468
- } else if std::io::IsTerminal::is_terminal(&std::io::stdout()) {
469
- println!("[binary: {}, {} bytes]", mime, part.data.len());
470
- } else {
471
- use std::io::Write;
472
- std::io::stdout().write_all(&part.data)?;
473
- }
474
- }
475
- runtime::exports::act::tools::tool_provider::ToolEvent::Error(err) => {
476
- let ls = act_types::types::LocalizedString::from(&err.message);
477
- anyhow::bail!("{}: {}", err.kind, ls.any_text());
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
- Ok(())
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