Skip to content

refactor: SDK changes to support new API#746

Open
markturansky wants to merge 1 commit intoambient-code:mainfrom
markturansky:feat/ambient-sdk
Open

refactor: SDK changes to support new API#746
markturansky wants to merge 1 commit intoambient-code:mainfrom
markturansky:feat/ambient-sdk

Conversation

@markturansky
Copy link
Contributor

@markturansky markturansky commented Mar 1, 2026

Summary

  • Removed AMBIENT_PROJECT environment variable dependency
  • Updated ProjectBuilder to require explicit namespace
  • Modified client.NewClientFromEnv() to require project parameter
  • Updated .gitignore for SDK generator binaries
  • Bumped TRex dependency to v0.0.14

Motivation

Improve SDK usability by making project/namespace specification more explicit and reducing reliance on environment variables. Synchronize SDK with latest API dependency.

Test Plan

  • Verify changes in go-sdk/examples/main.go
  • Ensure NewClientFromEnv() requires project parameter
  • Verify ProjectBuilder now requires namespace
  • Confirm TRex dependency updated to v0.0.14

\ud83e\udd16 Generated with Claude Code

@markturansky markturansky changed the title refactor(sdk): remove AMBIENT_PROJECT env var, make namespace explicit refactor: SDK changes to support new API Mar 1, 2026
@github-actions

This comment has been minimized.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

Claude Code Review

Summary

This PR refactors the Ambient SDK to update the API base path from /api/ambient-api-server/v1 to /api/ambient/v1 across all three SDK clients (Go, Python, TypeScript), and adds real-time session watching via gRPC (Go) and SSE (Python, TypeScript). The URL path change is consistently applied. However, the PR has several blockers and critical bugs that must be resolved before merging.


Issues by Severity

🚫 Blocker Issues

1. Unresolved merge conflict in components/ambient-api-server/go.mod and go.sum

Lines 9 and 22 in the diff contain raw ======= conflict markers. This file will fail to parse as a valid go.mod:

github.com/openshift-online/rh-trex-ai v0.0.6
=======
github.com/openshift-online/rh-trex-ai v0.0.14

The build is broken as-is. The old version (v0.0.6) should be removed and only v0.0.14 retained, with the corresponding go.sum entries cleaned up.

2. Python gRPC watch imports a non-existent module

components/ambient-sdk/python-sdk/ambient_platform/session_watch.py line 133:

from . import _grpc_stubs  # This would be generated

The comment itself admits this module does not exist. Any call to the gRPC watch path will fail at runtime with ImportError. Either ship the generated stubs or remove the gRPC path from this PR.

3. Binary executables committed to git

Two compiled binaries are tracked:

  • components/ambient-sdk/bin/ambient-sdk-generator (new, 100755)
  • components/ambient-sdk/generator/ambient-sdk-generator (modified)

The PR description says .gitignore is updated to ignore generator binaries, but these files are being added in the same PR — which is contradictory. Binary blobs in git create bloat, break diffs, and can introduce supply-chain risks. Use a Makefile target or CI step to rebuild the generator from source instead.


🔴 Critical Issues

4. Immediate context cancellation bug in Go Watch() (session_watch.go:277–327)

if opts.Timeout > 0 {
    var cancel context.CancelFunc
    streamCtx, cancel = context.WithTimeout(streamCtx, opts.Timeout)
    defer cancel()   // fires when Watch() returns — not when the stream ends
}

stream, err := client.WatchSessions(streamCtx, &ambient_v1.WatchSessionsRequest{})
// ...
return watcher, nil
// defer cancel() fires HERE, immediately cancelling streamCtx

The gRPC stream is created with streamCtx. When Watch() returns to the caller, defer cancel() fires, cancelling streamCtx. Since gRPC streams are context-bound, the stream is terminated the moment the caller receives the watcher. The timeout option is effectively broken and always causes an instant disconnect.

Fix: store the cancel in the SessionWatcher struct alongside watchCtx and call it from Stop(), or use a context.WithTimeout derived from the outer ctx that lives beyond the Watch() call.

5. WatchOptions silently ignored in TypeScript (session_api.ts:881–883)

watch(opts?: WatchOptions): SessionWatcher {
    return new SessionWatcher(this.config);  // opts is dropped entirely
}

resourceVersion, timeout, and signal from WatchOptions are never forwarded to SessionWatcher. The SessionWatcher constructor and watch() method both accept opts — they just are not used here.

6. EventSource cannot send Authorization headers

session_watch.ts uses the browser's native EventSource:

this.eventSource = new EventSource(url);

The EventSource API does not support custom request headers — there is no way to attach Authorization: Bearer <token>. This means the watch stream will arrive unauthenticated, and the server should return 401.

Use fetch() with ReadableStream / getReader() for authenticated SSE, or send the token as a query parameter (with appropriate security considerations).


🟡 Major Issues

7. Busy-wait polling loop in TypeScript (session_watch.ts:1986–1997)

while (!finished && !this.closed) {
    if (eventQueue.length > 0) {
        yield eventQueue.shift()!;
    } else {
        await new Promise(resolve => setTimeout(resolve, 10));  // polls every 10ms
    }
}

This spins at 100 iterations per second when idle. For a long-running watch (default 30 min), that is millions of wasted microtask evaluations. Use a proper Promise-based notification mechanism (e.g., a Deferred or ReadableStream backed by the event listener).

8. Duplicate class definitions across Python watch modules

Both session_watch.py and session_watch_sse.py define classes named SessionWatchEvent and SessionWatcher with identical public interfaces but different implementations. Neither file is imported in __init__.py or the existing client. Decide on one transport (SSE is more practical for browser; gRPC for the Go SDK), expose it from __init__.py, and remove or clearly separate the other.

9. Missing newlines at end of file (pre-commit will block)

The pre-commit end-of-file-fixer hook will reject commits for:

  • go-sdk/examples/watch/main.go
  • python-sdk/ambient_platform/session_watch.py
  • python-sdk/ambient_platform/session_watch_sse.py
  • python-sdk/examples/watch_example.py
  • ts-sdk/examples/watch-example.ts

10. Deprecated gRPC dial API (session_watch.go:505–509)

conn, err := grpc.DialContext(ctx, grpcAddr,
    grpc.WithTransportCredentials(creds),
    grpc.WithBlock(),
    grpc.FailOnNonTempDialError(true),
)

grpc.DialContext, grpc.WithBlock(), and grpc.FailOnNonTempDialError are all deprecated in google.golang.org/grpc v1.65+. The project already uses v1.75.1. Use grpc.NewClient() instead. Additionally, grpc.WithBlock() without a dial timeout will block indefinitely if the server is unreachable.

11. Go module path inconsistency in go-sdk/go.mod

require (
    github.com/ambient/platform/components/ambient-api-server v0.0.0
)
replace github.com/ambient/platform/components/ambient-api-server => ../../ambient-api-server

The module path is github.com/ambient/platform but the rest of the codebase uses github.com/ambient-code/platform. Verify this is intentional or a typo that will cause import resolution failures.


🔵 Minor Issues

12. any type in TypeScript example (watch-example.ts:1673)

function handleWatchEvent(event: any) {

Violates the zero-any rule from the project standards. Should be typed as SessionWatchEvent.

13. Monkey-patching SessionAPI in Python (session_watch.py:1256–1265)

def add_watch_to_session_api():
    """Monkey patch to add watch method to SessionAPI."""
    from ._session_api import SessionAPI
    SessionAPI.watch = watch

This function is defined but never called. The watch method will not be available on SessionAPI instances. Add the method directly to SessionAPI in _session_api.py instead.

14. Empty TLS config in Go gRPC connection

session_watch.go uses credentials.NewTLS(&tls.Config{}) (empty config). Consider whether a custom CA or InsecureSkipVerify option is needed for self-signed certs in development clusters, and document the expectation.

15. deriveGRPCAddress() edge cases

if !strings.Contains(addr, ":") {
    addr += ":4434"
}

IPv6 addresses like [::1] contain : and will not receive the default port, yet will also lack a usable port, causing connection failures. Use net.SplitHostPort for robust parsing.


Positive Highlights

  • URL path migration is clean and consistent: all three SDKs, all generated files, the generator templates, and example code are updated uniformly. The spec SHA256 and generation timestamp are properly updated everywhere.
  • Validation order fix (placeholder-before-length check) improves error messages for placeholder tokens shorter than 20 chars — a real usability improvement.
  • TypeScript type definitions for SessionWatchEvent use proper discriminated union-style 'CREATED' | 'UPDATED' | 'DELETED' literal types instead of a loose string.
  • Go receiveEvents() goroutine correctly selects on w.ctx.Done() in both send paths to avoid goroutine leaks on cancellation.
  • Python SSE watch (session_watch_sse.py) correctly handles both sync and async usage patterns with context managers.
  • Test updated (test_client.py) to assert the new base path — good hygiene when changing constants.

Recommendations

  1. Fix the merge conflict in go.mod / go.sum immediately — this is a build breaker.
  2. Remove binary files from git; add them to .gitignore and document a make build-generator step.
  3. Fix the defer cancel() scope bug in Go's Watch() — the stream is immediately torn down on every call.
  4. Fix the missing opts forwarding in TypeScript session_api.ts.
  5. Replace EventSource with fetch() + streaming body for authenticated SSE.
  6. Either ship the Python gRPC stubs or remove the gRPC watch code — the current state is a guaranteed runtime crash.
  7. Pick one canonical Python watch implementation and expose it cleanly from __init__.py.
  8. Add newlines at end of all new files.
  9. Upgrade deprecated gRPC dial APIs to grpc.NewClient.

🤖 Generated with Claude Code


🔍 View AI decision process (logs available for 90 days)

📋 View memory system files loaded (click to expand)

What Amber Loaded for Code Review

Amber automatically loaded these repository standards from the memory system:

  1. CLAUDE.md - Master project instructions, development standards
  2. backend-development.md - Go backend, K8s integration patterns
  3. frontend-development.md - NextJS, Shadcn UI, React Query patterns
  4. security-standards.md - Auth, RBAC, token handling
  5. k8s-client-usage.md - User token vs service account patterns
  6. error-handling.md - Consistent error patterns
  7. react-query-usage.md - Data fetching patterns

Impact: This review used your repository's specific code quality standards, security patterns, and best practices from the memory system (PRs #359, #360) - not just generic code review guidelines.

@markturansky markturansky force-pushed the feat/ambient-sdk branch 2 times, most recently from e46d11a to 6fae61f Compare March 1, 2026 14:03
@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

test comment

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

Ambient Code Platform

Kubernetes-native AI automation platform that orchestrates agentic sessions through containerized microservices. Built with Go (backend, operator), NextJS + Shadcn (frontend), Python (runner), and Kubernetes CRDs.

Technical artifacts still use "vteam" for backward compatibility.

Structure

  • components/backend/ - Go REST API (Gin), manages K8s Custom Resources with multi-tenant project isolation
  • components/frontend/ - NextJS web UI for session management and monitoring
  • components/operator/ - Go Kubernetes controller, watches CRDs and creates Jobs
  • components/runners/claude-code-runner/ - Python runner executing Claude Code CLI in Job pods
  • components/ambient-cli/ - Go CLI (acpctl), manages agentic sessions from the command line
  • components/public-api/ - Stateless HTTP gateway, proxies to backend (no direct K8s access)
  • components/manifests/ - Kustomize-based deployment manifests and overlays
  • e2e/ - Cypress end-to-end tests
  • docs/ - Astro Starlight documentation site

Key Files

  • CRD definitions: components/manifests/base/crds/agenticsessions-crd.yaml, projectsettings-crd.yaml
  • Session lifecycle: components/backend/handlers/sessions.go, components/operator/internal/handlers/sessions.go
  • Auth & RBAC middleware: components/backend/handlers/middleware.go
  • K8s client init: components/operator/internal/config/config.go
  • Runner entry point: components/runners/claude-code-runner/main.py
  • Route registration: components/backend/routes.go
  • Frontend API layer: components/frontend/src/services/api/, src/services/queries/

Session Flow

User Creates Session → Backend Creates CR → Operator Spawns Job →
Pod Runs Claude CLI → Results Stored in CR → UI Displays Progress

Commands

make build-all                # Build all container images
make deploy                   # Deploy to cluster
make test                     # Run tests
make lint                     # Lint code
make kind-up                  # Start local Kind cluster
make test-e2e-local           # Run E2E tests against Kind

Per-Component

# Backend / Operator (Go)
cd components/backend && gofmt -l . && go vet ./... && golangci-lint run
cd components/operator && gofmt -l . && go vet ./... && golangci-lint run

# Frontend
cd components/frontend && npm run build  # Must pass with 0 errors, 0 warnings

# Runner (Python)
cd components/runners/claude-code-runner && uv venv && uv pip install -e .

# Docs
cd docs && npm run dev  # http://localhost:4321

Critical Context

  • User token auth required: All user-facing API ops use GetK8sClientsForRequest(c), never the backend service account
  • OwnerReferences on all child resources: Jobs, Secrets, PVCs must have controller owner refs
  • No panic() in production: Return explicit fmt.Errorf with context
  • No any types in frontend: Use proper types, unknown, or generic constraints
  • Conventional commits: Squashed on merge to main

Pre-commit Hooks

The project uses the pre-commit framework to run linters locally before every commit. Configuration lives in .pre-commit-config.yaml.

Install

make setup-hooks

What Runs

On every git commit:

Hook Scope
trailing-whitespace, end-of-file-fixer, check-yaml, check-added-large-files, check-merge-conflict, detect-private-key All files
ruff-format, ruff (check + fix) Python (runners, scripts)
gofmt, go vet, golangci-lint Go (backend, operator, public-api — per-module)
eslint Frontend TypeScript/JavaScript
branch-protection Blocks commits to main/master/production

On every git push:

Hook Scope
push-protection Blocks pushes to main/master/production

Run Manually

make lint                                    # All hooks, all files
pre-commit run gofmt-check --all-files       # Single hook
pre-commit run --files path/to/file.go       # Single file

Skip Hooks

git commit --no-verify    # Skip pre-commit hooks
git push --no-verify      # Skip pre-push hooks

Notes

  • Go and ESLint wrappers (scripts/pre-commit/) skip gracefully if the toolchain is not installed
  • tsc --noEmit and npm run build are not included (slow; CI gates on them)
  • Branch/push protection scripts remain in scripts/git-hooks/ and are invoked by pre-commit

More Info

See BOOKMARKS.md for architecture decisions, development context, code patterns, and component-specific guides.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

Claude Code Review -- PR #746

BLOCKER 1: TypeScript any violations (explicit project rule breach)
ts-sdk/src/session_watch.ts uses raw: any in convertSession(). ts-sdk/examples/watch-example.ts uses event: any in handleWatchEvent. CLAUDE.md prohibits any types. Fix: use unknown with a type guard or RawSession type.

BLOCKER 2: Committed binary artifacts
components/ambient-sdk/bin/ambient-sdk-generator and generator/ambient-sdk-generator are binaries checked into the repo. Binaries must be built from source via a Makefile target, not committed.

CRITICAL 3: Python gRPC watch stub is unresolvable
python-sdk/ambient_platform/session_watch.py references _grpc_stubs which cannot be resolved -- the import fails at runtime. Remove session_watch.py and ship only session_watch_sse.py until stubs are generated.

CRITICAL 4: go-sdk/go.mod replace directive breaks external consumers
The replace directive uses ../../ambient-api-server which only resolves inside the monorepo. External module consumers get a build failure. Publish protobuf types as a standalone module or gate gRPC behind a build tag.

MAJOR 5: No tests for session watch
~800 lines of new watch code ships with zero tests. Need: Go stream mock test, Python SSE parsing test, TypeScript SessionWatcher test with mocked EventSource.

MAJOR 6: Dual Python implementations, no guidance
session_watch.py (gRPC, broken) and session_watch_sse.py (SSE, working) coexist with no explanation. Remove session_watch.py until stubs exist.

MAJOR 7: Monkey-patching SessionAPI
add_watch_to_session_api() monkey-patches .watch() onto SessionAPI at import time. Fragile, breaks type checkers. Add the method directly to the class.

MINOR 8: createGRPCConnection() hardcodes port 4434 -- make it configurable via WatchOptions.
MINOR 9: session_watch.ts uses 10ms setTimeout busy-poll -- use a Promise-based queue instead.
MINOR 10: Token validation reorder (placeholder before length) is correct but undocumented -- a short token now gets a different error message.
MINOR 11: ambient-cli/go.mod bumps to go 1.24.0/toolchain go1.24.9 but backend/operator still declare go 1.21 -- standardize together.

Positives: Path prefix cleanup is complete and consistent. TRex v0.0.14 bump is clean. Go session watcher has good channel-based design (Events/Errors/Done/Stop with context.WithCancel). SSE Python watcher provides sync+async variants with context manager support. TypeScript example covers Node.js and browser.

Summary: Items 1-4 are blocking. Items 5-6 are strongly recommended before merge.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

Claude Code Review

Summary

This PR refactors SDK clients to use the new /api/ambient/v1 path (from /api/ambient-api-server/v1), bumps TRex to v0.0.14, cleans up indirect deps in ambient-api-server/go.mod, and adds new gRPC/SSE-based real-time session watch functionality across Go, Python, and TypeScript SDKs. The URL path migration and dep cleanup are clean, but the new watch implementations have several blockers and critical issues that need fixing before merge.


Issues by Severity

Blocker Issues

1. Unresolved git conflict marker in ambient-api-server/go.sum

components/ambient-api-server/go.sum contains a raw git merge conflict around the rh-trex-ai entries. The ======= line is a raw git conflict marker (not a valid go.sum line). Go tooling will reject this file as malformed on first go build or go mod tidy. Remove the old v0.0.6 lines and the ======= marker, keeping only the v0.0.14 entries.

2. Module path mismatch breaks go-sdk build

components/go-sdk/go.mod imports ambient-api-server with the old module path (github.com/ambient/platform/components/ambient-api-server) but ambient-api-server/go.mod was renamed to github.com/ambient-code/platform/components/ambient-api-server. Go requires the module path in the declaration and the require/replace directives to agree. Update both require and replace in go-sdk/go.mod to github.com/ambient-code/platform/components/ambient-api-server.

3. session_watch.go imports the old module path

components/ambient-sdk/go-sdk/client/session_watch.go:28 imports ambient_v1 "github.com/ambient/platform/components/ambient-api-server/pkg/api/grpc/ambient/v1" — must be updated to github.com/ambient-code/platform/....

4. Missing newline at end of ambient-api-server/go.mod

The diff shows \ No newline at end of file. This fails the end-of-file-fixer pre-commit hook.


Critical Issues

5. Timeout context cancelled immediately on Watch() return (session_watch.go)

The defer cancel() inside Watch() fires the moment Watch() returns — but Watch() launches a background goroutine and returns immediately. This cancels streamCtx and terminates the gRPC stream before any events are received. The cancel must be managed inside the goroutine (passed to it, called on exit), not deferred in the spawning function.

6. Incomplete Python gRPC implementation — imports non-existent module

session_watch.py (~line 78) contains from . import _grpc_stubs # This would be generated — this raises ImportError at runtime. The file is not ready to ship.

7. Python add_watch_to_session_api() monkey-patch is never called

session_watch.py defines a function that monkey-patches watch onto SessionAPI, but the function is never invoked. The watch method will never exist on SessionAPI instances.

8. Duplicate SessionWatchEvent / SessionWatcher classes across Python files

Both session_watch.py and session_watch_sse.py define classes with identical names. Since the gRPC implementation is incomplete (issue 6), remove session_watch.py and ship only session_watch_sse.py.

9. Binary file committed to the repository

components/ambient-sdk/bin/ambient-sdk-generator is a new compiled binary committed to git. Binary provenance cannot be verified from source; check-added-large-files may flag it. Add to .gitignore and distribute via CI releases or a build target instead.


Major Issues

10. any types in TypeScript violate zero-tolerance rule

session_watch.ts line ~184: private convertSession(raw: any): Session and watch-example.ts line ~73: function handleWatchEvent(event: any). Per CLAUDE.md: no any types. Use unknown with a type guard or Record<string, unknown>.

11. EventSource cannot send auth headers — watch stream is unauthenticated

SessionWatcher uses new EventSource(url). The browser EventSource API does not support custom request headers, so Authorization: Bearer <token> and X-Ambient-Project will never be sent. Watch requests arrive at the server unauthenticated. Pass the token as a query parameter, use a cookie, or switch to a fetch()-based SSE implementation.

12. session_api.ts: opts parameter accepted but never forwarded

watch(opts?: WatchOptions): SessionWatcher {
    return new SessionWatcher(this.config);  // opts silently dropped
}

WatchOptions (timeout, signal, resourceVersion) are thrown away. SessionWatcher's constructor should accept and use them.

13. buildWatchURL() depends on browser window — breaks Node.js

new URL(path, window.location.href)window does not exist in Node.js. The included watch-example.ts is Node-targeted (require.main === module). Drop the second argument since baseUrl is already absolute.

14. grpc.DialContext is deprecated in gRPC v1.54+

This PR introduces gRPC v1.75.1 where grpc.DialContext is deprecated in favour of grpc.NewClient. Additionally, grpc.WithBlock() makes the dial synchronous — Watch() hangs if the gRPC server is unreachable. Remove WithBlock().


Minor Issues

15. Multiple new files missing trailing newline — failing end-of-file-fixer pre-commit hook:

  • go-sdk/examples/watch/main.go
  • ts-sdk/examples/watch-example.ts
  • ts-sdk/src/session_watch.ts
  • python-sdk/ambient_platform/session_watch.py
  • python-sdk/ambient_platform/session_watch_sse.py
  • python-sdk/examples/watch_example.py

16. Go SessionWatcher has no interface — impossible to mock in tests. Consider a SessionWatcher interface with Events(), Errors(), Done(), Stop().

17. deriveGRPCAddress() fragile for IPv6strings.Contains(addr, ":") incorrectly flags IPv6 addresses. Use net.SplitHostPort.

18. Busy-wait in session_watch.tsawait new Promise(resolve => setTimeout(resolve, 10)) fires every 10ms. Use a proper promise-based event queue.

19. interface instead of type in TypeScriptSessionWatchEvent and WatchOptions use interface. Project standards prefer type.


Positive Highlights

  • URL path migration is clean and consistent/api/ambient-api-server/v1 to /api/ambient/v1 is applied correctly across all three SDK languages, generator templates, and the path parser. The test_client.py update provides regression coverage.
  • Validation order fix is a solid defensive improvement — checking placeholder tokens before the length check prevents confusing errors for short sentinel values.
  • ambient-api-server/go.mod dep cleanup is meaningful — removing ~70 unnecessary indirect dependencies reduces supply-chain surface area and speeds up module resolution.
  • TRex bump (v0.0.6 to v0.0.14) is correctly applied in both go.mod and go.sum.
  • Python SSE watcher design is well-structured — sync/async variants with context manager support and explicit close() follow good Python async conventions.
  • TypeScript SessionWatchEventUtils and AbortSignal support are ergonomic touches that improve developer experience.

Recommendations

  1. Blocker: Resolve git conflict marker in ambient-api-server/go.sum — keep only v0.0.14 entries
  2. Blocker: Update go-sdk/go.mod (require + replace) and session_watch.go imports to github.com/ambient-code/platform/components/ambient-api-server
  3. Blocker: Add trailing newline to ambient-api-server/go.mod
  4. Critical: Remove session_watch.py (incomplete gRPC stubs) — ship only session_watch_sse.py
  5. Critical: Fix defer cancel() bug in session_watch.go Watch() — manage timeout inside the goroutine
  6. Critical: Remove binary components/ambient-sdk/bin/ambient-sdk-generator from the commit; add to .gitignore
  7. Critical: Consolidate duplicate Python watch classes into a single file
  8. Major: Replace any types in TypeScript with unknown or proper typed interfaces
  9. Major: Fix EventSource auth gap in session_watch.ts — pass token as query param or use fetch()-based SSE
  10. Major: Forward opts to SessionWatcher in session_api.ts
  11. Major: Remove window.location.href from buildWatchURL()
  12. Major: Replace grpc.DialContext with grpc.NewClient; remove WithBlock()
  13. Minor: Add trailing newlines to all new files

Generated with Claude Code


🔍 View AI decision process (logs available for 90 days)

📋 View memory system files loaded (click to expand)

What Amber Loaded for Code Review

Amber automatically loaded these repository standards from the memory system:

  1. CLAUDE.md - Master project instructions, development standards
  2. backend-development.md - Go backend, K8s integration patterns
  3. frontend-development.md - NextJS, Shadcn UI, React Query patterns
  4. security-standards.md - Auth, RBAC, token handling
  5. k8s-client-usage.md - User token vs service account patterns
  6. error-handling.md - Consistent error patterns
  7. react-query-usage.md - Data fetching patterns

Impact: This review used your repository's specific code quality standards, security patterns, and best practices from the memory system (PRs #359, #360) - not just generic code review guidelines.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

Amber Code Review — PR #746

Note: The base-ref/.claude/commands/amber.review.md review template was not found on the base branch, so this review follows standard Amber review conventions.


Summary

This PR has good intent — fixing the API path prefix (/api/ambient-api-server/v1/api/ambient/v1) and adding real-time session watch functionality across all three SDKs. The path fix is correct and straightforward. However, the watch implementation has several blocking issues that prevent it from compiling or working correctly.


🔴 Blocking Issues

1. Go SDK: Invalid module path in go.mod and session_watch.go

components/ambient-sdk/go-sdk/go.mod declares:

require (
    github.com/ambient/platform/components/ambient-api-server v0.0.0
    ...
)
replace github.com/ambient/platform/components/ambient-api-server => ../../ambient-api-server

The module path uses github.com/ambient/platform but the canonical path throughout this repo is github.com/ambient-code/platform. This mismatch means the module will not resolve correctly and the Go SDK will fail to compile.

session_watch.go imports:

ambient_v1 "github.com/ambient/platform/components/ambient-api-server/pkg/api/grpc/ambient/v1"

This import path is wrong on two counts: (a) wrong module prefix (ambient vs ambient-code), and (b) the gRPC proto package at pkg/api/grpc/ambient/v1 does not appear to exist in the ambient-api-server — no gRPC service has been defined there.

2. Go SDK: Context cancelled before stream is used

In session_watch.go, Watch():

if opts.Timeout > 0 {
    var cancel context.CancelFunc
    streamCtx, cancel = context.WithTimeout(streamCtx, opts.Timeout)
    defer cancel()  // ← BUG
}

stream, err := client.WatchSessions(streamCtx, ...)
// ...
watcher := &SessionWatcher{...}
go watcher.receiveEvents()
return watcher, nil  // ← defer cancel() fires HERE, killing the stream context

defer cancel() runs when Watch() returns, immediately cancelling streamCtx and terminating the gRPC stream before any events are received. The cancel function must be stored in the watcher and called from Stop() instead.

3. Python SDK: Non-existent gRPC stubs import

session_watch.py imports:

from . import _grpc_stubs  # This would be generated

This module does not exist in the package. The file will raise ImportError at runtime. The PR introduces a session_watch_sse.py (SSE-based) as an alternative, but session_watch.py (gRPC-based) is also committed and broken. Only one of these should be included, and it should be complete.

4. TypeScript: window.location.href in SDK code

session_watch.ts, buildWatchURL():

const url = new URL(`${baseUrl}/api/ambient/v1/sessions`, window.location.href);

window is not defined in Node.js environments. SDK code must not reference browser globals. This will throw ReferenceError: window is not defined for any server-side or CLI consumer. Use new URL(path) directly against the absolute base URL, or just string concatenation.

5. TypeScript: watch() silently ignores opts

session_api.ts:

watch(opts?: WatchOptions): SessionWatcher {
    return new SessionWatcher(this.config);  // opts is completely discarded
}

WatchOptions (timeout, signal, resourceVersion) is accepted but never passed to SessionWatcher. The consumer's timeout and abort signal are silently ignored.


🟡 Notable Issues

6. TypeScript: any type in convertSession

session_watch.ts, convertSession():

private convertSession(raw: any): Session {

The project style guide explicitly forbids any in TypeScript. Use unknown with appropriate narrowing or a RawSession type alias.

7. Protocol inconsistency across SDKs

  • Go SDK → gRPC (which doesn't compile)
  • Python SDK → two files: gRPC placeholder + SSE implementation
  • TypeScript SDK → SSE via EventSource

Consumers of the SDK should get a consistent experience. The PR should settle on one protocol (SSE appears to be the working one) and implement it consistently, or clearly document the difference with working implementations.

8. Python SDK: Two duplicate SessionWatchEvent classes

session_watch.py and session_watch_sse.py both define SessionWatchEvent with identical APIs. If both modules are exported, this will cause confusion. Deduplicate into a shared types module.

9. Dependency downgrades in ambient-api-server/go.mod

Several packages were downgraded compared to the base branch:

  • golang.org/x/crypto: v0.45.0 → v0.41.0
  • golang.org/x/net: v0.47.0 → v0.43.0
  • golang-jwt/jwt/v4: v4.5.2 → v4.5.0
  • jackc/pgconn: v1.14.3 → v1.12.0, pgtype: v1.14.0 → v1.11.0, pgx/v4: v4.18.2 → v4.16.0

These look unintentional — likely a side effect of merging/rebasing. Please restore these to the versions on main (or newer) to avoid unintentional regressions. Downgrades to security-relevant packages (crypto, jwt) especially warrant scrutiny.

10. TypeScript polling loop is CPU-wasteful

session_watch.ts, watch() generator:

} else {
    await new Promise(resolve => setTimeout(resolve, 10));
}

Polling every 10ms to check an in-memory queue is inefficient. Standard practice for SSE with async generators is to use a Promise-based event queue that resolves on new events, rather than busy-polling.

11. Go SDK replace directive breaks external SDK consumers

go-sdk/go.mod uses a relative replace directive:

replace github.com/ambient/platform/components/ambient-api-server => ../../ambient-api-server

This makes the SDK non-distributable — anyone importing github.com/ambient-code/platform/components/ambient-sdk/go-sdk externally will not have ../../ambient-api-server on their filesystem. The gRPC proto types should either be published separately or the SDK should use the REST/SSE approach (consistent with the TS SDK) instead.


✅ What's Good

  • The /api/ambient-api-server/v1/api/ambient/v1 path fix is correct and applied consistently across Go, Python, TypeScript SDKs, and the generator templates.
  • The generator parser.go path fixes are correctly applied.
  • The Go SDK channel-based watcher design (Events(), Errors(), Done()) is idiomatic and well-structured.
  • The SSE-based Python watcher (session_watch_sse.py) shows a clean, practical approach.
  • Test assertions updated correctly for the new path.
  • TRex dependency bump to v0.0.14 looks intentional and appropriate.

Recommended Next Steps

  1. Fix Go SDK module path: change github.com/ambient/platformgithub.com/ambient-code/platform everywhere, or remove the gRPC approach and use SSE.
  2. Fix defer cancel() bug in Watch().
  3. Remove session_watch.py (broken gRPC placeholder) or complete it. Keep session_watch_sse.py as the Python watch implementation.
  4. Fix window.location.href in TypeScript.
  5. Pass opts to SessionWatcher in session_api.ts.
  6. Replace any in convertSession with a proper type.
  7. Restore unintentionally downgraded dependencies in ambient-api-server/go.mod.

🤖 Reviewed by Amber (Claude Code)

@markturansky markturansky force-pushed the feat/ambient-sdk branch 2 times, most recently from 6b283fc to dc9e83b Compare March 2, 2026 17:18
- Generator: fix path prefix (/api/ambient/v1) for correct delete/update method detection
- Generator: regenerate all SDK clients (Go, Python, TypeScript) from OpenAPI spec
- Go SDK: add session watch via gRPC streaming (session_watch.go, types/session_watch.go)
- Go SDK: NewClientFromEnv requires explicit project param (remove AMBIENT_PROJECT env var)
- Go SDK: fix default API port 8080→8000
- Go SDK: fix examples/go.mod missing ambient-api-server replace directive
- Go SDK: fix examples project lookup on conflict (List search instead of Get-by-name)
- Python/TS SDK: add session watch via SSE streaming
- All SDK clients: default port corrected to localhost:8000

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@ambient-code
Copy link

ambient-code bot commented Mar 3, 2026

Merge Readiness — Blockers Found

Check Status Detail
CI pass
Merge conflicts pass
Review comments FAIL Multiple blockers: git conflict in go.sum, module path mismatch, incomplete implementations, context cancel bug
Jira hygiene warn No Jira reference found
Fork PR warn Fork from markturansky — no automated agent review
Staleness pass

This comment is auto-generated by the PR Overview workflow and will be updated when the PR changes.

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