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,215 @@
|
|
|
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
|
+
|
|
14
|
+
interface ValidationResult {
|
|
15
|
+
valid: boolean;
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validates a Gemini candidate object structure.
|
|
21
|
+
*/
|
|
22
|
+
export function validateCandidate(candidate: unknown): ValidationResult {
|
|
23
|
+
if (!candidate || typeof candidate !== 'object') {
|
|
24
|
+
return { valid: false, error: 'Candidate is null or not an object' };
|
|
25
|
+
}
|
|
26
|
+
const c = candidate as Record<string, unknown>;
|
|
27
|
+
if (!c.content || typeof c.content !== 'object') {
|
|
28
|
+
return { valid: false, error: 'Candidate missing content object' };
|
|
29
|
+
}
|
|
30
|
+
const content = c.content as Record<string, unknown>;
|
|
31
|
+
if (!Array.isArray(content.parts)) {
|
|
32
|
+
return { valid: false, error: 'Candidate content.parts is not an array' };
|
|
33
|
+
}
|
|
34
|
+
if (content.role && content.role !== 'model') {
|
|
35
|
+
return { valid: false, error: `Unexpected candidate role: ${content.role}` };
|
|
36
|
+
}
|
|
37
|
+
if (c.finishReason && typeof c.finishReason !== 'string') {
|
|
38
|
+
return { valid: false, error: 'finishReason must be a string' };
|
|
39
|
+
}
|
|
40
|
+
return { valid: true };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Validates a Gemini GenerateContentResponse structure (top-level).
|
|
45
|
+
*/
|
|
46
|
+
export function validateGenerateContentResponse(response: unknown): ValidationResult {
|
|
47
|
+
if (!response || typeof response !== 'object') {
|
|
48
|
+
return { valid: false, error: 'Response is null or not an object' };
|
|
49
|
+
}
|
|
50
|
+
const r = response as Record<string, unknown>;
|
|
51
|
+
if (!Array.isArray(r.candidates)) {
|
|
52
|
+
return { valid: false, error: 'Response candidates is not an array' };
|
|
53
|
+
}
|
|
54
|
+
if (r.candidates.length === 0) {
|
|
55
|
+
return { valid: false, error: 'Response has no candidates' };
|
|
56
|
+
}
|
|
57
|
+
for (let i = 0; i < r.candidates.length; i++) {
|
|
58
|
+
const candidateResult = validateCandidate(r.candidates[i]);
|
|
59
|
+
if (!candidateResult.valid) {
|
|
60
|
+
return { valid: false, error: `Candidate[${i}]: ${candidateResult.error}` };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { valid: true };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Validates a Cloud Code envelope (wrapper with response, traceId, metadata).
|
|
68
|
+
*/
|
|
69
|
+
export function validateCloudCodeEnvelope(envelope: unknown): ValidationResult {
|
|
70
|
+
if (!envelope || typeof envelope !== 'object') {
|
|
71
|
+
return { valid: false, error: 'Envelope is null or not an object' };
|
|
72
|
+
}
|
|
73
|
+
const e = envelope as Record<string, unknown>;
|
|
74
|
+
if (!e.response || typeof e.response !== 'object') {
|
|
75
|
+
return { valid: false, error: 'Envelope missing response object' };
|
|
76
|
+
}
|
|
77
|
+
return validateGenerateContentResponse(e.response);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Validates a custom model configuration object.
|
|
82
|
+
*/
|
|
83
|
+
export function validateCustomModel(model: unknown): ValidationResult {
|
|
84
|
+
if (!model || typeof model !== 'object') {
|
|
85
|
+
return { valid: false, error: 'Model is null or not an object' };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const m = model as Record<string, unknown>;
|
|
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
|
+
|
|
96
|
+
const name = m.name as string;
|
|
97
|
+
// Validate model name format: should start with "models/" or be a valid path
|
|
98
|
+
if (!name.startsWith('models/') && !name.includes('/')) {
|
|
99
|
+
return { valid: false, error: 'Model name must start with "models/"' };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const provider = m.provider as string;
|
|
103
|
+
// Validate provider is one of the supported types
|
|
104
|
+
const validProviders = ['openai', 'anthropic', 'google', 'ollama', 'custom', 'openrouter'];
|
|
105
|
+
if (!validProviders.includes(provider)) {
|
|
106
|
+
return { valid: false, error: `Unsupported provider: ${provider}. Must be one of: ${validProviders.join(', ')}` };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const apiUrl = m.apiUrl as string;
|
|
110
|
+
// Validate API URL format
|
|
111
|
+
try {
|
|
112
|
+
const url = new URL(apiUrl);
|
|
113
|
+
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
114
|
+
return { valid: false, error: 'API URL must use http or https protocol' };
|
|
115
|
+
}
|
|
116
|
+
} catch (e) {
|
|
117
|
+
return { valid: false, error: `Invalid API URL: ${(e as Error).message}` };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Validate optional fields
|
|
121
|
+
if (m.externalModelName && typeof m.externalModelName !== 'string') {
|
|
122
|
+
return { valid: false, error: 'externalModelName must be a string' };
|
|
123
|
+
}
|
|
124
|
+
if (m.displayName && typeof m.displayName !== 'string') {
|
|
125
|
+
return { valid: false, error: 'displayName must be a string' };
|
|
126
|
+
}
|
|
127
|
+
if (m.apiKey && typeof m.apiKey !== 'string') {
|
|
128
|
+
return { valid: false, error: 'apiKey must be a string' };
|
|
129
|
+
}
|
|
130
|
+
if (m.allowUnauthorized !== undefined && typeof m.allowUnauthorized !== 'boolean') {
|
|
131
|
+
return { valid: false, error: 'allowUnauthorized must be a boolean' };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { valid: true };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Validates an array of custom model configurations.
|
|
139
|
+
*/
|
|
140
|
+
export function validateCustomModels(models: unknown): ValidationResult {
|
|
141
|
+
if (!Array.isArray(models)) {
|
|
142
|
+
return { valid: false, error: 'Models must be an array' };
|
|
143
|
+
}
|
|
144
|
+
for (let i = 0; i < models.length; i++) {
|
|
145
|
+
const result = validateCustomModel(models[i]);
|
|
146
|
+
if (!result.valid) {
|
|
147
|
+
return { valid: false, error: `Model[${i}]: ${result.error}` };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { valid: true };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Validates a Gemini request body (contents, generationConfig, tools, etc.)
|
|
155
|
+
*/
|
|
156
|
+
export function validateGenerateContentRequest(body: unknown): ValidationResult {
|
|
157
|
+
if (!body || typeof body !== 'object') {
|
|
158
|
+
return { valid: false, error: 'Body is null or not an object' };
|
|
159
|
+
}
|
|
160
|
+
const b = body as Record<string, unknown>;
|
|
161
|
+
if (!Array.isArray(b.contents) || b.contents.length === 0) {
|
|
162
|
+
return { valid: false, error: 'Request must have non-empty contents array' };
|
|
163
|
+
}
|
|
164
|
+
if (b.systemInstruction && typeof b.systemInstruction !== 'object') {
|
|
165
|
+
return { valid: false, error: 'systemInstruction must be an object' };
|
|
166
|
+
}
|
|
167
|
+
if (b.generationConfig && typeof b.generationConfig !== 'object') {
|
|
168
|
+
return { valid: false, error: 'generationConfig must be an object' };
|
|
169
|
+
}
|
|
170
|
+
if (b.tools && !Array.isArray(b.tools)) {
|
|
171
|
+
return { valid: false, error: 'tools must be an array' };
|
|
172
|
+
}
|
|
173
|
+
return { valid: true };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Validates an OpenAI-style streaming chunk.
|
|
178
|
+
*/
|
|
179
|
+
export function validateOpenAiChunk(chunk: unknown): ValidationResult {
|
|
180
|
+
if (!chunk || typeof chunk !== 'object') {
|
|
181
|
+
return { valid: false, error: 'Chunk is null or not an object' };
|
|
182
|
+
}
|
|
183
|
+
const c = chunk as Record<string, unknown>;
|
|
184
|
+
if (!Array.isArray(c.choices)) {
|
|
185
|
+
return { valid: false, error: 'Chunk choices is not an array' };
|
|
186
|
+
}
|
|
187
|
+
return { valid: true };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Validates an Anthropic streaming event.
|
|
192
|
+
*/
|
|
193
|
+
export function validateAnthropicEvent(event: unknown): ValidationResult {
|
|
194
|
+
if (!event || typeof event !== 'object') {
|
|
195
|
+
return { valid: false, error: 'Event is null or not an object' };
|
|
196
|
+
}
|
|
197
|
+
const e = event as Record<string, unknown>;
|
|
198
|
+
if (!e.type || typeof e.type !== 'string') {
|
|
199
|
+
return { valid: false, error: 'Event missing type field' };
|
|
200
|
+
}
|
|
201
|
+
const validTypes = [
|
|
202
|
+
'message_start',
|
|
203
|
+
'content_block_start',
|
|
204
|
+
'content_block_delta',
|
|
205
|
+
'content_block_stop',
|
|
206
|
+
'message_delta',
|
|
207
|
+
'message_stop',
|
|
208
|
+
'ping',
|
|
209
|
+
'error',
|
|
210
|
+
];
|
|
211
|
+
if (!validTypes.includes(e.type as string)) {
|
|
212
|
+
return { valid: false, error: `Unknown event type: ${e.type}` };
|
|
213
|
+
}
|
|
214
|
+
return { valid: true };
|
|
215
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|