Skip to content
Closed
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
2 changes: 1 addition & 1 deletion ci/source-architecture-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"src/lib/actions/sandbox/mcp-bridge-contracts.ts": 25,
"src/lib/actions/sandbox/process-recovery.ts": 27,
"src/lib/adapters/docker/index.ts": 43,
"src/lib/adapters/openshell/client.ts": 20,
"src/lib/adapters/openshell/client.ts": 19,
"src/lib/adapters/openshell/resolve.ts": 27,
"src/lib/adapters/openshell/runtime.ts": 55,
"src/lib/adapters/openshell/timeouts.ts": 39,
Expand Down
5 changes: 5 additions & 0 deletions src/commands/credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ describe("credentials oclif adapter source coverage", () => {
["provider", "list", "--names"],
{
ignoreError: true,
maxBuffer: 64 * 1024,
stdio: ["ignore", "pipe", "pipe"],
timeout: 30_000,
},
Expand Down Expand Up @@ -259,6 +260,7 @@ describe("credentials oclif adapter source coverage", () => {
["provider", "profile", "export", "openai", "--output", "json"],
{
ignoreError: true,
maxBuffer: 64 * 1024,
suppressOutput: true,
stdio: ["ignore", "pipe", "pipe"],
timeout: 30_000,
Expand Down Expand Up @@ -292,6 +294,7 @@ describe("credentials oclif adapter source coverage", () => {
["provider", "profile", "export", "openai", "--output", "json"],
{
ignoreError: true,
maxBuffer: 64 * 1024,
suppressOutput: true,
stdio: ["ignore", "pipe", "pipe"],
timeout: 30_000,
Expand Down Expand Up @@ -341,12 +344,14 @@ describe("credentials oclif adapter source coverage", () => {
).toEqual([
{
ignoreError: true,
maxBuffer: 64 * 1024,
suppressOutput: true,
stdio: ["ignore", "pipe", "pipe"],
timeout: 30_000,
},
{
ignoreError: true,
maxBuffer: 64 * 1024,
suppressOutput: true,
stdio: ["ignore", "pipe", "pipe"],
timeout: 30_000,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/actions/inference-set-failure-handling.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ describe("runInferenceSet failure handling", () => {
expect(deps.calls.captureOpenshell).toHaveBeenNthCalledWith(
2,
["provider", "list", "--names"],
{ ignoreError: true, maxBuffer: 64 * 1024, timeout: 5_000 },
{ ignoreError: true, includeStreams: true, maxBuffer: 64 * 1024, timeout: 5_000 },
);
expect(deps.calls.writeSandboxConfig).not.toHaveBeenCalled();
expect(deps.calls.updateSandbox).not.toHaveBeenCalled();
Expand Down
33 changes: 33 additions & 0 deletions src/lib/actions/inference-set-no-auth-compatible.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, it, vi } from "vitest";
import type { OpenShellProviderAdapter } from "../adapters/openshell/provider-adapter";
import type { SandboxEntry } from "../state/registry";
import { runInferenceSet } from "./inference-set";
import {
Expand Down Expand Up @@ -174,6 +175,38 @@ describe("runInferenceSet on a loopback no-auth compatible endpoint", () => {
expect(deps.calls.writeSandboxConfig).not.toHaveBeenCalled();
});

it("refuses incomplete typed provider identity before route mutation (#9806)", async () => {
const captureOpenshell = noAuthProviderCapture();
const providerAdapter = {
getProvider: vi.fn(async () => ({
ok: true as const,
value: {
name: "compatible-endpoint",
type: "openai",
credentialKeys: [NO_AUTH_CREDENTIAL_ENV],
configKeys: ["OPENAI_BASE_URL"],
revision: null,
},
})),
} as unknown as OpenShellProviderAdapter;
const deps = createDeps({
config: CONFIG,
entry: noAuthEntry(),
session: noAuthSession(),
captureOpenshell,
providerAdapter,
});

await expect(
runInferenceSet({ provider: "compatible-endpoint", model: "model-b" }, deps),
).rejects.toThrow(/without a revision/);

expect(inferenceSetArgs(captureOpenshell)).toEqual([]);
expect(providerMutationArgs(captureOpenshell)).toEqual([]);
expect(deps.calls.probeSandboxRoute).not.toHaveBeenCalled();
expect(deps.calls.updateSandbox).not.toHaveBeenCalled();
});

it("refuses an absent provider before selecting the route with no endpoint options", async () => {
const captureOpenshell = noAuthProviderCapture({ initiallyPresent: false });
const deps = createDeps({
Expand Down
73 changes: 43 additions & 30 deletions src/lib/actions/inference-set-provider-diagnostics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,42 @@
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, it, vi } from "vitest";
import type {
OpenShellProviderAdapter,
OpenShellProviderResult,
OpenShellProviderInventory,
} from "../adapters/openshell/provider-adapter";
import { classifyGatewayProviderNames, isBridgeProviderName } from "../credentials/provider-list";
import { queryRegisteredGatewayProviders } from "./inference-set-provider-diagnostics";

const STATIC_WARNING =
" ⚠ Could not query registered OpenShell providers while formatting the failure.";

describe("inference set provider diagnostics", () => {
it("returns sorted gateway credentials and excludes messaging providers (#5924)", () => {
const captureOpenshell = vi.fn(() => ({
status: 0,
output: "nvidia-prod\nalpha-telegram-bridge\nanthropic-prod\n",
}));
function adapterWithList(
result: OpenShellProviderResult<OpenShellProviderInventory>,
): OpenShellProviderAdapter {
return {
listProviders: vi.fn(async () => result),
} as unknown as OpenShellProviderAdapter;
}

it("returns sorted gateway credentials and excludes messaging providers (#5924)", async () => {
const providerAdapter = adapterWithList({
ok: true,
value: {
names: ["nvidia-prod", "alpha-telegram-bridge", "anthropic-prod"],
},
});
const log = vi.fn();

expect(queryRegisteredGatewayProviders({ captureOpenshell, log })).toEqual([
await expect(queryRegisteredGatewayProviders({ providerAdapter, log })).resolves.toEqual([
"anthropic-prod",
"nvidia-prod",
]);
expect(captureOpenshell).toHaveBeenCalledWith(["provider", "list", "--names"], {
ignoreError: true,
maxBuffer: 64 * 1024,
timeout: 5_000,
expect(providerAdapter.listProviders).toHaveBeenCalledWith({
target: { kind: "selected" },
timeoutMs: 5_000,
});
expect(log).not.toHaveBeenCalled();
});
Expand All @@ -33,45 +47,44 @@ describe("inference set provider diagnostics", () => {
bridgeNames: [],
credentialNames: [],
});
expect(
classifyGatewayProviderNames(["alpha-telegram-bridge", "alpha-slack-app"]),
).toEqual({ bridgeNames: ["alpha-telegram-bridge", "alpha-slack-app"], credentialNames: [] });
expect(classifyGatewayProviderNames(["alpha-telegram-bridge", "alpha-slack-app"])).toEqual({
bridgeNames: ["alpha-telegram-bridge", "alpha-slack-app"],
credentialNames: [],
});
expect(isBridgeProviderName("alpha-discord-bridge")).toBe(true);
expect(isBridgeProviderName("nvidia-prod")).toBe(false);
});

it.each([
{
name: "thrown capture error",
capture: () => {
list: async () => {
throw new Error("query-secret");
},
},
{
name: "timeout",
capture: () => ({
status: null,
output: "partial-timeout-provider",
error: Object.assign(new Error("query-secret"), { code: "ETIMEDOUT" }),
list: async () => ({
ok: false as const,
error: { kind: "timeout" as const, message: "safe timeout" },
}),
},
{
name: "buffer overflow",
capture: () => ({
status: null,
output: "partial-overflow-provider",
error: Object.assign(new Error("query-secret"), { code: "ENOBUFS" }),
name: "command failure",
list: async () => ({
ok: false as const,
error: { kind: "command" as const, reason: "failed" as const, message: "safe failure" },
}),
},
{
name: "nonzero status",
capture: () => ({ status: 17, output: "query-secret" }),
},
])("uses the static fallback for $name", ({ capture }) => {
const captureOpenshell = vi.fn(capture);
])("uses the static fallback for $name", async ({ list }) => {
const providerAdapter = {
listProviders: vi.fn(list),
} as unknown as OpenShellProviderAdapter;
const log = vi.fn();

expect(queryRegisteredGatewayProviders({ captureOpenshell, log })).toBeUndefined();
await expect(
queryRegisteredGatewayProviders({ providerAdapter, log }),
).resolves.toBeUndefined();
expect(log).toHaveBeenCalledWith(STATIC_WARNING);
expect(log).not.toHaveBeenCalledWith(expect.stringContaining("query-secret"));
});
Expand Down
35 changes: 16 additions & 19 deletions src/lib/actions/inference-set-provider-diagnostics.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,32 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import type { CaptureOpenshellOptions, CaptureOpenshellResult } from "../adapters/openshell/client";
import { parseCliOpenShellProviderNames } from "../adapters/openshell/provider-command";
import type { CaptureOpenshellResult } from "../adapters/openshell/client";
import type { OpenShellProviderAdapter } from "../adapters/openshell/provider-adapter";
import { selectedOpenShellGateway } from "../adapters/openshell/sandbox-observer";
import { classifyGatewayProviderNames } from "../credentials/provider-list";
import {
buildOpenshellInferenceSetFailureMessage,
OPEN_SHELL_FAILURE_CAPTURE_MAX_BUFFER,
openshellReportsProviderNotFound,
} from "./inference-set-error";

const OPEN_SHELL_DIAGNOSTIC_TIMEOUT_MS = 5_000;

interface ProviderDiagnosticDeps {
captureOpenshell: (
args: string[],
opts?: Pick<CaptureOpenshellOptions, "ignoreError" | "maxBuffer" | "timeout">,
) => CaptureOpenshellResult;
providerAdapter: OpenShellProviderAdapter;
log: (message: string) => void;
}

export function queryRegisteredGatewayProviders(
export async function queryRegisteredGatewayProviders(
deps: ProviderDiagnosticDeps,
): string[] | undefined {
): Promise<string[] | undefined> {
try {
const result = deps.captureOpenshell(["provider", "list", "--names"], {
ignoreError: true,
maxBuffer: OPEN_SHELL_FAILURE_CAPTURE_MAX_BUFFER,
timeout: OPEN_SHELL_DIAGNOSTIC_TIMEOUT_MS,
const result = await deps.providerAdapter.listProviders({
target: selectedOpenShellGateway(),
timeoutMs: OPEN_SHELL_DIAGNOSTIC_TIMEOUT_MS,
});
if (result.status === 0) {
return classifyGatewayProviderNames(parseCliOpenShellProviderNames(result.output))
.credentialNames;
if (result.ok) {
return classifyGatewayProviderNames(result.value.names).credentialNames;
}
} catch (_error: unknown) {
// #5924: intentionally treat every thrown query or parsing error identically.
Expand All @@ -42,11 +37,11 @@ export function queryRegisteredGatewayProviders(
return undefined;
}

export function buildInferenceSetFailure(
export async function buildInferenceSetFailure(
setResult: CaptureOpenshellResult,
provider: string,
deps: ProviderDiagnosticDeps,
): { exitCode: number; message: string } {
): Promise<{ exitCode: number; message: string }> {
const stderr = typeof setResult.stderr === "string" ? setResult.stderr : "";
const stdout = typeof setResult.stdout === "string" ? setResult.stdout : "";
const providerNotFound = openshellReportsProviderNotFound(`${stderr}\n${stdout}`, provider);
Expand All @@ -56,7 +51,9 @@ export function buildInferenceSetFailure(
message: buildOpenshellInferenceSetFailureMessage({
exitCode,
providerNotFound,
registeredProviders: providerNotFound ? queryRegisteredGatewayProviders(deps) : undefined,
registeredProviders: providerNotFound
? await queryRegisteredGatewayProviders(deps)
: undefined,
stderr,
stdout,
}),
Expand Down
Loading
Loading