Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 96 additions & 8 deletions docs/openrpc/the-spec/firebolt-open-rpc.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,18 @@
"summary": "The current intent as a JSON document.",
"schema": {
"type": "object",
"required": ["intent", "intentId"],
"required": [
"intent",
"intentId"
],
"properties": {
"intent": { "type": "string" },
"intentId": { "type": "integer" }
"intent": {
"type": "object"
},
"intentId": {
Comment thread
swethasukumarr marked this conversation as resolved.
Comment on lines 76 to +80
"type": "integer",
"minimum": 0
}
}
Comment thread
swethasukumarr marked this conversation as resolved.
}
Comment thread
swethasukumarr marked this conversation as resolved.
},
Expand All @@ -81,7 +89,15 @@
"name": "Get the current intent",
"result": {
"name": "Default Result",
"value": { "intent": "launch", "intentId": 1 }
"value": {
"intent": {
"action": "pre-load",
"context": {
"source": "system"
}
},
"intentId": 0
}
}
}
]
Expand Down Expand Up @@ -115,10 +131,18 @@
"summary": "The current intent as a JSON document.",
"schema": {
"type": "object",
"required": ["intent", "intentId"],
"required": [
"intent",
"intentId"
],
"properties": {
"intent": { "type": "string" },
"intentId": { "type": "integer" }
"intent": {
"type": "object"
},
"intentId": {
Comment thread
swethasukumarr marked this conversation as resolved.
Comment on lines 138 to +142
"type": "integer",
"minimum": 0
}
}
Comment thread
swethasukumarr marked this conversation as resolved.
}
Comment thread
swethasukumarr marked this conversation as resolved.
},
Expand All @@ -133,7 +157,71 @@
],
"result": {
"name": "Default Result",
"value": { "intent": "launch", "intentId": 1 }
"value": {
"intent": {
"action": "pre-load",
"context": {
"source": "system"
}
},
"intentId": 0
}
}
}
]
},
{
"name": "Actions.start",
"summary": "Sends an intent to the platform.",
"tags": [
{
"name": "capabilities",
"x-uses": [
"xrn:firebolt:capability:actions:intent"
]
}
],
"params": [
{
"name": "intent",
"summary": "The intent to send, as a JSON document.",
"required": true,
"schema": {
"type": "object"
}
Comment on lines +189 to +191
},
{
"name": "handlerAppId",
"summary": "Optional ID of the application that should handle the intent.",
"required": false,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "result",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Start an intent",
"params": [
{
"name": "intent",
"value": {
"action": "pre-load",
"context": {
"source": "system"
}
}
}
],
"result": {
"name": "Default Result",
"value": null
}
}
]
Expand Down
28 changes: 23 additions & 5 deletions include/firebolt/actions.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,47 @@
#include <functional>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

namespace Firebolt::Actions
{

struct IntentContext
{
std::string source;
};

struct IntentData
{
std::string action;
IntentContext context;
};

struct Intent
{
IntentData intent;
uint32_t intentId{0};
};

class IActions
{
public:
virtual ~IActions() = default;

virtual Result<std::string> intent() const = 0;
virtual Result<Intent> intent() const = 0;

virtual Result<SubscriptionId> subscribeOnIntent(std::function<void(const std::string&)>&& notification) = 0;
virtual Result<SubscriptionId> subscribeOnIntentChanged(std::function<void(const std::string&)>&& notification)
virtual Result<SubscriptionId> subscribeOnIntent(std::function<void(const Intent&)>&& notification) = 0;
virtual Result<SubscriptionId> subscribeOnIntentChanged(std::function<void(const Intent&)>&& notification)
Comment thread
swethasukumarr marked this conversation as resolved.
{
Comment thread
swethasukumarr marked this conversation as resolved.
Comment thread
swethasukumarr marked this conversation as resolved.
return subscribeOnIntent(std::move(notification));
}

virtual Result<void> unsubscribe(SubscriptionId id) = 0;
virtual void unsubscribeAll() = 0;

virtual Result<void> start(const std::string& intent,
std::optional<std::string> handlerAppId = std::nullopt) const = 0;

}; // class IActions

} // namespace Firebolt::Actions
Expand Down
31 changes: 27 additions & 4 deletions src/actions_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,37 @@ ActionsImpl::ActionsImpl(Firebolt::Helpers::IHelper& helper)
{
}

Result<std::string> ActionsImpl::intent() const
Result<Intent> ActionsImpl::intent() const
{
return helper_.get<JsonData::JsonString, std::string>("Actions.intent");
return helper_.get<JsonData::JsonValue, Intent>("Actions.intent");
}

Result<SubscriptionId> ActionsImpl::subscribeOnIntent(std::function<void(const std::string&)>&& notification)
Result<SubscriptionId> ActionsImpl::subscribeOnIntent(std::function<void(const Intent&)>&& notification)
{
return subscriptionManager_.subscribe<JsonData::JsonString>("Actions.onIntent", std::move(notification));
return subscriptionManager_.subscribe<JsonData::JsonValue>("Actions.onIntent", std::move(notification));
}

Result<void> ActionsImpl::start(const std::string& intent, std::optional<std::string> handlerAppId) const
{
nlohmann::json params;
try
{
auto parsedIntent = nlohmann::json::parse(intent);
if (!parsedIntent.is_object())
{
return Firebolt::Result<void>{Firebolt::Error::InvalidParams};
}
params["intent"] = std::move(parsedIntent);
}
catch (const nlohmann::json::parse_error&)
{
return Firebolt::Result<void>{Firebolt::Error::InvalidParams};
}
Comment thread
Copilot marked this conversation as resolved.
if (handlerAppId)
{
params["handlerAppId"] = *handlerAppId;
}
return helper_.invoke("Actions.start", params);
}

Result<void> ActionsImpl::unsubscribe(SubscriptionId id)
Expand Down
6 changes: 4 additions & 2 deletions src/actions_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ class ActionsImpl : public IActions
ActionsImpl& operator=(const ActionsImpl&) = delete;
~ActionsImpl() override = default;

Result<std::string> intent() const override;
Result<Intent> intent() const override;

Result<SubscriptionId> subscribeOnIntent(std::function<void(const std::string&)>&& notification) override;
Result<SubscriptionId> subscribeOnIntent(std::function<void(const Intent&)>&& notification) override;

Result<void> start(const std::string& intent, std::optional<std::string> handlerAppId = std::nullopt) const override;

Result<void> unsubscribe(SubscriptionId id) override;
void unsubscribeAll() override;
Expand Down
27 changes: 18 additions & 9 deletions src/json_types/actions.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,35 @@
#include "firebolt/actions.h"
#include <firebolt/json_types.h>
#include <nlohmann/json.hpp>
#include <type_traits>
#include <stdexcept>

namespace Firebolt::Actions
{

namespace JsonData
{

// Serialises any JSON value (object, string, …) to its compact JSON text
// representation. Used for Actions.intent / Actions.onIntent whose wire format
// is the object {"intent":"...","intentId":N} but whose public C++ API surface
// exposes the whole document as a std::string, per the Firebolt 9 spec.
class JsonString : public Firebolt::JSON::NL_Json_Basic<std::string>
// Deserialises the wire object {"intent":{"action":"...","context":{"source":"..."}},"intentId":N}
// into Firebolt::Actions::Intent. nlohmann stays hidden in this impl-layer header.
class JsonValue : public Firebolt::JSON::NL_Json_Basic<Intent>
{
public:
void fromJson(const nlohmann::json& json) override { value_ = json.dump(); }
std::string value() const override { return value_; }
void fromJson(const nlohmann::json& json) override
{
if (!checkRequiredFields(json, {"intent", "intentId"}) || !json["intent"].is_object() ||
!checkRequiredFields(json["intent"], {"action", "context"}) || !json["intent"]["context"].is_object() ||
!checkRequiredFields(json["intent"]["context"], {"source"}))
{
throw std::invalid_argument("Missing required fields in JSON");
}
value_.intent.action = json["intent"]["action"].get<std::string>();
value_.intent.context.source = json["intent"]["context"]["source"].get<std::string>();
value_.intentId = json["intentId"].get<uint32_t>();
}
Intent value() const override { return value_; }

private:
std::string value_;
Intent value_;
};

} // namespace JsonData
Expand Down
102 changes: 102 additions & 0 deletions test/api_test_app/apis/actionsDemo.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* Copyright 2026 Comcast Cable Communications Management, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/

#include "actionsDemo.h"
#include <firebolt/firebolt.h>
#include <iostream>
#include <string>
#include <utility>

using namespace Firebolt;
using namespace Firebolt::Actions;

ActionsDemo::ActionsDemo()
: DemoBase("Actions")
{
methods_.push_back("Actions.intent");
methods_.push_back("Actions.start");
methods_.push_back("Actions.onIntent");
methods_.push_back("Actions.unsubscribe");
methods_.push_back("Actions.unsubscribeAll");
}

void ActionsDemo::runOption(const std::string& method)
{
std::cout << "Running Actions method: " << method << std::endl;

if (method == "Actions.intent")
{
auto r = Firebolt::IFireboltAccessor::Instance().ActionsInterface().intent();
if (succeed(r))
{
std::cout << "Current Intent - action: " << r->intent.action << ", source: " << r->intent.context.source
<< ", intentId: " << r->intentId << std::endl;
}
}
else if (method == "Actions.start")
{
std::string intent = paramFromConsole("intent (JSON)", R"({"action":"pre-load","context":{"source":"system"}})");
std::string handlerAppIdStr = paramFromConsole("handlerAppId (leave empty to skip)", "");
std::optional<std::string> handlerAppId;
if (!handlerAppIdStr.empty())
{
handlerAppId = handlerAppIdStr;
}
auto r = Firebolt::IFireboltAccessor::Instance().ActionsInterface().start(intent, handlerAppId);
if (succeed(r))
{
std::cout << "Actions.start: Success" << std::endl;
}
}
else if (method == "Actions.onIntent")
{
auto callback = [&](const Intent& payload)
{
std::cout << "Intent received - action: " << payload.intent.action
<< ", source: " << payload.intent.context.source << ", intentId: " << payload.intentId
<< std::endl;
};
auto r = Firebolt::IFireboltAccessor::Instance().ActionsInterface().subscribeOnIntent(std::move(callback));
if (succeed(r))
{
std::cout << "Subscribed to Actions.onIntent with Subscription ID: " << *r << std::endl;
}
}
else if (method == "Actions.unsubscribe")
{
std::string idStr = paramFromConsole("subscription ID", "0");
SubscriptionId id = 0;
try
{
id = static_cast<SubscriptionId>(std::stoul(idStr));
}
catch (const std::exception&)
{
}
auto r = Firebolt::IFireboltAccessor::Instance().ActionsInterface().unsubscribe(id);
if (succeed(r))
{
std::cout << "Unsubscribed from Actions subscription " << id << std::endl;
}
}
else if (method == "Actions.unsubscribeAll")
{
Firebolt::IFireboltAccessor::Instance().ActionsInterface().unsubscribeAll();
std::cout << "Unsubscribed from all Actions subscriptions" << std::endl;
}
}
Loading
Loading