From 510639017183fd642ea7b6af1e70d8205b9164c0 Mon Sep 17 00:00:00 2001 From: TanmayPatwary Date: Tue, 27 May 2025 08:32:11 +0530 Subject: [PATCH 1/2] Metered services documented --- src/backend/doc/features/metered-services.md | 95 ++++++++++++++++++++ src/backend/src/modules/puterai/README.md | 16 ++-- 2 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 src/backend/doc/features/metered-services.md diff --git a/src/backend/doc/features/metered-services.md b/src/backend/doc/features/metered-services.md new file mode 100644 index 0000000000..6a74aa2426 --- /dev/null +++ b/src/backend/doc/features/metered-services.md @@ -0,0 +1,95 @@ +# Metered Services in Puter + +Puter implements metered services through a centralized cost tracking and credit management system. This document describes the core mechanisms that enable metered services in Puter's open-source codebase. + +## Overview + +Metered services in Puter are managed through the `CostService`, which provides a unified interface for: + +- Checking available credits +- Recording service costs +- Tracking funding updates + +While the specific funding logic and credit allocation may vary in different Puter deployments (e.g., puter.com), the underlying mechanism remains consistent. + +## Core Components + +### CostService + +Location: `src/backend/src/services/drivers/CostService.js` + +The CostService is the central component for metered services, providing the following key functionalities: + +1. **Credit Availability Check** + + ```javascript + async get_funding_allowed(options = { minimum: 100 }) + ``` + + - Verifies if sufficient credits are available for an operation + - Default minimum threshold is 100 (1/10th of a cent) + - Returns boolean indicating if funding is allowed + +2. **Cost Recording** + + ```javascript + async record_cost({ cost }) + ``` + + - Records the cost of an operation + - Associates costs with the current actor + - Emits events for credit tracking + +3. **Funding Updates** + ```javascript + async record_funding_update({ old_amount, new_amount }) + ``` + - Tracks changes in user funding + - Maintains audit trail of funding modifications + +## Event System + +CostService uses an event-based architecture to communicate with other system components: + +- `credit.check-available`: Checks available credits +- `credit.record-cost`: Records operation costs +- `credit.funding-update`: Tracks funding changes + +## Integration + +### Using CostService in Modules + +To integrate metered services in a module: + +1. Access the service: + + ```javascript + const costService = services.get("cost"); + ``` + +2. Check funding before expensive operations: + + ```javascript + const fundingAllowed = await costService.get_funding_allowed({ + minimum: requiredAmount, + }); + if (!fundingAllowed) { + throw new Error("Insufficient credits"); + } + ``` + +3. Record costs after operations: + ```javascript + await costService.record_cost({ cost: operationCost }); + ``` + +## Security Considerations + +- All cost operations are associated with the current actor +- Cost recording includes audit logging +- Minimum thresholds prevent micro-transactions + +## Related Documentation + +- For AI-specific metering, see: [PuterAI Documentation](../modules/puterai/README.md) +- For driver implementation details: [How to Make a Driver](../howto_make_driver.md) diff --git a/src/backend/src/modules/puterai/README.md b/src/backend/src/modules/puterai/README.md index 6e04881615..cc38e556cb 100644 --- a/src/backend/src/modules/puterai/README.md +++ b/src/backend/src/modules/puterai/README.md @@ -7,6 +7,10 @@ Services are conditionally registered based on configuration settings, allowing flexible deployment with different AI providers like AWS, OpenAI, Claude, Together AI, Mistral, Groq, and XAI. +## Metered Services + +This module makes use of Puter's metered services system for tracking and managing costs of AI operations. For detailed information about how metered services work in Puter, including cost tracking, credit management, and integration details, please see the [Metered Services Documentation](../../doc/features/metered-services.md). + ## Services ### AIChatService @@ -26,6 +30,7 @@ and populating model lists/maps from providers. Registers each provider as an 'ai-chat' service alias and fetches their available models and pricing information. Populates: + - simple_model_list: Basic list of supported models - detail_model_list: Detailed model info including costs - detail_model_map: Maps model IDs/aliases to their details @@ -34,8 +39,6 @@ available models and pricing information. Populates: ##### `register_provider` - - ##### `moderate` Moderates chat messages for inappropriate content using OpenAI's moderation service @@ -63,8 +66,6 @@ the first one that is not in the tried list. ##### `get_model_from_request` - - ### AIInterfaceService Service class that manages AI interface registrations and configurations. @@ -142,8 +143,6 @@ Service that emulates Claude's behavior using alternative AI models ##### `adapt_model` - - ### ClaudeService ClaudeService class extends BaseService to provide integration with Anthropic's Claude AI models. @@ -240,8 +239,6 @@ validation, and spending tracking. ##### `generate` - - ### TogetherAIService TogetherAIService class provides integration with Together AI's language models. @@ -270,8 +267,6 @@ Gets the system prompt used for AI interactions ##### `adapt_model` - - ##### `get_default_model` Returns the default model identifier for the XAI service @@ -285,6 +280,7 @@ removed it may become possible to move this module to an extension. **Imports:** + - `../../api/APIError` - `../../services/auth/PermissionService` - `../../services/BaseService` (use.BaseService) From 6f2006c3c6a82aaad358dacd90affe6a4c46f6ef Mon Sep 17 00:00:00 2001 From: TanmayPatwary Date: Wed, 28 May 2025 09:32:26 +0530 Subject: [PATCH 2/2] Error fixed --- src/gui/src/helpers/update_username_in_gui.js | 180 +++++++++++------- 1 file changed, 113 insertions(+), 67 deletions(-) diff --git a/src/gui/src/helpers/update_username_in_gui.js b/src/gui/src/helpers/update_username_in_gui.js index b6f5d98dda..6b959fa380 100644 --- a/src/gui/src/helpers/update_username_in_gui.js +++ b/src/gui/src/helpers/update_username_in_gui.js @@ -7,84 +7,130 @@ * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const update_username_in_gui = function(new_username){ - // ------------------------------------------------------------ - // Update all item/window/... paths, with the new username - // ------------------------------------------------------------ - $(':not([data-path=""]),:not([data-item-path=""])').each((i, el)=>{ - const $el = $(el); - const attr_path = $el.attr('data-path'); - const attr_item_path = $el.attr('data-item-path'); - const attr_shortcut_to_path = $el.attr('data-shortcut_to_path'); - // data-path - if(attr_path && attr_path !== 'null' && attr_path !== 'undefined'){ - // /[username] - if(attr_path === '/' + window.user.username) - $el.attr('data-path', '/' + new_username); - // /[username]/... - else if (attr_path.startsWith('/' + window.user.username + '/')) - $el.attr('data-path', attr_path.replace('/' + window.user.username + '/', '/' + new_username + '/')); +const update_username_in_gui = function ( + new_username, + old_username = window.user.username +) { + // ------------------------------------------------------------ + // Update all item/window/... paths, with the new username + // ------------------------------------------------------------ + $(':not([data-path=""]),:not([data-item-path=""])').each((i, el) => { + const $el = $(el); + const attr_path = $el.attr("data-path"); + const attr_item_path = $el.attr("data-item-path"); + const attr_shortcut_to_path = $el.attr("data-shortcut_to_path"); + // data-path + if (attr_path && attr_path !== "null" && attr_path !== "undefined") { + // /[username] + if (attr_path === "/" + old_username) + $el.attr("data-path", "/" + new_username); + // /[username]/... + else if (attr_path.startsWith("/" + old_username + "/")) + $el.attr( + "data-path", + attr_path.replace("/" + old_username + "/", "/" + new_username + "/") + ); - // .window-navbar-path-dirname - if($el.hasClass('window-navbar-path-dirname') && attr_path === '/' + window.user.username) - $el.text(new_username) + // .window-navbar-path-dirname + if ( + $el.hasClass("window-navbar-path-dirname") && + attr_path === "/" + old_username + ) + $el.text(new_username); + // .window-navbar-path-input value + else if ($el.hasClass("window-navbar-path-input")) { + // /[username] + if (attr_path === "/" + old_username) $el.val("/" + new_username); + // /[username]/... + else if (attr_path.startsWith("/" + old_username + "/")) + $el.val( + attr_path.replace( + "/" + old_username + "/", + "/" + new_username + "/" + ) + ); + } - // .window-navbar-path-input value - else if($el.hasClass('window-navbar-path-input')){ - // /[username] - if(attr_path === '/' + window.user.username) - $el.val('/' + new_username); - // /[username]/... - else if (attr_path.startsWith('/' + window.user.username + '/')) - $el.val(attr_path.replace('/' + window.user.username + '/', '/' + new_username + '/')); - } - } - // data-shortcut_to_path - if(attr_shortcut_to_path && attr_shortcut_to_path !== '' && attr_shortcut_to_path !== 'null' && attr_shortcut_to_path !== 'undefined'){ - // home dir - if(attr_shortcut_to_path === '/' + window.user.username) - $el.attr('data-shortcut_to_path', '/' + new_username); - // every other paths - else if(attr_shortcut_to_path.startsWith('/' + window.user.username + '/')) - $el.attr('data-shortcut_to_path', attr_shortcut_to_path.replace('/' + window.user.username + '/', '/' + new_username + '/')); + // Update sidebar shared user paths + if ( + $el.hasClass("window-sidebar-item") && + attr_path === "/" + old_username + ) { + $el.attr("data-path", "/" + new_username); + // Also update the display text if this is a shared user item + if ($el.closest(".shared-users-list").length) { + $el.text(new_username); } - // data-item-path - if(attr_item_path && attr_item_path !== 'null' && attr_item_path !== 'undefined'){ - // /[username] - if(attr_item_path === '/' + window.user.username) - $el.attr('data-item-path', '/' + new_username); - // /[username]/... - else if (attr_item_path.startsWith('/' + window.user.username + '/')) - $el.attr('data-item-path', attr_item_path.replace('/' + window.user.username + '/', '/' + new_username + '/')); - } - - // any element with username class - $('.username').text(new_username); - }) + } + } + // data-shortcut_to_path + if ( + attr_shortcut_to_path && + attr_shortcut_to_path !== "" && + attr_shortcut_to_path !== "null" && + attr_shortcut_to_path !== "undefined" + ) { + // home dir + if (attr_shortcut_to_path === "/" + old_username) + $el.attr("data-shortcut_to_path", "/" + new_username); + // every other paths + else if (attr_shortcut_to_path.startsWith("/" + old_username + "/")) + $el.attr( + "data-shortcut_to_path", + attr_shortcut_to_path.replace( + "/" + old_username + "/", + "/" + new_username + "/" + ) + ); + } + // data-item-path + if ( + attr_item_path && + attr_item_path !== "null" && + attr_item_path !== "undefined" + ) { + // /[username] + if (attr_item_path === "/" + old_username) + $el.attr("data-item-path", "/" + new_username); + // /[username]/... + else if (attr_item_path.startsWith("/" + old_username + "/")) + $el.attr( + "data-item-path", + attr_item_path.replace( + "/" + old_username + "/", + "/" + new_username + "/" + ) + ); + } - // todo update all window paths - $('.window').each((i, el)=>{ - }) + // any element with username class + if (old_username === window.user.username) { + $(".username").text(new_username); + } + }); - window.desktop_path = '/' + new_username + '/Desktop'; - window.trash_path = '/' + new_username + '/Trash'; - window.appdata_path = '/' + new_username + '/AppData'; - window.docs_path = '/' + new_username + '/Documents'; - window.pictures_path = '/' + new_username + '/Pictures'; - window.videos_path = '/' + new_username + '/Videos'; - window.desktop_path = '/' + new_username + '/Desktop'; - window.public_path = '/' + new_username + '/Public'; - window.home_path = '/' + new_username; -} + // Update window paths if this is the current user + if (old_username === window.user.username) { + window.desktop_path = "/" + new_username + "/Desktop"; + window.trash_path = "/" + new_username + "/Trash"; + window.appdata_path = "/" + new_username + "/AppData"; + window.docs_path = "/" + new_username + "/Documents"; + window.pictures_path = "/" + new_username + "/Pictures"; + window.videos_path = "/" + new_username + "/Videos"; + window.desktop_path = "/" + new_username + "/Desktop"; + window.public_path = "/" + new_username + "/Public"; + window.home_path = "/" + new_username; + } +}; -export default update_username_in_gui; \ No newline at end of file +export default update_username_in_gui;