feat: add native SAP AI Core support #50
Closed
rodin
wants to merge 12 commits from
feat/aicore-provider into main
pull from: feat/aicore-provider
merge into: rodin:main
rodin:main
rodin:ci/cleanup
rodin:ci-selfreview-gate
rodin:issue-150
rodin:issue-157
rodin:issue-141
rodin:issue-154
rodin:review-bot-dev-loop
rodin:issue-143
rodin:issue-146
rodin:pr-153
rodin:review-bot-issue-130-work
rodin:issue-148
rodin:issue-139
rodin:issue-137
rodin:review-bot-fixes
rodin:review-bot-issue-133
rodin:review-bot-issue-130
rodin:issue-130
rodin:github-support
rodin:issue-123-work
rodin:issue-123
rodin:review-bot-issue-120
rodin:fix/125-readme-cli-example
rodin:issue-125
rodin:issue-124
rodin:issue-120
rodin:feature/github-support
rodin:review-bot-issue-116
rodin:review-bot-issue-115
rodin:review-bot-issue-114
rodin:review-bot-issue-96
rodin:review-bot-issue-107
rodin:review-bot-issue-82
rodin:review-bot-issue-95
rodin:review-bot-issue-92
rodin:review-bot-issue-94
rodin:review-bot-issue-81
rodin:review-bot-issue-91
rodin:review-bot-issue-97
rodin:issue-80-c-file-reader
rodin:issue-80-b-pr-reader
rodin:issue-80-a-client
rodin:review-bot-issue-80
rodin:review-bot-issue-87
rodin:review-bot-issue-79
rodin:review-bot-issue-84
rodin:review-bot-issue-78
rodin:issue-73
rodin:issue-70
rodin:issue-68
rodin:issue-66
rodin:issue-64
rodin:issue-60-remote-personas
rodin:issue-60
rodin:issue-57
rodin:allow-deps
rodin:feat/aicore-provider-v2
rodin:issue-51
rodin:ci/pr-ready-gate
rodin:fix/stale-commit-check
rodin:fix/response-body-truncation
rodin:fix/json-repair
rodin:fix/sonnet-reviewer
rodin:fix/consistent-path-escape
rodin:feat/inline-review-comments
rodin:feat/6-update-existing-review
rodin:fix/19-context-overflow
rodin:feat/18-anthropic-api
rodin:fix/url-escaping-and-shadow
rodin:fix/quick-wins
rodin:fix/context-and-encapsulation
rodin:docs/code-review-report
rodin:ci/release-workflow
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "feat/aicore-provider"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds a new
aicoreLLM provider that authenticates directly with SAP AI Core using OAuth client credentials. This eliminates the need for an external proxy (hai-aicore or hai) when running review-bot in SAP environments.Changes
llm/aicore.gowithAICoreClientfor OAuth token management and deployment discovery/invoke(Anthropic) or/chat/completions(OpenAI) based on model name--aicore-client-id,--aicore-client-secret,--aicore-auth-url,--aicore-api-url,--aicore-resource-groupExample Usage
Closes #49
Adds a new 'aicore' LLM provider that authenticates directly with SAP AI Core using OAuth client credentials. This eliminates the need for an external proxy (hai-aicore or hai) when running review-bot in SAP environments. Key changes: - New llm/aicore.go with AICoreClient for OAuth token management and deployment discovery - Thread-safe token caching with automatic refresh before expiry - Automatic routing to /invoke (Anthropic) or /chat/completions (OpenAI) based on model name - New CLI flags: --aicore-client-id, --aicore-client-secret, --aicore-auth-url, --aicore-api-url, --aicore-resource-group - Updated action.yml with corresponding inputs - Comprehensive test coverage for AI Core client Example usage in CI: llm-provider: aicore llm-model: anthropic--claude-4.6-sonnet aicore-client-id: ${{ secrets.AICORE_CLIENT_ID }} aicore-client-secret: ${{ secrets.AICORE_CLIENT_SECRET }} aicore-auth-url: ${{ secrets.AICORE_AUTH_URL }} aicore-api-url: ${{ secrets.AICORE_API_URL }} Closes #49Security Review
Summary
Adds SAP AI Core provider with OAuth client-credential flow, thread-safe token caching, and deployment discovery. The implementation is generally sound with appropriate context use and minimal logging of sensitive data.
Findings
llm/aicore.gollm/aicore.gollm/aicore.goRecommendation
APPROVE — The new SAP AI Core integration appears robust with proper context handling, timeouts, and thread-safe token caching. To harden security, validate that AuthURL/APIURL use HTTPS (or emit a clear warning when they do not), and avoid logging full response bodies from OAuth and AI Core API errors to prevent accidental disclosure of sensitive information. With these minor adjustments, the feature should be safe for production use.
Review by security
Evaluated against
c3ec44a8@@ -0,0 +70,4 @@if err != nil {return "", err}c.token = token[MINOR] AuthURL/APIURL scheme is not validated. If misconfigured to use http://, the client ID/secret and tokens would be sent in cleartext. Consider enforcing https or at least logging a warning when non-HTTPS endpoints are configured to reduce risk of credential exposure.
@@ -0,0 +94,4 @@return "", time.Time{}, fmt.Errorf("token request: %w", err)}defer resp.Body.Close()[MINOR] On token fetch failure, the error includes the full response body ("token request failed ...: %s"). While the auth server typically won't echo secrets, returning raw bodies can leak potentially sensitive diagnostic details into logs. Consider redacting or truncating the body in errors.
@@ -0,0 +147,4 @@return "", err}if d, ok := c.deployments[model]; ok {[MINOR] For non-2xx responses from AI Core requests (both /invoke and /chat/completions), errors include the full response body. Depending on backend behavior, this may reflect parts of the prompt or internal details into logs. Prefer redacting or truncating bodies in error messages.
Original reviewSuperseded — see current review for up-to-date findings.
Previous findings (commit
c3ec44a8)Gpt Review
Summary
Solid addition of native SAP AI Core support with thoughtful token caching, deployment discovery, and routing, plus good tests and documentation updates. However, the AI Core HTTP client uses a hard-coded 30s timeout and ignores the configured LLM timeout, which will surprise users and cause premature request cancellations.
Findings
llm/aicore.gollm/aicore.gollm/aicore.goRecommendation
REQUEST_CHANGES — Propagate the configured LLM timeout to the AI Core client to align behavior across providers. A straightforward fix is to have llm.Client.WithAICore either: (a) pass c.http (or at least its Timeout) into the AICoreClient, or (b) add AICoreClient.WithTimeout and call it from llm.Client.WithTimeout. This ensures --llm-timeout (and WithTimeout) are honored for AI Core requests. Also consider extracting the hard-coded OpenAI api-version into a constant or configurable option to facilitate upgrades, and tightening IsAnthropicModel to avoid false positives. Once the timeout propagation is addressed, the feature looks ready to merge.
Review by gpt
Evaluated against
c3ec44a8@@ -0,0 +43,4 @@func NewAICoreClient(cfg AICoreConfig) *AICoreClient {return &AICoreClient{config: cfg,http: &http.Client{Timeout: 30 * time.Second},[MAJOR] AICoreClient initializes its own http.Client with a hard-coded 30s timeout, ignoring the user-configured LLM timeout. This causes AI Core requests to time out at 30s even when --llm-timeout is larger, diverging from documented behavior.
@@ -0,0 +242,4 @@}reqBody := anthropicRequest{Model: model,[MINOR] The OpenAI-style endpoint appends a hard-coded api-version (2024-12-01-preview). Consider making this configurable (or at least a package constant) to avoid breaking changes when AI Core updates versions.
@@ -0,0 +354,4 @@var openaiResp ChatResponseif err := json.Unmarshal(body, &openaiResp); err != nil {return "", fmt.Errorf("parse response: %w", err)}[NIT] IsAnthropicModel uses a broad contains("claude") check, which may misclassify non-Anthropic models containing 'claude' in their name. Consider restricting detection further (e.g., prefix match or explicit allowlist).
Security Review
Summary
The PR adds native SAP AI Core support with OAuth and routing logic and looks structurally sound, but CI is failing and must be addressed before merge. Security-wise, consider limiting response body sizes and reducing error-body exposure, and enforce HTTPS for credentialed endpoints.
Findings
.gitea/actions/review/action.ymlllm/aicore.gollm/aicore.gollm/aicore.goRecommendation
REQUEST_CHANGES — Please address the failing CI first, as it blocks merge. For security hardening: (1) Add bounded reads for all HTTP responses in llm/aicore.go. A typical approach is to wrap resp.Body with an io.LimitedReader (e.g., 1–2MB for error responses and a reasonable cap for success bodies consistent with expected model outputs) before io.ReadAll. (2) Avoid logging full upstream response bodies in error messages; truncate to a small prefix (e.g., 1–2KB) and remove/avoid sensitive content. (3) Validate that AICore AuthURL and APIURL use the https scheme before making requests that carry client credentials or tokens; if you need to support non-HTTPS for local testing, gate it behind an explicit opt-in flag or environment variable and emit a clear warning. After these changes, re-run and ensure CI is green.
Review by security
Evaluated against
c3ec44a8@@ -0,0 +1,368 @@package llm[MINOR] Unbounded response body reads (io.ReadAll) in token fetch, deployment listing, and completion requests can enable resource exhaustion if the remote endpoint misbehaves or is misconfigured. Consider wrapping resp.Body with an io.LimitedReader and capping the maximum read size, especially for error paths.
[MINOR] Error messages include full upstream response bodies (e.g., on non-2xx for token/deployment/completion calls). This can leak provider or configuration details into logs. Consider truncating error bodies (e.g., first 1–2KB) and/or redacting sensitive fields before logging/returning.
[MINOR] No validation that AICore AuthURL/APIURL use HTTPS before sending client credentials and tokens. Misconfiguration to an http:// URL could expose client_secret and tokens over cleartext. Enforce https scheme by default and allow overrides only via an explicit 'insecure' flag for controlled environments.
Original reviewSuperseded — see current review for up-to-date findings.
Previous findings (commit
c3ec44a8)Gpt Review
Summary
Solid, idiomatic addition of a native SAP AI Core provider with thread-safe token caching, deployment discovery, and comprehensive tests. However, CI is failing, which mandates request changes, and there are a few minor design nits to address.
Findings
.gitea/actions/review/action.ymlllm/aicore.gollm/aicore.gollm/aicore_test.gocmd/review-bot/main.goRecommendation
REQUEST_CHANGES — Address the CI failures first; CI must be green before merging. The new AI Core provider implementation looks sound and idiomatic, with proper concurrency control on token caching and good test coverage. As small improvements: (1) make the AI Core OpenAI api-version a constant or configurable; (2) consider a refresh policy for the deployment cache (e.g., TTL or refetch on invoke errors) to avoid stale URLs; (3) tidy the token generation in tests for clarity; and (4) optionally clarify the aicore path in main to avoid constructing the client with empty baseURL/apiKey. Once CI passes and these minor points are addressed, the feature should be ready to merge.
Review by gpt
Evaluated against
c3ec44a8@@ -69,7 +69,13 @@ func main() {dryRun := flag.Bool("dry-run", false, "Print review to stdout instead of posting")[NIT] When provider=aicore, a Client is constructed with empty base URL and API key, which is harmless but slightly confusing. A small comment or constructing the Client after provider validation could improve clarity.
@@ -0,0 +154,4 @@}func (c *AICoreClient) fetchDeployments(ctx context.Context, token string) error {[MINOR] The deployment cache never refreshes once populated. If a deployment URL changes or a model becomes unavailable, the client may hold stale entries. Consider refreshing on 404/5xx from the invoke endpoint or adding a TTL/refresh strategy.
@@ -0,0 +347,4 @@return "", fmt.Errorf("read response: %w", err)}if resp.StatusCode < 200 || resp.StatusCode >= 300 {[MINOR] The OpenAI-through-AI-Core endpoint hard-codes the api-version ("2024-12-01-preview"). Consider making this a constant or configurable to avoid future breakage when AI Core updates versions.
@@ -0,0 +409,4 @@func TestClient_CompleteAICore(t *testing.T) {var baseURL stringmux := http.NewServeMux()[NIT] Token string generation uses rune arithmetic ("token-" + string(rune('0'+call))). This is fragile beyond single-digit calls. Prefer fmt.Sprintf("token-%d", call) for clarity and robustness.
Original reviewSuperseded — see current review for up-to-date findings.
Previous findings (commit
a62b791b)Sonnet Review
Summary
The SAP AI Core provider implementation is well-structured, correct, and follows established patterns in the codebase. The token caching uses proper double-checked locking, HTTP requests are context-aware, error handling follows conventions, and the test coverage is comprehensive. A few minor issues exist but none are blocking.
Findings
llm/aicore.gofunc (c *AICoreClient) fetchDeployments(...) error {\n\n). Cosmetic but inconsistent with the rest of the file style.llm/aicore.gollm/aicore.godeployment.IDfield is stored but never read — it's populated in fetchDeployments butgetDeploymentURLonly returnsd.URL. If the ID is truly unused, consider removing it to avoid dead code. If it's intended for future use (e.g., logging), add a comment.llm/aicore_test.gostring(rune('0'+call))which works for single-digit call counts (1, 2) but would produce incorrect results for call >= 10 (non-digit runes). Since the test only makes 2 calls this is safe, butfmt.Sprintf("token-%d", call)would be clearer and more robust.llm/aicore_test.gogetTokenon lines 390 and 396 (token1, _ :=andtoken2, _ :=). Tests should check errors or use t.Fatal to avoid masking failures.llm/aicore.gogetDeploymentURLand thengetTokenare called sequentially.getDeploymentURLinternally already callsgetToken(to fetch deployments), so the second explicitgetTokencall will typically be a cache hit. This is correct and safe, but a comment explaining the double-call or refactoring to return the token fromgetDeploymentURLwould improve readability..gitea/actions/review/action.ymldefault: 'default'line foraicore-resource-group(consistent with the oldllm-providerline it replaces, but worth cleaning up).Recommendation
APPROVE — Approve. The implementation is correct and well-tested. The double-checked locking pattern for token caching is properly implemented, all HTTP requests use context, errors are wrapped with context, and the routing logic (Anthropic vs OpenAI based on model name) is clean and tested. The minor issues noted (unused deployment ID field, no deployment cache TTL, test error suppression) are non-blocking for a feature addition of this scope.
Review by sonnet
Evaluated against
a62b791b@@ -0,0 +38,4 @@ID stringURL string}[MINOR] The
deployment.IDfield is stored but never read — it's populated in fetchDeployments butgetDeploymentURLonly returnsd.URL. If the ID is truly unused, consider removing it to avoid dead code. If it's intended for future use (e.g., logging), add a comment.@@ -0,0 +165,4 @@resp, err := c.http.Do(req)if err != nil {return fmt.Errorf("deployments request: %w", err)[MINOR] Deployments are fetched once and cached forever with no TTL or invalidation mechanism. If a deployment goes from RUNNING to STOPPED (or a new deployment is added) between reviews, the cached URL will remain stale until the process restarts. Given this tool runs as a short-lived CI process this is acceptable, but worth a comment explaining the intentional lack of cache expiry.
@@ -0,0 +192,4 @@} `json:"backend_details"`} `json:"resources"`} `json:"details"`} `json:"resources"`[MINOR] fetchDeployments has an unnecessary blank line after the function signature (
func (c *AICoreClient) fetchDeployments(...) error {\n\n). Cosmetic but inconsistent with the rest of the file style.@@ -0,0 +258,4 @@// AI Core uses /invoke for Anthropic modelsinvokeURL := strings.TrimRight(deployURL, "/") + "/invoke"req, err := http.NewRequestWithContext(ctx, http.MethodPost, invokeURL, bytes.NewReader(data))[NIT] In CompleteAnthropic,
getDeploymentURLand thengetTokenare called sequentially.getDeploymentURLinternally already callsgetToken(to fetch deployments), so the second explicitgetTokencall will typically be a cache hit. This is correct and safe, but a comment explaining the double-call or refactoring to return the token fromgetDeploymentURLwould improve readability.@@ -0,0 +385,4 @@}}func TestClient_WithAICore(t *testing.T) {[NIT] TestAICoreClient_TokenExpiry generates token strings with
string(rune('0'+call))which works for single-digit call counts (1, 2) but would produce incorrect results for call >= 10 (non-digit runes). Since the test only makes 2 calls this is safe, butfmt.Sprintf("token-%d", call)would be clearer and more robust.@@ -0,0 +387,4 @@func TestClient_WithAICore(t *testing.T) {client := NewClient("http://example.com", "key", "model")if client.provider != ProviderOpenAI {[NIT] TestAICoreClient_TokenExpiry ignores the error return from
getTokenon lines 390 and 396 (token1, _ :=andtoken2, _ :=). Tests should check errors or use t.Fatal to avoid masking failures.Security Review
Summary
The PR adds native SAP AI Core support with OAuth client-credential auth, deployment discovery, and provider routing. The implementation is generally secure and robust, with proper token caching and no exposure of secrets in logs. I found only minor hardening opportunities; CI has passed.
Findings
llm/aicore.gollm/aicore.gollm/aicore.goRecommendation
APPROVE — Overall, the changes are well-structured and security-conscious: credentials are never logged, token caching is synchronized, and path traversal for local files is mitigated. CI has passed and there are no exploitable vulnerabilities identified. I recommend approval. For defense-in-depth, consider (1) avoiding logging full upstream response bodies on auth/API errors to reduce information leakage, and (2) validating that SAP AI Core AuthURL/APIURL use HTTPS to prevent accidental plaintext credential transmission. Optionally, expose or align the AICore HTTP timeout with the main LLM timeout for consistent behavior.
Review by security
Evaluated against
a62b791b@@ -0,0 +42,4 @@// NewAICoreClient creates a new AI Core client with the given configuration.func NewAICoreClient(cfg AICoreConfig) *AICoreClient {return &AICoreClient{config: cfg,[NIT] AICoreClient uses an internal http.Client with a fixed 30s Timeout irrespective of the overall LLM timeout. While requests also honor context deadlines, the shorter client timeout may cause premature cancellations and inconsistent behavior. Consider aligning with the caller-provided context/timeout or making this configurable.
@@ -0,0 +75,4 @@return token, nil}func (c *AICoreClient) fetchToken(ctx context.Context) (string, time.Time, error) {[MINOR] AuthURL/APIURL are accepted as-is and used to construct requests without verifying the scheme. If misconfigured to use plain HTTP, client credentials could be sent in cleartext. Consider validating that AuthURL and APIURL use https:// (or at least warning/refusing non-HTTPS) to prevent accidental credential exposure.
@@ -0,0 +100,4 @@return "", time.Time{}, fmt.Errorf("read token response: %w", err)}if resp.StatusCode < 200 || resp.StatusCode >= 300 {[MINOR] On authentication and API failures, the code includes the full upstream response body in returned errors (e.g., token and deployments requests, and AI Core invoke errors). While convenient for debugging, this can leak detailed service information into logs. Consider redacting or truncating bodies on errors, or limiting to status code and a short message.
Original reviewSuperseded — see current review for up-to-date findings.
Previous findings (commit
a62b791b)Gpt Review
Summary
The AI Core integration is generally well-designed and covered by tests, with clean error handling and concurrency safety. However, the AI Core client uses its own http.Client with a fixed 30-second timeout, which does not honor the global LLM timeout configured via Client.WithTimeout or the CLI, leading to inconsistent and potentially premature timeouts. Addressing this will align provider behavior and avoid surprising failures.
Findings
llm/aicore.gollm/client.gollm/aicore.gollm/aicore.goRecommendation
REQUEST_CHANGES — Please propagate the configured LLM timeout to the AI Core HTTP client to ensure consistent behavior across providers. For example, have AICoreClient accept a timeout or an *http.Client, or set aicore.http.Timeout in llm.Client.WithTimeout/WithAICore. Optionally, clear c.aicore when switching providers or document ordering to avoid confusion, and consider extracting the AI Core OpenAI API version to a constant and tightening the IsAnthropicModel heuristic. With the timeout issue resolved, the PR looks solid and tests cover the new functionality well.
Review by gpt
Evaluated against
a62b791b@@ -0,0 +42,4 @@// NewAICoreClient creates a new AI Core client with the given configuration.func NewAICoreClient(cfg AICoreConfig) *AICoreClient {return &AICoreClient{config: cfg,[MAJOR] AICoreClient constructs an internal http.Client with a hard-coded 30s timeout and does not share or inherit the timeout set on llm.Client via WithTimeout. This creates inconsistent behavior: the top-level LLM timeout may be minutes, but AI Core calls will fail after 30s. Propagate the parent client's timeout or expose a way to configure AICoreClient's http timeout and set it from llm.Client.
@@ -0,0 +326,4 @@return "", fmt.Errorf("marshal request: %w", err)}// AI Core uses /chat/completions?api-version=2024-12-01-preview for OpenAI models[NIT] The OpenAI chat endpoint includes a hard-coded API version string ("2024-12-01-preview"). Consider defining this as a named constant for readability and easier future updates.
@@ -0,0 +363,4 @@}// IsAnthropicModel returns true if the model name indicates an Anthropic model.func IsAnthropicModel(model string) bool {[NIT] IsAnthropicModel uses a broad heuristic (contains "claude"). This may misclassify edge cases. Consider a stricter prefix check (e.g., anthropic--*) or a configurable mapping if model naming varies.
@@ -106,6 +119,18 @@ func (c *Client) Complete(ctx context.Context, messages []Message) (string, erroreturn "", err[MINOR] Provider switching can leave stale AICore client state: calling WithAICore sets c.aicore, but a subsequent WithProvider change (e.g., back to openai) leaves c.aicore non-nil. While logic routes by provider, consider documenting that WithProvider should precede WithAICore, or clear c.aicore when switching away to avoid confusion.
Original reviewSuperseded — see current review for up-to-date findings.
Previous findings (commit
34507dd9)Sonnet Review
Summary
This PR adds a well-implemented SAP AI Core provider with proper OAuth token caching, deployment discovery, and routing logic. The code follows the project's conventions, uses stdlib only, handles errors correctly, and has comprehensive test coverage. CI passes.
Findings
llm/aicore.gollm/aicore.gollm/aicore.gohttpshadows the importednet/httppackage name within the struct. While Go allows this (the package is still accessible by its full import path), it's a subtle naming collision. The existingllm/client.goalready uses the same pattern so this is consistent with the codebase, but worth noting as a readability concern.llm/aicore.gocompleteAICorein client.go (internal routing), exporting these gives callers the ability to bypass the routing logic (calling CompleteOpenAI with an Anthropic model name, for example). Consider making them unexported (completeAnthropic,completeOpenAI) since they're not part of any documented public API..gitea/actions/review/action.ymldefault: 'default'— minor style issue (pre-existing in the llm-provider field too, but this is new in the diff).Recommendation
APPROVE — The implementation is solid and correct. The OAuth double-checked locking pattern is properly implemented, error messages are well-formed with context wrapping, the retry logic in Complete() correctly skips retry for AI Core (since isRetryableError only matches specific transient error strings that AI Core errors won't produce), and test coverage is thorough. The two MINOR findings (deployment cache TTL and duplicate-model nondeterminism) are edge cases that won't affect normal operation but could cause subtle bugs in long-running processes or unusual AI Core configurations. They don't warrant blocking the PR. Approving.
Review by sonnet
Evaluated against
34507dd9@@ -0,0 +54,4 @@}// WithTimeout sets the HTTP request timeout for AI Core calls.// This should be called during construction, before concurrent use.[NIT] The field name
httpshadows the importednet/httppackage name within the struct. While Go allows this (the package is still accessible by its full import path), it's a subtle naming collision. The existingllm/client.goalready uses the same pattern so this is consistent with the codebase, but worth noting as a readability concern.@@ -0,0 +160,4 @@}if d, ok := c.deployments[model]; ok {return d.URL, nil[MINOR] Deployment cache has no TTL or invalidation mechanism. If a deployment goes from RUNNING to STOPPED (or a new deployment is added), the cached entry will be used until the process restarts. Consider adding a deployment cache TTL (e.g., 5-10 minutes) similar to the token refresh guard, or at least document this limitation. This mirrors the concern raised about the deployment URL being cached indefinitely once fetched.
@@ -0,0 +201,4 @@Name string `json:"name"`} `json:"model"`} `json:"backend_details"`} `json:"resources"`[MINOR] If a model has multiple RUNNING deployments (as the test demonstrates for 'gpt-5' with deploy-456 STOPPED and deploy-789 RUNNING), the final write wins in the loop since the map is overwritten. In the test, deploy-456 is STOPPED so it is filtered out and deploy-789 wins correctly. However, if two deployments for the same model name are both RUNNING, the result is nondeterministic (last one parsed wins). This edge case should either be documented or handled explicitly (e.g., pick the first RUNNING, or error).
@@ -0,0 +333,4 @@}data, err := json.Marshal(reqBody)if err != nil {[NIT] CompleteAnthropic and CompleteOpenAI are public methods on AICoreClient. Since the intended consumer is only
completeAICorein client.go (internal routing), exporting these gives callers the ability to bypass the routing logic (calling CompleteOpenAI with an Anthropic model name, for example). Consider making them unexported (completeAnthropic,completeOpenAI) since they're not part of any documented public API.Security Review
Summary
The addition of native SAP AI Core support is implemented cleanly with proper OAuth handling, thread-safe token caching, and provider routing. CI passed and I did not find exploitable vulnerabilities in the changes. I recommend a couple of hardening improvements around response size limits and error message content.
Findings
llm/aicore.gollm/aicore.gollm/aicore.gollm/aicore.gollm/aicore.gollm/aicore.gollm/aicore.gollm/aicore.goRecommendation
APPROVE — Approve the PR. The new AI Core provider integrates securely with OAuth client credentials, uses context-aware requests, and avoids logging secrets. For defense-in-depth, consider (1) limiting or validating response body sizes when calling AI Core endpoints to mitigate potential resource exhaustion if endpoints are misconfigured or hostile, and (2) truncating or redacting response bodies included in error messages, especially for authentication and API error paths, to reduce potential sensitive information exposure in logs. These are hardening measures; the current implementation is acceptable.
Review by security
Evaluated against
34507dd9@@ -0,0 +103,4 @@resp, err := c.http.Do(req)if err != nil {return "", time.Time{}, fmt.Errorf("token request: %w", err)[MINOR] Unbounded response body read from the token endpoint (io.ReadAll on resp.Body). If the AuthURL is misconfigured or points to a hostile server, this can lead to excessive memory usage. Consider adding a reasonable limit via io.LimitReader or validating Content-Length and capping size.
@@ -0,0 +108,4 @@defer resp.Body.Close()body, err := io.ReadAll(resp.Body)if err != nil {[MINOR] Error message includes full response body from the token endpoint (fmt.Errorf("token request failed ...: %s", body)). This can leak sensitive details from upstream error payloads into logs. Consider truncating or redacting bodies in errors, especially for auth endpoints.
@@ -0,0 +176,4 @@resp, err := c.http.Do(req)if err != nil {return fmt.Errorf("deployments request: %w", err)[MINOR] Unbounded response body read for deployments discovery (io.ReadAll). Similar DoS risk as above if APIURL is misconfigured or untrusted. Add a body size cap or Content-Length validation.
@@ -0,0 +181,4 @@defer resp.Body.Close()body, err := io.ReadAll(resp.Body)if err != nil {[MINOR] Error path for deployments includes the entire response body in the error message. Consider truncating or redacting to avoid leaking potentially sensitive upstream details.
@@ -0,0 +279,4 @@resp, err := c.http.Do(req)if err != nil {return "", fmt.Errorf("AI Core request: %w", err)[MINOR] Unbounded response body read for Anthropic invoke responses (io.ReadAll). To defend against large or malformed payloads from AI Core or misconfiguration, consider adding a read limit or Content-Length checks like in llm.Client.doRequest.
@@ -0,0 +284,4 @@defer resp.Body.Close()body, err := io.ReadAll(resp.Body)if err != nil {[MINOR] Anthropic error path includes full response body in error. Truncate or sanitize to reduce risk of leaking sensitive content into logs.
@@ -0,0 +349,4 @@resp, err := c.http.Do(req)if err != nil {return "", fmt.Errorf("AI Core request: %w", err)[MINOR] Unbounded response body read for OpenAI-compatible chat responses via AI Core. Consider adding a maximum body size or Content-Length validation for defense-in-depth.
@@ -0,0 +354,4 @@defer resp.Body.Close()body, err := io.ReadAll(resp.Body)if err != nil {[MINOR] OpenAI-compatible error path includes full response body in error. Recommend truncating or redacting the body in error messages to avoid potential sensitive data exposure.
Gpt Review
Summary
Well-scoped addition of native SAP AI Core support with clear CLI/Action wiring and comprehensive tests. Concurrency and error handling are solid; CI passed.
Findings
llm/aicore.gollm/aicore.goRecommendation
APPROVE — The implementation cleanly adds aicore as a first-class provider with proper flag validation, environment wiring in the composite action, and thorough tests covering token management, deployment discovery, and routing to the correct AI Core endpoints. Error handling follows repository conventions and the concurrency primitives are used correctly. The two minor design notes are about avoiding long-held locks during network calls and adding an invalidation strategy for the in-memory deployment cache. Neither blocks correctness. Given CI is green and the changes are well-tested, this is approved as-is; consider addressing the minor improvements in a follow-up.
Review by gpt
Evaluated against
34507dd9@@ -0,0 +149,4 @@c.mu.Lock()defer c.mu.Unlock()[MINOR] getDeploymentURL holds the write mutex while calling fetchDeployments, which performs network I/O. This can block all concurrent callers unnecessarily; consider releasing the lock before the request and re-acquiring it only to update the cache (double-check pattern), or use a singleflight to dedupe concurrent fetches.
@@ -0,0 +211,4 @@for _, r := range deployResp.Resources {if r.Status != "RUNNING" {continue[MINOR] The deployments cache is only ever appended to and never invalidated or refreshed (e.g., if a deployment transitions from RUNNING to STOPPED). Consider replacing the map on each fetch or introducing a TTL/refresh strategy to avoid serving stale URLs.
Self-review: PASS
No blocking issues found — ready for human review.
Notes (informational, not blocking)
[fit]
staticcheckreports:llm/aicore.go:237andllm/client.go:231: struct literal conversion style (S1016) — minor style nit, existing in both old and new codegitea/diff.go:78: HasPrefix return ignored (SA4017) — pre-existing, not introduced by this PRcmd/review-bot/main_test.go:347: nil Context (SA1012) — pre-existing, not introduced by this PR[fit] Body length validation:
aicore.godoes not include the Content-Length vs body length validation thatdoRequesthas inclient.go. This is acceptable because:[completeness] Tests pass (
go test ./...),go vetclean, no uncommitted changes.Security Review
Summary
The changes add a native SAP AI Core provider with OAuth client credentials, proper token caching, and route selection. Secrets are handled via environment variables without being logged, HTTP clients have timeouts, and file path handling remains safeguarded against traversal. No security-relevant vulnerabilities are evident.
Recommendation
APPROVE — The implementation appears secure and robust: OAuth tokens are cached thread-safely, credentials are not logged, requests use bounded timeouts, and CI wiring passes secrets through environment variables without echoing them. Consider optionally warning or documenting that AI Core URLs should use HTTPS and, if desired, avoid including full error bodies from upstream services in logs, but these are hardening suggestions rather than necessary changes. Proceed with merge.
Review by security
Evaluated against
d02c7548Gpt Review
Summary
Solid addition of native SAP AI Core support with clear flag validation, provider routing, and comprehensive tests. Concurrency and error handling are handled carefully, and docs/CI/action updates align with the new provider.
Findings
llm/aicore.gollm/aicore_test.go.gitea/actions/review/action.ymlRecommendation
APPROVE — The implementation is correct and idiomatic: token caching uses proper RWMutex double-checked locking; AI Core endpoint routing based on model name is clear; errors are wrapped with context throughout; and the new flags and CI/action changes are coherent. Tests cover token refresh, deployment discovery, endpoint behavior, and client configuration. Consider enhancing the deployment cache with invalidation on specific error responses or a refresh TTL to handle topology changes gracefully. Also, tidy the minor test name casing and YAML whitespace. Otherwise, this is ready to merge.
Review by gpt
Evaluated against
d02c7548@@ -0,0 +1,380 @@package llm[MINOR] Deployment cache never invalidates entries; if a previously RUNNING deployment stops, the cached URL may persist and cause repeated request failures. Consider invalidating on specific AI Core error responses (e.g., 404/410) or adding a periodic/TTL refresh.
@@ -0,0 +1,535 @@package llm[NIT] Test name 'TestClient_WithTimeout_PropagatestoAICore' has inconsistent capitalization ('Propagatesto'); consider renaming to 'TestClient_WithTimeout_PropagatesToAICore' for readability consistency.
Security Review
Summary
Overall, the changes add SAP AI Core support with OAuth client-credentials, thread-safe token caching, and provider routing. The implementation appears secure by default (timeouts, no secret logging, proper locking), and CI has passed.
Findings
llm/aicore.gollm/aicore.gollm/aicore.gocmd/review-bot/main.goRecommendation
APPROVE — The SAP AI Core integration is implemented thoughtfully with thread-safe token caching, proper timeouts, and no direct logging of secrets. Given CI passed and no exploitable issues were found, approval is recommended. For defense-in-depth, consider: (1) truncating or redacting upstream response bodies in error messages for token and deployment requests to reduce information leakage in logs; (2) validating deploymentUrl hosts (e.g., ensuring same host or scheme as configured APIURL) before use to mitigate SSRF or token leakage in the unlikely event of a compromised AI Core endpoint or misconfiguration; and (3) warning or enforcing HTTPS for AICORE_AUTH_URL and AICORE_API_URL to prevent accidental plaintext transport. None of these concerns block merge but would harden the integration further.
Review by security
Evaluated against
b92a968d@@ -91,0 +103,4 @@}if isAICore && (*aicoreClientID == "" || *aicoreClientSecret == "" || *aicoreAuthURL == "" || *aicoreAPIURL == "") {fmt.Fprintf(os.Stderr, "Error: AI Core credentials required for provider=aicore\n\n")fmt.Fprintf(os.Stderr, "Required: --aicore-client-id, --aicore-client-secret, --aicore-auth-url, --aicore-api-url\n")[MINOR] AI Core URLs (AICORE_AUTH_URL, AICORE_API_URL) are accepted as-is without enforcing or warning on non-HTTPS schemes. Requiring or warning on non-HTTPS would reduce risk of credential/token interception in misconfigurations.
@@ -0,0 +124,4 @@return "", time.Time{}, fmt.Errorf("parse token response: %w", err)}if tokenResp.AccessToken == "" {[MINOR] Error returns include the full response body on token request failure (fmt.Errorf("token request failed ...: %s", body)). This can lead to information leakage in logs if upstream returns verbose error details. Consider truncating or redacting bodies in errors.
@@ -0,0 +153,4 @@// Double-check after acquiring write lockif d, ok := c.deployments[model]; ok {return d.URL, nil}[MINOR] The code trusts and directly uses the deploymentUrl returned by the AI Core API to construct subsequent requests (appending /invoke or /chat/completions). While appropriate in normal operation, this creates a potential SSRF or token exfiltration vector if the AI Core API or configuration is compromised. As a defense-in-depth measure, consider validating that deploymentUrl shares the expected host/scheme with the configured APIURL.
@@ -0,0 +196,4 @@Status string `json:"status"`Details struct {Resources struct {BackendDetails struct {[MINOR] Error returns include the full response body on deployments request failure (fmt.Errorf("deployments request failed ...: %s", body)). This may leak internal details to logs. Prefer limiting or redacting response bodies in error messages.
Gpt Review
Summary
The PR cleanly adds native SAP AI Core support with well-structured client code, robust token caching, deployment discovery, and comprehensive tests. CLI, action, workflow, and README changes are coherent and consistent, and CI passed.
Findings
llm/aicore.gollm/aicore_test.go.gitea/actions/review/action.ymlllm/aicore.goRecommendation
APPROVE — Overall, this is a solid and well-tested addition. The AI Core client is thread-safe and integrates cleanly into the existing LLM client with sensible routing for Anthropic vs OpenAI models. Action/workflow/README updates are clear and consistent. Consider adding a refresh strategy for cached deployment URLs (TTL or retry-triggered refresh) to handle long-lived processes when deployments change, and tidy the minor test naming nit. Otherwise, the PR is ready to merge.
Review by gpt
Evaluated against
b92a968d@@ -0,0 +1,381 @@package llm[MINOR] Deployment URL caching never expires or refreshes once populated; if a deployment is replaced or its URL changes, the cached mapping may become stale and subsequent requests will keep failing without a retry path or re-discovery. Consider a TTL or a retry-on-4xx/5xx to refresh deployments.
[NIT] The AICoreOpenAIAPIVersion constant is set to a preview API version. Consider making this configurable or documenting the update process to reduce maintenance friction when versions change.
@@ -0,0 +1,535 @@package llm[NIT] Test name 'TestClient_WithTimeout_PropagatestoAICore' has a minor typo/casing; consider renaming to 'TestClient_WithTimeout_PropagatesToAICore' for readability and consistency.
Security Review
Summary
The new SAP AI Core provider is implemented cleanly with OAuth client-credentials, token caching, and deployment discovery, and integrates safely into the existing CLI and action. No exploitable vulnerabilities were identified; however, a few hardening opportunities exist around URL validation and error-body logging.
Findings
llm/aicore.gollm/aicore.gollm/aicore.goRecommendation
APPROVE — Overall, the addition of native SAP AI Core support is solid. Token handling uses short-lived OAuth tokens with thread-safe caching, and the main CLI avoids logging secrets while validating required inputs. To harden security further: (1) enforce or at least warn on non-HTTPS AuthURL/APIURL in AICoreConfig (rejecting plain HTTP to prevent credential leakage), (2) avoid logging complete response bodies in error messages by truncating or redacting to reduce information leakage into CI logs, and (3) optionally validate deployment URLs against the expected domain to mitigate token exfiltration in case of misconfiguration. None of these are blockers; approve the PR and consider addressing these in a follow-up.
Review by security
Evaluated against
b76270c2@@ -0,0 +83,4 @@return "", err}c.token = tokenc.tokenExpiry = expiry[MINOR] fetchToken constructs the token endpoint from AICoreConfig.AuthURL without enforcing HTTPS. If misconfigured to use http, client credentials would be sent in cleartext. Consider validating that AuthURL uses the https scheme (and fail or warn otherwise).
@@ -0,0 +102,4 @@req.Header.Set("Content-Type", "application/x-www-form-urlencoded")resp, err := c.http.Do(req)if err != nil {[MINOR] On non-2xx responses from the OAuth or AI Core APIs, the code includes the full response body in returned errors (e.g., "token request failed ...: %s"). Upstream callers log these errors, which can inadvertently leak request/response details to CI logs. Consider truncating or redacting error bodies before returning/logging.
@@ -0,0 +229,4 @@// CompleteAnthropic sends a request to an Anthropic model via AI Core.func (c *AICoreClient) CompleteAnthropic(ctx context.Context, model string, messages []Message, maxTokens int, temperature float64) (string, error) {deployURL, err := c.getDeploymentURL(ctx, model)if err != nil {[NIT] fetchDeployments trusts the deploymentUrl from the AI Core control plane and later sends Bearer tokens to that URL. As a defense-in-depth measure, consider validating that the deploymentUrl host matches (or is a subdomain of) the expected API domain to reduce risk of token exfiltration if the control plane is misconfigured.
Gpt Review
Summary
Solid addition of native SAP AI Core support with clean integration into flags, action, workflow, and LLM client. Tests are comprehensive and CI passed. A couple of minor design/robustness improvements are suggested.
Findings
llm/aicore.gollm/aicore.gollm/aicore.goRecommendation
APPROVE — The implementation is correct and idiomatic overall, with good error wrapping and thorough tests. Since CI has passed and there are no correctness blockers, approve as is. For future improvement, avoid holding the AICoreClient mutex across network calls in getDeploymentURL by fetching deployments outside the critical section and then updating the map under lock. Also consider adding Content-Length validation in the AI Core response handlers (similar to doRequest) to detect truncated bodies and better leverage the retry logic. Otherwise, the provider integration, flag validation, workflow updates, and documentation changes look solid.
Review by gpt
Evaluated against
b76270c2@@ -0,0 +155,4 @@return d.URL, nil}if err := c.fetchDeployments(ctx, token); err != nil {[MINOR] getDeploymentURL calls fetchDeployments while holding the write mutex, performing network I/O under lock. This can cause unnecessary contention and head-of-line blocking. Consider releasing the lock before the HTTP call and re-acquiring it only to update the shared map.
@@ -0,0 +284,4 @@}defer resp.Body.Close()body, err := io.ReadAll(resp.Body)[MINOR] CompleteAnthropic reads the response body without validating against Content-Length. For parity with the OpenAI path and better detection of truncated responses, consider checking resp.ContentLength and returning a specific error when len(body) < Content-Length.
@@ -0,0 +354,4 @@}defer resp.Body.Close()body, err := io.ReadAll(resp.Body)[MINOR] CompleteOpenAI also omits Content-Length vs body length validation. Adding the same check here improves robustness against truncated responses and enables the retry logic to trigger on body length mismatch.
Security Review
Summary
Overall, the new SAP AI Core provider is implemented cleanly with appropriate input validation, contextual timeouts, and no evident injection, path traversal, or auth flaws. I found a few security hardening opportunities around redirects, response truncation checks, and HTTPS enforcement, but no exploitable vulnerabilities.
Findings
llm/aicore.gollm/aicore.gollm/aicore.gocmd/review-bot/main.goRecommendation
APPROVE — The implementation introduces native SAP AI Core support with proper token caching, concurrency controls, and clean provider routing. No critical vulnerabilities were found. To further harden security, (1) disable or constrain redirects for the OAuth token request to prevent client credential leakage on 307/308 cross-host redirects, (2) add Content-Length vs body-size validation to AI Core responses (as done in the shared OpenAI path) to catch truncation, and (3) enforce or warn on non-HTTPS AICore URLs to avoid plaintext credential exposure due to misconfiguration. With these minor improvements, the change set looks robust and safe to merge.
Review by security
Evaluated against
b12df1a6@@ -0,0 +129,4 @@}expiry := time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second)return tokenResp.AccessToken, expiry, nil[MINOR] The OAuth token request uses the default http.Client redirect behavior, which will resend the POST body on 307/308 redirects. If the AuthURL endpoint (or a misconfigured proxy) issues a cross-host 307/308, client_id/client_secret could be sent to a different host. Consider setting a CheckRedirect function to prevent cross-host redirects or disallow redirects for the token endpoint.
@@ -0,0 +299,4 @@}if len(anthropicResp.Content) == 0 {return "", fmt.Errorf("no content in response")[MINOR] CompleteAnthropic reads the response body without validating it against Content-Length, so a truncated response could be parsed if it remains valid JSON. Consider adding the same Content-Length vs body-length validation used in llm/client.go#doRequest to detect truncation before attempting to parse.
@@ -0,0 +349,4 @@req.Header.Set("Content-Type", "application/json")resp, err := c.http.Do(req)if err != nil {[MINOR] CompleteOpenAI also reads the response body without Content-Length validation. Add a length check similar to llm/client.go#doRequest to guard against truncated responses.
Gpt Review
Summary
The PR adds a well-structured SAP AI Core provider with proper OAuth handling, deployment discovery, and routing, along with CLI and action updates. Tests are comprehensive and CI has passed. Only minor design/documentation nits were found.
Findings
llm/aicore.goTODO.mdRecommendation
APPROVE — Overall the implementation is solid: the AI Core client uses cached OAuth tokens with early refresh, deployment discovery is handled cleanly, and routing between Anthropic and OpenAI endpoints is automatic based on model name. The CLI, composite action, CI, and README are updated consistently, and tests cover token caching, deployment lookup, and requests for both Anthropic and OpenAI-style endpoints. CI has passed. As a minor improvement, avoid holding the mutex during the fetchDeployments HTTP call to reduce contention under load. Optionally, move the TODO/self-review document out of the repo. With these minor points noted, the changes are good to merge.
Review by gpt
Evaluated against
b12df1a6@@ -0,0 +1,19 @@## Self-Review: feat/aicore-provider — 2026-05-09[NIT] Self-review notes (staticcheck findings, rationale) are committed to the repository. Consider moving these to an issue or PR description to avoid shipping internal review notes in the codebase.
@@ -0,0 +155,4 @@return d.URL, nil}if err := c.fetchDeployments(ctx, token); err != nil {[MINOR] getDeploymentURL performs a blocking network request (fetchDeployments) while holding c.mu.Lock. This can block concurrent readers/writers longer than necessary. Consider releasing the lock before the HTTP call and reacquiring just to update c.deployments, using a double-check to avoid redundant fetches.
Gpt Review
Summary
Solid addition of native SAP AI Core support with clean integration into the CLI, CI, and documentation. Token caching and deployment discovery are implemented thread-safely, and comprehensive tests cover the new client behavior.
Findings
llm/aicore.gollm/aicore.go.gitea/actions/review/action.ymlRecommendation
APPROVE — The implementation is correct and idiomatic, with good error wrapping and concurrency safety, and CI has passed. I recommend merging as-is. As a follow-up, consider adding Content-Length vs body length validation in llm/aicore.go (similar to doRequest) to guard against truncated responses, and optionally tidy up the minor whitespace in action.yml. The deployment cache approach is reasonable; if deployment churn becomes an issue, a TTL-based refresh could be added later.
Review by gpt
Evaluated against
8da8fca1@@ -36,3 +38,3 @@required: truellm-provider:description: 'LLM API provider: openai or anthropic (default openai)'description: 'LLM API provider: openai, anthropic, or aicore (default openai)'[NIT] Minor trailing whitespace in the default value for aicore-resource-group ('default '). This is harmless but can be cleaned up for consistency.
@@ -0,0 +287,4 @@body, err := io.ReadAll(resp.Body)if err != nil {return "", fmt.Errorf("read response: %w", err)}[MINOR] Response handling in CompleteAnthropic does not validate the received body length against Content-Length like llm.Client.doRequest does. Consider adding a similar check to detect truncated responses.
@@ -0,0 +340,4 @@// AI Core uses /chat/completions?api-version=<version> for OpenAI modelschatURL := strings.TrimRight(deployURL, "/") + "/chat/completions?api-version=" + AICoreOpenAIAPIVersionreq, err := http.NewRequestWithContext(ctx, http.MethodPost, chatURL, bytes.NewReader(data))[MINOR] Response handling in CompleteOpenAI does not validate body length against Content-Length. Align with the OpenAI/Anthropic client path by adding a Content-Length vs received length check to detect truncation.
Security Review
Summary
The new SAP AI Core provider is implemented with proper OAuth flow, token caching, timeouts, and clear separation of concerns. Inputs are validated in the CLI, and secrets are not logged. I found no exploitable vulnerabilities; a few defense-in-depth hardening opportunities are noted.
Findings
llm/aicore.gollm/aicore.gollm/aicore.gollm/aicore.gollm/aicore.gocmd/review-bot/main.goRecommendation
APPROVE — Overall the implementation is sound and CI passed. To harden against abuse and misconfiguration, consider: (1) parsing and validating the deploymentUrl from AI Core before use (enforce https and restrict host/domain to expected values) to mitigate SSRF scenarios; (2) validating the AI-Resource-Group header value for allowed characters; (3) truncating or downgrading the logging of response bodies in error messages; and (4) optionally warning or rejecting non-HTTPS AI Core Auth/API URLs. These are defense-in-depth measures and do not block merging.
Review by security
Evaluated against
8da8fca1@@ -89,1 +93,4 @@// For aicore provider, llm-base-url and llm-api-key are not requiredisAICore := llm.Provider(*llmProvider) == llm.ProviderAICoreif *giteaURL == "" || *repo == "" || *prNum == "" || *reviewerToken == "" || *llmModel == "" {fmt.Fprintf(os.Stderr, "Error: missing required flags or environment variables\n\n")[NIT] When using provider=aicore, AuthURL and APIURL are accepted as provided. Consider warning or rejecting non-HTTPS URLs to avoid sending credentials or tokens over insecure transport.
@@ -0,0 +109,4 @@body, err := io.ReadAll(resp.Body)if err != nil {return "", time.Time{}, fmt.Errorf("read token response: %w", err)[MINOR] Error paths include the full response body in the returned error (token request and AI Core API errors). This can leak potentially sensitive server responses into logs. Consider truncating bodies in errors or logging them at a debug level only.
@@ -0,0 +168,4 @@func (c *AICoreClient) fetchDeployments(ctx context.Context, token string) error {deployURL := strings.TrimRight(c.config.APIURL, "/") + "/v2/lm/deployments"req, err := http.NewRequestWithContext(ctx, http.MethodGet, deployURL, nil)if err != nil {[MINOR] Header value for AI-Resource-Group is taken from unvalidated input and inserted into an HTTP header. While Go's net/http prevents CRLF injection in headers, consider validating this value (e.g., allow only [A-Za-z0-9-_]) to reduce risk and fail fast on invalid configuration.
@@ -0,0 +265,4 @@data, err := json.Marshal(reqBody)if err != nil {return "", fmt.Errorf("marshal request: %w", err)[MINOR] The code trusts the deploymentUrl returned by the AI Core API and directly constructs the invoke endpoint without validating the URL's scheme/host. An attacker controlling the API endpoint (or misconfiguration) could return a malicious URL, leading to SSRF attempts from the runner. Consider parsing and validating that the URL uses https and matches an expected domain or host pattern.
@@ -0,0 +335,4 @@data, err := json.Marshal(reqBody)if err != nil {return "", fmt.Errorf("marshal request: %w", err)[MINOR] Same SSRF concern for OpenAI-style models: the chat/completions URL is derived directly from deploymentUrl without scheme/host validation. Add URL parsing and enforce https and allowed host/domain.
@@ -0,0 +356,4 @@body, err := io.ReadAll(resp.Body)if err != nil {return "", fmt.Errorf("read response: %w", err)[MINOR] Same body-echoing concern on AI Core chat/completions error path; consider truncation or lower log level to avoid leaking large or sensitive content into logs.
Security Review
Summary
The PR adds a native SAP AI Core provider with OAuth client-credentials, thread-safe token caching, and deployment discovery. Overall the implementation is sound and integrates cleanly with existing code paths, and CI has passed. I found a few security hardening opportunities around response size limits, HTTPS enforcement, and error body logging.
Findings
llm/aicore.gollm/aicore.gollm/aicore.gollm/aicore.gocmd/review-bot/main.gollm/aicore.goRecommendation
APPROVE — Proceed with merge. The new AI Core provider follows good practices for token caching and avoids logging secrets. To harden security and resilience, I recommend: (1) cap response sizes in llm/aicore.go by using io.LimitReader or json.Decoder instead of io.ReadAll in fetchToken, fetchDeployments, CompleteAnthropic, and CompleteOpenAI; (2) validate that aicore-auth-url and aicore-api-url use https:// and warn or abort if not, to prevent accidental cleartext transmission of client credentials; (3) truncate or redact response bodies in error messages to avoid leaking potentially sensitive content into logs. None of these are blockers, but they improve robustness and reduce risk.
Review by security
Evaluated against
f71f26fc@@ -91,0 +101,4 @@fmt.Fprintf(os.Stderr, "Error: --llm-base-url and --llm-api-key are required for provider=%s\n", *llmProvider)os.Exit(1)}if isAICore && (*aicoreClientID == "" || *aicoreClientSecret == "" || *aicoreAuthURL == "" || *aicoreAPIURL == "") {[MINOR] No validation that --aicore-auth-url and --aicore-api-url use HTTPS. A misconfiguration could transmit client credentials over plain HTTP. Enforce or warn on non-https schemes for these URLs.
@@ -0,0 +113,4 @@}if resp.StatusCode < 200 || resp.StatusCode >= 300 {return "", time.Time{}, fmt.Errorf("token request failed (status %d): %s", resp.StatusCode, string(body))[MINOR] Unbounded response reads with io.ReadAll can lead to memory exhaustion if the endpoint returns very large bodies. Consider wrapping resp.Body with io.LimitReader or streaming decode (json.Decoder). This pattern occurs in multiple places (fetchToken, fetchDeployments, CompleteAnthropic, CompleteOpenAI).
@@ -0,0 +203,4 @@} `json:"backend_details"`} `json:"resources"`} `json:"details"`} `json:"resources"`[MINOR] Unbounded io.ReadAll in fetchDeployments; a malicious or misconfigured APIURL could cause large response bodies to be read into memory. Apply a reasonable size cap or stream the JSON.
@@ -0,0 +285,4 @@defer resp.Body.Close()body, err := io.ReadAll(resp.Body)if err != nil {[MINOR] Unbounded io.ReadAll in CompleteAnthropic; add a maximum body size to mitigate potential memory exhaustion if the server returns an unexpectedly large payload.
@@ -0,0 +296,4 @@var anthropicResp anthropicResponseif err := json.Unmarshal(body, &anthropicResp); err != nil {return "", fmt.Errorf("parse response: %w", err)}[NIT] Error messages include the full response body on non-2xx (e.g., in CompleteAnthropic/OpenAI). This can leak potentially sensitive prompt or model output to logs on failures. Consider truncating/redacting bodies in errors.
@@ -0,0 +355,4 @@defer resp.Body.Close()body, err := io.ReadAll(resp.Body)if err != nil {[MINOR] Unbounded io.ReadAll in CompleteOpenAI; consider io.LimitReader or streaming decode to limit memory use.
Gpt Review
Summary
Well-structured addition of the SAP AI Core provider with clear CLI wiring, action inputs, and comprehensive tests. Concurrency and error handling are generally solid; a couple of robustness and minor style improvements are suggested.
Findings
llm/aicore.gollm/aicore.go.gitea/actions/review/action.ymlllm/aicore_test.goRecommendation
APPROVE — Overall, the feature is well-implemented: native SAP AI Core support is integrated cleanly with proper flag validation, provider routing, and thorough httptest-based coverage. CI has passed, and no blocking issues were found. To further improve robustness and scalability, consider avoiding long-held write locks during HTTP calls in AICoreClient and add a Content-Length truncation check analogous to doRequest so that retries trigger on truncated responses. Minor nits include fixing a test name typo and trimming trailing whitespace in the action YAML. With these small improvements, the provider will be even more resilient and maintainable.
Review by gpt
Evaluated against
f71f26fc@@ -0,0 +1,381 @@package llm[MINOR] Network I/O is performed while holding the write mutex in getToken (fetchToken) and getDeploymentURL (fetchDeployments), which can block other callers for the duration of HTTP calls. Consider performing HTTP requests outside the lock and updating shared state under lock (or using singleflight) to reduce contention.
[MINOR] Responses are read without validating Content-Length vs received body length, unlike doRequest in client.go. A truncated body would likely fail JSON unmarshal and be treated as a non-retryable parse error by Client.Complete, reducing robustness. Consider adding a Content-Length mismatch check (and returning a specific error) to preserve retry semantics for transient truncations.
@@ -0,0 +1,535 @@package llm[NIT] Test name 'TestClient_WithTimeout_PropagatestoAICore' has a typo ('Propagatesto'). Consider renaming to 'PropagatesToAICore' for clarity and consistency.
Sonnet Review
Summary
Well-structured addition of SAP AI Core as a native LLM provider. The implementation follows existing patterns in the codebase, uses correct double-checked locking for thread-safe token/deployment caching, has comprehensive test coverage, and CI passes. A few minor style and design observations but nothing blocking.
Findings
llm/aicore.gohttp *http.Clientshadows thenet/httppackage name within the struct's method bodies. This is valid Go but can cause confusion — a reader seeingc.http.Do(req)has to remember it's a field, not the package. The existingClientinclient.gohas the same pattern, so this is consistent with the codebase, but renaming tohttpClientwould be cleaner.llm/aicore.gotokenExpiryhandles token refresh.llm/aicore.gogetDeploymentURL, the token is fetched before acquiring the write lock (correct to avoid deadlock with getToken's own lock), but thenfetchDeploymentsis called while holding the write lock AND passes the token in. IffetchDeploymentstakes a long time (slow network), all readers are blocked for the duration. This is acceptable for a CLI tool but worth noting — in a high-concurrency server scenario a different approach would be needed.llm/aicore.goTODO.mdself-review correctly notes thatCompleteAnthropicandCompleteOpenAIlack the Content-Length body validation thatdoRequesthas inclient.go. Given these methods duplicate the HTTP execution pattern rather than going throughdoRequest, any future fix to the retry/validation logic indoRequestwon't automatically apply here. Consider extracting a shared HTTP execution helper for AI Core, or routing throughdoRequestvia an adapter.llm/aicore.godeploymentstruct has an exportedIDandURLfield but the type is unexported. TheIDfield is stored in the map but never read back — onlyURLis used. TheIDfield could be removed unless planned for future use (e.g., logging, metrics).llm/aicore_test.go// Use a pointer to capture the server URL for use in the handleris slightly misleading —baseURLis a plainstringvariable captured by closure, not a pointer.TODO.mdTODO.mdcontaining a self-review into the main branch adds noise to the repository. This file is transient review scaffolding and would be better kept out of version control (e.g., in.gitignore) or removed before merge.Recommendation
APPROVE — Approve. The implementation is correct, idiomatic, and well-tested. CI passes. The double-checked locking pattern for token caching is implemented correctly (RLock check → RUnlock → Lock → double-check → fetch). The
IsAnthropicModelrouting function is clean and the test coverage is comprehensive including token expiry, deployment filtering by status, timeout propagation, and end-to-end routing throughClient.Complete. The minor findings (stale deployment cache, duplicated HTTP execution pattern, unexported struct with unused field) are acceptable for a first-pass implementation of a CLI tool and are documented in the self-review notes. The TODO.md file should be removed before merge or added to .gitignore.Review by sonnet
Evaluated against
44c80c36@@ -0,0 +1,19 @@## Self-Review: feat/aicore-provider — 2026-05-09[NIT] Committing a
TODO.mdcontaining a self-review into the main branch adds noise to the repository. This file is transient review scaffolding and would be better kept out of version control (e.g., in.gitignore) or removed before merge.@@ -0,0 +31,4 @@type AICoreClient struct {config AICoreConfighttp *http.Client[MINOR] The field
http *http.Clientshadows thenet/httppackage name within the struct's method bodies. This is valid Go but can cause confusion — a reader seeingc.http.Do(req)has to remember it's a field, not the package. The existingClientinclient.gohas the same pattern, so this is consistent with the codebase, but renaming tohttpClientwould be cleaner.@@ -0,0 +50,4 @@config: cfg,http: &http.Client{Timeout: 5 * time.Minute},deployments: make(map[string]deployment),}[NIT] The
deploymentstruct has an exportedIDandURLfield but the type is unexported. TheIDfield is stored in the map but never read back — onlyURLis used. TheIDfield could be removed unless planned for future use (e.g., logging, metrics).@@ -0,0 +159,4 @@return "", err}if d, ok := c.deployments[model]; ok {[MINOR] In
getDeploymentURL, the token is fetched before acquiring the write lock (correct to avoid deadlock with getToken's own lock), but thenfetchDeploymentsis called while holding the write lock AND passes the token in. IffetchDeploymentstakes a long time (slow network), all readers are blocked for the duration. This is acceptable for a CLI tool but worth noting — in a high-concurrency server scenario a different approach would be needed.@@ -0,0 +194,4 @@ID string `json:"id"`DeploymentURL string `json:"deploymentUrl"`Status string `json:"status"`Details struct {[MINOR] Deployments are cached indefinitely after first fetch. If a deployment is restarted, updated, or a new one is added for a model, the client will keep using the stale cached URL until the process restarts. Consider adding a TTL or a mechanism to refresh on 4xx/5xx errors from the deployment URL, similar to how
tokenExpiryhandles token refresh.@@ -0,0 +277,4 @@req.Header.Set("Authorization", "Bearer "+token)req.Header.Set("AI-Resource-Group", c.config.ResourceGroup)req.Header.Set("Content-Type", "application/json")[MINOR] The
TODO.mdself-review correctly notes thatCompleteAnthropicandCompleteOpenAIlack the Content-Length body validation thatdoRequesthas inclient.go. Given these methods duplicate the HTTP execution pattern rather than going throughdoRequest, any future fix to the retry/validation logic indoRequestwon't automatically apply here. Consider extracting a shared HTTP execution helper for AI Core, or routing throughdoRequestvia an adapter.@@ -0,0 +189,4 @@"resources": map[string]interface{}{"backend_details": map[string]interface{}{"model": map[string]interface{}{"name": "anthropic--claude-4.6-sonnet",[NIT] The test comment
// Use a pointer to capture the server URL for use in the handleris slightly misleading —baseURLis a plainstringvariable captured by closure, not a pointer.Security Review
Summary
Security-focused review found no exploitable issues in the new SAP AI Core integration. OAuth token handling, header construction, context propagation, and input validation appear sound, and sensitive values are not logged.
Recommendation
APPROVE — CI passed and the changes introduce a native SAP AI Core provider with proper OAuth client-credential flow, thread-safe token caching, deployment discovery, and provider routing without exposing secrets. Error messages avoid printing credentials, requests use context-based timeouts, and path handling for local files remains constrained to the workspace. From a security perspective, this is acceptable to merge. As future hardening, consider enforcing HTTPS for AI Core URLs and avoiding logging full upstream error bodies for auth/API failures, but these are not blockers.
Review by security
Evaluated against
44c80c36Gpt Review
Summary
Solid addition of native SAP AI Core support with clean integration into flags, action, and client routing. Code is idiomatic, thread-safe, and well-tested; CI passes.
Findings
llm/aicore.gollm/aicore.gollm/aicore.goRecommendation
APPROVE — The PR cleanly introduces native SAP AI Core support with appropriate flags, validation, routing, and comprehensive tests. The implementation is idiomatic, concurrency-safe, and aligns with repository conventions; CI is green. Consider minor improvements: avoid holding the mutex during network calls in fetchDeployments, add Content-Length mismatch validation in AI Core response handling (parity with doRequest), and add a basic invalidation strategy for the deployment cache to handle topology changes. None of these are blockers for merging.
Review by gpt
Evaluated against
44c80c36@@ -0,0 +145,4 @@token, err := c.getToken(ctx)if err != nil {return "", fmt.Errorf("get token for deployments: %w", err)}[MINOR] Deployment URL cache never invalidates for a model once populated. If a deployment stops or changes, the client may keep using a stale URL. Consider a TTL on entries or refreshing on specific HTTP errors (e.g., 404/410) to keep mappings current.
@@ -0,0 +150,4 @@c.mu.Lock()defer c.mu.Unlock()// Double-check after acquiring write lock[MINOR] getDeploymentURL holds the write lock while performing the network request in fetchDeployments, which can block other callers under load. Consider releasing the lock before the HTTP call and re-acquiring it to update the cache (double-checking first) to avoid long lock holds.
@@ -0,0 +279,4 @@req.Header.Set("Content-Type", "application/json")resp, err := c.http.Do(req)if err != nil {[MINOR] AI Core request paths do not validate response body length against Content-Length like client.doRequest does. Adding a similar check (and treating mismatches as retryable) would harden against truncated responses. This applies to both CompleteAnthropic and CompleteOpenAI.
Looking at the feedback there are some code quality issues that should be addressed.
Pull request closed