-
Notifications
You must be signed in to change notification settings - Fork 0
feat: route OpenCode reviews through contextual gateway #1170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
60dbfce
707088a
9ce3bcf
0bd7630
8cb6a1a
d45d5c5
81e2c75
595899d
e5e0093
4684f6e
84c7448
01b8cac
1f2b93e
3ce92c0
01cd9f6
071ca2c
4dd7065
c9a5ac9
fa4c6fa
cbf937b
e1b3678
2e6b24b
ba6fdec
8a13fa6
8901549
199e655
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2460,6 +2460,18 @@ jobs: | |
| "${install_dir}/opencode" --version | ||
| echo "$install_dir" >>"$GITHUB_PATH" | ||
|
|
||
| - name: Checkout pinned contextual-orchestrator review gateway | ||
| id: contextual_orchestrator_checkout | ||
| if: needs.validate-pr-metadata.outputs.is_private == 'false' | ||
| continue-on-error: true | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| repository: ContextualWisdomLab/contextual-orchestrator | ||
| ref: 838b3de160c341a6f36bf588ae9fcc09989c040c | ||
| path: trusted-contextual-orchestrator | ||
| fetch-depth: 1 | ||
| persist-credentials: false | ||
|
seonghobae marked this conversation as resolved.
seonghobae marked this conversation as resolved.
seonghobae marked this conversation as resolved.
|
||
|
|
||
| - name: Detect central review-process scope | ||
| id: central_review_process_fallback_scope | ||
| if: needs.coverage-evidence.result == 'success' | ||
|
|
@@ -3751,7 +3763,7 @@ jobs: | |
| "$schema": "https://opencode.ai/config.json", | ||
| "model": "nvidia-nim/nvidia/llama-3.3-nemotron-super-49b-v1.5", | ||
| "small_model": "nvidia-nim/meta/llama-3.3-70b-instruct", | ||
| "enabled_providers": ["nvidia-nim", "opencode-free", "opencode", "openai", "openrouter", "github-models"], | ||
| "enabled_providers": ["nvidia-nim", "opencode-free", "opencode", "openai", "openrouter", "github-models", "contextual-orchestrator"], | ||
|
seonghobae marked this conversation as resolved.
|
||
| "lsp": false, | ||
| "mcp": {}, | ||
| "permission": { | ||
|
|
@@ -4429,6 +4441,33 @@ jobs: | |
| } | ||
| } | ||
| } | ||
| }, | ||
| "contextual-orchestrator": { | ||
| "npm": "@ai-sdk/openai-compatible", | ||
| "name": "Contextual Orchestrator", | ||
| "options": { | ||
| "baseURL": "{env:CONTEXTUAL_ORCHESTRATOR_BASE_URL}", | ||
| "apiKey": "{env:CONTEXTUAL_ORCHESTRATOR_TOKEN}" | ||
| }, | ||
| "models": { | ||
| "contextual-orchestrator": { | ||
| "name": "Contextual Orchestrator (auto-discovered)", | ||
| "tool_call": true, | ||
| "reasoning": true, | ||
| "options": { | ||
| "reasoningEffort": "high" | ||
| }, | ||
| "variants": { | ||
| "high": { | ||
| "reasoningEffort": "high" | ||
| } | ||
| }, | ||
| "limit": { | ||
| "context": 200000, | ||
| "output": 32768 | ||
| } | ||
| } | ||
| } | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
|
||
| } | ||
| } | ||
| }' >"${OPENCODE_REVIEW_WORKDIR}/opencode.jsonc" | ||
|
|
@@ -4461,6 +4500,13 @@ jobs: | |
| NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} | ||
| OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} | ||
| NVIDIA_NIM_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} | ||
| NVIDIA_NIM_API_KEY_SUB: ${{ secrets.NVIDIA_NIM_API_KEY_SUB }} | ||
| BYTEZ_API_KEY: ${{ secrets.BYTEZ_API_KEY }} | ||
| CONTEXTUAL_ORCHESTRATOR_ENABLED: "false" | ||
| CONTEXTUAL_ORCHESTRATOR_BASE_URL: "http://127.0.0.1:18080/v1" | ||
| CONTEXTUAL_ORCHESTRATOR_TOKEN: "" | ||
| CONTEXTUAL_ORCHESTRATOR_CHECKOUT_SUCCEEDED: ${{ steps.contextual_orchestrator_checkout.outcome == 'success' && 'true' || 'false' }} | ||
| REPOSITORY_IS_PRIVATE: ${{ needs.validate-pr-metadata.outputs.is_private }} | ||
| SHARE: "false" | ||
| NPM_CONFIG_IGNORE_SCRIPTS: "true" | ||
| NO_COLOR: "1" | ||
|
|
@@ -4477,7 +4523,7 @@ jobs: | |
| # so the OpenRouter slots use cheap paid models billed against the | ||
| # org's OpenRouter credits), then the full-size GPT-4.1 long-context | ||
| # endpoint and provider-specific GPT/o3 fallbacks. | ||
| OPENCODE_MODEL_CANDIDATES: "${{ needs.validate-pr-metadata.outputs.is_private == 'false' && 'nvidia-nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 nvidia-nim/nvidia/llama-3.1-nemotron-ultra-253b-v1 nvidia-nim/nvidia/nemotron-3-super-120b-a12b nvidia-nim/nvidia/nemotron-3-ultra-550b-a55b nvidia-nim/meta/llama-3.3-70b-instruct nvidia-nim/deepseek-ai/deepseek-v4-pro nvidia-nim/mistralai/codestral-22b-instruct-v0.1 opencode-free/nemotron-3-ultra-free opencode-free/deepseek-v4-flash-free opencode-free/north-mini-code-free opencode-free/laguna-s-2.1-free opencode-free/ling-3.0-flash-free opencode-free/big-pickle opencode-free/mimo-v2.5-free opencode-free/hy3-free opencode-free/minimax-m3-free opencode-free/glm-5-free opencode-free/kimi-k2.5-free opencode-free/qwen3.6-plus-free ' || '' }}opencode/gpt-5.6-terra github-models/deepseek/deepseek-v3-0324 openai/gpt-5.4 openrouter/deepseek/deepseek-v3.2 openrouter/qwen/qwen3-coder github-models/openai/gpt-4.1 github-models/openai/gpt-5 github-models/openai/gpt-5-chat github-models/openai/o3 github-models/deepseek/deepseek-r1-0528 github-models/deepseek/deepseek-r1" | ||
| OPENCODE_MODEL_CANDIDATES: "${{ needs.validate-pr-metadata.outputs.is_private == 'false' && 'contextual-orchestrator/contextual-orchestrator nvidia-nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 nvidia-nim/nvidia/llama-3.1-nemotron-ultra-253b-v1 nvidia-nim/nvidia/nemotron-3-super-120b-a12b nvidia-nim/nvidia/nemotron-3-ultra-550b-a55b nvidia-nim/meta/llama-3.3-70b-instruct nvidia-nim/deepseek-ai/deepseek-v4-pro nvidia-nim/mistralai/codestral-22b-instruct-v0.1 opencode-free/nemotron-3-ultra-free opencode-free/deepseek-v4-flash-free opencode-free/north-mini-code-free opencode-free/laguna-s-2.1-free opencode-free/ling-3.0-flash-free opencode-free/big-pickle opencode-free/mimo-v2.5-free opencode-free/hy3-free opencode-free/minimax-m3-free opencode-free/glm-5-free opencode-free/kimi-k2.5-free opencode-free/qwen3.6-plus-free ' || '' }}opencode/gpt-5.6-terra github-models/deepseek/deepseek-v3-0324 openai/gpt-5.4 openrouter/deepseek/deepseek-v3.2 openrouter/qwen/qwen3-coder github-models/openai/gpt-4.1 github-models/openai/gpt-5 github-models/openai/gpt-5-chat github-models/openai/o3 github-models/deepseek/deepseek-r1-0528 github-models/deepseek/deepseek-r1" | ||
|
seonghobae marked this conversation as resolved.
|
||
| # One attempt per model, then fall through to the next model. Retrying | ||
| # the SAME model 5x let a rate-limited/hung leader consume the whole | ||
| # step, so the pool never reached a healthy fallback model. | ||
|
|
@@ -4542,6 +4588,104 @@ jobs: | |
| RUN_ATTEMPT: ${{ github.run_attempt }} | ||
| run: | | ||
| set -euo pipefail | ||
| contextual_gateway_pid="" | ||
| cleanup_contextual_gateway() { | ||
| if [ -n "$contextual_gateway_pid" ]; then | ||
| kill "$contextual_gateway_pid" 2>/dev/null || true | ||
| for cleanup_attempt in 1 2 3 4 5; do | ||
| if ! kill -0 "$contextual_gateway_pid" 2>/dev/null; then | ||
| wait "$contextual_gateway_pid" 2>/dev/null || true | ||
| contextual_gateway_pid="" | ||
| return | ||
| fi | ||
| sleep 1 | ||
| done | ||
| kill -KILL "$contextual_gateway_pid" 2>/dev/null || true | ||
| wait "$contextual_gateway_pid" 2>/dev/null || true | ||
| contextual_gateway_pid="" | ||
| fi | ||
| } | ||
|
seonghobae marked this conversation as resolved.
|
||
| trap cleanup_contextual_gateway EXIT | ||
|
seonghobae marked this conversation as resolved.
|
||
|
|
||
| if [ "$REPOSITORY_IS_PRIVATE" = "false" ]; then | ||
| gateway_license_allowed=false | ||
| license_file="$GITHUB_WORKSPACE/trusted-contextual-orchestrator/LICENSE" | ||
| # SPDX-License-Identifier: MIT | ||
| license_heading="$(sed -n '1p' "$license_file" 2>/dev/null || true)" | ||
| spdx_mit_license_blob_sha=591bbf197b355e60604618c8a8a50bc5a839b204 | ||
| if [ "$CONTEXTUAL_ORCHESTRATOR_CHECKOUT_SUCCEEDED" = "true" ] && | ||
| [ -f "$license_file" ] && | ||
| [ "$license_heading" = "MIT License" ] && | ||
| [ "$(git hash-object --no-filters "$license_file" 2>/dev/null)" = "$spdx_mit_license_blob_sha" ]; then | ||
| gateway_license_allowed=true | ||
| else | ||
| printf 'Pinned contextual-orchestrator gateway license is unavailable or not an allowed MIT license; preserving the existing provider pool.\n' | ||
| fi | ||
| if [ "$CONTEXTUAL_ORCHESTRATOR_CHECKOUT_SUCCEEDED" = "true" ] && | ||
| [ "$gateway_license_allowed" = "true" ] && | ||
| [ -f "$GITHUB_WORKSPACE/trusted-contextual-orchestrator/contextual_orchestrator/review_gateway.py" ] && { | ||
| [ -n "${BYTEZ_API_KEY:-}" ] || | ||
| [ -n "${NVIDIA_NIM_API_KEY:-}" ] || | ||
| [ -n "${NVIDIA_NIM_API_KEY_SUB:-}" ] || | ||
| [ -n "${OPENROUTER_API_KEY:-}" ] || | ||
| [ -n "${OPENAI_API_KEY:-}" ]; | ||
| }; then | ||
| CONTEXTUAL_ORCHESTRATOR_TOKEN="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')" | ||
|
seonghobae marked this conversation as resolved.
|
||
| CONTEXTUAL_ORCHESTRATOR_BASE_URL="http://127.0.0.1:18080/v1" | ||
| export CONTEXTUAL_ORCHESTRATOR_TOKEN CONTEXTUAL_ORCHESTRATOR_BASE_URL | ||
| echo "::add-mask::$CONTEXTUAL_ORCHESTRATOR_TOKEN" | ||
| env -i \ | ||
| PATH="$PATH" \ | ||
| HOME="$HOME" \ | ||
| PYTHONPATH="$GITHUB_WORKSPACE/trusted-contextual-orchestrator${PYTHONPATH:+:$PYTHONPATH}" \ | ||
| BYTEZ_API_KEY="${BYTEZ_API_KEY:-}" \ | ||
| NVIDIA_API_KEY="${NVIDIA_API_KEY:-}" \ | ||
| NVIDIA_NIM_API_KEY="${NVIDIA_NIM_API_KEY:-}" \ | ||
| NVIDIA_NIM_API_KEY_SUB="${NVIDIA_NIM_API_KEY_SUB:-}" \ | ||
| OPENROUTER_API_KEY="${OPENROUTER_API_KEY:-}" \ | ||
| OPENAI_API_KEY="${OPENAI_API_KEY:-}" \ | ||
| CONTEXTUAL_ORCHESTRATOR_TOKEN="$CONTEXTUAL_ORCHESTRATOR_TOKEN" \ | ||
| CONTEXTUAL_ORCHESTRATOR_BASE_URL="$CONTEXTUAL_ORCHESTRATOR_BASE_URL" \ | ||
| GITHUB_ENV=/dev/null \ | ||
| GITHUB_OUTPUT=/dev/null \ | ||
| GITHUB_PATH=/dev/null \ | ||
| GITHUB_STEP_SUMMARY=/dev/null \ | ||
| GITHUB_STATE=/dev/null \ | ||
| BASH_ENV=/dev/null \ | ||
| python3 -m contextual_orchestrator.review_gateway \ | ||
| --host 127.0.0.1 \ | ||
| --port 18080 \ | ||
| >"${RUNNER_TEMP}/contextual-orchestrator-gateway.log" 2>&1 & | ||
| contextual_gateway_pid=$! | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| contextual_gateway_ready=false | ||
| models_file="$(mktemp "${RUNNER_TEMP}/contextual-orchestrator-models.XXXXXX")" | ||
| for gateway_attempt in 1 2 3 4 5 6 7 8 9 10; do | ||
| if curl -fsS --max-time 3 \ | ||
| "${CONTEXTUAL_ORCHESTRATOR_BASE_URL%/v1}/healthz" >/dev/null && | ||
| curl -fsS --max-time 3 \ | ||
| -H "Authorization: Bearer ${CONTEXTUAL_ORCHESTRATOR_TOKEN}" \ | ||
| -o "$models_file" \ | ||
| "${CONTEXTUAL_ORCHESTRATOR_BASE_URL}/models" && | ||
| python3 -c 'import json,sys; payload=json.load(open(sys.argv[1], encoding="utf-8")); models=payload.get("data") if isinstance(payload, dict) else None; raise SystemExit(0 if isinstance(models, list) and any(isinstance(model, dict) and str(model.get("id") or "").strip() for model in models) else 1)' "$models_file" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: Readiness check prints tracebacks on malformed model responses The readiness loop's inline Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| then | ||
| contextual_gateway_ready=true | ||
| break | ||
| fi | ||
| sleep 1 | ||
| done | ||
|
seonghobae marked this conversation as resolved.
|
||
| if [ "$contextual_gateway_ready" = "true" ]; then | ||
| export CONTEXTUAL_ORCHESTRATOR_ENABLED=true | ||
| printf 'contextual-orchestrator review gateway ready on loopback with auto-discovered provider pool.\n' | ||
| else | ||
| export CONTEXTUAL_ORCHESTRATOR_ENABLED=false | ||
| CONTEXTUAL_ORCHESTRATOR_TOKEN="" | ||
| export CONTEXTUAL_ORCHESTRATOR_TOKEN | ||
|
Comment on lines
+4680
to
+4682
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: Gateway provider stays enabled with empty apiKey when sidecar unavailable When the sidecar is unavailable the token is cleared to empty and the candidate is skipped by the runner, yet the generated config still lists Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| printf 'contextual-orchestrator review gateway unavailable; preserving the existing OpenCode provider pool and its valid loopback base URL.\n' | ||
| cleanup_contextual_gateway | ||
| contextual_gateway_pid="" | ||
| fi | ||
|
seonghobae marked this conversation as resolved.
|
||
| fi | ||
| fi | ||
|
seonghobae marked this conversation as resolved.
|
||
| set +e | ||
| timeout --kill-after=30s "${OPENCODE_POOL_STEP_TIMEOUT_SECONDS:-3600}s" \ | ||
| bash "$GITHUB_WORKSPACE/scripts/ci/run_opencode_review_model_pool.sh" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| # Contextual Orchestrator OpenCode gateway | ||
|
|
||
| The trusted OpenCode review job starts `contextual-orchestrator` from the | ||
| pinned protected-main commit `838b3de160c341a6f36bf588ae9fcc09989c040c`. The sidecar binds only | ||
| to `127.0.0.1:18080`, registers any available provider keys in its process-local | ||
| KV bootstrap, discovers models across Bytez, both NVIDIA NIM credentials, | ||
| OpenRouter, and OpenAI, then serves the existing OpenAI-compatible review | ||
| request through the `contextual-orchestrator` model candidate. | ||
|
|
||
| ```mermaid | ||
| flowchart LR | ||
| A["GitHub Actions trusted review job"] --> B["Pinned contextual-orchestrator sidecar"] | ||
| B --> C["KV bootstrap and model discovery"] | ||
| C --> D["Cost-ranked provider pool"] | ||
| D --> E["OpenCode contextual-orchestrator candidate"] | ||
| E --> F["Existing provider-qualified fallbacks"] | ||
| ``` | ||
|
|
||
| The gateway candidate is first in the model pool only for public repositories, | ||
| after the pinned checkout succeeds, the sidecar reaches the unauthenticated | ||
| `/healthz` liveness check, and an authenticated `/v1/models` response contains | ||
| at least one non-empty model id. `/healthz` is liveness only. A missing or | ||
| unreadable pinned revision, missing `LICENSE`, a missing canonical `MIT License` | ||
| heading, or a license whose exact allowlisted `MIT` blob | ||
| (`591bbf197b355e60604618c8a8a50bc5a839b204`) is not present is non-fatal: | ||
| the gateway candidate is skipped and the established provider-qualified pool | ||
| remains available. Private repositories never start or select the gateway, | ||
| because its auto-discovered catalog includes providers excluded by the review | ||
| workflow's private-source retention policy. Review publication, current-head | ||
| binding, independent approval, Strix, and branch protection are unchanged. | ||
| `COPILOT_GITHUB_TOKEN` is not used. | ||
|
|
||
| The gateway candidate uses a 900-second per-attempt runtime cap by default | ||
| (`OPENCODE_CONTEXTUAL_ORCHESTRATOR_RUN_TIMEOUT_SECONDS`). This preserves the | ||
| existing provider-qualified fallback window when the sidecar or its selected | ||
| provider stalls; the surrounding retry budget and outer workflow timeout still | ||
| bound the complete review step. | ||
|
|
||
| The sidecar receives the five provider credentials only in the model-execution | ||
| step, plus the existing `NVIDIA_API_KEY` alias required by the NIM adapter. Its | ||
| child process starts under an `env -i` allowlist containing only those provider | ||
| keys, loopback configuration, Python/runtime paths, and inert Actions command | ||
| file paths. It therefore cannot inherit `GITHUB_TOKEN`, `GH_TOKEN`, OIDC or | ||
| Actions runtime tokens, `STRIX_GITHUB_MODELS_TOKEN`, `OPENCODE_API_KEY`, or a | ||
| future unrelated secret. The generated local bearer token is masked and used | ||
| only for loopback inference. Persistent production credential storage remains | ||
| the gateway deployment's existing KV boundary; this runner bootstrap is | ||
| intentionally process-local and ephemeral. | ||
|
|
||
| ## References | ||
|
|
||
| GitHub. (n.d.). *Building and testing Python*. Retrieved August 20, 2026, | ||
| from https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python | ||
|
|
||
| GitHub. (n.d.). *Using secrets in GitHub Actions*. Retrieved August 20, 2026, | ||
| from https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions | ||
|
|
||
| OpenCode. (n.d.). *OpenCode documentation*. Retrieved August 20, 2026, from | ||
| https://opencode.ai/docs/ | ||
|
|
||
| ContextualWisdomLab. (n.d.). *contextual-orchestrator* [Source repository]. | ||
| Retrieved August 20, 2026, from | ||
| https://github.com/ContextualWisdomLab/contextual-orchestrator |
Uh oh!
There was an error while loading. Please reload this page.