feat: add native SAP AI Core support #50

Closed
rodin wants to merge 12 commits from feat/aicore-provider into main
Owner

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.

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

- uses: https://gitea.weiker.me/rodin/review-bot/.gitea/actions/review@main
  with:
    reviewer-token: ${{ secrets.REVIEW_TOKEN }}
    reviewer-name: aicore-review
    llm-model: anthropic--claude-4.6-sonnet
    llm-provider: aicore
    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 #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. ## 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 ```yaml - uses: https://gitea.weiker.me/rodin/review-bot/.gitea/actions/review@main with: reviewer-token: ${{ secrets.REVIEW_TOKEN }} reviewer-name: aicore-review llm-model: anthropic--claude-4.6-sonnet llm-provider: aicore 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 #49
rodin added 1 commit 2026-05-09 00:49:41 +00:00
feat: add native SAP AI Core support
CI / test (pull_request) Successful in 13s
CI / review (/anthropic/v1, claude-sonnet-4-6, sonnet, anthropic, SONNET_REVIEW_TOKEN) (pull_request) Failing after 12s
CI / review (/openai/v1, gpt-4.1, gpt41, openai, GPT_REVIEW_TOKEN) (pull_request) Failing after 12s
CI / review (/openai/v1, gpt-4.1-mini, gpt41-mini, openai, GPT_REVIEW_TOKEN) (pull_request) Failing after 14s
CI / review (/openai/v1, gpt-5-mini, gpt5-mini, openai, GPT_REVIEW_TOKEN) (pull_request) Failing after 11s
CI / review (/openai/v1, gpt-5, security, openai, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m12s
CI / review (/openai/v1, gpt-5, gpt, openai, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m28s
cf453504cb
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 #49
rodin self-assigned this 2026-05-09 04:41:10 +00:00
rodin added 1 commit 2026-05-10 04:53:53 +00:00
chore: retrigger CI after LLM_BASE_URL fix
CI / test (pull_request) Successful in 14s
CI / review (/openai/v1, gpt-4.1-mini, gpt41-mini, openai, GPT_REVIEW_TOKEN) (pull_request) Failing after 16s
CI / review (/anthropic/v1, claude-sonnet-4-6, sonnet, anthropic, SONNET_REVIEW_TOKEN) (pull_request) Failing after 17s
CI / review (/openai/v1, gpt-4.1, gpt41, openai, GPT_REVIEW_TOKEN) (pull_request) Failing after 16s
CI / review (/openai/v1, gpt-5-mini, gpt5-mini, openai, GPT_REVIEW_TOKEN) (pull_request) Failing after 13s
CI / review (/openai/v1, gpt-5, security, openai, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m17s
CI / review (/openai/v1, gpt-5, gpt, openai, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m2s
c3ec44a87b
security-review-bot requested review from security-review-bot 2026-05-10 04:55:27 +00:00
security-review-bot approved these changes 2026-05-10 04:55:27 +00:00
Dismissed
security-review-bot left a comment
Collaborator

Security 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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 97 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.
2 [MINOR] llm/aicore.go 73 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.
3 [MINOR] llm/aicore.go 150 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.

Recommendation

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

# Security 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 97 | 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. | | 2 | [MINOR] | `llm/aicore.go` | 73 | 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. | | 3 | [MINOR] | `llm/aicore.go` | 150 | 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. | ## Recommendation **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* <!-- review-bot:security --> --- *Evaluated against c3ec44a8*
@@ -0,0 +70,4 @@
if err != nil {
return "", err
}
c.token = token
Collaborator

[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.

**[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()
Collaborator

[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.

**[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 {
Collaborator

[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.

**[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.
gpt-review-bot requested changes 2026-05-10 04:56:11 +00:00
gpt-review-bot left a comment
First-time contributor

Original review

Supersededsee 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

# Severity File Line Finding
1 [MAJOR] llm/aicore.go 46 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.
2 [MINOR] llm/aicore.go 245 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.
3 [NIT] llm/aicore.go 357 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).

Recommendation

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

~~Original review~~ **Superseded** — [see current review](https://gitea.weiker.me/rodin/review-bot/pulls/50#pullrequestreview-1935) for up-to-date findings. <details><summary>Previous findings (commit c3ec44a8)</summary> # 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MAJOR] | `llm/aicore.go` | 46 | 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. | | 2 | [MINOR] | `llm/aicore.go` | 245 | 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. | | 3 | [NIT] | `llm/aicore.go` | 357 | 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). | ## Recommendation **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* <!-- review-bot:gpt --> --- *Evaluated against c3ec44a8* </details> <!-- review-bot:gpt -->
@@ -0,0 +43,4 @@
func NewAICoreClient(cfg AICoreConfig) *AICoreClient {
return &AICoreClient{
config: cfg,
http: &http.Client{Timeout: 30 * time.Second},
First-time contributor

[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.

**[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,
First-time contributor

[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.

**[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 ChatResponse
if err := json.Unmarshal(body, &openaiResp); err != nil {
return "", fmt.Errorf("parse response: %w", err)
}
First-time contributor

[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).

**[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-bot requested review from security-review-bot 2026-05-10 04:58:11 +00:00
security-review-bot requested changes 2026-05-10 04:58:11 +00:00
Dismissed
security-review-bot left a comment
Collaborator

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

# Severity File Line Finding
1 [MAJOR] .gitea/actions/review/action.yml 1 CI is failing for multiple review jobs; per process rules, failing CI requires changes before merge.
2 [MINOR] llm/aicore.go 1 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.
3 [MINOR] llm/aicore.go 1 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.
4 [MINOR] llm/aicore.go 1 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.

Recommendation

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

# 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MAJOR] | `.gitea/actions/review/action.yml` | 1 | CI is failing for multiple review jobs; per process rules, failing CI requires changes before merge. | | 2 | [MINOR] | `llm/aicore.go` | 1 | 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. | | 3 | [MINOR] | `llm/aicore.go` | 1 | 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. | | 4 | [MINOR] | `llm/aicore.go` | 1 | 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. | ## Recommendation **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* <!-- review-bot:security --> --- *Evaluated against c3ec44a8*
@@ -0,0 +1,368 @@
package llm
Collaborator

[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]** 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.
Collaborator

[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]** 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.
Collaborator

[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.

**[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.
gpt-review-bot requested changes 2026-05-10 04:58:20 +00:00
gpt-review-bot left a comment
First-time contributor

Original review

Supersededsee 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

# Severity File Line Finding
1 [MAJOR] .gitea/actions/review/action.yml 1 CI is failing: multiple review jobs (OpenAI/Anthropic) are red in this PR. Per repo conventions, CI must pass before merge.
2 [MINOR] llm/aicore.go 350 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.
3 [MINOR] llm/aicore.go 157 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.
4 [NIT] llm/aicore_test.go 412 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.
5 [NIT] cmd/review-bot/main.go 69 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.

Recommendation

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

~~Original review~~ **Superseded** — [see current review](https://gitea.weiker.me/rodin/review-bot/pulls/50#pullrequestreview-1939) for up-to-date findings. <details><summary>Previous findings (commit c3ec44a8)</summary> # 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MAJOR] | `.gitea/actions/review/action.yml` | 1 | CI is failing: multiple review jobs (OpenAI/Anthropic) are red in this PR. Per repo conventions, CI must pass before merge. | | 2 | [MINOR] | `llm/aicore.go` | 350 | 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. | | 3 | [MINOR] | `llm/aicore.go` | 157 | 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. | | 4 | [NIT] | `llm/aicore_test.go` | 412 | 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. | | 5 | [NIT] | `cmd/review-bot/main.go` | 69 | 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. | ## Recommendation **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* <!-- review-bot:gpt --> --- *Evaluated against c3ec44a8* </details> <!-- review-bot:gpt -->
@@ -69,7 +69,13 @@ func main() {
dryRun := flag.Bool("dry-run", false, "Print review to stdout instead of posting")
First-time contributor

[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.

**[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 {
First-time contributor

[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.

**[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 {
First-time contributor

[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.

**[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 string
mux := http.NewServeMux()
First-time contributor

[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.

**[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.
rodin added 1 commit 2026-05-10 04:58:37 +00:00
ci: use SAP AI Core model names and remove unavailable models
CI / test (pull_request) Successful in 15s
CI / review (/anthropic/v1, anthropic--claude-4.6-sonnet, sonnet, anthropic, SONNET_REVIEW_TOKEN) (pull_request) Successful in 34s
CI / review (/openai/v1, gpt-5, security, openai, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 2m10s
CI / review (/openai/v1, gpt-5, gpt, openai, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m21s
a62b791b9e
- Use 'anthropic--claude-4.6-sonnet' instead of 'claude-sonnet-4-6'
  (hai-aicore proxy requires the 'anthropic--' prefix)
- Remove gpt-4.1, gpt-4.1-mini, gpt-5-mini reviewers since those models
  are not deployed in SAP AI Core
- Keep sonnet, gpt-5, and security reviews
sonnet-review-bot approved these changes 2026-05-10 04:59:26 +00:00
sonnet-review-bot left a comment
First-time contributor

Original review

Supersededsee 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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 195 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.
2 [MINOR] llm/aicore.go 168 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.
3 [MINOR] llm/aicore.go 41 The deployment.ID field is stored but never read — it's populated in fetchDeployments but getDeploymentURL only returns d.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.
4 [NIT] llm/aicore_test.go 388 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, but fmt.Sprintf("token-%d", call) would be clearer and more robust.
5 [NIT] llm/aicore_test.go 390 TestAICoreClient_TokenExpiry ignores the error return from getToken on lines 390 and 396 (token1, _ := and token2, _ :=). Tests should check errors or use t.Fatal to avoid masking failures.
6 [NIT] llm/aicore.go 261 In CompleteAnthropic, getDeploymentURL and then getToken are called sequentially. getDeploymentURL internally already calls getToken (to fetch deployments), so the second explicit getToken call will typically be a cache hit. This is correct and safe, but a comment explaining the double-call or refactoring to return the token from getDeploymentURL would improve readability.
7 [NIT] .gitea/actions/review/action.yml 66 Trailing whitespace on the default: 'default' line for aicore-resource-group (consistent with the old llm-provider line 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

~~Original review~~ **Superseded** — [see current review](https://gitea.weiker.me/rodin/review-bot/pulls/50#pullrequestreview-1948) for up-to-date findings. <details><summary>Previous findings (commit a62b791b)</summary> # 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 195 | 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. | | 2 | [MINOR] | `llm/aicore.go` | 168 | 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. | | 3 | [MINOR] | `llm/aicore.go` | 41 | The `deployment.ID` field is stored but never read — it's populated in fetchDeployments but `getDeploymentURL` only returns `d.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. | | 4 | [NIT] | `llm/aicore_test.go` | 388 | 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, but `fmt.Sprintf("token-%d", call)` would be clearer and more robust. | | 5 | [NIT] | `llm/aicore_test.go` | 390 | TestAICoreClient_TokenExpiry ignores the error return from `getToken` on lines 390 and 396 (`token1, _ :=` and `token2, _ :=`). Tests should check errors or use t.Fatal to avoid masking failures. | | 6 | [NIT] | `llm/aicore.go` | 261 | In CompleteAnthropic, `getDeploymentURL` and then `getToken` are called sequentially. `getDeploymentURL` internally already calls `getToken` (to fetch deployments), so the second explicit `getToken` call will typically be a cache hit. This is correct and safe, but a comment explaining the double-call or refactoring to return the token from `getDeploymentURL` would improve readability. | | 7 | [NIT] | `.gitea/actions/review/action.yml` | 66 | Trailing whitespace on the `default: 'default' ` line for `aicore-resource-group` (consistent with the old `llm-provider` line 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* <!-- review-bot:sonnet --> --- *Evaluated against a62b791b* </details> <!-- review-bot:sonnet -->
@@ -0,0 +38,4 @@
ID string
URL string
}
First-time contributor

[MINOR] The deployment.ID field is stored but never read — it's populated in fetchDeployments but getDeploymentURL only returns d.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.

**[MINOR]** The `deployment.ID` field is stored but never read — it's populated in fetchDeployments but `getDeploymentURL` only returns `d.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)
First-time contributor

[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.

**[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.
llm/aicore.go Outdated
@@ -0,0 +192,4 @@
} `json:"backend_details"`
} `json:"resources"`
} `json:"details"`
} `json:"resources"`
First-time contributor

[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.

**[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 models
invokeURL := strings.TrimRight(deployURL, "/") + "/invoke"
req, err := http.NewRequestWithContext(ctx, http.MethodPost, invokeURL, bytes.NewReader(data))
First-time contributor

[NIT] In CompleteAnthropic, getDeploymentURL and then getToken are called sequentially. getDeploymentURL internally already calls getToken (to fetch deployments), so the second explicit getToken call will typically be a cache hit. This is correct and safe, but a comment explaining the double-call or refactoring to return the token from getDeploymentURL would improve readability.

**[NIT]** In CompleteAnthropic, `getDeploymentURL` and then `getToken` are called sequentially. `getDeploymentURL` internally already calls `getToken` (to fetch deployments), so the second explicit `getToken` call will typically be a cache hit. This is correct and safe, but a comment explaining the double-call or refactoring to return the token from `getDeploymentURL` would improve readability.
@@ -0,0 +385,4 @@
}
}
func TestClient_WithAICore(t *testing.T) {
First-time contributor

[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, but fmt.Sprintf("token-%d", call) would be clearer and more robust.

**[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, but `fmt.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 {
First-time contributor

[NIT] TestAICoreClient_TokenExpiry ignores the error return from getToken on lines 390 and 396 (token1, _ := and token2, _ :=). Tests should check errors or use t.Fatal to avoid masking failures.

**[NIT]** TestAICoreClient_TokenExpiry ignores the error return from `getToken` on lines 390 and 396 (`token1, _ :=` and `token2, _ :=`). Tests should check errors or use t.Fatal to avoid masking failures.
security-review-bot requested review from security-review-bot 2026-05-10 05:01:02 +00:00
security-review-bot approved these changes 2026-05-10 05:01:02 +00:00
Dismissed
security-review-bot left a comment
Collaborator

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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 103 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.
2 [MINOR] llm/aicore.go 78 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.
3 [NIT] llm/aicore.go 45 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.

Recommendation

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

# 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 103 | 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. | | 2 | [MINOR] | `llm/aicore.go` | 78 | 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. | | 3 | [NIT] | `llm/aicore.go` | 45 | 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. | ## Recommendation **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* <!-- review-bot: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,
Collaborator

[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.

**[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) {
Collaborator

[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.

**[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 {
Collaborator

[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.

**[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.
gpt-review-bot requested changes 2026-05-10 05:01:13 +00:00
gpt-review-bot left a comment
First-time contributor

Original review

Supersededsee 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

# Severity File Line Finding
1 [MAJOR] llm/aicore.go 45 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.
2 [MINOR] llm/client.go 119 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.
3 [NIT] llm/aicore.go 329 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.
4 [NIT] llm/aicore.go 366 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.

Recommendation

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

~~Original review~~ **Superseded** — [see current review](https://gitea.weiker.me/rodin/review-bot/pulls/50#pullrequestreview-1951) for up-to-date findings. <details><summary>Previous findings (commit a62b791b)</summary> # 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MAJOR] | `llm/aicore.go` | 45 | 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. | | 2 | [MINOR] | `llm/client.go` | 119 | 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. | | 3 | [NIT] | `llm/aicore.go` | 329 | 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. | | 4 | [NIT] | `llm/aicore.go` | 366 | 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. | ## Recommendation **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* <!-- review-bot:gpt --> --- *Evaluated against a62b791b* </details> <!-- review-bot:gpt -->
@@ -0,0 +42,4 @@
// NewAICoreClient creates a new AI Core client with the given configuration.
func NewAICoreClient(cfg AICoreConfig) *AICoreClient {
return &AICoreClient{
config: cfg,
First-time contributor

[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.

**[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
First-time contributor

[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.

**[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 {
First-time contributor

[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.

**[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, erro
return "", err
First-time contributor

[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.

**[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.
rodin added 1 commit 2026-05-10 05:29:25 +00:00
fix: propagate LLM timeout to AI Core client
CI / test (pull_request) Successful in 15s
CI / review (/anthropic/v1, anthropic--claude-4.6-sonnet, sonnet, anthropic, SONNET_REVIEW_TOKEN) (pull_request) Successful in 32s
CI / review (/openai/v1, gpt-5, security, openai, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m58s
CI / review (/openai/v1, gpt-5, gpt, openai, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m16s
34507dd9ff
Address review feedback:

MAJOR:
- AICoreClient now defaults to 5min timeout (matching Client)
- Add AICoreClient.WithTimeout() for explicit timeout control
- Client.WithAICore() inherits parent client's timeout
- Client.WithTimeout() propagates to aicore client if set

MINOR:
- Extract AICoreOpenAIAPIVersion constant for the hardcoded api-version
- Tighten IsAnthropicModel to only match 'anthropic--' prefix
  (SAP AI Core always uses this prefix for Anthropic models)

NIT:
- Use fmt.Sprintf for token generation in tests (robust for >9 calls)
- Add error checking in TestAICoreClient_TokenExpiry
- Add tests for WithTimeout propagation
sonnet-review-bot approved these changes 2026-05-10 05:30:12 +00:00
sonnet-review-bot left a comment
First-time contributor

Original review

Supersededsee 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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 163 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.
2 [MINOR] llm/aicore.go 204 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).
3 [NIT] llm/aicore.go 57 The field name http shadows the imported net/http package 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 existing llm/client.go already uses the same pattern so this is consistent with the codebase, but worth noting as a readability concern.
4 [NIT] llm/aicore.go 336 CompleteAnthropic and CompleteOpenAI are public methods on AICoreClient. Since the intended consumer is only completeAICore in 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.
5 [NIT] .gitea/actions/review/action.yml 66 Trailing whitespace on default: '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

~~Original review~~ **Superseded** — [see current review](https://gitea.weiker.me/rodin/review-bot/pulls/50#pullrequestreview-1990) for up-to-date findings. <details><summary>Previous findings (commit 34507dd9)</summary> # 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 163 | 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. | | 2 | [MINOR] | `llm/aicore.go` | 204 | 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). | | 3 | [NIT] | `llm/aicore.go` | 57 | The field name `http` shadows the imported `net/http` package 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 existing `llm/client.go` already uses the same pattern so this is consistent with the codebase, but worth noting as a readability concern. | | 4 | [NIT] | `llm/aicore.go` | 336 | CompleteAnthropic and CompleteOpenAI are public methods on AICoreClient. Since the intended consumer is only `completeAICore` in 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. | | 5 | [NIT] | `.gitea/actions/review/action.yml` | 66 | Trailing whitespace on `default: '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* <!-- review-bot:sonnet --> --- *Evaluated against 34507dd9* </details> <!-- review-bot:sonnet -->
@@ -0,0 +54,4 @@
}
// WithTimeout sets the HTTP request timeout for AI Core calls.
// This should be called during construction, before concurrent use.
First-time contributor

[NIT] The field name http shadows the imported net/http package 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 existing llm/client.go already uses the same pattern so this is consistent with the codebase, but worth noting as a readability concern.

**[NIT]** The field name `http` shadows the imported `net/http` package 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 existing `llm/client.go` already 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
First-time contributor

[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.

**[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"`
First-time contributor

[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).

**[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 {
First-time contributor

[NIT] CompleteAnthropic and CompleteOpenAI are public methods on AICoreClient. Since the intended consumer is only completeAICore in 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.

**[NIT]** CompleteAnthropic and CompleteOpenAI are public methods on AICoreClient. Since the intended consumer is only `completeAICore` in 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-bot requested review from security-review-bot 2026-05-10 05:31:39 +00:00
security-review-bot approved these changes 2026-05-10 05:31:39 +00:00
Dismissed
security-review-bot left a comment
Collaborator

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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 106 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.
2 [MINOR] llm/aicore.go 111 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.
3 [MINOR] llm/aicore.go 179 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.
4 [MINOR] llm/aicore.go 184 Error path for deployments includes the entire response body in the error message. Consider truncating or redacting to avoid leaking potentially sensitive upstream details.
5 [MINOR] llm/aicore.go 282 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.
6 [MINOR] llm/aicore.go 287 Anthropic error path includes full response body in error. Truncate or sanitize to reduce risk of leaking sensitive content into logs.
7 [MINOR] llm/aicore.go 352 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.
8 [MINOR] llm/aicore.go 357 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.

Recommendation

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

# 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 106 | 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. | | 2 | [MINOR] | `llm/aicore.go` | 111 | 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. | | 3 | [MINOR] | `llm/aicore.go` | 179 | 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. | | 4 | [MINOR] | `llm/aicore.go` | 184 | Error path for deployments includes the entire response body in the error message. Consider truncating or redacting to avoid leaking potentially sensitive upstream details. | | 5 | [MINOR] | `llm/aicore.go` | 282 | 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. | | 6 | [MINOR] | `llm/aicore.go` | 287 | Anthropic error path includes full response body in error. Truncate or sanitize to reduce risk of leaking sensitive content into logs. | | 7 | [MINOR] | `llm/aicore.go` | 352 | 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. | | 8 | [MINOR] | `llm/aicore.go` | 357 | 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. | ## Recommendation **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* <!-- review-bot: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)
Collaborator

[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.

**[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 {
Collaborator

[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.

**[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)
Collaborator

[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.

**[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 {
Collaborator

[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.

**[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)
Collaborator

[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.

**[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 {
Collaborator

[MINOR] Anthropic error path includes full response body in error. Truncate or sanitize to reduce risk of leaking sensitive content into logs.

**[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)
Collaborator

[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.

**[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 {
Collaborator

[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.

**[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-bot approved these changes 2026-05-10 05:31:55 +00:00
gpt-review-bot left a comment
First-time contributor

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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 152 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.
2 [MINOR] llm/aicore.go 214 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.

Recommendation

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

# 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 152 | 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. | | 2 | [MINOR] | `llm/aicore.go` | 214 | 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. | ## Recommendation **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* <!-- review-bot:gpt --> --- *Evaluated against 34507dd9*
@@ -0,0 +149,4 @@
c.mu.Lock()
defer c.mu.Unlock()
First-time contributor

[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.

**[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
First-time contributor

[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.

**[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.
Author
Owner

Self-review: PASS

No blocking issues found — ready for human review.

Notes (informational, not blocking)

[fit] staticcheck reports:

  • llm/aicore.go:237 and llm/client.go:231: struct literal conversion style (S1016) — minor style nit, existing in both old and new code
  • gitea/diff.go:78: HasPrefix return ignored (SA4017) — pre-existing, not introduced by this PR
  • cmd/review-bot/main_test.go:347: nil Context (SA1012) — pre-existing, not introduced by this PR

[fit] Body length validation: aicore.go does not include the Content-Length vs body length validation that doRequest has in client.go. This is acceptable because:

  1. AI Core uses OAuth tokens which are short-lived, so truncation is less likely
  2. The retry logic still applies via "read response" error pattern
  3. Adding complexity to aicore.go for an edge case that hasn't manifested is premature

[completeness] Tests pass (go test ./...), go vet clean, no uncommitted changes.

## Self-review: PASS No blocking issues found — ready for human review. ### Notes (informational, not blocking) **[fit]** `staticcheck` reports: - `llm/aicore.go:237` and `llm/client.go:231`: struct literal conversion style (S1016) — minor style nit, existing in both old and new code - `gitea/diff.go:78`: HasPrefix return ignored (SA4017) — pre-existing, not introduced by this PR - `cmd/review-bot/main_test.go:347`: nil Context (SA1012) — pre-existing, not introduced by this PR **[fit]** Body length validation: `aicore.go` does not include the Content-Length vs body length validation that `doRequest` has in `client.go`. This is acceptable because: 1. AI Core uses OAuth tokens which are short-lived, so truncation is less likely 2. The retry logic still applies via "read response" error pattern 3. Adding complexity to aicore.go for an edge case that hasn't manifested is premature **[completeness]** Tests pass (`go test ./...`), `go vet` clean, no uncommitted changes.
rodin removed their assignment 2026-05-10 05:51:33 +00:00
aweiker was assigned by rodin 2026-05-10 05:51:33 +00:00
rodin added 1 commit 2026-05-10 06:14:52 +00:00
ci: switch to native aicore provider, remove HAI proxy dependency
CI / test (pull_request) Successful in 14s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Failing after 18s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m10s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m44s
d02c75486e
security-review-bot requested review from security-review-bot 2026-05-10 06:16:19 +00:00
security-review-bot approved these changes 2026-05-10 06:16:19 +00:00
Dismissed
security-review-bot left a comment
Collaborator

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 d02c7548

# 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* <!-- review-bot:security --> --- *Evaluated against d02c7548*
gpt-review-bot approved these changes 2026-05-10 06:16:53 +00:00
gpt-review-bot left a comment
First-time contributor

Gpt 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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 1 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.
2 [NIT] llm/aicore_test.go 1 Test name 'TestClient_WithTimeout_PropagatestoAICore' has inconsistent capitalization ('Propagatesto'); consider renaming to 'TestClient_WithTimeout_PropagatesToAICore' for readability consistency.
3 [NIT] .gitea/actions/review/action.yml 1 Minor formatting: the 'aicore-resource-group' input appears to have a trailing space after the default value in the diff; ensure no trailing whitespace in YAML to keep diffs clean.

Recommendation

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

# Gpt 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 1 | 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. | | 2 | [NIT] | `llm/aicore_test.go` | 1 | Test name 'TestClient_WithTimeout_PropagatestoAICore' has inconsistent capitalization ('Propagatesto'); consider renaming to 'TestClient_WithTimeout_PropagatesToAICore' for readability consistency. | | 3 | [NIT] | `.gitea/actions/review/action.yml` | 1 | Minor formatting: the 'aicore-resource-group' input appears to have a trailing space after the default value in the diff; ensure no trailing whitespace in YAML to keep diffs clean. | ## Recommendation **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* <!-- review-bot:gpt --> --- *Evaluated against d02c7548*
@@ -0,0 +1,380 @@
package llm
First-time contributor

[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.

**[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
First-time contributor

[NIT] Test name 'TestClient_WithTimeout_PropagatestoAICore' has inconsistent capitalization ('Propagatesto'); consider renaming to 'TestClient_WithTimeout_PropagatesToAICore' for readability consistency.

**[NIT]** Test name 'TestClient_WithTimeout_PropagatestoAICore' has inconsistent capitalization ('Propagatesto'); consider renaming to 'TestClient_WithTimeout_PropagatesToAICore' for readability consistency.
rodin added 1 commit 2026-05-10 06:19:06 +00:00
fix: add anthropic-version header for AI Core Anthropic endpoint
CI / test (pull_request) Successful in 14s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Failing after 17s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m4s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m9s
b92a968d93
security-review-bot requested review from security-review-bot 2026-05-10 06:20:37 +00:00
security-review-bot approved these changes 2026-05-10 06:20:37 +00:00
Dismissed
security-review-bot left a comment
Collaborator

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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 127 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.
2 [MINOR] llm/aicore.go 199 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.
3 [MINOR] llm/aicore.go 156 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.
4 [MINOR] cmd/review-bot/main.go 106 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.

Recommendation

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

# 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 127 | 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. | | 2 | [MINOR] | `llm/aicore.go` | 199 | 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. | | 3 | [MINOR] | `llm/aicore.go` | 156 | 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. | | 4 | [MINOR] | `cmd/review-bot/main.go` | 106 | 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. | ## Recommendation **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* <!-- review-bot: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")
Collaborator

[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.

**[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 == "" {
Collaborator

[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.

**[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 lock
if d, ok := c.deployments[model]; ok {
return d.URL, nil
}
Collaborator

[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.

**[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 {
Collaborator

[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.

**[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-bot approved these changes 2026-05-10 06:20:40 +00:00
gpt-review-bot left a comment
First-time contributor

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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 1 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.
2 [NIT] llm/aicore_test.go 1 Test name 'TestClient_WithTimeout_PropagatestoAICore' has a minor typo/casing; consider renaming to 'TestClient_WithTimeout_PropagatesToAICore' for readability and consistency.
3 [NIT] .gitea/actions/review/action.yml 1 Inputs for llm-base-url and llm-api-key are now optional in the action, with validation deferred to the binary (main.go). Consider adding a brief note in the action description or input docs that these are required unless llm-provider=aicore to reduce user confusion at the action level.
4 [NIT] llm/aicore.go 1 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.

Recommendation

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

# 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 1 | 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. | | 2 | [NIT] | `llm/aicore_test.go` | 1 | Test name 'TestClient_WithTimeout_PropagatestoAICore' has a minor typo/casing; consider renaming to 'TestClient_WithTimeout_PropagatesToAICore' for readability and consistency. | | 3 | [NIT] | `.gitea/actions/review/action.yml` | 1 | Inputs for llm-base-url and llm-api-key are now optional in the action, with validation deferred to the binary (main.go). Consider adding a brief note in the action description or input docs that these are required unless llm-provider=aicore to reduce user confusion at the action level. | | 4 | [NIT] | `llm/aicore.go` | 1 | 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. | ## Recommendation **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* <!-- review-bot:gpt --> --- *Evaluated against b92a968d*
@@ -0,0 +1,381 @@
package llm
First-time contributor

[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.

**[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.
First-time contributor

[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.

**[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
First-time contributor

[NIT] Test name 'TestClient_WithTimeout_PropagatestoAICore' has a minor typo/casing; consider renaming to 'TestClient_WithTimeout_PropagatesToAICore' for readability and consistency.

**[NIT]** Test name 'TestClient_WithTimeout_PropagatestoAICore' has a minor typo/casing; consider renaming to 'TestClient_WithTimeout_PropagatesToAICore' for readability and consistency.
rodin added 1 commit 2026-05-10 06:23:47 +00:00
fix: put anthropic_version in request body, not header
CI / test (pull_request) Successful in 12s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Failing after 17s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m50s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m43s
b76270c21b
SAP AI Core expects anthropic_version in the JSON body, not as a header.
security-review-bot requested review from security-review-bot 2026-05-10 06:25:49 +00:00
security-review-bot approved these changes 2026-05-10 06:25:50 +00:00
Dismissed
security-review-bot left a comment
Collaborator

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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 86 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).
2 [MINOR] llm/aicore.go 105 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.
3 [NIT] llm/aicore.go 232 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.

Recommendation

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

# 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 86 | 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). | | 2 | [MINOR] | `llm/aicore.go` | 105 | 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. | | 3 | [NIT] | `llm/aicore.go` | 232 | 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. | ## Recommendation **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* <!-- review-bot:security --> --- *Evaluated against b76270c2*
@@ -0,0 +83,4 @@
return "", err
}
c.token = token
c.tokenExpiry = expiry
Collaborator

[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).

**[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 {
Collaborator

[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.

**[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 {
Collaborator

[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.

**[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-bot approved these changes 2026-05-10 06:26:42 +00:00
gpt-review-bot left a comment
First-time contributor

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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 158 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.
2 [MINOR] llm/aicore.go 287 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.
3 [MINOR] llm/aicore.go 357 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.

Recommendation

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

# 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 158 | 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. | | 2 | [MINOR] | `llm/aicore.go` | 287 | 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. | | 3 | [MINOR] | `llm/aicore.go` | 357 | 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. | ## Recommendation **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* <!-- review-bot:gpt --> --- *Evaluated against b76270c2*
@@ -0,0 +155,4 @@
return d.URL, nil
}
if err := c.fetchDeployments(ctx, token); err != nil {
First-time contributor

[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.

**[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)
First-time contributor

[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.

**[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)
First-time contributor

[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.

**[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.
rodin added 1 commit 2026-05-10 06:28:29 +00:00
fix: omit model field from AI Core Anthropic request
CI / test (pull_request) Failing after 10s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Has been skipped
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Has been skipped
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Has been skipped
d13e062866
AI Core deployment URL already specifies the model. Sending model in the body
causes 'Extra inputs are not permitted' error.
rodin added 1 commit 2026-05-10 06:36:49 +00:00
test: update Anthropic test to check anthropic_version instead of model
CI / test (pull_request) Successful in 16s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Failing after 16s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m33s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m50s
b12df1a636
security-review-bot requested review from security-review-bot 2026-05-10 06:38:39 +00:00
security-review-bot approved these changes 2026-05-10 06:38:39 +00:00
Dismissed
security-review-bot left a comment
Collaborator

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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 132 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.
2 [MINOR] llm/aicore.go 302 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.
3 [MINOR] llm/aicore.go 352 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.
4 [MINOR] cmd/review-bot/main.go 112 When provider=aicore, the code does not validate that AICORE_AUTH_URL and AICORE_API_URL use HTTPS. Misconfiguration to HTTP would transmit credentials in clear text. Consider enforcing or warning if the URLs are not https://.

Recommendation

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

# 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 132 | 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. | | 2 | [MINOR] | `llm/aicore.go` | 302 | 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. | | 3 | [MINOR] | `llm/aicore.go` | 352 | 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. | | 4 | [MINOR] | `cmd/review-bot/main.go` | 112 | When provider=aicore, the code does not validate that AICORE_AUTH_URL and AICORE_API_URL use HTTPS. Misconfiguration to HTTP would transmit credentials in clear text. Consider enforcing or warning if the URLs are not https://. | ## Recommendation **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* <!-- review-bot:security --> --- *Evaluated against b12df1a6*
@@ -0,0 +129,4 @@
}
expiry := time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second)
return tokenResp.AccessToken, expiry, nil
Collaborator

[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.

**[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")
Collaborator

[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.

**[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 {
Collaborator

[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.

**[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-bot approved these changes 2026-05-10 06:38:55 +00:00
gpt-review-bot left a comment
First-time contributor

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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 158 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.
2 [NIT] TODO.md 1 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.

Recommendation

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

# 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 158 | 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. | | 2 | [NIT] | `TODO.md` | 1 | 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. | ## Recommendation **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* <!-- review-bot:gpt --> --- *Evaluated against b12df1a6*
@@ -0,0 +1,19 @@
## Self-Review: feat/aicore-provider — 2026-05-09
First-time contributor

[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.

**[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 {
First-time contributor

[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.

**[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.
rodin added 1 commit 2026-05-10 06:39:28 +00:00
fix: add omitempty to model field so it's not sent when empty
CI / test (pull_request) Successful in 15s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Failing after 16s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m25s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 2m39s
8da8fca19d
gpt-review-bot approved these changes 2026-05-10 06:41:06 +00:00
gpt-review-bot left a comment
First-time contributor

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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 290 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.
2 [MINOR] llm/aicore.go 343 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.
3 [NIT] .gitea/actions/review/action.yml 40 Minor trailing whitespace in the default value for aicore-resource-group ('default '). This is harmless but can be cleaned up for consistency.

Recommendation

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

# 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 290 | 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. | | 2 | [MINOR] | `llm/aicore.go` | 343 | 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. | | 3 | [NIT] | `.gitea/actions/review/action.yml` | 40 | Minor trailing whitespace in the default value for aicore-resource-group ('default '). This is harmless but can be cleaned up for consistency. | ## Recommendation **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* <!-- review-bot:gpt --> --- *Evaluated against 8da8fca1*
@@ -36,3 +38,3 @@
required: true
llm-provider:
description: 'LLM API provider: openai or anthropic (default openai)'
description: 'LLM API provider: openai, anthropic, or aicore (default openai)'
First-time contributor

[NIT] Minor trailing whitespace in the default value for aicore-resource-group ('default '). This is harmless but can be cleaned up for consistency.

**[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)
}
First-time contributor

[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.

**[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 models
chatURL := strings.TrimRight(deployURL, "/") + "/chat/completions?api-version=" + AICoreOpenAIAPIVersion
req, err := http.NewRequestWithContext(ctx, http.MethodPost, chatURL, bytes.NewReader(data))
First-time contributor

[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.

**[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-bot requested review from security-review-bot 2026-05-10 06:42:20 +00:00
security-review-bot approved these changes 2026-05-10 06:42:20 +00:00
Dismissed
security-review-bot left a comment
Collaborator

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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 268 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.
2 [MINOR] llm/aicore.go 338 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.
3 [MINOR] llm/aicore.go 171 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.
4 [MINOR] llm/aicore.go 112 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.
5 [MINOR] llm/aicore.go 359 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.
6 [NIT] cmd/review-bot/main.go 96 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.

Recommendation

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

# 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 268 | 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. | | 2 | [MINOR] | `llm/aicore.go` | 338 | 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. | | 3 | [MINOR] | `llm/aicore.go` | 171 | 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. | | 4 | [MINOR] | `llm/aicore.go` | 112 | 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. | | 5 | [MINOR] | `llm/aicore.go` | 359 | 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. | | 6 | [NIT] | `cmd/review-bot/main.go` | 96 | 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. | ## Recommendation **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* <!-- review-bot:security --> --- *Evaluated against 8da8fca1*
@@ -89,1 +93,4 @@
// For aicore provider, llm-base-url and llm-api-key are not required
isAICore := llm.Provider(*llmProvider) == llm.ProviderAICore
if *giteaURL == "" || *repo == "" || *prNum == "" || *reviewerToken == "" || *llmModel == "" {
fmt.Fprintf(os.Stderr, "Error: missing required flags or environment variables\n\n")
Collaborator

[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.

**[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)
Collaborator

[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.

**[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 {
Collaborator

[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.

**[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)
Collaborator

[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.

**[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)
Collaborator

[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.

**[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)
Collaborator

[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.

**[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.
rodin added 1 commit 2026-05-10 06:45:16 +00:00
fix: remove anthropic_version from body - AI Core rejects it
CI / test (pull_request) Successful in 15s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Failing after 17s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m37s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m2s
f71f26fcff
security-review-bot requested review from security-review-bot 2026-05-10 06:47:09 +00:00
security-review-bot approved these changes 2026-05-10 06:47:09 +00:00
Dismissed
security-review-bot left a comment
Collaborator

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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 116 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).
2 [MINOR] llm/aicore.go 206 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.
3 [MINOR] llm/aicore.go 288 Unbounded io.ReadAll in CompleteAnthropic; add a maximum body size to mitigate potential memory exhaustion if the server returns an unexpectedly large payload.
4 [MINOR] llm/aicore.go 358 Unbounded io.ReadAll in CompleteOpenAI; consider io.LimitReader or streaming decode to limit memory use.
5 [MINOR] cmd/review-bot/main.go 104 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.
6 [NIT] llm/aicore.go 299 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.

Recommendation

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

# 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 116 | 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). | | 2 | [MINOR] | `llm/aicore.go` | 206 | 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. | | 3 | [MINOR] | `llm/aicore.go` | 288 | Unbounded io.ReadAll in CompleteAnthropic; add a maximum body size to mitigate potential memory exhaustion if the server returns an unexpectedly large payload. | | 4 | [MINOR] | `llm/aicore.go` | 358 | Unbounded io.ReadAll in CompleteOpenAI; consider io.LimitReader or streaming decode to limit memory use. | | 5 | [MINOR] | `cmd/review-bot/main.go` | 104 | 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. | | 6 | [NIT] | `llm/aicore.go` | 299 | 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. | ## Recommendation **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* <!-- review-bot: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 == "") {
Collaborator

[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.

**[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))
Collaborator

[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).

**[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"`
Collaborator

[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.

**[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 {
Collaborator

[MINOR] Unbounded io.ReadAll in CompleteAnthropic; add a maximum body size to mitigate potential memory exhaustion if the server returns an unexpectedly large payload.

**[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 anthropicResponse
if err := json.Unmarshal(body, &anthropicResp); err != nil {
return "", fmt.Errorf("parse response: %w", err)
}
Collaborator

[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.

**[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 {
Collaborator

[MINOR] Unbounded io.ReadAll in CompleteOpenAI; consider io.LimitReader or streaming decode to limit memory use.

**[MINOR]** Unbounded io.ReadAll in CompleteOpenAI; consider io.LimitReader or streaming decode to limit memory use.
gpt-review-bot approved these changes 2026-05-10 06:47:33 +00:00
gpt-review-bot left a comment
First-time contributor

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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 1 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.
2 [MINOR] llm/aicore.go 1 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.
3 [NIT] .gitea/actions/review/action.yml 1 Minor YAML formatting nit: trailing space after the default for aicore-resource-group ('default: 'default' '). While harmless, removing trailing spaces keeps diffs clean.
4 [NIT] llm/aicore_test.go 1 Test name 'TestClient_WithTimeout_PropagatestoAICore' has a typo ('Propagatesto'). Consider renaming to 'PropagatesToAICore' for clarity and consistency.

Recommendation

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

# 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 1 | 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. | | 2 | [MINOR] | `llm/aicore.go` | 1 | 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. | | 3 | [NIT] | `.gitea/actions/review/action.yml` | 1 | Minor YAML formatting nit: trailing space after the default for aicore-resource-group ('default: 'default' '). While harmless, removing trailing spaces keeps diffs clean. | | 4 | [NIT] | `llm/aicore_test.go` | 1 | Test name 'TestClient_WithTimeout_PropagatestoAICore' has a typo ('Propagatesto'). Consider renaming to 'PropagatesToAICore' for clarity and consistency. | ## Recommendation **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* <!-- review-bot:gpt --> --- *Evaluated against f71f26fc*
@@ -0,0 +1,381 @@
package llm
First-time contributor

[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]** 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.
First-time contributor

[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.

**[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
First-time contributor

[NIT] Test name 'TestClient_WithTimeout_PropagatestoAICore' has a typo ('Propagatesto'). Consider renaming to 'PropagatesToAICore' for clarity and consistency.

**[NIT]** Test name 'TestClient_WithTimeout_PropagatestoAICore' has a typo ('Propagatesto'). Consider renaming to 'PropagatesToAICore' for clarity and consistency.
rodin added 1 commit 2026-05-10 06:48:28 +00:00
fix: use bedrock-2023-05-31 for AI Core Anthropic version
CI / test (pull_request) Successful in 15s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 38s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m11s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m52s
44c80c36cf
SAP AI Core's Anthropic endpoint uses AWS Bedrock API format, not native
Anthropic API. Verified via integration testing:
- 2023-06-01: Invalid API version
- bedrock-2023-05-31: Works ✓
sonnet-review-bot approved these changes 2026-05-10 06:49:21 +00:00
sonnet-review-bot left a comment
First-time contributor

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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 34 The field http *http.Client shadows the net/http package name within the struct's method bodies. This is valid Go but can cause confusion — a reader seeing c.http.Do(req) has to remember it's a field, not the package. The existing Client in client.go has the same pattern, so this is consistent with the codebase, but renaming to httpClient would be cleaner.
2 [MINOR] llm/aicore.go 197 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 tokenExpiry handles token refresh.
3 [MINOR] llm/aicore.go 162 In getDeploymentURL, the token is fetched before acquiring the write lock (correct to avoid deadlock with getToken's own lock), but then fetchDeployments is called while holding the write lock AND passes the token in. If fetchDeployments takes 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.
4 [MINOR] llm/aicore.go 280 The TODO.md self-review correctly notes that CompleteAnthropic and CompleteOpenAI lack the Content-Length body validation that doRequest has in client.go. Given these methods duplicate the HTTP execution pattern rather than going through doRequest, any future fix to the retry/validation logic in doRequest won't automatically apply here. Consider extracting a shared HTTP execution helper for AI Core, or routing through doRequest via an adapter.
5 [NIT] llm/aicore.go 53 The deployment struct has an exported ID and URL field but the type is unexported. The ID field is stored in the map but never read back — only URL is used. The ID field could be removed unless planned for future use (e.g., logging, metrics).
6 [NIT] llm/aicore_test.go 192 The test comment // Use a pointer to capture the server URL for use in the handler is slightly misleading — baseURL is a plain string variable captured by closure, not a pointer.
7 [NIT] TODO.md 1 Committing a TODO.md containing 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 IsAnthropicModel routing function is clean and the test coverage is comprehensive including token expiry, deployment filtering by status, timeout propagation, and end-to-end routing through Client.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

# 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 34 | The field `http *http.Client` shadows the `net/http` package name within the struct's method bodies. This is valid Go but can cause confusion — a reader seeing `c.http.Do(req)` has to remember it's a field, not the package. The existing `Client` in `client.go` has the same pattern, so this is consistent with the codebase, but renaming to `httpClient` would be cleaner. | | 2 | [MINOR] | `llm/aicore.go` | 197 | 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 `tokenExpiry` handles token refresh. | | 3 | [MINOR] | `llm/aicore.go` | 162 | In `getDeploymentURL`, the token is fetched *before* acquiring the write lock (correct to avoid deadlock with getToken's own lock), but then `fetchDeployments` is called while holding the write lock AND passes the token in. If `fetchDeployments` takes 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. | | 4 | [MINOR] | `llm/aicore.go` | 280 | The `TODO.md` self-review correctly notes that `CompleteAnthropic` and `CompleteOpenAI` lack the Content-Length body validation that `doRequest` has in `client.go`. Given these methods duplicate the HTTP execution pattern rather than going through `doRequest`, any future fix to the retry/validation logic in `doRequest` won't automatically apply here. Consider extracting a shared HTTP execution helper for AI Core, or routing through `doRequest` via an adapter. | | 5 | [NIT] | `llm/aicore.go` | 53 | The `deployment` struct has an exported `ID` and `URL` field but the type is unexported. The `ID` field is stored in the map but never read back — only `URL` is used. The `ID` field could be removed unless planned for future use (e.g., logging, metrics). | | 6 | [NIT] | `llm/aicore_test.go` | 192 | The test comment `// Use a pointer to capture the server URL for use in the handler` is slightly misleading — `baseURL` is a plain `string` variable captured by closure, not a pointer. | | 7 | [NIT] | `TODO.md` | 1 | Committing a `TODO.md` containing 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 `IsAnthropicModel` routing function is clean and the test coverage is comprehensive including token expiry, deployment filtering by status, timeout propagation, and end-to-end routing through `Client.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* <!-- review-bot:sonnet --> --- *Evaluated against 44c80c36*
@@ -0,0 +1,19 @@
## Self-Review: feat/aicore-provider — 2026-05-09
First-time contributor

[NIT] Committing a TODO.md containing 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.

**[NIT]** Committing a `TODO.md` containing 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 AICoreConfig
http *http.Client
First-time contributor

[MINOR] The field http *http.Client shadows the net/http package name within the struct's method bodies. This is valid Go but can cause confusion — a reader seeing c.http.Do(req) has to remember it's a field, not the package. The existing Client in client.go has the same pattern, so this is consistent with the codebase, but renaming to httpClient would be cleaner.

**[MINOR]** The field `http *http.Client` shadows the `net/http` package name within the struct's method bodies. This is valid Go but can cause confusion — a reader seeing `c.http.Do(req)` has to remember it's a field, not the package. The existing `Client` in `client.go` has the same pattern, so this is consistent with the codebase, but renaming to `httpClient` would be cleaner.
@@ -0,0 +50,4 @@
config: cfg,
http: &http.Client{Timeout: 5 * time.Minute},
deployments: make(map[string]deployment),
}
First-time contributor

[NIT] The deployment struct has an exported ID and URL field but the type is unexported. The ID field is stored in the map but never read back — only URL is used. The ID field could be removed unless planned for future use (e.g., logging, metrics).

**[NIT]** The `deployment` struct has an exported `ID` and `URL` field but the type is unexported. The `ID` field is stored in the map but never read back — only `URL` is used. The `ID` field 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 {
First-time contributor

[MINOR] In getDeploymentURL, the token is fetched before acquiring the write lock (correct to avoid deadlock with getToken's own lock), but then fetchDeployments is called while holding the write lock AND passes the token in. If fetchDeployments takes 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.

**[MINOR]** In `getDeploymentURL`, the token is fetched *before* acquiring the write lock (correct to avoid deadlock with getToken's own lock), but then `fetchDeployments` is called while holding the write lock AND passes the token in. If `fetchDeployments` takes 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 {
First-time contributor

[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 tokenExpiry handles token refresh.

**[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 `tokenExpiry` handles 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")
First-time contributor

[MINOR] The TODO.md self-review correctly notes that CompleteAnthropic and CompleteOpenAI lack the Content-Length body validation that doRequest has in client.go. Given these methods duplicate the HTTP execution pattern rather than going through doRequest, any future fix to the retry/validation logic in doRequest won't automatically apply here. Consider extracting a shared HTTP execution helper for AI Core, or routing through doRequest via an adapter.

**[MINOR]** The `TODO.md` self-review correctly notes that `CompleteAnthropic` and `CompleteOpenAI` lack the Content-Length body validation that `doRequest` has in `client.go`. Given these methods duplicate the HTTP execution pattern rather than going through `doRequest`, any future fix to the retry/validation logic in `doRequest` won't automatically apply here. Consider extracting a shared HTTP execution helper for AI Core, or routing through `doRequest` via 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",
First-time contributor

[NIT] The test comment // Use a pointer to capture the server URL for use in the handler is slightly misleading — baseURL is a plain string variable captured by closure, not a pointer.

**[NIT]** The test comment `// Use a pointer to capture the server URL for use in the handler` is slightly misleading — `baseURL` is a plain `string` variable captured by closure, not a pointer.
security-review-bot requested review from security-review-bot 2026-05-10 06:49:54 +00:00
security-review-bot approved these changes 2026-05-10 06:49:54 +00:00
security-review-bot left a comment
Collaborator

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 44c80c36

# 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* <!-- review-bot:security --> --- *Evaluated against 44c80c36*
gpt-review-bot approved these changes 2026-05-10 06:51:34 +00:00
gpt-review-bot left a comment
First-time contributor

Gpt 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

# Severity File Line Finding
1 [MINOR] llm/aicore.go 153 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.
2 [MINOR] llm/aicore.go 282 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.
3 [MINOR] llm/aicore.go 148 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.

Recommendation

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

# Gpt 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 | # | Severity | File | Line | Finding | |---|----------|------|------|--------| | 1 | [MINOR] | `llm/aicore.go` | 153 | 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. | | 2 | [MINOR] | `llm/aicore.go` | 282 | 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. | | 3 | [MINOR] | `llm/aicore.go` | 148 | 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. | ## Recommendation **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* <!-- review-bot: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)
}
First-time contributor

[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.

**[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
First-time contributor

[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.

**[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 {
First-time contributor

[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.

**[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.
aweiker requested changes 2026-05-10 06:55:30 +00:00
aweiker left a comment
Contributor

Looking at the feedback there are some code quality issues that should be addressed.

Looking at the feedback there are some code quality issues that should be addressed.
rodin was assigned by aweiker 2026-05-10 07:49:27 +00:00
rodin closed this pull request 2026-05-10 08:07:59 +00:00
Some checks are pending
CI / test (pull_request) Successful in 15s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 38s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m11s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m52s

Pull request closed

Sign in to join this conversation.
5 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rodin/review-bot#50