diff --git a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx index 2b0c32a775..6dc3784684 100644 --- a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx +++ b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx @@ -85,6 +85,15 @@ function isPermissionPolicy(value: unknown): value is PermissionPolicy { return typeof value === "string" && PERMISSION_POLICY_VALUES.has(value) } +/** + * Resolve the effective harness kind from the config, defaulting to `"pi_core"` when absent. + * The runner already treats a missing harness.kind as pi_core, so this aligns the UI with + * what actually runs. The default is read-path only — it never writes kind back into the config. + */ +export function resolveHarnessKind(harness: {kind?: string} | null | undefined): string { + return typeof harness?.kind === "string" ? harness.kind : "pi_core" +} + export function useModelHarness({ schema, config, @@ -171,7 +180,7 @@ export function useModelHarness({ // carries through extra keys (e.g. `extras`) so a form edit never silently drops them. The picker // is harness-filtered: selecting a model sets BOTH the model id and its provider, fed by the // `/inspect` capability map below. - const harnessValue = typeof harness.kind === "string" ? (harness.kind as string) : null + const harnessValue = resolveHarnessKind(harness) const isPiHarness = harnessValue === "pi_core" || harnessValue === "pi_agenta" const llm = config.llm const modelId = useMemo(() => modelIdFromConfig(llm), [llm]) diff --git a/web/packages/agenta-entity-ui/tests/unit/resolveHarnessKind.test.ts b/web/packages/agenta-entity-ui/tests/unit/resolveHarnessKind.test.ts new file mode 100644 index 0000000000..20cefd23c1 --- /dev/null +++ b/web/packages/agenta-entity-ui/tests/unit/resolveHarnessKind.test.ts @@ -0,0 +1,49 @@ +/** + * Unit tests for resolveHarnessKind — the defaulting logic that aligns the UI with the runner's + * treatment of absent harness.kind as pi_core. + * + * The default is read-path only; resolveHarnessKind never writes kind back into the config. + * Runs under @agenta/entity-ui's own vitest runner. + */ +import {describe, expect, it} from "vitest" + +import {resolveHarnessKind} from "../../src/DrillInView/SchemaControls/agentTemplate/useModelHarness" + +describe("resolveHarnessKind", () => { + it("returns pi_core when harness is null", () => { + expect(resolveHarnessKind(null)).toBe("pi_core") + }) + + it("returns pi_core when harness is undefined", () => { + expect(resolveHarnessKind(undefined)).toBe("pi_core") + }) + + it("returns pi_core when harness.kind is absent", () => { + expect(resolveHarnessKind({})).toBe("pi_core") + }) + + it("returns pi_core when harness.kind is undefined", () => { + expect(resolveHarnessKind({kind: undefined})).toBe("pi_core") + }) + + it("returns pi_core when harness.kind is an empty object (edge case)", () => { + // This covers the {harness: {}} case from the issue — the runner treats it as pi_core + expect(resolveHarnessKind({} as {kind?: string})).toBe("pi_core") + }) + + it("does not override an explicit claude harness", () => { + expect(resolveHarnessKind({kind: "claude"})).toBe("claude") + }) + + it("does not override an explicit openai harness", () => { + expect(resolveHarnessKind({kind: "openai"})).toBe("openai") + }) + + it("does not override an explicit pi_agenta harness", () => { + expect(resolveHarnessKind({kind: "pi_agenta"})).toBe("pi_agenta") + }) + + it("does not override an explicit bedrock harness", () => { + expect(resolveHarnessKind({kind: "bedrock"})).toBe("bedrock") + }) +}) \ No newline at end of file