Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,16 +164,23 @@ The same rule makes the combinations explicit rather than order-dependent:
| `llm: hosted` + any provider key | refused: pick one |
| `llm: hosted` + `license_key` | refused: use `llm: license` |
| `llm: license` without `license_key` | refused: names the secret to add |
| `llm: hosted` or `license` + `model`, `agent_model` or `parsing_model` | refused: CodeBoarding picks the model on its own key; bring your own key to choose one |
| `llm: anthropic` + `anthropic_api_key` | Anthropic, directly |
| `llm: anthropic`, key empty or absent | refused: names the input and the secret |
| `llm: anthropic` + `openai_api_key` | refused: a second provider's key |
| `llm: anthropic` + key + `license_key` | Anthropic, on a CodeBoarding plan |

A licence alongside your own key is deliberately allowed: it says "my CodeBoarding plan,
my own tokens". **Your key always wins.** A direct provider call never reaches
CodeBoarding, so the licence is recorded and reported but not spent, and nothing meters
that combination today. The job summary says so on every run, rather than leaving you to
infer it from the tier name.
my own tokens". **Your key always wins.** The analysis calls your provider directly, so
the licence is recorded and reported but not spent, and nothing meters that combination
today. The job summary says so on every run, rather than leaving you to infer it from the
tier name.

The closed features, such as the review guide, are the exception to "directly": their code
runs on CodeBoarding's service, so your key travels with that request, is used for that run
and is neither stored nor logged. A provider the service cannot call, such as a custom base
URL, LiteLLM or a self-hosted Ollama, keeps its key on the runner: those features make no
model call, and the run never falls back to CodeBoarding's model.

### Providers

Expand Down Expand Up @@ -347,7 +354,8 @@ The first sync after the merge creates `codeboarding/baseline`, catching up from
| `github_token` | both | `${{ github.token }}` | Token for comments and sync delivery. |
| `codeboarding_analysis_location` | both | `codeboarding_branch` | Where the synced branch's analysis is stored: `codeboarding_branch` (on `codeboarding/baseline`, for the default branch; the code is never written) or `in_place` (committed as `.codeboarding/` on the synced branch). Sync saves it there and review reads it from there. |
| `warmstart_retention_days` | review | `1` | Days to keep the reusable analysis. Only the next run reads it. |
| `review_guide_url` | review | `https://jxivqwx4or6oip3krgdmdaiunm0iaybf.lambda-url.us-east-1.on.aws/` | Experimental. Production service base URL for the pull request's review guide. The run sends it this analysis and stores `review_guide.json` in the review artifact. Set to `''` to disable. Never fails the run. |
| `review_guide_url` | review | `https://jxivqwx4or6oip3krgdmdaiunm0iaybf.lambda-url.us-east-1.on.aws/` | Experimental. Production service base URL for the closed features, such as the pull request's review guide. The run sends it this analysis and stores one file per feature in the review artifact (`closed/<feature>.json`, plus `review_guide.json`). Set to `''` to disable. Never fails the run. |
| `closed_features_source` | review | `true` | Whether a closed feature that asks for it may read this repository's source, for checks that need the whole tree and to verify a finding. Only that feature's request carries it, capped in size, and the service keeps nothing. The model sees the code around a finding, never the tree. With `false` those checks and the model review do not run, and the result says so. |

`target_branch` and `sync_strategy` are deprecated, and read as the `codeboarding_analysis_location` they stand for, with a warning: `sync_strategy: push` as `in_place`, `sync_strategy: pull_request` as `codeboarding_branch`. `target_branch` is accepted only when it names the branch the run is on: the push trigger picks the branch. `force_full: true` is ignored with a warning. Anything the new input cannot express fails, linking to [moving an existing setup](#moving-an-existing-setup).

Expand Down
56 changes: 45 additions & 11 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,20 @@ inputs:
default: '2'
review_guide_url:
description: >-
Experimental. The CodeBoarding service that writes a pull request's review guide (main
changes, each change as numbered steps on the map). Defaults to the production service.
A review run sends it this run's analysis and stores the guide in the review artifact for
the platform to show. Set to an empty string to disable.
Experimental. The CodeBoarding service behind the closed features, such as a pull request's
review guide (main changes, each change as numbered steps on the map). Defaults to the
production service. A review run sends it this run's analysis and stores each feature's
answer in the review artifact for the platform to show. Set to an empty string to disable.
required: false
default: 'https://jxivqwx4or6oip3krgdmdaiunm0iaybf.lambda-url.us-east-1.on.aws/'
closed_features_source:
description: >-
Whether a closed feature that asks for it may read this repository's source, for checks that
need the whole tree and to verify a finding. Only that feature's request carries it, capped in
size, and the service keeps nothing. The model sees the code around a finding, never the tree.
With 'false' those checks and the model review do not run, and the result says so.
required: false
default: 'true'
github_token:
description: 'Token used for comments and sync delivery.'
required: false
Expand Down Expand Up @@ -301,6 +309,9 @@ runs:
ACTION_PATH: ${{ github.action_path }}
CB_IN_LLM: ${{ inputs.llm }}
CB_IN_LICENSE_KEY: ${{ inputs.license_key }}
CB_IN_MODEL: ${{ inputs.model }}
CB_IN_AGENT_MODEL: ${{ inputs.agent_model }}
CB_IN_PARSING_MODEL: ${{ inputs.parsing_model }}
CB_IN_ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
CB_IN_AWS_BEDROCK_API_KEY: ${{ inputs.aws_bedrock_api_key }}
CB_IN_AWS_BEDROCK_REGION: ${{ inputs.aws_bedrock_region }}
Expand Down Expand Up @@ -694,15 +705,16 @@ runs:
ANALYSED_FILES_CHANGED: ${{ steps.review_render.outputs.analysed_files_changed }}
run: "$GITHUB_ACTION_PATH/scripts/action/build-review-artifact.sh"

# After the artifact holds the analysis and before it is uploaded: the guide is one more file
# in it. Uses production by default; an empty URL disables it. It never fails the run.
- name: Build review guide
id: review_guide
# After the artifact holds the analysis and before it is uploaded: each feature is one more
# file in it. Uses production by default; an empty URL disables it. It never fails the run.
- name: Run closed features
id: closed_features
if: steps.guard.outputs.skip != 'true' && steps.guard.outputs.mode == 'review' && inputs.review_guide_url != ''
continue-on-error: true
shell: bash
env:
REVIEW_GUIDE_URL: ${{ inputs.review_guide_url }}
CLOSED_FUNCTIONS_URL: ${{ inputs.review_guide_url }}
CLOSED_FEATURES_SOURCE: ${{ inputs.closed_features_source }}
REPOSITORY: ${{ github.repository }}
PR_NUMBER: ${{ steps.guard.outputs.pr_number }}
ARTIFACT_DIR: ${{ steps.review_artifact.outputs.artifact_dir }}
Expand All @@ -711,10 +723,32 @@ runs:
CHECKOUT_DIR: ${{ github.workspace }}/.codeboarding-target
GITHUB_TOKEN: ${{ inputs.github_token }}
LICENSE_KEY: ${{ inputs.license_key }}
LLM: ${{ inputs.llm }}
MODEL: ${{ inputs.model }}
AGENT_MODEL: ${{ inputs.agent_model }}
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
AWS_BEDROCK_API_KEY: ${{ inputs.aws_bedrock_api_key }}
AWS_BEDROCK_REGION: ${{ inputs.aws_bedrock_region }}
CEREBRAS_API_KEY: ${{ inputs.cerebras_api_key }}
DEEPSEEK_API_KEY: ${{ inputs.deepseek_api_key }}
DEEPSEEK_BASE_URL: ${{ inputs.deepseek_base_url }}
GLM_API_KEY: ${{ inputs.glm_api_key }}
GLM_BASE_URL: ${{ inputs.glm_base_url }}
GOOGLE_API_KEY: ${{ inputs.google_api_key }}
KIMI_API_KEY: ${{ inputs.kimi_api_key }}
KIMI_BASE_URL: ${{ inputs.kimi_base_url }}
OLLAMA_API_KEY: ${{ inputs.ollama_api_key }}
OPENROUTER_API_KEY: ${{ inputs.openrouter_api_key }}
OLLAMA_BASE_URL: ${{ inputs.ollama_base_url }}
OPENAI_API_KEY: ${{ inputs.openai_api_key }}
run: python3 "$GITHUB_ACTION_PATH/scripts/review_guide.py"
OPENAI_BASE_URL: ${{ inputs.openai_base_url }}
OPENROUTER_API_KEY: ${{ inputs.openrouter_api_key }}
OPPER_API_KEY: ${{ inputs.opper_api_key }}
ORCAROUTER_API_KEY: ${{ inputs.orcarouter_api_key }}
REQUESTY_API_KEY: ${{ inputs.requesty_api_key }}
REQUESTY_BASE_URL: ${{ inputs.requesty_base_url }}
VERCEL_API_KEY: ${{ inputs.vercel_api_key }}
VERCEL_BASE_URL: ${{ inputs.vercel_base_url }}
run: python3 "$GITHUB_ACTION_PATH/scripts/closed_features.py"

- name: Upload review artifact
id: upload_review_artifact_dotcom
Expand Down
17 changes: 17 additions & 0 deletions scripts/action/credential_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
"hosted_with_provider_key",
"hosted_with_license",
"license_with_provider_key",
"hosted_with_model",
"license_with_model",
"foreign_provider_key",
}
)
Expand Down Expand Up @@ -174,6 +176,19 @@ def _reject_provider_inputs(table: dict, given: dict[str, str], llm: str, tier:
)


def _reject_model(llm: str, tier: str, environ: dict[str, str]) -> None:
"""On CodeBoarding's key, CodeBoarding picks the model and its settings. Choosing one takes a
key of your own, and a workflow that names one here would otherwise run on another without
saying so."""
for name in ("model", "agent_model", "parsing_model"):
if environ.get(f"CB_IN_{name.upper()}", "").strip():
raise ConfigError(
f"{tier}_with_model",
f"`llm: {llm}` runs on CodeBoarding's model, so `{name}` cannot be set. "
"Remove it, or set `llm` to a provider and bring your own key to choose a model.",
)


def _require_id_token(llm: str, environ: dict[str, str]) -> None:
# Both, because the relay needs both (oidc_relay.py refuses to start without either)
# and a runner can expose one without the other. Checking only the URL let that case
Expand Down Expand Up @@ -259,6 +274,7 @@ def resolve(table: dict, environ: dict[str, str]) -> dict:

if llm == "hosted":
_reject_provider_inputs(table, given, llm, "hosted")
_reject_model(llm, "hosted", environ)
if license_key:
raise ConfigError(
"hosted_with_license",
Expand All @@ -270,6 +286,7 @@ def resolve(table: dict, environ: dict[str, str]) -> dict:

if llm == "license":
_reject_provider_inputs(table, given, llm, "license")
_reject_model(llm, "license", environ)
if not license_key:
raise ConfigError(
"missing_license_key",
Expand Down
Loading
Loading