fix(auth): stop the API key placeholder from shadowing Oh My Pi /login credentials (#78)

Oh My Pi kept the unresolved $COMMAND_CODE_API_KEY placeholder as a literal config API key that shadowed its /login credential store and was sent as the Bearer token (401). The placeholder is now registered only on pi, where it keeps the API-key auth method and --api-key working next to OAuth; on OMP the provider omits apiKey unless a real key is configured. Host-supplied placeholders are resolved or stripped on every stream path, and the legacy generate transport uses the same rule.

Stored /login OAuth and API-key credentials, --api-key, and env keys are now covered end to end on both pi and Oh My Pi, and CI runs the pi suite against a real binary.

Co-authored-by: ebreen <ebreen@users.noreply.github.com>
This commit is contained in:
ebreen
2026-09-02 22:43:23 +02:00
committed by GitHub
co-authored by ebreen
parent 8416e76d9e
commit 9296d3dc31
11 changed files with 399 additions and 56 deletions
+21 -4
View File
@@ -147,6 +147,13 @@ export const COMMAND_CODE_PLACEHOLDER_KEYS = new Set([
"COMMANDCODE_API_KEY",
])
function usableCommandCodeApiKey(value: string | undefined): string | undefined {
const trimmed = typeof value === "string" ? value.trim() : undefined
if (!trimmed) return undefined
if (COMMAND_CODE_PLACEHOLDER_KEYS.has(trimmed)) return undefined
return trimmed
}
/**
* Pick the real API key from a host registry value and/or the env/auth-file
* fallback, never returning a literal placeholder or an empty/whitespace value.
@@ -156,10 +163,20 @@ export function pickCommandCodeApiKey(
registryKey: string | undefined,
hostKey: string | undefined,
): string | undefined {
const trimmed = typeof registryKey === "string" ? registryKey.trim() : undefined
if (!trimmed) return hostKey
if (COMMAND_CODE_PLACEHOLDER_KEYS.has(trimmed)) return hostKey
return trimmed
return usableCommandCodeApiKey(registryKey) ?? usableCommandCodeApiKey(hostKey)
}
/**
* Replace a host-supplied placeholder (or missing key) with the configured
* fallback. Used for both registerProvider and the Provider API stream path.
*/
export function withResolvedCommandCodeApiKey<T extends { apiKey?: string }>(
options: T | undefined,
configuredKey: string | undefined,
): T | { apiKey?: string } {
const apiKey = pickCommandCodeApiKey(options?.apiKey, configuredKey)
if (options && apiKey === options.apiKey) return options
return { ...options, apiKey }
}
export function textContent(message: { content?: unknown }): string {
+5 -12
View File
@@ -19,6 +19,7 @@ import {
messagesToCC,
numberValue,
parseStreamEventLine,
pickCommandCodeApiKey,
recordOrEmpty,
stringValue,
toolsToJson,
@@ -237,22 +238,14 @@ export function createStreamCommandCode(deps: CoreDependencies) {
async function run() {
// Some hosts pass a literal env-var reference instead of resolving it.
const PLACEHOLDER_API_KEYS = new Set([
"$COMMAND_CODE_API_KEY",
"COMMAND_CODE_API_KEY",
"$COMMANDCODE_API_KEY",
"COMMANDCODE_API_KEY",
])
const hostKey =
options?.apiKey && !PLACEHOLDER_API_KEYS.has(options.apiKey) ? options.apiKey : undefined
const apiKey =
hostKey ??
const apiKey = pickCommandCodeApiKey(
options?.apiKey,
getApiKey({
env: deps.env,
authPaths: deps.authPaths,
homeDir: deps.homeDir,
})
}),
)
if (!apiKey) {
const msg: AssistantMessageLike = {