Skip to content

feat: report SDK version to cloud API and surface upgrade hints#21

Open
k-taro56 wants to merge 3 commits intomainfrom
eng-352
Open

feat: report SDK version to cloud API and surface upgrade hints#21
k-taro56 wants to merge 3 commits intomainfrom
eng-352

Conversation

@k-taro56
Copy link
Copy Markdown
Contributor

Summary

  • Stamp every outbound cloud API request with X-Arkor-Client: arkor/<semver> so the server can gate on the SDK version.
  • Render the cloud API's 426 Upgrade Required response to stderr with a package-manager-aware install command, and exit 1.
  • Read the standard Deprecation (RFC 9745), Sunset (RFC 8594), and Warning (RFC 7234) response headers and surface a one-line notice at the end of every CLI invocation.

What's new

  • SDK_VERSION constant inlined at build time via tsdown's define, so arkor --version and the wire-level header always match package.json.

  • X-Arkor-Client request header added to every outbound call: the typed Hono RPC client, the SSE event stream and chat fetches in core/client.ts, and the Studio proxies.

  • Package-manager detection in core/upgrade-hint.ts inspects npm_config_user_agent first, then falls back to a process.argv[1] path heuristic:

    detected install command shown
    npm npm install -g arkor@latest
    pnpm pnpm add -g arkor@latest
    yarn yarn global add arkor@latest
    bun bun add -g arkor@latest
  • Studio proxy (studio/server.ts) copies upstream Deprecation/Sunset/Warning response headers to the browser SPA so Studio-only users see warnings too.

  • arkor whoami on 426 sets process.exitCode = 1 rather than calling process.exit(), so the deprecation-warning flush in cli/main.ts still runs before exit. Scripts gating on arkor whoami now correctly fail-fast against an unsupported SDK.

Requires

The matching @arkor/cloud-api-client alpha that exports clientVersion, onDeprecation, upgradeMessageFromBody, and parseErrorBody.

Test plan

  • pnpm test — new core/upgrade-hint.test.ts covers the user-agent and path heuristics, including Windows-style separators, plus the npm/pnpm/yarn/bun command mapping.
  • SKIP_E2E_INSTALL=1 pnpm --filter @arkor/e2e-cli test — new arkor-whoami.test.ts spins up an in-process fake cloud API and asserts:
    • arkor whoami against a 426 → exit 1 + pm-aware stderr message for each of npm / pnpm / yarn / bun
    • arkor whoami against 200 + Deprecation: true → body rendered + deprecation warning at end with pm-aware install command
    • X-Arkor-Client: arkor/<version> is forwarded on every request
  • pnpm build && pnpm pack --pack-destination /tmp/ — leak grep tar -xzOf /tmp/arkor-*.tgz | grep -cE 'drizzle|control-plane' returns 0.

@k-taro56 k-taro56 self-assigned this Apr 27, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 27, 2026

Greptile Summary

This PR stamps every outbound cloud API request with X-Arkor-Client: arkor/<semver>, handles 426 Upgrade Required responses with a package-manager-aware install hint and correct exit 1, and surfaces Deprecation/Sunset/Warning headers as a one-line notice at CLI shutdown.

  • P1 – studio/server.ts: Three outbound call sites were not updated — the /api/jobs createClient call is missing clientVersion/onDeprecation, and the raw fetch calls for /api/jobs/:id/events (SSE) and /api/inference/chat both omit the X-Arkor-Client header — so the cloud API cannot version-gate or report deprecation for Studio-originated traffic on those routes.

Confidence Score: 4/5

Safe to merge after addressing three missing X-Arkor-Client / deprecation-wiring gaps in the Studio server.

The CLI path (whoami, main.ts, core/client.ts) is well-implemented and the prior P1 concern about the 426 fallback is fully addressed. However, studio/server.ts has three outbound call sites that were not updated with the new version header or deprecation wiring, leaving Studio-originated traffic invisible to the cloud API's version gate.

packages/arkor/src/studio/server.ts — three call sites need X-Arkor-Client and/or clientVersion/onDeprecation.

Important Files Changed

Filename Overview
packages/arkor/src/studio/server.ts Adds SDK version header and deprecation forwarding for /api/me, but three other outbound call sites (/api/jobs createClient, /api/jobs/:id/events SSE fetch, /api/inference/chat fetch) are missing X-Arkor-Client and deprecation wiring.
packages/arkor/src/core/upgrade-hint.ts New helper for package-manager detection and 426 upgrade message formatting; clean design with good test coverage.
packages/arkor/src/core/deprecation.ts New module-level singleton for recording deprecation notices; works correctly for CLI use but the singleton is never reset, which may affect library consumers or test suites running multiple requests in one process.
packages/arkor/src/cli/commands/whoami.ts 426 handling is now correct with unconditional exit-1 and fallback message; void CloudApiClient workaround is a minor smell but doesn't affect behaviour.
packages/arkor/src/cli/main.ts Deprecation flush in finally block is a clean, correct approach; SDK_VERSION plumbed into Commander version string.
packages/arkor/src/core/client.ts Adds X-Arkor-Client header to SSE and chat fetches, tapDeprecation calls, and refactors error building into buildCloudApiError; consistent and well-structured.
packages/arkor/src/core/version.ts Tiny new file; __SDK_VERSION__ define with vitest fallback is correct.
packages/arkor/tsdown.config.ts Adds define to inline package.json version at build time; straightforward and correct.
e2e/cli/src/arkor-whoami.test.ts Comprehensive E2E test covering 426 with/without valid body, all four package managers, header forwarding, and deprecation warning rendering.
packages/arkor/src/core/upgrade-hint.test.ts Good unit coverage for user-agent and path-based pm detection including Windows separators.

Sequence Diagram

sequenceDiagram
    participant CLI as arkor CLI / Studio SPA
    participant Main as cli/main.ts
    participant Whoami as commands/whoami.ts
    participant Client as core/client.ts
    participant Dep as core/deprecation.ts
    participant Cloud as Cloud API

    CLI->>Main: main(argv)
    Main->>Whoami: runWhoami()
    Whoami->>Cloud: GET /v1/me (X-Arkor-Client: arkor/x.y.z)
    alt 426 Upgrade Required
        Cloud-->>Whoami: 426 + body
        Whoami->>Whoami: formatSdkUpgradeError(body)
        Whoami->>CLI: stderr: upgrade message
        Whoami->>Main: process.exitCode = 1
    else 200 OK + Deprecation: true
        Cloud-->>Whoami: 200 + Deprecation/Sunset/Warning headers
        Whoami->>Dep: onDeprecation callback → recordDeprecation()
        Whoami-->>Main: return (success)
    else 200 OK
        Cloud-->>Whoami: 200
        Whoami-->>Main: return (success)
    end
    Note over Main: finally block
    Main->>Dep: getRecordedDeprecation()
    alt notice present
        Dep-->>Main: DeprecationNotice
        Main->>CLI: log.warn(message + detectedUpgradeCommand())
    end
    Note over Client,Cloud: core/client.ts SSE & chat fetches
    Client->>Cloud: fetch (X-Arkor-Client: arkor/x.y.z)
    Cloud-->>Client: response
    Client->>Dep: tapDeprecation(res, SDK_VERSION)
Loading

Comments Outside Diff (1)

  1. packages/arkor/src/studio/server.ts, line 204 (link)

    P1 /api/jobs route missing clientVersion and onDeprecation

    The createClient call for the /api/jobs route was not updated with clientVersion: SDK_VERSION and onDeprecation: recordDeprecation, unlike the /api/me route (lines 172–177). Requests from this route won't carry the X-Arkor-Client header and won't surface deprecation notices, creating an inconsistency within the same server.

    Fix in Claude Code

Fix All in Claude Code

Reviews (2): Last reviewed commit: "test(e2e): add regression test for 426 s..." | Re-trigger Greptile

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 13cfddc1af

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +42 to +44
if (upgrade) {
process.stderr.write(`${upgrade}\n`);
// Hard-block: scripts that gate on `arkor whoami`'s exit code must
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fail whoami on every 426 response

process.exitCode = 1 is guarded by if (upgrade), so a 426 Upgrade Required response with a non-JSON or unexpected body (already possible because res.json() is wrapped in .catch(() => null)) can fall through to the generic path and return without setting a non-zero exit code. That makes arkor whoami succeed (0) even when the SDK is rejected by the server, which breaks scripts that rely on this command as a version gate.

Useful? React with 👍 / 👎.

Comment thread packages/arkor/src/core/client.ts Outdated
Comment on lines +57 to +58
const message =
upgrade ?? fields.error ?? text ?? `cloud-api ${res.status}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve fallback text for empty error bodies

The new message selection uses nullish coalescing with text, but text is initialized to "", so empty-body error responses now produce an empty CloudApiError.message instead of cloud-api <status>. This regresses diagnostics for non-OK responses that have no parsable payload, because callers lose the status-based fallback message entirely.

Useful? React with 👍 / 👎.

* (the SSE and chat endpoints in `core/client.ts`, and the studio proxy).
*/
export function tapDeprecation(res: Response, sdkVersion: string): void {
if (res.headers.get("Deprecation") !== "true") return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept RFC 9745 Deprecation header values

tapDeprecation only records notices when Deprecation equals the literal string "true", but RFC 9745 defines Deprecation as a structured date value (for example @1688169599). Any compliant date-valued header on raw fetch paths (SSE/chat and studio proxy flows that rely on tapDeprecation) will be ignored, so users will miss deprecation warnings and upgrade hints.

Useful? React with 👍 / 👎.

Comment thread packages/arkor/src/cli/commands/whoami.ts
Comment thread packages/arkor/src/studio/server.ts
Comment thread packages/arkor/src/core/deprecation.ts
- Added deprecation notice recording and retrieval functionality in `core/deprecation.ts`.
- Enhanced CLI commands to log deprecation warnings and include SDK version in responses.
- Updated `tsdown.config.ts` to define `__SDK_VERSION__` from `package.json`.
- Implemented version reporting in the CLI and API responses, ensuring users receive relevant upgrade hints.
- Introduced package manager detection to provide tailored upgrade commands based on the user's environment.
- Added tests for package manager detection and upgrade command generation.

This update improves user experience by providing clear deprecation warnings and version information, facilitating smoother upgrades.
- Introduced a new test suite for the `arkor whoami` command to validate its interaction with the cloud-api SDK version gate.
- Implemented a fake HTTP server to simulate API responses, including handling of 426 status codes for unsupported SDK versions.
- Added tests to ensure correct upgrade command suggestions based on the package manager used (npm, pnpm, bun, yarn).
- Verified that the X-Arkor-Client header is forwarded correctly and that deprecation warnings are surfaced appropriately.

This enhances the test coverage for the CLI, ensuring users receive accurate feedback regarding SDK support and upgrade paths.
- Introduced a new test case to ensure that the `arkor whoami` command exits with code 1 when receiving a 426 status with a non-JSON body.
- Updated the implementation in `whoami.ts` to handle 426 responses consistently, ensuring that users are informed about unsupported SDK versions.
- Enhanced error messaging to provide clear upgrade instructions, improving user experience during version conflicts.

This update strengthens the reliability of the CLI by preventing silent failures when the SDK is no longer supported.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: db5f50e43d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +172 to +176
const rpc = createClient({
baseUrl,
token: getToken,
clientVersion: SDK_VERSION,
onDeprecation: recordDeprecation,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stamp SDK version on all Studio cloud API calls

This change only wires clientVersion/onDeprecation into the /api/me proxy, but buildStudioApp still issues other cloud API requests (/api/jobs, /api/jobs/:id/events, /api/inference/chat) without X-Arkor-Client. If the server enforces the 426 SDK gate on those routes, Studio job lists/streams/chat can fail even when whoami works, because those requests are still effectively unversioned.

Useful? React with 👍 / 👎.

Comment on lines +129 to +133
try {
await program.parseAsync(argv, { from: "user" });
} finally {
const notice = getRecordedDeprecation();
if (notice) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Delay deprecation flush until command lifecycle ends

The deprecation warning flush now runs in main()'s finally, which executes as soon as parseAsync returns; for arkor dev, runDev returns immediately after starting the server, so this block runs before any later Studio proxy traffic can call recordDeprecation. In practice that means long-running invocations miss upgrade notices entirely unless they happened during startup.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant