Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/lib/adapters/openshell/provider-profile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,4 +212,18 @@ describe("OpenShell endpointless provider profiles", () => {

expect(ensureProfile(runOpenshell)).toEqual({ ok: false, reason: "export-failed" });
});

it("reuses an exact profile whose import-race diagnostic is wrapped across a box-drawing line (#10371)", () => {
// Same failure shape #10159 fixed for the "not found" match: OpenShell
// can wrap "already exists" across a box-drawing continuation depending
// on terminal width/TTY-ness, which a plain substring test would miss.
const runOpenshell = vi
.fn()
.mockReturnValueOnce({ status: 1, stderr: "provider profile not found" })
.mockReturnValueOnce({ status: 1, stderr: "provider profile already\n │ exists" })
.mockReturnValueOnce({ status: 0, stdout: EXPECTED_PROFILE });

expect(ensureProfile(runOpenshell)).toEqual({ ok: true });
expect(runOpenshell).toHaveBeenCalledTimes(3);
});
});
63 changes: 60 additions & 3 deletions src/lib/adapters/openshell/provider-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,27 @@ function commandStdout(result: { readonly output?: unknown; readonly stdout?: un
return Array.isArray(result.output) ? outputText(result.output[1]) : outputText(result.output);
}

function isMissingProviderProfile(output: string, profileId: string): boolean {
const normalized = output
/**
* Strip ANSI escapes, carriage returns, and OpenShell's box-drawing line
* continuations so a diagnostic can be pattern-matched regardless of the
* terminal width or TTY-ness that produced them (#10159).
*/
export function normalizeOpenshellDiagnostic(output: string): string {
return output
.replace(/\u001b\[[0-?]*[ -/]*[@-~]/gu, "")
.replace(/\r/gu, "")
.replace(/\n\s*│\s*/gu, " ")
.trim();
}

/**
* Whether `output` (an export probe's failure diagnostic) means the profile
* genuinely doesn't exist yet, as opposed to the probe itself failing for
* some other reason (gateway unavailable, auth, timeout, malformed
* response). Only a genuine "not found" makes it safe to proceed to import.
*/
export function isMissingProviderProfile(output: string, profileId: string): boolean {
const normalized = normalizeOpenshellDiagnostic(output);
const escapedProfileId = profileId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const missingMessage = new RegExp(
`^(?:(?:custom )?provider )?profile(?: ['\"]${escapedProfileId}['\"])? not found[.!]?$`,
Expand All @@ -63,6 +78,48 @@ function isMissingProviderProfile(output: string, profileId: string): boolean {
return structuredStatus.test(normalized) && missingMessage.test(message);
}

/**
* Project an OpenShell provider-profile document down to the fields that
* define its authorization boundary (credentials, endpoints, binaries,
* inference capability), or null if the document doesn't have the expected
* shape. Callers compare this projection between an exported profile and its
* checked-in YAML rather than trusting a matching profile ID alone, since a
* host-global profile store can hold a profile some other process imported
* under the same ID with a different boundary.
*/
export function credentialBoundary(doc: Record<string, unknown>): Record<string, unknown> | null {
if (
typeof doc.id !== "string" ||
!Array.isArray(doc.credentials) ||
!Array.isArray(doc.endpoints) ||
!Array.isArray(doc.binaries) ||
typeof doc.inference_capable !== "boolean"
) {
return null;
}
const credentials = doc.credentials.map((entry) => {
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
const credential = entry as Record<string, unknown>;
return {
name: credential.name,
env_vars: credential.env_vars,
required: credential.required,
auth_style: credential.auth_style,
header_name: credential.header_name,
query_param: credential.query_param,
refresh: credential.refresh ?? null,
};
});
if (credentials.some((entry) => entry === null)) return null;
return {
id: doc.id,
credentials,
endpoints: doc.endpoints,
binaries: doc.binaries,
inference_capable: doc.inference_capable,
};
}

function profileHasExpectedCredentialBoundary(
output: string,
expected: { readonly id: string; readonly inferenceCapable: boolean },
Expand Down Expand Up @@ -147,7 +204,7 @@ export function ensureEndpointlessProviderProfile(input: {
if (imported.status === 0) return { ok: true };

const importOutput = commandOutput(imported);
if (!/already exists/iu.test(importOutput)) {
if (!/already exists/iu.test(normalizeOpenshellDiagnostic(importOutput))) {
return { ok: false, reason: "import-failed" };
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3394,7 +3394,7 @@ async function runOnboard(opts: OnboardOptions = {}): Promise<void> {
finalization: {
stagedLegacyKeys,
migratedLegacyKeys,
webSearchEnabled: (config) => braveProviderProfile.shouldEnableBraveWebSearch(config),
webSearchEnabled: (config) => braveProviderProfile.shouldEnableWebSearch(config),
webSearchProvider: (config) => webSearchProviderForConfig(config),
},
finalizationDeps: {
Expand Down
Loading
Loading