can1357/oh-my-pi · error · AIError.OnPromptRequiredError

Xiaomi Token Plan (${TOKEN_PLAN_REGION_NAMES[region]})

Error message

Xiaomi Token Plan (${TOKEN_PLAN_REGION_NAMES[region]})

What it means

loginXiaomiTokenPlan performs interactive OAuth-style login for Xiaomi's token plan. The library requires a prompt callback so the caller can collect the API key from the user; if the OAuthController has no onPrompt, it aborts with OnPromptRequiredError before doing any network work. This is a programming/configuration error in the caller, not a runtime condition.

Source

Thrown at packages/ai/src/registry/oauth/xiaomi.ts:190

	const trimmed = apiKey.trim();
	if (!trimmed) {
		throw new AIError.ApiKeyRequiredError();
	}

	options.onProgress?.(`Validating ${PROVIDER_ID} API key...`);
	await validateXiaomiApiKey(trimmed, undefined, options.signal, fetchImpl);
	return trimmed;
}

/**
 * Login to a regional Xiaomi Token Plan endpoint.
 *
 * Prompts for a token-plan API key and validates it against the selected region.
 */
export async function loginXiaomiTokenPlan(options: OAuthController, region: XiaomiTokenPlanRegion): Promise<string> {
	const fetchImpl = options.fetch ?? fetch;
	if (!options.onPrompt) {
		throw new AIError.OnPromptRequiredError(`Xiaomi Token Plan (${TOKEN_PLAN_REGION_NAMES[region]})`);
	}
	options.onAuth?.({
		url: TOKEN_PLAN_AUTH_URL,
		instructions: `Copy your token-plan API key for the ${TOKEN_PLAN_REGION_NAMES[region]} region`,
	});
	const apiKey = await options.onPrompt({
		message: `Paste your Xiaomi Token Plan ${TOKEN_PLAN_REGION_NAMES[region]} API key (tp-...)`,
		placeholder: "tp-...",
	});
	if (options.signal?.aborted) {
		throw new AIError.LoginCancelledError();
	}
	const trimmed = apiKey.trim();
	if (!trimmed) {
		throw new AIError.ApiKeyRequiredError();
	}

	options.onProgress?.(`Validating Xiaomi Token Plan (${TOKEN_PLAN_REGION_NAMES[region]}) API key...`);

View on GitHub (pinned to 9690622007)

Solutions

  1. Provide an onPrompt callback on the OAuthController that surfaces a text input to the user and resolves with the pasted tp-... API key.
  2. If no interaction is possible, bypass loginXiaomiTokenPlan and configure the API key directly via the provider's api-key entry path.
  3. Detect non-interactive environments up front and catch OnPromptRequiredError to report that interactive login is unavailable.

Example fix

// before
await loginXiaomiTokenPlan({ signal, fetch }, "cn");
// after
await loginXiaomiTokenPlan({ signal, fetch, onPrompt: async opts => window.prompt(opts.message) ?? "" }, "cn");
Defensive patterns

Strategy: try-catch

Validate before calling

if (typeof controller.onPrompt !== "function") throw new Error("Interactive Xiaomi login requires an onPrompt callback");

Type guard

function canPrompt(c): c is typeof c & { onPrompt: (o: { message: string; placeholder?: string }) => Promise<string> } { return typeof c.onPrompt === "function"; }

Try / catch

try { await loginXiaomiTokenPlan(controller, region); }
catch (e) { if (e instanceof AIError.OnPromptRequiredError) { console.error("Interactive login unavailable; set the API key directly."); return; } throw e; }

Prevention

When it happens

Trigger: loginXiaomiTokenPlan (or one of the region providers xiaomiTokenPlanAms/Cn/SgpProvider) is invoked with an OAuthController that lacks an onPrompt function, e.g. headless/SDK usage that only sets fetch or onProgress.

Common situations: Embedding the login flow in a script or CI job with no TTY; constructing the controller without wiring the UI callback; running in a non-interactive environment where the app skips prompt registration.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/4f13a785d5be4c7d. Report an issue: GitHub.