From a4ed19ba61b349e46ea7df05ddc0e5e832ce3d07 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Mon, 29 Jun 2026 15:57:19 -0400 Subject: [PATCH] feat: migrate model providers to Bedrock (Claude) + Fireworks (everything else) (#62) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: switch model providers to AWS Bedrock (Claude) and Fireworks (non-Claude) Migrate off direct provider APIs: AWS Bedrock for Anthropic/Claude via the cross-region inference profile us.anthropic.claude-opus-4-8, Fireworks AI for all non-Claude models. Drop OpenAI (gpt-5.5) and Google (gemini-3.5-flash) entirely. DEFAULT_MODEL_ID is now Bedrock Claude; all Fireworks models stay freely selectable for the agent and reviewer graphs and via team/profile defaults. - pyproject: add langchain-aws (ChatBedrockConverse + boto3) - options.py: Bedrock Claude entry + default; remove openai/google entries - model.py: bedrock_converse provider_model_kwargs (effort -> thinking budget), region pin in make_model, bedrock<->fireworks fallback pairing, AWS_REGION/ FIREWORKS_API_KEY local-dev validation - server.py: provider-aware fallback kwargs build - sanitize_thinking_blocks: also sanitize ChatBedrockConverse thinking blocks - model_fallback: treat transient botocore ClientError codes as fallback-worthy - eval_jobs: repoint hardcoded eval model id to Bedrock Claude - tests: repoint dropped model ids; drop obsolete google test module * fix(bedrock): use adaptive thinking + output_config.effort for Opus 4.8 The handoff spec wired Bedrock Converse thinking as {type: enabled, budget_tokens: N}, but Opus 4.7+ rejects that with a ValidationException: thinking.type "enabled" is not supported; it requires thinking.type "adaptive" plus output_config.effort. Verified by live invoke against us.anthropic.claude-opus-4-8 (account 328440206208, us-east-1): the enabled+budget shape 400s, adaptive+effort returns normally. Map profile effort to additional_model_request_fields: {thinking: {type: adaptive, display: summarized}, output_config: {effort: }} reusing anthropic_thinking_for/anthropic_effort_for. Update the two subagent-model tests asserting the old shape. * fix(deploy): seed Bedrock/Fireworks models, not the dropped anthropic:/openai: ids Model selection is store-driven, so seed_store.sh's team_settings/default seed is what runs in prod. It still seeded the removed providers, which would fail at runtime after the migration: - agent/builder: anthropic:claude-opus-4-8 -> bedrock_converse:us.anthropic.claude-opus-4-8 - reviewer: openai:gpt-5.5 (dropped) -> bedrock_converse:us.anthropic.claude-opus-4-8 (set SEED_REVIEWER_MODEL to a Fireworks model for a cross-family reviewer) - fetch-config REQUIRED_PROVIDER_KEYS default ANTHROPIC_API_KEY,OPENAI_API_KEY -> FIREWORKS_API_KEY (Bedrock auths via host IAM role; dropping the old keys would otherwise fail-fast at boot) - docs (DEPLOYMENT/ROTATION/put-config) updated to match. Surfaced by the cross-family review + verified against deploy/. * fix(bedrock): security-review NITs — region resolution, error sanitization, reasoning-block strip From /sh-security-review (all confirmed-low): - model.py: resolve region from AWS_REGION OR AWS_DEFAULT_REGION (matches validate_local_dev_llm_config) so the validated region is the one actually used. - model_fallback.py: sanitize Bedrock AccessDenied/ResourceNotFound errors to the error code only, so the role ARN + account id in the raw botocore message never reach logs or the user channel (CWE-209). - sanitize_thinking_blocks.py: also strip empty Bedrock reasoning_content blocks (Converse emits reasoning_content, not thinking) so the middleware is not a no-op on Bedrock; + unit tests. (Empty blocks replay fine today; defensive.) * deploy(bedrock): grant instance-role Bedrock invoke + repoint LLM_MODEL_ID / eval model ids Deployment-readiness for the Bedrock migration (PR #62): - instance-role.ts: least-privilege bedrock:InvokeModel[WithResponseStream] on the us.anthropic.claude-opus-4-8 inference-profile ARN + the foundation-model ARN in each routed region (us-east-1/2, us-west-2). The model runs in the server process on the box, so the EC2 instance role is the principal. Simulator-verified (allowed for opus-4-8, implicitDeny for other models) and synth-verified. Passed the mandatory GPT-4.1 IAM cross-review (no blockers, least-privilege confirmed). - config-store.ts: IaC SSM LLM_MODEL_ID anthropic:claude-opus-4-8 -> bedrock_converse:us.anthropic.claude-opus-4-8. This SSM value overrides seed_store.sh's default via pick precedence, so the seed-script fix alone was insufficient — both sources now point at the supported Bedrock id. - infra/README.md + evals/reviewer/config.toml: repoint stale anthropic:/google_genai: ids to the Bedrock id (config.toml's model_id was an active, now-broken value). AWS_REGION is already wired via user-data.sh (IMDS -> boot.env), so no change needed there. * chore(secrets): drop OPENAI/GOOGLE/GROQ key shells (revoked, providers removed) Those three providers were dropped in the Bedrock/Fireworks migration and their keys revoked; the live Secrets Manager objects (open-swe-{dev,prod}/{OPENAI,GOOGLE,GROQ}_API_KEY) were deleted (7-day recovery). Remove them from the IaC so a future cdk deploy does not recreate the shells, and from fetch-config's mirror array so boot stops requesting them: - config-store.ts SECRET_VARS + descriptions (28 -> 25 shells) - fetch-config.sh SECRET_VARS array (kept in lockstep) - put-config.sh: drop the put_secret lines; ANTHROPIC_API_KEY re-labelled optional (eval judge only — Bedrock builder/reviewer auth via the host IAM role). REQUIRED_PROVIDER_KEYS is not set in SSM, so it uses the FIREWORKS_API_KEY default. --- agent/dashboard/eval_jobs.py | 2 +- agent/dashboard/options.py | 20 +- agent/middleware/model_fallback.py | 26 +++ agent/middleware/sanitize_thinking_blocks.py | 34 ++- agent/server.py | 8 +- agent/utils/model.py | 48 +++-- deploy/seahaven/DEPLOYMENT.md | 10 +- deploy/seahaven/ROTATION.md | 2 +- deploy/seahaven/fetch-config.sh | 11 +- deploy/seahaven/put-config.sh | 7 +- deploy/seahaven/seed_store.sh | 4 +- evals/reviewer/config.toml | 5 +- infra/README.md | 2 +- infra/lib/constructs/config-store.ts | 19 +- infra/lib/constructs/instance-role.ts | 23 +++ pyproject.toml | 1 + tests/test_agent_subagent_models.py | 39 ++-- tests/test_dashboard_thread_api.py | 12 +- tests/test_dashboard_web_handoff.py | 4 +- tests/test_fireworks_model.py | 7 +- tests/test_google_model.py | 50 ----- tests/test_model_fallback_resolution.py | 10 +- tests/test_sanitize_thinking_blocks.py | 27 +++ tests/test_slack_context.py | 2 +- tests/test_team_settings_grouping.py | 4 +- tests/test_team_settings_org_guidelines.py | 4 +- uv.lock | 207 ++++++++++++++++++- 27 files changed, 425 insertions(+), 163 deletions(-) delete mode 100644 tests/test_google_model.py diff --git a/agent/dashboard/eval_jobs.py b/agent/dashboard/eval_jobs.py index 7b36ccd8..3cf14646 100644 --- a/agent/dashboard/eval_jobs.py +++ b/agent/dashboard/eval_jobs.py @@ -52,7 +52,7 @@ DEFAULT_REVIEWER_EVAL_CONFIG: ReviewerEvalConfig = { "langsmith_project": DEFAULT_EVAL_PROJECT, "langgraph_url": "", "assistant_id": "reviewer", - "model_id": "google_genai:gemini-3.5-flash", + "model_id": "bedrock_converse:us.anthropic.claude-opus-4-8", "reasoning_effort": "medium", "score_mode": "all_findings", "severity_threshold": "medium", diff --git a/agent/dashboard/options.py b/agent/dashboard/options.py index a617e493..bc26cc01 100644 --- a/agent/dashboard/options.py +++ b/agent/dashboard/options.py @@ -15,26 +15,12 @@ class ModelOption(TypedDict): SUPPORTED_MODELS: list[ModelOption] = [ { - "id": "anthropic:claude-opus-4-8", - "label": "Opus 4.8", + "id": "bedrock_converse:us.anthropic.claude-opus-4-8", + "label": "Opus 4.8 (Bedrock)", "efforts": ["low", "medium", "high", "xhigh", "max"], "default_effort": "high", "supports_images": True, }, - { - "id": "openai:gpt-5.5", - "label": "GPT-5.5", - "efforts": ["none", "low", "medium", "high", "xhigh"], - "default_effort": "xhigh", - "supports_images": True, - }, - { - "id": "google_genai:gemini-3.5-flash", - "label": "Gemini 3.5 Flash", - "efforts": ["minimal", "low", "medium", "high"], - "default_effort": "medium", - "supports_images": True, - }, { "id": "fireworks:accounts/fireworks/models/kimi-k2p7-code", "label": "Kimi K2.7", @@ -60,7 +46,7 @@ SUPPORTED_MODELS: list[ModelOption] = [ SUPPORTED_MODEL_IDS: frozenset[str] = frozenset(m["id"] for m in SUPPORTED_MODELS) -DEFAULT_MODEL_ID: str = "openai:gpt-5.5" +DEFAULT_MODEL_ID: str = "bedrock_converse:us.anthropic.claude-opus-4-8" DEFAULT_MODEL_EFFORT: str = "medium" diff --git a/agent/middleware/model_fallback.py b/agent/middleware/model_fallback.py index 6b8b3a42..41a2cb5c 100644 --- a/agent/middleware/model_fallback.py +++ b/agent/middleware/model_fallback.py @@ -18,6 +18,7 @@ from typing import Any import anthropic import openai +from botocore.exceptions import ClientError from langchain.agents.middleware import AgentMiddleware from langchain.agents.middleware.types import ModelCallResult, ModelRequest, ModelResponse from langchain_core.language_models import BaseChatModel @@ -39,6 +40,14 @@ _TRANSIENT_EXCEPTIONS: tuple[type[BaseException], ...] = ( ) +_RETRYABLE_BEDROCK_ERROR_CODES = { + "ThrottlingException", + "ServiceUnavailableException", + "ModelTimeoutException", + "InternalServerException", +} + + def _should_fallback(exc: BaseException) -> bool: if isinstance(exc, _TRANSIENT_EXCEPTIONS): return True @@ -47,6 +56,11 @@ def _should_fallback(exc: BaseException) -> bool: status = getattr(exc, "status_code", None) if isinstance(status, int) and status in _RETRYABLE_STATUS_CODES: return True + # Bedrock (Claude) raises botocore ClientError for transient throttling/5xx. + if isinstance(exc, ClientError): + code = exc.response.get("Error", {}).get("Code", "") + if code in _RETRYABLE_BEDROCK_ERROR_CODES: + return True return False @@ -87,6 +101,18 @@ def _provider_access_error_message(exc: BaseException) -> str | None: "Choose a different model or update the workspace's OpenAI access and retry." ) + # Bedrock access/lookup failures embed the caller's role ARN and account id in the + # raw botocore message; surface only the error code so identifiers never reach logs + # or the user-facing channel. + if isinstance(exc, ClientError): + code = exc.response.get("Error", {}).get("Code", "") + if code in {"AccessDeniedException", "ResourceNotFoundException"}: + return ( + "The selected Bedrock model is not available to this deployment " + f"(Bedrock error: {code}). Verify the model's inference-profile access and " + "IAM permissions, choose a different model, and retry." + ) + return None diff --git a/agent/middleware/sanitize_thinking_blocks.py b/agent/middleware/sanitize_thinking_blocks.py index f3b251e7..6e40beb9 100644 --- a/agent/middleware/sanitize_thinking_blocks.py +++ b/agent/middleware/sanitize_thinking_blocks.py @@ -8,6 +8,7 @@ from typing import Any from langchain.agents.middleware import AgentMiddleware from langchain.agents.middleware.types import ModelCallResult, ModelRequest, ModelResponse from langchain_anthropic import ChatAnthropic +from langchain_aws import ChatBedrockConverse from langchain_core.messages import AIMessage @@ -15,7 +16,7 @@ def _is_chat_anthropic(model: object) -> bool: seen: set[int] = set() current = model for _ in range(10): - if isinstance(current, ChatAnthropic): + if isinstance(current, (ChatAnthropic, ChatBedrockConverse)): return True current_id = id(current) if current_id in seen: @@ -28,19 +29,32 @@ def _is_chat_anthropic(model: object) -> bool: return False +def _is_empty_thinking_block(block: object) -> bool: + """True for an empty Anthropic ``thinking`` block or an empty Bedrock + ``reasoning_content`` block (Bedrock Converse emits the latter shape).""" + if not isinstance(block, dict): + return False + block_type = block.get("type") + if block_type == "thinking": + return not block.get("thinking") + if block_type == "reasoning_content": + payload = block.get("reasoning_content") + if isinstance(payload, dict): + return not ( + payload.get("text") + or payload.get("signature") + or payload.get("redactedContent") + or payload.get("redacted_content") + ) + return not payload + return False + + def _sanitize_messages(messages: list[Any]) -> None: for message in messages: if not isinstance(message, AIMessage) or not isinstance(message.content, list): continue - content = [ - block - for block in message.content - if not ( - isinstance(block, dict) - and block.get("type") == "thinking" - and not block.get("thinking") - ) - ] + content = [block for block in message.content if not _is_empty_thinking_block(block)] if len(content) != len(message.content): message.content = content diff --git a/agent/server.py b/agent/server.py index 57961ee0..5d2506c2 100644 --- a/agent/server.py +++ b/agent/server.py @@ -101,8 +101,6 @@ from .utils.github_app import ( from .utils.github_proxy import record_proxy_token_expiry from .utils.github_token import repo_cache_key from .utils.model import ( - DEFAULT_LLM_REASONING, - ModelKwargs, fallback_model_id_for, make_model, provider_model_kwargs, @@ -809,9 +807,9 @@ async def get_agent(config: RunnableConfig) -> Pregel: fallback_model_id = os.environ.get("LLM_FALLBACK_MODEL_ID") or fallback_model_id_for(model_id) fallback_middleware: list[Any] = [] if fallback_model_id and fallback_model_id != model_id: - fallback_kwargs: ModelKwargs = {"max_tokens": DEFAULT_LLM_MAX_TOKENS} - if fallback_model_id.startswith("openai:"): - fallback_kwargs["reasoning"] = DEFAULT_LLM_REASONING + fallback_kwargs = provider_model_kwargs( + fallback_model_id, None, max_tokens=DEFAULT_LLM_MAX_TOKENS + ) fallback_middleware.append( ModelFallbackMiddleware(make_model(fallback_model_id, **fallback_kwargs)) ) diff --git a/agent/utils/model.py b/agent/utils/model.py index 19a77404..cadf1dbd 100644 --- a/agent/utils/model.py +++ b/agent/utils/model.py @@ -45,6 +45,8 @@ class ModelKwargs(TypedDict, total=False): temperature: float | None max_retries: int | None model_kwargs: dict[str, object] | None + additional_model_request_fields: dict[str, object] | None + region_name: str | None _ANTHROPIC_EFFORTS: set[AnthropicEffort] = {"low", "medium", "high", "xhigh", "max"} @@ -57,6 +59,13 @@ def make_model(model_id: str, **kwargs: Unpack[ModelKwargs]): if model_id.startswith("openai:"): model_kwargs["base_url"] = OPENAI_RESPONSES_WS_BASE_URL model_kwargs["use_responses_api"] = True + elif model_id.startswith("bedrock_converse:"): + # Resolve region with the same precedence validate_local_dev_llm_config accepts + # (AWS_REGION or AWS_DEFAULT_REGION), so the validated value is the one actually used. + model_kwargs.setdefault( + "region_name", + os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION") or "us-east-1", + ) return init_chat_model(model=model_id, **model_kwargs) @@ -64,14 +73,14 @@ def make_model(model_id: str, **kwargs: Unpack[ModelKwargs]): def fallback_model_id_for(primary_model_id: str) -> str | None: """Return the cross-provider fallback model id for a given primary, if any. - Anthropic primaries fall back to OpenAI and vice versa. Returns ``None`` - when the provider has no configured cross-provider fallback (e.g. Google, - local, or self-hosted providers we don't want to silently route off-host). + Bedrock (Claude) primaries fall back to Fireworks and vice versa. Returns + ``None`` when the provider has no configured cross-provider fallback (e.g. + local or self-hosted providers we don't want to silently route off-host). """ - if primary_model_id.startswith("anthropic:"): - return "openai:gpt-5.5" - if primary_model_id.startswith("openai:"): - return "anthropic:claude-opus-4-5" + if primary_model_id.startswith("bedrock_converse:"): + return "fireworks:accounts/fireworks/models/deepseek-v4-pro" + if primary_model_id.startswith("fireworks:"): + return "bedrock_converse:us.anthropic.claude-opus-4-8" return None @@ -179,6 +188,19 @@ def provider_model_kwargs( effort = anthropic_effort_for(profile_effort) if effort is not None: kwargs["effort"] = effort + elif model_id.startswith("bedrock_converse:"): + # Opus 4.7+ on Bedrock rejects thinking.type "enabled"/budget_tokens with a + # ValidationException; it requires adaptive thinking plus output_config.effort, + # passed through Converse's additional_model_request_fields. + fields: dict[str, object] = {} + thinking = anthropic_thinking_for(profile_effort) + if thinking is not None: + fields["thinking"] = thinking + effort = anthropic_effort_for(profile_effort) + if effort is not None: + fields["output_config"] = {"effort": effort} + if fields: + kwargs["additional_model_request_fields"] = fields elif model_id.startswith("google_genai:") and is_gemini_3_family(model_id): thinking_level = google_thinking_level_for(profile_effort) if thinking_level is not None: @@ -204,13 +226,9 @@ def validate_local_dev_llm_config() -> None: model_id = os.environ.get("LLM_MODEL_ID", DEFAULT_MODEL_ID) - if model_id.startswith("openai:") and not os.environ.get("OPENAI_API_KEY"): - raise ValueError(f"OPENAI_API_KEY is required for configured model {model_id}") - elif model_id.startswith("anthropic:") and not os.environ.get("ANTHROPIC_API_KEY"): - raise ValueError(f"ANTHROPIC_API_KEY is required for configured model {model_id}") - elif model_id.startswith("google_genai:") and not os.environ.get("GOOGLE_API_KEY"): - raise ValueError(f"GOOGLE_API_KEY is required for configured model {model_id}") - elif model_id.startswith("groq:") and not os.environ.get("GROQ_API_KEY"): - raise ValueError(f"GROQ_API_KEY is required for configured model {model_id}") + if model_id.startswith("bedrock_converse:") and not ( + os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION") + ): + raise ValueError(f"AWS_REGION is required for configured model {model_id}") elif model_id.startswith("fireworks:") and not os.environ.get("FIREWORKS_API_KEY"): raise ValueError(f"FIREWORKS_API_KEY is required for configured model {model_id}") diff --git a/deploy/seahaven/DEPLOYMENT.md b/deploy/seahaven/DEPLOYMENT.md index 2b4fe985..0a54a18c 100644 --- a/deploy/seahaven/DEPLOYMENT.md +++ b/deploy/seahaven/DEPLOYMENT.md @@ -218,10 +218,12 @@ on-box swapfile — the OOM-prone SPA build now runs in CI, not on the box. Model selection is **store-driven**, not env. The `team_settings/default` store doc wins (then per-user profile, then per-thread); `LLM_MODEL_ID` is only a seed-time fallback. Defaults seeded by `seed_store.sh`: -- builder: `anthropic:claude-opus-4-8` (effort `high`) -- reviewer (cross-family): `openai:gpt-5.5` (effort `high`) — `openai:gpt-4.1` is - **not** in this fork's `SUPPORTED_MODELS` (`agent/dashboard/options.py`); add it - there first if you need 4.1. +- builder: `bedrock_converse:us.anthropic.claude-opus-4-8` (effort `high`) +- reviewer: `bedrock_converse:us.anthropic.claude-opus-4-8` (effort `high`) — set + `SEED_REVIEWER_MODEL` (or change it in the UI) to a Fireworks model if you want a + cross-family reviewer. Only ids present in `SUPPORTED_MODELS` + (`agent/dashboard/options.py`) are valid; OpenAI/Google models were removed in the + Bedrock/Fireworks migration. - the `analyzer` graph is hardcoded to the code default and ignores team settings. ## Triggering diff --git a/deploy/seahaven/ROTATION.md b/deploy/seahaven/ROTATION.md index f895fcbd..38f7de45 100644 --- a/deploy/seahaven/ROTATION.md +++ b/deploy/seahaven/ROTATION.md @@ -68,7 +68,7 @@ sudo systemctl restart open-swe.service | **LINEAR_WEBHOOK_SECRET** | Linear webhook signature. Required in prod only when the Linear integration is wired (`LINEAR_API_KEY` present). Rotate in Linear + AWS together, restart. | | **SLACK_CLIENT_SECRET / GITHUB_APP_CLIENT_SECRET** | OAuth client secrets (dashboard login / Slack OAuth). Rotate in the provider console + AWS, restart. Existing dashboard sessions are JWT-signed by `DASHBOARD_JWT_SECRET`, not these, so they survive. | | **DASHBOARD_JWT_SECRET** | Signs dashboard session cookies. Rotating **invalidates all active sessions** (users re-login). Hard-required (RuntimeError if empty). No overlap list — single value. | -| **Model provider keys** (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_API_KEY`, `GROQ_API_KEY`, `FIREWORKS_API_KEY`) | Standard API-key rotation: issue new key, update AWS, restart, revoke old. The **active** provider keys (whatever `team_settings/default` seeds — currently `ANTHROPIC_API_KEY` + `OPENAI_API_KEY`) are fail-fast-required; keep `REQUIRED_PROVIDER_KEYS` in sync if you change the seeded models. | +| **Model provider keys** (`FIREWORKS_API_KEY`; legacy `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `GOOGLE_API_KEY` / `GROQ_API_KEY`) | Standard API-key rotation: issue new key, update AWS, restart, revoke old. Bedrock (the seeded Claude builder + reviewer) authenticates via the **host IAM role — no API key**; the only fail-fast-required provider key is now `FIREWORKS_API_KEY` (fallback / subagents / any non-Claude model). Keep `REQUIRED_PROVIDER_KEYS` in sync if you change the seeded models. | | **LANGSMITH_API_KEY_PROD** | LangSmith key powering the `langsmith` sandbox (the only provider with working in-sandbox git/gh auth). Required when `SANDBOX_TYPE=langsmith` (fail-fast). Rotate in LangSmith + AWS, restart; existing sandboxes keep their already-injected proxy token until recycled. | | **SLACK_BOT_TOKEN / LINEAR_API_KEY / GITHUB_PAT / EXA_API_KEY / DAYTONA_API_KEY / RUNLOOP_API_KEY / CORRIDOR_* / USER_ID_API_KEY_MAP / X_SERVICE_AUTH_JWT_SECRET** | Plain API-token rotation: update AWS, restart, revoke old at the provider. None support an overlap list. | diff --git a/deploy/seahaven/fetch-config.sh b/deploy/seahaven/fetch-config.sh index bf82cad1..77457768 100755 --- a/deploy/seahaven/fetch-config.sh +++ b/deploy/seahaven/fetch-config.sh @@ -182,9 +182,9 @@ SECRET_VARS=( ANTHROPIC_API_KEY CORRIDOR_API_TOKEN CORRIDOR_MCP_TOKEN CORRIDOR_TOKEN DASHBOARD_JWT_SECRET DAYTONA_API_KEY EXA_API_KEY FIREWORKS_API_KEY GITHUB_APP_CLIENT_SECRET GITHUB_APP_PRIVATE_KEY GITHUB_PAT GITHUB_WEBHOOK_SECRET - GOOGLE_API_KEY GROQ_API_KEY JUDGE_ANTHROPIC_API_KEY LANGSMITH_API_KEY + JUDGE_ANTHROPIC_API_KEY LANGSMITH_API_KEY LANGSMITH_API_KEY_PROD LANGCHAIN_API_KEY LINEAR_API_KEY LINEAR_WEBHOOK_SECRET - OPENAI_API_KEY RUNLOOP_API_KEY SLACK_BOT_TOKEN SLACK_CLIENT_SECRET + RUNLOOP_API_KEY SLACK_BOT_TOKEN SLACK_CLIENT_SECRET SLACK_SIGNING_SECRET TOKEN_ENCRYPTION_KEY USER_ID_API_KEY_MAP X_SERVICE_AUTH_JWT_SECRET ) @@ -269,9 +269,10 @@ required=( # deployment-validation env (boot/health/boundary), not a live-triggered agent. # Active model-provider key(s): model selection is store-driven (team_settings), -# so fetch-config cannot infer it from .env. Default to the seeded cross-family -# pair (anthropic builder + openai reviewer). Override with a comma list. -IFS=',' read -r -a provider_keys <<<"${REQUIRED_PROVIDER_KEYS:-ANTHROPIC_API_KEY,OPENAI_API_KEY}" +# so fetch-config cannot infer it from .env. Bedrock (Claude builder + reviewer) +# authenticates via the host IAM role — no API key; Fireworks (fallback / subagents / +# any non-Claude model) needs its key. Override with a comma list if the active models change. +IFS=',' read -r -a provider_keys <<<"${REQUIRED_PROVIDER_KEYS:-FIREWORKS_API_KEY}" for k in "${provider_keys[@]}"; do k="${k//[[:space:]]/}" [ -n "$k" ] && required+=("$k") diff --git a/deploy/seahaven/put-config.sh b/deploy/seahaven/put-config.sh index 76c9d722..dddb9609 100755 --- a/deploy/seahaven/put-config.sh +++ b/deploy/seahaven/put-config.sh @@ -88,8 +88,7 @@ put_param() { # TOKEN_ENCRYPTION_KEY, GITHUB_APP_PRIVATE_KEY/CLIENT_SECRET, the active provider # key(s) (ANTHROPIC_API_KEY + OPENAI_API_KEY by default), LANGSMITH_API_KEY_PROD, # and (prod) GITHUB_WEBHOOK_SECRET + SLACK_SIGNING_SECRET. -put_secret ANTHROPIC_API_KEY # REQUIRED — primary builder provider key -put_secret OPENAI_API_KEY # REQUIRED — default reviewer provider key +put_secret ANTHROPIC_API_KEY # optional — eval judge only (JUDGE_ANTHROPIC_API_KEY fallback); Bedrock builder/reviewer use the host IAM role put_secret DASHBOARD_JWT_SECRET # REQUIRED — dashboard session JWT signing put_secret TOKEN_ENCRYPTION_KEY # REQUIRED — Fernet key(s) for GH-token crypto put_secret GITHUB_APP_PRIVATE_KEY # REQUIRED — GitHub App PEM (multiline; quote it) @@ -104,10 +103,8 @@ put_secret CORRIDOR_MCP_TOKEN # optional — Corridor MCP (alt name) put_secret CORRIDOR_TOKEN # optional — Corridor MCP (alt name) put_secret DAYTONA_API_KEY # only if SANDBOX_TYPE=daytona put_secret EXA_API_KEY # optional — Exa web search -put_secret FIREWORKS_API_KEY # only if a fireworks: model is used +put_secret FIREWORKS_API_KEY # active non-Claude key (fallback/subagents); Bedrock uses the host IAM role put_secret GITHUB_PAT # optional — PAT fallback -put_secret GOOGLE_API_KEY # only if a google_genai: model is used -put_secret GROQ_API_KEY # only if a groq: model is used put_secret JUDGE_ANTHROPIC_API_KEY # optional — eval judge (falls back to ANTHROPIC) put_secret LANGSMITH_API_KEY # optional — LangSmith (dev) put_secret LANGCHAIN_API_KEY # optional — LangSmith alt name diff --git a/deploy/seahaven/seed_store.sh b/deploy/seahaven/seed_store.sh index e3765917..dfe1a416 100755 --- a/deploy/seahaven/seed_store.sh +++ b/deploy/seahaven/seed_store.sh @@ -77,9 +77,9 @@ pick() { # pick DEFAULT OVERRIDE_VALUE FILE_KEY... # local server; OPENSWE_PORT may override the port but never the host. BASE="http://127.0.0.1:${OPENSWE_PORT:-2024}" -AGENT_MODEL="$(pick 'anthropic:claude-opus-4-8' "${OPENSWE_AGENT_MODEL:-}" SEED_AGENT_MODEL LLM_MODEL_ID)" +AGENT_MODEL="$(pick 'bedrock_converse:us.anthropic.claude-opus-4-8' "${OPENSWE_AGENT_MODEL:-}" SEED_AGENT_MODEL LLM_MODEL_ID)" AGENT_EFFORT="$(pick 'high' "${OPENSWE_AGENT_EFFORT:-}" SEED_AGENT_EFFORT)" -REVIEWER_MODEL="$(pick 'openai:gpt-5.5' "${OPENSWE_REVIEWER_MODEL:-}" SEED_REVIEWER_MODEL)" +REVIEWER_MODEL="$(pick 'bedrock_converse:us.anthropic.claude-opus-4-8' "${OPENSWE_REVIEWER_MODEL:-}" SEED_REVIEWER_MODEL)" REVIEWER_EFFORT="$(pick 'high' "${OPENSWE_REVIEWER_EFFORT:-}" SEED_REVIEWER_EFFORT)" # default_repo = owner/name from AWS config (DEFAULT_REPO_OWNER is hard-pinned diff --git a/evals/reviewer/config.toml b/evals/reviewer/config.toml index d8e659f5..86364682 100644 --- a/evals/reviewer/config.toml +++ b/evals/reviewer/config.toml @@ -9,8 +9,9 @@ langsmith_project = "open-swe-evals" # Leave blank to use LANGGRAPH_URL or local dev. langgraph_url = "" assistant_id = "reviewer" -# models: openai:gpt-5.5, anthropic:claude-opus-4-8, google_genai:gemini-3.5-flash -model_id = "google_genai:gemini-3.5-flash" +# models (post Bedrock/Fireworks migration): bedrock_converse:us.anthropic.claude-opus-4-8, +# or any fireworks:* id in agent/dashboard/options.py SUPPORTED_MODELS. +model_id = "bedrock_converse:us.anthropic.claude-opus-4-8" reasoning_effort = "medium" # score_mode: diff --git a/infra/README.md b/infra/README.md index b5c866a8..fc49e49b 100644 --- a/infra/README.md +++ b/infra/README.md @@ -156,7 +156,7 @@ Three buckets: | `DASHBOARD_BASE_URL` | `https://openswe-dev.seahaven.com` *(confirm host)* | `https://openswe.seahaven.com` *(confirm host)* | | `DASHBOARD_API_BASE_URL` | same as base | same as base | | `DASHBOARD_ALLOWED_ORIGINS` | same as base | same as base | - | `LLM_MODEL_ID` | `anthropic:claude-opus-4-8` *(confirm)* | `anthropic:claude-opus-4-8` *(confirm)* | + | `LLM_MODEL_ID` | `bedrock_converse:us.anthropic.claude-opus-4-8` | `bedrock_converse:us.anthropic.claude-opus-4-8` | 3. **Out-of-band SSM config — NOT created by CDK.** Operationally-variable or env-specific-unknown values listed in `OUT_OF_BAND_SSM` and populated by diff --git a/infra/lib/constructs/config-store.ts b/infra/lib/constructs/config-store.ts index 4bc79a52..099749ae 100644 --- a/infra/lib/constructs/config-store.ts +++ b/infra/lib/constructs/config-store.ts @@ -56,15 +56,12 @@ export const SECRET_VARS: readonly string[] = [ "GITHUB_APP_PRIVATE_KEY", "GITHUB_PAT", "GITHUB_WEBHOOK_SECRET", - "GOOGLE_API_KEY", - "GROQ_API_KEY", "JUDGE_ANTHROPIC_API_KEY", "LANGSMITH_API_KEY", "LANGSMITH_API_KEY_PROD", "LANGCHAIN_API_KEY", "LINEAR_API_KEY", "LINEAR_WEBHOOK_SECRET", - "OPENAI_API_KEY", "RUNLOOP_API_KEY", "SLACK_BOT_TOKEN", "SLACK_CLIENT_SECRET", @@ -73,7 +70,10 @@ export const SECRET_VARS: readonly string[] = [ "USER_ID_API_KEY_MAP", "X_SERVICE_AUTH_JWT_SECRET", ] as const; -// 28 secret shells. The T9 inventory header said "29" vs 27 enumerated; reconciled +// 25 secret shells (OPENAI_API_KEY / GOOGLE_API_KEY / GROQ_API_KEY removed in the +// Bedrock/Fireworks migration — those providers are dropped from SUPPORTED_MODELS and +// their keys revoked + secret objects deleted). The T9 inventory header said "29" vs +// 27 enumerated; reconciled // (Adam confirm 2026-06-26): USER_ID_API_KEY_MAP (maps user ids -> API keys; flagged // sensitive by the T5 security review) is a SECRET and is included here. // JUDGE_ANTHROPIC_BASE_URL is a URL (non-sensitive config, eval-only) -> SSM/default, @@ -93,15 +93,12 @@ const SECRET_DESCRIPTIONS: Record = { GITHUB_APP_PRIVATE_KEY: "GitHub App private key PEM (installation-token minting).", GITHUB_PAT: "GitHub PAT fallback (optional).", GITHUB_WEBHOOK_SECRET: "GitHub webhook signature secret (prod-required).", - GOOGLE_API_KEY: "Google GenAI key (only if a google_genai: model is used).", - GROQ_API_KEY: "Groq LLM key (only if a groq: model is used).", JUDGE_ANTHROPIC_API_KEY: "Eval judge key (optional; falls back to ANTHROPIC_API_KEY).", LANGSMITH_API_KEY: "LangSmith key (dev).", LANGSMITH_API_KEY_PROD: "LangSmith key (prod / deployed sandbox).", LANGCHAIN_API_KEY: "LangSmith key alt name (fallback).", LINEAR_API_KEY: "Linear API key (optional).", LINEAR_WEBHOOK_SECRET: "Linear webhook signature secret (required when Linear is wired).", - OPENAI_API_KEY: "OpenAI key (primary reviewer provider).", RUNLOOP_API_KEY: "Runloop sandbox key (only if SANDBOX_TYPE=runloop).", SLACK_BOT_TOKEN: "Slack bot token (optional).", SLACK_CLIENT_SECRET: "Slack OAuth client secret.", @@ -142,8 +139,12 @@ export function iacManagedSsm(env: EnvName): Record { DASHBOARD_BASE_URL: host, DASHBOARD_API_BASE_URL: host, DASHBOARD_ALLOWED_ORIGINS: host, - // Primary builder model (project memory team_settings: anthropic:claude-opus-4-8). - LLM_MODEL_ID: "anthropic:claude-opus-4-8", + // Primary builder model. seed_store.sh's `pick` precedence is + // OPENSWE_AGENT_MODEL > SEED_AGENT_MODEL > LLM_MODEL_ID > script default, so this + // SSM value overrides the seed-script default — it MUST be a supported id. Post + // Bedrock/Fireworks migration the only Bedrock-Claude id is the inference profile; + // `anthropic:claude-opus-4-8` was removed from SUPPORTED_MODELS. + LLM_MODEL_ID: "bedrock_converse:us.anthropic.claude-opus-4-8", }; // Dev e2e smoke: seed the owner's user_mapping so an @openswe comment from the // triggering GitHub login resolves (an unmapped commenter is silently skipped). diff --git a/infra/lib/constructs/instance-role.ts b/infra/lib/constructs/instance-role.ts index 4a32de9a..21d18859 100644 --- a/infra/lib/constructs/instance-role.ts +++ b/infra/lib/constructs/instance-role.ts @@ -117,6 +117,29 @@ export class InstanceRole extends Construct { // moves these to a customer CMK, add a scoped `kms:Decrypt` on that key ARN // ONLY (not `*`). + // Invoke the Bedrock Claude model. DEFAULT_MODEL_ID is + // `bedrock_converse:us.anthropic.claude-opus-4-8`, and the model runs in the + // LangGraph server PROCESS on this box (not in the sandbox), so the EC2 + // instance role is the calling principal. The `us.` cross-region inference + // profile fans out to us-east-1 / us-east-2 / us-west-2, and Bedrock authorizes + // InvokeModel against BOTH the inference-profile ARN AND the underlying + // foundation-model ARN in each routed region — all four resources are required + // or the call AccessDenies. Scoped to opus-4-8 ONLY (least-privilege): adding a + // new Bedrock model to SUPPORTED_MODELS means extending this resource list. + // IAM change — flag for T4 (GPT-4.1 IAM cross-review) / T5 (/sh-security-review). + this.role.addToPolicy( + new iam.PolicyStatement({ + sid: "InvokeBedrockClaude", + actions: ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"], + resources: [ + `arn:aws:bedrock:${REGION}:${ACCOUNT}:inference-profile/us.anthropic.claude-opus-4-8`, + "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-opus-4-8", + "arn:aws:bedrock:us-east-2::foundation-model/anthropic.claude-opus-4-8", + "arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-opus-4-8", + ], + }), + ); + // Ship application logs to CloudWatch Logs under /open-swe//*. this.role.addToPolicy( new iam.PolicyStatement({ diff --git a/pyproject.toml b/pyproject.toml index 31c77bbe..0323d620 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ dependencies = [ "langgraph>=1.1.10", "markdownify>=1.2.2", "langchain-anthropic>=1.4.6", + "langchain-aws>=0.2.0", "langgraph-cli[inmem]>=0.4.30", "langsmith==0.8.18", "langchain-openai>=1.2.2", diff --git a/tests/test_agent_subagent_models.py b/tests/test_agent_subagent_models.py index 13d91cdc..7d317f88 100644 --- a/tests/test_agent_subagent_models.py +++ b/tests/test_agent_subagent_models.py @@ -50,15 +50,18 @@ async def test_agent_uses_profile_subagent_model_override() -> None: patch( "agent.server.get_team_default_model_pair", new_callable=AsyncMock, - return_value=(("openai:gpt-5.5", "medium"), ("openai:gpt-5.5", "low")), + return_value=( + ("bedrock_converse:us.anthropic.claude-opus-4-8", "medium"), + ("fireworks:accounts/fireworks/models/deepseek-v4-pro", "low"), + ), ), patch( "agent.server.load_profile", new_callable=AsyncMock, return_value={ - "default_model": "anthropic:claude-opus-4-8", + "default_model": "bedrock_converse:us.anthropic.claude-opus-4-8", "reasoning_effort": "high", - "default_subagent_model": "openai:gpt-5.5", + "default_subagent_model": "fireworks:accounts/fireworks/models/deepseek-v4-pro", "subagent_reasoning_effort": "xhigh", }, ), @@ -76,13 +79,15 @@ async def test_agent_uses_profile_subagent_model_override() -> None: assert subagents[0]["model"] is subagent_model main_call = make_model.call_args_list[0] - assert main_call.args == ("anthropic:claude-opus-4-8",) - assert main_call.kwargs["thinking"] == {"type": "adaptive", "display": "summarized"} - assert main_call.kwargs["effort"] == "high" + assert main_call.args == ("bedrock_converse:us.anthropic.claude-opus-4-8",) + assert main_call.kwargs["additional_model_request_fields"] == { + "thinking": {"type": "adaptive", "display": "summarized"}, + "output_config": {"effort": "high"}, + } subagent_call = make_model.call_args_list[1] - assert subagent_call.args == ("openai:gpt-5.5",) - assert subagent_call.kwargs["reasoning"] == {"effort": "xhigh", "summary": "auto"} + assert subagent_call.args == ("fireworks:accounts/fireworks/models/deepseek-v4-pro",) + assert subagent_call.kwargs["model_kwargs"] == {"reasoning_effort": "xhigh"} @pytest.mark.asyncio @@ -123,13 +128,16 @@ async def test_agent_subagent_inherits_profile_model_override_without_explicit_p patch( "agent.server.get_team_default_model_pair", new_callable=AsyncMock, - return_value=(("openai:gpt-5.5", "medium"), ("openai:gpt-5.5", "low")), + return_value=( + ("bedrock_converse:us.anthropic.claude-opus-4-8", "medium"), + ("fireworks:accounts/fireworks/models/deepseek-v4-pro", "low"), + ), ), patch( "agent.server.load_profile", new_callable=AsyncMock, return_value={ - "default_model": "anthropic:claude-opus-4-8", + "default_model": "bedrock_converse:us.anthropic.claude-opus-4-8", "reasoning_effort": "high", }, ), @@ -143,10 +151,9 @@ async def test_agent_subagent_inherits_profile_model_override_without_explicit_p subagents = captured["subagents"] assert isinstance(subagents, list) assert subagents[0]["model"] is subagent_model - assert make_model.call_args_list[0].args == ("anthropic:claude-opus-4-8",) - assert make_model.call_args_list[1].args == ("anthropic:claude-opus-4-8",) - assert make_model.call_args_list[1].kwargs["thinking"] == { - "type": "adaptive", - "display": "summarized", + assert make_model.call_args_list[0].args == ("bedrock_converse:us.anthropic.claude-opus-4-8",) + assert make_model.call_args_list[1].args == ("bedrock_converse:us.anthropic.claude-opus-4-8",) + assert make_model.call_args_list[1].kwargs["additional_model_request_fields"] == { + "thinking": {"type": "adaptive", "display": "summarized"}, + "output_config": {"effort": "high"}, } - assert make_model.call_args_list[1].kwargs["effort"] == "high" diff --git a/tests/test_dashboard_thread_api.py b/tests/test_dashboard_thread_api.py index d8e05bb9..448ef794 100644 --- a/tests/test_dashboard_thread_api.py +++ b/tests/test_dashboard_thread_api.py @@ -8,7 +8,7 @@ from agent.dashboard.agent_overrides import resolve_agent_model_id from agent.dashboard.options import model_supports_images _TEXT_ONLY_MODEL = "fireworks:accounts/fireworks/models/deepseek-v4-pro" -_VISION_MODEL = "openai:gpt-5.5" +_VISION_MODEL = "bedrock_converse:us.anthropic.claude-opus-4-8" def _image() -> thread_api.DashboardImageBody: @@ -73,11 +73,11 @@ async def test_resolve_agent_model_choice_applies_request_before_profile(monkeyp model_id, effort = await thread_api._resolve_agent_model_choice( {"default_model": _TEXT_ONLY_MODEL, "reasoning_effort": "high"}, - "anthropic:claude-opus-4-8", + "bedrock_converse:us.anthropic.claude-opus-4-8", "high", ) - assert (model_id, effort) == ("anthropic:claude-opus-4-8", "high") + assert (model_id, effort) == ("bedrock_converse:us.anthropic.claude-opus-4-8", "high") async def test_resolve_agent_model_id_defaults_to_team_default(monkeypatch) -> None: @@ -113,8 +113,10 @@ async def test_resolve_agent_model_id_applies_per_thread_override(monkeypatch) - monkeypatch.setattr("agent.dashboard.agent_overrides.get_team_default_model", fake_team_default) monkeypatch.setattr("agent.dashboard.agent_overrides.load_profile", lambda login: None) - model_id = await resolve_agent_model_id(None, per_thread_model_id="anthropic:claude-opus-4-8") - assert model_id == "anthropic:claude-opus-4-8" + model_id = await resolve_agent_model_id( + None, per_thread_model_id="bedrock_converse:us.anthropic.claude-opus-4-8" + ) + assert model_id == "bedrock_converse:us.anthropic.claude-opus-4-8" def _new_thread_client(created: dict[str, object]) -> object: diff --git a/tests/test_dashboard_web_handoff.py b/tests/test_dashboard_web_handoff.py index 5d30841b..9100b725 100644 --- a/tests/test_dashboard_web_handoff.py +++ b/tests/test_dashboard_web_handoff.py @@ -169,7 +169,7 @@ async def test_dashboard_followup_on_busy_thread_queues_images( metadata = { "source": "dashboard", "github_login": "octocat", - "resolved_model": "openai:gpt-5.5", + "resolved_model": "bedrock_converse:us.anthropic.claude-opus-4-8", } client = _FakeClient(metadata) queued_messages: list[object] = [] @@ -232,7 +232,7 @@ async def test_dashboard_followup_on_busy_text_only_thread_rejects_images( thread_api.ThreadMessageBody( content="continue in web", images=[thread_api.DashboardImageBody(base64="aW1hZ2U=", mimeType="image/png")], - model_id="openai:gpt-5.5", + model_id="fireworks:accounts/fireworks/models/deepseek-v4-pro", effort="medium", ), ) diff --git a/tests/test_fireworks_model.py b/tests/test_fireworks_model.py index 54011540..368f31ca 100644 --- a/tests/test_fireworks_model.py +++ b/tests/test_fireworks_model.py @@ -56,8 +56,11 @@ def test_provider_model_kwargs_for_fireworks_unknown_effort_omits_reasoning() -> assert "model_kwargs" not in kwargs -def test_fireworks_has_no_cross_provider_fallback() -> None: - assert fallback_model_id_for("fireworks:accounts/fireworks/models/deepseek-v4-pro") is None +def test_fireworks_falls_back_to_bedrock() -> None: + assert ( + fallback_model_id_for("fireworks:accounts/fireworks/models/deepseek-v4-pro") + == "bedrock_converse:us.anthropic.claude-opus-4-8" + ) @pytest.mark.parametrize( diff --git a/tests/test_google_model.py b/tests/test_google_model.py deleted file mode 100644 index 3b7d0c1f..00000000 --- a/tests/test_google_model.py +++ /dev/null @@ -1,50 +0,0 @@ -from agent.dashboard.options import SUPPORTED_MODELS, provider_fallback_pair -from agent.utils.model import ( - google_thinking_level_for, - is_gemini_3_family, - provider_model_kwargs, -) - - -def test_gemini_3_family_detection() -> None: - assert is_gemini_3_family("google_genai:gemini-3.5-flash") is True - assert is_gemini_3_family("google_genai:gemini-2.5-flash") is False - - -def test_google_thinking_level_maps_effort() -> None: - assert google_thinking_level_for("minimal") == "minimal" - assert google_thinking_level_for("none") == "minimal" - assert google_thinking_level_for("medium") == "medium" - assert google_thinking_level_for("high") == "high" - assert google_thinking_level_for("unknown") is None - - -def test_gemini_35_flash_is_supported_with_documented_efforts() -> None: - gemini = next(m for m in SUPPORTED_MODELS if m["id"] == "google_genai:gemini-3.5-flash") - assert gemini["label"] == "Gemini 3.5 Flash" - assert gemini["efforts"] == ["minimal", "low", "medium", "high"] - assert gemini["default_effort"] == "medium" - - -def test_google_provider_fallback_uses_gemini_35_flash() -> None: - assert provider_fallback_pair("google_genai:gemini-3-flash-preview", "high") == ( - "google_genai:gemini-3.5-flash", - "high", - ) - - -def test_google_provider_fallback_maps_legacy_none_to_minimal() -> None: - assert provider_fallback_pair("google_genai:gemini-3-flash-preview", "none") == ( - "google_genai:gemini-3.5-flash", - "minimal", - ) - - -def test_provider_model_kwargs_for_google() -> None: - kwargs = provider_model_kwargs( - "google_genai:gemini-3.5-flash", - "high", - max_tokens=16_000, - ) - assert kwargs["max_tokens"] == 16_000 - assert kwargs["thinking_level"] == "high" diff --git a/tests/test_model_fallback_resolution.py b/tests/test_model_fallback_resolution.py index 516d7bc3..c4fee9f3 100644 --- a/tests/test_model_fallback_resolution.py +++ b/tests/test_model_fallback_resolution.py @@ -10,8 +10,8 @@ from agent.dashboard.options import ( ) from agent.dashboard.team_settings import get_team_default_model -STALE_ANTHROPIC = "anthropic:claude-opus-4-7" -SUPPORTED_ANTHROPIC = "anthropic:claude-opus-4-8" +STALE_ANTHROPIC = "bedrock_converse:us.anthropic.claude-opus-4-7" +SUPPORTED_ANTHROPIC = "bedrock_converse:us.anthropic.claude-opus-4-8" def test_provider_fallback_preserves_provider_and_effort() -> None: @@ -23,9 +23,9 @@ def test_provider_fallback_uses_default_effort_when_unsupported() -> None: assert provider_fallback_pair(STALE_ANTHROPIC, None) == (SUPPORTED_ANTHROPIC, "high") -def test_provider_fallback_resolves_openai_within_provider() -> None: - model, effort = provider_fallback_pair("openai:gpt-5-legacy", "low") - assert model.startswith("openai:") +def test_provider_fallback_resolves_fireworks_within_provider() -> None: + model, effort = provider_fallback_pair("fireworks:accounts/fireworks/models/legacy-old", "low") + assert model.startswith("fireworks:") assert effort == "low" diff --git a/tests/test_sanitize_thinking_blocks.py b/tests/test_sanitize_thinking_blocks.py index 7145fe73..5e8a4e96 100644 --- a/tests/test_sanitize_thinking_blocks.py +++ b/tests/test_sanitize_thinking_blocks.py @@ -4,6 +4,7 @@ from unittest.mock import MagicMock import pytest from langchain_anthropic import ChatAnthropic +from langchain_aws import ChatBedrockConverse from langchain_core.messages import AIMessage, HumanMessage from agent.middleware.sanitize_thinking_blocks import SanitizeThinkingBlocksMiddleware @@ -66,6 +67,32 @@ class TestSanitizeThinkingBlocksMiddleware: assert result is response assert message.content == [{"type": "text", "text": "ok"}] + def test_drops_empty_reasoning_content_block_for_bedrock(self) -> None: + message = AIMessage( + content=[ + {"type": "reasoning_content", "reasoning_content": {"text": "", "signature": ""}}, + {"type": "text", "text": "ok"}, + ] + ) + request = _make_request([message], model=MagicMock(spec=ChatBedrockConverse)) + + SanitizeThinkingBlocksMiddleware().wrap_model_call(request, lambda req: MagicMock()) + + assert message.content == [{"type": "text", "text": "ok"}] + + def test_preserves_non_empty_reasoning_content_block_for_bedrock(self) -> None: + reasoning_block = { + "type": "reasoning_content", + "reasoning_content": {"text": "because", "signature": "sig"}, + } + text_block = {"type": "text", "text": "ok"} + message = AIMessage(content=[reasoning_block, text_block]) + request = _make_request([message], model=MagicMock(spec=ChatBedrockConverse)) + + SanitizeThinkingBlocksMiddleware().wrap_model_call(request, lambda req: MagicMock()) + + assert message.content == [reasoning_block, text_block] + def test_ignores_non_anthropic_models(self) -> None: thinking_block = {"type": "thinking", "signature": "abc", "thinking": ""} message = AIMessage(content=[thinking_block, {"type": "text", "text": "ok"}]) diff --git a/tests/test_slack_context.py b/tests/test_slack_context.py index 5cb1331c..27eb1251 100644 --- a/tests/test_slack_context.py +++ b/tests/test_slack_context.py @@ -710,7 +710,7 @@ def test_process_slack_mention_queues_active_thread_message( monkeypatch.setattr(webapp, "get_valid_access_token", fake_get_valid_access_token) async def fake_resolve_agent_model_id(github_login, per_thread_model_id=None): - return "openai:gpt-5.5" + return "bedrock_converse:us.anthropic.claude-opus-4-8" monkeypatch.setattr(webapp, "resolve_agent_model_id", fake_resolve_agent_model_id) diff --git a/tests/test_team_settings_grouping.py b/tests/test_team_settings_grouping.py index b8c72abd..c7a5f713 100644 --- a/tests/test_team_settings_grouping.py +++ b/tests/test_team_settings_grouping.py @@ -10,8 +10,8 @@ from agent.dashboard.team_settings import ( get_team_default_grouping_model, ) -_REVIEWER_SUBAGENT_PAIR = ("openai:gpt-5.5", "low") -_GROUPING_PAIR = ("google_genai:gemini-3.5-flash", "low") +_REVIEWER_SUBAGENT_PAIR = ("fireworks:accounts/fireworks/models/deepseek-v4-pro", "low") +_GROUPING_PAIR = ("fireworks:accounts/fireworks/models/kimi-k2p7-code", "low") def _settings(**overrides: object) -> dict[str, object]: diff --git a/tests/test_team_settings_org_guidelines.py b/tests/test_team_settings_org_guidelines.py index dd5c35f4..eb1a36b6 100644 --- a/tests/test_team_settings_org_guidelines.py +++ b/tests/test_team_settings_org_guidelines.py @@ -12,8 +12,8 @@ from agent.dashboard.team_settings import ( get_team_default_model, ) -_AGENT_PAIR = ("anthropic:claude-opus-4-8", "high") -_CHAT_PAIR = ("google_genai:gemini-3.5-flash", "low") +_AGENT_PAIR = ("bedrock_converse:us.anthropic.claude-opus-4-8", "high") +_CHAT_PAIR = ("fireworks:accounts/fireworks/models/kimi-k2p7-code", "low") def test_org_guidelines_blank_normalizes_to_none() -> None: diff --git a/uv.lock b/uv.lock index 86d0553a..3a325ca1 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,8 @@ revision = 2 requires-python = ">=3.11" resolution-markers = [ "python_full_version >= '3.14'", - "python_full_version < '3.14'", + "python_full_version >= '3.12' and python_full_version < '3.14'", + "python_full_version < '3.12'", ] [[package]] @@ -251,6 +252,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/95/c1/84fc6811122f54b20de2e5afb312ee07a3a47a328755587d1e505475239b/blockbuster-1.5.26-py3-none-any.whl", hash = "sha256:f8e53fb2dd4b6c6ec2f04907ddbd063ca7cd1ef587d24448ef4e50e81e3a79bb", size = 13226, upload-time = "2025-12-05T10:43:48.778Z" }, ] +[[package]] +name = "boto3" +version = "1.43.36" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/9f/897287e955db0f50b12fd69ef45956e4fd2c7ddb48c736872f7ea2314443/boto3-1.43.36.tar.gz", hash = "sha256:587d7ee92a12e440ad12b0e7f11f3358f0c4d65b19f64726efc94aaf194aff28", size = 112690, upload-time = "2026-06-23T02:47:14.561Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/f1/274303f52483ecf199eae6f8d9b6f5951670397ee4d72c06cfd4eb644612/boto3-1.43.36-py3-none-any.whl", hash = "sha256:42942dde254673abcbc9e6e60017c88341a4f49d99d24e1f2e290fb38138c26f", size = 140031, upload-time = "2026-06-23T02:47:13.178Z" }, +] + +[[package]] +name = "botocore" +version = "1.43.36" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/37/da9e7f6ca73ac73afd7f0bb7f238aa5daba35c081e98d7f48a7c399599c0/botocore-1.43.36.tar.gz", hash = "sha256:4cae47d1b2d426316b85a0087d9e69e048f13bc003b5177d74639fe9dfd28205", size = 15625488, upload-time = "2026-06-23T02:47:03.192Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/19/934f81592527a3f7f9b943c893e334c721a4644948642bc33885d584e9ec/botocore-1.43.36-py3-none-any.whl", hash = "sha256:3c65fdc39ed01d8dfde1e961b34038aed03c459f8ddf80717a12ac006475e49d", size = 15313630, upload-time = "2026-06-23T02:46:59.327Z" }, +] + [[package]] name = "bracex" version = "2.6" @@ -1311,6 +1340,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/da/e9/1f9ada30cef7b05e74bb06f52127e7a724976c225f46adb65c37b1dadfb6/jiter-0.14.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f00d94b281174144d6532a04b66a12cb866cbdc47c3af3bfe2973677f9861a", size = 349613, upload-time = "2026-04-10T14:28:40.066Z" }, ] +[[package]] +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + [[package]] name = "jsonpatch" version = "1.33" @@ -1422,6 +1460,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/af/927dbbc5a1f5fea1a69adc2883f034cbd1430004e36f4eacd302d500393a/langchain_anthropic-1.4.6-py3-none-any.whl", hash = "sha256:dbd412a956b6b8b0716d9d8460ef71f834a6731cdbfc59e6160482a4a9fb5200", size = 51797, upload-time = "2026-06-12T16:54:14.159Z" }, ] +[[package]] +name = "langchain-aws" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boto3" }, + { name = "langchain-core" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/c6/4065908dc2f113324d1ac31ecf5c907a23863f7aeaab7b9361efd7109563/langchain_aws-1.6.1.tar.gz", hash = "sha256:b5b054f48e2697fa1b96733e9de2accfdd1a4948d9b4e3712e8180c4585cfd2f", size = 538265, upload-time = "2026-06-25T19:02:23.364Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/ab/d85b915c12394385459bb536202227d0fd81377929d71b16f177559ba074/langchain_aws-1.6.1-py3-none-any.whl", hash = "sha256:a121f687b36678239dd96ee9d0503a0b7d5fc4570b3af6d19983ef6ebfaf115e", size = 206315, upload-time = "2026-06-25T19:02:21.848Z" }, +] + [[package]] name = "langchain-core" version = "1.4.7" @@ -1923,6 +1977,143 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.12' and python_full_version < '3.14'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/05/3d27272d30698dc0ecb7fdfaa41ad70303b444f81722bb99bce1d818638a/numpy-2.5.0.tar.gz", hash = "sha256:5a129578019311b6e56bdd714250f19b518f7dceeeb8d1af5490f4942d3f891c", size = 20652461, upload-time = "2026-06-21T20:57:51.95Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/0a/11486d02add7b1384dff7374d124b1cfbb0ee864dcc9f6a2c0380638cf84/numpy-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:489780423903667933b4ed6197b6ec3b75ea5dd17d1d8f0f38d798feb6921561", size = 16789987, upload-time = "2026-06-21T20:56:16.657Z" }, + { url = "https://files.pythonhosted.org/packages/55/b2/285f48640a181947b4587a3766d21ec1eaa7fea833d4b49957e09da467a2/numpy-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ece55976ced6bca95a03ae2839e2e5ccffe8eb6a3e7022415645eb154a81e4e6", size = 11760322, upload-time = "2026-06-21T20:56:19.813Z" }, + { url = "https://files.pythonhosted.org/packages/dd/67/b032db1eb03ca30d16eda3b0c22aaa615338b9263c2fd559d0f29451aca4/numpy-2.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c83b664b0e6eee9594fa920cf0639d8af796606d3fad6cc70180c87e4b97c7be", size = 5319605, upload-time = "2026-06-21T20:56:22.173Z" }, + { url = "https://files.pythonhosted.org/packages/b9/83/03fc7300c7c6b6c84c487b1dc80d322817b95fbd1f4dd57a85e23b7198de/numpy-2.5.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bf80333980bf37f523341ddd72c783f39d6829ec7736b9eb99086388a2d52cc2", size = 6653628, upload-time = "2026-06-21T20:56:23.914Z" }, + { url = "https://files.pythonhosted.org/packages/82/49/2ec21730bc63ccfda829323f7040a8ed4715b3852ce658689cf74ee96a8c/numpy-2.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a4874217b36d5ac8fc876f52e39df56f8182c88463e9e2dceabf7ca8b7efb8", size = 15153691, upload-time = "2026-06-21T20:56:25.631Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6b/f4a3d0637692c49da8ef99d72d52526f92e0a8d6ac4f0ca9f31441b9d9ea/numpy-2.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaa760137137e8d3c920d27927748215b56014f92667dc9b6c27dfc61249255a", size = 16660066, upload-time = "2026-06-21T20:56:28.009Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2f/c354ec86d1f3f5c19649463b0d39652e160736e5b0a4cd18dff0576715c4/numpy-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7174ce8265fc7f7417d171c9ea8fe905220748893ea67a2a7abe726ec331c4b0", size = 16514638, upload-time = "2026-06-21T20:56:30.26Z" }, + { url = "https://files.pythonhosted.org/packages/06/34/43efdcb319988648580f93c11f1ae82cf7e2faa74925e98e454ae3aa95f8/numpy-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b8c3daaf99de52415d20b42f8e8155c78642cb04207d02f9d317a0dcf1b3fb54", size = 18419647, upload-time = "2026-06-21T20:56:32.41Z" }, + { url = "https://files.pythonhosted.org/packages/71/e2/f5d1676b1d7fb682eb5e9a1641e7ebd2414b3216c370661d1029778908b4/numpy-2.5.0-cp312-cp312-win32.whl", hash = "sha256:6206db0af545d73d068add6d992279145f158428d1da6cc49adc4b630c5d6ee5", size = 6056688, upload-time = "2026-06-21T20:56:34.657Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/48f115d1c58a34032facebcd51fdf2d02df2c51d4a46a81dd1197bb2ea6b/numpy-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f2d6873e2940c860a309d21e25b1e69af6aaffdd80aa056b04c16380db1c4f2", size = 12419237, upload-time = "2026-06-21T20:56:36.24Z" }, + { url = "https://files.pythonhosted.org/packages/86/26/2e0882f4044d1b1a1b63e875151fb2393389032022a8b7f5657a7996d3b2/numpy-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:a55e1eb2bca2cfd17a16b213c99dfc8502d47b0d494224d2122277d0400935ca", size = 10339912, upload-time = "2026-06-21T20:56:38.733Z" }, + { url = "https://files.pythonhosted.org/packages/8a/33/07675aaad7f26ea013d5e884d9a0d784b79c6bd7566c333f5a52fa3c610b/numpy-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:520e6b8be0a4b65840ac8090d4f51cef4bed66e2b0894d5a520f099adc24a9b2", size = 16784890, upload-time = "2026-06-21T20:56:40.799Z" }, + { url = "https://files.pythonhosted.org/packages/85/4b/953118a730ee3b35e28645e0eb4cf9beec5bdbb954e1ac2f5fcefba6bbc3/numpy-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:146b81cdd3967fdb6beca8ba25f00c58741d8f3cbd797f55af0fbe0bfec3469c", size = 11754584, upload-time = "2026-06-21T20:56:43.094Z" }, + { url = "https://files.pythonhosted.org/packages/44/9b/56dd530c367c74ae17411027cea4135ca57e1e0583bf5594cee18bd83217/numpy-2.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:126b88d95e8ff9b00c9e717aa540469f21d6180162f84c0caec51b16215d49cd", size = 5313904, upload-time = "2026-06-21T20:56:45.503Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b0/bcd672edad27ecca7da1f7bb0ce72cd1706a4f2d79ae94990afc97c13e1c/numpy-2.5.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d4313cef1594c5ce46c31b6e54e918338f63f16ee9322304e8c9114d6d81c8bd", size = 6648504, upload-time = "2026-06-21T20:56:47.567Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/15cdfcbd30a1544a46c9e487a00df331c4672450216538705a9e51fa6710/numpy-2.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:750fb097caf26fa878746d9d119f6f9da12dedcbff1eea966c3e3447647c4a9e", size = 15150086, upload-time = "2026-06-21T20:56:49.352Z" }, + { url = "https://files.pythonhosted.org/packages/32/4e/8d7656ccaab3e81e97258b8a9bc5f0c8502513a92fb4ceb0a2cbfebc17bf/numpy-2.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3893adc2dc7c0412ba76777db55a049215d99c9aa3113003be8f49f4f1290ab9", size = 16647250, upload-time = "2026-06-21T20:56:51.542Z" }, + { url = "https://files.pythonhosted.org/packages/3c/81/97060281b602ed07f21b12f4ec409eac1f75a2f91fbc829ed8b2becf3ad4/numpy-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:835e454dd99b238cdc5a3f63bce2371296f5ebc53ca1e0f8e6ddbb6d92a29aab", size = 16512864, upload-time = "2026-06-21T20:56:55.401Z" }, + { url = "https://files.pythonhosted.org/packages/33/ab/4496208146911f8d8ddb54f68a972aafa6c8d44babcb2ea03b0e5cc87c9d/numpy-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f9836778081a0a3c02a6a21493f3e9f5b311f8d2541934f31f05583dc999ea4", size = 18408407, upload-time = "2026-06-21T20:56:57.75Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9f/a4df67c181e4ee8b467aa3332dc2db10fd5c515136831302f3ca48bc0a01/numpy-2.5.0-cp313-cp313-win32.whl", hash = "sha256:0b525be4744b60bb0557ac872d53ef07d085b5f39622bc579c98d3809d05b988", size = 6054431, upload-time = "2026-06-21T20:57:00.016Z" }, + { url = "https://files.pythonhosted.org/packages/30/53/491e1c47c55b62ccc6a63c1c5b8635c73fc2258dddeb9bda27cae4a0ae96/numpy-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:44353e2878930039db472b99dc353d749826e4010bd4d2a7f835e94a97a5c748", size = 12414420, upload-time = "2026-06-21T20:57:01.815Z" }, + { url = "https://files.pythonhosted.org/packages/eb/4a/25c2906f541e9d9f4c5769764db732e6627be91a13f4724fa10634d77db4/numpy-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:48f54b00711f83a5f796b70c518e8c2b3c5848dda03a54911f23eb68519b9b60", size = 10339533, upload-time = "2026-06-21T20:57:03.961Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/abc44aaceaf7b17ee1edde2bbb4458da591bc79574cffff50c4bb35f00d1/numpy-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f27582c55ba4c750b7c58c8faf021d2cd9324a662b466229db8a417b41368af9", size = 16783807, upload-time = "2026-06-21T20:57:06.253Z" }, + { url = "https://files.pythonhosted.org/packages/5d/39/b72e168daf9c00fb20c9fc996d00437ccecdef3102387775d29d7a62576d/numpy-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:28e7137057d551e4a83c4ae414e3451f50568409db7569aacc7f9811ee06a446", size = 11765215, upload-time = "2026-06-21T20:57:08.547Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a0/8400a9c0e3625182347593f5e1f57da9a617a534794805c8df5518154ddc/numpy-2.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e1da54b53e75cd9fcfc23efcc7edab2c6aecf97b6037566d8a0fe804af8ec57c", size = 5324493, upload-time = "2026-06-21T20:57:11.012Z" }, + { url = "https://files.pythonhosted.org/packages/f6/8c/0d104deaa0401c93395a629ec902891618a2eff76d19229139cb5a887bfc/numpy-2.5.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:694d8f74e156f7fd01179f1aa8faa2f648ab6ae0f70b6c3fe57a03249aea2303", size = 6645211, upload-time = "2026-06-21T20:57:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d9/4a4a628c812750363786afc3d33492709a5cd64b215469c16b0f6c7bb811/numpy-2.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a7569a7b53c77716f036bb28cb1c91f166a26ec7d9502cd1e4bdfe502fdec22", size = 15166004, upload-time = "2026-06-21T20:57:14.717Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5e/2a902317d7fc4aa93236e80c932662dadfc459b323d758329e01775125e1/numpy-2.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39a0433bd4086ebd462960cf375e19195bb07b53dc1d87dd5fcf47ad78576f03", size = 16650797, upload-time = "2026-06-21T20:57:16.906Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a0/a0090e6329f4ca5992c07847bb579c5259a19953dc57255bb08793142ffb/numpy-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:929f0c79ac38bcbd7154fe631dc907abfeddbcc5027a896bd1f7767323271e7a", size = 16524647, upload-time = "2026-06-21T20:57:19.165Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7d/6caf27734c42b65837e7461ed0dbbd6b6fc835060c9714ec59d673bb383a/numpy-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cc4f247a47bbf070bfd70be53ccdcf47b800af563535e7bbe172322197c30e21", size = 18411841, upload-time = "2026-06-21T20:57:21.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/dc/26edadbd812536769a82c2e9e002234e33feb5da43061d47a044f6d309b7/numpy-2.5.0-cp314-cp314-win32.whl", hash = "sha256:5dc71423499fab3f46f7a7201155ade1669ea101f2f429d332df9e72f8161731", size = 6106361, upload-time = "2026-06-21T20:57:23.844Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9e/4dd1459282229a72d92dece2ae9138e5cac94a72263a7ceb48f37434c925/numpy-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:ebb81d9d5443e0309d6c54894c3fbed74ad7da0714352a67b6d773cd189eae73", size = 12551749, upload-time = "2026-06-21T20:57:25.945Z" }, + { url = "https://files.pythonhosted.org/packages/05/a7/6bc6384c080b86c7f6c85c5bc5b540b24f4f679cd144791d99574e90d462/numpy-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3b94d0d0deceebfad3e67ae5c0e5eb87371e8f7a0581cd04a779928c2450cf1e", size = 10617072, upload-time = "2026-06-21T20:57:28.175Z" }, + { url = "https://files.pythonhosted.org/packages/86/6b/4a2b71d66ada5608ae02b63f150dfad520f6940721cb7f029ad270befc0e/numpy-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:22f3d43e362d650bc39db1f17851302874a148ca95ba6981c1dfb5fa6862f35b", size = 11881067, upload-time = "2026-06-21T20:57:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b2/d365eb40a20efb49d67e9feb90494ed8511282ee1f5fa16006675c65397d/numpy-2.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:243563efb4cd7528a264567e9fd206c87826457322521d06206a00bfa316c927", size = 5440290, upload-time = "2026-06-21T20:57:32.193Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5e/e9c03188de5f9b767e46a8fe988bcfd3efad066a4a3fda8b9cb11a93f895/numpy-2.5.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:84881d825ca75249b189bbee875fcfe3238aa5c479e6100893cda566e8e86826", size = 6748371, upload-time = "2026-06-21T20:57:33.933Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1d/68c186a38a5027bae2c4ddd5ea681fdaf8b4d30fb7301def6d8ad270390f/numpy-2.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cda12aa4779d42b8771180aba759c96f527d43446d8f380ab59e2b35e8489efd", size = 15214643, upload-time = "2026-06-21T20:57:35.677Z" }, + { url = "https://files.pythonhosted.org/packages/8c/67/73f67b7c7e20635baae9c4c3ead4ae7326a005900297a6110971abd62eb5/numpy-2.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c0121101093d2bd74981b10f8837d78e794a8ff57834eb27179f49e1ba11ac6", size = 16690128, upload-time = "2026-06-21T20:57:38.159Z" }, + { url = "https://files.pythonhosted.org/packages/eb/05/d4c1fb0c46d02a27d6b2b8b319a78c90937acec8631c1641874670b31e6f/numpy-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d371c92cfa09da00022f501ab67fafaea813d752eb30ac44336d45b1e5b0268a", size = 16577902, upload-time = "2026-06-21T20:57:40.447Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1d/771c797d50fa26e4888989cccf1d50ee51f530d4e455ad2692dcb64fa711/numpy-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9990713e9c38154c6861e7547f1e3fc7a87e75ff09bab24ef1cc81d81c2835e9", size = 18452814, upload-time = "2026-06-21T20:57:42.875Z" }, + { url = "https://files.pythonhosted.org/packages/e8/46/52fc0d2a68d7643f0f149eeea5a5d8ea2a3507056ac8afa83c9212606e8b/numpy-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:edadfbd4794b1086c0d822f81863e8a68fc129d132fd0bb9e31e955d7fbbbdb7", size = 6253168, upload-time = "2026-06-21T20:57:45.101Z" }, + { url = "https://files.pythonhosted.org/packages/2a/be/6c8d1118b5f13b2881dc095d5b345de19c6638b8959c17409b6eff84c8aa/numpy-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f7e5fa4382967ae6548bd2f174219afb908e294b0d5f625af01166edd5f7d9aa", size = 12736286, upload-time = "2026-06-21T20:57:46.935Z" }, + { url = "https://files.pythonhosted.org/packages/fd/6a/d3a169aaf8536cf228d56a09e04bcb713a2fe4410d4e2105b9419b5a9c89/numpy-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:016623417bb330d719d579daf2d6b9a01ddc52e41a9ed61a47f39fde46dcd865", size = 10686451, upload-time = "2026-06-21T20:57:49.313Z" }, +] + [[package]] name = "obstore" version = "0.8.2" @@ -1998,6 +2189,7 @@ dependencies = [ { name = "httpx" }, { name = "langchain" }, { name = "langchain-anthropic" }, + { name = "langchain-aws" }, { name = "langchain-daytona" }, { name = "langchain-fireworks" }, { name = "langchain-google-genai" }, @@ -2032,6 +2224,7 @@ requires-dist = [ { name = "httpx", specifier = ">=0.28.1" }, { name = "langchain", specifier = ">=1.3.9" }, { name = "langchain-anthropic", specifier = ">=1.4.6" }, + { name = "langchain-aws", specifier = ">=0.2.0" }, { name = "langchain-daytona", specifier = ">=0.0.5" }, { name = "langchain-fireworks", specifier = ">=1.4.2" }, { name = "langchain-google-genai", specifier = ">=4.2.4" }, @@ -3104,6 +3297,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b1/47/d8f6b0d5bcf0d4ef9c1894af36d30d5ec275defa4e58dc9c77840377c712/runloop_api_client-1.20.1-py3-none-any.whl", hash = "sha256:b896b63385940bd1083de3256a8b182a3c34419a42389b5c26a2e891972c73f2", size = 380436, upload-time = "2026-05-01T20:13:00.138Z" }, ] +[[package]] +name = "s3transfer" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/94/dcdaeb1713cab9c84def276cfac7388b17c7d9855bbcfe88d77e4dbafd44/s3transfer-0.19.0.tar.gz", hash = "sha256:ce436931687addc4c1712d52d40b32f53e88315723f107ffa20ba82b05a0f685", size = 165171, upload-time = "2026-06-16T19:44:51.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/5f/4c174edad94f82de888ac00a5ddd8d07b35609b6c94f0bdf4d74af57703e/s3transfer-0.19.0-py3-none-any.whl", hash = "sha256:777cc2415536f1debadb5c2ef7779275d0fc0fe0e042411cdd6caebeb2685262", size = 90101, upload-time = "2026-06-16T19:44:50.439Z" }, +] + [[package]] name = "setuptools" version = "82.0.1"