diff --git a/src/backend/src/CoreModule.js b/src/backend/src/CoreModule.js
index d6115e586..10cf8cc34 100644
--- a/src/backend/src/CoreModule.js
+++ b/src/backend/src/CoreModule.js
@@ -367,6 +367,9 @@ const install = async ({ services, app, useapi, modapi }) => {
const { ThreadService } = require('./services/ThreadService');
services.registerService('thread', ThreadService);
+
+ const { ChatAPIService } = require('./services/ChatAPIService');
+ services.registerService('__chat-api', ChatAPIService);
}
const install_legacy = async ({ services }) => {
diff --git a/src/backend/src/services/ChatAPIService.js b/src/backend/src/services/ChatAPIService.js
new file mode 100644
index 000000000..943ead8b4
--- /dev/null
+++ b/src/backend/src/services/ChatAPIService.js
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024-present Puter Technologies Inc.
+ *
+ * This file is part of Puter.
+ *
+ * Puter is free software: you can redistribute it and/or modify
+ * 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 .
+ */
+
+// METADATA // {"ai-commented":{"service":"claude"}}
+const { Endpoint } = require("../util/expressutil");
+const BaseService = require("./BaseService");
+const APIError = require("../api/APIError");
+
+/**
+* @class ChatAPIService
+* @extends BaseService
+* @description Service class that handles public (unauthenticated) API endpoints for AI chat functionality.
+* This service provides endpoints for retrieving available AI chat models without requiring authentication.
+*/
+class ChatAPIService extends BaseService {
+ static MODULES = {
+ express: require('express'),
+ };
+
+ /**
+ * Installs routes for chat API endpoints into the Express app
+ * @param {Object} _ Unused parameter
+ * @param {Object} options Installation options
+ * @param {Express} options.app Express application instance to install routes on
+ * @returns {Promise}
+ */
+ async ['__on_install.routes'] (_, { app }) {
+ // Create a router for chat API endpoints
+ const router = (() => {
+ const require = this.require;
+ const express = require('express');
+ return express.Router();
+ })();
+
+ // Register the router with the Express app
+ app.use('/puterai/chat', router);
+
+ // Install endpoints
+ this.install_chat_endpoints_({ router });
+ }
+
+ /**
+ * Installs chat API endpoints on the provided router
+ * @param {Object} options Options object
+ * @param {express.Router} options.router Express router to install endpoints on
+ * @private
+ */
+ install_chat_endpoints_ ({ router }) {
+ // Endpoint to list available AI chat models
+ Endpoint({
+ route: '/models',
+ methods: ['GET'],
+ handler: async (req, res) => {
+ try {
+ // Use SUService to access AIChatService as system user
+ const svc_su = this.services.get('su');
+ const models = await svc_su.sudo(async () => {
+ const svc_aiChat = this.services.get('ai-chat');
+ // Return the simple model list which contains basic model information
+ return svc_aiChat.simple_model_list;
+ });
+
+ // Return the list of models
+ res.json({ models });
+ } catch (error) {
+ this.log.error('Error fetching models:', error);
+ throw APIError.create('internal_server_error');
+ }
+ }
+ }).attach(router);
+
+ // Endpoint to get detailed information about available AI chat models
+ Endpoint({
+ route: '/models/details',
+ methods: ['GET'],
+ handler: async (req, res) => {
+ try {
+ // Use SUService to access AIChatService as system user
+ const svc_su = this.services.get('su');
+ const models = await svc_su.sudo(async () => {
+ const svc_aiChat = this.services.get('ai-chat');
+ // Return the detailed model list which includes cost and capability information
+ return svc_aiChat.detail_model_list;
+ });
+
+ // Return the detailed list of models
+ res.json({ models });
+ } catch (error) {
+ this.log.error('Error fetching model details:', error);
+ throw APIError.create('internal_server_error');
+ }
+ }
+ }).attach(router);
+ }
+}
+
+module.exports = {
+ ChatAPIService,
+};
\ No newline at end of file
diff --git a/src/backend/src/services/ChatAPIService.test.js b/src/backend/src/services/ChatAPIService.test.js
new file mode 100644
index 000000000..c6fe92596
--- /dev/null
+++ b/src/backend/src/services/ChatAPIService.test.js
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2024-present Puter Technologies Inc.
+ *
+ * This file is part of Puter.
+ *
+ * Puter is free software: you can redistribute it and/or modify
+ * 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 { ChatAPIService } = require('./ChatAPIService');
+
+describe('ChatAPIService', () => {
+ let chatApiService;
+ let mockServices;
+ let mockRouter;
+ let mockApp;
+ let mockSUService;
+ let mockAIChatService;
+ let mockEndpoint;
+ let mockReq;
+ let mockRes;
+
+ beforeEach(() => {
+ // Mock AIChatService
+ mockAIChatService = {
+ simple_model_list: ['model1', 'model2'],
+ detail_model_list: [
+ { id: 'model1', name: 'Model 1', cost: { input: 1, output: 2 } },
+ { id: 'model2', name: 'Model 2', cost: { input: 3, output: 4 } }
+ ]
+ };
+
+ // Mock SUService
+ mockSUService = {
+ sudo: jest.fn().mockImplementation(async (callback) => {
+ if (typeof callback === 'function') {
+ return await callback();
+ }
+ return await mockSUService.sudo.mockImplementation(async (cb) => await cb());
+ })
+ };
+
+ // Mock services
+ mockServices = {
+ get: jest.fn().mockImplementation((serviceName) => {
+ if (serviceName === 'su') return mockSUService;
+ if (serviceName === 'ai-chat') return mockAIChatService;
+ return null;
+ })
+ };
+
+ // Mock router and app
+ mockRouter = {
+ use: jest.fn(),
+ get: jest.fn(),
+ post: jest.fn()
+ };
+ mockApp = {
+ use: jest.fn()
+ };
+
+ // Mock Endpoint function
+ mockEndpoint = jest.fn().mockReturnValue({
+ attach: jest.fn()
+ });
+
+ // Mock request and response
+ mockReq = {};
+ mockRes = {
+ json: jest.fn()
+ };
+
+ // Setup ChatAPIService
+ chatApiService = new ChatAPIService();
+ chatApiService.services = mockServices;
+ chatApiService.log = {
+ error: jest.fn()
+ };
+
+ // Mock the require function
+ chatApiService.require = jest.fn().mockImplementation((module) => {
+ if (module === 'express') return { Router: () => mockRouter };
+ return require(module);
+ });
+ });
+
+ describe('install_chat_endpoints_', () => {
+ it('should attach models endpoint to router', () => {
+ // Setup
+ global.Endpoint = mockEndpoint;
+
+ // Execute
+ chatApiService.install_chat_endpoints_({ router: mockRouter });
+
+ // Verify
+ expect(mockEndpoint).toHaveBeenCalledWith(expect.objectContaining({
+ route: '/models',
+ methods: ['GET']
+ }));
+ });
+
+ it('should attach models/details endpoint to router', () => {
+ // Setup
+ global.Endpoint = mockEndpoint;
+
+ // Execute
+ chatApiService.install_chat_endpoints_({ router: mockRouter });
+
+ // Verify
+ expect(mockEndpoint).toHaveBeenCalledWith(expect.objectContaining({
+ route: '/models/details',
+ methods: ['GET']
+ }));
+ });
+ });
+
+ describe('/models endpoint', () => {
+ it('should return list of models', async () => {
+ // Setup
+ global.Endpoint = mockEndpoint;
+ chatApiService.install_chat_endpoints_({ router: mockRouter });
+
+ // Get the handler function
+ const handler = mockEndpoint.mock.calls[0][0].handler;
+
+ // Execute
+ await handler(mockReq, mockRes);
+
+ // Verify
+ expect(mockSUService.sudo).toHaveBeenCalled();
+ expect(mockRes.json).toHaveBeenCalledWith({
+ models: mockAIChatService.simple_model_list
+ });
+ });
+ });
+
+ describe('/models/details endpoint', () => {
+ it('should return detailed list of models', async () => {
+ // Setup
+ global.Endpoint = mockEndpoint;
+ chatApiService.install_chat_endpoints_({ router: mockRouter });
+
+ // Get the handler function
+ const handler = mockEndpoint.mock.calls[1][0].handler;
+
+ // Execute
+ await handler(mockReq, mockRes);
+
+ // Verify
+ expect(mockSUService.sudo).toHaveBeenCalled();
+ expect(mockRes.json).toHaveBeenCalledWith({
+ models: mockAIChatService.detail_model_list
+ });
+ });
+ });
+});
\ No newline at end of file