@gajae-code/ai 0.4.2 → 0.4.4
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/types/types.d.ts
CHANGED
|
@@ -717,6 +717,14 @@ export interface Model<TApi extends Api = any> {
|
|
|
717
717
|
baseUrl: string;
|
|
718
718
|
reasoning: boolean;
|
|
719
719
|
input: ("text" | "image")[];
|
|
720
|
+
/**
|
|
721
|
+
* Output modalities the model can produce. Defaults to text-only when
|
|
722
|
+
* unset. A model that lists `"image"` advertises image-generation support
|
|
723
|
+
* (e.g. an OpenAI-compatible `gpt-image` model behind a proxy), which the
|
|
724
|
+
* `generate_image` tool uses to route requests without first-party
|
|
725
|
+
* provider/id heuristics.
|
|
726
|
+
*/
|
|
727
|
+
output?: ("text" | "image")[];
|
|
720
728
|
cost: {
|
|
721
729
|
input: number;
|
|
722
730
|
output: number;
|
|
@@ -13,6 +13,10 @@ export type CapturedHttpErrorResponse = {
|
|
|
13
13
|
bodyText?: string;
|
|
14
14
|
bodyJson?: unknown;
|
|
15
15
|
};
|
|
16
|
+
/** Whether `message` (from a 400 response) signals an unavailable/unknown model. */
|
|
17
|
+
export declare function isModelUnavailableError(message: string, error: unknown): boolean;
|
|
18
|
+
/** Actionable guidance for selecting an available model/provider. */
|
|
19
|
+
export declare function formatModelUnavailableGuidance(dump: RawHttpRequestDump | undefined): string;
|
|
16
20
|
export declare function appendRawHttpRequestDumpFor400(message: string, error: unknown, dump: RawHttpRequestDump | undefined): Promise<string>;
|
|
17
21
|
export declare function finalizeErrorMessage(error: unknown, rawRequestDump: RawHttpRequestDump | undefined, capturedErrorResponse?: CapturedHttpErrorResponse): Promise<string>;
|
|
18
22
|
export declare function withHttpStatus(error: unknown, status: number): Error;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/ai",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.4",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://gaebal-gajae.dev",
|
|
7
7
|
"author": "Yeachan-Heo",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@anthropic-ai/sdk": "^0.94.0",
|
|
45
45
|
"@bufbuild/protobuf": "^2.12.0",
|
|
46
|
-
"@gajae-code/utils": "0.4.
|
|
46
|
+
"@gajae-code/utils": "0.4.4",
|
|
47
47
|
"openai": "^6.36.0",
|
|
48
48
|
"partial-json": "^0.1.7",
|
|
49
49
|
"zod": "4.4.3"
|
package/src/models.json
CHANGED
|
@@ -9853,7 +9853,8 @@
|
|
|
9853
9853
|
"baseUrl": "https://api.kilo.ai/api/gateway",
|
|
9854
9854
|
"reasoning": false,
|
|
9855
9855
|
"input": [
|
|
9856
|
-
"text"
|
|
9856
|
+
"text",
|
|
9857
|
+
"image"
|
|
9857
9858
|
],
|
|
9858
9859
|
"cost": {
|
|
9859
9860
|
"input": 0,
|
|
@@ -9872,7 +9873,8 @@
|
|
|
9872
9873
|
"baseUrl": "https://api.kilo.ai/api/gateway",
|
|
9873
9874
|
"reasoning": false,
|
|
9874
9875
|
"input": [
|
|
9875
|
-
"text"
|
|
9876
|
+
"text",
|
|
9877
|
+
"image"
|
|
9876
9878
|
],
|
|
9877
9879
|
"cost": {
|
|
9878
9880
|
"input": 0,
|
|
@@ -16768,7 +16770,8 @@
|
|
|
16768
16770
|
"baseUrl": "https://api.kilo.ai/api/gateway",
|
|
16769
16771
|
"reasoning": false,
|
|
16770
16772
|
"input": [
|
|
16771
|
-
"text"
|
|
16773
|
+
"text",
|
|
16774
|
+
"image"
|
|
16772
16775
|
],
|
|
16773
16776
|
"cost": {
|
|
16774
16777
|
"input": 0,
|
|
@@ -63601,7 +63604,8 @@
|
|
|
63601
63604
|
"baseUrl": "https://api.venice.ai/api/v1",
|
|
63602
63605
|
"reasoning": false,
|
|
63603
63606
|
"input": [
|
|
63604
|
-
"text"
|
|
63607
|
+
"text",
|
|
63608
|
+
"image"
|
|
63605
63609
|
],
|
|
63606
63610
|
"cost": {
|
|
63607
63611
|
"input": 0,
|
package/src/types.ts
CHANGED
|
@@ -854,6 +854,14 @@ export interface Model<TApi extends Api = any> {
|
|
|
854
854
|
baseUrl: string;
|
|
855
855
|
reasoning: boolean;
|
|
856
856
|
input: ("text" | "image")[];
|
|
857
|
+
/**
|
|
858
|
+
* Output modalities the model can produce. Defaults to text-only when
|
|
859
|
+
* unset. A model that lists `"image"` advertises image-generation support
|
|
860
|
+
* (e.g. an OpenAI-compatible `gpt-image` model behind a proxy), which the
|
|
861
|
+
* `generate_image` tool uses to route requests without first-party
|
|
862
|
+
* provider/id heuristics.
|
|
863
|
+
*/
|
|
864
|
+
output?: ("text" | "image")[];
|
|
857
865
|
cost: {
|
|
858
866
|
input: number; // $/million tokens
|
|
859
867
|
output: number; // $/million tokens
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import { extractHttpStatusFromError, getLogsDir } from "@gajae-code/utils";
|
|
2
|
+
import { APP_NAME, extractHttpStatusFromError, getLogsDir } from "@gajae-code/utils";
|
|
3
3
|
import { isCopilotTransientModelError } from "./retry.js";
|
|
4
4
|
import { formatErrorMessageWithRetryAfter } from "./retry-after.js";
|
|
5
5
|
|
|
@@ -26,6 +26,46 @@ type ErrorWithStatus = {
|
|
|
26
26
|
|
|
27
27
|
const SENSITIVE_HEADERS = ["authorization", "x-api-key", "api-key", "cookie", "set-cookie", "proxy-authorization"];
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Privacy note appended next to a saved raw HTTP request dump. The dump is
|
|
31
|
+
* sanitized (secrets/thinking redacted) but can still contain prompt content
|
|
32
|
+
* and request metadata, so we explicitly discourage pasting it into public
|
|
33
|
+
* channels (issue #438).
|
|
34
|
+
*/
|
|
35
|
+
const RAW_HTTP_REQUEST_PRIVACY_NOTE =
|
|
36
|
+
"note: this local file is for your own debugging and may contain prompt content or request metadata — review it before sharing and do not paste its contents into public channels (issues, Discord, etc.).";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Patterns that indicate the configured model is unavailable on the provider
|
|
40
|
+
* (e.g. OpenAI's "The requested model '...' does not exist."). Used to surface
|
|
41
|
+
* actionable model/provider guidance instead of only a raw 400 + log path.
|
|
42
|
+
*/
|
|
43
|
+
const MODEL_UNAVAILABLE_PATTERNS: readonly RegExp[] = [
|
|
44
|
+
/\bmodel\b[^\n]*\bdoes not exist\b/i,
|
|
45
|
+
/\bdoes not exist\b[^\n]*\bmodel\b/i,
|
|
46
|
+
/\bmodel\b[^\n]*\b(not found|unavailable|not supported|no access|does not have access)\b/i,
|
|
47
|
+
/\b(unknown|unsupported|invalid)\s+model\b/i,
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
/** Whether `message` (from a 400 response) signals an unavailable/unknown model. */
|
|
51
|
+
export function isModelUnavailableError(message: string, error: unknown): boolean {
|
|
52
|
+
if (extractHttpStatusFromError(error) !== 400) return false;
|
|
53
|
+
return MODEL_UNAVAILABLE_PATTERNS.some(pattern => pattern.test(message));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Actionable guidance for selecting an available model/provider. */
|
|
57
|
+
export function formatModelUnavailableGuidance(dump: RawHttpRequestDump | undefined): string {
|
|
58
|
+
const modelPart = dump?.model ? ` '${dump.model}'` : "";
|
|
59
|
+
const providerPart = dump?.provider ? ` on provider '${dump.provider}'` : "";
|
|
60
|
+
return [
|
|
61
|
+
`The configured model${modelPart}${providerPart} is not available on this account/provider.`,
|
|
62
|
+
"Pick an available model before retrying:",
|
|
63
|
+
` • List available models: ${APP_NAME} --list-models`,
|
|
64
|
+
` • Run with a model: ${APP_NAME} --model <model>`,
|
|
65
|
+
` • Configure a provider: ${APP_NAME} setup provider`,
|
|
66
|
+
].join("\n");
|
|
67
|
+
}
|
|
68
|
+
|
|
29
69
|
export async function appendRawHttpRequestDumpFor400(
|
|
30
70
|
message: string,
|
|
31
71
|
error: unknown,
|
|
@@ -41,7 +81,7 @@ export async function appendRawHttpRequestDumpFor400(
|
|
|
41
81
|
|
|
42
82
|
try {
|
|
43
83
|
await Bun.write(filePath, `${JSON.stringify(sanitizedDump, null, 2)}\n`);
|
|
44
|
-
return `${message}\nraw-http-request=${filePath}`;
|
|
84
|
+
return `${message}\nraw-http-request=${filePath}\n${RAW_HTTP_REQUEST_PRIVACY_NOTE}`;
|
|
45
85
|
} catch (writeError) {
|
|
46
86
|
const writeMessage = writeError instanceof Error ? writeError.message : String(writeError);
|
|
47
87
|
return `${message}\nraw-http-request-save-failed=${writeMessage}`;
|
|
@@ -62,6 +102,9 @@ export async function finalizeErrorMessage(
|
|
|
62
102
|
message = `${message}\n${capturedMessage}`;
|
|
63
103
|
}
|
|
64
104
|
}
|
|
105
|
+
if (isModelUnavailableError(message, error)) {
|
|
106
|
+
message = `${message}\n\n${formatModelUnavailableGuidance(rawRequestDump)}`;
|
|
107
|
+
}
|
|
65
108
|
return appendRawHttpRequestDumpFor400(message, error, rawRequestDump);
|
|
66
109
|
}
|
|
67
110
|
|