portos-ai-toolkit 0.1.0

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.
@@ -0,0 +1,120 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mkdir, rm, writeFile } from 'fs/promises';
3
+ import { existsSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { createProviderService } from './providers.js';
6
+
7
+ const TEST_DATA_DIR = join(process.cwd(), 'test-data');
8
+
9
+ describe('Provider Service', () => {
10
+ let providerService;
11
+
12
+ beforeEach(async () => {
13
+ // Create test data directory
14
+ if (!existsSync(TEST_DATA_DIR)) {
15
+ await mkdir(TEST_DATA_DIR, { recursive: true });
16
+ }
17
+
18
+ providerService = createProviderService({
19
+ dataDir: TEST_DATA_DIR,
20
+ providersFile: 'providers.json'
21
+ });
22
+ });
23
+
24
+ afterEach(async () => {
25
+ // Clean up test data
26
+ if (existsSync(TEST_DATA_DIR)) {
27
+ await rm(TEST_DATA_DIR, { recursive: true });
28
+ }
29
+ });
30
+
31
+ it('should create a provider', async () => {
32
+ const provider = await providerService.createProvider({
33
+ name: 'Test Provider',
34
+ type: 'cli',
35
+ command: 'test',
36
+ args: ['--version']
37
+ });
38
+
39
+ expect(provider).toBeDefined();
40
+ expect(provider.id).toBe('test-provider');
41
+ expect(provider.name).toBe('Test Provider');
42
+ expect(provider.type).toBe('cli');
43
+ });
44
+
45
+ it('should get all providers', async () => {
46
+ await providerService.createProvider({
47
+ name: 'Test Provider 1',
48
+ type: 'cli',
49
+ command: 'test1'
50
+ });
51
+
52
+ await providerService.createProvider({
53
+ name: 'Test Provider 2',
54
+ type: 'api',
55
+ endpoint: 'https://api.example.com'
56
+ });
57
+
58
+ const { providers } = await providerService.getAllProviders();
59
+ expect(providers).toHaveLength(2);
60
+ });
61
+
62
+ it('should set active provider', async () => {
63
+ const newProvider = await providerService.createProvider({
64
+ name: 'Test Provider',
65
+ type: 'cli',
66
+ command: 'test'
67
+ });
68
+
69
+ const active = await providerService.setActiveProvider(newProvider.id);
70
+ expect(active).toBeDefined();
71
+ expect(active.id).toBe(newProvider.id);
72
+
73
+ const activeProvider = await providerService.getActiveProvider();
74
+ expect(activeProvider.id).toBe(newProvider.id);
75
+ });
76
+
77
+ it('should update a provider', async () => {
78
+ const newProvider = await providerService.createProvider({
79
+ name: 'Test Provider',
80
+ type: 'cli',
81
+ command: 'test'
82
+ });
83
+
84
+ const updated = await providerService.updateProvider(newProvider.id, {
85
+ command: 'updated-test'
86
+ });
87
+
88
+ expect(updated.command).toBe('updated-test');
89
+ });
90
+
91
+ it('should delete a provider', async () => {
92
+ const newProvider = await providerService.createProvider({
93
+ name: 'Test Provider',
94
+ type: 'cli',
95
+ command: 'test'
96
+ });
97
+
98
+ const deleted = await providerService.deleteProvider(newProvider.id);
99
+ expect(deleted).toBe(true);
100
+
101
+ const retrieved = await providerService.getProviderById(newProvider.id);
102
+ expect(retrieved).toBeNull();
103
+ });
104
+
105
+ it('should throw error for duplicate provider', async () => {
106
+ await providerService.createProvider({
107
+ name: 'Test Provider',
108
+ type: 'cli',
109
+ command: 'test'
110
+ });
111
+
112
+ await expect(
113
+ providerService.createProvider({
114
+ name: 'Test Provider',
115
+ type: 'cli',
116
+ command: 'test'
117
+ })
118
+ ).rejects.toThrow('Provider with this ID already exists');
119
+ });
120
+ });
@@ -0,0 +1,96 @@
1
+ import { Router } from 'express';
2
+
3
+ /**
4
+ * Create prompts routes
5
+ */
6
+ export function createPromptsRoutes(promptsService, options = {}) {
7
+ const router = Router();
8
+ const { asyncHandler = (fn) => fn } = options;
9
+
10
+ // GET /prompts/stages - Get all stages
11
+ router.get('/stages', asyncHandler(async (req, res) => {
12
+ const stages = promptsService.getStages();
13
+ res.json(stages);
14
+ }));
15
+
16
+ // GET /prompts/stages/:name - Get specific stage
17
+ router.get('/stages/:name', asyncHandler(async (req, res) => {
18
+ const stage = promptsService.getStage(req.params.name);
19
+
20
+ if (!stage) {
21
+ return res.status(404).json({ error: 'Stage not found' });
22
+ }
23
+
24
+ const template = await promptsService.getStageTemplate(req.params.name);
25
+ res.json({ ...stage, template });
26
+ }));
27
+
28
+ // PUT /prompts/stages/:name - Update stage
29
+ router.put('/stages/:name', asyncHandler(async (req, res) => {
30
+ const { config, template } = req.body;
31
+
32
+ if (config) {
33
+ await promptsService.updateStageConfig(req.params.name, config);
34
+ }
35
+
36
+ if (template) {
37
+ await promptsService.updateStageTemplate(req.params.name, template);
38
+ }
39
+
40
+ const updated = promptsService.getStage(req.params.name);
41
+ const updatedTemplate = await promptsService.getStageTemplate(req.params.name);
42
+
43
+ res.json({ ...updated, template: updatedTemplate });
44
+ }));
45
+
46
+ // POST /prompts/stages/:name/preview - Preview stage with test data
47
+ router.post('/stages/:name/preview', asyncHandler(async (req, res) => {
48
+ const preview = await promptsService.previewPrompt(req.params.name, req.body);
49
+ res.json({ preview });
50
+ }));
51
+
52
+ // GET /prompts/variables - Get all variables
53
+ router.get('/variables', asyncHandler(async (req, res) => {
54
+ const variables = promptsService.getVariables();
55
+ res.json(variables);
56
+ }));
57
+
58
+ // GET /prompts/variables/:key - Get specific variable
59
+ router.get('/variables/:key', asyncHandler(async (req, res) => {
60
+ const variable = promptsService.getVariable(req.params.key);
61
+
62
+ if (!variable) {
63
+ return res.status(404).json({ error: 'Variable not found' });
64
+ }
65
+
66
+ res.json(variable);
67
+ }));
68
+
69
+ // POST /prompts/variables - Create variable
70
+ router.post('/variables', asyncHandler(async (req, res) => {
71
+ const { key, ...data } = req.body;
72
+
73
+ if (!key) {
74
+ return res.status(400).json({ error: 'Variable key is required' });
75
+ }
76
+
77
+ await promptsService.createVariable(key, data);
78
+ const created = promptsService.getVariable(key);
79
+ res.status(201).json(created);
80
+ }));
81
+
82
+ // PUT /prompts/variables/:key - Update variable
83
+ router.put('/variables/:key', asyncHandler(async (req, res) => {
84
+ await promptsService.updateVariable(req.params.key, req.body);
85
+ const updated = promptsService.getVariable(req.params.key);
86
+ res.json(updated);
87
+ }));
88
+
89
+ // DELETE /prompts/variables/:key - Delete variable
90
+ router.delete('/variables/:key', asyncHandler(async (req, res) => {
91
+ await promptsService.deleteVariable(req.params.key);
92
+ res.status(204).send();
93
+ }));
94
+
95
+ return router;
96
+ }
@@ -0,0 +1,105 @@
1
+ import { Router } from 'express';
2
+
3
+ /**
4
+ * Create providers routes
5
+ */
6
+ export function createProvidersRoutes(providerService, options = {}) {
7
+ const router = Router();
8
+ const { asyncHandler = (fn) => fn } = options;
9
+
10
+ // GET /providers - List all providers
11
+ router.get('/', asyncHandler(async (req, res) => {
12
+ const data = await providerService.getAllProviders();
13
+ res.json(data);
14
+ }));
15
+
16
+ // GET /providers/active - Get active provider
17
+ router.get('/active', asyncHandler(async (req, res) => {
18
+ const provider = await providerService.getActiveProvider();
19
+ res.json(provider);
20
+ }));
21
+
22
+ // PUT /providers/active - Set active provider
23
+ router.put('/active', asyncHandler(async (req, res) => {
24
+ const { id } = req.body;
25
+ if (!id) {
26
+ return res.status(400).json({ error: 'Provider ID required' });
27
+ }
28
+
29
+ const provider = await providerService.setActiveProvider(id);
30
+
31
+ if (!provider) {
32
+ return res.status(404).json({ error: 'Provider not found' });
33
+ }
34
+
35
+ res.json(provider);
36
+ }));
37
+
38
+ // GET /providers/:id - Get provider by ID
39
+ router.get('/:id', asyncHandler(async (req, res) => {
40
+ const provider = await providerService.getProviderById(req.params.id);
41
+
42
+ if (!provider) {
43
+ return res.status(404).json({ error: 'Provider not found' });
44
+ }
45
+
46
+ res.json(provider);
47
+ }));
48
+
49
+ // POST /providers - Create new provider
50
+ router.post('/', asyncHandler(async (req, res) => {
51
+ const { name, type } = req.body;
52
+
53
+ if (!name) {
54
+ return res.status(400).json({ error: 'Name is required' });
55
+ }
56
+
57
+ if (!type || !['cli', 'api'].includes(type)) {
58
+ return res.status(400).json({ error: 'Type must be "cli" or "api"' });
59
+ }
60
+
61
+ const provider = await providerService.createProvider(req.body);
62
+ res.status(201).json(provider);
63
+ }));
64
+
65
+ // PUT /providers/:id - Update provider
66
+ router.put('/:id', asyncHandler(async (req, res) => {
67
+ const provider = await providerService.updateProvider(req.params.id, req.body);
68
+
69
+ if (!provider) {
70
+ return res.status(404).json({ error: 'Provider not found' });
71
+ }
72
+
73
+ res.json(provider);
74
+ }));
75
+
76
+ // DELETE /providers/:id - Delete provider
77
+ router.delete('/:id', asyncHandler(async (req, res) => {
78
+ const deleted = await providerService.deleteProvider(req.params.id);
79
+
80
+ if (!deleted) {
81
+ return res.status(404).json({ error: 'Provider not found' });
82
+ }
83
+
84
+ res.status(204).send();
85
+ }));
86
+
87
+ // POST /providers/:id/test - Test provider connectivity
88
+ router.post('/:id/test', asyncHandler(async (req, res) => {
89
+ const result = await providerService.testProvider(req.params.id);
90
+ res.json(result);
91
+ }));
92
+
93
+ // POST /providers/:id/refresh-models - Refresh models for API provider
94
+ router.post('/:id/refresh-models', asyncHandler(async (req, res) => {
95
+ const provider = await providerService.refreshProviderModels(req.params.id);
96
+
97
+ if (!provider) {
98
+ return res.status(404).json({ error: 'Provider not found or not an API type' });
99
+ }
100
+
101
+ res.json(provider);
102
+ }));
103
+
104
+ return router;
105
+ }
@@ -0,0 +1,157 @@
1
+ import { Router } from 'express';
2
+
3
+ /**
4
+ * Create runs routes
5
+ */
6
+ export function createRunsRoutes(runnerService, options = {}) {
7
+ const router = Router();
8
+ const { asyncHandler = (fn) => fn, io = null } = options;
9
+
10
+ // GET /runs - List runs
11
+ router.get('/', asyncHandler(async (req, res) => {
12
+ const limit = parseInt(req.query.limit) || 50;
13
+ const offset = parseInt(req.query.offset) || 0;
14
+ const source = req.query.source || 'all';
15
+
16
+ const result = await runnerService.listRuns(limit, offset, source);
17
+ res.json(result);
18
+ }));
19
+
20
+ // POST /runs - Create and execute a new run
21
+ router.post('/', asyncHandler(async (req, res) => {
22
+ const { providerId, model, prompt, workspacePath, workspaceName, timeout, screenshots } = req.body;
23
+ console.log(`🚀 POST /runs - provider: ${providerId}, model: ${model}, workspace: ${workspaceName}`);
24
+
25
+ if (!providerId) {
26
+ return res.status(400).json({ error: 'providerId is required' });
27
+ }
28
+
29
+ if (!prompt) {
30
+ return res.status(400).json({ error: 'prompt is required' });
31
+ }
32
+
33
+ const runData = await runnerService.createRun({
34
+ providerId,
35
+ model,
36
+ prompt,
37
+ workspacePath,
38
+ workspaceName,
39
+ timeout,
40
+ screenshots
41
+ });
42
+
43
+ const { runId, provider, metadata, timeout: effectiveTimeout } = runData;
44
+ console.log(`🚀 Run created: ${runId}, provider type: ${provider.type}`);
45
+
46
+ // Execute based on provider type
47
+ if (provider.type === 'cli') {
48
+ runnerService.executeCliRun(
49
+ runId,
50
+ provider,
51
+ prompt,
52
+ workspacePath,
53
+ (data) => {
54
+ io?.emit(`run:${runId}:data`, data);
55
+ },
56
+ (finalMetadata) => {
57
+ console.log(`✅ Run complete: ${runId}, success: ${finalMetadata.success}`);
58
+ io?.emit(`run:${runId}:complete`, finalMetadata);
59
+ },
60
+ effectiveTimeout
61
+ );
62
+ } else if (provider.type === 'api') {
63
+ runnerService.executeApiRun(
64
+ runId,
65
+ provider,
66
+ model,
67
+ prompt,
68
+ workspacePath,
69
+ screenshots,
70
+ (data) => {
71
+ io?.emit(`run:${runId}:data`, data);
72
+ },
73
+ (finalMetadata) => {
74
+ io?.emit(`run:${runId}:complete`, finalMetadata);
75
+ }
76
+ );
77
+ }
78
+
79
+ // Return immediately with run ID
80
+ res.status(202).json({
81
+ runId,
82
+ status: 'started',
83
+ metadata
84
+ });
85
+ }));
86
+
87
+ // GET /runs/:id - Get run metadata
88
+ router.get('/:id', asyncHandler(async (req, res) => {
89
+ const metadata = await runnerService.getRun(req.params.id);
90
+
91
+ if (!metadata) {
92
+ return res.status(404).json({ error: 'Run not found' });
93
+ }
94
+
95
+ const isActive = await runnerService.isRunActive(req.params.id);
96
+ res.json({
97
+ ...metadata,
98
+ isActive
99
+ });
100
+ }));
101
+
102
+ // GET /runs/:id/output - Get run output
103
+ router.get('/:id/output', asyncHandler(async (req, res) => {
104
+ const output = await runnerService.getRunOutput(req.params.id);
105
+
106
+ if (output === null) {
107
+ return res.status(404).json({ error: 'Run not found' });
108
+ }
109
+
110
+ res.type('text/plain').send(output);
111
+ }));
112
+
113
+ // GET /runs/:id/prompt - Get run prompt
114
+ router.get('/:id/prompt', asyncHandler(async (req, res) => {
115
+ const prompt = await runnerService.getRunPrompt(req.params.id);
116
+
117
+ if (prompt === null) {
118
+ return res.status(404).json({ error: 'Run not found' });
119
+ }
120
+
121
+ res.type('text/plain').send(prompt);
122
+ }));
123
+
124
+ // POST /runs/:id/stop - Stop a running execution
125
+ router.post('/:id/stop', asyncHandler(async (req, res) => {
126
+ const stopped = await runnerService.stopRun(req.params.id);
127
+
128
+ if (!stopped) {
129
+ return res.status(404).json({ error: 'Run not found or not active' });
130
+ }
131
+
132
+ res.json({ success: true });
133
+ }));
134
+
135
+ // DELETE /runs/:id - Delete a run
136
+ router.delete('/:id', asyncHandler(async (req, res) => {
137
+ const deleted = await runnerService.deleteRun(req.params.id);
138
+
139
+ if (!deleted) {
140
+ return res.status(404).json({ error: 'Run not found' });
141
+ }
142
+
143
+ res.status(204).send();
144
+ }));
145
+
146
+ // DELETE /runs/failed - Delete all failed runs
147
+ router.delete('/', asyncHandler(async (req, res) => {
148
+ if (req.query.filter !== 'failed') {
149
+ return res.status(400).json({ error: 'Only filter=failed is supported for bulk delete' });
150
+ }
151
+
152
+ const deletedCount = await runnerService.deleteFailedRuns();
153
+ res.json({ deletedCount });
154
+ }));
155
+
156
+ return router;
157
+ }