jeo-code 0.8.15 → 0.8.17
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/CHANGELOG.md +32 -0
- package/README.ja.md +5 -2
- package/README.ko.md +5 -2
- package/README.md +10 -2
- package/README.zh.md +5 -2
- package/package.json +1 -1
- package/src/agent/prompt-router.ts +46 -5
- package/src/agent/route-history.ts +107 -0
- package/src/agent/subagents.ts +20 -3
- package/src/ai/providers/anthropic-compatible.ts +19 -2
- package/src/ai/providers/errors.ts +34 -0
- package/src/ai/providers/kimi.ts +23 -8
- package/src/ai/providers/openai-compatible.ts +19 -2
- package/src/commands/computer.ts +11 -5
- package/src/commands/launch/computer-slash.ts +61 -0
- package/src/commands/launch/route-slash.ts +10 -6
- package/src/commands/launch.ts +169 -13
- package/src/tui/app.ts +43 -1
- package/src/tui/components/live-model-picker.ts +6 -3
- package/src/tui/components/slash.ts +2 -0
- package/src/util/provider-error.ts +62 -15
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { isUsageLimitError, isRefusalError, isConnectionError, ConnectionContextError } from "./retry";
|
|
8
8
|
import { companyLabel } from "../ai/model-catalog";
|
|
9
|
+
import { ProviderHttpError, ProviderStreamError } from "../ai/providers/errors";
|
|
9
10
|
// Re-export: engine.ts and callers import the refusal predicate from here; the
|
|
10
11
|
// definition lives in retry.ts so defaultRetryable can fail fast without a cycle.
|
|
11
12
|
export { isRefusalError } from "./retry";
|
|
@@ -15,8 +16,8 @@ export { isRefusalError } from "./retry";
|
|
|
15
16
|
* mismatch) — when the PROVIDER says the prompt doesn't fit, the loop can react
|
|
16
17
|
* (reactive trim + one retry) instead of dying on an opaque 400 (round-6 #4). */
|
|
17
18
|
export function isContextOverflowError(err: unknown): boolean {
|
|
18
|
-
const msg =
|
|
19
|
-
const status = (err
|
|
19
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
20
|
+
const status = statusOf(err);
|
|
20
21
|
const pattern = /context[ _-]?length|context window|prompt is too long|input is too long|too many tokens|maximum (input|context)|exceeds.{0,30}(context|token)/i;
|
|
21
22
|
if (pattern.test(msg)) return true;
|
|
22
23
|
return status === 413; // payload-too-large is always an overflow signal
|
|
@@ -32,6 +33,43 @@ function formatDuration(ms: number): string {
|
|
|
32
33
|
return rem ? `~${hours}h ${rem}m` : `~${hours}h`;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
/** Narrow an unknown error to one carrying a numeric `.status` field — covers the
|
|
37
|
+
* known structured classes AND ad-hoc errors a caller/test attaches `.status` to
|
|
38
|
+
* directly (`Object.assign(new Error(...), { status: 413 })`), matching the prior
|
|
39
|
+
* permissive cast-based read without an unchecked `as`. */
|
|
40
|
+
function hasNumericStatus(err: unknown): err is { status: number } {
|
|
41
|
+
return (
|
|
42
|
+
typeof err === "object" &&
|
|
43
|
+
err !== null &&
|
|
44
|
+
"status" in err &&
|
|
45
|
+
typeof err.status === "number"
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** True provider label for an error, when it's known: `ProviderHttpError`/
|
|
50
|
+
* `ProviderStreamError` always carry the ACTUAL backend's DISPLAY label (already
|
|
51
|
+
* `companyLabel`'d at every throw site — see `relabelProviderError`'s callers), so
|
|
52
|
+
* use it as-is rather than guessing from message text. `undefined` for anything else
|
|
53
|
+
* (bare `Error`, a re-wrapped doneReason string, …), letting the caller fall back to
|
|
54
|
+
* the regex-based guess below. */
|
|
55
|
+
function knownErrorProvider(err: unknown): string | undefined {
|
|
56
|
+
if (err instanceof ProviderHttpError || err instanceof ProviderStreamError) {
|
|
57
|
+
return err.provider;
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function statusOf(err: unknown): number | undefined {
|
|
63
|
+
if (err instanceof ProviderHttpError || err instanceof ProviderStreamError) {
|
|
64
|
+
return err.status;
|
|
65
|
+
}
|
|
66
|
+
return hasNumericStatus(err) ? err.status : undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function retryAfterMsOf(err: unknown): number | undefined {
|
|
70
|
+
return err instanceof ProviderHttpError ? err.retryAfterMs : undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
35
73
|
export function friendlyProviderError(err: unknown): string {
|
|
36
74
|
if (err instanceof ConnectionContextError) {
|
|
37
75
|
const provider = companyLabel(err.provider);
|
|
@@ -45,23 +83,32 @@ export function friendlyProviderError(err: unknown): string {
|
|
|
45
83
|
if (isConnectionError(err)) {
|
|
46
84
|
return `Could not connect to the provider. Check the configured base URL, your network connection, or switch model with /model.`;
|
|
47
85
|
}
|
|
48
|
-
const msg =
|
|
49
|
-
const status = (err
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
86
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
87
|
+
const status = statusOf(err);
|
|
88
|
+
// Prefer the error's OWN carried provider (accurate even for compat backends like
|
|
89
|
+
// Tencent/groq that speak Anthropic's/OpenAI's wire protocol — see
|
|
90
|
+
// `relabelProviderError`); only guess from message text for errors that never
|
|
91
|
+
// carried a `.provider` field (bare Error, a re-wrapped doneReason string, …).
|
|
92
|
+
const provider = knownErrorProvider(err) ?? (
|
|
93
|
+
/antigravity/i.test(msg)
|
|
94
|
+
? "Antigravity"
|
|
95
|
+
: /anthropic/i.test(msg)
|
|
96
|
+
? "Anthropic"
|
|
97
|
+
: /openai/i.test(msg)
|
|
98
|
+
? "OpenAI"
|
|
99
|
+
: /gemini|google/i.test(msg)
|
|
100
|
+
? "Gemini"
|
|
101
|
+
: "the provider"
|
|
102
|
+
);
|
|
59
103
|
|
|
60
104
|
if (isUsageLimitError(err)) {
|
|
61
105
|
return `${provider} usage/quota limit reached — this window will not clear in seconds, so auto-retry was skipped. Switch model with /model (e.g. a local ollama model), use another provider, or wait for the limit window to reset.`;
|
|
62
106
|
}
|
|
107
|
+
if (status === 402 || /\b402\b/.test(msg) || /payment required|insufficient credit|billing (?:is )?not enabled|free trial quota/i.test(msg)) {
|
|
108
|
+
return `${provider} requires billing/payment on this account (HTTP 402) — free trial quota exhausted or postpaid billing not enabled. Enable billing on the ${provider} account, or switch model with /model.`;
|
|
109
|
+
}
|
|
63
110
|
if (status === 429 || /\b429\b/.test(msg) || /rate[ _]?limit/i.test(msg)) {
|
|
64
|
-
const retryAfterMs = (err
|
|
111
|
+
const retryAfterMs = retryAfterMsOf(err);
|
|
65
112
|
const retry = typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs > 0
|
|
66
113
|
? ` Server requested retry after ${formatDuration(retryAfterMs)}.`
|
|
67
114
|
: "";
|
|
@@ -70,7 +117,7 @@ export function friendlyProviderError(err: unknown): string {
|
|
|
70
117
|
if (status === 401 || status === 403 || /\b40[13]\b/.test(msg)) {
|
|
71
118
|
return `${provider} rejected the credential (HTTP ${status ?? "401/403"}). Run 'jeo auth status', re-login with /provider login <name>, and for Antigravity prefer '/provider login antigravity' (gemini login only works when the Cloud Code Assist backend authorizes that token).`;
|
|
72
119
|
}
|
|
73
|
-
if ((err
|
|
120
|
+
if ((err instanceof Error && err.name === "TimeoutError") || /the operation timed out/i.test(msg)) {
|
|
74
121
|
return `${provider} did not complete the request within the call timeout (default 30min). This is expected for a HIGH/XHIGH-reasoning-effort completion that legitimately runs long on a hard problem — raise JEO_CALL_TIMEOUT_MS (ms) if this recurs, or lower the thinking level with /model.`;
|
|
75
122
|
}
|
|
76
123
|
if (/exceeded the overall deadline \(JEO_STREAM_MAX_MS\)/i.test(msg)) {
|