From 8071edf72a9e5d64c52686db241c79d0ee21bbdf Mon Sep 17 00:00:00 2001 From: Tim O'Farrell Date: Thu, 11 Jun 2026 15:28:04 -0600 Subject: [PATCH] =?UTF-8?q?Revert=20"chore:=20bump=20agent-server=20?= =?UTF-8?q?=E2=86=92=201.28.1,=20automation=20=E2=86=92=201.0.0a9,=20exten?= =?UTF-8?q?sions=20=E2=86=92=200.4.1=20(#1315)"=20(#1318)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 1917b5d39fbf09dc51213b4b484698fe394314c7. Co-authored-by: openhands --- AGENTS.md | 4 +- .../llm-settings-local-view.test.tsx | 40 ----------- __tests__/scripts/dev-safe.test.ts | 10 +-- config/defaults.json | 6 +- package-lock.json | 8 +-- package.json | 2 +- scripts/check-sdk-version-sync.mjs | 12 ++-- scripts/dev-safe.mjs | 2 +- .../llm-profiles/llm-settings-local-view.tsx | 17 +---- .../mock-llm-profile-management.spec.ts | 69 +++---------------- 10 files changed, 34 insertions(+), 136 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 7d4aef35e4..e07c05408a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -521,10 +521,10 @@ When adding code that needs a new string, decide up front which rule it falls un - `scripts/dev-safe.mjs` uses `uvx` for temporary agent-server installation — no permanent `uv tool install` needed. Environment variables (highest precedence first): - `OH_AGENT_SERVER_LOCAL_PATH` — absolute path to a local `software-agent-sdk` checkout. Runs the local checkout via `uvx` with `--with-editable` for `openhands-sdk`/`openhands-tools`/`openhands-workspace` and `--reinstall` for `openhands-agent-server`, so SDK edits are picked up on restart. Highest precedence. - `OH_AGENT_SERVER_GIT_REF` — git commit SHA or branch name (takes precedence over version) - - `OH_AGENT_SERVER_VERSION` — specific PyPI version (e.g., "1.28.1") + - `OH_AGENT_SERVER_VERSION` — specific PyPI version (e.g., "1.27.0") - `OH_SECRET_KEY` — secret key for settings encryption; auto-generated and persisted to `~/.openhands/agent-canvas/secret-key.txt` on first run (same file Docker uses), ensuring dev mode and Docker share the same key when both mount the same `~/.openhands` directory. Override with the env var to pin a specific key. - `SESSION_API_KEY` / `OH_SESSION_API_KEYS_0` / `VITE_SESSION_API_KEY` — session API key for agent-server authentication; auto-generated using `crypto.randomBytes(32)` if not set, passed to both agent-server (`OH_SESSION_API_KEYS_0`) and frontend (`VITE_SESSION_API_KEY`) - - Default: released PyPI version `1.28.1` for agent-server SDK libraries + - Default: released PyPI version `1.27.0` for agent-server SDK libraries - Security: `scripts/dev-safe.mjs` and `scripts/dev-with-automation.mjs` auto-generate random API keys when needed and persist the defaults so static builds, localStorage, and restarted services stay in sync: - `SESSION_API_KEY` — 64-character hex (256-bit) for agent-server API authentication; persisted at `~/.openhands/agent-canvas/session-api-key.txt` unless overridden via env var diff --git a/__tests__/components/settings/llm-profiles/llm-settings-local-view.test.tsx b/__tests__/components/settings/llm-profiles/llm-settings-local-view.test.tsx index 03c5033767..3ca4b23b1d 100644 --- a/__tests__/components/settings/llm-profiles/llm-settings-local-view.test.tsx +++ b/__tests__/components/settings/llm-profiles/llm-settings-local-view.test.tsx @@ -625,46 +625,6 @@ describe("LlmSettingsLocalView", () => { expect(savedLlm.model).toBe("litellm_proxy/claude-opus-4-8"); expect(savedLlm.base_url).toBe(OPENHANDS_LLM_PROXY_BASE_URL); }); - - it("injects the proxy base_url for a litellm_proxy model when server omits it (agent-server ≥1.28)", async () => { - // Arrange — agent-server ≥1.28 may omit the default base_url from stored - // profile configs. When the profile is fetched with base_url:null the save - // must still inject the All-Hands proxy URL, otherwise the profile is - // stranded on the next save from the Basic tab. - const user = userEvent.setup(); - vi.mocked(ProfilesService.getProfile).mockResolvedValue({ - name: "gpt-4-profile", - api_key_set: true, - config: { - model: "litellm_proxy/claude-opus-4-8", - api_key: "gAAAA_encrypted_key", - base_url: null, - }, - }); - mockSaveMutateAsync.mockResolvedValueOnce({ success: true }); - - renderWithProviders(); - - await user.click(screen.getAllByTestId("profile-menu-trigger")[0]); - await user.click(screen.getByTestId("profile-edit")); - await waitFor(() => { - expect(screen.getByTestId("profile-name-input")).toHaveValue( - "gpt-4-profile", - ); - }); - await user.click(await screen.findByTestId("sdk-section-basic-toggle")); - await waitFor(() => { - expect(screen.getByTestId("save-profile-btn")).not.toBeDisabled(); - }); - await user.click(screen.getByTestId("save-profile-btn")); - - // Assert — even though the server returned base_url:null, the Basic-tab - // save must inject the proxy URL so the profile stays usable. - await waitFor(() => expect(mockSaveMutateAsync).toHaveBeenCalled()); - const savedLlm = mockSaveMutateAsync.mock.calls[0][0].request.llm; - expect(savedLlm.model).toBe("litellm_proxy/claude-opus-4-8"); - expect(savedLlm.base_url).toBe(OPENHANDS_LLM_PROXY_BASE_URL); - }); }); describe("All tab save", () => { diff --git a/__tests__/scripts/dev-safe.test.ts b/__tests__/scripts/dev-safe.test.ts index ee062d2d2e..9bfc8e8638 100644 --- a/__tests__/scripts/dev-safe.test.ts +++ b/__tests__/scripts/dev-safe.test.ts @@ -389,16 +389,16 @@ describe("buildAgentServerCommand", () => { // Defaults to the released PyPI version with all SDK packages pinned to same version expect(cmd.args).toEqual([ "--from", - "openhands-agent-server==1.28.1", + "openhands-agent-server==1.27.0", "--with", - "openhands-sdk==1.28.1", + "openhands-sdk==1.27.0", "--with", - "openhands-tools==1.28.1", + "openhands-tools==1.27.0", "--with", - "openhands-workspace==1.28.1", + "openhands-workspace==1.27.0", "agent-server", ]); - expect(cmd.source).toBe("PyPI (1.28.1, default)"); + expect(cmd.source).toBe("PyPI (1.27.0, default)"); }); it("uses specific PyPI version when OH_AGENT_SERVER_VERSION is set with all packages pinned", () => { diff --git a/config/defaults.json b/config/defaults.json index e6fdfa971b..b30a64ab41 100644 --- a/config/defaults.json +++ b/config/defaults.json @@ -2,10 +2,10 @@ "_comment": "Single source of truth for version pins, ports, paths, and defaults shared across the npm and Docker install paths. Read by scripts/dev-safe.mjs, scripts/dev-with-automation.mjs, docker/entrypoint.sh (via generated defaults.env), and .github/workflows/docker.yml.", "versions": { - "agentServer": "1.28.1", + "agentServer": "1.27.0", "agentCanvas": "1.0.0-rc.6", - "automation": "1.0.0a9", - "automationSdk": "1.28.1", + "automation": "1.0.0a7", + "automationSdk": "1.27.0", "githubMcpServer": "1.2.0" }, diff --git a/package-lock.json b/package-lock.json index df22f98b84..34a3d55737 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@heroui/react": "2.8.10", "@microlink/react-json-view": "1.31.20", "@monaco-editor/react": "4.7.0", - "@openhands/extensions": "0.4.1", + "@openhands/extensions": "0.4.0", "@openhands/typescript-client": "1.24.3", "@react-router/node": "7.17.0", "@react-router/serve": "7.17.0", @@ -3468,9 +3468,9 @@ "license": "MIT" }, "node_modules/@openhands/extensions": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@openhands/extensions/-/extensions-0.4.1.tgz", - "integrity": "sha512-wpoLYiuy4aiaiLeJpUdkoc2oK861/u5uc8rvZD9s+98fDS8vjnDSImEIG35XKsKT6xMsKlOK6pTpdgNfb8sB8w==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@openhands/extensions/-/extensions-0.4.0.tgz", + "integrity": "sha512-cz0k8/rl/rfsD8rAatU1P0SOj/LKaSy+aUu9qp11yEfl0yF2D1vltY4XYa84LmTkTwxtOwDbKtqRPdZ6GJ5b/A==", "license": "MIT", "engines": { "node": ">=18.20.0" diff --git a/package.json b/package.json index 5b7a31e807..4a24c1235d 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@heroui/react": "2.8.10", "@microlink/react-json-view": "1.31.20", "@monaco-editor/react": "4.7.0", - "@openhands/extensions": "0.4.1", + "@openhands/extensions": "0.4.0", "@openhands/typescript-client": "1.24.3", "@react-router/node": "7.17.0", "@react-router/serve": "7.17.0", diff --git a/scripts/check-sdk-version-sync.mjs b/scripts/check-sdk-version-sync.mjs index b21e78cd57..582ef0c9aa 100644 --- a/scripts/check-sdk-version-sync.mjs +++ b/scripts/check-sdk-version-sync.mjs @@ -19,7 +19,7 @@ * * Usage: * node scripts/check-sdk-version-sync.mjs - * EXPECTED_SDK_VERSION=1.28.1 node scripts/check-sdk-version-sync.mjs + * EXPECTED_SDK_VERSION=1.27.0 node scripts/check-sdk-version-sync.mjs * node scripts/check-sdk-version-sync.mjs --check-pypi * * Environment variables: @@ -79,7 +79,7 @@ Triggering from other repos: -H "Authorization: token \$GITHUB_TOKEN" \\ -H "Accept: application/vnd.github.v3+json" \\ https://api.github.com/repos/OpenHands/agent-canvas/dispatches \\ - -d '{"event_type": "sdk-version-check", "client_payload": {"version": "1.28.1"}}' + -d '{"event_type": "sdk-version-check", "client_payload": {"version": "1.27.0"}}' `); process.exit(0); } @@ -268,9 +268,9 @@ async function fetchPyPIDependencies(packageName, version) { * Parse PyPI requires_dist array and extract SDK package versions * * PyPI returns dependencies in PEP 508 format like: - * "openhands-sdk>=1.28.1,<2.0.0" - * "openhands-tools==1.28.1" - * "openhands-workspace (>=1.28.1)" + * "openhands-sdk>=1.27.0,<2.0.0" + * "openhands-tools==1.27.0" + * "openhands-workspace (>=1.27.0)" */ function parseSdkVersionsFromRequiresDist(requiresDist) { const versions = {}; @@ -284,7 +284,7 @@ function parseSdkVersionsFromRequiresDist(requiresDist) { } // Extract the version number - look for patterns like: - // ">=1.28.1", "==1.28.1", "(>=1.28.1)", "~=1.28.1" + // ">=1.27.0", "==1.27.0", "(>=1.27.0)", "~=1.27.0" // After the package name and before any comma or closing paren const versionPattern = /[><=~!]+\s*([0-9]+(?:\.[0-9]+)*)/; const match = dep.match(versionPattern); diff --git a/scripts/dev-safe.mjs b/scripts/dev-safe.mjs index 284d1776e9..cac5b7ebc8 100644 --- a/scripts/dev-safe.mjs +++ b/scripts/dev-safe.mjs @@ -394,7 +394,7 @@ export function validateFrontendDependencies( * edits are picked up without a manual reinstall. The agent-server itself * is rebuilt from local source on each invocation (--reinstall). * - OH_AGENT_SERVER_GIT_REF: Git commit SHA or branch name - * - OH_AGENT_SERVER_VERSION: Specific PyPI version (e.g., "1.28.1") + * - OH_AGENT_SERVER_VERSION: Specific PyPI version (e.g., "1.27.0") * * If none are set, defaults to the released version specified by * DEFAULT_AGENT_SERVER_VERSION. Set OH_AGENT_SERVER_GIT_REF to use a diff --git a/src/components/features/settings/llm-profiles/llm-settings-local-view.tsx b/src/components/features/settings/llm-profiles/llm-settings-local-view.tsx index 2909785410..3309671f4a 100644 --- a/src/components/features/settings/llm-profiles/llm-settings-local-view.tsx +++ b/src/components/features/settings/llm-profiles/llm-settings-local-view.tsx @@ -255,26 +255,13 @@ export function LlmSettingsLocalView() { // The Basic tab has no base_url field; the provider implies it. Persist // the All-Hands proxy explicitly for OpenHands models — including ones the - // SDK has already rewritten to `litellm_proxy/*` — because local + // SDK has already rewritten to `litellm_proxy/*` — because older local // agent-server builds do not infer the LiteLLM proxy api_base on their own, // and dropping it strands the profile as `litellm_proxy/* + base_url:null` // (issue #1146). For other providers, drop any stale custom value and let // the backend use its normal provider defaults. - // - // Agent-server ≥1.28 may omit the default base_url from profile configs, - // causing the stored value to be null even for genuine OpenHands proxy - // models. Treat litellm_proxy/* with a missing base_url the same as - // litellm_proxy/* with the proxy URL already set. if (saveControl.view === "basic") { - const model = llmConfig.model; - const baseUrl = llmConfig.base_url; - const isProxy = isOpenHandsProxyModel(model, baseUrl); - const isLitellmProxyWithMissingBaseUrl = - !isProxy && - typeof model === "string" && - model.startsWith("litellm_proxy/") && - !baseUrl; - if (isProxy || isLitellmProxyWithMissingBaseUrl) { + if (isOpenHandsProxyModel(llmConfig.model, llmConfig.base_url)) { llmConfig.base_url = OPENHANDS_LLM_PROXY_BASE_URL; } else { delete llmConfig.base_url; diff --git a/tests/e2e/mock-llm/mock-llm-profile-management.spec.ts b/tests/e2e/mock-llm/mock-llm-profile-management.spec.ts index 5a66ed0eff..706387237b 100644 --- a/tests/e2e/mock-llm/mock-llm-profile-management.spec.ts +++ b/tests/e2e/mock-llm/mock-llm-profile-management.spec.ts @@ -70,46 +70,6 @@ async function getProfileConfig( return (data.config ?? {}) as Record; } -/** - * Assert that a proxy profile config is valid after a Basic-tab re-save. - * - * Agent-server ≥1.28 normalises `litellm_proxy/*` → `openhands/*` on storage - * and manages the proxy URL internally (returning base_url:null). Both - * representations are accepted here. The original issue #1146 regression - * (litellm_proxy/* with null base_url) is still guarded explicitly. - */ -function assertProxyProfileConfig( - config: Record, - litellmProxyModel: string, - openHandsEquivalentModel: string, - proxyBaseUrl: string, -) { - const model = config.model as string | null | undefined; - const baseUrl = config.base_url as string | null | undefined; - - const isLitellmForm = - typeof model === "string" && model.startsWith("litellm_proxy/"); - const isOpenHandsForm = - typeof model === "string" && model.startsWith("openhands/"); - - expect( - isLitellmForm || isOpenHandsForm, - `model must be ${litellmProxyModel} or ${openHandsEquivalentModel}; got: ${model}`, - ).toBe(true); - - if (isLitellmForm) { - // A litellm_proxy/* profile without the proxy URL is stranded (issue #1146). - // The frontend must always inject the URL when saving from the Basic tab. - expect( - baseUrl, - "base_url must be the All-Hands proxy URL for litellm_proxy/* profiles " + - "(dropping it strands the profile — issue #1146)", - ).toBe(proxyBaseUrl); - } - // For openhands/* (agent-server ≥1.28 rewrite), null base_url is correct; - // the server routes the request through the proxy internally. -} - test.describe.configure({ mode: "serial" }); // ═══════════════════════════════════════════════════════════════════════ @@ -397,13 +357,8 @@ test.describe("litellm_proxy proxy base_url preservation", () => { // Simulates the state the SDK persists after onboarding through the // OpenHands provider: openhands/* is rewritten to litellm_proxy/* and // paired with the All-Hands proxy base URL. - // - // Agent-server ≥1.28 additionally normalises litellm_proxy/* back to - // openhands/* on storage and manages the proxy URL internally (returning - // base_url:null). The assertions below accept both representations. const PROXY_PROFILE = "proxy-base-url-test"; const LITELLM_PROXY_MODEL = "litellm_proxy/claude-opus-4-8"; - const OPENHANDS_EQUIVALENT_MODEL = "openhands/claude-opus-4-8"; const OPENHANDS_PROXY_BASE_URL = "https://llm-proxy.app.all-hands.dev/"; test.beforeEach(async ({ page }) => { @@ -496,15 +451,15 @@ test.describe("litellm_proxy proxy base_url preservation", () => { await waitForTestId(page, "add-llm-profile"); }); - // ── Verify: the proxy setup survived the Basic-tab save ── - await test.step("verify proxy setup is preserved after save", async () => { + // ── Verify: the proxy base_url survived the Basic-tab save ── + await test.step("verify base_url is preserved after save", async () => { const config = await getProfileConfig(request, PROXY_PROFILE); - assertProxyProfileConfig( - config, - LITELLM_PROXY_MODEL, - OPENHANDS_EQUIVALENT_MODEL, - OPENHANDS_PROXY_BASE_URL, - ); + expect( + config.base_url, + "base_url must be the All-Hands proxy URL after a Basic-tab re-save; " + + "dropping it strands the profile (issue #1146)", + ).toBe(OPENHANDS_PROXY_BASE_URL); + expect(config.model).toBe(LITELLM_PROXY_MODEL); }); // ── Verify: the profile also looks correct after a page reload ── @@ -514,12 +469,8 @@ test.describe("litellm_proxy proxy base_url preservation", () => { // Re-read via API to confirm persistence is durable const config = await getProfileConfig(request, PROXY_PROFILE); - assertProxyProfileConfig( - config, - LITELLM_PROXY_MODEL, - OPENHANDS_EQUIVALENT_MODEL, - OPENHANDS_PROXY_BASE_URL, - ); + expect(config.base_url).toBe(OPENHANDS_PROXY_BASE_URL); + expect(config.model).toBe(LITELLM_PROXY_MODEL); }); }); });