diff --git a/README.md b/README.md index b72caca46..c3883eab7 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 .` 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** (`.`) 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 diff --git a/src/ucode/agents/__init__.py b/src/ucode/agents/__init__.py index a53e89526..ebef7eb88 100644 --- a/src/ucode/agents/__init__.py +++ b/src/ucode/agents/__init__.py @@ -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, @@ -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"], @@ -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: @@ -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) diff --git a/src/ucode/agents/copilot.py b/src/ucode/agents/copilot.py index 3d091088c..5e7adfbe4 100644 --- a/src/ucode/agents/copilot.py +++ b/src/ucode/agents/copilot.py @@ -12,7 +12,9 @@ `ug copilot --provider` registers a Model Provider Service's models instead: its Claude models on the Anthropic endpoint, its other chat models on the gateway's native OpenAI route, with every -registry provider sending the service's routing header. +registry provider sending the service's routing header. `ug copilot --model-location` registers +the model services of one Unity Catalog schema: those named like Claude or GPT models on their +usual wire, the rest on the Anthropic endpoint like Claude Code, which the gateway translates. Copilot holds its provider credential for the whole session, so every provider points at a per-session loopback proxy (`gateway_proxy.start_gateway_auth_proxy`): Copilot authenticates to @@ -66,6 +68,7 @@ bare_model_name, build_copilot_base_url, build_tool_base_url, + list_anthropic_model_catalog, list_provider_service_models, model_version_sort_key, resolve_provider_service, @@ -75,6 +78,7 @@ acquire_exclusive_file_lock, release_file_lock, ) +from ucode.smart_routing.routing import unwrap_anthropic_gateway_model from ucode.state import is_tool_managed, mark_tool_managed, save_state from ucode.telemetry import agent_version, ucode_user_agent, ug_version from ucode.ui import print_warning @@ -117,6 +121,8 @@ CATALOG_STATE_KEY = "copilot_catalog" # The Model Provider Service this launch routes through; set by the launch command, never saved. LAUNCH_PROVIDER_SERVICE_KEY = "_copilot_launch_provider_service" +# The Unity Catalog schema whose model services this launch uses; never saved. +LAUNCH_MODEL_LOCATION_KEY = "_copilot_launch_model_location" # The wire APIs Copilot speaks; `responses` and `completions` are Copilot's `wireApi` values. WIRE_ANTHROPIC = "anthropic" @@ -300,14 +306,22 @@ def _catalog_sort_key(model: CopilotModel) -> tuple: def build_catalog( - models: Iterable[tuple[str, Iterable[str]]], *, provider_type: str | None = None + models: Iterable[tuple[str, Iterable[str]]], + *, + provider_type: str | None = None, + fallback_wire: str | None = None, ) -> tuple[list[CopilotModel], list[tuple[str, str]]]: """The ``(model, api_types)`` pairs Copilot can use, sorted Claude first (by family, newest - first) then GPT, and the skipped models with the reason (see :func:`classify_model`).""" + first) then GPT, and the skipped models with the reason (see :func:`classify_model`). + + With ``fallback_wire``, a model whose name doesn't decide the wire uses it instead of being + skipped; unsupported models are still skipped.""" usable: dict[str, CopilotModel] = {} skipped: dict[str, str] = {} for model, api_types in models: wire, reason = classify_model(model, api_types, provider_type=provider_type) + if wire is None and fallback_wire and bare_model_name(model) not in _UNSUPPORTED_MODELS: + wire = fallback_wire if wire is not None: usable.setdefault(model, CopilotModel(model, wire)) elif reason is not None: @@ -379,6 +393,23 @@ def provider_service_catalog( return catalog +def location_catalog(state: dict, schema: str) -> list[CopilotModel]: + """The model services of Unity Catalog schema ``schema`` that this launch registers. + + The listing names some services by gateway alias (``anthropic-aigw--``); Copilot + sends the full name, which the gateway routes directly.""" + workspace = state["workspace"] + listing = list_anthropic_model_catalog( + workspace, launch_token(state, workspace), parent_schema=schema + ) + models = (unwrap_anthropic_gateway_model(model) for model in listing.model_ids) + catalog, _ = build_catalog(((model, ()) for model in models), fallback_wire=WIRE_ANTHROPIC) + if not catalog: + reason = f": {listing.error_msg}" if listing.error_msg else "" + raise RuntimeError(f"Found no model services to use in {schema}{reason}.") + return catalog + + def catalog_from_listings( entries: Iterable[ModelServiceEntry] | None, fallback: Iterable[str] ) -> list[CopilotModel] | None: @@ -567,6 +598,7 @@ def render_providers_config( session_secret: str, *, provider_service: str | None = None, + model_location: str | None = None, ) -> dict: """Copilot's providers.json for ``catalog``: one provider per wire, all on the session proxy. @@ -613,8 +645,12 @@ def render_providers_config( if catalog_id is not None: entry["modelId"] = catalog_id limits = copilot_model_limits(model.id) if model.wire == WIRE_ANTHROPIC else None - if limits is not None and provider_service and sum(limits) > _CLAUDE_200K_WINDOW: - # A Model Provider Service may not serve the 1M window. + if ( + limits is not None + and (provider_service or model_location) + and sum(limits) > _CLAUDE_200K_WINDOW + ): + # A Model Provider Service, or a location's model service, may not serve the 1M window. limits = _CLAUDE_200K_LIMITS if limits is not None: entry["maxPromptTokens"], entry["maxOutputTokens"] = limits @@ -852,14 +888,16 @@ def _wait_for_exit(proc: _ChildProcess) -> int: def _session_catalog( - state: dict, tool_args: list[str], provider_service: str | None + state: dict, tool_args: list[str], provider_service: str | None, model_location: str | None ) -> list[CopilotModel]: - """The models this launch registers: the Model Provider Service's, else the Databricks - catalog.""" + """The models this launch registers: the Model Provider Service's, the model location's, + else the Databricks catalog.""" if provider_service: return provider_service_catalog( state, provider_service, explicit_model_arg_value(tool_args) ) + if model_location: + return location_catalog(state, model_location) catalog = copilot_catalog(state) if not catalog: raise RuntimeError( @@ -873,11 +911,15 @@ def _session_catalog( def launch(state: dict, tool_args: list[str], *, options: LaunchOptions) -> None: provider_service = state.pop(LAUNCH_PROVIDER_SERVICE_KEY, None) - catalog = _session_catalog(state, tool_args, provider_service) + model_location = state.pop(LAUNCH_MODEL_LOCATION_KEY, None) + catalog = _session_catalog(state, tool_args, provider_service, model_location) # Resolve before the proxy starts so a bad model fails without starting Copilot. tool_args, selected = rewrite_model_args(tool_args, catalog) start_model = selected or launch_model( - state, catalog, requested=options.launch_model, managed_list=not provider_service + state, + catalog, + requested=options.launch_model, + managed_list=not (provider_service or model_location), ) scrub_stored_credentials() @@ -901,7 +943,11 @@ def launch(state: dict, tool_args: list[str], *, options: LaunchOptions) -> None try: proxy_base_url = f"http://{LOOPBACK_HOST}:{server.server_address[1]}" config = render_providers_config( - catalog, proxy_base_url, session_secret, provider_service=provider_service + catalog, + proxy_base_url, + session_secret, + provider_service=provider_service, + model_location=model_location, ) wire_override = os.environ.get("COPILOT_PROVIDER_WIRE_MODEL") if wire_override: diff --git a/src/ucode/cli.py b/src/ucode/cli.py index 0ef5a83f8..9b7e10a98 100644 --- a/src/ucode/cli.py +++ b/src/ucode/cli.py @@ -22,6 +22,7 @@ from ucode import custom_oauth from ucode.agents import ( + MODEL_LOCATION_TOOLS, TOOL_SPECS, LaunchOptions, check_gateway_endpoint, @@ -53,6 +54,7 @@ from ucode.constants import ( AGENT_CLAUDE, AGENT_CODEX, + AGENT_COPILOT, ISAAC_SMART_ROUTER_FEEDBACK_ENV_VAR, SMART_ROUTING_ENV_KEYS, UG_HEADERS_ENV_VAR, @@ -882,7 +884,7 @@ def _setup_single_agent( ) -> dict: """Log in, discover the agent's models, and write its config, then print the summary. - With ``apply_managed`` the workspace's managed config governs claude/codex (the + With ``apply_managed`` the workspace's managed config governs claude/codex/copilot (the ``ug configure --agent`` path: admin-enabled applies the admin config, a disabled agent is rejected). ``ug agents add`` passes ``apply_managed=False`` to set up a self-managed agent standalone, independent of the managed config. @@ -902,16 +904,17 @@ def _setup_single_agent( if apply_managed and tool in ("claude", "codex"): # Explicit configure always reads the workspace policy fresh, so it can install it. managed, _ = _refresh_configured_policy(state) + elif apply_managed: + # Other agents only read the policy; they never install it. + managed, _ = _read_policy_for_other_agent(state) + if apply_managed: refuse_user_scope_over_managed_config(state.get("workspace"), managed) + if apply_managed and tool in MODEL_LOCATION_TOOLS: _reject_disabled_agent(managed, tool) if managed is not None: state = resolve_state(managed, state, tool) if not managed_provider_service(managed, tool): parent_schema = managed_unity_catalog_location(managed, tool) - elif apply_managed: - # Other agents only read the policy; they never install it. - managed, _ = _read_policy_for_other_agent(state) - refuse_user_scope_over_managed_config(state.get("workspace"), managed) state = configure_single_tool(tool, state, parent_schema=parent_schema) if apply_managed and not managed_enabled_tools(managed or {}): state = _record_unmanaged_configured_agents(state, [tool]) @@ -924,9 +927,9 @@ def _setup_single_agent( skills_configured = _configure_managed_skills(managed) if resolved_mcps is not None and skills_configured: _sync_claude_cowork_mcp_servers(state, managed_mcp_servers=resolved_mcps) - install_databricks_ai_tools_for_agents( - [tool], state, force_refresh=tool not in ("claude", "codex") - ) + # Only the Claude and Codex branch above reads the managed config fresh. + managed_refreshed = apply_managed and tool in ("claude", "codex") + install_databricks_ai_tools_for_agents([tool], state, force_refresh=not managed_refreshed) spec = TOOL_SPECS[tool] provider_summary = "Databricks" if parent_schema else _provider_summary(tool, state) console.print( @@ -969,9 +972,9 @@ def _maybe_select_provider_service(tool: str, state: dict) -> dict: (``system.ai``), a Model Provider Service, or a custom Unity Catalog model location — and persist (or clear) the choice. - No-op for tools other than claude/codex/gemini/copilot (gemini and copilot have no custom - location). Falls back to Databricks when no matching provider services are found or the - listing fails and no custom location is offered. + No-op for tools other than claude/codex/gemini/copilot (gemini has no custom location). Falls + back to Databricks when no matching provider services are found or the listing fails and no + custom location is offered. """ if tool not in ("claude", "codex", "gemini", "copilot"): return state @@ -989,7 +992,7 @@ def _use_databricks() -> dict: token = get_databricks_token(state["workspace"], state.get("profile")) with spinner("Checking for model provider services..."): names, reason = list_tool_provider_services(tool, state["workspace"], token) - offers_location = tool in ("claude", "codex") + offers_location = tool in MODEL_LOCATION_TOOLS if reason is not None: # Most workspaces don't have the feature enabled — that's the common case, # so skip the provider choice silently. Only surface unexpected failures. @@ -1133,7 +1136,7 @@ def _configure_workspace_command( resolved = resolve_state(managed, state, tool_name) parent_schema = ( managed_unity_catalog_location(managed, tool_name) - if tool_name in ("claude", "codex") + if tool_name in MODEL_LOCATION_TOOLS and not managed_provider_service(managed, tool_name) else None ) @@ -3368,7 +3371,7 @@ def _launch_tool( parent_schema is None and explicit_provider is None and not persisted_provider - and tool in {"claude", "codex"} + and tool in MODEL_LOCATION_TOOLS ): parent_schema = get_model_location(state, tool) # An explicit --model-location displaces a persisted provider, as --provider displaces a @@ -3410,7 +3413,7 @@ def _launch_tool( managed_provider = managed_provider_service(managed or {}, tool) managed_parent_schema = ( managed_unity_catalog_location(managed or {}, tool) - if tool in {"claude", "codex"} and not managed_provider + if tool in MODEL_LOCATION_TOOLS and not managed_provider else None ) if managed_provider: @@ -3575,16 +3578,26 @@ def _launch_tool( route_root_model = None managed_model = None relayed_forward_model = None # forwarded to Claude Code's --model for a relayed provider - if provider or managed_parent_schema or picker_catalog: + # picker_catalog stands in for Claude's provider or location. Copilot picks its start model + # from its location catalog, and resolving one below would demand a Databricks model. + if ( + provider + or managed_parent_schema + or picker_catalog + or (tool == AGENT_COPILOT and parent_schema) + ): # Routing through a Model Provider Service pins no Databricks model; # scoped UC discovery likewise lets the agent select from the parent schema. Skip model # resolution, which would otherwise fail when global discovery found no models. resolved_model = None managed_source_model = ( managed_launch_model(managed or {}, recommendation, tool) - if tool == "claude" and (managed_provider or managed_parent_schema) + if tool in (AGENT_CLAUDE, AGENT_COPILOT) + and (managed_provider or managed_parent_schema) else None ) + if tool == AGENT_COPILOT: + managed_model = managed_source_model if tool == "claude" and managed_parent_schema: # Native discovery supplies the catalog, but the managed policy still controls # which model Claude starts on. @@ -3723,16 +3736,19 @@ def _launch_tool( _print_budget_panel(recommendation, tool, managed) # The managed config's MCP servers and skills are both applied at `ug configure`, not here, # so the launch hot path makes no per-launch discovery calls for them. - if tool == "claude": + if tool == AGENT_CLAUDE: if provider: state["_claude_launch_provider"] = provider - elif tool == "codex": + elif tool == AGENT_CODEX: if provider: state["_codex_launch_provider"] = provider elif parent_schema: state["_codex_launch_parent_schema"] = parent_schema - elif tool == "copilot" and provider: - state[copilot_agent.LAUNCH_PROVIDER_SERVICE_KEY] = provider + elif tool == AGENT_COPILOT: + if provider: + state[copilot_agent.LAUNCH_PROVIDER_SERVICE_KEY] = provider + elif parent_schema: + state[copilot_agent.LAUNCH_MODEL_LOCATION_KEY] = parent_schema launch_options = _launch_options( tool, ctx.args, @@ -4205,6 +4221,13 @@ def copilot_cmd( "(..); `/model` then lists that service's models.", ), ] = None, + model_location: Annotated[ + str | None, + typer.Option( + "--model-location", + help="Discover model services in `.`. Example: main.default", + ), + ] = None, skip_preflight: SkipPreflightOption = False, workspace: WorkspaceOption = None, ) -> None: @@ -4215,6 +4238,7 @@ def copilot_cmd( provider=provider, skip_preflight=skip_preflight, workspace_url=workspace, + parent_schema=model_location, ) diff --git a/src/ucode/constants.py b/src/ucode/constants.py index 6764ba84b..8ded72215 100644 --- a/src/ucode/constants.py +++ b/src/ucode/constants.py @@ -7,6 +7,7 @@ AGENT_CLAUDE = "claude" AGENT_CODEX = "codex" +AGENT_COPILOT = "copilot" ENABLE_SMART_ROUTING_ENV_VAR = "ENABLE_SMART_ROUTING_V2" ENABLE_SUBAGENT_ROUTING_ENV_VAR = "ENABLE_SMART_ROUTING_SUBAGENT_ONLY" diff --git a/tests/integration/README.md b/tests/integration/README.md index 4a074bfbe..e4a6ebec6 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -281,6 +281,8 @@ and then a GPT task chosen with Copilot's `dbx-responses/` form, an unkno fails before Copilot starts, and the TUI switches from Claude to GPT with `/model` mid-session. Model evidence comes from Copilot's own `~/.copilot/session-state/*/events.jsonl` (`utils/agents/copilot.py`). +`test_ug_copilot_model_location.py` launches Copilot from fresh state with `--model-location` on the +model-discovery schema and completes a file task on its Claude-compatible model service. `test_ug_copilot_providers.py` runs Copilot through the MPS fixtures below: a file task on the Anthropic service's model, on the OpenAI, Azure OpenAI and Microsoft Foundry services' `gpt-5-nano`, and on a named model of the Bedrock allow-all service; launching on the relayed service fails before Copilot starts. @@ -532,6 +534,7 @@ test_ug_copilot_headless.py # Copilot via the session proxy: Claude test_ug_copilot_lifecycle.py # Copilot refresh after revocation, sign-in expired, configure/revert, upgrade test_ug_copilot_models.py # Copilot model registry: Claude then GPT, unknown model, TUI /model switch test_ug_copilot_providers.py # Copilot via Model Provider Services: Anthropic, OpenAI, Azure OpenAI, Foundry, Bedrock allow-all; relayed refused +test_ug_copilot_model_location.py # Copilot on a Unity Catalog model location: fresh --model-location file task test_ug_codex_tracing.py # Codex OTLP export reaches the configured trace table test_ug_claude_commands.py # command help forwarding test_ug_codex_commands.py # command help and parser error forwarding @@ -738,7 +741,7 @@ cases instead complete real file tasks through `--workspace` and `--model-locati journeys check setup and launch with existing services, without inference. There are **88 live cases** (including 12 marked TUI journeys) and **7 installation -checks** with Claude and Codex; selecting OpenCode adds one live headless case and two `managed_fixture` cases; selecting Copilot adds sixteen live cases. One +checks** with Claude and Codex; selecting OpenCode adds one live headless case and two `managed_fixture` cases; selecting Copilot adds seventeen live cases. One **`workspace_switch` case** uses two real workspaces and checks skills MCP cleanup and a completed Claude task. A further **44 `managed_fixture` cases** (twelve of them also `live`) run on the managed workspace with a checked-in JSON CodingAgentConfig from `tests/fixtures/managed_config/` @@ -964,7 +967,7 @@ A second non-blocking job, **Managed OpenCode self-managed** (`managed_fixture a in `test_ug_agents_self_managed.py`), installs Claude, Codex and OpenCode and runs against the managed e2e workspace (`E2E_ADMIN_WORKSPACE`) with the injected `managed_workspace_default` config, which enables Claude/Codex but not OpenCode. It is likewise outside `cujs`. -A non-blocking **Copilot** job (`live and copilot`, fifteen cases) runs on Linux and Windows the +A non-blocking **Copilot** job (`live and copilot`, sixteen cases) runs on Linux and Windows the same way and is likewise outside `cujs` until it is stable. Each lane installs only the agent CLIs it needs (one, except the managed OpenCode lane's diff --git a/tests/integration/test_ug_copilot_model_location.py b/tests/integration/test_ug_copilot_model_location.py new file mode 100644 index 000000000..005985488 --- /dev/null +++ b/tests/integration/test_ug_copilot_model_location.py @@ -0,0 +1,46 @@ +"""CUJs for running GitHub Copilot CLI on a Unity Catalog model location via ug.""" + +import pytest +from utils.constants import COPILOT_HEADLESS_FLAGS +from utils.evidence import ( + FileTask, + assert_completed_task_model, + assert_no_session_registry_left, + copilot_selected_models, +) + +pytestmark = [pytest.mark.live, pytest.mark.copilot] + + +def test_ug_copilot_runs_a_task_on_a_model_location( + live_session, workspace, parent_schema, claude_parent_model +): + """Scenario: from fresh state, run a headless task with `--model-location` set to the + model-discovery schema and `--model` naming its Claude-compatible model service. + + Expected: ug sets up the workspace, Copilot starts on that service through `dbx-anthropic`, + the completed answer contains the fixture value and was sent as the service's full name, and + no session registry is left. + """ + session = live_session + task = FileTask(session) + session.record("copilot-model-location.json", {"location": parent_schema}) + + result = session.run( + "copilot", + "--workspace", + workspace, + "--model-location", + parent_schema, + "--model", + claude_parent_model, + "-p", + task.prompt, + *COPILOT_HEADLESS_FLAGS, + timeout=180, + ) + + task.assert_headless_answer("copilot", result) + assert_completed_task_model(session, "copilot", task.value, claude_parent_model) + assert f"dbx-anthropic/{claude_parent_model}" in copilot_selected_models(session) + assert_no_session_registry_left(session) diff --git a/tests/test_agent_copilot.py b/tests/test_agent_copilot.py index ec9eb7cc2..b1ac9a0f0 100644 --- a/tests/test_agent_copilot.py +++ b/tests/test_agent_copilot.py @@ -12,7 +12,7 @@ from ucode import custom_oauth from ucode.agents import copilot -from ucode.databricks import ModelServiceEntry +from ucode.databricks import AnthropicModelCatalog, ModelServiceEntry from ucode.telemetry import client_platform WS = "https://example.databricks.com" @@ -555,6 +555,48 @@ def test_invalid_service_raises_its_error(self, monkeypatch): copilot.provider_service_catalog({"workspace": WS}, MPS, None) +def _stub_location(monkeypatch, model_ids, error=None): + monkeypatch.setattr(copilot, "launch_token", lambda state, workspace, **_: "dbx-token") + seen = {} + + def anthropic_catalog(workspace, token, *, parent_schema): + seen["parent_schema"] = parent_schema + return AnthropicModelCatalog( + model_ids=list(model_ids), model_id_to_display_name={}, error_msg=error + ) + + monkeypatch.setattr(copilot, "list_anthropic_model_catalog", anthropic_catalog) + return seen + + +class TestLocationCatalog: + def test_registers_the_schemas_services_by_full_name(self, monkeypatch): + seen = _stub_location( + monkeypatch, + [ + "anthropic-aigw-1a2b3c4d-main.ucode.claude_haiku", + "main.ucode.claude-sonnet-4-6", + "anthropic-aigw-0f0f0f0f-main.ucode.gpt-5-6-luna", + "main.ucode.claude-opus-4-1", + ], + ) + + models = copilot.location_catalog({"workspace": WS}, "main.ucode") + + assert [(m.id, m.wire) for m in models] == [ + ("main.ucode.claude-sonnet-4-6", "anthropic"), + ("main.ucode.claude_haiku", "anthropic"), + ("main.ucode.gpt-5-6-luna", "responses"), + ] + assert seen["parent_schema"] == "main.ucode" + + def test_an_empty_schema_says_so(self, monkeypatch): + _stub_location(monkeypatch, [], error="AI Gateway returned no Anthropic model ids") + + with pytest.raises(RuntimeError, match="no model services to use in main.ucode"): + copilot.location_catalog({"workspace": WS}, "main.ucode") + + class TestCopilotModelLimits: @pytest.mark.parametrize( ("model", "limits"), @@ -1203,6 +1245,26 @@ def test_provider_service_launch_registers_its_models(self, tmp_path, monkeypatc assert seen["env"]["COPILOT_MODEL"] == "dbx-anthropic/claude-sonnet-5" assert copilot.LAUNCH_PROVIDER_SERVICE_KEY not in state + def test_model_location_launch_registers_the_schemas_services(self, tmp_path, monkeypatch): + isolate_copilot_config_paths(monkeypatch, tmp_path) + seen = _stub_launch(monkeypatch) + _stub_location(monkeypatch, ["main.ucode.claude-sonnet-4-6", "main.ucode.claude_haiku"]) + state = dict(STATE, copilot_static_models=[GPT]) + state[copilot.LAUNCH_MODEL_LOCATION_KEY] = "main.ucode" + + with pytest.raises(SystemExit): + copilot.launch(state, [], options=copilot.LaunchOptions()) + + registry = seen["registry"] + assert [m["id"] for m in registry["models"]] == [ + "main.ucode.claude-sonnet-4-6", + "main.ucode.claude_haiku", + ] + assert "headers" not in registry["providers"][0] + assert registry["models"][0]["maxPromptTokens"] == 168_000 + assert seen["env"]["COPILOT_MODEL"] == "dbx-anthropic/main.ucode.claude-sonnet-4-6" + assert copilot.LAUNCH_MODEL_LOCATION_KEY not in state + def test_provider_service_launch_rejects_a_model_it_does_not_serve(self, tmp_path, monkeypatch): isolate_copilot_config_paths(monkeypatch, tmp_path) seen = _stub_launch(monkeypatch) diff --git a/tests/test_cli.py b/tests/test_cli.py index bd237ac5d..3d275460e 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1186,39 +1186,57 @@ def test_codex_model_location_is_forwarded(self): assert mock_launch.call_args.kwargs["parent_schema"] == "main.default" assert mock_launch.call_args.args[1].args == [] - def test_codex_provider_and_model_location_are_mutually_exclusive(self): - with _launch_policy_patches(None): + @pytest.mark.parametrize("tool", ["claude", "codex", "copilot"]) + def test_provider_and_model_location_are_mutually_exclusive(self, tool): + with _launch_policy_patches(None) as calls: + calls["state"]["available_tools"] = [tool] result = runner.invoke( app, - [ - "codex", - "--provider", - "main.default.provider", - "--model-location", - "main.default", - ], + [tool, "--provider", "main.default.provider", "--model-location", "main.default"], ) assert result.exit_code == 1 assert "--provider and --model-location cannot be used together" in result.output - def test_claude_provider_and_model_location_are_mutually_exclusive(self): - with _launch_policy_patches(None): - result = runner.invoke( - app, - [ - "claude", - "--provider", - "main.default.provider", - "--model-location", - "main.default", - ], - ) + def test_copilot_launch_uses_saved_model_location(self): + with _launch_policy_patches(None) as calls: + calls["state"]["model_locations"] = {"copilot": "main.saved"} + result = runner.invoke(app, ["copilot"]) - assert result.exit_code == 1 - assert "--provider and --model-location cannot be used together" in result.output + assert result.exit_code == 0, result.output + launch_state = calls["launch"].call_args.args[1] + assert launch_state[copilot_agent.LAUNCH_MODEL_LOCATION_KEY] == "main.saved" - @pytest.mark.parametrize("tool", ["claude", "codex"]) + def test_copilot_explicit_provider_overrides_saved_location(self): + with _launch_policy_patches(None) as calls: + calls["state"]["available_tools"] = ["copilot"] + calls["state"]["model_locations"] = {"copilot": "main.saved"} + result = runner.invoke(app, ["copilot", "--provider", "main.default.openai"]) + + assert result.exit_code == 0, result.output + launch_state = calls["launch"].call_args.args[1] + assert launch_state[copilot_agent.LAUNCH_PROVIDER_SERVICE_KEY] == "main.default.openai" + assert copilot_agent.LAUNCH_MODEL_LOCATION_KEY not in launch_state + + def test_copilot_explicit_model_location_overrides_saved_provider(self): + with _launch_policy_patches(None, persisted_provider="main.default.openai") as calls: + result = runner.invoke(app, ["copilot", "--model-location", "main.flag"]) + + assert result.exit_code == 0, result.output + launch_state = calls["launch"].call_args.args[1] + assert launch_state[copilot_agent.LAUNCH_MODEL_LOCATION_KEY] == "main.flag" + assert copilot_agent.LAUNCH_PROVIDER_SERVICE_KEY not in launch_state + + def test_copilot_saved_model_location_is_ignored_under_managed_config(self): + managed = {"enabled_agents": {"copilot": {"model_config": {}}}} + with _launch_policy_patches(managed) as calls: + calls["state"]["model_locations"] = {"copilot": "main.saved"} + result = runner.invoke(app, ["copilot"]) + + assert result.exit_code == 0, result.output + assert copilot_agent.LAUNCH_MODEL_LOCATION_KEY not in calls["launch"].call_args.args[1] + + @pytest.mark.parametrize("tool", ["claude", "codex", "copilot"]) def test_invalid_model_location_is_rejected(self, tool): with _launch_policy_patches(None): result = runner.invoke(app, [tool, "--model-location", "main"]) @@ -2188,6 +2206,24 @@ def test_model_location_sets_transient_codex_launch_marker(self): assert result.exit_code == 0, result.output assert mock_launch.call_args.args[1]["_codex_launch_parent_schema"] == "main.default" + def test_model_location_sets_transient_copilot_launch_marker(self): + state = dict(MINIMAL_STATE, available_tools=["copilot"]) + with ( + patch("ucode.cli.ensure_bootstrap_dependencies"), + patch("ucode.cli.load_state", return_value=state), + patch("ucode.cli.ensure_provider_state", return_value=state), + patch("ucode.cli.configure_shared_state", return_value=state), + patch("ucode.cli.configure_tool", return_value=state) as mock_configure, + patch("ucode.cli._fetch_managed_config", return_value=(None, False)), + patch("ucode.cli.launch_agent") as mock_launch, + ): + result = runner.invoke(app, ["copilot", "--model-location", "main.default"]) + + assert result.exit_code == 0, result.output + assert mock_configure.call_args.args[2] is None + launch_state = mock_launch.call_args.args[1] + assert launch_state[copilot_agent.LAUNCH_MODEL_LOCATION_KEY] == "main.default" + class TestGeminiProviderLaunch: @staticmethod @@ -3764,6 +3800,51 @@ def test_managed_default_is_the_launch_model(self): assert result.exit_code == 0, result.output assert calls["launch"].call_args.kwargs["options"].launch_model == "system.ai.gpt-5-4" + @pytest.mark.parametrize( + "model_source", + [ + {"unity_catalog_location": "main.models"}, + {"model_provider_service": "main.default.mps"}, + ], + ) + def test_managed_default_is_the_launch_model_under_a_managed_source(self, model_source): + managed = { + "enabled_agents": { + "copilot": {"model_config": {"default_model": "gpt-5-4", **model_source}} + } + } + with _launch_policy_patches(managed) as calls: + result = runner.invoke(app, ["copilot"]) + + assert result.exit_code == 0, result.output + assert calls["launch"].call_args.kwargs["options"].launch_model == "gpt-5-4" + + @pytest.mark.parametrize( + ("model_config", "expected_provider", "expected_location"), + [ + ( + {"model_provider_service": "main.default.managed-mps"}, + "main.default.managed-mps", + None, + ), + ({"unity_catalog_location": "main.managed"}, None, "main.managed"), + ], + ids=["mps", "uc-parent"], + ) + def test_managed_source_overrides_saved_provider( + self, model_config, expected_provider, expected_location + ): + managed = {"enabled_agents": {"copilot": {"model_config": model_config}}} + with _launch_policy_patches(managed, persisted_provider="main.default.developer") as calls: + calls["state"]["model_locations"] = {"copilot": "main.saved"} + result = runner.invoke(app, ["copilot"]) + + assert result.exit_code == 0, result.output + assert calls["shared"].call_args.kwargs["skip_model_discovery"] is True + launch_state = calls["launch"].call_args.args[1] + assert launch_state.get(copilot_agent.LAUNCH_PROVIDER_SERVICE_KEY) == expected_provider + assert launch_state.get(copilot_agent.LAUNCH_MODEL_LOCATION_KEY) == expected_location + def test_a_managed_default_still_discovers_the_catalog(self): assert not cli_mod._skip_model_discovery( self.MANAGED, "copilot", provider=None, managed_parent_schema=None, family_defaults=None @@ -4728,6 +4809,17 @@ def test_picker_offers_location_when_provider_feature_unavailable(self, monkeypa assert asked == [["databricks", "location"]] assert cli_mod.get_model_location(result, "codex") == "main.models" + def test_picker_offers_location_for_copilot(self, monkeypatch): + state, _saved, asked = self._picker( + monkeypatch, "copilot", services=(["a.b.mps"], None), answers=["location"] + ) + + result = cli_mod._maybe_select_provider_service("copilot", state) + + assert asked == [["databricks", "mps", "location"]] + assert cli_mod.get_model_location(result, "copilot") == "main.models" + assert cli_mod.get_provider_service(result, "copilot") is None + def test_picker_reprompts_until_location_is_valid(self, monkeypatch): state, _saved, _asked = self._picker( monkeypatch, @@ -4988,6 +5080,86 @@ def test_single_codex_agent_passes_managed_parent_directly(self, monkeypatch): configure.assert_called_once_with("codex", state, parent_schema="main.models") install_ai_tools.assert_called_once_with(["codex"], state, force_refresh=False) + def test_managed_copilot_parent_is_passed_to_generic_configure(self, monkeypatch): + state = { + **MINIMAL_STATE, + "available_tools": [], + "copilot_models": [], + "provider_services": {"copilot": "main.default.developer"}, + } + managed = { + "enabled_agents": { + "copilot": {"model_config": {"unity_catalog_location": "main.models"}} + } + } + monkeypatch.setattr(cli_mod, "configure_shared_state", lambda *a, **k: state) + monkeypatch.setattr(cli_mod, "refresh_managed_config", lambda s, **_k: (managed, False)) + monkeypatch.setattr( + cli_mod, + "check_gateway_endpoint", + lambda *_a: pytest.fail("managed model sources do not require global models"), + ) + monkeypatch.setattr(cli_mod, "install_tool_binary", lambda *a, **k: True) + monkeypatch.setattr(cli_mod, "_print_managed_summary", lambda *a, **k: None) + monkeypatch.setattr(cli_mod, "_configure_managed_mcp_servers", lambda *_a: None) + configured: list[dict] = [] + monkeypatch.setattr( + cli_mod, + "configure_selected_tools", + lambda resolved, tools, **kwargs: configured.append(kwargs) or resolved, + ) + + assert cli_mod.configure_workspace_command(workspaces=[("https://w.com", None)]) == 0 + + assert configured[0]["parent_schemas"] == {"copilot": "main.models"} + + def test_single_copilot_agent_passes_managed_parent_directly(self, monkeypatch): + state = { + **MINIMAL_STATE, + "provider_services": {"copilot": "main.default.developer"}, + } + managed = { + "enabled_agents": { + "copilot": {"model_config": {"unity_catalog_location": "main.models"}} + } + } + monkeypatch.setattr(cli_mod, "install_databricks_cli", MagicMock()) + monkeypatch.setattr(cli_mod, "install_tool_binary", MagicMock()) + monkeypatch.setattr(cli_mod, "_configure_shared_workspace_states", lambda *a, **k: [state]) + refresh = MagicMock(return_value=(managed, False)) + monkeypatch.setattr(cli_mod, "refresh_managed_config", refresh) + configure = MagicMock(return_value=state) + monkeypatch.setattr(cli_mod, "configure_single_tool", configure) + install_ai_tools = MagicMock() + monkeypatch.setattr(cli_mod, "install_databricks_ai_tools_for_agents", install_ai_tools) + + result = runner.invoke( + app, ["configure", "--agent", "copilot", "--workspace", "https://w.com"] + ) + + assert result.exit_code == 0, result.output + assert "(Provider: Databricks)" in _strip_ansi(result.output) + refresh.assert_called_once_with(state, force_refresh=False) + configure.assert_called_once_with("copilot", state, parent_schema="main.models") + install_ai_tools.assert_called_once_with(["copilot"], state, force_refresh=True) + + @pytest.mark.parametrize("apply_managed", [True, False]) + def test_single_copilot_agent_refreshes_ai_tools_since_it_only_reads_the_policy( + self, monkeypatch, apply_managed + ): + state = dict(MINIMAL_STATE) + monkeypatch.setattr(cli_mod, "_configure_shared_workspace_states", lambda *a, **k: [state]) + monkeypatch.setattr(cli_mod, "refresh_managed_config", lambda *a, **k: (None, False)) + monkeypatch.setattr(cli_mod, "configure_single_tool", lambda *a, **k: state) + install_ai_tools = MagicMock() + monkeypatch.setattr(cli_mod, "install_databricks_ai_tools_for_agents", install_ai_tools) + + cli_mod._setup_single_agent( + [("https://w.com", None)], "copilot", apply_managed=apply_managed + ) + + install_ai_tools.assert_called_once_with(["copilot"], state, force_refresh=True) + def test_managed_config_fails_when_no_enabled_agent_is_available(self, monkeypatch): import ucode.cli as cli_mod diff --git a/uv.lock b/uv.lock index de683fde2..010d29ad8 100644 --- a/uv.lock +++ b/uv.lock @@ -10,7 +10,7 @@ resolution-markers = [ [[package]] name = "annotated-doc" version = "0.0.5" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" }, @@ -19,7 +19,7 @@ wheels = [ [[package]] name = "annotated-types" version = "0.8.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, @@ -28,7 +28,7 @@ wheels = [ [[package]] name = "anyio" version = "4.14.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, @@ -41,7 +41,7 @@ wheels = [ [[package]] name = "attrs" version = "26.1.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, @@ -50,7 +50,7 @@ wheels = [ [[package]] name = "certifi" version = "2026.7.22" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, @@ -59,7 +59,7 @@ wheels = [ [[package]] name = "cffi" version = "2.1.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] @@ -144,7 +144,7 @@ wheels = [ [[package]] name = "charset-normalizer" version = "3.5.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" }, @@ -275,7 +275,7 @@ wheels = [ [[package]] name = "click" version = "8.5.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, @@ -284,7 +284,7 @@ wheels = [ [[package]] name = "colorama" version = "0.4.6" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, @@ -293,7 +293,7 @@ wheels = [ [[package]] name = "cryptography" version = "49.0.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] @@ -343,7 +343,7 @@ wheels = [ [[package]] name = "databricks-sdk" version = "0.135.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "google-auth" }, { name = "protobuf" }, @@ -358,7 +358,7 @@ wheels = [ [[package]] name = "google-auth" version = "2.57.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "cryptography" }, { name = "pyasn1-modules" }, @@ -371,7 +371,7 @@ wheels = [ [[package]] name = "h11" version = "0.16.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, @@ -380,7 +380,7 @@ wheels = [ [[package]] name = "httpcore" version = "1.0.9" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "certifi" }, { name = "h11" }, @@ -393,7 +393,7 @@ wheels = [ [[package]] name = "httpcore2" version = "2.12.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "h11" }, { name = "truststore" }, @@ -406,7 +406,7 @@ wheels = [ [[package]] name = "httpx" version = "0.28.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "anyio" }, { name = "certifi" }, @@ -421,7 +421,7 @@ wheels = [ [[package]] name = "httpx2" version = "2.12.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "anyio", marker = "sys_platform != 'emscripten'" }, { name = "httpcore2", marker = "sys_platform != 'emscripten'" }, @@ -438,7 +438,7 @@ wheels = [ [[package]] name = "httpx2-jsfetch" version = "1.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" }, @@ -447,7 +447,7 @@ wheels = [ [[package]] name = "idna" version = "3.19" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, @@ -456,7 +456,7 @@ wheels = [ [[package]] name = "iniconfig" version = "2.3.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, @@ -465,7 +465,7 @@ wheels = [ [[package]] name = "jsonschema" version = "4.26.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "attrs" }, { name = "jsonschema-specifications" }, @@ -480,7 +480,7 @@ wheels = [ [[package]] name = "jsonschema-specifications" version = "2025.9.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "referencing" }, ] @@ -492,7 +492,7 @@ wheels = [ [[package]] name = "markdown-it-py" version = "4.2.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "mdurl" }, ] @@ -504,7 +504,7 @@ wheels = [ [[package]] name = "mcp" version = "2.1.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "anyio" }, { name = "httpx2" }, @@ -529,7 +529,7 @@ wheels = [ [[package]] name = "mcp-types" version = "2.1.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "pydantic" }, { name = "typing-extensions" }, @@ -542,7 +542,7 @@ wheels = [ [[package]] name = "mdurl" version = "0.1.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, @@ -551,7 +551,7 @@ wheels = [ [[package]] name = "opentelemetry-api" version = "1.44.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "typing-extensions" }, ] @@ -563,7 +563,7 @@ wheels = [ [[package]] name = "packaging" version = "26.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, @@ -572,7 +572,7 @@ wheels = [ [[package]] name = "pexpect" version = "4.9.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "ptyprocess" }, ] @@ -584,7 +584,7 @@ wheels = [ [[package]] name = "pluggy" version = "1.6.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, @@ -593,7 +593,7 @@ wheels = [ [[package]] name = "prompt-toolkit" version = "3.0.53" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "wcwidth" }, ] @@ -605,7 +605,7 @@ wheels = [ [[package]] name = "protobuf" version = "6.33.6" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, @@ -620,7 +620,7 @@ wheels = [ [[package]] name = "ptyprocess" version = "0.7.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, @@ -629,7 +629,7 @@ wheels = [ [[package]] name = "pyasn1" version = "0.6.4" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/a4/9a/23310166d960def5897e91fe20e5b724601b02a22e84ba1f94232c0b7f67/pyasn1-0.6.4.tar.gz", hash = "sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81", size = 151262, upload-time = "2026-07-09T01:12:33.988Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/9a/3b/6163796d69c3977d1e4287bea4a6979161cbbdd170ebb430511e8e1999ce/pyasn1-0.6.4-py3-none-any.whl", hash = "sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b", size = 84410, upload-time = "2026-07-09T01:12:32.92Z" }, @@ -638,7 +638,7 @@ wheels = [ [[package]] name = "pyasn1-modules" version = "0.4.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "pyasn1" }, ] @@ -650,7 +650,7 @@ wheels = [ [[package]] name = "pycparser" version = "3.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, @@ -659,7 +659,7 @@ wheels = [ [[package]] name = "pydantic" version = "2.13.5" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, @@ -674,7 +674,7 @@ wheels = [ [[package]] name = "pydantic-core" version = "2.46.5" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "typing-extensions" }, ] @@ -749,7 +749,7 @@ wheels = [ [[package]] name = "pygments" version = "2.21.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, @@ -758,7 +758,7 @@ wheels = [ [[package]] name = "pyjwt" version = "2.13.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, @@ -772,7 +772,7 @@ crypto = [ [[package]] name = "pyte" version = "0.8.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "wcwidth" }, ] @@ -784,7 +784,7 @@ wheels = [ [[package]] name = "pytest" version = "9.1.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, @@ -800,7 +800,7 @@ wheels = [ [[package]] name = "python-multipart" version = "0.0.32" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, @@ -809,7 +809,7 @@ wheels = [ [[package]] name = "pywin32" version = "312" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/83/ff/32aa7d2ed0ab12b323aaa64f9b75e6ad4f8fd09f9ccfc28c79414d46838d/pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b", size = 6371877, upload-time = "2026-06-04T07:49:28.836Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/03/d9/77040d3b43df3f3be32ea289433d660d2727f5ba327bc73be835127d9d60/pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc", size = 6914841, upload-time = "2026-06-04T07:49:31.85Z" }, @@ -828,7 +828,7 @@ wheels = [ [[package]] name = "pywinpty" version = "3.0.5" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/a9/ef/2d27f30c59a67be7025b2d7858c8c2d282b74d66544b2384730b82de74fd/pywinpty-3.0.5.tar.gz", hash = "sha256:61db0db063de9865adbea66db294628f8577f608d9764a4c7d3384eeacc4e81b", size = 16223484, upload-time = "2026-06-11T00:11:58.93Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/45/34/942cc95ca4e26489875aa8a95192766247a687379ec29543eebe73ec945f/pywinpty-3.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:d62946adf14b15b54c0b8d785f93fe18b04da23f4ad59e2e8c4612646e9abd23", size = 2090915, upload-time = "2026-06-10T23:43:14.98Z" }, @@ -846,7 +846,7 @@ wheels = [ [[package]] name = "questionary" version = "2.1.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "prompt-toolkit" }, ] @@ -858,7 +858,7 @@ wheels = [ [[package]] name = "referencing" version = "0.37.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "attrs" }, { name = "rpds-py" }, @@ -872,7 +872,7 @@ wheels = [ [[package]] name = "requests" version = "2.34.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "certifi" }, { name = "charset-normalizer" }, @@ -887,7 +887,7 @@ wheels = [ [[package]] name = "rich" version = "15.0.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, @@ -900,7 +900,7 @@ wheels = [ [[package]] name = "rpds-py" version = "2026.6.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", size = 343691, upload-time = "2026-06-30T07:15:14.96Z" }, @@ -996,7 +996,7 @@ wheels = [ [[package]] name = "ruff" version = "0.16.5" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/f3/85/c8e12473c93018f92d19dd988a294202e1c27426c47ec4de53ffb847b8d8/ruff-0.16.5.tar.gz", hash = "sha256:1b88500f9ffbcab3dedb0082c9f9492e91ec3d618aac1236a3e0189938f7040b", size = 4912003, upload-time = "2026-08-27T16:34:18.258Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/c6/b6/77c90a970fe2dae17a723acbd011043ea97c98d7deacccefdc4ba74ec512/ruff-0.16.5-py3-none-linux_armv6l.whl", hash = "sha256:12e5f673e774c35fbb62f288809c7653b73445f8ecec6b6063fd6ea3521aa14b", size = 10011941, upload-time = "2026-08-27T16:33:41.287Z" }, @@ -1021,7 +1021,7 @@ wheels = [ [[package]] name = "shellingham" version = "1.5.4" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, @@ -1030,7 +1030,7 @@ wheels = [ [[package]] name = "sse-starlette" version = "3.4.8" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "anyio" }, { name = "starlette" }, @@ -1043,7 +1043,7 @@ wheels = [ [[package]] name = "starlette" version = "1.6.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, @@ -1056,7 +1056,7 @@ wheels = [ [[package]] name = "tomlkit" version = "0.15.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/94/96/e07752635b98536177fa1f37671c8f3cdde2e724c6bcf6034b2cfb571565/tomlkit-0.15.1.tar.gz", hash = "sha256:e25bbf38843005246210a12982776f27f99cb9be67160e14434d0c0d21ee1e97", size = 180129, upload-time = "2026-07-17T01:48:04.562Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/13/bc/8c13eb66537dce1d2bd3a57132902f38d0e7f5bb46fa9f4daed9fe9d76ee/tomlkit-0.15.1-py3-none-any.whl", hash = "sha256:177a05aece5a8ca5266fd3c448abb47b8d352f09d477d3ca8332db4d89b24304", size = 49449, upload-time = "2026-07-17T01:48:05.728Z" }, @@ -1065,7 +1065,7 @@ wheels = [ [[package]] name = "truststore" version = "0.10.4" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, @@ -1074,7 +1074,7 @@ wheels = [ [[package]] name = "ty" version = "0.0.75" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/81/d0/d0c96f898d6974a4a3569ab3efdf9512c04ad99f9203effb55f72497fe97/ty-0.0.75.tar.gz", hash = "sha256:4c5eead33dfbf6e2ebb4f400f74b51ffc9bab702a6f23ddb648a1cbb740387e3", size = 6868326, upload-time = "2026-08-26T20:23:40.399Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/cb/6c/b12d03505f17581f0cfa3c12273fe34c1d67b36dfda1bc561a6bdc16512b/ty-0.0.75-py3-none-linux_armv6l.whl", hash = "sha256:e5409f50db2246fd4bd039d93d261e0cfa1daa554a4fb77256f91072c570349a", size = 12972606, upload-time = "2026-08-26T20:22:59.716Z" }, @@ -1099,7 +1099,7 @@ wheels = [ [[package]] name = "typer" version = "0.27.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "annotated-doc" }, { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1114,7 +1114,7 @@ wheels = [ [[package]] name = "typing-extensions" version = "4.16.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, @@ -1123,7 +1123,7 @@ wheels = [ [[package]] name = "typing-inspection" version = "0.4.4" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "typing-extensions" }, ] @@ -1179,7 +1179,7 @@ dev = [ [[package]] name = "urllib3" version = "2.7.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, @@ -1188,7 +1188,7 @@ wheels = [ [[package]] name = "uvicorn" version = "0.52.4" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } dependencies = [ { name = "click" }, { name = "h11" }, @@ -1201,7 +1201,7 @@ wheels = [ [[package]] name = "wcwidth" version = "0.8.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/34/74/c6428f875774288bec1396f5bfcbc2d925700a4dad61727fd5f2b12f249d/wcwidth-0.8.2.tar.gz", hash = "sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda", size = 1466253, upload-time = "2026-06-29T18:11:11.601Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/96/42/3e5985a0a7e57de470b320c6d6a1a67c844f6737a587f3d44dd13d1819e7/wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85", size = 323166, upload-time = "2026-06-29T18:11:09.888Z" }, @@ -1210,7 +1210,7 @@ wheels = [ [[package]] name = "websockets" version = "17.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/18/72/fba934cb3dff7a85d811820efffcd141ddd52b5a2a01637f64551373ff4d/websockets-17.1.tar.gz", hash = "sha256:acfea4c20bf54384883ea33b1240fc1db4f52e190823a4e2b334bc3e8bfca96a", size = 187520, upload-time = "2026-08-26T17:25:33.063Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/a6/0d/098f23c4c858e5de9459ffc554fa07d5493fbcfca7f040b5800cf1cecc35/websockets-17.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:76dd004f59115087c7b700474cb18f01325e37250032e19396c08ae41448e4b3", size = 217015, upload-time = "2026-08-26T14:55:45.194Z" },