act-cli 0.7.5__tar.gz → 0.8.0__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.5 → act_cli-0.8.0}/Cargo.lock +86 -5
- {act_cli-0.7.5 → act_cli-0.8.0}/Cargo.toml +2 -2
- {act_cli-0.7.5 → act_cli-0.8.0}/PKG-INFO +1 -1
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/Cargo.toml +1 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/src/http.rs +50 -8
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/src/main.rs +198 -29
- act_cli-0.8.0/act-cli/src/resolve.rs +42 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/src/rmcp_bridge.rs +134 -6
- act_cli-0.8.0/crates/act-store/Cargo.toml +30 -0
- act_cli-0.8.0/crates/act-store/README.md +125 -0
- act_cli-0.8.0/crates/act-store/src/fetch.rs +605 -0
- act_cli-0.8.0/crates/act-store/src/index.rs +302 -0
- act_cli-0.8.0/crates/act-store/src/layout.rs +111 -0
- act_cli-0.8.0/crates/act-store/src/lib.rs +61 -0
- act_cli-0.8.0/crates/act-store/src/lock.rs +75 -0
- act_cli-0.8.0/crates/act-store/src/provenance.rs +172 -0
- act_cli-0.8.0/crates/act-store/src/reference.rs +181 -0
- act_cli-0.8.0/crates/act-store/src/referrer.rs +64 -0
- act_cli-0.8.0/crates/act-store/src/store.rs +626 -0
- act_cli-0.7.5/act-cli/src/resolve.rs +0 -418
- {act_cli-0.7.5 → act_cli-0.8.0}/README.md +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/README.md +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/build.rs +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/src/config.rs +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/src/format.rs +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/src/runtime/bindings/mod.rs +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/src/runtime/effective.rs +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/src/runtime/fs_matcher.rs +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/src/runtime/fs_policy.rs +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/src/runtime/http_client.rs +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/src/runtime/http_policy.rs +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/src/runtime/mod.rs +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/src/runtime/network.rs +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/src/runtime/sessions.rs +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/src/runtime/sockets_policy.rs +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/wit/deps/act-core/act-core.wit +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/wit/deps/act-tools/act-tools.wit +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/wit/deps.lock +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/wit/deps.toml +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/act-cli/wit/world.wit +0 -0
- {act_cli-0.7.5 → act_cli-0.8.0}/pyproject.toml +0 -0
|
@@ -4,7 +4,7 @@ version = 4
|
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
6
6
|
name = "act-build"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.8.0"
|
|
8
8
|
dependencies = [
|
|
9
9
|
"act-types",
|
|
10
10
|
"anyhow",
|
|
@@ -18,7 +18,7 @@ dependencies = [
|
|
|
18
18
|
"json-patch",
|
|
19
19
|
"minijinja",
|
|
20
20
|
"oci-client",
|
|
21
|
-
"reqwest",
|
|
21
|
+
"reqwest 0.13.3",
|
|
22
22
|
"serde",
|
|
23
23
|
"serde_json",
|
|
24
24
|
"sha2 0.11.0",
|
|
@@ -35,8 +35,9 @@ dependencies = [
|
|
|
35
35
|
|
|
36
36
|
[[package]]
|
|
37
37
|
name = "act-cli"
|
|
38
|
-
version = "0.
|
|
38
|
+
version = "0.8.0"
|
|
39
39
|
dependencies = [
|
|
40
|
+
"act-store",
|
|
40
41
|
"act-types",
|
|
41
42
|
"anyhow",
|
|
42
43
|
"axum",
|
|
@@ -57,7 +58,7 @@ dependencies = [
|
|
|
57
58
|
"owo-colors",
|
|
58
59
|
"path-clean",
|
|
59
60
|
"regex",
|
|
60
|
-
"reqwest",
|
|
61
|
+
"reqwest 0.13.3",
|
|
61
62
|
"rmcp",
|
|
62
63
|
"serde",
|
|
63
64
|
"serde_json",
|
|
@@ -78,6 +79,27 @@ dependencies = [
|
|
|
78
79
|
"wasmtime-wasi-http",
|
|
79
80
|
]
|
|
80
81
|
|
|
82
|
+
[[package]]
|
|
83
|
+
name = "act-store"
|
|
84
|
+
version = "0.1.0"
|
|
85
|
+
dependencies = [
|
|
86
|
+
"chrono",
|
|
87
|
+
"dirs",
|
|
88
|
+
"fs2",
|
|
89
|
+
"oci-client",
|
|
90
|
+
"oci-spec",
|
|
91
|
+
"regex",
|
|
92
|
+
"reqwest 0.12.28",
|
|
93
|
+
"serde",
|
|
94
|
+
"serde_json",
|
|
95
|
+
"sha2 0.11.0",
|
|
96
|
+
"tempfile",
|
|
97
|
+
"thiserror 2.0.18",
|
|
98
|
+
"tokio",
|
|
99
|
+
"tracing",
|
|
100
|
+
"url",
|
|
101
|
+
]
|
|
102
|
+
|
|
81
103
|
[[package]]
|
|
82
104
|
name = "act-types"
|
|
83
105
|
version = "0.7.1"
|
|
@@ -1203,6 +1225,16 @@ dependencies = [
|
|
|
1203
1225
|
"windows-sys 0.59.0",
|
|
1204
1226
|
]
|
|
1205
1227
|
|
|
1228
|
+
[[package]]
|
|
1229
|
+
name = "fs2"
|
|
1230
|
+
version = "0.4.3"
|
|
1231
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1232
|
+
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
|
1233
|
+
dependencies = [
|
|
1234
|
+
"libc",
|
|
1235
|
+
"winapi",
|
|
1236
|
+
]
|
|
1237
|
+
|
|
1206
1238
|
[[package]]
|
|
1207
1239
|
name = "fs_extra"
|
|
1208
1240
|
version = "1.3.0"
|
|
@@ -1577,6 +1609,7 @@ dependencies = [
|
|
|
1577
1609
|
"tokio",
|
|
1578
1610
|
"tokio-rustls",
|
|
1579
1611
|
"tower-service",
|
|
1612
|
+
"webpki-roots",
|
|
1580
1613
|
]
|
|
1581
1614
|
|
|
1582
1615
|
[[package]]
|
|
@@ -2219,7 +2252,7 @@ dependencies = [
|
|
|
2219
2252
|
"oci-spec",
|
|
2220
2253
|
"olpc-cjson",
|
|
2221
2254
|
"regex",
|
|
2222
|
-
"reqwest",
|
|
2255
|
+
"reqwest 0.13.3",
|
|
2223
2256
|
"serde",
|
|
2224
2257
|
"serde_json",
|
|
2225
2258
|
"sha2 0.11.0",
|
|
@@ -2720,6 +2753,44 @@ version = "0.8.10"
|
|
|
2720
2753
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2721
2754
|
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
|
2722
2755
|
|
|
2756
|
+
[[package]]
|
|
2757
|
+
name = "reqwest"
|
|
2758
|
+
version = "0.12.28"
|
|
2759
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2760
|
+
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
|
|
2761
|
+
dependencies = [
|
|
2762
|
+
"base64",
|
|
2763
|
+
"bytes",
|
|
2764
|
+
"futures-core",
|
|
2765
|
+
"http",
|
|
2766
|
+
"http-body",
|
|
2767
|
+
"http-body-util",
|
|
2768
|
+
"hyper",
|
|
2769
|
+
"hyper-rustls",
|
|
2770
|
+
"hyper-util",
|
|
2771
|
+
"js-sys",
|
|
2772
|
+
"log",
|
|
2773
|
+
"percent-encoding",
|
|
2774
|
+
"pin-project-lite",
|
|
2775
|
+
"quinn",
|
|
2776
|
+
"rustls",
|
|
2777
|
+
"rustls-pki-types",
|
|
2778
|
+
"serde",
|
|
2779
|
+
"serde_json",
|
|
2780
|
+
"serde_urlencoded",
|
|
2781
|
+
"sync_wrapper",
|
|
2782
|
+
"tokio",
|
|
2783
|
+
"tokio-rustls",
|
|
2784
|
+
"tower",
|
|
2785
|
+
"tower-http",
|
|
2786
|
+
"tower-service",
|
|
2787
|
+
"url",
|
|
2788
|
+
"wasm-bindgen",
|
|
2789
|
+
"wasm-bindgen-futures",
|
|
2790
|
+
"web-sys",
|
|
2791
|
+
"webpki-roots",
|
|
2792
|
+
]
|
|
2793
|
+
|
|
2723
2794
|
[[package]]
|
|
2724
2795
|
name = "reqwest"
|
|
2725
2796
|
version = "0.13.3"
|
|
@@ -2873,6 +2944,7 @@ checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
|
|
|
2873
2944
|
dependencies = [
|
|
2874
2945
|
"aws-lc-rs",
|
|
2875
2946
|
"once_cell",
|
|
2947
|
+
"ring",
|
|
2876
2948
|
"rustls-pki-types",
|
|
2877
2949
|
"rustls-webpki",
|
|
2878
2950
|
"subtle",
|
|
@@ -4454,6 +4526,15 @@ dependencies = [
|
|
|
4454
4526
|
"rustls-pki-types",
|
|
4455
4527
|
]
|
|
4456
4528
|
|
|
4529
|
+
[[package]]
|
|
4530
|
+
name = "webpki-roots"
|
|
4531
|
+
version = "1.0.7"
|
|
4532
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4533
|
+
checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d"
|
|
4534
|
+
dependencies = [
|
|
4535
|
+
"rustls-pki-types",
|
|
4536
|
+
]
|
|
4537
|
+
|
|
4457
4538
|
[[package]]
|
|
4458
4539
|
name = "winapi"
|
|
4459
4540
|
version = "0.3.9"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
[workspace]
|
|
2
|
-
members = ["act-cli"]
|
|
2
|
+
members = ["act-cli", "crates/act-store"]
|
|
3
3
|
resolver = "3"
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
|
-
version = "0.
|
|
6
|
+
version = "0.8.0"
|
|
7
7
|
edition = "2024"
|
|
8
8
|
license = "MIT OR Apache-2.0"
|
|
9
9
|
repository = "https://github.com/actcore/act-cli"
|
|
@@ -24,6 +24,10 @@ pub struct AppState {
|
|
|
24
24
|
pub info: act_types::ComponentInfo,
|
|
25
25
|
pub component: runtime::ComponentHandle,
|
|
26
26
|
pub metadata: Metadata,
|
|
27
|
+
/// When `Some`, a single default session was pre-opened (session-of-1,
|
|
28
|
+
/// ACT-SESSIONS §3): `/sessions*` routes are unregistered and this id is
|
|
29
|
+
/// forced into every call's `std:session-id` metadata.
|
|
30
|
+
pub default_session_id: Option<String>,
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
// ── Conversion helpers ──
|
|
@@ -84,6 +88,14 @@ fn internal_error_response(message: &str) -> axum::response::Response {
|
|
|
84
88
|
.into_response()
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
/// Force `std:session-id` to the default when in session-of-1 mode, overriding
|
|
92
|
+
/// any body-supplied value (ACT-SESSIONS §3 "session-of-1").
|
|
93
|
+
fn apply_default_session(meta: &mut Metadata, default: &Option<String>) {
|
|
94
|
+
if let Some(id) = default {
|
|
95
|
+
meta.insert(META_SESSION_ID, serde_json::Value::String(id.clone()));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
87
99
|
/// Format an SseEvent as an axum SSE Event.
|
|
88
100
|
fn sse_event_to_axum(event: runtime::SseEvent) -> Option<Result<Event, std::convert::Infallible>> {
|
|
89
101
|
match event {
|
|
@@ -151,6 +163,7 @@ async fn list_tools_inner(
|
|
|
151
163
|
if let Some(value) = metadata {
|
|
152
164
|
meta.extend(runtime::Metadata::from(value));
|
|
153
165
|
}
|
|
166
|
+
apply_default_session(&mut meta, &state.default_session_id);
|
|
154
167
|
|
|
155
168
|
let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
|
|
156
169
|
let request = runtime::ComponentRequest::ListTools {
|
|
@@ -361,6 +374,7 @@ async fn tool_call_dispatcher(
|
|
|
361
374
|
if let Some(value) = body.metadata {
|
|
362
375
|
metadata.extend(Metadata::from(value));
|
|
363
376
|
}
|
|
377
|
+
apply_default_session(&mut metadata, &state.default_session_id);
|
|
364
378
|
|
|
365
379
|
let metadata_wit: Vec<(String, Vec<u8>)> = metadata.into();
|
|
366
380
|
|
|
@@ -522,16 +536,25 @@ async fn protocol_version_layer(request: Request, next: Next) -> axum::response:
|
|
|
522
536
|
}
|
|
523
537
|
|
|
524
538
|
pub fn create_router(state: Arc<AppState>) -> Router {
|
|
525
|
-
Router::new()
|
|
539
|
+
let mut router = Router::new()
|
|
526
540
|
.route("/info", get(get_info))
|
|
527
541
|
.route("/tools", axum::routing::any(tools_dispatcher))
|
|
528
|
-
.route("/tools/{name}", axum::routing::any(tool_call_dispatcher))
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
542
|
+
.route("/tools/{name}", axum::routing::any(tool_call_dispatcher));
|
|
543
|
+
|
|
544
|
+
// Session-of-1 hides the session machinery (ACT-SESSIONS §3): when a
|
|
545
|
+
// default session is pre-opened, the component looks stateless and the
|
|
546
|
+
// lifecycle endpoints are absent (404).
|
|
547
|
+
if state.default_session_id.is_none() {
|
|
548
|
+
router = router
|
|
549
|
+
.route(
|
|
550
|
+
"/sessions/open-args-schema",
|
|
551
|
+
axum::routing::any(session_open_args_schema_dispatcher),
|
|
552
|
+
)
|
|
553
|
+
.route("/sessions", axum::routing::post(session_open))
|
|
554
|
+
.route("/sessions/{id}", axum::routing::delete(session_close));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
router
|
|
535
558
|
.layer(middleware::from_fn(protocol_version_layer))
|
|
536
559
|
.with_state(state)
|
|
537
560
|
}
|
|
@@ -567,4 +590,23 @@ mod tests {
|
|
|
567
590
|
fn query_method_is_valid() {
|
|
568
591
|
assert_eq!(query_method().as_str(), "QUERY");
|
|
569
592
|
}
|
|
593
|
+
|
|
594
|
+
#[test]
|
|
595
|
+
fn apply_default_session_overrides_and_skips() {
|
|
596
|
+
// Some(id): inject, overriding any existing value.
|
|
597
|
+
let mut meta = Metadata::from(serde_json::json!({"std:session-id": "client"}));
|
|
598
|
+
apply_default_session(&mut meta, &Some("sid_default".to_string()));
|
|
599
|
+
assert_eq!(
|
|
600
|
+
meta.get_as::<String>(META_SESSION_ID).as_deref(),
|
|
601
|
+
Some("sid_default")
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
// None: leave metadata untouched.
|
|
605
|
+
let mut meta2 = Metadata::from(serde_json::json!({"std:session-id": "client"}));
|
|
606
|
+
apply_default_session(&mut meta2, &None);
|
|
607
|
+
assert_eq!(
|
|
608
|
+
meta2.get_as::<String>(META_SESSION_ID).as_deref(),
|
|
609
|
+
Some("client")
|
|
610
|
+
);
|
|
611
|
+
}
|
|
570
612
|
}
|
|
@@ -101,6 +101,15 @@ enum Command {
|
|
|
101
101
|
#[arg(short, long)]
|
|
102
102
|
listen: Option<String>,
|
|
103
103
|
|
|
104
|
+
/// Pre-open a single session at startup from this JSON object and
|
|
105
|
+
/// run as session-of-1: every call uses the pre-opened session, the
|
|
106
|
+
/// session machinery is hidden from clients (no virtual
|
|
107
|
+
/// open_session/close_session tools, no /sessions endpoints), and any
|
|
108
|
+
/// client-supplied std:session-id is ignored. Requires a component
|
|
109
|
+
/// that exports act:sessions/session-provider.
|
|
110
|
+
#[arg(long)]
|
|
111
|
+
session_args: Option<String>,
|
|
112
|
+
|
|
104
113
|
#[command(flatten)]
|
|
105
114
|
opts: CommonOpts,
|
|
106
115
|
},
|
|
@@ -176,6 +185,27 @@ enum Command {
|
|
|
176
185
|
/// the session lives as long as the host).
|
|
177
186
|
#[command(subcommand)]
|
|
178
187
|
Session(SessionCommand),
|
|
188
|
+
/// Manage the local component store (list, update, gc).
|
|
189
|
+
#[command(subcommand)]
|
|
190
|
+
Store(StoreCommand),
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#[derive(clap::Subcommand)]
|
|
194
|
+
enum StoreCommand {
|
|
195
|
+
/// List components in the local store.
|
|
196
|
+
List {
|
|
197
|
+
/// Output format.
|
|
198
|
+
#[arg(long, value_enum, default_value_t = OutputFormat::Text)]
|
|
199
|
+
format: OutputFormat,
|
|
200
|
+
},
|
|
201
|
+
/// Re-resolve stored components and re-pull any whose digest moved.
|
|
202
|
+
Update {
|
|
203
|
+
/// A single ref to update (omit to update all stored components).
|
|
204
|
+
#[arg(name = "ref")]
|
|
205
|
+
reference: Option<ComponentRef>,
|
|
206
|
+
},
|
|
207
|
+
/// Delete store blobs no longer referenced by any component.
|
|
208
|
+
Gc,
|
|
179
209
|
}
|
|
180
210
|
|
|
181
211
|
#[derive(clap::Subcommand)]
|
|
@@ -211,6 +241,7 @@ async fn main() -> Result<()> {
|
|
|
211
241
|
Command::Session(sub) => match sub {
|
|
212
242
|
SessionCommand::OpenArgsSchema { opts, .. } => opts.config.as_deref(),
|
|
213
243
|
},
|
|
244
|
+
Command::Store(_) => None,
|
|
214
245
|
};
|
|
215
246
|
let log_level = config::load_config(config_path)
|
|
216
247
|
.ok()
|
|
@@ -233,8 +264,9 @@ async fn main() -> Result<()> {
|
|
|
233
264
|
mcp,
|
|
234
265
|
http,
|
|
235
266
|
listen,
|
|
267
|
+
session_args,
|
|
236
268
|
opts,
|
|
237
|
-
} => cmd_run(component, mcp, http, listen, opts).await,
|
|
269
|
+
} => cmd_run(component, mcp, http, listen, session_args, opts).await,
|
|
238
270
|
Command::Call {
|
|
239
271
|
component,
|
|
240
272
|
tool,
|
|
@@ -259,6 +291,11 @@ async fn main() -> Result<()> {
|
|
|
259
291
|
cmd_session_open_args_schema(component, opts).await
|
|
260
292
|
}
|
|
261
293
|
},
|
|
294
|
+
Command::Store(sub) => match sub {
|
|
295
|
+
StoreCommand::List { format } => cmd_list(format).await,
|
|
296
|
+
StoreCommand::Update { reference } => cmd_update(reference).await,
|
|
297
|
+
StoreCommand::Gc => cmd_gc().await,
|
|
298
|
+
},
|
|
262
299
|
}
|
|
263
300
|
}
|
|
264
301
|
|
|
@@ -404,11 +441,34 @@ fn parse_listen_addr(s: &str) -> Result<SocketAddr> {
|
|
|
404
441
|
anyhow::bail!("invalid listen address: {s} (expected [host]:port or port number)")
|
|
405
442
|
}
|
|
406
443
|
|
|
444
|
+
/// If `session_args` is set, open a single default session against the
|
|
445
|
+
/// prepared component and return its id (session-of-1, ACT-SESSIONS §3). The
|
|
446
|
+
/// session is closed automatically when the component actor shuts down
|
|
447
|
+
/// (`runtime` closes every tracked session on deinit).
|
|
448
|
+
async fn maybe_open_default_session(
|
|
449
|
+
pc: &PreparedComponent,
|
|
450
|
+
session_args: &Option<String>,
|
|
451
|
+
) -> Result<Option<String>> {
|
|
452
|
+
match session_args {
|
|
453
|
+
Some(json) => {
|
|
454
|
+
if !pc.has_sessions {
|
|
455
|
+
anyhow::bail!(
|
|
456
|
+
"--session-args was set, but the component does not export \
|
|
457
|
+
act:sessions/session-provider"
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
Ok(Some(open_session_for_call(pc, json).await?))
|
|
461
|
+
}
|
|
462
|
+
None => Ok(None),
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
407
466
|
async fn cmd_run(
|
|
408
467
|
component: ComponentRef,
|
|
409
468
|
mcp: bool,
|
|
410
469
|
http: bool,
|
|
411
470
|
listen: Option<String>,
|
|
471
|
+
session_args: Option<String>,
|
|
412
472
|
opts: CommonOpts,
|
|
413
473
|
) -> Result<()> {
|
|
414
474
|
// Transport matrix:
|
|
@@ -422,7 +482,16 @@ async fn cmd_run(
|
|
|
422
482
|
None => "[::1]:3000".parse().unwrap(),
|
|
423
483
|
};
|
|
424
484
|
let pc = prepare_component(&component, &opts).await?;
|
|
425
|
-
|
|
485
|
+
let default_session_id = maybe_open_default_session(&pc, &session_args).await?;
|
|
486
|
+
return rmcp_bridge::run_http(
|
|
487
|
+
addr,
|
|
488
|
+
pc.info,
|
|
489
|
+
pc.handle,
|
|
490
|
+
pc.metadata,
|
|
491
|
+
pc.has_sessions,
|
|
492
|
+
default_session_id,
|
|
493
|
+
)
|
|
494
|
+
.await;
|
|
426
495
|
}
|
|
427
496
|
|
|
428
497
|
if mcp {
|
|
@@ -430,7 +499,15 @@ async fn cmd_run(
|
|
|
430
499
|
anyhow::bail!("--listen requires --http (MCP stdio has no listen address)");
|
|
431
500
|
}
|
|
432
501
|
let pc = prepare_component(&component, &opts).await?;
|
|
433
|
-
|
|
502
|
+
let default_session_id = maybe_open_default_session(&pc, &session_args).await?;
|
|
503
|
+
return rmcp_bridge::run_stdio(
|
|
504
|
+
pc.info,
|
|
505
|
+
pc.handle,
|
|
506
|
+
pc.metadata,
|
|
507
|
+
pc.has_sessions,
|
|
508
|
+
default_session_id,
|
|
509
|
+
)
|
|
510
|
+
.await;
|
|
434
511
|
}
|
|
435
512
|
|
|
436
513
|
if http || listen.is_some() {
|
|
@@ -440,11 +517,13 @@ async fn cmd_run(
|
|
|
440
517
|
};
|
|
441
518
|
|
|
442
519
|
let pc = prepare_component(&component, &opts).await?;
|
|
520
|
+
let default_session_id = maybe_open_default_session(&pc, &session_args).await?;
|
|
443
521
|
|
|
444
522
|
let state = Arc::new(http::AppState {
|
|
445
523
|
info: pc.info,
|
|
446
524
|
component: pc.handle,
|
|
447
525
|
metadata: pc.metadata,
|
|
526
|
+
default_session_id,
|
|
448
527
|
});
|
|
449
528
|
|
|
450
529
|
tracing::info!(%addr, "ACT host listening");
|
|
@@ -752,42 +831,132 @@ async fn cmd_pull(
|
|
|
752
831
|
output: Option<PathBuf>,
|
|
753
832
|
output_from_ref: bool,
|
|
754
833
|
) -> Result<()> {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
let
|
|
834
|
+
let store = resolve::open_store()?;
|
|
835
|
+
let reference_str = reference.to_string();
|
|
836
|
+
let stored = act_store::pull(&store, &reference_str)
|
|
837
|
+
.await
|
|
838
|
+
.with_context(|| format!("pulling {reference_str}"))?;
|
|
839
|
+
|
|
840
|
+
// Path to the stored wasm blob (read-through hit; no re-pull).
|
|
841
|
+
let stored_path = act_store::ensure(&store, &reference_str).await?;
|
|
842
|
+
|
|
843
|
+
let export = output.or_else(|| {
|
|
844
|
+
output_from_ref.then(|| {
|
|
845
|
+
let ref_str = reference.to_string();
|
|
846
|
+
let base = ref_str
|
|
847
|
+
.rsplit('/')
|
|
848
|
+
.next()
|
|
849
|
+
.unwrap_or(&ref_str)
|
|
850
|
+
.split(':')
|
|
851
|
+
.next()
|
|
852
|
+
.unwrap_or(&ref_str);
|
|
853
|
+
let filename = if base.ends_with(".wasm") {
|
|
854
|
+
base.to_string()
|
|
855
|
+
} else {
|
|
856
|
+
format!("{base}.wasm")
|
|
857
|
+
};
|
|
858
|
+
PathBuf::from(filename)
|
|
859
|
+
})
|
|
860
|
+
});
|
|
758
861
|
|
|
759
|
-
if let Some(out) =
|
|
760
|
-
tokio::fs::copy(&
|
|
761
|
-
.await
|
|
762
|
-
.with_context(|| format!("copying to {}", out.display()))?;
|
|
763
|
-
println!("{}", out.display());
|
|
764
|
-
} else if output_from_ref {
|
|
765
|
-
let ref_str = reference.to_string();
|
|
766
|
-
let base = ref_str
|
|
767
|
-
.rsplit('/')
|
|
768
|
-
.next()
|
|
769
|
-
.unwrap_or(&ref_str)
|
|
770
|
-
.split(':')
|
|
771
|
-
.next()
|
|
772
|
-
.unwrap_or(&ref_str);
|
|
773
|
-
let filename = if base.ends_with(".wasm") {
|
|
774
|
-
base.to_string()
|
|
775
|
-
} else {
|
|
776
|
-
format!("{base}.wasm")
|
|
777
|
-
};
|
|
778
|
-
let out = PathBuf::from(&filename);
|
|
779
|
-
tokio::fs::copy(&cached_path, &out)
|
|
862
|
+
if let Some(out) = export {
|
|
863
|
+
tokio::fs::copy(&stored_path, &out)
|
|
780
864
|
.await
|
|
781
865
|
.with_context(|| format!("copying to {}", out.display()))?;
|
|
782
866
|
println!("{}", out.display());
|
|
783
867
|
} else {
|
|
784
|
-
|
|
785
|
-
|
|
868
|
+
println!(
|
|
869
|
+
"{} -> {} (sha256:{})",
|
|
870
|
+
reference_str,
|
|
871
|
+
stored_path.display(),
|
|
872
|
+
stored.manifest_digest
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
Ok(())
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// ── Store subcommands ─────────────────────────────────────────────────────────
|
|
879
|
+
|
|
880
|
+
async fn cmd_list(format: OutputFormat) -> Result<()> {
|
|
881
|
+
let store = resolve::open_store()?;
|
|
882
|
+
let mut items = store.list()?;
|
|
883
|
+
items.sort_by(|a, b| source_ref(&a.provenance).cmp(source_ref(&b.provenance)));
|
|
884
|
+
match format {
|
|
885
|
+
OutputFormat::Json => {
|
|
886
|
+
let rows: Vec<_> = items
|
|
887
|
+
.iter()
|
|
888
|
+
.map(|s| {
|
|
889
|
+
serde_json::json!({
|
|
890
|
+
"ref": source_ref(&s.provenance),
|
|
891
|
+
"digest": s.provenance.digest,
|
|
892
|
+
"name": s.provenance.name,
|
|
893
|
+
"version": s.provenance.version,
|
|
894
|
+
"fetched_at": s.provenance.fetched_at,
|
|
895
|
+
})
|
|
896
|
+
})
|
|
897
|
+
.collect();
|
|
898
|
+
println!("{}", serde_json::to_string_pretty(&rows)?);
|
|
899
|
+
}
|
|
900
|
+
OutputFormat::Text => {
|
|
901
|
+
if items.is_empty() {
|
|
902
|
+
println!("(store is empty)");
|
|
903
|
+
}
|
|
904
|
+
for s in &items {
|
|
905
|
+
println!(
|
|
906
|
+
"{}\t{}\t{}",
|
|
907
|
+
source_ref(&s.provenance),
|
|
908
|
+
s.provenance.version.as_deref().unwrap_or("-"),
|
|
909
|
+
s.provenance.digest
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
786
913
|
}
|
|
914
|
+
Ok(())
|
|
915
|
+
}
|
|
787
916
|
|
|
917
|
+
async fn cmd_update(reference: Option<ComponentRef>) -> Result<()> {
|
|
918
|
+
let store = resolve::open_store()?;
|
|
919
|
+
let refs: Vec<String> = match reference {
|
|
920
|
+
Some(r) => vec![r.to_string()],
|
|
921
|
+
None => store
|
|
922
|
+
.list()?
|
|
923
|
+
.iter()
|
|
924
|
+
.map(|s| source_ref(&s.provenance).to_string())
|
|
925
|
+
.collect(),
|
|
926
|
+
};
|
|
927
|
+
if refs.is_empty() {
|
|
928
|
+
println!("(store is empty)");
|
|
929
|
+
return Ok(());
|
|
930
|
+
}
|
|
931
|
+
for r in refs {
|
|
932
|
+
match act_store::update(&store, &r).await {
|
|
933
|
+
Ok(act_store::UpdateOutcome::Unchanged) => println!("{r}\tunchanged"),
|
|
934
|
+
Ok(act_store::UpdateOutcome::Updated { from, to }) => {
|
|
935
|
+
println!("{r}\tupdated {from} -> {to}")
|
|
936
|
+
}
|
|
937
|
+
Ok(act_store::UpdateOutcome::NotStored) => println!("{r}\tnot stored"),
|
|
938
|
+
Err(e) => eprintln!("{r}\tERROR: {e}"),
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
Ok(())
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
async fn cmd_gc() -> Result<()> {
|
|
945
|
+
let store = resolve::open_store()?;
|
|
946
|
+
let removed = store.gc()?;
|
|
947
|
+
println!("removed {removed} unreferenced blob(s)");
|
|
788
948
|
Ok(())
|
|
789
949
|
}
|
|
790
950
|
|
|
951
|
+
/// The source ref (as typed) recorded in a provenance.
|
|
952
|
+
fn source_ref(p: &act_store::Provenance) -> &str {
|
|
953
|
+
match &p.source {
|
|
954
|
+
act_store::Source::Oci { reference } => reference,
|
|
955
|
+
act_store::Source::Http { url, .. } => url,
|
|
956
|
+
act_store::Source::Local { path } => path,
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
791
960
|
#[cfg(test)]
|
|
792
961
|
mod tests {
|
|
793
962
|
use super::*;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//! Component reference resolution, backed by the shared `act-store`.
|
|
2
|
+
//!
|
|
3
|
+
//! `ComponentRef` is re-exported from `act-store` (the parsing source of truth).
|
|
4
|
+
//! Local refs run in place; remote refs (OCI/HTTP) resolve read-through the
|
|
5
|
+
//! store (pulled on first use, then served from disk).
|
|
6
|
+
|
|
7
|
+
use std::path::PathBuf;
|
|
8
|
+
|
|
9
|
+
use anyhow::{Context, Result};
|
|
10
|
+
|
|
11
|
+
pub use act_store::Ref as ComponentRef;
|
|
12
|
+
|
|
13
|
+
/// Open the shared component store at its platform default location.
|
|
14
|
+
pub fn open_store() -> Result<act_store::Store> {
|
|
15
|
+
let dir = act_store::store_dir().context("locating component store")?;
|
|
16
|
+
act_store::Store::open(&dir).context("opening component store")
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// Resolve a component reference to a local `.wasm` path.
|
|
20
|
+
///
|
|
21
|
+
/// Local files are used in place (never copied into the store). Remote refs
|
|
22
|
+
/// (OCI/HTTP) are served read-through from the store; `fresh` forces a re-pull.
|
|
23
|
+
pub async fn resolve(component_ref: &ComponentRef, fresh: bool) -> Result<PathBuf> {
|
|
24
|
+
if let ComponentRef::Local(path) = component_ref {
|
|
25
|
+
anyhow::ensure!(
|
|
26
|
+
tokio::fs::try_exists(path).await.unwrap_or(false),
|
|
27
|
+
"component not found: {}",
|
|
28
|
+
path.display()
|
|
29
|
+
);
|
|
30
|
+
return Ok(path.clone());
|
|
31
|
+
}
|
|
32
|
+
let store = open_store()?;
|
|
33
|
+
let reference = component_ref.to_string();
|
|
34
|
+
if fresh {
|
|
35
|
+
act_store::pull(&store, &reference)
|
|
36
|
+
.await
|
|
37
|
+
.with_context(|| format!("pulling {reference}"))?;
|
|
38
|
+
}
|
|
39
|
+
act_store::ensure(&store, &reference)
|
|
40
|
+
.await
|
|
41
|
+
.with_context(|| format!("resolving {reference}"))
|
|
42
|
+
}
|