diff --git a/.agents/skills/prepare-code-freeze/SKILL.md b/.agents/skills/prepare-code-freeze/SKILL.md index 749e3388..ab78c5ab 100644 --- a/.agents/skills/prepare-code-freeze/SKILL.md +++ b/.agents/skills/prepare-code-freeze/SKILL.md @@ -40,19 +40,34 @@ This workflow assumes `upstream` is the NVIDIA repository remote branch. 6. Run `just set-version ` to bump all release-versioned package surfaces on `main`. -7. Validate with targeted checks: +7. Search documentation source for references to the old version and update + current-version install commands, package examples, and configuration + examples to `` where appropriate: + + ```bash + rg -n '' README.md docs fern --glob '!docs/_build/**' || true + ``` + + Review matches before changing them. Leave intentional historical references + alone, such as release notes, changelogs, generated build output, and + third-party dependency attribution entries. +8. Validate with targeted checks: ```bash ruby -e 'require "yaml"; YAML.load_file(".github/nightly-alpha-branches.yaml"); YAML.load_file(".github/workflows/nightly-alpha-tag.yaml")' just set-version + rg -n '' README.md docs fern --glob '!docs/_build/**' || true git diff --check ``` -8. Open a PR targeting `main` using `.github/pull_request_template.md`. The PR + Any remaining documentation matches for `` should be intentional + and called out in the PR description. +9. Open a PR targeting `main` using `.github/pull_request_template.md`. The PR must mention: - the new release branch - the nightly alpha branch config update - the `just set-version ` bump + - documentation old-version reference updates or intentional leftovers - that release-bound PRs now target the new `release/*` branch ## Guardrails diff --git a/.github/nightly-alpha-branches.yaml b/.github/nightly-alpha-branches.yaml index 04baa981..ff30a6d9 100644 --- a/.github/nightly-alpha-branches.yaml +++ b/.github/nightly-alpha-branches.yaml @@ -3,3 +3,4 @@ branches: - main + - release/0.3 diff --git a/Cargo.lock b/Cargo.lock index 0dd0dd48..0246c835 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1313,7 +1313,7 @@ dependencies = [ [[package]] name = "nemo-relay" -version = "0.3.0" +version = "0.4.0" dependencies = [ "async-trait", "bitflags", @@ -1345,7 +1345,7 @@ dependencies = [ [[package]] name = "nemo-relay-adaptive" -version = "0.3.0" +version = "0.4.0" dependencies = [ "chrono", "nemo-relay", @@ -1364,7 +1364,7 @@ dependencies = [ [[package]] name = "nemo-relay-cli" -version = "0.3.0" +version = "0.4.0" dependencies = [ "async-stream", "axum", @@ -1394,7 +1394,7 @@ dependencies = [ [[package]] name = "nemo-relay-ffi" -version = "0.3.0" +version = "0.4.0" dependencies = [ "cbindgen", "chrono", @@ -1409,7 +1409,7 @@ dependencies = [ [[package]] name = "nemo-relay-node" -version = "0.3.0" +version = "0.4.0" dependencies = [ "chrono", "napi", @@ -1426,7 +1426,7 @@ dependencies = [ [[package]] name = "nemo-relay-python" -version = "0.3.0" +version = "0.4.0" dependencies = [ "chrono", "nemo-relay", @@ -1443,7 +1443,7 @@ dependencies = [ [[package]] name = "nemo-relay-wasm" -version = "0.3.0" +version = "0.4.0" dependencies = [ "chrono", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index bc57452f..bf54af16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,16 +16,16 @@ exclude = [".tmp", ".uv-cache"] resolver = "2" [workspace.package] -version = "0.3.0" +version = "0.4.0" edition = "2024" license = "Apache-2.0" repository = "https://github.com/NVIDIA/NeMo-Relay" [workspace.dependencies] -nemo-relay = { version = "0.3.0", path = "crates/core", default-features = false } -nemo-relay-adaptive = { version = "0.3.0", path = "crates/adaptive" } -nemo-relay-ffi = { version = "0.3.0", path = "crates/ffi" } -nemo-relay-cli = { version = "0.3.0", path = "crates/cli" } +nemo-relay = { version = "0.4.0", path = "crates/core", default-features = false } +nemo-relay-adaptive = { version = "0.4.0", path = "crates/adaptive" } +nemo-relay-ffi = { version = "0.4.0", path = "crates/ffi" } +nemo-relay-cli = { version = "0.4.0", path = "crates/cli" } uuid = "=1.18.1" [workspace.lints.rust] diff --git a/crates/cli/src/alignment/mod.rs b/crates/cli/src/alignment/mod.rs index 3c582525..48e5f2f1 100644 --- a/crates/cli/src/alignment/mod.rs +++ b/crates/cli/src/alignment/mod.rs @@ -245,9 +245,10 @@ impl SessionAlignmentState { } // Resolves the session id for a gateway request in precedence order: -// explicit NeMo Relay header, agent-native headers, then agent-specific body fallbacks. Keeping the -// provider fallbacks behind one function makes a new agent integration add one small alignment -// adapter instead of threading bespoke checks through gateway request construction. +// explicit NeMo Relay header, agent-native headers, agent-specific body fallbacks, then the +// generic OpenAI-compatible `session_id` body field. Keeping the provider fallbacks behind one +// function makes a new agent integration add one small alignment adapter instead of threading +// bespoke checks through gateway request construction. pub(crate) fn gateway_session_id( headers: &HeaderMap, body: &Value, @@ -256,6 +257,21 @@ pub(crate) fn gateway_session_id( header_string(headers, "x-nemo-relay-session-id") .or_else(|| claude_code::session_id_from_headers(headers)) .or_else(|| codex::prompt_cache_session_id(body, route)) + .or_else(|| openai_body_session_id(body, route)) +} + +fn openai_body_session_id(body: &Value, route: GatewayRouteKind) -> Option { + if !matches!( + route, + GatewayRouteKind::OpenAiChatCompletions | GatewayRouteKind::OpenAiResponses + ) { + return None; + } + body.get("session_id") + .and_then(Value::as_str) + .map(str::trim) + .filter(|session_id| !session_id.is_empty()) + .map(ToOwned::to_owned) } // Gives provider adapters a chance to select an agent-native upstream before the gateway falls diff --git a/crates/cli/tests/coverage/alignment_tests.rs b/crates/cli/tests/coverage/alignment_tests.rs index c8077f50..8806e0dd 100644 --- a/crates/cli/tests/coverage/alignment_tests.rs +++ b/crates/cli/tests/coverage/alignment_tests.rs @@ -92,7 +92,8 @@ fn gateway_session_id_uses_explicit_claude_then_codex_fallbacks() { let mut headers = HeaderMap::new(); let codex_body = json!({ "prompt_cache_key": "codex-thread", - "client_metadata": { "x-codex-installation-id": "install-1" } + "client_metadata": { "x-codex-installation-id": "install-1" }, + "session_id": "body-thread" }); assert_eq!( @@ -119,6 +120,45 @@ fn gateway_session_id_uses_explicit_claude_then_codex_fallbacks() { ); } +#[test] +fn gateway_session_id_accepts_openai_body_session_id_fallback() { + let headers = HeaderMap::new(); + + assert_eq!( + gateway_session_id( + &headers, + &json!({ "session_id": " body-session " }), + GatewayRouteKind::OpenAiChatCompletions, + ) + .as_deref(), + Some("body-session") + ); + assert_eq!( + gateway_session_id( + &headers, + &json!({ "session_id": "body-session" }), + GatewayRouteKind::AnthropicMessages, + ), + None + ); + assert_eq!( + gateway_session_id( + &headers, + &json!({ "session_id": "" }), + GatewayRouteKind::OpenAiChatCompletions, + ), + None + ); + assert_eq!( + gateway_session_id( + &headers, + &json!({ "session_id": 42 }), + GatewayRouteKind::OpenAiResponses, + ), + None + ); +} + #[test] fn gateway_subagent_and_identifier_helpers_respect_header_precedence() { let mut headers = HeaderMap::new(); diff --git a/crates/cli/tests/coverage/gateway_tests.rs b/crates/cli/tests/coverage/gateway_tests.rs index 6aa6742a..748b389d 100644 --- a/crates/cli/tests/coverage/gateway_tests.rs +++ b/crates/cli/tests/coverage/gateway_tests.rs @@ -265,7 +265,8 @@ fn gateway_session_id_prefers_headers_and_has_fallbacks() { let mut headers = HeaderMap::new(); let codex_body = json!({ "prompt_cache_key": "codex-session", - "client_metadata": { "x-codex-installation-id": "install-1" } + "client_metadata": { "x-codex-installation-id": "install-1" }, + "session_id": "body-session" }); headers.insert( "anthropic-beta", @@ -316,6 +317,24 @@ fn gateway_session_id_prefers_headers_and_has_fallbacks() { &HeaderMap::new(), &codex_body, ProviderRoute::OpenAiChatCompletions, + ) + .as_deref(), + Some("body-session") + ); + assert_eq!( + gateway_session_id( + &HeaderMap::new(), + &json!({ "session_id": " body-session " }), + ProviderRoute::OpenAiResponses, + ) + .as_deref(), + Some("body-session") + ); + assert_eq!( + gateway_session_id( + &HeaderMap::new(), + &json!({ "session_id": "body-session" }), + ProviderRoute::AnthropicMessages, ), None ); diff --git a/crates/core/src/observability/atif.rs b/crates/core/src/observability/atif.rs index f5801551..96674db1 100644 --- a/crates/core/src/observability/atif.rs +++ b/crates/core/src/observability/atif.rs @@ -1194,6 +1194,7 @@ struct LlmSpanCandidate { start_ts: DateTime, end_ts: DateTime, request_signature: String, + request_correlation_keys: HashSet, response_signature: String, model_name: Option, fidelity_score: u8, @@ -1248,6 +1249,7 @@ impl LlmSpanCandidate { start_ts: *start.timestamp(), end_ts: *end.timestamp(), request_signature, + request_correlation_keys: llm_request_correlation_keys(start, end), response_signature, model_name: start .model_name() @@ -1274,6 +1276,61 @@ fn llm_response_signature(output: &Json) -> String { json_to_string(&extract_llm_response_message(output)) } +fn llm_request_correlation_keys(start: &Event, end: &Event) -> HashSet { + let mut keys = HashSet::new(); + collect_llm_request_correlation_keys(start, &mut keys); + collect_llm_request_correlation_keys(end, &mut keys); + keys +} + +fn collect_llm_request_correlation_keys(event: &Event, keys: &mut HashSet) { + if let Some(metadata) = event.metadata() { + collect_request_correlation_values(metadata, keys); + } + if let Some(data) = event.data() { + collect_request_correlation_values(data, keys); + collect_request_correlation_values(&unwrap_llm_request(data), keys); + } +} + +fn collect_request_correlation_values(value: &Json, keys: &mut HashSet) { + for path in [ + &["api_call_id"][..], + &["apiCallId"], + &["request_id"], + &["requestId"], + &["request", "id"], + &["metadata", "request_id"], + &["metadata", "requestId"], + &["extra", "api_call_id"], + &["extra", "apiCallId"], + &["extra", "request_id"], + &["extra", "requestId"], + &["llm_correlation_request_id"], + ] { + insert_correlation_key(keys, "request", json_string_at(value, path)); + } + + for path in [ + &["generation_id"][..], + &["generationId"], + &["generation", "id"], + &["metadata", "generation_id"], + &["metadata", "generationId"], + &["extra", "generation_id"], + &["extra", "generationId"], + &["llm_correlation_generation_id"], + ] { + insert_correlation_key(keys, "generation", json_string_at(value, path)); + } +} + +fn insert_correlation_key(keys: &mut HashSet, kind: &str, value: Option) { + if let Some(value) = value.filter(|value| !value.is_empty()) { + keys.insert(format!("{kind}:{value}")); + } +} + fn same_physical_llm_request(left: &LlmSpanCandidate, right: &LlmSpanCandidate) -> bool { same_parent(left, right) && compatible_model_names(left, right) @@ -1288,10 +1345,22 @@ fn same_llm_payload_signatures(left: &LlmSpanCandidate, right: &LlmSpanCandidate } fn complementary_hook_and_gateway_spans(left: &LlmSpanCandidate, right: &LlmSpanCandidate) -> bool { - (left.non_exact_provider_payload && left.hook_instrumentation && right.gateway_instrumentation) + let complementary_polarity = (left.non_exact_provider_payload + && left.hook_instrumentation + && right.gateway_instrumentation) || (right.non_exact_provider_payload && right.hook_instrumentation - && left.gateway_instrumentation) + && left.gateway_instrumentation); + + complementary_polarity + && (left.request_signature == right.request_signature + || shared_llm_request_correlation_key(left, right)) +} + +fn shared_llm_request_correlation_key(left: &LlmSpanCandidate, right: &LlmSpanCandidate) -> bool { + !left + .request_correlation_keys + .is_disjoint(&right.request_correlation_keys) } fn same_parent(left: &LlmSpanCandidate, right: &LlmSpanCandidate) -> bool { diff --git a/crates/core/tests/unit/atif_tests.rs b/crates/core/tests/unit/atif_tests.rs index c42d4333..a59048e6 100644 --- a/crates/core/tests/unit/atif_tests.rs +++ b/crates/core/tests/unit/atif_tests.rs @@ -1935,6 +1935,7 @@ fn test_exporter_prefers_gateway_span_over_non_exact_hook_summary() { .model_name("test-model") .metadata(json!({ "hook_event_name": "pre_api_request", + "api_call_id": "request-1", "fidelity_source": "agent_api_hooks", "provider_payload_exact": false })) @@ -1948,7 +1949,10 @@ fn test_exporter_prefers_gateway_span_over_non_exact_hook_summary() { .parent_uuid(parent_uuid) .scope_type(ScopeType::Llm) .model_name("test-model") - .metadata(json!({"gateway_path": "/v1/chat/completions"})) + .metadata(json!({ + "gateway_path": "/v1/chat/completions", + "llm_correlation_request_id": "request-1" + })) .input(json!({"content": request.clone(), "headers": {}})) .build(); let mut gateway_end = event_builder(gateway_uuid, EventType::End) @@ -1956,7 +1960,10 @@ fn test_exporter_prefers_gateway_span_over_non_exact_hook_summary() { .parent_uuid(parent_uuid) .scope_type(ScopeType::Llm) .model_name("test-model") - .metadata(json!({"gateway_path": "/v1/chat/completions"})) + .metadata(json!({ + "gateway_path": "/v1/chat/completions", + "llm_correlation_request_id": "request-1" + })) .output(json!({"choices": [{"message": {"content": "gateway_ok"}}]})) .build(); let mut hook_end = event_builder(hook_uuid, EventType::End) @@ -1966,6 +1973,7 @@ fn test_exporter_prefers_gateway_span_over_non_exact_hook_summary() { .model_name("test-model") .metadata(json!({ "hook_event_name": "post_api_request", + "api_call_id": "request-1", "fidelity_source": "agent_api_hooks", "provider_payload_exact": false })) @@ -2005,6 +2013,106 @@ fn test_exporter_prefers_gateway_span_over_non_exact_hook_summary() { })); } +#[test] +fn test_exporter_keeps_overlapping_non_exact_hook_and_gateway_spans_without_shared_request_key() { + let exporter = AtifExporter::new("session-1".to_string(), make_agent_info()); + let base = base_timestamp(); + let parent_uuid = Uuid::now_v7(); + let hook_uuid = Uuid::now_v7(); + let gateway_uuid = Uuid::now_v7(); + let request = json!({ + "messages": [{"role": "user", "content": "Reply with exactly gateway_distinct_ok"}], + "model": "test-model" + }); + + let mut hook_start = event_builder(hook_uuid, EventType::Start) + .name("openrouter") + .parent_uuid(parent_uuid) + .scope_type(ScopeType::Llm) + .model_name("test-model") + .metadata(json!({ + "hook_event_name": "pre_api_request", + "api_call_id": "hook-request", + "fidelity_source": "agent_api_hooks", + "provider_payload_exact": false + })) + .input(json!({ + "content": {"message_count": 2, "request_char_count": 128}, + "headers": {} + })) + .build(); + let mut gateway_start = event_builder(gateway_uuid, EventType::Start) + .name("openai.chat_completions") + .parent_uuid(parent_uuid) + .scope_type(ScopeType::Llm) + .model_name("test-model") + .metadata(json!({ + "gateway_path": "/v1/chat/completions", + "llm_correlation_request_id": "gateway-request" + })) + .input(json!({"content": request.clone(), "headers": {}})) + .build(); + let mut gateway_end = event_builder(gateway_uuid, EventType::End) + .name("openai.chat_completions") + .parent_uuid(parent_uuid) + .scope_type(ScopeType::Llm) + .model_name("test-model") + .metadata(json!({ + "gateway_path": "/v1/chat/completions", + "llm_correlation_request_id": "gateway-request" + })) + .output(json!({"choices": [{"message": {"content": "gateway_distinct_ok"}}]})) + .build(); + let mut hook_end = event_builder(hook_uuid, EventType::End) + .name("openrouter") + .parent_uuid(parent_uuid) + .scope_type(ScopeType::Llm) + .model_name("test-model") + .metadata(json!({ + "hook_event_name": "post_api_request", + "api_call_id": "hook-request", + "fidelity_source": "agent_api_hooks", + "provider_payload_exact": false + })) + .output(json!({"assistant_content_chars": 10, "finish_reason": "stop"})) + .build(); + + for (idx, event) in [ + &mut hook_start, + &mut gateway_start, + &mut gateway_end, + &mut hook_end, + ] + .into_iter() + .enumerate() + { + set_event_timestamp(event, base + chrono::Duration::milliseconds(idx as i64)); + } + + { + let mut state = exporter.state.lock().unwrap(); + state + .events + .extend([hook_start, gateway_start, gateway_end, hook_end]); + } + + let trajectory = exporter.export().unwrap(); + assert_eq!(trajectory.steps.len(), 4); + let function_name_count = |name: &str| { + trajectory + .steps + .iter() + .filter(|step| { + step.extra + .as_ref() + .is_some_and(|extra| extra["ancestry"]["function_name"] == name) + }) + .count() + }; + assert!(function_name_count("openrouter") > 0); + assert!(function_name_count("openai.chat_completions") > 0); +} + #[test] fn test_exporter_keeps_sequential_same_content_llm_spans() { let exporter = AtifExporter::new("session-1".to_string(), make_agent_info()); diff --git a/crates/node/package.json b/crates/node/package.json index 6ed164ac..2fe3a8fd 100644 --- a/crates/node/package.json +++ b/crates/node/package.json @@ -1,6 +1,6 @@ { "name": "nemo-relay-node", - "version": "0.3.0", + "version": "0.4.0", "description": "Node.js bindings for the NeMo Relay agent runtime.", "keywords": [ "agents", diff --git a/docs/getting-started/installation.mdx b/docs/getting-started/installation.mdx index 88a3194f..9ce156ae 100644 --- a/docs/getting-started/installation.mdx +++ b/docs/getting-started/installation.mdx @@ -20,7 +20,7 @@ Install the NeMo Relay CLI when you want the `nemo-relay` executable for coding-agent hook and LLM gateway observability. ```bash -cargo install nemo-relay-cli@0.3.0 +cargo install nemo-relay-cli@0.4.0 ``` ## Python @@ -29,7 +29,7 @@ Install the Python package when your application uses NeMo Relay through the Python wrapper. ```bash -uv add nemo-relay@0.3.0 +uv add nemo-relay@0.4.0 ``` Use `uv add` from an application project that has a `pyproject.toml`; it records @@ -52,8 +52,8 @@ npm install nemo-relay-node Add the Rust crates when your application uses NeMo Relay directly from Rust. ```bash -cargo add nemo-relay@0.3.0 -cargo add nemo-relay-adaptive@0.3.0 +cargo add nemo-relay@0.4.0 +cargo add nemo-relay-adaptive@0.4.0 ``` - `nemo-relay` provides the core runtime APIs for scopes, middleware, subscribers, plugins, tool calls, and LLM calls. @@ -70,7 +70,7 @@ Install the OpenClaw plugin through OpenClaw so OpenClaw can register and manage the package: ```bash -openclaw plugins install npm:nemo-relay-openclaw@0.3.0 +openclaw plugins install npm:nemo-relay-openclaw@0.4.0 openclaw gateway restart ``` @@ -85,7 +85,7 @@ Install the Python package with the supported framework extras when your application uses LangChain, LangGraph, or Deep Agents. ```bash -uv add "nemo-relay[langchain,langgraph,deepagents]@0.3.0" +uv add "nemo-relay[langchain,langgraph,deepagents]@0.4.0" ``` The extras install the NeMo Relay Python package plus the dependencies needed by diff --git a/docs/getting-started/quick-start/python.mdx b/docs/getting-started/quick-start/python.mdx index a6c03092..5c1b0db6 100644 --- a/docs/getting-started/quick-start/python.mdx +++ b/docs/getting-started/quick-start/python.mdx @@ -20,7 +20,7 @@ local checkout. Use this path when you want the published package for application development. ```bash -uv add nemo-relay@0.3.0 +uv add nemo-relay@0.4.0 ``` Run `uv add` from an application project that has a `pyproject.toml`; it records diff --git a/docs/getting-started/quick-start/rust.mdx b/docs/getting-started/quick-start/rust.mdx index 351dfe33..de6ef3a7 100644 --- a/docs/getting-started/quick-start/rust.mdx +++ b/docs/getting-started/quick-start/rust.mdx @@ -18,8 +18,8 @@ local checkout. Use the published crates when you are consuming a release: ```bash -cargo add nemo-relay@0.3.0 -cargo add nemo-relay-adaptive@0.3.0 +cargo add nemo-relay@0.4.0 +cargo add nemo-relay-adaptive@0.4.0 cargo add serde_json ``` @@ -27,7 +27,7 @@ Install the published NeMo Relay CLI separately when you need coding-agent hook and LLM gateway observability: ```bash -cargo install nemo-relay-cli@0.3.0 +cargo install nemo-relay-cli@0.4.0 ``` ### Install from the Repository @@ -43,7 +43,7 @@ serde_json = "1" - `nemo-relay` is the core Rust runtime surface. - `nemo-relay-adaptive` is the companion crate for adaptive runtime primitives and Redis-backed learning components. -- `nemo-relay-cli` is a binary crate. Use `cargo install nemo-relay-cli@0.3.0` when +- `nemo-relay-cli` is a binary crate. Use `cargo install nemo-relay-cli@0.4.0` when you need the NeMo Relay CLI. ## Push a Scope and Emit a Mark diff --git a/docs/observability-plugin/configuration.mdx b/docs/observability-plugin/configuration.mdx index 3bedd30f..2350814c 100644 --- a/docs/observability-plugin/configuration.mdx +++ b/docs/observability-plugin/configuration.mdx @@ -79,7 +79,7 @@ transport = "http_binary" endpoint = "http://localhost:4318/v1/traces" service_name = "nemo-relay" service_namespace = "agent" -service_version = "0.3.0" +service_version = "0.4.0" instrumentation_scope = "nemo-relay-observability" timeout_millis = 3000 @@ -96,7 +96,7 @@ transport = "http_binary" endpoint = "http://localhost:6006/v1/traces" service_name = "nemo-relay" service_namespace = "agent" -service_version = "0.3.0" +service_version = "0.4.0" instrumentation_scope = "nemo-relay-openinference" timeout_millis = 3000 @@ -153,7 +153,7 @@ config = plugin.PluginConfig( endpoint="http://localhost:4318/v1/traces", service_name="nemo-relay", service_namespace="agent", - service_version="0.3.0", + service_version="0.4.0", instrumentation_scope="nemo-relay-observability", resource_attributes={"deployment.environment": "dev"}, ), @@ -162,7 +162,7 @@ config = plugin.PluginConfig( endpoint="http://localhost:6006/v1/traces", service_name="nemo-relay", service_namespace="agent", - service_version="0.3.0", + service_version="0.4.0", instrumentation_scope="nemo-relay-openinference", resource_attributes={"deployment.environment": "dev"}, ), @@ -211,7 +211,7 @@ await plugin.initialize({ endpoint: "http://localhost:4318/v1/traces", service_name: "nemo-relay", service_namespace: "agent", - service_version: "0.3.0", + service_version: "0.4.0", instrumentation_scope: "nemo-relay-observability", resource_attributes: { "deployment.environment": "dev", @@ -222,7 +222,7 @@ await plugin.initialize({ endpoint: "http://localhost:6006/v1/traces", service_name: "nemo-relay", service_namespace: "agent", - service_version: "0.3.0", + service_version: "0.4.0", instrumentation_scope: "nemo-relay-openinference", resource_attributes: { "deployment.environment": "dev", @@ -267,7 +267,7 @@ let component = ComponentSpec::new(ObservabilityConfig { endpoint: Some("http://localhost:4318/v1/traces".into()), service_name: "nemo-relay".into(), service_namespace: Some("agent".into()), - service_version: Some("0.3.0".into()), + service_version: Some("0.4.0".into()), instrumentation_scope: Some("nemo-relay-observability".into()), resource_attributes: [("deployment.environment".into(), "dev".into())].into(), ..OtlpSectionConfig::default() @@ -277,7 +277,7 @@ let component = ComponentSpec::new(ObservabilityConfig { endpoint: Some("http://localhost:6006/v1/traces".into()), service_name: "nemo-relay".into(), service_namespace: Some("agent".into()), - service_version: Some("0.3.0".into()), + service_version: Some("0.4.0".into()), instrumentation_scope: Some("nemo-relay-openinference".into()), resource_attributes: [("deployment.environment".into(), "dev".into())].into(), ..OtlpSectionConfig::default() diff --git a/docs/supported-integrations/openclaw-plugin.mdx b/docs/supported-integrations/openclaw-plugin.mdx index f1024aca..31d90525 100644 --- a/docs/supported-integrations/openclaw-plugin.mdx +++ b/docs/supported-integrations/openclaw-plugin.mdx @@ -40,7 +40,7 @@ Optional: Install the plugin with OpenClaw so OpenClaw can register and manage it: ```bash -openclaw plugins install npm:nemo-relay-openclaw@0.3.0 +openclaw plugins install npm:nemo-relay-openclaw@0.4.0 openclaw gateway restart ``` @@ -53,7 +53,7 @@ If you manage OpenClaw plugin dependencies directly in a Node.js project, install the package with npm: ```bash -npm install nemo-relay-openclaw@0.3.0 +npm install nemo-relay-openclaw@0.4.0 ``` Installing with npm makes the package available to that project. Use diff --git a/integrations/openclaw/package.json b/integrations/openclaw/package.json index 9a3e0d12..604a5522 100644 --- a/integrations/openclaw/package.json +++ b/integrations/openclaw/package.json @@ -1,6 +1,6 @@ { "name": "nemo-relay-openclaw", - "version": "0.3.0", + "version": "0.4.0", "description": "NeMo Relay-authored observability plugin for OpenClaw.", "type": "module", "repository": { @@ -63,7 +63,7 @@ "openclaw": "^2026.5.26" }, "dependencies": { - "nemo-relay-node": "0.3.0" + "nemo-relay-node": "0.4.0" }, "devDependencies": { "@types/node": "^24.0.0", diff --git a/package-lock.json b/package-lock.json index c81981eb..d1090fd0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ }, "crates/node": { "name": "nemo-relay-node", - "version": "0.3.0", + "version": "0.4.0", "license": "Apache-2.0", "devDependencies": { "@napi-rs/cli": "^2", @@ -836,10 +836,10 @@ }, "integrations/openclaw": { "name": "nemo-relay-openclaw", - "version": "0.3.0", + "version": "0.4.0", "license": "Apache-2.0", "dependencies": { - "nemo-relay-node": "0.3.0" + "nemo-relay-node": "0.4.0" }, "devDependencies": { "@types/node": "^24.0.0",