free-antigravity-cli 1.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/LICENSE +201 -0
- package/README.md +142 -0
- package/dist/chat.d.ts +2 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +212 -0
- package/dist/chat.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +216 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +29 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +125 -0
- package/dist/config.js.map +1 -0
- package/dist/crypto.d.ts +5 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +93 -0
- package/dist/crypto.js.map +1 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +10 -0
- package/dist/logger.js.map +1 -0
- package/dist/proxy/modelUtils.d.ts +31 -0
- package/dist/proxy/modelUtils.d.ts.map +1 -0
- package/dist/proxy/modelUtils.js +55 -0
- package/dist/proxy/modelUtils.js.map +1 -0
- package/dist/proxy/registry.d.ts +40 -0
- package/dist/proxy/registry.d.ts.map +1 -0
- package/dist/proxy/registry.js +176 -0
- package/dist/proxy/registry.js.map +1 -0
- package/dist/proxy/shared.d.ts +39 -0
- package/dist/proxy/shared.d.ts.map +1 -0
- package/dist/proxy/shared.js +74 -0
- package/dist/proxy/shared.js.map +1 -0
- package/dist/proxy/translators/anthropic.d.ts +119 -0
- package/dist/proxy/translators/anthropic.d.ts.map +1 -0
- package/dist/proxy/translators/anthropic.js +273 -0
- package/dist/proxy/translators/anthropic.js.map +1 -0
- package/dist/proxy/translators/google.d.ts +86 -0
- package/dist/proxy/translators/google.d.ts.map +1 -0
- package/dist/proxy/translators/google.js +111 -0
- package/dist/proxy/translators/google.js.map +1 -0
- package/dist/proxy/translators/ollama.d.ts +27 -0
- package/dist/proxy/translators/ollama.d.ts.map +1 -0
- package/dist/proxy/translators/ollama.js +82 -0
- package/dist/proxy/translators/ollama.js.map +1 -0
- package/dist/proxy/translators/openai.d.ts +132 -0
- package/dist/proxy/translators/openai.d.ts.map +1 -0
- package/dist/proxy/translators/openai.js +396 -0
- package/dist/proxy/translators/openai.js.map +1 -0
- package/dist/proxy/translators/utils.d.ts +60 -0
- package/dist/proxy/translators/utils.d.ts.map +1 -0
- package/dist/proxy/translators/utils.js +504 -0
- package/dist/proxy/translators/utils.js.map +1 -0
- package/dist/proxy.d.ts +22 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +576 -0
- package/dist/proxy.js.map +1 -0
- package/dist/schemaValidator.d.ts +50 -0
- package/dist/schemaValidator.d.ts.map +1 -0
- package/dist/schemaValidator.js +208 -0
- package/dist/schemaValidator.js.map +1 -0
- package/install.cmd +33 -0
- package/package.json +46 -0
- package/src/chat.ts +184 -0
- package/src/cli.ts +184 -0
- package/src/config.ts +99 -0
- package/src/crypto.ts +49 -0
- package/src/logger.ts +8 -0
- package/src/proxy/modelUtils.ts +86 -0
- package/src/proxy/registry.ts +196 -0
- package/src/proxy/shared.ts +102 -0
- package/src/proxy/translators/anthropic.ts +420 -0
- package/src/proxy/translators/google.ts +162 -0
- package/src/proxy/translators/ollama.ts +88 -0
- package/src/proxy/translators/openai.ts +556 -0
- package/src/proxy/translators/utils.ts +552 -0
- package/src/proxy.ts +573 -0
- package/src/schemaValidator.ts +215 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Validator Module for Antigravity Proxy
|
|
3
|
+
*
|
|
4
|
+
* Validates API response schemas and data integrity for:
|
|
5
|
+
* - Gemini GenerateContentResponse format
|
|
6
|
+
* - Custom model configuration objects
|
|
7
|
+
* - Streaming chunk structure
|
|
8
|
+
*
|
|
9
|
+
* This module provides runtime validation to catch malformed
|
|
10
|
+
* responses before they reach the frontend, improving stability
|
|
11
|
+
* and preventing cryptic UI errors.
|
|
12
|
+
*/
|
|
13
|
+
interface ValidationResult {
|
|
14
|
+
valid: boolean;
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Validates a Gemini candidate object structure.
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateCandidate(candidate: unknown): ValidationResult;
|
|
21
|
+
/**
|
|
22
|
+
* Validates a Gemini GenerateContentResponse structure (top-level).
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateGenerateContentResponse(response: unknown): ValidationResult;
|
|
25
|
+
/**
|
|
26
|
+
* Validates a Cloud Code envelope (wrapper with response, traceId, metadata).
|
|
27
|
+
*/
|
|
28
|
+
export declare function validateCloudCodeEnvelope(envelope: unknown): ValidationResult;
|
|
29
|
+
/**
|
|
30
|
+
* Validates a custom model configuration object.
|
|
31
|
+
*/
|
|
32
|
+
export declare function validateCustomModel(model: unknown): ValidationResult;
|
|
33
|
+
/**
|
|
34
|
+
* Validates an array of custom model configurations.
|
|
35
|
+
*/
|
|
36
|
+
export declare function validateCustomModels(models: unknown): ValidationResult;
|
|
37
|
+
/**
|
|
38
|
+
* Validates a Gemini request body (contents, generationConfig, tools, etc.)
|
|
39
|
+
*/
|
|
40
|
+
export declare function validateGenerateContentRequest(body: unknown): ValidationResult;
|
|
41
|
+
/**
|
|
42
|
+
* Validates an OpenAI-style streaming chunk.
|
|
43
|
+
*/
|
|
44
|
+
export declare function validateOpenAiChunk(chunk: unknown): ValidationResult;
|
|
45
|
+
/**
|
|
46
|
+
* Validates an Anthropic streaming event.
|
|
47
|
+
*/
|
|
48
|
+
export declare function validateAnthropicEvent(event: unknown): ValidationResult;
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=schemaValidator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemaValidator.d.ts","sourceRoot":"","sources":["../src/schemaValidator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,UAAU,gBAAgB;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,OAAO,GAAG,gBAAgB,CAmBtE;AAED;;GAEG;AACH,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,OAAO,GAAG,gBAAgB,CAkBnF;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,OAAO,GAAG,gBAAgB,CAS7E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAoDpE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,gBAAgB,CAWtE;AAED;;GAEG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,OAAO,GAAG,gBAAgB,CAkB9E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CASpE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAsBvE"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Schema Validator Module for Antigravity Proxy
|
|
4
|
+
*
|
|
5
|
+
* Validates API response schemas and data integrity for:
|
|
6
|
+
* - Gemini GenerateContentResponse format
|
|
7
|
+
* - Custom model configuration objects
|
|
8
|
+
* - Streaming chunk structure
|
|
9
|
+
*
|
|
10
|
+
* This module provides runtime validation to catch malformed
|
|
11
|
+
* responses before they reach the frontend, improving stability
|
|
12
|
+
* and preventing cryptic UI errors.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.validateCandidate = validateCandidate;
|
|
16
|
+
exports.validateGenerateContentResponse = validateGenerateContentResponse;
|
|
17
|
+
exports.validateCloudCodeEnvelope = validateCloudCodeEnvelope;
|
|
18
|
+
exports.validateCustomModel = validateCustomModel;
|
|
19
|
+
exports.validateCustomModels = validateCustomModels;
|
|
20
|
+
exports.validateGenerateContentRequest = validateGenerateContentRequest;
|
|
21
|
+
exports.validateOpenAiChunk = validateOpenAiChunk;
|
|
22
|
+
exports.validateAnthropicEvent = validateAnthropicEvent;
|
|
23
|
+
/**
|
|
24
|
+
* Validates a Gemini candidate object structure.
|
|
25
|
+
*/
|
|
26
|
+
function validateCandidate(candidate) {
|
|
27
|
+
if (!candidate || typeof candidate !== 'object') {
|
|
28
|
+
return { valid: false, error: 'Candidate is null or not an object' };
|
|
29
|
+
}
|
|
30
|
+
const c = candidate;
|
|
31
|
+
if (!c.content || typeof c.content !== 'object') {
|
|
32
|
+
return { valid: false, error: 'Candidate missing content object' };
|
|
33
|
+
}
|
|
34
|
+
const content = c.content;
|
|
35
|
+
if (!Array.isArray(content.parts)) {
|
|
36
|
+
return { valid: false, error: 'Candidate content.parts is not an array' };
|
|
37
|
+
}
|
|
38
|
+
if (content.role && content.role !== 'model') {
|
|
39
|
+
return { valid: false, error: `Unexpected candidate role: ${content.role}` };
|
|
40
|
+
}
|
|
41
|
+
if (c.finishReason && typeof c.finishReason !== 'string') {
|
|
42
|
+
return { valid: false, error: 'finishReason must be a string' };
|
|
43
|
+
}
|
|
44
|
+
return { valid: true };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Validates a Gemini GenerateContentResponse structure (top-level).
|
|
48
|
+
*/
|
|
49
|
+
function validateGenerateContentResponse(response) {
|
|
50
|
+
if (!response || typeof response !== 'object') {
|
|
51
|
+
return { valid: false, error: 'Response is null or not an object' };
|
|
52
|
+
}
|
|
53
|
+
const r = response;
|
|
54
|
+
if (!Array.isArray(r.candidates)) {
|
|
55
|
+
return { valid: false, error: 'Response candidates is not an array' };
|
|
56
|
+
}
|
|
57
|
+
if (r.candidates.length === 0) {
|
|
58
|
+
return { valid: false, error: 'Response has no candidates' };
|
|
59
|
+
}
|
|
60
|
+
for (let i = 0; i < r.candidates.length; i++) {
|
|
61
|
+
const candidateResult = validateCandidate(r.candidates[i]);
|
|
62
|
+
if (!candidateResult.valid) {
|
|
63
|
+
return { valid: false, error: `Candidate[${i}]: ${candidateResult.error}` };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { valid: true };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Validates a Cloud Code envelope (wrapper with response, traceId, metadata).
|
|
70
|
+
*/
|
|
71
|
+
function validateCloudCodeEnvelope(envelope) {
|
|
72
|
+
if (!envelope || typeof envelope !== 'object') {
|
|
73
|
+
return { valid: false, error: 'Envelope is null or not an object' };
|
|
74
|
+
}
|
|
75
|
+
const e = envelope;
|
|
76
|
+
if (!e.response || typeof e.response !== 'object') {
|
|
77
|
+
return { valid: false, error: 'Envelope missing response object' };
|
|
78
|
+
}
|
|
79
|
+
return validateGenerateContentResponse(e.response);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Validates a custom model configuration object.
|
|
83
|
+
*/
|
|
84
|
+
function validateCustomModel(model) {
|
|
85
|
+
if (!model || typeof model !== 'object') {
|
|
86
|
+
return { valid: false, error: 'Model is null or not an object' };
|
|
87
|
+
}
|
|
88
|
+
const m = model;
|
|
89
|
+
const required = ['name', 'provider', 'apiUrl'];
|
|
90
|
+
for (const field of required) {
|
|
91
|
+
if (!m[field] || typeof m[field] !== 'string') {
|
|
92
|
+
return { valid: false, error: `Missing or invalid required field: ${field}` };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const name = m.name;
|
|
96
|
+
// Validate model name format: should start with "models/" or be a valid path
|
|
97
|
+
if (!name.startsWith('models/') && !name.includes('/')) {
|
|
98
|
+
return { valid: false, error: 'Model name must start with "models/"' };
|
|
99
|
+
}
|
|
100
|
+
const provider = m.provider;
|
|
101
|
+
// Validate provider is one of the supported types
|
|
102
|
+
const validProviders = ['openai', 'anthropic', 'google', 'ollama', 'custom', 'openrouter'];
|
|
103
|
+
if (!validProviders.includes(provider)) {
|
|
104
|
+
return { valid: false, error: `Unsupported provider: ${provider}. Must be one of: ${validProviders.join(', ')}` };
|
|
105
|
+
}
|
|
106
|
+
const apiUrl = m.apiUrl;
|
|
107
|
+
// Validate API URL format
|
|
108
|
+
try {
|
|
109
|
+
const url = new URL(apiUrl);
|
|
110
|
+
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
111
|
+
return { valid: false, error: 'API URL must use http or https protocol' };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
return { valid: false, error: `Invalid API URL: ${e.message}` };
|
|
116
|
+
}
|
|
117
|
+
// Validate optional fields
|
|
118
|
+
if (m.externalModelName && typeof m.externalModelName !== 'string') {
|
|
119
|
+
return { valid: false, error: 'externalModelName must be a string' };
|
|
120
|
+
}
|
|
121
|
+
if (m.displayName && typeof m.displayName !== 'string') {
|
|
122
|
+
return { valid: false, error: 'displayName must be a string' };
|
|
123
|
+
}
|
|
124
|
+
if (m.apiKey && typeof m.apiKey !== 'string') {
|
|
125
|
+
return { valid: false, error: 'apiKey must be a string' };
|
|
126
|
+
}
|
|
127
|
+
if (m.allowUnauthorized !== undefined && typeof m.allowUnauthorized !== 'boolean') {
|
|
128
|
+
return { valid: false, error: 'allowUnauthorized must be a boolean' };
|
|
129
|
+
}
|
|
130
|
+
return { valid: true };
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Validates an array of custom model configurations.
|
|
134
|
+
*/
|
|
135
|
+
function validateCustomModels(models) {
|
|
136
|
+
if (!Array.isArray(models)) {
|
|
137
|
+
return { valid: false, error: 'Models must be an array' };
|
|
138
|
+
}
|
|
139
|
+
for (let i = 0; i < models.length; i++) {
|
|
140
|
+
const result = validateCustomModel(models[i]);
|
|
141
|
+
if (!result.valid) {
|
|
142
|
+
return { valid: false, error: `Model[${i}]: ${result.error}` };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { valid: true };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Validates a Gemini request body (contents, generationConfig, tools, etc.)
|
|
149
|
+
*/
|
|
150
|
+
function validateGenerateContentRequest(body) {
|
|
151
|
+
if (!body || typeof body !== 'object') {
|
|
152
|
+
return { valid: false, error: 'Body is null or not an object' };
|
|
153
|
+
}
|
|
154
|
+
const b = body;
|
|
155
|
+
if (!Array.isArray(b.contents) || b.contents.length === 0) {
|
|
156
|
+
return { valid: false, error: 'Request must have non-empty contents array' };
|
|
157
|
+
}
|
|
158
|
+
if (b.systemInstruction && typeof b.systemInstruction !== 'object') {
|
|
159
|
+
return { valid: false, error: 'systemInstruction must be an object' };
|
|
160
|
+
}
|
|
161
|
+
if (b.generationConfig && typeof b.generationConfig !== 'object') {
|
|
162
|
+
return { valid: false, error: 'generationConfig must be an object' };
|
|
163
|
+
}
|
|
164
|
+
if (b.tools && !Array.isArray(b.tools)) {
|
|
165
|
+
return { valid: false, error: 'tools must be an array' };
|
|
166
|
+
}
|
|
167
|
+
return { valid: true };
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Validates an OpenAI-style streaming chunk.
|
|
171
|
+
*/
|
|
172
|
+
function validateOpenAiChunk(chunk) {
|
|
173
|
+
if (!chunk || typeof chunk !== 'object') {
|
|
174
|
+
return { valid: false, error: 'Chunk is null or not an object' };
|
|
175
|
+
}
|
|
176
|
+
const c = chunk;
|
|
177
|
+
if (!Array.isArray(c.choices)) {
|
|
178
|
+
return { valid: false, error: 'Chunk choices is not an array' };
|
|
179
|
+
}
|
|
180
|
+
return { valid: true };
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Validates an Anthropic streaming event.
|
|
184
|
+
*/
|
|
185
|
+
function validateAnthropicEvent(event) {
|
|
186
|
+
if (!event || typeof event !== 'object') {
|
|
187
|
+
return { valid: false, error: 'Event is null or not an object' };
|
|
188
|
+
}
|
|
189
|
+
const e = event;
|
|
190
|
+
if (!e.type || typeof e.type !== 'string') {
|
|
191
|
+
return { valid: false, error: 'Event missing type field' };
|
|
192
|
+
}
|
|
193
|
+
const validTypes = [
|
|
194
|
+
'message_start',
|
|
195
|
+
'content_block_start',
|
|
196
|
+
'content_block_delta',
|
|
197
|
+
'content_block_stop',
|
|
198
|
+
'message_delta',
|
|
199
|
+
'message_stop',
|
|
200
|
+
'ping',
|
|
201
|
+
'error',
|
|
202
|
+
];
|
|
203
|
+
if (!validTypes.includes(e.type)) {
|
|
204
|
+
return { valid: false, error: `Unknown event type: ${e.type}` };
|
|
205
|
+
}
|
|
206
|
+
return { valid: true };
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=schemaValidator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemaValidator.js","sourceRoot":"","sources":["../src/schemaValidator.ts"],"names":[],"mappings":"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"}
|
package/install.cmd
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
echo.
|
|
3
|
+
echo Free Antigravity CLI - Community Edition
|
|
4
|
+
echo ========================================
|
|
5
|
+
echo.
|
|
6
|
+
echo Installing...
|
|
7
|
+
echo.
|
|
8
|
+
|
|
9
|
+
REM Check if Node.js is installed
|
|
10
|
+
where node >nul 2>nul
|
|
11
|
+
if %ERRORLEVEL% neq 0 (
|
|
12
|
+
echo ERROR: Node.js is required but not found.
|
|
13
|
+
echo Download from https://nodejs.org
|
|
14
|
+
exit /b 1
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
REM Install globally via npm
|
|
18
|
+
call npm install -g free-antigravity-cli
|
|
19
|
+
|
|
20
|
+
if %ERRORLEVEL% neq 0 (
|
|
21
|
+
echo.
|
|
22
|
+
echo Direct install failed. Trying without global...
|
|
23
|
+
call npm install -g free-antigravity-cli --force
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
echo.
|
|
27
|
+
echo Installation complete!
|
|
28
|
+
echo.
|
|
29
|
+
echo Try it out: antigravity chat
|
|
30
|
+
echo.
|
|
31
|
+
echo Or add a model: antigravity models add
|
|
32
|
+
echo.
|
|
33
|
+
pause
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "free-antigravity-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Open-source community CLI for Antigravity - supports custom AI models (OpenAI, Anthropic, Ollama, OpenRouter, Google AI Studio) alongside Gemini",
|
|
5
|
+
"homepage": "https://github.com/vahapogut/free-antigravity-cli",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Vahap Ogut",
|
|
8
|
+
"email": "vahap.ogut@outlook.com"
|
|
9
|
+
},
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"main": "dist/cli.js",
|
|
12
|
+
"bin": {
|
|
13
|
+
"antigravity": "./dist/cli.js",
|
|
14
|
+
"free-antigravity": "./dist/cli.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "npx tsc",
|
|
18
|
+
"dev": "npx ts-node src/cli.ts",
|
|
19
|
+
"start": "node dist/cli.js",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"antigravity",
|
|
24
|
+
"cli",
|
|
25
|
+
"ai",
|
|
26
|
+
"openai",
|
|
27
|
+
"anthropic",
|
|
28
|
+
"claude",
|
|
29
|
+
"gemini",
|
|
30
|
+
"ollama",
|
|
31
|
+
"chatbot",
|
|
32
|
+
"community",
|
|
33
|
+
"open-source"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"chalk": "^4.1.2",
|
|
37
|
+
"commander": "^12.1.0",
|
|
38
|
+
"inquirer": "^8.2.6",
|
|
39
|
+
"ora": "^5.4.1"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/inquirer": "^8.2.10",
|
|
43
|
+
"@types/node": "^20.14.0",
|
|
44
|
+
"typescript": "^5.5.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/chat.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive chat REPL - streaming AI chat in the terminal.
|
|
3
|
+
*/
|
|
4
|
+
import * as readline from 'readline';
|
|
5
|
+
import * as http from 'http';
|
|
6
|
+
import { startProxy, getProxyPort, loadCustomModels, generateModelPlaceholderId, toSlug, CustomModel } from './proxy';
|
|
7
|
+
import { loadModels, CustomModelEntry } from './config';
|
|
8
|
+
|
|
9
|
+
function toProxyModel(m: CustomModelEntry): CustomModel {
|
|
10
|
+
return { ...m, displayName: m.displayName || m.name, description: m.description || '', _slug: undefined };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function getModels(): Promise<string[]> {
|
|
14
|
+
const custom = loadModels();
|
|
15
|
+
return custom.map((m) => m.displayName || m.name);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function selectModel(): Promise<string> {
|
|
19
|
+
const models = await getModels();
|
|
20
|
+
console.log('\nAvailable models:');
|
|
21
|
+
models.forEach((m, i) => console.log(` ${i + 1}. ${m}`));
|
|
22
|
+
console.log(' 0. Enter model name manually');
|
|
23
|
+
|
|
24
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
rl.question('\nSelect model (number): ', (answer) => {
|
|
27
|
+
rl.close();
|
|
28
|
+
const idx = parseInt(answer, 10) - 1;
|
|
29
|
+
if (idx >= 0 && idx < models.length) resolve(models[idx]);
|
|
30
|
+
else resolve('');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function startChat(model?: string): Promise<void> {
|
|
36
|
+
// Start proxy
|
|
37
|
+
let proxyPort: number;
|
|
38
|
+
try { proxyPort = await startProxy(); }
|
|
39
|
+
catch { console.error('Failed to start proxy. Is port 50999 in use?'); process.exit(1); }
|
|
40
|
+
|
|
41
|
+
console.log(`
|
|
42
|
+
╔══════════════════════════════════════╗
|
|
43
|
+
║ Free Antigravity CLI v1.0.0 ║
|
|
44
|
+
║ Community Edition ║
|
|
45
|
+
╚══════════════════════════════════════╝
|
|
46
|
+
Proxy: http://127.0.0.1:${proxyPort}
|
|
47
|
+
`);
|
|
48
|
+
|
|
49
|
+
if (!model) model = await selectModel();
|
|
50
|
+
if (!model) { console.log('No model selected. Exiting.'); process.exit(0); }
|
|
51
|
+
|
|
52
|
+
const customModels = loadModels();
|
|
53
|
+
const selectedEntry = customModels.find((m) => (m.displayName || m.name) === model);
|
|
54
|
+
if (!selectedEntry) {
|
|
55
|
+
console.log(`Model "${model}" not configured. Use "antigravity models add" first.`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const selectedModel = toProxyModel(selectedEntry);
|
|
60
|
+
console.log(`Using: ${selectedModel.displayName} (${selectedModel.provider})`);
|
|
61
|
+
console.log('Type /help for commands, /quit to exit.\n');
|
|
62
|
+
|
|
63
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: '\n> ' });
|
|
64
|
+
|
|
65
|
+
let conversationHistory: { role: string; parts: { text?: string }[] }[] = [];
|
|
66
|
+
|
|
67
|
+
rl.on('line', async (line) => {
|
|
68
|
+
const input = line.trim();
|
|
69
|
+
|
|
70
|
+
if (input === '/quit' || input === '/exit') {
|
|
71
|
+
console.log('Goodbye!');
|
|
72
|
+
rl.close();
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (input === '/help') {
|
|
77
|
+
console.log('Commands: /quit, /help, /clear (clear history), /model (switch model)');
|
|
78
|
+
rl.prompt();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (input === '/clear') {
|
|
83
|
+
conversationHistory = [];
|
|
84
|
+
console.log('Conversation cleared.');
|
|
85
|
+
rl.prompt();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (input === '/model') {
|
|
90
|
+
const newModel = await selectModel();
|
|
91
|
+
if (newModel) {
|
|
92
|
+
const m = customModels.find((x) => (x.displayName || x.name) === newModel);
|
|
93
|
+
if (m) { console.log(`Switched to ${m.displayName}`); }
|
|
94
|
+
}
|
|
95
|
+
rl.prompt();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!input) { rl.prompt(); return; }
|
|
100
|
+
|
|
101
|
+
// Build Gemini request
|
|
102
|
+
const geminiBody = {
|
|
103
|
+
model: selectedModel.name,
|
|
104
|
+
contents: [
|
|
105
|
+
...conversationHistory,
|
|
106
|
+
{ role: 'user', parts: [{ text: input }] },
|
|
107
|
+
],
|
|
108
|
+
generationConfig: { temperature: 0.7, maxOutputTokens: 4096 },
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Send to proxy
|
|
112
|
+
const placeholderId = generateModelPlaceholderId(selectedModel);
|
|
113
|
+
const slug = toSlug(selectedModel);
|
|
114
|
+
|
|
115
|
+
const postData = JSON.stringify({
|
|
116
|
+
request: geminiBody,
|
|
117
|
+
model: slug,
|
|
118
|
+
project: 'free-antigravity-cli',
|
|
119
|
+
requestId: Date.now().toString(),
|
|
120
|
+
requestType: 'CHAT',
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const options: http.RequestOptions = {
|
|
124
|
+
hostname: '127.0.0.1',
|
|
125
|
+
port: proxyPort,
|
|
126
|
+
path: '/v1internal:streamGenerateContent?alt=sse',
|
|
127
|
+
method: 'POST',
|
|
128
|
+
headers: {
|
|
129
|
+
'Content-Type': 'application/json',
|
|
130
|
+
'Content-Length': Buffer.byteLength(postData),
|
|
131
|
+
'Accept': 'text/event-stream',
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const req = http.request(options, (res) => {
|
|
136
|
+
let buffer = '';
|
|
137
|
+
let fullResponse = '';
|
|
138
|
+
|
|
139
|
+
res.on('data', (chunk: Buffer) => {
|
|
140
|
+
buffer += chunk.toString('utf-8');
|
|
141
|
+
const lines = buffer.split('\n');
|
|
142
|
+
buffer = lines.pop() || '';
|
|
143
|
+
|
|
144
|
+
for (const line of lines) {
|
|
145
|
+
const trimmed = line.trim();
|
|
146
|
+
if (!trimmed || !trimmed.startsWith('data: ')) continue;
|
|
147
|
+
try {
|
|
148
|
+
const data = JSON.parse(trimmed.substring(6));
|
|
149
|
+
const candidates = data.response?.candidates || [];
|
|
150
|
+
for (const c of candidates) {
|
|
151
|
+
const parts = c.content?.parts || [];
|
|
152
|
+
for (const p of parts) {
|
|
153
|
+
if (p.text && !p.thought) {
|
|
154
|
+
process.stdout.write(p.text);
|
|
155
|
+
fullResponse += p.text;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch { /* partial chunk */ }
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
res.on('end', () => {
|
|
164
|
+
console.log();
|
|
165
|
+
conversationHistory.push({ role: 'user', parts: [{ text: input }] });
|
|
166
|
+
conversationHistory.push({ role: 'model', parts: [{ text: fullResponse }] });
|
|
167
|
+
// Keep history manageable
|
|
168
|
+
if (conversationHistory.length > 20) conversationHistory = conversationHistory.slice(-20);
|
|
169
|
+
rl.prompt();
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
req.on('error', (err) => {
|
|
174
|
+
console.error(`\nConnection error: ${err.message}`);
|
|
175
|
+
console.error('Is the proxy running? Try: antigravity proxy');
|
|
176
|
+
rl.prompt();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
req.write(postData);
|
|
180
|
+
req.end();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
rl.prompt();
|
|
184
|
+
}
|