act-cli 0.7.0__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.0 → act_cli-0.7.2}/Cargo.lock +2 -2
  2. {act_cli-0.7.0 → act_cli-0.7.2}/Cargo.toml +1 -1
  3. {act_cli-0.7.0 → act_cli-0.7.2}/PKG-INFO +1 -1
  4. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/src/main.rs +146 -142
  5. {act_cli-0.7.0 → act_cli-0.7.2}/README.md +0 -0
  6. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/Cargo.toml +0 -0
  7. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/README.md +0 -0
  8. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/build.rs +0 -0
  9. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/src/config.rs +0 -0
  10. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/src/format.rs +0 -0
  11. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/src/http.rs +0 -0
  12. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/src/resolve.rs +0 -0
  13. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/src/rmcp_bridge.rs +0 -0
  14. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/src/runtime/bindings/mod.rs +0 -0
  15. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/src/runtime/effective.rs +0 -0
  16. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/src/runtime/fs_matcher.rs +0 -0
  17. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/src/runtime/fs_policy.rs +0 -0
  18. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/src/runtime/http_client.rs +0 -0
  19. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/src/runtime/http_policy.rs +0 -0
  20. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/src/runtime/mod.rs +0 -0
  21. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/src/runtime/network.rs +0 -0
  22. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/src/runtime/sessions.rs +0 -0
  23. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/wit/deps/act-core/act-core.wit +0 -0
  24. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/wit/deps/act-tools/act-tools.wit +0 -0
  25. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/wit/deps.lock +0 -0
  26. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/wit/deps.toml +0 -0
  27. {act_cli-0.7.0 → act_cli-0.7.2}/act-cli/wit/world.wit +0 -0
  28. {act_cli-0.7.0 → 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.0"
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.0"
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.0"
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.0
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
  },
@@ -146,7 +156,12 @@ enum Command {
146
156
  #[arg(short = 'O', conflicts_with = "output")]
147
157
  output_from_ref: bool,
148
158
  },
149
- /// Manage component sessions (`act:sessions/session-provider`).
159
+ /// Inspect `act:sessions/session-provider` — currently only
160
+ /// `open-args-schema`, since opening or closing a session from a
161
+ /// one-shot CLI invocation cannot keep the underlying wasm state
162
+ /// alive. For real session work, use `act run --http` or
163
+ /// `act run --mcp` (the host process holds the wasm instance and
164
+ /// the session lives as long as the host).
150
165
  #[command(subcommand)]
151
166
  Session(SessionCommand),
152
167
  }
@@ -159,22 +174,6 @@ enum SessionCommand {
159
174
  #[command(flatten)]
160
175
  opts: CommonOpts,
161
176
  },
162
- /// Open a new session, print the session record (id + metadata) as JSON.
163
- Open {
164
- component: ComponentRef,
165
- /// JSON object with session-args.
166
- #[arg(long, default_value = "{}")]
167
- args: String,
168
- #[command(flatten)]
169
- opts: CommonOpts,
170
- },
171
- /// Close a session by id.
172
- Close {
173
- component: ComponentRef,
174
- session_id: String,
175
- #[command(flatten)]
176
- opts: CommonOpts,
177
- },
178
177
  }
179
178
 
180
179
  #[tokio::main]
@@ -198,9 +197,7 @@ async fn main() -> Result<()> {
198
197
  }
199
198
  Command::Skill { .. } | Command::Pull { .. } => None,
200
199
  Command::Session(sub) => match sub {
201
- SessionCommand::OpenArgsSchema { opts, .. }
202
- | SessionCommand::Open { opts, .. }
203
- | SessionCommand::Close { opts, .. } => opts.config.as_deref(),
200
+ SessionCommand::OpenArgsSchema { opts, .. } => opts.config.as_deref(),
204
201
  },
205
202
  };
206
203
  let log_level = config::load_config(config_path)
@@ -230,8 +227,9 @@ async fn main() -> Result<()> {
230
227
  component,
231
228
  tool,
232
229
  args,
230
+ session_args,
233
231
  opts,
234
- } => cmd_call(component, tool, args, opts).await,
232
+ } => cmd_call(component, tool, args, session_args, opts).await,
235
233
  Command::Info {
236
234
  component,
237
235
  tools,
@@ -248,16 +246,6 @@ async fn main() -> Result<()> {
248
246
  SessionCommand::OpenArgsSchema { component, opts } => {
249
247
  cmd_session_open_args_schema(component, opts).await
250
248
  }
251
- SessionCommand::Open {
252
- component,
253
- args,
254
- opts,
255
- } => cmd_session_open(component, args, opts).await,
256
- SessionCommand::Close {
257
- component,
258
- session_id,
259
- opts,
260
- } => cmd_session_close(component, session_id, opts).await,
261
249
  },
262
250
  }
263
251
  }
@@ -442,6 +430,7 @@ async fn cmd_call(
442
430
  component: ComponentRef,
443
431
  tool: String,
444
432
  args: String,
433
+ session_args: Option<String>,
445
434
  opts: CommonOpts,
446
435
  ) -> Result<()> {
447
436
  let pc = prepare_component(&component, &opts).await?;
@@ -450,109 +439,113 @@ async fn cmd_call(
450
439
  serde_json::from_str(&args).context("invalid --args JSON")?;
451
440
  let cbor_args = cbor::json_to_cbor(&arguments).context("encoding args as CBOR")?;
452
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
+
453
466
  let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
454
467
  let request = runtime::ComponentRequest::CallTool {
455
468
  name: tool,
456
469
  arguments: cbor_args,
457
- metadata: pc.metadata.clone().into(),
470
+ metadata: metadata.into(),
458
471
  reply: reply_tx,
459
472
  };
460
473
 
461
- pc.handle
462
- .send(request)
463
- .await
464
- .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
+ };
465
482
 
466
- match reply_rx.await? {
467
- Ok(result) => {
468
- for event in &result.events {
469
- match event {
470
- runtime::exports::act::tools::tool_provider::ToolEvent::Content(part) => {
471
- let mime = part.mime_type.as_deref().unwrap_or("application/cbor");
472
- if mime.starts_with("text/")
473
- || mime == "application/json"
474
- || mime == "application/xml"
475
- {
476
- let text = String::from_utf8_lossy(&part.data);
477
- println!("{text}");
478
- } else if mime == "application/cbor" {
479
- let json_val = act_types::cbor::cbor_to_json(&part.data)
480
- .unwrap_or_else(|_| {
481
- serde_json::Value::String(format!(
482
- "[binary: {}, {} bytes]",
483
- mime,
484
- part.data.len()
485
- ))
486
- });
487
- match json_val {
488
- serde_json::Value::String(s) => println!("{s}"),
489
- other => println!("{}", serde_json::to_string_pretty(&other)?),
490
- }
491
- } else if std::io::IsTerminal::is_terminal(&std::io::stdout()) {
492
- println!("[binary: {}, {} bytes]", mime, part.data.len());
493
- } else {
494
- use std::io::Write;
495
- std::io::stdout().write_all(&part.data)?;
496
- }
497
- }
498
- runtime::exports::act::tools::tool_provider::ToolEvent::Error(err) => {
499
- let ls = act_types::types::LocalizedString::from(&err.message);
500
- 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)?),
501
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)?;
502
524
  }
503
525
  }
504
- Ok(())
505
- }
506
- Err(runtime::ComponentError::Tool(te)) => {
507
- let ls = act_types::types::LocalizedString::from(&te.message);
508
- anyhow::bail!("{}: {}", te.kind, ls.any_text());
509
- }
510
- Err(runtime::ComponentError::Internal(e)) => Err(e),
511
- }
512
- }
513
-
514
- // ── Session subcommands ────────────────────────────────────────────────────
515
-
516
- async fn cmd_session_open_args_schema(component: ComponentRef, opts: CommonOpts) -> Result<()> {
517
- let pc = prepare_component(&component, &opts).await?;
518
- let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
519
- pc.handle
520
- .send(runtime::ComponentRequest::GetOpenSessionArgsSchema {
521
- metadata: pc.metadata.clone().into(),
522
- reply: reply_tx,
523
- })
524
- .await
525
- .map_err(|_| anyhow::anyhow!("component actor unavailable"))?;
526
- match reply_rx.await? {
527
- Ok(schema) => {
528
- // Pretty-print if it's valid JSON; otherwise print as-is.
529
- match serde_json::from_str::<serde_json::Value>(&schema) {
530
- Ok(v) => println!("{}", serde_json::to_string_pretty(&v)?),
531
- Err(_) => println!("{schema}"),
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());
532
529
  }
533
- Ok(())
534
530
  }
535
- Err(runtime::ComponentError::Tool(te)) => {
536
- let ls = act_types::types::LocalizedString::from(&te.message);
537
- anyhow::bail!("{}: {}", te.kind, ls.any_text());
538
- }
539
- Err(runtime::ComponentError::Internal(e)) => Err(e),
540
531
  }
532
+ Ok(())
541
533
  }
542
534
 
543
- async fn cmd_session_open(component: ComponentRef, args: String, opts: CommonOpts) -> Result<()> {
544
- let pc = prepare_component(&component, &opts).await?;
545
-
546
- // Args are a JSON object; convert to metadata-shaped (key, cbor) pairs.
547
- let args_value: serde_json::Value =
548
- serde_json::from_str(&args).context("invalid --args JSON")?;
549
- let serde_json::Value::Object(args_obj) = args_value else {
550
- anyhow::bail!("--args must be a JSON object");
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");
551
543
  };
552
544
  let mut wit_args: Vec<(String, Vec<u8>)> = Vec::with_capacity(args_obj.len());
553
545
  for (key, value) in args_obj {
554
- let cbor = act_types::cbor::json_to_cbor(&value).context("encoding arg as CBOR")?;
555
- wit_args.push((key, cbor));
546
+ let bytes =
547
+ act_types::cbor::json_to_cbor(&value).context("encoding session arg as CBOR")?;
548
+ wit_args.push((key, bytes));
556
549
  }
557
550
 
558
551
  let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
@@ -566,47 +559,58 @@ async fn cmd_session_open(component: ComponentRef, args: String, opts: CommonOpt
566
559
  .map_err(|_| anyhow::anyhow!("component actor unavailable"))?;
567
560
 
568
561
  match reply_rx.await? {
569
- Ok(session) => {
570
- // Re-emit metadata as JSON object for human consumption.
571
- let metadata_json: serde_json::Map<String, serde_json::Value> = session
572
- .metadata
573
- .iter()
574
- .filter_map(|(k, v)| {
575
- let val = act_types::cbor::cbor_to_json(v).ok()?;
576
- Some((k.clone(), val))
577
- })
578
- .collect();
579
- let out = serde_json::json!({
580
- "id": session.id,
581
- "metadata": metadata_json,
582
- });
583
- println!("{}", serde_json::to_string_pretty(&out)?);
584
- Ok(())
585
- }
562
+ Ok(session) => Ok(session.id),
586
563
  Err(runtime::ComponentError::Tool(te)) => {
587
564
  let ls = act_types::types::LocalizedString::from(&te.message);
588
- anyhow::bail!("{}: {}", te.kind, ls.any_text());
565
+ anyhow::bail!("open-session failed: {}: {}", te.kind, ls.any_text());
589
566
  }
590
- Err(runtime::ComponentError::Internal(e)) => Err(e),
567
+ Err(runtime::ComponentError::Internal(e)) => Err(e.context("open-session failed")),
591
568
  }
592
569
  }
593
570
 
594
- async fn cmd_session_close(
595
- component: ComponentRef,
596
- session_id: String,
597
- opts: CommonOpts,
598
- ) -> Result<()> {
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");
590
+ }
591
+ }
592
+
593
+ // ── Session subcommands ────────────────────────────────────────────────────
594
+
595
+ async fn cmd_session_open_args_schema(component: ComponentRef, opts: CommonOpts) -> Result<()> {
599
596
  let pc = prepare_component(&component, &opts).await?;
600
597
  let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
601
598
  pc.handle
602
- .send(runtime::ComponentRequest::CloseSession {
603
- session_id,
599
+ .send(runtime::ComponentRequest::GetOpenSessionArgsSchema {
600
+ metadata: pc.metadata.clone().into(),
604
601
  reply: reply_tx,
605
602
  })
606
603
  .await
607
604
  .map_err(|_| anyhow::anyhow!("component actor unavailable"))?;
608
605
  match reply_rx.await? {
609
- Ok(()) => Ok(()),
606
+ Ok(schema) => {
607
+ // Pretty-print if it's valid JSON; otherwise print as-is.
608
+ match serde_json::from_str::<serde_json::Value>(&schema) {
609
+ Ok(v) => println!("{}", serde_json::to_string_pretty(&v)?),
610
+ Err(_) => println!("{schema}"),
611
+ }
612
+ Ok(())
613
+ }
610
614
  Err(runtime::ComponentError::Tool(te)) => {
611
615
  let ls = act_types::types::LocalizedString::from(&te.message);
612
616
  anyhow::bail!("{}: {}", te.kind, ls.any_text());
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