chore: update ji local changes
This commit is contained in:
1 parent
f590f672be
commit
e2a962de32
9 files changed
+185
-38
No files matched your search
@@ -64,24 +64,50 @@ export async function loginByUsername(
|
||||
return response.json() as Promise<LoginByUsernameResponse>;
|
||||
}
|
||||
|
||||
// The upstream token issuer and the verifier (token_info_url) run on separate
|
||||
// servers whose clocks can drift ~3-4s apart. A token verified right after it
|
||||
// is issued can look "not yet valid / expired" to the verifier, which surfaces
|
||||
// as a 401 here. That window closes on its own once real time advances past the
|
||||
// skew, so we silently retry the exchange a couple of times before giving up —
|
||||
// the LoginPage keeps showing its loading state, so the user never notices.
|
||||
const TOKEN_LOGIN_MAX_ATTEMPTS = 3; // initial try + 2 retries → covers up to ~6s skew
|
||||
const TOKEN_LOGIN_RETRY_DELAY_MS = 3000;
|
||||
|
||||
const wait = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
export async function loginByToken(
|
||||
token: string,
|
||||
): Promise<LoginByUsernameResponse> {
|
||||
// Token-exchange login: the gateway resolves the upstream token to a username
|
||||
// and signs the matching local account in.
|
||||
const response = await apiFetch(
|
||||
`${getBackendBaseURL()}/api/v1/auth/login/token?token=${encodeURIComponent(token)}`,
|
||||
{
|
||||
const url = `${getBackendBaseURL()}/api/v1/auth/login/token?token=${encodeURIComponent(token)}`;
|
||||
let lastError = "Login failed.";
|
||||
|
||||
for (let attempt = 1; attempt <= TOKEN_LOGIN_MAX_ATTEMPTS; attempt += 1) {
|
||||
const response = await apiFetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
);
|
||||
if (!response.ok) {
|
||||
});
|
||||
if (response.ok) {
|
||||
return response.json() as Promise<LoginByUsernameResponse>;
|
||||
}
|
||||
|
||||
const error = await response.json().catch(() => null);
|
||||
throw new Error(extractErrorMessage(error));
|
||||
lastError = extractErrorMessage(error);
|
||||
|
||||
// Only 401 is plausibly the transient clock-skew case and worth waiting on.
|
||||
// 403 (disabled), 429 (rate-limited), 500 (misconfigured) won't self-heal,
|
||||
// so fail fast instead of stalling the user.
|
||||
const isTransient = response.status === 401;
|
||||
if (!isTransient || attempt === TOKEN_LOGIN_MAX_ATTEMPTS) {
|
||||
throw new Error(lastError);
|
||||
}
|
||||
await wait(TOKEN_LOGIN_RETRY_DELAY_MS);
|
||||
}
|
||||
return response.json() as Promise<LoginByUsernameResponse>;
|
||||
|
||||
// Unreachable: the loop either returns or throws above.
|
||||
throw new Error(lastError);
|
||||
}
|
||||
|
||||
@@ -62,6 +62,21 @@ export function textOfMessage(message: Message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function titleOfThread(thread: AgentThread) {
|
||||
return thread.values?.title ?? "Untitled";
|
||||
export function titleOfThread(thread: AgentThread, fallback = "Untitled") {
|
||||
const title = thread.values?.title;
|
||||
if (title) return title;
|
||||
|
||||
// No title yet (e.g. the very first turn is still streaming, or the backend
|
||||
// title pass hasn't finished). Derive a sensible display title from the first
|
||||
// user message so the user never sees a bare "Untitled" placeholder.
|
||||
const messages = thread.values?.messages;
|
||||
if (Array.isArray(messages)) {
|
||||
const firstHuman = messages.find((m) => m.type === "human");
|
||||
const text = firstHuman ? textOfMessage(firstHuman)?.trim() : null;
|
||||
if (text) {
|
||||
return text.length > 50 ? `${text.slice(0, 50).trimEnd()}…` : text;
|
||||
}
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
Reference in new issue
Block a user