Skip to content

feat(api,sdk,cli): add unauthenticated version endpoint#1602

Merged
mergify[bot] merged 2 commits into
mainfrom
worktree-version-endpoint
May 21, 2026
Merged

feat(api,sdk,cli): add unauthenticated version endpoint#1602
mergify[bot] merged 2 commits into
mainfrom
worktree-version-endpoint

Conversation

@jeremyeder
Copy link
Copy Markdown
Contributor

@jeremyeder jeremyeder commented May 21, 2026

This PR uses the spec I generated off #1598

Summary

  • Add GET /api/ambient/v1/version returning build metadata (version, build_time, git_tag) without authentication
  • Pre-auth middleware pattern (same as proxy plugin) with pre-marshaled JSON response for zero per-request allocation
  • Full-stack: api-server plugin, Makefile/Dockerfile ldflags, Go SDK, Python SDK, acpctl version client+server output
  • Includes implementation plan at docs/plans/2026-05-20-001-feat-version-endpoint-plan.md

Test plan

  • cd components/ambient-api-server && go build ./... && go vet ./...
  • cd components/ambient-sdk/go-sdk && go build ./...
  • cd components/ambient-cli && go build ./...
  • python3 -c "from ambient_platform._version_api import fetch_server_version"
  • curl http://localhost:8000/api/ambient/v1/version returns JSON without auth
  • acpctl version shows both client and server lines

Closes #1598

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added an unauthenticated /api/ambient/v1/version endpoint that exposes version, build time, and git tag information as JSON
    • Enhanced the acpctl version command to fetch and display both client and server version information with build metadata
    • Added server version retrieval methods to the Go and Python SDKs for programmatic access to version details

Ambient Code Bot and others added 2 commits May 20, 2026 23:22
Add GET /api/ambient/v1/version returning build metadata (git SHA, build
time, git tag) without requiring authentication. Uses pre-auth middleware
pattern from the proxy plugin with a pre-marshaled JSON response for zero
per-request allocation.

Full-stack implementation:
- ambient-api-server: version plugin with RegisterPreAuthMiddleware
- Makefile/Dockerfile: inject git_tag via ldflags and build-args
- Go SDK: Client.ServerVersion() + standalone FetchServerVersion()
- Python SDK: fetch_server_version() with ServerVersion dataclass
- acpctl: version command now shows client and server version

Closes #1598

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Spec covering the unauthenticated /version endpoint design: pre-auth
middleware pattern, pre-marshaled response, full-stack implementation
units (api-server, Makefile, Dockerfile, Go SDK, Python SDK, CLI).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jeremyeder jeremyeder added the ambient-code:self-reviewed Self-reviewed by Ambient agent label May 21, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

📝 Walkthrough

Walkthrough

This PR implements an unauthenticated /api/ambient/v1/version endpoint in ambient-api-server that exposes build metadata (git version, build time, git tag). Build-time variables are injected via ldflags through Makefile and Dockerfile, exposed through Go SDK and Python SDK clients, and queried by acpctl version command with graceful fallback.

Changes

Version Endpoint Implementation

Layer / File(s) Summary
Build metadata injection and computation
components/ambient-api-server/Dockerfile, components/ambient-api-server/Makefile
Dockerfile builder stage accepts GIT_VERSION, BUILD_TIME, GIT_TAG as build args and injects them into go build ldflags targeting pkg/api fields. Makefile computes git_tag via git describe and passes all three as --build-arg values to container build.
API version variables declaration
components/ambient-api-server/pkg/api/api.go
Three exported string variables (Version, BuildTime, GitTag) are declared and populated by ldflags during binary compilation.
Server-side version endpoint plugin
components/ambient-api-server/plugins/version/plugin.go, components/ambient-api-server/cmd/ambient-api-server/main.go
New version plugin serves GET /api/ambient/v1/version as pre-auth middleware with cached JSON response. Package-level init() pre-marshals versionResponse struct from pkg/api variables. Plugin is registered via blank import in main.go.
Go SDK version client
components/ambient-sdk/go-sdk/client/version_api.go
ServerVersion struct and two methods: (*Client).ServerVersion(ctx) for authenticated requests, and standalone FetchServerVersion(ctx, baseURL, insecureSkipVerify) for unauthenticated requests with configurable TLS verification and 10s timeout.
Python SDK version client
components/ambient-sdk/python-sdk/ambient_platform/_version_api.py
ServerVersion frozen dataclass with from_dict constructor and fetch_server_version(base_url, *, timeout=10.0, verify_ssl=True) function for unauthenticated requests with configurable timeout and TLS verification.
CLI version command enhancement
components/ambient-cli/cmd/acpctl/version/cmd.go
acpctl version now prints separate Client and Server lines. Loads config, derives API URL, fetches server version with 5s timeout, and prints "unavailable" with error detail if fetch fails or config missing.
Feature plan documentation
docs/plans/2026-05-20-001-feat-version-endpoint-plan.md
Plan document specifying feature goal, design decisions, implementation units U1–U6 (ldflags variables, plugin, Makefile/Dockerfile, Go SDK, Python SDK, CLI), system impact, and risks including pre-auth middleware ordering and empty ldflags in dev environments.
🚥 Pre-merge checks | ✅ 7 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (7 passed)
Check name Status Explanation
Title check ✅ Passed Title follows Conventional Commits format with feat type and scope, and accurately summarizes the main change: adding an unauthenticated version endpoint across API, SDK, and CLI.
Linked Issues check ✅ Passed PR fully implements the objectives from #1598: declares Version/BuildTime/GitTag vars, adds version plugin via pre-auth middleware, injects metadata via ldflags, and extends SDK/CLI with version retrieval.
Out of Scope Changes check ✅ Passed All changes are in-scope. The implementation plan doc and SDK/CLI extensions align with the endpoint feature. No unrelated modifications detected.
Performance And Algorithmic Complexity ✅ Passed No performance regressions detected. Version endpoint uses pre-marshaled JSON, no loops with expensive work, O(1) middleware check, bounded timeouts, no N+1 or unbounded growth.
Security And Secret Handling ✅ Passed No secrets, credentials, or injection vulnerabilities found. Endpoint intentionally unauthenticated via RegisterPreAuthMiddleware. Only safe metadata exposed: git SHA, timestamp, version tag.
Kubernetes Resource Safety ✅ Passed PR contains no Kubernetes manifests or resource definitions; check is not applicable to source code, build, and documentation changes.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-version-endpoint
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch worktree-version-endpoint

Comment @coderabbitai help to get the list of available commands and usage tips.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 21, 2026

Deploy Preview for cheerful-kitten-f556a0 ready!

Name Link
🔨 Latest commit 8bdf93a
🔍 Latest deploy log https://app.netlify.com/projects/cheerful-kitten-f556a0/deploys/6a0e7d4ec1554e0007497c32
😎 Deploy Preview https://deploy-preview-1602--cheerful-kitten-f556a0.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
components/ambient-sdk/python-sdk/ambient_platform/_version_api.py (1)

16-21: ⚡ Quick win

Make from_dict’s input type more precise (API contract)

from_dict currently accepts data: dict, which drops key/value types. Use a typed mapping (e.g., Mapping[str, Any]) and adjust imports.

Proposed fix
-from typing import Optional
+from typing import Any, Mapping
@@
-    def from_dict(cls, data: dict) -> ServerVersion:
+    def from_dict(cls, data: Mapping[str, Any]) -> ServerVersion:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/ambient-sdk/python-sdk/ambient_platform/_version_api.py` around
lines 16 - 21, Change the from_dict signature to accept a precise typed mapping
instead of plain dict: update ServerVersion.from_dict to def from_dict(cls,
data: Mapping[str, Any]) -> ServerVersion and add the required imports (Mapping,
Any) from typing; keep the body unchanged but rely on the typed input to reflect
the API contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/ambient-api-server/plugins/version/plugin.go`:
- Around line 17-27: The code currently discards errors from json.Marshal when
building responseBytes and from w.Write when serving the version endpoint;
update the versionResponse creation and the handler returned by
pkgserver.RegisterPreAuthMiddleware to check and handle both errors: capture the
error returned by json.Marshal(versionResponse{...}) (e.g., log it and set
responseBytes to a safe fallback or abort startup) and capture the error from
w.Write(responseBytes) inside the handler and log/handle it and, if appropriate,
write an HTTP 500 or stop further writes; modify the symbols responseBytes,
json.Marshal call, and the http.HandlerFunc returned by
pkgserver.RegisterPreAuthMiddleware (the handler that checks r.Method and
versionPath) to perform these error checks and logging.

In `@components/ambient-cli/cmd/acpctl/version/cmd.go`:
- Around line 24-30: The current code silently returns when err != nil and when
apiURL == "" which discards the failure context; update the error paths in the
version command handler (the function containing the err check and apiURL :=
cfg.GetAPIUrl()) to return or propagate a descriptive error instead of a naked
return (e.g., wrap and return the original err when cfg retrieval fails, and
return a specific error when apiURL is empty), so callers see why server-version
lookup failed and can log or surface the cause.
- Line 37: Replace the user-facing printf that prints the raw error (the
fmt.Fprintf call writing to cmd.OutOrStdout() with "Server: unavailable (%v)\n",
err) with a generic message such as "Server: unavailable\n" for stdout, and send
the detailed err to a non-user-facing sink (e.g., cmd.ErrOrStderr() or existing
logger) so internal/transport details are not leaked; keep the generic message
in the output and log the full error separately for debugging.

---

Nitpick comments:
In `@components/ambient-sdk/python-sdk/ambient_platform/_version_api.py`:
- Around line 16-21: Change the from_dict signature to accept a precise typed
mapping instead of plain dict: update ServerVersion.from_dict to def
from_dict(cls, data: Mapping[str, Any]) -> ServerVersion and add the required
imports (Mapping, Any) from typing; keep the body unchanged but rely on the
typed input to reflect the API contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b63dfead-2645-4231-a8b3-a3b3e5cb723a

📥 Commits

Reviewing files that changed from the base of the PR and between 0fa9dea and 8bdf93a.

📒 Files selected for processing (9)
  • components/ambient-api-server/Dockerfile
  • components/ambient-api-server/Makefile
  • components/ambient-api-server/cmd/ambient-api-server/main.go
  • components/ambient-api-server/pkg/api/api.go
  • components/ambient-api-server/plugins/version/plugin.go
  • components/ambient-cli/cmd/acpctl/version/cmd.go
  • components/ambient-sdk/go-sdk/client/version_api.go
  • components/ambient-sdk/python-sdk/ambient_platform/_version_api.py
  • docs/plans/2026-05-20-001-feat-version-endpoint-plan.md

Comment on lines +17 to +27
responseBytes, _ = json.Marshal(versionResponse{
Version: localapi.Version,
BuildTime: localapi.BuildTime,
GitTag: localapi.GitTag,
})

pkgserver.RegisterPreAuthMiddleware(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet && strings.TrimSuffix(r.URL.Path, "/") == versionPath {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(responseBytes)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle the marshal/write error paths instead of discarding them.

Line 17 and Line 27 both ignore errors. Please collect/log these explicitly so failures are not silent in this unauthenticated path.

Suggested fix
 import (
 	"encoding/json"
+	"log"
 	"net/http"
 	"strings"
@@
 func init() {
-	responseBytes, _ = json.Marshal(versionResponse{
+	var err error
+	responseBytes, err = json.Marshal(versionResponse{
 		Version:   localapi.Version,
 		BuildTime: localapi.BuildTime,
 		GitTag:    localapi.GitTag,
 	})
+	if err != nil {
+		log.Printf("version plugin: failed to marshal version payload: %v", err)
+		responseBytes = []byte(`{"version":"","build_time":"","git_tag":""}`)
+	}
@@
-				_, _ = w.Write(responseBytes)
+				if _, err := w.Write(responseBytes); err != nil {
+					log.Printf("version plugin: failed to write response: %v", err)
+				}
 				return
 			}

As per coding guidelines, "Never silently swallow partial failures; every error path must propagate or be explicitly collected, never discarded".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
responseBytes, _ = json.Marshal(versionResponse{
Version: localapi.Version,
BuildTime: localapi.BuildTime,
GitTag: localapi.GitTag,
})
pkgserver.RegisterPreAuthMiddleware(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet && strings.TrimSuffix(r.URL.Path, "/") == versionPath {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(responseBytes)
var err error
responseBytes, err = json.Marshal(versionResponse{
Version: localapi.Version,
BuildTime: localapi.BuildTime,
GitTag: localapi.GitTag,
})
if err != nil {
log.Printf("version plugin: failed to marshal version payload: %v", err)
responseBytes = []byte(`{"version":"","build_time":"","git_tag":""}`)
}
pkgserver.RegisterPreAuthMiddleware(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet && strings.TrimSuffix(r.URL.Path, "/") == versionPath {
w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(responseBytes); err != nil {
log.Printf("version plugin: failed to write response: %v", err)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/ambient-api-server/plugins/version/plugin.go` around lines 17 -
27, The code currently discards errors from json.Marshal when building
responseBytes and from w.Write when serving the version endpoint; update the
versionResponse creation and the handler returned by
pkgserver.RegisterPreAuthMiddleware to check and handle both errors: capture the
error returned by json.Marshal(versionResponse{...}) (e.g., log it and set
responseBytes to a safe fallback or abort startup) and capture the error from
w.Write(responseBytes) inside the handler and log/handle it and, if appropriate,
write an HTTP 500 or stop further writes; modify the symbols responseBytes,
json.Marshal call, and the http.HandlerFunc returned by
pkgserver.RegisterPreAuthMiddleware (the handler that checks r.Method and
versionPath) to perform these error checks and logging.

Comment on lines +24 to +30
if err != nil {
return
}
apiURL := cfg.GetAPIUrl()
if apiURL == "" {
return
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not silently drop server-version failure states

Line 24 and Line 28 return without surfacing why server version is missing. This hides partial failures and makes troubleshooting harder.

Suggested fix
 		cfg, err := config.Load()
 		if err != nil {
+			fmt.Fprintln(cmd.OutOrStdout(), "Server: unavailable (config not loaded)")
 			return
 		}
 		apiURL := cfg.GetAPIUrl()
 		if apiURL == "" {
+			fmt.Fprintln(cmd.OutOrStdout(), "Server: unavailable (API URL not configured)")
 			return
 		}

As per coding guidelines, "**/*.go: Never silently swallow partial failures; every error path must propagate or be explicitly collected, never discarded".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if err != nil {
return
}
apiURL := cfg.GetAPIUrl()
if apiURL == "" {
return
}
if err != nil {
fmt.Fprintln(cmd.OutOrStdout(), "Server: unavailable (config not loaded)")
return
}
apiURL := cfg.GetAPIUrl()
if apiURL == "" {
fmt.Fprintln(cmd.OutOrStdout(), "Server: unavailable (API URL not configured)")
return
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/ambient-cli/cmd/acpctl/version/cmd.go` around lines 24 - 30, The
current code silently returns when err != nil and when apiURL == "" which
discards the failure context; update the error paths in the version command
handler (the function containing the err check and apiURL := cfg.GetAPIUrl()) to
return or propagate a descriptive error instead of a naked return (e.g., wrap
and return the original err when cfg retrieval fails, and return a specific
error when apiURL is empty), so callers see why server-version lookup failed and
can log or surface the cause.


sv, err := sdkclient.FetchServerVersion(ctx, apiURL, cfg.InsecureTLSVerify)
if err != nil {
fmt.Fprintf(cmd.OutOrStdout(), "Server: unavailable (%v)\n", err)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid printing raw errors in user-facing version output

Line 37 includes %v from the fetch error directly in stdout. That can leak endpoint/internal transport details; prefer a generic message for end users.

Suggested fix
-			fmt.Fprintf(cmd.OutOrStdout(), "Server: unavailable (%v)\n", err)
+			fmt.Fprintln(cmd.OutOrStdout(), "Server: unavailable")
 			return

As per coding guidelines, "**/*.go: Never log, error, or return tokens directly in responses; use len(token) for logging and provide generic messages to users".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fmt.Fprintf(cmd.OutOrStdout(), "Server: unavailable (%v)\n", err)
fmt.Fprintln(cmd.OutOrStdout(), "Server: unavailable")
return
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/ambient-cli/cmd/acpctl/version/cmd.go` at line 37, Replace the
user-facing printf that prints the raw error (the fmt.Fprintf call writing to
cmd.OutOrStdout() with "Server: unavailable (%v)\n", err) with a generic message
such as "Server: unavailable\n" for stdout, and send the detailed err to a
non-user-facing sink (e.g., cmd.ErrOrStderr() or existing logger) so
internal/transport details are not leaked; keep the generic message in the
output and log the full error separately for debugging.

@mergify mergify Bot added the queued label May 21, 2026
@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented May 21, 2026

Merge Queue Status

  • Entered queue2026-05-21 03:44 UTC · Rule: default
  • Checks skipped · PR is already up-to-date
  • Merged2026-05-21 03:45 UTC · at 8bdf93afc8c5c76218b86402c073bf438dcb29f9 · squash

This pull request spent 57 seconds in the queue, including 15 seconds running CI.

Required conditions to merge

@mergify mergify Bot merged commit 7e78baf into main May 21, 2026
58 of 60 checks passed
@mergify mergify Bot deleted the worktree-version-endpoint branch May 21, 2026 03:45
@mergify mergify Bot removed the queued label May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ambient-code:self-reviewed Self-reviewed by Ambient agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add unauthenticated /version endpoint to ambient-api-server

1 participant