Skip to content

feat(opencode): add vertex-ai auth support#275

Open
ptone wants to merge 9 commits into
mainfrom
scion/opencode-vertex-auth
Open

feat(opencode): add vertex-ai auth support#275
ptone wants to merge 9 commits into
mainfrom
scion/opencode-vertex-auth

Conversation

@ptone

@ptone ptone commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds vertex-ai as a third auth type for the opencode harness, matching the Claude harness pattern
  • Autodetects when GCP project + location env vars are present and gcp_metadata_mode is not "block"
  • When selected, resolves values from staged secret files and writes VERTEXAI_PROJECT and VERTEX_LOCATION to outputs/env.json
  • Precedence order: api-key > auth-file > vertex-ai (vertex-ai is the lowest-priority fallback)

Test plan

  • Python syntax check passes (py_compile)
  • Go harness tests pass (go test ./pkg/harness/...)
  • Manual test: provision with GOOGLE_CLOUD_PROJECT + GOOGLE_CLOUD_REGION env vars → selects vertex-ai
  • Manual test: provision with ANTHROPIC_API_KEY + vertex vars → selects api-key (higher priority)
  • Manual test: provision with gcp_metadata_mode: "block" → skips vertex-ai in autodetect
  • Manual test: explicit vertex-ai selection without project/location → error message

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new update command and API endpoint to re-import harness configurations from their source URLs, updates the image build executor to sync newly-built images back to storage and the database, and adds fallback support for viewing plain-text broker logs when Cloud Logging is unavailable. It also adds vertex-ai authentication support to the opencode harness, refines message channel tagging, and upgrades several dependencies. The review feedback highlights critical robustness issues in the opencode provisioner where candidates could be None or not a dictionary, causing AttributeErrors. Additionally, the reviewer suggests falling back to environment variables when resolving GCP secrets and returning resolution errors in cmd/build.go instead of silently ignoring them.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +124 to +129
def _env_secret_files(candidates: dict[str, Any]) -> dict[str, str]:
"""Map of env-var name -> container path of its 0600 secret value file."""
raw = candidates.get("env_secret_files") or {}
out: dict[str, str] = {}
if not isinstance(raw, dict):
return out

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If candidates is None or not a dictionary (which can happen if the manifest does not contain the "candidates" key or if it is null), calling candidates.get will raise an AttributeError and crash the provisioning process. Adding a type check ensures robustness.

def _env_secret_files(candidates: dict[str, Any] | None) -> dict[str, str]:
    """Map of env-var name -> container path of its 0600 secret value file."""
    if not isinstance(candidates, dict):
        return {}
    raw = candidates.get("env_secret_files") or {}
    out: dict[str, str] = {}
    if not isinstance(raw, dict):
        return out

Comment on lines +136 to +146
def _read_secret(env_secret_files: dict[str, str], name: str) -> str:
"""Read the 0600 secret value file for an env var. Returns "" on miss."""
path = env_secret_files.get(name)
if not path:
return ""
real = _expand(path)
try:
with open(real, "r", encoding="utf-8") as f:
return f.read().rstrip("\r\n")
except OSError:
return ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When running in environments where GCP credentials/project/location are passed directly as environment variables (such as local development, CI/CD, or standard Kubernetes pods) rather than staged secret files, env_secret_files will not contain entries for these variables. Falling back to os.environ.get(name, "") ensures the provisioner can still resolve these values correctly.

Suggested change
def _read_secret(env_secret_files: dict[str, str], name: str) -> str:
"""Read the 0600 secret value file for an env var. Returns "" on miss."""
path = env_secret_files.get(name)
if not path:
return ""
real = _expand(path)
try:
with open(real, "r", encoding="utf-8") as f:
return f.read().rstrip("\r\n")
except OSError:
return ""
def _read_secret(env_secret_files: dict[str, str], name: str) -> str:
"""Read the 0600 secret value file for an env var. Returns env var value on miss."""
path = env_secret_files.get(name)
if not path:
return os.environ.get(name, "")
real = _expand(path)
try:
with open(real, "r", encoding="utf-8") as f:
return f.read().rstrip("\r\n")
except OSError:
return os.environ.get(name, "")

Comment on lines +172 to +174
gcp_meta_mode = str(candidates.get("gcp_metadata_mode") or "").strip()
vertex_not_blocked = gcp_meta_mode != "block"
has_vertex = has_vertex_project and has_vertex_location and vertex_not_blocked

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similarly, candidates might be None or not a dictionary, which would cause candidates.get("gcp_metadata_mode") to raise an AttributeError. We should safely handle this case.

    gcp_meta_mode = ""
    if isinstance(candidates, dict):
        gcp_meta_mode = str(candidates.get("gcp_metadata_mode") or "").strip()
    vertex_not_blocked = gcp_meta_mode != "block"
    has_vertex = has_vertex_project and has_vertex_location and vertex_not_blocked

Comment thread cmd/build.go
Comment on lines +171 to +178
var gp string
if projectPath != "" {
if resolved, err := config.GetResolvedProjectDir(projectPath); err == nil {
gp = resolved
}
} else if resolved, err := config.GetResolvedProjectDir(""); err == nil {
gp = resolved
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If projectPath is explicitly provided but cannot be resolved, we should return the error rather than silently ignoring it and continuing with an empty gp. This keeps the behavior consistent with cmd/harness_config_update.go.

		var gp string
		if projectPath != "" {
			resolved, err := config.GetResolvedProjectDir(projectPath)
			if err != nil {
				return fmt.Errorf("failed to resolve project path %q: %w", projectPath, err)
			}
			gp = resolved
		} else if resolved, err := config.GetResolvedProjectDir(""); err == nil {
			gp = resolved
		}

ptone and others added 4 commits June 25, 2026 14:13
* codex harness: project template instructions

* codex harness: drop unused system prompt file

* codex harness: harden instruction projection

* codex harness: polish instruction projection output

---------

Co-authored-by: Scion Agent (codex-harness-template-dev) <agent@scion.dev>
* codex harness: enable notification hooks

* codex harness: escape otel toml values

* codex harness: rely on bundled dialect mapping

---------

Co-authored-by: Scion Agent (codex-harness-hooks-dev) <agent@scion.dev>
…m#489)

Co-authored-by: Scion Agent (codex-dialect-core-dev) <agent@scion.dev>
…pability (GoogleCloudPlatform#490)

- Extract session_id from .conversationId on PreInvocation/PostInvocation
- Extract tool_input from .toolCall.args on PreToolUse
- Remove false tool_name extraction from PostToolUse (field not in payload)
- Declare max_model_calls as supported (model-start/model-end are wired)
- Bump PROVISION_VERSION

Co-authored-by: Scion Agent (antigravity-hooks-dev) <agent@scion.dev>
@ptone ptone force-pushed the scion/opencode-vertex-auth branch from 8f710f1 to 153d6f5 Compare June 25, 2026 14:51
ptone and others added 5 commits June 25, 2026 16:36
…atform#491)

Co-authored-by: Scion Agent (codex-harness-template-dev) <agent@scion.dev>
Co-authored-by: Scion Agent (codex-harness-hooks-dev) <agent@scion.dev>
Add vertex-ai as a third auth type for the opencode harness, matching
the Claude harness pattern where vertex-ai is the lowest-priority
fallback after direct credentials (api-key > auth-file > vertex-ai).

Autodetects when GCP project + location env vars are present and
gcp_metadata_mode is not "block". When selected, writes VERTEXAI_PROJECT
and VERTEX_LOCATION to outputs/env.json.
The gcp_metadata_mode field is never written to auth-candidates.json
by the Go staging layer, making the guard inert. Add a comment noting
it is reserved for future use rather than removing it, since the
concept is actively used elsewhere in the system (e.g. claude_code
harness).
@ptone ptone force-pushed the scion/opencode-vertex-auth branch from 72c114a to 75c368d Compare June 25, 2026 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant