Skip to content
Draft
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
57 changes: 57 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,63 @@ jobs:
${{ runner.temp }}/ug-integration/artifacts/
${{ runner.temp }}/ug-integration/wheels/

# Non-blocking like the Copilot lane: continue-on-error and absent from the cujs `needs`.
# Runs the Copilot managed_fixture cases on the managed e2e workspace with injected configs that
# enable Copilot (a service principal authenticates it, like the `managed` lane).
managed-copilot:
name: Managed config · Copilot (non-blocking)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
continue-on-error: true
runs-on: ubuntu-22.04
timeout-minutes: 30
env:
UCODE_TEST_WORKSPACE: ${{ secrets.E2E_ADMIN_WORKSPACE }}
DATABRICKS_CLIENT_ID: ${{ secrets.E2E_ADMIN_SP_CLIENT_ID }}
DATABRICKS_CLIENT_SECRET: ${{ secrets.E2E_ADMIN_SP_CLIENT_SECRET }}
DEPENDENCY: ${{ inputs.dependency }}
TEST_MARKER: managed_fixture and copilot
ARTIFACT_NAME: integration-managed-copilot
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ inputs.ref || github.sha }}
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22.19.0
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: 0.9.8
enable-cache: ${{ inputs.ref == '' }}
- uses: databricks/setup-cli@bdb89f81c11a5bd647fd55b585b7c396ec68a25a # v1.0.0
with:
version: 1.17.0
- name: Run the Copilot managed cases against the managed workspace
shell: bash
run: |
args=(--copilot-version "$COPILOT_VERSION")
if [[ -n "$DEPENDENCY" ]]; then
args+=(--dependency "$DEPENDENCY")
fi
uv run --no-project --python 3.12 python scripts/run_integration.py \
--python 3.12 --ug-version "$UG_VERSION" --entry-point "$ENTRY_POINT" \
--default-index "$PACKAGE_INDEX" --output "$RUNNER_TEMP/ug-integration" \
"${args[@]}" -- -m "$TEST_MARKER"
- name: Upload managed Copilot evidence
if: ${{ !cancelled() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: ${{ env.ARTIFACT_NAME }}
include-hidden-files: true
path: |
${{ runner.temp }}/ug-integration/*.json
${{ runner.temp }}/ug-integration/*.txt
${{ runner.temp }}/ug-integration/*.xml
${{ runner.temp }}/ug-integration/*.log
${{ runner.temp }}/ug-integration/artifacts/
${{ runner.temp }}/ug-integration/wheels/

# managed and dedicated-cuj stay Linux-only: ug has no Windows managed-settings
# writer, and the CUJ suite drives agents through a POSIX PTY.
managed:
Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ to discovery or change ug's saved default.
`ug copilot` lists every Claude and GPT model you can use on the workspace in
Copilot's `/model` picker, and you can switch between them mid-session. Claude
models use the Anthropic Messages API, GPT-5 and newer use Responses, and older
GPT models use Chat Completions. Gemini and open-source models aren't offered.
GPT models use Chat Completions. Gemini and open-source models aren't offered, except under
`--model-location` (below).
`ug copilot --model claude-opus-5-5` (or `system.ai.claude-opus-5-5`, or Copilot's
own `dbx-anthropic/system.ai.claude-opus-5-5`) picks the model a session starts on;
an unknown or unsupported model fails before Copilot starts and lists the
Expand All @@ -95,15 +96,17 @@ Bedrock service, and the chat models of an OpenAI, Azure OpenAI or Microsoft Fou
allows every model, `/model` lists the models the gateway reports for it, which the provider
account may not serve; name the one to start on with `--model`. Services that relay a Claude subscription work only with Claude Code. Interactive
`ug configure` offers the matching services under **External Models**, and a managed workspace
config can set one for Copilot.
config can set one for Copilot. `ug copilot --model-location <catalog>.<schema>` uses the model
services of one schema instead: services named like Claude or GPT models use their usual API, and
the rest go through the gateway's Anthropic endpoint, as for Claude Code.

Without a managed workspace config, `ug claude` automatically discovers gateway
models for Claude Code's `/model` picker. Discovery defaults to `system.ai` when
no provider or model location is selected. Use `--provider` or `--model-location`
to select another model source; managed workspace configs control their own sources.
Interactive `ug configure` also offers a **Custom Model Location** (`<catalog>.<schema>`) for
Claude Code and Codex alongside Databricks Hosted and External Models. The choice is saved
with the rest of the configuration, so later `ug claude` / `ug codex` launches use it; an
Claude Code, Codex and Copilot alongside Databricks Hosted and External Models. The choice is saved
with the rest of the configuration, so later `ug claude` / `ug codex` / `ug copilot` launches use it; an
explicit `--provider` or `--model-location` still overrides it for that launch.

`ug codex` validates discovered models with the installed Codex binary and publishes
Expand Down Expand Up @@ -479,7 +482,7 @@ preferences are ignored and their files are left untouched. See the bundled
| Claude Code | `~/.claude/ucode-settings.json`, `~/.claude.json`, `/etc/claude-code/managed-settings.json` (Linux), `/Library/Application Support/ClaudeCode/managed-settings.json` (macOS), `C:\Program Files\ClaudeCode\managed-settings.json` (Windows, written only from an elevated terminal) |
| Gemini CLI | `~/.gemini/ucode.env`, `~/.ucode/.gemini-home/.gemini/settings.json` |
| OpenCode | `~/.ucode/opencode-xdg/opencode/opencode.json`, `~/.ucode/opencode-xdg/opencode/plugin/ucode-auth.js` |
| GitHub Copilot CLI | `~/.copilot/ucode.env`, `~/.copilot/ucode-mcp-config.json` |
| GitHub Copilot CLI | `~/.copilot/ucode.env`, `~/.copilot/mcp-config.json` (or `$COPILOT_HOME/mcp-config.json`) |
| Pi | `~/.ucode/pi-home/.pi/agent/models.json`, `~/.ucode/pi-home/.pi/agent/settings.json` |
| Cursor Agent | `~/.cursor/mcp.json` |
| Unity Gateway | `/etc/unitygateway/managed-settings.json` (Linux and macOS), `~/.ucode/refresh_ts.json`, `~/.ucode/managed-state.json`, `~/.ucode/managed-backups/` |
Expand Down
15 changes: 9 additions & 6 deletions src/ucode/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from pathlib import Path

from ucode.config_io import ToolSpec
from ucode.constants import AGENT_CLAUDE, AGENT_CODEX
from ucode.constants import AGENT_CLAUDE, AGENT_CODEX, AGENT_COPILOT
from ucode.databricks import (
AnthropicModelCatalog,
get_databricks_token,
Expand Down Expand Up @@ -80,6 +80,8 @@
DEFAULT_TOOL = "codex"
BUNDLE_VERSION = 1
_MANAGED_SETTINGS_TOOLS = {"claude", "codex"}
# Agents that can take their models from a Unity Catalog schema (`--model-location`).
MODEL_LOCATION_TOOLS = (AGENT_CLAUDE, AGENT_CODEX, AGENT_COPILOT)
_NATIVE_UPGRADE_COMMANDS = {
"claude": ["claude", "upgrade"],
"codex": ["codex", "update"],
Expand Down Expand Up @@ -483,8 +485,9 @@ def configure_tool(
picker_catalog=picker_catalog,
)
elif tool == "copilot":
# Under a model provider service, Copilot starts on one of the service's models, chosen at launch.
if not model and not provider:
# Under a model provider service or a model location, Copilot starts on one of its models,
# chosen at launch.
if not model and not provider and not parent_schema:
raise RuntimeError(f"A {tool} model must be selected before configuration.")
result = copilot.write_tool_config(state, model)
else:
Expand Down Expand Up @@ -605,10 +608,10 @@ def configure_single_tool(
def _persisted_model_location(tool: str, state: dict, provider: str | None) -> str | None:
"""The model location ``ug configure`` saved for ``tool``, unless a provider is in play.

Only claude and codex discover models by parent schema; an explicit or persisted provider
keeps the source it names.
Only ``MODEL_LOCATION_TOOLS`` discover models by parent schema; an explicit or persisted
provider keeps the source it names.
"""
if tool not in (AGENT_CLAUDE, AGENT_CODEX) or provider or get_provider_service(state, tool):
if tool not in MODEL_LOCATION_TOOLS or provider or get_provider_service(state, tool):
return None
return get_model_location(state, tool)

Expand Down
Loading
Loading