berget 1.3.1 → 2.0.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.
- package/.env.example +5 -0
- package/.github/workflows/publish.yml +56 -0
- package/.github/workflows/test.yml +38 -0
- package/AGENTS.md +184 -0
- package/README.md +177 -38
- package/TODO.md +2 -0
- package/blog-post.md +176 -0
- package/dist/index.js +11 -8
- package/dist/package.json +14 -3
- package/dist/src/commands/api-keys.js +4 -2
- package/dist/src/commands/chat.js +182 -23
- package/dist/src/commands/code.js +1424 -0
- package/dist/src/commands/index.js +2 -0
- package/dist/src/constants/command-structure.js +12 -0
- package/dist/src/schemas/opencode-schema.json +1121 -0
- package/dist/src/services/chat-service.js +10 -10
- package/dist/src/services/cluster-service.js +1 -1
- package/dist/src/utils/default-api-key.js +2 -2
- package/dist/src/utils/env-manager.js +86 -0
- package/dist/src/utils/error-handler.js +10 -3
- package/dist/src/utils/markdown-renderer.js +4 -4
- package/dist/src/utils/opencode-validator.js +122 -0
- package/dist/src/utils/token-manager.js +2 -2
- package/dist/tests/commands/chat.test.js +109 -0
- package/dist/tests/commands/code.test.js +414 -0
- package/dist/tests/utils/env-manager.test.js +148 -0
- package/dist/tests/utils/opencode-validator.test.js +103 -0
- package/dist/vitest.config.js +9 -0
- package/index.ts +67 -32
- package/opencode.json +182 -0
- package/package.json +14 -3
- package/src/client.ts +20 -20
- package/src/commands/api-keys.ts +93 -60
- package/src/commands/auth.ts +4 -2
- package/src/commands/billing.ts +6 -3
- package/src/commands/chat.ts +291 -97
- package/src/commands/clusters.ts +2 -2
- package/src/commands/code.ts +1696 -0
- package/src/commands/index.ts +2 -0
- package/src/commands/models.ts +3 -3
- package/src/commands/users.ts +2 -2
- package/src/constants/command-structure.ts +112 -58
- package/src/schemas/opencode-schema.json +991 -0
- package/src/services/api-key-service.ts +1 -1
- package/src/services/auth-service.ts +27 -25
- package/src/services/chat-service.ts +37 -44
- package/src/services/cluster-service.ts +5 -5
- package/src/services/collaborator-service.ts +3 -3
- package/src/services/flux-service.ts +2 -2
- package/src/services/helm-service.ts +2 -2
- package/src/services/kubectl-service.ts +3 -6
- package/src/types/api.d.ts +1032 -1010
- package/src/types/json.d.ts +3 -3
- package/src/utils/default-api-key.ts +54 -42
- package/src/utils/env-manager.ts +98 -0
- package/src/utils/error-handler.ts +24 -15
- package/src/utils/logger.ts +12 -12
- package/src/utils/markdown-renderer.ts +18 -18
- package/src/utils/opencode-validator.ts +134 -0
- package/src/utils/token-manager.ts +35 -23
- package/tests/commands/chat.test.ts +129 -0
- package/tests/commands/code.test.ts +505 -0
- package/tests/utils/env-manager.test.ts +199 -0
- package/tests/utils/opencode-validator.test.ts +118 -0
- package/tsconfig.json +8 -8
- package/vitest.config.ts +8 -0
- package/-27b-it +0 -0
package/blog-post.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# OpenCode + Berget AI: Den ultimata AI-kodassistensen för svenska företag
|
|
2
|
+
|
|
3
|
+
I en värld där AI-drivna kodassistenter blir standard, står svenska företag inför ett kritiskt val: hur man balanserar produktivitetsvinster med data-suveränitet och efterlevnad. Med integrationen mellan OpenCode och Berget AI får du nu det bästa av två världar – en kraftfull, open source AI-kodassistent med svensk datainfrastruktur.
|
|
4
|
+
|
|
5
|
+
## Vad är OpenCode?
|
|
6
|
+
|
|
7
|
+
OpenCode är en open source AI-kodassistent byggd för terminalen. Till skillnad från många kommersiella alternativ ger OpenCode dig full kontroll över din kod och dina data. Med över 26 000 GitHub-stjärnor och 200 000+ aktiva utvecklare varje månad har det blivit ett av de mest populära verktygen för AI-assisterad programmering.
|
|
8
|
+
|
|
9
|
+
**Nödvändiga funktioner i OpenCode:**
|
|
10
|
+
|
|
11
|
+
- 🎯 **Native TUI** – Responsivt terminalgränssnitt som fungerar i din befintliga workflow
|
|
12
|
+
- 🔄 **Multi-session** – Köra flera agenter parallellt på samma projekt
|
|
13
|
+
- 🔗 **Share links** – Dela sessioner för kodgranskning och felsökning
|
|
14
|
+
- 🤖 **Any model** – Stöd för 75+ LLM-providers via Models.dev
|
|
15
|
+
- 🛠️ **LSP enabled** – Automatisk laddning av rätt Language Servers för LLM:en
|
|
16
|
+
|
|
17
|
+
## Berget AI: Svensk datainfrastruktur
|
|
18
|
+
|
|
19
|
+
Berget AI är en svensk AI-infrastrukturleverantör som säkerställer att din data aldrig lämnar Sverige. Detta är särskilt viktigt för företag som hanterar känslig information, personuppgifter eller skyddad kod.
|
|
20
|
+
|
|
21
|
+
**Fördelar med Berget AI:**
|
|
22
|
+
|
|
23
|
+
- 🇸🇪 **Data-suveränitet** – All data bearbetas inom Sverige
|
|
24
|
+
- ⚖️ **GDPR-kompatibilitet** – Full efterlevnad med EU:s dataskyddslagar
|
|
25
|
+
- 🔒 **Säkerhet** – Industriell säkerhet med kryptering och isolering
|
|
26
|
+
- 🏛️ **Reglering** – Enklare efterlevnad av svenska och EU-regleringar
|
|
27
|
+
- 🚀 **Prestanda** – Låg latens med svensk infrastruktur
|
|
28
|
+
|
|
29
|
+
## Varför kombinationen är oslagbar
|
|
30
|
+
|
|
31
|
+
### 1. Data som aldrig lämnar Sverige
|
|
32
|
+
|
|
33
|
+
När du använder OpenCode med Berget AI förblir all din kod – inklusive API-nycklar, konfigurationer och känslig affärslogik – inom Sveriges gränser. Detta eliminerar risken för dataexponering som kan uppstå med internationella molntjänster.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Initiera ditt projekt med AI-assistent
|
|
37
|
+
berget code init
|
|
38
|
+
|
|
39
|
+
# Din kod och data förblir alltid i Sverige ✅
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Förenklad reglering och efterlevnad
|
|
43
|
+
|
|
44
|
+
För svenska företag, särskilt inom finans, vård och offentlig sektor, är efterlevnad avgörande. Med Berget AI får du:
|
|
45
|
+
|
|
46
|
+
- **GDPR-säker hantering** av personuppgifter i koden
|
|
47
|
+
- **Klassificerad information** hanteras säkert
|
|
48
|
+
- **Revisionsbara spår** av all AI-interaktion
|
|
49
|
+
- **Lokal lagring** av konfigurationer och API-nycklar
|
|
50
|
+
|
|
51
|
+
### 3. Säker hantering av känslig kod
|
|
52
|
+
|
|
53
|
+
Många företag arbetar med kod som innehåller:
|
|
54
|
+
|
|
55
|
+
- API-nycklar och hemligheter
|
|
56
|
+
- Affärskritisk algoritmlogik
|
|
57
|
+
- Känslig kunddata
|
|
58
|
+
- Proprietära systemintegrationer
|
|
59
|
+
|
|
60
|
+
Med OpenCode + Berget AI kan du använda AI-assistens för:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Refaktorera känslig kod utan att lämna Sverige
|
|
64
|
+
berget code run "Refaktorera denna autentiseringsmodul"
|
|
65
|
+
|
|
66
|
+
# Få hjälp med API-integrationer säkert
|
|
67
|
+
berget code run "Optimera denna databasanslutning"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 4. Projektspecifika API-nycklar
|
|
71
|
+
|
|
72
|
+
Varje projekt får sin egen unika API-nyckel som skapas automatiskt:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"model": "berget/deepseek-r1",
|
|
77
|
+
"apiKey": "projekt-specifik-nyckel",
|
|
78
|
+
"projectName": "mitt-svenska-projekt",
|
|
79
|
+
"provider": "berget",
|
|
80
|
+
"created": "2025-01-01T00:00:00.000Z",
|
|
81
|
+
"version": "1.0.0"
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Detta ger:
|
|
86
|
+
|
|
87
|
+
- 🔐 **Isolering** mellan projekt
|
|
88
|
+
- 📊 **Spårbarhet** av användning
|
|
89
|
+
- 🔄 **Enkel rotation** av nycklar
|
|
90
|
+
- 🎯 **Granulär kontroll** av åtkomst
|
|
91
|
+
|
|
92
|
+
## Kom igång på 2 minuter
|
|
93
|
+
|
|
94
|
+
### Steg 1: Installera OpenCode
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
curl -fsSL https://opencode.ai/install | bash
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Steg 2: Initiera ditt projekt
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
cd ditt-projekt
|
|
104
|
+
berget code init
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Steg 3: Starta AI-assistenen
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
berget code run
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Det var allt! Du har nu en fullfjädrad AI-kodassistent med svensk datainfrastruktur.
|
|
114
|
+
|
|
115
|
+
## Användningsfall för svenska företag
|
|
116
|
+
|
|
117
|
+
### Finans & Försäkring
|
|
118
|
+
|
|
119
|
+
- Refaktorera transaktionslogik med full säkerhet
|
|
120
|
+
- Hjälp med compliance-kod (KYC, AML)
|
|
121
|
+
- Optimera riskberäkningsalgoritmer
|
|
122
|
+
|
|
123
|
+
### Vård & Hälsa
|
|
124
|
+
|
|
125
|
+
- Utveckla patientdata-system med GDPR-säkerhet
|
|
126
|
+
- Hjälp med medicinsk kod och integrationer
|
|
127
|
+
- Säker hantering av journalsystem
|
|
128
|
+
|
|
129
|
+
### Offentlig Sektor
|
|
130
|
+
|
|
131
|
+
- Utveckla e-tjänster med svensk data
|
|
132
|
+
- Hjälp med myndighetsintegrationer
|
|
133
|
+
- Säker hantering av medborgardata
|
|
134
|
+
|
|
135
|
+
### Industri & Tillverkning
|
|
136
|
+
|
|
137
|
+
- Optimera PLC-kod och styrsystem
|
|
138
|
+
- Hjälp med IoT-integrationer
|
|
139
|
+
- Säker utveckling av produktionssystem
|
|
140
|
+
|
|
141
|
+
## Teknisk integration
|
|
142
|
+
|
|
143
|
+
OpenCode + Berget AI använder den kraftfulla **GLM-4.6** modellen (för närvarande deepseek-r1) som är optimerad för kodning:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Se vilka modeller som finns tillgängliga
|
|
147
|
+
berget models list
|
|
148
|
+
|
|
149
|
+
# Använd specifik modell
|
|
150
|
+
berget code run --model berget/glm-4-6 "Hjälp mig optimera denna funktion"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Framtiden för svensk AI-utveckling
|
|
154
|
+
|
|
155
|
+
Kombinationen av OpenCode och Berget AI representerar nästa generations AI-utveckling för svenska företag:
|
|
156
|
+
|
|
157
|
+
1. **Produktivitet** – AI-assisterad kodning utan kompromisser
|
|
158
|
+
2. **Säkerhet** – Full kontroll över din data och kod
|
|
159
|
+
3. **Efterlevnad** – Inbyggt stöd för svenska och EU-regleringar
|
|
160
|
+
4. **Flexibilitet** – Open source med frihet att välja modeller och verktyg
|
|
161
|
+
5. **Skalbarhet** – Från små projekt till enterprise-nivå
|
|
162
|
+
|
|
163
|
+
## Sammanfattning
|
|
164
|
+
|
|
165
|
+
OpenCode + Berget AI är inte bara ett verktyg – det är en strategisk lösning för svenska företag som vill leda inom AI-driven utveckling utan att kompromissa med säkerhet och efterlevnad.
|
|
166
|
+
|
|
167
|
+
Med svensk datainfrastruktur, projektspecifika API-nycklar och en open source AI-assistent får du det bästa av två världar: global AI-kompetens med lokal dataskydd.
|
|
168
|
+
|
|
169
|
+
**Redo att transformera din kodning med svensk AI?**
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Börja idag
|
|
173
|
+
berget code init
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
_Din kod, din data, ditt land – nu med AI-superkrafter._
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,8 @@ commander_1.program
|
|
|
19
19
|
| |_/ / __/ | | (_| | __/ |_ | | | |_| |_
|
|
20
20
|
\\____/ \\___|_| \\__, |\\___|\\_\\_ \\_| |_/\\___/
|
|
21
21
|
__/ |
|
|
22
|
-
|___/ AI on European terms
|
|
22
|
+
|___/ AI on European terms
|
|
23
|
+
Version: ${package_json_1.version}`)
|
|
23
24
|
.version(package_json_1.version, '-v, --version')
|
|
24
25
|
.option('--local', 'Use local API endpoint (hidden)', false)
|
|
25
26
|
.option('--debug', 'Enable debug output', false);
|
|
@@ -34,19 +35,21 @@ if (process.argv.length <= 2) {
|
|
|
34
35
|
console.log(chalk_1.default.blue(` ${chalk_1.default.bold('berget auth login')} - Log in to Berget`));
|
|
35
36
|
console.log(chalk_1.default.blue(` ${chalk_1.default.bold('berget models list')} - List available AI models`));
|
|
36
37
|
console.log(chalk_1.default.blue(` ${chalk_1.default.bold('berget chat run')} - Start a chat session`));
|
|
38
|
+
console.log(chalk_1.default.blue(` ${chalk_1.default.bold('berget code init')} - Initialize AI coding assistant`));
|
|
37
39
|
console.log(chalk_1.default.blue(` ${chalk_1.default.bold('berget api-keys list')} - List your API keys`));
|
|
38
40
|
console.log(chalk_1.default.blue(`\nRun ${chalk_1.default.bold('berget --help')} for a complete list of commands.`));
|
|
39
41
|
}
|
|
40
42
|
// Add helpful suggestions for common command mistakes
|
|
41
43
|
const commonMistakes = {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
login: 'auth login',
|
|
45
|
+
logout: 'auth logout',
|
|
46
|
+
whoami: 'auth whoami',
|
|
45
47
|
'list-models': 'models list',
|
|
46
48
|
'list-keys': 'api-keys list',
|
|
47
49
|
'create-key': 'api-keys create',
|
|
48
50
|
'list-clusters': 'clusters list',
|
|
49
|
-
|
|
51
|
+
usage: 'billing usage',
|
|
52
|
+
init: 'code init',
|
|
50
53
|
};
|
|
51
54
|
// Add error handler for unknown commands
|
|
52
55
|
commander_1.program.on('command:*', (operands) => {
|
|
@@ -58,11 +61,11 @@ commander_1.program.on('command:*', (operands) => {
|
|
|
58
61
|
}
|
|
59
62
|
else {
|
|
60
63
|
// Try to find similar commands
|
|
61
|
-
const availableCommands = commander_1.program.commands.map(cmd => cmd.name());
|
|
62
|
-
const similarCommands = availableCommands.filter(cmd => cmd.includes(unknownCommand) || unknownCommand.includes(cmd));
|
|
64
|
+
const availableCommands = commander_1.program.commands.map((cmd) => cmd.name());
|
|
65
|
+
const similarCommands = availableCommands.filter((cmd) => cmd.includes(unknownCommand) || unknownCommand.includes(cmd));
|
|
63
66
|
if (similarCommands.length > 0) {
|
|
64
67
|
console.log(chalk_1.default.yellow('Similar commands:'));
|
|
65
|
-
similarCommands.forEach(cmd => {
|
|
68
|
+
similarCommands.forEach((cmd) => {
|
|
66
69
|
console.log(chalk_1.default.yellow(` ${chalk_1.default.bold(`berget ${cmd}`)}`));
|
|
67
70
|
});
|
|
68
71
|
}
|
package/dist/package.json
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "berget",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"berget": "dist/index.js"
|
|
7
7
|
},
|
|
8
8
|
"private": false,
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
9
12
|
"scripts": {
|
|
10
13
|
"start": "node --import tsx ./index.ts --local",
|
|
11
14
|
"login": "node --import tsx ./index.ts --local auth login",
|
|
12
15
|
"logout": "node --import tsx ./index.ts --local auth logout",
|
|
13
16
|
"whoami": "node --import tsx ./index.ts --local auth whoami",
|
|
14
17
|
"build": "tsc",
|
|
18
|
+
"test": "vitest",
|
|
19
|
+
"test:run": "vitest run",
|
|
15
20
|
"prepublishOnly": "npm run build",
|
|
16
21
|
"generate-types": "openapi-typescript https://api.berget.ai/openapi.json -o src/types/api.d.ts"
|
|
17
22
|
},
|
|
@@ -19,21 +24,27 @@
|
|
|
19
24
|
"license": "MIT",
|
|
20
25
|
"description": "This is a cli command for interacting with the AI infrastructure provider Berget",
|
|
21
26
|
"devDependencies": {
|
|
27
|
+
"@types/dotenv": "^6.1.1",
|
|
22
28
|
"@types/marked": "^5.0.2",
|
|
23
29
|
"@types/marked-terminal": "^6.1.1",
|
|
24
30
|
"@types/node": "^20.11.20",
|
|
25
31
|
"tsx": "^4.19.3",
|
|
26
|
-
"typescript": "^5.3.3"
|
|
32
|
+
"typescript": "^5.3.3",
|
|
33
|
+
"vitest": "^1.0.0"
|
|
27
34
|
},
|
|
28
35
|
"dependencies": {
|
|
36
|
+
"ajv": "^8.17.1",
|
|
37
|
+
"ajv-formats": "^3.0.1",
|
|
29
38
|
"chalk": "^4.1.2",
|
|
30
39
|
"commander": "^12.0.0",
|
|
40
|
+
"dotenv": "^17.2.3",
|
|
31
41
|
"fs-extra": "^11.3.0",
|
|
32
42
|
"marked": "^9.1.6",
|
|
33
43
|
"marked-terminal": "^6.2.0",
|
|
34
44
|
"open": "^9.1.0",
|
|
35
45
|
"openapi-fetch": "^0.9.1",
|
|
36
46
|
"openapi-typescript": "^6.7.4",
|
|
37
|
-
"readline": "^1.3.0"
|
|
47
|
+
"readline": "^1.3.0",
|
|
48
|
+
"zod": "^4.1.12"
|
|
38
49
|
}
|
|
39
50
|
}
|
|
@@ -46,7 +46,9 @@ function registerApiKeyCommands(program) {
|
|
|
46
46
|
chalk_1.default.dim('LAST USED'));
|
|
47
47
|
console.log(chalk_1.default.dim('─'.repeat(85)));
|
|
48
48
|
keys.forEach((key) => {
|
|
49
|
-
const lastUsed = key.lastUsed
|
|
49
|
+
const lastUsed = key.lastUsed
|
|
50
|
+
? key.lastUsed.substring(0, 10)
|
|
51
|
+
: 'Never';
|
|
50
52
|
const status = key.active
|
|
51
53
|
? chalk_1.default.green('● Active')
|
|
52
54
|
: chalk_1.default.red('● Inactive');
|
|
@@ -227,7 +229,7 @@ function registerApiKeyCommands(program) {
|
|
|
227
229
|
try {
|
|
228
230
|
const apiKeyService = api_key_service_1.ApiKeyService.getInstance();
|
|
229
231
|
const keys = yield apiKeyService.list();
|
|
230
|
-
const selectedKey = keys.find(key => key.id.toString() === id);
|
|
232
|
+
const selectedKey = keys.find((key) => key.id.toString() === id);
|
|
231
233
|
if (!selectedKey) {
|
|
232
234
|
console.error(chalk_1.default.red(`Error: API key with ID ${id} not found`));
|
|
233
235
|
return;
|
|
@@ -8,6 +8,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
12
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
13
|
+
var m = o[Symbol.asyncIterator], i;
|
|
14
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
15
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
16
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
17
|
+
};
|
|
11
18
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
19
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
20
|
};
|
|
@@ -49,14 +56,16 @@ function registerChatCommands(program) {
|
|
|
49
56
|
chat
|
|
50
57
|
.command(command_structure_1.SUBCOMMANDS.CHAT.RUN)
|
|
51
58
|
.description('Run a chat session with a specified model')
|
|
52
|
-
.argument('[model]', 'Model to use (default:
|
|
59
|
+
.argument('[model]', 'Model to use (default: openai/gpt-oss)')
|
|
60
|
+
.argument('[message]', 'Message to send directly (skips interactive mode)')
|
|
53
61
|
.option('-s, --system <message>', 'System message')
|
|
54
62
|
.option('-t, --temperature <temp>', 'Temperature (0-1)', parseFloat)
|
|
55
63
|
.option('-m, --max-tokens <tokens>', 'Maximum tokens to generate', parseInt)
|
|
56
64
|
.option('-k, --api-key <key>', 'API key to use for this chat session')
|
|
57
65
|
.option('--api-key-id <id>', 'ID of the API key to use from your saved keys')
|
|
58
|
-
.option('--stream', '
|
|
59
|
-
.action((options) => __awaiter(this, void 0, void 0, function* () {
|
|
66
|
+
.option('--no-stream', 'Disable streaming (streaming is enabled by default)')
|
|
67
|
+
.action((model, message, options) => __awaiter(this, void 0, void 0, function* () {
|
|
68
|
+
var _a, e_1, _b, _c;
|
|
60
69
|
try {
|
|
61
70
|
const chatService = chat_service_1.ChatService.getInstance();
|
|
62
71
|
// Check if we have an API key or need to get one
|
|
@@ -168,11 +177,6 @@ function registerChatCommands(program) {
|
|
|
168
177
|
return;
|
|
169
178
|
}
|
|
170
179
|
}
|
|
171
|
-
// Set up readline interface for user input
|
|
172
|
-
const rl = readline_1.default.createInterface({
|
|
173
|
-
input: process.stdin,
|
|
174
|
-
output: process.stdout,
|
|
175
|
-
});
|
|
176
180
|
// Prepare messages array
|
|
177
181
|
const messages = [];
|
|
178
182
|
// Add system message if provided
|
|
@@ -182,12 +186,140 @@ function registerChatCommands(program) {
|
|
|
182
186
|
content: options.system,
|
|
183
187
|
});
|
|
184
188
|
}
|
|
189
|
+
// Check if input is being piped in
|
|
190
|
+
let inputMessage = message;
|
|
191
|
+
let stdinContent = '';
|
|
192
|
+
if (!process.stdin.isTTY) {
|
|
193
|
+
// Read from stdin (piped input)
|
|
194
|
+
const chunks = [];
|
|
195
|
+
try {
|
|
196
|
+
for (var _d = true, _e = __asyncValues(process.stdin), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
197
|
+
_c = _f.value;
|
|
198
|
+
_d = false;
|
|
199
|
+
const chunk = _c;
|
|
200
|
+
chunks.push(chunk);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
204
|
+
finally {
|
|
205
|
+
try {
|
|
206
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
207
|
+
}
|
|
208
|
+
finally { if (e_1) throw e_1.error; }
|
|
209
|
+
}
|
|
210
|
+
stdinContent = Buffer.concat(chunks).toString('utf8').trim();
|
|
211
|
+
}
|
|
212
|
+
// Combine stdin content with message if both exist
|
|
213
|
+
if (stdinContent && message) {
|
|
214
|
+
inputMessage = `${stdinContent}\n\n${message}`;
|
|
215
|
+
}
|
|
216
|
+
else if (stdinContent && !message) {
|
|
217
|
+
inputMessage = stdinContent;
|
|
218
|
+
}
|
|
219
|
+
// If a message is provided (either as argument, from stdin, or both), send it directly and exit
|
|
220
|
+
if (inputMessage) {
|
|
221
|
+
// Add user message
|
|
222
|
+
messages.push({
|
|
223
|
+
role: 'user',
|
|
224
|
+
content: inputMessage,
|
|
225
|
+
});
|
|
226
|
+
try {
|
|
227
|
+
// Call the API
|
|
228
|
+
const completionOptions = {
|
|
229
|
+
model: model || 'openai/gpt-oss',
|
|
230
|
+
messages: messages,
|
|
231
|
+
temperature: options.temperature !== undefined ? options.temperature : 0.7,
|
|
232
|
+
max_tokens: options.maxTokens || 4096,
|
|
233
|
+
stream: options.stream !== false,
|
|
234
|
+
};
|
|
235
|
+
// Only add apiKey if it actually exists
|
|
236
|
+
if (apiKey) {
|
|
237
|
+
completionOptions.apiKey = apiKey;
|
|
238
|
+
}
|
|
239
|
+
// Add streaming support (now default)
|
|
240
|
+
if (completionOptions.stream) {
|
|
241
|
+
let assistantResponse = '';
|
|
242
|
+
// Stream the response in real-time
|
|
243
|
+
completionOptions.onChunk = (chunk) => {
|
|
244
|
+
if (chunk.choices &&
|
|
245
|
+
chunk.choices[0] &&
|
|
246
|
+
chunk.choices[0].delta &&
|
|
247
|
+
chunk.choices[0].delta.content) {
|
|
248
|
+
const content = chunk.choices[0].delta.content;
|
|
249
|
+
try {
|
|
250
|
+
process.stdout.write(content);
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
// Handle EPIPE errors gracefully (when pipe is closed)
|
|
254
|
+
if (error.code === 'EPIPE') {
|
|
255
|
+
// Stop streaming if the pipe is closed
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
assistantResponse += content;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
try {
|
|
264
|
+
yield chatService.createCompletion(completionOptions);
|
|
265
|
+
}
|
|
266
|
+
catch (streamError) {
|
|
267
|
+
console.error(chalk_1.default.red('\nStreaming error:'), streamError);
|
|
268
|
+
// Fallback to non-streaming if streaming fails
|
|
269
|
+
console.log(chalk_1.default.yellow('Falling back to non-streaming mode...'));
|
|
270
|
+
completionOptions.stream = false;
|
|
271
|
+
delete completionOptions.onChunk;
|
|
272
|
+
const response = yield chatService.createCompletion(completionOptions);
|
|
273
|
+
if (response &&
|
|
274
|
+
response.choices &&
|
|
275
|
+
response.choices[0] &&
|
|
276
|
+
response.choices[0].message) {
|
|
277
|
+
assistantResponse = response.choices[0].message.content;
|
|
278
|
+
console.log(assistantResponse);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
console.log(); // Add newline at the end
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const response = yield chatService.createCompletion(completionOptions);
|
|
285
|
+
// Check if response has the expected structure
|
|
286
|
+
if (!response ||
|
|
287
|
+
!response.choices ||
|
|
288
|
+
!response.choices[0] ||
|
|
289
|
+
!response.choices[0].message) {
|
|
290
|
+
console.error(chalk_1.default.red('Error: Unexpected response format from API'));
|
|
291
|
+
console.error(chalk_1.default.red('Response:', JSON.stringify(response, null, 2)));
|
|
292
|
+
throw new Error('Unexpected response format from API');
|
|
293
|
+
}
|
|
294
|
+
// Get assistant's response
|
|
295
|
+
const assistantMessage = response.choices[0].message.content;
|
|
296
|
+
// Display the response
|
|
297
|
+
if ((0, markdown_renderer_1.containsMarkdown)(assistantMessage)) {
|
|
298
|
+
console.log((0, markdown_renderer_1.renderMarkdown)(assistantMessage));
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
console.log(assistantMessage);
|
|
302
|
+
}
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
console.error(chalk_1.default.red('Error: Failed to get response'));
|
|
307
|
+
if (error instanceof Error) {
|
|
308
|
+
console.error(chalk_1.default.red(error.message));
|
|
309
|
+
}
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// Set up readline interface for user input (only for interactive mode)
|
|
314
|
+
const rl = readline_1.default.createInterface({
|
|
315
|
+
input: process.stdin,
|
|
316
|
+
output: process.stdout,
|
|
317
|
+
});
|
|
185
318
|
console.log(chalk_1.default.cyan('Chat with Berget AI (type "exit" to quit)'));
|
|
186
319
|
console.log(chalk_1.default.cyan('----------------------------------------'));
|
|
187
320
|
// Start the conversation loop
|
|
188
321
|
const askQuestion = () => {
|
|
189
322
|
rl.question(chalk_1.default.green('You: '), (input) => __awaiter(this, void 0, void 0, function* () {
|
|
190
|
-
var _a;
|
|
191
323
|
// Check if user wants to exit
|
|
192
324
|
if (input.toLowerCase() === 'exit') {
|
|
193
325
|
console.log(chalk_1.default.cyan('Goodbye!'));
|
|
@@ -202,35 +334,64 @@ function registerChatCommands(program) {
|
|
|
202
334
|
try {
|
|
203
335
|
// Call the API
|
|
204
336
|
const completionOptions = {
|
|
205
|
-
model:
|
|
337
|
+
model: model || 'openai/gpt-oss',
|
|
206
338
|
messages: messages,
|
|
207
339
|
temperature: options.temperature !== undefined ? options.temperature : 0.7,
|
|
208
340
|
max_tokens: options.maxTokens || 4096,
|
|
209
|
-
stream: options.stream
|
|
341
|
+
stream: options.stream !== false,
|
|
210
342
|
};
|
|
211
343
|
// Only add apiKey if it actually exists
|
|
212
344
|
if (apiKey) {
|
|
213
345
|
completionOptions.apiKey = apiKey;
|
|
214
346
|
}
|
|
215
|
-
// Add streaming support
|
|
216
|
-
if (
|
|
347
|
+
// Add streaming support (now default)
|
|
348
|
+
if (completionOptions.stream) {
|
|
217
349
|
let assistantResponse = '';
|
|
218
350
|
console.log(chalk_1.default.blue('Assistant: '));
|
|
219
|
-
//
|
|
220
|
-
// since markdown needs the complete text to render properly
|
|
351
|
+
// Stream the response in real-time
|
|
221
352
|
completionOptions.onChunk = (chunk) => {
|
|
222
|
-
if (chunk.choices &&
|
|
353
|
+
if (chunk.choices &&
|
|
354
|
+
chunk.choices[0] &&
|
|
355
|
+
chunk.choices[0].delta &&
|
|
356
|
+
chunk.choices[0].delta.content) {
|
|
223
357
|
const content = chunk.choices[0].delta.content;
|
|
224
|
-
|
|
358
|
+
try {
|
|
359
|
+
process.stdout.write(content);
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
// Handle EPIPE errors gracefully (when pipe is closed)
|
|
363
|
+
if (error.code === 'EPIPE') {
|
|
364
|
+
// Stop streaming if the pipe is closed
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
225
369
|
assistantResponse += content;
|
|
226
370
|
}
|
|
227
371
|
};
|
|
228
|
-
|
|
372
|
+
try {
|
|
373
|
+
yield chatService.createCompletion(completionOptions);
|
|
374
|
+
}
|
|
375
|
+
catch (streamError) {
|
|
376
|
+
console.error(chalk_1.default.red('\nStreaming error:'), streamError);
|
|
377
|
+
// Fallback to non-streaming if streaming fails
|
|
378
|
+
console.log(chalk_1.default.yellow('Falling back to non-streaming mode...'));
|
|
379
|
+
completionOptions.stream = false;
|
|
380
|
+
delete completionOptions.onChunk;
|
|
381
|
+
const response = yield chatService.createCompletion(completionOptions);
|
|
382
|
+
if (response &&
|
|
383
|
+
response.choices &&
|
|
384
|
+
response.choices[0] &&
|
|
385
|
+
response.choices[0].message) {
|
|
386
|
+
assistantResponse = response.choices[0].message.content;
|
|
387
|
+
console.log(assistantResponse);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
229
390
|
console.log('\n');
|
|
230
391
|
// Add assistant response to messages
|
|
231
392
|
messages.push({
|
|
232
393
|
role: 'assistant',
|
|
233
|
-
content: assistantResponse
|
|
394
|
+
content: assistantResponse,
|
|
234
395
|
});
|
|
235
396
|
// Continue the conversation
|
|
236
397
|
askQuestion();
|
|
@@ -343,8 +504,7 @@ function registerChatCommands(program) {
|
|
|
343
504
|
}
|
|
344
505
|
console.log(chalk_1.default.bold('Available Chat Models:'));
|
|
345
506
|
console.log(chalk_1.default.dim('─'.repeat(70)));
|
|
346
|
-
console.log(chalk_1.default.dim('MODEL ID'.padEnd(40)) +
|
|
347
|
-
chalk_1.default.dim('CAPABILITIES'));
|
|
507
|
+
console.log(chalk_1.default.dim('MODEL ID'.padEnd(40)) + chalk_1.default.dim('CAPABILITIES'));
|
|
348
508
|
console.log(chalk_1.default.dim('─'.repeat(70)));
|
|
349
509
|
// Filter to only show active models
|
|
350
510
|
const activeModels = models.data.filter((model) => model.active === true);
|
|
@@ -358,8 +518,7 @@ function registerChatCommands(program) {
|
|
|
358
518
|
capabilities.push('json_mode');
|
|
359
519
|
// Format model ID in Huggingface compatible format (owner/model)
|
|
360
520
|
const modelId = `${model.owned_by.toLowerCase()}/${model.id}`.padEnd(40);
|
|
361
|
-
console.log(modelId +
|
|
362
|
-
capabilities.join(', '));
|
|
521
|
+
console.log(modelId + capabilities.join(', '));
|
|
363
522
|
});
|
|
364
523
|
}
|
|
365
524
|
catch (error) {
|