@almightygpt/core 0.9.1 → 0.9.2
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/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +3 -1
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/defaults.d.ts +34 -0
- package/dist/adapters/defaults.d.ts.map +1 -0
- package/dist/adapters/defaults.js +41 -0
- package/dist/adapters/defaults.js.map +1 -0
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +3 -1
- package/dist/adapters/gemini.js.map +1 -1
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +3 -1
- package/dist/adapters/openai.js.map +1 -1
- package/dist/auth/keychain.d.ts +11 -1
- package/dist/auth/keychain.d.ts.map +1 -1
- package/dist/auth/keychain.js +13 -4
- package/dist/auth/keychain.js.map +1 -1
- package/dist/auth/resolver.d.ts.map +1 -1
- package/dist/auth/resolver.js +19 -3
- package/dist/auth/resolver.js.map +1 -1
- package/dist/auth/types.d.ts +18 -5
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth/types.js +8 -6
- package/dist/auth/types.js.map +1 -1
- package/dist/auth/validator.d.ts +23 -3
- package/dist/auth/validator.d.ts.map +1 -1
- package/dist/auth/validator.js +126 -21
- package/dist/auth/validator.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/adapters/claude.ts +3 -1
- package/src/adapters/defaults.ts +44 -0
- package/src/adapters/gemini.ts +3 -1
- package/src/adapters/openai.ts +3 -1
- package/src/auth/keychain.ts +20 -6
- package/src/auth/resolver.ts +23 -3
- package/src/auth/types.ts +27 -8
- package/src/auth/validator.ts +149 -25
- package/src/index.ts +1 -1
package/src/auth/validator.ts
CHANGED
|
@@ -13,26 +13,41 @@
|
|
|
13
13
|
* - Google: tiny generateContent call against gemini-2.5-flash
|
|
14
14
|
*
|
|
15
15
|
* Cost is fractions of a cent per call. Latency is ~1-3 seconds.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
*
|
|
17
|
+
* **Error handling (per Codex v0.8 P2 #6):** failure responses are
|
|
18
|
+
* parsed into a short user-safe message + status code. We never
|
|
19
|
+
* return the raw provider body to the user — those bodies can include
|
|
20
|
+
* request IDs, billing/account state, org/project details, quota
|
|
21
|
+
* metadata, or other account information that's too noisy for CLI
|
|
22
|
+
* logs. The raw body is available via the `rawBody` field for callers
|
|
23
|
+
* that explicitly want it (e.g. debug mode).
|
|
24
|
+
*
|
|
25
|
+
* **Default models (per Codex v0.8 P2 #5):** sourced from
|
|
26
|
+
* `adapters/defaults.ts` — the single source of truth — so the
|
|
27
|
+
* validator can't drift from the adapters.
|
|
18
28
|
*/
|
|
19
29
|
|
|
20
30
|
import type { ProviderId } from "./types.js";
|
|
31
|
+
import { DEFAULT_MODELS } from "../adapters/defaults.js";
|
|
21
32
|
|
|
22
33
|
export interface ValidationResult {
|
|
23
34
|
ok: boolean;
|
|
24
35
|
/** Provider-reported model used (e.g. "gpt-4o-2024-08-06"). */
|
|
25
36
|
model?: string;
|
|
26
|
-
/**
|
|
37
|
+
/** Short user-safe message — never includes raw provider body. */
|
|
27
38
|
error?: string;
|
|
39
|
+
/** HTTP status code from the provider, if applicable. */
|
|
40
|
+
statusCode?: number;
|
|
41
|
+
/** Latency in ms — useful for `auth status --validate` JSON output. */
|
|
42
|
+
latencyMs?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Raw provider response body. Present iff a request was made and
|
|
45
|
+
* returned non-OK. Callers should NOT surface this to users without
|
|
46
|
+
* explicit debug intent; it may contain account metadata.
|
|
47
|
+
*/
|
|
48
|
+
rawBody?: string;
|
|
28
49
|
}
|
|
29
50
|
|
|
30
|
-
const DEFAULT_VALIDATION_MODELS: Record<ProviderId, string> = {
|
|
31
|
-
openai: "gpt-4o",
|
|
32
|
-
anthropic: "claude-sonnet-4-6",
|
|
33
|
-
google: "gemini-2.5-flash",
|
|
34
|
-
};
|
|
35
|
-
|
|
36
51
|
const VALIDATION_TIMEOUT_MS = 15_000;
|
|
37
52
|
|
|
38
53
|
/**
|
|
@@ -44,25 +59,33 @@ export async function validateKey(
|
|
|
44
59
|
provider: ProviderId,
|
|
45
60
|
key: string,
|
|
46
61
|
): Promise<ValidationResult> {
|
|
62
|
+
const start = Date.now();
|
|
47
63
|
try {
|
|
64
|
+
let result: ValidationResult;
|
|
48
65
|
switch (provider) {
|
|
49
66
|
case "openai":
|
|
50
|
-
|
|
67
|
+
result = await validateOpenAI(key);
|
|
68
|
+
break;
|
|
51
69
|
case "anthropic":
|
|
52
|
-
|
|
70
|
+
result = await validateAnthropic(key);
|
|
71
|
+
break;
|
|
53
72
|
case "google":
|
|
54
|
-
|
|
73
|
+
result = await validateGoogle(key);
|
|
74
|
+
break;
|
|
55
75
|
}
|
|
76
|
+
result.latencyMs = Date.now() - start;
|
|
77
|
+
return result;
|
|
56
78
|
} catch (err) {
|
|
57
79
|
return {
|
|
58
80
|
ok: false,
|
|
59
|
-
error:
|
|
81
|
+
error: friendlyNetworkError(err),
|
|
82
|
+
latencyMs: Date.now() - start,
|
|
60
83
|
};
|
|
61
84
|
}
|
|
62
85
|
}
|
|
63
86
|
|
|
64
87
|
async function validateOpenAI(key: string): Promise<ValidationResult> {
|
|
65
|
-
const model =
|
|
88
|
+
const model = DEFAULT_MODELS.openai;
|
|
66
89
|
const controller = new AbortController();
|
|
67
90
|
const timer = setTimeout(() => controller.abort(), VALIDATION_TIMEOUT_MS);
|
|
68
91
|
try {
|
|
@@ -80,7 +103,13 @@ async function validateOpenAI(key: string): Promise<ValidationResult> {
|
|
|
80
103
|
signal: controller.signal,
|
|
81
104
|
});
|
|
82
105
|
if (!res.ok) {
|
|
83
|
-
|
|
106
|
+
const rawBody = await res.text().catch(() => "");
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
statusCode: res.status,
|
|
110
|
+
error: normalizeOpenAIError(res.status, rawBody),
|
|
111
|
+
rawBody,
|
|
112
|
+
};
|
|
84
113
|
}
|
|
85
114
|
const data = (await res.json()) as { model?: string };
|
|
86
115
|
return { ok: true, model: data.model ?? model };
|
|
@@ -90,7 +119,7 @@ async function validateOpenAI(key: string): Promise<ValidationResult> {
|
|
|
90
119
|
}
|
|
91
120
|
|
|
92
121
|
async function validateAnthropic(key: string): Promise<ValidationResult> {
|
|
93
|
-
const model =
|
|
122
|
+
const model = DEFAULT_MODELS.anthropic;
|
|
94
123
|
const controller = new AbortController();
|
|
95
124
|
const timer = setTimeout(() => controller.abort(), VALIDATION_TIMEOUT_MS);
|
|
96
125
|
try {
|
|
@@ -109,7 +138,13 @@ async function validateAnthropic(key: string): Promise<ValidationResult> {
|
|
|
109
138
|
signal: controller.signal,
|
|
110
139
|
});
|
|
111
140
|
if (!res.ok) {
|
|
112
|
-
|
|
141
|
+
const rawBody = await res.text().catch(() => "");
|
|
142
|
+
return {
|
|
143
|
+
ok: false,
|
|
144
|
+
statusCode: res.status,
|
|
145
|
+
error: normalizeAnthropicError(res.status, rawBody),
|
|
146
|
+
rawBody,
|
|
147
|
+
};
|
|
113
148
|
}
|
|
114
149
|
const data = (await res.json()) as { model?: string };
|
|
115
150
|
return { ok: true, model: data.model ?? model };
|
|
@@ -119,16 +154,12 @@ async function validateAnthropic(key: string): Promise<ValidationResult> {
|
|
|
119
154
|
}
|
|
120
155
|
|
|
121
156
|
async function validateGoogle(key: string): Promise<ValidationResult> {
|
|
122
|
-
const model =
|
|
157
|
+
const model = DEFAULT_MODELS.google;
|
|
123
158
|
const controller = new AbortController();
|
|
124
159
|
const timer = setTimeout(() => controller.abort(), VALIDATION_TIMEOUT_MS);
|
|
125
160
|
try {
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
// path is supported by all v1beta endpoints. Codex's v0.8 security
|
|
129
|
-
// review flagged the URL-key approach as a P1.
|
|
130
|
-
const url =
|
|
131
|
-
`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`;
|
|
161
|
+
// x-goog-api-key header (per v0.9.1 fix) — never the URL query.
|
|
162
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`;
|
|
132
163
|
const res = await fetch(url, {
|
|
133
164
|
method: "POST",
|
|
134
165
|
headers: {
|
|
@@ -142,10 +173,103 @@ async function validateGoogle(key: string): Promise<ValidationResult> {
|
|
|
142
173
|
signal: controller.signal,
|
|
143
174
|
});
|
|
144
175
|
if (!res.ok) {
|
|
145
|
-
|
|
176
|
+
const rawBody = await res.text().catch(() => "");
|
|
177
|
+
return {
|
|
178
|
+
ok: false,
|
|
179
|
+
statusCode: res.status,
|
|
180
|
+
error: normalizeGoogleError(res.status, rawBody, key),
|
|
181
|
+
rawBody,
|
|
182
|
+
};
|
|
146
183
|
}
|
|
147
184
|
return { ok: true, model };
|
|
148
185
|
} finally {
|
|
149
186
|
clearTimeout(timer);
|
|
150
187
|
}
|
|
151
188
|
}
|
|
189
|
+
|
|
190
|
+
// ─── Error normalization (Codex v0.8 P2 #6) ──────────────────────────
|
|
191
|
+
//
|
|
192
|
+
// Parse known provider JSON error shapes into short, user-safe messages.
|
|
193
|
+
// Never echo the raw key back even by accident (defense in depth: we
|
|
194
|
+
// also redact anything that looks like the submitted key).
|
|
195
|
+
|
|
196
|
+
function normalizeOpenAIError(status: number, rawBody: string): string {
|
|
197
|
+
// OpenAI shape: { "error": { "message": "...", "type": "...", "code": "..." } }
|
|
198
|
+
try {
|
|
199
|
+
const parsed = JSON.parse(rawBody) as {
|
|
200
|
+
error?: { message?: string; type?: string; code?: string };
|
|
201
|
+
};
|
|
202
|
+
const msg = parsed.error?.message;
|
|
203
|
+
if (msg) return `[${status}] OpenAI: ${truncate(msg, 200)}`;
|
|
204
|
+
} catch {
|
|
205
|
+
/* fall through */
|
|
206
|
+
}
|
|
207
|
+
return statusOnlyMessage("OpenAI", status);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function normalizeAnthropicError(status: number, rawBody: string): string {
|
|
211
|
+
// Anthropic shape: { "type": "error", "error": { "type": "...", "message": "..." } }
|
|
212
|
+
try {
|
|
213
|
+
const parsed = JSON.parse(rawBody) as {
|
|
214
|
+
error?: { type?: string; message?: string };
|
|
215
|
+
};
|
|
216
|
+
const msg = parsed.error?.message;
|
|
217
|
+
if (msg) return `[${status}] Anthropic: ${truncate(msg, 200)}`;
|
|
218
|
+
} catch {
|
|
219
|
+
/* fall through */
|
|
220
|
+
}
|
|
221
|
+
return statusOnlyMessage("Anthropic", status);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function normalizeGoogleError(
|
|
225
|
+
status: number,
|
|
226
|
+
rawBody: string,
|
|
227
|
+
submittedKey: string,
|
|
228
|
+
): string {
|
|
229
|
+
// Google shape: { "error": { "code": N, "message": "...", "status": "..." } }
|
|
230
|
+
try {
|
|
231
|
+
const parsed = JSON.parse(rawBody) as {
|
|
232
|
+
error?: { code?: number; message?: string; status?: string };
|
|
233
|
+
};
|
|
234
|
+
let msg = parsed.error?.message ?? "";
|
|
235
|
+
// Belt-and-braces redaction: Google sometimes echoes the key in
|
|
236
|
+
// error messages (e.g. "API key not valid. Pass a valid API key.")
|
|
237
|
+
// — we don't ship the actual key value if it ever ends up here.
|
|
238
|
+
if (submittedKey && msg.includes(submittedKey)) {
|
|
239
|
+
msg = msg.replace(submittedKey, "<redacted-key>");
|
|
240
|
+
}
|
|
241
|
+
if (msg) return `[${status}] Google: ${truncate(msg, 200)}`;
|
|
242
|
+
} catch {
|
|
243
|
+
/* fall through */
|
|
244
|
+
}
|
|
245
|
+
return statusOnlyMessage("Google", status);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function statusOnlyMessage(provider: string, status: number): string {
|
|
249
|
+
if (status === 401 || status === 403) {
|
|
250
|
+
return `[${status}] ${provider} rejected the key (unauthorized).`;
|
|
251
|
+
}
|
|
252
|
+
if (status === 429) {
|
|
253
|
+
return `[${status}] ${provider} rate-limited or quota exceeded.`;
|
|
254
|
+
}
|
|
255
|
+
if (status >= 500) {
|
|
256
|
+
return `[${status}] ${provider} returned a server error; try again.`;
|
|
257
|
+
}
|
|
258
|
+
return `[${status}] ${provider} returned an unrecognized error.`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function truncate(s: string, n: number): string {
|
|
262
|
+
return s.length <= n ? s : s.slice(0, n - 1) + "…";
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function friendlyNetworkError(err: unknown): string {
|
|
266
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
267
|
+
if (msg.includes("abort")) {
|
|
268
|
+
return "Validation timed out (network or provider too slow).";
|
|
269
|
+
}
|
|
270
|
+
if (msg.includes("ENOTFOUND") || msg.includes("ECONNREFUSED")) {
|
|
271
|
+
return "Could not reach the provider (network issue).";
|
|
272
|
+
}
|
|
273
|
+
// Generic — but never include random stack data; just the message.
|
|
274
|
+
return `Network error: ${truncate(msg, 200)}`;
|
|
275
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - budget/ ✅ task #14 BudgetTracker + BudgetExceededError
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
export const VERSION = "0.9.
|
|
16
|
+
export const VERSION = "0.9.2";
|
|
17
17
|
|
|
18
18
|
// MCP server (v0.9.0+) — exposes AlmightyGPT's review surface as MCP tools.
|
|
19
19
|
export { startMcpServer } from "./mcp/server.js";
|